kld-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -0
- package/bin/kld-sdd-init.js +13 -0
- package/index.js +13 -0
- package/lib/init.js +607 -0
- package/package.json +33 -0
- package/templates/openspec/design.md +154 -0
- package/templates/openspec/propose.md +87 -0
- package/templates/openspec/spec.md +148 -0
- package/templates/openspec/task.md +184 -0
- package/templates/opsx-commands/design.md +164 -0
- package/templates/opsx-commands/propose.md +106 -0
- package/templates/opsx-commands/spec.md +145 -0
- package/templates/opsx-commands/task.md +179 -0
package/lib/init.js
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KLD SDD 初始化核心逻辑
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* 1. 检测并自动安装 openspec
|
|
6
|
+
* 2. 执行 openspec init 生成基础结构
|
|
7
|
+
* 3. 覆盖 opsx 命令(替换为4个独立命令)
|
|
8
|
+
* 4. 覆盖 openspec-* skill
|
|
9
|
+
* 5. 复制标准文档模版到项目
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { execSync } = require('child_process');
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
const rl = readline.createInterface({
|
|
19
|
+
input: process.stdin,
|
|
20
|
+
output: process.stdout
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// 工具类型配置
|
|
24
|
+
const TOOL_CONFIGS = {
|
|
25
|
+
cursor: {
|
|
26
|
+
name: 'Cursor',
|
|
27
|
+
configDir: '.cursor',
|
|
28
|
+
commandsDir: '.cursor/commands',
|
|
29
|
+
opsxDir: '.cursor/commands/opsx',
|
|
30
|
+
teamConfigDir: 'team-configs/cursor-commands'
|
|
31
|
+
},
|
|
32
|
+
claude: {
|
|
33
|
+
name: 'Claude Code',
|
|
34
|
+
configDir: '.claude',
|
|
35
|
+
commandsDir: '.claude/commands',
|
|
36
|
+
opsxDir: '.claude/commands/opsx',
|
|
37
|
+
teamConfigDir: 'team-configs/claude-commands'
|
|
38
|
+
},
|
|
39
|
+
codebuddy: {
|
|
40
|
+
name: 'CodeBuddy',
|
|
41
|
+
configDir: '.codebuddy',
|
|
42
|
+
commandsDir: '.codebuddy/commands',
|
|
43
|
+
opsxDir: '.codebuddy/commands/opsx',
|
|
44
|
+
teamConfigDir: 'team-configs/codebuddy-commands'
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 询问用户选择
|
|
50
|
+
*/
|
|
51
|
+
function ask(question) {
|
|
52
|
+
return new Promise(resolve => {
|
|
53
|
+
rl.question(question, answer => resolve(answer.trim()));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 检查命令是否可用
|
|
59
|
+
*/
|
|
60
|
+
function commandExists(command) {
|
|
61
|
+
try {
|
|
62
|
+
if (os.platform() === 'win32') {
|
|
63
|
+
execSync(`where ${command}`, { stdio: 'ignore' });
|
|
64
|
+
} else {
|
|
65
|
+
execSync(`which ${command}`, { stdio: 'ignore' });
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 获取包安装路径
|
|
75
|
+
*/
|
|
76
|
+
function getPackagePath() {
|
|
77
|
+
// 尝试多种方式找到包的路径
|
|
78
|
+
try {
|
|
79
|
+
// 方式1:通过 require.resolve 找到包位置
|
|
80
|
+
const mainPath = require.resolve('../package.json');
|
|
81
|
+
return path.dirname(mainPath);
|
|
82
|
+
} catch {
|
|
83
|
+
// 方式2:使用当前文件位置
|
|
84
|
+
return path.resolve(__dirname, '..');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 获取模版路径
|
|
90
|
+
*/
|
|
91
|
+
function getTemplatePath() {
|
|
92
|
+
const pkgPath = getPackagePath();
|
|
93
|
+
return path.join(pkgPath, 'templates', 'openspec');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 自动安装 openspec
|
|
98
|
+
*/
|
|
99
|
+
async function installOpenspec() {
|
|
100
|
+
console.log('📦 openspec 未安装,正在自动安装...');
|
|
101
|
+
|
|
102
|
+
const npmCmd = os.platform() === 'win32' ? 'npm.cmd' : 'npm';
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
// 尝试全局安装
|
|
106
|
+
console.log(' 正在执行: npm install -g @openspec/cli');
|
|
107
|
+
execSync(`${npmCmd} install -g @openspec/cli`, {
|
|
108
|
+
stdio: 'inherit',
|
|
109
|
+
timeout: 120000
|
|
110
|
+
});
|
|
111
|
+
console.log('✅ openspec 安装完成');
|
|
112
|
+
return true;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.log('❌ 全局安装失败,尝试本地安装...');
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// 本地安装到当前项目
|
|
118
|
+
const cwd = process.cwd();
|
|
119
|
+
execSync(`${npmCmd} install @openspec/cli`, {
|
|
120
|
+
cwd,
|
|
121
|
+
stdio: 'inherit',
|
|
122
|
+
timeout: 120000
|
|
123
|
+
});
|
|
124
|
+
console.log('✅ openspec 本地安装完成');
|
|
125
|
+
return true;
|
|
126
|
+
} catch (localError) {
|
|
127
|
+
console.log('❌ 安装失败,请手动安装: npm install -g @openspec/cli');
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 执行 openspec init
|
|
135
|
+
*/
|
|
136
|
+
async function runOpenspecInit() {
|
|
137
|
+
console.log('🚀 正在运行 openspec init...');
|
|
138
|
+
|
|
139
|
+
// 检查 openspec 是否安装
|
|
140
|
+
if (!commandExists('openspec')) {
|
|
141
|
+
const shouldInstall = await ask('openspec 未安装,是否自动安装? (y/n): ');
|
|
142
|
+
if (shouldInstall === 'y' || shouldInstall === 'Y') {
|
|
143
|
+
const installed = await installOpenspec();
|
|
144
|
+
if (!installed) {
|
|
145
|
+
console.log('⚠️ 跳过 openspec 初始化,继续应用模版...');
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
console.log('⚠️ 跳过 openspec 安装,继续应用模版...');
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
// 执行标准 openspec init
|
|
156
|
+
console.log(' 执行: openspec init');
|
|
157
|
+
execSync('openspec init', { stdio: 'inherit' });
|
|
158
|
+
|
|
159
|
+
console.log('✅ openspec 初始化完成');
|
|
160
|
+
return true;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.log('⚠️ openspec init 执行失败,继续应用模版...');
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 递归复制目录
|
|
169
|
+
*/
|
|
170
|
+
function copyDir(source, target) {
|
|
171
|
+
if (!fs.existsSync(target)) {
|
|
172
|
+
fs.mkdirSync(target, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const files = fs.readdirSync(source);
|
|
176
|
+
for (const file of files) {
|
|
177
|
+
const srcPath = path.join(source, file);
|
|
178
|
+
const tgtPath = path.join(target, file);
|
|
179
|
+
|
|
180
|
+
const stat = fs.statSync(srcPath);
|
|
181
|
+
if (stat.isDirectory()) {
|
|
182
|
+
copyDir(srcPath, tgtPath);
|
|
183
|
+
} else {
|
|
184
|
+
fs.copyFileSync(srcPath, tgtPath);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 覆盖 opsx 命令(替换为4个独立命令)
|
|
191
|
+
*/
|
|
192
|
+
function overrideOpsxCommands() {
|
|
193
|
+
console.log('🔧 正在覆盖 opsx 命令...');
|
|
194
|
+
|
|
195
|
+
const pkgPath = getPackagePath();
|
|
196
|
+
const opsxTemplatePath = path.join(pkgPath, 'templates', 'opsx-commands');
|
|
197
|
+
const cwd = process.cwd();
|
|
198
|
+
|
|
199
|
+
if (!fs.existsSync(opsxTemplatePath)) {
|
|
200
|
+
console.log(`⚠️ opsx 命令模版不存在: ${opsxTemplatePath}`);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 为每个检测到的工具覆盖 opsx 命令
|
|
205
|
+
for (const [toolKey, config] of Object.entries(TOOL_CONFIGS)) {
|
|
206
|
+
const targetOpsxDir = path.join(cwd, config.opsxDir);
|
|
207
|
+
|
|
208
|
+
// 检查该工具是否已初始化
|
|
209
|
+
if (!fs.existsSync(path.join(cwd, config.configDir))) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 确保 opsx 目录存在
|
|
214
|
+
if (!fs.existsSync(targetOpsxDir)) {
|
|
215
|
+
fs.mkdirSync(targetOpsxDir, { recursive: true });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log(` 覆盖 ${config.name} 的 opsx 命令...`);
|
|
219
|
+
|
|
220
|
+
// 读取我们的4个命令模版
|
|
221
|
+
const commands = ['propose.md', 'spec.md', 'design.md', 'task.md'];
|
|
222
|
+
|
|
223
|
+
for (const cmd of commands) {
|
|
224
|
+
const sourceFile = path.join(opsxTemplatePath, cmd);
|
|
225
|
+
const targetFile = path.join(targetOpsxDir, cmd);
|
|
226
|
+
|
|
227
|
+
if (fs.existsSync(sourceFile)) {
|
|
228
|
+
fs.copyFileSync(sourceFile, targetFile);
|
|
229
|
+
console.log(` ✓ ${cmd}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 删除原有的 propose.md(如果存在),因为我们用新的覆盖了
|
|
234
|
+
const oldProposePath = path.join(targetOpsxDir, 'propose.md');
|
|
235
|
+
if (fs.existsSync(oldProposePath)) {
|
|
236
|
+
console.log(` 🗑️ 已替换原有的 propose.md`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log('✅ opsx 命令覆盖完成');
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 覆盖 openspec-* skill
|
|
246
|
+
*/
|
|
247
|
+
function overrideOpenspecSkills() {
|
|
248
|
+
console.log('🔧 正在覆盖 openspec skills...');
|
|
249
|
+
|
|
250
|
+
const pkgPath = getPackagePath();
|
|
251
|
+
const skillsTemplatePath = path.join(pkgPath, 'templates', 'skills');
|
|
252
|
+
const cwd = process.cwd();
|
|
253
|
+
|
|
254
|
+
// 如果模版目录不存在,跳过
|
|
255
|
+
if (!fs.existsSync(skillsTemplatePath)) {
|
|
256
|
+
console.log(' 无 skills 模版需要覆盖');
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 为每个检测到的工具覆盖 skills
|
|
261
|
+
for (const [toolKey, config] of Object.entries(TOOL_CONFIGS)) {
|
|
262
|
+
const skillsDir = path.join(cwd, config.configDir, 'skills');
|
|
263
|
+
|
|
264
|
+
// 检查 skills 目录是否存在
|
|
265
|
+
if (!fs.existsSync(skillsDir)) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// 查找 openspec-* 开头的 skill 目录并覆盖
|
|
270
|
+
if (fs.existsSync(skillsTemplatePath)) {
|
|
271
|
+
const skillDirs = fs.readdirSync(skillsTemplatePath);
|
|
272
|
+
|
|
273
|
+
for (const skillDir of skillDirs) {
|
|
274
|
+
if (skillDir.startsWith('openspec-')) {
|
|
275
|
+
const sourceSkillPath = path.join(skillsTemplatePath, skillDir);
|
|
276
|
+
const targetSkillPath = path.join(skillsDir, skillDir);
|
|
277
|
+
|
|
278
|
+
if (fs.statSync(sourceSkillPath).isDirectory()) {
|
|
279
|
+
copyDir(sourceSkillPath, targetSkillPath);
|
|
280
|
+
console.log(` 覆盖 skill: ${skillDir}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.log('✅ openspec skills 覆盖完成');
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 复制标准文档模版到项目
|
|
293
|
+
*/
|
|
294
|
+
function copyTemplatesToProject() {
|
|
295
|
+
console.log('📄 正在复制标准文档模版到项目...');
|
|
296
|
+
|
|
297
|
+
const pkgPath = getPackagePath();
|
|
298
|
+
const templatePath = path.join(pkgPath, 'templates', 'openspec');
|
|
299
|
+
const cwd = process.cwd();
|
|
300
|
+
const targetDir = path.join(cwd, 'openspec-templates');
|
|
301
|
+
|
|
302
|
+
if (!fs.existsSync(templatePath)) {
|
|
303
|
+
console.log(`⚠️ 模版源目录不存在: ${templatePath}`);
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 创建目标目录
|
|
308
|
+
if (!fs.existsSync(targetDir)) {
|
|
309
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 复制模版文件
|
|
313
|
+
copyDir(templatePath, targetDir);
|
|
314
|
+
|
|
315
|
+
console.log(`✅ 标准文档模版已复制到: ${targetDir}`);
|
|
316
|
+
console.log(' 包含文件:');
|
|
317
|
+
const files = fs.readdirSync(targetDir);
|
|
318
|
+
files.forEach(file => {
|
|
319
|
+
console.log(` - ${file}`);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* 应用团队配置到编辑器
|
|
327
|
+
*/
|
|
328
|
+
function applyTeamConfig(toolKey) {
|
|
329
|
+
const config = TOOL_CONFIGS[toolKey];
|
|
330
|
+
const cwd = process.cwd();
|
|
331
|
+
const teamConfigPath = path.join(cwd, config.teamConfigDir);
|
|
332
|
+
const targetPath = path.join(cwd, config.commandsDir);
|
|
333
|
+
|
|
334
|
+
if (!fs.existsSync(teamConfigPath)) {
|
|
335
|
+
console.log(`⚠️ 未找到团队配置目录: ${config.teamConfigDir}`);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!fs.existsSync(targetPath)) {
|
|
340
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
console.log(`📁 正在应用 ${config.name} 团队配置...`);
|
|
344
|
+
copyDir(teamConfigPath, targetPath);
|
|
345
|
+
console.log(`✅ ${config.name} 配置已更新`);
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 创建团队配置目录结构
|
|
351
|
+
*/
|
|
352
|
+
function createTeamConfigStructure() {
|
|
353
|
+
const cwd = process.cwd();
|
|
354
|
+
|
|
355
|
+
console.log('📂 创建团队配置目录结构...');
|
|
356
|
+
|
|
357
|
+
// 创建 team-configs 目录
|
|
358
|
+
for (const config of Object.values(TOOL_CONFIGS)) {
|
|
359
|
+
const teamDir = path.join(cwd, config.teamConfigDir);
|
|
360
|
+
if (!fs.existsSync(teamDir)) {
|
|
361
|
+
fs.mkdirSync(teamDir, { recursive: true });
|
|
362
|
+
console.log(` ✓ 创建: ${config.teamConfigDir}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// 创建 team-schemas 目录
|
|
367
|
+
const schemasDir = path.join(cwd, 'team-schemas');
|
|
368
|
+
if (!fs.existsSync(schemasDir)) {
|
|
369
|
+
fs.mkdirSync(schemasDir, { recursive: true });
|
|
370
|
+
console.log(' ✓ 创建: team-schemas');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 创建 README
|
|
374
|
+
const readmePath = path.join(cwd, 'team-configs', 'README.md');
|
|
375
|
+
if (!fs.existsSync(readmePath)) {
|
|
376
|
+
const readmeContent = `# 团队配置目录
|
|
377
|
+
|
|
378
|
+
此目录用于存放团队共享的 AI 编辑器配置。
|
|
379
|
+
|
|
380
|
+
## 目录结构
|
|
381
|
+
|
|
382
|
+
\`\`\`
|
|
383
|
+
team-configs/
|
|
384
|
+
├── cursor-commands/ # Cursor 自定义命令
|
|
385
|
+
├── claude-commands/ # Claude Code 自定义命令
|
|
386
|
+
└── README.md
|
|
387
|
+
\`\`\`
|
|
388
|
+
|
|
389
|
+
## 使用说明
|
|
390
|
+
|
|
391
|
+
1. 将团队自定义命令文件放入对应编辑器的目录
|
|
392
|
+
2. 运行 \`kld-sdd-init\` 应用配置
|
|
393
|
+
3. 配置会自动覆盖到编辑器的命令目录
|
|
394
|
+
|
|
395
|
+
## 注意事项
|
|
396
|
+
|
|
397
|
+
- 请勿在此目录存放个人配置
|
|
398
|
+
- 修改配置后需重新运行初始化命令
|
|
399
|
+
- 建议将团队配置纳入版本控制
|
|
400
|
+
`;
|
|
401
|
+
fs.writeFileSync(readmePath, readmeContent);
|
|
402
|
+
console.log(' ✓ 创建: team-configs/README.md');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* 创建 .gitignore 配置
|
|
408
|
+
*/
|
|
409
|
+
function updateGitignore() {
|
|
410
|
+
const cwd = process.cwd();
|
|
411
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
412
|
+
|
|
413
|
+
const sddConfig = `
|
|
414
|
+
# KLD SDD 个人配置 (保留团队配置)
|
|
415
|
+
.cursor/commands/personal-*
|
|
416
|
+
.claude/commands/personal-*
|
|
417
|
+
|
|
418
|
+
# 但保留团队配置的占位目录
|
|
419
|
+
!.cursor/commands/team-*
|
|
420
|
+
!.claude/commands/team-*
|
|
421
|
+
`;
|
|
422
|
+
|
|
423
|
+
if (fs.existsSync(gitignorePath)) {
|
|
424
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
425
|
+
if (!content.includes('KLD SDD')) {
|
|
426
|
+
fs.appendFileSync(gitignorePath, sddConfig);
|
|
427
|
+
console.log('✅ 已更新 .gitignore');
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
fs.writeFileSync(gitignorePath, sddConfig.trim());
|
|
431
|
+
console.log('✅ 已创建 .gitignore');
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* 检测当前项目使用的AI编辑器
|
|
437
|
+
*/
|
|
438
|
+
function detectTools() {
|
|
439
|
+
const detected = [];
|
|
440
|
+
const cwd = process.cwd();
|
|
441
|
+
|
|
442
|
+
for (const [key, config] of Object.entries(TOOL_CONFIGS)) {
|
|
443
|
+
const configPath = path.join(cwd, config.configDir);
|
|
444
|
+
if (fs.existsSync(configPath)) {
|
|
445
|
+
detected.push(key);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return detected;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 显示帮助信息
|
|
454
|
+
*/
|
|
455
|
+
function showHelp() {
|
|
456
|
+
console.log(`
|
|
457
|
+
KLD SDD 项目初始化工具
|
|
458
|
+
|
|
459
|
+
用法:
|
|
460
|
+
kld-sdd-init [选项]
|
|
461
|
+
npx kld-sdd [选项]
|
|
462
|
+
|
|
463
|
+
选项:
|
|
464
|
+
-h, --help 显示帮助信息
|
|
465
|
+
-v, --version 显示版本号
|
|
466
|
+
--skip-openspec 跳过 openspec init 步骤
|
|
467
|
+
--skip-template 跳过复制内置模版
|
|
468
|
+
--tool <name> 指定编辑器工具 (cursor|claude)
|
|
469
|
+
|
|
470
|
+
示例:
|
|
471
|
+
kld-sdd-init # 完整初始化流程
|
|
472
|
+
kld-sdd-init --skip-openspec # 仅应用团队配置
|
|
473
|
+
kld-sdd-init --tool cursor # 仅配置 Cursor
|
|
474
|
+
`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* 主函数
|
|
479
|
+
*/
|
|
480
|
+
async function main() {
|
|
481
|
+
const args = process.argv.slice(2);
|
|
482
|
+
|
|
483
|
+
// 解析参数
|
|
484
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
485
|
+
showHelp();
|
|
486
|
+
rl.close();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (args.includes('-v') || args.includes('--version')) {
|
|
491
|
+
const pkg = require('../package.json');
|
|
492
|
+
console.log(pkg.version);
|
|
493
|
+
rl.close();
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const skipOpenspec = args.includes('--skip-openspec');
|
|
498
|
+
const skipTemplate = args.includes('--skip-template');
|
|
499
|
+
const toolIndex = args.indexOf('--tool');
|
|
500
|
+
const specifiedTool = toolIndex !== -1 ? args[toolIndex + 1] : null;
|
|
501
|
+
|
|
502
|
+
console.log('╔════════════════════════════════════════╗');
|
|
503
|
+
console.log('║ KLD SDD 项目初始化工具 ║');
|
|
504
|
+
console.log('║ 内置 openSpec 标准模版 ║');
|
|
505
|
+
console.log('╚════════════════════════════════════════╝');
|
|
506
|
+
console.log();
|
|
507
|
+
|
|
508
|
+
// 1. 运行 openspec init
|
|
509
|
+
if (!skipOpenspec) {
|
|
510
|
+
await runOpenspecInit();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// 2. 覆盖 opsx 命令(4个独立命令)
|
|
514
|
+
if (!skipTemplate) {
|
|
515
|
+
overrideOpsxCommands();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// 3. 覆盖 openspec skills
|
|
519
|
+
if (!skipTemplate) {
|
|
520
|
+
overrideOpenspecSkills();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// 4. 复制标准文档模版到项目(供参考)
|
|
524
|
+
if (!skipTemplate) {
|
|
525
|
+
copyTemplatesToProject();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// 5. 创建团队配置目录结构
|
|
529
|
+
createTeamConfigStructure();
|
|
530
|
+
|
|
531
|
+
// 6. 检测或选择工具
|
|
532
|
+
let toolsToConfig = [];
|
|
533
|
+
|
|
534
|
+
if (specifiedTool) {
|
|
535
|
+
if (TOOL_CONFIGS[specifiedTool]) {
|
|
536
|
+
toolsToConfig = [specifiedTool];
|
|
537
|
+
} else {
|
|
538
|
+
console.log(`❌ 未知工具: ${specifiedTool}`);
|
|
539
|
+
console.log(`支持的工具: ${Object.keys(TOOL_CONFIGS).join(', ')}`);
|
|
540
|
+
rl.close();
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
const detected = detectTools();
|
|
545
|
+
|
|
546
|
+
if (detected.length === 0) {
|
|
547
|
+
console.log('⚠️ 未检测到已初始化的 AI 编辑器');
|
|
548
|
+
const answer = await ask('是否手动选择要配置的工具? (cursor/claude/all/none): ');
|
|
549
|
+
|
|
550
|
+
if (answer === 'all') {
|
|
551
|
+
toolsToConfig = Object.keys(TOOL_CONFIGS);
|
|
552
|
+
} else if (TOOL_CONFIGS[answer]) {
|
|
553
|
+
toolsToConfig = [answer];
|
|
554
|
+
} else if (answer !== 'none') {
|
|
555
|
+
console.log('跳过工具配置');
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
console.log(`\n检测到以下已初始化的编辑器: ${detected.map(t => TOOL_CONFIGS[t].name).join(', ')}`);
|
|
559
|
+
const answer = await ask('是否应用团队配置? (y/n/all): ');
|
|
560
|
+
|
|
561
|
+
if (answer === 'all') {
|
|
562
|
+
toolsToConfig = Object.keys(TOOL_CONFIGS);
|
|
563
|
+
} else if (answer === 'y' || answer === 'Y') {
|
|
564
|
+
toolsToConfig = detected;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// 7. 应用团队配置
|
|
570
|
+
console.log();
|
|
571
|
+
if (toolsToConfig.length > 0) {
|
|
572
|
+
for (const tool of toolsToConfig) {
|
|
573
|
+
applyTeamConfig(tool);
|
|
574
|
+
}
|
|
575
|
+
} else {
|
|
576
|
+
console.log('⏭️ 跳过团队配置应用');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// 8. 更新 .gitignore
|
|
580
|
+
updateGitignore();
|
|
581
|
+
|
|
582
|
+
console.log();
|
|
583
|
+
console.log('╔════════════════════════════════════════╗');
|
|
584
|
+
console.log('║ ✅ 初始化完成! ║');
|
|
585
|
+
console.log('╚════════════════════════════════════════╝');
|
|
586
|
+
console.log();
|
|
587
|
+
console.log('已生成/覆盖:');
|
|
588
|
+
console.log(' 📄 openspec-templates/ # 标准四文档模版(参考用)');
|
|
589
|
+
console.log(' 🔧 .*/commands/opsx/ # 4个独立命令(propose/spec/design/task)');
|
|
590
|
+
console.log(' 📁 team-configs/ # 团队配置目录');
|
|
591
|
+
console.log();
|
|
592
|
+
console.log('可用命令:');
|
|
593
|
+
console.log(' /opsx:propose - 创建业务意图文档');
|
|
594
|
+
console.log(' /opsx:spec - 创建技术契约文档');
|
|
595
|
+
console.log(' /opsx:design - 创建技术实现方案');
|
|
596
|
+
console.log(' /opsx:task - 拆解AI编码任务');
|
|
597
|
+
console.log();
|
|
598
|
+
console.log('后续步骤:');
|
|
599
|
+
console.log(' 1. 运行 /opsx:propose <变更名称> 开始创建文档');
|
|
600
|
+
console.log(' 2. 按顺序执行 propose → spec → design → task');
|
|
601
|
+
console.log(' 3. 运行 /opsx:apply 开始代码实现');
|
|
602
|
+
console.log();
|
|
603
|
+
|
|
604
|
+
rl.close();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
module.exports = { main, detectTools, applyTeamConfig, getTemplatePath };
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kld-sdd",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "KLD SDD OpenSpec 项目初始化工具 - 内置模版一键初始化",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kld-sdd-init": "bin/kld-sdd-init.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"kld",
|
|
14
|
+
"sdd",
|
|
15
|
+
"openspec",
|
|
16
|
+
"init",
|
|
17
|
+
"cursor",
|
|
18
|
+
"claude",
|
|
19
|
+
"template"
|
|
20
|
+
],
|
|
21
|
+
"author": "KLD Team",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=14.0.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin/",
|
|
28
|
+
"lib/",
|
|
29
|
+
"templates/",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {}
|
|
33
|
+
}
|