Commit Graph
632 Commits
Author SHA1 Message Date
Martin Vogel b1502bf3eb fix(install): Windows hook scripts get .cmd form; legacy files removed
Extensionless bash shims under %USERPROFILE%\.claude\hooks triggered
the Windows 'How do you want to open this file?' dialog whenever an
editor (Cursor) scanned the hooks dir, and could not execute without
bash anyway. All three Claude hook scripts (discovery gate, session
reminder, subagent reminder) now install as .cmd files with cmd syntax
on Windows - @echo off bodies, caret-escaped pipes in the reminder
text, 2>/dev/null redirects - and the install removes the pre-existing
extensionless twin so upgrades stop triggering the dialog. The
settings.json command paths follow automatically since they are built
from the same script-name macros. POSIX behavior is byte-identical and
guarded (no .cmd twin).

Closes #929

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 19:45:29 +02:00
Martin Vogel 9789a7c55e fix(watcher): reindex dirty repos once per distinct state, not per poll
A persistently dirty or untracked worktree re-triggered a full reindex
(and its DB/artifact rewrite) on every poll cycle - git_is_dirty()
answered 'is the tree dirty' where the watcher needs 'did the tree
CHANGE'. Idle repos with one uncommitted file produced sustained disk
write amplification (reported at 1 TB/day across several repos).

The watcher now tracks a dirty-state signature: FNV-1a over the
entries of git status --porcelain -uall -z plus each listed path's
(size, mtime_ns). Same signature -> no reindex; editing a dirty file,
adding/removing one, or reverting the tree to clean each produce a new
signature and trigger exactly one reindex. Submodule status is folded
in on POSIX, mirroring the old submodule dirty probe. 0 is reserved
for a clean tree; a dirty-at-baseline tree still reindexes once (the
watcher cannot know whether that state reached the DB, e.g. after a
server restart with a stale artifact).

Baselines (HEAD and signature) are now committed only after a
SUCCESSFUL reindex: check_changes stages observations in pending_*
fields, and busy-skips (index_fn > 0, e.g. pipeline lock held) or
failed runs leave the baselines untouched so the change is retried on
the next poll instead of being recorded as seen and silently lost -
the old code stored the new HEAD at check time, dropping any commit
that arrived while the pipeline was busy.

Closes #937

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-12 13:34:42 +02:00
Martin Vogel d0626b0508 fix(cypher): keep cypher.c windows.h-free — use _Thread_local directly
The compat.h include added for CBM_TLS pulled windows.h into cypher.c,
whose legacy 'far' macro (windef.h defines it empty) broke the
existing EXISTS-pattern local of that name on every Windows leg. Use
C11 _Thread_local for the depth-clamp flag instead; cypher.c stays
free of windows.h.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 22:30:53 +02:00
Martin Vogel 73a3c347d9 fix(cypher): shortest-path var-length semantics + advertised depth clamp
Three of #797's four findings fixed (the fourth — repeated-variable
unification — already holds on main and is now pinned by the test):

- Walk padding: the BFS recursive CTE deduped (node, hop) PAIRS, so a
  single self-loop minted every hop level for every node it could
  reach — *k..k matched fabricated walks of arbitrary length (a
  100-hop 'longest chain' artifact) and the row set exploded to
  nodes x depth (the reported crash). The traversal now returns each
  node once at its MINIMAL hop (MIN/GROUP BY): variable-length matches
  nodes whose shortest path lies in [min,max]. This is deliberately
  shortest-path semantics, not openCypher trail enumeration — right
  for reachability/depth audits on call graphs and linear instead of
  exponential; trace_path shares the traversal and now also reports
  each node once on cyclic graphs.
