kld-sdd 2.6.8 → 2.6.10

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 (31) hide show
  1. package/lib/init.js +194 -88
  2. package/package.json +2 -2
  3. package/skywalk-sdd/context-client.cjs +59 -5
  4. package/skywalk-sdd/index.cjs +3 -2
  5. package/skywalk-sdd/ontology/cli.cjs +34 -7
  6. package/skywalk-sdd/ontology/naming-diagnose.cjs +77 -17
  7. package/skywalk-sdd/ontology/resolve-spec-root.cjs +102 -0
  8. package/skywalk-sdd/ontology/sdd-config.cjs +62 -16
  9. package/skywalk-sdd/ontology/workspace-layout.cjs +81 -0
  10. package/templates/git-hooks/commit-msg-sdd-trailer.cjs +9 -4
  11. package/templates/skills/kld-sdd/openspec-sync-specs/SKILL.md +148 -0
  12. package/templates/skills/kld-sdd/openspec-update-change/SKILL.md +86 -0
  13. package/templates/skills/kld-sdd/opsx-apply/SKILL.md +2 -1
  14. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +4 -2
  15. package/templates/skills/kld-sdd/opsx-check/SKILL.md +37 -2
  16. package/templates/skills/kld-sdd/opsx-design/SKILL.md +12 -1
  17. package/templates/skills/kld-sdd/opsx-explore/SKILL.md +3 -1
  18. package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +9 -14
  19. package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +83 -109
  20. package/templates/skills/kld-sdd/opsx-ontology-query/phase-1-prechange.md +276 -0
  21. package/templates/skills/kld-sdd/opsx-ontology-query/phase-2-during.md +354 -0
  22. package/templates/skills/kld-sdd/opsx-ontology-query/phase-3-postchange.md +223 -0
  23. package/templates/skills/kld-sdd/opsx-ontology-query/phase-4-explore.md +240 -0
  24. package/templates/skills/kld-sdd/opsx-ontology-query/phase-5-governance.md +232 -0
  25. package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +92 -4
  26. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +48 -4
  27. package/templates/skills/kld-sdd/opsx-propose/checklist.md +1 -0
  28. package/templates/skills/kld-sdd/opsx-rules/SKILL.md +3 -1
  29. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +36 -4
  30. package/templates/skills/kld-sdd/opsx-task/SKILL.md +12 -1
  31. package/templates/skills/kld-sdd/opsx-test/SKILL.md +3 -1
package/lib/init.js CHANGED
@@ -687,14 +687,13 @@ function cleanupNativeOpenspecSkills(selectedTools = Object.keys(TOOL_CONFIGS))
687
687
  }
688
688
 
689
689
  /**
690
- * 复制标准文档模版到项目
690
+ * 复制标准文档模版到项目(openspec-templates/)
691
691
  */
