Hooks y guards

Activa los hooks de contexto que achican la salida de herramientas, y corre las verificaciones locales de guardrail que mantienen sano un workshop compartido.

Objetivo#

Activar los dos hooks de contexto opcionales del harness (ambos apagados por defecto), instalar y correr las verificaciones de orch guard, y elegir un guard.profile que decide cuanto de esto conecta Orchemax por ti.

Hooks de contexto (opt-in, apagados por defecto)#

orch.yaml:

context:
  hooks:
    post_tool: true    # Claude Code PostToolUse / OpenCode tool.execute.after
    pre_compact: true  # Claude Code PreCompact
  • post_tool achica un resultado de herramienta sobredimensionado antes de que llegue al modelo (orch hook post-tool), usando los mismos ajustes de crush/externalize que context.guidance. Se salta cualquier nombre de herramienta listado en context.guidance.protect_tools (por defecto: Bash, PowerShell, Read, Grep, Glob, Edit, Write, Agent, Task, WebFetch, Shell, bash, read_file, write_file — herramientas de codigo donde una reescritura con perdida ocultaria un resultado real).
  • pre_compact corre el mismo condensador que orch memory condense sobre .workflow/BOARD.md y .orch/LAST_SESSION.md justo antes de que la compactacion nativa del harness descarte la transcripcion — asi la seccion ## Clarified del board y los checkboxes abiertos sobreviven byte por byte en vez de resumirse.

Ninguno de los dos hooks hace algo por si solo: ambos necesitan orch guard wire --claude (o --profile) para instalarse de verdad en ~/.claude/settings.json / la carpeta del plugin de OpenCode.

Verificaciones de orch guard#

orch guard dup <file>            # deniega/advierte sobre un archivo que duplica un simbolo compartido o clona un cuerpo de funcion
orch guard code-lang [root]      # los identificadores/strings deben coincidir con workspace.code_lang
orch guard lang [root]           # los docs deben coincidir con workspace.docs_lang
orch guard ddl [files...]        # migraciones/CREATE TABLE necesitan un companero seeder/repository
orch guard tracker [TRACKER.md]  # sanidad de la tabla de estado de TRACKER.md (T1–T4)
orch guard lock <path> --holder  # deniega editar shared/** sin un lock valido

orch guard dup es advisory por defecto — imprime donde vive la copia existente y deja que la escritura continue. Define guard.dup.mode: block en orch.yaml para denegar la escritura en vez de eso (off desactiva el escaneo por completo). guard.dup.window / min_tokens / max_files ajustan el escaneo de clones.

Un guard bloqueante — dup en modo block, una escritura sin lock en shared/, un DDL sin companero seeder/repository, un deny de idioma en docs/codigo, una regla de usuario con severity: block, o uno de los seis genesis guards cuando guard.profile lo hace bloqueante — falla la tarea despachada y hace que worktree_promote la rechace, nombrando el guard que denego; los hallazgos advisory y warn solo anotan el reporte y nunca cambian el estado de la tarea.

orch guard tracker y su recordatorio code staged without TRACKER.md al sellar son opt-in: no dicen nada hasta que el proyecto ya tiene un TRACKER.md en algun lugar del arbol, o se define guard.tracker: true en orch.yaml. Un workspace nuevo que nunca adopto la convencion no recibe ninguna nota de tracker en ningun lado — hooks, guard hooks run o sellado.

orch guard lang --count y orch guard code-lang --count recorren el arbol e imprimen una fila rule(lang) hits sample_path:line por cada idioma detectado, sin denegar nunca (sale con 0 siempre) — corre uno antes de cambiar un codebase existente a guard.profile: strict, que corre el mismo conteo primero y se niega sin --yes si encuentra hits.

Idiomas naturales#

workspace.docs_lang y workspace.code_lang aceptan cualquiera de estos codigos (tambien xx-YY, p. ej. es-US); un codigo sin soporte salta el guard con una nota en vez de fallar.

Codigo Idioma lang marca prosa en… code-lang marca identificadores/strings en…
en ingles cualquier otro idioma soportado cualquier otro idioma soportado
es espanol cualquier otro idioma soportado cualquier otro idioma soportado
pt portugues cualquier otro idioma soportado cualquier otro idioma soportado
fr frances cualquier otro idioma soportado cualquier otro idioma soportado
de aleman cualquier otro idioma soportado cualquier otro idioma soportado
it italiano cualquier otro idioma soportado cualquier otro idioma soportado

