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.
Files changed (70) hide show
  1. package/bin/kld-sdd-init.js +10 -0
  2. package/kld-sdd-guide.html +22 -0
  3. package/lib/device-auth-cli.js +130 -0
  4. package/lib/device-auth.js +345 -0
  5. package/lib/init-account-binding.js +108 -0
  6. package/lib/init.js +238 -55
  7. package/lib/skills-bundle.js +20 -1
  8. package/lib/tool-profiles.js +8 -0
  9. package/package.json +7 -3
  10. package/skywalk-sdd/apply-worktree-finish.cjs +2 -23
  11. package/skywalk-sdd/context-client.cjs +38 -87
  12. package/skywalk-sdd/index.cjs +860 -132
  13. package/skywalk-sdd/kb-sync-identity.cjs +780 -0
  14. package/skywalk-sdd/kb-upload.cjs +505 -0
  15. package/skywalk-sdd/lib/shared.cjs +811 -0
  16. package/skywalk-sdd/lib/usage-contract.cjs +276 -0
  17. package/skywalk-sdd/lib/usage-reporter.cjs +354 -0
  18. package/skywalk-sdd/lib/user-config.cjs +157 -0
  19. package/skywalk-sdd/metrics-v3.cjs +138 -8
  20. package/skywalk-sdd/ontology/archive-package.cjs +19 -34
  21. package/skywalk-sdd/ontology/change-lock.cjs +3 -7
  22. package/skywalk-sdd/ontology/external-key.cjs +18 -4
  23. package/skywalk-sdd/ontology/id.cjs +26 -5
  24. package/skywalk-sdd/ontology/identity-index.cjs +3 -7
  25. package/skywalk-sdd/ontology/resolve-spec-root.cjs +20 -6
  26. package/skywalk-sdd/ontology/runtime.cjs +16 -12
  27. package/skywalk-sdd/ontology/traceability-validator.cjs +7 -4
  28. package/skywalk-sdd/reporting/change-report-markdown.cjs +137 -19
  29. package/skywalk-sdd/reporting/change-report-model.cjs +993 -14
  30. package/skywalk-sdd/reporting/change-report-renderer.cjs +106 -41
  31. package/skywalk-sdd/reporting/change-report-view-model.cjs +272 -43
  32. package/skywalk-sdd/reporting/core-metric-definitions.cjs +192 -0
  33. package/skywalk-sdd/spec-root.cjs +31 -0
  34. package/templates/git-hooks/pre-commit-consistency-check.cjs +271 -116
  35. package/templates/git-hooks/pre-push-consistency-check.cjs +252 -123
  36. package/templates/hooks/codebuddy/hooks/hook-gate-core.cjs +327 -0
  37. package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +54 -9
  38. package/templates/hooks/codebuddy/hooks/sdd-mid-checkpoint.cjs +63 -6
  39. package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +113 -65
  40. package/templates/openspec/proposal.md +7 -3
  41. package/templates/openspec/spec.md +3 -3
  42. package/templates/skills/kld-sdd/opsx-apply/SKILL.md +8 -6
  43. package/templates/skills/kld-sdd/opsx-apply/checklist.md +2 -0
  44. package/templates/skills/kld-sdd/opsx-apply/reference.md +29 -7
  45. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +6 -5
  46. package/templates/skills/kld-sdd/opsx-check/SKILL.md +49 -17
  47. package/templates/skills/kld-sdd/opsx-check/checklist.md +4 -2
  48. package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +187 -257
  49. package/templates/skills/kld-sdd/opsx-consistency-check/reference.md +129 -0
  50. package/templates/skills/kld-sdd/opsx-design/SKILL.md +2 -2
  51. package/templates/skills/kld-sdd/opsx-explore/SKILL.md +2 -2
  52. package/templates/skills/kld-sdd/opsx-kb-config/SKILL.md +185 -0
  53. package/templates/skills/kld-sdd/opsx-kb-config/reference.md +127 -0
  54. package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +218 -53
  55. package/templates/skills/kld-sdd/opsx-kb-ingest/reference.md +51 -9
  56. package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +12 -50
  57. package/templates/skills/kld-sdd/opsx-ontology-query/phase-3-postchange.md +2 -2
  58. package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +1 -1
  59. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +35 -23
  60. package/templates/skills/kld-sdd/opsx-propose/checklist.md +2 -0
  61. package/templates/skills/kld-sdd/opsx-propose/reference.md +22 -17
  62. package/templates/skills/kld-sdd/opsx-rules/SKILL.md +2 -2
  63. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +19 -15
  64. package/templates/skills/kld-sdd/opsx-spec/checklist.md +2 -0
  65. package/templates/skills/kld-sdd/opsx-task/SKILL.md +2 -4
  66. package/templates/skills/kld-sdd/opsx-test/SKILL.md +2 -2
  67. package/templates/skills/kld-sdd/tdd-core/reference.md +1 -1
  68. package/templates/skills/kld-sdd/tdd-rules/rules/test-skeleton-telemetry.md +1 -1
  69. package/templates/skills/kld-sdd/opsx-kb-ingest/state.example.json +0 -7
  70. 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 crypto.createHash('sha256').update(value).digest('hex');
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
- function atomicWriteIfChanged(filePath, value) {
66
+ // atomicWriteIfChanged 已迁移至 lib/shared.cjs
67
+ const atomicWriteIfChanged = (filePath, value) => {
60
68
  const content = typeof value === 'string' ? value : serialized(value);
61
- if (fs.existsSync(filePath) && fs.readFileSync(filePath, 'utf8') === content) {
62
- return false;
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
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
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
- if (!Array.isArray(identityHistory)) return;
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: '编号不合规回需求系统换发;场景键回 opsx-spec 修正',
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
- `场景键 REQ 前缀不在 frontmatter requirement-refs 申报集合: ${normalizedScenario}`,
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, '&lt;')
14
+ .replace(/>/g, '&gt;')
15
+ .replace(/"/g, '&quot;')
16
+ .replace(/'/g, '&#39;')
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.metrics?.process?.rework_summary?.reasons;
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
- if (reasons?.by_category) {
62
- const parts = Object.entries(reasons.by_category)
63
- .map(([k, v]) => `${k}=${v}`)
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.status} · 判定:${issue.verdict}`,
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.disposition || 'open'}`);
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 = report.process_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(`- 按 kind 分布:${JSON.stringify(notes.by_kind)}`);
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.reconciliation_status)}`,
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
- const view = buildChangeReportViewModel(report);
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
- ...(projectLabel ? [`- 项目路径:${projectLabel}`] : []),
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} -->`,