Commit Graph

604 Commits

Author SHA1 Message Date
Alexey Z ec3cb5eb3e feat(dart): modernize AST parser, support nested generics and part-of redirection (#1098)
Modernizes the Dart extractor: comment stripping, `part of` redirection, nested-generic-aware extends/with/implements parsing, generic type-argument mapping, and generic call detection.

Verified against current v8: merges cleanly, full suite 1530 passed, and inheritance edges connect to same-file class nodes with no ghost-node splits or dangling edges (consistent with the #1033/#1096 node-ID invariants).
2026-06-01 16:13:36 +01:00
alacasse a8005c218a feat: add Kilo Code support (#512)
Adds Kilo Code as a supported platform: native skill + /graphify command, install/uninstall, and a .kilo tool.execute.before plugin (mirroring the OpenCode integration).

JSONC config is handled non-destructively - existing .kilo/kilo.jsonc is read but never rewritten; automated plugin registration goes to kilo.json, preserving user comments (addresses the Qodo review flag).

Verified locally: full suite 1525 passed, install/uninstall smoke test works.
2026-06-01 16:05:07 +01:00
Safi 4b17f199af drop redundant labels-file write in cluster-only
The auto-labeling branch wrote .graphify_labels.json, but the unconditional
write at the end of cluster-only already persists the final labels dict
(LLM or placeholder). Removed the duplicate; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.8.27
2026-05-31 23:44:42 +01:00
Safi 1dc5048b71 fold community labeling into llm.py instead of a standalone module
No behavior change. label_communities / generate_community_labels live next
to the backend dispatch (_call_llm, detect_backend) they use, so the lazy
import and separate file are no longer needed. Tests unchanged except the
import path; they still monkeypatch graphify.llm._call_llm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 23:37:28 +01:00
Safi 3922235eba release 0.8.27
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 23:31:50 +01:00
Safi c8b329d8f2 auto-name communities with the configured backend in standalone CLI (fixes #1097)
Community labeling was an agent-only step (skill.md Step 5): inside Claude Code
or Gemini CLI the agent names communities itself. Run as a bare CLI
(python -m graphify extract . --backend X), no agent does Step 5, so labels
stayed Community 0/1/2 for every backend.

Add graphify/labeling.py: one batched _call_llm asking the backend for a
{cid: name} map from each community's top node labels (god nodes first),
with per-community placeholder fallback and graceful degradation (no backend,
API error, or malformed reply -> Community N, never crashes).

Wire it in:
- cluster-only auto-labels when no .graphify_labels.json exists (the reported
  repro path); --no-label opts out, --backend=<name> overrides auto-detect
- new `graphify label <path>` subcommand force-regenerates names
- extract prints a hint pointing standalone users at cluster-only

Tests mock the backend (no network): happy path, partial/fenced/malformed
replies, no-backend and error fallback, god_nodes dict shape.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 20:46:39 +01:00
Safi ad0c8c0a62 relativize symbol node IDs for root-level files to match spec (fixes #1096)
The #1033 remap relativized file node IDs but symbol IDs still embedded the
absolute parent-dir stem, so a root-level file's symbols became
<rootdir>_main_run while the file node was correctly main and the skill.md
spec (and semantic subagent) wants main_run -- splitting every top-level
file's symbols into AST/semantic ghost pairs.

Extend the remap chokepoint to also canonicalize the symbol stem prefix
(old _file_node_id(path) -> new _file_node_id(rel)), gated by source_file so
two files sharing a prefix cannot cross-contaminate. raw_calls.caller_nid is
rewritten too, since the cross-file call pass consumes it after the remap and
would otherwise dangle. No-op for nested files (immediate parent identical in
absolute and relative form).

The originally-filed root cause (facts-collector path form) was a stale-cache
red herring; the real bug is the symbol-level continuation of #1033.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 17:09:30 +01:00
Safi 88a8e3bc91 capture TS interface-extends and same-file class heritage (fixes #1095)
Two gaps in TypeScript inheritance:

1. interface heritage is an extends_type_clause node, not class_heritage,
   so the walker never saw it and interface extends produced no inherits
   edge. Add an extends_type_clause branch reusing _ts_heritage_clause_entries
   (handles multiple extends).

2. a same-file superclass has no import alias, so the use-fact resolver
   (which only consulted the import table) dropped it; only imported bases
   resolved. Add a same-file fallback against the file's own symbol_nodes,
   scoped to inherits/implements so it does not duplicate same-file calls
   that already resolve via the call-graph pass. Import resolution still
   takes precedence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 16:47:52 +01:00
Safi 690b4e5d3e cap obsidian/canvas filenames to avoid ENAMETOOLONG on long labels (fixes #1094)
to_obsidian and to_canvas built note filenames from node labels with no
length cap, so a label >=255 bytes crashed write_text with OSError. Add a
shared _cap_filename helper that caps on UTF-8 bytes (not chars, so CJK
labels don't slip past) and appends an 8-char hash of the full label when
truncating, so two distinct labels sharing a long prefix stay distinct.
Both safe_name builders route node, community and canvas filenames through
it; wikilinks stay consistent because they read the same filename dict.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 16:41:18 +01:00
Safi c898dc62cd match AST file-level node IDs to the skill.md spec (fixes #1033)
AST file nodes were ID'd from the full relative path plus extension
(match_script_pipeline_step_py) while semantic subagents follow the
{parent_dir}_{stem} spec (script_pipeline_step), so every file split
into two disconnected ghost nodes.

Fix at the single remap chokepoint in extract(): file node IDs and all
edge endpoints already funnel through the #502 relative-path remap, so
changing that remap to emit _file_node_id (one parent dir, no extension)
converts the node and every referencing edge together - Python, TS, Lua,
C and bash import edges all stay connected. symbol_resolution pre-computes
the canonical form directly (bypassing the remap) so it is synced too.

Per-site conversion (as attempted in #1038/#1065) orphans edges because
it moves the node without the edge targets; the chokepoint approach
avoids that entirely.

Backward compat for existing graphs: graphify extract --force, as the
skill.md spec already documents.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 12:23:56 +01:00
Safi 8db19d6c85 sort file list lexicographically in detect() for deterministic graph output
os.walk order is non-deterministic across runs (filesystem b-tree order
shifts with cache/mount state), causing first-writer-wins decisions in
cross-file resolution to produce different node IDs each run; sorting
all_files and per-FileType lists stabilises graph.json without changing
any parent-child or connectivity semantics (closes #1090)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:20:18 +01:00
Safi 0cf596aa8a fix test_no_incremental_without_manifest false positive
pytest derives tmp_path from the test name, so the path contained
"incremental" and the bare-word assertion always failed; check for
the actual incremental-mode phrases instead

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 00:10:19 +01:00
Safi 31a608f73e fix Windows Unicode crash on non-UTF-8 consoles (issue #992)
reconfigure stdout/stderr to UTF-8 at startup; replace → and — in all
print statements with ASCII equivalents as belt-and-suspenders fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 00:06:35 +01:00
Safi 55d7dadb63 bump to 0.8.26
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.8.26
2026-05-30 21:40:52 +01:00
Manoj Mishra c066511bf2 feat: detect circular import dependencies at file level (#961)
* feat: detect circular import dependencies at file level

- Add find_import_cycles() to analyze.py
- Collapses symbol nodes to parent files, builds directed file graph
- Uses nx.simple_cycles() bounded by max_cycle_length (default 5)
- Deduplicates rotations, returns shortest cycles first
- Considers both imports_from and re_exports edges

Tested on a 976-file Next.js codebase: found 4 cycles including
a known utils↔barrel circular dependency and a 4-file API cycle.

* fix: resolve import-cycle merge blockers

- use source_file-only endpoint resolution (no label fallback)
- support Graph/DiGraph orientation via edge source_file
- return structured cycle records and include self-loops
- integrate Import Cycles section into GRAPH_REPORT.md
- expand cycle tests for real-schema IDs, undirected input,
  missing source_file nodes, and non-import relations
2026-05-30 21:37:47 +01:00
Safi cca13aa8fd fix anchored gitignore pattern leaking basename match into subtree (closes #1087)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 18:16:01 +01:00
Safi a9d6be6537 add custom LLM provider registry via providers.json (closes #1084)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 13:25:02 +01:00
Safi 006e1594eb fix extract_files_direct backend default: auto-detect instead of kimi (closes #1086)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 13:17:13 +01:00
Safi d4e1d4b553 fix pnpm-workspace.yaml packages:'.' crash on Python 3.10 (closes #1083)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 13:13:23 +01:00
Alexis Valentino 5056c72e67 docs: add Filipino (fil-PH) README translation
Co-authored-by: alexisvalentino <alexisvalentino@users.noreply.github.com>
2026-05-29 23:43:56 +01:00
Safi a7a732290c release 0.8.25: JS/TS phantom nodes, Lua imports, Windows cli, hook lock, antigravity path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.8.25 v0.8.24 v0.8.23
2026-05-29 16:59:17 +01:00
Safi 998594091b fix antigravity global install path to ~/.gemini/config/skills/ and uninstall symmetry (#1079)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 15:15:26 +01:00
Safi 9db86942bd warn against pip install on Mac/Windows due to env path mismatch (#1074)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 14:13:05 +01:00
Safi 282afaac7d add missing test coverage for #1077 scope guard and markdown fence fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 14:07:03 +01:00
Safi 925eb81ec1 fix JS/TS phantom god-nodes from arrow-fn locals and drop markdown code-block orphan nodes (#1077)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 13:46:30 +01:00
Safi 5642c1b533 fix Lua require() import edges lost due to wrong target ID resolution (#1075)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 13:36:04 +01:00
Safi f0badd9899 fix Windows claude-cli WinError 2 and post-commit hook silent drop on rapid commits
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 11:41:42 +01:00
Safi fd1aca480f release 0.8.24: cross-language semantics, claude-cli fix, edge direction fix, security audit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 02:28:52 +01:00
Safi 8a308519f9 Merge pull request #1063 from christophepub/fix/claude-cli-hollow-responses-v2
Fix hollow-response loop in claude-cli backend (re-targeted to v8, clean diff, with tests)
2026-05-29 01:54:37 +01:00
Safi aae027f7e6 Merge pull request #1071 from TheFedaikin/v8
Fix CI and ddd semantic contexts for ObjC, Julia, C, C++, Scala, Fortran, and PowerShell
2026-05-29 01:54:30 +01:00
Danil Tarasov 0080fbd13c feat: add objc, julia, c, c++, scala, fortran, powershell semantic contexts 2026-05-29 00:05:28 +03:00
Safi 66acfd81aa fix calls edge direction flipping on bidirectional pairs in undirected build (#1061)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:40:04 +01:00
Safi c2de9fa72f add uv-aware install detection to all skill files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:07:24 +01:00
Safi 8e179736ce fix .graphify_python path missing graphify-out/ prefix in skill files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 16:55:07 +01:00
christophepub 379d35e088 fix(claude-cli): eliminate hollow-response loop from system-prompt conflict
Three compounding bugs caused ~30-50% of semantic chunks to come back
as 'hollow responses' on the claude-cli backend, triggering adaptive
bisection that doubled or tripled the number of subprocess calls.

Root causes
-----------
1. _parse_llm_json only stripped markdown fences when raw.startswith('```').
   Claude frequently prepends a short preamble before the fence
   ('Here are the extracted entities:\n\n```json\n{...}```'), making
   the check fail. json.loads then drops the chunk. Each bisected half
   may exhibit the same failure, so cost compounds.

2. _call_claude_cli used --append-system-prompt, which layers graphify's
   extraction prompt on top of Claude Code's default interactive-agent
   prompt ('use markdown formatting', 'output text to communicate with
   the user'). The conflicting instructions explain ~50% of the
   preambles and fences from (1). Switching to --system-prompt (replace)
   eliminates the conflict at the source.

3. claude-cli defaults to Opus, which is overkill for the structured
   JSON extraction graphify performs. New GRAPHIFY_CLAUDE_CLI_MODEL env
   var lets users opt into haiku / sonnet for big builds. Default
   behaviour unchanged when the env var is unset.

Fix
---
- Robust _parse_llm_json: strips fences regardless of position, with a
  balanced-brace fallback that scans for the first complete JSON object
  in the response. Handles preambles, trailing prose, prose-wrapped
  JSON without fences. Diagnostic log on terminal failure includes the
  first 200 chars of the response.
- _call_claude_cli switches to --system-prompt.
- _call_claude_cli respects GRAPHIFY_CLAUDE_CLI_MODEL when set.

Tests (tests/test_llm_parser.py)
--------------------------------
- The four PR-body failure modes: preamble+fence, prose+JSON, raw JSON,
  total refusal.
- Bonus: uppercase fence tag, unclosed fence, empty response.
- argv shape: --system-prompt present, --append-system-prompt absent.
- argv shape: --model added iff GRAPHIFY_CLAUDE_CLI_MODEL is set.

19/19 tests pass (9 pre-existing in test_claude_cli_backend.py +
10 new). Verified end-to-end on a 800-file repo: 0 hollow responses
after, vs ~30-50% before; output tokens -93%; wall time 44 min -> 4 min.
2026-05-28 17:44:35 +02:00
Safi 7c12499783 chore: install all extras in CI to catch test gaps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 14:53:45 +01:00
Safi ffbe425c2f release 0.8.23
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 14:52:01 +01:00
TheFedaikin 32aa053e6c feat: semantic type-reference edges for Swift, Kotlin, PHP, Rust, and Go (#1015) 2026-05-28 14:46:03 +01:00
Safi cddf47d3a0 chore: commit uv.lock (regenerated for 0.8.22 with tree-sitter-dm)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 14:38:59 +01:00
Brian K 244a266c06 chore: switch CI to uv, remove uv.lock from gitignore (#885)
* chore: declare pytest as a uv dev dependency

The contributing guide currently tells contributors `pip install pytest`
as a separate step, and CI does the same. Move pytest into PEP 735
`[dependency-groups]` so it's declared in pyproject.toml and `uv sync`
installs it by default (no `--with` workaround, no separate install
line). Update CI to use astral-sh/setup-uv + `uv sync` + `uv run pytest`,
and refresh the Contributing section of the README to match.

`[dependency-groups]` is the right home (vs `[project.optional-dependencies]`)
because pytest is dev-only and shouldn't appear in the published wheel's
optional features list alongside things like `pdf` or `mcp`.

* remove uv.lock from gitignore
2026-05-28 14:38:31 +01:00
Safi a1706ff7fd docs: add --mode deep to graphify extract CLI reference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:30:36 +01:00
Safi 43bf3c2371 release 0.8.22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.8.22
2026-05-28 00:26:31 +01:00
Safi cfc945a507 fix: cap zlib decompression in extract_dmi, add size guard in extract_dmm
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 00:08:55 +01:00
Safi dacbdb539a feat: BYOND DreamMaker support, --mode deep flag, changelog fixes (#884, #1030)
- Feat: extract_dm (tree-sitter-dm), extract_dmi (PNG icon states),
  extract_dmm (tile dict uses edges), extract_dmf (window/elem hierarchy)
  for .dm .dme .dmi .dmm .dmf; 26 tests, fixtures, pyproject.toml dep
- Feat: graphify extract --mode deep flag; deep_mode threaded through all
  four LLM backends via extract_corpus_parallel
- Fix: CHANGELOG 0.8.21 entries for #1050, #1046, #1047 that were missing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:55:12 +01:00
Aadi c09fbef401 fix: remap hyperedges in community-aggregated meta-graph view (#1006)
When a graph exceeds the viz node limit, to_html() builds a
community-aggregated meta-graph and recursively calls itself.
The recursive call never carried hyperedges onto the meta-graph,
so graph.html always emitted const hyperedges = [] even when
graph.json contained plenty.

This fix remaps hyperedge node references from semantic node IDs
to community IDs before the recursive call, so hyperedge regions
render correctly in the aggregated view. Hyperedges that collapse
to fewer than 2 distinct communities are dropped (they wouldn't
render as a polygon anyway).

Fixes #1005
2026-05-27 22:39:01 +01:00
Safi 9f73400cbc fix: memory-dir gitignore leak, Pass 2 dedup cross-file identical merge, decorated method node ID mismatch (#1047, #1046, #1050)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 22:30:08 +01:00
Safi 740382af51 docs: add Amp platform, .svh extension, fix opencode uninstall in README 2026-05-27 13:04:50 +01:00
Safi 80301a06bf fix: OpenCode project path, hook loop guard, deterministic output, Amp platform, punctuation search, builtin god-node filter, .svh Verilog (#1040, #1018, #1037, #948, #994, #916, #1042)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.8.21
2026-05-27 12:39:22 +01:00
Aleksandr Tarutin a54a542b6c fix: make graph output deterministic (stop graphify-out churn) (#1010)
graphify-out regenerates differently on every `graphify update` even
when no source changed, so the committed graph is perpetually dirty and
the post-commit/post-checkout hooks fight every commit. Two independent
nondeterminism sources, each fixed here:

1. Edge direction flips. build.py builds an undirected graph and stores
   direction in _src/_tgt; collapsing two edges onto the same node pair
   is last-write-wins, and unstable edge iteration order flips them
   run-to-run. Fixed by sorting edges by (source, target, relation)
   before the add loop.

2. Clustering churn. The networkx Louvain fallback iterates string-keyed
   sets whose order is randomized per-process by PYTHONHASHSEED, so
   community assignments differ run-to-run even with seed=42. Fixed by
   exporting PYTHONHASHSEED=0 in the generated post-commit and
   post-checkout hook scripts.

With both fixes, `graphify update` is idempotent: rebuilding an
already-converged graphify-out reproduces graph.json and GRAPH_REPORT.md
byte-for-byte.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 12:20:31 +01:00
Safi d1d5751fa4 fix(watch): evict stale nodes in full re-extraction path when changed_paths is None (#1007)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 11:21:55 +01:00