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:
- Estrutura —
coverage,complexity,module_size,dependency_cycles. - Dívida & legibilidade — contagens de
TODO/FIXME,@deprecated/[Obsolete], código morto marcado e supressões de linter/compilador; maisnesting_depth,param_countmédio,magic_numbers,lint_densityedoc_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. - Decay —
hotspots: 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 carga —
perf_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étrica | Camada | Dir | O que mede | Padrão |
|---|---|---|---|---|
coverage | estrutura | ↑ | % de cobertura de linha (TS: vitest→LCOV · C#: dotnet-coverage/coverlet→Cobertura) | ratchet (coverage.min) |
complexity | estrutura | ↓ | Complexidade ciclomática média por função (lizard) | ratchet (complexity.max) |
module_size | estrutura | ↓ | NLOC médio por função/arquivo (lizard) | ratchet (module_size.max) |
dependency_cycles | estrutura | ↓ | Ciclos de import (TS: madge) / ciclos de project-reference (C#: dotnet list reference + Tarjan) | ratchet (dependencies.cycles_allowed) |
todo_markers | dívida | ↓ | Contagem de TODO/FIXME/HACK/XXX | ratchet |
deprecations | dívida | ↓ | @deprecated (TS) / [Obsolete] (C#) | ratchet |
dead_code | dívida | ↓ | Código morto marcado (#if false, // dead, unused-disable) — não por alcance | ratchet |
suppressions | dívida | ↓ | eslint-disable/@ts-ignore/@ts-nocheck (TS) · #pragma warning disable/[SuppressMessage] (C#) | ratchet (suppressions.max) |
nesting_depth | legibilidade | ↓ | Nível máximo de aninhamento de chaves | ratchet (nesting_depth.max) |
param_count | legibilidade | ↓ | Média de parâmetros por função (lizard) | ratchet (param_count.max) |
magic_numbers | legibilidade | ↓ | Literais numéricos, excluindo 0/1/-1, constantes nomeadas e strings | ratchet |
lint_density | legibilidade | ↓ | Findings do linter por 1000 NLOC (TS: eslint · C#: warnings de build); 0 se não houver linter | ratchet |
doc_coverage | docs | ↑ | Símbolos públicos/exportados documentados ÷ total | ratchet (doc_coverage.min) |
hotspots | decay | ↓ | Nº 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_risk | risco de carga | info | Queries/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_riskse 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 leem0. É só-info justamente por isso.magic_numbers,nesting_depthedoc_coveragevarrem 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
mainHEAD 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]| Flag | O que faz | Padrã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 |
--verbose | Mostra a saída crua do tooling junto do resumo. | off |
--emit-baseline | Imprime 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.