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,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-first-copilot",
3
- "version": "0.5.0-beta.16",
3
+ "version": "0.5.0-beta.17",
4
4
  "description": "Spec-first workflow kit for GitHub Copilot — AI-driven development with specs, not guesswork",
5
5
  "bin": {
6
6
  "spec-first-copilot": "bin/cli.js"
@@ -1,134 +1,110 @@
1
- # /sf-load <nome>
1
+ # /sf-load <nome> | --all
2
2
 
3
- Puxa os insumos de um scope do backend configurado (Confluence, filesystem, etc.)
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
- ## Argumento
5
+ ## Modos
7
6
 
8
- `<nome>` = nome do item no backend de input (título da page no Confluence,
9
- nome da pasta no filesystem). Match exato — se não encontrar, para com erro.
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 <nome>` ou `/sf-feature <nome>`** para trazer
14
- insumos que o PM publicou no Confluence (ou outro backend)
15
- - **Após o PM adicionar novos insumos** (ex: `ajustes_v1` como page filha)
16
- para re-sincronizar. O log incremental detecta NOVO/MODIFICADO/INALTERADO
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 → "Copie sfw.config.yml.example para sfw.config.yml e configure. Veja .github/adapters/SETUP.md" |
24
- | 2 | `sfw.config.yml` tem seção `input` com `adapter` e `config` | Parar → "Seção input não encontrada no sfw.config.yml" |
25
- | 3 | Adapter do input está acessível (MCP rodando pra Confluence, pasta existe pra filesystem) | Parar → orientar setup conforme `.github/adapters/SETUP.md` |
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` tipo do adapter (`confluence` ou `filesystem`)
33
- - `input.config` — config específica do adapter
34
- - `input.cache.local_dir` — onde materializar (default: `workspace/Input/`)
35
- - `input.cache.log` — arquivo de log (default: `.ai/sf-load-log.md`)
36
- - `input.cache.incremental` — se compara hashes antes de sobrescrever
37
- - `naming.output_container` — template pra nomear a pasta local (usa `{scope}`)
38
-
39
- ### 2. Buscar o scope no backend
40
-
41
- **Para Confluence** (`adapter: confluence`):
42
-
43
- ```
44
- 1. Chamar mcp__atlassian__confluence_get_page_children
45
- com page_id = config.parent_page_id
46
- 2. Na lista de filhos, buscar o que tem title == {nome} (match exato)
47
- 3. Se não encontrou → ERRO: "Scope '{nome}' não encontrado no Confluence
48
- (parent_page_id: {id}). Verifique se a page existe como filha de Input."
49
- 4. Anotar scope_id = page.id
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** — se uma page tem filhas, e as filhas têm netas, TODAS devem ser trazidas. `get_page_children` retorna só 1 nível o agent DEVE fazer loop recursivo.
65
- > 2. **Local é flat** — todos os arquivos ficam soltos em `workspace/Input/{nome}/`, SEM subpastas por nível. A hierarquia do Confluence NÃO é replicada localmente.
66
- > 3. **Refletir 100% do externo** — tudo que existe no backend (pages, attachments) DEVE existir em `workspace/Input/{nome}/`. Se falta algo, o `/sf-load` está errado.
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 Confluence**:
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} // ← FLAT, sem subpastas
81
- registrar no load-log: page.id, page.title, page.version, sha256(content), filename
76
+ salvar content em {targetDir}/{filename}
77
+ registrar no load-log
82
78
 
83
- // PASSO 3: Baixar attachments de CADA page (se include_attachments=true)
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 em attachments:
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} // ← FLAT junto com os .md
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 em children:
95
- result.push(child) // adiciona o filho
96
- collectAllPages(child.id, result) // desce nos netos, bisnetos, etc.
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
- **Resultado local em `workspace/Input/app_barbearia/` — TUDO FLAT:**
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
- **Nenhuma subpasta.** Não importa quantos níveis a árvore do Confluence tem — local é flat.
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 (input já é local)
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 funcionais | 2 | 8b1c... | workspace/Input/app_barbearia/requisitos_funcionais.md | NOVO |
145
- | 491573 | Regras de negocio | 1 | 4d2a... | workspace/Input/app_barbearia/regras_de_negocio.md | NOVO |
146
- | 491574 | Neta da pagina | 1 | 7f3e... | workspace/Input/app_barbearia/neta_da_pagina.md | NOVO |
147
- | 491575 | Jornadas | 1 | 9c5b... | workspace/Input/app_barbearia/jornadas.md | NOVO |
148
- | 491576 | Stack tecnica | 1 | 2e8d... | workspace/Input/app_barbearia/stack_tecnica.md | NOVO |
149
- | att:diagrama.png | diagrama.png | | 7d2e... | workspace/Input/app_barbearia/diagrama.png | NOVO |
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
- - Antes de baixar, comparar hash atual com hash no log anterior
154
- - Se hash idêntico → status `INALTERADO`, não sobrescrever
155
- - Se hash diferente → status `MODIFICADO`, sobrescrever
156
- - Se não existia no log → status `NOVO`
157
- - Arquivo que existia no log mas sumiu do backend → status `REMOVIDO` (log only, não deleta local)
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` (ou seja, é um backend externo como Confluence):
162
- - Verificar se `.gitignore` contém o bloco `# SFW: workspace ignored`
163
- - Se NÃO contém, **adicionar** ao final do `.gitignore`:
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
- Insumos carregados em workspace/Input/{nome}/
154
+ Scope "{nome}" carregado:
185
155
 
186
- {N} arquivos ({X novos, Y modificados, Z inalterados})
187
- {M} attachments
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} ← se é bootstrap técnico (gera TRD)
191
- /sf-feature {nome} ← se é feature (gera PRD)
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 salvo em .ai/sf-load-log.md
179
+ Log: .ai/sf-load-log.md
194
180
  ```
195
181
 
196
182
  ## Notas
197
183
 
198
- - `/sf-load` é **idempotente** — rodar N vezes com mesmos insumos no backend
199
- não muda nada (hashes batem, status = INALTERADO)
200
- - Se o PM adicionar page `ajustes_v1` como filha do scope no Confluence,
201
- re-rodar `/sf-load {nome}` traz como NOVO automaticamente
202
- - O `/sf-load` **nunca modifica o backend** —
203
- - O `/sf-load` **nunca deleta arquivos locais** — se algo sumiu do backend,
204
- marca como REMOVIDO no log mas mantém o arquivo local
205
- - Attachments ficam flat junto com os .md (sem subpasta `attachments/`)
206
- - Nomes de arquivo são sanitizados: espaços `_`, caracteres especiais removidos
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, orientar setup |
215
- | Adapter não configurado | Parar, apontar pra SETUP.md |
216
- | MCP não disponível (Confluence) | Parar "Reinicie o Claude Code. MCP carrega no startup" |
217
- | Scope não encontrado no backend | Parar, listar scopes disponíveis se possível |
218
- | Auth error (401/403) | Parar "Verifique credenciais no .mcp.json" |
219
- | Page sem conteúdo | OK — salvar arquivo vazio, registrar no log |
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