Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases)

## Unreleased

- Feat: MATLAB `.m` files now have a dedicated, offline tree-sitter extractor, completing the follow-up to #1702. Content sniffing still routes Objective-C `.m` files to their existing extractor, while MATLAB scripts, functions, nested/local functions, `classdef` classes, inheritance, properties, events, enumerations, `+package` folders, `@Class` folders, private functions, imports, typed receivers, function handles, and conservative cross-file calls are represented structurally. Call resolution preserves MATLAB's call-vs-index ambiguity instead of inventing edges, respects lexical/package/private scope, and cannot merge or resolve into same-named Objective-C/native symbols.

## 0.9.15 (2026-07-13)

- Fix: detection now honors nested `.gitignore`/`.graphifyignore` files below the scan root, not just those at the scan root and above (#1847, thanks @Mohak-Agrawal). git applies a `.gitignore` to everything under its own directory, but graphify only loaded ignore files from the VCS-root-down-to-scan-root chain — so a `vendor/sub/.gitignore` deeper in the tree was never read and its exclusions leaked into the graph. Each directory's own ignore files are now read during the walk and anchored to that directory, preserving last-match-wins precedence (nearer files win, including over `.git/info/exclude`) and the parent-exclusion rule.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ To remove graphify from all platforms at once: `graphify uninstall` (add `--purg

| Type | Extensions |
|------|-----------|
| Code (36 tree-sitter grammars) | `.py .ts .mts .cts .js .jsx .tsx .mjs .go .rs .java .c .cpp .cc .cxx .h .hpp .cu .cuh .metal .rb .cs .kt .kts .scala .php .swift .lua .luau .toc .zig .ps1 .psm1 .psd1 .ex .exs .m .mm .jl .vue .svelte .astro .groovy .gradle .dart .v .sv .svh .sql .f .f90 .f95 .f03 .f08 .pas .pp .dpr .dpk .lpr .inc .dfm .lfm .lpk .sh .bash .json .dm .dme .dmi .dmm .dmf .sln .slnx .csproj .fsproj .vbproj .xaml .razor .cshtml` (`.dm`/`.dme` requires `uv tool install graphifyy[dm]`; `.mts`/`.cts` reuse the TypeScript grammar, `.cc`/`.cxx` and CUDA `.cu`/`.cuh` and Metal `.metal` reuse the C++ grammar) |
| Code (37 tree-sitter grammars) | `.py .ts .mts .cts .js .jsx .tsx .mjs .go .rs .java .c .cpp .cc .cxx .h .hpp .cu .cuh .metal .rb .cs .kt .kts .scala .php .swift .lua .luau .toc .zig .ps1 .psm1 .psd1 .ex .exs .m .mm .jl .vue .svelte .astro .groovy .gradle .dart .v .sv .svh .sql .f .f90 .f95 .f03 .f08 .pas .pp .dpr .dpk .lpr .inc .dfm .lfm .lpk .sh .bash .json .dm .dme .dmi .dmm .dmf .sln .slnx .csproj .fsproj .vbproj .xaml .razor .cshtml` (`.m` is content-sniffed between MATLAB and Objective-C; `.mm` is Objective-C++; `.dm`/`.dme` requires `uv tool install graphifyy[dm]`; `.mts`/`.cts` reuse the TypeScript grammar, `.cc`/`.cxx` and CUDA `.cu`/`.cuh` and Metal `.metal` reuse the C++ grammar) |
| Salesforce Apex | `.cls .trigger` (regex-based; classes, interfaces, enums, methods, triggers, SOQL/DML edges) |
| Terraform / HCL | `.tf .tfvars .hcl` (requires `uv tool install graphifyy[terraform]`) |
| MCP configs | `.mcp.json` `mcp.json` `mcp_servers.json` `claude_desktop_config.json` — extracts server nodes, package refs, env var requirements |
Expand Down
23 changes: 18 additions & 5 deletions graphify/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
**{e: "go" for e in (".go",)},
**{e: "rust" for e in (".rs",)},
**{e: "jvm" for e in (".java", ".kt", ".kts", ".scala")},
**{e: "c" for e in (".c", ".h", ".cpp", ".cc", ".cxx", ".hpp")},
**{e: "c" for e in (".c", ".h", ".cpp", ".cc", ".cxx", ".hpp", ".m", ".mm")},
**{e: "ruby" for e in (".rb", ".rake")},
**{e: "swift" for e in (".swift",)},
**{e: "dotnet" for e in (".cs",)},
Expand All @@ -36,12 +36,17 @@
}


