Commit Graph
1226 Commits
Author SHA1 Message Date
Zadak 4efa22cd95 fix(graph-ui): navigate Windows drive breadcrumbs to real paths
The index file picker built breadcrumb targets as '/' + segments, so on a Windows drive path (C:/Users/rap) clicking a crumb browsed to '/C:/...', which the backend rejected as 'not a directory'. Only the '.. (up)' button worked.

Build drive-aware crumb targets (C:/, C:/Users) and drop the bogus unified '/' root crumb on Windows drive paths; POSIX behavior is unchanged. Add a regression test for Windows breadcrumb navigation and cleanup() for test isolation.

Signed-off-by: Zadak <rarepops@protonmail.com>
2026-07-04 09:39:28 +02:00
Martin Vogel 9871e7e086 Merge pull request #827 from DeusData/feat/index-supervisor-subprocess
feat(index): crash/hang supervisor — quarantine the culprit file, index the rest
2026-07-04 05:12:58 +02:00
Martin Vogel 836f90dec7 Merge pull request #817 from DeusData/distill/738-watcher-prune
feat(watcher): prune projects whose root stays missing (ENOENT-only, grace window)
2026-07-04 04:58:18 +02:00
Martin Vogel 4cd8d3e846 Merge pull request #810 from DeusData/distill/536-cross-repo
fix(cross-repo): normalize URLs, match templates, run both directions
2026-07-04 04:37:38 +02:00
Martin Vogel 90f59c5107 Merge pull request #809 from DeusData/distill/py-eval-guards
fix(py-lsp): memoize + guard expression evaluation (exponential re-eval hang)
2026-07-04 04:30:48 +02:00
Martin Vogel dac31d24e7 Merge pull request #815 from DeusData/distill/699-receiver-recursion
fix(extract): receiver-aware self-recursion detection
2026-07-04 04:25:18 +02:00
Martin Vogel 2e7c64b30c Merge pull request #811 from DeusData/distill/793-walk-exclusions
fix(pipeline): honor discovery exclusions in pkgmap/path-alias/envscan walks
2026-07-04 04:03:53 +02:00
Martin Vogel bfd9b896b2 Merge pull request #816 from DeusData/distill/549-ui-index-errors
fix(graph-ui): surface indexing-job failures instead of silently completing
2026-07-04 04:00:07 +02:00
Martin Vogel 010ac9b266 Merge pull request #812 from DeusData/distill/cli-hygiene
fix(cli): overflow-safe ZIP bounds check; env-bash shebangs
2026-07-04 03:46:45 +02:00
Martin Vogel bb2510eac3 Merge pull request #806 from DeusData/distill/560-architecture-overview
fix(mcp): get_architecture overview subset, aspects enum + validation (+ .cbmignore how-to)
2026-07-04 03:44:29 +02:00
Martin Vogel 9fbdac25eb Merge pull request #813 from DeusData/distill/702-installer-utf8
fix(windows): unswallow installer failures, quote CreateProcessW args, document MotW
2026-07-04 03:35:07 +02:00
Martin Vogel 5f6461e902 Merge pull request #818 from DeusData/distill/684-jvm-callgraph
fix(jvm): resolve mixed Java/Kotlin callgraph edges via declared packages
2026-07-04 03:13:37 +02:00
Martin Vogel 85349a8833 Merge pull request #826 from DeusData/fix/smoke-invariants-triage
fix(mcp): exit promptly on stdin EOF over a FIFO; smoke tools-list follows pagination
2026-07-04 03:09:12 +02:00
Martin Vogel 541ff613af Merge pull request #814 from DeusData/distill/770-imports-dedup
fix(graph): IMPORTS edges keyed by local_name across buffer, store and dump
2026-07-04 03:08:07 +02:00
Martin Vogel 6e59736711 lint: clang-format compliance, fork allowlist entry, reduce bin scope
- format the include trailing comments and wrap the over-long reap log line
  (clang-format-20 violations flagged by lint / lint)
- allowlist subprocess.c's fork with justification (security-static): the
  supervisor primitive forks and execs immediately; no code runs in the
  forked image
