kld-sdd 2.6.10 → 2.6.11

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 CHANGED
@@ -862,7 +862,7 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
862
862
  return true;
863
863
  }
864
864
 
865
- function installGitHookShim(cwd, hookName, scriptPath) {
865
+ function installGitHookShim(cwd, hookName, scriptPath, options = {}) {
866
866
  const hooksDir = path.join(cwd, '.git', 'hooks');
867
867
  if (!fs.existsSync(hooksDir)) {
868
868
  return;
@@ -870,11 +870,20 @@ function installGitHookShim(cwd, hookName, scriptPath) {
870
870
 
871
871
  const hookPath = path.join(hooksDir, hookName);
872
872
  const marker = 'KLD SDD quality gate';
873
+ // Git 把 commit-msg 文件路径放在 $1;--project 等额外参数必须跟在 "$@" 后面,
874
+ // 否则 trailer 脚本会把 --project=… 误当成 message 文件(argv[2])。
875
+ const extraArgs = options.extraArgs || '';
876
+ const nodeLine = extraArgs
877
+ ? ` node "${scriptPath}" "$@" ${extraArgs}`
878
+ : ` node "${scriptPath}" "$@"`;
873
879
  const shim = [
874
880
  '#!/bin/sh',
875
881
  `# ${marker}`,
876
882
  `if [ -f "${scriptPath}" ]; then`,
877
- ` node "${scriptPath}" "$@"`,
883
+ nodeLine,
884
+ 'else',
885
+ ` echo "[kld-sdd] Hook 脚本不存在: ${scriptPath}" >&2`,
886
+ ' exit 1',
878
887
  'fi',
879
888
  '',
880
889
  ].join('\n');
@@ -882,10 +891,21 @@ function installGitHookShim(cwd, hookName, scriptPath) {
882
891
  if (fs.existsSync(hookPath)) {
883
892
  const existing = fs.readFileSync(hookPath, 'utf8');
884
893
  if (existing.includes(marker)) {
885
- console.log(` ✓ .git/hooks/${hookName} 已包含 SDD 质量门禁`);
886
- } else {
887
- console.log(` ℹ️ .git/hooks/${hookName} 已存在,已保留不覆盖;可手动调用 ${scriptPath}`);
894
+ // 已是 SDD shim:若指向路径变化(如改为引用 spec 仓),允许覆盖升级
895
+ if (existing.includes(scriptPath) && (!extraArgs || existing.includes(extraArgs.trim()))) {
896
+ console.log(` .git/hooks/${hookName} 已包含 SDD 质量门禁`);
897
+ return;
898
+ }
899
+ fs.writeFileSync(hookPath, shim, 'utf8');
900
+ try {
901
+ fs.chmodSync(hookPath, 0o755);
902
+ } catch {
903
+ // ignore
904
+ }
905
+ console.log(` ✓ 更新 .git/hooks/${hookName} SDD 质量门禁`);
906
+ return;
888
907
  }
908
+ console.log(` ℹ️ .git/hooks/${hookName} 已存在,已保留不覆盖;可手动调用 ${scriptPath}`);
889
909
  return;
890
910
  }
891
911
 
@@ -898,14 +918,52 @@ function installGitHookShim(cwd, hookName, scriptPath) {
898
918
  console.log(` ✓ 安装 .git/hooks/${hookName} SDD 质量门禁`);
899
919
  }
900
920
 
921
+ /**
922
+ * 确保 spec 包裹包内有 commit-msg 脚本(供代码仓 Hook 远程引用)。
923
+ */
924
+ function ensureSpecCommitMsgScript(specRepoRoot, pkgPath = getPackagePath()) {
925
+ const specRoot = path.resolve(specRepoRoot);
926
+ const scriptPath = path.join(specRoot, 'skywalk-sdd', 'git-hooks', 'commit-msg-sdd-trailer.cjs');
927
+ if (fs.existsSync(scriptPath)) return { ok: true, scriptPath };
928
+
929
+ const src = path.join(pkgPath, 'templates', 'git-hooks', 'commit-msg-sdd-trailer.cjs');
930
+ if (!fs.existsSync(src)) {
931
+ return { ok: false, message: `commit-msg 模板缺失: ${src}` };
932
+ }
933
+ const ontologySrc = path.join(pkgPath, 'skywalk-sdd', 'ontology');
934
+ const ontologyDst = path.join(specRoot, 'skywalk-sdd', 'ontology');
935
+ if (fs.existsSync(ontologySrc) && !fs.existsSync(path.join(ontologyDst, 'change-key.cjs'))) {
936
+ copyDir(ontologySrc, ontologyDst);
937
+ }
938
+ fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
939
+ fs.copyFileSync(src, scriptPath);
940
+ return { ok: true, scriptPath, deployed: true };
941
+ }
942
+
943
+ /**
944
+ * 代码仓 commit-msg:只写 .git/hooks,脚本与 ontology 一律用 spec 仓那一份。
945
+ */
946
+ function installCodeRepoCommitMsgHook(codeRepoRoot, specRepoRoot, options = {}) {
947
+ const cwd = path.resolve(codeRepoRoot);
948
+ const ensured = ensureSpecCommitMsgScript(specRepoRoot, options.pkgPath || getPackagePath());
949
+ if (!ensured.ok) {
950
+ console.log(` ⚠️ ${ensured.message}`);
951
+ return false;
952
+ }
953
+ installGitHookShim(cwd, 'commit-msg', ensured.scriptPath, {
954
+ extraArgs: `--project=${cwd}`,
955
+ });
956
+ return true;
957
+ }
958
+
901
959
  /**
902
960
  * 部署 Git hooks / CI 兜底模板
903
961
  * 这些脚本只做质量门禁和 CI 补充记录,不替代 OPSX 主采集链路。
904
962
  *
905
963
  * @param {string} [targetCwd]
906
964
  * @param {{ mode?: 'full'|'commit-msg-only' }} [options]
907
- * full: spec 仓(pre-commit/pre-push/commit-msg + CI)
908
- * commit-msg-only: 代码仓(只装 Trailer Hook,不装 skills、不装文档门禁)
965
+ * full: spec 包裹包(pre-commit/pre-push/commit-msg + CI)
966
+ * commit-msg-only: 单仓 Git 根(本仓落 skywalk-sdd/git-hooks;多仓代码仓请用 installCodeRepoCommitMsgHook)
909
967
  */
910
968
  function deployQualityGateTemplates(targetCwd = process.cwd(), options = {}) {
911
969
  const pkgPath = getPackagePath();
@@ -946,12 +1004,12 @@ function deployQualityGateTemplates(targetCwd = process.cwd(), options = {}) {
946
1004
  }
947
1005
 
948
1006
  /**
949
- * 代码仓轻量接入:只部署 Hook 运行所需 ontology + commit-msg,不部署 skills
1007
+ * 代码仓轻量接入:只写 .sdd.yaml + sdd.specPath + .git/hooks/commit-msg。
1008
+ * 不在代码仓落 skywalk-sdd/——Hook 脚本与 ontology 引用 spec 包裹包那一份。
950
1009
  */
951
1010
  function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
952
1011
  const cwd = path.resolve(codeRepoRoot);
953
1012
  const specRoot = path.resolve(specRepoRoot);
954
- const pkgPath = options.pkgPath || getPackagePath();
955
1013
  const label = path.basename(cwd);
956
1014
 
957
1015
  if (!workspaceLayout.isGitRepo(cwd)) {
@@ -961,16 +1019,7 @@ function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
961
1019
  return { ok: false, repo: label, message: `spec 路径不是 Git 仓库: ${specRoot}` };
962
1020
  }
963
1021
 
964
- console.log(`🔗 轻量接入代码仓(无 skills): ${label}`);
965
-
966
- // Hook 依赖 ontology + git-hooks;不部署完整 telemetry/skills
967
- const dataDir = path.join(cwd, 'skywalk-sdd');
968
- const ontologySrc = path.join(pkgPath, 'skywalk-sdd', 'ontology');
969
- const ontologyDst = path.join(dataDir, 'ontology');
970
- if (fs.existsSync(ontologySrc)) {
971
- copyDir(ontologySrc, ontologyDst);
972
- }
973
- deployQualityGateTemplates(cwd, { mode: 'commit-msg-only' });
1022
+ console.log(`🔗 轻量接入代码仓(无 skywalk-sdd/、无 skills): ${label}`);
974
1023
 
975
1024
  const remote = sddConfig.gitRemoteUrl(specRoot) || 'git@gitlab.example.com:biz/xxx-sdd-specs.git';
976
1025
  const sddYamlPath = path.join(cwd, '.sdd.yaml');
@@ -996,6 +1045,9 @@ function attachCodeRepoLite(codeRepoRoot, specRepoRoot, options = {}) {
996
1045
  }
997
1046
  console.log(` ✓ sdd.specPath = ${linked.path}`);
998
1047
 
1048
+ installCodeRepoCommitMsgHook(cwd, specRoot, options);
1049
+ console.log(' ✓ commit-msg → spec 仓 skywalk-sdd/git-hooks/(代码仓不落 skywalk-sdd/)');
1050
+
999
1051
  return { ok: true, repo: label, path: cwd, specPath: linked.path };
1000
1052
  }
1001
1053
 
@@ -1155,6 +1207,98 @@ function writeSddSpecRootHint(anchorDir, specPackageAbs) {
1155
1207
  return filePath;
1156
1208
  }
1157
1209
 
1210
+ /**
1211
+ * 工作区 / 单仓 Git 根的 skywalk-sdd/ 改为指向包裹包内唯一实体(符号链接)。
1212
+ * skills 仍可用 `node skywalk-sdd/log.cjs`,实体文件只在 *-sdd-specs/skywalk-sdd/。
1213
+ * 链接失败时回退为薄转发脚本(不复制 ontology)。
1214
+ */
1215
+ function deploySkywalkForwarders(anchorDir, specPackageAbs) {
1216
+ const anchor = path.resolve(anchorDir);
1217
+ const specAbs = path.resolve(specPackageAbs);
1218
+ const relSpec = path.relative(anchor, specAbs).replace(/\\/g, '/');
1219
+ const skywalkDir = path.join(anchor, 'skywalk-sdd');
1220
+ const targetSkywalk = path.join(specAbs, 'skywalk-sdd');
1221
+ fs.mkdirSync(path.join(skywalkDir, 'ontology'), { recursive: true });
1222
+
1223
+ const writeForwarder = (destRel, targetAbsPath) => {
1224
+ const dest = path.join(skywalkDir, destRel);
1225
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
1226
+ const relFromDestDir = path.relative(path.dirname(dest), targetAbsPath).replace(/\\/g, '/');
1227
+ const body = [
1228
+ '#!/usr/bin/env node',
1229
+ "'use strict';",
1230
+ "const { spawnSync } = require('child_process');",
1231
+ "const path = require('path');",
1232
+ `const target = path.resolve(__dirname, ${JSON.stringify(relFromDestDir)});`,
1233
+ 'const r = spawnSync(process.execPath, [target, ...process.argv.slice(2)], { stdio: \'inherit\' });',
1234
+ 'process.exit(r.status === null ? 1 : r.status);',
1235
+ '',
1236
+ ].join('\n');
1237
+ fs.writeFileSync(dest, body, 'utf8');
1238
+ };
1239
+
1240
+ const entries = [
1241
+ 'log.cjs',
1242
+ 'openspec-shim.cjs',
1243
+ 'context-client.cjs',
1244
+ 'apply-worktree-finish.cjs',
1245
+ ];
1246
+ for (const name of entries) {
1247
+ writeForwarder(name, path.join(targetSkywalk, name));
1248
+ }
1249
+ writeForwarder(path.join('ontology', 'cli.cjs'), path.join(targetSkywalk, 'ontology', 'cli.cjs'));
1250
+ console.log(` ✓ skywalk-sdd/ 转发脚本 → ${relSpec}/skywalk-sdd/(实体在包裹包)`);
1251
+ return { ok: true, mode: 'forwarders', relSpec };
1252
+ }
1253
+
1254
+ function linkSkywalkToSpecPackage(anchorDir, specPackageAbs) {
1255
+ const anchor = path.resolve(anchorDir);
1256
+ const targetAbs = path.join(path.resolve(specPackageAbs), 'skywalk-sdd');
1257
+ const linkAbs = path.join(anchor, 'skywalk-sdd');
1258
+
1259
+ if (!fs.existsSync(targetAbs)) {
1260
+ console.log(' ⚠️ 包裹包尚无 skywalk-sdd/,跳过工作区链接');
1261
+ return { ok: false, message: 'spec skywalk-sdd missing' };
1262
+ }
1263
+
1264
+ try {
1265
+ let st = null;
1266
+ try {
1267
+ st = fs.lstatSync(linkAbs);
1268
+ } catch (err) {
1269
+ if (!err || err.code !== 'ENOENT') throw err;
1270
+ }
1271
+ if (st) {
1272
+ if (st.isSymbolicLink()) {
1273
+ const current = fs.realpathSync(linkAbs);
1274
+ if (current === fs.realpathSync(targetAbs)) {
1275
+ console.log(' ✓ skywalk-sdd/ 已链接到包裹包(唯一实体)');
1276
+ return { ok: true, mode: 'symlink', linkAbs, targetAbs };
1277
+ }
1278
+ fs.unlinkSync(linkAbs);
1279
+ } else if (st.isDirectory()) {
1280
+ // 旧完整副本 → 删除后改链接(权威数据在包裹包)
1281
+ fs.rmSync(linkAbs, { recursive: true, force: true });
1282
+ } else {
1283
+ fs.unlinkSync(linkAbs);
1284
+ }
1285
+ }
1286
+ } catch (err) {
1287
+ console.log(` ⚠️ 清理旧 skywalk-sdd/ 失败: ${err.message}`);
1288
+ return deploySkywalkForwarders(anchor, specPackageAbs);
1289
+ }
1290
+
1291
+ const rel = path.relative(anchor, targetAbs);
1292
+ try {
1293
+ fs.symlinkSync(rel, linkAbs, process.platform === 'win32' ? 'junction' : 'dir');
1294
+ console.log(` ✓ skywalk-sdd/ → ${rel.replace(/\\/g, '/')}(唯一实体在包裹包)`);
1295
+ return { ok: true, mode: 'symlink', linkAbs, targetAbs, rel };
1296
+ } catch (err) {
1297
+ console.log(` ⚠️ 符号链接失败 (${err.message}),回退为转发脚本`);
1298
+ return deploySkywalkForwarders(anchor, specPackageAbs);
1299
+ }
1300
+ }
1301
+
1158
1302
  /**
1159
1303
  * 检测当前项目已初始化的 AI 编辑器
1160
1304
  */
@@ -1185,6 +1329,8 @@ function updateGitignore() {
1185
1329
  '.agents/commands/personal-*',
1186
1330
  'skywalk-sdd/events/',
1187
1331
  'skywalk-sdd/state/',
1332
+ '**/skywalk-sdd/events/',
1333
+ '**/skywalk-sdd/state/',
1188
1334
  '.worktrees/'
1189
1335
  ];
1190
1336
 
@@ -1198,9 +1344,11 @@ function updateGitignore() {
1198
1344
  .workbuddy/commands/personal-*
1199
1345
  .agents/commands/personal-*
1200
1346
 
1201
- # SDD Telemetry 数据(本地度量,可选提交;本体 JSON 与报告随 openspec/changes/<change>/ 提交)
1347
+ # SDD Telemetry 数据(实体在 *-sdd-specs/skywalk-sdd/;工作区可能是 symlink)
1202
1348
  skywalk-sdd/events/
1203
1349
  skywalk-sdd/state/
1350
+ **/skywalk-sdd/events/
1351
+ **/skywalk-sdd/state/
1204
1352
 
1205
1353
  # SDD Apply 隔离 worktree(本地临时目录)
1206
1354
  .worktrees/
@@ -1360,8 +1508,6 @@ async function main() {
1360
1508
  deployProfileArtifacts(selectedTools, workspaceRoot, getPackagePath());
1361
1509
 
1362
1510
  if (layout.isWorkspace) {
1363
- deployTelemetryDataDir(workspaceRoot);
1364
-
1365
1511
  if (!specPackage) {
1366
1512
  console.log('📦 未发现 spec 仓,正在创建统一 SDD 包裹包(独立 Git)...');
1367
1513
  specPackage = workspaceLayout.ensureSpecPackage(workspaceRoot, { initGit: true });
@@ -1373,6 +1519,8 @@ async function main() {
1373
1519
  asIndependentGit: true,
1374
1520
  });
1375
1521
  writeSddSpecRootHint(workspaceRoot, specPackage.abs);
1522
+ // 工作区不复制 skywalk-sdd 实体,只链到包裹包(skills 路径仍可用)
1523
+ linkSkywalkToSpecPackage(workspaceRoot, specPackage.abs);
1376
1524
 
1377
1525
  // 重新探测,刷新 layout.specRepo / codeRepos
1378
1526
  layout = workspaceLayout.detectWorkspaceLayout(workspaceRoot);
@@ -1390,7 +1538,6 @@ async function main() {
1390
1538
  } else {
1391
1539
  // 单仓:Git 根只放代码 + skills + Hook;SDD 内容进统一包裹子目录
1392
1540
  const isGit = workspaceLayout.isGitRepo(workspaceRoot);
1393
- deployTelemetryDataDir(workspaceRoot);
1394
1541
 
1395
1542
  if (isGit) {
1396
1543
  console.log('📦 单仓模式:创建/复用仓内统一 SDD 包裹目录...');
@@ -1402,9 +1549,10 @@ async function main() {
1402
1549
  asIndependentGit: false,
1403
1550
  });
1404
1551
  writeSddSpecRootHint(workspaceRoot, specPackage.abs);
1552
+ linkSkywalkToSpecPackage(workspaceRoot, specPackage.abs);
1405
1553
 
1406
1554
  deployMonoSddYaml(workspaceRoot, { specPathRelative: specPackage.name });
1407
- // Hook 装在 Git 根;Trailer 读包裹目录里的 sdd.config / openspec
1555
+ // Hook 装在 Git 根;经 symlink 写入包裹包内的 git-hooks
1408
1556
  deployQualityGateTemplates(workspaceRoot, { mode: 'commit-msg-only' });
1409
1557
  const linked = sddConfig.setSpecPath(workspaceRoot, specPackage.abs, {
1410
1558
  allowNestedPackage: true,
@@ -1422,6 +1570,8 @@ async function main() {
1422
1570
  skipOpenspec,
1423
1571
  asIndependentGit: false,
1424
1572
  });
1573
+ writeSddSpecRootHint(workspaceRoot, specPackage.abs);
1574
+ linkSkywalkToSpecPackage(workspaceRoot, specPackage.abs);
1425
1575
  deployDotSddYaml(workspaceRoot);
1426
1576
  }
1427
1577
  }
@@ -1466,12 +1616,13 @@ async function main() {
1466
1616
  console.log('工作区部署结果:');
1467
1617
  console.log(' 🎯 工作目录 .*/skills/opsx-*/ # skills 只装这一份');
1468
1618
  console.log(' 📋 .sdd-workspace.yaml # 子仓编排清单');
1619
+ console.log(' 🔗 skywalk-sdd/ → *-sdd-specs/skywalk-sdd/ # 符号链接,实体只在包裹包');
1469
1620
  if (specPackage) {
1470
1621
  console.log(` 📄 ${specPackage.name}/ # 统一 SDD 包裹包(独立 Git)`);
1471
1622
  console.log(' modules.yaml / sdd.config.yaml / openspec / openspec-templates / skywalk-sdd');
1472
1623
  }
1473
1624
  for (const repo of layout.codeRepos) {
1474
- console.log(` 🔗 ${repo.name}/ ← commit-msg Hook + .sdd.yaml(无 skills)`);
1625
+ console.log(` 🔗 ${repo.name}/ ← commit-msg Hook + .sdd.yaml(无 skywalk-sdd/、无 skills)`);
1475
1626
  }
1476
1627
  console.log();
1477
1628
  console.log('新代码仓加入后执行:');
@@ -1485,6 +1636,7 @@ async function main() {
1485
1636
  console.log(' modules.yaml / sdd.config.yaml / openspec / openspec-templates / skywalk-sdd');
1486
1637
  }
1487
1638
  console.log(' 🔗 .sdd.yaml (layout: mono) + commit-msg Hook');
1639
+ console.log(' 🔗 skywalk-sdd/ → *-sdd-specs/skywalk-sdd/ # 符号链接,实体只在包裹包');
1488
1640
  }
1489
1641
  if (selectedTools.includes('kunlunzhima')) {
1490
1642
  console.log(' 📎 .kunlunzhima/commands/opsx/ # KunlunZhima OPSX command bridge(11 个)');
@@ -1496,10 +1648,6 @@ async function main() {
1496
1648
  if (selectedTools.includes('claude')) {
1497
1649
  console.log(' 🪝 .claude/hooks/ # Claude Code SDD Hook Pack(可选增强)');
1498
1650
  }
1499
- if (!layout.isWorkspace) {
1500
- console.log(' 📊 skywalk-sdd/ # Hook 运行时 ontology(Git 根)');
1501
- console.log(' 🧰 skywalk-sdd/git-hooks/ # commit-msg Trailer Hook');
1502
- }
1503
1651
  console.log();
1504
1652
 
1505
1653
  // 统一的 skill 格式说明
@@ -1564,6 +1712,8 @@ module.exports = {
1564
1712
  deployMonoSddYaml,
1565
1713
  populateSpecPackage,
1566
1714
  writeSddSpecRootHint,
1715
+ linkSkywalkToSpecPackage,
1716
+ deploySkywalkForwarders,
1567
1717
  deployQualityGateTemplates,
1568
1718
  deployTelemetryDataDir,
1569
1719
  attachCodeRepoLite,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kld-sdd",
3
- "version": "2.6.10",
3
+ "version": "2.6.11",
4
4
  "description": "KLD SDD OpenSpec 项目初始化工具 - 一键部署 SDD skills",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -67,17 +67,40 @@ function detectEntryMode(projectRoot) {
67
67
  return 'unknown';
68
68
  }
69
69
 
70
+ function resolveCommitMsgScriptPath(projectRoot) {
71
+ const localScript = path.join(projectRoot, 'skywalk-sdd', 'git-hooks', 'commit-msg-sdd-trailer.cjs');
72
+ if (fs.existsSync(localScript)) return localScript;
73
+ // 多仓代码仓:脚本在 spec 包裹包,不在本仓 skywalk-sdd/
74
+ try {
75
+ const context = sddConfig.resolveCodeRepoContext(projectRoot);
76
+ if (context && context.specPath) {
77
+ const remote = path.join(context.specPath, 'skywalk-sdd', 'git-hooks', 'commit-msg-sdd-trailer.cjs');
78
+ if (fs.existsSync(remote)) return remote;
79
+ }
80
+ } catch {
81
+ // ignore
82
+ }
83
+ if (sddConfig.isMonoLayout(projectRoot)) {
84
+ const monoSpec = sddConfig.resolveMonoSpecRoot(projectRoot);
85
+ if (monoSpec) {
86
+ const nested = path.join(monoSpec, 'skywalk-sdd', 'git-hooks', 'commit-msg-sdd-trailer.cjs');
87
+ if (fs.existsSync(nested)) return nested;
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+
70
93
  function checkHookInstalled(projectRoot) {
71
94
  const hookPath = path.join(projectRoot, '.git', 'hooks', 'commit-msg');
72
- const scriptPath = path.join(projectRoot, 'skywalk-sdd', 'git-hooks', 'commit-msg-sdd-trailer.cjs');
95
+ const scriptPath = resolveCommitMsgScriptPath(projectRoot);
73
96
  if (!fs.existsSync(path.join(projectRoot, '.git'))) {
74
97
  return { status: STATUS.SKIP, message: '非 Git 仓库,跳过 Hook 检查' };
75
98
  }
76
- if (!fs.existsSync(scriptPath)) {
99
+ if (!scriptPath) {
77
100
  return {
78
101
  status: STATUS.FIXABLE,
79
102
  code: 'HOOK_SCRIPT_MISSING',
80
- message: 'commit-msg 脚本未部署,可重新执行 kld-sdd-init 或手动复制模板',
103
+ message: 'commit-msg 脚本未找到(本仓或 sdd.specPath 下均无 skywalk-sdd/git-hooks/),可重新执行 kld-sdd-init',
81
104
  fix: { type: 'deploy-hook-script' },
82
105
  };
83
106
  }
@@ -169,8 +169,18 @@ function upsertTrailers(message, trailers) {
169
169
  return body;
170
170
  }
171
171
 
172
+ function resolveMsgFile(argv = process.argv) {
173
+ // Git 传入的 message 文件路径;跳过 --project= 等可选参数
174
+ for (let i = 2; i < argv.length; i += 1) {
175
+ const arg = argv[i];
176
+ if (!arg || arg.startsWith('--')) continue;
177
+ return arg;
178
+ }
179
+ return null;
180
+ }
181
+
172
182
  function main(env = process.env) {
173
- const msgFile = process.argv[2];
183
+ const msgFile = resolveMsgFile();
174
184
  if (!msgFile) {
175
185
  fail('缺少 commit message 文件参数');
176
186
  }