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
|
@@ -28,9 +28,16 @@ const {
|
|
|
28
28
|
resolveRevisionBundlePaths,
|
|
29
29
|
removeLegacyOntologyStateFiles,
|
|
30
30
|
} = require('./ontology-paths.cjs');
|
|
31
|
+
const {
|
|
32
|
+
sha256: sharedSha256,
|
|
33
|
+
readJson: sharedReadJson,
|
|
34
|
+
writeIfChanged,
|
|
35
|
+
ensureDir,
|
|
36
|
+
stripBom,
|
|
37
|
+
} = require('../lib/shared.cjs');
|
|
31
38
|
|
|
32
39
|
function sha256(value) {
|
|
33
|
-
return
|
|
40
|
+
return sharedSha256(value);
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
function resolveChangeDir(projectRoot, changeName) {
|
|
@@ -56,17 +63,11 @@ function serialized(value) {
|
|
|
56
63
|
return JSON.stringify(value, null, 2) + '\n';
|
|
57
64
|
}
|
|
58
65
|
|
|
59
|
-
|
|
66
|
+
// atomicWriteIfChanged 已迁移至 lib/shared.cjs
|
|
67
|
+
const atomicWriteIfChanged = (filePath, value) => {
|
|
60
68
|
const content = typeof value === 'string' ? value : serialized(value);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
65
|
-
const tempPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
66
|
-
fs.writeFileSync(tempPath, content, 'utf8');
|
|
67
|
-
fs.renameSync(tempPath, filePath);
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
69
|
+
return writeIfChanged(filePath, content);
|
|
70
|
+
};
|
|
70
71
|
|
|
71
72
|
function stateError(code, message) {
|
|
72
73
|
const error = new Error(`${code}: ${message}`);
|
|
@@ -74,9 +75,12 @@ function stateError(code, message) {
|
|
|
74
75
|
return error;
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
// readJson 改用 shared.cjs 版本(带 BOM 剥离),保留 stateError 包装
|
|
77
79
|
function readJson(filePath, code) {
|
|
78
80
|
try {
|
|
79
|
-
|
|
81
|
+
let raw = fs.readFileSync(filePath, 'utf8');
|
|
82
|
+
raw = stripBom(raw);
|
|
83
|
+
return JSON.parse(raw);
|
|
80
84
|
} catch (error) {
|
|
81
85
|
throw stateError(code, `无法读取状态文件 ${filePath}: ${error.message}`);
|
|
82
86
|
}
|
|
@@ -137,7 +137,10 @@ function validateIdentitySemantics(facts, diagnostics, identityHistory) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
// 当本地无身份历史(单变更、无 confirmed archive)时,跳过基于历史的校验。
|
|
141
|
+
// 此场景下 modified/unchanged 实体的 predecessor-version 来自 KB resolve,
|
|
142
|
+
// lineage 由 KB 后端 version_id 唯一性约束兜底,不由本地校验阻断。
|
|
143
|
+
if (!Array.isArray(identityHistory) || identityHistory.length === 0) return;
|
|
141
144
|
const historyByEntityId = new Map();
|
|
142
145
|
const historyByVersionId = new Map();
|
|
143
146
|
const historyByAnchor = new Map();
|
|
@@ -515,7 +518,7 @@ function validateContinuityAndExternalRefs(facts, diagnostics, options = {}) {
|
|
|
515
518
|
DIAGNOSTIC_CODES.EXTERNAL_KEY_FORMAT_INVALID,
|
|
516
519
|
'error',
|
|
517
520
|
format.message,
|
|
518
|
-
{ file: 'proposal.md', suggestion: '
|
|
521
|
+
{ file: 'proposal.md', suggestion: '检查外部编号格式(仅允许大写字母、数字、连字符、下划线),禁止改写' },
|
|
519
522
|
));
|
|
520
523
|
continue;
|
|
521
524
|
}
|
|
@@ -578,7 +581,7 @@ function validateContinuityAndExternalRefs(facts, diagnostics, options = {}) {
|
|
|
578
581
|
{
|
|
579
582
|
...sourceContext(entity),
|
|
580
583
|
entity_id: entity.id,
|
|
581
|
-
suggestion: '
|
|
584
|
+
suggestion: '编号格式不合规(仅允许大写字母、数字、连字符、下划线);场景键回 opsx-spec 修正',
|
|
582
585
|
},
|
|
583
586
|
));
|
|
584
587
|
}
|
|
@@ -605,7 +608,7 @@ function validateContinuityAndExternalRefs(facts, diagnostics, options = {}) {
|
|
|
605
608
|
diagnostics.push(diagnostic(
|
|
606
609
|
DIAGNOSTIC_CODES.EXTERNAL_KEY_SCOPE_MISMATCH,
|
|
607
610
|
'error',
|
|
608
|
-
|
|
611
|
+
`场景键需求编号前缀不在 frontmatter requirement-refs 申报集合: ${normalizedScenario}`,
|
|
609
612
|
{ ...sourceContext(entity), entity_id: entity.id },
|
|
610
613
|
));
|
|
611
614
|
}
|
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { buildChangeReportViewModel } = require('./change-report-view-model.cjs');
|
|
4
|
+
const { buildChangeReportModel } = require('./change-report-model.cjs');
|
|
5
|
+
|
|
6
|
+
function escapeMarkdownText(value) {
|
|
7
|
+
return String(value)
|
|
8
|
+
.replace(/&/g, '&')
|
|
9
|
+
.replace(/\\/g, '\')
|
|
10
|
+
.replace(/\[/g, '[')
|
|
11
|
+
.replace(/\]/g, ']')
|
|
12
|
+
.replace(/`/g, '`')
|
|
13
|
+
.replace(/</g, '<')
|
|
14
|
+
.replace(/>/g, '>')
|
|
15
|
+
.replace(/"/g, '"')
|
|
16
|
+
.replace(/'/g, ''')
|
|
17
|
+
.replace(/[\r\n]+/g, ' ');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sanitizeMarkdownValue(value) {
|
|
21
|
+
if (typeof value === 'string') return escapeMarkdownText(value);
|
|
22
|
+
if (Array.isArray(value)) return value.map(sanitizeMarkdownValue);
|
|
23
|
+
if (!value || typeof value !== 'object') return value;
|
|
24
|
+
const sanitized = {};
|
|
25
|
+
for (const [key, item] of Object.entries(value)) {
|
|
26
|
+
sanitized[escapeMarkdownText(key)] = sanitizeMarkdownValue(item);
|
|
27
|
+
}
|
|
28
|
+
return sanitized;
|
|
29
|
+
}
|
|
4
30
|
|
|
5
31
|
function formatValue(value) {
|
|
6
32
|
if (value == null || value === '') return '暂无数据';
|
|
@@ -12,6 +38,59 @@ function formatRatioToPercent(ratio) {
|
|
|
12
38
|
return `${(Number(ratio) * 100).toFixed(1)}%`;
|
|
13
39
|
}
|
|
14
40
|
|
|
41
|
+
function renderMetricDetail(detail) {
|
|
42
|
+
if (!detail) return [];
|
|
43
|
+
const lines = [
|
|
44
|
+
'',
|
|
45
|
+
`### ${detail.name}(${detail.code})`,
|
|
46
|
+
'',
|
|
47
|
+
`- 指标定义:${detail.definition}`,
|
|
48
|
+
`- 指标用途:${detail.purpose}`,
|
|
49
|
+
`- 计算公式:${detail.formula}`,
|
|
50
|
+
`- 数据来源:${detail.source_description}`,
|
|
51
|
+
`- 对应方案:${detail.scheme_reference}`,
|
|
52
|
+
'',
|
|
53
|
+
'#### 本次原始数据',
|
|
54
|
+
];
|
|
55
|
+
const inputs = detail.raw_inputs || [];
|
|
56
|
+
if (inputs.length === 0) {
|
|
57
|
+
lines.push('- 暂无可展示的原始数据。');
|
|
58
|
+
} else {
|
|
59
|
+
for (const input of inputs) {
|
|
60
|
+
const value = input.display_value ?? input.value ?? '暂无数据';
|
|
61
|
+
const inclusion = input.included === false
|
|
62
|
+
? `;不计入公式:${input.exclusion_reason || '仅作辅助说明'}`
|
|
63
|
+
: '';
|
|
64
|
+
lines.push(`- ${input.label}:${value}(来源:${input.source || '未记录'}${inclusion})`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
lines.push('', '#### 计算步骤');
|
|
68
|
+
for (const step of detail.calculation_steps || ['暂无可复算步骤。']) {
|
|
69
|
+
lines.push(`- ${step}`);
|
|
70
|
+
}
|
|
71
|
+
lines.push(
|
|
72
|
+
'',
|
|
73
|
+
'#### 本次结果',
|
|
74
|
+
`- ${detail.result?.display_value || '暂无数据'}`,
|
|
75
|
+
`- 可信度:${detail.trust?.explanation || '未记录'}`,
|
|
76
|
+
'',
|
|
77
|
+
'#### 证据与限制',
|
|
78
|
+
);
|
|
79
|
+
const evidence = detail.evidence || [];
|
|
80
|
+
if (evidence.length === 0) {
|
|
81
|
+
lines.push('- 当前没有可定位的证据编号;计算值可能来自兼容字段。');
|
|
82
|
+
} else {
|
|
83
|
+
for (const item of evidence) {
|
|
84
|
+
lines.push(`- ${item.path || item.event_id || item.type || '证据'}${item.count == null ? '' : `(${item.count} 条)`}`);
|
|
85
|
+
for (const scenario of item.scenarios || []) {
|
|
86
|
+
lines.push(` - 场景:${scenario.title || '未命名'}(第 ${scenario.line ?? '未知'} 行)`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const caveat of detail.caveats || []) lines.push(`- ${caveat}`);
|
|
91
|
+
return lines;
|
|
92
|
+
}
|
|
93
|
+
|
|
15
94
|
function renderSectionOverview(section, report) {
|
|
16
95
|
const minSet = report.minimum_metric_set || {};
|
|
17
96
|
const lines = [
|
|
@@ -47,7 +126,7 @@ function renderSectionOverview(section, report) {
|
|
|
47
126
|
|
|
48
127
|
function renderSectionMetrics(section, report) {
|
|
49
128
|
const tdd = section.tdd_pairs || {};
|
|
50
|
-
const reasons = report.
|
|
129
|
+
const reasons = report.metric_details?.p_r?.raw_values?.reasons;
|
|
51
130
|
const lines = [
|
|
52
131
|
`## ${section.title}`,
|
|
53
132
|
'',
|
|
@@ -58,9 +137,12 @@ function renderSectionMetrics(section, report) {
|
|
|
58
137
|
for (const metric of section.metric_cards || []) {
|
|
59
138
|
lines.push(`- ${metric.name}(${metric.code}):${metric.value} · ${metric.state}`);
|
|
60
139
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
140
|
+
for (const metric of section.metric_cards || []) {
|
|
141
|
+
lines.push(...renderMetricDetail(metric.detail));
|
|
142
|
+
}
|
|
143
|
+
if (reasons) {
|
|
144
|
+
const parts = Object.values(reasons)
|
|
145
|
+
.map(item => `${item.label}=${item.count}`)
|
|
64
146
|
.join(', ');
|
|
65
147
|
lines.push(`- 重复执行原因分布:${parts || '无'}`);
|
|
66
148
|
}
|
|
@@ -97,6 +179,11 @@ function renderSectionIssues(section, report) {
|
|
|
97
179
|
'',
|
|
98
180
|
section.summary,
|
|
99
181
|
'',
|
|
182
|
+
'### 这些内容是怎么收集的',
|
|
183
|
+
`- ${section.collection_method}`,
|
|
184
|
+
`- 功能验证:${section.functional_result?.summary || '未记录'}${section.functional_result?.explanation ? ` ${section.functional_result.explanation}` : ''}`,
|
|
185
|
+
`- 证据链判断:${section.traceability_result?.summary || '未记录'}${section.traceability_result?.explanation ? ` ${section.traceability_result.explanation}` : ''}`,
|
|
186
|
+
'',
|
|
100
187
|
];
|
|
101
188
|
const audit = section.task_evidence_audit || {};
|
|
102
189
|
if (audit.direct_test_backlink || audit.strict_test_coverage) {
|
|
@@ -129,7 +216,7 @@ function renderSectionIssues(section, report) {
|
|
|
129
216
|
lines.push('', '### 问题明细');
|
|
130
217
|
for (const issue of issues) {
|
|
131
218
|
lines.push(
|
|
132
|
-
`- [${issue.code}] ${issue.subject || '本次变更'} · 状态:${issue.
|
|
219
|
+
`- [${issue.code}] ${issue.subject || '本次变更'} · 状态:${issue.status_label || issue.status}`,
|
|
133
220
|
` - 根因:${issue.root_cause}`,
|
|
134
221
|
` - 预期:${issue.expected}`,
|
|
135
222
|
` - 实际:${issue.actual}`,
|
|
@@ -150,7 +237,7 @@ function renderSectionIssues(section, report) {
|
|
|
150
237
|
if (dispositions.length > 0) {
|
|
151
238
|
lines.push('', '### 告警处置');
|
|
152
239
|
for (const item of dispositions) {
|
|
153
|
-
lines.push(`- [告警处置] ${item.warning || item.code || 'UNKNOWN'}:${item.
|
|
240
|
+
lines.push(`- [告警处置] ${item.warning || item.code || 'UNKNOWN'}:${item.disposition_label || '待处理'}`);
|
|
154
241
|
}
|
|
155
242
|
}
|
|
156
243
|
|
|
@@ -168,8 +255,16 @@ function renderSectionIssues(section, report) {
|
|
|
168
255
|
lines.push(`- 警告:${warning.command || 'check'} 阶段 ${count} 个${statusLabel}`);
|
|
169
256
|
if (items && items.length > 0) {
|
|
170
257
|
for (const item of items) {
|
|
258
|
+
const itemStatus = ({
|
|
259
|
+
fixed: '已解决',
|
|
260
|
+
accepted: '已接受,保留观察',
|
|
261
|
+
waived: '已豁免,不阻断',
|
|
262
|
+
ignored: '不重要,已标记',
|
|
263
|
+
needs_input: '等待补充信息',
|
|
264
|
+
open: '待处理',
|
|
265
|
+
}[item.disposition] || statusLabel);
|
|
171
266
|
lines.push(
|
|
172
|
-
` - ${item.description || '待确认项'}${item.target ? `(${item.target})` : ''}`,
|
|
267
|
+
` - [${itemStatus}] ${item.description || '待确认项'}${item.target ? `(${item.target})` : ''}`,
|
|
173
268
|
);
|
|
174
269
|
}
|
|
175
270
|
}
|
|
@@ -178,12 +273,12 @@ function renderSectionIssues(section, report) {
|
|
|
178
273
|
lines.push('- 无警告(无需修复)');
|
|
179
274
|
}
|
|
180
275
|
|
|
181
|
-
const notes =
|
|
276
|
+
const notes = section.process_notes || {};
|
|
182
277
|
lines.push('', '## 过程记录');
|
|
183
278
|
lines.push(`- 数据可用:${notes._available ? '是' : '否'}`);
|
|
184
279
|
lines.push(`- 过程事件总数:${notes.total ?? 'null'}`);
|
|
185
280
|
if (notes.by_kind) {
|
|
186
|
-
lines.push(`-
|
|
281
|
+
lines.push(`- 按类型分布:${JSON.stringify(notes.by_kind)}`);
|
|
187
282
|
}
|
|
188
283
|
if (Array.isArray(notes.notes) && notes.notes.length > 0) {
|
|
189
284
|
for (const note of notes.notes) {
|
|
@@ -209,10 +304,34 @@ function renderSectionArtifacts(section, report) {
|
|
|
209
304
|
'',
|
|
210
305
|
'### 变更文件',
|
|
211
306
|
`- 数据可用:${rawChanged._available ? '是' : '否'}`,
|
|
212
|
-
`- 最终变更文件数:${formatValue(changed.files_changed)}`,
|
|
213
307
|
`- 新增行数:${formatValue(changed.added_lines)}`,
|
|
214
|
-
`- 对账状态:${formatValue(changed.
|
|
308
|
+
`- 对账状态:${formatValue(changed.reconciliation_status_label)}`,
|
|
215
309
|
];
|
|
310
|
+
const taskScope = changed.task_scope || {};
|
|
311
|
+
const finalScope = changed.final_scope || {};
|
|
312
|
+
lines.push(
|
|
313
|
+
'',
|
|
314
|
+
`#### ${taskScope.label || '任务过程中涉及的文件'}`,
|
|
315
|
+
`- 口径说明:${taskScope.explanation || '汇总任务完成事件中记录的文件。'}`,
|
|
316
|
+
`- 文件数:${formatValue(taskScope.count)}`,
|
|
317
|
+
`- 文件清单:${Array.isArray(taskScope.files) ? (taskScope.files.join(', ') || '无') : '暂无数据'}`,
|
|
318
|
+
`- 来源:${formatValue(taskScope.source)};任务:${(taskScope.task_ids || []).join(', ') || '未记录'};最新记录时间:${formatValue(taskScope.latest_at)}`,
|
|
319
|
+
);
|
|
320
|
+
if ((taskScope.no_file_change_reasons || []).length > 0) {
|
|
321
|
+
for (const item of taskScope.no_file_change_reasons) {
|
|
322
|
+
lines.push(`- 未改文件说明:${item.task_id || '未标识任务'}:${item.reason || item}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
lines.push(
|
|
326
|
+
'',
|
|
327
|
+
`#### ${finalScope.label || '最终实际交付的文件'}`,
|
|
328
|
+
`- 口径说明:${finalScope.explanation || '读取最后一次输出快照。'}`,
|
|
329
|
+
`- 文件数:${formatValue(finalScope.count)}`,
|
|
330
|
+
`- 文件清单:${Array.isArray(finalScope.files) ? (finalScope.files.join(', ') || '无') : '暂无数据'}`,
|
|
331
|
+
`- 来源:${formatValue(finalScope.source)};事件:${formatValue(finalScope.event_id)};快照时间:${formatValue(finalScope.captured_at)}`,
|
|
332
|
+
`- 未产生交付文件的原因:${formatValue(finalScope.no_output_reason)}`,
|
|
333
|
+
`- 行数统计说明:${finalScope.line_statistics_explanation || '未记录'}`,
|
|
334
|
+
);
|
|
216
335
|
if ((changed.only_in_task_events || []).length > 0) {
|
|
217
336
|
lines.push(`- 仅任务事件:${changed.only_in_task_events.join(', ')}`);
|
|
218
337
|
}
|
|
@@ -237,12 +356,6 @@ function renderSectionArtifacts(section, report) {
|
|
|
237
356
|
`- 迁移文件数:${Array.isArray(copied) ? copied.length : 'null'}`,
|
|
238
357
|
);
|
|
239
358
|
lines.push(
|
|
240
|
-
'',
|
|
241
|
-
'### 归因审计',
|
|
242
|
-
`- 显式关联历史事件:${section.attribution_summary?.linked_source_event_count ?? 0}`,
|
|
243
|
-
`- 有效关联:${section.attribution_summary?.active_link_count ?? 0}`,
|
|
244
|
-
`- 未归属候选:${section.attribution_summary?.unassigned_candidate_count ?? 0}`,
|
|
245
|
-
`- 被拒绝的跨变更碰撞:${section.attribution_summary?.rejected_collision_count ?? 0}`,
|
|
246
359
|
'',
|
|
247
360
|
'### 归档结果',
|
|
248
361
|
`- 归档原因:${archive?.reason || 'null'}`,
|
|
@@ -261,15 +374,20 @@ function renderSectionArtifacts(section, report) {
|
|
|
261
374
|
}
|
|
262
375
|
|
|
263
376
|
function renderChangeReportMarkdown(report) {
|
|
264
|
-
|
|
377
|
+
if (!report.change_summary || !report.artifacts?.files || !report.metric_details) {
|
|
378
|
+
report = buildChangeReportModel(report);
|
|
379
|
+
}
|
|
265
380
|
const projectLabel = report.project_root
|
|
266
381
|
? String(report.project_root).replace(/[\\/]+$/, '').split(/[\\/]/).pop()
|
|
267
382
|
: null;
|
|
383
|
+
const view = sanitizeMarkdownValue(buildChangeReportViewModel(report));
|
|
384
|
+
report = sanitizeMarkdownValue(report);
|
|
385
|
+
const safeProjectLabel = projectLabel ? escapeMarkdownText(projectLabel) : null;
|
|
268
386
|
const lines = [
|
|
269
387
|
`# SDD 效果度量报告${view.change ? ` - ${view.change}` : ''}`,
|
|
270
388
|
'',
|
|
271
389
|
`- 生成时间:${report.generated_at || '未知'}`,
|
|
272
|
-
...(
|
|
390
|
+
...(safeProjectLabel ? [`- 项目路径:${safeProjectLabel}`] : []),
|
|
273
391
|
`- 统计范围:change/${view.change || 'unknown'}`,
|
|
274
392
|
`- 报告 Schema:${view.schema_version || 'unknown'}(指标契约 ${view.metrics_contract || 'unknown'})`,
|
|
275
393
|
`<!-- sdd-semantic-digest:${view.semantic_digest} -->`,
|