Resumen
Fusiona el grafo estático de la Biblioteca (imports, calls, direct docs) con el historial git: bib_impact_query ahora enriquece sus resultados con un campo cochanged_files que contiene los archivos que históricamente cambian juntos al consultado, capturando acoplamiento empírico cross-capa invisible para las aristas declaradas.
Ejemplo real del backfill: monitoring/api/signage.py ↔ static/js/pages/signage/SignageContentManager.js (backend ↔ frontend) — esta dependencia nunca aparece en imports ni calls, pero el historial git lo revela.
Contexto: ¿Por qué co-cambio?
El grafo estático de la Biblioteca se construye a partir de:
- Direct docs (archivos enlazados en update_checklists).
- Imports (el scanner Python/TS detecta
from X import Y). - Calls (se infieren de relaciones de modelo/función).
- Edges declarados (definiciones
bib_edgesen configuración).
Pero no cubre:
- Acoplamiento implícito: cambios sincronizados en archivos que nunca se importan mutuamente (ej. backend API ↔ componente frontend de la misma feature).
- Patrones específicos del dominio: archivos que siempre se toca juntos en refactors temáticos.
- Efectos transversales: cambios en un template HTML que siempre van con una API en Django.
La señal de co-cambio (número de commits donde dos archivos cambiaron juntos) es un indicador empírico de este acoplamiento que el grafo estático nunca vería.
Componentes
1. Tabla D1 bib_cochanges (migración 0043)
Almacena pares de archivos y cuántas veces cambiaron juntos:
CREATE TABLE bib_cochanges (
id INTEGER PRIMARY KEY,
repo TEXT NOT NULL, -- 'CreaRack-Pro' | 'CreaRackSL-workspace'
file_a TEXT NOT NULL, -- par ordenado: file_a < file_b
file_b TEXT NOT NULL,
count INTEGER NOT NULL, -- nº de merges donde cambiaron juntos
last_seen TEXT NOT NULL, -- timestamp del último cambio conjunto
UNIQUE (repo, file_a, file_b)
);
- El par se almacena ordenado (
file_a < file_blexicográficamente) → cada pareja tiene una sola fila. countes aditivo: cada merge que toca ambos archivos suma +1.last_seense actualiza con UPSERT.- Índices en
file_ayfile_bpara búsquedas rápidas.
2. MCP Handler bib_record_cochanges (interno)
Registra co-cambios en D1. No está en la lista pública de tools; se invoca desde:
- Post-merge ingest (
.github/scripts/bib_ingest.py): cada merge envía la lista de archivos tocados. - Backfill (
CreaRack-Pro/scripts/bib_cochange_backfill.py): lee el historial git completo y pre-agrega pares.
Modos de entrada:
files: [path1, path2, ...]— lista de archivos de un commit/merge → genera todos los pares y suma +1 (ingest).pairs: [[file_a, file_b, count], ...]— pares pre-agregados del backfill → suma += count.
Anti-ruido:
- Solo archivos de código (
.py,.ts,.tsx,.js,.jsx,.mjs,.cjs,.astro,.vue,.html,.css,.scss,.sql). - Excluye
.md, CHANGELOG, scripts harness, docs públicas. - Tope MAX_FILES = 25: si un merge toca >25 archivos (refactor masivo), se descarta → evita N·(N-1)/2 pares basura.
- Tope MAX_PAIRS = 1000: no acepta >1000 pares en una llamada (el backfill los trocea).
- Denylist:
config/settings/base.py(cambia en cada release por versionado).
3. Enriquecimiento de bib_impact_query
Cuando se consulta bib_impact_query(file_path=X), el resultado JSON ahora incluye:
{
"cochanged_files": [
{ "file": "monitoring/api/signage.py", "count": 7, "last_seen": "2026-06-08T10:22:00Z" },
{ "file": "static/js/pages/signage/SignageContentManager.js", "count": 6, "last_seen": "2026-06-07T15:44:00Z" }
]
}
- Se ordena por
count DESC(co-cambios más frecuentes primero), después porlast_seen DESC. - Filtra por
cochange_min(default 2; parámetro nuevo de la tool). - Límite: top 15 archivos.
- Degradación elegante: si la tabla
bib_cochangesno existe (migración no aplicada), devuelve[]sin romper la tool CORE.
4. Post-merge ingest (record_cochanges())
Función en .github/scripts/bib_ingest.py que se ejecuta incondicional después de cada merge:
def record_cochanges(changed_files: list[str], mcp_token: str, repo_name: str) -> dict:
"""Registra qué archivos cambian JUNTOS en este merge."""
# Filtra solo código (mismas reglas que backfill)
code_paths = [f for f in changed_files if ...]
if len(code_paths) < 2:
return {"recorded_pairs": 0, "skipped": "..."}
# Llama a bib_record_cochanges con modo `files`
raw = call_mcp("bib_record_cochanges", {"files": code_paths, "repo": repo}, mcp_token)
return json.loads(raw)
- Tolerante a fallos: si la llamada MCP falla, se registra el error pero NO rompe el flujo del ingest.
- Idempotente: si un merge se reintentan varias veces, los UPSERT son seguros.
- Costo: ~50ms por merge, negligible.
Verificación
✅ tsc --noEmit verde (TS tipado en handlers).
✅ ruff verde en ingest.
✅ Backfill dry-run produce señal limpia: 1561 pares con count ≥ 2, top = acoplamiento código ↔ plantilla/frontend (esperado).
Despliegue
Orden crítico:
- Aplicar migración 0043 a D1 PROD:
wrangler d1 execute --remote < migrations/0043_create_bib_cochanges.sql. - CF Pages despliega Functions automáticamente (handlers + enriquecimiento en
bib_impact_query). - Backfill one-shot desde
CreaRack-Pro/scripts/bib_cochange_backfill.py(PR companion, opcional pero recomendado para historial completo).
Si el backfill no se ejecuta,
bib_impact_queryigualmente funciona: registra co-cambios desde el merge actual en adelante.
Ventajas
- Impacto sin dudar: al modificar un archivo, devs verán automáticamente qué otros archivos suelen cambiar con él → detección temprana de acoplamiento no documentado.
- Señal empírica limpia: basada en el historial real de merges, no en reglas estáticas frágiles.
- Transparente: la degradación elegante garantiza que
bib_impact_queryjamás se rompe aunque algo falle. - Escala: UPSERT en batch respeta el presupuesto de subrequests D1.
Véase también
- [[entity—functions—tool—bib-impact-query]]
- [[entity—functions—service—bib-record-cochanges]]
- [[concept—biblioteca—grafo-estatico-vs-empirico]]
- [[decision—20260609—cairn-cherry-picks]]
- [[runbook—biblioteca—backfill-cochanges]]
Referenciado desde
- bib_impact_query — Tool CORE: impacto de cambios en dependencias y co-cambios
- bib_record_cochanges — MCP handler interno para registrar co-cambios git
- Grafo estático vs. empirismo en la Biblioteca — Dos formas complementarias de entender dependencias
- Tabla D1: bib_cochanges — Registro de pares de archivos co-cambiados