kld-sdd 2.5.1 → 2.6.0

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 (35) hide show
  1. package/README.md +118 -8
  2. package/kld-sdd-guide.html +1 -1
  3. package/lib/init.js +24 -5
  4. package/lib/tool-profiles.js +1 -1
  5. package/package.json +4 -2
  6. package/skywalk-sdd/context-client.cjs +160 -0
  7. package/skywalk-sdd/index.cjs +445 -36
  8. package/skywalk-sdd/ontology/archive-package.cjs +489 -0
  9. package/skywalk-sdd/ontology/artifact-observer.cjs +91 -0
  10. package/skywalk-sdd/ontology/artifact-parser.cjs +621 -0
  11. package/skywalk-sdd/ontology/change-lock.cjs +126 -0
  12. package/skywalk-sdd/ontology/cli.cjs +146 -0
  13. package/skywalk-sdd/ontology/effective-graph.cjs +158 -0
  14. package/skywalk-sdd/ontology/id.cjs +126 -0
  15. package/skywalk-sdd/ontology/identity-index.cjs +287 -0
  16. package/skywalk-sdd/ontology/normalizer.cjs +107 -0
  17. package/skywalk-sdd/ontology/runtime.cjs +466 -0
  18. package/skywalk-sdd/ontology/schema.cjs +139 -0
  19. package/skywalk-sdd/ontology/structural-identity.cjs +77 -0
  20. package/skywalk-sdd/ontology/traceability-validator.cjs +610 -0
  21. package/skywalk-sdd/ontology/working-artifacts.cjs +243 -0
  22. package/templates/openspec/design.md +18 -0
  23. package/templates/openspec/proposal.md +19 -6
  24. package/templates/openspec/spec.md +62 -8
  25. package/templates/openspec/tasks.md +28 -6
  26. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +19 -1
  27. package/templates/skills/kld-sdd/opsx-archive/checklist.md +5 -1
  28. package/templates/skills/kld-sdd/opsx-check/SKILL.md +18 -0
  29. package/templates/skills/kld-sdd/opsx-check/checklist.md +2 -0
  30. package/templates/skills/kld-sdd/opsx-design/SKILL.md +11 -0
  31. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +12 -0
  32. package/templates/skills/kld-sdd/opsx-propose/checklist.md +2 -0
  33. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +34 -0
  34. package/templates/skills/kld-sdd/opsx-spec/checklist.md +5 -0
  35. package/templates/skills/kld-sdd/opsx-task/SKILL.md +11 -0
