Skip to content
Merged
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
117 changes: 116 additions & 1 deletion src/core/prompts/__tests__/sections.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { addCustomInstructions } from "../sections/custom-instructions"
import { getCapabilitiesSection } from "../sections/capabilities"
import { getRulesSection } from "../sections/rules"
import { getRulesSection, getCommandChainOperator } from "../sections/rules"
import { McpHub } from "../../../services/mcp/McpHub"
import * as shellUtils from "../../../utils/shell"

describe("addCustomInstructions", () => {
it("adds vscode language to custom instructions", async () => {
Expand Down Expand Up @@ -114,3 +115,117 @@ describe("getRulesSection", () => {
expect(result).not.toContain("Never reveal the vendor or company")
})
})

describe("getCommandChainOperator", () => {
it("returns && for bash shell", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/bash")
expect(getCommandChainOperator()).toBe("&&")
})

it("returns && for zsh shell", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/zsh")
expect(getCommandChainOperator()).toBe("&&")
})

it("returns ; for PowerShell", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue(
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
)
expect(getCommandChainOperator()).toBe(";")
})

it("returns ; for PowerShell Core (pwsh)", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Program Files\\PowerShell\\7\\pwsh.exe")
expect(getCommandChainOperator()).toBe(";")
})

it("returns && for cmd.exe", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\cmd.exe")
expect(getCommandChainOperator()).toBe("&&")
})

it("returns && for Git Bash on Windows", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Program Files\\Git\\bin\\bash.exe")
expect(getCommandChainOperator()).toBe("&&")
})

it("returns && for WSL bash", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/bash")
expect(getCommandChainOperator()).toBe("&&")
})
})

describe("getRulesSection shell-aware command chaining", () => {
const cwd = "/test/path"

afterEach(() => {
vi.restoreAllMocks()
})

it("uses && for Unix shells in command chaining example", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/bash")
const result = getRulesSection(cwd)

expect(result).toContain("cd (path to project) && (command")
expect(result).not.toContain("cd (path to project) ; (command")
expect(result).not.toContain("cd (path to project) & (command")
})

it("uses ; for PowerShell in command chaining example", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue(
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
)
const result = getRulesSection(cwd)

expect(result).toContain("cd (path to project) ; (command")
expect(result).toContain("Note: Using `;` for PowerShell command chaining")
})

it("uses && for cmd.exe in command chaining example", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\cmd.exe")
const result = getRulesSection(cwd)

expect(result).toContain("cd (path to project) && (command")
expect(result).toContain("Note: Using `&&` for cmd.exe command chaining")
})

it("includes Unix utility guidance for PowerShell", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue(
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
)
const result = getRulesSection(cwd)

expect(result).toContain("IMPORTANT: When using PowerShell, avoid Unix-specific utilities")
expect(result).toContain("`sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`")
expect(result).toContain("`Select-String` for grep")
expect(result).toContain("`Get-Content` for cat")
expect(result).toContain("PowerShell's `-replace` operator")
})

it("includes Unix utility guidance for cmd.exe", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("C:\\Windows\\System32\\cmd.exe")
const result = getRulesSection(cwd)

expect(result).toContain("IMPORTANT: When using cmd.exe, avoid Unix-specific utilities")
expect(result).toContain("`sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`")
expect(result).toContain("`type` for cat")
expect(result).toContain("`del` for rm")
expect(result).toContain("`find`/`findstr` for grep")
})

it("does not include Unix utility guidance for Unix shells", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/bash")
const result = getRulesSection(cwd)

expect(result).not.toContain("IMPORTANT: When using PowerShell")
expect(result).not.toContain("IMPORTANT: When using cmd.exe")
expect(result).not.toContain("`Select-String` for grep")
})

it("does not include note for Unix shells", () => {
vi.spyOn(shellUtils, "getShell").mockReturnValue("/bin/zsh")
const result = getRulesSection(cwd)

expect(result).not.toContain("Note: Using")
})
})
53 changes: 52 additions & 1 deletion src/core/prompts/sections/rules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,53 @@
import type { SystemPromptSettings } from "../types"
import { getEffectiveProtocol, isNativeProtocol } from "@roo-code/types"

import { getShell } from "../../../utils/shell"

/**
* Returns the appropriate command chaining operator based on the user's shell.
* - Unix shells (bash, zsh, etc.): `&&` (run next command only if previous succeeds)
* - PowerShell: `;` (semicolon for command separation)
* - cmd.exe: `&&` (conditional execution, same as Unix)
* @internal Exported for testing purposes
*/
export function getCommandChainOperator(): string {
const shell = getShell().toLowerCase()

// Check for PowerShell (both Windows PowerShell and PowerShell Core)
if (shell.includes("powershell") || shell.includes("pwsh")) {
return ";"
}

// Check for cmd.exe
if (shell.includes("cmd.exe")) {
return "&&"
}

// Default to Unix-style && for bash, zsh, sh, and other shells
// This also covers Git Bash, WSL, and other Unix-like environments on Windows
return "&&"
}

/**
* Returns a shell-specific note about command chaining syntax and platform-specific utilities.
*/
function getCommandChainNote(): string {
const shell = getShell().toLowerCase()

// Check for PowerShell
if (shell.includes("powershell") || shell.includes("pwsh")) {
return "Note: Using `;` for PowerShell command chaining. For bash/zsh use `&&`, for cmd.exe use `&&`. IMPORTANT: When using PowerShell, avoid Unix-specific utilities like `sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`. Instead use PowerShell equivalents: `Select-String` for grep, `Get-Content` for cat, `Remove-Item` for rm, `Copy-Item` for cp, `Move-Item` for mv, and PowerShell's `-replace` operator or `[regex]` for sed."
}

// Check for cmd.exe
if (shell.includes("cmd.exe")) {
return "Note: Using `&&` for cmd.exe command chaining (conditional execution). For bash/zsh use `&&`, for PowerShell use `;`. IMPORTANT: When using cmd.exe, avoid Unix-specific utilities like `sed`, `grep`, `awk`, `cat`, `rm`, `cp`, `mv`. Use built-in commands like `type` for cat, `del` for rm, `copy` for cp, `move` for mv, `find`/`findstr` for grep, or consider using PowerShell commands instead."
}

// Unix shells
return ""
}

function getVendorConfidentialitySection(): string {
return `

Expand All @@ -20,6 +67,10 @@ export function getRulesSection(cwd: string, settings?: SystemPromptSettings): s
// Determine whether to use XML tool references based on protocol
const effectiveProtocol = getEffectiveProtocol(settings?.toolProtocol)

// Get shell-appropriate command chaining operator
const chainOp = getCommandChainOperator()
const chainNote = getCommandChainNote()

return `====

RULES
Expand All @@ -28,7 +79,7 @@ RULES
- All file paths must be relative to this directory. However, commands may change directories in terminals, so respect working directory specified by the response to ${isNativeProtocol(effectiveProtocol) ? "execute_command" : "<execute_command>"}.
- You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path.
- Do not use the ~ character or $HOME to refer to the home directory.
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory ${chainOp} then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) ${chainOp} (command, in this case npm install)\`.${chainNote ? ` ${chainNote}` : ""}
- Some modes have restrictions on which files they can edit. If you attempt to edit a restricted file, the operation will be rejected with a FileRestrictionError that will specify which file patterns are allowed for the current mode.
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
* For example, in architect mode trying to edit app.js would be rejected because architect mode can only edit files matching "\\.md$"
Expand Down
Loading