Commit Graph
1599 Commits
Author SHA1 Message Date
safishamsi 753b5b7201 docs(changelog): add 0.9.52 entries for the razor/mcp-prs/get_node/install/provenance batch 2026-08-29 18:39:37 +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
safishamsi fac6f44a09 docs(changelog): open 0.9.52 with the php-new/inherited-fields/objc-remap/ts-import-type batch 2026-08-29 16:31:17 +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
safishamsiandClaude Opus 4.8 281ccaa4ff chore: bump to 0.9.51
Ships three batches: the leiden-direct perf win (#3104) and git-workflow docs; TS new_expression
constructor calls (#3116), elixir when-guard-only clauses (#3111), commonlisp path-qualified
ids, and the FalkorDB wrong-service test skip; and the correctness set — shrink guard armed on
hollow/unparseable/omitting chunks (#3105), --force --code-only full rescan (#3125), carried
hyperedge survivor remap (#3102), atexit stat-index no-resurrect (#2974), and Leiden
edge-endpoint determinism.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.51
2026-08-28 01:25:56 +01:00
safishamsi 982a09f9eb docs(changelog): add 0.9.51 entries for the shrink-guard/force-rescan/hyperedge/cache/cluster batch 2026-08-28 01:22:32 +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
ErichKinuya 27096817ed fix(cluster): canonicalise edge endpoints before sorting in _partition
For an undirected graph, (a,b) and (b,a) are the same edge but networkx may yield the
endpoints in either order depending on insertion order / version / hashseed, so the edge
sort was keyed on a non-canonical orientation and the ordering fed to Leiden could vary
across builds/machines, drifting community assignments. Sort each endpoint pair to (min,max)
before the sort so the edge order is canonical; the edge set and weights are unchanged.
2026-08-28 01:18:46 +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
safishamsi 384e71e008 docs(changelog): add 0.9.51 entries for the ts-new/elixir-guard/commonlisp-id/falkordb-test batch 2026-08-27 21:17:11 +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 3af5021e79 docs(changelog): open 0.9.51 with the leiden-perf + git-workflow-docs batch 2026-08-27 18:33:29 +01:00
safishamsi a6e2a6827d docs(readme): tighten the git-workflow rebuild claims (#3112)
Two accuracy corrections to the Recommended workflow table: the post-checkout hook rebuilds
only on branch switches (not a file-only git checkout -- <path>), and the commit/switch
rebuilds run detached in the background so the graph can briefly lag the commit.
2026-08-27 18:32:51 +01:00
Mohammad-Palla df2f99b33e docs(readme): add explicit git-workflow guidance for keeping the graph in sync
Add a Recommended workflow section documenting that commits and branch switches rebuild the
graph automatically via the installed hooks, while a git pull/merge needs a manual
graphify update . (no pull hook exists), with a pull-alias shortcut and a troubleshooting
entry for a stale graph.
2026-08-27 18:03:28 +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
Mohammad-Palla a9894ebb7f perf(cluster): call graspologic_native.leiden() directly, skip graspologic's import chain
The Leiden path imported graspologic, which drags in a heavy chain (umap / pynndescent /
numba JIT) at import time. Call the underlying graspologic_native.leiden() binding directly
with the same arguments the wrapper uses (seed 42, randomness 0.001, modularity, unit edge
weights), falling back to the graspologic wrapper and then NetworkX Louvain when the native
binding is absent. Clustering output is unchanged.
2026-08-27 18:01:13 +01:00
safishamsiandClaude Opus 4.8 43d54acbfa chore: bump to 0.9.50
Ships two batches: the detect ignore-perf rewrite (#2226) with the globstar cycle fix and
bounded pattern cache; C#/TypeScript enum-member nodes (#3063/#3064); watch read-only
inotify filtering; the postgres tree-sitter-sql extra; Ruby suffixed-method-id preservation
(#3077) and qualified-receiver matching (#3078); HOF-wrapped CommonJS export capture
(#3035); per-input community-id offset in merge-graphs (#3014); and the Windows PowerShell
BOM fix across all .graphify_root readers (#3028).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.50
2026-08-25 18:24:12 +01:00
safishamsi b963858e83 docs(changelog): add 0.9.50 entries for the ruby/HOF-export/merge/windows-BOM batch 2026-08-25 18:12:26 +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
santhiprakash 4e2e23278a fix(build): offset community ids per input in merge-graphs (#3014)
merge-graphs kept each input's community ids as-is, so community 0 from repo A and community
0 from repo B fused into one meta-community spanning unrelated repos. Offset each input's
community ids past the running maximum so every input's communities stay globally distinct
after merge, preserving within-input structure and recording the original id in
local_community.
2026-08-25 18:09:25 +01:00
hopstreax ad7c7d58d1 fix: capture HOF-wrapped CommonJS exports (#3035)
A CommonJS export wrapped in a higher-order function (exports.x = wrap(fn),
module.exports.y = onCall({...}, handler)) was not captured, so the exported symbol was
missing. Reach through the wrapping call to the topmost function closure argument (mirroring
the const-export HOF path #2552/#2568); the wrapper stays a call edge, not the export's
identity, and a non-function arg fabricates nothing.
2026-08-25 18:07:55 +01:00
safishamsi 5cb6532c24 fix(windows): read .graphify_root BOM-tolerantly in the update-recovery path too (#3028)
Complete the read-side BOM fix: the graphify update (no-args) recovery path at cli.py read
.graphify_root with plain utf-8, so a PowerShell-written BOM would fail the exists() check
and abort with path not found. Switch it to utf-8-sig like the other five reader sites.
Also clean up two invalid-escape f-strings in the windows skillgen test.
2026-08-25 18:07:40 +01:00
rohit-jsfreaky 3c448660cd fix(windows): stop a PowerShell BOM breaking hook rebuilds (#3028)
Windows PowerShell 5.1 writes a UTF-8 BOM, so the .graphify_root marker it produced carried
a leading BOM that Python's utf-8 decode kept as U+FEFF; .strip() left it in, breaking the
post-commit rebuild (WinError 123) and silently mis-rooting _infer_merge_root. Write the
marker BOM-less from PowerShell (WriteAllText + UTF8Encoding $false) and read the marker
with utf-8-sig at the reader sites so already-broken installs recover too.
2026-08-25 18:06:46 +01:00
safishamsi 5687fe1bcc docs(changelog): open 0.9.50 with the detect-perf + enum-member + watch/postgres batch 2026-08-25 15:52:49 +01:00
safishamsi d8abe52415 perf(detect): kill the globstar matcher's per-call cycle, bound the pattern cache (#2226)
Senior tweaks on the landed #3074:
- Lift the **-aware matcher out of the per-call @lru_cache closure (which referenced itself
  and leaked a reference cycle every call) into a module-level function with an explicit
  dict memo, plus a no-** fast path; port of the fix from the alternative #2845.
- Cap the process-global parsed-pattern cache so a long-lived watch across many repos cannot
  grow it without bound.
- Add a differential test pinning _lexical_relative against pathlib relative_to, and a
  gc test asserting the matcher leaves no cyclic garbage.
2026-08-25 15:52:10 +01:00
safishamsi f725eec172 chore(lock): sync uv.lock for the postgres tree-sitter-sql extra (#3072) 2026-08-25 15:46:44 +01:00
Azeem1985 258ab11db2 perf(detect): stop building pathlib objects per pattern per file in ignore evaluation (#2226)
Ignore evaluation built a Path and called relative_to (with its CPython quadratic parents
cost) per pattern per file, and re-parsed each pattern's flags every call. Replace it with a
lexical string-space relative computation (no Path construction, no exception control flow),
parse each pattern once into a process-wide cache, and memoize per-anchor relatives, segment
splits and is_dir per evaluation. Ignore decisions are unchanged (differential-fuzz
verified); ~50x faster on a pattern-heavy monorepo.
2026-08-25 15:46:44 +01:00
durmazoguzhan 3b12d5ce68 feat(typescript): emit a node per enum member (#3064)
A TypeScript enum's members got no graph node. Emit a member node per member with a case_of
edge to the enum (matching the C# handler), handling both member spellings
(property_identifier and enum_assignment), const enums, and unquoting a whole string member
name instead of one fragment; the member initializer is still walked so a nested expression
is not dropped.
2026-08-25 15:45:33 +01:00
durmazoguzhan 0e566345fc feat(csharp): emit a node per enum member (#3063)
A C# enum's members got no graph node, so an enum case was invisible. Emit a member node
per enum_member_declaration with a case_of edge to the enum (mirroring the Java/Kotlin/Swift
enum handlers), deduped against a case-only sibling collision; explicit and implicit values
both work and no builtin types are fabricated.
2026-08-25 15:45:06 +01:00
Azeem1985 62371ad28d fix(postgres): carry tree-sitter-sql in the postgres extra and surface grammar errors
The postgres introspection path synthesizes DDL and parses it with the SQL grammar, but
tree-sitter-sql was not in the postgres extra, so `pip install graphifyy[postgres]` left it
unable to parse and returned a silent empty result. Add tree-sitter-sql to the extra and
raise an actionable ImportError (with the install hint) when the grammar is missing or has
an incompatible ABI, instead of reporting zero nodes.
2026-08-25 15:44:52 +01:00
Azeem1985 b022c3469c fix(watch): ignore read-only inotify events so the watcher stops re-triggering on its own reads
On Linux, inotify emits opened / closed_no_write events for every file open and close,
including the watcher's own AST rebuild reading the tree, so the watcher fed itself a
rebuild loop. Drop those two read-only event types; close-after-write and
create/modify/move/delete still trigger, and the filter is a no-op on the macOS/Windows
backends that never emit them.
2026-08-25 15:44:45 +01:00
safishamsiandClaude Opus 4.8 282976b2f4 chore: bump to 0.9.49
Ships four batches this cycle: tsconfig cache-clear (#2917), C# generic type args at call
sites + field position (#2911), PDF/document slicing (#2900/#2903/#2906); the LLM/cache
resilience set — zero-node retry (#2927), reasoning-JSON recovery scope (#2926),
code-only --force semantic preservation (#2923), cross-language builtin-base fix (#2812),
uv-tool hook resolution (#2852), pre-manifest obsidian adoption (#2863); language
extractors — zig enum/union methods (#2999), julia abstract supertypes (#3000), commonlisp
cross-file inherits (#3001), powershell enums (#3002); and C#/extractor/merge fixes — C#
object creation (#2997), interface dispatch (#3003), property nodes (#3006), cross-repo
shared types (#3007), SQL transactions (#2953), bash command substitution (#2978), and
C++/ObjC definition-site preservation (#2990).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.49
2026-08-24 14:18:33 +01:00
safishamsi 0b5297b479 docs(changelog): add 0.9.49 entries for the sql/bash/cpp/csharp/merge batch 2026-08-24 14:13:57 +01:00