mirror of
https://github.com/cline/cline.git
synced 2026-09-02 07:42:19 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe53ab914e | |||
| 3881e3d2d5 | |||
| ba6e1671cb | |||
| f215cadf2a | |||
| e591c2af54 | |||
| 899d334f0d | |||
| 859bf80ecb | |||
| 01736423ac |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add AGENTS.md support
|
||||
@@ -81,6 +81,20 @@ your-project/
|
||||
|
||||
Cline's system prompt, on the other hand, is not user-editable ([here's where you can find it](https://github.com/cline/cline/blob/main/src/core/prompts/system.ts)). For a broader look at prompt engineering best practices, check out [this resource](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview).
|
||||
|
||||
### AGENTS.md Standard Support
|
||||
|
||||
Cline also supports the [AGENTS.md](https://agents.md/) standard as a fallback
|
||||
(in addition to Cline Rules) by automatically detecting `AGENTS.md` files in
|
||||
your workspace root. This allows you to use the same rules file across different AI
|
||||
coding tools.
|
||||
|
||||
```
|
||||
your-project/
|
||||
├── AGENTS.md
|
||||
├── src/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Tips for Writing Effective Cline Rules
|
||||
|
||||
- Be Clear and Concise: Use simple language and avoid ambiguity.
|
||||
|
||||
+13
-2
@@ -49,6 +49,9 @@ service FileService {
|
||||
// Toggle a Windsurf rule (enable or disable)
|
||||
rpc toggleWindsurfRule(ToggleWindsurfRuleRequest) returns (ClineRulesToggles);
|
||||
|
||||
// Toggle an Agents rule (enable or disable)
|
||||
rpc toggleAgentsRule(ToggleAgentsRuleRequest) returns (ClineRulesToggles);
|
||||
|
||||
// Refreshes all rule toggles (Cline, External, and Workflows)
|
||||
rpc refreshRules(EmptyRequest) returns (RefreshedRules);
|
||||
|
||||
@@ -74,8 +77,9 @@ message RefreshedRules {
|
||||
ClineRulesToggles local_cline_rules_toggles = 2;
|
||||
ClineRulesToggles local_cursor_rules_toggles = 3;
|
||||
ClineRulesToggles local_windsurf_rules_toggles = 4;
|
||||
ClineRulesToggles local_workflow_toggles = 5;
|
||||
ClineRulesToggles global_workflow_toggles = 6;
|
||||
ClineRulesToggles local_agents_rules_toggles = 5;
|
||||
ClineRulesToggles local_workflow_toggles = 6;
|
||||
ClineRulesToggles global_workflow_toggles = 7;
|
||||
}
|
||||
|
||||
// Request to toggle a Windsurf rule
|
||||
@@ -85,6 +89,13 @@ message ToggleWindsurfRuleRequest {
|
||||
bool enabled = 3; // Whether to enable or disable the rule
|
||||
}
|
||||
|
||||
// Request to toggle an Agents rule
|
||||
message ToggleAgentsRuleRequest {
|
||||
Metadata metadata = 1;
|
||||
string rule_path = 2; // Path to the rule file
|
||||
bool enabled = 3; // Whether to enable or disable the rule
|
||||
}
|
||||
|
||||
// Request to convert a list of URIs to relative paths
|
||||
message RelativePathsRequest {
|
||||
Metadata metadata = 1;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import {
|
||||
combineRuleToggles,
|
||||
getRuleFilesTotalContent,
|
||||
@@ -6,14 +9,53 @@ import {
|
||||
} from "@core/context/instructions/user-instructions/rule-helpers"
|
||||
import { formatResponse } from "@core/prompts/responses"
|
||||
import { GlobalFileNames } from "@core/storage/disk"
|
||||
import { listFiles } from "@services/glob/list-files"
|
||||
import { ClineRulesToggles } from "@shared/cline-rules"
|
||||
import { fileExistsAtPath, isDirectory } from "@utils/fs"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { Controller } from "@/core/controller"
|
||||
|
||||
// Types for better code clarity
|
||||
type RuleSource = {
|
||||
filePath: string
|
||||
extension?: string
|
||||
}
|
||||
|
||||
type RuleConfig = {
|
||||
stateKey: "localWindsurfRulesToggles" | "localCursorRulesToggles" | "localAgentsRulesToggles"
|
||||
sources: RuleSource[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the toggles for windsurf and cursor rules
|
||||
* Check if a directory is a sensitive location (home directory or Desktop)
|
||||
* Returns true if the directory is safe to process rules from
|
||||
*/
|
||||
function isSafeDirectory(workingDirectory: string): boolean {
|
||||
const normalizedPath = path.resolve(workingDirectory)
|
||||
const homeDir = os.homedir()
|
||||
const desktopDir = path.join(homeDir, "Desktop")
|
||||
|
||||
// Don't process rules from home directory or Desktop
|
||||
if (normalizedPath === homeDir || normalizedPath === desktopDir) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to synchronize a single rule source
|
||||
*/
|
||||
async function syncRuleSource(
|
||||
workingDirectory: string,
|
||||
source: RuleSource,
|
||||
currentToggles: ClineRulesToggles,
|
||||
): Promise<ClineRulesToggles> {
|
||||
const fullPath = path.resolve(workingDirectory, source.filePath)
|
||||
return await synchronizeRuleToggles(fullPath, currentToggles, source.extension)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the toggles for windsurf, cursor, and agents rules
|
||||
*/
|
||||
export async function refreshExternalRulesToggles(
|
||||
controller: Controller,
|
||||
@@ -21,30 +63,86 @@ export async function refreshExternalRulesToggles(
|
||||
): Promise<{
|
||||
windsurfLocalToggles: ClineRulesToggles
|
||||
cursorLocalToggles: ClineRulesToggles
|
||||
agentsLocalToggles: ClineRulesToggles
|
||||
}> {
|
||||
// local windsurf toggles
|
||||
const localWindsurfRulesToggles = controller.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
|
||||
const localWindsurfRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.windsurfRules)
|
||||
const updatedLocalWindsurfToggles = await synchronizeRuleToggles(localWindsurfRulesFilePath, localWindsurfRulesToggles)
|
||||
controller.stateManager.setWorkspaceState("localWindsurfRulesToggles", updatedLocalWindsurfToggles)
|
||||
// Safety check: Don't process rules from home directory or Desktop
|
||||
if (!isSafeDirectory(workingDirectory)) {
|
||||
// Return empty toggles for unsafe directories
|
||||
return {
|
||||
windsurfLocalToggles: {},
|
||||
cursorLocalToggles: {},
|
||||
agentsLocalToggles: {},
|
||||
}
|
||||
}
|
||||
|
||||
// local cursor toggles
|
||||
const localCursorRulesToggles = controller.stateManager.getWorkspaceStateKey("localCursorRulesToggles")
|
||||
const configs: Record<string, RuleConfig> = {
|
||||
windsurf: {
|
||||
stateKey: "localWindsurfRulesToggles",
|
||||
sources: [{ filePath: GlobalFileNames.windsurfRules }],
|
||||
},
|
||||
cursor: {
|
||||
stateKey: "localCursorRulesToggles",
|
||||
sources: [
|
||||
{ filePath: GlobalFileNames.cursorRulesDir, extension: ".mdc" },
|
||||
{ filePath: GlobalFileNames.cursorRulesFile },
|
||||
],
|
||||
},
|
||||
agents: {
|
||||
stateKey: "localAgentsRulesToggles",
|
||||
sources: [{ filePath: GlobalFileNames.agentsRulesFile }],
|
||||
},
|
||||
}
|
||||
|
||||
// cursor has two valid locations for rules files, so we need to check both and combine
|
||||
// synchronizeRuleToggles will drop whichever rules files are not in each given path, but combining the results will result in no data loss
|
||||
let localCursorRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.cursorRulesDir)
|
||||
const updatedLocalCursorToggles1 = await synchronizeRuleToggles(localCursorRulesFilePath, localCursorRulesToggles, ".mdc")
|
||||
// Process windsurf
|
||||
const windsurfConfig = configs.windsurf
|
||||
const windsurfToggles = controller.stateManager.getWorkspaceStateKey(windsurfConfig.stateKey)
|
||||
const windsurfLocalToggles = await syncRuleSource(workingDirectory, windsurfConfig.sources[0], windsurfToggles)
|
||||
controller.stateManager.setWorkspaceState(windsurfConfig.stateKey, windsurfLocalToggles)
|
||||
|
||||
localCursorRulesFilePath = path.resolve(workingDirectory, GlobalFileNames.cursorRulesFile)
|
||||
const updatedLocalCursorToggles2 = await synchronizeRuleToggles(localCursorRulesFilePath, localCursorRulesToggles)
|
||||
// Process cursor (combine results from both sources)
|
||||
const cursorConfig = configs.cursor
|
||||
const cursorToggles = controller.stateManager.getWorkspaceStateKey(cursorConfig.stateKey)
|
||||
const [cursorToggles1, cursorToggles2] = await Promise.all([
|
||||
syncRuleSource(workingDirectory, cursorConfig.sources[0], cursorToggles),
|
||||
syncRuleSource(workingDirectory, cursorConfig.sources[1], cursorToggles),
|
||||
])
|
||||
const cursorLocalToggles = combineRuleToggles(cursorToggles1, cursorToggles2)
|
||||
controller.stateManager.setWorkspaceState(cursorConfig.stateKey, cursorLocalToggles)
|
||||
|
||||
const updatedLocalCursorToggles = combineRuleToggles(updatedLocalCursorToggles1, updatedLocalCursorToggles2)
|
||||
controller.stateManager.setWorkspaceState("localCursorRulesToggles", updatedLocalCursorToggles)
|
||||
// Process agents
|
||||
const agentsConfig = configs.agents
|
||||
const agentsToggles = controller.stateManager.getWorkspaceStateKey(agentsConfig.stateKey)
|
||||
const agentsLocalToggles = await syncRuleSource(workingDirectory, agentsConfig.sources[0], agentsToggles)
|
||||
controller.stateManager.setWorkspaceState(agentsConfig.stateKey, agentsLocalToggles)
|
||||
|
||||
return {
|
||||
windsurfLocalToggles: updatedLocalWindsurfToggles,
|
||||
cursorLocalToggles: updatedLocalCursorToggles,
|
||||
windsurfLocalToggles,
|
||||
cursorLocalToggles,
|
||||
agentsLocalToggles,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to read a single rule file
|
||||
*/
|
||||
async function readRuleFile(filePath: string, toggles: ClineRulesToggles): Promise<string | undefined> {
|
||||
// Check if file exists and is enabled
|
||||
if (!(await fileExistsAtPath(filePath))) {
|
||||
return undefined
|
||||
}
|
||||
if (await isDirectory(filePath)) {
|
||||
return undefined
|
||||
}
|
||||
if (filePath in toggles && toggles[filePath] === false) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const content = (await fs.readFile(filePath, "utf8")).trim()
|
||||
return content || undefined
|
||||
} catch (error) {
|
||||
console.error(`Failed to read rule file at ${filePath}:`, error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,68 +150,120 @@ export async function refreshExternalRulesToggles(
|
||||
* Gather formatted windsurf rules
|
||||
*/
|
||||
export const getLocalWindsurfRules = async (cwd: string, toggles: ClineRulesToggles) => {
|
||||
const windsurfRulesFilePath = path.resolve(cwd, GlobalFileNames.windsurfRules)
|
||||
|
||||
let windsurfRulesFileInstructions: string | undefined
|
||||
|
||||
if (await fileExistsAtPath(windsurfRulesFilePath)) {
|
||||
if (!(await isDirectory(windsurfRulesFilePath))) {
|
||||
try {
|
||||
if (windsurfRulesFilePath in toggles && toggles[windsurfRulesFilePath] !== false) {
|
||||
const ruleFileContent = (await fs.readFile(windsurfRulesFilePath, "utf8")).trim()
|
||||
if (ruleFileContent) {
|
||||
windsurfRulesFileInstructions = formatResponse.windsurfRulesLocalFileInstructions(cwd, ruleFileContent)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .windsurfrules file at ${windsurfRulesFilePath}`)
|
||||
}
|
||||
}
|
||||
// Safety check: Don't process rules from home directory or Desktop
|
||||
if (!isSafeDirectory(cwd)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return windsurfRulesFileInstructions
|
||||
const filePath = path.resolve(cwd, GlobalFileNames.windsurfRules)
|
||||
const content = await readRuleFile(filePath, toggles)
|
||||
|
||||
return content ? formatResponse.windsurfRulesLocalFileInstructions(cwd, content) : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather formatted cursor rules, which can come from two sources
|
||||
*/
|
||||
export const getLocalCursorRules = async (cwd: string, toggles: ClineRulesToggles) => {
|
||||
// we first check for the .cursorrules file
|
||||
// Safety check: Don't process rules from home directory or Desktop
|
||||
if (!isSafeDirectory(cwd)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const results: (string | undefined)[] = []
|
||||
|
||||
// Check .cursorrules file
|
||||
const cursorRulesFilePath = path.resolve(cwd, GlobalFileNames.cursorRulesFile)
|
||||
let cursorRulesFileInstructions: string | undefined
|
||||
|
||||
if (await fileExistsAtPath(cursorRulesFilePath)) {
|
||||
if (!(await isDirectory(cursorRulesFilePath))) {
|
||||
try {
|
||||
if (cursorRulesFilePath in toggles && toggles[cursorRulesFilePath] !== false) {
|
||||
const ruleFileContent = (await fs.readFile(cursorRulesFilePath, "utf8")).trim()
|
||||
if (ruleFileContent) {
|
||||
cursorRulesFileInstructions = formatResponse.cursorRulesLocalFileInstructions(cwd, ruleFileContent)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .cursorrules file at ${cursorRulesFilePath}`)
|
||||
}
|
||||
}
|
||||
const fileContent = await readRuleFile(cursorRulesFilePath, toggles)
|
||||
if (fileContent) {
|
||||
results.push(formatResponse.cursorRulesLocalFileInstructions(cwd, fileContent))
|
||||
}
|
||||
|
||||
// we then check for the .cursor/rules dir
|
||||
// Check .cursor/rules directory
|
||||
const cursorRulesDirPath = path.resolve(cwd, GlobalFileNames.cursorRulesDir)
|
||||
let cursorRulesDirInstructions: string | undefined
|
||||
|
||||
if (await fileExistsAtPath(cursorRulesDirPath)) {
|
||||
if (await isDirectory(cursorRulesDirPath)) {
|
||||
try {
|
||||
const rulesFilePaths = await readDirectoryRecursive(cursorRulesDirPath, ".mdc")
|
||||
const rulesFilesTotalContent = await getRuleFilesTotalContent(rulesFilePaths, cwd, toggles)
|
||||
if (rulesFilesTotalContent) {
|
||||
cursorRulesDirInstructions = formatResponse.cursorRulesLocalDirectoryInstructions(cwd, rulesFilesTotalContent)
|
||||
}
|
||||
} catch {
|
||||
console.error(`Failed to read .cursor/rules directory at ${cursorRulesDirPath}`)
|
||||
if ((await fileExistsAtPath(cursorRulesDirPath)) && (await isDirectory(cursorRulesDirPath))) {
|
||||
try {
|
||||
const rulesFilePaths = await readDirectoryRecursive(cursorRulesDirPath, ".mdc")
|
||||
const rulesFilesTotalContent = await getRuleFilesTotalContent(rulesFilePaths, cwd, toggles)
|
||||
if (rulesFilesTotalContent) {
|
||||
results.push(formatResponse.cursorRulesLocalDirectoryInstructions(cwd, rulesFilesTotalContent))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to read .cursor/rules directory at ${cursorRulesDirPath}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
return [cursorRulesFileInstructions, cursorRulesDirInstructions]
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to find all agents.md files recursively (case-insensitive)
|
||||
* Only searches if a top-level agents.md file exists
|
||||
*/
|
||||
async function findAgentsMdFiles(cwd: string): Promise<string[]> {
|
||||
// First check if top-level agents.md exists
|
||||
const topLevelAgentsPath = path.resolve(cwd, GlobalFileNames.agentsRulesFile)
|
||||
if (!(await fileExistsAtPath(topLevelAgentsPath))) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
// Search recursively for all agents.md files
|
||||
const [allFiles] = await listFiles(cwd, true, 500)
|
||||
const agentsFileName = GlobalFileNames.agentsRulesFile.toLowerCase()
|
||||
|
||||
return allFiles.filter((filePath) => path.basename(filePath).toLowerCase() === agentsFileName)
|
||||
} catch (error) {
|
||||
console.error(`Failed to find agents.md files in ${cwd}:`, error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather formatted agents rules - searches recursively and combines all agents.md files
|
||||
*/
|
||||
export const getLocalAgentsRules = async (cwd: string, toggles: ClineRulesToggles) => {
|
||||
// Safety check: Don't process rules from home directory or Desktop
|
||||
if (!isSafeDirectory(cwd)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const agentsRulesFilePath = path.resolve(cwd, GlobalFileNames.agentsRulesFile)
|
||||
|
||||
// Check if the top-level agents.md file is enabled
|
||||
if (agentsRulesFilePath in toggles && toggles[agentsRulesFilePath] === false) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const agentsMdFiles = await findAgentsMdFiles(cwd)
|
||||
if (agentsMdFiles.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Read and combine all agents.md files in parallel
|
||||
const contentPromises = agentsMdFiles.map(async (filePath) => {
|
||||
try {
|
||||
const fullPath = path.resolve(cwd, filePath)
|
||||
const content = (await fs.readFile(fullPath, "utf8")).trim()
|
||||
if (!content) {
|
||||
return null
|
||||
}
|
||||
|
||||
const relativePath = path.relative(cwd, fullPath)
|
||||
return `## ${relativePath}\n\n${content}`
|
||||
} catch (error) {
|
||||
console.error(`Failed to read agents.md file at ${filePath}:`, error)
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
const contents = await Promise.all(contentPromises)
|
||||
const combinedContent = contents.filter(Boolean).join("\n\n")
|
||||
|
||||
return combinedContent ? formatResponse.agentsRulesLocalFileInstructions(cwd, combinedContent) : undefined
|
||||
} catch (error) {
|
||||
console.error("Failed to read agents.md files:", error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +299,10 @@ export async function deleteRuleFile(
|
||||
const toggles = controller.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
|
||||
delete toggles[rulePath]
|
||||
controller.stateManager.setWorkspaceState("localWindsurfRulesToggles", toggles)
|
||||
} else if (type === "agents") {
|
||||
const toggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
|
||||
delete toggles[rulePath]
|
||||
controller.stateManager.setWorkspaceState("localAgentsRulesToggles", toggles)
|
||||
} else {
|
||||
const toggles = controller.stateManager.getWorkspaceStateKey("localClineRulesToggles")
|
||||
delete toggles[rulePath]
|
||||
|
||||
@@ -16,7 +16,10 @@ export async function refreshRules(controller: Controller, _request: EmptyReques
|
||||
try {
|
||||
const cwd = await getCwd(getDesktopDir())
|
||||
const { globalToggles, localToggles } = await refreshClineRulesToggles(controller, cwd)
|
||||
const { cursorLocalToggles, windsurfLocalToggles } = await refreshExternalRulesToggles(controller, cwd)
|
||||
const { cursorLocalToggles, windsurfLocalToggles, agentsLocalToggles } = await refreshExternalRulesToggles(
|
||||
controller,
|
||||
cwd,
|
||||
)
|
||||
const { localWorkflowToggles, globalWorkflowToggles } = await refreshWorkflowToggles(controller, cwd)
|
||||
|
||||
return RefreshedRules.create({
|
||||
@@ -24,6 +27,7 @@ export async function refreshRules(controller: Controller, _request: EmptyReques
|
||||
localClineRulesToggles: { toggles: localToggles },
|
||||
localCursorRulesToggles: { toggles: cursorLocalToggles },
|
||||
localWindsurfRulesToggles: { toggles: windsurfLocalToggles },
|
||||
localAgentsRulesToggles: { toggles: agentsLocalToggles },
|
||||
localWorkflowToggles: { toggles: localWorkflowToggles },
|
||||
globalWorkflowToggles: { toggles: globalWorkflowToggles },
|
||||
})
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { ToggleAgentsRuleRequest } from "@shared/proto/cline/file"
|
||||
import { ClineRulesToggles } from "@shared/proto/cline/file"
|
||||
import type { Controller } from "../index"
|
||||
|
||||
/**
|
||||
* Toggles an Agents rule (enable or disable)
|
||||
* @param controller The controller instance
|
||||
* @param request The toggle request
|
||||
* @returns The updated Agents rule toggles
|
||||
*/
|
||||
export async function toggleAgentsRule(controller: Controller, request: ToggleAgentsRuleRequest): Promise<ClineRulesToggles> {
|
||||
const { rulePath, enabled } = request
|
||||
|
||||
if (!rulePath || typeof enabled !== "boolean") {
|
||||
console.error("toggleAgentsRule: Missing or invalid parameters", {
|
||||
rulePath,
|
||||
enabled: typeof enabled === "boolean" ? enabled : `Invalid: ${typeof enabled}`,
|
||||
})
|
||||
throw new Error("Missing or invalid parameters for toggleAgentsRule")
|
||||
}
|
||||
|
||||
// Update the toggle in workspace state
|
||||
const toggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
|
||||
toggles[rulePath] = enabled
|
||||
controller.stateManager.setWorkspaceState("localAgentsRulesToggles", toggles)
|
||||
|
||||
// Get the current state to return in the response
|
||||
const agentsToggles = controller.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
|
||||
|
||||
return ClineRulesToggles.create({
|
||||
toggles: agentsToggles,
|
||||
})
|
||||
}
|
||||
@@ -891,6 +891,7 @@ export class Controller {
|
||||
const localClineRulesToggles = this.stateManager.getWorkspaceStateKey("localClineRulesToggles")
|
||||
const localWindsurfRulesToggles = this.stateManager.getWorkspaceStateKey("localWindsurfRulesToggles")
|
||||
const localCursorRulesToggles = this.stateManager.getWorkspaceStateKey("localCursorRulesToggles")
|
||||
const localAgentsRulesToggles = this.stateManager.getWorkspaceStateKey("localAgentsRulesToggles")
|
||||
const workflowToggles = this.stateManager.getWorkspaceStateKey("workflowToggles")
|
||||
const autoCondenseThreshold = this.stateManager.getGlobalSettingsKey("autoCondenseThreshold")
|
||||
|
||||
@@ -946,6 +947,7 @@ export class Controller {
|
||||
localClineRulesToggles: localClineRulesToggles || {},
|
||||
localWindsurfRulesToggles: localWindsurfRulesToggles || {},
|
||||
localCursorRulesToggles: localCursorRulesToggles || {},
|
||||
localAgentsRulesToggles: localAgentsRulesToggles || {},
|
||||
localWorkflowToggles: workflowToggles || {},
|
||||
globalWorkflowToggles: globalWorkflowToggles || {},
|
||||
remoteRulesToggles: remoteRulesToggles,
|
||||
|
||||
@@ -249,6 +249,9 @@ Otherwise, if you have not completed the task and do not need additional informa
|
||||
cursorRulesLocalDirectoryInstructions: (cwd: string, content: string) =>
|
||||
`# .cursor/rules\n\nThe following is provided by a root-level .cursor/rules directory where the user has specified instructions for this working directory (${cwd.toPosix()})\n\n${content}`,
|
||||
|
||||
agentsRulesLocalFileInstructions: (cwd: string, content: string) =>
|
||||
`# AGENTS.md\n\nThe following is provided by AGENTS.md files found recursively throughout this working directory (${cwd.toPosix()}) where the user has specified instructions. Nested AGENTS.md will be combined below, and you should only apply the instructions for each AGENTS.md file that is directly applicable to the current task, i.e. if you are reading or writing to a file in that directory.\n\n${content}`,
|
||||
|
||||
fileContextWarning: (editedFiles: string[]): string => {
|
||||
const fileCount = editedFiles.length
|
||||
const fileVerb = fileCount === 1 ? "file has" : "files have"
|
||||
|
||||
@@ -15,6 +15,7 @@ export async function getUserInstructions(variant: PromptVariant, context: Syste
|
||||
context.localCursorRulesFileInstructions,
|
||||
context.localCursorRulesDirInstructions,
|
||||
context.localWindsurfRulesFileInstructions,
|
||||
context.localAgentsRulesFileInstructions,
|
||||
context.clineIgnoreInstructions,
|
||||
context.preferredLanguageInstructions,
|
||||
)
|
||||
@@ -37,6 +38,7 @@ function buildUserInstructions(
|
||||
localCursorRulesFileInstructions?: string,
|
||||
localCursorRulesDirInstructions?: string,
|
||||
localWindsurfRulesFileInstructions?: string,
|
||||
localAgentsRulesFileInstructions?: string,
|
||||
clineIgnoreInstructions?: string,
|
||||
preferredLanguageInstructions?: string,
|
||||
): string | undefined {
|
||||
@@ -59,6 +61,9 @@ function buildUserInstructions(
|
||||
if (localWindsurfRulesFileInstructions) {
|
||||
customInstructions.push(localWindsurfRulesFileInstructions)
|
||||
}
|
||||
if (localAgentsRulesFileInstructions) {
|
||||
customInstructions.push(localAgentsRulesFileInstructions)
|
||||
}
|
||||
if (clineIgnoreInstructions) {
|
||||
customInstructions.push(clineIgnoreInstructions)
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ export interface SystemPromptContext {
|
||||
readonly localCursorRulesFileInstructions?: string
|
||||
readonly localCursorRulesDirInstructions?: string
|
||||
readonly localWindsurfRulesFileInstructions?: string
|
||||
readonly localAgentsRulesFileInstructions?: string
|
||||
readonly clineIgnoreInstructions?: string
|
||||
readonly preferredLanguageInstructions?: string
|
||||
readonly browserSettings?: BrowserSettings
|
||||
|
||||
@@ -29,6 +29,7 @@ export const GlobalFileNames = {
|
||||
cursorRulesDir: ".cursor/rules",
|
||||
cursorRulesFile: ".cursorrules",
|
||||
windsurfRules: ".windsurfrules",
|
||||
agentsRulesFile: "AGENTS.md",
|
||||
taskMetadata: "task_metadata.json",
|
||||
mcpMarketplaceCatalog: "mcp_marketplace_catalog.json",
|
||||
remoteConfig: (orgId: string) => `remote_config_${orgId}.json`,
|
||||
|
||||
@@ -153,12 +153,14 @@ export async function readWorkspaceStateFromDisk(context: ExtensionContext): Pro
|
||||
const localClineRulesToggles = context.workspaceState.get("localClineRulesToggles") as ClineRulesToggles | undefined
|
||||
const localWindsurfRulesToggles = context.workspaceState.get("localWindsurfRulesToggles") as ClineRulesToggles | undefined
|
||||
const localCursorRulesToggles = context.workspaceState.get("localCursorRulesToggles") as ClineRulesToggles | undefined
|
||||
const localAgentsRulesToggles = context.workspaceState.get("localAgentsRulesToggles") as ClineRulesToggles | undefined
|
||||
const localWorkflowToggles = context.workspaceState.get("workflowToggles") as ClineRulesToggles | undefined
|
||||
|
||||
return {
|
||||
localClineRulesToggles: localClineRulesToggles || {},
|
||||
localWindsurfRulesToggles: localWindsurfRulesToggles || {},
|
||||
localCursorRulesToggles: localCursorRulesToggles || {},
|
||||
localAgentsRulesToggles: localAgentsRulesToggles || {},
|
||||
workflowToggles: localWorkflowToggles || {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
refreshClineRulesToggles,
|
||||
} from "@core/context/instructions/user-instructions/cline-rules"
|
||||
import {
|
||||
getLocalAgentsRules,
|
||||
getLocalCursorRules,
|
||||
getLocalWindsurfRules,
|
||||
refreshExternalRulesToggles,
|
||||
@@ -1985,7 +1986,10 @@ export class Task {
|
||||
}
|
||||
|
||||
const { globalToggles, localToggles } = await refreshClineRulesToggles(this.controller, this.cwd)
|
||||
const { windsurfLocalToggles, cursorLocalToggles } = await refreshExternalRulesToggles(this.controller, this.cwd)
|
||||
const { windsurfLocalToggles, cursorLocalToggles, agentsLocalToggles } = await refreshExternalRulesToggles(
|
||||
this.controller,
|
||||
this.cwd,
|
||||
)
|
||||
|
||||
const globalClineRulesFilePath = await ensureRulesDirectoryExists()
|
||||
const globalClineRulesFileInstructions = await getGlobalClineRules(globalClineRulesFilePath, globalToggles)
|
||||
@@ -1997,6 +2001,8 @@ export class Task {
|
||||
)
|
||||
const localWindsurfRulesFileInstructions = await getLocalWindsurfRules(this.cwd, windsurfLocalToggles)
|
||||
|
||||
const localAgentsRulesFileInstructions = await getLocalAgentsRules(this.cwd, agentsLocalToggles)
|
||||
|
||||
const clineIgnoreContent = this.clineIgnoreController.clineIgnoreContent
|
||||
let clineIgnoreInstructions: string | undefined
|
||||
if (clineIgnoreContent) {
|
||||
@@ -2032,6 +2038,7 @@ export class Task {
|
||||
localCursorRulesFileInstructions,
|
||||
localCursorRulesDirInstructions,
|
||||
localWindsurfRulesFileInstructions,
|
||||
localAgentsRulesFileInstructions,
|
||||
clineIgnoreInstructions,
|
||||
preferredLanguageInstructions,
|
||||
browserSettings: this.stateManager.getGlobalSettingsKey("browserSettings"),
|
||||
|
||||
@@ -81,6 +81,7 @@ export interface ExtensionState {
|
||||
localWindsurfRulesToggles: ClineRulesToggles
|
||||
remoteRulesToggles?: ClineRulesToggles
|
||||
remoteWorkflowToggles?: ClineRulesToggles
|
||||
localAgentsRulesToggles: ClineRulesToggles
|
||||
mcpResponsesCollapsed?: boolean
|
||||
strictPlanModeEnabled?: boolean
|
||||
yoloModeToggled?: boolean
|
||||
|
||||
@@ -269,5 +269,6 @@ export interface LocalState {
|
||||
localClineRulesToggles: ClineRulesToggles
|
||||
localCursorRulesToggles: ClineRulesToggles
|
||||
localWindsurfRulesToggles: ClineRulesToggles
|
||||
localAgentsRulesToggles: ClineRulesToggles
|
||||
workflowToggles: ClineRulesToggles
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ClineRulesToggles,
|
||||
RefreshedRules,
|
||||
RuleScope,
|
||||
ToggleAgentsRuleRequest,
|
||||
ToggleClineRuleRequest,
|
||||
ToggleCursorRuleRequest,
|
||||
ToggleWindsurfRuleRequest,
|
||||
@@ -25,6 +26,7 @@ const ClineRulesToggleModal: React.FC = () => {
|
||||
localClineRulesToggles = {},
|
||||
localCursorRulesToggles = {},
|
||||
localWindsurfRulesToggles = {},
|
||||
localAgentsRulesToggles = {},
|
||||
localWorkflowToggles = {},
|
||||
globalWorkflowToggles = {},
|
||||
remoteRulesToggles = {},
|
||||
@@ -34,6 +36,7 @@ const ClineRulesToggleModal: React.FC = () => {
|
||||
setLocalClineRulesToggles,
|
||||
setLocalCursorRulesToggles,
|
||||
setLocalWindsurfRulesToggles,
|
||||
setLocalAgentsRulesToggles,
|
||||
setLocalWorkflowToggles,
|
||||
setGlobalWorkflowToggles,
|
||||
setRemoteRulesToggles,
|
||||
@@ -64,6 +67,9 @@ const ClineRulesToggleModal: React.FC = () => {
|
||||
if (response.localWindsurfRulesToggles?.toggles) {
|
||||
setLocalWindsurfRulesToggles(response.localWindsurfRulesToggles.toggles)
|
||||
}
|
||||
if (response.localAgentsRulesToggles?.toggles) {
|
||||
setLocalAgentsRulesToggles(response.localAgentsRulesToggles.toggles)
|
||||
}
|
||||
if (response.localWorkflowToggles?.toggles) {
|
||||
setLocalWorkflowToggles(response.localWorkflowToggles.toggles)
|
||||
}
|
||||
@@ -95,6 +101,10 @@ const ClineRulesToggleModal: React.FC = () => {
|
||||
.map(([path, enabled]): [string, boolean] => [path, enabled as boolean])
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
|
||||
const agentsRules = Object.entries(localAgentsRulesToggles || {})
|
||||
.map(([path, enabled]): [string, boolean] => [path, enabled as boolean])
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
|
||||
const localWorkflows = Object.entries(localWorkflowToggles || {})
|
||||
.map(([path, enabled]): [string, boolean] => [path, enabled as boolean])
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
@@ -172,6 +182,23 @@ const ClineRulesToggleModal: React.FC = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const toggleAgentsRule = (rulePath: string, enabled: boolean) => {
|
||||
FileServiceClient.toggleAgentsRule(
|
||||
ToggleAgentsRuleRequest.create({
|
||||
rulePath,
|
||||
enabled,
|
||||
} as ToggleAgentsRuleRequest),
|
||||
)
|
||||
.then((response: ClineRulesToggles) => {
|
||||
if (response.toggles) {
|
||||
setLocalAgentsRulesToggles(response.toggles)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error toggling Agents rule:", error)
|
||||
})
|
||||
}
|
||||
|
||||
const toggleWorkflow = (isGlobal: boolean, workflowPath: string, enabled: boolean) => {
|
||||
FileServiceClient.toggleWorkflow(
|
||||
ToggleWorkflowRequest.create({
|
||||
@@ -330,7 +357,7 @@ const ClineRulesToggleModal: React.FC = () => {
|
||||
<VSCodeLink
|
||||
className="text-xs"
|
||||
href="https://docs.cline.bot/features/cline-rules"
|
||||
style={{ display: "inline" }}>
|
||||
style={{ display: "inline", fontSize: "inherit" }}>
|
||||
Docs
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
@@ -408,6 +435,7 @@ const ClineRulesToggleModal: React.FC = () => {
|
||||
showNoRules={false}
|
||||
toggleRule={(rulePath, enabled) => toggleRule(false, rulePath, enabled)}
|
||||
/>
|
||||
|
||||
<RulesToggleList
|
||||
isGlobal={false}
|
||||
listGap="small"
|
||||
@@ -422,10 +450,19 @@ const ClineRulesToggleModal: React.FC = () => {
|
||||
listGap="small"
|
||||
rules={windsurfRules}
|
||||
ruleType={"windsurf"}
|
||||
showNewRule={true}
|
||||
showNewRule={false}
|
||||
showNoRules={false}
|
||||
toggleRule={toggleWindsurfRule}
|
||||
/>
|
||||
<RulesToggleList
|
||||
isGlobal={false}
|
||||
listGap="small"
|
||||
rules={agentsRules}
|
||||
ruleType={"agents"}
|
||||
showNewRule={true}
|
||||
showNoRules={false}
|
||||
toggleRule={toggleAgentsRule}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { StringRequest } from "@shared/proto/cline/common"
|
||||
import { RuleFileRequest } from "@shared/proto/index.cline"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
||||
import { FileServiceClient } from "@/services/grpc-client"
|
||||
|
||||
const RuleRow: React.FC<{
|
||||
@@ -57,6 +58,20 @@ const RuleRow: React.FC<{
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
case "agents":
|
||||
return (
|
||||
<svg
|
||||
height="16"
|
||||
style={{ verticalAlign: "middle" }}
|
||||
viewBox="0 0 24 24"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5">
|
||||
<circle cx="12" cy="8" r="3" />
|
||||
<path d="M12 14c-4 0-6 2-6 4v2h12v-2c0-2-2-4-6-4z" />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
@@ -87,6 +102,18 @@ const RuleRow: React.FC<{
|
||||
<span className="flex-1 overflow-hidden break-all whitespace-normal flex items-center mr-1" title={rulePath}>
|
||||
{getRuleTypeIcon() && <span className="mr-1.5">{getRuleTypeIcon()}</span>}
|
||||
<span className="ph-no-capture">{finalDisplayName}</span>
|
||||
{ruleType === "agents" && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="mt-1 ml-1.5 cursor-help">
|
||||
<i className="codicon codicon-info" style={{ fontSize: "12px", opacity: 0.7 }} />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Searches recursively for all AGENTS.md files in the workspace when a top-level AGENTS.md exists
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
|
||||
{/* Toggle Switch */}
|
||||
|
||||
@@ -73,6 +73,7 @@ export interface ExtensionStateContextType extends ExtensionState {
|
||||
setLocalClineRulesToggles: (toggles: Record<string, boolean>) => void
|
||||
setLocalCursorRulesToggles: (toggles: Record<string, boolean>) => void
|
||||
setLocalWindsurfRulesToggles: (toggles: Record<string, boolean>) => void
|
||||
setLocalAgentsRulesToggles: (toggles: Record<string, boolean>) => void
|
||||
setLocalWorkflowToggles: (toggles: Record<string, boolean>) => void
|
||||
setGlobalWorkflowToggles: (toggles: Record<string, boolean>) => void
|
||||
setRemoteRulesToggles: (toggles: Record<string, boolean>) => void
|
||||
@@ -203,6 +204,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
localClineRulesToggles: {},
|
||||
localCursorRulesToggles: {},
|
||||
localWindsurfRulesToggles: {},
|
||||
localAgentsRulesToggles: {},
|
||||
localWorkflowToggles: {},
|
||||
globalWorkflowToggles: {},
|
||||
shellIntegrationTimeout: 4000,
|
||||
@@ -679,6 +681,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
localClineRulesToggles: state.localClineRulesToggles || {},
|
||||
localCursorRulesToggles: state.localCursorRulesToggles || {},
|
||||
localWindsurfRulesToggles: state.localWindsurfRulesToggles || {},
|
||||
localAgentsRulesToggles: state.localAgentsRulesToggles || {},
|
||||
localWorkflowToggles: state.localWorkflowToggles || {},
|
||||
globalWorkflowToggles: state.globalWorkflowToggles || {},
|
||||
remoteRulesToggles: state.remoteRulesToggles || {},
|
||||
@@ -736,6 +739,11 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
...prevState,
|
||||
localWindsurfRulesToggles: toggles,
|
||||
})),
|
||||
setLocalAgentsRulesToggles: (toggles) =>
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
localAgentsRulesToggles: toggles,
|
||||
})),
|
||||
setLocalWorkflowToggles: (toggles) =>
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
|
||||
Reference in New Issue
Block a user