def _cross_language(src_a: str, src_b: str) -> bool:
def _cross_language(
src_a: str,
src_b: str,
family_a: str | None = None,
family_b: str | None = None,
) -> bool:
"""Return True if two source files belong to different language families."""
ext_a = Path(src_a).suffix.lower()
ext_b = Path(src_b).suffix.lower()
fam_a = _LANG_FAMILY.get(ext_a)
fam_b = _LANG_FAMILY.get(ext_b)
fam_a = family_a if family_a and family_a != "native" else _LANG_FAMILY.get(ext_a)
fam_b = family_b if family_b and family_b != "native" else _LANG_FAMILY.get(ext_b)
if fam_a is None or fam_b is None:
return False
return fam_a != fam_b
Expand Down Expand Up @@ -222,7 +227,15 @@ def _surprise_score(
_suppress_structural = (
conf == "INFERRED"
and relation in ("calls", "uses")
and (_cross_language(u_source, v_source) or {cat_u, cat_v} == {"code", "doc"})
and (
_cross_language(
u_source,
v_source,
G.nodes[u].get("language_family"),
G.nodes[v].get("language_family"),
)
or {cat_u, cat_v} == {"code", "doc"}
)
)
if _suppress_structural:
conf_bonus = 0
Expand Down
18 changes: 16 additions & 2 deletions graphify/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,8 +716,22 @@ def build_from_json(extraction: dict, *, directed: bool = False, root: str | Pat
if _edge_rel in ("calls", "imports", "imports_from", "references"):
src_ext = Path(G.nodes[src].get("source_file") or "").suffix.lower()
tgt_ext = Path(G.nodes[tgt].get("source_file") or "").suffix.lower()
src_fam = _EDGE_LANG_FAMILY.get(src_ext)
tgt_fam = _EDGE_LANG_FAMILY.get(tgt_ext)
# ``.m`` is shared by Objective-C and MATLAB. Extractors stamp an
# explicit family for ambiguous suffixes; legacy data falls back to
# the extension map for backwards compatibility.
src_explicit = G.nodes[src].get("language_family")
tgt_explicit = G.nodes[tgt].get("language_family")
# ``native`` is the extraction resolver's broad interop family. The
# build guard intentionally keeps its existing C-vs-Swift boundary;
# only MATLAB needs to override the ambiguous `.m` suffix here.
src_fam = (
src_explicit if src_explicit and src_explicit != "native"
else _EDGE_LANG_FAMILY.get(src_ext)
)
tgt_fam = (
tgt_explicit if tgt_explicit and tgt_explicit != "native"
else _EDGE_LANG_FAMILY.get(tgt_ext)
)
if _edge_rel == "calls":
# Unchanged #1547/#1556 behavior: only INFERRED calls, and drop as
# soon as either family differs (an unknown ext counts as different).
Expand Down
7 changes: 5 additions & 2 deletions graphify/callflow_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,10 +515,13 @@ def node_kind(node: dict) -> str:
if any(word in label for word in ("component", "props", "hook", "store")) or hook_like or source_file.endswith((".tsx", ".jsx", ".vue", ".svelte")):
return "ui"
raw = raw_label
if raw.lower().endswith((
".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".rs", ".java", ".kt",
".rb", ".php", ".cs", ".swift", ".m", ".mm", ".vue", ".svelte",
)):
return "module"
if raw[:1].isupper() and not raw.endswith("()"):
return "klass"
if raw.endswith((".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".rs", ".java", ".kt", ".rb", ".php", ".cs", ".swift", ".vue", ".svelte")):
return "module"
return "function"


Expand Down
2 changes: 1 addition & 1 deletion graphify/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
_HOOK_SOURCE_EXTS = (
'.py', '.js', '.ts', '.tsx', '.jsx', '.astro', '.vue', '.svelte', '.go',
'.rs', '.java', '.rb', '.c', '.h', '.cpp', '.hpp', '.cc', '.cs', '.kt',
'.swift', '.php', '.scala', '.lua', '.sh', '.md', '.rst', '.txt', '.mdx',
'.swift', '.m', '.mm', '.php', '.scala', '.lua', '.sh', '.md', '.rst', '.txt', '.mdx',
)
_GEMINI_NUDGE_TEXT = (
'graphify: knowledge graph at graphify-out/. For focused questions, run '
Expand Down
Loading
Loading