@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/src/commands/update.js
CHANGED
|
@@ -1,188 +1,158 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
|
-
import { readFile, writeFile, access, mkdir, readdir } from 'fs/promises';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import
|
|
3
|
+
import { readFile, writeFile, access, mkdir, readdir, cp } from 'fs/promises';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { GLOBAL_DIR, getGlobalPath } from '../utils/global-path.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const TEMPLATES_DIR = join(__dirname, '..', '..', 'templates');
|
|
6
10
|
|
|
7
11
|
export async function updateCommand() {
|
|
8
12
|
console.log(chalk.bold('\n open-spec-kit update\n'));
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
// Check global install exists
|
|
15
|
+
try {
|
|
16
|
+
await access(GLOBAL_DIR);
|
|
17
|
+
} catch {
|
|
18
|
+
console.log(chalk.red(` Instalação global não encontrada (${GLOBAL_DIR})`));
|
|
19
|
+
console.log(chalk.dim(' Execute "open-spec-kit install" primeiro.\n'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const spinner = ora('Atualizando skills e rules globais...').start();
|
|
12
24
|
|
|
13
25
|
try {
|
|
14
|
-
|
|
15
|
-
|
|
26
|
+
// Phase 1: Re-copy templates from npm package → global
|
|
27
|
+
spinner.text = 'Copiando templates atualizados...';
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
const agentsSource = join(TEMPLATES_DIR, 'agents');
|
|
30
|
+
for (const subdir of ['skills', 'rules', 'agents', 'scripts']) {
|
|
31
|
+
const src = join(agentsSource, subdir);
|
|
32
|
+
const dest = getGlobalPath(subdir);
|
|
33
|
+
try {
|
|
34
|
+
await access(src);
|
|
35
|
+
await cp(src, dest, { recursive: true, force: true });
|
|
36
|
+
} catch { /* subdir may not exist in templates */ }
|
|
20
37
|
}
|
|
21
38
|
|
|
22
|
-
spinner.succeed(
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
spinner.succeed('Skills e rules globais atualizados');
|
|
40
|
+
|
|
41
|
+
// Phase 2: Sync Claude → Copilot mirrors
|
|
42
|
+
const syncSpinner = ora('Sincronizando mirrors Copilot...').start();
|
|
43
|
+
let synced = 0;
|
|
44
|
+
|
|
45
|
+
// Sync rules with Copilot frontmatter
|
|
46
|
+
const syncPairs = [
|
|
47
|
+
{
|
|
48
|
+
source: getGlobalPath('rules', 'ownership.instructions.md'),
|
|
49
|
+
target: getGlobalPath('github', 'instructions', 'ownership.instructions.md'),
|
|
50
|
+
label: 'ownership rules',
|
|
51
|
+
transform: (content) => addCopilotFrontmatter(content, 'specs/**,docs/**,projects.yml')
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
source: getGlobalPath('rules', 'hub_structure.instructions.md'),
|
|
55
|
+
target: getGlobalPath('github', 'instructions', 'hub_structure.instructions.md'),
|
|
56
|
+
label: 'structure rules',
|
|
57
|
+
transform: (content) => addCopilotFrontmatter(content, 'specs/**,docs/**,projects.yml')
|
|
58
|
+
},
|
|
29
59
|
];
|
|
30
60
|
|
|
31
|
-
const
|
|
32
|
-
for (const f of sourceFiles) {
|
|
61
|
+
for (const { source, target, label, transform } of syncPairs) {
|
|
33
62
|
try {
|
|
34
|
-
await
|
|
35
|
-
|
|
36
|
-
|
|
63
|
+
const sourceContent = await readFile(source, 'utf-8');
|
|
64
|
+
const targetContent = transform(sourceContent);
|
|
65
|
+
|
|
66
|
+
let existingTarget = '';
|
|
67
|
+
try {
|
|
68
|
+
existingTarget = await readFile(target, 'utf-8');
|
|
69
|
+
} catch { /* target doesn't exist yet */ }
|
|
70
|
+
|
|
71
|
+
if (existingTarget !== targetContent) {
|
|
72
|
+
await mkdir(getGlobalPath('github', 'instructions'), { recursive: true });
|
|
73
|
+
await writeFile(target, targetContent);
|
|
74
|
+
synced++;
|
|
75
|
+
console.log(chalk.green(` ✓ ${label} sincronizado`));
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.log(chalk.yellow(` ⚠ Não foi possível sincronizar ${label}: ${err.message}`));
|
|
37
79
|
}
|
|
38
80
|
}
|
|
39
81
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
82
|
+
// Sync skills
|
|
83
|
+
try {
|
|
84
|
+
const skillsDir = getGlobalPath('skills');
|
|
85
|
+
const skillEntries = await readdir(skillsDir, { withFileTypes: true });
|
|
86
|
+
const skillDirs = skillEntries.filter(e => e.isDirectory()).map(e => e.name);
|
|
87
|
+
|
|
88
|
+
for (const skillName of skillDirs) {
|
|
89
|
+
const source = join(skillsDir, skillName, 'SKILL.md');
|
|
90
|
+
const target = getGlobalPath('github', 'skills', skillName, 'SKILL.md');
|
|
46
91
|
|
|
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
92
|
try {
|
|
69
|
-
const sourceContent = await readFile(
|
|
70
|
-
const targetContent = transform(sourceContent);
|
|
93
|
+
const sourceContent = await readFile(source, 'utf-8');
|
|
71
94
|
|
|
72
95
|
let existingTarget = '';
|
|
73
96
|
try {
|
|
74
|
-
existingTarget = await readFile(
|
|
97
|
+
existingTarget = await readFile(target, 'utf-8');
|
|
75
98
|
} catch { /* target doesn't exist yet */ }
|
|
76
99
|
|
|
77
|
-
if (existingTarget !==
|
|
78
|
-
|
|
79
|
-
await
|
|
80
|
-
await writeFile(join(cwd, target), targetContent);
|
|
100
|
+
if (existingTarget !== sourceContent) {
|
|
101
|
+
await mkdir(getGlobalPath('github', 'skills', skillName), { recursive: true });
|
|
102
|
+
await writeFile(target, sourceContent);
|
|
81
103
|
synced++;
|
|
82
|
-
console.log(chalk.green(` ✓ ${
|
|
104
|
+
console.log(chalk.green(` ✓ skill ${skillName} sincronizado`));
|
|
83
105
|
}
|
|
84
106
|
} catch (err) {
|
|
85
|
-
|
|
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
|
-
}
|
|
107
|
+
if (err.code !== 'ENOENT') {
|
|
108
|
+
console.log(chalk.yellow(` ⚠ Não foi possível sincronizar skill ${skillName}: ${err.message}`));
|
|
125
109
|
}
|
|
126
110
|
}
|
|
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 (err) {
|
|
134
|
-
if (err.code === 'ENOENT') {
|
|
135
|
-
skillsSpinner.warn('Diretório .agents/skills/ não encontrado — skills não sincronizados');
|
|
136
|
-
} else {
|
|
137
|
-
skillsSpinner.warn(`Erro ao sincronizar skills: ${err.message}`);
|
|
138
|
-
}
|
|
139
111
|
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (projects.agents.includes('claude')) {
|
|
144
|
-
try {
|
|
145
|
-
await access(join(cwd, '.mcp.json'));
|
|
146
|
-
console.log(chalk.green(' ✓ .mcp.json (Claude Code) encontrado'));
|
|
147
|
-
} catch {
|
|
148
|
-
console.log(chalk.yellow(' ✗ .mcp.json (Claude Code) ausente — crie manualmente'));
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err.code === 'ENOENT') {
|
|
114
|
+
console.log(chalk.yellow(' ⚠ Diretório skills/ global não encontrado'));
|
|
149
115
|
}
|
|
150
116
|
}
|
|
151
117
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
} catch {
|
|
157
|
-
console.log(chalk.yellow(' ✗ .vscode/mcp.json (Copilot) ausente — execute init'));
|
|
158
|
-
}
|
|
159
|
-
}
|
|
118
|
+
// Sync agent hub
|
|
119
|
+
try {
|
|
120
|
+
const claudeAgent = await readFile(getGlobalPath('agents', 'spec-hub.agent.md'), 'utf-8');
|
|
121
|
+
const copilotAgentPath = getGlobalPath('github', 'agents', 'spec-hub.agent.md');
|
|
160
122
|
|
|
161
|
-
|
|
162
|
-
|
|
123
|
+
// Copy Copilot version from templates (has extra frontmatter)
|
|
124
|
+
const copilotTemplate = join(TEMPLATES_DIR, 'github', 'agents', 'spec-hub.agent.md');
|
|
163
125
|
try {
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
console.log(chalk.green(' ✓ spec-hub agent sincronizado entre Claude e Copilot'));
|
|
174
|
-
} else {
|
|
175
|
-
console.log(chalk.yellow(' ⚠ spec-hub agent diverge entre Claude (.agents/) e Copilot (.github/)'));
|
|
176
|
-
console.log(chalk.dim(' Atualize manualmente: o conteúdo funcional diferente entre plataformas.'));
|
|
177
|
-
console.log(chalk.dim(' Claude: .agents/agents/spec-hub.agent.md'));
|
|
178
|
-
console.log(chalk.dim(' Copilot: .github/agents/spec-hub.agent.md'));
|
|
126
|
+
await access(copilotTemplate);
|
|
127
|
+
const copilotContent = await readFile(copilotTemplate, 'utf-8');
|
|
128
|
+
let existing = '';
|
|
129
|
+
try { existing = await readFile(copilotAgentPath, 'utf-8'); } catch { /* */ }
|
|
130
|
+
if (existing !== copilotContent) {
|
|
131
|
+
await mkdir(getGlobalPath('github', 'agents'), { recursive: true });
|
|
132
|
+
await writeFile(copilotAgentPath, copilotContent);
|
|
133
|
+
synced++;
|
|
134
|
+
console.log(chalk.green(' ✓ spec-hub agent (Copilot) sincronizado'));
|
|
179
135
|
}
|
|
180
|
-
} catch {
|
|
181
|
-
|
|
182
|
-
|
|
136
|
+
} catch { /* template not found */ }
|
|
137
|
+
} catch { /* ignore */ }
|
|
138
|
+
|
|
139
|
+
if (synced > 0) {
|
|
140
|
+
syncSpinner.succeed(`${synced} arquivo(s) Copilot atualizado(s)`);
|
|
141
|
+
} else {
|
|
142
|
+
syncSpinner.succeed('Tudo já sincronizado');
|
|
183
143
|
}
|
|
184
144
|
|
|
185
|
-
|
|
145
|
+
// Update version.json
|
|
146
|
+
try {
|
|
147
|
+
const pkgPath = join(__dirname, '..', '..', 'package.json');
|
|
148
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
|
|
149
|
+
await writeFile(getGlobalPath('version.json'), JSON.stringify({
|
|
150
|
+
version: pkg.version,
|
|
151
|
+
installedAt: new Date().toISOString(),
|
|
152
|
+
}, null, 2) + '\n');
|
|
153
|
+
} catch { /* ignore version write failure */ }
|
|
154
|
+
|
|
155
|
+
console.log(chalk.dim(`\n Diretório global: ${GLOBAL_DIR}\n`));
|
|
186
156
|
|
|
187
157
|
} catch (err) {
|
|
188
158
|
spinner.fail(`Erro: ${err.message}`);
|
|
@@ -191,24 +161,6 @@ export async function updateCommand() {
|
|
|
191
161
|
}
|
|
192
162
|
|
|
193
163
|
function addCopilotFrontmatter(content, applyTo) {
|
|
194
|
-
// Remove existing frontmatter if present
|
|
195
164
|
const withoutFrontmatter = content.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
196
165
|
return `---\napplyTo: "${applyTo}"\n---\n\n${withoutFrontmatter.trim()}\n`;
|
|
197
166
|
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Parse projects.yml using the yaml package (robust — handles all valid YAML).
|
|
201
|
-
*/
|
|
202
|
-
function parseProjectsYaml(content) {
|
|
203
|
-
try {
|
|
204
|
-
const data = yaml.parse(content);
|
|
205
|
-
const agents = data?.project?.agents;
|
|
206
|
-
return {
|
|
207
|
-
agents: Array.isArray(agents) ? agents : [],
|
|
208
|
-
preset: data?.project?.preset || 'standard',
|
|
209
|
-
name: data?.project?.name || '',
|
|
210
|
-
};
|
|
211
|
-
} catch {
|
|
212
|
-
return { agents: [], preset: 'standard', name: '' };
|
|
213
|
-
}
|
|
214
|
-
}
|
|
@@ -457,7 +457,7 @@ export function rule21_taskReposMatchProjects(tasks, repoNames) {
|
|
|
457
457
|
*/
|
|
458
458
|
export function rule22_auditReportClean(auditContent) {
|
|
459
459
|
if (auditContent === null) {
|
|
460
|
-
return result(22, true, 'WARNING', `audit-report.md not found (generated by /spec)`);
|
|
460
|
+
return result(22, true, 'WARNING', `audit-report.md not found (generated by /osk-spec)`);
|
|
461
461
|
}
|
|
462
462
|
const hasFail = auditContent.includes('❌');
|
|
463
463
|
return result(22, !hasFail, 'ERROR',
|
|
@@ -517,7 +517,7 @@ export function rule28_noUnresolvedMarkers(contents) {
|
|
|
517
517
|
return result(28, unique.length === 0, 'ERROR',
|
|
518
518
|
unique.length === 0
|
|
519
519
|
? `No unresolved markers found`
|
|
520
|
-
: `Unresolved markers found: ${unique.join(', ')} — resolve before /
|
|
520
|
+
: `Unresolved markers found: ${unique.join(', ')} — resolve before /osk-build`,
|
|
521
521
|
unique.length > 0 ? unique : undefined,
|
|
522
522
|
);
|
|
523
523
|
}
|
|
@@ -624,7 +624,7 @@ export function rule33_openDecisions(architectureContent) {
|
|
|
624
624
|
const blocking = [];
|
|
625
625
|
|
|
626
626
|
for (const line of lines) {
|
|
627
|
-
if (/\|\s*aberta\s*\|/i.test(line) && /\/spec|\/
|
|
627
|
+
if (/\|\s*aberta\s*\|/i.test(line) && /\/osk-spec|\/osk-build/i.test(line)) {
|
|
628
628
|
const cells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
629
629
|
blocking.push(cells[1] || line.trim());
|
|
630
630
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
export const GLOBAL_DIR_NAME = '.open-spec-kit';
|
|
6
|
+
export const GLOBAL_DIR = join(homedir(), GLOBAL_DIR_NAME);
|
|
7
|
+
|
|
8
|
+
export function getGlobalPath(...segments) {
|
|
9
|
+
return join(GLOBAL_DIR, ...segments);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getGlobalPathPosix(...segments) {
|
|
13
|
+
return getGlobalPath(...segments).replace(/\\/g, '/');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getGlobalEnvPath() {
|
|
17
|
+
return getGlobalPath('.env');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse a .env file into a key-value object.
|
|
22
|
+
* Handles comments, empty lines, and quoted values.
|
|
23
|
+
*/
|
|
24
|
+
export function parseEnvContent(content) {
|
|
25
|
+
const env = {};
|
|
26
|
+
for (const line of content.split('\n')) {
|
|
27
|
+
const trimmed = line.trim();
|
|
28
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
29
|
+
const eqIdx = trimmed.indexOf('=');
|
|
30
|
+
if (eqIdx === -1) continue;
|
|
31
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
32
|
+
let value = trimmed.slice(eqIdx + 1).trim();
|
|
33
|
+
// Strip surrounding quotes
|
|
34
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
35
|
+
value = value.slice(1, -1);
|
|
36
|
+
}
|
|
37
|
+
env[key] = value;
|
|
38
|
+
}
|
|
39
|
+
return env;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read and parse the global .env file.
|
|
44
|
+
*/
|
|
45
|
+
export async function readGlobalEnv() {
|
|
46
|
+
const content = await readFile(getGlobalEnvPath(), 'utf-8');
|
|
47
|
+
return parseEnvContent(content);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Parse JSON that may contain comments (JSONC, used by VS Code settings).
|
|
52
|
+
* Respects quoted strings so URLs like "https://..." are not corrupted.
|
|
53
|
+
*/
|
|
54
|
+
export function parseJsonc(text) {
|
|
55
|
+
let result = '';
|
|
56
|
+
let i = 0;
|
|
57
|
+
while (i < text.length) {
|
|
58
|
+
if (text[i] === '"') {
|
|
59
|
+
const start = i++;
|
|
60
|
+
while (i < text.length && text[i] !== '"') { if (text[i] === '\\') i++; i++; }
|
|
61
|
+
result += text.slice(start, ++i);
|
|
62
|
+
} else if (text[i] === '/' && text[i + 1] === '/') {
|
|
63
|
+
while (i < text.length && text[i] !== '\n') i++;
|
|
64
|
+
} else if (text[i] === '/' && text[i + 1] === '*') {
|
|
65
|
+
i += 2;
|
|
66
|
+
while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) i++;
|
|
67
|
+
i += 2;
|
|
68
|
+
} else {
|
|
69
|
+
result += text[i++];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return JSON.parse(result.replace(/,\s*([}\]])/g, '$1'));
|
|
73
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-agent
|
|
3
|
-
description: "Orquestra o ciclo de especificação: lê contexto do projeto, invoca as skills (/
|
|
3
|
+
description: "Orquestra o ciclo de especificação: lê contexto do projeto, invoca as skills (/osk-init, /osk-discover, /osk-spec, /osk-build) e coordena agents do Labs quando necessário."
|
|
4
4
|
user-invocable: true
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
---
|
|
@@ -25,10 +25,10 @@ Nunca gere specs sem ter lido o acima.
|
|
|
25
25
|
|
|
26
26
|
| Skill | Comando | O que faz |
|
|
27
27
|
|-------|---------|-----------|
|
|
28
|
-
| Setup | `/
|
|
29
|
-
| Discovery | `/
|
|
30
|
-
| Spec | `/spec` | Quebra PRD em specs técnicas (brief, cenários, contratos, tasks) |
|
|
31
|
-
| Dev | `/
|
|
28
|
+
| Setup | `/osk-init @space_key` ou `/osk-init @page_id` | Bootstrap do projeto a partir do Confluence |
|
|
29
|
+
| Discovery | `/osk-discover` | Análise de demanda, Q&A com PO, gera PRD |
|
|
30
|
+
| Spec | `/osk-spec` | Quebra PRD em specs técnicas (brief, cenários, contratos, tasks) |
|
|
31
|
+
| Dev | `/osk-build NNN` | Orquestra implementação (TDD, MR, security, docs vivas) |
|
|
32
32
|
|
|
33
33
|
As instruções completas de cada skill estão em `.agents/skills/*/SKILL.md`.
|
|
34
34
|
|
|
@@ -36,7 +36,7 @@ As instruções completas de cada skill estão em `.agents/skills/*/SKILL.md`.
|
|
|
36
36
|
|
|
37
37
|
| Responsabilidade | Quem faz |
|
|
38
38
|
|-----------------|----------|
|
|
39
|
-
| Specs (brief, scenarios, contracts, tasks, links) | **spec-agent** via `/spec` |
|
|
39
|
+
| Specs (brief, scenarios, contracts, tasks, links) | **spec-agent** via `/osk-spec` |
|
|
40
40
|
| ADRs (`docs/decisions/`) | **spec-agent** |
|
|
41
41
|
| Design Document (DD) no Confluence | **`design-doc`** (Labs agent) — fallback: spec-agent via MCP direto |
|
|
42
42
|
| Implementação de código | **`dotnet-engineer` / `nodejs-engineer` / `java-engineer`** (Labs) |
|
|
@@ -60,12 +60,12 @@ As instruções completas de cada skill estão em `.agents/skills/*/SKILL.md`.
|
|
|
60
60
|
|
|
61
61
|
```
|
|
62
62
|
Projeto (Space ou página raiz)
|
|
63
|
-
├── 🎯 Visão do Produto ← /
|
|
64
|
-
├── 📖 Glossário ← /
|
|
65
|
-
├── 📐 DD (Design Document) ← design-doc agent gera | /
|
|
63
|
+
├── 🎯 Visão do Produto ← /osk-init gera
|
|
64
|
+
├── 📖 Glossário ← /osk-init gera | /osk-discover e /osk-build atualizam
|
|
65
|
+
├── 📐 DD (Design Document) ← design-doc agent gera | /osk-build atualiza
|
|
66
66
|
├── Demandas/ ← PO joga docs aqui
|
|
67
|
-
├── 🚀 Features/ ← /
|
|
68
|
-
├── 🏛️ Domínio/ ← /
|
|
67
|
+
├── 🚀 Features/ ← /osk-init cria | /osk-discover escreve dúvidas + PRD
|
|
68
|
+
├── 🏛️ Domínio/ ← /osk-init gera | /osk-build atualiza
|
|
69
69
|
│ ├── 📏 Regras
|
|
70
70
|
│ ├── 🔀 Fluxos
|
|
71
71
|
│ ├── 📊 Tabelas de Referência
|
|
@@ -82,8 +82,8 @@ specs/NNN-nome/
|
|
|
82
82
|
scenarios.md ← Given/When/Then (viram testes)
|
|
83
83
|
tasks.md ← tarefas por repo
|
|
84
84
|
links.md ← PRs + link da feature page
|
|
85
|
-
audit-report.md ← resultado do self-review do /spec
|
|
86
|
-
conformance-report.json ← validação pós-implementação do /
|
|
85
|
+
audit-report.md ← resultado do self-review do /osk-spec
|
|
86
|
+
conformance-report.json ← validação pós-implementação do /osk-build
|
|
87
87
|
conformance-history.log ← histórico de validações (append por run)
|
|
88
88
|
openapi.yaml ← stub OpenAPI 3.0 (opcional, se REST endpoints)
|
|
89
89
|
```
|
|
@@ -24,7 +24,7 @@ applyTo: '**/*.md'
|
|
|
24
24
|
|
|
25
25
|
1. Created: spec directory with brief + scenarios + contracts + tasks
|
|
26
26
|
2. In progress: tasks are checked off, links.md tracks PRs
|
|
27
|
-
3. Post-merge sync: if implementation diverged intentionally from spec, /
|
|
27
|
+
3. Post-merge sync: if implementation diverged intentionally from spec, /osk-build updates contracts.md and scenarios.md to match reality (Phase D.1.5)
|
|
28
28
|
4. Done: all tasks checked, spec directory stays (it IS the history)
|
|
29
29
|
|
|
30
30
|
Specs are NOT archived or moved. The spec directory with checked tasks IS the record.
|