@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,358 @@
1
+ ---
2
+ name: legacy-api-only-applications
3
+ description: Use ao escrever ou refatorar código que é maioritariamente wrapper de API externa (cap 15 Feathers + Supabase Edge Functions). Adapter / anti-corruption layer canônico — interface mínima testável + adapter para API real.
4
+ ---
5
+
6
+ # Legacy — API-Only Applications
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando user trabalha em código que é primariamente wrapper de API externa. Trigger phrases:
11
+
12
+ - "essa edge function só chama Stripe/OpenAI/Twilio"
13
+ - "como testar integração com [vendor]?"
14
+ - "anti-corruption layer", "adapter pattern"
15
+ - "API-only application", "cap 15 Feathers"
16
+ - "wrapper de API"
17
+ - arquivo em `supabase/functions/<name>/index.ts` com 60%+ de chamadas a SDKs/APIs externos
18
+
19
+ ## Regras absolutas
20
+
21
+ - **Adapter pattern é a resposta canônica.** Code de produção depende de **interface mínima**, não da API completa do vendor. Adapter concreto envolve a API real.
22
+ - **Interface mínima = só o que VOCÊ usa.** SDKs do Stripe/OpenAI/etc têm 100+ métodos; você usa 5. Sua interface tem 5.
23
+ - **Anti-corruption layer (DDD) = adapter + tradução de tipos.** Tipos do vendor (e.g., `Stripe.Charge`, `OpenAI.ChatCompletion`) NÃO atravessam camadas internas. Adapter traduz vendor type → domain type.
24
+ - **Modernização Supabase Edge Functions:** Edge Function que wrappar Stripe/OpenAI é o caso paradigmático moderno do cap 15. Pattern canônico: handler depende de interface, adapter implementa, adapter testado isolado, fake adapter em testes.
25
+ - **Modernização LLM providers:** OpenAI/Anthropic clients são API externa. Aplicar exatamente o mesmo pattern — `LLMProvider` interface + `OpenAIAdapter` + `AnthropicAdapter` + `FakeLLMProvider`. Nunca acoplar handler ao SDK específico.
26
+ - **Versionar a interface, não a API do vendor.** Quando vendor muda assinatura, adapter absorve a mudança; consumidor (handler interno) não vê.
27
+ - **Idempotência via adapter.** Adapter pode adicionar idempotency key, retry com jitter, deadline propagation, sem que handler precise saber.
28
+
29
+ ## Patterns canônicos
30
+
31
+ ### Pattern 1: Adapter para vendor API (Stripe canônico)
32
+
33
+ ```ts
34
+ // ANTES — handler acoplado ao SDK Stripe (intestável sem mock global)
35
+ import Stripe from 'stripe'
36
+
37
+ const stripe = new Stripe(Deno.env.get('STRIPE_KEY')!)
38
+
39
+ Deno.serve(async (req) => {
40
+ const order = await req.json()
41
+ const charge = await stripe.charges.create({ // ← acoplamento direto
42
+ amount: order.totalCents,
43
+ currency: order.currency,
44
+ source: order.cardToken,
45
+ })
46
+ return new Response(JSON.stringify({ id: charge.id, status: charge.status }))
47
+ })
48
+
49
+ // DEPOIS — handler depende de interface mínima
50
+ interface PaymentGateway {
51
+ charge(input: ChargeInput): Promise<ChargeResult>
52
+ }
53
+ type ChargeInput = { amountCents: number; currency: string; cardToken: string }
54
+ type ChargeResult = { id: string; status: 'succeeded' | 'failed' | 'pending' }
55
+
56
+ class StripeAdapter implements PaymentGateway {
57
+ constructor(private stripe: Stripe) {}
58
+ async charge(input: ChargeInput): Promise<ChargeResult> {
59
+ const c = await this.stripe.charges.create({
60
+ amount: input.amountCents,
61
+ currency: input.currency,
62
+ source: input.cardToken,
63
+ })
64
+ // anti-corruption: traduz Stripe.Charge.status para nosso domain enum
65
+ const status = this.translateStatus(c.status)
66
+ return { id: c.id, status }
67
+ }
68
+ private translateStatus(s: Stripe.Charge.Status): ChargeResult['status'] {
69
+ if (s === 'succeeded') return 'succeeded'
70
+ if (s === 'failed' || s === 'canceled') return 'failed'
71
+ return 'pending'
72
+ }
73
+ }
74
+
75
+ // Em produção
76
+ const gateway: PaymentGateway = new StripeAdapter(new Stripe(Deno.env.get('STRIPE_KEY')!))
77
+
78
+ // Handler — agora testável
79
+ async function handleCharge(req: Request, gateway: PaymentGateway) {
80
+ const order = await req.json()
81
+ const result = await gateway.charge({
82
+ amountCents: order.totalCents,
83
+ currency: order.currency,
84
+ cardToken: order.cardToken,
85
+ })
86
+ return new Response(JSON.stringify(result))
87
+ }
88
+
89
+ Deno.serve(req => handleCharge(req, gateway))
90
+
91
+ // Em teste
92
+ class FakePaymentGateway implements PaymentGateway {
93
+ charged: ChargeInput[] = []
94
+ result: ChargeResult = { id: 'ch_fake', status: 'succeeded' }
95
+ async charge(input: ChargeInput): Promise<ChargeResult> {
96
+ this.charged.push(input)
97
+ return this.result
98
+ }
99
+ }
100
+
101
+ test('handleCharge — typical input', async () => {
102
+ const gw = new FakePaymentGateway()
103
+ const req = new Request('http://x', {
104
+ method: 'POST',
105
+ body: JSON.stringify({ totalCents: 5000, currency: 'BRL', cardToken: 'tok_x' }),
106
+ })
107
+ await handleCharge(req, gw)
108
+ expect(gw.charged).toHaveLength(1)
109
+ expect(gw.charged[0].amountCents).toBe(5000)
110
+ })
111
+ ```
112
+
113
+ ### Pattern 2: Adapter para LLM provider (modernização total — sem precedente em 2004)
114
+
115
+ ```ts
116
+ // LLM provider como dependência testável (canônico em 2026)
117
+ interface LLMProvider {
118
+ generate(input: GenerateInput): Promise<GenerateResult>
119
+ }
120
+ type GenerateInput = {
121
+ prompt: string
122
+ maxTokens: number
123
+ temperature?: number
124
+ seed?: number // determinismo em testes
125
+ }
126
+ type GenerateResult = {
127
+ text: string
128
+ finishReason: 'stop' | 'length' | 'content_filter'
129
+ inputTokens: number
130
+ outputTokens: number
131
+ }
132
+
133
+ class OpenAIAdapter implements LLMProvider {
134
+ constructor(private client: OpenAI) {}
135
+ async generate(input: GenerateInput): Promise<GenerateResult> {
136
+ const r = await this.client.chat.completions.create({
137
+ model: 'gpt-4',
138
+ messages: [{ role: 'user', content: input.prompt }],
139
+ max_tokens: input.maxTokens,
140
+ temperature: input.temperature ?? 0,
141
+ seed: input.seed,
142
+ })
143
+ return {
144
+ text: r.choices[0].message.content ?? '',
145
+ finishReason: this.translateFinish(r.choices[0].finish_reason),
146
+ inputTokens: r.usage?.prompt_tokens ?? 0,
147
+ outputTokens: r.usage?.completion_tokens ?? 0,
148
+ }
149
+ }
150
+ private translateFinish(f: string): GenerateResult['finishReason'] {
151
+ if (f === 'stop') return 'stop'
152
+ if (f === 'length') return 'length'
153
+ return 'content_filter'
154
+ }
155
+ }
156
+
157
+ class AnthropicAdapter implements LLMProvider {
158
+ constructor(private client: Anthropic) {}
159
+ async generate(input: GenerateInput): Promise<GenerateResult> {
160
+ const r = await this.client.messages.create({
161
+ model: 'claude-opus-4-7',
162
+ messages: [{ role: 'user', content: input.prompt }],
163
+ max_tokens: input.maxTokens,
164
+ temperature: input.temperature ?? 0,
165
+ })
166
+ return {
167
+ text: r.content[0].type === 'text' ? r.content[0].text : '',
168
+ finishReason: this.translateStop(r.stop_reason),
169
+ inputTokens: r.usage.input_tokens,
170
+ outputTokens: r.usage.output_tokens,
171
+ }
172
+ }
173
+ private translateStop(s: string | null): GenerateResult['finishReason'] {
174
+ if (s === 'end_turn') return 'stop'
175
+ if (s === 'max_tokens') return 'length'
176
+ return 'content_filter'
177
+ }
178
+ }
179
+
180
+ class FakeLLMProvider implements LLMProvider {
181
+ responses: GenerateResult[] = []
182
+ callIndex = 0
183
+ async generate(_: GenerateInput): Promise<GenerateResult> {
184
+ if (this.callIndex < this.responses.length) return this.responses[this.callIndex++]
185
+ return { text: 'fake response', finishReason: 'stop', inputTokens: 10, outputTokens: 5 }
186
+ }
187
+ }
188
+ ```
189
+
190
+ **Insight:** sem essa abstração, edge function fica acoplada a 1 vendor. Trocar OpenAI → Anthropic = rewrite. Com adapter = trocar 1 linha (`new AnthropicAdapter(...)` em vez de `new OpenAIAdapter(...)`).
191
+
192
+ ### Pattern 3: Anti-corruption layer (DDD)
193
+
194
+ ```ts
195
+ // VENDOR types — bagunça típica (Stripe, OpenAI, Twilio têm shapes próprios)
196
+ type StripeChargeRaw = {
197
+ id: string
198
+ amount: number // cents
199
+ currency: string // lowercase ISO
200
+ status: 'succeeded' | 'pending' | 'failed' | 'canceled'
201
+ receipt_url?: string // snake_case do vendor
202
+ metadata?: Record<string, string>
203
+ }
204
+
205
+ // DOMAIN types — sua linguagem
206
+ type Charge = {
207
+ chargeId: string
208
+ amountCents: number
209
+ currencyIso4217: string // uppercase
210
+ status: ChargeStatus // domain enum, NÃO o do vendor
211
+ receiptUrl?: string
212
+ }
213
+ type ChargeStatus = 'succeeded' | 'failed' | 'pending' // simplificou; canceled vira failed
214
+
215
+ // Adapter ABSORVE diferenças — domain interno NÃO vê StripeChargeRaw
216
+ class StripeAdapter implements PaymentGateway {
217
+ async charge(input: ChargeInput): Promise<ChargeResult> {
218
+ const raw = await this.stripe.charges.create(...)
219
+ return this.toDomain(raw)
220
+ }
221
+ private toDomain(raw: StripeChargeRaw): Charge {
222
+ return {
223
+ chargeId: raw.id,
224
+ amountCents: raw.amount,
225
+ currencyIso4217: raw.currency.toUpperCase(),
226
+ status: raw.status === 'canceled' || raw.status === 'failed' ? 'failed' : raw.status,
227
+ receiptUrl: raw.receipt_url,
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ ### Pattern 4: Adapter aplicando cross-cutting concerns
234
+
235
+ Adapter é lugar canônico para retry, timeout, idempotency, instrumentation:
236
+
237
+ ```ts
238
+ class StripeAdapterResilient implements PaymentGateway {
239
+ constructor(private stripe: Stripe, private logger: Logger) {}
240
+
241
+ async charge(input: ChargeInput): Promise<ChargeResult> {
242
+ const idempotencyKey = await crypto.randomUUID()
243
+ const startMs = performance.now()
244
+
245
+ try {
246
+ const c = await retryWithJitter(
247
+ () => this.stripe.charges.create(
248
+ { amount: input.amountCents, currency: input.currency, source: input.cardToken },
249
+ { idempotencyKey, timeout: 5000 }
250
+ ),
251
+ { maxRetries: 3, baseMs: 250 }
252
+ )
253
+
254
+ const latency = performance.now() - startMs
255
+ this.logger.info('stripe.charge', { latency_ms: latency, status: c.status })
256
+ return this.toDomain(c)
257
+ } catch (e) {
258
+ this.logger.warn('stripe.charge.failed', { error: e.message })
259
+ throw e
260
+ }
261
+ }
262
+ }
263
+ ```
264
+
265
+ **Cross-suite:**
266
+ - Retry pattern de v1.11 (`retry-strategies`) aplicável aqui
267
+ - Logging segue v1.9 (`structured-events`)
268
+ - Latency histogram segue v1.10 (`four-golden-signals`)
269
+ - Adapter é exatamente onde "instrumentation shift-left" (v1.9 ODD) faz mais sentido
270
+
271
+ ### Pattern 5: Quando NÃO criar adapter
272
+
273
+ ```text
274
+ - Vendor SDK já tem interface mínima e estável (raríssimo)
275
+ - Edge function é one-shot script (não tem testes nem manutenção continuada)
276
+ - Spike/POC para validar viabilidade (descartável após decisão)
277
+ - Adapter custaria > 4h e prazo é < 1 dia (faça inline com warning de débito)
278
+ ```
279
+
280
+ ## Anti-patterns
281
+
282
+ ### ANTI: handler depende direto do SDK do vendor
283
+
284
+ ```text
285
+ ANTI: handler.ts: import Stripe from 'stripe'; ... stripe.charges.create(...)
286
+
287
+ PROBLEMA: handler intestável sem mocking SDK inteiro. Trocar vendor
288
+ = rewrite. Vendor SDK breaking change = bugs em handler.
289
+
290
+ CERTO: handler depende de interface mínima `PaymentGateway`. Adapter
291
+ absorve SDK. Testes do handler usam fake. Trocar vendor =
292
+ trocar 1 linha (constructor injection).
293
+ ```
294
+
295
+ ### ANTI: adapter expondo tipos do vendor
296
+
297
+ ```text
298
+ ANTI: interface PaymentGateway { charge(input): Promise<Stripe.Charge> }
299
+
300
+ PROBLEMA: Stripe.Charge atravessa camadas internas. Quando Stripe
301
+ renomeia field, refactor cascateia. Sem anti-corruption.
302
+
303
+ CERTO: interface tem TIPO próprio (ChargeResult). Adapter traduz.
304
+ Stripe.Charge fica encapsulado dentro do adapter.
305
+ ```
306
+
307
+ ### ANTI: 1 adapter por método do vendor
308
+
309
+ ```text
310
+ ANTI: StripeChargeAdapter, StripeRefundAdapter, StripePayoutAdapter,
311
+ StripeCustomerAdapter — 1 classe por endpoint.
312
+
313
+ PROBLEMA: explosão. 30 adapters para 1 vendor. Cross-cutting (retry,
314
+ logging) duplicado em cada um.
315
+
316
+ CERTO: 1 adapter por VENDOR + capability cluster. StripePaymentAdapter
317
+ (charge + refund), StripeCustomerAdapter (create + update).
318
+ Cross-cutting concerns aplicados consistente.
319
+ ```
320
+
321
+ ### ANTI: fake adapter testando o vendor real
322
+
323
+ ```text
324
+ ANTI: FakeStripeAdapter faz HTTP real para Stripe sandbox em testes.
325
+
326
+ PROBLEMA: testes lentos, flaky, dependentes de rede, custam $.
327
+ Sandbox vendor pode ter rate limits.
328
+
329
+ CERTO: FakeStripeAdapter implementa interface NÃO depende de Stripe.
330
+ Coleta inputs em array; retorna outputs canned. Test puramente
331
+ local. Fast, deterministic, free.
332
+ ```
333
+
334
+ ## Verificação
335
+
336
+ 1. Handler depende de interface, não de SDK do vendor diretamente
337
+ 2. Adapter implementa interface, encapsula SDK do vendor
338
+ 3. Tipos do vendor não atravessam adapter (anti-corruption)
339
+ 4. Fake adapter existe; tests do handler usam fake
340
+ 5. Adapter centraliza retry/timeout/idempotency/logging (cross-cutting)
341
+ 6. Tipos de DOMAIN são uppercase ISO/etc (não passam por convenção do vendor)
342
+ 7. Trocar vendor = trocar 1 linha (constructor)
343
+
344
+ ---
345
+
346
+ ## Ver também
347
+
348
+ - [`_shared-legacy/glossary.md`](../_shared-legacy/glossary.md) — vocabulário (adapter, anti-corruption layer)
349
+ - [`legacy-seams-and-test-harness`](../legacy-seams-and-test-harness/SKILL.md) — extract-interface é técnica do cap 25 que produz adapter
350
+ - [`legacy-characterization-tests`](../legacy-characterization-tests/SKILL.md) — characterize handler usando fake adapter (sem rede)
351
+ - [`supabase-edge-functions`](../supabase-edge-functions/SKILL.md) (v1.8) — Edge Functions são API-only paradigmáticas; adapter pattern aplicável
352
+ - [`supabase-edge-fn-writer`](../../agents/supabase-edge-fn-writer.md) (v1.8) — patch v1.12: adapter pattern como template default
353
+ - [`four-golden-signals`](../four-golden-signals/SKILL.md) (v1.10) — adapter é lugar canônico de instrumentation
354
+ - [`retry-strategies`](../retry-strategies/SKILL.md) (v1.11 — quando entregar) — retry pattern aplicado dentro do adapter
355
+ - [`llm-as-dependency`](../llm-as-dependency/SKILL.md) — caso especial de API-only para LLM providers
356
+
357
+ *Material-fonte: Working Effectively with Legacy Code — Feathers, 2004 — Cap 15: "My Application Is All API Calls".*
358
+ *Modernização (2026):* Supabase Edge Functions + LLM providers (OpenAI/Anthropic) como aplicação canônica do pattern.
@@ -0,0 +1,330 @@
1
+ ---
2
+ name: legacy-characterization-tests
3
+ description: Use ao refatorar código legado SEM testes prévios — characterization tests (cap 13 Feathers) capturam comportamento atual como golden snapshot, viram oracle imutável durante o refactor. Bloqueador para legacy refactor.
4
+ ---
5
+
6
+ # Legacy — Characterization Tests
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando o user vai modificar código sem suite de testes adequada e o objetivo é refactor (não bug fix). Trigger phrases:
11
+
12
+ - "refatorar [arquivo grande]", "extract method de", "quebrar essa classe"
13
+ - "esse arquivo não tem testes", "como começo testando isso?"
14
+ - "preservar comportamento", "snapshot test", "golden master"
15
+ - "cap 13 Feathers", "characterization test"
16
+ - "código legado", "legacy code", "edit and pray"
17
+ - arquivo > 500 linhas que será modificado
18
+ - arquivo com contrato externo (webhook, API, integração) sendo modificado
19
+
20
+ Carrega antes de planejar/executar refactor. **Bloqueia execução** até characterization existir.
21
+
22
+ ## Regras absolutas
23
+
24
+ - **Legacy code = código sem testes** (definição Feathers, não emocional). Idade não importa. Estética não importa. **Cobertura comportamental** importa.
25
+ - **Characterize first, refactor second.** Sempre. Sem exceção. Pular esse passo é "edit and pray" — o modo default que o livro existe para combater.
26
+ - **Capture o que o código FAZ, não o que DEVERIA fazer.** Se há bug, o teste preserva o bug. Bug fix é commit separado, depois do refactor, com seu próprio teste.
27
+ - **Mínimo de 5-10 inputs cobrindo grupos de equivalência** — null/vazio, válido típico, válido extremo, inválido recoverable, inválido fatal. Menos que isso = baseline frágil.
28
+ - **Behavioral coverage ≥ 70-80% antes de qualquer extract/move/rename**. Coverage % de linha NÃO É proxy de safety — verifique branches via mutation testing.
29
+ - **Golden master/snapshot é decisão, não copy-paste.** Leia output capturado linha por linha antes de salvar. Bugs conhecidos viram comentários inline (`// BUG #X: deveria Y, é Z`). PII/secrets/UUIDs locais → redact deterministic (hash, mask).
30
+ - **Vermelho em characterization test = regressão até prova ao contrário.** Nunca "atualize o expected" sem investigar e documentar a mudança comportamental no commit.
31
+ - **Bug fix dentro de refactor PR = veto.** Misturar invalida o oracle e torna PR não-revisável. Single-goal editing (cap 22) — uma intenção por commit.
32
+
33
+ ## Patterns canônicos
34
+
35
+ ### Pattern 1: Workflow de characterization (cap 13)
36
+
37
+ ```text
38
+ 1. Identificar o método/classe/arquivo a refatorar
39
+ 2. Inventariar entradas e saídas:
40
+ - Inputs: parâmetros + globals lidos + I/O (DB read, API call)
41
+ - Outputs: return + parâmetros mutados + I/O (DB write, log, API call)
42
+ 3. Para cada grupo de equivalência (5+ inputs):
43
+ a. Construir input ("arrange")
44
+ b. Executar código real ("act") — sem mocks ainda; isole I/O com seam mínimo se necessário
45
+ c. Capturar output completo ("snapshot")
46
+ d. REVISAR output linha por linha — marcar bugs conhecidos como comments
47
+ e. Salvar como `expected.txt` ou `__snapshots__/foo.test.ts.snap`
48
+ 4. Escrever teste:
49
+ - Arrange = mesmo input
50
+ - Act = mesmo código
51
+ - Assert = output igual ao salvo (deep equal OR snapshot match)
52
+ 5. Rodar suite — TODOS verdes → BASELINE estabelecido
53
+ 6. Refactor pode começar
54
+ ```
55
+
56
+ ### Pattern 2: Grupos de equivalência canônicos
57
+
58
+ Cobertura mínima — pelo menos 1 caso por grupo:
59
+
60
+ | Grupo | Definição | Exemplo (função `parseOrder(input)`) |
61
+ |---|---|---|
62
+ | **Empty** | Input ausente/zero/vazio | `parseOrder(null)`, `parseOrder({})` |
63
+ | **Typical valid** | Caso comum esperado | `parseOrder({ id: 'O123', items: [...] })` |
64
+ | **Boundary valid** | Limites superiores/inferiores válidos | `parseOrder({ ..., items: [singleItem] })`, `parseOrder({ ..., items: [maxItems_x_50] })` |
65
+ | **Recoverable invalid** | Erro que código trata graceful | `parseOrder({ id: 'O123', items: 'malformed' })` — espera-se exceção tipada |
66
+ | **Fatal invalid** | Erro que código não trata (vai propagar/crashar) | `parseOrder(undefined)` — espera-se NPE/crash |
67
+ | **Side-effect heavy** | Input que dispara muitos side effects (logs, DB writes) | Ordem grande que escreve em audit log + cache + queue |
68
+ | **Edge case histórico** | Cases conhecidos que já causaram bugs (consultar git log/issues) | Input com encoding UTF-16, timestamp negativo |
69
+
70
+ ### Pattern 3: Snapshot tooling por linguagem
71
+
72
+ | Linguagem | Framework | Snapshot syntax |
73
+ |---|---|---|
74
+ | **JavaScript/TypeScript** | Jest, Vitest | `expect(output).toMatchSnapshot()` ou `toMatchInlineSnapshot()` |
75
+ | **Python** | pytest + pytest-snapshot OR syrupy | `snapshot.assert_match(output)` ou `assert output == snapshot` |
76
+ | **Java** | JUnit + ApprovalTests | `Approvals.verify(output)` |
77
+ | **Ruby** | RSpec + rspec-snapshot | `expect(output).to match_snapshot('foo_bar')` |
78
+ | **Go** | go-cmp + cupaloy/snaps | `cupaloy.SnapshotT(t, output)` |
79
+ | **C#** | Verify, Snapshooter | `await Verifier.Verify(output)` |
80
+ | **Rust** | insta | `insta::assert_yaml_snapshot!(output)` |
81
+
82
+ **Anti-tooling:** evitar diff visual cru (eyeballed) — snapshot framework gera diff legível e atualiza expected via flag (`--updateSnapshot` no Jest, `--snapshot-update` em pytest). Sem framework, refactor de "atualizar oracle" vira manual e propenso a erro.
83
+
84
+ ### Pattern 4: Captura de outputs com side effects
85
+
86
+ Quando código tem side effects (DB writes, HTTP calls, logs), o snapshot deve incluir **todos** os efeitos observáveis, não só return. Estratégia:
87
+
88
+ ```ts
89
+ // PT-BR: capturar return + lista canônica de efeitos
90
+ async function characterize_placeOrder() {
91
+ const sideEffects = {
92
+ dbWrites: [] as Array<{ table: string, op: string, row: any }>,
93
+ httpCalls: [] as Array<{ url: string, method: string, body: any }>,
94
+ logs: [] as Array<{ level: string, msg: string, fields: any }>,
95
+ queueMsgs: [] as Array<{ queue: string, payload: any }>,
96
+ }
97
+
98
+ // Wire fakes que populam sideEffects ao invés de fazer real I/O
99
+ const db = makeFakeDb(sideEffects.dbWrites)
100
+ const http = makeFakeHttp(sideEffects.httpCalls)
101
+ const log = makeFakeLogger(sideEffects.logs)
102
+ const queue = makeFakeQueue(sideEffects.queueMsgs)
103
+
104
+ const input = { customerId: 'C-42', items: [{ sku: 'SKU-1', qty: 2 }] }
105
+ const result = await placeOrder(input, { db, http, log, queue })
106
+
107
+ return {
108
+ return: result,
109
+ sideEffects,
110
+ }
111
+ // ↑ ESSE objeto é o que vira snapshot
112
+ }
113
+
114
+ // Test
115
+ test('placeOrder — typical valid input', async () => {
116
+ const captured = await characterize_placeOrder()
117
+ expect(captured).toMatchSnapshot()
118
+ })
119
+ ```
120
+
121
+ Snapshot resultante captura return E efeitos, ambos congelados.
122
+
123
+ ### Pattern 5: Determinismo — eliminar non-determinism antes de capturar
124
+
125
+ Datas, UUIDs, random, nanos — todos não-determinísticos por default. Capture-os como dependência injetada:
126
+
127
+ ```ts
128
+ // PT-BR: dependências injetadas tornam snapshot reproduzível
129
+ const fakeClock = () => new Date('2024-01-15T10:00:00Z') // congelado
130
+ const fakeUuid = (() => { let n = 0; return () => `uuid-${++n}` })() // determinístico
131
+ const fakeRandom = (() => { let n = 0; return () => (n++ % 1000) / 1000 })() // ciclico
132
+
133
+ const result = await placeOrder(input, {
134
+ ...realDeps,
135
+ clock: fakeClock,
136
+ uuidGen: fakeUuid,
137
+ random: fakeRandom,
138
+ })
139
+ ```
140
+
141
+ Sem isso, cada run produz snapshot diferente → "flaky tests" → ninguém confia → suite ignorada.
142
+
143
+ ### Pattern 6: Sanitização para snapshot
144
+
145
+ Output cru pode incluir dados sensíveis ou voláteis. Sanitize ANTES de salvar:
146
+
147
+ ```ts
148
+ function sanitizeForSnapshot(o: any): any {
149
+ return JSON.parse(
150
+ JSON.stringify(o, (key, value) => {
151
+ if (key === 'apiKey' || key === 'password' || key === 'token') return '***REDACTED***'
152
+ if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) return '<TIMESTAMP>'
153
+ if (typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}/.test(value)) return '<UUID>'
154
+ return value
155
+ })
156
+ )
157
+ }
158
+ ```
159
+
160
+ Aplicar **antes** de `expect(...).toMatchSnapshot()`. Documentar quais campos foram sanitized para que reviewer entenda.
161
+
162
+ ### Pattern 7: Behavioral coverage check (mutation testing)
163
+
164
+ Coverage de linha NÃO É proxy de safety. Para confirmar que characterization realmente cobre comportamento:
165
+
166
+ ```bash
167
+ # JavaScript/TypeScript
168
+ npx stryker run
169
+
170
+ # Python
171
+ mutmut run
172
+ mutmut results
173
+
174
+ # Java
175
+ mvn pitest:mutationCoverage
176
+
177
+ # Métrica desejada: ≥ 70% de mutants killed
178
+ # Survived mutants = comportamento NÃO observado pelos tests = ponto cego
179
+ ```
180
+
181
+ Survived mutant tipicamente indica que falta um observation point. Adicione um test que exercita o branch correspondente.
182
+
183
+ ### Pattern 8: Effort budget para characterization
184
+
185
+ Dados empíricos baseados em arquivos típicos:
186
+
187
+ | Tamanho do alvo | Inputs a gerar | Esforço típico | Cobertura esperada |
188
+ |---|---|---|---|
189
+ | Método 20-50 linhas, 1-3 branches | 5-7 inputs | 1-2h | 80-90% behavioral |
190
+ | Método 50-150 linhas, 3-7 branches | 8-12 inputs | 3-6h | 70-85% behavioral |
191
+ | Método 150+ linhas (monster) | 15-25 inputs | 1-3 dias | 60-75% behavioral (exigir cap 22 antes) |
192
+ | Classe inteira 300-500 linhas | 20-40 inputs | 2-5 dias | 65-80% behavioral |
193
+ | Arquivo > 500 linhas | proibido refatorar sem split first | depende | exigir extract class antes |
194
+
195
+ **Não negocie cobertura para baixo "para ganhar tempo".** Cobertura insuficiente = false sense of safety, pior que ausência total.
196
+
197
+ ## Anti-patterns
198
+
199
+ ### ANTI: testar o "comportamento esperado"
200
+
201
+ ```text
202
+ ANTI: "Vou escrever um teste do que o método deveria fazer e refatorar
203
+ até passar".
204
+
205
+ PROBLEMA: o método tem bugs. Teste-do-esperado falha imediato porque o
206
+ estado atual É buggy. Você não consegue rodar nem 1 verde.
207
+ Frustrado, "ajusta" expected para o atual — perdeu o ponto
208
+ inteiro do exercício.
209
+
210
+ CERTO: characterize first. Capture o que o código faz HOJE, com bugs.
211
+ Refactor preserva isso. Bug fix vem depois, em commit separado.
212
+ ```
213
+
214
+ ### ANTI: 1 teste cobrindo "happy path"
215
+
216
+ ```text
217
+ ANTI: "Adicionei 1 test do caso comum, vai dar".
218
+
219
+ PROBLEMA: branches raras (null, vazio, edge case) são exatamente onde
220
+ regressão se esconde. Refactor "verde" no test de happy path
221
+ mas quebra null handling silencioso → bug em prod no primeiro
222
+ input null real (1% do tráfego, mas existe).
223
+
224
+ CERTO: 5+ inputs cobrindo grupos canônicos de equivalência. 1h a mais
225
+ de teste = N horas a menos de incident.
226
+ ```
227
+
228
+ ### ANTI: snapshot sem revisão
229
+
230
+ ```text
231
+ ANTI: rodar code → toMatchSnapshot() → CI verde → commit. "Funcionou".
232
+
233
+ PROBLEMA: snapshot pode incluir bug, PII, secret, UUID local. CI
234
+ "verde" só significa "snapshot está consistente com captura
235
+ anterior" — não que o conteúdo está certo.
236
+
237
+ CERTO: ler snapshot inteiro antes de commit. Marcar bugs com comments,
238
+ redact PII com sanitize fn, verificar que não há secrets. Commit
239
+ de snapshot é decisão de produto, não automation.
240
+ ```
241
+
242
+ ### ANTI: mocks excessivos = teste de mock, não de código
243
+
244
+ ```text
245
+ ANTI: tudo mockado — DB, HTTP, log, queue, clock, random. Test passa.
246
+
247
+ PROBLEMA: você testou que o método chama os mocks na ordem certa, não
248
+ que o método produz output correto para entrada real. Refactor
249
+ que muda ORDEM de chamadas (igualmente correto) quebra mock
250
+ assertion mas é regressão zero.
251
+
252
+ CERTO: minimize mocks. Use fakes que coletam side effects observáveis
253
+ (lista, counter), assert sobre o STATE final dos fakes, não
254
+ sobre sequência de invocações. Snapshot do state pós-execução
255
+ é mais resiliente que assertion de invocation order.
256
+ ```
257
+
258
+ ### ANTI: pular characterization "porque o método é simples"
259
+
260
+ ```text
261
+ ANTI: "esse método tem 30 linhas, é óbvio o que faz, vou refatorar
262
+ direto".
263
+
264
+ PROBLEMA: 30 linhas têm ~5-10 branches implícitas (early return, &&
265
+ short-circuit, exceções, type coercion). Cada branch é uma
266
+ assumption não-verificada. "Óbvio" é ilusão de quem escreveu
267
+ o código original — você está lendo, é diferente.
268
+
269
+ CERTO: SEMPRE characterize, mesmo métodos curtos. 30 linhas → 5 inputs
270
+ → 30 minutos. Custo trivial. Benefício: zero "wait, eu não sabia
271
+ que isso retornava undefined em X". Descobre-se durante captura,
272
+ não em prod.
273
+ ```
274
+
275
+ ### ANTI: characterization em fase de bug fix
276
+
277
+ ```text
278
+ ANTI: "Estou consertando bug X, vou aproveitar e characterize tudo
279
+ enquanto estou aqui".
280
+
281
+ PROBLEMA: scope creep. PR vira inrevisável (bug fix + 50 testes novos
282
+ + redesenho mental). Linha entre "preservei comportamento" e
283
+ "modifiquei comportamento" desaparece.
284
+
285
+ CERTO: bug fix é bug fix. Escreva 1 teste do COMPORTAMENTO CORRETO
286
+ (TDD agora, porque você está mudando intenção). Characterize é
287
+ fase prévia ao refactor — separa em PR/sprint próprio.
288
+ ```
289
+
290
+ ## Verificação
291
+
292
+ Antes de iniciar refactor de código legado:
293
+
294
+ 1. **Inventário completo de inputs/outputs** — todos os parâmetros, globals lidos, I/O capturados
295
+ 2. **5+ inputs cobrindo grupos de equivalência** — empty, typical, boundary, invalid recoverable, invalid fatal
296
+ 3. **Snapshots revisados linha por linha** — bugs marcados, PII/secrets redacted
297
+ 4. **Determinismo garantido** — clock/uuid/random injetáveis, fakes substituem em teste
298
+ 5. **Side effects capturados** — DB writes, HTTP calls, logs, queue msgs incluídos no snapshot
299
+ 6. **Suite verde** — todos characterization tests rodam OK no main branch
300
+ 7. **Behavioral coverage medida** — mutation testing rodado, ≥ 70% mutants killed
301
+ 8. **Documentação no PR** — link para snapshots, lista de bugs preservados, fonte do oracle
302
+
303
+ ## Limiar de "pronto para refactor"
304
+
305
+ ```text
306
+ Total inputs cobertos: ≥ 5 (mínimo); 10+ recomendado
307
+ Behavioral coverage (mutation kill): ≥ 70%
308
+ Branches conhecidas testadas: 100% (todas as branches do código que será tocado)
309
+ Side effects capturados: 100% (zero side effect "esquecido")
310
+ Snapshots revisados: 100% (cada arquivo lido por humano)
311
+ Bugs documentados como TODO: lista no PR
312
+ Determinismo: OK em 10 runs consecutivos sem flaky
313
+ ```
314
+
315
+ Se algum item < limiar → não inicie refactor. Volte para characterization.
316
+
317
+ ---
318
+
319
+ ## Ver também
320
+
321
+ - [`_shared-legacy/glossary.md`](../_shared-legacy/glossary.md) — vocabulário canônico
322
+ - [`legacy-seams-and-test-harness`](../legacy-seams-and-test-harness/SKILL.md) — quando characterization requer quebrar dependência primeiro
323
+ - [`legacy-effect-analysis`](../legacy-effect-analysis/SKILL.md) — quais inputs escolher? effect sketch identifica
324
+ - [`legacy-monster-methods`](../legacy-monster-methods/SKILL.md) — método > 100 linhas? characterization tem trato especial
325
+ - [`legacy-sprout-wrap-techniques`](../legacy-sprout-wrap-techniques/SKILL.md) — alternativa quando characterization é caro demais (sprout side-steps)
326
+ - [`pre-refactor-characterization`](../pre-refactor-characterization/SKILL.md) — gate auto-trigger que bloqueia refactor sem characterization
327
+ - [`event-based-slos`](../event-based-slos/SKILL.md) (v1.9) — refactor pode regredir SLO; characterization protege
328
+ - [`production-readiness-review`](../production-readiness-review/SKILL.md) (v1.10) — PRR Axe 5 (Change Management) verifica characterization antes de aceitar mudança em prod
329
+
330
+ *Material-fonte: Working Effectively with Legacy Code — Feathers, 2004 — Cap 13: "I Need to Make a Change, But I Don't Know What Tests to Write" + Cap 23: "How Do I Know That I'm Not Breaking Anything?".*