kld-sdd 2.6.15 → 2.6.16
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 +112 -73
- package/lib/skills-bundle.js +3 -0
- package/package.json +2 -1
- package/skywalk-sdd/openspec-shim.cjs +48 -0
- package/templates/git-hooks/commit-msg +46 -0
- package/templates/git-hooks/pre-commit +57 -0
- package/templates/git-hooks/pre-commit-consistency-check.cjs +193 -0
- package/templates/git-hooks/pre-push +57 -0
- package/templates/git-hooks/pre-push-consistency-check.cjs +197 -0
- package/templates/hooks/codebuddy/hooks/hook-gate-core.cjs +369 -0
- package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +41 -0
- package/templates/hooks/codebuddy/hooks/sdd-mid-checkpoint.cjs +108 -0
- package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +36 -1
- package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +248 -0
- package/templates/hooks/codebuddy/settings.json +8 -0
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +13 -0
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +15 -0
- package/templates/skills/kld-sdd/opsx-apply/reference.md +41 -0
- package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +592 -0
- package/templates/skills/kld-sdd/tdd-rules/SKILL.md +1 -0
- package/templates/skills/kld-sdd/tdd-rules/rules/tdd-rhythm-enforcement.md +101 -0
package/lib/init.js
CHANGED
|
@@ -894,64 +894,115 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
|
|
|
894
894
|
return true;
|
|
895
895
|
}
|
|
896
896
|
|
|
897
|
-
|
|
897
|
+
/**
|
|
898
|
+
* 安装 SDD shell hook(多脚本调用型 hook,如 pre-commit / pre-push / commit-msg)。
|
|
899
|
+
* 直接部署完整的 shell 脚本到 .git/hooks/,脚本内部远程引用 spec 仓的 .cjs 脚本。
|
|
900
|
+
*
|
|
901
|
+
* shell hook 运行时通过 git config sdd.specPath / .sdd-spec-root / 向上搜索
|
|
902
|
+
* 定位 spec 仓的 skywalk-sdd/git-hooks/ 目录,兼容单仓/多仓/mono 布局。
|
|
903
|
+
*
|
|
904
|
+
* @param {string} cwd - Git 仓库根目录
|
|
905
|
+
* @param {string} hookName - hook 名称(如 'pre-commit'、'pre-push')
|
|
906
|
+
* @param {string} pkgPath - kld-sdd 包路径(用于读取模板)
|
|
907
|
+
*/
|
|
908
|
+
function installSddShellHook(cwd, hookName, pkgPath = getPackagePath()) {
|
|
898
909
|
const hooksDir = path.join(cwd, '.git', 'hooks');
|
|
899
910
|
if (!fs.existsSync(hooksDir)) {
|
|
900
911
|
return;
|
|
901
912
|
}
|
|
902
913
|
|
|
914
|
+
const templatePath = path.join(pkgPath, 'templates', 'git-hooks', hookName);
|
|
915
|
+
if (!fs.existsSync(templatePath)) {
|
|
916
|
+
console.log(` ⚠️ Hook 模板不存在: ${templatePath}`);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
903
920
|
const hookPath = path.join(hooksDir, hookName);
|
|
904
921
|
const marker = 'KLD SDD quality gate';
|
|
905
|
-
|
|
906
|
-
// 否则 trailer 脚本会把 --project=… 误当成 message 文件(argv[2])。
|
|
907
|
-
const extraArgs = options.extraArgs || '';
|
|
908
|
-
// Windows 反斜杠路径在 #!/bin/sh(Git for Windows MSYS2)中不被 [ -f ] 和 node 识别,
|
|
909
|
-
// 统一转为正斜杠,确保 hook shim 跨平台可执行。
|
|
910
|
-
const shScriptPath = scriptPath.replace(/\\/g, '/');
|
|
911
|
-
const shExtraArgs = extraArgs.replace(/\\/g, '/');
|
|
912
|
-
const nodeLine = shExtraArgs
|
|
913
|
-
? ` node "${shScriptPath}" "$@" ${shExtraArgs}`
|
|
914
|
-
: ` node "${shScriptPath}" "$@"`;
|
|
915
|
-
const shim = [
|
|
916
|
-
'#!/bin/sh',
|
|
917
|
-
`# ${marker}`,
|
|
918
|
-
`if [ -f "${shScriptPath}" ]; then`,
|
|
919
|
-
nodeLine,
|
|
920
|
-
'else',
|
|
921
|
-
` echo "[kld-sdd] Hook 脚本不存在: ${shScriptPath}" >&2`,
|
|
922
|
-
' exit 1',
|
|
923
|
-
'fi',
|
|
924
|
-
'',
|
|
925
|
-
].join('\n');
|
|
922
|
+
const content = fs.readFileSync(templatePath, 'utf8');
|
|
926
923
|
|
|
927
924
|
if (fs.existsSync(hookPath)) {
|
|
928
925
|
const existing = fs.readFileSync(hookPath, 'utf8');
|
|
929
926
|
if (existing.includes(marker)) {
|
|
930
|
-
// 已是 SDD
|
|
931
|
-
|
|
932
|
-
console.log(` ✓ .git/hooks/${hookName} 已包含 SDD 质量门禁`);
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
fs.writeFileSync(hookPath, shim, 'utf8');
|
|
927
|
+
// 已是 SDD hook:允许覆盖升级
|
|
928
|
+
fs.writeFileSync(hookPath, content, 'utf8');
|
|
936
929
|
try {
|
|
937
930
|
fs.chmodSync(hookPath, 0o755);
|
|
938
931
|
} catch {
|
|
939
932
|
// ignore
|
|
940
933
|
}
|
|
941
|
-
console.log(` ✓ 更新 .git/hooks/${hookName} SDD
|
|
934
|
+
console.log(` ✓ 更新 .git/hooks/${hookName} SDD 质量门禁(多脚本)`);
|
|
942
935
|
return;
|
|
943
936
|
}
|
|
944
|
-
console.log(` ℹ️ .git/hooks/${hookName}
|
|
937
|
+
console.log(` ℹ️ .git/hooks/${hookName} 已存在且非 SDD 受管 hook,已保留不覆盖`);
|
|
945
938
|
return;
|
|
946
939
|
}
|
|
947
940
|
|
|
948
|
-
fs.writeFileSync(hookPath,
|
|
941
|
+
fs.writeFileSync(hookPath, content, 'utf8');
|
|
949
942
|
try {
|
|
950
943
|
fs.chmodSync(hookPath, 0o755);
|
|
951
944
|
} catch {
|
|
952
|
-
// Windows 上 chmod
|
|
945
|
+
// Windows 上 chmod 可能没有实际效果
|
|
953
946
|
}
|
|
954
|
-
console.log(` ✓ 安装 .git/hooks/${hookName} SDD
|
|
947
|
+
console.log(` ✓ 安装 .git/hooks/${hookName} SDD 质量门禁(多脚本)`);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* 部署代码仓全部 Git hooks(commit-msg + pre-commit + pre-push)。
|
|
952
|
+
*
|
|
953
|
+
* 统一入口,封装三种 hook 的部署逻辑。代码仓**不落 skywalk-sdd/**,
|
|
954
|
+
* 只在 .git/hooks/ 安装 shell hook 模板,远程引用 spec 仓的 .cjs 脚本:
|
|
955
|
+
*
|
|
956
|
+
* - **spec 仓**:确保 skywalk-sdd/git-hooks/ 下有全部 .cjs 脚本
|
|
957
|
+
* (commit-msg-sdd-trailer + pre-commit-* + pre-push-* + ontology)。
|
|
958
|
+
* - **代码仓 .git/hooks/**:仅安装 3 个 shell hook 模板,运行时通过
|
|
959
|
+
* git config sdd.specPath / .sdd-spec-root / 向上搜索定位 spec 仓的脚本。
|
|
960
|
+
*
|
|
961
|
+
* @param {string} codeRepoRoot - 代码仓根目录
|
|
962
|
+
* @param {string} specRepoRoot - spec 包裹包根目录
|
|
963
|
+
* @param {object} options - { pkgPath }
|
|
964
|
+
* @returns {{ ok: boolean, message?: string }}
|
|
965
|
+
*/
|
|
966
|
+
function deployCodeRepoHooks(codeRepoRoot, specRepoRoot, options = {}) {
|
|
967
|
+
const cwd = path.resolve(codeRepoRoot);
|
|
968
|
+
const specRoot = path.resolve(specRepoRoot);
|
|
969
|
+
const pkgPath = options.pkgPath || getPackagePath();
|
|
970
|
+
|
|
971
|
+
if (!workspaceLayout.isGitRepo(cwd)) {
|
|
972
|
+
return { ok: false, message: '不是 Git 仓库' };
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// ── 1. 确保 spec 仓有全部 hook 脚本 + ontology ──
|
|
976
|
+
// commit-msg .cjs + ontology(ensureSpecCommitMsgScript 已处理)
|
|
977
|
+
ensureSpecCommitMsgScript(specRoot, pkgPath);
|
|
978
|
+
// pre-commit / pre-push .cjs 脚本
|
|
979
|
+
const specHooksDir = path.join(specRoot, 'skywalk-sdd', 'git-hooks');
|
|
980
|
+
const gitHooksTemplateDir = path.join(pkgPath, 'templates', 'git-hooks');
|
|
981
|
+
if (!fs.existsSync(specHooksDir)) {
|
|
982
|
+
fs.mkdirSync(specHooksDir, { recursive: true });
|
|
983
|
+
}
|
|
984
|
+
const hookScripts = [
|
|
985
|
+
'pre-commit-sdd-check.cjs',
|
|
986
|
+
'pre-commit-consistency-check.cjs',
|
|
987
|
+
'pre-push-sdd-check.cjs',
|
|
988
|
+
'pre-push-consistency-check.cjs',
|
|
989
|
+
];
|
|
990
|
+
for (const script of hookScripts) {
|
|
991
|
+
const src = path.join(gitHooksTemplateDir, script);
|
|
992
|
+
const dst = path.join(specHooksDir, script);
|
|
993
|
+
if (fs.existsSync(src) && !fs.existsSync(dst)) {
|
|
994
|
+
fs.copyFileSync(src, dst);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
console.log(` ✓ 确保 spec 仓 skywalk-sdd/git-hooks/ 脚本齐全`);
|
|
998
|
+
|
|
999
|
+
// ── 2. 安装三个 shell hooks 到代码仓 .git/hooks/ ──
|
|
1000
|
+
// 模板 hook 运行时自动定位 spec 仓的 .cjs 脚本
|
|
1001
|
+
installSddShellHook(cwd, 'commit-msg', pkgPath);
|
|
1002
|
+
installSddShellHook(cwd, 'pre-commit', pkgPath);
|
|
1003
|
+
installSddShellHook(cwd, 'pre-push', pkgPath);
|
|
1004
|
+
|
|
1005
|
+
return { ok: true };
|
|
955
1006
|
}
|
|
956
1007
|
|
|
957
1008
|
/**
|
|
@@ -976,22 +1027,6 @@ function ensureSpecCommitMsgScript(specRepoRoot, pkgPath = getPackagePath()) {
|
|
|
976
1027
|
return { ok: true, scriptPath, deployed: true };
|
|
977
1028
|
}
|
|
978
1029
|
|
|
979
|
-
/**
|
|
980
|
-
* 代码仓 commit-msg:只写 .git/hooks,脚本与 ontology 一律用 spec 仓那一份。
|
|
981
|
-
*/
|
|
982
|
-
function installCodeRepoCommitMsgHook(codeRepoRoot, specRepoRoot, options = {}) {
|
|
983
|
-
const cwd = path.resolve(codeRepoRoot);
|
|
984
|
-
const ensured = ensureSpecCommitMsgScript(specRepoRoot, options.pkgPath || getPackagePath());
|
|
985
|
-
if (!ensured.ok) {
|
|
986
|
-
console.log(` ⚠️ ${ensured.message}`);
|
|
987
|
-
return false;
|
|
988
|
-
}
|
|
989
|
-
installGitHookShim(cwd, 'commit-msg', ensured.scriptPath, {
|
|
990
|
-
extraArgs: `"--project=${cwd}"`,
|
|
991
|
-
});
|
|
992
|
-
return true;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
1030
|
/**
|
|
996
1031
|
* 部署 Git hooks / CI 兜底模板
|
|
997
1032
|
* 这些脚本只做质量门禁和 CI 补充记录,不替代 OPSX 主采集链路。
|
|
@@ -999,7 +1034,7 @@ function installCodeRepoCommitMsgHook(codeRepoRoot, specRepoRoot, options = {})
|
|
|
999
1034
|
* @param {string} [targetCwd]
|
|
1000
1035
|
* @param {{ mode?: 'full'|'commit-msg-only' }} [options]
|
|
1001
1036
|
* full: spec 包裹包(pre-commit/pre-push/commit-msg + CI)
|
|
1002
|
-
* commit-msg-only:
|
|
1037
|
+
* commit-msg-only: 仅安装 commit-msg hook(跳过 pre-commit/pre-push)
|
|
1003
1038
|
*/
|
|
1004
1039
|
function deployQualityGateTemplates(targetCwd = process.cwd(), options = {}) {
|
|
1005
1040
|
const pkgPath = getPackagePath();
|
|
@@ -1030,18 +1065,18 @@ function deployQualityGateTemplates(targetCwd = process.cwd(), options = {}) {
|
|
|
1030
1065
|
}
|
|
1031
1066
|
|
|
1032
1067
|
if (mode === 'full') {
|
|
1033
|
-
|
|
1034
|
-
|
|
1068
|
+
installSddShellHook(cwd, 'pre-commit', pkgPath);
|
|
1069
|
+
installSddShellHook(cwd, 'pre-push', pkgPath);
|
|
1035
1070
|
}
|
|
1036
|
-
|
|
1071
|
+
installSddShellHook(cwd, 'commit-msg', pkgPath);
|
|
1037
1072
|
|
|
1038
1073
|
console.log('✅ Git hooks 已就绪');
|
|
1039
1074
|
return true;
|
|
1040
1075
|
}
|
|
1041
1076
|
|
|
1042
1077
|
/**
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1078
|
+
* 代码仓轻量接入:写 .sdd.yaml + sdd.specPath + .git/hooks/(commit-msg + pre-commit + pre-push)。
|
|
1079
|
+
* 代码仓不落 skywalk-sdd/,shell hook 远程引用 spec 仓 skywalk-sdd/git-hooks/ 下的 .cjs 脚本。
|
|
1045
1080
|
*/
|
|
1046
1081
|
function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
|
|
1047
1082
|
const cwd = path.resolve(codeRepoRoot);
|
|
@@ -1055,7 +1090,7 @@ function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
|
|
|
1055
1090
|
return { ok: false, repo: label, message: `spec 路径不是 Git 仓库: ${specRoot}` };
|
|
1056
1091
|
}
|
|
1057
1092
|
|
|
1058
|
-
console.log(`🔗
|
|
1093
|
+
console.log(`🔗 轻量接入代码仓(commit-msg + pre-commit + pre-push): ${label}`);
|
|
1059
1094
|
|
|
1060
1095
|
const remote = sddConfig.gitRemoteUrl(specRoot) || 'git@gitlab.example.com:biz/xxx-sdd-specs.git';
|
|
1061
1096
|
const sddYamlPath = path.join(cwd, '.sdd.yaml');
|
|
@@ -1081,8 +1116,8 @@ function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
|
|
|
1081
1116
|
}
|
|
1082
1117
|
console.log(` ✓ sdd.specPath = ${linked.path}`);
|
|
1083
1118
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1119
|
+
// 部署代码仓全部 Git hooks(commit-msg + pre-commit + pre-push)
|
|
1120
|
+
deployCodeRepoHooks(cwd, specRoot, options);
|
|
1086
1121
|
|
|
1087
1122
|
return { ok: true, repo: label, path: cwd, specPath: linked.path };
|
|
1088
1123
|
}
|
|
@@ -1555,8 +1590,9 @@ async function main() {
|
|
|
1555
1590
|
removeOuterSkywalk(workspaceRoot);
|
|
1556
1591
|
|
|
1557
1592
|
deployMonoSddYaml(workspaceRoot, { specPathRelative: specPackage.name });
|
|
1558
|
-
//
|
|
1559
|
-
|
|
1593
|
+
// 部署代码仓全部 Git hooks(commit-msg + pre-commit + pre-push)
|
|
1594
|
+
// 单仓 mono:hook 通过 .sdd-spec-root 定位包裹包的 skywalk-sdd/git-hooks/
|
|
1595
|
+
deployCodeRepoHooks(workspaceRoot, specPackage.abs);
|
|
1560
1596
|
const linked = sddConfig.setSpecPath(workspaceRoot, specPackage.abs, {
|
|
1561
1597
|
allowNestedPackage: true,
|
|
1562
1598
|
});
|
|
@@ -1649,7 +1685,7 @@ async function main() {
|
|
|
1649
1685
|
console.log(' 📌 .sdd-spec-root → 包裹包;无根目录 skywalk-sdd/');
|
|
1650
1686
|
}
|
|
1651
1687
|
if (selectedTools.includes('kunlunzhima')) {
|
|
1652
|
-
console.log(' 📎 .kunlunzhima/commands/opsx/ # KunlunZhima OPSX command bridge(
|
|
1688
|
+
console.log(' 📎 .kunlunzhima/commands/opsx/ # KunlunZhima OPSX command bridge(12 个)');
|
|
1653
1689
|
console.log(' ℹ️ KunlunZhima 通过 commands/skills 入口使用 SDD,未启用自动 Hook');
|
|
1654
1690
|
}
|
|
1655
1691
|
if (selectedTools.includes('codebuddy')) {
|
|
@@ -1661,18 +1697,19 @@ async function main() {
|
|
|
1661
1697
|
console.log();
|
|
1662
1698
|
|
|
1663
1699
|
// 统一的 skill 格式说明
|
|
1664
|
-
console.log('可用 skills(opsx-* 系列,
|
|
1665
|
-
console.log(' opsx-propose
|
|
1666
|
-
console.log(' opsx-spec
|
|
1667
|
-
console.log(' opsx-design
|
|
1668
|
-
console.log(' opsx-task
|
|
1669
|
-
console.log(' opsx-check
|
|
1670
|
-
console.log(' opsx-apply
|
|
1671
|
-
console.log(' opsx-test
|
|
1672
|
-
console.log(' opsx-archive
|
|
1673
|
-
console.log(' opsx-explore
|
|
1674
|
-
console.log(' opsx-knowledge
|
|
1675
|
-
console.log(' opsx-rules
|
|
1700
|
+
console.log('可用 skills(opsx-* 系列,14 个 SDD skills):');
|
|
1701
|
+
console.log(' opsx-propose - 创建业务意图文档(Why)');
|
|
1702
|
+
console.log(' opsx-spec - 创建技术契约文档(What)');
|
|
1703
|
+
console.log(' opsx-design - 创建技术实现方案(How)');
|
|
1704
|
+
console.log(' opsx-task - 拆解AI编码任务(Do)');
|
|
1705
|
+
console.log(' opsx-check - 质量门禁检查(Verify)');
|
|
1706
|
+
console.log(' opsx-apply - 申请变更实施(Apply)');
|
|
1707
|
+
console.log(' opsx-test - 执行单元测试(Test)');
|
|
1708
|
+
console.log(' opsx-archive - 归档变更(Archive)');
|
|
1709
|
+
console.log(' opsx-explore - 浏览变更状态(Explore)');
|
|
1710
|
+
console.log(' opsx-knowledge - 业务知识库检索(辅助)');
|
|
1711
|
+
console.log(' opsx-rules - 生成/更新 Agent 规则(辅助)');
|
|
1712
|
+
console.log(' opsx-consistency-check - Spec 一致性校验(代码 vs spec)');
|
|
1676
1713
|
|
|
1677
1714
|
console.log();
|
|
1678
1715
|
console.log('后续步骤:');
|
|
@@ -1729,5 +1766,7 @@ module.exports = {
|
|
|
1729
1766
|
syncWorkspaceRepos,
|
|
1730
1767
|
copyDirRendered,
|
|
1731
1768
|
parseSelectedTools,
|
|
1769
|
+
installSddShellHook,
|
|
1770
|
+
deployCodeRepoHooks,
|
|
1732
1771
|
TOOL_CONFIGS,
|
|
1733
1772
|
};
|
package/lib/skills-bundle.js
CHANGED
|
@@ -22,6 +22,8 @@ const OPSX_SKILL_DIRS = [
|
|
|
22
22
|
// Engineering KB 依赖技能(随 init 部署;propose/spec/archive 硬依赖)
|
|
23
23
|
'opsx-ontology-query',
|
|
24
24
|
'opsx-kb-ingest',
|
|
25
|
+
// Spec 一致性校验技能
|
|
26
|
+
'opsx-consistency-check',
|
|
25
27
|
];
|
|
26
28
|
|
|
27
29
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
@@ -102,6 +104,7 @@ const KUNLUN_SLASH_COMMANDS = [
|
|
|
102
104
|
'explore',
|
|
103
105
|
'ontology-query',
|
|
104
106
|
'kb-ingest',
|
|
107
|
+
'consistency-check',
|
|
105
108
|
];
|
|
106
109
|
|
|
107
110
|
function transformKunlunSlashCommands(content) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kld-sdd",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.16",
|
|
4
4
|
"description": "KLD SDD OpenSpec 项目初始化工具 - 一键部署 SDD skills",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"skywalk-sdd/runtime-metadata.cjs",
|
|
37
37
|
"skywalk-sdd/reporting/",
|
|
38
38
|
"skywalk-sdd/context-client.cjs",
|
|
39
|
+
"skywalk-sdd/openspec-shim.cjs",
|
|
39
40
|
"skywalk-sdd/ontology/",
|
|
40
41
|
"skywalk-sdd/apply-worktree-finish.cjs",
|
|
41
42
|
"README.md"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* openspec-shim.cjs — 自动定位 spec 包并透传 openspec 命令。
|
|
6
|
+
*
|
|
7
|
+
* 用法: node skywalk-sdd/openspec-shim.cjs <openspec-args>
|
|
8
|
+
* 示例: node skywalk-sdd/openspec-shim.cjs list
|
|
9
|
+
* node skywalk-sdd/openspec-shim.cjs add change my-feature
|
|
10
|
+
*
|
|
11
|
+
* 自动通过 resolve-spec-root.cjs 定位 spec 包目录,
|
|
12
|
+
* 然后在该目录下执行 openspec 命令。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { resolveSpecProjectRoot } = require('./ontology/resolve-spec-root.cjs');
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
|
|
19
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
20
|
+
console.log('用法: node skywalk-sdd/openspec-shim.cjs <openspec-args>');
|
|
21
|
+
console.log('示例: node skywalk-sdd/openspec-shim.cjs list');
|
|
22
|
+
console.log(' node skywalk-sdd/openspec-shim.cjs add change my-feature');
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const projectRoot = process.cwd();
|
|
27
|
+
const specRoot = resolveSpecProjectRoot(projectRoot);
|
|
28
|
+
|
|
29
|
+
if (specRoot === projectRoot) {
|
|
30
|
+
console.error('⚠️ 未找到 spec 包目录(未配置 .sdd-spec-root,也未检测到 *-sdd-specs 仓库)');
|
|
31
|
+
console.error(' 配置方式: 在项目根目录创建 .sdd-spec-root 文件,写入 spec 包相对或绝对路径');
|
|
32
|
+
console.error(' 或使用: kld-sdd link-spec --path=<spec-clone>');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { execSync } = require('child_process');
|
|
37
|
+
const openspecArgs = args.map(a => `"${a.replace(/"/g, '\\"')}"`).join(' ');
|
|
38
|
+
const cmd = `openspec ${openspecArgs}`;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
execSync(cmd, { cwd: specRoot, stdio: 'inherit' });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err.status) {
|
|
44
|
+
process.exit(err.status);
|
|
45
|
+
}
|
|
46
|
+
console.error(`❌ openspec 执行失败: ${err.message}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# KLD SDD Commit-MSG Hook
|
|
3
|
+
# 自动查找 commit-msg-sdd-trailer.cjs,兼容单仓库/多仓库/单仓mono布局
|
|
4
|
+
# marker: KLD SDD quality gate
|
|
5
|
+
|
|
6
|
+
# 从 hook 所在目录推算 git 根目录
|
|
7
|
+
hook_dir="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
git_dir="$(dirname "$hook_dir")"
|
|
9
|
+
git_root="$(dirname "$git_dir")"
|
|
10
|
+
|
|
11
|
+
script=""
|
|
12
|
+
|
|
13
|
+
# 方法1: git config sdd.specPath(多仓布局:spec 是独立 clone)
|
|
14
|
+
spec_path=$(git config --local sdd.specPath 2>/dev/null)
|
|
15
|
+
if [ -n "$spec_path" ] && [ -f "$spec_path/skywalk-sdd/git-hooks/commit-msg-sdd-trailer.cjs" ]; then
|
|
16
|
+
script="$spec_path/skywalk-sdd/git-hooks/commit-msg-sdd-trailer.cjs"
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# 方法2: .sdd-spec-root(单仓 mono 布局:skywalk-sdd 在 spec 包裹包子目录内)
|
|
20
|
+
if [ -z "$script" ] && [ -f "$git_root/.sdd-spec-root" ]; then
|
|
21
|
+
spec_rel=$(cat "$git_root/.sdd-spec-root" | head -1 | tr -d '[:space:]')
|
|
22
|
+
if [ -n "$spec_rel" ] && [ -f "$git_root/$spec_rel/skywalk-sdd/git-hooks/commit-msg-sdd-trailer.cjs" ]; then
|
|
23
|
+
script="$git_root/$spec_rel/skywalk-sdd/git-hooks/commit-msg-sdd-trailer.cjs"
|
|
24
|
+
fi
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# 方法3: 从 git 根向上搜索 skywalk-sdd/git-hooks(spec 仓本身或父级)
|
|
28
|
+
if [ -z "$script" ]; then
|
|
29
|
+
search_dir="$git_root"
|
|
30
|
+
while [ "$search_dir" != "/" ] && [ "$search_dir" != "" ]; do
|
|
31
|
+
if [ -f "$search_dir/skywalk-sdd/git-hooks/commit-msg-sdd-trailer.cjs" ]; then
|
|
32
|
+
script="$search_dir/skywalk-sdd/git-hooks/commit-msg-sdd-trailer.cjs"
|
|
33
|
+
break
|
|
34
|
+
fi
|
|
35
|
+
search_dir="$(dirname "$search_dir")"
|
|
36
|
+
done
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if [ -z "$script" ]; then
|
|
40
|
+
echo "[SDD] 未找到 commit-msg-sdd-trailer.cjs,跳过 commit-msg trailer"
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# $1 = Git 传入的 commit message 文件路径
|
|
45
|
+
# --project 告诉 .cjs 脚本代码仓根目录(用于解析 spec 布局)
|
|
46
|
+
node "$script" "$1" --project="$git_root"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# KLD SDD Pre-Commit Hook
|
|
3
|
+
# 远程引用 spec 仓 skywalk-sdd/git-hooks/ 下的 .cjs 脚本
|
|
4
|
+
# 兼容单仓库/多仓库/单仓mono布局
|
|
5
|
+
# marker: KLD SDD quality gate
|
|
6
|
+
|
|
7
|
+
# 从 hook 所在目录推算 git 根目录
|
|
8
|
+
hook_dir="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
git_dir="$(dirname "$hook_dir")"
|
|
10
|
+
git_root="$(dirname "$git_dir")"
|
|
11
|
+
|
|
12
|
+
hooks_dir=""
|
|
13
|
+
|
|
14
|
+
# 方法1: git config sdd.specPath(多仓布局:spec 是独立 clone)
|
|
15
|
+
spec_path=$(git config --local sdd.specPath 2>/dev/null)
|
|
16
|
+
if [ -n "$spec_path" ] && [ -d "$spec_path/skywalk-sdd/git-hooks" ]; then
|
|
17
|
+
hooks_dir="$spec_path/skywalk-sdd/git-hooks"
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# 方法2: .sdd-spec-root(单仓 mono 布局:skywalk-sdd 在 spec 包裹包子目录内)
|
|
21
|
+
if [ -z "$hooks_dir" ] && [ -f "$git_root/.sdd-spec-root" ]; then
|
|
22
|
+
spec_rel=$(cat "$git_root/.sdd-spec-root" | head -1 | tr -d '[:space:]')
|
|
23
|
+
if [ -n "$spec_rel" ] && [ -d "$git_root/$spec_rel/skywalk-sdd/git-hooks" ]; then
|
|
24
|
+
hooks_dir="$git_root/$spec_rel/skywalk-sdd/git-hooks"
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# 方法3: 从 git 根向上搜索 skywalk-sdd/git-hooks(spec 仓本身或父级)
|
|
29
|
+
if [ -z "$hooks_dir" ]; then
|
|
30
|
+
search_dir="$git_root"
|
|
31
|
+
while [ "$search_dir" != "/" ] && [ "$search_dir" != "" ]; do
|
|
32
|
+
if [ -d "$search_dir/skywalk-sdd/git-hooks" ]; then
|
|
33
|
+
hooks_dir="$search_dir/skywalk-sdd/git-hooks"
|
|
34
|
+
break
|
|
35
|
+
fi
|
|
36
|
+
search_dir="$(dirname "$search_dir")"
|
|
37
|
+
done
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ -z "$hooks_dir" ]; then
|
|
41
|
+
echo "[SDD] 未找到 spec 仓的 skywalk-sdd/git-hooks 目录,跳过 pre-commit 检查"
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
echo "[SDD] 执行提交前检查..."
|
|
46
|
+
|
|
47
|
+
# 1. tasks.md 完成度检查
|
|
48
|
+
if [ -f "$hooks_dir/pre-commit-sdd-check.cjs" ]; then
|
|
49
|
+
node "$hooks_dir/pre-commit-sdd-check.cjs" --project="$git_root" "$@" || exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# 2. 代码-spec 一致性检查
|
|
53
|
+
if [ -f "$hooks_dir/pre-commit-consistency-check.cjs" ]; then
|
|
54
|
+
node "$hooks_dir/pre-commit-consistency-check.cjs" --project="$git_root" "$@" || exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
echo "[SDD] ✅ 所有 pre-commit 检查通过"
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pre-commit-consistency-check.cjs
|
|
4
|
+
*
|
|
5
|
+
* Pre-commit 门禁:检查活跃变更的 Spec 一致性校验报告。
|
|
6
|
+
* 若 confidence=low 则阻止提交;缺少报告则警告但放行(向后兼容)。
|
|
7
|
+
*
|
|
8
|
+
* 用法: node pre-commit-consistency-check.cjs --project=<project-root> [--change=<change-name>]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
function readArg(name) {
|
|
17
|
+
const prefix = `--${name}=`;
|
|
18
|
+
const found = process.argv.find(arg => arg.startsWith(prefix));
|
|
19
|
+
return found ? found.slice(prefix.length) : '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function hasFlag(name) {
|
|
23
|
+
return process.argv.includes(`--${name}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 从 .sdd-spec-root 文件读取 spec 包裹包相对路径(单仓 mono 布局)
|
|
28
|
+
*/
|
|
29
|
+
function resolveSpecRoot(projectRoot) {
|
|
30
|
+
// 1. 检查 .sdd-spec-root(单仓 mono 模式)
|
|
31
|
+
const hintPath = path.join(projectRoot, '.sdd-spec-root');
|
|
32
|
+
if (fs.existsSync(hintPath)) {
|
|
33
|
+
const rel = fs.readFileSync(hintPath, 'utf8').trim();
|
|
34
|
+
if (rel) {
|
|
35
|
+
const specAbs = path.resolve(projectRoot, rel);
|
|
36
|
+
if (fs.existsSync(specAbs)) {
|
|
37
|
+
return specAbs;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. 查找 *-sdd-specs 子目录(多仓工作区模式)
|
|
43
|
+
const entries = fs.existsSync(projectRoot) ? fs.readdirSync(projectRoot) : [];
|
|
44
|
+
for (const name of entries) {
|
|
45
|
+
if (/-sdd-specs$/i.test(name)) {
|
|
46
|
+
const candidate = path.join(projectRoot, name);
|
|
47
|
+
if (fs.statSync(candidate).isDirectory() && fs.existsSync(path.join(candidate, 'openspec'))) {
|
|
48
|
+
return candidate;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. 项目根本身就是 spec 仓
|
|
54
|
+
if (fs.existsSync(path.join(projectRoot, 'openspec', 'changes'))) {
|
|
55
|
+
return projectRoot;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 发现活跃变更列表
|
|
63
|
+
*/
|
|
64
|
+
function discoverActiveChanges(specRoot, explicitChange) {
|
|
65
|
+
if (explicitChange) {
|
|
66
|
+
return [explicitChange];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const changesDir = path.join(specRoot, 'openspec', 'changes');
|
|
70
|
+
if (!fs.existsSync(changesDir)) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return fs.readdirSync(changesDir)
|
|
75
|
+
.filter(name => {
|
|
76
|
+
const fullPath = path.join(changesDir, name);
|
|
77
|
+
return fs.statSync(fullPath).isDirectory()
|
|
78
|
+
&& !name.startsWith('.')
|
|
79
|
+
&& name !== 'archive';
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 查找变更的一致性报告 JSON
|
|
85
|
+
*/
|
|
86
|
+
function findConsistencyReport(specRoot, changeName) {
|
|
87
|
+
const patterns = [
|
|
88
|
+
'consistency-report-result.json',
|
|
89
|
+
'consistency-report-self-review-result.json',
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
for (const pattern of patterns) {
|
|
93
|
+
const reportPath = path.join(specRoot, 'openspec', 'changes', changeName, pattern);
|
|
94
|
+
if (fs.existsSync(reportPath)) {
|
|
95
|
+
return reportPath;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 解析一致性报告,返回置信度
|
|
104
|
+
*/
|
|
105
|
+
function parseReport(reportPath) {
|
|
106
|
+
try {
|
|
107
|
+
const content = fs.readFileSync(reportPath, 'utf8');
|
|
108
|
+
const data = JSON.parse(content);
|
|
109
|
+
return {
|
|
110
|
+
confidence: (data.confidence || '').toLowerCase(),
|
|
111
|
+
overallResult: (data.overallResult || '').toLowerCase(),
|
|
112
|
+
changeName: data.changeName || '',
|
|
113
|
+
reportPath,
|
|
114
|
+
};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return { confidence: '', overallResult: '', changeName: '', reportPath, parseError: err.message };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function main() {
|
|
121
|
+
const projectRoot = path.resolve(readArg('project') || process.env.SDD_PROJECT || process.cwd());
|
|
122
|
+
const explicitChange = readArg('change') || process.env.SDD_CHANGE || process.env.OPENSPEC_CHANGE || '';
|
|
123
|
+
const strictMode = hasFlag('strict') || process.env.SDD_STRICT_CONSISTENCY === '1';
|
|
124
|
+
|
|
125
|
+
const specRoot = resolveSpecRoot(projectRoot);
|
|
126
|
+
if (!specRoot) {
|
|
127
|
+
console.log('SDD consistency-check: 未找到 spec 仓库,跳过一致性校验');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const changes = discoverActiveChanges(specRoot, explicitChange);
|
|
132
|
+
if (changes.length === 0) {
|
|
133
|
+
console.log('SDD consistency-check: 无活跃变更,跳过一致性校验');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const blockers = [];
|
|
138
|
+
const missing = [];
|
|
139
|
+
const passed = [];
|
|
140
|
+
|
|
141
|
+
for (const changeName of changes) {
|
|
142
|
+
const reportPath = findConsistencyReport(specRoot, changeName);
|
|
143
|
+
if (!reportPath) {
|
|
144
|
+
missing.push(changeName);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const report = parseReport(reportPath);
|
|
149
|
+
if (report.parseError) {
|
|
150
|
+
console.warn(`SDD consistency-check: ⚠️ ${changeName} 报告解析失败: ${report.parseError}`);
|
|
151
|
+
missing.push(changeName);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (report.confidence === 'low' || report.overallResult === 'fail') {
|
|
156
|
+
blockers.push({ changeName, report });
|
|
157
|
+
} else {
|
|
158
|
+
passed.push({ changeName, report });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 输出摘要
|
|
163
|
+
if (passed.length > 0) {
|
|
164
|
+
for (const item of passed) {
|
|
165
|
+
console.log(`SDD consistency-check: ✅ ${item.changeName} 置信度=${item.report.confidence}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (missing.length > 0) {
|
|
170
|
+
console.warn(`SDD consistency-check: ⚠️ 以下变更缺少一致性校验报告(允许提交,建议执行 opsx-consistency-check):`);
|
|
171
|
+
for (const name of missing) {
|
|
172
|
+
console.warn(` - ${name}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (blockers.length > 0) {
|
|
177
|
+
console.error(`SDD consistency-check: ❌ 以下变更的一致性校验未通过(confidence=low),阻止提交:`);
|
|
178
|
+
for (const item of blockers) {
|
|
179
|
+
console.error(` - ${item.changeName} (confidence=${item.report.confidence}, result=${item.report.overallResult})`);
|
|
180
|
+
console.error(` 报告路径: ${item.report.reportPath}`);
|
|
181
|
+
}
|
|
182
|
+
console.error('');
|
|
183
|
+
console.error('请修复不一致项后重新执行 opsx-consistency-check,或使用 --no-verify 跳过(不推荐)。');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (strictMode && missing.length > 0) {
|
|
188
|
+
console.error('SDD consistency-check: ❌ strict 模式已启用,缺少报告的变更不允许提交。');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main();
|