@repo-os/repoos 0.5.75 → 0.5.76

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.
Files changed (192) hide show
  1. package/README.md +12 -15
  2. package/dist/.build-info.json +2 -2
  3. package/dist/.build-stamp.json +1 -1
  4. package/dist/cli/api-route-parity.js +2 -0
  5. package/dist/cli/help.js +6 -0
  6. package/dist/cli/index.js +7 -1
  7. package/dist/commands/control-api.d.ts +6 -0
  8. package/dist/commands/control-api.js +53 -3
  9. package/dist/commands/init.js +13 -13
  10. package/dist/commands/schedule.js +15 -1
  11. package/dist/commands/serve-banner.test.js +24 -1
  12. package/dist/commands/serve.d.ts +5 -0
  13. package/dist/commands/serve.js +20 -0
  14. package/dist/commands/status.js +2 -2
  15. package/dist/commands/tasks.js +1 -1
  16. package/dist/commands/uninstall.d.ts +3 -0
  17. package/dist/commands/uninstall.js +84 -21
  18. package/dist/core/active-task-card.d.ts +11 -0
  19. package/dist/core/active-task-card.js +16 -1
  20. package/dist/core/check-plan.js +1 -1
  21. package/dist/core/check-skip.js +1 -1
  22. package/dist/core/config.js +29 -0
  23. package/dist/core/cross-process-slots.js +61 -22
  24. package/dist/core/cto-idle-nudge.d.ts +13 -0
  25. package/dist/core/cto-idle-nudge.js +13 -5
  26. package/dist/core/engineer-crash-recovery.d.ts +32 -0
  27. package/dist/core/engineer-crash-recovery.js +76 -8
  28. package/dist/core/git.d.ts +2 -0
  29. package/dist/core/git.js +18 -1
  30. package/dist/core/indexer.js +15 -4
  31. package/dist/core/install-layout.d.ts +11 -0
  32. package/dist/core/install-layout.js +50 -0
  33. package/dist/core/pipeline-view.d.ts +102 -0
  34. package/dist/core/pipeline-view.js +164 -0
  35. package/dist/core/pipeline-view.test.d.ts +1 -0
  36. package/dist/core/pipeline-view.test.js +147 -0
  37. package/dist/core/schedule.d.ts +9 -0
  38. package/dist/core/schedule.js +20 -1
  39. package/dist/core/shot-fixtures.d.ts +1 -1
  40. package/dist/core/shot-fixtures.js +15 -0
  41. package/dist/core/task.d.ts +6 -2
  42. package/dist/core/task.js +23 -4
  43. package/dist/core/types.d.ts +8 -0
  44. package/dist/server/agents.d.ts +61 -1
  45. package/dist/server/agents.js +189 -12
  46. package/dist/server/cto-actions.d.ts +5 -0
  47. package/dist/server/cto-actions.js +5 -0
  48. package/dist/server/cto-monitor.d.ts +13 -0
  49. package/dist/server/cto-monitor.js +97 -34
  50. package/dist/server/cto.js +4 -2
  51. package/dist/server/decision-digest.d.ts +6 -0
  52. package/dist/server/decision-digest.js +3 -1
  53. package/dist/server/driver-brief.d.ts +12 -0
  54. package/dist/server/driver-brief.js +7 -1
  55. package/dist/server/engineer-crash-recovery.js +13 -7
  56. package/dist/server/handoff-status.d.ts +59 -0
  57. package/dist/server/handoff-status.js +226 -0
  58. package/dist/server/handoff.js +37 -6
  59. package/dist/server/release-history.d.ts +46 -0
  60. package/dist/server/release-history.js +123 -0
  61. package/dist/server/release.js +158 -13
  62. package/dist/server/routes/decisions.js +1 -0
  63. package/dist/server/routes/driver.js +1 -0
  64. package/dist/server/routes/tasks.d.ts +6 -0
  65. package/dist/server/routes/tasks.js +18 -11
  66. package/dist/server/routes/ui.js +1 -1
  67. package/dist/server/server.js +16 -2
  68. package/dist/server/task-watchdog.js +1 -0
  69. package/dist/ui/assets/{ActivityIndicator-DsxETMHH.js → ActivityIndicator-meIh-6Qi.js} +1 -1
  70. package/dist/ui/assets/{AgentModelControl-BLHKdXCK.js → AgentModelControl-CUkgcahC.js} +1 -1
  71. package/dist/ui/assets/{AgentModelModal-DEAULUdA.js → AgentModelModal-et6vEhsU.js} +1 -1
  72. package/dist/ui/assets/AgentsView-DBPauK5u.css +1 -0
  73. package/dist/ui/assets/AgentsView-DF4JpOuL.js +2 -0
  74. package/dist/ui/assets/{ChatAgentModelChip-C1B23ArJ.js → ChatAgentModelChip-BzJzDKcR.js} +1 -1
  75. package/dist/ui/assets/{ChecksView-DaRwduJB.js → ChecksView-C7N3Ypeq.js} +0 -0
  76. package/dist/ui/assets/{ConfirmDialog-Dwn2G-I8.js → ConfirmDialog-C-nDmuId.js} +1 -1
  77. package/dist/ui/assets/ContextView-BZdavSqS.js +4 -0
  78. package/dist/ui/assets/DashboardView-OFLi1McO.js +1 -0
  79. package/dist/ui/assets/{DeleteConfirmDialog-CHkMTvmt.js → DeleteConfirmDialog-CZ-PtFd3.js} +1 -1
  80. package/dist/ui/assets/{DeploymentsView-DiB0gR1F.js → DeploymentsView-BvFWskO3.js} +1 -1
  81. package/dist/ui/assets/{DevView-B-32GGYO.js → DevView-DKXpK5Qm.js} +2 -2
  82. package/dist/ui/assets/{DiffView-DOftUGZx.js → DiffView-Br-vTJ0v.js} +2 -2
  83. package/dist/ui/assets/{FailedTestsList-C_9Floyr.js → FailedTestsList-DoNaF-o9.js} +1 -1
  84. package/dist/ui/assets/InputsView-KeayUEJY.js +1 -0
  85. package/dist/ui/assets/{LoginView-CcDGWaF3.js → LoginView-CvXpXpGP.js} +1 -1
  86. package/dist/ui/assets/{PageSkeleton-DRgDMfth.js → PageSkeleton-DJRKj5JK.js} +1 -1
  87. package/dist/ui/assets/{ReleasesView-Sxyylk3a.js → ReleasesView-9CdkP52G.js} +2 -2
  88. package/dist/ui/assets/SecretsView-BwDra-GT.js +1 -0
  89. package/dist/ui/assets/{SettingsView-T6midXQa.js → SettingsView-DNCRJx13.js} +3 -3
  90. package/dist/ui/assets/StoriesView-DEihGJ88.css +1 -0
  91. package/dist/ui/assets/StoriesView-to9fS2F0.js +1 -0
  92. package/dist/ui/assets/{VoiceDictate-D97LIVNB.js → VoiceDictate-CaVNQ8T-.js} +2 -2
  93. package/dist/ui/assets/WorkView-C7qnLKcz.css +1 -0
  94. package/dist/ui/assets/WorkView-D9GE8twS.js +2 -0
  95. package/dist/ui/assets/{abnfDiagram-VCTEODGH-B0BRuj2x.js → abnfDiagram-VCTEODGH-BI749mHF.js} +1 -1
  96. package/dist/ui/assets/{architectureDiagram-5GKGNRK7-ZfY0-Gdw.js → architectureDiagram-5GKGNRK7-CtGA7LVO.js} +1 -1
  97. package/dist/ui/assets/{blockDiagram-I7D4REHJ-C-J_DWun.js → blockDiagram-I7D4REHJ-puRAhxem.js} +1 -1
  98. package/dist/ui/assets/{button-BskHmmtR.js → button-II9J4IUe.js} +1 -1
  99. package/dist/ui/assets/{c4Diagram-7LVT6UL2-bQf4xDX5.js → c4Diagram-7LVT6UL2-C4CGYisP.js} +1 -1
  100. package/dist/ui/assets/channel-D_S1ZUW_.js +1 -0
  101. package/dist/ui/assets/check-setup-DSNmeJ0T.js +2 -0
  102. package/dist/ui/assets/{checkbox-BdCUqK2K.js → checkbox-4IcB0C9m.js} +1 -1
  103. package/dist/ui/assets/{chunk-2E4U76K2-DLFOie7L.js → chunk-2E4U76K2-DjnKVsYi.js} +1 -1
  104. package/dist/ui/assets/{chunk-4HAMMTFA-BPDy0z56.js → chunk-4HAMMTFA-CDnKKfj8.js} +1 -1
  105. package/dist/ui/assets/{chunk-75Z2AOVW-CGe3rSHt.js → chunk-75Z2AOVW-CKQ7DeM6.js} +1 -1
  106. package/dist/ui/assets/{chunk-CLGD4ZFX-Cc3Z-MGq.js → chunk-CLGD4ZFX-C4wu0WZN.js} +1 -1
  107. package/dist/ui/assets/{chunk-DU6HZSFF-DoNdcPzn.js → chunk-DU6HZSFF-DrJ5bmzy.js} +1 -1
  108. package/dist/ui/assets/{chunk-F27PBJKO-0W-eFq3z.js → chunk-F27PBJKO-CzXjmHDd.js} +1 -1
  109. package/dist/ui/assets/{chunk-GMAD6QVW-DIa91igC.js → chunk-GMAD6QVW-CcWcSwCd.js} +1 -1
  110. package/dist/ui/assets/{chunk-GVQU2GXP-B9kO2TNG.js → chunk-GVQU2GXP-BaenLrCK.js} +1 -1
  111. package/dist/ui/assets/{chunk-IMKFNOWR-BVh_kn6v.js → chunk-IMKFNOWR-JyaOH2dq.js} +1 -1
  112. package/dist/ui/assets/{chunk-L3NEJ4N5-BIAmP_VV.js → chunk-L3NEJ4N5-D-Soe8Pq.js} +1 -1
  113. package/dist/ui/assets/{chunk-OSK3NFVY-JwMwYbXa.js → chunk-OSK3NFVY-DyEqccWY.js} +1 -1
  114. package/dist/ui/assets/{chunk-P2QGCYS3-jcoeuQEp.js → chunk-P2QGCYS3-PZxLXffD.js} +1 -1
  115. package/dist/ui/assets/{chunk-POPQ4Y6H-DIWunY1p.js → chunk-POPQ4Y6H-CLOE0hWn.js} +1 -1
  116. package/dist/ui/assets/{chunk-PWAF6VOD-DK7-N58B.js → chunk-PWAF6VOD-092-eJK8.js} +1 -1
  117. package/dist/ui/assets/{chunk-SHT3W25Y-CcbXenLZ.js → chunk-SHT3W25Y-CSc5sy8R.js} +1 -1
  118. package/dist/ui/assets/{chunk-SVP7TREG-CU8QcmKz.js → chunk-SVP7TREG-BLTmX5VJ.js} +1 -1
  119. package/dist/ui/assets/{chunk-TICWLB2K-C2dfqOZm.js → chunk-TICWLB2K-SzItQUhb.js} +1 -1
  120. package/dist/ui/assets/{chunk-TLUHSLCS-D-41MDHJ.js → chunk-TLUHSLCS-DMnSH-ft.js} +2 -2
  121. package/dist/ui/assets/classDiagram-ZZMXUADV-B7QHFk-U.js +1 -0
  122. package/dist/ui/assets/classDiagram-v2-VYDZK3BY-B7QHFk-U.js +1 -0
  123. package/dist/ui/assets/{close-DyBUPuic.js → close-DcpwMOTE.js} +1 -1
  124. package/dist/ui/assets/{cynefinDiagram-5FMLGOSQ-OgOMfJ2M.js → cynefinDiagram-5FMLGOSQ-1ilx8mKu.js} +1 -1
  125. package/dist/ui/assets/{dagre-GXQ25YYZ-DbmwwCwb.js → dagre-GXQ25YYZ-C3A59Q6w.js} +1 -1
  126. package/dist/ui/assets/{diagram-S7CK7UJ4-fSJUV0Sv.js → diagram-S7CK7UJ4-B-6B9kWN.js} +1 -1
  127. package/dist/ui/assets/{diagram-UQ7AKVKN-CPLv2elu.js → diagram-UQ7AKVKN-BqxmQJR8.js} +1 -1
  128. package/dist/ui/assets/{diagram-VSXAHHWV-CsOV5L3Y.js → diagram-VSXAHHWV-Ck9wWlUm.js} +1 -1
  129. package/dist/ui/assets/{diagram-VX7I27RA-C6eTl1uM.js → diagram-VX7I27RA-BQe2g3vh.js} +1 -1
  130. package/dist/ui/assets/{diagram-Z3DM3KII-B4UZmomc.js → diagram-Z3DM3KII-DD8365dG.js} +1 -1
  131. package/dist/ui/assets/{docs-ZCGAA3kV.js → docs-itn09t2R.js} +1 -1
  132. package/dist/ui/assets/{ebnfDiagram-PWID7BFC-CSTTfh_S.js → ebnfDiagram-PWID7BFC-Bz8cpJNy.js} +1 -1
  133. package/dist/ui/assets/{erDiagram-RLTQ6QDP-BYT61Y7Q.js → erDiagram-RLTQ6QDP-Bc6u3S9N.js} +1 -1
  134. package/dist/ui/assets/flowDiagram-HODETNUW-Ba7ylFp4.js +1 -0
  135. package/dist/ui/assets/{ganttDiagram-EL5Y4UJY-DKTisdub.js → ganttDiagram-EL5Y4UJY-DVDVRjA0.js} +1 -1
  136. package/dist/ui/assets/{gitGraphDiagram-WWUBYQGX-ZkPvOAfk.js → gitGraphDiagram-WWUBYQGX-CFUXHZWf.js} +1 -1
  137. package/dist/ui/assets/index-DITWgaOj.css +1 -0
  138. package/dist/ui/assets/index-DqJOYNud.js +68 -0
  139. package/dist/ui/assets/{infoDiagram-27XIBGKW-Ck98QGBN.js → infoDiagram-27XIBGKW-C-7l9NCQ.js} +1 -1
  140. package/dist/ui/assets/{input-BxW7xGjP.js → input-DYSaV-Uv.js} +1 -1
  141. package/dist/ui/assets/{ishikawaDiagram-5VMMS53U-CqwQ5qZ8.js → ishikawaDiagram-5VMMS53U-Bjq-LdEc.js} +1 -1
  142. package/dist/ui/assets/{journeyDiagram-3NMN7TZE-C1UQN2RN.js → journeyDiagram-3NMN7TZE-o4M0lP2i.js} +1 -1
  143. package/dist/ui/assets/{kanban-definition-UXKFOSKX-9zw0uM6N.js → kanban-definition-UXKFOSKX-BYqVEzsr.js} +1 -1
  144. package/dist/ui/assets/{line-CeMjuG_7.js → line-4TL08MsA.js} +1 -1
  145. package/dist/ui/assets/{mermaid-parser.core-Bosog0P9.js → mermaid-parser.core-DrM_vPwv.js} +1 -1
  146. package/dist/ui/assets/{mermaid.core-CJqh-dHb.js → mermaid.core-Dzz_-qU1.js} +3 -3
  147. package/dist/ui/assets/{mindmap-definition-YA3MSWOX-Da8qCo0o.js → mindmap-definition-YA3MSWOX-CaKNZzR5.js} +1 -1
  148. package/dist/ui/assets/{pegDiagram-XKGWAZYB-DELDftZr.js → pegDiagram-XKGWAZYB-DbKJ-jrI.js} +1 -1
  149. package/dist/ui/assets/{pieDiagram-E7YTZNPT-Da3NN6-R.js → pieDiagram-E7YTZNPT-CPiwJysC.js} +1 -1
  150. package/dist/ui/assets/{quadrantDiagram-AXDQQJYC-B51kliky.js → quadrantDiagram-AXDQQJYC-q32X56Gx.js} +1 -1
  151. package/dist/ui/assets/{railroadDiagram-O6MQD6OU-BgsVU5EU.js → railroadDiagram-O6MQD6OU-DNMhXeke.js} +1 -1
  152. package/dist/ui/assets/repo-BuEVC7d2.js +59 -0
  153. package/dist/ui/assets/{requirementDiagram-BXWQKSXE-D1AT3iV0.js → requirementDiagram-BXWQKSXE-BW09ERRd.js} +1 -1
  154. package/dist/ui/assets/{sankeyDiagram-P5KCCOFB-BDK2LYvj.js → sankeyDiagram-P5KCCOFB-BM5y8xIj.js} +1 -1
  155. package/dist/ui/assets/{sequenceDiagram-WJ2MYXX4-CNiCcLn-.js → sequenceDiagram-WJ2MYXX4-QCl8JI9d.js} +1 -1
  156. package/dist/ui/assets/{stateDiagram-D77RDMKH-Cf-064cv.js → stateDiagram-D77RDMKH-CURAhx8F.js} +1 -1
  157. package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-CnyC3tkF.js +1 -0
  158. package/dist/ui/assets/{swimlanes-42K2YHIH-D-tbu4wq.js → swimlanes-42K2YHIH-CxCV3G9b.js} +1 -1
  159. package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-R6ULRXhK.js +8 -0
  160. package/dist/ui/assets/{switch-QAs7KZBM.js → switch-fubkLBfv.js} +1 -1
  161. package/dist/ui/assets/{timeline-definition-24CTP7MA-DRufowda.js → timeline-definition-24CTP7MA-_hEyz10W.js} +1 -1
  162. package/dist/ui/assets/{title-Ct1YpRGS.js → title-DrjQrrPh.js} +1 -1
  163. package/dist/ui/assets/{value-CnDjI3IE.js → value-UmIPBXR_.js} +1 -1
  164. package/dist/ui/assets/vendor-ui-BIC6pWFX.js +3 -0
  165. package/dist/ui/assets/{vennDiagram-4TSXK5OY-D1vf8POQ.js → vennDiagram-4TSXK5OY-CdB16vQ8.js} +1 -1
  166. package/dist/ui/assets/{viewport-BF2CEetx.js → viewport-vp1hmik7.js} +1 -1
  167. package/dist/ui/assets/{wardleyDiagram-VM6X3IG4-B4fB0w_8.js → wardleyDiagram-VM6X3IG4-Bl7UPuNA.js} +1 -1
  168. package/dist/ui/assets/{xychartDiagram-S5SC5T6Z-CpSh2ftP.js → xychartDiagram-S5SC5T6Z-BN1ho0Vx.js} +1 -1
  169. package/dist/ui/index.html +19 -19
  170. package/dist/ui/sw.js +2 -2
  171. package/package.json +3 -3
  172. package/dist/ui/assets/AgentsView-BWKykKEA.js +0 -2
  173. package/dist/ui/assets/AgentsView-DEIyf0uD.css +0 -1
  174. package/dist/ui/assets/ContextView-DQ6RW6w0.js +0 -4
  175. package/dist/ui/assets/DashboardView-B-7-dQLU.js +0 -1
  176. package/dist/ui/assets/InputsView-D4WtnYSg.js +0 -1
  177. package/dist/ui/assets/SecretsView-C99csAEO.js +0 -1
  178. package/dist/ui/assets/StoriesView-CRo6XH_e.css +0 -1
  179. package/dist/ui/assets/StoriesView-CpLqmKTx.js +0 -1
  180. package/dist/ui/assets/WorkView-C9Fd-nys.js +0 -2
  181. package/dist/ui/assets/WorkView-couHda3J.css +0 -1
  182. package/dist/ui/assets/channel-6uQ09cde.js +0 -1
  183. package/dist/ui/assets/check-progress-p3dLHxxW.js +0 -2
  184. package/dist/ui/assets/classDiagram-ZZMXUADV-CG7r0YBU.js +0 -1
  185. package/dist/ui/assets/classDiagram-v2-VYDZK3BY-CG7r0YBU.js +0 -1
  186. package/dist/ui/assets/flowDiagram-HODETNUW-DzLUu-VJ.js +0 -1
  187. package/dist/ui/assets/index-BUmR8syJ.css +0 -1
  188. package/dist/ui/assets/index-hshjr8FP.js +0 -68
  189. package/dist/ui/assets/repo-PZFVwA66.js +0 -54
  190. package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-CrFLdWWs.js +0 -1
  191. package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-D6iLKbDv.js +0 -8
  192. package/dist/ui/assets/vendor-ui-BJqOnU3b.js +0 -3
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # RepoOS
2
2
 
