@brunosps00/dev-workflow 1.6.0 → 1.7.1
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/.claude-plugin/plugin.json +2 -2
- package/README.md +2 -2
- package/lib/constants.js +2 -0
- package/lib/install-deps.js +37 -1
- package/lib/install-open-design.js +128 -0
- package/lib/utils.js +46 -6
- package/package.json +2 -2
- package/scaffold/en/commands/dw-help.md +3 -1
- package/scaffold/en/commands/dw-new-project.md +24 -21
- package/scaffold/en/commands/dw-open-design.md +324 -0
- package/scaffold/en/templates/project-onepager.md +12 -11
- package/scaffold/pt-br/commands/dw-help.md +3 -1
- package/scaffold/pt-br/commands/dw-new-project.md +24 -21
- package/scaffold/pt-br/commands/dw-open-design.md +323 -0
- package/scaffold/pt-br/templates/project-onepager.md +12 -11
- package/scaffold/scripts/open-design/capture-current.mjs +170 -0
- package/scaffold/scripts/open-design/gate-prototype.mjs +223 -0
- package/scaffold/skills/docker-compose-recipes/SKILL.md +6 -5
- package/scaffold/skills/docker-compose-recipes/references/compose-composition.md +7 -7
- package/scaffold/skills/docker-compose-recipes/references/env-conventions.md +3 -1
- package/scaffold/skills/docker-compose-recipes/references/healthcheck-patterns.md +4 -4
- package/scaffold/skills/docker-compose-recipes/services/mailpit.yml +4 -4
- package/scaffold/skills/docker-compose-recipes/services/postgres-pgvector.yml +31 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<system_instructions>
|
|
2
|
+
Voce e o **runner headless do Open Design (`od`)** para gerar e iterar prototipos HTML em uma pasta de projeto,
|
|
3
|
+
sem abrir Electron e sem depender do estado da GUI do usuario. Use o daemon do `nexu-io/open-design` em
|
|
4
|
+
`--headless`, importe a pasta alvo uma vez, rode briefs single ou batch em serie, verifique o arquivo produzido,
|
|
5
|
+
rode gate visual Playwright/Firefox e **PARE para o gate do dono** antes de qualquer commit.
|
|
6
|
+
|
|
7
|
+
<critical>NUNCA use o estado da GUI do usuario. Sempre rode com `OD_DATA_DIR` isolado por projeto, por exemplo `.dw/.open-design/data/`.</critical>
|
|
8
|
+
<critical>NUNCA commite prototipo reprovado. O gate e dual >=9: gate automatico + avaliacao visual propria >=9, depois gate do dono.</critical>
|
|
9
|
+
<critical>O agente (`codex` ou `claude`) deve ser sempre explicito em `od run start --agent <agent>`. Se o usuario nao passar, resolva de config/estado do projeto; fallback `codex`.</critical>
|
|
10
|
+
<critical>NUNCA despache o pedido cru do usuario para o `od`. O valor deste comando e refinar o prompt primeiro: ancore no codigo real, estruture o brief, qualifique estados/temas/a11y/deep-link, persista o brief revisavel e so entao anexe o apendice headless.</critical>
|
|
11
|
+
|
|
12
|
+
## Inputs
|
|
13
|
+
|
|
14
|
+
| Flag/argumento | Default | Descricao |
|
|
15
|
+
|---|---:|---|
|
|
16
|
+
| `--target <dir>` | `open-design/` | Pasta importada como projeto externo e onde os HTMLs serao escritos. |
|
|
17
|
+
| `--brief <file>` | nenhum | Um brief markdown para uma run single. |
|
|
18
|
+
| `--brief-glob <glob>` | nenhum | Batch em serie. Expanda para lista estavel e rode um prompt por vez. |
|
|
19
|
+
| `--output <file>` | obrigatorio no brief/apendice | HTML esperado; verifique no filesystem depois da run. |
|
|
20
|
+
| `--agent <codex|claude>` | estado/config -> `codex` | Agente repassado ao `od run start --agent`. |
|
|
21
|
+
| `--platform <text>` | `Web responsivo desktop-first e mobile-safe` | Resposta pre-preenchida para o discovery da skill `web-prototype`. |
|
|
22
|
+
| `--deep-link-id <id>` | nenhum | Valor usado no gate comportamental `?aberto=<id>` + Esc. |
|
|
23
|
+
| `--refactor` | falso | Modo redesign/refactor de tela existente; captura prints atuais e ancora o brief no estado visivel + codigo real. |
|
|
24
|
+
| `--url <url>` | nenhum | URL de app rodando para capturar prints atuais via Playwright Firefox. |
|
|
25
|
+
| `--viewports <lista>` | `1440x900,375x812` | Viewports separados por virgula para `--refactor --url`; expanda conforme a dor (ex.: `1920x1080` para tabela larga). |
|
|
26
|
+
| `--screenshot <path>` | repetivel | Print atual ja capturado quando nao houver `--url`; copie para `_refs/<slug>/`. |
|
|
27
|
+
|
|
28
|
+
## 0. Refinar o brief
|
|
29
|
+
|
|
30
|
+
Antes de qualquer pre-flight de daemon ou chamada ao `od`, transforme a intencao do usuario em um brief qualificado.
|
|
31
|
+
Nao envie a frase crua do usuario para o runner.
|
|
32
|
+
|
|
33
|
+
Contrato obrigatorio:
|
|
34
|
+
|
|
35
|
+
| Etapa | Exigencia |
|
|
36
|
+
|---|---|
|
|
37
|
+
| Ancorar na realidade | Se a tela existe, leia o codigo real dela: colunas, enums, acoes, estados, permissoes, contracts e dominio. Leia tambem design system, prototipos irmaos e guias de design do projeto. E proibido inventar campo, status ou acao. |
|
|
38
|
+
| Estruturar | Reescreva o pedido no esqueleto padrao abaixo, preenchendo paths reais e arquivo de saida exato. |
|
|
39
|
+
| Qualificar | Cubra loading/skeleton, empty/vazio, erro/falha, light + dark, a11y, limites do design system local (ex.: <=6 cards/filtros), responsividade e deep-link. |
|
|
40
|
+
| Persistir | Salve o brief refinado dentro do projeto, por exemplo `<target>/PROMPT-<slug>.md`. Ele e entregavel revisavel pelo dono, nao arquivo descartavel. |
|
|
41
|
+
| Revisar se ja vier pronto | Se o usuario trouxer um brief ja qualificado, faca uma revisao rapida por checklist, ajuste apenas lacunas e persista a versao final. |
|
|
42
|
+
|
|
43
|
+
Esqueleto padrao do brief refinado:
|
|
44
|
+
|
|
45
|
+
```markdown
|
|
46
|
+
# <titulo da tela/prototipo>
|
|
47
|
+
|
|
48
|
+
Cole este brief no Open Design. Arquivo de saida exato: `<OUTPUT_FILE>`.
|
|
49
|
+
|
|
50
|
+
## 1. Por que
|
|
51
|
+
<dor real do usuario/produto; problema observavel que este prototipo precisa resolver>
|
|
52
|
+
|
|
53
|
+
## 2. Decisao de interacao
|
|
54
|
+
<padrao de navegacao, abertura, selecao, filtros, ordenacao, edicao e confirmacoes>
|
|
55
|
+
|
|
56
|
+
## 3. Layout/lista
|
|
57
|
+
Tabela/lista baseada apenas em colunas REAIS:
|
|
58
|
+
|
|
59
|
+
| Coluna real | Origem/contract | Como aparece | Estado/limite |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| <nome> | <arquivo/API/schema> | <texto, badge, acao> | <truncate, vazio, erro> |
|
|
62
|
+
|
|
63
|
+
## 4. Detalhe/acoes
|
|
64
|
+
<drawer/modal/pagina de detalhe; acoes contextuais permitidas por estado/permissao real>
|
|
65
|
+
|
|
66
|
+
## 5. Fidelidade de dominio
|
|
67
|
+
Para o mock nao mentir: usar enums/status reais, dados de exemplo realistas no idioma do projeto e incluir estados raros/quebrados que existem no dominio.
|
|
68
|
+
|
|
69
|
+
## 6. Direcao visual
|
|
70
|
+
<tokens, componentes, densidade, referencias herdadas, prototipos irmaos e limites do design system>
|
|
71
|
+
|
|
72
|
+
## Entregar
|
|
73
|
+
- `<OUTPUT_FILE>`
|
|
74
|
+
- `<OUTPUT_FILE>.artifact.json` se houver sidecar
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
So depois de persistir esse arquivo anexe o apendice headless em `.dw/.open-design/runs/` e despache a run.
|
|
78
|
+
|
|
79
|
+
### 0R. Modo refactor/redesign
|
|
80
|
+
|
|
81
|
+
Quando `--refactor` estiver presente, a fase 0 continua obrigatoria. Prints complementam a ancoragem no codigo; eles
|
|
82
|
+
nao substituem leitura de contracts, enums, permissoes, estados, design system e prototipos irmaos.
|
|
83
|
+
|
|
84
|
+
Capture ou copie os prints atuais para dentro da pasta importada, em `_refs/<slug>/`, antes de persistir o brief:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
node .dw/scripts/open-design/capture-current.mjs \
|
|
88
|
+
--target "$TARGET_DIR" \
|
|
89
|
+
--slug "<slug>" \
|
|
90
|
+
--url "<url>" \
|
|
91
|
+
--viewports "1440x900,375x812"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Sem URL de app rodando, aceite prints prontos repetindo `--screenshot`:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
node .dw/scripts/open-design/capture-current.mjs \
|
|
98
|
+
--target "$TARGET_DIR" \
|
|
99
|
+
--slug "<slug>" \
|
|
100
|
+
--screenshot "./antes-1440-light.png" \
|
|
101
|
+
--screenshot "./antes-375-dark.png"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Regras de captura:
|
|
105
|
+
|
|
106
|
+
| Caso | Exigencia |
|
|
107
|
+
|---|---|
|
|
108
|
+
| Viewports | Default sensato: desktop `1440x900` + mobile `375x812`. O agente escolhe/expande conforme a dor: bug mobile inclui `375x812`; tabela larga inclui `1920x1080`; tablet/console inclui `768x1024`. |
|
|
109
|
+
| Temas | Capture light e dark quando aplicavel. O helper usa `light,dark` por default e grava nomes como `_refs/<slug>/atual-1440-light.png` e `_refs/<slug>/atual-375-dark.png`. |
|
|
110
|
+
| Caminho robusto | O agente do `od` le imagens do filesystem do projeto. Referencie caminhos relativos dentro do target; nao dependa de flag de imagem no `run start`. |
|
|
111
|
+
| Higiene | `_refs/` e material de referencia nao devem virar prototipo. Se o projeto-alvo versiona prototipos finais, documente limpeza ou adicione `<target>/_refs/` ao `.gitignore` conforme a convencao local. |
|
|
112
|
+
|
|
113
|
+
O brief refinado do modo refactor deve adicionar este bloco:
|
|
114
|
+
|
|
115
|
+
```markdown
|
|
116
|
+
## Referencias da tela atual
|
|
117
|
+
Abra e ANALISE os prints antes de escrever:
|
|
118
|
+
- `_refs/<slug>/atual-1440-light.png`
|
|
119
|
+
- `_refs/<slug>/atual-375-dark.png`
|
|
120
|
+
|
|
121
|
+
## O que esta errado hoje
|
|
122
|
+
<dor apontando elementos visiveis nos prints: densidade, hierarquia, tabela, filtros, estados, contraste, mobile, etc.>
|
|
123
|
+
|
|
124
|
+
## A ideia da mudanca
|
|
125
|
+
<o que muda e por que; decisao de interacao/visual proposta pelo usuario ou pelo agente apos ler codigo e prints>
|
|
126
|
+
|
|
127
|
+
## Preservar
|
|
128
|
+
<fluxos, campos, permissoes, estados, copys, affordances e integracoes que nao podem mudar>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Pre-flight
|
|
132
|
+
|
|
133
|
+
| Checagem | Acao |
|
|
134
|
+
|---|---|
|
|
135
|
+
| Resolver `od` | `OD_CLI_DIR` vence. Senao use `~/.dw/vendor/open-design`. Se `apps/daemon/bin/od.mjs` nao existir, pare e mande rodar `dev-workflow install-deps`. |
|
|
136
|
+
| Prerequisitos | O checkout do Open Design exige Node `~24` e pnpm `>=10.33`. Se faltarem, pare com instrucoes objetivas. |
|
|
137
|
+
| Estado isolado | Crie `.dw/.open-design/`, `.dw/.open-design/data/`, `.dw/.open-design/runs/` e `.dw/.open-design/state.json`. |
|
|
138
|
+
| Porta | Escolha porta livre em `127.0.0.1`; persista `daemonUrl` e `port` no state. |
|
|
139
|
+
| Daemon vivo | Se `od daemon status --daemon-url <url> --json` retornar `ok: true`, reuse. Senao inicie novo daemon. |
|
|
140
|
+
|
|
141
|
+
Comando base do daemon:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
OD_DATA_DIR="$PWD/.dw/.open-design/data" \
|
|
145
|
+
OD_CHAT_RUN_INACTIVITY_TIMEOUT_MS=1800000 \
|
|
146
|
+
node "$OD_DIR/apps/daemon/bin/od.mjs" daemon start \
|
|
147
|
+
--headless \
|
|
148
|
+
--host 127.0.0.1 \
|
|
149
|
+
--port "$PORT"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Ruido conhecido: `codex_core::shell_snapshot` pode logar `ERROR` de sintaxe. Isso e benigno; nao aborte se a run
|
|
153
|
+
continua e o arquivo alvo e criado.
|
|
154
|
+
|
|
155
|
+
## Import idempotente
|
|
156
|
+
|
|
157
|
+
Use `project import-folder` na pasta alvo resolvida absoluta. Default: `<repo>/open-design/`.
|
|
158
|
+
|
|
159
|
+
Persistir em `.dw/.open-design/state.json`:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"schema_version": "1.0",
|
|
164
|
+
"targetDir": "/abs/repo/open-design",
|
|
165
|
+
"projectId": "<project.id>",
|
|
166
|
+
"conversationId": "<conversationId>",
|
|
167
|
+
"daemonUrl": "http://127.0.0.1:<port>",
|
|
168
|
+
"port": 17556,
|
|
169
|
+
"agent": "codex"
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Se `projectId` existe no state e `od project info <id> --daemon-url <url> --json` funciona para o mesmo
|
|
174
|
+
`targetDir`, reuse. Caso contrario, reimporte:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
node "$OD_DIR/apps/daemon/bin/od.mjs" project import-folder "$TARGET_DIR" \
|
|
178
|
+
--daemon-url "$OD_URL" \
|
|
179
|
+
--name "$(basename "$TARGET_DIR")" \
|
|
180
|
+
--skill web-prototype \
|
|
181
|
+
--design-system default \
|
|
182
|
+
--json
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Capture `project.id` e `conversationId`; nao dependa de estado global do app.
|
|
186
|
+
|
|
187
|
+
## Apendice headless obrigatorio
|
|
188
|
+
|
|
189
|
+
Anexe este bloco ao final de cada brief em um arquivo temporario dentro de `.dw/.open-design/runs/`.
|
|
190
|
+
Preencha `<PLATFORM>`, `<OUTPUT_FILE>` e referencias irma se houver.
|
|
191
|
+
|
|
192
|
+
```markdown
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Apendice headless para Open Design
|
|
196
|
+
|
|
197
|
+
Plataforma-alvo: <PLATFORM>.
|
|
198
|
+
|
|
199
|
+
Nao faca discovery e nao faca perguntas. Nao emita `<question-form>`. Siga este brief com as respostas acima e
|
|
200
|
+
entregue arquivo primeiro.
|
|
201
|
+
|
|
202
|
+
Arquivo de saida obrigatorio: `<OUTPUT_FILE>`.
|
|
203
|
+
|
|
204
|
+
Contrato file-first:
|
|
205
|
+
- Escreva o HTML diretamente no filesystem do projeto, exatamente em `<OUTPUT_FILE>`.
|
|
206
|
+
- Nao altere arquivos existentes, exceto quando este prompt for explicitamente uma iteracao cirurgica sobre um arquivo alvo.
|
|
207
|
+
- Se precisar criar sidecar, mantenha `<OUTPUT_FILE>.artifact.json` coerente.
|
|
208
|
+
- Nao responda apenas com bloco `<artifact>` textual; run `succeeded` sem arquivo e falha.
|
|
209
|
+
|
|
210
|
+
Para evitar timeout headless:
|
|
211
|
+
- Escreva arquivos grandes em partes, com edicoes de aproximadamente 150 linhas por chamada.
|
|
212
|
+
- Mantenha progresso visivel ate finalizar o HTML.
|
|
213
|
+
|
|
214
|
+
Contexto de consistencia:
|
|
215
|
+
- Leia prototipos irmaos em `prototipos/` ou na pasta indicada no brief antes de definir padroes visuais.
|
|
216
|
+
- Preserve o sistema visual local quando houver referencia proxima.
|
|
217
|
+
|
|
218
|
+
Deep-link obrigatorio:
|
|
219
|
+
- Implemente `?aberto=<id>` para abrir o drawer/modal do item correto.
|
|
220
|
+
- Ao fechar com Esc ou acao de fechar, limpe o parametro com `history.replaceState`.
|
|
221
|
+
- O drawer/modal deve usar `role="dialog"` e atributos ARIA adequados.
|
|
222
|
+
- Inclua estados skeleton/loading, empty/vazio e erro/falha.
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Run single
|
|
226
|
+
|
|
227
|
+
Sempre passe `--agent` explicitamente:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
node "$OD_DIR/apps/daemon/bin/od.mjs" run start \
|
|
231
|
+
--daemon-url "$OD_URL" \
|
|
232
|
+
--project "$PROJECT_ID" \
|
|
233
|
+
--conversation "$CONVERSATION_ID" \
|
|
234
|
+
--agent "$AGENT" \
|
|
235
|
+
--skill web-prototype \
|
|
236
|
+
--design-system default \
|
|
237
|
+
--prompt-file "$PROMPT_WITH_APPENDIX" \
|
|
238
|
+
--follow \
|
|
239
|
+
--json | tee ".dw/.open-design/runs/<slug>.jsonl"
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Depois da run, confira o arquivo. **Nao aceite apenas status `succeeded`:**
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
test -s "$TARGET_DIR/<OUTPUT_FILE>"
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Registre no JSONL ou em sidecar de auditoria: brief, prompt com apendice, agente, daemonUrl, projectId,
|
|
249
|
+
conversationId, output esperado, status final e resultado da verificacao do arquivo.
|
|
250
|
+
|
|
251
|
+
## Batch
|
|
252
|
+
|
|
253
|
+
Para `--brief-glob`, expanda os briefs, ordene por nome e rode em serie. Pare no primeiro caso em que:
|
|
254
|
+
|
|
255
|
+
| Condicao | Resultado |
|
|
256
|
+
|---|---|
|
|
257
|
+
| run falha | `BLOCKED` com log JSONL |
|
|
258
|
+
| run diz sucesso mas arquivo nao existe ou esta vazio | `FINDINGS` e follow-up cirurgico ou `BLOCKED` |
|
|
259
|
+
| gate visual <9 | `FINDINGS`; iterar antes de continuar |
|
|
260
|
+
|
|
261
|
+
Nao rode prompts em paralelo no mesmo projeto `od`; preserve auditabilidade e contexto.
|
|
262
|
+
|
|
263
|
+
## Iteracao cirurgica
|
|
264
|
+
|
|
265
|
+
Use follow-up run no mesmo `projectId`/`conversationId`, com prompt curto e arquivo alvo explicito:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
node "$OD_DIR/apps/daemon/bin/od.mjs" run start \
|
|
269
|
+
--daemon-url "$OD_URL" \
|
|
270
|
+
--project "$PROJECT_ID" \
|
|
271
|
+
--conversation "$CONVERSATION_ID" \
|
|
272
|
+
--agent "$AGENT" \
|
|
273
|
+
--skill web-prototype \
|
|
274
|
+
--design-system default \
|
|
275
|
+
--prompt-file ".dw/.open-design/runs/<slug>.followup.md" \
|
|
276
|
+
--follow \
|
|
277
|
+
--json | tee -a ".dw/.open-design/runs/<slug>.jsonl"
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
O follow-up deve dizer: arquivo unico, mudanca exata, nao tocar prototipos irmaos, manter deep-link e estados.
|
|
281
|
+
|
|
282
|
+
## Gate visual obrigatorio
|
|
283
|
+
|
|
284
|
+
Execute o gate automatico:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
node .dw/scripts/open-design/gate-prototype.mjs \
|
|
288
|
+
--file "$TARGET_DIR/<OUTPUT_FILE>" \
|
|
289
|
+
--deep-link-id "<id-real>" \
|
|
290
|
+
--expected-text "<texto-do-item>" \
|
|
291
|
+
--out ".dw/.open-design/gate/<slug>"
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
O gate cobre:
|
|
295
|
+
|
|
296
|
+
| Camada | Exigencia |
|
|
297
|
+
|---|---|
|
|
298
|
+
| Estrutural | `<title>`, `aberto`, `replaceState`, `role=dialog`/ARIA, skeleton/loading, empty/vazio, erro/falha. |
|
|
299
|
+
| Screenshot | Firefox headless em light e dark, com `colorScheme` e `data-theme`. |
|
|
300
|
+
| Comportamental | `?aberto=<id>` abre o item certo; Esc fecha e limpa o parametro. |
|
|
301
|
+
| Avaliacao | Automatico PASS + sua nota visual >=9. Se abaixo de 9, faca follow-up cirurgico. |
|
|
302
|
+
|
|
303
|
+
Depois de passar, **pare para o gate do dono**. Nao commite ate o dono aprovar.
|
|
304
|
+
|
|
305
|
+
## Armadilhas validadas
|
|
306
|
+
|
|
307
|
+
| Armadilha | Sintoma | Contorno obrigatorio |
|
|
308
|
+
|---|---|---|
|
|
309
|
+
| Discovery da `web-prototype` | Abre com `<question-form>` e encerra o turno; headless nao gera GenUI (`od ui list` vazio). | Anexar o apendice headless respondendo plataforma e proibindo perguntas/discovery. |
|
|
310
|
+
| Timeout de inatividade | HTML grande fica silencioso e o run morre perto de 10min. | `OD_CHAT_RUN_INACTIVITY_TIMEOUT_MS=1800000` no daemon + instruir escrita em partes de ~150 linhas. |
|
|
311
|
+
| `codex_core::shell_snapshot` | Loga `ERROR` de sintaxe no stream. | Tratar como ruido se a run prossegue e o arquivo final existe. |
|
|
312
|
+
| Estado da GUI | Config/agente/projetos vazam do app do usuario. | `OD_DATA_DIR` isolado sempre; `--agent`, `--skill` e `--design-system` explicitos. |
|
|
313
|
+
|
|
314
|
+
## Structured Return
|
|
315
|
+
|
|
316
|
+
**Status:** `PASS` | `FINDINGS` | `BLOCKED` | `NOT_APPLICABLE`
|
|
317
|
+
**Scope:** pasta alvo, briefs executados, agente usado, projectId/conversationId.
|
|
318
|
+
**Evidence:** logs JSONL, arquivo(s) HTML verificados, gate JSON, screenshots light/dark.
|
|
319
|
+
**Artifacts:** paths dos prototipos e sidecars.
|
|
320
|
+
**Decisions:** defaults/overrides aplicados (`agent`, `target`, plataforma, porta).
|
|
321
|
+
**Risks:** qualquer gate abaixo de 9, arquivo nao gerado apesar de run succeeded, dependencia ausente.
|
|
322
|
+
**Next Step:** gate do dono, follow-up cirurgico necessario, ou comando de resume exato.
|
|
323
|
+
</system_instructions>
|
|
@@ -23,20 +23,21 @@ services: []
|
|
|
23
23
|
|--------|---------|---------------|
|
|
24
24
|
| Forma | frontend / backend / fullstack | [por que essa forma — superficie unica, API para parceiros, etc.] |
|
|
25
25
|
| Frontend | Next.js / Vite+React / n/a | [por que esse framework — SSR, simplicidade SPA, etc.] |
|
|
26
|
-
| Backend | FastAPI / ASP.NET Core minimal / Axum /
|
|
27
|
-
| Database | Postgres / MySQL / SQLite / MongoDB / nenhum | [por que esse DB — relacional,
|
|
26
|
+
| Backend | NestJS / Fastify / FastAPI / ASP.NET Core minimal / Axum / n/a | [por que — expertise do time, ecosystem, alvo de latencia] |
|
|
27
|
+
| Database | Postgres / Postgres+pgvector / MySQL / SQLite / MongoDB / nenhum | [por que esse DB — relacional, vetorial, transacional, etc.] |
|
|
28
28
|
| Cache | Redis / Memcached / nenhum | [por que ou por que nao] |
|
|
29
|
-
| Fila | BullMQ / Celery / RabbitMQ / LocalStack SQS / nenhum | [por que ou nao + sync vs async workers] |
|
|
30
|
-
| Email — dev |
|
|
29
|
+
| Fila | pg-boss / BullMQ / Celery / RabbitMQ / LocalStack SQS / nenhum | [por que ou nao + sync vs async workers] |
|
|
30
|
+
| Email — dev | Mailpit (default) / MailHog / smtp4dev / nenhum | [normalmente Mailpit — captura only, nunca envia real] |
|
|
31
31
|
| Email — prod | SMTP / SendGrid / Resend / Postmark / SES / nenhum | [por que esse provider — volume, deliverability, custo] |
|
|
32
32
|
| Object storage | S3 / MinIO (dev) / GCS / nenhum | [por que ou nao] |
|
|
33
33
|
| Search | Meilisearch / Typesense / Elasticsearch / nenhum | [por que esse engine — features, escala, simplicidade] |
|
|
34
34
|
| Observability | Sentry / OTel + Jaeger / nenhum | [por que essa abordagem — so error tracking, tracing completo, etc.] |
|
|
35
35
|
| Reverse proxy | Traefik / Caddy / nenhum | [normalmente so multi-host dev ou prod] |
|
|
36
|
-
| Auth |
|
|
36
|
+
| Auth | Auth.js / OIDC generico / Clerk / fastapi-users / dotnet Identity / JWT custom / nenhum | [por que — social login, B2B, etc.] |
|
|
37
37
|
| Linter / formatter | Biome / ESLint+Prettier / Ruff+Black / dotnet format / cargo fmt+clippy | [preferencia do time] |
|
|
38
38
|
| Package manager | pnpm / npm / yarn / poetry / uv / cargo / dotnet | [preferencia do time] |
|
|
39
|
-
|
|
|
39
|
+
| Workspace + task runner | pnpm workspaces + Turborepo / npm workspaces / Nx / n/a | [so para fullstack — caching/build] |
|
|
40
|
+
| Topologia de dev | apps no host / tudo no Compose | [trade-off de hot reload e paridade] |
|
|
40
41
|
| CI | GitHub Actions / nenhum | [normalmente GitHub Actions; pular so para repos nao-publicos] |
|
|
41
42
|
|
|
42
43
|
## Servicos & Infra
|
|
@@ -47,7 +48,7 @@ services: []
|
|
|
47
48
|
|---------|--------------|----|--------------------|
|
|
48
49
|
| postgres | 5432 | — | POSTGRES_USER=app, POSTGRES_PASSWORD=app, POSTGRES_DB=app |
|
|
49
50
|
| redis | 6379 | — | (sem auth em dev) |
|
|
50
|
-
|
|
|
51
|
+
| mailpit | 1025 (smtp), 8025 (UI) | http://localhost:8025 | (sem auth) |
|
|
51
52
|
| ... | ... | ... | ... |
|
|
52
53
|
|
|
53
54
|
## Diagrama da Arquitetura
|
|
@@ -59,12 +60,12 @@ services: []
|
|
|
59
60
|
[ Browser ] -> [ Next.js (apps/web) ]
|
|
60
61
|
|
|
61
62
|
# Fullstack
|
|
62
|
-
[ Browser ] -> [ Next.js (apps/web) ] -> [
|
|
63
|
+
[ Browser ] -> [ Next.js (apps/web) ] -> [ NestJS (apps/api) ] -> [ Postgres ]
|
|
63
64
|
|-> [ Redis ]
|
|
64
|
-
|-> [
|
|
65
|
+
|-> [ Mailpit ]
|
|
65
66
|
|
|
66
67
|
# Com observability
|
|
67
|
-
... -> [
|
|
68
|
+
... -> [ NestJS ] -> { OTLP } -> [ Jaeger ]
|
|
68
69
|
```
|
|
69
70
|
|
|
70
71
|
## Arquivos Gerados
|
|
@@ -75,7 +76,7 @@ services: []
|
|
|
75
76
|
{{TARGET_DIR}}/
|
|
76
77
|
├── apps/
|
|
77
78
|
│ ├── web/ (criado por `pnpm create next-app`)
|
|
78
|
-
│ └── api/ (
|
|
79
|
+
│ └── api/ (criado por `pnpm dlx @nestjs/cli` ou scaffold escolhido)
|
|
79
80
|
├── packages/
|
|
80
81
|
│ └── shared/ (criado pelo /dw-new-project)
|
|
81
82
|
├── docker-compose.dev.yml (composto a partir de .agents/skills/docker-compose-recipes/)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
function parseArgs(argv) {
|
|
7
|
+
const result = { screenshot: [] };
|
|
8
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
9
|
+
const token = argv[index];
|
|
10
|
+
if (!token.startsWith("--")) continue;
|
|
11
|
+
const [rawKey, inlineValue] = token.slice(2).split("=", 2);
|
|
12
|
+
const key = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
13
|
+
if (inlineValue !== undefined) {
|
|
14
|
+
if (key === "screenshot") result.screenshot.push(inlineValue);
|
|
15
|
+
else result[key] = inlineValue;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const next = argv[index + 1];
|
|
19
|
+
if (!next || next.startsWith("--")) {
|
|
20
|
+
if (key === "screenshot") {
|
|
21
|
+
throw new Error("Missing value for --screenshot <path>.");
|
|
22
|
+
}
|
|
23
|
+
result[key] = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (key === "screenshot") result.screenshot.push(next);
|
|
27
|
+
else result[key] = next;
|
|
28
|
+
index += 1;
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function importFirefox() {
|
|
34
|
+
try {
|
|
35
|
+
return (await import("@playwright/test")).firefox;
|
|
36
|
+
} catch {
|
|
37
|
+
try {
|
|
38
|
+
return (await import("playwright")).firefox;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function slugify(value) {
|
|
46
|
+
return (
|
|
47
|
+
String(value)
|
|
48
|
+
.toLowerCase()
|
|
49
|
+
.replace(/^https?:\/\//, "")
|
|
50
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
51
|
+
.replace(/^-+|-+$/g, "")
|
|
52
|
+
.slice(0, 70) || "screen"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseViewports(value) {
|
|
57
|
+
return String(value || "1440x900,375x812")
|
|
58
|
+
.split(",")
|
|
59
|
+
.map((entry) => {
|
|
60
|
+
const match = entry.trim().match(/^(\d{2,5})x(\d{2,5})$/);
|
|
61
|
+
if (!match) return null;
|
|
62
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
63
|
+
})
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseThemes(value) {
|
|
68
|
+
return String(value || "light,dark")
|
|
69
|
+
.split(",")
|
|
70
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
71
|
+
.filter((entry) => entry === "light" || entry === "dark");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function inferProvidedName(source, index) {
|
|
75
|
+
const base = path.basename(source, path.extname(source)).toLowerCase();
|
|
76
|
+
const width = base.match(/(?:^|[^0-9])(\d{3,5})(?:x\d{3,5})?(?:[^0-9]|$)/)?.[1];
|
|
77
|
+
const theme = base.includes("dark") ? "dark" : base.includes("light") ? "light" : "";
|
|
78
|
+
if (width && theme) return `atual-${width}-${theme}.png`;
|
|
79
|
+
if (width) return `atual-${width}-${String(index + 1).padStart(2, "0")}.png`;
|
|
80
|
+
return `atual-ref-${String(index + 1).padStart(2, "0")}.png`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function captureFromUrl(args, outDir) {
|
|
84
|
+
if (!args.url) return [];
|
|
85
|
+
const firefox = await importFirefox();
|
|
86
|
+
if (!firefox) {
|
|
87
|
+
throw new Error("Playwright is not installed. Run: npm i -D @playwright/test && npx playwright install firefox");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const viewports = parseViewports(args.viewports);
|
|
91
|
+
if (viewports.length === 0) {
|
|
92
|
+
throw new Error("No valid --viewports entries. Use comma-separated WIDTHxHEIGHT values, for example 1440x900,375x812.");
|
|
93
|
+
}
|
|
94
|
+
const themes = parseThemes(args.themes);
|
|
95
|
+
if (themes.length === 0) {
|
|
96
|
+
throw new Error("No valid --themes entries. Use light,dark or one of them.");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const browser = await firefox.launch({ headless: true });
|
|
100
|
+
const shots = [];
|
|
101
|
+
try {
|
|
102
|
+
for (const viewport of viewports) {
|
|
103
|
+
for (const theme of themes) {
|
|
104
|
+
const context = await browser.newContext({
|
|
105
|
+
colorScheme: theme,
|
|
106
|
+
viewport,
|
|
107
|
+
});
|
|
108
|
+
const page = await context.newPage();
|
|
109
|
+
await page.goto(String(args.url), { waitUntil: "networkidle" }).catch(() => page.goto(String(args.url)));
|
|
110
|
+
await page.evaluate((value) => {
|
|
111
|
+
document.documentElement.setAttribute("data-theme", value);
|
|
112
|
+
}, theme);
|
|
113
|
+
await page.waitForTimeout(300);
|
|
114
|
+
const target = path.join(outDir, `atual-${viewport.width}-${theme}.png`);
|
|
115
|
+
await page.screenshot({ path: target, fullPage: true });
|
|
116
|
+
shots.push({ source: String(args.url), viewport, theme, path: target });
|
|
117
|
+
await context.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
await browser.close();
|
|
122
|
+
}
|
|
123
|
+
return shots;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function copyProvidedScreenshots(args, outDir) {
|
|
127
|
+
return args.screenshot.map((source, index) => {
|
|
128
|
+
const resolved = path.resolve(String(source));
|
|
129
|
+
if (!fs.existsSync(resolved)) {
|
|
130
|
+
throw new Error(`Screenshot not found: ${resolved}`);
|
|
131
|
+
}
|
|
132
|
+
const target = path.join(outDir, inferProvidedName(resolved, index));
|
|
133
|
+
fs.copyFileSync(resolved, target);
|
|
134
|
+
return { source: resolved, path: target };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function main() {
|
|
139
|
+
const args = parseArgs(process.argv.slice(2));
|
|
140
|
+
if (!args.url && args.screenshot.length === 0) {
|
|
141
|
+
throw new Error("Pass --url <app-url> or one or more --screenshot <path> values.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const targetDir = path.resolve(String(args.target || "open-design"));
|
|
145
|
+
const slug = slugify(args.slug || args.url || "screen");
|
|
146
|
+
const outDir = path.resolve(String(args.out || path.join(targetDir, "_refs", slug)));
|
|
147
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
148
|
+
|
|
149
|
+
const copied = copyProvidedScreenshots(args, outDir);
|
|
150
|
+
const captured = await captureFromUrl(args, outDir);
|
|
151
|
+
const shots = [...copied, ...captured];
|
|
152
|
+
|
|
153
|
+
process.stdout.write(
|
|
154
|
+
`${JSON.stringify(
|
|
155
|
+
{
|
|
156
|
+
targetDir,
|
|
157
|
+
refsDir: outDir,
|
|
158
|
+
relativeRefs: shots.map((shot) => path.relative(targetDir, shot.path).split(path.sep).join("/")),
|
|
159
|
+
shots,
|
|
160
|
+
},
|
|
161
|
+
null,
|
|
162
|
+
2,
|
|
163
|
+
)}\n`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main().catch((error) => {
|
|
168
|
+
process.stderr.write(`capture-current: ${error.message}\n`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|