Conhecimento & notasIA & contexto

Graphify sem IA: transformar um wiki em grafo navegável com zero token

Mil notas ligadas e nenhuma visão de conjunto: não dá para ver quais ideias são centrais, quais estão isoladas, nem como dois conceitos distantes se encontram. Quando os links de um wiki já vêm explicados nas próprias notas, um script Python de 160 linhas basta para gerar o grafo completo, explorado pelo Graphify em 0,2 segundo e sem gastar um único token.

Info

Escrito originalmente em francês. Traduzido por IA — o sentido foi preservado, não a prosa.

O Graphify é uma ferramenta feita para mapear código pagando uma IA para ler cada arquivo. Eu a adaptei para mapear meu wiki de conhecimento, sem IA e sem chave de API. Um único arquivo Python de 160 linhas, 0,2 segundo de cálculo, e mil notas viram um grafo que você pode explorar, consultar e percorrer.

Mil notas sobre marketing. Notas atômicas, definições de glossário, mapas de leitura por livro. Todas ligadas entre si, e cada link explica por que existe.

Mesmo assim, era impossível enxergar a forma do conjunto. Impossível responder de um jeito simples a perguntas como: o que liga a reciprocidade de Cialdini ao churn de um SaaS? Quais notas estão no coração do tema, e quais estão isoladas?

A visão de grafo do Obsidian mostra um novelo de lã. Bonito, mas mudo.

Este artigo conta como consegui um grafo utilizável desse wiki usando o Graphify, uma ferramenta pensada para código, para outro fim — e o que esse grafo me ensinou sobre a minha própria base.

Já contei por que construí uma base de conhecimento mantida por uma IA. O princípio: eu entrego livros e documentos ao Claude, que extrai deles notas atômicas, entradas de glossário e outros tipos de ficha, e depois liga tudo entre si.

Uma regra conta mais que todas as outras para o que vem a seguir: um link nunca deve ser decorativo. Cada link vem seguido de uma frase curta que explica a relação.

- [[GLO00614-marche-minimal-viable]] — o MVMarket é a estratégia
  para atravessar o chasm, não o chasm em si.
- [[GLO00619-technology-adoption-life-cycle]] — quadro em que
  o chasm se insere.

Em outras palavras, as relações entre as ideias já estão escritas, preto no branco, nas notas. É esse detalhe que torna todo o resto possível.

O que é o Graphify?

O Graphify transforma uma pasta de código ou de documentos em grafo de conhecimento, que depois você pode consultar a partir de um assistente como o Claude Code.

Ele funciona em duas fases.

  1. A construção. O Graphify pede a uma IA que leia cada arquivo e deduza dele os elementos importantes e as relações entre eles. É a etapa cara: consome tokens, e o resultado varia de uma execução para outra.
  2. A exploração. Com o grafo construído, o Graphify o divide em grupos de nós próximos, gera um mapa interativo e um relatório, e responde a perguntas: quais são as conexões de tal elemento, que caminho liga A a B.

A ideia do uso alternativo

Num repositório de código, a etapa de construção faz sentido: ninguém escreveu preto no branco que tal função chama tal outra, é preciso deduzir.

No meu wiki, é o contrário. A IA não teria nada a adivinhar. As relações já estão lá, escritas à mão ou validadas no momento da ingestão. Pagar uma IA para redescobri-las seria caro, lento e menos fiel do que o que já está escrito.

Então troquei a construção por um pequeno script caseiro e mantive todo o resto do Graphify.

Notas do wiki (.md)
      │
      │  script caseiro: lê os links [[...]] e a explicação de cada um
      ▼
graph.json  (formato padrão NetworkX)
      │
      │  Graphify: grupos, mapa, relatório, perguntas
      ▼
graph.html · GRAPH_REPORT.md · explain · path · query

O que torna esse uso alternativo possível: o Graphify aceita qualquer grafo no formato padrão da biblioteca NetworkX. Ele não verifica de onde vem esse grafo. Basta entregá-lo.

Por baixo do capô: um único arquivo

O script faz três coisas.

1. Listar as notas. Cada arquivo Markdown vira um nó. O identificador é o nome do arquivo, e o tipo é deduzido do prefixo: ATO para uma nota atômica, GLO para o glossário, MOC para um mapa de leitura etc.

