Since a real re exec replaces the process it runs in, every test that
needs to observe whether it fired runs a small probe in its own
subprocess with a deliberately constructed environment, rather than
mocking inside the pytest process itself, which pytest's own handling
of its current test marker would defeat anyway. Cover each of the
four affected commands re executing with the seed pinned when unset,
an explicit seed never being overridden, unrelated commands never re
executing, the guard against firing while pytest itself is mid test,
a failed re exec being survived rather than raised, and a full real
invocation of update still completing successfully end to end through
the whole re exec.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
The #3359 scaling guard flaked on CI 3.13 (3.2x vs a 3.0x ceiling): at n=1000 the
measurement (~0.03s) is small enough that a GC pause landing in process_time
inflates the ratio. Not a regression — the normalizer path is unchanged; it was
green on 0.9.64 by luck. Harden it: disable GC around the measured section,
measure at 4k/8k where O(n) work dominates and linear converges to a clean ~2x,
and relax the ceiling to 3.5x (still separates linear from quadratic 4x).
Observed locally: 2.10x, stable across repeated runs on 3.10 and 3.13.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#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>
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)
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)
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)
#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)
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)
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)
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)
graph.json shipped imports/imports_from/re_exports edges whose target was an
external module (stdlib, a third-party dep) with no entry in nodes[]. Every
loader (networkx node_link_graph) materialises such an endpoint as an
attribute-less phantom, so declared node-count != loaded node-count, and
merge-graphs fragmented the same module into one node per repo.
- build_from_json now mints a typed external stub node (type=external,
external=True) for an import-family edge whose target is otherwise undeclared,
instead of dropping the edge. Non-import relations are unchanged, so a
sourceless external call target stays suppressed (#3156, no phantom god-node).
- The --no-cluster / incremental update path writes the raw merged extraction
rather than a builder graph, so it applies the same stubbing via the shared
mint_external_stubs_in_data helper. Idempotent; grows the node set so the
#479 shrink guard is unaffected.
- prefix_graph_for_global leaves external stubs unprefixed (no repo:: tag), so
the same stdlib/dependency module unifies into one global node across a
cross-repo merge instead of fragmenting per repo.
Adds regression tests for build-level stubbing, serialized-graph consistency,
the #3156 non-import drop, idempotency, cross-repo unification, and an
end-to-end `update --no-cluster` integrity check through the real rebuild.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#3603 resolves cross-file alias/import/require/use targets onto the real
defmodule node. Two guards ported from the competing #3566 so the resolver
does not over-reach:
- Only top-level modules are indexed as resolution targets. extract_elixir
marks them with _elixir_module; a nested defmodule (labeled with its bare
inner name) is left unmarked so an unrelated `use <Name>` from another file
cannot latch onto it. The marker rides through incremental rebuilds via the
resolution-context allow-list in watch.py and cli.py, so the fix does not
decay on the `graphify update` / watch path.
- A same-file alias is left unresolved: the import edge's source is the file
node, which already `contains` that module, so retargeting would duplicate
the pair as a weaker `imports` and clobber `contains` in the non-multi graph.
Adds regression tests for both guards plus an incremental-rebuild test that
routes through the real watch._rebuild_code builder (the path #3566's own
test bypassed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_reconcile_markdown_links treats every AST-tier references edge owned by a re-parsed
Markdown file as an authored [link](file) and prunes any pair it did not author. A code-span
mention is also a references edge owned by its document, so a plain `graphify update` over
an existing graph, with or without changes, dropped every mention edge; only a build from
scratch kept them.
A mention targets a code symbol, never a file representative, so the reconcile now leaves
those edges to the AST ownership rule: re-extracting the document regenerates them and
re-extracting the code side keeps or drops them with the target node.
(cherry picked from commit 73cf2ae56c)
Three findings from the automated review of #3562, all reproduced:
A dotted span with a non-code extension (README.md, pyproject.toml) was classified as a
symbol because only CODE_EXTENSIONS was checked, and every dotted form was reduced to its
last segment, so time.sleep matched a repo's own sleep(). The dotted form now keeps its
whole chain, the file early-exit also checks DOC_EXTENSIONS, and a bare-name match must
carry every qualifier as an owner label or a segment or stem of its source path.
_match_cited_file used lstrip("./"), which strips characters, so a .github/... cite could
never match by suffix. The leading ./ and ../ segments are now stripped as segments, and
the suffix form also matches a root-level file.
An explicit ./ or ../ cite that escaped the corpus fell through to the suffix rule and
could land on an unrelated copy of the file. It now resolves relative to the document
only.
(cherry picked from commit 3f84e7f3e2)
A module with no matching definition anywhere in the corpus, stdlib
or a hex dependency, must stay an external reference rather than
getting fabricated.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit da5e23a921)
Two different files each declaring a module with the same bare name
must leave the reference exactly as extracted rather than guessing
which one the caller meant.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 690d387626)
A module importing something declared in the same file already
resolved before this fix, since both ids share the same stem, and
must keep working exactly as before.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 8320da005a)
A module aliased from a different file now resolves to that module's
real, non dangling node.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 53877c6874)
Pooling methods across every node sharing a type's bare label is
what makes a split impl block work, but it is only safe when every
one of those nodes really is an impl block for the SAME type. Two
unrelated structs that happen to share a bare name, each with its
own different methods, could still link a caller to a method that
only exists on the wrong one, most concretely when one type
overrides a trait default the other relies on and so never gets an
explicit method node for it.
A struct, enum, or trait declaration gets a contains edge from its
file; an impl block never does. So the type name itself is now
treated as ambiguous, and pooling skipped entirely, whenever two or
more nodes sharing the bare label are real declarations rather than
impl blocks. A single declaration with any number of impl blocks
spread across files, the shape this pass exists for, is unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 141662b0eb)
A plain dict assignment silently overwrote an earlier target whenever
two method nodes shared both a source impl node and a stripped
label, which would have kept whichever one was seen last instead of
surfacing the case as the ambiguity it is. The index now keeps a set
per key and folds every match into the candidate set before the
exactly one candidate check runs, so a genuine duplicate still bails
instead of guessing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 2cbbf1a670)
The check against a pair already having an edge matched on ANY
relation, so a caller that already had, say, a references edge to
the exact same target (from also naming the type elsewhere) silently
suppressed a real call to it. The two relations are not mutually
exclusive and an existing non calls edge says nothing about whether
a call was resolved. Scoped the check to calls edges only, matching
an existing precedent elsewhere in this module for the identical
class of mistake.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 94735ce241)
Two different structs sharing a bare type name with a colliding
method name must yield no edge rather than a guess, and a self call
to a method that genuinely does not exist anywhere in the corpus
must not fabricate a target.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit afeeefa109)
Covers the issue's own shape (impl Foo split across two files, a
self call in one block reaching a method defined in the other) and a
same file negative control proving the pre existing bare name
resolution is unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 2d161af639)