mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Check .clineignore before executing edit and read operations
This commit is contained in:
parent
5d19bbd52a
commit
2b988a00ce
3 changed files with 89 additions and 10 deletions
|
|
@ -1,7 +1,8 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { loadClineIgnoreFile } from "../utils/cline-ignore"
|
||||
import cloneDeep from "clone-deep"
|
||||
import { DiffStrategy, getDiffStrategy, UnifiedDiffStrategy } from "./diff/DiffStrategy"
|
||||
import { validateToolUse, isToolAllowedForMode, ToolName } from "./mode-validator"
|
||||
import { isToolAllowedForMode, ToolName } from "./mode-validator"
|
||||
import delay from "delay"
|
||||
import fs from "fs/promises"
|
||||
import os from "os"
|
||||
|
|
@ -76,6 +77,7 @@ export class Cline {
|
|||
private urlContentFetcher: UrlContentFetcher
|
||||
private browserSession: BrowserSession
|
||||
private didEditFile: boolean = false
|
||||
private ignoreContent: string = ""
|
||||
customInstructions?: string
|
||||
diffStrategy?: DiffStrategy
|
||||
diffEnabled: boolean = false
|
||||
|
|
@ -132,6 +134,9 @@ export class Cline {
|
|||
this.providerRef = new WeakRef(provider)
|
||||
this.diffViewProvider = new DiffViewProvider(cwd)
|
||||
|
||||
// Load ignore content once at initialization
|
||||
loadClineIgnoreFile(cwd).then((content) => (this.ignoreContent = content))
|
||||
|
||||
if (historyItem) {
|
||||
this.taskId = historyItem.id
|
||||
}
|
||||
|
|
@ -1149,15 +1154,17 @@ export class Cline {
|
|||
const { mode } = (await this.providerRef.deref()?.getState()) ?? {}
|
||||
const { customModes } = (await this.providerRef.deref()?.getState()) ?? {}
|
||||
try {
|
||||
validateToolUse(
|
||||
const isAllowed = isToolAllowedForMode(
|
||||
block.name as ToolName,
|
||||
mode ?? defaultModeSlug,
|
||||
customModes ?? [],
|
||||
{
|
||||
apply_diff: this.diffEnabled,
|
||||
},
|
||||
{ apply_diff: this.diffEnabled },
|
||||
block.params,
|
||||
this.ignoreContent,
|
||||
)
|
||||
if (!isAllowed) {
|
||||
throw new Error(`Tool "${block.name}" is not allowed in ${mode ?? defaultModeSlug} mode.`)
|
||||
}
|
||||
} catch (error) {
|
||||
this.consecutiveMistakeCount++
|
||||
pushToolResult(formatResponse.toolError(error.message))
|
||||
|
|
@ -2718,9 +2725,7 @@ export class Cline {
|
|||
|
||||
// Add warning if not in code mode
|
||||
if (
|
||||
!isToolAllowedForMode("write_to_file", currentMode, customModes ?? [], {
|
||||
apply_diff: this.diffEnabled,
|
||||
}) &&
|
||||
!isToolAllowedForMode("write_to_file", currentMode, customModes ?? [], { apply_diff: this.diffEnabled }) &&
|
||||
!isToolAllowedForMode("apply_diff", currentMode, customModes ?? [], { apply_diff: this.diffEnabled })
|
||||
) {
|
||||
const currentModeName = getModeBySlug(currentMode, customModes)?.name ?? currentMode
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { isToolAllowedForMode, FileRestrictionError, ModeConfig } from "../modes"
|
||||
import { isToolAllowedForMode, FileRestrictionError, IgnoredFileError, ModeConfig } from "../modes"
|
||||
|
||||
describe("isToolAllowedForMode", () => {
|
||||
const customModes: ModeConfig[] = [
|
||||
|
|
@ -186,3 +186,47 @@ describe("FileRestrictionError", () => {
|
|||
expect(error.name).toBe("FileRestrictionError")
|
||||
})
|
||||
})
|
||||
|
||||
describe("IgnoredFileError", () => {
|
||||
it("formats error message correctly", () => {
|
||||
const error = new IgnoredFileError("test.js")
|
||||
expect(error.message).toBe("File test.js is ignored by .clineignore")
|
||||
expect(error.name).toBe("IgnoredFileError")
|
||||
})
|
||||
})
|
||||
|
||||
describe("clineignore handling", () => {
|
||||
const ignoreContent = "*.js\n!important.js\nnode_modules/"
|
||||
|
||||
it("throws IgnoredFileError for ignored files in read group", () => {
|
||||
expect(() =>
|
||||
isToolAllowedForMode("read_file", "code", [], undefined, { path: "test.js" }, ignoreContent),
|
||||
).toThrow(IgnoredFileError)
|
||||
|
||||
expect(() =>
|
||||
isToolAllowedForMode("read_file", "code", [], undefined, { path: "node_modules/test.ts" }, ignoreContent),
|
||||
).toThrow(IgnoredFileError)
|
||||
})
|
||||
|
||||
it("throws IgnoredFileError for ignored files in edit group", () => {
|
||||
expect(() =>
|
||||
isToolAllowedForMode("write_to_file", "code", [], undefined, { path: "test.js" }, ignoreContent),
|
||||
).toThrow(IgnoredFileError)
|
||||
|
||||
expect(() =>
|
||||
isToolAllowedForMode("apply_diff", "code", [], undefined, { path: "node_modules/test.ts" }, ignoreContent),
|
||||
).toThrow(IgnoredFileError)
|
||||
})
|
||||
|
||||
it("allows non-ignored files", () => {
|
||||
expect(isToolAllowedForMode("read_file", "code", [], undefined, { path: "test.ts" }, ignoreContent)).toBe(true)
|
||||
|
||||
expect(
|
||||
isToolAllowedForMode("write_to_file", "code", [], undefined, { path: "important.js" }, ignoreContent),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it("allows tools without paths", () => {
|
||||
expect(isToolAllowedForMode("browser_action", "code", [], undefined, undefined, ignoreContent)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { TOOL_GROUPS, ToolGroup, ALWAYS_AVAILABLE_TOOLS } from "./tool-groups"
|
||||
const { shouldIgnorePath } = require("../utils/cline-ignore")
|
||||
|
||||
// Mode types
|
||||
export type Mode = string
|
||||
|
|
@ -154,12 +155,31 @@ export class FileRestrictionError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
// Custom error class for ignored files
|
||||
export class IgnoredFileError extends Error {
|
||||
constructor(filePath: string) {
|
||||
super(`File ${filePath} is ignored by .clineignore`)
|
||||
this.name = "IgnoredFileError"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a tool is allowed for a given mode, including .clineignore restrictions
|
||||
* @param tool The tool name to check
|
||||
* @param modeSlug The mode slug to check against
|
||||
* @param customModes Array of custom mode configurations
|
||||
* @param toolRequirements Optional map of tool-specific requirements
|
||||
* @param toolParams Optional parameters passed to the tool
|
||||
* @param ignoreContent Optional .clineignore file content
|
||||
* @returns true if tool is allowed, throws error otherwise
|
||||
*/
|
||||
export function isToolAllowedForMode(
|
||||
tool: string,
|
||||
modeSlug: string,
|
||||
customModes: ModeConfig[],
|
||||
toolRequirements?: Record<string, boolean>,
|
||||
toolParams?: Record<string, any>, // All tool parameters
|
||||
toolParams?: Record<string, any>,
|
||||
ignoreContent?: string,
|
||||
): boolean {
|
||||
// Always allow these tools
|
||||
if (ALWAYS_AVAILABLE_TOOLS.includes(tool as any)) {
|
||||
|
|
@ -188,6 +208,16 @@ export function isToolAllowedForMode(
|
|||
continue
|
||||
}
|
||||
|
||||
// Check .clineignore for read/edit groups
|
||||
if (
|
||||
toolParams?.path &&
|
||||
ignoreContent &&
|
||||
(groupName === "read" || groupName === "edit") &&
|
||||
shouldIgnorePath(toolParams.path, ignoreContent)
|
||||
) {
|
||||
throw new IgnoredFileError(toolParams.path)
|
||||
}
|
||||
|
||||
// If there are no options, allow the tool
|
||||
if (!options) {
|
||||
return true
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue