kld-sdd 2.6.16 → 2.6.21
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/bin/kld-sdd-init.js +10 -0
- package/kld-sdd-guide.html +22 -0
- package/lib/device-auth-cli.js +130 -0
- package/lib/device-auth.js +345 -0
- package/lib/init-account-binding.js +108 -0
- package/lib/init.js +238 -55
- package/lib/skills-bundle.js +20 -1
- package/lib/tool-profiles.js +8 -0
- package/package.json +7 -3
- package/skywalk-sdd/apply-worktree-finish.cjs +2 -23
- package/skywalk-sdd/context-client.cjs +38 -87
- package/skywalk-sdd/index.cjs +860 -132
- package/skywalk-sdd/kb-sync-identity.cjs +780 -0
- package/skywalk-sdd/kb-upload.cjs +505 -0
- package/skywalk-sdd/lib/shared.cjs +811 -0
- package/skywalk-sdd/lib/usage-contract.cjs +276 -0
- package/skywalk-sdd/lib/usage-reporter.cjs +354 -0
- package/skywalk-sdd/lib/user-config.cjs +157 -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/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/pre-commit-consistency-check.cjs +271 -116
- package/templates/git-hooks/pre-push-consistency-check.cjs +252 -123
- package/templates/hooks/codebuddy/hooks/hook-gate-core.cjs +327 -0
- package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +54 -9
- package/templates/hooks/codebuddy/hooks/sdd-mid-checkpoint.cjs +63 -6
- package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +113 -65
- package/templates/openspec/proposal.md +7 -3
- package/templates/openspec/spec.md +3 -3
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +8 -6
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +2 -0
- package/templates/skills/kld-sdd/opsx-apply/reference.md +29 -7
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +6 -5
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +49 -17
- package/templates/skills/kld-sdd/opsx-check/checklist.md +4 -2
- package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +187 -257
- package/templates/skills/kld-sdd/opsx-consistency-check/reference.md +129 -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 +185 -0
- package/templates/skills/kld-sdd/opsx-kb-config/reference.md +127 -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/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
|
@@ -2,196 +2,325 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* pre-push-consistency-check.cjs
|
|
4
4
|
*
|
|
5
|
-
* Pre-push
|
|
6
|
-
*
|
|
7
|
-
* - confidence=low → 阻止推送
|
|
8
|
-
* - 缺少报告 → 警告(默认放行);strict 模式下阻止推送
|
|
5
|
+
* Pre-push 轻量门禁:仅检查报告是否存在、是否过期。
|
|
6
|
+
* 完整的置信度校验由 pre-commit 完成,push 阶段不再重复。
|
|
9
7
|
*
|
|
10
|
-
*
|
|
8
|
+
* 通过环境变量控制:
|
|
9
|
+
* - SDD_SKIP_CONSISTENCY_CHECK=1 → 跳过校验
|
|
10
|
+
* - SDD_WARN_ONLY=1 → 仅告警不拦截
|
|
11
|
+
* - 注意:两个环境变量互斥,只能启用一个
|
|
11
12
|
*/
|
|
12
|
-
|
|
13
13
|
'use strict';
|
|
14
14
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
|
+
const { execFileSync } = require('child_process');
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
const readArg = (name) => {
|
|
19
20
|
const prefix = `--${name}=`;
|
|
20
|
-
const found = process.argv.find(
|
|
21
|
+
const found = process.argv.find(a => a.startsWith(prefix));
|
|
21
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, '..', '..');
|
|
22
30
|
}
|
|
23
31
|
|
|
24
|
-
function
|
|
25
|
-
|
|
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
|
+
});
|
|
26
40
|
}
|
|
27
41
|
|
|
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
|
-
}
|
|
42
|
+
// --- 报告查找与解析 ---
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
53
|
}
|
|
54
54
|
|
|
55
|
-
//
|
|
56
|
-
if (
|
|
57
|
-
return
|
|
55
|
+
// 如果两种模式的报告都存在,返回错误标记
|
|
56
|
+
if (found.length > 1) {
|
|
57
|
+
return { error: 'duplicate', paths: found };
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
return null;
|
|
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
|
+
}
|
|
61
75
|
}
|
|
62
76
|
|
|
77
|
+
// --- 变更文件采集(基于配置文件精准定位) ---
|
|
78
|
+
|
|
63
79
|
/**
|
|
64
|
-
*
|
|
80
|
+
* 简易 YAML 解析器:仅提取 code_repos 列表
|
|
65
81
|
*/
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
function parseCodeRepos(yamlPath) {
|
|
83
|
+
try {
|
|
84
|
+
const content = fs.readFileSync(yamlPath, 'utf8');
|
|
85
|
+
const match = content.match(/code_repos:\s*\n((?:\s+-\s+\S+\n?)+)/);
|
|
86
|
+
if (!match) return null;
|
|
87
|
+
return match[1]
|
|
88
|
+
.split('\n')
|
|
89
|
+
.map(line => line.replace(/^\s+-\s+/, '').trim())
|
|
90
|
+
.filter(Boolean);
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
69
93
|
}
|
|
94
|
+
}
|
|
70
95
|
|
|
71
|
-
|
|
72
|
-
|
|
96
|
+
function gitDiff(repoRoot) {
|
|
97
|
+
try {
|
|
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 {
|
|
73
102
|
return [];
|
|
74
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
|
+
}
|
|
75
122
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const fullPath = path.join(changesDir, name);
|
|
79
|
-
return fs.statSync(fullPath).isDirectory()
|
|
80
|
-
&& !name.startsWith('.')
|
|
81
|
-
&& name !== 'archive';
|
|
82
|
-
});
|
|
123
|
+
// 单仓模式 / 兜底:直接对 projectRoot 做 diff
|
|
124
|
+
return gitDiff(projectRoot);
|
|
83
125
|
}
|
|
84
126
|
|
|
85
127
|
/**
|
|
86
|
-
*
|
|
128
|
+
* 获取报告生成时间:优先用 JSON 中的 generatedAt,降级用文件 mtime
|
|
87
129
|
*/
|
|
88
|
-
function
|
|
89
|
-
|
|
130
|
+
function getReportTime(jsonPath) {
|
|
131
|
+
try {
|
|
132
|
+
const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
133
|
+
if (data.generatedAt) {
|
|
134
|
+
const t = new Date(data.generatedAt).getTime();
|
|
135
|
+
if (!isNaN(t)) return t;
|
|
136
|
+
}
|
|
137
|
+
} catch {}
|
|
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',
|
|
90
152
|
'consistency-report-result.json',
|
|
91
153
|
'consistency-report-self-review-result.json',
|
|
92
154
|
];
|
|
93
155
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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}`));
|
|
98
205
|
}
|
|
206
|
+
|
|
207
|
+
lines.push(
|
|
208
|
+
'',
|
|
209
|
+
' 💡 建议:新开对话窗口执行校验,以获得独立、客观的审核结果',
|
|
210
|
+
);
|
|
99
211
|
}
|
|
100
212
|
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
}
|
|
103
224
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 };
|
|
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
|
+
);
|
|
119
232
|
}
|
|
233
|
+
|
|
234
|
+
lines.push('==========================================', '');
|
|
235
|
+
|
|
236
|
+
return lines.join('\n');
|
|
120
237
|
}
|
|
121
238
|
|
|
239
|
+
// --- 主流程 ---
|
|
240
|
+
|
|
122
241
|
function main() {
|
|
123
242
|
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
243
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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);
|
|
132
251
|
}
|
|
133
252
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return;
|
|
253
|
+
if (skip) {
|
|
254
|
+
console.log('[SDD push-consistency-check] 跳过校验');
|
|
255
|
+
process.exit(0);
|
|
138
256
|
}
|
|
139
257
|
|
|
140
|
-
|
|
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);
|
|
141
269
|
const missing = [];
|
|
142
|
-
const
|
|
270
|
+
const stale = [];
|
|
271
|
+
const needsConfirmation = [];
|
|
143
272
|
|
|
144
|
-
for (const
|
|
145
|
-
const
|
|
146
|
-
if (!
|
|
147
|
-
missing.push(
|
|
273
|
+
for (const cn of changes) {
|
|
274
|
+
const rp = findReport(specRoot, cn, 'json');
|
|
275
|
+
if (!rp) {
|
|
276
|
+
missing.push(cn);
|
|
148
277
|
continue;
|
|
149
278
|
}
|
|
150
279
|
|
|
151
|
-
|
|
152
|
-
if (
|
|
153
|
-
console.warn(`SDD consistency-check
|
|
154
|
-
|
|
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);
|
|
155
285
|
continue;
|
|
156
286
|
}
|
|
157
287
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
288
|
+
const report = parseReport(rp);
|
|
289
|
+
if (report.parseError) {
|
|
290
|
+
console.warn(`[SDD push-consistency-check] ⚠️ ${cn}: 报告解析失败`);
|
|
291
|
+
missing.push(cn);
|
|
292
|
+
continue;
|
|
162
293
|
}
|
|
163
|
-
}
|
|
164
294
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
295
|
+
if (isReportStale(specRoot, cn, modifiedFiles, projectRoot)) {
|
|
296
|
+
console.log(`[SDD push-consistency-check] ⚠️ ${cn}: 报告已过期`);
|
|
297
|
+
stale.push(cn);
|
|
298
|
+
continue;
|
|
169
299
|
}
|
|
170
|
-
}
|
|
171
300
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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);
|
|
301
|
+
// 自审模式需要人工确认
|
|
302
|
+
if (report.reviewMode === 'self-review' && !report.humanConfirmed) {
|
|
303
|
+
console.log(`[SDD push-consistency-check] ⚠️ ${cn}: 自审模式需人工确认`);
|
|
304
|
+
needsConfirmation.push(cn);
|
|
305
|
+
continue;
|
|
181
306
|
}
|
|
182
|
-
|
|
307
|
+
|
|
308
|
+
console.log(`[SDD push-consistency-check] ✅ ${cn}: 报告有效`);
|
|
183
309
|
}
|
|
184
310
|
|
|
185
|
-
if (
|
|
186
|
-
console.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
console.
|
|
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);
|
|
190
317
|
}
|
|
191
|
-
|
|
192
|
-
console.error('请修复不一致项后重新执行 opsx-consistency-check,或使用 --no-verify 跳过(不推荐)。');
|
|
318
|
+
|
|
193
319
|
process.exit(1);
|
|
194
320
|
}
|
|
321
|
+
|
|
322
|
+
console.log('\n[SDD push-consistency-check] ✅ 所有变更均已通过一致性校验,放行推送');
|
|
323
|
+
process.exit(0);
|
|
195
324
|
}
|
|
196
325
|
|
|
197
326
|
main();
|