kld-sdd 2.6.21 → 2.7.3

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.
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
  # KLD SDD Pre-Commit Hook
3
- # 远程引用 spec skywalk-sdd/git-hooks/ 下的 .cjs 脚本
4
- # 兼容单仓库/多仓库/单仓mono布局
3
+ # 在下方 SCRIPTS 列表中声明要加载的 .cjs 脚本
4
+ # 删除某行 = 禁用该插件
5
5
  # marker: KLD SDD quality gate
6
6
 
7
7
  # 从 hook 所在目录推算 git 根目录
@@ -38,20 +38,54 @@ if [ -z "$hooks_dir" ]; then
38
38
  fi
39
39
 
40
40
  if [ -z "$hooks_dir" ]; then
41
- echo "[SDD] 未找到 spec 仓的 skywalk-sdd/git-hooks 目录,跳过 pre-commit 检查"
41
+ echo "[SDD] 未找到 spec 仓的 skywalk-sdd/git-hooks 目录,跳过 pre-commit"
42
42
  exit 0
43
43
  fi
44
44
 
45
45
  echo "[SDD] 执行提交前检查..."
46
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
47
+ # 从配置文件读取插件列表
48
+ config_file="$hooks_dir/hooks.config"
49
+ if [ ! -f "$config_file" ]; then
50
+ echo "[SDD] ️ 配置文件不存在:hooks.config,使用默认插件"
51
+ SCRIPTS="pre-commit-sdd-check.cjs pre-commit-consistency-check.cjs"
52
+ else
53
+ # 读取 [pre-commit] 段落的配置
54
+ in_section=0
55
+ SCRIPTS=""
56
+ while IFS= read -r line; do
57
+ line=$(printf '%s' "$line" | tr -d '\r')
58
+ case "$line" in
59
+ "[pre-commit]") in_section=1 ;;
60
+ "["*) in_section=0 ;;
61
+ "#"*|"") ;;
62
+ *)
63
+ if [ "$in_section" -eq 1 ]; then
64
+ SCRIPTS="$SCRIPTS $line"
65
+ fi
66
+ ;;
67
+ esac
68
+ done < "$config_file"
50
69
  fi
51
70
 
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
71
+ executed=0
72
+ failed=0
73
+ for script_name in $SCRIPTS; do
74
+ script="$hooks_dir/$script_name"
75
+ if [ ! -f "$script" ]; then
76
+ echo "[SDD] ⚠️ 插件不存在:$script_name"
77
+ continue
78
+ fi
79
+ executed=$((executed + 1))
80
+ echo "[SDD] 执行 pre-commit 插件:$script_name"
81
+ node "$script" --project="$git_root" "$@" || failed=$((failed + 1))
82
+ done
56
83
 
57
- echo "[SDD] 所有 pre-commit 检查通过"
84
+ if [ "$executed" -eq 0 ]; then
85
+ echo "[SDD] 无 pre-commit 插件,跳过"
86
+ elif [ "$failed" -gt 0 ]; then
87
+ echo "[SDD] ❌ 共执行 $executed 个 pre-commit 插件,$failed 个失败"
88
+ exit 1
89
+ else
90
+ echo "[SDD] ✅ 共执行 $executed 个 pre-commit 插件,全部通过"
91
+ fi
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
  # KLD SDD Pre-Push Hook
3
- # 远程引用 spec skywalk-sdd/git-hooks/ 下的 .cjs 脚本
4
- # 兼容单仓库/多仓库/单仓mono布局
3
+ # 在下方 SCRIPTS 列表中声明要加载的 .cjs 脚本
4
+ # 删除某行 = 禁用该插件
5
5
  # marker: KLD SDD quality gate
6
6
 
7
7
  # 从 hook 所在目录推算 git 根目录
@@ -38,20 +38,62 @@ if [ -z "$hooks_dir" ]; then
38
38
  fi
39
39
 
40
40
  if [ -z "$hooks_dir" ]; then
41
- echo "[SDD] 未找到 spec 仓的 skywalk-sdd/git-hooks 目录,跳过 pre-push 检查"
41
+ echo "[SDD] 未找到 spec 仓的 skywalk-sdd/git-hooks 目录,跳过 pre-push"
42
42
  exit 0
43
43
  fi
44
44
 
45
45
  echo "[SDD] 执行推送前检查..."
46
46
 
