Compare commits

...

61 Commits

Author SHA1 Message Date
egutierrez 13c82be780 feat(eda): NUM DISTR muestra el valor de σ (std) en la leyenda del histograma
La leyenda de cada histograma del capítulo de distribuciones numéricas ya
reporta el valor de la media y la mediana; ahora también reporta el valor de
la desviación estándar σ. La entrada de leyenda de la banda ±1σ pasa a incluir
el número (±1σ (σ = X)) y, cuando la banda no puede dibujarse (sin media o
std<=0) pero σ es conocido, se añade una entrada de leyenda mediante un handle
proxy sin trazo, de modo que el valor de σ se reporta siempre.

No se altera el boxplot de Tukey ni el keep-together (Group) por columna.
Se añaden tests de la leyenda: golden (σ con valor junto a media y mediana),
edge sin banda (proxy) y edge sin std (no revienta). Bump 1.1.0 -> 1.2.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:01:12 +02:00
egutierrez c6d9bc26da merge: Fase 4a AutomaticEDA motor+glosario (verificado met)
- fix negrita-pisa PDF, zebra striping (PDF+PPT), keep-together (Group: heading+figura+texto misma pagina/slide), imagenes con caption en PPT
- portada construida-al-final mostrada en posicion 1 (con resumen agregado del cuerpo)
- capitulo glosario al final + terminos clicables REALES: PDF link annotation (add_pdf_internal_links, PyMuPDF) + PPT hyperlink nativo (pptx_link_run_to_slide); entropia enganchado en cat_distr como ejemplo E2E
- contrato docs/automatic_eda_contract.md §11 (glosario + keep-together + zebra)
- pymupdf>=1.28.0
2026-06-30 17:45:30 +02:00
egutierrez d1a3d58a6b feat(eda): motor AutomaticEDA fase 4a — render fixes + keep-together + glosario clicable
Mejoras transversales del motor de render (no del contenido de capítulos):

1. Fix negrita pisa texto (PDF): _place_rich_lines mide el ancho REAL de cada
   span con las métricas de fuente del renderer (peso correcto) en vez del
   grid de ancho medio; negrita y normal en la misma línea ya no se solapan.
