kld-sdd 2.6.9 → 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/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/opsx-apply/SKILL.md +2 -1
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +3 -1
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +3 -1
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +3 -1
- package/templates/skills/kld-sdd/opsx-explore/SKILL.md +3 -1
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +3 -1
- package/templates/skills/kld-sdd/opsx-rules/SKILL.md +3 -1
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +3 -1
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +3 -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",
|
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) {
|
|
@@ -17,6 +17,7 @@ const namingDiagnose = require('./naming-diagnose.cjs');
|
|
|
17
17
|
const listChanges = require('./list-changes.cjs');
|
|
18
18
|
const sddConfig = require('./sdd-config.cjs');
|
|
19
19
|
const activeChanges = require('./active-changes.cjs');
|
|
20
|
+
const { resolveProjectArg } = require('./resolve-spec-root.cjs');
|
|
20
21
|
|
|
21
22
|
function parseArgs(argv) {
|
|
22
23
|
const result = { _: [] };
|
|
@@ -70,6 +71,7 @@ function showHelp() {
|
|
|
70
71
|
node skywalk-sdd/ontology/cli.cjs active-change --register --change=<key> [--title=] [--summary=] [--module=]
|
|
71
72
|
node skywalk-sdd/ontology/cli.cjs active-change --remove --change=<key>
|
|
72
73
|
node skywalk-sdd/ontology/cli.cjs active-change --list [--json]
|
|
74
|
+
node skywalk-sdd/ontology/cli.cjs spec-root [--project=.] [--json]
|
|
73
75
|
node skywalk-sdd/ontology/cli.cjs reconcile --project=. --change=<name> [--profile=...]
|
|
74
76
|
node skywalk-sdd/ontology/cli.cjs check --project=. --change=<name> [--profile=...]
|
|
75
77
|
node skywalk-sdd/ontology/cli.cjs status --project=. --change=<name>
|
|
@@ -79,6 +81,10 @@ function showHelp() {
|
|
|
79
81
|
observe 只负责采集和同步;check/archive 前仍必须执行 reconcile。`);
|
|
80
82
|
}
|
|
81
83
|
|
|
84
|
+
function resolveCliProject(args, options = {}) {
|
|
85
|
+
return resolveProjectArg(args.project || '.', process.cwd(), options);
|
|
86
|
+
}
|
|
87
|
+
|
|
82
88
|
function main(argv = process.argv.slice(2)) {
|
|
83
89
|
const args = parseArgs(argv);
|
|
84
90
|
const command = args._[0];
|
|
@@ -109,7 +115,7 @@ function main(argv = process.argv.slice(2)) {
|
|
|
109
115
|
}
|
|
110
116
|
if (command === 'change-key') {
|
|
111
117
|
if (args.generate || args._[1] === 'generate') {
|
|
112
|
-
const projectRoot =
|
|
118
|
+
const projectRoot = resolveCliProject(args);
|
|
113
119
|
const registry = modules.loadModulesYaml(projectRoot);
|
|
114
120
|
const existing = listChanges.listActiveChanges(projectRoot);
|
|
115
121
|
const built = changeKey.buildChangeKey(args.module, args.slug, {
|
|
@@ -123,7 +129,7 @@ function main(argv = process.argv.slice(2)) {
|
|
|
123
129
|
}
|
|
124
130
|
const key = args.validate || args._[1];
|
|
125
131
|
if (!key) throw new Error('用法: change-key --validate <key> | --generate --module= --slug=');
|
|
126
|
-
const projectRoot =
|
|
132
|
+
const projectRoot = resolveCliProject(args);
|
|
127
133
|
const registry = modules.loadModulesYaml(projectRoot);
|
|
128
134
|
const result = changeKey.validate(key, {
|
|
129
135
|
registeredModules: registry.ok ? registry.codes : null,
|
|
@@ -140,7 +146,7 @@ function main(argv = process.argv.slice(2)) {
|
|
|
140
146
|
return;
|
|
141
147
|
}
|
|
142
148
|
if (command === 'modules') {
|
|
143
|
-
const projectRoot =
|
|
149
|
+
const projectRoot = resolveCliProject(args);
|
|
144
150
|
const result = modules.loadModulesYaml(projectRoot);
|
|
145
151
|
console.log(JSON.stringify({
|
|
146
152
|
ok: result.ok,
|
|
@@ -170,7 +176,7 @@ function main(argv = process.argv.slice(2)) {
|
|
|
170
176
|
return;
|
|
171
177
|
}
|
|
172
178
|
if (command === 'list-changes') {
|
|
173
|
-
const projectRoot =
|
|
179
|
+
const projectRoot = resolveCliProject(args);
|
|
174
180
|
const result = listChanges.groupChanges(projectRoot);
|
|
175
181
|
if (args.json) {
|
|
176
182
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -179,8 +185,29 @@ function main(argv = process.argv.slice(2)) {
|
|
|
179
185
|
}
|
|
180
186
|
return;
|
|
181
187
|
}
|
|
188
|
+
if (command === 'spec-root') {
|
|
189
|
+
const start = path.resolve(args.project || '.');
|
|
190
|
+
const { resolveSpecProjectRoot, isSpecPackageRoot } = require('./resolve-spec-root.cjs');
|
|
191
|
+
const specRoot = resolveSpecProjectRoot(start);
|
|
192
|
+
const payload = {
|
|
193
|
+
start,
|
|
194
|
+
specRoot,
|
|
195
|
+
resolved: specRoot !== start || isSpecPackageRoot(start),
|
|
196
|
+
openspecShim: 'node skywalk-sdd/openspec-shim.cjs <openspec-args>',
|
|
197
|
+
};
|
|
198
|
+
if (args.json) {
|
|
199
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
200
|
+
} else {
|
|
201
|
+
console.log(`start: ${payload.start}`);
|
|
202
|
+
console.log(`specRoot: ${payload.specRoot}`);
|
|
203
|
+
console.log(`resolved: ${payload.resolved}`);
|
|
204
|
+
console.log(`openspec: cd "${payload.specRoot}" && openspec list`);
|
|
205
|
+
console.log(`shim: ${payload.openspecShim}`);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
182
209
|
if (command === 'link-spec') {
|
|
183
|
-
const projectRoot =
|
|
210
|
+
const projectRoot = resolveCliProject(args, { forCodeRepo: true });
|
|
184
211
|
const specPath = args.path || args._[1];
|
|
185
212
|
if (!specPath) throw new Error('用法: link-spec --path=<spec-clone> [--project=.]');
|
|
186
213
|
const result = sddConfig.setSpecPath(projectRoot, specPath);
|
|
@@ -189,7 +216,7 @@ function main(argv = process.argv.slice(2)) {
|
|
|
189
216
|
return;
|
|
190
217
|
}
|
|
191
218
|
if (command === 'active-change') {
|
|
192
|
-
const specRoot =
|
|
219
|
+
const specRoot = resolveCliProject(args);
|
|
193
220
|
if (args.list || args._[1] === 'list') {
|
|
194
221
|
const config = activeChanges.loadSddConfig(specRoot);
|
|
195
222
|
if (args.json) {
|
|
@@ -227,7 +254,7 @@ function main(argv = process.argv.slice(2)) {
|
|
|
227
254
|
if (!result.ok) process.exitCode = 1;
|
|
228
255
|
return;
|
|
229
256
|
}
|
|
230
|
-
const projectRoot =
|
|
257
|
+
const projectRoot = resolveCliProject(args);
|
|
231
258
|
const changeName = args.change;
|
|
232
259
|
if (!changeName) throw new Error('缺少 --change 参数');
|
|
233
260
|
const profile = args.profile || 'auto';
|
|
@@ -57,6 +57,7 @@ function readProposalFrontmatter(changeDir) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function detectEntryMode(projectRoot) {
|
|
60
|
+
if (sddConfig.isMonoLayout(projectRoot)) return 'mono-repo';
|
|
60
61
|
const hasModules = fs.existsSync(path.join(projectRoot, 'modules.yaml'));
|
|
61
62
|
const hasOpenspecChanges = fs.existsSync(path.join(projectRoot, 'openspec', 'changes'));
|
|
62
63
|
const hasSddYaml = fs.existsSync(path.join(projectRoot, '.sdd.yaml'));
|
|
@@ -281,40 +282,94 @@ function diagnoseSpecRepo(projectRoot, changeName) {
|
|
|
281
282
|
...diagnoseActiveChanges(projectRoot, changeName),
|
|
282
283
|
});
|
|
283
284
|
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
checks.push({
|
|
286
|
+
id: 'code-repo-association',
|
|
287
|
+
status: STATUS.SKIP,
|
|
288
|
+
message: '当前位于 spec 仓库:代码仓 .sdd.yaml / Hook 请在各代码仓库或工作目录诊断',
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
mode: 'spec-repo',
|
|
293
|
+
checks,
|
|
294
|
+
summary: summarize(checks),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 单仓:Git 根有 layout: mono;SDD 内容在仓内统一包裹目录。
|
|
300
|
+
*/
|
|
301
|
+
function diagnoseMonoRepo(gitRoot, changeName) {
|
|
302
|
+
const root = path.resolve(gitRoot);
|
|
303
|
+
const specRoot = sddConfig.resolveMonoSpecRoot(root);
|
|
304
|
+
const checks = [];
|
|
305
|
+
|
|
306
|
+
checks.push({
|
|
307
|
+
id: 'spec-git',
|
|
308
|
+
status: workspaceLayout.isGitRepo(root) ? STATUS.PASS : STATUS.FAIL,
|
|
309
|
+
message: workspaceLayout.isGitRepo(root)
|
|
310
|
+
? '单仓 Git 根可用'
|
|
311
|
+
: '单仓目录不是 Git 仓库',
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const remote = sddConfig.gitRemoteUrl(root);
|
|
315
|
+
checks.push({
|
|
316
|
+
id: 'spec-remote',
|
|
317
|
+
status: remote ? STATUS.PASS : STATUS.NEEDS_INPUT,
|
|
318
|
+
message: remote ? `remote 可识别: ${remote}` : '无法识别 git remote',
|
|
319
|
+
remote,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const registry = modules.loadModulesYaml(specRoot);
|
|
323
|
+
checks.push({
|
|
324
|
+
id: 'modules-yaml',
|
|
325
|
+
status: registry.ok ? STATUS.PASS : (registry.code === modules.CODES.MISSING ? STATUS.NEEDS_INPUT : STATUS.FAIL),
|
|
326
|
+
message: registry.ok
|
|
327
|
+
? `modules.yaml 合法(${registry.codes.size} 个模块,路径 ${path.relative(root, specRoot) || '.'})`
|
|
328
|
+
: registry.message,
|
|
329
|
+
registry,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (changeName) {
|
|
333
|
+
const naming = diagnoseChangeNaming(specRoot, changeName, registry.ok ? registry : null);
|
|
286
334
|
checks.push({
|
|
287
|
-
id: '
|
|
288
|
-
|
|
335
|
+
id: 'change-naming',
|
|
336
|
+
status: naming.status,
|
|
337
|
+
message: naming.message,
|
|
338
|
+
detail: naming,
|
|
289
339
|
});
|
|
340
|
+
} else {
|
|
290
341
|
checks.push({
|
|
291
|
-
id: '
|
|
292
|
-
status: STATUS.
|
|
293
|
-
message: '
|
|
342
|
+
id: 'change-naming',
|
|
343
|
+
status: STATUS.SKIP,
|
|
344
|
+
message: '未指定 --change,跳过命名一致性检查',
|
|
294
345
|
});
|
|
295
|
-
return {
|
|
296
|
-
mode: 'mono-repo',
|
|
297
|
-
checks,
|
|
298
|
-
summary: summarize(checks),
|
|
299
|
-
};
|
|
300
346
|
}
|
|
301
347
|
|
|
348
|
+
checks.push({
|
|
349
|
+
id: 'active-changes',
|
|
350
|
+
...diagnoseActiveChanges(specRoot, changeName),
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
checks.push({
|
|
354
|
+
id: 'commit-hook',
|
|
355
|
+
...checkHookInstalled(root),
|
|
356
|
+
});
|
|
302
357
|
checks.push({
|
|
303
358
|
id: 'code-repo-association',
|
|
304
|
-
status: STATUS.
|
|
305
|
-
message:
|
|
359
|
+
status: STATUS.PASS,
|
|
360
|
+
message: `单仓布局(layout: mono,包裹包 ${path.relative(root, specRoot) || '.'}):commit 写 Spec-Change,不写 Spec-Revision`,
|
|
306
361
|
});
|
|
307
362
|
|
|
308
363
|
return {
|
|
309
|
-
mode: '
|
|
364
|
+
mode: 'mono-repo',
|
|
310
365
|
checks,
|
|
311
366
|
summary: summarize(checks),
|
|
367
|
+
specRoot,
|
|
312
368
|
};
|
|
313
369
|
}
|
|
314
370
|
|
|
315
371
|
/**
|
|
316
|
-
*
|
|
317
|
-
* 不用启发式,否则独立的 spec 仓 clone 会被误判成单仓。
|
|
372
|
+
* 单仓:由 .sdd.yaml 的 layout: mono 显式声明。
|
|
318
373
|
*/
|
|
319
374
|
function isMonoRepoLayout(projectRoot) {
|
|
320
375
|
return sddConfig.isMonoLayout(path.resolve(projectRoot));
|
|
@@ -532,6 +587,10 @@ function diagnose(projectRoot, options = {}) {
|
|
|
532
587
|
};
|
|
533
588
|
}
|
|
534
589
|
|
|
590
|
+
if (mode === 'mono-repo' || isMonoRepoLayout(root)) {
|
|
591
|
+
return diagnoseMonoRepo(root, options.change);
|
|
592
|
+
}
|
|
593
|
+
|
|
535
594
|
if (mode === 'code-repo') {
|
|
536
595
|
return diagnoseCodeRepo(root);
|
|
537
596
|
}
|
|
@@ -586,6 +645,7 @@ module.exports = {
|
|
|
586
645
|
diagnoseChangeNaming,
|
|
587
646
|
diagnoseActiveChanges,
|
|
588
647
|
diagnoseWorkspaceRepos,
|
|
648
|
+
diagnoseMonoRepo,
|
|
589
649
|
isMonoRepoLayout,
|
|
590
650
|
diagnose,
|
|
591
651
|
diagnoseTrailer,
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const sddConfig = require('./sdd-config.cjs');
|
|
6
|
+
const workspaceLayout = require('./workspace-layout.cjs');
|
|
7
|
+
|
|
8
|
+
function isSpecPackageRoot(dir) {
|
|
9
|
+
const root = path.resolve(dir);
|
|
10
|
+
return (
|
|
11
|
+
fs.existsSync(path.join(root, 'openspec', 'changes'))
|
|
12
|
+
|| fs.existsSync(path.join(root, 'modules.yaml'))
|
|
13
|
+
|| fs.existsSync(path.join(root, 'sdd.config.yaml'))
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 解析 SDD 文档根(*-sdd-specs 包裹包或旧平铺 spec 根)。
|
|
19
|
+
* openspec CLI 只认 cwd 下的 openspec/;本函数供 log.cjs / ontology cli 自动定位包裹包。
|
|
20
|
+
*
|
|
21
|
+
* 解析顺序:
|
|
22
|
+
* 1. start 本身已是 spec 包根
|
|
23
|
+
* 2. KLD_SDD_SPEC_ROOT(绝对或相对 start)
|
|
24
|
+
* 3. 单仓 layout: mono → spec_path / 子目录 *-sdd-specs
|
|
25
|
+
* 4. 工作区 layout → specRepo
|
|
26
|
+
* 5. start 下嵌套包裹包
|
|
27
|
+
* 6. 代码仓 git config sdd.specPath(含仓内 nested 包裹)
|
|
28
|
+
* 7. 向上最多 3 层找 mono / 嵌套包(Git 根、工作区根常见)
|
|
29
|
+
* 8. 回退 start
|
|
30
|
+
*/
|
|
31
|
+
function resolveSpecProjectRoot(startDir = process.cwd(), options = {}) {
|
|
32
|
+
const start = path.resolve(startDir || process.cwd());
|
|
33
|
+
if (options.skipResolve || options.forCodeRepo) return start;
|
|
34
|
+
if (isSpecPackageRoot(start)) return start;
|
|
35
|
+
|
|
36
|
+
const envRoot = process.env.KLD_SDD_SPEC_ROOT && String(process.env.KLD_SDD_SPEC_ROOT).trim();
|
|
37
|
+
if (envRoot) {
|
|
38
|
+
const candidate = path.isAbsolute(envRoot) ? envRoot : path.resolve(start, envRoot);
|
|
39
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (sddConfig.isMonoLayout(start)) {
|
|
43
|
+
const monoRoot = sddConfig.resolveMonoSpecRoot(start);
|
|
44
|
+
if (fs.existsSync(monoRoot)) return monoRoot;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const layout = workspaceLayout.detectWorkspaceLayout(start);
|
|
48
|
+
if (layout.specRepo && fs.existsSync(layout.specRepo.abs)) {
|
|
49
|
+
return layout.specRepo.abs;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const nested = workspaceLayout.findNestedSpecPackage(start);
|
|
53
|
+
if (nested) return nested.abs;
|
|
54
|
+
|
|
55
|
+
const specPathResult = sddConfig.resolveSpecPath(start);
|
|
56
|
+
if (specPathResult.ok) {
|
|
57
|
+
const valid = sddConfig.validateSpecPath(specPathResult.path, { allowNestedPackage: true });
|
|
58
|
+
if (valid.ok) return valid.path;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let dir = start;
|
|
62
|
+
for (let depth = 0; depth < 3; depth += 1) {
|
|
63
|
+
const parent = path.dirname(dir);
|
|
64
|
+
if (parent === dir) break;
|
|
65
|
+
if (sddConfig.isMonoLayout(parent)) {
|
|
66
|
+
const monoRoot = sddConfig.resolveMonoSpecRoot(parent);
|
|
67
|
+
if (fs.existsSync(monoRoot)) return monoRoot;
|
|
68
|
+
}
|
|
69
|
+
const parentNested = workspaceLayout.findNestedSpecPackage(parent);
|
|
70
|
+
if (parentNested) return parentNested.abs;
|
|
71
|
+
const parentLayout = workspaceLayout.detectWorkspaceLayout(parent);
|
|
72
|
+
if (parentLayout.specRepo && fs.existsSync(parentLayout.specRepo.abs)) {
|
|
73
|
+
return parentLayout.specRepo.abs;
|
|
74
|
+
}
|
|
75
|
+
dir = parent;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return start;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* CLI / skills 用:解析 --project 参数。
|
|
83
|
+
* '.' 或未显式绝对 spec 根时走 resolve;link-spec 等代码仓命令传 forCodeRepo:true。
|
|
84
|
+
*/
|
|
85
|
+
function resolveProjectArg(projectArg, startDir = process.cwd(), options = {}) {
|
|
86
|
+
const raw = projectArg == null || projectArg === '' ? '.' : String(projectArg);
|
|
87
|
+
const resolved = path.resolve(startDir, raw);
|
|
88
|
+
if (options.forCodeRepo) return resolved;
|
|
89
|
+
if (raw !== '.' && raw !== './' && path.isAbsolute(raw)) {
|
|
90
|
+
return isSpecPackageRoot(resolved) ? resolved : resolveSpecProjectRoot(resolved, options);
|
|
91
|
+
}
|
|
92
|
+
if (raw !== '.' && raw !== './') {
|
|
93
|
+
if (isSpecPackageRoot(resolved)) return resolved;
|
|
94
|
+
}
|
|
95
|
+
return resolveSpecProjectRoot(startDir, options);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
isSpecPackageRoot,
|
|
100
|
+
resolveSpecProjectRoot,
|
|
101
|
+
resolveProjectArg,
|
|
102
|
+
};
|
|
@@ -35,15 +35,17 @@ function stripYamlQuotes(value) {
|
|
|
35
35
|
* version: 1
|
|
36
36
|
* spec_repository: git@host:group/repo.git
|
|
37
37
|
*
|
|
38
|
-
*
|
|
38
|
+
* 单仓:同一 Git 仓内用统一包裹目录存放 openspec / 配置
|
|
39
39
|
* version: 1
|
|
40
40
|
* layout: mono
|
|
41
|
+
* spec_path: myapp-sdd-specs
|
|
41
42
|
*/
|
|
42
43
|
function parseSddYaml(text) {
|
|
43
44
|
const lines = String(text || '').split(/\r?\n/);
|
|
44
45
|
let version = null;
|
|
45
46
|
let specRepository = null;
|
|
46
47
|
let layout = null;
|
|
48
|
+
let specPathRel = null;
|
|
47
49
|
|
|
48
50
|
for (let index = 0; index < lines.length; index += 1) {
|
|
49
51
|
const raw = lines[index];
|
|
@@ -76,6 +78,12 @@ function parseSddYaml(text) {
|
|
|
76
78
|
continue;
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
const pathMatch = /^spec_path:\s*(.+?)\s*$/.exec(line);
|
|
82
|
+
if (pathMatch) {
|
|
83
|
+
specPathRel = stripYamlQuotes(pathMatch[1]);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
79
87
|
return {
|
|
80
88
|
ok: false,
|
|
81
89
|
code: CODES.FORMAT_INVALID,
|
|
@@ -108,6 +116,7 @@ function parseSddYaml(text) {
|
|
|
108
116
|
version,
|
|
109
117
|
layout: resolvedLayout,
|
|
110
118
|
spec_repository: specRepository || '',
|
|
119
|
+
spec_path: specPathRel || '',
|
|
111
120
|
};
|
|
112
121
|
}
|
|
113
122
|
|
|
@@ -132,6 +141,29 @@ function isMonoLayout(repoRoot) {
|
|
|
132
141
|
return loaded.ok && loaded.layout === LAYOUTS.MONO;
|
|
133
142
|
}
|
|
134
143
|
|
|
144
|
+
/**
|
|
145
|
+
* 单仓 SDD 包裹根:优先 .sdd.yaml spec_path → 子目录 *-sdd-specs → 兼容旧平铺根目录。
|
|
146
|
+
*/
|
|
147
|
+
function resolveMonoSpecRoot(repoRoot) {
|
|
148
|
+
const root = path.resolve(repoRoot);
|
|
149
|
+
const loaded = loadSddYaml(root);
|
|
150
|
+
if (loaded.ok && loaded.spec_path) {
|
|
151
|
+
return path.resolve(root, loaded.spec_path);
|
|
152
|
+
}
|
|
153
|
+
const workspaceLayout = require('./workspace-layout.cjs');
|
|
154
|
+
const nested = workspaceLayout.findNestedSpecPackage(root);
|
|
155
|
+
if (nested) return nested.abs;
|
|
156
|
+
return root;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function looksLikeSpecPackageContent(dir) {
|
|
160
|
+
return (
|
|
161
|
+
fs.existsSync(path.join(dir, 'modules.yaml'))
|
|
162
|
+
|| fs.existsSync(path.join(dir, 'sdd.config.yaml'))
|
|
163
|
+
|| fs.existsSync(path.join(dir, 'openspec', 'changes'))
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
135
167
|
function gitConfig(cwd, key) {
|
|
136
168
|
try {
|
|
137
169
|
return execFileSync('git', ['config', '--local', '--get', key], {
|
|
@@ -203,7 +235,7 @@ function resolveSpecPath(codeRepoRoot, env = process.env) {
|
|
|
203
235
|
};
|
|
204
236
|
}
|
|
205
237
|
|
|
206
|
-
function validateSpecPath(specPath) {
|
|
238
|
+
function validateSpecPath(specPath, options = {}) {
|
|
207
239
|
if (!specPath || !fs.existsSync(specPath)) {
|
|
208
240
|
return {
|
|
209
241
|
ok: false,
|
|
@@ -211,25 +243,37 @@ function validateSpecPath(specPath) {
|
|
|
211
243
|
message: `spec 路径不存在: ${specPath}`,
|
|
212
244
|
};
|
|
213
245
|
}
|
|
214
|
-
if (
|
|
215
|
-
return {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
};
|
|
246
|
+
if (fs.existsSync(path.join(specPath, '.git'))) {
|
|
247
|
+
return { ok: true, path: specPath, nested: false };
|
|
248
|
+
}
|
|
249
|
+
// 单仓内部包裹包:不是独立 Git,但内容齐全且落在某个 Git 仓内
|
|
250
|
+
if (options.allowNestedPackage && looksLikeSpecPackageContent(specPath)) {
|
|
251
|
+
return { ok: true, path: specPath, nested: true };
|
|
220
252
|
}
|
|
221
|
-
return {
|
|
253
|
+
return {
|
|
254
|
+
ok: false,
|
|
255
|
+
code: CODES.SPEC_PATH_INVALID,
|
|
256
|
+
message: `spec 路径不是 Git 仓库: ${specPath}`,
|
|
257
|
+
};
|
|
222
258
|
}
|
|
223
259
|
|
|
224
|
-
function setSpecPath(codeRepoRoot, specPath) {
|
|
260
|
+
function setSpecPath(codeRepoRoot, specPath, options = {}) {
|
|
225
261
|
const absolute = path.resolve(specPath);
|
|
226
|
-
const valid = validateSpecPath(absolute);
|
|
262
|
+
const valid = validateSpecPath(absolute, options);
|
|
227
263
|
if (!valid.ok) return valid;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
264
|
+
try {
|
|
265
|
+
execFileSync('git', ['config', '--local', 'sdd.specPath', absolute], {
|
|
266
|
+
cwd: codeRepoRoot,
|
|
267
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
268
|
+
});
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return {
|
|
271
|
+
ok: false,
|
|
272
|
+
code: CODES.SPEC_PATH_INVALID,
|
|
273
|
+
message: `无法写入 git config sdd.specPath(请确认目录是真实 Git 仓库): ${error.message}`,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return { ok: true, path: absolute, nested: Boolean(valid.nested) };
|
|
233
277
|
}
|
|
234
278
|
|
|
235
279
|
function resolveCodeRepoContext(codeRepoRoot, env = process.env) {
|
|
@@ -320,6 +364,8 @@ module.exports = {
|
|
|
320
364
|
LAYOUTS,
|
|
321
365
|
parseSddYaml,
|
|
322
366
|
isMonoLayout,
|
|
367
|
+
resolveMonoSpecRoot,
|
|
368
|
+
looksLikeSpecPackageContent,
|
|
323
369
|
loadSddYaml,
|
|
324
370
|
gitConfig,
|
|
325
371
|
gitRemoteUrl,
|
|
@@ -181,14 +181,95 @@ function findNewCodeRepos(workspaceRoot) {
|
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* 推导统一 SDD 包裹目录名:`<parentBasename>-sdd-specs`
|
|
186
|
+
*/
|
|
187
|
+
function deriveSpecPackageName(parentDir) {
|
|
188
|
+
const base = path.basename(path.resolve(parentDir)).toLowerCase();
|
|
189
|
+
if (!base || base === '.' || base === '/' || base === '\\') return 'project-sdd-specs';
|
|
190
|
+
if (base.endsWith('sdd-specs')) return path.basename(path.resolve(parentDir));
|
|
191
|
+
return `${path.basename(path.resolve(parentDir))}-sdd-specs`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function listChildDirectories(parentDir) {
|
|
195
|
+
const root = path.resolve(parentDir);
|
|
196
|
+
if (!fs.existsSync(root)) return [];
|
|
197
|
+
return fs.readdirSync(root)
|
|
198
|
+
.filter((name) => !name.startsWith('.') && !IGNORE_DIR_NAMES.has(name))
|
|
199
|
+
.map((name) => ({ name, abs: path.join(root, name) }))
|
|
200
|
+
.filter((entry) => {
|
|
201
|
+
try {
|
|
202
|
+
return fs.statSync(entry.abs).isDirectory();
|
|
203
|
+
} catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 在父目录下查找已有的 SDD 包裹包(不必是独立 Git)。
|
|
212
|
+
* 优先:modules.yaml → 目录名 *-sdd-specs → openspec/changes。
|
|
213
|
+
*/
|
|
214
|
+
function findNestedSpecPackage(parentDir) {
|
|
215
|
+
const children = listChildDirectories(parentDir);
|
|
216
|
+
const candidates = children.filter((c) => looksLikeSpecRepo(c.abs));
|
|
217
|
+
if (candidates.length === 0) return null;
|
|
218
|
+
return (
|
|
219
|
+
candidates.find((c) => fs.existsSync(path.join(c.abs, 'modules.yaml')))
|
|
220
|
+
|| candidates.find((c) => /-sdd-specs$/i.test(c.name))
|
|
221
|
+
|| candidates[0]
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 确保父目录下存在统一的 SDD 包裹包目录。
|
|
227
|
+
* @param {{ name?: string, initGit?: boolean }} options
|
|
228
|
+
* initGit=true:多仓兄弟目录,包裹包本身是独立 Git 仓
|
|
229
|
+
* initGit=false:单仓内部子目录,共用外层 Git
|
|
230
|
+
*/
|
|
231
|
+
function ensureSpecPackage(parentDir, options = {}) {
|
|
232
|
+
const { execFileSync } = require('child_process');
|
|
233
|
+
const root = path.resolve(parentDir);
|
|
234
|
+
const existing = findNestedSpecPackage(root);
|
|
235
|
+
if (existing) {
|
|
236
|
+
return {
|
|
237
|
+
name: existing.name,
|
|
238
|
+
abs: existing.abs,
|
|
239
|
+
created: false,
|
|
240
|
+
isGit: isGitRepo(existing.abs),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const name = options.name || deriveSpecPackageName(root);
|
|
245
|
+
const abs = path.join(root, name);
|
|
246
|
+
const existed = fs.existsSync(abs);
|
|
247
|
+
fs.mkdirSync(abs, { recursive: true });
|
|
248
|
+
|
|
249
|
+
let isGit = isGitRepo(abs);
|
|
250
|
+
if (options.initGit && !isGit) {
|
|
251
|
+
execFileSync('git', ['init'], {
|
|
252
|
+
cwd: abs,
|
|
253
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
254
|
+
});
|
|
255
|
+
isGit = true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { name, abs, created: !existed, isGit };
|
|
259
|
+
}
|
|
260
|
+
|
|
184
261
|
module.exports = {
|
|
185
262
|
WORKSPACE_FILE,
|
|
186
263
|
isGitRepo,
|
|
187
264
|
looksLikeSpecRepo,
|
|
188
265
|
listChildGitRepos,
|
|
266
|
+
listChildDirectories,
|
|
189
267
|
detectWorkspaceLayout,
|
|
190
268
|
parseWorkspaceYaml,
|
|
191
269
|
loadWorkspaceFile,
|
|
192
270
|
writeWorkspaceFile,
|
|
193
271
|
findNewCodeRepos,
|
|
272
|
+
deriveSpecPackageName,
|
|
273
|
+
findNestedSpecPackage,
|
|
274
|
+
ensureSpecPackage,
|
|
194
275
|
};
|
|
@@ -60,7 +60,7 @@ function resolveLayout(repoRoot, env = process.env) {
|
|
|
60
60
|
return { mode: 'unlinked' };
|
|
61
61
|
}
|
|
62
62
|
if (sddConfig.isMonoLayout(repoRoot)) {
|
|
63
|
-
return { mode: 'mono', specPath: repoRoot };
|
|
63
|
+
return { mode: 'mono', specPath: sddConfig.resolveMonoSpecRoot(repoRoot) };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const context = sddConfig.resolveCodeRepoContext(repoRoot, env);
|
|
@@ -70,9 +70,14 @@ function resolveLayout(repoRoot, env = process.env) {
|
|
|
70
70
|
if (context.code === sddConfig.CODES.SPEC_PATH_INVALID) {
|
|
71
71
|
fail(context.message);
|
|
72
72
|
}
|
|
73
|
-
// specPath
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
// specPath 指回本仓根:等同单仓(兼容旧平铺);若指向仓内包裹包仍按 mono
|
|
74
|
+
const resolvedSpec = path.resolve(context.specPath);
|
|
75
|
+
const resolvedRoot = path.resolve(repoRoot);
|
|
76
|
+
if (resolvedSpec === resolvedRoot) {
|
|
77
|
+
return { mode: 'mono', specPath: sddConfig.resolveMonoSpecRoot(repoRoot), context };
|
|
78
|
+
}
|
|
79
|
+
if (resolvedSpec.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
80
|
+
return { mode: 'mono', specPath: resolvedSpec, context };
|
|
76
81
|
}
|
|
77
82
|
return { mode: 'multi', specPath: context.specPath, context };
|
|
78
83
|
}
|
|
@@ -27,7 +27,8 @@ allowed-tools:
|
|
|
27
27
|
> - ⛔ **隔离红线**:绝对禁止加载同级其他 Capability 的文档
|
|
28
28
|
|
|
29
29
|
> **🖥️ 跨平台执行规则**
|
|
30
|
-
> -
|
|
30
|
+
> - **写代码**:cwd 保持在 Git 根(含 `src/`);**文档 / telemetry**:`--project` 指向 SDD 包裹包(`node skywalk-sdd/ontology/cli.cjs spec-root` 可查看)。
|
|
31
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包)。
|
|
31
32
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
32
33
|
> - ${SHELL_GUIDANCE}
|
|
33
34
|
> - 不要省略 `--source=opsx-command` 与 `--session-id=<会话ID>`。
|
|
@@ -20,7 +20,9 @@ allowed-tools:
|
|
|
20
20
|
> **硬依赖(收尾入库)**:zip 生成后的上传依赖同级已部署的 **`opsx-kb-ingest`**。进入 §5.5 前必须先 `Read` 该技能的 `SKILL.md` 并完成其 Session 启动。入库成功后,`Read` `opsx-ontology-query/phase-3-postchange.md` 验证版本生效、AC 保留、关系完整性。缺失则提示用户重新 `kld-sdd-init`,**不要**自造另一套入库协议。
|
|
21
21
|
|
|
22
22
|
> **跨平台执行规则**
|
|
23
|
-
> -
|
|
23
|
+
> - **SDD 文档根** = `*-sdd-specs` 包裹包(含 `openspec/`、`modules.yaml`),不是 Git 根或工作区根。
|
|
24
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包),或先 `cd` 到包裹包再执行。
|
|
25
|
+
> - `skywalk-sdd/log.cjs` / `ontology/cli.cjs`:`--project=.` 在 Git 根/工作区根会自动解析;也可 `node skywalk-sdd/ontology/cli.cjs spec-root` 查看路径。
|
|
24
26
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
25
27
|
> - 不要裸写 Windows 反斜杠绝对路径;如必须使用绝对路径,请加引号或改成正斜杠。
|
|
26
28
|
> - 不要省略 `--source=opsx-command` 和 `--session-id=<会话ID>`。
|
|
@@ -32,7 +32,9 @@ allowed-tools:
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
> **🖥️ 跨平台执行规则**
|
|
35
|
-
> -
|
|
35
|
+
> - **SDD 文档根** = `*-sdd-specs` 包裹包(含 `openspec/`、`modules.yaml`),不是 Git 根或工作区根。
|
|
36
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包),或先 `cd` 到包裹包再执行。
|
|
37
|
+
> - `skywalk-sdd/log.cjs` / `ontology/cli.cjs`:`--project=.` 在 Git 根/工作区根会自动解析;也可 `node skywalk-sdd/ontology/cli.cjs spec-root` 查看路径。
|
|
36
38
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
37
39
|
> - ${SHELL_GUIDANCE}
|
|
38
40
|
> - 不要省略 `--source=opsx-command` 与 `--session-id=<会话ID>`。
|
|
@@ -46,7 +46,9 @@ allowed-tools:
|
|
|
46
46
|
> - ⛔ **隔离红线**:绝对禁止跨目录读取同级其他 Capability 的 spec 或 design(Full 模式)
|
|
47
47
|
|
|
48
48
|
> **🖥️ 跨平台执行规则**
|
|
49
|
-
> -
|
|
49
|
+
> - **SDD 文档根** = `*-sdd-specs` 包裹包(含 `openspec/`、`modules.yaml`),不是 Git 根或工作区根。
|
|
50
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包),或先 `cd` 到包裹包再执行。
|
|
51
|
+
> - `skywalk-sdd/log.cjs` / `ontology/cli.cjs`:`--project=.` 在 Git 根/工作区根会自动解析;也可 `node skywalk-sdd/ontology/cli.cjs spec-root` 查看路径。
|
|
50
52
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
51
53
|
> - ${SHELL_GUIDANCE}
|
|
52
54
|
> - 不要省略 `--source=opsx-command` 与 `--session-id=<会话ID>`。
|
|
@@ -16,7 +16,9 @@ allowed-tools:
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
> **🖥️ 跨平台执行规则**
|
|
19
|
-
> -
|
|
19
|
+
> - **SDD 文档根** = `*-sdd-specs` 包裹包(含 `openspec/`、`modules.yaml`),不是 Git 根或工作区根。
|
|
20
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包),或先 `cd` 到包裹包再执行。
|
|
21
|
+
> - `skywalk-sdd/log.cjs` / `ontology/cli.cjs`:`--project=.` 在 Git 根/工作区根会自动解析;也可 `node skywalk-sdd/ontology/cli.cjs spec-root` 查看路径。
|
|
20
22
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
21
23
|
> - ${SHELL_GUIDANCE}
|
|
22
24
|
> - 不要省略 `--source=opsx-command` 与 `--session-id=<会话ID>`。
|
|
@@ -34,7 +34,9 @@ allowed-tools:
|
|
|
34
34
|
> 阶段边界自检见 `./checklist.md`「阶段边界⛔」。
|
|
35
35
|
|
|
36
36
|
> **🖥️ 跨平台执行规则**
|
|
37
|
-
> -
|
|
37
|
+
> - **SDD 文档根** = `*-sdd-specs` 包裹包(含 `openspec/`、`modules.yaml`),不是 Git 根或工作区根。
|
|
38
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包),或先 `cd` 到包裹包再执行。
|
|
39
|
+
> - `skywalk-sdd/log.cjs` / `ontology/cli.cjs`:`--project=.` 在 Git 根/工作区根会自动解析;也可 `node skywalk-sdd/ontology/cli.cjs spec-root` 查看路径。
|
|
38
40
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
39
41
|
> - ${SHELL_GUIDANCE}
|
|
40
42
|
> - 不要省略 `--source=opsx-command` 与 `--session-id=<会话ID>`。
|
|
@@ -19,7 +19,9 @@ allowed-tools:
|
|
|
19
19
|
你是一个 SDD 项目规则生成与维护专家。激活本技能后,你将扫描项目事实,为已部署的 Agent 生成或审查规则文件。
|
|
20
20
|
|
|
21
21
|
> **🖥️ 跨平台执行规则**
|
|
22
|
-
> -
|
|
22
|
+
> - **SDD 文档根** = `*-sdd-specs` 包裹包(含 `openspec/`、`modules.yaml`),不是 Git 根或工作区根。
|
|
23
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包),或先 `cd` 到包裹包再执行。
|
|
24
|
+
> - `skywalk-sdd/log.cjs` / `ontology/cli.cjs`:`--project=.` 在 Git 根/工作区根会自动解析;也可 `node skywalk-sdd/ontology/cli.cjs spec-root` 查看路径。
|
|
23
25
|
> - ${SHELL_GUIDANCE}
|
|
24
26
|
> - **本技能不写入 SkyWalk Telemetry**(与 `opsx-knowledge` 同属辅助技能,不走 `log.cjs start/end`)。
|
|
25
27
|
|
|
@@ -34,7 +34,9 @@ allowed-tools:
|
|
|
34
34
|
> 阶段边界自检见 `./checklist.md`「阶段边界⛔」。
|
|
35
35
|
|
|
36
36
|
> **🖥️ 跨平台执行规则**
|
|
37
|
-
> -
|
|
37
|
+
> - **SDD 文档根** = `*-sdd-specs` 包裹包(含 `openspec/`、`modules.yaml`),不是 Git 根或工作区根。
|
|
38
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包),或先 `cd` 到包裹包再执行。
|
|
39
|
+
> - `skywalk-sdd/log.cjs` / `ontology/cli.cjs`:`--project=.` 在 Git 根/工作区根会自动解析;也可 `node skywalk-sdd/ontology/cli.cjs spec-root` 查看路径。
|
|
38
40
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
39
41
|
> - ${SHELL_GUIDANCE}
|
|
40
42
|
> - 不要省略 `--source=opsx-command` 与 `--session-id=<会话ID>`。
|
|
@@ -47,7 +47,9 @@ allowed-tools:
|
|
|
47
47
|
> - ⛔ **隔离红线**:绝对禁止跨目录读取同级其他 Capability 的文档(Full 模式)
|
|
48
48
|
|
|
49
49
|
> **🖥️ 跨平台执行规则**
|
|
50
|
-
> -
|
|
50
|
+
> - **SDD 文档根** = `*-sdd-specs` 包裹包(含 `openspec/`、`modules.yaml`),不是 Git 根或工作区根。
|
|
51
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包),或先 `cd` 到包裹包再执行。
|
|
52
|
+
> - `skywalk-sdd/log.cjs` / `ontology/cli.cjs`:`--project=.` 在 Git 根/工作区根会自动解析;也可 `node skywalk-sdd/ontology/cli.cjs spec-root` 查看路径。
|
|
51
53
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
52
54
|
> - ${SHELL_GUIDANCE}
|
|
53
55
|
> - 不要省略 `--source=opsx-command` 与 `--session-id=<会话ID>`。
|
|
@@ -24,7 +24,9 @@ allowed-tools:
|
|
|
24
24
|
> - 输出标准化测试报告,便于质量追踪
|
|
25
25
|
|
|
26
26
|
> **🖥️ 跨平台执行规则**
|
|
27
|
-
> -
|
|
27
|
+
> - **SDD 文档根** = `*-sdd-specs` 包裹包(含 `openspec/`、`modules.yaml`),不是 Git 根或工作区根。
|
|
28
|
+
> - `openspec` 命令:优先 `node skywalk-sdd/openspec-shim.cjs list`(自动 cd 到包裹包),或先 `cd` 到包裹包再执行。
|
|
29
|
+
> - `skywalk-sdd/log.cjs` / `ontology/cli.cjs`:`--project=.` 在 Git 根/工作区根会自动解析;也可 `node skywalk-sdd/ontology/cli.cjs spec-root` 查看路径。
|
|
28
30
|
> - Telemetry 命令默认使用 `--project=.`,兼容 Windows、macOS、Linux。
|
|
29
31
|
> - ${SHELL_GUIDANCE}
|
|
30
32
|
> - 不要省略 `--source=opsx-command` 与 `--session-id=<会话ID>`。
|