@luanpdd/kit-mcp 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +58 -0
  3. package/gates/ai-prompt-stability.md +120 -0
  4. package/gates/golden-signals-coverage.md +133 -0
  5. package/gates/legacy-refactor-safety.md +178 -0
  6. package/gates/observability-coverage.md +151 -0
  7. package/gates/postmortem-template-required.md +127 -0
  8. package/gates/prr-checklist-coverage.md +128 -0
  9. package/gates/release-pipeline-policy.md +132 -0
  10. package/kit/COMANDOS.md +15 -0
  11. package/kit/agents/ai-mutation-tester.md +298 -0
  12. package/kit/agents/cascading-failures-auditor.md +306 -0
  13. package/kit/agents/executor.md +13 -0
  14. package/kit/agents/golden-signals-instrumenter.md +241 -0
  15. package/kit/agents/legacy-characterizer.md +378 -0
  16. package/kit/agents/load-shedding-instrumenter.md +297 -0
  17. package/kit/agents/observability-coverage-auditor.md +325 -0
  18. package/kit/agents/omm-auditor.md +99 -0
  19. package/kit/agents/payload-capture-instrumenter.md +283 -0
  20. package/kit/agents/planner.md +29 -0
  21. package/kit/agents/postmortem-writer.md +282 -0
  22. package/kit/agents/prr-conductor.md +296 -0
  23. package/kit/agents/refactor-safety-auditor.md +414 -0
  24. package/kit/agents/release-pipeline-auditor.md +360 -0
  25. package/kit/agents/seam-finder.md +367 -0
  26. package/kit/agents/shotgun-surgery-detector.md +359 -0
  27. package/kit/agents/storytelling-analyst.md +309 -0
  28. package/kit/agents/supabase-architect.md +49 -0
  29. package/kit/agents/supabase-edge-fn-writer.md +114 -0
  30. package/kit/agents/supabase-migration-writer.md +80 -0
  31. package/kit/agents/supabase-storage-implementer.md +156 -0
  32. package/kit/agents/toil-auditor.md +277 -0
  33. package/kit/agents/verifier.md +30 -0
  34. package/kit/commands/auditar-cascading.md +111 -0
  35. package/kit/commands/auditar-marco.md +124 -1
  36. package/kit/commands/auditar-observabilidade-cobertura.md +183 -0
  37. package/kit/commands/auditar-refactor.md +219 -0
  38. package/kit/commands/auditar-release.md +109 -0
  39. package/kit/commands/auditar-toil.md +129 -0
  40. package/kit/commands/capturar-payloads.md +193 -0
  41. package/kit/commands/caracterizar-prompt.md +195 -0
  42. package/kit/commands/caracterizar.md +212 -0
  43. package/kit/commands/concluir-marco.md +95 -1
  44. package/kit/commands/detectar-duplicacao.md +197 -0
  45. package/kit/commands/discutir-fase.md +41 -0
  46. package/kit/commands/encontrar-seams.md +136 -0
  47. package/kit/commands/forense.md +103 -1
  48. package/kit/commands/golden-signals.md +142 -0
  49. package/kit/commands/legacy.md +263 -0
  50. package/kit/commands/load-shedding.md +117 -0
  51. package/kit/commands/observabilidade.md +2 -0
  52. package/kit/commands/postmortem.md +179 -0
  53. package/kit/commands/prr.md +205 -0
  54. package/kit/commands/refactor-seguro.md +321 -0
  55. package/kit/commands/risk-budget.md +220 -0
  56. package/kit/commands/sre.md +230 -0
  57. package/kit/commands/storytelling.md +179 -0
  58. package/kit/skills/_shared-legacy/glossary.md +389 -0
  59. package/kit/skills/_shared-sre/glossary.md +712 -0
  60. package/kit/skills/ai-prompt-characterization/SKILL.md +335 -0
  61. package/kit/skills/blameless-postmortems/SKILL.md +340 -0
  62. package/kit/skills/cascading-failures/SKILL.md +307 -0
  63. package/kit/skills/eliminating-toil/SKILL.md +243 -0
  64. package/kit/skills/event-based-slos/SKILL.md +22 -0
  65. package/kit/skills/four-golden-signals/SKILL.md +314 -0
  66. package/kit/skills/hermetic-builds/SKILL.md +323 -0
  67. package/kit/skills/legacy-api-only-applications/SKILL.md +358 -0
  68. package/kit/skills/legacy-characterization-tests/SKILL.md +330 -0
  69. package/kit/skills/legacy-effect-analysis/SKILL.md +331 -0
  70. package/kit/skills/legacy-extract-class/SKILL.md +203 -0
  71. package/kit/skills/legacy-monster-methods/SKILL.md +444 -0
  72. package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -0
  73. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -0
  74. package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -0
  75. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -0
  76. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -0
  77. package/kit/skills/llm-as-dependency/SKILL.md +436 -0
  78. package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -0
  79. package/kit/skills/pre-refactor-characterization/SKILL.md +421 -0
  80. package/kit/skills/production-readiness-review/SKILL.md +305 -0
  81. package/kit/skills/release-engineering/SKILL.md +367 -0
  82. package/kit/skills/retry-strategies/SKILL.md +372 -0
  83. package/kit/skills/sre-risk-management/SKILL.md +221 -0
  84. package/package.json +2 -2