3
- **The repo is the operating system.**
3
+ **The repo is the operating system. Your AI dev team, living in your repo, for CTOs and builders.**
4
4
 
5
5
  RepoOS is a lightweight, repo-native way to track work that lives as markdown
6
- files inside your monorepo. Tasks, specs, bugs, and feature requests are plain
6
+ files inside your repo. Tasks, specs, bugs, and feature requests are plain
7
7
  `.md` files with YAML frontmatter — versioned in git, readable by humans, and
8
8
  designed to be consumed directly by AI coding agents.
9
9
 
@@ -50,7 +50,8 @@ curl -fsSL https://repoos.org/install | bash
50
50
  ```
51
51
 
52
52
  It runs on Bun when available (recommended), otherwise requires Node.js >=
53
- 20.6.0. It installs to `~/.repoos` and links `repoos` into `~/.local/bin`.
53
+ 20.6.0. It installs to `~/.local/share/repoos` and links `repoos` into
54
+ `~/.local/bin`. Machine state stays in `~/.repoos`.
54
55
 
55
56
  ### Homebrew
56
57
 
@@ -76,22 +77,18 @@ bun add -g @repo-os/repoos
76
77
  pnpm add -g @repo-os/repoos
77
78
  ```
78
79
 
79
- ### mise
80
+ mise is not a recommended install method. See `docs/install-layout.md`.
80
81
 
