Commit Graph
1726 Commits
Author SHA1 Message Date
Martin Vogel ea2a8f185f Merge main into fix/c-typedefs-macros-enumerators for the release batch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-08 01:31:33 +02:00
Martin Vogel 1b581f1ba3 Merge pull request #2346 from DeusData/feat/cross-repo-status
feat(cross-repo): report run status and skipped legacy stores (#2133)
2026-10-08 01:31:14 +02:00
Martin Vogel 60f62538ab Merge pull request #2490 from DeusData/fix/workspace-manifest-resolved-entries
fix(workspace): classify manifest entries in their resolved form
2026-10-08 00:41:53 +02:00
Martin Vogel 1aabc40af1 Merge pull request #2465 from DeusData/fix/laravel-array-invokable-handlers
fix(routes): HANDLES for Laravel array and invokable controller actions
2026-10-08 00:03:09 +02:00
Martin Vogel 279cd73a90 Merge pull request #2476 from DeusData/fix/ui-index-start-resolved-root
fix(ui): index job carries the resolved root; status body sized for it
2026-10-07 23:41:58 +02:00
Martin Vogel 625f8a6211 Merge main into fix/laravel-array-invokable-handlers for the release batch
Conflict in tests/test_edge_types_probe.c: both sides added a self-contained helper + tests before the Rails test (this branch: et_handles_exact_routes and the class-handler tests; main/#2471: et_route_set_exact and the slashless-route tests) and their RUN_TEST lines; both blocks kept whole, this branch's first.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 18:34:30 +02:00
Martin Vogel d050fcae35 Merge main into fix/c-typedefs-macros-enumerators for the release batch
Conflicts: internal/cbm/extract_defs.c includes (union of both sides); src/pipeline/registry.c keeps main's lang-aware index_under_name call, and the fence comment combines both facts (Rust cfg twins are variants of one QN; a C macro's #macro QN never reaches the registry).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 18:28:07 +02:00
Martin Vogel d19cab8592 Merge pull request #2461 from DeusData/fix/issue-1171
fix(artifact): write and detect artifacts through wide paths on Windows (#1171)
2026-10-07 18:24:05 +02:00
Martin Vogel 1d7c807136 Merge main into fix/workspace-manifest-resolved-entries for the release batch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 18:20:14 +02:00
Martin Vogel 7312f96bab Merge main into fix/ui-index-start-resolved-root for the release batch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 18:20:00 +02:00
Martin Vogel a10582e0d5 Merge main into fix/laravel-array-invokable-handlers for the release batch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 18:19:48 +02:00
Martin Vogel 515670bff8 Merge main into feat/cross-repo-status for the release batch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 18:19:35 +02:00
Martin Vogel 4cea83d385 Merge main into fix/issue-1171 for the release batch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 02:20:18 +02:00
Martin Vogel 6ea6d90bd4 Merge main into fix/issue-1717 for the release batch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 01:53:57 +02:00
Martin Vogel e775c7f511 fix(subprocess): a child that is exiting at the group check is a finished run
macOS answers ESRCH to setpgid and getpgid for our own unreaped child from
the moment it starts to exit, before waitid can report it. a5c7ae97 accepted
a child that had already exited, probed with waitid(WNOHANG|WNOWAIT); in the
window before the child is reapable that probe said "not exited", and the
spawn was reported as SPAWN_FAILED errno 3. macos-15-intel CI kept hitting it
in a different Git-backed fixture on each run; with the new diagnostics it
named itself: `git config --local core.protectNTFS false`, outcome=5 errno=3.

A child of ours that setpgid/getpgid cannot find is not running: it has
exited or is exiting. The parent now waits until it is reapable (WNOWAIT
leaves it for the supervision, which reaps it with its real exit status);
the wait ends with the child's exit. EACCES/EPERM keep the group check.

A test seam makes the parent observe the exiting state (ESRCH, not yet
reapable): subprocess_run_child_exiting_at_group_check is SPAWN_FAILED
without this change and exit status 3 with it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-06 21:42:18 +02:00
Martin Vogel dd598ef426 fix(test-impact): a changed code line is never a comment line
Seeding drops a hunk whose lines are all comments. The check took any line
starting with `*` for a block-comment line and any line ending in a block
comment's close for a comment, so a dereference (`*out = f(a, b, true);`)
and code with a trailing comment counted as comments and their edits seeded
nothing.

On the oracle, PR #2158 changed two `*out_* = extract_route_path_from_args(...)`
lines in scan_route_annotations. Both hunks were dropped, the function was
never a seed, and its coverage id never reached the changed set. The six
artifact_reconcile_* tests that execute it, recorded as complete coverage rows
of the merge-base map, were missed (6 per job, two jobs: the only misses
among 191 jobs). With the check fixed, all six are selected for COVERAGE
again; until now the new graph walk had reached them as STATIC only by
chance.

A line now counts as a comment only when it is nothing but comment: blank,
`//`, a block opened with nothing after its close, `*` followed by
whitespace or the end, a lone close, or the closing line of a block opened
earlier. `*name`, `**`, `*(` and code before a trailing comment are code.
When unsure the check says code, which only seeds more.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-06 18:07:16 +02:00
Martin Vogel ac80a6a6a8 fix(spawns): keep the security audit's system( pattern out of a comment
scripts/security-audit.sh blocks any `[^a-z_]system(` in a source file that
is not on the allow-list, comments included. A comment in pass_spawns.c
spelled a C call of `system(...)`, so security-static failed on the SPAWNS
pass, which never calls it. The comment now names the library function
without the call syntax; no allow-list entry, since there is no call.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-06 18:07:16 +02:00
Martin Vogel bb1f6238e7 fix(test-impact): report a Windows case collision as a collision
A tree with a file `A` and a directory `a` cannot be materialized on a
case-insensitive volume. On Windows, creating the file over the existing
directory answers ERROR_ACCESS_DENIED, not ERROR_FILE_EXISTS, so the pinned
tree reported an I/O failure where tree_d_native_collision_and_exact_spelling
expects CBM_PINNED_TREE_COLLISION (observed: error 5 on the create).

An access-denied create whose attempted name is present is now the same
collision; one whose name is absent stays an I/O failure.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-06 05:58:39 +02:00
Martin Vogel 4f709c2707 fix(test-impact): accept the owner-only DACL as Windows stores it
The pinned tree requires its private parent to be owner-only: a protected
DACL, the current user as owner, one full-access grant to that user. It
accepted only the single-entry encoding (OI|CI). cbm_mkdir_p stamps a fresh
directory with exactly that grant, but written with generic rights, and
Windows stores it as an effective entry plus an inherit-only one
(icacls: user:(F) and user:(OI)(CI)(IO)(F)). The work directory production
creates was therefore refused ("pinning HEAD failed: protected owner-only
DACL policy mismatch"), and every Windows selection ran everything.

Both encodings are now accepted: one or two entries, each an allow entry for
the current user with full access and only inheritance flags, at least one
applying to the object itself. Any other principal, deny entry, partial
mask or flag is still refused.

tree_e_windows_split_owner_dacl_is_owner_only creates the parent with
cbm_mkdir_p and pins on it (red before this change), pins on the split form
written explicitly, and is refused once the inherit-only entry names Everyone.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-06 05:58:39 +02:00
Martin Vogel 2cbd25dabf fix(test-impact): read MSYS2 and Cygwin git's metadata paths on Windows
Git facts ask `git rev-parse --path-format=absolute` for the top level, the
git directory and the common directory, and use them for native file checks
(grafts, shallow). MSYS2 git -- the git the Windows legs install -- answers in
its own namespace (/c/Users/...), which is not a Windows absolute path, so
every git-facts open failed with "Git metadata path is not absolute" and the
engine ran everything (41 test_impact_git failures).

The drive form maps one to one onto C:/Users/... (Cygwin: /cygdrive/c/...),
and is translated. Any other path of that namespace (/tmp, /usr) needs the
runtime's mount table and is still refused.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-06 05:58:39 +02:00
Martin Vogel a5c7ae9789 fix(subprocess): a child that already exited is a finished run, not a failed spawn
After posix_spawn the parent re-asserts the child's process group. A short
command can exit before the parent gets there; macOS then answers ESRCH for
the exited, unreaped child to both setpgid and getpgid, and the run was
reported as SPAWN_FAILED (errno 3). macOS CI read `git write-tree` that way
under load (macos-15-intel and the LeakSanitizer leg).

The group was already set before exec, by the spawn attributes or by the
child itself, so an exited child is accepted when waitid(WNOWAIT) shows it
waiting to be reaped; the supervision then collects its real exit status.

A test seam holds the parent until the child has exited, which makes that
order deterministic: subprocess_run_child_exited_before_group_check is red
without the fix (SPAWN_FAILED) and green with it, on macOS and Linux.

The test file is also reflowed to the project's clang-format, as on main.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-06 03:19:26 +02:00
Martin Vogel 42a9320200 graph: one identity per conditional definition, SPAWNS edges, no cross-language guesses
The test-impact engine selects tests by walking the graph, so it can only
be as right as the graph. Three gaps made it wrong:

One identity for a definition's mutually exclusive variants
- A definition written once per #if/#else branch, per #[cfg], per
  platform file (foo_linux.c / foo_win.c, platform directories) or per Go
  build tag is ONE node. The graph buffer keeps every variant's span in a
  `variants` node property (definition labels only), so a change in any
  variant maps to the definition.
- Extraction now sees definitions inside conditional blocks where it
  skipped them: C++/C#/ObjC class members under #if (one cursor walk,
  conditional block types per language in lang_specs), Elixir defs under
  if/unless, Fortran .F90/.F95/.F03/.F08, Rust #[cfg] twins (no more
  `name#cfg(...)` QNs that lost every call), C platform files and
  directories (non-static file-scope functions).
- Haskell: the vendored scanner reads only an #if group's first branch.
  The grammar stays upstream; every further branch is parsed as its own
  same-length view (cpp_branch_views.c) and adds what the raw parse
  missed. haskell/bytestring: no new partial parses, +27 functions,
  IMPORTS 322 -> 329, 131 definitions with variant spans.
- Incremental closure repair re-parses every file of such a definition
  when one of them changes (variant partners through DEFINES), so a
  repair equals a full index.

No cross-language name guesses
- A name-only resolution (unique_name, suffix_match, qualified_suffix,
  field_type_hint, and same_module across files that only share a path
  stem) never binds a symbol another language defines.
- Bash imports are the files a script sources: every top-level command
  was read as an import of its name (`set -e` imported "set", bound to a
  TSX method in 137 scripts here).
- This repository: cross-language CALLS 5,916 -> 5,091; the remainder are
  C/C++ pairs, builtins and same-stem module collisions (recorded).

SPAWNS edges (per language, like the HTTP client patterns)
- A call that starts a program (subprocess.run, exec.Command,
  child_process.spawn, posix_spawn, system, Process.Start, Command::new,
  ... about 30 languages, through the file's own imports) gets a SPAWNS
  edge to a Process node named after the program (literal first word, or
  <dynamic>) instead of a CALLS guess. No target resolution. Both
  resolvers ask at the same point (parity test).

The engine follows the graph
- Seeds map changed lines through every variant span; a test written
  once per #if branch is one test; when a change reaches a non-test
  `main`, the tests that spawn processes are reached too (the process
  runs main, and main reaches everything).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-06 02:44:56 +02:00
Martin Vogel 6a29a19c23 fix(test-impact): build and run on Linux GCC, git before 2.44, LeakSanitizer
PR CI on the first push of these commits found what no local leg had
built yet:

- test_impact_result_validate.c: initialize the count GCC's
  -Wmaybe-uninitialized flags (-Werror on every Linux build: smoke,
  shadow, analyze).
- tests: split one-line `if` chains GCC reports as misleading
  indentation, and drop a redundant `= {0}` it reads as missing braces
  (the GCC test runner did not compile).
- tests/test_test_impact_engine.c: realpath() into a PATH_MAX buffer;
  glibc's _FORTIFY_SOURCE aborts on anything smaller, as
  test_index_resilience.c already notes.
- test_impact_git.c: pass --no-lazy-fetch only when the git executable
  takes it (git 2.44+, probed once per session with the option on
  --version). Older git rejected it and every engine command failed;
  Ubuntu 24.04 ships git 2.43. `-c protocol.allow=never` already turns a
  lazy fetch into an error there, which is what the option asks for.
- test_impact_engine.c: the selection and publish job threads release
  their thread-local node-type bitset cache before they end, as the
  parallel workers and the MCP index thread do. LeakSanitizer reported it
  (488 bytes in 13 allocations, test-diag); the Linux arm64 ASan leg
  reproduced exactly that and is clean with the fix.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-05 23:55:25 +02:00
Martin Vogel 867e5f883d Merge origin/main into the smart-CI branch
Ten files changed on both sides; every conflict was a union of independent
additions:

- workflows (_lint.yml, _test.yml): keep the branch's lane `if:` and main's
  pinned ubuntu-24.04 runner with its reason.
- internal/cbm/cbm.h: main's callable-identity and module-doc fields plus the
  branch's configured-test fields.
- Makefile.cbm: both new test files; prod_lsp_all.o keeps main's
  .build-config dependency and the branch's coverage flags.
- scripts/test.sh: main's setup-scripts contract becomes Step 0y3, ahead of
  the branch's 0z series and the contracts-only exit.
- src/foundation/subprocess.c: main's cbm_posix_close_fds_from (it closes the
  error descriptor too).
- src/mcp/mcp.c: main's repository default base for detect_changes, with the
  early returns released through the branch's detect_release.
- src/pipeline/lsp_resolve.h, tests/test_main.c: both includes, suites and
  start-up steps (main's git environment reset first).
- scripts/memory-core-baseline.txt: recounted on the merged tree and lowered
  (mcp.c 773, config_toml_edit.c 73).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-05 18:53:37 +02:00
Martin Vogel 36e5874525 feat(test-impact): team artifact bundles and admitted coverage
A main commit can now publish its test-impact bundle — the commit's frozen
graph, its per-test coverage map and receipt.json binding both — and a PR
selection given the bundle of its merge base, verified by the caller,
builds its graph incrementally from the bundle's graph and narrows suites
by the admitted map. Without a verified bundle nothing changes: the graph
is built from scratch and every reached suite runs whole.

Producer and consumer:
- `codebase-memory-mcp test-impact publish|select` (in process, no
  daemon): publish builds the bundle of the checked-out commit, from the
  previous bundle's graph when that bundle verifies; select is the
  selection with optional --artifact/--artifact-commit/--artifact-verified.
  The MCP surface is unchanged.
- receipt.json binds the exact imported graph (graph digest), the graph's
  content and topology, the map's three files and a compatibility digest
  (platform label + the bytes of test_impact.coverage.compatibility_paths).
- A bundle is used only when the caller verified its source, it is the
  merge base's own, and its imported graph is the one the receipt names;
  the map is admitted by the existing receipt policy (source, ancestry,
  content, age, compatibility, metadata). Refusals become rejection
  reasons in the answer's receipt.
- C_eff: a changed function the map knows contributes its own id; one it
  does not know (new, platform-only, a type or macro) contributes its
  nearest mapped callers on the static inbound closure; deleted names
  contribute every mapped function of that name.

Graph:
- the frozen build accepts a base candidate and repairs it through the
  incremental closure route; any doubt declines to the full build. Pinned
  to one worker like the full frozen build.
- incremental closure repair skipped the interprocedural complexity pass:
  re-parsed functions lost transitive_loop_depth/recursive and callers kept
  stale values. One traversal now serves the in-memory pass and a new
  store-level recompute that runs before the importance rescore.
- test-impact's frozen builds use a fixed display Git context: the per-run
  pin path and the commit ids made two builds of one tree differ and made
  every commit a semantic-input change, so repair always declined.
- graph content and topology digests (store_content_digest.c): the exact
  graph digest binds one database (ids, db_uid, generation), never equal
  across builds; the content digest keys rows by what they say, the
  topology digest by what selection reads.

Measured on this repository (frozen publish, macOS arm64): a two-file C
change repairs in 18-20 s instead of 75-80 s with the topology of the full
build; a docs-only change repairs with identical content; selections with
and without the bundle are identical on all seven replayed pairs
(two repaired incrementally, five declined to the full build). Recorded, not
fixed: closure repair still differs from a full build in edge PROPERTIES
(argument quoting and key order, CONFIGURES args) and in the corpus-wide
SIMILAR_TO/SEMANTICALLY_RELATED edges; selection reads neither, so the
topology digest is the incremental-equals-full check and the receipt
carries both.

Tooling: scripts/test-impact/coverage-merge.py builds a push's incremental
coverage map (re-run suites verbatim, the rest carried with ids remapped
onto the new image's function table), with its contract as test.sh Step
0z7.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-05 18:52:26 +02:00
Martin Vogel c5abb64943 feat(test-impact): detect_changes scope:"tests" end to end
One request pins the change (merge base .. HEAD), builds the graph and the
runnable test registry of exactly the HEAD snapshot, seeds the graph from
the change, walks what depends on it and answers which tests to run
(cbm.test_impact). Missing or doubtful evidence never narrows: it becomes a
run-all answer that names why.

Snapshot (classified ledger -> frozen graph -> registry):
- one shared language classifier for discovery and pinned blobs (raw bytes
  on every platform); the classified ledger feeds the frozen build, which
  indexes exactly the listed files and records partial parses as per-file
  PARSE_GAP instead of refusing the graph;
- the pinned tree skips symlinks and submodule links (counted), as
  discovery never indexes them;
- the test model scopes uncertainty to the suites of its file (they run
  whole), reads line splices where they occur, treats a mid-file
  `#ifndef X` + `#define X` as an ordinary condition, and no longer reads a
  plain identifier named like a macro as an invocation. On this repository
  the registry is now narrowable and matches a text census exactly
  (8,365 cases, 141 runner suites).

Seeding and reach (port of the measured replay, rule B):
- hunk -> overlapping definitions, comment-only hunks dropped, pure
  deletions -> enclosing definition, no-definition hunks -> whole file,
  deleted names found by text, header changes propagated through the
  include closure and the headers' macros (pastes included), file-level
  escalation for unclassifiable header lines;
- inbound fixpoint walk over CALLS, ASYNC_CALLS, USAGE, CALL_REFERENCE,
  READS (+ routes); cases, suites and the runner's entry points are sinks;
  a reached runner main runs everything (RUNNER_REACHED);
- a parse gap whose text names a reached definition reaches the
  definition around it (or its file);
- macro-registered tests run their suites whole; cases and suites map to
  graph nodes by their start line.
The line patterns reproduce the reference regexes exactly on 1,383,850
lines of this repository and 37,167,926 kernel lines; on 24 replayed
changes the engine selects at least what the reference selected (13
identical, the rest only conservative supersets on the same graph).

Also:
- configured test declarations that cannot be mapped degrade their file
  (no configured roles, a test_declarations diagnostic) instead of
  aborting the whole index;
- the selector keeps other runners' suites out of the selection, and the
  result codec judges the model by narrowability like the selector;
- a change to .codebase-memory.json runs everything (built-in rule);
- .codebase-memory.json with the c-cbm preset and this repository's rules.

Gate fixes (suites, lint-ci, Windows cross-compile):
- the request runs on its own thread with a default-size stack (daemon
  tool calls run on 256 KiB threads, where the in-process build died);
- a request-supplied config_path is no longer followed by an unlink of an
  uninitialized path buffer;
- the inventory's frame-local reader no longer stays reachable from the
  caller's source after the filter call;
- FILE_ID_INFO needs _WIN32_WINNT >= 0x0602; the pinned-tree header pins
  it before any system header (toolchains defaulting lower failed);
- memory-core ratchet: the win_utf8 converters gain core-backed
  `_in(cls, ...)` forms over one implementation (the legacy names keep
  the C library for unmigrated callers), the Git facts module uses them,
  and the Windows spawn path releases everything through one failure
  exit; pipeline, extraction and config allocations that this work
  touched moved to the core in matched pairs; baselines tightened;
- the branch's C files formatted with the project's clang-format.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-05 02:15:08 +02:00
Martin Vogel 70e73442ed fix(routes): leave a PSR-4-covered handler without a class file unresolved
Why: when a composer.json PSR-4 prefix covers a handler class but no
class file exists for it, cbm_registry_resolve_handler fell through to
the namespace/folder fallback and bound the route to a same-named class
in any folder that mirrors the namespace, a class composer never loads.
Main's import resolver stops in that case: resolve_php_psr4_class leaves
the `use` import unresolved (#1186). The handler now does the same, so
no guessed HANDLES edge is minted. Classes that no PSR-4 prefix covers
keep the fallback unchanged.

Change:
- pipeline_internal.h: cbm_pipeline_psr4_member_qn returns a tri-state
  cbm_psr4_member_t (NOT_COVERED, RESOLVED, UNRESOLVED) and hands out
  the member QN through an out parameter, set only when RESOLVED.
  UNRESOLVED means a prefix covers the class but no class file holds
  the class with that member: the file is absent, or it lacks the class
  or the member.
- pass_pkgmap.c: maps the outcome of resolve_php_psr4_class onto it;
  the class/member lookup in the resolved file moves into
  psr4_member_in_file.
- registry.c: cbm_registry_resolve_handler returns unresolved on
  UNRESOLVED and runs the folder fallback only on NOT_COVERED.

Proof:
- New handles_laravel_psr4_absent_class_issue1146 and its _parallel
  twin (edge_types_probe) extend the issue-1146 fixture with a route to
  \Acme\Blog\Http\MissingController::index (covered by Acme\Blog\ ->
  packages/blog/src/, no class file) and one to PostsController::archive
  (class file exists, no archive method), plus namespace-mirroring
  decoys in legacy/Acme/Blog/Http that hold both members. The test
  asserts exactly the 4 HANDLES of the fixture and that both Route nodes
  exist. RED before the change on both paths with the 2 decoy edges
  (legacy...MissingController.index -> /blog/missing,
  legacy...PostsController.archive -> /blog/archive; expected=4
  actual=6; suite 77 passed, 2 failed). GREEN after (79 passed). RED
  again with the production change reverted (the same 2 edges), GREEN
  once it was restored.
- Real input: koel/koel v9.11.1 (App\ -> app/) indexed with the new
  binary under a private HOME, cache and runtime: 16,564 nodes, 60,177
  edges, 15 Routes and 1 HANDLES (/demo/new-session ->
  NewSessionController.__invoke), identical to the numbers recorded for
  42118ad1.
- Suites: edge_types_probe 79, edge_imports 68, pipeline 317 and
  route_canon 11 all pass (475). make lint-ci passes.

Refs #1146

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-05 01:37:11 +02:00
Martin Vogel 42118ad142 fix(routes): resolve PSR-4 handler classes through the class-file resolver
Why: since 830f0ccf (#2509) the package map stores a composer.json PSR-4
prefix ("Acme\Blog\") as its repo-relative directory ("packages/blog/src"),
not as a dotted QN root. The route-handler resolver still formatted that
value as a QN root and built "packages/blog/src.Http.PostsController.
PostsController.index", which names no node. The PSR-4 step therefore
resolved nothing. Handlers whose folders mirror their namespace
(App\Http\Controllers -> app/Http/Controllers) still got their edge through
the namespace/folder fallback, but a package class under a root whose folder
does not mirror its namespace got no HANDLES edge. That is the case of
packages/blog/src in the test fixture and of krayin's
Webkul\Admin\ -> packages/Webkul/Admin/src.

Change: resolve the handler class the way the import pass resolves
`use Ns\Class;` for PHP.
- pass_pkgmap.c: psr4_file_node and resolve_php_psr4_class take the graph
  buffer instead of the pipeline ctx, since they only read ctx->gbuf. The
  new cbm_pipeline_psr4_member_qn runs resolve_php_psr4_class for the class
  FQN, takes the Class/Interface/Trait/Enum node of that name defined in the
  resolved file, and returns the QN of <class QN>.<member> when that node
  exists.
- registry.c: psr4_member_qn delegates to it and no longer builds a QN from
  the package-map value. cbm_registry_resolve_handler takes the graph buffer
  in place of the package map. The namespace/folder fallback is unchanged.
- pass_calls.c and pass_parallel.c pass the graph buffer they already use
  for the handler lookup: ctx->gbuf, and main_gbuf, which is read-only
  during the resolve phase.

Proof:
- handles_laravel_class_handlers_issue1146 and its _parallel twin
  (edge_types_probe): RED 3/3 before the change ("missing
  PostsController.index -> /blog", expected=4 actual=3; suite 75 passed,
  2 failed). GREEN 3/3 after (77 passed). RED again with the production
  change reverted. GREEN again once it was restored.
- Real input: no Laravel app with a non-mirroring PSR-4 root is available
  locally. koel/koel v9.11.1 (App\ -> app/) was indexed with the binary
  before and after, each under a private runtime and cache. The results
  are identical: 16,564 nodes, 60,177 edges, 15 Routes and 1 HANDLES
  (/demo/new-session -> NewSessionController.__invoke), with the same
  handler rows.
- Suites: edge_types_probe 77, edge_imports 68, pipeline 317 and
  route_canon 11 all pass (473). make lint-ci passes.

Refs #1146

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-04 21:11:49 +02:00
Martin Vogel 64f5e918bb Merge pull request #2256 from DeusData/distill/1245-int-arg-range
fix(mcp): an integer argument outside int range takes the default
2026-10-04 21:01:13 +02:00
Martin Vogel 8ab555ee38 Merge pull request #2253 from DeusData/distill/1245-cypher-partial-where
fix(cypher): stop the early WHERE pruning rows it cannot judge yet
2026-10-04 21:01:04 +02:00
Martin Vogel 4e490bef5a fix(routes): HANDLES for Laravel array and invokable controller actions
Why: Laravel names a route's controller as [UserController::class, 'show'],
or, for an invokable controller, as GetCurrentUserController::class. The
route handler scan only took identifiers and strings, so these routes never
got a HANDLES edge. The cross-repo HTTP matcher reaches a route's handler
through that edge, so apps written this way - the #1146 reporter's app
among them - produced 0 CROSS_HTTP_CALLS. Resolving the class by short name
is not enough either: the call pass's import map bound `use
App\Http\Controllers\UserController` to Admin\UserController by name, even
with a composer.json PSR-4 map present, and the generic resolver's
multi-candidate fallback bound a vendor controller's show() to another
class's show().

Fix:
- extract_calls.c: for PHP, [X::class, 'm'] becomes the handler reference
  "Ns\X::m" and X::class becomes "Ns\X::__invoke". X is qualified the way PHP
  resolves it: through the file's `use` imports (aliases included), else
  the file namespace; a leading backslash is already absolute.
- registry.c, cbm_registry_resolve_handler (both route passes): a
  class-qualified handler is placed on a method QN only by what places the
  class file - the composer.json PSR-4 root in the package map, else the one
  candidate whose folders mirror the namespace (App\Http\Controllers ->
  app/Http/Controllers). A class that is not in the repo gets no handler.
  A handler reference without "::" resolves exactly as before.

Proof (release CLI built from this tree vs origin/main 1f9b4db1):
- laravel.io: HANDLES 0 -> 4, one for each of the 5 route registrations
  main mints (two share a Route). The use statement tells
  Articles\ArticlesController apart from Admin\ArticlesController.
  Route count unchanged (39). With the slashless-path and withRouting
  fixes also applied: HANDLES 0 -> 58, all from PHP controllers, samples
  checked against source (aliased AdminArticlesController and
  AdminUsersController included); the 7 routes left without a handler use
  methods from a vendor trait (AuthenticatesUsers).
- Reporter-shaped cross-repo check (Laravel 11 withRouting api:, invokable
  GetCurrentUserController under prefix('/users'), TS fetch('/api/users/me')):
  CROSS_HTTP_CALLS 0 with this fix alone (route /users/me, no /api mount) and
  0 with the withRouting fix alone (no HANDLES); 2 with both applied.
- krayin/laravel-crm (reduced to one route file): / <- Webkul\Admin\Http\
  Controllers\Controller::redirectToLogin, placed through the PSR-4 root
  Webkul\Admin\ -> packages/Webkul/Admin/src.

Tests (edge_types_probe): handles_laravel_class_handlers_issue1146,
handles_laravel_class_handlers_parallel_issue1146 and
handles_laravel_class_handlers_no_composer_issue1146 assert the exact HANDLES
set. They include controls for a same-named Admin\UserController::show, an
alias import, an unimported fully-qualified class, a package under a PSR-4
root whose folder does not mirror its namespace, a decoy __invoke, and two
vendor controllers that must get no edge. RED on origin/main 3x, green, RED
on revert. edge_types_probe, php_lsp, pipeline, parallel, route_canon and
cross_repo pass (730); make lint-ci passes.

Refs #1146

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-04 20:46:42 +02:00
Martin Vogel 100ea8f3b2 Merge pull request #2489 from DeusData/fix/path-alias-skip-links
fix(workspace): skip links during path-alias discovery
2026-10-04 20:27:45 +02:00
Martin Vogel 2e7ed659dd Merge pull request #2488 from DeusData/test/daemon-runtime-connection-cap-deterministic
fix(daemon): preserve capacity rejection before the client HELLO
2026-10-04 20:27:41 +02:00
Martin Vogel c442028010 fix(workspace): classify manifest entries in their resolved form
A project's .cbmpathwhitelist lists extra roots it would like indexed.
When such a manifest was approved, and again each time it was consulted,
every entry was classified exactly as written in the file, while the
directory that would actually be indexed is the one the entry resolves
to. An entry that reached the home directory, a credential directory or
the volume root through a link or through `..` segments was judged by
its spelling and passed as an ordinary path; only the containment check
resolved it. An entry naming a directory that did not exist was approved
as well.

Both cbm_workspace_manifest_approve and cbm_workspace_manifest_allows now
resolve each entry with cbm_canonical_path, the resolver every other
caller of the policy applies, and classify that form. Approval refuses an
entry that is relative, does not exist or is not a directory, with a
message that names the entry and, when the resolved directory differs
from what was written, shows both. Approval stays bound to the manifest
text; where an entry leads is read again when it is used, so a link that
is retargeted after approval is judged by its new target. At use time an
entry that no longer resolves matches nothing, and the other entries of
the manifest are consulted as before.

An ordinary, existing, absolute entry keeps its result. A manifest that
names a directory which does not exist yet is now refused at approval
and has to be approved once the directory is there.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-04 19:08:28 +02:00
Martin Vogel 88380a94b1 Merge branch 'main' into feat/cross-repo-status 2026-10-04 19:02:12 +02:00
Martin Vogel 7bb1de0ed5 Merge pull request #2479 from DeusData/fix/workspace-resolved-home
fix(workspace): compare the home directory in its canonical form
2026-10-04 18:03:21 +02:00
Martin Vogel f40b1a564b Merge pull request #2475 from DeusData/fix/regex-compile-size-budget
fix(regex): size a pattern before compiling and refuse oversized ones
2026-10-04 18:03:16 +02:00
Martin Vogel 5c774862ac Merge pull request #2474 from DeusData/fix/search-arg-bounds
fix(mcp): clamp search_code context on both sides, add sink ceilings
2026-10-04 18:03:10 +02:00
Martin Vogel 7c290008e4 Merge branch 'main' into fix/issue-1171 2026-10-04 17:45:15 +02:00
Martin Vogel 5cd9891e57 Merge branch 'main' into fix/issue-1717 2026-10-04 17:45:09 +02:00
Martin Vogel 1991bf109a Merge remote-tracking branch 'origin/main' into fix/c-typedefs-macros-enumerators
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

# Conflicts:
#	internal/cbm/cbm.h
#	src/mcp/mcp.c
2026-10-04 17:18:01 +02:00
Martin Vogel d8ece6ca9b fix(build): reconcile three call sites after concurrent merges
Two pairs of pull requests merged cleanly but left main unbuildable:

- #2447 added a route_mount parameter to emit_route_registration and
  handle_route_registration, while #2464 added a new call to each with
  the old argument list. The cross-language refused-route helper from
  #2464 now takes route_mount and passes it on, and the pass_calls.c
  call passes it too, as every other call site does. A route minted for
  a refused binding therefore gets the same Laravel api mount as any
  other route from that file.
- #2446 added a caller of is_project_db_file, which #2526 replaced with
  cbm_is_project_index_db. The new caller uses the replacement, so the
  owning-project lookup accepts the same file names as every other
  project scan.

Verified: build/c/test-runner and the server binary build again; suites
edge_types_probe, mcp, cli and pipeline: 1081 passed, 4 platform skips.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-04 16:40:05 +02:00
Martin Vogel 982621dd08 Merge pull request #2459 from DeusData/fix/doc-comment-capture
fix(extract): capture complete doc comments across languages
2026-10-04 16:30:34 +02:00
Martin Vogel b7f67c29a7 Merge pull request #2526 from DeusData/fix/underscore-project-discovery-v2
fix(mcp): discover projects with underscore-prefixed names
2026-10-04 16:24:34 +02:00
Martin Vogel 86f49a0f87 Merge pull request #2524 from DeusData/fix/issue-1366-search-hint-v2
fix(mcp): avoid misleading empty-search filter hints
2026-10-04 16:24:28 +02:00
Martin Vogel 266e2814e3 Merge pull request #2518 from DeusData/fix/search-code-scratch-isolation-v2
test(mcp): isolate search scratch cleanup fixtures
2026-10-04 16:24:15 +02:00
Martin Vogel 5e6fa657ee Merge pull request #2517 from DeusData/fix/uninstall-keeps-indexes-v2
fix(cli): keep project indexes by default during uninstall
2026-10-04 16:24:09 +02:00
Martin Vogel 20fd3aadb5 Merge pull request #2511 from DeusData/fix/issue-1364-distinct-before-cap
fix(cypher): count DISTINCT rows against the row cap
2026-10-04 16:24:03 +02:00
Martin Vogel 873a966504 Merge pull request #2510 from DeusData/fix/issue-1484-close-range-v2
fix(subprocess): close inherited descriptors with kernel range operation
2026-10-04 16:23:58 +02:00
Martin Vogel 02abf31016 Merge pull request #2509 from DeusData/fix/issue-1186-psr4-imports-v2
fix(pipeline): resolve PSR-4 imports to exact class files
2026-10-04 16:23:52 +02:00