@@ -0,0 +1,243 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const path = require('path');
5
+
6
+ const WORKING_ARTIFACT_SCHEMA_VERSION = 'kld-sdd-working-artifact-facts/v1';
7
+ const ARTIFACT_INDEX_SCHEMA_VERSION = 'kld-sdd-working-artifact-index/v1';
8
+
9
+ function sha256(value) {
10
+ return crypto.createHash('sha256').update(value).digest('hex');
11
+ }
12
+
13
+ function prefixedHash(value) {
14
+ const normalized = String(value || '').trim().toLowerCase().replace(/^sha256:/, '');
15
+ return normalized ? `sha256:${normalized}` : '';
16
+ }
17
+
18
+ function safeRelativePath(value) {
19
+ const input = String(value || '').trim().replace(/\\/g, '/');
20
+ const normalized = path.posix.normalize(input);
21
+ if (
22
+ !input
23
+ || input.startsWith('/')
24
+ || normalized !== input
25
+ || normalized === '..'
26
+ || normalized.startsWith('../')
27
+ || input.includes('\0')
28
+ ) {
29
+ throw new Error(`工作态 Artifact 路径不安全: ${value}`);
30
+ }
31
+ return normalized;
32
+ }
33
+
34
+ function artifactJsonPath(sourcePath) {
35
+ const normalized = safeRelativePath(sourcePath);
36
+ const extension = path.posix.extname(normalized);
37
+ const withoutExtension = extension
38
+ ? normalized.slice(0, -extension.length)
39
+ : normalized;
40
+ return `artifacts/${withoutExtension}.ontology.json`;
41
+ }
42
+
43
+ function normalizedSource(source, fallbackAnchor) {
44
+ const input = source && typeof source === 'object' ? source : {};
45
+ const line = Number(input.line || 0);
46
+ return {
47
+ artifact_type: String(input.artifact_type || 'unknown').trim().toLowerCase(),
48
+ file: safeRelativePath(input.file),
49
+ ...(Number.isInteger(line) && line > 0 ? { line } : {}),
50
+ anchor_id: String(input.anchor_id || fallbackAnchor || '').trim().toUpperCase(),
51
+ content_hash: prefixedHash(input.content_hash),
52
+ };
53
+ }
54
+
55
+ function workingEntity(entity, reviewStatus) {
56
+ const anchorId = String(entity.anchor_id || entity.id || '').trim().toUpperCase();
57
+ return {
58
+ anchor_id: anchorId,
59
+ type: entity.type,
60
+ name: String(entity.name || anchorId).trim(),
61
+ entity_id: String(entity.entity_id || '').trim().toLowerCase(),
62
+ entity_version_id: String(entity.entity_version_id || entity.version_id || '').trim().toLowerCase(),
63
+ predecessor_version_id: entity.predecessor_version_id
64
+ ? String(entity.predecessor_version_id).trim().toLowerCase()
65
+ : null,
66
+ delta_state: String(entity.delta_state || 'unresolved').trim().toLowerCase(),
67
+ content_hash: String(entity.content_hash || '').trim().toLowerCase(),
68
+ fact_kind: 'semantic',
69
+ assertion_type: 'asserted',
70
+ review_status: reviewStatus,
71
+ generation_role: entity.generation_role || 'current',
72
+ ...(entity.inherited_from ? { inherited_from: entity.inherited_from } : {}),
73
+ attributes: entity.attributes && typeof entity.attributes === 'object' ? entity.attributes : {},
74
+ source: normalizedSource(entity.source, anchorId),
75
+ };
76
+ }
77
+
78
+ function normalizeRelationDirection(relation, entityByAnchor) {
79
+ const originalType = String(relation.type || '').trim();
80
+ const originalFrom = String(relation.from || '').trim().toUpperCase();
81
+ const originalTo = String(relation.to || '').trim().toUpperCase();
82
+ if (originalType === 'acceptedBy') {
83
+ return { type: 'verifiedBy', from: originalFrom, to: originalTo };
84
+ }
85
+ if (originalType !== 'constrains') {
86
+ return { type: originalType, from: originalFrom, to: originalTo };
87
+ }
88
+ const fromType = entityByAnchor.get(originalFrom)?.type;
89
+ const toType = entityByAnchor.get(originalTo)?.type;
90
+ if (fromType === 'Constraint' && toType === 'SpecificationStatement') {
91
+ return { type: 'constrainedBy', from: originalTo, to: originalFrom };
92
+ }
93
+ return { type: 'constrainedBy', from: originalFrom, to: originalTo };
94
+ }
95
+
96
+ function workingRelation(relation, entityByAnchor, reviewStatus, producerVersion) {
97
+ const normalized = normalizeRelationDirection(relation, entityByAnchor);
98
+ const fromEntity = entityByAnchor.get(normalized.from);
99
+ const toEntity = entityByAnchor.get(normalized.to);
100
+ const assertionType = String(relation.assertion_type || 'asserted').trim().toLowerCase();
101
+ const output = {
102
+ type: normalized.type,
103
+ from_anchor_id: normalized.from,
104
+ to_anchor_id: normalized.to,
105
+ from_entity_id: String(fromEntity?.entity_id || '').trim().toLowerCase(),
106
+ to_entity_id: String(toEntity?.entity_id || '').trim().toLowerCase(),
107
+ assertion_type: assertionType,
108
+ fact_kind: 'semantic',
109
+ review_status: assertionType === 'suggested' ? 'pending' : reviewStatus,
110
+ generation_role: relation.generation_role || 'current',
111
+ ...(relation.inherited_from ? { inherited_from: relation.inherited_from } : {}),
112
+ source: normalizedSource(
113
+ relation.source,
114
+ `${normalized.from}-${normalized.type}-${normalized.to}`,
115
+ ),
116
+ };
117
+ if (assertionType === 'inferred') {
118
+ output.rule_id = String(relation.rule_id || '').trim();
119
+ output.generator = relation.generator || 'kld-sdd';
120
+ output.generator_version = relation.generator_version || producerVersion;
121
+ } else if (relation.rule_id) {
122
+ output.rule_id = relation.rule_id;
123
+ }
124
+ if (assertionType === 'suggested') {
125
+ output.generator = relation.generator;
126
+ output.generator_version = relation.generator_version;
127
+ output.confidence = relation.confidence;
128
+ output.support_evidence = Array.isArray(relation.support_evidence) ? relation.support_evidence : [];
129
+ output.opposition_evidence = Array.isArray(relation.opposition_evidence) ? relation.opposition_evidence : [];
130
+ }
131
+ return output;
132
+ }
133
+
134
+ function compareFacts(left, right) {
135
+ return (
136
+ String(left.anchor_id || left.type || '').localeCompare(String(right.anchor_id || right.type || ''))
137
+ || String(left.from_entity_id || '').localeCompare(String(right.from_entity_id || ''))
138
+ || String(left.to_entity_id || '').localeCompare(String(right.to_entity_id || ''))
139
+ );
140
+ }
141
+
142
+ function buildWorkingArtifactFacts(result, options = {}) {
143
+ const state = result.state || {};
144
+ const producerVersion = String(options.producerVersion || require('../../package.json').version);
145
+ const reviewStatus = state.review_status === 'pending' ? 'pending' : 'draft';
146
+ const rawEntities = Array.isArray(state.entities) ? state.entities : [];
147
+ const entityByAnchor = new Map(rawEntities.map((entity) => [
148
+ String(entity.anchor_id || entity.id || '').trim().toUpperCase(),
149
+ entity,
150
+ ]));
151
+ const artifactFiles = [];
152
+ const artifactIndex = [];
153
+
154
+ for (const artifact of state.artifacts || []) {
155
+ const sourcePath = safeRelativePath(artifact.path);
156
+ const jsonPath = artifactJsonPath(sourcePath);
157
+ const artifactDiagnostics = (result.diagnostics || []).filter((diagnostic) => (
158
+ !diagnostic.file || String(diagnostic.file).replace(/\\/g, '/') === sourcePath
159
+ ));
160
+ const artifactEntities = rawEntities
161
+ .filter((entity) => (
162
+ entity.source && String(entity.source.file).replace(/\\/g, '/') === sourcePath
163
+ ))
164
+ .map((entity) => workingEntity(entity, reviewStatus))
165
+ .sort(compareFacts);
166
+ const artifactRelations = (state.relations || [])
167
+ .filter((relation) => (
168
+ relation.source && String(relation.source.file).replace(/\\/g, '/') === sourcePath
169
+ ))
170
+ .map((relation) => workingRelation(relation, entityByAnchor, reviewStatus, producerVersion))
171
+ .sort(compareFacts);
172
+ const inheritedReferences = (state.inherited_references || []).filter((reference) => (
173
+ reference.source && String(reference.source.file).replace(/\\/g, '/') === sourcePath
174
+ ));
175
+ const inheritedRelations = (state.inherited_relations || []).filter((relation) => (
176
+ relation.source && String(relation.source.file).replace(/\\/g, '/') === sourcePath
177
+ ));
178
+ const semanticFacts = {
179
+ entities: artifactEntities,
180
+ relations: artifactRelations,
181
+ inherited_references: inheritedReferences,
182
+ inherited_relations: inheritedRelations,
183
+ };
184
+ const artifactFacts = {
185
+ schema_version: WORKING_ARTIFACT_SCHEMA_VERSION,
186
+ producer: `kld-sdd@${producerVersion}`,
187
+ canonical: false,
188
+ change: state.change,
189
+ change_id: state.change_id,
190
+ profile: state.profile,
191
+ stage: artifact.type,
192
+ review_status: reviewStatus,
193
+ valid: !artifactDiagnostics.some((diagnostic) => diagnostic.severity === 'error'),
194
+ revision: result.revision,
195
+ source: {
196
+ artifact_type: artifact.type,
197
+ path: sourcePath,
198
+ capability_id: artifact.capability_id || null,
199
+ content_hash: prefixedHash(artifact.content_hash),
200
+ },
201
+ facts_hash: sha256(JSON.stringify(semanticFacts)),
202
+ ...semanticFacts,
203
+ diagnostics: artifactDiagnostics,
204
+ };
205
+ artifactFiles.push({ path: jsonPath, value: artifactFacts });
206
+ artifactIndex.push({
207
+ artifact_type: artifact.type,
208
+ source_path: sourcePath,
209
+ json_path: jsonPath,
210
+ capability_id: artifact.capability_id || null,
211
+ source_content_hash: prefixedHash(artifact.content_hash),
212
+ facts_hash: artifactFacts.facts_hash,
213
+ entity_count: artifactEntities.length,
214
+ relation_count: artifactRelations.length,
215
+ diagnostic_count: artifactDiagnostics.length,
216
+ });
217
+ }
218
+
219
+ return {
220
+ index: {
221
+ schema_version: ARTIFACT_INDEX_SCHEMA_VERSION,
222
+ producer: `kld-sdd@${producerVersion}`,
223
+ canonical: false,
224
+ change: state.change,
225
+ change_id: state.change_id,
226
+ profile: state.profile,
227
+ review_status: reviewStatus,
228
+ valid: Boolean(result.valid),
229
+ revision: result.revision,
230
+ working_ontology_path: 'working-ontology.json',
231
+ artifacts: artifactIndex,
232
+ },
233
+ files: artifactFiles,
234
+ };
235
+ }
236
+
237
+ module.exports = {
238
+ WORKING_ARTIFACT_SCHEMA_VERSION,
239
+ ARTIFACT_INDEX_SCHEMA_VERSION,
240
+ safeRelativePath,
241
+ artifactJsonPath,
242
+ buildWorkingArtifactFacts,
243
+ };
@@ -1,3 +1,7 @@
1
+ ---
2
+ capability-id: "CAP-<CAPABILITY>" # 必须与对应 spec.md 一致
3
+ ---
4
+
1
5
  # 局部技术实现方案 - [Capability 名称]
