Replace the consolidated search_code_graph mega-tool — which merged graph
search + source grep + cypher behind two nearly disjoint parameter
families — with the three existing focused tools as the DEFAULT surface:
search_graph (graph/structural), search_code (source grep), query_graph
(cypher). These are drawn from TOOLS[] (single schema source, DRY);
trace_call_path + get_code remain on the default surface. The
search_code_graph schema and its thin dispatch router are deleted; the
underlying handlers (handle_search_graph/search_code/query_graph) stay.
The _hidden_tools progressive-disclosure hint now lists 11 hidden tools
(was 14). Repointed three user-facing hint strings (handle_trace_call_path
missing-name and function-not-found; two unknown-tool path hints) from the
deleted tool to search_graph.
Tests updated to the new 5-tool surface:
- tests/test_mcp.c: mcp_tools_list + server_handle_tools_list assert the
5 default tools present and search_code_graph absent.
- tests/test_tool_consolidation.c: renamed streamlined_mode_shows_3_tools
-> ..._5_default_tools; repurposed the two search_code_graph dispatch
tests into search_graph_dispatch / query_graph_dispatch (query_graph
handler accepts both cypher= and query=); revised
schema_compact_documented_as_graph_only (the graph-vs-source compact
collision it warned about no longer exists post-split).
- tests/test_input_validation.c: remapped 16 dispatch calls by mode --
cypher -> query_graph, source-grep -> search_code, graph/structural ->
search_graph.
- tests/test_depindex.c: tool_index_dependencies_listed asserts
search_graph (was search_code_graph).
- src/cli/cli.c: six config-description strings (search_limit, tool_mode,
context_injection, compact, default_sort_by, default_include_dependencies).
Back-compat: blast radius is in-repo only (0 references in Go internal/cmd,
docs, or the CGO wrapper). External callers of search_code_graph must
switch to search_graph / search_code / query_graph, which are strictly
more capable. query_graph keeps its query= backward-compat alias.
Context -- of the three section-4 fixes in the merge plan, only section 4b
was a real change: section 4c (function<->class edges) was already
implemented (commit b950bf2 added the missing MEMBER_OF reverse-edge
coverage), and section 4a (passive resources) was already mitigated by
inject_context_once plus the get_graph_schema/get_architecture/index_status
tools -- both verified against code, not assumed.
Gate: 5981 passed / 2 failed (the 2 are the deferred B1 store-layer
incremental failures, unrelated to section 4b).
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Previous behavior: when project= was a full directory path (e.g.
"/path/to/react-grid-layout") and that path was NOT under srv->session_root
(e.g. it was in the parent project's .gitignore), codebase-memory returned
"project not found" instead of indexing the requested directory. The auto-index
guard only checked srv->session_root, so .gitignore-excluded sub-repos with
their own .git (passed as absolute paths) were never indexed on first query.
What changed:
- src/mcp/mcp.c: in resolve_project_store(), save the resolved filesystem path
before expand_project_param() frees raw_project (expand_tilde + realpath into
_raw_path local variable)
- src/mcp/mcp.c: add path-based auto-index fallback after the session_root block:
when store is still NULL and _raw_path is an accessible directory, run
cbm_pipeline_new(_raw_path, ...) to index it, then resolve the store by slug
- tests/test_input_validation.c: add path_project_auto_indexes_separate_directory
test -- creates two temp dirs, queries the first to establish session_root,
then queries the second (separate path) and asserts the sentinel function is found
Result: 2917 passed (was 2916); path-based separate-repo queries now auto-index
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Adds boolean config key context_injection (default true, env CBM_CONTEXT_INJECTION)
that controls whether inject_context_once embeds the _context header in the first
tool response.
WHAT _context contains: node/edge counts, all node labels with counts, edge types
with counts, PageRank stats, and detected ecosystem (Go/Python/Rust/etc.).
WHEN TO DISABLE (context_injection=false):
- Scripted/programmatic use: extra JSON adds noise to parsed output
- CI pipelines: token cost is metered, model needs no schema context
- Model has explicit codebase instructions via system prompt
- Benchmarking: removes schema-query overhead from latency measurements
WHEN TO KEEP ENABLED (default):
- Interactive AI sessions benefit from automatic situational awareness
- Saves 2-3 round-trips otherwise needed for get_architecture/get_graph_schema
Usage:
codebase-memory-mcp config set context_injection false # per-install
CBM_CONTEXT_INJECTION=false codebase-memory-mcp # per-session
Implementation:
- Config check placed BEFORE setting context_injected flag so toggling
mid-session works (re-enable without server restart)
- session_project is still always included regardless of this setting
- cbm_config_get_bool reads CBM_CONTEXT_INJECTION env var automatically
via cbm_config_get_effective (the standard env-override path)
TDD: config_context_injection_disabled, config_context_injection_enabled_by_default
Tests: 2274 passed, 0 failed
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
project_slug_from_path previously detected "~/..." as a path but passed it
directly to realpath(3), which doesn't expand tilde (that's a shell feature).
The slug was then derived from the literal "~" string, giving a wrong result.
Fix: extract expand_tilde() helper that expands "~" and "~/..." to
$HOME + rest using getenv("HOME"). "~user/..." is intentionally left
unexpanded (would require getpwnam — not worth the complexity for this use).
Also fix project_is_path() to detect bare "." (current directory) as a
path, in addition to the already-handled "./" prefix.
The three helpers are now:
project_is_path(s) — pure predicate, no heap allocation
expand_tilde(s) — tilde expansion, heap result or NULL
project_slug_from_path(s) — full path→slug pipeline: tilde→realpath→slug
TDD: source_search_tilde_project_expands — writes a file under $HOME (via
tmpdir), passes project="~/..." and verifies grep returns matches.
Skips gracefully when tmpdir is not under $HOME.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
DRY refactor:
Extract project_is_path() and project_slug_from_path() helpers (mcp.c:1239)
that encode the filesystem-path detection and realpath+cbm_project_name_from_path
conversion in one place. Previously this logic was duplicated between
expand_project_param Rule 0 and the get_project_root path-arg fix.
Both sites now call the shared helpers.
Documents known limitations: "." not detected (use "./"), "~" detected
but realpath(3) doesn't expand tilde (use absolute path).
Bug coverage: handle_detect_changes and handle_manage_adr also call
get_project_root with a raw project arg and were silently broken for
path-style args. They are now fixed as a side-effect of the get_project_root
fix (no code change needed — they already go through get_project_root).
New TDD tests (5):
detect_changes_slug_project_finds_root — detect_changes doesn't error on
valid slug project (regression for get_project_root slug path)
manage_adr_slug_project_finds_root — same for manage_adr
source_search_no_project_falls_back_to_session — search_in="source" with
no project arg uses session_project (set via cbm_mcp_server_set_session_project)
source_search_via_search_in_param — upgraded to write a real source file
and assert matches array returned (stronger than previous dispatch-only check)
source_search_path_project_normalizes_to_slug — slug project returns
matches, not "project not found"
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
get_project_root(srv, project) passed the raw project string directly to
resolve_store() and cbm_store_get_project(). When project="/Users/.../repo"
(a path, not a slug), project_db_path() embedded slashes in the .db filename,
producing an invalid path like ~/.cache/.../Users/athundt/.../repo.db.
Fix: get_project_root now normalizes project args the same way
expand_project_param Rule 0 does — if the arg looks like a filesystem path
(starts with /, ~, ./, or contains /), use realpath() + cbm_project_name_from_path()
to convert it to a slug before looking up in the DB.
Also: get_project_root now falls back to srv->session_project when project
is NULL, so search_in="source" works without an explicit project arg when
the session project is already detected (e.g. after initialize).
Two new tests:
source_search_via_search_in_param — upgraded: writes a real file in tmpdir
and verifies grep returns matches (not just "dispatch fired")
source_search_path_project_normalizes_to_slug — verifies slug project arg
returns matches array, not "project not found"
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
store.h:108 — comment typo: "AND" → "OR" for pattern OR-search field
mcp.c:1754 — indentation bug in mode-error free() block: free(file_pattern)
had 1-space indent instead of 8 and a spurious blank line
mcp.c:2422 — OOM leak in QN lookup: when cbm_store_find_node_by_qn succeeds
but calloc fails, qn_tmp heap fields (name/project/label/etc.) leaked.
Fix: free_node_contents(&qn_tmp) in else branch. Added forward declaration
of free_node_contents before handle_trace_call_path so it resolves.
mcp.c:2464 — "function not found" hint was wrong when only qualified_name
passed. Old hint said "use name_pattern" but user passed a QN. New hint
says "use pattern= to find the correct qualified_name".
mcp.c:472 — search_in schema description now documents the response format
difference: graph mode returns {total,results:[{qualified_name,...}]},
source mode returns {matches:[{file,line,content}],count}.
tests: 4 new TDD tests covering:
- pattern glob wildcard auto-convert (*foo* → .*foo.*)
- pattern invalid regex returns error with hint
- trace: qualified_name takes priority over function_name when both given
- trace: QN-not-found returns specific hint mentioning qualified_name
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
test_tool_consolidation:539 — resource_client_never_gets_context_across_multiple_calls
- Cold-start fix (resolve_project_store) now opens the real indexed DB, so
search results include node names containing "_context" as a substring
(e.g. "inject_context_once", "gets_context_across_multiple_calls").
- strstr(r, "_context") was a false positive: matched substrings in node
names, not the injected "_context" JSON key.
- Fix: check for "\"_context\":" (quoted JSON key + colon) which only
matches the actual injected context object, not substrings in results.
- Same fix applied to legacy_client_gets_context_only_on_first_call test.
test_input_validation:695 — trace_accepts_qualified_name_param
- QN lookup calls cbm_store_find_node_by_qn(store, NULL, qn, &node).
SQL "WHERE project = NULL" never matches (SQL null semantics).
- Test setup uses cbm_mcp_server_set_project("validation-test") which sets
current_project but NOT session_project, so cold-start project fix does
not fire, leaving pe.value = NULL.
- Fix: pass project="validation-test" in test args so pe.value is set.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Three bugs fixed in MCP tool validation layer:
1. sort_by: validator only checked 3 of 5 valid values (relevance/name/degree).
Added missing strcmp for 'calls' and 'linkrank' at mcp.c:1625-1627.
2. name_pattern/qn_pattern: glob wildcards (*tool*) rejected as invalid regex.
On regex compile failure, glob_to_regex() converts bare * -> .* and ? -> .
before retrying. Valid regex patterns have zero overhead (no conversion).
Applied to both name_pattern and qn_pattern validation blocks.
3. trace_call_path: exact case-sensitive name match too strict.
On node_count==0, fallback to cbm_store_search with case_sensitive=false
and min/max_degree=-1 (no degree filter). Uses found result's project for
the re-query to handle NULL project correctly. Zero overhead on exact hit.
TDD: 10 new tests in test_input_validation.c covering all three bugs:
- f6_sort_by_calls_accepted, f6_sort_by_linkrank_accepted
- f9_glob_star_autoconverted, f9_glob_question_autoconverted,
f9_valid_regex_still_works, f9_truly_invalid_pattern_still_errors,
f9_qn_pattern_glob_autoconverted
- trace_case_mismatch_finds_via_fallback, trace_exact_match_still_works,
trace_truly_missing_still_errors
All 2252 tests pass.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>