@guilhermefsousa/open-spec-kit 0.1.0
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/README.md +57 -0
- package/bin/open-spec-kit.js +39 -0
- package/package.json +51 -0
- package/src/commands/doctor.js +324 -0
- package/src/commands/init.js +981 -0
- package/src/commands/update.js +168 -0
- package/src/commands/validate.js +599 -0
- package/src/parsers/markdown-sections.js +271 -0
- package/src/schemas/projects.schema.js +111 -0
- package/src/schemas/spec.schema.js +643 -0
- package/templates/agents/agents/spec-hub.agent.md +99 -0
- package/templates/agents/rules/hub_structure.instructions.md +49 -0
- package/templates/agents/rules/ownership.instructions.md +138 -0
- package/templates/agents/scripts/notify-gchat.ps1 +99 -0
- package/templates/agents/scripts/notify-gchat.sh +131 -0
- package/templates/agents/skills/dev-orchestrator/SKILL.md +573 -0
- package/templates/agents/skills/discovery/SKILL.md +406 -0
- package/templates/agents/skills/setup-project/SKILL.md +452 -0
- package/templates/agents/skills/specifying-features/SKILL.md +378 -0
- package/templates/github/agents/spec-hub.agent.md +75 -0
- package/templates/github/copilot-instructions.md +102 -0
- package/templates/github/instructions/hub_structure.instructions.md +33 -0
- package/templates/github/instructions/ownership.instructions.md +45 -0
- package/templates/github/prompts/dev.prompt.md +19 -0
- package/templates/github/prompts/discovery.prompt.md +20 -0
- package/templates/github/prompts/nova-feature.prompt.md +19 -0
- package/templates/github/prompts/setup.prompt.md +18 -0
- package/templates/github/skills/dev-orchestrator/SKILL.md +9 -0
- package/templates/github/skills/discovery/SKILL.md +9 -0
- package/templates/github/skills/setup-project/SKILL.md +9 -0
- 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.1.0",
|
|
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
|
+
}
|