Skip to content

dev-cache: add per-project include/exclude and age-based cache filters#73

Open
markcallen wants to merge 1 commit intomainfrom
feat/4-dev-cache-filters
Open

dev-cache: add per-project include/exclude and age-based cache filters#73
markcallen wants to merge 1 commit intomainfrom
feat/4-dev-cache-filters

Conversation

@markcallen
Copy link
Owner

Summary

  • Add path-based scan filters:
    • --exclude and config options.excludePaths
    • --include-only and config options.includeOnly
  • Add age-based cache filters:
    • --min-age / config options.minAge
    • --max-age / config options.maxAge
  • Apply include/exclude logic at project-root granularity during scan walk
  • Apply age filters to cache findings before reporting/cleanup
  • Update starter config and README flag/config docs
  • Add tests for duration parsing, path filtering, and age filter behavior

Issues

Validation

  • go test ./... in dev-cache
  • go test ./... in git-cleaner
  • go test ./... in mac-cache-cleaner

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 / includeOnly and minAge / 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) {
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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) {

Copilot uses AI. Check for mistakes.
// 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)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)

Copilot uses AI. Check for mistakes.
Comment on lines +200 to +202
if !isCacheDirectory(f) || f.ModMax.IsZero() {
return true
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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
}

Copilot uses AI. Check for mistakes.
Comment on lines +964 to +974
// 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)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
// 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)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[dev-cache] Per-project whitelist/blacklist in config [dev-cache] Age-based filtering for cache directories

2 participants