Hooks
What alfred-dev runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
> /plugin marketplace add 686f6c61/alfred-dev > /plugin install alfred-dev@alfred-dev
Ships with alfred-dev. Installing the plugin gets these hooks.
What fires, and when
SessionStart
Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.
- Matches
startup|resume|clear|compact|forkbashbash
SessionEnd
python3
UserPromptSubmit
Fires before Claude sees each prompt you send. A plugin can use it to inject context, so the same instruction reaches the model every turn instead of only at session start.
python3python3
PreToolUse
- Matches
Write|Edit|Bash|mcp__.*python3 - Matches
Bashpython3 - Matches
Readpython3
PostToolUse
- Matches
Bashpython3python3python3 - Matches
Write|Editpython3
In the plugin's words
How alfred-dev describes its own hook set.
Alfred Dev - hooks del ciclo de vida del plugin
Where it lives
- hooks/activity-capture.pyGitHub
Read the script
#!/usr/bin/env python3 """ Hook centralizado de captura de actividad para Alfred Dev. Sustituye a ``memory-capture.py`` y ``commit-capture.py`` proporcionando un unico punto de entrada que registra TODA la actividad relevante en la base de datos de memoria persistente. Cada evento se almacena con tres niveles de detalle: - **summary**: texto legible en castellano para el dashboard. - **payload**: JSON estructurado para filtrado programatico. - **content**: texto completo sin truncar para consulta bajo demanda. Politica de escritura: solo se persiste lo que un humano querría releer. En runtime se registran prompts de slash Alfred, commits `git commit` de la sesión y escrituras de estado/handoff/UAT/`docs/project/`. No se copian Read/Glob/Grep, ni el historial antiguo de Git, ni cada edit de código. Politica fail-open: el hook NUNCA bloquea el flujo de trabajo. Cualquier error se imprime en stderr con prefijo ``[activity-capture]`` y se sale con codigo 0. """ import json import os import re import shlex import subprocess import sys from typing import Optional # --------------------------------------------------------------------------- # Constantes # --------------------------------------------------------------------------- # Directorios y patrones excluidos de la captura generica. # Son ficheros internos cuya actividad genera ruido sin aportar contexto # util al historial del proyecto. _EXCLUDED_PREFIXES = ( ".claude/", ".git/", "node_modules/", "__pycache__/", ".venv/", "venv/", ".mypy_cache/", ".pytest_cache/", ) # Patron que detecta 'git commit' como comando real, no como argumento # de otro comando (grep, echo, etc.). Solo detecta git commit al inicio # de la linea o despues de operadores de shell (&&, ||, ;). _GIT_COMMIT_RE = re.compile(r"(?:^|&&|\|\||;)\s*git\s+commit\b") # Comandos triviales que no aportan contexto util al historial. # Son comandos de lectura, navegacion o diagnostico que no modifican # el estado del proyecto. _TRIVIAL_COMMANDS = frozenset({ "ls", "pwd", "cd", "echo", "cat", "head", "tail", "less", "more", "wc", "whoami", "date", "which", "where", "type", "file", "true", "false", "clear", "history", "env", "printenv", "set", "export", "alias", "unalias", "source", ".", "test", "[", }) # Comandos de Alfred que pueden prepararse de forma determinista antes de que # el modelo intente resolver el prompt por si mismo. _ALFRED_PREFETCH_COMMANDS = frozenset({ "alfred", "audit", "map-codebase", "discuss", "feature", "fix", "quick", "lucius", "ship", "spike", "memory-ui", }) # Prefijo para los mensajes de aviso en stderr. _LOG_PREFIX = "[activity-capture]" _HIGH_VOLUME_CONTENT_MAX_LINES = 80 _HIGH_VOLUME_CONTENT_MAX_CHARS = 4000 def _ensure_plugin_root_on_path() -> str: """Asegura que la raiz del plugin este disponible para imports. Los hooks se ejecutan como scripts sueltos desde Claude Code y no siempre reciben ``PYTHONPATH`` con la raiz del plugin. Sin este ajuste, imports como ``core.memory`` o ``core.memory_config`` pueden fallar incluso cuando el plugin esta bien instalado. Returns: Ruta absoluta a la raiz del plugin. """ plugin_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if plugin_root not in sys.path: sys.path.insert(0, plugin_root) return plugin_root # --------------------------------------------------------------------------- # Tabla de dispatchers # --------------------------------------------------------------------------- def _build_dispatcher_table(): """Construye el mapa evento -> funcion de despacho. Se define como funcion para evitar referencias anticipadas a funciones que aun no se han declarado en el momento de la definicion del modulo. Returns: Diccionario con claves de evento y valores de funcion dispatcher. """ return { "Write": _dispatch_write, "Edit": _dispatch_edit, "Bash": _dispatch_bash, "Read": _dispatch_read, "Glob": _dispatch_glob, "Grep": _dispatch_grep, "Agent": _dispatch_agent, "WebFetch": _dispatch_web_fetch, "WebSearch": _dispatch_web_search, "NotebookEdit": _dispatch_notebook, "UserPromptSubmit": _dispatch_prompt, "UserPromptExpansion": _dispatch_prompt, "PreCompact": _dispatch_compact, "Stop": _dispatch_stop, } # --------------------------------------------------------------------------- # Funciones auxiliares # --------------------------------------------------------------------------- def _is_memory_enabled() -> bool: """Comprueba si la memoria persistente esta habilitada en la configuracion. Busca el fichero ``alfred-dev.local.md`` en el directorio ``.claude`` del proyecto actual y verifica que contenga la seccion ``memoria:`` con ``enabled: true``. Returns: True si la memoria esta habilitada, False en caso contrario. """ _ensure_plugin_root_on_path() try: from core.memory_config import is_memory_enabled except ImportError: return False return is_memory_enabled(os.getcwd()) def _memory_settings(): """Carga la configuracion efectiva de memoria del proyecto.""" _ensure_plugin_root_on_path() try: from core.memory_config import load_memory_config except ImportError: return { "enabled": False, "sync_to_native": True, "sync_commits_limit": 10, "capture_decisions": True, "capture_commits": True, "retention_days": 365, } return load_memory_config(os.getcwd()) def _is_excluded_path(file_path: str) -> bool: """Determina si un fichero debe excluirse de la captura generica. Se excluyen ficheros internos del plugin, del sistema de control de versiones y de caches de herramientas. La comparacion se - hooks/dangerous-command-guard.pyGitHub
Read the script
#!/usr/bin/env python3 """ Hook PreToolUse para Bash: guardia de comandos peligrosos. Intercepta las ejecuciones de Bash y analiza el comando en busca de patrones potencialmente destructivos (borrado catastrofico, force push, destruccion de datos, fork bombs, etc.). Si detecta un patron peligroso, bloquea la operacion (exit 2) con un aviso explicativo. Politica de seguridad: fail-closed. Si no se puede parsear la entrada del hook o se produce cualquier error inesperado, se bloquea la operacion (exit 2). Un hook de seguridad que permite la operacion cuando falla equivale a desactivar la proteccion justo cuando mas se necesita. Esta politica es coherente con secret-guard.sh, que tambien es fail-closed. Patrones vigilados: - rm -rf / (o ~, o $HOME): borrado catastrofico del sistema o del home. - git push --force a main/master: perdida de historial en ramas protegidas. - DROP DATABASE / DROP TABLE: destruccion de datos en base de datos. - docker system prune -af: eliminacion de volumenes y datos de contenedores. - chmod -R 777: permisos inseguros en todo un arbol de directorios. - fork bombs: denegacion de servicio local. - mkfs / dd sobre dispositivos: destruccion de disco. - Escritura directa a dispositivos de bloque. """ import json import os import re import shlex import sys # --- Patrones peligrosos --------------------------------------------------- # Cada tupla contiene (patron_compilado, descripcion_del_riesgo). # Los patrones se evaluan en orden; la primera coincidencia bloquea. _DANGEROUS_PATTERNS = [ # Borrado catastrofico: rm -rf aplicado a raiz, home o rutas de sistema. # Cubre: flags juntas (-rf, -fr), separadas (-r -f), con sudo, y flags largas. ( re.compile( r"(?:sudo\s+)?rm\s+" r"(?:-[a-zA-Z]*\s+)*" r"(?:--\w[\w-]*\s+)*" r"(?=-[a-zA-Z]*r)(?=.*-[a-zA-Z]*f)" r".*\s+(/\s|/\*|/$|~\s|~$|~\/|\$HOME|\$\{HOME\}|/usr|/etc|/var|/boot|/System)" ), "Borrado catastrofico: rm -rf sobre directorio raiz o de sistema", ), # Force push a ramas protegidas ( re.compile( r"git\s+push\s+.*" r"(--force\b|-f\b)" r".*\b(main|master)\b" ), "Force push a rama protegida (main/master): riesgo de perdida de historial", ), # Variante: force push sin rama explicita (se asume rama actual) ( re.compile( r"git\s+push\s+--force-with-lease\s*$" r"|git\s+push\s+-f\s*$" r"|git\s+push\s+--force\s*$" ), "Force push sin rama explicita: verifica que no estas en main/master", ), # Destruccion de base de datos ( re.compile(r"DROP\s+(DATABASE|TABLE|SCHEMA)\s", re.IGNORECASE), "Destruccion de datos: DROP DATABASE/TABLE/SCHEMA", ), # Docker prune agresivo (cubre -af, -fa, -a -f, -f -a y combinaciones con otros flags) ( re.compile( r"docker\s+system\s+prune\s+.*(-af|-fa)\b" r"|docker\s+system\s+prune\s+.*-a\b.*-f\b" r"|docker\s+system\s+prune\s+.*-f\b.*-a\b" ), "Docker system prune con -af: elimina todos los datos de contenedores", ), # Permisos inseguros ( re.compile(r"chmod\s+(-R\s+)?777\s+/"), "Permisos inseguros: chmod 777 recursivo sobre directorio raiz", ), # Fork bomb (variantes comunes en bash) ( re.compile(r":\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;?\s*:"), "Fork bomb: denegacion de servicio local", ), # Formateo de disco ( re.compile(r"mkfs\.\w+\s+/dev/"), "Formateo de disco: mkfs sobre dispositivo de bloque", ), # dd sobre dispositivo de bloque ( re.compile(r"dd\s+.*of=/dev/(sd|hd|nvme|vd|xvd)"), "Escritura directa a dispositivo de bloque con dd", ), # Escritura a dispositivo via redireccion ( re.compile(r">\s*/dev/(sd|hd|nvme|vd|xvd)"), "Redireccion de salida a dispositivo de bloque", ), # git reset --hard a remote (destructivo en combinacion con push) ( re.compile(r"git\s+reset\s+--hard\s+origin/(main|master)"), "git reset --hard a origin/main: descarta todos los cambios locales", ), ] _SAFE_ALFRED_HELPER_SUBCOMMANDS = frozenset({ "allow-stop-once", "blocked", "consume-prefetch", "discuss", "in-progress", "map-codebase", "memory-ui", "next", "pause", "progress", "quick", "resume", "search", "standup", "validate", "verify", }) _SHELL_CONTROL_TOKENS = frozenset({ ";", "&&", "||", "|", ">", ">>", "<", "<<", "2>", "&>", "&>>", }) _SHELL_CONTROL_SUBSTRINGS = ( "$(", "`", ) _SAFE_CAPTURE_SUFFIXES = ( "2>&1", "2>/dev/null", "2>/dev/null 2>&1", ) _DROP_SQL_REGEX = re.compile(r"\bDROP\s+(DATABASE|TABLE|SCHEMA)\b", re.IGNORECASE) _DEVICE_REDIRECT_REGEX = re.compile(r">\s*/dev/(sd|hd|nvme|vd|xvd)\w*", re.IGNORECASE) _SHELL_WRAPPERS = frozenset({"sh", "bash", "zsh"}) _SQL_CLIENTS = frozenset({"psql", "mysql", "mariadb", "sqlite3", "duckdb"}) def _command_basename(token: str) -> str: return os.path.basename(token or "") def _strip_leading_wrappers(tokens): """Elimina wrappers neutros al principio: sudo, env y variables inline.""" idx = 0 while idx < len(tokens): token = tokens[idx] base = _command_basename(token) if base == "sudo": idx += 1 while idx < len(tokens) and tokens[idx].startswith("-"): idx += 1 continue if base == "env": idx += 1 while idx < len(tokens) and "=" in tokens[idx] and not tokens[idx].startswith("-"): idx += 1 continue if "=" in token and not token.startswith("-"): name, _value = token.split("=", 1) if name.replace("_", "").isalnum(): - hooks/evidence-guard.pyGitHub
Read the script
#!/usr/bin/env python3 """ Hook PostToolUse para Bash: verificacion de evidencia. Implementa el patron «evidencia antes que afirmaciones» inspirado en el plugin Ralph Loop. Registra en un fichero temporal las ejecuciones de tests detectadas durante la sesion, de modo que otros componentes del plugin (stop-hook, check_gate) puedan verificar que cuando un agente afirma que los tests pasan, efectivamente se ejecutaron. La logica de deteccion y almacenamiento esta en ``evidence_guard_lib.py`` para que otros modulos puedan importarla sin ejecutar el hook. Politica fail-open: nunca bloquea el flujo de trabajo. """ import json import os import sys # Anadir el directorio de hooks al path para importar la libreria _HOOKS_DIR = os.path.dirname(os.path.abspath(__file__)) if _HOOKS_DIR not in sys.path: sys.path.insert(0, _HOOKS_DIR) from evidence_guard_lib import ( is_test_command, detect_test_result, record_evidence, ) _LOG_PREFIX = "[evidence-guard]" def _get_tool_result(data: dict) -> dict: """Normaliza payloads actuales e historicos del runtime de hooks.""" for key in ("tool_response", "tool_result", "tool_output"): value = data.get(key) if isinstance(value, dict): return value if isinstance(value, str) and value: return {"output": value} return {} def main(): """Punto de entrada del hook PostToolUse para Bash. Lee el JSON de stdin, detecta si el comando ejecutado es un runner de tests, analiza su resultado y lo registra como evidencia. Politica fail-open: cualquier error inesperado se reporta en stderr pero el hook siempre termina con exit 0 para no bloquear el flujo. """ try: data = json.load(sys.stdin) except (ValueError, json.JSONDecodeError) as e: print( f"{_LOG_PREFIX} Aviso: no se pudo leer la entrada: {e}", file=sys.stderr, ) sys.exit(0) try: tool_input = data.get("tool_input", {}) tool_result = _get_tool_result(data) command = tool_input.get("command", "") if not command or not is_test_command(command): sys.exit(0) # Extraer la salida del comando stdout = tool_result.get("stdout", "") or tool_result.get("output", "") stderr = tool_result.get("stderr", "") exit_code = tool_result.get("exit_code") output = f"{stdout}\n{stderr}" # Detectar resultado y registrar evidencia result = detect_test_result(output, exit_code=exit_code) record_evidence(command, result) if result == "unknown": print( f"{_LOG_PREFIX} Tests ejecutados pero no se pudo determinar " f"el resultado. El comando se ha registrado igualmente.", file=sys.stderr, ) except Exception as e: print( f"{_LOG_PREFIX} Error inesperado (fail-open, no bloquea): {e}", file=sys.stderr, ) sys.exit(0) if __name__ == "__main__": main() - hooks/evidence_guard_lib.pyGitHub
Read the script
#!/usr/bin/env python3 """ Libreria de verificacion de evidencia para Alfred Dev. Contiene las funciones de deteccion y almacenamiento de evidencia de tests separadas del punto de entrada del hook. Esto permite que otros modulos (tests, stop-hook, orchestrator) importen las funciones sin ejecutar el hook. El hook ``evidence-guard.py`` importa desde aqui y solo añade el punto de entrada ``main()`` con lectura de stdin. """ import json import os import re import sys from datetime import datetime, timezone from typing import Any, Dict, List, Optional # --- Constantes --- # Fichero donde se registra la evidencia de tests. _EVIDENCE_FILENAME = "alfred-evidence.json" # Tiempo maximo (en segundos) para considerar una evidencia como reciente. EVIDENCE_MAX_AGE_SECONDS = 600 # 10 minutos # Patrones de runners de tests. _CMD_POS = r"(?:^|[;&|])\s*" TEST_RUNNERS = [ rf"{_CMD_POS}pytest\b", r"\bpython3?\s+-m\s+pytest\b", rf"{_CMD_POS}vitest\b", rf"{_CMD_POS}jest\b", rf"{_CMD_POS}mocha\b", r"\bcargo\s+test\b", r"\bgo\s+test\b", r"\bnpm\s+test\b", r"\bnpm\s+run\s+test\b", r"\bpnpm\s+test\b", r"\bpnpm\s+run\s+test\b", r"\bbun\s+test\b", r"\bbun\s+run\s+test\b", r"\byarn\s+test\b", r"\byarn\s+run\s+test\b", r"\bpython\s+-m\s+unittest\b", rf"{_CMD_POS}phpunit\b", rf"{_CMD_POS}rspec\b", r"\bmix\s+test\b", r"\bdotnet\s+test\b", r"\bmaven\s+test\b", r"\bmvn\s+test\b", r"\bgradle\s+test\b", ] # Patrones de fallo en salida de tests. # Se buscan en contexto de salida de runners, así que usamos patrones # específicos para evitar falsos positivos con palabras como «fail-safe» # o «OK» en texto genérico. FAILURE_PATTERNS = [ r"(?<!\d )\bFAIL[ED]*\b(?![-_])", # FAIL, FAILED pero no fail-safe, fail_over ni "0 failed" r"[1-9]\d*\s+failures?\b", # "1 failure", "3 failures" (excluye "0 failures") r"\bfailing\b", r"[Tt]ests?\s+failed", r"ERRORS?\s*[:=]", # "ERRORS:" o "ERROR=" pero no "ERROR" suelto r"Assertion(?:Error|Failed)", # AssertionError → typo corregido a ambas formas r"test\s+result:\s+FAILED", r"Build\s+FAILED", r"[1-9]\d*\s+failed", # "1 failed", "3 failed" (excluye "0 failed") r"not\s+ok\s+\d+", # TAP format: "not ok 1 - test name" ] # Patrones de exito en salida de tests. SUCCESS_PATTERNS = [ r"\d+\s+passed", r"[Tt]ests?\s+passed", r"test\s+result:\s+ok\b", r"All\s+tests\s+passed", r"\d+\s+passing\b", r"\bPASS(?:ED)?\b", # PASS o PASSED r"\d+\s+tests?\s+complete", r"[Tt]ests?\s+run:\s+\d+", # "Tests run: N" — formato JUnit/Maven/TestNG r"(?m)^ok\s+\S+", # go test: "ok github.com/foo/bar 0.003s" (multiline para anclar ^) ] # --- Funciones de deteccion --- def is_test_command(command: str) -> bool: """Determina si un comando corresponde a la ejecucion de tests. Args: command: comando Bash ejecutado (cadena completa). Returns: True si el comando coincide con un runner de tests. """ return any(re.search(pattern, command) for pattern in TEST_RUNNERS) def detect_test_result(output: str, exit_code: Optional[Any] = None) -> str: """Analiza la salida de un comando de tests para determinar el resultado. Los patrones de fallo tienen prioridad sobre los de exito para cubrir el caso de tests parciales (3 passed, 1 failed). Si la salida no permite decidir pero el runner devolvio un codigo de salida distinto de cero, se considera fallo igualmente. Args: output: salida completa del comando (stdout + stderr). exit_code: codigo de salida del proceso, si esta disponible. Returns: ``"pass"``, ``"fail"`` o ``"unknown"``. """ has_fail = any( re.search(pattern, output, re.IGNORECASE) for pattern in FAILURE_PATTERNS ) has_pass = any( re.search(pattern, output, re.IGNORECASE) for pattern in SUCCESS_PATTERNS ) if has_fail: return "fail" if exit_code is not None: try: if int(exit_code) != 0: return "fail" except (TypeError, ValueError): pass if has_pass: return "pass" return "unknown" # --- Gestion del fichero de evidencia --- def _evidence_path(project_dir: Optional[str] = None) -> str: """Calcula la ruta del fichero de evidencia.""" base = project_dir or os.getcwd() return os.path.join(base, ".claude", _EVIDENCE_FILENAME) def _load_evidence(project_dir: Optional[str] = None) -> List[Dict[str, Any]]: """Carga el registro de evidencia existente.""" path = _evidence_path(project_dir) try: with open(path, "r", encoding="utf-8") as f: data = json.load(f) if isinstance(data, list): return data print( f"[evidence-guard] Aviso: el fichero de evidencia no contiene " f"una lista. Se descarta el contenido.", file=sys.stderr, ) return [] except FileNotFoundError: return [] except json.JSONDecodeError as e: print( f"[evidence-guard] Aviso: fichero de evidencia corrupto " f"({path}): {e}. Se perdera la evidencia anterior.", file=sys.stderr, ) return [] except OSError as e: print( f"[evidence-guard] Aviso: no se pudo leer la evidencia " f"({path}): {e}.", file=sys.stderr, ) return [] def _save_evidence( records: List[Dict[str, Any]], project_dir: Optional[str] = None, ) -> None: """Guarda el registro de evidencia. Trunca a 50 registros.""" path = _evidence_path(project_dir) dir_path = os.path.dirname(path) os.makedirs(dir_path, exist_ok=True) trimmed = records[-50:] try: with open(path, "w", encoding="utf-8") as f: json.dump(trimmed, f, en - hooks/prompt-route.pyGitHub
Read the script
#!/usr/bin/env python3 """UserPromptSubmit fail-open: inyecta la ruta Alfred si el texto no trae slash.""" from __future__ import annotations import json import os import sys def _plugin_root() -> str: return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) def main() -> int: try: data = json.load(sys.stdin) except Exception: return 0 if not isinstance(data, dict): return 0 prompt = ( data.get("prompt") or data.get("user_prompt") or data.get("content") or "" ) if not isinstance(prompt, str) or not prompt.strip(): return 0 try: root = _plugin_root() if root not in sys.path: sys.path.insert(0, root) from core.prompt_route import classify_prompt, render_route_hint match = classify_prompt(prompt) if not match: return 0 hint = render_route_hint(match) print( json.dumps( { "hookSpecificOutput": { "hookEventName": "UserPromptSubmit", "additionalContext": hint, } }, ensure_ascii=False, ) ) except Exception as exc: print(f"[prompt-route] aviso: {exc}", file=sys.stderr) return 0 if __name__ == "__main__": raise SystemExit(main()) - hooks/quality-gate.pyGitHub
Read the script
#!/usr/bin/env python3 """ Hook PostToolUse para Bash: quality gate de tests. Intercepta la salida de comandos Bash para detectar si se han ejecutado tests y, en caso afirmativo, analizar si han fallado. Cuando detecta fallos, informa por stderr con la voz de "El Rompe-cosas" (QA). Delega la deteccion de comandos y resultados a ``evidence_guard_lib`` para mantener una unica fuente de verdad en los patrones. Solo actua sobre comandos que coincidan con runners de tests conocidos. El resto de comandos Bash pasan sin inspeccion. """ import json import os import sys # Anadir el directorio de hooks al path para importar la libreria _HOOKS_DIR = os.path.dirname(os.path.abspath(__file__)) if _HOOKS_DIR not in sys.path: sys.path.insert(0, _HOOKS_DIR) from evidence_guard_lib import is_test_command, detect_test_result def _get_tool_result(data: dict) -> dict: """Normaliza payloads actuales e historicos del runtime de hooks.""" for key in ("tool_response", "tool_result", "tool_output"): value = data.get(key) if isinstance(value, dict): return value if isinstance(value, str) and value: return {"output": value} return {} def main(): """Punto de entrada del hook. Lee el JSON de stdin, extrae el comando ejecutado y su salida, y determina si hay tests fallidos. Si los hay, emite un aviso por stderr con la voz de El Rompe-cosas. """ try: data = json.load(sys.stdin) except (ValueError, json.JSONDecodeError) as e: print( f"[quality-gate] Aviso: no se pudo leer la entrada del hook: {e}. " f"La monitorizacion de tests esta desactivada para este comando.", file=sys.stderr, ) sys.exit(0) tool_input = data.get("tool_input", {}) tool_result = _get_tool_result(data) command = tool_input.get("command", "") if not command or not is_test_command(command): sys.exit(0) stdout = tool_result.get("stdout", "") or tool_result.get("output", "") stderr_out = tool_result.get("stderr", "") exit_code = tool_result.get("exit_code") output = f"{stdout}\n{stderr_out}" if detect_test_result(output, exit_code=exit_code) == "fail": print( "\n" "[El Rompe-cosas] He pillado tests rotos\n" "\n" "Los tests no pasan. Sorpresa: ninguna.\n" "No se avanza con tests en rojo. Asi funciona esto.\n" "\n" "Repasa la salida, corrige los fallos y vuelve a ejecutar.\n" "Ese edge case que no contemplaste? Lo encontre.\n", file=sys.stderr, ) sys.exit(0) if __name__ == "__main__": main() - hooks/secret-guard.pyGitHub
- hooks/secret-guard.shGitHub
- hooks/sensitive-read-guard.pyGitHub
- hooks/session-bootstrap.shGitHub
- hooks/session-end.pyGitHub
- hooks/session-start.shGitHub
All 12 scripts are listed above. The source is inlined for 6 of them, starting with whatever hooks.json actually runs. See all of them in the repo.
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Tu equipo de desarrolladores en un plugin. 10 agentes, 11 skills planas, 18 comandos /alfred-dev:*. Memoria persistente, quality gates con evidencia y MCP local.

