hook-augment's PreToolUse Grep/Glob augmenter required POSIX-style
'/'-prefixed absolute paths in two guards, so a Windows cwd (C:\repo or
C:/repo) always failed: the _WIN32 cwd check returned early, and the
walk-up loop guard (dir[0] == '/') never iterated. The result was a
structural no-op on Windows — zero additionalContext for every Grep/Glob,
even for an exactly-indexed symbol.
Add cbm_hook_path_is_abs(), accepting POSIX "/..." and Windows "X:/..." roots;
normalize backslashes to forward slashes on the Windows cwd; and stop the
parent-climb at the drive root as well as the POSIX root. POSIX behavior
is unchanged.
Adds a cli_hook_augment_path_is_abs regression test (the predicate is
exposed as cbm_hook_path_is_abs via cli.h) covering POSIX and Windows
drive roots — it fails on the pre-fix POSIX-only guard and passes after.
Verified with that test plus a standalone walk self-test (full path
resolves first, then climbs to one level below the drive root, symmetric
to POSIX); clang-format clean on all three files; clang -fsyntax-only on
the translation unit. Not built through the full toolchain locally (no
make/zlib) — relying on Windows CI to confirm the build.
Fixes#618
Signed-off-by: catsmonster <shaked.brand@gmail.com>
The CLI only accepted a single inline JSON argument (cbm cli <tool> '<json>'),
which PowerShell mangles on Windows and which made `cli <tool> --help` return
"repo_path is required" -- the "--help" token was parsed as the JSON args, and
there was no per-tool help at all.
run_cli now resolves tool arguments by precedence: --args-file <path>, a raw
JSON object (back-compat), --flag / --flag=value / bare-boolean flags, piped
stdin, then empty {}. Flags are converted to a JSON object via the tool's
input_schema: kebab-case keys map to snake_case, values are typed
(string/integer/boolean), and repeated flags accumulate into array-typed
properties (--semantic-query a --semantic-query b -> ["a","b"]). stdin and
--args-file are UTF-8-clean and bypass shell quoting entirely (the robust path
on Windows). `cli <tool> --help` prints usage + the tool's flags, generated
from the same input_schema the MCP tools/list advertises -- no hand-maintained
table.
cbm_mcp_tool_input_schema() exposes a tool's schema; cbm_cli_build_args_json()
and cbm_cli_print_tool_help() do the conversion and help.
Reproduce-first: 8 cli tests (string / integer-not-string / bare-boolean /
repeated-array / kebab->snake / --key=value with spaces / bad-positional-errors
/ help) -- RED on a stub, GREEN after. Full suite 5730/0.
Note: true CJK-path support on Windows still needs a UTF-8/wmain entry point
(argv is ANSI-codepage-mangled upstream of parsing); stdin/--args-file mitigate
it and are tracked separately.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
QA round 1 (PR #632) flagged that the SubagentStart hook uses matcher "*",
the same matcher a user is most likely to pick for their own catch-all
SubagentStart hook. Because is_cmm_hook_entry keyed ownership on the matcher
string alone, install would remove the user's "*" entry and replace it with
ours, and uninstall could remove the wrong one.
Add an optional `match_command_substr` to the upsert/remove args, threaded into
is_cmm_hook_entry: when set, an entry must ALSO carry a hooks[].command
containing that substring to be claimed as ours. The Claude SubagentStart hook
passes "cbm-subagent-reminder"; all existing callers leave it NULL, preserving
their matcher-only behavior unchanged. Install/uninstall now only ever touch
CMM's own entry and never clobber a foreign "*" hook.
Add cli_claude_subagent_hook_preserves_user_entry covering the case: a
pre-existing user "*" SubagentStart hook survives both install and uninstall.
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Subagents spawned via the Agent tool do not fire SessionStart, so the
existing SessionStart reminder never reaches them — they start without the
codebase-memory-mcp code-discovery guidance and fall back to grep/file-read.
Register a Claude Code SubagentStart hook (matcher "*") that injects a leaner
variant of the protocol via JSON additionalContext, omitting the
index_repository step since the parent session has already indexed the
project. SubagentStart injects context only via a JSON object on stdout, not
plain text, so the generated script emits a static JSON literal — no runtime
escaping and no python3/jq dependency. Advisory only; installed and removed
alongside the SessionStart hook in install_claude_code_config.
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Under -D_GNU_SOURCE with optimization, glibc's string.h macros make
strstr() on a `const char *` (with a constant needle) yield a
`const char *`. gcc 15 then rejects assigning that to `char *` with
-Werror=discarded-qualifiers, breaking the build:
src/cli/cli.c:1503: error: initialization discards 'const'
qualifier from pointer target type [-Werror=discarded-qualifiers]
Minimal repro (gcc 15.2):
const char *c = "hello";
char *p = strstr(c, "ell"); // gcc -D_GNU_SOURCE -O2 -Werror=discarded-qualifiers
codex_hook_strip only reads through begin/end/cut (offset math and
byte comparisons, never writes), so making them `const char *` is the
correct fix and preserves the function's existing const contract
(`content` is already const). The malloc'd output buffer stays char *.
No behavior change; build succeeds on gcc 15.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jeremy Maserang <gothuncc@gothaunty.com>
* fix(cli): copy and sign the built binary on install instead of re-signing the stale target
`install` (especially with --force) only ad-hoc-signed whatever already sat at
~/.local/bin/codebase-memory-mcp and never copied the binary the operator ran
install from. An operator who built from source and ran `install -y --force`
was silently left with the OLD binary at the target — running stale code while
believing they had upgraded (#472).
Install now mirrors `update`: detect the running binary, copy it to the
canonical target (skipping the copy when we already ARE the target, which would
otherwise truncate the running file), make it executable, ad-hoc sign the
TARGET, and point all agent configs at the target. A different pre-existing
binary is only replaced with --force or interactive confirmation; --dry-run
reports the intended swap without touching anything.
The copy+sign also resolves the secondary report: a hand-copied clang build is
linker-signed (flags=0x20002) and gets Killed:9 when spawned by an MCP host —
re-signing ad-hoc (flags=0x2) makes it launchable, which install now does for
the placed binary.
Adds cbm_copy_binary_to_target() (with a same-file guard) plus regression tests
covering the binary swap and the self-copy guard.
Closes#472
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
* fix(cli): address QA round 1
Extend cli_install_same_file_guard_issue472 with a symlink case: two distinct
path strings resolving to the same inode (which a non-canonical
cbm_detect_self_path vs the hardcoded target can produce) must also be detected
as same-file and skipped, not truncated. POSIX-only (#ifndef _WIN32).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
* test(cli): guard install copy exec-bit assertion to POSIX
cli_install_copies_binary_to_target_issue472 asserted the executable bit on the
copied target, but cbm_copy_binary_to_target only chmods on POSIX (the exec bit
is not meaningful on Windows, and MinGW stat() derives it from the file
extension). Wrap the stat/S_IXUSR assertion in #ifndef _WIN32 so test-windows
passes; the content and creation assertions still run on all platforms.
Refs #472
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
---------
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The standard linux release binary dynamically links glibc 2.38+ and
GLIBCXX_3.4.32, so it fails to start on Debian 11, Ubuntu 20.04/22.04,
RHEL/Rocky 8/9, Amazon Linux 2, etc. — yet install.sh, the npm and PyPI
wrappers, and the binary's own self-update all fetched it by default,
contradicting the "single static binary" promise.
Point every linux install + self-update path at the fully-static
"-portable" asset (gcc -static), which has no glibc floor. macOS/Windows
are unaffected and unchanged.
- install.sh, pkg/npm/install.js, pkg/pypi _cli.py: select -portable on linux
- src/cli/cli.c: self-update download URL AND checksum archive name both use
-portable on linux (they must match or the update fails checksum verify)
- scripts/smoke-test.sh: assert linux self-update targets the -portable asset
- scripts/ci/check-glibc-compat.sh: new guard — runs the binary inside
debian:bullseye (glibc 2.31) and asserts it starts
- _smoke.yml: run the guard on the portable binary in smoke-linux-portable
Reproduced: standard binary -> "GLIBC_2.38 not found" on glibc 2.31;
portable binary runs cleanly.
Antigravity support targeted the pre-unification layout, so it never actually
configured the CLI:
- detection probed ~/.gemini/antigravity/ — the CLI now installs under
~/.gemini/antigravity-cli/ (brain/, mcp/, settings.json)
- MCP config was written to ~/.gemini/antigravity/mcp_config.json — Antigravity
reads the SHARED ~/.gemini/config/mcp_config.json (mcpServers, command/args)
- the SessionStart hook + AGENTS.md targeted the stale dir
Point detection at ~/.gemini/antigravity-cli/, write the MCP server to the
shared ~/.gemini/config/mcp_config.json (creating ~/.gemini/config if needed),
and place AGENTS.md + the SessionStart reminder under ~/.gemini/antigravity-cli/.
Install, uninstall, install --plan, the detection test, and the README table
updated.
Codex, Gemini CLI, and Antigravity all gained lifecycle-hook support, so give
them the same non-blocking SessionStart reminder Claude Code has — stdout is
injected as session context, nudging the agent to use codebase-memory-mcp
graph tools before grep.
- Codex: append a sentinel-delimited [[hooks.SessionStart]] block to
~/.codex/config.toml (idempotent upsert, preserves other content, removable).
- Gemini CLI: SessionStart hook in ~/.gemini/settings.json, reusing the shared
JSON hook upsert (alongside the existing BeforeTool reminder).
- Antigravity: same JSON hook in ~/.gemini/antigravity/settings.json (it shares
Gemini hook semantics).
The reminder command is written to be valid both as a TOML single-quoted
literal and a JSON string (no single quotes, no newlines). Wired into install,
uninstall, and the install --plan receipt. remove_hooks_json now prunes an
emptied event key so removal leaves no stale "<Event>": [] cruft. README
Multi-Agent table updated. Tests cover Codex upsert/idempotency/removal and
Gemini parity.
An installer that configures MCP entries, instruction files, Skills, and hooks
across many agents should let callers see what it will mutate before it does.
`--dry-run` already lists planned writes in human-readable form; this adds the
machine-readable `agent.install.plan.v1` JSON receipt the issue asked for.
`install --plan` runs the real install dispatch in record-only mode — a global
g_install_plan recorder that each write-site function appends to (at the same
point it would write) while all mutations are disabled — then emits JSON with
agents_detected, config_files_planned, instruction_files_planned,
hooks_planned, writes_started:false, network_after_install:false, and
next_safe_command. Because the plan is recorded on the actual install code
path, it cannot drift from real behavior. No config is written, no index
deleted, no network used.
cbm_build_install_plan_json is exposed for testing; the test asserts the
receipt content AND that building it creates no config files.
cbm_ensure_path appended `export PATH="<dir>:$PATH"` to every shell rc,
including config.fish — which is a syntax error in fish and broke users
fish config. Detect a .fish target and emit `fish_add_path <dir>` instead
(idempotent, prepends only if absent). POSIX shells are unchanged.
The symlink-attack vector (predictable /tmp/cbm-code-discovery-gate-$PPID)
was removed in c29e6d5 when the blocking bash gate became a stateless
compiled augmenter writing to stdout only. Add a regression guard so a
predictable temp/state file can never be reintroduced: assert the emitted
gate shim contains no /tmp path and no $PPID, and delegates to hook-augment.
Export cbm_install_hook_gate_script (was static) so the security property
is directly testable.
Cursor was never in the editor-detection table, so install/update only
ever wrote the VS Code config and silently skipped ~/.cursor/mcp.json,
even when Cursor was present. Uninstall removed the Cursor entry but a
subsequent install never re-created it.
Detect ~/.cursor/ as the Cursor agent, register the MCP server in
~/.cursor/mcp.json (mcpServers format, same as Windsurf/Gemini) on
install, remove it on uninstall, and list Cursor in detected agents.
Agent CLIs on Windows are installed as .cmd/.ps1/.exe shims (e.g. opencode
via mise/npm), but find_in_path only probed the bare name, so they weren't
detected. Try .exe/.cmd/.bat/.ps1 per PATH entry on Windows.
Relates to #221 (opencode not detected). The install-time taskkill 'eq'
error from the same report is addressed separately — cbm_kill_other_instances
now uses _spawnvp with an argv array rather than a shell string.
Route Claude Code config paths (skills, .mcp.json, .claude.json,
settings.json, hook scripts) and agent detection through
CLAUDE_CONFIG_DIR-aware helpers, falling back to ~/.claude. Hook command
strings written to settings.json keep the legacy tilde form when the env
var is unset, so existing configs stay portable across HOME values. Prints
a one-line migration nudge when CLAUDE_CONFIG_DIR is set and a legacy
~/.claude tree still exists.
Adds cli_detect_agents_finds_claude_via_env and isolates CLAUDE_CONFIG_DIR
in the existing detection tests so the runner env can't leak in.
Distilled from #321 onto current main (adapts to the v0.7.0 non-blocking
augmenter hook signature, which the original branch predated). Closes#320.
Format drift had accumulated across recent commits (the dry-run lint enforces clang-format-20, which had not run on these commits yet). No functional changes — whitespace/layout only, verified by a clean cppcheck + clang-format + NOLINT lint-ci pass.
The previous PreToolUse hook gated Grep/Glob/Read/Search with 'exit 2'
on the first call per session, which broke Claude Code's
read-before-edit invariant (issue #362) and could deny tool calls under
upgrade/missing-binary failure modes.
Replace it with a structurally non-blocking augmenter:
- New 'codebase-memory-mcp hook-augment' subcommand reads the hook JSON
from stdin and, for Grep/Glob, queries search_graph (in-process, no
shell) and emits hookSpecificOutput.additionalContext. Every failure
path (no project, short token, missing binary, slow query, timeout)
exits 0 with no stdout — the hook physically cannot block a tool call.
- 300 ms SIGALRM/_exit(0) in-process deadline; 5 s settings.json timeout
backstop. Output is written exactly once at the very end, so a
mid-work timeout yields a clean no-op (never partial JSON).
- Matcher narrowed to 'Grep|Glob' (Read explicitly excluded) for Claude;
Gemini matcher narrowed to 'google_search|grep_search' (excludes
read_file) for the same reason.
- The installed shim is a thin wrapper that delegates to the binary;
legacy filename 'cbm-code-discovery-gate' is kept so existing
settings.json entries upgrade with zero migration. Installer refuses
to embed binary paths containing a double quote (shim injection
defense).
- Per-agent 'old matchers' lists let upsert/remove clean up historical
matcher strings during upgrade.
- Smoke tests (8d/8e/8l) updated to assert the new behavior and
regress-test against re-introducing Read in the matcher or 'exit 2'
in the shim.
- Session reminder text updated: 'always Read a file before editing it'
replaces the prior 'fall back to Read only for text content'.
(cherry picked from commit f72c8e68c4d91e52911a569a967ad782ce5472b2)
Detect ~/.kiro/ directory, auto-configure MCP at ~/.kiro/settings/mcp.json
using standard mcpServers format. Adds install, uninstall, detection, and test.
Based on #174 by dLo999, rebased onto refactored install infrastructure.
Co-Authored-By: Dustin Obrecht <dustin@kurtnoble.com>
check_already_latest() exits early before printing dry-run info when
the running version matches the latest release. Skip this check in
dry-run mode so the user sees what would happen.
All hardcoded ~/.cache/codebase-memory-mcp paths now route through
cbm_resolve_cache_dir() in platform.c. Priority: CBM_CACHE_DIR env
var > default ~/.cache/codebase-memory-mcp.
Added db_path_for_project() helper in http_server.c to eliminate 5
copies of the same path construction pattern.
Fixes#154
Co-Authored-By: dLo999 <dLo999@users.noreply.github.com>
Check GitHub releases/latest redirect header before downloading.
Saves bandwidth and avoids unnecessary index rebuilds.
- "Already up to date" when version matches or is ahead
- --force flag to bypass the check
- Graceful degradation when network is unavailable
- Uses same curl dependency as the download itself
Fixes#142
Co-Authored-By: dLo999 <dLo999@users.noreply.github.com>
PATH uses ; on Windows, not : — strtok with : splits C:\Users at
the drive letter colon, producing garbage paths. Also skip S_IXUSR
check on Windows since stat() doesn't set Unix permission bits.
Fixes#159
Co-Authored-By: slvnlrt <slvnlrt@users.noreply.github.com>
Replaces hardcoded ~/.local/bin/ with OS-native runtime detection:
- Windows: GetModuleFileNameA()
- macOS: _NSGetExecutablePath()
- Linux: readlink("/proc/self/exe")
Falls back to ~/.local/bin/ if detection fails.
Fixes install.ps1 placing binary in $LOCALAPPDATA/Programs/ while
configs point to non-existent ~/.local/bin/ path.
Fixes#158
Co-Authored-By: slvnlrt <slvnlrt@users.noreply.github.com>
KiloCode is a VS Code extension — uses the same base config dir.
Apply same platform-aware paths as Zed/VS Code: macOS uses
$HOME/Library/Application Support/Code/, Windows uses %APPDATA%/Code/.
Merge codebase-memory-exploring, codebase-memory-tracing,
codebase-memory-quality, codebase-memory-reference into single
codebase-memory skill with decision matrix, workflows, and gotchas.
Old skill directories are cleaned up automatically during install.
Factual corrections vs original PR: trace_call_path -> trace_path,
removed non-existent tools (read_file, list_directory) and edge types
(OVERRIDE, USAGE, FILE_CHANGES_WITH), removed risk_labels parameter.
Co-Authored-By: gdilla <gdilla@users.noreply.github.com>
- platform.c: move nlen into if(env) block (variableScope)
- userconfig.c: move home into else block (variableScope)
- pipeline.c: remove dead rc!=0 check after httplinks removal
- cli.c: rmdir_recursive propagates cbm_rmdir errors, remove redundant local
- clang-tidy: whitelist cosmetic-only checks, enable all safety checks
- Thread-safe getenv via environ iteration (concurrency-mt-unsafe fix)
- Multiple files: add constants.h includes for named constants
Lower thresholds to best-practice values:
cognitive-complexity: 250 -> 25 (industry default)
statements: 400 -> 200
lines: 800 -> 400
Add NOLINTNEXTLINE to 168 functions that currently exceed these thresholds.
Each suppression will be removed as the function is split in subsequent
commits. Zero net behavioral changes.
Also: extract execute_with_clause from cypher execute_single.
- Serialize return_type, parent_class, base_classes on definition nodes
- DATA_FLOWS edges link callers through Route nodes to handlers
- YAML nested field extraction with dotted key paths (key_path property)
- Rename trace_call_path → trace_path with mode parameter:
calls (default), data_flow, cross_service
- Backward compatible: trace_call_path alias still accepted
- Updated CLI help text and all tests
Fixes OpenCode config to match their documented format:
- `"enabled": true`
- `"type": "local"`
- `"command": ["path"]` (array, not string)
Thanks @chitralverma for the fix and test coverage!
1. taskkill /IM killed the install process itself — now uses
/FI "PID ne <self>" to exclude own process
2. Windows soak jobs missing clang, zlib, make packages — added
to all soak-quick-windows and soak-asan-windows MSYS2 installs
3. Both dry-run.yml and release.yml fixed
1. Windows install/update: append .exe to binary path on _WIN32
(was writing empty command to .claude.json)
2. Vendored tre: add ssize_t typedef for Windows (basetsd.h SSIZE_T)
3. Smoke server: copy UI archive under standard name too, so
install.sh and update --standard work in UI variant smoke tests
CodeQL cpp/toctou-race-condition #32: fopen then chmod on the same
path allows a race where the file could be swapped between write
and chmod. Fix: use fchmod(fileno(f)) before fclose on POSIX.
Windows falls back to chmod (no fchmod).