Skip to content

[pull] v8 from safishamsi:v8#99

Merged
pull[bot] merged 7 commits into
miqdigital:v8from
safishamsi:v8
Jul 1, 2026
Merged

[pull] v8 from safishamsi:v8#99
pull[bot] merged 7 commits into
miqdigital:v8from
safishamsi:v8

Conversation

@pull

@pull pull Bot commented Jul 1, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

sheik-hiiobd and others added 7 commits June 30, 2026 21:53
`graphify affected` (blast radius) was blind to a function passed BY NAME as
a call argument — `executor.submit(fn)`, `Thread(target=fn)`, `map(fn, xs)`,
callbacks — so those callers were silently dropped from the affected set (an
under-counting blast radius reads complete while missing exactly where
regressions hide). Capture them as a distinct `indirect_call` relation
(INFERRED, context "argument"), kept separate from `calls` so strict
call-graph queries stay precise, and add it to DEFAULT_AFFECTED_RELATIONS.

Hardened over the original PR against the name-collision false edge (the
Doxygen #3748 trap): emit only when the argument identifier resolves to a
callable definition AND is not shadowed by a parameter or local binding in
the enclosing function. So `def via(pool, handler): pool.submit(handler)`
(handler is the param, not the module function) and `process(config)` where
`config` is local data emit no edge, while a genuine module function passed
by name still resolves. Dedup-safe against existing direct `calls` edges.
Python only; dict-literal / getattr-by-string / decorator dispatch deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#1565 captured a function passed by name as a call argument
(executor.submit(fn), Thread(target=fn), map(fn, xs)) only when the
callback was defined in the SAME file — it resolved through the in-file
label map. But the dominant real-world shape is cross-module: the callback
is imported (`from .handlers import on_event; pool.submit(on_event)`), so
the same-file map can't see it and the edge was dropped — exactly the
caller a blast-radius query must not miss.

When the argument identifier isn't defined in-file (and isn't shadowed by
a param/local — that guard already ran), emit an `indirect` raw_call and
let the existing cross-file resolution pass handle it, branching to a
distinct indirect_call/INFERRED edge instead of calls. It rides the same
single-definition god-node guard and import-evidence disambiguation as
direct calls (parity: when a direct call resolves, so does the indirect
one; when the name is ambiguous, both bail). Two added safeguards: the
target must be a real callable def (per-file callable_nids unioned across
the corpus) so an imported data constant can never become a dispatch
target, and an existing direct `calls` edge for the pair pre-empts it.

Smoke-verified: imported single-def callback resolves; ambiguous name
bails (same as direct); imported data constant rejected; direct+indirect
to the same target keeps only the direct edge; cross-file keyword
target=cb resolves. Full suite 2710 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Slice 1 of #1566. A function referenced as a VALUE in a dict/list/set/
tuple literal — the registry/route-table idiom (`ROUTES = {"create":
create_user}`, `HOOKS = [on_start, on_stop]`) — is an indirect dependency
that blast-radius must see, but the call-argument capture (#1565) didn't
cover it. Emit an indirect_call edge for each callable value:

- module-level tables attribute to the file node; function-scoped tables
  attribute to the enclosing function. Same-file and cross-file (an
  imported handler in a table routes through the cross-file resolver).
- dict KEYS are excluded (only values are references); non-callable values
  (a number, a string) never resolve; a name shadowed by a param/local or
  rebound at module scope is the local value, not the function.

Refactors the resolve-and-emit logic shared by the argument and table
paths into one guarded helper (_emit_python_indirect_ref) and threads the
edge `context` ("argument" | "collection") through the cross-file pass.
Adds _python_module_bound_names for the module-scope shadow set.

7 new tests (module dict/list, function-scoped, dict-keys-excluded,
non-callable-value, module-reassign shadow, cross-file table). Full suite
2717 passed. Python only; assignment/return refs + other languages remain
on #1566.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the indirect_call model to JavaScript and TypeScript, the next-
biggest callback-passing ecosystem. Now captured:

- callbacks passed by name as call arguments: `arr.map(fn)`, `setTimeout(fn)`,
  `el.addEventListener("x", fn)` — function-scoped, attributed to the caller.
- module-level callback registration (idiomatic in JS, unlike Python):
  Express routes `app.get("/", handler)`, event wiring `emitter.on("e", h)`,
  timers — attributed to the file.
- object/array dispatch tables: `const ROUTES = { create: handler }`,
  `const HOOKS = [onStart, onStop]`, object shorthand `{ handler }`.

Arrow-const functions (`const cb = () => {}`) are registered as callable
targets (threaded callable_def_nids + local_bound_names through
_js_extra_walk). Guards mirror Python: shadowing by param/local/module
reassignment is rejected (JS module shadow set excludes function-valued
declarators so arrow-consts stay resolvable), object KEYS and non-callable
values are excluded, inline arrows/function expressions are direct defs not
references, single-definition god-node guard cross-file.

Two cross-file fixes the JS import model exposed:
- a name that resolves to an import-surfaced FOREIGN symbol (JS named
  imports map the real node into the importing file's label map) is now
  deferred to the cross-file resolver instead of being mistaken for a local
  non-callable; the global callable_nids guard still rejects imported data.
- indirect_call dedup is now call-aware: a benign `imports` edge from a file
  to the symbol it imports no longer suppresses the indirect_call to it
  (only a real calls/indirect_call edge does).

Shared the resolve-and-emit helper across Python and JS/TS. 9 new tests
(func-arg, module object/array, Express-style registration, inline-arrow
negative, param-shadow, key/data exclusion, shorthand, cross-file import,
TS typed params). Full suite 2726 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_check_skill_version advised "Run 'graphify install' to update" on ANY
version mismatch. But `install` writes the package's OWN bundled skill and
re-stamps .graphify_version, so when the skill on disk is NEWER than the
running package, following that advice silently DOWNGRADES the skill to
silence the warning. The docstring even said "warn if the skill is from an
OLDER version" but the code never checked direction.

Compare versions numerically (new _version_tuple, so 0.10 > 0.9 and a
malformed stamp degrades instead of raising). When the skill is newer than
the package, recommend upgrading the package (uv tool upgrade / pip install
-U) instead of install; the older-skill case is unchanged. Hit in practice
by a stale `uv tool` CLI and by contributors whose dev checkout stamped a
newer skill. Reported by @TPAteeq.

4 tests (numeric ordering, both mismatch directions, silent-when-equal).
Full suite 2730 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lice 2)

A function bound to a name (cb = handler) or returned from a factory
(def make(): return handler) is a real reference, but indirect_call only
covered call arguments and dispatch tables, so `affected` still dropped these
callers.

Emit indirect_call (context "assignment"/"return", INFERRED) for the value-side
identifiers of a Python assignment RHS and a return, at function scope (owner =
enclosing function) and module scope (owner = file node). Reuses the shared
_emit_indirect_ref guard. Scans the VALUE side only -- the assignment target is
a new local binding, not a reference -- so the existing param/local shadow guard
still rejects the false edges #1565 fixed.

Negatives covered: param-shadow, local-shadow, non-callable emit nothing.
Full suite green; ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pull pull Bot locked and limited conversation to collaborators Jul 1, 2026
@pull pull Bot added the ⤵️ pull label Jul 1, 2026
@pull pull Bot merged commit 69b3997 into miqdigital:v8 Jul 1, 2026
4 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants