Skip to content
Merged
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
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

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

## 0.17.0 (2026-07-18)
## Unreleased

- Feature: a fourth `graph.html` view lens, "Calls" — alongside Community/File/Dependencies. Unlike Community (colors by inferred Leiden cluster, which mixes short code-symbol labels with long prose `concept`/`rationale` node labels from the LLM extraction pass) and Dependencies (collapses to one node per file), Calls stays at per-symbol granularity but hides every non-`code` `file_type` node (`concept`/`rationale`/`document`/`paper`) and restricts edges to the same real call/dependency relation whitelist Dependencies already used (`calls`/`imports`/`imports_from`/`references`/`inherits`/`implements`/`indirect_call`/`re_exports`/`uses`/`embeds`) — a plain function-to-function/class-to-class view without doc-node clutter or file collapsing. `graphify/export.py`; the relation whitelist is now hoisted to module scope and shared between the Dependencies and Calls lenses instead of being duplicated.
- Fix/Feature: `graph.html`'s search box had four gaps that made it harder to find a node than the CLI's `graphify query`. (1) It ignored the active lens — a hidden `concept`/`rationale` node in the new Calls lens could still surface as a search result, and clicking it silently panned the camera to nothing (`isNodeHidden` is now checked). (2) Matching was plain substring-on-label only — it now also tokenizes on camelCase/snake_case boundaries (same split rule as the CLI's `query.py` `_tokenize`, ported to JS) so e.g. searching "notification" finds `handleNotification`. (3) `source_file` is now also matched, so a file path substring finds nodes in that file. (4) A file-type dropdown (`code`/`concept`/`rationale`/`document`/`paper`/all) narrows results directly. Deliberately does not port the CLI's BM25 scoring, typo-correction, or synonym expansion — this is a client-side filter over an at-most-20-result list per keystroke, not a scored corpus search; a lightweight tiered ranking (exact > token match > token prefix > file match > substring) is enough at that scale.

- Feature: `.html`/`.htm` extraction now also runs a JS pass over inline `<script>` block bodies (`graphify/extract.py`'s `extract_html_with_scripts`, reusing the Vue SFC extractor's `_vue_mask_non_script` blank-and-preserve-newlines technique — that regex was already generic HTML script-tag matching, not Vue-specific). Previously only elements with an `id` attribute became nodes; a function/class defined inside a `<script>` block (the common single-file-app pattern) was invisible to the graph entirely — `graphify explain`/`query` could never find it. `<script src="...">` (external, no inline body) masks to an empty span and contributes nothing, unchanged.
- Fix: `explain`/`get_neighbors`/`blast_radius`/`path` (`_find_node_core` in `graphify/query.py`) could return "No node matching" for a real, unambiguous node whose name spans two fields — e.g. `ClassName.methodName` where the class lives in `source_file` and the method is the `label` — because every existing tier (source-exact/exact/prefix/substring) only checks a single field for the *whole* query string. Added a last-resort BM25 cross-field fallback tier (same ranking `query`/`path` already use), only engaged when all single-field tiers come up completely empty, returning every near-tied top scorer (10% gap) so a genuine tie still surfaces through the existing `_find_node_tied_group` ambiguity warning instead of a silent pick.
- Fix: `graphify affected` (`graphify/affected.py`'s `resolve_seed`) required an *exact single-field match count of 1* at every tier, so a name matching several nodes by raw substring count (even when one was clearly the better match) returned a blunt "No unique node match" instead of resolving. Added the same BM25 cross-field fallback as above (`_bm25_confident_pick`), but kept `resolve_seed`'s existing fail-closed contract intact: it only returns a pick when the top score clearly separates from the rest (10% gap) — a genuine near-tie (e.g. two identically-labeled nodes) still returns `None`, same as before.
## 0.17.0 (2026-07-18)
- Feature: editable git-clone checkouts now get a daily self-update notice, separate from the existing PyPI-upstream check. `graphify/git_update_check.py` compares this checkout's `CURRENT_VERSION` against `version.json` on this repo's `main` branch and, only on a clean working tree in an interactive terminal, asks y/N before running `git pull origin main`. A dirty tree gets a notice instead of a prompt; declining (or a non-interactive run) remembers the dismissal in `~/.config/graphify/update-dismissed` so it doesn't ask again until a newer version ships. Never auto-pulls.
- Fix (security): `collect_files()`/`detect()` had no check that a symlink inside the scan root actually resolves to a target under that root — a symlink pointing outside (accidental or planted) was silently followed and its content ingested into `graph.json`. Ported upstream's `_resolves_under_root()` containment check into both the directory-walk pruning and the per-file filter, in `extract.py` and `detect.py`. This fork's own `_auto_follow_symlinks` auto-detect convenience feature (upstream removed it in favor of opt-in-only) was left intact — only the missing containment check was closed, not that separate design choice. The LLM extraction path (`llm.py`'s `_read_files`/`_build_image_refs`) had the same gap — a symlinked text or image file was read straight off the escaped path before this fix — and now uses the same `_resolves_under_root()` guard, skipping with a stderr notice instead of reading through.
- Fix (#1638): an unresolved JS/TS import (bare package name after local/tsconfig resolution fails) is now namespaced with a `ref:` id prefix instead of a bare last-segment id, so it can no longer collide with an unrelated local file of the same stem and fabricate a phantom cross-language `imports_from` edge (e.g. `tailwindcss/colors` no longer aliases onto an unrelated `backend/utils/colors.py`).
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,19 @@ graphify export callflow-html

## What's different from upstream

This fork started from [safishamsi/graphify](https://github.com/safishamsi/graphify) and has repeatedly adopted upstream's own `v8` rewrite branch as its base over several months — verified down to commit author (`affected.py`, `resolver_registry.py`, `symbol_resolution.py`, and most of the rest of the shared package were written by upstream's own maintainer and contributors, not this fork). What follows is only what's built *on top of* that shared base, checked directly against upstream's current source (`3140b2e`, v0.9.6), not assumed:
This fork started from [safishamsi/graphify](https://github.com/safishamsi/graphify) and has repeatedly adopted upstream's own `v8` rewrite branch as its base over several months — verified down to commit author (`affected.py`, `resolver_registry.py`, `symbol_resolution.py`, and most of the rest of the shared package were written by upstream's own maintainer and contributors, not this fork). What follows is only what's built *on top of* that shared base, checked directly against upstream's current source (`fb992ce`, v0.9.19, re-verified 2026-07-18 — no claim below went stale across the 13 releases since the prior `3140b2e`/v0.9.6 check), not assumed:

**Fork-only, confirmed absent from upstream entirely:**
- **A real query/pathfinding layer** (`graphify/query.py`) — BM25 scoring with camelCase/snake_case tokenization, a typo/abbreviation/fuzzy-substring correction cascade, query synonym expansion, an optional local-embedding last resort, and `find_path_with_disambiguation` (retries every near-tied candidate pair, avoids degree-heavy hub nodes). Upstream's `serve.py` still resolves nodes with the older tiered `_EXACT_MATCH_BONUS` scorer and a bare `nx.shortest_path` with no hub avoidance. `--path`/`--source-path`/`--target-path`/`--context` flags on `path`/`explain`/`query` are part of this same layer.
- **7 extra language/config extractors** — `.css`/`.scss`, `.html`/`.htm`, `.yaml`/`.yml`, `.toml`, `.robot`/`.resource`, `.feature` (Gherkin), Fish shell (54 tree-sitter grammars vs upstream's 36).
- **Opt-in value-coupling** (`shares_value:<value>` edges, `value_coupling = true`) for config-as-code repos where nothing an AST sees actually "calls" anything.
- **Per-project config + `update --all`** (`graphify.toml` / `[tool.graphify]`, `config.py`).
- **Extra analysis & UX**: `god_nodes(by="pagerank")`, `cross_cutting_nodes()`, `unreachable_functions()`, `GRAPH_SUMMARY.md`, a 3D/lens `graph.html` viewer, dated-backup pruning, Playwright/Jest `test()`/`describe()` node synthesis.
- **Extra analysis & UX**: `god_nodes(by="pagerank")`, `cross_cutting_nodes()`, `unreachable_functions()`, `GRAPH_SUMMARY.md`, a 3D/lens `graph.html` viewer (Community/File/Dependencies/Calls lenses, lens-aware token/file-path search), dated-backup pruning, Playwright/Jest `test()`/`describe()` node synthesis.
- **CLI surface** is otherwise the same, not larger — upstream's `v8`-derived tree already ships the same `prs`, `reflect`, `global`, `merge-graphs`, `save-result`, and all 23 per-platform install subcommands.

**Upstream-only, not carried by this fork (some fixed here, most still open):**
- The entire **Obsidian/Canvas export** pipeline (`graphify export obsidian`) — this fork doesn't have it at all, by omission, not oversight.
- Several extraction-correctness fixes not yet pulled in: Ruby `module`/`Struct.new`/`Class.new` container nodes, Kotlin interface-delegation edges, Apex multi-interface `extends`, TS `namespace`/generator-function/import-equals nodes, `.mts`/`.cts` recognition.
- Several extraction-correctness fixes not yet pulled in: Ruby `module`/`Struct.new`/`Class.new` container nodes, Kotlin interface-delegation edges, Apex multi-interface `extends`, TS `namespace`/generator-function/import-equals nodes, `.mts`/`.cts` recognition. A second, narrower gap in the same area landed upstream since the last check (#1881, v0.9.16): uppercase TypeScript extensions (`.TS`/`.TSX`/`.MTS`/`.CTS`) aren't dispatched to the TS extractor — distinct from the `.mts`/`.cts`-recognition-at-all gap above, this is recognition failing only on case.
- Robustness fixes not yet pulled in: deterministic parallel-merge ordering, Windows long-path I/O, Office `--update` re-conversion, cached word-count detection.
- **Already fixed here**, previously upstream-only: C# receiver-typed member-call resolution (#1609), two JS/TS cross-file phantom-edge bugs (#1638, #1659), and a symlink-containment security gap in file collection.

Expand Down
Loading