kld-sdd 2.6.14 → 2.6.16
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 +7 -5
- package/lib/init.js +118 -73
- package/lib/skills-bundle.js +3 -0
- package/package.json +3 -2
- package/skywalk-sdd/index.cjs +936 -109
- package/skywalk-sdd/metrics-v3.cjs +103 -15
- package/skywalk-sdd/openspec-shim.cjs +48 -0
- package/skywalk-sdd/reporting/change-report-markdown.cjs +294 -0
- package/skywalk-sdd/reporting/change-report-model.cjs +170 -27
- package/skywalk-sdd/reporting/change-report-renderer.cjs +78 -154
- package/skywalk-sdd/reporting/change-report-view-model.cjs +340 -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 +369 -0
- package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +41 -0
- package/templates/hooks/codebuddy/hooks/sdd-mid-checkpoint.cjs +108 -0
- package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +36 -1
- package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +248 -0
- package/templates/hooks/codebuddy/settings.json +8 -0
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +13 -0
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +15 -0
- package/templates/skills/kld-sdd/opsx-apply/reference.md +45 -2
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +1 -1
- package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +592 -0
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +3 -3
- package/templates/skills/kld-sdd/opsx-propose/reference.md +6 -7
- package/templates/skills/kld-sdd/tdd-core/reference.md +3 -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
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const REPORT_SCHEMA_VERSION = 'sdd-efficiency-report/
|
|
4
|
-
const METRICS_CONTRACT_VERSION = '
|
|
5
|
-
const
|
|
3
|
+
const REPORT_SCHEMA_VERSION = 'sdd-efficiency-report/v4';
|
|
4
|
+
const METRICS_CONTRACT_VERSION = '4.0';
|
|
5
|
+
const SCHEME_VERSION = 'V3.0';
|
|
6
|
+
/** Writable change types (scheme 5.3). Legacy document/other read as unknown. */
|
|
7
|
+
const CHANGE_TYPES = ['config', 'transaction', 'report', 'composite'];
|
|
8
|
+
const LEGACY_CHANGE_TYPES = ['document', 'other'];
|
|
6
9
|
const MINIMUM_METRICS = ['e1', 'q3', 'p4', 'p_r', 'p_h'];
|
|
7
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Format artifact byte size: <1MiB → KB, else MB; two decimals; never bare B.
|
|
13
|
+
* @param {number} bytes
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
function formatArtifactSize(bytes) {
|
|
17
|
+
const n = Number(bytes);
|
|
18
|
+
if (!Number.isFinite(n) || n < 0) return '0.00 KB';
|
|
19
|
+
const kib = 1024;
|
|
20
|
+
const mib = 1024 * 1024;
|
|
21
|
+
if (n < mib) {
|
|
22
|
+
const kb = n / kib;
|
|
23
|
+
return `${kb.toFixed(2)} KB`;
|
|
24
|
+
}
|
|
25
|
+
return `${(n / mib).toFixed(2)} MB`;
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
const ALERT_THRESHOLDS = {
|
|
9
29
|
q3: { red: 70, yellow: 85, direction: 'higher_better' },
|
|
10
30
|
p4: { red: 0.4, yellow: 0.6, direction: 'higher_better' },
|
|
@@ -126,7 +146,27 @@ function countScenariosInMarkdown(text) {
|
|
|
126
146
|
function validateChangeType(value) {
|
|
127
147
|
if (typeof value !== 'string') return 'unknown';
|
|
128
148
|
const normalized = value.trim().toLowerCase();
|
|
129
|
-
|
|
149
|
+
if (CHANGE_TYPES.includes(normalized)) return normalized;
|
|
150
|
+
if (LEGACY_CHANGE_TYPES.includes(normalized)) return 'unknown';
|
|
151
|
+
return 'unknown';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function diagnoseChangeType(value) {
|
|
155
|
+
if (typeof value !== 'string') {
|
|
156
|
+
return { change_type: 'unknown', legacy_change_type: null, warning: null };
|
|
157
|
+
}
|
|
158
|
+
const normalized = value.trim().toLowerCase();
|
|
159
|
+
if (CHANGE_TYPES.includes(normalized)) {
|
|
160
|
+
return { change_type: normalized, legacy_change_type: null, warning: null };
|
|
161
|
+
}
|
|
162
|
+
if (LEGACY_CHANGE_TYPES.includes(normalized)) {
|
|
163
|
+
return {
|
|
164
|
+
change_type: 'unknown',
|
|
165
|
+
legacy_change_type: normalized,
|
|
166
|
+
warning: `legacy_change_type:${normalized}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return { change_type: 'unknown', legacy_change_type: null, warning: 'invalid_change_type' };
|
|
130
170
|
}
|
|
131
171
|
|
|
132
172
|
function resolveReviewerIndependence(meta = {}) {
|
|
@@ -850,9 +890,41 @@ function buildSchemeCompliance(input = {}) {
|
|
|
850
890
|
const minimumStatus = minimum.numeric_available === minimum.numeric_total
|
|
851
891
|
? (minimum.trusted_available === minimum.numeric_total ? 'implemented' : 'provisional')
|
|
852
892
|
: 'provisional';
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
893
|
+
const q1Raw = quality.q1_human_status
|
|
894
|
+
|| quality.q1_status
|
|
895
|
+
|| quality.q1_spec_conformance?.status
|
|
896
|
+
|| 'pending-upstream';
|
|
897
|
+
const q1Status = (q1Raw === 'unverified' || q1Raw === 'self-review')
|
|
898
|
+
? 'provisional'
|
|
899
|
+
: (q1Raw === 'pending-upstream' ? 'pending-upstream' : q1Raw);
|
|
900
|
+
const q4Raw = quality.q4_status || quality.q4_tdd_coverage?.status || 'pending-upstream';
|
|
901
|
+
const q4Source = quality.q4_source || quality.q4_tdd_coverage?.source || null;
|
|
902
|
+
const q4Status = q4Source === 'conformance-fallback' || q4Raw === 'conformance-fallback'
|
|
903
|
+
? 'pending-upstream'
|
|
904
|
+
: q4Raw;
|
|
905
|
+
const q3Independence = quality.q3_reviewer_independence || 'unknown';
|
|
906
|
+
const q3Status = quality.q3_score_status === 'verified'
|
|
907
|
+
&& q3Independence === 'independent-review'
|
|
908
|
+
? 'implemented'
|
|
909
|
+
: 'provisional';
|
|
910
|
+
const semanticParity = input.semantic_parity_status || input.semantic_parity || null;
|
|
911
|
+
const multiFormatStatus = semanticParity === 'pass'
|
|
912
|
+
? 'implemented'
|
|
913
|
+
: (['md', 'html', 'json'].every(format => formats.includes(format)) ? 'provisional' : 'provisional');
|
|
914
|
+
const alertLoopReady = Boolean(
|
|
915
|
+
input.alert_loop
|
|
916
|
+
&& input.alert_loop.root_cause
|
|
917
|
+
&& input.alert_loop.owner
|
|
918
|
+
&& input.alert_loop.due
|
|
919
|
+
&& input.alert_loop.expected_impact
|
|
920
|
+
&& input.alert_loop.two_cycle_evidence,
|
|
921
|
+
);
|
|
922
|
+
const eventContractHealth = input.evidence_contract_health
|
|
923
|
+
|| input.event_contract_health
|
|
924
|
+
|| null;
|
|
925
|
+
const reproducibleStatus = eventContractHealth === 'healthy' && Number(input.event_count) > 0
|
|
926
|
+
? 'implemented'
|
|
927
|
+
: (Number(input.event_count) > 0 ? 'provisional' : 'provisional');
|
|
856
928
|
const statusLabel = status => ({
|
|
857
929
|
implemented: '已实现',
|
|
858
930
|
provisional: '部分可用',
|
|
@@ -864,6 +936,7 @@ function buildSchemeCompliance(input = {}) {
|
|
|
864
936
|
unknown: '未知',
|
|
865
937
|
'independent-review': '独立复核',
|
|
866
938
|
'self-review': '自评',
|
|
939
|
+
unverified: '未人工验证',
|
|
867
940
|
}[status] || status || '未知');
|
|
868
941
|
|
|
869
942
|
const items = [
|
|
@@ -886,7 +959,7 @@ function buildSchemeCompliance(input = {}) {
|
|
|
886
959
|
label: '条件指标(Q1、Q4)',
|
|
887
960
|
status: q1Status === 'pending-upstream' || q4Status === 'pending-upstream'
|
|
888
961
|
? 'pending-upstream'
|
|
889
|
-
: 'implemented',
|
|
962
|
+
: (q1Status === 'provisional' || q4Status === 'provisional' ? 'provisional' : 'implemented'),
|
|
890
963
|
evidence: `Q1:${statusLabel(q1Status)};Q4:${statusLabel(q4Status)}`,
|
|
891
964
|
},
|
|
892
965
|
{
|
|
@@ -899,7 +972,7 @@ function buildSchemeCompliance(input = {}) {
|
|
|
899
972
|
requirement: 'independent-reviewer',
|
|
900
973
|
label: '独立复核',
|
|
901
974
|
status: q3Status,
|
|
902
|
-
evidence: `Q3:${statusLabel(quality.q3_score_status || 'provisional')};复核方式:${statusLabel(
|
|
975
|
+
evidence: `Q3:${statusLabel(quality.q3_score_status || 'provisional')};复核方式:${statusLabel(q3Independence)}`,
|
|
903
976
|
},
|
|
904
977
|
{
|
|
905
978
|
requirement: 'tracking-periods',
|
|
@@ -922,20 +995,30 @@ function buildSchemeCompliance(input = {}) {
|
|
|
922
995
|
{
|
|
923
996
|
requirement: 'alert-feedback-loop',
|
|
924
997
|
label: '指标与证据告警闭环',
|
|
925
|
-
status:
|
|
926
|
-
evidence:
|
|
998
|
+
status: alertLoopReady ? 'implemented' : 'provisional',
|
|
999
|
+
evidence: alertLoopReady
|
|
1000
|
+
? '具备根因、负责人、期限、预期影响与两周期验证'
|
|
1001
|
+
: '仍缺根因/负责人/期限/预期影响/两周期验证中的完整字段',
|
|
927
1002
|
},
|
|
928
1003
|
{
|
|
929
1004
|
requirement: 'multi-format-same-model',
|
|
930
1005
|
label: 'Markdown、HTML、JSON 同源',
|
|
931
|
-
status:
|
|
932
|
-
evidence:
|
|
1006
|
+
status: multiFormatStatus,
|
|
1007
|
+
evidence: semanticParity === 'pass'
|
|
1008
|
+
? '三格式语义对账通过'
|
|
1009
|
+
: (formats.length > 0
|
|
1010
|
+
? `${formats.map(format => ({ md: 'Markdown', html: 'HTML', json: 'JSON' }[format] || format)).join(' / ')} 已产出,语义对账未确认`
|
|
1011
|
+
: '格式信息未提供'),
|
|
933
1012
|
},
|
|
934
1013
|
{
|
|
935
1014
|
requirement: 'reproducible-auditable',
|
|
936
1015
|
label: '可复算、可审计',
|
|
937
|
-
status:
|
|
938
|
-
evidence:
|
|
1016
|
+
status: reproducibleStatus,
|
|
1017
|
+
evidence: eventContractHealth === 'healthy'
|
|
1018
|
+
? `${input.event_count} 条事件且证据契约健康`
|
|
1019
|
+
: (input.event_count > 0
|
|
1020
|
+
? `${input.event_count} 条 JSONL 事件;证据契约健康度未达标`
|
|
1021
|
+
: '未提供事件数量'),
|
|
939
1022
|
},
|
|
940
1023
|
];
|
|
941
1024
|
const counts = {};
|
|
@@ -945,6 +1028,7 @@ function buildSchemeCompliance(input = {}) {
|
|
|
945
1028
|
version: 'V3.0',
|
|
946
1029
|
items,
|
|
947
1030
|
counts,
|
|
1031
|
+
evidence_contract_health: eventContractHealth || null,
|
|
948
1032
|
};
|
|
949
1033
|
}
|
|
950
1034
|
|
|
@@ -1125,7 +1209,9 @@ function deriveSuccessfulReworkReason(attemptContext = {}) {
|
|
|
1125
1209
|
module.exports = {
|
|
1126
1210
|
REPORT_SCHEMA_VERSION,
|
|
1127
1211
|
METRICS_CONTRACT_VERSION,
|
|
1212
|
+
SCHEME_VERSION,
|
|
1128
1213
|
CHANGE_TYPES,
|
|
1214
|
+
LEGACY_CHANGE_TYPES,
|
|
1129
1215
|
ALERT_THRESHOLDS,
|
|
1130
1216
|
MINIMUM_METRICS,
|
|
1131
1217
|
clamp,
|
|
@@ -1148,6 +1234,8 @@ module.exports = {
|
|
|
1148
1234
|
findDevelopmentActivities,
|
|
1149
1235
|
deriveSuccessfulReworkReason,
|
|
1150
1236
|
validateChangeType,
|
|
1237
|
+
diagnoseChangeType,
|
|
1238
|
+
formatArtifactSize,
|
|
1151
1239
|
createInvalidArgError,
|
|
1152
1240
|
countScenariosInMarkdown,
|
|
1153
1241
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* openspec-shim.cjs — 自动定位 spec 包并透传 openspec 命令。
|
|
6
|
+
*
|
|
7
|
+
* 用法: node skywalk-sdd/openspec-shim.cjs <openspec-args>
|
|
8
|
+
* 示例: node skywalk-sdd/openspec-shim.cjs list
|
|
9
|
+
* node skywalk-sdd/openspec-shim.cjs add change my-feature
|
|
10
|
+
*
|
|
11
|
+
* 自动通过 resolve-spec-root.cjs 定位 spec 包目录,
|
|
12
|
+
* 然后在该目录下执行 openspec 命令。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { resolveSpecProjectRoot } = require('./ontology/resolve-spec-root.cjs');
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
|
|
19
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
20
|
+
console.log('用法: node skywalk-sdd/openspec-shim.cjs <openspec-args>');
|
|
21
|
+
console.log('示例: node skywalk-sdd/openspec-shim.cjs list');
|
|
22
|
+
console.log(' node skywalk-sdd/openspec-shim.cjs add change my-feature');
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const projectRoot = process.cwd();
|
|
27
|
+
const specRoot = resolveSpecProjectRoot(projectRoot);
|
|
28
|
+
|
|
29
|
+
if (specRoot === projectRoot) {
|
|
30
|
+
console.error('⚠️ 未找到 spec 包目录(未配置 .sdd-spec-root,也未检测到 *-sdd-specs 仓库)');
|
|
31
|
+
console.error(' 配置方式: 在项目根目录创建 .sdd-spec-root 文件,写入 spec 包相对或绝对路径');
|
|
32
|
+
console.error(' 或使用: kld-sdd link-spec --path=<spec-clone>');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { execSync } = require('child_process');
|
|
37
|
+
const openspecArgs = args.map(a => `"${a.replace(/"/g, '\\"')}"`).join(' ');
|
|
38
|
+
const cmd = `openspec ${openspecArgs}`;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
execSync(cmd, { cwd: specRoot, stdio: 'inherit' });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err.status) {
|
|
44
|
+
process.exit(err.status);
|
|
45
|
+
}
|
|
46
|
+
console.error(`❌ openspec 执行失败: ${err.message}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { buildChangeReportViewModel } = require('./change-report-view-model.cjs');
|
|
4
|
+
|
|
5
|
+
function formatValue(value) {
|
|
6
|
+
if (value == null || value === '') return '暂无数据';
|
|
7
|
+
return String(value);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function formatRatioToPercent(ratio) {
|
|
11
|
+
if (ratio == null || !Number.isFinite(Number(ratio))) return '暂无数据';
|
|
12
|
+
return `${(Number(ratio) * 100).toFixed(1)}%`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function renderSectionOverview(section, report) {
|
|
16
|
+
const minSet = report.minimum_metric_set || {};
|
|
17
|
+
const lines = [
|
|
18
|
+
'## 一句话结论',
|
|
19
|
+
'',
|
|
20
|
+
`> ${section.summary}`,
|
|
21
|
+
'',
|
|
22
|
+
`## ${section.title}`,
|
|
23
|
+
'',
|
|
24
|
+
'### 摘要卡片',
|
|
25
|
+
];
|
|
26
|
+
for (const card of section.cards || []) {
|
|
27
|
+
lines.push(`- ${card.label}:${formatValue(card.value)}`);
|
|
28
|
+
}
|
|
29
|
+
if (report.scheme_compliance?.counts) {
|
|
30
|
+
lines.push(
|
|
31
|
+
`- 方案覆盖:${report.scheme_compliance.counts.implemented ?? 0}/${report.scheme_compliance.items?.length ?? 0} 项已完整实现`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
lines.push(
|
|
35
|
+
'',
|
|
36
|
+
'### 关键指标可信覆盖',
|
|
37
|
+
`- 数值覆盖:${formatRatioToPercent(minSet.numeric_coverage_rate)}(${minSet.numeric_available ?? 0}/${minSet.numeric_total ?? 5})`,
|
|
38
|
+
`- 可信覆盖:${formatRatioToPercent(minSet.trusted_coverage_rate)}(${minSet.trusted_available ?? 0}/${minSet.numeric_total ?? 5};临时 ${minSet.provisional ?? 0} 项)`,
|
|
39
|
+
'',
|
|
40
|
+
'## 核心指标',
|
|
41
|
+
`- 每个场景平均用时(E1):见「指标与阶段」`,
|
|
42
|
+
`- 规约检查得分(Q3):见「指标与阶段」`,
|
|
43
|
+
`- 编码前是否先检查(P4):见「指标与阶段」`,
|
|
44
|
+
);
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderSectionMetrics(section, report) {
|
|
49
|
+
const tdd = section.tdd_pairs || {};
|
|
50
|
+
const reasons = report.metrics?.process?.rework_summary?.reasons;
|
|
51
|
+
const lines = [
|
|
52
|
+
`## ${section.title}`,
|
|
53
|
+
'',
|
|
54
|
+
section.summary,
|
|
55
|
+
'',
|
|
56
|
+
'### 关键指标',
|
|
57
|
+
];
|
|
58
|
+
for (const metric of section.metric_cards || []) {
|
|
59
|
+
lines.push(`- ${metric.name}(${metric.code}):${metric.value} · ${metric.state}`);
|
|
60
|
+
}
|
|
61
|
+
if (reasons?.by_category) {
|
|
62
|
+
const parts = Object.entries(reasons.by_category)
|
|
63
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
64
|
+
.join(', ');
|
|
65
|
+
lines.push(`- 重复执行原因分布:${parts || '无'}`);
|
|
66
|
+
}
|
|
67
|
+
lines.push(
|
|
68
|
+
'',
|
|
69
|
+
'### TDD 对',
|
|
70
|
+
`- 完整度:${tdd.complete == null || tdd.required == null ? '暂无数据' : `${tdd.complete}/${tdd.required}`}`,
|
|
71
|
+
);
|
|
72
|
+
const incomplete = (tdd.pairs || []).filter(pair => pair.status && pair.status !== 'complete');
|
|
73
|
+
if (incomplete.length > 0) {
|
|
74
|
+
lines.push('- 缺口:');
|
|
75
|
+
for (const pair of incomplete) {
|
|
76
|
+
lines.push(` - ${pair.pair_id}:${pair.status}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const stages = section.stages || [];
|
|
80
|
+
lines.push('', '### 阶段时间线');
|
|
81
|
+
if (stages.length === 0) {
|
|
82
|
+
lines.push('- 无阶段节点');
|
|
83
|
+
} else {
|
|
84
|
+
for (const node of stages.slice(0, 40)) {
|
|
85
|
+
lines.push(
|
|
86
|
+
`- ${node.display_stage} R${node.display_round}:${node.display_result}`
|
|
87
|
+
+ `(${node.display_duration})`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return lines;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function renderSectionIssues(section, report) {
|
|
95
|
+
const lines = [
|
|
96
|
+
`## ${section.title}`,
|
|
97
|
+
'',
|
|
98
|
+
section.summary,
|
|
99
|
+
'',
|
|
100
|
+
];
|
|
101
|
+
const audit = section.task_evidence_audit || {};
|
|
102
|
+
if (audit.direct_test_backlink || audit.strict_test_coverage) {
|
|
103
|
+
lines.push('### 任务证据四口径');
|
|
104
|
+
lines.push(
|
|
105
|
+
`- 文档完成:${formatValue(audit.document_completion?.completed)}/${formatValue(audit.document_completion?.total)}`,
|
|
106
|
+
);
|
|
107
|
+
lines.push(
|
|
108
|
+
`- 直接回链:${formatValue(audit.direct_test_backlink?.verified)}/${formatValue(audit.direct_test_backlink?.total)}`,
|
|
109
|
+
);
|
|
110
|
+
lines.push(
|
|
111
|
+
`- 严格覆盖:${formatValue(audit.strict_test_coverage?.verified)}/${formatValue(audit.strict_test_coverage?.total)}`,
|
|
112
|
+
);
|
|
113
|
+
lines.push('');
|
|
114
|
+
}
|
|
115
|
+
lines.push('### 问题类别');
|
|
116
|
+
const categories = section.categories || [];
|
|
117
|
+
if (categories.length === 0) {
|
|
118
|
+
lines.push('- 当前没有归并后的问题类别。');
|
|
119
|
+
} else {
|
|
120
|
+
for (const group of categories) {
|
|
121
|
+
lines.push(
|
|
122
|
+
`- ${group.label || group.code}:命中 ${group.occurrence_count || 0} 次,关联任务 ${(group.affected_tasks || []).length} 个`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const findings = section.apply_findings || [];
|
|
127
|
+
const issues = section.issues || [];
|
|
128
|
+
if (issues.length > 0) {
|
|
129
|
+
lines.push('', '### 问题明细');
|
|
130
|
+
for (const issue of issues) {
|
|
131
|
+
lines.push(
|
|
132
|
+
`- [${issue.code}] ${issue.subject || '本次变更'} · 状态:${issue.status} · 判定:${issue.verdict}`,
|
|
133
|
+
` - 根因:${issue.root_cause}`,
|
|
134
|
+
` - 预期:${issue.expected}`,
|
|
135
|
+
` - 实际:${issue.actual}`,
|
|
136
|
+
` - 影响:${issue.impact}`,
|
|
137
|
+
` - 修复建议:${issue.remediation}`,
|
|
138
|
+
` - 证据事件:${(issue.evidence_event_ids || []).join(', ') || '暂无可定位事件'}`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
} else if (findings.length > 0) {
|
|
142
|
+
lines.push('', '### 审计发现');
|
|
143
|
+
for (const finding of findings) {
|
|
144
|
+
lines.push(`- [${finding.code}] ${finding.task_id || '变更'}:${finding.message || ''}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const dispositions = section.warning_dispositions
|
|
148
|
+
|| section.report_warning_dispositions
|
|
149
|
+
|| [];
|
|
150
|
+
if (dispositions.length > 0) {
|
|
151
|
+
lines.push('', '### 告警处置');
|
|
152
|
+
for (const item of dispositions) {
|
|
153
|
+
lines.push(`- [告警处置] ${item.warning || item.code || 'UNKNOWN'}:${item.disposition || 'open'}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const known = report.known_risks || {};
|
|
158
|
+
lines.push('', '### 已知风险');
|
|
159
|
+
lines.push(`- 数据可用:${known._available ? '是' : '否'}`);
|
|
160
|
+
lines.push(
|
|
161
|
+
`- 严重问题数:${known._available ? (known.severe_issues?.length ?? 0) : '无数据'}`,
|
|
162
|
+
);
|
|
163
|
+
if (Array.isArray(known.warnings) && known.warnings.length > 0) {
|
|
164
|
+
for (const warning of known.warnings) {
|
|
165
|
+
const items = Array.isArray(warning.items) ? warning.items : null;
|
|
166
|
+
const count = items ? items.length : (warning.warnings || 0);
|
|
167
|
+
const statusLabel = warning.resolved === true ? '✅已修复' : '⏳待确认';
|
|
168
|
+
lines.push(`- 警告:${warning.command || 'check'} 阶段 ${count} 个${statusLabel}`);
|
|
169
|
+
if (items && items.length > 0) {
|
|
170
|
+
for (const item of items) {
|
|
171
|
+
lines.push(
|
|
172
|
+
` - ${item.description || '待确认项'}${item.target ? `(${item.target})` : ''}`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
lines.push('- 无警告(无需修复)');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const notes = report.process_notes || {};
|
|
182
|
+
lines.push('', '## 过程记录');
|
|
183
|
+
lines.push(`- 数据可用:${notes._available ? '是' : '否'}`);
|
|
184
|
+
lines.push(`- 过程事件总数:${notes.total ?? 'null'}`);
|
|
185
|
+
if (notes.by_kind) {
|
|
186
|
+
lines.push(`- 按 kind 分布:${JSON.stringify(notes.by_kind)}`);
|
|
187
|
+
}
|
|
188
|
+
if (Array.isArray(notes.notes) && notes.notes.length > 0) {
|
|
189
|
+
for (const note of notes.notes) {
|
|
190
|
+
lines.push(
|
|
191
|
+
`- [${note.stage || 'unknown'}] ${note.kind || 'note'}${note.round ? ` R${note.round}` : ''}:${note.summary || ''}${note.target ? ` → ${note.target}` : ''}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
lines.push('- 无过程事件');
|
|
196
|
+
}
|
|
197
|
+
return lines;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function renderSectionArtifacts(section, report) {
|
|
201
|
+
const changed = section.changed_files || {};
|
|
202
|
+
const rawChanged = report.changed_files || {};
|
|
203
|
+
const archive = report.archive_result || null;
|
|
204
|
+
const taskCompletion = archive?.task_completion || null;
|
|
205
|
+
const lines = [
|
|
206
|
+
`## ${section.title}`,
|
|
207
|
+
'',
|
|
208
|
+
section.summary,
|
|
209
|
+
'',
|
|
210
|
+
'### 变更文件',
|
|
211
|
+
`- 数据可用:${rawChanged._available ? '是' : '否'}`,
|
|
212
|
+
`- 最终变更文件数:${formatValue(changed.files_changed)}`,
|
|
213
|
+
`- 新增行数:${formatValue(changed.added_lines)}`,
|
|
214
|
+
`- 对账状态:${formatValue(changed.reconciliation_status)}`,
|
|
215
|
+
];
|
|
216
|
+
if ((changed.only_in_task_events || []).length > 0) {
|
|
217
|
+
lines.push(`- 仅任务事件:${changed.only_in_task_events.join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
if ((changed.only_in_final_output || []).length > 0) {
|
|
220
|
+
lines.push(`- 仅最终输出:${changed.only_in_final_output.join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
lines.push('', '### 产物清单');
|
|
223
|
+
const artifacts = section.artifacts || [];
|
|
224
|
+
if (artifacts.length === 0) {
|
|
225
|
+
lines.push('- 暂无产物条目');
|
|
226
|
+
} else {
|
|
227
|
+
for (const item of artifacts) {
|
|
228
|
+
const size = item.size_display
|
|
229
|
+
|| '—';
|
|
230
|
+
lines.push(
|
|
231
|
+
`- ${item.name}:${item.exists ? '已生成' : '未生成'} · ${item.path || '—'} · ${size}`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const copied = report.artifacts?.copied_specs;
|
|
236
|
+
lines.push(
|
|
237
|
+
`- 迁移文件数:${Array.isArray(copied) ? copied.length : 'null'}`,
|
|
238
|
+
);
|
|
239
|
+
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
|
+
'',
|
|
247
|
+
'### 归档结果',
|
|
248
|
+
`- 归档原因:${archive?.reason || 'null'}`,
|
|
249
|
+
`- 归档方式:${archive?.method || 'null'}`,
|
|
250
|
+
`- 归档目录:${archive?.archive_path || report.artifacts?.archive_path || 'null'}`,
|
|
251
|
+
`- 归档清单:${archive?.manifest_path || report.artifacts?.manifest_path || 'null'}`,
|
|
252
|
+
`- 最终报告:${archive?.report_path || 'null'}`,
|
|
253
|
+
`- 主任务完成:${taskCompletion?.primary_tasks
|
|
254
|
+
? `${taskCompletion.primary_tasks.completed}/${taskCompletion.primary_tasks.total}`
|
|
255
|
+
: 'null'}`,
|
|
256
|
+
`- 全部 checkbox 完成:${taskCompletion?.completed ?? 'null'}/${taskCompletion?.total ?? 'null'}(含验收子项;分母≠主任务数)`,
|
|
257
|
+
`- 未勾选 checkbox:${taskCompletion?.incomplete ?? 'null'}`,
|
|
258
|
+
`- 未勾选任务项:${taskCompletion?.incomplete ?? 'null'}`,
|
|
259
|
+
);
|
|
260
|
+
return lines;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function renderChangeReportMarkdown(report) {
|
|
264
|
+
const view = buildChangeReportViewModel(report);
|
|
265
|
+
const projectLabel = report.project_root
|
|
266
|
+
? String(report.project_root).replace(/[\\/]+$/, '').split(/[\\/]/).pop()
|
|
267
|
+
: null;
|
|
268
|
+
const lines = [
|
|
269
|
+
`# SDD 效果度量报告${view.change ? ` - ${view.change}` : ''}`,
|
|
270
|
+
'',
|
|
271
|
+
`- 生成时间:${report.generated_at || '未知'}`,
|
|
272
|
+
...(projectLabel ? [`- 项目路径:${projectLabel}`] : []),
|
|
273
|
+
`- 统计范围:change/${view.change || 'unknown'}`,
|
|
274
|
+
`- 报告 Schema:${view.schema_version || 'unknown'}(指标契约 ${view.metrics_contract || 'unknown'})`,
|
|
275
|
+
`<!-- sdd-semantic-digest:${view.semantic_digest} -->`,
|
|
276
|
+
'',
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
for (const sectionId of view.section_order) {
|
|
280
|
+
const section = view.sections[sectionId];
|
|
281
|
+
if (!section) continue;
|
|
282
|
+
if (sectionId === 'overview') lines.push(...renderSectionOverview(section, report));
|
|
283
|
+
else if (sectionId === 'metrics-stages') lines.push(...renderSectionMetrics(section, report));
|
|
284
|
+
else if (sectionId === 'issues-evidence') lines.push(...renderSectionIssues(section, report));
|
|
285
|
+
else if (sectionId === 'artifacts-trace') lines.push(...renderSectionArtifacts(section, report));
|
|
286
|
+
lines.push('');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return lines.join('\n');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = {
|
|
293
|
+
renderChangeReportMarkdown,
|
|
294
|
+
};
|