kld-sdd 2.6.13 → 2.6.15

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 (30) hide show
  1. package/README.md +16 -12
  2. package/kld-sdd-guide.html +4 -5
  3. package/lib/init.js +14 -11
  4. package/package.json +2 -2
  5. package/skywalk-sdd/index.cjs +936 -109
  6. package/skywalk-sdd/metrics-v3.cjs +103 -15
  7. package/skywalk-sdd/ontology/archive-package.cjs +6 -0
  8. package/skywalk-sdd/ontology/identity-index.cjs +9 -2
  9. package/skywalk-sdd/ontology/ontology-paths.cjs +73 -0
  10. package/skywalk-sdd/ontology/runtime.cjs +32 -22
  11. package/skywalk-sdd/ontology/structural-identity.cjs +11 -2
  12. package/skywalk-sdd/ontology/traceability-validator.cjs +16 -8
  13. package/skywalk-sdd/ontology/working-artifacts.cjs +2 -1
  14. package/skywalk-sdd/reporting/change-report-markdown.cjs +294 -0
  15. package/skywalk-sdd/reporting/change-report-model.cjs +452 -0
  16. package/skywalk-sdd/reporting/change-report-renderer.cjs +349 -0
  17. package/skywalk-sdd/reporting/change-report-view-model.cjs +340 -0
  18. package/skywalk-sdd/runtime-metadata.cjs +21 -0
  19. package/templates/skills/kld-sdd/opsx-apply/reference.md +4 -2
  20. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +1 -1
  21. package/templates/skills/kld-sdd/opsx-archive/checklist.md +1 -1
  22. package/templates/skills/kld-sdd/opsx-check/SKILL.md +2 -2
  23. package/templates/skills/kld-sdd/opsx-check/checklist.md +1 -1
  24. package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +14 -0
  25. package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +31 -0
  26. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +6 -6
  27. package/templates/skills/kld-sdd/opsx-propose/reference.md +6 -7
  28. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +1 -1
  29. package/templates/skills/kld-sdd/tdd-core/reference.md +3 -1
  30. package/templates/skills/kld-sdd/tdd-rules/rules/test-skeleton-telemetry.md +1 -1