47
- # 1. doctor 质量门禁检查
48
- if [ -f "$hooks_dir/pre-push-sdd-check.cjs" ]; then
49
- node "$hooks_dir/pre-push-sdd-check.cjs" --project="$git_root" "$@" || exit 1
50
- fi
47
+ # 保存 pre-push stdin(Git 标准输入:local_ref local_sha remote_ref remote_sha)
48
+ # 供下游 .cjs 读取本次 push 的 commit 范围
49
+ SDD_PRE_PUSH_REFS=""
50
+ while IFS= read -r line; do
51
+ SDD_PRE_PUSH_REFS="${SDD_PRE_PUSH_REFS}${line}\n"
52
+ done
53
+ export SDD_PRE_PUSH_REFS
51
54
 
52
- # 2. 代码-spec 一致性检查(推送前更严格)
53
- if [ -f "$hooks_dir/pre-push-consistency-check.cjs" ]; then
54
- node "$hooks_dir/pre-push-consistency-check.cjs" --project="$git_root" "$@" || exit 1
55
+ # 从配置文件读取插件列表
56
+ config_file="$hooks_dir/hooks.config"
57
+ if [ ! -f "$config_file" ]; then
58
+ echo "[SDD] ⚠️ 配置文件不存在:hooks.config,使用默认插件"
59
+ SCRIPTS="pre-push-sdd-check.cjs pre-push-consistency-check.cjs"
60
+ else
61
+ # 读取 [pre-push] 段落的配置
62
+ in_section=0
63
+ SCRIPTS=""
64
+ while IFS= read -r line; do
65
+ line=$(printf '%s' "$line" | tr -d '\r')
66
+ case "$line" in
67
+ "[pre-push]") in_section=1 ;;
68
+ "["*) in_section=0 ;;
69
+ "#"*|"") ;;
70
+ *)
71
+ if [ "$in_section" -eq 1 ]; then
72
+ SCRIPTS="$SCRIPTS $line"
73
+ fi
74
+ ;;
75
+ esac
76
+ done < "$config_file"
55
77
  fi
56
78
 
57
- echo "[SDD] ✅ 所有 pre-push 检查通过"
79
+ executed=0
80
+ failed=0
81
+ for script_name in $SCRIPTS; do
82
+ script="$hooks_dir/$script_name"
83
+ if [ ! -f "$script" ]; then
84
+ echo "[SDD] ⚠️ 插件不存在:$script_name"
85
+ continue
86
+ fi
87
+ executed=$((executed + 1))
88
+ echo "[SDD] 执行 pre-push 插件:$script_name"
89
+ node "$script" --project="$git_root" "$@" || failed=$((failed + 1))
90
+ done
91
+
92
+ if [ "$executed" -eq 0 ]; then
93
+ echo "[SDD] 无 pre-push 插件,跳过"
94
+ elif [ "$failed" -gt 0 ]; then
95
+ echo "[SDD] ❌ 共执行 $executed 个 pre-push 插件,$failed 个失败"
96
+ exit 1
97
+ else
98
+ echo "[SDD] ✅ 共执行 $executed 个 pre-push 插件,全部通过"
99
+ fi
@@ -39,6 +39,101 @@ function discoverActiveChanges(specRoot) {
39
39
  });
40
40
  }
41
41
 
