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.
Files changed (55) 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 +560 -533
  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 +215 -215
  16. package/templates/.github/agents/db-coder.md +165 -165
  17. package/templates/.github/agents/doc-writer.md +66 -66
  18. package/templates/.github/agents/frontend-coder.md +222 -222
  19. package/templates/.github/agents/infra-coder.md +341 -341
  20. package/templates/.github/agents/reviewer.md +99 -99
  21. package/templates/.github/agents/security-reviewer.md +153 -153
  22. package/templates/.github/copilot-instructions.md +272 -272
  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 -289
  27. package/templates/.github/skills/sf-design/SKILL.md +161 -161
  28. package/templates/.github/skills/sf-dev/SKILL.md +204 -204
  29. package/templates/.github/skills/sf-discovery/SKILL.md +415 -415
  30. package/templates/.github/skills/sf-extract/SKILL.md +225 -225
  31. package/templates/.github/skills/sf-load/SKILL.md +296 -296
  32. package/templates/.github/skills/sf-mcp/SKILL.md +386 -386
  33. package/templates/.github/skills/sf-merge-docs/SKILL.md +152 -152
  34. package/templates/.github/skills/sf-plan/SKILL.md +152 -152
  35. package/templates/.github/skills/sf-publish/SKILL.md +144 -144
  36. package/templates/.github/skills/sf-session-finish/SKILL.md +93 -93
  37. package/templates/.github/skills/sf-start/SKILL.md +192 -192
  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 -279
  44. package/templates/.github/templates/feature/Progresso.template.md +141 -141
  45. package/templates/.github/templates/feature/TRD.template.md +358 -358
  46. package/templates/.github/templates/feature/context.template.md +89 -89
  47. package/templates/.github/templates/feature/extract-log.template.md +49 -49
  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 -66
  51. package/templates/.github/templates/specs/contracts.template.md +147 -147
  52. package/templates/.github/templates/specs/scenarios.template.md +125 -125
  53. package/templates/.github/templates/specs/tasks.template.md +65 -65
  54. package/templates/_gitignore +35 -35
  55. 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" /></>` | `![X](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: `&nbsp;` → espaço, `&amp;` → `&`, `&lt;` → `<`, `&gt;` → `>`
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 `&nbsp;` 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" /></>` | `![X](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: `&nbsp;` → espaço, `&amp;` → `&`, `&lt;` → `<`, `&gt;` → `>`
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 `&nbsp;` 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`