kld-sdd 2.4.7
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 +287 -0
- package/bin/kld-sdd-init.js +24 -0
- package/index.js +13 -0
- package/lib/init.js +906 -0
- package/package.json +36 -0
- package/skywalk-sdd/index.js +587 -0
- package/templates/openspec/design.md +290 -0
- package/templates/openspec/overview.md +143 -0
- package/templates/openspec/proposal.md +108 -0
- package/templates/openspec/spec.md +185 -0
- package/templates/openspec/tasks.md +287 -0
- package/templates/opsx-commands/apply.md +420 -0
- package/templates/opsx-commands/archive.md +85 -0
- package/templates/opsx-commands/check.md +344 -0
- package/templates/opsx-commands/design.md +560 -0
- package/templates/opsx-commands/explore.md +89 -0
- package/templates/opsx-commands/propose.md +399 -0
- package/templates/opsx-commands/spec.md +516 -0
- package/templates/opsx-commands/task.md +423 -0
- package/templates/opsx-commands/test.md +207 -0
- package/templates/skills/opsx-apply/SKILL.md +167 -0
- package/templates/skills/opsx-archive/SKILL.md +97 -0
- package/templates/skills/opsx-check/SKILL.md +147 -0
- package/templates/skills/opsx-design/SKILL.md +179 -0
- package/templates/skills/opsx-explore/SKILL.md +99 -0
- package/templates/skills/opsx-propose/SKILL.md +258 -0
- package/templates/skills/opsx-spec/SKILL.md +190 -0
- package/templates/skills/opsx-task/SKILL.md +211 -0
- package/templates/skills/opsx-test/SKILL.md +138 -0
package/lib/init.js
ADDED
|
@@ -0,0 +1,906 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KLD SDD 初始化核心逻辑
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* 1. 检测并自动安装 openspec
|
|
6
|
+
* 2. 执行 openspec init 生成基础结构
|
|
7
|
+
* 3. 覆盖 opsx 命令(9个独立命令)
|
|
8
|
+
* 4. 部署 opsx skills
|
|
9
|
+
* 5. 复制标准文档模版到项目
|
|
10
|
+
* 6. 更新 .gitignore
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
const readline = require('readline');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
|
|
19
|
+
const rl = readline.createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stdout
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// 工具类型配置
|
|
25
|
+
const TOOL_CONFIGS = {
|
|
26
|
+
cursor: {
|
|
27
|
+
name: 'Cursor',
|
|
28
|
+
configDir: '.cursor',
|
|
29
|
+
commandsDir: '.cursor/commands',
|
|
30
|
+
opsxDir: '.cursor/commands/opsx',
|
|
31
|
+
skillsDir: '.cursor/skills'
|
|
32
|
+
},
|
|
33
|
+
claude: {
|
|
34
|
+
name: 'Claude Code',
|
|
35
|
+
configDir: '.claude',
|
|
36
|
+
commandsDir: '.claude/commands',
|
|
37
|
+
opsxDir: '.claude/commands/opsx',
|
|
38
|
+
skillsDir: '.claude/skills'
|
|
39
|
+
},
|
|
40
|
+
codebuddy: {
|
|
41
|
+
name: 'CodeBuddy',
|
|
42
|
+
configDir: '.codebuddy',
|
|
43
|
+
commandsDir: '.codebuddy/commands',
|
|
44
|
+
opsxDir: '.codebuddy/commands/opsx',
|
|
45
|
+
skillsDir: '.codebuddy/skills'
|
|
46
|
+
},
|
|
47
|
+
qoder: {
|
|
48
|
+
name: 'Qoder',
|
|
49
|
+
configDir: '.qoder',
|
|
50
|
+
commandsDir: '.qoder/commands',
|
|
51
|
+
opsxDir: '.qoder/commands/opsx',
|
|
52
|
+
skillsDir: '.qoder/skills'
|
|
53
|
+
},
|
|
54
|
+
opencode: {
|
|
55
|
+
name: 'OpenCode',
|
|
56
|
+
configDir: '.opencode',
|
|
57
|
+
commandsDir: '.opencode/commands',
|
|
58
|
+
opsxDir: '.opencode/commands/opsx',
|
|
59
|
+
skillsDir: '.opencode/skills',
|
|
60
|
+
// opencode 原生命令使用 command/(单数),需要额外清理
|
|
61
|
+
nativeCommandDir: '.opencode/command'
|
|
62
|
+
},
|
|
63
|
+
kunlunzhima: {
|
|
64
|
+
name: 'KunlunZhima',
|
|
65
|
+
configDir: '.kunlunzhima',
|
|
66
|
+
commandsDir: '.kunlunzhima/commands',
|
|
67
|
+
opsxDir: '.kunlunzhima/commands/opsx',
|
|
68
|
+
skillsDir: '.kunlunzhima/skills'
|
|
69
|
+
},
|
|
70
|
+
workbuddy: {
|
|
71
|
+
name: 'WorkBuddy',
|
|
72
|
+
configDir: '.workbuddy',
|
|
73
|
+
commandsDir: '.workbuddy/commands',
|
|
74
|
+
opsxDir: '.workbuddy/commands/opsx',
|
|
75
|
+
skillsDir: '.workbuddy/skills'
|
|
76
|
+
},
|
|
77
|
+
codex: {
|
|
78
|
+
name: 'Codex',
|
|
79
|
+
configDir: '.agents',
|
|
80
|
+
commandsDir: '.agents/commands',
|
|
81
|
+
opsxDir: '.agents/commands/opsx',
|
|
82
|
+
skillsDir: '.agents/skills'
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 询问用户选择
|
|
88
|
+
*/
|
|
89
|
+
function ask(question) {
|
|
90
|
+
return new Promise(resolve => {
|
|
91
|
+
rl.question(question, answer => resolve(answer.trim()));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 从命令行参数读取指定编辑器
|
|
97
|
+
* 支持:--tool codex、--tool=codex、--tool cursor,codex、--tool all
|
|
98
|
+
*/
|
|
99
|
+
function parseSelectedTools(args) {
|
|
100
|
+
const toolArgIndex = args.findIndex(arg => arg === '--tool' || arg.startsWith('--tool='));
|
|
101
|
+
if (toolArgIndex === -1) return null;
|
|
102
|
+
|
|
103
|
+
const rawValue = args[toolArgIndex] === '--tool'
|
|
104
|
+
? args[toolArgIndex + 1]
|
|
105
|
+
: args[toolArgIndex].slice('--tool='.length);
|
|
106
|
+
|
|
107
|
+
if (!rawValue) {
|
|
108
|
+
throw new Error('缺少 --tool 参数值,请指定编辑器名称');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const normalized = rawValue.trim().toLowerCase();
|
|
112
|
+
if (normalized === 'all' || normalized === '全部') {
|
|
113
|
+
return Object.keys(TOOL_CONFIGS);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const selected = normalized.split(',').map(item => item.trim()).filter(Boolean);
|
|
117
|
+
const invalid = selected.filter(tool => !TOOL_CONFIGS[tool]);
|
|
118
|
+
if (invalid.length > 0) {
|
|
119
|
+
throw new Error(`不支持的编辑器: ${invalid.join(', ')}。可用值: ${Object.keys(TOOL_CONFIGS).join(', ')}, all`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return selected;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 让用户选择编辑器
|
|
127
|
+
* 无效输入时重新询问,不允许默认
|
|
128
|
+
*/
|
|
129
|
+
async function selectEditor() {
|
|
130
|
+
const mapping = {
|
|
131
|
+
'1': ['cursor'],
|
|
132
|
+
'2': ['claude'],
|
|
133
|
+
'3': ['codebuddy'],
|
|
134
|
+
'4': ['qoder'],
|
|
135
|
+
'5': ['opencode'],
|
|
136
|
+
'6': ['kunlunzhima'],
|
|
137
|
+
'7': ['workbuddy'],
|
|
138
|
+
'8': ['codex'],
|
|
139
|
+
'9': ['cursor', 'claude', 'codebuddy', 'qoder', 'opencode', 'kunlunzhima', 'workbuddy', 'codex']
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
while (true) {
|
|
143
|
+
console.log('\n请选择您使用的 AI 编辑器:');
|
|
144
|
+
console.log(' 1. Cursor');
|
|
145
|
+
console.log(' 2. Claude Code');
|
|
146
|
+
console.log(' 3. CodeBuddy');
|
|
147
|
+
console.log(' 4. Qoder');
|
|
148
|
+
console.log(' 5. OpenCode');
|
|
149
|
+
console.log(' 6. KunlunZhima');
|
|
150
|
+
console.log(' 7. WorkBuddy');
|
|
151
|
+
console.log(' 8. Codex');
|
|
152
|
+
console.log(' 9. 全部(为所有编辑器生成命令)');
|
|
153
|
+
|
|
154
|
+
const answer = await ask('请输入选项 (1-9): ');
|
|
155
|
+
const selected = mapping[answer];
|
|
156
|
+
|
|
157
|
+
if (!selected) {
|
|
158
|
+
console.log(`❌ 无效选项 "${answer}",请输入 1-9 之间的数字\n`);
|
|
159
|
+
continue; // 重新循环询问
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const names = selected.map(k => TOOL_CONFIGS[k].name).join(', ');
|
|
163
|
+
console.log(`✓ 已选择: ${names}\n`);
|
|
164
|
+
return selected;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 检查命令是否可用
|
|
170
|
+
*/
|
|
171
|
+
function commandExists(command) {
|
|
172
|
+
try {
|
|
173
|
+
if (os.platform() === 'win32') {
|
|
174
|
+
execSync(`where ${command}`, { stdio: 'ignore' });
|
|
175
|
+
} else {
|
|
176
|
+
execSync(`which ${command}`, { stdio: 'ignore' });
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 获取包安装路径
|
|
186
|
+
*/
|
|
187
|
+
function getPackagePath() {
|
|
188
|
+
// 尝试多种方式找到包的路径
|
|
189
|
+
try {
|
|
190
|
+
// 方式1:通过 require.resolve 找到包位置
|
|
191
|
+
const mainPath = require.resolve('../package.json');
|
|
192
|
+
return path.dirname(mainPath);
|
|
193
|
+
} catch {
|
|
194
|
+
// 方式2:使用当前文件位置
|
|
195
|
+
return path.resolve(__dirname, '..');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 获取模版路径
|
|
201
|
+
*/
|
|
202
|
+
function getTemplatePath() {
|
|
203
|
+
const pkgPath = getPackagePath();
|
|
204
|
+
return path.join(pkgPath, 'templates', 'openspec');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 自动安装 openspec
|
|
209
|
+
*/
|
|
210
|
+
async function installOpenspec() {
|
|
211
|
+
console.log('📦 openspec 未安装,正在自动安装...');
|
|
212
|
+
|
|
213
|
+
const npmCmd = os.platform() === 'win32' ? 'npm.cmd' : 'npm';
|
|
214
|
+
const openspecPkg = '@fission-ai/openspec@latest';
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
// 尝试全局安装
|
|
218
|
+
console.log(` 正在执行: npm install -g ${openspecPkg}`);
|
|
219
|
+
execSync(`${npmCmd} install -g ${openspecPkg}`, {
|
|
220
|
+
stdio: 'inherit',
|
|
221
|
+
timeout: 180000
|
|
222
|
+
});
|
|
223
|
+
console.log('✅ openspec 安装完成');
|
|
224
|
+
return true;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.log('❌ 全局安装失败,尝试本地安装...');
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
// 本地安装到当前项目
|
|
230
|
+
const cwd = process.cwd();
|
|
231
|
+
execSync(`${npmCmd} install ${openspecPkg}`, {
|
|
232
|
+
cwd,
|
|
233
|
+
stdio: 'inherit',
|
|
234
|
+
timeout: 180000
|
|
235
|
+
});
|
|
236
|
+
console.log('✅ openspec 本地安装完成');
|
|
237
|
+
return true;
|
|
238
|
+
} catch (localError) {
|
|
239
|
+
console.log(`❌ 安装失败,请手动安装: npm install -g ${openspecPkg}`);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 执行 openspec init
|
|
247
|
+
* @param {string[]} selectedTools - 用户选择的编辑器列表
|
|
248
|
+
*/
|
|
249
|
+
async function runOpenspecInit(selectedTools = []) {
|
|
250
|
+
console.log('🚀 正在运行 openspec init...');
|
|
251
|
+
|
|
252
|
+
// 检查 openspec 是否安装
|
|
253
|
+
if (!commandExists('openspec')) {
|
|
254
|
+
const shouldInstall = await ask('openspec 未安装,是否自动安装? (y/n): ');
|
|
255
|
+
if (shouldInstall === 'y' || shouldInstall === 'Y') {
|
|
256
|
+
const installed = await installOpenspec();
|
|
257
|
+
if (!installed) {
|
|
258
|
+
console.log('⚠️ 跳过 openspec 初始化,继续应用模版...');
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
console.log('⚠️ 跳过 openspec 安装,继续应用模版...');
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// openspec CLI 支持的工具列表(kunlunzhima 等自定义工具不在其中)
|
|
269
|
+
const openspecSupportedTools = ['cursor', 'claude', 'codebuddy', 'qoder', 'opencode'];
|
|
270
|
+
|
|
271
|
+
// 过滤出 openspec 支持的工具
|
|
272
|
+
const supportedTools = selectedTools.filter(t => openspecSupportedTools.includes(t));
|
|
273
|
+
const unsupportedTools = selectedTools.filter(t => !openspecSupportedTools.includes(t));
|
|
274
|
+
|
|
275
|
+
if (unsupportedTools.length > 0) {
|
|
276
|
+
const names = unsupportedTools.map(k => TOOL_CONFIGS[k]?.name || k).join(', ');
|
|
277
|
+
console.log(` ℹ️ ${names} 不在 openspec 支持列表中,将单独创建目录结构`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 构建工具参数:只传递 openspec 支持的工具
|
|
281
|
+
// 当没有支持的工具时,使用 --tools none 创建基础目录结构
|
|
282
|
+
const toolsArg = supportedTools.length > 0
|
|
283
|
+
? `--tools ${supportedTools.join(',')}`
|
|
284
|
+
: '--tools none';
|
|
285
|
+
|
|
286
|
+
// 执行 openspec init,传入支持的工具(或 none)
|
|
287
|
+
const cmd = `openspec init ${toolsArg} --force`.trim();
|
|
288
|
+
console.log(` 执行: ${cmd}`);
|
|
289
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
290
|
+
|
|
291
|
+
console.log('✅ openspec 初始化完成');
|
|
292
|
+
return true;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.log('⚠️ openspec init 执行失败,继续应用模版...');
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 递归复制目录
|
|
301
|
+
*/
|
|
302
|
+
function copyDir(source, target) {
|
|
303
|
+
if (!fs.existsSync(target)) {
|
|
304
|
+
fs.mkdirSync(target, { recursive: true });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const files = fs.readdirSync(source);
|
|
308
|
+
for (const file of files) {
|
|
309
|
+
const srcPath = path.join(source, file);
|
|
310
|
+
const tgtPath = path.join(target, file);
|
|
311
|
+
|
|
312
|
+
const stat = fs.statSync(srcPath);
|
|
313
|
+
if (stat.isDirectory()) {
|
|
314
|
+
copyDir(srcPath, tgtPath);
|
|
315
|
+
} else {
|
|
316
|
+
fs.copyFileSync(srcPath, tgtPath);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 清理原生 openspec 命令(opsx-*.md),避免与 opsx:* 命令混淆
|
|
323
|
+
* 同时清理 openspec 遗留的 JSON 文件
|
|
324
|
+
*
|
|
325
|
+
* openspec 对不同工具生成的目录结构:
|
|
326
|
+
* - Cursor: commands/opsx-*.md(根目录,连字符)
|
|
327
|
+
* - Claude/CodeBuddy/Qoder: commands/opsx/*.md(opsx 子目录)
|
|
328
|
+
* - OpenCode: command/opsx-*.md(command 单数目录)
|
|
329
|
+
*
|
|
330
|
+
* @param {string[]} selectedTools - 用户选择的编辑器列表
|
|
331
|
+
*/
|
|
332
|
+
function cleanupNativeOpenspecCommands(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
333
|
+
console.log('🗑 正在清理原生 openspec 命令(避免与 opsx:* 命令混淆)...');
|
|
334
|
+
|
|
335
|
+
const cwd = process.cwd();
|
|
336
|
+
// 原生 openspec 根目录命令文件(使用连字符,与 opsx:* 冒号命令混淆)
|
|
337
|
+
const nativeCmds = ['opsx-propose.md', 'opsx-apply.md', 'opsx-archive.md', 'opsx-explore.md'];
|
|
338
|
+
|
|
339
|
+
for (const toolKey of selectedTools) {
|
|
340
|
+
const config = TOOL_CONFIGS[toolKey];
|
|
341
|
+
if (!config) continue;
|
|
342
|
+
|
|
343
|
+
const configDir = path.join(cwd, config.configDir);
|
|
344
|
+
if (!fs.existsSync(configDir)) continue;
|
|
345
|
+
|
|
346
|
+
// 处理 commands/ 目录(复数,标准目录)
|
|
347
|
+
const commandsDir = path.join(configDir, 'commands');
|
|
348
|
+
if (fs.existsSync(commandsDir)) {
|
|
349
|
+
// 删除 JSON 文件(openspec 安装的遗留)
|
|
350
|
+
const allFiles = fs.readdirSync(commandsDir);
|
|
351
|
+
let deletedJson = 0;
|
|
352
|
+
for (const file of allFiles) {
|
|
353
|
+
const filePath = path.join(commandsDir, file);
|
|
354
|
+
if (file.endsWith('.json') && fs.statSync(filePath).isFile()) {
|
|
355
|
+
fs.unlinkSync(filePath);
|
|
356
|
+
deletedJson++;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (deletedJson > 0) {
|
|
360
|
+
console.log(` 🗑 ${config.name}: 删除 ${deletedJson} 个 JSON 命令文件`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 删除 commands/ 目录下的原生 opsx-* 命令(Cursor 和其他工具都可能有)
|
|
364
|
+
let deletedCmds = 0;
|
|
365
|
+
for (const cmd of nativeCmds) {
|
|
366
|
+
const filePath = path.join(commandsDir, cmd);
|
|
367
|
+
if (fs.existsSync(filePath)) {
|
|
368
|
+
fs.unlinkSync(filePath);
|
|
369
|
+
deletedCmds++;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (deletedCmds > 0) {
|
|
373
|
+
console.log(` ✓ ${config.name}: 删除 ${deletedCmds} 个原生 opsx-* 命令(commands/)`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 删除 commands/opsx/ 子目录下的原生命令(Claude/CodeBuddy/Qoder)
|
|
377
|
+
const opsxDir = path.join(commandsDir, 'opsx');
|
|
378
|
+
if (fs.existsSync(opsxDir)) {
|
|
379
|
+
const opsxFiles = fs.readdirSync(opsxDir);
|
|
380
|
+
let deletedOpsxCmds = 0;
|
|
381
|
+
// 只删除原生的 4 个命令,保留其他可能存在的文件
|
|
382
|
+
const nativeOpsxCmds = ['propose.md', 'apply.md', 'archive.md', 'explore.md'];
|
|
383
|
+
for (const file of nativeOpsxCmds) {
|
|
384
|
+
const filePath = path.join(opsxDir, file);
|
|
385
|
+
if (fs.existsSync(filePath)) {
|
|
386
|
+
// 检查是否是原生命令(不含 YAML frontmatter 中的 SDD 标记)
|
|
387
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
388
|
+
if (!content.includes('# SDD') && !content.includes('OPSX:')) {
|
|
389
|
+
fs.unlinkSync(filePath);
|
|
390
|
+
deletedOpsxCmds++;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (deletedOpsxCmds > 0) {
|
|
395
|
+
console.log(` ✓ ${config.name}: 删除 ${deletedOpsxCmds} 个原生命令(commands/opsx/)`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// 处理 command/ 目录(单数,opencode 特有)
|
|
401
|
+
if (config.nativeCommandDir) {
|
|
402
|
+
const nativeDir = path.join(cwd, config.nativeCommandDir);
|
|
403
|
+
if (fs.existsSync(nativeDir)) {
|
|
404
|
+
// 删除整个 command/ 目录(全是原生命令)
|
|
405
|
+
fs.rmSync(nativeDir, { recursive: true, force: true });
|
|
406
|
+
console.log(` 🗑 ${config.name}: 删除原生 command/ 目录`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
console.log('✅ 原生 openspec 命令清理完成');
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* 覆盖 opsx 命令(9 个 SDD 工作流命令)
|
|
417
|
+
* 所有编辑器统一使用 templates/opsx-commands/ 下的 SDD 命令模板
|
|
418
|
+
* @param {string[]} selectedTools - 用户选择的编辑器列表
|
|
419
|
+
*/
|
|
420
|
+
function overrideOpsxCommands(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
421
|
+
console.log('🔧 正在覆盖 opsx 命令...');
|
|
422
|
+
|
|
423
|
+
const pkgPath = getPackagePath();
|
|
424
|
+
const cwd = process.cwd();
|
|
425
|
+
|
|
426
|
+
// 统一使用 opsx-commands 模板(9 个 SDD 命令,log 为内部自动触发)
|
|
427
|
+
const sddCommands = ['propose.md', 'spec.md', 'design.md', 'task.md', 'check.md', 'apply.md', 'test.md', 'archive.md', 'explore.md'];
|
|
428
|
+
const templatePath = path.join(pkgPath, 'templates', 'opsx-commands');
|
|
429
|
+
|
|
430
|
+
if (!fs.existsSync(templatePath)) {
|
|
431
|
+
console.log(`⚠️ SDD 命令模板目录不存在: ${templatePath}`);
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// 为用户选择的每个编辑器部署命令
|
|
436
|
+
for (const toolKey of selectedTools) {
|
|
437
|
+
const config = TOOL_CONFIGS[toolKey];
|
|
438
|
+
if (!config) continue;
|
|
439
|
+
|
|
440
|
+
const configDir = path.join(cwd, config.configDir);
|
|
441
|
+
const targetOpsxDir = path.join(cwd, config.opsxDir);
|
|
442
|
+
|
|
443
|
+
// 自动创建配置目录
|
|
444
|
+
if (!fs.existsSync(configDir)) {
|
|
445
|
+
console.log(` 自动创建 ${config.name} 配置目录...`);
|
|
446
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// 确保 opsx 命令目录存在
|
|
450
|
+
if (!fs.existsSync(targetOpsxDir)) {
|
|
451
|
+
fs.mkdirSync(targetOpsxDir, { recursive: true });
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
console.log(` 覆盖 ${config.name} 的 opsx 命令(9 个 SDD 命令)...`);
|
|
455
|
+
|
|
456
|
+
// 复制所有 SDD 命令到 opsx 目录
|
|
457
|
+
for (const cmd of sddCommands) {
|
|
458
|
+
const sourceFile = path.join(templatePath, cmd);
|
|
459
|
+
const targetFile = path.join(targetOpsxDir, cmd);
|
|
460
|
+
|
|
461
|
+
if (fs.existsSync(sourceFile)) {
|
|
462
|
+
fs.copyFileSync(sourceFile, targetFile);
|
|
463
|
+
console.log(` ✓ ${cmd}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
console.log('✅ opsx 命令覆盖完成');
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* 部署 SDD opsx skills(9 个技能文件)
|
|
474
|
+
* 从 templates/skills/ 目录复制到各编辑器的 skills 目录
|
|
475
|
+
* @param {string[]} selectedTools - 用户选择的编辑器列表
|
|
476
|
+
*/
|
|
477
|
+
function deployOpsxSkills(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
478
|
+
console.log('🎯 正在部署 opsx skills...');
|
|
479
|
+
|
|
480
|
+
const pkgPath = getPackagePath();
|
|
481
|
+
const cwd = process.cwd();
|
|
482
|
+
|
|
483
|
+
const skillsTemplatePath = path.join(pkgPath, 'templates', 'skills');
|
|
484
|
+
|
|
485
|
+
if (!fs.existsSync(skillsTemplatePath)) {
|
|
486
|
+
console.log(`⚠️ SDD skills 模板目录不存在: ${skillsTemplatePath}`);
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 获取所有 skill 目录
|
|
491
|
+
const skillDirs = fs.readdirSync(skillsTemplatePath).filter(d => {
|
|
492
|
+
return fs.statSync(path.join(skillsTemplatePath, d)).isDirectory();
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
if (skillDirs.length === 0) {
|
|
496
|
+
console.log('⚠️ 没有找到任何 skill 模板');
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
for (const toolKey of selectedTools) {
|
|
501
|
+
const config = TOOL_CONFIGS[toolKey];
|
|
502
|
+
if (!config || !config.skillsDir) continue;
|
|
503
|
+
|
|
504
|
+
const targetSkillsDir = path.join(cwd, config.skillsDir);
|
|
505
|
+
|
|
506
|
+
// 确保 skills 目录存在
|
|
507
|
+
if (!fs.existsSync(targetSkillsDir)) {
|
|
508
|
+
fs.mkdirSync(targetSkillsDir, { recursive: true });
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
console.log(` 部署 ${config.name} 的 opsx skills(${skillDirs.length} 个)...`);
|
|
512
|
+
|
|
513
|
+
for (const skillDir of skillDirs) {
|
|
514
|
+
const sourceDir = path.join(skillsTemplatePath, skillDir);
|
|
515
|
+
const targetDir = path.join(targetSkillsDir, skillDir);
|
|
516
|
+
|
|
517
|
+
if (!fs.existsSync(targetDir)) {
|
|
518
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const skillFile = path.join(sourceDir, 'SKILL.md');
|
|
522
|
+
if (fs.existsSync(skillFile)) {
|
|
523
|
+
fs.copyFileSync(skillFile, path.join(targetDir, 'SKILL.md'));
|
|
524
|
+
console.log(` ✓ ${skillDir}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
console.log('✅ opsx skills 部署完成');
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* 清理原生 openspec-* skills(防止残留)
|
|
535
|
+
* 仅负责删除 openspec init 产生的原生 skills。
|
|
536
|
+
*
|
|
537
|
+
* @param {string[]} selectedTools - 用户选择的编辑器列表
|
|
538
|
+
*/
|
|
539
|
+
function cleanupNativeOpenspecSkills(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
540
|
+
console.log('🧹 正在清理原生 openspec-* skills...');
|
|
541
|
+
|
|
542
|
+
const cwd = process.cwd();
|
|
543
|
+
|
|
544
|
+
// 原生 openspec-* skills(需要删除)
|
|
545
|
+
const nativeSkillsToRemove = [
|
|
546
|
+
'openspec-propose',
|
|
547
|
+
'openspec-apply-change',
|
|
548
|
+
'openspec-archive-change',
|
|
549
|
+
'openspec-explore'
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
for (const toolKey of selectedTools) {
|
|
553
|
+
const config = TOOL_CONFIGS[toolKey];
|
|
554
|
+
if (!config || !config.skillsDir) continue;
|
|
555
|
+
|
|
556
|
+
const skillsDir = path.join(cwd, config.skillsDir);
|
|
557
|
+
if (!fs.existsSync(skillsDir)) continue;
|
|
558
|
+
|
|
559
|
+
let removed = 0;
|
|
560
|
+
for (const nativeSkill of nativeSkillsToRemove) {
|
|
561
|
+
const nativeSkillDir = path.join(skillsDir, nativeSkill);
|
|
562
|
+
if (fs.existsSync(nativeSkillDir)) {
|
|
563
|
+
fs.rmSync(nativeSkillDir, { recursive: true, force: true });
|
|
564
|
+
removed++;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (removed > 0) {
|
|
568
|
+
console.log(` 🗑 ${config.name}: 删除 ${removed} 个原生 openspec-* skills`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
console.log('✅ 原生 skills 清理完成');
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* 复制标准文档模版到项目
|
|
578
|
+
*/
|
|
579
|
+
function copyTemplatesToProject() {
|
|
580
|
+
console.log('📄 正在复制标准文档模版到项目...');
|
|
581
|
+
|
|
582
|
+
const pkgPath = getPackagePath();
|
|
583
|
+
const templatePath = path.join(pkgPath, 'templates', 'openspec');
|
|
584
|
+
const cwd = process.cwd();
|
|
585
|
+
const targetDir = path.join(cwd, 'openspec-templates');
|
|
586
|
+
|
|
587
|
+
if (!fs.existsSync(templatePath)) {
|
|
588
|
+
console.log(`⚠️ 模版源目录不存在: ${templatePath}`);
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// 创建目标目录
|
|
593
|
+
if (!fs.existsSync(targetDir)) {
|
|
594
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// 复制模版文件
|
|
598
|
+
copyDir(templatePath, targetDir);
|
|
599
|
+
|
|
600
|
+
console.log(`✅ 标准文档模版已复制到: ${targetDir}`);
|
|
601
|
+
console.log(' 包含文件:');
|
|
602
|
+
const files = fs.readdirSync(targetDir);
|
|
603
|
+
files.forEach(file => {
|
|
604
|
+
console.log(` - ${file}`);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* 初始化全局 overview.md 到 openspec/specs/ 目录
|
|
612
|
+
* overview.md 是全局架构约束,所有 Capability 的设计都必须遵守
|
|
613
|
+
*/
|
|
614
|
+
function initGlobalOverview() {
|
|
615
|
+
console.log('🌐 正在初始化全局架构约束 (overview.md)...');
|
|
616
|
+
|
|
617
|
+
const pkgPath = getPackagePath();
|
|
618
|
+
const overviewSource = path.join(pkgPath, 'templates', 'openspec', 'overview.md');
|
|
619
|
+
const cwd = process.cwd();
|
|
620
|
+
const targetDir = path.join(cwd, 'openspec', 'specs');
|
|
621
|
+
const targetFile = path.join(targetDir, 'overview.md');
|
|
622
|
+
|
|
623
|
+
// 检查源文件是否存在
|
|
624
|
+
if (!fs.existsSync(overviewSource)) {
|
|
625
|
+
console.log(`⚠️ overview.md 源文件不存在: ${overviewSource}`);
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// 创建目标目录
|
|
630
|
+
if (!fs.existsSync(targetDir)) {
|
|
631
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// 检查目标文件是否已存在
|
|
635
|
+
if (fs.existsSync(targetFile)) {
|
|
636
|
+
// 读取现有文件,检查是否需要更新
|
|
637
|
+
const existingContent = fs.readFileSync(targetFile, 'utf-8');
|
|
638
|
+
const newContent = fs.readFileSync(overviewSource, 'utf-8');
|
|
639
|
+
|
|
640
|
+
if (existingContent === newContent) {
|
|
641
|
+
console.log(' ✓ overview.md 已是最新版本,跳过');
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// 备份现有文件
|
|
646
|
+
const backupFile = path.join(targetDir, 'overview.md.backup');
|
|
647
|
+
fs.copyFileSync(targetFile, backupFile);
|
|
648
|
+
console.log(` ℹ️ 已备份现有 overview.md 到 ${backupFile}`);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// 复制 overview.md
|
|
652
|
+
fs.copyFileSync(overviewSource, targetFile);
|
|
653
|
+
|
|
654
|
+
console.log(`✅ 全局架构约束已初始化: ${targetFile}`);
|
|
655
|
+
console.log(' ℹ️ 此文件定义了全局数据字典、接口规范、共享实体等约束');
|
|
656
|
+
console.log(' ℹ️ 执行 /opsx:design 或 /opsx:task 时会自动读取此文件');
|
|
657
|
+
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* 部署 SDD Telemetry 数据目录
|
|
663
|
+
* 创建本地数据目录用于存储事件和报告
|
|
664
|
+
*/
|
|
665
|
+
function deployTelemetryDataDir() {
|
|
666
|
+
console.log('📊 正在初始化 SDD Telemetry 数据目录...');
|
|
667
|
+
|
|
668
|
+
const pkgPath = getPackagePath();
|
|
669
|
+
const cwd = process.cwd();
|
|
670
|
+
const dataDir = path.join(cwd, 'skywalk-sdd');
|
|
671
|
+
|
|
672
|
+
if (!fs.existsSync(dataDir)) {
|
|
673
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// 创建本地数据目录(事件和报告存储)
|
|
677
|
+
const eventsDir = path.join(dataDir, 'events');
|
|
678
|
+
const reportsDir = path.join(dataDir, 'reports');
|
|
679
|
+
if (!fs.existsSync(eventsDir)) {
|
|
680
|
+
fs.mkdirSync(eventsDir, { recursive: true });
|
|
681
|
+
console.log(' ✓ 创建 skywalk-sdd/events/ 数据目录');
|
|
682
|
+
}
|
|
683
|
+
if (!fs.existsSync(reportsDir)) {
|
|
684
|
+
fs.mkdirSync(reportsDir, { recursive: true });
|
|
685
|
+
console.log(' ✓ 创建 skywalk-sdd/reports/ 报告目录');
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// 部署项目内 Telemetry CLI(不依赖 npm 发布)
|
|
689
|
+
const telemetrySrc = path.join(pkgPath, 'skywalk-sdd', 'index.js');
|
|
690
|
+
const telemetryDst = path.join(dataDir, 'log.js');
|
|
691
|
+
if (fs.existsSync(telemetrySrc)) {
|
|
692
|
+
fs.copyFileSync(telemetrySrc, telemetryDst);
|
|
693
|
+
console.log(' ✓ 部署 skywalk-sdd/log.js(本地 Telemetry CLI)');
|
|
694
|
+
} else {
|
|
695
|
+
console.log(` ⚠️ Telemetry CLI 源文件缺失: ${telemetrySrc}`);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
console.log(' ✓ 调用方式: node skywalk-sdd/log.js start|end|metrics');
|
|
699
|
+
console.log('✅ Telemetry 已就绪(数据存储在 skywalk-sdd/,无需配置 MCP)');
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* 检测当前项目已初始化的 AI 编辑器
|
|
705
|
+
*/
|
|
706
|
+
function detectTools() {
|
|
707
|
+
const detected = [];
|
|
708
|
+
const cwd = process.cwd();
|
|
709
|
+
for (const [key, config] of Object.entries(TOOL_CONFIGS)) {
|
|
710
|
+
if (fs.existsSync(path.join(cwd, config.configDir))) {
|
|
711
|
+
detected.push(key);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return detected;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* 创建 / 更新 .gitignore
|
|
719
|
+
*/
|
|
720
|
+
function updateGitignore() {
|
|
721
|
+
const cwd = process.cwd();
|
|
722
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
723
|
+
const requiredLines = [
|
|
724
|
+
'.cursor/commands/personal-*',
|
|
725
|
+
'.claude/commands/personal-*',
|
|
726
|
+
'.codebuddy/commands/personal-*',
|
|
727
|
+
'.qoder/commands/personal-*',
|
|
728
|
+
'.kunlunzhima/commands/personal-*',
|
|
729
|
+
'.workbuddy/commands/personal-*',
|
|
730
|
+
'.agents/commands/personal-*',
|
|
731
|
+
'skywalk-sdd/events/',
|
|
732
|
+
'skywalk-sdd/reports/'
|
|
733
|
+
];
|
|
734
|
+
|
|
735
|
+
const sddConfig = `
|
|
736
|
+
# KLD SDD AI 编辑器个人配置(请勿提交)
|
|
737
|
+
.cursor/commands/personal-*
|
|
738
|
+
.claude/commands/personal-*
|
|
739
|
+
.codebuddy/commands/personal-*
|
|
740
|
+
.qoder/commands/personal-*
|
|
741
|
+
.kunlunzhima/commands/personal-*
|
|
742
|
+
.workbuddy/commands/personal-*
|
|
743
|
+
.agents/commands/personal-*
|
|
744
|
+
|
|
745
|
+
# SDD Telemetry 数据(本地度量,可选提交)
|
|
746
|
+
skywalk-sdd/events/
|
|
747
|
+
skywalk-sdd/reports/
|
|
748
|
+
`;
|
|
749
|
+
|
|
750
|
+
if (fs.existsSync(gitignorePath)) {
|
|
751
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
752
|
+
if (!content.includes('KLD SDD')) {
|
|
753
|
+
fs.appendFileSync(gitignorePath, sddConfig);
|
|
754
|
+
console.log('✅ 已更新 .gitignore');
|
|
755
|
+
} else {
|
|
756
|
+
const missingLines = requiredLines.filter(line => !content.includes(line));
|
|
757
|
+
if (missingLines.length > 0) {
|
|
758
|
+
fs.appendFileSync(gitignorePath, `\n# KLD SDD 补充忽略规则\n${missingLines.join('\n')}\n`);
|
|
759
|
+
console.log('✅ 已补充 .gitignore');
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
} else {
|
|
763
|
+
fs.writeFileSync(gitignorePath, sddConfig.trim());
|
|
764
|
+
console.log('✅ 已创建 .gitignore');
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* 显示帮助信息
|
|
770
|
+
*/
|
|
771
|
+
function showHelp() {
|
|
772
|
+
console.log(`
|
|
773
|
+
KLD SDD 项目初始化工具
|
|
774
|
+
|
|
775
|
+
用法:
|
|
776
|
+
kld-sdd-init [选项]
|
|
777
|
+
npx kld-sdd [选项]
|
|
778
|
+
|
|
779
|
+
选项:
|
|
780
|
+
-h, --help 显示帮助信息
|
|
781
|
+
-v, --version 显示版本号
|
|
782
|
+
--skip-openspec 跳过 openspec init 步骤
|
|
783
|
+
--skip-template 跳过复制内置模版
|
|
784
|
+
--tool <name> 指定编辑器,可用值: cursor, claude, codebuddy, qoder, opencode, kunlunzhima, workbuddy, codex, all
|
|
785
|
+
|
|
786
|
+
示例:
|
|
787
|
+
kld-sdd-init # 完整初始化流程
|
|
788
|
+
kld-sdd-init --skip-openspec # 跳过 openspec,直接部署命令
|
|
789
|
+
kld-sdd-init --tool codex # 仅部署 Codex 配置
|
|
790
|
+
`);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* 主函数
|
|
795
|
+
*/
|
|
796
|
+
async function main() {
|
|
797
|
+
const args = process.argv.slice(2);
|
|
798
|
+
|
|
799
|
+
// 解析参数
|
|
800
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
801
|
+
showHelp();
|
|
802
|
+
rl.close();
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
if (args.includes('-v') || args.includes('--version')) {
|
|
807
|
+
const pkg = require('../package.json');
|
|
808
|
+
console.log(pkg.version);
|
|
809
|
+
rl.close();
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const skipOpenspec = args.includes('--skip-openspec');
|
|
814
|
+
const skipTemplate = args.includes('--skip-template');
|
|
815
|
+
|
|
816
|
+
console.log('╔════════════════════════════════════════╗');
|
|
817
|
+
console.log('║ KLD SDD 项目初始化工具 ║');
|
|
818
|
+
console.log('║ 内置 openSpec 标准模版 ║');
|
|
819
|
+
console.log('╚════════════════════════════════════════╝');
|
|
820
|
+
console.log();
|
|
821
|
+
|
|
822
|
+
// 0. 选择编辑器
|
|
823
|
+
let selectedTools;
|
|
824
|
+
try {
|
|
825
|
+
selectedTools = parseSelectedTools(args) || await selectEditor();
|
|
826
|
+
} catch (error) {
|
|
827
|
+
console.error(`❌ ${error.message}`);
|
|
828
|
+
rl.close();
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// 1. 运行 openspec init(传入用户选择的工具)
|
|
833
|
+
if (!skipOpenspec) {
|
|
834
|
+
await runOpenspecInit(selectedTools);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (!skipTemplate) {
|
|
838
|
+
// 2. 清理原生 openspec 命令(删除 opsx-*.md,避免命名混淆)
|
|
839
|
+
cleanupNativeOpenspecCommands(selectedTools);
|
|
840
|
+
|
|
841
|
+
// 3. 部署 SDD opsx 命令(根据编辑器类型使用不同模板)
|
|
842
|
+
overrideOpsxCommands(selectedTools);
|
|
843
|
+
|
|
844
|
+
// 4. 部署 SDD opsx skills
|
|
845
|
+
deployOpsxSkills(selectedTools);
|
|
846
|
+
|
|
847
|
+
// 4.1 清理原生 openspec-* skills(防止残留)
|
|
848
|
+
cleanupNativeOpenspecSkills(selectedTools);
|
|
849
|
+
|
|
850
|
+
// 5. 部署 SDD Telemetry 数据目录
|
|
851
|
+
deployTelemetryDataDir();
|
|
852
|
+
|
|
853
|
+
// 6. 复制标准文档模版到项目(供参考)
|
|
854
|
+
copyTemplatesToProject();
|
|
855
|
+
|
|
856
|
+
// 7. 初始化全局架构约束 (overview.md)
|
|
857
|
+
initGlobalOverview();
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// 7. 更新 .gitignore
|
|
861
|
+
updateGitignore();
|
|
862
|
+
|
|
863
|
+
const selectedNames = selectedTools.map(t => TOOL_CONFIGS[t].name).join(', ');
|
|
864
|
+
|
|
865
|
+
console.log();
|
|
866
|
+
console.log('╔════════════════════════════════════════╗');
|
|
867
|
+
console.log('║ ✅ 初始化完成! ║');
|
|
868
|
+
console.log('╚════════════════════════════════════════╝');
|
|
869
|
+
console.log();
|
|
870
|
+
console.log(`已为以下编辑器生成配置: ${selectedNames}`);
|
|
871
|
+
console.log();
|
|
872
|
+
console.log('已生成/覆盖:');
|
|
873
|
+
console.log(' 🌐 openspec/specs/overview.md # 全局架构约束(数据字典、接口规范)');
|
|
874
|
+
console.log(' 📄 openspec-templates/ # 标准文档模版(参考用)');
|
|
875
|
+
console.log(' 🔧 .*/commands/opsx/ # 9 个 SDD 命令');
|
|
876
|
+
console.log(' 📊 skywalk-sdd/ # SDD Telemetry 数据目录');
|
|
877
|
+
console.log();
|
|
878
|
+
|
|
879
|
+
// 统一的命令格式说明
|
|
880
|
+
console.log('可用命令(/opsx:* 系列,9 个 SDD 命令):');;
|
|
881
|
+
console.log(' /opsx:propose - 创建业务意图文档(Why)');
|
|
882
|
+
console.log(' /opsx:spec - 创建技术契约文档(What)');
|
|
883
|
+
console.log(' /opsx:design - 创建技术实现方案(How)');
|
|
884
|
+
console.log(' /opsx:task - 拆解AI编码任务(Do)');
|
|
885
|
+
console.log(' /opsx:check - 质量门禁检查(Verify)');
|
|
886
|
+
console.log(' /opsx:apply - 申请变更实施(Apply)');
|
|
887
|
+
console.log(' /opsx:test - 执行单元测试(Test)');
|
|
888
|
+
console.log(' /opsx:archive - 归档变更(Archive)');
|
|
889
|
+
console.log(' /opsx:explore - 浏览变更状态(Explore)');
|
|
890
|
+
|
|
891
|
+
console.log();
|
|
892
|
+
console.log('后续步骤:');
|
|
893
|
+
console.log(' 1. 运行 /opsx:propose <变更名称> 开始创建文档');
|
|
894
|
+
console.log(' 2. 按顺序执行 propose → spec → design → task');
|
|
895
|
+
console.log(' 3. 运行 /opsx:check 验证文档质量');
|
|
896
|
+
console.log(' 4. 运行 /opsx:apply 申请实施,/opsx:archive 归档完成');
|
|
897
|
+
console.log();
|
|
898
|
+
console.log('📊 SDD Telemetry(嵌入在命令流程中,自动执行,无需配置):');
|
|
899
|
+
console.log(' - 度量数据自动采集到 skywalk-sdd/events/');
|
|
900
|
+
console.log(' - 运行 node skywalk-sdd/log.js metrics --project=. 查看四维度指标');
|
|
901
|
+
console.log();
|
|
902
|
+
|
|
903
|
+
rl.close();
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
module.exports = { main, detectTools, getTemplatePath, selectEditor };
|