Skip to content

Commit ef9c468

Browse files
committed
Use predicted length as input for detecting omissions
1 parent a9775c0 commit ef9c468

File tree

7 files changed

+178
-19
lines changed

7 files changed

+178
-19
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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,11 +1125,12 @@ export class Cline {
11251125
this.diffViewProvider.scrollToFirstDiff()
11261126

11271127
// Check for code omissions before proceeding
1128-
if (detectCodeOmission(this.diffViewProvider.originalContent || "", newContent)) {
1128+
const predictedLineCount = parseInt(block.params.line_count ?? "0")
1129+
if (detectCodeOmission(this.diffViewProvider.originalContent || "", newContent, predictedLineCount)) {
11291130
if (this.diffStrategy) {
11301131
await this.diffViewProvider.revertChanges()
11311132
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."
1133+
`Content appears to be truncated (file has ${newContent.split("\n").length} lines but was predicted to have ${predictedLineCount} lines). 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.`
11331134
))
11341135
break
11351136
} 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
@@ -62,9 +62,11 @@ Usage:
6262
Description: Request to write full content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
6363
Parameters:
6464
- path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()})
65+
- line_count: (required) The number of lines in the content. This is used to determine if the user needs to provide more content to complete the file.
6566
- 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.
6667
Usage:
6768
<write_to_file>
69+
<line_count>total number of lines in the content, including empty lines</line_count>
6870
<path>File path here</path>
6971
<content>
7072
Your file content here
@@ -207,6 +209,7 @@ Your final result description here
207209
## Example 2: Requesting to write to a file
208210
209211
<write_to_file>
212+
<line_count>14</line_count>
210213
<path>frontend-config.json</path>
211214
<content>
212215
{

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

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

11-
it('should detect square bracket line range omission', () => {
11+
it('should skip square bracket checks for files under 100 lines', () => {
1212
const newContent = `[Previous content from line 1-305 remains exactly the same]
1313
const z = 3;`
14-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
14+
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
1515
})
1616

17-
it('should detect single-line comment omission', () => {
17+
it('should skip single-line 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+
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
2121
})
2222

23-
it('should detect multi-line comment omission', () => {
23+
it('should skip multi-line comment checks for files under 100 lines', () => {
2424
const newContent = `/* Previous content remains the same */
2525
const z = 3;`
26-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
26+
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
2727
})
2828

29-
it('should detect HTML-style comment omission', () => {
29+
it('should skip HTML-style comment checks for files under 100 lines', () => {
3030
const newContent = `<!-- Existing content unchanged -->
3131
const z = 3;`
32-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
32+
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
3333
})
3434

35-
it('should detect JSX-style comment omission', () => {
35+
it('should skip JSX-style comment checks for files under 100 lines', () => {
3636
const newContent = `{/* Rest of the code remains the same */}
3737
const z = 3;`
38-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
38+
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
3939
})
4040

41-
it('should detect Python-style comment omission', () => {
41+
it('should skip Python-style comment checks for files under 100 lines', () => {
4242
const newContent = `# Previous content remains unchanged
4343
const z = 3;`
44-
expect(detectCodeOmission(originalContent, newContent)).toBe(true)
44+
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
4545
})
4646

4747
it('should not detect regular comments without omission keywords', () => {
@@ -63,4 +63,130 @@ const z = 3;`
6363
const unchanged = true;`
6464
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
6565
})
66+
67+
describe('with predicted line count', () => {
68+
describe('length-based detection', () => {
69+
it('should skip length checks for files under 100 lines', () => {
70+
const newContent = `const x = 1;`
71+
const predictedLineCount = 50 // Less than 100 lines
72+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
73+
})
74+
75+
it('should detect truncation for files with exactly 100 lines', () => {
76+
const newContent = `const x = 1;`
77+
const predictedLineCount = 100 // Exactly 100 lines
78+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
79+
})
80+
81+
it('should detect truncation for files with more than 100 lines', () => {
82+
const newContent = `const x = 1;`
83+
const predictedLineCount = 150 // More than 100 lines
84+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
85+
})
86+
})
87+
88+
describe('comment-based detection for large files', () => {
89+
const generateLongContent = (commentLine: string) => {
90+
return `${commentLine}
91+
${Array.from({ length: 90 }, (_, i) => `const x${i} = ${i};`).join('\n')}
92+
const y = 2;`
93+
}
94+
95+
it('should detect suspicious single-line comment when content is more than 15% shorter', () => {
96+
const newContent = `// Previous content remains here
97+
const x = 1;`
98+
const predictedLineCount = 100
99+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
100+
})
101+
102+
it('should not flag suspicious single-line comment when content is less than 15% shorter', () => {
103+
const newContent = generateLongContent('// Previous content remains here')
104+
const predictedLineCount = 100
105+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
106+
})
107+
108+
it('should detect suspicious Python-style comment when content is more than 15% shorter', () => {
109+
const newContent = `# Previous content remains here
110+
const x = 1;`
111+
const predictedLineCount = 100
112+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
113+
})
114+
115+
it('should not flag suspicious Python-style comment when content is less than 15% shorter', () => {
116+
const newContent = generateLongContent('# Previous content remains here')
117+
const predictedLineCount = 100
118+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
119+
})
120+
121+
it('should detect suspicious multi-line comment when content is more than 15% shorter', () => {
122+
const newContent = `/* Previous content remains the same */
123+
const x = 1;`
124+
const predictedLineCount = 100
125+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
126+
})
127+
128+
it('should not flag suspicious multi-line comment when content is less than 15% shorter', () => {
129+
const newContent = generateLongContent('/* Previous content remains the same */')
130+
const predictedLineCount = 100
131+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
132+
})
133+
134+
it('should detect suspicious JSX comment when content is more than 15% shorter', () => {
135+
const newContent = `{/* Rest of the code remains the same */}
136+
const x = 1;`
137+
const predictedLineCount = 100
138+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
139+
})
140+
141+
it('should not flag suspicious JSX comment when content is less than 15% shorter', () => {
142+
const newContent = generateLongContent('{/* Rest of the code remains the same */}')
143+
const predictedLineCount = 100
144+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
145+
})
146+
147+
it('should detect suspicious HTML comment when content is more than 15% shorter', () => {
148+
const newContent = `<!-- Existing content unchanged -->
149+
const x = 1;`
150+
const predictedLineCount = 100
151+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
152+
})
153+
154+
it('should not flag suspicious HTML comment when content is less than 15% shorter', () => {
155+
const newContent = generateLongContent('<!-- Existing content unchanged -->')
156+
const predictedLineCount = 100
157+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
158+
})
159+
160+
it('should detect suspicious square bracket notation when content is more than 15% shorter', () => {
161+
const newContent = `[Previous content from line 1-305 remains exactly the same]
162+
const x = 1;`
163+
const predictedLineCount = 100
164+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
165+
})
166+
167+
it('should not flag suspicious square bracket notation when content is less than 15% shorter', () => {
168+
const newContent = generateLongContent('[Previous content from line 1-305 remains exactly the same]')
169+
const predictedLineCount = 100
170+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
171+
})
172+
})
173+
174+
it('should not flag content very close to predicted length', () => {
175+
const newContent = `const x = 1;
176+
const y = 2;
177+
// This is a legitimate comment that remains here`
178+
const predictedLineCount = newContent.split('\n').length // Exact line count match
179+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
180+
})
181+
182+
it('should not flag when content is longer than predicted', () => {
183+
const newContent = `const x = 1;
184+
const y = 2;
185+
// Previous content remains here but we added more
186+
const z = 3;
187+
const w = 4;`
188+
const predictedLineCount = 3 // Content has 4 lines (longer than predicted)
189+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
190+
})
191+
})
66192
})

src/integrations/editor/detect-omission.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,27 @@
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 Optional 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+
21+
// If content is more than 25% shorter than predicted, this is suspicious
22+
if (lengthRatio <= 0.75) {
23+
return true
24+
}
25+
826
const originalLines = originalFileContent.split("\n")
927
const newLines = newFileContent.split("\n")
1028
const omissionKeywords = ["remain", "remains", "unchanged", "rest", "previous", "existing", "content", "same", "..."]
@@ -18,17 +36,21 @@ export function detectCodeOmission(originalFileContent: string, newFileContent:
1836
/^\s*\[/, // Square bracket notation
1937
]
2038

39+
// Consider comments as suspicious if they weren't in the original file
40+
// and contain omission keywords
2141
for (const line of newLines) {
2242
if (commentPatterns.some((pattern) => pattern.test(line))) {
2343
const words = line.toLowerCase().split(/\s+/)
2444
if (omissionKeywords.some((keyword) => words.includes(keyword))) {
2545
if (!originalLines.includes(line)) {
26-
return true
46+
// For files with 100+ lines, only flag if content is more than 15% shorter
47+
if (lengthRatio <= 0.85) {
48+
return true
49+
}
2750
}
2851
}
2952
}
3053
}
3154

3255
return false
33-
}
34-
56+
}

0 commit comments

Comments
 (0)