create-sdd-ai-stack 0.1.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/AGENTS.md +164 -0
- package/APP-STACK.md +31 -0
- package/APP.md +46 -0
- package/ARCHITECTURE.md +115 -0
- package/DESIGN.md +219 -0
- package/LICENSE +21 -0
- package/NEXT.md +345 -0
- package/NODE.md +107 -0
- package/REACT.md +92 -0
- package/README.md +260 -0
- package/SKILLS/check-docs/SKILL.md +24 -0
- package/SKILLS/check-docs/check-docs.mjs +56 -0
- package/SKILLS/create-feature/SKILL.md +40 -0
- package/SKILLS/create-feature/create-feature.mjs +128 -0
- package/SKILLS/create-feature.sh +112 -0
- package/SKILLS/install-submodule/SKILL.md +42 -0
- package/SKILLS/install-submodule/install-submodule.mjs +119 -0
- package/bin/create-sdd-ai-stack.mjs +93 -0
- package/docs/CHANGELOG.md +209 -0
- package/docs/PLANNING.md +68 -0
- package/docs/PRODUCT.md +76 -0
- package/docs/RELEASE.md +332 -0
- package/lib/check-links.mjs +42 -0
- package/lib/constants.mjs +60 -0
- package/lib/scaffold.mjs +253 -0
- package/package.json +80 -0
- package/specs/BACKLOG.md +24 -0
- package/specs/PLAN.md +57 -0
- package/specs/ROADMAP.md +35 -0
- package/specs/history/phases/phase-0-bootstrap.md +67 -0
- package/specs/tasks/TASK_TEMPLATE.md +46 -0
- package/src/cli.mjs +119 -0
- package/stacks/README.md +33 -0
- package/stacks/ai.md +52 -0
- package/stacks/ci.md +59 -0
- package/stacks/database.md +56 -0
- package/stacks/git.md +51 -0
- package/stacks/shadcn.md +52 -0
- package/stacks/tailwind.md +65 -0
- package/stacks/testing.md +74 -0
- package/stacks/typescript.md +58 -0
- package/template/next/.env.example +4 -0
- package/template/next/README.md +47 -0
- package/template/next/biome.json +36 -0
- package/template/next/gitignore +32 -0
- package/template/next/next.config.ts +12 -0
- package/template/next/package.json +47 -0
- package/template/next/playwright.config.ts +21 -0
- package/template/next/postcss.config.mjs +7 -0
- package/template/next/src/app/(app)/app/page.tsx +18 -0
- package/template/next/src/app/(app)/error.tsx +30 -0
- package/template/next/src/app/(app)/layout.tsx +25 -0
- package/template/next/src/app/(marketing)/page.tsx +73 -0
- package/template/next/src/app/globals.css +437 -0
- package/template/next/src/app/layout.tsx +41 -0
- package/template/next/src/app/not-found.tsx +11 -0
- package/template/next/src/features/example/actions.ts +43 -0
- package/template/next/src/features/example/application/create-example.usecase.ts +26 -0
- package/template/next/src/features/example/container.ts +16 -0
- package/template/next/src/features/example/domain/IExampleRepository.ts +11 -0
- package/template/next/src/features/example/domain/example.schema.ts +18 -0
- package/template/next/src/features/example/infrastructure/example.repository.ts +26 -0
- package/template/next/src/features/example/ui/create-example-form.tsx +56 -0
- package/template/next/src/proxy.ts +23 -0
- package/template/next/src/shared/lib/cn.ts +6 -0
- package/template/next/src/shared/server/auth.ts +32 -0
- package/template/next/src/shared/server/env.ts +14 -0
- package/template/next/src/shared/ui/index.ts +3 -0
- package/template/next/tests/e2e/routes.spec.ts +34 -0
- package/template/next/tests/unit/create-example.test.ts +55 -0
- package/template/next/tsconfig.json +37 -0
- package/template/next/vitest.config.ts +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# 🤖 create-sdd-ai-stack
|
|
2
|
+
|
|
3
|
+
> **O kit de regras de desenvolvimento para agentes de IA, baseado em Spec-Driven Development (SDD).**
|
|
4
|
+
> Stack padrão: **Next.js 16**. Um `npx` e você tem um projeto com regras, arquitetura, design system e SDD prontos.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx create-sdd-ai-stack meu-dashboard
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 🎯 O que é isto
|
|
13
|
+
|
|
14
|
+
Um **template de regras + código** para agentes de IA (Claude Code, Cursor, Copilot, Codex, Gemini CLI, Cline, Windsurf…).
|
|
15
|
+
|
|
16
|
+
O agente abre o projeto, lê **uma** sequência de arquivos, e já sabe:
|
|
17
|
+
o que construir agora, como estruturar, como escrever código, como commitar, quando parar.
|
|
18
|
+
|
|
19
|
+
| Entrega | O que você recebe |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| **Regras** | `SDD/` com leis do agente, stack, design, arquitetura, SDD e stacks por ferramenta |
|
|
22
|
+
| **Template** | App Next.js 16 completo, com design system já aplicado e buildando |
|
|
23
|
+
| **CLI** | `npx create-sdd-ai-stack <nome>` — cria tudo em 1 comando |
|
|
24
|
+
| **Submodule** | Instala só as regras em qualquer projeto, com atalhos na raiz |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## ⚡ Começando
|
|
29
|
+
|
|
30
|
+
### 1. Projeto novo (recomendado)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx create-sdd-ai-stack meu-app
|
|
34
|
+
cd meu-app
|
|
35
|
+
npm run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
O que nasce:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
meu-app/
|
|
42
|
+
├── src/
|
|
43
|
+
│ ├── app/ # rotas (marketing pública + /app logado)
|
|
44
|
+
│ ├── features/ # vertical slice de exemplo (domain/application/infrastructure/ui)
|
|
45
|
+
│ ├── shared/ # design system, lib, server-only
|
|
46
|
+
│ ├── proxy.ts # network boundary + headers
|
|
47
|
+
│ └── app/globals.css# TOKENS DO DESIGN SYSTEM
|
|
48
|
+
├── tests/ # unit + e2e prontos
|
|
49
|
+
├── SDD/ # 🧠 as regras
|
|
50
|
+
├── AGENTS.md # → atalho para ./SDD/AGENTS.md
|
|
51
|
+
├── CLAUDE.md, GEMINI.md, .cursorrules, .github/copilot-instructions.md, …
|
|
52
|
+
└── package.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Projeto existente (só as regras)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Opção A — submodule (atualiza com git)
|
|
59
|
+
git submodule add https://github.com/marcelinosandroni/sdd-ai-stack.git SDD
|
|
60
|
+
node SDD/SKILLS/install-submodule/install-submodule.mjs
|
|
61
|
+
|
|
62
|
+
# Opção B — CLI
|
|
63
|
+
npx create-sdd-ai-stack . --rules-only
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Os atalhos da raiz (`AGENTS.md`, `CLAUDE.md`, `.cursorrules`…) apontam para `./SDD/AGENTS.md`,
|
|
67
|
+
então **todo agente já começa pelo lugar certo** — sem você precisar configurar nada.
|
|
68
|
+
|
|
69
|
+
Atualizar as regras depois:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git submodule update --remote --merge SDD
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> ⚠️ O `.gitignore` do app gerado protege `.env.local`, `.next/` e `node_modules/`.
|
|
76
|
+
> Nunca commite um `.env` de verdade — só o `.env.example` (que tem placeholders).
|
|
77
|
+
|
|
78
|
+
### 3. Opções da CLI
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npx create-sdd-ai-stack meu-app --template next # template (padrão)
|
|
82
|
+
npx create-sdd-ai-stack meu-app --rules-only # só as regras
|
|
83
|
+
npx create-sdd-ai-stack meu-app --install # roda npm install
|
|
84
|
+
npx create-sdd-ai-stack meu-app --git # git init + 1º commit
|
|
85
|
+
npx create-sdd-ai-stack meu-app --submodule # SDD/ como git submodule
|
|
86
|
+
npx create-sdd-ai-stack meu-app --submodule <url> # de um fork seu
|
|
87
|
+
npx create-sdd-ai-stack meu-app --shortcuts stub # sem symlink (Windows sem dev mode)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🧠 O mapa das regras (`SDD/`)
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
SDD/
|
|
96
|
+
├── AGENTS.md ⭐ leis + fluxo — LEIA PRIMEIRO
|
|
97
|
+
├── specs/PLAN.md ⭐ a task AGORA
|
|
98
|
+
├── APP.md o que é este app
|
|
99
|
+
├── APP-STACK.md qual stack este app usa
|
|
100
|
+
├── NEXT.md ⭐ Next.js 16 (stack padrão)
|
|
101
|
+
├── NODE.md Node.js puro (worker, cron, fila)
|
|
102
|
+
├── REACT.md React (server-first)
|
|
103
|
+
├── DESIGN.md 🎨 design system completo
|
|
104
|
+
├── ARCHITECTURE.md 🏗️ vertical slices
|
|
105
|
+
├── stacks/ 🧱 por ferramenta
|
|
106
|
+
│ ├── typescript.md tailwind.md shadcn.md
|
|
107
|
+
│ ├── testing.md database.md ai.md
|
|
108
|
+
│ └── git.md ci.md
|
|
109
|
+
├── specs/ SDD operacional
|
|
110
|
+
│ ├── PLAN.md BACKLOG.md ROADMAP.md
|
|
111
|
+
│ ├── tasks/TASK_TEMPLATE.md
|
|
112
|
+
│ └── history/phases/
|
|
113
|
+
├── docs/ PRODUCT.md CHANGELOG.md PLANNING.md
|
|
114
|
+
└── SKILLS/ automações (create-feature, install-submodule)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Ordem de leitura imposta pelo `AGENTS.md`
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
1. SDD/AGENTS.md leis e fluxo
|
|
121
|
+
2. SDD/specs/PLAN.md a única task [-]
|
|
122
|
+
3. SDD/APP.md o que é este app
|
|
123
|
+
4. SDD/APP-STACK.md qual stack
|
|
124
|
+
5. SDD/NEXT.md regras da stack
|
|
125
|
+
6. SDD/DESIGN.md só se mexer em UI
|
|
126
|
+
7. SDD/stacks/… só a ferramenta que está tocando
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> Cada doc de regra tem um **roteador no topo**: "se você está fazendo X, leia §Y".
|
|
130
|
+
> Isso mantém o contexto do agente pequeno — importante, porque contexto longo é onde o agente morre.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 🎨 Design System
|
|
135
|
+
|
|
136
|
+
O `DESIGN.md` implementa o **Executive Engineering**: ardósia profunda (nunca preto puro),
|
|
137
|
+
micro-bordas de 1px, acentos em lime neon `#BAF336` e mint `#34D399`, tipografia tripla
|
|
138
|
+
(**Manrope** estrutural + **JetBrains Mono** técnica + **Playfair Display** editorial), grid de 12 colunas com max 1320px.
|
|
139
|
+
|
|
140
|
+
Os tokens vivem em `src/app/globals.css` (bloco `@theme` do Tailwind v4) e viram utilitários
|
|
141
|
+
(`bg-surface-raised`, `text-text-secondary`, `text-label-mono`, `border-border-subtle`…) +
|
|
142
|
+
primitivos (`btn-primary`, `btn-secondary`, `card`, `card-metric`, `chip`, `field`).
|
|
143
|
+
|
|
144
|
+
**Um lugar só.** Mudou o design? Muda no `@theme`, nunca no componente.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 🏗️ Arquitetura
|
|
149
|
+
|
|
150
|
+
Vertical slices. Uma pasta por domínio, com tudo que aquele domínio precisa:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
src/features/<dominio>/
|
|
154
|
+
├── domain/ # entidades + contratos (I*.ts) — zero dependência
|
|
155
|
+
├── application/ # use cases — regra pura, sem Next, sem Prisma
|
|
156
|
+
├── infrastructure/ # Prisma, HTTP, filas
|
|
157
|
+
├── container.ts # DI do slice
|
|
158
|
+
├── queries.ts # entrada de leitura
|
|
159
|
+
├── actions.ts # entrada de escrita (Server Action)
|
|
160
|
+
└── ui/ # componentes do domínio
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Motivo de ser assim: **para entender um requisito você abre uma pasta só** — e o
|
|
164
|
+
`application/` é testável sem mock de infra.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 🚀 Publicar no npm
|
|
169
|
+
|
|
170
|
+
Fluxo único: **você versiona, o GitHub Actions publica.**
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npm run version:minor # 0.1.17 → 0.2.0 (commita + cria tag v0.2.0)
|
|
174
|
+
git push origin main
|
|
175
|
+
git push origin --tags # ← dispara a publicação
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
A primeira vez precisa resolver a autenticação. Com **2FA ligado na conta npm**, um token
|
|
179
|
+
comum não publica (`EOTP` — o CI não tem como digitar o OTP). Duas saídas:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# A. Recomendado: publica a 1ª versão da sua máquina, ativa OIDC e apaga o token
|
|
183
|
+
npm publish --access public --provenance=false --otp=123456
|
|
184
|
+
# ⚠️ --provenance=false é obrigatório fora do CI: o npm exige OIDC para gerar
|
|
185
|
+
# provenance e falha com "provider: null" se não achar o provedor
|
|
186
|
+
# depois: npmjs.com → create-sdd-ai-stack → Settings → Trusted publishing
|
|
187
|
+
# owner: marcelinosandroni · repo: sdd-ai-stack · workflow: release.yml · allow: npm publish
|
|
188
|
+
gh secret delete NPM_TOKEN --repo marcelinosandroni/sdd-ai-stack
|
|
189
|
+
|
|
190
|
+
# B. Ponte: token granular com "Bypass 2FA" marcado (deprecado pelo npm em jan/2027)
|
|
191
|
+
gh secret set NPM_TOKEN --repo marcelinosandroni/sdd-ai-stack
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
O workflow escolhe o modo sozinho: **com** `NPM_TOKEN` usa token, **sem** ele usa OIDC.
|
|
195
|
+
Nada de token fica gravado em arquivo.
|
|
196
|
+
|
|
197
|
+
**Guards antes de publicar:** `npm test` (22 testes) · links da doc · tag `vX.Y.Z` bate com
|
|
198
|
+
o `package.json` · 10 arquivos essenciais presentes no tarball · `npm ≥ 11.5.1` · `concurrency` · provenance.
|
|
199
|
+
|
|
200
|
+
📖 Passo a passo completo (os 3 caminhos de auth, troubleshooting e o caminho stage-only)
|
|
201
|
+
em [`docs/RELEASE.md`](./docs/RELEASE.md).
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 🔌 Agentes suportados
|
|
206
|
+
|
|
207
|
+
Os atalhos da raiz são criados para:
|
|
208
|
+
|
|
209
|
+
| Arquivo | Agente |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `AGENTS.md` | padrão de mercado (Cursor, Codex, Windsurf, Cline, Gemini) |
|
|
212
|
+
| `CLAUDE.md` | Claude Code |
|
|
213
|
+
| `GEMINI.md` | Gemini CLI |
|
|
214
|
+
| `.cursorrules` | Cursor (formato antigo) |
|
|
215
|
+
| `.windsurfrules` | Windsurf |
|
|
216
|
+
| `.github/copilot-instructions.md` | GitHub Copilot |
|
|
217
|
+
| `.clinerules` | Cline |
|
|
218
|
+
|
|
219
|
+
Todos apontam para `SDD/AGENTS.md`. Nenhuma configuração manual necessária.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 🧪 Verificação
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
npm test # 20 testes da CLI, do scaffold e da documentação
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
O template em `template/next/` é validado de verdade: `typecheck` + `lint` + `test` + `test:e2e` + `build`.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 📚 SKILLS
|
|
234
|
+
|
|
235
|
+
| SKILL | O que faz |
|
|
236
|
+
| --- | --- |
|
|
237
|
+
| `create-feature` | Cria um vertical slice novo com domain/application/container/queries/actions |
|
|
238
|
+
| `install-submodule` | Instala as regras em projeto existente + cria atalhos |
|
|
239
|
+
| `check-docs` | Valida que todo link relativo entre documentos resolve |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 🛡️ Qualidade
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
npm test # 22 testes: CLI, scaffold e integridade da documentação
|
|
247
|
+
node SDD/SKILLS/check-docs/check-docs.mjs # 29 documentos, links relativos
|
|
248
|
+
npm run check:pack # confere o que vai para o npm (71 arquivos, ~62 kB)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
O template em `template/next/` é validado de verdade: `typecheck` + `lint` + `test` + `test:e2e` + `build`.
|
|
252
|
+
O CI (`.github/workflows/ci.yml`) refaz essa validação a cada push, **gerando o app a partir do próprio template**.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 👨💻 Autor
|
|
257
|
+
|
|
258
|
+
**Marcelino Sandroni** — [github.com/marcelinosandroni](https://github.com/marcelinosandroni)
|
|
259
|
+
|
|
260
|
+
MIT License.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 🧩 SKILL: check-docs
|
|
2
|
+
|
|
3
|
+
> Valida que **todo link relativo entre documentos** resolve. Roda no hook de commit
|
|
4
|
+
> e no CI para uma regra nunca apontar para arquivo morto.
|
|
5
|
+
|
|
6
|
+
## ▶️ Uso
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
node SDD/SKILLS/check-docs/check-docs.mjs
|
|
10
|
+
node SDD/SKILLS/check-docs/check-docs.mjs ../outro-projeto
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 📦 O que verifica
|
|
14
|
+
|
|
15
|
+
- Coleta todo `.md` a partir da raiz do `SDD/`
|
|
16
|
+
- Ignora blocos de código (` ``` ` / `~~~ `) — link ilustrativo dentro de exemplo não conta
|
|
17
|
+
- Ignora `node_modules`, `.next`, `test-results`, `playwright-report`
|
|
18
|
+
- Saída: exit 1 com a lista dos quebrados, ou exit 0 com o total de documentos
|
|
19
|
+
|
|
20
|
+
## ✅ Quando rodar
|
|
21
|
+
|
|
22
|
+
- Antes de commitar mudança em documentação
|
|
23
|
+
- No CI, junto com os testes
|
|
24
|
+
- Depois de renomear/mover qualquer documento de regra
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SDD SKILL: check-docs
|
|
4
|
+
* Valida que todo link relativo entre documentos markdown resolve.
|
|
5
|
+
*
|
|
6
|
+
* Uso: node SDD/SKILLS/check-docs/check-docs.mjs [raiz]
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const root = path.resolve(process.argv[2] ?? path.join(here, "..", ".."));
|
|
14
|
+
|
|
15
|
+
function stripCodeFences(md) {
|
|
16
|
+
return md.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `template/` é excluído de propósito: o README dele aponta para `./SDD/...`,
|
|
21
|
+
* que só existe DEPOIS do scaffold. Validar aqui daria falso positivo.
|
|
22
|
+
*/
|
|
23
|
+
function collectMarkdown(dir) {
|
|
24
|
+
const files = [];
|
|
25
|
+
(function walk(p) {
|
|
26
|
+
if (!fs.existsSync(p)) return;
|
|
27
|
+
const stat = fs.statSync(p);
|
|
28
|
+
if (stat.isDirectory()) {
|
|
29
|
+
const segs = p.split(/[\\/]/);
|
|
30
|
+
if (segs.some((s) => ["node_modules", ".next", "template", "test-results", "playwright-report"].includes(s))) return;
|
|
31
|
+
for (const e of fs.readdirSync(p)) walk(path.join(p, e));
|
|
32
|
+
} else if (/\.md$/.test(p)) {
|
|
33
|
+
files.push(p);
|
|
34
|
+
}
|
|
35
|
+
})(dir);
|
|
36
|
+
return files;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const files = collectMarkdown(root);
|
|
40
|
+
|
|
41
|
+
const broken = [];
|
|
42
|
+
const re = /\]\((\.{0,2}\/[^)#\s]+)(?:#[^)]*)?\)/g;
|
|
43
|
+
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const txt = stripCodeFences(fs.readFileSync(file, "utf8"));
|
|
46
|
+
for (const m of txt.matchAll(re)) {
|
|
47
|
+
const target = path.resolve(path.dirname(file), m[1]);
|
|
48
|
+
if (!fs.existsSync(target)) broken.push(` ${path.relative(root, file)} -> ${m[1]}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (broken.length) {
|
|
53
|
+
console.error(`\n✖ ${broken.length} link(s) quebrado(s):\n${broken.join("\n")}\n`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log(`✓ ${files.length} documentos, todos os links relativos resolvem.`);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 🧩 SKILL: create-feature
|
|
2
|
+
|
|
3
|
+
> Cria um **vertical slice** novo em `src/features/<nome>/` já com domain, application,
|
|
4
|
+
> infrastructure, container, queries e actions seguindo [SDD/ARCHITECTURE.md](../../ARCHITECTURE.md).
|
|
5
|
+
|
|
6
|
+
## 🎯 Quando usar
|
|
7
|
+
|
|
8
|
+
Toda vez que começa uma feature nova. Antes de escrever arquivo na mão, rode isto.
|
|
9
|
+
|
|
10
|
+
## ▶️ Uso
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
node SDD/SKILLS/create-feature/create-feature.mjs billing
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
> Alternativa em bash: `bash SDD/SKILLS/create-feature.sh billing`
|
|
17
|
+
|
|
18
|
+
## 📦 O que é criado
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
src/features/billing/
|
|
22
|
+
├── domain/
|
|
23
|
+
│ ├── IBillingRepository.ts # contrato (interface, sem dep externa)
|
|
24
|
+
│ └── billing.schema.ts # Zod na fronteira
|
|
25
|
+
├── application/
|
|
26
|
+
│ └── create-billing.usecase.ts # regra de negócio pura
|
|
27
|
+
├── infrastructure/ # (vazio — você implementa o repositório)
|
|
28
|
+
├── container.ts # DI do slice
|
|
29
|
+
├── queries.ts # entrada de leitura
|
|
30
|
+
├── actions.ts # Server Action: auth → zod → authz → use case → cache
|
|
31
|
+
└── ui/ # componentes do domínio
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## ✅ Checklist depois de rodar
|
|
35
|
+
|
|
36
|
+
- [ ] Implementar `infrastructure/` (Prisma/API) satisfazendo a interface
|
|
37
|
+
- [ ] Escrever teste unit do use case em `tests/unit/`
|
|
38
|
+
- [ ] Registrar a task em `SDD/specs/PLAN.md` (`[-]`)
|
|
39
|
+
- [ ] Criar a rota em `src/app/` **só roteando** para o slice
|
|
40
|
+
- [ ] `npm run typecheck && npm run test:unit && npm run test:e2e`
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SKILL: create-feature (versão multiplataforma, espelha create-feature.sh)
|
|
4
|
+
* Uso: node SDD/SKILLS/create-feature/create-feature.mjs <nome-do-slice>
|
|
5
|
+
*/
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
const raw = process.argv[2];
|
|
10
|
+
if (!raw) {
|
|
11
|
+
console.error("Uso: node create-feature.mjs <nome-do-slice>");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const name = raw.trim().toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
16
|
+
if (!name) {
|
|
17
|
+
console.error("✖ Nome inválido.");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const Pascal = name
|
|
22
|
+
.split(/[-_\s]+/)
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.map((w) => w[0].toUpperCase() + w.slice(1))
|
|
25
|
+
.join("");
|
|
26
|
+
|
|
27
|
+
const dir = path.resolve(process.cwd(), "src", "features", name);
|
|
28
|
+
if (fs.existsSync(dir)) {
|
|
29
|
+
console.error(`✖ Já existe: ${dir}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const sub of ["domain", "application", "infrastructure", "ui"]) {
|
|
34
|
+
fs.mkdirSync(path.join(dir, sub), { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const write = (rel, body) => {
|
|
38
|
+
const p = path.join(dir, rel);
|
|
39
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
40
|
+
fs.writeFileSync(p, body, "utf8");
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
write(`domain/I${Pascal}Repository.ts`, `export interface I${Pascal}Repository {
|
|
44
|
+
// TODO: contratos do domínio. Sem dependência externa.
|
|
45
|
+
}
|
|
46
|
+
`);
|
|
47
|
+
|
|
48
|
+
write(`domain/${name}.schema.ts`, `import { z } from "zod";
|
|
49
|
+
|
|
50
|
+
export const ${Pascal}Schema = z.object({
|
|
51
|
+
// TODO: campos + validações
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export type ${Pascal}Input = z.infer<typeof ${Pascal}Schema>;
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
write(`application/create-${name}.usecase.ts`, `import type { ${Pascal}Input } from "../domain/${name}.schema";
|
|
58
|
+
import type { I${Pascal}Repository } from "../domain/I${Pascal}Repository";
|
|
59
|
+
|
|
60
|
+
export class Create${Pascal}UseCase {
|
|
61
|
+
constructor(private readonly repo: I${Pascal}Repository) {}
|
|
62
|
+
|
|
63
|
+
async execute(input: ${Pascal}Input) {
|
|
64
|
+
// TODO: regra de negócio pura. Sem Next, sem Prisma.
|
|
65
|
+
throw new Error("not implemented");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`);
|
|
69
|
+
|
|
70
|
+
write("container.ts", `import "server-only";
|
|
71
|
+
import { Create${Pascal}UseCase } from "./application/create-${name}.usecase";
|
|
72
|
+
import type { I${Pascal}Repository } from "./domain/I${Pascal}Repository";
|
|
73
|
+
|
|
74
|
+
export function create${Pascal}UseCases(repo: I${Pascal}Repository) {
|
|
75
|
+
return {
|
|
76
|
+
create${Pascal}: new Create${Pascal}UseCase(repo),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
`);
|
|
80
|
+
|
|
81
|
+
write("queries.ts", `import "server-only";
|
|
82
|
+
// TODO: leituras do domínio. Marque com 'use cache' quando fizer sentido.
|
|
83
|
+
// Ver SDD/NEXT.md §4 e §6.
|
|
84
|
+
|
|
85
|
+
export async function list${Pascal}() {
|
|
86
|
+
// TODO
|
|
87
|
+
throw new Error("not implemented");
|
|
88
|
+
}
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
write("actions.ts", `"use server";
|
|
92
|
+
import { revalidatePath, updateTag } from "next/cache";
|
|
93
|
+
import { z } from "zod";
|
|
94
|
+
import { create${Pascal}UseCases } from "./container";
|
|
95
|
+
import { ${Pascal}Schema } from "./domain/${name}.schema";
|
|
96
|
+
|
|
97
|
+
export type ${Pascal}ActionState = {
|
|
98
|
+
ok: boolean;
|
|
99
|
+
errors?: Record<string, string[]>;
|
|
100
|
+
error?: string;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export async function create${Pascal}Action(
|
|
104
|
+
_prev: ${Pascal}ActionState,
|
|
105
|
+
formData: FormData,
|
|
106
|
+
): Promise<${Pascal}ActionState> {
|
|
107
|
+
// 1. auth 2. zod 3. autorização 4. use case 5. cache
|
|
108
|
+
const parsed = ${Pascal}Schema.safeParse(Object.fromEntries(formData));
|
|
109
|
+
if (!parsed.success) {
|
|
110
|
+
return { ok: false, errors: z.flattenError(parsed.error).fieldErrors };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const { create${Pascal} } = create${Pascal}UseCases(/* repo */ undefined as never);
|
|
115
|
+
await create${Pascal}.execute(parsed.data);
|
|
116
|
+
updateTag("${name}");
|
|
117
|
+
revalidatePath("/");
|
|
118
|
+
return { ok: true };
|
|
119
|
+
} catch {
|
|
120
|
+
return { ok: false, error: "Não foi possível concluir. Tente novamente." };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
`);
|
|
124
|
+
|
|
125
|
+
console.log(`✓ Slice criado em ${path.relative(process.cwd(), dir)}`);
|
|
126
|
+
console.log(" 1. Implemente o repositório em infrastructure/");
|
|
127
|
+
console.log(" 2. Escreva o teste em tests/unit/");
|
|
128
|
+
console.log(" 3. Registre a task em SDD/specs/PLAN.md");
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SKILL: create-feature
|
|
3
|
+
# Cria um novo vertical slice em src/features/<nome>/ com a estrutura padrão.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
NAME="${1:-}"
|
|
7
|
+
if [ -z "$NAME" ]; then
|
|
8
|
+
echo "Uso: create-feature.sh <nome-do-slice>"
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
DIR="src/features/${NAME}"
|
|
13
|
+
|
|
14
|
+
if [ -e "$DIR" ]; then
|
|
15
|
+
echo "✖ Já existe: $DIR"
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
mkdir -p "$DIR"/{domain,application,infrastructure,ui}
|
|
20
|
+
|
|
21
|
+
cat > "$DIR/domain/I$(echo "$NAME" | sed 's/^\(.\)/\U\1/')Repository.ts" <<EOF
|
|
22
|
+
export interface I$(echo "$NAME" | sed 's/^\(.\)/\U\1/')Repository {
|
|
23
|
+
// TODO: contratos do domínio. Sem dependência externa.
|
|
24
|
+
}
|
|
25
|
+
EOF
|
|
26
|
+
|
|
27
|
+
cat > "$DIR/domain/${NAME}.schema.ts" <<EOF
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
|
|
30
|
+
export const ${NAME^}Schema = z.object({
|
|
31
|
+
// TODO: campos + validações
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export type ${NAME^}Input = z.infer<typeof ${NAME^}Schema>;
|
|
35
|
+
EOF
|
|
36
|
+
|
|
37
|
+
cat > "$DIR/application/create-${NAME}.usecase.ts" <<EOF
|
|
38
|
+
import type { ${NAME^}Input } from "../domain/${NAME}.schema";
|
|
39
|
+
import type { I${NAME^}Repository } from "../domain/I${NAME^}Repository";
|
|
40
|
+
|
|
41
|
+
export class Create${NAME^}UseCase {
|
|
42
|
+
constructor(private readonly repo: I${NAME^}Repository) {}
|
|
43
|
+
|
|
44
|
+
async execute(input: ${NAME^}Input) {
|
|
45
|
+
// TODO: regra de negócio pura. Sem Next, sem Prisma.
|
|
46
|
+
throw new Error("not implemented");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
EOF
|
|
50
|
+
|
|
51
|
+
cat > "$DIR/container.ts" <<EOF
|
|
52
|
+
import "server-only";
|
|
53
|
+
import { Create${NAME^}UseCase } from "./application/create-${NAME}.usecase";
|
|
54
|
+
import type { I${NAME^}Repository } from "./domain/I${NAME^}Repository";
|
|
55
|
+
|
|
56
|
+
export function create${NAME^}UseCases(repo: I${NAME^}Repository) {
|
|
57
|
+
return {
|
|
58
|
+
create${NAME^}: new Create${NAME^}UseCase(repo),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
EOF
|
|
62
|
+
|
|
63
|
+
cat > "$DIR/queries.ts" <<EOF
|
|
64
|
+
import "server-only";
|
|
65
|
+
// TODO: leituras do domínio. Marque com 'use cache' quando fizer sentido.
|
|
66
|
+
// Ver SDD/NEXT.md §4 e §6.
|
|
67
|
+
|
|
68
|
+
export async function list${NAME^}() {
|
|
69
|
+
// TODO
|
|
70
|
+
throw new Error("not implemented");
|
|
71
|
+
}
|
|
72
|
+
EOF
|
|
73
|
+
|
|
74
|
+
cat > "$DIR/actions.ts" <<EOF
|
|
75
|
+
"use server";
|
|
76
|
+
import { revalidatePath, updateTag } from "next/cache";
|
|
77
|
+
import { z } from "zod";
|
|
78
|
+
import { create${NAME^}UseCases } from "./container";
|
|
79
|
+
import { ${NAME^}Schema } from "./domain/${NAME}.schema";
|
|
80
|
+
|
|
81
|
+
export type ${NAME^}ActionState = {
|
|
82
|
+
ok: boolean;
|
|
83
|
+
errors?: Record<string, string[]>;
|
|
84
|
+
error?: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export async function create${NAME^}Action(
|
|
88
|
+
_prev: ${NAME^}ActionState,
|
|
89
|
+
formData: FormData,
|
|
90
|
+
): Promise<${NAME^}ActionState> {
|
|
91
|
+
// 1. auth 2. zod 3. autorização 4. use case 5. cache
|
|
92
|
+
const parsed = ${NAME^}Schema.safeParse(Object.fromEntries(formData));
|
|
93
|
+
if (!parsed.success) {
|
|
94
|
+
return { ok: false, errors: z.flattenError(parsed.error).fieldErrors };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const { create${NAME^} } = create${NAME^}UseCases(/* repo */ undefined as never);
|
|
99
|
+
await create${NAME^}.execute(parsed.data);
|
|
100
|
+
updateTag("${NAME}");
|
|
101
|
+
revalidatePath("/");
|
|
102
|
+
return { ok: true };
|
|
103
|
+
} catch {
|
|
104
|
+
return { ok: false, error: "Não foi possível concluir. Tente novamente." };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
EOF
|
|
108
|
+
|
|
109
|
+
echo "✓ Slice criado em $DIR"
|
|
110
|
+
echo " 1. Implemente o repositório em infrastructure/"
|
|
111
|
+
echo " 2. Escreva o teste em tests/unit/"
|
|
112
|
+
echo " 3. Registre a task em SDD/specs/PLAN.md"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 🧩 SKILL: install-submodule
|
|
2
|
+
|
|
3
|
+
> Instala o core de regras (`SDD/`) em um projeto **existente**, cria os atalhos da raiz
|
|
4
|
+
> e deixa o agente pronto para trabalhar.
|
|
5
|
+
|
|
6
|
+
## 🎯 Quando usar
|
|
7
|
+
|
|
8
|
+
- Projeto já existe e você **não** quer o template Next.js.
|
|
9
|
+
- Só quer as regras + atalhos, mantendo o código atual intocado.
|
|
10
|
+
|
|
11
|
+
## ▶️ Uso
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Dentro do projeto alvo (ou passe o caminho como 1º argumento)
|
|
15
|
+
node SDD/SKILLS/install-submodule/install-submodule.mjs
|
|
16
|
+
|
|
17
|
+
# Instala em outro diretório
|
|
18
|
+
node SDD/SKILLS/install-submodule/install-submodule.mjs ../meu-projeto
|
|
19
|
+
|
|
20
|
+
# Sem git: cópia local
|
|
21
|
+
node SDD/SKILLS/install-submodule/install-submodule.mjs . --copy
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 📦 O que faz
|
|
25
|
+
|
|
26
|
+
1. `git submodule add <repo> SDD` (ou cópia com `--copy`)
|
|
27
|
+
2. Cria atalhos na raiz: `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.cursorrules`,
|
|
28
|
+
`.windsurfrules`, `.github/copilot-instructions.md`, `.clinerules`
|
|
29
|
+
- tenta **symlink**; se o SO bloquear, grava um **stub** com o mesmo conteúdo da regra
|
|
30
|
+
3. Preserva arquivos que já existam (não sobrescreve)
|
|
31
|
+
|
|
32
|
+
## 🔄 Atualizar depois
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git submodule update --remote --merge SDD
|
|
36
|
+
git add SDD && git commit -m "chore(sdd): atualiza core"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## ⚠️ Pré-requisitos
|
|
40
|
+
|
|
41
|
+
- Projeto precisa ser um repositório git (para o modo submodule).
|
|
42
|
+
- `git` no PATH.
|