@@ -0,0 +1,340 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ /**
6
+ * Shared change-report view model for Markdown / HTML / parity digests.
7
+ * Four desktop tabs: 概览 / 指标与阶段 / 问题与证据 / 产物与追溯
8
+ */
9
+
10
+ const SECTION_ORDER = [
11
+ 'overview',
12
+ 'metrics-stages',
13
+ 'issues-evidence',
14
+ 'artifacts-trace',
15
+ ];
16
+
17
+ const SECTION_TITLES = {
18
+ overview: '概览',
19
+ 'metrics-stages': '指标与阶段',
20
+ 'issues-evidence': '问题与证据',
21
+ 'artifacts-trace': '产物与追溯',
22
+ };
23
+
24
+ function fractionText(verified, total) {
25
+ if (verified == null || total == null) return '暂无数据';
26
+ return `${verified}/${total}`;
27
+ }
28
+
29
+ function percentText(value, digits = 2) {
30
+ return Number.isFinite(Number(value))
31
+ ? `${(Number(value) * 100).toFixed(digits)}%`
32
+ : '暂无数据';
33
+ }
34
+
35
+ function durationText(value) {
36
+ if (!Number.isFinite(Number(value))) return '暂无数据';
37
+ const totalSeconds = Math.round(Number(value) / 1000);
38
+ const hours = Math.floor(totalSeconds / 3600);
39
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
40
+ const seconds = totalSeconds % 60;
41
+ return [hours ? `${hours}小时` : '', minutes ? `${minutes}分` : '', `${seconds}秒`]
42
+ .filter(Boolean)
43
+ .join('');
44
+ }
45
+
46
+ function localTimeText(value) {
47
+ if (!value) return '—';
48
+ const parsed = new Date(value);
49
+ return Number.isNaN(parsed.getTime())
50
+ ? '—'
51
+ : parsed.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
52
+ }
53
+
54
+ function buildSemanticDigestInput(report, sections) {
55
+ const audit = report.task_evidence_audit || {};
56
+ const tdd = report.tdd_pair_audit || {};
57
+ const changed = report.changed_files || {};
58
+ const evidence = report.change_summary?.evidence || {};
59
+ const artifacts = report.artifacts?.files || {};
60
+ return {
61
+ schema_version: report.schema_version || null,
62
+ metrics_contract: report.metrics_contract || null,
63
+ change: report.change || null,
64
+ section_ids: SECTION_ORDER.slice(),
65
+ conclusion_status: report.conclusion?.status || null,
66
+ tasks: {
67
+ document: report.change_summary?.tasks?.document || null,
68
+ direct_test_backlink: audit.direct_test_backlink || null,
69
+ strict_test_coverage: audit.strict_test_coverage || null,
70
+ traceability: report.change_summary?.tasks?.traceability || null,
71
+ },
72
+ tdd_pairs: {
73
+ required: tdd.required ?? null,
74
+ complete: tdd.complete ?? null,
75
+ },
76
+ tests: report.change_summary?.tests || null,
77
+ evidence: {
78
+ category_count: evidence.category_count ?? null,
79
+ occurrence_count: evidence.occurrence_count ?? null,
80
+ issues: (evidence.issues || []).map((issue) => ({
81
+ issue_id: issue.issue_id,
82
+ code: issue.code,
83
+ status: issue.status,
84
+ verdict: issue.verdict,
85
+ subject: issue.subject,
86
+ expected: issue.expected,
87
+ actual: issue.actual,
88
+ root_cause: issue.root_cause,
89
+ remediation: issue.remediation,
90
+ evidence_event_ids: issue.evidence_event_ids,
91
+ disposition: issue.disposition,
92
+ })),
93
+ },
94
+ changed_files: {
95
+ task_event_files_changed: changed.task_event_files_changed ?? null,
96
+ final_output_files_changed: changed.final_output_files_changed ?? null,
97
+ added_lines: changed.added_lines ?? null,
98
+ deleted_lines: changed.deleted_lines ?? null,
99
+ diff_source: changed.diff_source ?? null,
100
+ reconciliation_status: changed.reconciliation_status ?? null,
101
+ },
102
+ attribution: report.attribution_audit || null,
103
+ artifact_sizes: Object.fromEntries(
104
+ Object.entries(artifacts).map(([name, item]) => [name, item?.size_bytes ?? null]),
105
+ ),
106
+ section_summaries: Object.fromEntries(
107
+ SECTION_ORDER.map((id) => [id, sections[id]?.summary || null]),
108
+ ),
109
+ };
110
+ }
111
+
112
+ function buildChangeReportViewModel(report = {}) {
113
+ const summary = report.change_summary || {};
114
+ const audit = report.task_evidence_audit || {};
115
+ const tdd = report.tdd_pair_audit || {};
116
+ const changed = report.changed_files || {};
117
+ const evidence = summary.evidence || { categories: [], category_count: 0, occurrence_count: 0 };
118
+ const artifacts = report.artifacts?.files || {};
119
+ const metrics = report.metrics || {};
120
+ const efficiency = metrics.efficiency || {};
121
+ const quality = metrics.quality || {};
122
+ const process = metrics.process || {};
123
+ const telemetry = metrics.telemetry_health || {};
124
+ const minSet = report.minimum_metric_set || {};
125
+ const openIssueCount = (report.evidence_alerts?.open || []).length;
126
+ const totalDurationMs = efficiency.total_duration_ms
127
+ ?? efficiency.effective_stage_duration_including_rework_ms
128
+ ?? efficiency.e1_lead_time_ms
129
+ ?? null;
130
+ const e1 = efficiency.e1_scenario_delivery_efficiency_ms ?? null;
131
+ const e2 = efficiency.e2_coding_time_ratio_including_rework
132
+ ?? efficiency.e2_coding_time_ratio
133
+ ?? null;
134
+ const e3 = efficiency.e3_spec_time_ratio_including_rework
135
+ ?? efficiency.e3_spec_time_ratio
136
+ ?? null;
137
+ const metricCards = [
138
+ {
139
+ name: '每个场景平均用时',
140
+ code: 'E1',
141
+ value: Number.isFinite(e1) ? `${durationText(e1)} / 场景` : '暂无数据',
142
+ state: (
143
+ minSet.evidence_status?.e1 === 'trusted'
144
+ || minSet.trusted_metrics?.includes?.('e1')
145
+ || minSet.trusted?.includes?.('e1')
146
+ ) ? '可信' : '需补证',
147
+ explanation: '七个标准阶段总耗时(含返工)÷ 验收场景数。',
148
+ source: `公式:标准阶段总耗时 ÷ 场景数。场景数:${efficiency.scenario_count ?? '未知'};当前值:${e1 ?? '不可计算'} ms。`,
149
+ },
150
+ {
151
+ name: '规约检查得分',
152
+ code: 'Q3',
153
+ value: quality.q3_spec_quality_score == null ? '暂无数据' : `${quality.q3_spec_quality_score} / 100`,
154
+ state: quality.q3_score_status === 'verified' ? '已验证' : '临时',
155
+ explanation: '最新规约检查结果;独立复核后才标记为已验证。',
156
+ source: `评审独立性:${quality.q3_reviewer_independence || '未知'};证据状态:${quality.q3_score_status || '未知'}。`,
157
+ },
158
+ {
159
+ name: '编码前是否先检查',
160
+ code: 'P4',
161
+ value: percentText(process.p4_quality_gate_enforcement_rate),
162
+ state: Number.isFinite(process.p4_quality_gate_enforcement_rate) ? '可计算' : '需补证',
163
+ explanation: '首次进入编码前,是否存在已配对且成功或部分通过的检查。',
164
+ source: '只接受能与 check 开始事件配对、且发生在首次 apply 之前的成功或部分通过结果。',
165
+ },
166
+ {
167
+ name: '阶段重复次数',
168
+ code: 'P-R',
169
+ value: process.rework_summary?.total_rework_attempts == null
170
+ ? '暂无数据'
171
+ : `${process.rework_summary.total_rework_attempts} 次`,
172
+ state: process.rework_summary ? '可计算' : '需补证',
173
+ explanation: '七个标准阶段中,首次执行之后的重复尝试次数。',
174
+ source: '辅助 test / explore 阶段不计入标准阶段重复总数。',
175
+ },
176
+ {
177
+ name: '执行记录是否完整',
178
+ code: 'P-H',
179
+ value: telemetry.p_h_telemetry_health_score == null
180
+ ? '暂无数据'
181
+ : `${telemetry.p_h_telemetry_health_score} / 100`,
182
+ state: telemetry.p_h_telemetry_health_score == null ? '需补证' : '可计算',
183
+ explanation: '标准阶段的身份、开始结束配对和数据质量综合评分。',
184
+ source: '沿用既有权重,但只以 propose、spec、design、task、check、apply、archive 七阶段为计算范围。',
185
+ },
186
+ ];
187
+
188
+ const sections = {
189
+ overview: {
190
+ id: 'overview',
191
+ title: SECTION_TITLES.overview,
192
+ summary: report.conclusion?.text || '当前证据不足,暂不能形成可信结论。',
193
+ scope_duration: durationText(totalDurationMs),
194
+ cards: [
195
+ {
196
+ key: 'final_tests',
197
+ label: '最终测试',
198
+ value: fractionText(summary.tests?.passed, summary.tests?.total),
199
+ },
200
+ {
201
+ key: 'document_tasks',
202
+ label: '文档任务完成',
203
+ value: fractionText(summary.tasks?.document?.completed, summary.tasks?.document?.total),
204
+ },
205
+ {
206
+ key: 'direct_backlink',
207
+ label: '直接测试回链',
208
+ value: fractionText(
209
+ audit.direct_test_backlink?.verified,
210
+ audit.direct_test_backlink?.total,
211
+ ),
212
+ },
213
+ {
214
+ key: 'strict_coverage',
215
+ label: '严格测试覆盖',
216
+ value: fractionText(
217
+ audit.strict_test_coverage?.verified ?? summary.tasks?.traceability?.verified,
218
+ audit.strict_test_coverage?.total ?? summary.tasks?.traceability?.total,
219
+ ),
220
+ },
221
+ {
222
+ key: 'open_issues',
223
+ label: '开放证据问题',
224
+ value: String(openIssueCount),
225
+ },
226
+ ],
227
+ metric_cards: metricCards,
228
+ },
229
+ 'metrics-stages': {
230
+ id: 'metrics-stages',
231
+ title: SECTION_TITLES['metrics-stages'],
232
+ summary: '本次变更效率、质量、过程与阶段执行',
233
+ metrics: {
234
+ total_duration_ms: totalDurationMs,
235
+ e1,
236
+ e2,
237
+ e3,
238
+ q3: quality.q3_spec_quality_score ?? null,
239
+ p4: process.p4_quality_gate_enforcement_rate ?? null,
240
+ p_r: process.rework_summary?.total_rework_attempts ?? null,
241
+ p_h: telemetry.p_h_telemetry_health_score ?? null,
242
+ },
243
+ metric_cards: metricCards,
244
+ metric_rows: [
245
+ { label: '七阶段总耗时', value: durationText(totalDurationMs) },
246
+ { label: '每个场景平均用时', code: 'E1', value: metricCards[0].value },
247
+ { label: '编码时间占比', code: 'E2', value: percentText(e2) },
248
+ { label: '规划与规约时间占比', code: 'E3', value: percentText(e3) },
249
+ { label: '阶段重复执行', code: 'P-R', value: metricCards[3].value },
250
+ ],
251
+ stages: (report.stage_timeline?.nodes || []).map((node) => ({
252
+ ...node,
253
+ display_stage: node.stage || node.command || '未知阶段',
254
+ display_round: node.round || 1,
255
+ display_start: localTimeText(node.start_ts),
256
+ display_end: localTimeText(node.end_ts),
257
+ display_duration: durationText(node.duration_ms),
258
+ display_result: node.result || '未知',
259
+ })),
260
+ tdd_pairs: {
261
+ required: tdd.required ?? null,
262
+ complete: tdd.complete ?? null,
263
+ pairs: tdd.pairs || [],
264
+ },
265
+ },
266
+ 'issues-evidence': {
267
+ id: 'issues-evidence',
268
+ title: SECTION_TITLES['issues-evidence'],
269
+ summary: evidence.note
270
+ || `${evidence.category_count || 0} 类问题,${evidence.occurrence_count || 0} 次规则命中`,
271
+ categories: evidence.categories || [],
272
+ issues: evidence.issues || [],
273
+ open_alerts: report.evidence_alerts?.open || [],
274
+ task_evidence_audit: audit,
275
+ apply_findings: report.apply_evidence_audit?.findings || [],
276
+ warning_dispositions: report.warning_dispositions || [],
277
+ },
278
+ 'artifacts-trace': {
279
+ id: 'artifacts-trace',
280
+ title: SECTION_TITLES['artifacts-trace'],
281
+ summary: changed.reconciliation_note || '产物与文件对账',
282
+ changed_files: {
283
+ task_event_files_changed: changed.task_event_files_changed ?? null,
284
+ final_output_files_changed: changed.final_output_files_changed ?? null,
285
+ added_lines: changed.added_lines ?? null,
286
+ deleted_lines: changed.deleted_lines ?? null,
287
+ diff_source: changed.diff_source || null,
288
+ reconciliation_status: changed.reconciliation_status ?? null,
289
+ reconciliation_note: changed.reconciliation_note || null,
290
+ final_output_files: changed.final_output_files || changed.files || null,
291
+ only_in_task_events: changed.only_in_task_events || [],
292
+ only_in_final_output: changed.only_in_final_output || [],
293
+ },
294
+ artifacts: Object.entries(artifacts).map(([name, item]) => ({
295
+ name,
296
+ path: item?.path || null,
297
+ exists: Boolean(item?.exists),
298
+ size_bytes: item?.size_bytes ?? null,
299
+ size_display: item?.size_display || null,
300
+ sha256: item?.sha256 ?? null,
301
+ integrity_note: item?.integrity_note || null,
302
+ })),
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
+ },
311
+ };
312
+
313
+ return {
314
+ change: report.change || null,
315
+ schema_version: report.schema_version || null,
316
+ metrics_contract: report.metrics_contract || null,
317
+ generated_at: report.generated_at || null,
318
+ conclusion: report.conclusion || null,
319
+ minimum_metric_set: report.minimum_metric_set || null,
320
+ scheme_compliance: report.scheme_compliance || null,
321
+ known_risks: report.known_risks || null,
322
+ process_notes: report.process_notes || null,
323
+ telemetry_warnings: report.telemetry_warnings || null,
324
+ archive_result: report.archive_result || null,
325
+ report_scope: report.report_scope || null,
326
+ section_order: SECTION_ORDER.slice(),
327
+ sections,
328
+ semantic_digest_input: buildSemanticDigestInput(report, sections),
329
+ semantic_digest: crypto.createHash('sha256')
330
+ .update(JSON.stringify(buildSemanticDigestInput(report, sections)))
331
+ .digest('hex'),
332
+ };
333
+ }
334
+
335
+ module.exports = {
336
+ SECTION_ORDER,
337
+ SECTION_TITLES,
338
+ buildChangeReportViewModel,
339
+ buildSemanticDigestInput,
340
+ };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+
5
+ /**
6
+ * 解析运行时元数据,返回 producer 身份信息。
7
+ *
8
+ * 开发/测试模式:直接读取上级 package.json 的 version 字段。
9
+ * 部署模式:init.js 会生成一个版本号写死的 runtime-metadata.cjs 覆盖此文件。
10
+ *
11
+ * @returns {{ version: string, producer: string }}
12
+ */
13
+ function resolveRuntimeMetadata() {
14
+ const pkg = require(path.join(__dirname, '..', 'package.json'));
15
+ return {
16
+ version: pkg.version,
17
+ producer: `kld-sdd@${pkg.version}`,
18
+ };
19
+ }
20
+
21
+ module.exports = { resolveRuntimeMetadata };
@@ -17,10 +17,12 @@ description: opsx-apply 的详细模板:telemetry 命令、worktree 全套策
17
17
  ### task_update(每完成一个任务记录)