2
6
 
3
7
  > **定位**:单一 Capability 的业务维度技术实现方案
@@ -8,6 +12,20 @@
8
12
 
9
13
  ---
10
14
 
15
+ ## 0. 本体语义锚点
16
+
17
+ <!-- 每个独立设计单元创建一个 DES;修改既有设计时复用原 ID -->
18
+
19
+ ### [DES-<CAPABILITY>-NNN] <!-- 设计单元名称 -->
20
+ - **entity-id**: <UUID>
21
+ - **version-id**: <UUID>
22
+ - **delta-state**: added
23
+ - **predecessor-version**: 无
24
+ - **realizes**: STMT-<CAPABILITY>-NNN
25
+ - **设计范围**: <!-- 本设计负责实现的局部范围 -->
26
+
27
+ ---
28
+
11
29
  ## 1. 字段完整性追溯表
12
30
 
13
31
  > **⛔ 核心红线**:用户在 Spec 中输入的所有字段必须在此表中体现,严禁无故丢弃!
@@ -1,5 +1,11 @@
1
1
  ---
2
2
  # 【用户选择配置 - 由 /opsx:propose 引导填写】
3
+ change-id: "CHG-<CHANGE-SLUG>" # 创建时生成,后续不得修改
4
+ entity-id: "<UUID>" # Change 的全局逻辑实体 UUID,added 时生成
5
+ version-id: "<UUID>" # 本次 Change 版本 UUID
6
+ delta-state: "added"
7
+ predecessor-version: "" # added 留空;modified/removed 指向直接前序版本
8
+ mode: "" # full=分 Capability 产物,simple=根目录精简产物
3
9
  test-strategy: "" # tdd=测试先行, impl-first=实现优先, none=无测试
