Skip to content

Commit 0fc0ae9

Browse files
committed
Add support for a .rooignore file
1 parent 2a4756a commit 0fc0ae9

File tree

16 files changed

+1424
-29
lines changed

16 files changed

+1424
-29
lines changed

src/__mocks__/vscode.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ const vscode = {
8484
this.uri = uri
8585
}
8686
},
87+
RelativePattern: class {
88+
constructor(base, pattern) {
89+
this.base = base
90+
this.pattern = pattern
91+
}
92+
},
8793
}
8894

8995
module.exports = vscode

src/core/Cline.ts

Lines changed: 87 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { calculateApiCost } from "../utils/cost"
5353
import { fileExistsAtPath } from "../utils/fs"
5454
import { arePathsEqual, getReadablePath } from "../utils/path"
5555
import { parseMentions } from "./mentions"
56+
import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "./ignore/RooIgnoreController"
5657
import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "./assistant-message"
5758
import { formatResponse } from "./prompts/responses"
5859
import { SYSTEM_PROMPT } from "./prompts/system"
@@ -100,6 +101,7 @@ export class Cline {
100101

101102
apiConversationHistory: (Anthropic.MessageParam & { ts?: number })[] = []
102103
clineMessages: ClineMessage[] = []
104+
rooIgnoreController?: RooIgnoreController
103105
private askResponse?: ClineAskResponse
104106
private askResponseText?: string
105107
private askResponseImages?: string[]
@@ -148,6 +150,11 @@ export class Cline {
148150
throw new Error("Either historyItem or task/images must be provided")
149151
}
150152

153+
this.rooIgnoreController = new RooIgnoreController(cwd)
154+
this.rooIgnoreController.initialize().catch((error) => {
155+
console.error("Failed to initialize RooIgnoreController:", error)
156+
})
157+
151158
this.taskId = historyItem ? historyItem.id : crypto.randomUUID()
152159

153160
this.apiConfiguration = apiConfiguration
@@ -791,6 +798,7 @@ export class Cline {
791798
this.terminalManager.disposeAll()
792799
this.urlContentFetcher.closeBrowser()
793800
this.browserSession.closeBrowser()
801+
this.rooIgnoreController?.dispose()
794802

795803
// If we're not streaming then `abortStream` (which reverts the diff
796804
// view changes) won't be called, so we need to revert the changes here.
@@ -942,6 +950,8 @@ export class Cline {
942950
})
943951
}
944952

953+
const rooIgnoreInstructions = this.rooIgnoreController?.getInstructions()
954+
945955
const {
946956
browserViewportSize,
947957
mode,
@@ -972,6 +982,7 @@ export class Cline {
972982
this.diffEnabled,
973983
experiments,
974984
enableMcpServerCreation,
985+
rooIgnoreInstructions,
975986
)
976987
})()
977988

@@ -1346,6 +1357,15 @@ export class Cline {
13461357
// wait so we can determine if it's a new file or editing an existing file
13471358
break
13481359
}
1360+
1361+
const accessAllowed = this.rooIgnoreController?.validateAccess(relPath)
1362+
if (!accessAllowed) {
1363+
await this.say("rooignore_error", relPath)
1364+
pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath)))
1365+
1366+
break
1367+
}
1368+
13491369
// Check if file exists using cached map or fs.access
13501370
let fileExists: boolean
13511371
if (this.diffViewProvider.editType !== undefined) {
@@ -1555,6 +1575,14 @@ export class Cline {
15551575
break
15561576
}
15571577

1578+
const accessAllowed = this.rooIgnoreController?.validateAccess(relPath)
1579+
if (!accessAllowed) {
1580+
await this.say("rooignore_error", relPath)
1581+
pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath)))
1582+
1583+
break
1584+
}
1585+
15581586
const absolutePath = path.resolve(cwd, relPath)
15591587
const fileExists = await fileExistsAtPath(absolutePath)
15601588

