Evernote → second-brain: prompts para LLM local (Ollama)
Contexto: migrar/complementar notas e imágenes de un export de Evernote (.enex)
hacia ~/nef/second-brain (nodos con frontmatter estricto + journal). El error
clásico es dejar que el modelo “invente” slugs o metadatos: por eso el pipeline
separa extracción (scripts), triage (JSON estricto) y redacción (borradores que
un humano aprueba). El build falla con edges colgantes, así que el vocabulario
de slugs siempre va inyectado en el prompt.
Pipeline mínimo (3 pasos)
- Extraer, no promptear.
brew install evernote2md(wormski) y:evernote2md export.enex out/ -format markdown -includeoldmedia→ una nota.mdpor nota + carpetamedia/con imágenes reales. Nunca pasar base64 del .enex al modelo. - Triage con prompt A (batch de ~10 notas, salida JSON,
--format jsonde ollama). - Redacción con prompt B solo para lo aprobado. Para imágenes: prompt C con
un modelo de visión local (
ollama pull qwen2.5vl:7b).
Comando tipo:
cat batch.md | ollama run qwen3:8b --format json < prompt-A.md > triage.json
Vocabulario inyectado (regenerar antes de cada corrida):
ls content/nodes | sed 's/\.md//' | paste -sd, - → reemplazar {{SLUGS}}.
content/journal/ aún no existe; la convención propuesta es
content/journal/YYYY-MM-DD.md + content/journal/media/ (build lo ignora,
Quartz lo sincroniza como vault).
Prompt A — triage (por lote de notas)
Eres el archivista de un second-brain local (graph of study, Markdown + frontmatter).
Te llegan notas crudas exportadas de Evernote. Tu trabajo es SOLO clasificar, no reescribir.
Slugs de nodos EXISTENTES (única fuente de verdad; no inventes ninguno):
{{SLUGS}}
Relaciones permitidas: requires | implements | generalizes | applies | cites | contradicts
Kinds permitidos: concept | paper | tool | model | algorithm | person | dataset | experiment
Para cada nota (usa su id dado), responde un JSON array con objetos:
{
"id": "...",
"route": "journal" | "nuevo_nodo" | "complementa_nodo" | "borrar",
"target_slug": "solo si complementa_nodo, DEBE estar en la lista",
"propuesto": {"slug": "...", "title": "...", "kind": "...", "level_sugerido": 0-5},
"una_linea": "qué es y por qué importa, en una línea",
"imagenes": ["archivos de media/ relevantes, si los hay"],
"confianza": 0.0-1.0
}
Criterios:
- journal = texto personal/fechado (ideas del día, estados de ánimo, bitácora) → va al journal, no al grafo.
- complementa_nodo = la nota aporta cuerpo, imagen o referencia a un nodo que YA existe.
- nuevo_nodo = concepto de estudio genuino y duradero; si no estás seguro, journal.
- borrar = logística muerta (tickets, recibos, caducos).
- Nunca propongas un slug que esté en la lista de existentes.
Responde únicamente JSON válido.
Prompt B — redacción (por ítem aprobado)
Escribe un borrador Markdown para el second-brain. No agregues nada que no esté
en la fuente; conserva la voz personal de la nota original (nada de prosa corporativa).
Tipo: {{nuevo_nodo | journal | apéndice_a_nodo}}
Fuente cruda:
{{nota}}
Imágenes disponibles (usa rutas relativas  donde aporten,
máx 2, cada una con pie de una línea): {{imgs}}
Contrato de frontmatter (exacto, sin campos extra):
---
title: ...
kind: concept | paper | tool | model | algorithm | person | dataset | experiment
status: unseen | learning | applying | mastered
level: 0-5
hours: 0
edges: [{ to: "slug", rel: "requires|implements|generalizes|applies|cites|contradicts" }]
tags: [...]
open_questions: [...]
---
{{para journal, el frontmatter es solo: date, tags}}
Reglas:
- Cada edge "to" DEBE existir en: {{SLUGS}}. Si la dependencia natural no existe,
ponla en open_questions como "nodo candidato: X", no como edge.
- El primer párrafo del cuerpo = una línea concreta de qué es y por qué importa.
- La duda se conserva en open_questions; nunca se borra la incertidumbre.
- Devuelve solo el archivo, listo para git.
Prompt C — caption de imágenes (visión local, qwen2.5vl:7b)
Describe esta imagen en UNA línea factual (qué muestra, no qué sugiere).
Luego, de estos slugs del grafo de estudio, devuelve a lo sumo 2 a los que
ilustra; si ninguno encaja, devuelve [].
Slugs: {{SLUGS}}
Formato: {"caption": "...", "nodos": ["..."]}
por qué así
- El modelo solo toca lo semántico; rutas, slugs y media los deciden scripts.
--format json+ lista de slugs hace casi imposible el fallo de integridad referencial (el que rompenpm run build).- Nada se escribe directo a
content/: todo cae a_stubs/evernote/y se adopta a mano, igual quepaper-scan/book-scan. - Human-in-the-loop por lote pequeño: 20 notas revisadas valen más que 200 autoimportadas.