Skip to content

Commit b1bde61

Browse files
committed
feat: add .rooindex support for code indexer to override gitignore
- Created RooIndexController to handle .rooindex file patterns - Updated list-files.ts to support includeGitignored parameter - Modified scanner.ts to use RooIndexController for override logic - Added comprehensive tests for RooIndexController - Allows developers to specify files that should be indexed even if gitignored This addresses the need for indexing generated code, nested repositories, and other files excluded from version control but valuable for AI context.
1 parent 8fee312 commit b1bde61

File tree

4 files changed

+512
-23
lines changed

4 files changed

+512
-23
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import path from "path"
2+
import { fileExistsAtPath } from "../../utils/fs"
3+
import fs from "fs/promises"
4+
import ignore, { Ignore } from "ignore"
5+
import * as vscode from "vscode"
6+
7+
/**
8+
* Controls code indexer file inclusion by providing override patterns for gitignored files.
9+
* Uses the 'ignore' library to support standard .gitignore syntax in .rooindex files.
10+
*
11+
* The .rooindex file allows developers to specify patterns for files that should be
12+
* indexed even if they are gitignored. This is useful for:
13+
* - Generated code (TypeScript definitions, API clients)
14+
* - Meta-repository patterns with nested repositories
15+
* - Monorepos with selective version control
16+
* - Projects with generated documentation or configuration
17+
*/
18+
export class RooIndexController {
19+
private cwd: string
20+
private includeInstance: Ignore
21+
private disposables: vscode.Disposable[] = []
22+
rooIndexContent: string | undefined
23+
24+
constructor(cwd: string) {
25+
this.cwd = cwd
26+
this.includeInstance = ignore()
27+
this.rooIndexContent = undefined
28+
// Set up file watcher for .rooindex
29+
this.setupFileWatcher()
30+
}
31+
32+
/**
33+
* Initialize the controller by loading custom patterns
34+
* Must be called after construction and before using the controller
35+
*/
36+
async initialize(): Promise<void> {
37+
await this.loadRooIndex()
38+
}
39+
40+
/**
41+
* Set up the file watcher for .rooindex changes
42+
*/
43+
private setupFileWatcher(): void {
44+
const rooindexPattern = new vscode.RelativePattern(this.cwd, ".rooindex")
45+
const fileWatcher = vscode.workspace.createFileSystemWatcher(rooindexPattern)
46+
47+
// Watch for changes and updates
48+
this.disposables.push(
49+
fileWatcher.onDidChange(() => {
50+
this.loadRooIndex()
51+
}),
52+
fileWatcher.onDidCreate(() => {
53+
this.loadRooIndex()
54+
}),
55+
fileWatcher.onDidDelete(() => {
56+
this.loadRooIndex()
57+
}),
58+
)
59+
60+
// Add fileWatcher itself to disposables
61+
this.disposables.push(fileWatcher)
62+
}
63+
64+
/**
65+
* Load custom patterns from .rooindex if it exists
66+
*/
67+
private async loadRooIndex(): Promise<void> {
68+
try {
69+
// Reset include instance to prevent duplicate patterns
70+
this.includeInstance = ignore()
71+
const indexPath = path.join(this.cwd, ".rooindex")
72+
if (await fileExistsAtPath(indexPath)) {
73+
const content = await fs.readFile(indexPath, "utf8")
74+
this.rooIndexContent = content
75+
// Add patterns to the include instance
76+
// Note: We're using ignore library in reverse - patterns match what to INCLUDE
77+
this.includeInstance.add(content)
78+
} else {
79+
this.rooIndexContent = undefined
80+
}
81+
} catch (error) {
82+
// Should never happen: reading file failed even though it exists
83+
console.error("Unexpected error loading .rooindex:", error)
84+
}
85+
}
86+
87+
/**
88+
* Check if a file should be included for indexing based on .rooindex patterns
89+
* @param filePath - Path to check (relative to cwd or absolute)
90+
* @returns true if file matches an inclusion pattern, false otherwise
91+
*/
92+
shouldInclude(filePath: string): boolean {
93+
// If .rooindex does not exist, no overrides
94+
if (!this.rooIndexContent) {
95+
return false
96+
}
97+
try {
98+
// Convert to relative path for pattern matching
99+
let relativePath: string
100+
if (path.isAbsolute(filePath)) {
101+
relativePath = path.relative(this.cwd, filePath)
102+
} else {
103+
relativePath = filePath
104+
}
105+
106+
// Normalize path separators for cross-platform compatibility
107+
relativePath = relativePath.replace(/\\/g, "/")
108+
109+
// Check if the path matches any include pattern
110+
// We're using the ignore library to match patterns, but we want inclusion behavior
111+
// The library returns true if a path should be ignored, but we're using it for inclusion
112+
// So if ignores() returns true, it means the path matches our inclusion pattern
113+
return this.includeInstance.ignores(relativePath)
114+
} catch (error) {
115+
// On error, don't include the file
116+
return false
117+
}
118+
}
119+
120+
/**
121+
* Filter an array of paths to include only those that match .rooindex patterns
122+
* @param paths - Array of paths to filter
123+
* @returns Array of paths that match inclusion patterns
124+
*/
125+
filterForInclusion(paths: string[]): string[] {
126+
if (!this.rooIndexContent) {
127+
return []
128+
}
129+
130+
try {
131+
return paths.filter((p) => this.shouldInclude(p))
132+
} catch (error) {
133+
console.error("Error filtering paths for inclusion:", error)
134+
return []
135+
}
136+
}
137+
138+
/**
139+
* Check if a file that would normally be gitignored should be included for indexing
140+
* @param filePath - Path to check
141+
* @param isGitignored - Whether the file is gitignored
142+
* @returns true if the file should be included despite being gitignored
143+
*/
144+
shouldOverrideGitignore(filePath: string, isGitignored: boolean): boolean {
145+
// If not gitignored, no need to override
146+
if (!isGitignored) {
147+
return false
148+
}
149+
150+
// Check if .rooindex says to include this gitignored file
151+
return this.shouldInclude(filePath)
152+
}
153+
154+
/**
155+
* Clean up resources when the controller is no longer needed
156+
*/
157+
dispose(): void {
158+
this.disposables.forEach((d) => d.dispose())
159+
this.disposables = []
160+
}
161+
162+
/**
163+
* Get formatted instructions about the .rooindex file
164+
* @returns Formatted instructions or undefined if .rooindex doesn't exist
165+
*/
166+
getInstructions(): string | undefined {
167+
if (!this.rooIndexContent) {
168+
return undefined
169+
}
170+
171+
return `# .rooindex\n\n(The following patterns from .rooindex specify files that should be indexed even if they are gitignored. This allows the code indexer to access generated code, nested repositories, and other files excluded from version control but valuable for AI context.)\n\n${this.rooIndexContent}`
172+
}
173+
}

0 commit comments

Comments
 (0)