spec-first-copilot 0.5.0-beta.2 → 0.5.0-beta.3

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.
@@ -0,0 +1,241 @@
1
+ # Naming Templating Engine
2
+
3
+ > Motor trivial que transforma os templates em `sfw.config.yml > naming.*`
4
+ > em strings finais (títulos Confluence, nomes de pasta, etc.).
5
+ >
6
+ > **Função pura, zero dependência externa.** Implementável em qualquer
7
+ > linguagem em ~15 linhas. Este documento é a especificação que
8
+ > adapters/skills/bootstrap seguem.
9
+
10
+ ---
11
+
12
+ ## Por que existe
13
+
14
+ Antes da decisão do adapter layer, títulos eram hardcoded em string literal
15
+ no código das skills:
16
+
17
+ ```
18
+ title = `out_${scope}_TRD`; // ← hardcoded em 5 lugares
19
+ ```
20
+
21
+ Isso forçava:
22
+ 1. Qualquer mudança de convenção → tocar N arquivos
23
+ 2. Times com outra convenção (ex: `[TRD] scope`) não conseguiam usar SFW
24
+ 3. Testes precisavam mockar strings mágicas
25
+
26
+ Agora a convenção vive em **um** arquivo (`sfw.config.yml`) e é aplicada via
27
+ templating engine centralizado.
28
+
29
+ ---
30
+
31
+ ## Placeholders suportados
32
+
33
+ | Placeholder | O que substitui | Fonte |
34
+ |-------------|-----------------|-------|
35
+ | `{scope}` | Nome do item no Input (ex: `app_barbearia`, `feat_login`) | Argumento da skill |
36
+ | `{type}` | Tipo do artefato (ex: `TRD`, `SDD`, `PRD`, `Progresso`) | Contexto da skill |
37
+
38
+ **Nenhum outro placeholder é aceito no MVP.** Expansões futuras entram por
39
+ proposta explícita (ex: `{phase}`, `{repo}`, `{timestamp}`).
40
+
41
+ **O Input NÃO usa naming templating.** O usuário nomeia livremente os items
42
+ no backend (Confluence, filesystem, etc.). O agent lê o nome tal qual está.
43
+ Naming só é aplicado no Output (o que o agent GERA).
44
+
45
+ ---
46
+
47
+ ## Templates no manifest
48
+
49
+ O `sfw.config.yml` define 2 templates de output:
50
+
51
+ | Template | Onde | Exemplo |
52
+ |----------|------|---------|
53
+ | `output_container` | Subpasta/page-mãe por scope no Output | `"out_{scope}"` → `out_app_barbearia` |
54
+ | `output_artifact` | Nome do artefato dentro do container | `"{scope} - {type}"` → `app_barbearia - TRD` |
55
+
56
+ **`output_container`** é o agrupador. No Confluence vira uma page-mãe com
57
+ children; no filesystem vira uma pasta.
58
+
59
+ **`output_artifact`** é o item individual (TRD, SDD, PRD, Progresso). No
60
+ Confluence é title de uma child page; no filesystem é nome de arquivo
61
+ (adapter adiciona `.md` automaticamente).
62
+
63
+ **Por que `{scope}` no `output_artifact`?** No Confluence, titles são
64
+ unique per SPACE — sem `{scope}`, duas features teriam `TRD` e `SDD` com
65
+ mesmo título e colidiria. No filesystem não colide porque vive dentro de
66
+ pastas diferentes, então o user pode configurar `output_artifact: "{type}"`
67
+ se preferir limpar redundância.
68
+
69
+ ---
70
+
71
+ ## Contrato da função
72
+
73
+ ```typescript
74
+ /**
75
+ * Aplica templating de nome.
76
+ *
77
+ * @param template String com placeholders {scope}, {type}
78
+ * @param vars Objeto com os valores — chaves que não aparecem no template
79
+ * são ignoradas (não é erro)
80
+ * @returns String final, pronta pra uso como título/nome
81
+ * @throws NamingTemplateError se o template contém placeholder desconhecido
82
+ * OU se um placeholder usado não tem valor em vars
83
+ */
84
+ function applyNaming(template: string, vars: {
85
+ scope?: string;
86
+ type?: string;
87
+ }): string
88
+ ```
89
+
90
+ ### Regras invioláveis
91
+
92
+ 1. **Placeholder desconhecido lança erro.**
93
+ `"{type} {module}"` → `NamingTemplateError: unknown placeholder "{module}"`.
94
+ Nunca retorna a string literal `{module}` — isso vazaria inconsistência pros backends.
95
+
96
+ 2. **Placeholder sem valor lança erro.**
97
+ Template `"{scope} - {type}"` com `vars = { type: "TRD" }`
98
+ → `NamingTemplateError: placeholder {scope} required but not provided`.
99
+
100
+ 3. **Chaves extras em `vars` são ignoradas.**
101
+ Não é erro passar `vars.scope = "x"` pra um template que só usa `{type}`.
102
+ Isso permite que as skills passem o objeto cheio sem se preocupar com qual
103
+ template está sendo aplicado.
104
+
105
+ 4. **Substituição é literal — sem escape.**
106
+ Se o `scope` tem caracteres especiais pro backend (ex: `/` no filesystem,
107
+ `>` no Confluence), é responsabilidade do adapter sanitizar **depois** do
108
+ apply. Naming engine não conhece constraints de backend.
109
+
110
+ 5. **Case-sensitive.** `{Scope}` ≠ `{scope}`. Não normaliza.
111
+
112
+ 6. **Não suporta lógica.** Sem `{scope|upper}`, sem condicionais. Zero magic.
113
+
114
+ ---
115
+
116
+ ## Pseudo-código de referência
117
+
118
+ ```typescript
119
+ const PLACEHOLDER_RE = /\{(\w+)\}/g;
120
+ const KNOWN = new Set(["scope", "type"]);
121
+
122
+ function applyNaming(template: string, vars: Record<string, string | undefined>): string {
123
+ // 1. Detecta placeholders usados no template
124
+ const used = new Set<string>();
125
+ for (const m of template.matchAll(PLACEHOLDER_RE)) {
126
+ used.add(m[1]);
127
+ }
128
+
129
+ // 2. Valida placeholders desconhecidos
130
+ for (const p of used) {
131
+ if (!KNOWN.has(p)) {
132
+ throw new NamingTemplateError(
133
+ `unknown placeholder "{${p}}" in template "${template}" (supported: ${[...KNOWN].join(", ")})`
134
+ );
135
+ }
136
+ }
137
+
138
+ // 3. Valida valores ausentes
139
+ for (const p of used) {
140
+ if (vars[p] === undefined || vars[p] === "") {
141
+ throw new NamingTemplateError(
142
+ `placeholder {${p}} required but not provided`
143
+ );
144
+ }
145
+ }
146
+
147
+ // 4. Substitui
148
+ return template.replace(PLACEHOLDER_RE, (_, key) => vars[key]!);
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Exemplos (matriz de casos)
155
+
156
+ | Template | vars | Resultado | OK? |
157
+ |----------|------|-----------|-----|
158
+ | `out_{scope}` | `{scope:"app_barbearia"}` | `out_app_barbearia` | ✅ |
159
+ | `{scope} - {type}` | `{scope:"app_barbearia", type:"TRD"}` | `app_barbearia - TRD` | ✅ |
160
+ | `{type}` | `{type:"SDD"}` | `SDD` | ✅ (filesystem dentro de subpasta) |
161
+ | `[{type}] {scope}` | `{type:"PRD", scope:"feat_login"}` | `[PRD] feat_login` | ✅ |
162
+ | `{type}/{scope}` | `{type:"docs", scope:"arch"}` | `docs/arch` | ✅ (adapter sanitiza `/` depois) |
163
+ | `{scope} - {type}` | `{type:"TRD"}` | — | ❌ `NamingTemplateError: placeholder {scope} required` |
164
+ | `{type} {module}` | `{type:"TRD"}` | — | ❌ `NamingTemplateError: unknown placeholder "{module}"` |
165
+ | `hello world` | `{}` | `hello world` | ✅ (sem placeholders) |
166
+ | `out_{scope}` | `{scope:""}` | — | ❌ (string vazia é tratada como ausente) |
167
+
168
+ ---
169
+
170
+ ## Uso nas skills — matriz de quais templates usam quais placeholders
171
+
172
+ | Skill / operação | Template consumido | Placeholders usados |
173
+ |------------------|-------------------|---------------------|
174
+ | `/load` busca scope no Input | Nenhum — usa o nome do item **tal qual está** no backend | — |
175
+ | `/extract` publica TRD/PRD | `output_container` + `output_artifact` (type=TRD ou PRD) | `{scope}`, `{type}` |
176
+ | `/design` publica SDD | `output_container` + `output_artifact` (type=SDD) | `{scope}`, `{type}` |
177
+ | `/plan` publica Progresso | `output_container` + `output_artifact` (type=Progresso) | `{scope}`, `{type}` |
178
+ | `/load` grava scope folder local | `output_container` (reuso pro path local) | `{scope}` |
179
+
180
+ Skills **sempre** passam `{ scope, type }` cheio — o engine ignora o que o
181
+ template não usa.
182
+
183
+ **`/load` não formata Input** — só lê o nome real do item via
184
+ `adapter.listChildren()` e busca match exato com o argumento da skill.
185
+
186
+ ---
187
+
188
+ ## Fluxo ponta-a-ponta (exemplo)
189
+
190
+ Config:
191
+ ```yaml
192
+ naming:
193
+ output_container: "out_{scope}"
194
+ output_artifact: "{scope} - {type}"
195
+ ```
196
+
197
+ Cenário: `/new-project app_barbearia` → `/extract` → `/design` → `/plan`
198
+
199
+ | Passo | Template usado | vars | Resultado |
200
+ |-------|---------------|------|-----------|
201
+ | `/load` busca item no Input | nenhum | — | Encontra page "app_barbearia" pelo nome |
202
+ | `/load` grava local | `output_container` | `{scope:"app_barbearia"}` | Pasta `workspace/Input/out_app_barbearia/` |
203
+ | `/extract` cria container no Output | `output_container` | `{scope:"app_barbearia"}` | Page/pasta `out_app_barbearia` |
204
+ | `/extract` publica TRD | `output_artifact` | `{scope:"app_barbearia", type:"TRD"}` | `app_barbearia - TRD` (dentro do container) |
205
+ | `/design` publica SDD | `output_artifact` | `{scope:"app_barbearia", type:"SDD"}` | `app_barbearia - SDD` |
206
+ | `/plan` publica Progresso | `output_artifact` | `{scope:"app_barbearia", type:"Progresso"}` | `app_barbearia - Progresso` |
207
+
208
+ Resultado no Confluence:
209
+ ```
210
+ Output (page-mãe)
211
+ └── out_app_barbearia (container)
212
+ ├── app_barbearia - TRD
213
+ ├── app_barbearia - SDD
214
+ └── app_barbearia - Progresso
215
+ ```
216
+
217
+ Resultado no filesystem:
218
+ ```
219
+ workspace/Output/
220
+ └── out_app_barbearia/
221
+ ├── app_barbearia - TRD.md
222
+ ├── app_barbearia - SDD.md
223
+ └── app_barbearia - Progresso.md
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Onde vive o código da engine
229
+
230
+ Tom do MVP: implementação inline dentro do adapter layer, não arquivo próprio.
231
+ Cada adapter que precisa formatar título chama `applyNaming(template, vars)`.
232
+ Quando a implementação real for escrita (pós-P0.2), avaliar se extrai pra
233
+ `lib/naming.ts` ou mantém como função no registry.
234
+
235
+ ---
236
+
237
+ ## Referências
238
+
239
+ - Schema do manifest: `sfw.config.yml.example` (raiz do projeto)
240
+ - Interface do adapter: `.github/adapters/interface.md`
241
+ - Erros: `.github/adapters/errors.md` — `NamingTemplateError` é subclasse de `ValidationError`
@@ -0,0 +1,244 @@
1
+ # Adapter Registry
2
+
3
+ > Como o SFW resolve `adapter: "confluence"` no `sfw.config.yml` pra uma
4
+ > instância concreta de `SourceAdapter`.
5
+ >
6
+ > Este arquivo é a **especificação** do registry. A implementação real vive
7
+ > no bootstrap do kit (quando for codada — §9.9.P0.2+).
8
+
9
+ ---
10
+
11
+ ## Conceito
12
+
13
+ O usuário declara no manifest:
14
+
15
+ ```yaml
16
+ input:
17
+ adapter: confluence
18
+ config:
19
+ space_key: ST
20
+ parent_page_id: "360668"
21
+ ```
22
+
23
+ As skills **nunca** veem a string `"confluence"`. Elas recebem uma instância
24
+ que implementa `SourceAdapter` — já configurada, já validada. O registry é
25
+ a ponte entre os dois.
26
+
27
+ ---
28
+
29
+ ## Responsabilidades do registry
30
+
31
+ 1. **Descobrir adapters disponíveis** (builtin + futuros plugins)
32
+ 2. **Resolver** a string `adapter: X` na classe correta
33
+ 3. **Instanciar** o adapter passando o objeto `config` do manifest
34
+ 4. **Validar** chamando `adapter.validateConfig(config)` antes de devolver
35
+ 5. **Falhar cedo e rápido** se o adapter não existe ou config está errado
36
+
37
+ ---
38
+
39
+ ## Adapters built-in no MVP
40
+
41
+ | Chave no YAML | Classe | Arquivo de runbook | Status MVP |
42
+ |---------------|--------|-------------------|------------|
43
+ | `confluence` | `ConfluenceAdapter` | `.github/adapters/confluence.md` | P0.2 — reuso do validado em testes/barbearia |
44
+ | `filesystem` | `FilesystemAdapter` | `.github/adapters/filesystem.md` | P0.2 — novo, trivial |
45
+
46
+ **Apenas esses 2 no MVP.** Qualquer outro valor em `adapter:` → `ValidationError`
47
+ no load do manifest.
48
+
49
+ ---
50
+
51
+ ## Fluxo de resolução
52
+
53
+ ```
54
+ 1. Skill começa (ex: /load)
55
+ 2. Carrega sfw.config.yml
56
+ 3. Para cada seção que precisa de adapter (input, cada output.target):
57
+ a. Lê string `adapter: X`
58
+ b. Consulta registry: registry.get("X")
59
+ - Se X não existe → ValidationError: "unknown adapter 'X' (available: confluence, filesystem)"
60
+ c. Instancia: const a = new AdapterClass()
61
+ d. Valida config: a.validateConfig(target.config)
62
+ - Se inválido → ValidationError (propaga do adapter)
63
+ e. Armazena a instância configurada em memória pra reuso durante a skill
64
+ 4. Usa as instâncias via interface — nunca chama .name direto no hot path
65
+ ```
66
+
67
+ **Importante**: o registry é consultado **1 vez** por execução de skill.
68
+ Instâncias são cached pela duração da skill. Skills que rodam em sequência
69
+ (`/extract` → `/design`) podem re-instanciar — não há estado compartilhado
70
+ entre execuções (não deveria haver, por contrato).
71
+
72
+ ---
73
+
74
+ ## Contrato textual do registry
75
+
76
+ ```typescript
77
+ interface AdapterRegistry {
78
+ /**
79
+ * Retorna a classe do adapter (não instancia).
80
+ * @throws ValidationError se a chave não existir
81
+ */
82
+ get(key: string): AdapterClass;
83
+
84
+ /**
85
+ * Lista as chaves disponíveis. Usado em mensagens de erro.
86
+ */
87
+ available(): string[];
88
+
89
+ /**
90
+ * Registra um novo adapter. No MVP só é chamado internamente pros 2 builtin.
91
+ * Plugin system (P2) usaria isso pra carregar adapters externos.
92
+ */
93
+ register(key: string, cls: AdapterClass): void;
94
+ }
95
+
96
+ interface AdapterClass {
97
+ new(): SourceAdapter;
98
+ /** Chave que o adapter usa no YAML — deve bater com o que está no registry */
99
+ readonly adapterName: string;
100
+ }
101
+ ```
102
+
103
+ ### Inicialização builtin (pseudo-código)
104
+
105
+ ```typescript
106
+ const registry = new AdapterRegistry();
107
+ registry.register("confluence", ConfluenceAdapter);
108
+ registry.register("filesystem", FilesystemAdapter);
109
+
110
+ // quando o manifest é carregado:
111
+ function resolveAdapter(section: { adapter: string; config: object }): SourceAdapter {
112
+ const cls = registry.get(section.adapter); // throws se não existe
113
+ const instance = new cls();
114
+ instance.validateConfig(section.config); // throws se config errada
115
+ return instance;
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Declaração de config requerida (contrato por adapter)
122
+
123
+ Cada adapter **documenta** (em `.github/adapters/{name}.md`) quais campos
124
+ são obrigatórios e quais são opcionais na seção `config`. O registry não
125
+ valida isso — delega pro `validateConfig` do adapter. Isso evita duplicação
126
+ e mantém a responsabilidade localizada.
127
+
128
+ **Exemplo — ConfluenceAdapter**:
129
+ | Campo | Obrigatório | Tipo | Descrição |
130
+ |-------|:---:|------|-----------|
131
+ | `space_key` | ✅ | string | ex: `"ST"` |
132
+ | `parent_page_id` | ✅ | string | ID numérico da page pai |
133
+ | `recursive` | — | boolean | default `true` |
134
+ | `include_attachments` | — | boolean | default `true` |
135
+
136
+ **Exemplo — FilesystemAdapter**:
137
+ | Campo | Obrigatório | Tipo | Descrição |
138
+ |-------|:---:|------|-----------|
139
+ | `root_path` | ✅ | string | path absoluto ou relativo ao projeto |
140
+ | `glob` | — | string | default `**/*.md` |
141
+ | `watch` | — | boolean | default `false` (watch mode é P2) |
142
+
143
+ ---
144
+
145
+ ## Ordem de validação no load do manifest
146
+
147
+ Quando skill chama `loadConfig("sfw.config.yml")`:
148
+
149
+ 1. **Parse YAML.** Erro de sintaxe → `ValidationError` (nem chama registry).
150
+ 2. **Valida shape top-level.** Presença de `project`, `naming`, `input`, `output.targets[]`.
151
+ 3. **Resolve `input.adapter`** via registry + `validateConfig` do adapter.
152
+ 4. **Resolve cada `output.targets[i].adapter`** via registry + `validateConfig`.
153
+ 5. **Valida `naming.*`** — templates precisam ser strings não-vazias; placeholders
154
+ desconhecidos detectados agora (antecipa erro de runtime).
155
+ 6. **Valida coerência inter-seções**: ex., `output.targets[i].mode: auto` exige
156
+ `approval_mechanism` presente quando `publishes` contém TRD/PRD/SDD (artefatos
157
+ com gate de aprovação).
158
+
159
+ Tudo no load. Se qualquer passo falha, skill para antes de tocar backend.
160
+
161
+ ---
162
+
163
+ ## Config de exemplo resolvida
164
+
165
+ Input YAML:
166
+ ```yaml
167
+ project:
168
+ name: "Barbearia"
169
+ input:
170
+ adapter: confluence
171
+ config:
172
+ space_key: ST
173
+ parent_page_id: "360668"
174
+ output:
175
+ targets:
176
+ - name: confluence-mirror
177
+ adapter: confluence
178
+ config:
179
+ space_key: ST
180
+ parent_page_id: "294931"
181
+ publishes: [PRD, TRD, SDD, Progresso]
182
+ mode: auto
183
+ - name: local-mirror
184
+ adapter: filesystem
185
+ config:
186
+ root_path: "./mirror"
187
+ publishes: [PRD, TRD, SDD, Progresso, backlog]
188
+ mode: auto
189
+ ```
190
+
191
+ Após load:
192
+ ```typescript
193
+ const manifest = {
194
+ project: { name: "Barbearia" },
195
+ input: <ConfluenceAdapter instance, configured>,
196
+ outputs: [
197
+ { name: "confluence-mirror", adapter: <ConfluenceAdapter>, publishes: [...], mode: "auto" },
198
+ { name: "local-mirror", adapter: <FilesystemAdapter>, publishes: [...], mode: "auto" },
199
+ ],
200
+ };
201
+ ```
202
+
203
+ Skill chama `manifest.input.listChildren(...)` e `manifest.outputs[0].adapter.update(...)`.
204
+ A string `"confluence"` nunca aparece fora do registry.
205
+
206
+ ---
207
+
208
+ ## O que NÃO é responsabilidade do registry
209
+
210
+ - **Credenciais.** Adapter lê `.mcp.json` / env var direto. Registry nem vê.
211
+ - **Retry/backoff.** Cada adapter implementa. Registry devolve instância "crua".
212
+ - **Logging.** Skills logam. Adapter/registry silenciam (exceto em erro).
213
+ - **Connection pooling.** MVP é single-process single-skill. Se virar batch,
214
+ registry pode cachear. Não agora.
215
+
216
+ ---
217
+
218
+ ## Futuro (P2) — Plugin system
219
+
220
+ Quando aparecer o 3º caso real de backend não-builtin:
221
+
222
+ 1. `sfw.config.yml` ganha seção `plugins:`:
223
+ ```yaml
224
+ plugins:
225
+ - npm:@sfw/notion-adapter@1.2.0
226
+ - file:./local-adapters/sharepoint.js
227
+ ```
228
+ 2. Bootstrap do kit carrega plugins antes do manifest.
229
+ 3. Cada plugin exporta `{ key, class }` e chama `registry.register(...)`.
230
+ 4. Interface do adapter fica estável (breaking change vira major version).
231
+
232
+ **Decisão (2026-04-11)**: não implementar agora. Builtin resolve 95% dos casos
233
+ e prova que a interface está certa. Plugin system vira prioridade quando um
234
+ usuário real pedir.
235
+
236
+ ---
237
+
238
+ ## Referências
239
+
240
+ - Interface: `.github/adapters/interface.md`
241
+ - Erros: `.github/adapters/errors.md`
242
+ - Naming: `.github/adapters/naming.md`
243
+ - Manifest: `sfw.config.yml.example`
244
+ - Roadmap: `planodetarefas.md §9.9.P0.2` (implementação dos builtins)
@@ -0,0 +1,131 @@
1
+ # ============================================================================
2
+ # sfw.config.yml — Manifesto do projeto SFW
3
+ # ============================================================================
4
+ # Este arquivo define COMO o pipeline SFW fala com o mundo externo:
5
+ # - De onde vêm os insumos do PM/PO (input)
6
+ # - Pra onde vão os artefatos gerados (output.targets[])
7
+ # - Como os nomes dos itens são formados (naming)
8
+ #
9
+ # É commitável (zero segredos). Credenciais ficam em .mcp.json gitignored
10
+ # ou variáveis de ambiente exportadas ANTES de abrir o Claude Code.
11
+ #
12
+ # Gerado pelo CLI: `spec-first-claude init --name=X`
13
+ #
14
+ # Referências:
15
+ # - Interface do adapter: .github/adapters/interface.md
16
+ # - Adapters disponíveis: .github/adapters/registry.md
17
+ # - Templating de nomes: .github/adapters/naming.md
18
+ # - Erros: .github/adapters/errors.md
19
+ # ============================================================================
20
+
21
+ # ----------------------------------------------------------------------------
22
+ # project — identidade do projeto
23
+ # ----------------------------------------------------------------------------
24
+ project:
25
+ name: "Meu Projeto"
26
+
27
+ # ----------------------------------------------------------------------------
28
+ # naming — templates de nome (aplicados pela engine em .github/adapters/naming.md)
29
+ # ----------------------------------------------------------------------------
30
+ # Placeholders suportados: {scope}, {type}
31
+ # {scope} = nome do item no Input (ex: "app_barbearia", "feat_login")
32
+ # {type} = tipo do artefato (ex: "TRD", "SDD", "PRD", "Progresso")
33
+ # Qualquer outro placeholder → ValidationError no load do manifest.
34
+ #
35
+ # O usuário nomeia livremente os items no Input — o agent NÃO impõe naming.
36
+ # Estes templates controlam apenas o que o agent GERA no Output.
37
+ naming:
38
+ # Subpasta (ou page-mãe) criada no Output por scope
39
+ # Ex: "out_app_barbearia", "out_feat_login"
40
+ output_container: "out_{scope}"
41
+
42
+ # Nome do artefato DENTRO do container
43
+ # No Confluence: {scope} no nome evita colisão de títulos no space
44
+ # No filesystem: pode simplificar pra "{type}" (sem redundância de scope)
45
+ output_artifact: "{scope} - {type}"
46
+
47
+ # ----------------------------------------------------------------------------
48
+ # input — de onde vêm os insumos do PM/PO (single source no MVP)
49
+ # ----------------------------------------------------------------------------
50
+ # /load {scope} usa esta seção pra puxar o conteúdo pro filesystem local.
51
+ # Dev nunca edita o backend de input — só lê. PM owns este espaço.
52
+ #
53
+ # O agent faz listChildren(parent_page_id) e busca o scope pelo nome.
54
+ # Se o scope não existir no backend, /load falha com erro explícito.
55
+ input:
56
+ # Chave do adapter no registry. MVP: "confluence" | "filesystem"
57
+ adapter: confluence
58
+
59
+ # Config passada pro adapter.validateConfig(). Campos obrigatórios/opcionais
60
+ # são documentados no arquivo do adapter (ex: .github/adapters/confluence.md).
61
+ config:
62
+ space_key: ST
63
+ parent_page_id: "360668" # page-mãe que contém os scopes no Input
64
+ recursive: true # desce na árvore de scopes
65
+ include_attachments: true # baixa PNGs, PDFs, planilhas
66
+
67
+ # Cache local onde /load materializa os insumos
68
+ cache:
69
+ local_dir: "workspace/Input/"
70
+ log: ".ai/load-log.md" # append-only: id, version, sha256, timestamp
71
+ incremental: true # hash-based re-load (NOVO/MODIFICADO/INALTERADO)
72
+
73
+ # ----------------------------------------------------------------------------
74
+ # output — pra onde os artefatos vão (MVP: 1 target, multi-target em P2)
75
+ # ----------------------------------------------------------------------------
76
+ # Cada target recebe um subset de artefatos via `publishes`.
77
+ # Auto-publish é disparado pelas skills (/extract, /design, /plan).
78
+ # Agent owns este espaço. PM só aplica label `sfw-approved` pra aprovar.
79
+ #
80
+ # Premissa: 1 space = 1 projeto (no Confluence). Multi-projeto no mesmo
81
+ # space causa colisão de títulos — constraint do Confluence, não do SFW.
82
+ # Se necessário, o user diferencia no Input (nomes únicos) e o Output
83
+ # herda a unicidade via {scope} no naming.
84
+ output:
85
+ targets:
86
+
87
+ # --- Target 1: Confluence (espelho de aprovação pro time) ---------------
88
+ - name: confluence-mirror
89
+
90
+ adapter: confluence
91
+
92
+ config:
93
+ space_key: ST
94
+ parent_page_id: "294931" # page-mãe do Output no Confluence
95
+
96
+ # Whitelist de tipos que este target recebe.
97
+ # Tipos válidos: PRD, TRD, SDD, Progresso
98
+ # (tasks.md, docs/, projetos.yaml, specs/ ficam LOCAL ONLY —
99
+ # ver §9.9 "Boundary publish vs local")
100
+ publishes: [PRD, TRD, SDD, Progresso]
101
+
102
+ # auto — skill dispara publish automaticamente no fim
103
+ # manual — skill gera artefato local e pergunta se quer publicar
104
+ # off — target ignorado (offline-first natural)
105
+ mode: auto
106
+
107
+ # Estratégia de conflict detection:
108
+ # version — compara version do backend com publish-log antes de update
109
+ # hash — compara sha256 do conteúdo remoto com snapshot local
110
+ # none — sobrescreve cego (NÃO recomendado; perde drift humano)
111
+ conflict_detection: version
112
+
113
+ # Mecanismo de aprovação do artefato publicado:
114
+ # label — PM aplica label na página Confluence pra marcar aprovado
115
+ # none — sem aprovação formal (pipeline segue sem checar)
116
+ approval_mechanism: label
117
+ approval_label: "sfw-approved"
118
+
119
+ # --- Target 2: Filesystem mirror (opcional — time offline/backup) -------
120
+ # Descomente pra ativar. Útil pra testes E2E com FilesystemAdapter como
121
+ # mock natural — zero lib de mock, zero stub.
122
+ #
123
+ # - name: local-mirror
124
+ # adapter: filesystem
125
+ # config:
126
+ # root_path: "./mirror"
127
+ # glob: "**/*.md"
128
+ # publishes: [PRD, TRD, SDD, Progresso]
129
+ # mode: auto
130
+ # conflict_detection: hash
131
+ # approval_mechanism: none