Commit Graph
55 Commits
Author SHA1 Message Date
SS-42 9e57c6c259 test(smoke): isolate dry-run command state
Signed-off-by: SS-42 <noreply@incogni.to>
2026-07-02 23:01:50 +03:00
Martin Vogel 4a0a9cab90 test(smoke): exercise the full CLI flag surface end-to-end
Companion to the raw-JSON deprecation warning committed on this branch (adds the
smoke coverage that was meant to land with it).

Migrate scripts/smoke-test.sh from raw-JSON `cli <tool> '{...}'` invocations to
FLAG form for every tool (index_repository / search_graph / trace_path /
get_graph_schema / query_graph incl. all Cypher checks / get_architecture /
search_code / delete_project), with identical results verified against a prior
run; flag form passes the Cypher query as one argv token, so the old
JSON-escaping helper is removed. Add dedicated guards for an integer flag
(--limit / --depth), a bare boolean (--exclude-entry-points), a repeated array
flag (--semantic-query -> JSON array), piped stdin, --args-file, and per-tool
--help (RC 0 + shows a flag; unknown tool -> RC!=0), plus the deprecation guard
(raw JSON warns on stderr; flag form does not). Non-CLI phases (incl. Phase 10
binary security) unchanged. Full smoke: ALL PASSED.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-01 13:42:34 +02:00
Martin Vogel 99a21a91a0 fix(smoke): assert codesign --verify rejects a tampered binary (10c)
The "tampered arm64 binary is SIGKILLed (137)" premise is empirically false on
current macOS CI runners for an ad-hoc-signed CLI binary -- the binary has no
CS_KILL/hardened-runtime flag, so a tampered code page is not killed: it
executes the garbage and crashes with SIGILL (exit 132, run 28365724001), not
137. (remove-signature and corrupt-blob both ad-hoc re-sign on exec and run to
exit 0.) So no runtime exit code is a deterministic guard here, and
"tamper -> crash" is near-tautological (zeroed code crashes regardless of
signing).

Assert the real, deterministic integrity invariant instead: `codesign
--verify` REJECTS a tampered copy (the CodeDirectory page hashes no longer
match the modified code), while the untampered binary verifies cleanly (10a).
It is a pure userspace hash check -- no tampered code is executed. The copy is
separate, so the original binary stays intact for the 10e re-sign test.

Refs: github.com/garrytan/gstack#997, github.com/nodejs/node#40827

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-29 14:16:57 +02:00
Martin Vogel 49a9c2615a fix(smoke): tamper signed code, not the signature, to de-flake 10c
Refines the previous 10c change on this branch. Garbling only the signature
blob still ran (exit=0 on dry-run 28360363173): since macOS 11 a binary with
a missing/invalid signature is ad-hoc re-signed on exec by newer macOS and
RUNS, so neither remove-signature nor a corrupt blob triggers the kill.

The reliable "tampered binary is SIGKILLed (137)" trigger is tampering the
SIGNED CODE while leaving the valid signature attached: the kernel validates
each executed page against the intact CodeDirectory hash, finds the mismatch,
and kills the process before user code runs. Zero the entry-point
instructions (LC_MAIN entryoff, extracted dynamically) plus a span of early
__text on a SEPARATE copy, leaving the Mach-O header + load commands intact so
it still parses. The original binary is untouched, so the later 10e re-sign
step stays valid. x86_64 keeps remove-signature (code signing is not enforced
there).

Refs: github.com/garrytan/gstack#997, github.com/nodejs/node#40827

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-29 12:32:13 +02:00
Martin Vogel b27dc640d6 fix(smoke): de-flake 10c by corrupting the signature instead of removing it
Smoke test 10c (Phase 10 binary security E2E) verifies that an arm64 binary
with an invalid code signature is SIGKILLed (exit 137). It did
`codesign --remove-signature` then ran the binary, expecting the kernel to
kill it. But since macOS 11, a binary with NO LC_CODE_SIGNATURE is ad-hoc
re-signed on exec by newer macOS and RUNS (exit 0) -- so the test went flaky
and then consistently red on updated CI runner images (dry-runs 28350650225
and 28354735368 both failed only here; every other job, including the full
cross-platform test matrix, passed).

