@guilhermefsousa/open-spec-kit 0.0.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.
Files changed (31) hide show
  1. package/README.md +57 -0
  2. package/bin/open-spec-kit.js +39 -0
  3. package/package.json +51 -0
  4. package/src/commands/doctor.js +324 -0
  5. package/src/commands/init.js +981 -0
  6. package/src/commands/update.js +210 -0
  7. package/src/commands/validate.js +615 -0
  8. package/src/parsers/markdown-sections.js +271 -0
  9. package/src/schemas/projects.schema.js +111 -0
  10. package/src/schemas/spec.schema.js +760 -0
  11. package/templates/agents/agents/spec-hub.agent.md +99 -0
  12. package/templates/agents/rules/hub_structure.instructions.md +49 -0
  13. package/templates/agents/rules/ownership.instructions.md +138 -0
  14. package/templates/agents/scripts/notify-gchat.ps1 +99 -0
  15. package/templates/agents/scripts/notify-gchat.sh +131 -0
  16. package/templates/agents/skills/dev-orchestrator/SKILL.md +573 -0
  17. package/templates/agents/skills/discovery/SKILL.md +406 -0
  18. package/templates/agents/skills/setup-project/SKILL.md +459 -0
  19. package/templates/agents/skills/specifying-features/SKILL.md +379 -0
  20. package/templates/github/agents/spec-hub.agent.md +75 -0
  21. package/templates/github/copilot-instructions.md +102 -0
  22. package/templates/github/instructions/hub_structure.instructions.md +33 -0
  23. package/templates/github/instructions/ownership.instructions.md +45 -0
  24. package/templates/github/prompts/dev.prompt.md +19 -0
  25. package/templates/github/prompts/discovery.prompt.md +20 -0
  26. package/templates/github/prompts/nova-feature.prompt.md +19 -0
  27. package/templates/github/prompts/setup.prompt.md +18 -0
  28. package/templates/github/skills/dev-orchestrator/SKILL.md +9 -0
  29. package/templates/github/skills/discovery/SKILL.md +9 -0
  30. package/templates/github/skills/setup-project/SKILL.md +9 -0
  31. package/templates/github/skills/specifying-features/SKILL.md +9 -0
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Open Spec Kit
2
+
3
+ Kit de especificação assistida por IA que transforma documentos do PO em specs técnicas prontas para implementar. Suporta **Claude Code** e **GitHub Copilot** via arquitetura dual-platform.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Scaffold um novo projeto de specs
9
+ npx open-spec-kit init
10
+
11
+ # Verificar ambiente (Confluence, MCP, Python, Git)
12
+ npx open-spec-kit doctor
13
+
14
+ # Validar specs (32 regras determinísticas)
15
+ npx open-spec-kit validate
16
+
17
+ # Sincronizar .agents/ → .github/
18
+ npx open-spec-kit update
19
+ ```
20
+
21
+ ## Workflow
22
+
23
+ O workflow completo roda com 4 skills no VS Code:
24
+
25
+ ```
26
+ /setup → /discovery → /spec → /dev
27
+ ```
28
+
29
+ Cada skill lê o que a anterior produziu, gera artefatos e atualiza o Confluence automaticamente.
30
+
31
+ ## Comandos
32
+
33
+ | Comando | Descrição |
34
+ |---------|-----------|
35
+ | `open-spec-kit init` | Scaffold de um novo projeto de specs (interativo) |
36
+ | `open-spec-kit doctor` | Verifica ambiente: Confluence, MCP server, Python, Git |
37
+ | `open-spec-kit validate` | Valida specs com 32 regras determinísticas |
38
+ | `open-spec-kit validate --spec 001` | Valida uma spec específica |
39
+ | `open-spec-kit validate --json` | Output JSON (para CI) |
40
+ | `open-spec-kit validate --trace` | Mostra matriz de rastreabilidade REQ↔CT |
41
+ | `open-spec-kit update` | Re-sincroniza `.agents/` → `.github/` |
42
+
43
+ ## Pré-requisitos
44
+
45
+ - **Node.js 18+**
46
+ - **VS Code** + Claude Code ou GitHub Copilot
47
+ - **Confluence Cloud** + MCP server (`mcp-atlassian`)
48
+ - **Python 3.10+** (para o MCP server)
49
+ - **Git**
50
+
51
+ ## Documentação
52
+
53
+ Veja o [guia completo](https://github.com/guilhermefsousa/open-spec-kit#readme) no repositório.
54
+
55
+ ## License
56
+
57
+ MIT
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { initCommand } from '../src/commands/init.js';
5
+ import { updateCommand } from '../src/commands/update.js';
6
+ import { validateCommand } from '../src/commands/validate.js';
7
+ import { doctorCommand } from '../src/commands/doctor.js';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('open-spec-kit')
13
+ .description('CLI para spec-driven development com suporte a Claude Code e GitHub Copilot')
14
+ .version('0.1.0');
15
+
16
+ program
17
+ .command('init')
18
+ .description('Inicializar estrutura de spec-driven development no projeto')
19
+ .action(initCommand);
20
+
21
+ program
22
+ .command('update')
23
+ .description('Re-gerar arquivos de adapter quando skills ou agents mudarem')
24
+ .action(updateCommand);
25
+
26
+ program
27
+ .command('validate')
28
+ .description('Validate spec consistency (32 rules: structure, traceability, contracts, cross-spec)')
29
+ .option('--json', 'Output results as JSON (for CI pipelines)')
30
+ .option('--trace', 'Show REQ → CT traceability matrix')
31
+ .option('--spec <id>', 'Validate a single spec by number (e.g., 001)')
32
+ .action(validateCommand);
33
+
34
+ program
35
+ .command('doctor')
36
+ .description('Verificar se o ambiente está configurado corretamente')
37
+ .action(doctorCommand);
38
+
39
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@guilhermefsousa/open-spec-kit",
3
+ "version": "0.0.1",
4
+ "description": "CLI para spec-driven development com suporte a Claude Code e GitHub Copilot",
5
+ "type": "module",
6
+ "bin": {
7
+ "open-spec-kit": "bin/open-spec-kit.js",
8
+ "osk-kit": "bin/open-spec-kit.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "templates",
14
+ "package.json",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "start": "node bin/open-spec-kit.js",
19
+ "prepublishOnly": "node bin/open-spec-kit.js --version"
20
+ },
21
+ "dependencies": {
22
+ "commander": "^13.0.0",
23
+ "inquirer": "^12.0.0",
24
+ "chalk": "^5.4.0",
25
+ "ora": "^8.0.0",
26
+ "zod": "^3.24.0",
27
+ "yaml": "^2.7.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "keywords": [
33
+ "spec-driven-development",
34
+ "specification",
35
+ "claude",
36
+ "copilot",
37
+ "ai-agents",
38
+ "technical-specs",
39
+ "confluence"
40
+ ],
41
+ "author": "Guilherme Sousa",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/guilhermefsousa/open-spec-kit.git"
46
+ },
47
+ "homepage": "https://github.com/guilhermefsousa/open-spec-kit#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/guilhermefsousa/open-spec-kit/issues"
50
+ }
51
+ }
@@ -0,0 +1,324 @@
1
+ import chalk from 'chalk';
2
+ import { readFile, access } from 'fs/promises';
3
+ import { join } from 'path';
4
+ import { execSync } from 'child_process';
5
+ import yaml from 'yaml';
6
+
7
+ export async function doctorCommand() {
8
+ console.log(chalk.bold('\n open-spec-kit doctor\n'));
9
+
10
+ const cwd = process.cwd();
11
+ let pass = 0;
12
+ let fail = 0;
13
+ let projectsContent = '';
14
+ let claudeMcpContent = '';
15
+ let copilotMcpContent = '';
16
+
17
+ // Check 1: projects.yml exists and is filled
18
+ try {
19
+ projectsContent = await readFile(join(cwd, 'projects.yml'), 'utf-8');
20
+ const hasName = /name:\s*\S+/.test(projectsContent) && !projectsContent.includes('name: #');
21
+ if (hasName) {
22
+ console.log(chalk.green(' ✓ projects.yml existe e tem nome do projeto'));
23
+ pass++;
24
+ } else {
25
+ console.log(chalk.yellow(' ⚠ projects.yml existe mas não está preenchido'));
26
+ fail++;
27
+ }
28
+ } catch {
29
+ console.log(chalk.red(' ✗ projects.yml não encontrado'));
30
+ fail++;
31
+ }
32
+
33
+ // Check 2: Agents configured
34
+ let agents = [];
35
+ let projectsData = null;
36
+ try {
37
+ projectsData = yaml.parse(projectsContent);
38
+ agents = projectsData?.project?.agents || [];
39
+ if (!Array.isArray(agents)) agents = [];
40
+ } catch { /* fallback: ignore parse error, agents = [] */ }
41
+
42
+ if (agents.length > 0) {
43
+ console.log(chalk.green(` ✓ AI tools configurados: ${agents.join(', ')}`));
44
+ pass++;
45
+ } else {
46
+ console.log(chalk.yellow(' ⚠ Nenhum AI tool configurado em projects.yml (campo agents:)'));
47
+
48
+ fail++;
49
+ }
50
+
51
+ // Check 2b: Repos configured
52
+ try {
53
+ const repos = projectsData?.repos;
54
+ const hasRepos = Array.isArray(repos) && repos.some(r => r && typeof r === 'object' && r.name);
55
+ if (hasRepos) {
56
+ console.log(chalk.green(` ✓ ${repos.filter(r => r && r.name).length} repo(s) configurado(s) em projects.yml`));
57
+ pass++;
58
+ } else {
59
+ console.log(chalk.yellow(' ⚠ Nenhum repo configurado em projects.yml — adicione ao menos 1 em repos:'));
60
+ fail++;
61
+ }
62
+ } catch { /* ignore */ }
63
+
64
+ // Check 3: Claude Code files
65
+ if (agents.includes('claude')) {
66
+ try {
67
+ claudeMcpContent = await readFile(join(cwd, '.mcp.json'), 'utf-8');
68
+ if (claudeMcpContent.includes('SEU-TOKEN') || claudeMcpContent.includes('SEU-DOMINIO')) {
69
+ console.log(chalk.yellow(' ⚠ .mcp.json (Claude) existe mas tem placeholders'));
70
+ fail++;
71
+ } else {
72
+ console.log(chalk.green(' ✓ .mcp.json (Claude) configurado'));
73
+ pass++;
74
+ }
75
+ } catch {
76
+ console.log(chalk.red(' ✗ .mcp.json (Claude) nao encontrado'));
77
+ fail++;
78
+ }
79
+
80
+ try {
81
+ await access(join(cwd, '.agents/agents/spec-hub.agent.md'));
82
+ console.log(chalk.green(' ✓ .agents/agents/spec-hub.agent.md existe'));
83
+ pass++;
84
+ } catch {
85
+ console.log(chalk.red(' ✗ .agents/agents/spec-hub.agent.md ausente'));
86
+ fail++;
87
+ }
88
+ }
89
+
90
+ // Check 4: Copilot files
91
+ if (agents.includes('copilot')) {
92
+ try {
93
+ copilotMcpContent = await readFile(join(cwd, '.vscode/mcp.json'), 'utf-8');
94
+ console.log(chalk.green(' ✓ .vscode/mcp.json (Copilot) encontrado'));
95
+ pass++;
96
+ } catch {
97
+ console.log(chalk.red(' ✗ .vscode/mcp.json (Copilot) ausente'));
98
+ fail++;
99
+ }
100
+
101
+ try {
102
+ await access(join(cwd, '.github/agents/spec-hub.agent.md'));
103
+ console.log(chalk.green(' ✓ .github/agents/spec-hub.agent.md existe'));
104
+ pass++;
105
+ } catch {
106
+ console.log(chalk.red(' ✗ .github/agents/spec-hub.agent.md ausente'));
107
+ fail++;
108
+ }
109
+
110
+ try {
111
+ await access(join(cwd, '.github/copilot-instructions.md'));
112
+ console.log(chalk.green(' ✓ .github/copilot-instructions.md existe'));
113
+ pass++;
114
+ } catch {
115
+ console.log(chalk.red(' ✗ .github/copilot-instructions.md ausente'));
116
+ fail++;
117
+ }
118
+ }
119
+
120
+ // Check 5: Skills exist
121
+ const skills = [
122
+ '.agents/skills/setup-project/SKILL.md',
123
+ '.agents/skills/discovery/SKILL.md',
124
+ '.agents/skills/specifying-features/SKILL.md',
125
+ '.agents/skills/dev-orchestrator/SKILL.md',
126
+ ];
127
+ let skillCount = 0;
128
+ for (const skill of skills) {
129
+ try {
130
+ await access(join(cwd, skill));
131
+ skillCount++;
132
+ } catch { /* missing */ }
133
+ }
134
+ if (skillCount === skills.length) {
135
+ console.log(chalk.green(` ✓ ${skillCount}/4 skills encontrados`));
136
+ pass++;
137
+ } else {
138
+ console.log(chalk.yellow(` ⚠ ${skillCount}/4 skills encontrados`));
139
+ fail++;
140
+ }
141
+
142
+ // Check 6: Rules exist
143
+ const rules = [
144
+ '.agents/rules/ownership.instructions.md',
145
+ '.agents/rules/hub_structure.instructions.md',
146
+ ];
147
+ let ruleCount = 0;
148
+ for (const rule of rules) {
149
+ try {
150
+ await access(join(cwd, rule));
151
+ ruleCount++;
152
+ } catch { /* missing */ }
153
+ }
154
+ if (ruleCount === rules.length) {
155
+ console.log(chalk.green(` ✓ ${ruleCount}/2 rules encontradas`));
156
+ pass++;
157
+ } else {
158
+ console.log(chalk.yellow(` ⚠ ${ruleCount}/2 rules encontradas`));
159
+ fail++;
160
+ }
161
+
162
+ // Check 7: MCP CLI available — verify Python version too
163
+ try {
164
+ const whereCmd = process.platform === 'win32' ? 'where mcp-atlassian' : 'which mcp-atlassian';
165
+ execSync(whereCmd, { stdio: 'pipe', timeout: 5000 });
166
+ console.log(chalk.green(' ✓ mcp-atlassian disponivel no PATH'));
167
+ pass++;
168
+ } catch {
169
+ console.log(chalk.yellow(' ⚠ mcp-atlassian nao encontrado'));
170
+ console.log(chalk.dim(' Instale com: pip install mcp-atlassian (requer Python >= 3.10)'));
171
+ fail++;
172
+ }
173
+
174
+ // Check 7b: Python version >= 3.10 (required by mcp-atlassian)
175
+ try {
176
+ const pyCmd = process.platform === 'win32' ? 'python --version' : 'python3 --version';
177
+ const pyVersion = execSync(pyCmd, { stdio: 'pipe', timeout: 5000 }).toString().trim();
178
+ const match = pyVersion.match(/(\d+)\.(\d+)/);
179
+ if (match) {
180
+ const major = Number(match[1]);
181
+ const minor = Number(match[2]);
182
+ if (major > 3 || (major === 3 && minor >= 10)) {
183
+ console.log(chalk.green(` ✓ Python ${major}.${minor} (>= 3.10 exigido pelo mcp-atlassian)`));
184
+ pass++;
185
+ } else {
186
+ console.log(chalk.yellow(` ⚠ Python ${major}.${minor} detectado — mcp-atlassian requer >= 3.10`));
187
+ console.log(chalk.dim(' Instale Python 3.10+: https://python.org/downloads'));
188
+ fail++;
189
+ }
190
+ }
191
+ } catch {
192
+ console.log(chalk.yellow(' ⚠ Python não encontrado no PATH'));
193
+ console.log(chalk.dim(' Instale Python 3.10+: https://python.org/downloads'));
194
+ fail++;
195
+ }
196
+
197
+ // Check 8: Git repo
198
+ try {
199
+ execSync('git rev-parse --is-inside-work-tree', { cwd, stdio: 'pipe', timeout: 10000 });
200
+ console.log(chalk.green(' ✓ Repositório Git inicializado'));
201
+ pass++;
202
+ } catch {
203
+ console.log(chalk.yellow(' ⚠ Não é um repositório Git'));
204
+ fail++;
205
+ }
206
+
207
+ // Check 9: Dual-platform sync
208
+ if (agents.includes('claude') && agents.includes('copilot')) {
209
+ try {
210
+ const claudeAgent = await readFile(join(cwd, '.agents/agents/spec-hub.agent.md'), 'utf-8');
211
+ const copilotAgent = await readFile(join(cwd, '.github/agents/spec-hub.agent.md'), 'utf-8');
212
+
213
+ // Simple check: both should reference spec-agent
214
+ if (claudeAgent.includes('spec-agent') && copilotAgent.includes('spec-agent')) {
215
+ console.log(chalk.green(' ✓ Agents sincronizados entre Claude e Copilot'));
216
+ pass++;
217
+ } else {
218
+ console.log(chalk.yellow(' ⚠ Agents podem estar dessincronizados'));
219
+ fail++;
220
+ }
221
+ } catch {
222
+ console.log(chalk.yellow(' ⚠ Nao foi possivel verificar sincronizacao'));
223
+ fail++;
224
+ }
225
+ }
226
+
227
+ // Check 10: Figma MCP (optional — only if projects.yml declares figma)
228
+ const hasFigma = /figma:\s*\n\s+file_url:\s*\S+/.test(projectsContent);
229
+ if (hasFigma) {
230
+ if (claudeMcpContent) {
231
+ if (claudeMcpContent.includes('"figma"')) {
232
+ if (claudeMcpContent.includes('SEU-FIGMA-API-KEY')) {
233
+ console.log(chalk.yellow(' ⚠ .mcp.json tem servidor Figma mas com placeholder'));
234
+ fail++;
235
+ } else {
236
+ console.log(chalk.green(' ✓ .mcp.json (Claude) tem servidor Figma configurado'));
237
+ pass++;
238
+ }
239
+ } else {
240
+ console.log(chalk.yellow(' ⚠ projects.yml tem figma: mas .mcp.json não tem servidor figma'));
241
+ fail++;
242
+ }
243
+ }
244
+ if (copilotMcpContent) {
245
+ if (copilotMcpContent.includes('"figma"')) {
246
+ console.log(chalk.green(' ✓ .vscode/mcp.json (Copilot) tem servidor Figma configurado'));
247
+ pass++;
248
+ } else {
249
+ console.log(chalk.yellow(' ⚠ projects.yml tem figma: mas .vscode/mcp.json não tem servidor figma'));
250
+ fail++;
251
+ }
252
+ }
253
+ }
254
+
255
+ // Check 11: Confluence credentials — make real HTTP call to validate token
256
+ try {
257
+ const envContent = await readFile(join(cwd, '.env'), 'utf-8');
258
+ const envVars = Object.fromEntries(
259
+ envContent.split('\n')
260
+ .filter(l => l.includes('=') && !l.startsWith('#'))
261
+ .map(l => { const idx = l.indexOf('='); return [l.slice(0, idx).trim(), l.slice(idx + 1).trim()]; }),
262
+ );
263
+
264
+ const confUrl = envVars.CONFLUENCE_URL;
265
+ const confUser = envVars.CONFLUENCE_USER;
266
+ const confToken = envVars.CONFLUENCE_API_TOKEN;
267
+
268
+ const isPlaceholder = (v) => !v || v.includes('seu-') || v.includes('SEU-') || v === 'seu-api-token-aqui';
269
+
270
+ if (isPlaceholder(confUrl) || isPlaceholder(confUser) || isPlaceholder(confToken)) {
271
+ console.log(chalk.yellow(' ⚠ .env não configurado — pular validação de credenciais Confluence'));
272
+ console.log(chalk.dim(' Configure CONFLUENCE_URL, CONFLUENCE_USER e CONFLUENCE_API_TOKEN no .env'));
273
+ fail++;
274
+ } else {
275
+ const basicAuth = Buffer.from(`${confUser}:${confToken}`).toString('base64');
276
+ const controller = new AbortController();
277
+ const timeout = setTimeout(() => controller.abort(), 10000);
278
+
279
+ try {
280
+ const res = await fetch(`${confUrl}/rest/api/space?limit=1`, {
281
+ headers: { Authorization: `Basic ${basicAuth}`, Accept: 'application/json' },
282
+ signal: controller.signal,
283
+ });
284
+ clearTimeout(timeout);
285
+
286
+ if (res.ok) {
287
+ console.log(chalk.green(' ✓ Credenciais Confluence válidas (token ativo)'));
288
+ pass++;
289
+ } else if (res.status === 401 || res.status === 403) {
290
+ console.log(chalk.red(' ✗ Token Confluence inválido ou expirado (HTTP ' + res.status + ')'));
291
+ console.log(chalk.dim(' Renove em: https://id.atlassian.com/manage-profile/security/api-tokens'));
292
+ console.log(chalk.dim(' Atualize CONFLUENCE_API_TOKEN no .env'));
293
+ fail++;
294
+ } else {
295
+ console.log(chalk.yellow(` ⚠ Confluence respondeu HTTP ${res.status} — verifique CONFLUENCE_URL`));
296
+ fail++;
297
+ }
298
+ } catch (fetchErr) {
299
+ clearTimeout(timeout);
300
+ if (fetchErr.name === 'AbortError') {
301
+ console.log(chalk.yellow(' ⚠ Confluence não respondeu em 10s — sem rede ou URL incorreta'));
302
+ } else {
303
+ console.log(chalk.yellow(` ⚠ Não foi possível verificar credenciais Confluence: ${fetchErr.message}`));
304
+ }
305
+ // Network errors are WARN not FAIL — offline dev should not be blocked
306
+ fail++;
307
+ }
308
+ }
309
+ } catch {
310
+ // .env not found — already warned by MCP config check
311
+ }
312
+
313
+ // Summary
314
+ console.log(chalk.bold('\n Resultado:\n'));
315
+ console.log(` ${chalk.green('✓')} ${pass} ok`);
316
+ if (fail > 0) console.log(` ${chalk.yellow('⚠')} ${fail} problemas`);
317
+ console.log('');
318
+
319
+ if (fail > 0) {
320
+ console.log(chalk.dim(' Execute "open-spec-kit init" para corrigir a estrutura.\n'));
321
+ } else {
322
+ console.log(chalk.green(' Ambiente pronto! Execute /setup para comecar.\n'));
323
+ }
324
+ }