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
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
|
+
const {
|
|
5
|
+
localizeIssueStatus,
|
|
6
|
+
localizeMetricStatus,
|
|
7
|
+
localizeStageName,
|
|
8
|
+
ISSUE_STATUS_LABELS,
|
|
9
|
+
validateCoreMetricResult,
|
|
10
|
+
} = require('./core-metric-definitions.cjs');
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
13
|
* Shared change-report view model for Markdown / HTML / parity digests.
|
|
@@ -27,13 +34,22 @@ function fractionText(verified, total) {
|
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
function percentText(value, digits = 2) {
|
|
30
|
-
return
|
|
37
|
+
return value !== null
|
|
38
|
+
&& value !== undefined
|
|
39
|
+
&& value !== ''
|
|
40
|
+
&& Number.isFinite(Number(value))
|
|
31
41
|
? `${(Number(value) * 100).toFixed(digits)}%`
|
|
32
42
|
: '暂无数据';
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
function durationText(value) {
|
|
36
|
-
if (
|
|
46
|
+
if (value === null
|
|
47
|
+
|| value === undefined
|
|
48
|
+
|| value === ''
|
|
49
|
+
|| !Number.isFinite(Number(value))
|
|
50
|
+
|| Number(value) < 0) {
|
|
51
|
+
return '暂无数据';
|
|
52
|
+
}
|
|
37
53
|
const totalSeconds = Math.round(Number(value) / 1000);
|
|
38
54
|
const hours = Math.floor(totalSeconds / 3600);
|
|
39
55
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
@@ -51,6 +67,119 @@ function localTimeText(value) {
|
|
|
51
67
|
: parsed.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
|
52
68
|
}
|
|
53
69
|
|
|
70
|
+
function resultText(value) {
|
|
71
|
+
return ({
|
|
72
|
+
success: '成功',
|
|
73
|
+
partial: '部分通过',
|
|
74
|
+
failure: '失败',
|
|
75
|
+
failed: '失败',
|
|
76
|
+
error: '错误',
|
|
77
|
+
blocked: '已阻断',
|
|
78
|
+
unknown: '未知',
|
|
79
|
+
}[value] || String(value || '未知'));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function reviewIndependenceText(value) {
|
|
83
|
+
return ({
|
|
84
|
+
'independent-review': '独立复核',
|
|
85
|
+
'self-review': '自查',
|
|
86
|
+
unknown: '无法确认',
|
|
87
|
+
}[value] || '未记录');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const PROCESS_NOTE_KIND_LABELS = Object.freeze({
|
|
91
|
+
fix: '修复记录',
|
|
92
|
+
clarification: '需求澄清',
|
|
93
|
+
decision: '决策记录',
|
|
94
|
+
'api-error': '接口错误',
|
|
95
|
+
'model-switch': '模型切换',
|
|
96
|
+
user_decision: '用户决策',
|
|
97
|
+
scope_change: '范围调整',
|
|
98
|
+
api_error: '接口错误',
|
|
99
|
+
model_error: '模型错误',
|
|
100
|
+
recovery: '恢复处理',
|
|
101
|
+
test_exception: '测试例外',
|
|
102
|
+
note: '过程说明',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const RECONCILIATION_STATUS_LABELS = Object.freeze({
|
|
106
|
+
consistent: '两个口径一致',
|
|
107
|
+
unavailable: '暂无可对账数据',
|
|
108
|
+
'task-evidence-missing': '缺少任务过程文件记录',
|
|
109
|
+
'final-snapshot-missing': '缺少最终输出快照',
|
|
110
|
+
'count-mismatch': '两个口径的文件数量不同',
|
|
111
|
+
'list-mismatch': '两个口径的文件清单不同',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const FILE_SOURCE_LABELS = Object.freeze({
|
|
115
|
+
'task_update.files': '任务完成事件中的文件清单(task_update.files)',
|
|
116
|
+
'final_output_snapshot.files': '最终输出快照中的文件清单(final_output_snapshot.files)',
|
|
117
|
+
'ai_adoption_review.ai_diff.files': '旧版最终差异记录(ai_adoption_review.ai_diff.files)',
|
|
118
|
+
'test_results.assertions': '测试断言提到的文件(test_results.assertions)',
|
|
119
|
+
'dual-source': '任务事件与最终输出快照双来源(dual-source)',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
function processNoteKindText(value) {
|
|
123
|
+
return PROCESS_NOTE_KIND_LABELS[value] || String(value || '过程说明');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function fileSourceText(value) {
|
|
127
|
+
return FILE_SOURCE_LABELS[value] || String(value || '未记录');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function metricStateForDetail(
|
|
131
|
+
detail,
|
|
132
|
+
fallbackState,
|
|
133
|
+
fallbackCode = null,
|
|
134
|
+
provisionalLabel = '临时结果',
|
|
135
|
+
) {
|
|
136
|
+
const status = detail?.trust?.status || fallbackCode;
|
|
137
|
+
const label = ({
|
|
138
|
+
trusted: '可信',
|
|
139
|
+
verified: '已独立验证',
|
|
140
|
+
provisional: provisionalLabel,
|
|
141
|
+
available: '可计算',
|
|
142
|
+
inconsistent: '计算不一致',
|
|
143
|
+
missing: '需补证',
|
|
144
|
+
unavailable: '需补证',
|
|
145
|
+
'pending-upstream': '需补证',
|
|
146
|
+
}[status] || fallbackState);
|
|
147
|
+
return { label, code: status || null };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function translateProcessNotes(value) {
|
|
151
|
+
if (typeof value === 'string') {
|
|
152
|
+
return value.replace(/本轮不种桥/g, '本次未建立外部需求与本地能力的关联');
|
|
153
|
+
}
|
|
154
|
+
if (Array.isArray(value)) return value.map(translateProcessNotes);
|
|
155
|
+
if (value && typeof value === 'object') {
|
|
156
|
+
return Object.fromEntries(
|
|
157
|
+
Object.entries(value).map(([key, item]) => [key, translateProcessNotes(item)]),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function localizeProcessNotes(value) {
|
|
164
|
+
const translated = translateProcessNotes(value);
|
|
165
|
+
if (!translated || typeof translated !== 'object') return translated;
|
|
166
|
+
return {
|
|
167
|
+
...translated,
|
|
168
|
+
by_kind: translated.by_kind && typeof translated.by_kind === 'object'
|
|
169
|
+
? Object.fromEntries(
|
|
170
|
+
Object.entries(translated.by_kind).map(([kind, count]) => [processNoteKindText(kind), count]),
|
|
171
|
+
)
|
|
172
|
+
: translated.by_kind,
|
|
173
|
+
notes: Array.isArray(translated.notes)
|
|
174
|
+
? translated.notes.map(note => ({
|
|
175
|
+
...note,
|
|
176
|
+
kind_code: note.kind || null,
|
|
177
|
+
kind: processNoteKindText(note.kind),
|
|
178
|
+
}))
|
|
179
|
+
: translated.notes,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
54
183
|
function buildSemanticDigestInput(report, sections) {
|
|
55
184
|
const audit = report.task_evidence_audit || {};
|
|
56
185
|
const tdd = report.tdd_pair_audit || {};
|
|
@@ -99,7 +228,7 @@ function buildSemanticDigestInput(report, sections) {
|
|
|
99
228
|
diff_source: changed.diff_source ?? null,
|
|
100
229
|
reconciliation_status: changed.reconciliation_status ?? null,
|
|
101
230
|
},
|
|
102
|
-
|
|
231
|
+
metric_details: report.metric_details || null,
|
|
103
232
|
artifact_sizes: Object.fromEntries(
|
|
104
233
|
Object.entries(artifacts).map(([name, item]) => [name, item?.size_bytes ?? null]),
|
|
105
234
|
),
|
|
@@ -122,11 +251,20 @@ function buildChangeReportViewModel(report = {}) {
|
|
|
122
251
|
const process = metrics.process || {};
|
|
123
252
|
const telemetry = metrics.telemetry_health || {};
|
|
124
253
|
const minSet = report.minimum_metric_set || {};
|
|
125
|
-
const
|
|
126
|
-
|
|
254
|
+
const localizedIssues = (evidence.issues || []).map(issue => ({
|
|
255
|
+
...issue,
|
|
256
|
+
status_label: localizeIssueStatus(
|
|
257
|
+
Object.hasOwn(ISSUE_STATUS_LABELS, issue.disposition)
|
|
258
|
+
? issue.disposition
|
|
259
|
+
: issue.status,
|
|
260
|
+
),
|
|
261
|
+
}));
|
|
262
|
+
const openIssueCount = localizedIssues.filter(issue => issue.status === 'open').length;
|
|
263
|
+
const totalDurationMs = efficiency.total_stage_duration_ms
|
|
264
|
+
?? efficiency.measurement_stage_duration_ms
|
|
127
265
|
?? efficiency.effective_stage_duration_including_rework_ms
|
|
128
|
-
?? efficiency.e1_lead_time_ms
|
|
129
266
|
?? null;
|
|
267
|
+
const endpointElapsedMs = efficiency.e1_lead_time_ms ?? null;
|
|
130
268
|
const e1 = efficiency.e1_scenario_delivery_efficiency_ms ?? null;
|
|
131
269
|
const e2 = efficiency.e2_coding_time_ratio_including_rework
|
|
132
270
|
?? efficiency.e2_coding_time_ratio
|
|
@@ -134,54 +272,99 @@ function buildChangeReportViewModel(report = {}) {
|
|
|
134
272
|
const e3 = efficiency.e3_spec_time_ratio_including_rework
|
|
135
273
|
?? efficiency.e3_spec_time_ratio
|
|
136
274
|
?? null;
|
|
275
|
+
const e1State = metricStateForDetail(
|
|
276
|
+
report.metric_details?.e1,
|
|
277
|
+
(
|
|
278
|
+
minSet.evidence_status?.e1 === 'trusted'
|
|
279
|
+
|| minSet.trusted_metrics?.includes?.('e1')
|
|
280
|
+
|| minSet.trusted?.includes?.('e1')
|
|
281
|
+
) ? '可信' : '需补证',
|
|
282
|
+
minSet.evidence_status?.e1 || null,
|
|
283
|
+
);
|
|
284
|
+
const q3State = metricStateForDetail(
|
|
285
|
+
report.metric_details?.q3,
|
|
286
|
+
quality.q3_score_status === 'verified' ? '已独立验证' : '尚未独立验证',
|
|
287
|
+
quality.q3_score_status || null,
|
|
288
|
+
'尚未独立验证',
|
|
289
|
+
);
|
|
290
|
+
const p4State = metricStateForDetail(
|
|
291
|
+
report.metric_details?.p4,
|
|
292
|
+
Number.isFinite(process.p4_quality_gate_enforcement_rate) ? '可计算' : '需补证',
|
|
293
|
+
Number.isFinite(process.p4_quality_gate_enforcement_rate) ? 'available' : 'missing',
|
|
294
|
+
);
|
|
295
|
+
const pRState = metricStateForDetail(
|
|
296
|
+
report.metric_details?.p_r,
|
|
297
|
+
process.rework_summary ? '可计算' : '需补证',
|
|
298
|
+
process.rework_summary ? 'available' : 'missing',
|
|
299
|
+
);
|
|
300
|
+
const pHState = metricStateForDetail(
|
|
301
|
+
report.metric_details?.p_h,
|
|
302
|
+
telemetry.p_h_telemetry_health_score == null ? '需补证' : '可计算',
|
|
303
|
+
telemetry.p_h_telemetry_health_score == null ? 'missing' : 'available',
|
|
304
|
+
);
|
|
137
305
|
const metricCards = [
|
|
138
306
|
{
|
|
139
|
-
name: '
|
|
307
|
+
name: '场景交付效率(每个场景平均用时)',
|
|
140
308
|
code: 'E1',
|
|
141
|
-
value:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
) ? '可信' : '需补证',
|
|
309
|
+
value: validateCoreMetricResult('e1', e1).valid
|
|
310
|
+
? `${durationText(e1)} / 场景`
|
|
311
|
+
: '暂无数据',
|
|
312
|
+
state: e1State.label,
|
|
313
|
+
state_code: e1State.code,
|
|
147
314
|
explanation: '七个标准阶段总耗时(含返工)÷ 验收场景数。',
|
|
148
315
|
source: `公式:标准阶段总耗时 ÷ 场景数。场景数:${efficiency.scenario_count ?? '未知'};当前值:${e1 ?? '不可计算'} ms。`,
|
|
316
|
+
detail: report.metric_details?.e1 || null,
|
|
149
317
|
},
|
|
150
318
|
{
|
|
151
|
-
name: '
|
|
319
|
+
name: '规约质量评分(规约检查得分)',
|
|
152
320
|
code: 'Q3',
|
|
153
|
-
value:
|
|
154
|
-
|
|
321
|
+
value: validateCoreMetricResult('q3', quality.q3_spec_quality_score).valid
|
|
322
|
+
? `${quality.q3_spec_quality_score} / 100`
|
|
323
|
+
: '暂无数据',
|
|
324
|
+
state: q3State.label,
|
|
325
|
+
state_code: q3State.code,
|
|
155
326
|
explanation: '最新规约检查结果;独立复核后才标记为已验证。',
|
|
156
|
-
source: `评审独立性:${quality.q3_reviewer_independence
|
|
327
|
+
source: `评审独立性:${reviewIndependenceText(quality.q3_reviewer_independence)};证据状态:${localizeMetricStatus(quality.q3_score_status)}。`,
|
|
328
|
+
detail: report.metric_details?.q3 || null,
|
|
157
329
|
},
|
|
158
330
|
{
|
|
159
|
-
name: '
|
|
331
|
+
name: '质量门禁执行率(编码前是否先检查)',
|
|
160
332
|
code: 'P4',
|
|
161
|
-
value:
|
|
162
|
-
|
|
333
|
+
value: validateCoreMetricResult('p4', process.p4_quality_gate_enforcement_rate).valid
|
|
334
|
+
? percentText(process.p4_quality_gate_enforcement_rate)
|
|
335
|
+
: '暂无数据',
|
|
336
|
+
state: p4State.label,
|
|
337
|
+
state_code: p4State.code,
|
|
163
338
|
explanation: '首次进入编码前,是否存在已配对且成功或部分通过的检查。',
|
|
164
339
|
source: '只接受能与 check 开始事件配对、且发生在首次 apply 之前的成功或部分通过结果。',
|
|
340
|
+
detail: report.metric_details?.p4 || null,
|
|
165
341
|
},
|
|
166
342
|
{
|
|
167
|
-
name: '
|
|
343
|
+
name: '阶段重复执行(阶段重复次数)',
|
|
168
344
|
code: 'P-R',
|
|
169
|
-
value:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
345
|
+
value: validateCoreMetricResult(
|
|
346
|
+
'p_r',
|
|
347
|
+
process.rework_summary?.total_rework_attempts,
|
|
348
|
+
).valid
|
|
349
|
+
? `${process.rework_summary.total_rework_attempts} 次`
|
|
350
|
+
: '暂无数据',
|
|
351
|
+
state: pRState.label,
|
|
352
|
+
state_code: pRState.code,
|
|
173
353
|
explanation: '七个标准阶段中,首次执行之后的重复尝试次数。',
|
|
174
354
|
source: '辅助 test / explore 阶段不计入标准阶段重复总数。',
|
|
355
|
+
detail: report.metric_details?.p_r || null,
|
|
175
356
|
},
|
|
176
357
|
{
|
|
177
|
-
name: '
|
|
358
|
+
name: '数据质量分(执行记录是否完整)',
|
|
178
359
|
code: 'P-H',
|
|
179
|
-
value: telemetry.p_h_telemetry_health_score
|
|
180
|
-
?
|
|
181
|
-
:
|
|
182
|
-
state:
|
|
360
|
+
value: validateCoreMetricResult('p_h', telemetry.p_h_telemetry_health_score).valid
|
|
361
|
+
? `${telemetry.p_h_telemetry_health_score} / 100`
|
|
362
|
+
: '暂无数据',
|
|
363
|
+
state: pHState.label,
|
|
364
|
+
state_code: pHState.code,
|
|
183
365
|
explanation: '标准阶段的身份、开始结束配对和数据质量综合评分。',
|
|
184
366
|
source: '沿用既有权重,但只以 propose、spec、design、task、check、apply、archive 七阶段为计算范围。',
|
|
367
|
+
detail: report.metric_details?.p_h || null,
|
|
185
368
|
},
|
|
186
369
|
];
|
|
187
370
|
|
|
@@ -242,7 +425,8 @@ function buildChangeReportViewModel(report = {}) {
|
|
|
242
425
|
},
|
|
243
426
|
metric_cards: metricCards,
|
|
244
427
|
metric_rows: [
|
|
245
|
-
{ label: '
|
|
428
|
+
{ label: '标准阶段有效耗时(含返工)', value: durationText(totalDurationMs) },
|
|
429
|
+
{ label: '端到端经过时间(含阶段间空隙)', value: durationText(endpointElapsedMs) },
|
|
246
430
|
{ label: '每个场景平均用时', code: 'E1', value: metricCards[0].value },
|
|
247
431
|
{ label: '编码时间占比', code: 'E2', value: percentText(e2) },
|
|
248
432
|
{ label: '规划与规约时间占比', code: 'E3', value: percentText(e3) },
|
|
@@ -250,12 +434,12 @@ function buildChangeReportViewModel(report = {}) {
|
|
|
250
434
|
],
|
|
251
435
|
stages: (report.stage_timeline?.nodes || []).map((node) => ({
|
|
252
436
|
...node,
|
|
253
|
-
display_stage: node.stage || node.command
|
|
437
|
+
display_stage: localizeStageName(node.stage || node.command),
|
|
254
438
|
display_round: node.round || 1,
|
|
255
439
|
display_start: localTimeText(node.start_ts),
|
|
256
440
|
display_end: localTimeText(node.end_ts),
|
|
257
441
|
display_duration: durationText(node.duration_ms),
|
|
258
|
-
display_result: node.result
|
|
442
|
+
display_result: resultText(node.result),
|
|
259
443
|
})),
|
|
260
444
|
tdd_pairs: {
|
|
261
445
|
required: tdd.required ?? null,
|
|
@@ -269,11 +453,30 @@ function buildChangeReportViewModel(report = {}) {
|
|
|
269
453
|
summary: evidence.note
|
|
270
454
|
|| `${evidence.category_count || 0} 类问题,${evidence.occurrence_count || 0} 次规则命中`,
|
|
271
455
|
categories: evidence.categories || [],
|
|
272
|
-
|
|
456
|
+
collection_method: '系统先读取规则检查结果,再核对任务状态、测试结果和阶段事件,把同一问题的出现、处置和解决记录合并后展示。',
|
|
457
|
+
functional_result: {
|
|
458
|
+
summary: summary.tests?.status === 'passed'
|
|
459
|
+
? `最终功能测试通过:${summary.tests.passed}/${summary.tests.total}。`
|
|
460
|
+
: '最终功能测试没有形成可确认的全通过结果。',
|
|
461
|
+
explanation: '功能测试回答“功能是否正确”,不直接证明任务、测试和事件之间的记录是否完整。',
|
|
462
|
+
},
|
|
463
|
+
traceability_result: {
|
|
464
|
+
summary: openIssueCount === 0
|
|
465
|
+
? '证据链没有待处理问题。'
|
|
466
|
+
: `证据链仍有 ${openIssueCount} 个待处理问题。`,
|
|
467
|
+
explanation: openIssueCount === 0
|
|
468
|
+
? '历史问题仍按“已解决 / 已接受 / 不重要”保留。'
|
|
469
|
+
: '功能测试与 Check 可以通过,但记录和关联仍可能有缺口;这不等同于功能失败。',
|
|
470
|
+
},
|
|
471
|
+
issues: localizedIssues,
|
|
273
472
|
open_alerts: report.evidence_alerts?.open || [],
|
|
274
473
|
task_evidence_audit: audit,
|
|
275
474
|
apply_findings: report.apply_evidence_audit?.findings || [],
|
|
276
|
-
warning_dispositions: report.warning_dispositions || []
|
|
475
|
+
warning_dispositions: (report.warning_dispositions || []).map(item => ({
|
|
476
|
+
...item,
|
|
477
|
+
disposition_label: localizeIssueStatus(item.disposition),
|
|
478
|
+
})),
|
|
479
|
+
process_notes: localizeProcessNotes(report.process_notes || null),
|
|
277
480
|
},
|
|
278
481
|
'artifacts-trace': {
|
|
279
482
|
id: 'artifacts-trace',
|
|
@@ -286,10 +489,43 @@ function buildChangeReportViewModel(report = {}) {
|
|
|
286
489
|
deleted_lines: changed.deleted_lines ?? null,
|
|
287
490
|
diff_source: changed.diff_source || null,
|
|
288
491
|
reconciliation_status: changed.reconciliation_status ?? null,
|
|
492
|
+
reconciliation_status_label: RECONCILIATION_STATUS_LABELS[
|
|
493
|
+
changed.reconciliation_status
|
|
494
|
+
] || String(changed.reconciliation_status || '未记录'),
|
|
289
495
|
reconciliation_note: changed.reconciliation_note || null,
|
|
290
496
|
final_output_files: changed.final_output_files || changed.files || null,
|
|
497
|
+
task_event_files: changed.task_event_files || null,
|
|
291
498
|
only_in_task_events: changed.only_in_task_events || [],
|
|
292
499
|
only_in_final_output: changed.only_in_final_output || [],
|
|
500
|
+
task_scope: {
|
|
501
|
+
label: '任务过程中涉及的文件',
|
|
502
|
+
explanation: '汇总各条任务完成事件中记录的文件,回答“任务过程中动过哪些文件”。同一文件只计一次。',
|
|
503
|
+
count: changed.task_event_files_changed ?? null,
|
|
504
|
+
files: changed.task_event_files || null,
|
|
505
|
+
source: fileSourceText(changed.task_event_source || 'task_update.files'),
|
|
506
|
+
task_ids: changed.task_event_task_ids || [],
|
|
507
|
+
event_ids: changed.task_event_ids || [],
|
|
508
|
+
latest_at: changed.task_event_latest_at || null,
|
|
509
|
+
no_file_change_reasons: changed.no_file_change_reasons || [],
|
|
510
|
+
},
|
|
511
|
+
final_scope: {
|
|
512
|
+
label: '最终实际交付的文件',
|
|
513
|
+
explanation: '读取最后一次输出快照,回答“归档时最终实际交付了哪些文件”。它不与任务过程口径混为一谈。',
|
|
514
|
+
count: changed.final_output_files_changed ?? null,
|
|
515
|
+
files: changed.final_output_files || changed.files || null,
|
|
516
|
+
source: fileSourceText(
|
|
517
|
+
changed.final_output_source || changed.diff_source || null,
|
|
518
|
+
),
|
|
519
|
+
event_id: changed.final_output_event_id || null,
|
|
520
|
+
captured_at: changed.final_output_captured_at || null,
|
|
521
|
+
no_output_reason: changed.final_output_no_output_reason || null,
|
|
522
|
+
vcs_mode: changed.final_output_vcs_mode || null,
|
|
523
|
+
line_statistics_explanation: changed.final_output_vcs_mode === 'no-git'
|
|
524
|
+
? '当前环境没有 Git 差异记录,因此无法统计新增和删除行数;文件清单仍来自最终输出快照。'
|
|
525
|
+
: ((changed.added_lines == null || changed.deleted_lines == null)
|
|
526
|
+
? '最终输出快照没有完整的行数差异,因此新增和删除行数不可用。'
|
|
527
|
+
: '新增和删除行数来自最终输出快照中的 Git 差异统计。'),
|
|
528
|
+
},
|
|
293
529
|
},
|
|
294
530
|
artifacts: Object.entries(artifacts).map(([name, item]) => ({
|
|
295
531
|
name,
|
|
@@ -300,13 +536,6 @@ function buildChangeReportViewModel(report = {}) {
|
|
|
300
536
|
sha256: item?.sha256 ?? null,
|
|
301
537
|
integrity_note: item?.integrity_note || null,
|
|
302
538
|
})),
|
|
303
|
-
attribution: report.attribution_audit || null,
|
|
304
|
-
attribution_summary: {
|
|
305
|
-
linked_source_event_count: report.attribution_audit?.linked_source_event_count ?? 0,
|
|
306
|
-
active_link_count: report.attribution_audit?.active_links?.length ?? 0,
|
|
307
|
-
unassigned_candidate_count: report.attribution_audit?.unassigned_candidate_event_ids?.length ?? 0,
|
|
308
|
-
rejected_collision_count: report.attribution_audit?.rejected_direct_collision_event_ids?.length ?? 0,
|
|
309
|
-
},
|
|
310
539
|
},
|
|
311
540
|
};
|
|
312
541
|
|
|
@@ -319,7 +548,7 @@ function buildChangeReportViewModel(report = {}) {
|
|
|
319
548
|
minimum_metric_set: report.minimum_metric_set || null,
|
|
320
549
|
scheme_compliance: report.scheme_compliance || null,
|
|
321
550
|
known_risks: report.known_risks || null,
|
|
322
|
-
process_notes: report.process_notes || null,
|
|
551
|
+
process_notes: localizeProcessNotes(report.process_notes || null),
|
|
323
552
|
telemetry_warnings: report.telemetry_warnings || null,
|
|
324
553
|
archive_result: report.archive_result || null,
|
|
325
554
|
report_scope: report.report_scope || null,
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 核心指标的单一文字口径。
|
|
5
|
+
* JSON 事实模型、Markdown 与 HTML 必须复用这里的定义,避免同一指标出现多套解释。
|
|
6
|
+
*/
|
|
7
|
+
const CORE_METRIC_DEFINITIONS = Object.freeze({
|
|
8
|
+
e1: Object.freeze({
|
|
9
|
+
code: 'E1',
|
|
10
|
+
name: '场景交付效率',
|
|
11
|
+
english_name: 'Scenario Delivery Efficiency (SDE)',
|
|
12
|
+
definition: '每交付一个验收场景所需的平均有效耗时。',
|
|
13
|
+
purpose: '衡量在包含正常返工的情况下,交付一个可验证业务场景平均需要投入多少阶段执行时间。',
|
|
14
|
+
formula: '场景交付效率 = 标准阶段总耗时(含返工) ÷ 验收场景总数',
|
|
15
|
+
source_description: '场景数来自 spec.md 中“##### 场景”标题;耗时来自 skywalk-sdd 记录的标准阶段开始和结束事件。',
|
|
16
|
+
scheme_reference: '《3.3.4.2 SDD赋能提效评估与追踪方案》[E1]',
|
|
17
|
+
scheme_notes: [
|
|
18
|
+
'总耗时等于各标准阶段最终执行耗时加返工执行耗时,不含阶段之间的等待空隙。',
|
|
19
|
+
'标准阶段是 propose、spec、design、task、check、apply、archive;test 和 explore 只作辅助证据,不进入 E1。',
|
|
20
|
+
'验收场景数与阶段执行记录数是两种不同数据,不能互相替代。',
|
|
21
|
+
],
|
|
22
|
+
}),
|
|
23
|
+
q3: Object.freeze({
|
|
24
|
+
code: 'Q3',
|
|
25
|
+
name: '规约质量评分',
|
|
26
|
+
english_name: 'Spec Quality Score (SQS)',
|
|
27
|
+
definition: 'Check 阶段检查项的通过率,反映 spec、design、task 文档质量。',
|
|
28
|
+
purpose: '直接衡量规约文档质量,并与只判断“是否先做了 Check”的 P4 区分。',
|
|
29
|
+
formula: '规约质量评分 = (检查项总数 - 错误数) ÷ 检查项总数 × 100',
|
|
30
|
+
source_description: '来自 skywalk-sdd 在 Check 阶段记录的检查项总数、错误数、分类明细和评审执行信息。',
|
|
31
|
+
scheme_reference: '《3.3.4.2 SDD赋能提效评估与追踪方案》[Q3]',
|
|
32
|
+
scheme_notes: [
|
|
33
|
+
'警告和建议单独展示,当前不进入扣分公式。',
|
|
34
|
+
'只有真实独立子代理、新旧会话不同且记录了评审者身份时,结果才标记为“已独立验证”。',
|
|
35
|
+
'完整性、一致性、可执行性等分类用于下钻说明,不改变总分公式。',
|
|
36
|
+
],
|
|
37
|
+
}),
|
|
38
|
+
p4: Object.freeze({
|
|
39
|
+
code: 'P4',
|
|
40
|
+
name: '质量门禁执行率',
|
|
41
|
+
english_name: 'Quality Gate Enforcement Rate',
|
|
42
|
+
definition: '首次 Apply 之前是否执行了有效 Check。',
|
|
43
|
+
purpose: '回答“实现前有没有先做质量检查”,不评价检查内容本身的质量。',
|
|
44
|
+
formula: '首次 Apply 前存在已配对且结果为成功或部分通过的 Check = 1(100%);否则 = 0(0%)',
|
|
45
|
+
source_description: '来自 skywalk-sdd 的 Check 开始/结束事件、Check 结果和首次 Apply 开始时间的时序比较。',
|
|
46
|
+
scheme_reference: '《3.3.4.2 SDD赋能提效评估与追踪方案》[P4]',
|
|
47
|
+
scheme_notes: [
|
|
48
|
+
'单次变更只有 100% 或 0% 两种结果;周期报告才对多次变更求执行率。',
|
|
49
|
+
'P4 衡量“有没有做 Check”,Q3 衡量“Check 结果如何”。',
|
|
50
|
+
],
|
|
51
|
+
}),
|
|
52
|
+
p_r: Object.freeze({
|
|
53
|
+
code: 'P-R',
|
|
54
|
+
name: '阶段重复执行',
|
|
55
|
+
english_name: 'Stage Rework',
|
|
56
|
+
definition: '同一标准阶段被执行多次时,除第一次执行外的重复尝试次数及原因。',
|
|
57
|
+
purpose: '说明哪些阶段发生了重复执行、重复了几次,以及系统根据事件推导出的原因。',
|
|
58
|
+
formula: '单次变更重复次数 = Σ max(阶段执行次数 - 1, 0)',
|
|
59
|
+
source_description: '来自 skywalk-sdd 对各标准阶段开始和结束事件的配对、排序和重复轮次识别。',
|
|
60
|
+
scheme_reference: '《3.3.4.2 SDD赋能提效评估与追踪方案》[P-R]',
|
|
61
|
+
scheme_notes: [
|
|
62
|
+
'单次报告展示实际重复次数;周期报告中的平均返工次数是另一种统计口径。',
|
|
63
|
+
'原因由事件状态、Git 记录或遥测活动自动推导,不要求开发者手工标注。',
|
|
64
|
+
],
|
|
65
|
+
}),
|
|
66
|
+
p_h: Object.freeze({
|
|
67
|
+
code: 'P-H',
|
|
68
|
+
name: '数据质量分',
|
|
69
|
+
english_name: 'Telemetry Health Score',
|
|
70
|
+
definition: '标准阶段遥测数据完整性的综合评分(0-100%)。',
|
|
71
|
+
purpose: '判断用于计算核心指标的数据链是否完整、可信,避免把采集缺口误当成功能问题。',
|
|
72
|
+
formula: '数据质量分 = (1 - 加权问题值 ÷ 标准阶段事件总数) × 100',
|
|
73
|
+
source_description: '由 skywalk-sdd 诊断标准阶段事件后,按问题数量和内置影响权重自动计算。',
|
|
74
|
+
scheme_reference: '《3.3.4.2 SDD赋能提效评估与追踪方案》[P-H]',
|
|
75
|
+
scheme_notes: [
|
|
76
|
+
'只使用 propose、spec、design、task、check、apply、archive 七个标准阶段。',
|
|
77
|
+
'未闭环、孤儿事件等问题按其对指标计算的影响程度加权;权重是项目经验值,不是行业标准。',
|
|
78
|
+
],
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const CORE_METRIC_RESULT_RULES = Object.freeze({
|
|
83
|
+
e1: Object.freeze({
|
|
84
|
+
description: '大于等于 0 的有限数值',
|
|
85
|
+
validate: value => value >= 0,
|
|
86
|
+
}),
|
|
87
|
+
q3: Object.freeze({
|
|
88
|
+
description: '0 到 100 之间的分数',
|
|
89
|
+
validate: value => value >= 0 && value <= 100,
|
|
90
|
+
}),
|
|
91
|
+
p4: Object.freeze({
|
|
92
|
+
description: '0 或 1',
|
|
93
|
+
validate: value => value === 0 || value === 1,
|
|
94
|
+
}),
|
|
95
|
+
p_r: Object.freeze({
|
|
96
|
+
description: '大于等于 0 的整数',
|
|
97
|
+
validate: value => Number.isInteger(value) && value >= 0,
|
|
98
|
+
}),
|
|
99
|
+
p_h: Object.freeze({
|
|
100
|
+
description: '0 到 100 之间的分数',
|
|
101
|
+
validate: value => value >= 0 && value <= 100,
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
function validateCoreMetricResult(metricKey, value) {
|
|
106
|
+
const hasNumber = (typeof value === 'number' && Number.isFinite(value))
|
|
107
|
+
|| (typeof value === 'string' && value.trim() && Number.isFinite(Number(value)));
|
|
108
|
+
if (!hasNumber) {
|
|
109
|
+
return {
|
|
110
|
+
valid: false,
|
|
111
|
+
status: 'missing',
|
|
112
|
+
numeric_value: null,
|
|
113
|
+
explanation: '报告结果缺失或不是有效数字。',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const numericValue = Number(value);
|
|
117
|
+
const rule = CORE_METRIC_RESULT_RULES[metricKey];
|
|
118
|
+
if (!rule || rule.validate(numericValue)) {
|
|
119
|
+
return {
|
|
120
|
+
valid: true,
|
|
121
|
+
status: 'valid',
|
|
122
|
+
numeric_value: numericValue,
|
|
123
|
+
explanation: null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
valid: false,
|
|
128
|
+
status: 'invalid',
|
|
129
|
+
numeric_value: numericValue,
|
|
130
|
+
explanation: `报告结果 ${value} 不符合“${rule.description}”的取值要求。`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const METRIC_STATUS_LABELS = Object.freeze({
|
|
135
|
+
trusted: '可信',
|
|
136
|
+
verified: '已独立验证',
|
|
137
|
+
provisional: '已有计算结果,尚未独立验证',
|
|
138
|
+
missing: '当前缺少数据',
|
|
139
|
+
unavailable: '当前无法计算',
|
|
140
|
+
available: '可以计算',
|
|
141
|
+
inconsistent: '计算明细与结果不一致',
|
|
142
|
+
'pending-upstream': '等待上游数据',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const STAGE_LABELS = Object.freeze({
|
|
146
|
+
propose: '提出变更',
|
|
147
|
+
spec: '编写规约',
|
|
148
|
+
design: '设计方案',
|
|
149
|
+
task: '拆分任务',
|
|
150
|
+
check: '规约检查',
|
|
151
|
+
apply: '实施变更',
|
|
152
|
+
archive: '归档',
|
|
153
|
+
test: '测试(辅助阶段)',
|
|
154
|
+
explore: '探索(辅助阶段)',
|
|
155
|
+
'pre-archive': '归档前复检',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const ISSUE_STATUS_LABELS = Object.freeze({
|
|
159
|
+
open: '待处理',
|
|
160
|
+
resolved: '已解决',
|
|
161
|
+
fixed: '已解决',
|
|
162
|
+
accepted: '已接受',
|
|
163
|
+
waived: '已豁免',
|
|
164
|
+
informational: '仅供参考',
|
|
165
|
+
ignored: '不重要,已标记',
|
|
166
|
+
needs_input: '等待补充信息',
|
|
167
|
+
'not-applicable': '不适用',
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
function localizeMetricStatus(value) {
|
|
171
|
+
return METRIC_STATUS_LABELS[value] || String(value || '未记录');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function localizeStageName(value) {
|
|
175
|
+
return STAGE_LABELS[value] || String(value || '未记录阶段');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function localizeIssueStatus(value) {
|
|
179
|
+
return ISSUE_STATUS_LABELS[value] || String(value || '未记录状态');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = {
|
|
183
|
+
CORE_METRIC_DEFINITIONS,
|
|
184
|
+
CORE_METRIC_RESULT_RULES,
|
|
185
|
+
METRIC_STATUS_LABELS,
|
|
186
|
+
STAGE_LABELS,
|
|
187
|
+
ISSUE_STATUS_LABELS,
|
|
188
|
+
localizeMetricStatus,
|
|
189
|
+
localizeStageName,
|
|
190
|
+
localizeIssueStatus,
|
|
191
|
+
validateCoreMetricResult,
|
|
192
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* spec-root.cjs — 输出 SDD spec 包裹包的绝对路径。
|
|
6
|
+
*
|
|
7
|
+
* 设计目标:替代 SKILL 模板中脆弱的 $(cat .sdd-spec-root) 模式。
|
|
8
|
+
*
|
|
9
|
+
* 用法:
|
|
10
|
+
* node sdd-sdd-specs/skywalk-sdd/spec-root.cjs
|
|
11
|
+
* # 输出: C:\Users\zcc\Desktop\sdd\sdd-sdd-specs
|
|
12
|
+
*
|
|
13
|
+
* SPEC=$(node sdd-sdd-specs/skywalk-sdd/spec-root.cjs)
|
|
14
|
+
* node "$SPEC/skywalk-sdd/log.cjs" start --command=propose ...
|
|
15
|
+
*
|
|
16
|
+
* 核心特性:
|
|
17
|
+
* - 基于 __dirname(脚本自身位置)向上遍历查找 .sdd-spec-root
|
|
18
|
+
* - 不依赖 CWD,从任意子目录运行均可正确解析
|
|
19
|
+
* - 兼容 Windows PowerShell / macOS / Linux
|
|
20
|
+
* - 复用 lib/shared.cjs 的 resolveSpecRootFast(统一逻辑)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const { resolveSpecRootFast } = require('./lib/shared.cjs');
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const specRoot = resolveSpecRootFast(__dirname);
|
|
27
|
+
console.log(specRoot);
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error(`⚠️ ${e.message}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|