* feat(group): resolve cross-file named HTTP handlers via unique repo-wide lookup U1 of #2275. When a provider's named handler is defined in a file other than its route registration (e.g. router.get('/x', listUsers) with listUsers imported), the registration file's symbols don't contain it, so resolution fell back to the file-level boundary. Add a repo-wide name query (RESOLVE_BY_NAME_QUERY, the label-union pattern from manifest-extractor) consulted only after the file-scoped lookup misses, and honored ONLY when exactly one Function/Method/CodeElement carries that name (zero/many → keep the file fallback, no wrong-symbol attribution). Provider-only, cached by name. 4 unit tests; 743 group tests pass. * test(bench): cross-file named handler scenario (end-to-end proof of #2275) U2 of #2275. Adds a fifth bench scenario: a backend route whose handler (listUsers) is imported from another file than its registration, with a frontend consumer. Asserts the provider resolves to the handler via the repo-wide unique name lookup (sym=listUsers, uid set) and that the cross-repo trace is symbol- precise (no file-level fallback). verify.mjs now 12/12 on the real pipeline. * fix(review): apply autofix feedback ce-code-review (autofix) — no correctness/security findings; applied test-coverage + robustness fixes: repo-wide query throw -> empty (no exception); by-name lookup cache fires once across same-named handlers; consumers never consult the repo-wide lookup; same-file-wins now asserts the global path is bypassed; bench provider find scoped by contractId; clarified the uniqueness-guard comment. 167 extractor tests. * fix(group): tri-review fixes for cross-file handler resolution Two-engine PR tri-review (Claude swarm+ce, Codex gpt-5.5 swarm+ce+adversarial) on #2277. Correctness/security clean (injection refuted, bind-param). Fixes: - Named-provider wrapper-attach (Codex swarm P1 + Claude ce-adversarial, cross-engine): a named handler that fails both name lookups no longer falls through to line-span containment, which attached the route to the enclosing registrar (e.g. a setupRoutes() wrapper) instead of leaving it empty. Containment now applies only to consumers and inline-arrow providers. - CodeElement/ORM empty-file nodes (Claude ce-adversarial reproduced + ce-maintainability): RESOLVE_BY_NAME_QUERY gains 'AND n.filePath <> ""' so a handler name colliding with a synthetic ORM model node (orm.ts emits filePath:'') neither resolves to an edge-less node nor inflates the uniqueness count and masks the real handler; + a defensive empty-filePath guard in resolveSymbolByNameUnique. Added LIMIT 2 (Codex swarm P3 + ce-maintainability) to bound homonym materialization (count guard stays exact). - Documented the aliased-import limitation (Codex adversarial): the route-site identifier is the local alias, fix deferred to #2275 import narrowing. - README expected verdict 9/9 -> 12/12 (Codex swarm+ce P3). Tests: +3 (wrapper-no-attach, empty-filePath reject, empty-registration-file resolves) covering the cross-engine gaps. 170 extractor / 748 group+integration pass; bench 12/12 end-to-end. * feat(group): import-pinned handler resolution (fixes deferred alias case) Resolves the tri-review's deferred item: cross-file named handlers are now pinned to their import's target module instead of resolved by name alone, so aliases and names that collide with a local symbol resolve correctly. - node.ts builds a local-binding -> {declared name, module} map from the file's named imports; the express handler emits the DECLARED name + a handlerImport {name, module} (HttpDetection gains the optional field). - resolveDetectionSymbol gains an imported-handler rung: resolveImportedSymbol pins to the import's target file via RESOLVE_IN_MODULE_QUERY (n.name= AND filePath STARTS WITH the resolved module path), unique-match only. An imported handler never uses file-scoped lookup (it is defined elsewhere); on a module miss it falls back to a unique repo-wide name match on the DECLARED name, then null. Relative imports only; bare/non-relative imports keep the repo-wide fallback. Cached by (module-prefix, name). - Closes the Codex-adversarial alias finding: import { listUsers as handleUsers } + an unrelated handleUsers no longer mis-resolves — the route resolves to the imported listUsers in its module, and the alias is never looked up. - Shared toResolvedSymbol helper (dedups the row->symbol + empty-filePath guard). Tests: alias-resolves-to-declared-name + module-pin-resolves-ambiguous-name unit tests; same-file-wins reworked to a genuinely LOCAL handler. Bench scenario 6 (aliased import with a decoy) proves it end-to-end. 172 extractor / 751 group+integration pass; bench 14/14. * feat(group): import-pinned resolution for Python aliased handlers Extends the JS/TS import-pinning to Python. The Python analog of express router.get(path, handler) is Flask's imperative add_url_rule(view_func=...), whose view is often an imported (aliased) symbol. - New Flask add_url_rule provider pattern (path + view_func handler + methods; default GET, methods=[...] honored). High Flask-specificity keeps false positives low — unlike bare path()/Route(), which the plugin deliberately leaves to graph Route nodes. - buildPythonImportMap resolves 'from .mod import name as alias' (and plain 'from mod import name') to the declared name + raw module spec. - resolveModuleBase generalized to two relative-import dialects: path-style (JS './h/users') and dotted (Python '.handlers.users', '..pkg.users' — leading dots are package levels). Bare/absolute imports keep the repo-wide fallback. - Django stays graph-resolved (handlerSymbolId); FastAPI/Flask decorators stay same-file (decorated function). This only adds the imperative imported-view case Python lacked. Tests: Flask aliased add_url_rule unit test (relative dotted module pinned, alias never queried) + bench scenario 7 (end-to-end, 16/16). 173 extractor / 752 group+integration pass.
5.6 KiB
Cross-repo trace — end-to-end verification
Verifies the cross-repo trace MCP tool against the real pipeline (not
hand-persisted graphs): runFullAnalysis(--pdg) on two repos → real syncGroup
HTTP contract extraction + bridge build → callTool('trace', { repo: '@group' }).
Run from gitnexus/ (needs a current build for the parse worker):
node scripts/build.js
node bench/cross-repo-trace/verify.mjs
verify.mjs is self-contained — it generates each fixture inline, runs the real
analyze → sync → trace/impact pipeline, and prints PASS/FAIL per assertion
(exit non-zero on any failure). Expected verdict: 16/16 checks passed.
Cases covered (one scenario each)
- Named handlers, same file — a frontend with named
fetchwrappers (fetchUsers,createUserReq) and a backend with named express handlers (listUsers,createUser) on/api/usersGET/POST. Asserts: all four contracts resolve asymbolUid;traceis symbol-precise (the GET pair selectshttp::GET, the POST pairhttp::POST, no file-fallback note); the destination trace lands atlistUsers. - Anonymous handler —
router.get('/api/ping', (req,res) => …). Asserts the provider contract has an emptysymbolUid, and the destination trace (omitto) reaches it, reported as<http::GET::/api/ping handler>with an anonymous note. - Cross-repo
impactfan-out —impact @grouponfetchUserscrosses the boundary (cross_repo_hits >= 1); the samesymbolUidjoin was 0 before. - Multi-language (Python) — a Flask provider +
requestsconsumer; asserts the Python line wiring resolves the consumer and the cross-repotracestitchesfetch_items -> list_items. - Cross-file named handler (#2275) — a route whose handler (
listUsers) is imported from another file than its registration. Asserts the provider resolves to the handler via the import-pinned module lookup, and the trace is symbol-precise (no file-level fallback). - Aliased cross-file import (#2275) —
import { listUsers as handleUsers }with an unrelated decoyhandleUserselsewhere. Asserts the route resolves through the import to the declaredlistUsers(not the alias or the decoy), proving import-pinned resolution. - Python aliased import (#2275) — a Flask
add_url_rule('/api/users', view_func=handle_users)whose view isfrom .handlers.users import list_users as handle_users. Asserts the handler resolves through Python's dotted relative module tolist_users, symbol-precise.
The ambiguous-destination (a file making several HTTP calls whose consumer
contracts have no resolved uid) and degraded-member (a member DB that throws
mid-resolution) paths need synthetic inputs the real analyzer cannot produce, so
they live in the unit suite (test/unit/group/cross-trace.test.ts).
What it proves
analyze+syncGroupbuild the correctContractLinks (exact HTTP match).- HTTP contracts carry a real
symbolUidwhenever the endpoint resolves — the extractor binds each detection to the function it lives in (the function CONTAINING thefetch; the named handler, or the inline handler by line-span containment, for a route). A handler/consumer that resolves to no named symbol (a fully anonymous handler, or a language plugin that does not yet set the call-site line) keeps an empty uid and degrades to the file/destination fallback. When resolved, contracts reportextractionStrategy: 'source_scan_resolved'/'graph_assisted'with a uid. trace @group from=<calling fn> to=<handler fn>stitches the cross-repo path (fetchUsers → listUsers), reporting theCONTRACT_LINKhop and (withpdg:true) the data-flow enrichment, symbol-precise (GET pair →http::GETcontract, POST →http::POST), with no file-fallback note.- The same
symbolUidfix makesimpact @groupfan out across the boundary (it was 0 cross-repo hits before — both tools join crossings onsymbolUid).
Resolution precedence & residual limits
The extractor resolves symbolUid in this order, falling through on a miss:
- Named handler —
router.get('/x', listUsers)resolveslistUsersby name. - Containment — the innermost
Function/Methodwhose line span encloses the call/registration line (consumers; inline-arrow providers). - File-level boundary fallback (in
cross-trace) — only when 1–2 leave the uid empty: if the user'sfrom/toresolves into the contract's file, that endpoint anchors the boundary. Anotes[]entry flags it as file-level, not symbol-precise.
The call-site line is set by all bundled language plugins (Node/TS, Python, Go,
PHP, Kotlin, Java), and containment matches symbols by filePath across
Function/Method/CodeElement, so it also resolves methods nested in classes
(Java/Kotlin), not just top-level functions.
Anonymous handlers — the destination trace
A fully anonymous handler (router.get('/x', (req,res) => res.json(...)))
has no symbol node at all, so it cannot be named as a to target. This is
handled by the destination trace: omit to/to_uid/to_file on an
@group trace and trace from=<consumer> follows the consumer's outgoing HTTP
call across the bridge and reports where it lands — by route + file:line, with a
notes[] entry flagging the handler as anonymous:
app/frontend:fetchUsers → app/backend:<http::GET::/api/users handler> [CONTRACT_LINK]
To go deeper into an anonymous handler, trace to a named function it calls (the provider segment then resolves normally).