diff --git a/.metodo/CHECKSUMS b/.metodo/CHECKSUMS index b17a909..58d5b7a 100644 --- a/.metodo/CHECKSUMS +++ b/.metodo/CHECKSUMS @@ -1,2 +1,2 @@ -37cd8e9792f5cad5e57827068fe6cb00ca5602eca7692ef2ca943a48806bc8a8 metodo.md -22f810404cd02141177066918e66e98ba208c8529fe5ad92cdd66ccb98873ee5 bin/metodo +140cf44bc4d8e9eff08cd4f6466df22c897efa2c6c6f83662a747e3348a4f2bd metodo.md +bf2144db0bb987bd32ce7eb8d99021ca0d29cb7b6a48d30a8ffd0fe723ca2a3f bin/metodo diff --git a/.metodo/VERSION b/.metodo/VERSION index cc3bc00..6f140cd 100644 --- a/.metodo/VERSION +++ b/.metodo/VERSION @@ -1,5 +1,5 @@ # Copia vendorizada del Método (raíl C11). Gestionada por 'metodo init/update'. master: metodo -version: 2026-08-19 +version: 2026-08-22 estado: vendored adopted_commit: 6a10eab342a477846ab5f3e67f8ab17169f12807 diff --git a/.metodo/bin/metodo b/.metodo/bin/metodo index 17dcaa2..96f9813 100644 --- a/.metodo/bin/metodo +++ b/.metodo/bin/metodo @@ -16,6 +16,7 @@ set -uo pipefail # -u: variable sin definir es error · pipefail: un pipe here ROOT="." FAILS=0 +WARNS=0 # notas: se cuentan y se dicen, pero NO mueven el código de salida (ver check 8) SELF_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)" # raíz del repo del script # sha256 portable (Git Bash trae sha256sum; si no, shasum -a 256; si ninguno, la deriva no se mide) @@ -34,11 +35,12 @@ _write_checksums() { # $1 = dir .metodo del destino } _is_matriz() { [ -f "$SELF_ROOT/constitucion-de-facto.md" ]; } # marca inequívoca de la matriz -# --- veredictos (los cuatro colores del check) --- +# --- veredictos (los cinco colores del check) --- hdr() { printf '\n\033[1m== %s ==\033[0m\n' "$*"; } ok() { printf ' \033[32mok \033[0m %s\n' "$*"; } bad() { printf ' \033[31mAVISO\033[0m %s\n' "$*"; FAILS=$((FAILS+1)); } skip() { printf ' \033[33mSKIP\033[0m %s\n' "$*"; } +warn() { printf ' \033[33mnota\033[0m %s\n' "$*"; WARNS=$((WARNS+1)); } # ── Check 1 · integridad de la copia vendorizada (C11) ─────────────────────── # El máster vive en OTRO repo (la matriz), así que el testigo no es el máster sino el @@ -177,6 +179,93 @@ check_pointer() { [ "$broken" -eq 0 ] && ok "el destino del puntero resuelve ($found enlace(s) comprobado(s))" } +# ── Check 7 · la capa de DECISIONES: ids únicos (rojo) y campos (nota) ─────── +# Cuarta capa del corpus (`D6`, 2026-08-22). Existe porque una decisión guardada en el backlog se +# numera por FASE, y la numeración por fase colisiona: 18 decisiones distintas llamadas `D1` en un +# solo backlog.md. Por eso el id duplicado va en ROJO y no en nota — es AMBIGÜEDAD DE CITA sobre lo +# peligroso del repo: un nombre que apunta a dos decisiones distintas hace que citar una gobierne +# con la otra. Los CAMPOS van en nota: un repo que acaba de estrenar la capa no se pone rojo por +# terminar de rellenarla. +# Una entrada se DEFINE con un encabezado `### `, y el id es el primer token: así una +# mención inline ("ver D107") no define nada y el testigo no puede dar falsos positivos. +check_decisiones() { + hdr "capa de decisiones · ids y campos (C17 · D6)" + local f="$ROOT/doc/decisiones.md" + [ -f "$f" ] || { skip "sin doc/decisiones.md — la cuarta capa se adopta cuando la orquestadora del repo migre sus 'D' (D6)"; return; } + local ids n dups id + ids=$(awk '/^### /{ s=$0; gsub(/\*/,"",s); split(s,a,"[ \t]+"); if (a[2]!="") print a[2] }' "$f") + n=$(printf '%s\n' "$ids" | grep -c '[^[:space:]]') + if [ "$n" -eq 0 ]; then + warn "doc/decisiones.md no tiene NINGUNA entrada '### ' — un registro vacío no gatea nada (D6)" + return + fi + dups=$(printf '%s\n' "$ids" | sort | uniq -d) + if [ -n "$dups" ]; then + while IFS= read -r id; do + [ -n "$id" ] && bad "id duplicado en decisiones.md: $id — dos decisiones con el mismo nombre: citar una gobierna con la otra (C17); renumera la NUEVA desde el contador del repo" + done <<< "$dups" + else + ok "decisiones.md: $n entrada(s), sin ids duplicados" + fi + # Campos obligatorios de cada entrada: dueño (Xavier / técnica — una técnica escalada como suya + # ya coló) y estado (vigente / sustituida — sin él, una decisión muerta sigue mandando). + local faltan miss + faltan=$(awk ' + function flush( m){ if (id!="") { m=""; if(!hd) m=m" dueño"; if(!he) m=m" estado"; + if (m!="") print id "\t" m } id=""; hd=0; he=0 } + /^### / { flush(); s=$0; gsub(/\*/,"",s); split(s,a,"[ \t]+"); id=a[2]; next } + /^#{1,2} / { flush(); next } + { if ($0 ~ /\*\*due/) hd=1 + if ($0 ~ /\*\*estado/) he=1 } + END { flush() }' "$f") + if [ -n "$faltan" ]; then + while IFS=$'\t' read -r id miss; do + [ -n "$id" ] && warn "decisiones.md · «$id» sin:$miss — el dueño y el estado son el gate de la entrada (D6)" + done <<< "$faltan" + else + ok "todas las entradas llevan dueño y estado" + fi +} + +# ── Check 8 · textos de DECISIÓN dentro del backlog (nota) ─────────────────── +# Invierte el gradiente de comodidad: escribir la decisión en el sitio equivocado pasa a ser lo que +# cuesta. Se mide el BLOQUE —la línea que define `Dn` más sus líneas de continuación indentadas—; +# más de 3 líneas ya es un cuerpo de decisión, no una referencia. +# ⚠️ CALIBRADO para no enrojecer a los backlogs legacy que aún no han migrado, y SÍ se pueden +# distinguir: los ids nuevos nacen en `D100` (rango limpio — ningún legacy del árbol llega a 100). +# ⇒ `Dn` con n>=100 y cuerpo = decisión escrita HOY en el sitio equivocado → nota nominal. +# ⇒ `Dn` con n<100 = deuda de migración de la orquestadora de ese repo → se CUENTA y se dice, no se +# juzga. Y ninguno de los dos mueve el código de salida a propósito: la capa se estrena hoy y +# romper el gate de 21 repos por deuda ajena es cómo se aprende a apagar el gate. +check_decisiones_en_backlog() { + hdr "textos de decisión dentro del backlog (D6)" + local f="$ROOT/doc/backlog.md" + [ -f "$f" ] || { skip "sin doc/backlog.md (repo sin corpus de estado)"; return; } + local out legacy=0 nuevos=0 id len num + out=$(awk ' + function flush(){ if (id!="" && len>3) print id "\t" len; id=""; len=0 } + /^[[:space:]]*[-*|_]+[[:space:]]*[*~`]*D[0-9]+/ { + flush(); match($0, /D[0-9]+/); id=substr($0, RSTART, RLENGTH); len=1; next } + /^[[:space:]]+[^[:space:]]/ { if (id!="") { len++; next } } + { flush() } + END { flush() }' "$f") + if [ -n "$out" ]; then + while IFS=$'\t' read -r id len; do + [ -n "$id" ] || continue + num=${id#D} + if [ "$num" -ge 100 ] 2>/dev/null; then + nuevos=$((nuevos+1)) + warn "«$id» lleva $len líneas de texto de decisión dentro de backlog.md — las decisiones viven en doc/decisiones.md (D6)" + else + legacy=$((legacy+1)) + fi + done <<< "$out" + fi + [ "$legacy" -gt 0 ] && ok "$legacy texto(s) de decisión con id legacy ( "$d/backlog.md" <<'EOF' # Backlog -> Tres capas: **este backlog** = estado (metas + decisiones cerradas `D` + items) · -> AVISOS.md = bandera (se lee primero) · bitacora.md = relato append-only. -> Método vendorizado en ../.metodo/metodo.md. ⛔ Las `D` son inmutables. Un `##` no lleva marca de +> Cuatro capas: **este backlog** = el eje del TROCEO (metas + items; un item ≈ una sesión) · +> decisiones.md = lo peligroso, todo junto · AVISOS.md = bandera (se lee primero) · +> bitacora.md = relato append-only. Método vendorizado en ../.metodo/metodo.md. +> ⛔ Las decisiones son **inmutables**: contradecir = PARAR y reportar. Un `##` no lleva marca de > estado; abierto = tiene `[ ]`. -## Decisiones cerradas +## Decisiones + +Viven en **[decisiones.md](decisiones.md)** (cuarta capa, `D6`). Aquí se **citan por id**, no se +copian: escribirlas en los dos sitios es crear la deriva que §7 existe para impedir. ## Abierto - [ ] (siembra aquí el trabajo ABIERTO de hoy — brownfield: el pasado no se reconstruye) +EOF + [ -f "$d/decisiones.md" ] || cat > "$d/decisiones.md" <<'EOF' +# Decisiones + +> **Cuarta capa del corpus** (`D6`, Xavier 2026-08-22): backlog = el eje del troceo · +> **este fichero = lo peligroso, TODO JUNTO** · AVISOS.md = bandera, se lee primero · +> bitacora.md = relato append-only. Los raíles, en ../.metodo/metodo.md (§7: el destino se decide +> UNA vez). +> +> ⛔ **Una decisión `vigente` no se contradice**: o **PARAS y reportas**, o la **sustituyes** +> explícitamente — y la sustitución **edita la entrada vieja** (⚰️ + puntero a la nueva), nunca +> deja las dos vivas. +> ⛔ **Antes de cerrar nada, recorre el ámbito y mira QUÉ CONTRADICES.** Ésa es la fuerza que +> mantiene vivo este documento: no hay gate automático que la sustituya. +> +> **Orden: por ÁMBITO, no por fase** — numerar por fase es lo que fabricó **18 decisiones distintas +> llamadas `D1`** en un solo backlog. **Ids nuevos: contador único del repo desde `D100`**, +> monótono, jamás reiniciado por sección. Los legacy migran con su **id INTACTO**, citados +> `fase·Dn` (p. ej. `I11·D18`): C17 prohíbe renumerar, y renumerar rompe las citas cruzadas. +> +> **Formato**: una entrada = un `### `. El id es el primer token (es lo que lee +> `metodo check`). Los campos **dueño** y **estado** son el gate de la entrada. + +## Ámbito: (nombre del ámbito — p. ej. "transporte", "identidad", "panel") + +### D100 — (título corto, sin punto final) + +- **dueño**: Xavier · **fecha**: 2026-01-01 · **estado**: vigente +- **sustituye**: — · **testigo**: — *(si la decisión AFIRMA algo medible, el testigo y su condición + van AQUÍ, en esta misma línea: la clase de fallo del «VALIDADO» con su condición 116 líneas + más abajo)* + +(Texto **ÍNTEGRO** de la decisión, en las palabras en que se tomó. **Nunca un resumen**: un resumen +adquiere invariantes que la decisión no tiene, y ya gobernó una sesión entera en lugar de la `D`. +Sustituye este ejemplo por tu primera decisión real — si dejas el `D100` puesto, la primera de +verdad colisiona con él y `metodo check` se pone rojo.) EOF [ -f "$d/AVISOS.md" ] || cat > "$d/AVISOS.md" <<'EOF' # ⚠️ AVISOS @@ -306,7 +435,7 @@ Etiquetas: [NUEVA-REGLA] · [REFINA Cn] · [Cn-NO-ENCAJA] · [FALSA]. Se cosecha EOF _scaffold_docs "$tgt" _stamp_pointer "$tgt" - echo "metodo init: $tgt inicializado — .metodo/ (máster $mver) + doc/ de tres capas." + echo "metodo init: $tgt inicializado — .metodo/ (máster $mver) + doc/ de CUATRO capas (backlog · decisiones · AVISOS · bitácora)." echo " siguiente: siembra doc/backlog.md con el trabajo abierto de hoy y corre 'metodo check .'" } @@ -337,12 +466,16 @@ cmd_check() { check_headings check_links check_commits + check_decisiones + check_decisiones_en_backlog check_pointer hdr "resultado" + local nota="" + [ "$WARNS" -gt 0 ] && nota=$(printf ' · %d nota(s) — no mueven el código de salida, pero se cuentan' "$WARNS") if [ "$FAILS" -eq 0 ]; then - printf ' \033[32mPASA el Método\033[0m (0 avisos)\n'; exit 0 + printf ' \033[32mPASA el Método\033[0m (0 avisos%s)\n' "$nota"; exit 0 else - printf ' \033[31m%d aviso(s) — el Método NO pasa\033[0m\n' "$FAILS"; exit 1 + printf ' \033[31m%d aviso(s) — el Método NO pasa\033[0m%s\n' "$FAILS" "$nota"; exit 1 fi } diff --git a/.metodo/metodo.md b/.metodo/metodo.md index f1889a2..40257a9 100644 --- a/.metodo/metodo.md +++ b/.metodo/metodo.md @@ -4,7 +4,7 @@ Principios que valen para **cualquier** sesión (exploración, ejecución, despl incidente, orquestación) y para cualquier backlog. Los workflows concretos referencian este doc en vez de repetirlo. No son teoría: cada uno lleva el caso real que lo enseñó. -> **Versión 2026-08-19.** Documento **autocontenido**: es lo que se vendoriza, byte a byte, a cada +> **Versión 2026-08-22.** Documento **autocontenido**: es lo que se vendoriza, byte a byte, a cada > repo (no lleva enlaces a rutas de un árbol concreto, para que funcione clonado en solitario). > Cada regla lleva su **cicatriz** (meta-principio: un raíl se escribe con el incidente que lo > enseñó, no a priori). Procedencia, fuerza (nº de repos) y partición linter/juicio: fichero @@ -105,6 +105,15 @@ foco**, que es el recurso escaso. ⇒ Lo que convierte esto en método y no en e la mejora se **mide** y lo que queda fuera se **escribe** (un ⏸️ con su motivo, como el aplazamiento de la captura de hub), para que nadie lo confunda con un olvido. +⛔ **Y el refinement que lo hace ejecutable: una decisión que acarrea trabajo FUERA del alcance del +producto o de la fase NO se queda como casilla.** Se convierte en **épica nueva** si es grande, o se +va **a su fase** si tiene una — y se prioriza como todo lo demás: **por valor de producto**, no por +lo bien argumentada que esté. *(Xavier, `D7`, 2026-08-22. Estrenada el mismo día: `I11.11` salió de +su ficha a una épica propia **con su valor declarado — cero, porque no hay nadie esperándola**, que +es justo el dato que una casilla escondida dentro de otra ficha no da.)* +⇒ Es el mismo movimiento que el del ámbito de amenaza, una capa más arriba: **lo que no es de aquí +no se descarta ni se cuela — se muda, con su valor escrito.** + ## 1. Verifica el resultado, no la intención Comprueba lo que el sistema **sirve/hace**, no lo que su fuente **dice** que hará. Una señal @@ -920,11 +929,29 @@ copia pura.)* `sesion-ordenacion-docs.md` (capa `workflows/`)— pero vivía **en el workflow de la sesión que LIMPIA, no en el de las que ESCRIBEN**, así que se incumplía sistemáticamente y se pagaba después en una pasada. Por eso está aquí ahora. -⇒ **Al escribir, decide el destino UNA vez**, con el test del workflow: *¿una sesión futura necesita -esto para NO contradecir una decisión?* → **backlog**. *¿Es cómo nos enteramos —lo medido, las -predicciones, los mutantes, las premisas falsas, el relato—?* → **bitácora**, y en el backlog **un -puntero fechado**. ⭐ Escribirlo en los dos **no es prudencia: es crear la deriva** que §7 existe -para impedir, porque el día que uno de los dos se corrija el otro seguirá ahí. +⇒ **Al escribir, decide el destino UNA vez**, y los destinos son **CUATRO**: el corpus de un repo +tiene **cuatro capas**, no tres. *¿Es una decisión cerrada, de las que otra sesión no puede +contradecir sin PARAR?* → **`doc/decisiones.md`**, con su texto **íntegro**. *¿Es trabajo — un item +que cabe en una sesión?* → **`doc/backlog.md`**, que es **el eje del troceo**. *¿Es lo que hay que +saber el primer día?* → **`doc/AVISOS.md`**, que se lee antes que el backlog. *¿Es cómo nos +enteramos —lo medido, las predicciones, los mutantes, las premisas falsas, el relato—?* → +**`doc/bitacora.md`**, append-only, y en el backlog **un puntero fechado**. ⭐ Escribirlo en dos +**no es prudencia: es crear la deriva** que §7 existe para impedir, porque el día que uno de los dos +se corrija el otro seguirá ahí. +⛔ **Y la cuarta capa existe porque una decisión guardada en el backlog se numera POR FASE, y la +numeración por fase colisiona.** *(Cicatriz 2026-08-22, `D6` de Xavier: **18 decisiones distintas +llamadas `D1`** en un solo `backlog.md` — y cuatro fallos de una semana que salen de ahí: un +**resumen** que gobernó en lugar de la decisión, un «precio aceptado» que Xavier nunca pensó, dos +ids que colisionaban, y una sustitución con las **dos versiones vivas** a 90 líneas de distancia. +En sus palabras, para qué sirve la capa: «así tienes todo lo peligroso en un único documento».)* +⇒ Por eso se ordena **por ámbito, no por fase**; cada entrada lleva **texto íntegro** (nunca un +resumen — un resumen adquiere invariantes que la decisión no tiene), **dueño visible** (*Xavier* o +*técnica*: una técnica escalada como suya ya coló), **estado**, y el **testigo en la misma línea** +si afirma algo medible (§1). Una sustitución **edita la entrada vieja** —⚰️ y puntero—, jamás deja +las dos vivas. +⇒ Y «todo lo peligroso en un único documento» solo protege si **alguien lo recorre** (§8): toda +sesión de diseño mira ahí **qué contradice** antes de cerrar nada, y si contradice una `vigente`, +**PARA y reporta** (§6) o la sustituye explícitamente. ⛔ **Un valor por DEFECTO declarado como dato en la capa base solo es fuente única si TODOS los consumidores pasan por el merge.** El que se lo salta no es un descuido: es una **segunda fuente** —con su propia verdad— y hay que buscarlo **explícitamente al introducir el default**, no el día que