18
18
 
19
19
  ```bash
20
- node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --strict --type=task_update --command=apply --project=. --change=<变更名称> --capability=<capability-name> --task-id=<TASK-ID> --run-id=<本次任务更新稳定ID> --agent=<Agent类型> --source=opsx-command --session-id=<会话ID> --status=completed --result=success --summary="<TASK-ID> 完成" --details-json="{\"task_update\":{\"test_event_id\":\"<同一change内成功test_result的event_id>\",\"tdd_required\":<true|false>},\"files_changed\":[]}"
20
+ node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --strict --type=task_update --command=apply --project=. --change=<变更名称> --capability=<capability-name> --task-id=<TASK-ID> --run-id=<本次任务更新稳定ID> --agent=<Agent类型> --source=opsx-command --session-id=<会话ID> --status=completed --result=success --summary="<TASK-ID> 完成" --details-json="{\"task_update\":{\"test_event_id\":\"<同一change内成功test_result的event_id>\",\"tdd_required\":<true|false>,\"tdd_pair_id\":\"<pair-N>\",\"tdd_role\":\"green\"},\"files_changed\":[]}"
21
+
22
+ 也可互斥使用 `test_run_id`(同 change、覆盖当前任务、唯一严格完成候选);解析成功后会补齐规范化 `test_event_id`。
21
23
  ```
