feat(workspace): add rootResolution setting for multi-root workspaces#538
Conversation
Introduces a new zoo-code.workspace.rootResolution VS Code setting with two strategies: - activeEditor (default): legacy behavior - prefer the workspace folder containing the active editor, fall back to workspaceFolders[0]. - firstFolder: deterministic - always use workspaceFolders[0], independent of the focused file. Updates getWorkspacePath() and getWorkspacePathForContext() in src/utils/path.ts to honor the setting, registers it in src/package.json with localized strings in src/package.nls.json, expands src/utils/__tests__/path.spec.ts with focused unit tests for both strategies, and adds a design doc at docs/design/workspace-root-resolution.md. Ported from simurg79/Roo-Code commit d89c1ff (settings namespace adapted roo-cline -> zoo-code).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR adds a configurable ChangesWorkspace Root Resolution Feature
🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Adds the three settings.workspace.rootResolution.* keys to all 17 non-English package.nls locale files to fix the check-translations CI check.
edelauna
left a comment
There was a problem hiding this comment.
Nice! Thanks for adding this, had a couple edge case comments.
| "default": "activeEditor", | ||
| "description": "%settings.workspace.rootResolution.description%", | ||
| "markdownDescription": "%settings.workspace.rootResolution.description%" | ||
| } |
There was a problem hiding this comment.
Without an explicit "scope" this defaults to "window", which means a committed .vscode/settings.json can override it. Since this setting controls which workspace folder is used to locate .roo/mcp.json (and therefore which MCP servers get registered), could a malicious workspace settings file redirect that to an attacker-controlled folder in a multi-root workspace?
If workspace-level override should be blocked, "scope": "machine" would prevent it. If it is intentional, worth documenting the trust decision here.
There was a problem hiding this comment.
Good catch — this setting feeds MCP-server discovery, so I've set "scope": "machine" to prevent a committed workspace settings.json from redirecting the resolved root. This intentionally disallows workspace-level overrides (the security tradeoff is deliberate).
| // `Package` here (path.ts is a low-level utility and we want to keep | ||
| // it free of circular dependencies on shared/* and package.json). | ||
| const value = vscode.workspace | ||
| .getConfiguration("zoo-code") |
There was a problem hiding this comment.
Every other getConfiguration call in the codebase uses Package.name to respect the PKG_NAME build override. If this extension is ever built under a different package name, getConfiguration("zoo-code") will silently return only the supplied defaults — no error, no warning. Is the circular-dependency concern in the comment above actually verified? shared/package.ts only imports from ../package.json and doesn't look like it touches path.ts.
There was a problem hiding this comment.
You're right — shared/package.ts only imports package.json, so there's no cycle. Switched to getConfiguration(Package.name) so it honors the PKG_NAME build override, and removed the inaccurate comment.
| @@ -63,6 +108,155 @@ describe("Path Utilities", () => { | |||
| }) | |||
|
|
|||
| it("should return undefined when outside a workspace", () => {}) | |||
There was a problem hiding this comment.
This test body is empty and the description looks wrong — getWorkspacePath always returns a string, never undefined. Was this meant to be filled in?
There was a problem hiding this comment.
Removed — it was a leftover stub with an incorrect description (getWorkspacePath always returns a string). The no-workspace path is already covered by the falls back to defaultCwdPath cases.
| }) | ||
|
|
||
| it("falls back to default behavior when reading the setting throws", () => { | ||
| mockWorkspace.getConfiguration = () => { |
There was a problem hiding this comment.
getConfiguration is mutated here directly but withWorkspaceMock's cleanup (lines 66–70) only restores workspaceFolders, getWorkspaceFolder, and activeTextEditor. Recovery depends on the outer beforeEach running setRootResolution(undefined) next. If this test is ever moved or the beforeEach changes, subsequent tests will throw on any config read. Worth saving/restoring getConfiguration in the finally block here, or extending withWorkspaceMock to cover it.
There was a problem hiding this comment.
Done — withWorkspaceMock now snapshots and restores getConfiguration too, and the throwing-config test routes its mock through it, so a broken mock can't leak into sibling tests regardless of beforeEach.
| } finally { | ||
| restore() | ||
| } | ||
| }) |
There was a problem hiding this comment.
What happens in activeEditor mode when contextPath is provided but getWorkspaceFolder returns null? That fallback path (the console.debug branch in path.ts:183–186) doesn't appear to be covered here.
There was a problem hiding this comment.
Added a test for that case (activeEditor + contextPath set + getWorkspaceFolder → null); it covers the console.debug fallback branch and asserts it falls back to workspaceFolders[0].
There was a problem hiding this comment.
lets not commit this, this is a working file for you.
There was a problem hiding this comment.
Removed from the PR — that was a working scratch doc, not intended to ship.
… use Package.name, test mock cleanup + coverage, drop scratch design doc
edelauna
left a comment
There was a problem hiding this comment.
Thanks for the contribution, this is a cool feature for multiple folders within a workspace.
Summary
Ports the workspace root resolution feature from
simurg79/Roo-Code(commitd89c1ff) into Zoo-Code.Introduces a new
zoo-code.workspace.rootResolutionVS Code setting that controls how Zoo resolves the "project root" in a multi-root workspace (used to locate.roomodes,.roo/mcp.json,.roo/rules/, and other project-scoped configuration):activeEditor(default)workspaceFolders[0]. Preserves legacy behavior.firstFolderworkspaceFolders[0]. Deterministic — independent of which file is focused.The default preserves today's behavior exactly, so this is a non-breaking, opt-in change.
Motivation
The historic active-editor heuristic is convenient but non-deterministic: switching the focused file between roots silently changes which project config is picked up, and it can disagree with the marketplace installers (which always target
workspaceFolders[0]).firstFoldergives a stable root for CI/automation and multi-root setups where only one folder is the real project.Changes
src/utils/path.ts: addWorkspaceRootResolutiontype + exception-safegetRootResolutionStrategy()helper;getWorkspacePath()andgetWorkspacePathForContext()now honor the setting.src/package.json: registerzoo-code.workspace.rootResolution.src/package.nls.json: localized setting strings.src/utils/__tests__/path.spec.ts: expanded coverage for both strategies, exception safety, andgetWorkspacePathForContext.docs/design/workspace-root-resolution.md: design doc.Port notes
roo-cline.*to Zoo-Code'szoo-code.*.vscodemock viaresolve.alias(mutation-based) to match Zoo-Code'ssrc/vitest.config.ts.Testing
cd src && npx vitest run utils/__tests__/path.spec.ts→ 30 passed. Both modified JSON files validated as parseable. Pre-commit lint hooks passed.Summary by CodeRabbit