Habilidad: chrislacey89/excavate
¿Qué hace excavate?
Recupera lo que un repositorio heredado ya sabe sobre sí mismo antes de que la primera característica funcione a ciegas sobre él. La salida es un expediente fuera del árbol (puntos calientes, acoplamientos, decisiones recuperadas, un mapa de a quién preguntar y un bloque de Cobertura que dice lo que el historial pudo y no pudo decirte) más un PR opcional de entradas docs/solutions/ en el formato de /compound, para que /research, /write-a-prd y /execute los lean a través de la búsqueda grep de palabras clave que ya ejecutan.
Dos cosas lo mantienen honesto. El método de Tornhill extrae el control de versiones como un registro comportamental: un punto caliente es la intersección de la frecuencia de cambio y la complejidad, ninguno de los dos ejes por sí solo, limitado a una ventana fija, y es una probabilidad de inspeccionar, no un veredicto sobre el que actuar. Y la Regla de Conexión a Tierra (references/visual-rendering-core.md § 1) divide cada artefacto: los SHAs, números de PR, rutas, recuentos y fundamentos citados se copian de la salida de git y gh, nunca se vuelven a escribir; el modelo escribe solo la prosa que los rodea. Una decisión cuyo "por qué" es una suposición del modelo no se recupera, se inventa, y se queda fuera del repositorio.
Posición de Invocación
Ruta secundaria, invocada solo por el usuario. Se activa cuando un humano escribe /excavate; disable-model-invocation: true mantiene su descripción fuera del contexto de cualquier otra sesión, porque se escribe una vez por repositorio, durante la incorporación, y nunca tiene motivos para autoactivarse. Modo de planificación, humano en el circuito en todo momento; no hay ruta AFK, porque cada elemento vinculado a una confirmación necesita que un humano lea la cita en la que se basa.
Enviarlo como su propio mensaje, no como el primer mensaje de un espacio de trabajo nuevo. Una habilidad oculta escrita en un primer mensaje al que un anfitrión añade un preámbulo se lee como prosa y nunca se carga; la Posición de Invocación de /lfg registra el incidente de campo (chrislacey89/skills#383). Vuelve a escribirla como segundo mensaje.
El Paso 2 de /help indica la señal para comenzar con ella. No la uses en un repositorio que el pipeline ya compile (ejecuta /compound allí), como una revisión de rendimiento de nadie (ver § 5), o como un buscador de defectos (/qa y /triage-issue son los dueños de eso).
Se sitúa junto al pipeline principal y se reconecta en /shape: la primera característica se diseña con el expediente y las entradas en mano. Puede en su lugar pasar a /request-refactor-plan cuando el punto caliente principal es el trabajo, o a /qa cuando el historial muestra un clúster etiquetado como "fix" que nunca se cerró.
Reglas del método
Estas son las reglas que el proceso siguiente operacionaliza. Cada una señala de dónde proviene; ninguna se repite en ningún otro lugar de este archivo.
- Clasificar por revisiones, calificar por líneas de código, nunca un solo eje. La frecuencia sin tamaño señala archivos de configuración; el tamaño sin frecuencia señala código que se depuró hasta corregirlo hace años y no se ha tocado desde entonces (Tornhill, Caps. 1-4). LOC mediante
wc -les el proxy de complejidad: no se asumecloc, y LOC es el proxy que el libro usa por defecto. - Limitar a una ventana fija. Un recuento de revisiones en bruto no tiene sentido sin un período de referencia. Doce meses es el valor por defecto; redúcelo para repositorios de alta velocidad. Imprime la ventana real.
- Esperar una ley de potencia y cortar en el codo. Un puñado de archivos contiene la mayor parte del coste. Detente donde la curva se nivela, nunca en un número redondo.
- Una tendencia necesita complejidad y tamaño. Deterioro significa complejidad de sangría que aumenta más rápido que LOC; Refactorizado significa que ambos caen; Estable significa que ambos están planos (Tornhill, Caps. 5-6). LOC por sí solo no puede producir estas etiquetas.
- Filtrar archivos no del dominio antes del acoplamiento; clasificar cada acoplamiento antes de informarlo. Los archivos de bloqueo, manifiestos, código generado y dependencias son falsos positivos. Una prueba acoplada a su sujeto es intencional y se elimina; los acoplamientos de copiar-pegar, límite no soportado y productor-consumidor se mantienen y etiquetan (Tornhill, Caps. 8-9). El historial aplastado destruye el acoplamiento a nivel de confirmación; usa conjuntos de cambios a nivel de PR o di que la sección no está disponible.
- Los puntos calientes son probabilidades. Abre el archivo antes de nombrar un mecanismo, y no ofrezcas ninguna entrada para un archivo que no abriste.
- Los datos de propiedad tienen cuatro usos legítimos: encontrar un revisor, incorporación, decidir dónde van las pruebas y juzgar si una estructura se ajusta al equipo. Nunca se usan para clasificar o evaluar personas, y se leen a través de
.mailmapcon bots y la importación inicial excluidas (Tornhill, Caps. 12-14). - La documentación existente es aspiracional. Una afirmación de ADR, README o CHANGELOG se verifica contra el árbol actual antes de registrarse como una decisión vigente (Spinellis, Code Reading).
- El primer movimiento en un punto caliente es una prueba de caracterización en un punto de presión, no una refactorización (Feathers, Working Effectively with Legacy Code). La sección "Movimientos sugeridos primero" del expediente dice esto por punto caliente.
- Un punto caliente se selecciona por ser extremo, por lo que parte de su rango es ruido. La regresión a la media es la hipótesis nula: un archivo que encabeza una ventana puede ser promedio en la siguiente sin que se le haga nada, y un archivo que se enfrió después de una refactorización se habría enfriado de todos modos (Galton; Kahneman). Clasifica por persistencia en ambas mitades de la ventana, etiqueta cada punto caliente como
persistentosingle-window, y dirige solo los persistentes a la lista de primeros movimientos. La clasificación es un diagnóstico, nunca un objetivo; un equipo que establece metas sobre ella obtiene manipulación, no calidad (Goodhart). - Registra la suposición junto a la decisión. La justificación se olvida más rápido, y las suposiciones no registradas se endurecen en números que nadie se atreve a cambiar (Leveson, Engineering a Safer World, Cap. 10). Cada decisión recuperada lleva la restricción que su cita indica ("se eligió X porque Y, asumiendo Z"), y si Z aún se mantiene en el árbol es el detector de reversión.
- Una entrada de decisión tiene la forma de un ADR y una línea de cumplimiento. Título, estado, contexto, la decisión con su por qué, consecuencias y cómo se verifica; sin esto último, un registro es arqueología en lugar de gobierno (Richards & Ford, Fundamentals of Software Architecture, Cap. 19). La verificación aquí es la búsqueda grep o verificación de ruta contra el árbol actual.
- El expediente es leído por un recién llegado bajo la maldición de la experiencia. Comienza con un glosario corto y los puntos calientes del codo, mantén las tablas completas detrás de ellos, y haz que la primera tarea de bienvenida sea entender y resumir la decisión recuperada de un punto caliente en lugar de arreglarlo (Hermans, The Programmer's Brain).
Proceso
Cada bloque delimitado a continuación se ejecuta tal como está escrito. Cada llamada a la herramienta Bash es un shell nuevo, por lo que ninguna variable sobrevive de una llamada a la siguiente. El bloque de variables del Paso 0 se ejecuta una vez; el bloque después de él escribe cada valor resuelto en $WORK/vars.sh, y cada shell posterior, ya sea tuyo o de un minero, comienza con . "$WORK/vars.sh" en lugar de volver a ejecutar el Paso 0, lo que reiniciaría SINCE a su valor predeterminado y descartaría una extensión --since o NONDOMAIN. Un shell que se salta el archivo falla sin error: git log --since="" no devuelve confirmaciones y grep -Ev "" no imprime nada, y una salida vacía se lee como la respuesta válida "sin reversiones". WORK es una ruta derivada del repositorio, por lo que un nuevo shell la vuelve a derivar con la expresión WORK= del Paso 0 y obtiene el archivo debajo de ella; a los mineros se les pasa la ruta absoluta a vars.sh en sus instrucciones en lugar de heredar cualquier variable. <path> es el único marcador de posición, sustituido por punto caliente.
La semántica de las banderas es la documentación de la propia herramienta
(git-log,
git-shortlog,
git-rev-parse,
gh pr list); este archivo no las parafrasea. scripts/test-documented-git-commands.sh ejecuta cada bloque de git contra un fixture plantado, por lo que un bloque que deja de ser una invocación válida falla en CI.
0. Verificación previa
Analiza los argumentos: --since <fecha> (por defecto 12 months ago; cualquier forma que git log --since acepte) y --paths <pathspec>... (reemplaza el . en cada bloque a continuación). Luego establece las variables compartidas:
SINCE='12 months ago'
WORK="${TMPDIR:-/tmp}/excavate-$(git rev-parse --show-toplevel | cksum | cut -d' ' -f1)"
mkdir -p -m 700 "$WORK"
START=$(git log --since="$SINCE" --no-merges --format=%cs HEAD </dev/null | tail -1)
NONDOMAIN=(':(exclude)*.lock' ':(exclude)*package-lock.json' ':(exclude)*pnpm-lock.yaml' ':(exclude)*yarn.lock' ':(exclude)*.min.*' ':(exclude)*.snap' ':(exclude)*.generated.*' ':(exclude)CHANGELOG.md' ':(exclude)*/vendor/*' ':(exclude)*/node_modules/*')
BOTS='[[]bot[]]|dependabot|renovate'
MAXFILES=30
export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.quotePath GIT_CONFIG_VALUE_0=false
La exportación GIT_CONFIG_* establece core.quotePath=false para cada llamada de git en esta ejecución (ver git-config, Environment), por lo que una ruta con bytes no ASCII se imprime como ella misma en lugar de como una secuencia de escape entrecomillada que wc y las uniones a continuación no pueden abrir ni coincidir.
START es la primera fecha de confirmación dentro de la ventana, derivada de git en lugar de date, porque date -d es solo GNU y date -v es solo BSD. NONDOMAIN es una lista inicial: amplíala con las rutas generadas, dependencias, instantáneas y reflejadas de este repositorio antes del Paso 1 (verifica git check-attr linguist-generated linguist-vendored en cualquier cosa sospechosa). MAXFILES es el límite superior de tamaño de conjunto de cambios por encima del cual una confirmación se trata como un barrido masivo (reformato, importación, cambio de nombre masivo) y se excluye de los recuentos de frecuencia y acoplamiento. El registro compartido en el Paso 2 lo aplica una vez, cuando se construye el registro; ningún bloque posterior lo repite.
WORK está vinculado a la ruta de nivel superior del repositorio, no a HEAD, porque el Paso 8 mueve HEAD a la rama de documentos; permanece válida entre shells y se elimina en el Paso 9. Si ya hay un directorio de una ejecución interrumpida, elimínalo antes del primer bloque del Paso 2 para que los archivos obsoletos no puedan pasar por la salida de esta ejecución.
Persiste los valores resueltos una vez que NONDOMAIN se amplía (y de nuevo después de la puerta del Paso 1, si el usuario cambia la ventana: obtén el archivo, establece SINCE, vuelve a ejecutar la línea START= del Paso 0, luego ejecuta este bloque de nuevo):
{
declare -p SINCE START NONDOMAIN BOTS MAXFILES WORK
declare -px GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0
} > "$WORK/vars.sh"
Luego mide la cobertura, en un shell que comienza con . "$WORK/vars.sh":
git rev-parse --is-shallow-repository
git rev-list --count --since="$SINCE" --no-merges HEAD
git log --max-parents=0 --format=%cs HEAD </dev/null | sort | head -1
git log --since="$SINCE" --no-merges --format=%s HEAD </dev/null | awk '{ t++ } /\(#[0-9]+\)$/ { s++ } END { printf "%d of %d subjects end in (#N)\n", s, t }'
Las cuatro líneas son: superficial o no, confirmaciones en la ventana, fecha de la confirmación raíz (el límite superficial si es superficial, entonces di "desconocido") y la proporción de squash. Una proporción alta de squash significa que cada confirmación es un PR completo, por lo que el acoplamiento a nivel de confirmación ya es acoplamiento a nivel de PR; una proporción baja con PRs fusionados con squash en otro lugar es el caso a observar.
Luego cuenta las confirmaciones que aterrizan un cambio ya en la ventana, porque un repositorio que se envía a través de ramas de lanzamiento lleva el mismo cambio como original, cherry-pick y resumen de lanzamiento o sincronización, y cada recuento a continuación ve cada copia como una revisión separada:
git log --since="$SINCE" --no-merges --reverse -p --format='commit %H' HEAD -- . "${NONDOMAIN[@]}" </dev/null \
| git patch-id --stable | awk 'seen[$(1)]++ { print $(2) }' > "$WORK/dups.txt"
wc -l < "$WORK/dups.txt" | tr -d ' '
dups.txt enumera cada confirmación cuyo ID de parche
(git-patch-id) ya tenía una confirmación anterior dentro de la ventana, por lo que la copia más antigua es la que queda fuera de la lista. Lee la misma ventana y especificación de ruta que el registro compartido en el Paso 2, por lo que cuenta copias entre las confirmaciones que la clasificación lee; una rama fusionada cuyos cambios ya están en la línea principal es omitida por la simplificación de historial de git log en ambos. El recuento va en el bloque de Cobertura, y el Paso 3 reclasifica el codo sin estas confirmaciones. Se cuentan, no se excluyen: un cambio re-aplicado sigue siendo trabajo que alguien hizo, un resumen puede ser el único registro de los cambios que contiene, y cada bloque posterior lee el historial a través de su propio git log o git rev-list, por lo que un filtro aplicado solo al registro compartido dejaría los pasos en desacuerdo. Nombra cualquier resumen de lanzamiento o sincronización que veas en los asuntos en la puerta de alcance; tampoco se excluyen.
Prueba gh auth status y si el remoto es GitHub;
registra gh: yes o gh: no. Resuelve el directorio del expediente con
bash "$SC" path research, donde SC es la ruta absoluta instalada del resolvedor empaquetado de esta habilidad, references/sidecar.sh; nunca calcules una ruta de sidecar tú mismo. En SIDECAR MISS, el resolvedor sale con 1 e imprime un comando init: muéstralo al usuario y continúa una vez que lo hayan ejecutado, porque crear un sidecar es un acto humano. Busca en ese directorio un excavation-*.md anterior, y un docs/solutions/ existente; una re-ejecución actualiza las entradas en lugar de duplicarlas, y puntúa las etiquetas persistent del expediente anterior contra la nueva ventana (cuántas siguieron siendo puntos calientes) para que el bloque de Cobertura lleve una tasa de aciertos para sus propias predicciones.
Si el repositorio es superficial, di cuánto historial falta y ofrece
git fetch --unshallow; nunca lo ejecutes, ya que reescribe .git.
1. Puerta de alcance (humano en el circuito)
Imprime el bloque de Cobertura exactamente como encabezará el expediente:
Window: <START> → today (<SINCE>)
Commits: <N> non-merge (<M> excluded as bulk > MAXFILES files)
Shallow: yes | no
Squash ratio: <s>/<t>
Duplicates: <d> commits repeat an earlier commit's patch ID (counted, not excluded)
gh: yes (<fetched> of <total> merged PRs; <closed> closed unmerged) | no
Fix labels: <k> of <N> commits match the fix pattern (a lower bound; filled in at Step 3)
Excluded: <NONDOMAIN list, one line>
Not visible: closed-unmerged PRs, force-pushed or squashed history, deleted files, departed contributors
La línea Not visible no es un texto repetitivo. Todo lo que sigue clasifica...