@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,51 @@
|
|
|
1
|
+
import type { RepoOSConfig, Task } from "../core/types.js";
|
|
2
|
+
/** One persisted screenshot, as returned to the client. */
|
|
3
|
+
export interface ScreenshotMeta {
|
|
4
|
+
/** 1-based index within the task's attachment folder. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Original file name (sanitized). */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Repo-relative path, e.g. "work/.attachments/0123/screenshot-1.png". */
|
|
9
|
+
path: string;
|
|
10
|
+
/** API URL the UI can load the image from. */
|
|
11
|
+
url: string;
|
|
12
|
+
size: number;
|
|
13
|
+
mime: string;
|
|
14
|
+
}
|
|
15
|
+
/** Image MIME types treated as screenshots, mapped to their file extension. */
|
|
16
|
+
export declare const SCREENSHOT_MIME: Record<string, string>;
|
|
17
|
+
/** Max decoded size for one screenshot upload (bytes). */
|
|
18
|
+
export declare const MAX_SCREENSHOT_BYTES: number;
|
|
19
|
+
export declare function attachmentsDir(root: string, workDir: string, taskId: string): string;
|
|
20
|
+
/** API URL that serves one stored screenshot. */
|
|
21
|
+
export declare function screenshotUrl(taskId: string, file: string): string;
|
|
22
|
+
/** Strip directories and hostile characters from an uploaded file name. */
|
|
23
|
+
export declare function sanitizeName(name: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Persist one uploaded screenshot under the task's attachment folder and
|
|
26
|
+
* return its metadata, or `{ error }` on invalid input. Never throws.
|
|
27
|
+
*/
|
|
28
|
+
export declare function saveScreenshot(config: RepoOSConfig, task: Task, input: {
|
|
29
|
+
name?: unknown;
|
|
30
|
+
mime?: unknown;
|
|
31
|
+
data?: unknown;
|
|
32
|
+
}): ScreenshotMeta | {
|
|
33
|
+
error: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Insert screenshots into a task body's `## Screenshots` section, keeping it
|
|
37
|
+
* BEFORE the append-only Activity section so the activity log stays the last
|
|
38
|
+
* thing in the body. Appends the section at the end when there is no Activity
|
|
39
|
+
* section. When a Screenshots section already exists (a second upload batch,
|
|
40
|
+
* or the 0381 pending-batch attach landing on a task that has one), the new
|
|
41
|
+
* images merge into that section — repeated uploads must not stack duplicate
|
|
42
|
+
* headings.
|
|
43
|
+
*/
|
|
44
|
+
export declare function appendScreenshotsSection(body: string, metas: ScreenshotMeta[]): string;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a stored screenshot to its absolute path, refusing path traversal
|
|
47
|
+
* outside the task's own attachment folder. Returns null on a miss.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveScreenshot(config: RepoOSConfig, taskId: string, file: string): string | null;
|
|
50
|
+
/** MIME type for a stored screenshot file by extension, or null when unknown. */
|
|
51
|
+
export declare function mimeForExtension(file: string): string | null;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task screenshot attachments (0123).
|
|
3
|
+
*
|
|
4
|
+
* Screenshots are uploaded from the New task panel and land in
|
|
5
|
+
* `<workDir>/.attachments/<taskId>/`, referenced from the task body's
|
|
6
|
+
* `## Screenshots` section (kept BEFORE the append-only Activity section).
|
|
7
|
+
* Files are served back through `GET /api/tasks/:id/attachments/:file`.
|
|
8
|
+
* Dependency-free, mirroring the rest of the server: base64 in JSON in, a
|
|
9
|
+
* small safe writer for storage.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { basename, extname, join, resolve, sep } from "node:path";
|
|
13
|
+
import { SCREENSHOTS_HEADING, extractSection, removeSection } from "../core/task.js";
|
|
14
|
+
/** Image MIME types treated as screenshots, mapped to their file extension. */
|
|
15
|
+
export const SCREENSHOT_MIME = {
|
|
16
|
+
"image/png": ".png",
|
|
17
|
+
"image/jpeg": ".jpg",
|
|
18
|
+
"image/gif": ".gif",
|
|
19
|
+
"image/webp": ".webp",
|
|
20
|
+
"image/avif": ".avif",
|
|
21
|
+
"image/bmp": ".bmp",
|
|
22
|
+
};
|
|
23
|
+
/** Max decoded size for one screenshot upload (bytes). */
|
|
24
|
+
export const MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
|
|
25
|
+
export function attachmentsDir(root, workDir, taskId) {
|
|
26
|
+
return join(root, workDir, ".attachments", taskId);
|
|
27
|
+
}
|
|
28
|
+
/** API URL that serves one stored screenshot. */
|
|
29
|
+
export function screenshotUrl(taskId, file) {
|
|
30
|
+
return `/api/tasks/${taskId}/attachments/${encodeURIComponent(file)}`;
|
|
31
|
+
}
|
|
32
|
+
/** Strip directories and hostile characters from an uploaded file name. */
|
|
33
|
+
export function sanitizeName(name) {
|
|
34
|
+
const base = basename(name)
|
|
35
|
+
.replace(/\.[^./]+$/, "")
|
|
36
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
37
|
+
.replace(/^-+|-+$/g, "");
|
|
38
|
+
return base || "screenshot";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Persist one uploaded screenshot under the task's attachment folder and
|
|
42
|
+
* return its metadata, or `{ error }` on invalid input. Never throws.
|
|
43
|
+
*/
|
|
44
|
+
export function saveScreenshot(config, task, input) {
|
|
45
|
+
if (typeof input?.name !== "string" ||
|
|
46
|
+
typeof input?.mime !== "string" ||
|
|
47
|
+
typeof input?.data !== "string") {
|
|
48
|
+
return { error: "name, mime, and base64 data are required" };
|
|
49
|
+
}
|
|
50
|
+
const ext = SCREENSHOT_MIME[input.mime];
|
|
51
|
+
if (!ext) {
|
|
52
|
+
const supported = Object.keys(SCREENSHOT_MIME).join(", ");
|
|
53
|
+
return { error: `Unsupported image type "${input.mime}" — supported: ${supported}` };
|
|
54
|
+
}
|
|
55
|
+
let buf;
|
|
56
|
+
try {
|
|
57
|
+
buf = Buffer.from(input.data, "base64");
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return { error: "Invalid base64 image data" };
|
|
61
|
+
}
|
|
62
|
+
if (buf.length === 0)
|
|
63
|
+
return { error: "Image data is empty" };
|
|
64
|
+
if (buf.length > MAX_SCREENSHOT_BYTES) {
|
|
65
|
+
return {
|
|
66
|
+
error: `Image is too large (max ${Math.round(MAX_SCREENSHOT_BYTES / 1024 / 1024)} MB)`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const dir = attachmentsDir(config.root, config.workDir, task.id);
|
|
70
|
+
mkdirSync(dir, { recursive: true });
|
|
71
|
+
const taken = new Set(existsSync(dir) ? readdirSync(dir) : []);
|
|
72
|
+
let next = 1;
|
|
73
|
+
while (taken.has(`screenshot-${next}${ext}`))
|
|
74
|
+
next++;
|
|
75
|
+
const file = `screenshot-${next}${ext}`;
|
|
76
|
+
writeFileSync(join(dir, file), buf);
|
|
77
|
+
return {
|
|
78
|
+
id: String(next),
|
|
79
|
+
name: sanitizeName(input.name),
|
|
80
|
+
path: `${config.workDir.split("\\").join("/")}/.attachments/${task.id}/${file}`,
|
|
81
|
+
url: screenshotUrl(task.id, file),
|
|
82
|
+
size: buf.length,
|
|
83
|
+
mime: input.mime,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Insert screenshots into a task body's `## Screenshots` section, keeping it
|
|
88
|
+
* BEFORE the append-only Activity section so the activity log stays the last
|
|
89
|
+
* thing in the body. Appends the section at the end when there is no Activity
|
|
90
|
+
* section. When a Screenshots section already exists (a second upload batch,
|
|
91
|
+
* or the 0381 pending-batch attach landing on a task that has one), the new
|
|
92
|
+
* images merge into that section — repeated uploads must not stack duplicate
|
|
93
|
+
* headings.
|
|
94
|
+
*/
|
|
95
|
+
export function appendScreenshotsSection(body, metas) {
|
|
96
|
+
const links = metas.map((m) => `![${m.name.replace(/[[\]]/g, "")}](${m.url})`);
|
|
97
|
+
const trimmed = body.replace(/\s+$/, "");
|
|
98
|
+
const activityIndex = trimmed.lastIndexOf("\n## Activity\n");
|
|
99
|
+
const before = activityIndex === -1 ? trimmed : trimmed.slice(0, activityIndex).replace(/\s+$/, "");
|
|
100
|
+
const after = activityIndex === -1 ? "" : trimmed.slice(activityIndex + 1);
|
|
101
|
+
const existing = extractSection(before, SCREENSHOTS_HEADING);
|
|
102
|
+
let next;
|
|
103
|
+
if (existing !== null) {
|
|
104
|
+
const without = removeSection(before, SCREENSHOTS_HEADING).replace(/\s+$/, "");
|
|
105
|
+
next = [without, [existing, ...links].join("\n")].filter(Boolean).join("\n\n");
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
next = [before, ["## Screenshots", "", ...links].join("\n")].filter(Boolean).join("\n\n");
|
|
109
|
+
}
|
|
110
|
+
return after ? `${next}\n\n${after}\n` : `${next}\n`;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve a stored screenshot to its absolute path, refusing path traversal
|
|
114
|
+
* outside the task's own attachment folder. Returns null on a miss.
|
|
115
|
+
*/
|
|
116
|
+
export function resolveScreenshot(config, taskId, file) {
|
|
117
|
+
const base = resolve(attachmentsDir(config.root, config.workDir, taskId));
|
|
118
|
+
const abs = resolve(base, decodeURIComponent(file));
|
|
119
|
+
if (!abs.startsWith(base + sep))
|
|
120
|
+
return null;
|
|
121
|
+
if (!existsSync(abs) || !statSync(abs).isFile())
|
|
122
|
+
return null;
|
|
123
|
+
return abs;
|
|
124
|
+
}
|
|
125
|
+
/** Reverse of {@link SCREENSHOT_MIME}: file extension -> MIME type. */
|
|
126
|
+
const EXTENSION_MIME = Object.fromEntries(Object.entries(SCREENSHOT_MIME).map(([mime, ext]) => [ext, mime]));
|
|
127
|
+
/** MIME type for a stored screenshot file by extension, or null when unknown. */
|
|
128
|
+
export function mimeForExtension(file) {
|
|
129
|
+
return EXTENSION_MIME[extname(file).toLowerCase()] ?? null;
|
|
130
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { RepoOSConfig, Task } from "../core/types.js";
|
|
2
|
+
/** Result of a reconciliation attempt. */
|
|
3
|
+
export interface ReconciliationResult {
|
|
4
|
+
triggered: boolean;
|
|
5
|
+
outcome?: "selected" | "no-capacity" | "no-ready-work" | "pm-unavailable" | "pm-failed";
|
|
6
|
+
candidateIds?: string[];
|
|
7
|
+
selectedIds?: string[];
|
|
8
|
+
rationale?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Persisted decision record for Control page hydration. */
|
|
12
|
+
export interface AutoEngineeringDecision {
|
|
13
|
+
timestamp: string;
|
|
14
|
+
trigger: "active-to-review" | "inbox-to-ready" | "config-change" | "startup";
|
|
15
|
+
outcome: ReconciliationResult["outcome"];
|
|
16
|
+
activeCount: number;
|
|
17
|
+
maxActiveTasks: number;
|
|
18
|
+
availableSlots: number;
|
|
19
|
+
candidateIds: string[];
|
|
20
|
+
selectedIds: string[];
|
|
21
|
+
rationale?: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Orchestrator for auto-engineering mode. Singleton per server lifetime;
|
|
26
|
+
* tracks mode state and decision history for Control page display.
|
|
27
|
+
* Decisions persist to disk for browser refresh recovery.
|
|
28
|
+
*/
|
|
29
|
+
export declare class AutoEngineeringOrchestrator {
|
|
30
|
+
private lastDecision;
|
|
31
|
+
private reconciling;
|
|
32
|
+
private cacheDir;
|
|
33
|
+
constructor(cacheDir?: string);
|
|
34
|
+
getLastDecision(): AutoEngineeringDecision | null;
|
|
35
|
+
/** Store the latest decision for recovery after server restart. */
|
|
36
|
+
private persistDecision;
|
|
37
|
+
/** Load the last persisted decision from disk (for browser refresh recovery). */
|
|
38
|
+
loadPersistedDecision(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Check if auto-engineering should trigger: mode enabled, available capacity,
|
|
41
|
+
* and at least one ready task. Returns early if already reconciling (single-flight).
|
|
42
|
+
*/
|
|
43
|
+
reconcile(config: RepoOSConfig, allTasks: Task[], trigger: AutoEngineeringDecision["trigger"]): Promise<ReconciliationResult>;
|
|
44
|
+
private reconcileImpl;
|
|
45
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-engineering mode: automatically selects and starts ready tasks up to a
|
|
3
|
+
* configured maximum. The PM agent decides which tasks are most suitable;
|
|
4
|
+
* orchestration remains server-owned with proper safeguards against
|
|
5
|
+
* oversubscription and stale state.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { resolvePmAgent, runPrompt, recordOneShotSession } from "./agents.js";
|
|
10
|
+
/**
|
|
11
|
+
* Render the PM prompt for auto-engineering task selection. Receives all
|
|
12
|
+
* ready tasks and the number of available slots, returns selected task IDs
|
|
13
|
+
* and brief rationale. The PM never selects more tasks than slots available.
|
|
14
|
+
*/
|
|
15
|
+
function autoEngineeringPrompt(readyTasks, availableSlots) {
|
|
16
|
+
const tasksList = readyTasks
|
|
17
|
+
.map((t) => `- **#${t.id}** ${t.title}\n Type: ${t.type}, Priority: ${t.priority}, Area: ${t.area || "unspecified"}`)
|
|
18
|
+
.join("\n");
|
|
19
|
+
return [
|
|
20
|
+
"You are the PM agent for RepoOS auto-engineering mode. The engineering team has capacity for",
|
|
21
|
+
`${availableSlots} more task(s). Choose which ready task(s) to start now based on priority,`,
|
|
22
|
+
"impact, and dependencies.",
|
|
23
|
+
"",
|
|
24
|
+
"Ready tasks available:",
|
|
25
|
+
"",
|
|
26
|
+
tasksList,
|
|
27
|
+
"",
|
|
28
|
+
"Respond with ONLY a JSON object (no preamble, no code fences):",
|
|
29
|
+
'{"selected": ["0123", "0124"], "rationale": "Starting the most impactful high-priority tasks"}',
|
|
30
|
+
"",
|
|
31
|
+
"- `selected`: array of task IDs to start (must not exceed available slots)",
|
|
32
|
+
"- `rationale`: one-sentence explanation of why these were chosen",
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse the PM agent's structured JSON response. Never throws: anything
|
|
37
|
+
* unparseable yields an empty selection with an error description.
|
|
38
|
+
*/
|
|
39
|
+
function parseAutoEngineeringSelection(output) {
|
|
40
|
+
try {
|
|
41
|
+
const trimmed = output.trim();
|
|
42
|
+
const obj = JSON.parse(trimmed);
|
|
43
|
+
if (typeof obj === "object" &&
|
|
44
|
+
obj !== null &&
|
|
45
|
+
Array.isArray(obj.selected) &&
|
|
46
|
+
typeof obj.rationale === "string") {
|
|
47
|
+
return {
|
|
48
|
+
selected: obj.selected,
|
|
49
|
+
rationale: obj.rationale.trim(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
// Fall through to error response
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
selected: [],
|
|
58
|
+
rationale: "",
|
|
59
|
+
error: "PM response was not valid JSON or missing required fields",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Orchestrator for auto-engineering mode. Singleton per server lifetime;
|
|
64
|
+
* tracks mode state and decision history for Control page display.
|
|
65
|
+
* Decisions persist to disk for browser refresh recovery.
|
|
66
|
+
*/
|
|
67
|
+
export class AutoEngineeringOrchestrator {
|
|
68
|
+
lastDecision = null;
|
|
69
|
+
reconciling = false;
|
|
70
|
+
cacheDir;
|
|
71
|
+
constructor(cacheDir) {
|
|
72
|
+
this.cacheDir = cacheDir ?? ".repoos";
|
|
73
|
+
}
|
|
74
|
+
getLastDecision() {
|
|
75
|
+
return this.lastDecision;
|
|
76
|
+
}
|
|
77
|
+
/** Store the latest decision for recovery after server restart. */
|
|
78
|
+
persistDecision(decision) {
|
|
79
|
+
try {
|
|
80
|
+
const filePath = join(this.cacheDir, "auto-engineering-decision.json");
|
|
81
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
82
|
+
writeFileSync(filePath, JSON.stringify(decision, null, 2));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Persistence is best-effort — failures don't block reconciliation
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Load the last persisted decision from disk (for browser refresh recovery). */
|
|
89
|
+
loadPersistedDecision() {
|
|
90
|
+
try {
|
|
91
|
+
const filePath = join(this.cacheDir, "auto-engineering-decision.json");
|
|
92
|
+
if (existsSync(filePath)) {
|
|
93
|
+
const data = readFileSync(filePath, "utf8");
|
|
94
|
+
this.lastDecision = JSON.parse(data);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Load failure is silent — just start with no persisted state
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if auto-engineering should trigger: mode enabled, available capacity,
|
|
103
|
+
* and at least one ready task. Returns early if already reconciling (single-flight).
|
|
104
|
+
*/
|
|
105
|
+
async reconcile(config, allTasks, trigger) {
|
|
106
|
+
if (this.reconciling) {
|
|
107
|
+
return { triggered: false };
|
|
108
|
+
}
|
|
109
|
+
const enabled = config.autoEngineeringMode ?? false;
|
|
110
|
+
if (!enabled) {
|
|
111
|
+
return { triggered: false };
|
|
112
|
+
}
|
|
113
|
+
this.reconciling = true;
|
|
114
|
+
try {
|
|
115
|
+
return await this.reconcileImpl(config, allTasks, trigger);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
this.reconciling = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async reconcileImpl(config, allTasks, trigger) {
|
|
122
|
+
const maxActiveTasks = config.maxActiveTasks ?? 3;
|
|
123
|
+
const activeCount = allTasks.filter((t) => t.status === "active").length;
|
|
124
|
+
const availableSlots = Math.max(0, maxActiveTasks - activeCount);
|
|
125
|
+
// No capacity available.
|
|
126
|
+
if (availableSlots === 0) {
|
|
127
|
+
this.lastDecision = {
|
|
128
|
+
timestamp: new Date().toISOString(),
|
|
129
|
+
trigger,
|
|
130
|
+
outcome: "no-capacity",
|
|
131
|
+
activeCount,
|
|
132
|
+
maxActiveTasks,
|
|
133
|
+
availableSlots,
|
|
134
|
+
candidateIds: [],
|
|
135
|
+
selectedIds: [],
|
|
136
|
+
};
|
|
137
|
+
this.persistDecision(this.lastDecision);
|
|
138
|
+
return {
|
|
139
|
+
triggered: true,
|
|
140
|
+
outcome: "no-capacity",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const readyTasks = allTasks.filter((t) => t.status === "ready");
|
|
144
|
+
// No ready tasks.
|
|
145
|
+
if (readyTasks.length === 0) {
|
|
146
|
+
this.lastDecision = {
|
|
147
|
+
timestamp: new Date().toISOString(),
|
|
148
|
+
trigger,
|
|
149
|
+
outcome: "no-ready-work",
|
|
150
|
+
activeCount,
|
|
151
|
+
maxActiveTasks,
|
|
152
|
+
availableSlots,
|
|
153
|
+
candidateIds: [],
|
|
154
|
+
selectedIds: [],
|
|
155
|
+
};
|
|
156
|
+
this.persistDecision(this.lastDecision);
|
|
157
|
+
return {
|
|
158
|
+
triggered: true,
|
|
159
|
+
outcome: "no-ready-work",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const pm = resolvePmAgent(config);
|
|
163
|
+
if (!pm) {
|
|
164
|
+
this.lastDecision = {
|
|
165
|
+
timestamp: new Date().toISOString(),
|
|
166
|
+
trigger,
|
|
167
|
+
outcome: "pm-unavailable",
|
|
168
|
+
activeCount,
|
|
169
|
+
maxActiveTasks,
|
|
170
|
+
availableSlots,
|
|
171
|
+
candidateIds: readyTasks.map((t) => t.id),
|
|
172
|
+
selectedIds: [],
|
|
173
|
+
error: "PM agent is not configured or enabled",
|
|
174
|
+
};
|
|
175
|
+
this.persistDecision(this.lastDecision);
|
|
176
|
+
return {
|
|
177
|
+
triggered: true,
|
|
178
|
+
outcome: "pm-unavailable",
|
|
179
|
+
error: "PM agent is not configured or enabled",
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// Invoke PM agent to select which ready tasks to start.
|
|
183
|
+
const prompt = autoEngineeringPrompt(readyTasks, availableSlots);
|
|
184
|
+
let pmResult;
|
|
185
|
+
try {
|
|
186
|
+
pmResult = await runPrompt(pm, prompt, {
|
|
187
|
+
cwd: config.root,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
const error = `PM agent failed: ${e instanceof Error ? e.message : String(e)}`;
|
|
192
|
+
this.lastDecision = {
|
|
193
|
+
timestamp: new Date().toISOString(),
|
|
194
|
+
trigger,
|
|
195
|
+
outcome: "pm-failed",
|
|
196
|
+
activeCount,
|
|
197
|
+
maxActiveTasks,
|
|
198
|
+
availableSlots,
|
|
199
|
+
candidateIds: readyTasks.map((t) => t.id),
|
|
200
|
+
selectedIds: [],
|
|
201
|
+
error,
|
|
202
|
+
};
|
|
203
|
+
this.persistDecision(this.lastDecision);
|
|
204
|
+
return {
|
|
205
|
+
triggered: true,
|
|
206
|
+
outcome: "pm-failed",
|
|
207
|
+
error,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Board-level PM spend — the dispatch pass belongs to no single task (0311).
|
|
211
|
+
recordOneShotSession(config.root, pm, pmResult, { sessionType: "dispatch", taskId: null });
|
|
212
|
+
if (!pmResult.ok || !pmResult.output) {
|
|
213
|
+
const error = pmResult.error || "PM agent returned no output";
|
|
214
|
+
this.lastDecision = {
|
|
215
|
+
timestamp: new Date().toISOString(),
|
|
216
|
+
trigger,
|
|
217
|
+
outcome: "pm-failed",
|
|
218
|
+
activeCount,
|
|
219
|
+
maxActiveTasks,
|
|
220
|
+
availableSlots,
|
|
221
|
+
candidateIds: readyTasks.map((t) => t.id),
|
|
222
|
+
selectedIds: [],
|
|
223
|
+
error,
|
|
224
|
+
};
|
|
225
|
+
this.persistDecision(this.lastDecision);
|
|
226
|
+
return {
|
|
227
|
+
triggered: true,
|
|
228
|
+
outcome: "pm-failed",
|
|
229
|
+
error,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const selection = parseAutoEngineeringSelection(pmResult.output);
|
|
233
|
+
if (selection.error) {
|
|
234
|
+
this.lastDecision = {
|
|
235
|
+
timestamp: new Date().toISOString(),
|
|
236
|
+
trigger,
|
|
237
|
+
outcome: "pm-failed",
|
|
238
|
+
activeCount,
|
|
239
|
+
maxActiveTasks,
|
|
240
|
+
availableSlots,
|
|
241
|
+
candidateIds: readyTasks.map((t) => t.id),
|
|
242
|
+
selectedIds: [],
|
|
243
|
+
error: selection.error,
|
|
244
|
+
};
|
|
245
|
+
this.persistDecision(this.lastDecision);
|
|
246
|
+
return {
|
|
247
|
+
triggered: true,
|
|
248
|
+
outcome: "pm-failed",
|
|
249
|
+
error: selection.error,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
// Validate selection: ensure all IDs exist in ready pool and don't exceed slots.
|
|
253
|
+
const readySet = new Set(readyTasks.map((t) => t.id));
|
|
254
|
+
const validated = selection.selected.filter((id) => readySet.has(id)).slice(0, availableSlots);
|
|
255
|
+
this.lastDecision = {
|
|
256
|
+
timestamp: new Date().toISOString(),
|
|
257
|
+
trigger,
|
|
258
|
+
outcome: "selected",
|
|
259
|
+
activeCount,
|
|
260
|
+
maxActiveTasks,
|
|
261
|
+
availableSlots,
|
|
262
|
+
candidateIds: readyTasks.map((t) => t.id),
|
|
263
|
+
selectedIds: validated,
|
|
264
|
+
rationale: selection.rationale,
|
|
265
|
+
};
|
|
266
|
+
this.persistDecision(this.lastDecision);
|
|
267
|
+
return {
|
|
268
|
+
triggered: true,
|
|
269
|
+
outcome: "selected",
|
|
270
|
+
candidateIds: readyTasks.map((t) => t.id),
|
|
271
|
+
selectedIds: validated,
|
|
272
|
+
rationale: selection.rationale,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RepoOSConfig, Task } from "../core/types.js";
|
|
2
|
+
export interface CheckResult {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
/** Tail of combined stdout+stderr on failure, for the caller to surface. */
|
|
5
|
+
output?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Real check runner: prefers the local build's own CLI, same preference order as the close-out pipeline. */
|
|
8
|
+
export declare function runCheckOnRoot(root: string): CheckResult;
|
|
9
|
+
export interface ReleaseBranchlessResult {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
reason?: string;
|
|
12
|
+
task?: Task;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A task is eligible for this path when it has no branch and isn't already
|
|
16
|
+
* done. `review`-status is deliberately excluded even if branchless — that
|
|
17
|
+
* shape shouldn't occur (nothing sets `review` without a branch), and if it
|
|
18
|
+
* ever does, the normal `/done` merge path's own guard is the one that
|
|
19
|
+
* should reject it, not this one silently swallowing it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isBranchlessReleaseEligible(task: Task): boolean;
|
|
22
|
+
export declare function releaseBranchless(config: RepoOSConfig, task: Task, runCheck?: (root: string) => CheckResult): Promise<ReleaseBranchlessResult>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release a branch-less task directly, skipping the branch-merge close-out
|
|
3
|
+
* pipeline entirely.
|
|
4
|
+
*
|
|
5
|
+
* A task fixed by a direct commit on `main` (a hotfix — see #0212, not yet a
|
|
6
|
+
* first-class flow) has nothing to merge: its code is already on `main`.
|
|
7
|
+
* Routing it through the normal `/done` pipeline dead-ends on "no branch to
|
|
8
|
+
* merge" — not because the task isn't done, but because that pipeline is
|
|
9
|
+
* built entirely around merging a branch. This is the separate path: verify
|
|
10
|
+
* `main` is currently green, then release directly via `markTaskReleased`.
|
|
11
|
+
*
|
|
12
|
+
* Never touches the job queue or the repository lock — there is no merge to
|
|
13
|
+
* serialize against other close-outs, so those mechanisms don't apply.
|
|
14
|
+
*/
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { markTaskReleased } from "./write.js";
|
|
19
|
+
/** Real check runner: prefers the local build's own CLI, same preference order as the close-out pipeline. */
|
|
20
|
+
export function runCheckOnRoot(root) {
|
|
21
|
+
const localCli = join(root, "dist", "cli", "index.js");
|
|
22
|
+
const candidates = existsSync(localCli)
|
|
23
|
+
? [
|
|
24
|
+
[process.execPath, localCli, "check"],
|
|
25
|
+
["repoos", "check"],
|
|
26
|
+
]
|
|
27
|
+
: [
|
|
28
|
+
["repoos", "check"],
|
|
29
|
+
["bun", "run", "repoos", "check"],
|
|
30
|
+
];
|
|
31
|
+
let last = null;
|
|
32
|
+
for (const [cmd, ...args] of candidates) {
|
|
33
|
+
const r = spawnSync(cmd, args, { cwd: root, encoding: "utf8", timeout: 600_000 });
|
|
34
|
+
last = r;
|
|
35
|
+
if (r.status === 0)
|
|
36
|
+
return { ok: true };
|
|
37
|
+
}
|
|
38
|
+
const output = [last?.stdout, last?.stderr]
|
|
39
|
+
.filter((x) => typeof x === "string" && x.trim() !== "")
|
|
40
|
+
.join("\n")
|
|
41
|
+
.trim()
|
|
42
|
+
.split("\n")
|
|
43
|
+
.slice(-15)
|
|
44
|
+
.join("\n");
|
|
45
|
+
return { ok: false, output: output || "no output captured" };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A task is eligible for this path when it has no branch and isn't already
|
|
49
|
+
* done. `review`-status is deliberately excluded even if branchless — that
|
|
50
|
+
* shape shouldn't occur (nothing sets `review` without a branch), and if it
|
|
51
|
+
* ever does, the normal `/done` merge path's own guard is the one that
|
|
52
|
+
* should reject it, not this one silently swallowing it.
|
|
53
|
+
*/
|
|
54
|
+
export function isBranchlessReleaseEligible(task) {
|
|
55
|
+
return !task.branch && task.status !== "done" && task.status !== "review";
|
|
56
|
+
}
|
|
57
|
+
export async function releaseBranchless(config, task, runCheck = runCheckOnRoot) {
|
|
58
|
+
const check = runCheck(config.root);
|
|
59
|
+
if (!check.ok) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
reason: `repoos check failed on main — cannot release a branch-less task until main is green: ${check.output ?? ""}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const task_ = markTaskReleased(config, task.absPath);
|
|
66
|
+
return { ok: true, task: task_ };
|
|
67
|
+
}
|