spec-first-copilot 0.5.0-beta.16 → 0.5.0-beta.17
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/package.json
CHANGED
|
@@ -1,134 +1,110 @@
|
|
|
1
|
-
# /sf-load <nome>
|
|
1
|
+
# /sf-load <nome> | --all
|
|
2
2
|
|
|
3
|
-
Puxa
|
|
4
|
-
e materializa em `workspace/Input/{nome}/` no projeto local.
|
|
3
|
+
Puxa conteúdo do backend configurado (Confluence, filesystem, etc.) e materializa localmente.
|
|
5
4
|
|
|
6
|
-
##
|
|
5
|
+
## Modos
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
| Modo | O que traz | Destino local |
|
|
8
|
+
|------|-----------|---------------|
|
|
9
|
+
| `/sf-load <nome>` | Input **e** Output do scope `{nome}` | `workspace/Input/{nome}/` + `workspace/Output/{nome}/` |
|
|
10
|
+
| `/sf-load --all` | **Tudo** — todos os scopes de Input e Output | `workspace/Input/*/` + `workspace/Output/*/` |
|
|
11
|
+
|
|
12
|
+
## Por que trazer Output também?
|
|
13
|
+
|
|
14
|
+
O Output pode já ter artefatos publicados (TRD, SDD, Progresso) por outra sessão
|
|
15
|
+
ou outro dev. Se você não traz, o pipeline pensa que não existe e tenta recriar.
|
|
16
|
+
Trazer Output garante continuidade.
|
|
10
17
|
|
|
11
18
|
## Quando usar
|
|
12
19
|
|
|
13
|
-
- **Antes de `/sf-new-project
|
|
14
|
-
|
|
15
|
-
- **
|
|
16
|
-
|
|
17
|
-
- **Não obrigatório** se o usuário já colocou insumos direto em `workspace/Input/{nome}/`
|
|
20
|
+
- **Antes de `/sf-new-project` ou `/sf-feature`** — traz insumos frescos + artefatos existentes
|
|
21
|
+
- **Após o PM adicionar novos insumos** — re-sincroniza incrementalmente
|
|
22
|
+
- **Início de sessão num projeto existente** — `/sf-load --all` pra ter tudo atualizado
|
|
23
|
+
- **Não obrigatório** se o projeto é 100% local (sem `sfw.config.yml`)
|
|
18
24
|
|
|
19
25
|
## Pré-condições
|
|
20
26
|
|
|
21
27
|
| # | Validação | Se falhar |
|
|
22
28
|
|---|-----------|-----------|
|
|
23
|
-
| 1 | `sfw.config.yml` existe na raiz do projeto | Parar → "
|
|
24
|
-
| 2 | `sfw.config.yml` tem seção `input`
|
|
25
|
-
| 3 | Adapter
|
|
29
|
+
| 1 | `sfw.config.yml` existe na raiz do projeto | Parar → "Rode /sf-mcp confluence ou configure manualmente. Veja .github/adapters/SETUP.md" |
|
|
30
|
+
| 2 | `sfw.config.yml` tem seção `input` e `output` | Parar → "Seções input/output não encontradas no sfw.config.yml" |
|
|
31
|
+
| 3 | Adapter acessível (MCP rodando pra Confluence) | Parar → orientar setup conforme `.github/adapters/SETUP.md` |
|
|
26
32
|
|
|
27
33
|
## Execução
|
|
28
34
|
|
|
29
35
|
### 1. Carregar configuração
|
|
30
36
|
|
|
31
37
|
Ler `sfw.config.yml` e extrair:
|
|
32
|
-
- `input.adapter`
|
|
33
|
-
- `input.config` —
|
|
34
|
-
- `
|
|
35
|
-
- `
|
|
36
|
-
- `input.cache.
|
|
37
|
-
- `
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
**Para Filesystem** (`adapter: filesystem`):
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
1. Listar conteúdo de config.root_path
|
|
56
|
-
2. Buscar pasta ou arquivo com nome == {nome}
|
|
57
|
-
3. Se não encontrou → ERRO: "Scope '{nome}' não encontrado em {root_path}"
|
|
58
|
-
4. Anotar scope_path
|
|
59
|
-
```
|
|
38
|
+
- `input.adapter` + `input.config` — pra puxar Input
|
|
39
|
+
- `input.config.parent_page_id` — page-mãe do Input no backend
|
|
40
|
+
- `output.targets[0].adapter` + `output.targets[0].config` — pra puxar Output
|
|
41
|
+
- `output.targets[0].config.parent_page_id` — page-mãe do Output no backend
|
|
42
|
+
- `input.cache.local_dir` — default `workspace/Input/`
|
|
43
|
+
- `input.cache.log` — default `.ai/sf-load-log.md`
|
|
44
|
+
- `naming.output_container` — template pra nome da pasta de Output (ex: `out_{scope}`)
|
|
45
|
+
|
|
46
|
+
### 2. Determinar o que trazer
|
|
47
|
+
|
|
48
|
+
**Se `/sf-load <nome>`:**
|
|
49
|
+
- **Input**: buscar scope `{nome}` nos filhos de `input.parent_page_id`
|
|
50
|
+
- Match exato por título — se não encontrou, ERRO
|
|
51
|
+
- **Output**: buscar container `out_{nome}` (ou conforme `naming.output_container`) nos filhos de `output.parent_page_id`
|
|
52
|
+
- Se não encontrou → OK, ainda não foi publicado. Pular silenciosamente.
|
|
53
|
+
|
|
54
|
+
**Se `/sf-load --all`:**
|
|
55
|
+
- **Input**: listar TODOS os filhos de `input.parent_page_id`
|
|
56
|
+
- **Output**: listar TODOS os filhos de `output.parent_page_id`
|
|
57
|
+
- Processar cada um como se fosse `/sf-load <nome>` individual
|
|
60
58
|
|
|
61
59
|
### 3. Trazer conteúdo — RECURSIVO ATÉ O ÚLTIMO NÍVEL, FLAT LOCAL
|
|
62
60
|
|
|
63
61
|
> **3 REGRAS INVIOLÁVEIS:**
|
|
64
|
-
> 1. **Descer até o último nível** —
|
|
65
|
-
> 2. **Local é flat** — todos os arquivos
|
|
66
|
-
> 3. **Refletir 100% do externo** — tudo que existe no backend
|
|
62
|
+
> 1. **Descer até o último nível** — `get_page_children` retorna só 1 nível. O agent DEVE fazer loop recursivo.
|
|
63
|
+
> 2. **Local é flat** — todos os arquivos soltos na pasta, SEM subpastas por nível.
|
|
64
|
+
> 3. **Refletir 100% do externo** — tudo que existe no backend DEVE existir localmente.
|
|
67
65
|
|
|
68
|
-
**Para
|
|
66
|
+
**Para cada scope de Input — materializar em `workspace/Input/{nome}/`:**
|
|
69
67
|
|
|
70
68
|
```
|
|
71
69
|
function loadScope(pageId, targetDir):
|
|
72
|
-
// PASSO 1: Coletar TODAS as pages da árvore (recursivo)
|
|
73
70
|
allPages = []
|
|
74
71
|
collectAllPages(pageId, allPages)
|
|
75
72
|
|
|
76
|
-
// PASSO 2: Baixar conteúdo de CADA page como arquivo flat
|
|
77
73
|
para cada page em allPages:
|
|
78
74
|
content = mcp__atlassian__confluence_get_page(page_id=page.id, convert_to_markdown=true)
|
|
79
75
|
filename = sanitize(page.title) + ".md"
|
|
80
|
-
salvar content em {targetDir}/{filename}
|
|
81
|
-
registrar no load-log
|
|
76
|
+
salvar content em {targetDir}/{filename}
|
|
77
|
+
registrar no load-log
|
|
82
78
|
|
|
83
|
-
//
|
|
79
|
+
// Attachments de CADA page
|
|
84
80
|
para cada page em allPages:
|
|
85
81
|
attachments = mcp__atlassian__confluence_get_attachments(page_id=page.id)
|
|
86
|
-
para cada att
|
|
82
|
+
para cada att:
|
|
87
83
|
bytes = mcp__atlassian__confluence_download_attachment(page_id=page.id, filename=att.title)
|
|
88
|
-
salvar em {targetDir}/{att.title}
|
|
84
|
+
salvar em {targetDir}/{att.title}
|
|
89
85
|
registrar no load-log
|
|
90
86
|
|
|
91
87
|
function collectAllPages(pageId, result):
|
|
92
|
-
// get_page_children retorna APENAS filhos diretos — por isso o loop recursivo
|
|
93
88
|
children = mcp__atlassian__confluence_get_page_children(page_id=pageId)
|
|
94
|
-
para cada child
|
|
95
|
-
result.push(child)
|
|
96
|
-
collectAllPages(child.id, result)
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**Exemplo concreto — Confluence tem esta árvore:**
|
|
100
|
-
```
|
|
101
|
-
app_barbearia (page raiz do scope)
|
|
102
|
-
├── Requisitos funcionais
|
|
103
|
-
│ ├── Regras de negocio
|
|
104
|
-
│ │ └── Neta da pagina ← 3º nível!
|
|
105
|
-
│ └── Jornadas
|
|
106
|
-
├── Stack tecnica
|
|
107
|
-
└── attachment: diagrama.png
|
|
89
|
+
para cada child:
|
|
90
|
+
result.push(child)
|
|
91
|
+
collectAllPages(child.id, result) // recursão total
|
|
108
92
|
```
|
|
109
93
|
|
|
110
|
-
**
|
|
111
|
-
```
|
|
112
|
-
workspace/Input/app_barbearia/
|
|
113
|
-
├── app_barbearia.md ← conteúdo da page raiz
|
|
114
|
-
├── requisitos_funcionais.md ← filho
|
|
115
|
-
├── regras_de_negocio.md ← neto
|
|
116
|
-
├── neta_da_pagina.md ← bisneto (3º nível!)
|
|
117
|
-
├── jornadas.md ← filho
|
|
118
|
-
├── stack_tecnica.md ← filho
|
|
119
|
-
└── diagrama.png ← attachment
|
|
120
|
-
```
|
|
94
|
+
**Para cada scope de Output — materializar em `workspace/Output/{nome}/`:**
|
|
121
95
|
|
|
122
|
-
|
|
96
|
+
Mesmo processo, mas:
|
|
97
|
+
- Container no backend tem nome conforme `naming.output_container` (ex: `out_app_barbearia`)
|
|
98
|
+
- Artifacts dentro dele são TRD, SDD, Progresso, etc.
|
|
99
|
+
- Materializar cada um como `{tipo}.md` (ex: `TRD.md`, `sdd.md`, `Progresso.md`)
|
|
100
|
+
- Nome local do scope é extraído do container (remove prefixo `out_`)
|
|
123
101
|
|
|
124
|
-
**Para Filesystem
|
|
102
|
+
**Para Filesystem** (`adapter: filesystem`):
|
|
125
103
|
|
|
126
104
|
```
|
|
127
105
|
function loadScope(sourcePath, targetDir):
|
|
128
|
-
// Se source == targetDir → no-op
|
|
106
|
+
// Se source == targetDir → no-op
|
|
129
107
|
// Senão: copiar TODOS os arquivos recursivamente pra targetDir (flat)
|
|
130
|
-
// Subpastas do source viram arquivos flat no target
|
|
131
|
-
// Calcular sha256 de cada arquivo pra load-log
|
|
132
108
|
```
|
|
133
109
|
|
|
134
110
|
### 4. Log incremental (`.ai/sf-load-log.md`)
|
|
@@ -138,85 +114,93 @@ Formato append-only:
|
|
|
138
114
|
```markdown
|
|
139
115
|
## Load: {nome} — {ISO_DATETIME}
|
|
140
116
|
|
|
117
|
+
### Input
|
|
141
118
|
| Item ID | Title | Version | SHA256 | Local Path | Status |
|
|
142
119
|
|---------|-------|---------|--------|------------|--------|
|
|
143
120
|
| 294950 | app_barbearia | 3 | a3f5... | workspace/Input/app_barbearia/app_barbearia.md | NOVO |
|
|
144
|
-
| 491572 | Requisitos
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
|
148
|
-
|
|
149
|
-
|
|
|
121
|
+
| 491572 | Requisitos | 2 | 8b1c... | workspace/Input/app_barbearia/requisitos.md | NOVO |
|
|
122
|
+
|
|
123
|
+
### Output
|
|
124
|
+
| Item ID | Title | Version | SHA256 | Local Path | Status |
|
|
125
|
+
|---------|-------|---------|--------|------------|--------|
|
|
126
|
+
| 393238 | app_barbearia - TRD | 2 | 4d2a... | workspace/Output/app_barbearia/TRD.md | NOVO |
|
|
127
|
+
| 425998 | app_barbearia - SDD | 1 | 7f3e... | workspace/Output/app_barbearia/sdd.md | NOVO |
|
|
150
128
|
```
|
|
151
129
|
|
|
152
130
|
**Se `incremental: true`** (default):
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
131
|
+
- Comparar hash com log anterior antes de baixar
|
|
132
|
+
- `INALTERADO` = hash igual, não sobrescreve
|
|
133
|
+
- `MODIFICADO` = hash diferente, sobrescreve
|
|
134
|
+
- `NOVO` = não existia no log
|
|
135
|
+
- `REMOVIDO` = existia no log mas sumiu do backend (log only, não deleta local)
|
|
158
136
|
|
|
159
137
|
### 5. Ajustar `.gitignore` conforme o adapter
|
|
160
138
|
|
|
161
|
-
Se o adapter NÃO é `filesystem
|
|
162
|
-
- Verificar se `.gitignore` contém
|
|
163
|
-
- Se NÃO contém,
|
|
139
|
+
Se o adapter NÃO é `filesystem`:
|
|
140
|
+
- Verificar se `.gitignore` contém `# SFW: workspace ignored`
|
|
141
|
+
- Se NÃO contém, adicionar:
|
|
164
142
|
```
|
|
165
143
|
# SFW: workspace ignored (external backend detected)
|
|
166
|
-
# Content lives in the external backend (Confluence, etc.) — local is just cache
|
|
167
144
|
workspace/Output/**
|
|
168
145
|
!workspace/Output/.gitkeep
|
|
169
146
|
```
|
|
170
|
-
- Informar ao usuário: "workspace/Output/ adicionado ao .gitignore (conteúdo vive no {adapter})"
|
|
171
147
|
|
|
172
|
-
Se o adapter É `filesystem
|
|
173
|
-
- Se `.gitignore` contém o bloco `# SFW: workspace ignored`, **removê-lo**
|
|
174
|
-
- Informar ao usuário: "workspace/Output/ removido do .gitignore (modo local)"
|
|
175
|
-
|
|
176
|
-
Isso garante que:
|
|
177
|
-
- Projetos com Confluence não commitam cache local
|
|
178
|
-
- Projetos que mudam de Confluence → filesystem voltam a rastrear
|
|
179
|
-
- A lógica roda no momento certo (após configurar `sfw.config.yml`, não no `init`)
|
|
148
|
+
Se o adapter É `filesystem` e o bloco existe, removê-lo.
|
|
180
149
|
|
|
181
150
|
### 6. Informar o usuário
|
|
182
151
|
|
|
152
|
+
**Para `/sf-load <nome>`:**
|
|
183
153
|
```
|
|
184
|
-
|
|
154
|
+
Scope "{nome}" carregado:
|
|
185
155
|
|
|
186
|
-
{N} arquivos ({X novos, Y modificados, Z inalterados})
|
|
187
|
-
{M}
|
|
156
|
+
Input: {N} arquivos ({X novos, Y modificados, Z inalterados})
|
|
157
|
+
Output: {M} artefatos ({A novos, B modificados, C inalterados})
|
|
158
|
+
ou "nenhum artefato publicado ainda"
|
|
188
159
|
|
|
189
160
|
Próximo passo:
|
|
190
|
-
/sf-new-project {nome} ←
|
|
191
|
-
/sf-feature {nome} ←
|
|
161
|
+
/sf-new-project {nome} ← bootstrap técnico (gera TRD)
|
|
162
|
+
/sf-feature {nome} ← feature (gera PRD)
|
|
163
|
+
|
|
164
|
+
Log: .ai/sf-load-log.md
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Para `/sf-load --all`:**
|
|
168
|
+
```
|
|
169
|
+
Projeto sincronizado:
|
|
170
|
+
|
|
171
|
+
Input: {N} scopes, {X} arquivos total
|
|
172
|
+
Output: {M} scopes, {Y} artefatos total
|
|
173
|
+
|
|
174
|
+
Scopes encontrados:
|
|
175
|
+
- app_barbearia (Input: 5 arquivos, Output: TRD + SDD + Progresso)
|
|
176
|
+
- feat_login (Input: 3 arquivos, Output: nenhum)
|
|
177
|
+
- feat_pagamento (Input: 2 arquivos, Output: PRD)
|
|
192
178
|
|
|
193
|
-
Log
|
|
179
|
+
Log: .ai/sf-load-log.md
|
|
194
180
|
```
|
|
195
181
|
|
|
196
182
|
## Notas
|
|
197
183
|
|
|
198
|
-
- `/sf-load` é **idempotente** — rodar N vezes
|
|
199
|
-
|
|
200
|
-
-
|
|
201
|
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
- **Estrutura local é FLAT** — não replica hierarquia do backend. Todos os arquivos soltos na mesma pasta
|
|
208
|
-
- **Recursão é total** — desce até o último nível. Se `get_page_children` retornou filhos, DESCER em cada um
|
|
184
|
+
- `/sf-load` é **idempotente** — rodar N vezes não muda nada se backend não mudou
|
|
185
|
+
- `/sf-load` **nunca modifica o backend** — só lê
|
|
186
|
+
- `/sf-load` **nunca deleta arquivos locais** — marca REMOVIDO no log mas mantém
|
|
187
|
+
- Attachments ficam flat junto com os .md
|
|
188
|
+
- **Estrutura local é FLAT** — não replica hierarquia do backend
|
|
189
|
+
- **Recursão é total** — desce até o último nível
|
|
190
|
+
- Nomes sanitizados: espaços → `_`, caracteres especiais removidos
|
|
191
|
+
- Output que não existe no backend é pulado silenciosamente (ainda não publicado)
|
|
192
|
+
- `/sf-load --all` é útil no início de sessão pra ter tudo atualizado
|
|
209
193
|
|
|
210
194
|
## Erros
|
|
211
195
|
|
|
212
196
|
| Erro | Ação |
|
|
213
197
|
|------|------|
|
|
214
|
-
| `sfw.config.yml` não existe | Parar
|
|
215
|
-
|
|
|
216
|
-
|
|
|
217
|
-
|
|
|
218
|
-
|
|
|
219
|
-
|
|
|
198
|
+
| `sfw.config.yml` não existe | Parar → orientar /sf-mcp ou setup manual |
|
|
199
|
+
| MCP não disponível | Parar → "Reinicie o Claude Code" |
|
|
200
|
+
| Scope não encontrado no Input | Parar, listar scopes disponíveis |
|
|
201
|
+
| Auth error (401/403) | Parar → "Verifique .mcp.json" |
|
|
202
|
+
| Page sem conteúdo | OK — salvar vazio, registrar no log |
|
|
203
|
+
| Output não tem container pro scope | OK — pular, informar "nenhum artefato publicado ainda" |
|
|
220
204
|
|
|
221
205
|
## Referências
|
|
222
206
|
|