kld-sdd 2.5.2 → 2.6.1
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 +33 -10
- package/lib/init.js +11 -1
- package/package.json +3 -2
- package/skywalk-sdd/context-client.cjs +160 -0
- package/skywalk-sdd/index.cjs +118 -14
- package/skywalk-sdd/ontology/archive-package.cjs +489 -0
- package/skywalk-sdd/ontology/id.cjs +16 -19
- package/skywalk-sdd/ontology/identity-index.cjs +25 -0
- package/skywalk-sdd/ontology/runtime.cjs +179 -54
- package/skywalk-sdd/ontology/working-artifacts.cjs +243 -0
- package/templates/openspec/proposal.md +1 -1
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +27 -4
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +29 -1
- package/templates/skills/kld-sdd/opsx-apply/implementer-prompt.md +54 -3
- package/templates/skills/kld-sdd/opsx-apply/reference.md +26 -0
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +10 -1
- package/templates/skills/kld-sdd/opsx-archive/checklist.md +5 -1
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +21 -4
- package/templates/skills/kld-sdd/opsx-check/checklist.md +19 -1
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +2 -0
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +2 -0
- package/templates/skills/kld-sdd/opsx-propose/checklist.md +2 -0
- package/templates/skills/kld-sdd/opsx-propose/reference.md +6 -4
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +23 -0
- package/templates/skills/kld-sdd/opsx-spec/checklist.md +5 -0
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +43 -8
- package/templates/skills/kld-sdd/opsx-task/checklist.md +15 -0
- package/templates/skills/kld-sdd/opsx-task/reference.md +73 -2
- package/templates/skills/kld-sdd/opsx-test/SKILL.md +17 -0
package/README.md
CHANGED
|
@@ -32,8 +32,8 @@ Entity sourcedFrom SEC
|
|
|
32
32
|
每个本体实体使用双层身份:
|
|
33
33
|
|
|
34
34
|
- `anchor`:`STMT-ORDER-005` 等人工可读编号,跨迭代保持稳定且删除后不复用。
|
|
35
|
-
- `entity-id`:全局唯一
|
|
36
|
-
- `version-id`:全局唯一
|
|
35
|
+
- `entity-id`:全局唯一 32 位 ID(8 位十六进制),同一逻辑实体跨迭代复用。
|
|
36
|
+
- `version-id`:全局唯一 32 位 ID,每次 added/modified/removed 产生新版本;unchanged 复用历史版本。
|
|
37
37
|
- `predecessor-version`:modified/removed 指向同一实体的直接前序版本。
|
|
38
38
|
|
|
39
39
|
UUID 由本地命令生成,不依赖网络、机器号或中央 ID 服务:
|
|
@@ -53,7 +53,7 @@ node skywalk-sdd/log.cjs semantic-identity --delta-state=unchanged --entity-id=<
|
|
|
53
53
|
本地命令:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
# 解析与诊断;首次运行会为 Artifact/DocumentSection
|
|
56
|
+
# 解析与诊断;首次运行会为 Artifact/DocumentSection 分配并持久化 32 位结构 ID sidecar
|
|
57
57
|
node skywalk-sdd/log.cjs semantic-scan --project=. --change=<name>
|
|
58
58
|
|
|
59
59
|
# 文件与工作态本体实例全量对账
|
|
@@ -69,6 +69,10 @@ 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`,`openspec/changes/<name>/artifact-index.json` 与 `working-ontology.json` 提供统一索引与全量工作态视图;这些文件随 change 目录一起提交和归档。`skywalk-sdd/state/ontology/` 仅保留 revision 指针与跨进程锁,默认不提交。
|
|
73
|
+
|
|
74
|
+
工作态 JSON 使用 `kld-sdd-working-artifact-facts/v1`,明确标记 `canonical=false` 和 `review_status=draft|pending`。它用于当前 Change 的渐进式展开、定向查询和跨文档校验,不能直接入知识库。Check 重新解析原文、校验并在全部通过时生成 pending revision;Archive 再次对账后才确认、转换为 `canonical-facts.json` 并打包。
|
|
75
|
+
|
|
72
76
|
`semantic-observe` 不是 Hook 替代品:它不能阻止 Cursor、Qoder 或人工编辑,也不能在落盘前控制内容。Observer 启动/重启后会重新对账,暂态失败和锁冲突不会推进文件指纹;Check 和 Archive 始终重新执行 `semantic-reconcile`,以处理重复、乱序或丢失的文件事件。
|
|
73
77
|
|
|
74
78
|
校验 profile:
|
|
@@ -79,9 +83,17 @@ node skywalk-sdd/log.cjs semantic-observe --project=. --change=<name>
|
|
|
79
83
|
| `full` | 强制 STMT→AC→Design→Task 全链和 Task DAG |
|
|
80
84
|
| `strict` | 在 full 基础上要求完整语义来源 |
|
|
81
85
|
|
|
82
|
-
同一 Change
|
|
86
|
+
同一 Change 的作者阶段同步、observe、reconcile、check 和 archive 共用跨进程事务锁。`working-ontology.json`、diagnostics、file index、artifact index 及所有分文件 JSON 在 change 目录内原子提交,随后切换 `skywalk-sdd/state/ontology/<change>/current.json` 指针;读取时会拒绝任一文件缺失或 revision 撕裂。
|
|
87
|
+
|
|
88
|
+
Archive 使用两阶段事务:先复制到 staging,从 staging 重新解析正文并生成本体快照、canonical facts、转换报告和 v2 manifest,所有步骤成功后才切换为正式 archive。归档完成后同时生成相邻的 `<日期>-<name>.zip`,其中 `archive-manifest.json` 的文件清单和 SHA-256 必须精确覆盖包内文件,可直接交给知识库消费。语义校验、快照、协议转换、打包或 Spec 同步失败时,活动 Change 会保留或恢复,不留下伪 confirmed 半成品。
|
|
89
|
+
|
|
90
|
+
生产端归档协议产物:
|
|
83
91
|
|
|
84
|
-
|
|
92
|
+
- `archive-ontology.json`:KLD-SDD 内部 confirmed 本体快照。
|
|
93
|
+
- `canonical-facts.json`:面向知识库的稳定语义事实,实体与关系均带原文来源及内容哈希。
|
|
94
|
+
- `conversion-report.json`:本体快照到 canonical facts 的转换结果、数量和归一化告警。
|
|
95
|
+
- `archive-manifest.json`:`kld-sdd-archive-manifest/v2` 身份、内容哈希及完整文件清单。
|
|
96
|
+
- `openspec/changes/archive/<日期>-<name>.zip`:知识库可直接读取的 Archive Package。
|
|
85
97
|
|
|
86
98
|
## 快速开始
|
|
87
99
|
|
|
@@ -251,6 +263,21 @@ kld-sdd-init --tool codex # 仅配置 Codex
|
|
|
251
263
|
|
|
252
264
|
```
|
|
253
265
|
your-project/
|
|
266
|
+
├── openspec/
|
|
267
|
+
│ └── changes/
|
|
268
|
+
│ └── <name>/
|
|
269
|
+
│ ├── proposal.md
|
|
270
|
+
│ ├── working-ontology.json
|
|
271
|
+
│ ├── artifact-index.json
|
|
272
|
+
│ ├── diagnostics.json
|
|
273
|
+
│ ├── file-index.json
|
|
274
|
+
│ ├── ontology-identities.json
|
|
275
|
+
│ └── artifacts/
|
|
276
|
+
│ ├── proposal.ontology.json
|
|
277
|
+
│ └── specs/<capability>/
|
|
278
|
+
│ ├── spec.ontology.json
|
|
279
|
+
│ ├── design.ontology.json
|
|
280
|
+
│ └── tasks.ontology.json
|
|
254
281
|
├── openspec-templates/ # openSpec 四文档参考模版
|
|
255
282
|
│ ├── proposal.md
|
|
256
283
|
│ ├── spec.md
|
|
@@ -262,11 +289,7 @@ your-project/
|
|
|
262
289
|
│ └── state/ontology/
|
|
263
290
|
│ ├── .locks/<change>.lock # observe/reconcile/check/archive 跨进程锁
|
|
264
291
|
│ └── <change>/
|
|
265
|
-
│
|
|
266
|
-
│ └── revisions/<revision>/
|
|
267
|
-
│ ├── working-ontology.json
|
|
268
|
-
│ ├── diagnostics.json
|
|
269
|
-
│ └── file-index.json
|
|
292
|
+
│ └── current.json # 当前 revision 指针(本体 JSON 在 change 目录)
|
|
270
293
|
├── .cursor/
|
|
271
294
|
│ └── skills/opsx-*/ # SDD skills(扁平一层)
|
|
272
295
|
├── .claude/
|
package/lib/init.js
CHANGED
|
@@ -830,6 +830,15 @@ function deployTelemetryDataDir() {
|
|
|
830
830
|
console.log(` ⚠️ Telemetry CLI 源文件缺失: ${telemetrySrc}`);
|
|
831
831
|
}
|
|
832
832
|
|
|
833
|
+
const contextClientSrc = path.join(pkgPath, 'skywalk-sdd', 'context-client.cjs');
|
|
834
|
+
const contextClientDst = path.join(dataDir, 'context-client.cjs');
|
|
835
|
+
if (fs.existsSync(contextClientSrc)) {
|
|
836
|
+
fs.copyFileSync(contextClientSrc, contextClientDst);
|
|
837
|
+
console.log(' ✓ 部署 skywalk-sdd/context-client.cjs(工程 Spec 上下文客户端)');
|
|
838
|
+
} else {
|
|
839
|
+
console.log(` ⚠️ 工程 Spec 上下文客户端缺失: ${contextClientSrc}`);
|
|
840
|
+
}
|
|
841
|
+
|
|
833
842
|
const ontologySrc = path.join(pkgPath, 'skywalk-sdd', 'ontology');
|
|
834
843
|
const ontologyDst = path.join(dataDir, 'ontology');
|
|
835
844
|
if (fs.existsSync(ontologySrc)) {
|
|
@@ -847,6 +856,7 @@ function deployTelemetryDataDir() {
|
|
|
847
856
|
}
|
|
848
857
|
|
|
849
858
|
console.log(' ✓ 调用方式: node skywalk-sdd/log.cjs start|end|metrics|semantic-identity|semantic-reconcile|semantic-check');
|
|
859
|
+
console.log(' ✓ Spec Context: node skywalk-sdd/context-client.cjs --query="<自然语言需求>" --target-stage=spec');
|
|
850
860
|
console.log(' ✓ Apply worktree: record-base 在 §1.5;收尾 apply-worktree-finish.cjs --change=<name>');
|
|
851
861
|
console.log('✅ Telemetry 已就绪(数据存储在 skywalk-sdd/,无需配置 MCP)');
|
|
852
862
|
return true;
|
|
@@ -971,7 +981,7 @@ function updateGitignore() {
|
|
|
971
981
|
.workbuddy/commands/personal-*
|
|
972
982
|
.agents/commands/personal-*
|
|
973
983
|
|
|
974
|
-
# SDD Telemetry
|
|
984
|
+
# SDD Telemetry 数据(本地度量,可选提交;本体 JSON 与报告随 openspec/changes/<change>/ 提交)
|
|
975
985
|
skywalk-sdd/events/
|
|
976
986
|
skywalk-sdd/state/
|
|
977
987
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kld-sdd",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "KLD SDD OpenSpec 项目初始化工具 - 一键部署 SDD skills",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"kld-sdd-init": "bin/kld-sdd-init.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "node test/ontology-release-blockers.cjs && node test/ontology-semantic-core.cjs && node test/ontology-identity-versioning.cjs && node test/ontology-state-transaction.cjs && node test/ontology-process-concurrency.cjs && node test/ontology-observer-convergence.cjs && node test/ontology-working-runtime.cjs && node test/ontology-template-contract.cjs && node test/ontology-cli-archive.cjs && node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/settings-merge.cjs && node test/command-bridge.cjs && node test/codebuddy-hooks.cjs && node test/skill-content-contract.cjs && node test/init-agent-profiles.cjs"
|
|
11
|
+
"test": "node test/ontology-release-blockers.cjs && node test/ontology-semantic-core.cjs && node test/ontology-identity-versioning.cjs && node test/ontology-state-transaction.cjs && node test/ontology-process-concurrency.cjs && node test/ontology-observer-convergence.cjs && node test/ontology-working-runtime.cjs && node test/ontology-stage-materialization.cjs && node test/ontology-template-contract.cjs && node test/ontology-cli-archive.cjs && node test/archive-package-producer.cjs && node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/settings-merge.cjs && node test/command-bridge.cjs && node test/codebuddy-hooks.cjs && node test/skill-content-contract.cjs && node test/init-agent-profiles.cjs"
|
|
12
12
|
},
|
|
13
13
|
"keywords": [
|
|
14
14
|
"kld",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"templates/",
|
|
33
33
|
"kld-sdd-guide.html",
|
|
34
34
|
"skywalk-sdd/index.cjs",
|
|
35
|
+
"skywalk-sdd/context-client.cjs",
|
|
35
36
|
"skywalk-sdd/ontology/",
|
|
36
37
|
"skywalk-sdd/apply-worktree-finish.cjs",
|
|
37
38
|
"README.md"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const { URL } = require('url');
|
|
8
|
+
|
|
9
|
+
function parseArgs(argv) {
|
|
10
|
+
const args = {};
|
|
11
|
+
for (const item of argv.slice(2)) {
|
|
12
|
+
if (!item.startsWith('--')) continue;
|
|
13
|
+
const equal = item.indexOf('=');
|
|
14
|
+
if (equal < 0) args[item.slice(2)] = true;
|
|
15
|
+
else args[item.slice(2, equal)] = item.slice(equal + 1);
|
|
16
|
+
}
|
|
17
|
+
return args;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function queryFromArgs(args) {
|
|
21
|
+
if (typeof args.query === 'string' && args.query.trim()) {
|
|
22
|
+
return args.query.trim();
|
|
23
|
+
}
|
|
24
|
+
if (typeof args['query-file'] === 'string' && args['query-file'].trim()) {
|
|
25
|
+
return fs.readFileSync(args['query-file'], 'utf8').trim();
|
|
26
|
+
}
|
|
27
|
+
throw new Error('missing --query or --query-file');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildEndpoint(apiBase, spaceId) {
|
|
31
|
+
const base = String(apiBase || 'http://localhost:8090/api').replace(/\/$/, '');
|
|
32
|
+
return `${base}/v1/spaces/${encodeURIComponent(spaceId)}/context/match-requirement`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requestJson(url, payload, token, timeoutMs) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const target = new URL(url);
|
|
38
|
+
const transport = target.protocol === 'https:' ? https : http;
|
|
39
|
+
const body = JSON.stringify(payload);
|
|
40
|
+
const headers = {
|
|
41
|
+
Accept: 'application/json',
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'Content-Length': Buffer.byteLength(body),
|
|
44
|
+
};
|
|
45
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
46
|
+
|
|
47
|
+
const request = transport.request(
|
|
48
|
+
{
|
|
49
|
+
hostname: target.hostname,
|
|
50
|
+
port: target.port || (target.protocol === 'https:' ? 443 : 80),
|
|
51
|
+
path: target.pathname + target.search,
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers,
|
|
54
|
+
timeout: timeoutMs,
|
|
55
|
+
},
|
|
56
|
+
(response) => {
|
|
57
|
+
let responseBody = '';
|
|
58
|
+
response.on('data', (chunk) => {
|
|
59
|
+
responseBody += chunk;
|
|
60
|
+
});
|
|
61
|
+
response.on('end', () => {
|
|
62
|
+
let parsed;
|
|
63
|
+
try {
|
|
64
|
+
parsed = responseBody ? JSON.parse(responseBody) : null;
|
|
65
|
+
} catch {
|
|
66
|
+
reject(new Error(`knowledge base returned invalid JSON (HTTP ${response.statusCode})`));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (response.statusCode >= 400 || parsed?.code !== 0) {
|
|
70
|
+
reject(new Error(parsed?.message || `knowledge base HTTP ${response.statusCode}`));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
resolve(parsed.data);
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
request.on('timeout', () => request.destroy(new Error(`knowledge base timeout after ${timeoutMs}ms`)));
|
|
78
|
+
request.on('error', reject);
|
|
79
|
+
request.write(body);
|
|
80
|
+
request.end();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function compactContext(data, includeCandidates) {
|
|
85
|
+
if (includeCandidates) return data;
|
|
86
|
+
return {
|
|
87
|
+
matchType: data.matchType,
|
|
88
|
+
inheritanceAllowed: data.inheritanceAllowed,
|
|
89
|
+
reviewRequired: data.reviewRequired,
|
|
90
|
+
advisory: data.advisory,
|
|
91
|
+
degraded: data.degraded,
|
|
92
|
+
degradationReasons: data.degradationReasons,
|
|
93
|
+
targetStage: data.targetStage,
|
|
94
|
+
reuseBundles: data.reuseBundles,
|
|
95
|
+
answeredQuestions: data.answeredQuestions,
|
|
96
|
+
warnings: data.warnings,
|
|
97
|
+
clarificationQuestions: data.clarificationQuestions,
|
|
98
|
+
specGenerationContext: data.specGenerationContext,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function retrieveContext(args = parseArgs(process.argv), env = process.env) {
|
|
103
|
+
const query = queryFromArgs(args);
|
|
104
|
+
const spaceId = args['space-id'] || env.ENGINEERING_KB_SPACE_ID || '';
|
|
105
|
+
if (!spaceId) {
|
|
106
|
+
return {
|
|
107
|
+
available: false,
|
|
108
|
+
advisory: true,
|
|
109
|
+
degraded: true,
|
|
110
|
+
reason: 'engineering_kb_not_configured',
|
|
111
|
+
hint: 'set ENGINEERING_KB_SPACE_ID to enable reusable Spec context',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const apiBase = args['api-base'] || env.ENGINEERING_KB_API || 'http://localhost:8090/api';
|
|
116
|
+
const token = args.token || env.ENGINEERING_KB_TOKEN || '';
|
|
117
|
+
const timeoutMs = Number(args.timeout || env.ENGINEERING_KB_TIMEOUT_MS || 30000);
|
|
118
|
+
const data = await requestJson(
|
|
119
|
+
buildEndpoint(apiBase, spaceId),
|
|
120
|
+
{
|
|
121
|
+
query,
|
|
122
|
+
targetStage: args['target-stage'] || 'spec',
|
|
123
|
+
},
|
|
124
|
+
token,
|
|
125
|
+
timeoutMs,
|
|
126
|
+
);
|
|
127
|
+
return {
|
|
128
|
+
available: true,
|
|
129
|
+
...compactContext(data, Boolean(args['include-candidates'])),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function main() {
|
|
134
|
+
const args = parseArgs(process.argv);
|
|
135
|
+
try {
|
|
136
|
+
console.log(JSON.stringify(await retrieveContext(args), null, 2));
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.log(JSON.stringify({
|
|
139
|
+
available: false,
|
|
140
|
+
advisory: true,
|
|
141
|
+
degraded: true,
|
|
142
|
+
reason: 'engineering_kb_unavailable',
|
|
143
|
+
message: error.message,
|
|
144
|
+
}, null, 2));
|
|
145
|
+
if (args.strict) process.exitCode = 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (require.main === module) {
|
|
150
|
+
main();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
parseArgs,
|
|
155
|
+
queryFromArgs,
|
|
156
|
+
buildEndpoint,
|
|
157
|
+
requestJson,
|
|
158
|
+
compactContext,
|
|
159
|
+
retrieveContext,
|
|
160
|
+
};
|
package/skywalk-sdd/index.cjs
CHANGED
|
@@ -600,7 +600,6 @@ function assertConfirmedArchive(projectRoot, changeName, archiveDir, options = {
|
|
|
600
600
|
}
|
|
601
601
|
|
|
602
602
|
function ensureArchiveManifest(projectRoot, changeName, archiveDir, options = {}) {
|
|
603
|
-
const manifestPath = path.join(archiveDir, 'archive-manifest.json');
|
|
604
603
|
const copiedSpecs = Array.isArray(options.copiedSpecs)
|
|
605
604
|
? options.copiedSpecs
|
|
606
605
|
: syncArchivedSpecs(projectRoot, archiveDir, changeName);
|
|
@@ -616,20 +615,24 @@ function ensureArchiveManifest(projectRoot, changeName, archiveDir, options = {}
|
|
|
616
615
|
path.join(committedArchiveDir, 'evidence', 'events'),
|
|
617
616
|
);
|
|
618
617
|
}
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
618
|
+
const { materializeArchivePackage } = require('./ontology/archive-package.cjs');
|
|
619
|
+
const produced = materializeArchivePackage({
|
|
620
|
+
projectRoot,
|
|
621
|
+
changeName,
|
|
622
|
+
archiveDir,
|
|
623
|
+
archivePath: committedArchiveDir,
|
|
624
|
+
packagePath: Object.prototype.hasOwnProperty.call(options, 'packagePath')
|
|
625
|
+
? options.packagePath
|
|
626
|
+
: `${committedArchiveDir}.zip`,
|
|
627
|
+
archivedAt: options.archivedAt,
|
|
622
628
|
reason: options.reason || '变更已完成实施',
|
|
623
629
|
method: options.method || 'skywalk-full-spec-archive',
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
};
|
|
629
|
-
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
630
|
+
sourcePath: `openspec/changes/${changeName}`,
|
|
631
|
+
copiedSpecs,
|
|
632
|
+
evidenceEventsPath,
|
|
633
|
+
});
|
|
630
634
|
return {
|
|
631
|
-
|
|
632
|
-
manifest_path: manifestPath,
|
|
635
|
+
...produced,
|
|
633
636
|
copied_specs: copiedSpecs,
|
|
634
637
|
};
|
|
635
638
|
}
|
|
@@ -674,18 +677,27 @@ function ensureArchiveSuccessArtifacts(projectRoot, changeName, details = {}, op
|
|
|
674
677
|
const manifestInfo = ensureArchiveManifest(normalizedRoot, changeName, archiveDir, {
|
|
675
678
|
reason: archiveReason,
|
|
676
679
|
method: archiveMethod || 'skywalk-full-spec-archive',
|
|
677
|
-
archivedAt: details.archive_result?.
|
|
680
|
+
archivedAt: details.archive_result?.created_at || details.archive_result?.archived_at,
|
|
678
681
|
});
|
|
679
682
|
|
|
680
683
|
const archiveResult = {
|
|
681
684
|
reason: archiveReason,
|
|
682
685
|
method: manifestInfo.manifest.method,
|
|
686
|
+
schema_version: manifestInfo.manifest.schema_version,
|
|
687
|
+
producer: manifestInfo.manifest.producer,
|
|
688
|
+
project_id: manifestInfo.manifest.project_id,
|
|
689
|
+
archive_id: manifestInfo.manifest.archive_id,
|
|
690
|
+
change_id: manifestInfo.manifest.change_id,
|
|
691
|
+
created_at: manifestInfo.manifest.created_at,
|
|
683
692
|
archive_path: manifestInfo.manifest.archive_path,
|
|
684
693
|
report_path: toProjectRelative(normalizedRoot, reportPath),
|
|
685
694
|
// report_html_path 为预期路径:由 cmdEnd 落盘阶段写入(try/catch 容错,失败时不阻塞 md 主产物)。
|
|
686
695
|
// 此处无条件派生,html 实际落盘失败时该路径可能不存在;语义为"预期路径",消费者不应假定文件已存在。
|
|
687
696
|
report_html_path: toProjectRelative(normalizedRoot, reportPath.replace(/\.md$/i, '.html')),
|
|
688
697
|
manifest_path: toProjectRelative(normalizedRoot, manifestInfo.manifest_path),
|
|
698
|
+
canonical_facts_path: toProjectRelative(normalizedRoot, manifestInfo.canonical_facts_path),
|
|
699
|
+
conversion_report_path: toProjectRelative(normalizedRoot, manifestInfo.conversion_report_path),
|
|
700
|
+
package_path: toProjectRelative(normalizedRoot, manifestInfo.package_path),
|
|
689
701
|
task_completion: taskCompletion,
|
|
690
702
|
copied_specs: manifestInfo.copied_specs,
|
|
691
703
|
};
|
|
@@ -724,10 +736,13 @@ function archiveChangeDocs(projectRoot, changeName, options = {}) {
|
|
|
724
736
|
const lock = acquireChangeLock(normalizedRoot, changeName, options.lockOptions);
|
|
725
737
|
const token = lock.token.replace(/-/g, '');
|
|
726
738
|
const stagingDir = path.join(archiveRoot, `.archive-staging-${token}`);
|
|
739
|
+
const stagingPackagePath = `${stagingDir}.zip`;
|
|
740
|
+
const finalPackagePath = `${archiveDir}.zip`;
|
|
727
741
|
const sourceBackup = path.join(path.dirname(sourceDir), `.archive-source-${token}`);
|
|
728
742
|
let specTransaction = null;
|
|
729
743
|
let sourceMoved = false;
|
|
730
744
|
let archiveCommitted = false;
|
|
745
|
+
let packageCommitted = false;
|
|
731
746
|
try {
|
|
732
747
|
const semanticResult = ontologyRuntime.reconcileChange(normalizedRoot, changeName, {
|
|
733
748
|
profile: options.profile || 'auto',
|
|
@@ -742,6 +757,7 @@ function archiveChangeDocs(projectRoot, changeName, options = {}) {
|
|
|
742
757
|
}
|
|
743
758
|
|
|
744
759
|
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
760
|
+
fs.rmSync(stagingPackagePath, { force: true });
|
|
745
761
|
copyDirSync(sourceDir, stagingDir);
|
|
746
762
|
if (options.failAt === 'after-copy') throw new Error('after-copy');
|
|
747
763
|
|
|
@@ -768,6 +784,7 @@ function archiveChangeDocs(projectRoot, changeName, options = {}) {
|
|
|
768
784
|
reason: archiveReason,
|
|
769
785
|
method: 'skywalk-full-spec-archive',
|
|
770
786
|
archivePath: archiveDir,
|
|
787
|
+
packagePath: stagingPackagePath,
|
|
771
788
|
copiedSpecs: specTransaction.copiedSpecs,
|
|
772
789
|
});
|
|
773
790
|
const confirmed = assertConfirmedArchive(normalizedRoot, changeName, stagingDir, {
|
|
@@ -800,6 +817,9 @@ function archiveChangeDocs(projectRoot, changeName, options = {}) {
|
|
|
800
817
|
fs.renameSync(stagingDir, archiveDir);
|
|
801
818
|
archiveCommitted = true;
|
|
802
819
|
commitArchivedSpecSync(specTransaction, options.failAt);
|
|
820
|
+
fs.rmSync(finalPackagePath, { force: true });
|
|
821
|
+
fs.renameSync(stagingPackagePath, finalPackagePath);
|
|
822
|
+
packageCommitted = true;
|
|
803
823
|
if (sourceMoved) fs.rmSync(sourceBackup, { recursive: true, force: true });
|
|
804
824
|
cleanupArchivedSpecSync(specTransaction);
|
|
805
825
|
|
|
@@ -807,12 +827,18 @@ function archiveChangeDocs(projectRoot, changeName, options = {}) {
|
|
|
807
827
|
...manifestInfo.manifest,
|
|
808
828
|
project_root: normalizedRoot,
|
|
809
829
|
archive_path: archiveDir,
|
|
830
|
+
manifest_path: path.join(archiveDir, 'archive-manifest.json'),
|
|
831
|
+
canonical_facts_path: path.join(archiveDir, 'canonical-facts.json'),
|
|
832
|
+
conversion_report_path: path.join(archiveDir, 'conversion-report.json'),
|
|
833
|
+
package_path: finalPackagePath,
|
|
810
834
|
active_change_exists: fs.existsSync(sourceDir),
|
|
811
835
|
};
|
|
812
836
|
} catch (error) {
|
|
813
837
|
rollbackArchivedSpecSync(specTransaction);
|
|
814
838
|
if (archiveCommitted) fs.rmSync(archiveDir, { recursive: true, force: true });
|
|
839
|
+
if (packageCommitted) fs.rmSync(finalPackagePath, { force: true });
|
|
815
840
|
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
841
|
+
fs.rmSync(stagingPackagePath, { force: true });
|
|
816
842
|
if (sourceMoved && fs.existsSync(sourceBackup) && !fs.existsSync(sourceDir)) {
|
|
817
843
|
fs.renameSync(sourceBackup, sourceDir);
|
|
818
844
|
}
|
|
@@ -3507,6 +3533,37 @@ function cmdStart(args) {
|
|
|
3507
3533
|
console.log(JSON.stringify(output, null, 2));
|
|
3508
3534
|
}
|
|
3509
3535
|
|
|
3536
|
+
const ONTOLOGY_AUTHORING_STAGES = new Set(['propose', 'spec', 'design', 'task', 'check']);
|
|
3537
|
+
|
|
3538
|
+
function syncWorkingOntologyForStage(projectRoot, changeName, command, result) {
|
|
3539
|
+
if (!ONTOLOGY_AUTHORING_STAGES.has(command) || result === 'failure') return null;
|
|
3540
|
+
if (!changeName || changeName === 'general') return null;
|
|
3541
|
+
const changeDir = getChangeDir(projectRoot, changeName);
|
|
3542
|
+
if (!fs.existsSync(changeDir)) return null;
|
|
3543
|
+
const reconciled = require('./ontology/runtime.cjs').reconcileChange(
|
|
3544
|
+
projectRoot,
|
|
3545
|
+
changeName,
|
|
3546
|
+
{
|
|
3547
|
+
profile: 'auto',
|
|
3548
|
+
markPending: command === 'check',
|
|
3549
|
+
},
|
|
3550
|
+
);
|
|
3551
|
+
return {
|
|
3552
|
+
schema_version: reconciled.state.artifact_schema_version,
|
|
3553
|
+
index_schema_version: reconciled.state.artifact_index_schema_version,
|
|
3554
|
+
revision: reconciled.revision,
|
|
3555
|
+
review_status: reconciled.state.review_status,
|
|
3556
|
+
valid: reconciled.valid,
|
|
3557
|
+
changed: Boolean(reconciled.changed),
|
|
3558
|
+
working_ontology_path: toProjectRelative(projectRoot, reconciled.paths.working),
|
|
3559
|
+
artifact_index_path: toProjectRelative(projectRoot, reconciled.paths.artifactIndex),
|
|
3560
|
+
artifact_json_paths: reconciled.paths.artifactFacts.map((filePath) => (
|
|
3561
|
+
toProjectRelative(projectRoot, filePath)
|
|
3562
|
+
)),
|
|
3563
|
+
diagnostics: reconciled.diagnostics,
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3510
3567
|
/**
|
|
3511
3568
|
* log end: 记录阶段结束
|
|
3512
3569
|
*/
|
|
@@ -3589,6 +3646,30 @@ function cmdEnd(args, options = {}) {
|
|
|
3589
3646
|
fail(`details JSON 解析失败: ${err.message}`);
|
|
3590
3647
|
}
|
|
3591
3648
|
|
|
3649
|
+
let semanticState = null;
|
|
3650
|
+
try {
|
|
3651
|
+
semanticState = syncWorkingOntologyForStage(projectRoot, change, command, result);
|
|
3652
|
+
if (semanticState) {
|
|
3653
|
+
details = {
|
|
3654
|
+
...details,
|
|
3655
|
+
semantic_state: semanticState,
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
} catch (error) {
|
|
3659
|
+
if (ONTOLOGY_AUTHORING_STAGES.has(command) && result !== 'failure') {
|
|
3660
|
+
result = 'partial';
|
|
3661
|
+
summary = `${summary}(工作态本体 JSON 同步失败:${error.message})`.trim();
|
|
3662
|
+
semanticState = {
|
|
3663
|
+
status: 'failed',
|
|
3664
|
+
error: error.message,
|
|
3665
|
+
};
|
|
3666
|
+
details = {
|
|
3667
|
+
...details,
|
|
3668
|
+
semantic_state: semanticState,
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3592
3673
|
const durationMs = startEvent
|
|
3593
3674
|
? new Date(timestamp).getTime() - new Date(startEvent.timestamp).getTime()
|
|
3594
3675
|
: null;
|
|
@@ -3732,6 +3813,7 @@ function cmdEnd(args, options = {}) {
|
|
|
3732
3813
|
duration_ms: durationMs,
|
|
3733
3814
|
recorded_at: timestamp,
|
|
3734
3815
|
report_output: resolvedReportOutput || undefined,
|
|
3816
|
+
semantic_state: semanticState || undefined,
|
|
3735
3817
|
message: `SDD ${event.command} 阶段结束(${result},耗时 ${durationMs ? (durationMs / 1000).toFixed(1) + 's' : '未知'})`,
|
|
3736
3818
|
};
|
|
3737
3819
|
if (!options.silent) {
|
|
@@ -4463,6 +4545,28 @@ function cmdArchiveDocs(args) {
|
|
|
4463
4545
|
}, { silent: true });
|
|
4464
4546
|
}
|
|
4465
4547
|
|
|
4548
|
+
const archiveDir = path.resolve(result.archive_path);
|
|
4549
|
+
const finalPackage = ensureArchiveManifest(projectRoot, changeName, archiveDir, {
|
|
4550
|
+
reason: result.reason || args.reason || '',
|
|
4551
|
+
method: result.method || 'skywalk-full-spec-archive',
|
|
4552
|
+
archivePath: archiveDir,
|
|
4553
|
+
archivedAt: result.created_at || result.archived_at,
|
|
4554
|
+
copiedSpecs: result.copied_specs,
|
|
4555
|
+
});
|
|
4556
|
+
result = {
|
|
4557
|
+
...result,
|
|
4558
|
+
schema_version: finalPackage.manifest.schema_version,
|
|
4559
|
+
producer: finalPackage.manifest.producer,
|
|
4560
|
+
project_id: finalPackage.manifest.project_id,
|
|
4561
|
+
archive_id: finalPackage.manifest.archive_id,
|
|
4562
|
+
change_id: finalPackage.manifest.change_id,
|
|
4563
|
+
created_at: finalPackage.manifest.created_at,
|
|
4564
|
+
manifest_path: finalPackage.manifest_path,
|
|
4565
|
+
canonical_facts_path: finalPackage.canonical_facts_path,
|
|
4566
|
+
conversion_report_path: finalPackage.conversion_report_path,
|
|
4567
|
+
package_path: finalPackage.package_path,
|
|
4568
|
+
};
|
|
4569
|
+
|
|
4466
4570
|
console.log(JSON.stringify({
|
|
4467
4571
|
...result,
|
|
4468
4572
|
report_output: stageEnd ? stageEnd.report_output : reportOutput,
|
|
@@ -4582,7 +4686,7 @@ SDD Telemetry CLI - 流程度量采集工具
|
|
|
4582
4686
|
tasks-status 扫描 Full/Simple 模式 tasks.md 勾选状态
|
|
4583
4687
|
check-task 扫描变更目录 tasks.md 并勾选指定 task_id
|
|
4584
4688
|
archive-docs 将 Simple/Full spec 变更真实移动到 openspec/changes/archive/,并可结束 archive 阶段生成报告
|
|
4585
|
-
semantic-identity 生成或复用实体/版本
|
|
4689
|
+
semantic-identity 生成或复用实体/版本 ID(8 位十六进制);无需网络和中央 ID 服务
|
|
4586
4690
|
semantic-scan 只读解析并校验当前 Change
|
|
4587
4691
|
semantic-reconcile 全量对账文件与本地工作态本体实例
|
|
4588
4692
|
semantic-check 全量对账并在通过时标记 pending
|