Commit Graph
1291 Commits
Author SHA1 Message Date
周文瑄 93d2ee59ea fix(mcp): non-mutating query-only store resolve; annotate the ten read-only tools
search_graph, query_graph, trace_path, get_code_snippet, get_graph_schema,
get_architecture, search_code, index_status, check_index_coverage and
detect_changes were annotated readOnlyHint=false / destructiveHint=true
because resolve_store()'s corrupt-store recovery quarantines database
files - a mutation. That conservatism locked every plan-mode client out
of the read-only analysis surface (#1100 calls the same tools "eleven
read-only analysis tools"), since spec-compliant plan gates refuse tools
that are not declared read-only.

This closes the gap the annotation comment pointed at. The query-only
resolve now classifies a failed integrity check without mutating: no
mutation lease, no quarantine. A confirmed-corrupt database is reported
(a new STORE_RECOVERY_CORRUPT status; the tool reply names the corruption
and points at index_repository instead of a misleading "project not
found") and left in place for a write-side open to quarantine and
rebuild, exactly as before. Transient verdicts stay retryable. Write-side
opens (index_repository, manage_adr updates) keep the existing blocking
recovery, quarantine, and generation re-check semantics unchanged.

The ten query tools' annotations flip to readOnlyHint=true /
destructiveHint=false, making them callable under plan mode in Claude
Code, Codex, ZCode, and every other client that gates on the hints.

Tests: the annotations expectation table is flipped; the query branch of
the guard-balance and guard-denial tests now assert zero leases taken and
byte-identical files; the ghost-db test (#704) expects the corruption
message; the four write-side recovery tests (generation re-check after
lease wait, unique backup naming, publish failure, published-WAL
completeness) are driven through manage_adr update - the write path where
that recovery now lives.

Signed-off-by: 周文瑄 <zhouwx1997@126.com>
2026-09-03 15:33:57 +08:00
Martin Vogel db2f104a1f Merge pull request #2013 from shafty023/fix/1997-extract-scratch-arena
fix(extract): AST traversal stacks no longer outlive the file they walk
2026-09-03 08:14:16 +02:00
Martin Vogel 0e9ed75991 Merge pull request #2016 from rarepops/feat/ui-server-version
feat(ui): show serving binary version (#1820)
2026-09-03 08:13:57 +02:00
Martin Vogel c38aa35c27 Merge pull request #1777 from ocochard/feature/freebsd-pkg-managed-detection
fix(freebsd): treat ${LOCALBASE}/bin as pkg-managed; resolve self via sysctl
2026-09-03 00:07:06 +02:00
Martin Vogel 192f29609a Merge pull request #2006 from ilyabrykau-orca/fix/go-field-ref-member-signal
fix(pipeline): key the Go Field guard on the recorded selector shape, not reference text
2026-09-02 23:59:55 +02:00
Martin Vogel 01bf5f23e3 Merge pull request #2009 from CaptainMittens/fix/route-handler-last-argument
fix(routes): take a route's handler from the last argument, not the first
2026-09-02 23:53:32 +02:00
Martin Vogel e6a34fcea0 Merge pull request #1824 from The1nk/feat/blazor-razor-routes
feat(extract): discover Blazor .razor components and emit Route nodes for @page
2026-09-02 23:48:14 +02:00
Martin Vogel 2a0e9b9b80 Merge pull request #1876 from ertankucukoglu/fix/windows-unicode-session-context
Fix Unicode daemon session roots on Windows
2026-09-02 23:06:32 +02:00
Martin Vogel 7910de591b Merge pull request #1877 from CaptainMittens/fix/confidence-parse-sentinel
fix(confidence): stop an unreadable confidence from reading as 0.0
2026-09-02 23:04:20 +02:00
Martin Vogel 4bae9bd67f Merge pull request #1875 from CaptainMittens/fix/cypher-reject-trailing-tokens
fix(cypher): refuse a query the parser did not fully read
2026-09-02 23:04:04 +02:00
Martin Vogel 9639803175 Merge pull request #1836 from ertankucukoglu/perf/search-code-scope-suffix
perf(mcp): filter suffix globs while building search scope
2026-09-02 23:03:46 +02:00
Martin Vogel 620613ede6 Merge pull request #1986 from CaptainMittens/fix/swift-scanner-try-bang-shift
fix(grammar): stop the Swift try-bang entry shifting past its own type
2026-09-02 22:51:46 +02:00
Martin Vogel aefd2a1838 Merge pull request #2008 from CaptainMittens/fix/decorator-arg-scan-limit
fix(routes): find a route path wherever it sits in a decorator's arguments
2026-09-02 22:19:03 +02:00
Rares Popa e0785b353a feat(ui): show serving binary version
Expose the compiled server version through the existing UI config endpoint and render it beside the product name. Keep older or unavailable config responses compatible by hiding the label, with focused frontend and native endpoint coverage.

Fixes #1820

Signed-off-by: Rares Popa <2606875+rarepops@users.noreply.github.com>
2026-09-02 20:50:31 +02:00
Martin Vogel 4d8ac9e1ce Merge pull request #1802 from rarepops/fix/install-clients-registry-1798
fix(cli): apply client selector to registry integrations (#1798)
2026-09-02 19:22:45 +02:00
Martin Vogel 1424934980 Merge pull request #1799 from rarepops/fix/nanosleep-eintr-1616
fix(foundation): resume spawn backoff after EINTR (#1616)
2026-09-02 19:12:10 +02:00
Daniel Ochoa 40f2722d61 fix(extract): AST traversal stacks no longer outlive the file they walk
Fixes #2010. Refs #1997.

Indexing a 14k-file TypeScript repository holds 13.3 GB resident at
post_extract on Linux aarch64, and about 3.1 GB of it is AST traversal
scratch that no CBMFileResult points at.

ts_nstack_init cut its stacks from ctx->arena, which is result->arena. In
the parallel path that arena is stored into ec->result_cache[file_idx]
(pass_parallel.c:963) and freed only after parallel_resolve and the infra
passes (pipeline.c:1324), so a structure written for a one-file lifetime is
in fact held for the whole index, once per file, concurrently. The header
claimed otherwise and was wrong: "freed when the arena is destroyed at end
of file extraction".

The largest instance is channels. CHAN_STACK_CAP is 4096 and sizeof(TSNode)
is 32, so scan_string_consts_js (extract_channels.c:104) and
extract_channels_js (:376) take 128 KB each, per JavaScript or TypeScript
file. Across 12,673 such files that is 3,168 MB, and it produced 23 Channel
nodes out of 164,684.

CBMExtractCtx gains a scratch arena, created and destroyed by the
cbm_extract_file_ex call that builds the context. Nothing else changes about
where anything lives: ctx->arena still owns every string a CBMFileResult
points at, and ctx->scratch owns only traversal stacks, which nothing points
at.

Nothing a CBMFileResult points at moved. A context built without a scratch,
and a file whose scratch arena fails to allocate, both fall back to
ctx->arena, which is exactly the behaviour that shipped before this change,
so no path can be handed a NULL arena and silently lose nodes.

The signature change is what makes the mistake unrepresentable.
ts_nstack_init takes the context rather than an arena, so handing it
ctx->arena is an incompatible pointer type; push and push_children read the
arena the stack recorded, so a stale argument is an arity error. Both fail
under -Werror rather than silently retaining memory.

The 55 init sites are NOT one uniform sed. 29 pass ctx->arena and 26 pass a
local alias, and that alias is shared with result allocations: in
parse_zig_imports (extract_imports.c:1093) the same `a` feeds cbm_node_text,
strip_quotes, path_last and cbm_imports_push (extract_imports.c:1101, 1106,
1108, 1109). Rebinding it would have moved import names and module paths
into memory that dies with the call while the result kept pointing at them.
Each site was edited on its own and no alias was rebound. Re-derive the set
with:

  grep -rn 'ts_nstack_init' internal/cbm/extract_*.c
  grep -rn 'ts_nstack_push' internal/cbm/extract_*.c

parse_lisp_imports used its alias only for the stack, so the alias goes too.
push_nested_class_nodes and push_class_body_children took an arena purely to
reach the stack, so they take the context now; the one call site passes ctx.

Two measurements, from two different builds, and they are not the same
number.

A macOS census build, which instruments every cbm_arena_alloc and sums the
blocks behind every live CBMFileResult arena, reports the retained per-file
arena set dropping from 11,226 MB mapped to 5,680 MB, and blocks from 48,045
to 34,186. Those are mapped bytes, not resident: that build logs
mem.allocator.bound_populations_only, so untouched block tails never commit.

The resident figures come from Linux aarch64, gcc 13.3, static build, where
the build logs mem.allocator.owned classes=all and mapped equals resident.
Five runs per side on the full 13,999-file tree: mem.collect post_extract
rss_mb 13293 -> 6752 (-49.2%), mem.phase peak_mb 14840 -> 8565 (-42.3%).
nodes 164684, parse_partial 280 and registry entries/defines/imports
787954/804999/100027 identical on all ten runs; edges 835869-835874 against
835867-835881, overlapping ranges, so run to run noise. Wall clock means
35651 ms against 30502 ms, but the ranges overlap and the host had other
load, so the only supportable claim is no regression. ObjectScript Studio
Export on a small fixture: nodes 18, edges 25, both sides.

Scratch block size is 512 KB, chosen from measurement. arena_grow fires on
one file in 12,000 at both 512 KB and 1 MB, and constantly at 256 KB, where
the two channel stacks alone are exactly 262144 bytes. 512 KB therefore
costs half the resident block per worker for the same growth behaviour, and
stays under MI_LARGE_MAX_OBJ_SIZE in the vendored mimalloc, which is
MI_LARGE_PAGE_SIZE/8, exactly 524288 bytes (types.h:426, with
MI_ENABLE_LARGE_PAGES defaulting to 1 at :115 and not overridden by this
build), so a 512 KB block is still bin-allocated from a 4 MiB large page.
Growth is not free at that size for the same reason: arena_grow doubles to 1
MiB, above that bound and therefore a singleton OS allocation. One file in
twelve thousand pays it, which is why it is accepted. The largest single
file's scratch high water mark on this corpus is 33.5 MB, now transient
rather than retained.

Tests. Nothing in the suite asserted on result->arena size, block count or
total_alloc after cbm_extract_file, which is why this went unnoticed for the
life of extract_node_stack.h. traversal_stack_not_in_result_arena_issue2010
pins the byte budget: extracting "export const x = 1;" as TypeScript charged
365984 bytes to the result arena before and charges 87456 after, a
difference of 278528, exactly the two 4096-entry channel walks plus the
512-entry ES import walk at 32 bytes per TSNode. Of the 87456 that remain,
7680 is the defs item array at GROW_ARRAY's starting capacity of 32 times
sizeof(CBMDefinition) 240 and the other 79776 is everything else the
extraction interns, none of it traversal scratch; the 128 KB bound is
therefore above the new figure with room and a factor of four below the old
one. extract_traversal_stacks_come_from_ctx_scratch_issue2010 in test_mem.c
pins the lifetime, since a byte budget alone would also be satisfied by
shrinking CHAN_STACK_CAP: it builds the extraction context directly over two
arenas it owns, runs cbm_extract_channels, and asserts the scratch took at
least the 262144 bytes of the two walks while the result arena did not.
extract_c_macro_hidden_call_survives_preprocessed_pass_issue2010 is not a
scratch test. pp_ctx carries ctx->scratch so every context in the file is
uniform, but nothing reads it there: pp_ctx reaches only cbm_extract_unified
and cbm_run_c_lsp, and neither extract_unified.c nor anything under
internal/cbm/lsp/ includes extract_node_stack.h, so no traversal stack is
built on that path today. The test guards the macro-expansion path itself,
which had no assertion on a call that exists only after expansion.
ObjectScript composite extraction is already covered by
pipeline_objectscript_export_preserves_calls_sequential_parallel and
iris_export_xml_multi_class, so no new test is added there; a per-call arena
is correct on that path by construction, since each call owns and destroys
its own.

Local: scripts/test.sh, 7558 passed, 0 failed, 8 skipped, 139 suites.

Alternatives considered

Thread-local scratch arena, reset per file, with a depth guard for nesting.
Rejected on three counts. This codebase never uses thread-local storage for
owned bulk memory; every existing _Thread_local here is a small cache, a
guard or a log buffer, and every per-worker resource is a field in a context
struct. It would have needed cbm_arena_reset, which has no production caller
and a documented subtlety with grown block sizes (arena.c:213-217). And
extraction threads are created per cbm_parallel_for call and joined
(worker_pool.c:71,88) with no thread-exit hook in this project's own code,
so the arena would have leaked once per thread per dispatch and turned the
leak lane red.

Save and restore mark and release on the arena. Rejected because a per-call
arena already makes nesting correct by construction, and a mark type plus
two functions would have to be added to both arena headers.

Per-walk arenas, one per traversal rather than one per file. Rejected on
cost: 55 sites would each need an explicit span, for the same bound.

Right-sizing the initial capacities alone. CHAN_STACK_CAP at 256 rather than
4096 would cut those two sites from 3,168 MB to about 198 MB, which is real,
but it tunes the constant while the lifetime defect remains at all 55 sites,
and growth is not free: 256 doubling to 4096 abandons 122,880 bytes, and one
file in this corpus took 33.5 MB of stacks. The cap and the scratch block
size are coupled, which is a further reason to change one at a time. Worth
doing separately.

Routing to a scratch inside extract_node_stack.h while keeping the arena
parameters. Rejected because a parameter that is ignored lies about the API,
and the alias hazard above would stay a review obligation instead of a
compile error.

Freeing the per-file result arena earlier. Rejected: the results are read by
parallel_resolve and the infra passes. That is the retention half #1925
addresses, and this change is independent of it.

Signed-off-by: Daniel Ochoa <daniel.ochoa@closedloop.ai>
2026-09-02 09:22:37 -05:00
Martin Vogel 7fedaba331 Merge branch 'main' into feature/freebsd-pkg-managed-detection 2026-09-02 11:45:45 +02:00
Ilya Brykau 349aecba95 fix(pipeline): key the Go Field guard on the recorded selector shape, not reference text
cbm_go_suppress_bare_field_ref dropped a Field-targeted reference when
strchr(ref_name, '.') == NULL — but the extractor strips the receiver on
every path that reaches the resolver (resolve_lhs_write_name records the
trailing field name of a selector LHS; is_reference_node records the
inner field_identifier), so the dot test could never be false for Go.
The guard was a blanket veto: all ~4588 Go Field nodes recovered by
one 61de19bb were unreachable by USAGE/READS/WRITES, including genuine
selector references (issue #1962, found in maintainer post-merge review
of #1944).

The selector-vs-bare distinction exists at extraction time and was
discarded; record it and let the resolver consume it:

- CBMUsage/CBMReadWrite gain is_member_access. The usage recorder sets
  it when the reference node is a field_identifier (the member half of a
  selector); resolve_lhs_write_name reports it through an out-param when
  it takes the field/member LHS branch.
- cbm_go_suppress_bare_field_ref(is_go, is_member_access, target_label)
  refuses a Field bind only for references that were never the member
  half of a selector. All four resolver sites (sequential and parallel,
  USAGE and READS/WRITES) pass the recorded flag.

Reproduce-first: with the veto restored, the extended #1942 fixtures go
RED on both resolver paths at the new asserts (t.err = nil must WRITE
the field; t.n must produce USAGE); with the fix both twins are GREEN
and the original bare-local negatives still hold. The dead
ASSERT_FALSE(..., "t.err", ...) unit case — an input Go's extractor
cannot produce — is replaced by flag-based cases.

Fixes #1962.

Signed-off-by: Ilya Brykau <ilya.brykau@orca.security>
2026-09-02 10:39:03 +02:00
Martin Vogel 98e386e58b Merge branch 'main' into fix/autoindex-skip-limit-value-1466 2026-09-02 07:49:40 +02:00
Ertan 2f37f61e88 perf(mcp): filter suffix globs while building search scope
Signed-off-by: Ertan <ertan.kucukoglu@gmail.com>
2026-09-02 06:09:34 +03:00
Joshua Richter 592894a438 fix(routes): take a route's handler from the last argument, not the first
Express, Fastify, gin and Laravel all put middleware between the route
path and the handler, and the handler comes last:

    routerGet("/users", requireAuth, rateLimit, listUsers);

extract_handler_arg returned the FIRST argument that looked like a
function reference, so requireAuth won and the HANDLES edge pointed at
the middleware instead of listUsers. That is a wrong edge, not a
missing one, and it misleads anyone tracing a request through the
graph.

The scan bound hid a second fault. An arrow function matches none of
the accepted node kinds, so three inline middlewares pushed the real
handler past MAX_HANDLER_SCAN and no handler came back at all.

The loop now examines every argument and keeps the last eligible one.
Nothing that follows a handler matches the accepted kinds either, since
an options argument is an object node, so the last match is the
handler. HANDLER_START_IDX stays, because argument 0 really is the
path. MAX_HANDLER_SCAN had no other use and is gone.

A two-argument route has one eligible argument, so first and last agree
and the existing Express, Fastify, gin and Laravel route tests are
unchanged.

Both tests fail without the change, the first reporting "requireAuth"
where "listUsers" belongs and the second reporting no handler at all.

Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-01 22:31:03 -04:00
Joshua Richter c36b4fbc44 fix(routes): find a route path wherever it sits in a decorator's arguments
Java and Kotlin put no order on annotation attributes, so `path` can
appear after `method`, `produces` and `consumes`:

    @RequestMapping(method = RequestMethod.GET,
                    produces = MediaType.APPLICATION_JSON_VALUE,
                    consumes = MediaType.APPLICATION_JSON_VALUE,
                    path = "/orders")

extract_route_path_from_args stopped after the third argument, so the
path was never read and no Route node formed. The endpoint was in the
source and not in the graph.

Four call sites read this function, so the miss covered plain decorator
arguments, Spring mappings, and both JAX-RS paths.

The loop now checks every argument. That matches find_drf_kwarg_in_args
a few lines below, which walks the same kind of node with no cap. Cost
stays small: the loop returns on the first path-shaped string, and each
argument's own subtree walk is still bounded by find_route_path_literal.

The recursive breadth guard inside find_route_path_literal keeps
DECORATOR_SCAN_LIMIT. That loop descends to CBM_DESCENDANT_MAX_DEPTH, so
breadth 3 by depth 6 is 729 visits at worst and unbounded breadth there
is not bounded at all.

The test fails without the change, reporting the class-level "/api"
route present and "/api/orders" missing.

Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-01 22:30:54 -04:00
The1nkandClaude Opus 5 fd73c347fb feat(extract): emit Route nodes for Blazor @page directives
get_architecture's routes aspect returned nothing for a Blazor
application: the entry surface of the app was invisible in the graph
even though every @page directive names one.

@page lives in markup above the @code block, which tree-sitter's C#
grammar never parses, so there is no AST node to read it from. Scan the
raw source for it instead. The match is deliberately strict — the
directive must be the first token on its line, followed by whitespace
and a double-quoted path beginning with '/' — so @pageSize and prose
mentions cannot match.

The route is attached to the file's module def. A .razor component's
class is implicit, so there is no class node to carry it, and the module
QN already is the component's identity. insert_def_into_gbuf is
label-agnostic and creates Route + HANDLES from route_path.

pass_route_nodes.c's ensure_decorator_routes gains "Module" for the same
reason. Extraction covers the full-index path on its own; this backstop
is what runs on an incremental re-index, so without it a component's
Route would appear on a full index and vanish the next time that one
file changed. Its loop bound now derives from the labels array rather
than borrowing the unrelated RN_STRIP_PASSES, so adding a label cannot
silently skip it. Verified no double-creation when both paths can fire.

Measured on a real Blazor application (36 .razor files): routes went
from 0 to 11, with 11 matching HANDLES edges, and no change to the
363 Class / 1775 Method / 3805 CALLS already extracted from its C#.

Covered by two new tests in tests/test_extraction.c.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: The1nk <23087600+The1nk@users.noreply.github.com>
2026-09-01 22:15:52 -04:00
The1nkandClaude Opus 5 44caa4c386 feat(discover): map .razor to C# so Blazor components are discovered
A .razor file had no entry in EXT_TABLE, so discovery skipped it entirely
and a Blazor application produced no graph nodes for any of its
components. The only way in was an undocumented extra_extensions entry in
a per-project .codebase-memory.json.

Map .razor to CBM_LANG_CSHARP. This is best-effort by design: the C#
grammar recovers the @code block, while the surrounding markup lands in
ERROR regions and is reported through parse_partial. That is strictly
more than the Module-and-imports the other markup-hosted languages
(Vue, Svelte, Astro) extract today, and it needs no new grammar.

Covered by tests/test_language.c:lang_ext_razor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: The1nk <23087600+The1nk@users.noreply.github.com>
2026-09-01 20:32:09 -04:00
Joshua RichterandClaude Opus 5 62b44519c6 fix(confidence): stop an unreadable confidence from reading as 0.0
strtod answers 0.0 for text it cannot read, and 0.0 is a real confidence
in both places that call it. So a property blob carrying a malformed
value, such as "confidence":null, came back as a recorded confidence of
zero. Two things went wrong with that.

In src/graph_buffer/graph_buffer.c, edge_props_confidence answers
CBM_EDGE_CONF_ABSENT (-1) when an edge carries no confidence, so any real
confidence outranks it. A malformed value answered 0.0, which beats -1 in
the merge comparison, so the malformed blob displaced a clean stored one.
The function's own comment already promised that "absent/unparseable
reads as -1". Only the absent half was true.

In src/mcp/mcp.c, bfs_edge_evidence_for_hop sets the confidence to -1 and
the emitter publishes any value of 0 or more as a recorded number. A
malformed value printed as 0.00, which reads as "the resolver was certain
this is wrong" rather than "nobody wrote a number here".

Both sites now pass an end pointer to strtod and keep the absent sentinel
when the pointer never moved, which is the shape src/store/store.c:402
already uses.

Two tests come with the change, and both were seen failing before the fix
and passing after:

  gbuf_edge_props_unreadable_confidence_does_not_displace_absent
  tool_trace_path_unreadable_confidence_reports_not_recorded

Red: 258 passed, 2 failed. Green: 260 passed, 0 failed.

The full suite reports 7633 passed, 2 failed. Both failures are in
tests/test_cli.c (lines 1749 and 6725) and reproduce on a clean tree
without this change. They depend on the coding agents installed on the
machine, not on this change. make -f Makefile.cbm lint-ci passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-01 19:35:12 -04:00
Joshua RichterandClaude Opus 5 25a7aacc87 fix(cypher): refuse a query the parser did not fully read
cbm_parse built a query and returned success without checking that it
had read every token. The grammar accepts at most one WITH and treats
RETURN as optional, so the parser stopped at the first thing it did not
understand and reported success anyway.

The dropped tail took the filter and the RETURN with it. The engine then
answered from the fragment it had parsed, using its default projection.
It reported success and returned wrong rows, which is worse than a
refusal, because nothing tells the caller to look.

Two shapes hit this:

  MATCH (f:Function) WHERE f.name = 'x' RETURN f.name AS n BANANA SPLIT 99
  -> one row, no error, the trailing words silently dropped

  MATCH (f:Function)
  OPTIONAL MATCH (a)-[:CALLS]->(f)
  WITH f, count(a) AS calls
  OPTIONAL MATCH (b)-[:USAGE]->(f)
  WITH f, calls, count(b) AS usages
  WHERE calls = 0 AND usages = 0
  RETURN f.name AS n
  -> every function, unfiltered, under the default column names

cbm_parse now checks that the cursor sits on the end-of-input token
before it returns success, and names the leftover token when it does not.

The message adds a note about the one-WITH limit, but only when a
standalone WITH really sits in the part that went unread. Appending it
every time pointed the reader at WITH when the problem was a typo -- the
BANANA query above holds no WITH anywhere. The test cannot simply look at
the token the parse stopped on either: on the second-WITH query above it
stops at OPTIONAL rather than at WITH, because parse_post_where consumes
the first WITH and then has no way to take another MATCH stage. So it
scans the unread tail, and skips a WITH straight after STARTS, which is
the STARTS WITH operator rather than a clause.

The check exposed a second defect. parse_post_where parses the branch
after UNION by calling cbm_parse on a slice of the same tokens, using a
separate parser. The outer parser's cursor never moved past the UNION
keyword, so a valid UNION query looked unfinished. That cursor was
already wrong; nothing caught it, because nothing checked where the
cursor ended up. The UNION branch now moves the cursor to the end after
the sub-parse succeeds, which is sound because that sub-parse no longer
returns success with tokens left over.

Three tests cover both directions. Removing only the guard turns the two
rejection tests red with rc == 0 -- the parser reporting success on input
it never finished reading -- while the acceptance test stays green, so
the guard is load-bearing for exactly these two behaviours.

The two rejection tests also pin the message. The BANANA case must name
BANANA and must not mention WITH; that assertion was seen red first, at
tests/test_cypher.c:226. The second-WITH case must still carry the note;
that one passed before and after, and is the control that stops the note
being suppressed everywhere instead of only where it misleads.

Verified on macOS with Apple clang:
  make -f Makefile.cbm test-focused TEST_SUITES=cypher  -> 186 passed
  guard removed, tests kept                             -> 184 passed, 2 failed
  make -f Makefile.cbm cbm                              -> exit 0, no warnings

The full suite reports 7623 passed, 2 failed. Both failures are in
tests/test_cli.c (lines 1748 and 6723) and reproduce on a clean tree with
this change stashed out. They depend on the coding agents installed on
the machine, not on this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-01 19:27:58 -04:00
Ertan 9d4ac2b98b fix(daemon): accept Unicode session roots on Windows
Signed-off-by: Ertan <ertan.kucukoglu@gmail.com>
2026-09-02 01:00:31 +03:00
Joshua Richter 3f831936b5 fix(grammar): stop the Swift scanner shifting past the width of an int
The Swift scanner keeps a 64-bit mask of the symbols that suppress a
match -- the rule that stops `try!` emitting its `!` as a token of its
own. It tests one bit per candidate:

    uint64_t suppressing_symbols = OP_SYMBOL_SUPPRESSOR[full_match];
    for (uint64_t suppressor = 0; suppressor < TOKEN_COUNT; suppressor++) {
        if (!(suppressing_symbols & 1 << suppressor)) {

The mask is uint64_t but the literal `1` is an int, so the shift is an
int shift. TOKEN_COUNT is larger than 32, so once suppressor reaches 31
the shift runs past the width of the type. That is undefined behavior,
and every bit above 31 is tested against a value the standard does not
define.

Nothing caught it because nothing in the tree reached the suppressor
path. Any Swift force-unwrap does: `cached!` is enough.

UBSan reports it as:

    scanner.c:514:47: runtime error: left shift of 1 by 31 places
    cannot be represented in type 'int'

`1ULL` makes the literal as wide as the mask it is tested against.

The new test in tests/test_extraction.c cannot go red on its own. The
normal test build prints the UBSan message and carries on, which is why
this survived. The Windows CLANGARM64 leg runs UBSan in trap mode, and
there the same shift is an illegal-instruction crash -- so the test
exists to make sure that leg keeps parsing a force-unwrap at all.

scripts/vendored-checksums.txt records the new hash for the one changed
file, as scripts/security-vendored.sh --update writes it. Layer 8 of the
security gate compares vendored content against that manifest, so the
edit and its recorded hash belong in the same commit.

Found while adding Swift URL extraction in #1892 / #1976, and split out
of that PR so the vendored change can be reviewed on its own.

Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
2026-09-01 17:52:38 -04:00
Martin Vogel 359f7360dd Merge branch 'main' into fix/1542-trace-json-evidence 2026-09-01 20:17:57 +02:00
Martin Vogelandastandrik da61c81af2 fix(lsp): unify the two type short-name indexes that broke the main build
main does not compile. internal/cbm/lsp/type_registry.h declares
type_short_buckets, type_short_entries and type_short_bucket_count twice, and
cbm_registry_types_by_short_name twice with different parameter types.

Two changes implemented the same feature independently:

  a4c0ffbc  perf(cs): eliminate the C# corpus-proportional scans  (2026-08-16, ours)
            CBMTypeShortIter, cbm_type_short_iter_next
  793716dc  perf(lsp): index C++ short-name type lookups     (PR #1679, astandrik)
            CBMTypeNameIter,  cbm_type_name_iter_next

They landed in different hunks of the same struct and the same header section,
so git merged them with no conflict and nothing failed until the merge commit
was built. This is the failure mode where a clean merge is not evidence that
two changes compose.

The two iterators are structurally identical -- same five fields, byte-identical
_next bodies reading the same reg->type_short_entries chain. They differ in one
place, and it matters:

  #1679          on aux-index allocation failure, sets tail_i = 0, so the
                 iterator degrades to a full types[] scan and the candidate
                 set is unchanged.
  a4c0ffbc       sets tail_i = reg->type_qn_entry_count unconditionally, so on
                 that same failure it SKIPS the first type_qn_entry_count types
                 and silently yields an incomplete candidate set.

So this keeps a4c0ffbc's names -- CBMTypeShortIter reads correctly beside its
type_short_* members and beside CBMTypeEmbedIter and CBMFreeFuncIter -- and
#1679's body, which is the correct one. The surviving declaration carries both
rationales: the C++ short-name lookup and cs_resolve_type_name's step-9
fallback are now stated as two consumers of one index, which is what they are.

The merge also broke two comment/member pairings, repaired here: the duplicated
members had displaced the free-function index members from under their own
comment, and the CBMTypeShortIter block had been interleaved between
CBMFreeFuncIter's typedef and its declarations.

No behaviour change for either consumer beyond the allocation-failure path,
which strictly gains coverage for C#. c_lsp.c and cs_lsp.c now call one
function. #1679's tests, including the fault-injection case that NULLs the
buckets and asserts the full-scan fallback, carry over unchanged apart from the
type name.

Local: c_lsp, cs_lsp and registry -- 881 passed, 0 failed.

Co-Authored-By: astandrik <astandrik@yandex-team.ru>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-01 18:43:37 +02:00
Martin Vogel 7808686cee Merge pull request #1679 from astandrik/codex/perf-cpp-short-name-lookups
perf(lsp): index C++ short-name type lookups
2026-09-01 17:08:48 +02:00
Martin Vogel 641c71f53c Merge pull request #1868 from DeusData/feat/issue-469-file-outline
feat(mcp): add bounded file outline tool
2026-09-01 17:02:05 +02:00
Martin Vogel 3627eff580 Merge pull request #1389 from Yyunozor/fix/arg-url-false-positives
fix(pipeline): reject non-HTTP slash arguments
2026-09-01 17:00:12 +02:00
Martin Vogel f0cf84d251 Merge pull request #1426 from tthayer/feat/pkl-call-extraction
feat(pkl): extract calls, branches, throws and decorators
2026-09-01 16:57:12 +02:00
Martin Vogel a824d82a34 Merge pull request #1939 from xkchok/fix/go-cross-package-field-dispatch
fix(go): resolve cross-package field-chain calls via type dispatch
2026-09-01 14:18:32 +02:00
Martin Vogel 5a5be7409a Merge pull request #1844 from DeusData/codex/issue-1838-perl-grammar-v1.2.1
fix(perl): update grammar for format declarations
2026-09-01 14:16:16 +02:00
Martin Vogel b357d0902f Merge pull request #1657 from atirna/fix/index-worker-explicit-memory-budget
fix(mem): honor explicit worker budget override
2026-09-01 14:12:43 +02:00
Martin Vogel 011087ebbb Merge pull request #1961 from ilyabrykau-orca/test/windows-guards-go-fixture
test(ci): make the Windows path guard fixture polyglot so CI indexes Go end to end
2026-09-01 14:10:26 +02:00
Martin Vogel 58fb9455bc Merge pull request #1958 from ilyabrykau-orca/fix/windows-guards-setup-retry
fix(ci): gate Windows guard setups on the index response, not list_projects stats
2026-09-01 14:09:27 +02:00
Atirna 7f158e950f Merge branch 'main' into fix/index-worker-explicit-memory-budget 2026-09-01 14:54:15 +05:30
xkchok e11d5900cd fix(test): free the return-type table in the go field-chain helper
run_go_field_chain_sequential tears down the results, registry and
cross arenas but never calls harness_ctx_free_tables, so pass_calls'
CBMReturnTypeTable (strdup'd method return types) leaked whenever the
helper ran. ASan builds caught it: AddressSanitizer: 32911 byte(s)
leaked in 6 allocation(s) from build_return_type_table, failing
test-diag, test-lsan-macos and both gcc sanitizer shards. Verify
locally: build/c/test-runner parallel -> 73 passed, no leaks.

Signed-off-by: xkchok <143325001+xkchok@users.noreply.github.com>
2026-09-01 15:52:15 +08:00
xkchok 8f50841af4 fix(go): resolve cross-package field-chain calls via type dispatch
Go calls written through an aliased struct field (h.svc.Method(ctx,
req)) previously fell through to fuzzy suffix matching because:

- the per-file Go dispatch walk was bypassed, so field selectors were
  never descended;
- struct fields extracted as flat "Field" defs were dropped by the LSP
  label map, so every Go struct registered with zero fields;
- receiver types written through import aliases (svc.Svc) produced
  qualified names that exist nowhere in the project registry.

Changes:

- restore the Go walk in cbm_pxc_dispatch_file and descend field
  selectors through the shared prebuilt registry, emitting
  lsp_type_dispatch edges;
- fold per-field defs into their owning struct inside
  cbm_pxc_collect_all_defs (one site covers both the prebuilt-registry
  path and the per-file fallback);
- re-qualify the last alias segment through the calling file's import
  map when the direct receiver-type lookup misses
  (go_requalify_via_imports).

Tests:

- tests/test_parallel.c: cross-package field chain resolves end to end
  with an lsp_type_dispatch edge in the parallel resolver;
- tests/test_go_lsp.c: alias-typed struct field re-qualifies through
  the import map and lands on the real receiver type.

Signed-off-by: xkchok <143325001+xkchok@users.noreply.github.com>
2026-09-01 15:51:50 +08:00
Yyunozor c86f1ffafd fix(pipeline): reject non-http slash arguments
Signed-off-by: Yyunozor <yyunozor@icloud.com>
2026-08-31 20:14:01 +02:00
Tony Thayer-OsborneandClaude Fable 5 c9d68de607 Merge upstream/main into feat/pkl-call-extraction
Resolves conflicts with the Chialisp language addition (#1920) in three
repro ledgers. Both branches added one call-capable language, so beyond
the textual conflicts, the auto-merged shared totals had to be bumped a
second time:

- repro_language_registry.c: capability partition now 88/27/49,
  EXPECTED_CALL_CAPABLE_LANGUAGES 115, EXPECTED_NON_CALL_LANGUAGES 51,
  EXPECTED_MATRIX_B_ROWS 49
- repro_call_node_manifest.c: historical total 226, active primary 196,
  direct calls 160, constructor calls 22, primary owners 196
- repro_call_argument_matrix_b.c: ROUTINE_ARGUMENT_LANGUAGE_COUNT 39,
  matrix static assert 49 (RACKET..CHIALISP)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 09:13:50 -07:00
Martin Vogel ae5de7fe0e Merge pull request #1920 from OhOkThisIsFine/claude/focused-herschel-ee8e1c
fix(daemon): contain zombie generations from abandoned requests, name mute endpoint holders
2026-08-31 14:56:11 +02:00
Martin Vogel 0a8a1f297a Merge pull request #1921 from OhOkThisIsFine/fix/daemon-runtime-uutils-image-donor
fix(tests): copy the runner, not /bin/cat, as the POSIX blocked image
2026-08-31 14:40:19 +02:00
Martin Vogel 8d7d3892b3 Merge pull request #1938 from ilyabrykau-orca/fix/go-import-symbol-fallback
fix(pipeline): never bind a Go import to a symbol
2026-08-31 14:31:47 +02:00
Martin Vogel a6468ed441 Merge pull request #1922 from CaptainMittens/fix/reject-out-of-scope-projection
fix(cypher): refuse a name that is not in scope instead of projecting a blank column
2026-08-31 14:28:17 +02:00
Martin Vogel 20e86c02b1 Merge pull request #1944 from ilyabrykau-orca/fix/go-bare-ref-field-binding
fix(pipeline): a bare Go reference never binds a struct Field
2026-08-31 14:12:27 +02:00