kld-sdd 2.6.15 → 2.6.17
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 +174 -76
- package/lib/skills-bundle.js +23 -1
- package/lib/tool-profiles.js +8 -0
- package/package.json +6 -1
- package/skywalk-sdd/apply-worktree-finish.cjs +2 -23
- package/skywalk-sdd/context-client.cjs +38 -87
- package/skywalk-sdd/index.cjs +719 -108
- package/skywalk-sdd/kb-sync-identity.cjs +780 -0
- package/skywalk-sdd/kb-upload.cjs +505 -0
- package/skywalk-sdd/lib/shared.cjs +663 -0
- package/skywalk-sdd/metrics-v3.cjs +138 -8
- package/skywalk-sdd/ontology/archive-package.cjs +19 -34
- package/skywalk-sdd/ontology/change-lock.cjs +3 -7
- package/skywalk-sdd/ontology/external-key.cjs +18 -4
- package/skywalk-sdd/ontology/id.cjs +26 -5
- package/skywalk-sdd/ontology/identity-index.cjs +3 -7
- package/skywalk-sdd/ontology/resolve-spec-root.cjs +20 -6
- package/skywalk-sdd/ontology/runtime.cjs +16 -12
- package/skywalk-sdd/ontology/traceability-validator.cjs +7 -4
- package/skywalk-sdd/openspec-shim.cjs +48 -0
- package/skywalk-sdd/reporting/change-report-markdown.cjs +137 -19
- package/skywalk-sdd/reporting/change-report-model.cjs +993 -14
- package/skywalk-sdd/reporting/change-report-renderer.cjs +106 -41
- package/skywalk-sdd/reporting/change-report-view-model.cjs +272 -43
- package/skywalk-sdd/reporting/core-metric-definitions.cjs +192 -0
- package/skywalk-sdd/spec-root.cjs +31 -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 +696 -0
- package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +86 -0
- package/templates/hooks/codebuddy/hooks/sdd-mid-checkpoint.cjs +165 -0
- package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +36 -1
- package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +296 -0
- package/templates/hooks/codebuddy/settings.json +8 -0
- package/templates/openspec/proposal.md +7 -3
- package/templates/openspec/spec.md +3 -3
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +21 -6
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +17 -0
- package/templates/skills/kld-sdd/opsx-apply/reference.md +70 -7
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +6 -5
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +48 -16
- package/templates/skills/kld-sdd/opsx-check/checklist.md +4 -2
- package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +592 -0
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +2 -2
- package/templates/skills/kld-sdd/opsx-explore/SKILL.md +2 -2
- package/templates/skills/kld-sdd/opsx-kb-config/SKILL.md +164 -0
- package/templates/skills/kld-sdd/opsx-kb-config/reference.md +116 -0
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +218 -53
- package/templates/skills/kld-sdd/opsx-kb-ingest/reference.md +51 -9
- package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +12 -50
- package/templates/skills/kld-sdd/opsx-ontology-query/phase-3-postchange.md +2 -2
- package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +1 -1
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +35 -23
- package/templates/skills/kld-sdd/opsx-propose/checklist.md +2 -0
- package/templates/skills/kld-sdd/opsx-propose/reference.md +22 -17
- package/templates/skills/kld-sdd/opsx-rules/SKILL.md +2 -2
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +19 -15
- package/templates/skills/kld-sdd/opsx-spec/checklist.md +2 -0
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +2 -4
- package/templates/skills/kld-sdd/opsx-test/SKILL.md +2 -2
- package/templates/skills/kld-sdd/tdd-core/reference.md +1 -1
- 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/templates/skills/kld-sdd/tdd-rules/rules/test-skeleton-telemetry.md +1 -1
- package/templates/skills/kld-sdd/opsx-kb-ingest/state.example.json +0 -7
- package/templates/skills/kld-sdd/opsx-ontology-query/state.example.json +0 -7
|
@@ -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();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# KLD SDD Pre-Push 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-push 检查"
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
echo "[SDD] 执行推送前检查..."
|
|
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
|
|
51
|
+
|
|
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
|
+
fi
|
|
56
|
+
|
|
57
|
+
echo "[SDD] ✅ 所有 pre-push 检查通过"
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pre-push-consistency-check.cjs
|
|
4
|
+
*
|
|
5
|
+
* Pre-push 门禁:检查活跃变更的 Spec 一致性校验报告。
|
|
6
|
+
* 与 pre-commit-consistency-check.cjs 类似,但更严格:
|
|
7
|
+
* - confidence=low → 阻止推送
|
|
8
|
+
* - 缺少报告 → 警告(默认放行);strict 模式下阻止推送
|
|
9
|
+
*
|
|
10
|
+
* 用法: node pre-push-consistency-check.cjs --project=<project-root> [--change=<change-name>]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
function readArg(name) {
|
|
19
|
+
const prefix = `--${name}=`;
|
|
20
|
+
const found = process.argv.find(arg => arg.startsWith(prefix));
|
|
21
|
+
return found ? found.slice(prefix.length) : '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hasFlag(name) {
|
|
25
|
+
return process.argv.includes(`--${name}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 从 .sdd-spec-root 文件读取 spec 包裹包相对路径(单仓 mono 布局)
|
|
30
|
+
*/
|
|
31
|
+
function resolveSpecRoot(projectRoot) {
|
|
32
|
+
// 1. 检查 .sdd-spec-root(单仓 mono 模式)
|
|
33
|
+
const hintPath = path.join(projectRoot, '.sdd-spec-root');
|
|
34
|
+
if (fs.existsSync(hintPath)) {
|
|
35
|
+
const rel = fs.readFileSync(hintPath, 'utf8').trim();
|
|
36
|
+
if (rel) {
|
|
37
|
+
const specAbs = path.resolve(projectRoot, rel);
|
|
38
|
+
if (fs.existsSync(specAbs)) {
|
|
39
|
+
return specAbs;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 2. 查找 *-sdd-specs 子目录(多仓工作区模式)
|
|
45
|
+
const entries = fs.existsSync(projectRoot) ? fs.readdirSync(projectRoot) : [];
|
|
46
|
+
for (const name of entries) {
|
|
47
|
+
if (/-sdd-specs$/i.test(name)) {
|
|
48
|
+
const candidate = path.join(projectRoot, name);
|
|
49
|
+
if (fs.statSync(candidate).isDirectory() && fs.existsSync(path.join(candidate, 'openspec'))) {
|
|
50
|
+
return candidate;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 3. 项目根本身就是 spec 仓
|
|
56
|
+
if (fs.existsSync(path.join(projectRoot, 'openspec', 'changes'))) {
|
|
57
|
+
return projectRoot;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 发现活跃变更列表
|
|
65
|
+
*/
|
|
66
|
+
function discoverActiveChanges(specRoot, explicitChange) {
|
|
67
|
+
if (explicitChange) {
|
|
68
|
+
return [explicitChange];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const changesDir = path.join(specRoot, 'openspec', 'changes');
|
|
72
|
+
if (!fs.existsSync(changesDir)) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return fs.readdirSync(changesDir)
|
|
77
|
+
.filter(name => {
|
|
78
|
+
const fullPath = path.join(changesDir, name);
|
|
79
|
+
return fs.statSync(fullPath).isDirectory()
|
|
80
|
+
&& !name.startsWith('.')
|
|
81
|
+
&& name !== 'archive';
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 查找变更的一致性报告 JSON
|
|
87
|
+
*/
|
|
88
|
+
function findConsistencyReport(specRoot, changeName) {
|
|
89
|
+
const patterns = [
|
|
90
|
+
'consistency-report-result.json',
|
|
91
|
+
'consistency-report-self-review-result.json',
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
for (const pattern of patterns) {
|
|
95
|
+
const reportPath = path.join(specRoot, 'openspec', 'changes', changeName, pattern);
|
|
96
|
+
if (fs.existsSync(reportPath)) {
|
|
97
|
+
return reportPath;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 解析一致性报告,返回置信度
|
|
106
|
+
*/
|
|
107
|
+
function parseReport(reportPath) {
|
|
108
|
+
try {
|
|
109
|
+
const content = fs.readFileSync(reportPath, 'utf8');
|
|
110
|
+
const data = JSON.parse(content);
|
|
111
|
+
return {
|
|
112
|
+
confidence: (data.confidence || '').toLowerCase(),
|
|
113
|
+
overallResult: (data.overallResult || '').toLowerCase(),
|
|
114
|
+
changeName: data.changeName || '',
|
|
115
|
+
reportPath,
|
|
116
|
+
};
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return { confidence: '', overallResult: '', changeName: '', reportPath, parseError: err.message };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function main() {
|
|
123
|
+
const projectRoot = path.resolve(readArg('project') || process.env.SDD_PROJECT || process.cwd());
|
|
124
|
+
const explicitChange = readArg('change') || process.env.SDD_CHANGE || process.env.OPENSPEC_CHANGE || '';
|
|
125
|
+
const strictMode = hasFlag('strict') || process.env.SDD_STRICT_CONSISTENCY === '1';
|
|
126
|
+
const looseMode = hasFlag('no-strict') || process.env.SDD_LOOSE_CONSISTENCY === '1';
|
|
127
|
+
|
|
128
|
+
const specRoot = resolveSpecRoot(projectRoot);
|
|
129
|
+
if (!specRoot) {
|
|
130
|
+
console.log('SDD consistency-check (pre-push): 未找到 spec 仓库,跳过一致性校验');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const changes = discoverActiveChanges(specRoot, explicitChange);
|
|
135
|
+
if (changes.length === 0) {
|
|
136
|
+
console.log('SDD consistency-check (pre-push): 无活跃变更,跳过一致性校验');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const blockers = [];
|
|
141
|
+
const missing = [];
|
|
142
|
+
const passed = [];
|
|
143
|
+
|
|
144
|
+
for (const changeName of changes) {
|
|
145
|
+
const reportPath = findConsistencyReport(specRoot, changeName);
|
|
146
|
+
if (!reportPath) {
|
|
147
|
+
missing.push(changeName);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const report = parseReport(reportPath);
|
|
152
|
+
if (report.parseError) {
|
|
153
|
+
console.warn(`SDD consistency-check (pre-push): ⚠️ ${changeName} 报告解析失败: ${report.parseError}`);
|
|
154
|
+
missing.push(changeName);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (report.confidence === 'low' || report.overallResult === 'fail') {
|
|
159
|
+
blockers.push({ changeName, report });
|
|
160
|
+
} else {
|
|
161
|
+
passed.push({ changeName, report });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 输出摘要
|
|
166
|
+
if (passed.length > 0) {
|
|
167
|
+
for (const item of passed) {
|
|
168
|
+
console.log(`SDD consistency-check (pre-push): ✅ ${item.changeName} 置信度=${item.report.confidence}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (missing.length > 0) {
|
|
173
|
+
console.warn(`SDD consistency-check (pre-push): ⚠️ 以下变更缺少一致性校验报告:`);
|
|
174
|
+
for (const name of missing) {
|
|
175
|
+
console.warn(` - ${name}`);
|
|
176
|
+
}
|
|
177
|
+
if (strictMode && !looseMode) {
|
|
178
|
+
console.error('SDD consistency-check (pre-push): ❌ strict 模式已启用,缺少报告的变更不允许推送。');
|
|
179
|
+
console.error('请先执行 opsx-consistency-check 生成报告,或使用 --no-verify 跳过(不推荐)。');
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
console.warn(' → 推送已放行,建议尽快执行 opsx-consistency-check');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (blockers.length > 0) {
|
|
186
|
+
console.error(`SDD consistency-check (pre-push): ❌ 以下变更的一致性校验未通过(confidence=low),阻止推送:`);
|
|
187
|
+
for (const item of blockers) {
|
|
188
|
+
console.error(` - ${item.changeName} (confidence=${item.report.confidence}, result=${item.report.overallResult})`);
|
|
189
|
+
console.error(` 报告路径: ${item.report.reportPath}`);
|
|
190
|
+
}
|
|
191
|
+
console.error('');
|
|
192
|
+
console.error('请修复不一致项后重新执行 opsx-consistency-check,或使用 --no-verify 跳过(不推荐)。');
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
main();
|