Commit Graph
1555 Commits
Author SHA1 Message Date
lab 1207 ae5493c182 fix(extract): a require binding does not shadow the required class
A \const X = require('./m')\ binding is an import, not a module-scope data
binding. The node it produced in the importing file shadowed the class the
required file exports, so \
ew X()\ bound to the local node and the
required class got no incoming edge.

(cherry picked from commit 66bb69114a)
2026-10-21 19:33:10 +01:00
ayushcodes10 85537d9c01 Add regression tests for the member call defer fix
Covers the issue's own repro, a delegating wrapper that no longer
self loops and an unrelated same named sibling that no longer steals
the edge, plus three controls: a bare unqualified call still
resolves by name, this dot calls still bind through the caller's own
class, and a TypeScript typed receiver still resolves through its
declared type. A fourth control checks that a factory object's own
sibling methods still call each other, the one legitimate case that
keeps resolving locally instead of deferring.

(cherry picked from commit 49c645d2ff)
2026-10-21 19:33:01 +01:00
Jay Hemnani cc488b9076 fix(manifest_ingest): read only a POM's own dependencies
_parse_pom collected every <dependency> in the file with
".//dependencies/dependency", including <dependencyManagement> entries,
which only pin versions, and the <dependencies> of build plugins. Each
became a depends_on edge, so a parent POM that manages its modules'
versions depended on every module.

Read <dependencies> under <project> and under <profiles><profile>, where
Maven takes a project's dependencies from.

