Pular para o conteúdo

code-metrics

code-metrics te diz o que sua mudança fez com os números — estrutura, dívida, legibilidade, decay, risco de carga — no único momento em que você ainda pode agir barato: antes de abrir o PR.

O que resolve

As métricas de qualidade regridem em silêncio, e o autor é o último a notar. Uma função passa de um teto de complexidade, a cobertura cai uns pontos, surge um ciclo de import novo — a review raramente pega qualquer um deles, e um dashboard só mostra a tendência muito depois da mudança ter ido. O feedback chega tarde demais e longe demais de quem o causou.

Esta skill fecha essa lacuna sendo duas coisas ao mesmo tempo: imediata (uma leitura local no seu branch, sem ida-e-volta de CI) e quase de graça (os números são saída pura de tooling, então rodar custa praticamente zero tokens). Ela só gasta um modelo quando algo está de fato errado.

Como funciona

A skill é híbrida — medição determinística, assistida por modelo só na quebra.

Os números são produzidos por um adapter por-stack que roda o tooling real da sua linguagem e normaliza a saída para um contrato comum. O adapter é selecionado automaticamente a partir do stack detectado no repo, então o mesmo comando e o mesmo conjunto de métricas funcionam entre linguagens. Cada métrica é declarada num registry único (direção + onde fica seu threshold), então o catálogo cresce por uma linha mais uma função de adapter — nunca um novo ramo no orquestrador.

O que mede

O catálogo é em camadas, pela pergunta que responde e pelo custo de respondê-la:

  • Estruturacoverage, complexity, module_size, dependency_cycles.
  • Dívida & legibilidade — contagens de TODO/FIXME, @deprecated/[Obsolete], código morto marcado e supressões de linter/compilador; mais nesting_depth, param_count médio, magic_numbers, lint_density e doc_coverage. São os sinais baratos e objetivos que o time não tem como contestar — o ponto de medir legibilidade com contadores em vez de uma nota subjetiva.
  • Decayhotspots: os arquivos que mudam muito e são complexos (churn × complexidade ciclomática), onde o risco de fato se concentra ao longo do tempo.
  • Risco de cargaperf_risk: um proxy estático de queries/alocações dentro de loops e loops aninhados. É heurístico e de alto falso-positivo por natureza, então é reportado como info — exposto, nunca como gate.

Toda métrica é uma heurística determinística de shell (grep / awk / lizard / git) e ratchet-only por padrão, então um repo legado nunca nasce vermelho: só uma regressão nova no seu branch é sinalizada.

Referência completa das métricas

maior é melhor · menor é melhor · info reportado, nunca vira gate. “Padrão” é o comportamento sem config no .octopus.yml; a chave entre parênteses é o threshold absoluto opcional que você pode definir sob code_metrics:.

MétricaCamadaDirO que medePadrão
coverageestrutura% de cobertura de linha (TS: vitest→LCOV · C#: dotnet-coverage/coverlet→Cobertura)ratchet (coverage.min)
complexityestruturaComplexidade ciclomática média por função (lizard)ratchet (complexity.max)
module_sizeestruturaNLOC médio por função/arquivo (lizard)ratchet (module_size.max)
dependency_cyclesestruturaCiclos de import (TS: madge) / ciclos de project-reference (C#: dotnet list reference + Tarjan)ratchet (dependencies.cycles_allowed)
todo_markersdívidaContagem de TODO/FIXME/HACK/XXXratchet
deprecationsdívida@deprecated (TS) / [Obsolete] (C#)ratchet
dead_codedívidaCódigo morto marcado (#if false, // dead, unused-disable) — não por alcanceratchet
suppressionsdívidaeslint-disable/@ts-ignore/@ts-nocheck (TS) · #pragma warning disable/[SuppressMessage] (C#)ratchet (suppressions.max)
nesting_depthlegibilidadeNível máximo de aninhamento de chavesratchet (nesting_depth.max)
param_countlegibilidadeMédia de parâmetros por função (lizard)ratchet (param_count.max)
magic_numberslegibilidadeLiterais numéricos, excluindo 0/1/-1, constantes nomeadas e stringsratchet
lint_densitylegibilidadeFindings do linter por 1000 NLOC (TS: eslint · C#: warnings de build); 0 se não houver linterratchet
doc_coveragedocsSímbolos públicos/exportados documentados ÷ totalratchet (doc_coverage.min)
hotspotsdecayNº de arquivos que são ao mesmo tempo alto-churn e alta-complexidade — churn do git log × CCN do lizard. Ajuste via hotspots.{window_days,churn_min,ccn_min}ratchet (hotspots.max)
perf_riskrisco de cargainfoQueries/alocações dentro de loops + loops aninhados (candidatos a O(n²))só-info (nunca vira gate)

Caveats heurísticos (todas as métricas são shell-grade, aproximadas por design):

  • perf_risk se baseia num opener de loop com a { na mesma linha; bases com chaves Allman (C# idiomático, onde a { fica na própria linha) sub-detectam e frequentemente leem 0. É só-info justamente por isso.
  • magic_numbers, nesting_depth e doc_coverage varrem a árvore de fontes e não excluem código gerado (ex.: migrations do EF), então podem ler alto — são âncoras de ratchet, não notas absolutas de qualidade.

Ele reporta um delta duplo por métrica:

  • vs. baseline — seu branch contra o baseline do último merge na main (a âncora de tendência).
  • vs. main — seu branch contra seu main HEAD local (o impacto isolado deste PR).

O baseline é lido (git fetch) de um ref dedicado e não-protegido (octopus/code-metrics) que uma Action de push-na-main mantém fresco; o caminho de leitura nunca o escreve.

Os thresholds são ratchet por padrão — uma métrica não pode regredir contra o baseline — com alvos absolutos opcionais somados no .octopus.yml (coverage.min, complexity.max, suppressions.max, nesting_depth.max, hotspots.max, etc.; perf_risk não tem). Um campo sem alvo configurado continua no ratchet. Só quando uma métrica com gate cruza seu threshold é que a skill aciona um modelo de baixo custo para ler a mudança ofensora, explicar por que o número se moveu e sugerir um fix. Sem quebra, sem chamada de modelo — o contrato de custo que faz valer a pena rodar em todo PR.

Uso & parâmetros

octopus code-metrics [--stack <id>] [--metric <name>] [--verbose] [--emit-baseline]
FlagO que fazPadrão
--stack <id>Força um adapter de stack (csharp, typescript) em vez de auto-detectar.auto-detectado
--metric <name>Reporta uma única métrica em vez do catálogo todo.todas
--verboseMostra a saída crua do tooling junto do resumo.off
--emit-baselineImprime o baseline.json plano desta rodada (usado pela writer-Action).off

A rodada numérica é sempre sem modelo; o passo de curadoria dispara apenas numa quebra de threshold, no modelo de baixo custo do harness.