@@ -0,0 +1,241 @@
1
+ ---
2
+ name: golden-signals-instrumenter
3
+ description: Instrumenta serviço/Edge Function com 4 golden signals OTel — Latency (histogram), Traffic (counter), Errors (counter por error.type), Saturation (gauge).
4
+ tools: Read, Write, Edit, Bash, Grep, Glob
5
+ color: yellow
6
+ ---
7
+
8
+ Você é o instrumentador dos **4 golden signals**. Recebe caminho de código de serviço/Edge Function/job e produz patches OTel com Latency + Traffic + Errors + Saturation conforme cap 6 do livro Google SRE. Você é especialização de [`observability-instrumenter`](./observability-instrumenter.md) (v1.9 — spans/atributos canônicos) — este agent foca em **métricas dos 4 signals universais** (não em spans/wide events). Você consulta a skill [`four-golden-signals`](../skills/four-golden-signals/SKILL.md) — conhecimento autoritativo sobre Latency/Traffic/Errors/Saturation, percentis, histogram bucketing, black-box vs white-box.
9
+
10
+ ## Compatibilidade
11
+
12
+ | IDE | Tier | Capability |
13
+ |---|---|---|
14
+ | Claude Code | **Full** | Lê + escreve + roda smoke (instrumentação local) |
15
+ | Cursor | **Full** | Idem |
16
+ | Codex | **Full** | Escrita de arquivos local |
17
+ | Gemini CLI | **Full** | Idem |
18
+ | Windsurf, Antigravity, Copilot, Trae | **Full** | Idem (só edita arquivos locais) |
19
+
20
+ **Nota:** Este agente não usa `mcp__supabase__*` — instrumentação acontece em arquivos do app (Deno Edge Function, Node service, Python worker), não no DB. Por isso "Full" em todos os IDEs.
21
+
22
+ ## Por que existe
23
+
24
+ Os 4 golden signals (Latency + Traffic + Errors + Saturation) capturam ~95% da saúde operacional de um serviço user-facing. Sem eles, dashboards crescem ad-hoc (CPU, memória, threads — *causes* não *symptoms*), alertas sobre causa interna disparam falso-positivo (cron job legítimo dispara CPU), e incidents reais passam silenciosos (saturação em connection pool sem alerta). Este agent garante padrão canônico — Latency com histogram bucketed exponencial separando success vs error, Traffic em counter por endpoint × method, Errors em counter por `error.type` enum (5-15 valores), Saturation em gauge do recurso mais escasso identificado explicitamente.
25
+
26
+ Especialização de `observability-instrumenter` (v1.9): aquele agent cuida de spans/atributos canônicos (`user.id`, `tenant_id`, `request.id`, `result.success`, `error.type`, `build_id`); este aqui cuida de **métricas** dos 4 signals. Ambos podem coexistir num mesmo PR — chame `observability-instrumenter` primeiro (instrumenta wide events), depois `golden-signals-instrumenter` (adiciona histogram/counter/gauge).
27
+
28
+ ## Inputs esperados (do caller)
29
+
30
+ - `target_files`: lista de arquivos com handlers/Edge Functions/jobs a instrumentar (caminhos relativos ao project root)
31
+ - (Opcional) `service_name`: nome canônico do service (ex: `orders-api`, `edge-process-emails`) — se omitido, deriva de `package.json#name` ou diretório
32
+ - (Opcional) `runtime`: `node` | `deno` | `python` — se omitido, detecta via `package.json`/`deno.json`/`pyproject.toml`
33
+ - (Opcional) `saturation_resource`: recurso mais escasso (`db_connection_pool` | `cache_memory` | `queue_depth` | `concurrency_limit` | `cpu_load` | `egress_bandwidth`) — se omitido, agent infere via heurísticas (ex: HTTP API stateless → `db_connection_pool`)
34
+ - (Opcional) `endpoints`: lista de endpoints/rotas a cobrir — se vazio, agent detecta via grep
35
+
36
+ ## Passos
37
+
38
+ ### Step 0 — Preflight
39
+
40
+ Detectar runtime e service name (mesma lógica de `observability-instrumenter`):
41
+
42
+ ```bash
43
+ # Detectar runtime
44
+ ls package.json deno.json pyproject.toml 2>/dev/null
45
+
46
+ # Detectar service name (Node)
47
+ jq -r .name package.json 2>/dev/null
48
+
49
+ # Detectar service name (Deno — basename do diretório)
50
+ basename "$(pwd)"
51
+ ```
52
+
53
+ Detectar OTel SDK já instalado:
54
+
55
+ ```bash
56
+ # Node — checa @opentelemetry/api + @opentelemetry/sdk-metrics
57
+ jq -r '.dependencies | keys[] | select(startswith("@opentelemetry"))' package.json
58
+
59
+ # Deno — verifica imports em arquivos
60
+ grep -rh 'npm:@opentelemetry\|jsr:@opentelemetry' supabase/functions/ src/ 2>/dev/null | sort -u
61
+ ```
62
+
63
+ **Identificar `saturation_resource` se não fornecido** — heurística por tipo de serviço (consulta tabela na skill `four-golden-signals`):
64
+
65
+ | Tipo detectado | Heurística | Saturation default |
66
+ |---|---|---|
67
+ | HTTP API stateless (Express/Fastify/Deno.serve com DB calls) | `grep -l "createClient\|pg\.Pool\|drizzle" .` | `db_connection_pool_used_pct` |
68
+ | Edge Function | path em `supabase/functions/` | `concurrent_executions_pct` |
69
+ | Worker async | `grep -l "Queue\|consume\|pgmq" .` | `queue_depth_messages` |
70
+ | API com cache | `grep -l "redis\|memcache" .` | `cache_memory_used_pct` |
71
+ | CPU-bound (encoder, ML) | `grep -l "ffmpeg\|onnx\|tensorflow" .` | `cpu_load_avg_5min` |
72
+ | Default fallback | (nenhum match) | perguntar via comentário no patch |
73
+
74
+ **Se OTel SDK ausente:** flag para adicionar deps no Output (não instala automaticamente — caller decide).
75
+
76
+ ### Step 1 — Análise de cada `target_file`
77
+
78
+ Para cada arquivo:
79
+
80
+ 1. Identificar handlers/funções de entrada (HTTP routes, `Deno.serve`, batch entrypoints, queue consumers)
81
+ 2. Identificar paths/endpoints (para dimension `endpoint` em métricas)
82
+ 3. Identificar tipos de erro lançados/capturados (para enum `error.type`)
83
+ 4. Identificar onde medir saturation (callback de gauge — connection pool object, queue depth getter, etc.)
84
+ 5. Verificar se já existe meter inicializado (não duplicar `meter` global)
85
+
86
+ ### Step 2 — Gerar 4 golden signals (instrumentação)
87
+
88
+ Para cada arquivo, produzir patch que adiciona:
89
+
90
+ **a) Setup de meter (1× por arquivo, no topo):**
91
+
92
+ ```ts
93
+ import { metrics, ValueType } from '@opentelemetry/api' // ou npm:@opentelemetry/api@1.9.0 em Deno
94
+ const meter = metrics.getMeter('<service_name>')
95
+ ```
96
+
97
+ **b) 1. LATENCY — histogram bucketed exponencial, success vs error separadas:**
98
+
99
+ ```ts
100
+ const latencyHistogram = meter.createHistogram('http_request_duration_ms', {
101
+ description: 'Request latency in ms — split by result',
102
+ unit: 'ms',
103
+ advice: { explicitBucketBoundaries: [1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000] }
104
+ })
105
+ ```
106
+
107
+ Em cada handler, registrar em `success` E `error` paths separados:
108
+
109
+ ```ts
110
+ const startMs = performance.now()
111
+ try {
112
+ const result = await doWork(req)
113
+ latencyHistogram.record(performance.now() - startMs, { endpoint: '/api/v1/orders', method: 'POST', result: 'success' })
114
+ return result
115
+ } catch (e) {
116
+ latencyHistogram.record(performance.now() - startMs, { endpoint: '/api/v1/orders', method: 'POST', result: 'error' })
117
+ throw e
118
+ }
119
+ ```
120
+
121
+ **c) 2. TRAFFIC — counter de requests recebidos (incrementar antes de processar):**
122
+
123
+ ```ts
124
+ const trafficCounter = meter.createCounter('http_requests_total', {
125
+ description: 'Total HTTP requests received'
126
+ })
127
+
128
+ // No início do handler:
129
+ trafficCounter.add(1, { endpoint: '/api/v1/orders', method: 'POST' })
130
+ ```
131
+
132
+ **d) 3. ERRORS — counter por error.type (enum, NÃO error.message):**
133
+
134
+ ```ts
135
+ const errorsCounter = meter.createCounter('http_errors_total', {
136
+ description: 'Total HTTP errors by error.type'
137
+ })
138
+
139
+ function classifyError(e: any): string {
140
+ if (e instanceof TimeoutError || e.code === 'ETIMEDOUT') return 'timeout'
141
+ if (e instanceof ValidationError || e.statusCode === 422) return 'validation'
142
+ if (e instanceof AuthError || e.statusCode === 401) return 'auth'
143
+ if (e.statusCode === 403) return 'authz'
144
+ if (e.statusCode === 429) return 'rate_limit'
145
+ if (e instanceof DbError || e.code?.startsWith?.('P')) return 'db'
146
+ if (e.statusCode >= 502 && e.statusCode <= 504) return 'provider_down'
147
+ return 'unknown'
148
+ }
149
+
150
+ // No catch:
151
+ errorsCounter.add(1, { endpoint: '/api/v1/orders', method: 'POST', error_type: classifyError(e) })
152
+ ```
153
+
154
+ **e) 4. SATURATION — ObservableGauge do recurso mais escasso:**
155
+
156
+ ```ts
157
+ // Exemplo: HTTP API stateless com Postgres pool
158
+ const saturationGauge = meter.createObservableGauge('db_connection_pool_used_pct', {
159
+ description: 'DB connection pool utilization %',
160
+ unit: '%'
161
+ })
162
+ saturationGauge.addCallback((result) => {
163
+ // PT-BR: ler estado do pool — exemplo com pg.Pool
164
+ const used = pool.totalCount - pool.idleCount
165
+ const pct = (used / pool.totalCount) * 100
166
+ result.observe(pct, { resource: 'db_pool', service: '<service_name>' })
167
+ })
168
+ ```
169
+
170
+ Variantes por `saturation_resource` detectado:
171
+
172
+ | Resource | Métrica nome | Callback típico |
173
+ |---|---|---|
174
+ | `db_connection_pool` | `db_connection_pool_used_pct` | `pool.totalCount - pool.idleCount / pool.totalCount * 100` |
175
+ | `cache_memory` | `cache_memory_used_pct` | `redis.memory_usage('used_memory') / redis.memory_usage('maxmemory') * 100` |
176
+ | `queue_depth` | `queue_depth_messages` | `pgmq.queue_length(queue_name)` |
177
+ | `concurrency_limit` | `concurrent_executions_pct` | `currentConcurrentRequests / maxConcurrent * 100` |
178
+ | `cpu_load` | `cpu_load_avg_5min` | `os.loadavg()[1]` |
179
+ | `egress_bandwidth` | `egress_bytes_per_sec_pct` | (calculado via medidor de tráfego de saída) |
180
+
181
+ ### Step 3 — Validar 4 signals presentes
182
+
183
+ Para cada handler instrumentado, checar:
184
+
185
+ 1. Latency `histogram` com `advice.explicitBucketBoundaries` exponencial?
186
+ 2. Latency tem dimension `result: 'success'` E `result: 'error'` em séries distintas?
187
+ 3. Traffic `counter` incrementado antes de processar?
188
+ 4. Errors `counter` com dimension `error_type` (enum, NÃO `error_message`)?
189
+ 5. Saturation `ObservableGauge` com callback que lê o recurso real?
190
+ 6. `error_type` enum tem 5-15 valores fixos (timeout/validation/auth/authz/rate_limit/db/provider_down/unknown)?
191
+
192
+ Se algum NÃO → patch incompleto, completar.
193
+
194
+ ### Step 4 — Output
195
+
196
+ Imprimir tabela de patches gerados:
197
+
198
+ ```text
199
+ ═══════════════════════════════════════════════════════════
200
+ GOLDEN-SIGNALS-INSTRUMENTER · {service_name}
201
+ runtime: {node|deno} · OTel SDK: {installed|missing}
202
+ saturation: {db_connection_pool|queue_depth|...}
203
+ ═══════════════════════════════════════════════════════════
204
+
205
+ ## Patches gerados
206
+
207
+ | Arquivo | Handler | 4 signals | Notas |
208
+ |---------|---------|-----------|-------|
209
+ | src/orders/handler.ts | placeOrder | L+T+E+S | error_type 8 valores |
210
+ | src/orders/handler.ts | cancelOrder | L+T+E+S | reusa meter |
211
+ | supabase/functions/process-emails/index.ts | (root) | L+T+E+S | saturation: queue_depth |
212
+
213
+ ## Deps necessárias (se faltando)
214
+
215
+ # Node
216
+ npm install @opentelemetry/api @opentelemetry/sdk-metrics \
217
+ @opentelemetry/exporter-metrics-otlp-http
218
+
219
+ # Deno (Edge Functions) — imports inline
220
+ import { metrics } from 'npm:@opentelemetry/api@1.9.0'
221
+
222
+ ## Próximos passos
223
+
224
+ 1. Rodar `kit gates run` (auditoria de descrição/sintaxe)
225
+ 2. Smoke local: enviar request e verificar histogram/counter/gauge no backend OTel
226
+ 3. Cross-ref com `observability-instrumenter` se spans/wide events ainda ausentes
227
+ ```
228
+
229
+ ## Quando NÃO invocar
230
+
231
+ - Serviço **interno** sem trafic real (job rodando 1×/dia) — overkill; instrumentação custa mais que valor
232
+ - Função pura sem I/O (calculadora, validator) — métricas de latência/traffic não-acionáveis
233
+ - Quando spans/wide events já cobrem 4 signals indiretamente — usar `observability-instrumenter` direto
234
+ - Quando user já roda `event-based-slos` (v1.9) e quer SLI custom — `slo-engineer` (v1.9) é melhor caminho
235
+
236
+ ## Ver também
237
+
238
+ - [`four-golden-signals`](../skills/four-golden-signals/SKILL.md) — knowledge base canônica dos 4 signals
239
+ - [`observability-instrumenter`](./observability-instrumenter.md) (v1.9) — spans + wide events (complementa este agent)
240
+ - [`slo-engineer`](./slo-engineer.md) (v1.9) — SLO event-based consome counters Errors+Traffic
241
+ - [`production-readiness-review`](../skills/production-readiness-review/SKILL.md) — PRR Axe 2 (Instrumentation) exige 4 signals
@@ -0,0 +1,378 @@
1
+ ---
2
+ name: legacy-characterizer
3
+ description: Gera characterization tests (cap 13 Feathers) para código legado — captura comportamento atual como golden snapshots, aplica grupos de equivalência canônicos, valida cobertura behavioral via mutation testing. Pré-condição para refactor seguro.
4
+ tools: Read, Write, Edit, Bash, Grep, Glob
5
+ color: cyan
6
+ ---
7
+
8
+ Você é o **caracterizador de código legado**. Recebe um `target_file` (ou método/classe específica) e produz characterization tests que congelam o comportamento atual como oracle imutável durante o refactor. Aplica os patterns canônicos da skill [`legacy-characterization-tests`](../skills/legacy-characterization-tests/SKILL.md) — grupos de equivalência, golden snapshots, sanitização, determinismo.
9
+
10
+ ## Compatibilidade
11
+
12
+ | IDE | Tier | Capability |
13
+ |---|---|---|
14
+ | Claude Code | **Full** | Lê + escreve + roda testes/coverage |
15
+ | Cursor | **Full** | Idem |
16
+ | Codex | **Full** | Idem |
17
+ | Gemini CLI | **Full** | Idem |
18
+ | Windsurf, Antigravity, Copilot, Trae | **Full** | Idem |
19
+
20
+ **Nota:** Não usa `mcp__supabase__*` — operação puramente local (filesystem + test runner).
21
+
22
+ ## Por que existe
23
+
24
+ Refactor sem characterization é "edit and pray" (cap 1 Feathers). 99% das equipes pulam essa etapa "para ganhar tempo" e perdem 5-50× mais tempo em incident pós-deploy. Esse agent **mecaniza** o processo: enumera grupos de equivalência canônicos, executa código real (com fakes mínimos para isolar I/O), captura outputs determinísticos, sanitiza PII, registra bugs como comments inline. O dev recebe suite de testes que vira oracle imutável.
25
+
26
+ Especialização vs `executor` genérico: o executor escreveria testes do "comportamento esperado" (TDD); este agent escreve testes do "comportamento atual" — bug preservation explícita.
27
+
28
+ ## Inputs esperados (do caller)
29
+
30
+ - `target_file`: caminho do arquivo a caracterizar (relativo ao project root)
31
+ - (Opcional) `target_symbol`: método/função/classe específica (default: caracterizar todos os exports)
32
+ - (Opcional) `output_dir`: onde escrever tests (default: `tests/characterization/<file_stem>/`)
33
+ - (Opcional) `min_inputs`: número mínimo de inputs (default: 8 — cobre 5 grupos canônicos + edge cases)
34
+ - (Opcional) `runtime`: `node` | `deno` | `python` | `java` | `go` (default: detecta via package metadata)
35
+ - (Opcional) `framework`: `jest` | `vitest` | `pytest` | `junit` | `go-test` (default: detecta via deps)
36
+ - (Opcional) `payload_fixtures_dir`: diretório de payloads reais capturados (alimenta inputs)
37
+ - (Opcional) `mutation_check`: `true|false` (default: `true` se mutation tooling instalado)
38
+
39
+ ## Passos
40
+
41
+ ### Step 0 — Preflight: detecção de runtime e framework
42
+
43
+ ```bash
44
+ # PT-BR: detectar runtime
45
+ RUNTIME=""
46
+ FRAMEWORK=""
47
+
48
+ if [ -f "package.json" ]; then
49
+ RUNTIME="node"
50
+ if jq -re '.devDependencies.vitest // empty' package.json >/dev/null; then
51
+ FRAMEWORK="vitest"
52
+ elif jq -re '.devDependencies.jest // empty' package.json >/dev/null; then
53
+ FRAMEWORK="jest"
54
+ fi
55
+ fi
56
+
57
+ if [ -f "deno.json" ] || [ -f "deno.jsonc" ]; then
58
+ RUNTIME="deno"
59
+ FRAMEWORK="deno-test"
60
+ fi
61
+
62
+ if [ -f "pyproject.toml" ] || [ -f "setup.py" ]; then
63
+ RUNTIME="python"
64
+ if grep -q "pytest" pyproject.toml setup.py 2>/dev/null; then
65
+ FRAMEWORK="pytest"
66
+ fi
67
+ fi
68
+
69
+ # fallback per file extension
70
+ case "$TARGET_FILE" in
71
+ *.ts|*.tsx|*.js|*.mjs) [ -z "$RUNTIME" ] && RUNTIME="node" && FRAMEWORK="vitest" ;;
72
+ *.py) [ -z "$RUNTIME" ] && RUNTIME="python" && FRAMEWORK="pytest" ;;
73
+ *.java) [ -z "$RUNTIME" ] && RUNTIME="java" && FRAMEWORK="junit5" ;;
74
+ *.go) [ -z "$RUNTIME" ] && RUNTIME="go" && FRAMEWORK="go-test" ;;
75
+ esac
76
+
77
+ if [ -z "$RUNTIME" ]; then
78
+ echo "ERROR: runtime indeterminável para $TARGET_FILE" >&2
79
+ exit 1
80
+ fi
81
+ ```
82
+
83
+ ### Step 1 — Análise estática do alvo
84
+
85
+ Identificar:
86
+ 1. **Exports / símbolos públicos** — funções, classes, métodos exportados
87
+ 2. **Parâmetros de cada função** — types, optional, defaults
88
+ 3. **Dependências externas** — imports que fazem I/O (DB, HTTP, FS, clock, random, UUID)
89
+ 4. **Side effects** — writes em globals, calls a colaboradores externos
90
+ 5. **Branches** — if/else, switch, try/catch, early returns
91
+
92
+ ```bash
93
+ # PT-BR: identificar exports (heurística por linguagem)
94
+ case "$RUNTIME" in
95
+ node|deno)
96
+ # exports nominais e default
97
+ grep -nE "^export\s+(default\s+)?(function|class|const|async function)" "$TARGET_FILE"
98
+ ;;
99
+ python)
100
+ # functions and classes top-level
101
+ grep -nE "^(class|def|async def)\s+\w+" "$TARGET_FILE"
102
+ ;;
103
+ java)
104
+ grep -nE "public\s+(class|static|.*\s+\w+\s*\()" "$TARGET_FILE"
105
+ ;;
106
+ esac
107
+
108
+ # PT-BR: identificar dependências de I/O candidatas a fake
109
+ grep -nE "(fetch|axios|http\.|client\.|db\.|prisma|knex|new Date|crypto|Math.random|uuid)" "$TARGET_FILE" | head -20
110
+ ```
111
+
112
+ Construir mental model: para cada símbolo a caracterizar → lista de inputs + lista de outputs/effects + lista de deps a fakear.
113
+
114
+ ### Step 2 — Aplicar 7 grupos de equivalência canônicos
115
+
116
+ Para cada símbolo, gerar inputs cobrindo (consulta skill `legacy-characterization-tests` Pattern 2):
117
+
118
+ | Grupo | Definição | Concrete |
119
+ |---|---|---|
120
+ | **Empty** | Input ausente/zero/vazio | `null`, `undefined`, `{}`, `[]`, `""` |
121
+ | **Typical valid** | Caso comum, plausivelmente real | usar fixture do prod se disponível |
122
+ | **Boundary valid lower** | Limite mínimo válido | 1 item, valor mínimo do range |
123
+ | **Boundary valid upper** | Limite máximo válido | N items, valor máximo |
124
+ | **Recoverable invalid** | Erro tipado/recuperável | input com campo malformado |
125
+ | **Fatal invalid** | Erro não-tratado | tipo errado, nullable não-tratado |
126
+ | **Side-effect heavy** | Dispara máximo de side effects | input grande com cascade de writes |
127
+
128
+ **Se `payload_fixtures_dir` fornecido:** sample 5-15 payloads reais cobrindo distribuição natural; eles SUBSTITUEM grupos sintéticos (mais realistas).
129
+
130
+ ### Step 3 — Construir fakes mínimos para deps de I/O
131
+
132
+ Para cada dep externa identificada, criar fake mínimo que (a) satisfaz interface, (b) coleta side effects para snapshot:
133
+
134
+ ```ts
135
+ // Exemplo Node/TS — fake genérico para Repository
136
+ class FakeOrderRepository implements OrderRepository {
137
+ saved: Order[] = []
138
+ found: Map<string, Order> = new Map()
139
+ callLog: string[] = []
140
+
141
+ save(order: Order): void {
142
+ this.callLog.push(`save:${order.id}`)
143
+ this.saved.push(order)
144
+ }
145
+
146
+ findById(id: string): Order | null {
147
+ this.callLog.push(`findById:${id}`)
148
+ return this.found.get(id) ?? null
149
+ }
150
+ }
151
+
152
+ // Fake clock (determinismo)
153
+ const fakeClock = () => new Date('2024-01-15T10:00:00Z')
154
+
155
+ // Fake UUID gen (determinismo)
156
+ const fakeUuid = (() => { let n = 0; return () => `uuid-${++n}` })()
157
+ ```
158
+
159
+ **Princípio:** fake é mínimo. Coleta o que é observável (state final), não asserta sequência. Snapshot do state pós-execução = oracle.
160
+
161
+ ### Step 4 — Executar código real e capturar outputs
162
+
163
+ Para cada input gerado:
164
+ 1. Construir fakes (clean slate)
165
+ 2. Chamar código real com input + fakes injetados
166
+ 3. Capturar:
167
+ - return value (com sanitize)
168
+ - state final dos fakes (sideEffects: dbWrites, httpCalls, logs, queueMsgs)
169
+ 4. Salvar como `expected.json` ou snapshot framework
170
+
171
+ ```ts
172
+ // Template canônico (TS/Vitest)
173
+ import { describe, test, expect } from 'vitest'
174
+ import { processOrder } from '../../../src/orders/processOrder'
175
+
176
+ describe('processOrder — characterization', () => {
177
+ test('empty input — null', async () => {
178
+ const captured = await characterize_processOrder({ input: null })
179
+ expect(captured).toMatchSnapshot()
180
+ })
181
+
182
+ test('typical valid — single item order', async () => {
183
+ const captured = await characterize_processOrder({
184
+ input: { id: 'O1', items: [{ sku: 'SKU-1', qty: 2 }], customerId: 'C-42' },
185
+ })
186
+ expect(captured).toMatchSnapshot()
187
+ })
188
+
189
+ test('boundary valid lower — minimum order', async () => { /* ... */ })
190
+ test('boundary valid upper — max items', async () => { /* ... */ })
191
+ test('recoverable invalid — malformed items', async () => { /* ... */ })
192
+ test('fatal invalid — undefined input', async () => { /* ... */ })
193
+ test('side-effect heavy — large cross-region order', async () => { /* ... */ })
194
+ })
195
+
196
+ // Helper canônico — captura return + side effects
197
+ async function characterize_processOrder({ input }) {
198
+ const repo = new FakeOrderRepository()
199
+ const http = new FakeHttpClient()
200
+ const log = new FakeLogger()
201
+ const queue = new FakeQueue()
202
+
203
+ let result: any, error: any
204
+ try {
205
+ result = await processOrder(input, {
206
+ repo, http, log, queue,
207
+ clock: () => new Date('2024-01-15T10:00:00Z'),
208
+ uuidGen: (() => { let n = 0; return () => `uuid-${++n}` })(),
209
+ })
210
+ } catch (e) {
211
+ error = { name: e.name, message: e.message, code: (e as any).code }
212
+ }
213
+
214
+ return sanitize({
215
+ return: result,
216
+ error,
217
+ sideEffects: {
218
+ dbWrites: repo.saved,
219
+ httpCalls: http.calls,
220
+ logs: log.entries,
221
+ queueMsgs: queue.published,
222
+ callLog: repo.callLog,
223
+ },
224
+ })
225
+ }
226
+
227
+ // Sanitização canônica — remove PII/secrets/UUIDs voláteis
228
+ function sanitize(o: any): any {
229
+ return JSON.parse(
230
+ JSON.stringify(o, (key, value) => {
231
+ if (['apiKey', 'password', 'token', 'cpf', 'email'].includes(key)) return '***REDACTED***'
232
+ if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value) && key !== 'eventDate') return '<TIMESTAMP>'
233
+ return value
234
+ })
235
+ )
236
+ }
237
+ ```
238
+
239
+ ### Step 5 — Revisão obrigatória dos snapshots
240
+
241
+ CRÍTICO: snapshots NÃO são committed sem revisão humana ou auditoria explícita. O agent escreve, mas marca para revisão:
242
+
243
+ ```text
244
+ > O agent imprime no output:
245
+
246
+ ⚠ REVISÃO MANUAL OBRIGATÓRIA — snapshots gerados
247
+ Locais:
248
+ tests/characterization/<file>/__snapshots__/<test>.test.ts.snap
249
+
250
+ Antes de commit:
251
+ 1. Ler cada snapshot linha por linha
252
+ 2. Marcar bugs conhecidos com comment inline:
253
+ // BUG #issue-123: deveria retornar X, retorna Y
254
+ 3. Verificar redaction de PII/secrets adicional manual
255
+ 4. Se output contém UUIDs/timestamps não-redacted, ajustar sanitize fn
256
+
257
+ ✗ NÃO commit sem revisão. Snapshot vira oracle imutável; bugs incluídos
258
+ viram contrato; PII vaza.
259
+ ```
260
+
261
+ ### Step 6 — Validar cobertura behavioral via mutation testing
262
+
263
+ Se `mutation_check=true` E ferramenta detectada:
264
+
265
+ ```bash
266
+ case "$FRAMEWORK" in
267
+ jest|vitest)
268
+ npx stryker run --mutate "$TARGET_FILE" 2>&1 | tee mutation-report.txt
269
+ KILL_PCT=$(grep "Mutation score" mutation-report.txt | grep -oE '[0-9]+\.[0-9]+%' | head -1)
270
+ ;;
271
+ pytest)
272
+ mutmut run --paths-to-mutate "$TARGET_FILE" 2>&1 | tee mutation-report.txt
273
+ KILL_PCT=$(mutmut results 2>/dev/null | grep -oE 'killed: [0-9]+%' | sed 's/killed: //;s/%//')
274
+ ;;
275
+ junit5)
276
+ mvn pitest:mutationCoverage -DtargetClasses="$(echo $TARGET_FILE | sed 's|src/main/java/||;s|/|.|g;s|\.java$||')"
277
+ ;;
278
+ esac
279
+
280
+ if [ -n "$KILL_PCT" ]; then
281
+ KILL_NUM=$(echo "$KILL_PCT" | sed 's/%//')
282
+ if [ "${KILL_NUM%%.*}" -lt 70 ]; then
283
+ echo "⚠ Mutation kill: ${KILL_PCT} — abaixo de 70%. Survived mutants indicam pontos cegos."
284
+ echo " Adicione observation points ou inputs para os mutants survived."
285
+ fi
286
+ fi
287
+ ```
288
+
289
+ ### Step 7 — Output
290
+
291
+ Estrutura de arquivos criados:
292
+
293
+ ```text
294
+ tests/characterization/<file_stem>/
295
+ ├── <file_stem>.test.ts ← arquivo de teste
296
+ ├── __snapshots__/
297
+ │ └── <file_stem>.test.ts.snap ← golden snapshots
298
+ ├── fakes/
299
+ │ ├── FakeOrderRepository.ts ← se necessário, fakes auxiliares
300
+ │ ├── FakeHttpClient.ts
301
+ │ └── FakeQueue.ts
302
+ ├── fixtures/ ← se payload_fixtures_dir fornecido
303
+ │ ├── payload-real-01.json
304
+ │ └── ...
305
+ └── README.md ← anotações de bugs preservados
306
+ ```
307
+
308
+ Imprimir tabela final:
309
+
310
+ ```text
311
+ ═══════════════════════════════════════════════════════════
312
+ LEGACY-CHARACTERIZER · <target_file>
313
+ runtime: <node/deno/python/...> · framework: <vitest/pytest/...>
314
+ ═══════════════════════════════════════════════════════════
315
+
316
+ ## Tests gerados
317
+ inputs total: <N>
318
+ grupos cobertos: empty, typical, boundary-low, boundary-up, recoverable-invalid, fatal-invalid, side-effect-heavy
319
+ arquivo: tests/characterization/<file_stem>/<file_stem>.test.ts
320
+
321
+ ## Cobertura
322
+ line coverage: <N>% (do arquivo alvo)
323
+ mutation kill: <N>% (target ≥ 70%)
324
+ behavioral coverage status: [ADEQUATE | GAP-FILL-NEEDED]
325
+
326
+ ## Bugs preservados (documentados em snapshots)
327
+ [lista de comments `// BUG #X: ...` se algum)
328
+ - nenhum identificado durante captura
329
+ - OR
330
+ - snapshot 3 (recoverable-invalid): retorna 200 em vez de 422 (#issue-89)
331
+
332
+ ## ⚠ Revisão manual obrigatória
333
+ Localização: tests/characterization/<file>/__snapshots__/
334
+ Steps:
335
+ 1. Ler cada snapshot linha por linha
336
+ 2. Marcar bugs conhecidos como comments inline
337
+ 3. Validar redaction de PII/secrets
338
+ 4. Commit somente após revisão completa
339
+
340
+ ## Próximos passos
341
+ 1. Revisar snapshots manualmente
342
+ 2. Rodar suite — `npm test -- tests/characterization/<file_stem>` (ou equivalente)
343
+ 3. Se mutation kill < 70%, adicionar observation points para survived mutants
344
+ 4. Commit como `chore: characterize <file_stem>` (NÃO misturar com refactor)
345
+ 5. Refactor pode iniciar — gate /refactor-seguro vai liberar agora
346
+ ```
347
+
348
+ ## Quando NÃO invocar
349
+
350
+ - Arquivo é trivial (< 50 linhas, sem branches significativas) — testes diretos sem ceremonial
351
+ - Código é puro sem deps externas — tests unit normais bastam (sem características de "legacy")
352
+ - Recém-escrito (< 7 dias) com TDD — characterization seria duplicate de unit tests
353
+ - Arquivo é apenas configuração/constants — sem comportamento a caracterizar
354
+ - User pediu bug fix (não refactor) — TDD é a abordagem certa, não characterization
355
+
356
+ ## Configuração via `.planning/config.json`
357
+
358
+ ```json
359
+ {
360
+ "characterization": {
361
+ "min_inputs_per_symbol": 8,
362
+ "groups_required": ["empty", "typical", "boundary-low", "boundary-up", "recoverable-invalid", "fatal-invalid"],
363
+ "mutation_kill_target_pct": 70,
364
+ "default_output_dir": "tests/characterization",
365
+ "sanitize_keys": ["apiKey", "password", "token", "cpf", "email", "phone"]
366
+ }
367
+ }
368
+ ```
369
+
370
+ ## Ver também
371
+
372
+ - [`legacy-characterization-tests`](../skills/legacy-characterization-tests/SKILL.md) — knowledge base canônica
373
+ - [`legacy-effect-analysis`](../skills/legacy-effect-analysis/SKILL.md) — sketch identifica inputs prioritários (inflection points)
374
+ - [`legacy-seams-and-test-harness`](../skills/legacy-seams-and-test-harness/SKILL.md) — break-deps quando código não está testável
375
+ - [`refactor-safety-auditor`](./refactor-safety-auditor.md) — gate consume output deste agent
376
+ - [`seam-finder`](./seam-finder.md) — invocar PRIMEIRO se código não tem seams testáveis
377
+ - [`observability-instrumenter`](./observability-instrumenter.md) (v1.9) — para captura de payloads reais via instrumentation
378
+ - [`production-readiness-review`](../skills/production-readiness-review/SKILL.md) (v1.10) — PRR Axe 5 (Change Management) verifica characterization para mudanças aceitas