- Silent 100-hop cap: a clamped hop range now surfaces as
  result->warning ('clamped ... an empty result may mean clamped, not
  no such path'), emitted by query_graph in both TOON and JSON forms,
  on top of the existing log WARN.

Reproduce-first: cypher_exec_varlength_path_semantics_issue797 —
self-loop + real 2-chain fixture; RED on the walk semantics (*2..2
returned 3 endpoints instead of 1), GREEN now, incl. variable
unification, zero rows for nonexistent path lengths, and the clamp
warning assertion.

Closes #797

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 20:58:28 +02:00
Martin Vogel 4382b97908 feat(cypher): coalesce(var.prop, literal) in WHERE clauses
The WHERE parser rejected coalesce() outright ('unexpected operator'),
blocking null-safe numeric filters over optional graph properties —
the exact audit-query shape from the report:
  MATCH (f:Function) WHERE coalesce(f.transitive_loop_depth, 0) >= 2
RETURN-side coalesce already worked; only the WHERE leaf lacked it.

Implementation: the condition leaf gains a coalesce_default literal.
The operator/value tail of condition parsing is factored into a shared
parse_condition_op so both subjects (var[.prop] and
coalesce(var.prop, literal)) accept the full operator set (comparisons,
IS [NOT] NULL, IN). At eval time a missing/empty property value is
substituted with the default before the operator runs. Supported form
is coalesce(var.prop, literal) — matching openCypher's two-argument
null-substitution for this subset.

Reproduce-first: cypher_exec_where_coalesce_issue874 runs the
reporter's exact query — RED (rc=-1 parse error) on the old parser —
and pins both directions: a failing default excludes property-less
nodes, a passing default includes them.

Closes #874

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 19:10:07 +02:00
Martin Vogel 93740cedd6 fix(branch): restore files clobbered by a stale-base soft reset
The previous commit accidentally staged a stale-main vs current-main
delta (a git reset --soft against an outdated origin/main ref),
reverting the #1028 wide-canonicalization work and #1027's regression
test. Restore every touched file to current main; the net branch diff
is now exactly the intended two-file #898 fix (pass_calls.c +
tests/test_mcp.c).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 16:01:04 +02:00
Martin Vogel 51921d9063 fix(pipeline): valid JSON for sequential-path service edges and Route nodes
Two emission bugs in the sequential pipeline (pass_calls.c) — the
parallel path was correct, so the two pipelines produced different
bytes for the same input:

- Brokered ASYNC_CALLS edge props closed the method value's quote but
  not the broker's, emitting "broker":"bullmq} on EVERY such edge;
  the fixup below only repaired truncation, which never fires in the
  normal case. Rebuilt as the parallel path's incremental snprintf.
- create_svc_route_node stored the RAW method/broker string as the
  synthesized Route node's properties (literally 'bullmq' instead of
  {"broker":"bullmq"}). Now emits the parallel path's exact JSON.

