From c8d03bf799736a11a33223c064c1d4572c258ddf Mon Sep 17 00:00:00 2001 From: duanfuxiang Date: Tue, 1 Apr 2025 18:56:11 +0800 Subject: [PATCH] update fetch urls & web search show view --- src/components/chat-view/Chat.tsx | 1 - .../MarkdownFetchUrlsContentBlock.tsx | 85 +- .../chat-view/MarkdownListFilesBlock.tsx | 2 - .../MarkdownRegexSearchFilesBlock.tsx | 2 - .../chat-view/MarkdownSearchWebBlock.tsx | 23 +- .../MarkdownSemanticSearchFilesBlock.tsx | 2 - .../__tests__/multi-search-replace.test.ts | 1566 ----------------- .../strategies/__tests__/new-unified.test.ts | 738 -------- .../__tests__/search-replace.test.ts | 1557 ---------------- .../diff/strategies/__tests__/unified.test.ts | 228 --- .../__tests__/edit-strategies.test.ts | 295 ---- .../__tests__/search-strategies.test.ts | 262 --- .../strategies/new-unified/edit-strategies.ts | 28 +- src/core/diff/strategies/new-unified/index.ts | 3 +- .../new-unified/search-strategies.ts | 12 - src/core/llm/gemini.ts | 1 - src/core/prompts/responses.ts | 136 -- src/core/prompts/tools/browser-action.ts | 52 - src/core/prompts/tools/execute-command.ts | 17 - src/core/prompts/tools/index.ts | 9 +- src/database/database-manager.ts | 57 +- .../modules/vector/vector-repository.ts | 4 - src/utils/parse-infio-block.ts | 2 +- src/utils/web-search.ts | 5 +- styles.css | 36 + 25 files changed, 156 insertions(+), 4967 deletions(-) delete mode 100644 src/core/diff/strategies/__tests__/multi-search-replace.test.ts delete mode 100644 src/core/diff/strategies/__tests__/new-unified.test.ts delete mode 100644 src/core/diff/strategies/__tests__/search-replace.test.ts delete mode 100644 src/core/diff/strategies/__tests__/unified.test.ts delete mode 100644 src/core/diff/strategies/new-unified/__tests__/edit-strategies.test.ts delete mode 100644 src/core/diff/strategies/new-unified/__tests__/search-strategies.test.ts delete mode 100644 src/core/prompts/responses.ts delete mode 100644 src/core/prompts/tools/browser-action.ts delete mode 100644 src/core/prompts/tools/execute-command.ts diff --git a/src/components/chat-view/Chat.tsx b/src/components/chat-view/Chat.tsx index d30a5c6..deacb92 100644 --- a/src/components/chat-view/Chat.tsx +++ b/src/components/chat-view/Chat.tsx @@ -607,7 +607,6 @@ const Chat = forwardRef((props, ref) => { query: toolArgs.query, scope: scope_folders, }) - console.log("results", results) let snippets = results.map(({ path, content, metadata }) => { const contentWithLineNumbers = addLineNumbers(content, metadata.startLine) return `\n${contentWithLineNumbers}\n` diff --git a/src/components/chat-view/MarkdownFetchUrlsContentBlock.tsx b/src/components/chat-view/MarkdownFetchUrlsContentBlock.tsx index b20ca02..ba6df45 100644 --- a/src/components/chat-view/MarkdownFetchUrlsContentBlock.tsx +++ b/src/components/chat-view/MarkdownFetchUrlsContentBlock.tsx @@ -1,11 +1,8 @@ -import { ChevronDown, ChevronRight, Globe } from 'lucide-react' -import React, { useEffect, useMemo, useRef, useState } from 'react' +import { Check, ChevronDown, ChevronRight, Globe, Loader2, X } from 'lucide-react' +import React, { useEffect, useRef, useState } from 'react' -import { useDarkModeContext } from '../../contexts/DarkModeContext' import { ApplyStatus, FetchUrlsContentToolArgs } from '../../types/apply' -import { MemoizedSyntaxHighlighterWrapper } from './SyntaxHighlighterWrapper' - export default function MarkdownFetchUrlsContentBlock({ applyStatus, onApply, @@ -17,14 +14,11 @@ export default function MarkdownFetchUrlsContentBlock({ urls: string[], finish: boolean }) { - const { isDarkMode } = useDarkModeContext() const containerRef = useRef(null) const [isOpen, setIsOpen] = useState(true) React.useEffect(() => { - console.log('finish', finish, applyStatus) if (finish && applyStatus === ApplyStatus.Idle) { - console.log('finish auto fetch urls content', urls) onApply({ type: 'fetch_urls_content', urls: urls @@ -32,48 +26,67 @@ export default function MarkdownFetchUrlsContentBlock({ } }, [finish]) - const urlsMarkdownContent = useMemo(() => { - return urls.map(url => { - return `${url}` - }).join('\n\n') - }, [urls]) - useEffect(() => { if (containerRef.current) { containerRef.current.scrollTop = containerRef.current.scrollHeight } - }, [urlsMarkdownContent]) + }, [urls]) return ( - urlsMarkdownContent && ( -
-
-
+ urls.length > 0 && ( +
+
+
Fetch URLs Content
- +
+ + +
- - {urlsMarkdownContent} - +
    + {urls.map((url, index) => ( +
  • + + {url} + +
  • + ))} +
) diff --git a/src/components/chat-view/MarkdownListFilesBlock.tsx b/src/components/chat-view/MarkdownListFilesBlock.tsx index 7db761a..b165f4e 100644 --- a/src/components/chat-view/MarkdownListFilesBlock.tsx +++ b/src/components/chat-view/MarkdownListFilesBlock.tsx @@ -25,9 +25,7 @@ export default function MarkdownListFilesBlock({ } React.useEffect(() => { - console.log('finish', finish, applyStatus) if (finish && applyStatus === ApplyStatus.Idle) { - console.log('finish auto list files', path) onApply({ type: 'list_files', filepath: path, diff --git a/src/components/chat-view/MarkdownRegexSearchFilesBlock.tsx b/src/components/chat-view/MarkdownRegexSearchFilesBlock.tsx index 55378d6..b3642da 100644 --- a/src/components/chat-view/MarkdownRegexSearchFilesBlock.tsx +++ b/src/components/chat-view/MarkdownRegexSearchFilesBlock.tsx @@ -25,9 +25,7 @@ export default function MarkdownRegexSearchFilesBlock({ } React.useEffect(() => { - console.log('finish', finish, applyStatus) if (finish && applyStatus === ApplyStatus.Idle) { - console.log('finish auto regex search files', path) onApply({ type: 'regex_search_files', filepath: path, diff --git a/src/components/chat-view/MarkdownSearchWebBlock.tsx b/src/components/chat-view/MarkdownSearchWebBlock.tsx index e545ea8..fefb5f6 100644 --- a/src/components/chat-view/MarkdownSearchWebBlock.tsx +++ b/src/components/chat-view/MarkdownSearchWebBlock.tsx @@ -1,4 +1,4 @@ -import { Search } from 'lucide-react' +import { Check, Loader2, Search, X } from 'lucide-react' import React from 'react' import { useSettings } from '../../contexts/SettingsContext' @@ -48,6 +48,27 @@ export default function MarkdownWebSearchBlock({ Web search: {query}
+
+ +
) diff --git a/src/components/chat-view/MarkdownSemanticSearchFilesBlock.tsx b/src/components/chat-view/MarkdownSemanticSearchFilesBlock.tsx index 07b6bfa..9f15f35 100644 --- a/src/components/chat-view/MarkdownSemanticSearchFilesBlock.tsx +++ b/src/components/chat-view/MarkdownSemanticSearchFilesBlock.tsx @@ -25,9 +25,7 @@ export default function MarkdownSemanticSearchFilesBlock({ } React.useEffect(() => { - console.log('finish', finish, applyStatus) if (finish && applyStatus === ApplyStatus.Idle) { - console.log('finish auto semantic search files', path) onApply({ type: 'semantic_search_files', filepath: path, diff --git a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts b/src/core/diff/strategies/__tests__/multi-search-replace.test.ts deleted file mode 100644 index 8fc16d2..0000000 --- a/src/core/diff/strategies/__tests__/multi-search-replace.test.ts +++ /dev/null @@ -1,1566 +0,0 @@ -import { MultiSearchReplaceDiffStrategy } from "../multi-search-replace" - -describe("MultiSearchReplaceDiffStrategy", () => { - describe("exact matching", () => { - let strategy: MultiSearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new MultiSearchReplaceDiffStrategy(1.0, 5) // Default 1.0 threshold for exact matching, 5 line buffer for tests - }) - - it("should replace matching content", async () => { - const originalContent = 'function hello() {\n console.log("hello")\n}\n' - const diffContent = `test.ts -<<<<<<< SEARCH -function hello() { - console.log("hello") -} -======= -function hello() { - console.log("hello world") -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe('function hello() {\n console.log("hello world")\n}\n') - } - }) - - it("should match content with different surrounding whitespace", async () => { - const originalContent = "\nfunction example() {\n return 42;\n}\n\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function example() { - return 42; -} -======= -function example() { - return 43; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("\nfunction example() {\n return 43;\n}\n\n") - } - }) - - it("should match content with different indentation in search block", async () => { - const originalContent = " function test() {\n return true;\n }\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function test() { - return true; -} -======= -function test() { - return false; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(" function test() {\n return false;\n }\n") - } - }) - - it("should handle tab-based indentation", async () => { - const originalContent = "function test() {\n\treturn true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function test() { -\treturn true; -} -======= -function test() { -\treturn false; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n\treturn false;\n}\n") - } - }) - - it("should preserve mixed tabs and spaces", async () => { - const originalContent = "\tclass Example {\n\t constructor() {\n\t\tthis.value = 0;\n\t }\n\t}" - const diffContent = `test.ts -<<<<<<< SEARCH -\tclass Example { -\t constructor() { -\t\tthis.value = 0; -\t } -\t} -======= -\tclass Example { -\t constructor() { -\t\tthis.value = 1; -\t } -\t} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - "\tclass Example {\n\t constructor() {\n\t\tthis.value = 1;\n\t }\n\t}", - ) - } - }) - - it("should handle additional indentation with tabs", async () => { - const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}" - const diffContent = `test.ts -<<<<<<< SEARCH -function test() { -\treturn true; -} -======= -function test() { -\t// Add comment -\treturn false; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("\tfunction test() {\n\t\t// Add comment\n\t\treturn false;\n\t}") - } - }) - - it("should preserve exact indentation characters when adding lines", async () => { - const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}" - const diffContent = `test.ts -<<<<<<< SEARCH -\tfunction test() { -\t\treturn true; -\t} -======= -\tfunction test() { -\t\t// First comment -\t\t// Second comment -\t\treturn true; -\t} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - "\tfunction test() {\n\t\t// First comment\n\t\t// Second comment\n\t\treturn true;\n\t}", - ) - } - }) - - it("should handle Windows-style CRLF line endings", async () => { - const originalContent = "function test() {\r\n return true;\r\n}\r\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function test() { - return true; -} -======= -function test() { - return false; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\r\n return false;\r\n}\r\n") - } - }) - - it("should return false if search content does not match", async () => { - const originalContent = 'function hello() {\n console.log("hello")\n}\n' - const diffContent = `test.ts -<<<<<<< SEARCH -function hello() { - console.log("wrong") -} -======= -function hello() { - console.log("hello world") -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - - it("should return false if diff format is invalid", async () => { - const originalContent = 'function hello() {\n console.log("hello")\n}\n' - const diffContent = `test.ts\nInvalid diff format` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - - it("should handle multiple lines with proper indentation", async () => { - const originalContent = - "class Example {\n constructor() {\n this.value = 0\n }\n\n getValue() {\n return this.value\n }\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH - getValue() { - return this.value - } -======= - getValue() { - // Add logging - console.log("Getting value") - return this.value - } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - 'class Example {\n constructor() {\n this.value = 0\n }\n\n getValue() {\n // Add logging\n console.log("Getting value")\n return this.value\n }\n}\n', - ) - } - }) - - it("should preserve whitespace exactly in the output", async () => { - const originalContent = " indented\n more indented\n back\n" - const diffContent = `test.ts -<<<<<<< SEARCH - indented - more indented - back -======= - modified - still indented - end ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(" modified\n still indented\n end\n") - } - }) - - it("should preserve indentation when adding new lines after existing content", async () => { - const originalContent = " onScroll={() => updateHighlights()}" - const diffContent = `test.ts -<<<<<<< SEARCH - onScroll={() => updateHighlights()} -======= - onScroll={() => updateHighlights()} - onDragOver={(e) => { - e.preventDefault() - e.stopPropagation() - }} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - " onScroll={() => updateHighlights()}\n onDragOver={(e) => {\n e.preventDefault()\n e.stopPropagation()\n }}", - ) - } - }) - - it("should handle varying indentation levels correctly", async () => { - const originalContent = ` -class Example { - constructor() { - this.value = 0; - if (true) { - this.init(); - } - } -}`.trim() - - const diffContent = `test.ts -<<<<<<< SEARCH - class Example { - constructor() { - this.value = 0; - if (true) { - this.init(); - } - } - } -======= - class Example { - constructor() { - this.value = 1; - if (true) { - this.init(); - this.setup(); - this.validate(); - } - } - } ->>>>>>> REPLACE`.trim() - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - ` -class Example { - constructor() { - this.value = 1; - if (true) { - this.init(); - this.setup(); - this.validate(); - } - } -}`.trim(), - ) - } - }) - - it("should handle mixed indentation styles in the same file", async () => { - const originalContent = `class Example { - constructor() { - this.value = 0; - if (true) { - this.init(); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - constructor() { - this.value = 0; - if (true) { - this.init(); - } - } -======= - constructor() { - this.value = 1; - if (true) { - this.init(); - this.validate(); - } - } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - this.value = 1; - if (true) { - this.init(); - this.validate(); - } - } -}`) - } - }) - - it("should handle Python-style significant whitespace", async () => { - const originalContent = `def example(): - if condition: - do_something() - for item in items: - process(item) - return True`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - if condition: - do_something() - for item in items: - process(item) -======= - if condition: - do_something() - while items: - item = items.pop() - process(item) ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`def example(): - if condition: - do_something() - while items: - item = items.pop() - process(item) - return True`) - } - }) - - it("should preserve empty lines with indentation", async () => { - const originalContent = `function test() { - const x = 1; - - if (x) { - return true; - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - const x = 1; - - if (x) { -======= - const x = 1; - - // Check x - if (x) { ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - const x = 1; - - // Check x - if (x) { - return true; - } -}`) - } - }) - - it("should handle indentation when replacing entire blocks", async () => { - const originalContent = `class Test { - method() { - if (true) { - console.log("test"); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - method() { - if (true) { - console.log("test"); - } - } -======= - method() { - try { - if (true) { - console.log("test"); - } - } catch (e) { - console.error(e); - } - } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Test { - method() { - try { - if (true) { - console.log("test"); - } - } catch (e) { - console.error(e); - } - } -}`) - } - }) - - it("should handle negative indentation relative to search content", async () => { - const originalContent = `class Example { - constructor() { - if (true) { - this.init(); - this.setup(); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - this.init(); - this.setup(); -======= - this.init(); - this.setup(); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - if (true) { - this.init(); - this.setup(); - } - } -}`) - } - }) - - it("should handle extreme negative indentation (no indent)", async () => { - const originalContent = `class Example { - constructor() { - if (true) { - this.init(); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - this.init(); -======= -this.init(); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - if (true) { -this.init(); - } - } -}`) - } - }) - - it("should handle mixed indentation changes in replace block", async () => { - const originalContent = `class Example { - constructor() { - if (true) { - this.init(); - this.setup(); - this.validate(); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - this.init(); - this.setup(); - this.validate(); -======= - this.init(); - this.setup(); - this.validate(); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - if (true) { - this.init(); - this.setup(); - this.validate(); - } - } -}`) - } - }) - - it("should find matches from middle out", async () => { - const originalContent = ` -function one() { - return "target"; -} - -function two() { - return "target"; -} - -function three() { - return "target"; -} - -function four() { - return "target"; -} - -function five() { - return "target"; -}`.trim() - - const diffContent = `test.ts -<<<<<<< SEARCH - return "target"; -======= - return "updated"; ->>>>>>> REPLACE` - - // Search around the middle (function three) - // Even though all functions contain the target text, - // it should match the one closest to line 9 first - const result = await strategy.applyDiff(originalContent, diffContent, 9, 9) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return "target"; -} - -function two() { - return "target"; -} - -function three() { - return "updated"; -} - -function four() { - return "target"; -} - -function five() { - return "target"; -}`) - } - }) - }) - - describe("line number stripping", () => { - describe("line number stripping", () => { - let strategy: MultiSearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new MultiSearchReplaceDiffStrategy() - }) - - it("should strip line numbers from both search and replace sections", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -1 | function test() { -2 | return true; -3 | } -======= -1 | function test() { -2 | return false; -3 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n return false;\n}\n") - } - }) - - it("should strip line numbers with leading spaces", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH - 1 | function test() { - 2 | return true; - 3 | } -======= - 1 | function test() { - 2 | return false; - 3 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n return false;\n}\n") - } - }) - - it("should not strip when not all lines have numbers in either section", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -1 | function test() { -2 | return true; -3 | } -======= -1 | function test() { - return false; -3 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - - it("should preserve content that naturally starts with pipe", async () => { - const originalContent = "|header|another|\n|---|---|\n|data|more|\n" - const diffContent = `test.ts -<<<<<<< SEARCH -1 | |header|another| -2 | |---|---| -3 | |data|more| -======= -1 | |header|another| -2 | |---|---| -3 | |data|updated| ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("|header|another|\n|---|---|\n|data|updated|\n") - } - }) - - it("should preserve indentation when stripping line numbers", async () => { - const originalContent = " function test() {\n return true;\n }\n" - const diffContent = `test.ts -<<<<<<< SEARCH -1 | function test() { -2 | return true; -3 | } -======= -1 | function test() { -2 | return false; -3 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(" function test() {\n return false;\n }\n") - } - }) - - it("should handle different line numbers between sections", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -10 | function test() { -11 | return true; -12 | } -======= -20 | function test() { -21 | return false; -22 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n return false;\n}\n") - } - }) - - it("should not strip content that starts with pipe but no line number", async () => { - const originalContent = "| Pipe\n|---|\n| Data\n" - const diffContent = `test.ts -<<<<<<< SEARCH -| Pipe -|---| -| Data -======= -| Pipe -|---| -| Updated ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("| Pipe\n|---|\n| Updated\n") - } - }) - - it("should handle mix of line-numbered and pipe-only content", async () => { - const originalContent = "| Pipe\n|---|\n| Data\n" - const diffContent = `test.ts -<<<<<<< SEARCH -| Pipe -|---| -| Data -======= -1 | | Pipe -2 | |---| -3 | | NewData ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("1 | | Pipe\n2 | |---|\n3 | | NewData\n") - } - }) - }) - }) - - describe("insertion/deletion", () => { - let strategy: MultiSearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new MultiSearchReplaceDiffStrategy() - }) - - describe("deletion", () => { - it("should delete code when replace block is empty", async () => { - const originalContent = `function test() { - console.log("hello"); - // Comment to remove - console.log("world"); -}` - const diffContent = `test.ts -<<<<<<< SEARCH - // Comment to remove -======= ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - console.log("hello"); - console.log("world"); -}`) - } - }) - - it("should delete multiple lines when replace block is empty", async () => { - const originalContent = `class Example { - constructor() { - // Initialize - this.value = 0; - // Set defaults - this.name = ""; - // End init - } -}` - const diffContent = `test.ts -<<<<<<< SEARCH - // Initialize - this.value = 0; - // Set defaults - this.name = ""; - // End init -======= ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - } -}`) - } - }) - - it("should preserve indentation when deleting nested code", async () => { - const originalContent = `function outer() { - if (true) { - // Remove this - console.log("test"); - // And this - } - return true; -}` - const diffContent = `test.ts -<<<<<<< SEARCH - // Remove this - console.log("test"); - // And this -======= ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function outer() { - if (true) { - } - return true; -}`) - } - }) - }) - - describe("insertion", () => { - it("should insert code at specified line when search block is empty", async () => { - const originalContent = `function test() { - const x = 1; - return x; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -:start_line:2 -:end_line:2 -------- -======= - console.log("Adding log"); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 2, 2) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - console.log("Adding log"); - const x = 1; - return x; -}`) - } - }) - - it("should preserve indentation when inserting at nested location", async () => { - const originalContent = `function test() { - if (true) { - const x = 1; - } -}` - const diffContent = `test.ts -<<<<<<< SEARCH -:start_line:3 -:end_line:3 -------- -======= - console.log("Before"); - console.log("After"); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 3, 3) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - if (true) { - console.log("Before"); - console.log("After"); - const x = 1; - } -}`) - } - }) - - it("should handle insertion at start of file", async () => { - const originalContent = `function test() { - return true; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -:start_line:1 -:end_line:1 -------- -======= -// Copyright 2024 -// License: MIT - ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 1, 1) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`// Copyright 2024 -// License: MIT - -function test() { - return true; -}`) - } - }) - - it("should handle insertion at end of file", async () => { - const originalContent = `function test() { - return true; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -:start_line:4 -:end_line:4 -------- -======= - -// End of file ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 4, 4) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - return true; -} - -// End of file`) - } - }) - - it("should error if no start_line is provided for insertion", async () => { - const originalContent = `function test() { - return true; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -======= -console.log("test"); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - }) - }) - - describe("fuzzy matching", () => { - let strategy: MultiSearchReplaceDiffStrategy - beforeEach(() => { - strategy = new MultiSearchReplaceDiffStrategy(0.9, 5) // 90% similarity threshold, 5 line buffer for tests - }) - - it("should match content with small differences (>90% similar)", async () => { - const originalContent = - "function getData() {\n const results = fetchData();\n return results.filter(Boolean);\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function getData() { - const result = fetchData(); - return results.filter(Boolean); -} -======= -function getData() { - const data = fetchData(); - return data.filter(Boolean); -} ->>>>>>> REPLACE` - - strategy = new MultiSearchReplaceDiffStrategy(0.9, 5) // Use 5 line buffer for tests - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - "function getData() {\n const data = fetchData();\n return data.filter(Boolean);\n}\n", - ) - } - }) - - it("should not match when content is too different (<90% similar)", async () => { - const originalContent = "function processUsers(data) {\n return data.map(user => user.name);\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function handleItems(items) { - return items.map(item => item.username); -} -======= -function processData(data) { - return data.map(d => d.value); -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - - it("should match content with extra whitespace", async () => { - const originalContent = "function sum(a, b) {\n return a + b;\n}" - const diffContent = `test.ts -<<<<<<< SEARCH -function sum(a, b) { - return a + b; -} -======= -function sum(a, b) { - return a + b + 1; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function sum(a, b) {\n return a + b + 1;\n}") - } - }) - - it("should not exact match empty lines", async () => { - const originalContent = "function sum(a, b) {\n\n return a + b;\n}" - const diffContent = `test.ts -<<<<<<< SEARCH -function sum(a, b) { -======= -import { a } from "a"; -function sum(a, b) { ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe('import { a } from "a";\nfunction sum(a, b) {\n\n return a + b;\n}') - } - }) - }) - - describe("line-constrained search", () => { - let strategy: MultiSearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new MultiSearchReplaceDiffStrategy(0.9, 5) - }) - - it("should find and replace within specified line range", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function two() { - return 2; -} -======= -function two() { - return "two"; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 5, 7) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function two() { - return "two"; -} - -function three() { - return 3; -}`) - } - }) - - it("should find and replace within buffer zone (5 lines before/after)", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function three() { - return 3; -} -======= -function three() { - return "three"; -} ->>>>>>> REPLACE` - - // Even though we specify lines 5-7, it should still find the match at lines 9-11 - // because it's within the 5-line buffer zone - const result = await strategy.applyDiff(originalContent, diffContent, 5, 7) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return "three"; -}`) - } - }) - - it("should not find matches outside search range and buffer zone", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} - -function four() { - return 4; -} - -function five() { - return 5; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -:start_line:5 -:end_line:7 -------- -function five() { - return 5; -} -======= -function five() { - return "five"; -} ->>>>>>> REPLACE` - - // Searching around function two() (lines 5-7) - // function five() is more than 5 lines away, so it shouldn't match - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - - it("should handle search range at start of file", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function one() { - return 1; -} -======= -function one() { - return "one"; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 1, 3) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return "one"; -} - -function two() { - return 2; -}`) - } - }) - - it("should handle search range at end of file", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function two() { - return 2; -} -======= -function two() { - return "two"; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 5, 7) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function two() { - return "two"; -}`) - } - }) - - it("should match specific instance of duplicate code using line numbers", async () => { - const originalContent = ` -function processData(data) { - return data.map(x => x * 2); -} - -function unrelatedStuff() { - console.log("hello"); -} - -// Another data processor -function processData(data) { - return data.map(x => x * 2); -} - -function moreStuff() { - console.log("world"); -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function processData(data) { - return data.map(x => x * 2); -} -======= -function processData(data) { - // Add logging - console.log("Processing data..."); - return data.map(x => x * 2); -} ->>>>>>> REPLACE` - - // Target the second instance of processData - const result = await strategy.applyDiff(originalContent, diffContent, 10, 12) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function processData(data) { - return data.map(x => x * 2); -} - -function unrelatedStuff() { - console.log("hello"); -} - -// Another data processor -function processData(data) { - // Add logging - console.log("Processing data..."); - return data.map(x => x * 2); -} - -function moreStuff() { - console.log("world"); -}`) - } - }) - - it("should search from start line to end of file when only start_line is provided", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function three() { - return 3; -} -======= -function three() { - return "three"; -} ->>>>>>> REPLACE` - - // Only provide start_line, should search from there to end of file - const result = await strategy.applyDiff(originalContent, diffContent, 8) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return "three"; -}`) - } - }) - - it("should search from start of file to end line when only end_line is provided", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function one() { - return 1; -} -======= -function one() { - return "one"; -} ->>>>>>> REPLACE` - - // Only provide end_line, should search from start of file to there - const result = await strategy.applyDiff(originalContent, diffContent, undefined, 4) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return "one"; -} - -function two() { - return 2; -} - -function three() { - return 3; -}`) - } - }) - - it("should prioritize exact line match over expanded search", async () => { - const originalContent = ` -function one() { - return 1; -} - -function process() { - return "old"; -} - -function process() { - return "old"; -} - -function two() { - return 2; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -function process() { - return "old"; -} -======= -function process() { - return "new"; -} ->>>>>>> REPLACE` - - // Should match the second instance exactly at lines 10-12 - // even though the first instance at 6-8 is within the expanded search range - const result = await strategy.applyDiff(originalContent, diffContent, 10, 12) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(` -function one() { - return 1; -} - -function process() { - return "old"; -} - -function process() { - return "new"; -} - -function two() { - return 2; -}`) - } - }) - - it("should fall back to expanded search only if exact match fails", async () => { - const originalContent = ` -function one() { - return 1; -} - -function process() { - return "target"; -} - -function two() { - return 2; -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function process() { - return "target"; -} -======= -function process() { - return "updated"; -} ->>>>>>> REPLACE` - - // Specify wrong line numbers (3-5), but content exists at 6-8 - // Should still find and replace it since it's within the expanded range - const result = await strategy.applyDiff(originalContent, diffContent, 3, 5) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function process() { - return "updated"; -} - -function two() { - return 2; -}`) - } - }) - }) - - describe("getToolDescription", () => { - let strategy: MultiSearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new MultiSearchReplaceDiffStrategy() - }) - - it("should include the current working directory", async () => { - const cwd = "/test/dir" - const description = await strategy.getToolDescription({ cwd }) - expect(description).toContain(`relative to the current working directory ${cwd}`) - }) - - it("should include required format elements", async () => { - const description = await strategy.getToolDescription({ cwd: "/test" }) - expect(description).toContain("<<<<<<< SEARCH") - expect(description).toContain("=======") - expect(description).toContain(">>>>>>> REPLACE") - expect(description).toContain("") - expect(description).toContain("") - }) - }) -}) diff --git a/src/core/diff/strategies/__tests__/new-unified.test.ts b/src/core/diff/strategies/__tests__/new-unified.test.ts deleted file mode 100644 index 8832f9e..0000000 --- a/src/core/diff/strategies/__tests__/new-unified.test.ts +++ /dev/null @@ -1,738 +0,0 @@ -import { NewUnifiedDiffStrategy } from "../new-unified" - -describe("main", () => { - let strategy: NewUnifiedDiffStrategy - - beforeEach(() => { - strategy = new NewUnifiedDiffStrategy(0.97) - }) - - describe("constructor", () => { - it("should use default confidence threshold when not provided", () => { - const defaultStrategy = new NewUnifiedDiffStrategy() - expect(defaultStrategy["confidenceThreshold"]).toBe(1) - }) - - it("should use provided confidence threshold", () => { - const customStrategy = new NewUnifiedDiffStrategy(0.85) - expect(customStrategy["confidenceThreshold"]).toBe(0.85) - }) - - it("should enforce minimum confidence threshold", () => { - const lowStrategy = new NewUnifiedDiffStrategy(0.7) // Below minimum of 0.8 - expect(lowStrategy["confidenceThreshold"]).toBe(0.8) - }) - }) - - describe("getToolDescription", () => { - it("should return tool description with correct cwd", () => { - const cwd = "/test/path" - const description = strategy.getToolDescription({ cwd }) - - expect(description).toContain("apply_diff Tool - Generate Precise Code Changes") - expect(description).toContain(cwd) - expect(description).toContain("Step-by-Step Instructions") - expect(description).toContain("Requirements") - expect(description).toContain("Examples") - expect(description).toContain("Parameters:") - }) - }) - - it("should apply simple diff correctly", async () => { - const original = `line1 -line2 -line3` - - const diff = `--- a/file.txt -+++ b/file.txt -@@ ... @@ - line1 -+new line - line2 --line3 -+modified line3` - - const result = await strategy.applyDiff(original, diff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`line1 -new line -line2 -modified line3`) - } - }) - - it("should handle multiple hunks", async () => { - const original = `line1 -line2 -line3 -line4 -line5` - - const diff = `--- a/file.txt -+++ b/file.txt -@@ ... @@ - line1 -+new line - line2 --line3 -+modified line3 -@@ ... @@ - line4 --line5 -+modified line5 -+new line at end` - - const result = await strategy.applyDiff(original, diff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`line1 -new line -line2 -modified line3 -line4 -modified line5 -new line at end`) - } - }) - - it("should handle complex large", async () => { - const original = `line1 -line2 -line3 -line4 -line5 -line6 -line7 -line8 -line9 -line10` - - const diff = `--- a/file.txt -+++ b/file.txt -@@ ... @@ - line1 -+header line -+another header - line2 --line3 --line4 -+modified line3 -+modified line4 -+extra line -@@ ... @@ - line6 -+middle section - line7 --line8 -+changed line8 -+bonus line -@@ ... @@ - line9 --line10 -+final line -+very last line` - - const result = await strategy.applyDiff(original, diff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`line1 -header line -another header -line2 -modified line3 -modified line4 -extra line -line5 -line6 -middle section -line7 -changed line8 -bonus line -line9 -final line -very last line`) - } - }) - - it("should handle indentation changes", async () => { - const original = `first line - indented line - double indented line - back to single indent -no indent - indented again - double indent again - triple indent - back to single -last line` - - const diff = `--- original -+++ modified -@@ ... @@ - first line - indented line -+ tab indented line -+ new indented line - double indented line - back to single indent - no indent - indented again - double indent again -- triple indent -+ hi there mate - back to single - last line` - - const expected = `first line - indented line - tab indented line - new indented line - double indented line - back to single indent -no indent - indented again - double indent again - hi there mate - back to single -last line` - - const result = await strategy.applyDiff(original, diff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(expected) - } - }) - - it("should handle high level edits", async () => { - const original = `def factorial(n): - if n == 0: - return 1 - else: - return n * factorial(n-1)` - const diff = `@@ ... @@ --def factorial(n): -- if n == 0: -- return 1 -- else: -- return n * factorial(n-1) -+def factorial(number): -+ if number == 0: -+ return 1 -+ else: -+ return number * factorial(number-1)` - - const expected = `def factorial(number): - if number == 0: - return 1 - else: - return number * factorial(number-1)` - - const result = await strategy.applyDiff(original, diff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(expected) - } - }) - - it("it should handle very complex edits", async () => { - const original = `//Initialize the array that will hold the primes -var primeArray = []; -/*Write a function that checks for primeness and - pushes those values to t*he array*/ -function PrimeCheck(candidate){ - isPrime = true; - for(var i = 2; i < candidate && isPrime; i++){ - if(candidate%i === 0){ - isPrime = false; - } else { - isPrime = true; - } - } - if(isPrime){ - primeArray.push(candidate); - } - return primeArray; -} -/*Write the code that runs the above until the - l ength of the array equa*ls the number of primes - desired*/ - -var numPrimes = prompt("How many primes?"); - -//Display the finished array of primes - -//for loop starting at 2 as that is the lowest prime number keep going until the array is as long as we requested -for (var i = 2; primeArray.length < numPrimes; i++) { - PrimeCheck(i); // -} -console.log(primeArray); -` - - const diff = `--- test_diff.js -+++ test_diff.js -@@ ... @@ --//Initialize the array that will hold the primes - var primeArray = []; --/*Write a function that checks for primeness and -- pushes those values to t*he array*/ - function PrimeCheck(candidate){ - isPrime = true; - for(var i = 2; i < candidate && isPrime; i++){ -@@ ... @@ - return primeArray; - } --/*Write the code that runs the above until the -- l ength of the array equa*ls the number of primes -- desired*/ - - var numPrimes = prompt("How many primes?"); - --//Display the finished array of primes -- --//for loop starting at 2 as that is the lowest prime number keep going until the array is as long as we requested - for (var i = 2; primeArray.length < numPrimes; i++) { -- PrimeCheck(i); // -+ PrimeCheck(i); - } - console.log(primeArray);` - - const expected = `var primeArray = []; -function PrimeCheck(candidate){ - isPrime = true; - for(var i = 2; i < candidate && isPrime; i++){ - if(candidate%i === 0){ - isPrime = false; - } else { - isPrime = true; - } - } - if(isPrime){ - primeArray.push(candidate); - } - return primeArray; -} - -var numPrimes = prompt("How many primes?"); - -for (var i = 2; primeArray.length < numPrimes; i++) { - PrimeCheck(i); -} -console.log(primeArray); -` - - const result = await strategy.applyDiff(original, diff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(expected) - } - }) - - describe("error handling and edge cases", () => { - it("should reject completely invalid diff format", async () => { - const original = "line1\nline2\nline3" - const invalidDiff = "this is not a diff at all" - - const result = await strategy.applyDiff(original, invalidDiff) - expect(result.success).toBe(false) - }) - - it("should reject diff with invalid hunk format", async () => { - const original = "line1\nline2\nline3" - const invalidHunkDiff = `--- a/file.txt -+++ b/file.txt -invalid hunk header - line1 --line2 -+new line` - - const result = await strategy.applyDiff(original, invalidHunkDiff) - expect(result.success).toBe(false) - }) - - it("should fail when diff tries to modify non-existent content", async () => { - const original = "line1\nline2\nline3" - const nonMatchingDiff = `--- a/file.txt -+++ b/file.txt -@@ ... @@ - line1 --nonexistent line -+new line - line3` - - const result = await strategy.applyDiff(original, nonMatchingDiff) - expect(result.success).toBe(false) - }) - - it("should handle overlapping hunks", async () => { - const original = `line1 -line2 -line3 -line4 -line5` - const overlappingDiff = `--- a/file.txt -+++ b/file.txt -@@ ... @@ - line1 - line2 --line3 -+modified3 - line4 -@@ ... @@ - line2 --line3 --line4 -+modified3and4 - line5` - - const result = await strategy.applyDiff(original, overlappingDiff) - expect(result.success).toBe(false) - }) - - it("should handle empty lines modifications", async () => { - const original = `line1 - -line3 - -line5` - const emptyLinesDiff = `--- a/file.txt -+++ b/file.txt -@@ ... @@ - line1 - --line3 -+line3modified - - line5` - - const result = await strategy.applyDiff(original, emptyLinesDiff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`line1 - -line3modified - -line5`) - } - }) - - it("should handle mixed line endings in diff", async () => { - const original = "line1\r\nline2\nline3\r\n" - const mixedEndingsDiff = `--- a/file.txt -+++ b/file.txt -@@ ... @@ - line1\r --line2 -+modified2\r - line3` - - const result = await strategy.applyDiff(original, mixedEndingsDiff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("line1\r\nmodified2\r\nline3\r\n") - } - }) - - it("should handle partial line modifications", async () => { - const original = "const value = oldValue + 123;" - const partialDiff = `--- a/file.txt -+++ b/file.txt -@@ ... @@ --const value = oldValue + 123; -+const value = newValue + 123;` - - const result = await strategy.applyDiff(original, partialDiff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("const value = newValue + 123;") - } - }) - - it("should handle slightly malformed but recoverable diff", async () => { - const original = "line1\nline2\nline3" - // Missing space after --- and +++ - const slightlyBadDiff = `---a/file.txt -+++b/file.txt -@@ ... @@ - line1 --line2 -+new line - line3` - - const result = await strategy.applyDiff(original, slightlyBadDiff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("line1\nnew line\nline3") - } - }) - }) - - describe("similar code sections", () => { - it("should correctly modify the right section when similar code exists", async () => { - const original = `function add(a, b) { - return a + b; -} - -function subtract(a, b) { - return a - b; -} - -function multiply(a, b) { - return a + b; // Bug here -}` - - const diff = `--- a/math.js -+++ b/math.js -@@ ... @@ - function multiply(a, b) { -- return a + b; // Bug here -+ return a * b; - }` - - const result = await strategy.applyDiff(original, diff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function add(a, b) { - return a + b; -} - -function subtract(a, b) { - return a - b; -} - -function multiply(a, b) { - return a * b; -}`) - } - }) - - it("should handle multiple similar sections with correct context", async () => { - const original = `if (condition) { - doSomething(); - doSomething(); - doSomething(); -} - -if (otherCondition) { - doSomething(); - doSomething(); - doSomething(); -}` - - const diff = `--- a/file.js -+++ b/file.js -@@ ... @@ - if (otherCondition) { - doSomething(); -- doSomething(); -+ doSomethingElse(); - doSomething(); - }` - - const result = await strategy.applyDiff(original, diff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`if (condition) { - doSomething(); - doSomething(); - doSomething(); -} - -if (otherCondition) { - doSomething(); - doSomethingElse(); - doSomething(); -}`) - } - }) - }) - - describe("hunk splitting", () => { - it("should handle large diffs with multiple non-contiguous changes", async () => { - const original = `import { readFile } from 'fs'; -import { join } from 'path'; -import { Logger } from './logger'; - -const logger = new Logger(); - -async function processFile(filePath: string) { - try { - const data = await readFile(filePath, 'utf8'); - logger.info('File read successfully'); - return data; - } catch (error) { - logger.error('Failed to read file:', error); - throw error; - } -} - -function validateInput(input: string): boolean { - if (!input) { - logger.warn('Empty input received'); - return false; - } - return input.length > 0; -} - -async function writeOutput(data: string) { - logger.info('Processing output'); - // TODO: Implement output writing - return Promise.resolve(); -} - -function parseConfig(configPath: string) { - logger.debug('Reading config from:', configPath); - // Basic config parsing - return { - enabled: true, - maxRetries: 3 - }; -} - -export { - processFile, - validateInput, - writeOutput, - parseConfig -};` - - const diff = `--- a/file.ts -+++ b/file.ts -@@ ... @@ --import { readFile } from 'fs'; -+import { readFile, writeFile } from 'fs'; - import { join } from 'path'; --import { Logger } from './logger'; -+import { Logger } from './utils/logger'; -+import { Config } from './types'; - --const logger = new Logger(); -+const logger = new Logger('FileProcessor'); - - async function processFile(filePath: string) { - try { - const data = await readFile(filePath, 'utf8'); -- logger.info('File read successfully'); -+ logger.info(\`File \${filePath} read successfully\`); - return data; - } catch (error) { -- logger.error('Failed to read file:', error); -+ logger.error(\`Failed to read file \${filePath}:\`, error); - throw error; - } - } - - function validateInput(input: string): boolean { - if (!input) { -- logger.warn('Empty input received'); -+ logger.warn('Validation failed: Empty input received'); - return false; - } -- return input.length > 0; -+ return input.trim().length > 0; - } - --async function writeOutput(data: string) { -- logger.info('Processing output'); -- // TODO: Implement output writing -- return Promise.resolve(); -+async function writeOutput(data: string, outputPath: string) { -+ try { -+ await writeFile(outputPath, data, 'utf8'); -+ logger.info(\`Output written to \${outputPath}\`); -+ } catch (error) { -+ logger.error(\`Failed to write output to \${outputPath}:\`, error); -+ throw error; -+ } - } - --function parseConfig(configPath: string) { -- logger.debug('Reading config from:', configPath); -- // Basic config parsing -- return { -- enabled: true, -- maxRetries: 3 -- }; -+async function parseConfig(configPath: string): Promise { -+ try { -+ const configData = await readFile(configPath, 'utf8'); -+ logger.debug(\`Reading config from \${configPath}\`); -+ return JSON.parse(configData); -+ } catch (error) { -+ logger.error(\`Failed to parse config from \${configPath}:\`, error); -+ throw error; -+ } - } - - export { - processFile, - validateInput, - writeOutput, -- parseConfig -+ parseConfig, -+ type Config - };` - - const expected = `import { readFile, writeFile } from 'fs'; -import { join } from 'path'; -import { Logger } from './utils/logger'; -import { Config } from './types'; - -const logger = new Logger('FileProcessor'); - -async function processFile(filePath: string) { - try { - const data = await readFile(filePath, 'utf8'); - logger.info(\`File \${filePath} read successfully\`); - return data; - } catch (error) { - logger.error(\`Failed to read file \${filePath}:\`, error); - throw error; - } -} - -function validateInput(input: string): boolean { - if (!input) { - logger.warn('Validation failed: Empty input received'); - return false; - } - return input.trim().length > 0; -} - -async function writeOutput(data: string, outputPath: string) { - try { - await writeFile(outputPath, data, 'utf8'); - logger.info(\`Output written to \${outputPath}\`); - } catch (error) { - logger.error(\`Failed to write output to \${outputPath}:\`, error); - throw error; - } -} - -async function parseConfig(configPath: string): Promise { - try { - const configData = await readFile(configPath, 'utf8'); - logger.debug(\`Reading config from \${configPath}\`); - return JSON.parse(configData); - } catch (error) { - logger.error(\`Failed to parse config from \${configPath}:\`, error); - throw error; - } -} - -export { - processFile, - validateInput, - writeOutput, - parseConfig, - type Config -};` - - const result = await strategy.applyDiff(original, diff) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(expected) - } - }) - }) -}) diff --git a/src/core/diff/strategies/__tests__/search-replace.test.ts b/src/core/diff/strategies/__tests__/search-replace.test.ts deleted file mode 100644 index cd71eda..0000000 --- a/src/core/diff/strategies/__tests__/search-replace.test.ts +++ /dev/null @@ -1,1557 +0,0 @@ -import { SearchReplaceDiffStrategy } from "../search-replace" - -describe("SearchReplaceDiffStrategy", () => { - describe("exact matching", () => { - let strategy: SearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new SearchReplaceDiffStrategy(1.0, 5) // Default 1.0 threshold for exact matching, 5 line buffer for tests - }) - - it("should replace matching content", async () => { - const originalContent = 'function hello() {\n console.log("hello")\n}\n' - const diffContent = `test.ts -<<<<<<< SEARCH -function hello() { - console.log("hello") -} -======= -function hello() { - console.log("hello world") -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe('function hello() {\n console.log("hello world")\n}\n') - } - }) - - it("should match content with different surrounding whitespace", async () => { - const originalContent = "\nfunction example() {\n return 42;\n}\n\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function example() { - return 42; -} -======= -function example() { - return 43; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("\nfunction example() {\n return 43;\n}\n\n") - } - }) - - it("should match content with different indentation in search block", async () => { - const originalContent = " function test() {\n return true;\n }\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function test() { - return true; -} -======= -function test() { - return false; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(" function test() {\n return false;\n }\n") - } - }) - - it("should handle tab-based indentation", async () => { - const originalContent = "function test() {\n\treturn true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function test() { -\treturn true; -} -======= -function test() { -\treturn false; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n\treturn false;\n}\n") - } - }) - - it("should preserve mixed tabs and spaces", async () => { - const originalContent = "\tclass Example {\n\t constructor() {\n\t\tthis.value = 0;\n\t }\n\t}" - const diffContent = `test.ts -<<<<<<< SEARCH -\tclass Example { -\t constructor() { -\t\tthis.value = 0; -\t } -\t} -======= -\tclass Example { -\t constructor() { -\t\tthis.value = 1; -\t } -\t} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - "\tclass Example {\n\t constructor() {\n\t\tthis.value = 1;\n\t }\n\t}", - ) - } - }) - - it("should handle additional indentation with tabs", async () => { - const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}" - const diffContent = `test.ts -<<<<<<< SEARCH -function test() { -\treturn true; -} -======= -function test() { -\t// Add comment -\treturn false; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("\tfunction test() {\n\t\t// Add comment\n\t\treturn false;\n\t}") - } - }) - - it("should preserve exact indentation characters when adding lines", async () => { - const originalContent = "\tfunction test() {\n\t\treturn true;\n\t}" - const diffContent = `test.ts -<<<<<<< SEARCH -\tfunction test() { -\t\treturn true; -\t} -======= -\tfunction test() { -\t\t// First comment -\t\t// Second comment -\t\treturn true; -\t} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - "\tfunction test() {\n\t\t// First comment\n\t\t// Second comment\n\t\treturn true;\n\t}", - ) - } - }) - - it("should handle Windows-style CRLF line endings", async () => { - const originalContent = "function test() {\r\n return true;\r\n}\r\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function test() { - return true; -} -======= -function test() { - return false; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\r\n return false;\r\n}\r\n") - } - }) - - it("should return false if search content does not match", async () => { - const originalContent = 'function hello() {\n console.log("hello")\n}\n' - const diffContent = `test.ts -<<<<<<< SEARCH -function hello() { - console.log("wrong") -} -======= -function hello() { - console.log("hello world") -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - - it("should return false if diff format is invalid", async () => { - const originalContent = 'function hello() {\n console.log("hello")\n}\n' - const diffContent = `test.ts\nInvalid diff format` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - - it("should handle multiple lines with proper indentation", async () => { - const originalContent = - "class Example {\n constructor() {\n this.value = 0\n }\n\n getValue() {\n return this.value\n }\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH - getValue() { - return this.value - } -======= - getValue() { - // Add logging - console.log("Getting value") - return this.value - } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - 'class Example {\n constructor() {\n this.value = 0\n }\n\n getValue() {\n // Add logging\n console.log("Getting value")\n return this.value\n }\n}\n', - ) - } - }) - - it("should preserve whitespace exactly in the output", async () => { - const originalContent = " indented\n more indented\n back\n" - const diffContent = `test.ts -<<<<<<< SEARCH - indented - more indented - back -======= - modified - still indented - end ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(" modified\n still indented\n end\n") - } - }) - - it("should preserve indentation when adding new lines after existing content", async () => { - const originalContent = " onScroll={() => updateHighlights()}" - const diffContent = `test.ts -<<<<<<< SEARCH - onScroll={() => updateHighlights()} -======= - onScroll={() => updateHighlights()} - onDragOver={(e) => { - e.preventDefault() - e.stopPropagation() - }} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - " onScroll={() => updateHighlights()}\n onDragOver={(e) => {\n e.preventDefault()\n e.stopPropagation()\n }}", - ) - } - }) - - it("should handle varying indentation levels correctly", async () => { - const originalContent = ` -class Example { - constructor() { - this.value = 0; - if (true) { - this.init(); - } - } -}`.trim() - - const diffContent = `test.ts -<<<<<<< SEARCH - class Example { - constructor() { - this.value = 0; - if (true) { - this.init(); - } - } - } -======= - class Example { - constructor() { - this.value = 1; - if (true) { - this.init(); - this.setup(); - this.validate(); - } - } - } ->>>>>>> REPLACE`.trim() - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - ` -class Example { - constructor() { - this.value = 1; - if (true) { - this.init(); - this.setup(); - this.validate(); - } - } -}`.trim(), - ) - } - }) - - it("should handle mixed indentation styles in the same file", async () => { - const originalContent = `class Example { - constructor() { - this.value = 0; - if (true) { - this.init(); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - constructor() { - this.value = 0; - if (true) { - this.init(); - } - } -======= - constructor() { - this.value = 1; - if (true) { - this.init(); - this.validate(); - } - } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - this.value = 1; - if (true) { - this.init(); - this.validate(); - } - } -}`) - } - }) - - it("should handle Python-style significant whitespace", async () => { - const originalContent = `def example(): - if condition: - do_something() - for item in items: - process(item) - return True`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - if condition: - do_something() - for item in items: - process(item) -======= - if condition: - do_something() - while items: - item = items.pop() - process(item) ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`def example(): - if condition: - do_something() - while items: - item = items.pop() - process(item) - return True`) - } - }) - - it("should preserve empty lines with indentation", async () => { - const originalContent = `function test() { - const x = 1; - - if (x) { - return true; - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - const x = 1; - - if (x) { -======= - const x = 1; - - // Check x - if (x) { ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - const x = 1; - - // Check x - if (x) { - return true; - } -}`) - } - }) - - it("should handle indentation when replacing entire blocks", async () => { - const originalContent = `class Test { - method() { - if (true) { - console.log("test"); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - method() { - if (true) { - console.log("test"); - } - } -======= - method() { - try { - if (true) { - console.log("test"); - } - } catch (e) { - console.error(e); - } - } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Test { - method() { - try { - if (true) { - console.log("test"); - } - } catch (e) { - console.error(e); - } - } -}`) - } - }) - - it("should handle negative indentation relative to search content", async () => { - const originalContent = `class Example { - constructor() { - if (true) { - this.init(); - this.setup(); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - this.init(); - this.setup(); -======= - this.init(); - this.setup(); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - if (true) { - this.init(); - this.setup(); - } - } -}`) - } - }) - - it("should handle extreme negative indentation (no indent)", async () => { - const originalContent = `class Example { - constructor() { - if (true) { - this.init(); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - this.init(); -======= -this.init(); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - if (true) { -this.init(); - } - } -}`) - } - }) - - it("should handle mixed indentation changes in replace block", async () => { - const originalContent = `class Example { - constructor() { - if (true) { - this.init(); - this.setup(); - this.validate(); - } - } -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH - this.init(); - this.setup(); - this.validate(); -======= - this.init(); - this.setup(); - this.validate(); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - if (true) { - this.init(); - this.setup(); - this.validate(); - } - } -}`) - } - }) - - it("should find matches from middle out", async () => { - const originalContent = ` -function one() { - return "target"; -} - -function two() { - return "target"; -} - -function three() { - return "target"; -} - -function four() { - return "target"; -} - -function five() { - return "target"; -}`.trim() - - const diffContent = `test.ts -<<<<<<< SEARCH - return "target"; -======= - return "updated"; ->>>>>>> REPLACE` - - // Search around the middle (function three) - // Even though all functions contain the target text, - // it should match the one closest to line 9 first - const result = await strategy.applyDiff(originalContent, diffContent, 9, 9) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return "target"; -} - -function two() { - return "target"; -} - -function three() { - return "updated"; -} - -function four() { - return "target"; -} - -function five() { - return "target"; -}`) - } - }) - }) - - describe("line number stripping", () => { - describe("line number stripping", () => { - let strategy: SearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new SearchReplaceDiffStrategy() - }) - - it("should strip line numbers from both search and replace sections", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -1 | function test() { -2 | return true; -3 | } -======= -1 | function test() { -2 | return false; -3 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n return false;\n}\n") - } - }) - - it("should strip line numbers with leading spaces", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH - 1 | function test() { - 2 | return true; - 3 | } -======= - 1 | function test() { - 2 | return false; - 3 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n return false;\n}\n") - } - }) - - it("should not strip when not all lines have numbers in either section", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -1 | function test() { -2 | return true; -3 | } -======= -1 | function test() { - return false; -3 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - - it("should preserve content that naturally starts with pipe", async () => { - const originalContent = "|header|another|\n|---|---|\n|data|more|\n" - const diffContent = `test.ts -<<<<<<< SEARCH -1 | |header|another| -2 | |---|---| -3 | |data|more| -======= -1 | |header|another| -2 | |---|---| -3 | |data|updated| ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("|header|another|\n|---|---|\n|data|updated|\n") - } - }) - - it("should preserve indentation when stripping line numbers", async () => { - const originalContent = " function test() {\n return true;\n }\n" - const diffContent = `test.ts -<<<<<<< SEARCH -1 | function test() { -2 | return true; -3 | } -======= -1 | function test() { -2 | return false; -3 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(" function test() {\n return false;\n }\n") - } - }) - - it("should handle different line numbers between sections", async () => { - const originalContent = "function test() {\n return true;\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -10 | function test() { -11 | return true; -12 | } -======= -20 | function test() { -21 | return false; -22 | } ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function test() {\n return false;\n}\n") - } - }) - - it("should not strip content that starts with pipe but no line number", async () => { - const originalContent = "| Pipe\n|---|\n| Data\n" - const diffContent = `test.ts -<<<<<<< SEARCH -| Pipe -|---| -| Data -======= -| Pipe -|---| -| Updated ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("| Pipe\n|---|\n| Updated\n") - } - }) - - it("should handle mix of line-numbered and pipe-only content", async () => { - const originalContent = "| Pipe\n|---|\n| Data\n" - const diffContent = `test.ts -<<<<<<< SEARCH -| Pipe -|---| -| Data -======= -1 | | Pipe -2 | |---| -3 | | NewData ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("1 | | Pipe\n2 | |---|\n3 | | NewData\n") - } - }) - }) - }) - - describe("insertion/deletion", () => { - let strategy: SearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new SearchReplaceDiffStrategy() - }) - - describe("deletion", () => { - it("should delete code when replace block is empty", async () => { - const originalContent = `function test() { - console.log("hello"); - // Comment to remove - console.log("world"); -}` - const diffContent = `test.ts -<<<<<<< SEARCH - // Comment to remove -======= ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - console.log("hello"); - console.log("world"); -}`) - } - }) - - it("should delete multiple lines when replace block is empty", async () => { - const originalContent = `class Example { - constructor() { - // Initialize - this.value = 0; - // Set defaults - this.name = ""; - // End init - } -}` - const diffContent = `test.ts -<<<<<<< SEARCH - // Initialize - this.value = 0; - // Set defaults - this.name = ""; - // End init -======= ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`class Example { - constructor() { - } -}`) - } - }) - - it("should preserve indentation when deleting nested code", async () => { - const originalContent = `function outer() { - if (true) { - // Remove this - console.log("test"); - // And this - } - return true; -}` - const diffContent = `test.ts -<<<<<<< SEARCH - // Remove this - console.log("test"); - // And this -======= ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function outer() { - if (true) { - } - return true; -}`) - } - }) - }) - - describe("insertion", () => { - it("should insert code at specified line when search block is empty", async () => { - const originalContent = `function test() { - const x = 1; - return x; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -======= - console.log("Adding log"); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 2, 2) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - console.log("Adding log"); - const x = 1; - return x; -}`) - } - }) - - it("should preserve indentation when inserting at nested location", async () => { - const originalContent = `function test() { - if (true) { - const x = 1; - } -}` - const diffContent = `test.ts -<<<<<<< SEARCH -======= - console.log("Before"); - console.log("After"); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 3, 3) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - if (true) { - console.log("Before"); - console.log("After"); - const x = 1; - } -}`) - } - }) - - it("should handle insertion at start of file", async () => { - const originalContent = `function test() { - return true; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -======= -// Copyright 2024 -// License: MIT - ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 1, 1) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`// Copyright 2024 -// License: MIT - -function test() { - return true; -}`) - } - }) - - it("should handle insertion at end of file", async () => { - const originalContent = `function test() { - return true; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -======= - -// End of file ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 4, 4) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function test() { - return true; -} - -// End of file`) - } - }) - - it("should error if no start_line is provided for insertion", async () => { - const originalContent = `function test() { - return true; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -======= -console.log("test"); ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - }) - }) - - describe("fuzzy matching", () => { - let strategy: SearchReplaceDiffStrategy - beforeEach(() => { - strategy = new SearchReplaceDiffStrategy(0.9, 5) // 90% similarity threshold, 5 line buffer for tests - }) - - it("should match content with small differences (>90% similar)", async () => { - const originalContent = - "function getData() {\n const results = fetchData();\n return results.filter(Boolean);\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function getData() { - const result = fetchData(); - return results.filter(Boolean); -} -======= -function getData() { - const data = fetchData(); - return data.filter(Boolean); -} ->>>>>>> REPLACE` - - strategy = new SearchReplaceDiffStrategy(0.9, 5) // Use 5 line buffer for tests - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe( - "function getData() {\n const data = fetchData();\n return data.filter(Boolean);\n}\n", - ) - } - }) - - it("should not match when content is too different (<90% similar)", async () => { - const originalContent = "function processUsers(data) {\n return data.map(user => user.name);\n}\n" - const diffContent = `test.ts -<<<<<<< SEARCH -function handleItems(items) { - return items.map(item => item.username); -} -======= -function processData(data) { - return data.map(d => d.value); -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(false) - }) - - it("should match content with extra whitespace", async () => { - const originalContent = "function sum(a, b) {\n return a + b;\n}" - const diffContent = `test.ts -<<<<<<< SEARCH -function sum(a, b) { - return a + b; -} -======= -function sum(a, b) { - return a + b + 1; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe("function sum(a, b) {\n return a + b + 1;\n}") - } - }) - - it("should not exact match empty lines", async () => { - const originalContent = "function sum(a, b) {\n\n return a + b;\n}" - const diffContent = `test.ts -<<<<<<< SEARCH -function sum(a, b) { -======= -import { a } from "a"; -function sum(a, b) { ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe('import { a } from "a";\nfunction sum(a, b) {\n\n return a + b;\n}') - } - }) - }) - - describe("line-constrained search", () => { - let strategy: SearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new SearchReplaceDiffStrategy(0.9, 5) - }) - - it("should find and replace within specified line range", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function two() { - return 2; -} -======= -function two() { - return "two"; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 5, 7) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function two() { - return "two"; -} - -function three() { - return 3; -}`) - } - }) - - it("should find and replace within buffer zone (5 lines before/after)", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function three() { - return 3; -} -======= -function three() { - return "three"; -} ->>>>>>> REPLACE` - - // Even though we specify lines 5-7, it should still find the match at lines 9-11 - // because it's within the 5-line buffer zone - const result = await strategy.applyDiff(originalContent, diffContent, 5, 7) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return "three"; -}`) - } - }) - - it("should not find matches outside search range and buffer zone", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} - -function four() { - return 4; -} - -function five() { - return 5; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function five() { - return 5; -} -======= -function five() { - return "five"; -} ->>>>>>> REPLACE` - - // Searching around function two() (lines 5-7) - // function five() is more than 5 lines away, so it shouldn't match - const result = await strategy.applyDiff(originalContent, diffContent, 5, 7) - expect(result.success).toBe(false) - }) - - it("should handle search range at start of file", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function one() { - return 1; -} -======= -function one() { - return "one"; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 1, 3) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return "one"; -} - -function two() { - return 2; -}`) - } - }) - - it("should handle search range at end of file", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function two() { - return 2; -} -======= -function two() { - return "two"; -} ->>>>>>> REPLACE` - - const result = await strategy.applyDiff(originalContent, diffContent, 5, 7) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function two() { - return "two"; -}`) - } - }) - - it("should match specific instance of duplicate code using line numbers", async () => { - const originalContent = ` -function processData(data) { - return data.map(x => x * 2); -} - -function unrelatedStuff() { - console.log("hello"); -} - -// Another data processor -function processData(data) { - return data.map(x => x * 2); -} - -function moreStuff() { - console.log("world"); -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function processData(data) { - return data.map(x => x * 2); -} -======= -function processData(data) { - // Add logging - console.log("Processing data..."); - return data.map(x => x * 2); -} ->>>>>>> REPLACE` - - // Target the second instance of processData - const result = await strategy.applyDiff(originalContent, diffContent, 10, 12) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function processData(data) { - return data.map(x => x * 2); -} - -function unrelatedStuff() { - console.log("hello"); -} - -// Another data processor -function processData(data) { - // Add logging - console.log("Processing data..."); - return data.map(x => x * 2); -} - -function moreStuff() { - console.log("world"); -}`) - } - }) - - it("should search from start line to end of file when only start_line is provided", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function three() { - return 3; -} -======= -function three() { - return "three"; -} ->>>>>>> REPLACE` - - // Only provide start_line, should search from there to end of file - const result = await strategy.applyDiff(originalContent, diffContent, 8) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return "three"; -}`) - } - }) - - it("should search from start of file to end line when only end_line is provided", async () => { - const originalContent = ` -function one() { - return 1; -} - -function two() { - return 2; -} - -function three() { - return 3; -} -`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function one() { - return 1; -} -======= -function one() { - return "one"; -} ->>>>>>> REPLACE` - - // Only provide end_line, should search from start of file to there - const result = await strategy.applyDiff(originalContent, diffContent, undefined, 4) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return "one"; -} - -function two() { - return 2; -} - -function three() { - return 3; -}`) - } - }) - - it("should prioritize exact line match over expanded search", async () => { - const originalContent = ` -function one() { - return 1; -} - -function process() { - return "old"; -} - -function process() { - return "old"; -} - -function two() { - return 2; -}` - const diffContent = `test.ts -<<<<<<< SEARCH -function process() { - return "old"; -} -======= -function process() { - return "new"; -} ->>>>>>> REPLACE` - - // Should match the second instance exactly at lines 10-12 - // even though the first instance at 6-8 is within the expanded search range - const result = await strategy.applyDiff(originalContent, diffContent, 10, 12) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(` -function one() { - return 1; -} - -function process() { - return "old"; -} - -function process() { - return "new"; -} - -function two() { - return 2; -}`) - } - }) - - it("should fall back to expanded search only if exact match fails", async () => { - const originalContent = ` -function one() { - return 1; -} - -function process() { - return "target"; -} - -function two() { - return 2; -}`.trim() - const diffContent = `test.ts -<<<<<<< SEARCH -function process() { - return "target"; -} -======= -function process() { - return "updated"; -} ->>>>>>> REPLACE` - - // Specify wrong line numbers (3-5), but content exists at 6-8 - // Should still find and replace it since it's within the expanded range - const result = await strategy.applyDiff(originalContent, diffContent, 3, 5) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(`function one() { - return 1; -} - -function process() { - return "updated"; -} - -function two() { - return 2; -}`) - } - }) - }) - - describe("getToolDescription", () => { - let strategy: SearchReplaceDiffStrategy - - beforeEach(() => { - strategy = new SearchReplaceDiffStrategy() - }) - - it("should include the current working directory", async () => { - const cwd = "/test/dir" - const description = await strategy.getToolDescription({ cwd }) - expect(description).toContain(`relative to the current working directory ${cwd}`) - }) - - it("should include required format elements", async () => { - const description = await strategy.getToolDescription({ cwd: "/test" }) - expect(description).toContain("<<<<<<< SEARCH") - expect(description).toContain("=======") - expect(description).toContain(">>>>>>> REPLACE") - expect(description).toContain("") - expect(description).toContain("") - }) - - it("should document start_line and end_line parameters", async () => { - const description = await strategy.getToolDescription({ cwd: "/test" }) - expect(description).toContain("start_line: (required) The line number where the search block starts.") - expect(description).toContain("end_line: (required) The line number where the search block ends.") - }) - }) -}) diff --git a/src/core/diff/strategies/__tests__/unified.test.ts b/src/core/diff/strategies/__tests__/unified.test.ts deleted file mode 100644 index 1d9847b..0000000 --- a/src/core/diff/strategies/__tests__/unified.test.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { UnifiedDiffStrategy } from "../unified" - -describe("UnifiedDiffStrategy", () => { - let strategy: UnifiedDiffStrategy - - beforeEach(() => { - strategy = new UnifiedDiffStrategy() - }) - - describe("getToolDescription", () => { - it("should return tool description with correct cwd", () => { - const cwd = "/test/path" - const description = strategy.getToolDescription({ cwd }) - - expect(description).toContain("apply_diff") - expect(description).toContain(cwd) - expect(description).toContain("Parameters:") - expect(description).toContain("Format Requirements:") - }) - }) - - describe("applyDiff", () => { - it("should successfully apply a function modification diff", async () => { - const originalContent = `import { Logger } from '../logger'; - -function calculateTotal(items: number[]): number { - return items.reduce((sum, item) => { - return sum + item; - }, 0); -} - -export { calculateTotal };` - - const diffContent = `--- src/utils/helper.ts -+++ src/utils/helper.ts -@@ -1,9 +1,10 @@ - import { Logger } from '../logger'; - - function calculateTotal(items: number[]): number { -- return items.reduce((sum, item) => { -- return sum + item; -+ const total = items.reduce((sum, item) => { -+ return sum + item * 1.1; // Add 10% markup - }, 0); -+ return Math.round(total * 100) / 100; // Round to 2 decimal places - } - - export { calculateTotal };` - - const expected = `import { Logger } from '../logger'; - -function calculateTotal(items: number[]): number { - const total = items.reduce((sum, item) => { - return sum + item * 1.1; // Add 10% markup - }, 0); - return Math.round(total * 100) / 100; // Round to 2 decimal places -} - -export { calculateTotal };` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(expected) - } - }) - - it("should successfully apply a diff adding a new method", async () => { - const originalContent = `class Calculator { - add(a: number, b: number): number { - return a + b; - } -}` - - const diffContent = `--- src/Calculator.ts -+++ src/Calculator.ts -@@ -1,5 +1,9 @@ - class Calculator { - add(a: number, b: number): number { - return a + b; - } -+ -+ multiply(a: number, b: number): number { -+ return a * b; -+ } - }` - - const expected = `class Calculator { - add(a: number, b: number): number { - return a + b; - } - - multiply(a: number, b: number): number { - return a * b; - } -}` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(expected) - } - }) - - it("should successfully apply a diff modifying imports", async () => { - const originalContent = `import { useState } from 'react'; -import { Button } from './components'; - -function App() { - const [count, setCount] = useState(0); - return ; -}` - - const diffContent = `--- src/App.tsx -+++ src/App.tsx -@@ -1,7 +1,8 @@ --import { useState } from 'react'; -+import { useState, useEffect } from 'react'; - import { Button } from './components'; - - function App() { - const [count, setCount] = useState(0); -+ useEffect(() => { document.title = \`Count: \${count}\` }, [count]); - return ; - }` - - const expected = `import { useState, useEffect } from 'react'; -import { Button } from './components'; - -function App() { - const [count, setCount] = useState(0); - useEffect(() => { document.title = \`Count: \${count}\` }, [count]); - return ; -}` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(expected) - } - }) - - it("should successfully apply a diff with multiple hunks", async () => { - const originalContent = `import { readFile, writeFile } from 'fs'; - -function processFile(path: string) { - readFile(path, 'utf8', (err, data) => { - if (err) throw err; - const processed = data.toUpperCase(); - writeFile(path, processed, (err) => { - if (err) throw err; - }); - }); -} - -export { processFile };` - - const diffContent = `--- src/file-processor.ts -+++ src/file-processor.ts -@@ -1,12 +1,14 @@ --import { readFile, writeFile } from 'fs'; -+import { promises as fs } from 'fs'; -+import { join } from 'path'; - --function processFile(path: string) { -- readFile(path, 'utf8', (err, data) => { -- if (err) throw err; -+async function processFile(path: string) { -+ try { -+ const data = await fs.readFile(join(__dirname, path), 'utf8'); - const processed = data.toUpperCase(); -- writeFile(path, processed, (err) => { -- if (err) throw err; -- }); -- }); -+ await fs.writeFile(join(__dirname, path), processed); -+ } catch (error) { -+ console.error('Failed to process file:', error); -+ throw error; -+ } - } - - export { processFile };` - - const expected = `import { promises as fs } from 'fs'; -import { join } from 'path'; - -async function processFile(path: string) { - try { - const data = await fs.readFile(join(__dirname, path), 'utf8'); - const processed = data.toUpperCase(); - await fs.writeFile(join(__dirname, path), processed); - } catch (error) { - console.error('Failed to process file:', error); - throw error; - } -} - -export { processFile };` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(expected) - } - }) - - it("should handle empty original content", async () => { - const originalContent = "" - const diffContent = `--- empty.ts -+++ empty.ts -@@ -0,0 +1,3 @@ -+export function greet(name: string): string { -+ return \`Hello, \${name}!\`; -+}` - - const expected = `export function greet(name: string): string { - return \`Hello, \${name}!\`; -}\n` - - const result = await strategy.applyDiff(originalContent, diffContent) - expect(result.success).toBe(true) - if (result.success) { - expect(result.content).toBe(expected) - } - }) - }) -}) diff --git a/src/core/diff/strategies/new-unified/__tests__/edit-strategies.test.ts b/src/core/diff/strategies/new-unified/__tests__/edit-strategies.test.ts deleted file mode 100644 index 2bc3554..0000000 --- a/src/core/diff/strategies/new-unified/__tests__/edit-strategies.test.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { applyContextMatching, applyDMP, applyGitFallback } from "../edit-strategies" -import { Hunk } from "../types" - -const testCases = [ - { - name: "should return original content if no match is found", - hunk: { - changes: [ - { type: "context", content: "line1" }, - { type: "add", content: "line2" }, - ], - } as Hunk, - content: ["line1", "line3"], - matchPosition: -1, - expected: { - confidence: 0, - result: ["line1", "line3"], - }, - expectedResult: "line1\nline3", - strategies: ["context", "dmp"], - }, - { - name: "should apply a simple add change", - hunk: { - changes: [ - { type: "context", content: "line1" }, - { type: "add", content: "line2" }, - ], - } as Hunk, - content: ["line1", "line3"], - matchPosition: 0, - expected: { - confidence: 1, - result: ["line1", "line2", "line3"], - }, - expectedResult: "line1\nline2\nline3", - strategies: ["context", "dmp"], - }, - { - name: "should apply a simple remove change", - hunk: { - changes: [ - { type: "context", content: "line1" }, - { type: "remove", content: "line2" }, - ], - } as Hunk, - content: ["line1", "line2", "line3"], - matchPosition: 0, - expected: { - confidence: 1, - result: ["line1", "line3"], - }, - expectedResult: "line1\nline3", - strategies: ["context", "dmp"], - }, - { - name: "should apply a simple context change", - hunk: { - changes: [{ type: "context", content: "line1" }], - } as Hunk, - content: ["line1", "line2", "line3"], - matchPosition: 0, - expected: { - confidence: 1, - result: ["line1", "line2", "line3"], - }, - expectedResult: "line1\nline2\nline3", - strategies: ["context", "dmp"], - }, - { - name: "should apply a multi-line add change", - hunk: { - changes: [ - { type: "context", content: "line1" }, - { type: "add", content: "line2\nline3" }, - ], - } as Hunk, - content: ["line1", "line4"], - matchPosition: 0, - expected: { - confidence: 1, - result: ["line1", "line2\nline3", "line4"], - }, - expectedResult: "line1\nline2\nline3\nline4", - strategies: ["context", "dmp"], - }, - { - name: "should apply a multi-line remove change", - hunk: { - changes: [ - { type: "context", content: "line1" }, - { type: "remove", content: "line2\nline3" }, - ], - } as Hunk, - content: ["line1", "line2", "line3", "line4"], - matchPosition: 0, - expected: { - confidence: 1, - result: ["line1", "line4"], - }, - expectedResult: "line1\nline4", - strategies: ["context", "dmp"], - }, - { - name: "should apply a multi-line context change", - hunk: { - changes: [ - { type: "context", content: "line1" }, - { type: "context", content: "line2\nline3" }, - ], - } as Hunk, - content: ["line1", "line2", "line3", "line4"], - matchPosition: 0, - expected: { - confidence: 1, - result: ["line1", "line2\nline3", "line4"], - }, - expectedResult: "line1\nline2\nline3\nline4", - strategies: ["context", "dmp"], - }, - { - name: "should apply a change with indentation", - hunk: { - changes: [ - { type: "context", content: " line1" }, - { type: "add", content: " line2" }, - ], - } as Hunk, - content: [" line1", " line3"], - matchPosition: 0, - expected: { - confidence: 1, - result: [" line1", " line2", " line3"], - }, - expectedResult: " line1\n line2\n line3", - strategies: ["context", "dmp"], - }, - { - name: "should apply a change with mixed indentation", - hunk: { - changes: [ - { type: "context", content: "\tline1" }, - { type: "add", content: " line2" }, - ], - } as Hunk, - content: ["\tline1", " line3"], - matchPosition: 0, - expected: { - confidence: 1, - result: ["\tline1", " line2", " line3"], - }, - expectedResult: "\tline1\n line2\n line3", - strategies: ["context", "dmp"], - }, - { - name: "should apply a change with mixed indentation and multi-line", - hunk: { - changes: [ - { type: "context", content: " line1" }, - { type: "add", content: "\tline2\n line3" }, - ], - } as Hunk, - content: [" line1", " line4"], - matchPosition: 0, - expected: { - confidence: 1, - result: [" line1", "\tline2\n line3", " line4"], - }, - expectedResult: " line1\n\tline2\n line3\n line4", - strategies: ["context", "dmp"], - }, - { - name: "should apply a complex change with mixed indentation and multi-line", - hunk: { - changes: [ - { type: "context", content: " line1" }, - { type: "remove", content: " line2" }, - { type: "add", content: "\tline3\n line4" }, - { type: "context", content: " line5" }, - ], - } as Hunk, - content: [" line1", " line2", " line5", " line6"], - matchPosition: 0, - expected: { - confidence: 1, - result: [" line1", "\tline3\n line4", " line5", " line6"], - }, - expectedResult: " line1\n\tline3\n line4\n line5\n line6", - strategies: ["context", "dmp"], - }, - { - name: "should apply a complex change with mixed indentation and multi-line and context", - hunk: { - changes: [ - { type: "context", content: " line1" }, - { type: "remove", content: " line2" }, - { type: "add", content: "\tline3\n line4" }, - { type: "context", content: " line5" }, - { type: "context", content: " line6" }, - ], - } as Hunk, - content: [" line1", " line2", " line5", " line6", " line7"], - matchPosition: 0, - expected: { - confidence: 1, - result: [" line1", "\tline3\n line4", " line5", " line6", " line7"], - }, - expectedResult: " line1\n\tline3\n line4\n line5\n line6\n line7", - strategies: ["context", "dmp"], - }, - { - name: "should apply a complex change with mixed indentation and multi-line and context and a different match position", - hunk: { - changes: [ - { type: "context", content: " line1" }, - { type: "remove", content: " line2" }, - { type: "add", content: "\tline3\n line4" }, - { type: "context", content: " line5" }, - { type: "context", content: " line6" }, - ], - } as Hunk, - content: [" line0", " line1", " line2", " line5", " line6", " line7"], - matchPosition: 1, - expected: { - confidence: 1, - result: [" line0", " line1", "\tline3\n line4", " line5", " line6", " line7"], - }, - expectedResult: " line0\n line1\n\tline3\n line4\n line5\n line6\n line7", - strategies: ["context", "dmp"], - }, -] - -describe("applyContextMatching", () => { - testCases.forEach(({ name, hunk, content, matchPosition, expected, strategies, expectedResult }) => { - if (!strategies?.includes("context")) { - return - } - it(name, () => { - const result = applyContextMatching(hunk, content, matchPosition) - expect(result.result.join("\n")).toEqual(expectedResult) - expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence) - expect(result.strategy).toBe("context") - }) - }) -}) - -describe("applyDMP", () => { - testCases.forEach(({ name, hunk, content, matchPosition, expected, strategies, expectedResult }) => { - if (!strategies?.includes("dmp")) { - return - } - it(name, () => { - const result = applyDMP(hunk, content, matchPosition) - expect(result.result.join("\n")).toEqual(expectedResult) - expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence) - expect(result.strategy).toBe("dmp") - }) - }) -}) - -describe("applyGitFallback", () => { - it("should successfully apply changes using git operations", async () => { - const hunk = { - changes: [ - { type: "context", content: "line1", indent: "" }, - { type: "remove", content: "line2", indent: "" }, - { type: "add", content: "new line2", indent: "" }, - { type: "context", content: "line3", indent: "" }, - ], - } as Hunk - - const content = ["line1", "line2", "line3"] - const result = await applyGitFallback(hunk, content) - - expect(result.result.join("\n")).toEqual("line1\nnew line2\nline3") - expect(result.confidence).toBe(1) - expect(result.strategy).toBe("git-fallback") - }) - - it("should return original content with 0 confidence when changes cannot be applied", async () => { - const hunk = { - changes: [ - { type: "context", content: "nonexistent", indent: "" }, - { type: "add", content: "new line", indent: "" }, - ], - } as Hunk - - const content = ["line1", "line2", "line3"] - const result = await applyGitFallback(hunk, content) - - expect(result.result).toEqual(content) - expect(result.confidence).toBe(0) - expect(result.strategy).toBe("git-fallback") - }) -}) diff --git a/src/core/diff/strategies/new-unified/__tests__/search-strategies.test.ts b/src/core/diff/strategies/new-unified/__tests__/search-strategies.test.ts deleted file mode 100644 index 5bee537..0000000 --- a/src/core/diff/strategies/new-unified/__tests__/search-strategies.test.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { findAnchorMatch, findExactMatch, findSimilarityMatch, findLevenshteinMatch } from "../search-strategies" - -type SearchStrategy = ( - searchStr: string, - content: string[], - startIndex?: number, -) => { - index: number - confidence: number - strategy: string -} - -const testCases = [ - { - name: "should return no match if the search string is not found", - searchStr: "not found", - content: ["line1", "line2", "line3"], - expected: { index: -1, confidence: 0 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match if the search string is found", - searchStr: "line2", - content: ["line1", "line2", "line3"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match with correct index when startIndex is provided", - searchStr: "line3", - content: ["line1", "line2", "line3", "line4", "line3"], - startIndex: 3, - expected: { index: 4, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match even if there are more lines in content", - searchStr: "line2", - content: ["line1", "line2", "line3", "line4", "line5"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match even if the search string is at the beginning of the content", - searchStr: "line1", - content: ["line1", "line2", "line3"], - expected: { index: 0, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match even if the search string is at the end of the content", - searchStr: "line3", - content: ["line1", "line2", "line3"], - expected: { index: 2, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match for a multi-line search string", - searchStr: "line2\nline3", - content: ["line1", "line2", "line3", "line4"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return no match if a multi-line search string is not found", - searchStr: "line2\nline4", - content: ["line1", "line2", "line3", "line4"], - expected: { index: -1, confidence: 0 }, - strategies: ["exact", "similarity"], - }, - { - name: "should return a match with indentation", - searchStr: " line2", - content: ["line1", " line2", "line3"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match with more complex indentation", - searchStr: " line3", - content: [" line1", " line2", " line3", " line4"], - expected: { index: 2, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match with mixed indentation", - searchStr: "\tline2", - content: [" line1", "\tline2", " line3"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match with mixed indentation and multi-line", - searchStr: " line2\n\tline3", - content: ["line1", " line2", "\tline3", " line4"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return no match if mixed indentation and multi-line is not found", - searchStr: " line2\n line4", - content: ["line1", " line2", "\tline3", " line4"], - expected: { index: -1, confidence: 0 }, - strategies: ["exact", "similarity"], - }, - { - name: "should return a match with leading and trailing spaces", - searchStr: " line2 ", - content: ["line1", " line2 ", "line3"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match with leading and trailing tabs", - searchStr: "\tline2\t", - content: ["line1", "\tline2\t", "line3"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match with mixed leading and trailing spaces and tabs", - searchStr: " \tline2\t ", - content: ["line1", " \tline2\t ", "line3"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return a match with mixed leading and trailing spaces and tabs and multi-line", - searchStr: " \tline2\t \n line3 ", - content: ["line1", " \tline2\t ", " line3 ", "line4"], - expected: { index: 1, confidence: 1 }, - strategies: ["exact", "similarity", "levenshtein"], - }, - { - name: "should return no match if mixed leading and trailing spaces and tabs and multi-line is not found", - searchStr: " \tline2\t \n line4 ", - content: ["line1", " \tline2\t ", " line3 ", "line4"], - expected: { index: -1, confidence: 0 }, - strategies: ["exact", "similarity"], - }, -] - -describe("findExactMatch", () => { - testCases.forEach(({ name, searchStr, content, startIndex, expected, strategies }) => { - if (!strategies?.includes("exact")) { - return - } - it(name, () => { - const result = findExactMatch(searchStr, content, startIndex) - expect(result.index).toBe(expected.index) - expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence) - expect(result.strategy).toMatch(/exact(-overlapping)?/) - }) - }) -}) - -describe("findAnchorMatch", () => { - const anchorTestCases = [ - { - name: "should return no match if no anchors are found", - searchStr: " \n \n ", - content: ["line1", "line2", "line3"], - expected: { index: -1, confidence: 0 }, - }, - { - name: "should return no match if anchor positions cannot be validated", - searchStr: "unique line\ncontext line 1\ncontext line 2", - content: [ - "different line 1", - "different line 2", - "different line 3", - "another unique line", - "context line 1", - "context line 2", - ], - expected: { index: -1, confidence: 0 }, - }, - { - name: "should return a match if anchor positions can be validated", - searchStr: "unique line\ncontext line 1\ncontext line 2", - content: ["line1", "line2", "unique line", "context line 1", "context line 2", "line 6"], - expected: { index: 2, confidence: 1 }, - }, - { - name: "should return a match with correct index when startIndex is provided", - searchStr: "unique line\ncontext line 1\ncontext line 2", - content: ["line1", "line2", "line3", "unique line", "context line 1", "context line 2", "line 7"], - startIndex: 3, - expected: { index: 3, confidence: 1 }, - }, - { - name: "should return a match even if there are more lines in content", - searchStr: "unique line\ncontext line 1\ncontext line 2", - content: [ - "line1", - "line2", - "unique line", - "context line 1", - "context line 2", - "line 6", - "extra line 1", - "extra line 2", - ], - expected: { index: 2, confidence: 1 }, - }, - { - name: "should return a match even if the anchor is at the beginning of the content", - searchStr: "unique line\ncontext line 1\ncontext line 2", - content: ["unique line", "context line 1", "context line 2", "line 6"], - expected: { index: 0, confidence: 1 }, - }, - { - name: "should return a match even if the anchor is at the end of the content", - searchStr: "unique line\ncontext line 1\ncontext line 2", - content: ["line1", "line2", "unique line", "context line 1", "context line 2"], - expected: { index: 2, confidence: 1 }, - }, - { - name: "should return no match if no valid anchor is found", - searchStr: "non-unique line\ncontext line 1\ncontext line 2", - content: ["line1", "line2", "non-unique line", "context line 1", "context line 2", "non-unique line"], - expected: { index: -1, confidence: 0 }, - }, - ] - - anchorTestCases.forEach(({ name, searchStr, content, startIndex, expected }) => { - it(name, () => { - const result = findAnchorMatch(searchStr, content, startIndex) - expect(result.index).toBe(expected.index) - expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence) - expect(result.strategy).toBe("anchor") - }) - }) -}) - -describe("findSimilarityMatch", () => { - testCases.forEach(({ name, searchStr, content, startIndex, expected, strategies }) => { - if (!strategies?.includes("similarity")) { - return - } - it(name, () => { - const result = findSimilarityMatch(searchStr, content, startIndex) - expect(result.index).toBe(expected.index) - expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence) - expect(result.strategy).toBe("similarity") - }) - }) -}) - -describe("findLevenshteinMatch", () => { - testCases.forEach(({ name, searchStr, content, startIndex, expected, strategies }) => { - if (!strategies?.includes("levenshtein")) { - return - } - it(name, () => { - const result = findLevenshteinMatch(searchStr, content, startIndex) - expect(result.index).toBe(expected.index) - expect(result.confidence).toBeGreaterThanOrEqual(expected.confidence) - expect(result.strategy).toBe("levenshtein") - }) - }) -}) diff --git a/src/core/diff/strategies/new-unified/edit-strategies.ts b/src/core/diff/strategies/new-unified/edit-strategies.ts index 18c00b3..934ee59 100644 --- a/src/core/diff/strategies/new-unified/edit-strategies.ts +++ b/src/core/diff/strategies/new-unified/edit-strategies.ts @@ -157,7 +157,7 @@ export async function applyGitFallback(app: App, hunk: Hunk, content: string[]): const vaultBasePath = adapter.getBasePath(); const tmpGitPath = normalizePath(path.join(vaultBasePath, ".tmp_git")); - console.log("tmpGitPath", tmpGitPath) + // console.log("tmpGitPath", tmpGitPath) try { const exists = await adapter.exists(tmpGitPath); @@ -190,22 +190,22 @@ export async function applyGitFallback(app: App, hunk: Hunk, content: string[]): fs.writeFileSync(filePath, originalText) await git.add("file.txt") const originalCommit = await git.commit("original") - console.log("Strategy 1 - Original commit:", originalCommit.commit) + // console.log("Strategy 1 - Original commit:", originalCommit.commit) fs.writeFileSync(filePath, searchText) await git.add("file.txt") - const searchCommit1 = await git.commit("search") - console.log("Strategy 1 - Search commit:", searchCommit1.commit) + // const searchCommit1 = await git.commit("search") + // console.log("Strategy 1 - Search commit:", searchCommit1.commit) fs.writeFileSync(filePath, replaceText) await git.add("file.txt") const replaceCommit = await git.commit("replace") - console.log("Strategy 1 - Replace commit:", replaceCommit.commit) + // console.log("Strategy 1 - Replace commit:", replaceCommit.commit) - console.log("Strategy 1 - Attempting checkout of:", originalCommit.commit) + // console.log("Strategy 1 - Attempting checkout of:", originalCommit.commit) await git.raw(["checkout", originalCommit.commit]) try { - console.log("Strategy 1 - Attempting cherry-pick of:", replaceCommit.commit) + // console.log("Strategy 1 - Attempting cherry-pick of:", replaceCommit.commit) await git.raw(["cherry-pick", "--minimal", replaceCommit.commit]) const newText = fs.readFileSync(filePath, "utf-8") @@ -231,23 +231,23 @@ export async function applyGitFallback(app: App, hunk: Hunk, content: string[]): await git.add("file.txt") const searchCommit = await git.commit("search") const searchHash = searchCommit.commit.replace(/^HEAD /, "") - console.log("Strategy 2 - Search commit:", searchHash) + // console.log("Strategy 2 - Search commit:", searchHash) fs.writeFileSync(filePath, replaceText) await git.add("file.txt") const replaceCommit = await git.commit("replace") const replaceHash = replaceCommit.commit.replace(/^HEAD /, "") - console.log("Strategy 2 - Replace commit:", replaceHash) + // console.log("Strategy 2 - Replace commit:", replaceHash) - console.log("Strategy 2 - Attempting checkout of:", searchHash) + // console.log("Strategy 2 - Attempting checkout of:", searchHash) await git.raw(["checkout", searchHash]) fs.writeFileSync(filePath, originalText) await git.add("file.txt") - const originalCommit2 = await git.commit("original") - console.log("Strategy 2 - Original commit:", originalCommit2.commit) + // const originalCommit2 = await git.commit("original") + // console.log("Strategy 2 - Original commit:", originalCommit2.commit) try { - console.log("Strategy 2 - Attempting cherry-pick of:", replaceHash) + // console.log("Strategy 2 - Attempting cherry-pick of:", replaceHash) await git.raw(["cherry-pick", "--minimal", replaceHash]) const newText = fs.readFileSync(filePath, "utf-8") @@ -287,7 +287,7 @@ export async function applyEdit( ): Promise { // Don't attempt regular edits if confidence is too low if (confidence < confidenceThreshold) { - console.log( + console.warn( `Search confidence (${confidence}) below minimum threshold (${confidenceThreshold}), trying git fallback...`, ) return applyGitFallback(app, hunk, content) diff --git a/src/core/diff/strategies/new-unified/index.ts b/src/core/diff/strategies/new-unified/index.ts index 16fe733..e36369d 100644 --- a/src/core/diff/strategies/new-unified/index.ts +++ b/src/core/diff/strategies/new-unified/index.ts @@ -27,7 +27,6 @@ export class NewUnifiedDiffStrategy implements DiffStrategy { private parseUnifiedDiff(diff: string): Diff { const MAX_CONTEXT_LINES = 6 // Number of context lines to keep before/after changes const lines = diff.split("\n") - // console.log("lines: ", lines) const hunks: Hunk[] = [] let currentHunk: Hunk | null = null @@ -269,7 +268,7 @@ Your diff here strategy, } = findBestMatch(contextStr, result, 0, this.confidenceThreshold) if (confidence < this.confidenceThreshold) { - console.log("Full hunk application failed, trying sub-hunks strategy") + console.warn("Full hunk application failed, trying sub-hunks strategy") // Try splitting the hunk into smaller hunks const subHunks = this.splitHunk(hunk) let subHunkSuccess = true diff --git a/src/core/diff/strategies/new-unified/search-strategies.ts b/src/core/diff/strategies/new-unified/search-strategies.ts index 4aafa8b..97dcf3f 100644 --- a/src/core/diff/strategies/new-unified/search-strategies.ts +++ b/src/core/diff/strategies/new-unified/search-strategies.ts @@ -199,16 +199,12 @@ export function findExactMatch( startIndex: number = 0, confidenceThreshold: number = 0.97, ): SearchResult { - // console.log("searchStr: ", searchStr) - // console.log("content: ", content) const searchLines = searchStr.split("\n") const windows = createOverlappingWindows(content.slice(startIndex), searchLines.length) const matches: (SearchResult & { windowIndex: number })[] = [] windows.forEach((windowData, windowIndex) => { const windowStr = windowData.window.join("\n") - // console.log("searchStr: ", searchStr) - // console.log("windowStr:", windowStr) const exactMatch = windowStr.indexOf(searchStr) if (exactMatch !== -1) { @@ -404,18 +400,10 @@ export function findBestMatch( for (const strategy of strategies) { const result = strategy(searchStr, content, startIndex, confidenceThreshold) - if (searchStr === "由于年久失修,街区路面坑洼不平,污水横流,垃圾遍地,甚至可见弹痕血迹。") { - console.log("findBestMatch result: ", strategy.name, result) - } if (result.confidence > bestResult.confidence) { bestResult = result } } - // if (bestResult.confidence < 0.97) { - // console.log("searchStr: ", searchStr) - // console.log("content: ", content) - // console.log("findBestMatch result: ", bestResult) - // } return bestResult } diff --git a/src/core/llm/gemini.ts b/src/core/llm/gemini.ts index 22c12cb..3103694 100644 --- a/src/core/llm/gemini.ts +++ b/src/core/llm/gemini.ts @@ -60,7 +60,6 @@ export class GeminiProvider implements BaseLLMProvider { : undefined try { - console.log(request) const model = this.client.getGenerativeModel({ model: request.model, generationConfig: { diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts deleted file mode 100644 index 6e60d6c..0000000 --- a/src/core/prompts/responses.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { Anthropic } from "@anthropic-ai/sdk" -import * as diff from "diff" -import * as path from "path" - -export const formatResponse = { - toolDenied: () => `The user denied this operation.`, - - toolDeniedWithFeedback: (feedback?: string) => - `The user denied this operation and provided the following feedback:\n\n${feedback}\n`, - - toolApprovedWithFeedback: (feedback?: string) => - `The user approved this operation and provided the following context:\n\n${feedback}\n`, - - toolError: (error?: string) => `The tool execution failed with the following error:\n\n${error}\n`, - - noToolsUsed: () => - `[ERROR] You did not use a tool in your previous response! Please retry with a tool use. - -${toolUseInstructionsReminder} - -# Next Steps - -If you have completed the user's task, use the attempt_completion tool. -If you require additional information from the user, use the ask_followup_question tool. -Otherwise, if you have not completed the task and do not need additional information, then proceed with the next step of the task. -(This is an automated message, so do not respond to it conversationally.)`, - - tooManyMistakes: (feedback?: string) => - `You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n\n${feedback}\n`, - - missingToolParameterError: (paramName: string) => - `Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${toolUseInstructionsReminder}`, - - invalidMcpToolArgumentError: (serverName: string, toolName: string) => - `Invalid JSON argument used with ${serverName} for ${toolName}. Please retry with a properly formatted JSON argument.`, - - toolResult: ( - text: string, - images?: string[], - ): string | Array => { - if (images && images.length > 0) { - const textBlock: Anthropic.TextBlockParam = { type: "text", text } - const imageBlocks: Anthropic.ImageBlockParam[] = formatImagesIntoBlocks(images) - // Placing images after text leads to better results - return [textBlock, ...imageBlocks] - } else { - return text - } - }, - - imageBlocks: (images?: string[]): Anthropic.ImageBlockParam[] => { - return formatImagesIntoBlocks(images) - }, - - formatFilesList: (absolutePath: string, files: string[], didHitLimit: boolean): string => { - const sorted = files - .map((file) => { - // convert absolute path to relative path - const relativePath = path.relative(absolutePath, file).toPosix() - return file.endsWith("/") ? relativePath + "/" : relativePath - }) - // Sort so files are listed under their respective directories to make it clear what files are children of what directories. Since we build file list top down, even if file list is truncated it will show directories that cline can then explore further. - .sort((a, b) => { - const aParts = a.split("/") // only works if we use toPosix first - const bParts = b.split("/") - for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) { - if (aParts[i] !== bParts[i]) { - // If one is a directory and the other isn't at this level, sort the directory first - if (i + 1 === aParts.length && i + 1 < bParts.length) { - return -1 - } - if (i + 1 === bParts.length && i + 1 < aParts.length) { - return 1 - } - // Otherwise, sort alphabetically - return aParts[i].localeCompare(bParts[i], undefined, { numeric: true, sensitivity: "base" }) - } - } - // If all parts are the same up to the length of the shorter path, - // the shorter one comes first - return aParts.length - bParts.length - }) - if (didHitLimit) { - return `${sorted.join( - "\n", - )}\n\n(File list truncated. Use list_files on specific subdirectories if you need to explore further.)` - } else if (sorted.length === 0 || (sorted.length === 1 && sorted[0] === "")) { - return "No files found." - } else { - return sorted.join("\n") - } - }, - - createPrettyPatch: (filename = "file", oldStr?: string, newStr?: string) => { - // strings cannot be undefined or diff throws exception - const patch = diff.createPatch(filename.toPosix(), oldStr || "", newStr || "") - const lines = patch.split("\n") - const prettyPatchLines = lines.slice(4) - return prettyPatchLines.join("\n") - }, -} - -// to avoid circular dependency -const formatImagesIntoBlocks = (images?: string[]): Anthropic.ImageBlockParam[] => { - return images - ? images.map((dataUrl) => { - // data:image/png;base64,base64string - const [rest, base64] = dataUrl.split(",") - const mimeType = rest.split(":")[1].split(";")[0] - return { - type: "image", - source: { type: "base64", media_type: mimeType, data: base64 }, - } as Anthropic.ImageBlockParam - }) - : [] -} - -const toolUseInstructionsReminder = `# Reminder: Instructions for Tool Use - -Tool uses are formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: - - -value1 -value2 -... - - -For example: - - - -I have completed the task... - - - -Always adhere to this format for all tool uses to ensure proper parsing and execution.` diff --git a/src/core/prompts/tools/browser-action.ts b/src/core/prompts/tools/browser-action.ts deleted file mode 100644 index bd36be5..0000000 --- a/src/core/prompts/tools/browser-action.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ToolArgs } from "./types" - -export function getBrowserActionDescription(args: ToolArgs): string | undefined { - if (!args.supportsComputerUse) { - return undefined - } - return `## browser_action -Description: Request to interact with a Puppeteer-controlled browser. Use this tool for research, information gathering, citation verification, or content reference when writing. Every action, except \`close\`, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action. -- The sequence of actions **must always start with** launching the browser at a URL, and **must always end with** closing the browser. If you need to visit a new URL that is not possible to navigate to from the current webpage, you must first close the browser, then launch again at the new URL. -- While the browser is active, only the \`browser_action\` tool can be used. No other tools should be called during this time. You may proceed to use other tools only after closing the browser. For example if you need to save research findings to a document, you must close the browser, then use other tools to write the information to files. -- The browser window has a resolution of **${args.browserViewportSize}** pixels. When performing any click actions, ensure the coordinates are within this resolution range. -- Before clicking on any elements such as icons, links, or buttons, you must consult the provided screenshot of the page to determine the coordinates of the element. The click should be targeted at the **center of the element**, not on its edges. -Parameters: -- action: (required) The action to perform. The available actions are: - * launch: Launch a new Puppeteer-controlled browser instance at the specified URL. This **must always be the first action**. - - Use with the \`url\` parameter to provide the URL. - - Ensure the URL is valid and includes the appropriate protocol (e.g. https://en.wikipedia.org/wiki/Writing, https://scholar.google.com, etc.) - * click: Click at a specific x,y coordinate. - - Use with the \`coordinate\` parameter to specify the location. - - Always click in the center of an element (icon, button, link, etc.) based on coordinates derived from a screenshot. - * type: Type a string of text on the keyboard. You might use this after clicking on a text field to input text. - - Use with the \`text\` parameter to provide the string to type. - * scroll_down: Scroll down the page by one page height. - * scroll_up: Scroll up the page by one page height. - * close: Close the Puppeteer-controlled browser instance. This **must always be the final browser action**. - - Example: \`close\` -- url: (optional) Use this for providing the URL for the \`launch\` action. - * Example: https://en.wikipedia.org/wiki/Writing -- coordinate: (optional) The X and Y coordinates for the \`click\` action. Coordinates should be within the **${args.browserViewportSize}** resolution. - * Example: 450,300 -- text: (optional) Use this for providing the text for the \`type\` action. - * Example: academic writing research -Usage: - -Action to perform (e.g., launch, click, type, scroll_down, scroll_up, close) -URL to launch the browser at (optional) -x,y coordinates (optional) -Text to type (optional) - - -Example: Requesting to launch a browser at a research resource - -launch -https://scholar.google.com - - -Example: Requesting to type a search query - -type -academic writing styles comparison -` -} diff --git a/src/core/prompts/tools/execute-command.ts b/src/core/prompts/tools/execute-command.ts deleted file mode 100644 index d9894d2..0000000 --- a/src/core/prompts/tools/execute-command.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ToolArgs } from "./types" - -export function getExecuteCommandDescription(args: ToolArgs): string | undefined { - return `## execute_command -Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory: ${args.cwd} -Parameters: -- command: (required) The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions. -Usage: - -Your command here - - -Example: Requesting to convert a markdown file to PDF using pandoc - -pandoc document.md -o document.pdf -` -} diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index 6bcde81..7ab865d 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -5,8 +5,6 @@ import { McpHub } from "../../mcp/McpHub" import { getAccessMcpResourceDescription } from "./access-mcp-resource" import { getAskFollowupQuestionDescription } from "./ask-followup-question" import { getAttemptCompletionDescription } from "./attempt-completion" -import { getBrowserActionDescription } from "./browser-action" -import { getExecuteCommandDescription } from "./execute-command" import { getFetchUrlsContentDescription } from "./fetch-url-content" import { getInsertContentDescription } from "./insert-content" import { getListFilesDescription } from "./list-files" @@ -22,7 +20,6 @@ import { getWriteToFileDescription } from "./write-to-file" // Map of tool names to their description functions const toolDescriptionMap: Record string | undefined> = { - execute_command: (args) => getExecuteCommandDescription(args), read_file: (args) => getReadFileDescription(args), write_to_file: (args) => getWriteToFileDescription(args), search_files: (args) => getSearchFilesDescription(args), @@ -41,7 +38,7 @@ const toolDescriptionMap: Record string | undefined> export function getToolDescriptionsForMode( mode: Mode, cwd: string, - searchTool: string, + searchTool: string, supportsComputerUse: boolean, diffStrategy?: DiffStrategy, browserViewportSize?: string, @@ -95,8 +92,6 @@ export function getToolDescriptionsForMode( // Export individual description functions for backward compatibility export { - getAccessMcpResourceDescription, getAskFollowupQuestionDescription, - getAttemptCompletionDescription, getBrowserActionDescription, getExecuteCommandDescription, getInsertContentDescription, - getListFilesDescription, getReadFileDescription, getSearchAndReplaceDescription, getSearchFilesDescription, getSwitchModeDescription, getUseMcpToolDescription, getWriteToFileDescription + getAccessMcpResourceDescription, getAskFollowupQuestionDescription, getAttemptCompletionDescription, getInsertContentDescription, getListFilesDescription, getReadFileDescription, getSearchAndReplaceDescription, getSearchFilesDescription, getSwitchModeDescription, getUseMcpToolDescription, getWriteToFileDescription } diff --git a/src/database/database-manager.ts b/src/database/database-manager.ts index c6ae5b8..5a57be0 100644 --- a/src/database/database-manager.ts +++ b/src/database/database-manager.ts @@ -65,34 +65,33 @@ export class DBManager { // }) // } - private async loadExistingDatabase() { - try { - const databaseFileExists = await this.app.vault.adapter.exists( - this.dbPath, - ) - if (!databaseFileExists) { - return null - } - const fileBuffer = await this.app.vault.adapter.readBinary(this.dbPath) - const fileBlob = new Blob([fileBuffer], { type: 'application/x-gzip' }) - const { fsBundle, wasmModule, vectorExtensionBundlePath } = - await this.loadPGliteResources() - this.db = await PGlite.create({ - loadDataDir: fileBlob, - fsBundle: fsBundle, - wasmModule: wasmModule, - extensions: { - vector: vectorExtensionBundlePath, - live - }, - }) - // return drizzle(this.pgClient) - } catch (error) { - console.error('Error loading database:', error) - console.log(this.dbPath) - return null - } - } + // private async loadExistingDatabase() { + // try { + // const databaseFileExists = await this.app.vault.adapter.exists( + // this.dbPath, + // ) + // if (!databaseFileExists) { + // return null + // } + // const fileBuffer = await this.app.vault.adapter.readBinary(this.dbPath) + // const fileBlob = new Blob([fileBuffer], { type: 'application/x-gzip' }) + // const { fsBundle, wasmModule, vectorExtensionBundlePath } = + // await this.loadPGliteResources() + // this.db = await PGlite.create({ + // loadDataDir: fileBlob, + // fsBundle: fsBundle, + // wasmModule: wasmModule, + // extensions: { + // vector: vectorExtensionBundlePath, + // live + // }, + // }) + // // return drizzle(this.pgClient) + // } catch (error) { + // console.error('Error loading database:', error) + // return null + // } + // } // private async migrateDatabase(): Promise { // if (!this.db) { @@ -115,7 +114,7 @@ export class DBManager { // } async save(): Promise { - console.log("need remove") + console.warn("need remove") } async cleanup() { diff --git a/src/database/modules/vector/vector-repository.ts b/src/database/modules/vector/vector-repository.ts index 301562f..c76b499 100644 --- a/src/database/modules/vector/vector-repository.ts +++ b/src/database/modules/vector/vector-repository.ts @@ -162,10 +162,6 @@ export class VectorRepository { } } - const queryVectorLength = `SELECT count(1) FROM "${tableName}"`; - const queryVectorLengthResult = await this.db.query(queryVectorLength) - console.log('queryVectorLengthResult, ', queryVectorLengthResult) - const query = ` SELECT id, path, mtime, content, metadata, diff --git a/src/utils/parse-infio-block.ts b/src/utils/parse-infio-block.ts index 9530f1a..ce612e6 100644 --- a/src/utils/parse-infio-block.ts +++ b/src/utils/parse-infio-block.ts @@ -550,7 +550,7 @@ export function parseMsgBlocks( urls = parsedUrls } } catch (error) { - console.error('Failed to parse URLs JSON', error) + // console.error('Failed to parse URLs JSON', error) } } } diff --git a/src/utils/web-search.ts b/src/utils/web-search.ts index 112debd..4a16577 100644 --- a/src/utils/web-search.ts +++ b/src/utils/web-search.ts @@ -32,7 +32,7 @@ function cosineSimilarity(vecA: number[], vecB: number[]): number { async function serperSearch(query: string, serperApiKey: string, serperSearchEngine: string): Promise { return new Promise((resolve, reject) => { const url = `${SERPER_BASE_URL}?q=${encodeURIComponent(query)}&engine=${serperSearchEngine}&api_key=${serperApiKey}&num=20`; - + console.log("serper search url: ", url) https.get(url, (res: any) => { let data = ''; @@ -67,6 +67,7 @@ async function serperSearch(query: string, serperApiKey: string, serperSearchEng } }); }).on('error', (error: Error) => { + console.error("serper search error: ", error) reject(error); }); }); @@ -195,8 +196,8 @@ export async function fetchUrlContent(url: string, apiKey: string): Promise { try { diff --git a/styles.css b/styles.css index 046dbf8..5c6234e 100644 --- a/styles.css +++ b/styles.css @@ -1835,3 +1835,39 @@ button.infio-chat-input-model-select { height: 100%; width: 100%; } + +.infio-chat-code-block-url-list { + list-style: none; + padding: 0; + margin: 0; +} + +.infio-chat-code-block-url-list li { + padding: 8px 16px; + border-bottom: 1px solid var(--background-modifier-border); +} + +.infio-chat-code-block-url-list li:last-child { + border-bottom: none; +} + +.infio-chat-code-block-url-link { + color: var(--text-accent); + text-decoration: none; + word-break: break-all; +} + +.infio-chat-code-block-url-link:hover { + text-decoration: underline; +} + +.infio-chat-code-block-status-button { + color: #008000; + background: none; + border: none; + padding: 4px 8px; + cursor: default; + display: flex; + align-items: center; + gap: 4px; +}