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.
- package/README.md +16 -12
- package/kld-sdd-guide.html +4 -5
- package/lib/init.js +14 -11
- package/package.json +2 -2
- package/skywalk-sdd/index.cjs +936 -109
- package/skywalk-sdd/metrics-v3.cjs +103 -15
- package/skywalk-sdd/ontology/archive-package.cjs +6 -0
- package/skywalk-sdd/ontology/identity-index.cjs +9 -2
- package/skywalk-sdd/ontology/ontology-paths.cjs +73 -0
- package/skywalk-sdd/ontology/runtime.cjs +32 -22
- package/skywalk-sdd/ontology/structural-identity.cjs +11 -2
- package/skywalk-sdd/ontology/traceability-validator.cjs +16 -8
- package/skywalk-sdd/ontology/working-artifacts.cjs +2 -1
- package/skywalk-sdd/reporting/change-report-markdown.cjs +294 -0
- package/skywalk-sdd/reporting/change-report-model.cjs +452 -0
- package/skywalk-sdd/reporting/change-report-renderer.cjs +349 -0
- package/skywalk-sdd/reporting/change-report-view-model.cjs +340 -0
- package/skywalk-sdd/runtime-metadata.cjs +21 -0
- package/templates/skills/kld-sdd/opsx-apply/reference.md +4 -2
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +1 -1
- package/templates/skills/kld-sdd/opsx-archive/checklist.md +1 -1
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +2 -2
- package/templates/skills/kld-sdd/opsx-check/checklist.md +1 -1
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +14 -0
- package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +31 -0
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +6 -6
- package/templates/skills/kld-sdd/opsx-propose/reference.md +6 -7
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +1 -1
- package/templates/skills/kld-sdd/tdd-core/reference.md +3 -1
- 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/
|
|
4
|
-
const METRICS_CONTRACT_VERSION = '
|
|
5
|
-
const
|
|
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
|
-
|
|
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
|
|
854
|
-
|
|
855
|
-
|
|
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(
|
|
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:
|
|
926
|
-
evidence:
|
|
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:
|
|
932
|
-
evidence:
|
|
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:
|
|
938
|
-
evidence:
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
167
|
-
const existing = readJson(
|
|
168
|
+
if (fs.existsSync(readablePaths.working)) {
|
|
169
|
+
const existing = readJson(readablePaths.working, 'SEM_STATE_CORRUPT');
|
|
168
170
|
if (existing.revision === result.revision) {
|
|
169
|
-
readRevisionBundle(
|
|
170
|
-
const artifactIndex = readJson(
|
|
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:
|
|
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:
|
|
278
|
+
continuityResolutionPath: resolveOntologyStateFile(
|
|
276
279
|
parsed.changeDir,
|
|
277
|
-
|
|
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(
|
|
370
|
+
return readRevisionBundle(readablePaths, pointer.revision).working;
|
|
367
371
|
}
|
|
368
|
-
if (!fs.existsSync(
|
|
369
|
-
const working = readJson(
|
|
370
|
-
readRevisionBundle(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
?
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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 ||
|
|
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:
|
|
232
|
+
working_ontology_path: ontologyStateRelative(ONTOLOGY_STATE_FILES.working),
|
|
232
233
|
artifacts: artifactIndex,
|
|
233
234
|
},
|
|
234
235
|
files: artifactFiles,
|