@@ -1988,6 +2016,15 @@ export class Cline {
19882016
pushToolResult(await this.sayAndCreateMissingParamError("read_file", "path"))
19892017
break
19902018
}
2019+
2020+
const accessAllowed = this.rooIgnoreController?.validateAccess(relPath)
2021+
if (!accessAllowed) {
2022+
await this.say("rooignore_error", relPath)
2023+
pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath)))
2024+
2025+
break
2026+
}
2027+
19912028
this.consecutiveMistakeCount = 0
19922029
const absolutePath = path.resolve(cwd, relPath)
19932030
const completeMessage = JSON.stringify({
@@ -2033,7 +2070,12 @@ export class Cline {
20332070
this.consecutiveMistakeCount = 0
20342071
const absolutePath = path.resolve(cwd, relDirPath)
20352072
const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200)
2036-
const result = formatResponse.formatFilesList(absolutePath, files, didHitLimit)
2073+
const result = formatResponse.formatFilesList(
2074+
absolutePath,
2075+
files,
2076+
didHitLimit,
2077+
this.rooIgnoreController,
2078+
)
20372079
const completeMessage = JSON.stringify({
20382080
...sharedMessageProps,
20392081
content: result,
@@ -2074,7 +2116,10 @@ export class Cline {
20742116
}
20752117
this.consecutiveMistakeCount = 0
20762118
const absolutePath = path.resolve(cwd, relDirPath)
2077-
const result = await parseSourceCodeForDefinitionsTopLevel(absolutePath)
2119+
const result = await parseSourceCodeForDefinitionsTopLevel(
2120+
absolutePath,
2121+
this.rooIgnoreController,
2122+
)
20782123
const completeMessage = JSON.stringify({
20792124
...sharedMessageProps,
20802125
content: result,
@@ -2122,7 +2167,13 @@ export class Cline {
21222167
}
21232168
this.consecutiveMistakeCount = 0
21242169
const absolutePath = path.resolve(cwd, relDirPath)
2125-
const results = await regexSearchFiles(cwd, absolutePath, regex, filePattern)
2170+
const results = await regexSearchFiles(
2171+
cwd,
2172+
absolutePath,
2173+
regex,
2174+
filePattern,
2175+
this.rooIgnoreController,
2176+
)
21262177
const completeMessage = JSON.stringify({
21272178
...sharedMessageProps,
21282179
content: results,
@@ -2301,6 +2352,19 @@ export class Cline {
23012352
)
23022353
break
23032354
}
2355+
2356+
const ignoredFileAttemptedToAccess = this.rooIgnoreController?.validateCommand(command)
2357+
if (ignoredFileAttemptedToAccess) {
2358+
await this.say("rooignore_error", ignoredFileAttemptedToAccess)
2359+
pushToolResult(
2360+
formatResponse.toolError(
2361+
formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess),
2362+
),
2363+
)
2364+
2365+
break
2366+
}
2367+
23042368
this.consecutiveMistakeCount = 0
23052369

23062370
const didApprove = await askApproval("command", command)
@@ -3161,29 +3225,39 @@ export class Cline {
31613225

31623226
// It could be useful for cline to know if the user went from one or no file to another between messages, so we always include this context
31633227
details += "\n\n# VSCode Visible Files"
3164-
const visibleFiles = vscode.window.visibleTextEditors
3228+
const visibleFilePaths = vscode.window.visibleTextEditors
31653229
?.map((editor) => editor.document?.uri?.fsPath)
31663230
.filter(Boolean)
3167-
.map((absolutePath) => path.relative(cwd, absolutePath).toPosix())
3168-
.join("\n")
3169-
if (visibleFiles) {
3170-
details += `\n${visibleFiles}`
3231+
.map((absolutePath) => path.relative(cwd, absolutePath))
3232+
3233+
// Filter paths through rooIgnoreController
3234+
const allowedVisibleFiles = this.rooIgnoreController
3235+
? this.rooIgnoreController.filterPaths(visibleFilePaths)
3236+
: visibleFilePaths.map((p) => p.toPosix()).join("\n")
3237+
3238+
if (allowedVisibleFiles) {
3239+
details += `\n${allowedVisibleFiles}`
31713240
} else {
31723241
details += "\n(No visible files)"
31733242
}
31743243

31753244
details += "\n\n# VSCode Open Tabs"
31763245
const { maxOpenTabsContext } = (await this.providerRef.deref()?.getState()) ?? {}
31773246
const maxTabs = maxOpenTabsContext ?? 20
3178-
const openTabs = vscode.window.tabGroups.all
3247+
const openTabPaths = vscode.window.tabGroups.all
31793248
.flatMap((group) => group.tabs)
31803249
.map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath)
31813250
.filter(Boolean)
31823251
.map((absolutePath) => path.relative(cwd, absolutePath).toPosix())
31833252
.slice(0, maxTabs)
3184-
.join("\n")
3185-
if (openTabs) {
3186-
details += `\n${openTabs}`
3253+
3254+
// Filter paths through rooIgnoreController
3255+
const allowedOpenTabs = this.rooIgnoreController
3256+
? this.rooIgnoreController.filterPaths(openTabPaths)
3257+
: openTabPaths.map((p) => p.toPosix()).join("\n")
3258+
3259+
if (allowedOpenTabs) {
3260+
details += `\n${allowedOpenTabs}`
31873261
} else {
31883262
details += "\n(No open tabs)"
31893263
}
@@ -3342,7 +3416,7 @@ export class Cline {
33423416
details += "(Desktop files not shown automatically. Use list_files to explore if needed.)"
33433417
} else {
33443418
const [files, didHitLimit] = await listFiles(cwd, true, 200)
3345-
const result = formatResponse.formatFilesList(cwd, files, didHitLimit)
3419+
const result = formatResponse.formatFilesList(cwd, files, didHitLimit, this.rooIgnoreController)
33463420
details += result
33473421
}
33483422
}

src/core/__tests__/Cline.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import * as vscode from "vscode"
99
import * as os from "os"
1010
import * as path from "path"
1111

12+
// Mock RooIgnoreController
13+
jest.mock("../ignore/RooIgnoreController")
14+
1215
// Mock all MCP-related modules
1316
jest.mock(
1417
"@modelcontextprotocol/sdk/types.js",

0 commit comments

Comments
 (0)