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.
- package/README.md +252 -167
- package/bin/cli.js +70 -70
- package/lib/init.js +92 -92
- package/lib/update.js +132 -132
- package/package.json +1 -1
- package/templates/.ai/memory/napkin.md +68 -68
- package/templates/.github/CHANGELOG.md +121 -0
- package/templates/.github/adapters/SETUP.md +314 -314
- package/templates/.github/adapters/confluence.md +295 -295
- package/templates/.github/adapters/errors.md +234 -234
- package/templates/.github/adapters/filesystem.md +353 -353
- package/templates/.github/adapters/interface.md +301 -301
- package/templates/.github/adapters/naming.md +241 -241
- package/templates/.github/adapters/registry.md +244 -244
- package/templates/.github/agents/backend-coder.md +14 -14
- package/templates/.github/agents/db-coder.md +165 -165
- package/templates/.github/agents/doc-writer.md +66 -53
- package/templates/.github/agents/frontend-coder.md +5 -5
- package/templates/.github/agents/infra-coder.md +341 -341
- package/templates/.github/agents/reviewer.md +6 -6
- package/templates/.github/agents/security-reviewer.md +153 -153
- package/templates/.github/copilot-instructions.md +272 -262
- package/templates/.github/instructions/docs.instructions.md +147 -145
- package/templates/.github/instructions/sensitive-files.instructions.md +32 -32
- package/templates/.github/rules.md +229 -229
- package/templates/.github/scripts/bootstrap-confluence.js +289 -223
- package/templates/.github/skills/sf-design/SKILL.md +161 -216
- package/templates/.github/skills/sf-dev/SKILL.md +204 -351
- package/templates/.github/skills/sf-discovery/SKILL.md +415 -414
- package/templates/.github/skills/sf-extract/SKILL.md +225 -249
- package/templates/.github/skills/sf-load/SKILL.md +296 -295
- package/templates/.github/skills/sf-mcp/SKILL.md +386 -385
- package/templates/.github/skills/sf-merge-docs/SKILL.md +152 -100
- package/templates/.github/skills/sf-plan/SKILL.md +152 -128
- package/templates/.github/skills/sf-publish/SKILL.md +144 -143
- package/templates/.github/skills/sf-session-finish/SKILL.md +93 -120
- package/templates/.github/skills/sf-start/SKILL.md +192 -145
- package/templates/.github/templates/estrutura/apiContracts.template.md +160 -159
- package/templates/.github/templates/estrutura/architecture.template.md +169 -168
- package/templates/.github/templates/estrutura/conventions.template.md +214 -212
- package/templates/.github/templates/estrutura/decisions.template.md +107 -107
- package/templates/.github/templates/estrutura/domain.template.md +161 -160
- package/templates/.github/templates/feature/PRD.template.md +279 -286
- package/templates/.github/templates/feature/Progresso.template.md +141 -141
- package/templates/.github/templates/feature/TRD.template.md +358 -0
- package/templates/.github/templates/feature/context.template.md +89 -48
- package/templates/.github/templates/feature/extract-log.template.md +49 -39
- package/templates/.github/templates/feature/projetos.template.yaml +79 -79
- package/templates/.github/templates/global/progresso_global.template.md +59 -57
- package/templates/.github/templates/specs/brief.template.md +66 -59
- package/templates/.github/templates/specs/contracts.template.md +147 -141
- package/templates/.github/templates/specs/scenarios.template.md +125 -117
- package/templates/.github/templates/specs/tasks.template.md +65 -63
- package/templates/_gitignore +35 -35
- package/templates/sfw.config.yml.example +147 -147
- package/templates/.github/templates/feature/backlog-extraido.template.md +0 -156
- 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`
|