@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,33 @@
|
|
|
1
|
+
import { type RepoIndex, type RepoOSConfig } from "./types.js";
|
|
2
|
+
import { type WorktreeDirtyCache } from "./git.js";
|
|
3
|
+
export declare function buildIndex(config: RepoOSConfig): RepoIndex;
|
|
4
|
+
/**
|
|
5
|
+
* Async counterpart of `buildIndex`. Identical result, but the per-task git
|
|
6
|
+
* enrichment (last commit + worktree status) runs CONCURRENTLY instead of one
|
|
7
|
+
* task at a time on the main thread — `buildIndex`'s synchronous git spawns
|
|
8
|
+
* each block the event loop for a full OS round-trip, so N tasks cost N
|
|
9
|
+
* serial round-trips no matter how idle the machine is. With 260+ tasks that
|
|
10
|
+
* was several seconds of RepoOS's slow boot on top of the redundant spawns
|
|
11
|
+
* fixed above (#0271 follow-up). Used by the server's startup path, which can
|
|
12
|
+
* let `listen()` proceed while this populates the index in the background;
|
|
13
|
+
* `buildIndex` remains for callers (the CLI) that want a synchronous result.
|
|
14
|
+
*
|
|
15
|
+
* `opts.fastWorktreeStatus` skips the per-worktree `git status` entirely and
|
|
16
|
+
* reports each task's `dirty` from the persisted cache plus its ahead count.
|
|
17
|
+
* The server passes this at boot and follows up with
|
|
18
|
+
* `LiveIndex.reconcileWorktreeStatus()` a moment later, trading a few seconds
|
|
19
|
+
* of a possibly-stale "uncommitted changes" dot for a fast first paint.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildIndexAsync(config: RepoOSConfig, opts?: {
|
|
22
|
+
fastWorktreeStatus?: boolean;
|
|
23
|
+
}): Promise<RepoIndex>;
|
|
24
|
+
export declare function indexCachePath(config: RepoOSConfig): string;
|
|
25
|
+
/**
|
|
26
|
+
* Load the persisted per-branch working-tree dirtiness so a rebuild can skip
|
|
27
|
+
* `git status` for worktrees whose HEAD has not moved. Always safe: a missing,
|
|
28
|
+
* malformed, or stale file just means more `git status` calls this run.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readWorktreeDirtyCache(config: RepoOSConfig): WorktreeDirtyCache;
|
|
31
|
+
export declare function writeWorktreeDirtyCache(config: RepoOSConfig, cache: WorktreeDirtyCache): void;
|
|
32
|
+
export declare function writeIndexCache(config: RepoOSConfig, index: RepoIndex): void;
|
|
33
|
+
export declare function readIndexCache(config: RepoOSConfig): RepoIndex | null;
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The indexer. Walks the work directory, parses every task file, enriches with
|
|
3
|
+
* best-effort git facts, sorts deterministically, and produces a RepoIndex.
|
|
4
|
+
*
|
|
5
|
+
* The index is DERIVED: it can be thrown away and rebuilt from files at any
|
|
6
|
+
* time. It is written to <cacheDir>/index.json purely as a cache for fast reads
|
|
7
|
+
* (and, later, for the Stage 2 server to serve without re-walking on every
|
|
8
|
+
* request).
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { extname, join } from "node:path";
|
|
12
|
+
import { STATUSES, PRIORITIES, } from "./types.js";
|
|
13
|
+
import { parseTask } from "./task.js";
|
|
14
|
+
import { parseDocument } from "./frontmatter.js";
|
|
15
|
+
import { isGitRepo, localBranches, lastCommitsForDir, lastCommitsForDirAsync, worktreeStatus, worktreePaths, currentBranch, emptyGitInfo, resolveWorktreeStatuses, } from "./git.js";
|
|
16
|
+
const INDEX_VERSION = 1;
|
|
17
|
+
function walkFiles(dir, exts, acc = []) {
|
|
18
|
+
if (!existsSync(dir))
|
|
19
|
+
return acc;
|
|
20
|
+
for (const entry of readdirSync(dir)) {
|
|
21
|
+
if (entry.startsWith("."))
|
|
22
|
+
continue;
|
|
23
|
+
const full = join(dir, entry);
|
|
24
|
+
const st = statSync(full);
|
|
25
|
+
if (st.isDirectory()) {
|
|
26
|
+
walkFiles(full, exts, acc);
|
|
27
|
+
}
|
|
28
|
+
else if (exts.includes(extname(entry))) {
|
|
29
|
+
acc.push(full);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return acc;
|
|
33
|
+
}
|
|
34
|
+
function priorityRank(p) {
|
|
35
|
+
const i = PRIORITIES.indexOf(p);
|
|
36
|
+
return i === -1 ? PRIORITIES.length : i;
|
|
37
|
+
}
|
|
38
|
+
function statusRank(s) {
|
|
39
|
+
return STATUSES.indexOf(s);
|
|
40
|
+
}
|
|
41
|
+
export function buildIndex(config) {
|
|
42
|
+
const workPath = join(config.root, config.workDir);
|
|
43
|
+
const files = walkFiles(workPath, config.taskExtensions);
|
|
44
|
+
const useGit = isGitRepo(config.root);
|
|
45
|
+
const branches = useGit ? localBranches(config.root) : new Set();
|
|
46
|
+
const worktrees = useGit ? worktreePaths(config.root) : new Map();
|
|
47
|
+
// Fetched once and passed to every `worktreeStatus` call below — otherwise
|
|
48
|
+
// each of the N tasks with a branch re-runs `git worktree list` and
|
|
49
|
+
// `git rev-parse HEAD` itself, turning a 260-task index build into
|
|
50
|
+
// hundreds of redundant git spawns (#0271 follow-up: this was a large
|
|
51
|
+
// share of RepoOS's 20-30s boot time).
|
|
52
|
+
const baseBranch = useGit ? currentBranch(config.root) : null;
|
|
53
|
+
// One `git log` pass for every task file's last commit, instead of one
|
|
54
|
+
// history walk per file inside the loop below (#0271 follow-up).
|
|
55
|
+
const lastCommits = useGit
|
|
56
|
+
? lastCommitsForDir(config.root, config.workDir)
|
|
57
|
+
: new Map();
|
|
58
|
+
let skippedTaskFiles = 0;
|
|
59
|
+
const tasks = files.flatMap((absPath) => {
|
|
60
|
+
const content = readFileSync(absPath, "utf8");
|
|
61
|
+
// Reject files with no explicit `id` in frontmatter — the API always sets
|
|
62
|
+
// it, so a missing id means a human or AI agent hand-wrote the file. These
|
|
63
|
+
// get an opaque derived id and would silently pollute the task list.
|
|
64
|
+
const { data } = parseDocument(content);
|
|
65
|
+
if (!("id" in data)) {
|
|
66
|
+
skippedTaskFiles++;
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const base = parseTask({
|
|
70
|
+
content,
|
|
71
|
+
absPath,
|
|
72
|
+
root: config.root,
|
|
73
|
+
defaultStatus: config.defaultStatus,
|
|
74
|
+
defaultAssignee: config.defaultAssignee,
|
|
75
|
+
git: emptyGitInfo(),
|
|
76
|
+
});
|
|
77
|
+
if (useGit) {
|
|
78
|
+
const { subject, date } = lastCommits.get(base.path) ?? {
|
|
79
|
+
subject: null,
|
|
80
|
+
date: null,
|
|
81
|
+
};
|
|
82
|
+
const wt = base.branch
|
|
83
|
+
? worktreeStatus(config.root, base.branch, { worktrees, baseBranch })
|
|
84
|
+
: { path: null, dirty: false };
|
|
85
|
+
base.git = {
|
|
86
|
+
branchExists: base.branch ? branches.has(base.branch) : false,
|
|
87
|
+
worktreeExists: base.branch ? worktrees.has(base.branch) : false,
|
|
88
|
+
lastCommit: subject,
|
|
89
|
+
lastCommitAt: date,
|
|
90
|
+
worktreePath: wt.path,
|
|
91
|
+
dirty: wt.dirty,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return [base];
|
|
95
|
+
});
|
|
96
|
+
if (skippedTaskFiles > 0) {
|
|
97
|
+
console.warn(`[repoos] skipped ${skippedTaskFiles} task file(s) with no \`id\` in frontmatter — ` +
|
|
98
|
+
`use the API (POST /api/tasks or PATCH /api/tasks/:id) instead of writing work/*.md directly`);
|
|
99
|
+
}
|
|
100
|
+
tasks.sort((a, b) => {
|
|
101
|
+
const s = statusRank(a.status) - statusRank(b.status);
|
|
102
|
+
if (s !== 0)
|
|
103
|
+
return s;
|
|
104
|
+
const p = priorityRank(a.priority) - priorityRank(b.priority);
|
|
105
|
+
if (p !== 0)
|
|
106
|
+
return p;
|
|
107
|
+
return a.id.localeCompare(b.id);
|
|
108
|
+
});
|
|
109
|
+
const counts = Object.fromEntries(STATUSES.map((s) => [s, 0]));
|
|
110
|
+
for (const t of tasks)
|
|
111
|
+
counts[t.status]++;
|
|
112
|
+
return {
|
|
113
|
+
version: INDEX_VERSION,
|
|
114
|
+
generatedAt: new Date().toISOString(),
|
|
115
|
+
root: config.root,
|
|
116
|
+
taskCount: tasks.length,
|
|
117
|
+
tasks,
|
|
118
|
+
counts,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Async counterpart of `buildIndex`. Identical result, but the per-task git
|
|
123
|
+
* enrichment (last commit + worktree status) runs CONCURRENTLY instead of one
|
|
124
|
+
* task at a time on the main thread — `buildIndex`'s synchronous git spawns
|
|
125
|
+
* each block the event loop for a full OS round-trip, so N tasks cost N
|
|
126
|
+
* serial round-trips no matter how idle the machine is. With 260+ tasks that
|
|
127
|
+
* was several seconds of RepoOS's slow boot on top of the redundant spawns
|
|
128
|
+
* fixed above (#0271 follow-up). Used by the server's startup path, which can
|
|
129
|
+
* let `listen()` proceed while this populates the index in the background;
|
|
130
|
+
* `buildIndex` remains for callers (the CLI) that want a synchronous result.
|
|
131
|
+
*
|
|
132
|
+
* `opts.fastWorktreeStatus` skips the per-worktree `git status` entirely and
|
|
133
|
+
* reports each task's `dirty` from the persisted cache plus its ahead count.
|
|
134
|
+
* The server passes this at boot and follows up with
|
|
135
|
+
* `LiveIndex.reconcileWorktreeStatus()` a moment later, trading a few seconds
|
|
136
|
+
* of a possibly-stale "uncommitted changes" dot for a fast first paint.
|
|
137
|
+
*/
|
|
138
|
+
export async function buildIndexAsync(config, opts = {}) {
|
|
139
|
+
const workPath = join(config.root, config.workDir);
|
|
140
|
+
const files = walkFiles(workPath, config.taskExtensions);
|
|
141
|
+
const useGit = isGitRepo(config.root);
|
|
142
|
+
const branches = useGit ? localBranches(config.root) : new Set();
|
|
143
|
+
const worktrees = useGit ? worktreePaths(config.root) : new Map();
|
|
144
|
+
const baseBranch = useGit ? currentBranch(config.root) : null;
|
|
145
|
+
const lastCommits = useGit
|
|
146
|
+
? await lastCommitsForDirAsync(config.root, config.workDir)
|
|
147
|
+
: new Map();
|
|
148
|
+
let skippedTaskFiles = 0;
|
|
149
|
+
const tasks = [];
|
|
150
|
+
for (const absPath of files) {
|
|
151
|
+
const content = readFileSync(absPath, "utf8");
|
|
152
|
+
const { data } = parseDocument(content);
|
|
153
|
+
if (!("id" in data)) {
|
|
154
|
+
skippedTaskFiles++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
tasks.push(parseTask({
|
|
158
|
+
content,
|
|
159
|
+
absPath,
|
|
160
|
+
root: config.root,
|
|
161
|
+
defaultStatus: config.defaultStatus,
|
|
162
|
+
defaultAssignee: config.defaultAssignee,
|
|
163
|
+
git: emptyGitInfo(),
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
if (skippedTaskFiles > 0) {
|
|
167
|
+
console.warn(`[repoos] skipped ${skippedTaskFiles} task file(s) with no \`id\` in frontmatter — ` +
|
|
168
|
+
`use the API (POST /api/tasks or PATCH /api/tasks/:id) instead of writing work/*.md directly`);
|
|
169
|
+
}
|
|
170
|
+
if (useGit) {
|
|
171
|
+
// Batched worktree status: two git spawns for every branch's ahead count
|
|
172
|
+
// and worktree HEAD, then one `git status` only per worktree whose HEAD
|
|
173
|
+
// moved since the last run (persisted in worktree-status.json). On a
|
|
174
|
+
// server reload — HEADs unchanged — this does zero `git status` calls;
|
|
175
|
+
// the old path ran two spawns per task branch (~2.5s at 40 branches).
|
|
176
|
+
const dirtyCache = readWorktreeDirtyCache(config);
|
|
177
|
+
const branchesWithTasks = tasks.map((t) => t.branch).filter((b) => !!b);
|
|
178
|
+
const { statuses, cache, changed } = await resolveWorktreeStatuses(config.root, branchesWithTasks, { baseBranch, cache: dirtyCache, skipStatus: opts.fastWorktreeStatus });
|
|
179
|
+
if (changed)
|
|
180
|
+
writeWorktreeDirtyCache(config, cache);
|
|
181
|
+
for (const base of tasks) {
|
|
182
|
+
const { subject, date } = lastCommits.get(base.path) ?? {
|
|
183
|
+
subject: null,
|
|
184
|
+
date: null,
|
|
185
|
+
};
|
|
186
|
+
const wt = (base.branch && statuses.get(base.branch)) || { path: null, dirty: false };
|
|
187
|
+
base.git = {
|
|
188
|
+
branchExists: base.branch ? branches.has(base.branch) : false,
|
|
189
|
+
worktreeExists: base.branch ? worktrees.has(base.branch) : false,
|
|
190
|
+
lastCommit: subject,
|
|
191
|
+
lastCommitAt: date,
|
|
192
|
+
worktreePath: wt.path,
|
|
193
|
+
dirty: wt.dirty,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
tasks.sort((a, b) => {
|
|
198
|
+
const s = statusRank(a.status) - statusRank(b.status);
|
|
199
|
+
if (s !== 0)
|
|
200
|
+
return s;
|
|
201
|
+
const p = priorityRank(a.priority) - priorityRank(b.priority);
|
|
202
|
+
if (p !== 0)
|
|
203
|
+
return p;
|
|
204
|
+
return a.id.localeCompare(b.id);
|
|
205
|
+
});
|
|
206
|
+
const counts = Object.fromEntries(STATUSES.map((s) => [s, 0]));
|
|
207
|
+
for (const t of tasks)
|
|
208
|
+
counts[t.status]++;
|
|
209
|
+
return {
|
|
210
|
+
version: INDEX_VERSION,
|
|
211
|
+
generatedAt: new Date().toISOString(),
|
|
212
|
+
root: config.root,
|
|
213
|
+
taskCount: tasks.length,
|
|
214
|
+
tasks,
|
|
215
|
+
counts,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
export function indexCachePath(config) {
|
|
219
|
+
return join(config.root, config.cacheDir, "index.json");
|
|
220
|
+
}
|
|
221
|
+
function worktreeDirtyCachePath(config) {
|
|
222
|
+
return join(config.root, config.cacheDir, "worktree-status.json");
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Load the persisted per-branch working-tree dirtiness so a rebuild can skip
|
|
226
|
+
* `git status` for worktrees whose HEAD has not moved. Always safe: a missing,
|
|
227
|
+
* malformed, or stale file just means more `git status` calls this run.
|
|
228
|
+
*/
|
|
229
|
+
export function readWorktreeDirtyCache(config) {
|
|
230
|
+
const cache = new Map();
|
|
231
|
+
try {
|
|
232
|
+
const raw = JSON.parse(readFileSync(worktreeDirtyCachePath(config), "utf8"));
|
|
233
|
+
if (raw?.version === 1 && raw.branches) {
|
|
234
|
+
for (const [branch, v] of Object.entries(raw.branches)) {
|
|
235
|
+
if (v && typeof v.head === "string" && typeof v.uncommitted === "boolean") {
|
|
236
|
+
cache.set(branch, { head: v.head, uncommitted: v.uncommitted });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
/* no cache / bad cache -> empty */
|
|
243
|
+
}
|
|
244
|
+
return cache;
|
|
245
|
+
}
|
|
246
|
+
export function writeWorktreeDirtyCache(config, cache) {
|
|
247
|
+
const dir = join(config.root, config.cacheDir);
|
|
248
|
+
try {
|
|
249
|
+
if (!existsSync(dir))
|
|
250
|
+
mkdirSync(dir, { recursive: true });
|
|
251
|
+
const branches = {};
|
|
252
|
+
for (const [branch, v] of cache)
|
|
253
|
+
branches[branch] = { ...v };
|
|
254
|
+
writeFileSync(worktreeDirtyCachePath(config), JSON.stringify({ version: 1, branches }));
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
/* best-effort: losing the cache only costs `git status` calls next run */
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
export function writeIndexCache(config, index) {
|
|
261
|
+
const dir = join(config.root, config.cacheDir);
|
|
262
|
+
if (!existsSync(dir))
|
|
263
|
+
mkdirSync(dir, { recursive: true });
|
|
264
|
+
// Strip absPath/body from the cache to keep it small and portable; callers
|
|
265
|
+
// that need them should read the file or rebuild in-memory.
|
|
266
|
+
const slim = {
|
|
267
|
+
...index,
|
|
268
|
+
tasks: index.tasks.map((t) => ({ ...t, body: "", absPath: "" })),
|
|
269
|
+
};
|
|
270
|
+
writeFileSync(indexCachePath(config), JSON.stringify(slim, null, 2));
|
|
271
|
+
}
|
|
272
|
+
export function readIndexCache(config) {
|
|
273
|
+
const p = indexCachePath(config);
|
|
274
|
+
if (!existsSync(p))
|
|
275
|
+
return null;
|
|
276
|
+
try {
|
|
277
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { RepoOSConfig } from "./types.js";
|
|
2
|
+
export type InputStatus = "new" | "reviewing" | "processed";
|
|
3
|
+
/**
|
|
4
|
+
* How a `processed` input was resolved: `task` means it became a task (the id
|
|
5
|
+
* is in `resolvedTask`), `none` means the decision was to take no action. An
|
|
6
|
+
* empty string means no resolution was recorded (e.g. an input moved to
|
|
7
|
+
* `processed` by hand before this feature existed).
|
|
8
|
+
*/
|
|
9
|
+
export type InputResolution = "task" | "none";
|
|
10
|
+
export interface InputAttachment {
|
|
11
|
+
name: string;
|
|
12
|
+
mime: string;
|
|
13
|
+
size: number;
|
|
14
|
+
path: string;
|
|
15
|
+
}
|
|
16
|
+
export interface Input {
|
|
17
|
+
id: string;
|
|
18
|
+
/**
|
|
19
|
+
* Stable, zero-padded 4-digit number (`"0001"`), the input's counterpart to
|
|
20
|
+
* a task's `id`. Assigned once — at creation, or retroactively by
|
|
21
|
+
* `ensureInputNumbers` — and never reused or renumbered. Empty string only
|
|
22
|
+
* for an input written before this field existed and not yet migrated.
|
|
23
|
+
*/
|
|
24
|
+
number: string;
|
|
25
|
+
title: string;
|
|
26
|
+
status: InputStatus;
|
|
27
|
+
body: string;
|
|
28
|
+
type: string;
|
|
29
|
+
area: string;
|
|
30
|
+
createdBy: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
path: string;
|
|
34
|
+
attachments: InputAttachment[];
|
|
35
|
+
resolution: InputResolution | "";
|
|
36
|
+
resolvedTask: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Retroactively assign a stable number to every input that lacks one, in
|
|
40
|
+
* place. Idempotent: numbers already present are never touched, renumbered, or
|
|
41
|
+
* reused, so a restart (or a repeated call) is a no-op. Existing inputs are
|
|
42
|
+
* numbered oldest-first (by `created_at`, then path) so the assignment is
|
|
43
|
+
* deterministic. Returns the inputs that were changed, for callers that want
|
|
44
|
+
* to commit them; an empty array means everything already had a number.
|
|
45
|
+
* See AGENTS.md — this touches the repo's own `inputs/*.md` data.
|
|
46
|
+
*/
|
|
47
|
+
export declare function ensureInputNumbers(c: RepoOSConfig): Input[];
|
|
48
|
+
export declare function listInputs(c: RepoOSConfig): Input[];
|
|
49
|
+
export declare function createInput(c: RepoOSConfig, body: string, type?: string, createdBy?: string): Input;
|
|
50
|
+
export declare function updateInput(c: RepoOSConfig, id: string, status: InputStatus): Input;
|
|
51
|
+
/**
|
|
52
|
+
* Record how an input was resolved and move it to `processed`. `task` stores
|
|
53
|
+
* the created task's id in `resolved_task`; `none` clears it. Both keys are
|
|
54
|
+
* persisted in the input file so the outcome survives a reload.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveInput(c: RepoOSConfig, id: string, resolution: InputResolution, taskId?: string): Input;
|
|
57
|
+
export declare function enrichInput(c: RepoOSConfig, id: string, fields: {
|
|
58
|
+
title?: string;
|
|
59
|
+
type?: string;
|
|
60
|
+
area?: string;
|
|
61
|
+
}): Input;
|
|
62
|
+
export declare function saveInputAttachment(c: RepoOSConfig, id: string, name: string, data: string): InputAttachment;
|
|
63
|
+
export declare function readInputAttachment(c: RepoOSConfig, id: string, name: string): {
|
|
64
|
+
data: Buffer;
|
|
65
|
+
mime: string;
|
|
66
|
+
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { parseDocument } from "./frontmatter.js";
|
|
4
|
+
const q = (v) => `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
5
|
+
/**
|
|
6
|
+
* Set a frontmatter key in a file's raw content, adding it to the block if it
|
|
7
|
+
* isn't there yet. Assumes the content's frontmatter is the first `---` block
|
|
8
|
+
* (true for every input file `createInput` writes).
|
|
9
|
+
*/
|
|
10
|
+
function setField(content, key, value) {
|
|
11
|
+
const line = `${key}: ${q(value)}`;
|
|
12
|
+
const re = new RegExp(`^${key}:.*$`, "m");
|
|
13
|
+
return re.test(content) ? content.replace(re, line) : content.replace("---\n", `---\n${line}\n`);
|
|
14
|
+
}
|
|
15
|
+
const slug = (v) => v
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
18
|
+
.replace(/^-|-$/g, "")
|
|
19
|
+
.slice(0, 55) || "input";
|
|
20
|
+
const inputRoot = (c) => c.inputsDir ?? "inputs";
|
|
21
|
+
const dir = (c) => join(c.root, inputRoot(c));
|
|
22
|
+
const attDir = (c, id) => join(dir(c), ".attachments", id);
|
|
23
|
+
/**
|
|
24
|
+
* Normalize a raw frontmatter `number` to the canonical zero-padded form.
|
|
25
|
+
* Returns `""` for anything that isn't a plain integer (missing, empty, or
|
|
26
|
+
* garbage), which callers treat as "needs a number".
|
|
27
|
+
*/
|
|
28
|
+
function normalizeNumber(v) {
|
|
29
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
30
|
+
return String(Math.trunc(v)).padStart(4, "0");
|
|
31
|
+
if (typeof v === "string" && /^\d+$/.test(v.trim()))
|
|
32
|
+
return v.trim().padStart(4, "0");
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
/** Next free 4-digit input number: highest existing number + 1, else `"0001"`. */
|
|
36
|
+
function nextInputNumber(c) {
|
|
37
|
+
let max = 0;
|
|
38
|
+
for (const i of listInputs(c)) {
|
|
39
|
+
const n = parseInt(i.number, 10);
|
|
40
|
+
if (!Number.isNaN(n) && n > max)
|
|
41
|
+
max = n;
|
|
42
|
+
}
|
|
43
|
+
return String(max + 1).padStart(4, "0");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Retroactively assign a stable number to every input that lacks one, in
|
|
47
|
+
* place. Idempotent: numbers already present are never touched, renumbered, or
|
|
48
|
+
* reused, so a restart (or a repeated call) is a no-op. Existing inputs are
|
|
49
|
+
* numbered oldest-first (by `created_at`, then path) so the assignment is
|
|
50
|
+
* deterministic. Returns the inputs that were changed, for callers that want
|
|
51
|
+
* to commit them; an empty array means everything already had a number.
|
|
52
|
+
* See AGENTS.md — this touches the repo's own `inputs/*.md` data.
|
|
53
|
+
*/
|
|
54
|
+
export function ensureInputNumbers(c) {
|
|
55
|
+
const items = listInputs(c);
|
|
56
|
+
const missing = items
|
|
57
|
+
.filter((i) => !i.number)
|
|
58
|
+
.sort((a, b) => (a.createdAt || "").localeCompare(b.createdAt || "") || a.path.localeCompare(b.path));
|
|
59
|
+
if (!missing.length)
|
|
60
|
+
return [];
|
|
61
|
+
const used = new Set(items.map((i) => i.number).filter(Boolean));
|
|
62
|
+
let max = 0;
|
|
63
|
+
for (const n of used)
|
|
64
|
+
max = Math.max(max, parseInt(n, 10));
|
|
65
|
+
const changed = [];
|
|
66
|
+
for (const item of missing) {
|
|
67
|
+
let number = String(max + 1).padStart(4, "0");
|
|
68
|
+
while (used.has(number))
|
|
69
|
+
number = String(++max).padStart(4, "0");
|
|
70
|
+
used.add(number);
|
|
71
|
+
max = parseInt(number, 10);
|
|
72
|
+
const file = join(c.root, item.path);
|
|
73
|
+
writeFileSync(file, setField(readFileSync(file, "utf8"), "number", number));
|
|
74
|
+
changed.push({ ...item, number });
|
|
75
|
+
}
|
|
76
|
+
return changed;
|
|
77
|
+
}
|
|
78
|
+
export function listInputs(c) {
|
|
79
|
+
if (!existsSync(dir(c)))
|
|
80
|
+
return [];
|
|
81
|
+
return readdirSync(dir(c))
|
|
82
|
+
.filter((n) => n.endsWith(".md"))
|
|
83
|
+
.flatMap((name) => {
|
|
84
|
+
const path = join(inputRoot(c), name), parsed = parseDocument(readFileSync(join(dir(c), name), "utf8")), d = parsed.data;
|
|
85
|
+
if (typeof d.id !== "string")
|
|
86
|
+
return [];
|
|
87
|
+
const ad = attDir(c, d.id), attachments = existsSync(ad)
|
|
88
|
+
? readdirSync(ad).map((n) => ({
|
|
89
|
+
name: n,
|
|
90
|
+
mime: mimeForName(n),
|
|
91
|
+
size: statSync(join(ad, n)).size,
|
|
92
|
+
path: join(inputRoot(c), ".attachments", d.id, n),
|
|
93
|
+
}))
|
|
94
|
+
: [];
|
|
95
|
+
return [
|
|
96
|
+
{
|
|
97
|
+
id: d.id,
|
|
98
|
+
number: normalizeNumber(d.number),
|
|
99
|
+
title: String(d.title ?? "Untitled input"),
|
|
100
|
+
status: (d.status === "reviewing" || d.status === "processed"
|
|
101
|
+
? d.status
|
|
102
|
+
: "new"),
|
|
103
|
+
type: String(d.type ?? "other"),
|
|
104
|
+
area: String(d.area ?? ""),
|
|
105
|
+
createdBy: String(d.created_by ?? ""),
|
|
106
|
+
createdAt: String(d.created_at ?? ""),
|
|
107
|
+
updatedAt: String(d.updated_at ?? ""),
|
|
108
|
+
path,
|
|
109
|
+
body: parsed.body.trim(),
|
|
110
|
+
attachments,
|
|
111
|
+
resolution: (d.resolution === "task" || d.resolution === "none" ? d.resolution : ""),
|
|
112
|
+
resolvedTask: typeof d.resolved_task === "string" ? d.resolved_task : "",
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
})
|
|
116
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
117
|
+
}
|
|
118
|
+
export function createInput(c, body, type = "other", createdBy = "") {
|
|
119
|
+
const now = new Date().toISOString(), id = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`, number = nextInputNumber(c), title = body.trim().split(/\n/)[0].replace(/^#\s*/, "").slice(0, 100) || "Untitled input", name = `${id}-${slug(title)}.md`;
|
|
120
|
+
mkdirSync(dir(c), { recursive: true });
|
|
121
|
+
writeFileSync(join(dir(c), name), [
|
|
122
|
+
"---",
|
|
123
|
+
`id: ${q(id)}`,
|
|
124
|
+
`number: ${q(number)}`,
|
|
125
|
+
`title: ${q(title)}`,
|
|
126
|
+
"status: new",
|
|
127
|
+
`type: ${q(type)}`,
|
|
128
|
+
`created_by: ${q(createdBy)}`,
|
|
129
|
+
`created_at: ${q(now)}`,
|
|
130
|
+
`updated_at: ${q(now)}`,
|
|
131
|
+
"---",
|
|
132
|
+
"",
|
|
133
|
+
body.trim(),
|
|
134
|
+
"",
|
|
135
|
+
].join("\n"));
|
|
136
|
+
return listInputs(c).find((i) => i.id === id);
|
|
137
|
+
}
|
|
138
|
+
export function updateInput(c, id, status) {
|
|
139
|
+
const item = listInputs(c).find((i) => i.id === id);
|
|
140
|
+
if (!item)
|
|
141
|
+
throw new Error("input not found");
|
|
142
|
+
const file = join(c.root, item.path);
|
|
143
|
+
let content = readFileSync(file, "utf8")
|
|
144
|
+
.replace(/^status:.*$/m, `status: ${status}`)
|
|
145
|
+
.replace(/^updated_at:.*$/m, `updated_at: ${q(new Date().toISOString())}`);
|
|
146
|
+
// A manual move out of `processed` invalidates any recorded resolution —
|
|
147
|
+
// leaving it behind would re-display a stale outcome if the input is later
|
|
148
|
+
// marked processed again without a real resolve action.
|
|
149
|
+
if (status !== "processed") {
|
|
150
|
+
content = setField(content, "resolution", "");
|
|
151
|
+
content = setField(content, "resolved_task", "");
|
|
152
|
+
}
|
|
153
|
+
writeFileSync(file, content);
|
|
154
|
+
return listInputs(c).find((i) => i.id === id);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Record how an input was resolved and move it to `processed`. `task` stores
|
|
158
|
+
* the created task's id in `resolved_task`; `none` clears it. Both keys are
|
|
159
|
+
* persisted in the input file so the outcome survives a reload.
|
|
160
|
+
*/
|
|
161
|
+
export function resolveInput(c, id, resolution, taskId = "") {
|
|
162
|
+
const item = listInputs(c).find((i) => i.id === id);
|
|
163
|
+
if (!item)
|
|
164
|
+
throw new Error("input not found");
|
|
165
|
+
const file = join(c.root, item.path);
|
|
166
|
+
let content = readFileSync(file, "utf8");
|
|
167
|
+
content = setField(content, "status", "processed");
|
|
168
|
+
content = setField(content, "resolution", resolution);
|
|
169
|
+
content = setField(content, "resolved_task", resolution === "task" ? taskId : "");
|
|
170
|
+
content = content.replace(/^updated_at:.*$/m, `updated_at: ${q(new Date().toISOString())}`);
|
|
171
|
+
writeFileSync(file, content);
|
|
172
|
+
return listInputs(c).find((i) => i.id === id);
|
|
173
|
+
}
|
|
174
|
+
export function enrichInput(c, id, fields) {
|
|
175
|
+
const item = listInputs(c).find((i) => i.id === id);
|
|
176
|
+
if (!item)
|
|
177
|
+
throw new Error("input not found");
|
|
178
|
+
const file = join(c.root, item.path);
|
|
179
|
+
let content = readFileSync(file, "utf8");
|
|
180
|
+
for (const [key, value] of Object.entries(fields))
|
|
181
|
+
if (typeof value === "string" && value.trim()) {
|
|
182
|
+
content = setField(content, key, value.trim());
|
|
183
|
+
}
|
|
184
|
+
content = content.replace(/^updated_at:.*$/m, `updated_at: ${q(new Date().toISOString())}`);
|
|
185
|
+
writeFileSync(file, content);
|
|
186
|
+
return listInputs(c).find((i) => i.id === id);
|
|
187
|
+
}
|
|
188
|
+
export function saveInputAttachment(c, id, name, data) {
|
|
189
|
+
if (!listInputs(c).some((i) => i.id === id))
|
|
190
|
+
throw new Error("input not found");
|
|
191
|
+
const safe = name.replace(/[^a-zA-Z0-9._-]/g, "-") || "attachment", target = join(attDir(c, id), safe);
|
|
192
|
+
mkdirSync(attDir(c, id), { recursive: true });
|
|
193
|
+
writeFileSync(target, Buffer.from(data, "base64"));
|
|
194
|
+
return {
|
|
195
|
+
name: safe,
|
|
196
|
+
mime: mimeForName(safe),
|
|
197
|
+
size: Buffer.byteLength(data, "base64"),
|
|
198
|
+
path: join(inputRoot(c), ".attachments", id, safe),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function mimeForName(name) {
|
|
202
|
+
const ext = name.toLowerCase().split(".").pop();
|
|
203
|
+
return ({
|
|
204
|
+
png: "image/png",
|
|
205
|
+
jpg: "image/jpeg",
|
|
206
|
+
jpeg: "image/jpeg",
|
|
207
|
+
gif: "image/gif",
|
|
208
|
+
webp: "image/webp",
|
|
209
|
+
svg: "image/svg+xml",
|
|
210
|
+
pdf: "application/pdf",
|
|
211
|
+
txt: "text/plain",
|
|
212
|
+
md: "text/markdown",
|
|
213
|
+
}[ext ?? ""] ?? "application/octet-stream");
|
|
214
|
+
}
|
|
215
|
+
export function readInputAttachment(c, id, name) {
|
|
216
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(name) || !listInputs(c).some((i) => i.id === id))
|
|
217
|
+
throw new Error("attachment not found");
|
|
218
|
+
const base = resolve(attDir(c, id)), file = resolve(join(base, name));
|
|
219
|
+
if (!file.startsWith(`${base}/`) || !existsSync(file))
|
|
220
|
+
throw new Error("attachment not found");
|
|
221
|
+
return { data: readFileSync(file), mime: mimeForName(name) };
|
|
222
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized logging for RepoOS system, tasks, and agents.
|
|
3
|
+
* Logs are stored as newline-delimited JSON (NDJSON) in .repoos/logs/
|
|
4
|
+
* Structure:
|
|
5
|
+
* .repoos/logs/system.log - System-wide errors and critical events
|
|
6
|
+
* .repoos/logs/tasks/<id>.log - Per-task lifecycle and state changes
|
|
7
|
+
* .repoos/logs/agents/<id>.log - Per-agent activity and errors
|
|
8
|
+
*/
|
|
9
|
+
export type LogLevel = "debug" | "info" | "warn" | "error" | "fatal";
|
|
10
|
+
export interface LogEntry {
|
|
11
|
+
timestamp: string;
|
|
12
|
+
level: LogLevel;
|
|
13
|
+
component: "system" | "task" | "agent" | "integration";
|
|
14
|
+
message: string;
|
|
15
|
+
context?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
interface LoggerOptions {
|
|
18
|
+
root: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Central logger for RepoOS. Creates and manages log files in .repoos/logs/
|
|
22
|
+
*/
|
|
23
|
+
export declare class Logger {
|
|
24
|
+
private root;
|
|
25
|
+
private logsDir;
|
|
26
|
+
constructor(opts: LoggerOptions);
|
|
27
|
+
/**
|
|
28
|
+
* Log a system-wide event or error. Includes fatal crashes and critical failures.
|
|
29
|
+
*/
|
|
30
|
+
system(level: LogLevel, message: string, context?: Record<string, unknown>): void;
|
|
31
|
+
/**
|
|
32
|
+
* Log a task lifecycle event: state changes, errors, transitions through pipeline.
|
|
33
|
+
*/
|
|
34
|
+
task(taskId: string, level: LogLevel, message: string, context?: Record<string, unknown>): void;
|
|
35
|
+
/**
|
|
36
|
+
* Log agent activity: startup, completion, errors, output streams.
|
|
37
|
+
*/
|
|
38
|
+
agent(agentId: string, level: LogLevel, message: string, context?: Record<string, unknown>): void;
|
|
39
|
+
/**
|
|
40
|
+
* Log integration/merge job activity (phase transitions, errors).
|
|
41
|
+
*/
|
|
42
|
+
integration(taskId: string, level: LogLevel, message: string, context?: Record<string, unknown>): void;
|
|
43
|
+
/**
|
|
44
|
+
* Read up to `limit` log entries for a specific task or agent, in
|
|
45
|
+
* chronological order (oldest first). Callers that want most-recent-first
|
|
46
|
+
* (`getTaskLogs`, `getAgentLogs`, `getSystemLogs`) reverse the result.
|
|
47
|
+
*/
|
|
48
|
+
readLogs(logPath: string, limit?: number): LogEntry[];
|
|
49
|
+
/**
|
|
50
|
+
* Get task logs in reverse order (most recent first).
|
|
51
|
+
*/
|
|
52
|
+
getTaskLogs(taskId: string, limit?: number): LogEntry[];
|
|
53
|
+
/**
|
|
54
|
+
* Get agent logs in reverse order (most recent first).
|
|
55
|
+
*/
|
|
56
|
+
getAgentLogs(agentId: string, limit?: number): LogEntry[];
|
|
57
|
+
/**
|
|
58
|
+
* Get system logs in reverse order (most recent first).
|
|
59
|
+
*/
|
|
60
|
+
getSystemLogs(limit?: number): LogEntry[];
|
|
61
|
+
/**
|
|
62
|
+
* Write a log entry as NDJSON to the appropriate log file.
|
|
63
|
+
*/
|
|
64
|
+
private write;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a logger for a repository root.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createLogger(root: string): Logger;
|
|
70
|
+
export {};
|