Skip to content

Commit bd4c524

Browse files
committed
Merge upstream/v8 (v0.9.80) into consolidated/php-support-1682
* upstream/v8: (184 commits) release: 0.9.80 — include #4195, #4198 feat(python): resolve obj.method() through an annotated/constructor-bound local (#4198) perf(extract): resolve and parse each path once in the id remap and call tie-break release: 0.9.80 fix(deps): raise tree-sitter runtime floor to >=0.25 for ABI 15 grammars (#4148) fix(build): give --no-cluster graph.json the same endpoint rules as build_from_json fix(watch): keep extract()'s run-only keys out of --no-cluster graph.json fix(watch): drop an external import stub once no edge references it fix(detect): drop a previous checkout's absolute manifest keys (#4175) fix(python): keep TYPE_CHECKING-only imports out of import cycles (#3159) fix(python): resolve self.<attr>.<method>() calls through the attribute's constructor type (#2860) fix(cache): treat an AST hit whose import target is gone as a miss fix(extract): preserve cross-drive syntax warnings fix(extract): let the Windows console script use the extraction pool fix(resolution): keep re_exports from both files whose ids collide fix(markdown): resolve links to file names that contain a space fix(java): capture methods and calls inside anonymous class bodies test: skip optional-extra tests when the extra is not installed (#4190) fix(serve): show the graph's build commit in graph_stats perf(serve): build the MCP traversal view and shortest-path graphs once per graph ... # Conflicts: # CHANGELOG.md # graphify/extract.py # graphify/extractors/engine.py # graphify/serve.py # pyproject.toml # uv.lock
2 parents bfad35e + 6478eb7 commit bd4c524

231 files changed

Lines changed: 17595 additions & 1059 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎ARCHITECTURE.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ Every extractor returns:
6464

6565
`validate.py` enforces this schema before `build()` consumes it.
6666

