47fac22230
- .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>
232 lines
7.0 KiB
Bash
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
|