Compare commits

..

3 Commits

Author SHA1 Message Date
github-actions[bot] c294b86524 v3.17.14 Release Notes
v3.17.14 Release Notes
2025-06-17 16:53:43 -07:00
pashpashpash 8d133d9031 supporting legacy search and replace blocks too (#4264)
* supporting legacy search and replace blocks too

* changeset

* Handle legacy search/replace chars for first/last partial lines

* error recovery + fixing

* throwing error if malformed search block - previously this would cause entire file to be deleted

* Update cyan-books-cry.md

* fixing broken test

---------

Co-authored-by: Cline Evaluation <cline@example.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-06-18 01:14:28 +02:00
Ramesh 1adf19a873 update star alignment (#3935) 2025-06-17 16:07:48 -07:00
22 changed files with 116 additions and 126 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": minor
---
Fix list_files tool to return files if the targeted directory is a hidden directory
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": minor
---
add ability to constrain size of terminal output
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Exclude clinerules from checkpoints
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
chore(bedrock): remove @anthropic-ai/bedrock-sdk
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Update developer reset to allow for resetting workspace settings.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Added file context warnings to reduce diff edit errors when resuming a task after it has been restored
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Clear the chat input when the user changes mode within a task
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": minor
---
Added a configurable default terminal profile setting
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fixed race condition where clineAsk was undefined, leading to task restoration and other downstream issues
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": minor
---
update the copy button functionality
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix where clineMessages were not saving checkpoint commitHash on some messages
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Integrate Claude Code
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Better debounce on checkmark control menu
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": minor
---
Add SAP AI Core as a provider for Cline
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": minor
---
Adding MCP Rich Display settings in cline's settings page, to enable users to change it in a persistent manner
+17
View File
@@ -1,5 +1,22 @@
# Changelog
## [3.17.14]
- Add Claude Code as a new API provider, allowing integration with Anthropic's Claude Code CLI tool and Claude Max Plan (Thanks @BarreiroT!)
- Add SAP AI Core as a new API provider with support for Claude and GPT models (Thanks @schardosin!)
- Add configurable default terminal profile setting, allowing users to specify which terminal Cline should use (Thanks @valinha!)
- Add terminal output size constraint setting to limit how much terminal output is processed
- Add MCP Rich Display settings to the settings page for persistent configuration (Thanks @Vl4diC0de!)
- Improve copy button functionality with refactored reusable components (Thanks @shouhanzen!)
- Improve AWS Bedrock provider by removing deprecated dependency and using standard AWS SDK (Thanks @watany-dev!)
- Fix list_files tool to properly return files when targeting hidden directories
- Fix search and replace edge case that could cause file deletion, making the algorithm more lenient for models using different diff formats
- Fix task restoration issues that could occur when resuming interrupted tasks
- Fix checkpoint saving to properly track all file changes
- Improve file context warnings to reduce diff edit errors when resuming restored tasks
- Clear chat input when switching between Plan/Act modes within a task
- Exclude .clinerules files from checkpoint tracking
## [3.17.13]
- Add Thinking UX for Gemini models, providing visual feedback during model reasoning
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
"version": "3.17.13",
"version": "3.17.14",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.17.13",
"version": "3.17.14",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.37.0",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "claude-dev",
"displayName": "Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "3.17.13",
"version": "3.17.14",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
+1 -4
View File
@@ -21,10 +21,7 @@ const NICE_JS_OUT_DIR = path.join(ROOT_DIR, "src/generated/nice-grpc")
const DESCRIPTOR_OUT_DIR = path.join(ROOT_DIR, "dist-standalone/proto")
const isWindows = process.platform === "win32"
const TS_PROTO_PLUGIN = isWindows
? path.join(ROOT_DIR, "node_modules", ".bin", "protoc-gen-ts_proto.cmd") // Use the .bin directory path for Windows
: require.resolve("ts-proto/protoc-gen-ts_proto")
const TS_PROTO_PLUGIN = require.resolve("ts-proto/protoc-gen-ts_proto") + (isWindows ? ".cmd" : "")
const TS_PROTO_OPTIONS = [
"env=node",
"esModuleInterop=true",
+51 -15
View File
@@ -19,14 +19,34 @@ new content
isFinal: true,
},
{
name: "full file replacement",
original: "old content",
diff: `------- SEARCH
name: "malformed search - mixed symbols",
original: "line1\nline2\nline3",
diff: `<<-- SEARCH
line2
=======
new content
replaced
+++++++ REPLACE`,
expected: "new content\n",
isFinal: true,
shouldThrow: true,
},
{
name: "malformed search - insufficient dashes",
original: "line1\nline2\nline3",
diff: `-- SEARCH
line2
=======
replaced
+++++++ REPLACE`,
shouldThrow: true,
},
{
name: "malformed search - missing space",
original: "line1\nline2\nline3",
diff: `-------SEARCH
line2
=======
replaced
+++++++ REPLACE`,
shouldThrow: true,
},
{
name: "exact match replacement",
@@ -139,17 +159,33 @@ replaced
]
//.filter(({name}) => name === "multiple ordered replacements")
//.filter(({name}) => name === "delete then replace")
testCases.forEach(({ name, original, diff, expected, isFinal }) => {
testCases.forEach(({ name, original, diff, expected, isFinal, shouldThrow }) => {
it(`should handle ${name} case correctly`, async () => {
const result1 = await cnfc(diff, original, isFinal)
const result2 = await cnfc2(diff, original, isFinal)
const equal = result1 === result2
const equal2 = result1 === expected
// Verify both implementations produce same result
expect(result1).to.equal(result2)
if (shouldThrow) {
try {
await cnfc(diff, original, isFinal ?? true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
// Verify result matches expected
expect(result1).to.equal(expected)
try {
await cnfc2(diff, original, isFinal ?? true)
expect.fail("Expected an error to be thrown")
} catch (err) {
expect(err).to.be.an("error")
}
} else {
const result1 = await cnfc(diff, original, isFinal ?? true)
const result2 = await cnfc2(diff, original, isFinal ?? true)
const equal = result1 === result2
const equal2 = result1 === expected
// Verify both implementations produce same result
expect(result1).to.equal(result2)
// Verify result matches expected
expect(result1).to.equal(expected)
}
})
})
+27 -12
View File
@@ -4,15 +4,19 @@ const REPLACE_BLOCK_END = "+++++++ REPLACE"
const SEARCH_BLOCK_CHAR = "-"
const REPLACE_BLOCK_CHAR = "+"
const LEGACY_SEARCH_BLOCK_CHAR = "<"
const LEGACY_REPLACE_BLOCK_CHAR = ">"
// Replace the exact string constants with flexible regex patterns
const SEARCH_BLOCK_START_REGEX = /^[-]{3,} SEARCH$/
const SEARCH_BLOCK_END_REGEX = /^[=]{3,}$/
const REPLACE_BLOCK_END_REGEX = /^[+]{3,} REPLACE$/
const LEGACY_SEARCH_BLOCK_START_REGEX = /^[<]{3,} SEARCH$/
const LEGACY_REPLACE_BLOCK_END_REGEX = /^[>]{3,} REPLACE$/
// Helper functions to check if a line matches the flexible patterns
function isSearchBlockStart(line: string): boolean {
return SEARCH_BLOCK_START_REGEX.test(line)
return SEARCH_BLOCK_START_REGEX.test(line) || LEGACY_SEARCH_BLOCK_START_REGEX.test(line)
}
function isSearchBlockEnd(line: string): boolean {
@@ -20,7 +24,7 @@ function isSearchBlockEnd(line: string): boolean {
}
function isReplaceBlockEnd(line: string): boolean {
return REPLACE_BLOCK_END_REGEX.test(line)
return REPLACE_BLOCK_END_REGEX.test(line) || LEGACY_REPLACE_BLOCK_END_REGEX.test(line)
}
/**
@@ -269,7 +273,11 @@ async function constructNewFileContentV1(diffContent: string, originalContent: s
const lastLine = lines[lines.length - 1]
if (
lines.length > 0 &&
(lastLine.startsWith(SEARCH_BLOCK_CHAR) || lastLine.startsWith("=") || lastLine.startsWith(REPLACE_BLOCK_CHAR)) &&
(lastLine.startsWith(SEARCH_BLOCK_CHAR) ||
lastLine.startsWith(LEGACY_SEARCH_BLOCK_CHAR) ||
lastLine.startsWith("=") ||
lastLine.startsWith(REPLACE_BLOCK_CHAR) ||
lastLine.startsWith(LEGACY_REPLACE_BLOCK_CHAR)) &&
!isSearchBlockStart(lastLine) &&
!isSearchBlockEnd(lastLine) &&
!isReplaceBlockEnd(lastLine)
@@ -303,9 +311,12 @@ async function constructNewFileContentV1(diffContent: string, originalContent: s
searchMatchIndex = 0
searchEndIndex = 0
} else {
// Complete file replacement scenario: treat the entire file as matched
searchMatchIndex = 0
searchEndIndex = originalContent.length
// ERROR: Empty search block with non-empty file indicates malformed SEARCH marker
throw new Error(
"Empty SEARCH block detected with non-empty file. This usually indicates a malformed SEARCH marker.\n" +
"Please ensure your SEARCH marker follows the correct format:\n" +
"- Use '------- SEARCH' (7+ dashes + space + SEARCH)\n",
)
}
} else {
// Add check for inefficient full-file search
@@ -570,7 +581,7 @@ class NewFileContentConstructor {
pendingNonStandardLineLimit: number,
): number {
let removeLineCount = 0
if (line === SEARCH_BLOCK_START) {
if (isSearchBlockStart(line)) {
removeLineCount = this.trimPendingNonStandardTrailingEmptyLines(pendingNonStandardLineLimit)
if (removeLineCount > 0) {
pendingNonStandardLineLimit = pendingNonStandardLineLimit - removeLineCount
@@ -580,7 +591,7 @@ class NewFileContentConstructor {
canWritependingNonStandardLines && (this.pendingNonStandardLines.length = 0)
}
this.activateSearchState()
} else if (line === SEARCH_BLOCK_END) {
} else if (isSearchBlockEnd(line)) {
// 校验非标内容
if (!this.isSearchingActive()) {
this.tryFixSearchBlock(pendingNonStandardLineLimit)
@@ -588,7 +599,7 @@ class NewFileContentConstructor {
}
this.activateReplaceState()
this.beforeReplace()
} else if (line === REPLACE_BLOCK_END) {
} else if (isReplaceBlockEnd(line)) {
if (!this.isReplacingActive()) {
this.tryFixReplaceBlock(pendingNonStandardLineLimit)
canWritependingNonStandardLines && (this.pendingNonStandardLines.length = 0)
@@ -695,7 +706,7 @@ class NewFileContentConstructor {
if (!lineLimit) {
throw new Error("Invalid SEARCH/REPLACE block structure - no lines available to process")
}
let searchTagRegexp = /^[-]{3,} SEARCH$/
let searchTagRegexp = /^([-]{3,}|[<]{3,}) SEARCH$/
const searchTagIndex = this.findLastMatchingLineIndex(searchTagRegexp, lineLimit)
if (searchTagIndex !== -1) {
let fixLines = this.pendingNonStandardLines.slice(searchTagIndex, lineLimit)
@@ -746,7 +757,7 @@ class NewFileContentConstructor {
throw new Error()
}
let replaceEndTagRegexp = /^[+]{3,} REPLACE$/
let replaceEndTagRegexp = /^([+]{3,}|[>]{3,}) REPLACE$/
const replaceEndTagIndex = this.findLastMatchingLineIndex(replaceEndTagRegexp, lineLimit)
const likeReplaceEndTag = replaceEndTagIndex === lineLimit - 1
if (likeReplaceEndTag) {
@@ -795,7 +806,11 @@ export async function constructNewFileContentV2(diffContent: string, originalCon
const lastLine = lines[lines.length - 1]
if (
lines.length > 0 &&
(lastLine.startsWith(SEARCH_BLOCK_CHAR) || lastLine.startsWith("=") || lastLine.startsWith(REPLACE_BLOCK_CHAR)) &&
(lastLine.startsWith(SEARCH_BLOCK_CHAR) ||
lastLine.startsWith(LEGACY_SEARCH_BLOCK_CHAR) ||
lastLine.startsWith("=") ||
lastLine.startsWith(REPLACE_BLOCK_CHAR) ||
lastLine.startsWith(LEGACY_REPLACE_BLOCK_CHAR)) &&
lastLine !== SEARCH_BLOCK_START &&
lastLine !== SEARCH_BLOCK_END &&
lastLine !== REPLACE_BLOCK_END
@@ -94,27 +94,27 @@ const AutoApproveMenuItem = ({
<HeroTooltip content={action.description} delay={500}>
<CheckboxContainer isFavorited={favorited} onClick={onChange}>
<div className="left-content">
{onToggleFavorite && !condensed && (
<HeroTooltip
delay={500}
content={favorited ? "Remove from quick-access menu" : "Add to quick-access menu"}>
<span
className={`p-0.5 codicon codicon-${favorited ? "star-full" : "star-empty"} star`}
style={{
cursor: "pointer",
}}
onClick={async (e) => {
e.stopPropagation()
if (action.id === "enableAll") return
await onToggleFavorite?.(action.id)
}}
/>
</HeroTooltip>
)}
<VSCodeCheckbox checked={checked} />
{showIcon && <span className={`codicon ${action.icon} icon`}></span>}
<span className="label">{condensed ? action.shortName : action.label}</span>
</div>
{onToggleFavorite && !condensed && (
<HeroTooltip
delay={500}
content={favorited ? "Remove from quick-access menu" : "Add to quick-access menu"}>
<span
className={`p-0.5 codicon codicon-${favorited ? "star-full" : "star-empty"} star`}
style={{
cursor: "pointer",
}}
onClick={async (e) => {
e.stopPropagation()
if (action.id === "enableAll") return
await onToggleFavorite?.(action.id)
}}
/>
</HeroTooltip>
)}
</CheckboxContainer>
</HeroTooltip>
</ActionButtonContainer>