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
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.createLoopExecutors = createLoopExecutors;
|
|
7
4
|
/**
|
|
@@ -12,45 +9,62 @@ exports.createLoopExecutors = createLoopExecutors;
|
|
|
12
9
|
* from coverage; the engine's traversal/decision logic is unit-tested against
|
|
13
10
|
* fake executors in `loop-run-manager.test.ts`.
|
|
14
11
|
*/
|
|
12
|
+
const core_execution_1 = require("./core-execution");
|
|
13
|
+
const codex_spawn_1 = require("./plugins/codex-spawn");
|
|
15
14
|
const node_child_process_1 = require("node:child_process");
|
|
16
15
|
const node_crypto_1 = require("node:crypto");
|
|
17
|
-
const
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
18
|
+
const win_spawn_1 = require("./util/win-spawn");
|
|
18
19
|
const providers_1 = require("./providers");
|
|
19
20
|
const workspace_manager_1 = require("./workspace-manager");
|
|
20
21
|
const claude_trust_1 = require("./claude-trust");
|
|
21
22
|
const spawn_lifecycle_1 = require("./spawn-lifecycle");
|
|
22
23
|
const result_event_1 = require("./result-event");
|
|
24
|
+
const terminal_result_1 = require("./providers/terminal-result");
|
|
25
|
+
const claude_background_tasks_1 = require("./providers/claude-background-tasks");
|
|
26
|
+
const loop_constants_1 = require("./loop-constants");
|
|
23
27
|
const loop_decider_1 = require("./loop-decider");
|
|
24
28
|
const rail_isolation_1 = require("./rail-isolation");
|
|
25
29
|
const repo_map_1 = require("./repo-map");
|
|
26
30
|
const feature_flags_1 = require("./feature-flags");
|
|
31
|
+
const loop_step_idle_1 = require("./loop-step-idle");
|
|
27
32
|
const runtime_1 = require("./providers/runtime");
|
|
33
|
+
const loop_shell_invocation_1 = require("./loop-shell-invocation");
|
|
28
34
|
// Per-step wall-clock caps so a single hung step can't block the engine's
|
|
29
35
|
// overall deadline check (which only runs between nodes).
|
|
30
36
|
const AI_STEP_TIMEOUT_MS = 15 * 60_000;
|
|
31
37
|
const DECIDER_TIMEOUT_MS = 3 * 60_000;
|
|
32
38
|
const SHELL_TIMEOUT_MS = 10 * 60_000;
|
|
33
39
|
const SHELL_OUTPUT_CAP = 256 * 1024;
|
|
40
|
+
// Match the queue's idle budget: a long implementation may keep running while
|
|
41
|
+
// producing output; a silent, wedged CLI must eventually release its rail.
|
|
42
|
+
const DEFAULT_INACTIVITY_TIMEOUT_MS = 30 * 60_000;
|
|
34
43
|
function runShellCommand(command, cwd, env, timeoutMs, onLine, onSpawn) {
|
|
35
44
|
return new Promise((resolve) => {
|
|
36
45
|
const isWin = process.platform === 'win32';
|
|
37
|
-
const
|
|
38
|
-
const args = isWin ? ['/d', '/s', '/c', command] : ['-c', command];
|
|
46
|
+
const shellEnv = isWin ? (0, win_spawn_1.windowsSpawnEnv)(env) : env;
|
|
39
47
|
const start = Date.now();
|
|
40
48
|
let stdout = '';
|
|
41
49
|
let stderr = '';
|
|
42
50
|
let settled = false;
|
|
43
51
|
let child;
|
|
44
52
|
try {
|
|
45
|
-
|
|
53
|
+
const bundled = (0, loop_shell_invocation_1.bundledLoopShellInvocation)(command);
|
|
54
|
+
const options = {
|
|
46
55
|
cwd,
|
|
47
|
-
env,
|
|
56
|
+
env: shellEnv,
|
|
48
57
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
49
|
-
|
|
50
|
-
}
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
};
|
|
60
|
+
// Node wraps /d /s /c with the required outer quotes. Passing a raw
|
|
61
|
+
// command ourselves with verbatim argv loses the first executable's quotes.
|
|
62
|
+
child = bundled ? (0, node_child_process_1.spawn)(bundled.binary, bundled.args, options) : isWin
|
|
63
|
+
? (0, node_child_process_1.spawn)(command, { ...options, shell: shellEnv.ComSpec || 'cmd.exe' })
|
|
64
|
+
: (0, node_child_process_1.spawn)('/bin/sh', ['-c', command], options);
|
|
51
65
|
}
|
|
52
|
-
catch {
|
|
53
|
-
resolve({ stdout: '', stderr:
|
|
66
|
+
catch (error) {
|
|
67
|
+
resolve({ stdout: '', stderr: `failed to spawn shell: ${error instanceof Error ? error.message : String(error)}`, exitCode: -1, durationMs: 0 });
|
|
54
68
|
return;
|
|
55
69
|
}
|
|
56
70
|
onSpawn?.(child);
|
|
@@ -64,7 +78,7 @@ function runShellCommand(command, cwd, env, timeoutMs, onLine, onSpawn) {
|
|
|
64
78
|
const timer = setTimeout(() => {
|
|
65
79
|
if (child.pid) {
|
|
66
80
|
try {
|
|
67
|
-
(0,
|
|
81
|
+
(0, win_spawn_1.treeKillSafe)(child.pid, 'SIGKILL', () => { });
|
|
68
82
|
}
|
|
69
83
|
catch { /* gone */ }
|
|
70
84
|
}
|
|
@@ -94,12 +108,12 @@ function runShellCommand(command, cwd, env, timeoutMs, onLine, onSpawn) {
|
|
|
94
108
|
* app's own draft-PR delivery. Default ON (flag=0/false/off disables). See
|
|
95
109
|
* rail-isolation + the specrails-core SPECRAILS_GIT_AUTO contract.
|
|
96
110
|
*/
|
|
97
|
-
function aiStepEnv(env, repoDir) {
|
|
98
|
-
const base = repoDir ? { ...
|
|
111
|
+
function aiStepEnv(env, repoDir, manifest) {
|
|
112
|
+
const base = { ...env, ...(repoDir ? { SPECRAILS_REPO_DIR: repoDir } : {}), ...(manifest ? { SPECRAILS_EXECUTION_MANIFEST: JSON.stringify(manifest), SPECRAILS_REPOSITORIES: JSON.stringify(manifest.repositories.map((repo) => ({ id: repo.repositoryId, name: repo.name, path: repo.worktreePath }))) } : {}) };
|
|
99
113
|
// Deterministic repo map (zero-AI) so the pipeline's exploration phase
|
|
100
114
|
// starts oriented instead of spending its first turns on `ls`/`find`.
|
|
101
115
|
const withMap = (0, repo_map_1.injectRepoMapEnv)(base, repoDir);
|
|
102
|
-
return (0, rail_isolation_1.isRailPrDeliveryEnabled)() ? { ...withMap, SPECRAILS_GIT_AUTO: 'false' } : withMap;
|
|
116
|
+
return manifest || (0, rail_isolation_1.isRailPrDeliveryEnabled)() ? { ...withMap, SPECRAILS_GIT_AUTO: 'false' } : withMap;
|
|
103
117
|
}
|
|
104
118
|
/**
|
|
105
119
|
* Relocated cwd is the workspace; the source repo is reached via the
|
|
@@ -113,27 +127,45 @@ function aiStepEnv(env, repoDir) {
|
|
|
113
127
|
* (and cwd, defensively) to codex's sandbox writable_roots. Harmless
|
|
114
128
|
* no-op under danger-full-access on iteration 1.
|
|
115
129
|
*/
|
|
116
|
-
function aiStepExtraArgs(adapter, cwd, repoDir) {
|
|
130
|
+
function aiStepExtraArgs(adapter, cwd, repoDir, manifest) {
|
|
117
131
|
if (!repoDir)
|
|
118
132
|
return undefined;
|
|
119
|
-
const args = (0, runtime_1.buildProviderRepoAccessArgs)(adapter, [repoDir, ...(adapter.id === 'codex' ? [cwd] : [])]);
|
|
133
|
+
const args = (0, runtime_1.buildProviderRepoAccessArgs)(adapter, [...new Set([repoDir, ...(manifest?.repositories.map((repo) => repo.worktreePath) ?? []), ...(adapter.id === 'codex' ? [cwd] : [])])]);
|
|
120
134
|
return args.length > 0 ? args : undefined;
|
|
121
135
|
}
|
|
122
136
|
function createLoopExecutors(opts = {}) {
|
|
123
137
|
const resolveEnv = () => typeof opts.env === 'function' ? opts.env() : opts.env ?? process.env;
|
|
138
|
+
const inactivityTimeoutMs = () => {
|
|
139
|
+
const env = resolveEnv();
|
|
140
|
+
const configuredIdle = env.SPECRAILS_LOOP_STEP_IDLE_TIMEOUT_MS ?? process.env.SPECRAILS_LOOP_STEP_IDLE_TIMEOUT_MS;
|
|
141
|
+
if (configuredIdle != null)
|
|
142
|
+
return (0, loop_step_idle_1.resolveLoopStepIdleTimeoutMs)({ ...env, SPECRAILS_LOOP_STEP_IDLE_TIMEOUT_MS: configuredIdle });
|
|
143
|
+
const raw = env.SPECRAILS_LOOP_INACTIVITY_MS ?? process.env.SPECRAILS_LOOP_INACTIVITY_MS;
|
|
144
|
+
const value = raw?.trim() ? Number(raw) : DEFAULT_INACTIVITY_TIMEOUT_MS;
|
|
145
|
+
return Number.isFinite(value) && value >= 0 ? value : DEFAULT_INACTIVITY_TIMEOUT_MS;
|
|
146
|
+
};
|
|
124
147
|
// Deciders deliberately excluded: they run no pipeline agents.
|
|
125
|
-
const withProfileEnv = (env, provider) => {
|
|
148
|
+
const withProfileEnv = (env, provider, profileName) => {
|
|
149
|
+
if (profileName === null) {
|
|
150
|
+
const legacyEnv = { ...env };
|
|
151
|
+
delete legacyEnv.SPECRAILS_PROFILE_PATH;
|
|
152
|
+
return legacyEnv;
|
|
153
|
+
}
|
|
126
154
|
try {
|
|
127
|
-
const profilePath = opts.profilePathFor?.(provider) ?? null;
|
|
155
|
+
const profilePath = opts.profilePathFor?.(provider, profileName) ?? null;
|
|
128
156
|
return profilePath ? { ...env, SPECRAILS_PROFILE_PATH: profilePath } : env;
|
|
129
157
|
}
|
|
130
|
-
catch {
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (profileName)
|
|
160
|
+
throw error;
|
|
131
161
|
return env;
|
|
132
162
|
}
|
|
133
163
|
};
|
|
134
164
|
return {
|
|
135
165
|
// Cheap working-tree fingerprint for the engine's non-convergence guard:
|
|
136
|
-
// HEAD
|
|
166
|
+
// HEAD, tracked diffs and untracked file contents, hashed. Status/path names
|
|
167
|
+
// alone stay identical while a fixer keeps improving the same files.
|
|
168
|
+
// Two identical fingerprints
|
|
137
169
|
// across consecutive Decider 'continue' verdicts ⇒ the loop changed nothing
|
|
138
170
|
// ⇒ abort `stalled`. Synchronous + best-effort: any git failure (no repo,
|
|
139
171
|
// detached, etc.) returns null so the guard simply stays off. Never throws.
|
|
@@ -144,13 +176,34 @@ function createLoopExecutors(opts = {}) {
|
|
|
144
176
|
// `-z` NUL-delimited + include untracked so a brand-new file counts as
|
|
145
177
|
// a change; stable ordering makes the hash deterministic per tree state.
|
|
146
178
|
const porcelain = (0, node_child_process_1.execFileSync)('git', ['status', '--porcelain', '-z', '--untracked-files=all'], opt);
|
|
147
|
-
|
|
179
|
+
const hash = (0, node_crypto_1.createHash)('sha256').update(head).update('\0').update(porcelain);
|
|
180
|
+
// Separate index/worktree diffs preserve staged-only changes and staged
|
|
181
|
+
// content subsequently edited back to HEAD. Binary patches include
|
|
182
|
+
// actual bytes; disable external diff/textconv so this is read-only.
|
|
183
|
+
for (const args of [
|
|
184
|
+
['diff', '--no-ext-diff', '--no-textconv', '--binary', '--'],
|
|
185
|
+
['diff', '--cached', '--no-ext-diff', '--no-textconv', '--binary', '--'],
|
|
186
|
+
])
|
|
187
|
+
hash.update('\0').update((0, node_child_process_1.execFileSync)('git', args, { ...opt, maxBuffer: 16 * 1024 * 1024 }));
|
|
188
|
+
const untracked = (0, node_child_process_1.execFileSync)('git', ['ls-files', '--others', '--exclude-standard', '-z'], opt);
|
|
189
|
+
for (const relative of untracked.split('\0').filter(Boolean).sort()) {
|
|
190
|
+
const file = (0, node_path_1.resolve)(dir, relative);
|
|
191
|
+
const stat = (0, node_fs_1.lstatSync)(file);
|
|
192
|
+
hash.update('\0').update(relative).update('\0').update(String(stat.mode));
|
|
193
|
+
// Never follow a symlink outside the worktree. If an unreadable file
|
|
194
|
+
// or a racing delete defeats the snapshot, disable this sample.
|
|
195
|
+
if (stat.isSymbolicLink())
|
|
196
|
+
hash.update((0, node_fs_1.readlinkSync)(file));
|
|
197
|
+
else if (stat.isFile())
|
|
198
|
+
hash.update((0, node_fs_1.readFileSync)(file));
|
|
199
|
+
}
|
|
200
|
+
return hash.digest('hex');
|
|
148
201
|
}
|
|
149
202
|
catch {
|
|
150
203
|
return null;
|
|
151
204
|
}
|
|
152
205
|
},
|
|
153
|
-
async runAiStep({ prompt, sessionId, provider, model, effort, cwd, repoDir, onLine, onRawLine, onSpawn, aiStepTimeoutMs }) {
|
|
206
|
+
async runAiStep({ coreRun, prompt, sessionId, provider, model, effort, profileName, cwd, repoDir, executionManifest, onLine, onRawLine, onSpawn, aiStepTimeoutMs, idleTimeoutMs }) {
|
|
154
207
|
const adapter = (0, providers_1.getAdapter)(provider);
|
|
155
208
|
// First iteration spawns headless (rail-job); subsequent iterations resume
|
|
156
209
|
// the session so the agent keeps prior context across iterations.
|
|
@@ -160,14 +213,22 @@ function createLoopExecutors(opts = {}) {
|
|
|
160
213
|
// for the pipeline's I/O exactly like QueueManager: SPECRAILS_REPO_DIR +
|
|
161
214
|
// claude `--add-dir <repoDir>` (see the shared helpers above). Best-effort
|
|
162
215
|
// agent self-heal on Windows.
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
216
|
+
const baseStepEnv = withProfileEnv(aiStepEnv(resolveEnv(), repoDir, executionManifest), provider, profileName);
|
|
217
|
+
const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv }) : undefined;
|
|
218
|
+
const stepEnv = core?.env ?? baseStepEnv;
|
|
219
|
+
let extraArgs = aiStepExtraArgs(adapter, cwd, repoDir, executionManifest);
|
|
220
|
+
const pluginScope = adapter.id === 'codex' ? opts.pluginScope?.() : undefined;
|
|
221
|
+
const pluginArgs = (0, codex_spawn_1.buildCodexPluginArgs)({ providerId: adapter.id, stateRoot: pluginScope?.stateRoot ?? stepEnv.SPECRAILS_WORKSPACE_DIR ?? cwd, repositoryPath: repoDir ?? cwd, legacyProviderId: pluginScope?.legacyProviderId });
|
|
222
|
+
if (pluginArgs.length)
|
|
223
|
+
extraArgs = [...(extraArgs ?? []), ...pluginArgs];
|
|
224
|
+
const effectivePrompt = (0, runtime_1.formatProviderCommand)(adapter, [core?.promptPrefix, prompt].filter(Boolean).join('\n\n'), cwd, sessionId ?? undefined);
|
|
166
225
|
const buildOpts = {
|
|
167
226
|
prompt: effectivePrompt,
|
|
227
|
+
...(adapter.id === 'claude' ? { systemPrompt: loop_constants_1.FOREGROUND_RULE } : {}),
|
|
168
228
|
model,
|
|
169
229
|
sessionId: sessionId ?? undefined,
|
|
170
230
|
reasoning_effort: effort,
|
|
231
|
+
...(executionManifest ? { scopedWorkingDirectories: true } : {}),
|
|
171
232
|
extraArgs,
|
|
172
233
|
};
|
|
173
234
|
if (repoDir) {
|
|
@@ -180,15 +241,32 @@ function createLoopExecutors(opts = {}) {
|
|
|
180
241
|
// Pre-trust the spawn dir so headless claude honours the overlaid
|
|
181
242
|
// `.claude/settings.json` permissions.allow (else "workspace not trusted").
|
|
182
243
|
try {
|
|
183
|
-
(0, claude_trust_1.ensureClaudeTrusted)(adapter.id, [cwd, repoDir]);
|
|
244
|
+
(0, claude_trust_1.ensureClaudeTrusted)(adapter.id, [cwd, repoDir, ...(executionManifest?.repositories.map((repo) => repo.worktreePath) ?? [])]);
|
|
184
245
|
}
|
|
185
246
|
catch { /* best-effort */ }
|
|
247
|
+
// Gemini acknowledges project agents before headless execution. Rails
|
|
248
|
+
// using the loop engine need the same adapter preparation as queue jobs.
|
|
249
|
+
try {
|
|
250
|
+
adapter.prepareHeadlessSpawn?.(cwd);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
console.warn(`[loop] provider preparation failed: ${err.message}`);
|
|
254
|
+
}
|
|
186
255
|
let text = '';
|
|
187
256
|
// The adapter parses provider failures (codex `turn.failed`, etc.) into a
|
|
188
257
|
// structured `error` event on the stdout stream — capture its message so we
|
|
189
258
|
// surface the REAL reason (e.g. "You've hit your usage limit") instead of
|
|
190
259
|
// the `stderrTail`, which often holds only an informational line.
|
|
191
260
|
let errorText;
|
|
261
|
+
// Idle watchdog (loop-step-idle): the child produced NO stdout/stderr for
|
|
262
|
+
// the idle budget — a wedge, not a slow step. Flagged separately from the
|
|
263
|
+
// wall-clock timeout so the engine can retry the step once by resume.
|
|
264
|
+
let stalled = false;
|
|
265
|
+
// The provider flagged the final `result` as an error (claude
|
|
266
|
+
// `is_error: true` — a usage/rate-limit notice returned AS the reply).
|
|
267
|
+
let resultIsError = false;
|
|
268
|
+
let liveBackgroundTasks = [];
|
|
269
|
+
const effectiveIdleTimeoutMs = idleTimeoutMs ?? inactivityTimeoutMs();
|
|
192
270
|
const wallStartedAt = Date.now();
|
|
193
271
|
const res = await (0, spawn_lifecycle_1.runAiCliInvocation)({
|
|
194
272
|
adapter,
|
|
@@ -199,13 +277,25 @@ function createLoopExecutors(opts = {}) {
|
|
|
199
277
|
// 0 ⇒ watchdog disabled (factory loops run untimed; the loop's
|
|
200
278
|
// maxIterations / cost cap remain the runaway guards).
|
|
201
279
|
timeoutMs: (aiStepTimeoutMs ?? AI_STEP_TIMEOUT_MS) > 0 ? (aiStepTimeoutMs ?? AI_STEP_TIMEOUT_MS) : undefined,
|
|
280
|
+
// The engine's explicit bound wins, including 0. Direct callers retain
|
|
281
|
+
// the existing env-resolved watchdog when no bound was supplied.
|
|
282
|
+
inactivityTimeoutMs: effectiveIdleTimeoutMs > 0 ? effectiveIdleTimeoutMs : undefined,
|
|
283
|
+
onInactivityTimeout: () => { stalled = true; },
|
|
202
284
|
onSpawn,
|
|
203
285
|
// Two complementary streams, mirroring QueueManager's contract:
|
|
204
286
|
// • RAW JSONL via onStdoutLine → engine emits parsed `event`s that drive
|
|
205
287
|
// JobStatusPanel activity (LogViewer SKIPS these to avoid dupes).
|
|
206
288
|
// • display text/tools via onEvent → engine emits `log` lines that
|
|
207
289
|
// LogViewer actually RENDERS (the visible transcript).
|
|
208
|
-
onStdoutLine:
|
|
290
|
+
onStdoutLine: (line) => {
|
|
291
|
+
if (adapter.id === 'claude') {
|
|
292
|
+
try {
|
|
293
|
+
liveBackgroundTasks = (0, claude_background_tasks_1.readClaudeBackgroundTasks)(JSON.parse(line)) ?? liveBackgroundTasks;
|
|
294
|
+
}
|
|
295
|
+
catch { /* ordinary output */ }
|
|
296
|
+
}
|
|
297
|
+
onRawLine?.(line);
|
|
298
|
+
},
|
|
209
299
|
onEvent: (ev) => {
|
|
210
300
|
if (ev.kind === 'text-delta') {
|
|
211
301
|
text += ev.text;
|
|
@@ -215,18 +305,39 @@ function createLoopExecutors(opts = {}) {
|
|
|
215
305
|
onLine?.(`🔧 ${ev.name} ${ev.inputPreview ?? ''}`.trim());
|
|
216
306
|
else if (ev.kind === 'error' && ev.message)
|
|
217
307
|
errorText = ev.message;
|
|
308
|
+
else if (ev.kind === 'result') {
|
|
309
|
+
const terminalError = (0, terminal_result_1.terminalResultError)(ev.payload);
|
|
310
|
+
errorText = terminalError ?? errorText;
|
|
311
|
+
if (ev.isError || terminalError)
|
|
312
|
+
resultIsError = true;
|
|
313
|
+
}
|
|
218
314
|
},
|
|
219
315
|
});
|
|
316
|
+
// The kill-switch one-shot transport cannot continue a resident parent.
|
|
317
|
+
// Never certify a clean CLI exit as completed work if its workers were
|
|
318
|
+
// still pending. Interactive mode can join them in the same session.
|
|
319
|
+
if (liveBackgroundTasks.length > 0 && !errorText) {
|
|
320
|
+
errorText = `Agent exited with ${liveBackgroundTasks.length} unfinished background task(s); implementation step is incomplete.`;
|
|
321
|
+
}
|
|
220
322
|
const failed = res.spawnFailed ||
|
|
221
323
|
res.timedOut ||
|
|
222
|
-
|
|
223
|
-
errorText !== undefined
|
|
324
|
+
res.code !== 0 ||
|
|
325
|
+
errorText !== undefined ||
|
|
326
|
+
resultIsError;
|
|
224
327
|
if (res.spawnFailed) {
|
|
225
328
|
errorText = errorText ?? `failed to spawn "${adapter.binary}" (on PATH?)`;
|
|
226
329
|
const msg = `AI step: ${errorText}`;
|
|
227
330
|
console.error(`[loop] ${msg}`);
|
|
228
331
|
onLine?.(msg, 'stderr');
|
|
229
332
|
}
|
|
333
|
+
else if (stalled) {
|
|
334
|
+
// The inactivity watchdog tore the child down (settles timedOut too, so
|
|
335
|
+
// this branch must precede the wall-clock one). The engine retries once.
|
|
336
|
+
errorText = loop_step_idle_1.AI_STEP_STALLED_ERROR;
|
|
337
|
+
const msg = `AI step: ${adapter.binary} produced no output for ${Math.round(effectiveIdleTimeoutMs / 1000)}s — stalled`;
|
|
338
|
+
console.error(`[loop] ${msg}`);
|
|
339
|
+
onLine?.(msg, 'stderr');
|
|
340
|
+
}
|
|
230
341
|
else if (res.timedOut) {
|
|
231
342
|
// A wedged/unresponsive provider settles with code=null + timedOut. Count
|
|
232
343
|
// it as a failure so the engine's fail-fast can bail instead of paying the
|
|
@@ -236,11 +347,20 @@ function createLoopExecutors(opts = {}) {
|
|
|
236
347
|
console.error(`[loop] ${msg}`);
|
|
237
348
|
onLine?.(msg, 'stderr');
|
|
238
349
|
}
|
|
239
|
-
else if (
|
|
350
|
+
else if (resultIsError) {
|
|
351
|
+
// The provider returned an error AS the reply (claude `is_error: true`
|
|
352
|
+
// — typically a usage/rate-limit notice). The text IS the reason.
|
|
353
|
+
errorText = errorText ?? (text.trim().slice(-300) || 'the provider returned an error result');
|
|
354
|
+
const msg = `AI step: provider returned an error result — ${errorText}`;
|
|
355
|
+
console.error(`[loop] ${msg}`);
|
|
356
|
+
onLine?.(msg, 'stderr');
|
|
357
|
+
}
|
|
358
|
+
else if (res.code !== 0) {
|
|
240
359
|
// Prefer the adapter's structured error (e.g. codex `turn.failed`:
|
|
241
360
|
// "You've hit your usage limit") over `stderrTail`, which for codex holds
|
|
242
361
|
// only the informational "Reading additional input from stdin…" line.
|
|
243
362
|
const reason = errorText ?? (res.stderrTail ? res.stderrTail.slice(0, 300) : '');
|
|
363
|
+
errorText = reason || `${adapter.binary} terminated without a successful exit (code=${res.code})`;
|
|
244
364
|
const msg = `AI step: ${adapter.binary} exited code=${res.code}${reason ? ` — ${reason}` : ''}`;
|
|
245
365
|
console.error(`[loop] ${msg}`);
|
|
246
366
|
onLine?.(msg, 'stderr');
|
|
@@ -286,6 +406,8 @@ function createLoopExecutors(opts = {}) {
|
|
|
286
406
|
estimated,
|
|
287
407
|
failed,
|
|
288
408
|
errorText,
|
|
409
|
+
...(stalled ? { stalled: true } : {}),
|
|
410
|
+
...(resultIsError ? { resultIsError: true } : {}),
|
|
289
411
|
};
|
|
290
412
|
},
|
|
291
413
|
/**
|
|
@@ -298,14 +420,16 @@ function createLoopExecutors(opts = {}) {
|
|
|
298
420
|
* spike-verified on claude 2.1.198). Null ⇒ the engine one-shots the step
|
|
299
421
|
* (non-claude providers, or the kill-switch) — byte-identical legacy.
|
|
300
422
|
*/
|
|
301
|
-
planInteractiveAiStep({ provider, model, effort, cwd, repoDir, sessionId, aiStepTimeoutMs }) {
|
|
423
|
+
planInteractiveAiStep({ coreRun, provider, model, effort, profileName, cwd, repoDir, executionManifest, sessionId, aiStepTimeoutMs, idleTimeoutMs }) {
|
|
302
424
|
if (!(0, feature_flags_1.isInteractiveJobsEnabled)())
|
|
303
425
|
return null;
|
|
304
426
|
const adapter = (0, providers_1.getAdapter)(provider);
|
|
305
427
|
if (!adapter.capabilities.persistentStdin)
|
|
306
428
|
return null;
|
|
307
|
-
const
|
|
308
|
-
const
|
|
429
|
+
const baseStepEnv = withProfileEnv(aiStepEnv(resolveEnv(), repoDir, executionManifest), provider, profileName);
|
|
430
|
+
const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv }) : undefined;
|
|
431
|
+
const stepEnv = core?.env ?? baseStepEnv;
|
|
432
|
+
const extraArgs = aiStepExtraArgs(adapter, cwd, repoDir, executionManifest);
|
|
309
433
|
if (repoDir) {
|
|
310
434
|
try {
|
|
311
435
|
(0, workspace_manager_1.ensureFrameworkAgents)(cwd, adapter.projectDirName);
|
|
@@ -316,9 +440,15 @@ function createLoopExecutors(opts = {}) {
|
|
|
316
440
|
// Pre-trust the spawn dir so headless claude honours the overlaid
|
|
317
441
|
// `.claude/settings.json` permissions.allow (else "workspace not trusted").
|
|
318
442
|
try {
|
|
319
|
-
(0, claude_trust_1.ensureClaudeTrusted)(adapter.id, [cwd, repoDir]);
|
|
443
|
+
(0, claude_trust_1.ensureClaudeTrusted)(adapter.id, [cwd, repoDir, ...(executionManifest?.repositories.map((repo) => repo.worktreePath) ?? [])]);
|
|
320
444
|
}
|
|
321
445
|
catch { /* best-effort */ }
|
|
446
|
+
try {
|
|
447
|
+
adapter.prepareHeadlessSpawn?.(cwd);
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
console.warn(`[loop] provider preparation failed: ${err.message}`);
|
|
451
|
+
}
|
|
322
452
|
const buildOpts = {
|
|
323
453
|
// chat-stream feeds the prompt over stdin per-turn, so the argv `prompt`
|
|
324
454
|
// is unused — pass empty to satisfy the shared SpawnOptions shape.
|
|
@@ -329,28 +459,36 @@ function createLoopExecutors(opts = {}) {
|
|
|
329
459
|
// drops the session id at an iterate-loop's loop-back).
|
|
330
460
|
sessionId: sessionId ?? undefined,
|
|
331
461
|
reasoning_effort: effort,
|
|
462
|
+
...(executionManifest ? { scopedWorkingDirectories: true } : {}),
|
|
332
463
|
extraArgs,
|
|
333
464
|
};
|
|
334
465
|
const args = adapter.buildArgs('chat-stream', buildOpts);
|
|
466
|
+
const effectiveIdleTimeoutMs = idleTimeoutMs ?? inactivityTimeoutMs();
|
|
335
467
|
return {
|
|
336
468
|
adapter,
|
|
337
469
|
spec: { binary: adapter.binary, args, cwd, env: (0, runtime_1.buildProviderEnv)(adapter, buildOpts, stepEnv) },
|
|
338
470
|
// The loop's ai-step timeout bounds the WHOLE step, interactive
|
|
339
471
|
// included. 0 ⇒ unbounded (the engine skips arming the step timer).
|
|
472
|
+
...(core?.promptPrefix ? { promptPrefix: core.promptPrefix } : {}),
|
|
340
473
|
stepTimeoutMs: aiStepTimeoutMs ?? AI_STEP_TIMEOUT_MS,
|
|
474
|
+
// Keep the old field for callers that already inspect the plan.
|
|
475
|
+
inactivityTimeoutMs: effectiveIdleTimeoutMs,
|
|
476
|
+
...(effectiveIdleTimeoutMs > 0 ? { idleTimeoutMs: effectiveIdleTimeoutMs } : {}),
|
|
341
477
|
};
|
|
342
478
|
},
|
|
343
|
-
async runShell({ command, cwd, onLine, onSpawn }) {
|
|
344
|
-
|
|
479
|
+
async runShell({ command, cwd, repoDir, onLine, onSpawn, timeoutMs }) {
|
|
480
|
+
const env = resolveEnv();
|
|
481
|
+
return runShellCommand(command, repoDir ?? cwd, repoDir ? { ...env, SPECRAILS_REPO_DIR: repoDir } : env, timeoutMs ?? SHELL_TIMEOUT_MS, onLine, onSpawn);
|
|
345
482
|
},
|
|
346
|
-
async runDecider({ systemPrompt, userPrompt, provider, model, effort, cwd, repoDir, onRawLine, onSpawn }) {
|
|
483
|
+
async runDecider({ systemPrompt, userPrompt, provider, model, effort, cwd, repoDir, executionManifest, onRawLine, onSpawn, timeoutMs }) {
|
|
347
484
|
const adapter = (0, providers_1.getAdapter)(provider);
|
|
348
485
|
let text = '';
|
|
486
|
+
let errorText;
|
|
349
487
|
const baseEnv = resolveEnv();
|
|
350
|
-
const decEnv =
|
|
488
|
+
const decEnv = aiStepEnv(baseEnv, repoDir, executionManifest);
|
|
351
489
|
// spec-gen is a one-shot, system-prompted invocation (workspace-write on
|
|
352
490
|
// codex, not full-access) — appropriate for a read-only judgment.
|
|
353
|
-
const buildOpts = { prompt: userPrompt, systemPrompt, model, maxTurns: 1, reasoning_effort: effort, toolPolicy: 'read-only' };
|
|
491
|
+
const buildOpts = { prompt: userPrompt, systemPrompt, model, maxTurns: 1, reasoning_effort: effort, toolPolicy: 'read-only', ...(executionManifest ? { extraArgs: aiStepExtraArgs(adapter, cwd, repoDir, executionManifest) } : {}) };
|
|
354
492
|
const wallStartedAt = Date.now();
|
|
355
493
|
const res = await (0, spawn_lifecycle_1.runAiCliInvocation)({
|
|
356
494
|
adapter,
|
|
@@ -358,13 +496,24 @@ function createLoopExecutors(opts = {}) {
|
|
|
358
496
|
buildOpts,
|
|
359
497
|
cwd,
|
|
360
498
|
env: (0, runtime_1.buildProviderEnv)(adapter, buildOpts, decEnv),
|
|
361
|
-
timeoutMs: DECIDER_TIMEOUT_MS,
|
|
499
|
+
timeoutMs: timeoutMs ?? DECIDER_TIMEOUT_MS,
|
|
362
500
|
onSpawn,
|
|
363
501
|
onStdoutLine: onRawLine,
|
|
364
|
-
onEvent: (ev) => {
|
|
365
|
-
|
|
502
|
+
onEvent: (ev) => {
|
|
503
|
+
if (ev.kind === 'text-delta')
|
|
504
|
+
text += ev.text;
|
|
505
|
+
else if (ev.kind === 'error' && ev.message)
|
|
506
|
+
errorText = ev.message;
|
|
507
|
+
else if (ev.kind === 'result')
|
|
508
|
+
errorText = (0, terminal_result_1.terminalResultError)(ev.payload) ?? errorText;
|
|
509
|
+
},
|
|
366
510
|
});
|
|
367
|
-
|
|
511
|
+
// A streamed verdict from an interrupted/failed invocation is not an
|
|
512
|
+
// authoritative completion gate, even when the partial JSON parses.
|
|
513
|
+
const failed = res.spawnFailed || res.timedOut || res.code !== 0 || errorText !== undefined;
|
|
514
|
+
const decision = failed
|
|
515
|
+
? { continue: true, blocked: false, parsed: false, reasoning: errorText ?? (res.timedOut ? 'Decider timed out; verification is incomplete.' : res.stderrTail || 'Decider failed; verification is incomplete.') }
|
|
516
|
+
: (0, loop_decider_1.parseDeciderDecision)(text);
|
|
368
517
|
const { result, estimated } = (0, result_event_1.finaliseInvocationResult)(adapter, res.events, {
|
|
369
518
|
fallbackModel: model,
|
|
370
519
|
durationMs: Math.max(0, Date.now() - wallStartedAt),
|
|
@@ -6,7 +6,7 @@ exports.isFactoryLoopId = isFactoryLoopId;
|
|
|
6
6
|
exports.factoryLoopMode = factoryLoopMode;
|
|
7
7
|
exports.factoryLoopForMode = factoryLoopForMode;
|
|
8
8
|
const loop_templates_1 = require("./loop-templates");
|
|
9
|
-
const GREEN_GOAL = 'Stop only when the latest verification step reports {{const:VERIFICATION_PASS}} and the history proves
|
|
9
|
+
const GREEN_GOAL = 'Stop only when the latest verification step reports {{const:VERIFICATION_PASS}} and the history proves every acceptance criterion is implemented across all required tickets and repositories, with the relevant behavioral checks and configured tests/build passing. Green baseline checks, setup, or planning alone do not prove completion.';
|
|
10
10
|
// Factory loops run the WHOLE architect→developer→reviewer pipeline inside a
|
|
11
11
|
// single AI step (`/specrails:implement` etc.), so no fixed wall-clock budget is
|
|
12
12
|
// honest — a legit implement outran the old 60-min step cap and got killed
|
|
@@ -29,7 +29,7 @@ exports.FACTORY_LOOPS = [
|
|
|
29
29
|
{
|
|
30
30
|
id: 'factory:implement',
|
|
31
31
|
name: 'Implement',
|
|
32
|
-
description: '
|
|
32
|
+
description: 'Implement the spec, verify its acceptance criteria and project checks, then complete missing work or fix defects until the full change passes.',
|
|
33
33
|
mode: 'implement',
|
|
34
34
|
graph: (0, loop_templates_1.fixLoopGraph)(['{{cmd:implement}}'], GREEN_GOAL, FACTORY_MAX_ITERATIONS, FACTORY_LOOP_TIMEOUT_MIN, FACTORY_AI_STEP_TIMEOUT_MIN),
|
|
35
35
|
},
|
|
@@ -60,9 +60,16 @@ exports.FACTORY_LOOPS = [
|
|
|
60
60
|
// renders as an EMPTY string, so a hand-launched run would have a blank
|
|
61
61
|
// central instruction. The launch route refuses it; the prompt guards too.
|
|
62
62
|
launchable: false,
|
|
63
|
-
description: "Apply the ONE change the user asked for on top of work already delivered,
|
|
63
|
+
description: "Apply the ONE change the user asked for on top of work already delivered, then run one independent reviewer-owned verification gate and refine until green. No re-planning. Runs automatically when you ask for changes on a review — it is not started by hand.",
|
|
64
64
|
mode: 'loop',
|
|
65
|
-
|
|
65
|
+
// Revision is the ONLY factory loop that swaps the generic `{{cmd:verify}}`
|
|
66
|
+
// gate: `{{cmd:revision-verify}}` owns reviewer re-grading AND the full pass
|
|
67
|
+
// of record in ONE read-only step, so the mutator no longer runs sr-reviewer
|
|
68
|
+
// itself and no second repository-wide gate follows it. The trailing `true`
|
|
69
|
+
// makes verify/fix run in a FRESH provider session — the gate's verdict and
|
|
70
|
+
// its confidence artifact then describe the candidate on disk rather than
|
|
71
|
+
// the mutator's own account of it.
|
|
72
|
+
graph: (0, loop_templates_1.fixLoopGraph)(['{{cmd:revise}}'], GREEN_GOAL, FACTORY_MAX_ITERATIONS, FACTORY_LOOP_TIMEOUT_MIN, FACTORY_AI_STEP_TIMEOUT_MIN, '{{cmd:revision-verify}}', true),
|
|
66
73
|
},
|
|
67
74
|
SDD_QUICK_OPENSPEC_FACTORY,
|
|
68
75
|
];
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* trivially unit-testable and reused verbatim by both the store and the engine.
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.assertLoopShellRepositoryScope = assertLoopShellRepositoryScope;
|
|
13
14
|
exports.emptyLoopGraph = emptyLoopGraph;
|
|
14
15
|
exports.validateLoopGraph = validateLoopGraph;
|
|
15
16
|
exports.nodesById = nodesById;
|
|
@@ -17,6 +18,15 @@ exports.findStartNode = findStartNode;
|
|
|
17
18
|
exports.successors = successors;
|
|
18
19
|
exports.loopNeedsTicket = loopNeedsTicket;
|
|
19
20
|
exports.interpolateSpec = interpolateSpec;
|
|
21
|
+
/** A named shell target must never silently fall back to another checkout. */
|
|
22
|
+
function assertLoopShellRepositoryScope(graph, repositoryIds) {
|
|
23
|
+
for (const node of graph.nodes) {
|
|
24
|
+
const target = node.type === 'shell' ? node.data?.repositoryId : undefined;
|
|
25
|
+
if (target !== undefined && (typeof target !== 'string' || !repositoryIds.includes(target))) {
|
|
26
|
+
throw new Error(`Shell step ${node.id} targets a repository outside this launch`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
20
30
|
/** A minimal, structurally-valid empty graph (used to seed a new Draft). */
|
|
21
31
|
function emptyLoopGraph() {
|
|
22
32
|
return { nodes: [], edges: [], config: { maxIterations: 10, timeoutMinutes: 30 } };
|
|
@@ -34,8 +44,32 @@ function emptyLoopGraph() {
|
|
|
34
44
|
*/
|
|
35
45
|
function validateLoopGraph(graph) {
|
|
36
46
|
const errors = [];
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
if (!graph || !Array.isArray(graph.nodes) || !Array.isArray(graph.edges)) {
|
|
48
|
+
return { valid: false, errors: [{ code: 'INVALID_CONFIG', message: 'A loop graph must contain nodes and edges arrays.' }] };
|
|
49
|
+
}
|
|
50
|
+
const nodes = graph.nodes;
|
|
51
|
+
const edges = graph.edges;
|
|
52
|
+
const kinds = new Set(['start', 'ai-step', 'shell', 'decider', 'condition', 'end']);
|
|
53
|
+
if (nodes.some((node) => !node || typeof node.id !== 'string' || !node.id.trim() || !kinds.has(node.type))) {
|
|
54
|
+
return { valid: false, errors: [{ code: 'INVALID_NODE', message: 'Every node needs a non-empty id and a supported node type.' }] };
|
|
55
|
+
}
|
|
56
|
+
if (edges.some((edge) => !edge || typeof edge.id !== 'string' || !edge.id.trim() || typeof edge.source !== 'string' || typeof edge.target !== 'string')) {
|
|
57
|
+
return { valid: false, errors: [{ code: 'DANGLING_EDGE', message: 'Every edge needs a non-empty id, source and target.' }] };
|
|
58
|
+
}
|
|
59
|
+
for (const [items, code] of [[nodes, 'DUPLICATE_NODE'], [edges, 'DUPLICATE_EDGE']]) {
|
|
60
|
+
const ids = new Set();
|
|
61
|
+
for (const item of items) {
|
|
62
|
+
if (ids.has(item.id))
|
|
63
|
+
errors.push({ code, message: `Duplicate ${code === 'DUPLICATE_NODE' ? 'node' : 'edge'} id "${item.id}".` });
|
|
64
|
+
ids.add(item.id);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const node of nodes) {
|
|
68
|
+
if (node.type === 'shell' && node.data?.repositoryId !== undefined &&
|
|
69
|
+
(typeof node.data.repositoryId !== 'string' || !node.data.repositoryId.trim())) {
|
|
70
|
+
errors.push({ code: 'INVALID_NODE', nodeId: node.id, message: 'A shell repositoryId must be a non-empty registered repository id.' });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
39
73
|
// ── Start cardinality ──────────────────────────────────────────────────────
|
|
40
74
|
const starts = nodes.filter((n) => n.type === 'start');
|
|
41
75
|
if (starts.length === 0) {
|
|
@@ -61,10 +95,12 @@ function validateLoopGraph(graph) {
|
|
|
61
95
|
cfg.maxIterations < 1 ||
|
|
62
96
|
typeof cfg.timeoutMinutes !== 'number' ||
|
|
63
97
|
!Number.isFinite(cfg.timeoutMinutes) ||
|
|
64
|
-
cfg.timeoutMinutes < 0
|
|
98
|
+
cfg.timeoutMinutes < 0 ||
|
|
99
|
+
(cfg.aiStepTimeoutMinutes !== undefined && (!Number.isFinite(cfg.aiStepTimeoutMinutes) || cfg.aiStepTimeoutMinutes < 0)) ||
|
|
100
|
+
(cfg.maxCostUsd !== undefined && !Number.isFinite(cfg.maxCostUsd))) {
|
|
65
101
|
errors.push({
|
|
66
102
|
code: 'INVALID_CONFIG',
|
|
67
|
-
message: 'maxIterations must be ≥ 1 and
|
|
103
|
+
message: 'maxIterations must be ≥ 1; timeouts must be finite and ≥ 0 (0 = no timeout); maxCostUsd must be finite.',
|
|
68
104
|
});
|
|
69
105
|
}
|
|
70
106
|
// ── Dangling edges ────────────────────────────────────────────────────────────
|
|
@@ -78,6 +114,33 @@ function validateLoopGraph(graph) {
|
|
|
78
114
|
});
|
|
79
115
|
}
|
|
80
116
|
}
|
|
117
|
+
// The executor follows one successor at a time. Publishing a branching AI,
|
|
118
|
+
// shell or condition node would silently skip all but its first branch.
|
|
119
|
+
for (const node of nodes) {
|
|
120
|
+
const out = edges.filter((edge) => edge.source === node.id);
|
|
121
|
+
if (node.type === 'end') {
|
|
122
|
+
if (out.length)
|
|
123
|
+
errors.push({ code: 'INVALID_BRANCH', nodeId: node.id, message: 'End nodes cannot have outgoing edges.' });
|
|
124
|
+
}
|
|
125
|
+
else if (node.type === 'decider') {
|
|
126
|
+
const labeled = out.some((edge) => edge.branch !== undefined);
|
|
127
|
+
const validBranches = labeled
|
|
128
|
+
? out.length === 2 && out.filter((edge) => edge.branch === 'continue').length === 1 && out.filter((edge) => edge.branch === 'stop').length === 1
|
|
129
|
+
: out.length === 2 && out.filter((edge) => nodes.find((candidate) => candidate.id === edge.target)?.type === 'end').length === 1;
|
|
130
|
+
if (!validBranches)
|
|
131
|
+
errors.push({ code: 'INVALID_BRANCH', nodeId: node.id, message: 'A Decider needs exactly one continue branch and one stop branch.' });
|
|
132
|
+
const cont = out.find((edge) => edge.branch === 'continue');
|
|
133
|
+
if (cont && nodes.find((candidate) => candidate.id === cont.target)?.type === 'end') {
|
|
134
|
+
errors.push({ code: 'INVALID_BRANCH', nodeId: node.id, message: 'The continue branch must lead to another step, not End.' });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (!out.length) {
|
|
138
|
+
errors.push({ code: 'DEAD_END', nodeId: node.id, message: `Node "${node.id}" needs a next step or an End.` });
|
|
139
|
+
}
|
|
140
|
+
else if (out.length !== 1) {
|
|
141
|
+
errors.push({ code: 'UNSUPPORTED_BRANCHING', nodeId: node.id, message: `Node "${node.id}" has multiple successors; use sequential steps or a Decider with explicit branches.` });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
81
144
|
// ── Reachability from Start (orphan detection) ──────────────────────────────
|
|
82
145
|
// Only meaningful with exactly one Start; otherwise the start-cardinality
|
|
83
146
|
// errors above already explain the shape problem.
|
|
@@ -151,6 +214,29 @@ function loopNeedsTicket(graph) {
|
|
|
151
214
|
}
|
|
152
215
|
function interpolateSpec(text, spec) {
|
|
153
216
|
return text.replace(/\{\{\s*spec\.(\w+)\s*\}\}/g, (_match, key) => {
|
|
217
|
+
if (key === 'scope') {
|
|
218
|
+
if (!spec)
|
|
219
|
+
return '';
|
|
220
|
+
const changeName = typeof spec.openspecChangeName === 'string' ? spec.openspecChangeName.trim()
|
|
221
|
+
: typeof spec.metadata?.openspecChangeName === 'string' ? spec.metadata.openspecChangeName.trim() : undefined;
|
|
222
|
+
// Explicit task data, not arbitrary metadata or injected template code.
|
|
223
|
+
// Preserve all batch descriptions so later fresh verification/repair
|
|
224
|
+
// sessions do not have to reconstruct acceptance scope from short logs.
|
|
225
|
+
const scope = JSON.stringify({
|
|
226
|
+
id: spec.id,
|
|
227
|
+
ticketIds: spec.ticketIds,
|
|
228
|
+
title: spec.title,
|
|
229
|
+
description: spec.description,
|
|
230
|
+
repositoryIds: spec.repositoryIds,
|
|
231
|
+
openspecChangeName: changeName || undefined,
|
|
232
|
+
tickets: spec.tickets?.map(ticket => ({ id: ticket.id, title: ticket.title, description: ticket.description, repositoryIds: ticket.repositoryIds })),
|
|
233
|
+
}, null, 2);
|
|
234
|
+
if (scope === '{}')
|
|
235
|
+
return '';
|
|
236
|
+
// Constants/run-vars expand after spec data. Escape their delimiters
|
|
237
|
+
// inside JSON strings, along with the surrounding data block delimiter.
|
|
238
|
+
return scope.replace(/\{\{/g, '\\u007b\\u007b').replace(/\}\}/g, '\\u007d\\u007d').replace(/</g, '\\u003c').replace(/>/g, '\\u003e');
|
|
239
|
+
}
|
|
154
240
|
// `{{spec.ids}}` → all rail ticket ids as `#1 #2 #3` (used by implement/batch).
|
|
155
241
|
if (key === 'ids') {
|
|
156
242
|
const ids = spec?.ticketIds ?? (spec?.id != null ? [spec.id] : []);
|