O que excavate faz?
Recupere o que um repositório legado já sabe sobre si mesmo antes da primeira funcionalidade executar cega nele. A saída é um dossiê off-tree (hotspots, acoplamento, decisões recuperadas, um mapa de quem perguntar e um bloco de Cobertura dizendo o que o histórico pôde e não pôde lhe contar) mais um PR opcional com entradas docs/solutions/ no formato do /compound, para que /research, /write-a-prd e /execute os leiam através da busca por palavra-chave que eles já executam.
Duas coisas mantêm isso honesto. O método de Tornhill extrai o controle de versão como um registro comportamental: um hotspot é a interseção da frequência de mudança com a complexidade, nenhum eixo sozinho, limitado a uma janela fixa, e é uma probabilidade para inspecionar, não um veredicto para agir. E a Regra de Fundamentação (references/visual-rendering-core.md § 1) divide cada artefato: SHAs, números de PR, caminhos, contagens e justificativas citadas são copiadas da saída de git e gh, nunca redigitadas; o modelo escreve apenas a prosa ao redor delas. Uma decisão cujo "porquê" é um palpite do modelo não é recuperada, é inventada, e fica fora do repositório.
Posição de Invocação
Trilha lateral, invocada apenas pelo usuário. É acionada quando um humano digita /excavate; disable-model-invocation: true mantém sua descrição fora do contexto de qualquer outra sessão, porque é digitada uma vez por repositório, na integração, e nunca tem motivo para autoativação. Modo de planejamento, HITL (humano no loop) em toda parte; não há caminho AFK (longe do teclado), porque cada item vinculado a um commit precisa que um humano leia a citação na qual se apoia.
Envie-o como sua própria mensagem, não como a primeira mensagem de um espaço de trabalho novo. Uma habilidade oculta digitada em uma primeira mensagem à qual um host anexa um preâmbulo é lida como prosa e nunca carrega; a Posição de Invocação do /lfg registra o incidente de campo (chrislacey89/skills#383). Redigite-a como uma segunda mensagem.
O Passo 2 do /help declara o sinal para começar com ela. Não a use em um repositório que o pipeline já compõe (execute /compound lá), como uma avaliação de desempenho de alguém (veja § 5), ou como um localizador de defeitos (/qa e /triage-issue são responsáveis por isso).
Ela fica ao lado do pipeline principal e reconecta-se no /shape: a primeira funcionalidade é moldada com o dossiê e as entradas em mãos. Pode, em vez disso, passar para /request-refactor-plan quando o hotspot principal é o trabalho, ou para /qa quando o histórico mostra um cluster rotulado como "correção" que nunca foi fechado.
Regras do método
Estas são as regras que o processo abaixo operacionaliza. Cada uma aponta para sua origem; nenhuma é reafirmada em outro lugar neste arquivo.
- Classificar por revisões, qualificar por linhas de código, nunca um eixo sozinho. Frequência sem tamanho sinaliza arquivos de configuração; tamanho sem frequência sinaliza código que foi depurado até a correção anos atrás e nunca mais tocado desde então (Tornhill, Caps. 1-4). LOC via
wc -lé o proxy de complexidade: nenhumclocé assumido, e LOC é o proxy ao qual o livro recorre por padrão. - Limitar a uma janela fixa. Uma contagem bruta de revisões é sem sentido sem um período de referência. Doze meses é o padrão; encurte-o para repositórios de alta velocidade. Imprima a janela real.
- Esperar uma lei de potência e cortar no joelho. Um punhado de arquivos detém a maior parte do custo. Pare onde a curva se achata, nunca em um número redondo.
- Uma tendência precisa de complexidade e tamanho. Deteriorando significa complexidade de indentação subindo mais rápido que LOC; Refatorado significa ambos caindo; Estável significa ambos planos (Tornhill, Caps. 5-6). LOC sozinho não pode gerar esses rótulos.
- Filtrar arquivos não relacionados ao domínio antes do acoplamento; classificar cada acoplamento antes de relatá-lo. Arquivos de trava, manifestos, código gerado e empacotado são falsos positivos. Um teste acoplado ao seu assunto é intencional e é descartado; acoplamentos de copiar-e-colar, fronteira sem suporte e produtor-consumidor são mantidos e rotulados (Tornhill, Caps. 8-9). Histórico squashado destrói o acoplamento no nível de commit; use conjuntos de mudanças no nível de PR ou diga que a seção não está disponível.
- Hotspots são probabilidades. Abra o arquivo antes de nomear um mecanismo e não ofereça entrada para um arquivo que você não abriu.
- Os dados de propriedade têm quatro usos legítimos: encontrar um revisor, integração, decidir onde os testes vão e julgar se uma estrutura se encaixa na equipe. Nunca são usados para classificar ou avaliar pessoas, e são lidos através do
.mailmapcom bots e a importação inicial excluídos (Tornhill, Caps. 12-14). - A documentação existente é aspiracional. Uma alegação de ADR, README ou CHANGELOG é verificada contra a árvore atual antes de ser registrada como uma decisão vigente (Spinellis, Code Reading).
- O primeiro movimento em um hotspot é um teste de caracterização em um ponto de estrangulamento, não uma refatoração (Feathers, Working Effectively with Legacy Code). A seção "Movimentos iniciais sugeridos" do dossiê diz isso por hotspot.
- Um hotspot é selecionado por ser extremo, então parte de sua classificação é ruído. Regressão à média é a hipótese nula: um arquivo que lidera uma janela pode ser mediano na seguinte sem que nada seja feito a ele, e um arquivo que esfriou após uma refatoração teria esfriado de qualquer maneira (Galton; Kahneman). Classifique por persistência em ambas as metades da janela, marque cada hotspot como
persistentousingle-window, e encaminhe apenas os persistentes para a lista de primeiros movimentos. A classificação é um diagnóstico, nunca um alvo; uma equipe que define metas nela obtém manipulação, não qualidade (Goodhart). - Registre a suposição ao lado da decisão. A justificativa é esquecida mais rápido, e suposições não registradas endurecem em números que ninguém ousa mudar (Leveson, Engineering a Safer World, Cap. 10). Cada decisão recuperada carrega a restrição que sua citação declara ("escolheu X porque Y, assumiu Z"), e se Z ainda é válido na árvore é o detector de reversão.
- Uma entrada de decisão tem a forma de um ADR e uma linha de conformidade. Título, status, contexto, a decisão com seu porquê, consequências e como é verificada; sem o último, um registro é arqueologia em vez de governança (Richards & Ford, Fundamentals of Software Architecture, Cap. 19). A verificação aqui é a busca por grep ou verificação de caminho executada contra a árvore atual.
- O dossiê é lido por um recém-chegado sob a maldição da expertise. Comece com um breve glossário e os hotspots do joelho, mantenha as tabelas completas atrás deles e torne a primeira tarefa de boas-vindas entender e resumir a decisão recuperada de um hotspot em vez de corrigi-la (Hermans, The Programmer's Brain).
Processo
Cada bloco cercado abaixo é executado como escrito. Cada chamada de ferramenta Bash é um shell novo, então nenhuma variável sobrevive de uma chamada para a próxima. O bloco de variáveis do Passo 0 é executado uma vez; o bloco após ele escreve cada valor resolvido em $WORK/vars.sh, e todo shell posterior, seu ou de um minerador, começa com . "$WORK/vars.sh" em vez de reexecutar o Passo 0, o que redefiniria SINCE para seu padrão e descartaria uma extensão --since ou NONDOMAIN. Um shell que pula o arquivo falha sem erro: git log --since="" não retorna commits e grep -Ev "" não imprime nada, e a saída vazia é lida como a resposta válida "sem reversões". WORK é um caminho derivado do repositório, então um novo shell o rederiva com a expressão WORK= do Passo 0 e carrega o arquivo sob ele; os mineradores recebem o caminho absoluto para vars.sh em sua instrução em vez de herdar qualquer variável. <path> é o único espaço reservado, substituído por hotspot.
A semântica das flags é a documentação da própria ferramenta (git-log, git-shortlog, git-rev-parse, gh pr list); este arquivo não as parafraseia. scripts/test-documented-git-commands.sh executa cada bloco de git contra uma fixture plantada, então um bloco que deixa de ser uma invocação válida falha no CI.
0. Pré-voo
Analise os argumentos: --since <data> (padrão 12 months ago; qualquer forma que git log --since aceite) e --paths <pathspec>... (substitui o . em cada bloco abaixo). Em seguida, defina as variáveis compartilhadas:
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
A exportação GIT_CONFIG_* define core.quotePath=false para cada chamada de git nesta execução (veja git-config, Environment), para que um caminho com bytes não ASCII seja impresso como ele mesmo, em vez de como uma sequência de escape entre aspas que o wc e as junções abaixo não conseguem abrir ou corresponder.
START é a primeira data de commit na janela, derivada do git em vez de date, porque date -d é exclusivo do GNU e date -v é exclusivo do BSD. NONDOMAIN é uma lista inicial: estenda-a com os caminhos gerados, empacotados, snapshot e espelhados deste repositório antes do Passo 1 (verifique git check-attr linguist-generated linguist-vendored em qualquer coisa suspeita). MAXFILES é o teto do tamanho do conjunto de mudanças acima do qual um commit é tratado como uma varredura em massa (reformatação, importação, renomeação em massa) e excluído das contagens de frequência e acoplamento. O log compartilhado no Passo 2 o aplica uma vez, quando o log é construído; nenhum bloco posterior o repete.
WORK é chaveado no caminho do nível superior do repositório, não em HEAD, porque o Passo 8 move HEAD para o branch de documentação; permanece válido entre shells e é removido no Passo 9. Se um diretório de uma execução interrompida já estiver lá, exclua-o antes do primeiro bloco do Passo 2 para que arquivos antigos não possam passar pela saída desta execução.
Persista os valores resolvidos uma vez que NONDOMAIN seja estendido (e novamente após o portão do Passo 1, se o usuário alterar a janela: carregue o arquivo, defina SINCE, reexecute a linha START= do Passo 0, então execute este bloco novamente):
{
declare -p SINCE START NONDOMAIN BOTS MAXFILES WORK
declare -px GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0
} > "$WORK/vars.sh"
Em seguida, meça a cobertura, em um shell que comece com . "$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 }'
As quatro linhas são: raso ou não, commits na janela, data do commit raiz (o limite raso se for raso, então diga "desconhecido" então), e a proporção de squash. Uma proporção alta de squash significa que cada commit é um PR inteiro, então o acoplamento no nível de commit já é acoplamento no nível de PR; uma proporção baixa com PRs mesclados por squash em outro lugar é o caso a observar.
Em seguida, conte os commits que aplicam uma mudança já na janela, porque um repositório que envia através de branches de release carrega a mesma mudança como um original, uma cherry-pick e uma release ou roll-up de sincronização, e cada contagem abaixo vê cada cópia como uma revisão 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 lista cada commit cujo ID de patch git-patch-id um commit anterior na janela já possuía, então a cópia mais antiga é aquela que não está na lista. Ele lê a mesma janela e especificação de caminho que o log compartilhado no Passo 2, então conta cópias entre os commits que a classificação lê; um branch mesclado cujas mudanças já estão na linha principal é ignorado pela simplificação de histórico do git log em ambos. A contagem vai no bloco de Cobertura, e o Passo 3 reclassifica o joelho sem esses commits. Eles são contados, não excluídos: uma mudança reaplicada ainda é trabalho que alguém fez, um roll-up pode ser o único registro das mudanças que carrega, e cada bloco posterior lê o histórico através de seu próprio git log ou git rev-list, então um filtro aplicado apenas ao log compartilhado deixaria as etapas em desacordo. Nomeie quaisquer roll-ups de release ou sincronização que você vir nos assuntos no portão de escopo; eles também não são excluídos.
Teste gh auth status e se o remoto é GitHub; registre gh: yes ou gh: no. Resolva o diretório do dossiê com bash "$SC" path research, onde SC é o caminho absoluto instalado do resolvedor agrupado desta habilidade, references/sidecar.sh; nunca calcule um caminho de sidecar você mesmo. Em SIDECAR MISS, o resolvedor sai com código 1 e imprime um comando init: mostre-o ao usuário e continue depois que ele o executar, porque criar um sidecar é um ato humano. Procure nesse diretório por um excavation-*.md anterior e por um docs/solutions/ existente; uma reexecução atualiza as entradas em vez de duplicá-las, e pontua as tags persistent do dossiê anterior contra a nova janela (quantas permaneceram quentes) para que o bloco de Cobertura carregue uma taxa de acerto para suas próprias previsões.
Se o repositório for raso, diga quanto histórico está faltando e ofereça git fetch --unshallow; nunca o execute, pois ele reescreve .git.
1. Portão de escopo (HITL)
Imprima o bloco de Cobertura exatamente como ele encabeçará o dossiê:
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
A linha Not visible não é um modelo pronto. Tudo abaixo classifica s...