Motor reutilizable para archivar, de forma buscable y clasificada, las
respuestas tipo tutorial/procedimiento que genero (Claude) cuando
explico cómo usar una herramienta, un comando o resolver una tarea
concreta. Cada respuesta queda como una nota Markdown con frontmatter
en vault_out/, visualizable y buscable desde un panel Streamlit, y
alcanzable también por Telegram para poder preguntar (y archivar la
respuesta) estés donde estés, mientras el proceso esté corriendo en el
ordenador.
Este repo trae contenido de ejemplo en vault_out/ (notas sobre
git, docker y Streamlit) para que el panel y la CLI tengan algo que
mostrar nada más clonarlo — no una carpeta vacía. Sustitúyelo por tu
propio conocimiento sin problema.
Arquitectura hexagonal (puertos y adaptadores), inspirada en los
proyectos hermanos orquestador y heruka-life. El dominio (qué es
una nota, cómo se clasifica, cómo se reorganiza el árbol) no sabe nada
de Telegram, de Anthropic ni de Streamlit — esos son adaptadores
intercambiables.
Estado actual: las siete capas están implementadas y probadas —
domain/(entidades, puertos, casos de uso, excepciones),config/(schema + loader +conocimiento.yaml+paths.py),application/(prompts),adapters/out/(RepositorioNotasFilesystem,GeneradorRespuestasAnthropic/ClasificadorNotasAnthropic/NormalizadorVaultInAnthropic, y sus equivalentes*Mock),adapters/in_/(cli.py— incluyenormalizar, la versión automatizable de/normalizar-vault-in, sin Claude Code de por medio — ytelegram_bot.py) ymain.py(composition root). 113 tests enpytest(ninguno necesita red, API key ni un bot de Telegram real — los adaptadores de salida se prueban mockeando el SDK correspondiente, mismo criterio queorquestador) yruff check .limpio. No probado todavía: una conversación real con el bot de Telegram (hace falta unTELEGRAM_BOT_TOKENreal) ni el panel Streamlit abierto en un navegador.requirements.txt/requirements-dev.txtreflejan las dependencias reales ya usadas, no una lista provisional.
- Standalone (clonas solo este repo): usa su propio
vault_in//vault_out//.env, tal cual están aquí — con el contenido de ejemplo devault_out/de partida. - Embebido como submódulo git de otro repo que guarda ahí su vault
real (privado, con tu propio conocimiento). Ese repo host exporta la
variable
CONOCIMIENTO_HOMEapuntando a su propia raíz antes de lanzar cualquier script de aquí (verconfig/paths.py); a partir de ahí,.env,vault_in/yvault_out/se resuelven contra esa ruta en vez de contra la de este repo. SinCONOCIMIENTO_HOMEdefinida, todo cae de vuelta al modo standalone (punto 1).
domain/ → entidades (Nota), puertos (interfaces) y casos de
uso: guardar una nota, clasificarla, reorganizar
el árbol de vault_out, buscar. Sin dependencias
externas — es lo único que de verdad define "qué
es" este proyecto.
application/ → prompts.py: el texto de los prompts (estilo
tutorial, reglas de clasificación), separado de
domain/use_cases.py (que ya coordina el flujo
pregunta → respuesta → nota) y de adapters/out/
(que decide cómo llamar al LLM). No hay una
capa "orquestador" aparte: con una sola llamada
al LLM por caso de uso (sin bucle de tools),
domain/use_cases.py ya es esa coordinación.
adapters/in_/ → entradas: bot de Telegram (preguntar desde
cualquier sitio, o capturar una conversación
entera con /iniciar-/finalizar — ver "Bot de
Telegram" más abajo) y CLI (comandos de terminal:
guardar, buscar, reorganizar — ver más abajo).
adapters/out/ → salidas: LLM (Anthropic, o un mock para
desarrollar sin gastar tokens), y el repositorio
de notas que lee/escribe los .md de vault_out.
config/ → configuración declarativa (YAML) + loader: reglas
de clasificación, límites del árbol de carpetas
(ver "Crecimiento del árbol" más abajo); y
paths.py, que resuelve dónde viven .env/vault_in/
vault_out (ver "Dos formas de usarlo" arriba) sin
depender del directorio de trabajo del proceso.
streamlit_app/ → panel con dos pestañas: Chat (pregunta -> LLM ->
nota nueva en vault_out, reusando
ResponderYArchivar igual que el bot de Telegram,
fuente=streamlit; selector Anthropic/OpenAI por
sesión — a diferencia del bot de Telegram y de
`reorganizar`, que siguen fijos al proveedor de
PROVEEDOR_LLM) y Buscar (visualiza/busca las
notas ya archivadas y permite eliminarlas una a
una, con confirmación en dos pasos — para quitar
duplicados, p. ej. una misma pregunta archivada
dos veces). Proyecto hermano ligero: no
reimplementa generación ni búsqueda, solo las
reusa. La pestaña Chat necesita ANTHROPIC_API_KEY
y/o OPENAI_API_KEY según el proveedor elegido (o
PROVEEDOR_LLM=mock para probar sin ninguna); la
pestaña Buscar no necesita ninguna.
vault_in/ → bandeja de entrada para .md pegados a mano, o
volcados por el bot de Telegram (ver "Bot de
Telegram" más abajo): sin frontmatter, o con
frontmatter incompleto. Dos formas de normalizarla
a vault_out/ (plana, categoria: null, igual que
ResponderYArchivar): `cli.py normalizar`
(automatizable, sin Claude Code de por medio — ver
"Comandos básicos") o `/normalizar-vault-in` desde
una sesión de Claude Code (más lento, pero más
cuidadoso: puede razonar caso a caso). Viene vacía
en este repo (ver vault_in/README.md).
vault_out/ → las notas ya redactadas, el conocimiento en sí.
Empieza plana; el comando `reorganizar` es el
único que crea subcarpetas (ver más abajo). Trae
contenido de ejemplo en este repo (ver arriba).
main.py → composition root: conecta las piezas y arranca el
bot de Telegram (el "proceso funcionando en el
ordenador").
scripts/ → utilidades de desarrollo (arrancar/parar todo a
la vez, verificar entorno...).
tests/ → carpetas numeradas siguiendo el orden de
dependencia de la arquitectura (dominio → config
→ aplicación → adaptadores de salida →
adaptadores de entrada), mismo patrón que
orquestador/heruka-life.
Frontmatter YAML + cuerpo Markdown, mismo estilo que los vaults de Obsidian de los proyectos hermanos:
---
titulo: "Cómo revertir el último commit sin perder los cambios"
tags: [git, cli]
categoria: null # ruta relativa dentro de vault_out; null hasta que "reorganizar" la asigna
fuente: telegram # telegram | cli | claude-code | streamlit | manual
pregunta_origen: "¿cómo deshago el último commit pero me quedo con los cambios en el working tree?"
resumen: "git reset --soft HEAD~1 deshace el commit y deja los cambios staged."
fecha: 2026-08-15
---
# Cómo revertir el último commit sin perder los cambios
...categoria es lo que decide en qué subcarpeta vive la nota; el resto
del frontmatter (tags, resumen) alimenta la búsqueda del panel
Streamlit aunque la nota no se haya reorganizado todavía.
Reglas que respeta PlanificarReorganizacion (domain/use_cases.py)
para que el árbol crezca de forma armoniosa:
- Una nota vive en exactamente una carpeta. Nada de que un mismo tema encaje en dos ramas a la vez — si una nota duda entre dos sitios, es señal de que la taxonomía necesita ajustarse, no de duplicar la nota.
- No se crea una subcarpeta hasta que hay contenido suficiente que
la justifique (umbral configurable en
config/, por defecto unas 5 notas afines —config/conocimiento.yamlde este repo lo baja a 2 solo para que elvault_out/de ejemplo sea reproducible conreorganizar --aplicar; un vault real debería mantener el 5). Evita ramas con uno o dos archivos. - Se prioriza ampliar/reusar una carpeta existente sobre crear una nueva, si el tema encaja razonablemente.
- Profundidad máxima limitada (por defecto 2 niveles) para que el árbol siga siendo hojeable a mano, aunque la búsqueda del panel no dependa de ello.
- El comando corre en modo simulación por defecto: muestra el plan
de movimientos (qué nota iría a qué carpeta) antes de tocar nada;
hace falta
--aplicarpara ejecutarlo de verdad.
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Rellena ANTHROPIC_API_KEY (o pon PROVEEDOR_LLM=mock para probar sin
# gastar tokens ni tener API key) y, si quieres el bot, TELEGRAM_BOT_TOKEN
# (créalo hablando con @BotFather) y TELEGRAM_CHAT_ID_PERMITIDO.# Arrancar el proceso principal: bot de Telegram escuchando preguntas.
# Cada pregunta recibida se responde y la respuesta se guarda como
# nota nueva en vault_out/, plana, a la espera de reorganización.
python main.py
# Guardar a mano una nota ya redactada (p. ej. cuando yo, en una sesión
# de Claude Code, redacto directamente el markdown sin pasar por el
# adaptador LLM) — añade el frontmatter que falte y la deja en vault_out/.
python -m adapters.in_.cli guardar --archivo respuesta.md
cat respuesta.md | python -m adapters.in_.cli guardar --stdin
# Buscar notas por texto y/o tags (lo mismo que usa el panel Streamlit).
python -m adapters.in_.cli buscar "docker compose"
python -m adapters.in_.cli buscar --tag git --tag cli
# Reorganizar vault_out/ en subcarpetas siguiendo las reglas de arriba.
# Por defecto solo muestra el plan (dry-run).
python -m adapters.in_.cli reorganizar
python -m adapters.in_.cli reorganizar --aplicar
# Normalizar vault_in/: cada .md pendiente -> una o varias notas en
# vault_out/ (borra el original de vault_in/ solo con --aplicar). Es
# lo que corre el timer systemd cada 10 min en producción (ver
# despliegue), sin depender de Claude Code.
python -m adapters.in_.cli normalizar
python -m adapters.in_.cli normalizar --aplicar
# Panel de visualización y búsqueda.
streamlit run streamlit_app/app.py
# Tests y lint.
pip install -r requirements-dev.txt
pytest
pytest tests/01_domain # una sola capa
ruff check .Dos formas de usarlo (adapters/in_/telegram_bot.py), sin mezclarse
entre sí:
- Pregunta suelta: cualquier mensaje de texto normal se trata como
pregunta → el LLM genera una respuesta tipo tutorial → se archiva
directa en
vault_out/(fuente: telegram), igual que siempre. - Conversación capturada:
/iniciarempieza a acumular en memoria (sin llamar al LLM, sin responder más que un✓por mensaje) todo lo que escribas a continuación;/finalizarvuelca esos mensajes, en orden y separados por párrafo, como un único.mdnuevo envault_in/(telegram-AAAA-MM-DD-HHMMSS-ffffff.md, sin frontmatter) — pendiente de que lo recoja/normalizar-vault-in. Útil para pegar/dictar por partes una conversación larga de otra IA, o varias ideas sueltas, sin que cada mensaje individual dispare una respuesta y una nota aparte.
/iniciar dos veces seguidas no reinicia ni pierde lo ya acumulado; ni
esa conversación en curso ni sus mensajes se persisten a disco hasta
/finalizar — si el proceso se reinicia a media captura, se pierde a
propósito (mejor eso que volcar a vault_in/ una conversación a
medias sin que nadie lo haya pedido).
Ejemplo: pasar el repositorio de notas de ficheros locales a otra cosa (p. ej. sincronizado con un Obsidian vault real, o una base de datos).
- Crea
adapters/out/repositorio_notas_<x>.pyimplementando el mismo puerto dedomain/ports.py(RepositorioNotas). - En
main.py, cambia la instanciación enconstruir_sistema(). domain/yapplication/no cambian.
/guardar-respuesta [título]— archiva la última respuesta sustancial de la conversación como nota nueva envault_out/. Sigue siendo útil con los adaptadores ya implementados: es más rápido que redactar el fichero y llamar acli.py guardara mano desde una sesión de Claude Code./buscar-notas [texto o #tag]— busca en las notas ya archivadas./reorganizar-vault [--aplicar]— calcula (y, con--aplicar, ejecuta) el plan de reorganización en subcarpetas. Hace lo mismo quecli.py reorganizarcon unClasificadorNotasreal; útil sobre todo si no hayANTHROPIC_API_KEYa mano en ese momento./normalizar-vault-in [--aplicar]— detecta los .md de vault_in/ con frontmatter ausente o incompleto, infiere lo que falta (título — optimizado, no copiado literal; resumen; tags; unapregunta_origenrazonable cuando el cuerpo es la respuesta directa de una IA sin pregunta escrita — nuncacategoria) y los traslada ya normalizados a vault_out/. También detecta cuerpos aplanados (texto pegado desde una conversación de IA a un canal de Telegram, sin saltos de línea y con los/comandoconvertidos en enlacestg://bot_command), los reformatea — párrafos, listas, diagramas ASCII — y fusiona los fragmentos en los que la IA repite una respuesta anterior ya retocada con un dato nuevo del usuario, antes de archivarlos. Dry-run por defecto, igual que/reorganizar-vault./siguiente-pieza [pieza]— continúa implementando/refinando este motor capa a capa, siguiendo el orden y el estilo ya establecidos. Comando de desarrollo del propioconocimiento_app, no de uso diario del vault — no lo expongas si embebes este repo como submódulo de otro pensado solo para archivar conocimiento./ayuda— lista estos comandos desde su frontmatter.
- Probar el bot de Telegram con un
TELEGRAM_BOT_TOKENreal (hasta ahora solo probado con el SDK mockeado) y el panel Streamlit abierto en un navegador. - Si el clasificador de tags/carpeta debería poder usar un prompt/rol
distinto al de generación en vez del mismo modelo con dos prompts
separados (ahora mismo: mismo modelo,
application/prompts.pyya separa los dos textos).
MIT — ver LICENSE.