Commit Graph
889 Commits
Author SHA1 Message Date
imateusdev 8b813675be fix(routes): canonicalize path parameters so client calls match handlers
Route QNs (__route__METHOD__/path) are the rendezvous key for joining a client
HTTP_CALLS to the server handler that serves it, but they were built from the raw
path. Frameworks write path parameters with different placeholder syntaxes
(":id" Express/clients, "{id}" Axum/Spring/OpenAPI, "<id>" Flask/Rocket), so a
client call and its handler produced different QNs and never joined. Static
routes joined; parameterized routes (most of a REST surface) did not.

Add cbm_route_canon_path() which collapses each parameter token (":name",
"{name}", "<name>", "${...}") to a single "{}" token, and apply it to the path
at every HTTP-route QN construction site (pass_route_nodes, pass_parallel,
pass_calls, pass_cross_repo). Broker/async/infra QNs are left untouched. Only the
QN (identity/dedup key) is canonicalized; the Route node display name keeps the
original path. Parameter names are intentionally discarded so the same logical
endpoint matches across services that name the variable differently.

Adds tests/test_route_canon.c (11 cases). Fixes #505.

Signed-off-by: imateusdev <imateusdev@gmail.com>
2026-06-23 13:52:08 -03:00
Martin Vogel 684e35b0e2 Merge pull request #562 from DeusData/fix/493-gitignore-merge-atomic
fix(discover): make cbm_gitignore_merge atomic on allocation failure
2026-06-23 01:16:07 +02:00
Martin Vogel 5ea5579d2b fix(discover): make cbm_gitignore_merge atomic on allocation failure
cbm_gitignore_merge copied src patterns into dst one strdup at a time and
returned void on failure, leaving dst with a partial set of patterns and no
signal to the caller. If the dropped pattern was the critical exclude (the
.git/info/exclude entry that prevents an OOM walk), the bug could silently
recur with some patterns applied and others dropped.

Make the merge atomic: on any allocation failure roll back the partial copies
so dst is left exactly as it was, and return bool so callers can tell. The
.gitignore caller now degrades gracefully (exclude patterns skipped, as if the
file were absent) instead of ending up with a half-merged matcher.

