Commit Graph
267 Commits
Author SHA1 Message Date
Martin Vogel 9aa44426f7 Merge origin/main into feat/graph-gate-contract
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 07:17:42 +01:00
Martin Vogel 12920dae15 Merge origin/main into feat/codex-skill-location-compat
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 03:03:02 +01:00
Martin Vogel 8d9a8e30fe Merge origin/main into fix/2204-opencode-extension-untouched
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 00:05:31 +01:00
Martin Vogel e7a7a2b44e Merge origin/main into fix/2671-agent-config-symlinks
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-31 20:06:58 +01:00
Martin Vogel ecd2eab93d Merge origin/main into feat/codex-skill-location-compat
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-31 16:38:29 +01:00
Martin Vogel 23c195fd30 Merge origin/main into feat/graph-gate-contract
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-31 16:38:22 +01:00
Martin Vogel 0d1d44b74d Merge origin/main into fix/2204-opencode-extension-untouched
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-31 16:38:15 +01:00
Divakant Pandey b738549097 Merge main and enforce user ownership for directory symlinks in dir_exists
Follow POSIX symlinks in dir_exists() on top of cbm_path_info_utf8() only when
the invoking user owns both the symlink and the target directory it resolves
to. Leave cbm_agent_registry_path_exists() unchanged, guard the POSIX symlink
tests with #ifndef _WIN32, and isolate XDG_CONFIG_HOME for Linux runs.

Refs #2671
2026-10-31 12:26:21 +05:30
Martin Vogel 4787012023 Merge origin/main into fix/2722-install-managed-binary-path
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-31 06:11:22 +01:00
Martin Vogel 2418526e50 Merge origin/main into feat/codex-skill-location-compat
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-31 06:11:01 +01:00
Martin Vogel e546991175 Merge origin/main into feat/graph-gate-contract
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-31 06:10:40 +01:00
Martin Vogel 502d86aa0c Merge origin/main into fix/2204-opencode-extension-untouched
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-31 06:10:19 +01:00
Martin Vogel 395763cd1d Merge origin/main into fix/subagent-cli-hint
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-31 01:02:43 +01:00
Martin Vogel cf77fb84e2 Merge origin/main into fix/2722-install-managed-binary-path
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-30 22:40:59 +01:00
Martin Vogel 9cbc350720 Merge origin/main into feat/codex-skill-location-compat
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-30 22:30:12 +01:00
Martin Vogel e0546f527a Merge origin/main into feat/graph-gate-contract
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-30 22:30:04 +01:00
Martin Vogel 8f6974e025 Merge origin/main into fix/2204-opencode-extension-untouched
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-30 22:29:56 +01:00
Shane McCarron 7f2fd1081d fix(hooks): tell subagents without MCP tools how to reach the graph via the CLI
The SubagentStart hook matches every agent type, but agents defined with an
explicit tool list (e.g. tools: Read, Grep, Glob, Bash) have no MCP tools, and
SubagentStart input carries no tool list to detect that. Append one sentence to
the SubagentStart context only (indexed and unindexed variants) pointing such
agents at `codebase-memory-mcp cli <tool> --help`. Longest variant is ~1.1 KB,
well inside the 2 KB context buffer.

