Commit Graph
1564 Commits
Author SHA1 Message Date
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
safishamsi 960b6f5a0b test(merge): pin that a shared type links even when the declarations drift (#3007)
Document the cross-repo join's precision boundary: the key is namespace+name by design,
not structural, so a shared contract type whose declarations have drifted across repos
still links (and repo-unique types do not). Guards against a future change that tried to
gate the edge on member equality.
2026-08-24 14:12:53 +01:00
durmazoguzhan c46b756cde feat(merge): link a type declaration two repos share (#3007)
When merging graphs from multiple repos, a type declared in more than one repo under the
same fully-qualified namespace and name (a shared contract type) now gets a same_type_as
edge linking the declarations, so a cross-repo contract is navigable. Matching requires a
non-empty namespace plus label, a real sourced type declaration (not a method/field or
sourceless stub), and at least two distinct repos, so two unrelated types that merely share
a short name are not linked; the edge is INFERRED/0.9.
2026-08-24 14:09:46 +01:00
durmazoguzhan 3ef625b3b6 feat(csharp): emit a member node per property (#3006)
C# properties got no graph node, so a property was invisible as a class member (only
fields and methods appeared). Emit a member node per property_declaration with the same id
scheme, ownership, and defines/field-context edge as the C++ data-member branch; all
property shapes (auto, expression-bodied, read-only) are covered, the #2913 property-type
references walk is preserved, and no builtin types are fabricated.
2026-08-24 14:08:25 +01:00
winesoft-namjin-yun da8ca1684c fix(extract): keep the definition site when a decl/def pair is merged (#2990)
When a C/C++/ObjC declaration (header prototype) and its definition (implementation body)
merge into one node, the survivor kept the declaration's site, so the node pointed at the
prototype instead of the body. Record the definition's file/location on the survivor and
surface it as a Defined in: line; the pick is deterministic regardless of file order, and a
declaration with no definition keeps its own site.
2026-08-24 14:07:48 +01:00
akshitj11 5a56bd6943 fix(bash): emit calls edges for x=$(fn) command substitution (#2978)
A command substitution on the right of an assignment (x=$(foo)) was not recorded as a
call, unlike a bare $(foo). Gate call emission on the ancestor chain: allow a call inside a
command_substitution when it sits under a variable_assignment, while still suppressing bare
and arg-position substitutions and process substitutions.
2026-08-24 14:07:07 +01:00
akshitj11 6a06b652cc fix(sql): extract CREATE TABLE inside BEGIN/COMMIT blocks (#2953)
DDL wrapped in a transaction parses under a `transaction` node the statement walker never
descended into, so a CREATE TABLE inside BEGIN/COMMIT was never extracted while a top-level
one was. Recurse into transaction blocks; nested statements get the same handling, no
double-emit.
2026-08-24 14:06:55 +01:00
safishamsi 7fcff8eb4d docs(changelog): add 0.9.49 entries for the language-extractor + C# batch 2026-08-24 13:23:41 +01:00
durmazoguzhan 7f928bc1d0 feat(csharp): link interface methods to their single implementation (#3003)
A call through an injected interface dependency lands on the interface method, leaving the
implementation unreachable from the call site. When an interface has exactly one
implementing class, add a dispatches_to edge from each interface method to the matching
implementation method. Guarded to avoid false links: requires a real implements edge, a
single implementer, and a single case-sensitively same-named method; both ends must be C#;
0 or 2+ implementers emit nothing. Lives in graphify.csharp_dispatch as a registered
resolver.
2026-08-24 13:22:09 +01:00
durmazoguzhan 7f476e1447 fix(csharp): emit calls edges for object creation expressions (#2997)
A `new Foo()` object-creation expression emitted no edge, so constructor usage was invisible
in the graph. Add object_creation_expression to the C# call types and emit a calls edge to
the constructed type (member/qualified/generic forms), resolving a qualified construction
against declared namespaces without binding to an in-file placeholder. Built-in and
out-of-corpus types are not fabricated, and repeated constructions dedup to one edge.
2026-08-24 13:17:23 +01:00
rajatnagda45 be5110a789 fix(powershell): extract enum definitions and their members
A PowerShell enum definition and its members were not extracted, so a [Color] type
reference resolved to a sourceless phantom stub instead of the real enum. Emit the enum as
a real sourced node with its members as contained children (mirroring class_statement), so
references resolve to it; no builtins are fabricated.
2026-08-24 13:16:45 +01:00
rajatnagda45 46d3e5d5b8 fix(commonlisp): keep inherits edge when the superclass is in another file
A defclass whose superclass is defined in another file lost its inherits edge. Mint a
sourceless stub for the cross-file base (source_file empty, origin_file set) like the
import-stub path, so the corpus rewire collapses it onto the real definition and the
inherits edge survives; a same-file superclass still binds locally.
2026-08-24 13:16:39 +01:00
rajatnagda45 3c0eda3bdd fix(julia): extract abstract types declared with a supertype
An abstract type declared with a supertype (abstract type Dog <: Animal end) matched only
the bare-identifier form, so the subtyping declaration dropped the node entirely. Parse the
type_head shape shared with structs (factored into a helper) and emit the inherits edge; a
bare abstract type still works.
2026-08-24 13:15:31 +01:00
rajatnagda45 6930e4fa90 fix(zig): extract methods declared on enums and unions
Zig lets enums and unions carry methods like structs, but only the struct branch recursed
into the container body, so enum/union methods and the calls in their bodies were dropped.
Walk enum/union children the same way, parenting methods to the container with the same id
scheme; variants and fields are not turned into nodes.
2026-08-24 13:15:23 +01:00
safishamsi 512f9863bd docs(changelog): add 0.9.49 entries for the semantic-cache/cross-language/hooks/obsidian batch 2026-08-21 19:27:38 +01:00
abhay-codes07 4cbc8603dd fix(export): adopt a pre-manifest vault's own notes instead of orphaning them (#2863)
An Obsidian vault written before the ownership manifest existed had no record of which
notes graphify wrote, so on the next export those notes were orphaned. Seed ownership by
recognizing graphify's own notes (a graphify/ frontmatter tag for node notes; the
_COMMUNITY_ prefix plus the FROM #community/ Dataview marker for community notes) when the
manifest is absent, so the manifest starts out honest. The .obsidian config is not
adopted (Obsidian writes it too).
2026-08-21 19:22:21 +01:00
SinghAman21 245eb9dc9c fix(extract): scope fresh semantic results to dispatched files (#2926)
The #1757 guard scoped the semantic-cache WRITE to dispatched files, but the unfiltered
fresh result still fed build_merge, whose replace-set logic swapped a non-dispatched
file's entire prior contribution for a stray misattributed fragment (and logged the
'skipped out-of-scope source_file' warning). Apply the same allowlist to the result dict
before it reaches the merge, via a shared _semantic_source_matcher so the write guard and
the graph filter cannot drift.
2026-08-21 19:21:20 +01:00
hopstreax 24cf52d51e fix(cache): retry zero-node semantic results (#2927)
A semantic result with no nodes and no hyperedges (only edges, or nothing) was cached and
stamped into the manifest, so an empty/degenerate LLM reply for a file froze that file:
detect_incremental saw it unchanged and never re-dispatched it. Reject zero-node results
from the cache read and write, drop edges from the manifest stamp tuple, and heal an
existing manifest by re-queueing files that were already stamped with a zero-node result.
2026-08-21 18:26:49 +01:00
santhiprakash 5be36dac0d fix(extract): preserve existing semantic layer on --code-only --force (#2923)
A `--code-only --force` rebuild over an existing graph dropped the doc/paper/image
semantic tier, because force took the full-rebuild path and code-only never re-dispatched
those files. When an existing graph is present, keep incremental mode so build_merge
carries the semantic layer forward; files deleted from disk are still pruned.
2026-08-21 18:26:24 +01:00
santhiprakash 9452ef216b fix(hooks): resolve the uv tool venv when pin and launcher probes miss (#2852)
Installed via `uv tool install`, the post-commit hook could not find graphify: the pin and
launcher/shebang probes miss the uv tool venv, so the hook silently exit-0'd. Add a fourth
fallback probe that scans $UV_TOOL_DIR and the conventional uv tool locations for a python
whose env has graphify importable, firing only after the earlier probes miss and verifying
importability before adopting. Also gate the launcher read on a real #! header so a binary
trampoline is not misparsed as a shebang.
2026-08-21 18:26:11 +01:00
ousamabenyounes 037970159d fix(extract): stop a built-in base class from inheriting across languages (#2812)
A class extending a built-in base (PHP `extends \Exception`) emits a sourceless supertype
stub, and the corpus rewire bound it to the unique same-labelled real definition anywhere
in the corpus — so in a PHP+TS monorepo a PHP class inherited from the TypeScript
Exception, a phantom cross-language edge / god node. Gate the supertype-target rewire with
a per-language curated builtin-base set: refuse the rewire only when the referring file's
language names the base as a builtin and the resolved target is in a different language
family. Same-language inheritance and user classes named like a builtin still link.
2026-08-21 18:26:00 +01:00
safishamsi 0f625b6042 docs(changelog): open 0.9.49 with the tsconfig/csharp/pdf-slicing batch 2026-08-21 16:59:23 +01:00
safishamsi ada1c095ab fix(llm): bisect PDF slices on extracted text; reconcile splittable-PDF tests (#2906)
Three fixes landing the PDF-slicing set coherently:
- bisect_slice read raw container bytes, so the adaptive-retry path (a lone oversized
  slice that still overflows, #2880) searched for the newline cut in binary coordinates
  and could cut mid-line for a compressed PDF. Index unit_source_text instead, matching
  read_slice_text and expand_oversized_files; any converter failure falls back to None.
- #2900's test asserted a PDF is not splittable, which #2906 overturns; rewrite it to
  guard the real invariant (an image has no addressable text and is never sliced). The
  unreadable-PDF passthrough case is covered by test_pdf_slicing.
- add a bisect_slice-on-PDF regression test: the cut lands on an extracted-text line
  boundary and the halves tile the slice exactly.
2026-08-21 16:57:29 +01:00
abhay-codes07 4faa86c8d5 fix(llm): slice oversized PDFs through the converter (#2906)
An oversized PDF was sliced as raw container bytes, so slice offsets indexed binary data
instead of the extracted text the prompt carries. Introduce unit_source_text so both the
oversized-file expansion and per-slice reads index the same extracted text, and make PDFs
splittable through the converter.
2026-08-21 16:51:17 +01:00
abhay-codes07 b6b2fb17c8 fix(llm): slice every text document type, not just five of them (#2900)
The oversized-file slicer only split five suffixes while the doc allowlist reaching the
LLM was larger, so a large .qmd/.skill/.html/.yaml/.yml was truncated at the char cap
and silently dropped past it. Extend the splittable-text set to cover every text
document type, with a contract test so future allowlist additions fail loudly.
2026-08-21 16:51:00 +01:00
abhay-codes07 7c5203dde4 fix(llm): estimate PDF tokens from the extracted text, not the container (#2903)
Token estimation for a PDF read the raw container bytes, which are mostly binary and
bear no relation to the extractable text, so a small-text PDF could be judged oversized
(or vice versa). Estimate from the extracted text instead, memoized on path+size+mtime.
2026-08-21 16:50:44 +01:00
santhiprakash a4cb89083c fix(csharp): walk generic type arguments at call sites (#2911)
A type used as a generic argument at a call site (repo.Get<User>(),
services.AddSingleton<IFoo, Foo>()) did not emit a references edge, so DI registrations
and generic calls dropped their type dependencies. Walk the invocation's type-argument
list (member and static forms, nested args included); builtins and in-scope type
parameters are not fabricated.
2026-08-21 16:50:31 +01:00
brobl2008 09c985c553 fix(csharp): walk generic type arguments in field position
A type used as a generic argument in a field declaration (private List<Order> _orders)
did not emit a references edge to the argument type, so field-injected dependencies were
invisible. Walk the field's type through the shared type-ref collector, bringing fields
to parity with the property handler; builtins and in-scope type parameters are not
fabricated.
2026-08-21 16:50:25 +01:00
safishamsi 762a43a2e6 docs(resolution): note tsconfig caches are cleared per run (#2917)
Record the run-scoped-clear contract on both loader docstrings so the next reader
does not reintroduce the process-durable-cache assumption the fix removed.
2026-08-21 16:50:18 +01:00
sashankh cce41efb7c fix(extract): invalidate tsconfig alias/baseUrl caches per run (#2917)
The tsconfig/jsconfig alias and baseUrl caches were keyed by config path with no
mtime/content component and never cleared, so an edit to compilerOptions.paths or
baseUrl was never observed again for the life of the process — graphify watch and the
MCP server, which call extract() repeatedly in one process, kept resolving imports
through the stale alias map. Clear both caches per run alongside the other run-scoped
caches (within-run caching is preserved).
2026-08-21 16:49:55 +01:00
safishamsiandClaude Opus 4.8 b2cd362674 chore: bump to 0.9.48
Ships this cycle: the LLM-resilience batch — hollow-response same-chunk retry (#2880),
reasoning-first JSON recovery (#2882), deliberately-declined data JSON not counted as
failed (#2879); extractor fixes — C++ nested types + C++/CLI (#2876), markdown vault-wide
wikilinks (#2875); export fixes — control-char no longer aborts export (#2897), graph.html
restored for large graphs (#2853); and the --no-dedup opt-out (#2881).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v0.9.48
2026-08-20 16:33:07 +01:00
safishamsi 1c6cf4a880 docs(changelog): add 0.9.48 entries for #2897, #2853, #2881 2026-08-20 16:14:04 +01:00
safishamsi e16b01ce48 test(extract): assert --no-dedup graph invariant, not just kwarg propagation (#2881)
The PR's tests spy on the dedup kwarg reaching build/build_merge. Add graph-level
assertions for what the kwarg does: dedup=False preserves a fuzzy near-duplicate pair
that dedup=True merges, and exact-id collisions still collapse to one node either way
(the structural invariant that makes the flag safe).
2026-08-20 16:13:45 +01:00
rajarshidattapy eccc6cf0bf feat(extract): add --no-dedup so incremental merges can skip fuzzy dedup (#2881)
Adds --no-dedup (default off, so dedup stays on) to skip the fuzzy near-duplicate merge
pass on build and incremental merge, for operators who would rather keep distinct symbols
that fuzzy-matched than pay the merge. Exact-id uniqueness is unaffected (it is a graph
structural invariant, not a dedup responsibility), and the flag arms the #479 shrink
guard so a surprising node drop is refused loudly. Mutually exclusive with --dedup-llm.
2026-08-20 16:10:49 +01:00
oleksii-tumanov 05f90f1682 fix(export): restore graph.html for large graphs (#2853)
The label and cluster-only commands did not pass the viz node limit to to_html, so a
graph over the node limit raised and the except branch silently unlinked the graph.html
that update had produced. Always pass the limit (the aggregated community meta-graph
renders instead of raising), preserve the existing file on a failed render via an atomic
publish plus a stale marker, and regenerate a missing graph.html on the no-topology-change
fast path without reclustering.
2026-08-20 16:10:36 +01:00
abhay-codes07 917e331ab4 fix(export): a control character in a label must not abort the export (#2897)
A C0 control character in a node label or id (from a mangled extraction or an odd
filename) crashed the GraphML export (XML 1.0 forbids C0 except tab/LF/CR) and the
Obsidian export (EINVAL on Windows paths), aborting the whole artifact. Fold a
control-char scrub into the existing per-format coercion hooks so only the illegal
characters are stripped; tab/LF/CR and non-ASCII letters are preserved, and to_json
(and its byte-identity round-trip) is untouched.
2026-08-20 16:09:00 +01:00