refactor: address review findings from Rovodev PR #1377 - #1396
Conversation
- 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>
There was a problem hiding this comment.
Pull request overview
Refactors and hardens Rovodev-related rules handling in Rulesync, addressing follow-up review findings from Rovodev PR #1377 (including filesystem case-insensitivity and some internal cleanup).
Changes:
- Added a
buildDeletionRulesFromPathshelper inrules-processor.tsto deduplicate repeated “discover → validate → forDeletion → filter” logic. - Hardened Rovodev modular-rule reserved basename filtering to be case-insensitive (macOS/Windows) and refactored
RovodevRule.fromFileinto clearer private helpers. - Scoped
**/AGENTS.local.mdgitignore entry to therovodevtarget (instead ofcommon).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/features/rules/rules-processor.ts | Extracts a helper to reduce duplication in deletion-rule creation and clarifies import vs deletion filtering behavior. |
| src/features/rules/rovodev-rule.ts | Makes reserved-basename checks case-insensitive and splits fromFile into modular vs root helpers for readability. |
| src/cli/commands/gitignore-entries.ts | Moves AGENTS.local.md ignore entry from common to rovodev to limit scope. |
| const relativeDirPath = | ||
| opts?.relativeDirPathOverride ?? resolveRelativeDirPath(filePath); | ||
| const relativeFilePath = opts?.baseDirOverride | ||
| ? relative(effectiveBaseDir, filePath) | ||
| : basename(filePath); |
There was a problem hiding this comment.
buildDeletionRulesFromPaths allows baseDirOverride without requiring relativeDirPathOverride, but in that case relativeDirPath still comes from resolveRelativeDirPath(filePath) (which is based on this.baseDir). That can produce an incorrect deletion path (duplicated subdirs) if a future call site passes only baseDirOverride. Consider either (a) computing relativeDirPath relative to effectiveBaseDir when baseDirOverride is set, or (b) asserting/enforcing that relativeDirPathOverride must be provided whenever baseDirOverride is used.
| for (const segment of relativePath.split(/[/\\]/)) { | ||
| if (segment === "" || segment === "." || segment === "..") { | ||
| continue; | ||
| } | ||
| if (DISALLOWED_ROVODEV_MODULAR_RULE_BASENAMES.has(segment)) { | ||
| if (DISALLOWED_ROVODEV_MODULAR_RULE_BASENAMES.has(segment.toLowerCase())) { | ||
| return false; |
There was a problem hiding this comment.
The new case-insensitive reserved-name check isn’t currently covered by tests: existing specs only assert rejection for "AGENTS.md" / "AGENTS.local.md" exact casing. Add test cases for mixed/lowercase variants (e.g., "agents.md", "Agents.Local.MD", and nested segments) to prevent regressions on case-insensitive filesystems.
…d gitignore sync - Add test cases for case-insensitive basename rejection in isAllowedModularRulesRelativePath - Add JSDoc documenting two modes of buildDeletionRulesFromPaths - Move forDeletion check before nonRootPathsForImport filter to avoid unnecessary computation - Sync .gitignore via pnpm dev gitignore Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d gitignore sync - Enforce both-or-neither opts in buildDeletionRulesFromPaths (#1) - Rename destructured param in fromRootFile for clarity (#2) - Remove redundant optional chaining after nonRoot guard (#3) - Remove duplicate **/.rovodev/ gitignore entry (#4) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| feature: "general", | ||
| entry: "**/.rovodev/AGENTS.md", | ||
| }, | ||
| { target: "rovodev", feature: "rules", entry: "**/AGENTS.local.md" }, |
There was a problem hiding this comment.
The Rovodev target’s gitignore entries no longer cover the modular rules output directory (.rovodev/.rulesync/modular-rules/). Since RovodevRule.getSettablePaths() uses this location for non-root rules, generated modular rule files may now show up as unignored artifacts (previously masked by **/.rovodev/). Add an explicit ignore entry for .rovodev/.rulesync/modular-rules/ (likely under feature: "rules") and re-sync the generated .gitignore.
| { target: "rovodev", feature: "rules", entry: "**/AGENTS.local.md" }, | |
| { target: "rovodev", feature: "rules", entry: "**/AGENTS.local.md" }, | |
| { | |
| target: "rovodev", | |
| feature: "rules", | |
| entry: "**/.rovodev/.rulesync/modular-rules/", | |
| }, |
| // Rovodev | ||
| { | ||
| target: "rovodev", | ||
| feature: "general", |
There was a problem hiding this comment.
**/.rovodev/AGENTS.md is tagged as feature: "general", but it’s the primary Rovodev rules output file (analogous to **/CLAUDE.md, **/GEMINI.md, etc. which are tagged as feature: "rules"). Because general entries are always included by filterGitignoreEntries, this makes it impossible to exclude Rovodev rules outputs when filtering by features. Consider tagging this entry as feature: "rules" for consistency and correct feature filtering behavior.
| feature: "general", | |
| feature: "rules", |
…ev/.rulesync/ entry - Move **/AGENTS.local.md back to common target to ensure it is always gitignored regardless of whether rovodev target is enabled - Add **/.rovodev/.rulesync/ entry to cover generated modular-rules directory that was not covered by individual file entries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nale comment for common AGENTS.local.md - Replace inconsistent opts?.baseDirOverride/relativeDirPathOverride branching with a single isNonRoot boolean guard for clarity - Add comment explaining why AGENTS.local.md is in common gitignore scope Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| // AGENTS.local.md is placed in common scope (not rovodev-only) so that | ||
| // local rule files are always gitignored regardless of which targets are enabled. | ||
| // This prevents accidental commits when a user disables the rovodev target. | ||
| { target: "common", feature: "general", entry: "**/AGENTS.local.md" }, |
There was a problem hiding this comment.
PR description says the **/AGENTS.local.md gitignore entry was moved from common to rovodev, but this change keeps it in common (and adds a comment justifying that choice). Please either update the PR description to match the implemented behavior, or move the entry to the rovodev target if that was the intended outcome.
|
@dyoshikawa Thank you! |
Summary
DISALLOWED_ROVODEV_MODULAR_RULE_BASENAMESnow compares lowercased segments, preventing bypass on case-insensitive filesystems (macOS/Windows)**/AGENTS.local.mdentry fromcommontorovodevtarget since it is Rovodev-specificbuildDeletionRulesFromPathshelper inrules-processor.tsto deduplicate 4 instances of the file-discovery-to-deletion patternRovodevRule.fromFileintofromModularFile/fromRootFileprivate helpersnonRootPathsForImportis intentionally unused on theforDeletionpathAdditional fixes (from PR self-review)
buildDeletionRulesFromPaths(root vs non-root)forDeletioncheck beforenonRootPathsForImportfilter to avoid unnecessary computation.gitignoreviapnpm dev gitignoreCloses #1392
Test plan
pnpm cicheckpasses (lint, typecheck, 4608 tests, cspell, secretlint)pnpm dev gitignoreproduces correct output🤖 Generated with Claude Code