From 9ba56ff2015afddc342bf483e0d1b7efa413df09 Mon Sep 17 00:00:00 2001 From: uinstinct <61635505+uinstinct@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:41:57 +0530 Subject: [PATCH 1/3] feat(cli): add grep/find/findstr as alternative search strategy --- extensions/cli/src/tools/searchCode.ts | 106 +++++++++++++++++++++---- 1 file changed, 92 insertions(+), 14 deletions(-) diff --git a/extensions/cli/src/tools/searchCode.ts b/extensions/cli/src/tools/searchCode.ts index 76b500ce652..049a0884f49 100644 --- a/extensions/cli/src/tools/searchCode.ts +++ b/extensions/cli/src/tools/searchCode.ts @@ -3,11 +3,83 @@ import * as fs from "fs"; import * as util from "util"; import { ContinueError, ContinueErrorReason } from "core/util/errors.js"; +import { findUp } from "find-up"; import { Tool } from "./types.js"; const execPromise = util.promisify(child_process.exec); +async function getGitignorePatterns() { + const gitIgnorePath = await findUp(".gitignore"); + if (!gitIgnorePath) return []; + const content = fs.readFileSync(gitIgnorePath, "utf-8"); + const ignorePatterns = []; + for (let line of content.trim().split("\n")) { + line = line.trim(); + if (line.startsWith("#") || line === "") continue; // ignore comments and empty line + if (line.startsWith("!")) continue; // ignore negated ignores + ignorePatterns.push(line); + } + return ignorePatterns; +} + +// procedure 1: search with ripgrep +async function checkIfRipgrepIsInstalled(): Promise { + try { + await execPromise("rg --version"); + return true; + } catch { + return false; + } +} + +async function searchWithRipgrep( + pattern: string, + searchPath: string, + filePattern?: string, +) { + let command = `rg --line-number --with-filename --color never "${pattern}"`; + + if (filePattern) { + command += ` -g "${filePattern}"`; + } + + const ignorePatterns = await getGitignorePatterns(); + for (const ignorePattern of ignorePatterns) { + command += ` -g "!${ignorePattern}"`; + } + + command += ` "${searchPath}"`; + const { stdout, stderr } = await execPromise(command); + return { stdout, stderr }; +} + +// procedure 2: search with grep on unix or findstr on windows +async function searchWithGrepOrFindstr( + pattern: string, + searchPath: string, + filePattern?: string, +) { + const isWindows = process.platform === "win32"; + const ignorePatterns = await getGitignorePatterns(); + let command: string; + if (isWindows) { + const fileSpec = filePattern ? filePattern : "*"; + command = `findstr /S /N /P /R "${pattern}" "${fileSpec}"`; // findstr does not support ignoring patterns + } else { + let excludeArgs = ""; + for (const ignorePattern of ignorePatterns) { + excludeArgs += ` --exclude="${ignorePattern}" --exclude-dir="${ignorePattern}"`; // use both exclude and exclude-dir because ignorePattern can be a file or directory + } + if (filePattern) { + command = `find . -type f -name "${filePattern}" -print0 | xargs -0 grep -nH -I${excludeArgs} "${pattern}"`; + } else { + command = `grep -R -n -H -I${excludeArgs} "${pattern}" .`; + } + } + return await execPromise(command, { cwd: searchPath }); +} + // Default maximum number of results to display const DEFAULT_MAX_RESULTS = 100; @@ -63,15 +135,26 @@ export const searchCodeTool: Tool = { ); } - let command = `rg --line-number --with-filename --color never "${args.pattern}"`; - - if (args.file_pattern) { - command += ` -g "${args.file_pattern}"`; - } - - command += ` "${searchPath}"`; + let stdout = "", + stderr = ""; try { - const { stdout, stderr } = await execPromise(command); + if (await checkIfRipgrepIsInstalled()) { + const results = await searchWithRipgrep( + args.pattern, + searchPath, + args.file_pattern, + ); + stdout = results.stdout; + stderr = results.stderr; + } else { + const results = await searchWithGrepOrFindstr( + args.pattern, + searchPath, + args.file_pattern, + ); + stdout = results.stdout; + stderr = results.stderr; + } if (stderr) { return `Warning during search: ${stderr}\n\n${stdout}`; @@ -105,13 +188,8 @@ export const searchCodeTool: Tool = { args.file_pattern ? ` in files matching "${args.file_pattern}"` : "" }.`; } - if (error instanceof Error) { - if (error.message.includes("command not found")) { - throw new Error(`ripgrep is not installed.`); - } - } throw new Error( - `Error executing ripgrep: ${ + `Error executing search: ${ error instanceof Error ? error.message : String(error) }`, ); From 6434ddefda1bc320d477a58777a0a645fcb6fa8c Mon Sep 17 00:00:00 2001 From: uinstinct <61635505+uinstinct@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:38:37 +0530 Subject: [PATCH 2/3] use -path instead of -name --- extensions/cli/src/tools/searchCode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/cli/src/tools/searchCode.ts b/extensions/cli/src/tools/searchCode.ts index 3bba7dd16af..bbb4c07f975 100644 --- a/extensions/cli/src/tools/searchCode.ts +++ b/extensions/cli/src/tools/searchCode.ts @@ -72,7 +72,7 @@ async function searchWithGrepOrFindstr( excludeArgs += ` --exclude="${ignorePattern}" --exclude-dir="${ignorePattern}"`; // use both exclude and exclude-dir because ignorePattern can be a file or directory } if (filePattern) { - command = `find . -type f -name "${filePattern}" -print0 | xargs -0 grep -nH -I${excludeArgs} "${pattern}"`; + command = `find . -type f -path "${filePattern}" -print0 | xargs -0 grep -nH -I${excludeArgs} "${pattern}"`; } else { command = `grep -R -n -H -I${excludeArgs} "${pattern}" .`; } From a479d6f03e3e86d36d9481328d49ef7b91afc4a7 Mon Sep 17 00:00:00 2001 From: uinstinct <61635505+uinstinct@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:40:32 +0530 Subject: [PATCH 3/3] add dependent packages --- extensions/cli/package-lock.json | 137 +++++++++++++++++++------ extensions/cli/package.json | 3 +- extensions/cli/src/tools/searchCode.ts | 9 -- 3 files changed, 107 insertions(+), 42 deletions(-) diff --git a/extensions/cli/package-lock.json b/extensions/cli/package-lock.json index f5127df9bd6..69b92bac8e1 100644 --- a/extensions/cli/package-lock.json +++ b/extensions/cli/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@sentry/profiling-node": "^9.43.0", "fdir": "^6.4.2", + "find-up": "^8.0.0", "fzf": "^0.5.2", "js-yaml": "^4.1.1" }, @@ -275,7 +276,7 @@ "@aws-sdk/client-bedrock-runtime": "^3.842.0", "@aws-sdk/credential-providers": "^3.913.0", "@continuedev/config-types": "^1.0.14", - "@continuedev/config-yaml": "^1.29.0", + "@continuedev/config-yaml": "^1.33.0", "@continuedev/fetch": "^1.5.0", "dotenv": "^16.5.0", "google-auth-library": "^10.4.1", @@ -9140,6 +9141,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -9150,6 +9168,22 @@ "node": ">= 4" } }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -9163,6 +9197,51 @@ "node": "*" } }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -9590,17 +9669,16 @@ } }, "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-8.0.0.tgz", + "integrity": "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==", "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "locate-path": "^8.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11773,16 +11851,15 @@ } }, "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-8.0.0.tgz", + "integrity": "sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==", "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -15467,32 +15544,30 @@ } }, "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "yocto-queue": "^1.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -18573,7 +18648,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -19544,13 +19618,12 @@ } }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/extensions/cli/package.json b/extensions/cli/package.json index e717177975b..5debd98e555 100644 --- a/extensions/cli/package.json +++ b/extensions/cli/package.json @@ -46,6 +46,7 @@ "dependencies": { "@sentry/profiling-node": "^9.43.0", "fdir": "^6.4.2", + "find-up": "^8.0.0", "fzf": "^0.5.2", "js-yaml": "^4.1.1" }, @@ -87,6 +88,7 @@ "@vitest/ui": "^3.2.4", "@workos-inc/node": "^7.45.0", "chalk": "^5.4.1", + "clipboardy": "^4.0.0", "commander": "^14.0.0", "conventional-changelog-conventionalcommits": "^9.1.0", "core": "file:../../core", @@ -100,7 +102,6 @@ "eslint-plugin-import": "^2.32.0", "eslint-plugin-unused-imports": "^4.1.4", "execa": "^9.6.0", - "clipboardy": "^4.0.0", "express": "^5.1.0", "glob": "^11.0.3", "gpt-tokenizer": "^3.0.1", diff --git a/extensions/cli/src/tools/searchCode.ts b/extensions/cli/src/tools/searchCode.ts index bbb4c07f975..8f2cfb2e96b 100644 --- a/extensions/cli/src/tools/searchCode.ts +++ b/extensions/cli/src/tools/searchCode.ts @@ -84,15 +84,6 @@ async function searchWithGrepOrFindstr( const DEFAULT_MAX_RESULTS = 100; const MAX_LINE_LENGTH = 1000; -export async function checkIfRipgrepIsInstalled(): Promise { - try { - await execPromise("rg --version"); - return true; - } catch { - return false; - } -} - export const searchCodeTool: Tool = { name: "Search", displayName: "Search",