Commit Graph
925 Commits
Author SHA1 Message Date
abhay-codes07 42248aea4d fix(extract): collapse byte-identical duplicate edges at emission (#3251)
(cherry picked from commit f18b86ba61)
2026-09-05 01:18:59 +01:00
xiongjianxu c604f489cd feat(merge): finish member calls across a repo boundary (#3152)
When a member call's receiver type resolves to zero in-corpus declarations, park
{callee, receiver_type, lang, line} (names only, #3150-safe) on the caller; a merge-time pass
emits one calls/cross_repo/INFERRED edge only when the receiver type resolves to exactly one
declaration in another repo owning exactly one matching member (language-suffix matched,
non-sourceless). Strong-signal, single-definition guarded, edges-only — composes with the
#3007 cross-repo type link and the #3068 community offset. Wired for Java/C++/C#/Swift.
2026-09-02 14:58:01 +01:00
abhay-codes07 6f9b79ae87 fix(analyze): god_nodes honours exclude_hubs_percentile (#3205)
god_nodes had no exclusion parameter, so --exclude-hubs only reached cluster() and god-node
ranking ignored it. Apply the same percentile threshold cluster() uses (byte-identical
formula), thread it through the build/analyze/MCP paths; the default (no exclusion) output is
unchanged and the node dict shape is preserved for wiki/report consumers.
2026-09-02 14:49:03 +01:00
hopstreax b2825e05a5 fix: resolve Python type reference stubs across imports (#3252)
A Python type reference to a name imported from another module dangled or mis-resolved.
Run Python cross-file import resolution before the generic stub rewire and repoint sourceless
AST reference/inherits/implements edges onto the exact imported definition using the
file-scoped (module, symbol) -> node_id binding (not a bare-name match), so two same-named
types in different modules bind to their own; orphaned stubs are pruned only when no edge
still touches them.
2026-09-02 13:31:24 +01:00
hopstreax 02957036b2 fix: normalize definition_file on the watch and direct-extract paths (#3223)
Companion to #3243: extend definition_file portability to the paths build_from_json does not
cover — the watch/incremental relativize + cache-root rebase and the direct extract() return
normalization — via a shared _PORTABLE_PATH_KEYS list so source_file and definition_file stay
in lockstep. Together with #3243 this closes all four persistence paths (graph.json, AST
cache, watch, direct extract).
2026-09-02 04:00:08 +01:00
abhay-codes07 3e5944b0c7 fix(build): make definition_file portable, like its sibling source_file (#3223)
#2990 stamped definition_file with the still-absolute implementation path, so graph.json
shipped the build host's absolute path beside a repo-relative source_file (and the AST cache
replayed it on every warm rebuild). Normalize definition_file through the same
_norm_source_file path as source_file in build_from_json and the cache round-trip, so it is
relative/portable and byte-stable across machines.
2026-09-02 03:05:59 +01:00
Synvoya 588dc041f7 fix(go): don't emit embeds for interface type-set constraints (#1818) 2026-08-30 15:53:16 +01:00
Synvoya 9aeade7199 fix(csharp): classify an interface base as inherits, not implements (#1817) 2026-08-30 15:53:16 +01:00
Synvoya c361189046 fix(kotlin): resolve a qualified supertype to its tail type name (#1793)
A qualified Kotlin supertype (`class Foo : com.example.Base()`) resolved to the first
identifier (`com`), collapsing distinct bases onto a bogus node. Take the last segment of the
user type (handling generics like `Container<String>` -> `Container`), so the edge targets
the real base; simple and delegated supertypes are unaffected.
2026-08-30 15:52:38 +01:00
Synvoya bf385a5052 fix(scala): normalize qualified heritage names to the tail type (#1794)
A qualified Scala base (`extends pkg.Base with other.Trait`) parses as a
stable_type_identifier (or generic_type wrapping one), which the heritage handler did not
match, so the edge was dropped or targeted the full qualified string. Add a helper that
takes the tail type name across all three shapes, emitting inherits for the first parent and
mixes_in for the rest, with no package/generic-arg false edges.
2026-08-30 15:52:04 +01:00
Synvoya 5a8a84d7ac fix(scala): capture trait declarations as class-like nodes (#1792) 2026-08-30 15:51:26 +01:00
Synvoya 88f53962dc fix(php): capture interface/enum/trait heritage as class-like nodes (#1791)
Only class_declaration was treated as a class type, so PHP interfaces, enums, and traits
produced no node and no heritage edges. Add them as class-like containers (and walk the
enum body) so their extends/implements/use heritage is captured as inherits/implements/
mixes_in edges to the right targets.
2026-08-30 15:50:48 +01:00
Synvoya 6b2dfa0bf2 fix(extract): emit inherits edge for JavaScript class extends (#1790)
A plain JavaScript `class Dog extends Animal` produced no inherits edge — only the
TypeScript extends_clause/implements_clause shapes were handled, not the bare identifier
directly under class_heritage that JS emits. Fall back to reading heritage identifiers when
no clause is present, so JS class inheritance is captured; a dynamic base (mixin call) yields
nothing and TS is not double-emitted.
2026-08-30 15:48:40 +01:00
nshiveg 832256f824 feat: Robot Framework extractor for .robot/.resource files (#3192)
Add a Robot Framework extractor using the official robot.api parser (pure-Python, optional
[robot] extra), emitting suite/test/keyword nodes plus keyword-call, resource-import, and
library edges. Keyword resolution is case/space/underscore-insensitive (RF's real semantics)
and handles BDD prefixes and ${CURDIR}/${EXECDIR} path resolution; degrades to an actionable
error when robotframework is absent.
2026-08-30 15:14:33 +01:00
hopstreax 064957b398 fix: protect against unverified semantic node loss (#3203)
On the default incremental path a semantic chunk that returned successfully but under-produced
(prior semantic count > 1, fresh < prior) bypassed every guard, silently shrinking the graph.
Flag that per-source shrink (computed only from fresh nodes, so a genuinely deleted file is
never flagged) and route it through the existing shrink-guard refusal + manifest un-stamp so
the source re-extracts next run; --allow-partial overrides. Does not touch the prune path.
2026-08-30 15:12:10 +01:00
logan683 788c04e6c6 fix(watch): preserve unresolved authored reference edges (#3190)
Follow-up to #3191: an authored markdown link whose target basename is present in the graph
but under a different spelling (e.g. Windows-case [[target]] vs TARGET.md) was dropped on a
code-only rebuild. Preserve such an edge when its target node is still live in the merged
graph, without loosening the deleted-target existence gate (a genuinely deleted target's node
is evicted first, so it is never preserved).
2026-08-30 15:11:50 +01:00
abhay-codes07 4d8cb4c803 fix(install): actionable stale-skill warning; back up a diverged SKILL.md (#3144)
When the installed SKILL.md has diverged (user edit or old version), back it up beside the
install target before the atomic overwrite and print an actionable warning naming the stale
path and the exact reinstall command; a clean up-to-date reinstall backs up nothing (no
churn), and only the installed copy is touched, never the generated sources.
2026-08-30 15:11:40 +01:00
abhay-codes07 3993f023f7 fix(cli): hook-guard search detection matches executed commands, not prose (#3121)
The hook-guard matched a search command appearing anywhere in the intercepted Bash string
(a comment, a quoted -m message, an echo), firing false nudges. Match only an executed
command position (start-of-segment after a shell operator, wrapper-aware), so prose lookalikes
stay quiet while genuine pipes/&&/;/$() invocations still fire.
2026-08-30 15:11:40 +01:00
abhay-codes07 50d092db94 fix(llm): defang any <|token|> chat-template marker, not an enumerated few (#3183)
Only a handful of control tokens were defanged, so an attacker-chosen token outside the set
(e.g. Llama-3 <|eot_id|>) could still reach the prompt. Generalize the neutraliser to match
the control-token form (<|...|> and [/?INST|SYSTEM]) at the single _wrap_untrusted chokepoint;
the bounded charset leaves legitimate content (|| , markdown tables, generics) untouched.
2026-08-30 15:11:24 +01:00
abhay-codes07 fce26fc9b4 fix(report): make the report's headline numbers agree with themselves (#3148)
The GRAPH_REPORT headline figures disagreed: shown-community count included empty communities
the render loop skips, and the knowledge-gaps thin count used a hardcoded 3 beside a label
printing min_community_size. Drive every figure from one _real_count predicate so the counts
reconcile with what is actually rendered.
2026-08-30 15:11:24 +01:00
abhay-codes07 97e4275c09 fix(wiki): say when a god-node relation group was truncated (#3127)
A god-node relation group capped at 20 targets showed no indication it was truncated; append
a '…and N more' line (exact count, degree-sorted so the lowest-degree are dropped) only when
the group actually exceeds the cap.
2026-08-30 15:11:23 +01:00
safishamsi c24765a884 test(watch): pin that markdown reconcile does not resurrect a deleted target (#3190)
The reconcile relies on the extractor only stamping target_file when the link target exists
on disk. Add a negative test: link text intact but the target file deleted must leave no
reference edge and no edge pointing at the deleted document's node.
2026-08-29 19:35:41 +01:00
logan683 0a4c5e8fe3 fix(watch): reconcile markdown links to semantic-backed documents (#3190)
A code-only watch/update rebuild never re-creates a semantic-backed document node, so an
authored [[wikilink]] to such a doc resolved to a missing canonical id and reconcile
silently dropped the edge. Re-parse each markdown source and repoint authored references
edges onto a unique representative node for the target file (canonical id, else unique page
node, else unique document node; ambiguous -> keep the existing edge), preserving links to
the semantic tier across a code-only rebuild without resurrecting a deleted target.
2026-08-29 19:34:31 +01:00
SyedFahad7 aa5fef0c09 fix(cli): list prs, provider, and export formats in --help
graphify --help omitted the real prs and provider commands and the export format list. Add
them (verbatim from the dispatcher's own usage text), so every listed command/format exists
and the export formats match the supported allow-list.
2026-08-29 19:32:51 +01:00
egarcia74 8b3f1911b2 fix(sql): recover T-SQL bracket-named, CREATE OR ALTER, and PROC routines (#3164)
T-SQL routine idioms (CREATE OR ALTER, the PROC shorthand, bracket-delimited names like
[dbo].[Get Widgets]) parse to tree-sitter ERROR nodes, so the routine names were lost. Hoist
a single routine-recovery pattern covering all three forms and run it over a
comment/string-masked copy of the file so commented-out or dynamic-SQL DDL cannot fabricate
nodes; labels keep the raw bracketed text and plain CREATE PROCEDURE/FUNCTION is unchanged.
2026-08-29 19:32:44 +01:00
safishamsi b88e1640ca Revert "fix(extract): retain provenance for legacy and data nodes"
This reverts #3155 (b568488): the source_file backfill also fired on a shared external stub
(e.g. a cross-file `Path` reference with no source_file), giving the stub a file identity so
the orphan prune swept it when that one file was deleted even though another file still
referenced it — regressing test_prune_sweeps_orphans. The fix needs to distinguish a
legacy node that genuinely lost its provenance from an external stub that must stay
sourceless; sending #3155 back as needs-work.
2026-08-29 18:42:15 +01:00
hopstreax 663024253f fix(razor): resolve @inject types across files (#3187)
Wire Razor .razor/.cshtml @inject/@using through the existing scope-aware C# type resolver
instead of a bespoke path: @inject emits a references edge to a sourceless stub, @using
feeds scoping, and the type-resolution + stub-rewire passes now accept Razor sources. A
cross-file injected type resolves to its real definition; an external/undeclared type
fabricates nothing and a qualified inject does not mis-bind a same-named type.

Partially addresses #3187: a bare @inject of a type in a file-scoped namespace still dangles
(same as a bare C# cross-namespace reference without a using), and implicit _Imports.razor
global usings are not yet modeled — left as follow-up, so this does not auto-close the issue.
2026-08-29 18:38:33 +01:00
noQbot 72edfc0e10 Route get_node through the same resolver as get_neighbors
get_node used a raw substring scan and returned an iteration-order match, so it could miss
or mis-pick a node that get_neighbors resolves. Extract the shared tiered resolver
(_resolve_single_node) and route get_node through it — deterministic tiered winner or an
explicit ambiguity report, matching get_neighbors byte-for-byte; get_neighbors behavior and
return shapes are unchanged.
2026-08-29 18:38:05 +01:00
noQbot 09151a6727 Mark failed PR tool calls as isError instead of successful text
The prs MCP tools (list_prs, triage_prs, get_pr_impact) returned genuine failures (gh
missing/unauth, unresolved PR) as success text. Raise a ToolError propagated as isError on
both the 1.x and 2.x SDK paths, so a client sees the failure; an empty-but-successful result
(0 PRs, PR with no changed files) is unaffected. Mirrors the #2725 MCP-error pattern.
2026-08-29 18:37:06 +01:00
davidbhoward 35c63493e5 fix(install): emit a bare hook command for project-scoped installs (#3129)
A --project (committed/shared) install pinned the absolute interpreter path into the hook
command, so the committed hook churned across machines. Emit a bare `graphify <sub>` command
for project installs (resolved at hook-run time via the PATH/pin/uv-tool probes), while the
global install still emits the absolute path (preserving the Windows VS Code Codex case).
Uninstall still strips the bare command.
2026-08-29 18:36:56 +01:00
GmailTedam b568488cb4 fix(extract): retain provenance for legacy and data nodes
A sourceless legacy node now backfills its source_file from incident-edge provenance (only
when real edge evidence exists, deterministic min, never fabricated), and a declined data
JSON now emits a single file-identity node carrying source_file instead of nothing, so
freshness/affected checks can see the file. No node is resurrected and config JSON does not
double-add its file node.
2026-08-29 18:36:49 +01:00
hopstreax c60a564d1a fix(ts): normalize import types in call type arguments (#3185)
An `import(...)` used as a call type-argument (f<typeof import('mod')>()) could make the
parser drop declarations that follow it. Rewrite the import-type to a byte-length- and
newline-preserving placeholder before parsing so trailing declarations survive with exact
source locations; the dynamic-import edge is still recovered by re-reading the original
file. On the pinned tree-sitter-typescript (0.23.2) only the multiline form regresses today;
the single-line/member/TSX tests guard against regressions on future grammar bumps.
2026-08-29 16:19:48 +01:00
abhay-codes07 b84b4a7316 fix(ts): type-only imports no longer manufacture Import Cycles (#3123)
A type-only import (import type { T } from './m') produced an import edge like a value
import, so type-only round-trips showed up as false import cycles. Stamp type_only on the
module and symbol edges from type-only statements (correctly not firing for a mixed
import { type A, B }, which keeps its value edge) and exclude stamped edges from
find_import_cycles, mirroring the existing deferred-import exclusion.
2026-08-29 16:16:09 +01:00
safishamsi 8906834ff0 test(php): escape namespace backslashes in object-creation fixtures (#3115)
The fixtures embedded PHP namespace paths (\App\Bar, App\Command) as plain strings, so
\A / \C were invalid escape sequences (SyntaxWarning now, SyntaxError in a future Python).
Double the namespace backslashes; the \n newlines are unchanged.
2026-08-29 16:15:58 +01:00
abhay-codes07 f57af70f77 fix(php): emit a calls edge for object creation, new Foo(...) (#3115)
A PHP `new Foo()` emitted no edge. Add object_creation_expression to the PHP call types and
emit a calls edge to the constructed class (namespaced names resolve to the last segment),
consistent with the C# (#2998) and TypeScript (#3135) object-creation handling. Dynamic
(new $var()), self/static/parent, and unknown external constructions fabricate nothing.
2026-08-29 16:15:01 +01:00
abhay-codes07 b5162dc2e5 fix(engine): resolve fields declared on a superclass for receivers in a subclass (#3151)
A method call through a field whose type is declared on an ancestor class, used from a
subclass, did not resolve (the field-type table only held the class's own fields). Walk the
inheritance chain (per-file for Java/C#, cross-file for Java/ObjC) to type the receiver,
BFS with a seen-set so an inheritance cycle terminates. Target selection is unchanged (the
single-definition guards stand), so this only widens receiver typing and never fabricates
an edge; this.-only, and the binder refuses to guess on ambiguity.
2026-08-29 16:14:47 +01:00
abhay-codes07 208d0c1dcc fix(objc): remap the field->type table keys with the ids they hold (#3150)
The Objective-C field->type table is keyed by class node id, but the id-carrier rewrites
(prefix strip, symbol canonicalisation, cache portability round-trip) never remapped those
keys, so after graphify update the keys went stale and every self.field / obj.field method
call missed resolution. Remap the table keys alongside the existing id remaps (in-process
and in the cache round-trip); a dropped key can only cost a missed edge, never a phantom.
2026-08-29 16:14:29 +01:00
hopstreax ae074b2122 fix(extract): rescan code with --force --code-only (#3125)
--force --code-only over an existing graph set incremental_mode, so unchanged code files
were skipped and stale alias/import resolution persisted. Separate scan-mode from
graph-merge: --force --code-only now full-scans all code (via a new merge_existing_graph
gate) while still carrying the existing semantic tier forward (preserving the #2923 fix) and
still pruning deleted/excluded files. Other flag combinations are unchanged.
2026-08-28 01:20:57 +01:00
abhay-codes07 a8852e8740 fix(extract): hollow, unparseable and omitting chunks count as incomplete (#3105)
The #479 shrink guard is disarmed when a build is known-incomplete, but a run that silently
lost content to a hollow, unparseable, or file-omitting chunk was treated as complete, so it
could overwrite the existing graph with a smaller one. Mark those cases incomplete (uncovered
files or partial-semantic files) so the guard stays armed and refuses the shrink; a complete
run is untouched, a #2880-retry-recovered chunk does not count, and --allow-partial still
overrides.
2026-08-28 01:20:27 +01:00
abhay-codes07 3378ae8fef fix(build): route carried hyperedges through the dedup survivor remap (#3102)
A hyperedge carried from a prior graph.json kept its original member ids, so when dedup
merged one of those members away the hyperedge dangled. Feed carried hyperedges into build()
on the base chunk so deduplicate_entities remaps their members onto the survivor at the same
time as edge endpoints; a member that resolves to nothing is dropped (the whole hyperedge if
none survive), and a hyperedge re-emitted by a new chunk is not duplicated. Materialise
new_chunks so the added second iteration is safe.
2026-08-28 01:20:10 +01:00
safishamsi bad832f225 test(cluster): pin partition invariance to edge-endpoint orientation (#3146)
Build the same logical undirected graph with forward vs flipped edge endpoints and shuffled
node insertion, and assert _partition yields identical communities — guarding the canonical
edge-ordering the determinism fix introduced.
2026-08-28 01:19:45 +01:00
abhay-codes07 c60ddf7b3e fix(cache): the atexit stat-index flush must not resurrect a deleted corpus (#2974)
The atexit stat-index flush did an unconditional mkdir(parents=True) on the cache path, so
if the corpus/output tree was deleted during the run the flush recreated the whole
graphify-out/cache/ chain as a husk. Skip the write when the index root no longer exists
(clearing the dirty flag so nothing retries); a live run still writes, and a cold start is
unaffected because the root exists before graphify-out/ does.
2026-08-28 01:18:04 +01:00
hopstreax 590258fe1b fix(ts): resolve new_expression constructor calls (#3116)
A TypeScript/JavaScript `new Foo()` emitted no edge. Fall back to the new_expression's
constructor child in the generic callee branch, so it emits a calls edge to the constructed
class (member, chained, and generic forms), reusing the existing built-in-global guard so
new Map()/new Promise() are not fabricated. Consistent with the C# object-creation handling
(#2997).
2026-08-27 20:57:16 +01:00
santhiprakash c66465c77e fix(elixir): extract functions whose only clause has a when guard (#3111)
A guarded function head (def foo(x) when is_integer(x), do: ...) nests the head call under a
binary_operator, so a def whose only clause carries a guard was not extracted. Unwrap the
guard to the head call (the head is always the first child, so the guard predicate is never
selected) and emit the real function name; multi-clause, multi-condition guards, and defp
are handled.
2026-08-27 20:56:15 +01:00
guitelesc e906227cf6 fix(commonlisp): derive node ids from the full path stem
The Common Lisp extractor minted its id prefix from the bare path.stem instead of the
canonical _file_stem used by every other extractor, so two same-basename .lisp files in
different directories collided (one file's nodes vanished on merge) and nested .lisp files
tripped a spurious legacy-id nudge. Use _make_id(_file_stem(path)) to match the shared
convention; cross-file rewire is unaffected (it matches on label, not id).
2026-08-27 20:20:36 +01:00
guitelesc 8d611dc4d0 test(falkordb): skip when the port answers but is not FalkorDB
The integration test guarded only on ping(), so a plain Redis (or an SSH-forwarded one) on
6379 passed the liveness check and then FAILED both tests on 'unknown command GRAPH.QUERY'.
Add a read-only GRAPH.LIST probe that identifies the service and skips with a distinct
message when the graph module is absent, so the test still runs (and can fail) against a
real FalkorDB but no longer breaks on a wrong service.
2026-08-27 20:20:16 +01:00
safishamsi 996c6add76 test(cluster): pin native-leiden vs wrapper equivalence and absent-binding fallback (#3104)
Guard the output-equivalence claim the perf change rests on: _partition via the direct
graspologic_native path must produce the same node groupings as the graspologic wrapper
(skipped unless both are installed), and _native_leiden must return None when the native
binding cannot be imported so the wrapper/Louvain fallback still runs.
2026-08-27 18:03:10 +01:00
rohit-jsfreaky 984ff116d3 fix(ruby): match a qualified receiver by its constant path (#3078)
A call on a qualified constant receiver (ActiveRecord::Base.transaction) truncated the
receiver to its last segment (Base) and bound to any lone corpus class named Base, a false
EXTRACTED edge. Keep the full constant path and suffix-match it against declared classes,
emitting an edge only on a single unambiguous hit; a leading :: pins a whole-path match.
Bare receivers are unchanged.
2026-08-25 18:12:02 +01:00
hopstreax b48005276e fix(ruby): preserve suffixed method node IDs (#3077)
normalize_id strips !/?/= from Ruby method names, so save and save! (or foo and foo=) minted
the same node id and the second was dropped as a seen_ids collision. Add a Ruby symbol
sanitizer (!->_bang, ?->_pred, =->_eq) applied to the id only (the label keeps the raw
spelling), and rekey method_index on the raw name so member-call resolution still matches.
Ids stay stable across incremental re-extraction.
2026-08-25 18:10:43 +01:00
safishamsi ef00185bb2 test(merge): pin same-order community-offset byte-reproducibility (#3014)
Document that the per-input community-id offset is deterministic for a fixed input order
(consumers rely on stable merged output), while noting the offsets are position-dependent
by design.
2026-08-25 18:09:58 +01:00