specrails-desktop 2.36.0 → 2.42.2
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/README.md +94 -247
- package/client/dist/assets/ActivityFeedPage-DoEMumQS.js +1 -0
- package/client/dist/assets/AgentBrowserCapture-DIh4K0gh.js +1 -0
- package/client/dist/assets/AgentModeAnalyticsPane-mbq4WEoO.js +2 -0
- package/client/dist/assets/AgentModeCodePane-CKh7zuA4.js +2 -0
- package/client/dist/assets/AgentModeJobsPane-BDokPiLJ.js +1 -0
- package/client/dist/assets/AgentsPage-BFVAZbBj.js +87 -0
- package/client/dist/assets/AnalyticsPage-3Y9LuWwz.js +1 -0
- package/client/dist/assets/CodePage-C9KyuV_C.js +3 -0
- package/client/dist/assets/DesktopAnalyticsPage-Bhi54bXQ.js +1 -0
- package/client/dist/assets/{DocsDialog-C76VdBJC.js → DocsDialog-Dj6mt4ga.js} +2 -2
- package/client/dist/assets/{DocsPage-ziMlj6B6.js → DocsPage-24yWglNI.js} +1 -1
- package/client/dist/assets/ExportDropdown-ophapHrT.js +1 -0
- package/client/dist/assets/InteractiveJobComposer-C1ucmxQh.js +19 -0
- package/client/dist/assets/JobDetailModal-DNWmgVHD.js +1 -0
- package/client/dist/assets/JobDetailPage-BcajZyuu.js +1 -0
- package/client/dist/assets/JobsPage-B9nTZw6e.js +1 -0
- package/client/dist/assets/LoopBuilderPage-Oc48iOFW.js +7 -0
- package/client/dist/assets/LoopPreviewModal-ae0Fr6SU.js +1 -0
- package/client/dist/assets/LoopsPage-CUEqzUBI.js +1 -0
- package/client/dist/assets/MinimizedChatsContext-Deg39peL.js +1 -0
- package/client/dist/assets/PluginsPage-C1xtARIx.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-JZzrXFBN.js +1 -0
- package/client/dist/assets/RepositoryDeliveries-CSDsiVUz.js +5 -0
- package/client/dist/assets/RepositoryScopeSelector-D-RCaknl.js +1 -0
- package/client/dist/assets/ReviewPacketPage-x5EfGTiL.js +1 -0
- package/client/dist/assets/TemplatePreviewModal-BSyuZxGq.js +1 -0
- package/client/dist/assets/TicketDetailModalContext-DqyF0so-.js +14 -0
- package/client/dist/assets/Trans-SnMX4MhW.js +1 -0
- package/client/dist/assets/agent-BdT0SB1D.js +1 -0
- package/client/dist/assets/agent-Bh9K7iEt.js +1 -0
- package/client/dist/assets/agent-C4ibdFk9.js +1 -0
- package/client/dist/assets/agent-CSFY22Zo.js +1 -0
- package/client/dist/assets/agent-DAnqxUn8.js +1 -0
- package/client/dist/assets/agent-DTQiu0Mi.js +1 -0
- package/client/dist/assets/agent-T54WuR_Y.js +1 -0
- package/client/dist/assets/agent-i75WDZOA.js +1 -0
- package/client/dist/assets/browser-05vRDg_y.js +1 -0
- package/client/dist/assets/browser-Bulb-cB1.js +1 -0
- package/client/dist/assets/browser-C7f-Jsg1.js +1 -0
- package/client/dist/assets/browser-CG0PE1j_.js +1 -0
- package/client/dist/assets/browser-CMzaqAjo.js +1 -0
- package/client/dist/assets/browser-DMajYdNX.js +1 -0
- package/client/dist/assets/browser-DxcOHrdP.js +1 -0
- package/client/dist/assets/browser-ObE7xVo3.js +1 -0
- package/client/dist/assets/builder-765NeYTp.js +1 -0
- package/client/dist/assets/builder-8MG3V6ea.js +1 -0
- package/client/dist/assets/builder-B-1UBs5K.js +1 -0
- package/client/dist/assets/builder-BTiGnPT0.js +1 -0
- package/client/dist/assets/builder-Cx9Rz-7u.js +1 -0
- package/client/dist/assets/builder-DKFMELBl.js +1 -0
- package/client/dist/assets/builder-OcmKrC0h.js +1 -0
- package/client/dist/assets/builder-P9qVBXFZ.js +1 -0
- package/client/dist/assets/{button-CJqFnUOL.js → button-DCBqEaZl.js} +1 -1
- package/client/dist/assets/{code-CkUuW4m9.js → code-B37GRg9C.js} +1 -1
- package/client/dist/assets/{code-B0O8-p8k.js → code-BBOEZbf9.js} +1 -1
- package/client/dist/assets/{code-B1VLZDhS.js → code-BRm-jcyx.js} +1 -1
- package/client/dist/assets/{code-Bpj4Ddml.js → code-CF7w62CR.js} +1 -1
- package/client/dist/assets/{code-BykBZ5-e.js → code-CLuvJt2r.js} +1 -1
- package/client/dist/assets/{code-B86IMAfz.js → code-D0RCm59f.js} +1 -1
- package/client/dist/assets/code-DaZ-ZwCT.js +1 -0
- package/client/dist/assets/{code-cAqaqMTx.js → code-iIHkGaCL.js} +1 -1
- package/client/dist/assets/commands-0XjAftla.js +1 -0
- package/client/dist/assets/commands-BAWSYHjQ.js +1 -0
- package/client/dist/assets/commands-BBwowgiE.js +1 -0
- package/client/dist/assets/commands-BacjIZLN.js +1 -0
- package/client/dist/assets/commands-Bs62IAZ3.js +1 -0
- package/client/dist/assets/commands-CLPdh-Hh.js +1 -0
- package/client/dist/assets/commands-DlbFzgrp.js +1 -0
- package/client/dist/assets/commands-LjPSsi5F.js +1 -0
- package/client/dist/assets/common-B5AZNYMH.js +1 -0
- package/client/dist/assets/common-BFZ0c_vN.js +1 -0
- package/client/dist/assets/common-BrlwEnL7.js +1 -0
- package/client/dist/assets/common-BttQx3z4.js +1 -0
- package/client/dist/assets/common-CM65k_-e.js +1 -0
- package/client/dist/assets/common-D4mvFFlu.js +1 -0
- package/client/dist/assets/common-TZ9Ny96S.js +1 -0
- package/client/dist/assets/common-nFpSF4GC.js +1 -0
- package/client/dist/assets/dashboard-B14uYXkG.js +1 -0
- package/client/dist/assets/dashboard-BK_mez-z.js +1 -0
- package/client/dist/assets/dashboard-BfBir_6o.js +1 -0
- package/client/dist/assets/dashboard-Bt0cCzeG.js +1 -0
- package/client/dist/assets/dashboard-BwkjxI6H.js +1 -0
- package/client/dist/assets/{dashboard-C8WOEiPu.js → dashboard-C3bDKf7s.js} +1 -1
- package/client/dist/assets/dashboard-CdvjGfog.js +1 -0
- package/client/dist/assets/dashboard-fRrP0Kv0.js +1 -0
- package/client/dist/assets/dialog-SRw01_QU.js +45 -0
- package/client/dist/assets/dist-js-CJ_2XX1Y.js +1 -0
- package/client/dist/assets/formatDistanceToNow-DSj5DprF.js +1 -0
- package/client/dist/assets/getTimezoneOffsetInMilliseconds-YhD8rIEs.js +1 -0
- package/client/dist/assets/index-DwAmL7m5.css +2 -0
- package/client/dist/assets/index-EzrkUb8B.js +76 -0
- package/client/dist/assets/{jira-api-B1-MUehg.js → jira-api-CSCqGr-Q.js} +1 -1
- package/client/dist/assets/{jobs-CN_zv4Gr.js → jobs-B8rrzfyZ.js} +1 -1
- package/client/dist/assets/{jobs-DUlWSYq2.js → jobs-BS6RTurf.js} +1 -1
- package/client/dist/assets/{jobs-Bpks6doU.js → jobs-BTT0Bsks.js} +1 -1
- package/client/dist/assets/{jobs-Bgq-PZkC.js → jobs-CezU69AE.js} +1 -1
- package/client/dist/assets/{jobs-SwYS2M-B.js → jobs-ClFJae0s.js} +1 -1
- package/client/dist/assets/{jobs-BAls3bWq.js → jobs-DJSFIFVy.js} +1 -1
- package/client/dist/assets/{jobs-DhxKgWFq.js → jobs-DhgGr8vm.js} +1 -1
- package/client/dist/assets/{jobs-B_TeSQxb.js → jobs-DzMjUcaN.js} +1 -1
- package/client/dist/assets/mcp-6rMoZfEO.js +1 -0
- package/client/dist/assets/mcp-BD8sL5mP.js +1 -0
- package/client/dist/assets/mcp-BkcA3I5s.js +1 -0
- package/client/dist/assets/mcp-CCpSelOh.js +1 -0
- package/client/dist/assets/mcp-CUAN6ORK.js +1 -0
- package/client/dist/assets/mcp-CqbRxRTE.js +1 -0
- package/client/dist/assets/mcp-DP2YfVhO.js +1 -0
- package/client/dist/assets/mcp-DUVQamDz.js +1 -0
- package/client/dist/assets/narration-B-ynWTpn.js +1 -0
- package/client/dist/assets/narration-Bj_laZwg.js +1 -0
- package/client/dist/assets/narration-BxHpmMv_.js +1 -0
- package/client/dist/assets/narration-C1Px0-0j.js +1 -0
- package/client/dist/assets/narration-CAXhGbzM.js +1 -0
- package/client/dist/assets/narration-DQ1l_Kq6.js +1 -0
- package/client/dist/assets/narration-XISXwqZd.js +1 -0
- package/client/dist/assets/narration-kwzfglQo.js +1 -0
- package/client/dist/assets/nav-1-Hh2kOz.js +1 -0
- package/client/dist/assets/nav-B34QkESm.js +1 -0
- package/client/dist/assets/nav-Bap_UXAq.js +1 -0
- package/client/dist/assets/{nav-pYuXq7sf.js → nav-BwxXIB24.js} +1 -1
- package/client/dist/assets/nav-Ch-zHBG5.js +1 -0
- package/client/dist/assets/nav-CygC_WN-.js +1 -0
- package/client/dist/assets/{nav-iwjCg5FR.js → nav-KnnLQtOQ.js} +1 -1
- package/client/dist/assets/nav-m00uxYUG.js +1 -0
- package/client/dist/assets/packet-BQ2tXfJI.js +1 -0
- package/client/dist/assets/packet-BdpOJ1Cy.js +1 -0
- package/client/dist/assets/packet-C3Ievb9R.js +1 -0
- package/client/dist/assets/packet-C4zJIG0Y.js +1 -0
- package/client/dist/assets/packet-Cx-zRuWn.js +1 -0
- package/client/dist/assets/packet-DUTGX52M.js +1 -0
- package/client/dist/assets/packet-DhsKtUPB.js +1 -0
- package/client/dist/assets/packet-z3LJ8Zyy.js +1 -0
- package/client/dist/assets/project-repositories-DYhTvwQ1.js +1 -0
- package/client/dist/assets/provider-capabilities-B-wOHfLP.js +1 -0
- package/client/dist/assets/settings-BBt2xtDQ.js +1 -0
- package/client/dist/assets/settings-BJB9RKJj.js +1 -0
- package/client/dist/assets/settings-CHwQR2rB.js +1 -0
- package/client/dist/assets/settings-CrMi3kSi.js +1 -0
- package/client/dist/assets/settings-DKyhDGD1.js +1 -0
- package/client/dist/assets/settings-Deupji9c.js +1 -0
- package/client/dist/assets/settings-DqRMmwAM.js +1 -0
- package/client/dist/assets/settings-J4rj6vo-.js +1 -0
- package/client/dist/assets/setup-2xGgu4Sk.js +1 -0
- package/client/dist/assets/setup-Ba11l2JA.js +1 -0
- package/client/dist/assets/setup-BzW4kMok.js +1 -0
- package/client/dist/assets/setup-COzwYhkJ.js +1 -0
- package/client/dist/assets/setup-CRVULhtF.js +1 -0
- package/client/dist/assets/setup-DgvlxwpE.js +1 -0
- package/client/dist/assets/setup-Dw_wYeEY.js +1 -0
- package/client/dist/assets/setup-ZPtEeJPW.js +1 -0
- package/client/dist/assets/spending-BqbdPm_4.js +1 -0
- package/client/dist/assets/square-Ci-PFcFe.js +1 -0
- package/client/dist/assets/{terminal-BFRZnMPP.js → terminal-BAzpHtpY.js} +1 -1
- package/client/dist/assets/{terminal-IahE4LW9.js → terminal-C7dHeR3x.js} +1 -1
- package/client/dist/assets/{terminal-tITa81iz.js → terminal-CMla5Qsz.js} +1 -1
- package/client/dist/assets/{terminal-BZiIu3rf.js → terminal-CdNXH-n9.js} +1 -1
- package/client/dist/assets/{terminal-ScjyXNxQ.js → terminal-Cw5bZsmV.js} +1 -1
- package/client/dist/assets/{terminal-C2NAd7BW.js → terminal-DFUP4eNv.js} +1 -1
- package/client/dist/assets/{terminal-d7GadXbN.js → terminal-DGqljeK_.js} +1 -1
- package/client/dist/assets/{terminal-QpXZU2Bn.js → terminal-cU70NodO.js} +1 -1
- package/client/dist/assets/useDesktop-RSMTyy7V.js +1 -0
- package/client/dist/assets/useSharedWebSocket-BOdS5_Iz.js +2 -0
- package/client/dist/index.html +34 -35
- package/docs/README.md +3 -0
- package/docs/agent-live-steering.md +57 -0
- package/docs/ci-cd.md +57 -0
- package/docs/code-explorer.md +48 -0
- package/docs/codex.md +12 -1
- package/docs/features/detachable-mission-windows.md +59 -0
- package/docs/guide/de/integrations/5-mcp-server.md +13 -9
- package/docs/guide/de/integrations/6-agent-chat.md +1 -0
- package/docs/guide/en/integrations/5-mcp-server.md +13 -9
- package/docs/guide/en/integrations/6-agent-chat.md +1 -0
- package/docs/guide/es/integrations/5-mcp-server.md +13 -9
- package/docs/guide/es/integrations/6-agent-chat.md +1 -0
- package/docs/guide/fr/integrations/5-mcp-server.md +13 -9
- package/docs/guide/fr/integrations/6-agent-chat.md +1 -0
- package/docs/guide/it/integrations/5-mcp-server.md +13 -9
- package/docs/guide/it/integrations/6-agent-chat.md +1 -0
- package/docs/guide/ja/integrations/5-mcp-server.md +13 -9
- package/docs/guide/ja/integrations/6-agent-chat.md +1 -0
- package/docs/guide/pt/integrations/5-mcp-server.md +13 -9
- package/docs/guide/pt/integrations/6-agent-chat.md +1 -0
- package/docs/guide/zh/integrations/5-mcp-server.md +13 -9
- package/docs/guide/zh/integrations/6-agent-chat.md +1 -0
- package/docs/internals/README.md +1 -0
- package/docs/internals/browser-capture-performance.md +55 -3
- package/docs/internals/browser-login-popups.md +45 -0
- package/docs/internals/browser-native-retina-audit.md +39 -0
- package/docs/internals/ci-cd-audit.md +39 -0
- package/docs/internals/core-runtime-updates.md +68 -0
- package/docs/internals/embedded-browser-native-webview-evaluation.md +2 -0
- package/docs/internals/implementation-reliability-audit.md +93 -0
- package/docs/internals/interactive-jobs.md +110 -6
- package/docs/internals/mcp-mission-audit.md +76 -0
- package/docs/internals/project-builder.md +404 -42
- package/docs/internals/safe-pr-review-flow.md +52 -10
- package/docs/internals/startup-recovery-audit.md +58 -0
- package/docs/mcp.md +74 -41
- package/docs/mission-processes.md +59 -0
- package/docs/multi-repo-projects.md +51 -0
- package/docs/platforms/windows-parity.md +70 -0
- package/docs/platforms/windows.md +13 -11
- package/docs/running-pipelines.md +14 -0
- package/mcp-bridge/dist/specrails-mcp.js +7186 -0
- package/package.json +9 -4
- package/server/dist/agent-chat-manager.js +537 -68
- package/server/dist/agent-chat-router.js +87 -6
- package/server/dist/agent-context-resolver.js +129 -25
- package/server/dist/agent-defaults.js +19 -11
- package/server/dist/agent-input-store.js +206 -0
- package/server/dist/agent-mcp-config.js +54 -15
- package/server/dist/agent-operator-prompt.js +255 -65
- package/server/dist/agent-spec-framing.js +202 -0
- package/server/dist/agent-steering.js +218 -0
- package/server/dist/agent-store.js +153 -0
- package/server/dist/api-not-found.js +8 -0
- package/server/dist/background-process-control.js +164 -0
- package/server/dist/background-process-service.js +61 -0
- package/server/dist/background-process-store.js +242 -0
- package/server/dist/background-windows-bootstrap.js +238 -0
- package/server/dist/blueprint-chat-manager.js +476 -62
- package/server/dist/blueprint-commit.js +21 -1
- package/server/dist/blueprint-draft-parser.js +122 -17
- package/server/dist/blueprint-generation.js +238 -0
- package/server/dist/blueprint-operator-prompt.js +91 -63
- package/server/dist/blueprint-router.js +55 -3
- package/server/dist/blueprint-spec-fixtures.js +85 -0
- package/server/dist/blueprint-spec-quality.js +57 -16
- package/server/dist/blueprint-store.js +92 -1
- package/server/dist/browser-capture-manager.js +86 -30
- package/server/dist/browser-playwright.js +168 -45
- package/server/dist/browser-viewport.js +26 -0
- package/server/dist/chat-manager.js +23 -15
- package/server/dist/chromium-archive.cjs +83 -0
- package/server/dist/chromium-resolver.js +72 -64
- package/server/dist/code-activity.js +145 -0
- package/server/dist/code-explorer-router.js +714 -114
- package/server/dist/core-compat.js +28 -1
- package/server/dist/core-execution.js +111 -0
- package/server/dist/core-package.js +4 -16
- package/server/dist/core-runtime.js +202 -0
- package/server/dist/core-update-manager.js +161 -60
- package/server/dist/core-update-state.js +29 -0
- package/server/dist/db.js +66 -35
- package/server/dist/delivery-evidence.js +142 -39
- package/server/dist/desktop-db.js +239 -43
- package/server/dist/desktop-router.js +109 -9
- package/server/dist/dev-ports.js +8 -3
- package/server/dist/file-provenance.js +34 -22
- package/server/dist/file-story-manager.js +62 -64
- package/server/dist/file-story.js +118 -35
- package/server/dist/file-summary-generator.js +46 -5
- package/server/dist/file-summary-manager.js +355 -106
- package/server/dist/framework-manager.js +98 -110
- package/server/dist/framework-reseed.js +76 -10
- package/server/dist/git-diagnostics.js +2 -2
- package/server/dist/headroom-manager.js +38 -3
- package/server/dist/host-control.js +52 -0
- package/server/dist/index.js +43 -2
- package/server/dist/interactive-job-session.js +149 -16
- package/server/dist/internal-api.js +48 -0
- package/server/dist/jira/jira-materializer.js +1 -0
- package/server/dist/json-tolerant.js +174 -0
- package/server/dist/legacy-migration.js +4 -2
- package/server/dist/loop-command-catalog.js +83 -21
- package/server/dist/loop-constants.js +6 -1
- package/server/dist/loop-decider.js +11 -1
- package/server/dist/loop-executors.js +194 -45
- package/server/dist/loop-factory.js +11 -4
- package/server/dist/loop-graph.js +90 -4
- package/server/dist/loop-run-manager.js +301 -69
- package/server/dist/loop-shell-invocation.js +21 -0
- package/server/dist/loop-step-idle.js +55 -0
- package/server/dist/loop-templates.js +31 -7
- package/server/dist/mcp/agent-capability.js +37 -4
- package/server/dist/mcp/guide.js +149 -36
- package/server/dist/mcp/mcp-admin-router.js +20 -4
- package/server/dist/mcp/mcp-server.js +89 -43
- package/server/dist/mcp/mcp-tiers.js +6 -5
- package/server/dist/mcp/mcp-token.js +3 -1
- package/server/dist/mcp/resources.js +5 -4
- package/server/dist/mcp/tools/catalog.js +4 -0
- package/server/dist/mcp/tools/code.js +125 -14
- package/server/dist/mcp/tools/context.js +153 -0
- package/server/dist/mcp/tools/git.js +15 -5
- package/server/dist/mcp/tools/jobs.js +84 -71
- package/server/dist/mcp/tools/loops.js +17 -6
- package/server/dist/mcp/tools/meta.js +74 -27
- package/server/dist/mcp/tools/mission.js +29 -0
- package/server/dist/mcp/tools/projects.js +22 -6
- package/server/dist/mcp/tools/rails.js +48 -11
- package/server/dist/mcp/tools/specs.js +86 -14
- package/server/dist/mcp/tools/support.js +1 -1
- package/server/dist/mcp/tools/types.js +117 -43
- package/server/dist/mcp/tools/watch.js +144 -51
- package/server/dist/milestone-chain-store.js +164 -0
- package/server/dist/milestone-chain.js +487 -0
- package/server/dist/milestone-progress.js +348 -0
- package/server/dist/mobile/mobile-devices.js +10 -7
- package/server/dist/mobile/mobile-gateway.js +2 -0
- package/server/dist/mobile/mobile-missions.js +214 -0
- package/server/dist/mobile/mobile-redact.js +17 -3
- package/server/dist/mobile/mobile-router.js +46 -4
- package/server/dist/mobile/mobile-ws.js +49 -4
- package/server/dist/multi-repo-bases.js +56 -0
- package/server/dist/multi-repo-checkout.js +101 -0
- package/server/dist/multi-repo-delivery.js +100 -0
- package/server/dist/multi-repo-execution-store.js +65 -0
- package/server/dist/multi-repo-execution.js +453 -0
- package/server/dist/offline-assemble.js +10 -8
- package/server/dist/openspec-runtime-plugin-commands.json +18 -0
- package/server/dist/openspec-runtime-plugin.js +120 -0
- package/server/dist/plugins/codex-spawn.js +49 -0
- package/server/dist/plugins/serena/manifest.js +2 -2
- package/server/dist/plugins/serena/templates/instructions.md +16 -0
- package/server/dist/pr-publisher.js +1 -1
- package/server/dist/profiles-router.js +3 -18
- package/server/dist/project-code-discovery.js +142 -0
- package/server/dist/project-git.js +19 -21
- package/server/dist/project-registry.js +272 -20
- package/server/dist/project-repositories.js +157 -0
- package/server/dist/project-repository-provenance.js +31 -0
- package/server/dist/project-router-background-processes.js +105 -0
- package/server/dist/project-router-git.js +75 -16
- package/server/dist/project-router-jobs.js +2 -126
- package/server/dist/project-router-loop-runs.js +72 -2
- package/server/dist/project-router-tickets.js +53 -8
- package/server/dist/project-router.js +239 -38
- package/server/dist/provider-detection.js +18 -6
- package/server/dist/provider-limit.js +64 -0
- package/server/dist/providers/claude-adapter.js +26 -1
- package/server/dist/providers/claude-background-tasks.js +18 -0
- package/server/dist/providers/claude-live-session.js +383 -0
- package/server/dist/providers/codex-adapter.js +19 -0
- package/server/dist/providers/codex-live-session.js +545 -0
- package/server/dist/providers/gemini-adapter.js +1 -0
- package/server/dist/providers/live-session-types.js +12 -0
- package/server/dist/providers/live-session.js +13 -0
- package/server/dist/providers/terminal-result.js +24 -0
- package/server/dist/queue-manager.js +2 -0
- package/server/dist/rail-isolated-launch.js +141 -16
- package/server/dist/rail-pr-decision.js +364 -99
- package/server/dist/rail-pr-store.js +24 -10
- package/server/dist/rail-worktree-release.js +34 -2
- package/server/dist/rail-worktrees-store.js +4 -4
- package/server/dist/rails-router.js +249 -55
- package/server/dist/repo-lock.js +92 -17
- package/server/dist/review-packet.js +23 -13
- package/server/dist/schemas/file-summary.v1.json +59 -59
- package/server/dist/schemas/profile.v1.json +137 -137
- package/server/dist/setup-manager.js +52 -8
- package/server/dist/shell-integration/bash-shim.bash +45 -0
- package/server/dist/shell-integration/fish-shim.fish +36 -0
- package/server/dist/shell-integration/powershell-shim.ps1 +48 -0
- package/server/dist/shell-integration/zsh-shim.zsh +47 -0
- package/server/dist/smash-runner.js +1 -0
- package/server/dist/spawn-lifecycle.js +44 -27
- package/server/dist/spec-contract-prompt.js +143 -0
- package/server/dist/spec-draft-parser.js +6 -1
- package/server/dist/spec-models.js +1 -0
- package/server/dist/stuck-run-detector.js +1 -0
- package/server/dist/terminal-manager.js +21 -1
- package/server/dist/terminal-shell-integration.js +4 -1
- package/server/dist/transient-children.js +619 -230
- package/server/dist/util/cli-prompt.js +78 -5
- package/server/dist/util/sqlite-migrations.js +20 -0
- package/server/dist/util/win-spawn.js +18 -3
- package/server/dist/util/windows-provider-shell.js +29 -0
- package/server/dist/verification-sentinel.js +14 -0
- package/server/dist/vitest-setup.js +2 -0
- package/server/dist/windows-job-supervisor.js +133 -0
- package/server/dist/workspace-manager.js +9 -30
- package/server/dist/worktree-manager.js +17 -2
- package/server/dist/worktree-overlay.js +3 -1
- package/client/dist/assets/ActivityFeedPage-tAQcWs1R.js +0 -1
- package/client/dist/assets/AgentBrowserCapture-BX8BNae5.js +0 -1
- package/client/dist/assets/AgentModeAnalyticsPane-BVAqCL0K.js +0 -2
- package/client/dist/assets/AgentModeCodePane-9XfqQc9c.js +0 -2
- package/client/dist/assets/AgentModeJobsPane-BCjLsPhc.js +0 -1
- package/client/dist/assets/AgentsPage-C8J_42sm.js +0 -87
- package/client/dist/assets/AnalyticsPage-BtQuyfJm.js +0 -1
- package/client/dist/assets/CodePage-BZ2MwdCu.js +0 -2
- package/client/dist/assets/DesktopAnalyticsPage-Bh_29V4g.js +0 -1
- package/client/dist/assets/ExportDropdown-BrtfWzEG.js +0 -1
- package/client/dist/assets/InteractiveJobComposer-BCZiSfzc.js +0 -19
- package/client/dist/assets/JobDetailModal-NAJmuYIT.js +0 -1
- package/client/dist/assets/JobDetailPage-fE0ZycUX.js +0 -1
- package/client/dist/assets/JobsPage-z5M_HVJG.js +0 -1
- package/client/dist/assets/LoopBuilderPage-j1FQS47c.js +0 -7
- package/client/dist/assets/LoopPreviewModal-DhDJjdCe.js +0 -1
- package/client/dist/assets/LoopsPage-Ll7zORZb.js +0 -1
- package/client/dist/assets/MinimizedChatsContext-Bd3OqR_S.js +0 -1
- package/client/dist/assets/PluginsPage-DN4kGvJt.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-OgsgxjzF.js +0 -1
- package/client/dist/assets/ReviewPacketPage-DFog4sXn.js +0 -1
- package/client/dist/assets/TemplatePreviewModal-L2XxqiOD.js +0 -1
- package/client/dist/assets/TicketDetailModal-CsF-AWZX.js +0 -14
- package/client/dist/assets/Trans-Bsz9_gyq.js +0 -1
- package/client/dist/assets/agent-BQgKv_2F.js +0 -1
- package/client/dist/assets/agent-C1NRDJqL.js +0 -1
- package/client/dist/assets/agent-CLNleiy6.js +0 -1
- package/client/dist/assets/agent-CmceArkQ.js +0 -1
- package/client/dist/assets/agent-D3UxQ3SR.js +0 -1
- package/client/dist/assets/agent-DB1JeE64.js +0 -1
- package/client/dist/assets/agent-DXkHcQ-D.js +0 -1
- package/client/dist/assets/agent-DjJJJ2M8.js +0 -1
- package/client/dist/assets/auth-D_5bQv5W.js +0 -1
- package/client/dist/assets/ban-E4EaZe3a.js +0 -1
- package/client/dist/assets/bot-DzuuuC5R.js +0 -1
- package/client/dist/assets/browser-BgqHDMXP.js +0 -1
- package/client/dist/assets/browser-CsKJtrpv.js +0 -1
- package/client/dist/assets/browser-Cu58EpZU.js +0 -1
- package/client/dist/assets/browser-CvXOJm-j.js +0 -1
- package/client/dist/assets/browser-DOrFh1c7.js +0 -1
- package/client/dist/assets/browser-DQv_Jo7x.js +0 -1
- package/client/dist/assets/browser-LPgxvJu2.js +0 -1
- package/client/dist/assets/browser-eL-3MIEI.js +0 -1
- package/client/dist/assets/builder-0IWtosUm.js +0 -1
- package/client/dist/assets/builder-Bf2N6XWn.js +0 -1
- package/client/dist/assets/builder-BnqaaBkT.js +0 -1
- package/client/dist/assets/builder-BxxfGgAO.js +0 -1
- package/client/dist/assets/builder-C5IIgWsq.js +0 -1
- package/client/dist/assets/builder-CHuahItu.js +0 -1
- package/client/dist/assets/builder-CjWosXL_.js +0 -1
- package/client/dist/assets/builder-CpCIOoKj.js +0 -1
- package/client/dist/assets/code-CHPkOkDS.js +0 -1
- package/client/dist/assets/commands-6tYQih5m.js +0 -1
- package/client/dist/assets/commands-BK2QinXN.js +0 -1
- package/client/dist/assets/commands-C2Nca76h.js +0 -1
- package/client/dist/assets/commands-CKdLihNX.js +0 -1
- package/client/dist/assets/commands-CYIBTAlc.js +0 -1
- package/client/dist/assets/commands-CjUtYJIM.js +0 -1
- package/client/dist/assets/commands-Cq_u_8y9.js +0 -1
- package/client/dist/assets/commands-D_ZHYQug.js +0 -1
- package/client/dist/assets/common-6MLbD-jM.js +0 -1
- package/client/dist/assets/common-BKGEu4rm.js +0 -1
- package/client/dist/assets/common-BNbnv8cE.js +0 -1
- package/client/dist/assets/common-BqGWtDbt.js +0 -1
- package/client/dist/assets/common-DBtTzeYm.js +0 -1
- package/client/dist/assets/common-DEW3Nr8n.js +0 -1
- package/client/dist/assets/common-DHsv9N5d.js +0 -1
- package/client/dist/assets/common-mkrEcW-Y.js +0 -1
- package/client/dist/assets/dashboard-B9GhC5ku.js +0 -1
- package/client/dist/assets/dashboard-CBnv6zOI.js +0 -1
- package/client/dist/assets/dashboard-CaP0fU87.js +0 -1
- package/client/dist/assets/dashboard-D4ZZ4x84.js +0 -1
- package/client/dist/assets/dashboard-DmuTQBX8.js +0 -1
- package/client/dist/assets/dashboard-DynLYj3d.js +0 -1
- package/client/dist/assets/dashboard-zj4gGFJ1.js +0 -1
- package/client/dist/assets/dialog-CdWCb02j.js +0 -45
- package/client/dist/assets/dist-bVqfYmby.js +0 -1
- package/client/dist/assets/dist-js-CWeUdf5Z.js +0 -1
- package/client/dist/assets/formatDistanceToNow-DQCvMgUc.js +0 -1
- package/client/dist/assets/getTimezoneOffsetInMilliseconds-C16cxSe1.js +0 -1
- package/client/dist/assets/git-refresh-wTW0rO0Z.js +0 -1
- package/client/dist/assets/i18n-hnX2DPmo.js +0 -2
- package/client/dist/assets/index-DIhMEAVw.js +0 -66
- package/client/dist/assets/index-DwygcU7s.css +0 -2
- package/client/dist/assets/mcp-B8X8wBLu.js +0 -1
- package/client/dist/assets/mcp-C2AojZZN.js +0 -1
- package/client/dist/assets/mcp-D8BT78_L.js +0 -1
- package/client/dist/assets/mcp-Df5RWBNk.js +0 -1
- package/client/dist/assets/mcp-Dj7BgMQW.js +0 -1
- package/client/dist/assets/mcp-Q7iT7rcz.js +0 -1
- package/client/dist/assets/mcp-YlvNvJbW.js +0 -1
- package/client/dist/assets/mcp-mKFpTSRb.js +0 -1
- package/client/dist/assets/narration-4Z2VUhZw.js +0 -1
- package/client/dist/assets/narration-BGuiEDj3.js +0 -1
- package/client/dist/assets/narration-BuSoqRmv.js +0 -1
- package/client/dist/assets/narration-CB9t29fy.js +0 -1
- package/client/dist/assets/narration-CU3x8FHD.js +0 -1
- package/client/dist/assets/narration-CWeXhfP5.js +0 -1
- package/client/dist/assets/narration-DRaDnhT5.js +0 -1
- package/client/dist/assets/narration-DtEbQgCw.js +0 -1
- package/client/dist/assets/nav-ApJVg3xu.js +0 -1
- package/client/dist/assets/nav-BKNky1Bg.js +0 -1
- package/client/dist/assets/nav-C6vWWKYx.js +0 -1
- package/client/dist/assets/nav-CXJ6Aw89.js +0 -1
- package/client/dist/assets/nav-CsmFBIDf.js +0 -1
- package/client/dist/assets/nav-DjkmQJjd.js +0 -1
- package/client/dist/assets/packet-B1IxaL5f.js +0 -1
- package/client/dist/assets/packet-Bh5kkbiB.js +0 -1
- package/client/dist/assets/packet-By8fqYxW.js +0 -1
- package/client/dist/assets/packet-CZdvA44E.js +0 -1
- package/client/dist/assets/packet-CrjHRQCP.js +0 -1
- package/client/dist/assets/packet-Dwi2pTtx.js +0 -1
- package/client/dist/assets/packet-_-ZIa0IR.js +0 -1
- package/client/dist/assets/packet-n0W6E1Hn.js +0 -1
- package/client/dist/assets/play-DNlrawbS.js +0 -1
- package/client/dist/assets/provider-capabilities-Doej9lfa.js +0 -1
- package/client/dist/assets/settings-Bd0x4TBa.js +0 -1
- package/client/dist/assets/settings-C1-7cXzC.js +0 -1
- package/client/dist/assets/settings-CAFaT38b.js +0 -1
- package/client/dist/assets/settings-Ct3v1nW2.js +0 -1
- package/client/dist/assets/settings-DHh7_IF5.js +0 -1
- package/client/dist/assets/settings-DQXAmdcv.js +0 -1
- package/client/dist/assets/settings-guzlhpup.js +0 -1
- package/client/dist/assets/settings-sZfTPMEH.js +0 -1
- package/client/dist/assets/setup-3jWaoVUO.js +0 -1
- package/client/dist/assets/setup-BjgRneb2.js +0 -1
- package/client/dist/assets/setup-BzFIjv-f.js +0 -1
- package/client/dist/assets/setup-C4VfQsxE.js +0 -1
- package/client/dist/assets/setup-DfUkpDbN.js +0 -1
- package/client/dist/assets/setup-ECe90Pmu.js +0 -1
- package/client/dist/assets/setup-dlqC4Jmm.js +0 -1
- package/client/dist/assets/setup-jwfLMiM2.js +0 -1
- package/client/dist/assets/spending-EC9xqio-.js +0 -1
- package/client/dist/assets/useDesktop-BQVt8lFd.js +0 -1
|
@@ -14,6 +14,7 @@ const explore_cwd_manager_1 = require("./explore-cwd-manager");
|
|
|
14
14
|
const ai_invocations_1 = require("./ai-invocations");
|
|
15
15
|
const result_event_1 = require("./result-event");
|
|
16
16
|
const crypto_1 = require("crypto");
|
|
17
|
+
const codex_spawn_1 = require("./plugins/codex-spawn");
|
|
17
18
|
const spec_draft_parser_1 = require("./spec-draft-parser");
|
|
18
19
|
const attachment_manager_1 = require("./attachment-manager");
|
|
19
20
|
const providers_1 = require("./providers");
|
|
@@ -25,6 +26,7 @@ const explore_stdin_session_1 = require("./explore-stdin-session");
|
|
|
25
26
|
const workspace_resolution_1 = require("./workspace-resolution");
|
|
26
27
|
const workspace_manager_1 = require("./workspace-manager");
|
|
27
28
|
const blueprint_render_1 = require("./blueprint-render");
|
|
29
|
+
const spec_contract_prompt_1 = require("./spec-contract-prompt");
|
|
28
30
|
const explore_draft_title_1 = require("./explore-draft-title");
|
|
29
31
|
const COMMAND_INSTRUCTION = 'When you want to suggest a SpecRails command for the user to execute, wrap it in a command block like this: ' +
|
|
30
32
|
':::command\n/specrails:implement #42\n::: ' +
|
|
@@ -133,6 +135,7 @@ exports.EXPLORE_MAX_CONCURRENCY = 5;
|
|
|
133
135
|
exports.EXPLORE_QUEUE_TIMEOUT_MS = 30 * 1000;
|
|
134
136
|
// ─── ChatManager ──────────────────────────────────────────────────────────────
|
|
135
137
|
class ChatManager {
|
|
138
|
+
projectRepositories;
|
|
136
139
|
_broadcast;
|
|
137
140
|
_db;
|
|
138
141
|
_activeProcesses;
|
|
@@ -190,7 +193,8 @@ class ChatManager {
|
|
|
190
193
|
_adapter;
|
|
191
194
|
_projectId;
|
|
192
195
|
_projectSlug;
|
|
193
|
-
constructor(broadcast, db, cwd, projectName, provider, projectId, projectSlug) {
|
|
196
|
+
constructor(broadcast, db, cwd, projectName, provider, projectId, projectSlug, projectRepositories) {
|
|
197
|
+
this.projectRepositories = projectRepositories;
|
|
194
198
|
this._broadcast = broadcast;
|
|
195
199
|
this._db = db;
|
|
196
200
|
this._cwd = cwd;
|
|
@@ -713,26 +717,21 @@ class ChatManager {
|
|
|
713
717
|
'- dependsOnIndex is optional. When present it must point strictly backward to an earlier item in this batch;',
|
|
714
718
|
' the first item always omits it. Never point to the same item or a later item.',
|
|
715
719
|
'',
|
|
716
|
-
'## Canonical description contract',
|
|
720
|
+
'## Canonical description contract (premium bar)',
|
|
717
721
|
'',
|
|
718
|
-
|
|
719
|
-
'1. ## Problem Statement — the concrete user/system problem confirmed by the blueprint and current code.',
|
|
720
|
-
'2. ## Proposed Solution — observable behavior and integration with verified existing components/contracts.',
|
|
721
|
-
'3. ## Out of Scope — at least two bullets naming adjacent work deliberately deferred.',
|
|
722
|
-
'4. ## Technical Considerations — at least two bullets naming only verified paths and identifiers, plus data/contracts,',
|
|
723
|
-
' compatibility, risks, failure handling, observability, migrations, and test strategy where relevant.',
|
|
724
|
-
'5. ## Estimated Complexity — Low/Medium/High/Very High plus one sentence explaining the estimate.',
|
|
725
|
-
'Do NOT put an ## Acceptance Criteria heading in description. The app folds the separate criteria array into',
|
|
726
|
-
'the final ticket deterministically.',
|
|
722
|
+
...(0, spec_contract_prompt_1.premiumSpecContract)('verified').split('\n'),
|
|
727
723
|
'',
|
|
728
724
|
'## Acceptance and self-audit',
|
|
729
725
|
'',
|
|
730
|
-
'-
|
|
726
|
+
'- Every spec MUST reach the premium depth above; a restated title is a defect. Add code-grounded reports',
|
|
727
|
+
' of what exists today where it sharpens the Problem Statement or the Proposed Solution.',
|
|
728
|
+
'- acceptanceCriteria contains 6-10 non-empty, independent, testable outcomes rather than implementation steps.',
|
|
731
729
|
'- Across the criteria, cover intended functional behavior, observable failure/edge cases, compatibility where',
|
|
732
730
|
' relevant, and the automated unit/integration/end-to-end tests that prove the change.',
|
|
733
|
-
'- Before specsComplete: true, audit every item for all payload fields, exact heading names/order,
|
|
734
|
-
' sections, English content,
|
|
735
|
-
' verified code references, and strictly backward dependencies. Repair
|
|
731
|
+
'- Before specsComplete: true, audit every item for all payload fields, exact heading names/order, the ###',
|
|
732
|
+
' sub-blocks, the depth floors, non-empty sections, English content, 6-10 criteria, valid priority, milestone',
|
|
733
|
+
' plus domain labels, unique titles, verified code references, and strictly backward dependencies. Repair',
|
|
734
|
+
' failures before marking complete.',
|
|
736
735
|
];
|
|
737
736
|
if (execution?.relocated && execution.repoDir) {
|
|
738
737
|
lines.push('', '## Repository location (relocated project)', '', 'The process cwd is a Specrails workspace containing configuration and artifacts, NOT the source repository.', `The real source repository is at this absolute path: ${execution.repoDir}`, 'The same repository is mounted from the workspace as ./project and exported in SPECRAILS_REPO_DIR.', 'Use the absolute path above or ./project for every source list/search/read. Read/Grep/Glob tools do not expand', 'shell-variable expressions, so never pass literal ${SPECRAILS_REPO_DIR} or ${SPECRAILS_REPO_DIR:-.} as a path.', 'When writing specs, cite verified paths relative to the real repository (for example src/...), never paths', 'under the workspace and never a misleading project/ or .specrails/ prefix.');
|
|
@@ -913,6 +912,12 @@ class ChatManager {
|
|
|
913
912
|
: lightweight
|
|
914
913
|
? this._buildLightweightSystemPrompt(conversationScope)
|
|
915
914
|
: this._buildSystemPrompt();
|
|
915
|
+
if (conversation.kind === 'explore') {
|
|
916
|
+
const repositories = this.projectRepositories?.() ?? [];
|
|
917
|
+
if (repositories.length > 1) {
|
|
918
|
+
systemPrompt += `\n\nThis logical project shares one backlog across these repositories: ${JSON.stringify(repositories.map(({ id, name, path, isPrimary, kind }) => ({ id, name, path, isPrimary, kind })))}. Use these exact IDs in the optional repositoryIds array of spec-draft blocks to scope a spec to one or several repositories. Missing repositoryIds defaults to primary. Preserve the user's selected scope, describe shared contracts for cross-repository specs, and never invent repository IDs or split a spec merely because it touches several repos.`;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
916
921
|
if (hasAttachments)
|
|
917
922
|
systemPrompt = `${systemPrompt}\n\n${attachment_manager_1.USER_ATTACHMENT_SYSTEM_NOTE}`;
|
|
918
923
|
const binary = adapter.binary;
|
|
@@ -935,6 +940,9 @@ class ChatManager {
|
|
|
935
940
|
const scopeFlags = conversationScope && adapter.id === 'claude'
|
|
936
941
|
? (0, context_scope_1.toolFlagsForScope)(conversationScope).args
|
|
937
942
|
: [];
|
|
943
|
+
if (this._cwd && conversation.kind !== 'milestone' && (!conversationScope || conversationScope.mcp)) {
|
|
944
|
+
scopeFlags.push(...(0, codex_spawn_1.buildCodexPluginArgs)({ providerId: adapter.id, stateRoot: this._specrailsRoot() ?? this._cwd, repositoryPath: this._cwd, legacyProviderId: this._adapter.id }));
|
|
945
|
+
}
|
|
938
946
|
// Inject the user's OWN already-approved MCP servers when scope.userMcp is
|
|
939
947
|
// on. Claude-only via `--mcp-config` (codex reads ~/.codex natively, so
|
|
940
948
|
// buildUserMcpArgs returns []). Independent of the `mcp` toggle (project
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const assert = require('node:assert/strict')
|
|
2
|
+
const fs = require('node:fs')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
const { execFile } = require('node:child_process')
|
|
5
|
+
|
|
6
|
+
const MAX_ENTRIES = 100_000
|
|
7
|
+
const MAX_DEPTH = 24
|
|
8
|
+
|
|
9
|
+
function validateArchiveNames(listing, { platform = process.platform } = {}) {
|
|
10
|
+
const names = listing.split(/\r?\n/).filter(Boolean)
|
|
11
|
+
assert(names.length > 0 && names.length <= MAX_ENTRIES, 'Chromium archive is empty or exceeds its entry limit.')
|
|
12
|
+
for (const name of names) {
|
|
13
|
+
assert(!/^[\\/]|^[A-Za-z]:|[\x00-\x1f\x7f]/.test(name) && !name.includes('\\') && !name.split('/').includes('..'), `Unsafe Chromium archive path: ${name}`)
|
|
14
|
+
if (platform === 'win32') {
|
|
15
|
+
for (const component of name.split('/').filter(part => part !== '' && part !== '.')) {
|
|
16
|
+
assert(!/[:<>"|?*]|[ .]$/.test(component), `Unsafe Windows Chromium archive path: ${name}`)
|
|
17
|
+
const stem = component.split('.')[0].replace(/[ .]+$/g, '')
|
|
18
|
+
assert(!/^(?:CON|PRN|AUX|NUL|CONIN\$|CONOUT\$|COM[1-9¹²³]|LPT[1-9¹²³])$/i.test(stem), `Reserved Windows Chromium archive path: ${name}`)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validateWindowsArchiveTypes(listing) {
|
|
25
|
+
const entries = listing.split(/\r?\n/).filter(Boolean)
|
|
26
|
+
assert(entries.length > 0 && entries.length <= MAX_ENTRIES, 'Chromium archive is empty or exceeds its entry limit.')
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
// bsdtar (the Windows system tar) resolves GNU/PAX extensions before listing
|
|
29
|
+
// the entry type. Only the fixed permission field matters, never translated
|
|
30
|
+
// dates/owners or a textual link target. Windows Chromium needs no links.
|
|
31
|
+
assert(/^[-d][rwxStTs-]{9}[+@.]?\s/.test(entry), 'Windows Chromium archives must contain only regular files and directories; links and special entries are not allowed.')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function runTar(binary, args, { timeout = 180_000, maxBytes = 12 * 1024 * 1024, env } = {}) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
execFile(binary, args, { timeout, maxBuffer: maxBytes, env, encoding: 'utf8', windowsHide: true }, (error, stdout) => {
|
|
38
|
+
if (error) reject(error)
|
|
39
|
+
else resolve(stdout)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Admission must finish before any extraction. Windows tar.exe does not
|
|
45
|
+
* reliably reject writes through an archive-created link, unlike POSIX tar. */
|
|
46
|
+
async function validateChromiumArchive(archivePath, { platform = process.platform, run = runTar } = {}) {
|
|
47
|
+
assert(fs.lstatSync(archivePath).isFile(), 'Chromium archive must be a regular file.')
|
|
48
|
+
const size = fs.statSync(archivePath).size
|
|
49
|
+
assert(size > 0 && size <= 3 * 1024 ** 3, 'Chromium archive is empty or exceeds 3 GiB.')
|
|
50
|
+
const tar = platform === 'win32' ? 'tar.exe' : '/usr/bin/tar'
|
|
51
|
+
const env = { ...process.env, LC_ALL: 'C', LANG: 'C' }
|
|
52
|
+
delete env.TAR_OPTIONS
|
|
53
|
+
validateArchiveNames(await run(tar, ['-tf', archivePath], { env }), { platform })
|
|
54
|
+
if (platform === 'win32') validateWindowsArchiveTypes(await run(tar, ['-tvf', archivePath], { env }))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function inside(root, candidate) {
|
|
58
|
+
const relative = path.relative(root, candidate)
|
|
59
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Validate without following directories through links. macOS versioned
|
|
63
|
+
* framework symlinks are allowed only when their resolved target stays inside. */
|
|
64
|
+
function validateChromiumTree(root) {
|
|
65
|
+
const realRoot = fs.realpathSync(root)
|
|
66
|
+
let count = 0
|
|
67
|
+
const walk = (directory, depth) => {
|
|
68
|
+
assert(depth <= MAX_DEPTH, 'Chromium tree exceeds its depth limit.')
|
|
69
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
70
|
+
assert(++count <= MAX_ENTRIES, 'Chromium tree exceeds its entry limit.')
|
|
71
|
+
const file = path.join(directory, entry.name)
|
|
72
|
+
if (entry.isSymbolicLink()) assert(inside(realRoot, fs.realpathSync(file)), `Chromium link escapes the extracted tree: ${file}`)
|
|
73
|
+
else if (entry.isDirectory()) walk(file, depth + 1)
|
|
74
|
+
else assert(entry.isFile(), `Unsupported Chromium entry: ${file}`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
walk(realRoot, 0)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
exports.validateArchiveNames = validateArchiveNames
|
|
81
|
+
exports.validateWindowsArchiveTypes = validateWindowsArchiveTypes
|
|
82
|
+
exports.validateChromiumArchive = validateChromiumArchive
|
|
83
|
+
exports.validateChromiumTree = validateChromiumTree
|
|
@@ -12,40 +12,19 @@ const path_1 = __importDefault(require("path"));
|
|
|
12
12
|
const child_process_1 = require("child_process");
|
|
13
13
|
const stream_1 = require("stream");
|
|
14
14
|
const promises_1 = require("stream/promises");
|
|
15
|
+
const chromium_archive_cjs_1 = require("./chromium-archive.cjs");
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
+
* Discover bundled Chromium, extracting its distribution archive when necessary.
|
|
17
18
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* become flat duplicate copies) and invalidates its code signature — macOS
|
|
23
|
-
* notarization then rejects the app ("The signature of the binary is invalid").
|
|
19
|
+
* A transparent chromium.tar.gz preserves the versioned framework symlinks that
|
|
20
|
+
* Tauri resource copying would otherwise dereference. Release assembly signs and
|
|
21
|
+
* notarizes Chromium before packing it; extraction preserves that signed layout.
|
|
22
|
+
* The writable cache is an installation location, not a signing bypass.
|
|
24
23
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* Mach-O inside; Chromium's ~50 nested binaries are only ad-hoc ("linker") signed
|
|
30
|
-
* by Google, so a plain archive fails notarization. XOR-ing breaks the gzip/tar
|
|
31
|
-
* magic, so the notary cannot identify the file as a container and treats it as
|
|
32
|
-
* opaque data — nothing inside is inspected, and the app notarizes with NO
|
|
33
|
-
* Developer-ID signing of Chromium. (Obfuscation, not security: the key is public.)
|
|
34
|
-
* - Tauri also copies it as one regular file — nothing to dereference.
|
|
35
|
-
* - At first use we reverse the XOR and extract it to a writable cache
|
|
36
|
-
* (`~/.specrails/runtimes/chromium`), restoring the framework symlinks intact. The
|
|
37
|
-
* extracted Chromium keeps Google's ad-hoc signature, which is sufficient to execute
|
|
38
|
-
* on Apple Silicon, and — being self-extracted rather than downloaded — carries no
|
|
39
|
-
* `com.apple.quarantine` xattr, so Gatekeeper does not block it.
|
|
40
|
-
*
|
|
41
|
-
* A plain `chromium.tar.gz`/`chromium.tar` is also accepted (e.g. unobfuscated local
|
|
42
|
-
* builds). When no archive is present (dev, a runtimes-less build, a partial
|
|
43
|
-
* extraction) we fall back to discovering an UNPACKED `<runtimes>/chromium` tree, and
|
|
44
|
-
* finally to `null` so Playwright uses its own managed browser — never dead-ending.
|
|
45
|
-
*
|
|
46
|
-
* We DISCOVER rather than hard-code the executable path because Playwright's layout
|
|
47
|
-
* changes across versions (`chrome-mac/Chromium.app` → `chrome-mac-arm64/Google Chrome
|
|
48
|
-
* for Testing.app`, `chrome-win/chrome.exe`, `chrome-linux/chrome`).
|
|
24
|
+
* Older bundles used an XOR-encoded chromium.pak. Decode that format only for
|
|
25
|
+
* compatibility, preferring transparent archives whenever both are present.
|
|
26
|
+
* Unpacked development bundles and Playwright-managed browsers remain fallbacks.
|
|
27
|
+
* The executable is discovered because Playwright's platform layout can change.
|
|
49
28
|
*/
|
|
50
29
|
const MAX_DEPTH = 6;
|
|
51
30
|
function isFile(p) {
|
|
@@ -155,12 +134,9 @@ function resolveBundledChromiumPath() {
|
|
|
155
134
|
}
|
|
156
135
|
}
|
|
157
136
|
/** Candidate archive names under `<runtimes>/chromium`, in preference order. */
|
|
158
|
-
const ARCHIVE_NAMES = ['chromium.
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
// (Safe to have changed at the rebrand: the .pak and this binary always ship
|
|
162
|
-
// together in the same bundle, and the extraction cache re-extracts on a new
|
|
163
|
-
// archive via the `.source` marker.)
|
|
137
|
+
const ARCHIVE_NAMES = ['chromium.tar.gz', 'chromium.tar', 'chromium.pak'];
|
|
138
|
+
// Legacy archive decoding only. The bytes must remain compatible with previously
|
|
139
|
+
// shipped chromium.pak files; new release assembly produces transparent archives.
|
|
164
140
|
const OBFUSCATION_KEY = Buffer.from('specrails-desktop-chromium-pack-v1', 'utf8');
|
|
165
141
|
/** Streaming XOR transform (symmetric: packs and unpacks). */
|
|
166
142
|
function xorStream() {
|
|
@@ -185,10 +161,10 @@ function chromiumCacheDir() {
|
|
|
185
161
|
return (process.env.SPECRAILS_CHROMIUM_CACHE_DIR ||
|
|
186
162
|
path_1.default.join(os_1.default.homedir(), '.specrails', 'runtimes', 'chromium'));
|
|
187
163
|
}
|
|
188
|
-
/**
|
|
164
|
+
/** Bind the extraction to the selected archive and its precise filesystem revision. */
|
|
189
165
|
function archiveIdentity(archivePath) {
|
|
190
166
|
const st = fs_1.default.statSync(archivePath);
|
|
191
|
-
return
|
|
167
|
+
return JSON.stringify({ version: 2, path: fs_1.default.realpathSync(archivePath), size: st.size, mtimeMs: st.mtimeMs, ctimeMs: st.ctimeMs, dev: st.dev, ino: st.ino });
|
|
192
168
|
}
|
|
193
169
|
/** Resolve the platform `tar` binary. macOS/Linux ship `/usr/bin/tar`; Windows 10+ ships `tar` (bsdtar) on PATH. */
|
|
194
170
|
function tarBinary() {
|
|
@@ -196,12 +172,18 @@ function tarBinary() {
|
|
|
196
172
|
return '/usr/bin/tar';
|
|
197
173
|
return 'tar';
|
|
198
174
|
}
|
|
199
|
-
/** Extract
|
|
175
|
+
/** Extract a preflight-validated archive using the system tar (auto-detects gzip). */
|
|
200
176
|
function runTarExtract(archivePath, destDir) {
|
|
201
177
|
return new Promise((resolve, reject) => {
|
|
202
|
-
|
|
178
|
+
// Apple tar must restore the metadata carrying the stapled ticket. These
|
|
179
|
+
// caller overrides are appropriate for source copies, not signed bundles.
|
|
180
|
+
const env = { ...process.env };
|
|
181
|
+
delete env.TAR_OPTIONS;
|
|
182
|
+
delete env.COPYFILE_DISABLE;
|
|
183
|
+
delete env.COPY_EXTENDED_ATTRIBUTES_DISABLE;
|
|
184
|
+
const child = (0, child_process_1.spawn)(tarBinary(), ['-xf', archivePath, '-C', destDir], { stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true, env });
|
|
203
185
|
let stderr = '';
|
|
204
|
-
child.stderr?.on('data', (d) => { stderr
|
|
186
|
+
child.stderr?.on('data', (d) => { stderr = (stderr + d.toString()).slice(-4096); });
|
|
205
187
|
child.on('error', reject);
|
|
206
188
|
child.on('close', (code) => {
|
|
207
189
|
if (code === 0)
|
|
@@ -211,9 +193,9 @@ function runTarExtract(archivePath, destDir) {
|
|
|
211
193
|
});
|
|
212
194
|
});
|
|
213
195
|
}
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
|
|
196
|
+
// Share pending work and serialize replacement of the same cache directory.
|
|
197
|
+
// Completed promises are removed so deleted or changed cache files are rechecked.
|
|
198
|
+
const extractInflight = new Map();
|
|
217
199
|
/** First archive that exists under `<runtimes>/chromium`, or null. */
|
|
218
200
|
function findBundledArchive(runtimesPath) {
|
|
219
201
|
for (const name of ARCHIVE_NAMES) {
|
|
@@ -223,8 +205,7 @@ function findBundledArchive(runtimesPath) {
|
|
|
223
205
|
}
|
|
224
206
|
return null;
|
|
225
207
|
}
|
|
226
|
-
async function extractAndDiscover(archivePath, identity) {
|
|
227
|
-
const destRoot = chromiumCacheDir();
|
|
208
|
+
async function extractAndDiscover(archivePath, identity, destRoot) {
|
|
228
209
|
const marker = path_1.default.join(destRoot, '.source');
|
|
229
210
|
// Fast path: already extracted from this exact archive.
|
|
230
211
|
try {
|
|
@@ -235,10 +216,10 @@ async function extractAndDiscover(archivePath, identity) {
|
|
|
235
216
|
}
|
|
236
217
|
}
|
|
237
218
|
catch { /* not yet extracted / stale → fall through */ }
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
fs_1.default.
|
|
241
|
-
fs_1.default.
|
|
219
|
+
// Validate a complete extraction before publishing; retain the prior cache if
|
|
220
|
+
// publication fails (for example, while a running browser locks it on Windows).
|
|
221
|
+
fs_1.default.mkdirSync(path_1.default.dirname(destRoot), { recursive: true });
|
|
222
|
+
const tmpDir = fs_1.default.mkdtempSync(`${destRoot}.tmp-`);
|
|
242
223
|
// An obfuscated `.pak` is XOR-decoded to a real `.tar.gz` first; plain archives
|
|
243
224
|
// are fed straight to tar.
|
|
244
225
|
const isPak = archivePath.endsWith('.pak');
|
|
@@ -249,7 +230,11 @@ async function extractAndDiscover(archivePath, identity) {
|
|
|
249
230
|
await deobfuscate(archivePath, decodedTar);
|
|
250
231
|
tarSource = decodedTar;
|
|
251
232
|
}
|
|
233
|
+
// Windows tar can follow an archive-created link before a post-extraction
|
|
234
|
+
// check runs. Validate the complete archive before allowing any writes.
|
|
235
|
+
await (0, chromium_archive_cjs_1.validateChromiumArchive)(tarSource);
|
|
252
236
|
await runTarExtract(tarSource, tmpDir);
|
|
237
|
+
(0, chromium_archive_cjs_1.validateChromiumTree)(tmpDir);
|
|
253
238
|
const exeInTmp = discoverChromiumExecutable(tmpDir);
|
|
254
239
|
if (!exeInTmp)
|
|
255
240
|
throw new Error('no chromium executable found after extraction');
|
|
@@ -257,10 +242,29 @@ async function extractAndDiscover(archivePath, identity) {
|
|
|
257
242
|
fs_1.default.chmodSync(exeInTmp, 0o755);
|
|
258
243
|
}
|
|
259
244
|
catch { /* perms best-effort */ }
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
fs_1.default.
|
|
263
|
-
|
|
245
|
+
if (archiveIdentity(archivePath) !== identity)
|
|
246
|
+
throw new Error('Chromium archive changed during extraction; retry with the current bundle');
|
|
247
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.source'), identity);
|
|
248
|
+
const previous = `${tmpDir}.previous`;
|
|
249
|
+
let movedPrevious = false;
|
|
250
|
+
if (fs_1.default.existsSync(destRoot)) {
|
|
251
|
+
fs_1.default.renameSync(destRoot, previous);
|
|
252
|
+
movedPrevious = true;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
fs_1.default.renameSync(tmpDir, destRoot);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (movedPrevious)
|
|
259
|
+
fs_1.default.renameSync(previous, destRoot);
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
if (movedPrevious) {
|
|
263
|
+
try {
|
|
264
|
+
fs_1.default.rmSync(previous, { recursive: true, force: true });
|
|
265
|
+
}
|
|
266
|
+
catch { /* a locked prior version can be cleaned up later */ }
|
|
267
|
+
}
|
|
264
268
|
return discoverChromiumExecutable(destRoot);
|
|
265
269
|
}
|
|
266
270
|
finally {
|
|
@@ -272,8 +276,8 @@ async function extractAndDiscover(archivePath, identity) {
|
|
|
272
276
|
/**
|
|
273
277
|
* Resolve the bundled Chromium executable for the launch path, extracting the
|
|
274
278
|
* shipped archive on first use. Returns `null` (never throws) when not in desktop
|
|
275
|
-
* mode, when no bundle is present, or when extraction fails
|
|
276
|
-
*
|
|
279
|
+
* mode, when no bundle is present, or when extraction fails. The caller may then
|
|
280
|
+
* try an installed Playwright-managed browser; this resolver does not download one.
|
|
277
281
|
*/
|
|
278
282
|
async function resolveBundledChromiumExecutable() {
|
|
279
283
|
if (process.env.SPECRAILS_IS_DESKTOP !== '1')
|
|
@@ -298,14 +302,18 @@ async function resolveBundledChromiumExecutable() {
|
|
|
298
302
|
catch {
|
|
299
303
|
return resolveBundledChromiumPath();
|
|
300
304
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
+
const destRoot = path_1.default.resolve(chromiumCacheDir());
|
|
306
|
+
const pending = extractInflight.get(destRoot);
|
|
307
|
+
if (pending?.identity === identity)
|
|
308
|
+
return pending.promise;
|
|
309
|
+
const extract = () => extractAndDiscover(archivePath, identity, destRoot);
|
|
310
|
+
const promise = (pending ? pending.promise.then(extract) : extract()).catch((err) => {
|
|
305
311
|
console.error('[chromium-resolver] extraction failed:', err instanceof Error ? err.message : err);
|
|
306
|
-
|
|
307
|
-
|
|
312
|
+
return discoverChromiumExecutable(path_1.default.join(runtimesPath, 'chromium'));
|
|
313
|
+
}).finally(() => {
|
|
314
|
+
if (extractInflight.get(destRoot)?.promise === promise)
|
|
315
|
+
extractInflight.delete(destRoot);
|
|
308
316
|
});
|
|
309
|
-
|
|
317
|
+
extractInflight.set(destRoot, { identity, promise });
|
|
310
318
|
return promise;
|
|
311
319
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CodeActivityQueryError = void 0;
|
|
4
|
+
exports.parseCodeActivityQuery = parseCodeActivityQuery;
|
|
5
|
+
exports.listCodeActivity = listCodeActivity;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const project_repositories_1 = require("./project-repositories");
|
|
8
|
+
class CodeActivityQueryError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
constructor(message, status = 400) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.status = status;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.CodeActivityQueryError = CodeActivityQueryError;
|
|
16
|
+
const MAX_SCAN_ROWS = 2_000;
|
|
17
|
+
const MAX_SCAN_MS = 2_000;
|
|
18
|
+
const BATCH_SIZE = 200;
|
|
19
|
+
function scalar(value, field) {
|
|
20
|
+
if (value === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 2048)
|
|
23
|
+
throw new CodeActivityQueryError(`invalid_activity_${field}`);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function parseCodeActivityQuery(query, scopedRepositoryId) {
|
|
27
|
+
const repositoryId = scalar(query.repositoryId, 'repository');
|
|
28
|
+
if (scopedRepositoryId && repositoryId && repositoryId !== scopedRepositoryId)
|
|
29
|
+
throw new CodeActivityQueryError('repository_scope_mismatch');
|
|
30
|
+
const ticket = scalar(query.ticketId, 'ticket');
|
|
31
|
+
const limit = scalar(query.limit, 'limit');
|
|
32
|
+
if (ticket !== undefined && (!/^\d+$/.test(ticket) || !Number.isSafeInteger(Number(ticket)) || Number(ticket) <= 0))
|
|
33
|
+
throw new CodeActivityQueryError('invalid_activity_ticket');
|
|
34
|
+
if (limit !== undefined && (!/^\d+$/.test(limit) || Number(limit) < 1 || Number(limit) > 100))
|
|
35
|
+
throw new CodeActivityQueryError('invalid_activity_limit');
|
|
36
|
+
return {
|
|
37
|
+
repositoryId: scopedRepositoryId ?? repositoryId,
|
|
38
|
+
ticketId: ticket === undefined ? undefined : Number(ticket),
|
|
39
|
+
jobId: scalar(query.jobId, 'job'), limit: limit === undefined ? undefined : Number(limit),
|
|
40
|
+
cursor: scalar(query.cursor, 'cursor'),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Keyset pagination is fenced to the first request's maximum provenance ID.
|
|
44
|
+
* Newly captured events (including backdated ones) cannot move that snapshot.
|
|
45
|
+
* The cursor is a position, never authorization: every page rechecks membership,
|
|
46
|
+
* filters and current path policy. No provider or current source read is needed. */
|
|
47
|
+
async function listCodeActivity(db, project, query, filterPaths) {
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
const limit = query.limit ?? 50;
|
|
50
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
51
|
+
throw new CodeActivityQueryError('invalid_activity_limit');
|
|
52
|
+
const members = (0, project_repositories_1.getProjectRepositories)(project);
|
|
53
|
+
const repositories = query.repositoryId ? members.filter(repository => repository.id === query.repositoryId) : members;
|
|
54
|
+
if (!repositories.length)
|
|
55
|
+
throw new CodeActivityQueryError('repository_not_found', 404);
|
|
56
|
+
const primary = repositories.find(repository => repository.isPrimary);
|
|
57
|
+
const repositoryById = new Map(repositories.map(repository => [repository.id, repository]));
|
|
58
|
+
const scope = (0, node_crypto_1.createHash)('sha256').update(JSON.stringify({
|
|
59
|
+
projectId: project.id,
|
|
60
|
+
repositories: repositories.map(({ id, path, isPrimary }) => ({ id, path, isPrimary })).sort((a, b) => a.id.localeCompare(b.id)),
|
|
61
|
+
ticketId: query.ticketId ?? null, jobId: query.jobId ?? null,
|
|
62
|
+
})).digest('hex');
|
|
63
|
+
let cursor;
|
|
64
|
+
if (query.cursor) {
|
|
65
|
+
try {
|
|
66
|
+
if (query.cursor.length > 2048)
|
|
67
|
+
throw new Error();
|
|
68
|
+
cursor = JSON.parse(Buffer.from(query.cursor, 'base64url').toString('utf8'));
|
|
69
|
+
if (cursor.v !== 1 || cursor.scope !== scope || !Number.isSafeInteger(cursor.snapshot) || cursor.snapshot < 0 ||
|
|
70
|
+
!Number.isSafeInteger(cursor.id) || cursor.id <= 0 || cursor.id > cursor.snapshot || !Number.isSafeInteger(cursor.at))
|
|
71
|
+
throw new Error();
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new CodeActivityQueryError('invalid_activity_cursor');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const snapshot = cursor?.snapshot ?? db.prepare('SELECT COALESCE(MAX(id), 0) AS id FROM file_provenance').get().id;
|
|
78
|
+
const columns = db.prepare('PRAGMA table_info(file_provenance)').all();
|
|
79
|
+
const hasRepository = columns.some(column => column.name === 'repository_id');
|
|
80
|
+
const hasDiffs = !!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='file_provenance_diffs'").get();
|
|
81
|
+
const conditions = ['p.id <= ?'];
|
|
82
|
+
const parameters = [snapshot];
|
|
83
|
+
if (hasRepository) {
|
|
84
|
+
conditions.push(`(p.repository_id IN (${repositories.map(() => '?').join(',')})${primary ? ' OR p.repository_id IS NULL' : ''})`);
|
|
85
|
+
parameters.push(...repositories.map(repository => repository.id));
|
|
86
|
+
}
|
|
87
|
+
else if (!primary)
|
|
88
|
+
conditions.push('0 = 1');
|
|
89
|
+
if (query.ticketId !== undefined) {
|
|
90
|
+
conditions.push('p.ticket_id = ?');
|
|
91
|
+
parameters.push(query.ticketId);
|
|
92
|
+
}
|
|
93
|
+
if (query.jobId !== undefined) {
|
|
94
|
+
conditions.push('p.job_id = ?');
|
|
95
|
+
parameters.push(query.jobId);
|
|
96
|
+
}
|
|
97
|
+
const where = conditions.join(' AND ');
|
|
98
|
+
const join = hasDiffs ? 'LEFT JOIN file_provenance_diffs d ON d.provenance_id = p.id' : '';
|
|
99
|
+
const projection = `p.id, ${hasRepository ? 'p.repository_id' : 'NULL AS repository_id'}, p.file_path, p.ticket_id, p.job_id, p.kind, p.at, ${hasDiffs ? '(d.provenance_id IS NOT NULL) AS has_patch, d.truncated AS patch_truncated' : '0 AS has_patch, 0 AS patch_truncated'}`;
|
|
100
|
+
let last = cursor ? { at: cursor.at, id: cursor.id } : undefined;
|
|
101
|
+
const entries = [];
|
|
102
|
+
const warnings = new Set();
|
|
103
|
+
let scanned = 0;
|
|
104
|
+
let exhausted = false;
|
|
105
|
+
while (entries.length < limit && scanned < MAX_SCAN_ROWS && Date.now() - started < MAX_SCAN_MS) {
|
|
106
|
+
const rows = db.prepare(`SELECT ${projection} FROM file_provenance p ${join} WHERE ${where}
|
|
107
|
+
${last ? 'AND (p.at < ? OR (p.at = ? AND p.id < ?))' : ''}
|
|
108
|
+
ORDER BY p.at DESC, p.id DESC LIMIT ?`).all(...parameters, ...(last ? [last.at, last.at, last.id] : []), Math.min(BATCH_SIZE, MAX_SCAN_ROWS - scanned));
|
|
109
|
+
if (!rows.length) {
|
|
110
|
+
exhausted = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
const allowedByRepository = new Map();
|
|
114
|
+
for (const repository of repositories) {
|
|
115
|
+
const paths = [...new Set(rows.filter(row => row.repository_id === repository.id || (row.repository_id == null && repository.isPrimary)).map(row => row.file_path))];
|
|
116
|
+
if (!paths.length)
|
|
117
|
+
continue;
|
|
118
|
+
const filtered = await filterPaths(repository, paths, Math.max(0, MAX_SCAN_MS - (Date.now() - started)));
|
|
119
|
+
allowedByRepository.set(repository.id, filtered.allowed);
|
|
120
|
+
if (filtered.unavailable)
|
|
121
|
+
warnings.add(`repository-unavailable:${repository.id}`);
|
|
122
|
+
else if (filtered.incomplete)
|
|
123
|
+
warnings.add(`ignore-unverified:${repository.id}`);
|
|
124
|
+
}
|
|
125
|
+
for (const row of rows) {
|
|
126
|
+
last = { at: row.at, id: row.id };
|
|
127
|
+
scanned++;
|
|
128
|
+
const repository = row.repository_id == null ? primary : repositoryById.get(row.repository_id);
|
|
129
|
+
if (repository && allowedByRepository.get(repository.id)?.has(row.file_path)) {
|
|
130
|
+
entries.push({ id: row.id, repositoryId: repository.id, repositoryName: repository.name,
|
|
131
|
+
path: row.file_path, jobId: row.job_id, ticketId: row.ticket_id, kind: row.kind, at: row.at,
|
|
132
|
+
hasPatch: !!row.has_patch, patchTruncated: !!row.patch_truncated });
|
|
133
|
+
}
|
|
134
|
+
if (entries.length >= limit)
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const more = !exhausted && last !== undefined && !!db.prepare(`SELECT 1 FROM file_provenance p WHERE ${where} AND (p.at < ? OR (p.at = ? AND p.id < ?)) LIMIT 1`).get(...parameters, last.at, last.at, last.id);
|
|
139
|
+
if (more && entries.length < limit)
|
|
140
|
+
warnings.add(scanned >= MAX_SCAN_ROWS ? 'scan-limit' : 'time-limit');
|
|
141
|
+
return {
|
|
142
|
+
entries, nextCursor: more && last ? Buffer.from(JSON.stringify({ v: 1, scope, snapshot, ...last })).toString('base64url') : null,
|
|
143
|
+
truncated: warnings.size > 0, warnings: [...warnings],
|
|
144
|
+
};
|
|
145
|
+
}
|