Files
fn_registry/bash/functions/pipelines/generate_capability_doc.sh
T
egutierrez 47fac22230 chore: auto-commit (799 archivos)
- .claude/CLAUDE.md
- .claude/commands/subagentes.md
- .claude/rules/INDEX.md
- .mcp.json
- bash/functions/cybersecurity/analyze_dns.md
- bash/functions/cybersecurity/audit_http_headers.md
- bash/functions/cybersecurity/audit_ssh_config.md
- bash/functions/cybersecurity/check_firewall.md
- bash/functions/cybersecurity/detect_suspicious_users.md
- bash/functions/cybersecurity/encrypt_file.md
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 00:28:20 +02:00

232 lines
7.0 KiB
Bash

#!/usr/bin/env bash
# generate_capability_doc — regenera la tabla "Funciones" de una pagina capability
# Preserva bloques curated (Ejemplo canonico, Fronteras, Prerequisitos, Notas, etc.)
# Usage: generate_capability_doc <group> [--registry <path>] [--out <path>]
set -euo pipefail
# --------------------------------------------------------------------------
# Helpers
# --------------------------------------------------------------------------
die() { echo "ERROR: $*" >&2; exit 1; }
warn() { echo "WARN: $*" >&2; }
# --------------------------------------------------------------------------
# Parse args
# --------------------------------------------------------------------------
GROUP=""
REGISTRY_PATH=""
OUT_PATH=""
while [[ $# -gt 0 ]]; do
case "$1" in
--registry)
shift
REGISTRY_PATH="${1:-}"
[[ -z "$REGISTRY_PATH" ]] && die "--registry requiere un valor"
shift
;;
--out)
shift
OUT_PATH="${1:-}"
[[ -z "$OUT_PATH" ]] && die "--out requiere un valor"
shift
;;
-*)
die "Opcion desconocida: $1"
;;
*)
[[ -n "$GROUP" ]] && die "Solo se acepta un <group>. Ya se especifico: '$GROUP'"
GROUP="$1"
shift
;;
esac
done
[[ -z "$GROUP" ]] && die "Uso: generate_capability_doc <group> [--registry <path>] [--out <path>]"
# --------------------------------------------------------------------------
# Resolver registry root
# --------------------------------------------------------------------------
find_registry_root() {
local dir
dir="$(pwd)"
while [[ "$dir" != "/" ]]; do
if [[ -f "$dir/registry.db" ]]; then
echo "$dir"
return 0
fi
dir="$(dirname "$dir")"
done
return 1
}
if [[ -n "$REGISTRY_PATH" ]]; then
# Si se pasa un path que termina en registry.db, tomar el directorio
if [[ -f "$REGISTRY_PATH" ]]; then
REGISTRY_ROOT="$(dirname "$(realpath "$REGISTRY_PATH")")"
elif [[ -d "$REGISTRY_PATH" ]]; then
REGISTRY_ROOT="$(realpath "$REGISTRY_PATH")"
else
die "registry no encontrado en: $REGISTRY_PATH"
fi
else
REGISTRY_ROOT="$(find_registry_root)" || die "No se encontro registry.db. Ejecutar desde dentro del registry o pasar --registry."
fi
DB="$REGISTRY_ROOT/registry.db"
[[ -f "$DB" ]] || die "registry.db no encontrado en: $DB"
# --------------------------------------------------------------------------
# Resolver output path
# --------------------------------------------------------------------------
if [[ -z "$OUT_PATH" ]]; then
OUT_PATH="$REGISTRY_ROOT/docs/capabilities/${GROUP}.md"
fi
# --------------------------------------------------------------------------
# Consultar funciones del grupo
# --------------------------------------------------------------------------
# JOIN custom entre functions y json_each(tags) — excepcion autorizada para sqlite3 directo.
# Usamos U+001F (ASCII Unit Separator) como separador de campos para evitar conflictos
# con el caracter | que aparece en signatures Python (ej. "list[int] | None").
SEP=$'\x1f'
ROWS="$(sqlite3 -separator "$SEP" "$DB" \
"SELECT f.id, f.signature, f.description
FROM functions f, json_each(f.tags) j
WHERE j.value = '${GROUP}'
ORDER BY f.id;" 2>/dev/null)" || die "Error al consultar registry.db"
# Escapar | en un valor para que no rompa la tabla Markdown
escape_pipe() {
printf '%s' "$1" | sed 's/|/\\|/g'
}
# Construir tabla Markdown
TABLE_HEADER="| ID | Firma | Que hace |
|---|---|---|"
TABLE_ROWS=""
FUNC_COUNT=0
if [[ -n "$ROWS" ]]; then
while IFS="$SEP" read -r id signature description; do
# Limpiar espacios extra
id="${id// /}"
signature="$(printf '%s' "$signature" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
description="$(printf '%s' "$description" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
# Escapar pipes internos en firma y descripcion
sig_esc="$(escape_pipe "$signature")"
desc_esc="$(escape_pipe "$description")"
TABLE_ROWS="${TABLE_ROWS}| \`${id}\` | \`${sig_esc}\` | ${desc_esc} |
"
FUNC_COUNT=$((FUNC_COUNT + 1))
done <<< "$ROWS"
fi
if [[ $FUNC_COUNT -eq 0 ]]; then
warn "El grupo '${GROUP}' no tiene funciones con ese tag en registry.db."
TABLE_CONTENT="_No hay funciones con tag ${GROUP}._"
else
TABLE_CONTENT="${TABLE_HEADER}
${TABLE_ROWS}"
fi
# --------------------------------------------------------------------------
# Crear o actualizar el archivo
# --------------------------------------------------------------------------
mkdir -p "$(dirname "$OUT_PATH")"
if [[ ! -f "$OUT_PATH" ]]; then
# --- Archivo nuevo: plantilla minima ---
cat > "$OUT_PATH" <<TEMPLATE
# Capability: ${GROUP}
_(Descripcion del grupo — editar a mano)_
## Funciones
${TABLE_CONTENT}
## Ejemplo canonico
_(Anadir 1-2 bloques de codigo end-to-end)_
## Fronteras
_(Que NO cubre este grupo)_
TEMPLATE
echo "${OUT_PATH} created (${FUNC_COUNT} functions)"
else
# --- Archivo existente: reemplazar SOLO el bloque "## Funciones" ---
# Estrategia awk:
# - Copiar todo hasta (e incluyendo) "## Funciones"
# - Imprimir linea en blanco + nueva tabla + linea en blanco
# - Saltar lineas hasta encontrar la proxima seccion "^## "
# - Reanudar copia desde esa seccion en adelante
# Escribir tabla en archivo temporal para pasarla a awk sin problemas de escaping
TMP_TABLE="$(mktemp)"
trap 'rm -f "$TMP_TABLE"' EXIT
printf '%s\n' "$TABLE_CONTENT" > "$TMP_TABLE"
TMP_OUT="$(mktemp)"
trap 'rm -f "$TMP_TABLE" "$TMP_OUT"' EXIT
awk -v table_file="$TMP_TABLE" '
BEGIN {
in_functions = 0
done = 0
# Leer tabla nueva en una variable
table_content = ""
while ((getline line < table_file) > 0) {
table_content = table_content line "\n"
}
close(table_file)
}
# Detectar inicio de la seccion Funciones
/^## Funciones$/ && !done {
print $0
printf "\n"
printf "%s", table_content
in_functions = 1
next
}
# Mientras estamos en el bloque Funciones, saltar lineas hasta proxima seccion
in_functions && /^## / {
in_functions = 0
done = 1
printf "\n"
print $0
next
}
in_functions {
# Saltar contenido viejo del bloque Funciones
next
}
# Fuera del bloque: copiar tal cual
{ print $0 }
END {
# Si no habia seccion siguiente (Funciones era la ultima), cerrar bien
if (in_functions) {
printf "\n"
}
}
' "$OUT_PATH" > "$TMP_OUT"
# Verificar que el archivo resultante no este vacio
if [[ ! -s "$TMP_OUT" ]]; then
rm -f "$TMP_OUT"
die "Error: awk produjo un archivo vacio. El archivo original no fue modificado."
fi
mv "$TMP_OUT" "$OUT_PATH"
echo "${OUT_PATH} updated (${FUNC_COUNT} functions)"
fi
exit 0