* Fix#563: prevent rationale-node leakage and preserve calls direction
Two AST-extractor bugs were inflating god-node centrality:
1. Rationale leakage in cross-file resolvers
_resolve_cross_file_imports indexed any node whose label didn't end in
')' or '.py' as an importable entity, so rationale nodes (whose labels
are docstring text) ended up in both stem_to_entities and local_classes.
Result: phantom '<file>_rationale_N --uses--> ImportedThing' edges for
every imported entity in every file with docstrings.
The same leak existed in cross-file call resolution via
global_label_to_nid, where rationale labels could collide with callee
names.
Fix: skip nodes with file_type == 'rationale' in all three index loops.
2. Calls / rationale_for direction lost at export
to_json() called nx.json_graph.node_link_data() on a NetworkX undirected
Graph, which canonicalizes endpoint order. The build path already
stashes _src and _tgt on every edge for exactly this case (with a
comment to that effect at build.py:100), but the exporter never
consulted them, so 'calls' and 'rationale_for' edges were emitted with
arbitrary direction.
Fix: in to_json(), restore link['source'] / link['target'] from
_src / _tgt, then pop those internal fields before writing graph.json.
Repro on a minimal 3-file project (queue.py + contact_form.py + a test
file with multi-paragraph docstrings) showed:
Before fix: 31 edges, 12 incident on SQLiteQueue, 7 phantom uses-edges
from rationale nodes, 1 inverted calls edge.
After fix: 24 edges, 5 incident on SQLiteQueue, 0 phantom edges,
all calls edges go caller -> callee.
Adds tests/test_issue_563.py with 5 regression tests covering both bugs
plus a direction-pin on the existing sample_calls.py fixture. Full suite
passes (446/446).
* Address Copilot review on #576
- tests: identify rationale nodes by file_type metadata, not id substring
(decouples regression from current `<stem>_rationale_<line>` naming)
- tests: assert contact_form -> SQLiteQueue.enqueue calls edge directly
(previously only inversions were guarded; a dropped enqueue edge would
have passed silently)
- extract.py: clarify cross-file import indexing comment — the filter
intentionally indexes only class-level entities (function/method labels
end in "()" and are excluded by design)
* Fix#563 (HTML export): restore arrow direction in graph.html
to_json was patched to consult _src/_tgt before serializing edge
endpoints, but to_html still read endpoints directly from G.edges(),
so calls and rationale_for arrows in the rendered graph.html still
pointed in canonicalized (often inverted) order.
Apply the same direction restore in to_html when building vis.js
edges. Use data.get (not pop) since G.edges(data=True) yields the
live attribute dict and other exporters may run after to_html.
Adds test_to_html_preserves_calls_and_rationale_for_direction:
parses RAW_EDGES out of the rendered HTML and pins both the
caller->callee assertions from #563 and the rationale->parent
direction. Without the fix, the test reports 11 flipped arrows
on the 3-file repro.
to_wiki() writes a fresh set of community + god-node articles each call but
never deletes old files from previous runs. Since community labels are
LLM-generated and non-deterministic across rebuilds (per skill.md Step 5),
the same conceptual community is often named differently each time, leaving
its previous file as an orphan. After N rebuilds, wiki/ contains roughly N
times the active article count, with index.md only referencing the most
recent run's labels.
Real-world: a knowledge corpus accumulated 822 wiki .md files over 5
rebuilds, of which only 111 were referenced by index.md (710 orphans).
Fix: clear *.md files in the output directory at the start of to_wiki().
This is consistent with its existing fully-regenerative behavior — it
always writes the full set of articles + index, never partial updates.
Subdirectories and non-.md files are preserved (only top-level .md is
touched), so any user-placed auxiliary assets survive.
Tests: two new regression tests cover (1) stale article cleanup across
runs with different labels, and (2) preservation of non-.md user files
and nested subdirectories.
When a user configures core.hooksPath = ~/gitconfig/hooks in .gitconfig,
_hooks_dir() was constructing Path("~/gitconfig/hooks") without calling
expanduser(), so hooks were installed into <repo>/~/gitconfig/hooks instead
of the intended absolute path.
Add Path.expanduser() call immediately after reading the raw string from
git config, before the is_absolute() / root-relative fallback logic.
Fixes#547
Grep and Glob tools removed in CC v2.1.117; searches now go through Bash.
Hook now reads stdin tool_input and pattern-matches on search commands.
Uninstall/reinstall handles both old and new matcher for clean upgrades.
Closes#578
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- #352: add skill-kiro.md to pyproject.toml package-data
- #341: guard edge_betweenness at >5000 nodes; use approximate k=100 for suggest_questions on large graphs
- #354/#229: add Step 6b in skill.md to call to_wiki() when --wiki given (before Step 9 cleanup)
- #356: call _install_opencode_plugin() from install --platform opencode path
- #350: add cache_root param to extract() so subdirectory runs keep cache at ./graphify-out/cache/
- #230: PHP class_constant_access_expression emits references_constant edges
- #232: PHP scoped_call_expression (static method calls) emits calls edges
- #287: os.replace fallback for Windows WinError 5; graphify update exits 1 on failure; templates use graphify update . instead of python3 -c
- #348: cross-file call resolution for all languages via raw_calls + global label map pass in extract()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: suppress graspologic ANSI output that breaks PowerShell scrolling
graspologic's leiden() emits ANSI escape sequences (progress bars,
colored warnings) that corrupt PowerShell 5.1's scroll buffer on
Windows, disabling vertical scrolling. Redirect stdout/stderr to
StringIO during leiden() calls to prevent any escape codes from
reaching the terminal.
Add 2 tests verifying cluster() produces no stdout/stderr output.
Fixes#19
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add PowerShell troubleshooting section to Windows skill
Document the PowerShell 5.1 scrolling issue and provide 4
workarounds: upgrade graphify, use Windows Terminal, reset
terminal, or uninstall graspologic to use Louvain fallback.
Fixes#19
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Swift language support
Add tree-sitter-swift extractor for classes, structs, protocols,
functions, imports, and call graph edges. Includes 8 passing tests.
* feat: full Swift AST support — enums, extensions, actors, conformance
- Enums: extract enum types, methods, and cases (case_of edges)
- Extensions: methods attach to the original type (no duplicate nodes)
- Actors: recognized via unified class_declaration node type
- Conformance/inheritance: inherits edges from : Protocol syntax
- deinit/subscript: name resolution for nameless declarations
- 12 new tests (110 total, all passing)
- Replace pyvis with custom vis.js renderer: node size by degree,
click-to-inspect panel with clickable neighbors, search box,
community filter, physics clustering by community
- HTML graph generated by default on every run (no --html flag needed)
- Token reduction benchmark auto-runs after every /graphify on corpora >5k words
- Fix 292 edge warnings: silently skip stdlib/external edges in build.py
- Fix build() to merge extractions before building (cross-extraction edges were dropped)
- Add 5 HTML renderer tests (223 total)
- Remove unnecessary files: lib/, tests/eval_attention.py, misplaced eval reports
- Add graphify-out/ and .graphify_*.json to .gitignore
- Bump version to 0.1.4, remove pyvis dependency
- README: token reduction as top-level selling point, vis.js in tech stack,
graph.html in output listing, correct test count and install command
style: replace all em dashes with hyphens
fix: explain hidden .graphify/ folder in skill output and README
fix: rename .graphify/ to graphify-out/ so output is visible by default