Skip to main content

Activity Map

Contents

Info

# Para executar o script de qualquer diretório
~/dev/nextjs/qb-monorepo/scripts/activity-map.js --since=2025-01 --until=2026-02

Files

  • scripts/activity-map.py
  • scripts/activity-map.js

Requisitos

  • Gerar um script scripts/activity-map.py para gerar um "mapa de calor" de commits do diretório atual e subdiretórios.

Comandos Git:

git log --since="2026-02-01" --until="2026-03-01" -- .

# No script
git rev-parse --show-toplevel
git log --since=2025-01-01 --until=2027-01-01 --name-only --no-merges --pretty=format:--COMMIT--%n%H%n%cd --date=format:%Y-%m -- .
qb-monorepo/apps$ ../scripts/activity-map.py --tsv --zeros --since 2025-01-01 --until 2026-02-28
folder 2025-09 2025-10 2025-11 2025-12 2026-01 2026-02
0_arquivo/ 0 0 0 0 0 1
ai-rag-agent/ 5 0 0 24 0 0
android/ 0 10 0 0 0 0
arquivo/ 0 0 0 0 0 1
blog/ 0 8 0 0 0 0
bot/ 0 134 0 0 0 1
chat/ 180 0 0 0 0 0
docs/ 0 7 17 0 0 0
docusaurus/ 0 0 7 0 0 1
idp/ 0 0 106 0 0 0
infra/ 0 0 0 0 0 2
nx/ 0 0 0 110 0 0
playground/ 49 0 0 0 0 0
query-builder/ 16 56 0 0 0 2
server-monitor/ 26 0 0 0 0 0
sushiswap-v3/ 0 43 0 0 0 0
Na planilha GSheet:
Mapa de Calor
Selecionar área
Formato de número personalizado
#;;
Cor de preenchimento: vermelho 2
Formatar >> Formatação condicional
O valor é superior a 19 => vermelho 4
O valor é superior a 0 => vermelho 3

Lógica

Somente “desenhar” as estruturas.

Você já tem um stream de eventos assim:

  • marcador --COMMIT--
  • hash
  • yyyy-mm
  • N linhas de paths (0..N) até o próximo --COMMIT--

O que a gente quer no fim é uma “tabela”:

  • colunas = meses (yyyy-mm) no intervalo since..until
  • linhas = “buckets”:
    • "(root)" = commits que afetaram cwd diretamente (arquivos em path/ sem cair numa subpasta de 1º nível)
    • cada subpasta de 1 nível abaixo de cwd: ex n8n-compose, scripts, docs, etc
  • células = contagem de commits (por mês) que tocaram aquele bucket

1) Lista de meses (colunas)

Uma lista simples, ordenada:

  • months: list[str] Ex: ["2025-01", "2025-02", ..., "2026-12"]

E um map para índice (ajuda a preencher rápido depois):

  • month_idx: dict[str, int] Ex: {"2025-01": 0, "2025-02": 1, ...}

2) Contadores (matriz esparsa, fácil de somar)

O mais prático é um dicionário 2D:

  • counts: dict[str, dict[str, int]]

Onde:

  • chave 1 = bucket (ex: "(root)", "n8n-compose", "docs")
  • chave 2 = mês ("2026-02")
  • valor = contagem

Exemplo:

counts["n8n-compose"]["2026-02"] = 2
counts["(root)"]["2026-02"] = 1

Vantagem: você não precisa pré-criar todas as células; só as que aparecem.

3) “Evento commit” durante o parse (estado temporário)

Enquanto percorre o output, você precisa segurar o commit atual:

  • current_hash: str | None
  • current_month: str | None
  • current_files: list[str] (ou melhor: set, veja abaixo)

Mas pra contagem “por commit”, o detalhe importante é:

4) Buckets tocados por commit (para não contar duplicado)

Um commit pode alterar vários arquivos da mesma subpasta. A regra que você descreveu é “contagem de commits”, então um commit deve contar no máximo 1 vez por bucket no mês.

Então, ao invés de guardar current_files, o ideal é já ir montando:

  • touched_buckets: set[str]

Para cada linha de arquivo:

  • decide qual bucket ela pertence (root vs subpasta)
  • adiciona no set No fim do commit:
  • para cada bucket em touched_buckets: counts[bucket][month] += 1

Isso evita contar duas vezes o mesmo commit em n8n-compose só porque ele mudou 4 arquivos lá.

