@travisennis/acai 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -729
- package/bin/acai +52 -0
- package/dist/agent/index.d.ts +12 -2
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +378 -168
- package/dist/agent/sub-agent.d.ts +23 -0
- package/dist/agent/sub-agent.d.ts.map +1 -0
- package/dist/agent/sub-agent.js +109 -0
- package/dist/cli/index.d.ts +26 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/{cli.js → cli/index.js} +84 -76
- package/dist/cli/stdin.d.ts +9 -0
- package/dist/cli/stdin.d.ts.map +1 -0
- package/dist/cli/stdin.js +37 -0
- package/dist/commands/copy/index.js +2 -2
- package/dist/commands/copy/utils.d.ts.map +1 -1
- package/dist/commands/copy/utils.js +15 -13
- package/dist/commands/generate-rules/index.d.ts +1 -1
- package/dist/commands/generate-rules/index.d.ts.map +1 -1
- package/dist/commands/generate-rules/index.js +16 -100
- package/dist/commands/generate-rules/service.d.ts +21 -0
- package/dist/commands/generate-rules/service.d.ts.map +1 -0
- package/dist/commands/generate-rules/service.js +103 -0
- package/dist/commands/handoff/index.js +2 -2
- package/dist/commands/health/index.js +1 -1
- package/dist/commands/health/utils.d.ts.map +1 -1
- package/dist/commands/health/utils.js +6 -0
- package/dist/commands/history/index.d.ts +1 -1
- package/dist/commands/history/index.d.ts.map +1 -1
- package/dist/commands/history/index.js +17 -18
- package/dist/commands/history/types.d.ts +38 -0
- package/dist/commands/history/types.d.ts.map +1 -1
- package/dist/commands/history/utils.d.ts.map +1 -1
- package/dist/commands/history/utils.js +63 -58
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +3 -8
- package/dist/commands/init-project/index.d.ts.map +1 -1
- package/dist/commands/init-project/index.js +3 -3
- package/dist/commands/init-project/utils.d.ts.map +1 -1
- package/dist/commands/init-project/utils.js +10 -2
- package/dist/commands/list-tools/index.d.ts.map +1 -1
- package/dist/commands/list-tools/index.js +7 -31
- package/dist/commands/manager.d.ts +2 -2
- package/dist/commands/manager.d.ts.map +1 -1
- package/dist/commands/manager.js +57 -33
- package/dist/commands/model/index.d.ts.map +1 -1
- package/dist/commands/model/index.js +20 -151
- package/dist/commands/model/model-panel.d.ts +4 -0
- package/dist/commands/model/model-panel.d.ts.map +1 -0
- package/dist/commands/model/model-panel.js +144 -0
- package/dist/commands/paste/index.d.ts.map +1 -1
- package/dist/commands/paste/index.js +59 -62
- package/dist/commands/paste/utils.d.ts.map +1 -1
- package/dist/commands/paste/utils.js +88 -58
- package/dist/commands/pickup/index.d.ts.map +1 -1
- package/dist/commands/pickup/index.js +6 -3
- package/dist/commands/pickup/utils.js +3 -3
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +33 -50
- package/dist/commands/review/index.d.ts.map +1 -1
- package/dist/commands/review/index.js +3 -117
- package/dist/commands/review/review-panel.d.ts +3 -0
- package/dist/commands/review/review-panel.d.ts.map +1 -0
- package/dist/commands/review/review-panel.js +186 -0
- package/dist/commands/review/utils.d.ts +9 -0
- package/dist/commands/review/utils.d.ts.map +1 -1
- package/dist/commands/review/utils.js +127 -68
- package/dist/commands/session/index.d.ts +1 -1
- package/dist/commands/session/index.d.ts.map +1 -1
- package/dist/commands/session/index.js +134 -112
- package/dist/commands/session/types.d.ts +7 -0
- package/dist/commands/session/types.d.ts.map +1 -1
- package/dist/commands/share/html-renderer.d.ts +25 -0
- package/dist/commands/share/html-renderer.d.ts.map +1 -0
- package/dist/commands/share/html-renderer.js +384 -0
- package/dist/commands/share/index.d.ts +3 -0
- package/dist/commands/share/index.d.ts.map +1 -0
- package/dist/commands/share/index.js +122 -0
- package/dist/commands/shell/index.d.ts.map +1 -1
- package/dist/commands/shell/index.js +16 -1
- package/dist/commands/types.d.ts +2 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/{config.d.ts → config/index.d.ts} +20 -9
- package/dist/config/index.d.ts.map +1 -0
- package/dist/{config.js → config/index.js} +43 -42
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +75 -55
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +170 -127
- package/dist/middleware/cache.d.ts.map +1 -1
- package/dist/middleware/cache.js +18 -36
- package/dist/models/ai-config.d.ts +1 -0
- package/dist/models/ai-config.d.ts.map +1 -1
- package/dist/models/ai-config.js +4 -3
- package/dist/models/anthropic-provider.d.ts +2 -5
- package/dist/models/anthropic-provider.d.ts.map +1 -1
- package/dist/models/anthropic-provider.js +3 -70
- package/dist/models/deepseek-provider.d.ts +1 -0
- package/dist/models/deepseek-provider.d.ts.map +1 -1
- package/dist/models/google-provider.d.ts +2 -3
- package/dist/models/google-provider.d.ts.map +1 -1
- package/dist/models/google-provider.js +0 -26
- package/dist/models/groq-provider.d.ts +1 -0
- package/dist/models/groq-provider.d.ts.map +1 -1
- package/dist/models/manager.d.ts +13 -2
- package/dist/models/manager.d.ts.map +1 -1
- package/dist/models/manager.js +20 -8
- package/dist/models/openai-provider.d.ts +5 -5
- package/dist/models/openai-provider.d.ts.map +1 -1
- package/dist/models/openai-provider.js +27 -40
- package/dist/models/opencode-zen-provider.d.ts +8 -3
- package/dist/models/opencode-zen-provider.d.ts.map +1 -1
- package/dist/models/opencode-zen-provider.js +68 -11
- package/dist/models/openrouter-provider.d.ts +24 -30
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +92 -177
- package/dist/models/providers.d.ts +1 -1
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/xai-provider.d.ts +4 -3
- package/dist/models/xai-provider.d.ts.map +1 -1
- package/dist/models/xai-provider.js +18 -18
- package/dist/modes/manager.d.ts +23 -0
- package/dist/modes/manager.d.ts.map +1 -0
- package/dist/modes/manager.js +77 -0
- package/dist/modes/prompts.d.ts +2 -0
- package/dist/modes/prompts.d.ts.map +1 -0
- package/dist/modes/prompts.js +143 -0
- package/dist/prompts/mentions.d.ts +11 -0
- package/dist/prompts/mentions.d.ts.map +1 -0
- package/dist/{mentions.js → prompts/mentions.js} +21 -80
- package/dist/prompts/system-prompt.d.ts +26 -0
- package/dist/prompts/system-prompt.d.ts.map +1 -0
- package/dist/{prompts.js → prompts/system-prompt.js} +50 -22
- package/dist/repl/index.d.ts +174 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/{repl-new.js → repl/index.js} +399 -76
- package/dist/repl/project-status.d.ts +1 -0
- package/dist/repl/project-status.d.ts.map +1 -1
- package/dist/repl/project-status.js +4 -1
- package/dist/sessions/manager.d.ts +93 -1
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +264 -9
- package/dist/sessions/summary.d.ts +4 -0
- package/dist/sessions/summary.d.ts.map +1 -0
- package/dist/sessions/summary.js +30 -0
- package/dist/{skills.d.ts → skills/index.d.ts} +14 -2
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +294 -0
- package/dist/subagents/index.d.ts +15 -0
- package/dist/subagents/index.d.ts.map +1 -0
- package/dist/subagents/index.js +231 -0
- package/dist/terminal/control.d.ts +1 -1
- package/dist/terminal/control.d.ts.map +1 -1
- package/dist/terminal/control.js +30 -9
- package/dist/terminal/east-asian-width.d.ts.map +1 -1
- package/dist/terminal/east-asian-width.js +404 -351
- package/dist/terminal/keys.d.ts +17 -0
- package/dist/terminal/keys.d.ts.map +1 -1
- package/dist/terminal/keys.js +37 -0
- package/dist/terminal/select-prompt.d.ts.map +1 -1
- package/dist/terminal/select-prompt.js +24 -12
- package/dist/terminal/string-width.d.ts.map +1 -1
- package/dist/terminal/string-width.js +25 -27
- package/dist/terminal/style.d.ts.map +1 -1
- package/dist/terminal/style.js +4 -7
- package/dist/terminal/supports-color.d.ts.map +1 -1
- package/dist/terminal/supports-color.js +41 -27
- package/dist/terminal/table/cell.d.ts +12 -0
- package/dist/terminal/table/cell.d.ts.map +1 -1
- package/dist/terminal/table/cell.js +40 -25
- package/dist/terminal/table/layout-manager.d.ts.map +1 -1
- package/dist/terminal/table/layout-manager.js +100 -68
- package/dist/terminal/table/utils.d.ts.map +1 -1
- package/dist/terminal/table/utils.js +17 -10
- package/dist/terminal/wrap-ansi.d.ts.map +1 -1
- package/dist/terminal/wrap-ansi.js +172 -103
- package/dist/tokens/tracker.d.ts +1 -0
- package/dist/tokens/tracker.d.ts.map +1 -1
- package/dist/tokens/tracker.js +3 -0
- package/dist/tools/agent.d.ts +27 -0
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/agent.js +81 -0
- package/dist/tools/bash.d.ts +4 -3
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +343 -121
- package/dist/tools/code-search.d.ts +41 -0
- package/dist/tools/code-search.d.ts.map +1 -0
- package/dist/tools/code-search.js +195 -0
- package/dist/tools/directory-tree.d.ts +3 -3
- package/dist/tools/directory-tree.d.ts.map +1 -1
- package/dist/tools/directory-tree.js +8 -5
- package/dist/tools/dynamic-tool-loader.d.ts +2 -5
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
- package/dist/tools/dynamic-tool-loader.js +20 -4
- package/dist/tools/edit-file.d.ts +7 -7
- package/dist/tools/edit-file.d.ts.map +1 -1
- package/dist/tools/edit-file.js +164 -66
- package/dist/tools/glob.d.ts +6 -6
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +95 -55
- package/dist/tools/grep.d.ts +15 -12
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +300 -192
- package/dist/tools/index.d.ts +143 -5
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +39 -24
- package/dist/tools/ls.d.ts +2 -2
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +7 -5
- package/dist/tools/read-file.d.ts +3 -3
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +74 -34
- package/dist/tools/save-file.d.ts +3 -3
- package/dist/tools/save-file.d.ts.map +1 -1
- package/dist/tools/save-file.js +11 -11
- package/dist/tools/skill.d.ts +23 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +65 -0
- package/dist/tools/think.d.ts.map +1 -1
- package/dist/tools/think.js +2 -9
- package/dist/tools/utils.d.ts +2 -0
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +12 -0
- package/dist/tools/web-fetch.d.ts +62 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +429 -0
- package/dist/tools/web-search.d.ts +62 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +226 -0
- package/dist/tui/autocomplete/attachment-provider.d.ts +3 -6
- package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/attachment-provider.js +25 -78
- package/dist/tui/autocomplete/base-provider.d.ts +1 -0
- package/dist/tui/autocomplete/base-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/combined-provider.d.ts +1 -4
- package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/combined-provider.js +3 -17
- package/dist/tui/autocomplete/command-provider.d.ts +1 -0
- package/dist/tui/autocomplete/command-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/command-provider.js +3 -0
- package/dist/tui/autocomplete/file-search-provider.d.ts +2 -1
- package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/file-search-provider.js +36 -16
- package/dist/tui/autocomplete/skill-provider.d.ts +17 -0
- package/dist/tui/autocomplete/skill-provider.d.ts.map +1 -0
- package/dist/tui/autocomplete/skill-provider.js +49 -0
- package/dist/tui/autocomplete.d.ts +2 -2
- package/dist/tui/autocomplete.d.ts.map +1 -1
- package/dist/tui/autocomplete.js +3 -5
- package/dist/tui/components/assistant-message.d.ts.map +1 -1
- package/dist/tui/components/assistant-message.js +0 -4
- package/dist/tui/components/editor.d.ts +21 -2
- package/dist/tui/components/editor.d.ts.map +1 -1
- package/dist/tui/components/editor.js +228 -236
- package/dist/tui/components/footer.d.ts +6 -4
- package/dist/tui/components/footer.d.ts.map +1 -1
- package/dist/tui/components/footer.js +49 -25
- package/dist/tui/components/markdown.d.ts +8 -5
- package/dist/tui/components/markdown.d.ts.map +1 -1
- package/dist/tui/components/markdown.js +57 -39
- package/dist/tui/components/modal.d.ts.map +1 -1
- package/dist/tui/components/modal.js +35 -33
- package/dist/tui/components/notification.d.ts +13 -2
- package/dist/tui/components/notification.d.ts.map +1 -1
- package/dist/tui/components/notification.js +37 -2
- package/dist/tui/components/progress-bar.js +1 -1
- package/dist/tui/components/select-list.d.ts +1 -0
- package/dist/tui/components/select-list.d.ts.map +1 -1
- package/dist/tui/components/select-list.js +14 -11
- package/dist/tui/components/text.d.ts +16 -0
- package/dist/tui/components/text.d.ts.map +1 -1
- package/dist/tui/components/text.js +72 -57
- package/dist/tui/components/thinking-block.d.ts +9 -0
- package/dist/tui/components/thinking-block.d.ts.map +1 -1
- package/dist/tui/components/thinking-block.js +43 -11
- package/dist/tui/components/tool-execution.d.ts +5 -1
- package/dist/tui/components/tool-execution.d.ts.map +1 -1
- package/dist/tui/components/tool-execution.js +19 -10
- package/dist/tui/components/user-message.d.ts.map +1 -1
- package/dist/tui/components/user-message.js +0 -3
- package/dist/tui/components/welcome.js +2 -2
- package/dist/tui/editor-launcher.d.ts +13 -0
- package/dist/tui/editor-launcher.d.ts.map +1 -0
- package/dist/tui/editor-launcher.js +39 -0
- package/dist/tui/index.d.ts +3 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +1 -0
- package/dist/tui/terminal.d.ts +27 -0
- package/dist/tui/terminal.d.ts.map +1 -1
- package/dist/tui/terminal.js +144 -15
- package/dist/tui/tui.d.ts +43 -0
- package/dist/tui/tui.d.ts.map +1 -1
- package/dist/tui/tui.js +172 -41
- package/dist/utils/bash/parse.d.ts +19 -0
- package/dist/utils/bash/parse.d.ts.map +1 -0
- package/dist/utils/bash/parse.js +223 -0
- package/dist/utils/bash/quote.d.ts +6 -0
- package/dist/utils/bash/quote.d.ts.map +1 -0
- package/dist/utils/bash/quote.js +23 -0
- package/dist/utils/bash.d.ts.map +1 -1
- package/dist/utils/bash.js +211 -126
- package/dist/utils/command-protection.d.ts +28 -0
- package/dist/utils/command-protection.d.ts.map +1 -0
- package/dist/utils/command-protection.js +324 -0
- package/dist/utils/dedent.d.ts.map +1 -0
- package/dist/utils/env-expand.d.ts +2 -0
- package/dist/utils/env-expand.d.ts.map +1 -0
- package/dist/utils/env-expand.js +8 -0
- package/dist/utils/filesystem/path-display.d.ts +11 -0
- package/dist/utils/filesystem/path-display.d.ts.map +1 -0
- package/dist/utils/filesystem/path-display.js +32 -0
- package/dist/utils/filesystem/security.d.ts +2 -2
- package/dist/utils/filesystem/security.d.ts.map +1 -1
- package/dist/utils/filesystem/security.js +32 -31
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/{formatting.js → utils/formatting.js} +1 -1
- package/dist/utils/git.d.ts +4 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +30 -0
- package/dist/utils/glob.d.ts +1 -1
- package/dist/utils/glob.d.ts.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/{logger.js → utils/logger.js} +1 -1
- package/dist/utils/parsing.d.ts.map +1 -0
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +90 -37
- package/dist/utils/templates.d.ts +2 -0
- package/dist/utils/templates.d.ts.map +1 -0
- package/dist/utils/templates.js +24 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/{version.js → utils/version.js} +1 -1
- package/package.json +34 -25
- package/dist/cli.d.ts +0 -23
- package/dist/cli.d.ts.map +0 -1
- package/dist/commands/exit/index.d.ts +0 -10
- package/dist/commands/exit/index.d.ts.map +0 -1
- package/dist/commands/exit/index.js +0 -21
- package/dist/commands/exit/types.d.ts +0 -8
- package/dist/commands/exit/types.d.ts.map +0 -1
- package/dist/commands/exit/types.js +0 -1
- package/dist/commands/exit/utils.d.ts +0 -2
- package/dist/commands/exit/utils.d.ts.map +0 -1
- package/dist/commands/exit/utils.js +0 -13
- package/dist/commands/prompt/index.d.ts +0 -5
- package/dist/commands/prompt/index.d.ts.map +0 -1
- package/dist/commands/prompt/index.js +0 -126
- package/dist/commands/prompt/types.d.ts +0 -15
- package/dist/commands/prompt/types.d.ts.map +0 -1
- package/dist/commands/prompt/types.js +0 -1
- package/dist/commands/prompt/utils.d.ts +0 -12
- package/dist/commands/prompt/utils.d.ts.map +0 -1
- package/dist/commands/prompt/utils.js +0 -107
- package/dist/commands/reset/index.d.ts +0 -3
- package/dist/commands/reset/index.d.ts.map +0 -1
- package/dist/commands/reset/index.js +0 -25
- package/dist/commands/reset/types.d.ts +0 -1
- package/dist/commands/reset/types.d.ts.map +0 -1
- package/dist/commands/reset/types.js +0 -3
- package/dist/commands/save/index.d.ts +0 -3
- package/dist/commands/save/index.d.ts.map +0 -1
- package/dist/commands/save/index.js +0 -19
- package/dist/config.d.ts.map +0 -1
- package/dist/dedent.d.ts.map +0 -1
- package/dist/formatting.d.ts.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/mentions.d.ts +0 -14
- package/dist/mentions.d.ts.map +0 -1
- package/dist/parsing.d.ts.map +0 -1
- package/dist/prompts.d.ts +0 -10
- package/dist/prompts.d.ts.map +0 -1
- package/dist/repl-new.d.ts +0 -62
- package/dist/repl-new.d.ts.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -233
- package/dist/tui/autocomplete/path-provider.d.ts +0 -21
- package/dist/tui/autocomplete/path-provider.d.ts.map +0 -1
- package/dist/tui/autocomplete/path-provider.js +0 -164
- package/dist/version.d.ts.map +0 -1
- /package/dist/{dedent.d.ts → utils/dedent.d.ts} +0 -0
- /package/dist/{dedent.js → utils/dedent.js} +0 -0
- /package/dist/{formatting.d.ts → utils/formatting.d.ts} +0 -0
- /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
- /package/dist/{parsing.d.ts → utils/parsing.d.ts} +0 -0
- /package/dist/{parsing.js → utils/parsing.js} +0 -0
- /package/dist/{version.d.ts → utils/version.d.ts} +0 -0
package/dist/tools/grep.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile, } from "node:child_process";
|
|
2
2
|
import { inspect } from "node:util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import style from "../terminal/style.js";
|
|
5
|
+
import { toDisplayPath } from "../utils/filesystem/path-display.js";
|
|
5
6
|
import { convertNullString } from "../utils/zod.js";
|
|
6
7
|
// default limit
|
|
7
8
|
const DEFAULT_MAX_RESULTS = 100;
|
|
@@ -38,7 +39,7 @@ const inputSchema = z.object({
|
|
|
38
39
|
export const createGrepTool = () => {
|
|
39
40
|
return {
|
|
40
41
|
toolDef: {
|
|
41
|
-
description:
|
|
42
|
+
description: "Search file contents using ripgrep.",
|
|
42
43
|
inputSchema,
|
|
43
44
|
},
|
|
44
45
|
display({ pattern, path, filePattern, recursive, ignoreCase, contextLines, }) {
|
|
@@ -47,7 +48,8 @@ export const createGrepTool = () => {
|
|
|
47
48
|
? null
|
|
48
49
|
: filePattern;
|
|
49
50
|
// Enhanced tool-init with detailed search parameters
|
|
50
|
-
|
|
51
|
+
const displayPath = toDisplayPath(path);
|
|
52
|
+
let initMessage = `${style.cyan(inspect(pattern))} in ${style.cyan(displayPath)}`;
|
|
51
53
|
if (safeFilePattern) {
|
|
52
54
|
initMessage += ` ${style.dim(`(filter: ${safeFilePattern})`)}`;
|
|
53
55
|
}
|
|
@@ -66,7 +68,11 @@ export const createGrepTool = () => {
|
|
|
66
68
|
if (abortSignal?.aborted) {
|
|
67
69
|
throw new Error("Grep search aborted");
|
|
68
70
|
}
|
|
71
|
+
// Validate path - default to cwd if not provided
|
|
72
|
+
const effectivePath = typeof path === "string" && path.trim() !== "" ? path : process.cwd();
|
|
69
73
|
try {
|
|
74
|
+
// Compute likelyUnbalancedRegex once and pass through
|
|
75
|
+
const isLikelyUnbalanced = likelyUnbalancedRegex(pattern);
|
|
70
76
|
let effectiveLiteral = null;
|
|
71
77
|
if (literal === true) {
|
|
72
78
|
effectiveLiteral = true;
|
|
@@ -75,23 +81,13 @@ export const createGrepTool = () => {
|
|
|
75
81
|
effectiveLiteral = false;
|
|
76
82
|
}
|
|
77
83
|
else {
|
|
78
|
-
|
|
79
|
-
if (likelyUnbalancedRegex(pattern)) {
|
|
80
|
-
effectiveLiteral = true;
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
effectiveLiteral = false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch (_err) {
|
|
87
|
-
effectiveLiteral = false;
|
|
88
|
-
}
|
|
84
|
+
effectiveLiteral = isLikelyUnbalanced;
|
|
89
85
|
}
|
|
90
86
|
const effectiveMaxResults = maxResults ?? DEFAULT_MAX_RESULTS;
|
|
91
87
|
const safeFilePattern = filePattern === "null" || filePattern === "undefined"
|
|
92
88
|
? null
|
|
93
89
|
: filePattern;
|
|
94
|
-
const grepResult = grepFilesStructured(pattern,
|
|
90
|
+
const grepResult = await grepFilesStructured(pattern, effectivePath, {
|
|
95
91
|
recursive,
|
|
96
92
|
ignoreCase,
|
|
97
93
|
filePattern: safeFilePattern,
|
|
@@ -99,21 +95,22 @@ export const createGrepTool = () => {
|
|
|
99
95
|
searchIgnored,
|
|
100
96
|
literal: effectiveLiteral,
|
|
101
97
|
maxResults: effectiveMaxResults,
|
|
102
|
-
|
|
98
|
+
likelyUnbalanced: isLikelyUnbalanced,
|
|
99
|
+
}, abortSignal);
|
|
103
100
|
return grepResult.rawOutput;
|
|
104
101
|
}
|
|
105
102
|
catch (error) {
|
|
106
103
|
const errorMessage = error.message;
|
|
107
|
-
let userFriendlyError = `Error searching for "${pattern}" in ${
|
|
104
|
+
let userFriendlyError = `Error searching for "${pattern}" in ${effectivePath}: ${errorMessage}`;
|
|
108
105
|
if (errorMessage.includes("No such file or directory")) {
|
|
109
|
-
userFriendlyError = `Path not found: "${
|
|
106
|
+
userFriendlyError = `Path not found: "${effectivePath}"`;
|
|
110
107
|
if (filePattern) {
|
|
111
108
|
userFriendlyError += ` with file pattern "${filePattern}"`;
|
|
112
109
|
}
|
|
113
110
|
userFriendlyError += " - check if the path exists and is accessible";
|
|
114
111
|
}
|
|
115
112
|
else if (errorMessage.includes("permission denied")) {
|
|
116
|
-
userFriendlyError = `Permission denied accessing "${
|
|
113
|
+
userFriendlyError = `Permission denied accessing "${effectivePath}"`;
|
|
117
114
|
}
|
|
118
115
|
else if (errorMessage.includes("Regex parse error")) {
|
|
119
116
|
userFriendlyError = `Invalid search pattern "${pattern}" - try using literal=true for fixed-string search`;
|
|
@@ -123,7 +120,92 @@ export const createGrepTool = () => {
|
|
|
123
120
|
},
|
|
124
121
|
};
|
|
125
122
|
};
|
|
126
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Skips past a character class [...], handling escaped characters.
|
|
125
|
+
* Returns the new index after the character class.
|
|
126
|
+
*/
|
|
127
|
+
function skipCharacterClass(pattern, startIndex) {
|
|
128
|
+
let i = startIndex + 1;
|
|
129
|
+
while (i < pattern.length && pattern[i] !== "]") {
|
|
130
|
+
if (pattern[i] === "\\")
|
|
131
|
+
i++;
|
|
132
|
+
i++;
|
|
133
|
+
}
|
|
134
|
+
return i;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Parses and validates the content inside braces {..}.
|
|
138
|
+
* Returns true if the brace content is invalid.
|
|
139
|
+
*/
|
|
140
|
+
function isInvalidBraceContent(pattern, startIndex) {
|
|
141
|
+
let j = startIndex + 1;
|
|
142
|
+
let hasDigits = false;
|
|
143
|
+
let hasComma = false;
|
|
144
|
+
// Parse content inside braces
|
|
145
|
+
while (j < pattern.length && pattern[j] !== "}") {
|
|
146
|
+
const c = pattern[j];
|
|
147
|
+
if (c >= "0" && c <= "9") {
|
|
148
|
+
hasDigits = true;
|
|
149
|
+
}
|
|
150
|
+
else if (c === "," && !hasComma) {
|
|
151
|
+
hasComma = true;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Invalid character inside braces
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
j++;
|
|
158
|
+
}
|
|
159
|
+
// No closing brace found
|
|
160
|
+
if (j >= pattern.length) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
// Empty braces {} with no preceding atom are treated as literal
|
|
164
|
+
if (!hasDigits) {
|
|
165
|
+
const prev = startIndex > 0 ? pattern[startIndex - 1] : undefined;
|
|
166
|
+
if (prev !== undefined && /\S/.test(prev)) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Check for invalid repetition operators (e.g., {n}, {n,}, {n,m}) outside of character classes.
|
|
174
|
+
* Returns true if any invalid repetition operators are found.
|
|
175
|
+
*/
|
|
176
|
+
function hasInvalidRepetition(pattern) {
|
|
177
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
178
|
+
const ch = pattern[i];
|
|
179
|
+
if (ch === "\\") {
|
|
180
|
+
i++; // Skip the next character (escaped)
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (ch === "[") {
|
|
184
|
+
i = skipCharacterClass(pattern, i);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (ch === "}") {
|
|
188
|
+
// Unmatched closing brace is invalid
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
if (ch === "{") {
|
|
192
|
+
if (isInvalidBraceContent(pattern, i)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
// Find closing brace to move past it
|
|
196
|
+
let j = i + 1;
|
|
197
|
+
while (j < pattern.length && pattern[j] !== "}") {
|
|
198
|
+
j++;
|
|
199
|
+
}
|
|
200
|
+
i = j;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Count bracket/paren/brace pairs in a regex pattern, excluding character classes.
|
|
207
|
+
*/
|
|
208
|
+
function countBrackets(pattern) {
|
|
127
209
|
const counts = {
|
|
128
210
|
openParen: 0,
|
|
129
211
|
closeParen: 0,
|
|
@@ -175,91 +257,56 @@ export function likelyUnbalancedRegex(pattern) {
|
|
|
175
257
|
}
|
|
176
258
|
}
|
|
177
259
|
}
|
|
260
|
+
return counts;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Check if a pattern contains regex metacharacters.
|
|
264
|
+
* Returns true if the pattern likely needs regex mode.
|
|
265
|
+
*/
|
|
266
|
+
function containsRegexMetacharacters(pattern) {
|
|
267
|
+
// Regex metacharacters: ^ $ . * + ? [ ] ( ) { } | \
|
|
268
|
+
const metacharacterPattern = /[\^$.*+?[\](){}|\\]/;
|
|
269
|
+
return metacharacterPattern.test(pattern);
|
|
270
|
+
}
|
|
271
|
+
export function likelyUnbalancedRegex(pattern) {
|
|
272
|
+
// First check: unbalanced brackets/parentheses/braces = likely a typo, use literal
|
|
273
|
+
const counts = countBrackets(pattern);
|
|
178
274
|
// Check for unbalanced brackets, parentheses, and braces
|
|
179
275
|
const hasUnbalancedBrackets = counts.openBracket !== counts.closeBracket;
|
|
180
276
|
const hasUnbalancedParens = counts.openParen !== counts.closeParen;
|
|
181
277
|
const hasUnbalancedBraces = counts.openBrace !== counts.closeBrace;
|
|
182
|
-
// Also check for invalid repetition operators
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
let inClass2 = false;
|
|
187
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
188
|
-
const ch = pattern[i];
|
|
189
|
-
if (escaped2) {
|
|
190
|
-
escaped2 = false;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
if (ch === "\\") {
|
|
194
|
-
escaped2 = true;
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
if (ch === "[" && !inClass2) {
|
|
198
|
-
inClass2 = true;
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
if (ch === "]" && inClass2) {
|
|
202
|
-
inClass2 = false;
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (inClass2) {
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
if (ch === "{") {
|
|
209
|
-
let j = i + 1;
|
|
210
|
-
let hasDigits = false;
|
|
211
|
-
let hasComma = false;
|
|
212
|
-
while (j < pattern.length && pattern[j] !== "}") {
|
|
213
|
-
const c = pattern[j];
|
|
214
|
-
if (c >= "0" && c <= "9") {
|
|
215
|
-
hasDigits = true;
|
|
216
|
-
}
|
|
217
|
-
else if (c === "," && !hasComma) {
|
|
218
|
-
hasComma = true;
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
j++;
|
|
224
|
-
}
|
|
225
|
-
if (j >= pattern.length || pattern[j] !== "}") {
|
|
226
|
-
hasInvalidRepetition = true;
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
// At this point we have a closing brace at j
|
|
230
|
-
if (!hasDigits) {
|
|
231
|
-
// Heuristic: treat empty {} as non-quantifier when it doesn't follow a likely atom
|
|
232
|
-
const prev = i > 0 ? pattern[i - 1] : undefined;
|
|
233
|
-
if (prev !== undefined && /\S/.test(prev)) {
|
|
234
|
-
hasInvalidRepetition = true;
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
// else ignore as literal braces
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return (hasUnbalancedBrackets ||
|
|
278
|
+
// Also check for invalid repetition operators
|
|
279
|
+
const hasInvalidRepetitionFlag = hasInvalidRepetition(pattern);
|
|
280
|
+
// If pattern has unbalanced syntax, treat as literal (user probably meant to type literal)
|
|
281
|
+
if (hasUnbalancedBrackets ||
|
|
243
282
|
hasUnbalancedParens ||
|
|
244
283
|
hasUnbalancedBraces ||
|
|
245
|
-
|
|
284
|
+
hasInvalidRepetitionFlag) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
// Second check: if pattern has regex metacharacters, treat as regex (return false)
|
|
288
|
+
// This allows ripgrep to handle the pattern as a proper regex
|
|
289
|
+
if (containsRegexMetacharacters(pattern)) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
// Default: simple alphanumeric strings should use literal mode
|
|
293
|
+
return true;
|
|
246
294
|
}
|
|
247
295
|
/**
|
|
248
|
-
*
|
|
296
|
+
* Build grep command args array directly
|
|
249
297
|
*
|
|
250
298
|
* @param pattern - The regex pattern to search for
|
|
251
299
|
* @param path - The path to search in
|
|
252
300
|
* @param options - Additional options for the grep command
|
|
253
|
-
* @returns The
|
|
301
|
+
* @returns The args array for the grep command
|
|
254
302
|
*/
|
|
255
|
-
export function
|
|
303
|
+
export function buildGrepArgs(pattern, path, options = {}) {
|
|
256
304
|
const effectiveRecursive = options.recursive === null ? true : options.recursive;
|
|
257
305
|
const effectiveIgnoreCase = options.ignoreCase === null ? false : options.ignoreCase;
|
|
258
306
|
const effectiveSearchIgnored = options.searchIgnored === null ? false : options.searchIgnored;
|
|
259
307
|
const effectiveFilePattern = options.filePattern;
|
|
260
308
|
const effectiveContextLines = options.contextLines;
|
|
261
|
-
//
|
|
262
|
-
// If null/undefined, auto-detect unbalanced regexes and prefer fixed-strings.
|
|
309
|
+
// Use the pre-computed likelyUnbalanced result if available
|
|
263
310
|
let effectiveLiteral;
|
|
264
311
|
if (options.literal === true) {
|
|
265
312
|
effectiveLiteral = true;
|
|
@@ -267,27 +314,30 @@ export function buildGrepCommand(pattern, path, options = {}) {
|
|
|
267
314
|
else if (options.literal === false) {
|
|
268
315
|
effectiveLiteral = false;
|
|
269
316
|
}
|
|
317
|
+
else if (options.likelyUnbalanced !== undefined) {
|
|
318
|
+
effectiveLiteral = options.likelyUnbalanced;
|
|
319
|
+
}
|
|
270
320
|
else {
|
|
271
321
|
effectiveLiteral = likelyUnbalancedRegex(pattern);
|
|
272
322
|
}
|
|
273
|
-
|
|
323
|
+
const args = ["--json"];
|
|
274
324
|
if (effectiveRecursive === false) {
|
|
275
|
-
|
|
325
|
+
args.push("--max-depth=0");
|
|
276
326
|
}
|
|
277
327
|
if (effectiveIgnoreCase) {
|
|
278
|
-
|
|
328
|
+
args.push("--ignore-case");
|
|
279
329
|
}
|
|
280
330
|
if (effectiveContextLines !== null && effectiveContextLines !== undefined) {
|
|
281
|
-
|
|
331
|
+
args.push(`--context=${effectiveContextLines}`);
|
|
282
332
|
}
|
|
283
333
|
if (effectiveFilePattern !== null && effectiveFilePattern !== undefined) {
|
|
284
|
-
|
|
334
|
+
args.push(`--glob=${effectiveFilePattern}`);
|
|
285
335
|
}
|
|
286
336
|
if (effectiveSearchIgnored) {
|
|
287
|
-
|
|
337
|
+
args.push("--no-ignore");
|
|
288
338
|
}
|
|
289
339
|
if (effectiveLiteral) {
|
|
290
|
-
|
|
340
|
+
args.push("-F");
|
|
291
341
|
}
|
|
292
342
|
// Use ripgrep's --max-count flag to limit matches per file for efficiency
|
|
293
343
|
// This helps prevent any single file from dominating results
|
|
@@ -296,82 +346,88 @@ export function buildGrepCommand(pattern, path, options = {}) {
|
|
|
296
346
|
options.maxResults > 0) {
|
|
297
347
|
// Use a reasonable per-file limit (max 100 per file) to balance efficiency and completeness
|
|
298
348
|
const perFileLimit = Math.min(options.maxResults, 100);
|
|
299
|
-
|
|
349
|
+
args.push(`--max-count=${perFileLimit}`);
|
|
300
350
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return
|
|
351
|
+
args.push(pattern);
|
|
352
|
+
args.push(path);
|
|
353
|
+
return args;
|
|
304
354
|
}
|
|
305
355
|
/**
|
|
306
|
-
* Parse ripgrep output and extract structured match information
|
|
356
|
+
* Parse ripgrep JSON output and extract structured match information
|
|
307
357
|
*/
|
|
308
|
-
export function
|
|
309
|
-
if (content
|
|
358
|
+
export function parseRipgrepJsonOutput(content) {
|
|
359
|
+
if (!content || content.trim() === "") {
|
|
310
360
|
return [];
|
|
311
361
|
}
|
|
312
|
-
const lines = content.trim().split("\n");
|
|
313
362
|
const parsed = [];
|
|
363
|
+
const lines = content.trim().split("\n");
|
|
314
364
|
for (const line of lines) {
|
|
315
|
-
if (line
|
|
316
|
-
// Separator between file groups - skip
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
if (line.trim() === "") {
|
|
320
|
-
// Empty line - skip
|
|
321
|
-
continue;
|
|
322
|
-
}
|
|
323
|
-
// Try multi-file format: file:line:content
|
|
324
|
-
const multiFileMatch = line.match(/^([^:]+):(\d+):(.+)$/);
|
|
325
|
-
if (multiFileMatch) {
|
|
326
|
-
parsed.push({
|
|
327
|
-
file: multiFileMatch[1],
|
|
328
|
-
line: Number.parseInt(multiFileMatch[2], 10),
|
|
329
|
-
content: multiFileMatch[3],
|
|
330
|
-
isMatch: true,
|
|
331
|
-
});
|
|
365
|
+
if (!line.trim()) {
|
|
332
366
|
continue;
|
|
333
367
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
368
|
+
try {
|
|
369
|
+
const rgResult = JSON.parse(line);
|
|
370
|
+
// Handle different ripgrep message types
|
|
371
|
+
if (rgResult.type === "match") {
|
|
372
|
+
const data = rgResult.data;
|
|
373
|
+
parsed.push({
|
|
374
|
+
file: data.path?.text ?? data.path?.bytes?.toString(),
|
|
375
|
+
line: data.line_number ?? 0,
|
|
376
|
+
content: data.lines?.text ?? data.line ?? "",
|
|
377
|
+
isMatch: true,
|
|
378
|
+
lineNumber: data.line_number,
|
|
379
|
+
absolutePath: data.absolute_path?.text ?? data.absolute_path?.bytes?.toString(),
|
|
380
|
+
submatches: data.submatches?.map((sm) => ({
|
|
381
|
+
start: sm.start,
|
|
382
|
+
end: sm.end,
|
|
383
|
+
text: sm.text,
|
|
384
|
+
})),
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
else if (rgResult.type === "context") {
|
|
388
|
+
const data = rgResult.data;
|
|
389
|
+
parsed.push({
|
|
390
|
+
file: data.path?.text ?? data.path?.bytes?.toString(),
|
|
391
|
+
line: data.line_number ?? 0,
|
|
392
|
+
content: data.lines?.text ?? data.line ?? "",
|
|
393
|
+
isMatch: false,
|
|
394
|
+
isContext: true,
|
|
395
|
+
lineNumber: data.line_number,
|
|
396
|
+
absolutePath: data.absolute_path?.text ?? data.absolute_path?.bytes?.toString(),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
// Ignore other message types like "begin", "end", "summary"
|
|
343
400
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
401
|
+
catch { }
|
|
402
|
+
}
|
|
403
|
+
return parsed;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Convert parsed JSON matches back to legacy line-number format for backwards compatibility
|
|
407
|
+
*/
|
|
408
|
+
function matchesToLegacyFormat(matches) {
|
|
409
|
+
const lines = [];
|
|
410
|
+
for (const match of matches) {
|
|
411
|
+
const lineNum = match.lineNumber ?? match.line;
|
|
412
|
+
const file = match.file ?? match.absolutePath;
|
|
413
|
+
if (file) {
|
|
414
|
+
if (match.isMatch) {
|
|
415
|
+
lines.push(`${file}:${lineNum}:${match.content}`);
|
|
416
|
+
}
|
|
417
|
+
else if (match.isContext) {
|
|
418
|
+
lines.push(`${file}-${lineNum}-${match.content}`);
|
|
419
|
+
}
|
|
355
420
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
content
|
|
362
|
-
|
|
363
|
-
isContext: true,
|
|
364
|
-
});
|
|
365
|
-
continue;
|
|
421
|
+
else {
|
|
422
|
+
if (match.isMatch) {
|
|
423
|
+
lines.push(`${lineNum}:${match.content}`);
|
|
424
|
+
}
|
|
425
|
+
else if (match.isContext) {
|
|
426
|
+
lines.push(`${lineNum}-${match.content}`);
|
|
427
|
+
}
|
|
366
428
|
}
|
|
367
|
-
// If we get here, it's an unrecognized format - treat as match for backwards compatibility
|
|
368
|
-
parsed.push({
|
|
369
|
-
content: line,
|
|
370
|
-
line: 0,
|
|
371
|
-
isMatch: true,
|
|
372
|
-
});
|
|
373
429
|
}
|
|
374
|
-
return
|
|
430
|
+
return lines.join("\n");
|
|
375
431
|
}
|
|
376
432
|
/**
|
|
377
433
|
* Count actual matches (excluding context lines)
|
|
@@ -385,21 +441,49 @@ export function countActualMatches(parsed) {
|
|
|
385
441
|
export function countContextLines(parsed) {
|
|
386
442
|
return parsed.filter((match) => match.isContext).length;
|
|
387
443
|
}
|
|
444
|
+
function isContextNearKeptMatch(matches, index, indicesToKeep, contextWindow) {
|
|
445
|
+
for (let j = index - 1; j >= Math.max(0, index - contextWindow); j--) {
|
|
446
|
+
if (matches[j].isMatch && !matches[j].isContext && indicesToKeep.has(j)) {
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
for (let j = index + 1; j < Math.min(matches.length, index + contextWindow + 1); j++) {
|
|
451
|
+
if (matches[j].isMatch && !matches[j].isContext && indicesToKeep.has(j)) {
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
388
457
|
/**
|
|
389
|
-
* Truncate matches to a maximum number of results
|
|
458
|
+
* Truncate matches to a maximum number of results, preserving context lines for kept matches
|
|
390
459
|
*/
|
|
391
460
|
export function truncateMatches(matches, maxResults) {
|
|
392
461
|
if (!maxResults || maxResults <= 0) {
|
|
393
462
|
return { truncated: matches, isTruncated: false };
|
|
394
463
|
}
|
|
395
|
-
|
|
396
|
-
|
|
464
|
+
// Find indices of actual matches (excluding context lines)
|
|
465
|
+
const matchIndices = [];
|
|
466
|
+
for (let i = 0; i < matches.length; i++) {
|
|
467
|
+
if (matches[i].isMatch && !matches[i].isContext) {
|
|
468
|
+
matchIndices.push(i);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (matchIndices.length <= maxResults) {
|
|
397
472
|
return { truncated: matches, isTruncated: false };
|
|
398
473
|
}
|
|
474
|
+
// Get the indices of matches we want to keep
|
|
475
|
+
const indicesToKeep = new Set();
|
|
476
|
+
for (let i = 0; i < maxResults; i++) {
|
|
477
|
+
indicesToKeep.add(matchIndices[i]);
|
|
478
|
+
}
|
|
479
|
+
// Build truncated result: include all kept matches AND their associated context lines
|
|
399
480
|
const truncated = [];
|
|
400
481
|
let matchesKept = 0;
|
|
401
|
-
|
|
482
|
+
const contextWindow = 3; // Include up to 3 context lines around each match
|
|
483
|
+
for (let i = 0; i < matches.length; i++) {
|
|
484
|
+
const match = matches[i];
|
|
402
485
|
if (match.isMatch && !match.isContext) {
|
|
486
|
+
// This is an actual match
|
|
403
487
|
if (matchesKept < maxResults) {
|
|
404
488
|
truncated.push(match);
|
|
405
489
|
matchesKept++;
|
|
@@ -408,58 +492,84 @@ export function truncateMatches(matches, maxResults) {
|
|
|
408
492
|
break;
|
|
409
493
|
}
|
|
410
494
|
}
|
|
495
|
+
else if (match.isContext) {
|
|
496
|
+
if (isContextNearKeptMatch(matches, i, indicesToKeep, contextWindow)) {
|
|
497
|
+
truncated.push(match);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
// Other types, include them
|
|
502
|
+
truncated.push(match);
|
|
503
|
+
}
|
|
411
504
|
}
|
|
412
505
|
return {
|
|
413
506
|
truncated,
|
|
414
507
|
isTruncated: true,
|
|
415
508
|
};
|
|
416
509
|
}
|
|
417
|
-
|
|
418
|
-
* Extract matches from content (backwards compatibility wrapper)
|
|
419
|
-
*/
|
|
420
|
-
export function extractMatches(content) {
|
|
421
|
-
const parsed = parseRipgrepOutput(content);
|
|
422
|
-
const matches = parsed.filter((match) => match.isMatch && !match.isContext);
|
|
423
|
-
// Convert back to original string format for backwards compatibility
|
|
424
|
-
return matches.map((match) => {
|
|
425
|
-
if (match.file) {
|
|
426
|
-
return `${match.file}:${match.line}:${match.content}`;
|
|
427
|
-
}
|
|
428
|
-
return `${match.line}:${match.content}`;
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
export function grepFiles(pattern, path, options = {}) {
|
|
432
|
-
const result = grepFilesStructured(pattern, path, options);
|
|
433
|
-
return result.rawOutput;
|
|
434
|
-
}
|
|
435
|
-
export function grepFilesStructured(pattern, path, options = {}) {
|
|
510
|
+
export async function grepFilesStructured(pattern, path, options = {}, abortSignal) {
|
|
436
511
|
try {
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
512
|
+
const args = buildGrepArgs(pattern, path, options);
|
|
513
|
+
// Use execFile for async execution with proper abort signal handling
|
|
514
|
+
const rawOutput = await new Promise((resolve, reject) => {
|
|
515
|
+
const child = execFile("rg", args, {
|
|
516
|
+
encoding: "utf-8",
|
|
517
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
518
|
+
});
|
|
519
|
+
let stdout = "";
|
|
520
|
+
let stderr = "";
|
|
521
|
+
child.stdout?.on("data", (data) => {
|
|
522
|
+
stdout += data;
|
|
523
|
+
});
|
|
524
|
+
child.stderr?.on("data", (data) => {
|
|
525
|
+
stderr += data;
|
|
526
|
+
});
|
|
527
|
+
child.on("close", (code) => {
|
|
528
|
+
if (code === 0 || code === 1) {
|
|
529
|
+
resolve(stdout);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
reject(new Error(stderr || `ripgrep exited with code ${code}`));
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
child.on("error", (err) => {
|
|
536
|
+
reject(err);
|
|
537
|
+
});
|
|
538
|
+
if (abortSignal) {
|
|
539
|
+
abortSignal.addEventListener("abort", () => {
|
|
540
|
+
child.kill("SIGTERM");
|
|
541
|
+
reject(new Error("Grep search aborted"));
|
|
542
|
+
});
|
|
543
|
+
}
|
|
441
544
|
});
|
|
442
|
-
|
|
545
|
+
// Parse JSON output from ripgrep
|
|
546
|
+
const parsedMatches = parseRipgrepJsonOutput(rawOutput);
|
|
547
|
+
// If JSON parsing resulted in no matches, check if it's a "no matches" case
|
|
548
|
+
// (ripgrep --json returns empty for no matches)
|
|
549
|
+
const hasMatches = parsedMatches.length > 0;
|
|
443
550
|
const matchCount = countActualMatches(parsedMatches);
|
|
444
551
|
// Use the maxResults from options (which will be set by the execute function)
|
|
445
552
|
const maxResults = options.maxResults;
|
|
446
553
|
const { truncated, isTruncated } = truncateMatches(parsedMatches, maxResults);
|
|
447
554
|
const displayedCount = countActualMatches(truncated);
|
|
448
555
|
const displayedContextCount = countContextLines(truncated);
|
|
556
|
+
// Convert to legacy format for backwards compatibility
|
|
557
|
+
const legacyOutput = matchesToLegacyFormat(truncated);
|
|
558
|
+
const finalOutput = legacyOutput || "No matches found.";
|
|
449
559
|
return {
|
|
450
|
-
rawOutput,
|
|
560
|
+
rawOutput: finalOutput,
|
|
451
561
|
parsedMatches: truncated,
|
|
452
562
|
matchCount,
|
|
453
563
|
displayedCount,
|
|
454
564
|
contextCount: displayedContextCount,
|
|
455
|
-
hasMatches
|
|
565
|
+
hasMatches,
|
|
456
566
|
isTruncated,
|
|
457
567
|
};
|
|
458
568
|
}
|
|
459
569
|
catch (error) {
|
|
460
570
|
const execError = error;
|
|
461
|
-
const exitCode = execError
|
|
462
|
-
if (exitCode === 1) {
|
|
571
|
+
const exitCode = execError.code;
|
|
572
|
+
if (exitCode === "1") {
|
|
463
573
|
return {
|
|
464
574
|
rawOutput: "No matches found.",
|
|
465
575
|
parsedMatches: [],
|
|
@@ -468,10 +578,8 @@ export function grepFilesStructured(pattern, path, options = {}) {
|
|
|
468
578
|
hasMatches: false,
|
|
469
579
|
};
|
|
470
580
|
}
|
|
471
|
-
if (exitCode === 2) {
|
|
472
|
-
const stderrStr =
|
|
473
|
-
? execError.stderr
|
|
474
|
-
: (execError.stderr?.toString("utf-8") ?? execError.message);
|
|
581
|
+
if (exitCode === "2") {
|
|
582
|
+
const stderrStr = execError.message;
|
|
475
583
|
throw new Error(`Regex parse error in pattern "${pattern}": ${stderrStr}`);
|
|
476
584
|
}
|
|
477
585
|
throw new Error(`Error executing ripgrep: ${execError.message}`);
|