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.
Files changed (30) hide show
  1. package/lib/init.js +201 -12
  2. package/package.json +1 -1
  3. package/skywalk-sdd/index.js +2241 -127
  4. package/templates/ci/github-actions-sdd.yml +67 -0
  5. package/templates/ci/gitlab-ci-sdd.yml +44 -0
  6. package/templates/git-hooks/pre-commit-sdd-check.js +155 -0
  7. package/templates/git-hooks/pre-push-sdd-check.js +41 -0
  8. package/templates/hooks/claude/hooks/sdd-post-tool.js +120 -0
  9. package/templates/hooks/claude/hooks/sdd-pre-tool.js +38 -0
  10. package/templates/hooks/claude/hooks/sdd-prompt.js +66 -0
  11. package/templates/hooks/claude/hooks/sdd-stop.js +82 -0
  12. package/templates/hooks/claude/settings.json +46 -0
  13. package/templates/opsx-commands/apply.md +75 -7
  14. package/templates/opsx-commands/archive.md +116 -55
  15. package/templates/opsx-commands/check.md +123 -4
  16. package/templates/opsx-commands/design.md +14 -4
  17. package/templates/opsx-commands/explore.md +14 -4
  18. package/templates/opsx-commands/propose.md +10 -4
  19. package/templates/opsx-commands/spec.md +14 -4
  20. package/templates/opsx-commands/task.md +15 -5
  21. package/templates/opsx-commands/test.md +41 -4
  22. package/templates/skills/opsx-apply/SKILL.md +63 -5
  23. package/templates/skills/opsx-archive/SKILL.md +94 -47
  24. package/templates/skills/opsx-check/SKILL.md +47 -3
  25. package/templates/skills/opsx-design/SKILL.md +8 -3
  26. package/templates/skills/opsx-explore/SKILL.md +8 -3
  27. package/templates/skills/opsx-propose/SKILL.md +8 -3
  28. package/templates/skills/opsx-spec/SKILL.md +8 -3
  29. package/templates/skills/opsx-task/SKILL.md +8 -3
  30. 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.copyFileSync(sourceFile, targetFile);
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
- const skillFile = path.join(sourceDir, 'SKILL.md');
522
- if (fs.existsSync(skillFile)) {
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
  // 统一的命令格式说明
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kld-sdd",
3
- "version": "2.4.7",
3
+ "version": "2.4.9",
4
4
  "description": "KLD SDD OpenSpec 项目初始化工具 - 内置模版一键初始化",
5
5
  "main": "index.js",
6
6
  "bin": {