Three bugs fixed in autotune.py:
1. CWD bug: binary uses getcwd() (not rootUri) for session_root.
_send_batch now passes cwd=repo_root to subprocess so architecture
queries return results for the correct project.
2. Race condition: delete_project_db() before an MCP session triggered
the background autoindex thread which raced with index_repository,
causing isError=true. Removed the deletion — index_repository forces
a full reindex by itself.
3. CBM ground truth was wrong: used C in-degree (cbm_arena_alloc in-degree
21) but PageRank is nearly uniform for CBM's C graph (~0.000035 for all).
Updated to functions verified high by PageRank: cbm_go_stdlib_register,
cbm_extract_imports, cbm_levenshtein_distance, cbm_path_match_score, etc.
Added exclude_ui_tests and calls_boost_excl_tests experiments.
Final scores (9/30 vs 5/30 before fixes):
rtk: 5/10 — tokenize, resolved_command, check_for_hook, try_route_native_command
autorun: 2/10 — session_state, SessionStateManager (Path stdlib bug inflates top slots)
cbm: 1-2/10 — architecture IS improved, C/Go code shown instead of TypeScript UI
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Previous defaults: edge_weight_calls=1.0, edge_weight_usage=0.7,
key_functions_exclude="" (no exclusions).
What changed:
- scripts/autotune.py DEFAULTS: edge_weight_calls 1.0 → 2.0 (call edges
are the strongest signal for production importance)
- scripts/autotune.py DEFAULTS: edge_weight_usage 0.7 → 0.3 (type-reference
edges add noise, dampening improves ranking signal)
- scripts/autotune.py DEFAULTS: key_functions_exclude "" → "graph-ui/**,
tools/**,scripts/**,tests/**" (excluding non-production paths surfaces core
library functions instead of test helpers)
Why: autotune run on 2026-03-26 scored calls_boost_excl_tests at 6/30 across
3 repos (codebase-memory-mcp, autorun, rtk), best of 8 experiments. Baseline
scored 0/30. These defaults are now the baseline that experiments diverge from,
so future autotune runs search the config space around the current best.
Testable: python3 scripts/autotune.py (baseline_25 now starts from these values)
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Previous behavior: autotune set config keys but never triggered PageRank recompute
between experiments — all experiments read stale stored scores, producing identical
results. The binary also got SIGKILL'd on macOS 25+ due to invalidated ad-hoc
signature after `cp` during install.
What changed:
- scripts/autotune.py: replace query_architecture() (async REQUIRE_STORE reindex)
with index_and_query_architecture() — opens one persistent stdio MCP session per
repo per experiment sending 3 sequential messages: initialize → tools/call
index_repository (synchronous, blocks until full pipeline+PageRank completes with
current edge weights) → resources/read codebase://architecture
- scripts/autotune.py: add project_name_from_path() mirroring cbm_project_name_from_path()
from src/pipeline/fqn.c, and delete_project_db() to remove stale DBs
- scripts/autotune.py: add _send_batch() env+cwd params; pass CBM_TOOL_MODE=classic
so index_repository tool is available in MCP session
- scripts/autotune.py: add --top-matches (default 10) and --key-count (default 25)
CLI params; show matched expected names + top-N per repo in output
- scripts/autotune.py: default timeout 60s → 1200s (indexing takes ~40s per repo)
- scripts/autotune.py: add exclude_ui_tests experiment; rename calls_boost_excl →
calls_boost_excl_tests with tests/** added to exclude list
- scripts/autotune.py: save every run to scripts/autotune_results.json (appended,
with timestamp/binary/repos/experiments/best fields)
- scripts/autotune.py: show progress bar (█/░) and ◀ BEST marker in final report
- .gitignore: add scripts/autotune_results.json (generated artifact, not tracked)
Why: edge weights and PageRank iterations are only applied at index time via
cbm_pagerank_compute_with_config(); querying a DB indexed with old weights produces
wrong rankings regardless of config changes. Full reindex per experiment is required.
Also fixes macOS 25+ SIGKILL by rebuilding binary (Makefile.cbm re-signs with
codesign --force --sign - after install).
First run result: calls_boost_excl_tests scores 6/30 (best), baseline 0/30.
Testable: python3 scripts/autotune.py
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Fixes codebase://architecture returning only 10 results all from graph-ui
by wiring hardcoded limits through the config system and raising defaults to 25.
Key changes:
- mcp.c: add key_functions_count config (default 25); wire into
build_key_functions_sql (was hardcoded LIMIT 10 at line 4317) and
build_resource_architecture call site
- mcp.c: add arch_hotspot_limit config (default 25); wire into classic
get_architecture tool handler
- store.c/store.h: raise CBM_ARCH_HOTSPOT_DEFAULT_LIMIT 10->25; add
hotspot_limit param to cbm_store_get_architecture
- store.c/store.h: add sort_by=calls (ORDER BY calls_in+calls_out DESC)
and sort_by=linkrank (ORDER BY linkrank_in DESC) dispatch cases;
add degree_mode config (weighted|unweighted|calls_only) for
min_degree/max_degree filter column selection
- watcher.c/watcher.h: add poll_base_ms/poll_max_ms to struct cbm_watcher;
change cbm_watcher_run and cbm_watcher_poll_interval_ms signatures to
accept base_ms/max_ms params (0=defaults); wire watcher_poll_base_ms and
watcher_poll_max_ms config keys through main.c
- cli.h: extend cbm_config_entry_t with range and guidance fields (5->7)
- cli.c: replace entire CBM_CONFIG_REGISTRY with 7-field entries for all 32
config keys with broadest feasible ranges and actionable guidance strings;
update config list/get/help display to print [range] + guidance per entry
- scripts/autotune.py: new standalone Python 3.9+ script that sends JSON-RPC
directly to the binary via stdin/stdout, tries 7 experiments, scores
against expected top-10 ground truth for 3 repos, resets config on exit
- tests: update all callers of cbm_store_get_architecture (pass 0 for
hotspot_limit) and cbm_watcher_poll_interval_ms (pass 0,0 for defaults)
All 2238 tests pass.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
MinGW python3 is a native Windows binary that doesn't understand
POSIX paths like /tmp/foo.json. Piping file content through cat
(which runs in MSYS2 bash and handles path translation) to python's
stdin avoids the issue entirely.
Fixes Windows smoke test 8a and soak baseline collection.
Investigation: macOS arm64 grew 1MB (43→44MB) but slope was 1123
KB/hr. Linux amd64 grew 3MB (25→28MB) but slope was only 424 KB/hr.
The linear regression fits noise on short runs — small timing
fluctuations produce wildly different slopes on nearly flat data.
For 10-min quick soak: use RSS ceiling (200MB) + ratio (3.0x) +
FD drift + idle CPU. Slope is still reported but not enforced.
For 30+ min runs: slope enforcement at 500 KB/hr (enough samples
for reliable regression).
- Test project: 377 files (80 Python + 40 Go + 40 TSX + configs)
instead of 5 files. RSS baseline now ~47MB (real workload).
- Snapshots every 10s (was 30s) — 7+ data points in 1-min run.
- Reindex every 2min compressed (was 5min) — more cycles per run.
- heap_committed fallback: use RSS when mimalloc reports 0.
- Windows soak: added to quick soak matrix (MSYS2 + python3 + git).
- ASan soak: builds with -fsanitize=address (was building release).
- Collect snapshot with single python3 call (was 6 separate calls).
- Phase 14: detect UI vs standard variant from HTTP server contents
instead of hardcoding --standard (fixes UI smoke failure)
- Windows smoke: install zip + coreutils (sha256sum) in MSYS2
(fixes exit code 127)
- Phase 15: UI HTTP server reachability test — verifies root returns
200 and /rpc accepts POST (skips gracefully for non-UI builds)
- Fix switch fallthrough in release.yml (same MSYS2 deps)
Add isatty() checks to prompt_yn() and update variant chooser.
When stdin is not a terminal and no -y/-n or --standard/--ui flag
is provided, print a clear error and exit instead of hanging on
fgets(). Prevents agents from silently blocking when running
install/update programmatically.
Add smoke test 9b-9: verify non-interactive update fails cleanly.
Install command:
- Add cbm_kill_other_instances() to kill stale MCP servers
- Add cbm_macos_adhoc_sign() to sign binary if placed unsigned
Update command:
- Replace skills-only reinstall with full cbm_install_agent_configs()
- Replace external unzip with cbm_extract_binary_from_zip() via zlib
Refactor: extract 10-agent config loop into cbm_install_agent_configs()
called by both install and update.
New: cbm_extract_binary_from_zip() — in-memory zip extraction with
stored + deflate support, path traversal rejection, bounds checks.
4 unit tests. Smoke: add install.ps1 E2E for Windows.
install.sh: one-liner for macOS/Linux — detects OS/arch (Rosetta-
aware), downloads release, verifies checksum, extracts, signs on
macOS, runs install -y for all 10 agents. Supports --ui flag and
CBM_DOWNLOAD_URL env var for testing.
install.ps1: one-liner for Windows — Invoke-WebRequest + Expand-
Archive + Unblock-File (strips MOTW), installs to %LOCALAPPDATA%,
adds to user PATH via [Environment]::SetEnvironmentVariable.
CI pre-signing: add codesign --sign - step for macOS builds in
both dry-run.yml and release.yml, before archiving. Release
binaries now ship pre-signed.
Phase 12 smoke tests: real HTTP download via local artifact server,
checksum verification, archive extraction, binary verification.
Runs only when SMOKE_DOWNLOAD_URL is set (CI provides it).
Phase 13 smoke tests: install.sh E2E — runs full script with local
URL + isolated HOME, verifies binary placed, signed, runs, and
agent configs created.
CI HTTP server: smoke jobs start python3 HTTP server serving the
built binary as a tar.gz/zip archive + checksums.txt. Enables
Phases 12-13 in CI on all platforms.
Update security allowlist: remove system() entry (eliminated),
add cbm_popen for pgrep. Update README with one-liner Quick Start.
Make project a required parameter for all query tools (search_graph,
query_graph, trace_call_path, get_code_snippet, get_graph_schema,
get_architecture, search_code, index_status, detect_changes,
manage_adr, ingest_traces). Removes implicit fallback to session
project or last-opened store.
When project is missing or not found, return error with list of
available indexed projects so agents can self-correct.
Rename delete_project param from project_name to project for
consistency. Fix smoke test trace_call_path depth param name
(max_depth -> depth).
Expand smoke-test.sh with Phase 5 (MCP stdio transport), Phase 6
(CLI subcommands: install/uninstall/update --dry-run), and Phase 7
(MCP advanced tool calls: search_code v2, get_code_snippet).
Add smoke/smoke-amd64 services to Docker compose that build then
run all 7 smoke test phases. Include in run.sh full/all flows.
Add python3-minimal to Dockerfile for smoke test JSON parsing.
Fix Phase 4a shutdown test to use portable background+kill pattern
instead of `timeout` (not available on macOS).
Add --dry-run, --standard, --ui flags to update command. Fix
clang-tidy readability-implicit-bool-conversion in dry_run ternary.
Tests the actual agent handshake path that Claude Code, OpenCode, etc. use:
- 5a-d: initialize → notifications/initialized → tools/list via bare JSONL
- 5e: Full tool call round-trip (index + search) via JSON-RPC
- 5f: Content-Length framing (OpenCode compatibility)
Uses portable background process + kill instead of timeout command
(not available on all macOS configurations).
- Delete cmd/codebase-memory-mcp/assets/ — skills and codex instructions
are embedded as C string literals in src/cli/cli.c since v0.5.0
- Move BENCHMARK.md to docs/ to reduce root clutter
- Fix setup scripts: replace stale go build with scripts/build.sh
- THIRD_PARTY.md: replace Go module dependencies with vendored C
libraries (sqlite3, mimalloc, mongoose, yyjson, xxhash, tre),
fix CGo reference to static compilation
- Use O_NONBLOCK + clearerr() in Phase 2 fgetc probe to preserve the
60s idle eviction timeout when both kernel fd and FILE* buffer are
empty (fgetc on a blocking fd would otherwise block indefinitely,
bypassing Phase 3 poll timeout and preventing cbm_mcp_server_evict_idle)
- Add #include <fcntl.h> for fcntl()/O_NONBLOCK
- Fix comment: "two-phase" → "three-phase" (implementation has 3 phases)
- Improve Python integration test: verify id:1 (initialize) and id:2
(tools/list) response IDs are both present, not just "tools" substring
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Minified JS in dist/ contains hundreds of string-constant URLs from
bundled npm libraries (React error pages, W3C namespace URIs, CDN
references, OSS credits) that are never fetched at runtime. Scanning
them produced an endless stream of false positives requiring per-URL
allowlisting.
Fix: split the A1 URL check by directory:
- src/ (our code): strict — any external URL is blocked
- dist/ (npm build output): skip inline URL scan entirely
Structural checks (A2-A6) still apply to dist/:
- External <script>/<link> loads in HTML
- Tracking/analytics identifiers
- Iframes, eval/Function, external WebSockets
- UI audit (L6): allowlist bundled framework URLs in dist/ (React error
URLs, W3C namespace URIs, Three.js credits, Google Fonts, Tailwind).
These are embedded by npm deps during Vite build, not our code.
- Binary strings (L2): skip URLs shorter than 15 chars — Windows binary
has byte sequences that strings(1) interprets as "https://H9" etc.
- Allow Google Fonts <link> in HTML (loaded by index.html for Inter/
JetBrains Mono fonts).
MSYS2 mktemp returns /tmp/tmp.XXX but the Windows binary can't resolve
POSIX paths. Use cygpath -m to convert to C:/msys64/tmp/tmp.XXX which
works in both MSYS2 shell and Windows file APIs.
1. smoke-windows: Use CLANG64 (not UCRT64) so runtime matches the
build environment. Binary built with CLANG64 needs CLANG64 DLLs.
2. smoke-unix: Add fail-fast: false so one platform failure doesn't
cancel all 7 other smoke jobs.
3. smoke-test.sh: Capture and display stderr on indexing failure to
help debug smoke-unix errors.
4. release.yml: Same CLANG64 smoke fix.
1. Add zip to MSYS2 build packages (both dry-run and release).
2. embed-frontend.sh: replace xxd with od+sed for hex dump — xxd
requires vim which isn't in MSYS2 CLANG64 by default.
3. embed-frontend.sh: flip IS_MACOS→IS_LINUX — Linux gets ld -r -b
binary (ELF-only), everything else (macOS, Windows) gets the
portable C-array approach via od.
4. release.yml: same Windows archive fix (msys2 shell + .exe detect).
1. dry-run.yml: Add skip_tests and skip_builds workflow_dispatch inputs
for faster iteration. Build jobs use always() to run even when tests
are skipped.
2. Windows archive: Switch from PowerShell Copy-Item to MSYS2 shell
with .exe extension detection. MSYS2 Clang produces .exe suffix
that Copy-Item didn't account for.
3. Linux embed: Use absolute path for ld output when cd'd into dist
directory. Relative path build/c/embedded/*.o was invalid from
inside graph-ui/dist/.
4. release.yml: Same Windows archive fix as dry-run.
- Use env: block instead of shell quoting for WIN32_LIBS in CI
- Forward WIN32_LIBS from environment in test.sh and build.sh
- Add skip_lint input to dry-run workflow for faster iteration
MinGW GCC does not ship ASan runtime on Windows. MSYS2 CLANG64
provides full sanitizer support via compiler-rt. All platforms
now run tests with ASan + UBSan — no exceptions.
- Fix GCC -O2 warnings: calloc for cf (maybe-uninitialized),
suppress stringop-truncation/alloc-size-larger-than (false positives),
guard louvain against negative node_count
- Default compiler: cc on macOS, gcc on Linux/Windows (fixes gcc-14
not found on MSYS2)
- Docker test infra now runs BOTH test (ASan) and build (-O2 -Werror)
- Add scripts/clean.sh, test.sh, build.sh, lint.sh as single source
of truth for local and CI builds (always clean, always parallel)
- Fix -Wmissing-braces: replace {{0}} with memset for struct arrays
- Fix -Wrestrict: use intermediate buffer for same-struct snprintf
- Update dry-run.yml and release.yml to use the new scripts