81
- ```bash
82
- mise use --global npm:@repo-os/repoos
83
- ```
84
-
85
- This uses mise's built-in npm backend and records RepoOS in your global mise
86
- configuration. Make sure [mise shell activation](https://mise.jdx.dev/getting-started.html)
87
- is enabled so its `repoos` shim is on your PATH.
82
+ Bun installs `bun` into `~/.bun/bin`; add that directory to `PATH` before
83
+ `bun add -g`. pnpm on a clean account needs `SHELL` set and `pnpm setup`
84
+ before a global install.
88
85
 
89
86
  Use the same tool to update a package-manager install: `npm update -g
90
87
  @repo-os/repoos`, `bun update -g @repo-os/repoos`, `pnpm update -g
91
- @repo-os/repoos`, `mise upgrade npm:@repo-os/repoos`, or `brew update && brew
92
- upgrade repo-os/tap/repoos`. A standalone install updates with `repoos upgrade`
93
- and removes with `repoos uninstall`. When run from a recognised package-manager
94
- install, `repoos upgrade` prints the matching package-manager command instead.
88
+ @repo-os/repoos`, or `brew update && brew upgrade repo-os/tap/repoos`. A
89
+ standalone install updates with `repoos upgrade` and removes with
90
+ `repoos uninstall`. When run from a recognised package-manager install,
91
+ `repoos upgrade` prints the matching package-manager command instead.
95
92
 
96
93
  Then in any repo:
97
94
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "hash": "bd7090b8a9f41e6d39058987c990b663fd2d8e51ec58782d6dafbb41fd7e540a",
3
- "version": "0.5.75",
2
+ "hash": "ee6f28bc85995205fba9c8e23d6fc9802ccba3ff85045ffd94eaba4a635c603f",
3
+ "version": "0.5.76",
4
4
  "devUi": false
5
5
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "generatedAt": "2026-11-04T03:49:23.103Z"
2
+ "generatedAt": "2026-11-05T04:53:41.853Z"
3
3
  }
@@ -18,6 +18,8 @@ const CONTROL_API_MATCHERS = [
18
18
  re("POST", /^\/api\/tasks\/([^/]+)\/needs-input\/dismiss$/.source),
19
19
  re("POST", /^\/api\/tasks\/([^/]+)\/review\/again$/.source),
20
20
  re("GET", /^\/api\/tasks\/([^/]+)\/landable$/.source),
21
+ // Non-blocking handoff outcome for `repoos handoff-status` (#1028).
22
+ re("GET", /^\/api\/tasks\/([^/]+)\/handoff-status$/.source),
21
23
  re("POST", /^\/api\/tasks\/([^/]+)\/preview$/.source),
22
24
  re("POST", /^\/api\/tasks\/([^/]+)\/preview\/stop$/.source),
23
25
  // `repoos shot` single-entry capture through the browser-capture policy (#0874).
package/dist/cli/help.js CHANGED
@@ -160,6 +160,12 @@ const GROUPS = [
160
160
  desc: "Read-only checks before merging (verdict, lock, worktree, merge, scope, main) — exits non-zero when the task is not ready to merge (GET /api/tasks/:id/landable)",
161
161
  flags: "--json · --port",
162
162
  },
163
+ {
164
+ name: "handoff-status",
165
+ usage: "handoff-status <id> [--json] [--port N]",
166
+ desc: "Non-blocking state of the latest handoff attempt: not requested, running, passed, failed or deferred (GET /api/tasks/:id/handoff-status)",
167
+ flags: "--json · --port",
168
+ },
163
169
  {
164
170
  name: "override",
165
171
  usage: "override <id> [--cli <name>] [--model <model>] [--json] [--port N]",
package/dist/cli/index.js CHANGED
@@ -27,7 +27,7 @@ import { cmdCertify } from "../commands/certify.js";
27
27
  import { cmdSupport } from "../commands/support.js";
28
28
  import { cmdService } from "../commands/service.js";
29
29
  import { cmdServers } from "../commands/servers.js";
30
- import { cmdAgentsRunning, cmdConfig, cmdDone, cmdLandable, cmdMessage, cmdSendBack, cmdOverride, cmdPause, cmdPreview, cmdDev, cmdReview, cmdVerify, cmdRunners, cmdStart, cmdSync, cmdStats, cmdDecisions, cmdApi, cmdIntegration, cmdAttention, cmdHold, cmdUnhold, } from "../commands/control-api.js";
30
+ import { cmdAgentsRunning, cmdConfig, cmdDone, cmdLandable, cmdHandoffStatus, cmdMessage, cmdSendBack, cmdOverride, cmdPause, cmdPreview, cmdDev, cmdReview, cmdVerify, cmdRunners, cmdStart, cmdSync, cmdStats, cmdDecisions, cmdApi, cmdIntegration, cmdAttention, cmdHold, cmdUnhold, } from "../commands/control-api.js";
31
31
  import { cmdWatch } from "../commands/watch.js";
32
32
  import { cmdDriver } from "../commands/driver.js";
33
33
  import { cmdBundle } from "../commands/bundle.js";
@@ -327,6 +327,12 @@ function main() {
327
327
  process.exitCode = code;
328
328
  });
329
329
  break;
330
+ case "handoff-status":
331
+ void cmdHandoffStatus(rest).then((code) => {
332
+ if (code !== 0)
333
+ process.exitCode = code;
334
+ });
335
+ break;
330
336
  case "override":
331
337
  void cmdOverride(rest).then((code) => {
332
338
  if (code !== 0)
@@ -83,6 +83,12 @@ interface StatsGitJobView {
83
83
  ageMs?: number;
84
84
  }
85
85
  export declare function printSystemSummary(system: StatsSystemView): void;
86
+ /**
87
+ * `repoos handoff-status <id>` (#1028): print the latest handoff attempt's
88
+ * state immediately. Never waits for, starts, or alters a handoff — safe to
89
+ * call from an engineer that is unsure whether its request reached the server.
90
+ */
91
+ export declare function cmdHandoffStatus(args: string[]): Promise<number>;
86
92
  /**
87
93
  * `repoos landable <id>` (#0867): print the read-only landing pre-checks and
88
94
  * exit 0 only when every check passes. Never edits, commits or moves status.
@@ -179,9 +179,9 @@ export async function cmdHold(args) {
179
179
  return 1;
180
180
  }
181
181
  const api = new RepoOsApi({ port: opts.port });
182
- // #0960 input validation (mirrors the API guard): a hold needs a reason so it
183
- // is visible on the board, and `--until` must be a real date or it would
184
- // never auto-release and become a permanent hold.
182
+ // #0960: the CLI still requires `--reason`. The HTTP hold route accepts a
183
+ // missing reason (#1033). `--until` must be a real date or it would never
184
+ // auto-release and become a permanent hold.
185
185
  if (!opts.reason || !opts.reason.trim()) {
186
186
  printErr("hold requires a --reason (free text documenting why the task is parked)");
187
187
  return 1;
@@ -1506,6 +1506,56 @@ function landableIcon(status) {
1506
1506
  return c.yellow("!");
1507
1507
  return c.red("✗");
1508
1508
  }
1509
+ /**
1510
+ * `repoos handoff-status <id>` (#1028): print the latest handoff attempt's
1511
+ * state immediately. Never waits for, starts, or alters a handoff — safe to
1512
+ * call from an engineer that is unsure whether its request reached the server.
1513
+ */
1514
+ export async function cmdHandoffStatus(args) {
1515
+ const { rest, opts, error } = parseCommonFlags(args);
1516
+ if (error) {
1517
+ printErr(error);
1518
+ return 1;
1519
+ }
1520
+ const id = rest[0];
1521
+ if (!id) {
1522
+ printErr("Usage: repoos handoff-status <id> [--json] [--port N]");
1523
+ return 1;
1524
+ }
1525
+ const api = new RepoOsApi({ port: opts.port });
1526
+ try {
1527
+ await ensureServer(api);
1528
+ const status = (await api.requestOk("GET", `/api/tasks/${id}/handoff-status`));
1529
+ if (opts.json) {
1530
+ printJson(status);
1531
+ return 0;
1532
+ }
1533
+ const label = status.state === "passed"
1534
+ ? c.green("passed")
1535
+ : status.state === "failed"
1536
+ ? c.red("failed")
1537
+ : status.state === "running"
1538
+ ? c.yellow("running")
1539
+ : status.state === "deferred"
1540
+ ? c.yellow("deferred")
1541
+ : c.dim("not requested");
1542
+ console.log(` ${c.bold(`#${id}`)} handoff: ${label}`);
1543
+ if (status.step)
1544
+ console.log(` step: ${status.step}`);
1545
+ if (status.reason)
1546
+ console.log(` reason: ${status.reason}`);
1547
+ if (status.updatedAt)
1548
+ console.log(` at: ${status.updatedAt}`);
1549
+ if (status.state === "not_requested") {
1550
+ console.log(c.dim(` no handoff has been requested for this task (request it with \`repoos mv ${id} review\`)`));
1551
+ }
1552
+ return 0;
1553
+ }
1554
+ catch (e) {
1555
+ printErr(e.message);
1556
+ return 1;
1557
+ }
1558
+ }
1509
1559
  /**
1510
1560
  * `repoos landable <id>` (#0867): print the read-only landing pre-checks and
1511
1561
  * exit 0 only when every check passes. Never edits, commits or moves status.
@@ -22,7 +22,7 @@ import { deriveServePort, findRepoRoot, loadConfig } from "../core/config.js";
22
22
  import { CHECK_PLAN_PROPOSAL_FILE, proposeCheckPlan } from "../core/check-plan-proposal.js";
23
23
  import { formatPlanToml } from "../core/check-plan.js";
24
24
  import { importDocsInto, scaffoldDocsInto } from "./docs-io.js";
25
- import { gitAvailable, gitCommitAll, gitConfig, gitInit, isGitRepo } from "../core/git.js";
25
+ import { gitAvailable, gitCommitAll, gitIdentityConfigured, gitInit, isGitRepo, } from "../core/git.js";
26
26
  import { c } from "../cli/colors.js";
27
27
  import { cmdServe } from "./serve.js";
28
28
  import { detectAgents } from "../core/detect.js";
@@ -178,11 +178,11 @@ repo itself is the source of truth. This file tells AI agents how to operate.
178
178
 
179
179
  New to this repo? The board is \`${workDir}/\` — one markdown file per task.
180
180
  Create work with \`repoos new "<title>"\`, move it with \`repoos mv <id> <status>\`,
181
- and **never** edit \`${workDir}/*.md\` by hand (use the CLI or the HTTP API).
181
+ and **never** edit task files under \`${workDir}/\` by hand (use the CLI or the HTTP API).
182
182
  Project context lives under \`${docsDir}/\`; start there. Run \`repoos serve\` in a
183
183
  terminal you keep open, or install \`repoos service\` for an unattended server —
184
184
  a detached \`repoos serve\` started from an agent shell is reaped as a stray orphan
185
- (see \`docs/agent-process-safety.md\`). RepoOS runs each managed agent turn in its
185
+ (see https://docs.repoos.org/running-with-agents). RepoOS runs each managed agent turn in its
186
186
  own process group and tears that group down when the turn ends; never use \`pkill\`
187
187
  or \`killall\` to clean up helpers. When a task is done, run \`repoos check\` and
188
188
  hand off with \`repoos mv <id> review\`; see the operating loop below.
@@ -192,8 +192,8 @@ hand off with \`repoos mv <id> review\`; see the operating loop below.
192
192
  For a RepoOS-managed task runner:
193
193
 
194
194
  1. Read this file, your assigned task under \`${workDir}/\`, and relevant
195
- project docs under \`${docsDir}/\` — start from the index at
196
- \`${docsDir}/README.md\`, which lists the reading order. Before reading a
195
+ project docs under \`${docsDir}/\`. Maintain ${docsDir}/README.md as the
196
+ reading-order index as the doc set grows. Before reading a
197
197
  large file, run \`repoos outline <path>\` to get its symbols with line
198
198
  numbers, then read only the range you need instead of the whole file.
199
199
  2. Work in the task branch and dedicated worktree RepoOS assigned. The server
@@ -225,7 +225,7 @@ unless the human explicitly requests that separate workflow.
225
225
 
226
226
  The reviewer agent, when enabled, writes an advisory report in the task drawer.
227
227
  It does not edit code or replace human approval. After the human approves,
228
- use **Move to done** in RepoOS or \`POST /api/tasks/:id/done\`. The server
228
+ use **Move to done** in RepoOS or POST /api/tasks/:id/done. The server
229
229
  merges the task into a separate candidate worktree, validates the combined
230
230
  result, rechecks the primary branch under a publication lock, publishes,
231
231
  cleans up, and records completion. \`repoos mv <id> done\` only changes metadata;
@@ -233,7 +233,7 @@ it does not run close-out or merge code.
233
233
 
234
234
  A direct commit on the primary branch is an exception requiring explicit
235
235
  human authorization. If that already-landed work has a branchless task record,
236
- the \`/done\` endpoint can check the primary checkout and record release without
236
+ the /done endpoint can check the primary checkout and record release without
237
237
  a candidate merge. Do not erase a task's branch metadata to force this path.
238
238
 
239
239
  ## Rules
@@ -248,7 +248,7 @@ a candidate merge. Do not erase a task's branch metadata to force this path.
248
248
  invent ownership rules for RepoOS directories; the config is the only source
249
249
  of truth for layout.
250
250
  - **Never** move task files between folders. Status lives in frontmatter.
251
- - **Never write directly to \`${workDir}/*.md\` files.** All task creation and
251
+ - **Never write directly to task files under \`${workDir}/\`.** All task creation and
252
252
  manipulation goes through \`repoos\` commands or HTTP API endpoints
253
253
  (\`POST /api/tasks\`, \`PATCH /api/tasks/:id\`, etc.). If the RepoOS server
254
254
  is unreachable, stop and report the issue — do not hand-write task files.
@@ -306,7 +306,7 @@ active/review.
306
306
  ## Declaring evidence shots (## Shots)
307
307
 
308
308
  A task body may carry a \`## Shots\` section with a fenced JSON list of capture
309
- entries (see \`src/core/shot-plan.ts\`). Each entry has \`target\`, \`route\`, an
309
+ entries (see https://docs.repoos.org/cli#the-tasks-shots-list). Each entry has \`target\`, \`route\`, an
310
310
  optional \`selector\` (element crop — the exception), an optional \`highlight\`
311
311
  CSS selector (outlines changed elements), a \`label\`, and optional ordered
312
312
  \`steps\` (click/fill/wait, plain CSS selectors). Use \`repoos update <id>
@@ -315,9 +315,9 @@ fenced section) without touching the rest of the body — a full \`--body\` repl
315
315
  Problem / Desired UX / Acceptance criteria / Notes for AI is refused unless
316
316
  \`--force\` is passed.
317
317
 
318
- For tabbed views (e.g. Agents), prefer \`?tab=<id>\` routes
319
- (\`/agents?tab=detected\`) over click steps to reach the right tab; a task drawer
320
- is reached the same way (\`/work?task=<id>&tab=changes\`), and always
318
+ For tabbed views (e.g. Agents), prefer ?tab=<id> routes
319
+ (/agents?tab=detected) over click steps to reach the right tab; a task drawer
320
+ is reached the same way (/work?task=<id>&tab=changes), and always
321
321
  set \`highlight\` to the changed elements. Reach a populated state with a
322
322
  \`route\` or a named \`state\` fixture, never with \`steps\` that click a
323
323
  placeholder selector (\`data-task-id "9999"\` and the like) — such a step times
@@ -1724,7 +1724,7 @@ async function guidedNewRepo(opts) {
1724
1724
  if (!gitOk) {
1725
1725
  console.log(c.yellow("\n Warning: git doesn't appear to be installed — scaffolding without git."));
1726
1726
  }
1727
- else if (!gitConfig(target, "user.name") && !gitConfig(target, "user.email")) {
1727
+ else if (!gitIdentityConfigured(target)) {
1728
1728
  console.log(c.yellow("\n Warning: no git identity is configured."));
1729
1729
  console.log(c.dim(" git may auto-detect it, or the initial commit may fail. Set it with:"));
1730
1730
  console.log(c.dim(' git config --global user.name "You" && git config --global user.email you@example.com'));
@@ -10,7 +10,7 @@ import { commitTaskFile } from "../core/git.js";
10
10
  import { c } from "../cli/colors.js";
11
11
  import { createRepoOS } from "../core/repoos.js";
12
12
  import { computeNextRun, formatNextRunPreview, } from "../core/schedule-time.js";
13
- import { createSchedule, getSchedule, listSchedules, updateSchedule, validateScheduleInput, } from "../core/schedule.js";
13
+ import { createSchedule, getSchedule, listSchedules, scheduleTemplateWarning, updateSchedule, validateScheduleInput, } from "../core/schedule.js";
14
14
  import { executeScheduleRun, findDueSchedule, releaseScheduleOccurrenceLock, tryAcquireScheduleOccurrenceLock, } from "../server/schedule-runner.js";
15
15
  import { scheduleHostileReason, scheduleRunRefusalMessage } from "../server/schedule-automation.js";
16
16
  import { deleteScheduleDataFile } from "../server/write.js";
@@ -132,6 +132,16 @@ function flagsToCreateInput(flags, fallback) {
132
132
  body: flags.body ?? fallback?.body,
133
133
  };
134
134
  }
135
+ function printScheduleTemplateWarning(input) {
136
+ const detail = scheduleTemplateWarning({
137
+ body: input.body ?? "",
138
+ taskArea: input.taskArea,
139
+ taskAssignedTo: input.taskAssignedTo,
140
+ });
141
+ if (detail) {
142
+ console.log(c.yellow(" warning: ") + c.dim("schedule template is underspecified — ") + detail);
143
+ }
144
+ }
135
145
  export function cmdScheduleList(args) {
136
146
  const { flags, error } = parseScheduleFlags(args);
137
147
  if (error) {
@@ -207,6 +217,7 @@ export function cmdScheduleNew(args) {
207
217
  const created = createSchedule(config, input);
208
218
  const committed = commitSchedule(config.root, created.path, created.id, "create");
209
219
  console.log(c.green(` created schedule #${created.id} ${created.title}`));
220
+ printScheduleTemplateWarning(input);
210
221
  if (!committed)
211
222
  console.log(c.yellow(" warning: ") + c.dim("file left uncommitted"));
212
223
  }
@@ -270,6 +281,9 @@ export function cmdScheduleUpdate(args) {
270
281
  const updated = updateSchedule(config, existing.id, patch);
271
282
  const committed = commitSchedule(config.root, updated.path, updated.id, "update");
272
283
  console.log(c.green(` updated schedule #${updated.id}`));
284
+ const merged = flagsToCreateInput(flags, existing);
285
+ if (merged)
286
+ printScheduleTemplateWarning(merged);
273
287
  if (!committed)
274
288
  console.log(c.yellow(" warning: ") + c.dim("file left uncommitted"));
275
289
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { renderServeBanner } from "./serve.js";
2
+ import { formatServeBindBannerValue, renderServeBanner } from "./serve.js";
3
3
  describe("renderServeBanner", () => {
4
4
  const rows = [
5
5
  { label: "Local", value: "http://127.0.0.1:7515" },
@@ -10,6 +10,29 @@ describe("renderServeBanner", () => {
10
10
  expect(out).toContain("RepoOS v1.2.3");
11
11
  expect(out).toContain("Local http://127.0.0.1:7515");
12
12
  });
13
+ it("names the bind address on localhost and tailnet rows", () => {
14
+ const localOnly = renderServeBanner("1.0.0", [
15
+ { label: "Bind", value: formatServeBindBannerValue("127.0.0.1", false) },
16
+ { label: "Local", value: "http://127.0.0.1:7515" },
17
+ ]);
18
+ expect(localOnly).toMatch(/Bind\s+127\.0\.0\.1/);
19
+ const tailscale = renderServeBanner("1.0.0", [
20
+ {
21
+ label: "Bind",
22
+ value: formatServeBindBannerValue("0.0.0.0", true),
23
+ },
24
+ { label: "Local", value: "http://127.0.0.1:7515" },
25
+ { label: "Tailnet", value: "http://100.64.0.1:7515" },
26
+ ]);
27
+ expect(tailscale).toMatch(/Bind\s+0\.0\.0\.0/);
28
+ expect(tailscale).toContain("--host 127.0.0.1");
29
+ });
30
+ it("formatServeBindBannerValue uses tailscaleDetected, not host alone", () => {
31
+ expect(formatServeBindBannerValue("0.0.0.0", false)).toContain("explicit --host");
32
+ expect(formatServeBindBannerValue("0.0.0.0", false)).not.toContain("Tailscale detected");
33
+ expect(formatServeBindBannerValue("0.0.0.0", true)).toContain("Tailscale detected");
34
+ expect(formatServeBindBannerValue("192.168.1.5", false)).toContain("192.168.1.5");
35
+ });
13
36
  it("splits an HTTPS warning into problem, fix and link", () => {
14
37
  const out = renderServeBanner("1.2.3", rows, "HTTPS certificates aren't enabled for your tailnet — turn on MagicDNS and HTTPS Certificates at https://login.tailscale.com/admin/dns");
15
38
  expect(out).toContain("Tailscale HTTPS unavailable");
@@ -40,6 +40,11 @@ export declare function resolveServeHost(explicitHost?: string): {
40
40
  tailscaleDetected: boolean;
41
41
  tailscaleIP?: string;
42
42
  };
43
+ /**
44
+ * Bind row text for the serve startup banner. Uses {@link tailscaleDetected},
45
+ * not `host === "0.0.0.0"`, so an explicit `--host 0.0.0.0` is not mislabeled.
46
+ */
47
+ export declare function formatServeBindBannerValue(host: string, tailscaleDetected: boolean): string;
43
48
  /**
44
49
  * Whether this `repoos serve` should apply the repo's preview-only
45
50
  * `[preview.*]` config overlay (#0464).
@@ -81,6 +81,22 @@ export function resolveServeHost(explicitHost) {
81
81
  return { host: "0.0.0.0", tailscaleDetected: true, tailscaleIP };
82
82
  return { host: "127.0.0.1", tailscaleDetected: false };
83
83
  }
84
+ /**
85
+ * Bind row text for the serve startup banner. Uses {@link tailscaleDetected},
86
+ * not `host === "0.0.0.0"`, so an explicit `--host 0.0.0.0` is not mislabeled.
87
+ */
88
+ export function formatServeBindBannerValue(host, tailscaleDetected) {
89
+ if (tailscaleDetected) {
90
+ return (c.cyan("0.0.0.0") +
91
+ c.dim(" (all interfaces — Tailscale detected; pass ") +
92
+ c.cyan("--host 127.0.0.1") +
93
+ c.dim(" for localhost only)"));
94
+ }
95
+ if (host === "127.0.0.1") {
96
+ return c.cyan("127.0.0.1") + c.dim(" (localhost only)");
97
+ }
98
+ return c.cyan(host) + c.dim(" (explicit --host)");
99
+ }
84
100
  /**
85
101
  * Whether this `repoos serve` should apply the repo's preview-only
86
102
  * `[preview.*]` config overlay (#0464).
@@ -225,6 +241,10 @@ export async function cmdServe(args, opts = {}) {
225
241
  ? `Bun ${process.versions.bun ?? ""}`.trim()
226
242
  : `Node ${process.versions.node}`;
227
243
  const rows = [
244
+ {
245
+ label: "Bind",
246
+ value: formatServeBindBannerValue(host, tailscaleDetected),
247
+ },
228
248
  { label: "Local", value: c.cyan(c.bold(handle.url)) },
229
249
  { label: "Events", value: c.cyan(handle.url + "/api/events") + c.dim(" (SSE)") },
230
250
  { label: "Watching", value: `${snap.taskCount} tasks` },
@@ -22,7 +22,7 @@ import { checkBuildForRoot, readBuildMeta, readBuildStamp } from "../core/build.
22
22
  import { isManagedService } from "../core/service-manager.js";
23
23
  import { readTunnelConfig } from "../core/tunnel.js";
24
24
  import { readRegistry, reconcileIdentity, syncFromRepo, unionApps, } from "../core/tunnel-registry.js";
25
- import { currentBranch, runGit } from "../core/git.js";
25
+ import { currentBranch, isGitRepo, runGit } from "../core/git.js";
26
26
  import { countWorktrees, formatGcEntryDetails, sweepStaleWorktrees } from "../core/worktree-gc.js";
27
27
  import { isOrphanServeCommand, isPortListening } from "../server/serve-reaper.js";
28
28
  import { STATUSES } from "../core/types.js";
@@ -683,7 +683,7 @@ export function renderStatus(s, now = new Date()) {
683
683
  }
684
684
  // git
685
685
  if (!s.git.branch) {
686
- row("git", c.dim("not a git repository"));
686
+ row("git", c.dim(isGitRepo(s.root) ? "unknown branch" : "not a git repository"));
687
687
  }
688
688
  else {
689
689
  const bits = [c.cyan(s.git.branch)];
@@ -105,7 +105,7 @@ async function listTasksViaApi(statusArg) {
105
105
  return { rootName: root.split("/").pop() ?? "repo", tasks };
106
106
  }
107
107
  catch (error) {
108
- console.warn(`[repoos] task API unavailable; falling back to the local index: ${error instanceof Error ? error.message : String(error)}`);
108
+ console.warn(`[repoos] Server unavailable — using local index. Start with \`repoos serve\`.`);
109
109
  return null;
110
110
  }
111
111
  }
@@ -1,6 +1,9 @@
1
1
  export interface StandaloneUninstallPlan {
2
2
  installDir: string;
3
+ /** True when the tarball was unpacked into the state directory (pre-split installs). */
4
+ legacyCombined: boolean;
3
5
  launcherPath: string | null;
