Volver a la wiki

Búsqueda de texto completo del Workspace (FTS5 en D1 + /api/search)

Qué es

La cajita del header del Workspace busca desde el 11-09-2026 en el cuerpo entero de las 1.271 páginas de las cinco wikis y del Supercontexto, en el diario de trabajo y en los briefings STATE/NEXT/LOG. Garantía pactada con Edu el 10-09-2026 ([[decision—20260910—busqueda-texto-completo-wikis]]): si un término está en cualquier parte de una página, esa página aparece. Sustituye a la búsqueda con Fuse.js sobre un índice estático de 1,7 MB que solo llevaba los primeros 1.500 caracteres de cada página ([[feature—workspace—busqueda-global-fuse]]).

Desde la entrega 2 (11-09-2026) el Oráculo y la tool MCP bib_search_semantic también son híbridos: la misma tabla FTS5 se funde con la búsqueda por significado (sección “Entrega 2” más abajo).

Cómo funciona

scripts/index-search.mjs  (cron OPS · bib-reindex-ws search · diario 01:15)
  lee el repo: src/content/wiki/*.md · WORKLOG.md · public/supercontext/{STATE,briefings/NEXT,LOG}.md · páginas fijas
        │  lotes de 50 · tool MCP bib_index_search · sello de generación · poda al final
        ▼
D1 · bib_search_fts  (FTS5, migración 0054 · doc_id, title, content, product, status, kind, href, updated_at)
        │  MATCH + bm25 (título ×12) · snippet() · ORDER BY bloque de wiki, relevancia · LIMIT/OFFSET
        ▼
GET /api/search?q=&include_drafts=0&page=1&limit=30   (auth del middleware /api/*)
        │  { total, counts{producto}, results[{doc_id,title,product,status,kind,href,snippet}] }
        ▼
SearchInline.tsx  (debounce 200 ms · grupos por wiki · Supercontexto plegado · "Cargar más" · interruptor de borradores)
PiezaFicheroDetalle
Tablamigrations/0054_search_fts.sqlTabla virtual FTS5, tokenizer unicode61 remove_diacritics 2 (“búsqueda” = “busqueda”). Una fila por documento.
Consulta y upsertfunctions/_lib/search.tsbuildMatchQuery entrecomilla cada término (un ? o un bib_chunks no rompen MATCH); searchFts; upsertSearchDocs (DELETE+INSERT en db.batch); pruneSearchBefore.
Endpointfunctions/api/search/index.tsSin IA. 503 con mensaje si la migración no está aplicada.
Tool MCPbib_index_search (handlers/archivo.ts)≤60 docs por llamada, generation ISO común, prune_before en la última llamada. Está en el gate de escritura (un token readonly no la invoca).
Indexadorscripts/index-search.mjs1.790 documentos, 9,6 MB de texto (primera pasada real desde OPS el 11-09-2026: 19 s). --dry-run cuenta sin llamar al MCP. Salta la poda si algún lote falló.
Cronscripts/cron-bib-reindex-ws.sh searchModo nuevo del reindex de OPS; log en /opt/bib-reindex/cron-bib-reindex-ws.log; el heartbeat ya vigila ese log.
Bateríascripts/bench-search.mjs120 páginas al azar con semilla fija; cuatro consultas por página (título, frase temprana, frase tardía, identificador exacto). Línea base Fuse: 84 / 81 / 77 / 0 %. Medido el 11-09-2026 contra el sitio real (120 páginas, 464 consultas): 97 / 99 / 99 / 100 %, mediana 87 ms.
Cajitasrc/components/shell/SearchInline.tsxResultados por wiki (Help, Tech, Workspace, Workspace Tech, IA Tech, diario, briefings; Supercontexto plegado con “Mostrar N fichas”), snippet con la coincidencia marcada, etiqueta de estado cuando se muestran borradores, “Cargar más”. El flujo del Oráculo se mantiene.

Entrega 2 · Oráculo híbrido (11-09-2026)

El Oráculo buscaba solo por significado (embeddings BGE-M3 + Vectorize sobre los trozos de bib_chunks): una frase literal del cuerpo o un identificador (pgbouncer, v1.132.2, un nombre de fichero) no lo encontraban si el vector no se parecía. Ahora POST /api/oraculo/ask y bib_search_semantic llaman a searchHybrid (functions/_lib/retrieval-hybrid.ts):

pregunta ──► embedding ──► searchChunks (Vectorize, top-k)          lista SEMÁNTICA (trozos)
         └─► queryTerms ─► searchFts(order 'relevance')             lista EXACTA (páginas, bm25, OR entre términos)
                            └─► ftsHitsToChunks: cada página → UN trozo
                                  · wiki: el trozo de bib_chunks que contiene el término y más se parece a la pregunta
                                    (si ninguno lo contiene, el primero de la página)
                                  · diario / briefings / páginas fijas: fragmento de ~1.400 caracteres del texto completo
         rrfFuse (k = 60): 1/(k+posición) por lista, se suma lo que sale en las dos ──► top-k para la síntesis

Decisiones fijadas

Cómo comprobarlo

Véase también

Subir