Compare commits

...

51 Commits

Author SHA1 Message Date
egutierrez ab21e5d90b merge: 4b flag profile_level lite/standard/full en render_automatic_eda (lite 4.5s vs full 39.3s, verificado met) 2026-06-30 18:29:44 +02:00
egutierrez da60211826 merge: 4b relaciones — capitulo PK/FK + candidatos intra/inter-tabla (reusa infer_fk_containment_duckdb+build_join_graph, verificado met) 2026-06-30 18:22:29 +02:00
egutierrez 3be188a921 feat(eda): profile_level (lite/standard/full) en render_automatic_eda
Añade el parámetro profile_level a render_automatic_eda como preset de
consumo CPU/LLM que mapea a los flags existentes (run_models, run_series,
run_llm, sample). Tres niveles:

- lite (bajo consumo): run_llm=False, run_series=False, sample=2000 y modelos
  limitados a PCA + normalidad, SIN KMeans ni IsolationForest (lo caro en CPU).
  Para un vistazo rápido y barato.
- standard (default): comportamiento histórico — modelos completos, serie,
  sin LLM.
- full: standard + narrativa LLM por capítulo.

Precedencia: un flag explícito del caller (run_llm=..., run_models=..., etc.)
siempre prima sobre el default que fija el preset; el preset solo aplica al
parámetro que se deja en None.

Cableado del modo lite sin tocar profile_table (lo tocan otros agentes en
paralelo): profile_table NO corre los modelos (evita pagar KMeans +
IsolationForest); este pipeline los corre con run_eda_models(run_kmeans=False,
run_isolation=False) reusando ctx['raw_numeric'], y quita raw_numeric del ctx
para que el capítulo modelos no reproyecte clusters KMeans en vivo
(project_clusters_2d). geo_points ya queda derivado, así que geospatial no se
afecta.

Cambio aditivo y retro-compatible: sin profile_level el comportamiento es
idéntico al de v1.0.0 (standard). Tests nuevos cubren lite/standard, la
precedencia flag-sobre-preset, y la equivalencia del default con el histórico.
Bump 1.0.0 -> 1.1.0 + growth log en el .md. Skill /eda documenta --lite/--full.

Verificación: golden lite/standard/full sobre titanic — lite 4.8s (PCA+norm,
sin KMeans/iso/LLM/serie), standard 7.8s (modelos completos), full 38.3s
(+LLM). Suite render_automatic_eda + automatic_eda: 96 passed. fn index sin
error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:20:17 +02:00
egutierrez aa5aa67d50 merge: 4b calidad — nueva formula (completeness 0.6+validity 0.4, dataset row_uniqueness, outliers fuera a Observaciones, sin doble conteo) report 2046 (verificado met) 2026-06-30 18:17:23 +02:00
egutierrez 68f4ddabce feat(eda): capítulo RELACIONES para AutomaticEDA
Añade el capítulo `relaciones` al motor AutomaticEDA: analiza las
relaciones de clave de la tabla/base y se coloca tras `correlacion`,
antes de `modelos`, en CHAPTER_ORDER.

Capas que renderiza (solo las que aplican; None si no hay nada que decir):
- Claves declaradas: PK/FK/UNIQUE reales del esquema DuckDB, vía la nueva
  función `detect_declared_keys_duckdb` (lee `duckdb_constraints()`).
- Candidatos a clave primaria: los `key_candidates` del TableProfile.
- FK candidatas inter-tabla: reusa `infer_fk_containment_duckdb`
  (containment + señal de nombre) y `build_join_graph` (roles de nodos +
  diagrama Mermaid pegable). Solo si la fuente DuckDB tiene varias tablas.
- FK candidatas intra-tabla: heurística nombre + cardinalidad, vía la nueva
  función pura `suggest_intratable_fk_candidates`, marcada como sugerencia.

Engancha al glosario clicable los términos PK, FK, containment/inclusión y
cardinalidad (contrato §11.1) y usa Group (keep-together) para el grafo.

Funciones nuevas del registry (grupo `eda`):
- detect_declared_keys_duckdb (impure, datascience) + test.
- suggest_intratable_fk_candidates (pure, datascience) + test.

Tests: relaciones_test.py (golden intra + inter, edges, no-cut render) +
los tests de ambas funciones. Suite automatic_eda + render_automatic_eda
verde (89 passed). Golden end-to-end con el pipeline render_automatic_eda
verificado sobre titanic (intra) y una BD customers/orders (inter).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:15:15 +02:00
egutierrez 43821ab11d merge: 4b analisis_llm — dedup Diccionario de datos + Datos personales (verificado met) 2026-06-30 18:14:17 +02:00
egutierrez 32054ad781 merge: 4b portada — tamano grande junto al nombre + descripcion y granularidad funcionando (verificado met) 2026-06-30 18:12:22 +02:00
egutierrez a2074a0167 feat(eda): nueva fórmula de calidad de datos (report 2046) + capítulo calidad
Implementa el modelo de calidad del report 2046 en el grupo eda.

Score de columna: 0.6·completeness + 0.4·validity con renormalización por
aplicabilidad (si la validez no es medible —texto libre o columna 100% nula— el
score se basa solo en completeness). Validez = conformidad real al tipo: nativo
numérico/fecha/bool = 1.0; texto promovido a número/fecha = parse rate
(validity_rate); texto con semantic_type = match_rate; texto libre = no aplica.

Outliers, columnas constantes e identificadores salen del score a un bloque de
observaciones analíticas (no son defectos de calidad). Se elimina el doble
conteo de la falta de datos (mostly_null ya no castiga validez) y el bug de
escala de outliers (que además ya no entran en el score).

Score de dataset: 100·(0.85·cell_quality + 0.15·row_uniqueness) en vez de la
media simple. Se pobla duplicate_rows/duplicate_pct push-down en
summarize_table_duckdb (COUNT sobre DISTINCT *, sin RAM) para habilitar la
unicidad de registro; renormaliza a solo cell_quality si no se puede calcular.

Capítulo calidad (v2.0.0): intro de dos dimensiones (60/40) que declara que los
outliers no bajan el score; tabla de scores Columna|Calidad|Completitud|Validez
(sin Consistencia, n/a cuando no aplica); DOS tablas separadas (Problemas de
calidad vs Observaciones analíticas); resumen con Unicidad de registro; glosario
clicable de completitud, validez, unicidad de registro y calidad de datos.

Verificado: 123 tests verdes (automatic_eda + render_automatic_eda +
column_quality_score + summarize_table_duckdb + profile_table). Golden EDA de
titanic (run_models+run_llm) con score recomputado a mano, outliers separados en
observaciones y glosario clicable (5 links GOTO en el PDF).

column_quality_score v2.0.0, summarize_table_duckdb v1.1.0, profile_table v1.1.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:10:23 +02:00
egutierrez d001d90306 merge: 4b glosario_hooks — terminos clicables en correlacion/modelos/agregacion (12/12 PDF+PPT, verificado met) 2026-06-30 18:09:37 +02:00
egutierrez 7045f37554 fix(eda): quita rótulos duplicados en capítulo ANÁLISIS LLM
El capítulo etiquetaba dos secciones por partida doble: un Heading de nivel 2
más el 'title' del propio DataTable, imprimiendo 'Diccionario de datos' y
'Datos personales (PII / RGPD)' dos veces seguidas en PDF y PPTX.

Se elimina el 'title' de ambos DataTable y se conserva el Heading único (el
patrón canónico OVERVIEW del contrato §8: el rótulo lo da el Heading, la tabla
solo repite su cabecera de columnas al paginar). El DataTable de PII mantiene su
'note' orientativa. La columna del diccionario ya lee 'Significado de negocio'.

CHAPTER_VERSION 1.0.0 -> 1.1.0. Test nuevo
test_sin_rotulos_duplicados_y_significado_de_negocio fija: tablas sin title,
cabecera exacta 'Significado de negocio', y cada rótulo una sola vez en el PDF.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:07:12 +02:00
egutierrez fa8db01059 merge: 4b num_distr — desv std (sigma) en leyenda del histograma (verificado met) 2026-06-30 18:06:46 +02:00
egutierrez f2ac734ef7 merge: 4b head_rows — overview muestra df.head (build_eda_render_ctx pobla head_rows, verificado met) 2026-06-30 18:04:51 +02:00
egutierrez 048781df3f feat(eda): portada — tamaño grande + descripción/granularidad reales
El capítulo PORTADA ahora muestra SIEMPRE el tamaño del dataset (N filas ×
M columnas) en grande, como heading junto al nombre y agrupado con él
(Group keep-together), en lugar de enterrarlo en la tabla de metadatos.

La Descripción y la Granularidad ya no salen vacías ni con placeholders:
se resuelven por cascada — ctx explícito > bloque LLM (profile['llm'].summary
/ row_meaning de eda_llm_insights) > derivación del propio perfil (forma,
mezcla de tipos y score de calidad para la descripción; columnas
key_candidates o la forma de la tabla para una frase 'Cada fila es…').
Las derivaciones son honestas (declaran que vienen del perfil) y nunca
inventan significado de negocio.

