Commit Graph
1055 Commits
Author SHA1 Message Date
Andrew Hundt bc73e8e889 test(arch): analytics work across a polyglot graph (#36)
Contract test confirming the graph-analytics chain (PageRank + architecture)
runs on a multi-"language" graph (nodes from .py/.ts/.go files with
cross-language call edges). Analytics are language-agnostic (graph-level), so
this complements the language-agnostic pagerank/arch unit tests + the
per-language LSP tests: it asserts the CHAIN runs across the polyglot case
(PageRank ranks all 6 nodes; architecture returns OK). language_count is not
asserted — that depends on language metadata populated at real-index time, not
the analytics. store_arch 53 pass.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 22:59:27 -04:00
Andrew Hundt 7a27530838 feat(mcp): push key_functions in _context (close architecture pull-only gap)
The codebase://architecture resource (key functions = where to start tracing)
is an MCP resource — application-controlled/pull-only by spec, only fetched on
explicit @-mention. So the model never got it automatically. The _context
header already PUSHED schema/status/pagerank (the reliable channel into the
model); now it also pushes a bounded key_functions summary (top by PageRank,
honoring key_functions_exclude), so the model knows where to start without
pulling the resource or enabling the hidden get_architecture tool.

Reuses build_key_functions_sql (the quote-escaped path). Bounded by
CBM_CONTEXT_KEY_FUNCTIONS_LIMIT (10, smaller than get_architecture's 25) to
keep first-response token cost modest. Gated on context_injection (default
true). Verified end-to-end: search_graph's first response now includes
_context.key_functions (10 entries) on the self-indexed repo. mcp 108 pass.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 22:45:37 -04:00
Andrew Hundt 1ad7556fac fix(graph_buffer): retain DB on relative root_path (was false-deleted) (#57)
The post-dump verify (B1 mitigation, 63de254) used the strict
cbm_store_check_integrity, which treats a non-absolute root_path (e.g. a
relative repo_path like ".") as bad_root_path → the verify DELETED a valid DB.
Indexing with a relative path therefore failed (status=error) — caught by
self-indexing the repo (cbm cli index_repository repo_path="."); the same
index with an absolute path succeeded cleanly (14723 nodes).

Switch the verify to cbm_store_check_integrity_full with the path_only
out-param: a path-only defect (cosmetic root_path) is RETAINED, consistent
with #557 resolve_store — only genuine structural corruption (>5 project
rows) is deleted. Verified: relative-path index now returns status=indexed;
absolute-path index unchanged.

test_graph_buffer gbuf_dump_relative_root_path_retained locks this in.
graph_buffer 50 pass.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 22:37:03 -04:00
Andrew Hundt 3b208736b3 store.c: bound the architecture file-tree dir build (was unbounded strcat)
The architecture file-tree builder concatenated path components into a stack
char dir[CBM_SZ_512] with strcat, bounded only by ST_MAX_PATH_DEPTH (component
COUNT) — not total length. A file_path with >512 chars across components would
overflow the buffer. Replace the strcat loop with length-checked appends that
stop (truncating that dir key) when the next segment wouldn't fit. Normal
paths are unchanged; store_arch 52 pass (exercises the file tree).

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 22:14:41 -04:00
Andrew Hundt 8bad3e9666 fix(mcp): escape quotes in key_functions exclude → close SQL injection
build_key_functions_sql baked each exclude glob (from the get_architecture
`exclude` MCP tool arg AND the key_functions_exclude config) into the SQL
text as `AND n.file_path NOT LIKE '<glob_to_like output>'`. cbm_glob_to_like
copies single quotes verbatim, so an exclude value containing ' broke out of
the string literal — e.g. exclude=["' OR '1'='1"] became
`NOT LIKE '' OR '1'='1'`, an MCP-input-reachable SQL injection.

The search path is NOT affected (it binds LIKE patterns as ?N parameters via
sqlite3_bind_text, which is safe), so the fix is local to key_functions: add
sql_escape_quotes() (doubles ' -> '', the standard SQL string-literal escape)
and apply it to the glob→like output before interpolation. The bound-parameter
callers of cbm_glob_to_like are intentionally untouched (escaping there would
double-escape).

Found by the quality/security sweep agent; verified independently (reachability
via the `exclude` tool arg at mcp.c:3372; cbm_glob_to_like copies ' verbatim at
store.c:2168). The sweep's other 2 findings (cascade_delete_edges NULL-deref,
parse_fp_from_props off-by-one) were verified as FALSE POSITIVES
(remove_edge_from_ptr_array NULL-checks; the hex check uses HEX_LEN not HEX_BUF).
Green: store_arch 52 / mcp 108.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 22:09:55 -04:00
Andrew Hundt 29025375a6 test: validation — ranking across all sort modes + config-knob clamps (#49)
- store_prefix_ranks_project_above_dep_all_sort_modes: proves dep-last
  ranking holds for EVERY sort_by (relevance/name/degree/calls/linkrank),
  not just the default. Dep inserted first (lower id) so a missing dep-last
  in any mode would let the dep win the id tiebreak — a real per-mode check.
- pagerank_invalid_inputs_clamp_cleanly: negative/>1 damping, negative/zero
  epsilon, negative/zero max_iter all clamp to defaults and produce finite
  ranks summing to ~1 (guards the #21 config knobs against bad values, and
  complements the #44 NaN fix).

Green: tool_consolidation 88 / pagerank 45.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 21:58:00 -04:00
Andrew Hundt d7c59a5735 feat(similarity): expose Jaccard threshold as a config-tunable knob (#41)
pass_similarity hardcoded the SIMILAR-edge Jaccard cutoff to
CBM_MINHASH_JACCARD_THRESHOLD (0.95). Now tunable via config.

- pipeline: add similarity_threshold to cbm_pipeline_t + a setter
  cbm_pipeline_set_similarity_threshold(); copy it into the pipeline ctx
  (alongside mode). <=0 (unset) keeps the built-in default.
- pass_similarity: the sim_query_ctx_t worker struct carries the threshold
  (set from ctx); the per-pair check uses sc->threshold when >0, else the
  default. No behavior change unless tuned.
- mcp: CBM_CONFIG_SIMILARITY_THRESHOLD ("similarity_threshold") read via
  cbm_config_get_double in handle_index_repository (default 0.0 = built-in)
  and applied before pipeline_run.

Together with the Leiden resolution knob (prev commit), all graph-analytics
params are now config-tunable: pagerank damping/epsilon/max_iter + edge
weights, Leiden resolution, similarity threshold. mcp 108 / graph_buffer 49 /
full suite clean through the bulk (only known B1 incremental failures).

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 21:50:31 -04:00
Andrew Hundt 76565f3592 feat(architecture): expose Leiden resolution as a config-tunable knob (#41)
cbm_store_get_architecture hardcoded Leiden resolution (gamma) = 1.0 at both
cbm_leiden call sites (arch_clusters + the cbm_louvain wrapper). gamma
controls cluster granularity (>1 -> smaller/more clusters, <1 -> larger).

- store: add a `double leiden_resolution` param to cbm_store_get_architecture
  (after hotspot_limit, matching its existing config-threaded-param pattern)
  and to arch_clusters; cbm_louvain keeps its 1.0 default (separate low-level
  API). NaN/non-positive clamps to 1.0 (same hardening as the PageRank NaN
  fix, #44).
- mcp: CBM_CONFIG_ARCH_RESOLUTION ("architecture_resolution") read via
  cbm_config_get_double (default 1.0) and passed through handle_get_architecture.
- test_store_arch: arch_clusters_resolution_knob verifies the knob is threaded
  (multiple resolutions succeed + valid output) and NaN clamps. 14 existing
  callers updated to pass 1.0 (compiler-enforced). store_arch 52 pass.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 21:27:12 -04:00
Andrew Hundt 39de1eb7b5 perf: root-cause fixes for fork-origin #3 (purge) + #8 (dep N+1)
#3 — incremental purge was O(C·(N+E)): cbm_gbuf_delete_by_file was called
once per changed/deleted file, each a full nodes+edges scan. Add
cbm_gbuf_delete_by_paths (one pass over nodes matching a path membership
set + one edge cascade = O(N+E) regardless of path count); refactor
cbm_gbuf_delete_by_file to delegate to it (DRY — single implementation).
pipeline_incremental.c now purges all changed+deleted paths in one call
(OOM fallback keeps the old per-file path). graph_buffer 49 + incremental
purge tests pass; only the known B1 full-reindex failures remain.

#8 — cbm_dep_link_cross_edges was N+1: one cbm_store_search per import
(<=500) to find a matching dep Module. Now ONE bulk fetch of all dep Module
nodes + an in-memory name->id hash (first-wins, preserving limit=1
semantics) -> O(1) per import. depindex 32 pass.

#5 verified NOT a bug (avoided a false fix): node_degree IS populated by
cbm_pagerank_compute (pagerank.c:422 INSERT OR REPLACE INTO node_degree);
the correlated-subquery fallback only fires pre-PageRank.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 20:57:11 -04:00
Andrew Hundt 511370b2ea store.c: simplify vestigial pr_col ternary (cppcheck duplicateExpression)
cbm_store_search had `pr_col = has_degree_filter ? "pr_rank" : "pr_rank"`
— both branches identical because the pagerank JOIN alias is always
pr_rank (unlike name_col/proj_col which legitimately differ under the
degree-filter subquery wrap). cppcheck flagged the duplicate expression.
Simplify to a direct assignment. No behavior change; tool_consolidation 87 passed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 20:18:55 -04:00
Andrew Hundt 7d589d457b docs: fix stale tool count + document query_graph dep ordering
- README: "14 MCP tools" -> 15 (classic), in all 4 places; clarify that a
  streamlined subset is the default and CBM_TOOL_MODE=classic exposes all 15.
- mcp.c query_graph description: note that dependency sub-project symbols
  (proj.dep.*) are tagged source:dependency, and show the ORDER BY pattern to
  rank project symbols above them (Cypher is declarative — user controls
  ORDER BY, so this is documentation, not auto-reordering).

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 20:07:29 -04:00
Andrew Hundt 1087dfd846 fix(pagerank): reject NaN damping/epsilon in the range clamp
cbm_pagerank_compute's clamp used `damping < 0 || damping > 1` and
`epsilon <= 0`, both of which IEEE-754 NaN bypasses (every NaN
comparison is false). Since damping/epsilon became config-tunable and
cbm_config_get_double uses strtod (which parses "nan"), a
`config set pagerank_damping nan` would let NaN propagate through the
power iteration: every rank becomes NaN, the convergence check
`delta < epsilon` is always false (runs full max_iter), and the pagerank
table is silently corrupted, poisoning the search relevance sort.

Switch to the inverted-range form `!(damping >= 0 && damping <= 1)` and
`!(epsilon > 0)`, which reject NaN because the inner comparison is
false. Found by the silent-failure review agent; verified independently
(strtod parses "nan"; NaN bypassed the old clamp).

test_pagerank.c pagerank_nan_damping_is_clamped: a NaN damping must
yield finite ranks summing to ~1. Green: pagerank 44 passed.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 19:39:26 -04:00
Andrew Hundt 3c619e767e feat(pagerank): expose damping + epsilon as config knobs
cbm_pagerank_compute_with_config already read max_iter and the 12
edge-weight keys from config, but damping (0.85) and epsilon (1e-6) were
passed as hard-coded #defines -- the two core convergence params were
not tunable.

- pagerank.h: CBM_CONFIG_PAGERANK_DAMPING / CBM_CONFIG_PAGERANK_EPSILON.
- pagerank.c: read both via cbm_config_get_double in
  cbm_pagerank_compute_with_config (defaults = existing #defines, so no
  behavior change unless a user tunes them).
- test_pagerank.c: pagerank_damping_epsilon_config_tunable asserts that
  tuning damping (0.5 vs 0.99) materially changes the hub rank value and
  ranks still sum to ~1 under a custom config.

Green: pagerank 43 passed.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 19:08:47 -04:00
Andrew Hundt be82cacbe2 feat(search): rank project symbols above dependencies; make it tunable
The dep-last tiebreak in cbm_store_search only fired for glob searches
(params->project_pattern), so the common prefix-match path
(params->project, which includes proj.dep.*) let a dependency symbol
outrank the project's own symbol of the same name -- the "Path" concern
(a stdlib Path fronting the user's own Path).

- store.c: hoist dep-last into one clause applied consistently across
  ALL sort modes (relevance/degree/calls/linkrank/name) whenever a
  search is scoped to a project; gated on the new param.
- store.h: cbm_search_params_t::disable_dep_ranking (default false =
  deps rank last; inverted sense so {0}-init preserves the safe default).
- mcp.c: search_disable_dep_ranking config key (bool, default false)
  wired through srv->config and documented in the search_graph schema,
  following the existing CBM_CONFIG_* pattern.
- test_tool_consolidation.c: store_prefix_ranks_project_above_dep
  (default ranks project above dep) and store_prefix_disable_dep_ranking_lets_dep_win
  (pure relevance when disabled).

Green: tool_consolidation 87 passed. Full suite unchanged except the 2
known B1 incremental failures.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 19:08:19 -04:00
Andrew Hundt 1d1f9da5f6 test: add B1 corruption-isolation probes + CBM_ONLY_SUITE filters
Two regression probes that isolate the non-deterministic .db corruption
(B1) to the extraction-population hop, each with a decisive negative:

- test_sqlite_writer.c sw_scale_root_path_integrity: variable-length
  properties_json (20-1500B, stressing every page boundary) through
  cbm_write_db. Clean 10/10 -> rules out the variable-record
  page-boundary hypothesis in the writer.
- test_graph_buffer.c gbuf_dump_pipeline_path_integrity: drives
  cbm_gbuf_dump_to_sqlite with a populated + merged gbuf (variable heap
  properties_json + cbm_gbuf_merge ID remap, 15k nodes / 50k edges).
  Clean 10/10 -> rules out the gbuf->dump handoff (build_dump_nodes /
  temp_to_final).

Both confirm the writer and the gbuf->dump handoff are correct on
realistic synthetic data; B1 must originate in real tree-sitter
extraction populating the gbuf (needs real source data to reproduce).

test_main.c: add CBM_ONLY_SUITE fast-filters for graph_buffer + pagerank.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 19:07:26 -04:00
Andrew Hundt b896d7a549 build: add macOS memory-corruption debug targets (test-memory, test-gmalloc)
The existing test-leak uses `leaks --atExit`, which finds heap LEAKS — the
wrong tool for non-deterministic corruption (uninit reads, use-after-free,
overruns) that ASan/TSan miss (e.g. the B1 custom-writer bug). Add two
macOS-native targets that run the nosan binary (ASan replaces malloc, which
defeats these libmalloc knobs):

- test-memory: MallocScribble=1 + MallocPreScribble=1 — freed memory -> 0x55
  (catches UAF), freshly-allocated -> 0xAA (catches uninitialized reads; the
  macOS MSan equivalent, makes uninit deterministic).
- test-gmalloc: DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib (Guard Malloc)
  — guard page per allocation, crashes at the exact overrun/UAF with a stack
  trace. The decisive memory tool.

Both tee to build/c/mem-report.txt; honor CBM_ONLY_SUITE for targeted runs.
Linux falls back to a pointer to test-leak / -fsanitize=memory. Documented in
CLAUDE.md (Memory-Corruption Debugging section). .PHONY updated.

Verified: `make -n test-memory`/`test-gmalloc` expand correctly; default
`test` target unchanged.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 16:30:48 -04:00
Andrew Hundt 416e65fc51 fix(graph_buffer): verify + remove corrupt .db after dump (B1 mitigation)
cbm_gbuf_dump_to_sqlite intermittently emits a structurally-corrupt .db
(B1): on the ~16k-node fastapi repo a full index non-deterministically
persists 0/partial node counts and a garbage project root_path, leaving an
unreadable .db that queries then fail on (PRAGMA integrity_check won't even
prepare). The corruption is ASan/TSan/clang--analyze-invisible in the dump
path; root cause still open.

Add a post-write check in cbm_gbuf_dump_to_sqlite: after cbm_writer_finalize,
open the freshly-written .db and run cbm_store_check_integrity (O(1)
projects-table check, not a full integrity_check). If it fails, unlink the
.db and return GB_ERR so the caller re-indexes instead of serving garbage.
Confirmed firing in the incremental suite (logs dump.verify_corrupt, deletes
the .db).

Makes the system fail-safe against the worst (structurally-unreadable) B1
symptom: no silent corrupt .db is left for queries to read. Does not by
itself fix the underlying non-deterministic write corruption — the 2
pre-existing incremental-accuracy failures (incr_db_deleted_recovery,
incr_accuracy_vs_full) remain; that needs the root-cause fix. Complements
the #557 retain-bad-root_path mitigation in 22e438f.

Verified: store_nodes 56/56, sqlite_writer 7/7, mcp 108/108 (no regression).
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-26 13:52:35 -04:00
Andrew Hundt 06b7bd257d test(sqlite_writer): add scale root_path integrity regression (B1 probe)
Adds sw_scale_root_path_integrity: writes a ~fastapi-scale DB in ISOLATION
(20k nodes / 200k unique edges) via cbm_write_db, then asserts root_path
round-trips exactly, PRAGMA integrity_check == "ok", and node/edge counts.

Purpose: isolate whether the B1 custom-writer corruption reproduces without
the pipeline. Result: PASSES 3/3 — the writer is sound at scale in isolation
(sw_minimal_data covers the tiny case). This rules out the writer's
serialization as the B1 cause and points at the pipeline/parallel handoff
(a data race — to be confirmed with TSan). Also a permanent regression guard
for the custom B-tree writer at scale.

Edges generated with unique (source,target) pairs to respect the table's
UNIQUE(source_id, target_id, type) constraint. Adds sqlite_writer to the
CBM_ONLY_SUITE filter.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-25 06:05:41 -04:00
Andrew Hundt 7cfcff9e68 test: add CBM_ONLY_SUITE env filter for targeted suite runs
When CBM_ONLY_SUITE is set (e.g. "incremental", "mcp", "store_nodes",
"tool_consolidation"), run only that suite then summarize and exit;
otherwise behavior is unchanged. Zero cost when unset. Speeds the
edit/build/test loop for focused work without spinning up the heavy
LSP/stdlib suites.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-25 05:11:40 -04:00
Andrew Hundt 4f71dfb4f4 fix(mcp): retain bad-root_path DBs instead of auto-deleting (#557 data loss)
resolve_store (mcp.c) auto-cleaned (deleted .db + WAL + SHM) whenever
cbm_store_check_integrity flagged the projects table. A single malformed
project root_path (e.g. a numeric value) is a cosmetic projects-row
defect — node/edge data stays intact and queries key off project name,
not root_path — yet it triggered a wholesale delete of the freshly-indexed
DB, losing all indexed data (upstream #557).

Add cbm_store_check_integrity_full(store, &path_only_failure) (store.h,
store.c) which classifies a bad-root_path-only defect (rows fine) apart
from genuine >5-row corruption. resolve_store now RETAINS path-only-defect
DBs (logs store.integrity_retain) and only auto-deletes genuine
corruption. The watcher registration is guarded against a bogus
(numeric/empty) root_path from a retained DB. cbm_store_check_integrity()
is kept as a thin wrapper so artifact.c, pipeline.c, and existing tests
are unchanged.

Mitigates #557 data loss and stabilizes incremental node-count reads (the
count no longer sporadically drops to 0 after a query). Does NOT fully
close the 2 pre-existing incremental-accuracy failures
(incr_db_deleted_recovery, incr_accuracy_vs_full), which stem from a
separate full-reindex-after-deletion node-count discrepancy — tracked
separately.

Verified: store_nodes 56/56 incl. new store_integrity_full_path_only_classification;
mcp suite green; incremental suite shows only the 2 pre-existing failures
(no regression; diff_pct=96 identical pre/post).

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-25 05:11:12 -04:00
Andrew Hundt 3a96bc49c7 perf(pipeline): batch full-index file-hash upserts in one transaction
The full-index hash-persist path (pipeline.c:1127) upserted one row per file under autocommit -- a 10k-file reindex did 10k separate fsyncs under WAL. Wrap the loop in cbm_store_begin/cbm_store_commit (falling back to per-row autocommit if BEGIN fails), matching the incremental path (pipeline_incremental.c:458, persist_hashes). Fork-origin perf finding (#6 from the post-merge latency/complexity audit; git blame = Andrew Hundt). No behavior change: same upserts, single commit.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-25 03:22:31 -04:00
Andrew Hundt 3da5fe800a perf(incremental): batch file-hash upserts in one transaction
persist_hashes (pipeline_incremental.c:452) upserted one file-hash row per file in autocommit -- a 10k-file reindex issued 10k separate COMMITs (each a WAL fsync). Wrap both upsert loops (current discovery + mode-skipped) in cbm_store_begin/commit so N files -> 1 COMMIT. Falls back to per-row autocommit if BEGIN fails (a caller already holds a transaction): 'batched' is gated on BEGIN's return and COMMIT runs only when BEGIN succeeded, so the existing partial-failure policy is unchanged. Fork-origin perf finding #6 (git blame: Andrew Hundt). Compiles clean (make cbm). Other perf findings are upstream-origin (cypher sort/DISTINCT, query_graph JSON, include_connected, properties re-parse -- DeusData #593/#601/#238) or fork-origin but higher-risk (deferred).

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-25 01:19:09 -04:00
Andrew Hundt 36aba14724 fix(mcp): complete §4b split consistency (stale search_code_graph refs + hidden-tools list)
The §4b split (39539ef) deleted the search_code_graph mega-tool but left inconsistent references, found by a systematic tool-name + AI-info-string audit: (1) _hidden_tools pseudo-tool JSON (mcp.c:6365) still listed the 3 now-default tools (14 entries) contradicting its own '11 additional' description -- trimmed to the 11 genuinely-hidden tools (15 TOOLS[] minus 4 default-surface-in-TOOLS[]; get_code is a streamlined-only alias); (2) handle_get_code_snippet error hints (4752, 4923) referenced the deleted tool -- search_graph; (3) search_graph mode-error hints (2663, 2670) pointed at the dead search_in='source' param -- the search_code tool; (4) comments (1566, 4725) updated. String/comment-only; no handler logic changed. The two critical hints previously sent clients/AIs to a non-existent tool from default-surface error paths.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-25 00:23:45 -04:00
Andrew Hundt 74a5c63eff feat(mcp): split search_code_graph into focused tools (§4b)
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>
2026-06-24 22:00:24 -04:00
Andrew Hundt 950fa19d19 test(lang_contract): assert MEMBER_OF reverse edge (§4c bidirectional coverage)
contract_edge_defines_method asserted only the forward DEFINES_METHOD
(Class->Method) edge. The fork's reverse MEMBER_OF (Method->Class) edge —
emitted in pass_definitions.c:322 / pass_parallel.c / pass_normalize.c and
consumed by pagerank.c (member_rank_factor) — had ZERO test coverage
anywhere in the suite. Add the MEMBER_OF assertion so the function<->class
tie is locked in in both directions.

§4c investigation finding (evidence-based, refutes the plan's assumption
that a code fix was needed): the function<->class tie was ALREADY
implemented, not a merge regression. The Method-only gate
(pass_definitions.c:318) is identical in fork (3018b22:270), upstream
(34efbc0:315), and HEAD; DEFINES_METHOD is tested+passing across 9
languages (defines_method_go/rust/java/csharp/php/ruby/kotlin/ts/scala in
test_edge_types_probe.c); the documented "Go parent_class never derived"
bug is already fixed (extract_defs.c:2776-2783); and every class-scoped
callable is labeled Method with parent_class populated (push_method_def /
generic extractor / extract_rust_impl), so there is no class-scoped
Function gap to fix. Broadening the gate would change nothing. The only
real gap was the untested reverse edge, closed here.

Gate: 5981 passed / 2 failed (the 2 are the deferred B1 incremental
store-layer failures, unrelated to §4c).

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-24 19:34:23 -04:00
Andrew Hundt 66c6d00d55 build: wire test-runner-tsan target + gitignore scan-build .plist
test-runner-tsan mirrors test-runner but links vendored objects compiled
with -fsanitize=thread and links the mimalloc object with MI_OVERRIDE=0
(no malloc override, so TSan intercepts the real malloc/free), satisfying
the unconditional mi_* calls in src/foundation/mem.c. TSan cannot combine
with ASan/UBSan and uses the system allocator. Replaces the placeholder
test-tsan stub ("TSan not yet wired for full extraction tests").

  make -f Makefile.cbm test-tsan   # slow; race reports go to stderr

Also add *.plist to .gitignore: `make test-analyze` (clang --analyze)
drops a .plist report per source file in CWD; these regenerable reports
polluted the tree (183 files after the analyzer run).

Verification: built + ran test-runner-tsan over suite_incremental; 0 data
races. The deferred B1 incremental races (HANDLES-edge nondeterminism
under the concurrent parallel pipeline) are timing-nondeterministic and
did not reproduce under TSan's scheduling, so the B1 fix stays a dedicated
follow-up (tracked in notes sec 8f). TSan targets the incremental +
parallel suites that exercise the racy paths.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-24 18:58:36 -04:00
Andrew Hundt aff5df33f9 test(mcp): cover classic-mode tools list (CBM_TOOL_MODE=classic)
The merged cbm_mcp_tools_list (src/mcp/mcp.c:868) defaults to streamlined mode
when srv is NULL, so tests/test_mcp.c:mcp_tools_list only exercised the 3
consolidated tools. The classic-mode path (CBM_TOOL_MODE=classic, mcp.c:876)
that emits all 15 TOOLS[] split tools had no test — the MERGE-TODO flagged at
tests/test_mcp.c:172.

Add mcp_tools_list_classic_mode: setenv("CBM_TOOL_MODE","classic"), capture the
list, then unsetenv BEFORE any ASSERT so a failed assertion cannot leak the
classic setting into sibling tests (which expect the streamlined default). Assert
the classic split tools (index_repository, search_graph, query_graph) are present
and the streamlined-only consolidated search_code_graph + the _hidden_tools
progressive-disclosure hint are absent. Registered in suite_mcp.

Full suite: 5981 passed / 2 failed (the 2 are the deferred B1 incremental races,
unrelated to this test).

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-24 18:01:42 -04:00
Andrew Hundt e494cee91a fix(leak): unhang stack_overflow + lang_contract fork tests under leaks --atExit
tests/test_stack_overflow.c and tests/test_lang_contract.c each have a fork()
helper that runs cbm_extract_file on pathological input in a child process (to
verify no-crash), then bare-waitpids with waitpid(pid, &status, 0). Under
`leaks --atExit` (macOS) the forked child is SIGSTOP'd during heap inspection
and the bare waitpid never returns — the identical hang class already fixed in
b336466 (test_store_bulk.c, src/foundation/compat_fs.c) and 148b4d7
(test_dump_verify_io.c).

Replace the bare waitpid with the WUNTRACED+SIGCONT loop used elsewhere in the
suite (kill(child, SIGCONT) on WIFSTOPPED so leaks can finish and the child
proceeds to _exit), and add #include <signal.h> for kill() in both files. The
helpers keep their existing `return WIFSIGNALED(status)` contract.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-24 17:30:04 -04:00
Andrew Hundt ec206caa4b fix(leak): unhang dump_verify_io_fork_crash under leaks --atExit
dump_verify_io_fork_crash_uncommitted_discarded used bare
waitpid(pid, &status, 0), which never returns when `leaks --atExit` (macOS)
SIGSTOPs the forked child during heap inspection — the identical hang b336466
fixed in test_store_bulk.c (bulk_crash_recovery) and src/foundation/compat_fs.c
(cbm_exec_no_shell). This test was missed by that earlier fix.

Replace the bare waitpid with the WUNTRACED+SIGCONT loop that mirrors
test_store_bulk.c: on WIFSTOPPED, kill(child, SIGCONT) so leaks can finish
inspecting and the child proceeds to _exit(). Add #include <signal.h> for kill().

Unblocks `make test-leak` past this test (the run previously hung here
indefinitely).

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-24 17:03:07 -04:00
Andrew Hundt d85f5958fc build: fix test-runner-nosan link failure (missing lz4/zstd/nomic objects)
The test-runner-nosan target (used by `make test-leak` on macOS for
`leaks --atExit`) failed to link with Undefined symbols: _LZ4_compressBound,
_LZ4_compress_HC, _LZ4_decompress_safe, _suite_zstd, _cbm_artifact_import,
_PRETRAINED_VECTOR_BLOB.

Root cause: OBJS_VENDORED_NOSAN in Makefile.cbm omitted the LZ4/ZSTD/nomic
objects that OBJS_VENDORED_TEST includes, and the test-runner-nosan target
prerequisites/link line missed $(ZSTD_SRCS). Because the LZ4/ZSTD test objects
are compiled with $(SANITIZE) (ASan) and ASan replaces malloc (incompatible with
the `leaks` tool), add sanitizer-free NOSAN variants ($(NOSAN_DIR)/lz4.o,
lz4hc.o, zstd.o) that mirror the existing NOSAN sqlite3/ts_runtime/lsp_all/
preprocessor objects, plus $(UNIXCODER_OBJ) and $(ZSTD_SRCS).

Result: build/c/test-runner-nosan now links; `make test-leak` builds and runs.
(A separate runtime hang at dump_verify_io_fork_crash_uncommitted_discarded
under `leaks --atExit` — a fork+crash+waitpid/leaks interaction — remains and is
tracked separately; it is unrelated to this link fix.)

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-24 16:57:51 -04:00
Andrew Hundt 92f3b86f7a merge(upstream/main): integrate DeusData main into api-consolidation
Deep --no-ff merge of upstream DeusData/main (597 commits, merge-base 988d975)
into fork api-consolidation (HEAD 3018b22), producing a superset that combines
upstream breadth with the fork's surviving depth.

Upstream additions integrated: semantic/embedding search (vendored/nomic),
minhash/SimHash, cross-repo + cross-file LSP + route + pkgmap passes, artifact
import/export, path_alias, git_context, hook_augment, Windows support
(compat_fs/compat_thread), the UI layer, and ~140 expanded tree-sitter grammars.

Fork depth retained: PageRank/LinkRank ranking (src/pagerank/; sort_by=
relevance/linkrank/degree/calls), dependency indexing (src/depindex/;
CBM_INDEX_MODE=DEP), token reduction, and HTTP-endpoint discovery
(src/pipeline/httplink.c, pass_httplinks.c).

Conflict resolution (27 regions across the 39-file intersection):
- src/mcp/mcp.c: kept the fork superset handler pattern (resolve_project_store +
  expand_project_param + inject_context_once) as the single path; UNIONed the
  tool dispatch table; emit status:"indexed", excluded[], cross_service trace.
- src/store/store.c: upstream base + fork pagerank/linkrank/node_degree tables
  and sort_by plumbing; cbm_store_node_degree now excludes INHERITS
  (WHERE type != 'INHERITS'); removed the duplicate cbm_store_get_db.
- src/pipeline/pipeline.c: took upstream run_extraction_phase; grafted the fork
  passes (httplinks, normalize, flush_store dump branch); call
  cbm_pipeline_set_committed_counts before cbm_gbuf_dump_to_sqlite.
- internal/cbm/lang_specs.c: 158 entries combining the fork section_node_types
  with upstream ts_factory/embedded_imports; wired 13 orphaned type-arrays.
- src/depindex/depindex.h: renamed CBM_PATH_MAX -> CBM_DEP_PATH_MAX (it collided
  with constants.h enum CBM_PATH_MAX=1024) + 11 call sites in depindex.c.
- Makefile.cbm: upstream grammar/nomic units + fork CGO targets; added
  httplink.c + pass_httplinks.c to PIPELINE_SRCS; removed mongoose NOSAN
  machinery (mongoose was deleted upstream).
- httplinks restored (the merge silently deleted them because the fork had left
  them unchanged since the base): httplink.c/.h, pass_httplinks.c,
  tests/test_httplink.c; registered RUN_SUITE(httplink) in tests/test_main.c.
- CBM_INDEX_MODE enum unified to FULL=0, MODERATE=1, FAST=2, DEP=3.

Completeness (dual-parent check): 0 upstream files dropped from the merge tree;
6 fork files dropped, all intentional (upstream-superseded): CHANGELOG.md,
vendored/mimalloc/src/{arena-abandon,segment-map,segment}.c,
vendored/mongoose/{mongoose.c,mongoose.h}. Fork features (pagerank, depindex,
token_reduction, httplink) verified present and registered in tests/test_main.c.
src/pipeline/pass_parallel.c has an identical function set to upstream (49=49,
none dropped); the large line-deltas vs upstream are within-function divergence
of the fork extractor, not dropped functions.

Gate (run manually; hooks skip on merge commits): build green
(make -f Makefile.cbm cbm, exit 0); full C test suite 5980 passed / 2 failed /
0 crashes. The fork's pre-existing ASan crash in build_project_list_error_srv is
fixed by upstream's mcp.c rewrite, so the merge is strictly better than the fork
baseline on the crash axis.

Known issue, tracked separately (NOT a merge defect): 2 nondeterministic
failures in tests/test_incremental.c — incr_db_deleted_recovery:772 (diff_pct)
and incr_accuracy_vs_full:803 (full_nodes vs incr_nodes). Cause: the fork's
extractor (internal/cbm/extract_*.c) and parallel-resolve Route-node creation
are not thread-safe under the concurrent use of upstream's parallel pipeline
(run_parallel_pipeline), producing nondeterministic route/infra HANDLES edges
(~234k edge swing between runs). Sequential extraction (CBM_WORKERS=1) is
deterministic and correct. Root cause, bisect, and repro are documented in
notes/2026-06-24-fork-vs-upstream-divergence-assessment.md (section 8f); the fix
is deferred to a dedicated thread-safety audit (the make test-tsan target is
currently a stub and needs wiring).
2026-06-24 16:12:00 -04:00
Martin Vogel 34efbc0bc5 Merge pull request #596 from DeusData/chore/triage-automation 2026-06-24 08:35:12 +02:00
Martin Vogel 170d5a82ab fix(ci): allow-list MSYS2 toolchain URL in binary string audit
The MSYS2 CLANG64 toolchain bakes its package-tracker URL
(https://github.com/msys2/MINGW-packages) into the static Windows .exe via
libc++/compiler-rt. The Layer 2 binary string audit flagged it as an
unauthorized URL, failing smoke-windows (both the standard and ui variants)
while Linux smoke stayed green. Add it to ALLOWED_URLS alongside the other
compiler/linker toolchain URLs (gcc.gnu.org, sourceware.org, launchpad).

Add tests/test_security_strings_allowlist.sh as a reproduce-first regression
guard: it reproduces the BLOCKED failure with a binary fixture and includes a
negative control proving a genuinely unauthorized URL is still rejected. Wired
into scripts/test.sh as Step 6.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-24 03:15:36 +02:00
Martin Vogel dc5a1aa675 Merge pull request #590 from DeusData/vendor/objectscript-grammar
chore(vendor): vendor the InterSystems ObjectScript tree-sitter grammars
2026-06-24 01:21:51 +02:00
Martin Vogel c37848a73c ci: add issue-triage bots (stale, label-actions, area-labeler)
Three native GitHub Actions to automate the mechanical parts of issue
triage:

- stale.yml: actions/stale scoped to the awaiting-reporter label —
  warns at 21 days idle, closes at 35, auto-resets when the reporter
  replies. Never touches PRs or unlabeled issues.
- label-actions.yml + .github/label-actions.yml: posts a templated
  comment when duplicate or awaiting-reporter is applied (duplicates are
  linked, not auto-closed).
- issue-labeler.yml + .github/issue-labeler.yml: adds area labels
  (windows, stability/performance, parsing/quality, editor/integration,
  ux/behavior, cypher, language-request) from title/body keywords.
  Additive only; base bug/enhancement labels still come from the forms.

All third-party actions pinned to full commit SHAs with least-privilege
per-job permissions (issues: write only where required).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-24 01:18:29 +02:00
Martin Vogel 5352d5229d Merge pull request #591 from DeusData/fix/security-alerts
chore(security): resolve flagged Scorecard + Dependabot alerts
2026-06-24 01:15:35 +02:00
Martin Vogel a3336bf346 chore(security): resolve flagged Scorecard + Dependabot alerts
- graph-ui: bump vite ^6.4.2 -> ^6.4.3 and add overrides pinning the transitive
  form-data >=4.0.6 and @babel/core >=7.29.6 (all dev-scope build/test deps, not
  shipped in the binary). Clears the 4 open Dependabot alerts and Scorecard's
  VulnerabilitiesID. `npm audit` now reports 0 vulnerabilities.
- codeql.yml: move `security-events: write` from the workflow top level to the
  `analyze` job (top level is now `contents: read`), resolving Scorecard's
  TokenPermissionsID least-privilege finding. CodeQL still uploads results from the
  job-scoped token.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-24 00:34:05 +02:00
Martin Vogel 880f899099 Merge pull request #532 from DeusData/dependabot/github_actions/softprops/action-gh-release-3.0.1
build(deps): bump softprops/action-gh-release from 3.0.0 to 3.0.1
2026-06-24 00:30:35 +02:00
Martin Vogel 803aadc492 Merge pull request #458 from DeusData/dependabot/github_actions/msys2/setup-msys2-2.32.0
build(deps): bump msys2/setup-msys2 from 2.31.1 to 2.32.0
2026-06-24 00:30:30 +02:00
Martin Vogel 242988bd57 chore(vendor): vendor the InterSystems ObjectScript tree-sitter grammars
Vendors the two MIT-licensed grammars from intersystems/tree-sitter-objectscript
@a7ffcdf (the language vendor's official grammars, ABI 15) that PR #467 needs:

  internal/cbm/vendored/grammars/objectscript_udl/      (.cls)
  internal/cbm/vendored/grammars/objectscript_routine/  (.mac/.inc/.rtn/.int)

Each directory carries the verbatim generated parser.c + scanner.c + tree_sitter/
headers + LICENSE. The one adjustment: each scanner.c's upstream
`#include "../../common/scanner.h"` is repointed to a per-directory
`objectscript_common.h` (a verbatim copy of the upstream common/scanner.h), because
this repo's shared vendored/common/scanner.h belongs to the cfml/fsharp grammars and
differs. Updates MANIFEST.md + THIRD_PARTY.md.

These files are dormant until #467 (the grammar shims + extraction) is rebased on
top; main's build is unaffected.

Refs #462, #467.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-24 00:20:57 +02:00
Martin Vogel 3d4236e861 Merge pull request #486 from mvanhorn/fix/400-windows-persistence-artifact-silent-failure
fix: Surface a real error when persistence artifact export silently fails on Windows
2026-06-23 23:52:36 +02:00
Martin Vogel 090a1a72d5 Merge pull request #542 from mvanhorn/fix/499-global-core-excludesfile-ignore-source
fix: honor git global core.excludesFile as an ignore source
2026-06-23 23:46:24 +02:00
Martin Vogel f82c6aee68 Merge pull request #526 from jstar0/fix/snippet-valid-utf8
fix(mcp): return valid UTF-8 snippets
2026-06-23 23:46:20 +02:00
Martin Vogel 96c1fc38fb Merge pull request #465 from KerseyFabrications/fix/cypher-with-node-props
fix(cypher): preserve node properties through WITH aggregation
2026-06-23 23:46:16 +02:00
Martin Vogel bbaca2a848 Merge pull request #447 from isc-tdyar/fix/string-dispatch-dead-code
fix(extract): string-dispatch CALLS edges never emitted — detection was in dead code
2026-06-23 23:46:13 +02:00
Martin Vogel ca5c0d2a27 Merge pull request #588 from GothUncc/fix/gcc15-strstr-const-qualifier
fix(cli): const-qualify local pointers in codex_hook_strip for gcc 15
2026-06-23 23:08:24 +02:00
Martin Vogel eeca97ecb2 Merge pull request #587 from HackAttack/fix-brew-formula-location
Fix homebrew formula location
2026-06-23 23:08:20 +02:00
Martin Vogel 5e40a24326 Merge pull request #579 from BombaxCeiba/fix/list-projects-2gb-windows
fix(mcp): replace `.db`-targeting `stat()` with `cbm_file_size` on Windows (> 2 GB)
2026-06-23 23:08:17 +02:00
Martin Vogel 56d36ba17e Merge pull request #572 from mvanhorn/fix/558-search-graph-degree-inherits-edges
fix: count INHERITS/IMPLEMENTS edges in search_graph degree
2026-06-23 23:08:13 +02:00
Martin Vogel 879c3cbe5b Merge pull request #566 from jsw6701/fix/kotlin-spring-routes
fix(extract): extract Spring routes from Kotlin controllers
2026-06-23 23:08:09 +02:00