Skip to content

Commit 1cb3206

Browse files
authored
Merge pull request #175 from RooVetGit/fix_omission_detecting
Use predicted length as input for detecting omissions
2 parents a9775c0 + 5d93098 commit 1cb3206

File tree

7 files changed

+147
-45
lines changed

7 files changed

+147
-45
lines changed

.changeset/pink-ways-behave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Take predicted file length into account when detecting omissions

.clinerules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Before attempting completion, always make sure that any code changes have test coverage and that the tests pass.

src/core/Cline.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,7 @@ export class Cline {
10411041
case "write_to_file": {
10421042
const relPath: string | undefined = block.params.path
10431043
let newContent: string | undefined = block.params.content
1044+
let predictedLineCount: number | undefined = parseInt(block.params.line_count ?? "0")
10441045
if (!relPath || !newContent) {
10451046
// checking for newContent ensure relPath is complete
10461047
// wait so we can determine if it's a new file or editing an existing file
@@ -1109,6 +1110,12 @@ export class Cline {
11091110
await this.diffViewProvider.reset()
11101111
break
11111112
}
1113+
if (!predictedLineCount) {
1114+
this.consecutiveMistakeCount++
1115+
pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "line_count"))
1116+
await this.diffViewProvider.reset()
1117+
break
1118+
}
11121119
this.consecutiveMistakeCount = 0
11131120

11141121
// if isEditingFile false, that means we have the full contents of the file already.
@@ -1125,11 +1132,11 @@ export class Cline {
11251132
this.diffViewProvider.scrollToFirstDiff()
11261133

11271134
// Check for code omissions before proceeding
1128-
if (detectCodeOmission(this.diffViewProvider.originalContent || "", newContent)) {
1135+
if (detectCodeOmission(this.diffViewProvider.originalContent || "", newContent, predictedLineCount)) {
11291136
if (this.diffStrategy) {
11301137
await this.diffViewProvider.revertChanges()
11311138
pushToolResult(formatResponse.toolError(
1132-
"Content appears to be truncated. Found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file."
1139+
`Content appears to be truncated (file has ${newContent.split("\n").length} lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`
11331140
))
11341141
break
11351142
} else {

src/core/assistant-message/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const toolParamNames = [
3030
"command",
3131
"path",
3232
"content",
33+
"line_count",
3334
"regex",
3435
"file_pattern",
3536
"recursive",
@@ -71,7 +72,7 @@ export interface ReadFileToolUse extends ToolUse {
7172

7273
export interface WriteToFileToolUse extends ToolUse {
7374
name: "write_to_file"
74-
params: Partial<Pick<Record<ToolParamName, string>, "path" | "content">>
75+
params: Partial<Pick<Record<ToolParamName, string>, "path" | "content" | "line_count">>
7576
}
7677

7778
export interface SearchFilesToolUse extends ToolUse {

src/core/prompts/system.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,14 @@ Description: Request to write full content to a file at the specified path. If t
6363
Parameters:
6464
- path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()})
6565
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
66+
- line_count: (required) The number of lines in the file. Make sure to compute this based on the actual content of the file, not the number of lines in the content you're providing.
6667
Usage:
6768
<write_to_file>
6869
<path>File path here</path>
6970
<content>
7071
Your file content here
7172
</content>
73+
<line_count>total number of lines in the file, including empty lines</line_count>
7274
</write_to_file>
7375
7476
${diffStrategy ? diffStrategy.getToolDescription(cwd.toPosix()) : ""}
@@ -224,6 +226,7 @@ Your final result description here
224226
"version": "1.0.0"
225227
}
226228
</content>
229+
<line_count>14</line_count>
227230
</write_to_file>
228231
229232
## Example 3: Requesting to use an MCP tool

src/integrations/editor/__tests__/detect-omission.test.ts

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,59 +8,127 @@ describe('detectCodeOmission', () => {
88
return x + y;
99
}`
1010

11-
it('should detect square bracket line range omission', () => {
12-
const newContent = `[Previous content from line 1-305 remains exactly the same]
13-
const z = 3;`
14-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
15-
})
11+
const generateLongContent = (commentLine: string, length: number = 90) => {
12+
return `${commentLine}
13+
${Array.from({ length }, (_, i) => `const x${i} = ${i};`).join('\n')}
14+
const y = 2;`
15+
}
1616

17-
it('should detect single-line comment omission', () => {
17+
it('should skip comment checks for files under 100 lines', () => {
1818
const newContent = `// Lines 1-50 remain unchanged
1919
const z = 3;`
20-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
20+
const predictedLineCount = 50
21+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
2122
})
2223

23-
it('should detect multi-line comment omission', () => {
24-
const newContent = `/* Previous content remains the same */
25-
const z = 3;`
26-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
24+
it('should not detect regular comments without omission keywords', () => {
25+
const newContent = generateLongContent('// Adding new functionality')
26+
const predictedLineCount = 150
27+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
2728
})
2829

29-
it('should detect HTML-style comment omission', () => {
30-
const newContent = `<!-- Existing content unchanged -->
31-
const z = 3;`
32-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
30+
it('should not detect when comment is part of original content', () => {
31+
const originalWithComment = `// Content remains unchanged
32+
${originalContent}`
33+
const newContent = generateLongContent('// Content remains unchanged')
34+
const predictedLineCount = 150
35+
expect(detectCodeOmission(originalWithComment, newContent, predictedLineCount)).toBe(false)
3336
})
3437

35-
it('should detect JSX-style comment omission', () => {
36-
const newContent = `{/* Rest of the code remains the same */}
37-
const z = 3;`
38-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
38+
it('should not detect code that happens to contain omission keywords', () => {
39+
const newContent = generateLongContent(`const remains = 'some value';
40+
const unchanged = true;`)
41+
const predictedLineCount = 150
42+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
3943
})
4044

41-
it('should detect Python-style comment omission', () => {
42-
const newContent = `# Previous content remains unchanged
43-
const z = 3;`
44-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
45+
it('should detect suspicious single-line comment when content is more than 20% shorter', () => {
46+
const newContent = generateLongContent('// Previous content remains here\nconst x = 1;')
47+
const predictedLineCount = 150
48+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
4549
})
4650

47-
it('should not detect regular comments without omission keywords', () => {
48-
const newContent = `// Adding new functionality
49-
const z = 3;`
50-
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
51+
it('should not flag suspicious single-line comment when content is less than 20% shorter', () => {
52+
const newContent = generateLongContent('// Previous content remains here', 130)
53+
const predictedLineCount = 150
54+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
5155
})
5256

53-
it('should not detect when comment is part of original content', () => {
54-
const originalWithComment = `// Content remains unchanged
55-
${originalContent}`
56-
const newContent = `// Content remains unchanged
57-
const z = 3;`
58-
expect(detectCodeOmission(originalWithComment, newContent)).toBe(false)
57+
it('should detect suspicious Python-style comment when content is more than 20% shorter', () => {
58+
const newContent = generateLongContent('# Previous content remains here\nconst x = 1;')
59+
const predictedLineCount = 150
60+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
5961
})
6062

61-
it('should not detect code that happens to contain omission keywords', () => {
62-
const newContent = `const remains = 'some value';
63-
const unchanged = true;`
64-
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
63+
it('should not flag suspicious Python-style comment when content is less than 20% shorter', () => {
64+
const newContent = generateLongContent('# Previous content remains here', 130)
65+
const predictedLineCount = 150
66+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
67+
})
68+
69+
it('should detect suspicious multi-line comment when content is more than 20% shorter', () => {
70+
const newContent = generateLongContent('/* Previous content remains the same */\nconst x = 1;')
71+
const predictedLineCount = 150
72+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
73+
})
74+
75+
it('should not flag suspicious multi-line comment when content is less than 20% shorter', () => {
76+
const newContent = generateLongContent('/* Previous content remains the same */', 130)
77+
const predictedLineCount = 150
78+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
79+
})
80+
81+
it('should detect suspicious JSX comment when content is more than 20% shorter', () => {
82+
const newContent = generateLongContent('{/* Rest of the code remains the same */}\nconst x = 1;')
83+
const predictedLineCount = 150
84+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
85+
})
86+
87+
it('should not flag suspicious JSX comment when content is less than 20% shorter', () => {
88+
const newContent = generateLongContent('{/* Rest of the code remains the same */}', 130)
89+
const predictedLineCount = 150
90+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
91+
})
92+
93+
it('should detect suspicious HTML comment when content is more than 20% shorter', () => {
94+
const newContent = generateLongContent('<!-- Existing content unchanged -->\nconst x = 1;')
95+
const predictedLineCount = 150
96+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
97+
})
98+
99+
it('should not flag suspicious HTML comment when content is less than 20% shorter', () => {
100+
const newContent = generateLongContent('<!-- Existing content unchanged -->', 130)
101+
const predictedLineCount = 150
102+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
103+
})
104+
105+
it('should detect suspicious square bracket notation when content is more than 20% shorter', () => {
106+
const newContent = generateLongContent('[Previous content from line 1-305 remains exactly the same]\nconst x = 1;')
107+
const predictedLineCount = 150
108+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
109+
})
110+
111+
it('should not flag suspicious square bracket notation when content is less than 20% shorter', () => {
112+
const newContent = generateLongContent('[Previous content from line 1-305 remains exactly the same]', 130)
113+
const predictedLineCount = 150
114+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
115+
})
116+
117+
it('should not flag content very close to predicted length', () => {
118+
const newContent = generateLongContent(`const x = 1;
119+
const y = 2;
120+
// This is a legitimate comment that remains here`, 130)
121+
const predictedLineCount = 150
122+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
123+
})
124+
125+
it('should not flag when content is longer than predicted', () => {
126+
const newContent = generateLongContent(`const x = 1;
127+
const y = 2;
128+
// Previous content remains here but we added more
129+
const z = 3;
130+
const w = 4;`, 160)
131+
const predictedLineCount = 150
132+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
65133
})
66-
})
134+
})