22
24
 
23
- **⚠️ 注意**:`--task-id=<TASK-ID>` 必须替换为实际任务 ID。完成任务必须引用同一 change 内、`tdd_phase` 为 green/refactor/regression、退出码为 0、`failure_type=none` 的 `test_result.event_id`,并明确填写 `tdd_required=true/false`;禁止把测试计数复制进 `task_update`。
25
+ **⚠️ 注意**:`--task-id=<TASK-ID>` 必须替换为实际任务 ID。完成任务必须引用同一 change 内、`tdd_phase` 为 green/refactor/regression、退出码为 0、`failure_type=none` 的 `test_result.event_id`(或可唯一解析的 `test_run_id`),并明确填写 `tdd_required=true/false`;TDD 任务应写同一 `tdd_pair_id` 与 `tdd_role`;禁止把测试计数复制进 `task_update`。
24
26
 
25
27
  只有文档、纯配置说明等确实不适用测试的任务,才能显式声明:
26
28
 
@@ -112,7 +112,7 @@ node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" tasks-status --project=. --chan
112
112
  ```json
113
113
  {
114
114
  "maturity_evidence": {
115
- "change_type": "config|document|report|composite|other",
115
+ "change_type": "config|transaction|report|composite",
116
116
  "e1_actual_ms": 500,
117
117
  "e1_target_ms": 600,
118
118
  "e1_type_target_met": true,
@@ -21,7 +21,7 @@ description: "opsx-archive 前后日志/总结自检清单 — 仅在 archive
21
21
 
22
22
  - [ ] 归档目录 `openspec/changes/archive/<日期>-<变更名称>/` 存在
23
23
  - [ ] `openspec/changes/archive/<日期>-<变更名称>/` 下 `archive-ontology.json`、`canonical-facts.json`、`conversion-report.json` 和 `archive-manifest.json` 均存在
24
- - [ ] 活动 change 目录下的 `artifacts/*.ontology.json` 与 `artifact-index.json` 已随归档目录一并迁移
24
+ - [ ] 活动 change 目录下的 `artifacts/*.ontology.json` 与 `ontology/artifact-index.json` 已随归档目录一并迁移
25
25
  - [ ] `archive-manifest.json` 为 `kld-sdd-archive-manifest/v2`,且 `files` 精确覆盖 ZIP 内除 manifest 自身外的全部文件
26
26
  - [ ] `openspec/changes/archive/<日期>-<变更名称>.zip` 存在并可由知识库 `ArchivePackageReader` 读取
27
27
  - [ ] canonical facts 中每个实体/关系均能通过 `source.file`、`source.anchor_id`、`source.content_hash` 定向展开到包内原文
@@ -305,7 +305,7 @@ node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --type=conformance_revie
305
305
  `opsx-check` **不联网提问**。Agent 应在 propose/spec 已问完;本阶段只验证并入既有 apply 前门禁:
306
306
 
307
307
  - Continuity=`iteration` 时每个 CAP 具备 KB 回传 `entity-id` / `version-id`
308
- - 已写 spec 的 Capability:场景 `external-ref` 与 `continuity-resolution.json` 决议一致;同 key+同锚点未偷偷换 entity_id
308
+ - 已写 spec 的 Capability:场景 `external-ref` 与 `ontology/continuity-resolution.json` 决议一致;同 key+同锚点未偷偷换 entity_id
309
309
  - 用户选「原对象」却仍用新 id、或选「新对象」却仍共用旧锚点 → 失败(`CONTINUITY_IDENTITY_MISMATCH` / `EXTERNAL_REF_CONFLICT`)
310
310
  - 决议缺失 / pending / 与产物不一致 → `CONTINUITY_DECISION_REQUIRED`
311
311
  - **编号诊断码**(并入五维报告与 apply gate):
@@ -333,7 +333,7 @@ node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" semantic-check --project=. --ch
333
333
  - `added` 必须使用全新实体/版本 UUID;`modified/removed` 必须复用实体 UUID并指向直接前序版本;`unchanged` 必须复用历史实体和版本 UUID且不得复制历史正文。
334
334
  - 文件观察结果只能作为快速上下文,check 必须重新全量 semantic-reconcile。
335
335
  - propose/spec/design/task 对应工作态 JSON 应已在各作者阶段生成;check 不负责首次生成业务事实,只重新解析 Markdown、核对各 JSON 与同一 revision,并在全部通过时把派生 revision 更新为 `review_status=pending`。
336
- - Check 只读是指不修改 proposal/spec/design/tasks 原文;允许原子刷新 `openspec/changes/<变更名称>/` 下可再生的 `working-ontology.json`、`artifact-index.json` 与 `artifacts/*.ontology.json`。
336
+ - Check 只读是指不修改 proposal/spec/design/tasks 原文;允许原子刷新 `openspec/changes/<变更名称>/` 下可再生的 `ontology/working-ontology.json`、`ontology/artifact-index.json` 与 `artifacts/*.ontology.json`。
337
337
 
338
338
  ## Guardrails
339
339
 
@@ -57,5 +57,5 @@ description: "opsx-check 阶段日志自检清单 — 仅在 check 自检时读
57
57
 
58
58
  ## F. 语义门禁与工作态
59
59
 
60
- - [ ] `openspec/changes/<变更名称>/artifact-index.json` 已覆盖当前全部 proposal/spec/design/tasks,且每份 `artifacts/*.ontology.json` 与 `working-ontology.json` revision 一致
60
+ - [ ] `openspec/changes/<变更名称>/ontology/artifact-index.json` 已覆盖当前全部 proposal/spec/design/tasks,且每份 `artifacts/*.ontology.json` 与 `ontology/working-ontology.json` revision 一致
61
61
  - [ ] 全部语义门禁通过时工作态 JSON 已从 draft 刷新为 pending;check 未修改任何 Markdown 原文
@@ -49,6 +49,20 @@ Task Progress:
49
49
 
50
50
  入库时:用户指定目标 KB(从 `targets` 中选择),仅对选中的 KB 执行上传。
51
51
 
52
+ ### 3.5 兜底:确保 project-identity.json 存在(上传前必做)
53
+
54
+ > 正常情况下 `project-identity.json` 已在 `opsx-ontology-query` 首次配置 KB 时创建。
55
+ > 本步是兜底:如果用户跳过了 ontology-query(如选择 archive 降级路径),直接来做入库,需要确保文件存在且 `project_id` 正确。
56
+
57
+ 1. 读取 spec 包裹包路径:
58
+ ```bash
59
+ SPEC_ROOT=$(node "$(cat .sdd-spec-root)/skywalk-sdd/ontology/cli.cjs" spec-root)
60
+ ```
61
+ 2. 检查 `$SPEC_ROOT/skywalk-sdd/project-identity.json`:
62
+ - **不存在** → 从 `targets[0].spaceKey` 创建(格式同 `opsx-ontology-query` Step 3.5)
63
+ - **已存在但 `project_id !== targets[0].spaceKey`** → 提示用户:`⚠️ project_id 与 Space spaceKey 不一致,入库将报 PROJECT_SPACE_MISMATCH。是否更新?`
64
+ - **已存在且一致** → 跳过
65
+
52
66
  ### 4. 入库操作
53
67
 
54
68
  路径前缀:`/api/v1/spaces/{spaceId}/knowledge-bases/{kbId}/ingestions`
@@ -56,6 +56,37 @@ Task Progress:
56
56
 
57
57
  **选择空间/KB**:`GET $API/v1/spaces?tenantKey=…` → 对每个 space `GET …/knowledge-bases` → 展示清单**允许多选** → 写入共享 `../.shared/kb-state.json` 的 `targets`。
58
58
 
59
+ ### 3.5 写入项目身份文件(首次配置 Space 后)
60
+
61
+ > `project-identity.json` 是 spec 仓 Git 中的**团队共享**文件,记录 `project_id`(= KB Space 的 `spaceKey`)。Archive 阶段 `archive-docs` 读取此文件生成 `archive-manifest.json`,kb-ingest 上传时 KB 校验 `project_id === spaceKey`。
62
+
63
+ 选择 Space 完成后(Step 3 写入 `targets` 后),执行以下逻辑:
64
+
65
+ 1. 读取 spec 包裹包路径:
66
+ ```bash
67
+ SPEC_ROOT=$(node "$(cat .sdd-spec-root)/skywalk-sdd/ontology/cli.cjs" spec-root)
68
+ ```
69
+ 2. 检查 `$SPEC_ROOT/skywalk-sdd/project-identity.json` 是否已存在
70
+ 3. **不存在** → 创建(取 `targets[0].spaceKey` 作为 `project_id`):
71
+ ```json
72
+ {
73
+ "schema_version": "kld-sdd-project-identity/v1",
74
+ "project_id": "<targets[0].spaceKey>",
75
+ "created_at": "<ISO timestamp>",
76
+ "kb_space_id": "<targets[0].spaceId>",
77
+ "kb_space_name": "<targets[0].spaceName>"
78
+ }
79
+ ```
80
+ 输出:`✓ 已创建 skywalk-sdd/project-identity.json(project_id: <spaceKey>),请提交到 spec 仓 Git 以便团队共享`
81
+ 4. **已存在但 `project_id` 与 `targets[0].spaceKey` 不一致** → 用 AskUserQuestion 询问:
82
+ > "project-identity.json 中的 project_id 与当前选择的 KB Space spaceKey 不一致:
83
+ > - 文件中:`<existing project_id>`
84
+ > - 当前 Space:`<spaceKey>`
85
+ > 是否更新?"
86
+ - 用户确认 → 更新 `project_id` + `kb_space_id` + `kb_space_name`
87
+ - 用户拒绝 → 保留原值(可能入库时报 `PROJECT_SPACE_MISMATCH`)
88
+ 5. **已存在且一致** → 跳过,不输出
89
+
59
90
  > state.json 字段 schema、鉴权细节、列表接口 → [reference.md](reference.md)。
60
91
 
61
92
  ## 查询
@@ -229,9 +229,9 @@ node "$(cat .sdd-spec-root)/skywalk-sdd/context-client.cjs" --mode=resolve \
229
229
  5. 若申报了 feature-id:额外 `objectType=feature` resolve 一次,展示「该功能下已有能力 n 个(存活 m / 失效 k)」辅助勾选 CAP 范围;不改变 Continuity 判定优先级。
230
230
  6. 按 KB 结果确认 Continuity:`iteration` / `similar-reference` / `new`;勾选本次涉及的 CAP。
231
231
  7. 写入 proposal frontmatter:`requirement-refs`(含 `feature-id`)/ `numbering-waiver` + `continuity`。**禁止**写本地 archive 文件夹名作为 `base-archive`。
232
- 8. CAP 级「同 key + 同锚点、不同 entity_id」当场问 A/B/C;决议写入 `continuity-resolution.json` 的 `capabilities[]`。
232
+ 8. CAP 级「同 key + 同锚点、不同 entity_id」当场问 A/B/C;决议写入 `ontology/continuity-resolution.json` 的 `capabilities[]`。
233
233
  9. KB 不可用 → `degraded` 继续,**禁止**扫本地 `archive/` 抄 UUID。KB degraded(用户选择跳过)时走路径 B(archive 降级):
234
- - 扫描 `openspec/changes/archive/*/ontology-identities.json` 匹配 canonicalKey
234
+ - 扫描 `openspec/changes/archive/*/ontology/ontology-identities.json` 匹配 canonicalKey
235
235
  - path B 的所有 entity-id 来源在 sdd-output.md 知识库使用表中必须标注 `source: archive(degraded)`,与 `source: KB current` 明确区分。
236
236
  - ⚠️ **降级风险**:archive 中的 version-id 可能已过时(如果该 capability 在 archive 之后又有新版本入库到 KB)。path B 的 predecessor-version 不保证是 KB current。入库时可能触发 `VERSION_CONFLICT`。
237
237
  10. **不得**在本阶段生成 STMT/AC/场景或裁决场景身份;**不得**铸/改 REQ/FEAT 号。
@@ -254,7 +254,7 @@ resolve(canonicalKey=CAP-USER-REGISTRATION) → CREATE_NEW
254
254
  ```
255
255
  # 扫描归档(仅 KB degraded 时)
256
256
  Read openspec/changes/archive/*/proposal.md 的能力分解章节