5) Regra de bucket (conceito, ainda sem código)

Dado root e path (apps/infra), cada arquivo do git log vem como path do repo:

  • Ex: apps/infra/n8n-compose/README.md

A ideia é “relativizar” ao path do cwd:

  • tira o prefixo apps/infra/ → sobra n8n-compose/README.md
  • se a sobra NÃO tem / (tipo README.md ou .gitignore) → bucket "(root)"
  • se tem / → bucket = primeiro segmento (n8n-compose)

6) Estrutura para imprimir no fim (opcional)

Quando for imprimir TSV:

  • você pode montar uma lista de linhas:

    • rows: list[tuple[bucket, list[int]]] ou imprimir direto consultando counts[bucket].get(month, 0).

Se eu tivesse que escolher as 3 estruturas essenciais:

  1. months: list[str]
  2. counts: dict[bucket][month] = int
  3. touched_buckets: set[str] por commit (estado temporário no parser)

Quando você mandar o próximo “passo”, a gente implementa a parte que você pedir (por exemplo: gerar months, ou parsear o output em eventos, etc.).

Alteração de requisito – contagem de commits

Requisito anterior (descartado)

Para cada commit, contabilizar no máximo 1 ocorrência por bucket e por mês, independentemente do número de arquivos alterados dentro do mesmo commit.

Isso exigia:

  • detectar commits
  • agrupar arquivos por bucket
  • evitar contagem duplicada dentro do mesmo commit

Requisito atual (novo)

Cada arquivo listado no git log conta como 1 ocorrência, independentemente de pertencer ao mesmo commit ou não.

Ou, de forma equivalente:

  • A unidade de contagem passa a ser arquivo alterado, não mais commit.
  • Se um commit alterar 3 arquivos no mesmo bucket e no mesmo mês, a contagem aumenta em +3 para aquele bucket naquele mês.
  • Se alterar arquivos em buckets diferentes, cada bucket recebe sua contagem normalmente.

Implicações práticas

  • Não é mais necessário:

    • rastrear início/fim de commit
    • usar touched_buckets
    • deduplicar por commit
  • O parser pode ser linear e estateless:

    • leu um mês
    • leu um path
    • incrementa o contador

Exemplo com o novo critério

Entrada:

--COMMIT--
43ab5092...
2025-09
apps/server-monitor/server/.gitignore
apps/server-monitor/server/app.py
apps/server-monitor/server/requirements.txt

Resultado:

bucket = "server"
month = "2025-09"
+3

E:

--COMMIT--
d3273f41...
2025-09
apps/server-monitor/README.md

Resultado:

bucket = "(root)"
month = "2025-09"
+1

Por que isso é melhor agora

  • Código muito mais simples
  • Sem estado por commit
  • Mais rápido de implementar e debugar
  • Mapa de calor passa a refletir volume real de alterações, não só frequência de commits

Se quiser, o próximo passo natural é:

parser linha-a-linha que, ao ler um path, já resolve bucket + month e incrementa counts bem direto, quase “for dummies” 😄

Lógica do parser

Você está certo: com o novo requisito, dá pra percorrer linearmente e pronto. O “passo 1” que eu falei (split e contar linhas) era só uma checagem boba de sanidade, mas não é necessário.

O parser, no seu formato atual, vira basicamente isto (conceitualmente):

  1. manter uma variável current_month (inicia vazia)

  2. para cada linha:

    • se for --COMMIT-- → ignora (reset opcional)

    • se for hash (40 hex) → ignora

    • se bater com YYYY-MM → seta current_month = linha

    • se parecer path (contém / ou . etc) →

      • transforma em “relativo ao cwd” removendo o prefixo path/
      • bucket = primeiro segmento ou (root)
      • incrementa counts[bucket][current_month] += 1

O que ainda vale fazer em micro-passos é separar as regras para você conseguir testar e entender sem dar bug besta:

  • regra “essa linha é mês?”
  • regra “essa linha é hash?”
  • regra “essa linha é path?”
  • regra “path → bucket”

Mas sim: já dá pra implementar o parser agora e testar com o output real.

Se você quiser, o próximo passo mais seguro (pequeno e testável) é: ✅ implementar só a detecção de current_month e imprimir quando mudar.

Exemplo do que ver no terminal:

month: 2025-09
month: 2025-10
...

Aí no passo seguinte a gente adiciona “quando encontrar um path, print bucket + month” antes de atualizar counts.