src/integrations/editor/detect-omission.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,22 @@
22
* Detects potential AI-generated code omissions in the given file content.
33
* @param originalFileContent The original content of the file.
44
* @param newFileContent The new content of the file to check.
5+
* @param predictedLineCount The predicted number of lines in the new content.
56
* @returns True if a potential omission is detected, false otherwise.
67
*/
7-
export function detectCodeOmission(originalFileContent: string, newFileContent: string): boolean {
8+
export function detectCodeOmission(
9+
originalFileContent: string,
10+
newFileContent: string,
11+
predictedLineCount: number
12+
): boolean {
13+
// Skip all checks if predictedLineCount is less than 100
14+
if (!predictedLineCount || predictedLineCount < 100) {
15+
return false
16+
}
17+
18+
const actualLineCount = newFileContent.split("\n").length
19+
const lengthRatio = actualLineCount / predictedLineCount
20+
821
const originalLines = originalFileContent.split("\n")
922
const newLines = newFileContent.split("\n")
1023
const omissionKeywords = ["remain", "remains", "unchanged", "rest", "previous", "existing", "content", "same", "..."]
@@ -18,17 +31,21 @@ export function detectCodeOmission(originalFileContent: string, newFileContent:
1831
/^\s*\[/, // Square bracket notation
1932
]
2033

34+
// Consider comments as suspicious if they weren't in the original file
35+
// and contain omission keywords
2136
for (const line of newLines) {
2237
if (commentPatterns.some((pattern) => pattern.test(line))) {
2338
const words = line.toLowerCase().split(/\s+/)
2439
if (omissionKeywords.some((keyword) => words.includes(keyword))) {
2540
if (!originalLines.includes(line)) {
26-
return true
41+
// For files with 100+ lines, only flag if content is more than 20% shorter
42+
if (lengthRatio <= 0.80) {
43+
return true
44+
}
2745
}
2846
}
2947
}
3048
}
3149

3250
return false
33-
}
34-
51+
}

0 commit comments

Comments
 (0)