Commit Graph
244 Commits
Author SHA1 Message Date
Martin Vogel f6afd6138e fix(mcp): drop the tool title accessor with the title field
Tool definitions no longer carry a separate title (the name is the MCP
display fallback), so the accessor main added for the pi adapter has no
field to read. The adapter already used the name when no title existed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel e93db6a95a test(cli): isolate activation runtime
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

# Conflicts:
#	tests/test_cli.c
2026-09-05 06:05:08 +02:00
Martin Vogel 48149cac08 fix(output): align discovery defaults and help
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 4a9fcd97d0 feat(cli): add explicit quiet output mode
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 5f58e233dc fix(output): preserve compact output safety signals
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 3909e5a0e1 feat(mcp): make tool output lean and lossless
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 7d20ff9398 fix: register Claude hooks without a shell
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-04 22:00:37 +02:00
Martin Vogel e63292a8d4 Merge branch 'main' into fix/env-int-parse-strict 2026-09-03 21:57:52 +02:00
Martin Vogel 0ac290e2e2 fix(cli): drop the POSIX probe-counter store that Clang 23 rejects
`g_mcp_command_path_probe_counter` is only ever read inside
`cbm_json_mcp_probe_command_path`, which is compiled for Windows alone.
On POSIX test builds the setter stored a pointer nothing read, and Clang
23 (Homebrew LLVM 23.1.0, which `brew install llvm` now pours on the
macOS LSan leg) flags that as `-Wunused-but-set-global`; with `-Werror`
the whole test-runner build dies at `src/cli/cli.c:1768:21`.

Keep the counter where a probe can happen (Windows) and make the POSIX
setter an explicit no-op with the reason spelled out. The three tests
that install a counter keep asserting it stays at zero on every platform,
which on POSIX is exactly what "no classifier" means.

Verified in the failing arena: `origin/main` `src/cli/cli.c` fails under
clang 23.1.1 with the CI diagnostic; with this change it compiles clean,
as do all 105 built `src` TUs and all 155 test + extraction TUs under the
same flags. `cli` suite: 292 passed with Homebrew LLVM 22.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-03 15:48:59 +02:00
Joshua RichterandClaude Opus 5 6b801eec68 fix(env): stop an unreadable environment setting from picking a wrong default
atoi and atol answer 0 for text they cannot read, and 0 is a real setting at
three places in this project. So a typo, a trailing unit such as "30s", or a
stray space silently chose a value nobody asked for, and nothing on screen
said the setting had been dropped.

  src/mcp/index_supervisor.c  CBM_INDEX_WORKER_TIMEOUT_S
  src/cli/hook_augment.c      CBM_HOOK_DEADLINE_MS
  src/mcp/mcp.c               CBM_INDEX_MAX_RESTARTS

CBM_HOOK_DEADLINE_MS was the worst of the three. atoi answered 0, 0 is below
HA_DEADLINE_MIN_MS, and the clamp then handed back 50 ms -- the SHORTEST
deadline the setting allows, for a setting whose only purpose is to give the
hook more room. The comment above that function records a hunt for hook runs
that never finished, 0 of 24 real sessions, which is the exact symptom a
silently-shortened deadline produces.

CBM_INDEX_MAX_RESTARTS lost twice. A typo kept the default of 100, and
CBM_INDEX_MAX_RESTARTS=0 -- which reads as "do not restart" to anybody who
sets it -- also kept 100. The setting did the opposite of the request.

CBM_INDEX_WORKER_TIMEOUT_S fell through to the 15-minute default, so a test
set to give up after 30 seconds hung for 15 minutes with nothing to explain
why.

The fix adds one helper rather than three copies of the same check:

  bool cbm_env_long(const char *name, long *out);

It answers true only when the variable is set, is not empty, and reads
cleanly from its first character to its last. It holds no policy -- no
minimum, no maximum, no default -- because the three sites disagree on all
three, and a helper that guessed would be wrong at two of them. The shape is
the one src/main.c:1104 already uses: an end pointer, errno, and a check that
nothing was left over. It also refuses a leading blank, which strtol would
otherwise step over, so " 5" is a slip rather than the number 5.

