* feat(api_impact): list the callers of the requesting function as indirect consumers
The FETCHES edge is file-level, so api_impact saw only the file that sends
the request. The files that call its request function never mention the URL
and were missing from the report (#3644).
The edge's reason now names the callables whose spans contain the request
sites, one `|via:<id>` field each, under the containment rule the R3-6 sink
set already uses; that rule moves into EnclosingCallableIndex so both stay
one. Several sites of one file for one route now share the edge instead of
colliding on its id. api_impact follows the CALLS edges into those callables
one level up and reports the callers as `indirectConsumers`, apart from the
direct consumers and outside the risk level. Direct consumer files and the
handler file are left out. An index built before the attribution carries no
field. Expo navigation sites carry their real line so they attribute to the
component that contains them.
Closes#3644
* fix(api_impact): attribute a request site by its AST enclosing callable
A helper defined on the line of the request has the smaller span, so the
line-only lookup named it and the callers of the real requesting function
vanished from indirectConsumers. The parser now records the enclosing
callable of each fetch site and of each wrapper call (through the call's
position), and attribution prefers that identity; the span lookup remains
for sites found by text scan. The same preference applies to the R3-6 sink
set. A real analyze of the fixture followed by api_impact pins the helper
case and the wrapper case.
* test(bench): rebaseline receiver resolution for the indirect-consumers fixture
The api-impact-indirect-consumers fixture adds three receiver-unresolved
sites to the corpus: two `.then(...)` calls on the untyped return of
`fetch(...)` and of the local wrapper `apiFetch(...)`, and one
`req.query.path` read inside an inline Express handler with an untyped
`req`. Call drops 138 -> 140, reads 30 -> 31, all kinds 168 -> 171. No
shape-arm state and no existing site changed.
* fix(api_impact): preserve module-scope request attribution
Keep AST-confirmed module scope distinct from missing attribution for direct
fetches, HTTP clients, and wrapper calls. Prevent overlapping callable spans
from inventing indirect consumers or process sinks.
Preserve span fallback for callable forms the generic identity walk cannot
name. Add eight regression cases, including real analyze/api_impact coverage.
Rebaseline receiver-resolution counts for the two then calls and one property
read added by this PR's existing fixtures; shape expectations are unchanged.
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
* fix(routes): scope inline Express handlers and report api_impact lower bound
Inline Express handlers are anonymous Function nodes named by position, but
the route facts only recorded a handler name for bare identifiers. Routes
with inline handlers therefore had no handlerSymbolId: every route in a file
got the union of its handlers' response keys and every flow starting there.
Record the positional name for inline handlers, read response keys from the
resolved handler when a file registers several routes, and let processes
link exactly by handlerSymbolId as they already do for named handlers.
Count, per route, the files that pass its URL to a callee not proven to be
a fetch wrapper (excluding proven consumers and the route's own file). The
count is stored as Route.unverifiedConsumers; api_impact reports
epistemic: 'lower-bound' with that count when it is non-zero. Older indexes
without the column fall back to the previous query.
Closes#3488
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(routes): match unverified consumers once per URL
Memoize route matching per distinct fetch URL, skip the proven-consumer
scan when no unproven call matches a route, and build the Route query's
optional columns from a list instead of two booleans.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(group): keep inline handlers attributed to their registrar
Inline Express handlers now carry their positional Function name in the
route facts so ingestion can resolve a handlerSymbolId. Group HTTP
contracts read the same facts, but that name is a graph identity rather
than a symbol name the contract lookup can match, so the provider lost its
enclosing-registrar attribution. Ignore the name for inline handlers there.
Rebaseline the two bench guards the branch moved:
- emit-persistence: route.csv gains the header-only unverifiedConsumers
field; stripping it restores the prior fingerprint exactly.
- receiver-resolution: the new fixture's req.query.path read on an untyped
inline-handler parameter adds one receiver-unresolved read (191 -> 192).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
* feat: configure prettier with pre-commit hook integration
Add prettier, lint-staged, and prettier-plugin-tailwindcss at the repo
root with husky pre-commit hook integration. Moves husky from
gitnexus/ to root package.json for reliable hook installation.
- Root package.json with prepare/format/format:check scripts
- .prettierrc with endOfLine:lf and tailwindStylesheet for TW v4
- .prettierignore excluding fixtures, vendor, generated, *.d.ts, *.md
- .gitattributes enforcing LF line endings for Windows consistency
- Pre-commit hook uses direct node_modules/.bin/ paths (no npx)
* style: apply prettier formatting to entire codebase
One-time bulk format. No logic changes.
Use .git-blame-ignore-revs to skip this commit in git blame.
* chore: add .git-blame-ignore-revs for prettier format commit
* perf: pre-commit hook runs only tests related to staged files
Use vitest --related to scope test execution to tests that import
the changed files, instead of running the full suite on every commit.
* perf: remove vitest from pre-commit hook, keep in CI only
Pre-commit now runs lint-staged + tsc only. Tests run in CI
(ci-tests.yml) where they belong — keeps commits fast.
* ci: add prettier format check to quality workflow
PRs will now fail if code isn't formatted with prettier.