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