spec-first-copilot 0.5.0-beta.2 → 0.5.0-beta.4
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/lib/init.js +2 -2
- package/package.json +1 -1
- package/templates/.github/adapters/confluence.md +273 -0
- package/templates/.github/adapters/errors.md +234 -0
- package/templates/.github/adapters/filesystem.md +353 -0
- package/templates/.github/adapters/interface.md +301 -0
- package/templates/.github/adapters/naming.md +241 -0
- package/templates/.github/adapters/registry.md +244 -0
- package/templates/.github/copilot-instructions.md +4 -5
- package/templates/.github/skills/sf-new-project/SKILL.md +128 -0
- package/templates/sfw.config.yml.example +131 -0
- package/templates/.github/skills/sf-setup-projeto/SKILL.md +0 -123
- package/templates/workspace/Input/setup_projeto/.gitkeep +0 -0
|
@@ -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)
|
|
@@ -24,7 +24,7 @@ Verificar que TODAS as 9 skills estão acessíveis:
|
|
|
24
24
|
|
|
25
25
|
| Skill | Caminho |
|
|
26
26
|
|-------|---------|
|
|
27
|
-
| `/sf-
|
|
27
|
+
| `/sf-new-project` | `.github/skills/sf-new-project/SKILL.md` |
|
|
28
28
|
| `/sf-feature` | `.github/skills/sf-feature/SKILL.md` |
|
|
29
29
|
| `/sf-extract` | `.github/skills/sf-extract/SKILL.md` |
|
|
30
30
|
| `/sf-design` | `.github/skills/sf-design/SKILL.md` |
|
|
@@ -77,7 +77,7 @@ Nenhum código é escrito sem especificação aprovada (SDD).
|
|
|
77
77
|
|
|
78
78
|
```
|
|
79
79
|
workspace/Input/ (qualquer arquivo)
|
|
80
|
-
↓ /sf-
|
|
80
|
+
↓ /sf-new-project <nome> (TRD — bootstrap técnico) ou /sf-feature <nome> (PRD — feature)
|
|
81
81
|
/sf-discovery → análise profunda dos insumos (RECOMENDADO, opcional)
|
|
82
82
|
↓
|
|
83
83
|
/sf-extract → PRD ou TRD em workspace/Output/{nome}/ (checkpoint — usuário revisa e aprova)
|
|
@@ -94,7 +94,7 @@ workspace/Input/ (qualquer arquivo)
|
|
|
94
94
|
|
|
95
95
|
| Skill | Tipo | O que faz |
|
|
96
96
|
|-------|------|-----------|
|
|
97
|
-
| `/sf-
|
|
97
|
+
| `/sf-new-project <nome>` | Orquestrador | Bootstrap técnico: cria .context.md tipo TRD, chama /sf-extract, para no checkpoint |
|
|
98
98
|
| `/sf-feature <nome>` | Orquestrador | Feature: cria .context.md tipo PRD, chama /sf-extract, para no checkpoint |
|
|
99
99
|
| `/sf-extract <nome>` | Atômica | Lê workspace/Input/{nome}/ → gera PRD ou TRD. Re-executável para novos insumos |
|
|
100
100
|
| `/sf-design <nome>` | Atômica | Pergunta aprovação → gera SDD a partir do PRD/TRD |
|
|
@@ -168,8 +168,7 @@ specs/ ← AGENT CONTRACTS (projeções do SDD pro coder)
|
|
|
168
168
|
|
|
169
169
|
workspace/ ← TEAM CONTENT (futuro wiki)
|
|
170
170
|
├── Input/ ← Insumos brutos (QUALQUER formato — nunca modificar)
|
|
171
|
-
│
|
|
172
|
-
│ └── feat_*/ ← Insumos por feature
|
|
171
|
+
│ └── {nome}/ ← Nomeado livremente pelo usuário (ex: app_barbearia, feat_login)
|
|
173
172
|
└── Output/ ← Artefatos humanos gerados pelo workflow
|
|
174
173
|
└── {nome}/
|
|
175
174
|
├── PRD.md ou TRD.md ← Requirements (gerado pelo /sf-extract)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-new-project
|
|
3
|
+
description: |
|
|
4
|
+
Bootstrap técnico do projeto. Recebe nome do scope no Input, cria contexto TRD,
|
|
5
|
+
chama /sf-extract, para no checkpoint.
|
|
6
|
+
Trigger: /sf-new-project <nome>
|
|
7
|
+
author: GustavoMaritan
|
|
8
|
+
version: 2.0.0
|
|
9
|
+
date: 2026-04-12
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Skill: /sf-new-project <nome>
|
|
13
|
+
|
|
14
|
+
> Orquestrador de bootstrap técnico do projeto.
|
|
15
|
+
> Cria contexto TRD a partir dos insumos em `workspace/Input/{nome}/`,
|
|
16
|
+
> chama `/sf-extract` e para no checkpoint de aprovação.
|
|
17
|
+
>
|
|
18
|
+
> Simétrico ao `/sf-feature <nome>` — a diferença é que gera TRD (perfil técnico)
|
|
19
|
+
> em vez de PRD (perfil de produto).
|
|
20
|
+
|
|
21
|
+
## Tipo
|
|
22
|
+
Orquestrador (primeira etapa do pipeline)
|
|
23
|
+
|
|
24
|
+
## Uso
|
|
25
|
+
```
|
|
26
|
+
/sf-new-project <nome>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`<nome>` = nome da pasta no Input. O usuário nomeia livremente
|
|
30
|
+
(ex: `app_barbearia`, `meu_sistema`, `api_pagamentos`).
|
|
31
|
+
|
|
32
|
+
## Pré-condições
|
|
33
|
+
|
|
34
|
+
| # | Validação | Se falhar |
|
|
35
|
+
|---|-----------|-----------|
|
|
36
|
+
| 1 | `workspace/Input/{nome}/` existe | Parar → "Crie a pasta workspace/Input/{nome}/ e adicione seus insumos" |
|
|
37
|
+
| 2 | Pasta contém pelo menos 1 arquivo (ignorar .gitkeep) | Parar → "Adicione insumos na pasta (aceitos: .md, .txt, .sql, .xml, .html, .json, .csv, .png, .jpg, .pdf — qualquer formato)" |
|
|
38
|
+
| 3 | `workspace/Output/{nome}/.context.md` não existe ou status é `not_started` | Parar → "Este escopo já está em andamento. Verifique o status em .context.md" |
|
|
39
|
+
|
|
40
|
+
## Passos
|
|
41
|
+
|
|
42
|
+
### 1. Criar estrutura
|
|
43
|
+
```
|
|
44
|
+
workspace/Output/{nome}/
|
|
45
|
+
├── .context.md ← criado agora
|
|
46
|
+
└── (TRD.md será criado pelo /sf-extract)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Criar `.context.md`
|
|
50
|
+
```yaml
|
|
51
|
+
---
|
|
52
|
+
nome: "{nome}"
|
|
53
|
+
tipo: "project"
|
|
54
|
+
documento: "TRD"
|
|
55
|
+
pm_path: "workspace/Input/{nome}/"
|
|
56
|
+
status: "not_started"
|
|
57
|
+
ultima_skill: "/sf-new-project"
|
|
58
|
+
atualizado_em: "{{ISO_DATETIME}}"
|
|
59
|
+
---
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Sugerir /sf-discovery (RECOMENDADO)
|
|
63
|
+
|
|
64
|
+
Antes de extrair, perguntar ao usuário:
|
|
65
|
+
```
|
|
66
|
+
Insumos encontrados em workspace/Input/{nome}/.
|
|
67
|
+
|
|
68
|
+
Recomendo rodar /sf-discovery antes da extração para:
|
|
69
|
+
- Análise profunda dos insumos (parseia drawio, XML, SQL completo)
|
|
70
|
+
- Identificar gaps e contradições antes de estruturar
|
|
71
|
+
- Validar entendimento com você (mapa do sistema)
|
|
72
|
+
|
|
73
|
+
Quer rodar /sf-discovery workspace/Input/{nome}/ agora? (s/n)
|
|
74
|
+
Se SIM → rodar discovery, salvar discovery.md, depois continuar com extract
|
|
75
|
+
Se NÃO → pular direto para /sf-extract (ok para insumos simples)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Se o usuário aceitar:
|
|
79
|
+
- Rodar `/sf-discovery workspace/Input/{nome}/`
|
|
80
|
+
- Aguardar conclusão — discovery.md salvo em `workspace/Output/{nome}/`
|
|
81
|
+
- Continuar para o passo 4
|
|
82
|
+
|
|
83
|
+
### 4. Chamar `/sf-extract {nome}`
|
|
84
|
+
O `/sf-extract` lê os insumos + discovery.md (se existir), gera o TRD e atualiza status para `extract_done`.
|
|
85
|
+
|
|
86
|
+
### 5. CHECKPOINT — Parar e informar o usuário
|
|
87
|
+
Mensagem ao usuário:
|
|
88
|
+
```
|
|
89
|
+
TRD gerado em workspace/Output/{nome}/TRD.md
|
|
90
|
+
|
|
91
|
+
Revise o documento. Quando estiver satisfeito, execute:
|
|
92
|
+
/sf-design {nome}
|
|
93
|
+
|
|
94
|
+
Se precisar adicionar mais insumos, coloque na pasta workspace/Input/{nome}/
|
|
95
|
+
e execute:
|
|
96
|
+
/sf-extract {nome}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**O orquestrador encerra aqui.** O restante do pipeline é responsabilidade do usuário chamar as skills atômicas na ordem:
|
|
100
|
+
1. `/sf-design {nome}` (pergunta aprovação → gera SDD + docs/ + projetos.yaml)
|
|
101
|
+
2. `/sf-plan {nome}` (gera tasks com campo Repo por task)
|
|
102
|
+
3. `/sf-dev {nome}` (INFRA-001 cria/clona repos em projetos/, executa tasks nos repos corretos)
|
|
103
|
+
|
|
104
|
+
## Saídas diretas desta skill
|
|
105
|
+
- `workspace/Output/{nome}/.context.md`
|
|
106
|
+
- `workspace/Output/{nome}/TRD.md` (via /sf-extract)
|
|
107
|
+
- `workspace/Output/{nome}/.extract-log.md` (via /sf-extract)
|
|
108
|
+
|
|
109
|
+
## Saídas indiretas (skills subsequentes)
|
|
110
|
+
- `sdd.md` (via /sf-design)
|
|
111
|
+
- `projetos.yaml` (via /sf-design — manifesto de repos)
|
|
112
|
+
- `docs/` populado (via /sf-design)
|
|
113
|
+
- `specs/{nome}/tasks.md` + `Progresso.md` (via /sf-plan)
|
|
114
|
+
- `projetos/` com repos criados/clonados (via /sf-dev INFRA-001)
|
|
115
|
+
|
|
116
|
+
## Erros
|
|
117
|
+
|
|
118
|
+
| Erro | Ação |
|
|
119
|
+
|------|------|
|
|
120
|
+
| workspace/Input/{nome}/ não existe | Parar, instruir criação |
|
|
121
|
+
| workspace/Input/{nome}/ vazio | Parar, listar formatos aceitos |
|
|
122
|
+
| Pipeline já iniciado | Parar, mostrar status atual do .context.md |
|
|
123
|
+
|
|
124
|
+
## Notas
|
|
125
|
+
- docs/ é gerado pelo /sf-design (passo 3), não por tasks DOC
|
|
126
|
+
- `projetos.yaml` é gerado pelo /sf-design (passo 3b) — mapeia serviços para repos
|
|
127
|
+
- Repos são criados/clonados pelo /sf-dev (INFRA-001) dentro de `projetos/`
|
|
128
|
+
- O `/sf-merge-delta` NÃO se aplica ao new-project (é criação, não atualização)
|