Skills Agentes

Guarding Destructive Operations

Guarda operaciones destructivas: borrados, sobrescrituras, reescritura de historial o resolución de nombres a rutas; rechaza en vez de avisar, comprueba antes de mutar, clasifica por estructura y prueba cada mitad.

Estrellas
947

en todo el repo

Actividad
60

0–100, la ruta de este skill

Actualizado
hace 5 días

último commit aquí

Commits
1

últimos 90 días

Contexto
2.6k tok

162 tok en reposo

Paquete
1 archivo

10 KB

Instalar

Funciona con cualquier agente que lea SKILL.md

npx -y skills add kajisho5/ffmpeg-skill --skill destructive-operations --agent claude-code

Se instala solo en este repositorio.

Qué hace

  • Añade y revisa precondiciones en operaciones destructivas y hace que fallen antes de mutar nada si no se cumplen.
  • Rechaza con un error, sin avisos ni flags `--force`, y coloca la comprobación antes de la primera mutación.
  • Clasifica rutas por estructura (no por prefijo) y valida nombres además de comprobar que la ruta resuelta no escape del directorio base.
  • Hace explícito el desfase entre el `--dry-run` y la ejecución real, y comprueba que el rechazo llegue a quien llama.
  • Exige pruebas que muten cada mitad de la guarda por separado y que verifiquen que el destino queda intacto al rechazar.

Úsalo cuando

  • Al escribir o revisar comandos `--rebuild`, `--reset` o `--purge`, borrados masivos o reescrituras de historial.
  • Al revisar un paso con forma de `rm -rf` o cualquier código que sobrescriba artefactos guardados.
  • Al revisar código que resuelve un nombre proporcionado por quien llama a una ruta del sistema de archivos.
  • Al auditar operaciones irreversibles que necesitan precondiciones claras y pruebas de que no mutan al fallar.