42
+ // --- Push 范围收敛 ---
43
+
44
+ /**
45
+ * 读取 pre-push 引用范围。
46
+ * 优先从 shell hook 通过环境变量 SDD_PRE_PUSH_REFS 传入(shell hook 已消费 stdin),
47
+ * 降级时尝试直接读 stdin。
48
+ */
49
+ function readPushRefLines() {
50
+ const envRefs = process.env.SDD_PRE_PUSH_REFS;
51
+ if (envRefs) {
52
+ // shell hook 用 \n 字面量拼接,也可能含真实换行符
53
+ return envRefs.split(/\\n|\n/).filter(Boolean);
54
+ }
55
+
56
+ try {
57
+ if (process.stdin.isTTY) return [];
58
+ const buffer = fs.readFileSync(0, 'utf8'); // fd 0 = stdin
59
+ return buffer.split(/\r?\n/).filter(Boolean);
60
+ } catch {
61
+ return [];
62
+ }
63
+ }
64
+
65
+ /**
66
+ * 从 pre-push stdin 行解析 commit 范围,获取本次 push 实际变更的文件列表
67
+ */
68
+ function getPushRangeFiles(projectRoot) {
69
+ const lines = readPushRefLines();
70
+ if (lines.length === 0) return null;
71
+
72
+ const files = new Set();
73
+ for (const line of lines) {
74
+ const parts = line.split(/\s+/);
75
+ if (parts.length < 4) continue;
76
+ const localSha = parts[1];
77
+ const remoteSha = parts[3];
78
+
79
+ // 删除分支:localSha 为全 0
80
+ if (localSha === '0000000000000000000000000000000000000000') continue;
81
+
82
+ try {
83
+ let diffFiles;
84
+ if (remoteSha === '0000000000000000000000000000000000000000') {
85
+ // 新建分支:用 git show 获取该 commit 引入的文件
86
+ diffFiles = execFileSync('git', ['show', '--name-only', '--pretty=format:', localSha], {
87
+ cwd: projectRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
88
+ }).split(/\r?\n/).filter(Boolean);
89
+ } else {
90
+ // 已有分支:用 git diff 获取范围变更
91
+ diffFiles = execFileSync('git', ['diff', `${remoteSha}..${localSha}`, '--name-only'], {
92
+ cwd: projectRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
93
+ }).split(/\r?\n/).filter(Boolean);
94
+ }
95
+ for (const f of diffFiles) files.add(f.replace(/\\/g, '/'));
96
+ } catch {
97
+ // 单个 ref 失败不阻断,继续处理其他 ref
98
+ }
99
+ }
100
+
101
+ return files.size > 0 ? Array.from(files) : null;
102
+ }
103
+
104
+ function normalizePath(filePath) {
105
+ return filePath.replace(/\\/g, '/');
106
+ }
107
+
108
+ function getChangeNameFromPath(filePath) {
109
+ const normalized = normalizePath(filePath);
110
+ const match = normalized.match(/^openspec\/changes\/([^/]+)\//);
111
+ return match ? match[1] : '';
112
+ }
113
+
114
+ /**
115
+ * 从 push 的变更文件列表中提取涉及的 change name
116
+ */
117
+ function discoverChangesFromPushFiles(projectRoot, specRoot) {
118
+ const pushFiles = getPushRangeFiles(projectRoot);
119
+ if (!pushFiles) return null; // 降级信号
120
+
121
+ const changes = new Set();
122
+ for (const file of pushFiles) {
123
+ const changeName = getChangeNameFromPath(file);
124
+ if (changeName) changes.add(changeName);
125
+ }
126
+
127
+ if (changes.size === 0) {
128
+ console.log('[SDD push-consistency-check] 本次 push 不涉及 openspec 变更,跳过');
129
+ process.exit(0);
130
+ }
131
+
132
+ // 过滤掉已归档的 change
133
+ const activeChanges = new Set(discoverActiveChanges(specRoot));
134
+ return Array.from(changes).filter(cn => activeChanges.has(cn));
135
+ }
136
+
42
137
  // --- 报告查找与解析 ---
43
138
 
44
139
  function findReport(specRoot, changeName, type) {
@@ -259,13 +354,28 @@ function main() {
259
354
 
260
355
  const specRoot = resolveSpecRoot();
261
356
 
262
- const changes = discoverActiveChanges(specRoot);
357
+ // 优先尝试按本次 push 范围收敛变更
358
+ let changes = discoverChangesFromPushFiles(projectRoot, specRoot);
359
+ let modifiedFiles = null;
360
+
361
+ if (changes === null) {
362
+ // 降级:stdin 读取失败,回退到现有行为
363
+ console.log('[SDD push-consistency-check] 无法读取 push 范围,降级为全量检查');
364
+ changes = discoverActiveChanges(specRoot);
365
+ modifiedFiles = getModifiedFiles(projectRoot);
366
+ } else if (changes.length === 0) {
367
+ console.log('[SDD push-consistency-check] 无相关活跃变更,跳过');
368
+ process.exit(0);
369
+ } else {
370
+ // 收敛成功:用 push 范围文件做过期判定
371
+ modifiedFiles = getPushRangeFiles(projectRoot) || [];
372
+ }
373
+
263
374
  if (!changes.length) {
264
375
  console.log('[SDD push-consistency-check] 无活跃变更,跳过');
265
376
  process.exit(0);
266
377
  }
267
378
 
268
- const modifiedFiles = getModifiedFiles(projectRoot);
269
379
  const missing = [];
270
380
  const stale = [];
271
381
  const needsConfirmation = [];
@@ -1,130 +0,0 @@
1
- // kld-T03 — auth 子命令 CLI 薄层
2
- // 仅做参数解析、交互提示与退出码;业务逻辑在 lib/device-auth.js + skywalk-sdd/lib/user-config.cjs
3
- 'use strict';
4
-
5
- const readline = require('node:readline');
6
-
7
- const {
8
- resolveServer,
9
- runDeviceFlow,
10
- DEFAULT_SERVER_URL,
11
- } = require('./device-auth.js');
12
- const {
13
- readUserConfig,
14
- clearUserToken,
15
- redactToken,
16
- USER_CONFIG_VERSION,
17
- } = require('../skywalk-sdd/lib/user-config.cjs');
18
- const { normalizeServerUrl } = require('../skywalk-sdd/lib/usage-contract.cjs');
19
-
20
- async function ask(rl, question) {
21
- return new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim())));
22
- }
23
-
24
- async function cmdStatus() {
25
- const cfg = readUserConfig();
26
- if (!cfg || !cfg.token) {
27
- console.log('未绑定平台账号。');
28
- console.log('使用 `kld-sdd auth login` 完成 Device Flow 绑定。');
29
- return 0;
30
- }
31
- console.log('已绑定平台账号:');
32
- console.log(` server: ${cfg.server || '(未设置)'}`);
33
- console.log(` userName: ${cfg.userName || '(未知)'}`);
34
- console.log(` boundAt: ${cfg.boundAt || '(未知)'}`);
35
- console.log(` token: ${redactToken(cfg.token)}`);
36
- return 0;
37
- }
38
-
39
- async function cmdLogout() {
40
- const removed = clearUserToken();
41
- if (removed) {
42
- console.log('✅ 已清除本地绑定(其他配置保留)。');
43
- } else {
44
- console.log('未检测到已绑定 token。');
45
- }
46
- return 0;
47
- }
48
-
49
- async function cmdLogin(args) {
50
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
51
- try {
52
- // 解析 server:已有绑定 → env → 交互输入
53
- let server = resolveServer();
54
- const forceArg = args.find((a) => a.startsWith('--server='));
55
- if (forceArg) {
56
- const normalized = normalizeServerUrl(forceArg.slice('--server='.length));
57
- if (!normalized) {
58
- console.error('❌ --server 必须是 http/https URL');
59
- return 1;
60
- }
61
- server = normalized;
62
- }
63
- if (!server) {
64
- const answer = await ask(rl, `请输入平台地址(http/https,回车默认 ${DEFAULT_SERVER_URL}):`);
65
- const normalized = normalizeServerUrl(answer || DEFAULT_SERVER_URL);
66
- if (!normalized) {
67
- console.error('❌ 非法地址,必须 http/https');
68
- return 1;
69
- }
70
- server = normalized;
71
- }
72
-
73
- const existing = readUserConfig();
74
- if (existing && existing.token && existing.server === server) {
75
- const answer = await ask(rl, `已绑定 ${server}(userName=${existing.userName || '未知'}),是否更新绑定?[y/N] `);
76
- if (!/^[yY]/.test(answer)) {
77
- console.log('保留原有绑定。');
78
- return 0;
79
- }
80
- }
81
-
82
- const pkg = require('../package.json');
83
- console.log(`正在通过 Device Flow 绑定到 ${server} ...`);
84
- const ac = new AbortController();
85
- const sigint = () => { ac.abort(); };
86
- process.on('SIGINT', sigint);
87
- try {
88
- const result = await runDeviceFlow({
89
- server,
90
- clientName: 'kld-sdd',
91
- clientVersion: pkg && pkg.version ? String(pkg.version) : 'unknown',
92
- onPrint: (msg) => console.log(msg),
93
- signal: ac.signal,
94
- });
95
- if (result.status === 'bound') {
96
- console.log('✅ 绑定成功。');
97
- return 0;
98
- }
99
- if (result.status === 'aborted') {
100
- console.log('已取消。');
101
- return 130;
102
- }
103
- if (result.status === 'expired') {
104
- console.log('⏱ 授权已过期,请重试。');
105
- return 1;
106
- }
107
- if (result.status === 'denied') {
108
- console.log('已拒绝授权。');
109
- return 1;
110
- }
111
- console.error(`❌ 绑定失败:${result.reason || 'unknown'}`);
112
- return 1;
113
- } finally {
114
- process.removeListener('SIGINT', sigint);
115
- }
116
- } finally {
117
- rl.close();
118
- }
119
- }
120
-
121
- async function runAuthCommand(sub, args) {
122
- if (sub === 'status') return cmdStatus(args);
123
- if (sub === 'login') return cmdLogin(args);
124
- if (sub === 'logout') return cmdLogout(args);
125
- console.error(`未知 auth 子命令:${sub}`);
126
- console.error('用法: kld-sdd auth [status|login|logout] [--server=<url>]');
127
- return 2;
128
- }
129
-
130
- module.exports = { runAuthCommand };