Adds a reproduce-first test that injects a strdup failure mid-merge via a test
seam and asserts dst is unchanged; it is red without the rollback.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-06-22 23:51:49 +02:00
Martin Vogel acb7eaaa05 Merge pull request #528 from win4r/fix/incremental-inbound-cross-file-edges
fix(pipeline): preserve inbound cross-file edges on incremental reindex
2026-06-22 23:28:28 +02:00
Martin Vogel ff89931070 Merge pull request #475 from rainder/fix/args-json-stack-overflow
fix(pipeline): prevent stack-buffer-overflow in append_args_json
2026-06-22 23:28:10 +02:00
Martin Vogel 6e0f7f7926 Merge pull request #466 from KerseyFabrications/fix/name-resolution
fix(mcp): prefer definitions and report ambiguity in name resolution
2026-06-22 23:28:07 +02:00
Martin Vogel c3b5b00796 Merge pull request #464 from KerseyFabrications/fix/detect-changes-since
fix(mcp): honor detect_changes 'since' parameter
2026-06-22 23:28:03 +02:00
Martin Vogel 0ad4d2d029 Merge pull request #448 from isc-tdyar/fix/deploy-dir-always-skip
fix(discover): deploy/ directories indexed as duplicate source in full mode
2026-06-22 23:27:09 +02:00
Martin Vogel 97cbfce055 Merge pull request #412 from isc-tdyar/fix/cypher-label-filter-truncation
fix(cypher): label-filtered edge traversal silently truncates at 10 results
2026-06-22 23:26:43 +02:00
Pcristin 0618eaa99a fix: honor file_pattern in bm25 graph search (#555)
Signed-off-by: pcristin <xxxokzxxx@protonmail.com>
2026-06-22 22:05:29 +02:00
Kiril Klein, PhDandClaude Opus 4.8 df8bc9254d fix(mcp): trace_path cross_service follows CROSS_* edges (#522) (#538)
trace_path mode=cross_service resolved its default edge-type list to
{HTTP_CALLS, ASYNC_CALLS, DATA_FLOWS, CALLS} — none of the CROSS_* edge
types the cross-repo pass actually writes. Since cbm_store_bfs filters
edges with an exact `WHERE e.type IN (...)` clause, cross-service traces
silently skipped every cross hop even when the edge was present and
visible via query_graph.

Add the six CROSS_* types (CROSS_HTTP_CALLS, CROSS_ASYNC_CALLS,
CROSS_CHANNEL, CROSS_GRPC_CALLS, CROSS_GRAPHQL_CALLS, CROSS_TRPC_CALLS)
to the cross_service default list — exactly the set pass_cross_repo.c
emits and cross_repo_delete_edges() cleans up. The count is now computed
from the array size (sizeof) so it can't drift; the dead MCP_N_DEFAULTS_4
constant is removed. Tool description updated to match.

Adds integ_mcp_trace_path_cross_service: seeds a CROSS_HTTP_CALLS edge
between two indexed functions with no CALLS relationship and asserts
cross_service surfaces the hop while calls mode does not.

Signed-off-by: kirilklein <kikl@di.ku.dk>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:05:18 +02:00
Alex 8ba286324c fix(pipeline): persist call-site line on CALLS edges (#508)
* fix(pipeline): persist call-site line on CALLS edges

Persist CBMCall.start_line as a "line" property on CALLS edges in both resolution paths (finalize_and_emit + calls_emit_edge), so call-site lines are queryable instead of needing grep. Scoped to CALLS only: route/config edge props feed full-only predump passes the incremental path does not re-run, so changing them desyncs full vs incremental indexing. Verified: full suite matches unpatched main; line present on CALLS both paths.

Fixes #503

Signed-off-by: alexisperinger-ux <alexis.peringer@iss-stoxx.com>

* test(pipeline): assert CALLS edge carries call-site line

Indexes a Go fixture where Helper() is called on line 8 and asserts the Main->Helper CALLS edge persists a line property (line:8) in its JSON props.

Refs #503.

Signed-off-by: alexisperinger-ux <alexis.peringer@iss-stoxx.com>

---------

Signed-off-by: alexisperinger-ux <alexis.peringer@iss-stoxx.com>
2026-06-22 22:05:09 +02:00
Nan 550a2cc65c fix(ui): render graph labels without remote font fetches (#496)
Signed-off-by: Nan <630971265@qq.com>
2026-06-22 22:04:54 +02:00
Shaurya 11009f2d94 fix(discover): honor .git/info/exclude during file discovery (#493)
* fix(discover): honor .git/info/exclude during file discovery

index_repository only loaded .gitignore when building the exclusion
matcher. .git/info/exclude — the per-clone exclude file that Git treats
as authoritative — was never read, so paths excluded only there were
walked in full. On repos with Sandcastle worktrees this caused the
indexer to traverse 661K files / 170 GB and OOM (issue #489).

Add cbm_gitignore_merge() to append patterns from one matcher into
another. In cbm_discover_ex, after loading .gitignore, also load
.git/info/exclude and merge its patterns in. If only the exclude file
exists, use it directly. No downstream call paths change.

Add three unit tests for cbm_gitignore_merge and two integration tests
that reproduce the issue scenario (exclude-only and exclude-stacked-
with-gitignore).

Signed-off-by: ShauryaaSharma <shauryasofficial27@gmail.com>

* style(discover): fix clang-format alignment in cbm_gitignore_merge

Signed-off-by: ShauryaaSharma <shauryasofficial27@gmail.com>

---------

Signed-off-by: ShauryaaSharma <shauryasofficial27@gmail.com>
2026-06-22 22:04:42 +02:00
Matt Van Horn f5ea36cc64 fix: Reject filesystem paths in HTTP_CALLS route extraction so client routes can join server routes (#485)
Signed-off-by: mvanhorn <mvanhorn@gmail.com>
2026-06-22 22:04:31 +02:00
c8f9f94e08 fix(search): ignore empty-string label in search_graph name_pattern path (#483)
* fix(search): ignore empty-string label in search_graph name_pattern path

search_where_basic guarded the label filter on NULL only (`if (params->label)`),
so a search passing label="" appended a literal `n.label = ''` clause that
matches no node — silently returning zero results. The BM25 query path already
treats an empty label as absent, so name_pattern/qn_pattern searches and BM25
searches behaved inconsistently for the same `"label":""` payload (issue #481):
agents fell back to grep because structural class/service discovery returned
nothing.

Guard the clause on `params->label && params->label[0]` so an empty-string
label is a no-op, identical to an omitted label and to the BM25 path.

Adds store_search_empty_label_ignored: name_pattern + label="" returns the same
match as name_pattern alone, while a non-empty label still filters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* test(search): address QA round 1 — cover qn_pattern + empty label

QA round 1 noted store_search_empty_label_ignored asserted only the
name_pattern path though the comment referenced qn_pattern too. Add a
qn_pattern + label="" assertion so a future change that special-cased the
qn_pattern branch can't silently reintroduce the empty-label bug. Both paths
share search_where_basic, so this locks in the shared guard.

Test-only; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

---------

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:04:19 +02:00
cd660270cb fix(resolver): disambiguate qualified cross-file calls by full namespace tail (#479)
* fix(resolver): disambiguate qualified cross-file calls by full namespace tail

cbm_registry_resolve reduced a package/namespace-qualified callee
(Foo::Bar::sub) to its bare simple name before the multi-candidate name
lookup. When that bare name was defined in several packages, the bare-name
scorer (best_by_import_distance) picked one winner deterministically and
routed every caller to it — collapsing distinct packages' same-named subs
onto a single definition and orphaning the rest. Impact is heaviest for Perl,
which has no cross-file LSP and relies entirely on this generic resolver, but
the defect applies to any "::"- or "."-qualified callee.

Add qualified_suffix_match: when a callee is qualified and its bare name has
multiple candidates, pick the sole candidate whose qualified tail matches at a
segment boundary (normalizing "::" to "."). Returns the unique match with a new
high-confidence "qualified_suffix" strategy; falls through to the existing
bare-name scoring when zero or several candidates match. Language agnostic —
bare callees contain no separator and are unaffected.

Adds resolve_qualified_disambiguates_same_name covering per-package routing of
a name defined in three packages, plus the bare-call (no-disambiguation) case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* test(resolver): address QA round 1 (qualified-suffix edge coverage)

Harden resolve_qualified_disambiguates_same_name and add a new test per the
QA round 1 advisory notes, guarding the fall-through paths against silent
regression:
- dotted callee form (App.Beta.save) resolves via qualified_suffix
- qualified callee matching no candidate tail falls through (not qualified_suffix)
- resolve_qualified_ambiguous_tail_falls_through: two candidates sharing the
  same qualified tail are ambiguous → fall through to bare-name scoring

Test-only; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

---------

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:04:06 +02:00
92b4506cd5 fix(perl): eliminate false-positive Perl CALLS edges (builtins, framework method calls, config strings) (#477)
* fix(perl): stop extracting config strings as call targets

The Perl branch of extract_scripting_callee blindly returned the text of
child(0) of every call node. In config-heavy Perl (.cgi/.pl with embedded
log4perl-style config), tree-sitter-perl misparses dotted config tokens
(e.g. "log4perl.appender.File.utf8") into call-shaped nodes, and that
dotted string was emitted as a callee_name, later matched by the generic
short-name resolver to unrelated project subs.

Now the Perl branch pulls the real name token (method/function field, else
child(0)) and validates it as a bare Perl sub/method identifier via
perl_is_identifier_callee: must start with a letter or '_' and contain only
[A-Za-z0-9_:] (allowing the '::' package separator). Any '.', whitespace,
quote, or '/' disqualifies it and NULL is returned so no CALLS edge forms.
Gated to CBM_LANG_PERL; other languages are untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* fix(resolver): don't match Perl builtins to project subs

Perl builtins (push/shift/keys/sprintf/...) carry no language or call-kind
awareness through the generic name-matcher in cbm_registry_resolve. When a
project defines a sub whose name collides with a builtin, an invocation of
the builtin was wired to that sub by same-module / suffix matching - a
false-positive CALLS edge.

Adds cbm_perl_is_builtin (curated, sorted bsearch set of 94 perlfunc core
builtins) and applies it in both call-resolution passes (sequential
resolve_single_call and parallel resolve_file_calls), gated on the file
language == CBM_LANG_PERL and only AFTER LSP resolution has declined, so a
genuine LSP-resolved call is never suppressed. The file language is threaded
into both resolvers via a new trailing CBMLanguage parameter; every other
language reaches cbm_registry_resolve unchanged (byte-identical behavior).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* fix(resolver): suppress generic short-name matching for Perl method calls

A Perl method call ($obj->m / Class->m) carries no receiver type at the
structural tier, so the generic short-name matcher in cbm_registry_resolve
would wire $dbh->commit / $cgi->param / $logger->log to any project sub
sharing the bare method name - the dominant source of false-positive CALLS
edges in CPAN/framework-heavy Perl. Resolving such a call correctly is the
LSP's job, not the bare-name matcher's.

Adds a CBMCall.is_method flag (zero-init false, so all other languages and
existing call sites are unaffected). method_call_expression is added to the
Perl call node set and handle_calls sets is_method=true only for that node
type when the file language is Perl. Both call-resolution passes then skip
generic resolution for Perl method calls (combined with the builtin guard
from the prior commit). Genuine intra-project function calls (non-method,
non-builtin) still resolve as before. LSP-resolved method calls are
unaffected because the guard runs only after LSP resolution declines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* test(perl): cover call-graph noise fixes (builtins, methods, config strings)

Hermetic tests for the three Perl call-graph noise fixes:

test_extraction.c (extraction tier):
  - config string is never emitted as a callee; genuine call still extracted
  - builtin calls (push/keys) extracted but never flagged is_method
  - arrow/method calls ($self->commit / $dbh->commit) set is_method=true,
    while the genuine function call (helper) does not
  - a JS method call never sets is_method (flag is Perl-only — other
    languages unaffected)

test_registry.c (resolver tier):
  - cbm_perl_is_builtin recognizes core builtins (incl. first/last of the
    sorted set) and rejects project subs, case variants, empty, and NULL

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* fix(perl): address QA round 1

Round 1 (Claude panel + DO DeepSeek) findings:
- Rework the Perl noise guard so it suppresses only WEAK generic matches
  (suffix_match/unique_name) and KEEPS high-confidence same_module/import_map.
  The prior guard ran before cbm_registry_resolve and dropped genuine same-file
  calls to builtin-named subs (e.g. a project sub log/index/open called as a
  bare function) that pre-PR resolved via same_module. Extracted the decision
  into pure, unit-tested cbm_perl_suppress_generic_match() shared by the
  sequential (pass_calls.c) and parallel (pass_parallel.c) resolvers; corrected
  the inaccurate comments (Perl has no LSP/textual stage before the guard).
- Tighten perl_is_identifier_callee to require '::' pairs (reject a lone ':',
  ':::', or trailing '::').
- Add resolver-contract tests covering weak-match suppression, same_module/
  import_map retention, genuine-call survival, non-Perl no-op, and NULL strategy.

Verified on a real Perl monorepo: .cgi builtin/CPAN/config-string noise stays
eliminated while same_module edges to builtin-named subs are recovered.

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* fix(perl): address QA round 2

Round 2 (Claude panel) caught a regression introduced by the round-1 refactor:
cbm_perl_suppress_generic_match whitelisted only the exact strategies
"same_module" and "import_map", but resolve_import_map can also return
"import_map_suffix" (confidence 0.85 — a genuine import-based resolution, not a
weak short-name guess). A '::'-qualified Perl builtin/method call resolved via
the import-suffix fallback was therefore dropped, contradicting the helper's
documented contract and partially missing acceptance criterion (d).

Add import_map_suffix to the kept (high-confidence) set so only the weak
short-name strategies (suffix_match / unique_name) are suppressed; update the
doc comment and add a unit-test case asserting import_map_suffix is retained.

Deferred as advisory (non-blocking, noted on the PR): a hypothetical leading-'::'
(main:: shorthand) under-extraction in perl_is_identifier_callee, and a
colon-edge-case coverage gap (logic correct by inspection).

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

---------

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:03:53 +02:00
b63aa9dc39 fix(cli): copy and sign the built binary on install instead of re-signing the stale target (#473)
* fix(cli): copy and sign the built binary on install instead of re-signing the stale target

`install` (especially with --force) only ad-hoc-signed whatever already sat at
~/.local/bin/codebase-memory-mcp and never copied the binary the operator ran
install from. An operator who built from source and ran `install -y --force`
was silently left with the OLD binary at the target — running stale code while
believing they had upgraded (#472).

Install now mirrors `update`: detect the running binary, copy it to the
canonical target (skipping the copy when we already ARE the target, which would
otherwise truncate the running file), make it executable, ad-hoc sign the
TARGET, and point all agent configs at the target. A different pre-existing
binary is only replaced with --force or interactive confirmation; --dry-run
reports the intended swap without touching anything.

The copy+sign also resolves the secondary report: a hand-copied clang build is
linker-signed (flags=0x20002) and gets Killed:9 when spawned by an MCP host —
re-signing ad-hoc (flags=0x2) makes it launchable, which install now does for
the placed binary.

Adds cbm_copy_binary_to_target() (with a same-file guard) plus regression tests
covering the binary swap and the self-copy guard.

Closes #472

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* fix(cli): address QA round 1

Extend cli_install_same_file_guard_issue472 with a symlink case: two distinct
path strings resolving to the same inode (which a non-canonical
cbm_detect_self_path vs the hardcoded target can produce) must also be detected
as same-file and skipped, not truncated. POSIX-only (#ifndef _WIN32).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

* test(cli): guard install copy exec-bit assertion to POSIX

cli_install_copies_binary_to_target_issue472 asserted the executable bit on the
copied target, but cbm_copy_binary_to_target only chmods on POSIX (the exec bit
is not meaningful on Windows, and MinGW stat() derives it from the file
extension). Wrap the stat/S_IXUSR assertion in #ifndef _WIN32 so test-windows
passes; the content and creation assertions still run on all platforms.

Refs #472

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>

---------

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Shane McCarron <shane.mccarron@corvexconnect.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:03:37 +02:00
Martin Vogel 53ebeb4cf1 docs(security): coordinated-disclosure process + realistic reporting policy
Add docs/SECURITY-DISCLOSURE.md describing how vulnerability reports are
handled end to end (private fix, cross-platform validation, reporter
verification, patched release, then a GitHub Security Advisory with a CVE
and credit).

Update SECURITY.md: add GitHub private vulnerability reporting as the
preferred channel, replace the over-tight 48h/7-day commitment with honest
best-effort targets for a solo-maintained project, add a safe-harbor
statement, and refresh the stale supported-versions table (0.5.x -> 0.8.x).

Signed-off-by: Martin Vogel <martin.vogel@datadice.io>
2026-06-22 09:19:51 +02:00
win4randClaude Opus 4.8 be3e038aac fix(pipeline): preserve inbound cross-file edges on incremental reindex
Incremental re-indexing dropped inbound cross-file edges (CALLS, USAGE,
...) whose source lived in an unchanged file. cbm_pipeline_run_incremental
purges a changed file's nodes -- the cascade deletes every edge referencing
them, including inbound edges from unchanged callers -- but only re-resolves
the changed files, so those edges were never regenerated. The graph
silently diverged from a full reindex on every edit (and degraded under the
auto-sync watcher); e.g. a cross-file `Serve -> Help` CALLS edge vanished
when only the callee's file was edited.

Fix (two parts):

- Snapshot inbound cross-file edges into changed files before the purge,
  keyed by endpoint qualified_name (stable across re-parse), and re-link
  them after re-resolution + post-passes. insert_edge dedups; a target
  whose qn no longer exists (deleted/renamed) is dropped, matching
  full-reindex semantics. Edge types recomputed by post-passes that do not
  run incrementally (SIMILAR_TO, SEMANTICALLY_RELATED, FILE_CHANGES_WITH,
  DATA_FLOWS) are skipped so a stale snapshot cannot introduce an edge a
  full reindex would not produce.

- registry_visitor now seeds the resolver with only the definition labels
  the full index seeds -- {Function, Method, Class, Interface, Variable,
  Field} (mirrors pass_definitions.c) -- instead of also seeding container
  nodes (File/Module/Folder). Previously a type usage like `Word` resolved
  to the same-named Module node instead of the Class node, diverging from
  full.

Verified: an incremental reindex now produces a byte-identical structural
edge set to a fresh full reindex across 8 edit shapes (comment, body,
caller-file, two-file, add-function, model, view, 4-round iterative). Adds
a reproduce-first regression test that fails before this change (the
cross-file Serve -> Help edge is dropped) and passes after.

Signed-off-by: win4r <win4r@outlook.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:31:02 -07:00
pcristin 1519f8619a feat(git): add worktree branch roots
Signed-off-by: pcristin <xxxokzxxx@protonmail.com>
2026-06-19 13:56:17 +02:00
Andrius SkerlaandClaude Opus 4.8 74d15a6064 fix(pipeline): prevent stack-buffer-overflow in append_args_json
A call carrying enough long arguments drove append_args_json()'s running
position past the fixed CBM_SZ_2K `props` stack buffer in
emit_normal_calls_edge(): format_call_arg() returns snprintf's *untruncated*
length, so `pos += (size_t)n` could exceed `bufsize`, after which the
trailing `buf[pos] = '\0'` (and `buf[pos++] = ']'`) wrote out of bounds. The
stack canary caught it as SIGABRT, so full-repo indexing of large TypeScript
codebases crashed the server in the parallel resolve pass
(emit_service_edge -> emit_normal_calls_edge -> finalize_and_emit ->
append_args_json). Confirmed with AddressSanitizer:
stack-buffer-overflow WRITE at pass_parallel.c:1124, 'props' (2048 B).

Fix: when an argument does not fully fit, roll back to before its separator
and stop appending (atomic field, matching append_json_string's behaviour),
so `pos` can never advance past the buffer.

Add regression test parallel_args_json_no_overflow: indexes a fixture whose
single call carries 60 long string args (args JSON well past 2 KB); under the
ASan test build it aborts without this fix and passes with it.

Signed-off-by: Andrius Skerla <1492322+rainder@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:26:25 +03:00
Kris Kersey 382dc24d17 fix(mcp): prefer definitions and report ambiguity in name resolution
trace_path resolved a function_name from the first row of an unordered name
query with no ambiguity check, so a same-named entity (e.g. a shell script's
main()) could silently shadow the intended C main(). get_code_snippet
reported "ambiguous" for a short name even when one match was the obvious
definition (the .c body vs a .h declaration).

Fix: add a deterministic resolution ranking — a callable label outranks a
module, then the larger definition by line span wins, preferring a real
definition without hardcoding file extensions — and a picker that flags a
genuine tie. trace_path now traces the preferred node and returns the
existing ambiguous-suggestions response on a true tie instead of silently
taking nodes[0]; get_code_snippet resolves directly to the preferred match,
reporting ambiguity only for real ties. Adds regression tests
tool_trace_call_path_ambiguous and tool_trace_call_path_prefers_definition.

Signed-off-by: Kris Kersey <kris@kerseyfabrications.com>
2026-06-14 15:10:43 +00:00
Kris Kersey 53501b0d40 fix(mcp): honor detect_changes 'since' parameter
detect_changes advertised a `since` parameter in its inputSchema but the
handler never read it — it always diffed against base_branch (default
"main"), so detect_changes(since="HEAD~10") silently returned the wrong or
empty result when HEAD was on the default branch.

Fix: read `since` and, when present, route it through base_branch so the
existing shell-arg validation (cbm_validate_shell_arg) and the
`<base>...HEAD` diff apply unchanged; `since` takes precedence over
base_branch. Also narrows the schema description — the prior "date" form
(e.g. 2026-01-01) is not a revision and never worked through this path — and
documents the inherited three-dot semantics. Adds regression tests
tool_detect_changes_since and tool_detect_changes_since_precedence.

Refs #371

Signed-off-by: Kris Kersey <kris@kerseyfabrications.com>
2026-06-14 15:08:16 +00:00
Thomas Dyar bfd53f33ec fix(discover): deploy/ directories indexed as duplicate source in full mode
deploy/ and deployed/ build artifact directories were in FAST_SKIP_DIRS,
which only applies in fast/moderate indexing modes. In the default full
mode they were indexed alongside source files, creating duplicate
Class/Method rows with stale data. Any MATCH (c:Class {name:'Foo'})
without a file_path filter returned multiple rows — one from source,
one from the build copy.

Move deploy and deployed to ALWAYS_SKIP_DIRS so they are excluded in all
indexing modes.

Regression test: discover_deploy_excluded_full_mode in test_discover.c.

Signed-off-by: Thomas Dyar <tdyar@intersystems.com>
2026-06-12 14:55:46 -04:00
Thomas Dyar c43fc8df0e fix(cypher): label-filtered edge traversal silently truncates at 10 results
MATCH (c:Class)-[:DEFINES_METHOD]->(m:Method) returned at most 10 results
for any class, regardless of how many methods it actually has.

Root cause: bind_cap was set to scan_count (the number of nodes matched in
the initial pattern — typically 1 when querying a single class by name).
max_new = bind_cap * 10 = 10, so the edge expansion loop exited after
collecting 10 results. No error, no warning, no truncation indicator.

This is language-agnostic: any class with more than 10 methods in any
language was silently truncated. The fix is two characters:
  bind_cap = scan_count > max_rows ? scan_count : max_rows

Regression test: a Python class with 15 methods must return all 15 via
MATCH (c:Class)-[:DEFINES_METHOD]->(m:Method) with label filtering.

Signed-off-by: Thomas Dyar <tdyar@intersystems.com>
2026-06-12 14:39:38 -04:00
Martin Vogel e599df1d56 Resolve npm through the shell for Windows compatibility
The UI license enumeration invoked npm by bare name via subprocess,
which fails on Windows (npm is npm.cmd there — WinError 2 in the
Windows UI-archive packaging step). Resolve it through the shell, which
handles platform shims everywhere; the command is a constant string.

Signed-off-by: Martin Vogel <martin.vogel@datadice.io>
2026-06-12 18:16:43 +02:00
Martin Vogel 4630af6437 Cancel superseded CI runs on new pushes
A new push to a PR (or ref) now cancels the in-progress validation for
the previous commit instead of letting obsolete pipelines run to
completion. Drafts deliberately keep the full gate set — multi-platform
feedback matters most during iteration.

Signed-off-by: Martin Vogel <martin.vogel@datadice.io>
2026-06-12 18:16:43 +02:00
Martin Vogel 3d9917f325 Add community files and strengthen the contribution terms
Adds the Contributor Covenant 2.1 code of conduct (canonical text,
byte-verified against upstream) and reproduction-first issue forms that
ask for shareable reproductions — dummy snippets or public OSS repos —
plus logs and exact commands, never proprietary code. CONTRIBUTING.md
now states explicitly that a Signed-off-by line certifies the DCO in
full for that contribution, with GitHub ToS inbound=outbound noted as
the independent backstop. The VirusTotal badge now points at the latest
release instead of a pinned historical scan.

Signed-off-by: Martin Vogel <martin.vogel@datadice.io>
2026-06-12 16:58:55 +02:00
Martin Vogel b129aabb27 Exempt the provenance-audit tool from the ScanCode pass
It names license identifiers in its verdict maps by design — the same
documented false-positive category as the other license tooling.

Signed-off-by: Martin Vogel <martin.vogel@datadice.io>
2026-06-12 15:57:08 +02:00
Martin Vogel 578ac55664 Add lint and the test suite to PR validation
PRs now run security gates, lint, and the full test suite (perf
assertions excluded — they stay in dry runs and releases where a
timing-flaky red cannot block a merge). Builds, smoke and soak remain
maintainer-driven.

Signed-off-by: Martin Vogel <martin.vogel@datadice.io>
2026-06-12 15:57:08 +02:00
Martin Vogel eb2e8d5963 Limit PR validation to the security gates
Pull requests run the security island only — static audit, license
gates, and the CodeQL gate plus the ci-ok summary; the full dry-run
chain (lint/test/build/smoke) stays maintainer-driven via
workflow_dispatch. The CodeQL gate now resolves the PR head SHA instead
of the synthetic merge commit so it can find the analysis run.

Signed-off-by: Martin Vogel <martin.vogel@datadice.io>
2026-06-12 15:57:08 +02:00
Martin Vogel 54c0b87a1d Run the full validation pipeline on pull requests
Pull requests now execute the dry-run chain (security including the
license gates, lint, full test suite, all build legs, smoke) plus a
single ci-ok summary job that fails unless every stage succeeded.
Branch protection requires dco + ci-ok, so nothing unverified can merge
— including from admins. CodeQL also runs on pull requests so the
security island completes on PR commits.

Signed-off-by: Martin Vogel <martin.vogel@datadice.io>
2026-06-12 15:57:08 +02:00
Martin Vogel 2a0ec321c3 Adopt the Developer Certificate of Origin for all commits
Every commit must now carry a Signed-off-by trailer matching its
author, certifying the right to submit the change under the project's
MIT license (DCO 1.1, the Linux kernel mechanism). Enforcement is
strict at three layers: a commit-msg hook rejects unsigned commits
locally (scripts/install-git-hooks.sh), and the new DCO workflow
rejects every push and pull request containing one. Merge commits and
bot authors are exempt, matching standard DCO checks.

Signed-off-by: Martin Vogel <martin.vogel@datadice.io>
2026-06-12 14:31:55 +02:00
Martin Vogel 1b383e376a Run the gate self-test and provenance audit in the security workflow
The license gate now proves itself before running: a --selftest mode
plants an unlicensed vendored file and asserts the structural layer
detects it, so a silently broken gate can no longer pass. The
byte-identity provenance audit gains exit-code semantics (any verdict
outside the accepted set fails) and runs as a blocking step in the
security workflow for both dry runs and releases.
2026-06-12 13:24:07 +02:00
Martin Vogel 4451ca0800 Align every vendored grammar license byte-for-byte with its upstream
A byte-identity audit of all vendored license files against their
upstream repositories found 113 copies differing only by a missing
trailing newline (vendoring artifact) — replaced with the exact
upstream bytes. Five needed real corrections: fennel carried an
unfilled MIT template although its upstream is CC0-1.0 at the pinned
commit; ron now carries the repo's LICENSE-MIT (dual MIT/Apache
upstream); python, wit, and verilog were refreshed to current upstream
bytes. The manifest's first-party table was also corrected: six of the
twelve grammars are self-maintained forks whose retained upstream
licenses are now byte-verified (assembly's upstream has been deleted
from GitHub; the retained MIT copy is the surviving grant). The audit
tool is kept as scripts/audit-license-provenance.py for future sweeps.
2026-06-12 13:16:52 +02:00
Martin Vogel 952eea9f1f Extend the license gate to ancestry coverage and the UI dependency tree
Structural layer: drop the hardcoded component list — any directory
containing vendored source or data files must now be covered by a
license file in itself or an ancestor within the vendored tree, so
newly vendored code without a license fails immediately. New third
layer: the graph-UI npm production tree is resolved (postinstall
scripts disabled) and every bundled package must be on the policy
allow-list; unknown licenses fail. Reclassifies @tailwindcss/vite as a
devDependency — it is a build plugin and was inflating the production
tree with native tooling.
2026-06-12 13:03:12 +02:00
Martin Vogel c72d85659f Wire jscpd structural-clone pass into LSP originality guard
The guard now runs a token-level clone detector (jscpd, with an npx
fallback) comparing internal/cbm/lsp/ C sources against clangd C++ — the
one reference close enough to C to tokenize alike — staging both trees as
a common format and reporting only clone pairs that span them. Catches
structural ports that rewrote strings/identifiers, which the verbatim
string/comment scan misses. clangd verified: no structural clones.
2026-06-12 12:58:38 +02:00
Martin Vogel e93d86dd9f Add LSP originality guard (scripts/check-lsp-originality.sh)
Scans internal/cbm/lsp/ for verbatim string/comment overlap against the
reference language servers (pyright, gopls, tsserver, Roslyn, clangd,
Eclipse JDT LS, kotlin-language-server, rust-analyzer, phpactor). Run
before committing new LSP work; add a new language's upstream reference to
the REFS manifest when implementing it. All 9 current languages verified
with no verbatim overlap.
2026-06-12 12:22:53 +02:00
Martin Vogel f714bf3de1 Extend the notices bundle to the UI dependencies and the Glama image
The with-ui archives now append the per-package license texts of the
frontend bundle's production dependency tree to THIRD_PARTY_NOTICES.md
(platform-specific native build tooling is listed but excluded — its
code never reaches the browser bundle). The Glama check image carries
LICENSE and the notices file alongside the binary. Also renames a
TypeScript LSP test to describe its generic ODM-interface fixture.
2026-06-12 10:51:11 +02:00
Martin Vogel 324b6fc547 Pin the registry sync to the dispatched release version
The server.json sync step read its version from pkg/npm/package.json,
which can lag behind the release being published in the dispatched
commit. Use the workflow's version input instead — it is authoritative
for the run.
2026-06-12 10:25:27 +02:00
Martin Vogel 92f38b63b3 Sync packaging metadata to v0.8.1
Bump the npm/PyPI/server.json version fields and the PyPI shim's
version fallback, and drop the stale 0.6.x Winget manifest folders
whose installer URLs no longer resolve.
2026-06-12 10:07:21 +02:00
Martin Vogel eb748cb532 Bump the go-install shim to v0.8.1 2026-06-12 09:59:51 +02:00
Martin Vogel 83eb998c2e Point package-manager manifests at v0.8.1
Homebrew formula, AUR PKGBUILD/.SRCINFO, Scoop, Winget (new 0.8.1
manifest folder), and Chocolatey all move from 0.6.1 to the current
release with refreshed checksums.
2026-06-12 09:57:59 +02:00
Martin Vogel f0c9be19c5 Keep the publish chain alive when the soak stage is skipped
With soak_level=none the soak job is skipped; release-draft already
handles that with an explicit condition, but verify and the publish
jobs relied on the default success() check, which treats a skipped
ancestor as non-success and silently skips the whole publish chain.
Give each downstream job an explicit condition that only blocks on
real failures or cancellations.
v0.8.1
2026-06-12 07:05:55 +02:00
Martin Vogel 4fa71d9235 Exempt the graph-UI server from the raw-network-call audit
The graph-UI HTTP server now owns its listening socket directly, so the
Layer 1 static audit's network-call scan would flag its socket()/bind()
calls. Exempt src/ui/httpd.c — it binds 127.0.0.1 only and is audited
separately by security-ui.sh — while still forbidding raw network calls
in every other source file.
2026-06-12 02:28:05 +02:00
Martin Vogel 487f3f945b Bundle third-party notices into release archives; extend release tooling
Release archives now carry THIRD_PARTY_NOTICES.md, generated by
scripts/gen-third-party-notices.sh from THIRD_PARTY.md, the grammar
manifest, and the per-component license texts; the Homebrew formula
and AUR PKGBUILD install it alongside the binary. The SBOM gains
per-component license metadata, corrected versions, and the previously
missing vendored libraries. The security workflow gains a
vendored-license scan with an explicit allow-list policy, and the
release workflow exposes a skip_perf input for releases that do not
touch pipeline logic.
2026-06-12 02:17:39 +02:00
Martin Vogel 3ee9d5d112 Drop the nim grammar and refresh language counts
Removes the heaviest vendored grammar (66 MB) along with its language
wiring and test fixtures; the supported-language count moves to 158
across README, site, and manifest. The grammar manifest additionally
records the canonical-source decisions for the five
registry-disagreement grammars and the per-directory license files
restored earlier this week.
2026-06-12 02:17:39 +02:00
Martin Vogel 630bd40a90 Replace the graph-UI HTTP server with a first-party implementation
The web server behind the graph UI is now a purpose-built in-house
module (src/ui/httpd.c): localhost-only listener, strict HTTP/1.1
parsing with fixed request caps, a per-connection receive deadline,
and Connection: close semantics. http_server.c keeps the routing and
handlers, rewritten against the new transport API; the public server
API and main.c are unchanged. The previously vendored third-party
server is removed entirely.

Comes with a new 28-test transport + routing suite (tests/test_httpd.c)
covering parsing edge cases (strict CRLF, Content-Length limits, raw
path matching, percent-decode rules) and live-socket behavior (CORS
policy, RPC dispatch, receive deadline, clean shutdown). The security
audit scripts now check the new file layout and treat any network call
in vendored code as a failure.
2026-06-12 02:17:25 +02:00