spec-first-copilot 0.7.0-beta.1 → 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 +560 -533
- 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 +215 -215
- package/templates/.github/agents/db-coder.md +165 -165
- package/templates/.github/agents/doc-writer.md +66 -66
- package/templates/.github/agents/frontend-coder.md +222 -222
- package/templates/.github/agents/infra-coder.md +341 -341
- package/templates/.github/agents/reviewer.md +99 -99
- package/templates/.github/agents/security-reviewer.md +153 -153
- package/templates/.github/copilot-instructions.md +272 -272
- 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 -289
- package/templates/.github/skills/sf-design/SKILL.md +161 -161
- package/templates/.github/skills/sf-dev/SKILL.md +204 -204
- package/templates/.github/skills/sf-discovery/SKILL.md +415 -415
- package/templates/.github/skills/sf-extract/SKILL.md +225 -225
- package/templates/.github/skills/sf-load/SKILL.md +296 -296
- package/templates/.github/skills/sf-mcp/SKILL.md +386 -386
- package/templates/.github/skills/sf-merge-docs/SKILL.md +152 -152
- package/templates/.github/skills/sf-plan/SKILL.md +152 -152
- package/templates/.github/skills/sf-publish/SKILL.md +144 -144
- package/templates/.github/skills/sf-session-finish/SKILL.md +93 -93
- package/templates/.github/skills/sf-start/SKILL.md +192 -192
- 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 -279
- package/templates/.github/templates/feature/Progresso.template.md +141 -141
- package/templates/.github/templates/feature/TRD.template.md +358 -358
- package/templates/.github/templates/feature/context.template.md +89 -89
- package/templates/.github/templates/feature/extract-log.template.md +49 -49
- 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 -66
- package/templates/.github/templates/specs/contracts.template.md +147 -147
- package/templates/.github/templates/specs/scenarios.template.md +125 -125
- package/templates/.github/templates/specs/tasks.template.md +65 -65
- package/templates/_gitignore +35 -35
- package/templates/sfw.config.yml.example +147 -147
|
@@ -1,296 +1,296 @@
|
|
|
1
|
-
|
|
2
|
-
# /sf-load <nome> | --all
|
|
3
|
-
|
|
4
|
-
Puxa conteúdo do backend configurado (Confluence, filesystem, etc.) e materializa localmente.
|
|
5
|
-
|
|
6
|
-
## Modos
|
|
7
|
-
|
|
8
|
-
| Modo | O que traz | Destino local |
|
|
9
|
-
|------|-----------|---------------|
|
|
10
|
-
| `/sf-load <nome>` | Input **e** Output do scope `{nome}` | `workspace/Input/{nome}/` + `workspace/Output/{nome}/` |
|
|
11
|
-
| `/sf-load --all` | **Tudo** — todos os scopes de Input e Output | `workspace/Input/*/` + `workspace/Output/*/` |
|
|
12
|
-
|
|
13
|
-
## Por que trazer Output também?
|
|
14
|
-
|
|
15
|
-
O Output pode já ter artefatos publicados (PRD, TRD, Progresso) por outra sessão
|
|
16
|
-
ou outro dev. Se você não traz, o pipeline pensa que não existe e tenta recriar.
|
|
17
|
-
Trazer Output garante continuidade.
|
|
18
|
-
|
|
19
|
-
## Quando usar
|
|
20
|
-
|
|
21
|
-
- **Antes de `/sf-start`** — traz insumos frescos + artefatos existentes
|
|
22
|
-
- **Após o PM adicionar novos insumos** — re-sincroniza incrementalmente
|
|
23
|
-
- **Início de sessão num projeto existente** — `/sf-load --all` pra ter tudo atualizado
|
|
24
|
-
- **Não obrigatório** se o projeto é 100% local (sem `sfw.config.yml`)
|
|
25
|
-
|
|
26
|
-
## Pré-condições
|
|
27
|
-
|
|
28
|
-
| # | Validação | Se falhar |
|
|
29
|
-
|---|-----------|-----------|
|
|
30
|
-
| 1 | `sfw.config.yml` existe na raiz do projeto | Parar → "Rode /sf-mcp confluence ou configure manualmente. Veja .github/adapters/SETUP.md" |
|
|
31
|
-
| 2 | `sfw.config.yml` tem seção `input` e `output` | Parar → "Seções input/output não encontradas no sfw.config.yml" |
|
|
32
|
-
| 3 | Adapter acessível (MCP rodando pra Confluence) | Parar → orientar setup conforme `.github/adapters/SETUP.md` |
|
|
33
|
-
|
|
34
|
-
## Execução
|
|
35
|
-
|
|
36
|
-
### 1. Carregar configuração
|
|
37
|
-
|
|
38
|
-
Ler `sfw.config.yml` e extrair:
|
|
39
|
-
- `input.adapter` + `input.config` — pra puxar Input
|
|
40
|
-
- `input.config.parent_page_id` — page-mãe do Input no backend
|
|
41
|
-
- `output.targets[0].adapter` + `output.targets[0].config` — pra puxar Output
|
|
42
|
-
- `output.targets[0].config.parent_page_id` — page-mãe do Output no backend
|
|
43
|
-
- `input.cache.local_dir` — default `workspace/Input/`
|
|
44
|
-
- `input.cache.log` — default `.ai/load-log.md`
|
|
45
|
-
- `naming.output_container` — template pra nome da pasta de Output (ex: `out_{scope}`)
|
|
46
|
-
|
|
47
|
-
### 2. Determinar o que trazer
|
|
48
|
-
|
|
49
|
-
**Se `/sf-load <nome>`:**
|
|
50
|
-
- **Input**: buscar scope `{nome}` nos filhos de `input.parent_page_id`
|
|
51
|
-
- Match exato por título — se não encontrou, ERRO
|
|
52
|
-
- **Output**: buscar container `out_{nome}` (ou conforme `naming.output_container`) nos filhos de `output.parent_page_id`
|
|
53
|
-
- Se não encontrou → OK, ainda não foi publicado. Pular silenciosamente.
|
|
54
|
-
|
|
55
|
-
**Se `/sf-load --all`:**
|
|
56
|
-
- **Input**: listar TODOS os filhos de `input.parent_page_id`
|
|
57
|
-
- **Output**: listar TODOS os filhos de `output.parent_page_id`
|
|
58
|
-
- Processar cada um como se fosse `/sf-load <nome>` individual
|
|
59
|
-
|
|
60
|
-
### 3. Trazer conteúdo — RECURSIVO ATÉ O ÚLTIMO NÍVEL, FLAT LOCAL
|
|
61
|
-
|
|
62
|
-
> **3 REGRAS INVIOLÁVEIS:**
|
|
63
|
-
> 1. **Descer até o último nível** — `get_page_children` retorna só 1 nível. O agent DEVE fazer loop recursivo.
|
|
64
|
-
> 2. **Local é flat** — todos os arquivos soltos na pasta, SEM subpastas por nível.
|
|
65
|
-
> 3. **Refletir 100% do externo** — tudo que existe no backend DEVE existir localmente.
|
|
66
|
-
|
|
67
|
-
**Para cada scope de Input — materializar em `workspace/Input/{nome}/`:**
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
function loadScope(pageId, targetDir):
|
|
71
|
-
allPages = []
|
|
72
|
-
collectAllPages(pageId, allPages)
|
|
73
|
-
|
|
74
|
-
para cada page em allPages:
|
|
75
|
-
// PEDIR MARKDOWN PRIMEIRO (MCP converte do storage format)
|
|
76
|
-
result = mcp__atlassian__confluence_get_page(page_id=page.id, convert_to_markdown=true)
|
|
77
|
-
content = result.content (ou result.body — depende do MCP)
|
|
78
|
-
|
|
79
|
-
content = preservarFormatacao(content)
|
|
80
|
-
|
|
81
|
-
filename = sanitize(page.title) + ".md"
|
|
82
|
-
salvar content em {targetDir}/{filename}
|
|
83
|
-
registrar no load-log
|
|
84
|
-
|
|
85
|
-
// Attachments de CADA page
|
|
86
|
-
para cada page em allPages:
|
|
87
|
-
attachments = mcp__atlassian__confluence_get_attachments(page_id=page.id)
|
|
88
|
-
para cada att:
|
|
89
|
-
bytes = mcp__atlassian__confluence_download_attachment(page_id=page.id, filename=att.title)
|
|
90
|
-
salvar em {targetDir}/{att.title}
|
|
91
|
-
registrar no load-log
|
|
92
|
-
|
|
93
|
-
function collectAllPages(pageId, result):
|
|
94
|
-
children = mcp__atlassian__confluence_get_page_children(page_id=pageId)
|
|
95
|
-
para cada child:
|
|
96
|
-
result.push(child)
|
|
97
|
-
collectAllPages(child.id, result) // recursão total
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### 3.1 Função `preservarFormatacao(content)` — regras obrigatórias
|
|
101
|
-
|
|
102
|
-
O MCP pode retornar markdown "parcial" (com tags HTML misturadas) ou storage format
|
|
103
|
-
(XHTML do Confluence). A função DEVE preservar toda a estrutura semântica:
|
|
104
|
-
|
|
105
|
-
**Passo 1 — Macros do Confluence (prioridade alta — preservar semântica)**
|
|
106
|
-
|
|
107
|
-
| Macro Confluence | Converter pra markdown |
|
|
108
|
-
|------------------|------------------------|
|
|
109
|
-
| `<ac:structured-macro ac:name="code">...<ac:plain-text-body><![CDATA[...]]></>` | ```` ```{lang}\n{código}\n``` ```` (pegar linguagem de `<ac:parameter ac:name="language">`) |
|
|
110
|
-
| `<ac:structured-macro ac:name="info\|note\|warning\|tip">` | `> ℹ️` (info), `> 📝` (note), `> ⚠️` (warning), `> 💡` (tip) + conteúdo como blockquote |
|
|
111
|
-
| `<ac:structured-macro ac:name="expand">` | `<details><summary>{title}</summary>\n{body}\n</details>` |
|
|
112
|
-
| `<ac:structured-macro ac:name="panel">` | blockquote simples `> ` |
|
|
113
|
-
| `<ac:link><ri:page ri:content-title="X" /></>` | `[X](../X.md)` (link interno) |
|
|
114
|
-
| `<ac:image><ri:attachment ri:filename="X" /></>` | `` (imagem inline — attachment já é baixado) |
|
|
115
|
-
|
|
116
|
-
**Passo 2 — Tabelas (crítico — fidelidade alta)**
|
|
117
|
-
|
|
118
|
-
Preservar estrutura completa:
|
|
119
|
-
```
|
|
120
|
-
<table>
|
|
121
|
-
<tbody>
|
|
122
|
-
<tr><th>Col A</th><th>Col B</th></tr>
|
|
123
|
-
<tr><td>val1</td><td>val2</td></tr>
|
|
124
|
-
</tbody>
|
|
125
|
-
</table>
|
|
126
|
-
```
|
|
127
|
-
vira:
|
|
128
|
-
```
|
|
129
|
-
| Col A | Col B |
|
|
130
|
-
|-------|-------|
|
|
131
|
-
| val1 | val2 |
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
**NUNCA** achatar tabela em lista ou parágrafo — perde dados estruturados.
|
|
135
|
-
Se célula tem formatação inline (bold, link), preservar dentro da célula.
|
|
136
|
-
|
|
137
|
-
**Passo 3 — Tags semânticas padrão**
|
|
138
|
-
|
|
139
|
-
| HTML | Markdown |
|
|
140
|
-
|------|----------|
|
|
141
|
-
| `<h1>` → `<h6>` | `#` → `######` |
|
|
142
|
-
| `<strong>`, `<b>` | `**texto**` |
|
|
143
|
-
| `<em>`, `<i>` | `*texto*` |
|
|
144
|
-
| `<u>` | `<u>texto</u>` (markdown não tem underline nativo — manter tag) |
|
|
145
|
-
| `<s>`, `<del>` | `~~texto~~` |
|
|
146
|
-
| `<a href="URL">` | `[texto](URL)` |
|
|
147
|
-
| `<ul><li>` | `- item` |
|
|
148
|
-
| `<ol><li>` | `1. item` |
|
|
149
|
-
| `<code>` inline | `` `código` `` |
|
|
150
|
-
| `<pre>` sem macro code | ```` ``` ```` bloco |
|
|
151
|
-
| `<blockquote>` | `> texto` |
|
|
152
|
-
| `<hr>` | `---` |
|
|
153
|
-
| `<br>` | quebra de linha |
|
|
154
|
-
| `<p>` (simples) | parágrafo (linha em branco antes/depois) |
|
|
155
|
-
|
|
156
|
-
**Passo 4 — Remover metadata/lixo do Confluence**
|
|
157
|
-
|
|
158
|
-
Strip silencioso (sem manter nada):
|
|
159
|
-
- `<p local-id="uuid">` → remove tag, mantém conteúdo
|
|
160
|
-
- Atributos `ac:schema-version`, `ac:macro-id`, `ac:local-id`, `ri:version-at-save`
|
|
161
|
-
- `<ri:user>`, `<ri:space>` sem contexto útil
|
|
162
|
-
- Comentários HTML `<!-- ... -->`
|
|
163
|
-
- Entidades HTML: ` ` → espaço, `&` → `&`, `<` → `<`, `>` → `>`
|
|
164
|
-
|
|
165
|
-
**Passo 5 — Limpeza final**
|
|
166
|
-
|
|
167
|
-
- Colapsar 3+ linhas em branco consecutivas em 2
|
|
168
|
-
- Trim whitespace no fim de cada linha
|
|
169
|
-
- Garantir que arquivo termine com uma única newline
|
|
170
|
-
|
|
171
|
-
**Passo 6 — Validação**
|
|
172
|
-
|
|
173
|
-
Após conversão, o markdown resultante NÃO deve conter:
|
|
174
|
-
- Nenhuma tag `<ac:...>`, `<ri:...>`, `<p local-id=...>`
|
|
175
|
-
- Nenhum ` ` ou entidade HTML não convertida
|
|
176
|
-
- Tabelas sem pipes markdown
|
|
177
|
-
|
|
178
|
-
Se qualquer um aparecer, é bug da função — reportar ao user no load-log com flag `FORMAT_WARN`.
|
|
179
|
-
|
|
180
|
-
**Para cada scope de Output — materializar em `workspace/Output/{nome}/`:**
|
|
181
|
-
|
|
182
|
-
Mesmo processo, mas:
|
|
183
|
-
- Container no backend tem nome conforme `naming.output_container` (ex: `out_app_barbearia`)
|
|
184
|
-
- Artifacts dentro dele são PRD, TRD, Progresso, etc.
|
|
185
|
-
- Materializar cada um como `{tipo}.md` (ex: `PRD.md`, `TRD.md`, `Progresso.md`)
|
|
186
|
-
- Nome local do scope é extraído do container (remove prefixo `out_`)
|
|
187
|
-
|
|
188
|
-
**Para Filesystem** (`adapter: filesystem`):
|
|
189
|
-
|
|
190
|
-
```
|
|
191
|
-
function loadScope(sourcePath, targetDir):
|
|
192
|
-
// Se source == targetDir → no-op
|
|
193
|
-
// Senão: copiar TODOS os arquivos recursivamente pra targetDir (flat)
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### 4. Log incremental (`.ai/load-log.md`)
|
|
197
|
-
|
|
198
|
-
Formato append-only:
|
|
199
|
-
|
|
200
|
-
```markdown
|
|
201
|
-
## Load: {nome} — {ISO_DATETIME}
|
|
202
|
-
|
|
203
|
-
### Input
|
|
204
|
-
| Item ID | Title | Version | SHA256 | Local Path | Status |
|
|
205
|
-
|---------|-------|---------|--------|------------|--------|
|
|
206
|
-
| 294950 | app_barbearia | 3 | a3f5... | workspace/Input/app_barbearia/app_barbearia.md | NOVO |
|
|
207
|
-
| 491572 | Requisitos | 2 | 8b1c... | workspace/Input/app_barbearia/requisitos.md | NOVO |
|
|
208
|
-
|
|
209
|
-
### Output
|
|
210
|
-
| Item ID | Title | Version | SHA256 | Local Path | Status |
|
|
211
|
-
|---------|-------|---------|--------|------------|--------|
|
|
212
|
-
| 393238 | app_barbearia - PRD | 2 | 4d2a... | workspace/Output/app_barbearia/PRD.md | NOVO |
|
|
213
|
-
| 425998 | app_barbearia - TRD | 1 | 7f3e... | workspace/Output/app_barbearia/TRD.md | NOVO |
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
**Se `incremental: true`** (default):
|
|
217
|
-
- Comparar hash com log anterior antes de baixar
|
|
218
|
-
- `INALTERADO` = hash igual, não sobrescreve
|
|
219
|
-
- `MODIFICADO` = hash diferente, sobrescreve
|
|
220
|
-
- `NOVO` = não existia no log
|
|
221
|
-
- `REMOVIDO` = existia no log mas sumiu do backend (log only, não deleta local)
|
|
222
|
-
|
|
223
|
-
### 5. Ajustar `.gitignore` conforme o adapter
|
|
224
|
-
|
|
225
|
-
Se o adapter NÃO é `filesystem`:
|
|
226
|
-
- Verificar se `.gitignore` contém `# SFW: workspace ignored`
|
|
227
|
-
- Se NÃO contém, adicionar:
|
|
228
|
-
```
|
|
229
|
-
# SFW: workspace ignored (external backend detected)
|
|
230
|
-
workspace/Output/**
|
|
231
|
-
!workspace/Output/.gitkeep
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
Se o adapter É `filesystem` e o bloco existe, removê-lo.
|
|
235
|
-
|
|
236
|
-
### 6. Informar o usuário
|
|
237
|
-
|
|
238
|
-
**Para `/sf-load <nome>`:**
|
|
239
|
-
```
|
|
240
|
-
Scope "{nome}" carregado:
|
|
241
|
-
|
|
242
|
-
Input: {N} arquivos ({X novos, Y modificados, Z inalterados})
|
|
243
|
-
Output: {M} artefatos ({A novos, B modificados, C inalterados})
|
|
244
|
-
ou "nenhum artefato publicado ainda"
|
|
245
|
-
|
|
246
|
-
Próximo passo:
|
|
247
|
-
/sf-start {nome} ← entrada única (bootstrap ou feature, detecta via docs/)
|
|
248
|
-
|
|
249
|
-
Log: .ai/load-log.md
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
**Para `/sf-load --all`:**
|
|
253
|
-
```
|
|
254
|
-
Projeto sincronizado:
|
|
255
|
-
|
|
256
|
-
Input: {N} scopes, {X} arquivos total
|
|
257
|
-
Output: {M} scopes, {Y} artefatos total
|
|
258
|
-
|
|
259
|
-
Scopes encontrados:
|
|
260
|
-
- app_barbearia (Input: 5 arquivos, Output: PRD + TRD + Progresso)
|
|
261
|
-
- feat_login (Input: 3 arquivos, Output: nenhum)
|
|
262
|
-
- feat_pagamento (Input: 2 arquivos, Output: PRD)
|
|
263
|
-
|
|
264
|
-
Log: .ai/load-log.md
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
## Notas
|
|
268
|
-
|
|
269
|
-
- `/sf-load` é **idempotente** — rodar N vezes não muda nada se backend não mudou
|
|
270
|
-
- `/sf-load` **nunca modifica o backend** — só lê
|
|
271
|
-
- `/sf-load` **nunca deleta arquivos locais** — marca REMOVIDO no log mas mantém
|
|
272
|
-
- Attachments ficam flat junto com os .md
|
|
273
|
-
- **Estrutura local é FLAT** — não replica hierarquia do backend
|
|
274
|
-
- **Recursão é total** — desce até o último nível
|
|
275
|
-
- Nomes sanitizados: espaços → `_`, caracteres especiais removidos
|
|
276
|
-
- Output que não existe no backend é pulado silenciosamente (ainda não publicado)
|
|
277
|
-
- `/sf-load --all` é útil no início de sessão pra ter tudo atualizado
|
|
278
|
-
|
|
279
|
-
## Erros
|
|
280
|
-
|
|
281
|
-
| Erro | Ação |
|
|
282
|
-
|------|------|
|
|
283
|
-
| `sfw.config.yml` não existe | Parar → orientar /sf-mcp ou setup manual |
|
|
284
|
-
| MCP não disponível | Parar → "Reinicie o VS Code com Copilot Chat" |
|
|
285
|
-
| Scope não encontrado no Input | Parar, listar scopes disponíveis |
|
|
286
|
-
| Auth error (401/403) | Parar → "Verifique .mcp.json" |
|
|
287
|
-
| Page sem conteúdo | OK — salvar vazio, registrar no log |
|
|
288
|
-
| Output não tem container pro scope | OK — pular, informar "nenhum artefato publicado ainda" |
|
|
289
|
-
|
|
290
|
-
## Referências
|
|
291
|
-
|
|
292
|
-
- Adapter interface: `.github/adapters/interface.md`
|
|
293
|
-
- Confluence adapter: `.github/adapters/confluence.md`
|
|
294
|
-
- Filesystem adapter: `.github/adapters/filesystem.md`
|
|
295
|
-
- Setup guide: `.github/adapters/SETUP.md`
|
|
296
|
-
- Naming: `.github/adapters/naming.md`
|
|
1
|
+
|
|
2
|
+
# /sf-load <nome> | --all
|
|
3
|
+
|
|
4
|
+
Puxa conteúdo do backend configurado (Confluence, filesystem, etc.) e materializa localmente.
|
|
5
|
+
|
|
6
|
+
## Modos
|
|
7
|
+
|
|
8
|
+
| Modo | O que traz | Destino local |
|
|
9
|
+
|------|-----------|---------------|
|
|
10
|
+
| `/sf-load <nome>` | Input **e** Output do scope `{nome}` | `workspace/Input/{nome}/` + `workspace/Output/{nome}/` |
|
|
11
|
+
| `/sf-load --all` | **Tudo** — todos os scopes de Input e Output | `workspace/Input/*/` + `workspace/Output/*/` |
|
|
12
|
+
|
|
13
|
+
## Por que trazer Output também?
|
|
14
|
+
|
|
15
|
+
O Output pode já ter artefatos publicados (PRD, TRD, Progresso) por outra sessão
|
|
16
|
+
ou outro dev. Se você não traz, o pipeline pensa que não existe e tenta recriar.
|
|
17
|
+
Trazer Output garante continuidade.
|
|
18
|
+
|
|
19
|
+
## Quando usar
|
|
20
|
+
|
|
21
|
+
- **Antes de `/sf-start`** — traz insumos frescos + artefatos existentes
|
|
22
|
+
- **Após o PM adicionar novos insumos** — re-sincroniza incrementalmente
|
|
23
|
+
- **Início de sessão num projeto existente** — `/sf-load --all` pra ter tudo atualizado
|
|
24
|
+
- **Não obrigatório** se o projeto é 100% local (sem `sfw.config.yml`)
|
|
25
|
+
|
|
26
|
+
## Pré-condições
|
|
27
|
+
|
|
28
|
+
| # | Validação | Se falhar |
|
|
29
|
+
|---|-----------|-----------|
|
|
30
|
+
| 1 | `sfw.config.yml` existe na raiz do projeto | Parar → "Rode /sf-mcp confluence ou configure manualmente. Veja .github/adapters/SETUP.md" |
|
|
31
|
+
| 2 | `sfw.config.yml` tem seção `input` e `output` | Parar → "Seções input/output não encontradas no sfw.config.yml" |
|
|
32
|
+
| 3 | Adapter acessível (MCP rodando pra Confluence) | Parar → orientar setup conforme `.github/adapters/SETUP.md` |
|
|
33
|
+
|
|
34
|
+
## Execução
|
|
35
|
+
|
|
36
|
+
### 1. Carregar configuração
|
|
37
|
+
|
|
38
|
+
Ler `sfw.config.yml` e extrair:
|
|
39
|
+
- `input.adapter` + `input.config` — pra puxar Input
|
|
40
|
+
- `input.config.parent_page_id` — page-mãe do Input no backend
|
|
41
|
+
- `output.targets[0].adapter` + `output.targets[0].config` — pra puxar Output
|
|
42
|
+
- `output.targets[0].config.parent_page_id` — page-mãe do Output no backend
|
|
43
|
+
- `input.cache.local_dir` — default `workspace/Input/`
|
|
44
|
+
- `input.cache.log` — default `.ai/load-log.md`
|
|
45
|
+
- `naming.output_container` — template pra nome da pasta de Output (ex: `out_{scope}`)
|
|
46
|
+
|
|
47
|
+
### 2. Determinar o que trazer
|
|
48
|
+
|
|
49
|
+
**Se `/sf-load <nome>`:**
|
|
50
|
+
- **Input**: buscar scope `{nome}` nos filhos de `input.parent_page_id`
|
|
51
|
+
- Match exato por título — se não encontrou, ERRO
|
|
52
|
+
- **Output**: buscar container `out_{nome}` (ou conforme `naming.output_container`) nos filhos de `output.parent_page_id`
|
|
53
|
+
- Se não encontrou → OK, ainda não foi publicado. Pular silenciosamente.
|
|
54
|
+
|
|
55
|
+
**Se `/sf-load --all`:**
|
|
56
|
+
- **Input**: listar TODOS os filhos de `input.parent_page_id`
|
|
57
|
+
- **Output**: listar TODOS os filhos de `output.parent_page_id`
|
|
58
|
+
- Processar cada um como se fosse `/sf-load <nome>` individual
|
|
59
|
+
|
|
60
|
+
### 3. Trazer conteúdo — RECURSIVO ATÉ O ÚLTIMO NÍVEL, FLAT LOCAL
|
|
61
|
+
|
|
62
|
+
> **3 REGRAS INVIOLÁVEIS:**
|
|
63
|
+
> 1. **Descer até o último nível** — `get_page_children` retorna só 1 nível. O agent DEVE fazer loop recursivo.
|
|
64
|
+
> 2. **Local é flat** — todos os arquivos soltos na pasta, SEM subpastas por nível.
|
|
65
|
+
> 3. **Refletir 100% do externo** — tudo que existe no backend DEVE existir localmente.
|
|
66
|
+
|
|
67
|
+
**Para cada scope de Input — materializar em `workspace/Input/{nome}/`:**
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
function loadScope(pageId, targetDir):
|
|
71
|
+
allPages = []
|
|
72
|
+
collectAllPages(pageId, allPages)
|
|
73
|
+
|
|
74
|
+
para cada page em allPages:
|
|
75
|
+
// PEDIR MARKDOWN PRIMEIRO (MCP converte do storage format)
|
|
76
|
+
result = mcp__atlassian__confluence_get_page(page_id=page.id, convert_to_markdown=true)
|
|
77
|
+
content = result.content (ou result.body — depende do MCP)
|
|
78
|
+
|
|
79
|
+
content = preservarFormatacao(content)
|
|
80
|
+
|
|
81
|
+
filename = sanitize(page.title) + ".md"
|
|
82
|
+
salvar content em {targetDir}/{filename}
|
|
83
|
+
registrar no load-log
|
|
84
|
+
|
|
85
|
+
// Attachments de CADA page
|
|
86
|
+
para cada page em allPages:
|
|
87
|
+
attachments = mcp__atlassian__confluence_get_attachments(page_id=page.id)
|
|
88
|
+
para cada att:
|
|
89
|
+
bytes = mcp__atlassian__confluence_download_attachment(page_id=page.id, filename=att.title)
|
|
90
|
+
salvar em {targetDir}/{att.title}
|
|
91
|
+
registrar no load-log
|
|
92
|
+
|
|
93
|
+
function collectAllPages(pageId, result):
|
|
94
|
+
children = mcp__atlassian__confluence_get_page_children(page_id=pageId)
|
|
95
|
+
para cada child:
|
|
96
|
+
result.push(child)
|
|
97
|
+
collectAllPages(child.id, result) // recursão total
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3.1 Função `preservarFormatacao(content)` — regras obrigatórias
|
|
101
|
+
|
|
102
|
+
O MCP pode retornar markdown "parcial" (com tags HTML misturadas) ou storage format
|
|
103
|
+
(XHTML do Confluence). A função DEVE preservar toda a estrutura semântica:
|
|
104
|
+
|
|
105
|
+
**Passo 1 — Macros do Confluence (prioridade alta — preservar semântica)**
|
|
106
|
+
|
|
107
|
+
| Macro Confluence | Converter pra markdown |
|
|
108
|
+
|------------------|------------------------|
|
|
109
|
+
| `<ac:structured-macro ac:name="code">...<ac:plain-text-body><![CDATA[...]]></>` | ```` ```{lang}\n{código}\n``` ```` (pegar linguagem de `<ac:parameter ac:name="language">`) |
|
|
110
|
+
| `<ac:structured-macro ac:name="info\|note\|warning\|tip">` | `> ℹ️` (info), `> 📝` (note), `> ⚠️` (warning), `> 💡` (tip) + conteúdo como blockquote |
|
|
111
|
+
| `<ac:structured-macro ac:name="expand">` | `<details><summary>{title}</summary>\n{body}\n</details>` |
|
|
112
|
+
| `<ac:structured-macro ac:name="panel">` | blockquote simples `> ` |
|
|
113
|
+
| `<ac:link><ri:page ri:content-title="X" /></>` | `[X](../X.md)` (link interno) |
|
|
114
|
+
| `<ac:image><ri:attachment ri:filename="X" /></>` | `` (imagem inline — attachment já é baixado) |
|
|
115
|
+
|
|
116
|
+
**Passo 2 — Tabelas (crítico — fidelidade alta)**
|
|
117
|
+
|
|
118
|
+
Preservar estrutura completa:
|
|
119
|
+
```
|
|
120
|
+
<table>
|
|
121
|
+
<tbody>
|
|
122
|
+
<tr><th>Col A</th><th>Col B</th></tr>
|
|
123
|
+
<tr><td>val1</td><td>val2</td></tr>
|
|
124
|
+
</tbody>
|
|
125
|
+
</table>
|
|
126
|
+
```
|
|
127
|
+
vira:
|
|
128
|
+
```
|
|
129
|
+
| Col A | Col B |
|
|
130
|
+
|-------|-------|
|
|
131
|
+
| val1 | val2 |
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**NUNCA** achatar tabela em lista ou parágrafo — perde dados estruturados.
|
|
135
|
+
Se célula tem formatação inline (bold, link), preservar dentro da célula.
|
|
136
|
+
|
|
137
|
+
**Passo 3 — Tags semânticas padrão**
|
|
138
|
+
|
|
139
|
+
| HTML | Markdown |
|
|
140
|
+
|------|----------|
|
|
141
|
+
| `<h1>` → `<h6>` | `#` → `######` |
|
|
142
|
+
| `<strong>`, `<b>` | `**texto**` |
|
|
143
|
+
| `<em>`, `<i>` | `*texto*` |
|
|
144
|
+
| `<u>` | `<u>texto</u>` (markdown não tem underline nativo — manter tag) |
|
|
145
|
+
| `<s>`, `<del>` | `~~texto~~` |
|
|
146
|
+
| `<a href="URL">` | `[texto](URL)` |
|
|
147
|
+
| `<ul><li>` | `- item` |
|
|
148
|
+
| `<ol><li>` | `1. item` |
|
|
149
|
+
| `<code>` inline | `` `código` `` |
|
|
150
|
+
| `<pre>` sem macro code | ```` ``` ```` bloco |
|
|
151
|
+
| `<blockquote>` | `> texto` |
|
|
152
|
+
| `<hr>` | `---` |
|
|
153
|
+
| `<br>` | quebra de linha |
|
|
154
|
+
| `<p>` (simples) | parágrafo (linha em branco antes/depois) |
|
|
155
|
+
|
|
156
|
+
**Passo 4 — Remover metadata/lixo do Confluence**
|
|
157
|
+
|
|
158
|
+
Strip silencioso (sem manter nada):
|
|
159
|
+
- `<p local-id="uuid">` → remove tag, mantém conteúdo
|
|
160
|
+
- Atributos `ac:schema-version`, `ac:macro-id`, `ac:local-id`, `ri:version-at-save`
|
|
161
|
+
- `<ri:user>`, `<ri:space>` sem contexto útil
|
|
162
|
+
- Comentários HTML `<!-- ... -->`
|
|
163
|
+
- Entidades HTML: ` ` → espaço, `&` → `&`, `<` → `<`, `>` → `>`
|
|
164
|
+
|
|
165
|
+
**Passo 5 — Limpeza final**
|
|
166
|
+
|
|
167
|
+
- Colapsar 3+ linhas em branco consecutivas em 2
|
|
168
|
+
- Trim whitespace no fim de cada linha
|
|
169
|
+
- Garantir que arquivo termine com uma única newline
|
|
170
|
+
|
|
171
|
+
**Passo 6 — Validação**
|
|
172
|
+
|
|
173
|
+
Após conversão, o markdown resultante NÃO deve conter:
|
|
174
|
+
- Nenhuma tag `<ac:...>`, `<ri:...>`, `<p local-id=...>`
|
|
175
|
+
- Nenhum ` ` ou entidade HTML não convertida
|
|
176
|
+
- Tabelas sem pipes markdown
|
|
177
|
+
|
|
178
|
+
Se qualquer um aparecer, é bug da função — reportar ao user no load-log com flag `FORMAT_WARN`.
|
|
179
|
+
|
|
180
|
+
**Para cada scope de Output — materializar em `workspace/Output/{nome}/`:**
|
|
181
|
+
|
|
182
|
+
Mesmo processo, mas:
|
|
183
|
+
- Container no backend tem nome conforme `naming.output_container` (ex: `out_app_barbearia`)
|
|
184
|
+
- Artifacts dentro dele são PRD, TRD, Progresso, etc.
|
|
185
|
+
- Materializar cada um como `{tipo}.md` (ex: `PRD.md`, `TRD.md`, `Progresso.md`)
|
|
186
|
+
- Nome local do scope é extraído do container (remove prefixo `out_`)
|
|
187
|
+
|
|
188
|
+
**Para Filesystem** (`adapter: filesystem`):
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
function loadScope(sourcePath, targetDir):
|
|
192
|
+
// Se source == targetDir → no-op
|
|
193
|
+
// Senão: copiar TODOS os arquivos recursivamente pra targetDir (flat)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 4. Log incremental (`.ai/load-log.md`)
|
|
197
|
+
|
|
198
|
+
Formato append-only:
|
|
199
|
+
|
|
200
|
+
```markdown
|
|
201
|
+
## Load: {nome} — {ISO_DATETIME}
|
|
202
|
+
|
|
203
|
+
### Input
|
|
204
|
+
| Item ID | Title | Version | SHA256 | Local Path | Status |
|
|
205
|
+
|---------|-------|---------|--------|------------|--------|
|
|
206
|
+
| 294950 | app_barbearia | 3 | a3f5... | workspace/Input/app_barbearia/app_barbearia.md | NOVO |
|
|
207
|
+
| 491572 | Requisitos | 2 | 8b1c... | workspace/Input/app_barbearia/requisitos.md | NOVO |
|
|
208
|
+
|
|
209
|
+
### Output
|
|
210
|
+
| Item ID | Title | Version | SHA256 | Local Path | Status |
|
|
211
|
+
|---------|-------|---------|--------|------------|--------|
|
|
212
|
+
| 393238 | app_barbearia - PRD | 2 | 4d2a... | workspace/Output/app_barbearia/PRD.md | NOVO |
|
|
213
|
+
| 425998 | app_barbearia - TRD | 1 | 7f3e... | workspace/Output/app_barbearia/TRD.md | NOVO |
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Se `incremental: true`** (default):
|
|
217
|
+
- Comparar hash com log anterior antes de baixar
|
|
218
|
+
- `INALTERADO` = hash igual, não sobrescreve
|
|
219
|
+
- `MODIFICADO` = hash diferente, sobrescreve
|
|
220
|
+
- `NOVO` = não existia no log
|
|
221
|
+
- `REMOVIDO` = existia no log mas sumiu do backend (log only, não deleta local)
|
|
222
|
+
|
|
223
|
+
### 5. Ajustar `.gitignore` conforme o adapter
|
|
224
|
+
|
|
225
|
+
Se o adapter NÃO é `filesystem`:
|
|
226
|
+
- Verificar se `.gitignore` contém `# SFW: workspace ignored`
|
|
227
|
+
- Se NÃO contém, adicionar:
|
|
228
|
+
```
|
|
229
|
+
# SFW: workspace ignored (external backend detected)
|
|
230
|
+
workspace/Output/**
|
|
231
|
+
!workspace/Output/.gitkeep
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Se o adapter É `filesystem` e o bloco existe, removê-lo.
|
|
235
|
+
|
|
236
|
+
### 6. Informar o usuário
|
|
237
|
+
|
|
238
|
+
**Para `/sf-load <nome>`:**
|
|
239
|
+
```
|
|
240
|
+
Scope "{nome}" carregado:
|
|
241
|
+
|
|
242
|
+
Input: {N} arquivos ({X novos, Y modificados, Z inalterados})
|
|
243
|
+
Output: {M} artefatos ({A novos, B modificados, C inalterados})
|
|
244
|
+
ou "nenhum artefato publicado ainda"
|
|
245
|
+
|
|
246
|
+
Próximo passo:
|
|
247
|
+
/sf-start {nome} ← entrada única (bootstrap ou feature, detecta via docs/)
|
|
248
|
+
|
|
249
|
+
Log: .ai/load-log.md
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**Para `/sf-load --all`:**
|
|
253
|
+
```
|
|
254
|
+
Projeto sincronizado:
|
|
255
|
+
|
|
256
|
+
Input: {N} scopes, {X} arquivos total
|
|
257
|
+
Output: {M} scopes, {Y} artefatos total
|
|
258
|
+
|
|
259
|
+
Scopes encontrados:
|
|
260
|
+
- app_barbearia (Input: 5 arquivos, Output: PRD + TRD + Progresso)
|
|
261
|
+
- feat_login (Input: 3 arquivos, Output: nenhum)
|
|
262
|
+
- feat_pagamento (Input: 2 arquivos, Output: PRD)
|
|
263
|
+
|
|
264
|
+
Log: .ai/load-log.md
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Notas
|
|
268
|
+
|
|
269
|
+
- `/sf-load` é **idempotente** — rodar N vezes não muda nada se backend não mudou
|
|
270
|
+
- `/sf-load` **nunca modifica o backend** — só lê
|
|
271
|
+
- `/sf-load` **nunca deleta arquivos locais** — marca REMOVIDO no log mas mantém
|
|
272
|
+
- Attachments ficam flat junto com os .md
|
|
273
|
+
- **Estrutura local é FLAT** — não replica hierarquia do backend
|
|
274
|
+
- **Recursão é total** — desce até o último nível
|
|
275
|
+
- Nomes sanitizados: espaços → `_`, caracteres especiais removidos
|
|
276
|
+
- Output que não existe no backend é pulado silenciosamente (ainda não publicado)
|
|
277
|
+
- `/sf-load --all` é útil no início de sessão pra ter tudo atualizado
|
|
278
|
+
|
|
279
|
+
## Erros
|
|
280
|
+
|
|
281
|
+
| Erro | Ação |
|
|
282
|
+
|------|------|
|
|
283
|
+
| `sfw.config.yml` não existe | Parar → orientar /sf-mcp ou setup manual |
|
|
284
|
+
| MCP não disponível | Parar → "Reinicie o VS Code com Copilot Chat" |
|
|
285
|
+
| Scope não encontrado no Input | Parar, listar scopes disponíveis |
|
|
286
|
+
| Auth error (401/403) | Parar → "Verifique .mcp.json" |
|
|
287
|
+
| Page sem conteúdo | OK — salvar vazio, registrar no log |
|
|
288
|
+
| Output não tem container pro scope | OK — pular, informar "nenhum artefato publicado ainda" |
|
|
289
|
+
|
|
290
|
+
## Referências
|
|
291
|
+
|
|
292
|
+
- Adapter interface: `.github/adapters/interface.md`
|
|
293
|
+
- Confluence adapter: `.github/adapters/confluence.md`
|
|
294
|
+
- Filesystem adapter: `.github/adapters/filesystem.md`
|
|
295
|
+
- Setup guide: `.github/adapters/SETUP.md`
|
|
296
|
+
- Naming: `.github/adapters/naming.md`
|