(cherry picked from commit 534177d1ea)
2026-10-21 19:33:01 +01:00
Rishi Gupta 2b07311b0d test(security): reject URLs with embedded credentials
(cherry picked from commit 09494cb720)
2026-10-21 19:32:46 +01:00
Rishi Gupta 52dea94b01 test(security): cover malformed URL ports
(cherry picked from commit 57d6217281)
2026-10-21 19:32:19 +01:00
Rishi Gupta 8f6ad0f61e test(security): cover hostless HTTP URLs
(cherry picked from commit fad73d94d9)
2026-10-21 19:32:11 +01:00
safishamsiandClaude Opus 4.8 b90b9df794 test: account for #3987 Step B3.5 block in the B3-merge skill tests
The partial-read coverage check (#3987) adds a python block between the
Step B3 merge and Part C. Two skill-path tests that landed separately in
the 0.9.9x skill work hard-code the block count in that range (3) and
then execute every block in it. Bump the expected count to 4; the
behavioral assertions (token sums, merged node sets, stale-chunk
clearing) still hold because B3.5 only writes an empty
.graphify_partial.txt on the all-cached and codex-merge paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 16:56:09 +01:00
nothariharan 8a982c4a92 fix(skill): a partly-read file is no longer a silent, permanent loss
The Read tool returns at most 2000 lines per call and reports no error when it
truncates, so a subagent that read only the head of a long file still writes a
well-formed chunk. Step B3 treated "chunk exists and parses" as success, and
because build_merge replaces a re-extracted file's nodes, the unread tail was
not merely missed: it was deleted, the run reported success, and the manifest
recorded the file as done so no later --update retried it. On a 4165-line file
that left 103 nodes covering 31% of the document, with 90 of 131 entries gone
and the manifest treating it as finished.

Three changes, in the fragments, so all thirteen disk-dispatching hosts pick
them up from one edit:

The extraction spec (verbose and compact) now states the cap and forbids
emitting nodes for a partly-read file, and asks the subagent to report a
top-level "coverage" map of file -> lines read. A short count is recoverable; a
missing entry is indistinguishable from a complete read, which is why the
machine check needs the prompt to ask.

Step B1 bounds a chunk by total lines as well as file count (20-25 files AND
~6000 lines), and gives any file past 2000 lines a chunk to itself. File count
alone was only half the mechanism: a subagent holding 25 files also dilutes
attention, which is how a 590-line plan came back with zero nodes.

Step B3.5 compares each reported coverage against the file's real length, names
what was short, and hands the list to the two places that decide whether a file
is ever looked at again: save_semantic_cache(partial_source_files=...) stamps
those entries partial, which load_cached treats as a miss, and
_stamped_manifest_files(partial_source_files=...) leaves them unstamped so
detect_incremental re-queues them. Both are the existing contract the library
path already uses for a truncated LLM response (#933, #1950), so the skill and
library paths now recover the same way. Nodes already found for those files are
kept; the next run's replace-on-re-extract swaps them for a complete set.

The check is fail-safe in the direction that loses nothing: no coverage reported
means no file is flagged, and a flagged file is re-read rather than trusted.
Codex collects subagent results in memory with no per-chunk files on disk, so it
gets the prompt-layer fix but not the Step B3.5 gate; its inline extraction has
no chunk file to compare against.

Tests execute the emitted python instead of matching its source (the #2126/#2641
convention), including a round trip through the real semantic cache: the
partly-read file must come back as uncached, while the fully-read file still
hits. Reverting the fix makes that round trip return an empty uncached list -
the file served from cache forever, which is the bug.

Fixes #2225

(cherry picked from commit 0b6a1868af)
2026-10-20 15:40:39 +01:00
56a0329b31 fix(query): only refuse an EXACT-name ambiguity, not a search keyword (#4517)
The ambiguous-name refusal added in #4517 routed through _resolve_single_node,
whose winning tier can be a prefix or substring match, so a bare search keyword
that merely substring-matched several labels was wrongly refused: `query "client"`
returned EXIT 3 because it is a substring of `AlphaClient`/`BetaClient`. That
narrowed query's keyword-search semantics.

Adds _query_name_refusal, which refuses only when the term EXACTLY names several
nodes (the source_exact/exact tiers) -- #4511's `.nonce()` case -- and otherwise
falls through to scoring. The `query` and `--seed` paths use it; explain/path/
get_node keep _resolve_single_node, whose prefix/substring tiers are correct for a
direct node lookup. Adds a regression test that a substring keyword still searches.

Co-Authored-By: shivampal46011 <shivampal46011@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 14:57:06 +01:00
shivampal46011 8ff7b39899 Refuse an ambiguous name in graphify query
query guessed a node when the same name existed in several files, and an ambiguous --seed still exited 0. explain and path already refuse that lookup. query now returns the same candidate list and exits 3, and a --seed that matches nothing exits 4.
2026-10-20 14:53:31 +01:00
Rudy CelekliandClaude Opus 5.5 f000104ba9 fix(detect): classify extensionless scripts whose shebang names a versioned interpreter
_shebang_interpreter returned the interpreter exactly as written, and both
detect's _SHEBANG_CODE_INTERPRETERS and extract's _SHEBANG_DISPATCH list
only the generic names. An extensionless CLI starting with
#!/usr/bin/env python3.12, #!/usr/bin/python2.7 or #!/usr/bin/lua5.4 was
classified as None and silently left out of the scan. Map a version
suffix on a known interpreter back to that interpreter (python3.12 ->
python3, lua5.4 -> lua) in the shared parser, so detection and extractor
dispatch both see it. Unknown names are left as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:28:07 +01:00
Rudy CelekliandClaude Opus 5.5 bf3aac0e28 fix(detect): warn when a PDF cannot be read instead of returning "" silently (#4527)
extract_pdf_text wrapped the whole PdfReader loop in except Exception:
return "", so a malformed or encrypted PDF looked exactly like a PDF with
no text and dropped out of the graph with no trace. Keep returning ""
(callers rely on it), but print a warning naming the file and the error
first. Warn once per path, since detect() and the semantic pass both read
the same file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:28:05 +01:00
berce ffaf5c820d fix(markdown): resolve wikilinks that start with the scan root's own folders (#4559)
Obsidian writes wikilink paths from the vault root, so scanning a folder
below it meets links like [[Corp/wiki/Fox]] from inside Corp/. The
vault-wide fallback matched them as suffixes of root-relative paths,
which never succeeds, and the references edge was dropped. When nothing
matches, drop the longest leading run of the scan root's own folders
and match the rest exactly from the root.
2026-10-20 11:28:04 +01:00
berce 5f4cccf0e7 fix(cli): leave installed skills alone on version and help (#4545)
_run_cli refreshed stale skills and old OpenCode plugins before the
--version/--help early returns, so checking what was installed rewrote
SKILL.md, left a .bak and restamped .graphify_version. Skip both
refreshes for version, help and `<cmd> --help`; the stale-skill warning
still runs, since it only reads. Free-text commands keep refreshing.
2026-10-20 11:28:03 +01:00
8b32528802 test(python): module-call exact-name tests, incl. a cross-module negative
Carries the tests that belong with the module-call exact-name fix (#4577): the
fix landed via the PR's follow-up perf commit, which did not include the test
file. Adds the PR author's five cases (exact-name function/class resolution,
aliased import, fold-only fail-closed) plus a negative cross-module case proving a
call never binds to a same-named child of an UNRELATED module -- the one headline
risk of re-bucketing by exact name.

Co-Authored-By: madhusudanpatnaik <madhusudanpatnaik@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 10:55:51 +01:00
safishamsiandClaude Opus 4.8 374adfadab fix(extract): also redact opaque Bearer tokens; guard against over-redaction
Follow-up to #4566. Adds an opaque (non-JWT) `Bearer <token>` shape to the
credential redactor so `Authorization: Bearer <opaque>` is redacted (the JWT form
was already caught by the eyJ pattern); the word "Bearer" is kept, only the token
is replaced. Adds an over-redaction regression test proving a 40-char git SHA, a
sha256 digest and a UUID pass through untouched, so the redactor never mangles the
ordinary high-entropy identifiers that appear in real rationale labels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-20 10:54:35 +01:00
rajatnagda45 e90a4b5dcf fix(cpp): don't mint sourceless god nodes for Metal/CUDA GPU qualifiers
Metal (.metal) and CUDA (.cu/.cuh) are C++-derived GPU languages that route
through the C++ extractor, but their qualifiers are not part of the C++ grammar:

  - Metal address-space / function qualifiers: `device`, `constant`,
    `threadgroup`, `thread`, `kernel`, `vertex`, `fragment`, ...
  - CUDA execution-space / memory qualifiers: `__global__`, `__device__`,
    `__host__`, `__shared__`, `__constant__`, `__forceinline__`, ...

tree-sitter-cpp parses a leading qualifier as the declaration's
`type_identifier` (the real type is pushed into an ERROR node), and
`_cpp_collect_type_refs` emitted it as a type reference. Each qualifier then
became a sourceless, corpus-shared god node that every shader/kernel
`references` (e.g. `kernel`/`device`/`constant` on sample.metal,
`__global__`/`__device__`/`__host__` on sample.cu, the CUDA ones colliding on
ids `global`/`device`/`host`). That is a provenance violation — a node with no
source_file — and the hubs also pollute god-node rankings in analyze.

Skip a fixed per-extension set of dialect qualifier keywords in the C++
type-ref collector, keyed by file extension so plain `.cpp`/`.cc`/`.h` files
are untouched (there these words are legal identifiers and still resolve as
real user types). Genuine user types in the shader/kernel (the `Vec3` struct)
are preserved.

Documented limitation: `constant Foo* p` buries the real `Foo` type inside the
ERROR node, so `Foo` is not recovered; this change only removes the bogus
qualifier node.
2026-10-20 10:53:16 +01:00
0xkaushik-aiandClaude Sonnet 5.5 44bd623a2e fix(extract): redact credential-shaped text from rationale labels
Docstrings and `# WHY:`/`# NOTE:` comments become rationale node labels,
which are persisted verbatim to graph.json, graph.html, GRAPH_REPORT.md and
the AST cache. A token pasted into a comment therefore ended up in the
artifacts the README tells users to commit and share.

Add security.redact_secrets() (shape-based: vendor-prefixed tokens, PEM
private-key headers, JWTs, URL passwords) and apply it in
_shorten_rationale_label(), before truncation so a cut can't leave a partial
token. That helper is shared by the Python and JS/TS rationale extractors.

Related to #2781 (document text sent to LLM backends unredacted); this
covers only the comment/docstring -> graph path.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 10:53:04 +01:00
bekaandClaude Opus 5.5 3c1754591a fix(extract): route extern "C" headers to the C++ extractor
A C-API header guarded by `#ifdef __cplusplus extern "C" {` has no
class/namespace/template/:: marker, so `_is_cpp_header` left it on
extract_c. The C grammar rejects the linkage block and the header
extracts zero symbols (e.g. whisper.cpp's include/whisper.h and
include/parakeet.h), dropping the project's public API from the graph.

Add `extern "C"` to `_CPP_HEADER_MARKERS`. ObjC sniffing keeps priority.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:50:37 +01:00
Rudy CelekliandClaude Opus 5.5 ffa5a6b051 fix(export): export GraphML from a graph loaded as a MultiGraph
Since #4500, graph.json carries "multigraph": true whenever it holds
parallel edges (imports and calls between the same pair, say), and
graphify export loads it as a MultiGraph. to_graphml's attribute
coercion indexed edges as H.edges[u, v], which needs a key on a
MultiGraph, so graphify export graphml crashed with "not enough values to
unpack" and wrote nothing. Iterate edges(data=True) instead, which works
for both graph types and keeps every parallel edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:50:37 +01:00
Rudy CelekliandClaude Opus 5.5 8d39c75d02 fix(prs): read StatusContext state when classifying CI
gh's statusCheckRollup mixes CheckRun entries (status/conclusion) with
StatusContext entries, the commit statuses external CI such as Jenkins or
CircleCI posts, which carry only a state. _parse_ci never read state, so a
failing external CI showed as NONE, or as SUCCESS beside a passing check
run, and the PR was classified READY instead of CI-FAIL in pr list,
triage and the MCP PR tools. Fold state into the conclusions, with ERROR
as a failure and PENDING/EXPECTED as pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:50:36 +01:00
Rudy CelekliandClaude Opus 5.5 8b06e7033c fix(prs): keep a default branch name that contains a slash
The git symbolic-ref fallback in _detect_default_branch kept only the last
path segment of refs/remotes/origin/<branch>, so a default branch named
release/2.x came back as 2.x and every PR targeting it was classified
WRONG-BASE. Strip the refs/remotes/origin/ prefix instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:50:35 +01:00
Deepjyoti Sarmah a378b4ddbe test: a Rust use rooted at a child mod reaches its declaration (#4564) 2026-10-20 10:35:37 +01:00
rajatnagda45 f893a6e2da fix(swift): extract macro declarations and resolve freestanding macro calls
Swift 5.9 macros were dropped entirely. `macro stringify(...) = #externalMacro(...)`
parses as a `macro_declaration` (freestanding or attached), which was in neither
`function_types` nor `class_types`, so the symbol never entered the graph. A
freestanding call `#stringify(...)` parses as a `macro_invocation`, not a
`call_expression`, so its call edge was lost too.

- Add `macro_declaration` to the Swift `function_types`. It has no `name` field,
  but the name is a bare `simple_identifier` child already covered by
  `name_fallback_child_types`, so the existing name resolution handles it; the
  generic `<T>` type parameter is excluded by the existing type-param scope pass
  (no `t` god node).
- Add `macro_invocation` to `call_types` and give it a callee branch in
  walk_calls: a freestanding macro is a free top-level symbol (resolved by bare
  name, no `self` receiver). Calls inside the macro's arguments
  (`#stringify(helper())`) still resolve because walk_calls recurses into the
  invocation's children.

Attached-macro *usage* (`@AddCompletion` on a type) is an attribute, not a
`macro_invocation`, so it is not linked to the macro — left unresolved
(fail-closed); the declaration itself is still extracted.
2026-10-20 10:35:25 +01:00
rajatnagda45 0a351eaa37 fix(csharp): extract delegate declarations so references don't mint sourceless stubs
A C# `delegate` is a named type, but `delegate_declaration` was in neither
`class_types` nor `function_types`, so the declaration was dropped from the
graph. A field or parameter typed as the delegate (`public BinOp Op;`,
`int Apply(BinOp f, ...)`) still resolved the name `BinOp` through
`ensure_named_node`, which minted a *sourceless* stub and pointed the
`references` edge at it — even though `BinOp` is declared in the same file.

That violates the provenance invariant: a real source-backed declaration must
not lose to a sourceless god node (and the bare-id stub becomes a cross-file
hub shared by every same-named reference in the corpus).

`delegate_declaration` has a `name` field and no body, so adding it to the C#
`class_types` emits a clean empty type node (no phantom members) that the
in-file references now bind to. Namespaced delegates are scoped and contained
exactly like the sibling class/enum/struct declarations.

Delegate parameter/return type references and delegate-instance invocation
resolution are unchanged (out of scope).
2026-10-20 10:35:24 +01:00
rajatnagda45 d2ac22c621 fix(kotlin): extract typealias declarations so references don't mint sourceless stubs
A Kotlin `typealias` is a named type, but `type_alias` was in neither
`class_types` nor `function_types`, so the declaration was dropped from the
graph. A property or parameter typed as the alias (`var onClick: ClickHandler?`,
`fun handler(h: ClickHandler)`) still resolved the name `ClickHandler` through
`ensure_named_node`, which minted a *sourceless* stub and pointed the
`references` edge at it — even though the alias is declared in the same file.

That violates the provenance invariant: a real source-backed declaration must
not lose to a sourceless god node (and a bare-id stub becomes a cross-file hub
shared by every same-named reference in the corpus). Scala and TypeScript
already model type aliases as nodes, so Kotlin was the inconsistent outlier.

`type_alias` has an `identifier` name (covered by `name_fallback_child_types`)
and no body, so adding it to the Kotlin `class_types` emits a clean empty type
node (no phantom members) that the in-file references now bind to.
2026-10-20 10:35:23 +01:00
Rudy CelekliandClaude Opus 5.5 7a32f3324e fix(build): fold node confidence the same way as edges and hyperedges (#4529)
_fold_confidence normalized the confidence/confidence_score pair on edges
and hyperedges, but nodes passed through the build verbatim, so a model
emitting a lowercase enum value, a bare float, or a drifted word for a
node's confidence landed in graph.json as-is. Fold it in
_fold_node_aliases, which both build_from_json and build_merge already
call, so nodes share one normalization contract with edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:35:23 +01:00
Rudy CelekliandClaude Opus 5.5 35f9adb48a fix(llm): decode JSON escapes in community labels salvaged from a truncated reply
When a labeling reply is cut off mid-object, _parse_label_response falls
back to a regex that pulls the complete "<cid>": "<name>" pairs, but it
used the captured string body as-is. Escapes such as \u00e9 and \" then
ended up in the community name as literal backslashes, while the same
name in a complete reply decoded correctly. Decode each salvaged value as
a JSON string body, keeping it unchanged when it is not a valid one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:35:22 +01:00
Rudy CelekliandClaude Opus 5.5 0d69958ff1 fix(rcfile): read .graphifyrc with utf-8-sig so a BOM doesn't drop the first key
Windows PowerShell 5.1 (Set-Content -Encoding UTF8) and older Notepad save
UTF-8 with a BOM. load_graphifyrc read the file as plain utf-8, so the BOM
became part of the first key; unknown keys are ignored by design, so that
setting (say language.inc=php) vanished without a warning. Read with
utf-8-sig, as the other Windows-edited files in the repo already are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:35:22 +01:00
Raphael Malikian 3ab2cbabb4 fix(mcp): preserve parallel edges through graph.json round-trip and MCP output
Fixes #4500

Signed-off-by: Raphael Malikian <rtmalikian@gmail.com>
(cherry picked from commit b30705dd3f)
2026-10-16 18:25:28 +01:00
berce e2d2fddffb fix(skill): dispatch Pi's semantic subagents as pi-subagents' delegate
The Pi skill rendered the shared agent-tool-disk dispatch, which tells the
host to use subagent_type="general-purpose". pi-subagents has no agent by
that name and rejects it as unknown, so semantic extraction never started
(#4494). Step B3's re-run hints named the same agent.

Pi now gets its own dispatch fragment that sends one builtin `delegate`
agent per chunk; delegate has read, bash, edit and write. The two B3 hints
are core slots filled per dispatch variant, so every other host renders
byte-identical output.

(cherry picked from commit a0b3a053a5)
2026-10-16 18:21:34 +01:00
abhay-codes07 97922b9c66 fix(extractors): share the in-flight scan root via base, not graphify.extract (#3666)
`extractors/markdown.py` read the scan root of the extraction in flight with a
function-local `import graphify.extract` + `getattr(_extract,
"_XAML_ACTIVE_EXTRACT_ROOT", None)`. That violates MIGRATION.md invariant #4
("never import from graphify.extract inside this package; direction is
extract.py -> extractors/"), and the `getattr(..., None)` meant a rename of the
module global would silently disable Markdown wikilink (Obsidian vault)
resolution rather than fail loudly.

Move the ambient scan root onto `extractors/base.py` — where the extractors
legitimately live — as `set_active_scan_root()` / `active_scan_root()`.
`_safe_extract_with_xaml_root` now publishes the root there (still saving and
restoring for nested extraction), the two XAML readers and the Markdown vault
lookup read it through the accessor, and `markdown.py` imports it in the correct
direction. The `_XAML_ACTIVE_EXTRACT_ROOT` module global in extract.py is gone;
nothing else referenced it.

Behavior is unchanged — the same process-global root is set around each
extraction and read by the same sites. Tests assert the extractor no longer
imports graphify.extract (fails on the old code), that the set/get/restore
contract nests correctly, and that markdown's accessor IS the base accessor.
The markdown, XAML/dotnet, and language suites stay green; the only failures on
this machine are the pre-existing v8 baseline set (c-include determinism,
wikilink-unicode, two watch cwd-deleted cases), which fail identically on clean
v8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJbLfztSxm2tH5cJwBbe9q
(cherry picked from commit 86721f765d)
2026-10-16 18:21:28 +01:00
ayushcodes10 5fec9d32e8 Add regression tests for the drifted confidence fold
Covers every shape the issue reports: the non numeric drifted words,
a numeric looking string that still backfills confidence_score, a
case only variant that gets recased rather than treated as an
unknown word, the three valid spellings staying untouched, and the
new hyperedge fold. A build_from_json test reproduces the issue's
own warning text end to end and checks it no longer prints.

(cherry picked from commit da369c5ea8)
2026-10-16 18:21:28 +01:00
ayushcodes10 eadf123fa9 Add a regression test for the source less collision survivor
Covers the exact repro from issue 4492: a node with a source_file and
one without, sharing an id that neither file encodes. Checks both
arrival orders keep the sourced node, matching the sibling order
independent tests already covering the lifecycle and path form
tiebreaks right next to it.

(cherry picked from commit 151becb880)
2026-10-16 18:21:28 +01:00
Deepjyoti-Sarmah 6801164019 test(skill): keep graphify off PATH and satisfy pyright in the update test
bash runs the backticked text in Part C's comment when graphify is on PATH, which broke the test in CI. The blocks do not need it. Also narrow three Optional values for pyright. The test checks the same things.

(cherry picked from commit e487672683)
2026-10-16 17:14:07 +01:00
Deepjyoti-Sarmah b4406d7cd7 fix(skill): aider and devin --update populate .graphify_detect.json
Part A reads .graphify_detect.json and Step 9 deletes it, but the aider
and devin --update sections never wrote it again, so Part A raised
FileNotFoundError after any full build.

The sections now write it from .graphify_incremental.json, as the
generated references/update.md does, with each file's own path
convention.

The test no longer seeds the file; it asserts the update section wrote
it.

(cherry picked from commit ca090b6da5)
2026-10-16 17:14:07 +01:00
Deepjyoti-Sarmah ea2bc68ba8 fix(skill): aider and devin code-only runs reach the merge (#4447)
On aider and devin nothing wrote .graphify_semantic.json for a code-only
run, so Part C raised FileNotFoundError, and the --update paragraph sent
the agent from Part A to a merge that reads .graphify_extract.json.

Part B now has the fast-path block, the paragraph names Step 3A, the
fast path, Part C and the merge, and aider's only-deletions guard writes
the file its merge reads.

Adds tests/test_code_only_update_all_hosts.py for the two monoliths and
a skillgen audit entry for the new lines; the 14 generated hosts are
left to #4313.

(cherry picked from commit bbe1a7ca9b)
2026-10-16 17:14:07 +01:00
Deepjyoti-Sarmah 0a2732122e test(skill): no command runs from backticks in -c "..." blocks
Checks every generated skill file statically, and runs the Part C and
update merge statements under bash with a stub interpreter. This commit
alone fails 58 cases (29 per check); the next commit fixes them.

(cherry picked from commit 0bbdd9d36c)
2026-10-16 17:13:54 +01:00
Vibhanshu Mishra 0051c3e6bc fix: materialize extraction for code-only incremental updates
Ensure code-only incremental updates generate a fresh AST-only extraction before merging.

Prevent FileNotFoundError when semantic extraction is skipped.

Preserve existing semantic graph data and cache entries.

Add regression coverage for added, modified, and deleted code files.

Refs #3232

Co-authored-by: OpenAI Codex <codex@openai.com>
(cherry picked from commit 8868862892)
2026-10-16 17:13:49 +01:00
Vibhanshu Mishra 3f75d42a67 fix(exports): preserve custom Neo4j and FalkorDB push URIs
Preserve user-supplied database URIs in generated export instructions.

Prevent Neo4j and FalkorDB push workflows from replacing custom destinations with localhost.

Document safe URI quoting and credential handling.

Add regression coverage for URI forwarding and export-only behavior.

Co-authored-by: OpenAI Codex <codex@openai.com>
(cherry picked from commit 813b94ff51)
2026-10-16 17:13:49 +01:00
samie a. 5fcdb2d791 test: pin in-file calls to salted Rust functions and Go ids outside a collision group
Two guards for the review questions on #4468: an in-file call to
`ignore()`, `e()` or `E()` binds by the name as written and reaches the
salted node (on v8 the calls to `ignore()` and `E()` bind nowhere, since
those functions had no node), and the other types in a Go file that holds
a case-only pair keep the ids they have without the pair.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5c49344807)
2026-10-16 17:12:53 +01:00
samie a. 918483e6a3 fix(rust): resolve a forward reference to a type that keeps its id beside a case-only sibling
#3782 left a reference placed above `struct Handle` on a sourceless stub
whenever another file-level item folded to the same id (`fn handle`,
`const HANDLE`, an `impl` for `HANDLE`), because walk() gave that id to
whichever item came first. Those items now get ids of their own, so the
struct owns its id and the reference can resolve to it, as a reference
below the declaration already did. Without this, the stub only folds back
when the type's name is unique across the corpus.

A declared type is now pre-registered unless a non-type item has its
exact name (`fn Foo()` beside `struct Foo {}` still share one node), and a
`type HANDLE` alias still keeps its stub. The #3782 test that pinned the
stub for the case-only shapes now asserts the resolution; the shapes that
still share a node keep the stub assertion.

local_type_names becomes a frozenset, which is what
_rust_collect_type_refs is annotated to take.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f710c8a16e)
2026-10-16 17:12:53 +01:00
samie a 102fff9f15 fix(go,rust): keep same-file declarations whose names differ only by case apart
make_id casefolds, so in one file `type ResponseWriter interface` and
`type responseWriter struct` (gin), or `struct Ignore` and `fn ignore`
(ripgrep), mint one node id. The walk keeps whichever
declaration it meets first and drops the other: the dropped struct's
methods hang off the interface, and a dropped function's calls are
credited to the struct.

Go: the #2779 census now also counts `type` declarations, using its
existing rule. The exported type keeps the plain id and the other is
salted; its methods, receiver anchors and in-file type references use the
salted id. Types are counted first so methods are keyed under their
owner's final id.

Rust: the #3782 pre-scan already lists every file-level item behind an
id, so it doubles as the census. The one declared struct/enum/trait in a
group keeps the plain id (type positions and `impl` blocks name it, so it
and its methods do not move) and every other member is salted with a
hash of its own name; with no single declared type, every member is
salted. `#[cfg]` twins are one name and still share one node.

A name that does not collide keeps its id. The census is per file: a
case-only pair split across two files of one Go package is not covered.

Related to #3302 and #2810.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit bef78ffdbd)
2026-10-16 17:12:53 +01:00
rohit-jsfreaky 425a402bd7 feat(solidity): resolve calls, super calls and modifiers inherited from base contracts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit cdcec4510a)
2026-10-16 17:12:42 +01:00
rohit-jsfreaky e9d06faab3 test(python): pin that forwarded import edges only gate fixture calls on a rebuild
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 5b0ca535c1)
2026-10-16 17:11:42 +01:00
rohit-jsfreaky 9b667f6edf feat(python): type member calls on pytest fixture parameters from the fixture's returned class
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
(cherry picked from commit 3093df2250)
2026-10-16 17:11:42 +01:00
FolatheDuckofDuckingburg 6948da8e8a test(ts): cover method-assigned field receivers
(cherry picked from commit 4b8c5d68f1)
2026-10-16 17:11:41 +01:00
Mpasha17 c4589a0c1b fix(ts): don't guess between members the normalized key can't tell apart
Review on #4479: after the exact-name lookup, the `_key` fallback in
_resolve_typescript_member_calls still matched loosely. With #4475 splitting
close/#close, updateHash/_updateHash and set/$set into separate nodes, one
normalized key can now map to two members, and method_index kept whichever
was registered last. So c._close() bound to #close, c.UpdateHash() to
_updateHash, and c.a() to $a, none of which is the called name. Track keys
that more than one member normalizes to and skip the fallback for them (no
edge rather than a guess). Exact hits, and loose hits with a single
candidate, are unchanged.

Co-Authored-By: Grok Bot
(cherry picked from commit a1ce057757)
2026-10-16 17:11:41 +01:00
Mpasha17 70a600196f fix(js): keep class members that differ only by _, $ or # as separate nodes
make_id drops _, $ and #, so updateHash/_updateHash, close/#close and
set/$set in one JS/TS class body minted one id and the second member
disappeared, with its calls attributed to the survivor. Group each class
body's member names by id and reuse the Python/C# collision rule
(_underscore_salted_nid, now with configurable private prefixes): a unique
public name keeps its id, colliding private ones get a salted id.
Non-colliding members keep their ids. The TS cross-file member resolver now
matches the exact member name before falling back to the normalized key, so
c.set() still binds to set and not $set.

Fixes #4475

Co-Authored-By: Grok Bot
(cherry picked from commit 03beac9d09)
2026-10-16 17:11:41 +01:00
Hemanth 627c49a770 fix(python): salt module-level class ids that collide with a function or class (#2810)
The Python collision pre-scan (#3302) recorded module-level functions and
class methods but not module-level class names, so a class sharing an id
with a function or another class after normalization still lost one
declaration to first-wins add_node:

- `class Session` / `def session()` (requests/sessions.py): session() is
  absent from the graph, and which one survives depends on declaration order
- `class Foo` / `class FOO`, `class Pair` / `class _Pair`

Record module-level classes in the same scope as functions and salt the
class id with the existing _underscore_salted_nid rule. Methods of a salted
class are minted under the salted id, so the pre-scan now keys their groups
under the class's resolved id; keying them under the plain one would drop
the existing `_get`/`get` protection inside that class. Same-file base-class
references go through the same salt (as C# already does), so
`class Sub(Session)` binds to the real class instead of minting a sourceless
`Session` stub. The pre-scan also looks through decorated_definition, so
`@dataclass class Session` and the `_x` / `@property x` method idiom are
covered too.

Ids change only for files that contain such a collision; cached AST results
for them keep the collapsed ids until the AST cache schema is bumped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 03b420df97)
2026-10-16 17:11:41 +01:00