2. Zebra striping: filas pares sombreadas (#f6f8fa) en DataTable (PDF + PPTX),
   coherente al partir tablas largas (índice de fila lógico, no por página).
3. Keep-together: bloque Group nuevo; el renderer mide el grupo entero y lo
   mueve completo a la página/slide siguiente si no cabe, y encoge la figura
   (height_in) para dejar sitio a su título y texto. num_distr lo usa.
4. Caption siempre visible en toda figura PPTX (fallback al heading); la figura
   reserva el alto de su caption para que ambos quepan en el mismo slide.
5. Portada construida al final (con resumen agregado del análisis vía
   ctx['document_summary']) pero colocada primera por build_document.
6. Glosario: capítulo nuevo (último) + GlossaryCollector en ctx; los capítulos
   registran términos y marcan apariciones con [[term:key]]...[[/term]]. Links
   clicables reales: PDF (PyMuPDF, link GOTO) y PPTX (slide-jump nativo).
   Enganchado "entropía" en cat_distr como ejemplo end-to-end.

Funciones reutilizables delegadas a fn-constructor (tag eda):
- add_pdf_internal_links_py_datascience (PyMuPDF)
- pptx_link_run_to_slide_py_datascience (slide-jump)

Contrato docs/automatic_eda_contract.md actualizado (§1/§3/§5 + §11 nueva) con
la API de glosario, keep-together y zebra para la siguiente fase. PyMuPDF
declarado en pyproject. Suite verde (90 tests); golden titanic verificado.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:35:19 +02:00
egutierrez b5334a2e97 merge: Fase 3 AutomaticEDA wiring (verificado met)
- build_eda_render_ctx: arma ctx (raw_numeric, timeseries_raw, geo_points, db_path+table) desde tabla DuckDB
- pipeline render_automatic_eda: perfila + ctx + build_document -> PDF + PPTX (11 capitulos poblados)
- profile_table: flag emit_automatic emite el report AutomaticEDA (PDF+PPT) sin romper render_eda_pdf
- text_layout: render real de **negrita** en PDF y PPTX
- .claude/commands/eda.md actualizado

Los 4 capitulos que degradaban (modelos/timeseries/geospatial/agregacion) ahora salen POBLADOS end-to-end.
2026-06-30 16:19:52 +02:00
egutierrez 437409641c docs(eda): el skill /eda emite SIEMPRE PDF + PPTX con AutomaticEDA
Actualiza el flujo del comando para que un EDA completo emita el informe
AutomaticEDA en sus dos formatos (PDF A5 móvil + PPTX 16:9) con los 11 capítulos
poblados, vía render_automatic_eda (o profile_table(emit_automatic=True)). El PDF
legacy (emit_pdf/render_eda_pdf) queda como salida independiente opcional.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 16:08:50 +02:00
egutierrez f3d427d9e4 feat(eda): wiring AutomaticEDA — build_eda_render_ctx + pipeline render_automatic_eda + profile_table(emit_automatic)
Conecta el motor AutomaticEDA con los datos crudos para que los 4 capítulos
dependientes de ctx (modelos, timeseries, geospatial, agregacion) salgan
POBLADOS en vez de degradar a una nota.

- build_eda_render_ctx (datascience, impure, dict-no-throw): dado db_path+table
  y el TableProfile agregado, construye el ctx con los datos crudos que el
  perfil no incluye: raw_numeric {col:[float|None]} alineado por fila (modelos /
  geospatial), timeseries_raw {time_col,t,series} vía extract_timeseries_raw,
  geo_points {lats,lons} desde el par lat/lon detectado, y db_path/table para el
  groupby/pivot push-down de agregacion. Muestrea con LIMIT (no trae la tabla
  entera a RAM). Compone detect_time_column / extract_timeseries_raw /
  detect_latlon_columns / duckdb_query_readonly (imports lazy para evitar ciclo).
- render_automatic_eda (pipeline): one-shot perfil -> ctx -> PDF + PPTX con los
  11 capítulos poblados; devuelve rutas + manifest de versiones por capítulo.
- profile_table: flag aditivo emit_automatic=True emite el AutomaticEDA PDF+PPTX
  además del flujo legacy (emit_pdf/render_eda_pdf intacto). Nuevas claves de
  retorno aeda_pdf_path / aeda_pptx_path / aeda_manifest_path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 16:08:41 +02:00
egutierrez f5b30b23dc feat(eda): negrita inline real (**bold**) en renderers AutomaticEDA
El render de Markdown del motor AutomaticEDA quitaba los marcadores **negrita**
sin aplicar estilo. Ahora los spans **bold**/__bold__ se renderizan en negrita
real, de forma aditiva y sin romper el anti-corte:

- text_layout.py: parse_inline_bold() tokeniza spans preservando el texto
  visible (== strip_inline_md) y wrap_rich() envuelve por palabras a max_chars
  conservando el flag de negrita por segmento (la anchura visible no cambia, así
  que la paginación es idéntica).
- render_pdf_impl.py: _place_rich_lines() dibuja cada segmento con su fontweight
  avanzando x por el mismo grid de caracteres que usa el wrap (párrafos+bullets).
- render_pptx_impl.py: _add_rich_text() usa runs nativos de python-pptx con
  font.bold por segmento (negrita real de PowerPoint).
- bold_render_test.py: helpers puros (no-overflow, bold preservado, marcadores
  desbalanceados) + e2e que abre el .pptx y confirma un run con font.bold True.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 16:08:16 +02:00
egutierrez 5eaf3f662e merge: capitulo AutomaticEDA agregacion (verificado met) + funciones delegadas eda 2026-06-30 15:45:37 +02:00
egutierrez 05fe76bce0 merge: capitulo AutomaticEDA timeseries (verificado met) + funciones delegadas eda 2026-06-30 15:45:37 +02:00
egutierrez 864430e988 merge: capitulo AutomaticEDA geospatial (verificado met) + detect_latlon_columns/analyze_geo_extent/build_geo_scatter 2026-06-30 15:36:22 +02:00
egutierrez a69d14d38e feat(eda): capítulo TIMESERIES del AutomaticEDA (evolución + análisis de serie)
Capítulo nuevo build_timeseries(profile, ctx) -> Chapter|None del motor
AutomaticEDA. Cuando la tabla tiene columna de fecha/datetime, grafica la
evolución de cada columna numérica por periodo (valor agregado + conteo de filas)
y los paneles de descomposición STL y autocorrelación (ACF), con el análisis de
la serie: estacionariedad (ADF+KPSS), autocorrelación (Ljung-Box), fuerzas de
tendencia/estacionalidad (Hyndman) y la transformación sugerida (retornos o
diferencias) para evitar correlaciones espurias. Sin columna temporal devuelve
None. Consolida series OHLC casi idénticas en un único gráfico conservando el
análisis de cada columna.

La serie cruda llega por ctx['timeseries_raw'] (mismo patrón que modelos con
raw_numeric); las figuras son perezosas (Figure.make) y el paginador del núcleo
garantiza no-corte en PDF y PPTX. CHAPTER_VERSION 1.0.0.

Cubre los MUST del diseño (report 2043): MUST-9.1 (línea valor-vs-tiempo + conteo
por periodo), MUST-9.2 (paneles STL + ACF), MUST-9.3 (perfil datetime +
consolidación OHLC).

Funciones nuevas del registry (grupo eda), delegadas a fn-constructor, no inline:
- detect_time_column (pure): detecta la columna temporal y las numéricas
- profile_datetime (pure): rango/frecuencia/regularidad/huecos de la fecha
- resample_timeseries (pure): agrega la serie por periodo + conteo
- extract_timeseries_raw (impure): lee la serie cruda ordenada de DuckDB/PG

Verificación: 69 tests verdes (capítulo 9 + funciones 28 + núcleo/renderers);
golden real sobre seattle-weather (estacional) y aapl (OHLC) con PDF+PPTX sin
cortar nada (cols_cortadas=[]).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:35:42 +02:00
egutierrez fd59530751 feat(eda): capítulo AGREGACION del AutomaticEDA (groupby + pivot + barras)
Capítulo nuevo (siempre presente cuando hay categóricas agrupables) que analiza la
tabla por grupos: stats de numéricas por grupo, tablas dinámicas (pivot) y gráficos
de barras desde cero. Obtiene los datos por ctx['aggregations'] precomputado o en
vivo vía push-down (ctx['db_path']+table), siguiendo el patrón de chapters/modelos.py.
Degrada a None cuando no hay categóricas; emite los bloques del modelo (DataTable,
Markdown, Figure) para que el paginador del núcleo no corte nada en PDF ni PPTX.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:33:55 +02:00
egutierrez 96da9e3015 feat(eda): funciones de agregación/OLAP para AutomaticEDA (groupby/pivot push-down + selección LLM)
Cuatro funciones nuevas del grupo eda que nutren el capítulo AGREGACION:
- select_groupby_keys (pure): elige categóricas agrupables + numéricas medida desde el TableProfile.
- groupby_stats_duckdb (impure): GROUP BY push-down en DuckDB (count/mean/median/std/min/max por grupo).
- pivot_table_duckdb (impure): pivot A×B push-down, limitado a top filas/cols para no cortar.
- suggest_aggregations_llm (impure): el LLM elige las agregaciones interesantes con fallback determinista.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:33:55 +02:00
egutierrez 00cd5274bc feat(eda): capítulo GEOSPATIAL del AutomaticEDA (scatter geográfico + zona/país)
Capítulo nuevo chapters/geospatial.py (CHAPTER_VERSION 1.0.0). Cuando el dataset
tiene un par de coordenadas, dibuja un scatter geográfico en proyección
equirectangular (la escala respeta la latitud para no estirar la longitud) y
analiza la extensión: bounding box, centroide, span, conteo por zona/país,
hemisferios y una interpretación. Cuando NO hay coordenadas, build_geospatial
devuelve None y el capítulo se omite.

Sigue el contrato de capítulos (firma build_<id>(profile, ctx) -> Chapter|None,
lectura defensiva, nunca lanza) y el patrón de modelos/num_distr: delega el
cálculo a las primitivas puras del registry (detect_latlon_columns,
analyze_geo_extent, build_geo_scatter) y solo dibuja la figura matplotlib de
forma perezosa. Las coordenadas crudas llegan por ctx['geo_points'] o
ctx['raw_numeric'] (como modelos lee raw_numeric); sin ellas, degrada con un
bounding box aproximado de numeric.min/max y una nota honesta.

Anti-cortes: usa DataTable/KVTable/Figure/Markdown del modelo, que el paginador
parte sin cortar. Test self-contained con golden + 6 edges + anti-cut (nombres
largos + 2100 puntos en varias regiones renderizan a PDF y PPTX sin truncar).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:29:33 +02:00
egutierrez cd658cc703 feat(eda): primitivas geoespaciales del grupo eda (detección lat/lon + extensión + scatter)
Tres funciones puras nuevas del dominio datascience (tags eda + geospatial) que
sostienen el capítulo GEOSPATIAL del AutomaticEDA, delegadas a fn-constructor:

- detect_latlon_columns: identifica el par (lat, lon) por nombre de columna +
  rango de valores ([-90,90] / [-180,180]) desde profile['columns']. Devuelve
  {lat_col, lon_col, confidence, reason}. 9 tests.
- analyze_geo_extent: bbox, centroide, span haversine, conteo por zona/país
  (lookup offline con bounding boxes embebidos, KISS sin geopandas) y
  hemisferios. 7 tests.
- build_geo_scatter: prepara los puntos del scatter en orden [lon, lat] con
  downsampling determinista por paso fijo + aspect equirectangular 1/cos(lat)
  clampado. 6 tests.

Registradas en datascience/__init__.py. Todas pure, params_schema completo,
.md autosuficiente (Ejemplo + Cuando usarla + Gotchas).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:29:33 +02:00
egutierrez 81b57f9acd merge: capitulo AutomaticEDA analisis_llm (verificado met) 2026-06-30 15:15:39 +02:00
egutierrez 02ee222dde merge: capitulo AutomaticEDA cat_distr (verificado met) 2026-06-30 15:15:39 +02:00
egutierrez ba162ab301 merge: capitulo AutomaticEDA correlacion (verificado met) 2026-06-30 15:15:39 +02:00
egutierrez 415154d9a3 merge: capitulo AutomaticEDA modelos (verificado met) 2026-06-30 15:10:23 +02:00
egutierrez d479a8e4e2 merge: capitulo AutomaticEDA calidad (verificado met) 2026-06-30 15:10:22 +02:00
egutierrez 9286e3b6b1 merge: capitulo AutomaticEDA num_distr (verificado met) 2026-06-30 15:10:22 +02:00
egutierrez 649de07d6b feat(eda): capítulo AutomaticEDA CAT DISTR + funciones cardinalidad/pie
Capítulo cat_distr del motor AutomaticEDA: distribuciones categóricas con
explicación de entropía de Shannon, métricas de cardinalidad por columna
(valores distintos, % distintos, total de filas, valores únicos, entropía y
su máximo log2(k) + normalizada), tabla top-k y un donut de las categorías
más comunes (top-k + «Otros»). Marca columnas id-like y dominadas.

Delegadas a fn-constructor (grupo eda):
- categorical_cardinality_block: deriva métricas de cardinalidad/entropía.
- categorical_top_pie_figure: figura donut top-k + «Otros», leyenda lateral.

Defensivo (dict-no-throw): None si no hay columnas categóricas; normaliza
mode_pct a escala 0-100 (summarize_categorical lo emite como fracción).
Tablas vía DataTable y figura perezosa: el paginador del núcleo garantiza
no-corte en PDF y PPTX. Tests: golden + edge (sin categóricas) + anti-corte
(label largo / muchas columnas) en ambos renderers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:04:10 +02:00
egutierrez af1dd9bcc2 test(eda): tests del capítulo ANÁLISIS LLM (golden + edges + anti-cortes)
Suite self-contained (perfil sintético + un golden, sin DuckDB):
- golden: build_analisis_llm devuelve el Chapter y el documento entero renderiza
  a PDF y PPTX con resumen, análisis sugeridos, limpieza y una columna del
  diccionario presentes.
- orden: el capítulo queda inmediatamente después de `overview`.
- edges: profile sin bloque `llm` (o None/{}/malformado/llm vacío) -> None sin
  lanzar; fallback a ctx['llm'].
- anti-cortes: diccionario de 40 filas + sugerencia de limpieza de ~150 chars se
  reparten en varias páginas/slides sin perder ninguna fila ni palabra.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:01:26 +02:00
egutierrez fc5bc334c8 feat(eda): capítulo ANÁLISIS LLM para AutomaticEDA, junto al overview
Nuevo capítulo `analisis_llm` del motor AutomaticEDA. Consume el bloque `llm`
que `eda_llm_insights` (grupo eda) ya deja en el TableProfile —no llama al LLM
ni recalcula— y lo convierte en bloques del modelo de documento para que se
renderice sin cortarse en PDF ni PPTX:

- Resumen de la tabla y significado de una fila -> bloques Markdown (el
  renderer los envuelve a líneas completas, nunca pierde texto).
- Diccionario de datos y PII -> DataTable (el paginador parte por filas
  repitiendo cabecera y envuelve celdas largas dentro de su columna).
- Análisis sugeridos y limpieza sugerida -> listas de viñetas Markdown; cada
  entrada es una línea completa que el renderer envuelve, nunca trunca.

Lectura defensiva (.get) en todo; devuelve None si el profile no trae bloque
`llm` (p.ej. profile_table sin run_llm) para omitir el capítulo.

MUST-3.2 (report 2043): se mueve `analisis_llm` en CHAPTER_ORDER a la posición
inmediatamente posterior a `overview`, como pidió el usuario ("va junto al
overview").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:01:26 +02:00
egutierrez 03f3dca823 feat(eda): capítulo CORRELACION de AutomaticEDA (matriz + top pares ±)
Implementa chapters/correlacion.py siguiendo el contrato de capítulos:
build_correlacion(profile, ctx) -> Chapter|None, CHAPTER_VERSION="1.0.0".

Consume profile['correlations'] (salida de association_matrix del grupo eda,
sin recalcular estadística) y emite, como bloques del modelo:

- Matriz de asociación (Figure/heatmap perezoso, RdBu_r, con signo en num-num
  y magnitud en métricas mixtas; etiquetas ordenadas por conectividad y
  recortadas a las 16 más conectadas para legibilidad).
- TOP de pares POSITIVOS y TOP de pares NEGATIVOS en dos DataTable separadas
  (los negativos son por construcción num-num, único método con signo), con
  método, valor, p-valor corregido (FDR) y significancia.
- Resumen FDR (multiple_testing) + leyenda de métodos.
- Aviso de espuriedad por niveles no estacionarios (Granger-Newbold) cuando el
  profile lo marca.

Lectura defensiva en todo (None si no hay pares; nunca lanza). Anti-cortes:
sólo bloques del modelo, el paginador parte tablas repitiendo cabecera y escala
la figura entera.

Test self-contained (5 casos): golden a nivel de bloques + golden render
PDF/PPTX, edge sin pares -> None, edge sólo positivos -> nota honesta, y
anti-corte con matriz ancha + etiquetas largas (dato íntegro a nivel de bloque,
ambos renderers sin reventar).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:59:50 +02:00
egutierrez d412522db9 feat(eda): capítulo CALIDAD del AutomaticEDA (criterios + scores + problemas ES)
Añade el capítulo de calidad de datos al motor AutomaticEDA, siguiendo el
contrato de capítulos (build_calidad(profile, ctx) -> Chapter | None,
CHAPTER_VERSION). El capítulo responde lo que pidió el usuario, en español y
en formato de tabla:

- Intro "Cómo se calcula la calidad": explica los tres criterios y sus pesos
  (completitud 50%, validez 30%, consistencia 20%) antes de cualquier número,
  más una KVTable de resumen a nivel tabla (calidad global y agregados).
- Tabla "Scores por columna": score total más su desglose en completitud /
  validez / consistencia, ordenada de peor a mejor.
- Tabla "Problemas detectados": los issues en español por columna, separados de
  los flags de tipo. Cuando no hay problemas, una nota honesta.

Registry-first: el desglose y los issues NO se recalculan aquí; se consumen de
la función pura del registry column_quality_score (grupo eda), que ya deriva
{score, completeness, validity, consistency, issues} del ColumnProfile. El
capítulo es render-only y compone bloques del modelo; los renderers paginan las
tablas (parten por filas repitiendo cabecera) y envuelven celdas largas, de modo
que nada se corta en PDF ni en PPTX. La lista de issues por celda se acota a
160 caracteres con "(+N más)" para que una fila nunca crezca más que una página.

Test self-contained (sin DuckDB): golden con desglose + issues ES, edges
(None/{}/sin columnas -> None; perfil limpio -> nota), y anti-cortes (perfil de
22 columnas con nombres largos renderizado a PDF y PPTX: el nombre completo
sobrevive al envolverse, sin marcador de truncado).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:59:10 +02:00
Egutierrez c1a4a83717 feat(eda): capítulo num_distr — histograma con media/mediana/±σ + boxplot Tukey
Capítulo NUM DISTR del motor AutomaticEDA. Por cada columna numérica emite,
como una sola Figure indivisible de dos ejes compartiendo X, un histograma con
la media (línea roja discontinua), la mediana (línea verde continua) y la banda
±1σ dibujadas como referencias, y un boxplot de Tukey debajo (caja P25–P75,
bigotes a 1,5·IQR, marca de valores fuera de las vallas). Una nota por columna
traduce el distribution_type a lenguaje llano (MUST-4.1/4.2/4.3 del report 2043).

Consume el profile del grupo eda sin recalcular: el histograma usa los bins
{lo,hi,count} de describe_numeric y las vallas del boxplot las deriva la función
pura build_boxplot_stats_py_datascience. Lectura defensiva: sin columna numérica
devuelve None; profile None/{} no lanza. Test self-contained: golden + edges +
anti-corte (8 columnas no cortan en PDF ni PPTX).
2026-06-30 14:58:03 +02:00
egutierrez 81e8597d21 feat(eda): capitulo MODELOS de AutomaticEDA (markdown, scatter PCA+clusters, micro-LLM)
Implementa chapters/modelos.py (build_modelos / CHAPTER_VERSION) consumiendo
profile['models'] {pca,kmeans,outliers,normality} de run_eda_models. Render
markdown estructurado con bloques anti-corte:

- Intro de normalizacion z-score: por que se estandariza antes de PCA/KMeans (MUST-8.3).
- PCA: scree plot (varianza explicada + acumulada, un solo eje Y) + tablas de
  varianza y cargas principales (SHOULD-8.4).
- Segmentacion KMeans: scatter PCA coloreado por cluster con centroides, en su
  propia pagina/slide (MUST-8.1); tabla de tamaños; micro-analisis LLM por
  cluster con titulo, cada entrada indivisible (MUST-8.2).
- Isolation Forest: explicacion de la deteccion multivariante de outliers y del
  umbral + conteos (MUST-8.3).
- Normalidad: tabla por columna (Jarque-Bera / D'Agostino / Shapiro), pagina sola.

El scatter coloreado y los titulos LLM no estan en el TableProfile, asi que el
capitulo los toma de ctx (cluster_projection precomputado, o raw_numeric para
calcular project_clusters_2d en vivo, o cluster_titles/run_cluster_llm para el
micro-analisis), igual que overview lee head_rows; degrada honesto con una Note
cuando faltan. Devuelve None si el profile no trae bloque models renderizable.

Tests self-contained (sin DuckDB/sklearn/LLM/red): golden PDF+PPTX, edges
(profile None/vacio/insuficiente, kmeans sin proyeccion), anti-corte (tabla de
normalidad de 40 columnas parte repitiendo cabecera sin perder ninguna). 8/8.
Suite del nucleo render_automatic_eda_pdf/pptx sigue verde.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:57:43 +02:00
egutierrez 4de071f2f9 feat(eda): project_clusters_2d + describe_clusters_llm para el capitulo MODELOS
project_clusters_2d (pura): PCA(2)+KMeans sobre el MISMO subset estandarizado,
devolviendo proyeccion 2D y labels alineados por fila + centroides en espacio PCA
+ perfiles de cluster desestandarizados. Es la pieza que garantiza la alineacion
points<->labels que pca_explained y kmeans_segments no cubren (estandarizan por
separado y kmeans descarta los labels). Habilita el scatter PCA coloreado por
cluster (MUST-8.1).

describe_clusters_llm (impura): micro-analisis LLM de los clusters en una sola
llamada a ask_llm (grupo claude-direct), devuelve titulo + descripcion por cluster
con degradacion dict-no-throw a titulos genericos si el LLM no responde (MUST-8.2).

Ambas re-exportadas en datascience/__init__.py. Tests: 6/6 y 9/9 (sin red).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:57:27 +02:00
egutierrez fcf5a4c6a3 feat(eda): build_boxplot_stats — estadísticas de boxplot Tukey desde sub-bloque numeric
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:54:49 +02:00
egutierrez 959648ec4f Merge remote-tracking branch 'origin/master' 2026-06-30 14:43:51 +02:00
egutierrez a3f75d61ec chore: avance acumulado de sesiones previas (reorg dev/issues + ajustes)
Reorganizacion de dev/issues en subcarpetas (completed/, cpp/, gamedev/,
kanban/, trading/, imagegen/, matrix/) y cambios acumulados en cmd/fn/pyrunner,
.claude/commands y settings. Trabajo de otro LLM/sesion, commiteado a peticion
del usuario para desbloquear el working tree. Excluido logs/ardour_mcp_server.log (ruido).
2026-06-30 14:43:51 +02:00
egutierrez cb7a7fc1fd docs(eda): contrato de capítulos AutomaticEDA + capability page
Añade docs/automatic_eda_contract.md: documento autoritativo y autosuficiente
para que otros agentes escriban capítulos en paralelo (NUM DISTR, CAT DISTR,
CALIDAD, CORRELACIÓN, MODELOS, ANÁLISIS LLM, TIMESERIES, GEOSPATIAL,
AGREGACIÓN). Cubre el modelo de bloques/capítulo exacto, la firma
build_<chapter>(profile, ctx) -> Chapter|None, la declaración de
CHAPTER_VERSION, dónde colocar el módulo, cómo se registra el orden del
documento, qué claves del profile consume cada capítulo, las claves nuevas que
la fase de cálculo debe añadir (head_rows, columns[].examples) y un ejemplo
completo del capítulo de referencia OVERVIEW.

Enlaza las dos funciones nuevas y el contrato desde docs/capabilities/eda.md y
actualiza el recuento del grupo eda en el índice de capabilities.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:30:31 +02:00
egutierrez 9cdde4a341 feat(eda): núcleo AutomaticEDA — documento por capítulos + renderers PDF/PPTX anti-corte
Introduce la capa intermedia entre el contenido de un EDA y su formato de
salida. Un documento es una lista de capítulos versionados; cada capítulo es
un conjunto ordenado de bloques (heading, markdown, kv_table, data_table,
figure, image, caption, note) independientes del formato.

Núcleo (paquete de soporte python/functions/datascience/automatic_eda/):
- model.py: dataclasses de bloques + Chapter, normalizadores defensivos
  (aceptan dataclass o dict, nunca lanzan), ENGINE_VERSION y el manifiesto
  por capítulo (automatic_eda_manifest.json).
- text_layout.py: medición/wrapping por rejilla de caracteres compartida.
- chapters_registry.py: CHAPTER_ORDER pre-declarado + build_document con
  auto-discovery de capítulos por convención (permite añadir capítulos en
  paralelo sin editar el registro).
- render_pdf_impl.py: paginador A5 retrato móvil que MIDE cada bloque y nunca
  corta: texto a líneas completas, tablas largas partidas por filas repitiendo
  cabecera, figuras/imágenes escaladas para caber enteras. Pie versionado por
  capítulo.
- render_pptx_impl.py: mismo principio sobre slides 16:9 (continúa en slide
  "(cont.)"; tablas repiten cabecera; figuras exportadas a PNG escaladas).
- chapters/portada.py y chapters/overview.py: capítulos de referencia. Portada
  con nombre, rótulo Automatic-EDA, fuente, almacenamiento (inferido de
  source), fecha europea, filas×cols, descripción, granularidad y calidad con
  criterios. Overview con df.head (placeholder honesto si falta head_rows),
  diccionario de columnas (tipo/nulos/ejemplos) y describe numérico.

Funciones públicas del registry (grupo eda, dict-no-throw):
- render_automatic_eda_pdf / render_automatic_eda_pptx: aceptan capítulos o un
  TableProfile (construyen los capítulos con build_document) y escriben el
  manifiesto. Aditivas — no reemplazan render_eda_pdf.

Tests self-contained (sin DuckDB) para ambos renderers: golden (portada +
overview), partición de tablas largas repitiendo cabecera, no-corte de celdas
y markdown largos, profile None/{} válido de 1 página/slide, y error path en
directorio no escribible. 23 tests verdes (incluye los previos de
render_eda_pdf, intactos).

Dependencia nueva python-pptx>=1.0.2 declarada en python/pyproject.toml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:30:31 +02:00
egutierrez 5501507588 feat(infra): launch_fleetclaude auto-detecta terminal (kitty ↔ Windows Terminal)
La ruta ventana-nueva ya no asume kitty. Elige terminal según el host, sin
config por PC: kitty si está instalado y hay display ($DISPLAY/$WAYLAND_DISPLAY);
si no, en WSL abre Windows Terminal (wt.exe) ejecutando
`wsl.exe [-d $WSL_DISTRO_NAME] -- bash -lic 'tmux ... attach'`.

Arregla el síntoma "se lanza la flota pero no se ve": en WSL sin kitty la sesión
tmux se creaba pero ninguna ventana la mostraba. Mismo `fleetclaude` funciona en
un PC con kitty y en otro WSL sin kitty.

wt.exe se lanza desde un subshell con cwd /mnt/c para evitar el warning por cwd
UNC (\\wsl.localhost\...). El path de attach interactivo (terminal real fuera de
tmux) queda intacto. Bump 1.5.0 -> 1.6.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 12:50:20 +02:00
egutierrez 88eabb0457 chore: auto-commit (1 archivos)
- logs/ardour_mcp_server.log

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-29 11:05:00 +02:00
Egutierrez ebb00d8a42 chore(issues): cierra 0173-0177 (hallazgos del benchmark EDA resueltos en rondas 2-4)
Los 14 hallazgos H1-H14 del benchmark estan corregidos y verificados con re-corrida.
Commits: caf8c25d (S), c4cff5ed (render H4/H9), e142ef02 (comportamiento H2/H3/H6/H7/H8/H10/H11).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 06:38:51 +02:00
Egutierrez e142ef026d fix(eda): hallazgos de comportamiento del benchmark (H2,H3,H6,H7,H8,H10,H11)
Ronda 4 (verificada con re-corrida sobre los datasets afectados):
- H2: stl_decompose deriva periodo de la frecuencia del indice (seattle period=365
  seasonal_strength=0.84; fin del period=2 espurio)
- H3+H10: infer_fk por senal de nombre (<X>Id->X.<X>Id) + excluir no-clave -> chinook
  111->9 FK, todas reales, cero absurdas, 16-27x mas rapido; base intacta (flag off->111)
- H6: association no computa eta2 si cardinalidad~=n (Ticket-Fare espurio fuera)
- H7: id secuencial monotono excluido de correlacion y PCA/KMeans (PassengerId fuera)
- H8: correlacion de series no estacionarias marcada espuria / sobre retornos
- H11: distribution_type usa modos/cardinalidad/normalidad (quality->discrete)
- 66 tests verdes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 06:37:47 +02:00
Egutierrez c4cff5ed5b feat(eda): render de models en markdown + PDF DB-level para profile_database (H4,H9)
- H4: render_eda_markdown anade seccion Modelos (PCA/KMeans/normalidad/outliers);
  render_eda_pdf formatea models/series/caveats como tablas (no str(dict) crudo)
- H9: profile_database gana flag emit_pdf -> PDF movil DB-level (resumen tablas +
  join graph) via render_eda_pdf_relational; clave report_pdf_path
- aditivos y retrocompatibles (flags default False). 38 tests verdes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 04:05:38 +02:00
Egutierrez caf8c25d99 fix(eda): bugs de bajo riesgo del benchmark (H1,H5,H12,H13,H14) + tests faltantes
- H1: render_eda_markdown ya no aplica doble x100 a outlier_pct (336% -> real)
- H5: profile_database filtra base_tables_only (excluye VIEWs; sakila 21->16)
- H12: suggest_reexpression salta columnas no-continuas
- H13: to_returns/profile_table elige retornos (financiera) vs diferencias (fisica)
- H14: test de regresion ATTACH sqlite via information_schema
- +8 tests de las funciones eda nuevas (acf_pacf, adf_kpss, ...). 77 tests verdes
- L/M (H2,H3,H4,H6,H7,H8,H9,H10,H11) quedan en issues 0174-0177 para revision

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 03:51:11 +02:00
Egutierrez 7ac69ab4fb feat(eda): series temporales + rigor anti-data-mining + PDF movil + /eda + benchmark issues
Bloque del grupo eda (sesion ausente EDA-benchmark):
- 8 funciones nuevas: adf_kpss_stationarity, acf_pacf, stl_decompose, to_returns,
  fdr_correction, suggest_reexpression, exploratory_caveats, render_eda_pdf
- integracion: profile_table (run_series, emit_pdf), association_matrix (FDR Benjamini-Hochberg),
  render_eda_markdown (secciones series/reexpresion/caveats)
- slash commands /eda y /capitulos
- issues 0173-0177: mejoras del /eda derivadas del benchmark sobre 12 datasets reales
  (outlier_pct x100, periodo estacional, FK inference, render models, tipos id-like)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 03:34:01 +02:00
egutierrez 02301aaed3 feat(datascience): auto-commit con 5 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-28 18:16:24 +02:00
egutierrez 2729629f0a merge(gamedev-2d): comfyui_walk_cycle_oneshot — walk cycle pose-driven + pixelart animado (5 funciones) 2026-06-28 18:16:18 +02:00
egutierrez 6cc90558d4 feat(gamedev-2d): pipeline walk_cycle_oneshot — personaje andando en pixel-art animado
Promueve el caso 1 del report 0217 (animacion de sprites de personaje) a un
pipeline one-shot: de un prompt de personaje a un sprite sheet + GIF/WEBP en loop,
frame-by-frame dirigido por pose (ControlNet OpenPose + seed fija + Rembg) con cada
frame pixelizado a NxN RGBA.

Nuevas funciones reutilizables (issue 0087, crecimiento por composicion):
- comfyui_walk_cycle_oneshot (pipeline): orquesta poses -> generacion -> pixelizado
  -> ensamblado. No-throw, salta frames que fallan. Modo openpose (esqueletos reales)
  con fallback prompt-pose.
- render_openpose_walk_skeletons: dibuja N esqueletos OpenPose COCO-18 del walk cycle
  (el insumo que el report 0217 marco como faltante).
- comfyui_pixelize_sprite_png: PNG existente -> NxN RGBA pixel-art real (compone
  crop_to_content + pixeloe_downscale + comfyui_pixelize_image).
- assemble_animated_sprite: frames RGBA -> sprite sheet horizontal + WEBP/GIF loop.
- comfyui_build_walk_cycle_workflow (pura): grafo API del workflow animado para la UI
  (ControlNet OpenPose -> KSampler xN seed fija -> ImageBatch -> Rembg -> SaveAnimatedWEBP).

Verificado en GPU: GIF/WEBP de caballero andando, 4 frames 32x32 (y 64x64) RGBA con
fondo transparente y 16 colores, identidad de silueta consistente, piernas que cambian.
Metodo de poses usado: OpenPose real (sin fallback). Evidencia en report 0221.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:14:46 +02:00
egutierrez 36a725ba10 feat(ml): auto-commit con 4 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-28 16:02:08 +02:00
egutierrez 1dd6c889e5 tune(comfyui): crop_to_content pad_ratio 0.06->0.02 — sprite llena más el frame 2026-06-28 16:02:02 +02:00
egutierrez 7aaac44a49 test(comfyui): reubicar tests del sprite-fix a tests/ 2026-06-28 16:00:20 +02:00
egutierrez ffcb69ce02 merge(comfyui): pixelart sprite fix — llena el frame (crop_to_content) + fondo transparente (alpha-aware) 2026-06-28 16:00:20 +02:00
egutierrez c79f33265e fix(comfyui): pixelart_real_oneshot — sprite llena el frame + fondo transparente
Arregla los dos defectos reportados del pipeline comfyui_pixelart_real_oneshot:
el sujeto salía diminuto respecto al frame y siempre traía fondo (sin opción de
transparencia).

Causa raíz: comfyui_pixelize_image hacía convert("RGB") y descartaba el alpha;
comfyui_build_pixelart_workflow no inyectaba rembg (a diferencia de sus hermanos
item_icon/enemy_creature); y no había ningún paso de auto-crop al contenido.

Orden correcto del pipeline ahora:
generar (rembg) -> autocrop al bbox + cuadrar -> downscale (alpha aparte por
PixelOE) -> cuantización alpha-aware -> PNG RGBA transparente.

Piezas:
- comfyui_pixelize_image (1.1.0): keep_alpha/alpha_threshold. Con RGBA cuantiza
  solo el RGB (fondo transparente relleno con la moda del sujeto, fuera de la
  paleta) y preserva/binariza el alpha aparte. RGB sin alpha intacto.
- crop_to_content (NUEVA, pura PIL): bbox del contenido (alpha o diff-fondo) ->
  recorta -> margen -> cuadra centrando. No-throw; imagen vacía -> copia intacta.
- comfyui_build_pixelart_workflow (1.1.0): transparent=True + rembg_model.
  Inyecta nodo Image Rembg tras VAEDecode (patrón de item_icon).
- comfyui_pixelart_real_oneshot (1.1.0): transparent + autocrop + crop_pad_ratio
  + rembg_model. Recombina el alpha aparte tras PixelOE (trabaja en RGB). Campos
  nuevos: has_alpha, autocrop_applied.

Verificado en GPU (knight 64px): RGBA con 4 esquinas alpha==0, contenido cubre
88% del frame (antes 48%), 16 colores, 64x64. 32 tests offline en verde.
Report: reports/0218-2026-06-28-pixelart-sprite-fix.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:59:26 +02:00
egutierrez 31c2f6ac7f test(comfyui): reubicar test de pixeloe_downscale a tests/ 2026-06-28 15:27:13 +02:00
egutierrez 3bc97828e3 merge(comfyui): comfyui_pixelart_real_oneshot + pixeloe_downscale (pixelart real: PixelOE + cuantización dura) 2026-06-28 15:27:08 +02:00
egutierrez ccdd529bdc feat(comfyui): pipeline comfyui_pixelart_real_oneshot — pixelart REAL (PixelOE + cuantizacion dura)
Materializa el metodo ganador del report 0215: generar a alta-res con SDXL +
LoRA SDXL_pixel-art, downscale contrast-aware con PixelOE (engine=pixeloe para
sprites/personajes) o nearest (tiles), y cuantizacion dura con
comfyui_pixelize_image (16 colores libres o paleta fija pico-8/nes/game-boy).

- pixeloe_downscale_py_ml: downscale contrast-aware via lib pixeloe con bridge
  de interprete (la lib vive en el venv de ComfyUI, no en el del registry).
  No-throw, fallback limpio si pixeloe no disponible.
- comfyui_pixelart_real_oneshot_py_pipelines: one-shot que compone build_pixelart
  + submit + wait + fetch + pixeloe_downscale + pixelize_image. Fallback
  automatico pixeloe->nearest. Sweet-spot 64px personajes, 32px iconos.

Verificado por PIL: personaje 64x64=16 colores, icono 32x32=16 colores (vs ~33k
de la imagen de difusion cruda). 100% grid duro + outline nitido.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:24:15 +02:00
egutierrez 741724f633 chore: auto-commit (1 archivos)
- logs/ardour_mcp_server.log

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-28 15:03:24 +02:00
egutierrez 2be62f6ef6 merge(comfyui): comfyui_generate_until_quality — loop generar/juzgar/refinar (best-of-N + escalate + refine_prompt) 2026-06-28 15:02:45 +02:00
Egutierrez 8e9e1e6c8a feat(comfyui): pipeline comfyui_generate_until_quality (loop evaluator-optimizer)
Loop tipo GAN sin entrenar: genera con un builder del registry, juzga con el
panel multi-juez (comfyui_judge_image) y, si no alcanza el umbral, refina (nueva
seed, mas steps/cfg, prompt corregido con el feedback del juez via ask_llm) y
regenera hasta converger (verdict 'good') o agotar max_iters. Devuelve siempre
la mejor candidata por score (best-of-N), nunca lanza excepcion cruda.

Compone comfyui_submit_workflow + comfyui_wait_result + comfyui_fetch_output_image
+ comfyui_judge_image + ask_llm. Filtra kwargs por inspect.signature para ser
robusto entre builders. Caso HUD verificado: itera iter0 bad -> iter1 good.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:01:37 +02:00
egutierrez ec46aae04c chore: auto-commit (1 archivos)
- logs/ardour_mcp_server.log

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-28 07:34:26 +02:00
egutierrez b173ac2703 merge(comfyui): higiene capability pages (drift conteos + styles + audio/templates + build_flux + parallax) 2026-06-28 07:34:02 +02:00
egutierrez 5280499df5 merge(comfyui): tests offline para 16 builders puros (376 tests verdes) + tested:true 2026-06-28 07:32:15 +02:00
egutierrez 346f859b86 test(comfyui): tests offline para 15 builders/funciones puras sin test
Cubre 15 funciones del grupo comfyui (+ las 4 de comfyui-judge) que no tenian
test, con tests offline (sin red, sin GPU, sin servidor ComfyUI):

- 5 builders puros gamedev-2d: build_asset_variant, build_directional_sprite,
  build_inpaint_asset, build_outpaint_asset, build_sprite_from_sketch (estructura
  del workflow en API format + cableado + determinismo + error paths).
- 3 impuras offline via PIL/stdlib: build_grid, flatten_alpha_on_color,
  read_png_metadata (PNGs reales en tmp, error paths).
- 4 de comfyui-judge: score_aesthetic y score_clip_alignment por sus guards
  previos al subproceso torch; judge_image (panel) y critique_image_llm con la
  dependencia pesada monkeypatcheada.
- 3 que componen otras funciones: resolve_workflow_deps, import_workflow_json,
  extract_recipe_from_png (dependencia de red monkeypatcheada o fallback offline).

Cada .md actualizado con tested: true + test_file_path + tests.
Cobertura del grupo comfyui (tag plano): 79 -> 90 con test (47 -> 36 sin).
comfyui-judge: 0/4 -> 4/4. pytest: 101 passed; carpeta ml/tests: 376 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:30:59 +02:00
egutierrez 287abbd6ee merge(comfyui): fix firmas keyword-only para que fn run despache (5 funciones de skills) 2026-06-28 07:26:02 +02:00
egutierrez f8793f96ac fix(comfyui): firmas sin keyword-only para que fn run las despache
El generador de runner de fn run (cmd/fn/pyrunner.go::generatePyRunner)
parsea la signature de la funcion desde el frontmatter del .md y emite
`<param> = _args[i]` por cada parametro posicional. Cuando la firma es
keyword-only (`def f(*, ...)`), el `*` se trata como un nombre de parametro
y genera la linea invalida `* = _args[0]`, que rompe el runner con
`SyntaxError: invalid syntax` antes de ejecutar la funcion.

Se quita el separador keyword-only (`*,`) de la firma — tanto en la `def`
del .py como en el campo `signature:` del .md (la fuente que lee el
indexer y el runner) — convirtiendo los parametros keyword-only en
parametros normales con su mismo default. No cambia nombres, defaults ni
comportamiento: las llamadas con keyword siguen siendo validas.

Afecta a 5 funciones detectadas en el report 0208 §3.3, todas con
SyntaxError reproducido via `fn run <id>`:
- comfyui_fetch_civitai_image_meta
- comfyui_load_skill
- comfyui_save_skill
- comfyui_import_workflow_png
- comfyui_list_skills

Se completa ademas el fix de comfyui_interrupt_queue: el commit 643ebfb8
quito el `*,` del .py pero dejo el `*,` en el campo `signature:` del .md,
que es justo lo que lee el runner — por eso `fn run comfyui_interrupt_queue`
seguia fallando. Aqui se corrige el .md.

Verificado: tras el cambio las 6 despachan sin SyntaxError (las 4 con
primer arg requerido devuelven el `missing required arg` esperado del
runner; list_skills e interrupt_queue ejecutan `ok:true`). Tests
existentes verdes (comfyui_fetch_civitai_image_meta_test.py +
tests/test_comfyui_interrupt_queue.py: 8 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:23:59 +02:00
343 changed files with 32488 additions and 414 deletions
+204
View File
@@ -0,0 +1,204 @@
---
description: Genera en un vault Obsidian un resumen capítulo a capítulo de uno o varios libros, siguiendo el formato de notas del vault captacion_clientes (MOC de libro + una nota por capítulo + MOC de categoría, todo enlazado con wikilinks).
---
# /capitulos — resumen de libros capítulo a capítulo en Obsidian
Genera notas de estudio de un libro (o varios) en un vault Obsidian, replicando el formato
canónico del vault `captacion_clientes`: una nota MOC por libro, una nota por capítulo, y una
nota MOC de categoría que agrupa los libros. Todo enlazado con wikilinks `[[ ]]` para que
Obsidian construya el grafo.
## Argumentos
`$ARGUMENTS` contiene, en lenguaje natural, los libros a procesar y opcionalmente el destino.
Interpreta:
- **Libros** — uno o varios títulos. Pueden venir con autor ("Forecasting de Hyndman"). Si el
usuario dice "los libros que me has dicho" o similar, usa los que se recomendaron en la
conversación previa.
- **Vault destino** — si no se especifica, **PREGUNTA** antes de escribir (ver Decisiones).
Vault por defecto de ejemplo de formato: `/home/enmanuel/Obsidian/captacion_clientes`.
- **Categoría** — la subcarpeta bajo `Libros/` que agrupa los libros (ej. "Marca y Mercado",
"Datos e Inversión"). Si no se da, propón una coherente con el tema de los libros y confírmala.
- **Profundidad** — `completo` (default, como The Mom Test: idea central + puntos clave +
citas + aplicación por capítulo) o `breve` (idea central + 3 bullets por capítulo).
## Decisiones a confirmar antes de escribir (si faltan en los argumentos)
Usa `AskUserQuestion` para resolver lo que cambie el trabajo, NO inventes:
1. **Vault y categoría destino** — dónde se crean las notas.
2. **Alcance** — qué libros exactamente y cuántos (si la lista es grande, confirma si son
todos o un subconjunto; cada libro es trabajo no trivial).
3. **Enfoque de "Aplicación"** — el ángulo desde el que se escribe la sección "Aplicación a mi
negocio / a mi caso" de cada capítulo (ej. inversión cuantitativa, data-analyst, SaaS…).
El vault de captación lo orienta al negocio del usuario; mantén ese espíritu pero ajustado
al tema real de los libros.
## Estructura de archivos a crear
```
<vault>/Libros/<Categoría>/
<Categoría> - MOC.md # MOC de categoría (crear o ACTUALIZAR, no sobrescribir)
<Libro>/
<Libro> - MOC.md # MOC del libro
01 - <Título capítulo>.md # una nota por capítulo, NN zero-padded a 2 dígitos
02 - <Título capítulo>.md
...
```
- Carpeta por libro, archivo por capítulo. Nombre de capítulo: `NN - <Título>.md` con `NN`
empezando en `01`. Si el capítulo tiene título original en otro idioma, puedes incluir la
traducción entre paréntesis como en el vault (`01 - The Mom Test (El test de la madre).md`).
- Nombres de archivo sin caracteres que rompan en Obsidian (evita `/`, `:`; los paréntesis y
acentos son válidos).
## Determinar los capítulos de cada libro
Para listar los capítulos reales de un libro:
1. Usa tu conocimiento del libro si lo conoces con fiabilidad (índice real, no inventado).
2. Si no estás seguro del índice exacto, **búscalo en la web** (`WebSearch` / `WebFetch` sobre
la tabla de contenidos del libro) antes de escribir. No inventes capítulos.
3. Indica en el MOC del libro si el índice procede de una edición concreta.
**Regla dura:** nunca te inventes el número o los títulos de los capítulos. Si no puedes
verificarlos, dilo y pregunta al usuario en vez de fabricar un índice plausible.
## Plantilla — MOC del libro (`<Libro> - MOC.md`)
```markdown
---
title: <Libro> - MOC
book: <Libro>
author: <Autor>
year: <Año>
type: book-moc
tags:
- <slug-libro>
- <tema-1>
- moc
---
# <Libro> — Mapa de contenidos (MOC)
## Metadata
- **Autor:** <Autor>
- **Año:** <Año> (<edición si aplica>)
- **Subtítulo:** *<subtítulo original>* (<traducción>)
- **Tema:** <de qué va en una frase>
- **Por qué importa:** <2-3 frases sobre qué problema resuelve y para quién>
## Resumen global
<Un párrafo denso (8-15 líneas) que sintetiza la tesis del libro y recorre el hilo de los
capítulos sin enumerarlos uno a uno: cuenta el argumento completo en prosa.>
## Capítulos
1. [[01 - <Título capítulo>]]
2. [[02 - <Título capítulo>]]
...
## Aplicación a mi caso (visión transversal)
<Párrafo que conecta el libro entero con el objetivo concreto del usuario (el enfoque
confirmado en las Decisiones): qué capítulos son los más relevantes y por qué.>
```
## Plantilla — nota de capítulo (`NN - <Título>.md`)
```markdown
---
title: <Título capítulo>
book: <Libro>
author: <Autor>
chapter: <N>
type: chapter-summary
tags:
- <slug-libro>
- <tema>
---
# NN. <Título capítulo>
> Libro: [[<Libro> - MOC]]
## Idea central
<1-3 frases con la tesis del capítulo.>
## Puntos clave
- <bullet sustantivo, no genérico>
- <…>
- <…>
## Ejemplos / citas
- <ejemplo concreto del capítulo o cita textual con su traducción si es en otro idioma>
- <…>
## Aplicación a mi caso
<Párrafo concreto: cómo aplicar la idea del capítulo al caso del usuario.>
---
Anterior: [[NN-1 - <Título anterior>]] · Siguiente: [[NN+1 - <Título siguiente>]] · Índice: [[<Libro> - MOC]]
```
Notas de la plantilla:
- El primer capítulo: `Anterior: —`. El último: `Siguiente: —`. (Ver patrón en el vault.)
- La sección "Aplicación" es obligatoria y debe ser específica del caso del usuario, no un
consejo genérico. Es lo que da valor a estas notas frente a un resumen cualquiera.
- En profundidad `breve`, omite "Ejemplos / citas" y deja "Puntos clave" en 3 bullets.
## Plantilla — MOC de categoría (`<Categoría> - MOC.md`)
Si ya existe, **ACTUALÍZALO** añadiendo los libros nuevos a la sección que corresponda (no lo
reescribas perdiendo lo previo). Si no existe, créalo:
```markdown
---
title: <Categoría> — MOC
type: moc
tags:
- libros
- <tema-categoría>
---
# <Categoría> — Mapa de contenidos
<Frase que describe el tema común de los libros de esta categoría.>
Cada libro tiene su propia nota MOC con el índice de capítulos enlazados.
## <Sub-tema 1>
- [[<Libro A> - MOC]] — <Autor>. <una línea de qué aporta>.
- [[<Libro B> - MOC]] — <Autor>. <…>.
## Orden de lectura recomendado
1. **<Libro>** — <por qué primero>.
2. ...
```
## Flujo de ejecución
1. Parsear `$ARGUMENTS`: libros, vault, categoría, profundidad, enfoque.
2. Resolver decisiones faltantes con `AskUserQuestion`.
3. Para cada libro: verificar el índice real de capítulos (conocimiento fiable o WebSearch).
4. Crear carpeta del libro. Escribir el MOC del libro y todas las notas de capítulo con
wikilinks y navegación correctos.
5. Crear o actualizar el MOC de categoría enlazando los libros nuevos.
6. **Paralelización:** si son varios libros, cada libro es independiente (carpetas disjuntas).
En modo orquestador, lanza un ejecutor por libro (o por lote de libros) escribiendo en
carpetas distintas del mismo vault. Cada ejecutor escribe SOLO su carpeta de libro; el MOC
de categoría lo actualiza UN único agente al final (o el orquestador) para evitar que dos
ejecutores editen el mismo archivo a la vez.
7. Reportar: lista de archivos creados (MOC + nº de capítulos por libro) y la ruta del vault
para abrirlo en Obsidian.
## Gotchas
- **El vault es artefacto local** (gitignored en fn_registry, symlink a `~/Obsidian/<vault>`).
Escribir notas NO toca el repo `fn_registry`. Si el vault es su propio repo git, NO commitees
desde varios ejecutores a la vez (race): deja el commit/sync al usuario o a un único paso final.
- **No sobrescribas** un MOC de categoría existente ni notas de capítulo ya escritas a mano sin
confirmarlo. Ante colisión de nombre, pregunta.
- **Índices inventados = bug.** Verifica los capítulos reales antes de escribir.
- **Wikilinks deben resolver:** el texto dentro de `[[ ]]` debe coincidir exactamente con el
nombre de archivo (sin extensión). Un typo rompe el enlace en Obsidian.
+105
View File
@@ -0,0 +1,105 @@
---
description: EDA (exploratory data analysis) de una tabla o de una base entera con el grupo `eda` del registry. Perfila, escribe el report (JSON + Markdown + PDF móvil) y monta un analysis Jupyter lanzado en el navegador colaborativo y ejecutado en vivo por Claude.
---
# /eda — Exploratory Data Analysis con el grupo `eda`
Cuando Enmanuel pide un EDA ("hazme un EDA de X", "analiza esta tabla", "qué hay en estos datos"), **no escribas análisis inline**: usa el grupo de capacidad `eda` del registry, escribe los reports y monta el analysis Jupyter en su navegador colaborativo, ejecutando las celdas tú mismo en vivo. Respeta la memoria `eda-workflow-registry` y la regla `.claude/rules/notebook_collaboration.md`.
Página madre del grupo: `docs/capabilities/eda.md` (léela primero para cargar el cluster entero).
## Uso
```
/eda /ruta/datos.duckdb tabla # EDA de una tabla DuckDB
/eda /ruta/datos.csv # CSV/Parquet → cargar a DuckDB y perfilar
/eda postgresql://user:pass@host:5432/db tabla # EDA de una tabla PostgreSQL (backend="postgres")
/eda /ruta/datos.duckdb --all # EDA de TODA la base (todas las tablas + FK + join graph)
/eda /ruta/datos.duckdb ventas --series --pdf # con análisis de serie temporal + PDF móvil
```
`$ARGUMENTS` lleva la fuente y, opcionalmente, la tabla y flags. Interpreta:
- **Fuente**: ruta a `.duckdb`/`.csv`/`.parquet`, o un DSN PostgreSQL (`postgresql://...` o `postgres://...`).
- **Tabla**: nombre de la tabla. Si no se da y la fuente es un único archivo CSV/Parquet, usa su nombre base. Si se pide "toda la base" / `--all`, usa `profile_database`.
- **Flags** (actívalos según lo que pida el usuario; pregunta solo si es ambiguo y costoso):
- `--models``run_models=True` (PCA/KMeans/IsolationForest/normalidad).
- `--llm``run_llm=True` (1 call LLM sobre el perfil agregado).
- `--series``run_series=True` (estacionariedad ADF+KPSS, ACF/PACF, STL, retornos por columna numérica).
- `--pdf``emit_pdf=True` (PDF A5 legacy de `render_eda_pdf`, legible en móvil).
- `--legacy-only` → emite SOLO el PDF legacy (sin AutomaticEDA), para casos en que solo se quiera el PDF rápido.
Por defecto, **un EDA completo emite SIEMPRE el informe AutomaticEDA en sus dos formatos: PDF (A5 móvil) Y PPTX (16:9 para compartir)** con los 11 capítulos poblados (portada, overview, distribuciones, calidad, correlaciones, modelos, series, geoespacial, agregación, interpretación LLM). Usa el pipeline `render_automatic_eda` (o `profile_table(emit_automatic=True)`), que activa `run_models` y `run_series` para que los capítulos de modelos/series/geoespacial/agregación salgan poblados. Deja `run_llm` para cuando el usuario lo pida o interese la interpretación semántica + narrativa por capítulo (es la única parte que gasta tokens del modelo).
## Reglas duras
1. **Registry-first**: invoca las funciones del grupo `eda`, no reescribas lógica de perfilado ni de gráficos inline (regla `registry_first.md`).
2. **CSV/Parquet/Excel** entran cargándolos antes a DuckDB (`read_csv_auto`/`read_parquet`/`read_xlsx`) — DuckDB es el motor por defecto. No traigas la tabla entera a RAM.
3. **Secretos**: si la fuente es un DSN PostgreSQL con credenciales, NO las imprimas en los reports ni en el notebook; resuélvelas vía `resolve_pg_dsn`/`pass` cuando aplique.
4. **El report es un artefacto local**: vive en `reports/` (gitignored), no se sube a Gitea ni se versiona. Compartir = pasar la ruta (regla `reports.md`).
5. **Entrega las salidas**: el informe **AutomaticEDA PDF + PPTX** (siempre, con `render_automatic_eda` / `emit_automatic=True`) + (opcional) JSON sidecar + Markdown + PDF legacy + **notebook Jupyter colaborativo ejecutado en vivo**. Comparte las rutas de PDF y PPTX.
## Paso 1 — Perfilar y escribir los reports
Una tabla (caso normal):
```bash
PYTHONPATH=python/functions python/.venv/bin/python3 - <<'PYEOF'
from pipelines.render_automatic_eda import render_automatic_eda
# Informe AutomaticEDA COMPLETO one-shot: perfil + ctx (datos crudos) + PDF + PPTX
# con los 11 capítulos poblados (clusters pintados, evolución temporal, mapa,
# tablas de agregación). run_llm=True añade la narrativa LLM por capítulo.
r = render_automatic_eda(
"/ruta/datos.duckdb", "ventas",
run_models=True, run_series=True, run_llm=False, out_dir="reports",
)
print("status:", r["status"])
print("pdf: ", r["pdf_path"], "(", r["n_pages"], "págs )")
print("pptx: ", r["pptx_path"], "(", r["n_slides"], "slides )")
print("manifest:", r["manifest_path"])
PYEOF
```
Si además quieres el report Markdown + JSON sidecar y/o el PDF legacy junto al
AutomaticEDA, usa `profile_table(emit_automatic=True, emit_pdf=True, write_report=True)`:
emite todo a la vez (`report_md_path`, `report_json_path`, `pdf_path` legacy,
`aeda_pdf_path`, `aeda_pptx_path`, `aeda_manifest_path`).
Una base entera (todas las tablas + relaciones FK):
```bash
PYTHONPATH=python/functions python/.venv/bin/python3 - <<'PYEOF'
from pipelines.profile_database import profile_database
r = profile_database("/ruta/datos.duckdb")
print(r["db_profile"]["join_graph"]["mermaid"])
PYEOF
```
Lee el Markdown resultante y resume a Enmanuel lo esencial: forma, calidad, correlaciones fuertes (ya corregidas por FDR), series no estacionarias, transformaciones sugeridas y avisos exploratorios.
## Paso 2 — Notebook Jupyter colaborativo, ejecutado en vivo por Claude
Sigue la memoria `eda-workflow-registry` y la regla `notebook_collaboration.md`:
1. Genera el notebook con `build_eda_notebook` (mismo perfil de la tabla):
```bash
PYTHONPATH=python/functions python/.venv/bin/python3 - <<'PYEOF'
from datascience import build_eda_notebook
build_eda_notebook("/ruta/datos.duckdb", "ventas",
"analysis/eda_ventas/notebooks/01_eda.ipynb", run_models=True)
PYEOF
```
(o crea un analysis dedicado con `fn run init_jupyter_analysis eda_ventas duckdb` y escribe el notebook dentro de `notebooks/`).
2. Confirma que hay Jupyter colaborativo activo con `jupyter_discover` (o lánzalo con el `run-jupyter-lab.sh` del analysis) y **ábrelo en el navegador colaborativo** para que Enmanuel lo vea en vivo.
3. **Ejecuta tú las celdas** (no se las dejes para que las corra él): usa las funciones del dominio `notebook` (`jupyter_exec` append+execute / `jupyter_read`) descritas en `notebook_collaboration.md`, o el MCP `jupyter` si está conectado en la sesión del analysis. Ejecuta de arriba a abajo, comenta cada bloque relevante y deja el notebook navegable.
## Notas
- El `TableProfile` lleva ahora, además del perfilado base y las correlaciones con FDR: `series` (por columna numérica, con `run_series`), `reexpression` por columna numérica (escalera de Tukey) y `caveats` (siempre, avisos exploratorios). El Markdown y el PDF renderizan estas secciones automáticamente cuando están presentes.
- El informe **AutomaticEDA** (`render_automatic_eda` / `emit_automatic=True`) emite el MISMO documento por capítulos a **PDF (A5 móvil)** y **PPTX (16:9)** con garantía de no-corte (texto envuelto, tablas partidas repitiendo cabecera, figuras escaladas) y negrita real (`**texto**`). Escribe `automatic_eda_manifest.json` con la versión de cada capítulo. Los capítulos modelos/series/geoespacial/agregación se pueblan con los datos crudos que `build_eda_render_ctx` muestrea de la base (no se traen tablas enteras a RAM).
- El PDF legacy (`emit_pdf`, `render_eda_pdf`) sigue disponible y es independiente del AutomaticEDA (A5 vertical, gráficos Tufte). Se escribe junto al Markdown en `reports/`.
- `run_series` ordena por la primera columna datetime si existe; si no, por el orden físico de filas. Necesita ≥8 puntos válidos por columna.
- Fuentes: DuckDB (CSV/Parquet/Excel cargados antes) y PostgreSQL (`backend="postgres"`). `profile_database` (multi-tabla + FK) es solo DuckDB por ahora.
+3 -2
View File
@@ -31,12 +31,13 @@ Diferencia con `dev/flows/`:
**Fase 1 (manual via Claude):**
El agente lee `dev/issues/*.md`, parsea frontmatter YAML con `yaml.safe_load`, aplica el filtro, imprime tabla.
El agente lee `dev/issues/**/*.md` (recursivo: incluye subcarpetas por dominio como `dev/issues/kanban/`, `dev/issues/cpp/`, ... excluyendo `completed/`), parsea frontmatter YAML con `yaml.safe_load`, aplica el filtro, imprime tabla.
```python
import yaml, pathlib, re
issues = []
for f in pathlib.Path("dev/issues").glob("*.md"):
for f in pathlib.Path("dev/issues").glob("**/*.md"):
if f.parent.name == "completed": continue
if f.name in {"README.md", "template.md"}: continue
txt = f.read_text()
m = re.match(r"^---\n(.*?)\n---", txt, re.S)
+3 -1
View File
@@ -9,7 +9,9 @@
"enabledMcpjsonServers": [
"registry",
"jupyter",
"orchestrator"
"orchestrator",
"godot",
"ardour"
],
"hooks": {
"PreToolUse": [
+35 -14
View File
@@ -3,11 +3,11 @@ name: launch_fleetclaude
kind: function
lang: bash
domain: infra
version: "1.5.0"
version: "1.6.0"
purity: impure
signature: "launch_fleetclaude [--cwd <dir>] [--bin <path>] [--session <name>] [--reuse] [--cols <n>]"
description: "Entrypoint de FleetView: abre una ventana kitty con una sesion tmux (socket aislado por perfil) de dos panes (TUI fleetview a la izquierda, claude --dangerously-skip-permissions a la derecha) para centralizar la flota de Claudes. El pane de la TUI corre dentro del bucle supervisor supervise_fleetview_tui, que la relanza si muere (crash/panic/kill), asi el panel de control NUNCA se pierde. Soporta PERFILES multiples: sin --session/--reuse cada invocacion abre un perfil nuevo (fleet, fleet2, fleet3, ...) con su propia flota; inyecta FLEET_SOCKET/FLEET_SESSION a la TUI para que cada panel vea solo sus Claudes. Instala atajos alt+flechas/alt+enter/alt+n que controlan la TUI desde cualquier pane, y fija el ancho del sidebar con hooks."
tags: [claude-fleet, infra, kitty, tmux, claude, fleetview, launcher]
description: "Entrypoint de FleetView: abre una ventana de terminal con una sesion tmux (socket aislado por perfil) de dos panes (TUI fleetview a la izquierda, claude --dangerously-skip-permissions a la derecha) para centralizar la flota de Claudes. La terminal se AUTO-DETECTA sin config por PC: kitty si esta instalado y hay display ($DISPLAY/$WAYLAND_DISPLAY), si no Windows Terminal (wt.exe) en WSL adjuntando via wsl.exe. El pane de la TUI corre dentro del bucle supervisor supervise_fleetview_tui, que la relanza si muere (crash/panic/kill), asi el panel de control NUNCA se pierde. Soporta PERFILES multiples: sin --session/--reuse cada invocacion abre un perfil nuevo (fleet, fleet2, fleet3, ...) con su propia flota; inyecta FLEET_SOCKET/FLEET_SESSION a la TUI para que cada panel vea solo sus Claudes. Instala atajos alt+flechas/alt+enter/alt+n que controlan la TUI desde cualquier pane, y fija el ancho del sidebar con hooks."
tags: [claude-fleet, infra, kitty, tmux, claude, fleetview, launcher, wsl, windows-terminal]
params:
- name: --cwd
desc: "Directorio de trabajo de ambos panes tmux. Opcional. Default: raiz del repo fn_registry, derivada dinamicamente via git rev-parse desde la ubicacion del script (sin hardcodear paths de usuario)."
@@ -19,7 +19,7 @@ params:
desc: "Reattach al perfil principal 'fleet' en vez de abrir uno nuevo. Opcional. Recupera el comportamiento idempotente clasico (volver a invocar NO duplica la flota, reusa la existente)."
- name: --cols
desc: "Ancho en columnas del pane izquierdo (la TUI). Opcional. Default: 40."
output: "Crea/reutiliza una sesion tmux detached con dos panes y lanza una ventana kitty 'FleetView' adjunta a ella, desacoplada del shell padre (setsid). Imprime el estado por stdout. Sin valor de retorno; exit 0 en exito."
output: "Crea/reutiliza una sesion tmux detached con dos panes y lanza una ventana de terminal 'FleetView' adjunta a ella (kitty o Windows Terminal segun auto-deteccion), desacoplada del shell padre. Imprime el estado por stdout. Sin valor de retorno; exit 0 en exito."
uses_functions:
- supervise_fleetview_tui_bash_infra
uses_types: []
@@ -49,7 +49,7 @@ launch_fleetclaude --reuse
launch_fleetclaude --session trabajo --cols 50
```
Tras invocarlo aparece una ventana kitty titulada `FleetView (<perfil>)` con dos
Tras invocarlo aparece una ventana de terminal titulada `FleetView (<perfil>)` con dos
panes lado a lado: a la izquierda la TUI `fleetview`, a la derecha una sesion de
`claude --dangerously-skip-permissions`. Cada perfil es un socket+sesion tmux
aislados con su propia flota: puedes tener varias FleetView abiertas a la vez.
@@ -78,12 +78,24 @@ al retomar el trabajo en el repo `fn_registry`.
`respawn-pane` de alt+R y los Claude nuevos hereden el socket). `main.go` los
lee con fallback a `fleet`. Por eso cada panel ve SOLO los Claude de su perfil
(cruza la lista del sistema con los panes de su socket).
- **Auto-deteccion de terminal (sin config por PC)**: en la ruta ventana-nueva el
launcher elige terminal solo. (1) `kitty` instalado **y** display usable
(`$DISPLAY`/`$WAYLAND_DISPLAY`) → kitty (escritorio Linux nativo o WSLg con
kitty). (2) Si no, WSL con `wt.exe` en el PATH → Windows Terminal ejecutando
`wsl.exe [-d $WSL_DISTRO_NAME] -- bash -lic 'tmux -L <perfil> attach ...'`.
(3) Ninguna → error con las salidas posibles. Asi el MISMO `fleetclaude`
funciona en un PC con kitty y en otro WSL sin kitty, cada uno elige su
terminal. Causa raiz del sintoma "se lanza la flota pero no se ve": kitty no
instalado en WSL hacia que la sesion tmux se creara sin ventana que la mostrara.
- **Dentro de tmux abre ventana nueva**: si invocas `fleetclaude` desde dentro de
una sesion tmux (`$TMUX` definido), NO hace `attach` anidado (rompe / avisa de
nesting); cae a la ruta kitty y abre una ventana nueva. Fuera de tmux y con
TTY, reutiliza la terminal actual con `exec tmux attach`.
- **kitty detached (setsid)**: la ventana se lanza con `setsid ... &` para
sobrevivir al cierre de la terminal que la invoco. No bloquea al shell padre.
nesting); cae a la ruta ventana-nueva (auto-deteccion de terminal). Fuera de
tmux y con TTY, reutiliza la terminal actual con `exec tmux attach`.
- **kitty detached (setsid)**: la ventana kitty se lanza con `setsid ... &` para
sobrevivir al cierre de la terminal que la invoco. La ventana de Windows
Terminal (wt.exe) ya es un proceso Windows independiente del arbol Linux, asi
que sobrevive sola (se lanza con `&`+`disown` desde un subshell con cwd `/mnt/c`
para evitar el warning de wt.exe por cwd UNC `\\wsl.localhost\...`).
- **TUI bajo supervisor (auto-respawn)**: el pane izquierdo NO corre un
`exec fleetview` de una sola vida, sino `supervise_fleetview_tui` (bucle que
relanza la TUI si muere por crash/panic/kill). Asi el panel de control nunca se
@@ -116,14 +128,23 @@ al retomar el trabajo en el repo `fn_registry`.
- **Ancho del sidebar via hooks**: `client-resized` y `window-layout-changed`
re-fijan el pane 0 (TUI) a `--cols` columnas, porque el `attach` de kitty y el
conmutar de Claude redistribuyen el espacio.
- **tmux siempre, kitty solo sin TTY**: `tmux` es obligatorio (aborta != 0 si
falta). `kitty` solo se necesita en la ruta sin-TTY (atajo de escritorio, cron,
script), donde abre una ventana nueva. Invocado desde una terminal interactiva
(el caso normal del alias `fleetclaude`), reutiliza la terminal actual con
`exec tmux attach` y NO necesita kitty — util en WSL u hosts sin kitty.
- **tmux siempre; terminal (kitty/wt.exe) solo sin TTY**: `tmux` es obligatorio
(aborta != 0 si falta). Una terminal nueva (kitty o Windows Terminal) solo se
necesita en la ruta sin-TTY (dentro de tmux, atajo de escritorio, cron, script),
donde abre una ventana nueva. Invocado desde una terminal interactiva fuera de
tmux (el caso normal del alias `fleetclaude`), reutiliza la terminal actual con
`exec tmux attach` y no necesita ni kitty ni wt.exe.
## Capability growth log
- v1.6.0 (2026-06-29) — **auto-deteccion de terminal (kitty ↔ Windows Terminal)**.
La ruta ventana-nueva ya no asume kitty: elige terminal segun el host. kitty si
esta instalado y hay display (`$DISPLAY`/`$WAYLAND_DISPLAY`); si no, en WSL abre
Windows Terminal (`wt.exe`) ejecutando `wsl.exe [-d $WSL_DISTRO_NAME] -- bash
-lic 'tmux ... attach'`. Mismo `fleetclaude` en un PC con kitty y en otro WSL
sin kitty. Arregla el sintoma "se lanza la flota pero no se ve": en WSL sin
kitty la sesion tmux se creaba pero ninguna ventana la mostraba. wt.exe se
lanza desde un subshell con cwd `/mnt/c` para evitar el warning por cwd UNC.
- v1.5.0 (2026-06-24) — **auto-respawn de la TUI**. El pane izquierdo ya no corre
`exec fleetview` (una sola vida), sino el bucle supervisor
`supervise_fleetview_tui`, que relanza la TUI si muere (crash/panic/kill de su
+44 -14
View File
@@ -294,31 +294,61 @@ USAGE
$T set-hook -g window-layout-changed "resize-pane -t $left_pane -x $cols"
# -----------------------------------------------------------------------
# Lanzar kitty adjuntando la sesion, DESACOPLADA del shell padre con
# setsid, para que no muera al cerrar la terminal invocadora.
# (Mismo patron que reboot_all_claudes para relanzar terminales.)
# Adjuntar la sesion en una terminal, DESACOPLADA del shell padre para que
# no muera al cerrar la terminal invocadora.
# -----------------------------------------------------------------------
# Adjuntar la sesion:
# - Terminal interactiva y FUERA de tmux: convertir ESA terminal en el
# panel FleetView (exec reemplaza el proceso; al hacer detach vuelve la
# shell). Asi `fleetclaude` no abre otra ventana: usa la actual.
# - DENTRO de tmux (o sin TTY: atajo de escritorio, cron, script): abrir
# una ventana kitty nueva desacoplada (setsid). No hacemos `attach`
# una ventana de terminal NUEVA desacoplada. No hacemos `attach`
# anidado dentro de otra sesion tmux (rompe / da el warning de nesting).
if [ -t 0 ] && [ -t 1 ] && [ -z "${TMUX:-}" ]; then
exec tmux -L "$session" attach -t "$session"
fi
# Ruta ventana-nueva: necesitamos kitty para abrirla.
if ! command -v kitty >/dev/null 2>&1; then
echo "launch_fleetclaude: kitty no esta instalado (necesario para abrir ventana nueva)." >&2
echo "launch_fleetclaude: lanzalo desde una terminal interactiva fuera de tmux, o instala kitty." >&2
return 1
fi
setsid kitty --title "FleetView ($session)" -e tmux -L "$session" attach -t "$session" </dev/null >/dev/null 2>&1 &
disown 2>/dev/null || true
echo "launch_fleetclaude: ventana kitty 'FleetView ($session)' adjunta al perfil '$session'."
return 0
# -----------------------------------------------------------------------
# Ruta ventana-nueva: AUTO-DETECTAR la terminal disponible (sin config por
# PC). El mismo `fleetclaude` funciona en un escritorio Linux con kitty y en
# un WSL sin kitty pero con Windows Terminal.
# 1. kitty instalado + display usable ($DISPLAY/$WAYLAND_DISPLAY) -> kitty
# (escritorio Linux nativo, o WSLg con kitty instalado).
# 2. WSL con wt.exe alcanzable -> Windows Terminal ejecutando wsl.exe que
# adjunta la sesion tmux (PCs WSL sin kitty: la ventana kitty nunca
# aparece sin una terminal Linux real, por eso "se lanza pero no se ve").
# 3. Ninguna -> error claro con las dos salidas posibles.
# -----------------------------------------------------------------------
if command -v kitty >/dev/null 2>&1 && [[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]]; then
setsid kitty --title "FleetView ($session)" -e tmux -L "$session" attach -t "$session" </dev/null >/dev/null 2>&1 &
disown 2>/dev/null || true
echo "launch_fleetclaude: ventana kitty 'FleetView ($session)' adjunta al perfil '$session'."
return 0
fi
if command -v wt.exe >/dev/null 2>&1; then
# bash -lic <attach> dentro de wsl.exe: login+interactive para que tmux y
# el PATH del perfil esten disponibles en la ventana de Windows Terminal.
local attach_cmd
attach_cmd="tmux -L $(printf '%q' "$session") attach -t $(printf '%q' "$session")"
local distro="${WSL_DISTRO_NAME:-}"
local wsl_args=(wsl.exe)
[[ -n "$distro" ]] && wsl_args+=(-d "$distro")
wsl_args+=(-- bash -lic "$attach_cmd")
# cd a una ruta Windows (/mnt/c) evita el warning de wt.exe por cwd UNC
# (\\wsl.localhost\...). El cwd real de los panes lo fija la sesion tmux.
( cd /mnt/c 2>/dev/null || cd /
wt.exe new-tab --title "FleetView ($session)" "${wsl_args[@]}" </dev/null >/dev/null 2>&1 &
disown 2>/dev/null || true )
echo "launch_fleetclaude: Windows Terminal 'FleetView ($session)' adjunta al perfil '$session' (WSL distro '${distro:-default}')."
return 0
fi
echo "launch_fleetclaude: no hay terminal para abrir una ventana nueva." >&2
echo "launch_fleetclaude: - escritorio Linux: instala kitty y exporta DISPLAY/WAYLAND_DISPLAY." >&2
echo "launch_fleetclaude: - WSL: usa Windows Terminal (wt.exe debe estar en el PATH)." >&2
echo "launch_fleetclaude: - o lanza fleetclaude desde una terminal interactiva fuera de tmux." >&2
return 1
}
# Permitir ejecutar el archivo directamente (no solo como funcion sourced).
+50 -34
View File
@@ -18,6 +18,7 @@ type pyParam struct {
Default string // empty if required
IsKwargs bool // **kwargs
IsRegistry bool // type is a registry type (needs factory)
KwOnly bool // declared after a bare "*" or "*args" — must be passed by keyword
}
// pyFactory links a registry type to the function that creates it.
@@ -45,12 +46,21 @@ func parsePySignature(sig string) []pyParam {
// Split by comma, respecting nested brackets
parts := splitParams(raw)
var params []pyParam
kwOnly := false
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" || part == "self" || part == "cls" {
continue
}
// A bare "*" (PEP 3102) or "*args" var-positional marks the start of
// keyword-only params. Neither maps cleanly to positional CLI args, so
// skip the marker itself and flag every following param as keyword-only.
if part == "*" || (strings.HasPrefix(part, "*") && !strings.HasPrefix(part, "**")) {
kwOnly = true
continue
}
p := parseSingleParam(part)
p.KwOnly = kwOnly
params = append(params, p)
}
return params
@@ -189,11 +199,19 @@ func generatePyRunner(fn *registry.Function, db *registry.DB, registryRoot strin
// Classify params
var factoryImports []string // import lines for factories
var factorySetup []string // code to create factory objects
var argLines []string // code to parse CLI args
var callArgs []string // arguments to pass to the function
var bodyLines []string // code that fills _call_args / _call_kwargs
cliArgIdx := 0
// emitCall appends one param to _call_args (positional) or _call_kwargs
// (keyword-only). indent prefixes the line (for params read inside an `if`).
emitCall := func(p pyParam, indent string) string {
if p.KwOnly {
return fmt.Sprintf("%s_call_kwargs[%q] = %s", indent, p.Name, p.Name)
}
return fmt.Sprintf("%s_call_args.append(%s)", indent, p.Name)
}
for _, p := range params {
if p.IsKwargs {
// Skip **kwargs for now — can't auto-resolve from CLI
@@ -235,27 +253,35 @@ func generatePyRunner(fn *registry.Function, db *registry.DB, registryRoot strin
fmt.Sprintf("%s = %s(%s)", p.Name, factory.FuncName,
strings.Join(factoryArgs, ", ")))
callArgs = append(callArgs, p.Name)
// Factory objects are always present (required).
bodyLines = append(bodyLines, emitCall(p, ""))
} else {
// Primitive type — from CLI args
// Primitive type — from CLI args.
if p.Default != "" {
// Optional param with default
argLines = append(argLines,
fmt.Sprintf("%s = _args[%d] if len(_args) > %d else %s",
p.Name, cliArgIdx, cliArgIdx, convertDefault(p.Type, p.Default)))
argLines = append(argLines,
convertArg(p.Name, p.Type, true))
// Optional: only pass when the CLI arg is present. When absent we
// DON'T replicate the signature default (it may reference a module
// constant that doesn't exist in this runner) — we simply omit the
// argument so the function applies its own native default.
bodyLines = append(bodyLines,
fmt.Sprintf("if len(_args) > %d:", cliArgIdx))
bodyLines = append(bodyLines,
fmt.Sprintf(" %s = _args[%d]", p.Name, cliArgIdx))
if conv := convertArg(p.Name, p.Type, true); conv != "" {
bodyLines = append(bodyLines, " "+conv)
}
bodyLines = append(bodyLines, emitCall(p, " "))
} else {
// Required param
argLines = append(argLines,
// Required param.
bodyLines = append(bodyLines,
fmt.Sprintf("if len(_args) <= %d: sys.exit('error: missing required arg: %s (%s)')",
cliArgIdx, p.Name, p.Type))
argLines = append(argLines,
bodyLines = append(bodyLines,
fmt.Sprintf("%s = _args[%d]", p.Name, cliArgIdx))
argLines = append(argLines,
convertArg(p.Name, p.Type, false))
if conv := convertArg(p.Name, p.Type, false); conv != "" {
bodyLines = append(bodyLines, conv)
}
bodyLines = append(bodyLines, emitCall(p, ""))
}
callArgs = append(callArgs, p.Name)
cliArgIdx++
}
}
@@ -289,18 +315,18 @@ func generatePyRunner(fn *registry.Function, db *registry.DB, registryRoot strin
sb.WriteString("\n")
}
// Arg parsing
if len(argLines) > 0 {
sb.WriteString("# --- parse CLI args ---\n")
for _, line := range argLines {
sb.WriteString(line + "\n")
}
sb.WriteString("\n")
// Arg parsing — build the positional/keyword argument collections.
sb.WriteString("# --- parse CLI args ---\n")
sb.WriteString("_call_args = []\n")
sb.WriteString("_call_kwargs = {}\n")
for _, line := range bodyLines {
sb.WriteString(line + "\n")
}
sb.WriteString("\n")
// Call
sb.WriteString("# --- execute ---\n")
sb.WriteString(fmt.Sprintf("_result = %s(%s)\n", fn.Name, strings.Join(callArgs, ", ")))
sb.WriteString(fmt.Sprintf("_result = %s(*_call_args, **_call_kwargs)\n", fn.Name))
sb.WriteString("\n")
// Output
@@ -365,16 +391,6 @@ func convertArg(name, typ string, _ bool) string {
}
}
// convertDefault ensures the default value is valid Python for the given type.
func convertDefault(_, def string) string {
// Most defaults from the signature are already valid Python
// Just handle the None case for Optional types
if def == "None" || def == "" {
return "None"
}
return def
}
// pythonList creates a Python list literal from strings: ["a", "b", "c"]
func pythonList(items []string) string {
quoted := make([]string, len(items))
+141
View File
@@ -0,0 +1,141 @@
package main
import (
"os"
"os/exec"
"strings"
"testing"
"fn-registry/registry"
)
// Signature with a bare "*" (PEP 3102) separating positional from keyword-only
// params. This is the shape that used to make fn run emit "* = _args[3]".
const kwOnlySig = "def add_event_dav(summary: str, start: str, end: str = '', *, location: str = '', all_day: bool = False) -> dict"
func TestParsePySignatureBareStarKeywordOnly(t *testing.T) {
params := parsePySignature(kwOnlySig)
// The bare "*" marker must never surface as a real parameter.
for _, p := range params {
if p.Name == "*" {
t.Fatalf("bare '*' leaked as a param: %+v", params)
}
}
want := map[string]bool{ // name -> expected KwOnly
"summary": false,
"start": false,
"end": false,
"location": true,
"all_day": true,
}
if len(params) != len(want) {
t.Fatalf("got %d params, want %d: %+v", len(params), len(want), params)
}
for _, p := range params {
kw, ok := want[p.Name]
if !ok {
t.Errorf("unexpected param %q", p.Name)
continue
}
if p.KwOnly != kw {
t.Errorf("param %q KwOnly=%v, want %v", p.Name, p.KwOnly, kw)
}
}
}
func TestGeneratePyRunnerKeywordOnlyValid(t *testing.T) {
fn := &registry.Function{
Name: "add_event_dav",
Lang: "py",
FilePath: "python/functions/pipelines/add_event_dav.py",
Signature: kwOnlySig,
}
// All params are primitive, so no factory lookup happens and db is unused.
script, err := generatePyRunner(fn, nil, "")
if err != nil {
t.Fatalf("generatePyRunner: %v", err)
}
if strings.Contains(script, "* = _args") {
t.Fatalf("runner emitted invalid syntax '* = _args':\n%s", script)
}
// The signature default DEFAULT_BASE_URL (a module constant) must NOT be
// replicated into the runner — that NameErrors at runtime.
if strings.Contains(script, "DEFAULT_BASE_URL") {
t.Errorf("runner replicated non-literal default DEFAULT_BASE_URL:\n%s", script)
}
// Required positionals are appended; keyword-only optionals go to kwargs.
for _, want := range []string{
"_call_args.append(summary)",
"_call_args.append(start)",
`_call_kwargs["location"] = location`,
`_call_kwargs["all_day"] = all_day`,
"_result = add_event_dav(*_call_args, **_call_kwargs)",
} {
if !strings.Contains(script, want) {
t.Errorf("missing %q in generated runner:\n%s", want, script)
}
}
// The generated runner must itself be valid Python (compile, don't run).
mustCompilePython(t, script)
}
// mustCompilePython checks the script parses as valid Python via py_compile.
func mustCompilePython(t *testing.T, script string) {
t.Helper()
f, err := os.CreateTemp(t.TempDir(), "runner_*.py")
if err != nil {
t.Fatalf("temp file: %v", err)
}
if _, err := f.WriteString(script); err != nil {
t.Fatalf("write: %v", err)
}
f.Close()
py := pythonBinForTest()
out, err := exec.Command(py, "-m", "py_compile", f.Name()).CombinedOutput()
if err != nil {
t.Fatalf("generated runner is not valid Python (%s): %v\n%s", py, err, out)
}
}
// pythonBinForTest prefers the project venv, falling back to python3 on PATH.
func pythonBinForTest() string {
for _, c := range []string{"../../python/.venv/bin/python3", "python3"} {
if c == "python3" {
return c
}
if _, err := os.Stat(c); err == nil {
return c
}
}
return "python3"
}
// A "*args" var-positional marker must behave like the bare "*": skipped, and
// everything after it treated as keyword-only.
func TestParsePySignatureVarargsKeywordOnly(t *testing.T) {
sig := "def f(a: str, *args, b: int = 0) -> dict"
params := parsePySignature(sig)
for _, p := range params {
if strings.HasPrefix(p.Name, "*") {
t.Fatalf("'*args' marker leaked as a param: %+v", params)
}
}
if len(params) != 2 {
t.Fatalf("got %d params, want 2: %+v", len(params), params)
}
got := map[string]bool{}
for _, p := range params {
got[p.Name] = p.KwOnly
}
if got["a"] != false || got["b"] != true {
t.Errorf("KwOnly mismatch: a=%v (want false), b=%v (want true)", got["a"], got["b"])
}
}
@@ -11,7 +11,7 @@ blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
# 0051 — Funciones pendientes del pipeline de extraccion (NER+RE+OpenIE)
@@ -13,7 +13,7 @@ blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
# 0054 — deploy_server: refactor registry-first (SSH/systemd/rsync/health/docker-compose)
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
# 0055 — docker_tui: refactor para usar funciones docker_* del registry
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
# 0056 — audit_uses_functions: detectar imports Python anidados (`from pkg.subpkg import X`)
+1 -1
View File
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
# 0057 — audit_uses_functions: mejorar deteccion de simbolos Go con abreviaturas
@@ -11,7 +11,7 @@ blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
# 0060 — `fn doctor secrets`: scan de secrets en TODOS los repos
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
# 0061 — Integrar `notify_telegram` en deploy_server + bucle reactivo
@@ -7,8 +7,7 @@ domain:
- registry-quality
scope: registry-only
priority: alta
depends:
- "0071f"
depends: ["0071f"]
blocks: []
related: []
created: 2026-05-10
+1 -2
View File
@@ -7,8 +7,7 @@ domain:
- registry-quality
scope: registry-only
priority: media
depends:
- "0071f"
depends: ["0071f"]
blocks: []
related: []
created: 2026-05-10
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-10
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
## Contexto
+1 -1
View File
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-10
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
## Contexto
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-10
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
## Sintoma
+1 -1
View File
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-10
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
## Sintoma
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
# 0100 — Migrar frontmatter inline a YAML canonico en dev/issues/
@@ -16,7 +16,7 @@ related:
- "0103"
created: 2026-05-17
updated: 2026-05-17
tags: [slash-command, dispatch, type-aware]
tags: [slash-command, dispatch, type-aware, ausente-ready]
---
# 0104 — `/fix-issue` type-aware dispatch
+1 -1
View File
@@ -16,7 +16,7 @@ related:
- "0107"
created: 2026-05-17
updated: 2026-05-17
tags: [modules, versioning, codegen, fail-loud]
tags: [modules, versioning, codegen, fail-loud, ausente-ready]
---
# 0107e — Version pinning + codegen fail-loud
@@ -15,12 +15,7 @@ related:
- "0109"
created: 2026-05-17
updated: 2026-05-17
tags:
- skill-tree
- cpp
- imgui
- dashboard
- gamification
tags: [ausente-ready, skill-tree, cpp, imgui, dashboard, gamification]
---
# 0109k — Dashboard panel
+1 -7
View File
@@ -16,13 +16,7 @@ related:
- "0106"
created: 2026-05-18
updated: 2026-05-18
tags:
- service
- go
- http
- issues
- flows
- api
tags: [ausente-ready, service, go, http, issues, flows, api]
---
# 0109m — issues_api service
+1 -1
View File
@@ -16,7 +16,7 @@ related:
- "0068"
created: 2026-05-18
updated: 2026-05-19
tags: [e2e_checks, recopilador, batch, coverage, epic]
tags: [e2e_checks, recopilador, batch, coverage, epic, ausente-ready]
---
# Sub-issues
+1 -1
View File
@@ -16,7 +16,7 @@ related:
- "0068"
created: 2026-05-19
updated: 2026-05-19
tags: [e2e_checks, recopilador, batch, design]
tags: [e2e_checks, recopilador, batch, design, ausente-ready]
---
# 0121a — Design-e2e batch
+1 -3
View File
@@ -7,9 +7,7 @@ domain:
- registry-quality
scope: registry
priority: media
depends:
- "0121a"
- "0121b"
depends: ["0121a"]
blocks:
- "0122"
related:
+1 -1
View File
@@ -17,7 +17,7 @@ related:
- "0086"
created: 2026-05-18
updated: 2026-05-18
tags: [revisor, mejorador, proposals, auto-apply, autonomous]
tags: [revisor, mejorador, proposals, auto-apply, autonomous, ausente-ready]
---
# 0122 — fn-revisor + ampliar filtro auto-aplicable del orquestador
+1 -1
View File
@@ -13,7 +13,7 @@ related:
- "0121a"
created: 2026-05-19
updated: 2026-05-19
tags: [dag_engine, cleanup, technical-debt]
tags: [dag_engine, cleanup, technical-debt, ausente-ready]
---
# 0124 — dag_engine cleanup
+1 -1
View File
@@ -13,7 +13,7 @@ related:
- "0121a"
created: 2026-05-19
updated: 2026-05-19
tags: [deploy_server, cli, idempotency]
tags: [deploy_server, cli, idempotency, ausente-ready]
---
# 0125 — deploy_server `--db` flag
+1 -1
View File
@@ -1,7 +1,7 @@
---
id: "0128"
title: "kanban: adjuntar archivos (drag&drop desc/chat + tab Archivos)"
status: in_progress
status: in-progress
type: feature
domain:
- apps-tools
+1 -6
View File
@@ -13,12 +13,7 @@ blocks:
- 0130b
related:
- "0130"
tags:
- registry
- go
- parser
- frontmatter
- fsnotify
tags: [registry, go, parser, frontmatter, fsnotify, ausente-ready]
flow: "0130"
created: "2026-05-22"
updated: "2026-05-22"
+1 -2
View File
@@ -8,8 +8,7 @@ domain:
- dev-ux
scope: app-scoped
priority: alta
depends:
- "0130a"
depends: ["0130a"]
blocks:
- "0130c"
related:
+2 -2
View File
@@ -1,14 +1,14 @@
---
id: "0134"
title: "Mesh protocol spec: capability manifests, ed25519 envelopes, enrollment, audit chain"
status: pending
status: pendiente
type: spec
domain:
- infra
- cybersecurity
- protocols
scope: cross-app
priority: high
priority: alta
depends: []
blocks:
- "0135"
+2 -2
View File
@@ -1,7 +1,7 @@
---
id: "0144"
title: "Agent LLM per machine (user + sudo) con tool registry y mesh dispatch"
status: pending
status: pendiente
type: spec
domain:
- agents
@@ -9,7 +9,7 @@ domain:
- infra
- cybersecurity
scope: multi-app
priority: high
priority: alta
depends:
- "0134"
- "0140"
@@ -1,8 +1,8 @@
---
id: "0146"
title: "add-pc one-shot: añade PC al mesh + agente LLM en <2min desde movil"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0009"]
related_issues: ["0134", "0144", "0145"]
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: "0147"
title: "matrix-client-pc scaffold: Wails + React+Mantine + login MAS"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0010"]
related_issues: ["0148", "0162"]
@@ -1,8 +1,8 @@
---
id: "0148"
title: "matrix-client-pc rooms list + timeline con sync incremental"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0010"]
related_issues: ["0147", "0149"]
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: "0149"
title: "matrix-client-pc composer: markdown, reply, edit, reactions, media"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0010"]
related_issues: ["0148", "0150"]
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: "0150"
title: "matrix-client-pc E2EE: cross-signing, SAS verification, recovery"
status: pending
priority: critical
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0010"]
related_issues: ["0149", "0151"]
@@ -1,8 +1,8 @@
---
id: "0151"
title: "matrix-client-pc calls LiveKit: 1:1 + grupales, mic/cam/screen"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0010"]
related_issues: ["0150", "0152"]
@@ -1,8 +1,8 @@
---
id: "0152"
title: "matrix-client-pc mini-webapps embebidas: Matrix Widget API v2"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0010"]
related_issues: ["0151", "0153"]
@@ -1,8 +1,8 @@
---
id: "0154"
title: "matrix-client-android scaffold: Kotlin + Compose + login MAS"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0011"]
related_issues: ["0155", "0162"]
@@ -1,8 +1,8 @@
---
id: "0155"
title: "matrix-client-android rooms list + timeline Compose"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0011"]
related_issues: ["0154", "0156"]
@@ -1,8 +1,8 @@
---
id: "0156"
title: "matrix-client-android composer: markdown, replies, edits, reactions, media"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0011"]
related_issues: ["0155", "0157"]
@@ -1,8 +1,8 @@
---
id: "0157"
title: "matrix-client-android E2EE rust-sdk: cross-signing, SAS, recovery"
status: pending
priority: critical
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0011"]
related_issues: ["0156", "0158"]
@@ -1,8 +1,8 @@
---
id: "0158"
title: "matrix-client-android calls LiveKit nativo: mic/cam/screen + PiP"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0011"]
related_issues: ["0157", "0159", "0161"]
@@ -1,8 +1,8 @@
---
id: "0159"
title: "matrix-client-android push FCM via sygnal + Firebase setup"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0011"]
related_issues: ["0158", "0160"]
@@ -1,8 +1,8 @@
---
id: "0160"
title: "matrix-client-android mini-webapps: WebView + Widget API v2 bridge"
status: pending
priority: medium
status: pendiente
priority: media
created: 2026-05-24
related_flows: ["0011"]
related_issues: ["0159", "0161"]
@@ -1,8 +1,8 @@
---
id: "0161"
title: "matrix-client-android foreground service: calls + lifecycle + lockscreen"
status: pending
priority: high
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0011"]
related_issues: ["0158", "0160"]
@@ -1,8 +1,8 @@
---
id: "0162"
title: "Matrix: migrar Synapse a MAS como unico auth provider (MSC3861)"
status: pending
priority: critical
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0010", "0011"]
related_issues: ["0147", "0154", "0163"]
+2 -2
View File
@@ -1,8 +1,8 @@
---
id: "0163"
title: "Matrix admin panel propio: users, rooms, devices, sessions (sustituye synapse-admin)"
status: pending
priority: medium
status: pendiente
priority: media
created: 2026-05-24
related_flows: ["0010", "0011"]
related_issues: ["0162", "0147"]
@@ -0,0 +1,45 @@
---
id: "0179"
title: "dev_console: escaneo recursivo de dev/issues/ (subcarpetas por dominio)"
status: in-progress
type: bugfix
domain:
- meta
scope: app-scoped
priority: media
depends: []
blocks: []
related: []
created: 2026-06-30
updated: 2026-06-30
tags: [ausente-ready]
---
# 0179 — dev_console: escaneo recursivo de dev/issues/
## Contexto
Los issues activos se reorganizaron en subcarpetas por dominio dentro de `dev/issues/` (`kanban/`, `trading/`, `gamedev/`, `cpp/`, `matrix/`, `imagegen/`) para descongestionar el listado plano. El skill `/issue` ya se actualizó a glob recursivo (`dev/issues/**/*.md`, excluyendo `completed/`). Falta alinear el binario `dev_console`, que carga los issues con `LoadAllIssues(root)` / `LoadOpenIssues(root)` en `apps/dev_console/` y hoy no recorre subcarpetas — por lo que no ve los 49 issues movidos.
## Objetivo
Que `dev_console issue list/board/work` y los flujos que dependen de `LoadAllIssues`/`LoadOpenIssues` recorran `dev/issues/` de forma recursiva, excluyendo `dev/issues/completed/`, manteniendo el resto del comportamiento idéntico.
## Tareas
- [ ] Localizar la implementación de `LoadAllIssues` / `LoadOpenIssues` en `apps/dev_console/` (probable `parser.go` o equivalente).
- [ ] Cambiar el escaneo a `filepath.WalkDir` (o glob recursivo) bajo `dev/issues/`, saltando el directorio `completed/`.
- [ ] Mantener el orden de salida estable (ordenar por `id`).
- [ ] Recompilar el binario en el sub-repo de `dev_console` siguiendo TBD (`issue/0179-...`).
## Definition of Done
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: lista incluye subcarpetas | e2e | `./apps/dev_console/dev_console issue list` | Aparecen issues de `cpp/`, `kanban/`, `trading/`, etc. (>= 49 que antes faltaban) |
| Edge: excluye completed/ | e2e | `dev_console issue list` | Ningún issue con `status: completado` de `completed/` aparece en el listado activo |
| Edge: conteo total coincide con /issue | e2e | comparar conteo con el glob recursivo de `/issue` | Mismo total de activos |
| Error: dev/issues vacío o ausente | unit | run en dir sin `dev/issues/` | Error claro, no panic |
## Notas
Hermano del cambio ya hecho en `.claude/commands/issue.md` (glob `**/*.md`). Hasta cerrar este issue, usar `/issue` (no `dev_console`) para vistas completas del backlog.
@@ -0,0 +1,59 @@
---
id: "0180"
title: "Modo ausente sobre la cola de issues: parametrizar /ausente + DAG dag_engine + validación"
status: pendiente
type: infra
domain:
- meta
scope: multi-app
priority: alta
depends: ["0179"]
blocks: []
related: []
created: 2026-06-30
updated: 2026-06-30
tags: []
---
# 0180 — Modo ausente sobre la cola de issues (parametrizar /ausente + DAG + validación)
## Contexto
Modelo de colaboración acordado (ver memoria `modelo-colaboracion-ausente`): durante la jornada de oficina (LJ 1014 / 1519, V 1016) y la noche (0109), Claude trabaja en `/ausente` la cola de issues `ausente-ready` (39 issues hoy), sin supervisión. La curación del backlog ya está hecha (triage, taxonomía, deps de series formalizadas, tag `ausente-ready`).
Faltan 3 piezas para automatizarlo de forma segura.
## Problemas a resolver
1. **`/ausente` está acoplado al roadmap ComfyUI.** El skill (`.claude/commands/ausente.md`) hardcodea su backlog a funciones ComfyUI (secciones "Configuración" y "Backlog del roadmap ComfyUI"). Hay que **parametrizar la fuente de tareas** para que pueda tomar la cola de issues: la siguiente tarea = primer issue de `/issue list -t ausente-ready` cuyas `depends` estén todas en `completed/`, re-cruzando deps en cada ciclo (un issue se libera cuando su dep se cierra).
2. **Lanzamiento headless desde dag_engine.** `dag_engine` ejecuta steps (command/script/function), no abre una sesión Claude interactiva. Hay que resolver cómo un step arranca una sesión `role=orchestrator` en modo `/ausente` (candidatos: `launch_claude_agent_kitty_bash_infra` con DISPLAY, o `spawn_fleet_agent_bash_infra` si hay sesión tmux fleet) con el prompt autónomo + presupuesto.
3. **Presupuesto conservador aplicado.** Tope: 12 ejecutores concurrentes, solo issues S/M, ~1M tokens por franja, parada al llegar. Materializar el tope de tokens (hoy `orchestration.md` solo fija fan-out=6).
## Schedule objetivo (cuando se active)
- Inicio de franjas de oficina: `0 10 * * 1-5` (10:00 LV) y `0 15 * * 1-4` (15:00 LJ, tras comida).
- Nocturno: `0 1 * * *` (01:00 diario).
- El modo, una vez lanzado, itera con `ScheduleWakeup` hasta que el humano vuelve (para al recibir prompt humano).
Borrador del DAG: `apps/dag_engine/dags/ausente-issues-queue.yaml` (creado como DRAFT sin schedule activo).
## Definition of Done
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: corrida manual | e2e | lanzar `/ausente` con backlog=issues sobre 1 issue S de la cola | Coge el issue, lo implementa en worktree/sub-repo aislado, cierra DoD verde (golden+edge+error), push, bitácora actualizada |
| Edge: dep no satisfecha | e2e | cola con un issue cuya `depends` sigue activa | NO lo coge; pasa al siguiente arrancable |
| Edge: flota llena | e2e | 2 ejecutores activos (tope conservador) | Encola el resto, no lanza el 3.º |
| Error: presupuesto agotado | e2e | tope de tokens alcanzado | Para limpio, deja bitácora con lo pendiente, no deja agentes huérfanos |
| Vida útil | observabilidad | tras activar cron, 1 semana | Issues cerrados/semana > 0, 0 merges rotos a master, bitácora legible |
## Plan
1. Cerrar `0179` (dev_console recursivo) — dependencia.
2. Parametrizar `/ausente` (fuente de backlog = issues ausente-ready | roadmap; pasar la fuente al invocar).
3. Resolver el step de lanzamiento headless + presupuesto de tokens.
4. **Validación manual** (golden + edges) antes de activar el cron.
5. Activar schedule en el DAG + `systemctl --user restart dag_engine.service` con `--scheduler`.
## Notas
Este issue NO es `ausente-ready` a propósito: requiere decisiones de diseño humanas (mecanismo de lanzamiento, forma del presupuesto) y toca el propio sistema que orquesta el modo ausente. Se hace JUNTOS, no desatendido.
@@ -1,7 +1,7 @@
---
id: "0059"
title: "Resolver doble tracking de `apps/*/app.md` (fn_registry + sub-repo)"
status: pendiente
status: completado
type: infra
domain:
- registry-quality
@@ -1,7 +1,7 @@
---
id: "55"
title: "Roadmap de prereqs — issues de osint_graph que odr_console necesita antes/durante MVP"
status: pendiente
status: deferred
type: epic
domain:
- osint
@@ -1,7 +1,7 @@
---
id: "0087"
title: "Capability Discovery Acceleration"
status: pendiente
status: completado
type: feature
domain:
- meta
@@ -1,7 +1,7 @@
---
id: "0096"
title: "Estandarizar ubicacion de apps: fuera de carpetas por lenguaje"
status: pendiente
status: completado
type: feature
domain:
- apps-infra
@@ -1,7 +1,7 @@
---
id: "0101"
title: "dev_console Go binario: /issue /flow /work unificados"
status: pendiente
status: completado
type: app
domain:
- meta
@@ -1,7 +1,7 @@
---
id: "0103"
title: "Taxonomia + slash commands /issue /flow /work"
status: pendiente
status: completado
type: feature
domain:
- meta
@@ -1,7 +1,7 @@
---
id: "0105"
title: "Estandarizar bloque service: en app.md + indexer + fn doctor services-spec"
status: in-progress
status: completado
type: feature
domain:
- meta
@@ -1,7 +1,7 @@
---
id: "0109g"
title: "skill_tree: panel terminal embebida (claude TUI dentro de la app)"
status: pendiente
status: deferred
type: feature
domain:
- meta
@@ -19,7 +19,7 @@ related:
- "0102"
created: 2026-05-18
updated: 2026-05-18
tags: [dod, evidence, frontmatter, taxonomy, validator]
tags: [dod, evidence, frontmatter, taxonomy, validator, ausente-ready]
flow: "0008"
---
@@ -15,7 +15,7 @@ blocks:
related: []
created: 2026-05-22
updated: 2026-05-22
tags: [agents_and_robots, http, sse, apikey, traefik, systemd]
tags: [agents_and_robots, http, sse, apikey, traefik, systemd, ausente-ready]
dod_evidence_schema:
- id: build_ok
kind: cmd
@@ -1,7 +1,7 @@
---
id: "0153"
title: "matrix-client-pc agent integration: paneles para rooms operados por agentes"
status: pending
status: deferred
priority: medium
created: 2026-05-24
related_flows: ["0010", "0009"]
@@ -1,7 +1,7 @@
---
id: "0164"
title: "Bots agents_and_robots: cryptohelper.Init() cuelga al habilitar encryption=true"
status: pending
status: deferred
priority: high
created: 2026-05-24
related_flows: ["0009"]
@@ -12,7 +12,7 @@ blocks: []
related: ["0167", "0168"]
created: 2026-05-24
updated: 2026-05-24
tags: [matrix, livekit, webrtc, turn, nat]
tags: [matrix, livekit, webrtc, turn, nat, ausente-ready]
---
# 0166 — Desplegar TURN para LiveKit (coturn o integrado)
@@ -1,7 +1,7 @@
---
id: "0171"
title: "Manifest de sub-repos por project + re-clonado y auditoría de cobertura en Gitea"
status: pendiente
status: completado
type: enhancement
domain:
- registry-quality
@@ -0,0 +1,91 @@
---
id: "0173"
title: "EDA: bugs críticos de correctitud estadística (outlier_pct ×100, distribution_type por-skew)"
status: completado
type: bugfix
domain:
- registry-quality
scope: registry-only
priority: alta
depends: []
blocks: []
related: ["0174", "0175", "0176", "0177", "0068"]
created: 2026-06-29
updated: 2026-06-29
tags: [eda, datascience, profile_table, render_eda_markdown, describe_numeric, benchmark]
---
# 0173 — EDA: bugs críticos de correctitud estadística
## Contexto
Un benchmark adversarial del workflow `/eda` sobre 12 datasets reales (29/06/2026,
`temp/eda_benchmark/EVALUATION.md`) detectó que los estadísticos descriptivos base son
correctos, pero el **porcentaje de outliers que el report markdown muestra es imposible**
(supera el 100%, hasta 336%), engañando a un lector no experto con apariencia de autoridad.
Hallazgos cubiertos por este issue:
| Hallazgo | Severidad | Evidencia del benchmark |
|---|---|---|
| H1 — `outlier_pct` por-columna >100% en el report markdown | crítico | wine-red `chlorides` 193.87%, `density` 112.57% (skew 0.07); titanic `SibSp` 336.70%, `Fare` 224.47%; seattle `precipitation` 253.25% |
| H11 — `distribution_type` por-skew etiqueta mal discretas/ordinales/multimodales | bajo | wine `quality` (6 valores) → "normal-ish"; precios BTC multimodales → "normal-ish" (skew 0.45) |
### Causa raíz de H1 (verificada en código, READ-ONLY)
`EVALUATION.md` propuso "corregir la fórmula en `describe_numeric`". **Eso es incorrecto.** Al
leer el código:
- `python/functions/datascience/describe_numeric.py:113` calcula
`outlier_pct = 100.0 * n_outliers / n` — ya en escala 0-100 y acotado a [0,100]. **Está bien.**
- `python/functions/datascience/render_eda_markdown.py:203-204` renderiza ese valor con
`_fmt_pct(val)`, y `_fmt_pct` (líneas 31-44) hace `num * 100` porque **asume que su input es
una fracción 0-1**. Resultado: **doble ×100** (un 1.94 real se muestra como 193.87%).
- El PDF (`render_eda_pdf.py:296`) usa `_fmt_num(outlier_pct, 1) + "%"` sin multiplicar — por eso
el PDF muestra el outlier_pct correcto y el markdown no. El bug es **exclusivo del renderer
markdown**.
El factor "19-40×" que observó el evaluador se debe a que comparaba contra outliers IQR (3-10%),
mientras `describe_numeric` usa z-score (umbral 3.0, da menos outliers); pero el mecanismo del bug
es el doble ×100, no la fórmula.
## Tareas
1. **H1 (fix de 1 línea):** en `python/functions/datascience/render_eda_markdown.py:203-204`,
sustituir `_fmt_pct(val)` por un formateo que NO multiplique (p.ej. `f"{_fmt_num(val, 2)}%"`),
porque `numeric.outlier_pct` ya viene en escala 0-100. **No tocar** `describe_numeric.py` (su
fórmula es correcta).
2. Auditar el resto de `render_eda_markdown.py` por si otro campo en escala 0-100 pasa por
`_fmt_pct` (los `*_pct` del perfil base sí son fracciones 0-1 y deben seguir con `_fmt_pct`;
solo `numeric.outlier_pct` está en escala 0-100). Documentar en el docstring de `describe_numeric`
que `outlier_pct` está en 0-100 para evitar la confusión a futuro.
3. **H11:** en `python/functions/datascience/detect_distribution_type.py`, no etiquetar por skew
solamente: usar también nº de modos / cardinalidad y, cuando esté disponible, el test de
normalidad Jarque-Bera (`normality_tests.py`, ya expuesto en `models.normality` vía
`run_eda_models`). Una variable discreta/ordinal/multimodal no debe salir "normal-ish".
4. Añadir/extender tests unitarios: `describe_numeric_test.py` (outlier_pct en [0,100]),
`render_eda_markdown_test.py` (un perfil con `outlier_pct=7.0` renderiza `"7.00%"`, no `"700%"`),
y un test de `detect_distribution_type` (discreta de 6 valores no se etiqueta "normal-ish"). Nota:
hoy NO existe `detect_distribution_type_test.py` en `python/functions/datascience/` — hay que
crearlo (a confirmar el nombre canónico al implementar; el resto de tests citados sí existen).
## Definition of Done
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: outlier_pct en rango | e2e | re-correr `profile_table` sobre `temp/eda_benchmark/datasets/.../wine-red` y leer el `.md` | `chlorides`/`density` muestran `outlier_pct` en [0,100]% (no 193.87% / 112.57%) |
| Edge: skew alto real | unit | `describe_numeric_test.py` con datos de cola fuerte | `outlier_pct` ≤ 100 y coherente con n_outliers/n |
| Edge: discreta ordinal | unit | `detect_distribution_type_test.py` con 6 valores discretos | NO etiqueta "normal-ish" |
| Error: input vacío/no numérico | unit | `describe_numeric([])` | claves None, sin crash (contrato actual preservado) |
| Mecánica | — | `./fn run describe_numeric_py_datascience`, `./fn run render_eda_markdown_py_datascience` | tests verdes; `fn index` limpio |
Re-correr el benchmark sobre wine-red y titanic y confirmar que ningún `outlier_pct` supera 100%.
## Notas
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md` (consolidación del benchmark). H1 es el fix de
mayor ratio impacto/esfuerzo del lote (una línea elimina los números imposibles que más minan la
confianza del report). Hermanos: 0174 (series), 0175 (relational), 0176 (render), 0177 (tipos).
## Resolucion (2026-06-29, sesion /ausente)
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: caf8c25d. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
@@ -0,0 +1,90 @@
---
id: "0174"
title: "EDA series temporales: período estacional roto + correlación de niveles + to_returns ciego"
status: completado
type: bugfix
domain:
- registry-quality
scope: registry-only
priority: alta
depends: []
blocks: []
related: ["0173", "0175", "0176", "0177"]
created: 2026-06-29
updated: 2026-06-29
tags: [eda, datascience, stl_decompose, profile_table, to_returns, series, benchmark]
---
# 0174 — EDA series temporales: período estacional + correlación de niveles
## Contexto
El benchmark `/eda` (29/06/2026, `temp/eda_benchmark/EVALUATION.md`) confirmó que la
estacionariedad (ADF+KPSS), la autocorrelación (Ljung-Box) y el aviso de espuriedad
Granger-Newbold están **bien** (verificados a mano con `statsmodels`). Pero el **detector de
período estacional está roto**, lo que produce falsos negativos de estacionalidad, y la
correlación de precios se calcula sobre niveles (espuria para uso financiero).
Hallazgos cubiertos:
| Hallazgo | Severidad | Evidencia del benchmark |
|---|---|---|
| H2 — período estacional sale `2` casi siempre → `seasonal_strength=0` | crítico | seattle `temp_max` reporta "sin estacionalidad" (`period=2`); STL real con `period=365` da fuerza estacional **0.843**. UNRATE (mensual) debería usar 12, no 2 |
| H8 — correlación de precios sobre niveles marcada `sig=sí` | medio-alto | aapl/btc `CloseOpen=0.998 sig=sí`: espuria por construcción (niveles autocorrelados no estacionarios) |
| H13 — `to_returns` sugerido ciegamente a temperatura (sin sentido físico) | bajo | seattle `temp_max`: "convertir a retornos"; debería ser "diferencias" |
### Causa raíz H2 (verificada en código, READ-ONLY)
`python/functions/datascience/stl_decompose.py:34-58` (`_infer_period`) busca el lag entre 2 y
`max_period` que maximiza la autocorrelación **cruda** de la serie. En cualquier serie con
tendencia (precios, temperatura), la autocorrelación decae monótonamente desde el lag mínimo, así
que **el lag 2 casi siempre gana**`period=2` espurio y un STL con componente estacional que es
ruido (`seasonal_strength≈0`). Además, `python/functions/pipelines/profile_table.py:175`
(`_build_series_block`) llama `stl_decompose(series_vals)` **sin pasar el período**, pese a que el
pipeline ya conoce la columna de orden temporal (`order_col`) y podría derivar la frecuencia.
## Tareas
1. **H2 — arreglar la inferencia de período** en `stl_decompose.py:34-58`. Opciones (preferir la
robusta): (a) detrend antes de autocorrelar; (b) buscar picos en el periodograma/FFT en vez del
primer lag; (c) **derivar el período de la frecuencia del índice datetime** (mensual→12,
diario→7 y/o 365) — la señal más fiable.
2. **H2 — pasar el período desde el pipeline:** en `profile_table.py:_build_series_block`, cuando
exista `order_col` datetime, inferir la frecuencia del índice y pasar `period=` explícito a
`stl_decompose`. Si no se puede determinar un período fiable, que `stl_decompose` **no reporte
`seasonal_strength=0`** como conclusión: devolver `note` "período no determinado" (ya hay una
rama así en `:139-145`; extenderla a los casos que hoy caen en `period=2`).
3. **H8 — correlación sobre retornos para series no estacionarias:** en la sección de correlaciones
de `profile_table.py:346-384`, cuando una columna sea una serie no estacionaria de niveles
(verdict `non_stationary`/`inconclusive`, ya detectado), correlacionar sobre retornos/diferencias
(`to_returns`, ya importado) o marcar esos pares de niveles como "posible espuria" junto a la
tabla. El aviso global existe pero está lejos de los números.
4. **H13 — retornos vs diferencias por semántica:** en `profile_table.py:189` / `to_returns.py`,
elegir "retornos" (financiero, estrictamente positivo multiplicativo) vs "diferencias" (físico,
aditivo) según la naturaleza, o usar "diferencias" por defecto cuando no haya señal financiera.
5. Tests: `stl_decompose_test.py` (serie sintética mensual con estacionalidad anual → período
correcto y `seasonal_strength` alta; serie con tendencia sin estacionalidad → nota, no
`period=2`); cobertura de `_build_series_block` con `order_col` datetime.
## Definition of Done
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: estacionalidad anual | e2e | re-correr `profile_table` con `run_series=True` sobre seattle `temp_max` | `seasonal_strength ≈ 0.84` con período ≈ 365 (NO "sin estacionalidad", NO `period=2`) |
| Edge: serie mensual | unit | `stl_decompose_test.py` serie mensual sintética con ciclo 12 | período inferido 12 y fuerza estacional alta |
| Edge: sin estacionalidad | unit | `stl_decompose_test.py` serie con solo tendencia | `note` "período no determinado", NO `seasonal_strength=0` como conclusión |
| Error: serie corta | unit | `stl_decompose([...]<2*period)` | nota "serie corta", sin crash (contrato actual) |
| H8 | e2e | re-correr `profile_table` sobre aapl/btc | pares de niveles no estacionarios marcados como posible espuria o correlación sobre retornos |
| Mecánica | — | `./fn run stl_decompose_py_datascience`; `fn index` | tests verdes; índice limpio |
Re-correr el benchmark sobre seattle, fred-unrate, aapl y btc y confirmar que la estacionalidad se
detecta donde existe y no se inventa donde no.
## Notas
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md`. H2 es el segundo bloqueante de fiabilidad: un
"sin estacionalidad" donde la hay es un falso negativo que un decisor creería. La estacionariedad ya
funciona — no tocarla. Hermanos: 0173, 0175, 0176, 0177.
## Resolucion (2026-06-29, sesion /ausente)
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: e142ef02. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
@@ -0,0 +1,93 @@
---
id: "0175"
title: "EDA relational: precisión de FK inference (falsos positivos) + filtrar VIEWs + test ATTACH"
status: completado
type: bugfix
domain:
- registry-quality
scope: registry-only
priority: alta
depends: []
blocks: []
related: ["0173", "0174", "0176", "0177"]
created: 2026-06-29
updated: 2026-06-29
tags: [eda, datascience, infer_fk_containment_duckdb, build_join_graph, profile_database, duckdb, benchmark]
---
# 0175 — EDA relational: precisión de FK inference + filtrar VIEWs
## Contexto
El benchmark `/eda` (29/06/2026, `temp/eda_benchmark/EVALUATION.md`) confirmó que la inferencia de
claves foráneas a nivel de base es **inútil por falsos positivos masivos** y que las VISTAS se
perfilan como tablas base. El join graph resultante necesita filtrado manual para ser legible.
Hallazgos cubiertos:
| Hallazgo | Severidad | Evidencia del benchmark |
|---|---|---|
| H3 — FK inference por contención: 10-20× falsos positivos | crítico | chinook 111 candidatas vs ~11 reales; sakila 565 vs ~30. Casos absurdos: `InvoiceLine.Quantity→Album.AlbumId`, `Genre.GenreId→{Album,Artist,Customer,…}` |
| H5 — VIEWs perfiladas como tablas base | alto | sakila `n_tables=21` incluye 5 VISTAS (`customer_list`, `film_list` 5462 filas, `staff_list`, `sales_by_store`, `sales_by_film_category`) + `film_text` (FTS, 0 filas) |
| H10 — coste relacional gastado en computar FK falsas | medio | sakila 31.82s: la mayoría en INTERSECT de los 565 pares candidatos, casi todos falsos |
| H14 — bug `sqlite_master does not exist` tras ATTACH (ya parcheado, falta test) | bajo (resuelto) | `_run.log`: `profile_database` falló con `Catalog Error: src.sqlite_master`; re-run posterior `ok` |
### Causa raíz (verificada en código, READ-ONLY)
- `python/functions/datascience/infer_fk_containment_duckdb.py:217-285` emite una FK candidata si
`inclusion(A⊆B) ≥ min_inclusion` **y** B "parece clave" (unicidad ≥0.95). **No usa el nombre de
la columna**, que es la señal más fuerte de FK (`AlbumId→Album.AlbumId`), ni excluye columnas
no-clave (cantidades, importes) como ORIGEN. Enteros pequeños (`GenreId` 1..25) están contenidos
en casi todo → ruido.
- `python/functions/pipelines/profile_database.py:155-159` lista tablas con `duckdb_list_tables`
sin filtrar `table_type` → perfila VIEWs y tablas FTS como base (H5), lo que infla el universo de
pares y multiplica las FK falsas (relaciona H10).
- H10 es el **mismo cambio** que H3: filtrar candidatos por nombre **antes** del INTERSECT reduce
pares (más rápido) y falsos positivos (más preciso) a la vez.
## Tareas
1. **H3+H10 — señal de nombre en `infer_fk_containment_duckdb.py:217-285`:** antes de lanzar el
INTERSECT, exigir coincidencia/patrón de nombre entre origen y destino (`from_col` casa con
`to_table`/`to_col`, patrón `<X>Id → <X>.<X>Id`; case-insensitive). Excluir como ORIGEN columnas
claramente no-clave (cantidades, importes, flags) por heurística de nombre/tipo. Esto poda el
O(tablas²×columnas²) y elimina la mayoría de los falsos positivos. Validar mejor la cardinalidad
(los `1:1` imposibles del benchmark).
2. **H5 — filtrar VIEWs** antes de perfilar e inferir FK: filtrar `table_type='BASE TABLE'` vía
`information_schema.tables` / `duckdb_tables()`. Decidir (a confirmar al implementar) si el filtro
va como flag nuevo en `duckdb_list_tables` (infra, reutilizable) o en `profile_database.py` tras
listar. Preferir el flag en `duckdb_list_tables` si no rompe consumidores.
3. **H3 — propagar al join graph:** verificar que `build_join_graph.py` recibe la lista ya filtrada
y que el diagrama Mermaid resultante es legible (sin nodos VIEW ni aristas espurias).
4. **H14 — test de regresión:** añadir test (en `profile_database_test.py` o
`infer_fk_containment_duckdb_test.py`) que haga `ATTACH` de una base SQLite pequeña en DuckDB y
perfile, confirmando que se usa `information_schema`/`duckdb_tables()` y nunca `sqlite_master`.
(A confirmar: localizar la función que hace el ATTACH —probablemente `summarize_table_duckdb.py`
o una primitiva infra `duckdb_*`— para cubrirla.)
5. Tests: casos sintéticos con tablas que tengan columnas tipo `XId` (FK real) y columnas de
cantidad contenidas en claves (falso positivo) → confirmar que solo emite las reales.
## Definition of Done
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: FK reales sin ruido | e2e | re-correr `profile_database` sobre chinook | ~11 FK candidatas (no 111); incluyen `Album.ArtistId→Artist.ArtistId`, `Invoice.CustomerId→Customer.CustomerId`; NO incluyen `InvoiceLine.Quantity→Album.AlbumId` |
| Edge: VIEWs excluidas | e2e | re-correr `profile_database` sobre sakila | `n_tables` cuenta solo BASE TABLE (sin `customer_list`/`film_list`/…); FK candidatas ≪ 565 |
| Edge: cantidad vs clave | unit | `infer_fk_containment_duckdb_test.py` con columna `Quantity` contenida en una clave | NO emite FK desde `Quantity` |
| Error: ATTACH SQLite | unit | test de regresión ATTACH SQLite→DuckDB | perfila sin `sqlite_master does not exist`; usa information_schema |
| Rendimiento (H10) | e2e | medir duración de `profile_database` sobre sakila | menor que el baseline 31.82s (menos INTERSECT) |
| Mecánica | — | `./fn run infer_fk_containment_duckdb_py_datascience`, `./fn run profile_database_py_pipelines`; `fn index` | tests verdes; índice limpio |
Re-correr el benchmark sobre chinook y sakila y confirmar que las FK reales son distinguibles del
ruido y que las VIEWs no se cuentan como tablas.
## Notas
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md`. Tres síntomas (H3/H5/H10) con un núcleo común:
la capa de inferencia de relaciones inter-tabla. Atacarlos juntos en una rama; filtrar VIEWs reduce
el universo de pares y filtrar candidatos por nombre arregla precisión y velocidad a la vez. H14 ya
está parcheado en producción; este issue solo añade el test de regresión que faltaba.
Hermanos: 0173, 0174, 0176, 0177.
## Resolucion (2026-06-29, sesion /ausente)
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: e142ef02. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
@@ -0,0 +1,84 @@
---
id: "0176"
title: "EDA render: models/series/caveats en markdown+PDF + PDF para profile_database"
status: completado
type: feature
domain:
- registry-quality
scope: registry-only
priority: media
depends: []
blocks: []
related: ["0173", "0174", "0175", "0177"]
created: 2026-06-29
updated: 2026-06-29
tags: [eda, datascience, render_eda_markdown, render_eda_pdf, profile_database, pdf, benchmark]
---
# 0176 — EDA render: models/series/caveats en markdown+PDF + PDF para profile_database
## Contexto
El benchmark `/eda` (29/06/2026, `temp/eda_benchmark/EVALUATION.md`) confirmó que la información de
modelos (PCA/KMeans) está completa en el JSON pero **no llega legible a ningún formato**, y que el
análisis relacional no tiene salida móvil (PDF). El tercio final del PDF queda ilegible.
Hallazgos cubiertos:
| Hallazgo | Severidad | Evidencia del benchmark |
|---|---|---|
| H4 — `models` omitido en Markdown; `models`/`series`/`caveats` como dict crudo truncado en PDF | alto | wine-red `.md` (12 numéricas, PCA valioso) → cero menciones de models. PDF aapl: `- pca: {'n_components': 2, …` cortado a media línea |
| H9 — `profile_database` no genera PDF | medio | chinook y sakila con `pdf=null`; análisis relacional solo en Markdown |
### Causa raíz (verificada en código, READ-ONLY)
- `python/functions/datascience/render_eda_markdown.py`: tiene formatters para `series` (`:337`) y
`caveats` (`:407`), pero **no para `models`** → el bloque PCA/KMeans nunca se renderiza en MD.
- `python/functions/datascience/render_eda_pdf.py:50-55`: `_KNOWN_TOP_KEYS` **no incluye** `models`,
`series` ni `caveats`, así que caen en `_generic_pages` (`:479-495`) → `_wrap_value`
`str(dict)` truncado a 60-64 chars. Por eso esas tres secciones salen como dict crudo en el PDF.
- `python/functions/pipelines/profile_database.py:205-218`: solo escribe MD+JSON, nunca invoca
`render_eda_pdf`; no tiene param `emit_pdf`.
## Tareas
1. **H4 — markdown:** añadir una sección `## Modelos` (PCA/KMeans/outliers/normalidad) a
`render_eda_markdown.py`, formateando `models.pca` (varianza explicada, top loadings, acumulada),
`models.kmeans` (best_k, silhouette, tamaños de cluster) y `models.outliers` como tablas legibles.
2. **H4 — PDF:** en `render_eda_pdf.py`, añadir builders dedicados para `models`, `series` y
`caveats` (tablas/listas, no `str(dict)`) y registrarlos en `_KNOWN_TOP_KEYS` + en la lista
`builders` (`:595-604`) para sacarlos del volcado genérico. Mantener el contrato dict-no-throw
(una sección que falle no aborta el PDF).
3. **Unificar renderers:** asegurar que MD y PDF cubren el mismo conjunto de secciones (`models`,
`series`, `caveats`) para que no diverjan otra vez.
4. **H9 — PDF relational:** añadir un renderer PDF DB-level (puede ser una variante en
`render_eda_pdf.py` o una función nueva) con: portada de la base, resumen de tablas, join graph
filtrado (tras 0175), y FK candidatas. Añadir param `emit_pdf` a `profile_database.py` que lo
invoque y devuelva `pdf_path`.
5. Tests: `render_eda_markdown_test.py` (perfil con `models` → aparece sección Modelos);
`render_eda_pdf_test.py` (perfil con `models`/`series`/`caveats` → NO aparecen como `str(dict)`;
`n_pages` incrementa); test de `profile_database(emit_pdf=True)``pdf_path` no nulo, PDF válido.
## Definition of Done
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: models en MD | e2e | re-correr `profile_table(run_models=True)` sobre wine-red y leer el `.md` | sección `## Modelos` con PCA (varianza explicada) y KMeans (silhouette) legibles |
| Golden: PDF legible | e2e | re-correr sobre aapl y `pdftotext` del PDF | `models`/`series`/`caveats` como tablas, sin `{'n_components': 2, …` truncado |
| Edge: perfil sin models | unit | `render_eda_markdown_test.py`/`render_eda_pdf_test.py` con `models=None` | sección omitida limpiamente, sin crash |
| Edge: PDF relational | e2e | `profile_database(emit_pdf=True)` sobre chinook | `pdf_path` no nulo; PDF con resumen de tablas + join graph |
| Error: sección corrupta | unit | `render_eda_pdf` con una sección con tipo inesperado | esa sección se omite con nota; PDF sigue válido (≥1 página) |
| Mecánica | — | `./fn run render_eda_markdown_py_datascience`, `./fn run render_eda_pdf_py_datascience`; `fn index` | tests verdes; índice limpio |
Re-correr el benchmark sobre un single-table con modelos (wine-red) y sobre un relational (chinook)
y confirmar que models llega al MD y al PDF, y que `profile_database` emite PDF.
## Notas
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md`. Tipo `feature` porque, además de arreglar el
volcado crudo (H4, fix), añade un renderer PDF relational nuevo (H9). La información ya existe en el
JSON; este issue solo la hace legible en las dos salidas pensadas para humanos. Hermanos: 0173, 0174,
0175, 0177.
## Resolucion (2026-06-29, sesion /ausente)
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: c4cff5ed. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
@@ -0,0 +1,90 @@
---
id: "0177"
title: "EDA tipos: id secuencial fuera de correlación/PCA + η² espurio por cardinalidad + re-expresión no-continuas"
status: completado
type: bugfix
domain:
- registry-quality
scope: registry-only
priority: media
depends: []
blocks: []
related: ["0173", "0174", "0175", "0176"]
created: 2026-06-29
updated: 2026-06-29
tags: [eda, datascience, profile_table, association_matrix, correlation_ratio, run_eda_models, suggest_reexpression, benchmark]
---
# 0177 — EDA tipos: id secuencial fuera de correlación/PCA + η² espurio
## Contexto
El benchmark `/eda` (29/06/2026, `temp/eda_benchmark/EVALUATION.md`) **refutó** el riesgo temido
(que el EDA excluyera columnas financieras `Open/Close/High/Low/Volume` por marcarlas id-like: NO
ocurre, aparecen en todo). Pero detectó el **problema inverso**: el flag `possible_id` es cosmético
y no excluye lo que sí debería (índices secuenciales), y la razón de correlación η² da artefactos
≈1 por cardinalidad.
Hallazgos cubiertos:
| Hallazgo | Severidad | Evidencia del benchmark |
|---|---|---|
| H7 — `possible_id` no excluye id secuencial (`PassengerId`) de correlación ni de PCA/KMeans | medio-alto | titanic `PassengerIdCabin` η²=0.897 `sig=sí`; `models.pca.n_features=7` incluye `PassengerId`, `Survived`, `Pclass` |
| H6 — `correlation_ratio` (η²) ≈1 espurio cuando la categórica tiene cardinalidad ≈ n | alto | titanic `TicketFare=1 sig=sí` (`Ticket` 681 distintos/891); aapl/btc/seattle/fred `Date* =1` |
| H12 — `suggest_reexpression` sugiere fila para binarias/ordinales/ids (aunque sea `none`) | bajo | titanic `Survived` (0/1), `Pclass` (ordinal), `PassengerId` (id) listadas |
### Causa raíz (verificada en código, READ-ONLY)
- `python/functions/pipelines/profile_table.py:356-361` (`_skip_for_assoc`) excluye de la matriz de
asociación las columnas id-like **categóricas/text** (`possible_id`/`high_cardinality`), pero **no**
excluye numéricas secuenciales (`PassengerId` es numérica con `possible_id`) ni columnas datetime.
El `assoc_input` resultante se pasa tal cual a `run_eda_models` (`:391`), así que el id secuencial,
el target binario y el ordinal entran como features de PCA/KMeans.
- H6: `correlation_ratio.py` calcula η² sin guard de cardinalidad; cuando cada grupo tiene ~1
observación (categórica de cardinalidad ≈ n), la varianza intra-grupo ≈0 → η²≈1 trivialmente. El
FDR no protege (artefacto determinista, no azar).
- H12: `suggest_reexpression` (llamado en `profile_table.py:300` para toda numérica) no salta
binarias/ordinales/ids.
## Tareas
1. **H7 — distinguir id secuencial de float continuo:** en la detección de tipos
(`summarize_table_duckdb.py` / lógica de `possible_id`) o en `profile_table.py`, marcar
"índice entero secuencial/monótono" distinto de "float continuo de alta cardinalidad". El primero
se excluye de correlación y de PCA/KMeans; el segundo se mantiene (precios). **Nunca** excluir
floats continuos.
2. **H7 — excluir no-features de los modelos:** en `_skip_for_assoc` (y/o en `run_eda_models.py`)
excluir de PCA/KMeans los ids secuenciales, binarias, ordinales y el target evidente, además de
las categóricas id-like que ya se excluyen.
3. **H6 — guard de cardinalidad en η²:** en `correlation_ratio.py` (y/o al construir los pares en
`association_matrix.py`/`profile_table.py`), no computar η² si la categórica tiene cardinalidad
cercana a `n` o tamaño de grupo medio ≈1; excluir columnas datetime/id de los pares categóricos.
4. **H12 — saltar no-continuas en re-expresión:** en `suggest_reexpression.py` (o en la llamada de
`profile_table.py:300`), no emitir fila de re-expresión para binarias/ordinales/ids.
5. Tests: `correlation_ratio_test.py` (categórica cardinalidad≈n → no η²≈1 espurio);
`run_eda_models_test.py` (id secuencial/target/ordinal no entran como features);
`suggest_reexpression_test.py` (binaria/ordinal/id → sin sugerencia).
## Definition of Done
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: id secuencial fuera | e2e | re-correr `profile_table(run_models=True)` sobre titanic | `PassengerId` NO aparece en correlaciones ni en `models.pca.features`; floats continuos (precios en aapl/btc) SÍ se conservan |
| Golden: η² sin artefacto | e2e | re-correr sobre titanic | `TicketFare` y `Date*` NO aparecen como par fuerte η²=1 |
| Edge: float continuo | unit | `correlation_ratio_test.py` / detección de tipos | columna float de alta cardinalidad (precio) se mantiene en correlación |
| Edge: re-expresión | unit | `suggest_reexpression_test.py` con binaria/ordinal/id | sin fila de re-expresión |
| Error: solo numéricas | unit | `run_eda_models` con assoc_input vacío tras filtrar | sin crash; bloque models coherente |
| Mecánica | — | `./fn run correlation_ratio_py_datascience`, `./fn run run_eda_models_py_datascience`, `./fn run suggest_reexpression_py_datascience`; `fn index` | tests verdes; índice limpio |
Re-correr el benchmark sobre titanic (id secuencial + η² espurio) y sobre aapl/btc (confirmar que
los floats financieros NO se excluyen) y verificar ambos comportamientos.
## Notas
Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md`. El warning "grave" del benchmark (excluir
columnas financieras) quedó **refutado**: este issue arregla el problema inverso real (no excluir
ids secuenciales) sin tocar el tratamiento correcto de los floats continuos. Hermanos: 0173, 0174,
0175, 0176.
## Resolucion (2026-06-29, sesion /ausente)
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: e142ef02. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
@@ -12,7 +12,7 @@ blocks: []
related: []
created: 2026-05-17
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
# 0033 — C++ http_inspector + websocket_client
@@ -13,10 +13,7 @@ blocks: []
related: []
created: 2026-05-10
updated: 2026-05-17
tags:
- gamedev
- cpp
- wasm
tags: [ausente-ready, gamedev, cpp, wasm]
---
## Objetivo
@@ -13,7 +13,7 @@ blocks: []
related: []
created: 2026-05-13
updated: 2026-05-17
tags: []
tags: [ausente-ready]
---
## Objetivo
@@ -15,7 +15,7 @@ related:
- "0106"
created: 2026-05-18
updated: 2026-05-18
tags: [http, cpp, registry-gap, curl, helper]
tags: [http, cpp, registry-gap, curl, helper, ausente-ready]
---
# 0110 — Helper HTTP cliente C++ en el registry
@@ -8,8 +8,7 @@ domain:
- dev-ux
scope: app-scoped
priority: alta
depends:
- "0130b"
depends: ["0130b"]
blocks: []
related:
- "0130"
@@ -16,7 +16,7 @@ related:
- "0131"
created: 2026-05-22
updated: 2026-05-22
tags: [cpp, imgui, terminal, pty, module]
tags: [cpp, imgui, terminal, pty, module, ausente-ready]
flow: ""
---
@@ -7,8 +7,7 @@ domain:
- gamedev
scope: multi-app
priority: alta
depends:
- "0072a"
depends: ["0072a"]
blocks: []
related: []
created: 2026-05-10
@@ -7,8 +7,7 @@ domain:
- gamedev
scope: multi-app
priority: alta
depends:
- "0072b"
depends: ["0072b"]
blocks: []
related: []
created: 2026-05-10
@@ -7,9 +7,7 @@ domain:
- gamedev
scope: multi-app
priority: alta
depends:
- "0072a"
- "0072b"
depends: ["0072a", "0072b"]
blocks: []
related: []
created: 2026-05-10
@@ -7,9 +7,7 @@ domain:
- gamedev
scope: multi-app
priority: alta
depends:
- "0072a"
- "0072d"
depends: ["0072a", "0072d"]
blocks: []
related: []
created: 2026-05-10

Some files were not shown because too many files have changed in this diff Show More