More flexibility for LLMs not being great at this

This commit is contained in:
Matt Rubens
2024-12-19 02:12:20 -05:00
parent ef9c468f17
commit 5d930981a4
4 changed files with 105 additions and 162 deletions

View File

@@ -1041,6 +1041,7 @@ export class Cline {
case "write_to_file": {
const relPath: string | undefined = block.params.path
let newContent: string | undefined = block.params.content
let predictedLineCount: number | undefined = parseInt(block.params.line_count ?? "0")
if (!relPath || !newContent) {
// checking for newContent ensure relPath is complete
// wait so we can determine if it's a new file or editing an existing file
@@ -1109,6 +1110,12 @@ export class Cline {
await this.diffViewProvider.reset()
break
}
if (!predictedLineCount) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "line_count"))
await this.diffViewProvider.reset()
break
}
this.consecutiveMistakeCount = 0
// if isEditingFile false, that means we have the full contents of the file already.
@@ -1125,12 +1132,11 @@ export class Cline {
this.diffViewProvider.scrollToFirstDiff()
// Check for code omissions before proceeding
const predictedLineCount = parseInt(block.params.line_count ?? "0")
if (detectCodeOmission(this.diffViewProvider.originalContent || "", newContent, predictedLineCount)) {
if (this.diffStrategy) {
await this.diffViewProvider.revertChanges()
pushToolResult(formatResponse.toolError(
`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.`
`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.`
))
break
} else {

View File

@@ -62,15 +62,15 @@ Usage:
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.
Parameters:
- path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()})
- 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.
- 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.
- 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.
Usage:
<write_to_file>
<line_count>total number of lines in the content, including empty lines</line_count>
<path>File path here</path>
<content>
Your file content here
</content>
<line_count>total number of lines in the file, including empty lines</line_count>
</write_to_file>
${diffStrategy ? diffStrategy.getToolDescription(cwd.toPosix()) : ""}
@@ -209,7 +209,6 @@ Your final result description here
## Example 2: Requesting to write to a file
<write_to_file>
<line_count>14</line_count>
<path>frontend-config.json</path>
<content>
{
@@ -227,6 +226,7 @@ Your final result description here
"version": "1.0.0"
}
</content>
<line_count>14</line_count>
</write_to_file>
## Example 3: Requesting to use an MCP tool

View File

@@ -8,185 +8,127 @@ describe('detectCodeOmission', () => {
return x + y;
}`
it('should skip square bracket checks for files under 100 lines', () => {
const newContent = `[Previous content from line 1-305 remains exactly the same]
const z = 3;`
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
const generateLongContent = (commentLine: string, length: number = 90) => {
return `${commentLine}
${Array.from({ length }, (_, i) => `const x${i} = ${i};`).join('\n')}
const y = 2;`
}
it('should skip single-line comment checks for files under 100 lines', () => {
it('should skip comment checks for files under 100 lines', () => {
const newContent = `// Lines 1-50 remain unchanged
const z = 3;`
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it('should skip multi-line comment checks for files under 100 lines', () => {
const newContent = `/* Previous content remains the same */
const z = 3;`
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it('should skip HTML-style comment checks for files under 100 lines', () => {
const newContent = `<!-- Existing content unchanged -->
const z = 3;`
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it('should skip JSX-style comment checks for files under 100 lines', () => {
const newContent = `{/* Rest of the code remains the same */}
const z = 3;`
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
})
it('should skip Python-style comment checks for files under 100 lines', () => {
const newContent = `# Previous content remains unchanged
const z = 3;`
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
const predictedLineCount = 50
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should not detect regular comments without omission keywords', () => {
const newContent = `// Adding new functionality
const z = 3;`
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
const newContent = generateLongContent('// Adding new functionality')
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should not detect when comment is part of original content', () => {
const originalWithComment = `// Content remains unchanged
${originalContent}`
const newContent = `// Content remains unchanged
const z = 3;`
expect(detectCodeOmission(originalWithComment, newContent)).toBe(false)
const newContent = generateLongContent('// Content remains unchanged')
const predictedLineCount = 150
expect(detectCodeOmission(originalWithComment, newContent, predictedLineCount)).toBe(false)
})
it('should not detect code that happens to contain omission keywords', () => {
const newContent = `const remains = 'some value';
const unchanged = true;`
expect(detectCodeOmission(originalContent, newContent)).toBe(false)
const newContent = generateLongContent(`const remains = 'some value';
const unchanged = true;`)
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
describe('with predicted line count', () => {
describe('length-based detection', () => {
it('should skip length checks for files under 100 lines', () => {
const newContent = `const x = 1;`
const predictedLineCount = 50 // Less than 100 lines
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should detect suspicious single-line comment when content is more than 20% shorter', () => {
const newContent = generateLongContent('// Previous content remains here\nconst x = 1;')
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should detect truncation for files with exactly 100 lines', () => {
const newContent = `const x = 1;`
const predictedLineCount = 100 // Exactly 100 lines
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should not flag suspicious single-line comment when content is less than 20% shorter', () => {
const newContent = generateLongContent('// Previous content remains here', 130)
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should detect truncation for files with more than 100 lines', () => {
const newContent = `const x = 1;`
const predictedLineCount = 150 // More than 100 lines
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
})
it('should detect suspicious Python-style comment when content is more than 20% shorter', () => {
const newContent = generateLongContent('# Previous content remains here\nconst x = 1;')
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
describe('comment-based detection for large files', () => {
const generateLongContent = (commentLine: string) => {
return `${commentLine}
${Array.from({ length: 90 }, (_, i) => `const x${i} = ${i};`).join('\n')}
const y = 2;`
}
it('should not flag suspicious Python-style comment when content is less than 20% shorter', () => {
const newContent = generateLongContent('# Previous content remains here', 130)
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should detect suspicious single-line comment when content is more than 15% shorter', () => {
const newContent = `// Previous content remains here
const x = 1;`
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should detect suspicious multi-line comment when content is more than 20% shorter', () => {
const newContent = generateLongContent('/* Previous content remains the same */\nconst x = 1;')
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should not flag suspicious single-line comment when content is less than 15% shorter', () => {
const newContent = generateLongContent('// Previous content remains here')
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should not flag suspicious multi-line comment when content is less than 20% shorter', () => {
const newContent = generateLongContent('/* Previous content remains the same */', 130)
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should detect suspicious Python-style comment when content is more than 15% shorter', () => {
const newContent = `# Previous content remains here
const x = 1;`
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should detect suspicious JSX comment when content is more than 20% shorter', () => {
const newContent = generateLongContent('{/* Rest of the code remains the same */}\nconst x = 1;')
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should not flag suspicious Python-style comment when content is less than 15% shorter', () => {
const newContent = generateLongContent('# Previous content remains here')
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should not flag suspicious JSX comment when content is less than 20% shorter', () => {
const newContent = generateLongContent('{/* Rest of the code remains the same */}', 130)
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should detect suspicious multi-line comment when content is more than 15% shorter', () => {
const newContent = `/* Previous content remains the same */
const x = 1;`
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should detect suspicious HTML comment when content is more than 20% shorter', () => {
const newContent = generateLongContent('<!-- Existing content unchanged -->\nconst x = 1;')
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should not flag suspicious multi-line comment when content is less than 15% shorter', () => {
const newContent = generateLongContent('/* Previous content remains the same */')
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should not flag suspicious HTML comment when content is less than 20% shorter', () => {
const newContent = generateLongContent('<!-- Existing content unchanged -->', 130)
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should detect suspicious JSX comment when content is more than 15% shorter', () => {
const newContent = `{/* Rest of the code remains the same */}
const x = 1;`
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should detect suspicious square bracket notation when content is more than 20% shorter', () => {
const newContent = generateLongContent('[Previous content from line 1-305 remains exactly the same]\nconst x = 1;')
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should not flag suspicious JSX comment when content is less than 15% shorter', () => {
const newContent = generateLongContent('{/* Rest of the code remains the same */}')
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should not flag suspicious square bracket notation when content is less than 20% shorter', () => {
const newContent = generateLongContent('[Previous content from line 1-305 remains exactly the same]', 130)
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should detect suspicious HTML comment when content is more than 15% shorter', () => {
const newContent = `<!-- Existing content unchanged -->
const x = 1;`
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should not flag suspicious HTML comment when content is less than 15% shorter', () => {
const newContent = generateLongContent('<!-- Existing content unchanged -->')
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should detect suspicious square bracket notation when content is more than 15% shorter', () => {
const newContent = `[Previous content from line 1-305 remains exactly the same]
const x = 1;`
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(true)
})
it('should not flag suspicious square bracket notation when content is less than 15% shorter', () => {
const newContent = generateLongContent('[Previous content from line 1-305 remains exactly the same]')
const predictedLineCount = 100
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
})
it('should not flag content very close to predicted length', () => {
const newContent = `const x = 1;
it('should not flag content very close to predicted length', () => {
const newContent = generateLongContent(`const x = 1;
const y = 2;
// This is a legitimate comment that remains here`
const predictedLineCount = newContent.split('\n').length // Exact line count match
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
// This is a legitimate comment that remains here`, 130)
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
it('should not flag when content is longer than predicted', () => {
const newContent = `const x = 1;
it('should not flag when content is longer than predicted', () => {
const newContent = generateLongContent(`const x = 1;
const y = 2;
// Previous content remains here but we added more
const z = 3;
const w = 4;`
const predictedLineCount = 3 // Content has 4 lines (longer than predicted)
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
const w = 4;`, 160)
const predictedLineCount = 150
expect(detectCodeOmission(originalContent, newContent, predictedLineCount)).toBe(false)
})
})
})