67+
## Edge direction in graph.json
68+
69+
`graph.json` is written `"directed": false`, but every link is directed: **arc order is the direction** (`source` → `target`, #563). Older canonicalized files carry it in `_src`/`_tgt` markers instead, and those win where present (#2309).
70+
71+
A plain `json_graph.node_link_graph()` load loses this: an undirected graph re-orders each edge's endpoints by node-list position. Load through `paths.load_node_link_graph()` (or apply `paths.restore_arc_direction()` to the raw dict first), which stamps `_src`/`_tgt` on every edge. Then read direction as `data.get("_src", u)` / `data.get("_tgt", v)`, never from `u, v`. Anything that writes graph.json back out pops the markers into arc order first, as `export.to_json()` does.
72+
6773
## Confidence labels
6874

6975
| Label | Meaning |
@@ -93,10 +99,22 @@ See `SECURITY.md` for the full threat model.
9399

94100
## Testing
95101

102+
`analyze.suggest_questions()` interleaves candidates across question types before
103+
applying its result limit (seven by default). This keeps ambiguous relationships
104+
from hiding bridge, inferred-relationship, isolation, and low-cohesion signals.
105+
The existing candidate order within each type is preserved; if the limit is smaller
106+
than the number of available types, their generation order determines priority.
107+
96108
One test file per module under `tests/`. Run with:
97109

98110
```bash
99111
pytest tests/ -q
100112
```
101113

102114
The test suite is designed to avoid network access and uncontrolled filesystem effects; most tests are isolated with temporary directories and environment fixtures.
115+
116+
R extraction preserves symbol-only bindings such as `%||%` as separate nodes.
117+
When normal ID normalization removes an entire binding name, its UTF-8 bytes
118+
provide a deterministic operator suffix within the same lexical owner. Its double
119+
underscore separators cannot collide with an ordinary normalized binding ID. Local
120+
call resolution stops if malformed scope metadata cycles.

‎BENCHMARKS.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Highlights:
3030
| Memory | LOCOMO (300) | QA accuracy | 45.3% | supermemory 49.7% (11x ingest cost), bm25 31.3%, mem0 27.3% |
3131
| Memory | LOCOMO (300) | recall@10 | 0.497 | bm25 0.362, mem0 0.048 |
3232
| Memory | LongMemEval-S (50) | QA accuracy | 76% | dense RAG 76%, hybrid 74%, mem0 70% |
33+
| Code | ERPNext cross-tool (6) | key-fact coverage | 82.0% | grep/read baseline 70.8% |
3334
| Cost | LOCOMO ingest | USD | ~$1.40 | supermemory $15.67, mem0 $3.48 |
3435
| Cost | graph build | LLM credits | $0 | n/a |
3536

@@ -148,6 +149,21 @@ graphify pays for itself in accuracy against searching raw files, and avoids the
148149
context-stuffing anti-pattern of packing the whole repo into every turn (which
149150
costs roughly 20x the tokens for lower coverage).
150151

152+
### INFERRED cross-file edge precision (FastAPI demo corpus)
153+
154+
The README hero graph is the upstream [FastAPI](https://github.com/fastapi/fastapi)
155+
library extracted AST-only (no semantic pass). To check the `INFERRED` confidence
156+
tag on cross-file resolver edges, we manually audited every such edge
157+
(`calls`, `indirect_call`, `imports`, `imports_from`, `inherits`) whose source and
158+
target both live under `fastapi/` (production package sources, excluding the test
159+
tree). This is a dated manual spot-check, not a continuously-measured metric: on a
160+
fresh AST-only extract (2026-10-05) all 12 such edges were correct. Reproduce by
161+
running `graphify .` against a FastAPI checkout and inspecting the INFERRED
162+
cross-file edges whose endpoints both sit under `fastapi/`. Homonymous helpers in
163+
`tests/` produce noisier INFERRED `calls` edges; the tag is most meaningful on
164+
production cross-file resolution, where the resolver prefers no edge over a wrong
165+
one.
166+
151167
## Results: temporal (15 years of ERPNext)
152168

153169
689 weekly AST checkpoints, 2011 to 2026, built deterministically with no LLM.

‎CHANGELOG.md‎

Lines changed: 154 additions & 6 deletions
Large diffs are not rendered by default.

‎CONTRIBUTING.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ git remote add upstream https://github.com/Graphify-Labs/graphify.git
4242
git fetch upstream
4343
git checkout -b my-feature-branch upstream/v8 # Always branch off upstream v8, never commit directly to v8
4444

45-
# Set up the environment
46-
uv sync
45+
# Set up the environment (--all-extras installs the optional grammars and SDKs,
46+
# matching CI; without it the tests that need them are skipped)
47+
uv sync --all-extras
4748
uv run pre-commit install
4849

4950
# Run the test suite to confirm your setup

‎README.md‎

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@
2020
<a href="LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue" alt="License: Apache-2.0"/></a>
2121
<a href="https://pepy.tech/project/graphifyy"><img src="https://img.shields.io/pepy/dt/graphifyy?color=blue&label=downloads" alt="Downloads"/></a>
2222
<a href="https://docs.graphify.com"><img src="https://img.shields.io/badge/Docs-docs.graphify.com-0b7285?style=flat&logo=readthedocs&logoColor=white" alt="Docs"/></a>
23+
<a href="https://smithery.ai/servers/graphify/graphify"><img src="https://img.shields.io/badge/Smithery-MCP%20server-5c4ee5?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTEyIDJMMiA3djEwbDEwIDUgMTAtNVY3eiIvPjwvc3ZnPg==" alt="Smithery"/></a>
2324
<a href="https://discord.gg/XDnKVpzdXB"><img src="https://img.shields.io/badge/Discord-Join-5865F2?style=flat&logo=discord&logoColor=white" alt="Discord"/></a>
2425
<a href="https://www.youtube.com/@graphifylabs"><img src="https://img.shields.io/badge/YouTube-Graphify%20Labs-FF0000?style=flat&logo=youtube&logoColor=white" alt="YouTube"/></a>
2526
<a href="https://www.linkedin.com/company/graphify-labs"><img src="https://img.shields.io/badge/LinkedIn-Graphify%20Labs-0077B5?logo=linkedin" alt="LinkedIn"/></a>
2627
<a href="https://www.ycombinator.com/companies/graphify-labs"><img src="https://img.shields.io/badge/Y%20Combinator-S26-F0652F?style=flat&logo=ycombinator&logoColor=white" alt="YC S26"/></a>
2728
</p>
2829

2930
<p align="center">
30-
<b>Early access to the graphify platform is open before the public v1 launch: <a href="https://app.graphify.com/login">app.graphify.com</a></b>
31+
<b>Try the graphify platform free for 14 days: <a href="https://app.graphify.com/login">app.graphify.com</a></b>
3132
</p>
3233

3334
Type `/graphify` in your AI coding assistant and it maps your entire project (code, docs, PDFs, images, videos) into a **knowledge graph** you can **query instead of grepping** through files.
@@ -67,6 +68,10 @@ graphify-out/
6768
└── graph.json the full graph — query it anytime without re-reading your files
6869
```
6970

71+
The persisted graph includes `graph.schema_version` so integrations can detect
72+
incompatible format changes, plus `graph.graphify_version` identifying the
73+
Graphify release that produced it.
74+
7075
**Works in** Claude Code, Cursor, Codex, Gemini CLI, GitHub Copilot, and 15+ more — [pick your platform](#install).
7176

7277
---
@@ -125,6 +130,7 @@ What you get out of the box:
125130
| LOCOMO (n=300) | recall@10 | **0.497** | mem0 0.048, supermemory 0.149 |
126131
| LOCOMO (n=300) | QA accuracy | 45.3% | supermemory 49.7%, mem0 27.3% |
127132
| LongMemEval-S (n=50) | QA accuracy | **76%** | tied with dense RAG |
133+
| ERPNext cross-tool (n=6) | key-fact coverage | **82.0%** | grep/read baseline 70.8% |
128134
| Graph build | LLM credits | **0** | per-token for most systems |
129135

130136
Every system ran on the same harness with the same model and budgets, scored by a judge blind-validated against a second judge (90.6% agreement, Cohen's kappa 0.81). Full per-system tables, the code-intelligence result, and reproduction commands: **[BENCHMARKS.md](./BENCHMARKS.md)**.
@@ -344,7 +350,7 @@ To remove graphify from all platforms at once: `graphify uninstall` (add `--purg
344350

345351
| Type | Extensions |
346352
|------|-----------|
347-
| 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 .ml .mli .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]`, `.ml`/`.mli` requires `uv tool install graphifyy[ocaml]`; `.mts`/`.cts` reuse the TypeScript grammar, `.cc`/`.cxx` and CUDA `.cu`/`.cuh` and Metal `.metal` reuse the C++ grammar) |
353+
| 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 .ml .mli .jl .vue .svelte .astro .groovy .gradle .dart .v .sv .svh .vh .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]`, `.ml`/`.mli` requires `uv tool install graphifyy[ocaml]`; `.mts`/`.cts` reuse the TypeScript grammar, `.cc`/`.cxx` and CUDA `.cu`/`.cuh` and Metal `.metal` reuse the C++ grammar) |
348354
| Salesforce Apex | `.cls .trigger` (regex-based; classes, interfaces, enums, methods, triggers, SOQL/DML edges) |
349355
| Terraform / HCL | `.tf .tfvars .hcl` (requires `uv tool install graphifyy[terraform]`) |
350356
| OCaml | `.ml .mli` (requires `uv tool install graphifyy[ocaml]`) |
@@ -512,6 +518,19 @@ python -m graphify.serve graphify-out/graph.json --transport http --host 0.0.0.0
512518