Corrupt the signature blob in place instead, leaving the LC_CODE_SIGNATURE
load command intact: AMFI then sees "signed but invalid" and rejects the
binary before any user code runs (deterministic 137). Only the signature
blob is garbled (not the code), so the later 10e re-sign step stays valid --
it replaces the blob and the code is untouched. x86_64 keeps
remove-signature (code signing is not enforced there).

Refs: github.com/garrytan/gstack#997, github.com/nodejs/node#40827

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-29 10:55:27 +02:00
Caio Ribeiro 2358b3c452 Fix OpenClaw MCP config install path
Signed-off-by: Caio Ribeiro <caio.ribeiro.clw@gmail.com>
2026-06-27 23:17:49 +00:00
Martin Vogel 8d6dfc5758 Merge branch 'session-combined' 2026-06-09 11:57:03 +02:00
Martin Vogel eab4148de8 Upgrade vendored mimalloc 3.2.8->3.3.2 + platform-correct override; regen vendored checksums (#424)
mimalloc bumped to v3.3.2 (MI_MALLOC_VERSION 30302), which adds static-CRT malloc override on Windows. Makefile: define the real MI_MALLOC_OVERRIDE=1 ONLY on MinGW (enables the static-CRT override entry points — the generic Windows fix for #424); on Unix keep static-link-order override (defining MI_MALLOC_OVERRIDE there compiles alloc-override.c's forwarding defs and crashes macOS with 'mi_free: invalid pointer' since system libs keep the system allocator). The old -DMI_OVERRIDE=1 was never read by mimalloc source — kept as a prod marker. smoke-test.sh: hard-fail on any 'mimalloc: error' in stderr. Regenerated scripts/vendored-checksums.txt for the new tree (security-vendored.sh passes; no dangerous calls in vendored mimalloc).
2026-06-09 11:55:59 +02:00
Martin Vogel 69c16cada4 fix: deliver the static -portable linux binary on all install/update paths
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.
2026-06-09 11:36:00 +02:00
Martin Vogel bb4af38a29 smoke: index a large templated C++ header to reproduce the #424 prod crash
The C++ heap-corruption crash only manifests in the production binary (MI_OVERRIDE=1), so the smoke job (which runs the prod binary) is where it reproduces — most reliably on Windows. A ~10k-line templated header exercises the parse-churn path; indexing it must succeed.
2026-06-09 09:21:34 +02:00
Martin Vogel e83bd73c1a Trigger the #406 orphan smoke test via stdin EOF, not a FIFO writer-close
Phase 16's FIFO writer-close surfaced as POLLHUP rather than a clean POLLIN+EOF, which the server's poll()-based read loop does not treat as shutdown, so the probe left the process alive and the smoke job failed on every platform. Switch to a plain '< /dev/null' immediate EOF — the server exits reliably (~1s) via its normal getline-EOF path, and it is fully portable (POSIX + MSYS2 bash), so no OS gate is needed. Verified end-to-end: bash scripts/smoke-test.sh reaches 'ALL PASSED'.
2026-06-07 01:11:22 +02:00
Martin Vogel f677992d17 Add smoke test: stdio server leaves no orphan after shutdown (#406)
Phase 16 of smoke-test.sh launches the stdio MCP server with its stdin held open (a running instance), closes stdin to trigger shutdown, and asserts the process terminates within ~6s — guarding the orphaned-background-process failure mode behind #406 from the prod binary on every platform. POSIX-gated (Windows uses job objects; mkfifo is unavailable on MSYS2).
2026-06-06 22:37:50 +02:00
Martin Vogel 4ce03051a2 Check stderr for the unsupported-function smoke assertion
The cypher unsupported-function check captured stdout, but the CLI prints the parse error to stderr (which cli() redirects into $CLI_STDERR) and exits non-zero, leaving stdout empty — so the assertion always failed once the run reached the smoke stage. Verify the exit status and the stderr channel where the binary actually reports the loud failure. Confirmed locally: full smoke-test.sh passes.
2026-06-06 17:03:19 +02:00
Martin Vogel 7990c6fb9d Escape embedded quotes in the smoke query helper
cyp_first_cell built the query_graph JSON by interpolation, so a query with a double-quoted string literal (e.g. replace(f.name, "a", "A")) produced invalid JSON and an empty result (FAIL: replace empty). Escape embedded double-quotes before building the JSON.
2026-06-06 01:26:06 +02:00
Martin Vogel 09e3f71bf6 fix(search_code): accept '&' in paths, warn on literal-'|' trap, report timing
#272: '&' is neutralised by the single-/double-quoting of the grep/Select-String/
git commands, so it no longer fails validation. A relaxed validate_search_path_arg
(cbm_validate_shell_arg minus '&') is used for search_code + detect_changes
root_path/file_pattern; all other shell metacharacters stay rejected and
base_branch keeps the strict check.

#282: search_code defaulted regex=false, so 'foo|bar' matched the literal pipe
and silently returned 0 results. The result now carries a warnings[] array that
flags a literal '|' under regex=false (with the regex=true hint), so the trap is
visible instead of looking like a legitimate no-match. (The related invalid-regex
silent-empty case was already fixed in #283.)

Also: every search_code result now reports elapsed_ms, and a perf warning fires
when a search exceeds 5s (also logged as search.slow) so slow calls are visible.

Tests for both issues + the multi-word/invalid-regex no-regression; generous
search_code smoke coverage (basic+elapsed_ms, literal-| warning, '&' acceptance).

Closes #272
Closes #282
2026-06-01 22:53:17 +02:00
Martin Vogel d87cffe168 feat(arch): surface Leiden community clusters in get_architecture
Wire the Leiden detector into get_architecture via a new 'clusters' aspect.
arch_clusters loads Function/Method/Class nodes + CALLS edges (capped for very
large graphs), runs cbm_leiden, and reports the top-N communities compactly —
each with a dominant-package label, member count, cohesion (internal vs boundary
edge ratio), representative top nodes (by degree), and the packages spanned.
Singletons are skipped to avoid noise. The mcp.c serializer already emitted the
clusters array; it is now populated.

Tests: arch_clusters_basic (two cliques + a bridge -> >=2 connected clusters).
Generous smoke coverage added for the full Cypher surface (labels/type/id/keys/
properties/size/reverse/replace/left/coalesce/substring, NOT EXISTS dead-code,
CASE, unsupported-function error) plus the get_architecture clusters aspect.
2026-06-01 22:36:50 +02:00
Martin Vogel 08b62f03b9 feat(cypher): bounded EXISTS { } pattern predicate in WHERE
Add a single-hop, anchored existence predicate:
  WHERE NOT EXISTS { (f)<-[:CALLS]-() }   -- functions with no caller
  WHERE EXISTS { (f)-[:CALLS]->() }       -- functions that call something

Parsed via parse_exists_predicate (reusing parse_node/parse_rel) into a leaf
condition (op=EXISTS, anchor variable, edge type, direction); evaluated against
the bound node with cbm_store_find_edges_by_source_type / _by_target_type. This
is edge-type-specific, so it finds true orphans that in_degree/out_degree miss
(e.g. a node with only a DEFINES edge but no CALLS caller). Multi-hop / nested-
WHERE EXISTS is intentionally unsupported and errors clearly.

Tests cover the dead-code (NOT EXISTS) and has-outgoing (EXISTS) cases; smoke
coverage added. ASan-clean.

Refs: Cypher read suite (EXISTS predicate)
2026-06-01 21:17:56 +02:00
Martin Vogel c160465118 feat(cypher): multi-argument scalar functions (suite tier 2b)
Add an args-list (cbm_func_arg_t) to return items and parse comma-separated
arguments (var.prop or string/number literals), enabling multi-argument scalar
functions in projections:

- coalesce(a, b, ...)        first non-empty value
- substring(s, start[, len]) 0-indexed substring
- replace(s, from, to)       replace all occurrences
- left(s, n) / right(s, n)   leading/trailing n chars

Arguments are freed in free_return_clause (ASan-clean). Unit test
cypher_func_multiarg plus smoke coverage for substring + coalesce.

Refs: Cypher read suite (tier 2b)
2026-06-01 21:04:25 +02:00
Martin Vogel 67a7334e60 feat(cypher): string functions + fail loudly on unsupported syntax
Add single-argument string functions to projections — size, length, trim,
ltrim, rtrim, reverse — via the generic function recognizer.

More importantly, change the engine to FAIL LOUDLY on unsupported syntax instead
of silently projecting an empty column. An unknown function call (e.g.
split(...), coalesce(...)) or list indexing/slicing in RETURN/WITH now returns a
clear "unsupported function '<name>' (supported: ...)" error rather than a
valid-looking but blank result — the same silent-empty failure mode that hid the
labels() bug. This supersedes the #373 graceful-resync behaviour (its test is
flipped to assert the error; a new test covers an unknown function in RETURN).

Decision: full Cypher Tier 3 (lists/maps/paths/comprehensions/params — a value
data-model rewrite) is intentionally NOT implemented; clear errors on
unsupported features are the higher-value, lower-risk completeness win for the
read subset agents actually use.

Refs: Cypher read suite (tiers 1-2a + unsupported-syntax hardening)
2026-06-01 20:47:06 +02:00
Martin Vogel 71a6c5714e feat(cypher): scalar + entity-introspection functions (suite tier 1)
Add single-argument functions to RETURN/WITH projections, dispatched via a
generic IDENT-call recognizer so new functions no longer need a dedicated lexer
keyword:

- labels(n)      -> ["<label>"]
- type(r)        -> relationship type
- id(n)          -> node/edge identity
- keys(n)        -> JSON list of the node's non-null property keys
- properties(n)  -> the node/edge properties JSON object
- toInteger/toFloat/toBoolean(x) -> numeric/boolean casts (null on non-numeric)

Previously labels()/id()/keys()/etc. fell into the unknown-function path and
silently projected empty (labels(n) returned ""); they now evaluate correctly.
Regex =~ in WHERE was already supported. Unit tests for each function plus
smoke-test coverage via the live query_graph CLI.

Refs: full Cypher read suite (tier 1 of 3)
2026-06-01 20:28:53 +02:00
Martin Vogel d67329efbf test(smoke): update Antigravity E2E to 2026 CLI layout
The Phase 8 agent-install E2E checked the pre-unification Antigravity paths
(~/.gemini/antigravity/), which broke after the config-path fix. Point the
stub setup at ~/.gemini/antigravity-cli/ and assert the MCP server lands in
the shared ~/.gemini/config/mcp_config.json with AGENTS.md under
~/.gemini/antigravity-cli/. Verified end-to-end (smoke ALL PASSED).
2026-05-31 21:42:42 +02:00
Martin Vogel 869341c709 feat(cypher): support COUNT(DISTINCT x) (#239)
count(DISTINCT x) was a parse error ("expected token type 85, got 9").
parse_aggregate_item now consumes an optional DISTINCT inside the call and
records it on the return item. Both aggregation paths track the set of unique
values per group and emit its size for COUNT(DISTINCT): the RETURN path reuses
the existing collect_lists machinery; the WITH path gains distinct_lists/
distinct_n fields. Plain COUNT and the other aggregates are unchanged.

Unit test: COUNT(DISTINCT f.label)=1 (all same label), count(f.label)=4
(non-distinct), COUNT(DISTINCT f.name)=4 (unique names). Smoke suite adds a
count(DISTINCT) query_graph check.
2026-05-31 20:22:55 +02:00
Martin Vogel 2a5515e586 feat(cypher): support label alternation MATCH (n:A|B) (#242)
openCypher node-label alternation was unsupported (parse error after the
first label). parse_node now consumes `:A|B|C` into a single "A|B|C" label
string (mirroring the existing relationship-type alternation). The seed path
unions per-label results (scan_alternation_labels), and target/hop label
filters use label_alt_matches, which ORs over the alternatives. Single-label
matching is unchanged.

Unit test (Function|Module → 5 rows; Function|Class → 4) plus a query_graph
alternation check in the smoke suite. Relationship-type alternation tests
remain green.
2026-05-31 20:11:12 +02:00
Martin Vogel b30e6d6443 feat(cypher): support label tests in WHERE — WHERE n:Label (#241)
openCypher label predicates in WHERE clauses were a parse error
("expected token type 67, got 73"). Parse `var:Label` in the WHERE leaf as
a condition with op=HAS_LABEL/value=Label, and evaluate it against the bound
node label in eval_condition (honoring NOT). Composes with AND/OR and works
in count(...) queries.

Unit test covers true/false/negated label predicates; smoke suite adds a
query_graph WHERE-label check.
2026-05-31 20:02:23 +02:00
Martin Vogel 3014867cb3 feat(cypher): apply WITH DISTINCT deduplication (#238)
The DISTINCT keyword on a WITH clause was parsed (r->distinct) but never
applied, so WITH DISTINCT silently returned duplicate rows. Add a dedup pass
in execute_with_clause that drops projected rows whose full value tuple
duplicates an earlier one (first occurrence kept), gated on wc->distinct.
No-op for the aggregation path (which already collapses per group).

Unit test (4 same-label functions collapse to 1 row; control without DISTINCT
keeps 4) plus a query_graph WITH DISTINCT check added to the smoke suite.
2026-05-31 19:53:25 +02:00
Martin Vogel c29e6d51f4 fix(hooks): replace blocking Claude PreToolUse gate with non-blocking augmenter
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)
2026-05-19 23:46:46 +02:00
Martin Vogel ec23b4f3e9 fix(smoke-test): parse CLI output as single JSON
The CLI's default print mode (cli_print_mcp_result in src/main.c)
unwraps the MCP envelope and prints the inner JSON directly. The
smoke test was double-parsing as if it received {content:[{text:...}]},
which silently fell through to empty values and failed every assertion
across all platforms (8 occurrences fixed).
2026-05-04 21:21:53 +02:00
Martin Vogel 74099d8600 Fix smoke test: platform-specific config paths + consolidated skill name
- Set APPDATA/LOCALAPPDATA env vars for Windows so cbm_app_config_dir()
  and cbm_app_local_dir() resolve to FAKE_HOME paths
- Add Windows (*.exe) branches for Zed, KiloCode, VSCode config checks
- Add macOS branch for KiloCode (was hardcoded to .config/ on all platforms)
- Create platform-correct detection dirs (AppData on Windows, Library on
  macOS, .config on Linux) so agent detection + install paths match
- Update skill check from old 4-skill names to consolidated codebase-memory
  (old dirs are cleaned up during install since skill consolidation)
2026-04-06 15:25:09 +02:00
Martin Vogel 6e1b3b2eaf Fix smoke test: set XDG_CONFIG_HOME for portable Linux agent detection
On Alpine musl (portable builds), cbm_app_config_dir() needs explicit
XDG_CONFIG_HOME to resolve to FAKE_HOME/.config. Without it, the install
writes to the wrong path and KiloCode/Zed config checks fail.
2026-04-06 15:00:06 +02:00
Martin Vogel 1b84943000 Separate perf tests from CI, fix cross-platform build issues
- Add CBM_SKIP_PERF=1 env var to skip incremental/perf test suite
- CI and Docker test targets skip perf by default (run.sh perf for manual)
- Convert all perf assertions to warnings (log timing, never block)
- Fix store.h anonymous enum in struct (GCC rejects, clang accepts)
- Fix test_store_search.c mkstemp on non-template path
- Add ca-certificates to Docker test image for git HTTPS
- Add cbm_gmtime_r shim in compat.h (Windows gmtime_s wrapper)
- Fix compat.c missing constants.h include (Windows build)
- Fix platform.c _environ redeclaration on mingw
- Rename trace_call_path -> trace_path in smoke/soak/fuzz scripts
2026-04-02 14:52:14 +02:00
Martin Vogel de2b55bfe1 Fix Windows smoke 12a: normalize MSYS2 uname to 'windows' for archive name 2026-03-26 10:47:07 +01:00
Martin Vogel d8f168cd39 Fix Windows smoke 14e + soak latency threshold
- Phase 14: copy binary with .exe suffix on Windows (was creating
  non-.exe copy that uninstall didn't remove)
- Phase 14e: check both .exe and non-.exe paths
- Soak latency: exclude index_repository from max query latency
  (indexing 377 files is legitimately slow on Windows/MSYS2)
2026-03-26 10:34:00 +01:00
Martin Vogel 5ca369f313 Fix Windows smoke: skip OpenCode/Aider when binary not on PATH 2026-03-25 23:55:39 +01:00
Martin Vogel 14a197c165 Fix ALL MSYS2 python3 path issues: pipe via cat for every JSON read 2026-03-25 23:39:56 +01:00
Martin Vogel 331b4c4dfb Fix all Windows path comparisons: path_match helper with basename fallback 2026-03-25 23:22:57 +01:00
Martin Vogel c0de9ff5a4 Weekly soak: run Sunday 2am UTC instead of nightly 2026-03-25 22:58:05 +01:00
Martin Vogel b568884e4a Fix MSYS2 python3 path issue: pipe files via cat instead of open()
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.
2026-03-25 22:48:31 +01:00
Martin Vogel 6d4d3246ad Fix Windows smoke 8a: basename comparison + debug output for path mismatch 2026-03-25 22:30:54 +01:00
Martin Vogel 3a457d9458 Fix smoke: Windows .exe binary name, UI variant archive fallback 2026-03-25 15:38:36 +01:00
Martin Vogel 7d640081e0 UI smoke: verify HTML content in GET / and JSON-RPC in POST /rpc 2026-03-25 10:48:40 +01:00
Martin Vogel 6300feb70c Fix smoke tests: variant detection, Windows deps, UI reachability
- 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)
2026-03-25 10:45:58 +01:00
Martin Vogel abe319b46c Real E2E update/uninstall + CBM_DOWNLOAD_URL support
Add CBM_DOWNLOAD_URL env var to cbm_cmd_update and checksum
verification — allows E2E testing against local HTTP server.

Phase 14: runs actual update --standard -y against local server,
verifies binary replaced, agent configs refreshed (stale path
updated), then real uninstall -y verifies binary removed and
configs cleaned.

Phase 13: add PATH setup verification. Non-interactive stdin:
isatty() prevents silent hangs.
2026-03-24 23:14:33 +01:00
Martin Vogel da7638c651 Prevent silent hangs on non-interactive stdin
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.
2026-03-24 23:14:33 +01:00
Martin Vogel 8951647d3e Fix remaining install/update gaps + in-memory zip extraction
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.
2026-03-24 23:14:33 +01:00
Martin Vogel 006e6dbf20 Add install scripts, CI pre-signing, download E2E smoke tests
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.
2026-03-24 23:14:33 +01:00
Martin Vogel d2451c4fd6 Add comprehensive E2E smoke tests for install/update/uninstall
Phase 8: Agent config install E2E — creates stub environment with
all 10 agent detection dirs, runs install -y with isolated HOME,
verifies exact JSON structure, TOML content, hook format, skills,
and merge-not-overwrite behavior for every agent config.

Phase 9: Agent config uninstall E2E — verifies complete removal of
all MCP entries, hooks, instructions while preserving user's existing
config keys. Adversarial tests: idempotent double install, uninstall
without prior install, corrupt JSON/TOML handling, double uninstall.

Phase 10: Platform binary security — macOS: codesign verify, strip
signature, SIGKILL on unsigned arm64 (exit 137), re-sign + verify.
Linux/Windows: verify unsigned binary runs without signing.

Phase 11: Process kill — start MCP server, verify running, kill,
verify gone.

Phase 14: Update flow — binary replacement with signing, read-only
binary edge case, platform-specific verification.

Fix cbm_find_cli: add S_IXUSR check for hardcoded path search
(was missing, only checked for PATH-based search).
2026-03-24 23:14:33 +01:00
Martin Vogel c00723e5c8 Require project param on all MCP tool calls
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).
2026-03-24 13:38:25 +01:00
Martin Vogel f24475ca4a Add simulated binary replacement to smoke tests (Phase 6e)
Tests the update command's replacement flow without network calls:
copy binary as "downloaded" version, unlink-then-replace the
"installed" version, verify replaced binary runs --version.
Also tests read-only binary replacement edge case (#114).
2026-03-23 22:02:51 +01:00
Martin Vogel 20814e8b27 Add smoke tests to Docker test infra + CI pipeline
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.
2026-03-23 21:56:33 +01:00
Martin Vogel 85da0908f6 Add MCP stdio transport smoke tests (Phase 5)
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).
2026-03-23 18:33:27 +01:00