1193 Commits
Author SHA1 Message Date
berce c228df205e fix(install): name the symlink target when writing CLAUDE.md (#3805)
When ~/.claude/CLAUDE.md is a symlink, the registration block is written
into the file it points to, but install only printed the link path, so a
rules file elsewhere changed without any mention. Print `link -> target`
for symlinks on install and on the project-scoped removal path.

(cherry picked from commit f05abb2b25)
2026-09-30 20:41:10 +01:00
berce b30a40a171 fix(install): keep a symlinked instructions file on uninstall
When removing the graphify section leaves an instructions file empty,
uninstall deletes it. If the file is a symlink, only the link was deleted
and the graphify section stayed in the file it points to. Now uninstall
writes through the symlink instead, so the target is cleaned and the
link stays.

Applies to .claude/CLAUDE.md, CLAUDE.md, GEMINI.md, AGENTS.md,
CODEBUDDY.md and .github/copilot-instructions.md.

(cherry picked from commit cf260b5163)
2026-09-30 20:41:10 +01:00
Aditya kumar singh c5c4ee6bfd fix(manifest): treat virtual-workspace-root Cargo.toml as skipped by design (#3910)
(cherry picked from commit 89b4e4e147)
2026-09-30 20:41:10 +01:00
berce 81bb5280c0 fix(hooks): report hooks written by an older release as out of date (#3771)
Upgrading the package never touches hooks already on disk, so repos
initialised before a hook fix (such as the #2253 console-window launcher)
keep running the old script while `graphify hook status` says
"installed". Status now compares the installed graphify block with the
current template, ignoring the two per-install values (the pinned
interpreter and the viz limit export), and tells the user to run
`graphify hook install`, which already rewrites the block in place.

(cherry picked from commit 2e43d53ffa)
2026-09-30 20:41:10 +01:00
VikramLex 18d834f771 fix(label): keep labels already named when a nested retry fails to parse
_label_batch_with_retry retried the ids a truncated reply left out (#3671)
inside the try that guards the batch's own parse. A retry that still would
not parse therefore landed in this batch's except branch, which re-split the
whole batch, asking the backend again for ids it had already named, and when
that failed the same way the error discarded every name of the batch, the
ones already parsed included. Likewise, once a parse failure split a batch,
one half failing at the base case discarded the names the other half got.

Parse the batch's own reply inside the try, then retry the missing ids (all
of them after a parse failure, the plain #1278 split) outside it, one half at
a time: a half that still won't parse is recorded and the other half still
runs. A batch re-raises only when none of it could be named, so
label_communities still skips a batch that yields nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 24055dabfc)
2026-09-30 20:41:10 +01:00
Yi-111-a bab648b0c3 fix(export): index (label, id) before the graph DB push upsert loop
`push_to_falkordb` and `push_to_neo4j` upsert every node with
`MERGE (n:<Label> {id: $id}) SET n += $props`, which matches on (label, id).
Neither path created an index on that pattern first, so the engine had to
scan every node already carrying the label to decide whether the node is
already there. Each upsert then costs O(nodes with that label) and a full push
degrades to O(n^2): on the graph in #3804 (167,428 nodes / 209,215 edges
across 6 labels) throughput collapsed from ~10,000 node upserts/60s to
~100 upserts/300s partway through, with the database at ~97% CPU.

Create one index per distinct label up front, before the upsert loop. The
label set is derived exactly as the push loops derive it, so every label a
MERGE matches against is covered. Neo4j uses IF NOT EXISTS to stay idempotent
across re-pushes; FalkorDB's CREATE INDEX has no such clause, so a re-push
against an already-indexed graph is tolerated rather than failing an
otherwise valid push.

Refs #3804

Co-Authored-By: opencode space-bunny-free <noreply@opencode.ai>
(cherry picked from commit a3275e2c97)
2026-09-30 20:41:10 +01:00
neo1777 480115033d fix(cluster): keep hub-only neighbours with their hub under --exclude-hubs
With exclude_hubs_percentile set, excluded hubs are removed from the
graph that gets partitioned. A node whose only neighbours are hubs (e.g.
a local symbol of a hub file, or a dependency entry of a manifest) is
then isolated in that subgraph and came back as a singleton community,
while the hub itself was reattached by majority vote. Every such node
ended up cut off from the only node it connects to, and the community
count inflated with singletons.

Such nodes are now held out of the partition and placed after the hubs
are reattached, with the same majority vote the hubs use; since all of
their neighbours are hubs and every hub is placed by then, the vote
always has a winner. A self-loop does not count as a neighbour. True
isolates, and nodes whose only edge is a self-loop, are unchanged.

Follow-up to #919, where --exclude-hubs was introduced. The cluster()
docstring now says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 39a24a0626)
2026-09-30 20:41:10 +01:00
Kevin f9a6893844 fix(extract): parse only Astro frontmatter and scripts in the AST pass
extract_astro fed the whole .astro file to the JS grammar. The template is
not JS, so every page reported a syntax error (usually at line 1-2) and
symbols tree-sitter could not recover past the template were dropped.

Blank everything outside the frontmatter and JS <script> bodies (keeping
newlines so locations still match, as _vue_mask_non_script does) and parse
with the TS grammar, Astro's default. Scripts with a non-JS type
(application/ld+json, ...) are blanked too. The existing regex import
rescue is unchanged.

Fixes #2788. Covers the unmasked-script half of #3337.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit fe07657fe1)
2026-09-30 20:41:10 +01:00
neo1777 a20c272d46 fix(analyze): make the cross-repo/directory surprise bonus match its reason
_top_level_dir() returned the whole path when it had no "/", so two
files at the scan root (a flat folder of notes, or README.md and
setup.py) got different "top-level dirs". Every such cross-file edge
(other than suppressed INFERRED calls/uses) received +2 and the reason
"connects across different repos/directories" in surprising_connections,
which is false.

- A path without "/" now maps to the scan root ("."). "." rather than ""
  keeps root files distinct from an absolute path outside the scan root,
  whose first component is "".
- Backslashes and a leading "./" are normalized first: build_from_json
  converts backslashes but keeps "./", and serve.py's _load_graph() and
  API callers can skip build_from_json altogether.
- The comparison key is (node "repo", top-level dir). merge-graphs and
  global add keep source_file repo-relative and tag nodes with "repo",
  so two repos can share directory names; single-repo graphs have no
  "repo" and are unaffected. On merged graphs a cross-repo edge now gets
  the bonus whenever the repos differ; before, it depended on whether
  the top-level directory names happened to differ.

Tests: the flat case (fails on d6eaa8a), merged graphs, backslash and
"./" paths, plus pins for what must not change (root vs subdirectory,
root vs out-of-root absolute path).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 05d6410535)
2026-09-30 20:41:10 +01:00
rajatnagda45 4dc58d17a7 fix(vbnet): resolve type/module-qualified calls to their target method
The invocation resolver only fired for self/unqualified receivers (Me, the
enclosing type, or a bare name). A call qualified by a type or module name —
Helpers.Log(), the idiomatic way to reach a shared Module Sub — matched
nothing and produced no edge at all, so the caller was disconnected from the
method it invoked.

Map each declared type/module's short name to its owner key, and resolve a
qualified call against the receiver's owner (falling back to raw_calls for
cross-file partial-type merging). A call through a value receiver (a local
variable) still has no known owner and is left unresolved rather than
guessed (fail-closed).

