Commit Graph
1203 Commits
Author SHA1 Message Date
Andrew Hundt e9f3ffa88b fix(mcp): reject undeclared tool arguments
Previously, tools/list left top-level input objects open and cbm_mcp_handle_tool dispatched raw JSON without checking property names. A search_code call containing repo_path could therefore ignore the caller's intended project and return a plausible empty result. The canonical schemas also omitted the implemented index_repository.format and index_status.verbose inputs.

Add mcp_add_tool_input_schema to close every classic, streamlined pre-reveal, streamlined post-reveal, and _hidden_tools input schema. Add mcp_validate_tool_argument_keys before dispatch so direct, stdio, HTTP, and raw-JSON CLI calls return an isError:true result naming the unknown key and canonical supported keys. Keep the public schema registry as the property authority; output schemas remain open.

Preserve only verified, unambiguous raw-JSON compatibility: string-valued project_name/project_id/projectName map to project; query_graph.cypher maps to query per 32a3820c; search_code search_in=source remains from the 3d133a3c to 39539eff split. Other search_in values name search_graph, and ignored query_graph label input is rejected.

MCP Tool Execution Error behavior follows https://modelcontextprotocol.io/specification/2025-11-25/server/tools. Closed no-argument schemas follow https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1613.

Verification: ASan/UBSan MCP 267/267; TSan MCP 267/267; CLI 232/232; tool consolidation 113/113; HTTP 44/44 with one platform skip; production smoke 35/35; four focused macOS leaks checks each 0 leaks/0 bytes; scripts/check-source-safety.sh; optimized real-store CLI query_graph and typo-rejection dogfood.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 21:46:24 -04:00
Andrew Hundt fced498a32 fix(cypher): preserve null and entity identity in aggregates
Previous behavior:
- OPTIONAL MATCH null bindings were encoded as empty strings, so count(expr), count(DISTINCT expr), and collect(expr) counted absent values.
- WHERE and coalesce treated a valid JSON empty string as null.
- WITH aggregation grouped distinct same-named nodes together by display name.
- edge_prop used process-global rotating scratch buffers across concurrent requests.

Changes:
- src/cypher/cypher.c adds presence-aware json_extract_prop_ex, node_prop_ex, edge_prop_ex, and binding_get_virtual_ex lookups plus one null bit per bounded binding slot.
- with_agg_accumulate and ret_agg_accumulate skip null expressions while count(*) retains row semantics; WHERE and coalesce preserve valid empty strings.
- group_key_append keys nodes and edges by canonical store ID and length-prefixes scalar values, preventing name and delimiter collisions.
- edge projection scratch storage is thread-local; aggregate null vectors follow existing group cleanup ownership.
- tests/test_cypher.c covers OPTIONAL null aggregates, post-WITH counts, numeric aggregate defaults, empty/null/missing properties, predicates, coalesce, and same-name node grouping.

Complexity and lifecycle:
- Property presence adds no per-row allocation and aggregation retains its existing row/group traversal order.
- Binding storage grows by CYP_MAX_VARS booleans; edge scratch remains fixed-size per thread.
- The one per-group null vector is released with the existing aggregate state.

References:
- https://s3.amazonaws.com/artifacts.opencypher.org/openCypher9.pdf
- https://neo4j.com/docs/cypher-manual/25/functions/aggregating/
- https://neo4j.com/docs/cypher-manual/current/values-and-types/working-with-null/

Verification:
- CBM_ONLY_SUITE=cypher build/c/test-runner: 187 passed under ASan/UBSan.
- CBM_ONLY_SUITE=mcp build/c/test-runner: 262 passed under ASan/UBSan.
- bash scripts/check-source-safety.sh: passed.
- git diff --cached --check: passed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 20:47:31 -04:00
Andrew Hundt 277b8f55c2 fix(pipeline): reextract ObjectScript consumers after include changes
src/pipeline/pipeline_incremental.c now detects changed, deleted, and renamed .inc macro context and expands the affected set to discovered ObjectScript files before dirty-ledger and publication routing. This matches cbm_build_macro_table_from_files, whose repository-wide macro table has no per-include consumer map, while leaving other-language increments on the existing O(changed-files) path and reusing exact-delta bounds plus containment fallback.

tests/test_pipeline.c extends the cleanup-safe ObjectScript oracle to prove changed and deleted includes replace or remove stale CALLS edges and remain graph-equal to a fresh rebuild. Verification: pipeline ASan/UBSan 384/384; focused changed/deleted include TSan passed; focused macOS leaks reported 0 leaks for 0 total leaked bytes; scripts/check-source-safety.sh passed; git diff --check passed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 19:22:31 -04:00
Andrew Hundt 8f0f6fc834 fix(pipeline): retain ObjectScript macros during incremental extraction
Previously, src/pipeline/pass_definitions.c populated the sequential result cache without ctx->macro_table, so pass_calls reused unexpanded $$$ calls. Exact incremental runs also extracted only the changed .cls slice without unchanged .inc definitions, dropping the CALLS edge that a clean rebuild produced.

Route sequential extraction through cbm_extract_file_with_options_ex, declare cbm_build_macro_table_from_files once in pipeline_internal.h, and let parallel extraction borrow an immutable caller-owned table. pipeline_incremental.c now builds repository macro context only when the changed slice contains ObjectScript, releases it on every return path, and otherwise preserves changed-file setup complexity.

tests/test_extraction.c verifies the expanded callee remains scoped to MyApp.Caller.Run. tests/test_pipeline.c verifies full resolution, exact incremental publication, CALLS-edge retention, clean-rebuild graph equality, and default fixture cleanup.

Verification: ASan/UBSan pipeline 382/382, extraction 313/313, parallel 36/36; focused TSan passed; macOS leaks reported 0 leaks for 0 total leaked bytes; bash scripts/check-source-safety.sh; git diff --check.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 19:04:23 -04:00
Andrew Hundt 0a988e0fa4 fix(cypher): apply row caps after exact selection
Previous behavior: scan_pattern_nodes() limited unlabeled candidates to max_rows * 10 before WHERE evaluation; execute_return_simple() projected max_rows bindings before ORDER BY; and an explicit Cypher LIMIT could bypass max_rows. An exact named-function lookup beyond the seed prefix returned zero rows, while a descending order query ranked only the prefix.

Push mandatory, non-negated file_path CONTAINS conjuncts into SQLite with literal instr() semantics, while retaining the full C evaluator as semantic authority. Restrict bounded initial scans to prefix-preserving query shapes; predicates, relationships, aggregation, DISTINCT, ORDER BY, SKIP, and later stages evaluate the complete eligible set after exact SQL reduction.

Make max_rows and query_max_rows authoritative result ceilings: Cypher LIMIT may lower but not bypass them. Align the shared MCP definition and CLI config registry so streamlined, classic, and CLI surfaces describe the same contract.

TDD covers a 65-node exact lookup beyond the former 50-node prefix, global ORDER BY selection, unsafe OR pushdown exclusion, literal percent/underscore CONTAINS behavior, configured cap enforcement, and streamlined/classic schema parity. Verification: 531 Cypher/companion and 612 MCP/schema/companion ASan/UBSan tests passed.

Retained optimized large-graph validation passed graph and quality gates: query_graph 2.367 ms, exact incremental indexing 363 ms at 71 MB peak, full indexing 6494 ms at 1332 MB peak, and 17.89x full-over-incremental speedup. The benchmark artifact remains untracked.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 16:38:27 -04:00
Andrew Hundt 1bb6fa6b9e perf(store): skip active overlay CTE on clean graphs
Previous behavior: cbm_store_get_overlay_node_view_summary() materialized active_node_candidates and applied ROW_NUMBER ownership ranking for every read, even when overlay_generations contained no overlay_ready row. On the retained 15,664-node graph this added about 120 ms to search_graph, search_code, route, and other MCP reads.