2. Encontrar os links. Uma expressão regular encontra cada [[ID-slug]] no texto. Se a nota de destino existe, um link é criado.

3. Dar nome à relação. O texto escrito depois do travessão que segue o link vira o nome da relação. É isso que permite ao Graphify mostrar "o MVMarket é a estratégia para atravessar o chasm" em vez de um simples "link".

for line in text.splitlines():
    matches = LINK_RE.findall(line)
    relation_match = re.search(r"\]\]\s*[—-]+\s*(.+)", line)
    relation = relation_match.group(1).strip()[:80] if relation_match else "links"

Nenhuma biblioteca para instalar: o script usa só módulos que já vêm com o Python. Aqui está o arquivo completo.

"""
Build graph.json from Obsidian [[links]] in wiki notes — no LLM needed.
Output format: NetworkX node_link_graph (compatible with graphify path/explain/query).

Usage:
    .venv/bin/python scripts/build_graph.py [--theme maintenance] [--output _GRAPHITY/]
"""

import argparse
import json
import re
import uuid
from pathlib import Path

WIKI_ROOT = Path(__file__).resolve().parent.parent.parent.parent / "wiki"
LINK_RE = re.compile(r"\[\[([A-Z]{2,3}\d{5}-[^\]\|]+?)(?:\|[^\]]*)?\]\]")
FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---", re.DOTALL)

TYPE_MAP = {
    "ATO": "atomic_note",
    "GLO": "glossary_term",
    "NOR": "norm_reference",
    "EXN": "normative_requirement",
    "PRO": "business_process",
    "OBJ": "domain_object",
    "BRL": "business_rule",
    "REG": "regulatory_text",
    "HAB": "habilitation",
    "IND": "indicator",
    "MOC": "moc",
    "SRC": "source_record",
}


def parse_frontmatter(text: str) -> dict:
    m = FRONTMATTER_RE.match(text)
    if not m:
        return {}
    result = {}
    for line in m.group(1).splitlines():
        if ":" in line:
            k, _, v = line.partition(":")
            result[k.strip()] = v.strip().strip('"')
    return result


def slug_to_id(slug: str) -> str:
    """Extract the ID prefix (e.g. 'ATO00001') from a slug like 'ATO00001-some-title'."""
    parts = slug.split("-", 1)
    return parts[0].upper()


def build_graph(theme: str, output_dir: Path) -> dict:
    notes_root = WIKI_ROOT / "themes" / theme / "notes"
    if not notes_root.exists():
        raise SystemExit(f"Notes directory not found: {notes_root}")

    nodes = {}   # node_id -> node dict
    edges = []   # list of {source, target, relation}

    md_files = list(notes_root.rglob("*.md"))
    md_files = [f for f in md_files if f.name != "README.md"]

    # Index: slug -> node_id (file stem without extension)
    slug_index = {}
    for f in md_files:
        stem = f.stem  # e.g. ATO00001-some-title
        slug_index[stem] = stem
        # Also index by bare ID (ATO00001) for robustness
        bare_id = stem.split("-")[0]
        slug_index.setdefault(bare_id, stem)

    print(f"[build_graph] {len(md_files)} notes found in {notes_root}")

    for f in md_files:
        text = f.read_text(encoding="utf-8")
        fm = parse_frontmatter(text)

        node_id = f.stem
        prefix = node_id[:3].upper()
        node_type = TYPE_MAP.get(prefix, "note")
        title = fm.get("title") or fm.get("term") or fm.get("name") or node_id
        subdir = f.parent.name  # e.g. "atomiques", "glossaire"

        nodes[node_id] = {
            "id": node_id,
            # label = slug so graphify path/explain resolve by ID (e.g. "ATO00001-...")
            "label": node_id,
            "title": title,
            # norm_label includes slug tokens for fuzzy matching
            "norm_label": node_id.lower().replace("-", " "),
            "type": node_type,
            "kind": node_type,
            "source_file": str(f.relative_to(WIKI_ROOT.parent)),
            "subdir": subdir,
            "status": fm.get("status", "draft"),
            "uid": fm.get("uid") or str(uuid.uuid4()),
            "wiki_id": fm.get("id", ""),
        }

    # Second pass: extract links
    seen_edges = set()
    for f in md_files:
        text = f.read_text(encoding="utf-8")
        source_id = f.stem

        # Extract label from link context line (text after — )
        for line in text.splitlines():
            matches = LINK_RE.findall(line)
            # Extract relation label from "— relation text" after the link
            relation_match = re.search(r"\]\]\s*[—-]+\s*(.+)", line)
            relation = relation_match.group(1).strip()[:80] if relation_match else "links"

            for slug in matches:
                # Resolve target
                target_id = slug_index.get(slug) or slug_index.get(slug.split("-")[0])
                if not target_id or target_id == source_id:
                    continue
                if target_id not in nodes:
                    continue
                key = (source_id, target_id)
                if key in seen_edges:
                    continue
                seen_edges.add(key)
                edges.append({
                    "source": source_id,
                    "target": target_id,
                    "relation": relation,
                    "key": 0,
                })

    node_list = list(nodes.values())

    graph_data = {
        "directed": True,
        "multigraph": False,
        "graph": {"theme": theme},
        "nodes": node_list,
        "links": edges,
    }

    output_dir.mkdir(parents=True, exist_ok=True)
    graph_path = output_dir / "graph.json"
    graph_path.write_text(json.dumps(graph_data, ensure_ascii=False, indent=2), encoding="utf-8")

    print(f"[build_graph] {len(node_list)} nodes, {len(edges)} edges")
    print(f"[build_graph] graph.json → {graph_path}")
    return graph_data


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--theme", default="maintenance")
    parser.add_argument("--output", default="_GRAPHITY/")
    args = parser.parse_args()

    build_graph(args.theme, Path(args.output) / args.theme)

