kld-sdd 2.8.2 → 2.8.3
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/package.json +1 -1
- package/skywalk-sdd/kb-ingest.cjs +4 -3
- package/skywalk-sdd/kb-upload.cjs +12 -38
- package/skywalk-sdd/ontology/archive-package.cjs +1 -6
- package/templates/skills/kld-sdd/opsx-kb-config/SKILL.md +1 -1
- package/templates/skills/kld-sdd/opsx-kb-config/reference.md +6 -8
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +2 -2
- package/templates/skills/kld-sdd/opsx-kb-ingest/reference.md +7 -5
package/package.json
CHANGED
|
@@ -80,7 +80,7 @@ function classify(local, remote, receipt) {
|
|
|
80
80
|
return { status: 'conflict', label: '请先同步团队 Spec', selected: false };
|
|
81
81
|
}
|
|
82
82
|
async function listChanges(root, options = {}) {
|
|
83
|
-
const c = connection(root), base = path.join(root, 'openspec/changes');
|
|
83
|
+
const c = connection(root, false, options), base = path.join(root, 'openspec/changes');
|
|
84
84
|
const keys = [];
|
|
85
85
|
for (const entry of fs.readdirSync(base, { withFileTypes: true })) {
|
|
86
86
|
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
@@ -97,7 +97,7 @@ async function listChanges(root, options = {}) {
|
|
|
97
97
|
if (ids.slice(i, i + 200).some(id => !remote.has(id))) throw new Error('知识库未完整返回入库状态,请稍后刷新;本次未上传');
|
|
98
98
|
}
|
|
99
99
|
const receipts = configStore.readReceipts(root).receipts || {};
|
|
100
|
-
return { configRevision: c.revision, destination: c.target.kbName || c.target.kbId, items: local.map(item => {
|
|
100
|
+
return { configRevision: c.revision, uploadTarget: { spaceId: c.target.spaceId, kbId: c.target.kbId }, destination: [c.target.spaceName, c.target.kbName || c.target.kbId].filter(Boolean).join(' / '), items: local.map(item => {
|
|
101
101
|
if (item.error) return { key: item.key, status: 'invalid', label: item.error, selected: false };
|
|
102
102
|
let current = remote.get(item.changeId);
|
|
103
103
|
if (!current?.snapshotId && item.legacyChangeId && remote.get(item.legacyChangeId)?.snapshotId) { item.changeId = item.legacyChangeId; current = remote.get(item.changeId); }
|
|
@@ -183,7 +183,8 @@ async function main() {
|
|
|
183
183
|
const { parseArgs, resolveSpecRootFast } = require('./lib/shared.cjs');
|
|
184
184
|
const args = parseArgs(process.argv.slice(2)), root = args.project ? path.resolve(args.project) : resolveSpecRootFast(__dirname);
|
|
185
185
|
const selections = args.selection ? JSON.parse(fs.readFileSync(args.selection, 'utf8')).items : [{ key: args.change }];
|
|
186
|
-
const
|
|
186
|
+
const options = { spaceId: args['space-id'], kbId: args['kb-id'], overrideUploadTarget: Boolean(args['override-upload-target']), configRevision: args.revision, requireReview: Boolean(args['require-review']), confirmReview: Boolean(args['confirm-review']) };
|
|
187
|
+
const result = args.list ? await listChanges(root, options) : await uploadChanges(root, selections, options);
|
|
187
188
|
console.log('SDD_INGEST_RESULT:' + JSON.stringify(result));
|
|
188
189
|
if (result.items.some(x => x.status === 'failed')) process.exitCode = 1;
|
|
189
190
|
}
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
*
|
|
22
22
|
* project_id 解析:
|
|
23
23
|
* 1. $SPEC_ROOT/skywalk-sdd/project-identity.json 的 project_id
|
|
24
|
-
* 2.
|
|
25
|
-
* 3.
|
|
24
|
+
* 2. 缺少身份时自动创建稳定的 Spec 来源标识,不依赖上传库
|
|
25
|
+
* 3. 已有身份(含旧离线身份)原样保留,上传目标只由 uploadTarget 决定
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
const fs = require('fs');
|
|
@@ -173,41 +173,29 @@ function resolveSpecRoot(explicit) {
|
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
175
|
* 解析 project_id
|
|
176
|
-
*
|
|
176
|
+
* 保留 Spec 来源标识;切换知识库不会改变实体及版本命名空间。
|
|
177
177
|
*/
|
|
178
178
|
function resolveProjectId(specRoot, target) {
|
|
179
179
|
const identityPath = path.join(specRoot, 'skywalk-sdd', 'project-identity.json');
|
|
180
|
-
if (!fs.existsSync(identityPath) &&
|
|
180
|
+
if (!fs.existsSync(identityPath) && target) {
|
|
181
181
|
ensureDir(path.dirname(identityPath));
|
|
182
182
|
try {
|
|
183
183
|
fs.writeFileSync(identityPath, JSON.stringify({
|
|
184
184
|
schema_version: 'kld-sdd-project-identity/v1',
|
|
185
|
-
project_id:
|
|
186
|
-
kb_space_id: target.spaceId,
|
|
187
|
-
kb_space_name: target.spaceName || '',
|
|
185
|
+
project_id: 'urn:kld:sdd:project:' + require('crypto').randomBytes(16).toString('hex'),
|
|
188
186
|
created_at: new Date().toISOString(),
|
|
189
187
|
}, null, 2) + '\n', { flag: 'wx' });
|
|
190
188
|
} catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
191
189
|
}
|
|
192
190
|
if (fs.existsSync(identityPath)) {
|
|
193
191
|
const identity = readJson(identityPath);
|
|
194
|
-
const provisional = !identity?.kb_space_id && typeof identity?._warning === 'string'
|
|
195
|
-
&& identity._warning.startsWith('UNCONFIGURED') && /^urn:kld:sdd:project:/.test(identity.project_id || '');
|
|
196
|
-
if (provisional && typeof target?.spaceKey === 'string' && target.spaceKey.trim()) {
|
|
197
|
-
const next = { ...identity, previous_unconfigured_project_id: identity.project_id,
|
|
198
|
-
project_id: target.spaceKey.trim(), kb_space_id: target.spaceId, kb_space_name: target.spaceName || '',
|
|
199
|
-
configured_at: new Date().toISOString() };
|
|
200
|
-
delete next._warning;
|
|
201
|
-
writeJsonAtomic(identityPath, next);
|
|
202
|
-
return next.project_id;
|
|
203
|
-
}
|
|
204
192
|
if (typeof identity?.project_id === 'string' && identity.project_id.trim()) {
|
|
205
193
|
return identity.project_id;
|
|
206
194
|
}
|
|
207
195
|
}
|
|
208
196
|
throw new Error(
|
|
209
197
|
'无法确定 project_id。project-identity.json 不存在或无 project_id 字段。\n' +
|
|
210
|
-
'
|
|
198
|
+
'请检查该文件;上传目标在知识库配置中单独选择。'
|
|
211
199
|
);
|
|
212
200
|
}
|
|
213
201
|
|
|
@@ -278,26 +266,15 @@ function validateRequiredFiles(folderPath, minimumLevel) {
|
|
|
278
266
|
}
|
|
279
267
|
|
|
280
268
|
/**
|
|
281
|
-
*
|
|
269
|
+
* 兼容旧调用:只检查来源标识和有效目标,不限制来源与目标空间一致。
|
|
282
270
|
*/
|
|
283
271
|
function preCheckProjectId(projectId, state, selectedTarget) {
|
|
284
272
|
if (!state.targets || !state.targets[0]) {
|
|
285
273
|
throw new Error('KB 配置中无 targets,请运行 /opsx-kb-config 配置知识库。');
|
|
286
274
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
if (projectId !== spaceKey) {
|
|
293
|
-
throw new Error(
|
|
294
|
-
`PROJECT_SPACE_MISMATCH 预检失败:\n` +
|
|
295
|
-
` project_id (project-identity.json): ${projectId}\n` +
|
|
296
|
-
` spaceKey (kb-state.json): ${spaceKey}\n` +
|
|
297
|
-
`请运行 /opsx-kb-config 更新配置。`
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
console.log(`✓ project_id 预检通过: ${projectId} === ${spaceKey}`);
|
|
275
|
+
if (typeof projectId !== 'string' || !projectId.trim()) throw new Error('Spec 来源标识为空,请修复 project-identity.json');
|
|
276
|
+
const target = selectedTarget || resolveUploadTarget(state);
|
|
277
|
+
if (!target.spaceId || !target.kbId) throw new Error('请选择有效的上传知识库');
|
|
301
278
|
}
|
|
302
279
|
|
|
303
280
|
/**
|
|
@@ -503,13 +480,10 @@ async function main() {
|
|
|
503
480
|
const identityPath = path.join(specRoot, 'skywalk-sdd/project-identity.json');
|
|
504
481
|
const identity = fs.existsSync(identityPath) ? readJson(identityPath) : null;
|
|
505
482
|
if (fs.existsSync(identityPath) && (!identity || typeof identity.project_id !== 'string' || !identity.project_id.trim())) throw new Error('项目身份文件缺少 project_id,已保留原文件,请先修复');
|
|
506
|
-
|
|
507
|
-
&& identity._warning.startsWith('UNCONFIGURED') && /^urn:kld:sdd:project:/.test(identity.project_id || '');
|
|
508
|
-
if (identity?.project_id && !provisional) preCheckProjectId(identity.project_id, state, target);
|
|
509
|
-
else if (!target.spaceKey) throw new Error('上传目标缺少项目标识 spaceKey;请在助手重新登录获取知识库列表,或运行 /opsx-kb-config 补齐');
|
|
483
|
+
if (identity?.project_id) preCheckProjectId(identity.project_id, state, target);
|
|
510
484
|
console.log(JSON.stringify({ configFile: resolveKbStatePath(specRoot), api: state.api,
|
|
511
485
|
uploadTarget: { spaceId: target.spaceId, kbId: target.kbId, spaceKey: target.spaceKey, spaceName: target.spaceName, kbName: target.kbName }, projectId: identity?.project_id || null,
|
|
512
|
-
identityWillBeCreated: !identity
|
|
486
|
+
identityWillBeCreated: !identity, identityWillBeBound: false }, null, 2));
|
|
513
487
|
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
514
488
|
return;
|
|
515
489
|
}
|
|
@@ -57,16 +57,11 @@ function ensureProjectIdentity(projectRoot) {
|
|
|
57
57
|
}
|
|
58
58
|
return existing;
|
|
59
59
|
}
|
|
60
|
-
//
|
|
61
|
-
// 此时 project_id 为随机值,KB 入库时会因 project_id !== spaceKey 而失败
|
|
62
|
-
console.warn('⚠️ project-identity.json 不存在 — 未通过 opsx-kb-config 配置 KB。');
|
|
63
|
-
console.warn('⚠️ 将生成临时 project_id,KB 入库时需确保与 Space spaceKey 一致。');
|
|
64
|
-
console.warn('⚠️ 建议运行 /opsx-kb-config 配置 KB 后更新 project_id 为 Space spaceKey。');
|
|
60
|
+
// Stable source identity is independent of where the package is uploaded.
|
|
65
61
|
const identity = {
|
|
66
62
|
schema_version: PROJECT_IDENTITY_SCHEMA_VERSION,
|
|
67
63
|
project_id: `urn:kld:sdd:project:${generateUuidV7()}`,
|
|
68
64
|
created_at: new Date().toISOString(),
|
|
69
|
-
_warning: 'UNCONFIGURED — 请运行 /opsx-kb-config 配置 KB 后更新 project_id 为 Space spaceKey',
|
|
70
65
|
};
|
|
71
66
|
writeJsonAtomic(identityPath, identity);
|
|
72
67
|
return identity;
|
|
@@ -39,7 +39,7 @@ allowed-tools:
|
|
|
39
39
|
|
|
40
40
|
调用 `saveProjectConfig(specRoot, effectiveConfig, {revision, team})`。保留已有有效字段,只合并用户明确提供的改动。只有用户明确选择共享地址和知识库选择时 team=true;否则 team=false。团队文件存在时,个人保存不能修改团队地址和上传库。
|
|
41
41
|
|
|
42
|
-
所有密钥只落个人文件,公共文件只写白名单字段。解析器自动加入个人文件和本地入库记录的 Git
|
|
42
|
+
所有密钥只落个人文件,公共文件只写白名单字段。解析器自动加入个人文件和本地入库记录的 Git 忽略规则。Spec 来源标识自动生成并长期保留,不绑定上传库。同一 Spec 可分别入库到不同知识库,状态按目标知识库核对;不要求用户填写 project_id。
|
|
43
43
|
|
|
44
44
|
没有 API Key 时推荐用户在托盘登录知识库,按名称多选读取库、单选上传库,连接并保存。不能让用户手工寻找 Space/KB UUID,除非其明确要求手动配置。
|
|
45
45
|
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
| `projectName` | 否 | 历史兼容字段;当前配置流程不要求填写,不作为七种 Skill 使用统计或业务归属依据 |
|
|
43
43
|
| `scopes` | 建议 | API Client scope 列表 |
|
|
44
44
|
| `targets` | 操作前必填 | 多选;可为空仅当尚未完成选择 |
|
|
45
|
-
| `uploadTarget` | 多目标上传前必填 |
|
|
45
|
+
| `uploadTarget` | 多目标上传前必填 | 默认上传位置(可切换),必须引用 `targets` 中的一项;不影响多目标查询 |
|
|
46
46
|
| `updatedAt` | 建议 | ISO-8601 |
|
|
47
47
|
|
|
48
48
|
## 监控与入库分别验证
|
|
@@ -56,20 +56,18 @@
|
|
|
56
56
|
```json
|
|
57
57
|
{
|
|
58
58
|
"schema_version": "kld-sdd-project-identity/v1",
|
|
59
|
-
"project_id": "
|
|
60
|
-
"created_at": "<ISO timestamp>"
|
|
61
|
-
"kb_space_id": "<Space UUID>",
|
|
62
|
-
"kb_space_name": "<Space 名称>"
|
|
59
|
+
"project_id": "<自动生成的稳定 Spec 来源标识>",
|
|
60
|
+
"created_at": "<ISO timestamp>"
|
|
63
61
|
}
|
|
64
62
|
```
|
|
65
63
|
|
|
66
64
|
| 字段 | 必填 | 说明 |
|
|
67
65
|
|------|------|------|
|
|
68
66
|
| `schema_version` | 是 | 固定 `kld-sdd-project-identity/v1` |
|
|
69
|
-
| `project_id` | 是 |
|
|
67
|
+
| `project_id` | 是 | 产物来源标识,自动生成;已有值保持不变,与上传位置无关 |
|
|
70
68
|
| `created_at` | 是 | ISO-8601 |
|
|
71
|
-
| `kb_space_id` | 建议 |
|
|
72
|
-
| `kb_space_name` | 建议 |
|
|
69
|
+
| `kb_space_id` | 建议 | 历史配置元数据,保留但不限制上传目标 |
|
|
70
|
+
| `kb_space_name` | 建议 | 历史配置元数据,保留但不限制上传目标 |
|
|
73
71
|
|
|
74
72
|
## 鉴权
|
|
75
73
|
|
|
@@ -25,9 +25,9 @@ description: >-
|
|
|
25
25
|
|
|
26
26
|
通过 `skywalk-sdd/lib/kb-config.cjs` 的 `readProjectConfig(specRoot)` 读取有效配置,不能绕过公共解析器只读个人文件。
|
|
27
27
|
|
|
28
|
-
- `kb.config.json`:团队共享地址、读取知识库 `targets
|
|
28
|
+
- `kb.config.json`:团队共享地址、读取知识库 `targets`、默认上传位置 `uploadTarget`。可提交 Git,不允许包含 Key。
|
|
29
29
|
- `kb-state.json`:个人 API Key 和额外读取范围;不提交 Git。旧项目只存在这个文件时继续兼容。
|
|
30
|
-
-
|
|
30
|
+
- 同一 Spec 可分别上传到多个知识库,入库状态按目标知识库核对;切换知识库不得改写 Spec 的 project_id。上传默认采用团队上传位置;无团队文件时采用 `kb-state.json.uploadTarget`。旧单目标且未声明 uploadTarget 时可兼容唯一目标;显式 null 必须停止,不能回退。
|
|
31
31
|
- 禁止直接使用 `targets[0]`,不能把内容广播到所有读取库。正常上传不要重复拼接 Space/KB 参数。调用 `kb-upload.cjs` 时默认不再重复拼接目标参数。
|
|
32
32
|
- 团队地址变化后必须重新登录;不能把旧 Key 发给新地址。
|
|
33
33
|
|
|
@@ -152,7 +152,7 @@ POST {base}/ingestions/{jobId}/retry
|
|
|
152
152
|
| errorCode | 含义 |
|
|
153
153
|
|-----------|------|
|
|
154
154
|
| `INGEST_FAILED` | 通用入库失败 |
|
|
155
|
-
| `PROJECT_SPACE_MISMATCH` |
|
|
155
|
+
| 旧服务返回 `PROJECT_SPACE_MISMATCH` | 服务仍限制来源项目与上传空间一致;部署新版知识库服务,不修改产物标识绕过 |
|
|
156
156
|
| `ARCHIVE_IMMUTABILITY_VIOLATION` | 同一 archive_id 已存在不同 content_hash |
|
|
157
157
|
| `VERSION_IDENTITY_CONFLICT` | version-id 已存在但 content_hash 或 entity_id 不同;错误消息含 anchorId / name / versionId 帮助定位。该 Change 之前已入库,需对被修改的实体按「重新入库前:KB 身份同步」步骤更新 delta-state=modified + predecessor-version 后重新上传 |
|
|
158
158
|
| `SEM_VERSION_LINEAGE_MISSING` | 语义校验阶段:modified/removed 实体的 predecessor-version 在本地 archive 中找不到对应版本。检查 predecessor-version 是否来自 KB resolve 返回的 entityVersionId |
|
|
@@ -221,7 +221,7 @@ requirement 条目可选 `feature_id`(同 feature 文法);配对关系随
|
|
|
221
221
|
```json
|
|
222
222
|
{
|
|
223
223
|
"archive_id": "2026-07-18-user-center",
|
|
224
|
-
"project_id": "
|
|
224
|
+
"project_id": "<产物稳定的来源标识,与上传库无关>",
|
|
225
225
|
"content_hash": "sha256:<canonical-facts.json 的哈希>",
|
|
226
226
|
"files": [
|
|
227
227
|
{ "path": "canonical-facts.json", "content_hash": "sha256:…" },
|
|
@@ -263,9 +263,11 @@ node skywalk-sdd/kb-upload.cjs --package=<zip-path> [--space-id=<id>] [--kb-id=<
|
|
|
263
263
|
### project_id 解析优先级
|
|
264
264
|
|
|
265
265
|
1. `$SPEC_ROOT/skywalk-sdd/project-identity.json` 的 `project_id` 字段
|
|
266
|
-
2.
|
|
267
|
-
3.
|
|
268
|
-
4.
|
|
266
|
+
2. 文件不存在 → 首次上传自动创建稳定来源标识,不要求 spaceKey,不从上传库生成来源身份
|
|
267
|
+
3. 旧离线 `UNCONFIGURED` 标识、旧 spaceKey 标识以及历史归档均原样保留;切换上传库不重建身份
|
|
268
|
+
4. 已有来源文件损坏或缺少 project_id → 停止并提示修复,不覆盖原文件。来源标识无需与任何 Space key 相同。
|
|
269
|
+
|
|
270
|
+
同一 Spec 可以上传到多个知识库。每次只上传用户明确选择的目标,按规范化服务地址 + spaceId + kbId + Change ID 核对远端快照和来源 SHA-256;A 库已上传不影响 B 库首次上传。临时改投不改写团队默认值;库内并发基线保护仍然有效。
|
|
269
271
|
|
|
270
272
|
### 上传等级校验
|
|
271
273
|
|