No lo uses cuando

    Qué lo activa

    Di cualquiera de estas frases y el agente debería cargar este skill.

    • Revísame este comando `--rebuild` y asegúrate de que no borre nada que no deba.
    • Añade una guarda que haga fallar el `rm -rf` si el repositorio contiene algo fuera de lo permitido.
    • Quiero validar que un nombre recibido como argumento no pueda escaparse del directorio base.
    • Ayúdame a escribir una prueba de que un borrado rechazado no muta ningún archivo.

    SKILL.md

    En inglés

    Guarding Destructive Operations

    Some operations have no undo: an orphan-branch rewrite, a recursive delete of every tracked file, an overwrite of a stored artifact, a DROP/purge path. The implementation is usually short and usually correct for the setup its author had in mind. The bug is never the deletion itself — it is that nothing checked whether the target was the thing the author imagined.

    Two shapes recur, and they take the same fix:

    • Blast radius. The operation is correct only in a dedicated, single-purpose target and is catastrophic in a mixed one.
    • Boundary escape. A caller-supplied name is joined onto a base directory and lands outside it.

    Refuse; don't warn, and don't add an override

    A destructive operation that discovers its precondition is violated should return an error and change nothing. A warning is read by nobody, and a --force escape hatch turns the guard into documentation.

    // A rebuild that recreates history does `checkout --orphan` then
    // `rm -rf --ignore-unmatch .`, keeping only the state files it rewrites.
    // That is correct in a dedicated mirror repository whose ONLY tracked content
    // is `.state/`. Run it where source code also lives and the new branch loses
    // the source code.
    func (e *Engine) rebuild() error {
        if err := e.ensureDedicatedRepo(); err != nil {
            return err   // nothing mutated yet
        }
        ...
    }
    

    The honest justification for having no override flag: there is no situation where the operator wants this command to delete unrelated tracked files. If a real one appears later, that is a separate, differently-named command — not a boolean on this one.

    Put the guard ahead of the first mutation

    "Refuses" is only true if the refusal happens before anything has changed. Walk the function and find the first statement with an effect — it is often not the obvious deletion. In-memory state clearing, a branch checkout, and a read-then-rewrite of the very files you are preserving all count.

    // Order that makes the refusal safe:
    //   1. reject detached HEAD / ambiguous state
    //   2. ensureDedicatedRepo()          <-- the new guard, nothing mutated yet
    //   3. read the state files to preserve
    //   4. CheckoutOrphan()
    //   5. RemoveAllTrackedFiles()
    //   6. ClearAllProgressCounts()
    

    Steps 3-6 are all mutations of something (3 is not, but it is where the "what to preserve" snapshot is taken, and a guard after it has already committed you to a shape). Land the guard at 2 and a rejected run leaves the working tree byte-identical.

    Also refuse ambiguous state before rewriting it. A history rewrite that assumes a named branch should reject detached HEAD up front rather than silently recreating the wrong ref.

    Classify structurally, not by string prefix

    The guard needs to answer "is every tracked path inside the directory this command owns?". A HasPrefix/startswith on the bare directory name is the wrong answer and looks right in every test you would think to write.

    // Bad — admits `.state-backup/notes.md` and `.stateful.txt`, so a repository
    // full of unrelated content passes the guard and gets deleted.
    func owned(path string) bool {
        return strings.HasPrefix(path, ".state")
    }
    
    // Good — the directory itself, or something genuinely under it.
    func owned(path string) bool {
        return path == ".state" || strings.HasPrefix(path, ".state/")
    }
    

    The same distinction in Python is Path(p) == base or base in Path(p).parents, not p.startswith(str(base)). Any time a check compares paths as strings, ask what a sibling with a longer name does to it.

    Name validation and resolved containment are two checks, not one

    When a public argument selects a file — a baseline name, a template id, a report slug — do both, in this order:

    NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
    
    def _validate_name(name: str) -> str:
        if ".." in name or not NAME_RE.match(name):
            raise ValueError(f"invalid name: {name!r}")
        return name
    
    def _resolve(name: str, base: Path) -> Path:
        candidate = (base / f"{_validate_name(name)}.json").resolve()
        if not candidate.is_relative_to(base.resolve()):
            raise ValueError(f"name escapes {base}: {name!r}")
        return candidate
    

    The containment half is not redundant with the regex. The regex stops .. segments and absolute paths; only the resolve() + containment check stops a symlink placed inside the directory that points out of it — a name matching ^[A-Za-z0-9] all the way through can still resolve anywhere. Route every entry point through both: the loader, the saver, and any path-building helper a public method still calls directly. A single unrouted helper reinstates the whole hole.

    Without this, an implicit suffix (name + ".json") plus an absolute-path argument reads any file on disk whose name happens to end in that suffix.

    A guard on the destructive path is invisible to the preview

    If the real run reaches the guard but --dry-run short-circuits earlier, the preview cheerfully describes an operation the real run will refuse. That is a defensible design — the destructive path is the one that must be safe — but be explicit about it:

    • The preview will show a full rebuild in a repository where the rebuild is forbidden. Only the real invocation errors.
    • Anything meant to warn the operator earlier has to live in the preview stage or the CLI argument-validation layer, duplicated deliberately, not moved.

    Say which one you chose in the PR description; a reviewer cannot tell from the diff whether the dry-run gap is intentional.

    Know what the caller does with your refusal

    Tightening validation only helps if the new error reaches somebody. A broad except Exception two frames up will turn a hard refusal into an ordinary result shape:

    try:
        features = self._load(baseline)
    except Exception as e:                       # already there, catches your ValueError
        return {"error": f"Analysis failed: {e}", "features": {}, "flags": {...}}
    

    This is often fine — an API boundary that never raises is a real contract, and the refusal surfaces as a normal error-shaped response rather than a protocol error. But check it deliberately: if that shape is what your caller sees, the integration test asserts on result["error"], not on pytest.raises.

    Testing

    • Mutate each half separately. Neuter only the regex and confirm a .. fixture fails; neuter only the containment check and confirm a symlink fixture fails. One combined test passes with either half deleted and proves neither.
    • Write the sibling-name fixture explicitly. A test whose repository tracks .state/data.json passes against HasPrefix(path, ".state") too. The test that separates the implementations tracks .state-backup/old.json and asserts the operation is refused.
    • Assert nothing was mutated on refusal, not just that an error came back: the branch is unchanged, the tracked file list is unchanged, the in-memory progress counters are unchanged. That is the actual claim.
    • Existing fixtures become the rejected shape. Older tests for the destructive path were often built loosely — a repository tracking app.txt alongside the state directory, because nothing cared. After the guard, such a test no longer reaches the code it was written to cover; it now exercises the refusal. Tighten those fixtures to the allowed shape and add the refusal case as a new test, or you silently lose coverage of the destructive path.
    • Reproduce the failure with the narrowest hook. When you need one specific operation to fail in order to test the recovery path, target it precisely — a blanket hook that rejects every operation of that kind aborts the run earlier than the path you were trying to reach, and the test proves something else.

    Checklist

    • Every irreversible operation states its precondition in code, not in a doc
    • Violation returns an error; no --force, no warn-and-continue
    • The guard runs before the first statement with an effect
    • Ambiguous state (detached HEAD, missing target, multiple candidates) rejected up front
    • Path/ownership classification is structural, never a bare string prefix
    • Caller-supplied names are validated and resolved-path contained
    • Every entry point routes through both checks, including internal helpers
    • The dry-run/preview gap is intentional and stated
    • Tests mutate each half of the guard independently
    • A sibling-name (x-backup/) and a symlink fixture both exist
    • Refusal tests assert the target is unchanged, not just that an error was raised

    Note for this repository (ffmpeg-skill)

    This repo's actual exposure is much narrower than the examples above — there is no --rebuild/--purge-shaped command, no history rewriting, and "Keep originals" is already a stated design principle (README.md design principle #9: no tool overwrites its input; a test hashes every input after every run, see tests/test_all.py/tests/test_contract.py's input-hash checks). The closest analogue is -o/output-path handling: every tool takes a caller-supplied output path and ffmpeg_base() always passes -y (overwrite without prompting) — there is no confirmation step before an existing file at that path is silently replaced. Since this is a local CLI a human or agent invokes directly (not a server resolving untrusted network input against a base directory), the "boundary escape" half of this skill is low-stakes here; the "refuse before the first mutation" and "existing input-hash tests still prove nothing was touched" halves are the parts that actually apply.

    Source: wdm0006/python-skills (MIT).

    Reproducido de kajisho5/ffmpeg-skill bajo licencia MIT. Leer esta página en markdown.

    Archivos

    1 archivo en el paquete. Solo se lee SKILL.md al activarse — las referencias se cargan si el skill decide que las necesita.

    Detalles

    Creador
    kajisho5
    Licencia
    MIT
    Recursos incluidos
    Solo SKILL.md
    Código fuente
    Ver SKILL.md

    Etiquetas

    Más de kajisho5/ffmpeg-skill

    Este repo incluye 13 skills. Si instalas uno, normalmente ya tienes los demás. Ver el pack ffmpeg-skill entero y su comando de instalación

    Edita vídeo y audio con FFmpeg local desde lenguaje natural: cortes, unión, reframe 9:16/1:1, velocidad, subtítulos, overlays, multicámara/sync, LUFS, HDR→SDR, LUTs, export y verificación. Python 3.9 stdlib, sin nube ni API keys.

    Costo de contexto al activarse
    8.1k tok
    Tamaño del paquete
    123 archivos
    Última actualización
    ayer
    redaccion contenido

    Genera configuraciones de CI/CD para GitHub Actions: compilación y pruebas de librerías y paquetes. Úsalo al crear o actualizar workflows de npm, Python, Go o Rust con caché de dependencias, pruebas en matriz y publicación de artefactos.

    Costo de contexto al activarse
    1.1k tok
    Tamaño del paquete
    5 archivos
    Última actualización
    hace 5 días
    devops infraestructura

    Resuelve conflictos de merge con varias ramas abiertas: unión, recomputación y reconstrucción como resoluciones correctas; artefactos generados, serialización no determinista e IDs renumerados; un auto-merge limpio no es un test que pasa.

    Costo de contexto al activarse
    2.8k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 5 días
    herramientas desarrollo

    Previene, detecta y corrige la subida a git de secretos (.env, API tokens, credenciales) y artefactos dev (builds, BD de trabajo, editor/SO). Cubre .gitignore (por qué no deja de trackear), git rm --cached, auditoría, historial y rotación.

    Costo de contexto al activarse
    2.1k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 5 días
    herramientas desarrollo

    Gestiona lanzamientos de librerías Python: versionado semántico, changelog (Keep a Changelog), automatización con GitHub Actions y deprecaciones. Úsalo al planificar lanzamientos, escribir changelogs o comunicar breaking changes.

    Costo de contexto al activarse
    1.1k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 5 días
    herramientas desarrollo

    Reproduce el gate de CI en local a partir del workflow: comando, rutas, marcadores y entorno exactos; desbloquea pasos cortocircuitados, fija la versión del linter que resuelve CI y confirma que el run es verde.

    Costo de contexto al activarse
    2.9k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 5 días
    herramientas desarrollo

    Skills relacionados

    Crea servidores MCP robustos en Python con FastMCP: diseño de herramientas, contratos de error, trabajo bloqueante, subprocesos/CLI, distribución, pruebas e inyección de prompts. Úsalo al escribir, exponer, depurar o probar servidores MCP.

    Costo de contexto al activarse
    3.2k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 5 días
    herramientas desarrollo

    Resuelve conflictos de merge con varias ramas abiertas: unión, recomputación y reconstrucción como resoluciones correctas; artefactos generados, serialización no determinista e IDs renumerados; un auto-merge limpio no es un test que pasa.

    Costo de contexto al activarse
    2.8k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 5 días
    herramientas desarrollo

    Previene, detecta y corrige la subida a git de secretos (.env, API tokens, credenciales) y artefactos dev (builds, BD de trabajo, editor/SO). Cubre .gitignore (por qué no deja de trackear), git rm --cached, auditoría, historial y rotación.

    Costo de contexto al activarse
    2.1k tok
    Tamaño del paquete
    1 archivo
    Última actualización
    hace 5 días
    herramientas desarrollo