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
@@ -0,0 +1,184 @@
1
+ /** Workspace roots + the `file.external` boundary (ported from the Nimbus harness, PORT #81).
2
+ *
3
+ * Until this port nothing locked the file tools to the cwd: read / edit / write / glob / grep / ls open any
4
+ * absolute path verbatim (coding/hashline.ts, coding/files.ts, coding/diff.ts resolvePath) and the gated rule set
5
+ * says `file.read * allow` — so an absolute path outside the project was read silently. This module INTRODUCES
6
+ * the boundary and `--add-dir <dir>` widens it:
7
+ * - the cwd (ToolContext.cwd — a task child's own dir for children) is the implicit root; every `--add-dir`
8
+ * value is an extra root, canonicalised (realpath through the deepest existing ancestor: junction, 8.3 and
9
+ * case spellings collapse), deduplicated, and dropped with ONE note when it lies inside the cwd or inside
10
+ * another root; a value that is not a directory, or is a filesystem root (`C:\`, `/` — it would disable the
11
+ * boundary), is an error the surfaces turn into exit 2;
12
+ * - containment is `path.relative` (not a string prefix: `E:\proj2\x` is OUTSIDE `E:\proj`; on win32 relative()
13
+ * compares case-insensitively, so a non-existent tail keeps working), both sides canonical — a junction inside
14
+ * the cwd that points outside is OUTSIDE, because its real target is;
15
+ * - the ONE ladder (core/tools.ts dispatch step 2a) evaluates the action `file.external` for a path-declared tool
16
+ * whose canonical resource is outside the cwd, with resource `<real containing dir>\*` (the parent dir of a
17
+ * file target, the dir itself when glob/grep/ls name an existing directory, `C:\*` for a drive root): the
18
+ * built-in gated set says `file.external * prompt`, yolo's `* * allow` covers it, the roots enter as
19
+ * `allow file.external <root>\*` rules after the built-in set; the prompt names the path, the cwd, the roots
20
+ * that exist and the remedy — never a bare denial;
21
+ * - what a tool OPENS is what the ladder JUDGED: toolPath() below is the ONE spelling rule — `path.resolve`
22
+ * against the cwd, so `.` / `..` collapse LEXICALLY before any filesystem lookup — shared by core/tools.ts
23
+ * describeResource and the read / edit / write / glob / grep / ls tools and the diff preview. A
24
+ * `<cwd>/<symlink>/../<outside>` spelling therefore names `<cwd>/<outside>` on BOTH sides: the symlink is never
25
+ * handed to the kernel (a POSIX kernel applies `..` to the link's TARGET; Win32 collapses it lexically), so a
26
+ * lexical INSIDE verdict can no longer open an OUTSIDE file. The direct `<cwd>/<symlink>/<file>` spelling
27
+ * stays OUTSIDE via realpath.
28
+ * Pure over the filesystem: no settings knob, no env — a repo must never widen its own roots.
29
+ *
30
+ * What the boundary does NOT cover, said plainly: `bash` is not path-declared, so `cd ../other && …` is judged
31
+ * as a shell command (its own rules), not as a path; the executor's cwd lock is what bash has. And checkpoints
32
+ * (coding/checkpoints.ts) are ONE shadow repository whose work-tree is the cwd: a change under an added root is
33
+ * not snapshotted and /restore does not undo it — WorkspaceRoots.checkpointNote() says so at boot and in
34
+ * `rovecode doctor` rather than letting one root look whole. Spanning roots means one shadow repo per root; not
35
+ * built here.
36
+ *
37
+ * Pattern sources (pattern level, no code copied):
38
+ * - opencode (MIT): `packages/opencode/src/tool/external-directory.ts` :15-45 (a target outside the instance
39
+ * directory asks an `external_directory` permission whose pattern AND `always` grant is `path.join(dir, "*")`),
40
+ * `packages/core/src/fs-util.ts` :270-273 contains() (`relative` result not absolute / not `..`-prefixed),
41
+ * `packages/opencode/src/project/instance-context.ts` :18-24 containsPath (a `/` worktree is never a boundary);
42
+ * - gemini-cli (Apache-2.0): `packages/core/src/utils/workspaceContext.ts` :262-272 isPathWithinRoot (the same
43
+ * relative() rule over fully-resolved paths), and the `--include-directories` flag name; Claude Code's
44
+ * `--add-dir` is a flag-name mention only (proprietary). */
45
+
46
+ import { realpathSync, statSync } from "node:fs";
47
+ import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
48
+ import type { PermissionRule } from "./types.ts";
49
+
50
+ /** the ladder action a path-declared tool's call outside the workspace is evaluated under */
51
+ export const EXTERNAL_ACTION = "file.external";
52
+
53
+ /** The canonical spelling of a path: its real path (symlinks, junctions, 8.3 short names and drive-letter case
54
+ * resolved). A path that does not (or no longer) exist keys as the real path of its deepest existing ancestor plus
55
+ * the missing tail — so a file the model is about to CREATE inside a junction is still judged by where the junction
56
+ * really points. (The Nimbus tree kept this in core/trust.ts as trustKey; rovecode has no trust store, so it lives
57
+ * here — the one canonicalisation for roots, resources and grants.) */
58
+ export function canonical(p: string): string {
59
+ const abs = resolve(p);
60
+ try { return realpathSync.native(abs); } catch { /* not on disk: resolve through the existing ancestors below */ }
61
+ const parent = dirname(abs);
62
+ return parent === abs ? abs : join(canonical(parent), basename(abs));
63
+ }
64
+
65
+ /** The ONE spelling a `path` argument has for BOTH the ladder (core/tools.ts describeResource) and the tool that opens it
66
+ * (coding/hashline.ts read / edit / write, coding/files.ts glob / grep / ls, coding/diff.ts previewDiff): `path.resolve`
67
+ * against the cwd — absolute, with `.` / `..` / doubled and mixed separators collapsed LEXICALLY before any filesystem lookup
68
+ * and a trailing separator dropped. On win32 a rooted spelling without a drive (`/workspace/x`) resolves on the PROCESS's
69
+ * current drive — that is where node:fs opens it, so it is judged there too (a posix carve-out would let `/proj/.env` dodge an
70
+ * absolute deny). The lexical collapse is the point: `<cwd>/<symlink>/../<outside>` names `<cwd>/<outside>` here, so the
71
+ * symlink never reaches the kernel and the tool opens exactly the path policy judged. */
72
+ export function toolPath(cwd: string, p: string): string {
73
+ return resolve(isAbsolute(p) ? p : join(cwd, p));
74
+ }
75
+
76
+ /** is `p` inside `root` (or root itself)? Both CANONICAL. `path.relative`, not a prefix test: `E:\proj2` is not inside `E:\proj`;
77
+ * on win32 relative() is case-insensitive (opencode fs-util contains(); gemini-cli isPathWithinRoot) */
78
+ export function isInside(root: string, p: string): boolean {
79
+ const r = relative(root, p);
80
+ return r === "" || (!isAbsolute(r) && r !== ".." && !r.startsWith(`..${sep}`));
81
+ }
82
+
83
+ /** is the canonical form of `resource` outside the canonical form of `cwd`? (the dispatch gate) */
84
+ export function outsideWorkspace(cwd: string, resource: string): boolean { return !isInside(canonical(cwd), canonical(resource)); }
85
+
86
+ function isDirectory(p: string): boolean { try { return statSync(p).isDirectory(); } catch { return false; } }
87
+
88
+ /** the `file.external` resource for a path resource: `<canonical containing dir>\*` — the dir itself when the target is an
89
+ * existing directory (glob / grep / ls), else its parent; a drive / filesystem root gives `C:\*` / `/*` (opencode's
90
+ * `path.join(dir, "*")` pattern). The glob is the SAVED / CACHED shape too, so a second file in that dir matches it. */
91
+ export function externalResource(resource: string): string {
92
+ const c = canonical(resource);
93
+ return join(isDirectory(c) ? c : dirname(c), "*");
94
+ }
95
+
96
+ /** the ONE prompt text for a call outside the workspace: names the path, the cwd, the roots the ladder already allows
97
+ * (its `allow file.external <dir>\*` rules — `--add-dir` roots and grants) and the remedy. Never a bare denial. */
98
+ export function outsidePrompt(resource: string, cwd: string, rules: readonly PermissionRule[]): string {
99
+ const roots = rules.filter((r) => r.action === EXTERNAL_ACTION && r.effect === "allow" && r.resource !== "*").map((r) => r.resource);
100
+ return `${resource} is outside the workspace ${cwd}${roots.length > 0 ? ` and its roots ${roots.join(", ")}` : ""} — allow once/always for this directory, or start rovecode with --add-dir <dir> to make it a workspace root`;
101
+ }
102
+
103
+ /** why `abs` cannot be a workspace root (`spelled` = the value as the user typed it), or undefined when it can */
104
+ export function rootProblem(abs: string, spelled: string): string | undefined {
105
+ // a `*` in the name (POSIX allows it) would be a ladder wildcard in the root's `allow file.external <dir>\*` rule; checked on
106
+ // the spelling first (before the stat) and again on the real path (a link whose target's name holds `*`)
107
+ const wildcard = `--add-dir "${spelled}": the directory name contains "*", a rule wildcard (no escape syntax) — its allow rule would match more than that directory`;
108
+ if (abs.includes("*")) return wildcard;
109
+ if (!isDirectory(abs)) return `--add-dir "${spelled}" is not a directory`;
110
+ const c = canonical(abs);
111
+ if (c.includes("*")) return wildcard;
112
+ if (parse(c).root === c) return `--add-dir "${spelled}" is a filesystem root — it would disable the workspace boundary — pass --yolo instead`;
113
+ return undefined;
114
+ }
115
+
116
+ export interface ResolvedRoots { dirs: string[]; notes: string[] }
117
+
118
+ /** thrown by resolveRoots for a value that cannot be a root (cli/runtime.ts turns it into a startup error → exit 2) */
119
+ export class WorkspaceRootError extends Error {
120
+ constructor(message: string) { super(message); this.name = "WorkspaceRootError"; }
121
+ }
122
+
123
+ /** Canonicalise `addDirs` (each resolved against `base` — the LAUNCH dir) into the extra roots: duplicates, a dir inside the
124
+ * cwd and a dir inside another root are dropped (a later root that CONTAINS an earlier one replaces it) with ONE note line;
125
+ * a non-directory or a filesystem root throws WorkspaceRootError. */
126
+ export function resolveRoots(cwd: string, addDirs: readonly string[] = [], base: string = process.cwd()): ResolvedRoots {
127
+ const home = canonical(cwd);
128
+ const dirs: string[] = [], dropped: string[] = [];
129
+ for (const spelled of addDirs) {
130
+ const abs = resolve(base, spelled);
131
+ const problem = rootProblem(abs, spelled);
132
+ if (problem !== undefined) throw new WorkspaceRootError(problem);
133
+ const c = canonical(abs);
134
+ if (isInside(home, c) || dirs.some((d) => isInside(d, c))) { dropped.push(spelled); continue; }
135
+ for (const d of dirs) if (isInside(c, d)) dropped.push(d); // the wider root replaces the narrower one
136
+ dirs.splice(0, dirs.length, ...dirs.filter((d) => !isInside(c, d)), c);
137
+ }
138
+ const notes = dropped.length === 0 ? [] : [`rovecode: --add-dir: ${dropped.length} value${dropped.length === 1 ? "" : "s"} dropped — already inside the workspace ${home} or another root: ${dropped.join(", ")}`];
139
+ return { dirs, notes };
140
+ }
141
+
142
+ /** The roots a runtime carries: canonical cwd + extra dirs, their ladder rules and the banner / prompt spellings. */
143
+ export class WorkspaceRoots {
144
+ readonly cwd: string;
145
+ readonly dirs: readonly string[];
146
+ readonly notes: readonly string[];
147
+
148
+ constructor(cwd: string, resolved: ResolvedRoots = { dirs: [], notes: [] }) {
149
+ this.cwd = canonical(cwd);
150
+ this.dirs = resolved.dirs;
151
+ this.notes = resolved.notes;
152
+ }
153
+
154
+ /** the extra root containing `abs` (canonical compare), or undefined — cwd is not a root here (callers fall back to it) */
155
+ rootOf(abs: string): string | undefined {
156
+ const c = canonical(abs);
157
+ return this.dirs.find((d) => isInside(d, c));
158
+ }
159
+
160
+ /** `allow file.external <root>\*` per extra root — the ladder layer right after the built-in set */
161
+ rules(): PermissionRule[] {
162
+ return this.dirs.map((d) => ({ action: EXTERNAL_ACTION, resource: join(d, "*"), effect: "allow" as const }));
163
+ }
164
+
165
+ /** `allow file.write <root>\*` per extra root — what accept-edits adds for the cwd, extended to every root, so a root is
166
+ * visible everywhere a permission decision is made (the cwd's own rule stays where runtime.ts puts it) */
167
+ acceptEditsRules(): PermissionRule[] {
168
+ return this.dirs.map((d) => ({ action: "file.write", resource: join(d, "*"), effect: "allow" as const }));
169
+ }
170
+
171
+ /** the banner spelling: `+<dir> +<dir>` ("" without roots) */
172
+ describe(): string { return this.dirs.map((d) => `+${d}`).join(" "); }
173
+
174
+ /** the system-prompt sentence; "" without roots (the prompt stays byte-identical) */
175
+ promptLine(): string {
176
+ return this.dirs.length === 0 ? "" : `\n\nAdditional workspace roots (name them by absolute path): ${this.dirs.join(", ")}`;
177
+ }
178
+
179
+ /** the limit, named: checkpoints (coding/checkpoints.ts) snapshot the cwd only. "" without roots. Shown once at boot on every
180
+ * surface and as the doctor row's detail — a root that looks checkpointed and is not would be the silent kind of wrong. */
181
+ checkpointNote(): string {
182
+ return this.dirs.length === 0 ? "" : `roots: ${this.describe()} — checkpoints cover ${this.cwd} only; a change under an added root is not snapshotted and /restore does not undo it`;
183
+ }
184
+ }