spec-first-copilot 0.6.0-beta.9 → 0.7.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 (57) hide show
  1. package/README.md +252 -167
  2. package/bin/cli.js +70 -70
  3. package/lib/init.js +92 -92
  4. package/lib/update.js +132 -132
  5. package/package.json +1 -1
  6. package/templates/.ai/memory/napkin.md +68 -68
  7. package/templates/.github/CHANGELOG.md +121 -0
  8. package/templates/.github/adapters/SETUP.md +314 -314
  9. package/templates/.github/adapters/confluence.md +295 -295
  10. package/templates/.github/adapters/errors.md +234 -234
  11. package/templates/.github/adapters/filesystem.md +353 -353
  12. package/templates/.github/adapters/interface.md +301 -301
  13. package/templates/.github/adapters/naming.md +241 -241
  14. package/templates/.github/adapters/registry.md +244 -244
  15. package/templates/.github/agents/backend-coder.md +14 -14
  16. package/templates/.github/agents/db-coder.md +165 -165
  17. package/templates/.github/agents/doc-writer.md +66 -53
  18. package/templates/.github/agents/frontend-coder.md +5 -5
  19. package/templates/.github/agents/infra-coder.md +341 -341
  20. package/templates/.github/agents/reviewer.md +6 -6
  21. package/templates/.github/agents/security-reviewer.md +153 -153
  22. package/templates/.github/copilot-instructions.md +272 -262
  23. package/templates/.github/instructions/docs.instructions.md +147 -145
  24. package/templates/.github/instructions/sensitive-files.instructions.md +32 -32
  25. package/templates/.github/rules.md +229 -229
  26. package/templates/.github/scripts/bootstrap-confluence.js +289 -223
  27. package/templates/.github/skills/sf-design/SKILL.md +161 -216
  28. package/templates/.github/skills/sf-dev/SKILL.md +204 -351
  29. package/templates/.github/skills/sf-discovery/SKILL.md +415 -414
  30. package/templates/.github/skills/sf-extract/SKILL.md +225 -249
  31. package/templates/.github/skills/sf-load/SKILL.md +296 -295
  32. package/templates/.github/skills/sf-mcp/SKILL.md +386 -385
  33. package/templates/.github/skills/sf-merge-docs/SKILL.md +152 -100
  34. package/templates/.github/skills/sf-plan/SKILL.md +152 -128
  35. package/templates/.github/skills/sf-publish/SKILL.md +144 -143
  36. package/templates/.github/skills/sf-session-finish/SKILL.md +93 -120
  37. package/templates/.github/skills/sf-start/SKILL.md +192 -145
  38. package/templates/.github/templates/estrutura/apiContracts.template.md +160 -159
  39. package/templates/.github/templates/estrutura/architecture.template.md +169 -168
  40. package/templates/.github/templates/estrutura/conventions.template.md +214 -212
  41. package/templates/.github/templates/estrutura/decisions.template.md +107 -107
  42. package/templates/.github/templates/estrutura/domain.template.md +161 -160
  43. package/templates/.github/templates/feature/PRD.template.md +279 -286
  44. package/templates/.github/templates/feature/Progresso.template.md +141 -141
  45. package/templates/.github/templates/feature/TRD.template.md +358 -0
  46. package/templates/.github/templates/feature/context.template.md +89 -48
  47. package/templates/.github/templates/feature/extract-log.template.md +49 -39
  48. package/templates/.github/templates/feature/projetos.template.yaml +79 -79
  49. package/templates/.github/templates/global/progresso_global.template.md +59 -57
  50. package/templates/.github/templates/specs/brief.template.md +66 -59
  51. package/templates/.github/templates/specs/contracts.template.md +147 -141
  52. package/templates/.github/templates/specs/scenarios.template.md +125 -117
  53. package/templates/.github/templates/specs/tasks.template.md +65 -63
  54. package/templates/_gitignore +35 -35
  55. package/templates/sfw.config.yml.example +147 -147
  56. package/templates/.github/templates/feature/backlog-extraido.template.md +0 -156
  57. package/templates/.github/templates/feature/sdd.template.md +0 -559
