The recursion detector flagged any call whose short name matched the
enclosing def as self-recursion, so super().save() inside save,
axios.get() inside get, and console.error() inside error were all
false positives feeding is_recursive, recursion_in_loop and
unguarded_recursion.
Add is_self_receiver() and AND it into the short-name comparison: a
qualified callee only counts when its whole receiver chain (everything
before the last '.') names the same object — self/this/cls/@self, or
the enclosing def's own receiver identifier parsed from
CBMDefinition.receiver (Go: s in 'func (s *Store) save()'). Bare names
keep the prior behavior. Matching the full chain keeps self.obj.recur()
(a field's same-named method) out, and the dynamic receiver whitelist
keeps Go s.save() detected.
Distilled from PR #699 with two corrections: the receiver chain is
matched via the last dot instead of the first segment (self.obj.recur
was still a false positive), and the enclosing-receiver whitelist so
Go method self-recursion is not lost.
Closes#599
Co-authored-by: Gen Li <lg320531124@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Spring class-level RequestMapping prefixes were not folded into method mapping route_path values, so the graph could contain partial Route nodes like /orders instead of the actual endpoint /api/orders even while HANDLES edges existed.
Combine Java/Kotlin class mapping prefixes with method-level mappings during method definition extraction, and scan bounded annotation-argument subtrees so named or array-valued Spring annotations can provide the path literal.
Strengthen edge_types_probe to assert exact Spring Route node names for Java and Kotlin, not just HANDLES edge counts.
Signed-off-by: jonghyun.lee <jonghyun.lee@musinsa.com>
index_repository silently dropped files that failed to index: CBMFileResult.has_error
was set but never read, oversized files (>100 MB) were dropped with no signal, and
read/extract failures only bumped a logged-only counter — a file that couldn't be
indexed just vanished from the graph.
Collect and surface them:
- New retained {path, reason, phase} error list on struct cbm_pipeline (mirrors the
excluded_dirs pattern) + accessor + a back-pointer on cbm_pipeline_ctx_t so both
extraction paths can append. Wired in BOTH the sequential (pass_definitions) and
parallel (per-worker, merged lock-free) paths — small repos take the sequential
path, so wiring only one would leave the guard vacuous.
- Feeds: read-fail, extract-fail, the newly-CONSUMED has_error (parse timeout /
parse failed, with error_msg), and oversized. The cross_lsp phase is reserved for
the crash supervisor (Track C) and not fed here (the cross-LSP passes are
best-effort with no failure return; feeding the no-source case would be false
positives).
- MCP/CLI response gains top-level "skipped_count" (0 on clean) and, when >0, a
capped "skipped":{files[<=50],count,truncated} + "logfile". Status stays "indexed"
— a reported skip is the expected, handled outcome, not a degradation.
- Per-run logfile (full uncapped list) written ONLY when >=1 file is skipped:
$CBM_INDEX_LOG override else <cache_dir>/logs/<project>-<epoch>.log.
- Generous env-configurable caps (src/foundation/limits.c): CBM_MAX_FILE_BYTES,
default raised 100 MB -> 512 MiB; over-cap files are REPORTED (phase oversized) +
WARNed, never silently dropped.
Reproduce-first: tests/test_index_resilience.c (gating) — an oversized file (cap
lowered via env) must appear in skipped[] with the 2 good files still indexed and a
logfile written; a clean run has skipped_count 0 and no logfile. Genuine guard:
no-op'ing the recorder makes the oversized file silently vanish (RED). Full suite
5750/0, no ASan/UBSan.
Part of the resilient-indexing effort (Track B surfacing layer). Refs #668.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The per-language "resolve calls in AST node" walkers recurse once per nesting
level. Four of them — Python (py_resolve_calls_in), Go (resolve_calls_in_node),
PHP (php_resolve_calls_in_node), Kotlin (kt_resolve_calls_in_node) — had no depth
guard, so a deeply-nested or cyclic file could drive them into a native stack
overflow (SIGSEGV) that takes down the whole index run. C and Java already guard
this (C_LSP_MAX_WALK_DEPTH / JAVA_LSP_MAX_WALK_DEPTH).
Add the same wrapper/inner depth guard to all four, backed by a shared,
env-configurable cap: CBM_LSP_MAX_WALK_DEPTH (default 512) in scope.h. Past the
cap the wrapper skips the subtree — those calls stay unresolved (graceful
degradation, not a crash). The walk_depth-- runs after the inner body returns, so
early returns can't leak the counter.
Reproduce-first: lsp_{python,go,php,kotlin}_deep_nesting_no_crash (30000-deep
nested calls, run under a forked child + WIFSIGNALED). RED without the cap (the
child SIGSEGVs — verified by raising CBM_LSP_MAX_WALK_DEPTH to 100M), GREEN with
it. Full suite 5752/0, no ASan/leak.
Part of the resilient-indexing effort (Track B prevention layer). Refs #668.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
walk_defs used a fixed `walk_defs_frame_t stack[4096]` — a single ~160 KB frame on
the C stack. Two problems:
- On small thread stacks (notably the pre-2026-03 Windows 1 MB main thread) that
huge frame overflowed the stack during the definitions pass — the 0xC00000FD
crash a reporter hit indexing a large SQL file (#668). Reproduced locally by
running extraction under a constrained stack (~208 KB) + ASan.
- The `top < 4096` push guards SILENTLY DROPPED every top-level definition past
4096, so a file with more than 4096 top-level defs was only partially indexed.
Replace the fixed array with a growable heap stack (wd_stack_t + wd_push): a
256-frame initial buffer that doubles on demand, bounded by a generous,
env-configurable ceiling (CBM_WALK_DEFS_MAX, default 8M frames) that WARNs once
instead of dropping, and returns cleanly on realloc OOM (no NULL deref). A normal
file now uses ~10 KB of heap instead of 160 KB of stack, and files with thousands
of top-level defs are fully extracted. The frame stack is a pure index-addressed
LIFO (frames copied by value on pop), so heap-backing is semantically identical.
Reproduce-first: walk_defs_no_truncation_over_4096_issue668 — 5000 top-level defs
must all be extracted (RED on the old 4096 cap, GREEN after). Full suite 5748/0,
no ASan/UBSan reports on the new alloc.
Root cause was pinned by reproduction (constrained-stack + ASan), refuting earlier
grammar/scanner hypotheses — extraction and the vendored tree-sitter tree ops are
otherwise iterative. The separate error-recovery assertion mode
(symbol<token_count) was not locally reproducible and is addressed by the
forthcoming general crash-containment layer.
Refs #668
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A wildcard tsconfig path alias (e.g. "@lib/*" -> "./src/lib/*") matches
any import specifier sharing that prefix, including an unrelated scoped
npm package meant to resolve from node_modules. The direct match
already failed cleanly (no node exists at the substituted path), but
cbm_pipeline_resolve_import_node's Strategy 4 - a generic "strip a
trailing path segment and retry" fallback applied to every language,
not just its intended Rust crate::/self::/super:: case - re-resolves
the truncated import string and can land on the tsconfig's OTHER, bare
alias entry that maps straight to a directory. The resulting QN
collides with that directory's real Folder node (cbm_pipeline_fqn_module
and cbm_pipeline_fqn_folder both dot-join path segments with no type
tag), and Strategy 4 accepted whatever node it found there.
Strategy 3 (symbol-name fallback) already guards its matches with
import_targetable_label(), which excludes Folder/Project/etc. Strategy
1b (resolve_sibling_file, used only for markup/build-tool sibling
imports - SCSS/Just/BitBake/Meson/Pony, none of which are directory-
module languages) and Strategy 4 lacked the same guard; both now use
it too.
Strategy 1 (the primary, direct resolution path) intentionally keeps
no such filter: Go and Java are directory-module languages in this
pipeline (cbm_lang_module_is_dir), so a bare package import correctly
and deliberately resolves straight to a Folder node there - adding the
guard to Strategy 1 broke that (verified via the Go/Java integration
tests) before being scoped down to just the fallback strategies where
the collision actually originates.
Closes#767.
Signed-off-by: Alexandros Pappas <11921291+apappas1129@users.noreply.github.com>
tsconfig.json `paths` entries that climb out of their config's directory
(e.g. a tsconfig at apps/web/tsconfig.json mapping "@shared/*" to
"../../packages/shared/src/*", the standard monorepo layout) resolved to
a broken path: resolve_target_relative() only stripped a single leading
"./" and otherwise concatenated dir_prefix and target verbatim, leaving
literal ".." segments in the result. Those never match a real module's
FQN, since cbm_pipeline_fqn_module tokenizes on '/' without collapsing
them - so every cross-package import through such an alias looked like
dead code.
resolve_target_relative now walks the target's path segments and pops
a directory off dir_prefix for each "..", the same normalization
cbm_pipeline_resolve_relative_import already does for plain relative
imports.
Closes#730.
Signed-off-by: Alexandros Pappas <11921291+apappas1129@users.noreply.github.com>
search_code's scoped grep writes an "<root>/<file>" filelist and pipes it to
xargs. With a newline-separated filelist + plain `xargs`, a space anywhere in
the absolute path splits one file into several bogus arguments, so grep finds
nothing and scoped search returns 0 matches for any repo indexed under a path
containing a space (#687).
Fix: write NUL-separated records and consume them with `xargs -0`, so each path
stays a single argument regardless of spaces. On Windows the scoped path uses
PowerShell `Get-Content | Select-String -LiteralPath`, which reads line-by-line
and already handles spaces, so the filelist keeps newline separators there.
write_scoped_filelist writes each record piece-by-piece with fwrite/fputc (no
fixed-size stack buffer), so arbitrarily long absolute paths cannot overflow.
Reproduce-first: search_code_scoped_path_with_spaces_issue687 indexes a repo
rooted at "<tmp>/my project" and asserts total_grep_matches > 0 (RED on the old
newline + plain-xargs code -> 0 matches; GREEN after). Full suite green.
Distilled from #693 (thanks @lg320531124). Keeps that PR's correct NUL/xargs-0
idea and its Windows newline branch, but replaces its char buf[2048] + unbounded
memcpy (a stack buffer overflow reachable via long indexed paths, PATH_MAX 4096)
with the direct piece-by-piece write, and adds the reproduce-first guard it
lacked.
Closes#687
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-Authored-By: lg320531124 <lg320531124@users.noreply.github.com>
On Windows every git_context test SKIP_PLATFORMs, leaving git_run/make_git_repo
as unused static functions -> -Wunused-function under -Werror failed the Windows
build. Guard the two helpers (only called from the non-Windows test bodies) so
the file compiles clean on Windows while the tests still run on Linux/macOS.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
detect_changes returned an empty impacted_symbols set for projects indexed
inside a git worktree or from a repo subdirectory (#659): canonical_root was
computed wrong. `git rev-parse --git-common-dir` emits a path relative to the
directory passed via -C (input_path), NOT to worktree_root, and the "/.git"
suffix was stripped textually without resolving ".." components. So e.g.
"../.git" joined against worktree_root, or "/ws/scripts/../.git" strip, left an
unresolved ".." and never matched the project's stored root path.
Fix (src/git/git_context.c): join the relative --git-common-dir against
input_path (the -C dir) instead of worktree_root, and realpath()/_fullpath()-
normalize the result BEFORE stripping "/.git" (the .git dir always exists for a
valid repo, so resolution succeeds). This is consistent with the existing
realpath'd root_path used for prefix matching (mcp.c).
Reproduce-first: tests/test_git_context.c. The subdirectory case is the genuine
guard — a repo indexed from a subdir yields a *relative* --git-common-dir, so the
unfixed code leaves an un-normalized "<root>/subdir/.." (verified RED; GREEN after
the realpath fix). The linked-worktree case is a supporting invariant, not the
#659 reproducer: git that emits an *absolute* worktree --git-common-dir (e.g.
2.48.x) doesn't manifest the bug, so it passes either way; it still enforces the
worktree->main-root invariant. All three git-based tests SKIP_PLATFORM on Windows
(the CI shell there cannot init a repo via system()).
Distilled from #672 (thanks @anivaryam); that PR's production fix is taken
verbatim. Its repo_root/subdir tests ran git unconditionally and failed the
Windows CI leg ("failed to init git repo"); here all three git-based tests are
Windows-skipped so ci-ok is green cross-platform. Supersedes #696, which only
realpath'd after the strip without changing the join base and so still yielded
the workspace parent for the subdirectory case.
Closes#659
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-Authored-By: anivaryam <anivaryam.dev@gmail.com>
The #546 fix (bfs_union_same_name) BFSs from every graph node sharing the
resolved name and merges the results, so a symbol split across a real
implementation and a body-less ambient .d.ts stub reports a complete caller
set. But pick_resolved_node flagged ambiguity only on an exact score tie, and
node_resolution_score adds the body span, so two genuinely-DIFFERENT functions
that share a name but differ in body length score differently, dodge the tie,
and get their caller sets unioned into one confidently-conflated answer -- the
mirror of the #546 under-report.
Complete the ambiguity rule: two or more *real* callable definitions sharing a
name (callable label + non-empty body span) are ambiguous and routed to the
existing disambiguation (snippet_suggestions) path. A body-less span==start node
is an ambient declaration / signature stub -- a fragment of one logical symbol
-- so a real implementation plus its .d.ts stub stays non-ambiguous and still
unions, preserving the #546 behavior.
Reproduce-first: tool_trace_call_path_distinct_defs_not_over_unioned (two
unrelated same-named functions with different spans -> merged caller set on the
old code = RED; ambiguous on the fix = GREEN) plus
tool_trace_call_path_dts_stub_unions_with_impl guarding that the stub+impl union
still works. Full suite green.
Distilled from #650 (thanks @mvanhorn); its trace_-prefixed reimplementation
collided with main's already-landed bfs_union_same_name and could not merge.
Refs #546, #650
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-Authored-By: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
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>
cbm_project_name_from_path hex-encodes non-ASCII path bytes (#571), but had no
length cap -- a deep CJK / non-ASCII path triples in length past the
filesystem's 255-byte filename-component limit, so <cache>/<name>.db becomes
un-openable (ENAMETOOLONG).
Cap the derived name at FQN_MAX_NAME_LEN (200): names within the cap are
returned UNCHANGED (no drift for already-indexed projects), while a longer name
keeps its first (CAP-9) bytes plus an FNV-1a hash of the full name as a "-%08x"
suffix, so distinct long paths stay distinct. The hex encoding scheme is
unchanged.
Distilled from #624 by Matt Van Horn (the ENAMETOOLONG catch); the rest of that
PR was redundant with the #571 fix already on main and would have swapped the
encoding scheme, causing project-name drift.
Reproduce-first: project_name_length_capped_issue624 builds a deep CJK path
(~376-byte raw name) and asserts the result is <= 200, validator-safe, and that
two long paths differing only in the trailing segment map to different names; a
short CJK path stays byte-identical (no drift). RED (376 > 200) -> GREEN. Full
suite 5738/0.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-Authored-By: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
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>
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>
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>
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>
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>
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>
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>
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>
In a linked worktree, <repo>/.git is a regular file ("gitdir: <path>")
rather than a directory. cbm_discover_ex gated all per-clone ignore
sources on .git being a directory, so inside a worktree it skipped
.git/info/exclude and the global core.excludesfile entirely and walked
into directories the user had excluded there (e.g. build/, twister-out/).
Committed .gitignore still applied via the issue #510 path, which is why
the gap was easy to miss.
Resolve the git common dir up front: when .git is a gitlink file, parse
'gitdir:', then follow <gitdir>/commondir to the shared dir where git
keeps info/exclude and config (falling back to the gitdir when no
commondir file is present). info/exclude and config are then read from
that common dir, so a worktree behaves like an ordinary checkout.
Adds discover_worktree_info_exclude (fails before this change) and
discover_worktree_committed_gitignore.
Signed-off-by: Nahim El Atmani <2959826+Naam@users.noreply.github.com>
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>
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>