Use the existing indexed cbm_store_count_overlay_generations() authority to gate the active view. When no ready generation exists, reuse cbm_store_count_nodes() for the exact canonical and total counts; projects with ready overlays retain the complete tombstone and ownership CTE.

Add an sqlite3_trace_v2 regression guard in tests/test_store_nodes.c that rejects active_node_candidates on the clean path while requiring the canonical count query. The same test continues to verify exact counts for successive ready overlays.

Verification: CBM_ONLY_TEST=overlay_node_view_summary make -f Makefile.cbm test-focused TEST_SUITES=store_nodes; ASan/UBSan store_nodes, mcp, and tool_consolidation suites; optimized self-dogfood MCP smoke with search_graph median reduced from 120.149 ms to 3.825 ms and search_code from 131.946 ms to 10.436 ms while the exact incremental gate passed.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 15:48:01 -04:00
Andrew Hundt 45c11176c9 merge(upstream): retain streamlined APIs with indexed impact analysis
Merge upstream/main 7d6cdb23 into api-consolidation 0fcb7e10 as a tested capability superset.

Retain the branch's streamlined, classic, and hidden-reveal MCP surfaces; JSON/TOON output; read-only Cypher execution; semantic search; dependency indexing; PageRank; incremental indexing; and modular harness installer. Integrate upstream ObjectScript routine/UDL extraction, macro and return-type call resolution, Python alias and C++ header resolution, atomic SQLite publication, SCC/cycle reporting, Cypher deadlines, and bounded multi-source impact traversal.

Fix merge-specific defects in handle_detect_changes project validation, cbm_pipeline_release_objectscript_tables ownership, no-sanitizer test defines, focused-suite registration, portable config publication, and installed streamlined guidance. The guidance keeps auto-index/get_code/query_graph discovery while adding coverage tiers and removes the stale fixed row ceiling.

Verification: ASan/UBSan full runner 7,155 passed with 27 sandbox-loopback cases separately passing in the escalated HTTP suite (44 passed, 1 Windows-only skip); TSan 449 passed; incremental leak gate 514 passed with 0 leaks; MCP 261; CLI 232; extraction 313; tool consolidation 113; Cypher 179; SQLite writer 16; configuration editors 127; integration 28; language contracts 38; PageRank 60. scripts/check-source-safety.sh and git diff --cached --check pass.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 14:09:36 -04:00
Andrew Hundt 77c38ce863 fix(cypher): preserve qualified names across aggregate WITH
Previous behavior:
- with_add_vbinding_var stored a WITH alias in cbm_node_t.qualified_name to share allocation ownership.
- RETURN g.qualified_name after WITH g, COUNT(*) therefore returned the alias literal g instead of the indexed qualified name.

Changes:
- Track heap-owned variable aliases with binding_t.var_name_owned.
- Copy and free aliases independently from cbm_node_t fields.
- Leave aggregate node stubs eligible for the existing id-based indexed property fetch.

Tests:
- cypher_exec_with_node_groupvar_prop reproduces g versus test.ValidateOrder and now passes.
- build/c/test-runner cypher: 519 passed.
- make -f Makefile.cbm test: 6865 passed, 1 skipped under ASan/UBSan.
- macOS leaks-at-exit exact regression: 0 leaks, 0 leaked bytes.
- Optimized -O2 query_graph returns qualified names in both TOON and JSON.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 08:02:40 -04:00
Andrew Hundt 8849410e2d fix(cypher): execute post-WITH stages without truncating fan-out
Previous behavior:
- src/cypher/cypher.c parsed MATCH after WITH without executing it against projected bindings.
- OPTIONAL MATCH predicates were applied after null extension, multi-key ORDER BY was not represented, and relationship expansion silently capped each seed at ten paths.

Changes:
- Add next_stage ownership and iterative execution/free traversal for MATCH and OPTIONAL MATCH after WITH, including UNION transfer and active-overlay checks.
- Apply OPTIONAL predicates to candidate paths before null extension.
- Represent up to 32 projected ORDER BY keys and use stable O(rows log rows) merge sorts with O(rows) pointer/index scratch.
- Replace fixed-factor node and relationship buffers with ceiling-bounded geometric growth shared by canonical and active-overlay paths.
- Keep classic and streamlined query_graph descriptions aligned with the implemented grammar.

Tests:
- make -f Makefile.cbm test: 6865 passed, 1 skipped under ASan/UBSan.
- build/c/test-runner cypher: 519 passed.
- build/c/test-runner mcp tool_consolidation: 588 passed.
- scripts/check-source-safety.sh and scripts/check-nolint-whitelist.sh pass.

Semantics checked against Neo4j Cypher documentation:
https://neo4j.com/docs/cypher-manual/current/clauses/with/
https://neo4j.com/docs/cypher-manual/current/clauses/match/
https://neo4j.com/docs/cypher-manual/current/clauses/order-by/

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 07:34:54 -04:00
Andrew Hundt 9a7d83275b feat(mcp): self-healing query_graph hints, one notification authority, live tools/list schema
Response-format consolidation: add cbm_mcp_response_format/
cbm_mcp_invalid_response_format so every response-generating handler
reads the format argument or default_response_format config through one
function instead of duplicating the enum-mapping logic, and migrate
query_graph's legacy-JSON detection to the canonical
cbm_mcp_output_format_t enum. Fix the regression this surfaced: TOON-path
_context/session_project delivery (toon_append_context_once) was
serializing the one-shot context object as a raw escaped JSON string
inside the TOON stream instead of native TOON fields, silently dropping
it for any TOON-consuming client. Add toon_append_context_model plus its
scalar/table helpers to map the same format-neutral context model
inject_context_once already builds into native TOON output.

Self-healing zero-row hints: query_graph_no_rows_hint reparses the
already-executed query on the zero-row path only, walks MATCH/OPTIONAL
MATCH patterns and EXISTS predicates in WHERE and post-WITH-WHERE
clauses (hint_walk_expr_exists_types/hint_walk_where_exists_types), and
probes each referenced label/edge type against the store via
cbm_store_schema_label_observed/cbm_store_schema_type_observed
(schema_probe_one_row: fail-open on any non-SQLITE_DONE outcome, so a
probe error never produces a false accusation). Unobserved names are
named in the response; a bounded hint_seen_t/hint_seen_add set (capped
at CBM_STORE_SCHEMA_HINT_VOCAB_LIMIT) dedups names repeated across
patterns/UNION branches so "Klass, Klass." can't happen and avoids
re-probing a name already resolved. The vocabulary summary and the
generic zero-row fallback no longer claim WHERE-clause predicates were
verified, since only labels/edge types are ever probed — only the
labels/edge types claim is made.

One invalidation authority: every graph-mutation path now calls
the single cbm_mcp_server_notify_index_published (flags-only,
any-thread-safe) instead of ad hoc local staleness bookkeeping —
sync auto-index, in-process index_repository, in-process
autoindex_thread, overlay compaction (gated on compacted>0),
index_dependencies. handle_delete_project's notify is gated on the
mutation actually succeeding (!is_error): a delete of a nonexistent
project is a no-op and must not stale the tools/list description cache
for nothing. The request-thread-only drain
(mcp_drain_tools_list_changed) is factored once and shared by both
transports (line-delimited and Content-Length-framed), gated so a
notification published before any tools/list has ever been served is
suppressed rather than sent for a client that never asked.

