Commit Graph
2890 Commits
Author SHA1 Message Date
Martin VogelandClaude Fable 5.1 7219a24cc6 fix(yaml): scope > | [ ] indicators to the value start (#1924)
Hermes `install` failed with `op=pre_llm_hook_install` whenever config.yaml
held a PLAIN scalar with a `>` or `|` anywhere after the value start —
`probe: kaomoji face >w< here` — while the identical string double-quoted,
or a mid-value `*`, installed fine. An agent-config failure aborts
activation, so the whole install exited 1 and user-PATH registration was
skipped although eleven other agents had configured cleanly. Bisected by
@ukind, key by key and persona by persona, down to two persona strings
(#1924, continuing #1631/#1656).

Root cause: yaml_sequence_line_has_unsupported — the document validator on
the mapping-sequence path that the hook upsert/remove uses — ran a second
scan after the shared yaml_range_has_unsupported_ex and returned
"unsupported" for `[`, `]`, `|` and `>` at ANY position of a plain scalar.
The mapping-body path (mcp_servers) never had that check, which is exactly
why the mcp op succeeded and only the hook op failed on the same file.

Fix: the positional rule the `*`/`&` fix (v0.10.4) and the quote fix
(#1631, v0.10.6) already use — an indicator counts only where a NODE begins
(range start, after a mapping colon, after a block-sequence dash).
Interior ones are text. `probe: >`, `probe: |`, `probe: >-` and
`probe: [a, b]` still begin a value and stay unsupported, byte-identically.

`[`/`]` get the same treatment on purpose: in block context they are
ordinary characters inside a plain scalar (`a: b[0]` parses as the string
`b[0]`), a value-start `[` still opens a flow sequence and stays refused
(the #1631 line: exact `[]` accepted, non-empty flow sequences unsupported),
and the mapping-body path already accepted them mid-value — leaving the
sequence scan stricter would keep the mcp-op-passes / hook-op-fails
asymmetry this issue is about. Known conservative residue, shared with the
`*` rule: a plain-scalar CONTINUATION line that itself starts with one of
these characters is still refused (nothing precedes it on its line).

Tests (tests/test_config_yaml_edit.c): three accept cases run both real
Hermes ops (mcp_servers + pre_llm_call) against a persona corpus with a
mid-value `>`, `|` and `[]` and require the original document to survive as
an untouched prefix; two pins keep value-start block scalars and a non-empty
flow sequence refused with the file byte-identical. RED on the unfixed
editor: 61 passed, 3 failed (`yaml_hermes_hook_upsert(&fixture) == -1`);
GREEN: 64 passed; RED again with the production change reverted.

config_yaml_edit 64 · yaml 73 · cli 306 — 443 passed, 0 failed (macOS,
ASan/UBSan test build).

Not in this change: the refusal still reaches the user as
`op=pre_llm_hook_install path=... (target: regular file, N bytes)` with no
reason. The editor collapses every one of its ~236 YAML_ERROR sites into
CBM_YAML_IDENTITY_EDIT_ERROR, so naming the refusing rule needs a detailed
variant with a typed failure out-parameter, the shape the Codex TOML hook
path already has (cbm_toml_codex_hook_failure_t +
record_agent_config_error_with_reason). Follow-up.

Fixes #1924.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-08 00:11:21 +02:00
Martin Vogel aa44c28ea5 Merge pull request #1941 from CaptainMittens/fix/parse-coverage-honest-ranges
fix(coverage): narrow parse-error ranges, and stop the report hiding what it dropped (#963)
2026-09-07 01:15:37 +02:00
Martin Vogel 994990893c Merge pull request #2074 from Tokarzewski/fix/721-vb6-frm-cls-disambiguation
fix(discover): keep VB6 .frm/.cls files out of the FORM and Apex grammars
2026-09-07 01:14:12 +02:00
Martin Vogel a6aea0bc07 Merge pull request #2059 from Balneario-de-Cofrentes/fix/yaml-remove-absent-config
fix(cli): YAML removers answer OK for an absent config, so uninstall no longer refuses
2026-09-07 01:13:46 +02:00
Martin Vogel 971111c7a9 Merge pull request #2070 from DeusData/fix/daemon-session-root-separators
fix(daemon): keep one separator spelling for the session root on Windows
2026-09-07 01:12:24 +02:00
Martin Vogel e2d538afd0 Merge pull request #2041 from CaptainMittens/test/cli-uninstall-path-isolation
test(cli): point PATH at the fixture so uninstall isolation holds on any machine
2026-09-07 00:10:42 +02:00
Martin Vogel 2b030a5b60 Merge pull request #2056 from bmcnaboe/fix/svelte-html-astro-embedded-structure
fix(extract): extract structure from Svelte/HTML/Astro inline scripts
2026-09-07 00:10:22 +02:00
TokarzewskiandClaude Opus 5 6db22aadb6 fix(discover): keep VB6 .frm/.cls files out of the FORM and Apex grammars
Visual Basic 6 shares .frm with FORM and .cls with Apex/ObjectScript, so VB6
forms were parsed by the FORM grammar (0 defs) and class modules by the Apex
grammar (junk nodes, extract timeouts). Recognise the VB6 export header
(VERSION x.yy / VERSION 1.0 CLASS / Begin VB. / Attribute VB_Name) and report
such files as unsupported (CBM_LANG_COUNT) until a Visual Basic grammar exists:
new cbm_disambiguate_frm(), VB6 check added to cbm_disambiguate_cls() ahead of
its ObjectScript scan. Defaults on any doubt are unchanged (FORM / Apex).

Short-term step agreed in #721; the grammar follows separately.

Refs #721

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Tokarzewski <bartlomiej.tokarzewski@gmail.com>
2026-09-06 17:41:47 +02:00
Martin Vogel 9846c3f171 fix(daemon): compare session roots separator-equivalently without respelling the policy root
The first cut on this branch (c1b9c451) respelled the session root and
the allowed root with forward slashes on the way into the session policy
(cbm_mcp_server_set_session_context) and at the three application.c sites
that canonicalize a root. CI's Windows shard then failed two tests:

  tests/test_daemon_application.c:2256: ASSERT(sensitive_blocked)
  tests/test_daemon_application.c:2380: ASSERT(sensitive_blocked)

Both set HOME to the cbm_canonical_path spelling of a directory - the
platform's native form, backslashes on Windows - and expect a session
rooted there to be refused for auto-index and for watch. The sensitive-root
and allowed-root containment walk (ws_is_ancestor_or_equal in
src/foundation/workspace.c) compares prefixes byte-exact, so a root that
now read C:/Users/... no longer matched a HOME of C:\Users\...: the
sensitive refusal was lost and $HOME was admitted. Respelling the policy
root was the wrong layer.

This commit keeps the policy roots in their canonical native spelling
(mcp.c and the three application.c sites are back to what main runs) and
folds the separator spelling at the one comparison the original defect
lives in: application_index_args_equal, where the running job's args are
compared with an explicit index_repository request's. The auto-index job
spells repo_path the way the session policy holds it; the handler spells
it with forward slashes. On Windows the two never matched, and the request
was refused as OPTIONS_CONFLICT instead of joining the job already running
for its root. The fold acts on the parsed copies only (yyjson_mut_obj_replace
with a cbm_normalize_path_sep'd string), so nothing the daemon stores
changes spelling and every other option stays exact.

Why this is correct on Windows without a local Windows run: the policy
spelling is untouched, so the containment path is byte-for-byte what main
runs there today; cbm_normalize_path_sep folds backslashes on every
platform, so the new fold is exercised by the new test wherever the suite
runs.

Tests: daemon_session_context_keeps_one_spelling_of_a_root asserted that
the policy respells a root with forward slashes. That was the wrong
contract: the policy's containment checks compare its spelling byte-exact
against HOME and the granted roots, so a respelled root is exactly what
stops matching them. It is replaced by
daemon_session_context_keeps_the_policy_spelling_of_a_root, which binds
the opposite: the policy stores the root as given. The comparison is exposed through
application_internal.h as cbm_daemon_application_index_args_equal_for_test
and bound by daemon_application_index_args_compare_repo_path_separator_equivalently:
a backslash and a forward-slash repo_path compare equal; a different path,
a sub-path, and a different mode still do not.

Refs #1726

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-06 16:23:45 +02:00
Martin Vogel 306c02d44e Merge branch 'main' into fix/yaml-remove-absent-config 2026-09-06 16:10:07 +02:00
Martin Vogel ddb6533cd9 Merge branch 'main' into fix/parse-coverage-honest-ranges 2026-09-06 16:10:04 +02:00
Martin Vogel 99fa463023 Merge branch 'main' into test/cli-uninstall-path-isolation 2026-09-06 15:54:57 +02:00
Martin Vogel 9cb2e9dd1c Merge branch 'main' into fix/svelte-html-astro-embedded-structure 2026-09-06 15:53:50 +02:00
Martin Vogel 745794df80 Merge pull request #2069 from DeusData/fix/test-wave-atomic-barrier-writes
test: write scheduler barrier files atomically
2026-09-06 15:52:43 +02:00
Martin Vogel 4efedf257b Merge pull request #1822 from AmirF194/fix/1815-symlink-skip-not-reported
fix(discover): report a symlink skip through the ignored-files taxonomy
2026-09-06 15:50:19 +02:00
Martin Vogel 86c031a661 Merge pull request #2066 from kavish-19/fix-grep-record-alignment
test(mcp): over-long grep records never invent matches (#2011)
2026-09-06 15:49:07 +02:00
Joshua RichterandClaude Opus 5 874a972fb7 test(cli): point PATH at the fixture so uninstall isolation holds on any machine
cli_uninstall_quiesces_active_cohort_before_removing_binary_and_index moved
HOME and CBM_CACHE_DIR into a temporary directory but left PATH alone. Agent
detection reads PATH first, so a real agent binary on the developer's machine
was found even though HOME pointed at the empty fixture. Uninstall then tried
to edit that agent's config file inside the fixture, failed because the fixture
never created one, and stopped before removing the binary and the index. The
test measures exactly that removal, so it failed.

On this machine the agent is Goose, at /Users/<me>/go/bin/goose. Same tree and
same built runner, only PATH differs:

  goose on PATH        291 passed, 1 failed
  goose off PATH       292 passed, 0 failed

With this change the suite reports 292 passed, 0 failed while goose is still on
PATH, so the fix is the redirect and not a quieter environment.

This only stops the suite from depending on what the developer has installed.
The abort it exposed is a real defect and is reported in #1954: one agent config
that cannot be edited stops teardown for every other agent and for the
executable and indexes.

Eleven other tests that call cli_activation_save_env leave PATH open the same
way. They pass today, so I left them alone rather than change what they
exercise without evidence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-06 00:43:49 -04:00
Martin Vogeland刘冲 c1b9c451c7 fix(daemon): keep one separator spelling for the session root on Windows
On Windows cbm_canonical_path answers in the platform's native backslash
form, and the daemon stored that spelling verbatim for the client's
session_root and allowed_root (cbm_mcp_server_set_session_context) and for
the auto-index job's repo_path (application.c canonicalizes with the bare
call at three sites). Every tool handler, however, normalizes separators
after canonicalizing. One directory therefore had two names, and the exact
comparison in application_index_args_equal never matched: an explicit
index_repository request for the session root was refused as
OPTIONS_CONFLICT instead of joining the auto-index job already running for
it.

Normalize the separator spelling of both session roots in
set_session_context, and canonicalize plus normalize at the three
application.c sites through one helper, application_canonical_root().
Separators fold on every platform, so the new test
daemon_session_context_keeps_one_spelling_of_a_root binds wherever the
suite runs; it fails on main with "C:\repos\cbm" != "C:/repos/cbm".

Distilled from #1726 with co-author credit.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-authored-by: 刘冲 <mail@liuchong.dev>
2026-09-06 02:48:16 +02:00
Martin Vogel 7da8bf1583 Merge branch 'main' into fix-grep-record-alignment 2026-09-06 02:28:04 +02:00
Martin Vogel f895152425 Merge branch 'main' into fix/test-wave-atomic-barrier-writes 2026-09-06 02:28:02 +02:00
Martin Vogel eb3bd818b5 Merge branch 'main' into fix/1815-symlink-skip-not-reported 2026-09-06 02:27:53 +02:00
Martin Vogel e384f88f90 Merge branch 'main' into fix/svelte-html-astro-embedded-structure 2026-09-06 02:27:51 +02:00
Martin Vogel 7b0f553cba Merge pull request #2067 from DeusData/fix/semantic-search-residuals-938
fix(mcp): harden semantic search inputs and store-error handling
2026-09-06 02:26:48 +02:00
David Gil 3007dbf842 fix(cli): YAML removers answer OK for an absent config, so uninstall no longer refuses
`uninstall` failed with rc 1 on any macOS where Hermes is installed:
Hermes is detected by its binary on PATH, not by its home, so a HOME
without `.hermes/` (every test HOME, and a fresh user's) reached the
Hermes uninstall ops with `~/.hermes/config.yaml` absent. The JSON
removers already treat a missing config as "nothing to remove"; the YAML
removers acquired the lock first, and the lock directory is created
beside the target — with the parent directory absent that failed, the
adapter recorded

  error: agent_config agent=Hermes op=pre_llm_hook_uninstall ... (target: does not exist or cannot be inspected)
  error: agent_config agent=Hermes op=mcp_uninstall ...

and uninstall refused to remove the executable and the indexes. This is
what made cli_uninstall_quiesces_active_cohort_before_removing_binary_and_index
fail on such a machine (7837 passed, 1 failed on a pristine main).

All four YAML removers short-circuit on a genuinely absent path and
create nothing: lstat + ENOENT on POSIX, and CreateFileW with
FILE_FLAG_OPEN_REPARSE_POINT on Windows, mirroring toml_read_file.
Opening the reparse point rather than following it keeps a dangling
symlink out of the short-circuit on both platforms, so it still reaches
the reader and its byte-identical rejection.

The regression test covers the four removers and runs on every platform:
it asserts "nothing was created" through cbm_path_info_utf8 rather than
lstat, so the Windows branch is exercised rather than compiled out.

Signed-off-by: David Gil <dgilperez@gmail.com>
2026-09-05 23:30:00 +02:00
Joshua RichterandClaude Opus 5 b04f54506a perf(coverage): find a line by table lookup, not by rescanning the file (#963)
cbm_refine_regions_with_pp_lines asks cbm_line_is_toplevel_macro_call about
every preprocessor-parsed line in a region. That check walked the source from
byte 0 to find the line, so the refinement cost bytes times lines — on exactly
the whole-file-ERROR C shape the refinement exists to narrow.

Build one line-offset table per file, and hand the byte span to the macro
check. The line-based form stays for the two callers that ask about a whole
region, where a single walk is cheaper than a table. A file with no table (the
allocation failed) still gets the old walk, so the answer never changes.

Measured on one C file whose file-scope brace is split by an #ifndef, padded to
5k / 10k / 20k / 40k lines. Release build, one machine, seconds:

  lines     main   branch   this commit
   5000     0.98     1.14     0.98
  10000     3.30     4.02     3.41
  20000    11.64    14.77    12.56
  40000    44.04    58.58    46.90

The branch ran 33% over main at 40k lines and now runs 6.5% over. The reported
ranges do not change: 8-8, where main reports the whole file as 5-40000.

A profile of the 40k run no longer reaches the macro check. What is left is
cbm_line_contains and cbm_span_contains_callable_def, which main carries too
and this branch does not touch. The fixture holds 40k definitions, which makes
those two cost more here than on a smaller file.

parse_coverage, extraction, pipeline, mcp and index_resilience: 964 passed,
4 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-05 16:24:25 -04:00
Joshua RichterandClaude Opus 5 f871178b8c fix(coverage): name the whole-file number for what it is, a range end (#963)
Review follow-up on this branch.

Each parse_unusable entry carries one number, and the field was called
"lines". That reads as the length of the file, and the two are not the same
number: a grammar can end an error node past the last line, which this repo
has already met — scripts/setup-windows.ps1 has 326 lines and its range ends
at 327. A report whose whole thesis is honest reporting should not name that
number after the wrong thing.

"lines" also already means something else in this same response. Every search
result carries a "lines" field holding a definition's line span. One word, two
meanings, one document.

The field is now "range_end", at both places that emit it — add_coverage_report
reading the persisted rows, and add_parse_unusable_summary reading the per-run
errors. The comment beside each one says the number can exceed the file, so the
next reader does not have to rediscover it.

Deriving the real file length instead was the other option and is not
available here: neither cbm_file_error_t nor cbm_coverage_row_t carries it,
only the path and the range string.

One test, proved RED first — "range_end is NULL" against the old field name.
It reads the end line from the persisted coverage row rather than a constant,
so it states the property and not a measurement, and it asserts the old name is
gone rather than kept beside the new one.

index_resilience, parse_coverage and mcp: 283 passed, 4 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-05 16:24:08 -04:00
Joshua RichterandClaude Opus 5 219b348263 test(coverage): pin the truncation marker, the partial ceiling and the grammar limit (#963)
Phase 5. Four test groups, each checked RED before it was kept.

- The Studio Export range join puts ONE ",+<N>" marker at the end with the
  summed drop count. A marker left mid-string makes every reader stop there
  and silently lose the ranges after it. Reaching the join through the
  pipeline needs an export file with 256+ error regions across two <Class>
  elements, so it goes through a test seam, following the pattern already in
  this repo (CBM_COVERAGE_MARKER_TEST_API).
- check_index_coverage emits every range in front of a marker, never turns
  the marker's digits into a range, and reports "truncated" from BOTH caps —
  the producer's and its own 256 limit.
- test_index_resilience now has a ceiling beside its floor: exactly one of
  the two fixture files is flagged, the clean neighbour is absent, and the
  range does not cover the whole file.
- The three _Thread_local forms are pinned as measured. Only the array form
  fails today; the plan's Phase 0 also listed the pointer form, and that is
  wrong on the grammar shipped now.

Also fixes 13 clang-format violations the earlier commits on this branch left
in cbm.c, mcp.c and pass_definitions.c. `make -f Makefile.cbm lint-format`
would have failed CI. The changes are whitespace only — the two reflowed tool
descriptions concatenate byte-identically, so no output moved.

Full suite: 7735 passed, 28 failed, 7 skipped. The 28 are the pre-existing
cli install/uninstall failures, identical at clean HEAD.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-05 16:02:17 -04:00
Joshua RichterandClaude Opus 5 a953509275 fix(coverage): stop the report hiding what it dropped, and name whole-file failures (#963)
Two silent failures in the parse-coverage report, both made visible by the
Phase 2 range refinement that came before this.

Two caps sat in series and both returned early without saying anything:

    CBM_MAX_ERROR_REGIONS = 64   internal/cbm/cbm.c
    COVERAGE_RANGE_MAX   = 128   src/mcp/mcp.c

Raising only the first would have moved the clip from 64 to 128, so both move
to 256. This was live behaviour, not a theoretical limit: after Phase 2 split
one whole-file range into many small ones, src/cli/cli.c and tests/test_cli.c
both reported exactly 64 ranges — the cap binding, dead-on, twice. Every
coverage figure measured before this change was a floor. With the cap at 256
the true numbers are cli.c 13.9% (not 9.8%) and test_cli.c 3.1%, and the
longest list in the repo is 85 ranges.

A raised cap is still a cap, so the report now says when it clipped:

- cbm_error_regions_t gained a `dropped` counter, and cbm_collect_error_regions
  walks to the end instead of stopping at the cap, so the count is exact rather
  than a lower bound. That costs little — the walk never descends into an ERROR
  subtree.
- cbm_error_ranges_str appends ",+<N>" when N ranges were thrown away.
- coverage_add_ranges reads that marker and sets "truncated": true, and also
  sets it when its own limit stops the loop. Before this the marker was
  invisible: the parser stopped at the '+' with no error and no leftover, so a
  clipped list arrived looking complete.
- objectscript_export_append_error_ranges strips markers off both operands
  before joining two Studio Export parts and adds one back at the end. A marker
  left mid-string would make every reader stop there and silently lose every
  range after it.

"Look at lines 1 to 13047" of a 13046-line file tells a reader nothing. Those
files now carry their own kind rather than being described as partially
covered.

New `parse_unusable` field in CBMFileResult, set when one range covers 80% or
more of the file. Its customers are non-C languages: the Phase 2 refinement
that narrows a whole-file range using the preprocessed parse only runs for C,
C++ and CUDA, so a Python, Java, Ruby or TypeScript file whose root node is
ERROR still reports 1-N. Verified against real files in all four.

The kind is `parse_unusable`, not `parse_failed`. index_coverage.kind already
means one of two things — indexed-but-partial, or a skip phase saying the file
was never indexed at all — and `parse_failed` reads as the second when it is
the first. The store.c schema comment, which is the only written record of this
vocabulary, now describes all three classes and says why.

Two places would have mislabelled the new kind as "skipped", which is exactly
that confusion: coverage_status fell through to its catch-all pass, and
add_coverage_report fell into its else branch. A reader who finds a file under
"skipped" believes it is absent from the graph, when it was indexed. Both now
have explicit branches. index_status gained parse_unusable_count so a CI gate
can read it without parsing anything else, get_code_snippet says "read the
source directly" instead of naming useless ranges, and the three tool
descriptions that listed two coverage kinds now list three.

Seven added. The cap test moved from 64 to 256; a new test asserts the marker
carries a real drop count and that nothing follows it; an inverse test asserts
an under-cap file carries no marker at all. For the new kind: a Python file
whose root is ERROR is unusable, a file with a local parse failure stays
partial, a clean file is neither, and — the one that matters most — the
that last one ever flips, the Phase 2 refinement has stopped working.

Full suite: 7732 passed, 28 failed, 7 skipped. The 28 are pre-existing
agent-client install/uninstall failures in the cli suite, identical in count
and identity at clean HEAD.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-05 16:01:05 -04:00
Joshua RichterandClaude Opus 5 cb167b9dcc fix(coverage): narrow parse-error ranges with the preprocessed parse (#963)
src/cli/cli.c reported an error range of 1-13047 — the whole file. The file
indexed fine; the report was wrong. Three #ifndef _WIN32 blocks split a brace
(two `if` headers, one closing brace), so the raw tree-sitter parse cannot
resync at file scope, the root node becomes ERROR, and cbm.c takes its
whole-file branch.

The pipeline already parses these files a second time after preprocessing, and
that parse is clean. The report just never consulted it.

Build one byte per original line from the preprocessed pass, then cut each raw
error range down to the runs of lines the second parse could not vouch for.

Three rules, all found by running it and all load-bearing:

- An expanded line only vouches for its original line when it HAS TEXT. The
  preprocessor emits a blank line where it dropped a branch; treating that
  blank as proof suppressed every C range in the suite.
- Preprocessor directive lines (with backslash continuations) never count as
  missing code — the preprocessor consumes them, so the second parse can never
  vouch for one. Without this every #include block reported as a miss. Known
  cost: a #define the raw parse really dropped no longer shows up on its own.
- A TOP-LEVEL macro invocation line never counts as vouched-for even when the
  expanded line parses clean. The macro can expand to a whole definition that
  the recovery walker deliberately refuses to adopt (#949), so a clean second
  parse there proves nothing. An in-body invocation is the benign #1071 case
  and is left to the existing macro subtraction.

The order of the three coverage steps is now settled by where each one's
evidence lives:

  recovery subtraction  -> before the refinement; its evidence is a whole
                           definition that STARTS inside the range, so it must
                           be asked while the range still matches the construct
  the refinement        -> middle
  #1071 macro rule      -> after the refinement; its evidence is per-line, so a
                           narrow range points at the call itself

Measured on this repo: src/cli/cli.c goes from one whole-file range to 64
ranges over ~9.8% of the file, tests/test_cli.c from 48.6% to ~2.9%,
src/cli/activation_transaction.c from 38% to 7.5%. What survives is honest —
the biggest remaining ranges in cli.c are genuinely discarded #ifdef _WIN32
and #ifdef CBM_CLI_ENABLE_TEST_API blocks, absent from the graph on this
platform.

Both percentages above are floors, not measurements: cli.c and test_cli.c now
land on exactly 64 ranges, which is CBM_MAX_ERROR_REGIONS. That cap drops
regions with no signal, and a follow-up raises it and adds a truncation marker.

Five tests, all red before the change: the range narrows to the dropped
branch; lines the preprocessor explained are excluded; a range never starts or
ends on a directive; real garbage beside a split brace stays flagged; a clean
file stays unflagged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-05 15:58:59 -04:00
Martin VogelandKody 3b9052c955 test: write scheduler barrier files atomically
Path.write_text is open-then-write: it creates and truncates the file
first and the content lands afterwards. The parallel harness contract
polls for `<suite>.ready` to appear and then parses its content as the
leader pid, so a reader that wins that window sees an empty file and
fails with `ValueError: invalid literal for int() with base 10: ''` --
a spurious FAIL of the harness contract with a scheduler-side cause.
The same shape applied to `<suite>.leader-exited`.

Publish the barrier files through a helper that writes a same-directory
temp file and os.replace()s it onto the destination, which is atomic on
POSIX and Windows: a poller now sees either no file or complete content.
All three barrier publications go through it.

Pin the contract structurally (no timing): the scheduler may not write a
barrier file in place, and it must rename into place instead. The pin is
RED on the previous scheduler.

Distilled from #1188 with co-author credit.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-authored-by: Kody <kaidi.shi.1121@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 21:11:48 +02:00
Martin VogelandDustin Persek b7e18c8940 fix(mcp): harden semantic search inputs and store-error handling
Land the four residual hardening points from #938 on the current
semantic-search design (semantic_limit/offset, the certified top-K
fetch loop):

- extract_semantic_keywords rejects a semantic_query array that holds a
  non-string element instead of silently running the string subset; the
  caller gets the same isError as for a bare-string semantic_query.
- run_semantic_query_core fails closed when the vector scan itself
  fails ("semantic search failed", isError) instead of rendering a store
  error as zero matches plus the broaden-your-keywords hint.
  cbm_store_vector_search now tells the two states apart: a store with
  no node_vectors table (lean index) is CBM_STORE_NOT_FOUND and keeps
  the empty-page + moderate/full-index hint that main relied on by
  swallowing the "no such table" prepare failure; a failing scan is
  CBM_STORE_ERR.
- vs_min_cosine_score hoists the keyword norms (once per search) and
  the node norm (once per node) out of the per-(node, keyword) loop.
  The integer sums are exact, so every score stays bit-identical.
- vs_append_result checks its strdup results, frees the partial row on
  failure and counts a row only once every copy succeeded, so the
  caller's free path stays valid.

Two reproduce-first tests in the mcp suite fail on main and pass with
the change: non-string elements produce the type error, and a forced
cbm_cosine_i8 failure produces the fail-closed error for semantic-only
and combined calls while a missing node_vectors table still yields the
lean-index hint.

Refs #915

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-authored-by: Dustin Persek <dustin.persek@protonmail.com>
2026-09-05 20:02:07 +02:00
kavish-19 c4bec4a6b3 test(mcp): over-long grep records never invent matches (#2011)
A grep record longer than the read buffer used to be split across two
reads, and the continuation was parsed as a fresh file:line:content
record — a fragment of matched content became a file path and the text
after the next delimiter became a line number. An agent reading that
result called get_code_snippet on a path that was never in the
repository. The same split could also lose a real match, when the tail
held fewer than two delimiters and the record was dropped instead.

The production fix landed via #1597 (3c7427e): search_code now reads each
record with cbm_getline into a growable buffer, so a record can no longer
split. That closed #2011 as a side effect, and nothing on main guards the
shape — the buffer could be reintroduced by a future rewrite of the read
loop with no test to catch it.

Add the regression test the fix never got. It writes a >2 KiB colon-laden
line plus a normal one, calls search_code through cbm_mcp_server_handle,
and asserts the repository's two real matches are reported as two — not
three — and that no row carries the line 0 the fabricated record used to
produce.

Signed-off-by: kavish-19 <63698788+kavish-19@users.noreply.github.com>
2026-09-05 22:54:00 +05:30
Martin Vogel 4f82a1e56f Merge branch 'main' into fix/svelte-html-astro-embedded-structure 2026-09-05 19:15:01 +02:00
Martin Vogel 7fbaf02904 Merge branch 'main' into fix/1815-symlink-skip-not-reported 2026-09-05 18:50:36 +02:00
Martin Vogel fe85a6b236 Merge pull request #2063 from DeusData/dependabot/github_actions/softprops/action-gh-release-3.0.3
build(deps): bump softprops/action-gh-release from 3.0.2 to 3.0.3
2026-09-05 17:22:28 +02:00
Martin Vogel 3b173288fe ci(release): label the action-gh-release pin as v3.0.3
The SHA dependabot pinned (efb35369) is upstream's v3.0.3 tag, a major bump
from the v2 line: 3.0.0 moves the action runtime from Node 20 to Node 24 and
changes nothing else. The pin comment still read v2, which would misdirect the
next reader of this file; label it with the version the SHA actually is.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 16:03:23 +02:00
Martin Vogel 094c89548b Merge pull request #2064 from DeusData/dependabot/github_actions/actions/deploy-pages-5.0.1
build(deps): bump actions/deploy-pages from 5.0.0 to 5.0.1
2026-09-05 15:51:55 +02:00
Martin Vogel 6df1607255 Merge pull request #2062 from DeusData/dependabot/npm_and_yarn/graph-ui/npm_and_yarn-a5e5b913b4
build(deps-dev): bump browserslist from 4.28.4 to 4.28.9 in /graph-ui in the npm_and_yarn group across 1 directory
2026-09-05 15:51:31 +02:00
Martin Vogel 3c7427efb7 Merge pull request #1597 from DeusData/feat/lean-output-contract
feat: make CLI and MCP output lean by default
2026-09-05 10:42:06 +02:00
Martin Vogel 9427f43515 test(log): restore the level a test found instead of the old quiet default
With the library default back at INFO, the branch's log and progress-sink
tests were still handing the process a WARN level on their way out —
main's `/* restore */` lines had been rewritten to WARN — so every suite
that ran after them in the same process lost its INFO records (the CI
shard that runs `log` before `index_format` is exactly that order).

test_log.c keeps main's tests and restores verbatim; the two branch
tests (the CBM_LOG_LEVEL opt-in and the startup policy) save the level on
entry and hand it back. The progress-sink tests pin the quiet level they
exercise explicitly and restore it, instead of assuming the process
entered at WARN.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 07:59:14 +02:00
dependabot[bot] 3979f12a74 build(deps): bump actions/deploy-pages from 5.0.0 to 5.0.1
Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/actions/deploy-pages/releases)
- [Commits](https://github.com/actions/deploy-pages/compare/cd2ce8fcbc39b97be8ca5fce6e763baed58fa128...368f82528645a54fb793d4d04e342629a3f51346)

---
updated-dependencies:
- dependency-name: actions/deploy-pages
  dependency-version: 5.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-05 05:46:19 +00:00
dependabot[bot] f8132b42ac build(deps): bump softprops/action-gh-release from 3.0.2 to 3.0.3
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/3d0d9888cb7fd7b750713d6e236d1fcb99157228...efb35369e0ad2afab669f228072c1b0d510eae64)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-05 05:43:59 +00:00
Martin Vogel b795203025 fix(log): keep the library default at INFO; quiet is a process policy
The lean-output branch had lowered the logger's raw default from INFO to
WARN so allocator setup could not pollute protocol or CLI output before
argv is classified. That moved a process policy into the library: any
process that never applies the role policy — the test runner, an
embedder — silently lost every INFO record, and tests that observe INFO
records through the log sink (the index-format boundary's
`pipeline.route path=format_change_reindex`, the importance pass
counters) went red for a contract that was in fact intact.

Restore the INFO library default and leave the quiet default where the
branch applies it, in cbm_log_init_for_process at process startup: thin
frontends go quiet, detached daemons keep INFO, physical workers require
it. The format-migration route record returns to INFO as on main; the
previous commit's WARN promotion was the wrong layer for this fix.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 07:29:13 +02:00
Martin Vogel c25c16206b fix(pipeline): keep the format-migration route visible at the quiet default
The lean-output branch starts the raw logger at WARN so allocator setup
cannot pollute protocol or CLI output before argv is classified. The
index-format boundary's route record, `pipeline.route
path=format_change_reindex`, was an INFO record, so a process that never
applies the role policy (the test runner, an embedder) and every thin
frontend at its default lost the only log-side evidence that an index was
rebuilt once because its format changed (#1104) — the response's
`format_migration:true` was unaffected. Log the migration route at WARN,
like the sibling `invalid_existing_db` deviation: a once-per-upgrade full
rebuild is exactly what explains a long index and a cursor reset.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 07:17:19 +02:00
Martin Vogel a15d99e39f test(mcp): drive the fail-closed search test through the scan seam
The unreadable-operand scenario used mode-0 permission bits, which
Windows cannot express and root ignores (the Colima legs run as root), so
the test skipped on both — and bare SKIP() is forbidden. The scoped file
list deliberately drops every missing or non-regular indexed path before
grep sees it, so a refusal can only come from the scan itself. The scan
seam now delivers one plausible hit and then grep's error status, on every
platform, and the test asserts that neither that hit nor zero matches is
ever reported as a complete page.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:27 +02:00
Martin Vogel b1100a499a fix(cli): print the cold-start hint as a plain line under --verbose
The hint moved into a structured info record when one-shot CLI output
went quiet by default. Structured values escape spaces, so the record
read `daemon_start` and a person (and the Windows lifecycle guard) could
not find `daemon start` in it. Keep the quiet default and, when --verbose
or CBM_LOG_LEVEL=info opts in, print the same plain stderr hint the CLI
printed before.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:27 +02:00
Martin Vogel c2b68d40b5 test(windows): settle the lean-output guards on current main
- test_daemon_lifecycle.py: daemon coordination is account-wide and
  CBM_CACHE_DIR does not move the rendezvous (main's CBM_RUNTIME_DIR
  relocation), so a private CBM_RUNTIME_DIR per cache is what makes the
  --verbose call cold and lets it observe the spawn hint.
- test_hook_augment.py: the CLI answers in the lean tree form by default;
  the guard parses the JSON projection, so it asks for it.
- test_mcp.c: the fail-closed search test skipped on Windows past the
  teardown that names its POSIX-only response.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:27 +02:00
Martin Vogel 619c683480 fix(mcp): settle the lean-output branch on current main
Fix-forward after rebasing onto main (545 commits, 26 conflicted-file
resolutions). Where main had shipped a behaviour in the meantime, main
wins; the PR keeps its output contract on top of it:

- search_code: the scan runs under main's supervised, deadline- and
  output-bounded runner and feeds the branch's streaming classifier from
  the runner's output file; the POSIX wrappers map grep's no-match to 0,
  so every non-zero exit fails closed (no exit-1 special case).
- trace_path: strategy/confidence precede args in header and rows (#1542),
  in both the tree table and the json legs.
- cypher: relationship expansion is never capped (a cap falsified
  aggregates) and unlabeled MATCH scans every candidate; `truncated` is
  raised only by a real ceiling — a clamped hop range is probed one hop
  beyond the cap and reports truncated only when a candidate exists there.
  The plain BFS and the trail BFS both probe one row past max_results so
  saturation is observable instead of silent.
- search_code full mode and the final json use main's U+FFFD sanitizer
  instead of '?' replacement.
- search_graph json keeps an empty `groups` array for semantic-only calls;
  list_projects json echoes offset/limit next to next_offset and accepts
  main's include_details spelling for the stats projection.
- discovery budget: main added get_file_outline, compare_graphs, manage_adr
  set_sections and the debug/include_details parameters; their descriptions
  are trimmed to the lean style and the ceiling moves from 15 to 18 KiB.

Tests that asserted a JSON default now request format:"json"; the
fail-closed search test uses an unreadable regular file (a missing or
non-regular indexed path is deliberately not a scan operand on main); the
non-regular fixture gives its real sources File nodes, as every indexed
project has; list_projects total counts listable projects (the 0-byte
ghost is never a row, so it is not a page either).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 7a1e069b29 fix(search): make scoped file patterns portable
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel f4c7d2010d test(watchdog): isolate worker runtime
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00