O resultado no tema marketing

O tema marketing do meu wiki é alimentado por uns vinte livros: Dunford, Seth Godin, Cialdini, Moore, Ries e Trout, Nagle, Byron Sharp, Rob Fitzpatrick…

Medida Valor
Notas 1 013
Links 6 442
Grupos de notas detectados 22
Tempo de construção do grafo 0,2 segundo
Tokens de IA consumidos 0

O mapa e seus grupos de notas

Mapa interativo do tema marketing gerado pelo Graphify

O Graphify divide o grafo em grupos de notas mais ligadas entre si do que com o resto. Alguns grupos encontrados no tema marketing:

  • Um grupo de 53 notas em torno do Mom Test: as conversas com clientes, os elogios que não valem nada, os fatos passados em vez das promessas.
  • Um grupo de 52 notas sobre influência: os seis princípios de Cialdini, a reciprocidade, o contraste perceptivo, o "porque" que dispara a obediência.
  • Um grupo de 46 notas sobre o chasm: o abismo entre os early adopters e a maioria pragmática, a estratégia do pino de boliche.
  • Um grupo de 26 notas sobre customer success: o churn como espiral mortal, a diferença entre customer success e suporte.

O relatório também identifica as notas mais conectadas. As dez primeiras são todas mapas de leitura: o de Obviously Awesome, da Dunford, fica em primeiro com 180 links, à frente de This is Marketing, do Seth Godin, com 156.

Por fim, ele aponta as notas isoladas. Uma só em 1 013: uma nota sobre persona de marketing, que nada liga ao resto. É um buraco no wiki, encontrado de graça.

As conexões de uma nota

Pedimos ao Graphify que explique a nota de glossário sobre o chasm:

graphify explain "GLO00618-chasm"

Ele responde com as 30 conexões da nota, nas duas direções, e a relação de cada uma. Um trecho:

Direção Nota ligada Relação escrita na nota
← Mapa de leitura de Crossing the Chasm o abismo central do modelo
→ Ciclo de adoção de tecnologia quadro em que o chasm se insere
→ Os early adopters buscam uma vantagem radical, a maioria uma melhoria gradual explica a causa psicográfica do chasm
← Produto completo (whole product) o chasm é em parte causado pela ausência de produto completo
← Bowling alley começa depois da travessia do chasm
→ O abismo de Moore separa os early adopters do mercado de massa mesma ideia, vinda de Seth Godin citando Moore

→: o chasm cita esta nota. ←: esta nota cita o chasm.

A última linha é a mais interessante. O chasm vem de Geoffrey Moore, mas Seth Godin também fala dele em outro livro. O grafo mostra que as duas fontes se encontram na mesma ideia.

