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.
- package/lib/init.js +194 -88
- package/package.json +2 -2
- package/skywalk-sdd/context-client.cjs +59 -5
- package/skywalk-sdd/index.cjs +3 -2
- package/skywalk-sdd/ontology/cli.cjs +34 -7
- package/skywalk-sdd/ontology/naming-diagnose.cjs +77 -17
- package/skywalk-sdd/ontology/resolve-spec-root.cjs +102 -0
- package/skywalk-sdd/ontology/sdd-config.cjs +62 -16
- package/skywalk-sdd/ontology/workspace-layout.cjs +81 -0
- package/templates/git-hooks/commit-msg-sdd-trailer.cjs +9 -4
- package/templates/skills/kld-sdd/openspec-sync-specs/SKILL.md +148 -0
- package/templates/skills/kld-sdd/openspec-update-change/SKILL.md +86 -0
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +2 -1
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +4 -2
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +37 -2
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +12 -1
- package/templates/skills/kld-sdd/opsx-explore/SKILL.md +3 -1
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +9 -14
- package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +83 -109
- package/templates/skills/kld-sdd/opsx-ontology-query/phase-1-prechange.md +276 -0
- package/templates/skills/kld-sdd/opsx-ontology-query/phase-2-during.md +354 -0
- package/templates/skills/kld-sdd/opsx-ontology-query/phase-3-postchange.md +223 -0
- package/templates/skills/kld-sdd/opsx-ontology-query/phase-4-explore.md +240 -0
- package/templates/skills/kld-sdd/opsx-ontology-query/phase-5-governance.md +232 -0
- package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +92 -4
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +48 -4
- package/templates/skills/kld-sdd/opsx-propose/checklist.md +1 -0
- package/templates/skills/kld-sdd/opsx-rules/SKILL.md +3 -1
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +36 -4
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +12 -1
- 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
|
-
*
|
|
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
|
|
1093
|
+
console.log('⚠️ .sdd.yaml 已存在但未声明 layout: mono;单仓请手动补 `layout: mono`');
|
|
1080
1094
|
return false;
|
|
1081
1095
|
}
|
|
1082
1096
|
const lines = [
|
|
1083
|
-
'#
|
|
1084
|
-
'#
|
|
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
|
-
|
|
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: ${
|
|
1348
|
+
console.log(` Spec: ${specPackage ? specPackage.name : '(未发现,将自动创建包裹包)'}`);
|
|
1281
1349
|
console.log(` 代码仓: ${layout.codeRepos.map((c) => c.name).join(', ') || '(无)'}`);
|
|
1282
|
-
console.log(' → skills
|
|
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
|
-
//
|
|
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 (
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
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
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
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
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
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 (
|
|
1373
|
-
console.log(` 📄 ${
|
|
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('
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
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/ #
|
|
1402
|
-
console.log(' 🧰 skywalk-sdd/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 &&
|
|
1424
|
-
console.log(` 0.
|
|
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
|
-
|
|
1442
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
153
|
-
|
|
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(
|
package/skywalk-sdd/index.cjs
CHANGED
|
@@ -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 ||
|
|
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
|
|
81
|
+
return resolveProjectArg(rawProjectRoot, process.cwd());
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
function inferAgentType(args) {
|