Closes #2837

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
2026-10-29 10:48:36 -05:00
Divakant Pandey b94a7d7a32 fix(cli): recognize symlinked agent config directories during detection (#2671)
Agent detection used POSIX lstat in dir_exists and rejected symlinks
in cbm_agent_registry_path_exists, causing dotfiles setups with
symlinked configuration directories (e.g. ~/Library/Application Support/Zed
or ~/.cursor) to be reported as undetected and skipped during install.
Use stat so valid directory symlinks are followed and detected while
preserving dangling symlink rejection.

Signed-off-by: Divakant Pandey <divakant01@gmail.com>
2026-10-29 19:39:31 +05:30
Martin Vogel 4a85a2665c Merge origin/main into fix/2722-install-managed-binary-path
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-29 01:58:26 +01:00
Martin Vogel 848975f635 Merge origin/main into feat/codex-skill-location-compat
README agent table: keep main's Claude Code row (drift note) and this
branch's Codex CLI row (skill path + PreToolUse).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-28 18:29:56 +01:00
Martin Vogel c04d41921e Merge origin/main into feat/graph-gate-contract
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-28 18:14:47 +01:00
Martin Vogel 2a195bd202 Merge origin/main into fix/2204-opencode-extension-untouched
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-28 18:13:01 +01:00
Martin Vogel c614edcde2 Merge origin/main into fix/windows-unicode-agent-detection
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-28 18:12:44 +01:00
Divakant Pandey 55844c9da9 fix(cli): use managed binary path for agent configs when externally managed (#2722)
Signed-off-by: Divakant Pandey <divakant01@gmail.com>
2026-10-28 17:26:47 +05:30
Martin Vogel 994432f068 Merge origin/main into feat/codex-skill-location-compat
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-27 20:05:45 +01:00
Martin Vogel 48762667b9 Merge origin/main into fix/2204-opencode-extension-untouched
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-27 19:17:31 +01:00
Martin Vogel 1fb8125861 Merge origin/main into fix/windows-unicode-agent-detection
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-27 19:16:50 +01:00
Martin Vogel f4ad327add Merge origin/main into feat/graph-gate-contract
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-27 19:16:42 +01:00
Martin Vogel 402617555c feat(cli): note when a Claude Code session keeps searching without the graph
The search hook gives each Grep, Glob or Bash search the matching graph
symbols, but a session can keep searching text for questions the graph answers
in one call (#509). Once a session has run three searches with no
codebase-memory-mcp tool call, the next augmentation now adds one factual line
saying so. The next note comes after six searches, then twelve, and any graph
tool call resets the count.

- State: a per-session counter in the process that runs hook requests (the
  daemon), keyed by the payload's session_id, in a bounded 64-slot table that
  evicts the least recently used session, behind a spin lock. Only searches
  that reach the graph lookup count. A search without an augmentation still
  counts, and the note waits for the next augmentation.
- Reset: Claude's managed PostToolUse entry now matches
  "Read|mcp__.*codebase-memory-mcp__.*", covering plain and plugin server
  names. The previous Read-only entry, as installs up to v0.12 wrote it, is
  recognized as owned, so install replaces it in place and uninstall removes
  either.
- Scope: Claude Code only. Codex sends the same event payload with a turn_id
  but has no reset hook, so its sessions are not tracked, and payloads without
  a session_id are not tracked either.
- Cost: a graph tool call now runs one hook round-trip to the daemon for the
  reset, about 130 ms on a local build.

Refs #509

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-27 19:04:47 +01:00
Martin Vogel 8fe97d79af Merge origin/main into fix/2204-opencode-extension-untouched
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-26 02:23:15 +01:00
Martin Vogel 158a0c9da2 test(cli): assert a real install leaves a user-owned module untouched
The byte check in cli_extension_leaves_user_owned_module_untouched_issue2204
ran only after a dry run and the plan receipt, and neither of them writes,
so it could not catch the #2204 append. The test now also runs a real
install over the user-owned module, then checks that the file is
byte-identical and that the run reports it as left untouched. Each phase's
stdout checks are bounded to that phase's slice of the capture, so no phase
can pass on another's line.

With the early return that ends the user-owned branch of
install_generated_client_extension() dropped, the test now fails; before
this change it still passed.

Refs #2204

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-26 02:22:58 +01:00
Martin Vogel 762a6e47d8 Merge origin/main into feat/codex-skill-location-compat
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-26 02:20:26 +01:00
Martin Vogel aaee4848c1 Merge branch 'main' into feat/graph-gate-contract
Brings the opt-in index exit contract up to date with main 0433142a.
The merge is clean; no file this branch changes needed a resolution.

The previous base (fceb25d8) predates the shared CI baseline (72c36044),
whose language-count contract repair is why every test leg failed at
Step 0w on the last run. That failure was in the base, not in this
branch.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-26 01:39:28 +01:00
Martin Vogel 5350b5b83e fix(cli): render the Copilot manifest through the memory core
The PostToolUse change rendered the manifest twice per install and uninstall
(current and previous generation) and freed both with free(), which grew the
raw allocator count in src/cli/cli.c past its baseline (lint-memory-core).
The manifest is now written with cbm_json_writer_allocator() and released with
cbm_free(CBM_MEM_CLASS_OTHER, ...), including the frees that predate the
change, so cli.c drops from 170 to 166 raw sites and its baseline is lowered
to match. The rendered bytes are unchanged, so byte-exact ownership of
manifests written by earlier installs still holds.

Refs #509

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-25 20:57:54 +01:00
Martin Vogel 5bb4ee209a Merge origin/main into fix/windows-unicode-agent-detection
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-25 20:14:25 +01:00
Martin Vogel a18b4a4574 feat(cli): give Copilot searches graph context through a PostToolUse hook
Copilot CLI's preToolUse output can only decide permissions, and its command
hooks fail closed on errors, so the graph context that Claude Code and Codex
searches get could not ride a pre-tool hook there. postToolUse
additionalContext is appended to the tool result the model sees on the same
turn (github/docs content/copilot/reference/hooks-reference.md). VS Code's
agent mode shares the Copilot harness and reads the same ~/.copilot/hooks
manifest.

- Manifest: $COPILOT_HOME/hooks/codebase-memory-mcp.json adds a PascalCase
  PostToolUse entry, matcher "Bash|Grep|Glob|bash|powershell|grep|rg|glob",
  running hook-augment --event PostToolUse --dialect copilot. The previous
  lifecycle-only manifest, as installs up to v0.12 wrote it, stays recognized
  as owned: install upgrades it in place and uninstall removes either
  generation. A foreign manifest still fails closed.
- hook-augment: the Copilot dialect reads either payload form
  (tool_name/tool_input or toolName/toolArgs), maps runtime tool names to the
  Claude names (bash/powershell to Bash, grep/rg to Grep, glob to Glob),
  parses arguments passed as a JSON string, and answers with top-level
  additionalContext.
- Fast path: Copilot's forced PostToolUse goes through the no-op gate before
  executable-identity hashing. The gate also skips tools the augmenter does
  not read, because VS Code's local hook parser ignores matchers, so a
  non-search shell call or an edit exits in about 20 ms instead of paying the
  full round-trip of about 130 ms.
- The context header no longer says the search results are "below": after a
  post-tool hook they are above it.
- Tests: the three Copilot foreign-manifest tests now create
  .copilot/mcp-config.json. Without it Copilot CLI was never detected, so
  install and uninstall never reached the manifest, and the tests also passed
  with the ownership checks removed.

Refs #509

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-25 19:50:35 +01:00
Martin Vogel 619192a990 Merge pull request #2719 from DeusData/feat/codex-pretooluse-hook
feat(cli): give Codex shell searches graph context through a PreToolUse hook
2026-10-25 19:48:37 +01:00
Martin Vogel 3e5234f660 feat(cli): give Codex shell searches graph context through a PreToolUse hook
Codex runs its hooks with the same contract as Claude Code: its shell tool is
named Bash, tool_input.command is a string, and PreToolUse hookSpecificOutput
additionalContext reaches the model (openai/codex hooks/src/events/
pre_tool_use.rs, core/src/hook_runtime.rs). Codex only had the SessionStart and
SubagentStart reminders (#330), so its grep and rg calls never got the graph
context Claude Code's searches get (#715).

- config.toml: the managed block adds [[hooks.PreToolUse]] matcher = "Bash"
  running the same hook-augment command. The previous block (lifecycle pair
  only, as installs up to v0.12 wrote it) stays recognized as owned, so an
  upgrade replaces it in place and uninstall removes either generation.
- Inline forms (#1432): Codex's inline rewrite of our PreToolUse is replaced.
  A user's own inline hooks.PreToolUse is kept and our block then leaves
  PreToolUse out, because TOML cannot hold an inline array and array-of-tables
  entries for the same key; install says so instead of failing.
- hooks.json: the Codex-only PreToolUse Bash entry is added and removed next to
  the lifecycle pair, touching only the entry whose command is ours.

Codex asks users to re-trust changed hook definitions (/hooks).

Refs #715, #509

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-25 17:56:39 +01:00
Martin Vogel c21c42b4d6 test(cli): create the non-ASCII Windows profile through the wide API
The Windows regression test for #2145 created its profile directory
with test_mkdirp and read the result as a boolean. test_mkdirp returns
0 on success, so the test failed as soon as the directory existed and
never reached the detection it was written to check.

Inverting the check is not enough on its own. test_mkdirp uses
cbm_mkdir, which is the narrow _mkdir on Windows. Under an ANSI code
page such as 1252 it does not create the intended name: it creates a
directory whose name is the UTF-8 bytes read as code-page characters.
The old narrow stat finds that mangled directory and the new wide
lookup correctly does not, so the test would pass without the fix and
fail with it. th_mkdir_p goes through the wide API and creates the
real name, the way a non-ASCII Windows profile actually looks.

Since 2b329fe7, Claude Code is detected from settings.json,
.claude.json or the claude CLI, through cbm_file_exists, which is
already wide on Windows; a bare .claude directory no longer marks it
as detected. The test now asserts Cursor, which is detected by its
directory alone, so the assertion goes through dir_exists, the
function this branch changes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 19:30:41 +02:00
Martin Vogel a36c6927c2 Merge origin/main into fix/windows-unicode-agent-detection
Brings #2244 (agent detection through the UTF-8 path wrapper) up to
date with main a2227157. The branch was based on 45c45be5, 1,323
commits behind.

The merge is clean. The merged tree adds exactly this branch's 22
lines to main and removes 6, in src/cli/cli.c and tests/test_cli.c.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 19:30:41 +02:00
Martin VogelandVladislav Rahmanov cb62f63739 fix(cli): read gate thresholds through cbm_env_long
cli_gate_threshold re-implemented the project's strict whole-number
environment reader and missed one case: strtol skips leading blanks, so
CBM_GATE_MAX_PARTIAL_PCT=" 20" was read as 20, although the test lists
a leading blank as unreadable. cbm_env_long (src/foundation/platform.h)
refuses empty text, leading blanks, trailing junk and out-of-range
values, leaves its output untouched on refusal, and reads the Windows
environment the same way every other setting does. Anything unreadable
now falls back to the documented default.

The test's numeric-looking unreadable values move from 10 to 50. At 10
they equalled the default, so a reader that wrongly accepted one still
failed the 20% payload and the test could not tell the difference. At
50 an accepted value passes the run and the test fails; against the
previous reader it does, on " 50".

Requested in the maintainer review of #2092.

Co-authored-by: Vladislav Rahmanov <rahmanov.official@yandex.ru>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 19:28:10 +02:00
Martin VogelandVladislav Rahmanov 864484d3d8 fix(cli): move index verdict exit codes off the usage-error code
The binary already exits 2 for a usage error (an invalid --tool-profile),
and shells and CI tooling read 2 that way. With the quality verdict on 2
a gate could not tell "the index is below threshold" from "the command
line was wrong". The two verdicts move to codes nothing else uses:

  0   indexed, at or above the thresholds     (unchanged)
  1   hard failure in a reachable repository  (unchanged)
  10  indexed, below a quality threshold      (was 2)
  11  target unavailable                      (was 3)

The header now states that these values are a stable contract, and a new
test pins the four numbers so a renumbering fails in the suite rather
than in a caller's pipeline.

Requested in the maintainer review of #2092.

Co-authored-by: Vladislav Rahmanov <rahmanov.official@yandex.ru>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 19:28:10 +02:00
Martin Vogel 20840107a8 Merge branch 'main' into feat/graph-gate-contract
Brings the opt-in index exit contract up to date with main a2227157.
Two conflicts, both additive on each side:

- src/mcp/mcp.c, the error branch of handle_index_repository: main's
  #1665 export-error hint and this branch's "reason" field are
  independent. The reason (target_unavailable vs pipeline_failed) is
  computed first; main's hint selection follows unchanged.
- tests/test_cli.c: main's #2200 update test and this branch's exit
  contract tests were both appended after the same test; both are kept.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 19:28:10 +02:00
Martin Vogel 079fb2067a test(cli): run the #221 long-PATH and Windows shim regressions in the cli suite
The two regressions sat in tests/repro/repro_issue221.c, which builds only
into the non-gating bug-repro runner, so CI could not stop the fix from
regressing. The Windows-only case also skipped on macOS and Linux, and
scripts/repro.sh reports any skipped case as an incomplete board.

Move both into tests/test_cli.c next to the other cbm_find_cli tests,
unchanged apart from their names: the cli suite runs on every CI leg, and
SKIP_PLATFORM is how that suite already marks Windows-only cases.
tests/repro/repro_issue221.c is back to its content on main.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 18:33:20 +02:00
Martin Vogel cad8169697 Merge branch 'main' into feat/codex-skill-location-compat 2026-10-20 05:34:50 +02:00
Martin Vogel 3f612f6b71 fix(cli): keep a user-owned OpenCode module out of the install plan
With --plan, install_generated_client_extension() recorded the extension
before the ownership check and printed the skip notice on stdout. The
receipt therefore listed a write the run would not make, and
`install --plan` no longer printed valid JSON when a user-owned module
was present.

For a user-owned module, plan mode now records and prints nothing. The
dry run prints the "would be skipped" notice instead, alongside the
hook-script notice from #1387, and the real run keeps its "left
untouched" line. The cli test now also checks the receipt and plan-mode
stdout, with a control that the receipt does list the module once it is
no longer user-owned.

Refs #2204

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 00:42:36 +02:00
Martin Vogel ccedf273aa Merge branch 'main' into fix/2204-opencode-extension-untouched
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 00:21:23 +02:00
Martin Vogel c52ca5d34c feat(hooks): name the graph follow-up tools in every search augmentation
When an agent greps for a symbol that the graph knows, the PreToolUse
augmentation (Grep, Glob and Bash searches) lists the matching symbols. It now
closes with one line that is cbm's own statement, outside the untrusted symbol
block, naming the graph tools for the usual next question:

  [codebase-memory] For these symbols, trace_path lists callers and callees,
  get_code_snippet returns the exact source, and search_graph finds related
  symbols by name.

The line carries no repository-derived value and is stated as facts, as Claude
Code asks of hook context. It is appended at the string's real end into
reserved room: the list loop's offset can run past its cap when a row is
truncated, so appending at that offset could have written past the buffer.
Searches without a hit, errors and non-search events are unchanged.

Refs #509

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-19 23:52:05 +02:00
Martin Vogel c2ee12200c feat(mcp): load the structural tools upfront and say what the graph answers
Claude Code defers every MCP tool behind ToolSearch by default: at session
start the model sees only tool names and server instructions, while Grep and
Read are always loaded. Codex does the same and shows the server instructions
as the namespace description of the deferred tools. Agents therefore reached
for grep before the graph tools were even loaded (#509, discussion #2025).

- tools/list: search_graph, trace_path and get_code_snippet carry
  _meta["anthropic/alwaysLoad"] = true (a documented per-tool hint), so the
  structural discovery path loads upfront while the other 14 tools stay
  deferred. About 4.6K characters of schema; clients that do not know the key
  ignore it, as MCP reserves _meta.
- Server instructions (default profile) now state, keywords first and as
  facts, which structural questions the graph answers and where grep fits
  better: 309 -> 999 characters, within Claude Code's 4,096-character cut.
- SessionStart/SubagentStart: the untrusted-metadata label now covers the
  graph-derived project name only. Wrapping the routing sentences too told the
  model to treat cbm's own guidance as data. The other sentences are unchanged;
  sanitization is unchanged.

The initialize byte budget is raised deliberately from 512 B to 1.5 KiB (1,188
bytes now); tools/list stays within its 21 KiB budget (20,938 bytes).

Refs #509

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-18 23:49:01 +02:00