Add agent integration layer for AI coding assistants#153
Conversation
Add `codesearch install <platform>`, `uninstall`, and `hooks` commands that wire codesearch into AI coding assistants so they reach for semantic search instead of grepping or reading files one by one. Two layers: - Agent hooks that nudge toward codesearch search/context/impact when the assistant is about to grep or read source files. Claude Code gets PreToolUse hooks in settings.json; OpenCode an auto-loaded tool.execute plugin; Pi a live TypeScript extension (tool_result); Zed always-on .rules plus an MCP context_servers registration (Zed has no payload pre-tool hook). - Git hooks (post-commit/post-checkout) that re-index in the background so the nudge always points at fresh data. A new `codesearch hook-check` command centralises the nudge decision (reading the tool payload on stdin), and `codesearch index` drops a per-project .codesearch/project.json marker so hooks only fire in indexed repositories. Every hook fails open and only adds context, never blocking a tool call.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (13)
💤 Files with no reviewable changes (6)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThis PR introduces a complete agent integration framework enabling codesearch to integrate with multiple AI platforms (Claude, OpenCode, Pi, Zed). It adds CLI commands for installing/uninstalling platform-specific hooks and plugins, a persistent project indexing marker system, runtime tool-call evaluation that nudges assistants toward codesearch, and git hook management for automatic repository indexing. ChangesAgent integration framework and CLI wiring
Sequence Diagram(s)sequenceDiagram
participant User as User/CLI
participant Main as main
participant PreTool as pre_tool_call
participant Dispatch as install::dispatch
participant Platform as Platform module
participant Marker as marker
participant GitHook as git_hooks
rect rgba(200, 150, 100, 0.5)
note over User,GitHook: Pre-tool-call nudge flow
User->>Main: codesearch pre-tool-call
Main->>PreTool: run() stdin JSON
PreTool->>Marker: find_marker(cwd)
Marker-->>PreTool: marker path or None
alt marker exists and tool matches
PreTool-->>User: nudge PreToolUse output
else
PreTool-->>User: (silent, tool proceeds)
end
end
rect rgba(150, 200, 150, 0.5)
note over User,GitHook: Platform install/uninstall flow
User->>Main: codesearch install/uninstall --platform all
Main->>Dispatch: dispatch(Install/Uninstall)
Dispatch->>Platform: loop for each platform
Platform->>Marker: reads/writes marker
Platform-->>Dispatch: status messages
Dispatch-->>User: formatted report
end
rect rgba(100, 150, 200, 0.5)
note over User,GitHook: Git hook management flow
User->>Main: codesearch hooks install
Main->>Dispatch: dispatch(Hooks)
Dispatch->>GitHook: dispatch(Install)
GitHook->>GitHook: git rev-parse hooks dir
GitHook->>GitHook: write post-commit/post-checkout
GitHook-->>Dispatch: hook status
Dispatch-->>User: summary
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes This PR introduces a substantial agent integration framework spanning platform-specific installers, JSON/file management helpers, marker persistence, tool-call evaluation heuristics, git hook scripts, and end-to-end integration tests. Heterogeneous changes across five platform modules, pre-tool-call logic with command/path filtering, git hook shell scripting, and comprehensive test coverage demand careful review of each functional checkpoint to validate behavior, idempotency, error handling, and inter-component dependencies. Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/connector/api/agent/git_hooks.rs`:
- Around line 90-96: The IO calls in git_hooks.rs (e.g., the read_to_string and
write in the hook installation flow and the remove_file / set_permissions calls
referenced elsewhere) are being propagated as bare errors; wrap each fallible
filesystem call with anyhow::Context to add call-site operation+path information
before propagating (for example, use .with_context(|| format!("failed to read
hook file {}", path.display())) on read_to_string, similarly "failed to write
hook file {path}", "failed to remove hook file {path}", "failed to set
permissions for hook file {path}") so errors include the operation and the hook
path; ensure anyhow::Context is imported at the top of the module and apply this
pattern to the functions/methods that call read_to_string, write, remove_file,
and set_permissions.
- Around line 89-100: When appending to an existing hook the code writes merged
content but never ensures the file is executable; after writing the merged
content to path in the branch that returns Ok(format!("{name}: appended to
existing hook")), call set_executable(&path)? to make the hook executable (same
helper used in the install branch), and propagate any error so the function
returns a Result as before; update the block around path.exists() where content
is read/merged (using variables marker, script, path) to invoke
set_executable(&path)? immediately after std::fs::write(&path, merged)?.
In `@src/connector/api/agent/mod.rs`:
- Around line 165-172: The function load_json_object currently swallows read and
parse/type errors and returns an empty Map, which can silently erase config;
change load_json_object to propagate errors instead of returning {} — update its
signature to return a Result<Map<String, Value>, Box<dyn std::error::Error>> (or
a concrete error type used in the project), read the file with
std::fs::read_to_string, parse with serde_json::from_str::<Value>, and on
success convert to an object with v.as_object().cloned().ok_or(...) returning a
clear error for non-object JSON; callers that expect a Map (e.g.,
installer/uninstaller paths) should be updated to handle the Result (bubble up
or surface a user-facing error) rather than writing back an empty map.
In `@src/connector/api/controller/index_controller.rs`:
- Around line 47-53: The marker is being written to the canonicalized input path
which may be a subdirectory; instead resolve the repository worktree root and
write the marker there: before calling write_marker (and before use_case.execute
if appropriate) derive the git top-level directory (e.g., via running `git
rev-parse --show-toplevel` or using the repo library) and canonicalize that
worktree root, then create ProjectMarker and call write_marker(&worktree_root,
&marker) so the marker ends up at the actual repo root; alternatively, enforce
callers to pass repo root but prefer deriving the root in this module (affecting
use_case.execute, write_marker, and write_project_marker flows).
- Around line 41-59: write_project_marker performs synchronous filesystem I/O
(std::fs::canonicalize and marker::write_marker) and is currently called from
the async IndexController::index, so move that blocking work into a blocking
task: change callers (IndexController::index) to spawn and await a
tokio::task::spawn_blocking that invokes write_project_marker (or move the fs
logic into a new blocking helper invoked inside spawn_blocking), ensure any use
of self/container state passed into the closure is cloned or made 'static (e.g.,
repo.id/name/namespace or container.memory_storage() check), and propagate or
log errors after awaiting the spawned task; keep the original
write_project_marker signature or create write_project_marker_blocking to
contain only synchronous fs operations (canonicalize and marker::write_marker).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6209feb4-49d5-4064-8f0f-e039eb804088
📒 Files selected for processing (15)
src/cli/mod.rssrc/connector/api/agent/claude.rssrc/connector/api/agent/git_hooks.rssrc/connector/api/agent/hook_check.rssrc/connector/api/agent/marker.rssrc/connector/api/agent/mod.rssrc/connector/api/agent/opencode.rssrc/connector/api/agent/pi.rssrc/connector/api/agent/zed.rssrc/connector/api/controller/index_controller.rssrc/connector/api/mod.rssrc/connector/api/router.rssrc/lib.rssrc/main.rstests/agent_install_tests.rs
| let root = std::fs::canonicalize(path).unwrap_or_else(|_| std::path::PathBuf::from(path)); | ||
| let marker = ProjectMarker::new( | ||
| repo.id().to_string(), | ||
| repo.name().to_string(), | ||
| repo.namespace().map(str::to_string), | ||
| ); | ||
| if let Err(e) = write_marker(&root, &marker) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether Repository exposes a root/path field and how index use case resolves input path.
# Expected: evidence that marker target path is repo root in all successful index flows.
set -euo pipefail
echo "== Locate Repository definition =="
fd -i 'repository.rs|mod.rs' src | xargs -r rg -n -C2 'struct\s+Repository|impl\s+Repository|fn\s+path\s*\(|fn\s+root\s*\('
echo "== Locate index use case execute signature/implementation =="
rg -n -C3 'fn\s+execute\s*\(.*path|execute\s*\(&path' src
echo "== Locate any canonicalization/root-resolution in indexing pipeline =="
rg -n -C3 'canonicalize|repo root|repository root|worktree|git rev-parse|current_dir' src/connector src/domain src/usecaseRepository: ArtemisMucaj/codesearch
Length of output: 10202
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Inspect index_controller.rs around write_project_marker and marker write call =="
sed -n '1,140p' src/connector/api/controller/index_controller.rs
echo "== Inspect marker.rs (MARKER_DIR/MARKER_FILE/marker_path/write_marker) =="
sed -n '1,220p' src/connector/api/agent/marker.rs
echo "== Locate index use case implementation and how it resolves input path =="
rg -n "index_use_case" src/connector src/domain src/usecase 2>/dev/null || true
rg -n "fn\s+execute\s*\(\s*&.*path\s*," src 2>/dev/null || true
rg -n "execute\s*\(\s*&path\s*," src 2>/dev/null || true
fd -t f 'index*usecase*|*index*use*case*|*index*' src | head -n 200Repository: ArtemisMucaj/codesearch
Length of output: 7313
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find IndexRepositoryUseCase type =="
rg -n "IndexRepositoryUseCase" src/connector src/domain src/usecase
echo "== Find execute implementation for the index use case =="
rg -n "impl\s+.*IndexRepositoryUseCase|impl\s+.*Index.*UseCase|fn\s+execute\s*\(.*&.*path" src/connector src/domain src/usecase
echo "== Inspect connector/api/container.rs index_use_case wiring =="
sed -n '400,520p' src/connector/api/container.rs
echo "== Inspect Repository model around path() and construction =="
sed -n '1,220p' src/domain/models/repository.rs
echo "== Find where Repository::new(name, path) is called =="
rg -n "Repository::new\(" src/connector src/domain src/usecase
echo "== Inspect hook_check marker lookup behavior (relative to repo root expectations) =="
sed -n '90,180p' src/connector/api/agent/hook_check.rsRepository: ArtemisMucaj/codesearch
Length of output: 464
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== List src top-level directories =="
ls -la src
echo "== Search for IndexRepositoryUseCase definition/impl =="
rg -n "struct\s+IndexRepositoryUseCase|enum\s+IndexRepositoryUseCase" src || true
rg -n "impl\s+.*IndexRepositoryUseCase" src || true
rg -n "IndexRepositoryUseCase::new" src || true
echo "== Inspect container.rs around index_use_case() =="
sed -n '420,520p' src/connector/api/container.rs
echo "== Inspect repository model around path() semantics =="
sed -n '1,220p' src/domain/models/repository.rs
echo "== Find where Repository::new(name, path) is called =="
rg -n "Repository::new\s*\(" src || true
echo "== Inspect hook_check marker lookup behavior =="
sed -n '90,200p' src/connector/api/agent/hook_check.rsRepository: ArtemisMucaj/codesearch
Length of output: 14994
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Inspect src/application/use_cases/index_repository.rs around execute =="
sed -n '1,220p' src/application/use_cases/index_repository.rs
rg -n "fn\s+execute|canonicalize|worktree|rev-parse|repo\s+root|repository\s+root|current_dir" src/application/use_cases/index_repository.rs
echo "== Inspect further down for path resolution =="
sed -n '220,520p' src/application/use_cases/index_repository.rs
echo "== Find any helper that resolves repository root from input path (used by indexing) =="
rg -n "worktree|rev-parse|git\s+rev-parse|current_dir|canonicalize|resolve.*(root|repo)|repo.*(root|path)" src/connector src/application src/domain | head -n 200Repository: ArtemisMucaj/codesearch
Length of output: 30173
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Inspect git_hooks.rs to see how hook_check runs / what cwd is =="
sed -n '1,140p' src/connector/api/agent/git_hooks.rs
sed -n '140,260p' src/connector/api/agent/git_hooks.rs
echo "== Search for hook_check invocation =="
rg -n "hook_check|hook-check|hookcheck" src/connector/api/agent src/connector/api/controller src/application || true
echo "== Inspect any hook install logic (looking for post-checkout) =="
rg -n "post-checkout|post checkout|post_checkout" src/connector/api/agent src/connector/api || trueRepository: ArtemisMucaj/codesearch
Length of output: 10015
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for git repo root resolution (show-toplevel/worktree) =="
rg -n "show-toplevel|rev-parse --show-toplevel|git rev-parse --git-dir|git rev-parse.*toplevel|worktree root|--show-cdup" src/connector src/application src/domain || true
echo "== Search for any function that derives repo root from input path =="
rg -n "git_root|repo_root|repository_root|worktree|rev_parse.*toplevel" src/connector src/application src/domain || trueRepository: ArtemisMucaj/codesearch
Length of output: 290
Ensure marker is written at the git repository root (not just the canonicalized user input path)
use_case.execute(&path, ...)canonicalizespathand indexes that directory directly; it does not resolve a VCS/working-tree root (so passing a subdirectory makes the “root” a subdirectory).write_project_markerthen writes to<canonicalized path>/.codesearch/project.json, so the marker can land under that subdirectory.- The
post-checkouthook checks for.codesearch/project.jsonrelative to the repo working dir before runningcodesearch index ., so if the marker isn’t at the actual repo root, re-index on checkout won’t fire.
Either enforce that callers pass the repo root, or have the indexing/marker path resolution derive the git worktree root (e.g., via git rev-parse --show-toplevel) and use that for the marker.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/connector/api/controller/index_controller.rs` around lines 47 - 53, The
marker is being written to the canonicalized input path which may be a
subdirectory; instead resolve the repository worktree root and write the marker
there: before calling write_marker (and before use_case.execute if appropriate)
derive the git top-level directory (e.g., via running `git rev-parse
--show-toplevel` or using the repo library) and canonicalize that worktree root,
then create ProjectMarker and call write_marker(&worktree_root, &marker) so the
marker ends up at the actual repo root; alternatively, enforce callers to pass
repo root but prefer deriving the root in this module (affecting
use_case.execute, write_marker, and write_project_marker flows).
There was a problem hiding this comment.
Declining this one (the other four findings in this review are fixed in ec58877).
codesearch indexes arbitrary directories, not necessarily git worktrees — index is regularly run against non-git folders. Resolving the marker location via git rev-parse --show-toplevel would therefore be wrong in two ways: it would fail/misbehave for non-git directories, and it would override a deliberate choice to index a subdirectory.
The marker is intentionally written at the root of what was indexed (the canonicalized input path). In the standard codesearch index . flow — and in the git hooks themselves, which run codesearch index . from the worktree root — the indexed path is the repo root, so the marker lands exactly where the post-checkout hook ([ -f ".codesearch/project.json" ]) and pre-tool-call (which walks up from cwd) look for it. Indexing a subdirectory is a valid, scoped use; the marker correctly reflects that scope rather than silently re-pointing at a VCS root the user didn't index.
Generated by Claude Code
There was a problem hiding this comment.
@ArtemisMucaj, understood — the explanation is clear and the design makes sense. Writing the marker at the canonicalized input path (rather than forcing VCS root resolution) correctly handles non-git directories, preserves subdirectory indexing scope, and aligns with the standard codesearch index . from the worktree root convention used by the git hooks. I'll keep this in mind for future reviews.
(¬ 、¬)
✏️ Learnings added
Learnt from: ArtemisMucaj
Repo: ArtemisMucaj/codesearch PR: 153
File: src/connector/api/controller/index_controller.rs:0-0
Timestamp: 2026-06-08T09:46:30.518Z
Learning: In the `codesearch` project (Rust), the project marker `.codesearch/project.json` is intentionally written at the canonicalized root of **what was indexed** (the input path to `codesearch index`), not at the git worktree root. This is by design because:
1. `codesearch index` can be run against arbitrary directories, including non-git folders — resolving via `git rev-parse --show-toplevel` would fail for those.
2. Indexing a subdirectory is a valid scoped use case; the marker should reflect that scope.
3. In the standard flow, `codesearch index .` is run from the worktree root (including by git hooks), so the marker naturally lands at the repo root where the `post-checkout` hook (`[ -f ".codesearch/project.json" ]`) and `hook-check` (which walks up from cwd) look for it.
File: `src/connector/api/controller/index_controller.rs`, function `write_project_marker`.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
The internal `codesearch hook-check` command name was vague. Rename it to `codesearch pre-tool-call` (and the module/constant to match) so the command clearly reflects that it evaluates a pre-tool-call payload. Updates the Claude PreToolUse hook command string and all references; behaviour is unchanged.
- git_hooks: set executable bit when appending to an existing hook so git
runs the codesearch section; add anyhow::Context to all filesystem calls
- agent::load_json_object: return Result and propagate parse/read errors
instead of silently overwriting a malformed-but-present config with {}
- index_controller: move the blocking marker write into spawn_blocking so it
never stalls a Tokio worker during concurrent indexing
The agent integration extends the ecosystem around codesearch (editor/agent hooks) rather than adding to its core search/analysis capabilities — much like the TUI. Move it out of the connector layer (src/connector/api/agent) to a sibling top-level module (src/agent) to reflect that, and repoint the four references (lib.rs, main.rs, connector/api/mod.rs, index_controller.rs). No behaviour change.
…ctor - Move the per-project marker (.codesearch/project.json read/write/find) out of the agent module into the connector layer (src/connector/marker.rs), where filesystem persistence belongs; it is consumed by both the index controller and the pre-tool-call command. - Group all install/uninstall/hook-writing code under a dedicated src/agent/install submodule, leaving src/agent with just the install subtree and the runtime pre_tool_call command. Repoints references (main.rs, connector/mod.rs, index_controller, pre_tool_call). No behaviour change.
Summary
Introduces a comprehensive agent integration system that wires codesearch into AI coding assistants (Claude Code, OpenCode, Pi, Zed) so they reach for semantic search instead of grepping. The system consists of two main components:
codesearch search/context/impactwhen they attempt to grep or read source filespost-commitandpost-checkoutautomation that keeps the index fresh in the backgroundKey Changes
New Modules
src/connector/api/agent/mod.rs— Main orchestration layer for install/uninstall across all platforms, with shared filesystem and JSON helperssrc/connector/api/agent/marker.rs— Per-project indexing marker (.codesearch/project.json) that gates nudges to indexed repositories onlysrc/connector/api/agent/hook_check.rs— Decision logic for evaluatingPreToolUsepayloads and deciding when to nudge; reused across all platforms viacodesearch hook-checkcommandsrc/connector/api/agent/git_hooks.rs— Gitpost-commitandpost-checkouthook installation/uninstall with smart merging into existing user hookssrc/connector/api/agent/claude.rs— Claude Code integration viaPreToolUsehooks insettings.jsonsrc/connector/api/agent/opencode.rs— OpenCode integration viatool.execute.before/afterpluginsrc/connector/api/agent/pi.rs— Pi coding agent integration via TypeScript extension withtool_call/tool_resulthookssrc/connector/api/agent/zed.rs— Zed integration via.rulesguidance block and MCPcontext_serversregistrationCLI Changes
src/cli/mod.rs— AddedAgentPlatformenum (Claude, Opencode, Pi, Zed, All) andHooksSubcommandenum (Install, Uninstall, Status)src/main.rs— Early dispatch of lightweight commands (hook-check,install,uninstall,hooks) before logging initialization to keep stdout clean for JSON outputsrc/connector/api/router.rs— Routed agent commands to the new moduleIndex Integration
src/connector/api/controller/index_controller.rs— After successful indexing, writes the project marker so installed hooks know the repository is indexedTesting
tests/agent_install_tests.rs— End-to-end tests for install/uninstall across all platforms and hook-check behavior with/without markersImplementation Details
.codesearch/project.jsonexists, preventing spam in freshly cloned or never-indexed repositorieshook_check::evaluate()centralizes the nudge decision and is called by both the CLI command and platform-specific integrations.rulesfor Zed)codesearch index .in the background (detached, non-blocking) and skip during rebase/merge/cherry-pick operationshttps://claude.ai/code/session_01H7BrS8BtStu56KPWcTBQv4
Summary by CodeRabbit
Release Notes
New Features
Tests