@guilhermefsousa/open-spec-kit 0.0.9 → 0.0.11
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 +1 -1
- package/bin/open-spec-kit.js +7 -0
- package/package.json +1 -1
- package/src/commands/doctor.js +107 -197
- package/src/commands/init.js +112 -347
- package/src/commands/install.js +393 -0
- package/src/commands/update.js +117 -165
- package/src/schemas/spec.schema.js +3 -3
- package/src/utils/global-path.js +73 -0
- package/templates/agents/agents/spec-hub.agent.md +13 -13
- package/templates/agents/rules/hub_structure.instructions.md +1 -1
- package/templates/agents/rules/ownership.instructions.md +39 -39
- package/templates/agents/skills/dev-orchestrator/SKILL.md +17 -17
- package/templates/agents/skills/discovery/SKILL.md +17 -17
- package/templates/agents/skills/setup-project/SKILL.md +15 -15
- package/templates/agents/skills/specifying-features/SKILL.md +28 -28
- package/templates/github/agents/spec-hub.agent.md +5 -5
- package/templates/github/copilot-instructions.md +9 -9
- package/templates/github/instructions/hub_structure.instructions.md +1 -1
- package/templates/github/instructions/ownership.instructions.md +9 -9
- package/templates/github/skills/dev-orchestrator/SKILL.md +619 -5
- package/templates/github/skills/discovery/SKILL.md +419 -5
- package/templates/github/skills/setup-project/SKILL.md +496 -5
- package/templates/github/skills/specifying-features/SKILL.md +417 -5
- /package/templates/github/prompts/{dev.prompt.md → osk-build.prompt.md} +0 -0
- /package/templates/github/prompts/{discovery.prompt.md → osk-discover.prompt.md} +0 -0
- /package/templates/github/prompts/{setup.prompt.md → osk-init.prompt.md} +0 -0
- /package/templates/github/prompts/{nova-feature.prompt.md → osk-spec.prompt.md} +0 -0
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ npx open-spec-kit update
|
|
|
23
23
|
O workflow completo roda com 4 skills no VS Code:
|
|
24
24
|
|
|
25
25
|
```
|
|
26
|
-
/
|
|
26
|
+
/osk-init → /osk-discover → /osk-spec → /osk-build
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Cada skill lê o que a anterior produziu, gera artefatos e atualiza o Confluence automaticamente.
|
package/bin/open-spec-kit.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { initCommand } from '../src/commands/init.js';
|
|
5
|
+
import { installCommand } from '../src/commands/install.js';
|
|
5
6
|
import { updateCommand } from '../src/commands/update.js';
|
|
6
7
|
import { validateCommand } from '../src/commands/validate.js';
|
|
7
8
|
import { doctorCommand } from '../src/commands/doctor.js';
|
|
@@ -18,6 +19,12 @@ program
|
|
|
18
19
|
.description('Inicializar estrutura de spec-driven development no projeto')
|
|
19
20
|
.action(initCommand);
|
|
20
21
|
|
|
22
|
+
program
|
|
23
|
+
.command('install')
|
|
24
|
+
.description('Instalar skills e agents globalmente em ~/.open-spec-kit/')
|
|
25
|
+
.option('--force', 'Sobrescrever tudo, incluindo credenciais')
|
|
26
|
+
.action(installCommand);
|
|
27
|
+
|
|
21
28
|
program
|
|
22
29
|
.command('update')
|
|
23
30
|
.description('Re-gerar arquivos de adapter quando skills ou agents mudarem')
|
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -5,6 +5,7 @@ import { execSync } from 'child_process';
|
|
|
5
5
|
import yaml from 'yaml';
|
|
6
6
|
import { detectMcpRunner, getInstallInstructions } from '../utils/mcp-detect.js';
|
|
7
7
|
import { confluenceRequest, figmaRequest, isSslError } from '../utils/http.js';
|
|
8
|
+
import { GLOBAL_DIR, getGlobalPath, getGlobalEnvPath, parseEnvContent } from '../utils/global-path.js';
|
|
8
9
|
|
|
9
10
|
export async function doctorCommand() {
|
|
10
11
|
console.log(chalk.bold('\n open-spec-kit doctor\n'));
|
|
@@ -16,6 +17,28 @@ export async function doctorCommand() {
|
|
|
16
17
|
let claudeMcpContent = '';
|
|
17
18
|
let copilotMcpContent = '';
|
|
18
19
|
|
|
20
|
+
// Check 0: Global installation exists
|
|
21
|
+
try {
|
|
22
|
+
await access(GLOBAL_DIR);
|
|
23
|
+
await access(getGlobalEnvPath());
|
|
24
|
+
console.log(chalk.green(` ✓ Instalação global encontrada (${GLOBAL_DIR})`));
|
|
25
|
+
pass++;
|
|
26
|
+
} catch {
|
|
27
|
+
console.log(chalk.red(` ✗ Instalação global não encontrada (${GLOBAL_DIR})`));
|
|
28
|
+
console.log(chalk.dim(' Execute "open-spec-kit install" para instalar skills e credenciais.'));
|
|
29
|
+
fail++;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check 0b: Version check
|
|
33
|
+
try {
|
|
34
|
+
const versionJson = JSON.parse(await readFile(getGlobalPath('version.json'), 'utf-8'));
|
|
35
|
+
console.log(chalk.green(` ✓ Versão instalada: ${versionJson.version}`));
|
|
36
|
+
pass++;
|
|
37
|
+
} catch {
|
|
38
|
+
console.log(chalk.yellow(' ⚠ version.json ausente — execute "open-spec-kit install" para atualizar'));
|
|
39
|
+
fail++;
|
|
40
|
+
}
|
|
41
|
+
|
|
19
42
|
// Check 1: projects.yml exists and is filled
|
|
20
43
|
try {
|
|
21
44
|
projectsContent = await readFile(join(cwd, 'projects.yml'), 'utf-8');
|
|
@@ -32,25 +55,12 @@ export async function doctorCommand() {
|
|
|
32
55
|
fail++;
|
|
33
56
|
}
|
|
34
57
|
|
|
35
|
-
// Check 2:
|
|
36
|
-
let agents = [];
|
|
58
|
+
// Check 2: Repos configured
|
|
37
59
|
let projectsData = null;
|
|
38
60
|
try {
|
|
39
61
|
projectsData = yaml.parse(projectsContent);
|
|
40
|
-
|
|
41
|
-
if (!Array.isArray(agents)) agents = [];
|
|
42
|
-
} catch { /* fallback: ignore parse error, agents = [] */ }
|
|
43
|
-
|
|
44
|
-
if (agents.length > 0) {
|
|
45
|
-
console.log(chalk.green(` ✓ AI tools configurados: ${agents.join(', ')}`));
|
|
46
|
-
pass++;
|
|
47
|
-
} else {
|
|
48
|
-
console.log(chalk.yellow(' ⚠ Nenhum AI tool configurado em projects.yml (campo agents:)'));
|
|
49
|
-
|
|
50
|
-
fail++;
|
|
51
|
-
}
|
|
62
|
+
} catch { /* ignore */ }
|
|
52
63
|
|
|
53
|
-
// Check 2b: Repos configured
|
|
54
64
|
try {
|
|
55
65
|
const repos = projectsData?.repos;
|
|
56
66
|
const hasRepos = Array.isArray(repos) && repos.some(r => r && typeof r === 'object' && r.name);
|
|
@@ -63,131 +73,101 @@ export async function doctorCommand() {
|
|
|
63
73
|
}
|
|
64
74
|
} catch { /* ignore */ }
|
|
65
75
|
|
|
66
|
-
// Check 3:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|| claudeMcpContent.includes('seu-email') || claudeMcpContent.includes('${CONFLUENCE_URL}')
|
|
73
|
-
|| claudeMcpContent.includes('${CONFLUENCE_API_TOKEN}')) {
|
|
74
|
-
console.log(chalk.yellow(' ⚠ .mcp.json (Claude) existe mas tem placeholders'));
|
|
75
|
-
fail++;
|
|
76
|
-
} else {
|
|
77
|
-
console.log(chalk.green(' ✓ .mcp.json (Claude) configurado'));
|
|
78
|
-
pass++;
|
|
79
|
-
}
|
|
80
|
-
} catch {
|
|
81
|
-
console.log(chalk.red(' ✗ .mcp.json (Claude) nao encontrado'));
|
|
76
|
+
// Check 3: MCP configs (still per-project)
|
|
77
|
+
try {
|
|
78
|
+
claudeMcpContent = await readFile(join(cwd, '.mcp.json'), 'utf-8');
|
|
79
|
+
if (claudeMcpContent.includes('SEU-TOKEN') || claudeMcpContent.includes('seu-token-aqui')
|
|
80
|
+
|| claudeMcpContent.includes('seu-dominio') || claudeMcpContent.includes('${CONFLUENCE_URL}')) {
|
|
81
|
+
console.log(chalk.yellow(' ⚠ .mcp.json (Claude) existe mas tem placeholders'));
|
|
82
82
|
fail++;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
await access(join(cwd, '.agents/agents/spec-hub.agent.md'));
|
|
87
|
-
console.log(chalk.green(' ✓ .agents/agents/spec-hub.agent.md existe'));
|
|
83
|
+
} else {
|
|
84
|
+
console.log(chalk.green(' ✓ .mcp.json (Claude) configurado'));
|
|
88
85
|
pass++;
|
|
89
|
-
} catch {
|
|
90
|
-
console.log(chalk.red(' ✗ .agents/agents/spec-hub.agent.md ausente'));
|
|
91
|
-
fail++;
|
|
92
86
|
}
|
|
87
|
+
} catch {
|
|
88
|
+
console.log(chalk.yellow(' ⚠ .mcp.json (Claude) não encontrado'));
|
|
89
|
+
fail++;
|
|
93
90
|
}
|
|
94
91
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|| copilotMcpContent.includes('seu-token-aqui') || copilotMcpContent.includes('seu-dominio')
|
|
101
|
-
|| copilotMcpContent.includes('seu-email') || copilotMcpContent.includes('${CONFLUENCE_URL}')
|
|
102
|
-
|| copilotMcpContent.includes('${CONFLUENCE_API_TOKEN}')) {
|
|
103
|
-
console.log(chalk.yellow(' ⚠ .vscode/mcp.json (Copilot) existe mas tem placeholders'));
|
|
104
|
-
fail++;
|
|
105
|
-
} else {
|
|
106
|
-
console.log(chalk.green(' ✓ .vscode/mcp.json (Copilot) configurado'));
|
|
107
|
-
pass++;
|
|
108
|
-
}
|
|
109
|
-
} catch {
|
|
110
|
-
console.log(chalk.red(' ✗ .vscode/mcp.json (Copilot) ausente'));
|
|
92
|
+
try {
|
|
93
|
+
copilotMcpContent = await readFile(join(cwd, '.vscode/mcp.json'), 'utf-8');
|
|
94
|
+
if (copilotMcpContent.includes('SEU-TOKEN') || copilotMcpContent.includes('seu-token-aqui')
|
|
95
|
+
|| copilotMcpContent.includes('seu-dominio') || copilotMcpContent.includes('${CONFLUENCE_URL}')) {
|
|
96
|
+
console.log(chalk.yellow(' ⚠ .vscode/mcp.json (Copilot) existe mas tem placeholders'));
|
|
111
97
|
fail++;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
await access(join(cwd, '.github/agents/spec-hub.agent.md'));
|
|
116
|
-
console.log(chalk.green(' ✓ .github/agents/spec-hub.agent.md existe'));
|
|
98
|
+
} else {
|
|
99
|
+
console.log(chalk.green(' ✓ .vscode/mcp.json (Copilot) configurado'));
|
|
117
100
|
pass++;
|
|
118
|
-
} catch {
|
|
119
|
-
console.log(chalk.red(' ✗ .github/agents/spec-hub.agent.md ausente'));
|
|
120
|
-
fail++;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
await access(join(cwd, '.github/copilot-instructions.md'));
|
|
125
|
-
console.log(chalk.green(' ✓ .github/copilot-instructions.md existe'));
|
|
126
|
-
pass++;
|
|
127
|
-
} catch {
|
|
128
|
-
console.log(chalk.red(' ✗ .github/copilot-instructions.md ausente'));
|
|
129
|
-
fail++;
|
|
130
101
|
}
|
|
102
|
+
} catch {
|
|
103
|
+
console.log(chalk.yellow(' ⚠ .vscode/mcp.json (Copilot) não encontrado'));
|
|
104
|
+
fail++;
|
|
131
105
|
}
|
|
132
106
|
|
|
133
|
-
// Check
|
|
107
|
+
// Check 4: Global skills exist
|
|
134
108
|
const skills = [
|
|
135
|
-
'
|
|
136
|
-
'
|
|
137
|
-
'
|
|
138
|
-
'
|
|
109
|
+
'skills/setup-project/SKILL.md',
|
|
110
|
+
'skills/discovery/SKILL.md',
|
|
111
|
+
'skills/specifying-features/SKILL.md',
|
|
112
|
+
'skills/dev-orchestrator/SKILL.md',
|
|
139
113
|
];
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
await access(join(cwd, skill));
|
|
144
|
-
skillCount++;
|
|
145
|
-
} catch { /* missing */ }
|
|
146
|
-
}
|
|
114
|
+
const skillResults = await Promise.all(skills.map(s => access(getGlobalPath(s)).then(() => true, () => false)));
|
|
115
|
+
const skillCount = skillResults.filter(Boolean).length;
|
|
147
116
|
if (skillCount === skills.length) {
|
|
148
|
-
console.log(chalk.green(` ✓ ${skillCount}/4 skills encontrados`));
|
|
117
|
+
console.log(chalk.green(` ✓ ${skillCount}/4 skills globais encontrados`));
|
|
149
118
|
pass++;
|
|
150
119
|
} else {
|
|
151
|
-
console.log(chalk.yellow(` ⚠ ${skillCount}/4 skills encontrados`));
|
|
120
|
+
console.log(chalk.yellow(` ⚠ ${skillCount}/4 skills globais encontrados`));
|
|
152
121
|
fail++;
|
|
153
122
|
}
|
|
154
123
|
|
|
155
|
-
// Check
|
|
156
|
-
const rules = [
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
];
|
|
160
|
-
let ruleCount = 0;
|
|
161
|
-
for (const rule of rules) {
|
|
162
|
-
try {
|
|
163
|
-
await access(join(cwd, rule));
|
|
164
|
-
ruleCount++;
|
|
165
|
-
} catch { /* missing */ }
|
|
166
|
-
}
|
|
124
|
+
// Check 5: Global rules exist
|
|
125
|
+
const rules = ['rules/ownership.instructions.md', 'rules/hub_structure.instructions.md'];
|
|
126
|
+
const ruleResults = await Promise.all(rules.map(r => access(getGlobalPath(r)).then(() => true, () => false)));
|
|
127
|
+
const ruleCount = ruleResults.filter(Boolean).length;
|
|
167
128
|
if (ruleCount === rules.length) {
|
|
168
|
-
console.log(chalk.green(` ✓ ${ruleCount}/2 rules encontradas`));
|
|
129
|
+
console.log(chalk.green(` ✓ ${ruleCount}/2 rules globais encontradas`));
|
|
169
130
|
pass++;
|
|
170
131
|
} else {
|
|
171
|
-
console.log(chalk.yellow(` ⚠ ${ruleCount}/2 rules encontradas`));
|
|
132
|
+
console.log(chalk.yellow(` ⚠ ${ruleCount}/2 rules globais encontradas`));
|
|
172
133
|
fail++;
|
|
173
134
|
}
|
|
174
135
|
|
|
175
|
-
// Check
|
|
136
|
+
// Check 6: Global agent hub
|
|
137
|
+
try {
|
|
138
|
+
await access(getGlobalPath('agents', 'spec-hub.agent.md'));
|
|
139
|
+
console.log(chalk.green(' ✓ spec-hub.agent.md global encontrado'));
|
|
140
|
+
pass++;
|
|
141
|
+
} catch {
|
|
142
|
+
console.log(chalk.yellow(' ⚠ spec-hub.agent.md global ausente'));
|
|
143
|
+
fail++;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check 7: Copilot global files
|
|
147
|
+
try {
|
|
148
|
+
await access(getGlobalPath('github', 'agents', 'spec-hub.agent.md'));
|
|
149
|
+
console.log(chalk.green(' ✓ Copilot agents globais encontrados'));
|
|
150
|
+
pass++;
|
|
151
|
+
} catch {
|
|
152
|
+
console.log(chalk.yellow(' ⚠ Copilot agents globais ausentes'));
|
|
153
|
+
fail++;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check 8: MCP CLI available
|
|
176
157
|
const mcpRunner = detectMcpRunner();
|
|
177
158
|
if (mcpRunner.method !== 'pip-fallback') {
|
|
178
159
|
const via = mcpRunner.method === 'direct' ? 'PATH' : mcpRunner.method;
|
|
179
|
-
console.log(chalk.green(` ✓ mcp-atlassian disponível via ${via}
|
|
160
|
+
console.log(chalk.green(` ✓ mcp-atlassian disponível via ${via}`));
|
|
180
161
|
pass++;
|
|
181
162
|
} else {
|
|
182
|
-
console.log(chalk.yellow(' ⚠ mcp-atlassian não encontrado
|
|
163
|
+
console.log(chalk.yellow(' ⚠ mcp-atlassian não encontrado'));
|
|
183
164
|
for (const line of getInstallInstructions()) {
|
|
184
165
|
console.log(chalk.dim(` ${line}`));
|
|
185
166
|
}
|
|
186
167
|
fail++;
|
|
187
168
|
}
|
|
188
169
|
|
|
189
|
-
// Check
|
|
190
|
-
// Try python3 first (Linux/macOS default), fall back to python (Windows default)
|
|
170
|
+
// Check 9: Python version >= 3.10
|
|
191
171
|
try {
|
|
192
172
|
let pyVersion;
|
|
193
173
|
try {
|
|
@@ -200,21 +180,19 @@ export async function doctorCommand() {
|
|
|
200
180
|
const major = Number(match[1]);
|
|
201
181
|
const minor = Number(match[2]);
|
|
202
182
|
if (major > 3 || (major === 3 && minor >= 10)) {
|
|
203
|
-
console.log(chalk.green(` ✓ Python ${major}.${minor}
|
|
183
|
+
console.log(chalk.green(` ✓ Python ${major}.${minor}`));
|
|
204
184
|
pass++;
|
|
205
185
|
} else {
|
|
206
|
-
console.log(chalk.yellow(` ⚠ Python ${major}.${minor}
|
|
207
|
-
console.log(chalk.dim(' Instale Python 3.10+: https://python.org/downloads'));
|
|
186
|
+
console.log(chalk.yellow(` ⚠ Python ${major}.${minor} — mcp-atlassian requer >= 3.10`));
|
|
208
187
|
fail++;
|
|
209
188
|
}
|
|
210
189
|
}
|
|
211
190
|
} catch {
|
|
212
191
|
console.log(chalk.yellow(' ⚠ Python não encontrado no PATH'));
|
|
213
|
-
console.log(chalk.dim(' Instale Python 3.10+: https://python.org/downloads'));
|
|
214
192
|
fail++;
|
|
215
193
|
}
|
|
216
194
|
|
|
217
|
-
// Check
|
|
195
|
+
// Check 10: Git repo
|
|
218
196
|
try {
|
|
219
197
|
execSync('git rev-parse --is-inside-work-tree', { cwd, stdio: 'pipe', timeout: 10000 });
|
|
220
198
|
console.log(chalk.green(' ✓ Repositório Git inicializado'));
|
|
@@ -224,127 +202,58 @@ export async function doctorCommand() {
|
|
|
224
202
|
fail++;
|
|
225
203
|
}
|
|
226
204
|
|
|
227
|
-
// Check
|
|
228
|
-
if (agents.includes('claude') && agents.includes('copilot')) {
|
|
229
|
-
try {
|
|
230
|
-
const claudeAgent = await readFile(join(cwd, '.agents/agents/spec-hub.agent.md'), 'utf-8');
|
|
231
|
-
const copilotAgent = await readFile(join(cwd, '.github/agents/spec-hub.agent.md'), 'utf-8');
|
|
232
|
-
|
|
233
|
-
// Simple check: both should reference spec-agent
|
|
234
|
-
if (claudeAgent.includes('spec-agent') && copilotAgent.includes('spec-agent')) {
|
|
235
|
-
console.log(chalk.green(' ✓ Agents sincronizados entre Claude e Copilot'));
|
|
236
|
-
pass++;
|
|
237
|
-
} else {
|
|
238
|
-
console.log(chalk.yellow(' ⚠ Agents podem estar dessincronizados'));
|
|
239
|
-
fail++;
|
|
240
|
-
}
|
|
241
|
-
} catch {
|
|
242
|
-
console.log(chalk.yellow(' ⚠ Nao foi possivel verificar sincronizacao'));
|
|
243
|
-
fail++;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Check 10: Figma MCP (optional — only if projects.yml declares figma)
|
|
248
|
-
const hasFigma = /figma:\s*\n\s+file_url:\s*\S+/.test(projectsContent);
|
|
249
|
-
if (hasFigma) {
|
|
250
|
-
if (claudeMcpContent) {
|
|
251
|
-
if (claudeMcpContent.includes('"figma"')) {
|
|
252
|
-
console.log(chalk.green(' ✓ .mcp.json (Claude) tem servidor Figma configurado'));
|
|
253
|
-
pass++;
|
|
254
|
-
} else {
|
|
255
|
-
console.log(chalk.yellow(' ⚠ projects.yml tem figma: mas .mcp.json não tem servidor figma'));
|
|
256
|
-
fail++;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
if (copilotMcpContent) {
|
|
260
|
-
if (copilotMcpContent.includes('"figma"')) {
|
|
261
|
-
console.log(chalk.green(' ✓ .vscode/mcp.json (Copilot) tem servidor Figma configurado'));
|
|
262
|
-
pass++;
|
|
263
|
-
} else {
|
|
264
|
-
console.log(chalk.yellow(' ⚠ projects.yml tem figma: mas .vscode/mcp.json não tem servidor figma'));
|
|
265
|
-
fail++;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Parse .env once for credential checks (11 + 12)
|
|
205
|
+
// Check 11: Confluence credentials (from global .env)
|
|
271
206
|
let envVars = {};
|
|
272
207
|
try {
|
|
273
|
-
const envContent = await readFile(
|
|
274
|
-
envVars =
|
|
275
|
-
|
|
276
|
-
.filter(l => l.includes('=') && !l.startsWith('#'))
|
|
277
|
-
.map(l => { const idx = l.indexOf('='); return [l.slice(0, idx).trim(), l.slice(idx + 1).trim()]; }),
|
|
278
|
-
);
|
|
279
|
-
} catch {
|
|
280
|
-
// .env not found — already warned by MCP config check
|
|
281
|
-
}
|
|
208
|
+
const envContent = await readFile(getGlobalEnvPath(), 'utf-8');
|
|
209
|
+
envVars = parseEnvContent(envContent);
|
|
210
|
+
} catch { /* global .env not found */ }
|
|
282
211
|
|
|
283
|
-
const isPlaceholder = (v) => !v || v.includes('seu-') || v.includes('SEU-')
|
|
212
|
+
const isPlaceholder = (v) => !v || v.includes('seu-') || v.includes('SEU-');
|
|
284
213
|
|
|
285
|
-
// Check 11: Confluence credentials — make real HTTP call to validate token
|
|
286
214
|
{
|
|
287
215
|
const confUrl = envVars.CONFLUENCE_URL;
|
|
288
216
|
const confUser = envVars.CONFLUENCE_USERNAME;
|
|
289
217
|
const confToken = envVars.CONFLUENCE_API_TOKEN;
|
|
290
218
|
|
|
291
219
|
if (isPlaceholder(confUrl) || isPlaceholder(confUser) || isPlaceholder(confToken)) {
|
|
292
|
-
console.log(chalk.yellow(' ⚠
|
|
293
|
-
console.log(chalk.dim(
|
|
220
|
+
console.log(chalk.yellow(' ⚠ Credenciais Confluence não configuradas no .env global'));
|
|
221
|
+
console.log(chalk.dim(` Configure em: ${getGlobalEnvPath()}`));
|
|
294
222
|
fail++;
|
|
295
223
|
} else {
|
|
296
224
|
try {
|
|
297
225
|
await confluenceRequest(confUrl, '/rest/api/space?limit=1', confUser, confToken, 10000);
|
|
298
|
-
console.log(chalk.green(' ✓ Credenciais Confluence válidas
|
|
226
|
+
console.log(chalk.green(' ✓ Credenciais Confluence válidas'));
|
|
299
227
|
pass++;
|
|
300
228
|
} catch (err) {
|
|
301
229
|
if (err.status === 401 || err.status === 403) {
|
|
302
|
-
console.log(chalk.red(` ✗ Token Confluence inválido
|
|
303
|
-
console.log(chalk.dim(
|
|
304
|
-
console.log(chalk.dim(' Atualize CONFLUENCE_API_TOKEN no .env'));
|
|
230
|
+
console.log(chalk.red(` ✗ Token Confluence inválido (HTTP ${err.status})`));
|
|
231
|
+
console.log(chalk.dim(` Atualize em: ${getGlobalEnvPath()}`));
|
|
305
232
|
fail++;
|
|
306
233
|
} else {
|
|
307
|
-
console.log(chalk.yellow(` ⚠ Não foi possível verificar credenciais
|
|
234
|
+
console.log(chalk.yellow(` ⚠ Não foi possível verificar credenciais: ${err.message}`));
|
|
308
235
|
fail++;
|
|
309
236
|
}
|
|
310
237
|
}
|
|
311
238
|
}
|
|
312
239
|
}
|
|
313
240
|
|
|
314
|
-
// Check 12: Figma credentials
|
|
241
|
+
// Check 12: Figma credentials (optional)
|
|
242
|
+
const hasFigma = /figma:\s*\n\s+file_url:\s*\S+/.test(projectsContent);
|
|
315
243
|
if (hasFigma) {
|
|
316
244
|
const figmaKey = envVars.FIGMA_API_KEY;
|
|
317
|
-
|
|
318
245
|
if (isPlaceholder(figmaKey)) {
|
|
319
|
-
console.log(chalk.yellow(' ⚠ FIGMA_API_KEY não configurado no .env'));
|
|
320
|
-
console.log(chalk.dim(' Gere em: Figma > Account Settings > Personal Access Tokens'));
|
|
246
|
+
console.log(chalk.yellow(' ⚠ FIGMA_API_KEY não configurado no .env global'));
|
|
321
247
|
fail++;
|
|
322
248
|
} else {
|
|
323
249
|
try {
|
|
324
250
|
const result = await figmaRequest(figmaKey, 10000);
|
|
325
|
-
console.log(chalk.green(` ✓ Token Figma válido (
|
|
251
|
+
console.log(chalk.green(` ✓ Token Figma válido (${result.data.handle || result.data.email || 'OK'})`));
|
|
326
252
|
pass++;
|
|
327
253
|
} catch (err) {
|
|
328
254
|
if (err.status === 401 || err.status === 403) {
|
|
329
|
-
console.log(chalk.red(` ✗ Token Figma inválido
|
|
330
|
-
console.log(chalk.dim(' Renove em: Figma > Account Settings > Personal Access Tokens'));
|
|
331
|
-
console.log(chalk.dim(' Atualize FIGMA_API_KEY no .env'));
|
|
255
|
+
console.log(chalk.red(` ✗ Token Figma inválido (HTTP ${err.status})`));
|
|
332
256
|
fail++;
|
|
333
|
-
} else if (isSslError(err)) {
|
|
334
|
-
try {
|
|
335
|
-
const result = await figmaRequest(figmaKey, 10000, true);
|
|
336
|
-
console.log(chalk.green(` ✓ Token Figma válido — SSL corporativo ignorado (usuário: ${result.data.handle || result.data.email || 'OK'})`));
|
|
337
|
-
pass++;
|
|
338
|
-
} catch (retryErr) {
|
|
339
|
-
if (retryErr.status === 401 || retryErr.status === 403) {
|
|
340
|
-
console.log(chalk.red(` ✗ Token Figma inválido ou expirado (HTTP ${retryErr.status})`));
|
|
341
|
-
console.log(chalk.dim(' Renove em: Figma > Account Settings > Personal Access Tokens'));
|
|
342
|
-
console.log(chalk.dim(' Atualize FIGMA_API_KEY no .env'));
|
|
343
|
-
} else {
|
|
344
|
-
console.log(chalk.yellow(` ⚠ Não foi possível verificar token Figma: ${retryErr.message}`));
|
|
345
|
-
}
|
|
346
|
-
fail++;
|
|
347
|
-
}
|
|
348
257
|
} else {
|
|
349
258
|
console.log(chalk.yellow(` ⚠ Não foi possível verificar token Figma: ${err.message}`));
|
|
350
259
|
fail++;
|
|
@@ -360,8 +269,9 @@ export async function doctorCommand() {
|
|
|
360
269
|
console.log('');
|
|
361
270
|
|
|
362
271
|
if (fail > 0) {
|
|
363
|
-
console.log(chalk.dim(' Execute "open-spec-kit
|
|
272
|
+
console.log(chalk.dim(' Execute "open-spec-kit install" para corrigir a instalação global.'));
|
|
273
|
+
console.log(chalk.dim(' Execute "open-spec-kit init" para corrigir o projeto.\n'));
|
|
364
274
|
} else {
|
|
365
|
-
console.log(chalk.green(' Ambiente pronto! Execute /
|
|
275
|
+
console.log(chalk.green(' Ambiente pronto! Execute /osk-init para começar.\n'));
|
|
366
276
|
}
|
|
367
277
|
}
|