@@ -1,234 +1,234 @@
1
- # SourceAdapter — Contrato de Erros
2
-
3
- > Classes de erro tipadas que qualquer adapter **deve** lançar em vez de
4
- > `throw new Error(...)`. As skills (`/sf-load`, `/sf-publish`, `/sf-start`,
5
- > `/sf-extract`, `/sf-design`, `/sf-plan`) fazem `catch` tipado e decidem o que fazer.
6
- >
7
- > Arquivo é referência conceitual, não runtime. A implementação real vive no
8
- > adapter específico (`.github/adapters/confluence.md` etc.).
9
-
10
- ---
11
-
12
- ## Por que erros tipados
13
-
14
- 1. **Skills reagem diferente por categoria.** Um `ConflictError` é recuperável
15
- (pede ao usuário), um `AuthError` é fatal (para tudo e orienta setup).
16
- Genérico obriga a parsear mensagem — frágil.
17
- 2. **Logs ficam úteis.** `.ai/load-log.md` e `.ai/publish-log.md` registram
18
- o tipo do erro, não a mensagem livre. Re-execução pode filtrar por tipo.
19
- 3. **Testes ficam honestos.** FilesystemAdapter (mock natural) lança os mesmos
20
- tipos — skills testadas contra ele ganham cobertura real.
21
-
22
- ---
23
-
24
- ## Hierarquia
25
-
26
- ```
27
- SourceAdapterError (base — nunca lançado direto)
28
- ├── ValidationError (config do sfw.config.yml inválido)
29
- ├── AuthError (credenciais inválidas ou ausentes)
30
- ├── NotFoundError (itemId / containerId inexistente)
31
- ├── ConflictError (version drift, título duplicado)
32
- ├── TransportError (rede, MCP down, timeout)
33
- └── NotImplementedError (método opcional não implementado)
34
- ```
35
-
36
- Todos os erros **devem** carregar:
37
- - `name` — nome da classe (ex: `"ConflictError"`)
38
- - `adapter` — nome do adapter que lançou (ex: `"confluence"`)
39
- - `operation` — método que falhou (ex: `"update"`)
40
- - `message` — descrição humana
41
- - `cause` opcional — erro original (MCP, fs, fetch) pra debug
42
-
43
- ---
44
-
45
- ## Catálogo completo
46
-
47
- ### `ValidationError`
48
-
49
- **Quando**: `validateConfig(config)` detecta campo ausente, tipo errado, ou
50
- combinação inválida (ex: `mode: auto` sem `approval_mechanism` quando o target
51
- requer aprovação).
52
-
53
- **Campos extras**:
54
- - `field` — caminho do campo inválido (ex: `"output.targets[0].publishes"`)
55
- - `expected` — o que era esperado
56
- - `got` — o que veio
57
-
58
- **Como o caller reage**: para imediatamente. Nunca é recuperável em runtime —
59
- o usuário precisa arrumar o `sfw.config.yml` e re-rodar.
60
-
61
- **Exemplo**:
62
- ```
63
- ValidationError em confluence.validateConfig:
64
- field: input.config.space_key
65
- expected: string não-vazio
66
- got: undefined
67
- message: space_key obrigatório pro ConfluenceAdapter
68
- ```
69
-
70
- ---
71
-
72
- ### `AuthError`
73
-
74
- **Quando**: backend rejeita credenciais (401, 403, token expirado, MCP retorna
75
- auth failure). Também lançado se credenciais nem foram encontradas (`.mcp.json`
76
- ausente, env var não setada).
77
-
78
- **Campos extras**:
79
- - `hint` opcional — pista acionável (ex: `"verifique CONFLUENCE_TOKEN em .mcp.json"`)
80
-
81
- **Como o caller reage**: para imediatamente. Mostra `hint` pro usuário. Nunca
82
- tenta retry — credenciais não se consertam sozinhas.
83
-
84
- **Importante**: adapters nunca devem vazar o token no `message` ou `cause`.
85
- Se o erro original tem o token no URL, o adapter faz sanitize antes de embrulhar.
86
-
87
- ---
88
-
89
- ### `NotFoundError`
90
-
91
- **Quando**: operação aponta pra um `itemId` / `containerId` que não existe ou
92
- foi deletado. Inclui: `fetchContent`, `getVersion`, `update`, `listChildren`,
93
- `fetchAttachments` em um ID inválido.
94
-
95
- **Campos extras**:
96
- - `itemId` — o ID que não foi encontrado
97
- - `kind` — `"container"` | `"item"` | `"attachment"`
98
-
99
- **Como o caller reage**:
100
- - `/sf-load`: pode ser scope removido no backend. Loga e pula (não fatal).
101
- - `/sf-publish`: se título existia no publish-log mas sumiu do backend → o caller
102
- DEVE chamar `create` em vez de `update` (fallback implícito).
103
- - `/sf-start`: se parent root não existe → fatal (config errada).
104
-
105
- ---
106
-
107
- ### `ConflictError`
108
-
109
- **Quando**:
110
- - `create` encontra título duplicado no parent (ou no space, no Confluence).
111
- - `update` detecta `expectedVersion != version atual` (drift — alguém editou
112
- entre o último `getVersion` e o `update`).
113
-
114
- **Campos extras**:
115
- - `expectedVersion` — o que o caller achava que estava lá
116
- - `actualVersion` — o que o backend tem de fato
117
- - `itemId` — item que conflitou
118
- - `kind` — `"duplicate_title"` | `"version_drift"`
119
-
120
- **Como o caller reage**:
121
- - `duplicate_title` (no `create`): normalmente fatal em MVP — skills assumem
122
- naming unique via template com `{scope}` no título. Se bateu, dois
123
- scopes têm o mesmo nome — o user precisa renomear no Input.
124
- - `version_drift` (no `update`): **recuperável**. Fluxo:
125
- 1. `/sf-publish` para a operação atual
126
- 2. Baixa content remoto via `fetchContent`
127
- 3. Gera diff local-vs-remoto
128
- 4. Pergunta ao usuário: **sobrescrever, abortar, ou fazer merge manual**
129
- 5. Se sobrescrever → novo `update` com `expectedVersion` atualizada
130
-
131
- **Nunca retry automático** — drift é sinal de que um humano tocou o artefato
132
- (aprovação manual, edição pós-review). Requer decisão consciente.
133
-
134
- ---
135
-
136
- ### `TransportError`
137
-
138
- **Quando**: rede caiu, MCP morreu, timeout, 5xx do backend.
139
-
140
- **Campos extras**:
141
- - `retryable` — `true` se faz sentido tentar de novo (5xx, timeout)
142
- - `statusCode` opcional — se o backend deu HTTP
143
-
144
- **Como o caller reage**:
145
- - `retryable=true`: retry com backoff (proposta: 3 tentativas, 1s / 3s / 10s).
146
- Depois disso, escala pro usuário.
147
- - `retryable=false`: escala imediato.
148
-
149
- **Importante**: distinguir `TransportError` de `AuthError` é crítico — 401 **não**
150
- é transport, é auth. Adapter precisa classificar antes de embrulhar.
151
-
152
- ---
153
-
154
- ### `NotImplementedError`
155
-
156
- **Quando**: skill chama método opcional (`attachFile`) num adapter que não
157
- implementa.
158
-
159
- **Como o caller reage**: skill **deve** ter fallback ou skipar feature. Nunca
160
- é fatal — é esperado em MVP (FilesystemAdapter sem attachments na v0, por ex).
161
-
162
- Caller típico:
163
- ```
164
- try {
165
- await adapter.attachFile(id, name, buf, mime);
166
- } catch (e) {
167
- if (e instanceof NotImplementedError) {
168
- log("adapter ${adapter.name} não suporta attachments, pulando");
169
- return;
170
- }
171
- throw e;
172
- }
173
- ```
174
-
175
- ---
176
-
177
- ## Quem lança o quê (matriz)
178
-
179
- | Método | ValidationError | AuthError | NotFoundError | ConflictError | TransportError | NotImplementedError |
180
- |--------|:---:|:---:|:---:|:---:|:---:|:---:|
181
- | `validateConfig` | ✅ | — | — | — | — | — |
182
- | `listChildren` | — | ✅ | ✅ | — | ✅ | — |
183
- | `fetchContent` | — | ✅ | ✅ | — | ✅ | — |
184
- | `fetchAttachments` | — | ✅ | ✅ | — | ✅ | — |
185
- | `getVersion` | — | ✅ | ✅ | — | ✅ | — |
186
- | `create` | — | ✅ | ✅¹ | ✅² | ✅ | — |
187
- | `update` | — | ✅ | ✅ | ✅³ | ✅ | — |
188
- | `attachFile` | — | ✅ | ✅ | — | ✅ | ✅⁴ |
189
-
190
- **Notas**:
191
- - ¹ `NotFoundError` no `create` quando o `parentId` não existe.
192
- - ² `ConflictError` no `create` = `kind: "duplicate_title"`.
193
- - ³ `ConflictError` no `update` = `kind: "version_drift"`.
194
- - ⁴ `NotImplementedError` apenas em adapters que não suportam attachments.
195
-
196
- ---
197
-
198
- ## Padrão de implementação (pseudo-código)
199
-
200
- Todo adapter embrulha erros nativos nesses tipos. Exemplo genérico:
201
-
202
- ```typescript
203
- async update(itemId, content, expectedVersion) {
204
- try {
205
- const current = await this.getVersion(itemId);
206
- if (current !== expectedVersion) {
207
- throw new ConflictError({
208
- adapter: this.name,
209
- operation: "update",
210
- kind: "version_drift",
211
- itemId,
212
- expectedVersion,
213
- actualVersion: current,
214
- });
215
- }
216
- const result = await this.backendUpdate(itemId, content);
217
- return { version: result.version, ok: true };
218
- } catch (e) {
219
- if (e instanceof ConflictError) throw e;
220
- if (isNotFound(e)) throw new NotFoundError({ adapter: this.name, operation: "update", itemId, kind: "item", cause: e });
221
- if (isAuth(e)) throw new AuthError( { adapter: this.name, operation: "update", cause: e });
222
- if (isTransport(e)) throw new TransportError({ adapter: this.name, operation: "update", retryable: e.status >= 500, cause: e });
223
- throw e; // unknown — propaga cru
224
- }
225
- }
226
- ```
227
-
228
- ---
229
-
230
- ## Referências
231
-
232
- - Interface: `.github/adapters/interface.md`
233
- - Registry: `.github/adapters/registry.md`
234
- - Naming: `.github/adapters/naming.md`
1
+ # SourceAdapter — Contrato de Erros
2
+
3
+ > Classes de erro tipadas que qualquer adapter **deve** lançar em vez de
4
+ > `throw new Error(...)`. As skills (`/sf-load`, `/sf-publish`, `/sf-start`,
5
+ > `/sf-extract`, `/sf-design`, `/sf-plan`) fazem `catch` tipado e decidem o que fazer.
6
+ >
7
+ > Arquivo é referência conceitual, não runtime. A implementação real vive no
8
+ > adapter específico (`.github/adapters/confluence.md` etc.).
9
+
10
+ ---
11
+
12
+ ## Por que erros tipados
13
+
14
+ 1. **Skills reagem diferente por categoria.** Um `ConflictError` é recuperável
15
+ (pede ao usuário), um `AuthError` é fatal (para tudo e orienta setup).
16
+ Genérico obriga a parsear mensagem — frágil.
17
+ 2. **Logs ficam úteis.** `.ai/load-log.md` e `.ai/publish-log.md` registram
18
+ o tipo do erro, não a mensagem livre. Re-execução pode filtrar por tipo.
19
+ 3. **Testes ficam honestos.** FilesystemAdapter (mock natural) lança os mesmos
20
+ tipos — skills testadas contra ele ganham cobertura real.
21
+
22
+ ---
23
+
24
+ ## Hierarquia
25
+
26
+ ```
27
+ SourceAdapterError (base — nunca lançado direto)
28
+ ├── ValidationError (config do sfw.config.yml inválido)
29
+ ├── AuthError (credenciais inválidas ou ausentes)
30
+ ├── NotFoundError (itemId / containerId inexistente)
31
+ ├── ConflictError (version drift, título duplicado)
32
+ ├── TransportError (rede, MCP down, timeout)
33
+ └── NotImplementedError (método opcional não implementado)
34
+ ```
35
+
36
+ Todos os erros **devem** carregar:
37
+ - `name` — nome da classe (ex: `"ConflictError"`)
38
+ - `adapter` — nome do adapter que lançou (ex: `"confluence"`)
39
+ - `operation` — método que falhou (ex: `"update"`)
40
+ - `message` — descrição humana
41
+ - `cause` opcional — erro original (MCP, fs, fetch) pra debug
42
+
43
+ ---
44
+
45
+ ## Catálogo completo
46
+
47
+ ### `ValidationError`
48
+
49
+ **Quando**: `validateConfig(config)` detecta campo ausente, tipo errado, ou
50
+ combinação inválida (ex: `mode: auto` sem `approval_mechanism` quando o target
51
+ requer aprovação).
52
+
53
+ **Campos extras**:
54
+ - `field` — caminho do campo inválido (ex: `"output.targets[0].publishes"`)
55
+ - `expected` — o que era esperado
56
+ - `got` — o que veio
57
+
58
+ **Como o caller reage**: para imediatamente. Nunca é recuperável em runtime —
59
+ o usuário precisa arrumar o `sfw.config.yml` e re-rodar.
60
+
61
+ **Exemplo**:
62
+ ```
63
+ ValidationError em confluence.validateConfig:
64
+ field: input.config.space_key
65
+ expected: string não-vazio
66
+ got: undefined
67
+ message: space_key obrigatório pro ConfluenceAdapter
68
+ ```
69
+
70
+ ---
71
+
72
+ ### `AuthError`
73
+
74
+ **Quando**: backend rejeita credenciais (401, 403, token expirado, MCP retorna
75
+ auth failure). Também lançado se credenciais nem foram encontradas (`.mcp.json`
76
+ ausente, env var não setada).
77
+
78
+ **Campos extras**:
79
+ - `hint` opcional — pista acionável (ex: `"verifique CONFLUENCE_TOKEN em .mcp.json"`)
80
+
81
+ **Como o caller reage**: para imediatamente. Mostra `hint` pro usuário. Nunca
82
+ tenta retry — credenciais não se consertam sozinhas.
83
+
84
+ **Importante**: adapters nunca devem vazar o token no `message` ou `cause`.
85
+ Se o erro original tem o token no URL, o adapter faz sanitize antes de embrulhar.
86
+
87
+ ---
88
+
89
+ ### `NotFoundError`
90
+
91
+ **Quando**: operação aponta pra um `itemId` / `containerId` que não existe ou
92
+ foi deletado. Inclui: `fetchContent`, `getVersion`, `update`, `listChildren`,
93
+ `fetchAttachments` em um ID inválido.
94
+
95
+ **Campos extras**:
96
+ - `itemId` — o ID que não foi encontrado
97
+ - `kind` — `"container"` | `"item"` | `"attachment"`
98
+
99
+ **Como o caller reage**:
100
+ - `/sf-load`: pode ser scope removido no backend. Loga e pula (não fatal).
101
+ - `/sf-publish`: se título existia no publish-log mas sumiu do backend → o caller
102
+ DEVE chamar `create` em vez de `update` (fallback implícito).
103
+ - `/sf-start`: se parent root não existe → fatal (config errada).
104
+
105
+ ---
106
+
107
+ ### `ConflictError`
108
+
109
+ **Quando**:
110
+ - `create` encontra título duplicado no parent (ou no space, no Confluence).
111
+ - `update` detecta `expectedVersion != version atual` (drift — alguém editou
112
+ entre o último `getVersion` e o `update`).
113
+
114
+ **Campos extras**:
115
+ - `expectedVersion` — o que o caller achava que estava lá
116
+ - `actualVersion` — o que o backend tem de fato
117
+ - `itemId` — item que conflitou
118
+ - `kind` — `"duplicate_title"` | `"version_drift"`
119
+
120
+ **Como o caller reage**:
121
+ - `duplicate_title` (no `create`): normalmente fatal em MVP — skills assumem
122
+ naming unique via template com `{scope}` no título. Se bateu, dois
123
+ scopes têm o mesmo nome — o user precisa renomear no Input.
124
+ - `version_drift` (no `update`): **recuperável**. Fluxo:
125
+ 1. `/sf-publish` para a operação atual
126
+ 2. Baixa content remoto via `fetchContent`
127
+ 3. Gera diff local-vs-remoto
128
+ 4. Pergunta ao usuário: **sobrescrever, abortar, ou fazer merge manual**
129
+ 5. Se sobrescrever → novo `update` com `expectedVersion` atualizada
130
+
131
+ **Nunca retry automático** — drift é sinal de que um humano tocou o artefato
132
+ (aprovação manual, edição pós-review). Requer decisão consciente.
133
+
134
+ ---
135
+
136
+ ### `TransportError`
137
+
138
+ **Quando**: rede caiu, MCP morreu, timeout, 5xx do backend.
139
+
140
+ **Campos extras**:
141
+ - `retryable` — `true` se faz sentido tentar de novo (5xx, timeout)
142
+ - `statusCode` opcional — se o backend deu HTTP
143
+
144
+ **Como o caller reage**:
145
+ - `retryable=true`: retry com backoff (proposta: 3 tentativas, 1s / 3s / 10s).
146
+ Depois disso, escala pro usuário.
147
+ - `retryable=false`: escala imediato.
148
+
149
+ **Importante**: distinguir `TransportError` de `AuthError` é crítico — 401 **não**
150
+ é transport, é auth. Adapter precisa classificar antes de embrulhar.
151
+
152
+ ---
153
+
154
+ ### `NotImplementedError`
155
+
156
+ **Quando**: skill chama método opcional (`attachFile`) num adapter que não
157
+ implementa.
158
+
159
+ **Como o caller reage**: skill **deve** ter fallback ou skipar feature. Nunca
160
+ é fatal — é esperado em MVP (FilesystemAdapter sem attachments na v0, por ex).
161
+
162
+ Caller típico:
163
+ ```
164
+ try {
165
+ await adapter.attachFile(id, name, buf, mime);
166
+ } catch (e) {
167
+ if (e instanceof NotImplementedError) {
168
+ log("adapter ${adapter.name} não suporta attachments, pulando");
169
+ return;
170
+ }
171
+ throw e;
172
+ }
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Quem lança o quê (matriz)
178
+
179
+ | Método | ValidationError | AuthError | NotFoundError | ConflictError | TransportError | NotImplementedError |
180
+ |--------|:---:|:---:|:---:|:---:|:---:|:---:|
181
+ | `validateConfig` | ✅ | — | — | — | — | — |
182
+ | `listChildren` | — | ✅ | ✅ | — | ✅ | — |
183
+ | `fetchContent` | — | ✅ | ✅ | — | ✅ | — |
184
+ | `fetchAttachments` | — | ✅ | ✅ | — | ✅ | — |
185
+ | `getVersion` | — | ✅ | ✅ | — | ✅ | — |
186
+ | `create` | — | ✅ | ✅¹ | ✅² | ✅ | — |
187
+ | `update` | — | ✅ | ✅ | ✅³ | ✅ | — |
188
+ | `attachFile` | — | ✅ | ✅ | — | ✅ | ✅⁴ |
189
+
190
+ **Notas**:
191
+ - ¹ `NotFoundError` no `create` quando o `parentId` não existe.
192
+ - ² `ConflictError` no `create` = `kind: "duplicate_title"`.
193
+ - ³ `ConflictError` no `update` = `kind: "version_drift"`.
194
+ - ⁴ `NotImplementedError` apenas em adapters que não suportam attachments.
195
+
196
+ ---
197
+
198
+ ## Padrão de implementação (pseudo-código)
199
+
200
+ Todo adapter embrulha erros nativos nesses tipos. Exemplo genérico:
201
+
202
+ ```typescript
203
+ async update(itemId, content, expectedVersion) {
204
+ try {
205
+ const current = await this.getVersion(itemId);
206
+ if (current !== expectedVersion) {
207
+ throw new ConflictError({
208
+ adapter: this.name,
209
+ operation: "update",
210
+ kind: "version_drift",
211
+ itemId,
212
+ expectedVersion,
213
+ actualVersion: current,
214
+ });
215
+ }
216
+ const result = await this.backendUpdate(itemId, content);
217
+ return { version: result.version, ok: true };
218
+ } catch (e) {
219
+ if (e instanceof ConflictError) throw e;
220
+ if (isNotFound(e)) throw new NotFoundError({ adapter: this.name, operation: "update", itemId, kind: "item", cause: e });
221
+ if (isAuth(e)) throw new AuthError( { adapter: this.name, operation: "update", cause: e });
222
+ if (isTransport(e)) throw new TransportError({ adapter: this.name, operation: "update", retryable: e.status >= 500, cause: e });
223
+ throw e; // unknown — propaga cru
224
+ }
225
+ }
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Referências
231
+
232
+ - Interface: `.github/adapters/interface.md`
233
+ - Registry: `.github/adapters/registry.md`
234
+ - Naming: `.github/adapters/naming.md`