rovecode 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +737 -0
  3. package/THIRD_PARTY_NOTICES.md +268 -0
  4. package/bin/rovecode.ts +21 -0
  5. package/package.json +56 -0
  6. package/src/acp/server.ts +374 -0
  7. package/src/cli/auth-login.ts +122 -0
  8. package/src/cli/connect.ts +244 -0
  9. package/src/cli/context-cmd.ts +199 -0
  10. package/src/cli/dispatch.ts +82 -0
  11. package/src/cli/doctor.ts +362 -0
  12. package/src/cli/export.ts +276 -0
  13. package/src/cli/help.ts +293 -0
  14. package/src/cli/is-tui-invocation.ts +8 -0
  15. package/src/cli/main.ts +583 -0
  16. package/src/cli/market-cmd.ts +658 -0
  17. package/src/cli/mcp-login.ts +141 -0
  18. package/src/cli/mcp-market-cmd.ts +302 -0
  19. package/src/cli/output.ts +382 -0
  20. package/src/cli/repl.ts +250 -0
  21. package/src/cli/repomap-root.ts +14 -0
  22. package/src/cli/resume.ts +57 -0
  23. package/src/cli/run-flags.ts +43 -0
  24. package/src/cli/run-limits.ts +78 -0
  25. package/src/cli/runtime.ts +931 -0
  26. package/src/cli/session-arg.ts +30 -0
  27. package/src/cli/sessions-cmd.ts +145 -0
  28. package/src/cli/setup.ts +153 -0
  29. package/src/cli/skills-cmd.ts +194 -0
  30. package/src/cli/start-chat.ts +65 -0
  31. package/src/cli/trust-cmd.ts +52 -0
  32. package/src/coding/bash.ts +148 -0
  33. package/src/coding/checkpoints.ts +327 -0
  34. package/src/coding/diff.ts +138 -0
  35. package/src/coding/files.ts +341 -0
  36. package/src/coding/hashline.ts +274 -0
  37. package/src/coding/lsp-gate.ts +254 -0
  38. package/src/coding/lsp-servers.ts +147 -0
  39. package/src/coding/lsp.ts +283 -0
  40. package/src/coding/repomap-cache.ts +99 -0
  41. package/src/coding/repomap-files.ts +192 -0
  42. package/src/coding/repomap.ts +481 -0
  43. package/src/core/agents.ts +255 -0
  44. package/src/core/compaction.ts +259 -0
  45. package/src/core/config.ts +289 -0
  46. package/src/core/context-report.ts +228 -0
  47. package/src/core/context.ts +60 -0
  48. package/src/core/count-remote.ts +107 -0
  49. package/src/core/execpolicy-rules.ts +196 -0
  50. package/src/core/execpolicy.ts +385 -0
  51. package/src/core/executor.ts +454 -0
  52. package/src/core/guardrails.ts +400 -0
  53. package/src/core/hooks.ts +411 -0
  54. package/src/core/images.ts +230 -0
  55. package/src/core/intro.ts +266 -0
  56. package/src/core/loop.ts +567 -0
  57. package/src/core/modes.ts +372 -0
  58. package/src/core/orchestrator.ts +245 -0
  59. package/src/core/proc-group.ts +48 -0
  60. package/src/core/project-trust.ts +98 -0
  61. package/src/core/reflection.ts +165 -0
  62. package/src/core/sandbox-config.ts +186 -0
  63. package/src/core/session-id.ts +24 -0
  64. package/src/core/session-images.ts +73 -0
  65. package/src/core/session-ops.ts +183 -0
  66. package/src/core/session-text.ts +29 -0
  67. package/src/core/session.ts +469 -0
  68. package/src/core/settings.ts +170 -0
  69. package/src/core/tasks.ts +646 -0
  70. package/src/core/token-scale.ts +108 -0
  71. package/src/core/tools.ts +309 -0
  72. package/src/core/trust.ts +104 -0
  73. package/src/core/types.ts +330 -0
  74. package/src/core/update-check.ts +171 -0
  75. package/src/core/usage.ts +204 -0
  76. package/src/core/validate.ts +121 -0
  77. package/src/core/verify-gate.ts +159 -0
  78. package/src/core/verify.ts +236 -0
  79. package/src/core/voice.ts +158 -0
  80. package/src/core/win-job.ts +183 -0
  81. package/src/core/workspace.ts +184 -0
  82. package/src/design/audit.ts +797 -0
  83. package/src/design/direction.ts +190 -0
  84. package/src/design/rules.ts +157 -0
  85. package/src/eval/bench.ts +150 -0
  86. package/src/eval/gauntlet-runner.ts +215 -0
  87. package/src/eval/gauntlet-support.ts +84 -0
  88. package/src/eval/gauntlet-wave3.ts +269 -0
  89. package/src/eval/gauntlet-wave4.ts +217 -0
  90. package/src/eval/gauntlet.ts +253 -0
  91. package/src/index.ts +17 -0
  92. package/src/lanes/agy.ts +95 -0
  93. package/src/lanes/approval.ts +24 -0
  94. package/src/lanes/claude.ts +129 -0
  95. package/src/lanes/codex.ts +127 -0
  96. package/src/lanes/events.ts +130 -0
  97. package/src/lanes/job.ts +142 -0
  98. package/src/lanes/opencode.ts +122 -0
  99. package/src/lanes/process.ts +184 -0
  100. package/src/lanes/progress.ts +183 -0
  101. package/src/lanes/registry.ts +178 -0
  102. package/src/lanes/runner.ts +124 -0
  103. package/src/lanes/types.ts +112 -0
  104. package/src/market/catalogs/mcp-docs.json +111 -0
  105. package/src/market/catalogs/plugins.json +111 -0
  106. package/src/market/catalogs/skills.json +478 -0
  107. package/src/market/clone.ts +72 -0
  108. package/src/market/context-cost.ts +121 -0
  109. package/src/market/digest.ts +106 -0
  110. package/src/market/index.ts +22 -0
  111. package/src/market/install.ts +578 -0
  112. package/src/market/manifest.ts +187 -0
  113. package/src/market/prereq.ts +145 -0
  114. package/src/market/registry.ts +363 -0
  115. package/src/market/resolve.ts +111 -0
  116. package/src/market/types.ts +236 -0
  117. package/src/market/validate.ts +227 -0
  118. package/src/mcp/client.ts +449 -0
  119. package/src/mcp/config.ts +252 -0
  120. package/src/mcp/local-package.ts +211 -0
  121. package/src/mcp/market-catalog.ts +84 -0
  122. package/src/mcp/market-install.ts +289 -0
  123. package/src/mcp/market.ts +362 -0
  124. package/src/mcp/oauth.ts +251 -0
  125. package/src/mcp/prompts-resources.ts +249 -0
  126. package/src/mcp/shared.ts +149 -0
  127. package/src/mcp/status.ts +67 -0
  128. package/src/mcp/tools.ts +275 -0
  129. package/src/mcp/transport.ts +122 -0
  130. package/src/mcp/trust.ts +25 -0
  131. package/src/memory/blocks.ts +278 -0
  132. package/src/memory/recall.ts +355 -0
  133. package/src/memory/scope.ts +182 -0
  134. package/src/memory/store.ts +105 -0
  135. package/src/memory/tools.ts +99 -0
  136. package/src/plugins/cli.ts +119 -0
  137. package/src/plugins/discover.ts +108 -0
  138. package/src/plugins/index.ts +50 -0
  139. package/src/plugins/install.ts +184 -0
  140. package/src/plugins/load.ts +124 -0
  141. package/src/plugins/manifest.ts +92 -0
  142. package/src/plugins/state.ts +83 -0
  143. package/src/providers/auth.ts +408 -0
  144. package/src/providers/cache.ts +223 -0
  145. package/src/providers/catalog-local.ts +160 -0
  146. package/src/providers/catalog.ts +421 -0
  147. package/src/providers/middleware-context.ts +86 -0
  148. package/src/providers/middleware.ts +373 -0
  149. package/src/providers/model-list.ts +23 -0
  150. package/src/providers/models-index.json +1 -0
  151. package/src/providers/oauth/common.ts +105 -0
  152. package/src/providers/oauth/device-code.ts +107 -0
  153. package/src/providers/oauth/github-copilot.ts +146 -0
  154. package/src/providers/oauth/loopback.ts +158 -0
  155. package/src/providers/oauth/openai.ts +163 -0
  156. package/src/providers/oauth/openrouter.ts +89 -0
  157. package/src/providers/oauth/pkce.ts +45 -0
  158. package/src/providers/oauth/registry.ts +39 -0
  159. package/src/providers/oauth/seam.ts +89 -0
  160. package/src/providers/profile-glm53.ts +111 -0
  161. package/src/providers/profile-sonnet5-persona.ts +65 -0
  162. package/src/providers/profile-sonnet5-voice.ts +23 -0
  163. package/src/providers/profiles.ts +156 -0
  164. package/src/providers/provider-config.ts +311 -0
  165. package/src/providers/registry.ts +333 -0
  166. package/src/providers/responses.ts +209 -0
  167. package/src/providers/retry.ts +234 -0
  168. package/src/providers/router.ts +294 -0
  169. package/src/providers/sse.ts +26 -0
  170. package/src/providers/stream-errors.ts +117 -0
  171. package/src/providers/stream.ts +566 -0
  172. package/src/providers/thinking.ts +189 -0
  173. package/src/providers/wire-messages.ts +129 -0
  174. package/src/providers/wire-responses.ts +79 -0
  175. package/src/providers/wire-select.ts +53 -0
  176. package/src/server/http.ts +291 -0
  177. package/src/server/openapi.ts +246 -0
  178. package/src/sextant/card-hits.ts +102 -0
  179. package/src/sextant/card-keys.ts +55 -0
  180. package/src/sextant/context-source.ts +157 -0
  181. package/src/sextant/crew-cards.ts +350 -0
  182. package/src/sextant/draw-agents.ts +273 -0
  183. package/src/sextant/draw-code.ts +388 -0
  184. package/src/sextant/draw-context.ts +222 -0
  185. package/src/sextant/draw-frame.ts +164 -0
  186. package/src/sextant/draw-market.ts +573 -0
  187. package/src/sextant/draw-messages.ts +386 -0
  188. package/src/sextant/draw-pet.ts +230 -0
  189. package/src/sextant/draw-plan.ts +187 -0
  190. package/src/sextant/draw-tabs.ts +85 -0
  191. package/src/sextant/draw-util.ts +65 -0
  192. package/src/sextant/draw-wizard.ts +378 -0
  193. package/src/sextant/engine.ts +230 -0
  194. package/src/sextant/frame-hits.ts +25 -0
  195. package/src/sextant/frame.ts +101 -0
  196. package/src/sextant/git-status.ts +197 -0
  197. package/src/sextant/grid.ts +59 -0
  198. package/src/sextant/input.ts +119 -0
  199. package/src/sextant/keys.ts +521 -0
  200. package/src/sextant/layout.ts +86 -0
  201. package/src/sextant/local-commands.ts +169 -0
  202. package/src/sextant/market-source.ts +287 -0
  203. package/src/sextant/mentions.ts +200 -0
  204. package/src/sextant/message-hits.ts +26 -0
  205. package/src/sextant/model.ts +387 -0
  206. package/src/sextant/overlays.ts +456 -0
  207. package/src/sextant/panel-hits.ts +38 -0
  208. package/src/sextant/pet.ts +399 -0
  209. package/src/sextant/screen.ts +324 -0
  210. package/src/sextant/scroll-hits.ts +66 -0
  211. package/src/sextant/scrollbar.ts +82 -0
  212. package/src/sextant/sextant-bridge.ts +174 -0
  213. package/src/sextant/sextant-cards.ts +142 -0
  214. package/src/sextant/sextant-diff-base.ts +63 -0
  215. package/src/sextant/sextant-files.ts +154 -0
  216. package/src/sextant/sextant-frame-loop.ts +335 -0
  217. package/src/sextant/sextant-renderer.ts +574 -0
  218. package/src/sextant/sextant-repo.ts +140 -0
  219. package/src/sextant/theme.ts +66 -0
  220. package/src/sextant/tool-rows.ts +189 -0
  221. package/src/sextant/types.ts +493 -0
  222. package/src/skills/index.ts +387 -0
  223. package/src/skills/pack.ts +220 -0
  224. package/src/skills/spec.ts +162 -0
  225. package/src/skills/tools.ts +69 -0
  226. package/src/skills/versioned.ts +227 -0
  227. package/src/telemetry/otel-export.ts +122 -0
  228. package/src/telemetry/otel-lanes.ts +89 -0
  229. package/src/telemetry/otel-logs.ts +131 -0
  230. package/src/telemetry/otel-metrics.ts +136 -0
  231. package/src/telemetry/otel.ts +397 -0
  232. package/src/telemetry/otlp.ts +76 -0
  233. package/src/tools/ask-user.ts +156 -0
  234. package/src/tools/bash-bg.ts +94 -0
  235. package/src/tools/bash-jobs.ts +237 -0
  236. package/src/tools/design.ts +151 -0
  237. package/src/tools/evalcell.ts +338 -0
  238. package/src/tools/html-text.ts +139 -0
  239. package/src/tools/provider.ts +149 -0
  240. package/src/tools/task.ts +250 -0
  241. package/src/tools/todo.ts +320 -0
  242. package/src/tools/webfetch.ts +332 -0
  243. package/src/tools/websearch.ts +359 -0
  244. package/src/tui/agents-cmd.ts +41 -0
  245. package/src/tui/app.ts +749 -0
  246. package/src/tui/attach.ts +127 -0
  247. package/src/tui/boot-notes.ts +41 -0
  248. package/src/tui/builtin-prompts.ts +59 -0
  249. package/src/tui/checkpoints-cmd.ts +70 -0
  250. package/src/tui/clipboard-image.ts +81 -0
  251. package/src/tui/clipboard.ts +78 -0
  252. package/src/tui/commands.ts +283 -0
  253. package/src/tui/config-view.ts +53 -0
  254. package/src/tui/context-cmds.ts +282 -0
  255. package/src/tui/cost.ts +108 -0
  256. package/src/tui/crash-guard.ts +173 -0
  257. package/src/tui/focus-terminal.ts +34 -0
  258. package/src/tui/git-cmds.ts +273 -0
  259. package/src/tui/git-plain.ts +58 -0
  260. package/src/tui/info-cmd.ts +150 -0
  261. package/src/tui/input-plain.ts +76 -0
  262. package/src/tui/mcp-cmd.ts +128 -0
  263. package/src/tui/memory-note.ts +77 -0
  264. package/src/tui/modes-cmd.ts +45 -0
  265. package/src/tui/notify-seq.ts +100 -0
  266. package/src/tui/notify.ts +318 -0
  267. package/src/tui/overlays.ts +97 -0
  268. package/src/tui/pi-renderer.ts +428 -0
  269. package/src/tui/providers-cmd.ts +377 -0
  270. package/src/tui/reasoning-view.ts +56 -0
  271. package/src/tui/renderer.ts +128 -0
  272. package/src/tui/replay-marker.ts +29 -0
  273. package/src/tui/session-cmd.ts +148 -0
  274. package/src/tui/session-manage.ts +95 -0
  275. package/src/tui/sextant-attach.ts +102 -0
  276. package/src/tui/sextant-io.ts +202 -0
  277. package/src/tui/sextant-smoke.ts +110 -0
  278. package/src/tui/shell-cmd.ts +158 -0
  279. package/src/tui/smoke.ts +72 -0
  280. package/src/tui/staged-terminal.ts +50 -0
  281. package/src/tui/startup.ts +12 -0
  282. package/src/tui/theme.ts +59 -0
  283. package/src/tui/todo-label.ts +7 -0
  284. package/src/tui/trust-card.ts +107 -0
  285. package/src/tui/tui-commands.ts +87 -0
  286. package/tsconfig.json +30 -0
  287. package/vendor/pi-tui/LICENSE +21 -0
  288. package/vendor/pi-tui/PATCHES.md +12 -0
  289. package/vendor/pi-tui/PROVENANCE.md +12 -0
  290. package/vendor/pi-tui/README.upstream.md +854 -0
  291. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  292. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  293. package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
  294. package/vendor/pi-tui/src/autocomplete.ts +827 -0
  295. package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
  296. package/vendor/pi-tui/src/components/box.ts +138 -0
  297. package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
  298. package/vendor/pi-tui/src/components/editor.ts +2364 -0
  299. package/vendor/pi-tui/src/components/h-stack.ts +45 -0
  300. package/vendor/pi-tui/src/components/image.ts +128 -0
  301. package/vendor/pi-tui/src/components/input.ts +448 -0
  302. package/vendor/pi-tui/src/components/loader.ts +93 -0
  303. package/vendor/pi-tui/src/components/markdown.ts +1016 -0
  304. package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
  305. package/vendor/pi-tui/src/components/select-list.ts +230 -0
  306. package/vendor/pi-tui/src/components/settings-list.ts +277 -0
  307. package/vendor/pi-tui/src/components/spacer.ts +29 -0
  308. package/vendor/pi-tui/src/components/stack.ts +155 -0
  309. package/vendor/pi-tui/src/components/text.ts +108 -0
  310. package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
  311. package/vendor/pi-tui/src/components/v-stack.ts +34 -0
  312. package/vendor/pi-tui/src/editor-component.ts +75 -0
  313. package/vendor/pi-tui/src/fuzzy.ts +138 -0
  314. package/vendor/pi-tui/src/index.ts +149 -0
  315. package/vendor/pi-tui/src/keybindings.ts +321 -0
  316. package/vendor/pi-tui/src/keys.ts +1402 -0
  317. package/vendor/pi-tui/src/kill-ring.ts +47 -0
  318. package/vendor/pi-tui/src/latex.ts +1381 -0
  319. package/vendor/pi-tui/src/layout-node.ts +52 -0
  320. package/vendor/pi-tui/src/layout.ts +411 -0
  321. package/vendor/pi-tui/src/native-modifiers.ts +60 -0
  322. package/vendor/pi-tui/src/native-module-path.ts +32 -0
  323. package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
  324. package/vendor/pi-tui/src/terminal-colors.ts +74 -0
  325. package/vendor/pi-tui/src/terminal-image.ts +701 -0
  326. package/vendor/pi-tui/src/terminal.ts +554 -0
  327. package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
  328. package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
  329. package/vendor/pi-tui/src/tui.ts +1264 -0
  330. package/vendor/pi-tui/src/undo-stack.ts +29 -0
  331. package/vendor/pi-tui/src/utils.ts +1327 -0
  332. package/vendor/pi-tui/src/word-navigation.ts +118 -0
  333. package/vendor/pi-tui/test/test-themes.ts +39 -0
  334. package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