Consequences of the malformed rows: json_extract errors across the
query surfaces, the edges generated columns (local_name_gen, url_path
index) fail, and PRAGMA quick_check aborts with 'malformed JSON' —
which since the artifact deep-integrity check (#895) also means such
caches are refused at import.

Reproduce-first: sequential_service_edge_props_are_valid_json_issue898
indexes a small celery fixture (sequential path), asserts >=1
ASYNC_CALLS edge exists (non-vacuous), every edge/node properties blob
is json_valid, and the broker is json_extract-able exactly like the
parallel path emits it. RED on the old emitters (1 malformed edge +
1 malformed Route node), GREEN now.

Note: the reporter's original bullmq JS repro no longer classifies on
current main because CommonJS require()/ESM import resolution has a
separate gap — that is exactly open issue #871; probed and confirmed
while building this fix.

Closes #898

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 15:54:58 +02:00
Martin Vogel 8f0f14b028 Merge pull request #1028 from DeusData/fix/973-wide-canonicalization
fix(win): wide-path canonicalization — drop ANSI _access/_fullpath
2026-07-11 15:30:51 +02:00
Martin Vogel aca02e06ef Merge pull request #1027 from DeusData/fix/773-parser-allocator-epoch
fix(pipeline): destroy TLS parsers at allocator-epoch boundaries (second-index SIGABRT)
2026-07-11 14:13:57 +02:00
Martin Vogel 14198fc1e1 fix(win): wide-path canonicalization — drop ANSI _access/_fullpath (#973)
Three path-canonicalization sites routed UTF-8 paths through the ANSI
CRT (_access + _fullpath): the repo_path canonicalization in
handle_index_repository, project-name derivation in fqn.c, and
resolve_canonical_path (the containment check). ANSI decoding is
locale-dependent by construction — on a CJK system codepage (the
reporter runs Big5) the UTF-8 bytes of a CJK repo path re-decode into
different characters, corrupting canonicalization while our CI (en-US
codepage) sails through: that is why the existing non-ASCII guards
stayed green while v0.9.0 crashed in the field.

New cbm_canonical_path: realpath on POSIX (unchanged behavior); on
Windows a wide GetFileAttributesW existence check + GetFullPathNameW,
never touching the ANSI codepage. resolve_canonical_path now also
requires existence on Windows, aligning it with the POSIX realpath
semantics it mirrors.

Guard: the Windows non-ASCII CLI guard gains the reporter's exact
shape — Traditional-Chinese directory (雷達測試), flag-form
--repo-path + --mode fast, real supervised spawn. Honest caveat: CI
cannot switch the system codepage, so the guard validates the
mechanics under en-US; the locale-specific corruption is fixed by
construction (no ANSI path decoding remains on these sites).

Closes #973

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 14:08:24 +02:00
Martin Vogel 2f7befb9ad fix(pipeline): destroy TLS parsers at allocator-epoch boundaries
SIGABRT (free of a non-libmalloc pointer in ts_stack_delete) on the
second index_repository in one process, whenever a sequential run is
followed by a parallel one. The sequential path parses on the CALLING
thread and left its TLS parser alive; the next parallel run switches
the global tree-sitter allocator to the slab (cbm_slab_install), the
calling thread participates in extraction, reuses the stale parser,
and its per-file teardown frees mimalloc-epoch memory through
slab_free's foreign-pointer fallback -> plain free() -> libmalloc
abort. The v0.9.0 supervisor masks this on the default MCP path (fresh
worker process per index); the in-process path and every embedded
consumer died. Root-caused with allocator-epoch instrumentation: the
victim parser is created at epoch 0 in cbm_pipeline_pass_definitions
and deleted at epoch 1 in extract_worker.

Two boundary fixes:
- run_sequential_pipeline destroys the calling thread's parser at run
  end (also bounds its memory like the parallel workers already do);
- cbm_parallel_extract destroys the calling thread's parser BEFORE
  cbm_slab_install, so no parser ever crosses the allocator switch.

Guards: prod-tier smoke invariant inv_second_index_inprocess
(sequential-then-parallel double index over stdio, RED with signal 6
on the unfixed binary — ASan builds mask the allocator seam, so the
unit tier cannot catch this class) + suite-tier fork-isolated
index_second_inprocess_run_survives_issue773 pinning the in-process
double-index contract. Repro 5/5 clean after the fix (was 5/5 abort).

Closes #773

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 12:51:29 +02:00
Martin Vogel 1a38d0a2e8 Merge pull request #1016 from DeusData/fix/stale-wal-sidecars
fix(store): remove destination WAL/SHM sidecars before installing a fresh DB
2026-07-11 05:36:24 +02:00
Martin Vogel d7e6082be7 Merge pull request #1019 from DeusData/fix/artifact-torn-export
fix(artifact): snapshot exports consistently and refuse corrupt imports
2026-07-11 04:51:02 +02:00
Martin Vogel 4ac5bb6d80 Merge pull request #1018 from DeusData/fix/surface-sqlite-corrupt
fix(store): surface SQLITE_CORRUPT from row scans instead of truncating
2026-07-11 04:36:52 +02:00
Martin Vogel 43d84ee6f2 fix(store): replace-on-rename for dump installs on Windows
The new stale-sidecar guard exposed a pre-existing Windows gap in the
same code path: rename() fails with EEXIST when the destination
exists, so cbm_store_dump_to_file could never install over a previous
DB generation on Windows. New cbm_rename_replace uses
MoveFileExW(MOVEFILE_REPLACE_EXISTING) with wide paths (raw
MoveFileExA would re-mangle non-ASCII cache paths); POSIX keeps plain
atomic rename.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 04:14:04 +02:00
Martin Vogel b79c6da931 fix(artifact): snapshot exports consistently and refuse corrupt imports
The FAST export path (the watcher/incremental auto-update) read the
raw main-file bytes of a live WAL-mode store: committed rows still in
the -wal were missing entirely and mid-checkpoint reads produced torn
snapshots (field artifact: four short indexes + btreeInitPage error 11
across ~12 nodes-table pages). Import then installed the torn file
byte-for-byte — the integrity check only sanity-checked the projects
table.

- Export: both quality levels now snapshot via VACUUM INTO (the
  consistent copy); only the index-stripping remains BEST-only, FAST
  keeps zstd-3. The fast path pays a full-copy VACUUM INTO now —
  correctness over speed; a torn team-shared artifact costs far more.
- Import: new cbm_store_check_integrity_deep (shallow check + PRAGMA
  quick_check) — used only on this rare path, hot-path callers keep
  the cheap shallow check.

Reproduce-first: artifact_fast_export_snapshots_live_wal_store
exports FAST while the writer connection is open — RED on the old
code with 0 of 60 nodes surviving the round-trip (all rows lived in
the WAL); GREEN now. store_deep_integrity_detects_page_corruption
pins the import guard both directions (healthy passes, zero-banded
pages refused).

Closes #895

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 03:28:32 +02:00
Martin Vogel b422a16889 fix(store): surface SQLITE_CORRUPT from row scans instead of truncating
Every row-scan loop discarded the terminal sqlite3_step code, so
SQLITE_CORRUPT mid-scan was indistinguishable from a clean end of
results: against a page-corrupted DB, counts answered from covering
indexes stayed correct while row fetches died at the first corrupt
table page — every query surface returned plausible truncated/empty
answers with no error (search total=379 with results=[], Cypher counts
diverging from schema counts, BM25 total=0, empty schema sections).

All 31 scan loops now capture the terminal code and fail loudly with
CBM_STORE_ERR + a store error message when a scan aborts, with
per-site cleanup (partial results freed or handed to the caller's
normal free path). Two deliberate soft spots, both enrichment-only and
documented inline: include_connected neighbor names (no store handle)
and the cluster adjacency scan (partial adjacency; primary surfaces
already fail loudly). Best-effort schema property-name discovery and
two capped helper scans keep their by-design early-exit semantics.

Reproduce-first: corrupt_page_scan_returns_error_not_truncation
zeroes a 40-page mid-file band per the issue's dd recipe and asserts
scans return CBM_STORE_ERR. RED on the old code with the exact
reported symptom (OK with 264 of 2000 rows silently truncated); GREEN
now. Fixture-health guards make a healthy-scan pass impossible to
mistake for a fix.

Closes #896

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 03:13:45 +02:00
Martin Vogel 394198e8e9 fix(store): remove destination WAL/SHM sidecars before installing a fresh DB
Three code paths install a brand-new DB file where a previous
generation lived without removing the destination's -wal/-shm:
the pipeline page writer (cbm_writer_open), artifact import (which
cleaned the TMP file's sidecars but not the destination's), and
cbm_store_dump_to_file (whose comment wrongly asserted stale sidecars
'become stale and get recreated'). SQLite validates a WAL against the
sidecar's own header/checksums — not the main file — so a crashed
session's leftover WAL is replayed ON TOP of the freshly installed
file at the next open, splicing old-generation pages into it.

New shared helper cbm_remove_db_sidecars (compat_fs, wide-path safe on
Windows) called at all three install points.

Reproduce-first: dump_install_ignores_stale_wal_sidecar follows the
issue's recipe (hot-copy a live WAL, clean close, restore the copy,
install generation 2, reopen). RED on the unguarded code — the replay
clobbered the fresh generation entirely (fresh row unqueryable);
GREEN with the cleanup. Non-vacuous: asserts the hot WAL is non-empty
before the copy.

Closes #897

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 02:40:54 +02:00
Martin Vogel a432cf729e fix(mcp): answer resources/prompts discovery with empty lists, not -32601
Clients like Cline probe resources/list, prompts/list and
resources/templates/list on connect regardless of declared server
capabilities; the -32601 Method-not-found errors surfaced as a failed
connection (status=error) even though tools worked fine. Empty lists
are the interoperable answer for a tools-only server.

Reproduce-first: mcp_discovery_methods_return_empty_lists is RED on
the unhandled dispatch (Method not found) and GREEN with the empty
lists.

Closes #958

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 02:25:27 +02:00
Martin Vogel 751d2293f9 Merge pull request #1013 from DeusData/feat/compact-tool-output
feat(mcp): TOON compact tool output — ~10x fewer response tokens across the query surface
2026-07-11 02:13:00 +02:00
Martin Vogel be9976dd31 fix(hook-augment): request format=json for the internal search_graph call
search_graph now defaults to TOON text, but ha_format_context parses
the inner payload as JSON ("results"), so the Grep/Glob/Read hook
augmenter silently produced no hookSpecificOutput — caught by the
test_hook_augment.py Windows guard going red. Both internal call sites
route through ha_build_args; request the JSON encoding there.

Verified end-to-end: indexed fixture + PreToolUse payload via stdin
emits the symbol context again.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-11 00:23:36 +02:00
Martin Vogel bd996c8b91 Merge pull request #1012 from DeusData/fix/cli-unknown-flag-reject
fix(cli): reject unknown tool flags with a nearest-flag suggestion
2026-07-10 22:35:23 +02:00
Martin Vogel 0fd175cf54 feat(mcp): TOON output for trace/query/search_code/architecture + context-bomb defaults
Completes the compact-output pass across the remaining query tools
(consumers are LLM agents; every response byte is context tokens):

- trace_path: callees[N]{qn,hop}/callers[N]{qn,hop} tables (risk/test/
  args columns per flags); the per-hop JSON key envelope was 84% of the
  payload. 3,589B -> 1,634B on a representative trace.
- query_graph: rows[N]{cols}: table (columns double as the header);
  format:"json" restores columns/rows arrays.
- search_code compact mode: results/raw/dirs tables; drops the
  field (duplicate of the qn tail). 2,488B -> 1,135B for 17 hits.
- search_code full mode: per-hit source capped at a 60-line window
  anchored on the first match, with source_start/source_truncated
  markers — uncapped whole-symbol dumps ran to 142KB per response; the
  complete symbol stays one get_code_snippet call away.
- get_architecture: default (no aspects) is now a summary (languages,
  packages, entry_points + always-on totals/label/type counts) with an
  aspects_hint — the old default rendered EVERYTHING including the full
  file_tree (94KB -> 4.5KB -> 2.8KB with TOON tables); all sections
  emit as TOON tables, aspects:[...] and ["all"] keep full access
  (52KB vs 94KB even for the explicit full dump).
- get_code_snippet: drops the property-blob enrichment (41% of the
  response; signature/docstring are literally in the source; fp/sp/bt
  never belonged there). Metrics remain reachable via search_graph
  fields=[...].
- list_projects: per-project 12-field git block (mostly null) replaced
  by the branch name; name/root/nodes/edges/size kept.
- search_graph description: in/out documented as TOTAL all-edge degree,
  NOT caller/callee counts (agent evals mis-read it as callers).
- format:"json" escape hatch on every converted tool.

Guards: tool_output_byte_budgets pins absolute byte ceilings on default
search/trace outputs (a property blob sneaking back into row emission
blows them immediately); snippet_enriched_properties inverted to pin
the no-spill contract. Suite 5,984 green; smoke updated to the TOON
contract end-to-end.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-10 21:18:56 +02:00
Martin Vogel 2950e4ec03 fix(cli): reject unknown tool flags with a nearest-flag suggestion
cbm_cli_build_args_json typed unknown flags as strings and shipped them
as JSON args the server ignores: 'trace_path --max-depth 1' succeeded
but traced at the default depth 3 — silently-wrong output that inflated
an agent evaluation by thousands of tokens of unwanted hop-2/3 rows.

Unknown flags for a known tool now fail loudly with a Levenshtein
nearest-flag suggestion:
  unknown flag --max-depth for this tool (did you mean --depth?) — run
  'cli trace_path --help' for the supported flags

Unknown-tool (no schema), raw-JSON, --args-file, stdin, and supervisor
worker argv paths are unaffected — none of them route through the flag
converter.

Reproduce-first: cli_build_args_json_unknown_flag_rejected is RED on
the permissive code (JSON produced) and GREEN with the rejection.

Closes #997

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-10 21:12:33 +02:00
Martin Vogel afcce2d42b fix(registry): don't swallow call suffixes on aliased-import direct hits
The #979 direct-hit early return in resolve_import_map fires before
suffix handling, so any imported_symbol.method() call whose base symbol
is itself an indexed node resolved to the base node instead of
base.method — the exact mis-resolution the neighboring comment warns
about. On django this degraded the graph by ~11K edges (CALLS
74,685->72,175, TESTS 38,032->29,236); auth.login => Signal.send became
an edge onto the user_logged_in Variable node.

Take the direct hit only for suffix-less callees — precisely the
aliased direct-symbol import case (#875) the early return was added
for. Verified on django: CALLS 74,708 (baseline + the 23 genuine #979
wins), TESTS 38,035; the #979 bare-alias test stays green.

Reproduce-first: resolve_import_map_alias_with_suffix_hits_method is
RED on the unguarded code (returns the bare symbol QN) and GREEN with
the guard.

Closes #1000

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-10 21:07:11 +02:00
Martin Vogel 4843a34065 feat(mcp): TOON compact output for search_graph — ~90% fewer response tokens
Tool responses land in an LLM agent's context window, where the legacy
per-node JSON objects were dominated by data no agent can use: the
similarity/semantic pipeline intermediates fp (~450B minhash hex), bt
(body-token bag) and sp (structural profile) plus 14 complexity metrics
were grafted onto every result by enrich_node_properties — ~64% of every
node emission, ~370 tokens per search hit.

search_graph now defaults to TOON (Token-Oriented Object Notation,
toonformat.dev): scalars as 'key: value' lines and results as a
'results[N]{qn,label,file,lines,in,out}:' header + one row per hit.
Published benchmarks show equal-or-better LLM retrieval accuracy at
40-60% fewer tokens than JSON; rows now also carry line ranges, which
the legacy output lacked.

- fields:[...] opts into per-node property columns (complexity,
  signature, docstring, ...); fp/sp/bt are blocklisted even there
- format:"json" restores the legacy verbose objects byte-identically;
  include_connected forces it (nested neighbor lists)
- BM25 and semantic modes emit the same table shape (rank/score column)
- semantic-only calls no longer run the unfiltered regex search that
  silently prepended up to 200 enriched nodes (102.8KB -> 4.2KB)
- default limit 200 -> 50 (BM25 100 -> 50): cheap first page, page via
  has_more/offset as before
- new emitter module src/mcp/compact_out.{c,h} (string builder + TOON
  scalar/table emission with spec quoting rules)

Measured on a real index (14-hit regex search): 20,793B -> 1,327B
(-94%); BM25 19,667B -> 5,081B; semantic 102,837B -> 4,181B. A typical
discovery session drops ~50.9K -> ~4.7K tokens combined with the
follow-up slices.

Guards: tool_search_graph_toon_never_leaks_internal_fields (sentinel
fp/sp/bt never emitted, even when requested), rewritten
tool_search_graph_includes_node_properties pins default-lean/fields-
opt-in/json-escape-hatch; suite 5983 passed, smoke green.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-10 08:23:19 +02:00
Martin Vogel 2dca0a39be Merge pull request #461 from halindrome/perl-lsp-semantic-resolution
feat(perl-lsp): Perl LSP-tier semantic resolution (Closes #459)
2026-07-09 23:37:34 +02:00
Shane McCarron 9c6b26e55d fix(perl-lsp): surface resolved method/static calls as graph CALLS edges
- Add method_call_expression to perl_call_types in lang_specs.c so the
  structural tier emits a method-call edge for the LSP bridge to refine
  (parity with PHP member_call_expression). callee_name is the bare method
  via the field-based extractor's `method` branch.
- Normalize the textual callee in cbm_pipeline_find_lsp_resolution to its
  last "::"-separated segment so qualified static Pkg::sub() calls match the
  resolved sub's dotted short-name (parity with PHP scoped_call_expression).
- Zero-edge guarantee preserved: untyped receivers still emit no edge.

Resolves DEVN-04 from plan 22-03. Verified on a Base/Derived/main fixture:
run_typed->{greet,describe}, run_static->helper, run_classcall->greet,
describe->greet (inherited), run_untyped->none.

Signed-off-by: Shane McCarron <shane.mccarron@corvexconnect.com>
2026-07-09 14:09:39 -05:00
Martin Vogel 23fd0e84e2 Merge pull request #982 from Dhruvy0804/fix/issue-842-clobber-class
fix(pipeline): guard env-access CONFIGURES and throws sources against…
2026-07-09 21:07:57 +02:00
Martin Vogel 4071877086 Merge pull request #980 from dpersek/fix/tools-list-pagination-971
Fix tools/list default pagination for non-paginating clients
2026-07-09 21:07:54 +02:00
Martin Vogel a6207c6e4c Merge pull request #979 from LA-10/fix/875-alias-import-resolution
fix(registry): resolve aliased direct-symbol imports in import_map (#875)
2026-07-09 21:07:50 +02:00
Martin Vogel 0906b532a2 Merge pull request #954 from yangsec888/feat/mem-budget-resolver-clamp
feat(mem): pure resolver + strict parse + clamp for CBM_MEM_BUDGET_MB
2026-07-09 20:13:00 +02:00
Martin Vogel 3772777307 Merge pull request #939 from aaiyer/agent/mojo-language-wiring
Add Mojo language wiring
2026-07-09 20:12:57 +02:00
Martin Vogel a70d39d1eb Merge pull request #956 from Cosm1cAC/fix/windows-search-code-utf8-paths
fix(windows): read search_code temp files as UTF-8
2026-07-09 19:30:18 +02:00
Martin Vogel 1c0a714621 Merge pull request #927 from apappas1129/fix/856-native-fetch-detection
fix(pipeline): classify native fetch() as HTTP_CALLS, not local shadows
2026-07-09 19:30:10 +02:00
Dhruv Garg 17f856cc97 Merge branch 'main' into fix/issue-842-clobber-class 2026-07-09 22:26:19 +05:30
Martin Vogel 3ea4bb024b Merge pull request #916 from alex-plosceac/fix/artifact-gitattributes-merge-ours
fix(artifact): put binary macro before merge=ours in generated .gitattributes
2026-07-09 18:54:00 +02:00
Martin Vogel fb215142db Merge pull request #906 from pcristin/fix/cypher-distinct-order-limit-pr
fix(cypher): apply distinct before return limits
2026-07-09 18:53:57 +02:00
Dhruvy0804 6b2cd08a12 fix(pipeline): guard env-access CONFIGURES and throws sources against directory-module collisions
Follow-up to #787/#828, disclosed during #828's review: the same clobber
class also affects two call sites #828 deliberately left out of scope.

create_env_configures_for_file (pass_definitions.c) and resolve_file_throws
(pass_parallel.c) both resolved their edge source via a direct
cbm_gbuf_find_by_qn(enclosing_func_qn) lookup with no guard against the
lookup landing on the shared Folder/Project node for directory-module
languages (Java/Go) -- the exact #787 root cause, just unguarded at two
more sites. A class-level construct (no enclosing function) has its
enclosing_func_qn fall back to the MODULE QN, which for a directory-based
module collides with the package's Folder node, conflating every
same-package file's CONFIGURES/THROWS edges onto one arbitrary source.

- create_env_configures_for_file: added the same
  cbm_pipeline_node_is_dir_container guard already used by the three
  #828-fixed finders, falling back to the per-file File node on a
  directory-container hit.
- resolve_file_throws: was missing the `rel` parameter its sibling
  resolve_file_calls/usages/rw already carry, so it inlined a raw lookup
  instead of calling the shared, already-guarded find_source_node. Added
  `rel` and switched to find_source_node, matching its siblings exactly.

Regression guard: tests/repro/repro_issue842.c, reproduce-first per the
issue's request for "an #828-style multi-order red fixture":

- CONFIGURES reproduces on a small 5-file sequential fixture (Go, since
  Java's System.getenv detection is separately broken -- extract_env_key_from_call
  reads a "function" field Java's method_invocation node doesn't have; noted
  in the test, out of scope here).
- THROWS only reproduces past MIN_FILES_FOR_PARALLEL=50 (the worker-merge
  path) -- confirmed empirically that the small sequential fixture already
  attributes correctly even on unfixed HEAD, mirroring #787's own two-faced
  (sequential/parallel) bug. Padded with 60 filler classes, following
  repro_issue787.c's build_parallel_fixture precedent.

RED on unfixed HEAD (both tests): 0 passed, 2 failed. GREEN with this fix:
2 passed, 0 failed. Full scripts/test.sh (5958 cases) passes clean with
this change; scripts/lint.sh's two pre-existing failures (pass_lsp_cross.h
parameter-name mismatch, extract_defs.c cppcheck AST parse) are unrelated
and present identically on unfixed main.

Fixes #842

Signed-off-by: Dhruvy0804 <dhruvgarg553@gmail.com>
2026-07-09 22:22:16 +05:30
Dustin Persek 8a4bef25ad Merge branch 'main' into fix/tools-list-pagination-971 2026-07-09 11:28:51 -04:00
Martin Vogel 374734445e Merge pull request #960 from DeusData/fix/path-within-root-indexable
mcp: make cbm_path_within_root parseable without the preprocessor
2026-07-09 17:16:14 +02:00
Dustin Persek dff70f5eb0 fix(mcp): avoid default tools/list truncation
Signed-off-by: Dustin Persek <dustin.persek@protonmail.com>
2026-07-09 11:09:53 -04:00
L 421b39f2ef Merge branch 'main' into fix/875-alias-import-resolution 2026-07-09 14:31:25 +03:00
LA-10 89e1c47fd1 fix(registry): resolve aliased direct-symbol imports in import_map
Signed-off-by: LA-10 <lashuha@uwaterloo.com>
2026-07-09 14:20:16 +03:00
Pcristin 6c7fe78e3b fix(cypher): apply distinct before return limits
Signed-off-by: Pcristin <xxxokzxxx@protonmail.com>
2026-07-09 09:40:44 +03:00
Martin Vogel 2ba1107921 feat(index): report deliberately-ignored files as a by-design coverage class
Completes the #963 coverage taxonomy: alongside the best-effort FAILURE
signals (parse_partial, skipped), the report now lists what was
deliberately NOT indexed — a deterministic, by-design class, never mixed
into the failure surfaces.

- discover: file skips are classified by reason (gitignore / cbmignore /
  skip-list / ignored-suffix / fast-pattern / size-cap) and collected via
  the new cbm_discover_ex2 (per-file entries capped at 2000 with the
  uncapped total reported — truncation is explicit, never silent; whole
  excluded subtrees stay exhaustive as before). Unsupported-extension
  files are deliberately NOT recorded: no grammar for an extension is
  not an ignore decision, and listing every README/asset would drown
  the signal
- pipeline: ignored files captured per run (full + incremental) and
  persisted into the index_coverage table as not_indexed_file rows,
  excluded dirs as not_indexed_dir; incremental runs rewrite the
  by-design set fresh from discovery instead of carrying old rows
- store: the deleted-file prune exempts not_indexed_* (deliberately
  unindexed paths never have hash rows); the missed shadow graph keeps
  showing FAILURES only — the UI's report-an-edge-case callout can
  never fire for gitignored paths, and a project whose only coverage
  rows are by-design gets no miss graph at all
- mcp: index_repository response gains not_indexed_files {files, count,
  truncated, note} (dirs stay under excluded); index_status's coverage
  report gains a not_indexed section (dirs + files + note) and no longer
  lumps by-design rows into skipped; tool descriptions updated — the
  by-design class is presented as deliberate and deterministic (change
  the ignore rules and re-index), with no best-effort hedging
- tests: e2e guard (gitignored file + dir reported with reasons on both
  surfaces, failure surfaces stay clean, missed graph excludes them,
  rows survive an incremental re-index, ignored constructs genuinely
  absent from the graph); store unit test covers the prune exemption
  and shadow-graph exclusion

Refs #963

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-09 07:51:45 +02:00
Martin Vogel 3eb294fa11 fix(ui): serve the upstream issues URL from the backend
The UI security audit (security-ui.sh, layer A1) forbids hardcoded
external URLs in graph-ui source — the callout's GitHub issues link
tripped it on all pr-smoke legs. Follow the established pattern for
external targets (/api/repo-info deep-links): /api/ui-config now
carries upstream_issues_url and the callout consumes it, rendering the
issue button only when the backend provides an https URL. The protocol
check uses a regex literal on purpose: a bare protocol string in source
also aborts the audit's URL extraction.

Verified: security-ui.sh passes locally; /api/ui-config serves the URL;
the callout opens with the prefilled link in the browser; frontend
build + tests green.

Refs #963

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-09 03:38:34 +02:00
Martin Vogel 545971cd88 feat(mcp): fold coverage into index_status; coverage note on get_code_snippet
Per review: no dedicated coverage tool.

- index_status now carries the coverage report (parse_partial + skipped
  lists from the separate index_coverage table, plus the best-effort
  coverage_note); get_index_coverage is removed. Tool description
  updated so agents learn the coverage semantics from index_status;
  index_repository and the response note now point there
- the read hook resolves coverage through index_status (same envelope
  fields, unchanged note wording); verified end-to-end against a real
  index (flagged file -> note with exact ranges, clean file -> silence)
- get_code_snippet responses from a parse_partial file now carry a
  correlated coverage_note ("line range(s) X could not be parsed ...
  the source above is ground truth") — the result names its file, so
  the warning is precisely anchored. Entirely-skipped files cannot
  appear here (no nodes), so no uncorrelated noise is possible; clean
  files carry no note (asserted both ways in the e2e test)

Refs #963

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-09 00:34:46 +02:00
Alexandros Pappas a902263439 Merge branch 'main' into fix/856-native-fetch-detection 2026-07-09 02:11:18 +08:00
Martin Vogel afc649f4e8 feat(ui): missed skeleton beside the code galaxy with click-to-focus
Render the miss graph side by side with the code graph instead of
swapping layouts:

- /api/layout (code graph) now also attaches "missed_graph": {nodes,
  edges, offset} — the shadow-project layout placed below the primary
  cluster (same satellite pattern as linked_projects; -Y slot so
  cross-repo satellites collide last). graph=missed stays the isolated
  view for API users
- the UI paints the skeleton white and ghostly beside the galaxy,
  auto-frames BOTH clusters on load, and navigates naturally: clicking
  the skeleton flies the camera into it (file labels + detail panel),
  clicking a code node flies back to the code side, and clicking empty
  space while the skeleton has focus returns to the overview (the
  galaxy can be entirely off-screen at that point)
- the sidebar toggle now shows/hides the skeleton (with a missed-file
  count) rather than swapping the layout

Verified in the browser: overview composition, skeleton focus with
split.c labeled, and the empty-space return flight; ui/httpd suites and
frontend build + tests green.

Refs #963

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-08 20:07:01 +02:00