257
- Read openspec/changes/archive/*/ontology-identities.json
257
+ Read openspec/changes/archive/*/ontology/ontology-identities.json
258
258
 
259
259
  # 匹配结果示例:
260
260
  # "账号锁定从内存迁到DB" → 匹配归档 CAP-ACCOUNT-LOCKOUT (entity-id: 3d18c60e, version: 29e7f242)
@@ -272,7 +272,7 @@ Read openspec/changes/archive/*/ontology-identities.json
272
272
 
273
273
  **❗ 必须主动询问用户,不得默认选择**。在模式与测试策略选择附近,基于需求给出 `change-type` **推荐**并让用户确认;Auto 只能推荐,**禁止无提示静默写入**。
274
274
 
275
- 允许值固定为:`config | document | report | composite | other`(写入 proposal.md YAML frontmatter 的 `change-type` 字段)。AskUserQuestion 文案与类型说明见 `./reference.md`「§7.5 变更类型选择」。
275
+ 允许值固定为:`config | transaction | report | composite`(写入 proposal.md YAML frontmatter 的 `change-type` 字段)。AskUserQuestion 文案与类型说明见 `./reference.md`「§7.5 变更类型选择」。
276
276
 
277
277
  ### 8. 【交互引导】测试策略选择
278
278
 
@@ -340,11 +340,11 @@ Read openspec/changes/archive/*/ontology-identities.json
340
340
  - **⛔ 阶段边界**:本阶段禁止执行任何代码创建/修改操作。若用户要求处理代码,回复:「当前处于 Propose 阶段,代码操作请在完成文档后使用 `/opsx-apply` 执行。」
