@travisennis/acai 0.0.9 → 0.0.11
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 +51 -760
- 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 +380 -199
- 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 -77
- package/dist/{stdin.d.ts → cli/stdin.d.ts} +2 -1
- package/dist/cli/stdin.d.ts.map +1 -0
- package/dist/{stdin.js → cli/stdin.js} +11 -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 -101
- package/dist/commands/generate-rules/service.d.ts +22 -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 +3 -2
- 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 +2 -1
- 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 +55 -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 +15 -1
- 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 +124 -135
- 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 +148 -141
- 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 +2 -5
- package/dist/models/openai-provider.d.ts.map +1 -1
- package/dist/models/openai-provider.js +0 -52
- package/dist/models/opencode-go-provider.d.ts +25 -0
- package/dist/models/opencode-go-provider.d.ts.map +1 -0
- package/dist/models/opencode-go-provider.js +78 -0
- package/dist/models/opencode-zen-provider.d.ts +7 -3
- package/dist/models/opencode-zen-provider.d.ts.map +1 -1
- package/dist/models/opencode-zen-provider.js +49 -10
- package/dist/models/openrouter-provider.d.ts +27 -31
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +121 -180
- package/dist/models/providers.d.ts +3 -3
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +6 -0
- 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 +24 -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 +142 -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} +55 -85
- package/dist/{prompts.d.ts → prompts/system-prompt.d.ts} +7 -2
- package/dist/prompts/system-prompt.d.ts.map +1 -0
- package/dist/{prompts.js → prompts/system-prompt.js} +31 -16
- 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} +397 -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 +92 -0
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +262 -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/index.d.ts +29 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +294 -0
- package/dist/subagents/index.d.ts +16 -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 +3 -3
- 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 +1 -1
- 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 +174 -105
- 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/apply-patch.d.ts +62 -0
- package/dist/tools/apply-patch.d.ts.map +1 -0
- package/dist/tools/apply-patch.js +377 -0
- package/dist/tools/bash.d.ts +4 -3
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +349 -141
- 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 +3 -6
- 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 +292 -85
- package/dist/tools/glob.d.ts +6 -6
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +110 -63
- package/dist/tools/grep.d.ts +15 -12
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +315 -193
- package/dist/tools/index.d.ts +114 -9
- 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 +4 -6
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +84 -39
- 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 +36 -31
- 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 +50 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +446 -0
- package/dist/tools/web-search.d.ts +44 -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 +37 -17
- 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/utils.d.ts +2 -1
- package/dist/tui/autocomplete/utils.d.ts.map +1 -1
- package/dist/tui/autocomplete/utils.js +25 -23
- 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 +18 -3
- package/dist/tui/components/editor.d.ts.map +1 -1
- package/dist/tui/components/editor.js +211 -237
- 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 +10 -7
- 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 +36 -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.d.ts +2 -1
- package/dist/tui/components/welcome.d.ts.map +1 -1
- package/dist/tui/components/welcome.js +2 -2
- package/dist/tui/editor-launcher.d.ts +3 -2
- package/dist/tui/editor-launcher.d.ts.map +1 -1
- package/dist/tui/index.d.ts +0 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/terminal.d.ts.map +1 -1
- package/dist/tui/terminal.js +10 -2
- package/dist/tui/tui.d.ts +43 -0
- package/dist/tui/tui.d.ts.map +1 -1
- package/dist/tui/tui.js +166 -41
- package/dist/tui/utils.d.ts +1 -5
- package/dist/tui/utils.d.ts.map +1 -1
- package/dist/tui/utils.js +271 -44
- 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 +28 -30
- 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 +35 -26
- package/dist/cli.d.ts +0 -23
- package/dist/cli.d.ts.map +0 -1
- package/dist/commands/add-directory/types.d.ts +0 -6
- package/dist/commands/add-directory/types.d.ts.map +0 -1
- package/dist/commands/add-directory/types.js +0 -1
- package/dist/commands/copy/types.d.ts +0 -3
- package/dist/commands/copy/types.d.ts.map +0 -1
- package/dist/commands/copy/types.js +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 -122
- 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/review/types.d.ts +0 -12
- package/dist/commands/review/types.d.ts.map +0 -1
- package/dist/commands/review/types.js +0 -1
- 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.map +0 -1
- package/dist/repl-new.d.ts +0 -65
- package/dist/repl-new.d.ts.map +0 -1
- package/dist/skills.d.ts +0 -16
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -233
- package/dist/stdin.d.ts.map +0 -1
- 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/utils/iterables.d.ts +0 -2
- package/dist/utils/iterables.d.ts.map +0 -1
- package/dist/utils/iterables.js +0 -6
- 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,107 @@ 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
|
+
* Checks if a character is valid inside braces (digits 0-9 or comma).
|
|
138
|
+
*/
|
|
139
|
+
function isValidBraceChar(c) {
|
|
140
|
+
return (c >= "0" && c <= "9") || c === ",";
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Checks if empty braces have a preceding atom.
|
|
144
|
+
* Empty braces with no preceding atom are treated as literal.
|
|
145
|
+
*/
|
|
146
|
+
function hasPrecedingAtom(pattern, startIndex) {
|
|
147
|
+
if (startIndex === 0)
|
|
148
|
+
return false;
|
|
149
|
+
const prev = pattern[startIndex - 1];
|
|
150
|
+
return /\S/.test(prev);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Parses and validates the content inside braces {..}.
|
|
154
|
+
* Returns true if the brace content is invalid.
|
|
155
|
+
*/
|
|
156
|
+
function isInvalidBraceContent(pattern, startIndex) {
|
|
157
|
+
const j = startIndex + 1;
|
|
158
|
+
// Find closing brace and check for invalid characters
|
|
159
|
+
let hasDigits = false;
|
|
160
|
+
let k = j;
|
|
161
|
+
while (k < pattern.length && pattern[k] !== "}") {
|
|
162
|
+
if (!isValidBraceChar(pattern[k])) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
if (pattern[k] >= "0" && pattern[k] <= "9") {
|
|
166
|
+
hasDigits = true;
|
|
167
|
+
}
|
|
168
|
+
k++;
|
|
169
|
+
}
|
|
170
|
+
// No closing brace found
|
|
171
|
+
if (k >= pattern.length) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
// Empty braces {} with preceding atom are invalid
|
|
175
|
+
if (!hasDigits && hasPrecedingAtom(pattern, startIndex)) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Check for invalid repetition operators (e.g., {n}, {n,}, {n,m}) outside of character classes.
|
|
182
|
+
* Returns true if any invalid repetition operators are found.
|
|
183
|
+
*/
|
|
184
|
+
function hasInvalidRepetition(pattern) {
|
|
185
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
186
|
+
const ch = pattern[i];
|
|
187
|
+
if (ch === "\\") {
|
|
188
|
+
i++; // Skip the next character (escaped)
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (ch === "[") {
|
|
192
|
+
i = skipCharacterClass(pattern, i);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// Unmatched closing brace is invalid
|
|
196
|
+
if (ch === "}") {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
// Handle opening brace
|
|
200
|
+
if (ch === "{") {
|
|
201
|
+
if (isInvalidBraceContent(pattern, i)) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
i = findClosingBrace(pattern, i);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Finds the index of the closing brace matching an opening brace.
|
|
211
|
+
* Returns the index of the closing brace, or the last index of pattern if not found.
|
|
212
|
+
*/
|
|
213
|
+
function findClosingBrace(pattern, startIndex) {
|
|
214
|
+
let j = startIndex + 1;
|
|
215
|
+
while (j < pattern.length && pattern[j] !== "}") {
|
|
216
|
+
j++;
|
|
217
|
+
}
|
|
218
|
+
return j;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Count bracket/paren/brace pairs in a regex pattern, excluding character classes.
|
|
222
|
+
*/
|
|
223
|
+
function countBrackets(pattern) {
|
|
127
224
|
const counts = {
|
|
128
225
|
openParen: 0,
|
|
129
226
|
closeParen: 0,
|
|
@@ -175,91 +272,56 @@ export function likelyUnbalancedRegex(pattern) {
|
|
|
175
272
|
}
|
|
176
273
|
}
|
|
177
274
|
}
|
|
275
|
+
return counts;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Check if a pattern contains regex metacharacters.
|
|
279
|
+
* Returns true if the pattern likely needs regex mode.
|
|
280
|
+
*/
|
|
281
|
+
function containsRegexMetacharacters(pattern) {
|
|
282
|
+
// Regex metacharacters: ^ $ . * + ? [ ] ( ) { } | \
|
|
283
|
+
const metacharacterPattern = /[\^$.*+?[\](){}|\\]/;
|
|
284
|
+
return metacharacterPattern.test(pattern);
|
|
285
|
+
}
|
|
286
|
+
export function likelyUnbalancedRegex(pattern) {
|
|
287
|
+
// First check: unbalanced brackets/parentheses/braces = likely a typo, use literal
|
|
288
|
+
const counts = countBrackets(pattern);
|
|
178
289
|
// Check for unbalanced brackets, parentheses, and braces
|
|
179
290
|
const hasUnbalancedBrackets = counts.openBracket !== counts.closeBracket;
|
|
180
291
|
const hasUnbalancedParens = counts.openParen !== counts.closeParen;
|
|
181
292
|
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 ||
|
|
293
|
+
// Also check for invalid repetition operators
|
|
294
|
+
const hasInvalidRepetitionFlag = hasInvalidRepetition(pattern);
|
|
295
|
+
// If pattern has unbalanced syntax, treat as literal (user probably meant to type literal)
|
|
296
|
+
if (hasUnbalancedBrackets ||
|
|
243
297
|
hasUnbalancedParens ||
|
|
244
298
|
hasUnbalancedBraces ||
|
|
245
|
-
|
|
299
|
+
hasInvalidRepetitionFlag) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
// Second check: if pattern has regex metacharacters, treat as regex (return false)
|
|
303
|
+
// This allows ripgrep to handle the pattern as a proper regex
|
|
304
|
+
if (containsRegexMetacharacters(pattern)) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
// Default: simple alphanumeric strings should use literal mode
|
|
308
|
+
return true;
|
|
246
309
|
}
|
|
247
310
|
/**
|
|
248
|
-
*
|
|
311
|
+
* Build grep command args array directly
|
|
249
312
|
*
|
|
250
313
|
* @param pattern - The regex pattern to search for
|
|
251
314
|
* @param path - The path to search in
|
|
252
315
|
* @param options - Additional options for the grep command
|
|
253
|
-
* @returns The
|
|
316
|
+
* @returns The args array for the grep command
|
|
254
317
|
*/
|
|
255
|
-
export function
|
|
318
|
+
export function buildGrepArgs(pattern, path, options = {}) {
|
|
256
319
|
const effectiveRecursive = options.recursive === null ? true : options.recursive;
|
|
257
320
|
const effectiveIgnoreCase = options.ignoreCase === null ? false : options.ignoreCase;
|
|
258
321
|
const effectiveSearchIgnored = options.searchIgnored === null ? false : options.searchIgnored;
|
|
259
322
|
const effectiveFilePattern = options.filePattern;
|
|
260
323
|
const effectiveContextLines = options.contextLines;
|
|
261
|
-
//
|
|
262
|
-
// If null/undefined, auto-detect unbalanced regexes and prefer fixed-strings.
|
|
324
|
+
// Use the pre-computed likelyUnbalanced result if available
|
|
263
325
|
let effectiveLiteral;
|
|
264
326
|
if (options.literal === true) {
|
|
265
327
|
effectiveLiteral = true;
|
|
@@ -267,27 +329,30 @@ export function buildGrepCommand(pattern, path, options = {}) {
|
|
|
267
329
|
else if (options.literal === false) {
|
|
268
330
|
effectiveLiteral = false;
|
|
269
331
|
}
|
|
332
|
+
else if (options.likelyUnbalanced !== undefined) {
|
|
333
|
+
effectiveLiteral = options.likelyUnbalanced;
|
|
334
|
+
}
|
|
270
335
|
else {
|
|
271
336
|
effectiveLiteral = likelyUnbalancedRegex(pattern);
|
|
272
337
|
}
|
|
273
|
-
|
|
338
|
+
const args = ["--json"];
|
|
274
339
|
if (effectiveRecursive === false) {
|
|
275
|
-
|
|
340
|
+
args.push("--max-depth=0");
|
|
276
341
|
}
|
|
277
342
|
if (effectiveIgnoreCase) {
|
|
278
|
-
|
|
343
|
+
args.push("--ignore-case");
|
|
279
344
|
}
|
|
280
345
|
if (effectiveContextLines !== null && effectiveContextLines !== undefined) {
|
|
281
|
-
|
|
346
|
+
args.push(`--context=${effectiveContextLines}`);
|
|
282
347
|
}
|
|
283
348
|
if (effectiveFilePattern !== null && effectiveFilePattern !== undefined) {
|
|
284
|
-
|
|
349
|
+
args.push(`--glob=${effectiveFilePattern}`);
|
|
285
350
|
}
|
|
286
351
|
if (effectiveSearchIgnored) {
|
|
287
|
-
|
|
352
|
+
args.push("--no-ignore");
|
|
288
353
|
}
|
|
289
354
|
if (effectiveLiteral) {
|
|
290
|
-
|
|
355
|
+
args.push("-F");
|
|
291
356
|
}
|
|
292
357
|
// Use ripgrep's --max-count flag to limit matches per file for efficiency
|
|
293
358
|
// This helps prevent any single file from dominating results
|
|
@@ -296,82 +361,88 @@ export function buildGrepCommand(pattern, path, options = {}) {
|
|
|
296
361
|
options.maxResults > 0) {
|
|
297
362
|
// Use a reasonable per-file limit (max 100 per file) to balance efficiency and completeness
|
|
298
363
|
const perFileLimit = Math.min(options.maxResults, 100);
|
|
299
|
-
|
|
364
|
+
args.push(`--max-count=${perFileLimit}`);
|
|
300
365
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return
|
|
366
|
+
args.push(pattern);
|
|
367
|
+
args.push(path);
|
|
368
|
+
return args;
|
|
304
369
|
}
|
|
305
370
|
/**
|
|
306
|
-
* Parse ripgrep output and extract structured match information
|
|
371
|
+
* Parse ripgrep JSON output and extract structured match information
|
|
307
372
|
*/
|
|
308
|
-
export function
|
|
309
|
-
if (content
|
|
373
|
+
export function parseRipgrepJsonOutput(content) {
|
|
374
|
+
if (!content || content.trim() === "") {
|
|
310
375
|
return [];
|
|
311
376
|
}
|
|
312
|
-
const lines = content.trim().split("\n");
|
|
313
377
|
const parsed = [];
|
|
378
|
+
const lines = content.trim().split("\n");
|
|
314
379
|
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
|
|
380
|
+
if (!line.trim()) {
|
|
321
381
|
continue;
|
|
322
382
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
383
|
+
try {
|
|
384
|
+
const rgResult = JSON.parse(line);
|
|
385
|
+
// Handle different ripgrep message types
|
|
386
|
+
if (rgResult.type === "match") {
|
|
387
|
+
const data = rgResult.data;
|
|
388
|
+
parsed.push({
|
|
389
|
+
file: data.path?.text ?? data.path?.bytes?.toString(),
|
|
390
|
+
line: data.line_number ?? 0,
|
|
391
|
+
content: data.lines?.text ?? data.line ?? "",
|
|
392
|
+
isMatch: true,
|
|
393
|
+
lineNumber: data.line_number,
|
|
394
|
+
absolutePath: data.absolute_path?.text ?? data.absolute_path?.bytes?.toString(),
|
|
395
|
+
submatches: data.submatches?.map((sm) => ({
|
|
396
|
+
start: sm.start,
|
|
397
|
+
end: sm.end,
|
|
398
|
+
text: sm.text,
|
|
399
|
+
})),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
else if (rgResult.type === "context") {
|
|
403
|
+
const data = rgResult.data;
|
|
404
|
+
parsed.push({
|
|
405
|
+
file: data.path?.text ?? data.path?.bytes?.toString(),
|
|
406
|
+
line: data.line_number ?? 0,
|
|
407
|
+
content: data.lines?.text ?? data.line ?? "",
|
|
408
|
+
isMatch: false,
|
|
409
|
+
isContext: true,
|
|
410
|
+
lineNumber: data.line_number,
|
|
411
|
+
absolutePath: data.absolute_path?.text ?? data.absolute_path?.bytes?.toString(),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
// Ignore other message types like "begin", "end", "summary"
|
|
343
415
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
416
|
+
catch { }
|
|
417
|
+
}
|
|
418
|
+
return parsed;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Convert parsed JSON matches back to legacy line-number format for backwards compatibility
|
|
422
|
+
*/
|
|
423
|
+
function matchesToLegacyFormat(matches) {
|
|
424
|
+
const lines = [];
|
|
425
|
+
for (const match of matches) {
|
|
426
|
+
const lineNum = match.lineNumber ?? match.line;
|
|
427
|
+
const file = match.file ?? match.absolutePath;
|
|
428
|
+
if (file) {
|
|
429
|
+
if (match.isMatch) {
|
|
430
|
+
lines.push(`${file}:${lineNum}:${match.content}`);
|
|
431
|
+
}
|
|
432
|
+
else if (match.isContext) {
|
|
433
|
+
lines.push(`${file}-${lineNum}-${match.content}`);
|
|
434
|
+
}
|
|
355
435
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
content
|
|
362
|
-
|
|
363
|
-
isContext: true,
|
|
364
|
-
});
|
|
365
|
-
continue;
|
|
436
|
+
else {
|
|
437
|
+
if (match.isMatch) {
|
|
438
|
+
lines.push(`${lineNum}:${match.content}`);
|
|
439
|
+
}
|
|
440
|
+
else if (match.isContext) {
|
|
441
|
+
lines.push(`${lineNum}-${match.content}`);
|
|
442
|
+
}
|
|
366
443
|
}
|
|
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
444
|
}
|
|
374
|
-
return
|
|
445
|
+
return lines.join("\n");
|
|
375
446
|
}
|
|
376
447
|
/**
|
|
377
448
|
* Count actual matches (excluding context lines)
|
|
@@ -385,81 +456,134 @@ export function countActualMatches(parsed) {
|
|
|
385
456
|
export function countContextLines(parsed) {
|
|
386
457
|
return parsed.filter((match) => match.isContext).length;
|
|
387
458
|
}
|
|
459
|
+
function isContextNearKeptMatch(matches, index, indicesToKeep, contextWindow) {
|
|
460
|
+
for (let j = index - 1; j >= Math.max(0, index - contextWindow); j--) {
|
|
461
|
+
if (matches[j].isMatch && !matches[j].isContext && indicesToKeep.has(j)) {
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
for (let j = index + 1; j < Math.min(matches.length, index + contextWindow + 1); j++) {
|
|
466
|
+
if (matches[j].isMatch && !matches[j].isContext && indicesToKeep.has(j)) {
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
388
472
|
/**
|
|
389
|
-
* Truncate matches to a maximum number of results
|
|
473
|
+
* Truncate matches to a maximum number of results, preserving context lines for kept matches
|
|
390
474
|
*/
|
|
391
475
|
export function truncateMatches(matches, maxResults) {
|
|
392
476
|
if (!maxResults || maxResults <= 0) {
|
|
393
477
|
return { truncated: matches, isTruncated: false };
|
|
394
478
|
}
|
|
395
|
-
|
|
396
|
-
|
|
479
|
+
// Find indices of actual matches (excluding context lines)
|
|
480
|
+
const matchIndices = [];
|
|
481
|
+
for (let i = 0; i < matches.length; i++) {
|
|
482
|
+
if (matches[i].isMatch && !matches[i].isContext) {
|
|
483
|
+
matchIndices.push(i);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (matchIndices.length <= maxResults) {
|
|
397
487
|
return { truncated: matches, isTruncated: false };
|
|
398
488
|
}
|
|
489
|
+
// Get the indices of matches we want to keep
|
|
490
|
+
const indicesToKeep = new Set();
|
|
491
|
+
for (let i = 0; i < maxResults; i++) {
|
|
492
|
+
indicesToKeep.add(matchIndices[i]);
|
|
493
|
+
}
|
|
494
|
+
// Build truncated result: include all kept matches AND their associated context lines
|
|
399
495
|
const truncated = [];
|
|
400
496
|
let matchesKept = 0;
|
|
401
|
-
|
|
497
|
+
const contextWindow = 3; // Include up to 3 context lines around each match
|
|
498
|
+
for (let i = 0; i < matches.length; i++) {
|
|
499
|
+
const match = matches[i];
|
|
402
500
|
if (match.isMatch && !match.isContext) {
|
|
501
|
+
// This is an actual match
|
|
403
502
|
if (matchesKept < maxResults) {
|
|
404
503
|
truncated.push(match);
|
|
405
504
|
matchesKept++;
|
|
406
505
|
}
|
|
407
506
|
else {
|
|
408
|
-
break;
|
|
409
507
|
}
|
|
410
508
|
}
|
|
509
|
+
else if (match.isContext) {
|
|
510
|
+
if (isContextNearKeptMatch(matches, i, indicesToKeep, contextWindow)) {
|
|
511
|
+
truncated.push(match);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
// Other types, include them
|
|
516
|
+
truncated.push(match);
|
|
517
|
+
}
|
|
411
518
|
}
|
|
412
519
|
return {
|
|
413
520
|
truncated,
|
|
414
521
|
isTruncated: true,
|
|
415
522
|
};
|
|
416
523
|
}
|
|
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 = {}) {
|
|
524
|
+
export async function grepFilesStructured(pattern, path, options = {}, abortSignal) {
|
|
436
525
|
try {
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
526
|
+
const args = buildGrepArgs(pattern, path, options);
|
|
527
|
+
// Use execFile for async execution with proper abort signal handling
|
|
528
|
+
const rawOutput = await new Promise((resolve, reject) => {
|
|
529
|
+
const child = execFile("rg", args, {
|
|
530
|
+
encoding: "utf-8",
|
|
531
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
532
|
+
});
|
|
533
|
+
let stdout = "";
|
|
534
|
+
let stderr = "";
|
|
535
|
+
child.stdout?.on("data", (data) => {
|
|
536
|
+
stdout += data;
|
|
537
|
+
});
|
|
538
|
+
child.stderr?.on("data", (data) => {
|
|
539
|
+
stderr += data;
|
|
540
|
+
});
|
|
541
|
+
child.on("close", (code) => {
|
|
542
|
+
if (code === 0 || code === 1) {
|
|
543
|
+
resolve(stdout);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
reject(new Error(stderr || `ripgrep exited with code ${code}`));
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
child.on("error", (err) => {
|
|
550
|
+
reject(err);
|
|
551
|
+
});
|
|
552
|
+
if (abortSignal) {
|
|
553
|
+
abortSignal.addEventListener("abort", () => {
|
|
554
|
+
child.kill("SIGTERM");
|
|
555
|
+
reject(new Error("Grep search aborted"));
|
|
556
|
+
});
|
|
557
|
+
}
|
|
441
558
|
});
|
|
442
|
-
|
|
559
|
+
// Parse JSON output from ripgrep
|
|
560
|
+
const parsedMatches = parseRipgrepJsonOutput(rawOutput);
|
|
561
|
+
// If JSON parsing resulted in no matches, check if it's a "no matches" case
|
|
562
|
+
// (ripgrep --json returns empty for no matches)
|
|
563
|
+
const hasMatches = parsedMatches.length > 0;
|
|
443
564
|
const matchCount = countActualMatches(parsedMatches);
|
|
444
565
|
// Use the maxResults from options (which will be set by the execute function)
|
|
445
566
|
const maxResults = options.maxResults;
|
|
446
567
|
const { truncated, isTruncated } = truncateMatches(parsedMatches, maxResults);
|
|
447
568
|
const displayedCount = countActualMatches(truncated);
|
|
448
569
|
const displayedContextCount = countContextLines(truncated);
|
|
570
|
+
// Convert to legacy format for backwards compatibility
|
|
571
|
+
const legacyOutput = matchesToLegacyFormat(truncated);
|
|
572
|
+
const finalOutput = legacyOutput || "No matches found.";
|
|
449
573
|
return {
|
|
450
|
-
rawOutput,
|
|
574
|
+
rawOutput: finalOutput,
|
|
451
575
|
parsedMatches: truncated,
|
|
452
576
|
matchCount,
|
|
453
577
|
displayedCount,
|
|
454
578
|
contextCount: displayedContextCount,
|
|
455
|
-
hasMatches
|
|
579
|
+
hasMatches,
|
|
456
580
|
isTruncated,
|
|
457
581
|
};
|
|
458
582
|
}
|
|
459
583
|
catch (error) {
|
|
460
584
|
const execError = error;
|
|
461
|
-
const exitCode = execError
|
|
462
|
-
if (exitCode === 1) {
|
|
585
|
+
const exitCode = execError.code;
|
|
586
|
+
if (exitCode === "1") {
|
|
463
587
|
return {
|
|
464
588
|
rawOutput: "No matches found.",
|
|
465
589
|
parsedMatches: [],
|
|
@@ -468,10 +592,8 @@ export function grepFilesStructured(pattern, path, options = {}) {
|
|
|
468
592
|
hasMatches: false,
|
|
469
593
|
};
|
|
470
594
|
}
|
|
471
|
-
if (exitCode === 2) {
|
|
472
|
-
const stderrStr =
|
|
473
|
-
? execError.stderr
|
|
474
|
-
: (execError.stderr?.toString("utf-8") ?? execError.message);
|
|
595
|
+
if (exitCode === "2") {
|
|
596
|
+
const stderrStr = execError.message;
|
|
475
597
|
throw new Error(`Regex parse error in pattern "${pattern}": ${stderrStr}`);
|
|
476
598
|
}
|
|
477
599
|
throw new Error(`Error executing ripgrep: ${execError.message}`);
|