4
10
  ---
5
11
 
@@ -7,7 +13,7 @@ test-strategy: "" # tdd=测试先行, impl-first=实现优先, none=无测试
7
13
 
8
14
  > **定位**:变更的业务意图(Why)与上下文总览
9
15
  >
10
- > **可选性**:【可跳过,直入spec】若跳过,必须将"影响范围"在 specs 中补齐
16
+ > **必需性**:ontology v2 新产物必须保留 proposal,用于承载 Change/Capability 身份和 profile;旧 proposal-less Simple 只能兼容扫描,不能直接归档为 confirmed
11
17
 
12
18
  ---
13
19
 
@@ -41,11 +47,19 @@ test-strategy: "" # tdd=测试先行, impl-first=实现优先, none=无测试
41
47
 
42
48
  ### 3.1 新增能力
43
49
  <!-- 每个能力会创建 specs/<name>/spec.md,使用 kebab-case 命名 -->
44
- - `<capability-name>`: <能力简要描述>
50
+ - [CAP-<CAPABILITY>] `<capability-name>`: <能力简要描述>
51
+ - **entity-id**: <UUID>
52
+ - **version-id**: <UUID>
53
+ - **delta-state**: added
54
+ - **predecessor-version**: 无
45
55
 
46
56
  ### 3.2 修改能力
47
- <!-- 仅当已有能力的需求级别变更时填写,检查 openspec/specs/ 现有规格 -->
48
- - `<existing-name>`: <修改什么需求>
57
+ <!-- 修改既有能力必须复用原 CAP ID,不得重新编号 -->
58
+ - [CAP-<EXISTING>] `<existing-name>`: <修改什么需求>
59
+ - **entity-id**: <复用历史 UUID>
60
+ - **version-id**: <新 UUID>
61
+ - **delta-state**: modified
62
+ - **predecessor-version**: <直接前序 version UUID>
49
63
 
50
64
  ---
51
65
 
@@ -103,5 +117,4 @@ test-strategy: "" # tdd=测试先行, impl-first=实现优先, none=无测试
103
117
  > - [ ] 逻辑链路已闭环
104
118
  > - [ ] 受影响模块已明确
105
119
  > - [ ] 依赖关系已梳理
106
- > - [ ] 若跳过本文档,影响范围已在 specs 中补齐
107
- > - [ ] 能力分解章节已明确列出所有能力
120
+ > - [ ] 能力分解章节已明确列出所有能力
@@ -1,3 +1,7 @@
1
+ ---
2
+ capability-id: "CAP-<CAPABILITY>" # 必须与 proposal.md 中的 Capability ID 一致
3
+ ---
4
+
1
5
  # spec.md - 能力规格定义
2
6
 
3
7
  > **定位**:单个能力(capability)的技术规格定义,用于 `specs/<capability>/spec.md`
@@ -14,29 +18,49 @@
14
18
 
15
19
  ### 新增需求
16
20
 
17
- <!-- 新增的需求,使用「必须」强制要求 -->
21
+ <!-- 新增实体按当前最大序号 + 1 分配 ID;已有实体修改时复用原 ID -->
18
22
 
19
- #### 需求项:<!-- 需求名称 -->
23
+ #### 需求项:[STMT-<CAPABILITY>-NNN] <!-- 需求名称 -->
24
+ - **entity-id**: <UUID>
25
+ - **version-id**: <UUID>
26
+ - **delta-state**: added
27
+ - **predecessor-version**: 无
20
28
 
21
29
  <!-- 需求描述,使用「必须」而非「应该」「可以」 -->
22
30
  系统必须 ...
23
31
 
24
- ##### 场景:<!-- 场景名称 -->
32
+ ##### 场景:[AC-<CAPABILITY>-NNN] <!-- 场景名称 -->
33
+ - **entity-id**: <UUID>
34
+ - **version-id**: <UUID>
35
+ - **delta-state**: added
36
+ - **predecessor-version**: 无
25
37
  - **当** <!-- 触发条件 -->
26
38
  - **预期** <!-- 预期结果 -->
27
39
 
