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,301 +1,301 @@
1
- # SourceAdapter — Interface
2
-
3
- > Contrato que **todo adapter** (Confluence, Filesystem, Notion, JIRA, …) deve
4
- > implementar. As skills `/sf-load`, `/sf-publish`, `/sf-start` **nunca** falam com
5
- > backends direto — elas falam com essa interface.
6
- >
7
- > Este arquivo é **especificação**, não código executável. A implementação real
8
- > vive em `.github/adapters/confluence.md`, `.github/adapters/filesystem.md`, etc.
9
- > (arquivos de runbook que o agent segue, no mesmo estilo das skills).
10
-
11
- ---
12
-
13
- ## Princípio
14
-
15
- > **SFW é sobre processo, não sobre ferramenta.**
16
- > O pipeline é backend-agnóstico. Times que usam Confluence, Notion, SharePoint,
17
- > JIRA ou filesystem local plugam um adapter e o resto continua idêntico.
18
-
19
- ---
20
-
21
- ## Interface TypeScript (referência)
22
-
23
- ```typescript
24
- /**
25
- * SourceAdapter — contrato de qualquer backend de I/O usado pelo SFW.
26
- *
27
- * Todos os métodos são assíncronos (um adapter pode ser rede, disco, MCP).
28
- * Erros são tipados — ver .github/adapters/errors.md.
29
- */
30
- interface SourceAdapter {
31
- /**
32
- * Identificador textual do adapter (ex: "confluence", "filesystem").
33
- * Usado pelo registry pra resolver a string em sfw.config.yml.
34
- */
35
- readonly name: string;
36
-
37
- /**
38
- * Valida o objeto de config passado pelo sfw.config.yml.
39
- * Chamado 1x no load do manifest, antes de qualquer operação.
40
- * Lança ValidationError se config estiver incompleto/incoerente.
41
- */
42
- validateConfig(config: object): void;
43
-
44
- // ------------------------------------------------------------------
45
- // LEITURA — usado por /sf-load e /sf-start
46
- // ------------------------------------------------------------------
47
-
48
- /**
49
- * Lista filhos diretos de um container.
50
- *
51
- * @param containerId id opaco do container (page, folder, etc.)
52
- * O formato é adapter-specific — confluence usa page_id
53
- * numeric, filesystem usa path absoluto ou relativo.
54
- * @returns array ordenado (ordem do backend; não há garantia alfabética)
55
- */
56
- listChildren(containerId: string): Promise<Item[]>;
57
-
58
- /**
59
- * Baixa o conteúdo de um item em formato normalizado.
60
- *
61
- * @returns content em markdown (adapter converte se necessário)
62
- * + metadata livre do backend (pode ser útil pros skills)
63
- *
64
- * Importante: o adapter é responsável por normalizar pra markdown.
65
- * ConfluenceAdapter converte storage format → markdown.
66
- * FilesystemAdapter lê .md direto; .txt/.sql/.html também retornam
67
- * como markdown (texto cru embrulhado em code fence).
68
- */
69
- fetchContent(itemId: string): Promise<{
70
- content: string;
71
- metadata: Record<string, unknown>;
72
- }>;
73
-
74
- /**
75
- * Lista os attachments de um item (imagens, PDFs, planilhas, etc.).
76
- * Retorna metadata + função download() lazy — não baixa bytes a menos
77
- * que o skill chame .download() explicitamente.
78
- */
79
- fetchAttachments(itemId: string): Promise<Attachment[]>;
80
-
81
- /**
82
- * Lê a versão atual do item (sem baixar o conteúdo inteiro).
83
- * Usado pra conflict detection proativo e pra detectar drift.
84
- *
85
- * Cada adapter decide o que é "version":
86
- * - Confluence: integer monotônico (ex: "7")
87
- * - Filesystem: sha256 do conteúdo
88
- * - Git: commit hash
89
- *
90
- * A interface só exige que strings possam ser comparadas por igualdade.
91
- */
92
- getVersion(itemId: string): Promise<string>;
93
-
94
- // ------------------------------------------------------------------
95
- // ESCRITA — usado por /sf-publish
96
- // ------------------------------------------------------------------
97
-
98
- /**
99
- * Cria um novo item sob um parent.
100
- *
101
- * @throws ConflictError se já existe item com o mesmo título no parent
102
- * (ou no espaço, no caso do Confluence — title uniqueness
103
- * é responsabilidade do caller via naming templates)
104
- * @returns itemId do novo item + version inicial
105
- */
106
- create(
107
- parentId: string,
108
- title: string,
109
- content: string,
110
- ): Promise<{ itemId: string; version: string }>;
111
-
112
- /**
113
- * Atualiza conteúdo de um item existente com conflict detection.
114
- *
115
- * @param expectedVersion versão que o caller acredita estar lá.
116
- * Se o adapter não suporta optimistic lock server-side
117
- * (ex: Confluence MCP atual), ele faz check client-side:
118
- * 1. chama getVersion(itemId)
119
- * 2. se != expectedVersion → retorna { ok: false, conflict: true }
120
- * 3. caso contrário, faz o update
121
- * @returns ok=true + nova version, ou ok=false + conflict=true (drift detectado)
122
- *
123
- * Nunca sobrescreve cegamente — o caller DEVE tratar conflict=true
124
- * (re-lendo, fazendo merge, pedindo confirmação, etc.).
125
- */
126
- update(
127
- itemId: string,
128
- content: string,
129
- expectedVersion: string,
130
- ): Promise<{ version: string; ok: boolean; conflict?: boolean }>;
131
-
132
- /**
133
- * Anexa um arquivo binário a um item.
134
- * Opcional — adapters que não suportam attachments lançam NotImplementedError.
135
- */
136
- attachFile?(
137
- itemId: string,
138
- filename: string,
139
- content: Buffer,
140
- mimeType: string,
141
- ): Promise<void>;
142
- }
143
- ```
144
-
145
- ---
146
-
147
- ## Tipos auxiliares
148
-
149
- ```typescript
150
- /**
151
- * Representa qualquer "nó" na hierarquia do backend.
152
- * Um Item pode ser uma page do Confluence, um arquivo do filesystem,
153
- * uma issue do JIRA, uma doc do Notion, etc.
154
- */
155
- interface Item {
156
- /**
157
- * ID opaco, adapter-specific. Nunca é interpretado pelas skills.
158
- * O caller usa como chave em logs e em chamadas subsequentes.
159
- */
160
- id: string;
161
-
162
- /**
163
- * Título exibido ao usuário. Para filesystem = nome do arquivo.
164
- * Para Confluence = page title.
165
- */
166
- title: string;
167
-
168
- /**
169
- * Versão atual. Mesma semântica do getVersion().
170
- */
171
- version: string;
172
-
173
- /**
174
- * Tipo estrutural. Permite ao /sf-load decidir se desce recursivamente
175
- * (page/folder) ou se baixa conteúdo (file).
176
- */
177
- type: "page" | "folder" | "file";
178
-
179
- /**
180
- * true se o item pode ter filhos (otimização pra /sf-load não chamar
181
- * listChildren desnecessariamente em nós folha).
182
- */
183
- hasChildren: boolean;
184
- }
185
-
186
- /**
187
- * Metadata de attachment. download() é lazy — só baixa bytes quando chamado.
188
- */
189
- interface Attachment {
190
- filename: string;
191
- size: number; // bytes
192
- mimeType: string; // ex: "image/png", "application/pdf"
193
- download(): Promise<Buffer>;
194
- }
195
- ```
196
-
197
- ---
198
-
199
- ## Semântica de version — tabela por adapter
200
-
201
- | Adapter | Fonte de version | Exemplo | Monotônico? | Conflict detection |
202
- |---------|------------------|---------|-------------|--------------------|
203
- | **Confluence** | `version.number` da page | `"7"` | Sim (inteiro++) | Client-side (MCP não aceita `expectedVersion`) |
204
- | **Filesystem** | `sha256(content)` | `"a3f5…"` | Não (conteúdo-based) | Re-leitura + compare |
205
- | **Notion** | `last_edited_time` | `"2026-04-11T14:20:00Z"` | Sim (timestamp) | Client-side |
206
- | **Git** | `commit hash` onde o arquivo foi tocado | `"9fc3d2e"` | Não (DAG) | `git diff` vs HEAD |
207
-
208
- **Regra**: a interface só garante **igualdade**. Skills não fazem aritmética em
209
- version. Se um adapter quer ordenação (rollback, histórico), expõe em
210
- `metadata` via `fetchContent`.
211
-
212
- ---
213
-
214
- ## Contrato de comportamento (regras invioláveis)
215
-
216
- Qualquer adapter **DEVE** garantir:
217
-
218
- 1. **`update` nunca sobrescreve sem conflict check.**
219
- Mesmo backends que não suportam optimistic lock server-side precisam
220
- fazer `getVersion` antes do write. É melhor falhar com `conflict: true`
221
- do que perder dados do usuário.
222
-
223
- 2. **IDs são opacos pro pipeline.**
224
- O adapter escolhe o formato (page_id numérico, path string, UUID, etc.).
225
- Skills persistem o ID em `.ai/load-log.md` / `.ai/publish-log.md` e
226
- repassam como string — nunca parseiam.
227
-
228
- 3. **Content é sempre markdown na saída e na entrada.**
229
- Se o backend armazena em outro formato (Confluence storage, Notion blocks),
230
- o adapter converte nos 2 sentidos. A normalização é lossy — `getVersion`
231
- é a fonte de verdade pra drift, não diff de bytes do markdown.
232
-
233
- 4. **Erros são tipados.**
234
- Nada de `throw new Error("deu ruim")` — usar as classes de
235
- `.github/adapters/errors.md`. Skills decidem como reagir via `catch` tipado.
236
-
237
- 5. **Sem estado global.**
238
- Adapter é instanciado com config do manifest e não compartilha estado
239
- entre instâncias. Dois projetos rodando lado a lado devem poder usar
240
- 2 instâncias do mesmo adapter com configs diferentes.
241
-
242
- 6. **Operações idempotentes quando possível.**
243
- `create` com o mesmo título no mesmo parent → `ConflictError` (não cria
244
- duplicata). `update` com o mesmo content → ok, nova version igual ou
245
- adapter detecta no-op.
246
-
247
- ---
248
-
249
- ## Fluxo típico — quem chama o quê
250
-
251
- ```
252
- /sf-load {scope}
253
- ├── listChildren(input.parent_page_id) ← lista items no Input
254
- ├── busca item por nome == {scope} ← match exato com o arg da skill
255
- ├── listChildren(scope.id) ← desce no scope (filhos)
256
- ├── fetchContent(scope.id) ← conteúdo da página-mãe/arquivo
257
- ├── fetchContent(child.id) para cada filho
258
- ├── fetchAttachments(scope.id) ← se include_attachments=true
259
- └── (escreve em workspace/Input/{scope}/ localmente)
260
-
261
- /sf-publish (chamado por /sf-extract, /sf-design, /sf-plan)
262
- ├── applyNaming(output_container, {scope}) ← ex: "out_app_barbearia"
263
- ├── listChildren(output.parent_page_id) ← acha container existente por título
264
- ├── se container não existe:
265
- │ └── create(output.parent_page_id, containerTitle, seed) → container criado
266
- ├── applyNaming(output_artifact, {scope, type}) ← ex: "app_barbearia - PRD"
267
- ├── listChildren(container.id) ← busca artefato por título
268
- ├── se artefato não existe:
269
- │ └── create(container.id, artifactTitle, content) → log version
270
- └── se artefato existe:
271
- ├── getVersion(itemId) ← lê version atual
272
- ├── comparar com publish-log (drift check)
273
- ├── se drift → ConflictError pro usuário
274
- └── update(itemId, content, expectedVersion) → log nova version
275
- ```
276
-
277
- ---
278
-
279
- ## O que NÃO está nesta interface (intencional)
280
-
281
- - **Busca full-text.** Se o backend suporta, skills chamam via metadata/config
282
- específica do adapter, não pela interface. Aumentaria a superfície sem ganho
283
- pro MVP.
284
- - **Permissions/ACL.** Adapter assume que as credenciais já têm acesso.
285
- `AuthError` é o único sinal de permissão negada.
286
- - **Watching/live updates.** Pull model é suficiente. Push viria depois se
287
- houver caso real.
288
- - **Transactions.** Nenhum backend suportado tem transactions entre itens.
289
- Skills são projetadas pra serem idempotentes e re-executáveis.
290
- - **Attachments bidirecionais no MVP.** `attachFile` é opcional. Só Confluence
291
- implementa no MVP. FilesystemAdapter pode implementar trivialmente.
292
-
293
- ---
294
-
295
- ## Referências
296
-
297
- - Registry + resolução: `.github/adapters/registry.md`
298
- - Templating de nomes: `.github/adapters/naming.md`
299
- - Contrato de erros: `.github/adapters/errors.md`
300
- - Schema do manifest: `sfw.config.yml.example` (raiz do projeto)
301
- - Decisão arquitetural: `planodetarefas.md §9.9`
1
+ # SourceAdapter — Interface
2
+
3
+ > Contrato que **todo adapter** (Confluence, Filesystem, Notion, JIRA, …) deve
4
+ > implementar. As skills `/sf-load`, `/sf-publish`, `/sf-start` **nunca** falam com
5
+ > backends direto — elas falam com essa interface.
6
+ >
7
+ > Este arquivo é **especificação**, não código executável. A implementação real
8
+ > vive em `.github/adapters/confluence.md`, `.github/adapters/filesystem.md`, etc.
9
+ > (arquivos de runbook que o agent segue, no mesmo estilo das skills).
10
+
11
+ ---
12
+
13
+ ## Princípio
14
+
15
+ > **SFW é sobre processo, não sobre ferramenta.**
16
+ > O pipeline é backend-agnóstico. Times que usam Confluence, Notion, SharePoint,
17
+ > JIRA ou filesystem local plugam um adapter e o resto continua idêntico.
18
+
19
+ ---
20
+
21
+ ## Interface TypeScript (referência)
22
+
23
+ ```typescript
24
+ /**
25
+ * SourceAdapter — contrato de qualquer backend de I/O usado pelo SFW.
26
+ *
27
+ * Todos os métodos são assíncronos (um adapter pode ser rede, disco, MCP).
28
+ * Erros são tipados — ver .github/adapters/errors.md.
29
+ */
30
+ interface SourceAdapter {
31
+ /**
32
+ * Identificador textual do adapter (ex: "confluence", "filesystem").
33
+ * Usado pelo registry pra resolver a string em sfw.config.yml.
34
+ */
35
+ readonly name: string;
36
+
37
+ /**
38
+ * Valida o objeto de config passado pelo sfw.config.yml.
39
+ * Chamado 1x no load do manifest, antes de qualquer operação.
40
+ * Lança ValidationError se config estiver incompleto/incoerente.
41
+ */
42
+ validateConfig(config: object): void;
43
+
44
+ // ------------------------------------------------------------------
45
+ // LEITURA — usado por /sf-load e /sf-start
46
+ // ------------------------------------------------------------------
47
+
48
+ /**
49
+ * Lista filhos diretos de um container.
50
+ *
51
+ * @param containerId id opaco do container (page, folder, etc.)
52
+ * O formato é adapter-specific — confluence usa page_id
53
+ * numeric, filesystem usa path absoluto ou relativo.
54
+ * @returns array ordenado (ordem do backend; não há garantia alfabética)
55
+ */
56
+ listChildren(containerId: string): Promise<Item[]>;
57
+
58
+ /**
59
+ * Baixa o conteúdo de um item em formato normalizado.
60
+ *
61
+ * @returns content em markdown (adapter converte se necessário)
62
+ * + metadata livre do backend (pode ser útil pros skills)
63
+ *
64
+ * Importante: o adapter é responsável por normalizar pra markdown.
65
+ * ConfluenceAdapter converte storage format → markdown.
66
+ * FilesystemAdapter lê .md direto; .txt/.sql/.html também retornam
67
+ * como markdown (texto cru embrulhado em code fence).
68
+ */
69
+ fetchContent(itemId: string): Promise<{
70
+ content: string;
71
+ metadata: Record<string, unknown>;
72
+ }>;
73
+
74
+ /**
75
+ * Lista os attachments de um item (imagens, PDFs, planilhas, etc.).
76
+ * Retorna metadata + função download() lazy — não baixa bytes a menos
77
+ * que o skill chame .download() explicitamente.
78
+ */
79
+ fetchAttachments(itemId: string): Promise<Attachment[]>;
80
+
81
+ /**
82
+ * Lê a versão atual do item (sem baixar o conteúdo inteiro).
83
+ * Usado pra conflict detection proativo e pra detectar drift.
84
+ *
85
+ * Cada adapter decide o que é "version":
86
+ * - Confluence: integer monotônico (ex: "7")
87
+ * - Filesystem: sha256 do conteúdo
88
+ * - Git: commit hash
89
+ *
90
+ * A interface só exige que strings possam ser comparadas por igualdade.
91
+ */
92
+ getVersion(itemId: string): Promise<string>;
93
+
94
+ // ------------------------------------------------------------------
95
+ // ESCRITA — usado por /sf-publish
96
+ // ------------------------------------------------------------------
97
+
98
+ /**
99
+ * Cria um novo item sob um parent.
100
+ *
101
+ * @throws ConflictError se já existe item com o mesmo título no parent
102
+ * (ou no espaço, no caso do Confluence — title uniqueness
103
+ * é responsabilidade do caller via naming templates)
104
+ * @returns itemId do novo item + version inicial
105
+ */
106
+ create(
107
+ parentId: string,
108
+ title: string,
109
+ content: string,
110
+ ): Promise<{ itemId: string; version: string }>;
111
+
112
+ /**
113
+ * Atualiza conteúdo de um item existente com conflict detection.
114
+ *
115
+ * @param expectedVersion versão que o caller acredita estar lá.
116
+ * Se o adapter não suporta optimistic lock server-side
117
+ * (ex: Confluence MCP atual), ele faz check client-side:
118
+ * 1. chama getVersion(itemId)
119
+ * 2. se != expectedVersion → retorna { ok: false, conflict: true }
120
+ * 3. caso contrário, faz o update
121
+ * @returns ok=true + nova version, ou ok=false + conflict=true (drift detectado)
122
+ *
123
+ * Nunca sobrescreve cegamente — o caller DEVE tratar conflict=true
124
+ * (re-lendo, fazendo merge, pedindo confirmação, etc.).
125
+ */
126
+ update(
127
+ itemId: string,
128
+ content: string,
129
+ expectedVersion: string,
130
+ ): Promise<{ version: string; ok: boolean; conflict?: boolean }>;
131
+
132
+ /**
133
+ * Anexa um arquivo binário a um item.
134
+ * Opcional — adapters que não suportam attachments lançam NotImplementedError.
135
+ */
136
+ attachFile?(
137
+ itemId: string,
138
+ filename: string,
139
+ content: Buffer,
140
+ mimeType: string,
141
+ ): Promise<void>;
142
+ }
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Tipos auxiliares
148
+
149
+ ```typescript
150
+ /**
151
+ * Representa qualquer "nó" na hierarquia do backend.
152
+ * Um Item pode ser uma page do Confluence, um arquivo do filesystem,
153
+ * uma issue do JIRA, uma doc do Notion, etc.
154
+ */
155
+ interface Item {
156
+ /**
157
+ * ID opaco, adapter-specific. Nunca é interpretado pelas skills.
158
+ * O caller usa como chave em logs e em chamadas subsequentes.
159
+ */
160
+ id: string;
161
+
162
+ /**
163
+ * Título exibido ao usuário. Para filesystem = nome do arquivo.
164
+ * Para Confluence = page title.
165
+ */
166
+ title: string;
167
+
168
+ /**
169
+ * Versão atual. Mesma semântica do getVersion().
170
+ */
171
+ version: string;
172
+
173
+ /**
174
+ * Tipo estrutural. Permite ao /sf-load decidir se desce recursivamente
175
+ * (page/folder) ou se baixa conteúdo (file).
176
+ */
177
+ type: "page" | "folder" | "file";
178
+
179
+ /**
180
+ * true se o item pode ter filhos (otimização pra /sf-load não chamar
181
+ * listChildren desnecessariamente em nós folha).
182
+ */
183
+ hasChildren: boolean;
184
+ }
185
+
186
+ /**
187
+ * Metadata de attachment. download() é lazy — só baixa bytes quando chamado.
188
+ */
189
+ interface Attachment {
190
+ filename: string;
191
+ size: number; // bytes
192
+ mimeType: string; // ex: "image/png", "application/pdf"
193
+ download(): Promise<Buffer>;
194
+ }
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Semântica de version — tabela por adapter
200
+
201
+ | Adapter | Fonte de version | Exemplo | Monotônico? | Conflict detection |
202
+ |---------|------------------|---------|-------------|--------------------|
203
+ | **Confluence** | `version.number` da page | `"7"` | Sim (inteiro++) | Client-side (MCP não aceita `expectedVersion`) |
204
+ | **Filesystem** | `sha256(content)` | `"a3f5…"` | Não (conteúdo-based) | Re-leitura + compare |
205
+ | **Notion** | `last_edited_time` | `"2026-04-11T14:20:00Z"` | Sim (timestamp) | Client-side |
206
+ | **Git** | `commit hash` onde o arquivo foi tocado | `"9fc3d2e"` | Não (DAG) | `git diff` vs HEAD |
207
+
208
+ **Regra**: a interface só garante **igualdade**. Skills não fazem aritmética em
209
+ version. Se um adapter quer ordenação (rollback, histórico), expõe em
210
+ `metadata` via `fetchContent`.
211
+
212
+ ---
213
+
214
+ ## Contrato de comportamento (regras invioláveis)
215
+
216
+ Qualquer adapter **DEVE** garantir:
217
+
218
+ 1. **`update` nunca sobrescreve sem conflict check.**
219
+ Mesmo backends que não suportam optimistic lock server-side precisam
220
+ fazer `getVersion` antes do write. É melhor falhar com `conflict: true`
221
+ do que perder dados do usuário.
222
+
223
+ 2. **IDs são opacos pro pipeline.**
224
+ O adapter escolhe o formato (page_id numérico, path string, UUID, etc.).
225
+ Skills persistem o ID em `.ai/load-log.md` / `.ai/publish-log.md` e
226
+ repassam como string — nunca parseiam.
227
+
228
+ 3. **Content é sempre markdown na saída e na entrada.**
229
+ Se o backend armazena em outro formato (Confluence storage, Notion blocks),
230
+ o adapter converte nos 2 sentidos. A normalização é lossy — `getVersion`
231
+ é a fonte de verdade pra drift, não diff de bytes do markdown.
232
+
233
+ 4. **Erros são tipados.**
234
+ Nada de `throw new Error("deu ruim")` — usar as classes de
235
+ `.github/adapters/errors.md`. Skills decidem como reagir via `catch` tipado.
236
+
237
+ 5. **Sem estado global.**
238
+ Adapter é instanciado com config do manifest e não compartilha estado
239
+ entre instâncias. Dois projetos rodando lado a lado devem poder usar
240
+ 2 instâncias do mesmo adapter com configs diferentes.
241
+
242
+ 6. **Operações idempotentes quando possível.**
243
+ `create` com o mesmo título no mesmo parent → `ConflictError` (não cria
244
+ duplicata). `update` com o mesmo content → ok, nova version igual ou
245
+ adapter detecta no-op.
246
+
247
+ ---
248
+
249
+ ## Fluxo típico — quem chama o quê
250
+
251
+ ```
252
+ /sf-load {scope}
253
+ ├── listChildren(input.parent_page_id) ← lista items no Input
254
+ ├── busca item por nome == {scope} ← match exato com o arg da skill
255
+ ├── listChildren(scope.id) ← desce no scope (filhos)
256
+ ├── fetchContent(scope.id) ← conteúdo da página-mãe/arquivo
257
+ ├── fetchContent(child.id) para cada filho
258
+ ├── fetchAttachments(scope.id) ← se include_attachments=true
259
+ └── (escreve em workspace/Input/{scope}/ localmente)
260
+
261
+ /sf-publish (chamado por /sf-extract, /sf-design, /sf-plan)
262
+ ├── applyNaming(output_container, {scope}) ← ex: "out_app_barbearia"
263
+ ├── listChildren(output.parent_page_id) ← acha container existente por título
264
+ ├── se container não existe:
265
+ │ └── create(output.parent_page_id, containerTitle, seed) → container criado
266
+ ├── applyNaming(output_artifact, {scope, type}) ← ex: "app_barbearia - PRD"
267
+ ├── listChildren(container.id) ← busca artefato por título
268
+ ├── se artefato não existe:
269
+ │ └── create(container.id, artifactTitle, content) → log version
270
+ └── se artefato existe:
271
+ ├── getVersion(itemId) ← lê version atual
272
+ ├── comparar com publish-log (drift check)
273
+ ├── se drift → ConflictError pro usuário
274
+ └── update(itemId, content, expectedVersion) → log nova version
275
+ ```
276
+
277
+ ---
278
+
279
+ ## O que NÃO está nesta interface (intencional)
280
+
281
+ - **Busca full-text.** Se o backend suporta, skills chamam via metadata/config
282
+ específica do adapter, não pela interface. Aumentaria a superfície sem ganho
283
+ pro MVP.
284
+ - **Permissions/ACL.** Adapter assume que as credenciais já têm acesso.
285
+ `AuthError` é o único sinal de permissão negada.
286
+ - **Watching/live updates.** Pull model é suficiente. Push viria depois se
287
+ houver caso real.
288
+ - **Transactions.** Nenhum backend suportado tem transactions entre itens.
289
+ Skills são projetadas pra serem idempotentes e re-executáveis.
290
+ - **Attachments bidirecionais no MVP.** `attachFile` é opcional. Só Confluence
291
+ implementa no MVP. FilesystemAdapter pode implementar trivialmente.
292
+
293
+ ---
294
+
295
+ ## Referências
296
+
297
+ - Registry + resolução: `.github/adapters/registry.md`
298
+ - Templating de nomes: `.github/adapters/naming.md`
299
+ - Contrato de erros: `.github/adapters/errors.md`
300
+ - Schema do manifest: `sfw.config.yml.example` (raiz do projeto)
301
+ - Decisão arquitetural: `planodetarefas.md §9.9`