feat(mcp): minimal mem_find_project tool#661
Conversation
📝 WalkthroughWalkthroughAdds project-level memory relevance search through a new ChangesProject relevance search
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant MCPClient
participant handleFindProject
participant Store
MCPClient->>handleFindProject: query and match_mode
handleFindProject->>Store: SearchProjects(query, match_mode, limit)
Store-->>handleFindProject: ranked ProjectMatch results
handleFindProject-->>MCPClient: formatted project list or tool error
Possibly related issues
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 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: 3
🤖 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 `@internal/mcp/mcp_test.go`:
- Line 2257: Expand the tests around mem_find_project beyond tool registration
by invoking its handler with deterministic fixtures. Cover a successful search
result, missing query, invalid match_mode, no results, and store failures,
asserting each response and error behavior; keep the existing registration
assertion intact.
In `@internal/mcp/mcp.go`:
- Line 1199: Update the result formatting in the mem_find_project output around
TopRank so nearby computed ranks remain distinguishable and the returned value
preserves sufficient numeric precision; replace the fixed two-decimal formatting
in the fmt.Fprintf call with a precision-preserving representation while keeping
the existing project and match-count fields unchanged.
In `@internal/store/store_test.go`:
- Around line 8834-8906: Expand TestSearchProjects to cover empty queries,
any-mode matching, exclusion of deleted or blank-project observations,
zero/limited result bounds, query-error propagation, and deterministic TopRank
ordering. Use the existing test store and search-related helpers to create each
scenario, assert expected results and errors, and retain the current all-mode
grouping assertions.
🪄 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: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: acd92883-37d2-4a57-a6eb-7e1c4f617217
📒 Files selected for processing (4)
internal/mcp/mcp.gointernal/mcp/mcp_test.gointernal/store/store.gointernal/store/store_test.go
|
|
||
| allTools := []string{ | ||
| "mem_save", "mem_search", "mem_context", "mem_session_summary", | ||
| "mem_save", "mem_search", "mem_find_project", "mem_context", "mem_session_summary", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Test mem_find_project behavior, not only registration.
The new assertions only prove the tool is listed. Add deterministic handler tests for a successful search result, missing query, invalid match_mode, no results, and store failures.
As per path instructions, **/*_test.go must cover happy paths, error paths, and edge cases, and behavior changes without tests should be blocked.
🤖 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 `@internal/mcp/mcp_test.go` at line 2257, Expand the tests around
mem_find_project beyond tool registration by invoking its handler with
deterministic fixtures. Cover a successful search result, missing query, invalid
match_mode, no results, and store failures, asserting each response and error
behavior; keep the existing registration assertion intact.
Source: Path instructions
| var b strings.Builder | ||
| fmt.Fprintf(&b, "Found %d project(s) matching %q:\n", len(matches), query) | ||
| for _, m := range matches { | ||
| fmt.Fprintf(&b, "- %s (%d matches, rank: %.2f)\n", m.Project, m.MatchCount, m.TopRank) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate mcp.go =="
fd -a 'mcp\.go$' . | sed 's#^\./##'
echo "== context around line 1199 =="
if [ -f internal/mcp/mcp.go ]; then
nl -ba internal/mcp/mcp.go | sed -n '1160,1220p'
fi
echo "== TopRank definitions/usages =="
rg -n "TopRank|MatchCount|Project|Rank" internal/mcp/mcp.go | sed -n '1,220p'
echo "== behavioral probe for %.2f vs %.6g precision loss =="
python3 - <<'PY'
samples = [1.0000001, 1.0000002, 1.0000012, 1.0000013, 2.1234567, 2.1234568]
print("input -> %.2f -> %.6g")
for x in samples:
print(f"{x:.7f} {x:.2f} {x:.6g}")
print("collapsed distinct values for %.2f:", len(set(f"{x:.2f}" for x in samples)))
print("collapses for %.6g:", len(set(f"{x:.6g}" for x in samples)))
PYRepository: Gentleman-Programming/engram
Length of output: 296
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== mcp.go relevant definitions =="
rg -n "type .*struct|func .*TopRank|TopRank|MatchCount|Project" internal/mcp/mcp.go -C 3 | sed -n '1,260p'
echo "== compute/top-ranked value generation =="
rg -n "TopRank|MatchCount|Project" internal/mcp/mcp.go -C 5 | sed -n '1,260p'Repository: Gentleman-Programming/engram
Length of output: 22281
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== SearchProjects definitions/usages =="
rg -n "SearchProjects|SearchProject|TopRank|MatchCount" . -C 4 --include='*.go' | sed -n '1,260p'
echo "== Precision loss examples =="
python3 - <<'PY'
samples = [1.0000001, 1.0000002, 1.0000012, 1.0000013, 2.1234567, 2.1234568, -0.001, -0.0015]
fmt2 = [f"{x:.2f}" for x in samples]
fmt6g = [f"{x:.6g}" for x in samples]
print("input -> %.2f -> %.6g")
for x, a, b in zip(samples, fmt2, fmt6g):
print(f"{x:.8f} {a} {b}")
print("distinct %.2f:", len(set(fmt2)), f"{', '.join(set(fmt2))}" if len(set(fmt2)) < 8 else "")
print("distinct %.6g:", len(set(fmt6g)))
PYRepository: Gentleman-Programming/engram
Length of output: 648
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== SearchProjects definitions/usages =="
rg -n "SearchProjects|SearchProject|TopRank|MatchCount" . --glob '*.go' -C 4 | sed -n '1,280p'
echo "== Precision loss examples =="
python3 - <<'PY'
samples = [1.0000001, 1.0000002, 1.0000012, 1.0000013, 2.1234567, 2.1234568, -0.001, -0.0015]
fmt2 = [f"{x:.2f}" for x in samples]
fmt6g = [f"{x:.6g}" for x in samples]
print("input -> %.2f -> %.6g")
for x, a, b in zip(samples, fmt2, fmt6g):
print(f"{x:.8f} {a} {b}")
print("distinct %.2f:", len(set(fmt2)))
print("distinct %.6g:", len(set(fmt6g)))
PYRepository: Gentleman-Programming/engram
Length of output: 5421
Preserve usable TopRank precision.
%.2f can collapse nearby computed ranks to the same displayed value, so mem_find_project does not return the actual TopRank. Use a precision-preserving format or structured numeric metadata.
Proposed fix
- fmt.Fprintf(&b, "- %s (%d matches, rank: %.2f)\n", m.Project, m.MatchCount, m.TopRank)
+ fmt.Fprintf(&b, "- %s (%d matches, rank: %.6g)\n", m.Project, m.MatchCount, m.TopRank)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fmt.Fprintf(&b, "- %s (%d matches, rank: %.2f)\n", m.Project, m.MatchCount, m.TopRank) | |
| fmt.Fprintf(&b, "- %s (%d matches, rank: %.6g)\n", m.Project, m.MatchCount, m.TopRank) |
🤖 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 `@internal/mcp/mcp.go` at line 1199, Update the result formatting in the
mem_find_project output around TopRank so nearby computed ranks remain
distinguishable and the returned value preserves sufficient numeric precision;
replace the fixed two-decimal formatting in the fmt.Fprintf call with a
precision-preserving representation while keeping the existing project and
match-count fields unchanged.
| func TestSearchProjects(t *testing.T) { | ||
| st := newTestStore(t) | ||
|
|
||
| st.CreateSession("s1", "project-a", "/tmp/a") | ||
| st.CreateSession("s2", "project-b", "/tmp/b") | ||
| st.CreateSession("s3", "project-c", "/tmp/c") | ||
|
|
||
| // Seed 3 observations for project A | ||
| _, err := st.AddObservation(AddObservationParams{ | ||
| SessionID: "s1", Type: "bugfix", Title: "Fix auth token expiration", | ||
| Content: "The auth middleware was dropping tokens", Project: "project-a", | ||
| }) | ||
| if err != nil { t.Fatal(err) } | ||
| _, err = st.AddObservation(AddObservationParams{ | ||
| SessionID: "s1", Type: "bugfix", Title: "Auth token validation", | ||
| Content: "Middleware should validate auth tokens", Project: "project-a", | ||
| }) | ||
| if err != nil { t.Fatal(err) } | ||
| _, err = st.AddObservation(AddObservationParams{ | ||
| SessionID: "s1", Type: "bugfix", Title: "Minor fix", | ||
| Content: "Just a minor auth fix in the middleware", Project: "project-a", | ||
| }) | ||
| if err != nil { t.Fatal(err) } | ||
|
|
||
| // Seed 1 highly relevant observation for project B | ||
| _, err = st.AddObservation(AddObservationParams{ | ||
| SessionID: "s2", Type: "bugfix", Title: "Auth middleware completely rewritten", | ||
| Content: "Auth middleware auth middleware auth middleware tokens", Project: "project-b", | ||
| }) | ||
| if err != nil { t.Fatal(err) } | ||
|
|
||
| // Seed an irrelevant observation for project C | ||
| _, err = st.AddObservation(AddObservationParams{ | ||
| SessionID: "s3", Type: "feature", Title: "Database migration", | ||
| Content: "Added new tables", Project: "project-c", | ||
| }) | ||
| if err != nil { t.Fatal(err) } | ||
|
|
||
| // Force FTS sync if async (test setup normally does this, but just in case) | ||
| // We'll just search directly. | ||
|
|
||
| matches, err := st.SearchProjects("auth middleware", "all", 10) | ||
| if err != nil { | ||
| t.Fatalf("SearchProjects failed: %v", err) | ||
| } | ||
|
|
||
| if len(matches) != 2 { | ||
| t.Fatalf("Expected 2 projects, got %d: %+v", len(matches), matches) | ||
| } | ||
|
|
||
| // project-a should have 3 matches. project-b should have 1 match. | ||
| // project-b has more occurrences of the terms, so its top_rank might be better (more negative). | ||
| // Let's assert on the project names and counts. | ||
| hasA := false | ||
| hasB := false | ||
| for _, m := range matches { | ||
| if m.Project == "project-a" { | ||
| hasA = true | ||
| if m.MatchCount != 3 { | ||
| t.Errorf("project-a: expected 3 matches, got %d", m.MatchCount) | ||
| } | ||
| } | ||
| if m.Project == "project-b" { | ||
| hasB = true | ||
| if m.MatchCount != 1 { | ||
| t.Errorf("project-b: expected 1 match, got %d", m.MatchCount) | ||
| } | ||
| } | ||
| } | ||
| if !hasA || !hasB { | ||
| t.Errorf("Missing expected projects in results: %+v", matches) | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Cover SearchProjects error and edge paths.
This only tests normal all-mode grouping. Add deterministic coverage for empty input, any mode, deleted/blank-project exclusion, limit bounds, query failures, and TopRank ordering.
As per path instructions, **/*_test.go must cover happy paths, error paths, and edge cases, and behavior changes without tests should be blocked.
🤖 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 `@internal/store/store_test.go` around lines 8834 - 8906, Expand
TestSearchProjects to cover empty queries, any-mode matching, exclusion of
deleted or blank-project observations, zero/limited result bounds, query-error
propagation, and deterministic TopRank ordering. Use the existing test store and
search-related helpers to create each scenario, assert expected results and
errors, and retain the current all-mode grouping assertions.
Source: Path instructions
Draft PR for #304. Dropped the preview field to keep the diff tiny. It only returns
Project, MatchCount, and TopRank (using FTS5 bm25). All MCP tool count tests updated.
Summary by CodeRabbit
New Features
Bug Fixes