28
- ##### 场景:<!-- 另一个场景 -->
40
+ ##### 场景:[AC-<CAPABILITY>-NNN] <!-- 另一个场景 -->
41
+ - **entity-id**: <UUID>
42
+ - **version-id**: <UUID>
43
+ - **delta-state**: added
44
+ - **predecessor-version**: 无
29
45
  - **当** <!-- 触发条件 -->
30
46
  - **预期** <!-- 预期结果 -->
31
47
 
32
48
  ### 修改需求
33
49
 
34
- <!-- 仅当修改已有需求时使用,必须复制完整原需求内容再修改 -->
50
+ <!-- 修改已有需求时复用原 STMT/AC/CON ID,只展开实际变化内容 -->
35
51
 
36
- #### 需求项:<!-- 已有需求名称 -->
52
+ #### 需求项:[STMT-<CAPABILITY>-NNN] <!-- 已有需求名称,复用原 ID -->
53
+ - **entity-id**: <复用历史 UUID>
54
+ - **version-id**: <新 UUID>
55
+ - **delta-state**: modified
56
+ - **predecessor-version**: <直接前序 version UUID>
37
57
  系统必须 ...
38
58
 
39
- ##### 场景:<!-- 场景名称 -->
59
+ ##### 场景:[AC-<CAPABILITY>-NNN] <!-- 场景名称,复用或新增 ID -->
60
+ - **entity-id**: <复用历史 UUID;新增场景则生成新 UUID>
61
+ - **version-id**: <新 UUID>
62
+ - **delta-state**: <modified|added>
63
+ - **predecessor-version**: <modified 时填写;added 为无>
40
64
  - **当** <!-- 触发条件 -->
41
65
  - **预期** <!-- 预期结果 -->
42
66
 
@@ -44,10 +68,40 @@
44
68
 
45
69
  <!-- 仅当移除已有需求时使用 -->
46
70
 
47
- #### 需求项:<!-- 被移除的需求名称 -->
71
+ #### 需求项:[STMT-<CAPABILITY>-NNN] <!-- 被移除的需求名称,复用原 ID -->
72
+ - **entity-id**: <复用历史 UUID>
73
+ - **version-id**: <新墓碑版本 UUID>
74
+ - **delta-state**: removed
75
+ - **predecessor-version**: <直接前序 version UUID>
48
76
  **移除原因**:<!-- 移除原因 -->
49
77
  **迁移方案**:<!-- 迁移方案 -->
50
78
 
79
+ ### 约束
80
+
81
+ #### 约束:[CON-<CAPABILITY>-NNN] <!-- 约束名称 -->
82
+ - **entity-id**: <UUID>
83
+ - **version-id**: <UUID>
84
+ - **delta-state**: added
85
+ - **predecessor-version**: 无
86
+ - **constrains**: STMT-<CAPABILITY>-NNN
87
+ - **约束内容**: <!-- 可量化、可验证的约束 -->
88
+
89
+ ### 继承引用
90
+
91
+ <!-- unchanged 内容只记录既有实体/版本引用,不复制历史正文;断链时 opsx-check 必须阻断 -->
92
+ - **anchor**: `STMT-<CAPABILITY>-NNN`
93
+ - **entity-id**: `<复用历史 UUID>`
94
+ - **version-id**: `<复用历史 version UUID>`
95
+ - **delta-state**: `unchanged`
96
+ - **source**: `<archive>/<artifact>#<anchor>`
97
+ - **source-version-hash**: `<前序实体版本内容 SHA-256>`
98
+
99
+ ### 继承关系
100
+
101
+ <!-- 历史关系不会自动全部继承;只有本节显式列出的 confirmed 关系进入 Effective Graph -->
102
+ - **relation**: `STMT-<CAPABILITY>-NNN acceptedBy AC-<CAPABILITY>-NNN`
103
+ - **source**: `<archive>/<artifact>#<from>-<relation>-<to>`
104
+
51
105
  ---
52
106
 
53
107
  ## 2. 技术契约(SDD 扩展)
@@ -1,3 +1,7 @@
1
+ ---
2
+ capability-id: "CAP-<CAPABILITY>" # 必须与对应 spec/design 一致
3
+ ---
4
+
1
5
  # 实施任务拆解 - [Capability 名称]
2
6
 
3
7
  > **定位**:单一 Capability 的 AI 编码引擎执行单元
@@ -116,10 +120,16 @@
116
120
 
117
121
  ---
118
122
 
119
- ### [TASK-XXX-01] 任务名称
123
+ ### [TASK-<CAPABILITY>-NNN] 任务名称
120
124
 
125
+ - **entity-id**: <UUID>
126
+ - **version-id**: <UUID>
127
+ - **delta-state**: added
128
+ - **predecessor-version**: 无
121
129
  - **类型**: 数据层 / 接口层 / UI层 / 测试
122
- - **依赖**:
130
+ - **implements**: DES-<CAPABILITY>-NNN
131
+ - **covers**: STMT-<CAPABILITY>-NNN
132
+ - **dependsOn**: 无
123
133
  - **状态**: [ ] 未完成
124
134
 
125
135
  #### 任务描述
@@ -146,10 +156,16 @@
146
156
 
147
157
  ---
