dev-cache: add per-project include/exclude and age-based cache filters#73
dev-cache: add per-project include/exclude and age-based cache filters#73markcallen wants to merge 1 commit intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds scan-time filtering controls to dev-cache so users can restrict scanning/cleanup by project path (include/exclude) and by cache “age” (mtime-based min/max thresholds), and documents/tests the new behavior.
Changes:
- Add CLI flags + config options for
excludePaths/includeOnlyandminAge/maxAge, and plumb them into the scan flow. - Implement path-pattern matching at project-root granularity during the directory walk.
- Implement duration parsing + age-based filtering of cache findings, and add unit tests and README updates.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| dev-cache/main.go | Adds filter types/utilities, CLI/config wiring, and applies include/exclude + age filtering in scanDirectory. |
| dev-cache/main_test.go | Updates scanDirectory callsites and adds tests for duration parsing, path filtering, and age filtering. |
| dev-cache/README.md | Documents new flags and config keys for path and age filtering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if len(filters.IncludeOnly) > 0 { | ||
| allowed = matchesAnyPattern(projectRootPath, filters.IncludeOnly) | ||
| } | ||
| if allowed && len(filters.ExcludePaths) > 0 && matchesAnyPattern(projectRootPath, filters.ExcludePaths) { |
There was a problem hiding this comment.
The include/exclude precedence currently makes ExcludePaths win even when IncludeOnly is set (allowed=true from IncludeOnly, then possibly flipped to false by ExcludePaths). Issue #46's proposed behavior says includeOnly should override exclude (whitelist semantics). Either adjust the logic so IncludeOnly takes precedence over ExcludePaths, or document the intended precedence clearly in README/config docs.
| if allowed && len(filters.ExcludePaths) > 0 && matchesAnyPattern(projectRootPath, filters.ExcludePaths) { | |
| // When IncludeOnly is configured, it has whitelist semantics and | |
| // should not be overridden by ExcludePaths. Only apply ExcludePaths | |
| // when no IncludeOnly patterns are set. | |
| if len(filters.IncludeOnly) == 0 && allowed && len(filters.ExcludePaths) > 0 && matchesAnyPattern(projectRootPath, filters.ExcludePaths) { |
| // Re-scan to verify | ||
| fmt.Println("Re-scanning after cleanup...") | ||
| afterFindings := scanDirectory(scanPath, maxDepth, allPatterns, patternToLang, cfg.Options.DetectLanguage, langSignatures, langPriorities, langToPatterns) | ||
| afterFindings := scanDirectory(scanPath, maxDepth, allPatterns, patternToLang, cfg.Options.DetectLanguage, langSignatures, langPriorities, langToPatterns, filters) |
There was a problem hiding this comment.
There is inconsistent indentation on this line (extra leading tab), suggesting gofmt wasn’t run after the changes. Running gofmt on this file will normalize indentation and avoid noisy diffs / potential CI formatting checks.
| afterFindings := scanDirectory(scanPath, maxDepth, allPatterns, patternToLang, cfg.Options.DetectLanguage, langSignatures, langPriorities, langToPatterns, filters) | |
| afterFindings := scanDirectory(scanPath, maxDepth, allPatterns, patternToLang, cfg.Options.DetectLanguage, langSignatures, langPriorities, langToPatterns, filters) |
| if !isCacheDirectory(f) || f.ModMax.IsZero() { | ||
| return true | ||
| } |
There was a problem hiding this comment.
Age filter currently fails open for cache findings with ModMax.IsZero() (returns true). Since inspectPath only updates ModMax based on file mtimes, cache dirs that contain only subdirectories (or unreadable files) may bypass --min-age/--max-age and still be reported/cleaned. Consider initializing ModMax from the directory's own modtime and/or including directory mtimes in inspectPath, or make ModMax.IsZero() fail the age filter when MinAge/MaxAge are set.
| if !isCacheDirectory(f) || f.ModMax.IsZero() { | |
| return true | |
| } | |
| // Only apply age filters to cache directories. | |
| if !isCacheDirectory(f) { | |
| return true | |
| } | |
| // If we don't have a recorded max mtime, decide based on whether | |
| // age filters were requested. When MinAge/MaxAge are set, fail | |
| // closed so that entries with unknown age do not bypass the filter. | |
| if f.ModMax.IsZero() { | |
| if filters.MinAge > 0 || filters.MaxAge > 0 { | |
| return false | |
| } | |
| return true | |
| } |
| // Set project root to where language was detected, or parent if no language detection | ||
| if detectLang && projectRoot != "" { | ||
| f.ProjectRoot = projectRoot | ||
| } else { | ||
| // If no language detection, use parent directory as project root | ||
| f.ProjectRoot = filepath.Dir(cleanPath) | ||
| } | ||
| if !passesAgeFilter(f, filters) { | ||
| continue | ||
| } | ||
| findings = append(findings, f) |
There was a problem hiding this comment.
When a directory matches a cache pattern but is excluded by passesAgeFilter, the code uses continue, which means WalkDir will still descend into that cache directory. This can cause a big performance hit (walking huge cache trees) and may produce unexpected nested cache findings under a filtered-out cache dir. Consider returning filepath.SkipDir for matched cache dirs even when they're filtered out by age (and avoid further pattern checks for that directory).
| // Set project root to where language was detected, or parent if no language detection | |
| if detectLang && projectRoot != "" { | |
| f.ProjectRoot = projectRoot | |
| } else { | |
| // If no language detection, use parent directory as project root | |
| f.ProjectRoot = filepath.Dir(cleanPath) | |
| } | |
| if !passesAgeFilter(f, filters) { | |
| continue | |
| } | |
| findings = append(findings, f) | |
| // Set project root to where language was detected, or parent if no language detection | |
| if detectLang && projectRoot != "" { | |
| f.ProjectRoot = projectRoot | |
| } else { | |
| // If no language detection, use parent directory as project root | |
| f.ProjectRoot = filepath.Dir(cleanPath) | |
| } | |
| if !passesAgeFilter(f, filters) { | |
| // Directory matches a cache pattern but is excluded by age; | |
| // skip walking this directory's subtree entirely. | |
| return filepath.SkipDir | |
| } | |
| findings = append(findings, f) |
Summary
--excludeand configoptions.excludePaths--include-onlyand configoptions.includeOnly--min-age/ configoptions.minAge--max-age/ configoptions.maxAgeIssues
Validation
go test ./...indev-cachego test ./...ingit-cleanergo test ./...inmac-cache-cleaner