Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/swift-webs-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"roo-cline": minor
---

Add ability to Override tools descriptions system prompt. Example usage add your description in .roo/tools/read_file.md it will override default tool description it supports args param replacement with ${args.cwd}
64 changes: 33 additions & 31 deletions locales/ca/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/de/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/es/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/fr/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/hi/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/it/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/ja/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/ko/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/nl/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/pl/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/pt-BR/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/ru/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/tr/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/vi/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/zh-CN/README.md

Large diffs are not rendered by default.

64 changes: 33 additions & 31 deletions locales/zh-TW/README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const globalSettingsSchema = z.object({
allowedMaxRequests: z.number().nullish(),
autoCondenseContext: z.boolean().optional(),
autoCondenseContextPercent: z.number().optional(),
maxConcurrentFileReads: z.number().optional(),
maxConcurrentFileReads: z.number().optional(),

browserToolEnabled: z.boolean().optional(),
browserViewportSize: z.string().optional(),
Expand Down
210 changes: 210 additions & 0 deletions src/core/prompts/__tests__/tool-overrides.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { getToolDescriptionsForMode } from "../tools/index"
import { defaultModeSlug } from "../../../shared/modes"
import * as fs from "fs/promises"
import { toPosix } from "./utils"

// Mock the fs/promises module
jest.mock("fs/promises", () => ({
readFile: jest.fn(),
mkdir: jest.fn().mockResolvedValue(undefined),
access: jest.fn().mockResolvedValue(undefined),
}))

// Get the mocked fs module
const mockedFs = fs as jest.Mocked<typeof fs>

describe("Tool Override System", () => {
beforeEach(() => {
// Reset mocks before each test
jest.clearAllMocks()

// Default behavior: file doesn't exist
mockedFs.readFile.mockRejectedValue({ code: "ENOENT" })
})

it("should return default read_file tool description when no override file exists", async () => {
const toolDescriptions = await getToolDescriptionsForMode(
defaultModeSlug,
"test/workspace",
false, // supportsComputerUse
undefined, // codeIndexManager
undefined, // diffStrategy
undefined, // browserViewportSize
undefined, // mcpHub
undefined, // customModes
undefined, // experiments
)

// Should contain the default read_file description
expect(toolDescriptions).toContain("## read_file")
expect(toolDescriptions).toContain("Request to read the contents of a file")
expect(toolDescriptions).toContain("relative to the current workspace directory test/workspace")
expect(toolDescriptions).toContain("<read_file>")
expect(toolDescriptions).toContain("Examples:")
})

it("should use custom read_file tool description when override file exists", async () => {
// Mock the readFile to return content from an override file
const customReadFileDescription = `## read_file
Description: Custom read file description for testing
Parameters:
- path: (required) Custom path description for \${args.cwd}
- start_line: (optional) Custom start line description
- end_line: (optional) Custom end line description
Usage:
<read_file>
<path>Custom file path</path>
</read_file>

Custom example:
<read_file>
<path>custom-example.txt</path>
</read_file>`

mockedFs.readFile.mockImplementation((filePath, options) => {
if (toPosix(filePath).includes(".roo/tools/read_file.md") && options === "utf-8") {
return Promise.resolve(customReadFileDescription)
}
return Promise.reject({ code: "ENOENT" })
})

const toolDescriptions = await getToolDescriptionsForMode(
defaultModeSlug,
"test/workspace",
false, // supportsComputerUse
undefined, // codeIndexManager
undefined, // diffStrategy
undefined, // browserViewportSize
undefined, // mcpHub
undefined, // customModes
undefined, // experiments
)

// Should contain the custom read_file description
expect(toolDescriptions).toContain("Custom read file description for testing")
expect(toolDescriptions).toContain("Custom path description for test/workspace")
expect(toolDescriptions).toContain("Custom example:")
expect(toolDescriptions).toContain("custom-example.txt")

// Should not contain the default description text
expect(toolDescriptions).not.toContain("Request to read the contents of a file")
expect(toolDescriptions).not.toContain("3. Reading lines 500-1000 of a CSV file:")
})

it("should interpolate args properties in override content", async () => {
// Mock the readFile to return content with args interpolation
const customReadFileDescription = `## read_file
Description: Custom read file description with interpolated args
Parameters:
- path: (required) File path relative to workspace \${args.cwd}
- workspace: Current workspace is \${args.cwd}
Usage:
<read_file>
<path>File relative to \${args.cwd}</path>
</read_file>`

mockedFs.readFile.mockImplementation((filePath, options) => {
if (toPosix(filePath).includes(".roo/tools/read_file.md") && options === "utf-8") {
return Promise.resolve(customReadFileDescription)
}
return Promise.reject({ code: "ENOENT" })
})

const toolDescriptions = await getToolDescriptionsForMode(
defaultModeSlug,
"test/workspace",
false, // supportsComputerUse
undefined, // codeIndexManager
undefined, // diffStrategy
undefined, // browserViewportSize
undefined, // mcpHub
undefined, // customModes
undefined, // experiments
)

// Should contain interpolated values
expect(toolDescriptions).toContain("File path relative to workspace test/workspace")
expect(toolDescriptions).toContain("Current workspace is test/workspace")
expect(toolDescriptions).toContain("File relative to test/workspace")

// Should not contain the placeholder text
expect(toolDescriptions).not.toContain("\${args.cwd}")
})

it("should return multiple tool descriptions including read_file", async () => {
const toolDescriptions = await getToolDescriptionsForMode(
defaultModeSlug,
"test/workspace",
false, // supportsComputerUse
undefined, // codeIndexManager
undefined, // diffStrategy
undefined, // browserViewportSize
undefined, // mcpHub
undefined, // customModes
undefined, // experiments
)

// Should contain multiple tools from the default mode
expect(toolDescriptions).toContain("# Tools")
expect(toolDescriptions).toContain("## read_file")
expect(toolDescriptions).toContain("## write_to_file")
expect(toolDescriptions).toContain("## list_files")
expect(toolDescriptions).toContain("## search_files")

// Tools should be separated by double newlines
const toolSections = toolDescriptions.split("\n\n")
expect(toolSections.length).toBeGreaterThan(1)
})

it("should handle empty override file gracefully", async () => {
// Mock the readFile to return empty content
mockedFs.readFile.mockImplementation((filePath, options) => {
if (toPosix(filePath).includes(".roo/tools/read_file.md") && options === "utf-8") {
return Promise.resolve("")
}
return Promise.reject({ code: "ENOENT" })
})

const toolDescriptions = await getToolDescriptionsForMode(
defaultModeSlug,
"test/workspace",
false, // supportsComputerUse
undefined, // codeIndexManager
undefined, // diffStrategy
undefined, // browserViewportSize
undefined, // mcpHub
undefined, // customModes
undefined, // experiments
)

// Should fall back to default description when override file is empty
expect(toolDescriptions).toContain("## read_file")
expect(toolDescriptions).toContain("Request to read the contents of a file")
})

it("should handle whitespace-only override file gracefully", async () => {
// Mock the readFile to return whitespace-only content
mockedFs.readFile.mockImplementation((filePath, options) => {
if (toPosix(filePath).includes(".roo/tools/read_file.md") && options === "utf-8") {
return Promise.resolve(" \n \t \n ")
}
return Promise.reject({ code: "ENOENT" })
})

const toolDescriptions = await getToolDescriptionsForMode(
defaultModeSlug,
"test/workspace",
false, // supportsComputerUse
undefined, // codeIndexManager
undefined, // diffStrategy
undefined, // browserViewportSize
undefined, // mcpHub
undefined, // customModes
undefined, // experiments
)

// Should fall back to default description when override file contains only whitespace
expect(toolDescriptions).toContain("## read_file")
expect(toolDescriptions).toContain("Request to read the contents of a file")
})
})
20 changes: 12 additions & 8 deletions src/core/prompts/sections/__tests__/objective.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ describe("getObjectiveSection", () => {
describe("when codebase_search is available", () => {
it("should include codebase_search first enforcement in thinking process", () => {
const objective = getObjectiveSection(mockCodeIndexManagerEnabled)

// Check that the objective includes the codebase_search enforcement
expect(objective).toContain("if the task involves understanding existing code or functionality, you MUST use the `codebase_search` tool")
expect(objective).toContain(
"if the task involves understanding existing code or functionality, you MUST use the `codebase_search` tool",
)
expect(objective).toContain("BEFORE using any other search or file exploration tools")
})
})

describe("when codebase_search is not available", () => {
it("should not include codebase_search enforcement", () => {
const objective = getObjectiveSection(mockCodeIndexManagerDisabled)

// Check that the objective does not include the codebase_search enforcement
expect(objective).not.toContain("you MUST use the `codebase_search` tool")
expect(objective).not.toContain("BEFORE using any other search or file exploration tools")
Expand All @@ -39,7 +41,7 @@ describe("getObjectiveSection", () => {
it("should maintain proper structure regardless of codebase_search availability", () => {
const objectiveEnabled = getObjectiveSection(mockCodeIndexManagerEnabled)
const objectiveDisabled = getObjectiveSection(mockCodeIndexManagerDisabled)

// Check that all numbered items are present in both cases
for (const objective of [objectiveEnabled, objectiveDisabled]) {
expect(objective).toContain("1. Analyze the user's task")
Expand All @@ -53,7 +55,7 @@ describe("getObjectiveSection", () => {
it("should include thinking tags guidance regardless of codebase_search availability", () => {
const objectiveEnabled = getObjectiveSection(mockCodeIndexManagerEnabled)
const objectiveDisabled = getObjectiveSection(mockCodeIndexManagerDisabled)

// Check that thinking tags guidance is included in both cases
for (const objective of [objectiveEnabled, objectiveDisabled]) {
expect(objective).toContain("<thinking></thinking> tags")
Expand All @@ -65,13 +67,15 @@ describe("getObjectiveSection", () => {
it("should include parameter inference guidance regardless of codebase_search availability", () => {
const objectiveEnabled = getObjectiveSection(mockCodeIndexManagerEnabled)
const objectiveDisabled = getObjectiveSection(mockCodeIndexManagerDisabled)

// Check parameter inference guidance in both cases
for (const objective of [objectiveEnabled, objectiveDisabled]) {
expect(objective).toContain("Go through each of the required parameters")
expect(objective).toContain("determine if the user has directly provided or given enough information to infer a value")
expect(objective).toContain(
"determine if the user has directly provided or given enough information to infer a value",
)
expect(objective).toContain("DO NOT invoke the tool (not even with fillers for the missing params)")
expect(objective).toContain("ask_followup_question tool")
}
})
})
})
24 changes: 15 additions & 9 deletions src/core/prompts/sections/__tests__/tool-use-guidelines.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@ describe("getToolUseGuidelinesSection", () => {
describe("when codebase_search is available", () => {
it("should include codebase_search first enforcement", () => {
const guidelines = getToolUseGuidelinesSection(mockCodeIndexManagerEnabled)

// Check that the guidelines include the codebase_search enforcement
expect(guidelines).toContain("IMPORTANT: When starting a new task or when you need to understand existing code/functionality, you MUST use the `codebase_search` tool FIRST")
expect(guidelines).toContain(
"IMPORTANT: When starting a new task or when you need to understand existing code/functionality, you MUST use the `codebase_search` tool FIRST",
)
expect(guidelines).toContain("before any other search tools")
expect(guidelines).toContain("semantic search tool helps you find relevant code based on meaning rather than just keywords")
expect(guidelines).toContain(
"semantic search tool helps you find relevant code based on meaning rather than just keywords",
)
})

it("should maintain proper numbering with codebase_search", () => {
const guidelines = getToolUseGuidelinesSection(mockCodeIndexManagerEnabled)

// Check that all numbered items are present
expect(guidelines).toContain("1. In <thinking> tags")
expect(guidelines).toContain("2. **IMPORTANT:")
Expand All @@ -43,15 +47,17 @@ describe("getToolUseGuidelinesSection", () => {
describe("when codebase_search is not available", () => {
it("should not include codebase_search enforcement", () => {
const guidelines = getToolUseGuidelinesSection(mockCodeIndexManagerDisabled)

// Check that the guidelines do not include the codebase_search enforcement
expect(guidelines).not.toContain("IMPORTANT: When starting a new task or when you need to understand existing code/functionality, you MUST use the `codebase_search` tool FIRST")
expect(guidelines).not.toContain(
"IMPORTANT: When starting a new task or when you need to understand existing code/functionality, you MUST use the `codebase_search` tool FIRST",
)
expect(guidelines).not.toContain("semantic search tool helps you find relevant code based on meaning")
})

it("should maintain proper numbering without codebase_search", () => {
const guidelines = getToolUseGuidelinesSection(mockCodeIndexManagerDisabled)

// Check that all numbered items are present with correct numbering
expect(guidelines).toContain("1. In <thinking> tags")
expect(guidelines).toContain("2. Choose the most appropriate tool")
Expand All @@ -65,7 +71,7 @@ describe("getToolUseGuidelinesSection", () => {
it("should include iterative process guidelines regardless of codebase_search availability", () => {
const guidelinesEnabled = getToolUseGuidelinesSection(mockCodeIndexManagerEnabled)
const guidelinesDisabled = getToolUseGuidelinesSection(mockCodeIndexManagerDisabled)

// Check that the iterative process section is included in both cases
for (const guidelines of [guidelinesEnabled, guidelinesDisabled]) {
expect(guidelines).toContain("It is crucial to proceed step-by-step")
Expand All @@ -75,4 +81,4 @@ describe("getToolUseGuidelinesSection", () => {
expect(guidelines).toContain("4. Ensure that each action builds correctly")
}
})
})
})
3 changes: 2 additions & 1 deletion src/core/prompts/sections/objective.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { CodeIndexManager } from "../../../services/code-index/manager"

export function getObjectiveSection(codeIndexManager?: CodeIndexManager): string {
const isCodebaseSearchAvailable = codeIndexManager &&
const isCodebaseSearchAvailable =
codeIndexManager &&
codeIndexManager.isFeatureEnabled &&
codeIndexManager.isFeatureConfigured &&
codeIndexManager.isInitialized
Expand Down
10 changes: 8 additions & 2 deletions src/core/prompts/sections/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,14 @@ function getEditingInstructions(diffStrategy?: DiffStrategy): string {
return instructions.join("\n")
}

export function getRulesSection(cwd: string, supportsComputerUse: boolean, diffStrategy?: DiffStrategy, codeIndexManager?: CodeIndexManager): string {
const isCodebaseSearchAvailable = codeIndexManager &&
export function getRulesSection(
cwd: string,
supportsComputerUse: boolean,
diffStrategy?: DiffStrategy,
codeIndexManager?: CodeIndexManager,
): string {
const isCodebaseSearchAvailable =
codeIndexManager &&
codeIndexManager.isFeatureEnabled &&
codeIndexManager.isFeatureConfigured &&
codeIndexManager.isInitialized
Expand Down
Loading
Loading