El ingles nunca se marca dentro de identificadores para un code_lang que no sea ingles (las APIs de librerias y las palabras clave del lenguaje son en ingles por convencion) — define guard.code_lang_strict: true para marcarlo tambien ahi. Los strings (literales entre comillas) siempre se revisan contra todos los idiomas soportados, incluido el ingles, porque un string de producto en el idioma equivocado es un bug de producto de cualquier forma.

orch guard comment-prose [path] es advisory en todo perfil — nunca deniega. Por archivo de codigo anota bloques de 6+ lineas de comentario consecutivas (el encabezado del archivo, el doc de paquete/modulo, los bloques de licencia y los archivos generados quedan exentos) y cualquier bloque con un marcador narrativo (una fecha ISO, "before the fix", "used to", "we saw", "the founder", "historically"). Corre como nota en orch guard hooks run y en cada verification report; --count imprime una fila resumen en vez de cada hit.

Al sellar (la verificacion de un worker despachado), codigo que cambio sin que ningun archivo de test/spec cambiara en el mismo diff recibe una nota advisory tests: none touched — nunca una falla, y en silencio cuando lo unico que cambio es docs/config.

Gate-silla: cuando el seat que corre orch guard ide-hook es el Chair (ORCH_ROLE=chair, o sin rol con un Chair ya corriendo para este workspace), cuenta los archivos de codigo que el propio Chair escribe en la sesion. La 3ra escritura recibe un advisory que nombra dispatch_worker; la 4ta en adelante se deniega en shared/strict (solo advisory en lite, apagado en none). ORCH_GUARD_CHAIR_WRITES=0 lo desactiva para el resto de la sesion.

Sandbox y litter#

orch guard litter [files...] deniega un archivo o dot-directory nuevo que se lee como scratch de agente en vez de codigo de proyecto — editar un archivo existente nunca es litter. Las mismas formas se juzgan en el write hook del IDE, al sellar, y (solo lectura) en orch harness scan / workspace clean para lo que ya esta en disco.

Reglas de nombre y sufijo#

Un basename se juzga junto con su extension, asi que el codigo comun nunca cae en la regla:

Forma del basename Extension tipo doc (.md .txt .log .out .rtf .csv .json .html), o sin extension fuera de bin//scripts/ Cualquier otra extension
Stem de prueba/scratch solo — test(s) tmp temp scratch verify check e2e gate audit debug dump poc draft note(s) report transcript consulta (p. ej. report.md, scratch solo) litter nunca litter (report.go, check.py, verify.ts, tmp.go)
El mismo stem + mas palabras (gate-x.sh, verify_login.sh, e2e-smoke.sh) litter litter, salvo que la extension sea de codigo compilado/modulo/config (.go .rs .c .cs .java .tsx .jsx .vue .svelte .php .dart .sql .proto .py .ts .js .mjs .cjs .rb .pl .lua .yaml .yml .toml .ini .cfg .json … — decision del chair, el codigo normal nunca se deniega por nombre; las extensiones de shell/batch .sh .ps1 .cmd .bat quedan afuera a proposito, asi que verify_login.sh sigue siendo litter, y un .py/.js/.ts/.rb/.pl sin referencia se juzga en cambio por la regla de scripts sin referencia de abajo)

Los marcadores de sufijo se suman a la regla de nombre:

Sufijo Extension Litter?
-final -copy -backup -bak -orig -rej -swp cualquiera si
(1), (2), … (copia generada por el OS) cualquiera si
-v2/_v2/.v2, -new, -old solo tipo doc si
-v2/_v2/.v2, -new, -old codigo/schema (.go, .sql, .proto, …) no — es una version normal, no una copia

Duplicados#

  • Un archivo nuevo cuyos bytes son identicos a un archivo ya versionado del mismo tamano es litter — duplicate of <path> (solo se leen los archivos del mismo tamano). El paso de arbol completo de orch harness scan / workspace clean solo corre esta regla dentro de un repositorio git, y solo contra archivos sin versionar de 64 bytes o mas — un duplicado ya versionado es decision del usuario, y una coincidencia de tamano por debajo de ese piso es demasiado probable que sea casualidad.
  • Un doc nuevo bajo la carpeta de docs cuyo stem (quitando primero los sufijos de arriba) o primer encabezado # coincide con un doc ya versionado es litter — update the canonical doc <path>.

Scripts nuevos sin referencia#

