kld-sdd 2.5.0 → 2.5.2
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 +95 -8
- package/kld-sdd-guide.html +1109 -0
- package/lib/command-bridge.js +156 -0
- package/lib/deploy-codebuddy-hooks.js +99 -0
- package/lib/hook-gate-core.js +333 -0
- package/lib/init.js +137 -82
- package/lib/settings-merge.js +85 -0
- package/lib/skills-bundle.js +142 -5
- package/lib/tool-profiles.js +270 -0
- package/package.json +4 -2
- package/skywalk-sdd/index.cjs +329 -24
- package/skywalk-sdd/ontology/artifact-observer.cjs +91 -0
- package/skywalk-sdd/ontology/artifact-parser.cjs +621 -0
- package/skywalk-sdd/ontology/change-lock.cjs +126 -0
- package/skywalk-sdd/ontology/cli.cjs +146 -0
- package/skywalk-sdd/ontology/effective-graph.cjs +158 -0
- package/skywalk-sdd/ontology/id.cjs +126 -0
- package/skywalk-sdd/ontology/identity-index.cjs +262 -0
- package/skywalk-sdd/ontology/normalizer.cjs +107 -0
- package/skywalk-sdd/ontology/runtime.cjs +341 -0
- package/skywalk-sdd/ontology/schema.cjs +139 -0
- package/skywalk-sdd/ontology/structural-identity.cjs +77 -0
- package/skywalk-sdd/ontology/traceability-validator.cjs +610 -0
- package/templates/commands/kunlunzhima/skill-bridge.md +23 -0
- package/templates/hooks/codebuddy/hooks/sdd-apply-gate.cjs +16 -0
- package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +395 -0
- package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +123 -0
- package/templates/hooks/codebuddy/hooks/sdd-pre-tool.cjs +16 -0
- package/templates/hooks/codebuddy/hooks/sdd-prompt.cjs +48 -0
- package/templates/hooks/codebuddy/hooks/sdd-skill-apply-gate.cjs +16 -0
- package/templates/hooks/codebuddy/hooks/sdd-stop.cjs +70 -0
- package/templates/hooks/codebuddy/settings.json +72 -0
- package/templates/openspec/design.md +18 -0
- package/templates/openspec/proposal.md +19 -6
- package/templates/openspec/spec.md +62 -8
- package/templates/openspec/tasks.md +28 -6
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +5 -5
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +9 -0
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +17 -1
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +10 -1
- package/templates/skills/kld-sdd/opsx-explore/SKILL.md +1 -1
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +11 -1
- package/templates/skills/kld-sdd/opsx-rules/SKILL.md +131 -0
- package/templates/skills/kld-sdd/opsx-rules/checklist.md +27 -0
- package/templates/skills/kld-sdd/opsx-rules/reference.md +124 -0
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +12 -1
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +10 -1
- package/templates/skills/kld-sdd/opsx-test/SKILL.md +1 -1
package/lib/init.js
CHANGED
|
@@ -17,83 +17,25 @@ const os = require('os');
|
|
|
17
17
|
const {
|
|
18
18
|
SKILLS_BUNDLE_NAME,
|
|
19
19
|
OPSX_SKILL_DIRS,
|
|
20
|
-
|
|
20
|
+
OPSX_SKILL_METADATA,
|
|
21
|
+
renderTemplate,
|
|
22
|
+
validateRenderedContent,
|
|
21
23
|
} = require('./skills-bundle');
|
|
24
|
+
const { getToolProfile, buildToolConfigs } = require('./tool-profiles');
|
|
25
|
+
const {
|
|
26
|
+
deployKunlunCommandBridges,
|
|
27
|
+
cleanupManagedKunlunBridges,
|
|
28
|
+
BRIDGE_MARKER,
|
|
29
|
+
} = require('./command-bridge');
|
|
30
|
+
const { deployCodebuddyHookPack } = require('./deploy-codebuddy-hooks');
|
|
22
31
|
|
|
23
32
|
const rl = readline.createInterface({
|
|
24
33
|
input: process.stdin,
|
|
25
34
|
output: process.stdout
|
|
26
35
|
});
|
|
27
36
|
|
|
28
|
-
//
|
|
29
|
-
const TOOL_CONFIGS =
|
|
30
|
-
cursor: {
|
|
31
|
-
name: 'Cursor',
|
|
32
|
-
configDir: '.cursor',
|
|
33
|
-
commandsDir: '.cursor/commands',
|
|
34
|
-
opsxDir: '.cursor/commands/opsx',
|
|
35
|
-
skillsDir: '.cursor/skills',
|
|
36
|
-
agentType: 'cursor'
|
|
37
|
-
},
|
|
38
|
-
claude: {
|
|
39
|
-
name: 'Claude Code',
|
|
40
|
-
configDir: '.claude',
|
|
41
|
-
commandsDir: '.claude/commands',
|
|
42
|
-
opsxDir: '.claude/commands/opsx',
|
|
43
|
-
skillsDir: '.claude/skills',
|
|
44
|
-
agentType: 'claude-code'
|
|
45
|
-
},
|
|
46
|
-
codebuddy: {
|
|
47
|
-
name: 'CodeBuddy',
|
|
48
|
-
configDir: '.codebuddy',
|
|
49
|
-
commandsDir: '.codebuddy/commands',
|
|
50
|
-
opsxDir: '.codebuddy/commands/opsx',
|
|
51
|
-
skillsDir: '.codebuddy/skills',
|
|
52
|
-
agentType: 'codebuddy'
|
|
53
|
-
},
|
|
54
|
-
qoder: {
|
|
55
|
-
name: 'Qoder',
|
|
56
|
-
configDir: '.qoder',
|
|
57
|
-
commandsDir: '.qoder/commands',
|
|
58
|
-
opsxDir: '.qoder/commands/opsx',
|
|
59
|
-
skillsDir: '.qoder/skills',
|
|
60
|
-
agentType: 'qoder'
|
|
61
|
-
},
|
|
62
|
-
opencode: {
|
|
63
|
-
name: 'OpenCode',
|
|
64
|
-
configDir: '.opencode',
|
|
65
|
-
commandsDir: '.opencode/commands',
|
|
66
|
-
opsxDir: '.opencode/commands/opsx',
|
|
67
|
-
skillsDir: '.opencode/skills',
|
|
68
|
-
agentType: 'opencode',
|
|
69
|
-
// opencode 原生命令使用 command/(单数),需要额外清理
|
|
70
|
-
nativeCommandDir: '.opencode/command'
|
|
71
|
-
},
|
|
72
|
-
kunlunzhima: {
|
|
73
|
-
name: 'KunlunZhima',
|
|
74
|
-
configDir: '.kunlunzhima',
|
|
75
|
-
commandsDir: '.kunlunzhima/commands',
|
|
76
|
-
opsxDir: '.kunlunzhima/commands/opsx',
|
|
77
|
-
skillsDir: '.kunlunzhima/skills',
|
|
78
|
-
agentType: 'kunlunzhima'
|
|
79
|
-
},
|
|
80
|
-
workbuddy: {
|
|
81
|
-
name: 'WorkBuddy',
|
|
82
|
-
configDir: '.workbuddy',
|
|
83
|
-
commandsDir: '.workbuddy/commands',
|
|
84
|
-
opsxDir: '.workbuddy/commands/opsx',
|
|
85
|
-
skillsDir: '.workbuddy/skills',
|
|
86
|
-
agentType: 'workbuddy'
|
|
87
|
-
},
|
|
88
|
-
codex: {
|
|
89
|
-
name: 'Codex',
|
|
90
|
-
configDir: '.agents',
|
|
91
|
-
commandsDir: '.agents/commands',
|
|
92
|
-
opsxDir: '.agents/commands/opsx',
|
|
93
|
-
skillsDir: '.agents/skills',
|
|
94
|
-
agentType: 'codex'
|
|
95
|
-
}
|
|
96
|
-
};
|
|
37
|
+
// 工具类型配置(路径字段从 TOOL_PROFILES 派生,避免双份漂移)
|
|
38
|
+
const TOOL_CONFIGS = buildToolConfigs();
|
|
97
39
|
|
|
98
40
|
/**
|
|
99
41
|
* 询问用户选择
|
|
@@ -331,6 +273,8 @@ function copyDir(source, target) {
|
|
|
331
273
|
}
|
|
332
274
|
|
|
333
275
|
function copyDirRendered(source, target, config, toolKey) {
|
|
276
|
+
const profile = getToolProfile(toolKey);
|
|
277
|
+
|
|
334
278
|
if (!fs.existsSync(target)) {
|
|
335
279
|
fs.mkdirSync(target, { recursive: true });
|
|
336
280
|
}
|
|
@@ -344,7 +288,8 @@ function copyDirRendered(source, target, config, toolKey) {
|
|
|
344
288
|
if (stat.isDirectory()) {
|
|
345
289
|
copyDirRendered(srcPath, tgtPath, config, toolKey);
|
|
346
290
|
} else {
|
|
347
|
-
const rendered = renderTemplate(fs.readFileSync(srcPath, 'utf8'), config, toolKey);
|
|
291
|
+
const rendered = renderTemplate(fs.readFileSync(srcPath, 'utf8'), config, toolKey, profile);
|
|
292
|
+
validateRenderedContent(rendered, profile, tgtPath);
|
|
348
293
|
fs.writeFileSync(tgtPath, rendered, 'utf8');
|
|
349
294
|
}
|
|
350
295
|
}
|
|
@@ -363,7 +308,9 @@ const NATIVE_OPSX_CMDS_SLASH = ['propose.md', 'apply.md', 'archive.md', 'explore
|
|
|
363
308
|
* SDD 自定义 command 标记(init 不部署;用户手动维护时保留)
|
|
364
309
|
*/
|
|
365
310
|
function isSddCustomCommand(content) {
|
|
366
|
-
return content.includes(
|
|
311
|
+
return content.includes(BRIDGE_MARKER)
|
|
312
|
+
|| content.includes('skywalk-sdd/log.cjs')
|
|
313
|
+
|| /# SDD\b/.test(content);
|
|
367
314
|
}
|
|
368
315
|
|
|
369
316
|
/**
|
|
@@ -376,7 +323,8 @@ function isSddCustomCommand(content) {
|
|
|
376
323
|
* openspec 对不同工具生成的目录结构:
|
|
377
324
|
* - Cursor: commands/opsx-*.md(根目录,连字符)
|
|
378
325
|
* - Claude/CodeBuddy/Qoder 等: commands/opsx/*.md(opsx 子目录)
|
|
379
|
-
* - OpenCode:
|
|
326
|
+
* - OpenSpec OpenCode: commands/opsx-*.md(复数扁平,通用 commands/ 循环清理)
|
|
327
|
+
* - Legacy OpenCode: command/(单数目录,nativeCommandDir 整目录删除)
|
|
380
328
|
*
|
|
381
329
|
* @param {string[]} selectedTools - 用户选择的编辑器列表
|
|
382
330
|
*/
|
|
@@ -524,16 +472,17 @@ function deployOpsxSkills(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
|
524
472
|
|
|
525
473
|
for (const toolKey of selectedTools) {
|
|
526
474
|
const config = TOOL_CONFIGS[toolKey];
|
|
527
|
-
|
|
475
|
+
const profile = getToolProfile(toolKey);
|
|
476
|
+
if (!config || !profile.skillsDir) continue;
|
|
528
477
|
|
|
529
|
-
const targetSkillsDir = path.join(cwd,
|
|
478
|
+
const targetSkillsDir = path.join(cwd, profile.skillsDir);
|
|
530
479
|
|
|
531
480
|
// 确保 skills 目录存在
|
|
532
481
|
if (!fs.existsSync(targetSkillsDir)) {
|
|
533
482
|
fs.mkdirSync(targetSkillsDir, { recursive: true });
|
|
534
483
|
}
|
|
535
484
|
|
|
536
|
-
console.log(` 部署 ${config.name} 的 opsx skills(${skillDirs.length} 个,扁平到 ${
|
|
485
|
+
console.log(` 部署 ${config.name} 的 opsx skills(${skillDirs.length} 个,扁平到 ${profile.skillsDir}/)...`);
|
|
537
486
|
|
|
538
487
|
for (const skillDir of skillDirs) {
|
|
539
488
|
const sourceDir = path.join(skillsTemplatePath, skillDir);
|
|
@@ -554,6 +503,46 @@ function deployOpsxSkills(selectedTools = Object.keys(TOOL_CONFIGS)) {
|
|
|
554
503
|
return true;
|
|
555
504
|
}
|
|
556
505
|
|
|
506
|
+
/**
|
|
507
|
+
* 按 profile 部署 Agent 专属产物(Kunlun bridge / CodeBuddy hooks / Claude hooks)
|
|
508
|
+
* 仅处理 selectedTools 中的 profile,不触碰未选 Agent 目录。
|
|
509
|
+
*/
|
|
510
|
+
function deployProfileArtifacts(selectedTools = Object.keys(TOOL_CONFIGS), cwd = process.cwd(), pkgPath = getPackagePath()) {
|
|
511
|
+
console.log('🧩 正在部署 Agent 专属产物...');
|
|
512
|
+
|
|
513
|
+
for (const toolKey of selectedTools) {
|
|
514
|
+
const profile = getToolProfile(toolKey);
|
|
515
|
+
const config = TOOL_CONFIGS[toolKey];
|
|
516
|
+
if (!config) continue;
|
|
517
|
+
|
|
518
|
+
if (profile.requiresCommandBridge) {
|
|
519
|
+
cleanupManagedKunlunBridges(profile, cwd);
|
|
520
|
+
const bridgeResult = deployKunlunCommandBridges({
|
|
521
|
+
profile,
|
|
522
|
+
projectRoot: cwd,
|
|
523
|
+
pkgPath,
|
|
524
|
+
skillMetadata: OPSX_SKILL_METADATA,
|
|
525
|
+
});
|
|
526
|
+
console.log(` ✓ ${config.name}: 部署 ${bridgeResult.deployed.length} 个 Kunlun command bridge`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (profile.hookProvider === 'codebuddy') {
|
|
530
|
+
const hookResult = deployCodebuddyHookPack(profile, cwd, pkgPath);
|
|
531
|
+
if (!hookResult.ok) {
|
|
532
|
+
throw new Error(hookResult.error || `CodeBuddy Hook 部署失败: ${profile.configDir}/settings.json`);
|
|
533
|
+
}
|
|
534
|
+
console.log(` ✓ ${config.name}: 部署 Hook Pack(自动生效)`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (profile.hookProvider === 'claude') {
|
|
538
|
+
deployClaudeHookPack([toolKey]);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
console.log('✅ Agent 专属产物部署完成');
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
|
|
557
546
|
/**
|
|
558
547
|
* 部署 Claude Code Hook Pack(可选增强)
|
|
559
548
|
* Hook 只调用 skywalk-sdd/log.cjs,不写私有日志,不替代 OPSX 模板采集。
|
|
@@ -780,6 +769,30 @@ function initGlobalOverview() {
|
|
|
780
769
|
return true;
|
|
781
770
|
}
|
|
782
771
|
|
|
772
|
+
/**
|
|
773
|
+
* 部署 SDD 操作手册(HTML)到 openspec 目录
|
|
774
|
+
*/
|
|
775
|
+
function deploySddGuideManual(cwd = process.cwd(), pkgPath = getPackagePath()) {
|
|
776
|
+
console.log('📖 正在部署 SDD 操作手册...');
|
|
777
|
+
|
|
778
|
+
const source = path.join(pkgPath, 'kld-sdd-guide.html');
|
|
779
|
+
const targetDir = path.join(cwd, 'openspec');
|
|
780
|
+
const targetFile = path.join(targetDir, 'kld-sdd操作手册.html');
|
|
781
|
+
|
|
782
|
+
if (!fs.existsSync(source)) {
|
|
783
|
+
console.log('⚠️ 操作手册源文件不存在,跳过: kld-sdd-guide.html');
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (!fs.existsSync(targetDir)) {
|
|
788
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
fs.copyFileSync(source, targetFile);
|
|
792
|
+
console.log(`✅ SDD 操作手册已部署: openspec/kld-sdd操作手册.html`);
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
|
|
783
796
|
/**
|
|
784
797
|
* 部署 SDD Telemetry 数据目录
|
|
785
798
|
* 创建本地数据目录用于存储事件和活跃状态(最终报告落 openspec/changes/<change>/reports/,不再写 skywalk-sdd/reports/)
|
|
@@ -817,6 +830,15 @@ function deployTelemetryDataDir() {
|
|
|
817
830
|
console.log(` ⚠️ Telemetry CLI 源文件缺失: ${telemetrySrc}`);
|
|
818
831
|
}
|
|
819
832
|
|
|
833
|
+
const ontologySrc = path.join(pkgPath, 'skywalk-sdd', 'ontology');
|
|
834
|
+
const ontologyDst = path.join(dataDir, 'ontology');
|
|
835
|
+
if (fs.existsSync(ontologySrc)) {
|
|
836
|
+
copyDir(ontologySrc, ontologyDst);
|
|
837
|
+
console.log(' ✓ 部署 skywalk-sdd/ontology/(本地本体语义运行时)');
|
|
838
|
+
} else {
|
|
839
|
+
console.log(` ⚠️ 本体语义运行时缺失: ${ontologySrc}`);
|
|
840
|
+
}
|
|
841
|
+
|
|
820
842
|
const worktreeFinishSrc = path.join(pkgPath, 'skywalk-sdd', 'apply-worktree-finish.cjs');
|
|
821
843
|
const worktreeFinishDst = path.join(dataDir, 'apply-worktree-finish.cjs');
|
|
822
844
|
if (fs.existsSync(worktreeFinishSrc)) {
|
|
@@ -824,7 +846,7 @@ function deployTelemetryDataDir() {
|
|
|
824
846
|
console.log(' ✓ 部署 skywalk-sdd/apply-worktree-finish.cjs(Apply 收尾脚本)');
|
|
825
847
|
}
|
|
826
848
|
|
|
827
|
-
console.log(' ✓ 调用方式: node skywalk-sdd/log.cjs start|end|metrics');
|
|
849
|
+
console.log(' ✓ 调用方式: node skywalk-sdd/log.cjs start|end|metrics|semantic-identity|semantic-reconcile|semantic-check');
|
|
828
850
|
console.log(' ✓ Apply worktree: record-base 在 §1.5;收尾 apply-worktree-finish.cjs --change=<name>');
|
|
829
851
|
console.log('✅ Telemetry 已就绪(数据存储在 skywalk-sdd/,无需配置 MCP)');
|
|
830
852
|
return true;
|
|
@@ -1036,7 +1058,7 @@ async function main() {
|
|
|
1036
1058
|
} catch (error) {
|
|
1037
1059
|
console.error(`❌ ${error.message}`);
|
|
1038
1060
|
rl.close();
|
|
1039
|
-
|
|
1061
|
+
process.exit(1);
|
|
1040
1062
|
}
|
|
1041
1063
|
|
|
1042
1064
|
// 1. 运行 openspec init(传入用户选择的工具)
|
|
@@ -1057,8 +1079,8 @@ async function main() {
|
|
|
1057
1079
|
// 3.2 清理原生 openspec-* skills(防止残留)
|
|
1058
1080
|
cleanupNativeOpenspecSkills(selectedTools);
|
|
1059
1081
|
|
|
1060
|
-
// 3.3 部署
|
|
1061
|
-
|
|
1082
|
+
// 3.3 部署 Agent 专属产物(Kunlun bridge / CodeBuddy hooks / Claude hooks)
|
|
1083
|
+
deployProfileArtifacts(selectedTools, process.cwd(), getPackagePath());
|
|
1062
1084
|
|
|
1063
1085
|
// 5. 部署 SDD Telemetry 数据目录
|
|
1064
1086
|
deployTelemetryDataDir();
|
|
@@ -1071,6 +1093,9 @@ async function main() {
|
|
|
1071
1093
|
|
|
1072
1094
|
// 7. 初始化全局架构约束 (overview.md)
|
|
1073
1095
|
initGlobalOverview();
|
|
1096
|
+
|
|
1097
|
+
// 7.1 部署 SDD 操作手册(HTML)
|
|
1098
|
+
deploySddGuideManual();
|
|
1074
1099
|
}
|
|
1075
1100
|
|
|
1076
1101
|
// 7. 更新 .gitignore
|
|
@@ -1087,8 +1112,16 @@ async function main() {
|
|
|
1087
1112
|
console.log();
|
|
1088
1113
|
console.log('已生成/覆盖:');
|
|
1089
1114
|
console.log(' 🌐 openspec/specs/overview.md # 全局架构约束(数据字典、接口规范)');
|
|
1115
|
+
console.log(' 📖 openspec/kld-sdd操作手册.html # SDD 操作手册(HTML)');
|
|
1090
1116
|
console.log(' 📄 openspec-templates/ # 标准文档模版(参考用)');
|
|
1091
1117
|
console.log(' 🎯 .*/skills/opsx-*/ # SDD skills(扁平一层)');
|
|
1118
|
+
if (selectedTools.includes('kunlunzhima')) {
|
|
1119
|
+
console.log(' 📎 .kunlunzhima/commands/opsx/ # KunlunZhima OPSX command bridge(11 个)');
|
|
1120
|
+
console.log(' ℹ️ KunlunZhima 通过 commands/skills 入口使用 SDD,未启用自动 Hook');
|
|
1121
|
+
}
|
|
1122
|
+
if (selectedTools.includes('codebuddy')) {
|
|
1123
|
+
console.log(' 🪝 .codebuddy/hooks/ # CodeBuddy SDD Hook Pack(自动生效)');
|
|
1124
|
+
}
|
|
1092
1125
|
if (selectedTools.includes('claude')) {
|
|
1093
1126
|
console.log(' 🪝 .claude/hooks/ # Claude Code SDD Hook Pack(可选增强)');
|
|
1094
1127
|
}
|
|
@@ -1098,7 +1131,7 @@ async function main() {
|
|
|
1098
1131
|
console.log();
|
|
1099
1132
|
|
|
1100
1133
|
// 统一的 skill 格式说明
|
|
1101
|
-
console.log('可用 skills(opsx-* 系列,
|
|
1134
|
+
console.log('可用 skills(opsx-* 系列,11 个 SDD skills):');
|
|
1102
1135
|
console.log(' opsx-propose - 创建业务意图文档(Why)');
|
|
1103
1136
|
console.log(' opsx-spec - 创建技术契约文档(What)');
|
|
1104
1137
|
console.log(' opsx-design - 创建技术实现方案(How)');
|
|
@@ -1109,6 +1142,7 @@ async function main() {
|
|
|
1109
1142
|
console.log(' opsx-archive - 归档变更(Archive)');
|
|
1110
1143
|
console.log(' opsx-explore - 浏览变更状态(Explore)');
|
|
1111
1144
|
console.log(' opsx-knowledge - 业务知识库检索(辅助)');
|
|
1145
|
+
console.log(' opsx-rules - 生成/更新 Agent 规则(辅助)');
|
|
1112
1146
|
|
|
1113
1147
|
console.log();
|
|
1114
1148
|
console.log('后续步骤:');
|
|
@@ -1116,6 +1150,15 @@ async function main() {
|
|
|
1116
1150
|
console.log(' 2. 按顺序执行 propose → spec → design → task');
|
|
1117
1151
|
console.log(' 3. 激活 opsx-check 验证文档质量');
|
|
1118
1152
|
console.log(' 4. 激活 opsx-apply 申请实施,opsx-archive 归档完成');
|
|
1153
|
+
if (selectedTools.includes('codebuddy')) {
|
|
1154
|
+
console.log(' 5. CodeBuddy 用户:SDD Hook Pack 已随初始化自动部署,门禁自动生效,无需手动配置');
|
|
1155
|
+
}
|
|
1156
|
+
if (selectedTools.includes('kunlunzhima')) {
|
|
1157
|
+
console.log(' 5. KunlunZhima 用户:在 Craft 模式使用 /opsx:propose 等 OPSX 命令(.kunlunzhima/commands/opsx/)');
|
|
1158
|
+
}
|
|
1159
|
+
if (selectedTools.includes('opencode')) {
|
|
1160
|
+
console.log(' 5. OpenCode 用户:用 /opsx-propose 或 /skills 启动 SDD(.opencode/skills/opsx-*/,skill 即斜杠命令)');
|
|
1161
|
+
}
|
|
1119
1162
|
console.log();
|
|
1120
1163
|
console.log('📊 SDD Telemetry(嵌入在 skill 流程中,自动执行,无需配置):');
|
|
1121
1164
|
console.log(' - 度量数据自动采集到 skywalk-sdd/events/');
|
|
@@ -1125,4 +1168,16 @@ async function main() {
|
|
|
1125
1168
|
rl.close();
|
|
1126
1169
|
}
|
|
1127
1170
|
|
|
1128
|
-
module.exports = {
|
|
1171
|
+
module.exports = {
|
|
1172
|
+
main,
|
|
1173
|
+
detectTools,
|
|
1174
|
+
getTemplatePath,
|
|
1175
|
+
getPackagePath,
|
|
1176
|
+
selectEditor,
|
|
1177
|
+
deployOpsxSkills,
|
|
1178
|
+
deployProfileArtifacts,
|
|
1179
|
+
deploySddGuideManual,
|
|
1180
|
+
copyDirRendered,
|
|
1181
|
+
parseSelectedTools,
|
|
1182
|
+
TOOL_CONFIGS,
|
|
1183
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conservative merge for CodeBuddy .codebuddy/settings.json hooks.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const SDD_HOOK_COMMAND_RE = /(?:^|\s|["'])\$?CODEBUDDY_PROJECT_DIR[\\/]+\.codebuddy[\\/]+hooks[\\/]+sdd-[\w-]+\.cjs(?=$|\s|["'])/i;
|
|
9
|
+
|
|
10
|
+
function hookCommandText(hook) {
|
|
11
|
+
const args = Array.isArray(hook.args) ? hook.args : [];
|
|
12
|
+
return [hook.command, ...args].filter(Boolean).join(' ').replace(/\s+/g, ' ').trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isSddCodebuddyHook(hook) {
|
|
16
|
+
return SDD_HOOK_COMMAND_RE.test(hookCommandText(hook));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function sameHookCommand(left, right) {
|
|
20
|
+
return hookCommandText(left) === hookCommandText(right) &&
|
|
21
|
+
String(left.type || '') === String(right.type || '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function removeManagedSddHooks(entries) {
|
|
25
|
+
return entries
|
|
26
|
+
.map(entry => {
|
|
27
|
+
if (!Array.isArray(entry.hooks)) {
|
|
28
|
+
return entry;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...entry,
|
|
32
|
+
hooks: entry.hooks.filter(hook => !isSddCodebuddyHook(hook)),
|
|
33
|
+
};
|
|
34
|
+
})
|
|
35
|
+
.filter(entry => !Array.isArray(entry.hooks) || entry.hooks.length > 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function mergeCodebuddySettings(settingsPath, templateSettings) {
|
|
39
|
+
const displayPath = path.basename(settingsPath);
|
|
40
|
+
let originalBytes = '';
|
|
41
|
+
if (fs.existsSync(settingsPath)) {
|
|
42
|
+
originalBytes = fs.readFileSync(settingsPath, 'utf8');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let existingSettings = {};
|
|
46
|
+
if (originalBytes) {
|
|
47
|
+
try {
|
|
48
|
+
existingSettings = JSON.parse(originalBytes);
|
|
49
|
+
} catch {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
error: `${displayPath} 不是合法 JSON,请修复后重试(路径: ${settingsPath})`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const merged = { ...existingSettings };
|
|
58
|
+
merged.hooks = merged.hooks || {};
|
|
59
|
+
|
|
60
|
+
for (const [eventName, entries] of Object.entries(templateSettings.hooks || {})) {
|
|
61
|
+
merged.hooks[eventName] = removeManagedSddHooks(merged.hooks[eventName] || []);
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const templateHooks = entry.hooks || [];
|
|
64
|
+
const exists = merged.hooks[eventName].some(existingEntry => {
|
|
65
|
+
const existingHooks = existingEntry.hooks || [];
|
|
66
|
+
return templateHooks.every(templateHook =>
|
|
67
|
+
existingHooks.some(existingHook => sameHookCommand(existingHook, templateHook))
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
if (!exists) {
|
|
71
|
+
merged.hooks[eventName].push(entry);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fs.writeFileSync(settingsPath, JSON.stringify(merged, null, 2) + '\n', 'utf8');
|
|
77
|
+
return { ok: true };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
mergeCodebuddySettings,
|
|
82
|
+
isSddCodebuddyHook,
|
|
83
|
+
hookCommandText,
|
|
84
|
+
removeManagedSddHooks,
|
|
85
|
+
};
|
package/lib/skills-bundle.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* SDD skills bundle 共享配置与模板渲染
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
5
8
|
const SKILLS_BUNDLE_NAME = 'kld-sdd';
|
|
6
9
|
|
|
7
10
|
const OPSX_SKILL_DIRS = [
|
|
@@ -14,17 +17,151 @@ const OPSX_SKILL_DIRS = [
|
|
|
14
17
|
'opsx-test',
|
|
15
18
|
'opsx-archive',
|
|
16
19
|
'opsx-explore',
|
|
17
|
-
'opsx-knowledge'
|
|
20
|
+
'opsx-knowledge',
|
|
21
|
+
'opsx-rules',
|
|
18
22
|
];
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
25
|
+
|
|
26
|
+
function commandNameFromSkillDir(skillDir) {
|
|
27
|
+
return skillDir.replace(/^opsx-/, '');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function displayNameFromCommandName(commandName) {
|
|
31
|
+
return `OPSX: ${commandName.charAt(0).toUpperCase() + commandName.slice(1)}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseSkillFrontmatter(content) {
|
|
35
|
+
const stripped = content.replace(/^\uFEFF/, '');
|
|
36
|
+
if (!stripped.startsWith('---')) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const end = stripped.indexOf('---', 3);
|
|
41
|
+
if (end < 0) {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const block = stripped.slice(3, end).trim();
|
|
46
|
+
const result = {};
|
|
47
|
+
|
|
48
|
+
for (const rawLine of block.split('\n')) {
|
|
49
|
+
const line = rawLine.replace(/\r$/, '');
|
|
50
|
+
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
51
|
+
if (!match) continue;
|
|
52
|
+
const key = match[1];
|
|
53
|
+
let value = match[2].trim();
|
|
54
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
55
|
+
value = value.slice(1, -1);
|
|
56
|
+
}
|
|
57
|
+
result[key] = value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildOpsxSkillMetadata(pkgPath = PKG_ROOT) {
|
|
64
|
+
const skillsTemplateDir = path.join(pkgPath, 'templates', 'skills', SKILLS_BUNDLE_NAME);
|
|
65
|
+
|
|
66
|
+
return OPSX_SKILL_DIRS.map((skillDir) => {
|
|
67
|
+
const commandName = commandNameFromSkillDir(skillDir);
|
|
68
|
+
const skillMdPath = path.join(skillsTemplateDir, skillDir, 'SKILL.md');
|
|
69
|
+
let description = '';
|
|
70
|
+
let argumentHint = '';
|
|
71
|
+
|
|
72
|
+
if (fs.existsSync(skillMdPath)) {
|
|
73
|
+
const frontmatter = parseSkillFrontmatter(fs.readFileSync(skillMdPath, 'utf8'));
|
|
74
|
+
description = frontmatter.description || '';
|
|
75
|
+
argumentHint = frontmatter['argument-hint'] || '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
skillDir,
|
|
80
|
+
commandName,
|
|
81
|
+
displayName: displayNameFromCommandName(commandName),
|
|
82
|
+
description,
|
|
83
|
+
argumentHint,
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const OPSX_SKILL_METADATA = buildOpsxSkillMetadata(PKG_ROOT);
|
|
89
|
+
|
|
90
|
+
const KUNLUN_SLASH_COMMANDS = [
|
|
91
|
+
'propose',
|
|
92
|
+
'spec',
|
|
93
|
+
'design',
|
|
94
|
+
'task',
|
|
95
|
+
'check',
|
|
96
|
+
'apply',
|
|
97
|
+
'test',
|
|
98
|
+
'archive',
|
|
99
|
+
'explore',
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
function transformKunlunSlashCommands(content) {
|
|
103
|
+
if (typeof content !== 'string') {
|
|
104
|
+
return content;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let result = content;
|
|
108
|
+
for (const cmd of KUNLUN_SLASH_COMMANDS) {
|
|
109
|
+
result = result.replace(new RegExp(`/opsx-${cmd}(?![a-z-])`, 'g'), `/opsx:${cmd}`);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function replaceProfileTokens(content, profile) {
|
|
115
|
+
if (!profile) {
|
|
116
|
+
return content;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const vars = profile.templateVariables || {};
|
|
21
120
|
return content
|
|
22
|
-
.replace(
|
|
23
|
-
.replace(
|
|
121
|
+
.replace(/\$\{SKILL_RUNTIME_DIR\}/g, vars.SKILL_RUNTIME_DIR || profile.skillsDir || '')
|
|
122
|
+
.replace(/\$\{HOOK_GATE_DESCRIPTION\}/g, vars.HOOK_GATE_DESCRIPTION || '')
|
|
123
|
+
.replace(/\$\{SHELL_GUIDANCE\}/g, vars.SHELL_GUIDANCE || '')
|
|
124
|
+
.replace(/\$\{AGENT_SKILL_DIR\}/g, profile.skillsDir || '');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function renderTemplate(content, config, toolKey, profile) {
|
|
128
|
+
let rendered = replaceProfileTokens(
|
|
129
|
+
content
|
|
130
|
+
.replace(/^\uFEFF/, '')
|
|
131
|
+
.replace(/<Agent(?:\u7c7b\u578b|\u7eeb\u8bf2\u7037)>/g, config.agentType || toolKey),
|
|
132
|
+
profile
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (toolKey === 'kunlunzhima') {
|
|
136
|
+
rendered = transformKunlunSlashCommands(rendered);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return rendered;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function validateRenderedContent(content, profile, context = '') {
|
|
143
|
+
const unresolved = (content.match(/\$\{[A-Z_]+\}/g) || [])
|
|
144
|
+
// Claude Code 运行时注入,部署时保留字面量
|
|
145
|
+
.filter((token) => token !== '${CLAUDE_SKILL_DIR}');
|
|
146
|
+
if (unresolved.length > 0) {
|
|
147
|
+
const unique = [...new Set(unresolved)];
|
|
148
|
+
const prefix = context ? `${context}: ` : '';
|
|
149
|
+
throw new Error(`${prefix}未解析的模板 token: ${unique.join(', ')}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return true;
|
|
24
153
|
}
|
|
25
154
|
|
|
26
155
|
module.exports = {
|
|
27
156
|
SKILLS_BUNDLE_NAME,
|
|
28
157
|
OPSX_SKILL_DIRS,
|
|
29
|
-
|
|
158
|
+
OPSX_SKILL_METADATA,
|
|
159
|
+
buildOpsxSkillMetadata,
|
|
160
|
+
commandNameFromSkillDir,
|
|
161
|
+
displayNameFromCommandName,
|
|
162
|
+
parseSkillFrontmatter,
|
|
163
|
+
renderTemplate,
|
|
164
|
+
replaceProfileTokens,
|
|
165
|
+
transformKunlunSlashCommands,
|
|
166
|
+
validateRenderedContent,
|
|
30
167
|
};
|