Skip to content

Extraction: rationale node labels are a raw 80-character slice of the docstring, producing truncated mid-word titles and filenames ending in ..md #2206

Description

@irvinolave-alt

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:

  1. the slice lands mid-word (... solo dev, ... GET articles/consulta). Si);
  2. 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;
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions