mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Diff debugging
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Roo Cline Changelog
|
||||
|
||||
## [2.2.11]
|
||||
|
||||
- Added settings checkbox for verbose diff debugging
|
||||
|
||||
## [2.2.6 - 2.2.10]
|
||||
|
||||
- More fixes to search/replace diffs
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "roo-cline",
|
||||
"version": "2.2.10",
|
||||
"version": "2.2.11",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "roo-cline",
|
||||
"version": "2.2.10",
|
||||
"version": "2.2.11",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/bedrock-sdk": "^0.10.2",
|
||||
"@anthropic-ai/sdk": "^0.26.0",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"displayName": "Roo Cline",
|
||||
"description": "A fork of Cline, an autonomous coding agent, with some added experimental configuration and automation features.",
|
||||
"publisher": "RooVeterinaryInc",
|
||||
"version": "2.2.10",
|
||||
"version": "2.2.11",
|
||||
"icon": "assets/icons/rocket.png",
|
||||
"galleryBanner": {
|
||||
"color": "#617A91",
|
||||
|
||||
@@ -97,6 +97,7 @@ export class Cline {
|
||||
apiConfiguration: ApiConfiguration,
|
||||
customInstructions?: string,
|
||||
diffEnabled?: boolean,
|
||||
debugDiffEnabled?: boolean,
|
||||
task?: string,
|
||||
images?: string[],
|
||||
historyItem?: HistoryItem,
|
||||
@@ -109,7 +110,7 @@ export class Cline {
|
||||
this.diffViewProvider = new DiffViewProvider(cwd)
|
||||
this.customInstructions = customInstructions
|
||||
if (diffEnabled && this.api.getModel().id) {
|
||||
this.diffStrategy = getDiffStrategy(this.api.getModel().id)
|
||||
this.diffStrategy = getDiffStrategy(this.api.getModel().id, debugDiffEnabled)
|
||||
}
|
||||
if (historyItem) {
|
||||
this.taskId = historyItem.id
|
||||
|
||||
@@ -278,7 +278,8 @@ describe('Cline', () => {
|
||||
mockProvider,
|
||||
mockApiConfig,
|
||||
'custom instructions',
|
||||
false,
|
||||
false, // diffEnabled
|
||||
false, // debugDiffEnabled
|
||||
'test task'
|
||||
);
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ import { SearchReplaceDiffStrategy } from './strategies/search-replace'
|
||||
* @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus')
|
||||
* @returns The appropriate diff strategy for the model
|
||||
*/
|
||||
export function getDiffStrategy(model: string): DiffStrategy {
|
||||
export function getDiffStrategy(model: string, debugEnabled?: boolean): DiffStrategy {
|
||||
// For now, return SearchReplaceDiffStrategy for all models (with a fuzzy threshold of 0.9)
|
||||
// This architecture allows for future optimizations based on model capabilities
|
||||
return new SearchReplaceDiffStrategy(0.9)
|
||||
return new SearchReplaceDiffStrategy(0.9, debugEnabled)
|
||||
}
|
||||
|
||||
export type { DiffStrategy }
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { DiffStrategy, DiffResult } from "../types"
|
||||
import { addLineNumbers } from "../../../integrations/misc/extract-text"
|
||||
|
||||
const BUFFER_LINES = 5; // Number of extra context lines to show before and after matches
|
||||
|
||||
function levenshteinDistance(a: string, b: string): number {
|
||||
const matrix: number[][] = [];
|
||||
@@ -48,10 +51,12 @@ function getSimilarity(original: string, search: string): number {
|
||||
|
||||
export class SearchReplaceDiffStrategy implements DiffStrategy {
|
||||
private fuzzyThreshold: number;
|
||||
public debugEnabled: boolean;
|
||||
|
||||
constructor(fuzzyThreshold?: number) {
|
||||
constructor(fuzzyThreshold?: number, debugEnabled?: boolean) {
|
||||
// Default to exact matching (1.0) unless fuzzy threshold specified
|
||||
this.fuzzyThreshold = fuzzyThreshold ?? 1.0;
|
||||
this.debugEnabled = debugEnabled ?? false;
|
||||
}
|
||||
|
||||
getToolDescription(cwd: string): string {
|
||||
@@ -119,15 +124,11 @@ Your search/replace content here
|
||||
// Extract the search and replace blocks
|
||||
const match = diffContent.match(/<<<<<<< SEARCH\n([\s\S]*?)\n=======\n([\s\S]*?)\n>>>>>>> REPLACE/);
|
||||
if (!match) {
|
||||
// Log detailed format information
|
||||
console.log('Invalid Diff Format Debug:', {
|
||||
expectedFormat: "<<<<<<< SEARCH\\n[search content]\\n=======\\n[replace content]\\n>>>>>>> REPLACE",
|
||||
tip: "Make sure to include both SEARCH and REPLACE sections with correct markers"
|
||||
});
|
||||
const debugInfo = this.debugEnabled ? `\n\nDebug Info:\n- Expected Format: <<<<<<< SEARCH\\n[search content]\\n=======\\n[replace content]\\n>>>>>>> REPLACE\n- Tip: Make sure to include both SEARCH and REPLACE sections with correct markers` : '';
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: "Invalid diff format - missing required SEARCH/REPLACE sections"
|
||||
error: `Invalid diff format - missing required SEARCH/REPLACE sections${debugInfo}`
|
||||
};
|
||||
}
|
||||
|
||||
@@ -167,15 +168,11 @@ Your search/replace content here
|
||||
const exactEndIndex = endLine - 1;
|
||||
|
||||
if (exactStartIndex < 0 || exactEndIndex >= originalLines.length) {
|
||||
// Log detailed debug information
|
||||
console.log('Invalid Line Range Debug:', {
|
||||
requestedRange: { start: startLine, end: endLine },
|
||||
fileBounds: { start: 1, end: originalLines.length }
|
||||
});
|
||||
const debugInfo = this.debugEnabled ? `\n\nDebug Info:\n- Requested Range: lines ${startLine}-${endLine}\n- File Bounds: lines 1-${originalLines.length}` : '';
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: `Line range ${startLine}-${endLine} is invalid (file has ${originalLines.length} lines)`,
|
||||
error: `Line range ${startLine}-${endLine} is invalid (file has ${originalLines.length} lines)${debugInfo}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -198,11 +195,12 @@ Your search/replace content here
|
||||
|
||||
if (startLine !== undefined || endLine !== undefined) {
|
||||
// Convert to 0-based index and add buffer
|
||||
const BUFFER_LINES = 5;
|
||||
if (startLine !== undefined) {
|
||||
searchStartIndex = Math.max(0, startLine - 6);
|
||||
searchStartIndex = Math.max(0, startLine - (BUFFER_LINES + 1));
|
||||
}
|
||||
if (endLine !== undefined) {
|
||||
searchEndIndex = Math.min(originalLines.length, endLine + 5);
|
||||
searchEndIndex = Math.min(originalLines.length, endLine + BUFFER_LINES);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,17 +222,27 @@ Your search/replace content here
|
||||
// Require similarity to meet threshold
|
||||
if (matchIndex === -1 || bestMatchScore < this.fuzzyThreshold) {
|
||||
const searchChunk = searchLines.join('\n');
|
||||
// Log detailed debug information to console
|
||||
console.log('Search/Replace Debug Info:', {
|
||||
similarity: bestMatchScore,
|
||||
threshold: this.fuzzyThreshold,
|
||||
searchContent: searchChunk,
|
||||
bestMatch: bestMatchContent || undefined
|
||||
});
|
||||
const originalContentSection = startLine !== undefined && endLine !== undefined
|
||||
? `\n\nOriginal Content:\n${addLineNumbers(
|
||||
originalLines.slice(
|
||||
Math.max(0, startLine - 1 - BUFFER_LINES),
|
||||
Math.min(originalLines.length, endLine + BUFFER_LINES)
|
||||
).join('\n'),
|
||||
Math.max(1, startLine - BUFFER_LINES)
|
||||
)}`
|
||||
: `\n\nOriginal Content:\n${addLineNumbers(originalLines.join('\n'))}`;
|
||||
|
||||
const bestMatchSection = bestMatchContent
|
||||
? `\n\nBest Match Found:\n${addLineNumbers(bestMatchContent, matchIndex + 1)}`
|
||||
: `\n\nBest Match Found:\n(no match)`;
|
||||
|
||||
const debugInfo = this.debugEnabled ? `\n\nDebug Info:\n- Similarity Score: ${Math.floor(bestMatchScore * 100)}%\n- Required Threshold: ${Math.floor(this.fuzzyThreshold * 100)}%\n- Line Range: lines ${startLine}-${endLine}\n\nSearch Content:\n${searchChunk}${bestMatchSection}${originalContentSection}` : '';
|
||||
|
||||
const lineRange = startLine !== undefined || endLine !== undefined ?
|
||||
` at ${startLine !== undefined ? `start: ${startLine}` : 'start'} to ${endLine !== undefined ? `end: ${endLine}` : 'end'}` : '';
|
||||
return {
|
||||
success: false,
|
||||
error: `No sufficiently similar match found${startLine !== undefined ? ` near lines ${startLine}-${endLine}` : ''} (${Math.round(bestMatchScore * 100)}% similar, needs ${Math.round(this.fuzzyThreshold * 100)}%)`
|
||||
error: `No sufficiently similar match found${lineRange} (${Math.floor(bestMatchScore * 100)}% similar, needs ${Math.floor(this.fuzzyThreshold * 100)}%)${debugInfo}`
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@ export type DiffResult =
|
||||
}};
|
||||
|
||||
export interface DiffStrategy {
|
||||
/**
|
||||
* Whether to enable detailed debug logging
|
||||
*/
|
||||
debugEnabled?: boolean;
|
||||
|
||||
/**
|
||||
* Get the tool description for this diff strategy
|
||||
* @param cwd The current working directory
|
||||
|
||||
@@ -67,6 +67,7 @@ type GlobalStateKey =
|
||||
| "allowedCommands"
|
||||
| "soundEnabled"
|
||||
| "diffEnabled"
|
||||
| "debugDiffEnabled"
|
||||
| "alwaysAllowMcp"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
@@ -211,6 +212,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
diffEnabled,
|
||||
debugDiffEnabled,
|
||||
} = await this.getState()
|
||||
|
||||
this.cline = new Cline(
|
||||
@@ -218,6 +220,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
diffEnabled,
|
||||
debugDiffEnabled,
|
||||
task,
|
||||
images
|
||||
)
|
||||
@@ -229,6 +232,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
diffEnabled,
|
||||
debugDiffEnabled,
|
||||
} = await this.getState()
|
||||
|
||||
this.cline = new Cline(
|
||||
@@ -236,6 +240,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
apiConfiguration,
|
||||
customInstructions,
|
||||
diffEnabled,
|
||||
debugDiffEnabled,
|
||||
undefined,
|
||||
undefined,
|
||||
historyItem,
|
||||
@@ -597,6 +602,11 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
await this.updateGlobalState("diffEnabled", diffEnabled)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "debugDiffEnabled":
|
||||
const debugDiffEnabled = message.bool ?? false
|
||||
await this.updateGlobalState("debugDiffEnabled", debugDiffEnabled)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
}
|
||||
},
|
||||
null,
|
||||
@@ -923,6 +933,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
alwaysAllowMcp,
|
||||
soundEnabled,
|
||||
diffEnabled,
|
||||
debugDiffEnabled,
|
||||
taskHistory,
|
||||
} = await this.getState()
|
||||
|
||||
@@ -946,6 +957,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
.sort((a, b) => b.ts - a.ts),
|
||||
soundEnabled: soundEnabled ?? false,
|
||||
diffEnabled: diffEnabled ?? false,
|
||||
debugDiffEnabled: debugDiffEnabled ?? false,
|
||||
shouldShowAnnouncement: lastShownAnnouncementId !== this.latestAnnouncementId,
|
||||
allowedCommands,
|
||||
}
|
||||
@@ -1040,6 +1052,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
allowedCommands,
|
||||
soundEnabled,
|
||||
diffEnabled,
|
||||
debugDiffEnabled,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<string | undefined>,
|
||||
@@ -1077,6 +1090,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
this.getGlobalState("allowedCommands") as Promise<string[] | undefined>,
|
||||
this.getGlobalState("soundEnabled") as Promise<boolean | undefined>,
|
||||
this.getGlobalState("diffEnabled") as Promise<boolean | undefined>,
|
||||
this.getGlobalState("debugDiffEnabled") as Promise<boolean | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
@@ -1130,8 +1144,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
alwaysAllowMcp: alwaysAllowMcp ?? false,
|
||||
taskHistory,
|
||||
allowedCommands,
|
||||
soundEnabled,
|
||||
diffEnabled,
|
||||
soundEnabled: soundEnabled ?? false,
|
||||
diffEnabled: diffEnabled ?? false,
|
||||
debugDiffEnabled: debugDiffEnabled ?? false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
src/integrations/misc/__tests__/extract-text.test.ts
Normal file
32
src/integrations/misc/__tests__/extract-text.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { addLineNumbers } from '../extract-text';
|
||||
|
||||
describe('addLineNumbers', () => {
|
||||
it('should add line numbers starting from 1 by default', () => {
|
||||
const input = 'line 1\nline 2\nline 3';
|
||||
const expected = '1 | line 1\n2 | line 2\n3 | line 3';
|
||||
expect(addLineNumbers(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should add line numbers starting from specified line number', () => {
|
||||
const input = 'line 1\nline 2\nline 3';
|
||||
const expected = '10 | line 1\n11 | line 2\n12 | line 3';
|
||||
expect(addLineNumbers(input, 10)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle empty content', () => {
|
||||
expect(addLineNumbers('')).toBe('1 | ');
|
||||
expect(addLineNumbers('', 5)).toBe('5 | ');
|
||||
});
|
||||
|
||||
it('should handle single line content', () => {
|
||||
expect(addLineNumbers('single line')).toBe('1 | single line');
|
||||
expect(addLineNumbers('single line', 42)).toBe('42 | single line');
|
||||
});
|
||||
|
||||
it('should pad line numbers based on the highest line number', () => {
|
||||
const input = 'line 1\nline 2';
|
||||
// When starting from 99, highest line will be 100, so needs 3 spaces padding
|
||||
const expected = ' 99 | line 1\n100 | line 2';
|
||||
expect(addLineNumbers(input, 99)).toBe(expected);
|
||||
});
|
||||
});
|
||||
@@ -53,15 +53,12 @@ async function extractTextFromIPYNB(filePath: string): Promise<string> {
|
||||
|
||||
return addLineNumbers(extractedText)
|
||||
}
|
||||
|
||||
export function addLineNumbers(content: string): string {
|
||||
export function addLineNumbers(content: string, startLine: number = 1): string {
|
||||
const lines = content.split('\n')
|
||||
const maxLineNumberWidth = String(lines.length).length
|
||||
const maxLineNumberWidth = String(startLine + lines.length - 1).length
|
||||
return lines
|
||||
.map((line, index) => {
|
||||
const lineNumber = String(index + 1).padStart(maxLineNumberWidth, ' ')
|
||||
const lineNumber = String(startLine + index).padStart(maxLineNumberWidth, ' ')
|
||||
return `${lineNumber} | ${line}`
|
||||
}).join('\n')
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export interface ExtensionState {
|
||||
allowedCommands?: string[]
|
||||
soundEnabled?: boolean
|
||||
diffEnabled?: boolean
|
||||
debugDiffEnabled?: boolean
|
||||
}
|
||||
|
||||
export interface ClineMessage {
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface WebviewMessage {
|
||||
| "playSound"
|
||||
| "soundEnabled"
|
||||
| "diffEnabled"
|
||||
| "debugDiffEnabled"
|
||||
| "openMcpSettings"
|
||||
| "restartMcpServer"
|
||||
| "toggleToolAlwaysAllow"
|
||||
|
||||
@@ -31,6 +31,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
setSoundEnabled,
|
||||
diffEnabled,
|
||||
setDiffEnabled,
|
||||
debugDiffEnabled,
|
||||
setDebugDiffEnabled,
|
||||
openRouterModels,
|
||||
setAllowedCommands,
|
||||
allowedCommands,
|
||||
@@ -46,7 +48,10 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
setApiErrorMessage(apiValidationResult)
|
||||
setModelIdErrorMessage(modelIdValidationResult)
|
||||
if (!apiValidationResult && !modelIdValidationResult) {
|
||||
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
|
||||
vscode.postMessage({
|
||||
type: "apiConfiguration",
|
||||
apiConfiguration
|
||||
})
|
||||
vscode.postMessage({ type: "customInstructions", text: customInstructions })
|
||||
vscode.postMessage({ type: "alwaysAllowReadOnly", bool: alwaysAllowReadOnly })
|
||||
vscode.postMessage({ type: "alwaysAllowWrite", bool: alwaysAllowWrite })
|
||||
@@ -56,6 +61,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
vscode.postMessage({ type: "allowedCommands", commands: allowedCommands ?? [] })
|
||||
vscode.postMessage({ type: "soundEnabled", bool: soundEnabled })
|
||||
vscode.postMessage({ type: "diffEnabled", bool: diffEnabled })
|
||||
vscode.postMessage({ type: "debugDiffEnabled", bool: debugDiffEnabled })
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
@@ -324,6 +330,20 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
When enabled, Cline will play sound effects for notifications and events.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
<VSCodeCheckbox checked={debugDiffEnabled} onChange={(e: any) => setDebugDiffEnabled(e.target.checked)}>
|
||||
<span style={{ fontWeight: "500" }}>Debug diff operations</span>
|
||||
</VSCodeCheckbox>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
marginTop: "5px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
When enabled, Cline will show detailed debug information when applying diffs fails.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{IS_DEV && (
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface ExtensionStateContextType extends ExtensionState {
|
||||
setAllowedCommands: (value: string[]) => void
|
||||
setSoundEnabled: (value: boolean) => void
|
||||
setDiffEnabled: (value: boolean) => void
|
||||
setDebugDiffEnabled: (value: boolean) => void
|
||||
}
|
||||
|
||||
const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||
@@ -43,6 +44,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
allowedCommands: [],
|
||||
soundEnabled: false,
|
||||
diffEnabled: false,
|
||||
debugDiffEnabled: false,
|
||||
})
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState(false)
|
||||
@@ -129,7 +131,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
openRouterModels,
|
||||
mcpServers,
|
||||
filePaths,
|
||||
setApiConfiguration: (value) => setState((prevState) => ({ ...prevState, apiConfiguration: value })),
|
||||
setApiConfiguration: (value) => setState((prevState) => ({
|
||||
...prevState,
|
||||
apiConfiguration: value
|
||||
})),
|
||||
setCustomInstructions: (value) => setState((prevState) => ({ ...prevState, customInstructions: value })),
|
||||
setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })),
|
||||
setAlwaysAllowWrite: (value) => setState((prevState) => ({ ...prevState, alwaysAllowWrite: value })),
|
||||
@@ -140,6 +145,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
|
||||
setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
|
||||
setDiffEnabled: (value) => setState((prevState) => ({ ...prevState, diffEnabled: value })),
|
||||
setDebugDiffEnabled: (value) => setState((prevState) => ({
|
||||
...prevState,
|
||||
debugDiffEnabled: value
|
||||
})),
|
||||
}
|
||||
|
||||
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user