(cherry picked from commit 1297c18f50)
2026-09-30 20:41:10 +01:00
rajatnagda45 85390c83a4 fix(solidity): extract file-level free functions and their calls
Free (file-level) functions have been valid Solidity since 0.7, but the
extractor only descended into named type declarations (contract, library,
interface, abstract contract). A function declared at source-unit scope —
and every call made from its body — was dropped entirely, so a file of
free utility functions produced no nodes at all.

Walk source-unit function_definition nodes, emit each as a file-contained
callable node, and resolve an unambiguous same-file free-function call to a
calls edge. Member calls (Lib.f()) are parked as raw_calls for cross-file
resolution; calls that cross into contract scope are left unresolved rather
than guessed (fail-closed).

(cherry picked from commit d2e56e932f)
2026-09-30 20:41:10 +01:00
rajatnagda45 5bb8a61140 fix(cpp): extract enum / enum class specifiers and their enumerators
`enum_specifier` was missing from the C++ class_types, so an `enum` or
`enum class` produced no nodes at all: the enum type and every enumerator
vanished from the graph, even though struct/class specifiers right next to
them were extracted normally.

Add `enum_specifier` to the C++ class_types — its name and body sit on the
same `name`/`body` fields as struct_specifier (type_identifier +
enumerator_list) so the type gets a node and its body is walked — and add
`_cpp_extra_walk` to emit a node plus a `case_of` edge per `enumerator`.
Anonymous enums (`typedef enum { A, B } Flag`) carry no type name and are
skipped by the existing name guard rather than emitting a nameless node.
This is the C++ parity of Java's enum_constant (#1719), Swift, and Scala.