Each site keeps its own rule:

  worker timeout    an unreadable value keeps the 15-minute default AND logs
                    the value it dropped
  restart cap       0 now means no restarts; an unreadable value keeps 100
                    AND logs the value it dropped
  hook deadline     an unreadable value now yields HA_DEADLINE_DEFAULT_MS,
                    not the floor. This one stays silent on purpose: the file
                    includes no log header and writes no stderr, because its
                    output is hook protocol.

The restart-cap parse was lifted out of a very large function into a named
index_restart_cap(), so it can be read and reached on its own.

Four tests come with the change. The two that pin the user-visible behaviour
were seen failing before the fix:

  FAIL tests/test_cli.c:402: ms == 50, expected HOOK_DEADLINE_DEFAULT == 2000
    (with "unreadable value \"abc\" gave 50 ms" printed above it)
  FAIL tests/test_cli.c:434: cbm_index_restart_cap_for_testing() == 100,
    expected 0 == 0

After the fix, TEST_SUITES="platform cli mcp" reports 518 passed, 2 failed.
The full suite reports 7635 passed, 2 failed. Both failures are in
tests/test_cli.c (lines 1826 and 6802), print "error: one or more agent
cleanup operations failed", and reproduce on a clean tree without this
change -- they depend on the coding agents installed on the machine.
make -f Makefile.cbm lint-ci passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-02 21:55:27 -04:00
Martin Vogel c38aa35c27 Merge pull request #1777 from ocochard/feature/freebsd-pkg-managed-detection
fix(freebsd): treat ${LOCALBASE}/bin as pkg-managed; resolve self via sysctl
2026-09-03 00:07:06 +02:00
Martin Vogel 4d8ac9e1ce Merge pull request #1802 from rarepops/fix/install-clients-registry-1798
fix(cli): apply client selector to registry integrations (#1798)
2026-09-02 19:22:45 +02:00
Martin Vogel 7fedaba331 Merge branch 'main' into feature/freebsd-pkg-managed-detection 2026-09-02 11:45:45 +02:00
Martin Vogel 1e3f2fd209 Merge pull request #1487 from JCVIDev/feat/opencode-context-hooks
feat(cli): extend the OpenCode plugin to every documented context surface
2026-08-31 13:32:07 +02:00
Martin Vogel 51c48e2093 Merge pull request #1858 from DeusData/fix/issue-1834-pi-stdin-transport
fix(pi): send tool arguments over stdin
2026-08-29 01:07:08 +02:00
Martin Vogelandjc fb7cd2f8ef style(cli): clang-format the extended OpenCode generator block
Maintainer mechanics on top of the accepted feature: merge current main and
format the 24 violations in the edited region of client_adapter.c — the
only red CI leg. No content change; the generated plugin is byte-identical
apart from whitespace in the C literals' layout.

Co-authored-by: jc <jcvi007@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-28 14:58:40 +02:00
Martin Vogel 93f7d27c44 Merge remote-tracking branch 'origin/main' into pr1487-work 2026-08-28 14:47:41 +02:00
Martin Vogel d706c33ac2 Merge pull request #1337 from abendrothj/bash-tool-hook-augment
feat(hook-augment): augment Bash tool searches via graph
2026-08-28 14:40:32 +02:00
Martin Vogel f899770865 Merge pull request #1342 from cmeerw/fix/netbsd-daemon-support
fix(netbsd): add NetBSD support to daemon IPC and runtime
2026-08-28 13:43:07 +02:00
Martin VogelandJake Abendroth 09c6624166 perf(hook-augment): gate no-op Bash calls before identity hashing
Maintainer commit on top of the Bash-matcher work, closing the cost
question the 2026-08-03 review left open before default-on shipped.

Measured on a production binary (2026-08-28): every hook invocation paid
~1.1-1.2 s of user CPU BEFORE any gating — not in cbm_mcp_server_new as
first suspected, but in main()'s executable-identity hash for cohort
admission, which the hook-client path runs ahead of any input inspection.
With Bash in the installed matcher, nearly every agent command (`ls`,
`make`, `git`, ...) would have paid that for nothing: agents issue far
more Bash calls than Grep/Glob, and almost none are searches.

The fix: main() now reads stdin on the hook-client path and exits before
identity hashing when the event can never produce output — an un-forced
PreToolUse Bash event whose command yields no search pattern or no
queryable token. A no-op touches no shared state, so it needs no
identity. The gate is a pure exported function using the exact parser
pair ha_process uses, so gate and augmenter cannot disagree; anything
uncertain (lifecycle events, other tools, coverage adapters, oversized
stdin, forced events) pays full fare unchanged. Consumed stdin is handed
back through a prefetch so downstream readers see it exactly once.

Measured after: no-op Bash calls ~10 ms wall / 0.00 s user CPU (was
~1.2 s); real Bash searches and Grep/Glob unchanged at full fare.
cli 259/0, mcp 179/0.

Co-authored-by: Jake Abendroth <contact@jakea.net>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-28 12:37:53 +02:00
Martin Vogel 10a92a0ebd Merge remote-tracking branch 'origin/main' into pr1229-work
# Conflicts:
#	README.md
#	docs/index.html
#	docs/llms.txt
#	pkg/npm/README.md
#	src/cli/agent_profiles.c
#	src/cli/agent_profiles.h
#	tests/test_agent_profiles.c
2026-08-28 09:22:12 +02:00
Martin Vogel b0a3ca2861 Merge pull request #1105 from nguiaSoren/feat/335-watcher-enabled-config
feat(config): add watcher_enabled config key to disable the background watcher (#335)
2026-08-27 19:40:54 +02:00
Martin Vogel ee32816e3c fix(pi): send tool arguments over stdin
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-27 18:51:36 +02:00
Martin Vogel 72ee88054f feat(cli): add Grok Build as an install target
Grok Build (xAI's grok CLI/TUI) already reached codebase-memory-mcp through
its Claude/Cursor compat layer, but a Grok-only user got nothing. Detect
$GROK_HOME (default ~/.grok) or a grok CLI and install:

- an owned [mcp_servers.codebase-memory-mcp] managed block in config.toml;
  a same-name foreign table is left byte-identical because a second header
  would make Grok reject the whole file, while the known owned shape written
  by `grok mcp add` is adopted. No env forwarding: grok 1.0.5 passes the
  full parent environment to stdio servers (verified), unlike Codex (#1562).
- an owned rules file under $GROK_HOME/rules/ (always scanned, all projects)
- the shared skill under $GROK_HOME/skills/
- Scout/Verify/Auditor agents with named-server mcpInheritance and the tier
  allowlist spelled out as the server__tool ids Grok's search_tool/use_tool
  dispatcher accepts (inheritance filters per server, never per tool)
- hooks withheld: Grok's passive events discard stdout and PreToolUse honors
  only deny/rewrite decisions, so there is no context-output contract.

Verified against grok 1.0.5: inspect discovers the server, agents, skill and
rules; mcp doctor handshakes and discovers 15 tools. --clients=grok token,
README/npm/llms/landing-page matrices (43 -> 44 surfaces), and smoke coverage.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-27 13:20:33 +02:00
JCVIDev 0b67621d3a Merge branch 'main' into feat/opencode-context-hooks 2026-08-27 01:42:36 +02:00
soren 680bc72e75 feat(config): add watcher_enabled master switch for the background watcher (#335)
Add a persistent `watcher_enabled` config key (default true). When false, the
daemon host skips building the background watcher entirely: the poll thread
never starts and no projects are registered. Manual index_repository is
unaffected, and the config wiring stays unconditional so auto_index /
auto_watch keep working even when the watcher is off.

This is distinct from auto_watch, which only gates per-session registration
while the watcher IS running. watcher_enabled is the master switch that stops
the subsystem from starting at all — the request in #335 (doc-heavy repos where
every save churns the graph baseline).

The key is read once, when the daemon builds its host state. Changing it takes
effect when the daemon next starts; docs/CONFIGURATION.md says so explicitly.
A disabled watcher is not a daemon-startup failure — the daemon still comes up
and still owns IPC, HTTP/UI, project locks, and manual indexing; only the
watcher and the in-memory store backing it go unbuilt.

- cli.h/cli.c: CBM_CONFIG_WATCHER_ENABLED key + cbm_config_watcher_enabled()
  predicate (NULL-safe, default true), registered as a CONFIG_KEYS row next to
  auto_watch. The table is no longer only a print list: since #1522/#1558 it
  also backs key validation, so `config set/get/reset watcher_enabled` would be
  rejected as an unknown key without that row, and the default recorded there
  (true) is the one the table comment asks to keep in sync with the runtime
  reader's fallback.
- daemon/host.c: gate watcher construction in host_state_prepare() and the
  thread create in host_background_start(); log watcher.disabled when skipped.
  Teardown needed no change — stop NULL-checks, join is gated on
  watcher_started, and cbm_watcher_free is NULL-safe.
- test_cli.c: cli_config_watcher_enabled_default_and_persist pins the config
  layer — default on, disable/re-enable spellings, persistence across reopen,
  and a NULL-config default (a config-store failure must never silently
  disable the watcher).
- docs: document watcher_enabled, and add the previously-undocumented auto_watch
  row to CONFIGURATION.md so the watcher knobs are described together; README
  example.

Closes #335.

Signed-off-by: soren <nguiasoren@gmail.com>
2026-08-24 07:25:34 +09:00
Rares Popa 1e25d962d6 fix(cli): apply client selector to registry integrations (#1798)
Signed-off-by: Rares Popa <2606875+rarepops@users.noreply.github.com>
2026-08-22 20:42:04 +02:00
Christof Meerwald 3e1703d70a fix(netbsd): add NetBSD support to CLI, daemon IPC, and runtime
Extend Linux/FreeBSD codepaths in runtime.c to also cover NetBSD, and add
NetBSD-specific peer PID retrieval via LOCAL_PEEREID in ipc.c.

Signed-off-by: Christof Meerwald <cmeerw@cmeerw.org>
2026-08-22 13:04:02 +00:00
DavidHLP fa3a3ea3ac style(cli): format OMP agent directory changes
Signed-off-by: DavidHLP <lysf15520112973@163.com>
2026-08-21 20:07:37 +08:00
DavidHLP 410cd9a334 fix(cli): respect OMP agent directory and preserve instructions
Signed-off-by: DavidHLP <lysf15520112973@163.com>
2026-08-21 20:07:37 +08:00
DavidHLP f518eaee7b feat(cli): add Oh My Pi (omp) as 44th client surface
Add omp (Oh My Pi) as a STABLE, detected client surface with full
four-layer integration: MCP server registration, durable instructions,
skill, and three direct-MCP tiered subagent profiles (Scout/Verify/
Auditor). This matches Claude Code's integration depth — omp subagents
have direct MCP access (confirmed via ablation testing), unlike
parent-handoff-only clients (Pochi, Cursor, Rovo, Augment).

Schema (agent_clients.h):
  New CBM_AGENT_CLIENT_OMP enum value, positioned before COUNT.

Profile (agent_clients.c):
  - stability: CBM_AGENT_STABLE (auto-detected, auto-installed)
  - capabilities: MCP | INSTRUCTIONS | SKILL | AGENT
    No HOOK — omp's context event is fire-once advisory only; the
    recommended upstream path is skill + agents + MCP, not TS hooks.
  - detection_command: "omp" (specific enough per existing clients)
  - marker: ~/.omp/agent (omp-managed config root)
  - resolve_path: ~/.omp/agent/mcp.json
  - json_client: true (uses standard mcpServers JSON edit path)
  - agent_json_canonical: added to type:"stdio" branch alongside
    GitLab Duo and Visual Studio (omp's schema expects this field)

Dialect (agent_profiles.h/.c):
  New CBM_GRAPH_DIALECT_OMP, direct-capable (not in handoff-only list).
  - tool prefix: "mcp__codebase_memory_mcp_" (single underscore,
    hyphens→underscores — verified at runtime via omp device inventory)
  - render: YAML frontmatter with tools (read/grep/glob + tier-specific
    MCP tool allowlist), read-summarize: false (exact source verification
    for evidence tiers — omp's read returns structural summaries by
    default), autoloadSkills: [codebase-memory] (subagents start blank)

Install/uninstall (cli.c):
  - install_omp_durable_context: writes AGENTS.md + skill +
    3 tiered agent profiles to ~/.omp/agent/
  - uninstall_omp_durable_context: removes managed block + skill +
    tiered profiles
  - legacy_omp_verify_agent_content: migration source for prior installs
  - Wired into install_agent_client_registry and
    uninstall_agent_client_registry loops

Design decisions (documented for reviewers):
  1. No HOOK capability: omp's tool_call event can only block/pass (no
     augmentation channel like Claude's additionalContext). A context-
     event nudge was tested but is advisory-only and excluded from the
     upstream path to keep the PR minimal.
  2. read-summarize: false: omp's read tool returns structural summaries
     by default; Tier 2/3 require exact snippets for material claims.
  3. autoloadSkills: omp subagents start with no conversation history;
     without autoload the skill decision matrix is never loaded.
  4. Direct-capable (not handoff): ablation testing confirmed omp
     subagents can call mcp__* tools directly. write→xd:// dispatch
     does NOT work in subagents ("xd:// is not mounted in this session").

Test coverage:
  - test_agent_clients.c: omp in stable_ids (19 entries), capabilities
    array, json_schemas (type:stdio assertion), foreign_entries
  - test_agent_profiles.c: omp in direct_dialects array, direct-capable
    assertion, dedicated test verifying direct profile has
    mcp__codebase_memory_mcp_ prefix + read-summarize + autoloadSkills,
    and handoff profile excludes MCP tools
  - test_cli.c: required_agents updated to 44, all 43→44 / 37→38 string
    contracts updated across README/npm/index.html/llms.txt/main.c

Build: scripts/build.sh passes (exit 0).
Tests: scripts/test.sh passes (exit 0, all suites green).
Lint: scripts/lint.sh has one pre-existing error in pass_lsp_cross.h
(parameter name mismatch imp_keys/imp_vals vs imp_names/imp_qns),
unrelated to this change.

Signed-off-by: DavidHLP <lysf15520112973@163.com>
2026-08-21 20:07:37 +08:00
Jake Abendroth 36b7b18b4d test(smoke): expect Bash Claude hook matcher
Align the exact Claude PreToolUse smoke contract and documentation with the Bash-enabled search matcher.

Signed-off-by: Jake Abendroth <contact@jakea.net>
2026-08-21 04:43:52 -07:00
Jake Abendroth 23dfea58b1 fix(hook-augment): register Bash search matcher
Wire Bash into the installed Claude PreToolUse matcher, add end-to-end reachability coverage, migrate the previous Grep|Glob matcher, and remove the unused rtk_grep parameter.

Signed-off-by: Jake Abendroth <contact@jakea.net>
2026-08-21 04:21:09 -07:00
Olivier Cochard-Labbé 2bff501a62 fix(freebsd): treat ${LOCALBASE}/bin as pkg-managed; resolve self via sysctl
Two FreeBSD gaps remain after the runtime rework of install/update
(#1566) and the KERN_PROC_PATHNAME self-path fix landed on main.

1. cli_external_manager_name() matches only positive evidence (mise,
   Homebrew, nix, asdf, cargo). ${LOCALBASE}/bin was not among them, so a
   ports/pkg install of cbm was still copied into ~/.local/bin and a
   shell rc was edited -- exactly what externally-managed detection is
   meant to prevent. Recognise ${LOCALBASE}/bin and /sbin on FreeBSD so
   install skips the copy and update defers to `pkg upgrade`.

   LOCALBASE is configurable, so the FreeBSD port passes its real PREFIX
   via -DCBM_PKG_PREFIX and we honour it, falling back to the documented
   /usr/local default for a plain upstream build. --force-binary remains
   the escape hatch for anyone self-managing that prefix.

2. resolve_self_executable() in the HTTP server still read
   /proc/self/exe, which FreeBSD does not mount by default. Add a
   KERN_PROC_PATHNAME sysctl branch, mirroring cbm_detect_self_path().

Test updated: on FreeBSD ${LOCALBASE}/bin/cbm is now externally managed;
a non-LOCALBASE prefix (/opt/local) is not.

Signed-off-by: Olivier Cochard-Labbé <olivier@cochard.me>
2026-08-21 08:14:09 +02:00
Alexander Musichen 1f1832ab37 Merge branch 'main' into fix/pi-extension-tool-schemas 2026-08-18 13:37:23 +02:00
Martin Vogel 773bb037dc fix(test): the stdin gate's #1359 guards survive a registry with no zero-argument tool
#1181 gave list_projects pagination parameters, retiring the last
empty-properties schema — and the two #1359 regression tests leaned on
list_projects as their live zero-argument example, so main went red the
moment the merge train composed (the PR was green on its July base, which
predated these tests).

The gate's schema→decision core is split behind a CBM_CLI_ENABLE_TEST_API
seam, so the zero-argument branch stays pinned directly (empty properties,
absent properties, populated properties) regardless of what the registry
ships; list_projects now asserts its NEW truth (piped args accepted, TTY
still refused); and the schema↔gate parity sweep keeps running over every
tool without the impossible >=1 zero-argument floor. Production behavior
is unchanged — this is the tests catching up with an intended schema
change, plus a seam so they never again depend on a shipped example.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-18 07:15:09 +02:00
Alexander Musichen 4656a5f3e1 Merge branch 'main' into fix/pi-extension-tool-schemas 2026-08-18 04:20:37 +02:00
Alex Musichen 109299f262 fix(pi): match ToolDefinition.execute arity on 0.84.2
Pi 0.84.2 calls execute(toolCallId, params, signal, onUpdate, ctx).
The generated (args, ctx) shape bound the call id as the MCP arguments.

Forward params and signal, pin the @earendil-works/pi-coding-agent
contract in the generated header, and lock the 5-arg form in tests.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-18 04:11:49 +02:00
Martin Vogel 20ad3e5b89 fix(cli): forward CBM_RUNTIME_DIR in the generated Codex configuration (#1664)
Codex sanitizes stdio MCP subprocess environments to the names listed in
env_vars. Since #1645 CBM_RUNTIME_DIR relocates the daemon rendezvous, so a
Codex subprocess that does not receive it looks for the daemon in the DEFAULT
location and never finds it — the same silent client/daemon split
CBM_CACHE_DIR caused in #1562. Both names decide WHICH daemon a process talks
to and are now forwarded unconditionally (forward-if-present semantics);
behavioural knobs (log level, workers, budgets) deliberately stay
unforwarded — that broader list remains #1664's open enhancement question.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 05:40:38 +02:00
Martin Vogel 276664ebff fix(cli): compare our binary path separator-insensitively in MCP ownership (#1582)
gotspatel's live opencode.json stores our entry with backslashes
(`C:\...\codebase-memory-mcp.exe`) while the installer compares its own path
with forward slashes — the same file on disk, refused over the separator
spelling, so op=mcp_install failed on a correctly-installed machine (and on
Windows the dead-path probe rightly reported the binary PRESENT, which turned
the mismatch into a hard refusal).

Ownership comparison now treats `\` and `/` as equal everywhere and folds
case on Windows only, where the filesystem is case-insensitive; POSIX
byte-exactness otherwise holds. The annotated entry that names this binary is
recognised as already satisfied and preserved byte-for-byte.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 05:12:08 +02:00
Martin Vogel 4cd84422ac fix(yaml): accept a UTF-8 BOM as a document prologue (#1656)
PowerShell 5.1's `Set-Content -Encoding UTF8` writes a BOM, so real
Windows-authored Hermes configs start with EF BB BF — and both edit ops
failed content-independently (the reporter's 26-byte reproduction is their
23-byte file plus exactly this BOM; reproduced RED on macOS with the same
bytes, so the platform was never the variable).

The document read now validates past a leading BOM and yaml_doc_init treats
it as a prologue: the first line's structure starts after it, the key lookup
still sees our own section when the BOM immediately precedes it (guarded by a
dedicated no-duplicate-section test), and every edit splices interior ranges,
so the BOM survives writes byte-for-byte. Non-document inputs — keys, entry
blocks, identity scalars — keep the strict no-BOM rule.

Also makes the moved-entry cli test fixture platform-correct: the Windows
dead-path probe can only prove a fixed-drive path absent, so the Windows
branch uses one; a POSIX-shaped path is refused there by design.

Fixes #1656.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 04:54:14 +02:00
Martin Vogel 33d1ecea1b fix(cli): install-entry compatibility repairs for goose and annotated MCP entries
Two install failures with the same root theme — the entry we write is a
compatibility contract with the agent's parser, and both sides of that
contract needed repair:

goose (#1675): ExtensionConfig::Stdio declares `name` as a required serde
field with no default, and goose's loader silently drops entries that fail to
deserialize — install reported success and the extension was invisible. The
goose block now carries `name: codebase-memory-mcp`; the non-goose YAML
schema stays name-free. A CBM_CLI_ENABLE_TEST_API seam asserts the exact
block bytes per schema.

annotated MCP entries (#1630, the deferred field-merge): dbd20eaa recognised
an entry the client annotated ("enabled": true beside our command/type) but
could only leave it untouched, because replacing the whole entry would drop
the client's keys. config_json_like gains
cbm_json_like_replace_field_raw_if_unchanged — splice ONE member's value,
preserving every other byte (comments, ordering, client keys) — and the
upsert flow uses it on the two AUTHORIZED repair channels only:

- a relocating update (the entry names the previous managed binary), and
- the existing Windows dead-path probe, which previously fell back to a
  wholesale rewrite and lost the annotations.

POSIX keeps its doctrine unchanged: a config-supplied path is never trusted,
so a moved-looking entry without that authority is preserved byte-for-byte
and install fails loudly (cli_editor_mcp_preserves_unrecorded_posix_absolute_
entries_without_probe holds). All repair/refusal paths are covered by tests
proven RED on the unfixed flow.

Fixes #1675.
Fixes #1630.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 04:23:50 +02:00
Martin Vogel b54998d242 fix(yaml): accept the #1631 constructs and repair prior owned entries
Four legal-YAML constructs from the reporters' real Hermes configs made
`install` fail permanently (any one of them aborted mcp_install and/or
pre_llm_hook_install):

- exact empty flow collections as values (`plugins: []`, `tool_choice: {}`)
  — now validated key-only in both the mapping-body and sequence document
  scans, mirroring the #1673 empty-mapping exception; non-empty flow
  collections stay rejected.
- block sequences at the same indent as their mapping key (column-0 `- item`)
  — item lines directly after a value-less key are structure, not malformed
  keys, in the root walker, the key matcher, and the sequence mapping-range
  walker.
- double-quoted scalars continued across lines with a trailing `\` — the doc
  loader now precomputes per-line continuation flags; continuation lines are
  value bytes every structural walker skips, and a document ending inside an
  open continuation stays an error.
- mid-word quote characters in plain scalars (`LET'S`) — quotes are scalar
  indicators only at a node start (range start, after `:`, after `-`),
  exactly like the #1639 anchor/alias rule; real quoted values keep their
  protection.

Byte-identity alone also froze users on canonicals older releases wrote:
galaxy's entry had `command:` unquoted, and the goose block gained `name:`
(#1675), so the existing entry was declared FOREIGN forever. An entry under
our key now repairs when it parses as a known prior shape (single command
line, or the pre-name goose block) with a codebase-memory-mcp[.exe] command
basename; anything else stays FOREIGN and the file untouched.

End-to-end: both reporters' full configs (iandol 15.6 KB, galaxy 15.2 KB) now
install with zero agent_config errors, every original line byte-preserved,
and the goose upgrade path rewrites the old block in place. Each construct
carries a distilled regression test proven RED on the unfixed editor.

Fixes #1631.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-17 04:23:50 +02:00
Alex Musichen d918fce287 style: fix clang-format violation in pi adapter
clang-format wants no spaces inside a braced initializer.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-16 22:58:58 +02:00
Alex Musichen cf5eb61c20 fix(pi): return a valid AgentToolResult and request raw JSON
The generated execute forwarded the raw MCP JSON directly, but pi's
ToolDefinition.execute must return { content: [{ type: 'text', text }],
details } — a result without a content array crashes the TUI's
getTextOutput on result.content.filter(...).

Request raw JSON from the CLI ('--json') so the bridge parses the MCP
result instead of the human-readable text, then wrap it: pass the content
array through, throw on transport errors, and stringify anything else.
Adds coverage asserting the corrected execute shape and the --json flag.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-16 22:26:34 +02:00
Alex Musichen 263cf22b9c fix(pi): emit parameters and execute in the generated pi adapter
The generated Pi extension registered each MCP tool as { name, run }, but
Pi's ToolDefinition requires label, description, parameters, and execute.
Tools registered that way carried no parameter schema, so strict providers
such as xAI/Grok reject the request with a 422 'missing field parameters',
and the tools were uncallable because Pi invokes execute, never run.

Emit the full tool shape from the registry: label/description via new
accessors, the input_schema embedded directly as a JSON object literal, and
execute instead of run.

Signed-off-by: Alex Musichen <alex.musichen@gmail.com>
2026-08-16 16:41:36 +02:00
pcristin f277034fe5 fix(yaml): accept empty flow mappings in mapping bodies
Signed-off-by: pcristin <xxxokzxxx@protonmail.com>
2026-08-16 12:28:54 +00:00
Martin Vogel 3162f09173 Merge pull request #1638 from astandrik/codex/fix-1633-codex-hook-preflight-diagnostics
fix(cli): explain Codex hook preflight refusals
2026-08-15 15:44:03 +02:00
Martin Vogel d58962c010 fix(cli): only name an installer that is actually there
`update` hands off to install.sh (install.ps1 on Windows) and prints the command
to run. It built that command from cbm_detect_self_path - the BINARY's directory
- and treated "I resolved my own location" as "the installer is beside me".

Those are different questions. install.sh is placed beside the binary by
install.sh itself, but a binary that was moved, packaged by a distro, or built
from source has no installer next to it. We printed the path anyway:

    bash "/home/<user>/.local/bin/install.sh"
    /usr/bin/bash: /home/<user>/.local/bin/install.sh: No such file or directory

Reported on discussion #1560 (#1632) by a user who was already three releases
deep in install trouble and had just been told, by us, to run a file that does
not exist.

`update` exists to tell someone how to proceed. Ending the interaction on a
command that cannot run is the one outcome it must not produce - and the
fallback text was already there and already correct, naming install.sh as
shipping in the release archive without asserting a path.

The probe goes through cbm_path_info_utf8 so a non-ASCII install directory
resolves on Windows, and rejects a DIRECTORY of that name, because `bash <dir>`
is not a command either. A symlink still counts: it is reported rather than
followed, and the shell runs it perfectly well.

The Windows branch gets the same treatment; it had the identical assumption
about install.ps1.

Reproduce-first and revert-checked: with the probe forced to return true - the
old behaviour - the new test fails with "a directory with no installer must not
be named as one", and passes once it is restored. cli: 276 passed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-15 10:43:15 +02:00
astandrik 00e0cf38d6 fix(cli): harden hook diagnostic contract
Signed-off-by: astandrik <astandrik@yandex-team.ru>
2026-08-15 10:37:24 +03:00