Commit Graph
1115 Commits
Author SHA1 Message Date
Martin Vogelandmuskiteer 024c0c5f76 feat(extract): add Route+HANDLES for Django REST Framework @action methods
Django REST Framework ViewSets expose extra endpoints through @action-decorated
methods, e.g. @action(detail=True, methods=["post"], url_path="approve"). On
main these methods carried no route_path, so Phase 2a created no Route node and
no HANDLES edge for them -- the endpoint was invisible in the graph (#603).

Add try_drf_action_decorator (internal/cbm/extract_defs.c): when a method's
decorator identifier is "action", read the methods / url_path / detail kwargs
and set *out_path/*out_method. The existing decorator-route pipeline
(ensure_one_decorator_route, Phase 2a) then creates the Route + handler->Route
HANDLES edge in the established direction. Fallbacks: method name for url_path,
"GET" for methods; detail=True -> /{pk}/<seg>, else /<seg>.

Reproduce-first guard: handles_drf_action_python in the edge_types_probe suite
-- a DRF @action method must produce >=1 HANDLES edge (RED with 0 edges before
the extractor, GREEN after).

Known limitation: multi-method actions (methods=["get","post"]) capture only the
first method -> a single Route.

Distilled from #647 (thanks @muskiteer). Drops that PR's unrelated project-wide
HANDLES edge-direction reversal, which was unnecessary for the fix and broke the
handler->Route convention relied on by pass_cross_repo.c's find_route_handler.

Closes #603

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-Authored-By: muskiteer <agarwal.vansh3110@gmail.com>
2026-07-01 22:49:01 +02:00
Martin Vogel 2d48c06042 Merge pull request #703 from ShauryaaSharma/fix/windows-non-ascii-700
fix(windows): use _wfopen for non-ASCII project paths
2026-07-01 22:13:09 +02:00
Martin Vogel 5b75b16153 Merge pull request #744 from DeusData/feat/vendor-mojo-grammar
chore(grammar): vendor Mojo tree-sitter parser
2026-07-01 21:25:17 +02:00
Martin Vogel 7cf1e4d213 Merge pull request #619 from catsmonster/fix/hook-augment-windows-paths
fix(hook): make hook-augment work on Windows drive-letter paths
2026-07-01 20:59:52 +02:00
Martin Vogel 8d2088b92b chore(grammar): vendor Mojo tree-sitter parser
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-01 20:17:11 +02:00
Martin Vogel 67099fa249 Merge pull request #736 from DeusData/fix/deprecate-raw-json-cli
feat(cli): deprecation-warn raw-JSON args + full flag-ops smoke coverage
2026-07-01 17:21:27 +02:00
Martin Vogel 4e3c417c8c Merge pull request #735 from DeusData/fix/available-projects-json-truncation
fix(mcp): keep available_projects error JSON valid when many projects (#235)
2026-07-01 17:21:08 +02:00
catsmonster 4bb9effe4c fix(hook): make hook-augment work on Windows drive-letter paths
hook-augment's PreToolUse Grep/Glob augmenter required POSIX-style
'/'-prefixed absolute paths in two guards, so a Windows cwd (C:\repo or
C:/repo) always failed: the _WIN32 cwd check returned early, and the
walk-up loop guard (dir[0] == '/') never iterated. The result was a
structural no-op on Windows — zero additionalContext for every Grep/Glob,
even for an exactly-indexed symbol.

Add cbm_hook_path_is_abs(), accepting POSIX "/..." and Windows "X:/..." roots;
normalize backslashes to forward slashes on the Windows cwd; and stop the
parent-climb at the drive root as well as the POSIX root. POSIX behavior
is unchanged.

Adds a cli_hook_augment_path_is_abs regression test (the predicate is
exposed as cbm_hook_path_is_abs via cli.h) covering POSIX and Windows
drive roots — it fails on the pre-fix POSIX-only guard and passes after.

Verified with that test plus a standalone walk self-test (full path
resolves first, then climbs to one level below the drive root, symmetric
to POSIX); clang-format clean on all three files; clang -fsyntax-only on
the translation unit. Not built through the full toolchain locally (no
make/zlib) — relying on Windows CI to confirm the build.

Fixes #618

Signed-off-by: catsmonster <shaked.brand@gmail.com>
2026-07-01 06:19:30 -06: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 5c92f5c63e feat(cli): deprecation-warn raw-JSON args + full flag-ops smoke coverage
Raw JSON (cli <tool> '{...}') still works for back-compat, but now emits a
deprecation warning to STDERR (stdout stays clean JSON for piping) nudging
users to flags / --args-file / stdin. Only the raw-JSON path warns.

Also make scripts/smoke-test.sh exercise the whole new CLI input surface
end-to-end against the built binary: every tool call migrated from raw JSON to
FLAG form (index_repository / search_graph / trace_path / get_graph_schema /
query_graph incl. all Cypher checks / get_architecture / search_code /
delete_project -- flag form passes the query as one argv token, so the old
JSON-escaping dance is gone), plus 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). One raw-JSON call is kept
as the deprecation guard (asserts the warning fires; the flag form asserts it
does not).

Reproduce-first: reverting the warning makes the raw-JSON smoke assertion fail
("did not emit a deprecation warning"); restored -> green. Full smoke ALL
PASSED; C unit suite 5735/0 (unchanged -- only main.c + the smoke script).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-01 13:27:03 +02:00
Martin Vogel d4c0b6bdbc fix(mcp): keep available_projects error JSON valid when many projects (#235)
collect_db_project_names built the "project not found or not indexed" error's
available_projects list into a fixed 4 KB buffer and, on overflow, let snprintf
truncate the last name mid-token then clamped offset to the buffer end -- so the
error response was malformed, unterminated JSON (e.g. ...,"tmp-cb],"count":50})
whenever enough projects were indexed to overflow the buffer. A client parsing
the error would fail. Found while exercising the CLI against a cache with 280
indexed projects.

Write element-boundary instead: before each name, require the whole element
(optional leading comma + the quoted name + NUL) to fit in the remaining buffer;
otherwise stop at that boundary. The array then always holds only complete names
and count == its length, so the JSON is always valid (just fewer names when the
buffer is full -- callers can run list_projects for the complete set).

Reproduce-first: tool_bad_project_error_valid_json_issue235 builds 50 dbs with
~120-char internal names (well over 4 KB), triggers the error, and asserts the
response parses as JSON and available_projects is an array with length == count.
RED before (yyjson_read returns NULL on the truncated buffer), GREEN after. The
existing tool_bad_project_name_no_overflow_issue235 (buffer bounds) still passes.
Full suite 5736/0.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-01 12:58:32 +02:00
Martin Vogel 4a422856e3 Merge pull request #719 from DeusData/fix/readonly-query-open
fix(store): open query stores READONLY so queries never mutate dbs
2026-06-30 18:54:07 +02:00
Martin Vogel ad422bc59a Merge pull request #717 from DeusData/fix/704-store-resolution
fix(mcp): resolve projects by internal name + filter ghost dbs (#704)
2026-06-30 18:53:45 +02:00
Martin Vogel c11e659047 Merge pull request #716 from DeusData/fix/680-cli-flags
feat(cli): accept flags, stdin, and --args-file for tools + per-tool --help (#680)
2026-06-30 18:52:46 +02:00
Martin Vogel 96a2f550b1 Merge pull request #714 from DeusData/fix/640-project-arg-alias
fix(mcp): accept project_name alias + clearer no-store error (#640)
2026-06-30 18:52:13 +02:00
Martin Vogel 8ec7523a30 fix(store): open query stores READONLY so queries never mutate dbs
Query tools opened the project db with SQLITE_OPEN_READWRITE and ran write
pragmas (PRAGMA journal_mode=WAL + wal_checkpoint), so every query MUTATED the
db (rewrote the header, created -wal/-shm) and FAILED outright when the db or
its directory was read-only (the WAL pragma cannot create -shm on a read-only
filesystem -> resolve_store returned NULL -> "project not found"). Latent
data-integrity bug found while diagnosing #704 (a query grew a 0-byte shadow
db to 100 KB).

- cbm_store_open_path_query now opens SQLITE_OPEN_READONLY. configure_pragmas
  gains a read_only mode that runs only the non-writing pragmas (foreign_keys,
  temp_store, busy_timeout, mmap_size) and skips journal_mode / wal_checkpoint
  / synchronous. The read-write open (cbm_store_open_path) is unchanged.
- Read-only-filesystem fallback: SQLite opens lazily, so a RO-FS failure
  surfaces on first access; we probe with SELECT 1 and, only if the file
  exists, retry once with a `file:<path>?immutable=1` URI (URI-special
  characters percent-encoded) which bypasses WAL and reads the main db
  directly. The fallback is gated to that failure path so writable-fs readers
  still see uncheckpointed WAL frames via -shm.
- manage_adr is the only resolve_store caller that writes; it now opens its own
  read-write handle (cbm_store_open_path) for ADR storage (in-memory/embedded
  stores still write through the resolved store). All 12 resolve_store callers
  audited; query_graph is read-only (the Cypher engine rejects writes).

Reproduce-first (non-vacuous, no real read-only mount needed):
- readonly_query_does_not_mutate_db: snapshot a rollback-journal db's bytes,
  run search_graph, assert the file is byte-identical + no -wal created. RED
  before (the WAL pragma rewrote the header).
- readonly_query_succeeds_on_readonly_fs: chmod the containing DIRECTORY 0555
  (the real read-only-fs symptom -- a 0444 file alone is vacuous since SQLite
  downgrades the open), run search_graph, assert it succeeds. RED before (WAL
  pragma could not create -shm -> "project not found"); exercises the immutable
  fallback.
Full suite 5724/0.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-30 16:48:40 +02:00
Martin Vogel 1d1772a24f fix(mcp): resolve projects by internal name + filter ghost dbs (#704)
Project resolution was filename-addressed but internal-name-validated:
resolve_store opened <cache>/<passed>.db and required the internal
projects.name row to equal the passed name, while list_projects advertised
the .db FILENAME. When a db's filename differed from its internal name (a
copied/renamed db, or a legacy '.'-vs-'-' username twin from a past path
sanitization), it showed in list_projects but every query returned "project
not found or not indexed". Node rows are keyed on the internal name, so just
opening the file is not enough -- queries must use the internal name too. The
intermittency ("works now, fails 10 min later") was a warm in-process store
cache masking a broken cold resolve; a fresh cli call is always cold.

Key list + resolve on the INTERNAL project name, with no file renames or
mutation:
- list_projects / collect_db_project_names / build_project_json_entry now
  advertise each db's internal projects.name (via db_internal_project_name)
  and skip ghost/empty/corrupt dbs (0-byte, no project row) so the listed
  names are exactly the ones that resolve.
- resolve_store keeps the fast path (open <passed>.db, get_project -- the
  common case where filename == internal name). On a miss it runs a bounded
  cache-dir scan, adopting the db whose sole internal project name equals the
  passed name (gated on exactly one project row so it never serves another
  project's data). No match -> NULL (a typo stays not-found). The scan runs
  only on the fallback.

Reproduce-first: tool_resolve_store_by_internal_name_issue704 builds an
isolated CBM_CACHE_DIR with a control db, a drifted db (filename != internal
name), and a 0-byte ghost; asserts list advertises the internal name (not the
filename), the drifted project resolves via the scan, and the ghost is neither
listed nor resolvable. RED (113/1, list lacked the internal name) -> GREEN
(114/0). Full suite 5723/0.

Also hardened tool_bad_project_name_no_overflow_issue235: its fixture used
1-byte non-SQLite files that the new ghost filter would skip, making the
buffer-overflow guard vacuous; rebuilt it with valid dbs carrying long
internal names so the guard still fires.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-30 16:11:07 +02:00
Martin Vogel 3c2c12006f feat(cli): accept flags, stdin, and --args-file for tools + per-tool --help (#680)
The CLI only accepted a single inline JSON argument (cbm cli <tool> '<json>'),
which PowerShell mangles on Windows and which made `cli <tool> --help` return
"repo_path is required" -- the "--help" token was parsed as the JSON args, and
there was no per-tool help at all.

run_cli now resolves tool arguments by precedence: --args-file <path>, a raw
JSON object (back-compat), --flag / --flag=value / bare-boolean flags, piped
stdin, then empty {}. Flags are converted to a JSON object via the tool's
input_schema: kebab-case keys map to snake_case, values are typed
(string/integer/boolean), and repeated flags accumulate into array-typed
properties (--semantic-query a --semantic-query b -> ["a","b"]). stdin and
--args-file are UTF-8-clean and bypass shell quoting entirely (the robust path
on Windows). `cli <tool> --help` prints usage + the tool's flags, generated
from the same input_schema the MCP tools/list advertises -- no hand-maintained
table.

cbm_mcp_tool_input_schema() exposes a tool's schema; cbm_cli_build_args_json()
and cbm_cli_print_tool_help() do the conversion and help.

Reproduce-first: 8 cli tests (string / integer-not-string / bare-boolean /
repeated-array / kebab->snake / --key=value with spaces / bad-positional-errors
/ help) -- RED on a stub, GREEN after. Full suite 5730/0.

Note: true CJK-path support on Windows still needs a UTF-8/wmain entry point
(argv is ANSI-codepage-mangled upstream of parsing); stdin/--args-file mitigate
it and are tracked separately.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-30 15:44:36 +02:00
Martin Vogel 8b56c52231 fix(mcp): accept project_name alias + clearer no-store error (#640)
Query tools resolved the project only via the exact "project" key, but
list_projects surfaces the field as "name" and the not-found hint said
"pass the project name", so callers naturally pass "project_name". With no
alias the arg was NULL and resolve_store bailed before opening any .db, so
every query returned "project not found or not indexed" even though the
project was indexed -- the reporter's strace shows the cache dir scanned but
no .db opened. Reproduced on macOS; not Linux-specific.

Add get_project_arg() accepting project | project_name | project_id |
projectName across the 11 query handlers (not bare "name" -- index_repository
uses it for an explicit project-name override). Split the no-store error so a
missing argument reports "missing required argument: project" instead of
masquerading as "not indexed", and name the literal "project" key in the
not-found hint.

Reproduce-first: tool_get_architecture_accepts_project_name_alias_issue640
and tool_search_graph_accepts_project_name_alias_issue640 (RED before ->
GREEN after). Full suite 5724/0, no regressions.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-30 12:51:49 +02:00
Martin Vogel f957dffa80 Merge pull request #712 from DeusData/docs/runtime-network-behavior
docs(security): document runtime update check
2026-06-30 10:51:20 +02:00
Martin Vogel 74b1a478ce docs(security): document runtime update check
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-30 09:42:18 +02:00
Martin Vogel 63e3f2c7a6 Merge pull request #632 from halindrome/feat/subagent-cmm-startup-hook
feat(install): inject CMM code-discovery reminder into Claude subagents
2026-06-29 23:45:16 +02:00
ShauryaaSharma 98218a282b fix(windows): use _wfopen for non-ASCII project paths (#700)
Standard fopen on Windows uses the ANSI/system codepage and silently
fails to open files whose absolute path contains non-ASCII characters
(accented, CJK, etc.). This causes the pipeline to skip parsing those
files entirely — File nodes are created by directory traversal (which
already uses wide Win32 APIs) but tree-sitter never receives the source,
so no Function/Class/Method nodes are extracted.

Add cbm_fopen(path, mode) to compat_fs:
- Windows: converts the UTF-8 path to UTF-16LE via cbm_utf8_to_wide
  (existing helper in win_utf8.h) and calls _wfopen.
- POSIX: delegates to fopen (no-op change, paths are already UTF-8).

Replace every fopen call that operates on user project file paths:
- src/pipeline/pass_parallel.c    (primary indexing path)
- src/pipeline/pass_definitions.c
- src/pipeline/pass_calls.c
- src/pipeline/pass_semantic.c
- src/pipeline/pass_usages.c
- src/pipeline/pass_lsp_cross.c
- src/pipeline/pass_pkgmap.c
- src/pipeline/pass_k8s.c
- src/pipeline/pass_envscan.c
- src/pipeline/path_alias.c       (tsconfig detection)
- src/pipeline/artifact.c
- src/discover/language.c         (content-sniff for language detection)
- src/discover/gitignore.c
- src/discover/discover.c
- src/discover/userconfig.c
- src/mcp/mcp.c                   (read_file_lines, adr.md reader)

Fixes #700

Signed-off-by: ShauryaaSharma <shauryasofficial27@gmail.com>
2026-06-30 01:03:09 +05:30
Martin Vogel 7824e505c1 Merge pull request #692 from DeusData/fix/smoke-10c-deflake
fix(smoke): de-flake 10c by corrupting the signature instead of removing it
2026-06-29 16:16:00 +02:00
Shane McCarron 5255939a95 fix(install): address QA round 1
QA round 1 (PR #632) flagged that the SubagentStart hook uses matcher "*",
the same matcher a user is most likely to pick for their own catch-all
SubagentStart hook. Because is_cmm_hook_entry keyed ownership on the matcher
string alone, install would remove the user's "*" entry and replace it with
ours, and uninstall could remove the wrong one.

Add an optional `match_command_substr` to the upsert/remove args, threaded into
is_cmm_hook_entry: when set, an entry must ALSO carry a hooks[].command
containing that substring to be claimed as ours. The Claude SubagentStart hook
passes "cbm-subagent-reminder"; all existing callers leave it NULL, preserving
their matcher-only behavior unchanged. Install/uninstall now only ever touch
CMM's own entry and never clobber a foreign "*" hook.

Add cli_claude_subagent_hook_preserves_user_entry covering the case: a
pre-existing user "*" SubagentStart hook survives both install and uninstall.

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
2026-06-29 08:42:36 -05:00
Shane McCarron 3466ba02e8 test(install): cover SubagentStart hook registration
Verify the Claude SubagentStart reminder: install writes a SubagentStart
entry with a match-all matcher pointing at cbm-subagent-reminder, a second
upsert is idempotent (no duplicate entry), and removal leaves no SubagentStart
key behind.

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
2026-06-29 08:42:36 -05:00
Shane McCarron 64af0b8c7e feat(install): inject CMM code-discovery reminder into Claude subagents
Subagents spawned via the Agent tool do not fire SessionStart, so the
existing SessionStart reminder never reaches them — they start without the
codebase-memory-mcp code-discovery guidance and fall back to grep/file-read.

Register a Claude Code SubagentStart hook (matcher "*") that injects a leaner
variant of the protocol via JSON additionalContext, omitting the
index_repository step since the parent session has already indexed the
project. SubagentStart injects context only via a JSON object on stdout, not
plain text, so the generated script emits a static JSON literal — no runtime
escaping and no python3/jq dependency. Advisory only; installed and removed
alongside the SessionStart hook in install_claude_code_config.

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
2026-06-29 08:42:36 -05: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
Martin Vogel b1a646ee08 Merge pull request #677 from DeusData/fix/c-lsp-shared-registry-on2
fix(lsp): seal shared Tier-2 cross-registry against O(n^2) resolve hang
2026-06-29 07:26:29 +02:00
Martin Vogel d10701dced fix(lsp): seal shared Tier-2 cross-registry against O(n^2) resolve hang
The per-file C cross-LSP resolver mutated the finalized, project-wide cross
registry that is shared read-only across the parallel resolve workers. Each
post-finalize add landed in a tail the hash index does not cover, so every
lookup linear-scanned an ever-growing tail -> O(files*defs) on large C
codebases. The Linux-kernel full index hung at "[4/9] Resolving" on 11 cores
for >6 min and never finished, plus a heap data race across workers.

Seal every shared cross-registry: CBMTypeRegistry gains a read_only flag, set
by all five {c,py,cs,ts,go}_build_cross_registry builders right after
finalize, and cbm_registry_add_func/_type no-op on a sealed registry. One
chokepoint guards every language, robust to any resolver mutation site. The
plain-C function-registration site is also skipped directly when the registry
is shared.

Add six cross-language invariant tests (c, c++, py, c#, ts, go): resolving
against a finalized shared registry must leave func_count/type_count
unchanged -- RED before the fix (C +1, C++ +2 post-finalize adds), GREEN
after.

Verified: full suite 5720 passed / 0 failed; Linux-kernel full index now
completes all 9 phases in 643s (4.88M nodes, 11.9M edges) instead of hanging
at phase 4.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-29 01:33:40 +02:00
Martin Vogel 9169f461d6 Merge pull request #676 from DeusData/docs/codeowners-authority
docs: add maintainer ownership policy
2026-06-29 01:20:16 +02:00
Martin Vogel 51286d6a6c docs: add release preparation checklist
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-29 00:04:59 +02:00
Martin Vogel 6c18baea34 docs: make maintainer policy project-oriented
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-29 00:00:10 +02:00
Martin Vogel 6ccf81d841 docs: add maintainer ownership policy
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 23:55:29 +02:00
Martin Vogel acee801e5c Merge pull request #675 from DeusData/fix/581-leak-mitigation
feat(diag): persistent memory trajectory + query-leak CI soak (#581 observability)
2026-06-28 23:35:16 +02:00
Martin Vogel f7d6d34457 feat(diag): persistent memory trajectory + query-leak CI soak for #581
#581 (query-path memory growth) does not reproduce in our soak: a 10-min,
200-file query-only run shows RSS flat-to-shrinking and committed plateauing
(mimalloc already purges on free, purge_delay=0). Rather than ship speculative
mitigations for a leak we cannot measure, this adds the observability to find it
where it actually occurs, plus a continuous guard.

- diag: the diagnostics writer now appends a PERSISTENT NDJSON memory trajectory
  (one sample / 5s: rss, committed, peak fields, page_faults, fd, queries), kept
  on exit (rotates to a .1 generation past 8 MB) so users can send the time
  series post-mortem. The previous latest-snapshot file was overwritten every 5s
  and deleted on stop.
- ci: the soak now also runs a read-only query-leak leg (CBM_SOAK_MODE query-leak,
  no reindex/mutate) on every platform including Windows, so a regression that
  introduces a query-path leak is caught. soak-test.sh RESULTS_DIR is now
  env-overridable so both legs keep separate artifacts.
- docs: README Troubleshooting and Diagnostics section + a bug-report field
  explain the no-telemetry stance and how to capture and share the trajectory.

Build clean; unit suite 5714/0; trajectory verified to persist post-exit.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 23:28:01 +02:00
Martin Vogel 4ab00df63f Merge pull request #657 from DeusData/feat/mcp-protocol-2025-11-25
feat(mcp): add structured tool metadata
2026-06-28 22:08:57 +02:00
Martin Vogel b7f1a71a6b style: apply clang-format to new mcp/ui/logging lines
The CI lint gate (scripts/lint.sh --ci, clang-format-20) flagged 7 files whose
new lines were not run through the project formatter. Reformat only those lines
(line wrapping / continuation alignment) — no behavior change.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 21:49:17 +02:00
Martin Vogel 345425a1bb merge main into mcp protocol branch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 21:34:18 +02:00
Martin Vogel aedb979f55 Merge pull request #673 from DeusData/qa/bug-repro-suite
qa: Python-builtin + kotlin.Any LSP fixes; false-guard audit (honest #581 known-red)
2026-06-28 21:29:03 +02:00
Martin Vogel d5414111b3 feat(ui): add i18n and index picker UX
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 21:21:32 +02:00
Martin Vogel 63ed6789df Merge pull request #656 from caioribeiroclw-pixel/fix/openclaw-mcp-config
Fix OpenClaw MCP config path
2026-06-28 21:05:38 +02:00
Martin Vogel d9d70656b4 fix(ui): cap graph rendering and index spawn
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 20:57:14 +02:00
Martin Vogel 10ab2074f7 test(repro): mark #581 as a whitelisted known-red TODO
#581 (query-path memory leak) is an OPEN bug the unit-scale fixture cannot yet
reproduce, so the test stays honestly RED rather than vacuously green. The
bug-repro board is a non-gating status board where reds ARE the expected state
(repro_main.c), so this is a tracked known-red TODO, not a hidden skip. Tag it
TODO(#581) for tracking; turning it green needs a real WAL/committed-pages
reproduction tier plus the query-path compaction fix.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 20:16:37 +02:00
Martin Vogel 927dffc336 test(repro): harden assert_no_resolvable_edge against vacuous pass
The 9 no-resolvable-edge tests asserted only that no CALLS edge targets the
unresolvable callee via inv_no_calls_edge_to_qn, which returns PASS when the
fixture produces ZERO edges for any reason — so a green did not prove the
unresolvable call was actually extracted and correctly dropped (a vacuous-pass
false-guard risk, unlike the sibling assert_lsp_strategy which checks
callable_sourced > 0).

Add an exercised-check to all four helper copies (c_cpp/java_cs/go_py/ts):
require at least one callable-sourced CALLS edge. Give each of the 9 fixtures a
resolvable in-fixture control call (known / Helper.Known / c.Inc) so a real edge
forms. The no-edge-to-callee invariant is unchanged; the tests stay green but
can no longer pass when extraction silently produces nothing.

Repro board: 311 passed, 1 failed (the honest #581 RED) — no regressions.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 20:07:59 +02:00
Martin Vogel 25d2fa8af2 test(repro): honest-RED #581, honest safety-net #431 (false-guard audit)
repro_issue581 was a FALSE GUARD. A 3-node fixture over 150 search_graph calls
cannot move process RSS (observed factor=1.00), so rss_end <= 3.0x rss_warmup
passed even on the leaking build, and an explicit PASS() escape fired when RSS
read 0. #581 is OPEN: there is no SQLITE_CHECKPOINT_TRUNCATE anywhere and
cbm_mem_collect is never called in the query path (only after index/delete). The
green was a lie. Replace the vacuous assertion with an honest FAIL that documents
the leak is unfixed and not reproducible in this tier (needs a real
WAL/committed-pages reproduction tier plus the fix).

repro_issue431 is actually FIXED: install_vscode_profile_configs (cli.c:3211)
scans Code/User/profiles/ and plans a per-profile mcp.json, and the test passes
via the real assertion. Its detection-unmet branch returned a VACUOUS PASS that
would hide a future regression; change it to SKIP_PLATFORM so a host where
detection cannot fire is honestly inconclusive, not falsely green.

Repro board: 311 passed, 1 failed (the honest #581 RED).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 19:53:44 +02:00
Martin Vogel 006ea01d60 fix(kotlin): inject kotlin.Any nodes so lsp_kt_any edges form
The Kotlin LSP resolves a method call on an unknown-typed receiver whose member
is a universal kotlin.Any method (toString/equals/hashCode) to kotlin.Any.<m>
and emits lsp_kt_any, but no kotlin.Any node existed in the graph, so pass_calls
had no target node and dropped the call (callable=0).

Add kotlin_builtins.c (mirror of py_builtins.c): inject kotlin.Any plus its
three universal methods into result->defs during cbm_run_kotlin_lsp, before the
pipeline mints def nodes. The lsp_kt_any call now targets the injected node and
forms a CALLS edge.

Update the kotlin grammar-label golden to include the 4 injected nodes, exactly
as python's golden already includes its injected builtins.

Un-skips repro_lsp_kt_any. Repro suite 312/0, unit suite 5693/0.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 19:00:10 +02:00
Martin Vogel 6b1e3a0d76 fix(calls): extract resolvable builtin calls so LSP builtin edges form
The call extractor dropped any callee matched by cbm_is_keyword(), and the
Python keyword set lists builtins (len, str, range, int, list, dict, print) to
suppress them as bare usages. That also dropped the textual CALL, so the LSP
resolution to the injected builtins.<name> node (py_builtins.c) had no call to
attach to and no CALLS edge formed (callable=0) for free-function and
constructor builtins. Method builtins (s.upper) were unaffected because method
names are not in the keyword set.

Add cbm_is_resolvable_builtin() — the builtins we mint a real graph node for —
and keep those callees in call extraction only; the keyword filter still
applies to usage and semantic extraction (its four other call sites are
unchanged). Every un-filtered builtin has a target node, so the new edge is
always callable-sourced, never Module-sourced.

Un-skips repro_lsp_py_builtin and repro_lsp_py_builtin_constructor.
Repro suite 311/0, unit suite 5693/0.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-28 18:47:39 +02:00