Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
cfc7cf2
fix(cache): resolve FileSlice via unit_path in checkpoint allowlist (…
safishamsi Jul 13, 2026
20405a8
fix(dedup): report fuzzy count in summary when exact_merges is 0 (#1857)
thejesh23 Jul 13, 2026
e32be08
fix(detect): legacy-manifest branch must `!=`-compare mtime to catch …
thejesh23 Jul 13, 2026
3684078
docs(changelog): record #1862 and #1860 in the unreleased 0.9.16 section
safishamsi Jul 13, 2026
fb4d452
fix(detect): scope nested ignore patterns to their own subtree (#1873)
Alwyn93 Jul 14, 2026
40eae3c
test(watch): pin update-with-nested-gitignore (#1880); changelog for …
safishamsi Jul 14, 2026
1aca158
fix(cargo): honor `package = "..."` rename when resolving internal de…
thejesh23 Jul 13, 2026
4ac5f07
fix(watch): preserve semantic edges of re-extracted sources (#1865)
xor-xe Jul 13, 2026
2210d69
docs(changelog): record #1865 and #1858 in the unreleased 0.9.16 section
safishamsi Jul 14, 2026
70bc9ca
fix(dedup): let the defining file win an ID collision, and warn only …
bchan84x Jul 13, 2026
fd54f0e
fix(dedup): deterministic collision survivor + bare-file definer (#18…
safishamsi Jul 14, 2026
2795a18
fix(exclude): persist --exclude so rebuilds re-apply it (#1886)
safishamsi Jul 14, 2026
060dd63
fix(extract): resolve Python module-qualified calls to calls edges (#…
safishamsi Jul 14, 2026
f80e4fa
test(watch): pin update discovers newly-added files/dirs (#1837)
safishamsi Jul 14, 2026
4f6106a
fix(cli): send missing-skill warning to stderr
Mzt00 Jul 14, 2026
8361a81
fix(extract): recognize uppercase TypeScript
xkam7ar Jul 14, 2026
5c0a04c
fix(extract): filter Kotlin builtin/stdlib types from references graph
kebwlmbhee Jul 14, 2026
e1b8a17
docs(changelog): record #1881, #1876, #1893 in the unreleased 0.9.16 …
safishamsi Jul 14, 2026
b3dc15b
fix(extract): close residual absolute-path/username leaks (#1899)
safishamsi Jul 14, 2026
a4ab6ed
fix(llm): reconcile dispatched vs returned files in semantic extract …
safishamsi Jul 14, 2026
a0e4a1c
docs(changelog): date 0.9.16 for release
safishamsi Jul 14, 2026
75b7279
docs(readme): point star-history at Graphify-Labs org; X handle to @g…
safishamsi Jul 14, 2026
ef8771e
fix: correct postgres install hint to graphifyy (#1906); classify .sk…
safishamsi Jul 14, 2026
107cea0
fix(serve): drop German and Romance question stopwords from query ter…
safishamsi Jul 14, 2026
876c043
fix(export): prune orphaned Obsidian notes on re-export (#1896)
safishamsi Jul 14, 2026
f2a6139
Fix spurious hooksPath warning and register graph.json merge driver
safishamsi Jul 14, 2026
0cacd70
fix(llm): drop out-of-scope nodes from the merged extraction result (…
safishamsi Jul 14, 2026
b1e313c
fix(cli): stamp freshly-extracted semantic docs in the manifest (#1897)
safishamsi Jul 14, 2026
43b2aff
chore: open 0.9.17 with 8 batch fixes (#1895 #1897 #1902 #1907 #1896 …
safishamsi Jul 14, 2026
75cf56b
fix(detect,cli): excluded files are pruned from graph and manifest, n…
safishamsi Jul 15, 2026
49df466
fix(sql): recover CREATE FUNCTION/PROCEDURE from tree-sitter ERROR no…
safishamsi Jul 15, 2026
0d78391
docs(changelog): record #1908/#1909 and #1910 in unreleased 0.9.17
safishamsi Jul 15, 2026
2851f31
fix: treat .cjs (explicit CommonJS) as a code extension
Kookwater Jul 15, 2026
76f5bc9
fix(watch): stop AST-quick-scanning docs that already have semantic n…
safishamsi Jul 15, 2026
d916c61
fix(cache): stop persisting dangling edges/hyperedges in the semantic…
safishamsi Jul 15, 2026
b7ddee3
fix(extract): make --mode deep effective over a warm cache; add --for…
safishamsi Jul 15, 2026
1c280e4
docs(changelog): record #1894(PR-1) #1916 #1915 #1912 in unreleased 0…
safishamsi Jul 15, 2026
ff999a9
perf(serve): collapse T+1 per-term scoring passes into one traversal
Sirhan1 Jul 14, 2026
c0875cc
test(serve): rebase #1900 test onto #1918 _score_query API; bench new…
safishamsi Jul 15, 2026
19c496d
fix(build): make _semantic_id_remap idempotent to stop id accretion (…
safishamsi Jul 15, 2026
cb96bda
fix: preserve semantic layer, stamp hyperedges, PHP namespaces, ignor…
safishamsi Jul 15, 2026
f3acf2d
Add: R (.r/.R) language support via bespoke extract_r
Sirhan1 Jul 15, 2026
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
63 changes: 63 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ Codex users also need `multi_agent = true` under `[features]` in `~/.codex/confi
| `terraform` | Terraform / HCL `.tf`/`.tfvars`/`.hcl` AST extraction | `uv tool install "graphifyy[terraform]"` |
| `pascal` | Pascal / Delphi `.pas`/`.dpr`/`.dpk`/`.inc` AST extraction (more accurate `calls`/`inherits` edges; falls back to a regex extractor when absent) | `uv tool install "graphifyy[pascal]"` |
| `chinese` | Chinese query segmentation (jieba) | `uv tool install "graphifyy[chinese]"` |
| `r` | R `.r`/`.R` AST extraction (functions, calls, imports, static `source()`); ships via `tree-sitter-language-pack` which may fetch/cache the R grammar on first use (no source uploaded, R not executed) | `uv tool install "graphifyy[r]"` |
| `all` | Everything above | `uv tool install "graphifyy[all]"` |

</details>
Expand Down Expand Up @@ -327,7 +328,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 .r .R .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]`; `.r`/`.R` requires `uv tool install "graphifyy[r]"`; `.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 Expand Up @@ -835,8 +836,8 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for module responsibilities and how to ad
---

<p align="center">
<a href="https://star-history.com/#safishamsi/graphify&Date">
<img src="https://api.star-history.com/svg?repos=safishamsi/graphify&type=Date" alt="Star History Chart" width="370"/>
<a href="https://star-history.com/#Graphify-Labs/graphify&Date">
<img src="https://api.star-history.com/svg?repos=Graphify-Labs/graphify&type=Date" alt="Star History Chart" width="370"/>
</a>
</p>

Expand All @@ -846,7 +847,7 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for module responsibilities and how to ad

<p align="center">
<a href="https://discord.gg/598Ad9zQZ"><img src="https://img.shields.io/badge/Discord-Join-5865F2?style=flat&logo=discord&logoColor=white" alt="Discord"/></a>
<a href="https://x.com/graphifyy"><img src="https://img.shields.io/badge/X-graphifyy-000000?logo=x&logoColor=white" alt="X"/></a>
<a href="https://x.com/graphify"><img src="https://img.shields.io/badge/X-graphify-000000?logo=x&logoColor=white" alt="X"/></a>
<a href="https://github.com/sponsors/safishamsi"><img src="https://img.shields.io/badge/sponsor-safishamsi-ea4aaa?logo=github-sponsors" alt="Sponsor"/></a>
<a href="https://safishamsi.gumroad.com/l/qetvlo"><img src="https://img.shields.io/badge/Book-The%20Memory%20Layer-2ea44f?style=flat&logo=gitbook&logoColor=white" alt="The Memory Layer"/></a>
</p>
4 changes: 3 additions & 1 deletion graphify/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def _check_skill_version(skill_dst: Path) -> None:
except OSError:
return
if not skill_exists:
print(" warning: skill dir exists but SKILL.md is missing. Run 'graphify install' to repair.")
print(" warning: skill dir exists but SKILL.md is missing. Run 'graphify install' to repair.", file=sys.stderr)
return
# A progressive SKILL.md links to its references/ sidecar. If the body points
# at references/ but the dir is gone (manual delete, partial upgrade), the
Expand Down Expand Up @@ -597,6 +597,8 @@ def _run_cli() -> None:
print(" proxy, gateways): set ANTHROPIC_BASE_URL and ANTHROPIC_MODEL")
print(" --model M override backend default model")
print(" --mode deep aggressive INFERRED-edge semantic extraction")
print(" --force full re-scan and re-dispatch: skip the incremental")
print(" manifest gate and semantic cache reads (env: GRAPHIFY_FORCE=1)")
print(" --max-workers N AST extraction subprocess count (default: cpu_count)")
print(" --token-budget N per-chunk token cap for semantic extraction (default: 60000)")
print(" --max-concurrency N parallel semantic chunks in flight (default: 4; set 1 for local LLMs)")
Expand Down
2 changes: 1 addition & 1 deletion graphify/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
# Language families — extensions sharing a runtime can legitimately call each other
_LANG_FAMILY: dict[str, str] = {
**{e: "python" for e in (".py", ".pyw")},
**{e: "js" for e in (".js", ".jsx", ".mjs", ".ejs", ".ts", ".tsx", ".mts", ".cts", ".vue", ".svelte")},
**{e: "js" for e in (".js", ".jsx", ".mjs", ".cjs", ".ejs", ".ts", ".tsx", ".mts", ".cts", ".vue", ".svelte")},
**{e: "go" for e in (".go",)},
**{e: "rust" for e in (".rs",)},
**{e: "jvm" for e in (".java", ".kt", ".kts", ".scala")},
Expand Down
45 changes: 44 additions & 1 deletion graphify/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,16 @@ def _semantic_id_remap(nodes: list, root: str | None) -> dict:
if not new_stem:
continue
norm_nid = _normalize_id(nid)
# Idempotency guard (#1917): an id already carrying its canonical stem is
# done — do not re-run the legacy branch on it. When the canonical stem
# contains a shorter legacy stem as a prefix (parent dir name == file
# stem, e.g. `.claude/CLAUDE.md` -> `claude_claude` over legacy `claude`),
# an already-migrated id like `claude_claude_x` still matches the legacy
# `claude_` prefix below and would gain another stem segment on every
# build, defeating the same_topology/no_change short-circuits. Mirrors the
# canonical check in graph_has_legacy_ids.
if norm_nid == new_stem or norm_nid.startswith(new_stem + "_"):
continue
new_id: str | None = None
for old_stem in _old_file_stems(rel):
if old_stem == new_stem:
Expand Down Expand Up @@ -755,10 +765,43 @@ def build_from_json(extraction: dict, *, directed: bool = False, root: str | Pat
# Relativize hyperedge source_file the same way nodes and edges are
# (above), so to_json — which has no root and writes G.graph["hyperedges"]
# verbatim — never leaks an absolute path from a semantic subagent (#1418).
kept_hyperedges = []
for he in hyperedges:
if isinstance(he, dict) and he.get("source_file"):
he["source_file"] = _norm_source_file(he["source_file"], _root)
G.graph["hyperedges"] = hyperedges
# Validate members against the built node set (#1916): a hyperedge
# member absent from the graph used to be copied into
# G.graph["hyperedges"] verbatim and reach graph.json dangling,
# even from a live (non-cache) extraction. Mirror the pairwise-edge
# handling above: remap mismatched ids via normalization first,
# then drop members that still don't resolve; drop the hyperedge
# itself when no valid member remains (single-member hyperedges
# are legal in this codebase, e.g. a per-file flow, so we prune
# rather than require two survivors).
if isinstance(he, dict) and isinstance(he.get("nodes"), list):
valid_members = []
for m in he["nodes"]:
try:
hash(m)
except TypeError:
continue
if m not in node_set and isinstance(m, str):
m = norm_to_id.get(_normalize_id(m), m)
if m in node_set:
valid_members.append(m)
if not valid_members:
print(
f"[graphify] WARNING: dropping hyperedge "
f"{he.get('id', '?')!r} — none of its members "
f"{he.get('nodes')!r} match built nodes.",
file=sys.stderr,
)
continue
if valid_members != he["nodes"]:
he["nodes"] = valid_members
kept_hyperedges.append(he)
if kept_hyperedges:
G.graph["hyperedges"] = kept_hyperedges
return G


Expand Down
Loading