feat(doctor): add fn doctor CLI + 14 functions for system management
Adds `fn doctor` read-only diagnostic command with subcommands artefacts, services, sync, uses-functions, unused, and --json flag for agents. Each subcommand wraps a registry function in functions/infra/. New functions: - artefact_doctor, services_status, pc_locations_drift, audit_uses_functions, find_unused_functions (Go diagnostics) - backup_sqlite_db, rotate_backups, wait_for_http, wait_for_port, port_kill, tail_journal, pre_commit_hook_install (bash utilities) - notify_telegram (Go HTTP) - backup_all pipeline (tag launcher) Plus prior session leftovers (scan_secrets_in_dirty, append_diary_entry, git utilities, http_session_cookie_middleware, compile/full-git pipelines). Fixes pc_locations_drift filepath.Join bug with absolute dir_path. Documents fn doctor in CLAUDE.md, .claude/rules/fn_doctor.md (rule 23), docs/architecture.md, CHANGELOG.md (2026-05-07), and diary entry. First fn doctor uses-functions run found drift in 7/12 apps (deuda para sincronizar app.md con imports reales). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -145,6 +145,16 @@ fn show <id>
|
||||
fn add -k function # Template
|
||||
fn check params # Lista funciones sin params_schema
|
||||
|
||||
# Doctor: diagnostico read-only del registry y artefactos
|
||||
fn doctor # Corre todos los checks
|
||||
fn doctor artefacts # git/venv/app.md/upstream de cada app y analysis
|
||||
fn doctor services # apps tag 'service' + systemctl + puerto
|
||||
fn doctor sync # drift pc_locations BD vs disco
|
||||
fn doctor uses-functions # imports reales vs uses_functions del app.md
|
||||
fn doctor unused # funciones del registry sin consumidores
|
||||
fn doctor --json # salida JSON (cualquier subcomando)
|
||||
# Ver .claude/rules/fn_doctor.md para mapeo subcomando → funcion + acciones derivadas.
|
||||
|
||||
# Ejecutar funciones y pipelines (fn run)
|
||||
fn run <id_or_name> [args...] # Ejecuta por ID o nombre
|
||||
fn run init_metabase --project test # Go pipeline (go run .)
|
||||
|
||||
+26
-197
@@ -1,208 +1,37 @@
|
||||
# /compile — Compila la app actual y la copia al escritorio de Windows
|
||||
# /compile — Compila app C++ y la copia al escritorio de Windows
|
||||
|
||||
Compila una app del registry para los targets que soporte (Windows via MinGW, Android via Gradle/NDK si esta configurado) y deja el resultado en `/mnt/c/Users/lucas/Desktop/apps/<app>/`, listo para usar desde Windows.
|
||||
Wrapper sobre el pipeline `compile_cpp_app_bash_pipelines`. Toda la lógica vive en el registry (resolver app desde CWD/arg, cross-compile MinGW, copiar exe + DLLs + assets/ + enrichers/ + runtime/ a `/mnt/c/Users/lucas/Desktop/apps/<app>/`, taskkill previo, preservar `local_files/`).
|
||||
|
||||
Pensado para apps C++ del workspace `cpp/` (donde ya hay toolchain `mingw-w64.cmake` y build dir `cpp/build/windows/`). Si en el futuro hay apps Android (Gradle wrapper o NDK), tambien las detecta.
|
||||
```bash
|
||||
cd /home/lucas/fn_registry
|
||||
./fn run compile_cpp_app "$ARGUMENTS"
|
||||
```
|
||||
|
||||
## Argumento
|
||||
|
||||
`$ARGUMENTS` — opcional. Nombre de la app a compilar (ej: `chart_demo`, `registry_dashboard`).
|
||||
`$ARGUMENTS` — opcional. Nombre de app (ej: `chart_demo`).
|
||||
|
||||
- Sin argumento: detectar la app desde `pwd` (si estas dentro de `cpp/apps/<X>/` o `projects/*/apps/<X>/`).
|
||||
- Si no hay app deducible y no se pasa argumento → listar apps disponibles y pedir nombre.
|
||||
- Si se pasa argumento, usarlo directamente.
|
||||
- Sin argumento: deduce desde `pwd` si estás dentro de `cpp/apps/<X>/` o `projects/*/apps/<X>/`.
|
||||
- Si no se puede deducir y no se pasa argumento, el pipeline lista las apps disponibles en stderr y aborta.
|
||||
|
||||
## Pasos
|
||||
## Qué hace el pipeline
|
||||
|
||||
### 1. Resolver la app y su directorio fuente
|
||||
|
||||
```bash
|
||||
ROOT=/home/lucas/fn_registry
|
||||
APP_ARG="$ARGUMENTS"
|
||||
|
||||
# Detectar desde CWD si no hay argumento
|
||||
if [ -z "$APP_ARG" ]; then
|
||||
CWD="$(pwd)"
|
||||
case "$CWD" in
|
||||
"$ROOT"/cpp/apps/*|"$ROOT"/projects/*/apps/*)
|
||||
APP_ARG="$(basename "$CWD")" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Si sigue vacio, listar apps y abortar
|
||||
if [ -z "$APP_ARG" ]; then
|
||||
echo "Apps disponibles:"
|
||||
ls "$ROOT"/cpp/apps/ 2>/dev/null
|
||||
ls "$ROOT"/projects/*/apps/ 2>/dev/null
|
||||
echo "Uso: /compile <app_name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Buscar el directorio real
|
||||
APP_DIR=""
|
||||
for cand in "$ROOT/cpp/apps/$APP_ARG" "$ROOT"/projects/*/apps/"$APP_ARG"; do
|
||||
[ -d "$cand" ] && APP_DIR="$cand" && break
|
||||
done
|
||||
|
||||
if [ -z "$APP_DIR" ]; then
|
||||
echo "No encuentro app '$APP_ARG' en cpp/apps/ ni projects/*/apps/"
|
||||
exit 1
|
||||
fi
|
||||
echo "App: $APP_ARG"
|
||||
echo "Dir: $APP_DIR"
|
||||
```
|
||||
|
||||
### 2. Detectar targets soportados
|
||||
|
||||
Examinar el `app.md` y los archivos del directorio para decidir que se puede compilar:
|
||||
|
||||
- **Windows (MinGW)**: si la app tiene `CMakeLists.txt` y se registra en `cpp/CMakeLists.txt` (es decir, aparece como subdirectorio en `cpp/build/windows/apps/<APP>/`). Default para apps C++.
|
||||
- **Android**: si existe `AndroidManifest.xml`, `build.gradle`, `build.gradle.kts` o carpeta `android/` dentro de `$APP_DIR`. (Hoy no hay ninguna; saltar silenciosamente.)
|
||||
- **Linux** (opcional, no por defecto): el build dir `cpp/build/` ya genera el binario para Linux. Solo se hace si el usuario lo pide explicitamente.
|
||||
|
||||
```bash
|
||||
TARGETS=()
|
||||
[ -f "$APP_DIR/CMakeLists.txt" ] && TARGETS+=("windows")
|
||||
|
||||
if [ -f "$APP_DIR/AndroidManifest.xml" ] || \
|
||||
[ -f "$APP_DIR/build.gradle" ] || \
|
||||
[ -f "$APP_DIR/build.gradle.kts" ] || \
|
||||
[ -d "$APP_DIR/android" ]; then
|
||||
TARGETS+=("android")
|
||||
fi
|
||||
|
||||
if [ ${#TARGETS[@]} -eq 0 ]; then
|
||||
echo "No se detecta ningun target compilable en $APP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
echo "Targets: ${TARGETS[*]}"
|
||||
```
|
||||
|
||||
### 3. Compilar Windows (cross-compile MinGW)
|
||||
|
||||
Solo si `windows` esta en TARGETS.
|
||||
|
||||
```bash
|
||||
BUILD_WIN="$ROOT/cpp/build/windows"
|
||||
|
||||
# Configurar build dir si no existe
|
||||
if [ ! -f "$BUILD_WIN/CMakeCache.txt" ]; then
|
||||
mkdir -p "$BUILD_WIN"
|
||||
cmake -S "$ROOT/cpp" -B "$BUILD_WIN" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$ROOT/cpp/toolchains/mingw-w64.cmake" \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
fi
|
||||
|
||||
# Compilar SOLO el target de la app (no todo el arbol)
|
||||
cmake --build "$BUILD_WIN" --target "$APP_ARG" -j"$(nproc)"
|
||||
```
|
||||
|
||||
Si el target no existe en CMake (porque la app no esta registrada en `cpp/CMakeLists.txt`), reportar y proponer registrarla siguiendo `cpp_apps.md` §5. NO autoregistrarla sin confirmacion del usuario.
|
||||
|
||||
### 4. Copiar a `/mnt/c/Users/lucas/Desktop/apps/<APP>/`
|
||||
|
||||
Layout estandar (convencion `assets/` + `local_files/`, ver `cpp_apps.md` §7):
|
||||
|
||||
```
|
||||
Desktop/apps/<APP>/
|
||||
├── <APP>.exe ← binario (top level por convencion Windows DLL)
|
||||
├── *.dll ← DLLs nativas (Windows las busca junto al exe)
|
||||
├── assets/ ← read-only, ships con el zip
|
||||
│ ├── *.ttf ← fuentes (vienen de add_imgui_app)
|
||||
│ ├── enrichers/ ← si <app_dir>/enrichers existe
|
||||
│ ├── runtime/ ← Python embed si app.md tiene python_runtime: true
|
||||
│ ├── gx-cli, gx-cli.exe ← si la app necesita un MCP server
|
||||
│ └── ... ← cualquier otro asset distribuible
|
||||
└── local_files/ ← writable, per-PC, creado por la app al
|
||||
primer arranque. NUNCA borrar al recompilar.
|
||||
```
|
||||
|
||||
```bash
|
||||
DEST="/mnt/c/Users/lucas/Desktop/apps/$APP_ARG"
|
||||
ASSETS="$DEST/assets"
|
||||
mkdir -p "$DEST" "$ASSETS"
|
||||
|
||||
EXE_SRC="$BUILD_WIN/apps/$APP_ARG/$APP_ARG.exe"
|
||||
if [ ! -f "$EXE_SRC" ]; then
|
||||
echo "ERROR: no se ha generado $EXE_SRC"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 1. Binario + DLLs en el top level (Windows DLL search convention).
|
||||
cp -v "$EXE_SRC" "$DEST/"
|
||||
find "$BUILD_WIN/apps/$APP_ARG" -maxdepth 1 -type f -name '*.dll' \
|
||||
-exec cp -v {} "$DEST/" \;
|
||||
|
||||
# 2. assets/ — TTFs (las copia add_imgui_app a build/<app>/assets/) y
|
||||
# cualquier asset extra del build (build/<app>/assets/*).
|
||||
if [ -d "$BUILD_WIN/apps/$APP_ARG/assets" ]; then
|
||||
rsync -a --delete "$BUILD_WIN/apps/$APP_ARG/assets/" "$ASSETS/"
|
||||
fi
|
||||
|
||||
# 3. enrichers/ del app_dir -> assets/enrichers/.
|
||||
if [ -d "$APP_DIR/enrichers" ]; then
|
||||
rsync -a --delete --exclude '__pycache__' --exclude '*.pyc' \
|
||||
"$APP_DIR/enrichers/" "$ASSETS/enrichers/"
|
||||
fi
|
||||
|
||||
# 4. runtime/ Python embebido -> assets/runtime/ (si la app lo declara).
|
||||
if grep -q '^python_runtime:[[:space:]]*true' "$APP_DIR/app.md" 2>/dev/null; then
|
||||
if [ ! -d "$APP_DIR/runtime/python" ] || \
|
||||
[ "$APP_DIR/app.md" -nt "$APP_DIR/runtime/.lock" ]; then
|
||||
echo "[freeze] regenerando runtime Python (Windows) para $APP_ARG"
|
||||
"$APP_DIR/tools/freeze_python_runtime.sh" "$APP_DIR" windows
|
||||
fi
|
||||
rsync -a --delete --exclude '__pycache__' --exclude '*.pyc' \
|
||||
"$APP_DIR/runtime/" "$ASSETS/runtime/"
|
||||
fi
|
||||
|
||||
# 5. Otros assets sueltos del app_dir (gx-cli, scripts varios). El
|
||||
# convention es: si vive en <app_dir>/ y no es codigo fuente, va a
|
||||
# assets/. Ahora mismo la unica excepcion es gx-cli (graph_explorer).
|
||||
for extra in gx-cli gx-cli.exe; do
|
||||
if [ -f "$APP_DIR/$extra" ]; then
|
||||
cp -v "$APP_DIR/$extra" "$ASSETS/"
|
||||
fi
|
||||
done
|
||||
|
||||
# 6. NO TOCAR local_files/. Si existe en $DEST, preservar — contiene
|
||||
# estado del usuario (DBs, settings, layouts ImGui, proyectos).
|
||||
echo "OK: $APP_ARG -> $DEST"
|
||||
[ -d "$DEST/local_files" ] && echo " local_files/ preservado: $(du -sh "$DEST/local_files" | cut -f1)"
|
||||
```
|
||||
|
||||
### 5. Compilar Android (solo si TARGETS contiene `android`)
|
||||
|
||||
Hoy no hay apps Android en el registry, asi que esta rama no se ejecuta. Cuando se anada la primera, este es el patron previsto:
|
||||
|
||||
```bash
|
||||
if [[ " ${TARGETS[*]} " == *" android "* ]]; then
|
||||
ANDROID_DIR="$APP_DIR"
|
||||
[ -d "$APP_DIR/android" ] && ANDROID_DIR="$APP_DIR/android"
|
||||
|
||||
cd "$ANDROID_DIR"
|
||||
if [ -x "./gradlew" ]; then
|
||||
./gradlew assembleRelease
|
||||
APK="$(find "$ANDROID_DIR/app/build/outputs/apk/release" -name '*.apk' | head -n1)"
|
||||
[ -n "$APK" ] && cp -v "$APK" "/mnt/c/Users/lucas/Desktop/apps/$APP_ARG/"
|
||||
else
|
||||
echo "android: no hay ./gradlew en $ANDROID_DIR — saltando."
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
Cuando llegue la primera app Android, este bloque puede ampliarse (firma, ABI splits, etc.).
|
||||
|
||||
### 6. Resumen
|
||||
|
||||
Imprime al final una linea por target con:
|
||||
- Tamano del binario (`ls -lh`)
|
||||
- Path final en `/mnt/c/Users/lucas/Desktop/apps/<APP>/`
|
||||
- Nombre del exe/apk
|
||||
1. `resolve_cpp_app_dir_bash_infra` — resuelve `<app_name>` y `<dir absoluto>` desde arg o CWD.
|
||||
2. Verifica `CMakeLists.txt` en el dir resuelto.
|
||||
3. `build_cpp_windows_bash_infra <app>` — cross-compila el target específico con `cpp/build/windows/` (configura toolchain `mingw-w64.cmake` la primera vez).
|
||||
4. `deploy_cpp_exe_to_windows_bash_infra <app> <dir>`:
|
||||
- `taskkill.exe /IM <app>.exe /F` (pre-autorizado).
|
||||
- Copia `<app>.exe` + DLLs al top-level de `Desktop/apps/<app>/`.
|
||||
- rsync `cpp/build/windows/apps/<app>/assets/` → `Desktop/apps/<app>/assets/`.
|
||||
- rsync `<app_dir>/enrichers/` → `assets/enrichers/` si existe.
|
||||
- Si `app.md` declara `python_runtime: true`, regenera `runtime/` con `tools/freeze_python_runtime.sh` y rsync a `assets/runtime/`.
|
||||
- Copia `gx-cli`/`gx-cli.exe` si existen.
|
||||
- **NUNCA** toca `local_files/` (estado del usuario).
|
||||
5. Imprime `ls -lh` del `.exe` final.
|
||||
|
||||
## Notas
|
||||
|
||||
- El build de Windows usa `cpp/build/windows/` (no `cpp/build/`). El de Linux es `cpp/build/`. Coexisten sin conflicto.
|
||||
- El toolchain `mingw-w64.cmake` ya configura linkado estatico (`-static-libgcc -static-libstdc++ -lwinpthread`) — el `.exe` resultante es self-contained y no necesita DLLs en el escritorio.
|
||||
- Si se pasa una app que vive en `projects/<proj>/apps/<APP>/`, el target CMake sigue siendo `<APP>` (registrado en `cpp/CMakeLists.txt` con `add_subdirectory(${PROJ_DIR}/apps/<APP> ${CMAKE_BINARY_DIR}/apps/<APP>)`).
|
||||
- NO tocar `AdminLocal` ni instalar nada en `Program Files` — solo el escritorio del usuario.
|
||||
- Solo target Windows hoy. Android / Linux quedan fuera (Linux ya lo da `cpp/build/`).
|
||||
- Variables override-ables: `BUILD_WIN`, `WIN_DESKTOP_APPS`, `FN_REGISTRY_ROOT`.
|
||||
- Si la app no está registrada en `cpp/CMakeLists.txt`, `cmake --build --target <app>` falla. Registrar siguiendo `.claude/rules/cpp_apps.md` §5.
|
||||
- Para tocar la lógica: editar `bash/functions/{infra,pipelines}/{resolve_cpp_app_dir,deploy_cpp_exe_to_windows,compile_cpp_app}.sh`, no este wrapper.
|
||||
|
||||
@@ -1,63 +1,37 @@
|
||||
# /entrada_diario — Añadir entrada al diario del día
|
||||
|
||||
Añade una entrada nueva a `docs/diary/YYYY-MM-DD.md` con la fecha y hora actuales. Si el archivo del día no existe, lo crea con el encabezado del día. Si existe, **añade** una sección nueva al final (nunca sobrescribe ni reescribe entradas previas).
|
||||
Wrapper sobre `append_diary_entry_bash_infra`. La función del registry maneja todo el manejo de archivos (crear `docs/diary/YYYY-MM-DD.md` si no existe, append seguro, formato exacto). Este comando solo decide el contenido.
|
||||
|
||||
## Uso
|
||||
|
||||
```
|
||||
/entrada_diario <descripción o resumen del bloque de trabajo>
|
||||
/entrada_diario <descripción del bloque de trabajo>
|
||||
/entrada_diario # sin args → resume sesión actual
|
||||
```
|
||||
|
||||
Si no se pasa argumento, resume la sesión actual de forma concisa (qué hicimos, qué completamos, qué queda pendiente).
|
||||
## Pasos del asistente
|
||||
|
||||
## Pasos que debe seguir el asistente
|
||||
1. **Componer `TITULO` (corto, una linea) y `CUERPO`** (viñetas markdown):
|
||||
- Con `$ARGUMENTS`: derivar `TITULO` directo del argumento; `CUERPO` con viñetas concretas (`- Hecho:`, `- Pendiente:`).
|
||||
- Sin `$ARGUMENTS`: revisar TaskList + `git log --since=today` + `git status` y resumir en 3-5 viñetas.
|
||||
|
||||
1. **Fecha y hora**:
|
||||
2. **Llamar la función del registry**:
|
||||
```bash
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
TIME=$(date +%H:%M)
|
||||
cd /home/lucas/fn_registry
|
||||
source bash/functions/infra/append_diary_entry.sh
|
||||
append_diary_entry "<TITULO>" "$(cat <<'EOF'
|
||||
<CUERPO>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
2. **Ruta del archivo del día**: `docs/diary/${DATE}.md`
|
||||
|
||||
3. **Si el archivo NO existe**, crearlo con:
|
||||
```markdown
|
||||
# ${DATE}
|
||||
```
|
||||
|
||||
4. **Componer la entrada** en este formato exacto:
|
||||
```markdown
|
||||
|
||||
## ${TIME} — <título corto derivado del argumento>
|
||||
|
||||
<1-3 líneas de contexto breve si aplica>
|
||||
|
||||
- Hecho: <viñeta concreta>
|
||||
- Hecho: <viñeta concreta>
|
||||
- Pendiente: <viñeta si procede>
|
||||
|
||||
<Referencias opcionales: commit SHAs cortos, ADR #NNNN, issue #N, rutas a funciones del registry>
|
||||
```
|
||||
|
||||
5. **Añadir al final del archivo** (nunca editar secciones anteriores). Usar `Write` con el contenido completo si es el primer uso del día, `Edit` para append en días ya empezados.
|
||||
La función imprime el path del archivo escrito.
|
||||
|
||||
## Reglas de estilo
|
||||
|
||||
- **Viñetas breves**, no párrafos. Si un punto necesita explicación larga, probablemente es un ADR en lugar de un diario.
|
||||
- **Verbos en pasado para lo hecho**, infinitivo para lo pendiente.
|
||||
- **Enlaces a artefactos**: commits (`SHA` corto de 7-8 chars), ADRs (`[0001](../adr/0001-...)`), funciones del registry por ID.
|
||||
- **No duplicar con CHANGELOG**: el diario es contexto operativo ("qué hice hoy"), el CHANGELOG es "qué cambió cara al usuario".
|
||||
- Si el argumento es vacío, revisar TaskList + cambios en git (`git log --since=today`, `git status`) y resumir en 3-5 viñetas.
|
||||
|
||||
## Ejemplos
|
||||
|
||||
```
|
||||
/entrada_diario cerrado issue #23 del dashboard, fix en http_client.cpp
|
||||
```
|
||||
|
||||
```
|
||||
/entrada_diario # sin args → resume sesión
|
||||
```
|
||||
- Viñetas breves, no párrafos. Verbos en pasado para lo hecho, infinitivo para pendientes.
|
||||
- Enlaces a artefactos: commits (SHA corto 7-8 chars), ADRs (`[0001](../adr/0001-...)`), funciones del registry por ID.
|
||||
- No duplicar con CHANGELOG: el diario es contexto operativo ("qué hice hoy"), el CHANGELOG es "qué cambió cara al usuario".
|
||||
- NUNCA editar secciones anteriores. La función solo append.
|
||||
|
||||
## Relación con otras formas de registro
|
||||
|
||||
@@ -67,3 +41,7 @@ Si no se pasa argumento, resume la sesión actual de forma concisa (qué hicimos
|
||||
| Qué cambió en el código (cara usuario/agentes) | Editar `CHANGELOG.md` directamente |
|
||||
| Por qué tomamos una decisión arquitectural | Nuevo ADR en `docs/adr/NNNN-*.md` |
|
||||
| Una regla operativa nueva del registry | Nuevo archivo en `.claude/rules/` + entrada en INDEX.md |
|
||||
|
||||
## Para tocar la lógica
|
||||
|
||||
Editar la función `append_diary_entry_bash_infra` en el registry, no este wrapper.
|
||||
|
||||
@@ -1,124 +1,28 @@
|
||||
# /full-git-pull — Pull automático de fn_registry + sub-repos + submodules + fn sync
|
||||
|
||||
Trae los últimos cambios del remote para el repo principal `fn_registry`, todos los sub-repos git anidados que **ya existan localmente**, y los submodules de `cpp/vendor/`. Después regenera `registry.db` y corre `fn sync` para tirar de la metadata del `registry_api`.
|
||||
|
||||
**Modo automático (preferencia del usuario):** este comando NO pregunta. Auto-stashea dirty trees antes de pullear y hace `pop` después. Sigue con el resto de repos aunque uno falle. Solo se detiene si detecta riesgo serio (conflicto en stash pop que requiere intervención humana).
|
||||
|
||||
**No clona repos que falten.** Cada PC tiene solo el subset de apps/analyses que le interesa. Si en este PC necesitas un sub-repo que aún no tienes, clónalo a mano:
|
||||
Wrapper sobre el pipeline `full_git_pull_bash_pipelines`. Toda la lógica vive en el registry. Este comando solo ejecuta:
|
||||
|
||||
```bash
|
||||
git clone https://<user>:<token>@<gitea-host>/dataforge/<name>.git <path>
|
||||
cd /home/lucas/fn_registry
|
||||
./fn run full_git_pull_bash_pipelines
|
||||
```
|
||||
|
||||
Consulta `pc_locations` para ver dónde lo tiene otro PC y reproduce el path.
|
||||
|
||||
## Argumento
|
||||
|
||||
`$ARGUMENTS` — sin uso, ignorar.
|
||||
|
||||
## Pasos
|
||||
## Qué hace el pipeline
|
||||
|
||||
### 1. Descubrir repos locales
|
||||
|
||||
```bash
|
||||
cd /home/lucas/fn_registry
|
||||
|
||||
REPOS=$(find . -name ".git" -type d \
|
||||
-not -path "./.git" -not -path "./.git/*" \
|
||||
-not -path "*/node_modules/*" -not -path "*/.venv/*" \
|
||||
-not -path "*/cpp/vendor/*" -not -path "*/cpp/build/*" \
|
||||
-not -path "*/sources/*" -not -path "*/temp/*" -not -path "*/subrepos/*" 2>/dev/null \
|
||||
| sed 's|/.git$||')
|
||||
REPOS=". $REPOS"
|
||||
```
|
||||
|
||||
Solo se actualizan los sub-repos que ya tengan `.git/` localmente.
|
||||
|
||||
### 2. Para cada repo: stash si dirty, pull --ff-only, pop
|
||||
|
||||
```bash
|
||||
for r in $REPOS; do
|
||||
( cd "$r" \
|
||||
&& DIRTY=$(git status --porcelain | wc -l) \
|
||||
&& if [ "$DIRTY" -gt 0 ]; then
|
||||
git stash push -m "auto-stash before /full-git-pull" --include-untracked >/dev/null
|
||||
STASHED=1
|
||||
else
|
||||
STASHED=0
|
||||
fi \
|
||||
&& git fetch origin 2>&1 | tail -1 \
|
||||
&& git pull --ff-only 2>&1 | tail -3 \
|
||||
&& if [ "$STASHED" = "1" ]; then
|
||||
git stash pop 2>&1 | tail -3
|
||||
fi
|
||||
)
|
||||
done
|
||||
```
|
||||
|
||||
- Si `--ff-only` falla por divergencia → reportar ese repo, seguir con el resto. **No** rebasear ni mergear.
|
||||
- Si `stash pop` produce conflictos → **avisar al usuario al final** y dejar el conflicto sin tocar; seguir con los demás repos.
|
||||
|
||||
### 3. Submodules del repo principal
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive 2>&1 | tail -10
|
||||
```
|
||||
|
||||
### 4. Regenerar registry.db local
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=1 ./fn index 2>&1 | tail -3
|
||||
```
|
||||
|
||||
### 5. Pull del repo de pass (`~/.password-store`)
|
||||
|
||||
El password store es su propio repo Git en Gitea (`dataforge/pass-secrets`). `pass insert/edit/rm` commitea automaticamente, asi que aqui SOLO hay que pullear los commits remotos.
|
||||
|
||||
```bash
|
||||
PASS_DIR="$HOME/.password-store"
|
||||
if [ -d "$PASS_DIR/.git" ]; then
|
||||
( cd "$PASS_DIR" \
|
||||
&& DIRTY=$(git status --porcelain | wc -l) \
|
||||
&& if [ "$DIRTY" -gt 0 ]; then
|
||||
git stash push -m "auto-stash before /full-git-pull" --include-untracked >/dev/null
|
||||
STASHED=1
|
||||
else
|
||||
STASHED=0
|
||||
fi \
|
||||
&& git fetch origin 2>&1 | tail -1 \
|
||||
&& git pull --ff-only 2>&1 | tail -3 \
|
||||
&& if [ "$STASHED" = "1" ]; then
|
||||
git stash pop 2>&1 | tail -3
|
||||
fi
|
||||
)
|
||||
fi
|
||||
```
|
||||
|
||||
Reglas:
|
||||
- Mismo patron que el resto de repos: stash → fetch → pull --ff-only → pop.
|
||||
- Si `--ff-only` falla por divergencia, reportar y seguir; no resolver a mano.
|
||||
- Si `stash pop` da conflicto, avisar al final.
|
||||
|
||||
### 6. fn sync
|
||||
|
||||
```bash
|
||||
USER=$(pass registry/basicauth-user | head -1)
|
||||
PASSWD=$(pass registry/basicauth-pass | head -1)
|
||||
TOKEN=$(pass registry/api-token | head -1)
|
||||
export FN_REGISTRY_API="https://${USER}:${PASSWD}@registry.organic-machine.com"
|
||||
export REGISTRY_API_TOKEN="$TOKEN"
|
||||
./fn sync
|
||||
```
|
||||
|
||||
Si `pass` falla → gpg-agent bloqueado, pedir al usuario `pass show unlock` en su terminal real (entrada dummy que devuelve "Desbloqueada!" sin exponer API keys).
|
||||
|
||||
### 7. Resumen
|
||||
|
||||
Tabla concisa: por repo, commits pulleados o "ya estaba al día"; estado de `pass-secrets`; submodules actualizados; resultado de `fn index`; resultado de `fn sync`. Si algún repo quedó con conflicto de stash o divergencia, listarlos al final con la acción sugerida.
|
||||
1. `discover_git_repos_bash_infra` — lista repos locales (mismas exclusiones que push).
|
||||
2. `git_pull_with_stash_bash_infra` por repo: stash si dirty → fetch → pull --ff-only → pop. Estados posibles por repo: `[pulled]`, `[up-to-date]`, `[diverged]`, `[stash-conflict]`.
|
||||
3. `git submodule update --init --recursive` en root.
|
||||
4. `git_pull_with_stash` sobre `~/.password-store`.
|
||||
5. `CGO_ENABLED=1 ./fn index` para regenerar `registry.db`.
|
||||
6. `./fn sync` con credenciales de `pass`.
|
||||
|
||||
## Notas
|
||||
|
||||
- **Modo no-interactivo por diseño.** El usuario prefiere flujos rápidos sin confirmaciones.
|
||||
- Pull solo es fast-forward — nunca rebase ni merge automático.
|
||||
- Auto-stash incluye untracked (`--include-untracked`) para no perder archivos nuevos.
|
||||
- `fn index` se corre **antes** de `fn sync` para que las locations locales reflejen el estado actual.
|
||||
- **Modo no-interactivo.** Auto-stash con `--include-untracked`.
|
||||
- **Solo fast-forward.** Nunca rebase ni merge automático. Si un repo diverge, se reporta y sigue con el resto.
|
||||
- **No clona repos faltantes.** Cada PC tiene su subset. Para añadir uno, clonarlo a mano y mirar `pc_locations` para reproducir el path.
|
||||
- Para tocar la lógica: editar las funciones del registry, no este wrapper.
|
||||
|
||||
@@ -1,190 +1,29 @@
|
||||
# /full-git-push — Push automático de fn_registry + todos los sub-repos + fn sync
|
||||
# /full-git-push — Push automático de fn_registry + sub-repos + fn sync
|
||||
|
||||
Pushea el repo principal `fn_registry` y todos los sub-repos git anidados (apps y analyses, cada uno como repo independiente bajo `dataforge/<name>` en Gitea), y luego ejecuta `fn sync` para empujar la metadata no regenerable (proposals, apps, projects, analysis, vaults, pc_locations) al `registry_api`.
|
||||
|
||||
**Estandar:** todo `apps/<name>`, `analysis/<name>`, `projects/*/apps/<name>` y `projects/*/analysis/<name>` debe tener su propio repo Gitea bajo `dataforge/<basename>`. Los `subrepos/` de la raiz NO entran (mirrors upstream). Los `vaults/` tampoco.
|
||||
|
||||
**Modo automático (preferencia del usuario):** este comando NO pregunta. Si hay dirty trees, commitea automáticamente con un mensaje generado a partir de los cambios. Prioridad: hacer commits frecuentes y pushear rápido. **Único límite:** no commitear archivos que parezcan secrets (`.env`, `*credentials*`, `*.key`, `*.pem`, `id_rsa*`) — si se detectan, abortar y avisar.
|
||||
|
||||
## Argumento
|
||||
|
||||
`$ARGUMENTS` — opcional. Si se pasa texto, se usa como mensaje de commit. Sin argumento, se genera uno automáticamente con el patrón:
|
||||
|
||||
```
|
||||
chore: auto-commit (<N> archivos modificados, <N> nuevos, <N> borrados)
|
||||
|
||||
- <ruta1>
|
||||
- <ruta2>
|
||||
...
|
||||
```
|
||||
|
||||
Si los cambios tienen un patrón claro (todos en un mismo dominio/dir), usar ese patrón en el subject:
|
||||
- todo bajo `python/functions/<dom>/` → `feat(<dom>): auto-commit con N cambios`
|
||||
- todo bajo `dev/issues/` → `chore(issues): auto-commit`
|
||||
- mezclado → `chore: auto-commit`
|
||||
|
||||
## Pasos
|
||||
|
||||
### 1. Descubrir repos git + apps/analyses sin git
|
||||
Wrapper sobre el pipeline `full_git_push_bash_pipelines`. Toda la lógica vive en el registry. Este comando solo ejecuta:
|
||||
|
||||
```bash
|
||||
cd /home/lucas/fn_registry
|
||||
|
||||
REPOS=$(find . -name ".git" -type d \
|
||||
-not -path "./.git" -not -path "./.git/*" \
|
||||
-not -path "*/node_modules/*" -not -path "*/.venv/*" \
|
||||
-not -path "*/cpp/vendor/*" -not -path "*/cpp/build/*" \
|
||||
-not -path "*/sources/*" -not -path "*/temp/*" -not -path "*/subrepos/*" 2>/dev/null \
|
||||
| sed 's|/.git$||')
|
||||
REPOS=". $REPOS"
|
||||
|
||||
# Apps/analyses sin .git — auto-inicializar como dataforge/<basename>
|
||||
MISSING=()
|
||||
for d in apps/*/ analysis/*/ projects/*/apps/*/ projects/*/analysis/*/; do
|
||||
d="${d%/}"
|
||||
[[ -d "$d/.git" ]] || MISSING+=("$d")
|
||||
done
|
||||
./fn run full_git_push_bash_pipelines "$ARGUMENTS"
|
||||
```
|
||||
|
||||
### 1b. Auto-inicializar repos faltantes (sin pedir confirmación)
|
||||
## Argumento
|
||||
|
||||
Para cada `$d` en `MISSING`:
|
||||
`$ARGUMENTS` — opcional. Mensaje de commit fijo para todos los repos dirty. Sin argumento, el pipeline genera un mensaje automático por repo según los paths cambiados (ver `bash/functions/infra/git_auto_commit_dirty.sh`).
|
||||
|
||||
```bash
|
||||
export GITEA_URL=$(pass agentes/gitea-url | head -n1)
|
||||
export GITEA_TOKEN=$(pass gitea/dataforge-git-token | head -n1)
|
||||
export FN_REGISTRY_INFRA_DIR=/home/lucas/fn_registry/bash/functions/infra
|
||||
## Qué hace el pipeline
|
||||
|
||||
bash -c "
|
||||
source $FN_REGISTRY_INFRA_DIR/ensure_repo_synced.sh
|
||||
ensure_repo_synced '$d' dataforge \"\$(basename '$d')\" master 'chore: initial sync'
|
||||
"
|
||||
```
|
||||
|
||||
Si `$d/.gitignore` no existe antes de inicializar, escribir uno apropiado (ver `.claude/rules/apps_vs_functions.md`). Solo abortar la inicialización de ese repo concreto si falla; seguir con el resto.
|
||||
|
||||
### 2. Detectar secrets antes de commitear
|
||||
|
||||
Para cada repo dirty, listar archivos modificados/nuevos y comprobar nombres sospechosos:
|
||||
|
||||
```bash
|
||||
for r in $REPOS; do
|
||||
( cd "$r" \
|
||||
&& git status --porcelain | awk '{print $2}' \
|
||||
| grep -E '(^|/)(\.env(\..*)?$|.*credentials.*|.*\.key$|.*\.pem$|id_rsa.*|.*secret.*|.*token.*\.txt$)' \
|
||||
| head -5
|
||||
)
|
||||
done
|
||||
```
|
||||
|
||||
Si la lista de coincidencias **no está vacía**, abortar el push completo, listar los archivos sospechosos y pedir al usuario que los gestione (añadir a `.gitignore`, mover, o decidir explícitamente que entren).
|
||||
|
||||
### 3. Auto-commitear dirty trees
|
||||
|
||||
Para cada repo con cambios sin commitear:
|
||||
|
||||
```bash
|
||||
for r in $REPOS; do
|
||||
( cd "$r"
|
||||
[ -z "$(git status --porcelain)" ] && exit 0 # limpio, nada que hacer
|
||||
git add -A
|
||||
if [ -n "$ARGUMENTS" ]; then
|
||||
MSG="$ARGUMENTS"
|
||||
else
|
||||
MSG="$(generate_auto_message)" # patrón descrito en sección 'Argumento'
|
||||
fi
|
||||
git commit -m "$MSG" \
|
||||
-m "Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" 2>&1 | tail -3
|
||||
)
|
||||
done
|
||||
```
|
||||
|
||||
`generate_auto_message` debe inspeccionar `git diff --cached --stat` y producir un subject como `feat(notebook): N cambios` cuando todos los paths comparten prefijo, o `chore: auto-commit` si están dispersos.
|
||||
|
||||
### 4. Push solo de repos con cambios locales (NO usar `git push` ciego)
|
||||
|
||||
**Filtrar primero, pushear despues.** Sobre 30+ repos, hacer `git push` en todos (incluso "Everything up-to-date") cuesta 30-90s en handshakes SSH. Usar refs locales (sin red) para decidir si hay algo que pushear:
|
||||
|
||||
```bash
|
||||
for r in $REPOS; do
|
||||
( cd "$r" \
|
||||
&& BRANCH=$(git rev-parse --abbrev-ref HEAD) \
|
||||
&& UPSTREAM_OK=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null) \
|
||||
&& if [ -z "$UPSTREAM_OK" ]; then
|
||||
echo "[push -u] $r ($BRANCH)"
|
||||
git push -u origin "$BRANCH" 2>&1 | tail -3
|
||||
else
|
||||
AHEAD=$(git rev-list --count @{u}..HEAD 2>/dev/null)
|
||||
if [ "${AHEAD:-0}" -gt 0 ]; then
|
||||
echo "[push] $r ($BRANCH, $AHEAD commits ahead)"
|
||||
git push origin "$BRANCH" 2>&1 | tail -3
|
||||
else
|
||||
echo "[skip] $r (up-to-date local refs)"
|
||||
fi
|
||||
fi
|
||||
)
|
||||
done
|
||||
```
|
||||
|
||||
Reglas:
|
||||
- Sin upstream → `git push -u` (siempre).
|
||||
- Con upstream y `rev-list @{u}..HEAD` > 0 → push.
|
||||
- Con upstream y 0 ahead → skip (ni siquiera intentar).
|
||||
|
||||
`rev-list @{u}..HEAD` solo lee refs locales, no toca la red. Esto es seguro porque cualquier commit local hecho en este PC ya esta a partir del paso 3 (auto-commit). Si en otro PC se hizo push y aqui no se ha hecho pull, sigue siendo correcto: no tenemos nada local que pushear.
|
||||
|
||||
Si `push` rechaza por non-fast-forward (rama behind), no abortar el resto. Reportar ese repo concreto y sugerir `/full-git-pull` antes; seguir con los demás repos.
|
||||
|
||||
### 5. Push del repo de pass (`~/.password-store`)
|
||||
|
||||
El password store es su propio repo Git en Gitea (`dataforge/pass-secrets`). `pass insert/edit/rm` ya commitea automaticamente, por lo que aqui SOLO hay que pushear los commits locales.
|
||||
|
||||
```bash
|
||||
PASS_DIR="$HOME/.password-store"
|
||||
if [ -d "$PASS_DIR/.git" ]; then
|
||||
( cd "$PASS_DIR" \
|
||||
&& DIRTY=$(git status --porcelain | wc -l) \
|
||||
&& if [ "$DIRTY" -gt 0 ]; then
|
||||
echo "[warn] $PASS_DIR tiene cambios sin commitear; pass deberia commitear solo. Saltando push."
|
||||
else
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD) \
|
||||
&& AHEAD=$(git rev-list --count @{u}..HEAD 2>/dev/null) \
|
||||
&& if [ "${AHEAD:-0}" -gt 0 ]; then
|
||||
echo "[push] pass-secrets ($BRANCH, $AHEAD commits ahead)"
|
||||
git push origin "$BRANCH" 2>&1 | tail -3
|
||||
else
|
||||
echo "[skip] pass-secrets (up-to-date)"
|
||||
fi
|
||||
fi
|
||||
)
|
||||
fi
|
||||
```
|
||||
|
||||
Reglas:
|
||||
- NO hacer `git add` ni `git commit` en `~/.password-store` (pass lo gestiona). Si hay dirty tree, avisar y saltar.
|
||||
- NO escanear `*.gpg` por patrones de secrets — son secrets cifrados, su contenido es opaco.
|
||||
- El push usa el remote ya configurado (`dataforge/pass-secrets` en Gitea).
|
||||
|
||||
### 6. fn sync
|
||||
|
||||
```bash
|
||||
USER=$(pass registry/basicauth-user | head -1)
|
||||
PASSWD=$(pass registry/basicauth-pass | head -1)
|
||||
TOKEN=$(pass registry/api-token | head -1)
|
||||
export FN_REGISTRY_API="https://${USER}:${PASSWD}@registry.organic-machine.com"
|
||||
export REGISTRY_API_TOKEN="$TOKEN"
|
||||
./fn sync
|
||||
```
|
||||
|
||||
Si `pass` falla con "decryption failed" → gpg-agent bloqueado. Pedir al usuario que ejecute `pass show unlock` en su terminal real (Bash tool no tiene TTY) y reintentar. Esa entrada es un dummy que solo muestra `Desbloqueada!` para cachear el passphrase sin exponer ninguna API key.
|
||||
|
||||
### 7. Resumen
|
||||
|
||||
Tabla concisa: por repo, commits creados (cuántos y subject), commits pusheados, o "ya estaba al día". Estado de `pass-secrets` (pushed / skipped / dirty). Y resultado de `fn sync` (sent / received / imported).
|
||||
1. `discover_git_repos_bash_infra` — lista repos bajo `fn_registry` (excluye `node_modules`, `.venv`, `cpp/vendor`, `cpp/build`, `sources`, `temp`, `subrepos`).
|
||||
2. Auto-inicializa apps/analyses sin `.git` con `ensure_repo_synced_bash_infra` (Gitea `dataforge/<basename>`).
|
||||
3. `scan_secrets_in_dirty_bash_cybersecurity` — aborta si detecta nombres sospechosos (`.env*`, `*credentials*`, `*.key`, `*.pem`, `id_rsa*`, `*secret*`, `*token*.txt`).
|
||||
4. `git_auto_commit_dirty_bash_infra` — commitea cada repo dirty.
|
||||
5. `git_push_if_ahead_bash_infra` — push solo si `rev-list @{u}..HEAD > 0` (sin red previa).
|
||||
6. Push de `~/.password-store` (sin commitear, pass autocommitea).
|
||||
7. `./fn sync` con credenciales cargadas desde `pass`.
|
||||
|
||||
## Notas
|
||||
|
||||
- **Modo no-interactivo por diseño.** El usuario prefiere commits frecuentes y push rápido. No se pregunta si commitear ni se pide mensaje (salvo que se pase via `$ARGUMENTS`).
|
||||
- **Secrets son la única razón para abortar antes de commitear.** Cualquier patrón sospechoso (`.env`, credenciales, claves) detiene el flujo y se reporta al usuario.
|
||||
- Submodules `cpp/vendor/` (mirrors upstream) se ignoran.
|
||||
- Si un sub-repo va `behind` el remote, su push se omite con un mensaje (no se aborta el resto). El usuario corre `/full-git-pull` cuando le convenga.
|
||||
- **Modo no-interactivo por diseño.** Auto-commitea sin preguntar.
|
||||
- **Único motivo de aborto antes de commitear:** secret detectado por nombre.
|
||||
- Si un sub-repo va `behind` el remote, su push se omite (no aborta el resto). Correr `/full-git-pull` y reintentar.
|
||||
- Para tocar la lógica: editar las funciones del registry, no este wrapper.
|
||||
|
||||
@@ -26,3 +26,4 @@ Reglas operativas del proyecto. Cada archivo es una regla independiente.
|
||||
| 20 | [artefactos.md](artefactos.md) | Termino paraguas para apps, analysis, vaults, projects y playgrounds (todo lo que no es codigo reutilizable) |
|
||||
| 21 | [playgrounds.md](playgrounds.md) | Prototipos rapidos dentro de un artefacto padre — heredan entorno, no se indexan, no tienen repo propio |
|
||||
| 22 | [registry_first.md](registry_first.md) | Antes de escribir codigo en un artefacto: buscar en el registry, reutilizar si existe, delegar a `fn-constructor` si falta |
|
||||
| 23 | [fn_doctor.md](fn_doctor.md) | `fn doctor`: diagnostico read-only de artefactos, services, sync drift, uses_functions, unused — wrappers de funciones del registry |
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
## fn doctor: diagnostico del registry y artefactos
|
||||
|
||||
`fn doctor` es el entrypoint unico para auditar la salud del sistema de forma read-only. Compone funciones del registry (`functions/infra/`) y formatea su salida. No modifica nada.
|
||||
|
||||
### Cuando usar
|
||||
|
||||
- Despues de un deploy: confirmar que servicios siguen vivos y artefactos intactos.
|
||||
- Despues de `git pull` o `fn sync`: detectar drift entre BD y disco.
|
||||
- Antes de `fn index` masivo: confirmar que apps Go/Py siguen declarando bien sus deps.
|
||||
- Periodicamente (cron): listar funciones del registry sin consumidores para limpiar.
|
||||
- Como gate antes de crear proposals: si `fn doctor` esta verde, las metricas del bucle reactivo son fiables.
|
||||
|
||||
### Comandos
|
||||
|
||||
```bash
|
||||
fn doctor # Corre TODOS los checks (artefacts + services + sync + uses-functions + unused)
|
||||
fn doctor artefacts # Solo artefactos: git/venv/app.md/upstream
|
||||
fn doctor services # Solo apps con tag 'service' + systemctl + puerto
|
||||
fn doctor sync # Solo drift pc_locations BD vs disco local
|
||||
fn doctor uses-functions # Solo audit imports reales vs uses_functions
|
||||
fn doctor unused # Solo funciones huerfanas del registry
|
||||
|
||||
fn doctor --json # Salida JSON (cualquier subcomando) — para agentes/scripts
|
||||
```
|
||||
|
||||
### Mapeo subcomando → funcion del registry
|
||||
|
||||
| Subcomando | Funcion |
|
||||
|---|---|
|
||||
| `artefacts` | `artefact_doctor_go_infra` |
|
||||
| `services` | `services_status_go_infra` |
|
||||
| `sync` | `pc_locations_drift_go_infra` |
|
||||
| `uses-functions` | `audit_uses_functions_go_infra` |
|
||||
| `unused` | `find_unused_functions_go_infra` |
|
||||
|
||||
Cada subcomando es un wrapper fino. Toda la logica vive en la funcion. Si quieres usar la salida en otro programa Go, importa la funcion directamente.
|
||||
|
||||
### Salida
|
||||
|
||||
Texto humano por defecto (tabwriter). `--json` produce array/objeto serializable para `jq`, agentes o pipes.
|
||||
|
||||
### Idempotente y seguro
|
||||
|
||||
- Read-only: ningun subcomando escribe, mata procesos ni cambia estado.
|
||||
- `services` abre conexiones TCP a `127.0.0.1:<port>` con timeout 500ms — no genera trafico saliente.
|
||||
- `artefacts` ejecuta `git rev-parse @{u}` con timeout 3s por artefacto.
|
||||
|
||||
### Acciones complementarias (NO son `fn doctor`)
|
||||
|
||||
`fn doctor` solo diagnostica. Las acciones derivadas son verbos separados:
|
||||
|
||||
| Si `fn doctor` reporta... | Accion |
|
||||
|---|---|
|
||||
| `directory_missing` | Marcar `pc_locations.status='missing'` o re-clonar via `/full-git-pull` |
|
||||
| `git_not_initialized` | `gitea_create_repo_bash_infra` + `ensure_repo_synced_bash_infra` |
|
||||
| `venv_broken_path` | `cd <analysis_dir> && rm -rf .venv && uv sync` |
|
||||
| `service active=inactive` | `systemctl --user start <unit>` o investigar logs |
|
||||
| `port not listening` | `port_kill_bash_infra <port>` (si zombie) y relanzar |
|
||||
| `missing_in_app_md` | Editar `app.md` y añadir el ID a `uses_functions` |
|
||||
| `unused` (funcion huerfana) | Decidir: usar, deprecar (tag), o borrar |
|
||||
| Backup viejo | `backup_all_bash_pipelines ~/backups/fn_registry` |
|
||||
|
||||
### Para agentes
|
||||
|
||||
Patron recomendado tras una accion no trivial (deploy, sync, mass edit):
|
||||
|
||||
```bash
|
||||
fn doctor --json > /tmp/doctor.json
|
||||
# Agente parsea JSON, decide si crear proposals o avisar al humano
|
||||
```
|
||||
|
||||
Si el agente quiere actuar sobre los hallazgos, abre proposals con `fn proposal add` referenciando los IDs afectados — NO toca artefactos directamente sin aprobacion humana.
|
||||
Reference in New Issue
Block a user