package/README.md ADDED
@@ -0,0 +1,737 @@
1
+ # Rovecode
2
+
3
+ A coding agent for the terminal. The cockpit is a panelled TUI called sextant; the mascot is a
4
+ weather cloud whose mood follows the run. Under the hood, rovecode is a research-derived harness in
5
+ TypeScript on Bun: instead of inventing architecture it ports evidence-based patterns from open-source
6
+ harnesses (pi, opencode, codex, cline, aider, gemini-cli, oh-my-pi, hermes-agent, senpi, prime-agent,
7
+ OpenHands) — every port traces to file:line in a snapshotted source and lands only after an independent
8
+ fresh-context critic verifies it against a pre-written bar (ledger: `PORTS.md`, kept outside this
9
+ repository for now).
10
+
11
+ ![The sextant TUI at 160×44 cells: the files tree with git statuses, the code panel on src/auth/callback.ts with edited lines highlighted, the messages panel with read and edit tool rows and an approval card asking to run bun test, the plan at step 1 of 4, usage at 13% context, and the rovecode cloud pet waiting for a nod.](https://raw.githubusercontent.com/9Code-Labs/rovecode-site/main/public/shots/approval-160x44.png)
12
+
13
+ ## Status (2026-09-04, post wave 4)
14
+
15
+ - **All 20 BLUEPRINT §3 ports landed** (P1 8/8 · P2 6/6 · P3 4/4 · P4 2/2) **+ all 19 Wave-3 parity ports (#21–#39)** landed
16
+ through the gauntlet-loop (builder → fresh-context critic → fix wave → re-verify; ledger: `PORTS.md`)
17
+ - **Tests**: 2360 pass / 0 fail (191 files, unit + integration; measured 2026-09-06). CI runs the
18
+ same suite on `ubuntu-latest` (`.github/workflows/ci.yml`), so POSIX paths are gated, not just exercised.
19
+ `bun test` runs against an **empty `ROVECODE_HOME`**: `bunfig.toml` preloads `test/helpers/isolate-home.ts`,
20
+ which points it at a fresh temp directory before any test file loads and clears every `*_API_KEY`,
21
+ `GITHUB_TOKEN`/`GH_TOKEN` and `ROVECODE_*` variable, so the skills, plugins, MCP servers and keys installed
22
+ or exported on the machine cannot decide a result (installing one skill used to fail a plugin test, two MCP
23
+ servers failed twenty-five, and an exported `ANTHROPIC_API_KEY` let a headless run on a clean checkout bill
24
+ a real call). A test that wants a home or a variable of its own still sets it itself
25
+ - **Gauntlet**: 10/10 (basic, coding, failure-recovery, adversarial: loop-guard, huge-output, permission-bypass)
26
+ - **Typecheck**: 0 errors · TUI render smoke: PASS
27
+ - **Wave 4**: the sextant surface (`src/sextant/*`, the new default TUI ported from the user's prototype) is merged —
28
+ core, model/panels, code/messages, input, pet, crew board and the renderer integration; external agentic-CLI
29
+ lanes (#47) are not implemented in this repository; the pi-tui chat stays available as `--classic`
30
+ - **After wave 4**, the ports ledger stops covering what shipped. Also landed:
31
+ - the **interface-design protocol** (`src/design`, `docs/design.md`): no default look, `design_direction`
32
+ records the direction the human chose, `design_audit` checks later screens against it, and a headless run
33
+ records a *provisional* direction instead of passing its own taste off as a decision
34
+ - **three permission tiers** (ask first · accept edits · auto) with `.rovecode/settings.json` to persist one
35
+ - the **plugin format** (`src/plugins`) and the **MCP market with its project trust gate** (`src/mcp`)
36
+ - **model profiles** (`src/providers/profiles.ts`) and one **thinking dial** across every provider dialect
37
+ (`docs/thinking.md`; `rovecode model show` prints what your model receives per level)
38
+ - **run budgets** — `--max-turns` / `--max-seconds`, so a spiral ends in a result instead of an outside kill
39
+ - a decided **wire-failure policy** (`docs/wire-failures.md`): what is retried, what is never retried after
40
+ text has arrived, and the wait announced while it happens
41
+ - the **site** ([its own repo](https://github.com/9Code-Labs/rovecode-site), 15 languages, prerendered, [live](http://64.177.43.110/)) and the CI/CD workflows
42
+ that build and ship it
43
+ - **sextant** gained mouse and scrollbar dragging, the page tab strip, the notices history and prompt suggestions
44
+ - **startup** is lazy: `rovecode --help` no longer boots the TUI, the loop, the runtime or the plugin scanner
45
+
46
+ ## Install
47
+
48
+ Requires [Bun](https://bun.sh) ≥ 1.3.14 (the CLI entry is TypeScript, executed by bun — node cannot run it).
49
+
50
+ ```bash
51
+ # from source
52
+ git clone https://github.com/9Code-Labs/rovecode.git && cd rovecode && bun install
53
+ bun run src/cli/main.ts --help # or: bun link → `rovecode` on PATH
54
+ bun run build:cli # optional: pre-bundle (TUI cold start ~230 ms → ~80 ms); re-run after git pull
55
+
56
+ # single binary (~110 MB: bun runtime + bundled deps + embedded native addons)
57
+ bun run build # scripts/build.ts → dist/rovecode(.exe) + smoke
58
+ dist/rovecode.exe --version
59
+
60
+ # from an npm tarball (npm pack) — global install shims to bun via the shebang
61
+ npm install -g ./rovecode-0.3.1.tgz
62
+ ```
63
+
64
+ Not yet published to the npm registry (name availability unverified). Releases are cut on
65
+ [GitHub Releases](https://github.com/9Code-Labs/rovecode/releases) — v0.3.1 is the current one — and there is no
66
+ self-update: `git pull` and rebuild, or reinstall the binary. Rovecode does tell you when that is worth doing: the
67
+ TUI's startup card carries `update available: 0.3.0 → 0.4.0 · <release url>` when a newer release exists. The check
68
+ (`src/core/update-check.ts`) asks GitHub once every six hours (cached in `~/.rovecode/update-check.json`), never
69
+ blocks the start and never throws. The repository is private, so it needs `GITHUB_TOKEN`, `GH_TOKEN` or a
70
+ `gh auth login`; without one the result is "unknown — the release repository is private and no token is set",
71
+ never "up to date", and the card prints nothing rather than a guess. Only a real newer release produces a line
72
+ on the card — `rovecode --version` prints the version to stdout alone (so a script can still read it) and, on
73
+ stderr, whatever the last check found. It reads the cache and never opens a socket: a courtesy line must not
74
+ make the one command scripts call to identify a build wait on the network.
75
+
76
+ ## Quickstart
77
+
78
+ On a terminal, `rovecode` opens with a ~1.1 s intro centred on a cleared screen (`src/core/intro.ts`): the
79
+ ROVECODE mark fills in left to right, a hairline frame draws inward from the four corners until the halves meet,
80
+ the cloud mascot leans down out of that top line, and the mark breathes once. The session boots underneath it, in
81
+ parallel, so the only wall time this adds is whatever is left of the show once the session is otherwise ready.
82
+ Skip it with `--no-intro` or `ROVECODE_INTRO=0`; nothing is drawn into a pipe or under `--plain`, and it never
83
+ reads stdin, so keys typed during it reach the session. It hands over to a card that names the version, the connected
84
+ model, what loaded (`3 skills · 1 plugin · 2 MCP servers` — zeroes are omitted), the folder and permission tier,
85
+ and, only when there is one, the newer release (see Install). A resumed session gets a one-line note instead; a
86
+ terminal narrower than the mark gets the same facts as prose.
87
+
88
+ ```bash
89
+ rovecode connect # connect a model, step by step: pick a provider, paste the key (hidden), one
90
+ # test call
91
+ rovecode connect anthropic # the same in one line — see Providers for the flags (rovecode setup = the
92
+ # wizard)
93
+ rovecode # TUI chat — sextant on a colour TTY ≥ 100×30 (truecolor or 256), else the
94
+ # classic chat
95
+ rovecode --classic # force the classic chat; --plain = readline REPL; --pet <name> names the
96
+ # sextant pet
97
+ rovecode "fix the failing test" # one-shot task
98
+ rovecode run "<prompt>" --yolo # one-shot in auto mode (never asks)
99
+ rovecode --effort high # how hard the model thinks first: auto (default) | off | low | medium | high
100
+ # (/effort in the TUI, ROVECODE_EFFORT=…). auto sends no thinking field and
101
+ # leaves the endpoint's own default standing. Anthropic takes
102
+ # output_config.effort or a thinking budget depending on the model — rovecode
103
+ # learns which from the endpoint's own 400 and remembers it; OpenAI takes
104
+ # reasoning_effort. Billed as output tokens. `rovecode model show` prints what
105
+ # YOUR model receives per level (docs/thinking.md).
106
+ rovecode --accept-edits # middle tier: writes INSIDE this folder stop asking; shell, subagents,
107
+ /yolo --save # make it stick: the level is written to ~/.rovecode/settings.json and the
108
+ /accept-edits --save --project # next launch starts there. --project pins it to this checkout instead.
109
+ # Ladder: CLI flag > ROVECODE_PERMISSION > .rovecode/settings.json (project) >
110
+ # ~/.rovecode/settings.json (user) > ask. Without --save a toggle lasts one
111
+ # session. network and writes outside it still ask (/accept-edits ·
112
+ # ROVECODE_ACCEPT_EDITS=1 · or the `all edits` button on a write approval
113
+ # card)
114
+ # The same file takes "bell": false — both TUIs notify you (bell, or an
115
+ # OSC 9 toast where the terminal shows one) when a run ends or a card
116
+ # needs you, ONLY while the terminal is unfocused; off with that key.
117
+ @src/auth.ts why does this fail # @file attaches the file to the message as a `read` would return it —
118
+ # contents + edit anchors, no tool round-trip. Capped and said: 400 lines
119
+ # per file (the footer names the offset to continue), 8 files, ~60k chars
120
+ # per message; a directory, a binary, a 2 MB+ file or a path outside the
121
+ # workspace is named and left out. The panel shows one chip per file.
122
+ rovecode run "<prompt>" --output json # ONE result object on stdout (ndjson: one line per RunEvent + a
123
+ # result line)
124
+ rovecode run "<prompt>" --max-seconds 300 --max-turns 40 # ceilings on one run: a hit ends it cleanly with
125
+ # status "budget" (exit 1) and the work so far, not
126
+ # an outside kill. A headless run already has a
127
+ # 20-minute clock (--max-seconds off removes it);
128
+ # ROVECODE_MAX_TURNS / ROVECODE_MAX_SECONDS set both
129
+ # on every surface, TUI included
130
+ rovecode run "/review src/x.ts" # a leading /name expands .rovecode/commands/<name>.md (a custom slash
131
+ # command) headlessly
132
+ rovecode gauntlet # adversarial eval suite (offline, deterministic, 10 tasks)
133
+ rovecode gauntlet --live # 9 of those tasks against the configured REAL model, through the real prompt
134
+ # (--model provider/model, --effort …): the before/after instrument for prompt
135
+ # work
136
+ rovecode bench # cross-harness micro-benchmarks
137
+ rovecode tools # registered tool listing
138
+ rovecode auth set <provider> # store an API key (prompted on the terminal, never echoed); auth list / auth
139
+ # remove
140
+ rovecode provider add <id> <url> # register any OpenAI-compatible or Anthropic endpoint — live, no
141
+ # restart; also provider list|test
142
+ rovecode model # pick from a numbered menu of every configured provider's models
143
+ rovecode models # just list them (* = current); alias for `model list`
144
+ rovecode model use <provider/model> # persist the default model directly (--project pins it to this repo)
145
+ rovecode model show # the model, its protocol, and the exact thinking field each --effort level
146
+ # sends
147
+ rovecode sessions [--json] # this folder's sessions, newest first (+ a footer counting empty session dirs)
148
+ rovecode sessions rename|delete|fork|search # name, remove (with its checkpoints), copy or search sessions —
149
+ # ids are exact or a unique prefix; ambiguous/unknown → exit 2, nothing touched
150
+ rovecode trace <id|prefix> # replay a session's JSONL tree
151
+ rovecode acp # Agent Client Protocol v1 over stdio (Zed/JetBrains)
152
+ rovecode serve # headless HTTP + SSE server (ROVECODE_PORT, loopback-only)
153
+ ```
154
+
155
+ Without a provider configured, one-shot runs answer from a scripted mock (also how the packaging
156
+ smoke works), and the TUI opens with a card pointing at `/setup`. The quickest way to a real model is
157
+ `rovecode connect` (or `/setup` in the TUI). Give it a provider id and it stops asking — the whole
158
+ sitting becomes one line that fits in a README, a Dockerfile or a CI step:
159
+
160
+ ```bash
161
+ rovecode connect # no arguments: the step-by-step wizard (rovecode setup)
162
+ rovecode connect anthropic # a built-in: key from the env, else one hidden prompt
163
+ rovecode connect anthropic --model claude-opus-5 # pin the model too
164
+ rovecode connect ollama --no-key # a local server: nothing to store
165
+ rovecode connect gw https://gw.corp/v1 --protocol anthropic --key --project # your own endpoint
166
+ echo "$KEY" | rovecode connect groq --key-stdin # scripts and CI: no terminal needed
167
+ rovecode connect gw https://gw.corp/v1 --key-env GW_TOKEN --no-test # register only, skip the test call
168
+ ```
169
+
170
+ It registers the endpoint, stores the key, picks the model, makes one tiny real call and persists the
171
+ default. A key is never a flag *value* — that would sit in the shell history and in every `ps` listing
172
+ — so `--key` prompts (hidden), `--key-stdin` reads one piped line and `--key-env NAME` names an env var
173
+ to read at call time. Exit codes: `0` connected, `1` the test call failed (**the config is still
174
+ written** — `rovecode provider test <id>` retries it), `2` a usage error.
175
+
176
+ By hand, store a key once — built-in providers need nothing else:
177
+
178
+ ```bash
179
+ rovecode auth set anthropic # prompts for ANTHROPIC_API_KEY — never echoed, never logged
180
+ rovecode auth set kaesra --key MY_PROXY_KEY # override the key name recorded for a provider
181
+ rovecode auth list # stored providers + key names, values redacted (first 4 chars)
182
+ rovecode auth remove anthropic
183
+ rovecode auth set openai < key.txt # piped stdin: reads one line, no prompt (scripts)
184
+ ```
185
+
186
+ Any other OpenAI-compatible or Anthropic endpoint — a proxy, a gateway, a local server — is one line
187
+ away, and every change is **live**: a running TUI, `rovecode serve` or `rovecode acp` picks it up on the
188
+ next model call, no restart:
189
+
190
+ ```bash
191
+ rovecode provider add myproxy https://llm.example.com/v1 --model gpt-5 --key # --key prompts (never echoed)
192
+ rovecode provider add ollama http://127.0.0.1:11434/v1 --no-key # local server, no key
193
+ rovecode provider add gw https://gw.corp/v1 --protocol anthropic --key-env GW_TOKEN --project # ./.rovecode
194
+ rovecode provider list # configured providers + the default provider/model (key SOURCES
195
+ # only)
196
+ rovecode provider test myproxy # one tiny real call: url + key + model
197
+ rovecode model list myproxy # ids from providers.json or the endpoint's /models
198
+ rovecode model use myproxy/gpt-5 # persist the default (running TUIs switch live)
199
+ ```
200
+
201
+ In the TUI the same surface is `/connect` (bare: the guided cards, same as `/setup`; with an id: the
202
+ one-liner above, minus the key flags — there is no hidden prompt in there, so a missing key is handed
203
+ over to `rovecode auth set <id>` or `/provider key <id> <secret>` and the command finishes itself the
204
+ moment the key lands), `/provider list|add|remove|use|test|key <id> <secret>`, `/models
205
+ [provider]` and `/model <provider/model> [--save]`; the agent itself has `provider_list` (read-only) and
206
+ `provider_edit` (add/remove/use — asks for approval, **never accepts a key**: the human stores it with
207
+ `rovecode auth set <id>` or `/provider key`, or names an env var with `keyEnv`).
208
+
209
+ Keys live in `~/.rovecode/credentials.json` (`ROVECODE_HOME` overrides the directory). On POSIX the file is
210
+ written 0600 inside a 0700 directory. On Windows, mode bits are not enforced — the file is protected
211
+ by the NTFS ACL of your user profile (`%USERPROFILE%`, which `~/.rovecode` inherits), not by permission
212
+ bits. Stored keys beat `<NAME>_API_KEY` env vars; an explicit `ROVECODE_BASE_URL`/`ROVECODE_API_KEY` pair
213
+ beats both. Endpoints live in `providers.json` (see Configuration → Providers) — never keys.
214
+
215
+ Or configure by env:
216
+
217
+ ```bash
218
+ ROVECODE_BASE_URL=... ROVECODE_API_KEY=... # any OpenAI-compatible or Anthropic endpoint (always wins)
219
+ OPENAI_API_KEY=... / ANTHROPIC_API_KEY=... / DEEPSEEK_API_KEY=... / GROQ_API_KEY=... # named providers
220
+ ROVECODE_MODEL=zai-org/glm-5.3 # model id
221
+ ROVECODE_MODEL_DEFAULT=prov/a,prov/b # role fallback chains (DEFAULT SMOL PLAN COMMIT TASK); advance on
222
+ # 429/5xx
223
+ ```
224
+
225
+ TUI slash commands: `/help /setup /status /cost /model /effort /yolo /accept-edits /plan /act /rewind /tree
226
+ /sessions /resume /new /checkpoints /restore /skills /memory /export /todos /tasks /attach /paste /mcp /exit`,
227
+ plus one `/name` per custom command
228
+ file in `.rovecode/commands/` (project) or `~/.rovecode/commands/` (user scope). The sextant surface adds its own
229
+ renderer-local `/theme night|ember|contrast`, `/open <file>`, `/diff [file]`, `/focus messages|code|files`,
230
+ `/agents` and `/notices` (the notification history, also `⌃b`) — they never reach the agent; the names are
231
+ reserved against custom commands.
232
+
233
+ Images: `/attach <path>` stages an image for your next message (text is still required, at most 8 per
234
+ message, `ROVECODE_IMAGE_MAX_BYTES` caps each), `/paste` (or `⌃v`) takes the image on the clipboard, and
235
+ dragging an image file onto the terminal attaches it directly.
236
+
237
+ **The sextant surface** (wave 4, ports #40–#46 — ported from the user's own sextant v0.4.0 prototype):
238
+ a panelled cockpit instead of a chat log — `files` (git tree with M/A/D, touched-file spinner) · `code`
239
+ (the file the agent reads/edits with the highlight band, `±` HEAD-vs-disk diff after an edit lands and the
240
+ approval preview before it, `$` run output with PASS/FAIL chips, `∷` the crew board over background tasks)
241
+ · `messages` (compact tool rows `· read x … N lines` `~ edit x +a −b` `$ run cmd`, the ONE modal card for
242
+ approvals and `ask_user`, the prompt with `/` suggestions and `@file` mentions) · `plan` (the session's
243
+ todos + crew) · `usage` (tokens, context bar, cost) · `rovecode`, the weather-cloud pet whose mood follows
244
+ the run. `rovecode` picks it when stdout is a TTY of at least 100×30 that renders truecolor (`COLORTERM`,
245
+ `WT_SESSION`, `TERM_PROGRAM` vscode/iTerm/WezTerm/ghostty, kitty/`-direct` `TERM`) or 256 colors (a
246
+ `*-256color` `TERM` with no `COLORTERM`, painted through the xterm-256 quantizer); `ROVECODE_TUI=sextant|classic`
247
+ overrides the heuristics (a non-TTY never gets sextant, nor does a TTY under the 40×12 floor), `--classic`
248
+ beats both. After an edit the `code` panel's diff is the ONE change that landed — captured before an
249
+ approved edit, rebuilt from the edit's own anchors after an ungated one — and falls back to a `vs HEAD`
250
+ view (every uncommitted change) only when neither is possible. Git runs beside the frame loop: a slow
251
+ `git status` never stalls the spinner or the keys. Keys: `⏎` send · `tab`
252
+ complete/cycle focus · `esc esc` stop the run · `⌃c` quit (interrupts first) · `⌃k`/`⌃p` palette · `⌃s` code
253
+ (and focus it) · `⌃d` diff (press again to go back to code) · `⌃r` run output · `⌃a` agents board · `⌃e` files · `⌃o` cycle the page tabs (code/files/plan, narrow terminals) · `⌃b`
254
+ notifications · `⌃v` paste a clipboard image · `⌃t` theme · `⌃n` new session · `⌃u` clear the prompt ·
255
+ `⌃←`/`⌃→` word jump · mouse: clicks everywhere (tabs, file rows, cards, a tool row opens its file, the header's unread badge, the footer's theme and effort words), wheel over any panel, scrollbar drag. `rovecode smoke-tui --sextant` renders a
256
+ 160×44 frame through the real pipeline and prints PASS.
257
+
258
+ **`@file` in the prompt** (both TUIs) attaches the file to the message exactly as the `read` tool would return it —
259
+ hashline header, numbered lines with their anchors, footer — so the model has the contents and valid edit anchors
260
+ without a tool round-trip. In the sextant, `@` opens a fuzzy picker over the workspace's files and a mention resolves
261
+ the way the picker does (exact path, unique basename, best fuzzy match); in the classic chat, `@` autocompletes
262
+ paths and a mention must be the exact cwd-relative path. The transcript shows one chip per file, never the file body.
263
+ A file the model did not ask for is still context you pay for, so every limit is enforced AND said (a toast in the
264
+ sextant, a note in the classic chat): **400 lines per file** (the block's footer names the offset that continues),
265
+ **8 files per message**, **about 60,000 characters of files per message** (a further file is named instead of being
266
+ cut to a fragment). Named and left out: a mention nothing matches, a directory, a binary, a file over **2 MB**, a path
267
+ outside the workspace. A `/command` or a `!shell` line is never expanded. Settings: none — the caps are fixed.
268
+
269
+ ## Features beyond the 20 ports (wave 3, verified per port in `PORTS.md`)
270
+
271
+ - **Custom slash commands** (#30) — `.rovecode/commands/<name>.md` (project shadows `~/.rovecode/commands/`):
272
+ optional frontmatter `description:` / `model:` (per-run override, restored after) / `mode: plan|act`
273
+ (durable switch), body = prompt template with `$ARGUMENTS`, `$1..$9`, `$$`; autocomplete + `/help`
274
+ list them; a built-in name always wins (boot warning). `rovecode run "/name args"` expands the same files
275
+ headlessly (model:/mode: are TUI-only there). Arguments reach the template raw — whitespace runs and
276
+ pasted newlines survive.
277
+ - **Todo list** (#32) — `todo_write`/`todo_read` keep one `todos.json` per session (whole-list replace,
278
+ one `in_progress` at a time, bounded); `/todos` renders it as checkboxes and the status bar shows
279
+ `todos done/total`. Plan mode keeps `todo_write` (the plan's own artifact) while denying every other write.
280
+ - **Background tasks** (#26) — the `task` tool starts child agent sessions as bounded FIFO jobs
281
+ (`ROVECODE_TASKS_MAX`, default 3) through the ONE agent loop; completion notes land on the parent's next
282
+ turn as steering; `/tasks` lists them, `/tasks cancel <id>|all` cancels; quitting the TUI, `rovecode serve`
283
+ `stop()` and `rovecode acp` shutdown cancel every live child; `GET /session/:id/tasks` over HTTP.
284
+ - **ask_user** (#33) — the model asks a question through a modal overlay (options or free text) on
285
+ interactive surfaces; headless surfaces fail the tool closed.
286
+ - **web_fetch** (#31) — bounded, SSRF-guarded HTTP fetch (`net.fetch <host>` policy action; prompt by
287
+ default; `ROVECODE_WEBFETCH_TIMEOUT_MS`, `ROVECODE_WEBFETCH_ALLOW_PRIVATE=1`).
288
+ - **Output modes** (#35) — `rovecode run --output text|json|ndjson`: `json` = exactly ONE result object
289
+ `{status, summary, sessionId, model, origin, usage, costUsd, toolCalls, durationMs, exitCode}` on stdout;
290
+ `ndjson` = every RunEvent as a JSON line then a final `{type:"result"}` line; stdout is JSON-only
291
+ (progress → stderr; the guard is up before the runtime boots, so even a `session_open` hook's prints land
292
+ on stderr); `--output=<mode>` also accepted; exit 0 done · 1 error/budget · 2 usage/startup
293
+ error (one stderr line, nothing on stdout — validated before the runtime boots) · 130 aborted.
294
+ - **Reflection** (#28, aider pattern) — a failed `edit`/`write` (or one that introduces LSP diagnostics)
295
+ gets ONE `reflection: …` nudge on the next turn with the error in context, capped at 2 per run
296
+ (`ROVECODE_REFLECTION_MAX`; `ROVECODE_REFLECTION=0` disables); identical repeat failures are not re-nudged and
297
+ the loop guard still fires. Nudges serve the active session's runs only — a background-task child gets
298
+ none (its loop guard still bounds repeats; its failure text reaches the parent through the task note).
299
+ Failed edits now report the anchor line's current text and hash, the lines
300
+ that do match, and the read-then-retry remedy; `write` into a missing directory says so.
301
+ - Also landed: first-class `glob`/`grep`/`ls` tools (#22), same-model retry with backoff (#23), diff
302
+ previews in approval overlays (#24), compaction strategies (#25), per-project sandbox rung (#27),
303
+ `rovecode export` (#38), `rovecode auth` credential onboarding (#37), packaging (#36).
304
+
305
+ ## What's ported (the 20 landed ports)
306
+
307
+ Full ledger with bars, critic verdicts, and evidence: `PORTS.md` (not yet published with this repository). Sources are MIT or
308
+ Apache-2.0 only; Apache attributions in `THIRD_PARTY_NOTICES.md`.
309
+
310
+ **Surfaces**
311
+ - #1 differential-render TUI, vendored pi-tui behind a `Renderer` seam (pi, MIT) — now the `--classic` chat
312
+ - #40–#46 the sextant surface (user-owned prototype): cell buffer + diff flush, key/mouse/paste parser, the
313
+ RunEvent reducer, files/code/messages/plan/usage panels, the crew board and the pet — a second `Renderer`
314
+ implementation over the same ONE loop (`src/sextant/`, `tui/sextant-io.ts`); `onEvent?`/`attach?` are the
315
+ two optional seam members it needs
316
+ - #2 branch navigator + rewind/edit-resubmit over the session DAG (pi pattern, MIT)
317
+ - #15 ACP v1 endpoint for Zed/JetBrains via the official SDK (Apache-2.0)
318
+ - #19 headless HTTP server: sessions, SSE RunEvents, OpenAPI at `/doc` (opencode design, MIT)
319
+ - #20 Plan/Act modes with per-mode model config, plan = read-only tool rules (cline, Apache-2.0)
320
+
321
+ **Providers**
322
+ - #5 prompt-cache boundaries (`cache_control`) + normalized usage accounting (hermes pattern + tokenlens, MIT)
323
+ - #6 models.dev pricing/context catalog, offline snapshot, `/cost` (OSS)
324
+ - #7 tool-call middleware: XML/Hermes/JSON-in-text parsed to native tool calls for non-native models (senpi, MIT)
325
+ - #14 role routers + fallback chains + rate-limit chain advance (OMP, MIT + gemini-cli, Apache-2.0)
326
+
327
+ **Coding**
328
+ - #11 shadow-git checkpoints, 3 restore modes, user `.git` never touched (cline, Apache-2.0)
329
+ - #12 repo-map: tree-sitter (@ast-grep) symbols + PageRank, budgeted context chunk, persistent cache (aider, Apache-2.0)
330
+ - #13 LSP diagnostics gate after edits (opencode/OMP, MIT)
331
+
332
+ **Safety & execution**
333
+ - #4 tool-loop guardrails: repeat-signature loop break, duplicate-result stubs (hermes-agent, MIT)
334
+ - #9 execpolicy: declarative command policy, strictest-wins, forbidden never executes (openai/codex, Apache-2.0)
335
+ - #10 executor sandbox ladder: direct/WSL2/Docker rungs behind one `Executor` seam, probed not assumed (codex + OpenHands patterns); #27 makes the rung selectable per project (`.rovecode/sandbox.json` / `ROVECODE_SANDBOX`)
336
+
337
+ **Memory & context**
338
+ - #3 MCP client, stdio + HTTP, lazy disclosure (two registry tools, ~0 idle token cost) (MIT)
339
+ - #8 config inheritance: AGENTS.md / CLAUDE.md / .claude / .cursor / .github instructions harvested into capped chunks (oh-my-pi, MIT)
340
+ - #16 versioned memory/skill edits with optimistic concurrency + one-call rollback (prime-agent, MIT)
341
+ - #17 cross-session recall: FTS over past session JSONL (hermes-agent, MIT)
342
+ - #18 persistent eval cell / code-mode, feature-flagged `ROVECODE_EVAL_CELL=1` (OMP/prime/codex patterns)
343
+
344
+ ## Architecture
345
+
346
+ ```
347
+ providers/stream.ts StreamFn seam — never throws; errors are stopReasons
348
+ + middleware.ts text tool-calls → native parts (non-native models)
349
+ + router.ts role tables + fallback chains
350
+ + cache.ts, catalog.ts prompt-cache boundaries, usage, pricing
351
+ ↓
352
+ core/loop.ts ONE generator agentLoop: steering/follow-up drains,
353
+ eviction, compaction, guardrails, depth-threaded spawns
354
+ ↓
355
+ core/tools.ts validate → revise(hooks) → policy(deny-default, last-match)
356
+ → approve(revised args, cached) → execute → typed outcome
357
+ + execpolicy.ts declarative command rules (allow/prompt/deny/forbidden)
358
+ + guardrails.ts loop signatures + duplicate stubs
359
+ ↓
360
+ core/session.ts append-only JSONL tree: branch=rewind, sha256 hash chain
361
+ coding/ hashline anchored edits · repomap · lsp gate · checkpoints
362
+ memory/ bounded blocks + versioned edits + cross-session recall
363
+ tools/ the non-coding built-ins: task, todo, ask_user, webfetch,
364
+ design, eval cell — each a Tool the registry gates
365
+ design/ the interface-design protocol: prompt section, the
366
+ design_direction record, the design_audit checker
367
+ skills/ SKILL.md discovery + the versioned skill tools
368
+ plugins/ plugin.json folders: tools, hooks, commands, skills, MCP
369
+ telemetry/ OpenTelemetry spans and the OTLP exporter
370
+ mcp/ acp/ server/ tui/ sextant/ surfaces over the same loop (no second loop
371
+ generation); sextant is the default TUI, tui the --classic one
372
+ eval/ scripted-provider gauntlet + deterministic benches
373
+ ```
374
+
375
+ ## Configuration
376
+
377
+ Defaults < project config chunks (harvested, capped) < env < CLI flags.
378
+
379
+ - `~/.rovecode/providers.json` (user) and `.rovecode/providers.json` (project) — model providers and the
380
+ default model; see Providers below
381
+ - `.rovecode/mcp.json` (+ harvested `.mcp.json`, + `~/.rovecode/mcp.json` for you) — MCP servers; `rovecode mcp add`
382
+ writes them. PROJECT files are gated: until you approve them (`rovecode mcp show` to review,
383
+ `rovecode mcp trust` to approve) their servers stay off, and any edit asks again (`docs/mcp-market.md`)
384
+ - **Project trust** (`src/core/trust.ts`, 2026-09-07) — the same gate, one store (`~/.rovecode/plugins.json`, path →
385
+ sha256), now covers EVERY project file that could make rovecode run or import something: `.rovecode/hooks.ts`
386
+ (imported in-process at boot — the worst of them), `.rovecode/sandbox.json` (the rung and the docker image every
387
+ bash command runs in), `.rovecode/settings.json` when it carries a command-bearing key (`verify`, `lsp`,
388
+ `notify_command`), and the MCP files. A cloned repository's copies contribute NOTHING until `rovecode trust`:
389
+ `rovecode trust show` lists each file with what it WOULD do (the keys and their values, the rung and image, the
390
+ server commands), `rovecode trust [--yes]` approves them as they are now, `rovecode trust untrust` withdraws; an
391
+ edit asks again. Your own `~/.rovecode` files never ask. `verify: false` is a refusal, not a command, and is honoured
392
+ from any file. `.rovecode/commands/*.md` and `agents/*.md` are prompt text, not executables — out of this gate's scope
393
+ - `.rovecode/modes.json` — per-mode model config (TUI-scoped; see limitations)
394
+ - `.rovecode/sandbox.json` — `{"rung": "direct"|"wsl"|"docker", "dockerImage"?: "…"}` selects where `bash` runs (#27);
395
+ `ROVECODE_SANDBOX=<rung>` / `ROVECODE_SANDBOX_IMAGE=<image>` override it; default `direct`
396
+ - `.rovecode/commands/*.md` — custom slash commands (project scope); `~/.rovecode/commands/*.md` (user scope,
397
+ `ROVECODE_HOME`-aware) is scanned first and shadowed by the project's (#30)
398
+ - `.rovecode/profiles/<id>.md` (project) / `~/.rovecode/profiles/<id>.md` (user) — replaces a model profile's
399
+ prompt section (see Model profiles below); `ROVECODE_PROFILE=off|<id>` turns profiles off or forces one
400
+ - `.rovecode/design.json` — the interface-design direction this project chose; written by `design_direction`,
401
+ checked by `design_audit` (`ROVECODE_DESIGN=off` drops the prompt section). See `docs/design.md`
402
+ - `.rovecode/settings.json` (project) / `~/.rovecode/settings.json` (user) — the answers you should only have to
403
+ give once; the project file wins key by key. Three keys: `"permission": "ask" | "accept-edits" | "auto"` (written
404
+ by `/yolo --save` and `/accept-edits --save [--project]`; ladder: CLI flag > `ROVECODE_PERMISSION` > project >
405
+ user > ask), `"effort": "auto" | "off" | "low" | "medium" | "high"` (the thinking dial, `/effort --save`), and
406
+ `"bell": false` — both TUIs notify you when a run ends and when an approval or question card opens, but ONLY while
407
+ the terminal is unfocused: the point is telling someone who looked away, and a bell on every turn is a bell nobody
408
+ hears (a terminal that never reports focus reads as watched and stays silent). `"notify": "auto" | "bell" | "osc9" |
409
+ "osc777"` picks the signal (auto = an OSC 9 desktop toast on Ghostty, iTerm2, kitty, Warp and WezTerm, the BEL
410
+ everywhere else; Windows Terminal turns the bell into a flash, a chime or a taskbar badge per profile);
411
+ `"notify_when": "always"` brings back a signal on every run; `"notify_command": ["notify-send","rovecode"]` (a JSON
412
+ string array, or whitespace-split words) runs a desktop hook under the same gate with a JSON payload as its last
413
+ argument, never through a shell — from the project file it applies only once that file is trusted, from the user file
414
+ always. `ROVECODE_NOTIFY[=off]`, `ROVECODE_NOTIFY_WHEN` and `ROVECODE_NOTIFY_COMMAND` override the files. Fourth key: `"verify": "bun run check"`
415
+ (or a list, run in order; or `false`) — the check the loop runs after the agent's last edit before its reply counts
416
+ as done. Without the key rovecode infers one only where both the name and the shape are recognised: a
417
+ `package.json` `check` script (unless its body names deploy/publish/push/docker/curl and the like), or a
418
+ `typecheck`/`lint`/`test` script whose body is a runner known to run unattended and end (tsc, eslint, biome, bun
419
+ test, vitest run, jest, node --test, mocha — never a watch mode), `cargo check`, `go vet ./...`, `ruff check .`;
420
+ `npm test` with any other body, `cargo test`, `pytest` and Makefile targets are never inferred, and `rovecode doctor`
421
+ prints each refusal with its reason so you can set the key deliberately. The check runs after edits made with the
422
+ `edit` or `write` tools; a run that changed files only through `bash` is not counted, so it is not verified. Anything else in the file, or a
423
+ value of the wrong type (`"bell": "off"`), is ignored rather than guessed at
424
+ - `.rovecode/` also holds sessions (each with its `todos.json`), checkpoints, repo-map cache
425
+ - Permission rules: deny-by-default, last-match wildcard (`file.read/write`, `shell.exec`, `spawn`, `memory.write`, `net.fetch`, `tool.*`). Three permission levels, as the screen names them: **ask first** (default — I ask before every write, shell command and subagent), **accept edits** (`--accept-edits` / `ROVECODE_ACCEPT_EDITS=1` / `/accept-edits` — writes inside this folder stop asking; shell, subagents, network and writes outside it still ask) and **auto (never asks)** (`--yolo` / `ROVECODE_YOLO=1` / `/yolo` in the TUI). `ROVECODE_PERMISSION=ask|accept-edits|auto` sets the level a run starts at; auto skips the prompts, never the deny rules or plan mode
426
+ - `.rovecode/hooks.ts` (+ `~/.rovecode/hooks.ts`, `ROVECODE_HOME`-aware) — typed hook set (#29; see Extending → Hooks);
427
+ `ROVECODE_NO_HOOKS=1` skips the files, `ROVECODE_HOOK_TIMEOUT_MS` bounds every call
428
+
429
+ ### Providers
430
+
431
+ Providers are data, merged per id in this order (later wins): the built-in table (kaesra, openai,
432
+ anthropic, deepseek, groq, openrouter, ollama, lmstudio, together, mistral, cerebras, fireworks,
433
+ perplexity, xai, moondream, vllm) < `~/.rovecode/providers.json` < `<project>/.rovecode/providers.json` <
434
+ the `ROVECODE_BASE_URL`/`ROVECODE_API_KEY` pair (provider id `custom`, always the default). Both files share
435
+ one shape:
436
+
437
+ ```json
438
+ {
439
+ "default": "myproxy/gpt-5",
440
+ "providers": {
441
+ "myproxy": { "baseUrl": "https://llm.example.com/v1", "protocol": "openai",
442
+ "keyEnv": "MYPROXY_API_KEY", "defaultModel": "gpt-5",
443
+ "models": ["gpt-5", "gpt-5-mini"], "headers": { "x-org": "9code" } },
444
+ "ollama": { "baseUrl": "http://127.0.0.1:11434/v1", "noKey": true }
445
+ }
446
+ }
447
+ ```
448
+
449
+ - `protocol` is `openai` (chat/completions) or `anthropic` (messages); omitted, it is inferred from the URL.
450
+ - Keys are never in this file. A provider's key is its stored credential (`rovecode auth set <id>`) else
451
+ `process.env[keyEnv]` (default: the models.dev name, e.g. `ANTHROPIC_API_KEY`, else `<ID>_API_KEY`);
452
+ `noKey: true` marks local servers. `headers` are sent on every request (the protocol's own auth header wins).
453
+ - `default` is `provider/model` (split on the first slash, so model ids keep their slashes) or a bare provider
454
+ id (→ its `defaultModel`); the project file's `default` beats the user's, `ROVECODE_MODEL` beats both.
455
+ Without a `default`: the first stored credential in table order, else the first env key, else a keyless
456
+ file provider.
457
+ - **Hot reload.** The runtime keeps ONE live registry (`src/providers/registry.ts`) whose stream resolves
458
+ `model.provider` on every call and re-reads the two `providers.json` files and `credentials.json` when
459
+ their mtime changes. `rovecode provider add …` / `rovecode auth set …` in another terminal, `/provider …` in
460
+ the TUI, and the agent's `provider_edit` tool all take effect on the next model call — no restart. A call
461
+ to an unknown provider, or one without a key, ends the turn with ONE `config:` error naming the fix; the
462
+ router/retry layers treat that prefix as non-retryable.
463
+ - Because routing is per call, `/model other/model` switches providers mid-session and cross-provider
464
+ fallback chains (`ROVECODE_MODEL_<ROLE>=a/x,b/y`) really fail over to the other endpoint.
465
+ - Surface: `rovecode provider list [--all] | add <id> <baseUrl> [--protocol openai|anthropic] [--key-env NAME]
466
+ [--model <id>] [--no-key] [--project] [--key] | remove <id> | test <id> [model]`, `rovecode model list [provider]
467
+ | use <provider/model> [--project]`; TUI `/provider …`, `/models [provider]`, `/model <provider/model | model>
468
+ [--save]`; agent tools `provider_list` (kind read: list/models/test) and `provider_edit` (kind custom →
469
+ `tool.provider_edit`, prompted in ask-first mode, denied in plan mode; add/remove/use; refuses API keys).
470
+
471
+ Environment knobs (`rovecode help env` is the full reference; this list is the commentary):
472
+
473
+ - `ROVECODE_BASE_URL` / `ROVECODE_API_KEY` — any OpenAI-compatible or Anthropic endpoint; always wins over stored and named keys
474
+ - `ROVECODE_MODEL` — model id (beats the providers.json `default`); `ROVECODE_MODEL_<ROLE>` — fallback chain per role
475
+ (DEFAULT SMOL PLAN COMMIT TASK), comma-separated `provider/model`, advancing on 429/5xx (#14); each candidate
476
+ is served by its own provider's endpoint
477
+ - `ROVECODE_RETRY_MAX` (default 3, so 4 attempts; 0 = off) / `ROVECODE_RETRY_BASE_MS` (default 1000) — same-model
478
+ retries on 429/5xx/transport failures. The first backoff is capped at `RETRY_BASE_MS`, doubles per attempt up to
479
+ 20 s, is fully jittered, and a `Retry-After` header raises the wait but never lowers it. Wired INSIDE the router,
480
+ so retries exhaust before the fallback chain advances (#23). The wait is announced **while it happens** —
481
+ `anthropic: overloaded — retrying in 4 s (2/4)` as a TUI note or on `rovecode run`'s stderr — and giving up says
482
+ which limit was hit: the attempts, the retry budget, or the run's own deadline
483
+ - `ROVECODE_FIRST_BYTE_TIMEOUT_MS` (default 60000) — how long a provider may go without sending **anything** before
484
+ the request counts as failed and is retried. Only the first byte is on this clock; once the model is talking the
485
+ body may take as long as it takes. A connection that drops mid-stream **after** text arrived is NOT retried: the
486
+ partial answer is kept and the error row says why (`docs/wire-failures.md`)
487
+ - `ROVECODE_WEBFETCH_TIMEOUT_MS` (default 30000) / `ROVECODE_WEBFETCH_ALLOW_PRIVATE=1` — `web_fetch` timeout and the SSRF-guard
488
+ escape for loopback/private hosts (local dev servers) (#31)
489
+ - `ROVECODE_COMPACTION` — `head-summarize` (default) | `keep-window` | `provider-native` (#25)
490
+ - `ROVECODE_TASKS_MAX` (default 3) — concurrent background tasks; extra `task start`s queue FIFO (#26)
491
+ - `ROVECODE_OTEL_ENDPOINT` (e.g. `http://host:4318`) — OTLP/HTTP collector; exports one trace per run (`rovecode.run` ⊃
492
+ `rovecode.turn` ⊃ `rovecode.tool`) with token/latency/cost attributes; unset = off, the exporter is never constructed (#39);
493
+ `ROVECODE_OTEL_HEADERS=k=v,k2=v2` — extra OTLP headers (e.g. `authorization=Bearer …`)
494
+ - `ROVECODE_REFLECTION=0` disables the reflection nudges; `ROVECODE_REFLECTION_MAX` (default 2) caps them per run (#28)
495
+ - `ROVECODE_SANDBOX` / `ROVECODE_SANDBOX_IMAGE` — executor rung for `bash` and the docker image (#27)
496
+ - `--output text|json|ndjson` (flag, `rovecode run` only) — output mode (#35); `ROVECODE_YOLO=1` — allow all tool actions;
497
+ `ROVECODE_STREAM` — streaming is ON by default for both protocols; `off`/`json`/`0`/`false`/`none` fall back
498
+ to the one-shot JSON adapters (a proxy with no SSE route); `sse` selects the raw OpenAI-compatible SSE
499
+ adapter for `rovecode run` — still streaming, but without the text-tool-call middleware wrap;
500
+ `ROVECODE_OPENAI_WIRE=responses|chat` — which OpenAI wire a request takes (`src/providers/wire-select.ts`, per call).
501
+ Unset: a stored ChatGPT login (`rovecode auth login openai`) always takes `/responses` (its token is accepted by the
502
+ Codex backend only, with `originator: rovecode`); provider `openai` takes `/responses` for models the catalog does not
503
+ mark non-reasoning (gpt-5, o3, unknown ids) and `/chat/completions` for the gpt-4o class; every other provider stays
504
+ on `/chat/completions`, byte-identical to before. Selection is static — a `/responses` 404 is an error turn, never a
505
+ re-issue. Not done yet on `/responses`: reasoning items are not replayed to the model on the next turn (each turn
506
+ reasons afresh; reasoning text still streams live) — that needs a reasoning message part across the store, export
507
+ and the other wires;
508
+ `ROVECODE_HOME` — credentials + user-scope commands dir (default `~/.rovecode`)
509
+ - `ROVECODE_TUI=sextant|classic` — force the TUI surface (#44; sextant still needs a TTY of at least 40×12,
510
+ `--classic` wins); `ROVECODE_THEME=night|ember|contrast` — the sextant palette at boot (`/theme` switches it
511
+ live); `ROVECODE_PET=0` — hide the sextant pet panel (`--pet <name>` renames it); the surface picks itself
512
+ at ≥ 100×30 cells with truecolor or a 256-color `TERM` — below that, or on a pipe, `rovecode` opens the
513
+ classic pi-tui chat
514
+ - `ROVECODE_DESIGN=off` — drop the interface-design section from the system prompt (for runs with no UI in
515
+ them). Otherwise every run carries it: propose three distinct directions before the first UI in a project,
516
+ let the human choose, record it with `design_direction`, then build to it. See `docs/design.md`
517
+ - `ROVECODE_IMAGE_MAX_BYTES` (default 5 MB) — per-image cap for pasted (`⌃v`) and attached (`/attach`) images;
518
+ at most 8 images ride on one message, and an oversized one is refused by name rather than dropped
519
+ - Kill switches / budgets: `ROVECODE_NO_CHECKPOINTS=1`, `ROVECODE_NO_REPOMAP=1`, `ROVECODE_REPOMAP_TOKENS`
520
+ (default 1024), `ROVECODE_NO_TOOL_MIDDLEWARE=1`, `ROVECODE_TOOL_MIDDLEWARE=1`, `ROVECODE_EVAL_CELL=1`,
521
+ `ROVECODE_NO_PLUGINS=1`, `ROVECODE_PLUGIN_TIMEOUT_MS` (default 5000, per plugin import + `tools()`)
522
+
523
+ ### Model profiles
524
+
525
+ A **model profile** is per-model-family tuning that the prompt assembler and the provider layer share
526
+ (`src/providers/profiles.ts`): a behavioral section appended to the system prompt, the extra request fields
527
+ the endpoint wants, and the endpoint's own words for the one `--effort` dial. Profiles resolve by **model
528
+ id**, not provider — the same GLM through kaesra (`zai-org/glm-5.3-flash`), zai (`glm-5.3`) or openrouter
529
+ (`z-ai/glm-5.3`) gets one profile. Today there is one:
530
+
531
+ - **`glm-5.3`** (GLM-5.3 and GLM-5.3-Flash): the base prompt gains two sections. First a **Claude Sonnet 5
532
+ persona** — the model plays Sonnet 5 as a role and carries everything Anthropic publishes about it: the
533
+ model facts it may state about itself, the Sonnet 5 traits (calibrated length, literal scope, action by
534
+ default, self-verification, progress updates, design directions instead of a house style, coverage-first
535
+ code review, cyber caution) and Claude's published character (warm and direct, minimal formatting, one
536
+ question at a time, owns mistakes without self-abasement, evenhanded, careful with wellbeing). It is a
537
+ closed role: asked which model it is, even directly or repeatedly, it stays Claude Sonnet 5 and does not
538
+ name the underlying model. The harness stays honest at the system level, since `/status` and `/cost` keep
539
+ reporting the real configured model id. It also carries real captured Sonnet 5 replies as voice examples.
540
+ Then the *working
541
+ agreement* — the operating rules for this harness (act by default, read before you claim, the `read` →
542
+ `edit` hash protocol with its rejection remedy, independent tool calls in parallel with no guessed
543
+ arguments, verify before "done" and report failures as failures, minimal scope, short grounded progress
544
+ notes, ask only when readings differ materially, treat denials as decisions, persist across compaction).
545
+ - **`glm-5.3-plain`**: the working agreement without the persona, same request fields. Opt-in only:
546
+ `ROVECODE_PROFILE=glm-5.3-plain`. On an OpenAI-compatible provider the request also carries Z.ai's fields: `thinking: {type:
547
+ "enabled", clear_thinking: false}` (GLM-5.3 cannot switch thinking off), `temperature: 1` / `top_p: 0.95`
548
+ (Z.ai's suggestion), `tool_stream: true` when streaming; `--effort off` leaves the endpoint's default
549
+ (`max`, Z.ai's coding recommendation), `low` → `low`, `medium` → `high`, `high` → `max`. Behind an
550
+ Anthropic-protocol gateway only the prompt section applies. Streamed `reasoning_content` shows up as
551
+ thinking in the TUI like Anthropic's `thinking_delta`.
552
+
553
+ `ROVECODE_PROFILE=off` runs every model bare; `ROVECODE_PROFILE=glm-5.3` forces the contract's **prompt
554
+ section** onto any model (A/B it on something it was not written for) while the request fields keep
555
+ following the model id, so gpt-5 never receives `thinking` or `max`. `.rovecode/profiles/glm-5.3.md` (project) or
556
+ `~/.rovecode/profiles/glm-5.3.md` (user) **replaces** the built-in section text — edit, restart the run,
557
+ no rebuild; an empty file drops the section and keeps the wire tuning. The persona is a role, not a
558
+ relabeling: the harness keeps reporting the real model id in `/status` and `/cost`, and you can loosen the
559
+ closed role in the override file. Measure a change with `rovecode gauntlet
560
+ --live` before and after (pass count, tool calls, tokens per task).
561
+
562
+ ## Safety model (stacked, honest)
563
+
564
+ 1. **Policy** — deny-default wildcard rules, evaluated on revised args
565
+ 2. **execpolicy** — declarative per-command verdicts; `forbidden` never reaches execution or a human
566
+ 3. **Gate** — approvals resolved on revised args, cached; child agents cannot prompt
567
+ 4. **Runtime** — bash denylist + cwd lock + output truncation
568
+
569
+ This is **not an OS sandbox**. Where `bash` runs is selectable (#10 seam + #27 config):
570
+ `.rovecode/sandbox.json` `{"rung": "direct"|"wsl"|"docker", "dockerImage"?: "…"}`, or `ROVECODE_SANDBOX=<rung>`
571
+ (+ `ROVECODE_SANDBOX_IMAGE`; env beats file; default `direct`). Every rung is **delegation, not isolation**:
572
+ `direct` is in-process bash with the denylist + cwd lock; `wsl` runs each command through `wsl.exe` in the
573
+ default distro, which must contain bash (Docker Desktop's `docker-desktop` distro has none — set a real
574
+ distro as default); `docker` runs each command in `docker run --rm -v <cwd>:/workspace <image>` and needs a
575
+ running daemon plus an image with bash (default `debian:stable-slim`). A configured rung is probed at boot
576
+ by a 500 ms trial through its own wrapper (`wsl.exe --exec bash -c true` / `docker run --rm <image> bash -c
577
+ true`); an unavailable rung is a one-line startup error on every entrypoint (`run`/TUI/`--plain` exit 2,
578
+ `serve` 503, `acp` JSON-RPC error) — never a silent fallback to `direct`. A cold WSL utility VM can exceed
579
+ the cap and report unavailable: warm it (`wsl.exe --exec bash -c true`) and retry. `/status` shows the
580
+ active rung. Use a container/microVM for untrusted work.
581
+
582
+ ## Observability
583
+
584
+ **OTel spans** (#39, `telemetry/otel.ts`): set `ROVECODE_OTEL_ENDPOINT` and every run exports one trace as
585
+ OTLP/HTTP JSON — `rovecode.run` ⊃ `rovecode.turn` (one per model step) ⊃ `rovecode.tool`, with per-span tokens, latency,
586
+ served model and cost (omitted when unpriced), compaction and never-dispatched calls as span events; ids, sizes
587
+ and outcomes only (no goal, args, output or headers). Batched once per run, 5 s timeout, a failed export is one
588
+ `hooks:` warning and never blocks a run. Cancelled runs export too (Esc, `session/cancel`, HTTP DELETE or a
589
+ client disconnect → status `stopped`); `rovecode.tool_calls` counts issued calls, the `--output json` `toolCalls`
590
+ count — both per issuing turn, so a call id a provider reuses across turns counts once per turn; the one gap is a
591
+ run aborted while a call awaited approval after its `pre_tool` hook (a span, never an event). Unset = zero cost:
592
+ the exporter is never constructed; a malformed endpoint is one warning, not a stall.
593
+
594
+ Typed `RunEvent` stream (run/turn/tool/compaction events) persisted with the session tree;
595
+ `rovecode trace <id>` replays any session with corruption findings. `/cost` and `/status` surface
596
+ tokens, cache hits, and catalog-priced spend.
597
+
598
+ ## Known limitations
599
+
600
+ - **Cancellation is mid-turn; how far the kill reaches is platform-specific.** Esc/`session/cancel`/HTTP
601
+ DELETE abort the run's controller: the in-flight provider fetch dies (≤2 ms measured) and the running
602
+ `bash` call is killed. Windows: the launcher is placed in a kernel Job Object right after spawn, so the
603
+ abort terminates the whole tree — compound, nested `bash -c`, and backgrounded children included —
604
+ with `taskkill /T /F` as a sweep; a box without `bun:ffi`/kernel32 job objects falls back to taskkill
605
+ alone, which reaches the shell but not msys children whose forked stub already exited. POSIX: the shell
606
+ gets SIGTERM and never runs its next statement, but a forked grandchild (`sleep`, `npm`, `python` inside
607
+ a compound command) is orphaned and finishes on its own — no process-group kill yet. Never reached: work
608
+ already handed to another process tree (a container started by the `docker` rung outlives its
609
+ `docker run` client; a WSL-side process may outlive `wsl.exe`; services, COM- or `schtasks`-launched
610
+ programs). A command that completes on its own keeps its deliberately backgrounded daemon
611
+ (`server > log 2>&1 &`), as before. The runner always settles within ~0.5 s of the abort, even one that
612
+ lands after the launcher already exited while an unredirected child it left behind (`sleep 600 & echo
613
+ started`) still holds a pipe end — the job kill reaches that child (measured 3 ms; exit 143); an orphan
614
+ outside the tree that keeps a pipe end open past the kill yields output so far + `[output truncated:
615
+ process tree terminated on abort]`, exit 143.
616
+ - **Sandbox rungs delegate, they do not isolate.** `wsl`/`docker` (#27) isolate only as well as the
617
+ wrapped runtime does; `direct` (the default) is denylist + cwd lock. The executor seam is process-wide:
618
+ `rovecode serve`/`rovecode acp` sessions booted from different project dirs share the most recently booted
619
+ session's rung. The offline `rovecode gauntlet` always runs `direct` (it never builds a runtime); `gauntlet --live`
620
+ boots the runtime and honors the configured rung like any other run.
621
+ - **ACP is the one surface that mixes cwds.** `rovecode acp` boots a runtime per `session/new` cwd on that
622
+ process-wide seam: a `session/new` REFUSED because its cwd asks for a rung this machine cannot provide
623
+ (JSON-RPC error) still leaves the seam holding that unmet rung, so existing sessions' `bash` calls fail
624
+ with the rung error until a later `session/new` boots successfully. Keep one editor window per project,
625
+ or every project on the same rung.
626
+ - **Plan/Act modes are TUI-scoped.** `run`/`acp`/`serve` ignore `.rovecode/modes.json` including
627
+ `defaultMode`.
628
+ - **Server sessions are in-memory.** `rovecode serve` loses its session routing table on restart
629
+ (JSONL trees persist on disk).
630
+ - **Windows-first, Linux-checked.** Developed on Windows 11 + Git Bash, where the suite, the gauntlet and
631
+ the render smoke are run by hand before anything lands. CI (`.github/workflows/ci.yml`) runs `tsc` and the
632
+ full suite on `ubuntu-latest` for every push and pull request, so POSIX paths are gated rather than merely
633
+ exercised. **macOS is not verified anywhere** — nothing runs there, by CI or by hand.
634
+ - **Packaging**: not published to npm; compiled binary is ~110 MB (bun runtime).
635
+
636
+ ## Extending
637
+
638
+ - **Plugins** (`src/plugins`, `docs/plugins.md`): a folder with a `plugin.json` that bundles the things below —
639
+ an in-process entry module (tools + hooks), `commands/*.md`, `skills/**/SKILL.md`, MCP servers — so one
640
+ `rovecode plugin add <folder|git-url>` installs all of it and `rovecode plugin list` shows all of it. User scope
641
+ `~/.rovecode/plugins/<name>`; project scope `.rovecode/plugins/<name>` is **listed but never run** until
642
+ `rovecode plugin trust <name>` records the folder's content digest in your home (a repo cannot trust itself; a
643
+ pull that changes any file asks again). Plugin tools go through the same `ToolRegistry` and permission rules as
644
+ built-ins (a plugin cannot replace `bash`); plugin hooks join the same `HookRunner`. Read once per process;
645
+ `ROVECODE_NO_PLUGINS=1` skips discovery. First-party plugins live in `plugins/` (safety-net · notes ·
646
+ conventional-commits).
647
+ - **Tool**: implement `Tool` (schema + kind + execute), `registry.register(t)`; kind maps to a policy action.
648
+ - **Provider**: implement `StreamFn` — must not throw; failures become `{stopReason: "error"}`.
649
+ - **Hooks** (`core/hooks.ts`, port #29): drop a `.rovecode/hooks.ts` (or `.js`; user scope `~/.rovecode/hooks.*`)
650
+ exporting `{ version: 1, hooks: {…} }` — plain `import`, no build step. Nine typed hooks, all optional,
651
+ sync or async: `pre_run`, `post_run` (also fired, as `stopped`, when the consumer cancels a run mid-way),
652
+ `pre_tool` (return `{deny: reason}` to block), `post_tool` (return
653
+ `{output}` to annotate what the model sees, growth-bounded), `approval` (return `"allow"`/`"deny"` to
654
+ pre-answer a prompt), `compaction`, `session_open`, `session_close`, `on_event` (every RunEvent, not
655
+ awaited). Every call is timeout-bounded (`ROVECODE_HOOK_TIMEOUT_MS`, default 5000) and isolated — a throwing
656
+ or hanging hook is one warning note, never a dead run. Policy wins: rules run before `pre_tool` (a hook
657
+ can only deny, in every mode incl. auto (`--yolo`), and the same hooks govern background-task children); the
658
+ approval hook sits where the human would, INSIDE the execpolicy wrap (rules → execpolicy → hook →
659
+ human): forbidden argv is denied before any hook sees it, allow-listed argv runs without asking one, and
660
+ a hook `"allow"` is exactly a human's one-shot yes (never cached). Hooks receive copies of args and
661
+ results — only a returned value counts. Hooks are trusted code run in-process (same class as
662
+ `.rovecode/mcp.json`); loaded once per process, restart to pick up edits; `ROVECODE_NO_HOOKS=1` skips the files.
663
+ The programmatic `ExtensionHooks.reviseToolArgs` still rewrites args before policy + approval (approval
664
+ sees revised args).
665
+ - **MCP** (`src/mcp`, `docs/mcp-market.md`): `rovecode mcp search|info|add|remove|list|show|trust|untrust` and `/mcp`
666
+ in the TUI install servers from a curated shelf and the official registry — the exact command/URL, publisher and
667
+ version are shown before a yes, keys are asked masked by name and written as values only to `~/.rovecode/mcp.json`
668
+ (a `--project` file gets `${NAME}`). Servers live in `~/.rovecode/mcp.json` < `.mcp.json` < `.rovecode/mcp.json`;
669
+ tools arrive lazily through `mcp_list`/`mcp_call` under the same policy pipeline. A server a **repository** brings
670
+ with it is listed but never connected until you trust it: `mcp show` prints every configured file and what it would
671
+ run, `mcp trust` records the project files' content digest in your home (`--yes` to skip the prompt; an edit to
672
+ either file asks again) and `mcp untrust` revokes it. User-scope servers need no gate. `rovecode trust` is the same
673
+ store one level up: it approves the MCP files together with `hooks.ts`, `sandbox.json` and the command-bearing
674
+ settings keys in one step (see Project trust above).
675
+ - **Market** (`src/market`, `docs/market.md`): one shelf over all three — `rovecode market search|info|docs|
676
+ install|remove|list|update|sources|verify|validate` (each with `--json`) and `/market` in the TUI find MCP
677
+ servers, skills and plugins
678
+ and install any of them with one command. A single argument resolves five shapes (bare id, `kind:id`, a git URL,
679
+ an npm package, a local folder) and prints the candidates rather than guessing when two kinds share a name.
680
+ Installing is always resolve → plan without touching the disk → show exactly what will be written → write, and
681
+ the preview says what each kind actually is: a skill is files that are never executed, a plugin is code rovecode
682
+ will load and run. The skill and plugin shelves are generated from their sources (`scripts/build-*-catalog.mjs`,
683
+ idempotent under `--check`), so "is this real?" is answered by re-running them.
684
+ - **Context and cost** (`src/core/context-report.ts`, `docs/context.md`): `rovecode context` breaks the window into
685
+ the rows a reader thinks in and prints the provider's own count of the same prompt beside our estimate, naming
686
+ the gap past 5% — compaction fires on the estimate, so a meter that reads low compacts too late. Cache reads and
687
+ writes are counted as the prompt they are. Cost follows the vendors' prompt-size tiers: over xAI's or Google's
688
+ 200k threshold the whole request bills at the upper rate. The history budget is derived from the model's window
689
+ rather than a flat 200k (`ROVECODE_CONTEXT_BUDGET` overrides).
690
+ - **Interface design** (`src/design`, `docs/design.md`): no default palette, typeface or layout ships — instead a
691
+ protocol (propose three distinct directions, the human picks, `design_direction` records it in
692
+ `.rovecode/design.json`) and `design_audit`, which counts template patterns in source and reports them as
693
+ *slop* only while nothing is recorded, or as *deviation* from what the project chose. `ROVECODE_DESIGN=off`
694
+ drops the section for runs with no UI in them.
695
+ - **Site** ([9Code-Labs/rovecode-site](https://github.com/9Code-Labs/rovecode-site), `docs/deploy.md`):
696
+ the landing page, the docs and the market — Vite + React, prerendered once per language, no runtime.
697
+ It moved out of this repository on 2026-09-06 and builds without reading anything outside itself.
698
+ What stays here is the content it renders: `bun run publish:site` regenerates `docs.json`,
699
+ `market.json` and `facts.json` from `docs/*.md`, `src/market/catalogs/`, `src/mcp/market-catalog.ts`
700
+ and `plugins/`, and pushes them there. Deploying is `bun run deploy` in that repo. Live at
701
+ [64.177.43.110](http://64.177.43.110/) until there is a domain.
702
+
703
+ ## License & notices
704
+
705
+ Rovecode is free software under the GNU Affero General Public License v3.0 — see `LICENSE`.
706
+ Copyright (C) 2026 9Code Labs. You may use, study, modify and redistribute it; every copy and every
707
+ derivative must keep this license and its copyright notices, and if you run a modified rovecode as a
708
+ network service you must offer its complete source to the users of that service.
709
+
710
+ Third-party attributions (Apache-2.0 NOTICE entries + MIT credits): `THIRD_PARTY_NOTICES.md`
711
+ (shipped in the npm tarball). No code from crush (FSL), claw-code, nanocoder, iflow, or the Claude
712
+ Agent SDK.
713
+
714
+ ## What wave 3 added (`PORTS.md` §Wave-3)
715
+
716
+ Landed, not planned. Ports #21–#39: mid-turn cancellation, first-class grep/glob/ls tools,
717
+ retry-with-backoff, approval diff previews, compaction v2, background subagents, sandbox rung config,
718
+ reflection retries, hooks v2, custom slash commands, web fetch, todo/ask_user tools, image input,
719
+ JSON/NDJSON output modes, session export, OTel spans.
720
+
721
+ ### Not built
722
+
723
+ - **#47 external agentic-CLI lanes** — the one wave-4 row that was never implemented here. Measured
724
+ 2026-09-06 rather than assumed: `claude -p` and `opencode run` already work through the `bash` tool
725
+ today, in print mode, without a TTY. What makes a lane a real feature rather than a shortcut is the
726
+ finding that came with it — **the child agent obeys its own permission configuration, not rovecode's.**
727
+ A `claude -p "create a file"` spawned from a rovecode run under `auto` created the file, because that
728
+ machine's `~/.claude/settings.json` sets `bypassPermissions`; rovecode's approval gate saw one `bash`
729
+ call and never saw a write. Under `ask` or `accept-edits` the bash call is refused first, so the hole is
730
+ exactly as wide as unattended `bash` — but a nested agent turns one approved command into an
731
+ unsupervised multi-step agent, under someone else's write policy, billing a different account. A lane
732
+ therefore has to run the child in a directory the approver saw, pass no `--dangerously-*` flag ever,
733
+ and name the account it spends from. (`opencode` also writes `.opencode/` and `docs/` into the working
734
+ directory even when it fails, and on Windows a nested shell layer ate the backslashes of an absolute
735
+ path and produced a file literally named `C:UsersberkaycikAppData…banana.txt`.)
736
+ - **Publishing**: no npm package; the binary is built locally (see Known limitations)
737
+ - **Linux/macOS CI**: POSIX paths are exercised in tests, but only Windows is gated