692
- function copyTemplatesToProject() {
692
+ function copyTemplatesToProject(cwd = process.cwd()) {
693
693
  console.log('📄 正在复制标准文档模版到项目...');
694
694
 
695
695
  const pkgPath = getPackagePath();
696
696
  const templatePath = path.join(pkgPath, 'templates', 'openspec');
697
- const cwd = process.cwd();
698
697
  const targetDir = path.join(cwd, 'openspec-templates');
699
698
 
700
699
  if (!fs.existsSync(templatePath)) {
@@ -702,12 +701,10 @@ function copyTemplatesToProject() {
702
701
  return false;
703
702
  }
704
703
 
705
- // 创建目标目录
706
704
  if (!fs.existsSync(targetDir)) {
707
705
  fs.mkdirSync(targetDir, { recursive: true });
708
706
  }
709
707
 
710
- // 复制模版文件
711
708
  copyDir(templatePath, targetDir);
712
709
 
713
710
  console.log(`✅ 标准文档模版已复制到: ${targetDir}`);
@@ -724,29 +721,24 @@ function copyTemplatesToProject() {
724
721
  * 初始化全局 overview.md 到 openspec/specs/ 目录
725
722
  * overview.md 是全局架构约束,所有 Capability 的设计都必须遵守
726
723
  */
727
- function initGlobalOverview() {
724
+ function initGlobalOverview(cwd = process.cwd()) {
728
725
  console.log('🌐 正在初始化全局架构约束 (overview.md)...');
729
726
 
730
727
  const pkgPath = getPackagePath();
731
728
  const overviewSource = path.join(pkgPath, 'templates', 'openspec', 'overview.md');
732
- const cwd = process.cwd();
733
729
  const targetDir = path.join(cwd, 'openspec', 'specs');
734
730
  const targetFile = path.join(targetDir, 'overview.md');
735
731
 
736
- // 检查源文件是否存在
737
732
  if (!fs.existsSync(overviewSource)) {
738
733
  console.log(`⚠️ overview.md 源文件不存在: ${overviewSource}`);
739
734
  return false;
740
735
  }
741
736
 
742
- // 创建目标目录
743
737
  if (!fs.existsSync(targetDir)) {
744
738
  fs.mkdirSync(targetDir, { recursive: true });
745
739
  }
746
740
 
747
- // 检查目标文件是否已存在
748
741
  if (fs.existsSync(targetFile)) {
749
- // 读取现有文件,检查是否需要更新
750
742
  const existingContent = fs.readFileSync(targetFile, 'utf-8');
751
743
  const newContent = fs.readFileSync(overviewSource, 'utf-8');
752
744
 
@@ -755,13 +747,11 @@ function initGlobalOverview() {
755
747
  return true;
756
748
  }
757
749
 
758
- // 备份现有文件
759
750
  const backupFile = path.join(targetDir, 'overview.md.backup');
760
751
  fs.copyFileSync(targetFile, backupFile);
761
752
  console.log(` ℹ️ 已备份现有 overview.md 到 ${backupFile}`);
762
753
  }
763
754
 
764
- // 复制 overview.md
765
755
  fs.copyFileSync(overviewSource, targetFile);
766
756
 
767
757
  console.log(`✅ 全局架构约束已初始化: ${targetFile}`);
@@ -857,7 +847,15 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
857
847
  console.log(' ✓ 部署 skywalk-sdd/apply-worktree-finish.cjs(Apply 收尾脚本)');
858
848
  }
859
849
 
850
+ const openspecShimSrc = path.join(pkgPath, 'skywalk-sdd', 'openspec-shim.cjs');
851
+ const openspecShimDst = path.join(dataDir, 'openspec-shim.cjs');
852
+ if (fs.existsSync(openspecShimSrc)) {
853
+ fs.copyFileSync(openspecShimSrc, openspecShimDst);
854
+ console.log(' ✓ 部署 skywalk-sdd/openspec-shim.cjs(openspec 包裹包路径 shim)');
855
+ }
856
+
860
857
  console.log(' ✓ 调用方式: node skywalk-sdd/log.cjs start|end|metrics|semantic-identity|semantic-reconcile|semantic-check');
858
+ console.log(' ✓ openspec: node skywalk-sdd/openspec-shim.cjs list(自动 cd 到 *-sdd-specs)');
861
859
  console.log(' ✓ Spec Context: node skywalk-sdd/context-client.cjs --query="<自然语言需求>" --target-stage=spec');
862
860
  console.log(' ✓ Apply worktree: record-base 在 §1.5;收尾 apply-worktree-finish.cjs --change=<name>');
863
861
  console.log('✅ Telemetry 已就绪(数据存储在 skywalk-sdd/,无需配置 MCP)');
@@ -1065,32 +1063,98 @@ function deployDotSddYaml(cwd = process.cwd(), pkgPath = getPackagePath()) {
1065
1063
  }
1066
1064
 
1067
1065
  /**
1068
- * 单仓(openspec 与代码同仓)声明 layout: mono
1066
+ * 单仓声明 layout: mono,并记录仓内统一包裹目录相对路径。
1069
1067
  * commit-msg Hook 靠它跳过「spec 工作区干净」检查并只写 Spec-Change。
1070
1068
  */
1071
- function deployMonoSddYaml(cwd = process.cwd()) {
1069
+ function deployMonoSddYaml(cwd = process.cwd(), options = {}) {
1072
1070
  const target = path.join(cwd, '.sdd.yaml');
1071
+ const specRel = options.specPathRelative
1072
+ ? String(options.specPathRelative).replace(/\\/g, '/')
1073
+ : '';
1074
+
1073
1075
  if (fs.existsSync(target)) {
1074
1076
  const existing = fs.readFileSync(target, 'utf8');
1075
1077
  if (/^layout:\s*mono\s*$/m.test(existing)) {
1078
+ if (specRel && !new RegExp(`^spec_path:\\s*${specRel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'm').test(existing)) {
1079
+ // 已有 mono 但缺/错 spec_path:补写相对路径
1080
+ let next = existing.replace(/\s*$/, '');
1081
+ if (/^spec_path:\s*.+$/m.test(next)) {
1082
+ next = next.replace(/^spec_path:\s*.+$/m, `spec_path: ${specRel}`);
1083
+ } else {
1084
+ next = `${next}\nspec_path: ${specRel}`;
1085
+ }
1086
+ fs.writeFileSync(target, `${next}\n`, 'utf8');
1087
+ console.log(` ✓ 已更新 .sdd.yaml spec_path → ${specRel}`);
1088
+ return true;
1089
+ }
1076
1090
  console.log(' ✓ .sdd.yaml 已声明 layout: mono,跳过');
1077
1091
  return true;
1078
1092
  }
1079
- console.log('⚠️ .sdd.yaml 已存在但未声明 layout: mono;单仓请手动补一行 `layout: mono`');
1093
+ console.log('⚠️ .sdd.yaml 已存在但未声明 layout: mono;单仓请手动补 `layout: mono`');
1080
1094
  return false;
1081
1095
  }
1082
1096
  const lines = [
1083
- '# 单仓布局:openspec 文档与代码在同一个仓库',
1084
- '# commit-msg 只写 Spec-Change(spec 与代码同属一次 commit,无需 Spec-Revision 指针)',
1097
+ '# 单仓布局:业务代码与 SDD 包裹包在同一个 Git 仓库',
1098
+ '# SDD 内容统一在 spec_path 目录内(openspec / modules / sdd.config / 模版)',
1099
+ '# commit-msg 只写 Spec-Change(同仓一次 commit,无需 Spec-Revision 指针)',
1085
1100
  'version: 1',
1086
1101
  'layout: mono',
1087
- '',
1088
1102
  ];
1103
+ if (specRel) lines.push(`spec_path: ${specRel}`);
1104
+ lines.push('');
1089
1105
  fs.writeFileSync(target, lines.join('\n'), 'utf8');
1090
- console.log(`✅ 已创建 .sdd.yaml(layout: mono): ${target}`);
1106
+ console.log(`✅ 已创建 .sdd.yaml(layout: mono${specRel ? `, spec_path: ${specRel}` : ''}): ${target}`);
1091
1107
  return true;
1092
1108
  }
1093
1109
 
1110
+ /**
1111
+ * 向统一 SDD 包裹目录写入完整内容(多仓兄弟仓 / 单仓内部子目录共用)。
1112
+ * 不在此部署编辑器 skills——skills 只装工作目录或单仓 Git 根。
1113
+ */
1114
+ async function populateSpecPackage(specRoot, options = {}) {
1115
+ const root = path.resolve(specRoot);
1116
+ const skipOpenspec = Boolean(options.skipOpenspec);
1117
+ const asIndependentGit = Boolean(options.asIndependentGit);
1118
+ console.log(`📦 正在填充 SDD 包裹包: ${root}`);
1119
+
1120
+ if (!skipOpenspec) {
1121
+ const prev = process.cwd();
1122
+ try {
1123
+ process.chdir(root);
1124
+ // 包裹包内只建 openspec 目录结构,不把编辑器配置写进文档仓
1125
+ await runOpenspecInit([]);
1126
+ } finally {
1127
+ process.chdir(prev);
1128
+ }
1129
+ }
1130
+
1131
+ deployTelemetryDataDir(root);
1132
+ if (asIndependentGit || workspaceLayout.isGitRepo(root)) {
1133
+ deployQualityGateTemplates(root, { mode: 'full' });
1134
+ }
1135
+ deployModulesYaml(root);
1136
+ deploySddConfigYaml(root);
1137
+ copyTemplatesToProject(root);
1138
+ initGlobalOverview(root);
1139
+ deploySddGuideManual(root);
1140
+ console.log(`✅ SDD 包裹包已就绪: ${path.basename(root)}`);
1141
+ return { ok: true, path: root };
1142
+ }
1143
+
1144
+ /**
1145
+ * 在工作区 / Git 根写入 .sdd-spec-root,供 shell 与 skills 引用相对包裹包路径。
1146
+ */
1147
+ function writeSddSpecRootHint(anchorDir, specPackageAbs) {
1148
+ const anchor = path.resolve(anchorDir);
1149
+ const specAbs = path.resolve(specPackageAbs);
1150
+ const rel = path.relative(anchor, specAbs).replace(/\\/g, '/');
1151
+ if (!rel || rel.startsWith('..')) return null;
1152
+ const filePath = path.join(anchor, '.sdd-spec-root');
1153
+ fs.writeFileSync(filePath, `${rel}\n`, 'utf8');
1154
+ console.log(` ✓ 已写入 .sdd-spec-root → ${rel}`);
1155
+ return filePath;
1156
+ }
1157
+
1094
1158
  /**
1095
1159
  * 检测当前项目已初始化的 AI 编辑器
1096
1160
  */
@@ -1274,32 +1338,21 @@ async function main() {
1274
1338
  }
1275
1339
 
1276
1340
  const workspaceRoot = process.cwd();
1277
- const layout = workspaceLayout.detectWorkspaceLayout(workspaceRoot);
1341
+ let layout = workspaceLayout.detectWorkspaceLayout(workspaceRoot);
1342
+ let specPackage = layout.specRepo
1343
+ ? { name: layout.specRepo.name, abs: layout.specRepo.abs, created: false, isGit: true }
1344
+ : null;
1345
+
1278
1346
  if (layout.isWorkspace) {
1279
1347
  console.log('📂 检测到个人工作目录布局(子目录 Git 仓库)');
1280
- console.log(` Spec: ${layout.specRepo ? layout.specRepo.name : '(未发现)'}`);
1348
+ console.log(` Spec: ${specPackage ? specPackage.name : '(未发现,将自动创建包裹包)'}`);
1281
1349
  console.log(` 代码仓: ${layout.codeRepos.map((c) => c.name).join(', ') || '(无)'}`);
1282
- console.log(' → skills 只部署到本工作目录;代码仓仅轻量接入 Hook/关联');
1350
+ console.log(' → skills 只部署到本工作目录;SDD 文档统一进 *-sdd-specs 包裹包');
1283
1351
  console.log();
1284
1352
  }
1285
1353
 
1286
- // 1. openspec init:工作区模式下若有 spec 仓则在其内执行
1287
- if (!skipOpenspec) {
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
- }
1299
- }
1300
-
1301
1354
  if (!skipTemplate) {
1302
- // 2/3. skills 与编辑器产物:永远只装在当前工作目录一次
1355
+ // skills / 编辑器产物:永远只装在当前工作目录一次
1303
1356
  cleanupNativeOpenspecCommands(selectedTools);
1304
1357
  cleanupLegacyBundledOpsxSkills(selectedTools);
1305
1358
  deployOpsxSkills(selectedTools);
@@ -1307,49 +1360,93 @@ async function main() {
1307
1360
  deployProfileArtifacts(selectedTools, workspaceRoot, getPackagePath());
1308
1361
 
1309
1362
  if (layout.isWorkspace) {
1310
- // 工作目录本身保留一份 telemetry,供 skills 调用
1311
1363
  deployTelemetryDataDir(workspaceRoot);
1312
1364
 
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
- }
1365
+ if (!specPackage) {
1366
+ console.log('📦 未发现 spec 仓,正在创建统一 SDD 包裹包(独立 Git)...');
1367
+ specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: true });
1368
+ console.log(` ✓ ${specPackage.created ? '已创建' : '已复用'}: ${specPackage.name}`);
1369
+ }
1327
1370
 
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');
1371
+ await populateSpecPackage(specPackage.abs, {
1372
+ skipOpenspec,
1373
+ asIndependentGit: true,
1374
+ });
1375
+ writeSddSpecRootHint(workspaceRoot, specPackage.abs);
1376
+
1377
+ // 重新探测,刷新 layout.specRepo / codeRepos
1378
+ layout = workspaceLayout.detectWorkspaceLayout(workspaceRoot);
1379
+ if (!layout.specRepo) {
1380
+ layout.specRepo = { name: specPackage.name, abs: specPackage.abs };
1381
+ }
1382
+
1383
+ console.log();
1384
+ console.log('🔗 正在为子代码仓做轻量接入(不安装 skills)...');
1385
+ for (const repo of layout.codeRepos) {
1386
+ attachCodeRepoLite(repo.abs, specPackage.abs);
1337
1387
  }
1388
+ const wsFile = workspaceLayout.writeWorkspaceFile(workspaceRoot, layout);
1389
+ console.log(`✅ 工作区清单: ${wsFile}`);
1338
1390
  } else {
1339
- // 单仓模式(旧行为):当前目录既是项目根
1391
+ // 单仓:Git 根只放代码 + skills + Hook;SDD 内容进统一包裹子目录
1392
+ const isGit = workspaceLayout.isGitRepo(workspaceRoot);
1340
1393
  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);
1394
+
1395
+ if (isGit) {
1396
+ console.log('📦 单仓模式:创建/复用仓内统一 SDD 包裹目录...');
1397
+ specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: false });
1398
+ console.log(` ✓ ${specPackage.created ? '已创建' : '已复用'}: ${specPackage.name}`);
1399
+
1400
+ await populateSpecPackage(specPackage.abs, {
1401
+ skipOpenspec,
1402
+ asIndependentGit: false,
1403
+ });
1404
+ writeSddSpecRootHint(workspaceRoot, specPackage.abs);
1405
+
1406
+ deployMonoSddYaml(workspaceRoot, { specPathRelative: specPackage.name });
1407
+ // Hook 装在 Git 根;Trailer 读包裹目录里的 sdd.config / openspec
1408
+ deployQualityGateTemplates(workspaceRoot, { mode: 'commit-msg-only' });
1409
+ const linked = sddConfig.setSpecPath(workspaceRoot, specPackage.abs, {
1410
+ allowNestedPackage: true,
1411
+ });
1412
+ if (linked.ok) {
1413
+ console.log(` ✓ sdd.specPath = ${linked.path}`);
1414
+ } else {
1415
+ console.log(` ⚠️ 未能写入 sdd.specPath: ${linked.message}`);
1416
+ }
1347
1417
  } else {
1418
+ // 非 Git 目录:仍创建包裹包,便于随后 git init
1419
+ console.log('📦 当前目录不是 Git 仓:仍创建 SDD 包裹包(请稍后 git init)...');
1420
+ specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: false });
1421
+ await populateSpecPackage(specPackage.abs, {
1422
+ skipOpenspec,
1423
+ asIndependentGit: false,
1424
+ });
1348
1425
  deployDotSddYaml(workspaceRoot);
1349
1426
  }
1350
- copyTemplatesToProject();
1351
- initGlobalOverview();
1352
- deploySddGuideManual(workspaceRoot);
1427
+ }
1428
+ } else if (!skipOpenspec) {
1429
+ // 仅 openspec、跳过模版时:仍尊重包裹包,避免在工作区根平铺
1430
+ if (layout.isWorkspace) {
1431
+ if (!specPackage) {
1432
+ specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: true });
1433
+ }
1434
+ const prev = process.cwd();
1435
+ try {
1436
+ process.chdir(specPackage.abs);
1437
+ await runOpenspecInit([]);
1438
+ } finally {
1439
+ process.chdir(prev);
1440
+ }
1441
+ } else {
1442
+ specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: false });
1443
+ const prev = process.cwd();
1444
+ try {
1445
+ process.chdir(specPackage.abs);
1446
+ await runOpenspecInit([]);
1447
+ } finally {
1448
+ process.chdir(prev);
1449
+ }
1353
1450
  }
1354
1451
  }
1355
1452
 
@@ -1369,8 +1466,9 @@ async function main() {
1369
1466
  console.log('工作区部署结果:');
1370
1467
  console.log(' 🎯 工作目录 .*/skills/opsx-*/ # skills 只装这一份');
1371
1468
  console.log(' 📋 .sdd-workspace.yaml # 子仓编排清单');
1372
- if (layout.specRepo) {
1373
- console.log(` 📄 ${layout.specRepo.name}/modules.yaml + openspec-templates`);
1469
+ if (specPackage) {
1470
+ console.log(` 📄 ${specPackage.name}/ # 统一 SDD 包裹包(独立 Git)`);
1471
+ console.log(' modules.yaml / sdd.config.yaml / openspec / openspec-templates / skywalk-sdd');
1374
1472
  }
1375
1473
  for (const repo of layout.codeRepos) {
1376
1474
  console.log(` 🔗 ${repo.name}/ ← commit-msg Hook + .sdd.yaml(无 skills)`);
@@ -1380,12 +1478,13 @@ async function main() {
1380
1478
  console.log(' kld-sdd sync-repos');
1381
1479
  console.log('Trailer 仍在各代码仓 git commit 时由 Hook 写入。');
1382
1480
  } 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(扁平一层)');
1481
+ console.log('单仓部署结果:');
1482
+ console.log(' 🎯 .*/skills/opsx-*/ # SDD skills(装在 Git 根)');
1483
+ if (specPackage) {
1484
+ console.log(` 📄 ${specPackage.name}/ # 统一 SDD 包裹包(仓内子目录)`);
1485
+ console.log(' modules.yaml / sdd.config.yaml / openspec / openspec-templates / skywalk-sdd');
1486
+ }
1487
+ console.log(' 🔗 .sdd.yaml (layout: mono) + commit-msg Hook');
1389
1488
  }
1390
1489
  if (selectedTools.includes('kunlunzhima')) {
1391
1490
  console.log(' 📎 .kunlunzhima/commands/opsx/ # KunlunZhima OPSX command bridge(11 个)');
@@ -1398,9 +1497,8 @@ async function main() {
1398
1497
  console.log(' 🪝 .claude/hooks/ # Claude Code SDD Hook Pack(可选增强)');
1399
1498
  }
1400
1499
  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 兜底采集模板');
1500
+ console.log(' 📊 skywalk-sdd/ # Hook 运行时 ontology(Git 根)');
1501
+ console.log(' 🧰 skywalk-sdd/git-hooks/ # commit-msg Trailer Hook');
1404
1502
  }
1405
1503
  console.log();
1406
1504
 
@@ -1420,8 +1518,10 @@ async function main() {
1420
1518
 
1421
1519
  console.log();
1422
1520
  console.log('后续步骤:');
1423
- if (layout.isWorkspace && layout.specRepo) {
1424
- console.log(` 0. 文档类命令请以 spec 仓为 --project(例: --project=${layout.specRepo.name})`);
1521
+ if (layout.isWorkspace && specPackage) {
1522
+ console.log(` 0. 文档类命令请以包裹包为 --project(例: --project=${specPackage.name})`);
1523
+ } else if (specPackage) {
1524
+ console.log(` 0. 文档类命令请以包裹包为 --project(例: --project=${specPackage.name})`);
1425
1525
  }
1426
1526
  console.log(' 1. 激活 opsx-propose skill,输入变更名称开始创建文档');
1427
1527
  console.log(' 2. 按顺序执行 propose → spec → design → task');
@@ -1438,8 +1538,12 @@ async function main() {
1438
1538
  }
1439
1539
  console.log();
1440
1540
  console.log('📊 SDD Telemetry(嵌入在 skill 流程中,自动执行,无需配置):');
1441
- console.log(' - 度量数据自动采集到 skywalk-sdd/events/');
1442
- console.log(' - 运行 node skywalk-sdd/log.cjs metrics --project=. 查看四维度指标');
1541
+ if (specPackage) {
1542
+ console.log(` - 文档侧度量在 ${specPackage.name}/skywalk-sdd/events/`);
1543
+ } else {
1544
+ console.log(' - 度量数据自动采集到 skywalk-sdd/events/');
1545
+ }
1546
+ console.log(' - 运行 node skywalk-sdd/log.cjs metrics --project=<包裹包> 查看四维度指标');
1443
1547
  console.log();
1444
1548
 
1445
1549
  rl.close();
@@ -1458,6 +1562,8 @@ module.exports = {
1458
1562
  deploySddConfigYaml,
1459
1563
  deployDotSddYaml,
1460
1564
  deployMonoSddYaml,
1565
+ populateSpecPackage,
1566
+ writeSddSpecRootHint,
1461
1567
  deployQualityGateTemplates,
1462
1568
  deployTelemetryDataDir,
1463
1569
  attachCodeRepoLite,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kld-sdd",
3
- "version": "2.6.8",
3
+ "version": "2.6.10",
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/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"
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/spec-package-init.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(
@@ -13,6 +13,7 @@
13
13
 
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
+ const { resolveProjectArg } = require('./ontology/resolve-spec-root.cjs');
16
17
  const crypto = require('crypto');
17
18
  const { execFileSync } = require('child_process');
18
19
 
@@ -73,11 +74,11 @@ function nowISO() {
73
74
  }
74
75
 
75
76
  function normalizeProjectRoot(projectRoot) {
76
- const rawProjectRoot = projectRoot || process.cwd();
77
+ const rawProjectRoot = projectRoot || '.';
77
78
  if (process.platform === 'win32' && /^[a-zA-Z]:[^\\/]/.test(String(rawProjectRoot))) {
78
79
  fail(`Windows 项目路径格式不安全: ${rawProjectRoot}。请使用 --project=.,或使用正斜杠路径如 D:/project/demo,或给路径加引号。`);
79
80
  }
80
- return path.resolve(rawProjectRoot);
81
+ return resolveProjectArg(rawProjectRoot, process.cwd());
81
82
  }
82
83
 
83
84
  function inferAgentType(args) {