Copy inherited field-type pointer arrays into the writable registry arena
before Python refines an existing field. Keep shared type objects intact
and avoid retaining per-file scratch types in the sealed fallback.
Add deterministic shared-state and later-overlay coverage, retaining the
sequential Python field regression and adding a forced-parallel variant.
Refs #1277
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Use the Linux close_range syscall or BSD closefrom before exec, retaining
the bounded close loop when the range operation is unavailable. Add
coverage of descriptor closure, strategy selection, and forced fallback.
Refs #1484
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Preserve import kinds and resolve class imports through Composer prefixes
to the existing target class file, including autoload-dev mappings.
Refs #1186
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Carry declared instance-field types through extraction and result
compaction into the shared Python type registry so chained calls through
imported classes resolve their receiver types.
Refs #1277
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The client-detection fix moved the Windows Hermes home to
%LOCALAPPDATA%\hermes and stopped counting a bare ~/.claude directory as a
Claude Code install. The smoke script followed only part of the way.
Hermes: 8x and 8x-i read the new HERMES_HOME_DIR, but 8x-ii still looked
for the pre_llm_call hook in ~/.hermes/config.yaml, and the uninstall
absence checks 9m and 9n still named ~/.hermes. On Linux and macOS
HERMES_HOME_DIR is ~/.hermes, so the stale paths pointed at the right
directory and nothing showed. Only on Windows do the two differ: 8x-ii
failed on a hook that install had written to the new home, and 9m and 9n
passed without looking at any file install had produced. All three now use
HERMES_HOME_DIR. The assertions are unchanged.
Claude Code: the fixtures of 9b-2, 9b-3, 9b-8 and both Phase 13 legs
created only an empty ~/.claude, which is no longer a Claude Code home.
Wherever no claude executable is installed, 9b-2 then finds no .claude.json
entry and 13e finds no agent config, and 9b-3, 9b-8 and the install.ps1 leg
stop exercising the Claude paths without saying so. The Linux test
container reproduces both failures. The Windows job never got that far,
because 8x-ii stops it in Phase 8. The fixtures now seed the .claude.json
user config through one helper, the same seed the unit tests use.
9b-1b pins the new rule end to end: where the empty-home control shows no
Claude Code, a home with only a bare ~/.claude gets no Claude config. A
venue that has a claude executable skips it, with the reason at the case.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Laravel 11+ registers its route files in bootstrap/app.php through
Application::configure()->withRouting(web: ..., api: ..., apiPrefix: ...)
and mounts every file passed as `api:` under `apiPrefix` (default 'api').
That prefix lives in a different file than the routes, so per-file
extraction -- which already composes the in-file Route::prefix()->group()
chain (#952) -- minted `GET /users/me` for a route Laravel serves at
`GET /api/users/me`. The Route qualified_name is the join key for
cross-repo HTTP matching, so real callers of /api/... never linked, and an
api route could silently merge with a same-path web route.
Fix: a small Laravel mount resolver, src/pipeline/laravel_routing.c. For
each PHP file that registers a path-shaped route, both route passes (the
sequential calls pass and the parallel resolver) look up the nearest
ancestor bootstrap/app.php once, parse it with the PHP grammar, and, when
its withRouting(api: ...) names that file, prepend the apiPrefix to every
Route minted from it. Only source facts are used: an `api:` entry must be a
__DIR__ / dirname(__DIR__) concatenation or a base_path() call over a
string literal (single path or array), and a given apiPrefix must be a
string literal. A non-literal apiPrefix, positional arguments or a
`using:` callback leave the paths untouched, the `web:` file is never
prefixed, and nothing is inferred from file names. The lookup keys only on
the repo path and the file path, so full, incremental and delta runs
compose the same identity.
Proof (origin/main 1f9b4db1 vs this change, same machine):
- krayin/laravel-crm @03ebedd7: GET /user -> GET /api/user; the other
four Route nodes are unchanged.
- filamentphp/demo @d5b618a1: GET /user -> GET /api/user.
- laravelio/laravel.io @24be4893: the api.php POST '/' inside
prefix('articles') was merged into web.php's POST /articles node; it is
now its own POST /api/articles (39 -> 40 Route nodes, every web route
unchanged).
- Cross-repo: a TypeScript client calling axios.get('/api/users/me') and
axios.post('/api/orders') against a Laravel 11 server:
CROSS_HTTP_CALLS 0 -> 2.
Tests (edge_types_probe): default api prefix plus the composed
qualified_name, a custom multi-segment apiPrefix with the array form of
`api:`, the `web:` file stays unprefixed, a Laravel <= 10
RouteServiceProvider control stays as it is, a non-literal apiPrefix is
left alone, and the parallel resolver with the app in a monorepo
subdirectory. The four bug tests were RED 3x before the fix, pass with
it, and go RED again with only the composition reverted.
Fixes#1146
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The vendored tree-sitter-c-sharp (88366631d598) predated upstream's C# 12
collection-expression support (tree-sitter-c-sharp #402, first released in
v0.23.4). A `[...]` literal could only be parsed as an
element_binding_expression, so `c ? [] : x`, wrapped `? []` ternaries and
both-branch collection conditionals had no valid parse. Error recovery then
ate the surrounding statement, the file was reported as parse_partial, and
nearby declarations could fall out of the graph.
Refresh the generated grammar bundle byte-for-byte from upstream tag v0.23.5
(cac6d5fb595f5811a076336682d5d595ac1c9e85). The ABI stays at 15, the MIT
LICENSE is byte-identical, and scanner.c is content-identical. The only new
node types are collection_expression, collection_element,
expression_element and spread_element. No node type is removed or renamed,
and the C# extractor and LSP depend on none of the old or new names.
MANIFEST.md and the vendored checksums are updated.
Add parse_coverage regressions for the reported variants plus a `? null`
control. A second test checks that calls inside a collection expression
(element and spread) are still extracted. A malformed-source guard checks
that broken C# is still flagged.
On dotnet/runtime (60108ba6), 4 files are no longer parse_partial and no
file becomes newly partial. Utf8JsonWriterTests.cs gets its class back, and
216 orphaned Functions are re-attached as Methods. Two files that were
already partial for unrelated unsupported syntax (the preview `closed`
modifier, `async` as an identifier) recover differently and lose 46 and
10 definitions.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
With #2134, an unnamed index_repository of a root that another indexed
project already owns adopts that owner's name, and the daemon keys the
job by it (application_index_project_key reads the forwarded "name").
The unnamed status poll that the #2144 async reply asks for ("the same
repo_path and status: true") still derived its key from the path alone
in index_status_project_key. For an owned root it never found the job
it had just started and answered "no index job or index is known for
project '<path-derived>'"; an ambiguous root got that same misleading
answer instead of the loud error the start returns.
index_status_project_key now runs the start path's
index_root_owner_resolve on the same canonical repo_path: an explicit
name still wins, a single other owner is the key, several owners fail
with the same error naming them, otherwise the path-derived key stays.
The status error buffer grows to hold that owner list.
The daemon's other readers of the key (job dedupe in
application_job_subscribe_locked, the synchronous cut-short marker set
and taken around application_index_run_sync) derive it from the
forwarded args, which already carry the owner name, so they agree with
the start; status was the only reader that diverged.
Refs #2134, #2144
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Overloads that share a base QN (Java/C#/C++/Kotlin/Swift/Scala/ObjC) merge
into one graph node today, and that node collects the CALLS of every
overload. The agreed cure is one signature-qualified callable identity for
all overloading languages:
callable_qn := base_qn [<tparams>] (params) [cvref]
This change lands the plumbing with every language still at
CBM_CALLABLE_ID_NONE, so the graph is byte-identical; each language is
switched on later in its own change (with its index-format bump).
- internal/cbm/callable_sig.{c,h}: the per-language mode side table
(NONE|TYPED|LABELED_TYPED|LABELED|ARITY; kept out of CBMLangSpec like
cbm_string_dispatch_suffixes, since ~160 positional rows would otherwise
all need the member under -Wmissing-field-initializers), the shared
builder cbm_callable_sig[_mode] for the def and the call-scope QN, and
the inverse cbm_qn_callable_base_len[_named]. Suffix contract: no '.',
'::' or '->'; whitespace-normalized; comments/annotations/attributes
dropped; qualified types keep their last segment; '->' spelled '=>';
variadic '[]' (Java) / '~'; C++ top-level cv dropped, arrays decay,
cvref kept; <= 200 bytes, longer ones keep whole leading entries and end
in '#<fnv1a64 of the full suffix>)'. The builder is iterative (cursor
walk, bounded declarator loop) and self-checks against the inverse.
- The inverse is anchored to the node's bare name wherever the name is
known: real QNs already end in ')' without being callables (a Java field
whose name is its declarator `X = f()`, make `$(VAR)` targets), and only
the anchor makes the inverse exact on them.
- Leaf splitters strip a suffix first (no-op today): registry by-name key
(indexed by the bare name for a suffixed callable), cbm_lsp_bare_segment,
cbm_pipeline_qn_class_method_tail, pxc_last_component, pxc_qn_leaf,
go_index_visit_node, collect_prod_defs, sg_qn_prefix_len.
- New unused fields: CBMDefinition.qn_sig_off, CBMResolvedCall.callee_sig
(relocated by result compaction), CBMRegisteredFunc.sig, CBMLSPDef.sig.
The lsp_surface codec is unchanged (no format bump in this change).
- get_code_snippet and trace_path gain base-QN tiers
(cbm_store_find_nodes_by_qn_base) that find nothing until suffixes exist.
- store: cbm_store_find_nodes_by_qn_suffix built its LIKE pattern in a
512-byte buffer (a long suffix was truncated into a pattern that matched
nothing) and did not escape '_'/'%' (`my_func` also matched `myXfunc`).
The pattern is now heap-built, escaped, and matched with ESCAPE '\'.
Proof: full sorted node+edge+lsp_surface dumps are byte-identical before
and after on 7 corpora (this repo, elasticsearch libs, Exposed, dotnet
System.Linq, System.Collections, coreclr/gc, a C corpus).
Refs #2061
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Every index is written with 64 KiB pages (sqlite_writer.c), but read-write
connections kept SQLite's default 2000 KiB page cache, which holds about 31
of those pages. A closure-repair delta purges and patches rows across the
node and edge tables and all of their indexes, so the transaction kept
spilling dirty pages into the WAL and rewriting them in place. A one-line
body change on a 294 MB django index wrote 5.2 GiB to the WAL (about 75k
in-place page rewrites) and 5.7 GiB of logical writes in total. Read-write
on-disk connections now use a 64 MiB cache (1024 pages, the budget bulk mode
already used), and end_bulk restores that instead of the 2000 KiB default.
Query connections keep the default.
cbm_pipeline_publish_staged also forced synchronous=FULL on the private
staging database, so every metadata and FTS commit fsynced the WAL. A crash
before the atomic rename discards the stage, and
cbm_store_seal_for_atomic_publish() still raises the connection to FULL for
the TRUNCATE checkpoint and DELETE-mode switch that make the published file
durable, so those per-commit fsyncs protected nothing. The commits now run at
the store's NORMAL level, which SQLite documents as corruption-safe under
WAL. No corruption or atomicity guarantee changes: the live database is
still only replaced by a sealed, integrity-checked file via rename.
Measured on macOS, main vs this change, same machine and corpora:
- django one-line edit (closure repair): WAL writes 5.2 GiB -> 0.52 GiB,
logical writes 5.76 GiB -> 1.08 GiB, WAL fsyncs 13 -> 8
- django full index: WAL writes 58 MiB -> 47.5 MiB, WAL fsyncs 9 -> 2
- php (65 MB index) one-line edit: WAL writes 82.5 MiB -> 32.3 MiB, spill
rewrites 468 -> 0
- kubernetes (1.7 GB index) full index: WAL writes 669 MiB -> 390 MiB, WAL
fsyncs 13 -> 6
The comments that put a healthy WAL at ~4 MiB assumed 4 KiB pages; at 64 KiB
the 1000-page autocheckpoint is ~64 MiB, still well under the 256 MiB
journal_size_limit.
Tests: store_pragmas/store_role_pragmas_issue1419 pins the effective pragmas
of each store role (read-write, bulk, seal, query), and
pipeline_semantic_manifest_repro/
pipeline_staged_publish_commits_below_full_sync_issue1419 reads the effective
synchronous level from a WAL hook on every connection that a full index plus
a closure-repair reindex opens, and requires that no commit runs at FULL.
Refs #1419
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Git exports repository-local variables (GIT_DIR, GIT_INDEX_FILE,
GIT_WORK_TREE, ...) into hooks, and they take precedence over
`git -C <dir>`. Every fixture git command in the test runner therefore
acted on the caller's real repository whenever the suite ran with those
variables set: with GIT_DIR alone, fixture assertions failed and commits,
branches and linked worktrees were written into the caller's clone; with
the full hook set, fixture commits failed and git-dependent tests degraded
to "git not available" SKIPs. The pre-commit hook already clears the
variables (7883507d), but any other caller (a shell with GIT_DIR set, an
editor hook, `GIT_DIR=... scripts/test.sh`) still hit it.
The test runner and the repro runner now clear git's local-env list
(`git rev-parse --local-env-vars`) once at startup, before any fixture or
re-exec'd child spawns git, so every fixture scopes to its own repository
no matter who invokes the suite.
tests/test_git_env_isolation_contract.sh (Step 3a of scripts/test.sh)
drives the real runner's git_context suite with GIT_DIR, and with the full
hook set, aimed at a throwaway decoy repository. It requires the same
pass/skip summary as a clean run and a decoy left untouched (no refs, no
linked worktrees).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The memory-core ratchet flagged src/pipeline/pass_pkgmap.c growing by 2 raw
allocator sites (82 -> 84). Both are frees of strings returned by
cbm_pipeline_fqn_compute and cbm_pipeline_resolve_module, which the core
never counted, so they now go through safe_free as the rest of the codebase
does rather than raw free or cbm_free. Behaviour is unchanged;
pass_pkgmap.c is back at its baseline of 82.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
`from unittest.mock import patch` names a module outside the project.
Strategy 1 of cbm_pipeline_resolve_import_node cannot resolve it, so the
Strategy-3 symbol-name fallback bound the import to the only project
definition whose leaf is `patch` -- a REST view's PkgConfigView.patch --
and every `patch(...)` call became an import_map CALLS edge at confidence
0.95. Once that IMPORTS edge is gone the same call falls through to the
weak short-name strategies (unique_name 0.75, suffix_match), which bind it
again; `mock.patch(...)`, `json.loads(...)`, `os.path.join(...)` reached
project methods the same way, because the member guard exempts
import-rooted receivers.
Two per-language (Python-only) rules, both keyed on the import's module
chain rather than on any spelling list:
- Import side (pass_pkgmap.c, shared by both drivers): for an import
whose module is not in the project, a Strategy-3 hit must spell the
import's module chain as an ordered subsequence of its QN (a sys.path
root or src/ layout the resolver missed still qualifies), and whatever
names a module (an enclosing path segment, any segment of a plain
`import x`) may only bind a Module/File node. Imports of project
modules are unchanged (they may re-export from anywhere).
- Call side (pass_calls.c + pass_parallel.c, identical gates): a call
whose root identifier is bound by an external import (no IMPORTS edge
materialized it) and whose weak-strategy target contradicts that
import's chain is not emitted as a plain CALLS edge. import_map /
same_module / lsp_* edges are never touched.
django/django (parallel pipeline): CALLS 62,430 -> 60,123, TESTS
29,872 -> 28,571, IMPORTS 16,068 -> 15,491. Removed CALLS are
member calls through stdlib imports (1,900: os.path.join ->
defaultfilters.join, json.loads -> signing.loads, mock.patch ->
RedirectView.patch) and bare calls of stdlib names (398: date ->
defaultfilters.date, urlsplit -> MultiPartParser.parse); a random
25-edge sample was 25/25 false. Six IMPORTS are re-targeted to the real
definition (template_tests.utils.setup, multiple_database TestRouter,
pr_quality errors.Message).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>