Volver a la wiki

Lint Bibliotecario en 2 capas: bulk sin LLM + contradictions con Haiku

ADR — Lint Bibliotecario en 2 capas: bulk sin LLM + contradictions con Haiku

Contexto

El sistema Supercontexto produce y mantiene un corpus de páginas wiki (concept, feature, entity, decision, runbook, incident). En estado operativo tiene del orden de 90-200 pages activas en D1, con bib_wiki_pages como tabla canónica y los .md en src/content/wiki/ como fuente reproducible.

El corpus se degrada con el tiempo de tres maneras independientes:

  1. Drift estructural: pages con last_verified vencido, pages huérfanas (sin backlinks vía related[] ni vía documents edges), pages con sources que apuntan a archivos de código que ya no existen en el repo.
  2. Drift semántico: dos pages activas afirman cosas incompatibles sobre el mismo tema (un ADR dice “siempre async”, una feature dice “siempre sync”; un concept reporta una cifra distinta a la del entity correspondiente).
  3. Drift de cobertura: pages que existen en .md pero no llegaron a D1, o entradas de D1 sin .md correspondiente.

Cada tipo de drift requiere un detector con coste, latencia y frecuencia muy distintos. Los dos primeros caen en el alcance de Lint y son el objeto de esta decisión. El tercero pertenece al ADR de reconcile D1↔repo.

Problema

Una sola implementación de Lint que cubra los tres detectores estructurales (stale, orphans, broken_sources) y además el detector semántico (contradicciones) tiene perfiles de coste incompatibles. Los detectores estructurales son SQL puro sobre D1: ejecutables a diario sin coste de proveedor IA. La detección de contradicciones requiere comparar pages por pares dentro de la misma community o tipo y pedir a un LLM un veredicto, lo que escala como O(N²) sobre la community y consume tokens reales en cada par.

Si todo va por el mismo workflow con la misma cadencia:

Además, durante la sesión 10 (commit 2319b6c) un primer intento monolítico contra 60+ pages reventó CF Workers con Too many subrequests by single Worker invocation (60 queries D1 orphan-check + M fetches GitHub) y D1_ERROR: LIKE or GLOB pattern too complex (búsqueda de backlinks con WHERE related LIKE '%"<slug>"%'). Esto exigió rediseñar la parte estructural antes de poder añadir la parte semántica.

Opciones consideradas

1. Lint single-tier sólo bulk (sin LLM) — un único wiki_lint_bulk diario que detecta drift estructural. Cubre stale, orphans y broken_sources. Descartada: deja sin cobertura las contradicciones semánticas, que el AUDIT inicial ya había marcado como deuda explícita (“No hay Lint semántico: detectamos stale pero no contradicciones”).

2. Lint single-tier todo por Haiku — un único workflow que para cada page evalúa todo, incluida la consistencia frente a sus peers, llamando a Haiku N×N veces. Descartada: coste lineal con el corpus al cuadrado, supera el budget de subrequests CF Workers en una invocación, y la mayor parte del trabajo no necesita LLM (un last_verified vencido se ve con una resta de fechas).

3. Lint adversarial / abogado del diablo — un agente LLM que ataca el corpus generando hipótesis de contradicción y validándolas contra pages reales. Descartada por complejidad: añade un componente generativo difícil de acotar en coste y de evaluar en precisión, y el corpus aún no tiene tamaño suficiente para justificar esa sofisticación.

4. Lint two-tier: bulk diario barato + contradictions semanal Haiku — separar los dos perfiles de coste en dos handlers MCP distintos (wiki_lint_bulk, wiki_lint_contradictions) ejecutados por dos crons distintos con cadencias distintas. Seleccionada.

Decisión

Lint dividido en dos capas con responsabilidades disjuntas, dos handlers MCP y dos workflows:

Capa 1 — wiki_lint_bulk (diario, sin LLM)

Handler en functions/api/mcp/handlers/wiki.ts (wikiLintBulk). 6 pasos en una sola invocación CF Workers, sin llamadas a proveedores IA:

Workflow bibliotecario-lint.yml, cron diario 30 4 * * *, dry_run por defecto.

Capa 2 — wiki_lint_contradictions (semanal full, diario incremental)

Handler wikiLintContradictions. Agrupa pages active por community (community_id si existe, si no fallback a type como pseudo-community). Para cada par dentro del grupo invoca Haiku con dos snippets de 2000 chars y le pide JSON estructurado {verdict, subject, claim_a, claim_b, reason}. Si verdict=contradict, inserta en bib_wiki_contradictions de forma idempotente.

Dos modos:

Optimizaciones internas: pre-fetch de snippets en paralelo chunked (SNIPPET_CONCURRENCY=10) y cache por slug para no refetchar GitHub.

Toggle de pausa

Ambos workflows respetan la variable BIBLIOTECARIO_PAUSADO. El bulk no se pausa porque es SQL puro y no consume Haiku; el step de contradictions sí. El permite frenar consumo Anthropic durante planes intensivos sin desactivar el cron entero.

Consecuencias

Status

Accepted. Implementado en sesión 10 (Fase 5 Supercontexto, 2026-04-22), refactor anti-N+1 y anti-LIKE en commit 2319b6c. Refinado en sesión 17 con paginación (de47c6f) y pre-fetch chunked (fc6f0b0). En dry_run por defecto; pendiente subir a apply=true cuando se acumulen 3-7 días de runs estables y el corpus tenga community_id poblado para minimizar falsos positivos del agrupamiento por type.

Véase también

Subir