[Bug]: export html/neo4j/falkordb/canvas, merge-driver and global add reverse stored edge direction
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
- #4088 @e4c5 による — マージされずにクローズ
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 48/100
調査の方向性
Start with paths.load_node_link_graph, global_graph.global_add, the export loader in cli.py, watch._reconcile_graph_html, and the merge-driver path. Compare their handling with the _src/_tgt logic in query, path/explain, affected, merge-graphs, and to_html, then run the reproduction and the reported regression tests. Done means merge-driver, global add, HTML, Neo4j, FalkorDB, and Canvas outputs preserve graph.json edge direction without breaking undirected graph behavior.
索引モデルが issue の本文から書いたものです。
説明
Pre-flight checks
- I have checked the Troubleshooting section in the README
What happened?
Several commands that reload graph.json reverse the direction of stored edges. Some write the reversal to disk: the git merge driver rewrites the committed graph.json, and global add writes ~/.graphify/global-graph.json.
graph.json is written with "directed": false. The real direction is in each link's arc order (source → target, since #563), or in _src/_tgt on older files (#2309). A plain undirected json_graph.node_link_graph() load re-orders each edge's endpoints by node-list position. An edge comes back reversed exactly when its target node comes before its source node in the node list. On graphify's own graph.json that is 21,098 of 26,727 links (79%). It is deterministic, so the output looks stable.
Several readers already handle this with the _src = link.get("_src", link["source"]) idiom: query, path/explain, affected, merge-graphs and the MCP server (#1174, #2261, #2309, #2487). These paths still don't:
| Path | Where | Effect |
|---|---|---|
merge driver (graphify hook install) |
paths.load_node_link_graph + raw node_link_data write in merge-driver |
Rewrites the committed graph.json reversed, even when merging three identical copies. Later readers take it as truth. |
global add |
undirected load in global_graph.global_add |
~/.graphify/global-graph.json stored reversed; path on the global graph gives wrong routes. |
graphify update / watch / git hooks |
watch._reconcile_graph_html → load_node_link_graph |
graph.html arrows backwards after any topology change (one 1-function edit: 14/19 arrows reversed). |
export html |
export loader in cli.py |
graph.html arrows backwards. |
export neo4j / export falkordb (cypher.txt and --push) |
export loader; to_cypher, push_to_neo4j, push_to_falkordb ignore _src/_tgt |
(a)-[:CALLS]->(b) written as (b)-[:CALLS]->(a), so "who calls X" queries return callees. |
export obsidian → graph.canvas |
export loader; to_canvas ignores _src/_tgt |
Canvas arrows backwards. |
Expected: every output keeps the direction stored in graph.json.
Not affected: cluster-only/label HTML (rebuilt via build_from_json), and the readers listed above. export graphml declares edgedefault="undirected", so it drops direction rather than contradicting it.
Steps to reproduce
mkdir -p repro/graphify-out && cd repro
cat > graphify-out/graph.json <<'EOF'
{"directed": false, "multigraph": false, "graph": {},
"nodes": [{"id": "normalize", "label": "normalize()", "file_type": "code", "source_file": "util.py"},
{"id": "tokenize", "label": "tokenize()", "file_type": "code", "source_file": "util.py"}],
"links": [{"source": "tokenize", "target": "normalize", "relation": "calls", "confidence": "EXTRACTED"}]}
EOF
# graph.json: tokenize calls normalize. The callee is listed first in "nodes".
graphify export neo4j
grep CALLS graphify-out/cypher.txt
graphify export html
grep -o '"from": *"[a-z]*", *"to": *"[a-z]*"' graphify-out/graph.html
# merge driver, three identical inputs
cp graphify-out/graph.json base.json; cp base.json cur.json; cp base.json other.json
graphify merge-driver base.json cur.json other.json
python3 -c "import json; [print(l['source'], '->', l['target']) for l in json.load(open('cur.json'))['links']]"
Error output or graph output
MATCH (a {id: 'normalize'}), (b {id: 'tokenize'}) MERGE (a)-[:CALLS {confidence: 'EXTRACTED'}]->(b);
"from": "normalize", "to": "tokenize"
normalize -> tokenize
All three should be tokenize → normalize.
Graphify version
0.9.75 (v8 @ 48d7c0e)
Operating System
Linux
Python Version
3.12
Installation Method
built from source (git clone)
Additional Environment Details
No provider environment variables set. Reproduced on a clean checkout of v8 at 48d7c0e and with the uv tool install of 0.9.75.
Additional context
Proposed fix (I'm happy to send PRs):
- Add one helper that copies arc order into
_src/_tgt, keeping any existing markers. It is the same idiomqueryandmerge-graphsalready use inline. - Call it in
paths.load_node_link_graph, theexportloader andglobal_add. - Make
to_cypher,to_canvasandpush_to_*readdata.get("_src", u)/data.get("_tgt", v), asto_htmlalready does. - Have the merge driver pop the markers back into arc order before writing, as
to_jsondoes.
I've prototyped this with regression tests for each path above. Each test fails on v8 and passes with the fix, and the full suite is otherwise unchanged.
Forcing a directed load instead would break Obsidian/wiki output: DiGraph.neighbors() returns successors only, so notes lose their incoming links.
I'd split it into two PRs:
- Loaders, exporters and the merge driver.
global add. That one also has to remap_src/_tgtwhen it merges external-library nodes shared between repos. Today it only rewires the edge endpoints, so markers can name a node that no longer exists, and that already happens on v8 for files that carry markers.
Existing outputs after a fix:
- A graph.json already rewritten by the merge driver:
graphify updaterepairs code edges. Semantic edges are carried over, so they need re-extraction. global-graph.json:global addskips unchanged repos, so it needsglobal remove <tag>and thenglobal add.- graph.html: an unchanged
updatedoesn't re-render it, so it needsgraphify export html.
Related: #3495, which covers the same "directed": false mismatch from the external-consumer side. #3022 moves global_add onto a new loader, but with preserve_direction=False, so it keeps this reversal.
AI disclosure: I investigated this and wrote the reproduction and prototype with Claude Code (Claude Opus 5.5), and verified them myself.
- 主要言語
- Python
- スター
- 124k
- フォーク
- 11.9k
- PR マージ指標
- 30日以内にマージされた PR はありません
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Graphify-Labs/graphify のほかの issue
-
hooks: last-resort `python3` probe launches the Microsoft Store redirector on Windows (visible console flash, exit 9009)対応中かも @Deepjyoti-Sarmah が 8 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
Graphify-Labs/graphify#4563 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
GRAPHIFY_DISABLE_THINKING is ignored by the claude backend, so thinking eats max_tokens and extraction truncates対応中かも @bercedev が 8 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 69/100
Graphify-Labs/graphify#4546 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
--update runbook: graph.json backup instruction comes after the diff that needs it, so the graph diff never runs対応中かも @sunildivakar96-cpu が 9 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
Graphify-Labs/graphify#4539 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[Bug] Pi skill uses unsupported general-purpose subagent instead of delegate再び着手できるかも @bercedev が 11 日前に担当しましたが、オープン中のプルリクエストはありません。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
Graphify-Labs/graphify#4494 · コメント 7 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
Graphify-Labs/graphify#3763 ·
メンテナーはふだん 1 日以内に返信
Graphify-Labs/graphify の issue をすべて見る
似ている issue
-
lesson-submission pending-review
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
Ikalus1988/MisakaNet#3956 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
aws-samples/sample-per-model-bedrock#574 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 70/100
521xueweihan/HelloGitHub#4133 ·
-
plot_components fails for logistic models with minmax scaling and no floor対応中かも @RaphaelFakhri が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 4 日以内に返信
-
Update migenオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
YosysHQ/oss-cad-suite-build#220 ·