(cherry picked from commit 9cde6c2a7f)
2026-09-30 20:16:06 +01:00
rajatnagda45 35207e9b4b fix(scala): extract Scala 3 enum definitions, their cases and methods
`enum_definition` was missing from the Scala class_types, so a Scala 3
`enum` produced no nodes at all: the enum type, its cases, and any methods
declared in its body all vanished from the graph, and references to the
enum resolved to nothing.

Add `enum_definition` to the Scala class_types and `enum_body` to the
body fallbacks so the type gets a node and its body is walked, and add
`_scala_extra_walk` to emit a node plus a `case_of` edge per case. Cases
nest under an `enum_case_definitions` wrapper as `simple_enum_case`
(`case Red`) or `full_enum_case` (`case Add(x: Int)`); both carry the case
name as an `identifier` child. This is the Scala parity of Java's
enum_constant (#1719), Kotlin's enum_entry (#1738), and Swift.

Case constructor parameter types and per-case `extends` arguments are not
linked; the case node and its case_of edge are the fix for the dropped
members.

(cherry picked from commit 175af52419)
2026-09-30 20:10:34 +01:00
kclamano b507207a1a fix(llm): warn once when file exceeds _FILE_CHAR_CAP in _read_files (#3773)
(cherry picked from commit 5f0ae1f784)
2026-09-30 20:10:34 +01:00
Jan Mohr 03ac9071fd fix(java): resolve calls to inherited methods and super
Member calls were only matched against the receiver's own type, so
this.start(), super.close() or worker.start() got no edge when the method
lives in a superclass. Walk up the extends chain and take the first
declaration. No edge if the chain leaves the project.

Fixes #3931

(cherry picked from commit a2da367fc7)
2026-09-30 20:10:34 +01:00
rajatnagda45 244f777323 fix(zig): emit a node per enum member with a case_of edge
The enum body walk only emitted an enum's methods; its members — the
`container_field` nodes (`red`, `north = 0`) — were dropped, so an
`enum { red, green, blue }` produced a node for the enum with no members at
all, leaving it a leaf.

Emit a node plus a `case_of` edge per enum member. The handler is gated on
the `enum_declaration` parent so struct and union fields, which share the
`container_field` node shape, stay untouched. This brings Zig to parity with
the other languages that have enums: Java's enum_constant (#1719), Swift,
Scala, and Rust.

(cherry picked from commit 1e44311e22)
2026-09-30 20:10:34 +01:00
rajatnagda45 b8ee397721 fix(rust): emit a node per enum variant with a case_of edge
The enum_item handler walked each variant only to collect its payload type
references (`Click(Logger)` -> Logger), but never emitted a node for the
variant itself. So an `enum Shape { Circle, Square, Empty }` produced a node
for Shape with no members at all — the enum was a leaf and its variants were
absent from the graph.

Emit a node plus a `case_of` edge per enum_variant, keyed on the variant's
`identifier` child, alongside the existing payload-type walk. This brings
Rust to parity with every other language that has enums: Java's enum_constant
(#1719), Kotlin's enum_entry (#1738), Swift, and Scala.

(cherry picked from commit 13903a8ac3)
2026-09-30 20:10:34 +01:00
berce 8ae9878465 feat(cli): refresh stale skills after a package upgrade (#1805)
After `uv tool upgrade graphifyy` or `pip install -U graphifyy`, the
installed skills stay on the old version. `graphify install` only fixes
the platform it detects, so codex, opencode, gemini and the others keep
running outdated instructions until someone reinstalls each one by hand.

The CLI now checks the version stamp of every user-scope skill on its
first run after an upgrade, `graphify update` included, and re-copies
the ones that are older than the package. This is on by default and can
be turned off with GRAPHIFY_NO_AUTO_REFRESH=1.

It is deliberately conservative about what it touches:

- only skills graphify installed itself (stamp and SKILL.md both present)
- never a skill newer than the package, so nothing gets downgraded (#1568)
- never a directory that two installers write to, such as copilot and
  `graphify vscode install`, or two platform dirs symlinked to the same
  place, because the stamp can't say which variant lives there
- a locally edited SKILL.md is kept as SKILL.md.bak (#3144)

Install, uninstall and the refresh take the same lock per skill
directory, so two processes never write through each other's fixed
.tmp staging names, and an uninstall can't race a refresh that would
put the skill back. It is an OS file lock (flock on POSIX,
msvcrt.locking on Windows). The OS releases it however the holder
exits, so a crash never leaves a directory locked and there is no
staleness timeout to tune. The lock file lives under
~/.cache/graphify/skill-locks/ rather than in the skill directory, so a
project install leaves nothing behind in the repository. Install and
uninstall wait for the lock; the refresh skips a directory another
process holds and reads the stamp again once it has the lock. Where the
file system can't lock, install and uninstall run unlocked as they did
before, and the refresh leaves the skill to the version warning.

Output goes to stderr, one line per refreshed directory, so --json and
the MCP stdio server are unaffected. A failed refresh never breaks the
command the user actually ran: the stamp stays old and the existing
version warning still points to the manual fix.

While here, the version warning now also checks the gemini skill, which
it used to miss because gemini is not in _PLATFORM_CONFIG, and the vscode
skill path moved into _vscode_skill_destination() so install, uninstall
and the refresh share it. The README lists the new variable and mentions
the refresh in the version-mismatch troubleshooting entry.

(cherry picked from commit 50ebe642af)
2026-09-29 14:37:10 +01:00
harshaygadekarandClaude Opus 4.8 e34c558035 test(kotlin): add #3899's annotated-inferred-property regression test
The code fix landed via #3915 (byte-identical hoist). Keep @harshaygadekar's
independent regression test for #3884 (found simultaneously) for coverage
and contributor credit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-29 13:51:37 +01:00
nothariharan af153f85ea fix(kotlin): keep annotated inferred properties in the graph
An annotation on a class property with no explicit type never assigned
`line`, so the annotation loop raised UnboundLocalError and _safe_extract
dropped the whole file. Set `line` before the type-node check so the file
and its attribute edge both survive.

Fixes #3884

Co-Authored-By: Grok 4.7 <noreply@x.ai>
(cherry picked from commit 03ba8cc9a3)
2026-09-29 13:49:39 +01:00
harshaygadekar 95c498f967 fix(extract): ignore contained symbols when resolving imported modules (#3887)
In _resolve_python_member_calls, candidate imported modules are gathered from imported_by_filenode by checking 	 in contains_children. Following #3410, functions enclosing a nested def also have contains edges to their nested functions. When a caller imports both a module and a function from that module (where the function contains a nested def), both the module and the function matched the candidate filter because the function's _module_stem_key evaluates to the stem of its source file. The resulting len(mods) == 2 triggered the ambiguity guard and dropped all module.func() calls from that file.

Exclude contained symbols by verifying 	 not in file_of_node, which is already populated for all containment targets and excludes top-level file/module nodes.

(cherry picked from commit 542e4869ba)
2026-09-29 13:49:39 +01:00
rajatnagda45 494c08452c fix(blade): link a view to the layout it @extends
The Blade extractor recognised @include, <livewire:> components, and
wire:click bindings, but not @extends. Template inheritance is the primary
structural relationship in a Blade view — most page templates render inside
a layout via @extends('layouts.app') — so the child was left with no edge
to its parent layout in the graph.

Emit an 'extends' edge from the view to the named layout, mirroring the
existing @include handling (dotted view name -> path). Adds the first test
coverage for the Blade extractor.

(cherry picked from commit efeeeefa53)
2026-09-29 13:49:39 +01:00
rajatnagda45 4f6a4efd1f fix(razor): extract methods from @functions blocks, not just @code
@code is the Blazor (.razor) spelling of an embedded C# member block;
@functions is the equivalent Razor Pages / MVC (.cshtml) spelling. Both
compile to class members, and this extractor serves both file types, but
only @code was matched. A .cshtml page that declared its methods inside an
@functions block lost every one of them.

Match @functions alongside @code in the block regex; the existing
brace-balanced body scan and method extraction are reused unchanged.

(cherry picked from commit e3693430b3)
2026-09-29 13:49:39 +01:00
berce 452e7b7da2 fix(sql): walk block nodes so DDL before a DO block is extracted
With a bare `BEGIN;` transaction, a later `DO $$ ... END $$;` makes
tree-sitter-sql read everything from BEGIN up to the DO body's END as a
single block node. The top-level dispatch had no branch for `block`, so
every CREATE TABLE inside it was silently dropped. Walk block nodes the
same way transaction nodes are walked.

Fixes #3886

(cherry picked from commit 8f47001d5b)
2026-09-29 13:49:39 +01:00
himanshupatro-334 3394d10ccf fix: correct node info field names
(cherry picked from commit 390264da80)
2026-09-29 13:49:39 +01:00
shobhitagnihotri69 2aa891be4e fix(detect): warn when pypdf extra is missing in extract_pdf_text (#3702)
Closes #3702

(cherry picked from commit 81e1c6e627)
2026-09-28 10:18:13 +01:00
shashank-100andClaude Opus 4.8 3cf033cce2 fix(watch,cli): publish label sidecars atomically, labels before signatures
Publish the community-label and signature sidecars via the existing
atomic write helper and write labels before the .sig, so an interruption
between the two leaves the .sig trailing (which the staleness guard reads
as a mismatch and safely re-labels) rather than silently keeping stale
labels for a clustering that no longer exists.

Cherry-picked from #3853 by @shashank-100; the unrelated CI-matrix change
(dropping Python 3.13/3.14) in that PR was omitted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-28 10:16:51 +01:00
ayushcodes10 f5afa6f37a Add a regression test for the community listing real node filter
Covers issue 3794 directly: a community made of real code nodes plus
a rationale docstring fragment node and a concept node must only
count and list the real code nodes in its Nodes (N) line, matching
the exact repro shape from the issue. Confirmed against pre fix code
via git stash, the test fails there and reproduces the issue's own
example output almost exactly.

(cherry picked from commit 02435f5dd3)
2026-09-28 10:15:38 +01:00
L4XB e27d51d7c0 fix(detect): keep tables in place and read all text in docx sidecars
docx_to_markdown wrote every paragraph first and appended all tables at the
end, so a table lost the heading it belongs to. Paragraph.text also drops
tracked insertions, content controls, fields and text boxes. And a cell's
text went into its markdown row as is, so a line break split the row and a
pipe added a column.

Walk the body in document order, read each paragraph's runs inside those
containers (skipping deleted text and the fallback copy of a text box), and
put a cell on one line with its pipes escaped.

(cherry picked from commit ee72d5256f)
2026-09-28 10:15:38 +01:00
Peter Pshenichny 5793226ff4 fix(markdown): parse escaped wikilink aliases
(cherry picked from commit f50f96f3c8)
2026-09-28 10:15:38 +01:00
breken-ai efc5a4ded3 fix(wiki): keep an article labelled index from overwriting index.md
A god node labelled index() (an ordinary web view) or a community named
after one slugs to "index", so to_wiki wrote its article to index.md and
then replaced it with the catalog; the catalog entry linked back to the
catalog itself. Reserve the index slug in the dedup set, and link the
navigation footer to index.md directly so the label resolver can map an
"index" label to its own deduplicated file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit aad5e0feab)
2026-09-28 10:15:38 +01:00
rajatnagda45 aa2584c455 fix(r): resolve R6 self$ and private$ intra-class method calls
An R6 method reaches a sibling only through self$method() or private$method(),
never as a bare name, but walk_calls only handled a bare identifier callee. So
every intra-class call inside an R6 class was dropped, leaving methods that
clearly call each other disconnected in the graph.

Handle an extract_operator callee whose receiver is self or private: resolve
the method against the enclosing class scope, which is already reachable from
the caller via scope_parent. super$ is deliberately excluded — it dispatches to
a parent class this single-file pass cannot see, so a local binding would be
wrong.

Added a regression test covering both self$ and private$ calls.

(cherry picked from commit 4e00eaf105)
2026-09-28 09:14:38 +01:00
rajatnagda45 a4c6323910 fix(r): recognise namespace-qualified class constructors (R6::R6Class)
R6::R6Class(...) is the idiomatic, library()-free way to define an R6 class,
but call_parts returns the qualified text 'R6::R6Class', which never matched
_CLASS_CONSTRUCTORS. The binding therefore fell through to a plain variable
node and the entire class body — every method — was dropped. The same applied
to methods::setClass / methods::setRefClass in the statement path.

Strip the namespace qualifier before the constructor check in both the binding
path and process_call_declaration, so a qualified constructor is treated
exactly like its bare form. Unqualified declarations are unaffected.

(cherry picked from commit 513bea43c2)
2026-09-28 09:13:29 +01:00
abhirupone 57a263ce8f fix(markdown): respect ignore rules when building wikilink index (#3822)
(cherry picked from commit 48a8da896b)
2026-09-28 09:13:29 +01:00
rajatnagda45 4536c5cdc0 fix(groovy): extract enum declarations and their constants
enum_declaration was missing from the Groovy config's class_types, so a Groovy
enum and every constant it declares were dropped from the graph — a consumer
could not see which value a branch selects. tree-sitter-groovy exposes the same
enum_constant shape (a name field, optional class_body) that Java uses, so the
enum type now joins class_types and the existing Java enum-constant handler is
reused to emit a node per member with a case_of edge.

Added a regression test asserting the enum type and its constants are present,
attached to the enum rather than the file.

(cherry picked from commit 2c386de21a)
2026-09-28 09:13:29 +01:00
rajatnagda45 d7e463e476 fix(sql): extract triggers and link them to their table
Two defects left CREATE TRIGGER under-extracted:

- The create_trigger AST branch read the subject table off keyword_for, but the
  table follows ON ('... AFTER INSERT ON users'); FOR EACH ROW carries no table.
  So even a cleanly-parsed trigger got a node with no link to the table it fires
  on. Read the table after keyword_on instead.

- A trigger with a procedural body ('FOR EACH ROW BEGIN ... END') has no grammar
  production, so the statement lands in an ERROR node and only the whole-file
  routine-recovery scan can save it — but that scan's pattern matched only
  FUNCTION/PROCEDURE, so the trigger and its table were dropped entirely. Add a
  dedicated trigger-recovery pattern that mints the trigger node (label without
  (), since a trigger is not callable) and a triggers edge to the table named by
  the first ON after the trigger name. A trigger already recovered via the AST
  path is skipped to avoid a duplicate edge.

Added regression tests for both the clean EXECUTE FUNCTION form and the
procedural BEGIN...END form.

(cherry picked from commit a28de74a52)
2026-09-28 09:13:29 +01:00
Daksh 1e7531fb25 fix(detect): keep foreign manifest keys in their own path syntax
_to_absolute_from_storage normalized a foreign-platform absolute key
with os.path.normpath, which applies the current platform's rules: on
Windows "/home/u/foo.py" became "\home\u\foo.py", a string POSIX cannot
read back. Collapse dot segments under the key's own syntax instead
(ntpath for a drive letter or leading backslash, posixpath otherwise).

test_save_manifest_relativize_step_collapses_seeded_duplicates never
reached save_manifest's own collapse: load_manifest already removes the
duplicate first, so the test passed with the save side reverted to
last-wins. Stub load_manifest to hand over the un-collapsed rows.

Also annotate _collapse_manifest_duplicates like its sibling helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9db2cfe2eb)
2026-09-28 09:13:29 +01:00
Daksh c2fb556919 fix(ingest): rewrite only the host when normalizing a tweet URL for oEmbed
_fetch_tweet did url.replace("x.com", "twitter.com") over the whole URL,
so "x.com" in the path or query was rewritten too (a ?next= link, or a
handle like dropbox.com_fan becoming dropbotwitter.com_fan), and an
uppercase X.COM host was missed. Match and rewrite the host only, the
same way _detect_url_type classifies it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 3ec4fdcfca)
2026-09-28 09:13:29 +01:00
harshaygadekar e4a6f5098e fix(watch): serialize _rebuild_lock on Windows via msvcrt
Closes #3881.

On Windows, _rebuild_lock falls back to yield True when fcntl cannot be
imported, leaving concurrent rebuilds completely unserialized and causing
file collisions on .graph.tmp.json.

Implement Windows file locking using Python's standard library msvcrt module.
Lock a byte range at offset 4096 so external readers can still read the owning
PID at byte 0 without hitting Windows mandatory lock PermissionErrors.

Un-skip the 4 rebuild lock tests and 3 pending queue tests on Windows, and
add test_rebuild_lock_blocks_concurrent_process.

(cherry picked from commit d8cd456650)
2026-09-27 18:59:12 +01:00
safishamsiandClaude Opus 4.8 e1d9deed6a security(fortran): strip #include directives before cpp (GHSA-pcc4-rvhr-2pr8)
The Fortran capital-F cpp step relied on -nostdinc -I /dev/null to block
#include-based host-file reads, but cpp always searches the source file's
own directory for quoted includes and always honours absolute paths
regardless of those flags. A malicious .F/.F90 file with
#include "/etc/passwd" or #include "../../../secret" could inline
arbitrary host files into graph.json/GRAPH_REPORT.md and the LLM context
on the default offline path (CWE-22/73/200, high severity).

Strip every #include directive before preprocessing and feed the
sanitized source to cpp on stdin, so cpp never opens the file's directory
or any absolute/traversing include path. Macro expansion (#define/#ifdef,
the actual reason capital-F files need cpp) is preserved; cross-file
include fidelity is traded away to close the arbitrary-read vector.
Feeding stdin also removes the -I/etc/x.F90 argument-injection edge case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-27 18:56:31 +01:00
nikhil2004 c66eebe25f fix(python): resolve nested scan-root imports via namespace projection (#3843)
When the scan root is a sub-directory of a monorepo package hierarchy
(e.g., root = Company/Apps/Jobs/Team/), absolute imports using the full
dotted namespace (from Company.Apps.Jobs.Team.lib import delivery) were
unresolved because the scan root probed root/Company/Apps/Jobs/Team/lib
which doesn't exist inside root.

_infer_scan_root_namespace walks above the scan root collecting ancestor
directory names that contain __init__.py, establishing the root's
namespace prefix. When an absolute import's module name starts with that
prefix, the prefix is stripped and the remainder is re-probed relative
to root.

The existing root/rel fast path, ancestor walk (#2072), ambiguous import
guard (#3729), and phantom-edge retraction (#3784) are all untouched.
Third-party imports cannot false-match because the prefix check requires
a strict dotted prefix plus a remaining local component.

Co-Authored-By: Google Antigravity <noreply@google.com>
(cherry picked from commit 640e39efc4)
2026-09-27 18:40:06 +01:00
Sinan Gümüşkabak 1990b02b00 fix(extract): skip JSX member tags
With a lowercase receiver, <icons.Close /> and <props.Comp /> resolved by the
property name and bound to unrelated local Close() / Comp() functions. None
of the member tags tried produced a correct edge, so only bare identifiers
are handled now.

(cherry picked from commit aa4be46dac)
2026-09-27 18:39:52 +01:00
Sinan Gümüşkabak a3559c2541 test: member JSX tag must not bind to a local function by its last segment
(cherry picked from commit af7ea6adaf)
2026-09-27 18:39:52 +01:00
Sinan Gümüşkabak 3a8db79305 fix(extract): add calls edges for JSX component usage
<Comp /> and <Comp>...</Comp> are parsed as jsx_self_closing_element /
jsx_opening_element with the tag in the `name` field, and neither type was
in call_types, so components that are only rendered got no incoming edge.

Add both types to the JS and TSX call_types and read the callee from
`name`. Tags starting with a lowercase letter are intrinsic elements and
are skipped; for member tags the last part decides, so <div> and
<motion.div> don't bind to a local div(). Namespaced tags and fragments
are ignored.

(cherry picked from commit 2828b55851)
2026-09-27 18:39:52 +01:00
KaiyiQuan cd7aa6cb78 test(csharp): verify user-defined tuple element types stay referenced (#3796)
Per Copilot review: the regression test must prove both halves of the fix —
element names are excluded, AND the real element type references are still
collected. Add a test using user-defined element types `(Mode mode, Label
label)`: asserts Mode/Label nodes and their references edges survive while
the names mode/label are not minted.

(cherry picked from commit 6506c32824)
2026-09-27 18:39:52 +01:00
KaiyiQuan 3c407dcb8c fix(csharp): don't collect tuple element names as type references (#3796)
A named tuple type `(int mode, string label)` is a `tuple_type` whose
`tuple_element` children carry both a `type` and a `name` field.
_csharp_collect_type_refs walked every named child, so the element NAMES
were collected as type references, minted as sourceless placeholder nodes
and emitted `references` edges — one junk type node per element, repeated
in every file declaring such a signature.

Only the `type` field of each tuple element should feed type-reference
collection; the `name` is an identifier, not a type reference. Fix the
collector to descend into the element's type field only.

Regression: tests/test_csharp_tuple_type_refs.py asserts no node/edge is
minted for element names (all existing C# tests pass).

(cherry picked from commit bfcdda97b0)
2026-09-27 18:39:52 +01:00
himanshupatro-334 286000d199 fix(skillgen): prevent INPUT_PATH shell injection
(cherry picked from commit a9551306af)
2026-09-27 18:39:51 +01:00
himanshupatro-334 fa284e9993 fix(skillgen): prevent monolith watch path injection
(cherry picked from commit a898e6890c)
2026-09-27 18:39:51 +01:00
breken-ai cb1016c405 fix(terraform): redact the value paired with a secret-named name in name/value lists
The ECS environment idiom { name = "DB_PASSWORD", value = "hunter2" } names the
secret in the name literal, not in a key, so key-name redaction let the value
reach graph.json and the MCP surface verbatim. When a map's name literal
matches the sensitive pattern, redact its value/valueFrom.

Fixes #3787

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e11cf947d0)
2026-09-27 18:39:51 +01:00
ayushcodes10 c973b90dea Add a regression test for foreign platform absolute key detection
Covers the review finding directly: a Windows drive letter key, a
UNC key, and a POSIX absolute key must all be recognized as already
absolute regardless of which platform the test runs on, and must not
be joined onto the scan root. Confirmed against pre fix code via git
stash, the test fails there since the recognizing helper does not
exist yet.

(cherry picked from commit 33b3a7a00a)
2026-09-26 13:53:08 +01:00