#3699: the Fermat-spiral seed positions referenced `i` inside RAW_NODES.map(n => ...)
which has no index param, so the emitted JS threw 'ReferenceError: i is not defined'
and graph.html failed to render at every graph size. Bind the index: map((n, i) => ...).
#3686: the tooltip `title` sinks were correctly un-escaped (vis-network renders a
string title via Popup.setText -> innerText, verified in the pinned 9.1.6 bundle:
its only non-Element branch is innerText and it has zero innerHTML=<var> sinks).
Documented that invariant at the sink as the #1838 stored-XSS boundary.
Adds two guards to test_export.py: a vis-network version pin assertion (a bump must
force re-verifying the innerText tooltip path) and a spiral-seed indexed-map guard
(catches the free-`i` regression, which the substring-only export tests missed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves the `BarnesHutSolver.js` maximum call stack size exceeded
error. By explicitly initializing node X/Y coordinates using a
sunflower spiral (Fermat's spiral) formula, nodes no longer spawn
stacked on top of each other. This prevents `avoidOverlap` physics
forces from dividing by zero and exploding into infinite values
on the very first frame.
(cherry picked from commit a20b3593bd)
The two test_issue_3695_symlink_worker_* tests monkeypatch _reconcile_existing_graph
to inject a deleted_source_identities collision, then assert the 'fail-closed: kept'
message prints — but on v8 that setup never reaches the corpus-sweep excluded-alive
branch, so the message never fires and the tests fail regardless of the fix. The
production fix stays genuinely guarded by the honest tests (fail_closed_preserved /
full_rebuild_preserves_alive / reextracted / shrink_involving), verified to fail
without the watch.py change and pass with it, plus the real-OS-symlink
test_requires_symlinks_rebuild_symlink_worker.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers the issue's three reported shapes (const inside an if block,
a for of binding referenced after the loop, and a closure that
outlives the loop), the control case that must not regress (a
reference still inside its own loop), var's continued function wide
reach in the same shapes, and a genuine reference elsewhere still
resolving. The C style for loop case asserts on source_location since
both calls read as identical source text and a plain edge presence
check cannot tell "only the outside call resolved" from "both wrongly
resolved."
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 9801c0eac1)
_js_local_bound_names collected let/const declarator targets, and a
for of/for in loop's own binding, into one function wide shadow set,
but both are block scoped in JS, unlike var, which is function scoped
through hoisting regardless of nesting depth. A reference to a
same named module callable made outside the block that actually binds
the name had its genuine indirect_call edge suppressed, whether the
binding sat inside an if, try, or bare brace block, came from a for
of/for in loop referenced after the loop ends, or was read from a
closure that outlives the loop.
The function wide set now only collects var, distinguished by
checking whether a declarator's parent is a variable_declaration
rather than a lexical_declaration, and a for in loop's own binding
only when it uses var. Every let/const and for/for of/for in binding
is instead folded into extra_locals for exactly its own block or loop
subtree, the same mechanism already used for catch bindings, so a
reference genuinely inside that block or loop still correctly shadows
while one outside it resolves. A C style for loop's own let/const
initializer gets the same treatment, since it is scoped to the whole
loop the same way a for of binding is.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 875c2a4761)
A module instantiation minted its target id with a bare, unscoped
_make_id(name), while a module definition uses _make_id(stem, name). The
two never matched, so instantiating a module defined in the same file:
module counter(...); endmodule
module top;
counter u1(...);
endmodule
split `counter` into a real definition node and a second, sourced phantom
that carried the `instantiates` edge. `top`'s dependency on the module you
actually defined was invisible, and because the phantom was sourced it also
baked this file's path into the id and blocked the corpus-level rewire that
would otherwise collapse it (the #1402 duplicate shape the other
extractors guard against).
Pre-scan module names so a same-file instantiation (Verilog allows
forward/any-order instantiation) resolves to the definition id. A module
defined in another file becomes a SOURCELESS stub instead, so the
corpus-level rewire collapses it onto the real definition rather than a
per-file duplicate — mirroring the Go extractor's ensure_named_node.
Adds regression coverage for both the local-definition link and the
cross-file sourceless stub.
(cherry picked from commit bca8bbac72)
A Java enum wraps its fields, constructors and methods in an
enum_body_declarations node, nested under enum_body after the constant
list. The generic walker recurses into a class body keeping the enclosing
type as the parent scope, but an unknown wrapper node resets that scope to
None (an unknown wrapper usually IS a scope boundary). That reset orphaned
every enum method, field and constructor onto the file:
public enum Planet {
EARTH(5.976e+24), MARS(6.421e+23);
private final double mass;
Planet(double mass) { this.mass = mass; }
public double surfaceGravity() { return 6.673e-11 * mass; }
}
Planet became a bare list of constants; surfaceGravity was emitted as a
file-level function and the constructor was dropped entirely.
enum_body_declarations is not a scope of its own - its members belong to
the enum - so recurse through it transparently, preserving the enum as the
parent scope (mirrors the companion_object and ERROR handling already in
the walker). Constructors and methods now hang off the enum via method
edges and their bodies are walked as call-graph scopes.
Adds regression coverage: members attach to the enum and don't leak onto
the file, an inter-method call is captured, and the constant case_of edges
are untouched.
(cherry picked from commit 820018ee3c)
tree-sitter-swift gives a protocol's body-less method requirement its own
node type, protocol_function_declaration, instead of reusing the
function_declaration used inside a class or struct. The Swift config's
function_types only listed function_declaration, so a protocol like
protocol Drawable {
func draw()
func area() -> Double
}
became an empty node - the method contract every conformer must implement
never entered the graph.
Add protocol_function_declaration to function_types and
function_boundary_types. Requirements now surface as .method() nodes hung
off the protocol, their signature type references (return/param types) are
captured, and they stay distinct from a conformer's implementation of the
same name. Stored property requirements are left alone, matching how
stored properties on a class are already handled.
Adds regression coverage for all three.
(cherry picked from commit 4300054291)
The interface_type branch only handled interface embedding and generic
type-set constraints, so the method requirements that make up an
interface's contract were dropped. An interface such as
type Shape interface {
Area() float64
Perimeter() (float64, error)
}
became an empty node - Area and Perimeter never entered the graph, and
nothing could resolve against the interface's method set.
Walk the method_elem children too, mirroring the receiver-method path:
each requirement becomes a .Method() node hung off the interface via a
method edge. Embedding (type_elem) keeps emitting embeds/references, so
the two never blur.
Adds regression coverage: requirements surface as method nodes, an
embedded interface stays a heritage edge, and an interface method and a
concrete struct method of the same name remain distinct nodes.
(cherry picked from commit 739940a42b)
#3644 persists HCL block attributes into node metadata, which is serialized to
graph.json verbatim and surfaced to the model over MCP (query/get_node) with no
sanitize_metadata pass. A hardcoded credential in a .tf file (password,
db_password, aws_secret_access_key, client_secret, a connection string, ...)
would leak. Redact the VALUE of any attribute whose key names a secret while
keeping the key visible, so instance_type/ami queries still work and a user can
still see THAT a secret is set. Recurses into map values so a nested secret in a
tags/connection map is redacted too.
Also notes Terraform block attributes in the _AST_CACHE_SCHEMA 4 comment (they
are newly-cached facts covered by the same bump #3653 introduced).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers a same name PHP/JS function pair in one file: exactly one
surviving node, the JS call to the colliding name absent as a calls
edge entirely rather than misattached, and the unrelated JS symbol's
own contains edge from the shared file node still present.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit f59ce06c8c)
A reviewer on this PR pointed out that when a JS symbol's id collides
with an existing PHP node (same name, one file, resolved in favor of
the PHP node), the merge still appended every JS edge unconditionally.
An edge meant for the discarded JS symbol silently attached to the
unrelated PHP node sharing its id instead of vanishing with it. The
file node's own always on collision (both passes deliberately reuse
its id) is excluded from this so an unrelated JS symbol's own edges
sourced from the file node are unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit c96169b320)
Covers the issue's exact reproduction (two functions plus the call
edge between them), that line numbers survive the masking pass, that
a script tag with only a src attribute contributes nothing without
crashing, and that a plain PHP file with no script tag is unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 2d408de529)
The PHP tree sitter grammar treats a span that is not PHP, such as an
inline script block, as opaque markup, so any JavaScript declared
there was entirely absent from the graph: functions, calls, and
everything else in that region.
extract_php now runs a second pass after the PHP extraction. Every
part of the source that is not a script body is blanked to spaces,
preserving newlines so line numbers stay accurate, and the masked
source is parsed with the JS grammar the same way the Vue extractor
already masks everything outside its own script block. The two node
sets are merged onto the one file node, so a symbol declared in the
script block looks like any other symbol in the file. A script tag
with only a src attribute and no body masks to an empty region and
contributes nothing, and a file with no script tag at all skips the
second pass entirely.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 2114766c91)
#3598 resolves Kotlin object/class-qualified member calls across files. Adds
two regression tests the PR omitted:
- a `companion object` member call (`Service.create()`), pinning the #2565
enclosing-class attribution the resolver relies on;
- an incremental-rebuild test routed through the real watch._rebuild_code, so
it actually exercises the context builder's allow-list carrying _callable_class
+ method edges (a hand-fed resolution_context_nodes list would bypass exactly
the path that silently regressed in sibling PRs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers three cases: the new cross file resolution (Config.load() called
from another file resolves to object Config), a same file control to
prove the existing in file resolution is unaffected, and an ambiguous
receiver defined in two packages that must yield no edge under the
exactly one candidate guard.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit f22f17b575)
Fixes issue 1698.
Mirrors _resolve_kotlin_qualified_calls just above it and the same
type_by_name plus methods_by_type shape _resolve_swift_member_calls
uses for its own type qualified case: index every Kotlin sourced
class or object by its bare name, resolve the receiver against that
index, then look up the callee among that type's methods. Both steps
bail on anything but exactly one candidate, the same god node guard
every cross file resolver in this module already uses.
Verified end to end: the issue's own cross file repro now produces an
EXTRACTED calls edge, the same file control case is unaffected, and
two objects sharing the same name in different packages correctly
produce zero edges instead of an ambiguous guess.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit a6c89df809)
Still toward issue 1698. Still no observable behavior change on its
own: nothing reads the new kotlin_object_receiver field yet, so
existing edges are unaffected. The resolver that consumes it lands
in the next commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit aed98d6395)
Still toward issue 1698, still no behavior change: the variable is
computed but nothing reads it yet.
A capitalized two segment receiver (MessageHelper in
MessageHelper.isRoomClosed()) names an object singleton or a class or
companion member, statically unambiguous in source. Captured
separately from member_receiver so the existing in file bare name
lookup and its capitalized receiver deferral guard are untouched;
threading this into raw_calls for a dedicated cross file resolver
lands next.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 7728d99295)
Toward issue 1698: a call like MessageHelper.isRoomClosed() has
exactly two navigation segments, so the existing fully qualified
prefix capture (which only fires at three or more segments) never
sees it, and the code deliberately never stamps member_receiver for
this shape either, since that would defer the in file bare name
lookup that already resolves the same file case correctly.
This adds the new variable with no behavior change yet; capturing
into it and threading it through to raw_calls lands in the next
commits.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 91550dd055)
Two different classes that both fail header parsing and happen to
share a class name and a method name must not let a call resolve to
either one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 9e26952d8b)
The issue's own repro, an Unreal style UCLASS whose method
declaration never survives header parsing, must still get a calls
edge from a site that invokes it through the qualified name.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 3d4dec6b9a)
Only the Foo colon colon bar branch tags a fallback label, since it
is the only one with a class name in source to build one from. The
lookup only runs when the primary method index lookup already came
back empty, and only accepts an exactly one candidate match, the
same guard every other resolver in this module already applies.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit b5e066c7d4)
Prep for resolving a scoped call whose callee exists only as a
qualified labeled fallback node: builds a label to node id index
for every node whose label looks like a qualified method
(Class colon colon method with parens), restricted to nodes with a
real source file. Nothing reads it yet, so no behavior change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 1c1f1336e3)
god_nodes ranks purely by graph degree, independent of the community
partition, so clearing it to an empty list on the stale path and
relying on the wiki subcommand's own recompute-if-empty fallback
further down worked today only because wiki happens to be the only
current reader of this value. Recompute it directly here instead, so
a future reader that does not carry the same fallback cannot silently
lose real data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit b071d6e788)
The node id set comparison missed a whole class of staleness: a merge,
a split, or a single node moving from one community to another can
leave the overall node set exactly unchanged while still describing a
different clustering, so a stale sidecar that happened to still cover
the same nodes kept winning. Compare each side's set of community
blocks instead, which catches a partition change even when the flat
node set stays identical, while still ignoring a pure id renumbering
of the same partition per the existing #1667 guard.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 30a5955931)
Guards against a false positive: an up to date sidecar (the normal
case immediately after extract or the clustering command) must not
trip the new staleness warning or take the reconstruction path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 21342bda37)
Confirms the wiki export path specifically, since it is the one that
prints cohesion into article metadata: a stale, impossibly large
cohesion value in the sidecar must not survive into the export once
staleness is detected, proving score_all actually ran on the fresh
reconstruction rather than the fossil value merely being ignored by
coincidence.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 12c50b975a)
Corrupts the analysis sidecar community node id set (the exact
staleness signature the issue describes) and confirms export html
still succeeds, still renders graph.html, and prints the new warning
instead of silently exporting against the fossil.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 4518bae439)
Fixes issue 2386.
The existing fallback only reconstructed communities from graph.json
when the sidecar was missing entirely; a sidecar that exists but was
written by an earlier clustering pass looked identical from the
outside and kept winning, since update and watch never regenerate
.graphify_analysis.json.
This compares the node id set each side covers, cheap and already in
memory, rather than the community ids themselves, since those can
renumber run to run even for the same partition. A mismatch means the
sidecar is stale: prefer the fresh reconstruction, recompute cohesion
with score_all so wiki articles and the HTML export do not swap one
silent degradation for a smaller one, reset gods_data so the existing
self heal at the wiki export site recomputes it, and print a warning
naming the exact command that refreshes the sidecar, since the whole
point of this issue is that nothing currently signals the divergence.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit d4eb7af599)
Toward issue 2386: this hoists the reconstruction from graph.json's
per node community attribute out of the "sidecar is missing" branch
so it runs every time, with no behavior change yet since it is still
only used when the sidecar produced nothing. The next commit compares
it against the sidecar to also catch a sidecar that exists but is
stale.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 0932abc4e0)
A file that only imports a package subpath must resolve just like one
that imports the bare package, using the exact package.json plus two
file shape from the issue, instead of coming out fully disconnected
from the framework it depends on.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 5fb7fc34c1)
A scoped package subpath lands on the scope plus package only, never
on a different package that happens to share the same scope.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 02183335e3)
A plain package subpath now lands on the same resolver target as its
bare import.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit b703d3126e)
An unresolved bare specifier fell through to a ref namespaced id built
from the whole raw string, so a package subpath import like
next/image minted a different id than a bare import of next, even
though both refer to the same package. package.json's own dependency
node for that package is keyed off the bare name alone, so a subpath
import could never land on it and produced no edge at all while the
bare import of the same package resolved fine.
The fallback now strips the specifier down to its package root first:
the first segment for an ordinary package, the first two for a scoped
one, matching how the dependency node itself is keyed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit be0eb7f2a5)