Que fait Excavate ?
Récupérez ce qu'un dépôt legacy sait déjà de lui-même avant que la première fonctionnalité ne s'y lance à l'aveugle. Le résultat est un dossier hors-arborescence (points chauds, couplages, décisions récupérées, une carte des personnes à contacter et un bloc Couverture indiquant ce que l'historique a pu ou n'a pas pu vous dire) plus une PR optionnelle de saisies docs/solutions/ au format de /compound, afin que /research, /write-a-prd et /execute les lisent via le grep de mots-clés qu'ils exécutent déjà.
Deux choses assurent l'honnêteté. La méthode de Tornhill exploite le contrôle de version comme un enregistrement comportemental : un point chaud est l'intersection de la fréquence de modification et de la complexité, pas un seul axe, limité à une fenêtre fixe, et c'est une probabilité à inspecter, pas un verdict à appliquer. Et la Règle de Fondement (references/visual-rendering-core.md § 1) divise chaque artefact : les SHA, numéros de PR, chemins, comptages et justifications citées sont copiés depuis les sorties de git et gh, jamais retapés ; le modèle écrit uniquement la prose qui les entoure. Une décision dont le « pourquoi » est une supposition du modèle n'est pas récupérée, elle est inventée, et elle reste en dehors du dépôt.
Position d'invocation
Routage secondaire, invoqué uniquement par l'utilisateur. Il se déclenche lorsqu'un humain tape /excavate ; disable-model-invocation: true maintient sa description hors du contexte de toute autre session, car il est tapé une fois par dépôt, lors de l'intégration, et n'a jamais de raison de s'auto-déclencher. Mode planification, HITL tout au long ; il n'y a pas de chemin AFK, car chaque élément lié à un commit nécessite qu'un humain lise la citation sur laquelle il repose.
Envoyez-le comme son propre message, pas comme le premier message d'un espace de travail frais. Une compétence cachée tapée dans un premier message auquel un hôte ajoute un préambule est lue comme de la prose et ne se charge jamais ; la Position d'invocation de /lfg documente l'incident de terrain (chrislacey89/skills#383). Retapez-la comme deuxième message.
L'étape 2 de /help indique le signal pour commencer avec. Ne l'utilisez pas sur un dépôt que le pipeline compose déjà (exécutez /compound à la place), comme évaluation des performances de quiconque (voir § 5), ou comme chercheur de défauts (/qa et /triage-issue s'en chargent).
Il se tient à côté du pipeline principal et se reconnecte à /shape : la première fonctionnalité est façonnée avec le dossier et les saisies en main. Il peut également passer la main à /request-refactor-plan lorsque le point chaud principal est le travail à faire, ou à /qa lorsque l'historique montre un cluster étiqueté « fix » qui ne s'est jamais fermé.
Règles méthodologiques
Ce sont les règles que le processus ci-dessous met en œuvre. Chacune pointe vers son origine ; aucune n'est répétée ailleurs dans ce fichier.
- Classer par révisions, qualifier par lignes de code, jamais un seul axe.
La fréquence sans la taille signale les fichiers de configuration ; la taille sans la fréquence signale le code qui a été débogué pour être correct il y a des années et jamais retouché depuis
(Tornhill, Ch. 1-4). Le LOC via
wc -lest le proxy de complexité : aucunclocn'est supposé, et le LOC est le proxy par défaut du livre. - Limiter à une fenêtre fixe. Un nombre brut de révisions est dénué de sens sans période de référence. Douze mois est la valeur par défaut ; réduisez-la pour les dépôts à haute vélocité. Affichez la fenêtre réelle.
- Attendre une loi de puissance et couper au coude. Une poignée de fichiers concentrent la plupart des coûts. Arrêtez là où la courbe s'aplatit, jamais à un nombre rond.
- Une tendance a besoin de complexité et de taille. Détérioration signifie complexité d'indentation qui augmente plus vite que le LOC ; Refactoré signifie que les deux baissent ; Stable signifie que les deux sont plats (Tornhill, Ch. 5-6). Le LOC seul ne peut pas produire ces étiquettes.
- Filtrer les fichiers non-domaine avant le couplage ; classer chaque couplage avant de le signaler. Les fichiers de verrouillage, manifestes, code généré et vendu sont des faux positifs. Un test couplé à son sujet est intentionnel et est supprimé ; les couplages copier-coller, de frontière non-supportive, et producteur-consommateur sont conservés et étiquetés (Tornhill, Ch. 8-9). L'historique écrasé détruit le couplage au niveau du commit ; utilisez les ensembles de modifications au niveau PR ou dites que la section est indisponible.
- Les points chauds sont des probabilités. Ouvrez le fichier avant de nommer un mécanisme, et ne proposez pas d'entrée pour un fichier que vous n'avez pas ouvert.
- Les données de propriété ont quatre utilisations légitimes : trouver un relecteur, intégration, décider où vont les tests, et juger si une structure correspond à l'équipe. Elles ne sont jamais utilisées pour classer ou évaluer les personnes, et elles sont lues via
.mailmapavec les bots et l'import initial exclus (Tornhill, Ch. 12-14). - La documentation existante est aspirationnelle. Une affirmation d'ADR, README ou CHANGELOG est vérifiée par rapport à l'arborescence actuelle avant d'être enregistrée comme décision en vigueur (Spinellis, Code Reading).
- La première action sur un point chaud est un test de caractérisation à un point de pincement, pas une refactorisation (Feathers, Working Effectively with Legacy Code). La section « Premières actions suggérées » du dossier le dit par point chaud.
- Un point chaud est sélectionné parce qu'il est extrême, donc une partie de son classement est du bruit.
La régression vers la moyenne est l'hypothèse nulle : un fichier qui domine une fenêtre
peut être moyen dans la suivante sans qu'on y touche, et un fichier qui s'est refroidi
après une refactorisation aurait refroidi de toute façon (Galton ; Kahneman). Classez sur
la persistance à travers les deux moitiés de la fenêtre, étiquetez chaque point chaud
persistentousingle-window, et acheminez uniquement les persistants vers la liste des premières actions. Le classement est un diagnostic, jamais un objectif ; une équipe qui se fixe des objectifs dessus obtient du jeu, pas de la qualité (Goodhart). - Enregistrez l'hypothèse à côté de la décision. La justification s'oublie le plus vite, et les hypothèses non enregistrées durcissent en chiffres que personne n'ose modifier (Leveson, Engineering a Safer World, Ch. 10). Chaque décision récupérée porte la contrainte que sa citation énonce (« a choisi X parce que Y, a supposé Z »), et savoir si Z tient toujours dans l'arbre est le détecteur d'inversion.
- Une entrée de décision a la forme d'un ADR et une ligne de conformité. Titre, statut, contexte, la décision avec son pourquoi, conséquences, et comment elle est vérifiée ; sans ce dernier, un enregistrement est de l'archéologie plutôt que de la gouvernance (Richards & Ford, Fundamentals of Software Architecture, Ch. 19). La vérification ici est le grep ou la vérification de chemin effectuée sur l'arborescence actuelle.
- Le dossier est lu par un nouveau venu sous la malédiction de l'expertise. Commencez par un court glossaire et les points chauds du coude, conservez les tableaux complets derrière eux, et faites de la première tâche de bienvenue comprendre et résumer une décision récupérée d'un point chaud plutôt que de la réparer (Hermans, The Programmer's Brain).
Processus
Chaque bloc délimité ci-dessous s'exécute tel qu'écrit. Chaque appel d'outil Bash est un shell frais, donc aucune variable ne survit d'un appel au suivant. Le bloc de variables de l'Étape 0 s'exécute une fois ; le bloc après lui écrit chaque valeur résolue dans $WORK/vars.sh, et chaque shell ultérieur, le vôtre ou celui d'un mineur, commence par . "$WORK/vars.sh" au lieu de ré-exécuter l'Étape 0, ce qui réinitialiserait SINCE à sa valeur par défaut et ignorerait une extension --since ou NONDOMAIN. Un shell qui saute le fichier échoue sans erreur : git log --since="" ne renvoie aucun commit et grep -Ev "" n'affiche rien, et une sortie vide se lit comme la réponse valide « pas de retours ». WORK est un chemin dérivé du dépôt, donc un nouveau shell le redérive avec l'expression WORK= de l'Étape 0 et source le fichier sous celui-ci ; les mineurs reçoivent le chemin absolu vers vars.sh dans leur briefing plutôt que d'hériter d'une variable. <path> est le seul espace réservé,
substitué par point chaud.
La sémantique des drapeaux est la documentation de l'outil lui-même
(git-log,
git-shortlog,
git-rev-parse,
gh pr list) ; ce fichier ne
les paraphrase pas. scripts/test-documented-git-commands.sh exécute chaque bloc git
contre un fixture planté, donc un bloc qui cesse d'être une invocation valide échoue
en CI.
0. Pré-vérification
Analysez les arguments : --since <date> (par défaut 12 months ago ; toute forme
que git log --since accepte) et --paths <spéc de chemin>... (remplace le . dans
chaque bloc ci-dessous). Définissez ensuite les variables partagées :
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
L'export GIT_CONFIG_* définit core.quotePath=false pour chaque appel git dans cette exécution (voir git-config, Environnement), afin qu'un chemin avec des octets non-ASCII s'affiche tel quel plutôt que sous forme d'échappement entre guillemets que wc et les jointures ci-dessous ne peuvent pas ouvrir ou faire correspondre.
START est la première date de commit dans la fenêtre, dérivée de git plutôt que de
date, car date -d est spécifique à GNU et date -v est spécifique à BSD. NONDOMAIN
est une liste de départ : étendez-la avec les chemins générés, vendus, instantanés et miroirs de ce dépôt avant l'Étape 1 (vérifiez git check-attr linguist-generated linguist-vendored sur tout élément suspect). MAXFILES est le plafond de taille d'ensemble de modifications au-dessus duquel un commit est traité comme un balayage en vrac (reformatage, import, renommage en masse) et exclu des comptages de fréquence et de couplage. Le journal partagé à l'Étape 2 l'applique une fois, lors de la construction du journal ; aucun bloc ultérieur ne le répète.
WORK est indexé sur le chemin de niveau supérieur du dépôt, pas sur HEAD, car l'Étape 8
déplace HEAD sur la branche de documentation ; il reste valide d'un shell à l'autre et est supprimé à l'Étape 9. Si un répertoire d'une exécution interrompue existe déjà, supprimez-le avant le premier bloc de l'Étape 2 afin que les fichiers obsolètes ne puissent pas passer pour la sortie de cette exécution.
Persistez les valeurs résolues une fois que NONDOMAIN est étendu (et à nouveau après la
porte de l'Étape 1, si l'utilisateur change la fenêtre : sourcez le fichier, définissez SINCE,
réeexécutez la ligne START= de l'Étape 0, puis exécutez à nouveau ce bloc) :
{
declare -p SINCE START NONDOMAIN BOTS MAXFILES WORK
declare -px GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0
} > "$WORK/vars.sh"
Mesurez ensuite la couverture, dans un shell qui commence par . "$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 }'
Les quatre lignes sont : peu profond ou non, commits dans la fenêtre, date du commit racine (la limite peu profonde si peu profond, donc dites « inconnu » alors), et le taux de squash. Un taux de squash élevé signifie que chaque commit est une PR entière, donc le couplage au niveau commit est déjà un couplage au niveau PR ; un taux faible avec des PR squash-merged ailleurs est le cas à surveiller.
Comptez ensuite les commits qui posent une modification déjà dans la fenêtre, car un dépôt qui livre via des branches de release porte la même modification en tant qu'originale, chery-pick et rollup de release ou de synchronisation, et chaque comptage ci-dessous voit chaque copie comme une révision distincte :
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 liste chaque commit dont l'ID de patch
(git-patch-id) un commit antérieur dans la fenêtre
avait déjà, donc la copie la plus ancienne est celle laissée hors de la liste. Il lit
la même fenêtre et le même spécificateur de chemin que le journal partagé à l'Étape 2, il compte donc les copies parmi les commits que le classement lit ; une branche fusionnée dont les modifications sont déjà sur la ligne principale est ignorée par la simplification d'historique de git log dans les deux. Le comptage va dans le bloc Couverture, et l'Étape 3 re-classe le coude sans ces commits.
Ils sont comptés, pas exclus : une modification replacée est toujours un travail que quelqu'un
A fait, un rollup peut être le seul enregistrement des modifications qu'il transporte, et
chaque bloc ultérieur lit l'historique via son propre git log ou git rev-list, donc
un filtre appliqué au seul journal partagé laisserait les étapes en désaccord. Nommez tout rollup de release ou de synchronisation que vous voyez dans les sujets à la porte de portée ; ils ne sont pas exclus non plus.
Sondez gh auth status et si le remote est GitHub ;
enregistrez gh: yes ou gh: no. Résolvez le répertoire du dossier avec
bash "$SC" path research, où SC est le chemin absolu installé du résolveur
fourni avec cette compétence, references/sidecar.sh ; ne
calculez jamais vous-même un chemin sidecar. Sur SIDECAR MISS, le résolveur se termine par 1 et
affiche une commande init : montrez-la à l'utilisateur et continuez une fois qu'il l'a exécutée,
car la création d'un sidecar est un acte humain. Cherchez dans ce répertoire un
excavation-*.md antérieur, et un docs/solutions/ existant ; une réexécution met à jour
les saisies plutôt que de les dupliquer, et note les étiquettes persistent du dossier
précédent par rapport à la nouvelle fenêtre (combien sont restées chaudes) afin que le bloc Couverture
porte un taux de réussite pour ses propres prédictions.
Si le dépôt est peu profond, dites combien d'historique manque et proposez
git fetch --unshallow ; ne l'exécutez jamais, car cela réécrit .git.
1. Porte de portée (HITL)
Affichez le bloc Couverture exactement comme il sera en tête du dossier :
Fenêtre : <START> → aujourd'hui (<SINCE>)
Commits : <N> non-fusionnés (<M> exclus comme bulk > MAXFILES fichiers)
Peu profond : oui | non
Taux de squash : <s>/<t>
Doublons : <d> commits répètent l'ID de patch d'un commit antérieur (comptés, pas exclus)
gh : oui (<récupéré> de <total> PR fusionnées ; <fermé> fermées non fusionnées) | non
Étiquettes fix : <k> de <N> commits correspondent au modèle de correctif (une limite inférieure ; rempli à l'Étape 3)
Exclus : <liste NONDOMAIN, une ligne>
Non visible : PR fermées non fusionnées, historique force-pushé ou écrasé, fichiers supprimés, contributeurs partis
La ligne Non visible n'est pas un passe-partout. Tout ce qui suit classe s
…
[suite tronquée pour la traduction]