@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.
- 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 +210 -0
- package/src/commands/validate.js +615 -0
- package/src/parsers/markdown-sections.js +271 -0
- package/src/schemas/projects.schema.js +111 -0
- package/src/schemas/spec.schema.js +760 -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 +459 -0
- package/templates/agents/skills/specifying-features/SKILL.md +379 -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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { readFile, writeFile, access, mkdir, readdir } from 'fs/promises';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import yaml from 'yaml';
|
|
6
|
+
|
|
7
|
+
export async function updateCommand() {
|
|
8
|
+
console.log(chalk.bold('\n open-spec-kit update\n'));
|
|
9
|
+
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const spinner = ora('Lendo projects.yml...').start();
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const projectsRaw = await readFile(join(cwd, 'projects.yml'), 'utf-8');
|
|
15
|
+
const projects = parseProjectsYaml(projectsRaw);
|
|
16
|
+
|
|
17
|
+
if (projects.agents.length === 0) {
|
|
18
|
+
spinner.fail('projects.yml não contém campo "agents". Execute "open-spec-kit init" primeiro.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
spinner.succeed(`Agents configurados: ${projects.agents.join(', ')}`);
|
|
23
|
+
|
|
24
|
+
// Check source of truth files exist
|
|
25
|
+
const sourceFiles = [
|
|
26
|
+
'.agents/agents/spec-hub.agent.md',
|
|
27
|
+
'.agents/rules/ownership.instructions.md',
|
|
28
|
+
'.agents/rules/hub_structure.instructions.md',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const missingSource = [];
|
|
32
|
+
for (const f of sourceFiles) {
|
|
33
|
+
try {
|
|
34
|
+
await access(join(cwd, f));
|
|
35
|
+
} catch {
|
|
36
|
+
missingSource.push(f);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (missingSource.length > 0) {
|
|
41
|
+
console.log(chalk.yellow('\n Arquivos fonte ausentes (source of truth):'));
|
|
42
|
+
missingSource.forEach(f => console.log(chalk.dim(` - ${f}`)));
|
|
43
|
+
console.log(chalk.dim(' Execute "open-spec-kit init" para gerar.'));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Sync Copilot files if configured
|
|
48
|
+
if (projects.agents.includes('copilot')) {
|
|
49
|
+
const syncSpinner = ora('Sincronizando arquivos Copilot...').start();
|
|
50
|
+
|
|
51
|
+
const syncPairs = [
|
|
52
|
+
{
|
|
53
|
+
source: '.agents/rules/ownership.instructions.md',
|
|
54
|
+
target: '.github/instructions/ownership.instructions.md',
|
|
55
|
+
label: 'ownership rules',
|
|
56
|
+
transform: (content) => addCopilotFrontmatter(content, 'specs/**,docs/**,projects.yml,.agents/**')
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
source: '.agents/rules/hub_structure.instructions.md',
|
|
60
|
+
target: '.github/instructions/hub_structure.instructions.md',
|
|
61
|
+
label: 'structure rules',
|
|
62
|
+
transform: (content) => addCopilotFrontmatter(content, 'specs/**,docs/**,projects.yml')
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
let synced = 0;
|
|
67
|
+
for (const { source, target, label, transform } of syncPairs) {
|
|
68
|
+
try {
|
|
69
|
+
const sourceContent = await readFile(join(cwd, source), 'utf-8');
|
|
70
|
+
const targetContent = transform(sourceContent);
|
|
71
|
+
|
|
72
|
+
let existingTarget = '';
|
|
73
|
+
try {
|
|
74
|
+
existingTarget = await readFile(join(cwd, target), 'utf-8');
|
|
75
|
+
} catch { /* target doesn't exist yet */ }
|
|
76
|
+
|
|
77
|
+
if (existingTarget !== targetContent) {
|
|
78
|
+
syncSpinner.text = `Atualizando ${label}...`;
|
|
79
|
+
await mkdir(join(cwd, '.github/instructions'), { recursive: true });
|
|
80
|
+
await writeFile(join(cwd, target), targetContent);
|
|
81
|
+
synced++;
|
|
82
|
+
console.log(chalk.green(` ✓ ${label} sincronizado`));
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.log(chalk.yellow(` ⚠ Não foi possível sincronizar ${label}: ${err.message}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (synced > 0) {
|
|
90
|
+
syncSpinner.succeed(`${synced} arquivo(s) Copilot atualizado(s)`);
|
|
91
|
+
} else {
|
|
92
|
+
syncSpinner.succeed('Arquivos Copilot já estão sincronizados');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Sync skills: .agents/skills/*/SKILL.md → .github/skills/*/SKILL.md
|
|
96
|
+
const skillsSpinner = ora('Sincronizando skills...').start();
|
|
97
|
+
let skillsSynced = 0;
|
|
98
|
+
try {
|
|
99
|
+
const skillsDir = join(cwd, '.agents', 'skills');
|
|
100
|
+
const skillEntries = await readdir(skillsDir, { withFileTypes: true });
|
|
101
|
+
const skillDirs = skillEntries.filter(e => e.isDirectory()).map(e => e.name);
|
|
102
|
+
|
|
103
|
+
for (const skillName of skillDirs) {
|
|
104
|
+
const source = join(skillsDir, skillName, 'SKILL.md');
|
|
105
|
+
const target = join(cwd, '.github', 'skills', skillName, 'SKILL.md');
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const sourceContent = await readFile(source, 'utf-8');
|
|
109
|
+
|
|
110
|
+
let existingTarget = '';
|
|
111
|
+
try {
|
|
112
|
+
existingTarget = await readFile(target, 'utf-8');
|
|
113
|
+
} catch { /* target doesn't exist yet */ }
|
|
114
|
+
|
|
115
|
+
if (existingTarget !== sourceContent) {
|
|
116
|
+
await mkdir(join(cwd, '.github', 'skills', skillName), { recursive: true });
|
|
117
|
+
await writeFile(target, sourceContent);
|
|
118
|
+
skillsSynced++;
|
|
119
|
+
console.log(chalk.green(` ✓ skill ${skillName} sincronizado`));
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
if (err.code !== 'ENOENT') {
|
|
123
|
+
console.log(chalk.yellow(` ⚠ Não foi possível sincronizar skill ${skillName}: ${err.message}`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (skillsSynced > 0) {
|
|
129
|
+
skillsSpinner.succeed(`${skillsSynced} skill(s) Copilot atualizado(s)`);
|
|
130
|
+
} else {
|
|
131
|
+
skillsSpinner.succeed('Skills Copilot já estão sincronizados');
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
skillsSpinner.warn('Diretório .agents/skills/ não encontrado — skills não sincronizados');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check MCP configs
|
|
139
|
+
if (projects.agents.includes('claude')) {
|
|
140
|
+
try {
|
|
141
|
+
await access(join(cwd, '.mcp.json'));
|
|
142
|
+
console.log(chalk.green(' ✓ .mcp.json (Claude Code) encontrado'));
|
|
143
|
+
} catch {
|
|
144
|
+
console.log(chalk.yellow(' ✗ .mcp.json (Claude Code) ausente — crie manualmente'));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (projects.agents.includes('copilot')) {
|
|
149
|
+
try {
|
|
150
|
+
await access(join(cwd, '.vscode/mcp.json'));
|
|
151
|
+
console.log(chalk.green(' ✓ .vscode/mcp.json (Copilot) encontrado'));
|
|
152
|
+
} catch {
|
|
153
|
+
console.log(chalk.yellow(' ✗ .vscode/mcp.json (Copilot) ausente — execute init'));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check for spec-hub agent divergence between Claude and Copilot
|
|
158
|
+
if (projects.agents.includes('claude') && projects.agents.includes('copilot')) {
|
|
159
|
+
try {
|
|
160
|
+
const claudeAgentRaw = await readFile(join(cwd, '.agents/agents/spec-hub.agent.md'), 'utf-8');
|
|
161
|
+
const copilotAgentRaw = await readFile(join(cwd, '.github/agents/spec-hub.agent.md'), 'utf-8');
|
|
162
|
+
|
|
163
|
+
// Strip frontmatter (--- ... ---) from both to compare functional content
|
|
164
|
+
const stripFrontmatter = (content) => content.replace(/^---\n[\s\S]*?\n---\n+/, '').trim();
|
|
165
|
+
const claudeBody = stripFrontmatter(claudeAgentRaw);
|
|
166
|
+
const copilotBody = stripFrontmatter(copilotAgentRaw);
|
|
167
|
+
|
|
168
|
+
if (claudeBody === copilotBody) {
|
|
169
|
+
console.log(chalk.green(' ✓ spec-hub agent sincronizado entre Claude e Copilot'));
|
|
170
|
+
} else {
|
|
171
|
+
console.log(chalk.yellow(' ⚠ spec-hub agent diverge entre Claude (.agents/) e Copilot (.github/)'));
|
|
172
|
+
console.log(chalk.dim(' Atualize manualmente: o conteúdo funcional diferente entre plataformas.'));
|
|
173
|
+
console.log(chalk.dim(' Claude: .agents/agents/spec-hub.agent.md'));
|
|
174
|
+
console.log(chalk.dim(' Copilot: .github/agents/spec-hub.agent.md'));
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
console.log(chalk.yellow(' ⚠ Não foi possível verificar divergência dos agent files'));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log('');
|
|
182
|
+
|
|
183
|
+
} catch (err) {
|
|
184
|
+
spinner.fail(`Erro: ${err.message}`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function addCopilotFrontmatter(content, applyTo) {
|
|
190
|
+
// Remove existing frontmatter if present
|
|
191
|
+
const withoutFrontmatter = content.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
192
|
+
return `---\napplyTo: "${applyTo}"\n---\n\n${withoutFrontmatter.trim()}\n`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Parse projects.yml using the yaml package (robust — handles all valid YAML).
|
|
197
|
+
*/
|
|
198
|
+
function parseProjectsYaml(content) {
|
|
199
|
+
try {
|
|
200
|
+
const data = yaml.parse(content);
|
|
201
|
+
const agents = data?.project?.agents;
|
|
202
|
+
return {
|
|
203
|
+
agents: Array.isArray(agents) ? agents : [],
|
|
204
|
+
preset: data?.project?.preset || 'standard',
|
|
205
|
+
name: data?.project?.name || '',
|
|
206
|
+
};
|
|
207
|
+
} catch {
|
|
208
|
+
return { agents: [], preset: 'standard', name: '' };
|
|
209
|
+
}
|
|
210
|
+
}
|