@repo-os/repoos 0.5.45
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/LICENSE.md +110 -0
- package/README.md +272 -0
- package/dist/.build-info.json +4 -0
- package/dist/.build-stamp.json +3 -0
- package/dist/cli/colors.d.ts +13 -0
- package/dist/cli/colors.js +37 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +221 -0
- package/dist/commands/check.d.ts +170 -0
- package/dist/commands/check.js +1227 -0
- package/dist/commands/docs.d.ts +2 -0
- package/dist/commands/docs.js +70 -0
- package/dist/commands/gc.d.ts +1 -0
- package/dist/commands/gc.js +57 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +699 -0
- package/dist/commands/screenshots.d.ts +7 -0
- package/dist/commands/screenshots.js +77 -0
- package/dist/commands/serve.d.ts +55 -0
- package/dist/commands/serve.js +204 -0
- package/dist/commands/status.d.ts +140 -0
- package/dist/commands/status.js +569 -0
- package/dist/commands/stop.d.ts +1 -0
- package/dist/commands/stop.js +59 -0
- package/dist/commands/tasks.d.ts +47 -0
- package/dist/commands/tasks.js +334 -0
- package/dist/commands/tunnel.d.ts +1 -0
- package/dist/commands/tunnel.js +1271 -0
- package/dist/commands/ui-harness.d.ts +53 -0
- package/dist/commands/ui-harness.js +62 -0
- package/dist/commands/ui-smoke.d.ts +6 -0
- package/dist/commands/ui-smoke.js +220 -0
- package/dist/commands/upgrade.d.ts +15 -0
- package/dist/commands/upgrade.js +188 -0
- package/dist/core/auth-store.d.ts +81 -0
- package/dist/core/auth-store.js +530 -0
- package/dist/core/auth.d.ts +75 -0
- package/dist/core/auth.js +124 -0
- package/dist/core/bootstrap.d.ts +41 -0
- package/dist/core/bootstrap.js +236 -0
- package/dist/core/build.d.ts +92 -0
- package/dist/core/build.js +249 -0
- package/dist/core/canary.d.ts +10 -0
- package/dist/core/canary.js +14 -0
- package/dist/core/check-failure-summary.d.ts +10 -0
- package/dist/core/check-failure-summary.js +34 -0
- package/dist/core/config.d.ts +160 -0
- package/dist/core/config.js +1288 -0
- package/dist/core/context-pack.d.ts +74 -0
- package/dist/core/context-pack.js +778 -0
- package/dist/core/db.d.ts +236 -0
- package/dist/core/db.js +747 -0
- package/dist/core/detect.d.ts +69 -0
- package/dist/core/detect.js +251 -0
- package/dist/core/docs.d.ts +76 -0
- package/dist/core/docs.js +225 -0
- package/dist/core/frontmatter.d.ts +29 -0
- package/dist/core/frontmatter.js +215 -0
- package/dist/core/git.d.ts +505 -0
- package/dist/core/git.js +1453 -0
- package/dist/core/indexer.d.ts +33 -0
- package/dist/core/indexer.js +282 -0
- package/dist/core/input.d.ts +66 -0
- package/dist/core/input.js +222 -0
- package/dist/core/logger.d.ts +70 -0
- package/dist/core/logger.js +138 -0
- package/dist/core/models.d.ts +45 -0
- package/dist/core/models.js +291 -0
- package/dist/core/net-probe.d.ts +4 -0
- package/dist/core/net-probe.js +20 -0
- package/dist/core/providers/deepinfra.d.ts +23 -0
- package/dist/core/providers/deepinfra.js +44 -0
- package/dist/core/providers/index.d.ts +24 -0
- package/dist/core/providers/index.js +59 -0
- package/dist/core/providers/openrouter.d.ts +28 -0
- package/dist/core/providers/openrouter.js +82 -0
- package/dist/core/providers/spend.d.ts +86 -0
- package/dist/core/providers/spend.js +286 -0
- package/dist/core/providers/types.d.ts +56 -0
- package/dist/core/providers/types.js +39 -0
- package/dist/core/repoos.d.ts +59 -0
- package/dist/core/repoos.js +221 -0
- package/dist/core/runtime.d.ts +35 -0
- package/dist/core/runtime.js +167 -0
- package/dist/core/skills-registry.d.ts +39 -0
- package/dist/core/skills-registry.js +148 -0
- package/dist/core/skills-registry.test.d.ts +1 -0
- package/dist/core/skills-registry.test.js +52 -0
- package/dist/core/sysmem.d.ts +7 -0
- package/dist/core/sysmem.js +55 -0
- package/dist/core/tailscale.d.ts +2 -0
- package/dist/core/tailscale.js +19 -0
- package/dist/core/task.d.ts +53 -0
- package/dist/core/task.js +310 -0
- package/dist/core/toml-line.d.ts +20 -0
- package/dist/core/toml-line.js +52 -0
- package/dist/core/toml-validate.d.ts +44 -0
- package/dist/core/toml-validate.js +335 -0
- package/dist/core/tunnel-assistant.d.ts +30 -0
- package/dist/core/tunnel-assistant.js +65 -0
- package/dist/core/tunnel-registry.d.ts +96 -0
- package/dist/core/tunnel-registry.js +267 -0
- package/dist/core/tunnel-registry.test.d.ts +1 -0
- package/dist/core/tunnel-registry.test.js +309 -0
- package/dist/core/tunnel.d.ts +103 -0
- package/dist/core/tunnel.js +320 -0
- package/dist/core/types.d.ts +811 -0
- package/dist/core/types.js +27 -0
- package/dist/core/worktree-gc.d.ts +57 -0
- package/dist/core/worktree-gc.js +194 -0
- package/dist/server/__tests__/integration-job.test.d.ts +1 -0
- package/dist/server/__tests__/integration-job.test.js +129 -0
- package/dist/server/agents.d.ts +1020 -0
- package/dist/server/agents.js +4070 -0
- package/dist/server/attachments.d.ts +51 -0
- package/dist/server/attachments.js +130 -0
- package/dist/server/auto-engineering.d.ts +45 -0
- package/dist/server/auto-engineering.js +275 -0
- package/dist/server/branchless-release.d.ts +22 -0
- package/dist/server/branchless-release.js +67 -0
- package/dist/server/built-in-agents.d.ts +305 -0
- package/dist/server/built-in-agents.js +1838 -0
- package/dist/server/cto-monitor.d.ts +35 -0
- package/dist/server/cto-monitor.js +281 -0
- package/dist/server/cto.d.ts +125 -0
- package/dist/server/cto.js +590 -0
- package/dist/server/deployments.d.ts +119 -0
- package/dist/server/deployments.js +325 -0
- package/dist/server/diff-snapshot.d.ts +20 -0
- package/dist/server/diff-snapshot.js +73 -0
- package/dist/server/done.d.ts +160 -0
- package/dist/server/done.js +488 -0
- package/dist/server/freeform.d.ts +55 -0
- package/dist/server/freeform.js +179 -0
- package/dist/server/handoff.d.ts +99 -0
- package/dist/server/handoff.js +575 -0
- package/dist/server/hetzner.d.ts +45 -0
- package/dist/server/hetzner.js +117 -0
- package/dist/server/icons.d.ts +8 -0
- package/dist/server/icons.js +195 -0
- package/dist/server/integration-job.d.ts +75 -0
- package/dist/server/integration-job.js +144 -0
- package/dist/server/integration-orchestrator.d.ts +211 -0
- package/dist/server/integration-orchestrator.js +1299 -0
- package/dist/server/integration-status.d.ts +48 -0
- package/dist/server/integration-status.js +71 -0
- package/dist/server/live-index.d.ts +295 -0
- package/dist/server/live-index.js +452 -0
- package/dist/server/model-test.d.ts +20 -0
- package/dist/server/model-test.js +100 -0
- package/dist/server/ntfy.d.ts +57 -0
- package/dist/server/ntfy.js +112 -0
- package/dist/server/pm-attachments.d.ts +35 -0
- package/dist/server/pm-attachments.js +181 -0
- package/dist/server/pm-runs.d.ts +58 -0
- package/dist/server/pm-runs.js +80 -0
- package/dist/server/preview.d.ts +174 -0
- package/dist/server/preview.js +641 -0
- package/dist/server/release.d.ts +88 -0
- package/dist/server/release.js +449 -0
- package/dist/server/reload.d.ts +194 -0
- package/dist/server/reload.js +586 -0
- package/dist/server/remote-validation.d.ts +132 -0
- package/dist/server/remote-validation.js +478 -0
- package/dist/server/repo-lock.d.ts +36 -0
- package/dist/server/repo-lock.js +141 -0
- package/dist/server/review-guard.d.ts +14 -0
- package/dist/server/review-guard.js +159 -0
- package/dist/server/review.d.ts +243 -0
- package/dist/server/review.js +1211 -0
- package/dist/server/routes/agents.d.ts +6 -0
- package/dist/server/routes/agents.js +28 -0
- package/dist/server/routes/auth.d.ts +60 -0
- package/dist/server/routes/auth.js +755 -0
- package/dist/server/routes/config.d.ts +20 -0
- package/dist/server/routes/config.js +347 -0
- package/dist/server/routes/debugger.d.ts +33 -0
- package/dist/server/routes/debugger.js +360 -0
- package/dist/server/routes/deployments.d.ts +3 -0
- package/dist/server/routes/deployments.js +16 -0
- package/dist/server/routes/docs.d.ts +8 -0
- package/dist/server/routes/docs.js +135 -0
- package/dist/server/routes/helpers.d.ts +21 -0
- package/dist/server/routes/helpers.js +171 -0
- package/dist/server/routes/index.d.ts +19 -0
- package/dist/server/routes/index.js +18 -0
- package/dist/server/routes/info.d.ts +17 -0
- package/dist/server/routes/info.js +177 -0
- package/dist/server/routes/inputs.d.ts +7 -0
- package/dist/server/routes/inputs.js +125 -0
- package/dist/server/routes/model-providers.d.ts +27 -0
- package/dist/server/routes/model-providers.js +94 -0
- package/dist/server/routes/models.d.ts +3 -0
- package/dist/server/routes/models.js +36 -0
- package/dist/server/routes/notify.d.ts +2 -0
- package/dist/server/routes/notify.js +17 -0
- package/dist/server/routes/playground.d.ts +29 -0
- package/dist/server/routes/playground.js +81 -0
- package/dist/server/routes/release.d.ts +20 -0
- package/dist/server/routes/release.js +86 -0
- package/dist/server/routes/router.d.ts +12 -0
- package/dist/server/routes/router.js +32 -0
- package/dist/server/routes/skill-registry.d.ts +5 -0
- package/dist/server/routes/skill-registry.js +51 -0
- package/dist/server/routes/tasks.d.ts +58 -0
- package/dist/server/routes/tasks.js +1442 -0
- package/dist/server/routes/transcribe.d.ts +2 -0
- package/dist/server/routes/transcribe.js +124 -0
- package/dist/server/routes/types.d.ts +68 -0
- package/dist/server/routes/types.js +1 -0
- package/dist/server/routes/ui.d.ts +5 -0
- package/dist/server/routes/ui.js +81 -0
- package/dist/server/routes/utils.d.ts +3 -0
- package/dist/server/routes/utils.js +23 -0
- package/dist/server/serve-reaper.d.ts +135 -0
- package/dist/server/serve-reaper.js +491 -0
- package/dist/server/server.d.ts +49 -0
- package/dist/server/server.js +2171 -0
- package/dist/server/supervisor.d.ts +89 -0
- package/dist/server/supervisor.js +231 -0
- package/dist/server/system.d.ts +198 -0
- package/dist/server/system.js +480 -0
- package/dist/server/task-check.d.ts +45 -0
- package/dist/server/task-check.js +71 -0
- package/dist/server/task-transitions.d.ts +51 -0
- package/dist/server/task-transitions.js +78 -0
- package/dist/server/task-watchdog.d.ts +158 -0
- package/dist/server/task-watchdog.js +681 -0
- package/dist/server/test-run.d.ts +28 -0
- package/dist/server/test-run.js +81 -0
- package/dist/server/watcher.d.ts +21 -0
- package/dist/server/watcher.js +194 -0
- package/dist/server/write.d.ts +94 -0
- package/dist/server/write.js +345 -0
- package/dist/ui/assets/AgentsView-CF4wRvHh.css +1 -0
- package/dist/ui/assets/AgentsView-D-myL-kA.js +1 -0
- package/dist/ui/assets/ContextView-D3TC-RSp.css +1 -0
- package/dist/ui/assets/ContextView-DsOqAdyE.js +4 -0
- package/dist/ui/assets/DashboardView-BOhsIJqO.js +1 -0
- package/dist/ui/assets/DashboardView-Ck9o0RP2.css +1 -0
- package/dist/ui/assets/DeploymentsView--iIUVfEH.css +1 -0
- package/dist/ui/assets/DeploymentsView-DXu_FYR2.js +1 -0
- package/dist/ui/assets/InputsView-24HVbVPk.js +1 -0
- package/dist/ui/assets/InputsView-BLb7GHtx.css +1 -0
- package/dist/ui/assets/LoginView-6QlGg6cx.css +1 -0
- package/dist/ui/assets/LoginView-BqupI0Lt.js +1 -0
- package/dist/ui/assets/ReleasesView-BEG1A0YH.css +1 -0
- package/dist/ui/assets/ReleasesView-CAgMuxpe.js +3 -0
- package/dist/ui/assets/SettingsView-CA-NYSMK.css +1 -0
- package/dist/ui/assets/SettingsView-O3iZSnjg.js +3 -0
- package/dist/ui/assets/WorkView-D8Tl6qwy.js +1 -0
- package/dist/ui/assets/WorkView-DMYL2s3W.css +1 -0
- package/dist/ui/assets/abnfDiagram-VCTEODGH-J5lkSnEE.js +1 -0
- package/dist/ui/assets/arc-BHZnmhhM.js +1 -0
- package/dist/ui/assets/architecture-7GRP2DOG-pLdFt-xS.js +1 -0
- package/dist/ui/assets/architectureDiagram-5GKGNRK7-C3k89uKX.js +36 -0
- package/dist/ui/assets/array-BifhSqXX.js +1 -0
- package/dist/ui/assets/blockDiagram-I7D4REHJ-AWNoBzop.js +129 -0
- package/dist/ui/assets/button-lbikqSph.js +1 -0
- package/dist/ui/assets/c4Diagram-7LVT6UL2-DKG1D5gd.js +38 -0
- package/dist/ui/assets/channel-gA4qBv3A.js +1 -0
- package/dist/ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/ui/assets/chunk-4HAMMTFA-D1YR8lF_.js +62 -0
- package/dist/ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/ui/assets/chunk-75Z2AOVW-5GuBxfsL.js +2 -0
- package/dist/ui/assets/chunk-DU6HZSFF-DfdNNqwa.js +127 -0
- package/dist/ui/assets/chunk-F27PBJKO-BPZzinQ4.js +1 -0
- package/dist/ui/assets/chunk-FOHPRMQF-l2z4K2jD.js +161 -0
- package/dist/ui/assets/chunk-GMAD6QVW-DGWvSKj5.js +72 -0
- package/dist/ui/assets/chunk-GVQU2GXP-D6Aw75wJ.js +1 -0
- package/dist/ui/assets/chunk-IMKFNOWR-B8mMBKCW.js +231 -0
- package/dist/ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/ui/assets/chunk-L3NEJ4N5-D5OJuXYE.js +1 -0
- package/dist/ui/assets/chunk-OSK3NFVY-B7r9w2UW.js +10 -0
- package/dist/ui/assets/chunk-P2QGCYS3-C74X4AQX.js +1 -0
- package/dist/ui/assets/chunk-POPQ4Y6H-C5_u1gUS.js +1 -0
- package/dist/ui/assets/chunk-PWAF6VOD-DfoTLtxn.js +1 -0
- package/dist/ui/assets/chunk-SHT3W25Y-D40m2eqz.js +168 -0
- package/dist/ui/assets/chunk-SVP7TREG-euxi0Kap.js +88 -0
- package/dist/ui/assets/chunk-TICWLB2K-fkdxngzN.js +206 -0
- package/dist/ui/assets/chunk-XXDRQBXY-DFBUG-OT.js +1 -0
- package/dist/ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dist/ui/assets/classDiagram-ZZMXUADV-BzW8bq2P.js +1 -0
- package/dist/ui/assets/classDiagram-v2-VYDZK3BY-BzW8bq2P.js +1 -0
- package/dist/ui/assets/cose-bilkent-JH36ORCC-BTcgMon1.js +1 -0
- package/dist/ui/assets/cynefin-OW5HDTMX-B5m_COsS.js +1 -0
- package/dist/ui/assets/cynefinDiagram-5FMLGOSQ-C-AG6CGe.js +62 -0
- package/dist/ui/assets/cytoscape.esm-FBntH-9t.js +321 -0
- package/dist/ui/assets/dagre-GXQ25YYZ-DDfDnp8L.js +4 -0
- package/dist/ui/assets/dagre-PrKaheQc.js +1 -0
- package/dist/ui/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/ui/assets/diagram-S7CK7UJ4-DjQZAlEY.js +30 -0
- package/dist/ui/assets/diagram-UQ7AKVKN-DAZ9yp1B.js +41 -0
- package/dist/ui/assets/diagram-VSXAHHWV-BWepUKJD.js +3 -0
- package/dist/ui/assets/diagram-VX7I27RA-EcV1J81b.js +24 -0
- package/dist/ui/assets/diagram-Z3DM3KII-Dbz7XU_K.js +24 -0
- package/dist/ui/assets/dist-DTg6UBE_.js +1 -0
- package/dist/ui/assets/ebnfDiagram-PWID7BFC-BUyQw8Mm.js +1 -0
- package/dist/ui/assets/erDiagram-RLTQ6QDP-WVf8ukKt.js +99 -0
- package/dist/ui/assets/eventmodeling-NTZA5JFV-DHqyLOAn.js +1 -0
- package/dist/ui/assets/flowDiagram-HODETNUW-CYk_ze5K.js +1 -0
- package/dist/ui/assets/ganttDiagram-EL5Y4UJY-rB4MZuB-.js +292 -0
- package/dist/ui/assets/gitGraph-4MIJSDKK-BOTWM8dY.js +1 -0
- package/dist/ui/assets/gitGraphDiagram-WWUBYQGX-eYscuhtP.js +106 -0
- package/dist/ui/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/ui/assets/index-BACBfnga.css +1 -0
- package/dist/ui/assets/index-BOQ-ust6.js +26 -0
- package/dist/ui/assets/info-A6RAGUB7-_67wOORe.js +1 -0
- package/dist/ui/assets/infoDiagram-27XIBGKW-XVXbddTA.js +2 -0
- package/dist/ui/assets/init-C-OQMol4.js +1 -0
- package/dist/ui/assets/ishikawaDiagram-5VMMS53U-ClLNiEru.js +70 -0
- package/dist/ui/assets/journeyDiagram-3NMN7TZE-NRawjR6i.js +139 -0
- package/dist/ui/assets/kanban-definition-UXKFOSKX-BgSzBfIx.js +89 -0
- package/dist/ui/assets/katex-DolUETbr.js +257 -0
- package/dist/ui/assets/line-KAyfuul4.js +1 -0
- package/dist/ui/assets/linear-cCCPOpu7.js +1 -0
- package/dist/ui/assets/mermaid-parser.core-CgXZk1iN.js +7 -0
- package/dist/ui/assets/mermaid.core-BUUkChHW.js +44 -0
- package/dist/ui/assets/mindmap-definition-YA3MSWOX-Cpn7vW4r.js +96 -0
- package/dist/ui/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/ui/assets/packet-AYTQ26CC-0lZwM-d6.js +1 -0
- package/dist/ui/assets/path-COt_16Va.js +1 -0
- package/dist/ui/assets/pegDiagram-XKGWAZYB-impy0inn.js +1 -0
- package/dist/ui/assets/pie-WAS4IAKB-CqfdGHYg.js +1 -0
- package/dist/ui/assets/pieDiagram-E7YTZNPT-CxjL4I1h.js +39 -0
- package/dist/ui/assets/quadrantDiagram-AXDQQJYC-Cjd1QMIe.js +7 -0
- package/dist/ui/assets/radar-RG4KPBEZ-CRkQIwiv.js +1 -0
- package/dist/ui/assets/railroad-74A4TZTK-i67XSWTB.js +1 -0
- package/dist/ui/assets/railroad-abnf-HS5TGJTU-Cv9m5aS3.js +1 -0
- package/dist/ui/assets/railroad-ebnf-LZEXJU2U-C5ZIn-Jh.js +1 -0
- package/dist/ui/assets/railroad-peg-WCYAUIDC-CGra9Oq-.js +1 -0
- package/dist/ui/assets/railroadDiagram-O6MQD6OU-_pK0qyaY.js +1 -0
- package/dist/ui/assets/releases-B0WNV-SY.js +1 -0
- package/dist/ui/assets/repoos-cto-square.webp +0 -0
- package/dist/ui/assets/repoos-helpful-pm-square.webp +0 -0
- package/dist/ui/assets/repoos-orchestrator-square.webp +0 -0
- package/dist/ui/assets/repoos-performance-agent-square.webp +0 -0
- package/dist/ui/assets/repoos-ross-from-friends-square.webp +0 -0
- package/dist/ui/assets/repoos-tech-debt-agent-square.webp +0 -0
- package/dist/ui/assets/requirementDiagram-BXWQKSXE-CRrFX-yw.js +84 -0
- package/dist/ui/assets/rough.esm-By172zw-.js +1 -0
- package/dist/ui/assets/sankeyDiagram-P5KCCOFB-X_xFRj9E.js +40 -0
- package/dist/ui/assets/sequenceDiagram-WJ2MYXX4-Byb49lKf.js +162 -0
- package/dist/ui/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- package/dist/ui/assets/src-BH-TyZbA.js +1 -0
- package/dist/ui/assets/stateDiagram-D77RDMKH-CzaPrqYF.js +1 -0
- package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-BCXckz7K.js +1 -0
- package/dist/ui/assets/swimlanes-42K2YHIH-Cx_1lnqr.js +1 -0
- package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-54qsKi6A.js +8 -0
- package/dist/ui/assets/timeline-definition-24CTP7MA-B1IbZPQ-.js +120 -0
- package/dist/ui/assets/treeView-Q6P3EWNA-DFWhVE3d.js +1 -0
- package/dist/ui/assets/treemap-WGGIJYW6-BmbYPThn.js +1 -0
- package/dist/ui/assets/vendor-ui-DPm7zLxi.js +3 -0
- package/dist/ui/assets/vendor-vue-CobgyEJp.js +1 -0
- package/dist/ui/assets/vennDiagram-4TSXK5OY-Ae2AtRpt.js +34 -0
- package/dist/ui/assets/wardley-WFR3VGLG-BtD4X4tL.js +1 -0
- package/dist/ui/assets/wardleyDiagram-VM6X3IG4-B3YN2fTd.js +78 -0
- package/dist/ui/assets/xychartDiagram-S5SC5T6Z-CIzJfMKA.js +7 -0
- package/dist/ui/favicon.svg +11 -0
- package/dist/ui/index.html +49 -0
- package/dist/ui/sw.js +48 -0
- package/package.json +91 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { RepoOSConfig, Task } from "../core/types.js";
|
|
2
|
+
export interface CheckSummary {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
/** Failing close-out stage — "build" | "check". */
|
|
5
|
+
stage?: string;
|
|
6
|
+
/** The exact command that failed (argv joined with spaces). */
|
|
7
|
+
command?: string;
|
|
8
|
+
/** Exit status of the failing command (null = killed / never exited). */
|
|
9
|
+
exitCode?: number | null;
|
|
10
|
+
/** Spawn failure code when the command could not start (ENOENT/EACCES). */
|
|
11
|
+
errorCode?: string;
|
|
12
|
+
/** Redacted tail of stdout+stderr (bounded). */
|
|
13
|
+
output?: string;
|
|
14
|
+
/** Human-readable failure detail (redacted, includes command + status). */
|
|
15
|
+
detail?: string;
|
|
16
|
+
/** True when a timeout suggests machine contention, not a test assertion. */
|
|
17
|
+
transient?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface CompleteResult {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
/** Whether the branch was merged into main (even when later steps failed). */
|
|
22
|
+
merged: boolean;
|
|
23
|
+
/** True when the branch was already integrated into main before this run. */
|
|
24
|
+
alreadyMerged?: boolean;
|
|
25
|
+
/** Files that conflicted (merge aborted, task still in review). */
|
|
26
|
+
conflicts: string[];
|
|
27
|
+
/** Whether the merge was a fast-forward. */
|
|
28
|
+
ff: boolean;
|
|
29
|
+
/** True when a non-fast-forward/conflicting merge was detected in pre-flight. */
|
|
30
|
+
drifted: boolean;
|
|
31
|
+
/** Post-merge build/check gate (undefined when the merge itself failed). */
|
|
32
|
+
check?: CheckSummary;
|
|
33
|
+
/** The updated task, present when the task reached `done`. */
|
|
34
|
+
task?: Task;
|
|
35
|
+
/** True when the failure is a dirty main that must be committed/stashed first. */
|
|
36
|
+
needsCommit?: boolean;
|
|
37
|
+
/** The uncommitted files on main that blocked the merge, when known. */
|
|
38
|
+
dirtyFiles?: string[];
|
|
39
|
+
/** Human-readable reason (not ok). */
|
|
40
|
+
reason?: string;
|
|
41
|
+
}
|
|
42
|
+
/** The progress steps reported to the UI while the flow runs. */
|
|
43
|
+
export type DoneStep = "sync" | "merge" | "build" | "check" | "done";
|
|
44
|
+
export interface MergeAttempt {
|
|
45
|
+
/** Whether the branch was merged into the main checkout. */
|
|
46
|
+
merged: boolean;
|
|
47
|
+
/** Whether the merge was a fast-forward. */
|
|
48
|
+
ff: boolean;
|
|
49
|
+
/** True when main was not an ancestor of the branch at merge time. */
|
|
50
|
+
drifted: boolean;
|
|
51
|
+
/** Files that conflicted (merge aborted, task still in review). */
|
|
52
|
+
conflicts: string[];
|
|
53
|
+
/** Human-readable failure reason (not merged and not a conflict). */
|
|
54
|
+
reason?: string;
|
|
55
|
+
/** True when an automatic sync-with-main ran before the successful merge. */
|
|
56
|
+
autoSynced?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* True when the branch was already integrated into main before this run (a
|
|
59
|
+
* retry after an earlier close-out merged it), so the merge step is a no-op.
|
|
60
|
+
*/
|
|
61
|
+
alreadyMerged?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Merge `branch` into the main checkout, auto-syncing with main on a snag.
|
|
65
|
+
*
|
|
66
|
+
* Runs the existing pre-flight + merge once. If the merge cannot proceed
|
|
67
|
+
* because the branch needs to be synchronized with main (drift or a conflict),
|
|
68
|
+
* it runs the repository's existing sync-with-main operation (`main` merged
|
|
69
|
+
* into the branch's worktree) and retries the merge. A successful sync makes
|
|
70
|
+
* the retry a clean fast-forward, so the close-out completes without a manual
|
|
71
|
+
* "Sync with main" step. Real source conflicts fail the sync too, and the
|
|
72
|
+
* original failure is returned so the user sees exactly which files conflicted.
|
|
73
|
+
* Never merges before the first attempt — automatic sync is the fallback, not
|
|
74
|
+
* the default.
|
|
75
|
+
*/
|
|
76
|
+
export declare function mergeTaskBranchWithAutoSync(root: string, branch: string, opts?: {
|
|
77
|
+
autoResolve?: string[];
|
|
78
|
+
}): Promise<MergeAttempt>;
|
|
79
|
+
/** Replace anything that looks like a credential with `***`. */
|
|
80
|
+
export declare function redactSecrets(text: string): string;
|
|
81
|
+
/** Strip ANSI SGR escape sequences so diagnostics stay readable in JSON. */
|
|
82
|
+
export declare function stripAnsi(text: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* The useful part of a failed command's combined output: the LAST meaningful
|
|
85
|
+
* lines (errors print at the tail, so the old first-6-lines slice kept noise
|
|
86
|
+
* and dropped the reason), redacted, ANSI-free, and bounded.
|
|
87
|
+
*/
|
|
88
|
+
export declare function captureOutput(stdout: string, stderr: string): string;
|
|
89
|
+
export type CheckStage = "build" | "check";
|
|
90
|
+
export interface RunStepOptions {
|
|
91
|
+
cwd: string;
|
|
92
|
+
candidates: string[][];
|
|
93
|
+
/** Human-readable label used in failure text. */
|
|
94
|
+
label: string;
|
|
95
|
+
/** Stage the failing command belongs to (for diagnostics). */
|
|
96
|
+
stage: CheckStage;
|
|
97
|
+
/** Per-command wall-clock budget before SIGKILL, in ms. */
|
|
98
|
+
timeout?: number;
|
|
99
|
+
/** Child-process environment (defaults to the parent's). */
|
|
100
|
+
env?: NodeJS.ProcessEnv;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Try each candidate command in order until one exits 0. A command that runs
|
|
104
|
+
* and fails (non-zero exit, spawn error other than ENOENT/EACCES) is the
|
|
105
|
+
* authoritative failure: it is reported with the exact command, exit status,
|
|
106
|
+
* and a redacted output tail so the failing stage is diagnosable. Commands
|
|
107
|
+
* that cannot be launched (missing tool) are skipped in favor of the next
|
|
108
|
+
* candidate. Returns `{ ok: true }` on success.
|
|
109
|
+
*/
|
|
110
|
+
export declare function runDoneStep(opts: RunStepOptions): Promise<CheckSummary>;
|
|
111
|
+
/**
|
|
112
|
+
* Report the outcome of a close-out check retry honestly (#0216). A retry is
|
|
113
|
+
* only reached when the first run failed with a timeout-shaped signature, but
|
|
114
|
+
* that heuristic is message-based and a genuine assertion failure could match
|
|
115
|
+
* it too — so the retry is never assumed to be contention. The task's own
|
|
116
|
+
* guidance for telling the two apart: a failure that reproduces IDENTICALLY is
|
|
117
|
+
* a real defect, because contention lands on a different test each run. When
|
|
118
|
+
* the retry produces the same output as the first run, say so instead of
|
|
119
|
+
* blaming the machine; only a retry that fails differently supports the
|
|
120
|
+
* contention reading.
|
|
121
|
+
*/
|
|
122
|
+
export declare function describeRetryFailure(first: CheckSummary, retry: CheckSummary): CheckSummary;
|
|
123
|
+
/**
|
|
124
|
+
* A timeout means the gate could not obtain enough machine time; it says
|
|
125
|
+
* nothing about the merged branch. Retry that infrastructure failure once,
|
|
126
|
+
* while preserving ordinary non-zero test failures as immediately actionable.
|
|
127
|
+
* Exported for the retry-wiring regression tests.
|
|
128
|
+
*/
|
|
129
|
+
export declare function runCloseOutCheck(root: string, env?: NodeJS.ProcessEnv): Promise<CheckSummary>;
|
|
130
|
+
/** Post-merge gate runners, overridable in tests to avoid real builds. */
|
|
131
|
+
export interface CompleteTaskSteps {
|
|
132
|
+
build?: (cwd: string) => Promise<CheckSummary>;
|
|
133
|
+
check?: (cwd: string) => Promise<CheckSummary>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A server-owned close-out lock (0143). The serve process's ReloadManager
|
|
137
|
+
* reads `closingOut()` and defers every auto-reload while the pipeline runs —
|
|
138
|
+
* a close-out builds/checks dist itself, so an auto-reload mid-flight would
|
|
139
|
+
* kill the very server orchestrating the close-out. `completeTask` acquires
|
|
140
|
+
* the lock for its whole run and always releases it.
|
|
141
|
+
*/
|
|
142
|
+
export interface CloseOutLock {
|
|
143
|
+
closingOut: () => boolean;
|
|
144
|
+
acquire: () => void;
|
|
145
|
+
release: () => void;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Not the live close-out path (0187 review — flagged as unlogged; noting why
|
|
149
|
+
* it stays that way rather than adding logging nothing will ever read).
|
|
150
|
+
* `server.ts` imports this but never calls it — since #0118 the merge-queue
|
|
151
|
+
* (`JobCoordinator` + `CloseOutOrchestrator` in integration-orchestrator.ts,
|
|
152
|
+
* which now carries the phase/failure logging) is what actually runs a
|
|
153
|
+
* review->done close-out. This single-shot, run-against-main-directly version
|
|
154
|
+
* predates that and is exercised only by `src/ui-app/tests/done-*.test.ts`.
|
|
155
|
+
* Kept for its helpers (`mergeTaskBranchWithAutoSync`, `runDoneStep`,
|
|
156
|
+
* `redactSecrets`, `stripAnsi` — the last used live by `agents.ts`); consider
|
|
157
|
+
* deleting `completeTask`/`completeTaskLocked` themselves in a follow-up if
|
|
158
|
+
* nothing comes to depend on them.
|
|
159
|
+
*/
|
|
160
|
+
export declare function completeTask(config: RepoOSConfig, task: Task, onProgress?: (step: DoneStep) => void, steps?: CompleteTaskSteps, closeOut?: CloseOutLock): Promise<CompleteResult>;
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /api/tasks/:id/done` orchestration — the review -> done close-out.
|
|
3
|
+
*
|
|
4
|
+
* Runs against the MAIN checkout (`config.root`), never the task's worktree:
|
|
5
|
+
* 1. merge the task's branch into main (FF when possible, merge commit
|
|
6
|
+
* otherwise); if the merge hits a synchronization snag (drift/conflict),
|
|
7
|
+
* the branch is automatically synced with main and the merge retried;
|
|
8
|
+
* an unrecoverable conflict is aborted and the task stays review.
|
|
9
|
+
* A retry after the branch was already integrated is detected up front
|
|
10
|
+
* (`alreadyMerged`) so it resumes the remaining steps instead of being
|
|
11
|
+
* mislabelled as a failed merge.
|
|
12
|
+
* 2. rebuild + `repoos check` so the merged main
|
|
13
|
+
* stays green. The check runs the merged checkout's OWN freshly-built
|
|
14
|
+
* CLI (`dist/cli/index.js`) first — a globally linked `repoos` resolves
|
|
15
|
+
* build freshness and gate code against its own install snapshot, which
|
|
16
|
+
* can disagree with the checkout being closed out (#0130). Failure output
|
|
17
|
+
* is preserved with the exact command, exit status, and a redacted tail
|
|
18
|
+
* of stdout/stderr so the failing stage is diagnosable.
|
|
19
|
+
* 3. set status `done`, delete the branch, remove the task's worktree.
|
|
20
|
+
* A failure at any step leaves the task in `review`; if the merge already
|
|
21
|
+
* happened, we report that state honestly and a retry resumes from the failed
|
|
22
|
+
* step rather than treating the branch as a new conflicting merge.
|
|
23
|
+
*/
|
|
24
|
+
import { spawn } from "node:child_process";
|
|
25
|
+
import { existsSync } from "node:fs";
|
|
26
|
+
import { join, relative } from "node:path";
|
|
27
|
+
import { mergeBranch, preflightMerge, syncBranchWithMain, deleteBranch, removeWorktree, commitTaskFile, currentBranch, isAncestor, localBranches, runGit, dirtyFiles, GitDirtyCheckError, worktreePathForBranch, } from "../core/git.js";
|
|
28
|
+
import { markTaskReleased } from "./write.js";
|
|
29
|
+
/**
|
|
30
|
+
* Merge `branch` into the main checkout, auto-syncing with main on a snag.
|
|
31
|
+
*
|
|
32
|
+
* Runs the existing pre-flight + merge once. If the merge cannot proceed
|
|
33
|
+
* because the branch needs to be synchronized with main (drift or a conflict),
|
|
34
|
+
* it runs the repository's existing sync-with-main operation (`main` merged
|
|
35
|
+
* into the branch's worktree) and retries the merge. A successful sync makes
|
|
36
|
+
* the retry a clean fast-forward, so the close-out completes without a manual
|
|
37
|
+
* "Sync with main" step. Real source conflicts fail the sync too, and the
|
|
38
|
+
* original failure is returned so the user sees exactly which files conflicted.
|
|
39
|
+
* Never merges before the first attempt — automatic sync is the fallback, not
|
|
40
|
+
* the default.
|
|
41
|
+
*/
|
|
42
|
+
export async function mergeTaskBranchWithAutoSync(root, branch, opts = {}) {
|
|
43
|
+
// Already integrated — the idempotent-retry case (#0130). The branch tip is
|
|
44
|
+
// an ancestor of main (an earlier close-out merged it but stranded the task
|
|
45
|
+
// in review on a later gate), so the merge step is a no-op and a retry must
|
|
46
|
+
// resume the remaining steps rather than report a failed merge. When the
|
|
47
|
+
// branch ref is also gone the close-out cleanup already ran (`deleteBranch`
|
|
48
|
+
// refuses unmerged branches, so a missing branch was integrated) and there
|
|
49
|
+
// is nothing left to merge.
|
|
50
|
+
const head = currentBranch(root);
|
|
51
|
+
if (head !== null && head !== branch && isAncestor(root, branch, head) === true) {
|
|
52
|
+
return {
|
|
53
|
+
merged: true,
|
|
54
|
+
ff: true,
|
|
55
|
+
drifted: false,
|
|
56
|
+
conflicts: [],
|
|
57
|
+
alreadyMerged: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (!localBranches(root).has(branch) && worktreePathForBranch(root, branch) === null) {
|
|
61
|
+
return {
|
|
62
|
+
merged: true,
|
|
63
|
+
ff: true,
|
|
64
|
+
drifted: false,
|
|
65
|
+
conflicts: [],
|
|
66
|
+
alreadyMerged: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const attempt = async () => {
|
|
70
|
+
const preflight = await preflightMerge(root, branch, opts);
|
|
71
|
+
if (!preflight.ok) {
|
|
72
|
+
return {
|
|
73
|
+
merged: false,
|
|
74
|
+
ff: false,
|
|
75
|
+
drifted: preflight.drifted,
|
|
76
|
+
conflicts: preflight.conflicts,
|
|
77
|
+
reason: preflight.reason ?? "merge conflict",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const merge = await mergeBranch(root, branch, opts);
|
|
81
|
+
return {
|
|
82
|
+
merged: merge.merged,
|
|
83
|
+
ff: merge.ff,
|
|
84
|
+
drifted: preflight.drifted,
|
|
85
|
+
conflicts: merge.conflicts,
|
|
86
|
+
reason: merge.conflicts.length
|
|
87
|
+
? `merge conflict: ${merge.conflicts.join(", ")}`
|
|
88
|
+
: merge.reason,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
const first = await attempt();
|
|
92
|
+
if (first.merged)
|
|
93
|
+
return first;
|
|
94
|
+
// The merge hit a synchronization-related snag: automatically sync the branch
|
|
95
|
+
// with main, then retry. If the sync itself cannot complete cleanly (real
|
|
96
|
+
// source conflicts), surface the first failure rather than inventing a new
|
|
97
|
+
// error — the user resolves those files in the worktree and retries.
|
|
98
|
+
const sync = await syncBranchWithMain(root, branch, opts);
|
|
99
|
+
if (!sync.ok)
|
|
100
|
+
return first;
|
|
101
|
+
const retry = await attempt();
|
|
102
|
+
if (retry.merged)
|
|
103
|
+
return { ...retry, autoSynced: true };
|
|
104
|
+
return retry;
|
|
105
|
+
}
|
|
106
|
+
/** Non-blocking `spawn`, resolving with exit status + captured output. */
|
|
107
|
+
function runProcess(cmd, args, opts) {
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
const child = spawn(cmd, args, { cwd: opts.cwd, env: opts.env });
|
|
110
|
+
let stdout = "";
|
|
111
|
+
let stderr = "";
|
|
112
|
+
let timer;
|
|
113
|
+
let settled = false;
|
|
114
|
+
let timedOut = false;
|
|
115
|
+
const finish = (status, error) => {
|
|
116
|
+
if (settled)
|
|
117
|
+
return;
|
|
118
|
+
settled = true;
|
|
119
|
+
if (timer)
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
resolve({ status, stdout, stderr, error, timedOut });
|
|
122
|
+
};
|
|
123
|
+
child.stdout.on("data", (d) => (stdout += d.toString("utf8")));
|
|
124
|
+
child.stderr.on("data", (d) => (stderr += d.toString("utf8")));
|
|
125
|
+
child.on("error", (err) => finish(null, err));
|
|
126
|
+
child.on("close", (code) => finish(code));
|
|
127
|
+
timer = setTimeout(() => {
|
|
128
|
+
timedOut = true;
|
|
129
|
+
child.kill("SIGKILL");
|
|
130
|
+
}, opts.timeout);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/** Token shapes that must never surface in close-out diagnostics. */
|
|
134
|
+
const SECRET_PATTERNS = [
|
|
135
|
+
/github_pat_[A-Za-z0-9_]+/g,
|
|
136
|
+
/gh[pousr]_[A-Za-z0-9]+/g,
|
|
137
|
+
/\bsk-[A-Za-z0-9_-]+/g,
|
|
138
|
+
/xox[baprs]-[A-Za-z0-9-]+/g,
|
|
139
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
140
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,
|
|
141
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/g,
|
|
142
|
+
];
|
|
143
|
+
/** Replace anything that looks like a credential with `***`. */
|
|
144
|
+
export function redactSecrets(text) {
|
|
145
|
+
let out = text;
|
|
146
|
+
for (const re of SECRET_PATTERNS)
|
|
147
|
+
out = out.replace(re, "***");
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
/** Strip ANSI SGR escape sequences so diagnostics stay readable in JSON. */
|
|
151
|
+
export function stripAnsi(text) {
|
|
152
|
+
// eslint-disable-next-line no-control-regex
|
|
153
|
+
return text.replace(/\u001b\[[0-9;]*m/g, "");
|
|
154
|
+
}
|
|
155
|
+
const MAX_OUTPUT_CHARS = 4000;
|
|
156
|
+
const MAX_OUTPUT_LINES = 40;
|
|
157
|
+
/**
|
|
158
|
+
* The useful part of a failed command's combined output: the LAST meaningful
|
|
159
|
+
* lines (errors print at the tail, so the old first-6-lines slice kept noise
|
|
160
|
+
* and dropped the reason), redacted, ANSI-free, and bounded.
|
|
161
|
+
*/
|
|
162
|
+
export function captureOutput(stdout, stderr) {
|
|
163
|
+
const combined = redactSecrets(stripAnsi([stdout, stderr].join("\n")));
|
|
164
|
+
const lines = combined
|
|
165
|
+
.split("\n")
|
|
166
|
+
.map((l) => l.trim())
|
|
167
|
+
.filter(Boolean);
|
|
168
|
+
const tail = lines.slice(-MAX_OUTPUT_LINES).join("\n");
|
|
169
|
+
return tail.length > MAX_OUTPUT_CHARS ? tail.slice(-MAX_OUTPUT_CHARS) : tail;
|
|
170
|
+
}
|
|
171
|
+
/** Quote argv so a path with spaces stays a single command token. */
|
|
172
|
+
function fmtCommand(argv) {
|
|
173
|
+
return argv.map((a) => (/\s/.test(a) ? JSON.stringify(a) : a)).join(" ");
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Try each candidate command in order until one exits 0. A command that runs
|
|
177
|
+
* and fails (non-zero exit, spawn error other than ENOENT/EACCES) is the
|
|
178
|
+
* authoritative failure: it is reported with the exact command, exit status,
|
|
179
|
+
* and a redacted output tail so the failing stage is diagnosable. Commands
|
|
180
|
+
* that cannot be launched (missing tool) are skipped in favor of the next
|
|
181
|
+
* candidate. Returns `{ ok: true }` on success.
|
|
182
|
+
*/
|
|
183
|
+
export async function runDoneStep(opts) {
|
|
184
|
+
const timeout = opts.timeout ?? 240_000;
|
|
185
|
+
let missing = "";
|
|
186
|
+
for (const cmd of opts.candidates) {
|
|
187
|
+
const run = await runProcess(cmd[0], cmd.slice(1), {
|
|
188
|
+
cwd: opts.cwd,
|
|
189
|
+
timeout,
|
|
190
|
+
env: opts.env,
|
|
191
|
+
});
|
|
192
|
+
if (run.status === 0)
|
|
193
|
+
return { ok: true, stage: opts.stage };
|
|
194
|
+
if (run.error && (run.error.code === "ENOENT" || run.error.code === "EACCES")) {
|
|
195
|
+
missing = `${cmd[0]} is not available`;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const command = fmtCommand(cmd);
|
|
199
|
+
const statusText = run.timedOut
|
|
200
|
+
? `timed out after ${Math.round(timeout / 1000)}s (killed)`
|
|
201
|
+
: run.error
|
|
202
|
+
? `could not start (${run.error.code ?? run.error.message})`
|
|
203
|
+
: `exit ${run.status}`;
|
|
204
|
+
const output = captureOutput(run.stdout, run.stderr);
|
|
205
|
+
const short = output.slice(0, 500).replace(/\s+/g, " ").trim();
|
|
206
|
+
// Vitest reports a contention-induced polling deadline as a normal
|
|
207
|
+
// non-zero test result, not as a timeout of this outer subprocess. Treat
|
|
208
|
+
// that narrow shape like a process deadline: retry once, but do not hide
|
|
209
|
+
// assertion failures or other real regressions behind a retry.
|
|
210
|
+
const transient = run.timedOut ||
|
|
211
|
+
(opts.stage === "check" &&
|
|
212
|
+
/(?:timed out waiting for|test timed out|worker .*?(?:timeout|exited))/i.test(output));
|
|
213
|
+
return {
|
|
214
|
+
ok: false,
|
|
215
|
+
stage: opts.stage,
|
|
216
|
+
command,
|
|
217
|
+
exitCode: run.status,
|
|
218
|
+
errorCode: run.error?.code,
|
|
219
|
+
output,
|
|
220
|
+
detail: `${opts.label} failed (${statusText}) — ${command}${short ? ` — ${short}` : ""}`,
|
|
221
|
+
transient,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return { ok: false, stage: opts.stage, detail: missing || `\`${opts.label}\` could not be run` };
|
|
225
|
+
}
|
|
226
|
+
const BUILD_STEPS = [
|
|
227
|
+
["bun", "run", "build"],
|
|
228
|
+
["npm", "run", "build"],
|
|
229
|
+
];
|
|
230
|
+
/**
|
|
231
|
+
* Commit regenerated `dist/` — the post-merge build just rewrote it — so main
|
|
232
|
+
* stays clean and mergeable. Nothing else generated is tracked: screenshots
|
|
233
|
+
* are regenerated on demand via `repoos screenshots`, never by the close-out.
|
|
234
|
+
*/
|
|
235
|
+
async function commitGenerated(root) {
|
|
236
|
+
await runProcess("git", ["add", "-A", "--", "dist"], { cwd: root, timeout: 4000 });
|
|
237
|
+
// `--only` scopes the commit to dist: a plain commit would take the whole
|
|
238
|
+
// index and sweep in any unrelated work staged in main (#0353). When dist is
|
|
239
|
+
// gitignored (as here) nothing is staged and git fails the commit harmlessly.
|
|
240
|
+
await runProcess("git", ["commit", "-o", "-m", "chore: regenerate dist", "--", "dist"], {
|
|
241
|
+
cwd: root,
|
|
242
|
+
timeout: 4000,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The commands that run the post-merge gate, in preference order. The merged
|
|
247
|
+
* checkout's OWN freshly-built CLI comes first: a globally linked `repoos`
|
|
248
|
+
* resolves build freshness and runs gate code against its own install snapshot
|
|
249
|
+
* (in this self-hosted repo the global install is the main checkout's dist),
|
|
250
|
+
* which can disagree with the checkout being closed out — running `check` from
|
|
251
|
+
* the merged `dist/cli/index.js` guarantees the gate evaluates the merged code
|
|
252
|
+
* with the merged gate logic (#0130).
|
|
253
|
+
*/
|
|
254
|
+
function checkCandidates(root) {
|
|
255
|
+
const localCli = join(root, "dist", "cli", "index.js");
|
|
256
|
+
const list = [];
|
|
257
|
+
if (existsSync(localCli)) {
|
|
258
|
+
list.push([process.execPath, localCli, "check"]);
|
|
259
|
+
}
|
|
260
|
+
list.push(["repoos", "check"], ["bun", "run", "repoos", "check"]);
|
|
261
|
+
return list;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Report the outcome of a close-out check retry honestly (#0216). A retry is
|
|
265
|
+
* only reached when the first run failed with a timeout-shaped signature, but
|
|
266
|
+
* that heuristic is message-based and a genuine assertion failure could match
|
|
267
|
+
* it too — so the retry is never assumed to be contention. The task's own
|
|
268
|
+
* guidance for telling the two apart: a failure that reproduces IDENTICALLY is
|
|
269
|
+
* a real defect, because contention lands on a different test each run. When
|
|
270
|
+
* the retry produces the same output as the first run, say so instead of
|
|
271
|
+
* blaming the machine; only a retry that fails differently supports the
|
|
272
|
+
* contention reading.
|
|
273
|
+
*/
|
|
274
|
+
export function describeRetryFailure(first, retry) {
|
|
275
|
+
if (retry.ok || !retry.transient)
|
|
276
|
+
return retry;
|
|
277
|
+
const reproduced = first.output !== undefined && first.output === retry.output;
|
|
278
|
+
retry.detail = `${retry.detail ?? "repoos check failed"} — ${reproduced
|
|
279
|
+
? "the same failure appeared on the retry with identical output. Identical reproduction points at a genuine defect, not machine contention"
|
|
280
|
+
: first.output === undefined || retry.output === undefined
|
|
281
|
+
? "the failure reappeared on the retry, but it could not be compared against the first run"
|
|
282
|
+
: "the retry failed on different output than the first run; the machine may be too loaded to run the gate reliably"}`;
|
|
283
|
+
return retry;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* A timeout means the gate could not obtain enough machine time; it says
|
|
287
|
+
* nothing about the merged branch. Retry that infrastructure failure once,
|
|
288
|
+
* while preserving ordinary non-zero test failures as immediately actionable.
|
|
289
|
+
* Exported for the retry-wiring regression tests.
|
|
290
|
+
*/
|
|
291
|
+
export async function runCloseOutCheck(root, env) {
|
|
292
|
+
const first = await runDoneStep({
|
|
293
|
+
cwd: root,
|
|
294
|
+
candidates: checkCandidates(root),
|
|
295
|
+
label: "repoos check",
|
|
296
|
+
stage: "check",
|
|
297
|
+
timeout: 600_000,
|
|
298
|
+
env,
|
|
299
|
+
});
|
|
300
|
+
if (!first.transient)
|
|
301
|
+
return first;
|
|
302
|
+
const retry = await runDoneStep({
|
|
303
|
+
cwd: root,
|
|
304
|
+
candidates: checkCandidates(root),
|
|
305
|
+
label: "repoos check (retry after timeout)",
|
|
306
|
+
stage: "check",
|
|
307
|
+
timeout: 600_000,
|
|
308
|
+
env,
|
|
309
|
+
});
|
|
310
|
+
return describeRetryFailure(first, retry);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Not the live close-out path (0187 review — flagged as unlogged; noting why
|
|
314
|
+
* it stays that way rather than adding logging nothing will ever read).
|
|
315
|
+
* `server.ts` imports this but never calls it — since #0118 the merge-queue
|
|
316
|
+
* (`JobCoordinator` + `CloseOutOrchestrator` in integration-orchestrator.ts,
|
|
317
|
+
* which now carries the phase/failure logging) is what actually runs a
|
|
318
|
+
* review->done close-out. This single-shot, run-against-main-directly version
|
|
319
|
+
* predates that and is exercised only by `src/ui-app/tests/done-*.test.ts`.
|
|
320
|
+
* Kept for its helpers (`mergeTaskBranchWithAutoSync`, `runDoneStep`,
|
|
321
|
+
* `redactSecrets`, `stripAnsi` — the last used live by `agents.ts`); consider
|
|
322
|
+
* deleting `completeTask`/`completeTaskLocked` themselves in a follow-up if
|
|
323
|
+
* nothing comes to depend on them.
|
|
324
|
+
*/
|
|
325
|
+
export async function completeTask(config, task, onProgress, steps = {}, closeOut) {
|
|
326
|
+
closeOut?.acquire();
|
|
327
|
+
try {
|
|
328
|
+
return await completeTaskLocked(config, task, onProgress, steps);
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
closeOut?.release();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function completeTaskLocked(config, task, onProgress, steps = {}) {
|
|
335
|
+
const root = config.root;
|
|
336
|
+
onProgress?.("merge");
|
|
337
|
+
// Ensure the task file is committed in main before merging: API-created tasks
|
|
338
|
+
// are untracked in main, and the agent's main-copy sync leaves uncommitted
|
|
339
|
+
// edits — either would abort `git merge`. The branch's version of the task
|
|
340
|
+
// file is authoritative for the final state, so if it is the only conflicted
|
|
341
|
+
// path the merge resolves it automatically.
|
|
342
|
+
commitTaskFile(root, task.absPath, `docs(${task.id}): update task`);
|
|
343
|
+
const rel = relative(root, task.absPath);
|
|
344
|
+
// Drop `dist/` and `screenshots/` from the branch before merging so hashed
|
|
345
|
+
// Vite asset filenames and binary PNGs don't cause artificial conflicts.
|
|
346
|
+
// Both are regenerated from source on main right after the merge anyway.
|
|
347
|
+
const wt = worktreePathForBranch(root, task.branch);
|
|
348
|
+
if (wt !== null && !task.hotfix) {
|
|
349
|
+
const r1 = await runGit(wt, ["ls-files", "dist/", "screenshots/"], 5000);
|
|
350
|
+
if (r1.status === 0 && r1.stdout.trim().length > 0) {
|
|
351
|
+
await runGit(wt, ["rm", "-r", "--cached", "--", "dist/", "screenshots/"], 15_000);
|
|
352
|
+
await runGit(wt, ["commit", "-m", "chore: drop dist and screenshots before merge"], 15_000);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const autoResolve = [rel, "dist/", "screenshots/"];
|
|
356
|
+
// Direct-to-main hotfix: the work was done on main itself; skip the merge.
|
|
357
|
+
const isMainHotfix = task.hotfix && task.hotfixTarget === "main";
|
|
358
|
+
// Publish-time dirty-main guard (#0211): the main tree can be dirtied
|
|
359
|
+
// between the enqueue-time check and this publish step (validation takes
|
|
360
|
+
// minutes and `repoos check`/builds regenerate `dist/`). Re-check right
|
|
361
|
+
// before the merge so a dirty main is surfaced as an actionable error
|
|
362
|
+
// instead of git's raw "your local changes would be overwritten" message at
|
|
363
|
+
// the tail of a long job. Fails closed: an error/timeout in the check is
|
|
364
|
+
// "unknown", not "clean", and aborts the close-out rather than risking a
|
|
365
|
+
// merge git will refuse.
|
|
366
|
+
let publishDirty;
|
|
367
|
+
try {
|
|
368
|
+
publishDirty = await dirtyFiles(root);
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
if (err instanceof GitDirtyCheckError) {
|
|
372
|
+
return {
|
|
373
|
+
ok: false,
|
|
374
|
+
merged: false,
|
|
375
|
+
conflicts: [],
|
|
376
|
+
ff: false,
|
|
377
|
+
drifted: false,
|
|
378
|
+
needsCommit: true,
|
|
379
|
+
reason: `could not verify main is clean at publish time (${err.message}). The close-out was NOT merged; retry, or commit/stash main's working tree first.`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
throw err;
|
|
383
|
+
}
|
|
384
|
+
if (publishDirty.length > 0) {
|
|
385
|
+
return {
|
|
386
|
+
ok: false,
|
|
387
|
+
merged: false,
|
|
388
|
+
conflicts: [],
|
|
389
|
+
ff: false,
|
|
390
|
+
drifted: false,
|
|
391
|
+
needsCommit: true,
|
|
392
|
+
dirtyFiles: publishDirty,
|
|
393
|
+
reason: `main has ${publishDirty.length} uncommitted file${publishDirty.length === 1 ? "" : "s"} at publish time, so the merge would abort: ${publishDirty.slice(0, 8).join(", ")}${publishDirty.length > 8 ? ", …" : ""}. The close-out was NOT merged; commit or stash those on main (or use "Commit & continue") and retry.`,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
let merge;
|
|
397
|
+
if (isMainHotfix) {
|
|
398
|
+
merge = { merged: true, ff: true, drifted: false, conflicts: [] };
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
// Cheap pre-flight: detect source-file conflicts before the expensive
|
|
402
|
+
// build/check steps run. Never leaves a half-applied merge.
|
|
403
|
+
// When the merge cannot proceed because the branch needs to be synchronized
|
|
404
|
+
// with main, `mergeTaskBranchWithAutoSync` runs the existing sync-with-main
|
|
405
|
+
// operation and retries, so the close-out completes without a manual step.
|
|
406
|
+
// A retry where the branch was already integrated is detected up front and
|
|
407
|
+
// reported as `alreadyMerged`, resuming from the remaining steps.
|
|
408
|
+
merge = await mergeTaskBranchWithAutoSync(root, task.branch, { autoResolve });
|
|
409
|
+
}
|
|
410
|
+
if (!merge.merged) {
|
|
411
|
+
return {
|
|
412
|
+
ok: false,
|
|
413
|
+
merged: false,
|
|
414
|
+
alreadyMerged: merge.alreadyMerged,
|
|
415
|
+
conflicts: merge.conflicts,
|
|
416
|
+
ff: false,
|
|
417
|
+
drifted: merge.drifted,
|
|
418
|
+
reason: merge.reason ?? "merge failed",
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
onProgress?.("build");
|
|
422
|
+
const build = steps.build
|
|
423
|
+
? await steps.build(root)
|
|
424
|
+
: await runDoneStep({
|
|
425
|
+
cwd: root,
|
|
426
|
+
candidates: BUILD_STEPS,
|
|
427
|
+
label: "bun run build",
|
|
428
|
+
stage: "build",
|
|
429
|
+
timeout: 300_000,
|
|
430
|
+
});
|
|
431
|
+
if (!build.ok) {
|
|
432
|
+
return {
|
|
433
|
+
ok: false,
|
|
434
|
+
merged: true,
|
|
435
|
+
alreadyMerged: merge.alreadyMerged,
|
|
436
|
+
conflicts: [],
|
|
437
|
+
ff: merge.ff,
|
|
438
|
+
drifted: merge.drifted,
|
|
439
|
+
check: build,
|
|
440
|
+
reason: `build failed after merge (${build.exitCode ?? build.errorCode ?? "killed"}) — the branch IS merged into main; retrying resumes from the build step. Task kept in review.`,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
await commitGenerated(root);
|
|
444
|
+
onProgress?.("check");
|
|
445
|
+
// The close-out already ran a full build above (`BUILD_STEPS`), and
|
|
446
|
+
// `bun run build` is staleness-aware now (#0377), so `repoos check`'s own
|
|
447
|
+
// "Full build" step detects the fresh marker and skips itself — no private
|
|
448
|
+
// skip env flag needed (that plumbing was removed with #0377).
|
|
449
|
+
const check = steps.check ? await steps.check(root) : await runCloseOutCheck(root);
|
|
450
|
+
if (!check.ok) {
|
|
451
|
+
return {
|
|
452
|
+
ok: false,
|
|
453
|
+
merged: true,
|
|
454
|
+
alreadyMerged: merge.alreadyMerged,
|
|
455
|
+
conflicts: [],
|
|
456
|
+
ff: merge.ff,
|
|
457
|
+
drifted: merge.drifted,
|
|
458
|
+
check,
|
|
459
|
+
reason: `repoos check failed after merge (${check.exitCode ?? check.errorCode ?? "killed"}) — the branch IS merged into main; retrying resumes from the check step. Task kept in review.`,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
onProgress?.("done");
|
|
463
|
+
const updated = markTaskReleased(config, task.absPath);
|
|
464
|
+
// Best-effort cleanup; content is preserved in the merged main. The worktree
|
|
465
|
+
// must go first — git refuses to delete a branch checked out in a worktree.
|
|
466
|
+
// Both are fail-soft, so an already-cleaned retry is a no-op here.
|
|
467
|
+
// Hotfix tasks: never delete a worktree that doesn't exist, and never delete
|
|
468
|
+
// main. For branch-mode hotfixes, switch back to main after closing.
|
|
469
|
+
if (!task.hotfix) {
|
|
470
|
+
removeWorktree(root, task.branch);
|
|
471
|
+
deleteBranch(root, task.branch);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
if (task.hotfixTarget !== "main" && task.branch) {
|
|
475
|
+
deleteBranch(root, task.branch);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
ok: true,
|
|
480
|
+
merged: true,
|
|
481
|
+
alreadyMerged: merge.alreadyMerged,
|
|
482
|
+
conflicts: [],
|
|
483
|
+
ff: merge.ff,
|
|
484
|
+
drifted: merge.drifted,
|
|
485
|
+
check: { ok: true },
|
|
486
|
+
task: updated,
|
|
487
|
+
};
|
|
488
|
+
}
|