Consolidate tilde-aware path canonicalization for project slugging and path auto-index resolution. Add a graph-backed regression test for search_graph(project='~/repo') using an isolated fake HOME so explicit repo-directory params resolve to the same slug that indexing creates.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Replace direct environment and filesystem cleanup calls in MCP, CLI, input validation, and HTTP link tests with existing cbm compatibility wrappers. Also updates the MCP publish metadata fixture to use the current three-file containment fallback instead of the now-exact new-folder path.
Validation: git diff --check; bash scripts/check-source-safety.sh; make -j8 -f Makefile.cbm build/c/test-runner; CBM_ONLY_SUITE=mcp ./build/c/test-runner; CBM_ONLY_SUITE=cli ./build/c/test-runner; CBM_ONLY_SUITE=input_validation ./build/c/test-runner; CBM_ONLY_SUITE=httplink ./build/c/test-runner.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Use the bounded discover count for startup, first-use, and explicit path autoindexing so CBM_AUTO_INDEX_LIMIT cannot be bypassed by a later MCP handler path.
Move sync first-use indexing into resolve_project_store via a shared MCP-local helper, leaving REQUIRE_STORE responsible for joining an in-flight startup index and reporting missing stores. This avoids duplicate indexing attempts and duplicate over-limit warnings while preserving structured stderr logging.
Add an input-validation regression for over-limit explicit path projects and clarify the config help text to say the limit counts indexable files.
Verified: build/c/test-runner; build/c/codebase-memory-mcp; CBM_ONLY_SUITE=input_validation (44), mcp (113), discover (83), cli (104).
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Share the MCP auto-index enablement decision across startup, synchronous first-use indexing, and explicit path indexing. Production servers still attach config and keep the registry default auto_index=true, while embedded/no-config servers remain manual unless CBM_AUTO_INDEX explicitly opts in.
This prevents search/trace/resource paths from bypassing auto_index=false after startup and avoids accidental full worktree indexing in test or embedded contexts. The path auto-index regression test now opts in explicitly and restores CBM_AUTO_INDEX afterward.
Validation:
- make -f Makefile.cbm build/c/test-runner
- CBM_ONLY_SUITE=mcp build/c/test-runner: 113 passed
- CBM_ONLY_SUITE=tool_consolidation build/c/test-runner: 94 passed
- CBM_ONLY_SUITE=input_validation build/c/test-runner: 43 passed
- git diff --check
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Use trace_path as the single canonical call-tracing tool across classic and streamlined tool lists, with the streamlined list reusing the canonical schema to prevent drift. Revealing hidden tools now makes the classic-only tools discoverable through tools/list for clients that only call advertised tools.
Extend trace_path with the existing optional filters and modes exposed by the fork, validate cheap arguments before project resolution so malformed calls cannot trigger auto-indexing, and handle string-array allocation failures explicitly. Add focused coverage for schema parity, hidden-tool reveal, invalid trace modes, test filtering, risk labels, and exclude filters.
Verified with: make -f Makefile.cbm cbm; make -f Makefile.cbm build/c/test-runner; CBM_ONLY_SUITE=mcp build/c/test-runner; CBM_ONLY_SUITE=tool_consolidation build/c/test-runner; CBM_ONLY_SUITE=input_validation build/c/test-runner; CBM_ONLY_SUITE=token_reduction build/c/test-runner; git diff --check.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Use trace_path as the single public call-tracing tool across classic and streamlined MCP surfaces, while exposing existing semantic_query and search_code mode parameters in the advertised schemas. Keep the streamlined surface compact, but ensure the hidden-tool reveal covers the full classic capability set without trace aliases.
Persist PageRank, LinkRank, and node_degree rows with direct VALUES inserts instead of per-row SELECT probes. The node/edge project is loaded once with the graph rows so full-scope dependency attribution stays identical to the previous behavior.
Add focused coverage for dependency project attribution, streamlined parameter contracts, and targeted suite allowlist entries so these checks cannot silently run zero tests.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
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>