148
158
 
149
- ### [TASK-XXX-02] 任务名称
159
+ ### [TASK-<CAPABILITY>-NNN] 任务名称
150
160
 
161
+ - **entity-id**: <UUID>
162
+ - **version-id**: <UUID>
163
+ - **delta-state**: added
164
+ - **predecessor-version**: 无
151
165
  - **类型**: 数据层 / 接口层 / UI层 / 测试
152
- - **依赖**:
166
+ - **implements**: DES-<CAPABILITY>-NNN
167
+ - **covers**: STMT-<CAPABILITY>-NNN
168
+ - **dependsOn**: 无
153
169
  - **状态**: [ ] 未完成
154
170
 
155
171
  #### 任务描述
@@ -173,10 +189,16 @@
173
189
 
174
190
  ---
175
191
 
176
- ### [TASK-XXX-03] 任务名称
192
+ ### [TASK-<CAPABILITY>-NNN] 任务名称
177
193
 
194
+ - **entity-id**: <UUID>
195
+ - **version-id**: <UUID>
196
+ - **delta-state**: added
197
+ - **predecessor-version**: 无
178
198
  - **类型**: 数据层 / 接口层 / UI层 / 测试
179
- - **依赖**: TASK-XXX-01, TASK-XXX-02
199
+ - **implements**: DES-<CAPABILITY>-NNN
200
+ - **covers**: STMT-<CAPABILITY>-NNN
201
+ - **dependsOn**: TASK-<CAPABILITY>-NNN, TASK-<CAPABILITY>-NNN
180
202
  - **状态**: [ ] 未完成
181
203
 
182
204
  #### 任务描述
@@ -107,7 +107,8 @@ node skywalk-sdd/log.cjs archive-docs --project=. --change=<变更名称> --reas
107
107
 
108
108
  该命令成功后必须已经完成:
109
109
  - 活动目录 `openspec/changes/<name>/` 被移入 `openspec/changes/archive/<日期>-<name>/`。
110
- - 归档目录写入 `archive-manifest.json`。
110
+ - 归档目录写入 `archive-ontology.json`、`canonical-facts.json`、`conversion-report.json` 和新版 `archive-manifest.json`。
111
+ - 生成知识库可直接消费的 `openspec/changes/archive/<日期>-<name>.zip`,包内文件必须由 manifest 完整列举并通过 SHA-256 校验。
111
112
  - Full Spec 的 `specs/<capability>/spec.md` 同步到 `openspec/specs/<capability>/spec.md`。
112
113
  - archive 阶段写入 `stage_end`。
113
114
  - 未勾选 tasks 被写入 `archive_result.task_completion`。
@@ -132,6 +133,8 @@ node skywalk-sdd/log.cjs end --event-id=<event_id> --command=archive --project=.
132
133
  > - 归档目录:`openspec/changes/archive/<日期>-<name>/`
133
134
  > - 最终报告(默认同时生成 .md + .html):`openspec/changes/archive/<日期>-<name>/reports/<name>-report.md`、`openspec/changes/archive/<日期>-<name>/reports/<name>-report.html`
134
135
  > - 执行日志:`openspec/changes/archive/<日期>-<name>/logs/execution-log.md`
136
+ > - 知识库归档包:`openspec/changes/archive/<日期>-<name>.zip`
137
+ > - 本体消费入口:`openspec/changes/archive/<日期>-<name>/canonical-facts.json`
135
138
  > - 未勾选任务:X 项,已记录到报告,不阻断归档
136
139
  > - 正式 specs:`openspec/specs/`
137
140
 
@@ -155,6 +158,21 @@ node skywalk-sdd/log.cjs record --type=baseline_record --command=archive --proje
155
158
 
156
159
  ---
157
160
 
161
+ ## 本体语义归档门禁
162
+
163
+ - 归档前必须运行 `node skywalk-sdd/log.cjs semantic-reconcile --project=. --change=<变更名称>`,不能只信任文件观察事件。
164
+ - 随后必须运行 `node skywalk-sdd/log.cjs semantic-check --project=. --change=<变更名称>`;有阻断诊断时禁止移动活动 Change。
165
+ - Archive 不负责首次生成 propose/spec/design/tasks 的工作态事实;它重新解析原文核对 pending revision,补充 `OntologySnapshot/project_id/archive_id` 后冻结为 confirmed。
166
+ - `archive-docs` 成功后必须在归档目录生成 `archive-ontology.json`,并将 `review_status` 标记为 `confirmed`。
167
+ - 生产端必须把 confirmed 本体转换为 `canonical-facts.json`;实体固定为 `fact_kind=semantic`、`assertion_type=asserted`、`review_status=confirmed`,关系必须补齐对应语义边界字段。
168
+ - `acceptedBy` 在消费协议中归一化为 `verifiedBy`;inferred 关系必须携带 `rule_id`、`generator` 和 `generator_version`。
169
+ - 每个 canonical 实体和关系的 `source.file` 必须存在于 Archive Package,`source.content_hash` 必须等于该原文文件的 SHA-256。
170
+ - `archive-manifest.json` 必须使用 `kld-sdd-archive-manifest/v2`,并与 canonical facts 的 project/archive/change 身份一致。
171
+ - `conversion-report.json` 必须记录源/目标 schema、转换状态、实体数、关系数和归一化告警。
172
+ - Archive 快照必须固化人工锚点、逻辑实体 UUID、版本 UUID、前序版本 UUID、delta-state 和内容哈希;任一 UUID 冲突或谱系断裂都不得 confirmed。
173
+ - 归档目录已经复制但语义快照失败时,不得删除活动 Change,不得标记 confirmed。
174
+ - unchanged 正文保留在前序 Archive;当前归档只固化差量事实、继承引用和来源锚点。
175
+
158
176
  ## Guardrails
