- Grouped `var ( a = …; b = a.Group(…) )`: earlier specs are in scope for
later ones (boundValue handles var_spec; the walk checks preceding specs).
- A write to the group in an earlier nested scope, or between loop
iterations (post statement, condition, body), makes the binding
control-flow dependent: the route is declined (CONFLICT) instead of
emitting the older prefix. A nested `:=` is a new variable, not a write.
- A name used inside its own if/switch/for/range/type-switch initializer
resolves to the outer binding instead of recursing into itself.
- Comments are not arguments: the framework query no longer anchors the
path with `.`, scan requires the path to be the first code argument and
picks the handler from code arguments only (also in Group prefixes).
- Qualified handlers (`b.List`) set the new HttpDetection.qualifiedHandler:
the orchestrator skips the file-first name lookup, which could bind an
unrelated same-named local method, and accepts only a repo-wide unique
match, else the file-level fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The framework route query anchored the path on interpreted_string_literal
only, so `GET(`/health`, h)` produced no group-layer contract although
ingestion (Strategy A) decodes both Go string forms via the shared
stringLiteral. Match raw_string_literal too so both strategies emit the
same contract id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selector handlers emit the field name only (`h.List`, `o.List` → `List`),
and the non-strict resolver takes the first same-named row in the
registration file. When that file declares the name more than once
(methods on different receivers), mark the detection
strictHandlerResolution so it resolves only to an in-file-unique match and
otherwise keeps a file-level anchor instead of a wrong handler. Names
declared once, or defined in other files, keep the existing resolution.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
findBinding collapses "declared without a value" (`var echo Factory`)
and "not declared" into null, so isLocalName missed that shadow. Split
the walk into lookupBinding, which returns undefined only when the name
is not declared before the enclosing function; findBinding keeps its
null contract and isLocalName checks for any declaration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- groupPrefix / receiverBindsToEchoConstructor return null past
MAX_GROUP_DEPTH; scan declines the route instead of emitting a
truncated prefix or falling back to gin's handler order for an
echo chain.
- The echo constructor check rejects an `echo` qualifier shadowed by a
local binding or an enclosing function's parameter/receiver.
- The gin integration fixture now imports gin like a real file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review finding on the mixed-import handler choice: a route registered on a
group (`api := e.Group(...); api.GET(...)`) bound its receiver to the
Group() call rather than a constructor, so constructor provenance failed
and the conservative last-argument fallback picked echo's middleware as
the handler name.
receiverBindsToEchoConstructor now walks enclosing Group() calls on the
way to the constructor (depth-capped by MAX_GROUP_DEPTH, mirroring
groupPrefix), so an echo group chain resolves to echo.New()/echo.Default()
and takes the first-argument handler, while a gin chain still fails the
echo-alias check and keeps the last-argument rule. Provenance must still
END at a constructor: parameters and unrelated New() remain fallback.
Tests: 39 in the go-gin file (new: grouped echo + gin chains in one mixed
file); group suite 1302 green; prettier/eslint/tsc clean.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Round-3 review findings (#7/#8/#9) on the Go group-mode HTTP plugin:
- #7: in a file importing both gin and echo, pick the handler argument per
call from the receiver's constructor provenance (`e := echo.New()` /
`echo.Default()` with a verified echo import alias) instead of pinning
every call to the last argument. Unprovable receivers (parameters,
Group()-derived receivers, unrelated `New()`) keep the conservative
last-argument fallback unchanged.
- #8: read the type-switch guard from the tree-sitter `alias` field, matching
the ingestion-side route-bindings convention; bare `switch x.(type)` has no
alias and skips naturally.
- #9: treat select `communication_case`/`default_case` as statement
containers and stop the binding walk (null, no prefix) when a case head
receives into the name — the received value is statically unknown, so a
route must not inherit an outer group (fixes `/outer/x` leaking into ids
for `case g := <-ch:`).
Tests: 38 in the go-gin file (3 new: constructor provenance, unrelated
New(), select receive); group suite 1301 green; prettier/eslint/tsc clean.
Co-Authored-By: Claude Code <noreply@anthropic.com>
normalizeHttpPath (the shared contract-id normalizer) does not add a
leading slash, while ingestion's normalizeExtractedRoutePath always
does — so a literal path "x" or a slashless Group("api") prefix split
the contract id across the two strategies (`http::GET::x` vs
`http::GET::/x`). After joining and collapsing "//", ensure the result
starts with "/" so both strategies feed the same bytes into the id.
Gin also panics when registering a route path without a leading slash.
Tests: go-gin-route-groups 35, group suite 1298 (all green).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Address the three nexus-check findings on PR #3458:
- Echo handler position: `e.GET("/x", h.Handler, auth.Middleware)` puts the
handler SECOND (echo: path, handler, middleware...), gin puts it last
(path, middleware..., handler). An echo-only import (matched on the import
path, alias-safe) now selects the first argument after the path; gin-only,
mixed, and import-less files keep the last-argument anchor.
- For-loop post binding: findBinding scanned every named child of a
for_clause, including `update`, which runs after the body — a
`g = r.Group("/new")` post statement shadowed the group the body sees.
Only the `initializer` slot binds before the body.
- Duplicate slashes: joinRoutePath collapsed "//" only at the join boundary,
while ingestion's normalizeExtractedRoutePath collapses every run and the
downstream contract-id normalizer does not — a path keeping "//" split into
two contract ids across the strategies. Collapse the final result on every
return branch instead.
Tests: +6 (echo-only handler, both-imports fallback, for-post scoping,
duplicate-slash collapse); go-gin suite 36, group suite 1297, ingestion Go 71.
Co-Authored-By: Claude Code <noreply@anthropic.com>
unquoteLiteral only strips surrounding quotes, so an interpreted Go
string kept its escape sequences verbatim: r.Group("/api\x2fv1") was
recorded as /api\x2fv1 although the runtime registers /api/v1. The
ingestion extractor already decodes Go strings fully (strconv.Unquote
semantics: simple/hex/octal/\u/\U escapes, raw-string carriage returns,
fail-closed on undecodable bytes), so the graph and the source-scan
plugin disagreed on the same route's contract id — both survive the
merge as a wrong-path duplicate.
Extract that decoding into route-extractors/go-shared.ts and use it
from both layers: go-gin-echo.ts imports it unchanged, and every
string capture in the group Go plugin (group prefixes, framework paths,
HandleFunc, http client, NewRequest, resty) now goes through it.
Raw (backtick) strings keep Go semantics: no escape processing.
Covers it with two tests: hex escapes decoded in a prefix and a route
path, and escapes left literal inside a raw-string prefix.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The Go group-mode HTTP plugin missed two kinds of legal Go bindings when
tracing a route receiver back to its Group("/p") call:
- findBinding only scanned preceding statements in `block` nodes, so a
group bound by a statement-scoped initializer was invisible: an `if` or
`switch` initializer, a `for` clause (including `range`), a type-switch
guard, and declarations inside `expression_case`/`type_case` were all
skipped, and an outer group of the same name leaked into the route path
(or the prefix was dropped entirely). These now scope over their
statement the same way Go scopes them, shadowing outer bindings.
- asGroupCall only accepted `interpreted_string_literal`, so a raw-string
Group prefix (`r.Group(` + "'`/api`'" + `)`) contributed nothing even
though the shared unquoteLiteral already strips backticks.
Covers both with seven new tests (if-init body+else, shadow/non-Group
init, switch init + case-clause group, type-switch guard + type case,
for init + range shadow, backtick prefix).
Co-Authored-By: Claude Code <noreply@anthropic.com>
The Go group-mode HTTP plugin dropped framework routes whose handler is a
method value or package function (`h.List`, `pkg.List`, a
selector_expression) and ignored `Group("/p")` prefixes, so an idiomatic gin
backend produced almost no provider contracts, and the few it did produce
carried group-relative paths that exact contract matching could never link.
- Accept selector_expression handlers on go-framework-route; the resolved
name is the selector field. The last-argument anchor is unchanged, so
variadic middleware is still not taken for the handler.
- Recover the route prefix by walking the receiver back through `:=`, `=`
and `var` bindings to literal-prefix `Group(...)` calls, lexically within
the enclosing function (nested groups, empty groups, `{ }` blocks,
closures, chained `Group().GET()`). A receiver that cannot be traced, such
as a group passed in as a parameter, keeps the literal path.
Refs #1668
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(fastapi): carry package router mount prefixes to child routes
* fix(fastapi): address review feedback on nested router prefixes (#3408)
- Skip unprefixed includes in the parse-impl legacy loop so a bare
include_router in another file no longer shadows the real prefix.
- Union exact-file prefixes with legacy long/short prefixes via a shared
mergeMountPrefixes helper in both ingestion and the group extractor.
- Join the parent APIRouter(prefix=...) between the mount prefix and the
child include prefix.
- Resolve the group layer over every repo path (empty files included) so
absolute-import ambiguity matches ingestion.
- Memoize (file, prefix) frames so diamond-shaped include graphs stay
linear; drop the stack.pop() non-null assertion.
- Accept extra keyword arguments and a trailing comma in unprefixed
include_router calls without double-firing on prefix= calls.
- Document that pass-through is limited to a host named `router`.
- Bump parse-cache SCHEMA_BUMP to 123 for the new capture fields.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(fastapi): seed prefix propagation from bare router mounts (#3408)
- A router mounted without a prefix now seeds traversal with an empty
prefix (only when no prefixed mount targets the same file), so its own
APIRouter(prefix=...) reaches unprefixed children on both surfaces.
- An all-empty chain records nothing and leaves the child on its legacy
fallback.
- The bare-mount integration test no longer asserts that the test app's
unprefixed mount is absent; it pins only that the real prefix survives.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(fastapi): capture include prefixes after nested-call arguments (#3408)
- Let the Shape A/B and unprefixed include_router patterns step over one
level of nested calls such as dependencies=[Depends(auth)], so a
prefix= written after them is captured by the worker (the group
layer's tree-sitter patterns already handled this shape).
- Replace the unit test that pinned the dropped prefix with one that
pins the captured prefixes and the unprefixed Depends-only edge; add a
group-layer parity test.
- Correct the diamond test comment to 2^39 root-to-leaf paths.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): model restoring helper calls in decorator identity (#3414)
A bare module-level call to a same-file helper whose every global
binding of a descriptor name is an unconditional del or builtins import
now restores the builtin at the call site, matching CPython. A nonlocal
rebind nested in the enclosing function now shadows an owned builtins
import, closing a false builtin. Unprovable call orders stay fail-closed
and are pinned against CPython 3.11.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(review): apply review findings
Close three false-builtin paths the review found: a match-pattern
capture now counts as a binding (at any scope, including inside a
restoring helper), a call before the helper's def no longer counts as a
restore, and the helper-name uniqueness check sees match captures.
Pin the helper rejections (conditional restore, async, early and nested
return, wildcard import) against CPython 3.11.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cache): bump parse-cache schema to v122 for #3414
Python decorator identity verdicts changed, so warm v121 ParsedFiles
would replay stale receiver bindings.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): require an argument-free call to a helper with no required parameters
A call that fails to bind the helper's parameters raises TypeError
before the body runs, so it proves no restore. Keep such calls
fail-closed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): count only real captures and module bindings for decorator identity
Match value patterns, class names and keyword keys read a name rather
than capture it, so they no longer shadow a builtin descriptor. A local
of the same name as a restoring helper no longer disqualifies the
module-level helper; only a module binding or a global rebind does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(python): state the fail-closed contract of the descriptor identity table
`false` means the resolver does not prove the builtin, not that CPython
shadows it. Cases where CPython keeps the builtin but the resolver fails
closed carry a comment saying so.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): resolve decorator identity like CPython
Decorator identity was decided by two different predicates. One read
the raw decorator text, so a trailing comment such as
`@staticmethod # type: ignore` hid the builtin and turned an explicit
first parameter into a fabricated receiver. The other trusted any
`staticmethod` spelling, including a module-level rebinding and a
`staticmethod(classmethod(f))` stack, which CPython cannot call.
Read the decorator expression node only, and recognize a bare builtin
descriptor only when the file does not rebind that name. Publish subtype
capacity only for a plain function or a single builtin staticmethod or
classmethod wrapper. Drop a no-op coverage guard, move the implicit
classmethod comment next to the code it describes, and bump the parse
cache to v121.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): shadow builtin descriptors only by visible bindings
The whole-file identifier scan counted plain reads (`staticmethod(f)`),
`from builtins import staticmethod`, and bindings that run after the
decorator as rebindings. CPython evaluates a class-body decorator with
LOAD_NAME when the `def` runs, so none of those change which object the
decorator names. Methods decorated with the real builtin lost their
subtype call shape, and static methods lost their first parameter in
arity metadata.
Move decorator identity into builtin-descriptors.ts and count only
binding occurrences (assignment and loop targets, walrus, def/class,
parameters, import aliases, except/with/match captures, del, type
parameters, and wildcard imports) that are visible where the decorator
runs: the class body or module before the definition, a repeating
enclosing loop, any binding in an enclosing function, and any
global/nonlocal rebind.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): model global and del like CPython's symbol table
`global x` and `nonlocal x` bind nothing; they redirect the declaring
function's own bindings of `x` to an outer scope. A bare declaration
was treated as an unconditional rebinding, so `@staticmethod` anywhere
in the file lost builtin recognition.
A module- or class-level `del` restores the outer lookup rather than
binding the name. Treat an unconditional `del` that runs after a
binding and before the decorator as undoing that binding. A `del`
inside control flow may not run, and a `del` inside a function still
makes the name local there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): order global rebinds and honor builtins re-exports
A function that declares `global staticmethod` and assigns it rebinds
the module name only when called, and it cannot be called before the
top-level statement that defines it runs. Treat such a rebind as
visible only when that statement precedes the decorator, or when the
decorator sits in a deferred class body. `nonlocal` rebinds stay
visible anywhere in the enclosing function.
`from builtins import staticmethod as staticmethod` binds the builtin
to its own name, so it no longer counts as shadowing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(python): scope the descriptor-identity CPython claim
The wildcard-import case expects the fail-closed resolver verdict, not a
CPython outcome, because the imported module's exports are unknown.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): resolve descriptor names as LOAD_NAME does
Model each binding by its effect on the namespace (Language Reference
4.2.1): an import that binds the `builtins` object itself (plain, aliased
to the same name, or `from builtins import *`) restores the builtin, a
module- or class-level `del` unbinds so lookup falls through, and any
other binding shadows. Resolve the decorator like LOAD_NAME (4.2.2):
the class namespace, then module globals, then builtins, each as it
stands when the `def` runs.
A restoring effect counts only as an unconditional simple statement that
runs before the decorator, so an import or `del` under `if`/`try` or a
loop stays fail-closed. A helper's `global` delete depends on whether
the helper is called, which the resolver does not model, so it keeps the
override.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* refactor(python): simplify decorator descriptor lookup
Derive the descriptor type and name set from one `as const` list,
replace indexed non-null assertions with destructuring, build the scope
chain without an assertion, and skip the enclosing-function owner lookup
when the decorator has no enclosing function. Key the stacked-decorator
verdicts by case name so a failure names its case.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): resolve enclosing-function and deferred descriptor lookups
A class body reads a free name from the innermost enclosing function that
binds it (Language Reference 4.2.2). Evaluate that function's namespace
the same way as the class and module ones, so an unconditional
`from builtins import staticmethod` there resolves to the builtin. Any
other binding still shadows, including a local assigned only after the
class, which raises NameError rather than falling back to the builtin.
A class body inside a function runs whenever that function is called,
which can be any time after its top-level statement starts. Read module
state at that statement instead of after the whole module, so an earlier
`del` restores the builtin, and treat any later module override as
possibly visible.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): suppress uncertain decorated receivers
* fix(ci): keep uncertain Python receivers out of method arity
Unrecognized decorators now leave the receiver kind unproven, but the
first parameter is still the implicit receiver slot for ordinary bound
calls. Method extraction stopped stripping it, so decorated methods
reported one extra parameter and shifted capture arity metadata.
Share the uncertain-receiver classification between type-binding
synthesis and parameter extraction, then refresh the Python capture
golden and benchmark fingerprint for the intended capture change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(python): resolve mixin calls with CPython C3 order
Breadth-first MRO bound a diamond mixin call to the wrong base, and dropping the site left the real method out of the graph. Use C3 and take the first compatible method in that order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(autofix): apply prettier + eslint fixes via /autofix command
* fix(python): correct mixin receiver baseline counts
* fix(python): guard incomplete mixin inheritance
* fix(python): record unresolved MRO tail coverage (#3393)
Track a missing subtype target when the last indexed MRO owner has an unindexed parent, and cover the case with an integration test. Correct the C3 fixture description.
Note: local full npm test timed out amid parse-worker startup failures; focused tests and benchmark baseline passed.
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(mcp): add read_file + grep tools (REST parity for /api/file slice + /api/grep)
* chore(autofix): apply prettier + eslint fixes via /autofix command
* Address PR review feedback (#3377)
- Fail read_file and grep when full source is unavailable, matching the HTTP 410 contract instead of an empty grep or a not-found on a missing checkout.
- Reject branch on those tools so a pinned index is not labeled onto checkout bytes, and stop advertising branch in their schemas.
- Point the grep hint at a 0-based read_file window, pass caseSensitive and literal through, and test the handlers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3377)
- Keep read_file and grep in the multi-repo schema requirement without advertising branch.
- Reject negative maxLines and return integer slice bounds for fractional line positions.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3377)
- Reject a negative read_file endLine before slicing so JavaScript does not treat it as an offset from the end of the file.
- Drop the fractional startLine/endLine claim so the integer schema is the advertised contract.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3377)
- Skip indexed grep paths whose realpath leaves the checkout so a symlink cannot return lines from outside the repo.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(bench): record the 19-tool MCP roster
read_file and grep are real tools, so tools/list and GITNEXUS_TOOLS both
moved from 17 to 19. The timing ratios were already inside budget.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(mcp): share read_file and grep contracts with existing helpers
Boolean grep flags go through isFlagTrue, the whole-file cap is one constant, and checkout tools stay on the per-repo schema without advertising branch.
---------
Co-authored-by: svjack <svjack@example.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>