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,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`
|