Found on graphifyy 0.9.26 (uv tool install, Python 3.11, Linux), running graphify <dir> --backend claude-cli. Unlike #2204 and #2205 this one is not export-specific — it is in the extractor.
Summary
_extract_python_rationale / _extract_js_rationale build the node label by slicing the raw docstring to 80 characters before normalizing whitespace. Three consequences:
- the slice lands mid-word (
... solo dev, ... GET articles/consulta). Si);
- the docstring's newline + indentation survives as a run of literal spaces inside the label, because the slice happens before the
\n → replacement collapses anything;
- a docstring ending in
. at the slice boundary yields an Obsidian filename ending in ..md.
These labels are not export-only — they are the label in graph.json, so they also show up in GRAPH_REPORT.md, in graphify query output, and in explain.
Where
graphify/extract.py:1075 (Python) and graphify/extract.py:1212 (JS/TS) — identical line:
label = text[:80].replace("\r\n", " ").replace("\r", " ").replace("\n", " ").strip()
The slice is applied to text, before the replacements. Reversing the order would fix (2) but not (1) or (3).
Reproduction
def list_products(...):
"""Lista/busca productos del catálogo de The Best (GET /articles/consulta).
Si no se pasa consulta, devuelve el catálogo completo.
"""
produces, in graph.json:
{"label": "Listabusca productos del catálogo de The Best (GET articlesconsulta). Si",
"file_type": "rationale", "source_file": "mcp-thebest/server.py",
"source_location": "L187", "_origin": "ast"}
and, in the Obsidian vault, the file:
Listabusca productos del catálogo de The Best (GET articlesconsulta). Si.md
Twelve of my fourteen rationale nodes were affected. A second real example, where the truncation cuts mid-sentence and the trailing period makes a double-dot filename:
000005 - TIENDA PIURA' - '000005'..md
Expected
A rationale label that reads as a title: whitespace collapsed, cut on a word boundary, no trailing punctuation artifact.
Suggested fix
Normalize first, then slice on a word boundary:
flat = " ".join(text.split()) # collapses \r\n, \n and indentation runs
label = textwrap.shorten(flat, width=80, placeholder="…")
textwrap.shorten never cuts mid-word and gives a visible truncation marker, so explain/query output stops implying the docstring ended there.
Bigger suggestion, separable
Even fully normalized, a docstring makes a poor node identity. Every rationale node has exactly one rationale_for edge to the thing it explains, so it could be labelled after its target — rationale: list_products(), disambiguated by source_location when one symbol carries several. That is a nicer graph (rationale nodes sort next to their subject, and the label is stable across docstring edits, so the node ID stops churning), and it makes the Obsidian vault navigable. I renamed mine that way by hand and it was a large readability win. Happy to open this as a separate enhancement issue if you'd rather keep the bug narrow.
Found on graphifyy 0.9.26 (uv tool install, Python 3.11, Linux), running
graphify <dir> --backend claude-cli. Unlike #2204 and #2205 this one is not export-specific — it is in the extractor.Summary
_extract_python_rationale/_extract_js_rationalebuild the node label by slicing the raw docstring to 80 characters before normalizing whitespace. Three consequences:... solo dev,... GET articles/consulta). Si);\n→replacement collapses anything;.at the slice boundary yields an Obsidian filename ending in..md.These labels are not export-only — they are the
labelingraph.json, so they also show up inGRAPH_REPORT.md, ingraphify queryoutput, and inexplain.Where
graphify/extract.py:1075(Python) andgraphify/extract.py:1212(JS/TS) — identical line:The slice is applied to
text, before the replacements. Reversing the order would fix (2) but not (1) or (3).Reproduction
produces, in
graph.json:{"label": "Listabusca productos del catálogo de The Best (GET articlesconsulta). Si", "file_type": "rationale", "source_file": "mcp-thebest/server.py", "source_location": "L187", "_origin": "ast"}and, in the Obsidian vault, the file:
Twelve of my fourteen rationale nodes were affected. A second real example, where the truncation cuts mid-sentence and the trailing period makes a double-dot filename:
Expected
A rationale label that reads as a title: whitespace collapsed, cut on a word boundary, no trailing punctuation artifact.
Suggested fix
Normalize first, then slice on a word boundary:
textwrap.shortennever cuts mid-word and gives a visible truncation marker, soexplain/queryoutput stops implying the docstring ended there.Bigger suggestion, separable
Even fully normalized, a docstring makes a poor node identity. Every rationale node has exactly one
rationale_foredge to the thing it explains, so it could be labelled after its target —rationale: list_products(), disambiguated bysource_locationwhen one symbol carries several. That is a nicer graph (rationale nodes sort next to their subject, and the label is stable across docstring edits, so the node ID stops churning), and it makes the Obsidian vault navigable. I renamed mine that way by hand and it was a large readability win. Happy to open this as a separate enhancement issue if you'd rather keep the bug narrow.