6
+ stateDir: string;
4
7
  }
5
8
  /**
6
9
  * Return the files owned by a default curl install, or null for every other
@@ -1,17 +1,20 @@
1
1
  /**
2
2
  * `repoos uninstall` — remove a standalone install created by install.sh.
3
3
  *
4
- * This deliberately has a narrow scope. It never removes a source checkout or
5
- * a package-manager install, and it leaves project data and user configuration
6
- * alone. Package-manager installs must be removed by their package manager.
4
+ * Removes the install directory (`~/.local/share/repoos`, or a legacy
5
+ * `~/.repoos` payload) and the machine state entries RepoOS created there.
6
+ * Per-repo files (`repoos.toml`, a repo's `.repoos/` cache) are not touched.
7
+ * A package-manager install is not deleted; the command names the package
8
+ * manager command and any machine state that remains.
7
9
  */
8
- import { existsSync, readFileSync, rmSync } from "node:fs";
10
+ import { existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
9
11
  import { homedir } from "node:os";
10
12
  import { dirname, join } from "node:path";
11
13
  import { createInterface } from "node:readline/promises";
12
14
  import { fileURLToPath } from "node:url";
13
15
  import { c } from "../cli/colors.js";
14
- import { launcherScript } from "./upgrade.js";
16
+ import { STANDALONE_INSTALL_PAYLOAD, classifyMachineState, isStandaloneInstallDir, machineStateDir, } from "../core/install-layout.js";
17
+ import { launcherScript, packageManagerUpgrade } from "./upgrade.js";
15
18
  /**
16
19
  * Return the files owned by a default curl install, or null for every other
17
20
  * kind of execution (source checkout, npm/Bun, custom installation, etc.).
@@ -21,8 +24,8 @@ import { launcherScript } from "./upgrade.js";
21
24
  */
22
25
  export function standaloneUninstallPlan(entryPath, home = homedir(), binDir = join(home, ".local", "bin")) {
23
26
  const installDir = dirname(dirname(entryPath));
24
- const expectedInstallDir = join(home, ".repoos");
25
- if (installDir !== expectedInstallDir || !existsSync(join(installDir, "cli", "index.js"))) {
27
+ if (!isStandaloneInstallDir(installDir, home) ||
28
+ !existsSync(join(installDir, "cli", "index.js"))) {
26
29
  return null;
27
30
  }
28
31
  const launcherPath = join(binDir, "repoos");
@@ -35,12 +38,18 @@ export function standaloneUninstallPlan(entryPath, home = homedir(), binDir = jo
35
38
  catch {
36
39
  // A launcher we cannot read is not safe to remove.
37
40
  }
38
- return { installDir, launcherPath: ownsLauncher ? launcherPath : null };
41
+ const stateDir = machineStateDir(home);
42
+ return {
43
+ installDir,
44
+ legacyCombined: installDir === stateDir,
45
+ launcherPath: ownsLauncher ? launcherPath : null,
46
+ stateDir,
47
+ };
39
48
  }
40
49
  async function confirm() {
41
50
  const rl = createInterface({ input: process.stdin, output: process.stdout });
42
51
  try {
43
- const answer = (await rl.question(" Remove the standalone RepoOS install? [y/N] "))
52
+ const answer = (await rl.question(" Remove the standalone RepoOS install and its machine state? [y/N] "))
44
53
  .trim()
45
54
  .toLowerCase();
46
55
  return answer === "y" || answer === "yes";
@@ -49,27 +58,68 @@ async function confirm() {
49
58
  rl.close();
50
59
  }
51
60
  }
52
- function printPackageManagerRemoval() {
61
+ function printPackageManagerRemoval(home) {
53
62
  console.log(c.yellow(" repoos was installed via a package manager."));
54
- console.log(c.dim(" Remove it with the same package manager:"));
63
+ console.log(c.dim(" Remove the package with the same package manager:"));
55
64
  console.log(c.dim(" npm uninstall -g @repo-os/repoos"));
56
65
  console.log(c.dim(" bun remove -g @repo-os/repoos"));
57
66
  console.log(c.dim(" pnpm remove -g @repo-os/repoos"));
58
- console.log(c.dim(" mise unuse --global npm:@repo-os/repoos"));
67
+ console.log(c.dim(" brew uninstall repoos"));
68
+ console.log(c.dim(" mise is not a supported install method. If you used it anyway, remove the shim with:"));
69
+ console.log(c.dim(" mise unuse --global npm:@repo-os/repoos && mise prune"));
70
+ const stateDir = machineStateDir(home);
71
+ const { remove, keep } = classifyMachineState(stateDir);
72
+ if (remove.length === 0 && keep.length === 0 && !existsSync(join(stateDir, "cli", "index.js"))) {
73
+ console.log(c.dim(" No machine state was found in ~/.repoos."));
74
+ return;
75
+ }
76
+ console.log(c.dim(" This command did not delete machine state. Still on disk:"));
77
+ for (const name of remove)
78
+ console.log(c.dim(` ${join(stateDir, name)}`));
79
+ for (const name of keep)
80
+ console.log(c.dim(` ${join(stateDir, name)}`));
81
+ if (existsSync(join(stateDir, "cli", "index.js"))) {
82
+ console.log(c.dim(` ${join(stateDir, "cli")} (legacy install payload)`));
83
+ }
59
84
  }
60
85
  function printSourceRemoval() {
61
86
  console.log(c.yellow(" repoos appears to be running from a source checkout, not a standalone install."));
62
87
  console.log(c.dim(" There is nothing for RepoOS to uninstall. Remove the checkout only if you no longer need it."));
63
88
  }
89
+ function removeStandalone(plan) {
90
+ if (plan.launcherPath)
91
+ rmSync(plan.launcherPath, { force: true });
92
+ if (plan.legacyCombined) {
93
+ for (const name of STANDALONE_INSTALL_PAYLOAD) {
94
+ rmSync(join(plan.installDir, name), { recursive: true, force: true });
95
+ }
96
+ }
97
+ else {
98
+ rmSync(plan.installDir, { recursive: true, force: true });
99
+ for (const name of STANDALONE_INSTALL_PAYLOAD) {
100
+ rmSync(join(plan.stateDir, name), { recursive: true, force: true });
101
+ }
102
+ }
103
+ const { remove, keep } = classifyMachineState(plan.stateDir);
104
+ const removedState = [];
105
+ for (const name of remove) {
106
+ rmSync(join(plan.stateDir, name), { recursive: true, force: true });
107
+ removedState.push(name);
108
+ }
109
+ if (existsSync(plan.stateDir) && readdirSync(plan.stateDir).length === 0) {
110
+ rmSync(plan.stateDir, { recursive: true, force: true });
111
+ }
112
+ return { removedState, keptState: keep };
113
+ }
64
114
  export async function cmdUninstall(args) {
65
115
  if (args.includes("--help") || args.includes("-h")) {
66
- console.log("\n Usage: repoos uninstall [--yes]\n\n Remove the default standalone install created by install.sh.\n Project data and user configuration are left untouched.\n");
116
+ console.log("\n Usage: repoos uninstall [--yes]\n\n Remove the standalone install created by install.sh (~/.local/share/repoos,\n or a legacy ~/.repoos payload) and machine state under ~/.repoos\n (servers, services.json, local-check-slots, agent-run-tmp).\n Other files in ~/.repoos are listed and left in place.\n Per-repo files are not removed. A package-manager install is not deleted;\n the command prints that package manager's uninstall and any state left behind.\n");
67
117
  return;
68
118
  }
69
119
  const entryPath = fileURLToPath(import.meta.url);
70
120
  const root = dirname(dirname(entryPath));
71
- if (root.includes("node_modules")) {
72
- printPackageManagerRemoval();
121
+ if (packageManagerUpgrade(root)) {
122
+ printPackageManagerRemoval(homedir());
73
123
  return;
74
124
  }
75
125
  const plan = standaloneUninstallPlan(entryPath);
@@ -86,26 +136,39 @@ export async function cmdUninstall(args) {
86
136
  return;
87
137
  }
88
138
  if (!(await confirm())) {
89
- console.log(c.dim(" Kept the standalone RepoOS install."));
139
+ console.log(c.dim(" Kept the standalone RepoOS install and machine state."));
90
140
  return;
91
141
  }
92
142
  }
143
+ let removed;
93
144
  try {
94
- if (plan.launcherPath)
95
- rmSync(plan.launcherPath, { force: true });
96
- rmSync(plan.installDir, { recursive: true, force: true });
145
+ removed = removeStandalone(plan);
97
146
  }
98
147
  catch (e) {
99
148
  console.error(c.red(" Failed to remove the standalone install: " + e.message));
100
149
  process.exitCode = 1;
101
150
  return;
102
151
  }
103
- console.log(c.green(" Removed the standalone RepoOS install."));
152
+ console.log(c.green(` Removed the standalone RepoOS install at ${plan.installDir}.`));
104
153
  if (plan.launcherPath) {
105
154
  console.log(c.dim(` Removed launcher: ${plan.launcherPath}`));
106
155
  }
107
156
  else {
108
157
  console.log(c.dim(" Kept ~/.local/bin/repoos because it is not the launcher this install created."));
109
158
  }
110
- console.log(c.dim(" Your RepoOS projects and user configuration were not changed."));
159
+ if (removed.removedState.length > 0) {
160
+ console.log(c.dim(" Removed machine state:"));
161
+ for (const name of removed.removedState) {
162
+ console.log(c.dim(` ${join(plan.stateDir, name)}`));
163
+ }
164
+ }
165
+ else {
166
+ console.log(c.dim(" No RepoOS machine state entries were present under ~/.repoos."));
167
+ }
168
+ if (removed.keptState.length > 0) {
169
+ console.log(c.dim(" Left these entries in ~/.repoos (not created by RepoOS):"));
170
+ for (const name of removed.keptState)
171
+ console.log(c.dim(` ${join(plan.stateDir, name)}`));
172
+ }
173
+ console.log(c.dim(" Per-repo files were not removed (repoos.toml and each repo's .repoos/ directory)."));
111
174
  }