Shared overlay-aware schema-view selector (mcp_get_current_schema/
mcp_overlay_view_ready) ends the schema/overlay-readiness dance
previously triplicated across handle_get_graph_schema,
build_resource_schema, and the query path, so query_graph's advertised
vocabulary can never diverge from the view a query actually runs
against. The selector takes an mcp_cypher_vocabulary_t level instead of
a bool: MCP_CYPHER_MATCH_VOCABULARY (labels/edge types/observed
patterns with counts — enough to compose executable MATCH clauses;
never parses property JSON) vs MCP_CYPHER_FULL_QUERY_VOCABULARY (adds
per-label/type property keys for WHERE/RETURN authorship; O(total
property rows) json_each discovery, so only the docstring cache and
get_graph_schema request it).

Route the two remaining canonical-only cbm_store_get_schema callers
through the selector at MATCH_VOCABULARY level: inject_context_once
(first-response _context previously paid the full property-key scan for
counts it never rendered, and could advertise canonical labels a ready
overlay had tombstoned) and build_resource_architecture (rendered only
rel_patterns yet fetched full property keys, canonical-only). The
architecture resource's mixed-read-model disclosure moves
relationship_patterns into the overlay-aware list accordingly.

build_query_graph_tool_description resolves the project's real
store via resolve_store(srv, project) instead of trusting srv->store
as-is: on the very first tools/list of a fresh session for an
already-indexed project, srv->store was still the empty default
in-memory store (maybe_auto_index detects "already indexed" and skips
reindexing without ever opening it), so the schema section rendered
silently empty until some other tool call happened to resolve it first.
The project name is snapshotted into a local buffer before the
resolve_store call rather than passed as a live alias to
srv->current_project, because resolve_store can call
reap_stale_store internally, which frees srv->current_project when a
deferred invalidation is pending — aliasing it produced a
heap-use-after-free (caught by the ASan full-suite run, confirmed fixed
by rerun).

Docstring clarity for newcomers: the live schema section now embeds its
notation legend in the section header ("Labels name{extra property
keys}[count]:") so the compact schema is self-explanatory in every
tools/list response, and the query_graph/get_architecture/search_code
format parameters gain the brief description their three siblings
already had, all naming default_response_format.

Tests: response-format and TOON-context regressions in test_mcp.c/
test_tool_consolidation.c/test_input_validation.c/test_token_reduction.c;
zero-row hint coverage including no-hidden-tool-name, missed-graph shadow-
project probing, empty-project no-crash, TOON/JSON format parity,
UNION/repeated-name dedup, and the previously-untested EXISTS-predicate
walk; notification coverage for every notify call site including the
delete_project no-op negative direction and the "notify before first
tools/list is suppressed" negative direction; a cold-start regression
test seeding a real on-disk project store and calling tools/list as the
literal first request of a fresh session; overlay-consistency tests for
the two rerouted callers (first_response_context_uses_ready_overlay_schema,
resource_arch_rel_patterns_use_ready_overlay) proving _context and the
architecture resource report the active-overlay vocabulary, not
tombstoned canonical labels/types.

Docs: README.md documents the zero-row hint behavior and the
default_response_format config example; docs/CONFIGURATION.md adds the
default_response_format row to the important-keys table.

Verification: make -f Makefile.cbm test (full ASan/UBSan suite) run on
this exact tree after the final revisions. Earlier iterations of this
content additionally passed make -f Makefile.cbm test-leak (0 leaks for
0 total leaked bytes) and manual checks against the live MCP binary
(zero-row hint names real schema on two repositories; the query_graph
tools/list docstring shows the live schema on the very first request of
a fresh session); revisions since those checks are docstring text, the
vocabulary-enum rename, and the two selector reroutes, all covered by
the full-suite rerun.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 01:42:38 -04:00
Andrew Hundt ad9d637402 feat(config): add default_response_format (toon/json) canonical enum and key
query_graph and related tools previously had no config-controlled
default output encoding, and the TOON/JSON format enum lived nowhere
canonical, risking drift between the wire strings different call sites
might use. Add cbm_mcp_output_format_t {CBM_MCP_OUTPUT_TOON,
CBM_MCP_OUTPUT_JSON, CBM_MCP_OUTPUT_INVALID} plus the "toon"/"json" wire
string constants to mcp.h with a drift-prevention comment, as the single
source of truth for every response-format call site.

Add the default_response_format config key to CBM_CONFIG_REGISTRY in
cli.c (default "toon", validated to "toon" or "json" only via
cbm_config_set), so `codebase-memory-mcp config set default_response_format
json` persists a default and CBM_MCP_OUTPUT_INVALID.

Verification: make -f Makefile.cbm test — 6850 passed, 1 skipped, 0
failed, ASan/UBSan clean.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 01:10:36 -04:00
Andrew Hundt 580ecdd161 refactor(store): structured relationship facts and declared property-key registry
get_schema/get_schema_overlay_view previously returned relationship
patterns as preformatted "(A)-[T]->(B) [Nx]" strings via
schema_format_rel_pattern, forcing every consumer to re-parse text to get
at the source label, edge type, target label, or observed count
individually. Replace it with a structured cbm_schema_relationship_t
{source_label, edge_type, target_label, observed_count} array
(schema_collect_rel_patterns_from_stmt, store.h/store.c), and centralize
the previously-duplicated SCHEMA_MAX_JSON_KEYS/SCHEMA_REL_PATTERN_LIMIT
enum constants as CBM_STORE_SCHEMA_PROPERTY_KEY_LIMIT and
CBM_STORE_SCHEMA_RELATIONSHIP_PATTERN_LIMIT beside the existing schema
caps in store.h. Add const-correct cbm_store_schema_node_base_properties/
cbm_store_schema_edge_base_properties accessors so base-column lists have
one owner instead of being redeclared per caller.

Add a declared node/edge property-key registry
(schema_declared_node_property_keys/schema_declared_edge_property_keys,
~107 entries, sorted and duplicate-free) enumerating every JSON property
key any pipeline pass or git-context writer can emit on a node or edge,
exposed via cbm_store_schema_declared_node_property_keys/
cbm_store_schema_declared_edge_property_keys. Each row carries a trailing
comment naming its writer for provenance. The registry is a maintenance
contract, not a runtime dependency: nothing outside the new contract test
reads it. Document the four places that must stay in sync (writer,
store.c table, store.h accessor comment, contract test) in both store.h
and CLAUDE.md.

