kld-sdd 2.4.7 → 2.4.9
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/lib/init.js +201 -12
- package/package.json +1 -1
- package/skywalk-sdd/index.js +2241 -127
- package/templates/ci/github-actions-sdd.yml +67 -0
- package/templates/ci/gitlab-ci-sdd.yml +44 -0
- package/templates/git-hooks/pre-commit-sdd-check.js +155 -0
- package/templates/git-hooks/pre-push-sdd-check.js +41 -0
- package/templates/hooks/claude/hooks/sdd-post-tool.js +120 -0
- package/templates/hooks/claude/hooks/sdd-pre-tool.js +38 -0
- package/templates/hooks/claude/hooks/sdd-prompt.js +66 -0
- package/templates/hooks/claude/hooks/sdd-stop.js +82 -0
- package/templates/hooks/claude/settings.json +46 -0
- package/templates/opsx-commands/apply.md +75 -7
- package/templates/opsx-commands/archive.md +116 -55
- package/templates/opsx-commands/check.md +123 -4
- package/templates/opsx-commands/design.md +14 -4
- package/templates/opsx-commands/explore.md +14 -4
- package/templates/opsx-commands/propose.md +10 -4
- package/templates/opsx-commands/spec.md +14 -4
- package/templates/opsx-commands/task.md +15 -5
- package/templates/opsx-commands/test.md +41 -4
- package/templates/skills/opsx-apply/SKILL.md +63 -5
- package/templates/skills/opsx-archive/SKILL.md +94 -47
- package/templates/skills/opsx-check/SKILL.md +47 -3
- package/templates/skills/opsx-design/SKILL.md +8 -3
- package/templates/skills/opsx-explore/SKILL.md +8 -3
- package/templates/skills/opsx-propose/SKILL.md +8 -3
- package/templates/skills/opsx-spec/SKILL.md +8 -3
- package/templates/skills/opsx-task/SKILL.md +8 -3
- package/templates/skills/opsx-test/SKILL.md +8 -3
package/lib/init.js
CHANGED
|
@@ -28,28 +28,32 @@ const TOOL_CONFIGS = {
|
|
|
28
28
|
configDir: '.cursor',
|
|
29
29
|
commandsDir: '.cursor/commands',
|
|
30
30
|
opsxDir: '.cursor/commands/opsx',
|
|
31
|
-
skillsDir: '.cursor/skills'
|
|
31
|
+
skillsDir: '.cursor/skills',
|
|
32
|
+
agentType: 'cursor'
|
|
32
33
|
},
|
|
33
34
|
claude: {
|
|
34
35
|
name: 'Claude Code',
|
|
35
36
|
configDir: '.claude',
|
|
36
37
|
commandsDir: '.claude/commands',
|
|
37
38
|
opsxDir: '.claude/commands/opsx',
|
|
38
|
-
skillsDir: '.claude/skills'
|
|
39
|
+
skillsDir: '.claude/skills',
|
|
40
|
+
agentType: 'claude-code'
|
|
39
41
|
},
|
|
40
42
|
codebuddy: {
|
|
41
43
|
name: 'CodeBuddy',
|
|
42
44
|
configDir: '.codebuddy',
|
|
43
45
|
commandsDir: '.codebuddy/commands',
|
|
44
46
|
opsxDir: '.codebuddy/commands/opsx',
|
|
45
|
-
skillsDir: '.codebuddy/skills'
|
|
47
|
+
skillsDir: '.codebuddy/skills',
|
|
48
|
+
agentType: 'codebuddy'
|
|
46
49
|
},
|
|
47
50
|
qoder: {
|
|
48
51
|
name: 'Qoder',
|
|
49
52
|
configDir: '.qoder',
|
|
50
53
|
commandsDir: '.qoder/commands',
|
|
51
54
|
opsxDir: '.qoder/commands/opsx',
|
|
52
|
-
skillsDir: '.qoder/skills'
|
|
55
|
+
skillsDir: '.qoder/skills',
|
|
56
|
+
agentType: 'qoder'
|
|
53
57
|
},
|
|
54
58
|
opencode: {
|
|
55
59
|
name: 'OpenCode',
|
|
@@ -57,6 +61,7 @@ const TOOL_CONFIGS = {
|
|
|
57
61
|
commandsDir: '.opencode/commands',
|
|
58
62
|
opsxDir: '.opencode/commands/opsx',
|
|
59
63
|
skillsDir: '.opencode/skills',
|
|
64
|
+
agentType: 'opencode',
|
|
60
65
|
// opencode 原生命令使用 command/(单数),需要额外清理
|
|
61
66
|
nativeCommandDir: '.opencode/command'
|
|
62
67
|
},
|
|
@@ -65,21 +70,24 @@ const TOOL_CONFIGS = {
|
|
|
65
70
|
configDir: '.kunlunzhima',
|
|
66
71
|
commandsDir: '.kunlunzhima/commands',
|
|
67
72
|
opsxDir: '.kunlunzhima/commands/opsx',
|
|
68
|
-
skillsDir: '.kunlunzhima/skills'
|
|
73
|
+
skillsDir: '.kunlunzhima/skills',
|
|
74
|
+
agentType: 'kunlunzhima'
|
|
69
75
|
},
|
|
70
76
|
workbuddy: {
|
|
71
77
|
name: 'WorkBuddy',
|
|
72
78
|
configDir: '.workbuddy',
|
|
73
79
|
commandsDir: '.workbuddy/commands',
|
|
74
80
|
opsxDir: '.workbuddy/commands/opsx',
|
|
75
|
-
skillsDir: '.workbuddy/skills'
|
|
81
|
+
skillsDir: '.workbuddy/skills',
|
|
82
|
+
agentType: 'workbuddy'
|
|
76
83
|
},
|
|
77
84
|
codex: {
|
|
78
85
|
name: 'Codex',
|
|
79
86
|
configDir: '.agents',
|
|
80
87
|
commandsDir: '.agents/commands',
|
|
81
88
|
opsxDir: '.agents/commands/opsx',
|
|
82
|
-
skillsDir: '.agents/skills'
|
|
89
|
+
skillsDir: '.agents/skills',
|
|
90
|
+
agentType: 'codex'
|
|
83
91
|
}
|
|
84
92
|
};
|
|
85
93
|
|
|
@@ -318,6 +326,32 @@ function copyDir(source, target) {
|
|
|
318
326
|
}
|
|
319
327
|
}
|
|
320
328
|
|
|
329
|
+
function renderTemplate(content, config, toolKey) {
|
|
330
|
+
return content
|
|
331
|
+
.replace(/^\uFEFF/, '')
|
|
332
|
+
.replace(/<Agent(?:\u7c7b\u578b|\u7eeb\u8bf2\u7037)>/g, config.agentType || toolKey);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function copyDirRendered(source, target, config, toolKey) {
|
|
336
|
+
if (!fs.existsSync(target)) {
|
|
337
|
+
fs.mkdirSync(target, { recursive: true });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const files = fs.readdirSync(source);
|
|
341
|
+
for (const file of files) {
|
|
342
|
+
const srcPath = path.join(source, file);
|
|
343
|
+
const tgtPath = path.join(target, file);
|
|
344
|
+
|
|
345
|
+
const stat = fs.statSync(srcPath);
|
|
346
|
+
if (stat.isDirectory()) {
|
|
347
|
+
copyDirRendered(srcPath, tgtPath, config, toolKey);
|
|
348
|
+
} else {
|
|
349
|
+
const rendered = renderTemplate(fs.readFileSync(srcPath, 'utf8'), config, toolKey);
|
|
350
|
+
fs.writeFileSync(tgtPath, rendered, 'utf8');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
321
355
|
/**
|
|
322
356
|
* 清理原生 openspec 命令(opsx-*.md),避免与 opsx:* 命令混淆
|
|
323
357
|
* 同时清理 openspec 遗留的 JSON 文件
|
|
@@ -459,7 +493,8 @@ function overrideOpsxCommands(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
|
459
493
|
const targetFile = path.join(targetOpsxDir, cmd);
|
|
460
494
|
|
|
461
495
|
if (fs.existsSync(sourceFile)) {
|
|
462
|
-
fs.
|
|
496
|
+
const rendered = renderTemplate(fs.readFileSync(sourceFile, 'utf8'), config, toolKey);
|
|
497
|
+
fs.writeFileSync(targetFile, rendered, 'utf8');
|
|
463
498
|
console.log(` ✓ ${cmd}`);
|
|
464
499
|
}
|
|
465
500
|
}
|
|
@@ -518,9 +553,8 @@ function deployOpsxSkills(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
|
518
553
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
519
554
|
}
|
|
520
555
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
fs.copyFileSync(skillFile, path.join(targetDir, 'SKILL.md'));
|
|
556
|
+
if (fs.existsSync(sourceDir)) {
|
|
557
|
+
copyDirRendered(sourceDir, targetDir, config, toolKey);
|
|
524
558
|
console.log(` ✓ ${skillDir}`);
|
|
525
559
|
}
|
|
526
560
|
}
|
|
@@ -530,6 +564,71 @@ function deployOpsxSkills(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
|
530
564
|
return true;
|
|
531
565
|
}
|
|
532
566
|
|
|
567
|
+
/**
|
|
568
|
+
* 部署 Claude Code Hook Pack(可选增强)
|
|
569
|
+
* Hook 只调用 skywalk-sdd/log.js,不写私有日志,不替代 OPSX 模板采集。
|
|
570
|
+
*/
|
|
571
|
+
function deployClaudeHookPack(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
572
|
+
if (!selectedTools.includes('claude')) {
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
console.log('🪝 正在部署 Claude Code SDD Hook Pack...');
|
|
577
|
+
|
|
578
|
+
const pkgPath = getPackagePath();
|
|
579
|
+
const cwd = process.cwd();
|
|
580
|
+
const hookTemplateDir = path.join(pkgPath, 'templates', 'hooks', 'claude');
|
|
581
|
+
const hookSourceDir = path.join(hookTemplateDir, 'hooks');
|
|
582
|
+
const settingsTemplatePath = path.join(hookTemplateDir, 'settings.json');
|
|
583
|
+
|
|
584
|
+
if (!fs.existsSync(hookSourceDir) || !fs.existsSync(settingsTemplatePath)) {
|
|
585
|
+
console.log(` ⚠️ Claude Hook 模板缺失: ${hookTemplateDir}`);
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const claudeDir = path.join(cwd, '.claude');
|
|
590
|
+
const targetHookDir = path.join(claudeDir, 'hooks');
|
|
591
|
+
if (!fs.existsSync(targetHookDir)) {
|
|
592
|
+
fs.mkdirSync(targetHookDir, { recursive: true });
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
copyDir(hookSourceDir, targetHookDir);
|
|
596
|
+
console.log(' ✓ 部署 .claude/hooks/sdd-*.js');
|
|
597
|
+
|
|
598
|
+
const targetSettingsPath = path.join(claudeDir, 'settings.json');
|
|
599
|
+
const templateSettings = JSON.parse(fs.readFileSync(settingsTemplatePath, 'utf8'));
|
|
600
|
+
let existingSettings = {};
|
|
601
|
+
if (fs.existsSync(targetSettingsPath)) {
|
|
602
|
+
try {
|
|
603
|
+
existingSettings = JSON.parse(fs.readFileSync(targetSettingsPath, 'utf8'));
|
|
604
|
+
} catch {
|
|
605
|
+
const backupPath = `${targetSettingsPath}.backup`;
|
|
606
|
+
fs.copyFileSync(targetSettingsPath, backupPath);
|
|
607
|
+
console.log(` ℹ️ settings.json 不是合法 JSON,已备份到 ${backupPath}`);
|
|
608
|
+
existingSettings = {};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
existingSettings.hooks = existingSettings.hooks || {};
|
|
613
|
+
for (const [eventName, entries] of Object.entries(templateSettings.hooks || {})) {
|
|
614
|
+
existingSettings.hooks[eventName] = existingSettings.hooks[eventName] || [];
|
|
615
|
+
for (const entry of entries) {
|
|
616
|
+
const templateCommands = (entry.hooks || []).map(h => h.command).filter(Boolean);
|
|
617
|
+
const exists = existingSettings.hooks[eventName].some(existingEntry => {
|
|
618
|
+
return (existingEntry.hooks || []).some(h => templateCommands.includes(h.command));
|
|
619
|
+
});
|
|
620
|
+
if (!exists) {
|
|
621
|
+
existingSettings.hooks[eventName].push(entry);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
fs.writeFileSync(targetSettingsPath, JSON.stringify(existingSettings, null, 2) + '\n', 'utf8');
|
|
627
|
+
console.log(' ✓ 合并 .claude/settings.json hooks 配置');
|
|
628
|
+
console.log('✅ Claude Code SDD Hook Pack 已就绪(可选增强,核心采集仍由 OPSX 模板完成)');
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
|
|
533
632
|
/**
|
|
534
633
|
* 清理原生 openspec-* skills(防止残留)
|
|
535
634
|
* 仅负责删除 openspec init 产生的原生 skills。
|
|
@@ -676,6 +775,7 @@ function deployTelemetryDataDir() {
|
|
|
676
775
|
// 创建本地数据目录(事件和报告存储)
|
|
677
776
|
const eventsDir = path.join(dataDir, 'events');
|
|
678
777
|
const reportsDir = path.join(dataDir, 'reports');
|
|
778
|
+
const stateDir = path.join(dataDir, 'state');
|
|
679
779
|
if (!fs.existsSync(eventsDir)) {
|
|
680
780
|
fs.mkdirSync(eventsDir, { recursive: true });
|
|
681
781
|
console.log(' ✓ 创建 skywalk-sdd/events/ 数据目录');
|
|
@@ -684,6 +784,10 @@ function deployTelemetryDataDir() {
|
|
|
684
784
|
fs.mkdirSync(reportsDir, { recursive: true });
|
|
685
785
|
console.log(' ✓ 创建 skywalk-sdd/reports/ 报告目录');
|
|
686
786
|
}
|
|
787
|
+
if (!fs.existsSync(stateDir)) {
|
|
788
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
789
|
+
console.log(' ✓ 创建 skywalk-sdd/state/ 活跃阶段状态目录');
|
|
790
|
+
}
|
|
687
791
|
|
|
688
792
|
// 部署项目内 Telemetry CLI(不依赖 npm 发布)
|
|
689
793
|
const telemetrySrc = path.join(pkgPath, 'skywalk-sdd', 'index.js');
|
|
@@ -700,6 +804,78 @@ function deployTelemetryDataDir() {
|
|
|
700
804
|
return true;
|
|
701
805
|
}
|
|
702
806
|
|
|
807
|
+
function installGitHookShim(cwd, hookName, scriptPath) {
|
|
808
|
+
const hooksDir = path.join(cwd, '.git', 'hooks');
|
|
809
|
+
if (!fs.existsSync(hooksDir)) {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const hookPath = path.join(hooksDir, hookName);
|
|
814
|
+
const marker = 'KLD SDD quality gate';
|
|
815
|
+
const shim = [
|
|
816
|
+
'#!/bin/sh',
|
|
817
|
+
`# ${marker}`,
|
|
818
|
+
`if [ -f "${scriptPath}" ]; then`,
|
|
819
|
+
` node "${scriptPath}" "$@"`,
|
|
820
|
+
'fi',
|
|
821
|
+
'',
|
|
822
|
+
].join('\n');
|
|
823
|
+
|
|
824
|
+
if (fs.existsSync(hookPath)) {
|
|
825
|
+
const existing = fs.readFileSync(hookPath, 'utf8');
|
|
826
|
+
if (existing.includes(marker)) {
|
|
827
|
+
console.log(` ✓ .git/hooks/${hookName} 已包含 SDD 质量门禁`);
|
|
828
|
+
} else {
|
|
829
|
+
console.log(` ℹ️ .git/hooks/${hookName} 已存在,已保留不覆盖;可手动调用 ${scriptPath}`);
|
|
830
|
+
}
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
fs.writeFileSync(hookPath, shim, 'utf8');
|
|
835
|
+
try {
|
|
836
|
+
fs.chmodSync(hookPath, 0o755);
|
|
837
|
+
} catch {
|
|
838
|
+
// Windows 上 chmod 可能没有实际效果,不影响 Git Bash 执行文本 hook。
|
|
839
|
+
}
|
|
840
|
+
console.log(` ✓ 安装 .git/hooks/${hookName} SDD 质量门禁`);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* 部署 Git hooks / CI 兜底模板
|
|
845
|
+
* 这些脚本只做质量门禁和 CI 补充记录,不替代 OPSX 主采集链路。
|
|
846
|
+
*/
|
|
847
|
+
function deployQualityGateTemplates() {
|
|
848
|
+
console.log('🧰 正在部署 SDD Git hooks / CI 兜底模板...');
|
|
849
|
+
|
|
850
|
+
const pkgPath = getPackagePath();
|
|
851
|
+
const cwd = process.cwd();
|
|
852
|
+
const dataDir = path.join(cwd, 'skywalk-sdd');
|
|
853
|
+
const gitHooksTemplateDir = path.join(pkgPath, 'templates', 'git-hooks');
|
|
854
|
+
const ciTemplateDir = path.join(pkgPath, 'templates', 'ci');
|
|
855
|
+
const targetGitHooksDir = path.join(dataDir, 'git-hooks');
|
|
856
|
+
const targetCiDir = path.join(dataDir, 'ci');
|
|
857
|
+
|
|
858
|
+
if (fs.existsSync(gitHooksTemplateDir)) {
|
|
859
|
+
copyDir(gitHooksTemplateDir, targetGitHooksDir);
|
|
860
|
+
console.log(' ✓ 部署 skywalk-sdd/git-hooks/');
|
|
861
|
+
} else {
|
|
862
|
+
console.log(` ⚠️ Git hooks 模板缺失: ${gitHooksTemplateDir}`);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (fs.existsSync(ciTemplateDir)) {
|
|
866
|
+
copyDir(ciTemplateDir, targetCiDir);
|
|
867
|
+
console.log(' ✓ 部署 skywalk-sdd/ci/');
|
|
868
|
+
} else {
|
|
869
|
+
console.log(` ⚠️ CI 模板缺失: ${ciTemplateDir}`);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
installGitHookShim(cwd, 'pre-commit', 'skywalk-sdd/git-hooks/pre-commit-sdd-check.js');
|
|
873
|
+
installGitHookShim(cwd, 'pre-push', 'skywalk-sdd/git-hooks/pre-push-sdd-check.js');
|
|
874
|
+
|
|
875
|
+
console.log('✅ Git hooks / CI 兜底模板已就绪');
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
|
|
703
879
|
/**
|
|
704
880
|
* 检测当前项目已初始化的 AI 编辑器
|
|
705
881
|
*/
|
|
@@ -729,7 +905,8 @@ function updateGitignore() {
|
|
|
729
905
|
'.workbuddy/commands/personal-*',
|
|
730
906
|
'.agents/commands/personal-*',
|
|
731
907
|
'skywalk-sdd/events/',
|
|
732
|
-
'skywalk-sdd/reports/'
|
|
908
|
+
'skywalk-sdd/reports/',
|
|
909
|
+
'skywalk-sdd/state/'
|
|
733
910
|
];
|
|
734
911
|
|
|
735
912
|
const sddConfig = `
|
|
@@ -745,6 +922,7 @@ function updateGitignore() {
|
|
|
745
922
|
# SDD Telemetry 数据(本地度量,可选提交)
|
|
746
923
|
skywalk-sdd/events/
|
|
747
924
|
skywalk-sdd/reports/
|
|
925
|
+
skywalk-sdd/state/
|
|
748
926
|
`;
|
|
749
927
|
|
|
750
928
|
if (fs.existsSync(gitignorePath)) {
|
|
@@ -847,9 +1025,15 @@ async function main() {
|
|
|
847
1025
|
// 4.1 清理原生 openspec-* skills(防止残留)
|
|
848
1026
|
cleanupNativeOpenspecSkills(selectedTools);
|
|
849
1027
|
|
|
1028
|
+
// 4.2 部署 Claude Code Hook Pack(可选增强)
|
|
1029
|
+
deployClaudeHookPack(selectedTools);
|
|
1030
|
+
|
|
850
1031
|
// 5. 部署 SDD Telemetry 数据目录
|
|
851
1032
|
deployTelemetryDataDir();
|
|
852
1033
|
|
|
1034
|
+
// 5.1 部署 Git hooks / CI 兜底模板
|
|
1035
|
+
deployQualityGateTemplates();
|
|
1036
|
+
|
|
853
1037
|
// 6. 复制标准文档模版到项目(供参考)
|
|
854
1038
|
copyTemplatesToProject();
|
|
855
1039
|
|
|
@@ -873,7 +1057,12 @@ async function main() {
|
|
|
873
1057
|
console.log(' 🌐 openspec/specs/overview.md # 全局架构约束(数据字典、接口规范)');
|
|
874
1058
|
console.log(' 📄 openspec-templates/ # 标准文档模版(参考用)');
|
|
875
1059
|
console.log(' 🔧 .*/commands/opsx/ # 9 个 SDD 命令');
|
|
1060
|
+
if (selectedTools.includes('claude')) {
|
|
1061
|
+
console.log(' 🪝 .claude/hooks/ # Claude Code SDD Hook Pack(可选增强)');
|
|
1062
|
+
}
|
|
876
1063
|
console.log(' 📊 skywalk-sdd/ # SDD Telemetry 数据目录');
|
|
1064
|
+
console.log(' 🧰 skywalk-sdd/git-hooks/ # Git hooks 质量门禁模板');
|
|
1065
|
+
console.log(' 🧪 skywalk-sdd/ci/ # CI 兜底采集模板');
|
|
877
1066
|
console.log();
|
|
878
1067
|
|
|
879
1068
|
// 统一的命令格式说明
|