- move bin into the fork-child block alongside argv (cppcheck variableScope
  consistency)

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 03:01:12 +02:00
Martin Vogel 416e4f2315 Merge pull request #807 from DeusData/distill/543-cbmignore-neg
fix(discover): .cbmignore negation can un-skip built-in dirs (safety core excluded)
2026-07-04 02:35:35 +02:00
Martin Vogel 39a091777a ci: re-run PR workflow (ci-ok context missing after cancelled run)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 01:31:12 +02:00
Martin Vogel c5aef83cc4 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 01:08:09 +02:00
Martin Vogel eb2e0d5d90 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 01:08:08 +02:00
Martin Vogel b6bd28c954 lint: reduce argv scope to the fork child in cbm_run_posix (cppcheck variableScope)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 01:07:51 +02:00
Martin Vogel 185451ad58 Merge pull request #819 from DeusData/distill/756-search-prefilter
perf(mcp): prefilter scoped search_code filelist by path_filter
2026-07-04 00:54:46 +02:00
Martin Vogel c3d9a6548a ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 00:38:00 +02:00
Martin Vogel 6ed3dc0749 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-04 00:19:07 +02:00
Martin Vogel 6df5804fc0 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 23:38:34 +02:00
Martin Vogel 56f28ec5d0 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 23:38:32 +02:00
Martin Vogel 7b152097e6 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 23:31:20 +02:00
Martin Vogel fb49470944 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 23:31:19 +02:00
Martin Vogel d413654c49 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 23:31:17 +02:00
Martin Vogel 138bd867f6 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 23:31:15 +02:00
Martin Vogel c2744264d9 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 23:31:14 +02:00
Martin Vogel 76e87f4086 ci: re-trigger CodeQL gate (gate lookup fixed on main via #820)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 23:31:12 +02:00
Martin Vogel 3ee05c183e Merge pull request #820 from SS-42/fix/codeql-gate-head-sha
Fix CodeQL gate lookup for busy queues
2026-07-03 23:30:11 +02:00
Martin Vogel ef04f6d28d ci: re-trigger CodeQL gate (poll-timeout flake, no code change)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 22:00:46 +02:00
Martin Vogel 2758cee339 ci: re-trigger CodeQL gate (poll-timeout flake, no code change)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 22:00:44 +02:00
SS-42 14828822bf fix(ci): query CodeQL runs by head SHA
Signed-off-by: SS-42 <noreply@incogni.to>
2026-07-03 22:35:31 +03:00
Martin Vogel 78dab4cc18 docs: use scanner-neutral URL example in cross-repo comment
The hardcoded-URL security audit flags http:// literals in src/; the comment
only illustrates the shape of stored url_path values.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 21:07:27 +02:00
Martin Vogelandyutianyu111602-glitch b4070c5d44 perf(mcp): prefilter scoped search_code filelist by path_filter
Apply path_filter while writing the scoped filelist instead of only after
grep, so large indexed projects do not scan files whose hits the post-grep
filter would discard anyway. The prefilter runs the SAME compiled regex via
the same cbm_regexec call against the same root-relative path (Windows
separators normalized first) as the post-grep filter in collect_grep_matches,
which is kept as belt-and-suspenders — results-preserving by construction.

Guard the edge the prefilter introduces: a path_filter matching zero indexed
files now short-circuits with the normal empty result instead of invoking
xargs on an empty filelist (GNU execs grep once with no operands, BSD skips).

Tests: positive guard (filter matching the hit's file still returns it,
out-of-filter file excluded) and zero-match guard (clean empty response,
grep subprocess skipped). Both green pre-change too — perf-only change.

Distilled from #756 rebased onto the post-#751 filelist writer.

Co-authored-by: yutianyu111602-glitch <yutianyu111602@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 21:04:20 +02:00
Martin Vogelandnguyentamdat 5ff29caddc fix(jvm): resolve mixed Java/Kotlin callgraph edges via declared packages
Mixed Gradle/Maven source roots (src/main/java + src/main/kotlin) share
JVM packages that path-derived module QNs cannot join: cross-file LSP
emitted package-shaped names while graph nodes stayed path-derived, and
same-package defs across roots were filtered out, so Java<->Kotlin calls
fell back to weak or wrong textual edges.

The declared package is JVM ground truth. Normalize JVM defs to their
declared package (inferring it from conventional source roots only when
no package was extracted), index defs by declared namespace alongside
the path module so same-package defs survive per-file filtering, restrict
JVM callers to JVM defs, register cross-def return-type signatures for
Kotlin receiver typing, and consume qualified member-call overrides by
leaf name in the parallel lsp_idx fast path.

Distilled from #684 with one design change: the new unique-Class.method
tail-match fallbacks in lsp_resolve.h are gated to JVM callers
(cbm_pipeline_lsp_allow_tail_match, Java/Kotlin only). Tail-matching by
unique leaf is safe where class-per-file package semantics hold; in
other languages a single wrong-module coincidence would fabricate a
CALLS edge. The leaf-key lsp_idx lookup stays global: it aligns the
fast path with the build-side key that already used leaf names.

Tests: carried mixed Java/Kotlin regression (red on main, green here)
plus a gate guard asserting the tail fallbacks stay off for non-JVM
callers and on for JVM.

Co-authored-by: nguyentamdat <nguyentamdat@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:50:46 +02:00
Martin Vogelandpcristin e5bb74916a feat(watcher): prune projects whose root stays missing (ENOENT-only, grace window)
Distilled from #738: the watcher now prunes a watched project whose root
directory has genuinely disappeared - deleting the cached DB (+wal/shm,
validated name, cache-dir-only paths) and removing the watch entry - so
vanished worktrees stop being watched forever (#286).

Hardened beyond the original PR to remove a data-loss hazard (the cached
DB can hold user-authored data such as the ADR, unrecoverable once
deleted):

- Only ENOENT/ENOTDIR stat failures count as missing. Any other failure
  (EACCES, EIO, transient mounts, macOS TCC revocation) resets the
  streak and logs watcher.root_stat_error with the errno; Windows
  (mingw/UCRT) maps not-found to ENOENT so the check holds there.
- Pruning requires BOTH >=3 consecutive missing polls AND a sustained-
  absence grace window since the streak's first miss (default 600s,
  override via CBM_WATCHER_PRUNE_GRACE_S), tracked with monotonic
  cbm_now_ms.
- Root reappearance resets streak + timestamp (watcher.root_restored).
- The pruned entry is released via the deferred-free list poll_once
  drains (one freeing model shared with cbm_watcher_unwatch).

Limitation: only currently-watched projects are pruned; stale DBs left
by older sessions are out of scope.

Co-authored-by: pcristin <xxxokzxxx@protonmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:40:22 +02:00
Martin Vogelandsahil-mangla cfc73f8d0b fix(graph-ui): surface indexing-job failures instead of silently completing
The backend /api/index-status reports status:"error" plus an error
message for failed indexing jobs, but IndexProgress treated any
non-"indexing" state as successful completion: the spinner vanished
with no feedback (e.g. after an OOM-killed indexer subprocess), the
project never appeared, and no error was shown.

Render a visible error banner (path + error text) with a Dismiss
button instead, and keep the success flow unchanged. Beyond the
original PR:

- Restore the empty-jobs guard the PR dropped: the backend keeps
  finished jobs listed as "done"/"error" (handle_index_status only
  skips idle slots), so an empty list mid-index only occurs on
  transient state loss and must not be treated as completion.
- Route the new user-facing strings through the i18n system
  (projects.indexingFailed, common.dismiss; EN + zh entries).
- Cover error banner + dismiss, success flow and the empty-jobs
  guard with vitest cases on the now-exported IndexProgress.

Distilled from #549.

Refs #524

Co-authored-by: sahil-mangla <manglasahil2017@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:28:27 +02:00
Martin VogelandGen Li 87091edd24 fix(extract): receiver-aware self-recursion detection
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>
2026-07-03 20:20:49 +02:00
Martin Vogel 027f9239a8 ci: re-trigger CodeQL gate (poll-timeout flake, no code change)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:19:31 +02:00
Martin Vogel 30f0577c15 ci: re-trigger CodeQL gate (poll-timeout flake, no code change)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:19:30 +02:00
Martin VogelandAlexandros Pappas 6012f4626e fix(graph): IMPORTS edges keyed by local_name across buffer, store and dump
Distills PR #770 (graph-buffer dedup key) and completes the edge-uniqueness
contract it left partial: the contract lives in THREE places, and changing
only the buffer ships databases that violate their own UNIQUE constraint
(PRAGMA integrity_check: non-unique entry in sqlite_autoindex_edges_1).

A single 'import { A, B } from ./lib' produced ONE IMPORTS edge: the graph
buffer dedups edges on (source_id, target_id, type) and merge-replaces
properties on collision, so the second symbol silently overwrote the first.
pass_calls.c, pass_usages.c, pass_semantic.c and pass_lsp_cross.c parse one
local_name per IMPORTS edge for cross-file resolution, so the dropped
symbol's calls failed to resolve too — not just "who imports X" queries.

Changes, kept in sync across all three sites:

- graph_buffer.c (from #770): make_edge_key() folds local_name into the
  dedup key for IMPORTS edges only; all three key call-sites updated.
  Hardened beyond #770: EDGE_KEY_BUF bumped to 256 and oversized
  local_names are re-keyed with an FNV-1a hash of the full name instead
  of being silently truncated (two long names sharing a prefix must not
  collide back into one edge).
- store.c: edges gains local_name_gen, a VIRTUAL generated column
  (IMPORTS -> coalesce(json_extract(properties,'$.local_name'),''),
  else '' — NOT NULL because NULLs never conflict in a UNIQUE index);
  uniqueness widened to UNIQUE(source_id, target_id, type,
  local_name_gen) and the insert upsert's conflict target matches.
  init_schema probes pre-#768 DBs (no local_name_gen) and fails the
  open: SQLite cannot ALTER a table constraint in place, and an
  unopenable DB already takes the existing repair path — full index
  deletes + rebuilds, artifact import refuses and falls back to a
  reindex. Read-only query opens skip init_schema and keep working.
- sqlite_writer.c/.h: dump DDL matches the widened schema; the hand-built
  sqlite_autoindex_edges_1 comparator and entry builder include the
  local_name column; CBMDumpEdge carries local_name extracted via real
  JSON parsing (yyjson) so index entries match json_extract's unescaped
  values exactly, per the idx_edges_url_path precedent.
- artifact.h: CBM_ARTIFACT_SCHEMA_VERSION 1 -> 2 so old binaries refuse
  artifacts carrying the widened schema (their 3-column conflict target
  can no longer prepare against it).

Tests (reproduce-first, all red on the unfixed code):
- gbuf tier: multi-symbol import -> 2 IMPORTS edges; long-local_name
  truncation guard.
- store tier: distinct local_name coexists as 2 rows, same local_name
  still upserts, non-IMPORTS dedup unchanged.
- writer tier: dumped DB passes integrity_check with 2 sibling imports
  and exposes matching local_name_gen values.
- end-to-end: TS fixture through the real pipeline -> 2 queryable
  IMPORTS edges AND integrity_check ok; with only the buffer half of
  the fix this test still fails on integrity_check, proving the schema
  half is required.

Closes #768.

Co-authored-by: Alexandros Pappas <11921291+apappas1129@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:16:39 +02:00
Martin VogelandShauryaaSharma cf32e6c540 fix(windows): unswallow installer failures, quote CreateProcessW args, document MotW
Distilled from PR #702 (fixes #697):

- install.ps1: replace the try/catch that swallowed `codebase-memory-mcp
  install` failures with a $LASTEXITCODE check that reports the exit
  code and fails the installer, instead of silently leaving no coding
  agent configured.
- README: document Unblock-File for the Mark-of-the-Web restriction
  that blocks the downloaded install.ps1, plus the execution-policy
  escape hatch.
- cbm_exec_no_shell (Windows): switch from _spawnvp, whose CRT does not
  quote arguments containing spaces (the taskkill filter "IMAGENAME eq
  codebase-memory-mcp.exe" arrived as three bare tokens), to
  CreateProcessW over a two-pass MSVC-convention quoted command line
  (cbm_build_cmdline, exposed for tests via compat_fs_internal.h).
- UTF-8-correct widening: the quoted command line is assembled in UTF-8
  bytes and converted once via cbm_utf8_to_wide, so non-ASCII arguments
  (e.g. a non-ASCII %USERPROFILE%) survive intact instead of being
  byte-widened as Latin-1 mojibake.
- tests: regression guard for the #697 taskkill filter, MSVC quoting
  edge cases (empty arg, embedded quote, trailing backslashes), UTF-8
  widening guards (2-byte sequence with explicit code points; mixed
  2-/3-byte round-trip via cbm_utf8_to_wide), and live CreateProcessW
  spawn tests. All Windows-only, exercised by the Windows CI leg.

Co-authored-by: ShauryaaSharma <shauryasofficial27@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:15:52 +02:00
Martin VogelandSandro Jäckel 50392a4188 fix(scripts): use /usr/bin/env bash shebangs (NixOS has no /bin/bash)
On NixOS (and other non-FHS systems) /bin/bash does not exist, so
scripts with an absolute shebang fail to run. Switch the remaining
holdouts to /usr/bin/env bash: eleven scripts/*.sh,
test-infrastructure/run.sh, and the three Claude Code hook scripts
emitted by src/cli/cli.c (gate, session reminder, subagent reminder).

Distilled from PR #674, with parser-test coverage preserved: the
infra_parse_shell* fixtures in tests/test_pipeline.c intentionally keep
#!/bin/bash so absolute-path shebang extraction stays covered, and
tests/repro fixtures are untouched.

Also replace the GitHub-PAT-shaped fixture string flagged in the #674
thread with an obviously fake placeholder (ghp_FAKE...) that still
matches the ghp_ + 36-alnum secret detector.

Co-authored-by: Sandro Jäckel <sandro.jaeckel@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:11:49 +02:00
Martin VogelandSS-42 7b93210ca2 fix(cli): overflow-safe ZIP entry bounds check in self-update extraction
The truncation check `header_end + (int)comp_size > data_len` was
bypassable for comp_size >= 2^31: the int cast turns the size negative,
the sum drops below data_len, and the entry is accepted -- authorizing
inflate to read up to ~4GB past the downloaded archive buffer during
self-update ZIP extraction. Replace it with
`header_end > data_len || comp_size > (uint32_t)(data_len - header_end)`,
which is overflow-safe and strictly tighter.

Regression guard cli_extract_binary_from_zip_rejects_truncated_deflate_size_over_int_max
builds a 52-byte archive whose entry claims comp_size=0xFFFF0000 with a
self-terminating DEFLATE stream: the old check admits it and extraction
returns non-NULL (verified red pre-fix); the new check rejects it.

Secondary cppcheck cleanups from the same review:
- cli.c: extract zip_read_u16le/zip_read_u32le little-endian helpers
  (behavior-identical) and widen zip_extract_entry sizes to size_t with
  an explicit UINT_MAX guard before the zlib uInt narrowing.
- cypher.c: drop unreachable IS NULL / IS NOT NULL null guards in
  eval_condition (resolve_condition_value result is checked earlier).

Distilled from PR #784.

Co-authored-by: SS-42 <noreply@incogni.to>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 20:05:10 +02:00
Martin VogelandPatrick bd9ad5a17e fix(pipeline): honor discovery exclusions in pkgmap/path-alias/envscan walks
The auxiliary filesystem walks (pkgmap manifest scan, tsconfig/jsconfig
path-alias discovery, env-URL scan) ignored the directory subtrees that
discovery had already excluded (gitignore + skip dirs). On a huge
monorepo with a gitignored vendored tree this kept the pkgmap walk busy
for ~15 minutes re-traversing directories the index never uses (#792).

Distills PR #793 by Patrick (@Sowiedu): a shared root-anchored,
'/'-boundary exclusion predicate (cbm_pipeline_relpath_is_excluded) in
pipeline_internal.h, exclusion parameters threaded through
cbm_pkgmap_scan_repo / cbm_pkgmap_build_from_repo, find_alias_files /
cbm_load_path_aliases_excluded and cbm_scan_project_env_urls_excluded,
NULL-exclusion wrappers preserving the old signatures, and the borrowed
excluded_dirs/excluded_count pair on cbm_pipeline_ctx_t.

Beyond #793:
- Thread the excluded list into pipeline_incremental.c's extract ctx
  and its path-alias load as well, so incremental runs stop walking
  excluded trees too (#804).
- Add regression tests: boundary semantics of the exclusion predicate,
  plus walk-level exclusion tests for pkgmap, path-alias, and envscan
  (each with an unexcluded control run so they cannot pass vacuously).
  All are red against the unfixed sources.
- Correct the envscan narrative: cbm_scan_project_env_urls has no
  production callers today; the exclusion plumbing is kept for
  consistency and exercised by tests.

Closes #792. Closes #804.

Co-authored-by: Patrick <11910229+Sowiedu@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 19:57:01 +02:00
Martin VogelandRithvikReddy0-0 1d5fd65602 fix(cross-repo): normalize URLs, match templates, run both directions
Distills the pass_cross_repo.c half of #536; the pass_calls.c half landed
separately as beaa776 (broader: covers the parallel path too).

match_http_routes could not match a stored HTTP_CALLS url_path that was a
full URL (scheme://host:port/path is stored raw from the call's first
string argument), never matched a concrete client path against a templated
route QN, and only ran consumer->provider, so a provider-side run found
nothing - and worse, destroyed previously recorded links: delete_cross_edges
wiped the provider's reverse edges and nothing recreated them.

- cr_url_path(): strip scheme://host[:port] before route-QN construction;
  a bare base URL maps to the root route path.
- cr_path_matches_template() + find_route_handler_fuzzy(): segment-wise
  fallback so /v2/orders/123 matches /v2/orders/{} (method-gated, only for
  edges the exact lookup missed; O(calls x routes) per project pair,
  bounded by CR_MAX_EDGES).
- Reverse-direction match_http_routes run so provider-side invocations
  find the consumer's HTTP_CALLS and re-create the wiped reverse edges.
  Row dedup needs no insert guard: the edges table upserts on its
  (source_id, target_id, type) UNIQUE key.

Four reproduce-first tests in tests/repro/repro_issue523.c (scheme-strip,
template fuzzy match, provider-side run, bidirectional convergence), all
red on the previous code, green with the fix.

Refs #523

Co-authored-by: RithvikReddy0-0 <rithvikreddymukkara@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 19:53:37 +02:00
673b2201fb fix(py-lsp): memoize + guard expression evaluation (exponential re-eval hang)
py_eval_expr_type evaluated a call node's attribute receiver twice (once
in the container special-case, once in the general attribute path), so an
N-link chained call cost O(2^N) evaluations — real-world ~65-link builder
chains hung indexing for hours (#710). The evaluator also recursed once
per expression nesting level with no depth guard, so a pathologically deep
expression overflowed the native stack (#720).

Three guard layers, mirroring c_eval_expr_type's design in c_lsp.c:

1. Memoization: py_eval_expr_type is now a wrapper around the renamed
   py_eval_expr_type_uncached. Results are cached per node in an
   open-addressing table on PyLSPContext (arena-allocated, cold per file),
   keyed by node identity (TSNode.id) — NOT the start byte, since every
   leftmost descendant of a chain shares it and byte-keyed entries alias
   distinct nodes into silently wrong resolutions. Entries carry a scope
   generation; every scope bind/restore bumps it (O(1) whole-cache flush),
   so re-evaluation under changed bindings (lambda re-walks, isinstance
   narrowing) behaves exactly as before. Inserts are load-bounded (<75%)
   and rejected when growth fails, so a full table can never spin.

2. Depth cap: PY_LSP_MAX_EVAL_DEPTH 256 (same as C_EVAL_DEPTH_LIMIT);
   past the cap the evaluator returns unknown instead of overflowing the
   stack. Truncated results — including any ancestor of a truncation —
   are never cached, so a depth- or budget-limited `unknown` cannot
   poison post-truncation reuse.

3. Step budget: PY_EVAL_MAX_STEPS_PER_FILE 10000 (mirrors
   C_EVAL_MAX_STEPS_PER_FILE); pathological files degrade to unknown
   instead of stalling indexing. Cache hits don't consume budget.

Distills PR #732 (memoization; fixed the start-byte cache key and the
missing staleness handling) and PR #758 (depth cap; raised 64 -> 256 to
match the sibling LSPs, added the never-cache-truncated ordering), and
adds the step budget plus reproduce-first tests: a 40-link chain that
must resolve its final link, a heterogeneous-receiver chain that fails
under byte keying, a 30k-deep expression crash guard, and a budget
degradation guard.

Fixes #710. Fixes #720.

Co-authored-by: LA-10 <lashuha@uwaterloo.com>
Co-authored-by: Dustin Persek <dustin.persek@protonmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-03 19:49:03 +02:00