Digest de Citas Bibliográficas (citation-digest)
Introduce un artículo y obtén «quién lo cita + qué problema resuelve cada cita + agrupado por categorías», generando un Markdown o una página de Notion.
El pipeline consta de cuatro pasos, ya empaquetados en scripts listos para usar. No reinventes la rueda:
s2_citations.py → citations.json Quién lo cita (incluye número de citas/fecha/DOI/arXiv)
kimi_q1.py → kimi.json Texto original de Kimi Q1 para cada cita (bloques estructurados)
(tú decides) → classify.json Resultado de la clasificación
build_content.py → content.json + digest.md
notion_publish.py→ Página de Notion
0. Requisitos previos
- Clave S2: prioridad
$SEMANTIC_SCHOLAR_API_KEY, luego--key, por último~/.env/semantic-scholar. Sin clave también funciona, pero recibirás muchos errores 429; el script reintenta automáticamente (8 veces × 12s). - Token de Notion:
~/.notion-tokeno$NOTION_TOKEN; la página de destino debe compartirse previamente con esta integración, de lo contrario la búsqueda fallará.
1. Obtener citas
python3 scripts/s2_citations.py --title "MP-GUI: Modality Perception with MLLMs for GUI Understanding" --out citations.json
# También puedes usar --arxiv 2503.14021 / --paper-id 0e931ba8...
El script incorpora tres reglas que seguro pisarás:
- Deduplicación: S2 suele devolver el mismo artículo arXiv dos veces (una como
A Survey on ...y otra comoA Summary on ..., una con ArXiv, la otra solo con DOI10.48550/arXiv.xxxx). Se deduplica por arXiv id → DOI → título, conservando la entrada con más citas. - Más de 20 artículos: solo los dos top10:
--max-total 20 --top 10conserva la unión del «top10 por número de citas» ∪ «top10 por fecha». Activado por defecto, no lo filtres tú. - Coincidencia de títulos: la interfaz de búsqueda selecciona el resultado con mayor solapamiento de tokens; si la puntuación es <0.55, se muestran los candidatos en stderr para que el usuario confirme si es el mismo artículo.
Primero revisa citations.json → source.citationCount, que es el número de citas a reportar.
2. Obtener resúmenes Kimi
python3 scripts/kimi_q1.py --in citations.json --out kimi.json --sleep 10 --resume
- Llama a
POST https://papers.cool/arxiv/kimi?paper=<arxiv_id>(es la implementación subyacente del MCP papers-cool local). Es obligatorio incluir las cabecerasUser-Agent/Origin/Referer, de lo contrario la solicitud será bloqueada. - Devuelve un FAQ en HTML. Solo se extrae la Q1 «¿Qué problema intenta resolver este artículo?», convertida a bloques estructurados:
###→ heading_3,-→ bulleted_list_item,**negrita**→ negrita. El texto se copia tal cual, sin comprimir ni reescribir. - papers.cool no solo recoge arXiv, también tiene una sección de venues:
/venue/<CODE>.<YEAR>, con CODE ∈ AAAI/ACL/COLM/COLT/CoRL/CVPR/ECCV/EMNLP/ICCV/ICLR/ICML/IJCAI/INTERSPEECH/IWSLT/MICCAI/MLSYS/NAACL/NDSS/NeurIPS/OSDI/UAI. Para artículos sin arXiv id, el script usa automáticamente esta ruta de respaldo:/venue/<CODE>.<YEAR>?show=5000-> página de listado → coincide el título conid="title-<pid>"para obtener el paper id (con forma49@2026@IJCAI, el número secuencial de IJCAI es la parte final del DOI), luegoPOST /venue/kimi?paper=<pid>, que devuelve el mismo FAQ HTML. La página de listado del venue solo muestra 25 elementos por defecto; es obligatorio incluir?show=. - Si ambas rutas fallan (ej. IEEE MIPR, AIoTC: artículos no indexados por papers.cool), se marcan como
missingy se reemplazan por el resumen o el título. Debe indicarse claramente en la página que «No es un resumen Kimi» —build_content.py/notion_publish.pyya añade automáticamente una marca ⚠️ cuandosource != "kimi", y además se debe explicar el motivo enclassify.jsondentro denotes. --sleep 10es más estable que 2; para artículos sin caché, Kimi debe calcularlos en tiempo real, cada uno puede tardar 60-90s. Cada artículo se escribe en--outinmediatamente después de obtenerse. Si la ejecución se interrumpe, se puede reanudar con--resume.- ⚠️ No uses
pkill -f kimi_q1.py: varias sesiones de Claude ejecutando este skill se matarían entre sí (hemos perdido 26 artículos ya obtenidos pero no escritos en disco). Si necesitas ejecutarlo en segundo plano, primero copia el script:cp scripts/kimi_q1.py <scratchpad>/pcfetch_<id_sesion>.pyy usa un nombre de proceso independiente.
3. Clasificación (la haces tú, no el script)
Escribe classify.json:
{
"categories": [
{"name": "🧩 Innovación de escenario / problema", "desc": "Nueva tarea, nuevo benchmark, nueva métrica de evaluación, o redefinición del problema",
"papers": ["2503.14021", "10.1109/CVPR52734.2025.02766", "Título exacto"]},
{"name": "🔧 Mejora técnica", "desc": "Problema conocido, contribución en método/proceso de entrenamiento/sistema/arquitectura",
"papers": ["..."]},
{"name": "📚 Solo revisión", "desc": "No propone método ni tarea nueva; su valor está en la síntesis y las preguntas abiertas",
"papers": ["..."]}
],
"notes": {"2504.20464": "Este artículo tiene dos registros duplicados en S2; se han fusionado"}
}
En papers puedes escribir arXiv id / DOI / título (coincidencia exacta o parcial). Los artículos no cubiertos se asignan automáticamente a «❓ Sin clasificar»; no se pierden en silencio.
Criterios (si el usuario no indica otros, usa estos):
- Innovación de escenario / problema: propone una nueva tarea, un nuevo benchmark, una nueva dimensión de evaluación (ej. «no basta con el estado final, hay que ver el proceso»), o redefine un fenómeno como un problema (ej. «la herramienta falla pero no se usa»).
- Mejora técnica: el problema es ampliamente aceptado; la contribución está en el método, el proceso de entrenamiento, la implementación del sistema o la arquitectura.
- Solo revisión: Survey / Review / Artículo de opinión que no presenta un método experimental nuevo.
- Casos límite (¿el benchmark es innovación de escenario o categoría aparte? ¿los artículos analíticos dónde van?) – primero clasifica según los criterios anteriores, y luego en la respuesta explica brevemente el motivo de la decisión; no clasifiques en silencio.
Etiquetas en el cuerpo: para artículos normales usa «Problema que intenta resolver»; para revisiones usa «Visión central» – pero como el texto original de Q1 se copia tal cual, normalmente no se añaden etiquetas; deja que el texto hable por sí mismo.
4. Ensamblaje y generación
python3 scripts/build_content.py --citations citations.json --kimi kimi.json \
--classify classify.json --out content.json --md digest.md
5. Publicar en Notion
python3 scripts/notion_publish.py --content content.json --page-title "lectura de artículos sobre tema X"
# o --page-id xxx; para sobrescribir contenido existente añade --replace
La estructura es fija: un toggle raíz con el nombre del artículo → párrafo explicativo + tres toggles de categoría → un toggle por artículo (primera línea metadatos en gris: Lugar de publicación · Fecha · Citado N · arXiv:xxx, enlace clicable; debajo, el bloque original de Q1).
Problemas conocidos (todos pisados en pruebas reales)
| Síntoma | Causa / Solución |
|---|---|
POST /v1/blocks/{id}/children → 400 invalid_request_url | Esta integración solo acepta PATCH para añadir; notion_publish.py ya usa PATCH |
| Quieres «mover» un bloque existente | Notion no tiene API de movimiento; solo se puede reconstruir: leer el bloque antiguo → crearlo bajo el nuevo padre → eliminar el antiguo |
Error al eliminar un bloque: Can't edit block that is archived | Usa DELETE /v1/blocks/{id} (después de PATCH {archived:true} ya no se puede modificar) |
| Un solo rich_text muy largo | Cada párrafo ≤2000 caracteres; seg() ya divide automáticamente en segmentos de 1900 (el resumen largo en inglés siempre pisa esto, probado con 2063 caracteres) |
PATCH /blocks/{id}/children → 400 ... start_cursor provided is invalid | Inconsistencia eventual de Notion: un bloque recién creado puede devolver este error si se le añaden hijos inmediatamente, aunque la escritura suele haber tenido éxito. Además, GET children puede devolver 0 hijos justo después. req() ya tiene un reintento con backoff para este 400; al depurar, no te apresures a reenviar toda la página, mejor recorre los toggles raíz para ver la estructura real |
| Antes de reenviar toda la página | El script no es idempotente; volver a ejecutarlo crea un toggle raíz adicional. Primero haz DELETE /v1/blocks/<id del toggle raíz anterior> (el id aparece en los logs como root toggle:), luego vuelve a ejecutar |
| Añadir demasiados bloques de una vez | Cada solicitud admite ≤100 bloques (el script divide en grupos de 80) |
| No se pueden crear anidamientos profundos | En una sola solicitud se admiten como máximo dos niveles: dentro de un toggle se puede anidar otro toggle, pero tres niveles o más requieren varias solicitudes append |
| S2 devuelve 429 constantemente | Normal, reintento con backoff; las solicitudes en paralelo aumentan la probabilidad de error; mejor serie + sleep |
Autocomprobación antes de terminar
- El valor
citations.json.source.citationCountcoincide con el número real de artículos listados (≤20 se listan todos; >20 es la unión de dos listas) - Cada artículo tiene una línea de metadatos (número de citas obligatorio); si tiene arXiv, incluye un enlace clicable
- Los artículos sin texto original Kimi llevan una explicación (marca ⚠️ +
notes) - En Notion, solo hay un toggle raíz en el nivel superior (sin duplicados por reenvíos)
- Número de artículos clasificados + no clasificados = total de artículos
- Verifica un toggle en Notion: título → metadatos → texto original Q1 (subtítulos/listas/negritas presentes)