kld-sdd 2.6.13 → 2.6.14
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 +9 -7
- package/kld-sdd-guide.html +4 -5
- package/lib/init.js +8 -11
- package/package.json +1 -1
- 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-model.cjs +309 -0
- package/skywalk-sdd/reporting/change-report-renderer.cjs +425 -0
- package/skywalk-sdd/runtime-metadata.cjs +21 -0
- 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 +3 -3
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ node skywalk-sdd/log.cjs semantic-status --project=. --change=<name>
|
|
|
69
69
|
node skywalk-sdd/log.cjs semantic-observe --project=. --change=<name>
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
`propose/spec/design/task` 在文档写入完成时就必须同步工作态 JSON,不等待 Check 或 Archive。各阶段的 `stage_end` 会自动执行一次 reconcile 作为无 Hook 环境的兜底;Skill 中的显式 `semantic-reconcile` 与该兜底幂等。每份 Markdown 的结构化结果写入 `openspec/changes/<name>/artifacts/<原相对路径>.ontology.json
|
|
72
|
+
`propose/spec/design/task` 在文档写入完成时就必须同步工作态 JSON,不等待 Check 或 Archive。各阶段的 `stage_end` 会自动执行一次 reconcile 作为无 Hook 环境的兜底;Skill 中的显式 `semantic-reconcile` 与该兜底幂等。每份 Markdown 的结构化结果写入 `openspec/changes/<name>/artifacts/<原相对路径>.ontology.json`,聚合索引与全量工作态写入 `openspec/changes/<name>/ontology/`(含 `artifact-index.json`、`working-ontology.json` 等);这些文件随 change 目录一起提交和归档。`skywalk-sdd/state/ontology/` 仅保留 revision 指针与跨进程锁,默认不提交。
|
|
73
73
|
|
|
74
74
|
工作态 JSON 使用 `kld-sdd-working-artifact-facts/v1`,明确标记 `canonical=false` 和 `review_status=draft|pending`。它用于当前 Change 的渐进式展开、定向查询和跨文档校验,不能直接入知识库。Check 重新解析原文、校验并在全部通过时生成 pending revision;Archive 再次对账后才确认、转换为 `canonical-facts.json` 并打包。
|
|
75
75
|
|
|
@@ -83,7 +83,7 @@ node skywalk-sdd/log.cjs semantic-observe --project=. --change=<name>
|
|
|
83
83
|
| `full` | 强制 STMT→AC→Design→Task 全链和 Task DAG |
|
|
84
84
|
| `strict` | 在 full 基础上要求完整语义来源 |
|
|
85
85
|
|
|
86
|
-
同一 Change 的作者阶段同步、observe、reconcile、check 和 archive 共用跨进程事务锁。`working-ontology.json`、diagnostics、file index、artifact index 及所有分文件 JSON 在 change 目录内原子提交,随后切换 `skywalk-sdd/state/ontology/<change>/current.json` 指针;读取时会拒绝任一文件缺失或 revision 撕裂。
|
|
86
|
+
同一 Change 的作者阶段同步、observe、reconcile、check 和 archive 共用跨进程事务锁。`ontology/working-ontology.json`、diagnostics、file index、artifact index 及所有分文件 JSON 在 change 目录内原子提交,随后切换 `skywalk-sdd/state/ontology/<change>/current.json` 指针;读取时会拒绝任一文件缺失或 revision 撕裂。
|
|
87
87
|
|
|
88
88
|
Archive 使用两阶段事务:先复制到 staging,从 staging 重新解析正文并生成本体快照、canonical facts、转换报告和 v2 manifest,所有步骤成功后才切换为正式 archive。归档完成后同时生成相邻的 `<日期>-<name>.zip`,其中 `archive-manifest.json` 的文件清单和 SHA-256 必须精确覆盖包内文件,可直接交给知识库消费。语义校验、快照、协议转换、打包或 Spec 同步失败时,活动 Change 会保留或恢复,不留下伪 confirmed 半成品。
|
|
89
89
|
|
|
@@ -286,11 +286,13 @@ your-project/
|
|
|
286
286
|
│ └── changes/
|
|
287
287
|
│ └── <name>/
|
|
288
288
|
│ ├── proposal.md
|
|
289
|
-
│ ├──
|
|
290
|
-
│ ├──
|
|
291
|
-
│ ├──
|
|
292
|
-
│ ├──
|
|
293
|
-
│ ├──
|
|
289
|
+
│ ├── ontology/
|
|
290
|
+
│ │ ├── working-ontology.json
|
|
291
|
+
│ │ ├── artifact-index.json
|
|
292
|
+
│ │ ├── diagnostics.json
|
|
293
|
+
│ │ ├── file-index.json
|
|
294
|
+
│ │ ├── ontology-identities.json
|
|
295
|
+
│ │ └── continuity-resolution.json
|
|
294
296
|
│ └── artifacts/
|
|
295
297
|
│ ├── proposal.ontology.json
|
|
296
298
|
│ └── specs/<capability>/
|
package/kld-sdd-guide.html
CHANGED
|
@@ -680,7 +680,7 @@ npx kld-sdd --version">复制</button>
|
|
|
680
680
|
<div class="top"><span class="idx">01</span><h3>opsx-propose <span class="arg"><变更名称></span></h3><span class="role">Why · 业务意图</span></div>
|
|
681
681
|
<p>创建业务意图文档,明确“为什么做”。除痛点、目标、影响模块与约束外,还会让你确认 <b>Full / Simple 模式、测试策略和 change-type</b>(config / document / report / composite / other),并写入稳定的 <code>change-id</code>。</p>
|
|
682
682
|
<p>若提供 REQ / FEAT 外部编号,会通过 <code>opsx-ontology-query</code> 做 Continuity 查询,识别新增、延续或迭代能力;不会把本地 archive 当成跨迭代权威。</p>
|
|
683
|
-
<div class="out">proposal.md + artifacts/proposal.ontology.json + working-ontology.json</div>
|
|
683
|
+
<div class="out">proposal.md + artifacts/proposal.ontology.json + ontology/working-ontology.json</div>
|
|
684
684
|
</div>
|
|
685
685
|
|
|
686
686
|
<div class="skill reveal" id="sk-spec" style="border-left-color:#38bdf8">
|
|
@@ -824,7 +824,7 @@ npx kld-sdd --version">复制</button>
|
|
|
824
824
|
<article class="feature-card reveal">
|
|
825
825
|
<div class="eyebrow">Materialize</div>
|
|
826
826
|
<h3>写完即生成工作态事实</h3>
|
|
827
|
-
<p>propose/spec/design/task 每次写入后立即 reconcile,产出 <code>artifacts/**/*.ontology.json</code>、<code>artifact-index.json</code> 与 <code>working-ontology.json</code>,状态为 draft/pending,尚不能直接入库。</p>
|
|
827
|
+
<p>propose/spec/design/task 每次写入后立即 reconcile,产出 <code>artifacts/**/*.ontology.json</code>、<code>ontology/artifact-index.json</code> 与 <code>ontology/working-ontology.json</code>,状态为 draft/pending,尚不能直接入库。</p>
|
|
828
828
|
</article>
|
|
829
829
|
<article class="feature-card reveal">
|
|
830
830
|
<div class="eyebrow">Traceability</div>
|
|
@@ -913,9 +913,8 @@ node skywalk-sdd/log.cjs semantic-observe --project=. --change=add-user-auth">
|
|
|
913
913
|
│ │ ├── <span class="file">proposal.md</span> <span class="cmt"># mode / change-id / change-type / test-strategy</span>
|
|
914
914
|
│ │ ├── <span class="dir">specs/<capability>/</span> <span class="cmt"># Full:spec / design / tasks</span>
|
|
915
915
|
│ │ ├── <span class="file">spec · design · tasks.md</span> <span class="cmt"># Simple:根目录三文档</span>
|
|
916
|
-
│ │ ├── <span class="
|
|
917
|
-
│ │ ├── <span class="dir">
|
|
918
|
-
│ │ ├── <span class="dir">ontology-identities/ · artifacts/**/*.ontology.json</span>
|
|
916
|
+
│ │ ├── <span class="dir">ontology/</span> <span class="cmt"># working-ontology · artifact-index · diagnostics · file-index · identities · continuity-resolution</span>
|
|
917
|
+
│ │ ├── <span class="dir">artifacts/**/*.ontology.json</span>
|
|
919
918
|
│ │ └── <span class="file">logs/execution-log.md</span>
|
|
920
919
|
│ └── <span class="dir">archive/</span>
|
|
921
920
|
│ ├── <span class="dir"><date>-<name>/</span>
|
package/lib/init.js
CHANGED
|
@@ -831,18 +831,14 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
|
|
|
831
831
|
console.log(` ⚠️ V3 指标模块源文件缺失: ${metricsV3Src}(report/metrics 可能无法加载 metrics-v3)`);
|
|
832
832
|
}
|
|
833
833
|
|
|
834
|
-
const
|
|
834
|
+
const packageVersion = require(path.join(pkgPath, 'package.json')).version;
|
|
835
835
|
const runtimeMetadataDst = path.join(dataDir, 'runtime-metadata.cjs');
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
version: packageVersion,
|
|
843
|
-
}, null, 2)}\n`, 'utf8');
|
|
844
|
-
console.log(` ✓ 部署 kld-sdd 运行时身份(${packageVersion})`);
|
|
845
|
-
}
|
|
836
|
+
fs.writeFileSync(
|
|
837
|
+
runtimeMetadataDst,
|
|
838
|
+
`'use strict';\nmodule.exports = { resolveRuntimeMetadata() { return { version: '${packageVersion}', producer: 'kld-sdd@${packageVersion}' }; } };\n`,
|
|
839
|
+
'utf8',
|
|
840
|
+
);
|
|
841
|
+
console.log(` ✓ 部署 kld-sdd 运行时身份(${packageVersion})`);
|
|
846
842
|
|
|
847
843
|
const reportingSrc = path.join(pkgPath, 'skywalk-sdd', 'reporting');
|
|
848
844
|
const reportingDst = path.join(dataDir, 'reporting');
|
|
@@ -887,6 +883,7 @@ function deployTelemetryDataDir(targetCwd = process.cwd()) {
|
|
|
887
883
|
console.log(' ✓ openspec: node skywalk-sdd/openspec-shim.cjs list(自动 cd 到 *-sdd-specs)');
|
|
888
884
|
console.log(' ✓ Spec Context: node skywalk-sdd/context-client.cjs --query="<自然语言需求>" --target-stage=spec');
|
|
889
885
|
console.log(' ✓ Apply worktree: record-base 在 §1.5;收尾 apply-worktree-finish.cjs --change=<name>');
|
|
886
|
+
console.log(' ℹ️ project-identity.json 将在首次配置 KB(opsx-ontology-query)时自动创建');
|
|
890
887
|
console.log('✅ Telemetry 已就绪(数据存储在 skywalk-sdd/,无需配置 MCP)');
|
|
891
888
|
return true;
|
|
892
889
|
}
|
package/package.json
CHANGED
|
@@ -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,
|