341
341
  - **⛔ 单阶段原则**:完成 proposal.md 后必须立即停止。仅提示用户下一步可运行 `/opsx-spec`,绝对禁止自动执行 spec/design/task 等后续阶段。每个阶段必须由用户主动触发。
342
342
  - **⛔ Frontmatter 规范(L7)**:YAML frontmatter 中禁止写 `#` 注释(YAML 注释在 frontmatter 中可能导致解析问题)。如需说明,在 frontmatter 之前或之后用正文描述。
343
- - **⛔ change-type 必采**:frontmatter 必须含 `change-type`,取值仅限 `config | document | report | composite | other`;Agent 可推荐但须经用户确认后写入,禁止静默默认。
343
+ - **⛔ change-type 必采**:frontmatter 必须含 `change-type`,取值仅限 `config | transaction | report | composite`;Agent 可推荐但须经用户确认后写入,禁止静默默认。
344
344
 
345
345
  ---
346
346
 
347
347
  ## 渐进披露
348
348
 
349
349
  - Read `checklist.md` 仅在执行 propose 需要校验时 — 含阶段边界⛔(Propose 阶段约束)、§6 需求完整性检查、Guardrails ⛔ 强制项勾选表。
350
- - Read `reference.md` 仅在需要参考详细模板时 — 含 📊 Telemetry 命令模板(start/end)、§7 文档拆分模式(Full/Simple/Auto)、§7.5 变更类型(config/document/report/composite/other)、§8 测试策略(TDD/Impl-First/None)、§10 质量红线自检清单(8 项)。
350
+ - Read `reference.md` 仅在需要参考详细模板时 — 含 📊 Telemetry 命令模板(start/end)、§7 文档拆分模式(Full/Simple/Auto)、§7.5 变更类型(config/transaction/report/composite)、§8 测试策略(TDD/Impl-First/None)、§10 质量红线自检清单(8 项)。
@@ -56,20 +56,19 @@ description: opsx-propose 的详细模板:telemetry 命令、文档拆分模
56
56
 