View File

@@ -2,13 +2,13 @@
* Detects potential AI-generated code omissions in the given file content.
* @param originalFileContent The original content of the file.
* @param newFileContent The new content of the file to check.
* @param predictedLineCount Optional predicted number of lines in the new content.
* @param predictedLineCount The predicted number of lines in the new content.
* @returns True if a potential omission is detected, false otherwise.
*/
export function detectCodeOmission(
originalFileContent: string,
newFileContent: string,
predictedLineCount?: number
predictedLineCount: number
): boolean {
// Skip all checks if predictedLineCount is less than 100
if (!predictedLineCount || predictedLineCount < 100) {
@@ -17,11 +17,6 @@ export function detectCodeOmission(
const actualLineCount = newFileContent.split("\n").length
const lengthRatio = actualLineCount / predictedLineCount
// If content is more than 25% shorter than predicted, this is suspicious
if (lengthRatio <= 0.75) {
return true
}
const originalLines = originalFileContent.split("\n")
const newLines = newFileContent.split("\n")
@@ -43,8 +38,8 @@ export function detectCodeOmission(
const words = line.toLowerCase().split(/\s+/)
if (omissionKeywords.some((keyword) => words.includes(keyword))) {
if (!originalLines.includes(line)) {
// For files with 100+ lines, only flag if content is more than 15% shorter
if (lengthRatio <= 0.85) {
// For files with 100+ lines, only flag if content is more than 20% shorter
if (lengthRatio <= 0.80) {
return true
}
}