Al sellar, un archivo nuevo con forma de script (.sh .ps1 .cmd .bat, o cualquier archivo nuevo con el bit ejecutable de Unix) que nada en el cambio ni en el arbol versionado nombra por ruta es litter — muevelo a .sandbox/<seat>/ o conectalo a deploy/Makefile/CI. .py .js .ts .rb .pl tienen una segunda forma de pasar: un hermano ya versionado con la misma extension en ese mismo directorio, o que el stem del archivo aparezca como palabra completa (al estilo import) en algun otro archivo. Los nombres de punto de entrada por convencion de framework (index.*, main.*, __init__.py, *.config.*, *.d.ts, …) nunca necesitan referencia.

Dot-directories#

Solo .sandbox/, .orch/, .git/, .cursor/rules/, .github/, y los archivos .claude/settings.local.json (y el legado .claude/settings.json), .gitignore, .gitattributes, .editorconfig, .mcp.json los conecta Orchemax. Cualquier otro dot-directory nuevo se deniega por diseno — un agente no puede crear .vscode/, .devcontainer/, .husky/, .storybook/, ni ninguna otra carpeta de herramienta que empiece con punto. Si necesitas uno de esos en el arbol, subelo tu mismo a mano (un archivo existente anterior al propio wiring de Orchemax, o cuyo ultimo commit no es de una identidad orch-<seat>, nunca lo reporta harness scan/workspace clean), o agrega su glob a keep en el proyecto.

Shell write gate#

El mismo veredicto cubre el shell: el hook PreToolUse de Bash/PowerShell (y el beforeShellExecution de cursor) lee la linea de comando buscando las rutas que crearia — >/>>, cat > x <<EOF, tee, destinos de cp/mv, Out-File, Set-Content, Add-Content, New-Item, Copy-Item, Move-Item —, las resuelve contra el cwd del shell y deniega un destino litter o un nombre de dispositivo de Windows (> nul, que deja un archivo literal imposible de borrar; descarta con >/dev/null o $null), mientras que /dev/null, $null, NUL: y un > dentro de comillas quedan intactos.

Severidad, el sandbox, y "tuyo, no litter"#

La severidad sigue a guard.profile: deniega en el hook PreToolUse del IDE y al sellar en lite/shared/strict, solo advisory en bring, apagado en none.

orch harness scan y orch workspace clean nunca reportan un archivo existente y versionado como litter cuando se cumple alguna de estas:

Excepcion Condicion
referenciado otro archivo de texto versionado lo nombra, por ruta o por dir/basename
pre-Orchemax o humano versionado, y o bien fue agregado antes del propio wiring de Orchemax en este proyecto, o su ultimo commit no tiene autor orch-<seat>
en keep su ruta coincide con un glob de projects[].keep (soporta **) en orch.yaml

Un archivo no versionado solo puede recibir la primera o la tercera excepcion — nunca la segunda, porque no tiene historial de commits que revisar. Cada commit que hace un worker despachado lleva GIT_AUTHOR_NAME/GIT_COMMITTER_NAME = orch-<seat> y correo <seat>@orch.local, asi que esa revision es una identidad real, no un trailer adivinado. El nombre de seat/worker en si debe coincidir con [A-Za-z0-9][A-Za-z0-9._-]{0,63} (1 a 64 caracteres, sin separador, sin punto inicial) y no puede ser un nombre de dispositivo de Windows — se rechaza antes del dispatch, porque se vuelve un segmento de ruta bajo .sandbox/.

orch project add siembra <project>/.sandbox/README.md y marca .sandbox/ en el .gitignore del proyecto. Al cerrar cada tarea, cualquier archivo que esa tarea creo bajo su seat con prefijo tmp/temp o terminacion .log/.out/.tmp se borra de inmediato; todo lo demas en .sandbox/<seat>/ espera el barrido por edad una vez que pasa guard.sandbox_days (default 14) — que nunca borra README.md, nunca borra un directorio, y nunca sigue un symlink o junction fuera del sandbox. orch workspace clean --dry-run lista la carpeta de sandbox de cada seat con su tamano. orch harness scan lista el litter que ya existe en disco (conteo y primeras 10 rutas) y nunca lo borra.