57
57
  > "📊 **变更类型(用于 V3 指标分层统计)**
58
58
  >
59
- > 根据本次需求,推荐类型:**[config | document | report | composite | other]** — [一句话理由]
59
+ > 根据本次需求,推荐类型:**[config | transaction | report | composite]** — [一句话理由]
60
60
  >
61
61
  > 请选择或确认:
62
62
  > - **config** — 配置/开关/环境/依赖调整,几乎不改业务逻辑
63
- > - **document** — 文档、规范、模板、注释类变更
64
- > - **report** — 报告、度量、仪表盘、采集契约类变更
63
+ > - **transaction** — 单据类:业务流程、状态流转、CRUD/交易路径变更
64
+ > - **report** — 报表类:报表查询、度量展示、采集契约(不是“度量报告工具”本身的专属类型)
65
65
  > - **composite** — 跨多类能力的组合变更
66
- > - **other** — 以上均不合适时使用
67
66
  >
68
67
  > A) 采用推荐 B) 手动选择其他类型"
69
68
 
70
- 根据用户确认,在 proposal.md YAML frontmatter 写入 `change-type: <值>`。允许值固定:`config | document | report | composite | other`。
69
+ 根据用户确认,在 proposal.md YAML frontmatter 写入 `change-type: <值>`。允许值固定:`config | transaction | report | composite`。
71
70
 
