@@ -9,6 +9,9 @@ import * as path from "path"
99import { serializeError } from "serialize-error"
1010import * as vscode from "vscode"
1111import { ApiHandler , buildApiHandler } from "../api"
12+ import { OpenAiHandler } from "../api/providers/openai"
13+ import { OpenRouterHandler } from "../api/providers/openrouter"
14+ import { ApiStream } from "../api/transform/stream"
1215import CheckpointTracker from "../integrations/checkpoints/CheckpointTracker"
1316import { DIFF_VIEW_URI_SCHEME , DiffViewProvider } from "../integrations/editor/DiffViewProvider"
1417import { findToolName , formatContentBlockToMarkdown } from "../integrations/misc/export-markdown"
@@ -46,20 +49,16 @@ import { HistoryItem } from "../shared/HistoryItem"
4649import { ClineAskResponse , ClineCheckpointRestore } from "../shared/WebviewMessage"
4750import { calculateApiCost } from "../utils/cost"
4851import { fileExistsAtPath } from "../utils/fs"
49- import { LLMFileAccessController } from "../services/llm-access-control/LLMFileAccessController"
5052import { arePathsEqual , getReadablePath } from "../utils/path"
5153import { fixModelHtmlEscaping , removeInvalidChars } from "../utils/string"
5254import { AssistantMessageContent , parseAssistantMessage , ToolParamName , ToolUseName } from "./assistant-message"
5355import { constructNewFileContent } from "./assistant-message/diff"
56+ import { ClineIgnoreController , LOCK_TEXT_SYMBOL } from "./ignore/ClineIgnoreController"
5457import { parseMentions } from "./mentions"
5558import { formatResponse } from "./prompts/responses"
56- import { ClineProvider , GlobalFileNames } from "./webview/ClineProvider"
57- import { OpenRouterHandler } from "../api/providers/openrouter"
59+ import { addUserInstructions , SYSTEM_PROMPT } from "./prompts/system"
5860import { getNextTruncationRange , getTruncatedMessages } from "./sliding-window"
59- import { SYSTEM_PROMPT } from "./prompts/system"
60- import { addUserInstructions } from "./prompts/system"
61- import { OpenAiHandler } from "../api/providers/openai"
62- import { ApiStream } from "../api/transform/stream"
61+ import { ClineProvider , GlobalFileNames } from "./webview/ClineProvider"
6362
6463const cwd = vscode . workspace . workspaceFolders ?. map ( ( folder ) => folder . uri . fsPath ) . at ( 0 ) ?? path . join ( os . homedir ( ) , "Desktop" ) // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
6564
@@ -81,7 +80,7 @@ export class Cline {
8180 private chatSettings : ChatSettings
8281 apiConversationHistory : Anthropic . MessageParam [ ] = [ ]
8382 clineMessages : ClineMessage [ ] = [ ]
84- private llmAccessController : LLMFileAccessController
83+ private clineIgnoreController : ClineIgnoreController
8584 private askResponse ?: ClineAskResponse
8685 private askResponseText ?: string
8786 private askResponseImages ?: string [ ]
@@ -125,9 +124,9 @@ export class Cline {
125124 images ?: string [ ] ,
126125 historyItem ?: HistoryItem ,
127126 ) {
128- this . llmAccessController = new LLMFileAccessController ( cwd )
129- this . llmAccessController . initialize ( ) . catch ( ( error ) => {
130- console . error ( "Failed to initialize LLMFileAccessController :" , error )
127+ this . clineIgnoreController = new ClineIgnoreController ( cwd )
128+ this . clineIgnoreController . initialize ( ) . catch ( ( error ) => {
129+ console . error ( "Failed to initialize ClineIgnoreController :" , error )
131130 } )
132131 this . providerRef = new WeakRef ( provider )
133132 this . api = buildApiHandler ( apiConfiguration )
@@ -1057,7 +1056,7 @@ export class Cline {
10571056 this . terminalManager . disposeAll ( )
10581057 this . urlContentFetcher . closeBrowser ( )
10591058 this . browserSession . closeBrowser ( )
1060- this . llmAccessController . dispose ( )
1059+ this . clineIgnoreController . dispose ( )
10611060 await this . diffViewProvider . revertChanges ( ) // need to await for when we want to make sure directories/files are reverted before re-starting the task from a checkpoint
10621061 }
10631062
@@ -1242,9 +1241,15 @@ export class Cline {
12421241 }
12431242 }
12441243
1244+ const clineIgnoreContent = this . clineIgnoreController . clineIgnoreContent
1245+ let clineIgnoreInstructions : string | undefined
1246+ if ( clineIgnoreContent ) {
1247+ clineIgnoreInstructions = `# .clineignore\n\n(The following is provided by a root-level .clineignore file where the user has specified files and directories that should not be accessed. When using list_files, you'll notice a ${ LOCK_TEXT_SYMBOL } next to files that are blocked. Attempting to access the file's contents e.g. through read_file will result in an error.)\n\n${ clineIgnoreContent } \n.clineignore`
1248+ }
1249+
12451250 if ( settingsCustomInstructions || clineRulesFileInstructions ) {
12461251 // altering the system prompt mid-task will break the prompt cache, but in the grand scheme this will not change often so it's better to not pollute user messages with it the way we have to with <potentially relevant details>
1247- systemPrompt += addUserInstructions ( settingsCustomInstructions , clineRulesFileInstructions )
1252+ systemPrompt += addUserInstructions ( settingsCustomInstructions , clineRulesFileInstructions , clineIgnoreInstructions )
12481253 }
12491254
12501255 // If the previous API request's total token usage is close to the context window, truncate the conversation history to free up space for the new request
@@ -1591,6 +1596,15 @@ export class Cline {
15911596 // wait so we can determine if it's a new file or editing an existing file
15921597 break
15931598 }
1599+
1600+ const accessAllowed = this . clineIgnoreController . validateAccess ( relPath )
1601+ if ( ! accessAllowed ) {
1602+ await this . say ( "clineignore_error" , relPath )
1603+ pushToolResult ( formatResponse . toolError ( formatResponse . clineIgnoreError ( relPath ) ) )
1604+ await this . saveCheckpoint ( )
1605+ break
1606+ }
1607+
15941608 // Check if file exists using cached map or fs.access
15951609 let fileExists : boolean
15961610 if ( this . diffViewProvider . editType !== undefined ) {
@@ -1708,6 +1722,7 @@ export class Cline {
17081722 await this . saveCheckpoint ( )
17091723 break
17101724 }
1725+
17111726 this . consecutiveMistakeCount = 0
17121727
17131728 // if isEditingFile false, that means we have the full contents of the file already.
@@ -1859,6 +1874,15 @@ export class Cline {
18591874 await this . saveCheckpoint ( )
18601875 break
18611876 }
1877+
1878+ const accessAllowed = this . clineIgnoreController . validateAccess ( relPath )
1879+ if ( ! accessAllowed ) {
1880+ await this . say ( "clineignore_error" , relPath )
1881+ pushToolResult ( formatResponse . toolError ( formatResponse . clineIgnoreError ( relPath ) ) )
1882+ await this . saveCheckpoint ( )
1883+ break
1884+ }
1885+
18621886 this . consecutiveMistakeCount = 0
18631887 const absolutePath = path . resolve ( cwd , relPath )
18641888 const completeMessage = JSON . stringify ( {
@@ -1922,9 +1946,17 @@ export class Cline {
19221946 break
19231947 }
19241948 this . consecutiveMistakeCount = 0
1949+
19251950 const absolutePath = path . resolve ( cwd , relDirPath )
1951+
19261952 const [ files , didHitLimit ] = await listFiles ( absolutePath , recursive , 200 )
1927- const result = formatResponse . formatFilesList ( absolutePath , files , didHitLimit )
1953+
1954+ const result = formatResponse . formatFilesList (
1955+ absolutePath ,
1956+ files ,
1957+ didHitLimit ,
1958+ this . clineIgnoreController ,
1959+ )
19281960 const completeMessage = JSON . stringify ( {
19291961 ...sharedMessageProps ,
19301962 content : result ,
@@ -1981,9 +2013,15 @@ export class Cline {
19812013 await this . saveCheckpoint ( )
19822014 break
19832015 }
2016+
19842017 this . consecutiveMistakeCount = 0
2018+
19852019 const absolutePath = path . resolve ( cwd , relDirPath )
1986- const result = await parseSourceCodeForDefinitionsTopLevel ( absolutePath )
2020+ const result = await parseSourceCodeForDefinitionsTopLevel (
2021+ absolutePath ,
2022+ this . clineIgnoreController ,
2023+ )
2024+
19872025 const completeMessage = JSON . stringify ( {
19882026 ...sharedMessageProps ,
19892027 content : result ,
@@ -2051,8 +2089,16 @@ export class Cline {
20512089 break
20522090 }
20532091 this . consecutiveMistakeCount = 0
2092+
20542093 const absolutePath = path . resolve ( cwd , relDirPath )
2055- const results = await regexSearchFiles ( cwd , absolutePath , regex , filePattern )
2094+ const results = await regexSearchFiles (
2095+ cwd ,
2096+ absolutePath ,
2097+ regex ,
2098+ filePattern ,
2099+ this . clineIgnoreController ,
2100+ )
2101+
20562102 const completeMessage = JSON . stringify ( {
20572103 ...sharedMessageProps ,
20582104 content : results ,
@@ -2289,6 +2335,16 @@ export class Cline {
22892335 }
22902336 this . consecutiveMistakeCount = 0
22912337
2338+ const ignoredFileAttemptedToAccess = this . clineIgnoreController . validateCommand ( command )
2339+ if ( ignoredFileAttemptedToAccess ) {
2340+ await this . say ( "clineignore_error" , ignoredFileAttemptedToAccess )
2341+ pushToolResult (
2342+ formatResponse . toolError ( formatResponse . clineIgnoreError ( ignoredFileAttemptedToAccess ) ) ,
2343+ )
2344+ await this . saveCheckpoint ( )
2345+ break
2346+ }
2347+
22922348 let didAutoApprove = false
22932349
22942350 if ( ! requiresApproval && this . shouldAutoApproveTool ( block . name ) ) {
@@ -3192,26 +3248,38 @@ export class Cline {
31923248
31933249 // 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
31943250 details += "\n\n# VSCode Visible Files"
3195- const visibleFiles = vscode . window . visibleTextEditors
3251+ const visibleFilePaths = vscode . window . visibleTextEditors
31963252 ?. map ( ( editor ) => editor . document ?. uri ?. fsPath )
31973253 . filter ( Boolean )
3198- . map ( ( absolutePath ) => path . relative ( cwd , absolutePath ) . toPosix ( ) )
3254+ . map ( ( absolutePath ) => path . relative ( cwd , absolutePath ) )
3255+
3256+ // Filter paths through clineIgnoreController
3257+ const allowedVisibleFiles = this . clineIgnoreController
3258+ . filterPaths ( visibleFilePaths )
3259+ . map ( ( p ) => p . toPosix ( ) )
31993260 . join ( "\n" )
3200- if ( visibleFiles ) {
3201- details += `\n${ visibleFiles } `
3261+
3262+ if ( allowedVisibleFiles ) {
3263+ details += `\n${ allowedVisibleFiles } `
32023264 } else {
32033265 details += "\n(No visible files)"
32043266 }
32053267
32063268 details += "\n\n# VSCode Open Tabs"
3207- const openTabs = vscode . window . tabGroups . all
3269+ const openTabPaths = vscode . window . tabGroups . all
32083270 . flatMap ( ( group ) => group . tabs )
32093271 . map ( ( tab ) => ( tab . input as vscode . TabInputText ) ?. uri ?. fsPath )
32103272 . filter ( Boolean )
3211- . map ( ( absolutePath ) => path . relative ( cwd , absolutePath ) . toPosix ( ) )
3273+ . map ( ( absolutePath ) => path . relative ( cwd , absolutePath ) )
3274+
3275+ // Filter paths through clineIgnoreController
3276+ const allowedOpenTabs = this . clineIgnoreController
3277+ . filterPaths ( openTabPaths )
3278+ . map ( ( p ) => p . toPosix ( ) )
32123279 . join ( "\n" )
3213- if ( openTabs ) {
3214- details += `\n${ openTabs } `
3280+
3281+ if ( allowedOpenTabs ) {
3282+ details += `\n${ allowedOpenTabs } `
32153283 } else {
32163284 details += "\n(No open tabs)"
32173285 }
@@ -3325,7 +3393,7 @@ export class Cline {
33253393 details += "(Desktop files not shown automatically. Use list_files to explore if needed.)"
33263394 } else {
33273395 const [ files , didHitLimit ] = await listFiles ( cwd , true , 200 )
3328- const result = formatResponse . formatFilesList ( cwd , files , didHitLimit )
3396+ const result = formatResponse . formatFilesList ( cwd , files , didHitLimit , this . clineIgnoreController )
33293397 details += result
33303398 }
33313399 }
0 commit comments