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
@@ -1,10 +1,30 @@
1
1
  'use strict';
2
2
 
3
- const REPORT_SCHEMA_VERSION = 'sdd-efficiency-report/v3';
4
- const METRICS_CONTRACT_VERSION = '3.0';
5
- const CHANGE_TYPES = ['config', 'document', 'report', 'composite', 'other'];
3
+ const REPORT_SCHEMA_VERSION = 'sdd-efficiency-report/v4';
4
+ const METRICS_CONTRACT_VERSION = '4.0';
5
+ const SCHEME_VERSION = 'V3.0';
6
+ /** Writable change types (scheme 5.3). Legacy document/other read as unknown. */
7
+ const CHANGE_TYPES = ['config', 'transaction', 'report', 'composite'];
8
+ const LEGACY_CHANGE_TYPES = ['document', 'other'];
6
9
  const MINIMUM_METRICS = ['e1', 'q3', 'p4', 'p_r', 'p_h'];
7
10
 
11
+ /**
12
+ * Format artifact byte size: <1MiB → KB, else MB; two decimals; never bare B.
13
+ * @param {number} bytes
14
+ * @returns {string}
15
+ */
16
+ function formatArtifactSize(bytes) {
17
+ const n = Number(bytes);
18
+ if (!Number.isFinite(n) || n < 0) return '0.00 KB';
19
+ const kib = 1024;
20
+ const mib = 1024 * 1024;
21
+ if (n < mib) {
22
+ const kb = n / kib;
23
+ return `${kb.toFixed(2)} KB`;
24
+ }
25
+ return `${(n / mib).toFixed(2)} MB`;
26
+ }
27
+
8
28
  const ALERT_THRESHOLDS = {
9
29
  q3: { red: 70, yellow: 85, direction: 'higher_better' },
10
30
  p4: { red: 0.4, yellow: 0.6, direction: 'higher_better' },
@@ -126,7 +146,27 @@ function countScenariosInMarkdown(text) {
126
146
  function validateChangeType(value) {
127
147
  if (typeof value !== 'string') return 'unknown';
128
148
  const normalized = value.trim().toLowerCase();
129
- return CHANGE_TYPES.includes(normalized) ? normalized : 'unknown';
149
+ if (CHANGE_TYPES.includes(normalized)) return normalized;
150
+ if (LEGACY_CHANGE_TYPES.includes(normalized)) return 'unknown';
151
+ return 'unknown';
152
+ }
153
+
154
+ function diagnoseChangeType(value) {
155
+ if (typeof value !== 'string') {
156
+ return { change_type: 'unknown', legacy_change_type: null, warning: null };
157
+ }
158
+ const normalized = value.trim().toLowerCase();
159
+ if (CHANGE_TYPES.includes(normalized)) {
160
+ return { change_type: normalized, legacy_change_type: null, warning: null };
161
+ }
162
+ if (LEGACY_CHANGE_TYPES.includes(normalized)) {
163
+ return {
164
+ change_type: 'unknown',
165
+ legacy_change_type: normalized,
166
+ warning: `legacy_change_type:${normalized}`,
167
+ };
168
+ }
169
+ return { change_type: 'unknown', legacy_change_type: null, warning: 'invalid_change_type' };
130
170
  }
131
171
 
132
172
  function resolveReviewerIndependence(meta = {}) {
@@ -850,9 +890,41 @@ function buildSchemeCompliance(input = {}) {
850
890
  const minimumStatus = minimum.numeric_available === minimum.numeric_total
851
891
  ? (minimum.trusted_available === minimum.numeric_total ? 'implemented' : 'provisional')
852
892
  : 'provisional';
853
- const q1Status = quality.q1_status || quality.q1_spec_conformance?.status || 'pending-upstream';
854
- const q4Status = quality.q4_status || quality.q4_tdd_coverage?.status || 'pending-upstream';
855
- const q3Status = quality.q3_score_status === 'verified' ? 'implemented' : 'provisional';
893
+ const q1Raw = quality.q1_human_status
894
+ || quality.q1_status
895
+ || quality.q1_spec_conformance?.status
896
+ || 'pending-upstream';
897
+ const q1Status = (q1Raw === 'unverified' || q1Raw === 'self-review')
898
+ ? 'provisional'
899
+ : (q1Raw === 'pending-upstream' ? 'pending-upstream' : q1Raw);
900
+ const q4Raw = quality.q4_status || quality.q4_tdd_coverage?.status || 'pending-upstream';
901
+ const q4Source = quality.q4_source || quality.q4_tdd_coverage?.source || null;
902
+ const q4Status = q4Source === 'conformance-fallback' || q4Raw === 'conformance-fallback'
903
+ ? 'pending-upstream'
904
+ : q4Raw;
905
+ const q3Independence = quality.q3_reviewer_independence || 'unknown';
906
+ const q3Status = quality.q3_score_status === 'verified'
907
+ && q3Independence === 'independent-review'
908
+ ? 'implemented'
909
+ : 'provisional';
910
+ const semanticParity = input.semantic_parity_status || input.semantic_parity || null;
911
+ const multiFormatStatus = semanticParity === 'pass'
912
+ ? 'implemented'
913
+ : (['md', 'html', 'json'].every(format => formats.includes(format)) ? 'provisional' : 'provisional');
914
+ const alertLoopReady = Boolean(
915
+ input.alert_loop
916
+ && input.alert_loop.root_cause
917
+ && input.alert_loop.owner
918
+ && input.alert_loop.due
919
+ && input.alert_loop.expected_impact
920
+ && input.alert_loop.two_cycle_evidence,
921
+ );
922
+ const eventContractHealth = input.evidence_contract_health
923
+ || input.event_contract_health
924
+ || null;
925
+ const reproducibleStatus = eventContractHealth === 'healthy' && Number(input.event_count) > 0
926
+ ? 'implemented'
927
+ : (Number(input.event_count) > 0 ? 'provisional' : 'provisional');
856
928
  const statusLabel = status => ({
857
929
  implemented: '已实现',
858
930
  provisional: '部分可用',
@@ -864,6 +936,7 @@ function buildSchemeCompliance(input = {}) {
864
936
  unknown: '未知',
865
937
  'independent-review': '独立复核',
866
938
  'self-review': '自评',
939
+ unverified: '未人工验证',
867
940
  }[status] || status || '未知');
868
941
 
869
942
  const items = [
@@ -886,7 +959,7 @@ function buildSchemeCompliance(input = {}) {
886
959
  label: '条件指标(Q1、Q4)',
887
960
  status: q1Status === 'pending-upstream' || q4Status === 'pending-upstream'
888
961
  ? 'pending-upstream'
889
- : 'implemented',
962
+ : (q1Status === 'provisional' || q4Status === 'provisional' ? 'provisional' : 'implemented'),
890
963
  evidence: `Q1:${statusLabel(q1Status)};Q4:${statusLabel(q4Status)}`,
891
964
  },
892
965
  {
@@ -899,7 +972,7 @@ function buildSchemeCompliance(input = {}) {
899
972
  requirement: 'independent-reviewer',
900
973
  label: '独立复核',
901
974
  status: q3Status,
902
- evidence: `Q3:${statusLabel(quality.q3_score_status || 'provisional')};复核方式:${statusLabel(quality.q3_reviewer_independence || 'unknown')}`,
975
+ evidence: `Q3:${statusLabel(quality.q3_score_status || 'provisional')};复核方式:${statusLabel(q3Independence)}`,
903
976
  },
904
977
  {
905
978
  requirement: 'tracking-periods',
@@ -922,20 +995,30 @@ function buildSchemeCompliance(input = {}) {
922
995
  {
923
996
  requirement: 'alert-feedback-loop',
924
997
  label: '指标与证据告警闭环',
925
- status: input.evidence_alerts ? 'implemented' : 'provisional',
926
- evidence: input.evidence_alerts ? '稳定问题编号、未解决/已解决状态和历史记录' : '当前模型未提供告警生命周期',
998
+ status: alertLoopReady ? 'implemented' : 'provisional',
999
+ evidence: alertLoopReady
1000
+ ? '具备根因、负责人、期限、预期影响与两周期验证'
1001
+ : '仍缺根因/负责人/期限/预期影响/两周期验证中的完整字段',
927
1002
  },
928
1003
  {
929
1004
  requirement: 'multi-format-same-model',
930
1005
  label: 'Markdown、HTML、JSON 同源',
931
- status: ['md', 'html', 'json'].every(format => formats.includes(format)) ? 'implemented' : 'provisional',
932
- evidence: formats.length > 0 ? formats.map(format => ({ md: 'Markdown', html: 'HTML', json: 'JSON' }[format] || format)).join(' / ') : '格式信息未提供',
1006
+ status: multiFormatStatus,
1007
+ evidence: semanticParity === 'pass'
1008
+ ? '三格式语义对账通过'
1009
+ : (formats.length > 0
1010
+ ? `${formats.map(format => ({ md: 'Markdown', html: 'HTML', json: 'JSON' }[format] || format)).join(' / ')} 已产出,语义对账未确认`
1011
+ : '格式信息未提供'),
933
1012
  },
934
1013
  {
935
1014
  requirement: 'reproducible-auditable',
936
1015
  label: '可复算、可审计',
937
- status: input.event_count > 0 ? 'implemented' : 'provisional',
938
- evidence: input.event_count > 0 ? `${input.event_count} 条 JSONL 事件` : '未提供事件数量',
1016
+ status: reproducibleStatus,
1017
+ evidence: eventContractHealth === 'healthy'
1018
+ ? `${input.event_count} 条事件且证据契约健康`
1019
+ : (input.event_count > 0
1020
+ ? `${input.event_count} 条 JSONL 事件;证据契约健康度未达标`
1021
+ : '未提供事件数量'),
939
1022
  },
940
1023
  ];
941
1024
  const counts = {};
@@ -945,6 +1028,7 @@ function buildSchemeCompliance(input = {}) {
945
1028
  version: 'V3.0',
946
1029
  items,
947
1030
  counts,
1031
+ evidence_contract_health: eventContractHealth || null,
948
1032
  };
949
1033
  }
950
1034
 
@@ -1125,7 +1209,9 @@ function deriveSuccessfulReworkReason(attemptContext = {}) {
1125
1209
  module.exports = {
1126
1210
  REPORT_SCHEMA_VERSION,
1127
1211
  METRICS_CONTRACT_VERSION,
1212
+ SCHEME_VERSION,
1128
1213
  CHANGE_TYPES,
1214
+ LEGACY_CHANGE_TYPES,
1129
1215
  ALERT_THRESHOLDS,
1130
1216
  MINIMUM_METRICS,
1131
1217
  clamp,
@@ -1148,6 +1234,8 @@ module.exports = {
1148
1234
  findDevelopmentActivities,
1149
1235
  deriveSuccessfulReworkReason,
1150
1236
  validateChangeType,
1237
+ diagnoseChangeType,
1238
+ formatArtifactSize,
1151
1239
  createInvalidArgError,
1152
1240
  countScenariosInMarkdown,
1153
1241
  };
@@ -65,10 +65,16 @@ function ensureProjectIdentity(projectRoot) {
65
65
  }
66
66
  return existing;
67
67
  }
68
+ // 兜底:project-identity.json 未通过 opsx-ontology-query 配置 KB 时创建
69
+ // 此时 project_id 为随机值,KB 入库时会因 project_id !== spaceKey 而失败
70
+ console.warn('⚠️ project-identity.json 不存在 — 未通过 opsx-ontology-query 配置 KB。');
71
+ console.warn('⚠️ 将生成临时 project_id,KB 入库时需确保与 Space spaceKey 一致。');
72
+ console.warn('⚠️ 建议在首次使用 opsx-ontology-query 配置 KB 时自动创建正确的 project-identity.json。');
68
73
  const identity = {
69
74
  schema_version: PROJECT_IDENTITY_SCHEMA_VERSION,
70
75
  project_id: `urn:kld:sdd:project:${generateUuidV7()}`,
71
76
  created_at: new Date().toISOString(),
77
+ _warning: 'UNCONFIGURED — 请通过 opsx-ontology-query 配置 KB 后更新 project_id 为 Space spaceKey',
72
78
  };
73
79
  writeJson(identityPath, identity);
74
80
  return identity;
@@ -5,6 +5,10 @@ const path = require('path');
5
5
  const { parseChangeArtifacts } = require('./artifact-parser.cjs');
6
6
  const { normalizeFacts } = require('./normalizer.cjs');
7
7
  const { DIAGNOSTIC_CODES } = require('./schema.cjs');
8
+ const {
9
+ ONTOLOGY_STATE_FILES,
10
+ resolveOntologyStateFile,
11
+ } = require('./ontology-paths.cjs');
8
12
 
9
13
  function catalogDiagnostic(code, message, context = {}) {
10
14
  return {
@@ -97,11 +101,14 @@ function validateArchiveCandidate(candidate, facts) {
97
101
  return { eligible: false, snapshot, manifest, diagnostics };
98
102
  }
99
103
 
100
- const artifactIndexPath = path.join(candidate.changeDir, 'artifact-index.json');
104
+ const artifactIndexPath = resolveOntologyStateFile(
105
+ candidate.changeDir,
106
+ ONTOLOGY_STATE_FILES.artifactIndex,
107
+ );
101
108
  if (!fs.existsSync(artifactIndexPath)) {
102
109
  diagnostics.push(catalogDiagnostic(
103
110
  DIAGNOSTIC_CODES.ARCHIVE_NOT_CONFIRMED,
104
- `Archive 缺少 change 目录下的 artifact-index.json: ${candidate.change}`,
111
+ `Archive 缺少 change 目录下的 ontology/artifact-index.json: ${candidate.change}`,
105
112
  { file: artifactIndexPath, suggestion: '通过 KLD-SDD reconcile/archive 在 change 目录生成工作态 JSON 后再归档' },
106
113
  ));
107
114
  return { eligible: false, snapshot, manifest, diagnostics };
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const ONTOLOGY_STATE_DIR = 'ontology';
7
+
8
+ const ONTOLOGY_STATE_FILES = {
9
+ working: 'working-ontology.json',
10
+ diagnostics: 'diagnostics.json',
11
+ fileIndex: 'file-index.json',
12
+ artifactIndex: 'artifact-index.json',
13
+ identities: 'ontology-identities.json',
14
+ continuityResolution: 'continuity-resolution.json',
15
+ };
16
+
17
+ function ontologyStateRelative(basename) {
18
+ return `${ONTOLOGY_STATE_DIR}/${basename}`;
19
+ }
20
+
21
+ function changeOntologyFilePaths(changeDir) {
22
+ const resolved = path.resolve(changeDir);
23
+ const ontologyDir = path.join(resolved, ONTOLOGY_STATE_DIR);
24
+ return {
25
+ changeDir: resolved,
26
+ ontologyDir,
27
+ working: path.join(ontologyDir, ONTOLOGY_STATE_FILES.working),
28
+ diagnostics: path.join(ontologyDir, ONTOLOGY_STATE_FILES.diagnostics),
29
+ fileIndex: path.join(ontologyDir, ONTOLOGY_STATE_FILES.fileIndex),
30
+ artifactIndex: path.join(ontologyDir, ONTOLOGY_STATE_FILES.artifactIndex),
31
+ continuityResolution: path.join(ontologyDir, ONTOLOGY_STATE_FILES.continuityResolution),
32
+ identities: path.join(ontologyDir, ONTOLOGY_STATE_FILES.identities),
33
+ artifactDir: path.join(resolved, 'artifacts'),
34
+ };
35
+ }
36
+
37
+ function resolveOntologyStateFile(changeDir, basename) {
38
+ const resolved = path.resolve(changeDir);
39
+ const modern = path.join(resolved, ONTOLOGY_STATE_DIR, basename);
40
+ if (fs.existsSync(modern)) return modern;
41
+ const legacy = path.join(resolved, basename);
42
+ if (fs.existsSync(legacy)) return legacy;
43
+ return modern;
44
+ }
45
+
46
+ function resolveRevisionBundlePaths(changeDir) {
47
+ const preferred = changeOntologyFilePaths(changeDir);
48
+ return {
49
+ ...preferred,
50
+ working: resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.working),
51
+ diagnostics: resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.diagnostics),
52
+ fileIndex: resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.fileIndex),
53
+ artifactIndex: resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.artifactIndex),
54
+ };
55
+ }
56
+
57
+ function removeLegacyOntologyStateFiles(changeDir) {
58
+ const resolved = path.resolve(changeDir);
59
+ for (const basename of Object.values(ONTOLOGY_STATE_FILES)) {
60
+ const legacy = path.join(resolved, basename);
61
+ if (fs.existsSync(legacy)) fs.rmSync(legacy, { force: true });
62
+ }
63
+ }
64
+
65
+ module.exports = {
66
+ ONTOLOGY_STATE_DIR,
67
+ ONTOLOGY_STATE_FILES,
68
+ ontologyStateRelative,
69
+ changeOntologyFilePaths,
70
+ resolveOntologyStateFile,
71
+ resolveRevisionBundlePaths,
72
+ removeLegacyOntologyStateFiles,
73
+ };
@@ -20,6 +20,14 @@ const {
20
20
  assertChangeLock,
21
21
  releaseChangeLock,
22
22
  } = require('./change-lock.cjs');
23
+ const {
24
+ ONTOLOGY_STATE_FILES,
25
+ ontologyStateRelative,
26
+ changeOntologyFilePaths,
27
+ resolveOntologyStateFile,
28
+ resolveRevisionBundlePaths,
29
+ removeLegacyOntologyStateFiles,
30
+ } = require('./ontology-paths.cjs');
23
31
 
24
32
  function sha256(value) {
25
33
  return crypto.createHash('sha256').update(value).digest('hex');
@@ -31,15 +39,7 @@ function resolveChangeDir(projectRoot, changeName) {
31
39
  }
32
40
 
33
41
  function changeOntologyPaths(projectRoot, changeName) {
34
- const changeDir = resolveChangeDir(projectRoot, changeName);
35
- return {
36
- changeDir,
37
- working: path.join(changeDir, 'working-ontology.json'),
38
- diagnostics: path.join(changeDir, 'diagnostics.json'),
39
- fileIndex: path.join(changeDir, 'file-index.json'),
40
- artifactIndex: path.join(changeDir, 'artifact-index.json'),
41
- artifactDir: path.join(changeDir, 'artifacts'),
42
- };
42
+ return changeOntologyFilePaths(resolveChangeDir(projectRoot, changeName));
43
43
  }
44
44
 
45
45
  function statePaths(projectRoot, changeName) {
@@ -160,14 +160,16 @@ function commitRevision(projectRoot, changeName, result, lock) {
160
160
  assertChangeLock(lock);
161
161
  const basePaths = statePaths(projectRoot, changeName);
162
162
  const paths = changeOntologyPaths(projectRoot, changeName);
163
+ const readablePaths = resolveRevisionBundlePaths(paths.changeDir);
163
164
  fs.mkdirSync(basePaths.dir, { recursive: true });
164
165
  fs.mkdirSync(paths.changeDir, { recursive: true });
166
+ fs.mkdirSync(paths.ontologyDir, { recursive: true });
165
167
 
166
- if (fs.existsSync(paths.working)) {
167
- const existing = readJson(paths.working, 'SEM_STATE_CORRUPT');
168
+ if (fs.existsSync(readablePaths.working)) {
169
+ const existing = readJson(readablePaths.working, 'SEM_STATE_CORRUPT');
168
170
  if (existing.revision === result.revision) {
169
- readRevisionBundle(paths, result.revision);
170
- const artifactIndex = readJson(paths.artifactIndex, 'SEM_STATE_CORRUPT');
171
+ readRevisionBundle(readablePaths, result.revision);
172
+ const artifactIndex = readJson(readablePaths.artifactIndex, 'SEM_STATE_CORRUPT');
171
173
  return {
172
174
  changed: false,
173
175
  paths: {
@@ -225,6 +227,7 @@ function commitRevision(projectRoot, changeName, result, lock) {
225
227
  atomicWriteIfChanged(path.join(paths.changeDir, artifactFile.path), fs.readFileSync(stagedPath, 'utf8'));
226
228
  }
227
229
  pruneStaleArtifacts(paths.changeDir, artifactBundle.index);
230
+ removeLegacyOntologyStateFiles(paths.changeDir);
228
231
  fs.rmSync(stagingDir, { recursive: true, force: true });
229
232
 
230
233
  const pointer = {
@@ -232,7 +235,7 @@ function commitRevision(projectRoot, changeName, result, lock) {
232
235
  change: safeChangeName(changeName),
233
236
  revision: result.revision,
234
237
  change_dir: path.relative(path.resolve(projectRoot), paths.changeDir).replace(/\\/g, '/'),
235
- artifact_index: 'artifact-index.json',
238
+ artifact_index: ontologyStateRelative(ONTOLOGY_STATE_FILES.artifactIndex),
236
239
  };
237
240
  assertChangeLock(lock);
238
241
  const changed = atomicWriteIfChanged(basePaths.current, pointer);
@@ -272,9 +275,9 @@ function scanChange(projectRoot, changeName, options = {}) {
272
275
  identityHistory,
273
276
  effectiveGraph,
274
277
  projectRoot,
275
- continuityResolutionPath: path.join(
278
+ continuityResolutionPath: resolveOntologyStateFile(
276
279
  parsed.changeDir,
277
- 'continuity-resolution.json',
280
+ ONTOLOGY_STATE_FILES.continuityResolution,
278
281
  ),
279
282
  });
280
283
  const reviewStatus = options.markPending && validation.valid ? 'pending' : 'draft';
@@ -358,16 +361,17 @@ function reconcileChange(projectRoot, changeName, options = {}) {
358
361
  function readWorkingState(projectRoot, changeName) {
359
362
  const basePaths = statePaths(projectRoot, changeName);
360
363
  const paths = changeOntologyPaths(projectRoot, changeName);
364
+ const readablePaths = resolveRevisionBundlePaths(paths.changeDir);
361
365
  if (fs.existsSync(basePaths.current)) {
362
366
  const pointer = readJson(basePaths.current, 'SEM_STATE_POINTER_CORRUPT');
363
367
  if (!pointer.revision || pointer.change !== safeChangeName(changeName)) {
364
368
  throw stateError('SEM_STATE_POINTER_CORRUPT', `current.json 不属于 Change ${changeName}`);
365
369
  }
366
- return readRevisionBundle(paths, pointer.revision).working;
370
+ return readRevisionBundle(readablePaths, pointer.revision).working;
367
371
  }
368
- if (!fs.existsSync(paths.working)) return null;
369
- const working = readJson(paths.working, 'SEM_STATE_CORRUPT');
370
- readRevisionBundle(paths, working.revision);
372
+ if (!fs.existsSync(readablePaths.working)) return null;
373
+ const working = readJson(readablePaths.working, 'SEM_STATE_CORRUPT');
374
+ readRevisionBundle(readablePaths, working.revision);
371
375
  return working;
372
376
  }
373
377
 
@@ -396,9 +400,12 @@ function createArchiveSnapshot(projectRoot, changeName, archiveDir, inputState)
396
400
  if (!state.facts_hash || state.facts_hash !== actualFacts.facts_hash) {
397
401
  throw new Error(`归档正文与待确认本体事实不一致: ${changeName}`);
398
402
  }
399
- const artifactIndexPath = path.join(targetDir, 'artifact-index.json');
403
+ const artifactIndexPath = resolveOntologyStateFile(
404
+ targetDir,
405
+ ONTOLOGY_STATE_FILES.artifactIndex,
406
+ );
400
407
  if (!fs.existsSync(artifactIndexPath)) {
401
- throw new Error(`归档目录缺少 artifact-index.json: ${targetDir}`);
408
+ throw new Error(`归档目录缺少 ontology/artifact-index.json: ${targetDir}`);
402
409
  }
403
410
  const artifactIndex = readJson(artifactIndexPath, 'SEM_STATE_CORRUPT');
404
411
  if (artifactIndex.revision !== state.revision) {
@@ -468,4 +475,7 @@ module.exports = {
468
475
  reconcileChange,
469
476
  readWorkingState,
470
477
  createArchiveSnapshot,
478
+ resolveOntologyStateFile,
479
+ resolveRevisionBundlePaths,
480
+ ONTOLOGY_STATE_FILES,
471
481
  };
@@ -4,15 +4,20 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { SCHEMA_VERSION } = require('./schema.cjs');
6
6
  const { generateUuidV7, isValidUuidV7 } = require('./id.cjs');
7
+ const {
8
+ ONTOLOGY_STATE_FILES,
9
+ ontologyStateRelative,
10
+ resolveOntologyStateFile,
11
+ } = require('./ontology-paths.cjs');
7
12
 
8
- const REGISTRY_FILE = 'ontology-identities.json';
13
+ const REGISTRY_FILE = ontologyStateRelative(ONTOLOGY_STATE_FILES.identities);
9
14
 
10
15
  function registryPath(changeDir) {
11
16
  return path.join(path.resolve(changeDir), REGISTRY_FILE);
12
17
  }
13
18
 
14
19
  function readRegistry(changeDir) {
15
- const filePath = registryPath(changeDir);
20
+ const filePath = resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.identities);
16
21
  if (!fs.existsSync(filePath)) {
17
22
  return { schema_version: SCHEMA_VERSION, identities: {} };
18
23
  }
@@ -35,6 +40,10 @@ function writeRegistry(changeDir, registry) {
35
40
  const tempPath = `${filePath}.${process.pid}.tmp`;
36
41
  fs.writeFileSync(tempPath, content, 'utf8');
37
42
  fs.renameSync(tempPath, filePath);
43
+ const legacy = path.join(path.resolve(changeDir), ONTOLOGY_STATE_FILES.identities);
44
+ if (legacy !== filePath && fs.existsSync(legacy)) {
45
+ fs.rmSync(legacy, { force: true });
46
+ }
38
47
  return true;
39
48
  }
40
49
 
@@ -1,11 +1,18 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
4
+ const fs = require('fs');
3
5
  const {
4
6
  DIAGNOSTIC_CODES,
5
7
  normalizeProfile,
6
8
  relationDefinition,
7
9
  } = require('./schema.cjs');
8
10
  const { isValidEntityId, isValidUuid } = require('./id.cjs');
11
+ const {
12
+ ONTOLOGY_STATE_FILES,
13
+ ontologyStateRelative,
14
+ resolveOntologyStateFile,
15
+ } = require('./ontology-paths.cjs');
9
16
 
10
17
  function diagnostic(code, severity, message, context = {}) {
11
18
  return {
@@ -350,18 +357,19 @@ function validateContinuityAndExternalRefs(facts, diagnostics, options = {}) {
350
357
  const kind = String(continuity.kind || '').toLowerCase();
351
358
  const resolutionPath = options.continuityResolutionPath
352
359
  || (facts.change
353
- ? require('path').join(
354
- options.projectRoot || process.cwd(),
355
- 'openspec',
356
- 'changes',
357
- facts.change,
358
- 'continuity-resolution.json',
360
+ ? resolveOntologyStateFile(
361
+ path.join(
362
+ options.projectRoot || process.cwd(),
363
+ 'openspec',
364
+ 'changes',
365
+ facts.change,
366
+ ),
367
+ ONTOLOGY_STATE_FILES.continuityResolution,
359
368
  )
360
369
  : '');
361
370
  let resolution = null;
362
371
  if (resolutionPath) {
363
372
  try {
364
- const fs = require('fs');
365
373
  if (fs.existsSync(resolutionPath)) {
366
374
  resolution = JSON.parse(fs.readFileSync(resolutionPath, 'utf8'));
367
375
  }
@@ -392,7 +400,7 @@ function validateContinuityAndExternalRefs(facts, diagnostics, options = {}) {
392
400
  'error',
393
401
  'Continuity=iteration 但 continuity-resolution.json 缺失或仍为 pending',
394
402
  {
395
- file: resolutionPath || 'continuity-resolution.json',
403
+ file: resolutionPath || ontologyStateRelative(ONTOLOGY_STATE_FILES.continuityResolution),
396
404
  suggestion: '在 propose/spec 完成 A/B/C 决议后再 check',
397
405
  },
398
406
  ));
@@ -3,6 +3,7 @@
3
3
  const crypto = require('crypto');
4
4
  const path = require('path');
5
5
  const { resolveRuntimeMetadata } = require('../runtime-metadata.cjs');
6
+ const { ontologyStateRelative, ONTOLOGY_STATE_FILES } = require('./ontology-paths.cjs');
6
7
 
7
8
  const WORKING_ARTIFACT_SCHEMA_VERSION = 'kld-sdd-working-artifact-facts/v1';
8
9
  const ARTIFACT_INDEX_SCHEMA_VERSION = 'kld-sdd-working-artifact-index/v1';
@@ -228,7 +229,7 @@ function buildWorkingArtifactFacts(result, options = {}) {
228
229
  review_status: reviewStatus,
229
230
  valid: Boolean(result.valid),
230
231
  revision: result.revision,
231
- working_ontology_path: 'working-ontology.json',
232
+ working_ontology_path: ontologyStateRelative(ONTOLOGY_STATE_FILES.working),
232
233
  artifacts: artifactIndex,
233
234
  },
234
235
  files: artifactFiles,