159
177
 
160
178
  - 归档操作执行前必须让用户确认归档原因。
@@ -18,7 +18,11 @@ description: "opsx-archive 前后日志/总结自检清单 — 仅在 archive
18
18
  ## B. 归档后自检
19
19
 
20
20
  - [ ] 归档目录 `openspec/changes/archive/<日期>-<变更名称>/` 存在
21
- - [ ] `openspec/changes/archive/<日期>-<变更名称>/archive-manifest.json` 存在
21
+ - [ ] `openspec/changes/archive/<日期>-<变更名称>/` 下 `archive-ontology.json`、`canonical-facts.json`、`conversion-report.json` 和 `archive-manifest.json` 均存在
22
+ - [ ] 活动 change 目录下的 `artifacts/*.ontology.json` 与 `artifact-index.json` 已随归档目录一并迁移
23
+ - [ ] `archive-manifest.json` 为 `kld-sdd-archive-manifest/v2`,且 `files` 精确覆盖 ZIP 内除 manifest 自身外的全部文件
24
+ - [ ] `openspec/changes/archive/<日期>-<变更名称>.zip` 存在并可由知识库 `ArchivePackageReader` 读取
25
+ - [ ] canonical facts 中每个实体/关系均能通过 `source.file`、`source.anchor_id`、`source.content_hash` 定向展开到包内原文
22
26
  - [ ] 最终报告 `openspec/changes/archive/<日期>-<变更名称>/reports/<变更名称>-report.md` 存在且含「归档结果」段
23
27
  - [ ] `reports/<变更名称>-report.md` 与 `reports/<变更名称>-report.html` 都已生成(md+html 双产物)
24
28
  - [ ] html 报告含 `SDD 效果度量报告` 标题与各度量章节(执行摘要/效率/质量/过程/归档结果/说明)
@@ -200,6 +200,24 @@ node skywalk-sdd/log.cjs record --type=conformance_review --command=check --proj
200
200
 
201
201
  ---
202
202
 
203
+ ## 本体语义关系门禁
204
+
205
+ 在其他质量检查前必须运行:
206
+
207
+ ```bash
208
+ node skywalk-sdd/log.cjs semantic-reconcile --project=. --change=<变更名称> --profile=<simple|full|strict>
209
+ node skywalk-sdd/log.cjs semantic-check --project=. --change=<变更名称> --profile=<simple|full|strict>
210
+ ```
211
+
212
+ - `simple` 必须阻断 STMT 无 AC;没有 design/tasks 时跳过对应链路,不得误报。
213
+ - `full` 必须阻断 STMT→AC→Design→Task 任一断链。
214
+ - `strict` 在 full 基础上要求完整来源字段并提升可选警告。
215
+ - 所有 profile 都必须阻断重复人工锚点、UUID 缺失/非法、跨 Change 或 Archive 的实体 UUID 误复用、版本 UUID 重用、版本谱系断裂、悬空引用、非法 domain/range 和 Task 依赖成环。
216
+ - `added` 必须使用全新实体/版本 UUID;`modified/removed` 必须复用实体 UUID并指向直接前序版本;`unchanged` 必须复用历史实体和版本 UUID且不得复制历史正文。
217
+ - 文件观察结果只能作为快速上下文,check 必须重新全量 semantic-reconcile。
218
+ - propose/spec/design/task 对应工作态 JSON 应已在各作者阶段生成;check 不负责首次生成业务事实,只重新解析 Markdown、核对各 JSON 与同一 revision,并在全部通过时把派生 revision 更新为 `review_status=pending`。
219
+ - Check 只读是指不修改 proposal/spec/design/tasks 原文;允许原子刷新 `openspec/changes/<变更名称>/` 下可再生的 `working-ontology.json`、`artifact-index.json` 与 `artifacts/*.ontology.json`。
220
+
203
221
  ## Guardrails
204
222
 
205
223
  - Check 是**只读检查**操作,不修改任何文档内容
@@ -35,3 +35,5 @@ description: "opsx-check 阶段日志自检清单 — 仅在 check 自检时读
35
35
 
36
36
  - [ ] 完整性、一致性、算法正确性、可执行性四维均已输出
37
37
  - [ ] 报告问题对应修复建议(spec/design/task)