513519
The MCP server gives your assistant structured access: `query_graph`, `get_node`, `get_neighbors`, `shortest_path`, `list_prs`, `get_pr_impact`, `triage_prs`.
514520

521+
### One-click install (VS Code · Smithery)
522+
523+
Prefer not to edit MCP config by hand? Register the graphify MCP server in one step. Run `/graphify` first so a `graphify-out/graph.json` exists in the workspace; the server reads it on launch.
524+
525+
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_graphify_MCP-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=graphify&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22graphifyy%5Bmcp%5D%22%2C%22graphify-mcp%22%5D%7D)
526+
527+
The button adds a `graphify` server that launches with `uvx --from "graphifyy[mcp]" graphify-mcp` (no API key, no prior install). Or install via [Smithery](https://smithery.ai/servers/graphify/graphify) for Claude, Cursor, VS Code, and other clients:
528+
529+
```bash
530+
# pick your client (claude, cursor, vscode, windsurf, ...)
531+
npx -y @smithery/cli install graphify/graphify --client claude
532+
```
533+
515534
### Shared HTTP server
516535

517536
`--transport stdio` (the default) spawns one local server per developer. `--transport http` serves the same tools over the MCP Streamable HTTP transport, so a single shared process can serve the graph for the whole team — clients point their IDE MCP config at `http://<host>:8080/mcp` instead of running graphify locally.
@@ -579,6 +598,7 @@ These are only needed for **headless / CI extraction** (`graphify extract`). Whe
579598
| `GRAPHIFY_QUERY_LOG` | Enable the query log and write it to this path instead of the default | optional — off unless this or `_ENABLE` is set |
580599
| `GRAPHIFY_QUERY_LOG_DISABLE` | Set to `1` to force the query log off (wins over the enable vars) | optional |
581600
| `GRAPHIFY_QUERY_LOG_RESPONSES` | When the log is enabled, also record full subgraph responses (off by default) | optional |
601+
| `GRAPHIFY_NO_AUTO_REFRESH` | Set to `1` to stop the CLI from refreshing installed skills that are older than the package after an upgrade | optional — refresh is on by default |
582602
| `GRAPHIFY_MAX_GRAPH_BYTES` | Override the 512 MiB graph.json size cap — e.g. `700MB`, `2GB`, or plain bytes | optional — useful for very large corpora |
583603
| `GRAPHIFY_MAX_CONTEXTS` | Maximum number of non-default project graphs retained by one multi-project MCP server | optional — default: `8`; invalid values use `8`, and values below `1` use `1` |
584604
| `GRAPHIFY_LLM_TEMPERATURE` | Override LLM temperature for semantic extraction — e.g. `0.7`, or `none` to omit | optional — auto-omitted for o1/o3/o4/gpt-5 reasoning models |
@@ -674,10 +694,10 @@ ANTHROPIC_API_KEY=sk-... graphify extract ./docs --backend claude
674694
```
675695
676696
**Skill version mismatch warning in your IDE**
677-
Your installed graphify version is different from the skill file. Update:
697+
Your installed graphify version is different from the skill file. After an upgrade, the first `graphify` command refreshes every installed skill that is older than the package, on all platforms at once, and keeps a locally edited `SKILL.md` as `SKILL.md.bak`. Set `GRAPHIFY_NO_AUTO_REFRESH=1` to turn this off. A skill it can't refresh safely keeps the warning: one newer than the package, or a directory that two installers share (for example Copilot and `graphify vscode install`). Reinstall that platform by hand:
678698
```bash
679699
uv tool upgrade graphifyy
680-
graphify install # overwrites the skill file
700+
graphify install --platform <name> # overwrites the skill file
681701
```
682702
683703
**Claude Code prompt cache invalidated after every `graphify extract`**
@@ -845,6 +865,12 @@ graphify label ./my-project # (re)name commun
845865
graphify label ./my-project --backend=openai --model gpt-4o # force a specific backend and model
846866
```
847867
868+
`--no-dedup` also skips coalescing distinct non-AST nodes solely because they
869+
share a file and label. The Python equivalents are `build(chunks, dedup=False)`,
870+
`build_merge(chunks, graph_path, dedup=False)`, and
871+
`build_from_json(extraction, dedup=False)`. AST/semantic twins still reconcile to
872+
the canonical AST node, and document-file twin reconciliation remains enabled.
873+
848874
> **Community names:** inside an agent (Claude Code, Gemini CLI) the agent names communities itself. When you run the bare CLI, `cluster-only` auto-names them with the configured backend (built-in or custom OpenAI-compatible provider) — pass `--no-label` to keep `Community N`, or run `graphify label` to (re)generate names on demand.
849875
850876
---
@@ -877,6 +903,22 @@ New here? Say hi on [Discord](https://discord.gg/XDnKVpzdXB) or in [GitHub Discu
877903
878904
---
879905
906+
## Contributors
907+
908+
<a href="https://github.com/Graphify-Labs/graphify/graphs/contributors">
909+
<img src="https://contrib.rocks/image?repo=Graphify-Labs/graphify" alt="graphify contributors" />
910+
</a>
911+
912+
Made with [contrib.rocks](https://contrib.rocks).
913+
914+
---
915+
916+
## Translations
917+
918+
The README is available in 32 languages. Use the language switcher at the top of this file to read it in yours, or browse [`docs/translations/`](docs/translations/). To improve a translation or add a new one, open a pull request against the matching file there.
919+
920+
---
921+
880922
## Community and links
881923
882924
<p align="center">
500 KB
Loading

‎docs/how-it-works.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ EXTRACTED edges always have confidence 1.0. INFERRED edges use a discrete rubric
4848
- **0.65** — weak (naming similarity only)
4949
- **0.55** — speculative
5050

51-
That rubric describes edges Claude inferred. The per-language member-call resolvers are a separate, deterministic source of INFERRED edges: they read the receiver's declared type out of the AST and bind at a fixed **0.8**, reserving EXTRACTED (1.0) for a receiver whose type is named in the source at the call site.
51+
That rubric describes edges Claude inferred. The per-language member-call resolvers are a separate, deterministic source of INFERRED edges: they read the receiver's declared type out of the AST and bind at a fixed **0.85** (a step of the same rubric), reserving EXTRACTED (1.0) for a receiver whose type is named in the source at the call site.
5252

53-
**PHP member calls refuse rather than guess.** `$this->prop->method()`, `$obj?->method()`, a typed parameter and a `$var = new T()` local all bind to the receiver's declared type as INFERRED 0.8; `(new Service())->method()` is EXTRACTED 1.0, but only when the namespace written at the call site corroborates the class that was found. When the type is not provably one concrete in-corpus class, no edge is emitted at all — untyped, union- and intersection-typed receivers, a short name that matches two classes, a method the receiver's class does not declare, chained and array-element receivers, a local rebound or shadowed anywhere in the method, anonymous classes, and `self`/`static`/`parent`. A receiver typed by an `interface`, `enum` or `trait` binds to that declaration's own method only; implementations are never guessed — `MailNotifier implements Notifier` gets nothing from a `Notifier`-typed receiver. A Laravel corpus has many identically named `search()`/`log()`/`handle()` methods, so an absent edge is worth more than a guessed one.
53+
**PHP member calls refuse rather than guess.** `$this->prop->method()`, `$obj?->method()`, a typed parameter and a `$var = new T()` local all bind to the receiver's declared type as INFERRED 0.85; `(new Service())->method()` is EXTRACTED 1.0, but only when the namespace written at the call site corroborates the class that was found. When the type is not provably one concrete in-corpus class, no edge is emitted at all — untyped, union- and intersection-typed receivers, a short name that matches two classes, a method the receiver's class does not declare, chained and array-element receivers, a local rebound or shadowed anywhere in the method, anonymous classes, and `self`/`static`/`parent`. A receiver typed by an `interface`, `enum` or `trait` binds to that declaration's own method only; implementations are never guessed — `MailNotifier implements Notifier` gets nothing from a `Notifier`-typed receiver. A Laravel corpus has many identically named `search()`/`log()`/`handle()` methods, so an absent edge is worth more than a guessed one.
5454

5555
A PHP 8.1 first-class callable — `$obj->method(...)`, including the nullsafe and `$this` forms — resolves by exactly those rules but is emitted as **`indirect_call`**, not `calls`: the syntax creates a `Closure` and names the method without invoking it, the same shape the resolver already labels `indirect_call` for a callback passed by name. `calls` therefore keeps meaning "control flow transfers here". The spread form `$obj->method(...$args)` is a real invocation and stays `calls`.
5656

0 commit comments

Comments
 (0)