Añade chapters/portada_test.py: golden (tamaño grande + textos del LLM,
sin fila 'Tamaño' duplicada), fallbacks sin LLM (keys / forma), prioridad
de ctx, edge de perfil vacío sin lanzar, y render a PDF + PPTX.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:04:05 +02:00
egutierrez a421f13d2e feat(eda): engancha glosario clicable en correlacion/modelos/agregacion
Fase 4b — extiende el glosario clicable de AutomaticEDA (mecanismo ya probado
end-to-end con `entropia` en cat_distr) a tres capítulos más, siguiendo el
contrato sección 11 (glossary.add(key,label,def) + span [[term:KEY]]texto[[/term]]):

- correlacion: Pearson, Spearman, Cramér's V, razón de correlación (η) y la
  corrección por comparaciones múltiples (FDR). Los métodos se marcan en el
  intro (siempre presente); FDR se registra y marca solo cuando se emite su
  resumen, para no dejar entradas de glosario sin aparición que las referencie.
- modelos: PCA, KMeans, coeficiente de silueta (silhouette), Isolation Forest y
  la estandarización z-score. Cada término se registra dentro de la sección que
  lo usa (tras su early-return), de modo que un término solo entra al glosario
  cuando su sección realmente se renderiza.
- agregacion: agrupación (split-apply-combine / groupby) y tabla dinámica
  (pivot), ambos en el intro siempre presente.

Solo se añaden los enganches de glosario: ningún cambio en la lógica de datos.
El texto visible es idéntico con o sin marcador (los renderers lo eliminan),
así que el layout de línea no cambia. Sin colector en ctx (render suelto) los
capítulos degradan y no marcan nada.

Tests: un test de glosario por capítulo verifica registro + marcado y la
degradación sin colector. Suite AutomaticEDA + render pipeline: 87 passed.
Golden titanic (run_models+series+llm): los 12 términos aparecen como entradas
del glosario en PDF (16 link annotations GOTO) y PPTX (15 saltos hlinksldjump).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:02:31 +02:00
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 7fb00defdf fix(fleetclaude): reusar contexto dentro de la flota tmux en vez de abrir kitty nueva
Lanzar `fleetclaude` estando ya dentro de una flota tmux viva abría una ventana
kitty nueva (y creaba un perfil/socket nuevo fleetN+1) en vez de mostrar la flota
en el pane actual. Causa: con $TMUX definido el launcher saltaba el `exec tmux
attach` y caía a la rama `setsid kitty`.

Cambio: cuando se invoca sin --new desde dentro de una flota fleetview viva (el
socket actual, derivado de $TMUX, tiene una sesión homónima con window 'console'),
se trae la TUI al contexto/pane actual (`fleetview show`, o `tmux select-window`
de la window console como fallback sin binario) y se retorna 0 antes de las ramas
kitty/wt.exe. Nuevo flag --new para forzar el comportamiento clásico (flota+ventana
nueva) aun dentro de tmux; pasar --session con un nombre distinto al perfil actual
equivale a --new implícito. Fuera de tmux el comportamiento es intacto (exec tmux
attach reutiliza la terminal).

Fix incidental: `local left_pane="" right_pane=""` (antes `local left_pane
right_pane` reventaba con "unbound variable" bajo `set -u` al reutilizar una sesión
existente, p. ej. con --reuse/--session sobre una flota viva).

Verificación e2e con sockets aislados fctest* (sin tocar la flota del humano):
golden (reuse, exit 0, kitty invariante), --new y --session-distinto (no reuse,
ruta ventana-nueva), fuera de tmux (salta reuse, ruta attach). bash -n limpio.

Docs: launch_fleetclaude.md (signature, params --new, ejemplo, cuando usarla,
gotchas, growth log v1.7.0) + /fleet show en .claude/commands/fleet.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:56:41 +02:00
egutierrez b1d205203a feat(eda): poblar head_rows real en el capitulo OVERVIEW (df.head)
El capitulo OVERVIEW del motor AutomaticEDA mostraba "df.head no disponible"
porque ninguna fase de calculo poblaba las primeras filas crudas de la tabla.

- build_eda_render_ctx: nuevo bloque que muestrea SELECT * LIMIT head_n
  (param nuevo head_n=10) y lo expone en ctx["head_rows"] como lista de
  dicts fila. Estilo dict-no-throw: si la query falla, se omite la clave.
- profile_table: puebla prof["head_rows"] reusando _sample_rows (SELECT de
  las columnas LIMIT 10) tras recalcular el type_breakdown. Asi el report
  JSON sidecar tambien lo lleva y el capitulo lo recoge via profile aunque
  no se construya el ctx.
- overview.py: la nota del DataTable de df.head ahora indica el total de
  filas del dataset cuando se conoce ("primeras 10 filas de 891"). Bump
  CHAPTER_VERSION 1.0.0 -> 1.1.0.
- overview_test.py (nuevo): golden (head via profile y via ctx, render PDF
  + PPTX muestran las filas reales, placeholder ausente), edge (sin
  head_rows degrada a nota honesta sin romper, None/vacio devuelven None).