38
+ - [ ] `openspec/changes/<变更名称>/artifact-index.json` 已覆盖当前全部 proposal/spec/design/tasks,且每份 `artifacts/*.ontology.json` 与 `working-ontology.json` revision 一致
39
+ - [ ] 全部语义门禁通过时工作态 JSON 已从 draft 刷新为 pending;check 未修改任何 Markdown 原文
@@ -164,6 +164,17 @@ design.md 中若使用 version 正则约束(如格式校验 `^\d+\.\d+\.\d+$`
164
164
 
165
165
  ---
166
166
 
167
+ ## 本体语义生成契约
168
+
169
+ - 写入与当前 spec 一致的 `capability-id`。
170
+ - 每个独立设计单元必须使用 `DES-<CAPABILITY>-NNN`;新增前按当前最大序号 + 1 分配,修改时复用原 ID。
171
+ - 每个 DES 必须同时写 `entity-id`、`version-id`、`delta-state` 和按需的 `predecessor-version`;新增用 `semantic-identity --delta-state=added`,修改时复用实体 UUID 并生成新版本 UUID。
172
+ - 每个 DES 必须通过 `**realizes**: STMT-*` 显式引用一个或多个真实 STMT。
173
+ - 不得仅凭文本相似创建 realizes;没有上游 STMT 时必须标记 unresolved。
174
+ - 生成结束后必须运行 `node skywalk-sdd/log.cjs semantic-reconcile --project=. --change=<name>`。
175
+ - reconcile 必须立即生成 `openspec/changes/<name>/artifacts/.../design.ontology.json`,其中 DES 和 realizes 关系保留原文 source;不得推迟到 check/archive。
176
+ - 阶段 `stage_end` 会在无 Hook 环境下幂等执行同步兜底。
177
+
167
178
  ## Guardrails
168
179
 
169
180
  > 完整 ⛔ 强制项勾选清单见 `./checklist.md`「Guardrails ⛔ 强制项」。核心红线:
@@ -202,6 +202,18 @@ openspec instructions proposal --change "<name>" --json
202
202
 
203
203
  ---
204
204
 
205
+ ## 本体语义生成契约
206
+
207
+ - 创建 Change 时必须写入 `change-id: CHG-<CHANGE-SLUG>`;重新编辑时不得修改已有 Change ID。
208
+ - 每个新增 Capability 必须写成 `[CAP-<CAPABILITY>] <slug>: <说明>`。
209
+ - 修改既有 Capability 时必须复用已有 CAP ID,不得修改或重新分配已有实体 ID。
210
+ - 分配新 CAP ID 前必须扫描当前 proposal 和归档中的显式编号;不得只凭标题认定跨 Change 同一性。
211
+ - 每个 Change/Capability 同时写人工锚点、`entity-id`、`version-id` 和 `delta-state`。新增时必须调用 `node skywalk-sdd/log.cjs semantic-identity --delta-state=added`,不得手写或复制 UUID。
212
+ - 修改既有 Capability 时调用 `semantic-identity --delta-state=modified --entity-id=<历史实体UUID> --predecessor-version=<直接前序版本UUID>`;复用实体 UUID,但必须生成新版本 UUID。
213
+ - 本阶段只声明 Change 和 Capability,不得提前生成 STMT、AC、DES 或 TASK 事实。
214
+ - proposal.md 写入结束后必须立即运行 `node skywalk-sdd/log.cjs semantic-reconcile --project=. --change=<name>`,生成 `openspec/changes/<name>/artifacts/proposal.ontology.json`;不得等到 check 或 archive 才首次生成 JSON。
215
+ - 阶段 `stage_end` 会在无 Hook 环境下幂等执行同一次同步作为兜底,结果中的 `semantic_state.artifact_json_paths` 必须包含 proposal 对应 JSON。
216
+
205
217
  ## Guardrails
206
218
 
207
219
  > 完整 ⛔ 强制项勾选清单见 `./checklist.md`「Guardrails ⛔ 强制项」。核心红线:
@@ -39,6 +39,8 @@ description: opsx-propose 的阶段强制检查点与自检清单。仅在执行
39
39
  - [ ] Capabilities 章节是关键:决定后续 specs 文件夹结构
40
40
  - [ ] 若跳过此文档,后续 specs 必须补齐影响范围
41
41
  - [ ] 文档写入后验证文件确实存在
42
+ - [ ] proposal.md 写入后已运行 `semantic-reconcile`,`openspec/changes/<变更名称>/artifacts/proposal.ontology.json` 已存在
43
+ - [ ] 工作态 JSON 为 `canonical=false`、`review_status=draft`,实体能够通过 `source.file/source.anchor_id/source.content_hash` 展开到 proposal.md 原文
42
44
  - [ ] 每次生成都提供文档摘要,等待用户确认后再继续
43
45
  - [ ] ⛔ **阶段边界**:本阶段禁止执行任何代码创建/修改操作;用户要求处理代码时回复「当前处于 Propose 阶段,代码操作请在完成文档后使用 `/opsx-apply` 执行。」
44
46
  - [ ] ⛔ **单阶段原则**:完成 proposal.md 后必须立即停止;仅提示用户下一步可运行 `/opsx-spec`,绝对禁止自动执行 spec/design/task 等后续阶段。每个阶段必须由用户主动触发。