The node id set comparison missed a whole class of staleness: a merge,
a split, or a single node moving from one community to another can
leave the overall node set exactly unchanged while still describing a
different clustering, so a stale sidecar that happened to still cover
the same nodes kept winning. Compare each side's set of community
blocks instead, which catches a partition change even when the flat
node set stays identical, while still ignoring a pure id renumbering
of the same partition per the existing #1667 guard.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 30a5955931)
Guards against a false positive: an up to date sidecar (the normal
case immediately after extract or the clustering command) must not
trip the new staleness warning or take the reconstruction path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 21342bda37)
Confirms the wiki export path specifically, since it is the one that
prints cohesion into article metadata: a stale, impossibly large
cohesion value in the sidecar must not survive into the export once
staleness is detected, proving score_all actually ran on the fresh
reconstruction rather than the fossil value merely being ignored by
coincidence.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 12c50b975a)
Corrupts the analysis sidecar community node id set (the exact
staleness signature the issue describes) and confirms export html
still succeeds, still renders graph.html, and prints the new warning
instead of silently exporting against the fossil.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh
(cherry picked from commit 4518bae439)
The header logic is unit-tested, but only a real subprocess run proves the CLI
query command actually passes the resolved graph path through — the wiring that
was the point of #2789. Cover both the under-CWD relative form and an explicit
--graph outside the CWD shown in full. Plus 0.9.47 changelog entry.
cluster-only warns when --backend/--model/--batch-size are ignored on the
label-reuse path; the community-label prompt key no longer collides with
the discard sentinel (an echoed key was silently dropped); tree --root
exits non-zero when it matches no source file instead of flattening the
tree; and cluster-only stamps built_at_commit from the analysed graph, not
the shell cwd. Also folds in the cluster-only refused-write guard from
PR #2522 (thanks @aniJani).
Thanks @elecnix for the report.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#2486 (thanks @adminwat): normalize dict-shaped hyperedge members to ids
(or drop with a warning) so a malformed hyperedge can't abort a completed
merge with a TypeError.
#2484 (thanks @sortakool; approach from @oleksii-tumanov's #1691):
merge-graphs relabels hyperedge member ids and ids with the repo prefix,
unions both inputs' hyperedges instead of clobbering, and writes both
persistence slots.
#2485 (thanks @sortakool): build_from_json reads hyperedges from the
top-level and nested slots; a full validation wipeout is reported loudly.
#2490 (thanks @PapiScholz): the skill Step-5 flow passes curated
community_labels to to_json, so graph.json ships community_name.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The absolute-path-in-node-ids leak reported on 0.8.19 is already fixed on v8:
detect() returns paths relative to the scan root, so the CLI-produced graph.json
uses relative structural node ids (portable, no username/home leak). Lock it with
a regression test that extracts the same corpus from two different absolute
checkout dirs and asserts identical, leak-free node ids.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Case 1 — `extract <corpus> --out <dir>`: the graph went to <dir> (cache_root is
already passed to the AST extractor), but detect()'s word-count/stat-index cache
uses the scan root, so a stray graphify-out/cache/ was created inside the corpus
(and left behind even when the run aborted at the no-LLM-key gate). Thread an
optional cache_root through detect() -> cached_word_count() -> _ensure_stat_index()
and pass out_root from the extract CLI, so the stat index lives under --out. Entry
keys are absolute paths, so relocating the index file is safe.
Case 2 — `cluster-only --graph <elsewhere>/graphify-out/graph.json`: outputs
(GRAPH_REPORT.md, re-clustered graph.json, labels, analysis, html) were written to
the CWD's graphify-out/, ignoring where --graph lives. They now write beside the
input graph when it sits in a graphify-out/ dir (another project/tenant's output),
while still falling back to the CWD for an arbitrary archived backup/graph.json —
the restore-into-place workflow #934 pins.
Regression tests for both cases; #934 still passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The GRAPHIFY_OUT override (custom output-dir name / absolute path, #686) was only
respected by some readers. `graphify extract` and several commands hardcoded the
literal "graphify-out", so `GRAPHIFY_OUT=custom-out graphify extract` still wrote
to graphify-out/ and downstream query/serve/update looked in the wrong place.
Resolve the output-dir name through graphify.paths everywhere it matters:
- new graphify.paths.out_path()/default_graph_json() helpers
- __main__: extract write dir, cluster-only/label, query/affected/benchmark
defaults, save-result --memory-dir, uninstall --purge, cache-check
- detect: _MANIFEST_PATH, memory/ + converted/ dirs, and the scan-exclude (a
renamed output dir is no longer re-ingested as source input)
- transcribe._TRANSCRIPTS_DIR; build_merge/serve/benchmark/prs graph-path defaults
Default behaviour is unchanged: with no env var everything still uses graphify-out/.
Verified end-to-end (extract -> cluster-only -> query under GRAPHIFY_OUT=custom-out
writes/reads custom-out/, no stray graphify-out/) and added a CLI regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds FalkorDB as a sibling option to the existing Neo4j sink, selected via
`graphify export falkordb [--push redis://localhost:6379]`.
- New push_to_falkordb() in graphify/export.py mirrors push_to_neo4j; FalkorDB
is OpenCypher-compatible so the MERGE/SET upsert queries are identical.
- export falkordb subcommand wired in graphify/__main__.py (cypher.txt when no
--push, direct push otherwise). Auth is optional; target graph defaults to
"graphify".
- falkordb optional extra in pyproject.toml (and in the all extra).
- Tests: CLI cypher generation (CI-safe) + real-FalkorDB integration tests that
skip when no instance is reachable.
- README extras table + command reference and CHANGELOG updated.
cluster-only re-runs Leiden clustering and then re-applies the existing
.graphify_labels.json by raw cid index, which causes labels to attach to
clusters whose members are unrelated to the label's original meaning
whenever the graph has changed between labeling and re-clustering.
Mirror the safety net already present in watch.py:_rebuild_code added in
#822 for the watch/update paths.
Adds a regression test that fails without the fix (label cids become
orphaned from graph.json community attributes after re-clustering).
Refs: #1027
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`graphify export html|obsidian|wiki|svg|graphml|neo4j` reads `communities`
exclusively from `.graphify_analysis.json` (set to `{}` if missing). The
post-commit / watch rebuild path doesn't regenerate that sidecar — only
graph.json + GRAPH_REPORT.md. Several skill workflows also delete temp
files at the end of `graphify extract`. In both cases the per-node
`community` attribute (`to_json` writes it on every node) is intact, but
the CLI ignores it.
Observed failure: `graphify export html` on a graph that exceeds the
viz node limit prints
Graph has 64703 nodes (above 5000 limit). Building aggregated community view...
Single community - aggregated view not useful. Skipping graph.html.
even though the same graph.json has 2,026 distinct `community` values
on its nodes — `to_html` just received an empty `communities` dict and
the aggregator collapsed to a single meta-node.
Fix: when the analysis sidecar is absent (or its `communities` field is
empty), reconstruct the `cid -> [node_ids]` mapping from the per-node
attribute in graph.json. The sidecar remains the canonical source of
truth when present; the reconstruction is a strict fallback. Every
downstream subcommand (`html`, `obsidian`, `wiki`, `svg`, `graphml`,
`neo4j`) sees the same shape it always did, just populated from the
graph itself instead of an externally-cached sidecar.
Tests added (tests/test_cli_export.py):
- `test_export_html_falls_back_to_node_community_attribute` —
delete the sidecar, run export html, confirm `graph.html` exists
and the "Single community" bail-out path does NOT fire.
- `test_export_html_fallback_recovers_multiple_communities` —
stronger guarantee that the reconstructed community count equals
what the sidecar would have provided (no silent data loss).
- `test_export_html_no_community_data_at_all_still_succeeds` —
hand-build a graph.json with no per-node `community` attribute
(older `to_json` versions, manually-constructed graphs); the
command must still exit cleanly rather than crash.
All 26 tests in test_cli_export.py pass; ruff clean on both files.
Make `graphify update` idempotent by skipping output rewrites when graph/report content is unchanged, add `update --no-cluster`, and preserve community IDs across runs via overlap-based remapping with deterministic partition inputs.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add Fortran support (26th language): .f/.F/.f90/.F90/.f95/.F95/.f03/.F03/.f08/.F08
via tree-sitter-fortran; capital-F files preprocessed with cpp -w -P
- Add graphify export {html,obsidian,wiki,svg,graphml,neo4j} CLI subcommands
- Add graphify query/path/explain CLI subcommands
- Reduce skill.md from 63KB to 47KB by replacing Python heredocs with CLI calls
- Extend to_html() with node_limit param for auto-aggregation on large graphs
- Add integration tests for all export/query/path/explain subcommands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>