O caminho entre duas ideias distantes

O que liga a regra da reciprocidade de Cialdini, um princípio de psicologia social, ao churn num negócio de assinatura?

graphify path \
  "ATO01269-la-regle-de-reciprocite..." \
  "ATO01404-dans-un-business-de-revenus-recurrents-le-churn..."

O Graphify encontra um caminho de 8 etapas:

  1. A regra da reciprocidade é universal e sustenta a cooperação humana
  2. → o mapa de leitura de Influence (Cialdini)
  3. → O compromisso inicial muda a autoimagem e produz uma coerência automática
  4. → O risco alto gera confiança; baixar o preço a diminui
  5. → próximo de: um preço alto sinaliza qualidade e sustenta a confiança → O efeito preço-qualidade faz um preço alto ser percebido como sinal de qualidade
  6. → o mapa de leitura de The Strategy and Tactics of Pricing (Nagle)
  7. → O custo de troca reduz a sensibilidade ao preço → converge: os switching costs são uma alavanca importante de retenção → A retenção é o multiplicador de crescimento mais poderoso
  8. → convergência com a tese de Customer Success: o churn destrói o crescimento → Num negócio recorrente, o churn é uma espiral mortal

O caminho conta uma história: da psicologia do compromisso ao preço como sinal de confiança, e depois do custo de troca à retenção. Ninguém escreveu esse raciocínio de uma vez. Ele surge dos links criados nota após nota, livro após livro.

O que isso traz a mais que a visão de grafo do Obsidian

A visão de grafo do Obsidian desenha os links. O Graphify, além disso:

  • detecta os grupos de notas automaticamente;
  • dá nome às relações, retomando a explicação escrita na nota;
  • encontra o caminho mais curto entre duas notas;
  • produz um relatório: notas centrais, notas isoladas, conexões inesperadas;
  • roda na linha de comando, então o próprio Claude pode consultá-lo enquanto trabalha no wiki.

Os limites

A busca por pergunta só lê os títulos. O Graphify também tem um comando de busca livre. Ele procura as palavras da pergunta no nome das notas, não no conteúdo, e depois amplia para as notas vizinhas. Se a pergunta não usa as palavras do título, ele não encontra nada.

Os grupos seguem os livros mais do que os conceitos. O posicionamento, por exemplo, aparece espalhado em cinco grupos, basicamente conforme o livro de origem: Dunford de um lado, Ries e Trout do outro. Os mapas de leitura por livro concentram tantos links que estruturam o grafo inteiro. O mesmo efeito aparece nos caminhos: o que vai do chasm à disposição a pagar passa por três mapas de leitura, o que é verdade, mas ensina pouco.

Os grupos não têm nome. O Graphify pode nomeá-los com uma IA, mas isso traria de volta o custo que eu queria evitar. Eles se chamam, portanto, "Community 0", "Community 1"… e cabe a mim interpretá-los.

O Graphify continua sendo uma ferramenta para código. A cada construção, ele emite 12 884 avisos: os links não têm "arquivo de origem", uma noção que só faz sentido para código. Isso não traz consequências, mas lembra que estamos usando a ferramenta fora do uso previsto.

O grafo só vale o que valem os links. Um link sem explicação vira uma relação "links" muda. Uma nota mal ligada continua isolada. O grafo não corrige nada, ele mostra.

Como reproduzir em casa

Você precisa de um conjunto de notas em Markdown ligadas por links [[...]]:

  • ou um cofre do Obsidian que você mesmo vai ligando;
  • ou um wiki mantido por uma IA, como o descrito neste artigo.

O script acima pressupõe identificadores do tipo ATO00001-slug e uma árvore de pastas por tema. Adapte a expressão regular dos links e a pasta das notas à sua própria organização.

Depois, três comandos:

pip install graphifyy

python build_graph.py --theme marketing --output _GRAPHITY/

graphify cluster-only _GRAPHITY/marketing/ --no-label \
  --graph _GRAPHITY/marketing/graph.json

O mapa interativo fica então em _GRAPHITY/marketing/graphify-out/graph.html, e o relatório em GRAPH_REPORT.md, ao lado. Os comandos explain e path funcionam direto sobre o arquivo graph.json.


A IA não tinha nada a adivinhar. Bastava ler o que já tínhamos escrito.