kld-sdd 2.6.7 → 2.6.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 (39) hide show
  1. package/bin/kld-sdd-init.js +39 -3
  2. package/lib/init.js +320 -45
  3. package/lib/workspace-layout.js +2 -0
  4. package/package.json +2 -2
  5. package/skywalk-sdd/context-client.cjs +59 -5
  6. package/skywalk-sdd/ontology/active-changes.cjs +297 -0
  7. package/skywalk-sdd/ontology/change-key.cjs +241 -0
  8. package/skywalk-sdd/ontology/cli.cjs +135 -0
  9. package/skywalk-sdd/ontology/list-changes.cjs +110 -0
  10. package/skywalk-sdd/ontology/modules.cjs +167 -0
  11. package/skywalk-sdd/ontology/naming-diagnose.cjs +594 -0
  12. package/skywalk-sdd/ontology/sdd-config.cjs +335 -0
  13. package/skywalk-sdd/ontology/workspace-layout.cjs +194 -0
  14. package/templates/dot-sdd.yaml +8 -0
  15. package/templates/git-hooks/commit-msg-sdd-trailer.cjs +224 -0
  16. package/templates/modules.yaml +13 -0
  17. package/templates/openspec/proposal.md +7 -1
  18. package/templates/sdd.config.yaml +12 -0
  19. package/templates/skills/kld-sdd/openspec-sync-specs/SKILL.md +148 -0
  20. package/templates/skills/kld-sdd/openspec-update-change/SKILL.md +86 -0
  21. package/templates/skills/kld-sdd/opsx-apply/SKILL.md +3 -3
  22. package/templates/skills/kld-sdd/opsx-apply/checklist.md +1 -1
  23. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +11 -1
  24. package/templates/skills/kld-sdd/opsx-check/SKILL.md +73 -3
  25. package/templates/skills/kld-sdd/opsx-design/SKILL.md +9 -0
  26. package/templates/skills/kld-sdd/opsx-explore/SKILL.md +37 -17
  27. package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +9 -14
  28. package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +83 -109
  29. package/templates/skills/kld-sdd/opsx-ontology-query/phase-1-prechange.md +276 -0
  30. package/templates/skills/kld-sdd/opsx-ontology-query/phase-2-during.md +354 -0
  31. package/templates/skills/kld-sdd/opsx-ontology-query/phase-3-postchange.md +223 -0
  32. package/templates/skills/kld-sdd/opsx-ontology-query/phase-4-explore.md +240 -0
  33. package/templates/skills/kld-sdd/opsx-ontology-query/phase-5-governance.md +232 -0
  34. package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +92 -4
  35. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +87 -16
  36. package/templates/skills/kld-sdd/opsx-propose/checklist.md +1 -0
  37. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +33 -3
  38. package/templates/skills/kld-sdd/opsx-task/SKILL.md +10 -0
  39. package/templates/skills/kld-sdd/opsx-tdd-core/checklist.md +1 -1
@@ -3,17 +3,53 @@
3
3
  /**
4
4
  * KLD SDD 项目初始化工具
5
5
  * 一键配置团队标准化开发环境,内置 openSpec 模版
6
- *
6
+ *
7
7
  * 子命令:
8
- * (无参数) 运行项目初始化
9
- * log SDD Telemetry CLI(记录阶段事件、查询指标)
8
+ * (无参数) 运行项目初始化
9
+ * log SDD Telemetry CLI(记录阶段事件、查询指标)
10
+ * link-spec 将代码仓关联到本地 spec clone(git config sdd.specPath)
11
+ * sync-repos 工作目录下发现新增代码仓并轻量接入(不装 skills)
10
12
  */
11
13
 
14
+ const path = require('path');
12
15
  const args = process.argv.slice(2);
13
16
 