La regla en palabras, para cualquier agente que trabaje bajo Orchemax: todo lo que no es codigo de producto va a .sandbox/<seat>/; los archivos temporales ahi se borran al cerrar la tarea, no esperan al barrido. Nunca crear archivos -v2/-final/copia — actualizar el canonico en su lugar. Nunca > nul; usar /dev/null o $null. Los logs de build tambien van a .sandbox/<seat>/ (tee build.log en el arbol versionado se deniega igual que cualquier otra escritura litter). Un script que se invoca desde el shell no se inspecciona por dentro — el hook solo ve la linea de comando, no el cuerpo del script — asi que la misma regla aplica: su salida va bajo .sandbox/<seat>/, por disciplina propia.

Referencias colgantes#

Al sellar, Orchemax lee cada ruta que el cambio borro o renombro y busca el resto del arbol versionado por ella — la ruta relativa completa, o su forma mas corta dir/basename — solo cuenta si coincide en un limite real de segmento de ruta, asi que db/client.go nunca dispara sobre un mongodb/client.go borrado, y mover main.go a cmd/app/main.go con el Makefile actualizado no da falso positivo. Un hit en una linea de codigo/deploy/config falla el sellado y lista file:line → ruta vieja; un hit en una linea de puro comentario o dentro de un doc .md es solo advisory. orch harness scan reporta la misma forma para rutas que git ya no encuentra en disco (borradas, sin git rm todavia) pero que un archivo versionado sigue llamando, y por separado marca un core.hooksPath que apunta a un directorio que ya no existe. La severidad sigue la misma escalera de guard.profile que litter: advisory en bring, apagado en none.

Pasos#

orch setup ya ofrece instalar el scope de proyecto una vez, justo despues de enlazar el workspace (default si; --yes acepta, nunca toca el scope de usuario; --allow-user-scope hace falta para la entrada MCP global de agy). Los pasos de abajo son para instalar, reinstalar, o elegir otro perfil a mano.

  1. Elige un perfil (o acepta el default recomendado):

    orch guard profile lite     # docs + skills; sin hooks (recomendado dia 1)
    orch guard profile shared   # + hooks de git pre-commit/pre-push en repos shared/project
    orch guard profile strict   # + wire de hooks de usuario en Claude, conciencia de locale/tracker
    orch guard profile bring    # mantiene tu propio CLAUDE/rules; orch solo agrega locks/simbolos
    orch guard profile none     # solo orch.yaml
    
  2. Conecta los adaptadores de ese perfil:

    orch guard wire --profile
    

    O conecta adaptadores especificos directamente: --git, --claude, --cursor, --copilot. Todo wire es scope de proyecto por defecto: --claude escribe/mezcla <workspace>/.claude/settings.local.json — ignorado por git, la entrada se agrega al .gitignore del proyecto en el primer wire — y nunca abre ~/.claude/settings.json. Para conectar el scope de usuario — la configuracion que lee toda sesion de Claude Code en la maquina, mas el MCP global de agy y el plugin de OpenCode — agrega --scope user; Orchemax te pide confirmacion nombrando el archivo, el bloque marcado, el respaldo (settings.json.bak-<yyyymmdd-hhmmss>) y el comando para deshacer. En un script, --allow-user-scope es ese consentimiento; un --yes general no lo es. --copilot no hace nada: una corrida real de copilot -p en modo headless nunca carga .github/hooks/*.json (verificado en vivo contra el CLI instalado) — la confirmacion interactiva de confianza de carpeta que lo condiciona nunca aparece en modo headless, asi que Orchemax se niega a escribir un hook que solo parecería activo. Copilot queda cubierto por el sellado (la revision de guardrails que igual corre sobre el diff de cada worker al cerrar su sesion), igual que un agente sin hook en tiempo real. orch harness scan reporta cada scope (claude de usuario, claude.project, copilot) como conectado o solo-sellado — copilot hoy siempre es solo-sellado.

  3. Instala los hooks de git directamente (independiente de guard wire):

    orch guard hooks install [repo]
    orch guard hooks status [repo]
    orch guard hooks run [repo]       # lo que realmente llaman los hooks instalados
    

Verificar#

orch guard profile
orch guard hooks status

Deshacer#

orch guard unwire            # quita el bloque marcado de orch en ambos scopes (deja respaldo)
orch guard unwire --git      # tambien quita pre-commit/pre-push de git
orch guard unwire --project  # solo scope de proyecto — no abre ningun archivo del usuario
orch guard hooks uninstall [repo]

Relacionado#

  • your-own-rules.md — las verificaciones que son tuyas, corriendo donde corren estas.
  • memory-and-ledger.md — que es lo que condensa pre_compact.
  • troubleshooting.md — "el hook no hizo nada" cuando el workspace no esta registrado.