Improved rovodev support#1377
Conversation
Rovodev uses ruleDiscoveryMode "toon" like agentsmd and factorydroid but had no additionalConventions entry. Wire RovodevSubagent and RovodevSkill so synced AGENTS.md lists subagent and skill paths (no commands target). Clarify JSDoc: additional conventions embed paths for native features under toon discovery, not only simulated features.
typeof [] === "object", so parsed JSON could mis-classify an array as McpServers. Require a non-array object before tool MCP layers merge mcpServers. Add unit tests for the type guard.
…rn type Declare RovodevSubagent instead of ToolSubagent so call sites match RovodevSkill/RovodevMcp factory patterns and tests no longer need casts.
Stop spreading the full parsed Rovodev mcp.json into rulesync so unknown top-level keys (present or future) are not imported into .rulesync/mcp.json. Aligns with Roo/Deepagents; Cursor intentionally keeps extra keys. Add a regression test for ignored Rovodev-only top-level properties.
Extract parseRovodevMcpJson for constructor, fromFile, and fromRulesyncMcp so parse error messages stay consistent. Validate the parsed root value is a plain object via isRecord (arrays/scalars now fail with a clear error).
…agent.validate frontmatter is always assigned in the constructor, so the empty check never ran.
Align with Atlassian docs: project memory under .rovodev/AGENTS.md with a ./AGENTS.md mirror; localRoot -> AGENTS.local.md; global rules at ~/.rovodev/AGENTS.md. Import skills from .rovodev/skills and .agents/skills with primary-root precedence; emit skills to .rovodev/skills only; deletion enumerates every skill root. Document subagent dirs (.rovodev/subagents and ~/.rovodev/subagents). Mark Rovodev rules as supporting global mode in the supported-tools table and refresh file-formats localRoot notes.
This comment has been minimized.
This comment has been minimized.
|
The main issues are:
|
There was a problem hiding this comment.
Pull request overview
Updates Rulesync’s Rovodev integration to better match current Rovo Dev CLI behavior and addresses the review findings tracked in #1375.
Changes:
- Hardened MCP parsing/guards and reduced duplication; prevented Rovodev MCP tool-specific keys from leaking into Rulesync MCP.
- Improved Rovodev rules behavior (global support, AGENTS.local.md support, mirroring
.rovodev/AGENTS.mdto./AGENTS.md, deletion handling). - Added multi-root skill import support for Rovodev (
.rovodev/skillsprimary,.agents/skillsfallback) plus documentation updates.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types/mcp.ts | Hardens isMcpServers to exclude arrays. |
| src/types/mcp.test.ts | Adds unit coverage for isMcpServers. |
| src/features/subagents/rovodev-subagent.ts | Improves Rovodev subagent docs; fixes return type; removes dead validation branch. |
| src/features/subagents/rovodev-subagent.test.ts | Removes now-unnecessary casts after return type fix. |
| src/features/skills/tool-skill.ts | Adds alternativeSkillRoots and helper for ordered root scanning. |
| src/features/skills/skills-processor.ts | Loads skills from multiple roots with precedence and dedupe; expands deletion scanning. |
| src/features/skills/skills-processor.test.ts | Tests Rovodev alternative root loading, precedence, and deletion listing. |
| src/features/skills/rovodev-skill.ts | Declares .agents/skills as an alternative import root; expands docs. |
| src/features/skills/rovodev-skill.test.ts | Tests settable paths and loading from .agents/skills. |
| src/features/rules/rules-processor.ts | Enables Rovodev global rules, adds additionalConventions metadata, adds AGENTS mirroring + AGENTS.local handling + deletion support. |
| src/features/rules/rules-processor.test.ts | Tests Rovodev global targets, AGENTS mirroring, and AGENTS.local deletion/generation behaviors. |
| src/features/rules/rovodev-rule.ts | Moves canonical root to .rovodev/AGENTS.md, adds project-root fallback, and maps AGENTS.local.md to localRoot. |
| src/features/rules/rovodev-rule.test.ts | Updates expectations for new Rovodev paths and localRoot mapping. |
| src/features/mcp/rovodev-mcp.ts | Centralizes JSON parsing/validation; ensures toRulesyncMcp only emits mcpServers. |
| src/features/mcp/rovodev-mcp.test.ts | Tests that unknown top-level keys don’t propagate into Rulesync MCP JSON. |
| skills/rulesync/supported-tools.md | Updates Rovodev support matrix to reflect global generation. |
| skills/rulesync/file-formats.md | Documents Rovodev localRoot output (AGENTS.local.md). |
| docs/reference/supported-tools.md | Mirrors supported-tools matrix update in docs. |
| docs/reference/file-formats.md | Mirrors localRoot documentation update in docs. |
| README.md | Updates supported tools table for Rovodev global generation. |
| throw new Error( | ||
| `Rovodev AGENTS.md must be at ${join(paths.root.relativeDirPath, paths.root.relativeFilePath)} or project root, got: ${join(relativeDirPath, relativeFilePath)}`, |
There was a problem hiding this comment.
In fromFile(), the error message for disallowed locations always says "...or project root" even when global: true (where only .rovodev/AGENTS.md is allowed). This makes the thrown error misleading in global mode; consider branching the message based on whether alternativeRoots are available (project mode) vs global mode.
| throw new Error( | |
| `Rovodev AGENTS.md must be at ${join(paths.root.relativeDirPath, paths.root.relativeFilePath)} or project root, got: ${join(relativeDirPath, relativeFilePath)}`, | |
| const expectedLocationDescription = | |
| "alternativeRoots" in paths && paths.alternativeRoots && paths.alternativeRoots.length > 0 | |
| ? `${join(paths.root.relativeDirPath, paths.root.relativeFilePath)} or project root` | |
| : join(paths.root.relativeDirPath, paths.root.relativeFilePath); | |
| throw new Error( | |
| `Rovodev AGENTS.md must be at ${expectedLocationDescription}, got: ${join(relativeDirPath, relativeFilePath)}`, |
| expect(paths.alternativeRoots).toBeUndefined(); | ||
| expect(paths).toMatchObject({ | ||
| alternativeRoots: [{ relativeDirPath: ".", relativeFilePath: "AGENTS.md" }], | ||
| nonRoot: { relativeDirPath: ".rovodev/.rulesync/modular-rules" }, |
There was a problem hiding this comment.
This assertion hard-codes a POSIX-style path (.rovodev/.rulesync/modular-rules). Since the implementation uses join(), this can fail on Windows path separators. Use join(".rovodev", ".rulesync", "modular-rules") (or compare via toEqual with join) to keep the test OS-agnostic.
| nonRoot: { relativeDirPath: ".rovodev/.rulesync/modular-rules" }, | |
| nonRoot: { relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH }, |
| nonRoot: { | ||
| relativeDirPath: join(".rovodev", ".rulesync", "modular-rules"), | ||
| }, |
There was a problem hiding this comment.
getSettablePaths() now declares a nonRoot directory, which makes RulesProcessor.loadToolFiles() scan that path for *.md. But RovodevRule.fromFile() currently throws for any relativeFilePath other than AGENTS.md, so the presence of any markdown file under this nonRoot directory would cause loadToolFiles() to error and return an empty result. Either omit nonRoot for Rovodev, or teach fromFile()/fromRulesyncRule() how to handle non-root rules under this directory.
| nonRoot: { | |
| relativeDirPath: join(".rovodev", ".rulesync", "modular-rules"), | |
| }, |
Support markdown under .rovodev/.rulesync/modular-rules: fromFile when relativeDirPath matches modular root, fromRulesyncRule in project mode (with reserved AGENTS.md / AGENTS.local.md basenames rejected), and toRulesyncRule mapping with targets: [rovodev]. Let isTargetedByRulesyncRule include non-root rovodev-targeted rules so generate and simulated conventions work. RulesProcessor skips reserved modular paths on rovodev import with warnings, still lists all files for deletion, and passes relativeDirPath into non-root fromFile. Tighten AGENTS.md location errors so global mode does not mention project root. Map AGENTS.local.md import to targets: [rovodev]. Extend gitignore suggestions: **/AGENTS.local.md, **/.rovodev/AGENTS.md, **/.agents/skills/. Tests use join() for modular path expectations on Windows.
- Make DISALLOWED_ROVODEV_MODULAR_RULE_BASENAMES case-insensitive to prevent bypass on macOS/Windows filesystems (#7) - Scope AGENTS.local.md gitignore entry to rovodev target instead of common, since it is Rovodev-specific (#5) - Extract buildDeletionRulesFromPaths helper in rules-processor.ts to deduplicate the file-discovery-to-deletion pattern (#2) - Split RovodevRule.fromFile into fromModularFile/fromRootFile private helpers for improved readability (#4) - Add clarifying comment for nonRootPathsForImport unused on forDeletion path (#6) Closes #1392 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
refactor: address review findings from Rovodev PR #1377
Referenced docs: