The mixin walk recursed into prepend and include at every level with only
the depth cap of 16 as a bound, so a module that both prepends and
includes the next one doubled the paths per level: a miss on a chain of n
such modules cost 2^n visits (measured 4 -> 20,744 for n = 2..14).
Each lookup is now one walk that carries the fail-closed blocked flag and
a visit stamp. Ruby linearizes ancestors, so a module reached a second
time in one walk was already searched and missed (a hit or a block would
have ended the walk); skipping the repeat keeps every lookup linear in the
ancestors the file declares. Lookup order, hits and the fail-closed rules
are unchanged. The stamps live on the per-file class table, so the walk
allocates nothing.
The walk entries feed CBM_LSP_WORK (seam builds only; product builds
compile it to nothing). rubylsp_mixin_walk_visits_each_module_once pins
linear work for the doubled chain on the instance and the class side
(before: 66 -> 4,098 visits for n = 6 -> 12; after: 14 -> 26) and keeps a
diamond and a prepend-plus-include pair resolving exactly once.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The MSan output handler checked msg[0] before marking the text
initialized, so MSan reported the handler's own read
(tests/test_main.c:2151). Unpoison first, then test for the empty
replay.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The "group shrinks" step of doc_links_md_member_missing_incremental
removed one of two Python `area` methods and expected the closure-repair
route. Since the Python namespace admission on main (merged into this
branch with origin/main), a Python file whose surface changes always
takes the full route (closure_decline reason=python_namespace_changed),
so the step ran FORCED_FULL on every platform; the Windows leg was simply
the first to report it. Nothing here depends on timing: the planner
compares content hashes.
The group now lives in a Ruby file. Ruby has no doc-link scope, so the
lost method reports no removed name and the document re-resolves only
through the ambiguous `path::member` rule, which this step exists to
bind. (A Rust group was tried too: its scope reports the method as a
removed name, so the step passed with the rule removed.)
Test: the step takes closure repair with closure=2 (shapes.rb and
docs/areas.md). With "ambiguous" dropped from doc_row_member_reason it
stays on closure repair with closure=1 and keeps the stale ambiguous row
where the full index has the Areas -> Square.area edge.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The MSan lane compiles mimalloc without -fsanitize=memory on purpose, so
MSan never sees mimalloc write its own stack. Its text output is the one
place those stack bytes reach an MSan interceptor: a message formatted in
a mimalloc stack buffer arrives at fputs still carrying the shadow an
earlier instrumented frame left there (cbm_log's line_buf), and MSan
reports initialized bytes as uninitialized.
mimalloc 3.5.3 prints an arena-alignment fallback warning on the x86-64
MSan lane, which turned this into a use-of-uninitialized-value report in
the mem suite (_mi_prim_out_stderr, 43 bytes: the per-thread prefix).
Under __has_feature(memory_sanitizer) only, the test runner now registers
a mimalloc output handler at the top of main that unpoisons the finished
text and writes it to stderr. Registering replays mimalloc's delayed-output
buffer, which its constructor already wrote to stderr before main, so the
handler skips that replay instead of repeating it behind a stray newline.
Production code and every non-MSan build are unchanged.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
mimalloc 3.5.3 treats a MinGW build as having a process-attach hook only
when MI_MINGW_UCRT64 is defined (src/prim/windows/prim.c). Upstream's
CMake sets it; Makefile.cbm did not. src/prim/prim.c therefore also
compiled its constructor, and _mi_auto_process_init ran twice on Windows:
once from that constructor and once from the CRT TLS callback.
The second run flushes the delayed-output buffer to stderr again, and the
first flush had left a newline in it, so every Windows run started with a
blank line on stderr. That is what failed smoke-win's one-line --version
check. In the MI_DEBUG=2 test build the same second run reaches
mi_assert_internal(mi_out_default == NULL) in options.c, which aborts.
3.3.2 had no such condition.
Define it for IS_MINGW builds in both the production and the test
mimalloc flags. Every MinGW toolchain this repository builds with (MSYS2
CLANG64 and CLANGARM64) is UCRT. No vendored source changes.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
An unresolved `path::member` row (missing or ambiguous) leads to its
file by no edge, so an incremental run never re-resolved the document
when that file changed. Two edits then left a row where a full index
has an edge: a body edit that writes a missing member's name (no
definition added), and a same-named pair of methods shrinking to one
(the name stays, so no removed name reaches the rows).
The closure now also takes the files whose missing or ambiguous rows
with "::" name a changed or deleted file, matched by base name since a
reference may be written relative to the document or to the root. When
memory runs out, every such row's file is taken.
Test: doc_links_md_member_missing_incremental requires incremental ==
full on the closure-repair route for both edits and for dropping the
name again. Without the new closure rule the "member written" step
diverges; with it limited to missing rows the "group shrinks" step
does.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A `path::member` reference became a missing row whenever the member
lookup found no definition node, but real documentation names members
the file has without a node the lookup binds: a call form
(`src/util.py::clamp()`), a C macro or a prototype in a header, a
TypeScript namespace, a Python enum member, an annotated class
attribute. Those lost their correct file edge and were counted as doc
rot.
A member now reads without a trailing call form, so `clamp()` binds
`clamp`. When no definition matches, the resolver reads the code file
and calls the member missing only when the file does not write its name
at all (a whole identifier, case-sensitive); a member the file writes
keeps the file edge it had before. A file that cannot be read whole
makes no claim. Ambiguous members are unchanged.
Test: doc_links_md_member_written_issue2587 indexes each shape under its
own heading, and two members that stay missing (Color::BLUE, which the
file writes only as LIGHT_BLUE and a lowercase "blue", and
no_such_function). On the previous head all seven shapes are missing
rows; without the call-form trim the call form is; without the
identifier boundary, or with a case-insensitive match, BLUE takes the
file edge.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
- Non-ASCII paths: drop the NTFS junction example; the workaround is
ASCII-only paths for the two variables, or an upgrade.
- Update entry: name the real command (update, not upgrade) and mark
#1168 as closed, fixed in v0.10.0.
- Index worker: from v0.12.0 a worker that exits without returning a
result is no longer counted as a success, and its log is kept; ask
for logs when indexing still fails on v0.11.0 or newer.
- Antigravity: state the affected range (all releases through v0.12.1
when neither detection signal is present) and which signal each
version checks; scope the duplicate binary to v0.9.x and earlier.
- OpenCode: list the detection signals per version (PATH, config, and
from v0.12.0 the data folder), the 4 KB PATH and npm/mise/scoop shim
gaps fixed after v0.12.1 (#2381), and a workaround per version.
The page structure and the original entries were first drafted by Tamer
in #2262.
Refs #1170
Co-authored-by: Tamer <tamer.elbakkali@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The #2787 change added client_adapter_pi_emits_string_enum_issue2787 directly below client_adapter_pi_wraps_result_and_honors_abort and, in the same hunk, removed that test's check that the generated abort handler kills the cli child (if (!child.killed) child.kill();). The generator still emits the line; restore the assertion byte-for-byte so the abort path keeps its coverage.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Smoke check 8d required Claude's PostToolUse matcher to be exactly Read. The
drift note widens it on purpose to Read|mcp__.*codebase-memory-mcp__.*, so a
graph tool call can reset the search count, and the macOS PR smoke failed on
it. 8d now requires exactly the new matcher, and besides a Search matcher it
also refuses a catch-all ("*" or empty) matcher, as its comment promised.
Checked against real installs: the previous binary's Read-only settings fail
8d, the new install passes, and the new install plus a catch-all PostToolUse
entry fails.
Refs #509
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The search hook gives each Grep, Glob or Bash search the matching graph
symbols, but a session can keep searching text for questions the graph answers
in one call (#509). Once a session has run three searches with no
codebase-memory-mcp tool call, the next augmentation now adds one factual line
saying so. The next note comes after six searches, then twelve, and any graph
tool call resets the count.
- State: a per-session counter in the process that runs hook requests (the
daemon), keyed by the payload's session_id, in a bounded 64-slot table that
evicts the least recently used session, behind a spin lock. Only searches
that reach the graph lookup count. A search without an augmentation still
counts, and the note waits for the next augmentation.
- Reset: Claude's managed PostToolUse entry now matches
"Read|mcp__.*codebase-memory-mcp__.*", covering plain and plugin server
names. The previous Read-only entry, as installs up to v0.12 wrote it, is
recognized as owned, so install replaces it in place and uninstall removes
either.
- Scope: Claude Code only. Codex sends the same event payload with a turn_id
but has no reset hook, so its sessions are not tracked, and payloads without
a session_id are not tracked either.
- Cost: a graph tool call now runs one hook round-trip to the daemon for the
reset, about 130 ms on a local build.
Refs #509
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Split the PR along the line the review asked for. The request timing and
transport fields stay: every mcp.request log line carries
handler_duration_ms, response_serialization_ms, cancelled and timed_out,
and a search that reaches its deadline logs status=timeout.
The behaviour changes go back to the base text and need a separate
maintainer decision: stricter jsonrpc/id parsing, dropping "result":null,
NULL returns from the response formatters, leaving the stdio loop on a
missing response or an input/output failure, the removed blocking-poll
fallback, the HTTP 500 path, the daemon session end on a missing response,
and the update-notice warning. Their tests go with them. The list_projects
sort guard is dropped as well (main has the same fix).
cbm_mcp_server_handle formats its three responses with
cbm_jsonrpc_format_response directly; a NULL reply still marks the request
as an error. The cbm_log_mcp_request checks in tests/test_log.c are back
with the new signature, plus checks for the new fields and for
status=timeout. clang-format on the new lines.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>