Add tests/test_schema_declared_property_keys.c: indexes mixed-language
fixtures (Python/TypeScript/Rust) plus a best-effort real git repo
(git init/commit via the portable cbm_git_drain_command, matching
test_git_context.c's tolerance for "git not available") to also
forward-verify the ~10 git_context registry rows, asserts every
discovered non-base property key is in the declared registry for its
entity kind, asserts both tables are sorted/duplicate-free, and reverse-
pins a sample of registry keys to catch dead rows. Cap-blindness guarded:
a label/type that hits the discovery limit fails loudly instead of
passing on unprovable completeness.

Register the new suite in Makefile.cbm (TEST_STORE_SRCS) and
tests/test_main.c.

Verification: make -f Makefile.cbm test — 6850 passed, 1 skipped, 0
failed, ASan/UBSan clean. make -f Makefile.cbm test-leak — 0 leaks for 0
total leaked bytes.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-19 01:10:25 -04:00
Andrew Hundt b835b5e8b2 fix(cypher): defer unbound relationship predicates
execute_single previously evaluated the complete WHERE tree while only the first MATCH alias was bound. A target-alias predicate such as NOT callee.file_path CONTAINS "tests/" therefore rejected every seed before CALLS expansion and returned zero rows.

Add allocation-free three-valued partial evaluation in src/cypher/cypher.c: bound leaves prune early, unbound node or edge aliases remain unknown, and AND/OR/NOT/XOR preserve boolean semantics and safe short-circuiting. Register eval_expr_partial in src/foundation/recursion_whitelist.h.

Add four relationship-target and mixed-alias regressions in tests/test_cypher.c. Verified 159/159 Cypher tests under ASan/UBSan, source-safety and whitelist checks, an -O2 production build, and the formerly empty hotspot query returning 20 bounded rows.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-18 19:16:09 -04:00
Andrew Hundt a4b3a71e81 fix(cypher): reject unconsumed query tokens
cbm_parse previously returned success when parse_return_item stopped before postfix list indexing or an arbitrary trailing token. A query containing labels(n)[0] could therefore ignore its alias, aggregate, ordering, and limit and execute as an unbounded projection.

Require TOK_EOF after parse_post_where in src/cypher/cypher.c and transfer the parent cursor after a recursively validated UNION tail. Route both list-index rejection paths through one diagnostic that gives supported labels(n) and n.label/count(*) rewrites.

Add parser regressions in tests/test_cypher.c. Verified 155/155 Cypher tests under ASan/UBSan, all three UNION tests, source-safety, an optimized CLI rejection, and the supported five-row node-label aggregation.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-18 18:50:08 -04:00
Andrew Hundt 292299857e fix(mcp): skip unfiltered rows for semantic-only JSON
search_graph previously ran an unfiltered graph search before a JSON semantic-only lookup, so an empty vector result returned unrelated nodes and paid an unnecessary graph-result-page scan. Detect semantic-only requests, skip cbm_store_search and cbm_store_search_overlay_view, and return explicit empty semantic_results with recovery guidance.

Define query_graph as an early compositional tool for effective, computationally efficient custom Cypher. Mark examples, labels, properties, and LIMIT as optional guidance; retain server caps and dependency-ranking instructions; make truncation hints offer narrowing or cap changes without prescribing LIMIT.

Install portable sandbox recovery guidance that distinguishes MCP approval from shell sandbox authorization.

Verification: ASan/UBSan MCP 228 passed; tool_consolidation 103 passed; token_reduction 50 passed; CLI 150 passed; source-safety passed. The repository-wide clang-format target remains red on pre-existing files; changed-line formatting issues were corrected.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-18 17:56:39 -04:00
Andrew Hundt 5a13131e5d fix(mcp): return fresh watcher and wildcard query results
A supervised watcher reindex could replace a project database while the request thread retained a read-only handle to the unlinked prior generation. The watcher also treated any non-null worker response as success, including status=error, and shutdown freed g_server before an in-flight watcher callback had joined.

Pass the server through cbm_mcp_index_run_supervised_path, defer cached-store retirement through store_stale, classify indexed/degraded responses before committing watcher baselines, and join the watcher before freeing the server while keeping the watcher object alive for autoindex teardown.

Normalize glob-compatible wildcards before POSIX regex compilation so valid-but-ambiguous inputs such as alpha?orker and alternations return the intended symbols across name_pattern, qn_pattern, and pattern. Preserve explicit group, class, escaped, and dot-regex constructs.

Place validation fixtures under the isolated test cache, clean runner-owned caches after both passing and failing runs by default, and reuse CBM_TEST_ARTIFACT_DIR as the explicit failed-run retention path.

Verification: CBM_ONLY_SUITE=input_validation build/c/test-runner (48 passed); CBM_ONLY_SUITE=mcp build/c/test-runner (227 passed); make -f Makefile.cbm cbm (O2 build and ad-hoc signature); MCP initialize plus watcher-active EOF shutdown exited 0; git clang-format reported no changed-line edits; git diff --check passed. The repository-wide lint-format target still reports pre-existing formatting debt outside this diff.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-18 14:03:39 -04:00
Martin Vogel cc5f71bdff fix(hook,smoke): teach the last two consumers the tree output shape
The PR CI caught two consumers the format migration missed — both parsed
tool output by its old shape and failed silently or with a wrong count:

- hook-augment built its PreToolUse additionalContext by reading the
  legacy 'results' object-array from search_graph format:json; after the
  json-tree reshape that key no longer exists, so the hook emitted nothing
  (caught end-to-end by tests/windows/test_hook_augment.py). The parser
  now walks groups -> qn_prefix/file + column-ordered rows. A new local
  test feeds the parser the LIVE server envelope, so any future drift
  between response shape and hook parser fails in the normal suite on
  every platform — not only in the Windows CI guard. Reproduced RED
  (ctx NULL, the exact CI failure) before the fix, GREEN after.

- smoke-test.sh carried six old-shape parsers (callers[N], rows[N]{,
  clusters[N], semantic[N]{, and the assert_toon_table helper); the tree
  headers never matched, so trace verification read 0 callers. All six
  updated to the tree contract; the full smoke script passes end-to-end
  locally.

cli suite 222 passed; lint clean; scripted smoke ALL PASSED.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-18 10:36:08 +02:00
Andrew Hundt 23fb3bd144 fix(mcp): inject context into BM25 JSON responses
handle_search_graph returned completed BM25 JSON before the regular yyjson response builder. Requests with query plus format=json therefore omitted both session_project and the one-shot _context header, while graph-mode JSON and compact TOON included them.

Add json_payload_with_context_once in src/mcp/mcp.c to copy the bounded BM25 object, invoke the shared inject_context_once path, and serialize it before the early return. Extend search_graph_explicit_project_context_uses_resolved_store_project with the exact BM25 JSON branch.

TDD evidence: the new assertion failed with 101 existing consolidation tests passing because session_project was absent. Verified after the fix with ASan/UBSan: tool_consolidation 102/102 and mcp 225/225; scripts/check-source-safety.sh and git diff --check passed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-18 00:58:25 -04:00
Andrew Hundt 24d67d9144 fix(mcp): bind first context to queried project
inject_context_once used srv->session_project and srv->session_root even when search_graph resolved an explicit project in another store. That produced correct result rows with zero node counts, the wrong schema, and the session CWD ecosystem in the same response.

Pass the resolved project through the JSON and TOON context paths, retain session_project as the server CWD identity, resolve ecosystem metadata from the registered target root, and suppress stale PageRank statistics and key functions. Free cbm_project_t fields after the one-shot metadata lookup.

Add search_graph_explicit_project_context_uses_resolved_store_project in tests/test_tool_consolidation.c. Verified with ASan/UBSan suites: tool_consolidation 102/102, mcp 225/225, rust_lsp 506/506, registry 60/60, lang_contract 35/35; scripts/check-source-safety.sh passed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-18 00:50:28 -04:00
Martin Vogel 60390affb8 fix(mcp): close the legacy-generation cursor gap; json = the tree model everywhere
Fresh review pass over the whole branch; three classes of fix:

Cursor safety — the 'legacy' generation gap:
  A pre-migration DB (indexed by an older binary, opened read-only) has no
  store_meta, so its generation reads 'legacy' — and 'legacy' == 'legacy'
  across rebuilds, which silently defeats stale-cursor detection for exactly
  those DBs: a stale (hop,id) watermark would resume on wrong node ids.
  Cursors are now neither minted (truncated + raise-limit hint instead) nor
  accepted (cursor_unsupported teaching error) under a legacy generation.
  The standard flow was verified safe empirically: every index run seeds
  store_meta via cbm_store_upsert_project in the flush path, and a rebuilt
  DB file gets a fresh random db_uid, so aliasing is impossible there. The
  guard is unreachable through the public API (upsert always seeds) — it
  protects only genuinely old DBs, hence no synthetic test.

format:'json' now emits the tree model on EVERY tool (the last five):
  - BM25 query mode: {total, cols, rows[[qn,label,file,lines,rank]]} —
    rank order preserved, ranked output is never regrouped.
  - semantic: {semantic: {cols, rows[[qn,label,file,score]]}}.
  - search_code compact/full: {cols, rows} with nested match-line arrays;
    full appends a per-row source object (same guarded, windowed reader);
    raw_matches likewise {cols, rows}. files mode stays a plain list.
  - get_architecture: all twelve sections emit {cols, rows} via one shared
    section helper; cluster row cells carry their nested lists as arrays.
  - include_connected became a real  column in BOTH encodings
    (';'-joined cell in the text tree, nested name array in json) — the
    include-connected-forces-JSON coupling is gone, and the last legacy
    per-node-object emitters (emit_search_results, enrich_node_properties)
    are deleted.

Teaching-error parity: detect_changes now rejects an unknown
with the valid values (it silently fell back to inbound; trace_path already
errored — the caller would have misread the result's semantics).

Full suite 6,373 passed / 0 failed; lint clean. Live-verified on the
self-index: bm25/search_code/architecture json shapes, the connected
column, and cursor pagination.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-18 01:26:12 +02:00
Martin Vogel 47e4d25dac fix(mcp): review polish — honest totals, complete counts, quoted cells
Fresh-eyes review of the phase-2/3 work; five defects, each against our own
stated principles (no silent caps, exact counts, same model in both
encodings):

- trace callees_total/callers_total counted test-file rows even when
  include_tests=false hides them from the table — the scalar disagreed with
  the enumerable set (a field-eval agent read callers_total=175 against a
  handful of visible rows and stopped trusting the tool). Totals now apply
  the same filter as the emitters; description documents the transitive
  (within-depth) semantics. Guard test: filtered=1 vs include_tests=2.
- cycles clamped the SCC list at 100 silently, losing the true count.
  cycles_total is now always exact and cycles_omitted is emitted when the
  display clamp engages (both encodings).
- detect_changes accepted a 'limit' param (referenced by its own omitted-
  hint) that the schema never declared — undiscoverable. Declared.
- detect_changes format:json lacked the impacted_modules rollup the tree
  encoding carries — model parity restored via a shared rollup helper.
- tree search extra-field cells were appended raw: a signature with spaces
  shifted every following column. Cells now route through the shared
  emitters (space-triggered quoting, '-' placeholder); the properties test
  pins the quoted form.

Full suite 6,373 passed / 0 failed; lint clean.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-18 00:40:54 +02:00
Martin Vogel 130a82bd10 feat(mcp): real impact analysis + cycle detection (multi-source BFS, SCC)
Phase 3 of the graph-analysis overhaul. Two new capabilities on a shared
multi-source traversal substrate, plus a context-bomb guard.

detect_changes — rebuilt into real blast-radius analysis:
  The old handler parsed and echoed depth but never traversed: impacted was
  just the symbols DEFINED in changed files. It now resolves the git diff to
  those symbols, then runs ONE multi-source BFS to the transitive impact set.
  - default direction inbound = the blast radius (transitive callers that may
    need review); outbound/both available.
  - seeds are excluded from the result, and a changed file reached from
    another changed file is not counted as extra impact (that is not
    downstream fallout).
  - merge_base SHA pins the exact diff base for reproducibility.
  - impacted_modules: a 2-segment (src/mcp, tests/...) rollup — a quotient
    view of where the radius lands, always complete (overflow bucketed, never
    silently dropped). Per-symbol rows cap at limit (default 200, nearest
    hops first) with impacted_total exact and impacted_omitted surfaced.
  On a 7-file core diff: 1469 impacted symbols, 64KB unclipped -> 10.7KB with
  the rollup + cap. format:json returns the same model as structured JSON.

cbm_store_bfs_multi (store): one recursive CTE anchored on ALL seeds via a
  temp table — never seed_count separate walks over overlapping hubs.
  Seed-exclusion NOT IN, MIN(hop) across the set, canonical (hop,id) order,
  a memory-safety ceiling that reports *truncated instead of silently
  capping. Reproduce-first: seed-exclusion + min-hop, and truncation.

get_architecture aspects:[cycles] — SCC condensation:
  Iterative Tarjan (heap stacks, no recursion overflow) over the CALLS graph
  reports circular dependencies (strongly-connected components of size > 1).
  cbm_store_fetch_call_edges feeds it with a 400k-edge scan budget that
  reports partial coverage rather than truncating silently. Opt-in ONLY —
  never via a default or 'all' call, since it scans the whole call graph.
  On this repo it surfaces 26 real cycles (e.g. the C-LSP type-evaluation
  recursion) in ~5KB. Reproduce-first: a 3-node cycle is found, an acyclic
  chain is not, and a default call does not run the scan.

get_code_snippet: cap the returned line span at 500 (a structural Module/File
  node spans its whole file — an unclipped read returned ~400KB in one call).
  source_clipped flags it; start_line/end_line still allow a targeted re-read.

Full suite 6,372 passed / 0 failed; lint clean.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-18 00:13:50 +02:00
Martin Vogel 3f5ae18852 feat(mcp): complete the tree-format migration — TOON pruned, json = stringified tree
- cbm_toon_* renamed to cbm_tree_* (the emitters have emitted tree syntax
  since the format flip; the name now matches).
- format enums reduced to [tree, json], default tree; dead tree/toon flags
  removed; tool RESPONSE descriptions rewritten for the grouped-tree
  contract (group prefix printed once, full qn = prefix + '.' + name).
- format:'json' is now the json-stringified tree: the SAME grouped model
  serialized as JSON — {total, cols, groups:[{qn_prefix, file,
  rows:[[column-ordered cells]]}], has_more} for search, per-leg
  {cols, groups} for trace (risk/args columns follow the flags),
  query_graph's columns+rows shape already complied. Per-row key envelopes
  and the legacy property blob are gone (blob-never-spills pinned by test);
  requested  add columns in both encodings.
- include_connected keeps the per-node-object shape (nested neighbors) —
  the one remaining legacy consumer, conversion tracked.
- legacy verbose emitters deleted (bfs_to_json_array).

Full suite 6,367 passed / 0 failed; lint clean.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-17 22:58:22 +02:00
Martin Vogel c5bffb7fe6 feat(mcp): tree-format tool output, exactly-once pagination, lean defaults
Round-2 overhaul of the tool-output surface, built eval-first (byte harness +
no-context field-eval agents, always vs the v0.9.0 release binary):

Format — tree syntax everywhere:
- compact_out emits tree tables: 'key: N  (cols: a b c)' headers, indented
  space-delimited rows, '-' placeholders, space-aware quoting. Every table
  in every tool converts mechanically.
- Grouped prefix-factored output is the DEFAULT for search_graph and
  trace_path: rows grouped under their shared (qn-prefix, file) printed
  once, row qn = group prefix + '.' + name (stated in-band). Measured:
  192-hit search 2,929B (was 4,811 TOON / 238,466 v0.9.0), 82-row trace
  2,256B (was 4,234 / 8,094). Field eval: 5/5 answers correct, QN
  reconstruction verified, no comprehension loss.
- detail:'ids' tier: bare-qn enumeration for wide sweeps (-44%).

Pagination — exactly-once, stateless:
- store_meta generation (per-DB random uid + mutation counter bumped in
  cbm_store_upsert_project — every index run's choke point): cursors can
  never silently validate against a rebuilt DB.
- trace cursors: opaque token (leg, generation, params-hash, (hop,id)
  watermark); pages drain callees then callers with exact
  callees_total/callers_total on every page; teaching errors for
  params-mismatch and staleness. Guard: 12 callees paged 5+5+2, every row
  on exactly one page.
- deterministic ordering: BFS ORDER BY hop,id; canonical (hop,id) sort +
  min-hop dedup across same-name seeds (hop fed risk_labels wrong before);
  search (name,id)/(rank,id) tie-breaks make offset pages stable.

Lean defaults + teaching errors:
- get_graph_schema no longer advertises blocked internal fields (fp/sp/bt).
- index_repository coverage lists: counts + 5 examples + one-line pointer
  notes (full record was always in the logfile); 6.0KB -> 1.5KB here,
  53KB observed on a large repo. index_status gates the git context block
  behind verbose:true.
- trace limit param (<=5000) + truncated:true — never a silent truncation;
  invalid direction now errors with the valid values; requesting core
  columns via fields hints instead of emitting empty cells; cypher
  resolves f.file/f.qn/f.path aliases.

Trace-correctness fixes (reproduce-first, RED verified):
- bfs_collect_edges 4KB id-string overflow: past ~1000 visited nodes the
  edge SQL failed to prepare and EVERY trace edge silently vanished; now a
  per-connection temp-table join (star guard: 1200/1200 edges).
- bfs_union_same_name: min-hop across seeds + edge dedup.

Guards: gating tool-output regression suite (context-explosion detector:
blocked-field leaks, repeated-key envelopes, oversized notes + calibrated
byte ceilings + semantic floors). Full suite 6,367 passed / 0 failed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-17 22:32:53 +02:00
Martin Vogel e678b2b6ac Merge pull request #1137 from DeusData/fix/1083-wal-journal-size-limit
fix(store): bound the WAL (journal_size_limit) + warn on checkpoint starvation (#1083 part 1)
2026-07-16 22:29:34 +02:00
Andrew Hundt 8e1b081f11 fix(semantic): exclude project roots from qualified-name tokens
Semantic-edge tokenization included the project component prepended by the FQN pipeline. Because benchmark worktrees derive that component from their checkout path, identical source trees produced different LSH buckets, similarity scores, and edge counts across randomized roots.

Add cbm_pipeline_fqn_without_project() in src/pipeline/fqn.c and apply its borrowed repository-relative suffix in pass_semantic_edges.c. The exact '<project>.' boundary preserves relative namespaces and rejects partial-prefix matches in O(length(project)) time and O(1) auxiliary memory.

Register the existing semantic and ast_profile suites in tests/test_main.c's CBM_ONLY_SUITE dispatch; the documented selector previously exited successfully with '0 passed'. tests/repro/repro_parallel_determinism.c also accepts CBM_REPRO_DETERMINISM_CORPUS so the real pinned corpus can be exercised without a developer-specific path.

Verification: CBM_ONLY_SUITE=semantic (33 passed); ast_profile (10 passed); fqn (82 passed); benchmark/campaign/report unittest suite (112 passed); scripts/check-source-safety.sh; git diff --check. Two optimized lifecycle runs in distinct randomized roots produced identical observed-pair arrays and scores (initial 0.871, fresh 0.843); four fixed-corpus parallel runs produced identical graph fingerprints.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 16:27:33 -04:00
Martin Vogel 9dfe52f16e fix(store): warn when a checkpoint can't reset a large WAL (starvation)
Add a checkpoint-starvation warning to cbm_store_checkpoint: read the
wal_checkpoint_v2 out-params (frames in the WAL vs frames reclaimed) and, when
the WAL is large (>= ~1 GiB of frames) but the PASSIVE checkpoint couldn't reset
it because readers hold marks, log store.wal.starving. This is the operator
visibility the reclaim limit can't provide on its own — it surfaces the
starvation (and its scale) before the -wal file fills the disk. Portable
(no file stat / no path handling), fires only under genuine starvation.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-16 19:39:15 +02:00
Martin Vogel 4a3a9b745f fix(store): bound the WAL with journal_size_limit so a starved log is reclaimed
On-disk write connections set no journal_size_limit, so the -wal file is never
physically shrunk: all our checkpoints are PASSIVE (TRUNCATE is deliberately
avoided because its ftruncate(fd,0) can SIGBUS a sibling mmap'd process on
macOS), and PASSIVE never ftruncates. Under checkpoint starvation the WAL then
grows without bound (reported: 115 GB, filled the drive) and even a later
successful checkpoint leaves the file at its peak size (#1083).

Set journal_size_limit = 256 MiB on write connections so SQLite truncates the
-wal back to that bound whenever a checkpoint can finally reset the log —
reclaiming disk opportunistically during a session, not only on clean close.
256 MiB is far above the healthy WAL (~4 MiB under the default 1000-page
autocheckpoint), so normal indexing never triggers truncate/regrow churn; the
limit only fires after abnormal growth. No TRUNCATE checkpoint is introduced.

Scope: this is the *reclaim* half of #1083. It does not prevent the peak under
continuous reader contention (a checkpoint can't reset the log while a reader
holds a mark) — the write-amplification driver (concurrent full re-indexes of
one repo) is serialized by the cross-process pipeline lock in #892.

Test: a file-backed write store now reports journal_size_limit = 256 MiB
(RED on main: -1 / unlimited).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-16 18:53:35 +02:00
Martin Vogel 0ec24787fa Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	tests/test_lang_contract.c
2026-07-16 17:37:46 +02:00
Martin Vogel a688d85cc8 Merge pull request #1093 from PR9000/freebsd-hw-physmem
fix(foundation): use HW_PHYSMEM for physical memory detection on FreeBSD
2026-07-16 17:33:20 +02:00
Andrew Hundt 78245da17e fix(install): support Kilo CLI and remove PATH blocks
Standalone Kilo stores global local MCP servers in ~/.config/kilo/kilo.jsonc, while the installer only handled the legacy VS Code extension adapter. Detect standalone Kilo separately and reuse cbm_upsert_opencode_mcp()/cbm_remove_opencode_mcp() for its documented top-level mcp, type=local, command-array JSONC schema.

Uninstall also left the exact '# Added by codebase-memory-mcp install' PATH block in shell startup files after deleting the binary. Add cbm_remove_owned_path() and scan .zshrc, .bashrc, .bash_profile, .profile, and Fish config, removing only marker-owned exact blocks through atomic writes while preserving user content and dry-run behavior.

Document both Kilo targets in README.md with the official Kilo MCP configuration reference. Add detection/plan/JSONC/foreign-entry/uninstall coverage plus POSIX/Fish PATH ownership tests in tests/test_cli.c.

Verification on native Apple Silicon macOS: intentional compile-red tests captured missing kilo_cli and cbm_remove_owned_path; ASan/UBSan CLI suite passed 150/150; make -j2 -f Makefile.cbm cbm passed with -O2 -DCBM_BIND_TS_ALLOCATOR=1 and ad-hoc signing. An isolated 17-target install produced 32 byte-identical files across repeated installs; uninstall removed every owned reference and binary while preserving a foreign Kilo server. No native Windows execution is claimed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 09:25:45 -04:00
Andrew Hundt 53c38455e8 feat(config): apply exact capability presets atomically
Add cbm_config_apply_preset() in src/cli/cli.c with six named capability sets committed through one SQLite BEGIN IMMEDIATE transaction. Reuse the existing CBM_CONFIG_* keys from cli.h, depindex.h, pagerank.h, and pipeline.h instead of defining parallel settings.

Expose 'config preset list' and 'config preset apply <name>'. Return a nonzero status and identify any environment override whose effective value differs from the stored preset, so comparisons cannot silently run with a mixed configuration.

Document the quality and ablation presets in docs/CONFIGURATION.md. Cover the exact streamlined-quality and minimal-indexing values plus unknown-name rejection in tests/test_cli.c.

Verification: macOS Apple Silicon ASan/UBSan CLI suite 147/147 passed; optimized -O2 binary listed and applied classic-quality in an isolated cache; CBM_TOOL_MODE=streamlined correctly caused classic-quality to exit 1 with the effective-value warning; git diff --cached --check passed.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 09:01:18 -04:00
Andrew Hundt fce4733078 perf(subprocess): poll short-lived workers every 5 ms
The cbm_run_posix reap loop slept for 100 ms whenever waitpid(WNOHANG) observed a live child, and cbm_run_win waited 200 ms. That fixed delay dominated short index workers even when optional graph algorithms were disabled.

Add cbm_subprocess_poll_interval_ms in src/foundation/subprocess.c and use a 5 ms cadence during the first 250 ms in both supervisor loops. After the bounded window, POSIX returns to 100 ms and Windows returns to 200 ms, preserving steady-state wakeup complexity, cancellation, quiet-timeout, and process-tree cleanup behavior.

tests/test_subprocess.c covers the 0/249/250 ms policy boundaries and a POSIX short-child latency canary. Verified on macOS with CBM_ONLY_SUITE=subprocess build/c/test-runner (19 passed) and make -j2 -f Makefile.cbm cbm (-O2 production build). The pure Windows policy is covered, but no native Windows execution is claimed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 08:18:18 -04:00
Andrew Hundt 12bf472c63 fix(dependencies): read installed npm packages from manifest
Previous behavior: cbm_discover_installed_deps() filtered graph qualified names for dependencies|require, but package.json extraction emits names such as <project>.package.cbmbenchdep. Automatic npm indexing therefore reported zero packages even when node_modules contained the declared source.

Parse dependencies, devDependencies, optionalDependencies, and peerDependencies from a local package.json capped at 1 MiB in src/depindex/depindex.c:364-489. Resolve only installed package directories, deduplicate names with CBMHashTable, grow retained results geometrically to the configured limit, and release files, JSON documents, tables, paths, versions, and partial arrays on every failure path.

Add the exact extracted-QN regression at tests/test_depindex.c:712. Extend scripts/benchmark-incremental-speed.py with a network-free npm quality fixture, same-result source/package/read-only judgments, atomic report publication, worker/process timing attribution, and profile-only CLI lifecycle brackets.

Verification on native Apple Silicon macOS: ASAN+UBSAN depindex 36/36; ASAN+UBSAN CLI 146/146; Python benchmark/campaign/report 84/84; Ruff check passed; production link passed with -O2 -DCBM_BIND_TS_ALLOCATOR=1. Native Windows execution was not performed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 07:32:55 -04:00
Andrew Hundt 0434df337c feat(install): configure Claude Desktop MCP server
Previous behavior: install detection and receipts omitted Claude Desktop even when its platform config directory existed, so claude_desktop_config.json required manual registration.

Add cbm_claude_desktop_config_path() at src/cli/cli.c:1267 and route the detected client through the existing mcpServers editor writer at lines 4504-4509. Route uninstall through cbm_remove_editor_mcp() at lines 5232-5237 so only the codebase-memory-mcp entry is removed.

Add plan/install/uninstall ownership coverage at tests/test_cli.c:2157 and platform-directory detection coverage at line 2479. Document the target in README.md, docs/CONFIGURATION.md, and the CLI help output.

Verification: CBM_ONLY_SUITE=cli build/c/test-runner (ASAN+UBSAN, native Apple Silicon macOS): 146 passed. CBM_ONLY_SUITE=cli build/c/test-runner-nosan: 146 passed. Native Windows execution was not performed.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 07:01:10 -04:00
Andrew Hundt d92b240f36 fix(mcp): stop advertising static resource list changes
src/mcp/mcp.c:1484 advertised resources.listChanged=true even though resources/list always returns the same three URIs. The MCP 2025-11-25 resource specification defines listChanged for changes to that inventory, not changes to resource contents: https://modelcontextprotocol.io/specification/2025-11-25/server/resources

Remove the resource listChanged flag, the never-assigned client_has_resources field, notify_resources_updated(), and its three no-op index call sites. Keep resources.subscribe=false and the pull-only resources/list and resources/read compatibility paths. The independent tools.listChanged capability and _hidden_tools notification remain unchanged.

TDD on Apple Silicon macOS: the new capability test failed because resources.listChanged was present, then passed after the correction. Malformed tools/call, unknown-tool string-ID, and first-call startup-index tests passed 5/5. MCP ASAN/UBSan passed 225/225; scripts/check-source-safety.sh and git diff --check passed. Native Windows execution was not performed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 06:35:32 -04:00
Andrew Hundt aad95af5eb perf(mcp): resolve list_projects branches with one Git child
Previous behavior: src/mcp/mcp.c:3895 called cbm_git_context_resolve for the single branch field. Attached repositories could execute seven Git commands per project, and a rejected status-based replacement took 3,380.410 ms for the isolated 50-project page.

Add src/git/git_context.c:115 resolve_current_branch as the single attached/detached/unborn implementation shared by cbm_git_context_resolve and cbm_git_current_branch. cbm_git_run_first_line_buf drains stdout and reaps symbolic-ref once; cbm_pclose_exit_code normalizes POSIX wait status and Windows GetExitCodeProcess results. Exit 0 returns attached/unborn names, exit 1 returns DETACHED, and non-Git errors remain absent.

Route CBM_ONLY_SUITE=git_context in tests/test_main.c:386 so focused runs execute four tests instead of traversing high-memory fixtures. tests/test_git_context.c:252 covers attached, detached, unborn, non-Git, full-context parity, heap cleanup, and portable command setup; tests/test_security.c:621 covers normalized exit 7.

Verification: Git context ASAN/UBSan 4/4; normalized-exit canary 1/1; MCP ASAN/UBSan 224/224; scripts/check-source-safety.sh passed; staged diff check passed. The -O2 binary returned the isolated default page in 773.848 ms and explicit full inventory in 5,023.220 ms, preserved 41/53 branch fields including the unborn main branch, and reaped both server processes and all reader threads.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 06:09:34 -04:00
Andrew Hundt d10662a90b fix(mcp): paginate list_projects inventory
Previous behavior: src/mcp/mcp.c:3965 handle_list_projects opened every valid cache database and emitted the complete inventory for every call. On the 046d438 real-cache probe, that path returned 81,646 result bytes and took 6,462.768 ms.

Add MCP_PROJECTS_PAGE_SIZE=50 and MCP_PROJECTS_PAGE_MAX=200, stable filename sorting, lazy valid-database traversal, limit/offset metadata, and an explicit all=true compatibility path. collect_project_db_names frees partial allocations on failure; build_project_json_entry closes lookahead stores without querying counts or Git metadata.

Keep scripts/benchmark-incremental-speed.py:1183 full-inventory scaling semantics explicit with list_projects_arguments={all:true}. Add tests/test_mcp.c:963 coverage for stable pages, next_offset, schema fields, full compatibility, environment restoration, and cache cleanup.

Verification: focused ASAN/UBSan canary 1/1; MCP suite 224/224; tool_consolidation 100/100; benchmark harness unittest 41/41; scripts/check-source-safety.sh passed; git diff --cached --check passed.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 05:22:48 -04:00
Andrew Hundt d3c5530a3f fix(rust): keep cfg call edges module-local
Rust cfg-gated definitions append predicate text to qualified names, but cbm_registry_add at src/pipeline/registry.c:495 indexed the suffixed tail instead of the supplied source name. Local twins therefore disappeared from has_permission lookup and an unrelated cross-module symbol became a false unique_name target.

Add cbm_rust_cfg_qualified_name at internal/cbm/extract_defs.c:1912 and use it from both definition extraction and compute_func_qn at internal/cbm/extract_unified.c:442. The helper scans only adjacent Rust attribute spans, allocates no duplicate decorator array, and makes call-source QNs match cfg-qualified Function nodes instead of falling back to File nodes. Index registry candidates by the supplied semantic name while retaining exact QNs as identities.

Guard the resolver collision in tests/test_registry.c:891 and cfg caller identity in tests/repro/repro_issue495.c:212. Verified with ASan/UBSan: registry 60/60, extraction 276/276, parallel 36/36, incremental 162/162 with 15,328-node and 89,030-edge incremental/full parity; scripts/check-source-safety.sh passed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 04:52:38 -04:00
Andrew Hundt 76f7796fb8 fix(mcp): distinguish dirty worktree from HEAD
Previous behavior:
- index_status.git returned head_sha and base_sha without stating that HEAD identifies committed content only or whether the current filesystem differed from HEAD.
- A consumer could treat the SHA as the identity of a dirty working tree.

Changes:
- Reuse cbm_git_snapshot_read(CBM_GIT_SNAPSHOT_DIRTY) in add_git_context_json() at src/mcp/mcp.c:3106-3126.
- Emit head_scope=committed_revision_only, worktree_dirty, head_matches_worktree, worktree_state, and a dirty_hash only for dirty repositories.
- Emit null dirty/match fields plus worktree_state=unknown when snapshot capture is unavailable instead of claiming the tree is clean.
- State the HEAD/working-tree distinction in the shared index_status tool description.

Scope and lifecycle:
- Dirty status is computed only for single-project index_status responses; list_projects and search paths gain no git-status scan.
- cbm_git_snapshot_t is stack-owned and add_git_context_json() retains its existing cbm_git_context_free() cleanup.

Verification:
- Red: focused test failed at tests/test_mcp.c:3115 because worktree_dirty was absent.
- Green: clean-to-dirty focused test passed 1/1; MCP ASan/UBSan passed 223/223; tool_consolidation passed 100/100.
- make -f Makefile.cbm lint-source-safety passed both checks.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 04:25:36 -04:00
Andrew Hundt dab4b52ef2 fix(mcp): enforce compact JSON properties
Previous behavior:
- search_graph format=json unconditionally flattened node.properties_json, so compact=true returned opaque fp/sp/bt indexing intermediates and every optional property.
- The fields argument affected TOON only, despite compact=true being the advertised default.

Changes:
- Parse the existing bounded 12-entry fields selection in emit_search_results() at src/mcp/mcp.c:4873-4962 and apply it to compact JSON.
- Skip property JSON parsing when compact mode has no selected fields; compact=false retains useful non-internal metadata.
- Reuse sg_field_blocked() so fp, sp, and bt are excluded from TOON, compact JSON, explicit-field JSON, and non-compact JSON.
- Document compact/fields behavior in the shared classic/streamlined search_graph schema at src/mcp/mcp.c:1098-1114.
- Preserve the legacy verbose path explicitly through compact=false in tests/test_mcp.c:1259-1276.

Resource and complexity:
- Compact JSON without fields performs no property parse or property-document allocation.
- Field lookup is O(properties * min(requested_fields, 12)); result-document ownership remains O(results).
- If the property-document tracking array cannot be allocated, properties are omitted with an explicit warning instead of leaking parsed documents. Parsed documents with no selected output are freed immediately.

Verification:
- Red: focused ASan/UBSan test failed at tests/test_mcp.c:2085 because FPSENTINEL00 was present.
- Green: focused test passed 1/1; MCP ASan/UBSan suite passed 222/222; tool_consolidation passed 100/100.
- make -f Makefile.cbm lint-source-safety passed both checks.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 04:05:25 -04:00
Andrew Hundt 1751217dae fix(mcp): scope architecture key functions by path
Previous behavior:
- handle_get_architecture() built the PageRank key_functions query with only pr.project = ?1, so a JSON request scoped to apps/hoa returned GlobalKeyFunction from scripts/helpers.py.

Changes:
- Pass path_scoped into build_key_functions_sql() in src/mcp/mcp.c:1903 and add exact-file-or-descendant predicates bound as ?2 and ?3 at src/mcp/mcp.c:6983-6987.
- Preserve unscoped first-response and codebase://architecture queries by passing path_scoped=false.
- Extend tool_get_architecture_path_scoping in tests/test_mcp.c:4421-4494 with higher-ranked out-of-scope and lower-ranked in-scope functions.

Resource and complexity:
- The scope pattern is stack-owned and copied with SQLITE_TRANSIENT; the existing sqlite3_finalize() and free(kf_sql_heap) paths remain unchanged.
- The predicate narrows scoped PageRank rows and adds no thread, worker, heap-owner, or shared-state lifecycle.

Verification:
- Red: focused ASan/UBSan test failed at tests/test_mcp.c:4494 because GlobalKeyFunction was present.
- Green: focused ASan/UBSan test passed 1/1; MCP ASan/UBSan suite passed 222/222.
- make -f Makefile.cbm lint-source-safety passed both source-safety checks.
- git clang-format --diff HEAD reported no changed-line formatting edits.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-16 03:31:33 -04:00
Martin Vogel 5d150ea051 Merge pull request #1109 from DeusData/fix/601-cypher-exec-deadline
fix(cypher): add a wall-clock execution deadline to abort runaway queries (#601)
2026-07-16 04:56:50 +02:00
Martin Vogel 3d6aa6791d Merge pull request #1048 from DeusData/fix/999-ci-config-infra-routes
fix(pipeline): stop CI/tooling config URLs from minting infra Route nodes
2026-07-16 04:19:42 +02:00
Martin Vogel 6bd11ffb58 fix(cypher): add a wall-clock execution deadline to abort runaway queries
An unbounded whole-graph OPTIONAL MATCH or a GROUP BY that produces roughly one
group per node does O(bindings x groups) work in execute_return_agg and can run
for minutes on a large graph before emitting a single row. The 100k row ceiling
never fires (no rows are produced), so query_graph just hangs with no partial
result and no error (#601).

Arm a monotonic wall-clock budget (default 30s) at query entry and check it
(throttled, every 1024 iterations) in the scan, relationship-expansion and
aggregation hot loops. On expiry the query aborts with a clear, actionable error
instead of hanging. A thread-local test hook forces the budget so the guard is
covered by a deterministic reproduce-first test; a companion test confirms the
default budget does not false-positive on a normal query.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-16 03:09:27 +02:00
Martin Vogel 90d7315cf9 Merge pull request #1108 from DeusData/fix/964-1077-fqn-collision
fix(fqn): preserve full filename in File-node QNs so sibling files don't collide
2026-07-16 02:52:00 +02:00
Martin Vogel 848375912d Merge pull request #1098 from JhohanBustamante/fix/cypher-composite-props
fix(cypher): return whole value for composite and escaped properties
2026-07-16 02:47:38 +02:00
Martin Vogel b0aead257e Merge pull request #991 from harshitaajoshi/fix/cypher-coalesce-where-874
fix(cypher): support coalesce() and other multi-arg scalar functions in WHERE (#874)
2026-07-16 02:44:33 +02:00
Martin Vogel 0563caeec2 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	tests/test_lang_contract.c
2026-07-16 02:35:43 +02:00
Martin Vogel 81e7b73647 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	tests/test_lang_contract.c
2026-07-16 02:31:18 +02:00