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
Links
Requisitos
- Gerar um script
scripts/activity-map.pypara 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-- hashyyyy-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 empath/sem cair numa subpasta de 1º nível)- cada subpasta de 1 nível abaixo de
cwd: exn8n-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 | Nonecurrent_month: str | Nonecurrent_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/→ sobran8n-compose/README.md - se a sobra NÃO tem
/(tipoREADME.mdou.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 consultandocounts[bucket].get(month, 0).
Se eu tivesse que escolher as 3 estruturas essenciais:
months: list[str]counts: dict[bucket][month] = inttouched_buckets: set[str]por commit (estado temporário no parser)
Quando você mandar o próximo “passo”, a gente implementa só 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 logconta 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
countsbem 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):
-
manter uma variável
current_month(inicia vazia) -
para cada linha:
-
se for
--COMMIT--→ ignora (reset opcional) -
se for hash (40 hex) → ignora
-
se bater com
YYYY-MM→ setacurrent_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
- transforma em “relativo ao cwd” removendo o prefixo
-
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.