72
- > 历史 proposal 缺该字段时,报告侧归为 `unknown`;本阶段不得替用户猜测补写。
71
+ > 历史 proposal `document`/`other` 读取侧迁移为 `unknown` 并告警,不写入新 proposal;本阶段不得替用户猜测补写。
73
72
 
74
73
  ---
75
74
 
@@ -107,6 +106,6 @@ description: opsx-propose 的详细模板:telemetry 命令、文档拆分模
107
106
  - [ ] 前置依赖使用 checkbox 格式
108
107
  - [ ] 文档末尾包含质量红线检查清单
109
108
  - [ ] 能力分解章节已明确(决定后续 specs 文件夹结构)
110
- - [ ] frontmatter 含 `change-type`,取值为 `config | document | report | composite | other` 之一且经用户确认
109
+ - [ ] frontmatter 含 `change-type`,取值为 `config | transaction | report | composite` 之一且经用户确认
111
110
 
112
111
  **如有任意一项未满足,重新生成对应章节,直至全部通过。**
@@ -170,7 +170,7 @@ node "$(cat .sdd-spec-root)/skywalk-sdd/context-client.cjs" \
170
170
  2. 新场景:`SCN-<slug>-<NNN>`;slug=kebab-case 小写 ≤40;NNN=该 REQ 命名空间内 max+1(已用集合=KB 回传 ∪ 本 Change 已写键,含 removed 墓碑)。
171
171
  3. removed 号是墓碑:永不复用、永不重排;序号达 999 → 硬错误,回需求系统拆分需求,不扩位。
172
172
  4. 写完立即用 `cli.cjs external-key --validate … --type scenario` 校验;REQ 前缀必须 ∈ proposal `requirement-refs`。
173
- - 场景级「同 SCN key + 同锚点、不同 entity_id」当场问 A/B/C;未决不得进入下一 CAP / design。决议追加到 `continuity-resolution.json` 的 `scenarios[]`;决议中的 `externalKey` 必须是归一化形态。
173
+ - 场景级「同 SCN key + 同锚点、不同 entity_id」当场问 A/B/C;未决不得进入下一 CAP / design。决议追加到 `ontology/continuity-resolution.json` 的 `scenarios[]`;决议中的 `externalKey` 必须是归一化形态。
174
174
  - 优先消费 `reuseBundles[].statements`;`designElements` 只作理解上下文,不能写成 Spec 的 How。
175
175
  - 所有知识库内容均为 advisory;与用户确认 / proposal 冲突时以当前确认与 proposal 为准。
176
176
  - **禁止**铸/改 REQ/FEAT;**禁止**自动重排/回收 SCN。
@@ -129,7 +129,9 @@ node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --strict --type=test_res
129
129
  **实现任务**(不带 `task_kind`):
130
130
 
131
131
  ```bash
132
- node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --strict --type=task_update --command=apply --project=. --change=<变更名称> --capability=<capability-name> --task-id=<TASK-ID> --run-id=<任务更新稳定ID> --agent=<Agent类型> --source=opsx-command --session-id=<会话ID> --status=completed --result=success --summary="<TASK-ID> 完成" --details-json='{"files_changed":[],"task_update":{"test_event_id":"<成功的green/refactor测试event_id>","tdd_required":true}}'
132
+ node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --strict --type=task_update --command=apply --project=. --change=<变更名称> --capability=<capability-name> --task-id=<TASK-ID> --run-id=<任务更新稳定ID> --agent=<Agent类型> --source=opsx-command --session-id=<会话ID> --status=completed --result=success --summary="<TASK-ID> 完成" --details-json='{"files_changed":[],"task_update":{"test_event_id":"<成功的green/refactor测试event_id>","tdd_required":true,"tdd_pair_id":"<pair-N>","tdd_role":"green"}}'
133
+
134
+ > RED/GREEN 同对必须写相同 `tdd_pair_id`;也可用互斥的 `test_run_id`(唯一严格完成候选)代替 `test_event_id`。
133
135
  ```
134
136
 
135
137
  ## §9 单元测试真实执行
@@ -15,5 +15,5 @@ node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --strict --type=test_res
15
15
  实现任务不带 `task_kind`(默认 implementation),按真实测试结果记录。
16
16
 
17
17
  ```bash
18
- node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --strict --type=task_update --command=apply --project=. --change=<变更名称> --capability=<capability-name> --task-id=<TASK-ID> --run-id=<任务更新稳定ID> --agent=<Agent类型> --source=opsx-command --session-id=<会话ID> --status=completed --result=success --summary="<TASK-ID> 完成" --details-json='{"files_changed":[],"task_update":{"test_event_id":"<成功的green/refactor测试event_id>","tdd_required":true}}'
18
+ node "$(cat .sdd-spec-root)/skywalk-sdd/log.cjs" record --strict --type=task_update --command=apply --project=. --change=<变更名称> --capability=<capability-name> --task-id=<TASK-ID> --run-id=<任务更新稳定ID> --agent=<Agent类型> --source=opsx-command --session-id=<会话ID> --status=completed --result=success --summary="<TASK-ID> 完成" --details-json='{"files_changed":[],"task_update":{"test_event_id":"<成功的green/refactor测试event_id>","tdd_required":true,"tdd_pair_id":"<pair-N>","tdd_role":"green"}}'
19
19
  ```