diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ebd2d1..4d819e60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases) +## 0.8.28 (2026-06-01) + +- Feat: Kilo Code support — `graphify install --platform kilo` installs a native skill (`~/.config/kilo/skills/graphify/SKILL.md`) and `/graphify` command, plus a `.kilo` `tool.execute.before` plugin (mirroring the OpenCode integration). Existing `.kilo/kilo.jsonc` config is read but never rewritten — plugin registration goes to `kilo.json` so user comments are preserved (#512) +- Feat: modernized Dart extractor — comment stripping, `part of` redirection, nested-generic-aware `extends`/`with`/`implements` parsing, generic type-argument mapping, and generic call detection (#1098) +- Fix: `uv tool install graphifyy` / `pip install graphifyy` no longer fails to build on Linux/macOS — `tree-sitter-dm` (BYOND DreamMaker) ships only a Windows wheel, so on other platforms it compiled from source and aborted the entire install when a C toolchain or `python3-dev` headers were missing. It is now an optional extra (`graphifyy[dm]`, also in `[all]`) instead of a core dependency, so the default install needs no compiler (#1104). + - **Upgrade note:** DreamMaker `.dm`/`.dme` users must reinstall with `graphifyy[dm]` (or `[all]`) to keep AST extraction — on `uv tool upgrade` the now-optional grammar is removed. `.dmi`/`.dmm`/`.dmf` parsing is unaffected (no tree-sitter dependency). + ## 0.8.27 (2026-05-31) - Feat: standalone CLI now auto-names communities with the configured backend instead of leaving `Community N` placeholders — community labeling was previously an agent-only step (skill.md Step 5), so bare-CLI runs never got semantic names; `cluster-only` now auto-labels when no `.graphify_labels.json` exists, new `graphify label ` subcommand (re)generates names on demand, `--no-label` opts out, `--backend=` overrides auto-detection; one batched LLM call with per-community placeholder fallback and graceful degradation on missing backend/API error; works with all built-in and custom OpenAI-compatible backends (#1097) diff --git a/README.md b/README.md index 55b3d6b2..04950b21 100644 --- a/README.md +++ b/README.md @@ -153,21 +153,22 @@ Install only what you need: | Extra | What it adds | Install | |---|---|---| -| `pdf` | PDF extraction | `pip install "graphifyy[pdf]"` | -| `office` | `.docx` and `.xlsx` support | `pip install "graphifyy[office]"` | -| `google` | Google Sheets rendering | `pip install "graphifyy[google]"` | -| `video` | Video/audio transcription (faster-whisper + yt-dlp) | `pip install "graphifyy[video]"` | -| `mcp` | MCP stdio server | `pip install "graphifyy[mcp]"` | -| `neo4j` | Neo4j push support | `pip install "graphifyy[neo4j]"` | -| `svg` | SVG graph export | `pip install "graphifyy[svg]"` | -| `leiden` | Leiden community detection (Python < 3.13 only) | `pip install "graphifyy[leiden]"` | -| `ollama` | Ollama local inference | `pip install "graphifyy[ollama]"` | -| `openai` | OpenAI / OpenAI-compatible APIs | `pip install "graphifyy[openai]"` | -| `gemini` | Google Gemini API | `pip install "graphifyy[gemini]"` | -| `bedrock` | AWS Bedrock (uses IAM, no API key) | `pip install "graphifyy[bedrock]"` | -| `sql` | SQL schema extraction | `pip install "graphifyy[sql]"` | -| `chinese` | Chinese query segmentation (jieba) | `pip install "graphifyy[chinese]"` | -| `all` | Everything above | `pip install "graphifyy[all]"` | +| `pdf` | PDF extraction | `uv tool install "graphifyy[pdf]"` | +| `office` | `.docx` and `.xlsx` support | `uv tool install "graphifyy[office]"` | +| `google` | Google Sheets rendering | `uv tool install "graphifyy[google]"` | +| `video` | Video/audio transcription (faster-whisper + yt-dlp) | `uv tool install "graphifyy[video]"` | +| `mcp` | MCP stdio server | `uv tool install "graphifyy[mcp]"` | +| `neo4j` | Neo4j push support | `uv tool install "graphifyy[neo4j]"` | +| `svg` | SVG graph export | `uv tool install "graphifyy[svg]"` | +| `leiden` | Leiden community detection (Python < 3.13 only) | `uv tool install "graphifyy[leiden]"` | +| `ollama` | Ollama local inference | `uv tool install "graphifyy[ollama]"` | +| `openai` | OpenAI / OpenAI-compatible APIs | `uv tool install "graphifyy[openai]"` | +| `gemini` | Google Gemini API | `uv tool install "graphifyy[gemini]"` | +| `bedrock` | AWS Bedrock (uses IAM, no API key) | `uv tool install "graphifyy[bedrock]"` | +| `sql` | SQL schema extraction | `uv tool install "graphifyy[sql]"` | +| `dm` | BYOND DreamMaker `.dm`/`.dme` AST extraction (may need a C compiler + `python3-dev` if no wheel matches your platform) | `uv tool install "graphifyy[dm]"` | +| `chinese` | Chinese query segmentation (jieba) | `uv tool install "graphifyy[chinese]"` | +| `all` | Everything above | `uv tool install "graphifyy[all]"` | --- @@ -222,15 +223,15 @@ To remove graphify from all platforms at once: `graphify uninstall` (add `--purg | Type | Extensions | |------|-----------| -| Code (33 languages) | `.py .ts .js .jsx .tsx .mjs .go .rs .java .c .cpp .h .hpp .rb .cs .kt .scala .php .swift .lua .luau .zig .ps1 .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 .csproj .fsproj .vbproj .razor .cshtml` | +| Code (33 languages) | `.py .ts .js .jsx .tsx .mjs .go .rs .java .c .cpp .h .hpp .rb .cs .kt .scala .php .swift .lua .luau .zig .ps1 .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 .csproj .fsproj .vbproj .razor .cshtml` (`.dm`/`.dme` AST extraction requires `uv tool install graphifyy[dm]`) | | MCP configs | `.mcp.json` `mcp.json` `mcp_servers.json` `claude_desktop_config.json` — extracts server nodes, package refs, env var requirements | | Docs | `.md .mdx .qmd .html .txt .rst .yaml .yml` | -| Office | `.docx .xlsx` (requires `pip install graphifyy[office]`) | -| Google Workspace | `.gdoc .gsheet .gslides` (opt-in; requires `gws` auth and `--google-workspace`; Sheets need `pip install graphifyy[google]`) | +| Office | `.docx .xlsx` (requires `uv tool install graphifyy[office]`) | +| Google Workspace | `.gdoc .gsheet .gslides` (opt-in; requires `gws` auth and `--google-workspace`; Sheets need `uv tool install graphifyy[google]`) | | PDFs | `.pdf` | | Images | `.png .jpg .webp .gif` | -| Video / Audio | `.mp4 .mov .mp3 .wav` and more (requires `pip install graphifyy[video]`) | -| YouTube / URLs | any video URL (requires `pip install graphifyy[video]`) | +| Video / Audio | `.mp4 .mov .mp3 .wav` and more (requires `uv tool install graphifyy[video]`) | +| YouTube / URLs | any video URL (requires `uv tool install graphifyy[video]`) | Code is extracted locally with no API calls (AST via tree-sitter). Everything else goes through your AI assistant's model API. @@ -240,7 +241,7 @@ in a headless extraction, install and authenticate the [`gws` CLI](https://github.com/googleworkspace/cli), then run: ```bash -pip install "graphifyy[google]" # needed for Google Sheets table rendering +uv tool install "graphifyy[google]" # needed for Google Sheets table rendering gws auth login -s drive graphify extract ./docs --google-workspace ``` diff --git a/pyproject.toml b/pyproject.toml index 0ac77f71..8c4c071d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "graphifyy" -version = "0.8.27" +version = "0.8.28" description = "AI coding assistant skill (Claude Code, Codex, OpenCode, Kilo Code, Cursor, Gemini CLI, Aider, OpenClaw, Factory Droid, Trae, Hermes, Kiro, Pi, Devin CLI, Google Antigravity) - turn any folder of code, docs, papers, images, or videos into a queryable knowledge graph" readme = "README.md" license = { file = "LICENSE" } @@ -40,7 +40,6 @@ dependencies = [ "tree-sitter-fortran", "tree-sitter-bash", "tree-sitter-json", - "tree-sitter-dm", ] [project.urls] @@ -65,7 +64,11 @@ gemini = ["openai", "tiktoken"] openai = ["openai", "tiktoken"] chinese = ["jieba"] sql = ["tree-sitter-sql"] -all = ["mcp", "neo4j", "pypdf", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "python-docx", "openpyxl", "faster-whisper; python_version >= '3.11'", "yt-dlp", "matplotlib", "openai", "tiktoken", "boto3", "tree-sitter-sql", "jieba"] +# tree-sitter-dm (BYOND DreamMaker) ships only a Windows wheel, so on Linux/Mac it +# must compile from source (needs a C toolchain + python3-dev). Keeping it optional +# avoids breaking the default `uv tool install graphifyy` for everyone (#1104). +dm = ["tree-sitter-dm"] +all = ["mcp", "neo4j", "pypdf", "markdownify", "watchdog", "graspologic; python_version < '3.13'", "python-docx", "openpyxl", "faster-whisper; python_version >= '3.11'", "yt-dlp", "matplotlib", "openai", "tiktoken", "boto3", "tree-sitter-sql", "jieba", "tree-sitter-dm"] [project.scripts] graphify = "graphify.__main__:main" diff --git a/tests/test_languages.py b/tests/test_languages.py index fefc1380..dab5ce56 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -13,6 +13,15 @@ from graphify.extract import ( FIXTURES = Path(__file__).parent / "fixtures" +# tree-sitter-dm is an optional extra (#1104) - it ships no Linux/Mac wheel, so it +# is not installed by a default `uv sync`. Skip the .dm/.dme grammar tests when the +# grammar is absent (.dmi/.dmm/.dmf use no tree-sitter and are always tested). +import importlib.util as _ilu +_needs_dm = pytest.mark.skipif( + _ilu.find_spec("tree_sitter_dm") is None, + reason="tree-sitter-dm not installed (optional [dm] extra)", +) + def _labels(r): return [n["label"] for n in r["nodes"]] @@ -1301,38 +1310,45 @@ def test_groovy_spock_no_dangling_edges(): # ── DM (BYOND DreamMaker) ──────────────────────────────────────────────────── +@_needs_dm def test_dm_no_error(): r = extract_dm(FIXTURES / "sample.dm") assert "error" not in r +@_needs_dm def test_dm_finds_global_proc(): r = extract_dm(FIXTURES / "sample.dm") labels = _labels(r) assert any(l == "log_event()" for l in labels) assert any(l == "RunTest()" for l in labels) +@_needs_dm def test_dm_finds_type_definition(): r = extract_dm(FIXTURES / "sample.dm") labels = _labels(r) assert "/datum/weapon" in labels assert "/datum/weapon/sword" in labels +@_needs_dm def test_dm_qualifies_proc_with_type_path(): r = extract_dm(FIXTURES / "sample.dm") labels = _labels(r) assert "/datum/weapon/attack()" in labels assert "/datum/weapon/sword/attack()" in labels +@_needs_dm def test_dm_finds_path_form_proc_definition(): r = extract_dm(FIXTURES / "sample.dm") assert "/datum/weapon/sword/sharpen()" in _labels(r) +@_needs_dm def test_dm_emits_include_edge(): r = extract_dm(FIXTURES / "sample.dm") import_edges = _edges_with_relation(r, "imports", "imports_from") assert import_edges assert all(e.get("context") == "import" for e in import_edges) +@_needs_dm def test_dm_unresolved_include_flagged_external(): r = extract_dm(FIXTURES / "sample.dm") import_edges = _edges_with_relation(r, "imports", "imports_from") @@ -1340,12 +1356,14 @@ def test_dm_unresolved_include_flagged_external(): assert helpers assert all(e.get("external") is True for e in helpers) +@_needs_dm def test_dm_resolves_in_file_calls(): r = extract_dm(FIXTURES / "sample.dm") calls = _calls(r) assert any(callee == "log_event()" for _, callee in calls) assert ("/datum/weapon/sword/attack()", "/datum/weapon/sword/sharpen()") in calls +@_needs_dm def test_dm_ambiguous_member_call_left_unresolved(): r = extract_dm(FIXTURES / "sample.dm") calls = _calls(r) @@ -1354,6 +1372,7 @@ def test_dm_ambiguous_member_call_left_unresolved(): assert not runtest_to_attack assert any(rc["callee"] == "attack" for rc in r.get("raw_calls", [])) +@_needs_dm def test_dm_emits_new_as_instantiates(): r = extract_dm(FIXTURES / "sample.dm") node_by_id = {n["id"]: n["label"] for n in r["nodes"]} @@ -1361,18 +1380,21 @@ def test_dm_emits_new_as_instantiates(): for e in r["edges"] if e["relation"] == "instantiates"] assert ("RunTest()", "/datum/weapon/sword") in inst +@_needs_dm def test_dm_call_edges_have_call_context(): r = extract_dm(FIXTURES / "sample.dm") call_edges = _edges_with_relation(r, "calls", "instantiates") assert call_edges assert all(e.get("context") == "call" for e in call_edges) +@_needs_dm def test_dm_no_dangling_edges(): r = extract_dm(FIXTURES / "sample.dm") node_ids = {n["id"] for n in r["nodes"]} for e in r["edges"]: assert e["source"] in node_ids +@_needs_dm def test_dm_super_call_not_emitted(): r = extract_dm(FIXTURES / "sample.dm") calls = _calls(r)