Skip to content

Commit 5d93098

Browse files
committed
More flexibility for LLMs not being great at this
1 parent ef9c468 commit 5d93098

File tree

4 files changed

+116
-173
lines changed

4 files changed

+116
-173
lines changed

src/core/Cline.ts

Lines changed: 8 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,12 +1132,11 @@ export class Cline {
11251132
this.diffViewProvider.scrollToFirstDiff()
11261133

11271134
// Check for code omissions before proceeding
1128-
const predictedLineCount = parseInt(block.params.line_count ?? "0")
11291135
if (detectCodeOmission(this.diffViewProvider.originalContent || "", newContent, predictedLineCount)) {
11301136
if (this.diffStrategy) {
11311137
await this.diffViewProvider.revertChanges()
11321138
pushToolResult(formatResponse.toolError(
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.`
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.`
11341140
))
11351141
break
11361142
} else {

src/core/prompts/system.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ 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.
6665
- 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.
6767
Usage:
6868
<write_to_file>
69-
<line_count>total number of lines in the content, including empty lines</line_count>
7069
<path>File path here</path>
7170
<content>
7271
Your file content here
7372
</content>
73+
<line_count>total number of lines in the file, including empty lines</line_count>
7474
</write_to_file>
7575
7676
${diffStrategy ? diffStrategy.getToolDescription(cwd.toPosix()) : ""}
@@ -209,7 +209,6 @@ Your final result description here
209209
## Example 2: Requesting to write to a file
210210
211211
<write_to_file>
212-
<line_count>14</line_count>
213212
<path>frontend-config.json</path>
214213
<content>
215214
{
@@ -227,6 +226,7 @@ Your final result description here
227226
"version": "1.0.0"
228227
}
229228
</content>
229+
<line_count>14</line_count>
230230
</write_to_file>
231231
232232
## Example 3: Requesting to use an MCP tool

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

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

11-
it('should skip square bracket checks for files under 100 lines', () => {
12-
const newContent = `[Previous content from line 1-305 remains exactly the same]
13-
const z = 3;`
14-
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
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 skip single-line comment checks for files under 100 lines', () => {
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(false)
20+
const predictedLineCount = 50
21+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
2122
})
2223

23-
it('should skip multi-line comment checks for files under 100 lines', () => {
24-
const newContent = `/* Previous content remains the same */
25-
const z = 3;`
26-
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
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 skip HTML-style comment checks for files under 100 lines', () => {
30-
const newContent = `<!-- Existing content unchanged -->
31-
const z = 3;`
32-
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
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 skip JSX-style comment checks for files under 100 lines', () => {
36-
const newContent = `{/* Rest of the code remains the same */}
37-
const z = 3;`
38-
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
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 skip Python-style comment checks for files under 100 lines', () => {
42-
const newContent = `# Previous content remains unchanged
43-
const z = 3;`
44-
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
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)
65-
})
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;
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;
176119
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-
})
120+
// This is a legitimate comment that remains here`, 130)
121+
const predictedLineCount = 150
122+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
123+
})
181124

182-
it('should not flag when content is longer than predicted', () => {
183-
const newContent = `const x = 1;
125+
it('should not flag when content is longer than predicted', () => {
126+
const newContent = generateLongContent(`const x = 1;
184127
const y = 2;
185128
// Previous content remains here but we added more
186129
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-
})
130+
const w = 4;`, 160)
131+
const predictedLineCount = 150
132+
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
191133
})
192-
})
134+
})

src/integrations/editor/detect-omission.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
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.
5+
* @param predictedLineCount The predicted number of lines in the new content.
66
* @returns True if a potential omission is detected, false otherwise.
77
*/
88
export function detectCodeOmission(
99
originalFileContent: string,
1010
newFileContent: string,
11-
predictedLineCount?: number
11+
predictedLineCount: number
1212
): boolean {
1313
// Skip all checks if predictedLineCount is less than 100
1414
if (!predictedLineCount || predictedLineCount < 100) {
@@ -17,11 +17,6 @@ export function detectCodeOmission(
1717

1818
const actualLineCount = newFileContent.split("\n").length
1919
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-
}
2520

2621
const originalLines = originalFileContent.split("\n")
2722
const newLines = newFileContent.split("\n")
@@ -43,8 +38,8 @@ export function detectCodeOmission(
4338
const words = line.toLowerCase().split(/\s+/)
4439
if (omissionKeywords.some((keyword) => words.includes(keyword))) {
4540
if (!originalLines.includes(line)) {
46-
// For files with 100+ lines, only flag if content is more than 15% shorter
47-
if (lengthRatio <= 0.85) {
41+
// For files with 100+ lines, only flag if content is more than 20% shorter
42+
if (lengthRatio <= 0.80) {
4843
return true
4944
}
5045
}

0 commit comments

Comments
 (0)