kld-sdd 2.6.21 → 2.7.8
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/README.md +12 -5
- package/USABILITY.md +84 -0
- package/bin/kld-sdd-init.js +3 -9
- package/kld-sdd-guide.html +16 -4
- package/lib/hook-gate-core.js +327 -0
- package/lib/init.js +241 -94
- package/lib/scale-thresholds.json +19 -0
- package/lib/skills-bundle.js +2 -1
- package/package.json +5 -3
- package/skywalk-sdd/context-client.cjs +50 -30
- package/skywalk-sdd/index.cjs +49 -16
- package/skywalk-sdd/kb-sync-identity.cjs +99 -451
- package/skywalk-sdd/kb-upload.cjs +67 -44
- package/skywalk-sdd/lib/git-identity.cjs +270 -0
- package/skywalk-sdd/lib/usage-contract.cjs +13 -81
- package/skywalk-sdd/lib/usage-reporter.cjs +235 -104
- package/skywalk-sdd/lib/user-config.cjs +21 -46
- package/skywalk-sdd/metrics-v3.cjs +2 -2
- package/skywalk-sdd/ontology/active-changes.cjs +2 -1
- package/skywalk-sdd/ontology/archive-package.cjs +1 -1
- package/skywalk-sdd/ontology/artifact-parser.cjs +8 -6
- package/skywalk-sdd/ontology/id.cjs +5 -4
- package/skywalk-sdd/ontology/identity-index.cjs +4 -3
- package/skywalk-sdd/ontology/list-changes.cjs +1 -1
- package/skywalk-sdd/ontology/runtime.cjs +7 -3
- package/skywalk-sdd/ontology/schema.cjs +2 -0
- package/skywalk-sdd/ontology/traceability-validator.cjs +74 -4
- package/skywalk-sdd/ontology/workspace-layout.cjs +25 -5
- package/skywalk-sdd/reporting/change-report-model.cjs +4 -3
- package/templates/git-hooks/commit-msg +71 -27
- package/templates/git-hooks/consistency-check-core.cjs +1087 -0
- package/templates/git-hooks/hooks.config +38 -0
- package/templates/git-hooks/pre-commit +62 -27
- package/templates/git-hooks/pre-commit-consistency-check.cjs +29 -332
- package/templates/git-hooks/pre-commit-sdd-check.cjs +98 -0
- package/templates/git-hooks/pre-push +70 -27
- package/templates/git-hooks/pre-push-consistency-check.cjs +61 -299
- package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +1 -1
- package/templates/openspec/tasks.md +3 -3
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +43 -6
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +1 -1
- package/templates/skills/kld-sdd/opsx-apply/reference.md +1 -1
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +9 -13
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +54 -328
- package/templates/skills/kld-sdd/opsx-check/checklist.md +7 -4
- package/templates/skills/kld-sdd/opsx-check/reference.md +58 -0
- package/templates/skills/kld-sdd/opsx-check/result-template.json +52 -0
- package/templates/skills/kld-sdd/opsx-check/reviewer.md +100 -0
- package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +82 -451
- package/templates/skills/kld-sdd/opsx-consistency-check/{reference.md → references/reference.md} +0 -1
- package/templates/skills/kld-sdd/opsx-consistency-check/scripts/scripts.cjs +517 -0
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +10 -30
- package/templates/skills/kld-sdd/opsx-design/checklist.md +3 -4
- package/templates/skills/kld-sdd/opsx-design/reference.md +1 -1
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +12 -74
- package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +7 -5
- package/templates/skills/kld-sdd/opsx-ontology-query/phase-1-prechange.md +2 -2
- package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +1 -1
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +28 -73
- package/templates/skills/kld-sdd/opsx-propose/checklist.md +8 -8
- package/templates/skills/kld-sdd/opsx-propose/interaction-policy.md +28 -0
- package/templates/skills/kld-sdd/opsx-propose/reference.md +12 -46
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +14 -61
- package/templates/skills/kld-sdd/opsx-spec/checklist.md +3 -3
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +38 -32
- package/templates/skills/kld-sdd/opsx-task/checklist.md +5 -6
- package/templates/skills/kld-sdd/opsx-test/SKILL.md +2 -0
- package/templates/skills/kld-sdd/tdd-rules/rules/tdd-strategy-selection.md +1 -1
- package/lib/device-auth-cli.js +0 -130
- package/lib/device-auth.js +0 -345
- package/lib/init-account-binding.js +0 -108
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
# KLD SDD Pre-Push Hook
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# 在下方 SCRIPTS 列表中声明要加载的 .cjs 脚本
|
|
4
|
+
# 删除某行 = 禁用该插件
|
|
5
5
|
# marker: KLD SDD quality gate
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
# Git supplies the working tree context; this also supports worktrees and core.hooksPath.
|
|
8
|
+
git_root=$(git rev-parse --show-toplevel 2>/dev/null) || {
|
|
9
|
+
echo "[SDD] 无法定位当前 Git 工作区"
|
|
10
|
+
exit 1
|
|
11
|
+
}
|
|
12
12
|
hooks_dir=""
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
spec_path
|
|
16
|
-
if [ -n "$spec_path" ] && [ -d "$spec_path/skywalk-sdd/git-hooks" ]; then
|
|
13
|
+
spec_path=$(git -C "$git_root" config --local sdd.specPath 2>/dev/null)
|
|
14
|
+
if [ -n "$spec_path" ]; then
|
|
15
|
+
case "$spec_path" in /*) ;; *) spec_path="$git_root/$spec_path" ;; esac
|
|
17
16
|
hooks_dir="$spec_path/skywalk-sdd/git-hooks"
|
|
17
|
+
elif [ -f "$git_root/.sdd-spec-root" ]; then
|
|
18
|
+
spec_rel=$(head -1 "$git_root/.sdd-spec-root" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
19
|
+
case "$spec_rel" in /*) hooks_dir="$spec_rel/skywalk-sdd/git-hooks" ;; *) hooks_dir="$git_root/$spec_rel/skywalk-sdd/git-hooks" ;; esac
|
|
18
20
|
fi
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
21
|
+
if [ -n "$hooks_dir" ] && [ ! -d "$hooks_dir" ]; then
|
|
22
|
+
echo "[SDD] 已配置的 Spec 路径失效:$hooks_dir"
|
|
23
|
+
echo "[SDD] 请恢复 Spec 仓,或修正 sdd.specPath / .sdd-spec-root 后重试"
|
|
24
|
+
exit 1
|
|
26
25
|
fi
|
|
27
26
|
|
|
28
27
|
# 方法3: 从 git 根向上搜索 skywalk-sdd/git-hooks(spec 仓本身或父级)
|
|
@@ -38,20 +37,64 @@ if [ -z "$hooks_dir" ]; then
|
|
|
38
37
|
fi
|
|
39
38
|
|
|
40
39
|
if [ -z "$hooks_dir" ]; then
|
|
41
|
-
echo "[SDD] 未找到 spec 仓的 skywalk-sdd/git-hooks 目录,跳过 pre-push
|
|
40
|
+
echo "[SDD] 未找到 spec 仓的 skywalk-sdd/git-hooks 目录,跳过 pre-push"
|
|
42
41
|
exit 0
|
|
43
42
|
fi
|
|
44
43
|
|
|
45
44
|
echo "[SDD] 执行推送前检查..."
|
|
46
45
|
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
# 保存 pre-push stdin(Git 标准输入:local_ref local_sha remote_ref remote_sha)
|
|
47
|
+
# 供下游 .cjs 读取本次 push 的 commit 范围
|
|
48
|
+
SDD_PRE_PUSH_REFS=""
|
|
49
|
+
while IFS= read -r line; do
|
|
50
|
+
SDD_PRE_PUSH_REFS="${SDD_PRE_PUSH_REFS}${line}\n"
|
|
51
|
+
done
|
|
52
|
+
export SDD_PRE_PUSH_REFS
|
|
51
53
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
# 从配置文件读取插件列表
|
|
55
|
+
config_file="$hooks_dir/hooks.config"
|
|
56
|
+
if [ ! -f "$config_file" ]; then
|
|
57
|
+
echo "[SDD] ⚠️ 配置文件不存在:hooks.config,使用默认插件"
|
|
58
|
+
SCRIPTS="pre-push-sdd-check.cjs pre-push-consistency-check.cjs"
|
|
59
|
+
else
|
|
60
|
+
# 读取 [pre-push] 段落的配置
|
|
61
|
+
in_section=0
|
|
62
|
+
SCRIPTS=""
|
|
63
|
+
CR=$(printf '\r')
|
|
64
|
+
while IFS= read -r line; do
|
|
65
|
+
line=${line%"$CR"}
|
|
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
|
-
|
|
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
|
+
failed=$((failed + 1))
|
|
86
|
+
continue
|
|
87
|
+
fi
|
|
88
|
+
executed=$((executed + 1))
|
|
89
|
+
echo "[SDD] 执行 pre-push 插件:$script_name"
|
|
90
|
+
node "$script" --project="$git_root" "$@" || failed=$((failed + 1))
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
if [ "$failed" -gt 0 ]; then
|
|
94
|
+
echo "[SDD] ❌ 共执行 $executed 个 pre-push 插件,$failed 个失败"
|
|
95
|
+
exit 1
|
|
96
|
+
elif [ "$executed" -eq 0 ]; then
|
|
97
|
+
echo "[SDD] 配置已禁用 pre-push 插件"
|
|
98
|
+
else
|
|
99
|
+
echo "[SDD] ✅ 共执行 $executed 个 pre-push 插件,全部通过"
|
|
100
|
+
fi
|
|
@@ -2,325 +2,87 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* pre-push-consistency-check.cjs
|
|
4
4
|
*
|
|
5
|
-
* Pre-push
|
|
6
|
-
*
|
|
5
|
+
* Pre-push 轻量门禁:仅检查待推送范围涉及报告的存在性、是否过期。
|
|
6
|
+
* - diff 驱动:仅检查待推送文件与报告 coveredFiles 有交集的 change,无关 change 跳过
|
|
7
|
+
* - 完整的置信度校验由 pre-commit 完成,push 阶段不再重复。
|
|
8
|
+
* - 过期判断读取 HEAD 已提交内容(而非工作区),避免工作区未提交修改造成误判。
|
|
9
|
+
*
|
|
10
|
+
* 待推送范围采集(逐级降级):
|
|
11
|
+
* 1. git diff @{push}..HEAD(本地领先远程的提交)
|
|
12
|
+
* 2. git diff origin/HEAD..HEAD
|
|
13
|
+
* 3. 远程引用不存在(新分支/新仓库首次推送):git diff <空树>..HEAD,
|
|
14
|
+
* 语义 = 首推时 HEAD 全部文件都是新增。空树 SHA 是 git 内置常量,
|
|
15
|
+
* 不依赖任何远程引用,避免「首推因无远程引用被拦 → 引用永远建立不起来」的死锁
|
|
16
|
+
* 4. 连空树 diff 都失败(如空仓无 HEAD):返回 ok=false,由 core 拦截推送而非静默放行
|
|
7
17
|
*
|
|
8
18
|
* 通过环境变量控制:
|
|
9
19
|
* - SDD_SKIP_CONSISTENCY_CHECK=1 → 跳过校验
|
|
10
20
|
* - SDD_WARN_ONLY=1 → 仅告警不拦截
|
|
11
21
|
* - 注意:两个环境变量互斥,只能启用一个
|
|
22
|
+
*
|
|
23
|
+
* 共享逻辑见同目录 consistency-check-core.cjs。
|
|
12
24
|
*/
|
|
13
25
|
'use strict';
|
|
14
26
|
|
|
15
|
-
const
|
|
16
|
-
const
|
|
27
|
+
const core = require('./consistency-check-core.cjs');
|
|
28
|
+
const { gitDiffNameStatus } = core;
|
|
17
29
|
const { execFileSync } = require('child_process');
|
|
18
30
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const found = process.argv.find(a => a.startsWith(prefix));
|
|
22
|
-
return found ? found.slice(prefix.length) : '';
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// --- Spec 根路径解析 ---
|
|
26
|
-
|
|
27
|
-
function resolveSpecRoot() {
|
|
28
|
-
// .cjs 位于 <spec-root>/skywalk-sdd/git-hooks/,直接用 __dirname 推算
|
|
29
|
-
return path.resolve(__dirname, '..', '..');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function discoverActiveChanges(specRoot) {
|
|
33
|
-
const changesDir = path.join(specRoot, 'openspec', 'changes');
|
|
34
|
-
if (!fs.existsSync(changesDir)) return [];
|
|
35
|
-
|
|
36
|
-
return fs.readdirSync(changesDir).filter(name => {
|
|
37
|
-
const p = path.join(changesDir, name);
|
|
38
|
-
return fs.statSync(p).isDirectory() && !name.startsWith('.') && name !== 'archive';
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// --- 报告查找与解析 ---
|
|
43
|
-
|
|
44
|
-
function findReport(specRoot, changeName, type) {
|
|
45
|
-
const names = type === 'json'
|
|
46
|
-
? ['consistency-report-result.json', 'consistency-report-self-review-result.json']
|
|
47
|
-
: ['consistency-report.md', 'consistency-report-self-review.md'];
|
|
48
|
-
|
|
49
|
-
const found = [];
|
|
50
|
-
for (const n of names) {
|
|
51
|
-
const p = path.join(specRoot, 'openspec', 'changes', changeName, n);
|
|
52
|
-
if (fs.existsSync(p)) found.push(p);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 如果两种模式的报告都存在,返回错误标记
|
|
56
|
-
if (found.length > 1) {
|
|
57
|
-
return { error: 'duplicate', paths: found };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return found.length === 1 ? found[0] : null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function parseReport(reportPath) {
|
|
64
|
-
try {
|
|
65
|
-
const data = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
|
66
|
-
return {
|
|
67
|
-
confidence: (data.confidence || '').toLowerCase(),
|
|
68
|
-
reviewMode: data.reviewMode || 'independent-review',
|
|
69
|
-
humanConfirmed: data.humanConfirmed === true,
|
|
70
|
-
reportPath,
|
|
71
|
-
};
|
|
72
|
-
} catch (err) {
|
|
73
|
-
return { confidence: '', reviewMode: '', humanConfirmed: false, reportPath, parseError: err.message };
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// --- 变更文件采集(基于配置文件精准定位) ---
|
|
31
|
+
// git 内置空树对象 SHA(SHA-1 仓库恒定不变)
|
|
32
|
+
const EMPTY_TREE_SHA = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
78
33
|
|
|
79
34
|
/**
|
|
80
|
-
*
|
|
35
|
+
* 单仓待推送范围采集:本地领先远程的提交所涉及的文件。
|
|
36
|
+
* files=修改/新增(排除删除);deleted=删除文件。
|
|
37
|
+
* @returns {{files: string[], deleted: string[], ok: boolean}}
|
|
81
38
|
*/
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return execFileSync('git', ['diff', 'HEAD', '--name-only'], {
|
|
99
|
-
cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
100
|
-
}).split(/\r?\n/).filter(Boolean);
|
|
101
|
-
} catch {
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function getModifiedFiles(projectRoot) {
|
|
107
|
-
// 工作区模式:读 .sdd-workspace.yaml 的 code_repos
|
|
108
|
-
const wsYaml = path.join(projectRoot, '.sdd-workspace.yaml');
|
|
109
|
-
if (fs.existsSync(wsYaml)) {
|
|
110
|
-
const repos = parseCodeRepos(wsYaml);
|
|
111
|
-
if (repos && repos.length > 0) {
|
|
112
|
-
const files = [];
|
|
113
|
-
for (const repo of repos) {
|
|
114
|
-
const repoRoot = path.join(projectRoot, repo);
|
|
115
|
-
for (const f of gitDiff(repoRoot)) {
|
|
116
|
-
files.push(path.join(repo, f).replace(/\\/g, '/'));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return [...new Set(files)];
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 单仓模式 / 兜底:直接对 projectRoot 做 diff
|
|
124
|
-
return gitDiff(projectRoot);
|
|
39
|
+
function gitPushRangeDiff(repoRoot) {
|
|
40
|
+
const ranges = ['@{push}..HEAD', 'origin/HEAD..HEAD'];
|
|
41
|
+
for (const range of ranges) {
|
|
42
|
+
const r = gitDiffNameStatus(repoRoot, ['diff', range]);
|
|
43
|
+
if (r.ok) return r;
|
|
44
|
+
}
|
|
45
|
+
// 首推降级:远程引用不存在(@{push}/origin/HEAD 均无法解析)时,
|
|
46
|
+
// 用 git 内置空树 SHA 做 diff —— 语义为「首推 = HEAD 全部文件都是新增」。
|
|
47
|
+
// 空树对象不依赖任何远程引用,打破「首推被拦 → 远程引用永远建立不起来」的死锁。
|
|
48
|
+
const emptyTree = gitEmptyTreeSha(repoRoot);
|
|
49
|
+
if (emptyTree) {
|
|
50
|
+
const r = gitDiffNameStatus(repoRoot, ['diff', `${emptyTree}..HEAD`]);
|
|
51
|
+
if (r.ok) return r;
|
|
52
|
+
}
|
|
53
|
+
// 连空树 diff 都失败(如空仓无 HEAD):返回失败,由 core 拦截而非静默放行
|
|
54
|
+
return { files: [], deleted: [], ok: false };
|
|
125
55
|
}
|
|
126
56
|
|
|
127
57
|
/**
|
|
128
|
-
*
|
|
58
|
+
* 读取 git 内置空树 SHA(4b825dc642cb6eb9a060e54bf8d69288fbee4904)。
|
|
59
|
+
* 优先用 git mktree 动态计算(兼容 SHA-256 仓库),失败时回退内置常量。
|
|
60
|
+
* @param {string} repoRoot
|
|
61
|
+
* @returns {string|null}
|
|
129
62
|
*/
|
|
130
|
-
function
|
|
63
|
+
function gitEmptyTreeSha(repoRoot) {
|
|
131
64
|
try {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// 降级:用文件 mtime
|
|
139
|
-
return fs.statSync(jsonPath).mtimeMs;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function isReportStale(specRoot, changeName, modifiedFiles, projectRoot) {
|
|
143
|
-
const jsonPath = findReport(specRoot, changeName, 'json');
|
|
144
|
-
if (!jsonPath) return false;
|
|
145
|
-
|
|
146
|
-
const reportTime = getReportTime(jsonPath);
|
|
147
|
-
|
|
148
|
-
// 排除报告文件本身
|
|
149
|
-
const reportFiles = [
|
|
150
|
-
'consistency-report.md',
|
|
151
|
-
'consistency-report-self-review.md',
|
|
152
|
-
'consistency-report-result.json',
|
|
153
|
-
'consistency-report-self-review-result.json',
|
|
154
|
-
];
|
|
155
|
-
|
|
156
|
-
return modifiedFiles.some(file => {
|
|
157
|
-
if (reportFiles.includes(path.basename(file))) return false;
|
|
158
|
-
const fp = path.join(projectRoot, file);
|
|
159
|
-
return fs.existsSync(fp) && fs.statSync(fp).mtimeMs > reportTime;
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// --- 提示信息 ---
|
|
164
|
-
|
|
165
|
-
function generateInstructions(missing, stale, needsConfirmation, warnOnly) {
|
|
166
|
-
const lines = ['', '=========================================='];
|
|
167
|
-
const needsRegenerate = [...new Set([...missing, ...stale])];
|
|
168
|
-
|
|
169
|
-
if (warnOnly) {
|
|
170
|
-
lines.push('[SDD push-consistency-check] ⚠️ 仅告警模式(SDD_WARN_ONLY=1),以下问题不会阻止推送:');
|
|
171
|
-
} else {
|
|
172
|
-
lines.push('[SDD push-consistency-check] 以下变更存在问题,拒绝推送:');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
lines.push('');
|
|
176
|
-
|
|
177
|
-
if (missing.length) {
|
|
178
|
-
lines.push(' 缺少一致性报告:');
|
|
179
|
-
missing.forEach(c => lines.push(` - ${c}`));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (stale.length) {
|
|
183
|
-
lines.push(' 报告已过期(代码在报告生成后被修改):');
|
|
184
|
-
stale.forEach(c => lines.push(` - ${c}`));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (needsConfirmation.length) {
|
|
188
|
-
lines.push(' 自审模式需人工确认:');
|
|
189
|
-
needsConfirmation.forEach(c => lines.push(` - ${c}`));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// 只有需要重新生成报告时才显示 skill 执行建议
|
|
193
|
-
if (needsRegenerate.length > 0) {
|
|
194
|
-
lines.push('');
|
|
195
|
-
lines.push(warnOnly
|
|
196
|
-
? '建议执行 opsx-consistency-check skill 更新报告:'
|
|
197
|
-
: '请执行 opsx-consistency-check skill 更新报告后再推送:');
|
|
198
|
-
lines.push('');
|
|
199
|
-
|
|
200
|
-
if (needsRegenerate.length === 1) {
|
|
201
|
-
lines.push(` skill: opsx-consistency-check ${needsRegenerate[0]}`);
|
|
202
|
-
} else {
|
|
203
|
-
lines.push(' skill(逐个执行):');
|
|
204
|
-
needsRegenerate.forEach(c => lines.push(` opsx-consistency-check ${c}`));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
lines.push(
|
|
208
|
-
'',
|
|
209
|
-
' 💡 建议:新开对话窗口执行校验,以获得独立、客观的审核结果',
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// 自审模式人工确认指引
|
|
214
|
-
if (needsConfirmation.length > 0) {
|
|
215
|
-
lines.push('');
|
|
216
|
-
lines.push(' 人工确认步骤:');
|
|
217
|
-
lines.push(' 1. 打开 consistency-report-self-review.md,找到「人工确认(自审模式)」章节');
|
|
218
|
-
lines.push(' 2. 逐条核对断言,在「人工确认」列填写 ✅ 已确认 或 ❌ 有误');
|
|
219
|
-
lines.push(' 3. 确认无误后,编辑 consistency-report-self-review-result.json,将 humanConfirmed 从 false 改为 true');
|
|
220
|
-
lines.push('');
|
|
221
|
-
lines.push(' 报告路径: openspec/changes/<change-name>/consistency-report-self-review.md');
|
|
222
|
-
lines.push(' JSON 路径: openspec/changes/<change-name>/consistency-report-self-review-result.json');
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// 如果没有需要重新生成的报告,但有其他问题,显示默认报告路径
|
|
226
|
-
if (needsRegenerate.length > 0 && needsConfirmation.length === 0) {
|
|
227
|
-
lines.push(
|
|
228
|
-
'',
|
|
229
|
-
' 报告路径: openspec/changes/<change-name>/consistency-report.md',
|
|
230
|
-
' JSON 路径: openspec/changes/<change-name>/consistency-report-result.json',
|
|
231
|
-
);
|
|
65
|
+
const sha = execFileSync('git', ['mktree'], {
|
|
66
|
+
cwd: repoRoot, encoding: 'utf8', input: '', stdio: ['pipe', 'pipe', 'ignore'],
|
|
67
|
+
}).trim();
|
|
68
|
+
if (/^[0-9a-f]{40,64}$/.test(sha)) return sha;
|
|
69
|
+
} catch {
|
|
70
|
+
// 降级到内置常量
|
|
232
71
|
}
|
|
233
|
-
|
|
234
|
-
lines.push('==========================================', '');
|
|
235
|
-
|
|
236
|
-
return lines.join('\n');
|
|
72
|
+
return /^[0-9a-f]{40}$/.test(EMPTY_TREE_SHA) ? EMPTY_TREE_SHA : null;
|
|
237
73
|
}
|
|
238
74
|
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const skip = process.env.SDD_SKIP_CONSISTENCY_CHECK === '1';
|
|
245
|
-
const warnOnly = process.env.SDD_WARN_ONLY === '1';
|
|
246
|
-
|
|
247
|
-
// 互斥检查
|
|
248
|
-
if (skip && warnOnly) {
|
|
249
|
-
console.error('[SDD push-consistency-check] 错误:SDD_SKIP_CONSISTENCY_CHECK 与 SDD_WARN_ONLY 互斥,只能启用一个');
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (skip) {
|
|
254
|
-
console.log('[SDD push-consistency-check] 跳过校验');
|
|
255
|
-
process.exit(0);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (warnOnly) console.log('[SDD push-consistency-check] 仅告警模式');
|
|
259
|
-
|
|
260
|
-
const specRoot = resolveSpecRoot();
|
|
261
|
-
|
|
262
|
-
const changes = discoverActiveChanges(specRoot);
|
|
263
|
-
if (!changes.length) {
|
|
264
|
-
console.log('[SDD push-consistency-check] 无活跃变更,跳过');
|
|
265
|
-
process.exit(0);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const modifiedFiles = getModifiedFiles(projectRoot);
|
|
269
|
-
const missing = [];
|
|
270
|
-
const stale = [];
|
|
271
|
-
const needsConfirmation = [];
|
|
272
|
-
|
|
273
|
-
for (const cn of changes) {
|
|
274
|
-
const rp = findReport(specRoot, cn, 'json');
|
|
275
|
-
if (!rp) {
|
|
276
|
-
missing.push(cn);
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// 检查是否存在两种模式的报告
|
|
281
|
-
if (rp.error === 'duplicate') {
|
|
282
|
-
console.warn(`[SDD push-consistency-check] ⚠️ ${cn} 存在两种模式的报告,请删除其中一个:`);
|
|
283
|
-
rp.paths.forEach(p => console.warn(` - ${p}`));
|
|
284
|
-
missing.push(cn);
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const report = parseReport(rp);
|
|
289
|
-
if (report.parseError) {
|
|
290
|
-
console.warn(`[SDD push-consistency-check] ⚠️ ${cn}: 报告解析失败`);
|
|
291
|
-
missing.push(cn);
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (isReportStale(specRoot, cn, modifiedFiles, projectRoot)) {
|
|
296
|
-
console.log(`[SDD push-consistency-check] ⚠️ ${cn}: 报告已过期`);
|
|
297
|
-
stale.push(cn);
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// 自审模式需要人工确认
|
|
302
|
-
if (report.reviewMode === 'self-review' && !report.humanConfirmed) {
|
|
303
|
-
console.log(`[SDD push-consistency-check] ⚠️ ${cn}: 自审模式需人工确认`);
|
|
304
|
-
needsConfirmation.push(cn);
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
console.log(`[SDD push-consistency-check] ✅ ${cn}: 报告有效`);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (missing.length || stale.length || needsConfirmation.length) {
|
|
312
|
-
console.log(generateInstructions(missing, stale, needsConfirmation, warnOnly));
|
|
313
|
-
|
|
314
|
-
if (warnOnly) {
|
|
315
|
-
console.log('[SDD push-consistency-check] 仅告警模式,放行推送');
|
|
316
|
-
process.exit(0);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
process.exit(1);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
console.log('\n[SDD push-consistency-check] ✅ 所有变更均已通过一致性校验,放行推送');
|
|
323
|
-
process.exit(0);
|
|
75
|
+
// strict 模式仅 pre-commit 支持:显式提示而非静默忽略
|
|
76
|
+
const _mode = core.loadConsistencyMode();
|
|
77
|
+
if (process.env.SDD_STRICT_CONSISTENCY === '1' || _mode.strict) {
|
|
78
|
+
console.log('[SDD push-consistency-check] ℹ️ SDD_STRICT_CONSISTENCY 仅在 pre-commit 阶段生效,push 阶段忽略');
|
|
324
79
|
}
|
|
325
80
|
|
|
326
|
-
|
|
81
|
+
core.runConsistencyCheck({
|
|
82
|
+
label: '[SDD push-consistency-check]',
|
|
83
|
+
actionNoun: '推送',
|
|
84
|
+
diffFn: gitPushRangeDiff,
|
|
85
|
+
checkConfidence: false, // 置信度校验由 pre-commit 完成,push 阶段不重复
|
|
86
|
+
includeUntracked: false, // untracked 文件不在任何 commit 中,不会被推送
|
|
87
|
+
staleFromHead: true, // 过期判断读 HEAD 已提交内容,避免工作区未提交修改误判
|
|
88
|
+
});
|
|
@@ -154,7 +154,7 @@ if (require.main === module) {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// G4: 批量更新检测(E1 修正:不再提前退出,模式检查继续执行)
|
|
157
|
-
|
|
157
|
+
let skipPerTaskCheck = false;
|
|
158
158
|
if (changes.length >= 2 && hasTddTasks) {
|
|
159
159
|
// 安全网 2: 全部有 task_update 遥测 → check-task 合法更新,跳过逐个检查
|
|
160
160
|
const allHaveTaskUpdate = changes.every(change =>
|
|
@@ -8,7 +8,7 @@ capability-id: "CAP-<CAPABILITY>" # 必须与对应 spec/design 一致
|
|
|
8
8
|
>
|
|
9
9
|
> **⚠️ 边界声明**:本任务清单仅服务于当前 Capability,严禁跨模块任务。
|
|
10
10
|
>
|
|
11
|
-
>
|
|
11
|
+
> **【质量红线】任务按可验证行为或交付单元拆分,不设固定分钟数;且拆解的任务和验证逻辑必须 100% 覆盖 spec 和 design
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -105,7 +105,7 @@ capability-id: "CAP-<CAPABILITY>" # 必须与对应 spec/design 一致
|
|
|
105
105
|
|
|
106
106
|
## 3. 原子任务清单
|
|
107
107
|
|
|
108
|
-
<!--
|
|
108
|
+
<!-- 【质量红线】每个任务应有可验证结果和明确依赖,避免按字段或命令机械拆分 -->
|
|
109
109
|
|
|
110
110
|
### 3.0 任务类型说明
|
|
111
111
|
|
|
@@ -299,7 +299,7 @@ capability-id: "CAP-<CAPABILITY>" # 必须与对应 spec/design 一致
|
|
|
299
299
|
---
|
|
300
300
|
|
|
301
301
|
> **质量红线检查清单**
|
|
302
|
-
> - [ ]
|
|
302
|
+
> - [ ] 每个任务对应可验证结果,粒度与真实依赖相匹配
|
|
303
303
|
> - [ ] 任务清单 100% 覆盖 spec.md 定义
|
|
304
304
|
> - [ ] 任务清单 100% 覆盖 design.md 定义
|
|
305
305
|
> - [ ] 每个任务都有明确的验收标准
|
|
@@ -17,6 +17,8 @@ allowed-tools:
|
|
|
17
17
|
- Agent
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
+
> **执行方式**:先读 [交互与执行契约](../opsx-propose/interaction-policy.md);同一会话已读则复用。用户已授权连续处理时按范围推进,不重复索要阶段口令;只请求单阶段时完成即停。
|
|
21
|
+
|
|
20
22
|
你是一个 SDD(Specification-Driven Development)变更实施专家。激活本技能后,你将引导用户按 DAG 依赖顺序逐任务实施代码。
|
|
21
23
|
|
|
22
24
|
|
|
@@ -24,7 +26,7 @@ allowed-tools:
|
|
|
24
26
|
>
|
|
25
27
|
> - 本技能针对**单一 Capability** 执行实施
|
|
26
28
|
> - **上下文**:overview.md → proposal.md → 当前 capability 的 spec/design/tasks
|
|
27
|
-
> - ⛔
|
|
29
|
+
> - ⛔ **隔离红线**:默认聚焦当前 Capability;为核对已声明依赖可读取相关 Capability 契约并注明来源,不全量加载或混写其他能力。
|
|
28
30
|
|
|
29
31
|
> **🖥️ 跨平台执行规则**
|
|
30
32
|
> - **写代码**:cwd 保持在 Git 根(含 `src/`);**文档 / telemetry**:`--project` 指向 SDD 包裹包(`node <spec-package>/skywalk-sdd/spec-root.cjs` 可获取)。
|
|
@@ -116,12 +118,40 @@ openspec list --json
|
|
|
116
118
|
> - **Capability**:`<capability-name>`
|
|
117
119
|
> - **任务文件**:`changes/<name>/specs/<capability>/tasks.md`"
|
|
118
120
|
|
|
119
|
-
### 1.
|
|
121
|
+
### 1.5 【非阻断】编译依赖预检
|
|
122
|
+
|
|
123
|
+
读取当前 Capability 的 tasks.md 中所有 IMPL/CONTROLLER 任务的描述,提取:
|
|
124
|
+
1. 使用的注解(@PreAuthorize, @Transactional, @Valid, @Mapper 等)
|
|
125
|
+
2. 导入的框架类(spring-security, mybatis, validation 等)
|
|
126
|
+
3. 与当前 pom.xml(或 build.gradle)已声明依赖做对比
|
|
127
|
+
|
|
128
|
+
**预检输出格式**(仅警告,不拦截 apply):
|
|
129
|
+
> "📋 **编译依赖预检**(非阻断)
|
|
130
|
+
>
|
|
131
|
+
> | 检查项 | 状态 | 详情 |
|
|
132
|
+
> |--------|------|------|
|
|
133
|
+
> | @PreAuthorize 依赖 | ⚠️ | spring-boot-starter-security 未在 pom.xml 中声明 |
|
|
134
|
+
> | @Transactional 依赖 | ✅ | spring-tx 已声明 |
|
|
135
|
+
> | @Mapper 依赖 | ✅ | mybatis 已声明 |
|
|
136
|
+
>
|
|
137
|
+
> 建议:若上述缺失依赖确需使用,可在 Layer 0 补充依赖配置任务,或确认现有构建文件已包含。
|
|
138
|
+
>
|
|
139
|
+
> 请选择:
|
|
140
|
+
> - **A. 忽略警告,继续 apply**(默认)
|
|
141
|
+
> - **B. 暂停,先补充依赖任务**
|
|
142
|
+
> - **C. 确认 pom.xml 已包含(预检误报)**"
|
|
143
|
+
|
|
144
|
+
**预检规则**:
|
|
145
|
+
- 只扫描 tasks.md 中任务描述文本,不扫描实际代码(apply 前代码可能尚未生成)
|
|
146
|
+
- 依赖映射表维护常用注解→Maven/Gradle 依赖的对应关系(见 `./reference.md`「§1.5a 注解依赖映射表」)
|
|
147
|
+
- 误报时用户选择 C,记录 `process_note(kind=user_decision, decision_type=ignore_dependency_warning)`
|
|
148
|
+
|
|
149
|
+
### 1.6 【Check 门禁检查】验证质量门禁状态
|
|
120
150
|
|
|
121
151
|
> **⛔ 强制门禁**:apply 阶段开始前,必须确认 check 阶段已完成。这是 SDD 流程的关键质量保障。
|
|
122
152
|
> 详细检查步骤、判定与门禁体系说明见 `./checklist.md`「§1.2 Check 门禁」。未完成强制拒绝。${HOOK_GATE_DESCRIPTION}
|
|
123
153
|
|
|
124
|
-
### 1.
|
|
154
|
+
### 1.7 【本地并行】Worktree 与工作区(建议性策略)
|
|
125
155
|
|
|
126
156
|
> **目标(本质)**:在**本地仓库**加快 SDD Apply——让**解耦**的 spec/capability 可以各自实现、互不踩目录,做完再按依赖顺序合并回你**当时所在的分支**(集成分支,如 `hotfix-1`)。
|
|
127
157
|
>
|
|
@@ -153,7 +183,7 @@ openspec list --json
|
|
|
153
183
|
第 3 层:当前 Capability 四文档
|
|
154
184
|
→ spec.md → design.md → tasks.md
|
|
155
185
|
|
|
156
|
-
⛔
|
|
186
|
+
⛔ 隔离红线:默认聚焦当前 Capability;为核对已声明依赖可读取相关 Capability 契约并注明来源,不全量加载或混写其他能力。
|
|
157
187
|
```
|
|
158
188
|
|
|
159
189
|
**【可选】业务知识库检索**:
|
|
@@ -267,7 +297,14 @@ g. **继续下一个层级** — 重新检查 DAG,找出依赖已满足的下
|
|
|
267
297
|
> - 已完成:[N/M] 任务
|
|
268
298
|
> - 当前层级:Layer [X]
|
|
269
299
|
> - 下一步建议:[继续实施 / 运行测试 / 归档]"
|
|
270
|
-
|
|
300
|
+
|
|
301
|
+
**【已授权连续处理完成】**
|
|
302
|
+
若本次为用户已授权的连续处理,完成时展示一次整体交付结果:
|
|
303
|
+
> "✅ **本轮连续处理完成**
|
|
304
|
+
> - propose → spec → design → task → check → apply 已全部执行完毕
|
|
305
|
+
> - 各阶段文档已生成,质量门禁已执行
|
|
306
|
+
> - 建议下一步:运行测试 / 归档变更"
|
|
307
|
+
|
|
271
308
|
> **💡 代码评审提示**:
|
|
272
309
|
> 建议执行 `/kld-review --local` 对本次变更的代码进行评审。
|
|
273
310
|
> 该技能可在公司内部 SkillHub 下载安装。
|
|
@@ -295,7 +332,7 @@ g. **继续下一个层级** — 重新检查 DAG,找出依赖已满足的下
|
|
|
295
332
|
> 完整 ⛔ 强制项勾选清单见 `./checklist.md`「Guardrails ⛔ 强制项」。核心红线:
|
|
296
333
|
|
|
297
334
|
- **⛔ Check 门禁检查**:apply 前必须确认 check 已完成;未完成强制拒绝。${HOOK_GATE_DESCRIPTION}
|
|
298
|
-
- **⛔ 渐进式加载 + 隔离红线**:只加载当前 Capability
|
|
335
|
+
- **⛔ 渐进式加载 + 隔离红线**:只加载当前 Capability 的四文档链,默认聚焦当前 Capability;为核对已声明依赖可读取相关 Capability 契约并注明来源,不全量加载或混写其他能力。
|
|
299
336
|
- **⛔ DAG 依赖拦截**:执行任务前必须检查依赖,前置未完成必须拦截。
|
|
300
337
|
- **⛔ 编译检查门禁**:每完成一个任务后必须运行编译检查,编译失败禁止标记已完成。
|
|
301
338
|
- **⛔ 测试执行门禁**:根据 `test-strategy` 决定(tdd=强制, impl-first=强制补跑, none=跳过);须真实执行并留 telemetry。${HOOK_GATE_DESCRIPTION}
|
|
@@ -139,7 +139,7 @@ description: opsx-apply 的阶段强制检查点与自检清单。仅在执行 a
|
|
|
139
139
|
|
|
140
140
|
- [ ] ⛔ **Check 门禁检查**:apply 前必须确认 check 已完成;未完成强制拒绝,与 `sdd-apply-gate.cjs` Hook 形成双重保障
|
|
141
141
|
- [ ] ⛔ **渐进式加载**:只加载当前 Capability 的四文档链
|
|
142
|
-
- [ ] ⛔
|
|
142
|
+
- [ ] ⛔ **隔离红线**:默认聚焦当前 Capability;为核对已声明依赖可读取相关 Capability 契约并注明来源,不全量加载或混写其他能力。
|
|
143
143
|
- [ ] ⛔ **DAG 依赖拦截**:执行任务前必须检查依赖,前置未完成必须拦截
|
|
144
144
|
- [ ] ⛔ **编译检查门禁**:每完成一个任务后必须运行编译检查,编译失败禁止标记已完成
|
|
145
145
|
- [ ] ⛔ **测试执行门禁**:根据 `test-strategy` 决定(tdd=强制, impl-first=强制补跑, none=跳过);须真实执行并留 telemetry,`sdd-apply-test-gate` 校验非占位数据
|
|
@@ -237,7 +237,7 @@ node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --type=worktree_finish \
|
|
|
237
237
|
- **§4 依赖**(是否依赖 `<当前 capability>` 或其它 cap)
|
|
238
238
|
- **涉及模块 / 数据表 / 公共配置**(若有)
|
|
239
239
|
|
|
240
|
-
|
|
240
|
+
默认聚焦当前 cap;核对真实跨能力依赖时可读取相关契约并注明来源,避免全量加载或混写。
|
|
241
241
|
|
|
242
242
|
**Step D — 判定矩阵(输出给用户)**
|
|
243
243
|
|