Verificado end-to-end con titanic: render_automatic_eda emite PDF + PPTX con
df.head visible (Braund/Cumings/Heikkinen + columnas) y sin el placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:56:24 +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
248 changed files with 22689 additions and 462 deletions
+22 -10
View File
@@ -25,9 +25,11 @@ Página madre del grupo: `docs/capabilities/eda.md` (léela primero para cargar
- `--models``run_models=True` (PCA/KMeans/IsolationForest/normalidad). - `--models``run_models=True` (PCA/KMeans/IsolationForest/normalidad).
- `--llm``run_llm=True` (1 call LLM sobre el perfil agregado). - `--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). - `--series``run_series=True` (estacionariedad ADF+KPSS, ACF/PACF, STL, retornos por columna numérica).
- `--pdf``emit_pdf=True` (PDF A5 vertical legible en móvil). - `--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.
- `--lite` / `--bajo-consumo``render_automatic_eda(profile_level="lite")`: EDA barato y rápido (CI, vistazo previo, máquina sin GPU/red). Apaga LLM y serie temporal y limita los modelos a **PCA + normalidad** (sin KMeans ni IsolationForest, lo caro en CPU), con `sample` reducido. `--full``profile_level="full"` (standard + narrativa LLM). Por defecto `profile_level="standard"` (comportamiento histórico). Un flag explícito (`--llm`, `--models`, ...) prima sobre el preset.
Por defecto, para un EDA "completo" cuando el usuario no especifica, activa `run_models`, `run_series` y `emit_pdf`; deja `run_llm` para cuando lo pida o cuando interese la interpretación semántica (es la única parte que gasta tokens del modelo). 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 ## Reglas duras
@@ -35,7 +37,7 @@ Por defecto, para un EDA "completo" cuando el usuario no especifica, activa `run
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. 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. 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`). 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 4 salidas**: JSON sidecar + Markdown + **PDF móvil** + **notebook Jupyter colaborativo ejecutado en vivo**. 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 ## Paso 1 — Perfilar y escribir los reports
@@ -43,18 +45,27 @@ Una tabla (caso normal):
```bash ```bash
PYTHONPATH=python/functions python/.venv/bin/python3 - <<'PYEOF' PYTHONPATH=python/functions python/.venv/bin/python3 - <<'PYEOF'
from pipelines.profile_table import profile_table from pipelines.render_automatic_eda import render_automatic_eda
r = profile_table( # 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", "/ruta/datos.duckdb", "ventas",
run_models=True, run_series=True, emit_pdf=True, run_llm=False, profile_level="standard", # "lite" = bajo consumo CPU/LLM; "full" = + narrativa LLM
out_dir="reports",
) )
print("status:", r["status"]) print("status:", r["status"])
print("md: ", r["report_md_path"]) print("pdf: ", r["pdf_path"], "(", r["n_pages"], "págs )")
print("json: ", r["report_json_path"]) print("pptx: ", r["pptx_path"], "(", r["n_slides"], "slides )")
print("pdf: ", r["pdf_path"]) print("manifest:", r["manifest_path"])
PYEOF 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): Una base entera (todas las tablas + relaciones FK):
```bash ```bash
@@ -90,6 +101,7 @@ Sigue la memoria `eda-workflow-registry` y la regla `notebook_collaboration.md`:
## Notas ## 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 `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 PDF (`emit_pdf`) está pensado para leerse en el móvil (A5 vertical, tipografía grande, gráficos Tufte). Se escribe junto al Markdown en `reports/`. - 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. - `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. - Fuentes: DuckDB (CSV/Parquet/Excel cargados antes) y PostgreSQL (`backend="postgres"`). `profile_database` (multi-tabla + FK) es solo DuckDB por ahora.
+26 -3
View File
@@ -1,6 +1,6 @@
--- ---
description: Muestra la flota de Claudes vivos (sessionId + objetivo + estado) y, con argumento, salta con foco a esa conversación dentro de la sesión tmux fleet. description: Muestra la flota de Claudes vivos (sessionId + objetivo + estado) y, con argumento, salta con foco a esa conversación dentro de la sesión tmux fleet. `/fleet show` trae la TUI al contexto tmux actual.
argument-hint: "[texto|sessionId|PID para saltar — vacío = listar la flota]" argument-hint: "[show | texto|sessionId|PID para saltar — vacío = listar la flota]"
--- ---
# /fleet — ver y navegar la flota de Claudes # /fleet — ver y navegar la flota de Claudes
@@ -33,9 +33,32 @@ cd "${FN_REGISTRY_ROOT:-$HOME/fn_registry}/apps/fleetview" && go build -o fleetv
- la sesión actual / orquestador si la puedes identificar (su `session_id` coincide con el de quien invoca). - la sesión actual / orquestador si la puedes identificar (su `session_id` coincide con el de quien invoca).
4. Si la lista está vacía, indícalo y sugiere que el perfil fleet podría no estar activo (revisar `$FLEET_SOCKET` y que la sesión tmux exista). 4. Si la lista está vacía, indícalo y sugiere que el perfil fleet podría no estar activo (revisar `$FLEET_SOCKET` y que la sesión tmux exista).
### `show` → traer la TUI al contexto tmux actual
Si `$ARGUMENTS` es exactamente `show` (alias `open`/`attach`), el usuario quiere
volver a ver el panel FleetView en el contexto/pane actual sin abrir ninguna
ventana ni arrancar una flota nueva. Ejecuta:
```bash
"${FN_REGISTRY_ROOT:-$HOME/fn_registry}/apps/fleetview/fleetview" show
```
Comportamiento (decidido por la app, no abre terminal externa):
- **dentro de tmux con la flota viva** → `select-window` de la window `console`
del socket fleet (trae la TUI al frente; no abre nada).
- **fuera de tmux** → `attach` a la sesión fleet en la terminal actual (la reutiliza).
- **sin flota viva** → error claro, exit 1, no abre nada (sugiere arrancarla con
`fleetclaude`).
Es el equivalente del comportamiento de `fleetclaude` sin args invocado dentro de
una flota viva (reuse de contexto): úsalo cuando ya tengas una flota corriendo y
solo quieras recuperar la vista del panel. Para abrir una flota NUEVA aparte, usa
`fleetclaude --new` (no este comando).
### Con argumentos → saltar con foco ### Con argumentos → saltar con foco
El usuario quiere que la interfaz tmux salte a una conversación concreta. `$ARGUMENTS` es el query: texto del objetivo, prefijo de `sessionId`, o PID. El usuario quiere que la interfaz tmux salte a una conversación concreta. `$ARGUMENTS` es el query: texto del objetivo, prefijo de `sessionId`, o PID (cualquier valor que no sea `show`).
1. Ejecuta: 1. Ejecuta:
```bash ```bash
+3 -2
View File
@@ -31,12 +31,13 @@ Diferencia con `dev/flows/`:
**Fase 1 (manual via Claude):** **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 ```python
import yaml, pathlib, re import yaml, pathlib, re
issues = [] 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 if f.name in {"README.md", "template.md"}: continue
txt = f.read_text() txt = f.read_text()
m = re.match(r"^---\n(.*?)\n---", txt, re.S) m = re.match(r"^---\n(.*?)\n---", txt, re.S)
+3 -1
View File
@@ -9,7 +9,9 @@
"enabledMcpjsonServers": [ "enabledMcpjsonServers": [
"registry", "registry",
"jupyter", "jupyter",
"orchestrator" "orchestrator",
"godot",
"ardour"
], ],
"hooks": { "hooks": {
"PreToolUse": [ "PreToolUse": [
+96 -30
View File
@@ -3,23 +3,25 @@ name: launch_fleetclaude
kind: function kind: function
lang: bash lang: bash
domain: infra domain: infra
version: "1.5.0" version: "1.7.0"
purity: impure purity: impure
signature: "launch_fleetclaude [--cwd <dir>] [--bin <path>] [--session <name>] [--reuse] [--cols <n>]" signature: "launch_fleetclaude [--cwd <dir>] [--bin <path>] [--session <name>] [--reuse] [--new] [--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." 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. REUSO DE CONTEXTO: si se invoca DENTRO de una flota tmux viva (su window 'console') sin --new, NO abre ventana ni crea un perfil nuevo; trae la TUI al pane/contexto actual (equivale a 'fleetview show'). El flag --new fuerza una flota+ventana nueva aunque estes en tmux. 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: fuera de tmux, o con --new, 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] tags: [claude-fleet, infra, kitty, tmux, claude, fleetview, launcher, wsl, windows-terminal]
params: params:
- name: --cwd - 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)." 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)."
- name: --bin - name: --bin
desc: "Ruta al binario de la TUI fleetview que corre en el pane izquierdo. Opcional. Default: <repo>/apps/fleetview/fleetview. Si no es ejecutable, el pane izquierdo muestra un mensaje de como compilarla y deja una shell viva." desc: "Ruta al binario de la TUI fleetview que corre en el pane izquierdo. Opcional. Default: <repo>/apps/fleetview/fleetview. Si no es ejecutable, el pane izquierdo muestra un mensaje de como compilarla y deja una shell viva."
- name: --session - name: --session
desc: "Fija el perfil (socket+sesion tmux comparten nombre) por nombre exacto; reutiliza el existente si ya vive (idempotente sobre ese nombre). Opcional. Sin esta opcion, el perfil se elige automaticamente (primer nombre libre de la secuencia fleet, fleet2, ...)." desc: "Fija el perfil (socket+sesion tmux comparten nombre) por nombre exacto; reutiliza el existente si ya vive (idempotente sobre ese nombre). Opcional. Sin esta opcion, el perfil se elige automaticamente (primer nombre libre de la secuencia fleet, fleet2, ...). Invocado DENTRO de tmux con un nombre DISTINTO al de la flota actual equivale a --new (pides otra flota: ventana nueva, sin reuse de contexto)."
- name: --reuse - name: --reuse
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)." 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: --new
desc: "Fuerza una flota NUEVA en una ventana NUEVA (kitty/wt.exe) incluso estando dentro de una flota tmux. Opcional. Es la via explicita para abrir una FleetView aparte; sin este flag, invocado dentro de una flota viva se reusa el contexto actual (no abre ventana ni crea perfil)."
- name: --cols - name: --cols
desc: "Ancho en columnas del pane izquierdo (la TUI). Opcional. Default: 40." 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: "Caso reuse de contexto (dentro de una flota tmux viva, sin --new): trae la TUI al pane/contexto actual con select-window de la window 'console' (o 'fleetview show' si el binario existe) y retorna 0, sin abrir nada. Caso ventana-nueva (fuera de tmux, o con --new): crea/reutiliza una sesion tmux detached con dos panes y lanza una ventana de terminal 'FleetView' adjunta (kitty o Windows Terminal segun auto-deteccion), desacoplada del shell padre. Imprime el estado por stdout. Sin valor de retorno; exit 0 en exito, !=0 con mensaje claro si no hay terminal ni contexto que reusar."
uses_functions: uses_functions:
- supervise_fleetview_tui_bash_infra - supervise_fleetview_tui_bash_infra
uses_types: [] uses_types: []
@@ -36,32 +38,44 @@ file_path: "bash/functions/infra/launch_fleetclaude.sh"
## Ejemplo ## Ejemplo
```bash ```bash
# Via fn run (resuelve por nombre o ID): # DENTRO de una flota tmux viva (p. ej. en el pane del orquestador): reusa el
fn run launch_fleetclaude # contexto, trae la TUI al pane actual. NO abre ventana ni crea perfil nuevo.
fleetclaude
# Perfil nuevo automatico (fleet la 1a vez; fleet2, fleet3, ... si ya hay uno): # FUERA de tmux: perfil nuevo automatico (fleet la 1a vez; fleet2, ... si ya hay
launch_fleetclaude # uno) en una ventana de terminal nueva, reutilizando la terminal actual (attach):
fleetclaude
# Forzar una flota+ventana NUEVA aunque estes dentro de una flota tmux:
fleetclaude --new
# Reattach a la flota principal 'fleet' (comportamiento idempotente clasico): # Reattach a la flota principal 'fleet' (comportamiento idempotente clasico):
launch_fleetclaude --reuse fleetclaude --reuse
# Perfil con nombre fijo y ancho de pane personalizado: # Perfil con nombre fijo y ancho de pane personalizado:
launch_fleetclaude --session trabajo --cols 50 fleetclaude --session trabajo --cols 50
# Via fn run (resuelve por nombre o ID):
fn run launch_fleetclaude
``` ```
Tras invocarlo aparece una ventana kitty titulada `FleetView (<perfil>)` con dos Dentro de una flota viva, `fleetclaude` sin args reusa el contexto (la window
panes lado a lado: a la izquierda la TUI `fleetview`, a la derecha una sesion de `console` pasa al frente). Fuera de tmux (o con `--new`) aparece una ventana de
`claude --dangerously-skip-permissions`. Cada perfil es un socket+sesion tmux terminal titulada `FleetView (<perfil>)` con dos panes lado a lado: a la izquierda
aislados con su propia flota: puedes tener varias FleetView abiertas a la vez. la TUI `fleetview`, a la derecha una sesion de `claude --dangerously-skip-permissions`.
Por defecto, volver a invocarlo abre un perfil NUEVO (no reusa); usa `--reuse` Cada perfil es un socket+sesion tmux aislados con su propia flota: puedes tener
o `--session <nombre>` para volver a una flota concreta. varias FleetView abiertas a la vez con `--new`.
## Cuando usarla ## Cuando usarla
Usala cuando quieras un unico punto de entrada a la flota de Claudes en vez de Usala cuando quieras un unico punto de entrada a la flota de Claudes en vez de
N ventanas kitty sueltas: lanzas `fleetclaude` y tienes la TUI de control y un N ventanas kitty sueltas: lanzas `fleetclaude` y tienes la TUI de control y un
Claude listo para trabajar en la misma ventana. Tipico al empezar la jornada o Claude listo para trabajar en la misma ventana. Tipico al empezar la jornada o
al retomar el trabajo en el repo `fn_registry`. al retomar el trabajo en el repo `fn_registry`. Si **ya estas dentro de una
flota** (en el pane del orquestador) y solo quieres volver a ver la TUI, lanza
`fleetclaude` sin args: trae el panel al contexto actual sin abrir otra ventana
ni arrancar una flota duplicada. Usa `--new` solo cuando quieras DELIBERADAMENTE
una segunda flota aparte.
## Gotchas ## Gotchas
@@ -78,12 +92,41 @@ al retomar el trabajo en el repo `fn_registry`.
`respawn-pane` de alt+R y los Claude nuevos hereden el socket). `main.go` los `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 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). (cruza la lista del sistema con los panes de su socket).
- **Dentro de tmux abre ventana nueva**: si invocas `fleetclaude` desde dentro de - **Auto-deteccion de terminal (sin config por PC)**: en la ruta ventana-nueva el
una sesion tmux (`$TMUX` definido), NO hace `attach` anidado (rompe / avisa de launcher elige terminal solo. (1) `kitty` instalado **y** display usable
nesting); cae a la ruta kitty y abre una ventana nueva. Fuera de tmux y con (`$DISPLAY`/`$WAYLAND_DISPLAY`) → kitty (escritorio Linux nativo o WSLg con
TTY, reutiliza la terminal actual con `exec tmux attach`. kitty). (2) Si no, WSL con `wt.exe` en el PATH → Windows Terminal ejecutando
- **kitty detached (setsid)**: la ventana se lanza con `setsid ... &` para `wsl.exe [-d $WSL_DISTRO_NAME] -- bash -lic 'tmux -L <perfil> attach ...'`.
sobrevivir al cierre de la terminal que la invoco. No bloquea al shell padre. (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 una flota tmux viva: reuse de contexto (no ventana nueva)**: si
invocas `fleetclaude` sin `--new` desde dentro de una flota fleetview viva
(`$TMUX` definido y el socket actual tiene una sesion homonima con window
`console`), NO abre ventana ni crea un perfil `fleetN+1`: trae la TUI al pane
actual (`fleetview show`, o `tmux -L <perfil> select-window -t <perfil>:console`
si el binario no esta compilado) y retorna 0. El perfil de la flota actual se
deriva de `$TMUX` (basename del socket = nombre `-L`), senal fiable aunque
`$FLEET_SOCKET` venga vacio (ver `detect_fleet_context`). **`--new`** fuerza el
comportamiento clasico (flota+ventana nueva); pasar `--session <otro>` distinto
al perfil actual equivale a `--new` implicito. Fuera de tmux y con TTY, reutiliza
la terminal actual con `exec tmux attach` (nunca `attach` anidado dentro de
tmux). Sin TTY ni contexto que reusar (atajo de escritorio/cron) cae a la ruta
ventana-nueva. Antes de este fix (v1.6.0 y anteriores) cualquier `fleetclaude`
dentro de tmux abria una kitty nueva y un socket `fleetN+1` — el sintoma que
acumulaba 6+ sockets `fleet*`.
- **`local x` unbound bajo `set -u`**: el archivo corre con `set -euo pipefail`.
`local left_pane right_pane` dejaba esas vars *unbound* (no vacias), asi que la
rama "reutilizar sesion existente" (`--reuse`/`--session <vivo>`) reventaba con
`left_pane: unbound variable` al evaluar `[[ -z "$left_pane" ]]`. Se inicializan
explicitamente a `""` (`local left_pane="" right_pane=""`). Si tocas estas vars,
no vuelvas a declararlas sin valor.
- **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 - **TUI bajo supervisor (auto-respawn)**: el pane izquierdo NO corre un
`exec fleetview` de una sola vida, sino `supervise_fleetview_tui` (bucle que `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 relanza la TUI si muere por crash/panic/kill). Asi el panel de control nunca se
@@ -116,14 +159,37 @@ al retomar el trabajo en el repo `fn_registry`.
- **Ancho del sidebar via hooks**: `client-resized` y `window-layout-changed` - **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 re-fijan el pane 0 (TUI) a `--cols` columnas, porque el `attach` de kitty y el
conmutar de Claude redistribuyen el espacio. conmutar de Claude redistribuyen el espacio.
- **tmux siempre, kitty solo sin TTY**: `tmux` es obligatorio (aborta != 0 si - **tmux siempre; terminal (kitty/wt.exe) solo en la ruta ventana-nueva**: `tmux`
falta). `kitty` solo se necesita en la ruta sin-TTY (atajo de escritorio, cron, es obligatorio (aborta != 0 si falta). Una terminal nueva (kitty o Windows
script), donde abre una ventana nueva. Invocado desde una terminal interactiva Terminal) solo se necesita en la ruta ventana-nueva: `--new`, o sin TTY ni flota
(el caso normal del alias `fleetclaude`), reutiliza la terminal actual con viva que reusar (atajo de escritorio, cron, script). Dentro de una flota viva sin
`exec tmux attach` y NO necesita kitty — util en WSL u hosts sin kitty. `--new` se reusa el contexto (ni kitty ni wt.exe). Invocado desde una terminal
interactiva fuera de tmux (el caso normal del alias `fleetclaude`), reutiliza la
terminal actual con `exec tmux attach` y tampoco necesita kitty ni wt.exe.
## Capability growth log ## Capability growth log
- v1.7.0 (2026-06-30) — **reuse de contexto dentro de la flota + flag `--new`**.
Invocado sin `--new` desde dentro de una flota tmux viva (su window `console`),
`fleetclaude` ya NO abre una kitty nueva ni crea un perfil `fleetN+1`: trae la
TUI al pane/contexto actual (`fleetview show`, o `tmux -L <perfil> select-window
-t <perfil>:console` como fallback sin binario) y retorna 0. El perfil actual se
deriva de `$TMUX` (basename del socket); pasar `--session <otro>` distinto al
actual equivale a `--new` implicito. Nuevo flag `--new` para forzar la ruta
clasica (flota+ventana nueva) aun dentro de tmux. Fuera de tmux el comportamiento
es intacto (`exec tmux attach` reutiliza la terminal). Arregla el sintoma de que
lanzar `fleetclaude` dentro de una flota abria ventana kitty + socket nuevo
(`fleet7`, `fleet8`, ...). Fix incidental: `local left_pane="" right_pane=""`
(antes `local left_pane right_pane` reventaba con `unbound variable` bajo
`set -u` al reutilizar una sesion existente).
- 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 - 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 `exec fleetview` (una sola vida), sino el bucle supervisor
`supervise_fleetview_tui`, que relanza la TUI si muere (crash/panic/kill de su `supervise_fleetview_tui`, que relanza la TUI si muere (crash/panic/kill de su
+105 -16
View File
@@ -23,6 +23,7 @@ launch_fleetclaude() {
local cols=52 local cols=52
local explicit_session=0 # 1 si el usuario pasó --session <name> a mano local explicit_session=0 # 1 si el usuario pasó --session <name> a mano
local reuse=0 # 1 si el usuario pidió --reuse (reattach al perfil principal) local reuse=0 # 1 si el usuario pidió --reuse (reattach al perfil principal)
local want_new=0 # 1 si el usuario pidió --new (forzar flota+ventana nueva)
local T="" # socket tmux aislado; se fija al resolver el perfil local T="" # socket tmux aislado; se fija al resolver el perfil
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@@ -46,6 +47,9 @@ launch_fleetclaude() {
--reuse) --reuse)
reuse=1 reuse=1
;; ;;
--new)
want_new=1
;;
--cols) --cols)
shift shift
cols="${1:-40}" cols="${1:-40}"
@@ -62,6 +66,11 @@ Claudes). Sin --session ni --reuse, cada invocacion abre un perfil NUEVO: usa
el primer nombre libre de la secuencia fleet, fleet2, fleet3, ... Asi puedes el primer nombre libre de la secuencia fleet, fleet2, fleet3, ... Asi puedes
tener varias FleetView abiertas a la vez, cada una con su flota independiente. tener varias FleetView abiertas a la vez, cada una con su flota independiente.
REUSO DE CONTEXTO: si ya estas DENTRO de una flota tmux viva (p. ej. en el pane
del orquestador), 'fleetclaude' sin args NO abre una ventana ni crea un perfil
nuevo: trae la TUI al contexto/pane actual (equivale a 'fleetview show'). Para
abrir explicitamente una flota aparte en una ventana nueva, usa --new.
Opciones: Opciones:
--cwd <dir> Directorio de trabajo de los panes. --cwd <dir> Directorio de trabajo de los panes.
Default: raiz del repo fn_registry (derivada dinamicamente). Default: raiz del repo fn_registry (derivada dinamicamente).
@@ -69,13 +78,21 @@ Opciones:
Default: <repo>/apps/fleetview/fleetview Default: <repo>/apps/fleetview/fleetview
--session <name> Fija el perfil (socket+sesion) por nombre exacto; reutiliza --session <name> Fija el perfil (socket+sesion) por nombre exacto; reutiliza
el existente si ya esta vivo. Sin esta opcion, perfil auto. el existente si ya esta vivo. Sin esta opcion, perfil auto.
Si se invoca DENTRO de tmux con un nombre DISTINTO al de la
flota actual, equivale a --new (pides otra flota).
--reuse Reattach al perfil principal 'fleet' en vez de abrir uno --reuse Reattach al perfil principal 'fleet' en vez de abrir uno
nuevo (vuelve al comportamiento idempotente clasico). nuevo (vuelve al comportamiento idempotente clasico).
--new Fuerza una flota NUEVA en una ventana NUEVA (kitty/wt.exe),
incluso dentro de tmux. Es la via explicita para tener una
FleetView aparte; sin este flag, dentro de tmux se reusa el
contexto actual.
--cols <n> Ancho (columnas) del pane izquierdo. Default: 40. --cols <n> Ancho (columnas) del pane izquierdo. Default: 40.
-h, --help Muestra esta ayuda. -h, --help Muestra esta ayuda.
Ejemplos: Ejemplos:
launch_fleetclaude # perfil nuevo (fleet, luego fleet2, ...) launch_fleetclaude # dentro de la flota: reusa el contexto;
# fuera de tmux: perfil nuevo (fleet, ...)
launch_fleetclaude --new # flota+ventana nueva aunque estes en tmux
launch_fleetclaude --reuse # reattach a la flota principal 'fleet' launch_fleetclaude --reuse # reattach a la flota principal 'fleet'
launch_fleetclaude --session trabajo # perfil con nombre fijo 'trabajo' launch_fleetclaude --session trabajo # perfil con nombre fijo 'trabajo'
launch_fleetclaude --cwd ~/fn_registry --cols 50 launch_fleetclaude --cwd ~/fn_registry --cols 50
@@ -127,6 +144,45 @@ USAGE
return 1 return 1
fi fi
# -----------------------------------------------------------------------
# REUSO DE CONTEXTO (sin --new): si ya estamos DENTRO de una flota tmux
# viva, 'fleetclaude' sin args NO abre una ventana/terminal nueva ni crea
# un perfil fleetN+1 — trae la TUI al contexto/pane actual, igual que
# 'fleetview show'. El flag --new fuerza el comportamiento clasico (flota
# nueva en ventana nueva); --reuse mantiene su semantica historica.
#
# El perfil de la flota actual se deriva de $TMUX (el basename del socket
# es el nombre -L; senal fiable aunque $FLEET_SOCKET venga vacio, ver
# detect_fleet_context). Si se paso --session con un nombre DISTINTO al
# actual, es pedir OTRA flota -> se trata como --new implicito (no reusa).
# "Flota viva" = el socket tiene una sesion homonima con una window
# 'console' (la firma de una FleetView), no un tmux cualquiera.
# -----------------------------------------------------------------------
if [[ "$want_new" -eq 0 && "$reuse" -eq 0 && -n "${TMUX:-}" ]]; then
local current_socket target_socket
current_socket="$(basename "${TMUX%%,*}")"
target_socket="$current_socket"
[[ "$explicit_session" -eq 1 ]] && target_socket="$session"
if [[ "$target_socket" == "$current_socket" ]] \
&& tmux -L "$current_socket" has-session -t "$current_socket" 2>/dev/null \
&& tmux -L "$current_socket" list-windows -t "$current_socket" \
-F '#{window_name}' 2>/dev/null | grep -qx console; then
# Traer la TUI al contexto actual sin abrir nada nuevo. Preferimos
# el binario (centraliza la politica en la app: 'fleetview show');
# si no esta compilado, caemos a 'select-window' directo, que es lo
# que 'show' hace por dentro dentro de tmux (cero dependencia).
if [[ -x "$bin" ]] \
&& FLEET_SOCKET="$current_socket" FLEET_SESSION="$current_socket" \
"$bin" show 2>/dev/null; then
return 0
fi
tmux -L "$current_socket" select-window -t "$current_socket":console
echo "launch_fleetclaude: flota '$current_socket' viva; TUI traida al contexto actual (sin ventana nueva)."
return 0
fi
fi
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Resolver el PERFIL (socket+sesion tmux comparten nombre). # Resolver el PERFIL (socket+sesion tmux comparten nombre).
# #
@@ -200,7 +256,10 @@ USAGE
# indice 1 y cualquier referencia a console.0 falla con # indice 1 y cualquier referencia a console.0 falla con
# "can't find pane: 0". Los pane ID son estables e inmunes al base-index. # "can't find pane: 0". Los pane ID son estables e inmunes al base-index.
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
local left_pane right_pane # Inicializadas a "" (no solo declaradas): bajo `set -u` una `local x` sin
# valor queda *unbound*, y al reutilizar una sesion existente el `[[ -z
# "$left_pane" ]]` de mas abajo reventaba con "unbound variable".
local left_pane="" right_pane=""
if $T has-session -t "$session" 2>/dev/null; then if $T has-session -t "$session" 2>/dev/null; then
echo "launch_fleetclaude: la sesion tmux '$session' ya existe; reutilizandola." echo "launch_fleetclaude: la sesion tmux '$session' ya existe; reutilizandola."
else else
@@ -294,31 +353,61 @@ USAGE
$T set-hook -g window-layout-changed "resize-pane -t $left_pane -x $cols" $T set-hook -g window-layout-changed "resize-pane -t $left_pane -x $cols"
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Lanzar kitty adjuntando la sesion, DESACOPLADA del shell padre con # Adjuntar la sesion en una terminal, DESACOPLADA del shell padre para que
# setsid, para que no muera al cerrar la terminal invocadora. # no muera al cerrar la terminal invocadora.
# (Mismo patron que reboot_all_claudes para relanzar terminales.)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Adjuntar la sesion: # Adjuntar la sesion:
# - Terminal interactiva y FUERA de tmux: convertir ESA terminal en el # - Terminal interactiva y FUERA de tmux: convertir ESA terminal en el
# panel FleetView (exec reemplaza el proceso; al hacer detach vuelve la # panel FleetView (exec reemplaza el proceso; al hacer detach vuelve la
# shell). Asi `fleetclaude` no abre otra ventana: usa la actual. # shell). Asi `fleetclaude` no abre otra ventana: usa la actual.
# - DENTRO de tmux (o sin TTY: atajo de escritorio, cron, script): abrir # - 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). # anidado dentro de otra sesion tmux (rompe / da el warning de nesting).
if [ -t 0 ] && [ -t 1 ] && [ -z "${TMUX:-}" ]; then if [ -t 0 ] && [ -t 1 ] && [ -z "${TMUX:-}" ]; then
exec tmux -L "$session" attach -t "$session" exec tmux -L "$session" attach -t "$session"
fi 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). # 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 Default string // empty if required
IsKwargs bool // **kwargs IsKwargs bool // **kwargs
IsRegistry bool // type is a registry type (needs factory) 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. // 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 // Split by comma, respecting nested brackets
parts := splitParams(raw) parts := splitParams(raw)
var params []pyParam var params []pyParam
kwOnly := false
for _, part := range parts { for _, part := range parts {
part = strings.TrimSpace(part) part = strings.TrimSpace(part)
if part == "" || part == "self" || part == "cls" { if part == "" || part == "self" || part == "cls" {
continue 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 := parseSingleParam(part)
p.KwOnly = kwOnly
params = append(params, p) params = append(params, p)
} }
return params return params
@@ -189,11 +199,19 @@ func generatePyRunner(fn *registry.Function, db *registry.DB, registryRoot strin
// Classify params // Classify params
var factoryImports []string // import lines for factories var factoryImports []string // import lines for factories
var factorySetup []string // code to create factory objects var factorySetup []string // code to create factory objects
var argLines []string // code to parse CLI args var bodyLines []string // code that fills _call_args / _call_kwargs
var callArgs []string // arguments to pass to the function
cliArgIdx := 0 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 { for _, p := range params {
if p.IsKwargs { if p.IsKwargs {
// Skip **kwargs for now — can't auto-resolve from CLI // 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, fmt.Sprintf("%s = %s(%s)", p.Name, factory.FuncName,
strings.Join(factoryArgs, ", "))) strings.Join(factoryArgs, ", ")))
callArgs = append(callArgs, p.Name) // Factory objects are always present (required).
bodyLines = append(bodyLines, emitCall(p, ""))
} else { } else {
// Primitive type — from CLI args // Primitive type — from CLI args.
if p.Default != "" { if p.Default != "" {
// Optional param with default // Optional: only pass when the CLI arg is present. When absent we
argLines = append(argLines, // DON'T replicate the signature default (it may reference a module
fmt.Sprintf("%s = _args[%d] if len(_args) > %d else %s", // constant that doesn't exist in this runner) — we simply omit the
p.Name, cliArgIdx, cliArgIdx, convertDefault(p.Type, p.Default))) // argument so the function applies its own native default.
argLines = append(argLines, bodyLines = append(bodyLines,
convertArg(p.Name, p.Type, true)) 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 { } else {
// Required param // Required param.
argLines = append(argLines, bodyLines = append(bodyLines,
fmt.Sprintf("if len(_args) <= %d: sys.exit('error: missing required arg: %s (%s)')", fmt.Sprintf("if len(_args) <= %d: sys.exit('error: missing required arg: %s (%s)')",
cliArgIdx, p.Name, p.Type)) cliArgIdx, p.Name, p.Type))
argLines = append(argLines, bodyLines = append(bodyLines,
fmt.Sprintf("%s = _args[%d]", p.Name, cliArgIdx)) fmt.Sprintf("%s = _args[%d]", p.Name, cliArgIdx))
argLines = append(argLines, if conv := convertArg(p.Name, p.Type, false); conv != "" {
convertArg(p.Name, p.Type, false)) bodyLines = append(bodyLines, conv)
}
bodyLines = append(bodyLines, emitCall(p, ""))
} }
callArgs = append(callArgs, p.Name)
cliArgIdx++ cliArgIdx++
} }
} }
@@ -289,18 +315,18 @@ func generatePyRunner(fn *registry.Function, db *registry.DB, registryRoot strin
sb.WriteString("\n") sb.WriteString("\n")
} }
// Arg parsing // Arg parsing — build the positional/keyword argument collections.
if len(argLines) > 0 { sb.WriteString("# --- parse CLI args ---\n")
sb.WriteString("# --- parse CLI args ---\n") sb.WriteString("_call_args = []\n")
for _, line := range argLines { sb.WriteString("_call_kwargs = {}\n")
sb.WriteString(line + "\n") for _, line := range bodyLines {
} sb.WriteString(line + "\n")
sb.WriteString("\n")
} }
sb.WriteString("\n")
// Call // Call
sb.WriteString("# --- execute ---\n") 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") sb.WriteString("\n")
// Output // 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"] // pythonList creates a Python list literal from strings: ["a", "b", "c"]
func pythonList(items []string) string { func pythonList(items []string) string {
quoted := make([]string, len(items)) 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: [] related: []
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
# 0051 — Funciones pendientes del pipeline de extraccion (NER+RE+OpenIE) # 0051 — Funciones pendientes del pipeline de extraccion (NER+RE+OpenIE)
@@ -13,7 +13,7 @@ blocks: []
related: [] related: []
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
# 0054 — deploy_server: refactor registry-first (SSH/systemd/rsync/health/docker-compose) # 0054 — deploy_server: refactor registry-first (SSH/systemd/rsync/health/docker-compose)
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
# 0055 — docker_tui: refactor para usar funciones docker_* del registry # 0055 — docker_tui: refactor para usar funciones docker_* del registry
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
# 0056 — audit_uses_functions: detectar imports Python anidados (`from pkg.subpkg import X`) # 0056 — audit_uses_functions: detectar imports Python anidados (`from pkg.subpkg import X`)
+1 -1
View File
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
# 0057 — audit_uses_functions: mejorar deteccion de simbolos Go con abreviaturas # 0057 — audit_uses_functions: mejorar deteccion de simbolos Go con abreviaturas
@@ -11,7 +11,7 @@ blocks: []
related: [] related: []
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
# 0060 — `fn doctor secrets`: scan de secrets en TODOS los repos # 0060 — `fn doctor secrets`: scan de secrets en TODOS los repos
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
# 0061 — Integrar `notify_telegram` en deploy_server + bucle reactivo # 0061 — Integrar `notify_telegram` en deploy_server + bucle reactivo
@@ -7,8 +7,7 @@ domain:
- registry-quality - registry-quality
scope: registry-only scope: registry-only
priority: alta priority: alta
depends: depends: ["0071f"]
- "0071f"
blocks: [] blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
+1 -2
View File
@@ -7,8 +7,7 @@ domain:
- registry-quality - registry-quality
scope: registry-only scope: registry-only
priority: media priority: media
depends: depends: ["0071f"]
- "0071f"
blocks: [] blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
## Contexto ## Contexto
+1 -1
View File
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
## Contexto ## Contexto
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
## Sintoma ## Sintoma
+1 -1
View File
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
## Sintoma ## Sintoma
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
# 0100 — Migrar frontmatter inline a YAML canonico en dev/issues/ # 0100 — Migrar frontmatter inline a YAML canonico en dev/issues/
@@ -16,7 +16,7 @@ related:
- "0103" - "0103"
created: 2026-05-17 created: 2026-05-17
updated: 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 # 0104 — `/fix-issue` type-aware dispatch
+1 -1
View File
@@ -16,7 +16,7 @@ related:
- "0107" - "0107"
created: 2026-05-17 created: 2026-05-17
updated: 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 # 0107e — Version pinning + codegen fail-loud
@@ -15,12 +15,7 @@ related:
- "0109" - "0109"
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: tags: [ausente-ready, skill-tree, cpp, imgui, dashboard, gamification]
- skill-tree
- cpp
- imgui
- dashboard
- gamification
--- ---
# 0109k — Dashboard panel # 0109k — Dashboard panel
+1 -7
View File
@@ -16,13 +16,7 @@ related:
- "0106" - "0106"
created: 2026-05-18 created: 2026-05-18
updated: 2026-05-18 updated: 2026-05-18
tags: tags: [ausente-ready, service, go, http, issues, flows, api]
- service
- go
- http
- issues
- flows
- api
--- ---
# 0109m — issues_api service # 0109m — issues_api service
+1 -1
View File
@@ -16,7 +16,7 @@ related:
- "0068" - "0068"
created: 2026-05-18 created: 2026-05-18
updated: 2026-05-19 updated: 2026-05-19
tags: [e2e_checks, recopilador, batch, coverage, epic] tags: [e2e_checks, recopilador, batch, coverage, epic, ausente-ready]
--- ---
# Sub-issues # Sub-issues
+1 -1
View File
@@ -16,7 +16,7 @@ related:
- "0068" - "0068"
created: 2026-05-19 created: 2026-05-19
updated: 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 # 0121a — Design-e2e batch
+1 -3
View File
@@ -7,9 +7,7 @@ domain:
- registry-quality - registry-quality
scope: registry scope: registry
priority: media priority: media
depends: depends: ["0121a"]
- "0121a"
- "0121b"
blocks: blocks:
- "0122" - "0122"
related: related:
+1 -1
View File
@@ -17,7 +17,7 @@ related:
- "0086" - "0086"
created: 2026-05-18 created: 2026-05-18
updated: 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 # 0122 — fn-revisor + ampliar filtro auto-aplicable del orquestador
+1 -1
View File
@@ -13,7 +13,7 @@ related:
- "0121a" - "0121a"
created: 2026-05-19 created: 2026-05-19
updated: 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 # 0124 — dag_engine cleanup
+1 -1
View File
@@ -13,7 +13,7 @@ related:
- "0121a" - "0121a"
created: 2026-05-19 created: 2026-05-19
updated: 2026-05-19 updated: 2026-05-19
tags: [deploy_server, cli, idempotency] tags: [deploy_server, cli, idempotency, ausente-ready]
--- ---
# 0125 — deploy_server `--db` flag # 0125 — deploy_server `--db` flag
+1 -1
View File
@@ -1,7 +1,7 @@
--- ---
id: "0128" id: "0128"
title: "kanban: adjuntar archivos (drag&drop desc/chat + tab Archivos)" title: "kanban: adjuntar archivos (drag&drop desc/chat + tab Archivos)"
status: in_progress status: in-progress
type: feature type: feature
domain: domain:
- apps-tools - apps-tools
+1 -6
View File
@@ -13,12 +13,7 @@ blocks:
- 0130b - 0130b
related: related:
- "0130" - "0130"
tags: tags: [registry, go, parser, frontmatter, fsnotify, ausente-ready]
- registry
- go
- parser
- frontmatter
- fsnotify
flow: "0130" flow: "0130"
created: "2026-05-22" created: "2026-05-22"
updated: "2026-05-22" updated: "2026-05-22"
+1 -2
View File
@@ -8,8 +8,7 @@ domain:
- dev-ux - dev-ux
scope: app-scoped scope: app-scoped
priority: alta priority: alta
depends: depends: ["0130a"]
- "0130a"
blocks: blocks:
- "0130c" - "0130c"
related: related:
+2 -2
View File
@@ -1,14 +1,14 @@
--- ---
id: "0134" id: "0134"
title: "Mesh protocol spec: capability manifests, ed25519 envelopes, enrollment, audit chain" title: "Mesh protocol spec: capability manifests, ed25519 envelopes, enrollment, audit chain"
status: pending status: pendiente
type: spec type: spec
domain: domain:
- infra - infra
- cybersecurity - cybersecurity
- protocols - protocols
scope: cross-app scope: cross-app
priority: high priority: alta
depends: [] depends: []
blocks: blocks:
- "0135" - "0135"
+2 -2
View File
@@ -1,7 +1,7 @@
--- ---
id: "0144" id: "0144"
title: "Agent LLM per machine (user + sudo) con tool registry y mesh dispatch" title: "Agent LLM per machine (user + sudo) con tool registry y mesh dispatch"
status: pending status: pendiente
type: spec type: spec
domain: domain:
- agents - agents
@@ -9,7 +9,7 @@ domain:
- infra - infra
- cybersecurity - cybersecurity
scope: multi-app scope: multi-app
priority: high priority: alta
depends: depends:
- "0134" - "0134"
- "0140" - "0140"
@@ -1,8 +1,8 @@
--- ---
id: "0146" id: "0146"
title: "add-pc one-shot: añade PC al mesh + agente LLM en <2min desde movil" title: "add-pc one-shot: añade PC al mesh + agente LLM en <2min desde movil"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0009"] related_flows: ["0009"]
related_issues: ["0134", "0144", "0145"] related_issues: ["0134", "0144", "0145"]
+2 -2
View File
@@ -1,8 +1,8 @@
--- ---
id: "0147" id: "0147"
title: "matrix-client-pc scaffold: Wails + React+Mantine + login MAS" title: "matrix-client-pc scaffold: Wails + React+Mantine + login MAS"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0010"] related_flows: ["0010"]
related_issues: ["0148", "0162"] related_issues: ["0148", "0162"]
@@ -1,8 +1,8 @@
--- ---
id: "0148" id: "0148"
title: "matrix-client-pc rooms list + timeline con sync incremental" title: "matrix-client-pc rooms list + timeline con sync incremental"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0010"] related_flows: ["0010"]
related_issues: ["0147", "0149"] related_issues: ["0147", "0149"]
+2 -2
View File
@@ -1,8 +1,8 @@
--- ---
id: "0149" id: "0149"
title: "matrix-client-pc composer: markdown, reply, edit, reactions, media" title: "matrix-client-pc composer: markdown, reply, edit, reactions, media"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0010"] related_flows: ["0010"]
related_issues: ["0148", "0150"] related_issues: ["0148", "0150"]
+2 -2
View File
@@ -1,8 +1,8 @@
--- ---
id: "0150" id: "0150"
title: "matrix-client-pc E2EE: cross-signing, SAS verification, recovery" title: "matrix-client-pc E2EE: cross-signing, SAS verification, recovery"
status: pending status: pendiente
priority: critical priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0010"] related_flows: ["0010"]
related_issues: ["0149", "0151"] related_issues: ["0149", "0151"]
@@ -1,8 +1,8 @@
--- ---
id: "0151" id: "0151"
title: "matrix-client-pc calls LiveKit: 1:1 + grupales, mic/cam/screen" title: "matrix-client-pc calls LiveKit: 1:1 + grupales, mic/cam/screen"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0010"] related_flows: ["0010"]
related_issues: ["0150", "0152"] related_issues: ["0150", "0152"]
@@ -1,8 +1,8 @@
--- ---
id: "0152" id: "0152"
title: "matrix-client-pc mini-webapps embebidas: Matrix Widget API v2" title: "matrix-client-pc mini-webapps embebidas: Matrix Widget API v2"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0010"] related_flows: ["0010"]
related_issues: ["0151", "0153"] related_issues: ["0151", "0153"]
@@ -1,8 +1,8 @@
--- ---
id: "0154" id: "0154"
title: "matrix-client-android scaffold: Kotlin + Compose + login MAS" title: "matrix-client-android scaffold: Kotlin + Compose + login MAS"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0011"] related_flows: ["0011"]
related_issues: ["0155", "0162"] related_issues: ["0155", "0162"]
@@ -1,8 +1,8 @@
--- ---
id: "0155" id: "0155"
title: "matrix-client-android rooms list + timeline Compose" title: "matrix-client-android rooms list + timeline Compose"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0011"] related_flows: ["0011"]
related_issues: ["0154", "0156"] related_issues: ["0154", "0156"]
@@ -1,8 +1,8 @@
--- ---
id: "0156" id: "0156"
title: "matrix-client-android composer: markdown, replies, edits, reactions, media" title: "matrix-client-android composer: markdown, replies, edits, reactions, media"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0011"] related_flows: ["0011"]
related_issues: ["0155", "0157"] related_issues: ["0155", "0157"]
@@ -1,8 +1,8 @@
--- ---
id: "0157" id: "0157"
title: "matrix-client-android E2EE rust-sdk: cross-signing, SAS, recovery" title: "matrix-client-android E2EE rust-sdk: cross-signing, SAS, recovery"
status: pending status: pendiente
priority: critical priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0011"] related_flows: ["0011"]
related_issues: ["0156", "0158"] related_issues: ["0156", "0158"]
@@ -1,8 +1,8 @@
--- ---
id: "0158" id: "0158"
title: "matrix-client-android calls LiveKit nativo: mic/cam/screen + PiP" title: "matrix-client-android calls LiveKit nativo: mic/cam/screen + PiP"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0011"] related_flows: ["0011"]
related_issues: ["0157", "0159", "0161"] related_issues: ["0157", "0159", "0161"]
@@ -1,8 +1,8 @@
--- ---
id: "0159" id: "0159"
title: "matrix-client-android push FCM via sygnal + Firebase setup" title: "matrix-client-android push FCM via sygnal + Firebase setup"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0011"] related_flows: ["0011"]
related_issues: ["0158", "0160"] related_issues: ["0158", "0160"]
@@ -1,8 +1,8 @@
--- ---
id: "0160" id: "0160"
title: "matrix-client-android mini-webapps: WebView + Widget API v2 bridge" title: "matrix-client-android mini-webapps: WebView + Widget API v2 bridge"
status: pending status: pendiente
priority: medium priority: media
created: 2026-05-24 created: 2026-05-24
related_flows: ["0011"] related_flows: ["0011"]
related_issues: ["0159", "0161"] related_issues: ["0159", "0161"]
@@ -1,8 +1,8 @@
--- ---
id: "0161" id: "0161"
title: "matrix-client-android foreground service: calls + lifecycle + lockscreen" title: "matrix-client-android foreground service: calls + lifecycle + lockscreen"
status: pending status: pendiente
priority: high priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0011"] related_flows: ["0011"]
related_issues: ["0158", "0160"] related_issues: ["0158", "0160"]
@@ -1,8 +1,8 @@
--- ---
id: "0162" id: "0162"
title: "Matrix: migrar Synapse a MAS como unico auth provider (MSC3861)" title: "Matrix: migrar Synapse a MAS como unico auth provider (MSC3861)"
status: pending status: pendiente
priority: critical priority: alta
created: 2026-05-24 created: 2026-05-24
related_flows: ["0010", "0011"] related_flows: ["0010", "0011"]
related_issues: ["0147", "0154", "0163"] related_issues: ["0147", "0154", "0163"]
+2 -2
View File
@@ -1,8 +1,8 @@
--- ---
id: "0163" id: "0163"
title: "Matrix admin panel propio: users, rooms, devices, sessions (sustituye synapse-admin)" title: "Matrix admin panel propio: users, rooms, devices, sessions (sustituye synapse-admin)"
status: pending status: pendiente
priority: medium priority: media
created: 2026-05-24 created: 2026-05-24
related_flows: ["0010", "0011"] related_flows: ["0010", "0011"]
related_issues: ["0162", "0147"] 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" id: "0059"
title: "Resolver doble tracking de `apps/*/app.md` (fn_registry + sub-repo)" title: "Resolver doble tracking de `apps/*/app.md` (fn_registry + sub-repo)"
status: pendiente status: completado
type: infra type: infra
domain: domain:
- registry-quality - registry-quality
@@ -1,7 +1,7 @@
--- ---
id: "55" id: "55"
title: "Roadmap de prereqs — issues de osint_graph que odr_console necesita antes/durante MVP" title: "Roadmap de prereqs — issues de osint_graph que odr_console necesita antes/durante MVP"
status: pendiente status: deferred
type: epic type: epic
domain: domain:
- osint - osint
@@ -1,7 +1,7 @@
--- ---
id: "0087" id: "0087"
title: "Capability Discovery Acceleration" title: "Capability Discovery Acceleration"
status: pendiente status: completado
type: feature type: feature
domain: domain:
- meta - meta
@@ -1,7 +1,7 @@
--- ---
id: "0096" id: "0096"
title: "Estandarizar ubicacion de apps: fuera de carpetas por lenguaje" title: "Estandarizar ubicacion de apps: fuera de carpetas por lenguaje"
status: pendiente status: completado
type: feature type: feature
domain: domain:
- apps-infra - apps-infra
@@ -1,7 +1,7 @@
--- ---
id: "0101" id: "0101"
title: "dev_console Go binario: /issue /flow /work unificados" title: "dev_console Go binario: /issue /flow /work unificados"
status: pendiente status: completado
type: app type: app
domain: domain:
- meta - meta
@@ -1,7 +1,7 @@
--- ---
id: "0103" id: "0103"
title: "Taxonomia + slash commands /issue /flow /work" title: "Taxonomia + slash commands /issue /flow /work"
status: pendiente status: completado
type: feature type: feature
domain: domain:
- meta - meta
@@ -1,7 +1,7 @@
--- ---
id: "0105" id: "0105"
title: "Estandarizar bloque service: en app.md + indexer + fn doctor services-spec" title: "Estandarizar bloque service: en app.md + indexer + fn doctor services-spec"
status: in-progress status: completado
type: feature type: feature
domain: domain:
- meta - meta
@@ -1,7 +1,7 @@
--- ---
id: "0109g" id: "0109g"
title: "skill_tree: panel terminal embebida (claude TUI dentro de la app)" title: "skill_tree: panel terminal embebida (claude TUI dentro de la app)"
status: pendiente status: deferred
type: feature type: feature
domain: domain:
- meta - meta
@@ -19,7 +19,7 @@ related:
- "0102" - "0102"
created: 2026-05-18 created: 2026-05-18
updated: 2026-05-18 updated: 2026-05-18
tags: [dod, evidence, frontmatter, taxonomy, validator] tags: [dod, evidence, frontmatter, taxonomy, validator, ausente-ready]
flow: "0008" flow: "0008"
--- ---
@@ -15,7 +15,7 @@ blocks:
related: [] related: []
created: 2026-05-22 created: 2026-05-22
updated: 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: dod_evidence_schema:
- id: build_ok - id: build_ok
kind: cmd kind: cmd
@@ -1,7 +1,7 @@
--- ---
id: "0153" id: "0153"
title: "matrix-client-pc agent integration: paneles para rooms operados por agentes" title: "matrix-client-pc agent integration: paneles para rooms operados por agentes"
status: pending status: deferred
priority: medium priority: medium
created: 2026-05-24 created: 2026-05-24
related_flows: ["0010", "0009"] related_flows: ["0010", "0009"]
@@ -1,7 +1,7 @@
--- ---
id: "0164" id: "0164"
title: "Bots agents_and_robots: cryptohelper.Init() cuelga al habilitar encryption=true" title: "Bots agents_and_robots: cryptohelper.Init() cuelga al habilitar encryption=true"
status: pending status: deferred
priority: high priority: high
created: 2026-05-24 created: 2026-05-24
related_flows: ["0009"] related_flows: ["0009"]
@@ -12,7 +12,7 @@ blocks: []
related: ["0167", "0168"] related: ["0167", "0168"]
created: 2026-05-24 created: 2026-05-24
updated: 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) # 0166 — Desplegar TURN para LiveKit (coturn o integrado)
@@ -1,7 +1,7 @@
--- ---
id: "0171" id: "0171"
title: "Manifest de sub-repos por project + re-clonado y auditoría de cobertura en Gitea" title: "Manifest de sub-repos por project + re-clonado y auditoría de cobertura en Gitea"
status: pendiente status: completado
type: enhancement type: enhancement
domain: domain:
- registry-quality - registry-quality
@@ -1,7 +1,7 @@
--- ---
id: "0173" id: "0173"
title: "EDA: bugs críticos de correctitud estadística (outlier_pct ×100, distribution_type por-skew)" title: "EDA: bugs críticos de correctitud estadística (outlier_pct ×100, distribution_type por-skew)"
status: resuelto status: completado
type: bugfix type: bugfix
domain: domain:
- registry-quality - registry-quality
@@ -1,7 +1,7 @@
--- ---
id: "0174" id: "0174"
title: "EDA series temporales: período estacional roto + correlación de niveles + to_returns ciego" title: "EDA series temporales: período estacional roto + correlación de niveles + to_returns ciego"
status: resuelto status: completado
type: bugfix type: bugfix
domain: domain:
- registry-quality - registry-quality
@@ -1,7 +1,7 @@
--- ---
id: "0175" id: "0175"
title: "EDA relational: precisión de FK inference (falsos positivos) + filtrar VIEWs + test ATTACH" title: "EDA relational: precisión de FK inference (falsos positivos) + filtrar VIEWs + test ATTACH"
status: resuelto status: completado
type: bugfix type: bugfix
domain: domain:
- registry-quality - registry-quality
@@ -1,7 +1,7 @@
--- ---
id: "0176" id: "0176"
title: "EDA render: models/series/caveats en markdown+PDF + PDF para profile_database" title: "EDA render: models/series/caveats en markdown+PDF + PDF para profile_database"
status: resuelto status: completado
type: feature type: feature
domain: domain:
- registry-quality - registry-quality
@@ -1,7 +1,7 @@
--- ---
id: "0177" id: "0177"
title: "EDA tipos: id secuencial fuera de correlación/PCA + η² espurio por cardinalidad + re-expresión no-continuas" title: "EDA tipos: id secuencial fuera de correlación/PCA + η² espurio por cardinalidad + re-expresión no-continuas"
status: resuelto status: completado
type: bugfix type: bugfix
domain: domain:
- registry-quality - registry-quality
@@ -12,7 +12,7 @@ blocks: []
related: [] related: []
created: 2026-05-17 created: 2026-05-17
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
# 0033 — C++ http_inspector + websocket_client # 0033 — C++ http_inspector + websocket_client
@@ -13,10 +13,7 @@ blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
updated: 2026-05-17 updated: 2026-05-17
tags: tags: [ausente-ready, gamedev, cpp, wasm]
- gamedev
- cpp
- wasm
--- ---
## Objetivo ## Objetivo
@@ -13,7 +13,7 @@ blocks: []
related: [] related: []
created: 2026-05-13 created: 2026-05-13
updated: 2026-05-17 updated: 2026-05-17
tags: [] tags: [ausente-ready]
--- ---
## Objetivo ## Objetivo
@@ -15,7 +15,7 @@ related:
- "0106" - "0106"
created: 2026-05-18 created: 2026-05-18
updated: 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 # 0110 — Helper HTTP cliente C++ en el registry
@@ -8,8 +8,7 @@ domain:
- dev-ux - dev-ux
scope: app-scoped scope: app-scoped
priority: alta priority: alta
depends: depends: ["0130b"]
- "0130b"
blocks: [] blocks: []
related: related:
- "0130" - "0130"
@@ -16,7 +16,7 @@ related:
- "0131" - "0131"
created: 2026-05-22 created: 2026-05-22
updated: 2026-05-22 updated: 2026-05-22
tags: [cpp, imgui, terminal, pty, module] tags: [cpp, imgui, terminal, pty, module, ausente-ready]
flow: "" flow: ""
--- ---
@@ -7,8 +7,7 @@ domain:
- gamedev - gamedev
scope: multi-app scope: multi-app
priority: alta priority: alta
depends: depends: ["0072a"]
- "0072a"
blocks: [] blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
@@ -7,8 +7,7 @@ domain:
- gamedev - gamedev
scope: multi-app scope: multi-app
priority: alta priority: alta
depends: depends: ["0072b"]
- "0072b"
blocks: [] blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
@@ -7,9 +7,7 @@ domain:
- gamedev - gamedev
scope: multi-app scope: multi-app
priority: alta priority: alta
depends: depends: ["0072a", "0072b"]
- "0072a"
- "0072b"
blocks: [] blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10
@@ -7,9 +7,7 @@ domain:
- gamedev - gamedev
scope: multi-app scope: multi-app
priority: alta priority: alta
depends: depends: ["0072a", "0072d"]
- "0072a"
- "0072d"
blocks: [] blocks: []
related: [] related: []
created: 2026-05-10 created: 2026-05-10

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