14
17
  if (args[0] === 'log') {
15
18
  // Telemetry CLI 模式
16
19
  require('../skywalk-sdd/index.js').main();
20
+ } else if (args[0] === 'link-spec') {
21
+ const sddConfig = require('../skywalk-sdd/ontology/sdd-config.cjs');
22
+ const pathArg = args.find((item) => item.startsWith('--path='));
23
+ const projectArg = args.find((item) => item.startsWith('--project='));
24
+ const positional = args.slice(1).find((item) => !item.startsWith('--'));
25
+ const specPath = pathArg ? pathArg.slice('--path='.length) : positional;
26
+ const projectRoot = path.resolve(projectArg ? projectArg.slice('--project='.length) : '.');
27
+ if (!specPath) {
28
+ console.error('用法: kld-sdd link-spec --path=<spec-clone> [--project=.]');
29
+ process.exit(1);
30
+ }
31
+ const result = sddConfig.setSpecPath(projectRoot, specPath);
32
+ if (!result.ok) {
33
+ console.error(`❌ ${result.message}`);
34
+ process.exit(1);
35
+ }
36
+ console.log(`✅ 已设置 git config --local sdd.specPath = ${result.path}`);
37
+ } else if (args[0] === 'sync-repos') {
38
+ const { syncWorkspaceRepos } = require('../lib/init');
39
+ const projectArg = args.find((item) => item.startsWith('--project='));
40
+ const workspaceRoot = path.resolve(projectArg ? projectArg.slice('--project='.length) : '.');
41
+ const all = args.includes('--all');
42
+ const result = syncWorkspaceRepos(workspaceRoot, { onlyNew: !all });
43
+ if (!result.ok && result.attached.length === 0 && (!result.layout || !result.layout.specRepo)) {
44
+ process.exit(1);
45
+ }
46
+ if (result.failed && result.failed.length) {
47
+ for (const item of result.failed) {
48
+ console.error(`❌ ${item.repo}: ${item.message}`);
49
+ }
50
+ process.exit(1);
51
+ }
52
+ console.log(`✅ sync-repos 完成,接入 ${result.attached.length} 个代码仓`);
17
53
  } else {
18
54
  // 正常初始化模式
19
55
  const { main } = require('../lib/init');
package/lib/init.js CHANGED
@@ -28,6 +28,8 @@ const {
28
28
  BRIDGE_MARKER,
29
29
  } = require('./command-bridge');
30
30
  const { deployCodebuddyHookPack } = require('./deploy-codebuddy-hooks');
31
+ const workspaceLayout = require('./workspace-layout');
32
+ const sddConfig = require('../skywalk-sdd/ontology/sdd-config.cjs');
31
33
 
32
34
  const rl = readline.createInterface({
33
35
  input: process.stdin,
@@ -797,11 +799,11 @@ function deploySddGuideManual(cwd = process.cwd(), pkgPath = getPackagePath()) {
797
799
  * 部署 SDD Telemetry 数据目录
798
800
  * 创建本地数据目录用于存储事件和活跃状态(最终报告落 openspec/changes/<change>/reports/,不再写 skywalk-sdd/reports/)
799
801
  */
800
- function deployTelemetryDataDir() {
802
+ function deployTelemetryDataDir(targetCwd = process.cwd()) {
801
803
  console.log('📊 正在初始化 SDD Telemetry 数据目录...');
802
804
 
803
805
  const pkgPath = getPackagePath();
804
- const cwd = process.cwd();
806
+ const cwd = path.resolve(targetCwd);
805
807
  const dataDir = path.join(cwd, 'skywalk-sdd');
806
808
 
807
809
  if (!fs.existsSync(dataDir)) {
@@ -901,16 +903,22 @@ function installGitHookShim(cwd, hookName, scriptPath) {
901
903
  /**
902
904
  * 部署 Git hooks / CI 兜底模板
903
905
  * 这些脚本只做质量门禁和 CI 补充记录,不替代 OPSX 主采集链路。
906
+ *
907
+ * @param {string} [targetCwd]
908
+ * @param {{ mode?: 'full'|'commit-msg-only' }} [options]
909
+ * full: spec 仓(pre-commit/pre-push/commit-msg + CI)
910
+ * commit-msg-only: 代码仓(只装 Trailer Hook,不装 skills、不装文档门禁)
904
911
  */
905
- function deployQualityGateTemplates() {
912
+ function deployQualityGateTemplates(targetCwd = process.cwd(), options = {}) {
906
913
  const pkgPath = getPackagePath();
907
- const cwd = process.cwd();
914
+ const cwd = path.resolve(targetCwd);
915
+ const mode = options.mode || 'full';
908
916
  // U4: 非 Git 项目跳过 Git hooks / CI 兜底模板(按需部署,不污染非 Git 项目根)
909
917
  if (!fs.existsSync(path.join(cwd, '.git'))) {
910
918
  console.log('🧰 非 Git 项目,跳过 Git hooks / CI 兜底模板部署');
911
919
  return false;
912
920
  }
913
- console.log('🧰 正在部署 SDD Git hooks / CI 兜底模板...');
921
+ console.log(`🧰 正在部署 SDD Git hooks(${mode}): ${cwd}`);
914
922
  const dataDir = path.join(cwd, 'skywalk-sdd');
915
923
  const gitHooksTemplateDir = path.join(pkgPath, 'templates', 'git-hooks');
916
924
  const ciTemplateDir = path.join(pkgPath, 'templates', 'ci');
@@ -924,17 +932,162 @@ function deployQualityGateTemplates() {
924
932
  console.log(` ⚠️ Git hooks 模板缺失: ${gitHooksTemplateDir}`);
925
933
  }
926
934
 
927
- if (fs.existsSync(ciTemplateDir)) {
935
+ if (mode === 'full' && fs.existsSync(ciTemplateDir)) {
928
936
  copyDir(ciTemplateDir, targetCiDir);
929
937
  console.log(' ✓ 部署 skywalk-sdd/ci/');
938
+ }
939
+
940
+ if (mode === 'full') {
941
+ installGitHookShim(cwd, 'pre-commit', 'skywalk-sdd/git-hooks/pre-commit-sdd-check.cjs');
942
+ installGitHookShim(cwd, 'pre-push', 'skywalk-sdd/git-hooks/pre-push-sdd-check.cjs');
943
+ }
944
+ installGitHookShim(cwd, 'commit-msg', 'skywalk-sdd/git-hooks/commit-msg-sdd-trailer.cjs');
945
+
946
+ console.log('✅ Git hooks 已就绪');
947
+ return true;
948
+ }
949
+
950
+ /**
951
+ * 代码仓轻量接入:只部署 Hook 运行所需 ontology + commit-msg,不部署 skills。
952
+ */
953
+ function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
954
+ const cwd = path.resolve(codeRepoRoot);
955
+ const specRoot = path.resolve(specRepoRoot);
956
+ const pkgPath = options.pkgPath || getPackagePath();
957
+ const label = path.basename(cwd);
958
+
959
+ if (!workspaceLayout.isGitRepo(cwd)) {
960
+ return { ok: false, repo: label, message: '不是 Git 仓库' };
961
+ }
962
+ if (!workspaceLayout.isGitRepo(specRoot)) {
963
+ return { ok: false, repo: label, message: `spec 路径不是 Git 仓库: ${specRoot}` };
964
+ }
965
+
966
+ console.log(`🔗 轻量接入代码仓(无 skills): ${label}`);
967
+
968
+ // Hook 依赖 ontology + git-hooks;不部署完整 telemetry/skills
969
+ const dataDir = path.join(cwd, 'skywalk-sdd');
970
+ const ontologySrc = path.join(pkgPath, 'skywalk-sdd', 'ontology');
971
+ const ontologyDst = path.join(dataDir, 'ontology');
972
+ if (fs.existsSync(ontologySrc)) {
973
+ copyDir(ontologySrc, ontologyDst);
974
+ }
975
+ deployQualityGateTemplates(cwd, { mode: 'commit-msg-only' });
976
+
977
+ const remote = sddConfig.gitRemoteUrl(specRoot) || 'git@gitlab.example.com:biz/xxx-sdd-specs.git';
978
+ const sddYamlPath = path.join(cwd, '.sdd.yaml');
979
+ if (!fs.existsSync(sddYamlPath)) {
980
+ fs.writeFileSync(
981
+ sddYamlPath,
982
+ [
983
+ '# 代码仓库关联团队 spec Git 仓库(个人绝对路径勿写入本文件)',
984
+ 'version: 1',
985
+ `spec_repository: ${remote}`,
986
+ '',
987
+ ].join('\n'),
988
+ 'utf8',
989
+ );
990
+ console.log(` ✓ 写入 .sdd.yaml → ${remote}`);
930
991
  } else {
931
- console.log(` ⚠️ CI 模板缺失: ${ciTemplateDir}`);
992
+ console.log(' .sdd.yaml 已存在,跳过覆盖');
993
+ }
994
+
995
+ const linked = sddConfig.setSpecPath(cwd, specRoot);
996
+ if (!linked.ok) {
997
+ return { ok: false, repo: label, message: linked.message };
932
998
  }
999
+ console.log(` ✓ sdd.specPath = ${linked.path}`);
933
1000
 
934
- installGitHookShim(cwd, 'pre-commit', 'skywalk-sdd/git-hooks/pre-commit-sdd-check.cjs');
935
- installGitHookShim(cwd, 'pre-push', 'skywalk-sdd/git-hooks/pre-push-sdd-check.cjs');
1001
+ return { ok: true, repo: label, path: cwd, specPath: linked.path };
1002
+ }
936
1003
 
937
- console.log('✅ Git hooks / CI 兜底模板已就绪');
1004
+ /**
1005
+ * 部署 modules.yaml 到 spec 仓库根目录(若不存在)
1006
+ */
1007
+ function deployModulesYaml(cwd = process.cwd(), pkgPath = getPackagePath()) {
1008
+ console.log('📦 正在检查 modules.yaml...');
1009
+ const source = path.join(pkgPath, 'templates', 'modules.yaml');
1010
+ const target = path.join(cwd, 'modules.yaml');
1011
+ if (!fs.existsSync(source)) {
1012
+ console.log(`⚠️ modules.yaml 模板缺失: ${source}`);
1013
+ return false;
1014
+ }
1015
+ if (fs.existsSync(target)) {
1016
+ console.log(' ✓ modules.yaml 已存在,跳过');
1017
+ return true;
1018
+ }
1019
+ fs.copyFileSync(source, target);
1020
+ console.log(`✅ 已创建 modules.yaml: ${target}`);
1021
+ return true;
1022
+ }
1023
+
1024
+ /**
1025
+ * 部署 sdd.config.yaml(活动变更登记表)到 spec 仓库根目录(若不存在)
1026
+ * propose 隐式写入、archive 隐式移除;代码仓 AI 与 commit-msg Hook 读取
1027
+ */
1028
+ function deploySddConfigYaml(cwd = process.cwd(), pkgPath = getPackagePath()) {
1029
+ console.log('📦 正在检查 sdd.config.yaml...');
1030
+ const source = path.join(pkgPath, 'templates', 'sdd.config.yaml');
1031
+ const target = path.join(cwd, 'sdd.config.yaml');
1032
+ if (!fs.existsSync(source)) {
1033
+ console.log(`⚠️ sdd.config.yaml 模板缺失: ${source}`);
1034
+ return false;
1035
+ }
1036
+ if (fs.existsSync(target)) {
1037
+ console.log(' ✓ sdd.config.yaml 已存在,跳过');
1038
+ return true;
1039
+ }
1040
+ fs.copyFileSync(source, target);
1041
+ console.log(`✅ 已创建 sdd.config.yaml: ${target}`);
1042
+ return true;
1043
+ }
1044
+
1045
+ /**
1046
+ * 部署 .sdd.yaml 示例到代码仓库(若不存在且不像 spec 仓)
1047
+ * spec 仓特征:存在 openspec/changes 或 modules.yaml
1048
+ */
1049
+ function deployDotSddYaml(cwd = process.cwd(), pkgPath = getPackagePath()) {
1050
+ const hasSpecLayout =
1051
+ fs.existsSync(path.join(cwd, 'openspec', 'changes')) ||
1052
+ fs.existsSync(path.join(cwd, 'modules.yaml'));
1053
+ if (hasSpecLayout) {
1054
+ return false;
1055
+ }
1056
+ const source = path.join(pkgPath, 'templates', 'dot-sdd.yaml');
1057
+ const target = path.join(cwd, '.sdd.yaml');
1058
+ if (!fs.existsSync(source) || fs.existsSync(target)) {
1059
+ return false;
1060
+ }
1061
+ console.log('🔗 检测到代码仓库布局,部署 .sdd.yaml 示例...');
1062
+ fs.copyFileSync(source, target);
1063
+ console.log(`✅ 已创建 .sdd.yaml(请修改 spec_repository): ${target}`);
1064
+ return true;
1065
+ }
1066
+
1067
+ /**
1068
+ * 单仓(openspec 与代码同仓)声明 layout: mono。
1069
+ * commit-msg Hook 靠它跳过「spec 工作区干净」检查并只写 Spec-Change。
1070
+ */
1071
+ function deployMonoSddYaml(cwd = process.cwd()) {
1072
+ const target = path.join(cwd, '.sdd.yaml');
1073
+ if (fs.existsSync(target)) {
1074
+ const existing = fs.readFileSync(target, 'utf8');
1075
+ if (/^layout:\s*mono\s*$/m.test(existing)) {
1076
+ console.log(' ✓ .sdd.yaml 已声明 layout: mono,跳过');
1077
+ return true;
1078
+ }
1079
+ console.log('⚠️ .sdd.yaml 已存在但未声明 layout: mono;单仓请手动补一行 `layout: mono`');
1080
+ return false;
1081
+ }
1082
+ const lines = [
1083
+ '# 单仓布局:openspec 文档与代码在同一个仓库',
1084
+ '# commit-msg 只写 Spec-Change(spec 与代码同属一次 commit,无需 Spec-Revision 指针)',
1085
+ 'version: 1',
1086
+ 'layout: mono',
1087
+ '',
1088
+ ];
1089
+ fs.writeFileSync(target, lines.join('\n'), 'utf8');
1090
+ console.log(`✅ 已创建 .sdd.yaml(layout: mono): ${target}`);
938
1091
  return true;
939
1092
  }
940
1093
 
@@ -1017,6 +1170,7 @@ KLD SDD 项目初始化工具
1017
1170
  用法:
1018
1171
  kld-sdd-init [选项]
1019
1172
  npx kld-sdd [选项]
1173
+ kld-sdd sync-repos # 工作目录下发现新增代码仓并轻量接入
1020
1174
 
1021
1175
  选项:
1022
1176
  -h, --help 显示帮助信息
@@ -1025,13 +1179,61 @@ KLD SDD 项目初始化工具
1025
1179
  --skip-template 跳过复制内置模版
1026
1180
  --tool <name> 指定编辑器,可用值: cursor, claude, codebuddy, qoder, opencode, kunlunzhima, workbuddy, codex, all
1027
1181
 
1182
+ 工作区模式(在个人工作目录执行):
1183
+ - skills / 编辑器配置只部署一次到当前工作目录
1184
+ - 自动发现子目录 Git 仓库
1185
+ - spec 仓:modules.yaml / openspec 模板 / 文档门禁
1186
+ - 代码仓:仅 commit-msg Hook + .sdd.yaml + link-spec(不装 skills)
1187
+ - Trailer 数据仍在代码 commit 时由 Hook 写入
1188
+
1028
1189
  示例:
1029
1190
  kld-sdd-init # 完整初始化流程
1030
1191
  kld-sdd-init --skip-openspec # 跳过 openspec,直接部署 skills
1031
1192
  kld-sdd-init --tool codex # 仅部署 Codex 配置
1193
+ kld-sdd sync-repos # 新代码仓加入后同步接入
1032
1194
  `);
1033
1195
  }
1034
1196
 
1197
+ /**
1198
+ * 工作区:为已发现的代码仓做轻量接入,并刷新 .sdd-workspace.yaml
1199
+ */
1200
+ function syncWorkspaceRepos(workspaceRoot = process.cwd(), options = {}) {
1201
+ const root = path.resolve(workspaceRoot);
1202
+ const { layout, newcomers, saved } = workspaceLayout.findNewCodeRepos(root);
1203
+ const onlyNew = options.onlyNew !== false;
1204
+
1205
+ if (!layout.specRepo) {
1206
+ console.log('❌ 未发现 spec 仓库(需子目录含 modules.yaml / openspec/changes / *-sdd-specs)');
1207
+ return { ok: false, attached: [], layout };
1208
+ }
1209
+
1210
+ console.log(`📂 工作目录: ${root}`);
1211
+ console.log(`📄 Spec 仓: ${layout.specRepo.name}`);
1212
+ console.log(`🔎 已发现代码仓: ${layout.codeRepos.map((c) => c.name).join(', ') || '(无)'}`);
1213
+
1214
+ const targets = onlyNew && saved.ok
1215
+ ? newcomers
1216
+ : layout.codeRepos;
1217
+
1218
+ if (onlyNew && saved.ok) {
1219
+ console.log(`🆕 待接入新仓: ${newcomers.map((c) => c.name).join(', ') || '(无)'}`);
1220
+ }
1221
+
1222
+ const attached = [];
1223
+ const failed = [];
1224
+ for (const repo of targets) {
1225
+ const result = attachCodeRepoLite(repo.abs, layout.specRepo.abs);
1226
+ if (result.ok) attached.push(result);
1227
+ else failed.push(result);
1228
+ }
1229
+
1230
+ // 刷新清单(包含本次发现的全部代码仓)
1231
+ const filePath = workspaceLayout.writeWorkspaceFile(root, layout);
1232
+ console.log(`✅ 已更新 ${path.basename(filePath)}`);
1233
+
1234
+ return { ok: failed.length === 0, attached, failed, layout, filePath };
1235
+ }
1236
+
1035
1237
  /**
1036
1238
  * 主函数
1037
1239
  */
@@ -1071,44 +1273,87 @@ async function main() {
1071
1273
  process.exit(1);
1072
1274
  }
1073
1275
 
1074
- // 1. 运行 openspec init(传入用户选择的工具)
1276
+ const workspaceRoot = process.cwd();
1277
+ const layout = workspaceLayout.detectWorkspaceLayout(workspaceRoot);
1278
+ if (layout.isWorkspace) {
1279
+ console.log('📂 检测到个人工作目录布局(子目录 Git 仓库)');
1280
+ console.log(` Spec: ${layout.specRepo ? layout.specRepo.name : '(未发现)'}`);
1281
+ console.log(` 代码仓: ${layout.codeRepos.map((c) => c.name).join(', ') || '(无)'}`);
1282
+ console.log(' → skills 只部署到本工作目录;代码仓仅轻量接入 Hook/关联');
1283
+ console.log();
1284
+ }
1285
+
1286
+ // 1. openspec init:工作区模式下若有 spec 仓则在其内执行
1075
1287
  if (!skipOpenspec) {
1076
- await runOpenspecInit(selectedTools);
1288
+ if (layout.isWorkspace && layout.specRepo) {
1289
+ const prev = process.cwd();
1290
+ try {
1291
+ process.chdir(layout.specRepo.abs);
1292
+ await runOpenspecInit(selectedTools);
1293
+ } finally {
1294
+ process.chdir(prev);
1295
+ }
1296
+ } else {
1297
+ await runOpenspecInit(selectedTools);
1298
+ }
1077
1299
  }
1078
1300
 
1079
1301
  if (!skipTemplate) {
1080
- // 2. 清理原生 openspec 命令(删除 opsx-*.md,避免命名混淆)
1302
+ // 2/3. skills 与编辑器产物:永远只装在当前工作目录一次
1081
1303
  cleanupNativeOpenspecCommands(selectedTools);
1082
-
1083
- // 3. 清理旧版嵌套 skills/kld-sdd/ bundle(Claude Code 无法识别二层目录)
1084
1304
  cleanupLegacyBundledOpsxSkills(selectedTools);
1085
-
1086
- // 3.1 部署 SDD opsx skills(扁平到 .claude/skills/opsx-*/)
1087
1305
  deployOpsxSkills(selectedTools);
1088
-
1089
- // 3.2 清理原生 openspec-* skills(防止残留)
1090
1306
  cleanupNativeOpenspecSkills(selectedTools);
1307
+ deployProfileArtifacts(selectedTools, workspaceRoot, getPackagePath());
1308
+
1309
+ if (layout.isWorkspace) {
1310
+ // 工作目录本身保留一份 telemetry,供 skills 调用
1311
+ deployTelemetryDataDir(workspaceRoot);
1312
+
1313
+ if (layout.specRepo) {
1314
+ const prev = process.cwd();
1315
+ try {
1316
+ process.chdir(layout.specRepo.abs);
1317
+ deployTelemetryDataDir(layout.specRepo.abs);
1318
+ deployQualityGateTemplates(layout.specRepo.abs, { mode: 'full' });
1319
+ deployModulesYaml(layout.specRepo.abs);
1320
+ deploySddConfigYaml(layout.specRepo.abs);
1321
+ copyTemplatesToProject();
1322
+ initGlobalOverview();
1323
+ deploySddGuideManual(layout.specRepo.abs);
1324
+ } finally {
1325
+ process.chdir(prev);
1326
+ }
1091
1327
 
1092
- // 3.3 部署 Agent 专属产物(Kunlun bridge / CodeBuddy hooks / Claude hooks)
1093
- deployProfileArtifacts(selectedTools, process.cwd(), getPackagePath());
1094
-
1095
- // 5. 部署 SDD Telemetry 数据目录
1096
- deployTelemetryDataDir();
1097
-
1098
- // 5.1 部署 Git hooks / CI 兜底模板
1099
- deployQualityGateTemplates();
1100
-
1101
- // 6. 复制标准文档模版到项目(供参考)
1102
- copyTemplatesToProject();
1103
-
1104
- // 7. 初始化全局架构约束 (overview.md)
1105
- initGlobalOverview();
1106
-
1107
- // 7.1 部署 SDD 操作手册(HTML)
1108
- deploySddGuideManual();
1328
+ console.log();
1329
+ console.log('🔗 正在为子代码仓做轻量接入(不安装 skills)...');
1330
+ for (const repo of layout.codeRepos) {
1331
+ attachCodeRepoLite(repo.abs, layout.specRepo.abs);
1332
+ }
1333
+ const wsFile = workspaceLayout.writeWorkspaceFile(workspaceRoot, layout);
1334
+ console.log(`✅ 工作区清单: ${wsFile}`);
1335
+ } else {
1336
+ console.log('⚠️ 未发现 spec 仓,已跳过代码仓 Hook 接入。请先准备 *-sdd-specs 子目录后执行: kld-sdd sync-repos');
1337
+ }
1338
+ } else {
1339
+ // 单仓模式(旧行为):当前目录既是项目根
1340
+ deployTelemetryDataDir(workspaceRoot);
1341
+ deployQualityGateTemplates(workspaceRoot, { mode: 'full' });
1342
+ deployModulesYaml(workspaceRoot);
1343
+ deploySddConfigYaml(workspaceRoot);
1344
+ if (workspaceLayout.isGitRepo(workspaceRoot)) {
1345
+ // openspec 与代码同仓:显式声明单仓,Hook 才会写 Spec-Change
1346
+ deployMonoSddYaml(workspaceRoot);
1347
+ } else {
1348
+ deployDotSddYaml(workspaceRoot);
1349
+ }
1350
+ copyTemplatesToProject();
1351
+ initGlobalOverview();
1352
+ deploySddGuideManual(workspaceRoot);
1353
+ }
1109
1354
  }
1110
1355
 
1111
- // 7. 更新 .gitignore
1356
+ // 7. 更新工作目录 .gitignore
1112
1357
  updateGitignore();
1113
1358
 
1114
1359
  const selectedNames = selectedTools.map(t => TOOL_CONFIGS[t].name).join(', ');
@@ -1120,11 +1365,28 @@ async function main() {
1120
1365
  console.log();
1121
1366
  console.log(`已为以下编辑器生成配置: ${selectedNames}`);
1122
1367
  console.log();
1123
- console.log('已生成/覆盖:');
1124
- console.log(' 🌐 openspec/specs/overview.md # 全局架构约束(数据字典、接口规范)');
1125
- console.log(' 📖 openspec/kld-sdd操作手册.html # SDD 操作手册(HTML)');
1126
- console.log(' 📄 openspec-templates/ # 标准文档模版(参考用)');
1127
- console.log(' 🎯 .*/skills/opsx-*/ # SDD skills(扁平一层)');
1368
+ if (layout.isWorkspace) {
1369
+ console.log('工作区部署结果:');
1370
+ console.log(' 🎯 工作目录 .*/skills/opsx-*/ # skills 只装这一份');
1371
+ console.log(' 📋 .sdd-workspace.yaml # 子仓编排清单');
1372
+ if (layout.specRepo) {
1373
+ console.log(` 📄 ${layout.specRepo.name}/modules.yaml + openspec-templates`);
1374
+ }
1375
+ for (const repo of layout.codeRepos) {
1376
+ console.log(` 🔗 ${repo.name}/ ← commit-msg Hook + .sdd.yaml(无 skills)`);
1377
+ }
1378
+ console.log();
1379
+ console.log('新代码仓加入后执行:');
1380
+ console.log(' kld-sdd sync-repos');
1381
+ console.log('Trailer 仍在各代码仓 git commit 时由 Hook 写入。');
1382
+ } else {
1383
+ console.log('已生成/覆盖:');
1384
+ console.log(' 🌐 openspec/specs/overview.md # 全局架构约束(数据字典、接口规范)');
1385
+ console.log(' 📖 openspec/kld-sdd操作手册.html # SDD 操作手册(HTML)');
1386
+ console.log(' 📄 openspec-templates/ # 标准文档模版(参考用)');
1387
+ console.log(' 📦 modules.yaml / .sdd.yaml # 多仓库命名与关联配置');
1388
+ console.log(' 🎯 .*/skills/opsx-*/ # SDD skills(扁平一层)');
1389
+ }
1128
1390
  if (selectedTools.includes('kunlunzhima')) {
1129
1391
  console.log(' 📎 .kunlunzhima/commands/opsx/ # KunlunZhima OPSX command bridge(11 个)');
1130
1392
  console.log(' ℹ️ KunlunZhima 通过 commands/skills 入口使用 SDD,未启用自动 Hook');
@@ -1135,9 +1397,11 @@ async function main() {
1135
1397
  if (selectedTools.includes('claude')) {
1136
1398
  console.log(' 🪝 .claude/hooks/ # Claude Code SDD Hook Pack(可选增强)');
1137
1399
  }
1138
- console.log(' 📊 skywalk-sdd/ # SDD Telemetry 数据目录');
1139
- console.log(' 🧰 skywalk-sdd/git-hooks/ # Git hooks 质量门禁模板');
1140
- console.log(' 🧪 skywalk-sdd/ci/ # CI 兜底采集模板');
1400
+ if (!layout.isWorkspace) {
1401
+ console.log(' 📊 skywalk-sdd/ # SDD Telemetry 数据目录');
1402
+ console.log(' 🧰 skywalk-sdd/git-hooks/ # Git hooks 质量门禁模板');
1403
+ console.log(' 🧪 skywalk-sdd/ci/ # CI 兜底采集模板');
1404
+ }
1141
1405
  console.log();
1142
1406
 
1143
1407
  // 统一的 skill 格式说明
@@ -1156,6 +1420,9 @@ async function main() {
1156
1420
 
1157
1421
  console.log();
1158
1422
  console.log('后续步骤:');
1423
+ if (layout.isWorkspace && layout.specRepo) {
1424
+ console.log(` 0. 文档类命令请以 spec 仓为 --project(例: --project=${layout.specRepo.name})`);
1425
+ }
1159
1426
  console.log(' 1. 激活 opsx-propose skill,输入变更名称开始创建文档');
1160
1427
  console.log(' 2. 按顺序执行 propose → spec → design → task');
1161
1428
  console.log(' 3. 激活 opsx-check 验证文档质量');
@@ -1187,6 +1454,14 @@ module.exports = {
1187
1454
  deployOpsxSkills,
1188
1455
  deployProfileArtifacts,
1189
1456
  deploySddGuideManual,
1457
+ deployModulesYaml,
1458
+ deploySddConfigYaml,
1459
+ deployDotSddYaml,
1460
+ deployMonoSddYaml,
1461
+ deployQualityGateTemplates,
1462
+ deployTelemetryDataDir,
1463
+ attachCodeRepoLite,
1464
+ syncWorkspaceRepos,
1190
1465
  copyDirRendered,
1191
1466
  parseSelectedTools,
1192
1467
  TOOL_CONFIGS,
@@ -0,0 +1,2 @@
1
+ 'use strict';
2
+ module.exports = require('../skywalk-sdd/ontology/workspace-layout.cjs');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kld-sdd",
3
- "version": "2.6.7",
3
+ "version": "2.6.9",
4
4
  "description": "KLD SDD OpenSpec 项目初始化工具 - 一键部署 SDD skills",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  "kld-sdd-init": "bin/kld-sdd-init.js"
9
9
  },
10
10
  "scripts": {
11
- "test": "node test/external-key.cjs && node test/ontology-release-blockers.cjs && node test/ontology-semantic-core.cjs && node test/ontology-identity-versioning.cjs && node test/ontology-identity-continuity.cjs && node test/ontology-state-transaction.cjs && node test/ontology-process-concurrency.cjs && node test/ontology-observer-convergence.cjs && node test/ontology-working-runtime.cjs && node test/ontology-stage-materialization.cjs && node test/ontology-template-contract.cjs && node test/ontology-cli-archive.cjs && node test/archive-package-producer.cjs && node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/settings-merge.cjs && node test/command-bridge.cjs && node test/codebuddy-hooks.cjs && node test/skill-content-contract.cjs && node test/init-agent-profiles.cjs"
11
+ "test": "node test/external-key.cjs && node test/change-key.cjs && node test/modules-and-sdd-config.cjs && node test/active-changes.cjs && node test/hook-layouts.cjs && node test/workspace-layout.cjs && node test/ontology-release-blockers.cjs && node test/ontology-semantic-core.cjs && node test/ontology-identity-versioning.cjs && node test/ontology-identity-continuity.cjs && node test/ontology-state-transaction.cjs && node test/ontology-process-concurrency.cjs && node test/ontology-observer-convergence.cjs && node test/ontology-working-runtime.cjs && node test/ontology-stage-materialization.cjs && node test/ontology-template-contract.cjs && node test/ontology-cli-archive.cjs && node test/archive-package-producer.cjs && node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/settings-merge.cjs && node test/command-bridge.cjs && node test/codebuddy-hooks.cjs && node test/skill-content-contract.cjs && node test/init-agent-profiles.cjs"
12
12
  },
13
13
  "keywords": [
14
14
  "kld",
@@ -5,6 +5,7 @@ const fs = require('fs');
5
5
  const http = require('http');
6
6
  const https = require('https');
7
7
  const { URL } = require('url');
8
+ const path = require('path');
8
9
 
9
10
  function parseArgs(argv) {
10
11
  const args = {};
@@ -71,10 +72,16 @@ function requestJson(url, payload, token, timeoutMs) {
71
72
  reject(new Error(`knowledge base returned invalid JSON (HTTP ${response.statusCode})`));
72
73
  return;
73
74
  }
74
- if (response.statusCode >= 400 || parsed?.code !== 0) {
75
+ if (response.statusCode >= 400) {
75
76
  reject(new Error(parsed?.message || `knowledge base HTTP ${response.statusCode}`));
76
77
  return;
77
78
  }
79
+ // code 字段仅当存在且不为 0 时才视为业务错误(health/ingest 等管理 API 返回 code,
80
+ // 但 resolve/search 等 context API 直接返回 data,不包含 code 字段)
81
+ if (parsed?.code != null && parsed.code !== 0) {
82
+ reject(new Error(parsed?.message || `knowledge base error code ${parsed.code}`));
83
+ return;
84
+ }
78
85
  resolve(parsed.data);
79
86
  });
80
87
  },
@@ -149,8 +156,57 @@ function buildPayload(args, mode) {
149
156
  }
150
157
 
151
158
  async function retrieveContext(args = parseArgs(process.argv), env = process.env) {
152
- const spaceId = args['space-id'] || env.ENGINEERING_KB_SPACE_ID || '';
153
- const kbId = args['kb-id'] || env.ENGINEERING_KB_KB_ID || '';
159
+ // 优先从共享 state 文件读取配置(与 opsx-ontology-query / opsx-kb-ingest 共用)
160
+ // 搜索多个可能的 IDE skills 目录(.codebuddy / .claude / .cursor 等)
161
+ let stateConfig = {};
162
+ const stateFile = args['state-file'] || env.SDD_KB_STATE_FILE;
163
+ const candidatePaths = [];
164
+ if (stateFile) candidatePaths.push(stateFile);
165
+ // 从 skywalk-sdd 目录向上查找 skills/.shared/kb-state.json
166
+ const ideDirs = ['.codebuddy', '.claude', '.cursor', '.vscode'];
167
+ for (const ide of ideDirs) {
168
+ candidatePaths.push(path.join(__dirname, '..', ide, 'skills', '.shared', 'kb-state.json'));
169
+ }
170
+ // 也检查 skywalk-sdd 同级的 .shared
171
+ candidatePaths.push(path.join(__dirname, '.shared', 'kb-state.json'));
172
+ for (const p of candidatePaths) {
173
+ try {
174
+ if (fs.existsSync(p)) {
175
+ stateConfig = JSON.parse(fs.readFileSync(p, 'utf8'));
176
+ break;
177
+ }
178
+ } catch (_) { /* try next */ }
179
+ }
180
+
181
+ const apiBase = args['api-base'] || stateConfig.api || env.ENGINEERING_KB_API || 'http://localhost:8090/api';
182
+ const token = args.token || stateConfig.apiKey || env.ENGINEERING_KB_TOKEN || '';
183
+ const spaceId = args['space-id'] || env.ENGINEERING_KB_SPACE_ID ||
184
+ (stateConfig.targets && stateConfig.targets.length > 0 ? stateConfig.targets[0].spaceId : '');
185
+ const kbId = args['kb-id'] || env.ENGINEERING_KB_KB_ID ||
186
+ (stateConfig.targets && stateConfig.targets.length > 0 ? stateConfig.targets[0].kbId : '');
187
+
188
+ // --check-only: 只检查 KB 配置是否就绪,不发起 API 请求
189
+ // 用于 SDD 各阶段(propose/spec/design/task/check)统一检测 KB 可用性,消除相对路径歧义
190
+ if (args['check-only']) {
191
+ const missing = [];
192
+ if (!token) missing.push('apiKey');
193
+ if (!spaceId) missing.push('spaceId');
194
+ if (!kbId) missing.push('kbId');
195
+ if (missing.length > 0) {
196
+ return {
197
+ available: false,
198
+ reason: 'engineering_kb_not_configured',
199
+ missing,
200
+ hint: 'Run opsx-ontology-query Session startup to configure .shared/kb-state.json',
201
+ };
202
+ }
203
+ return {
204
+ available: true,
205
+ api: apiBase,
206
+ targets: stateConfig.targets || [],
207
+ };
208
+ }
209
+
154
210
  if (!spaceId || !kbId) {
155
211
  return {
156
212
  available: false,
@@ -162,8 +218,6 @@ async function retrieveContext(args = parseArgs(process.argv), env = process.env
162
218
  }
163
219
 
164
220
  const mode = String(args.mode || 'match').trim().toLowerCase() === 'resolve' ? 'resolve' : 'match';
165
- const apiBase = args['api-base'] || env.ENGINEERING_KB_API || 'http://localhost:8090/api';
166
- const token = args.token || env.ENGINEERING_KB_TOKEN || '';
167
221
  const timeoutMs = Number(args.timeout || env.ENGINEERING_KB_TIMEOUT_MS || 30000);
168
222
  const payload = buildPayload(args, mode);
169
223
  const data = await requestJson(