kld-sdd 2.7.8-5 → 2.7.8-6
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/bin/kld-sdd-init.js +86 -1
- package/lib/init.js +13 -1
- package/lib/maintenance.js +126 -1
- package/lib/skills-bundle.js +2 -0
- package/package.json +2 -2
- package/skywalk-sdd/context-client.cjs +38 -7
- package/skywalk-sdd/kb-upload.cjs +73 -28
- package/skywalk-sdd/ontology/archive-package.cjs +145 -0
- package/skywalk-sdd/ontology/artifact-parser.cjs +24 -3
- package/skywalk-sdd/ontology/cli.cjs +53 -0
- package/skywalk-sdd/ontology/id.cjs +1 -1
- package/skywalk-sdd/ontology/schema.cjs +6 -0
- package/skywalk-sdd/ontology/spec-ontology.cjs +384 -0
- package/templates/openspec/spec.md +8 -0
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +10 -10
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +3 -2
- package/templates/skills/kld-sdd/opsx-ontology-ingest/SKILL.md +75 -0
- package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +5 -4
- package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +14 -1
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +5 -3
package/bin/kld-sdd-init.js
CHANGED
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const { listToolProfileIds, buildToolConfigs } = require('../lib/tool-profiles');
|
|
22
22
|
const { MODE, OP, modeLabel } = require('../lib/uninstall');
|
|
23
|
-
const
|
|
23
|
+
const specDetect = require('../skywalk-sdd/ontology/spec-detect.cjs');
|
|
24
|
+
const { specSourceLabel } = specDetect;
|
|
24
25
|
// spec 候选单选交互(唯一实现,与 lib/init.js 共用)
|
|
25
26
|
const { selectSpecRoot: selectSpecChoice } = require('../lib/spec-choice');
|
|
26
27
|
|
|
@@ -125,6 +126,9 @@ function parseInitArgs(argv = cliArgs(), env = process.env) {
|
|
|
125
126
|
verbose: isVerbose(argv, env),
|
|
126
127
|
skipOpenspec: argv.includes('--skip-openspec'),
|
|
127
128
|
skipTemplate: argv.includes('--skip-template'),
|
|
129
|
+
repairGit: argv[0] === 'repair-git' || argv.includes('--repair-git'),
|
|
130
|
+
applyRepair: argv.includes('--apply'),
|
|
131
|
+
json: argv.includes('--json'),
|
|
128
132
|
tools,
|
|
129
133
|
// 显式给了 --tool 即自动化场景:lib 据此抑制 spec 路径的交互询问
|
|
130
134
|
hasToolArg: tools !== null,
|
|
@@ -357,6 +361,7 @@ KLD SDD 项目初始化工具
|
|
|
357
361
|
kld-sdd-init [选项]
|
|
358
362
|
npx kld-sdd [选项]
|
|
359
363
|
kld-sdd sync-repos # 工作目录下发现新增代码仓并轻量接入(多 spec 候选时单选)
|
|
364
|
+
kld-sdd repair-git # 检查并清理旧 Spec 仓的工具跟踪噪声(默认只预览)
|
|
360
365
|
kld-sdd uninstall [选项] # 卸载 SDD 产物(渐进式:预览工具范围 → 确认)
|
|
361
366
|
kld-sdd uninstall --help # 查看工具卸载与保留范围
|
|
362
367
|
|
|
@@ -366,6 +371,9 @@ KLD SDD 项目初始化工具
|
|
|
366
371
|
--verbose 输出每个安装文件/目录的明细日志(默认仅摘要,亦可用 KLD_SDD_VERBOSE=1)
|
|
367
372
|
--skip-openspec 跳过 openspec init 步骤
|
|
368
373
|
--skip-template 跳过复制内置模版
|
|
374
|
+
--repair-git 检查 Spec 仓中被 Git 跟踪的安装器工具(默认只预览)
|
|
375
|
+
--apply 与 --repair-git 同用:保留本地文件,仅从 Git 索引移除工具
|
|
376
|
+
--json --repair-git 输出机器可读 JSON,供托盘健康检查使用
|
|
369
377
|
--tool <name> 指定编辑器,可用值: cursor, claude, codebuddy, qoder, opencode, kunlunzhima, workbuddy, codex, all
|
|
370
378
|
--spec-path <dir> 指定 spec 包裹包目录(必须是当前目录的子目录)。
|
|
371
379
|
不指定时(交互终端):自动检测已有 spec 目录(目录名 *-sdd-specs,
|
|
@@ -385,6 +393,8 @@ KLD SDD 项目初始化工具
|
|
|
385
393
|
kld-sdd-init --skip-openspec # 跳过 openspec,直接部署 skills
|
|
386
394
|
kld-sdd-init --tool codex # 仅部署 Codex 配置
|
|
387
395
|
kld-sdd-init --spec-path erp-sdd-specs # 指定 spec 目录(当前目录的子目录)
|
|
396
|
+
kld-sdd repair-git --spec-path=. # 预览旧仓工具跟踪问题
|
|
397
|
+
kld-sdd repair-git --apply --spec-path=. # 一次迁出工具,文件仍留在磁盘
|
|
388
398
|
kld-sdd sync-repos # 新代码仓加入后同步接入
|
|
389
399
|
`);
|
|
390
400
|
}
|
|
@@ -393,6 +403,72 @@ KLD SDD 项目初始化工具
|
|
|
393
403
|
// 子命令路由:解析参数后调用 lib/ 业务实现
|
|
394
404
|
// ═══════════════════════════════════════════════════════════════
|
|
395
405
|
|
|
406
|
+
function resolveRepairSpecRoot(parsed, cwd = process.cwd()) {
|
|
407
|
+
const root = path.resolve(cwd);
|
|
408
|
+
if (parsed.specPath) {
|
|
409
|
+
const explicit = path.resolve(root, parsed.specPath);
|
|
410
|
+
if (!specDetect.isSpecPackageRoot(explicit)) {
|
|
411
|
+
throw new Error(`指定目录不是有效的 Spec 包裹包: ${explicit}`);
|
|
412
|
+
}
|
|
413
|
+
return explicit;
|
|
414
|
+
}
|
|
415
|
+
if (specDetect.isSpecPackageRoot(root)) return root;
|
|
416
|
+
const candidates = specDetect.detectSpecCandidatesWithGit(root);
|
|
417
|
+
if (candidates.length === 1) return candidates[0].abs;
|
|
418
|
+
if (candidates.length === 0) throw new Error('未发现 Spec 包裹包,请用 --spec-path=<dir> 指定');
|
|
419
|
+
throw new Error(`发现 ${candidates.length} 个 Spec 包裹包,请用 --spec-path=<dir> 指定,不自动猜测`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function runGitRepair(parsed) {
|
|
423
|
+
const init = require('../lib/init');
|
|
424
|
+
const maintenance = require('../lib/maintenance');
|
|
425
|
+
const specRoot = resolveRepairSpecRoot(parsed);
|
|
426
|
+
const plan = maintenance.trackedManagedSpecTools(specRoot);
|
|
427
|
+
if (!plan.ok) throw new Error(plan.error);
|
|
428
|
+
|
|
429
|
+
if (!parsed.applyRepair) {
|
|
430
|
+
const payload = {
|
|
431
|
+
mode: 'preview',
|
|
432
|
+
specRoot,
|
|
433
|
+
trackedToolCount: plan.files.length,
|
|
434
|
+
trackedTools: plan.files.map((file) => file.relative),
|
|
435
|
+
businessFilesAffected: 0,
|
|
436
|
+
};
|
|
437
|
+
if (parsed.json) {
|
|
438
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
console.log(`🧹 Spec Git 清洁度检查: ${path.basename(specRoot)}`);
|
|
442
|
+
console.log(` 被 Git 跟踪的可再生成工具: ${plan.files.length} 个`);
|
|
443
|
+
for (const file of plan.files.slice(0, 8)) console.log(` - ${file.relative}`);
|
|
444
|
+
if (plan.files.length > 8) console.log(` … 其余 ${plan.files.length - 8} 个`);
|
|
445
|
+
console.log(' 业务 Spec、配置、项目身份与 Hook 参数: 不处理');
|
|
446
|
+
if (plan.files.length) console.log(' 执行 --repair-git --apply 可一次迁出工具;不会删除磁盘文件。');
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const existingBefore = plan.files.filter((file) => require('fs').existsSync(path.join(specRoot, file.relative)));
|
|
451
|
+
init.updateGitignore(specRoot, { specPackage: true });
|
|
452
|
+
const result = maintenance.untrackManagedSpecTools(specRoot);
|
|
453
|
+
if (!result.ok) throw new Error(result.error);
|
|
454
|
+
const payload = {
|
|
455
|
+
mode: 'applied',
|
|
456
|
+
specRoot,
|
|
457
|
+
untrackedToolCount: result.files.length,
|
|
458
|
+
filesRemainOnDisk: existingBefore.every((file) => require('fs').existsSync(path.join(specRoot, file.relative))),
|
|
459
|
+
retainedLocalToolCount: existingBefore.length,
|
|
460
|
+
alreadyMissingToolCount: result.files.length - existingBefore.length,
|
|
461
|
+
businessFilesAffected: 0,
|
|
462
|
+
};
|
|
463
|
+
if (parsed.json) {
|
|
464
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
console.log(`✅ 已将 ${result.files.length} 个安装器工具移出 Git 跟踪,现存的 ${existingBefore.length} 个本地文件全部保留`);
|
|
468
|
+
if (payload.alreadyMissingToolCount) console.log(` ℹ️ 其中 ${payload.alreadyMissingToolCount} 个文件在修复前已从磁盘删除`);
|
|
469
|
+
console.log(' 请审阅一次 git status 并提交迁移;以后升级不再污染业务改动列表。');
|
|
470
|
+
}
|
|
471
|
+
|
|
396
472
|
async function runInit() {
|
|
397
473
|
let parsed;
|
|
398
474
|
try {
|
|
@@ -412,6 +488,15 @@ async function runInit() {
|
|
|
412
488
|
return;
|
|
413
489
|
}
|
|
414
490
|
|
|
491
|
+
if (parsed.repairGit) {
|
|
492
|
+
try {
|
|
493
|
+
runGitRepair(parsed);
|
|
494
|
+
} catch (error) {
|
|
495
|
+
fail(`Git 清洁度修复失败: ${error.message}`);
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
415
500
|
const init = require('../lib/init');
|
|
416
501
|
init.setVerbose(parsed.verbose);
|
|
417
502
|
|
package/lib/init.js
CHANGED
|
@@ -1282,6 +1282,9 @@ async function populateSpecPackage(specRoot, options = {}) {
|
|
|
1282
1282
|
copyTemplatesToProject(root);
|
|
1283
1283
|
initGlobalOverview(root);
|
|
1284
1284
|
deploySddGuideManual(root);
|
|
1285
|
+
// Spec 包可能是独立 Git 仓或单仓内的子目录;忽略规则必须落在它自身,
|
|
1286
|
+
// 不能只写工作区根,否则升级会把整套可再生成运行时显示为业务改动。
|
|
1287
|
+
updateGitignore(root, { specPackage: true });
|
|
1285
1288
|
console.log(`✅ SDD 包裹包已就绪: ${path.basename(root)}`);
|
|
1286
1289
|
return { ok: true, path: root };
|
|
1287
1290
|
}
|
|
@@ -1376,7 +1379,7 @@ function removeOuterSkywalk(anchorDir) {
|
|
|
1376
1379
|
* 创建 / 更新 .gitignore
|
|
1377
1380
|
* @param {string} [cwd=process.cwd()]
|
|
1378
1381
|
*/
|
|
1379
|
-
function updateGitignore(cwd = process.cwd()) {
|
|
1382
|
+
function updateGitignore(cwd = process.cwd(), options = {}) {
|
|
1380
1383
|
const root = path.resolve(cwd);
|
|
1381
1384
|
const gitignorePath = path.join(root, '.gitignore');
|
|
1382
1385
|
const requiredLines = [
|
|
@@ -1395,6 +1398,10 @@ function updateGitignore(cwd = process.cwd()) {
|
|
|
1395
1398
|
'**/skywalk-sdd/state/',
|
|
1396
1399
|
'.worktrees/'
|
|
1397
1400
|
];
|
|
1401
|
+
const specPackageLines = options.specPackage ? maintenance.specPackageIgnoreLines() : [];
|
|
1402
|
+
if (options.specPackage) {
|
|
1403
|
+
requiredLines.push(...specPackageLines);
|
|
1404
|
+
}
|
|
1398
1405
|
|
|
1399
1406
|
const sddConfig = `
|
|
1400
1407
|
# KLD SDD AI 编辑器个人配置(请勿提交)
|
|
@@ -1418,6 +1425,10 @@ skywalk-sdd/state/
|
|
|
1418
1425
|
|
|
1419
1426
|
# SDD Apply 隔离 worktree(本地临时目录)
|
|
1420
1427
|
.worktrees/
|
|
1428
|
+
${options.specPackage ? `
|
|
1429
|
+
# KLD SDD 安装器工具(可由 init/升级重新生成,不属于业务 Spec)
|
|
1430
|
+
${specPackageLines.join('\n')}
|
|
1431
|
+
` : ''}
|
|
1421
1432
|
`;
|
|
1422
1433
|
|
|
1423
1434
|
const result = deployFile(null, gitignorePath, {
|
|
@@ -1868,4 +1879,5 @@ module.exports = {
|
|
|
1868
1879
|
promptSpecPath,
|
|
1869
1880
|
listSpecCandidatesForInit,
|
|
1870
1881
|
deployCodeRepoHooks,
|
|
1882
|
+
updateGitignore,
|
|
1871
1883
|
};
|
package/lib/maintenance.js
CHANGED
|
@@ -3,11 +3,124 @@
|
|
|
3
3
|
// 安装、升级、卸载共用的边界;业务文档和关联配置不属于可清理的工具文件。
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
6
7
|
const sdd = require('../skywalk-sdd/ontology/sdd-config.cjs');
|
|
7
8
|
const layout = require('./workspace-layout');
|
|
8
9
|
const detect = require('../skywalk-sdd/ontology/spec-detect.cjs');
|
|
9
10
|
const pkg = path.resolve(__dirname, '..');
|
|
10
11
|
|
|
12
|
+
function posix(relative) {
|
|
13
|
+
return String(relative || '').replace(/\\/g, '/').replace(/^\.\//, '');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const MANAGED_ROOT_TOOLS = new Set([
|
|
17
|
+
'apply-worktree-finish.cjs',
|
|
18
|
+
'context-client.cjs',
|
|
19
|
+
'kb-sync-identity.cjs',
|
|
20
|
+
'kb-upload.cjs',
|
|
21
|
+
'log.cjs',
|
|
22
|
+
'metrics-v3.cjs',
|
|
23
|
+
'openspec-shim.cjs',
|
|
24
|
+
'runtime-metadata.cjs',
|
|
25
|
+
'spec-root.cjs',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const MANAGED_HOOK_TOOLS = new Set([
|
|
29
|
+
'commit-msg-runner.cjs',
|
|
30
|
+
'commit-msg-sdd-trailer.cjs',
|
|
31
|
+
'consistency-check-core.cjs',
|
|
32
|
+
'delivery-check.cjs',
|
|
33
|
+
'delivery-snapshot.cjs',
|
|
34
|
+
'pre-commit-consistency-check.cjs',
|
|
35
|
+
'pre-commit-sdd-check.cjs',
|
|
36
|
+
'pre-push-consistency-check.cjs',
|
|
37
|
+
'pre-push-sdd-check.cjs',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
function specPackageIgnoreLines() {
|
|
41
|
+
return [
|
|
42
|
+
...[...MANAGED_ROOT_TOOLS].sort().map((name) => `/skywalk-sdd/${name}`),
|
|
43
|
+
'/skywalk-sdd/lib/',
|
|
44
|
+
'/skywalk-sdd/ontology/',
|
|
45
|
+
'/skywalk-sdd/reporting/',
|
|
46
|
+
'/skywalk-sdd/ci/github-actions-sdd.yml',
|
|
47
|
+
'/skywalk-sdd/ci/gitlab-ci-sdd.yml',
|
|
48
|
+
...[...MANAGED_HOOK_TOOLS].sort().map((name) => `/skywalk-sdd/git-hooks/${name}`),
|
|
49
|
+
'/openspec-templates/',
|
|
50
|
+
'/openspec/kld-sdd操作手册.html',
|
|
51
|
+
'/_tmp_*',
|
|
52
|
+
'/*-start.log',
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 只识别能够由 kld-sdd 安装器重新生成的文件。
|
|
58
|
+
* 业务 Spec、项目身份、Hook 参数、KB 配置和历史数据都不属于工具文件。
|
|
59
|
+
*/
|
|
60
|
+
function isManagedSpecToolPath(relative) {
|
|
61
|
+
const rel = posix(relative);
|
|
62
|
+
if (rel === 'openspec/kld-sdd操作手册.html' || rel.startsWith('openspec-templates/')) return true;
|
|
63
|
+
if (rel.startsWith('skywalk-sdd/') && MANAGED_ROOT_TOOLS.has(rel.slice('skywalk-sdd/'.length))) return true;
|
|
64
|
+
if (/^skywalk-sdd\/(?:lib|ontology|reporting)\//.test(rel)) return true;
|
|
65
|
+
if (/^skywalk-sdd\/ci\/(?:github-actions-sdd|gitlab-ci-sdd)\.yml$/.test(rel)) return true;
|
|
66
|
+
const hookPrefix = 'skywalk-sdd/git-hooks/';
|
|
67
|
+
return rel.startsWith(hookPrefix) && MANAGED_HOOK_TOOLS.has(rel.slice(hookPrefix.length));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function git(cwd, args) {
|
|
71
|
+
const result = spawnSync('git', args, {
|
|
72
|
+
cwd,
|
|
73
|
+
encoding: 'utf8',
|
|
74
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
75
|
+
});
|
|
76
|
+
if (result.status !== 0) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
error: String(result.stderr || result.stdout || 'git 命令失败').trim(),
|
|
80
|
+
output: '',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { ok: true, output: String(result.stdout || '') };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 列出当前 Spec 包中仍被 Git 跟踪的安装器工具文件。 */
|
|
87
|
+
function trackedManagedSpecTools(specRoot) {
|
|
88
|
+
const requestedRoot = path.resolve(specRoot);
|
|
89
|
+
const root = fs.existsSync(requestedRoot) ? fs.realpathSync(requestedRoot) : requestedRoot;
|
|
90
|
+
const top = git(root, ['rev-parse', '--show-toplevel']);
|
|
91
|
+
if (!top.ok) return { ok: false, specRoot: root, files: [], error: '当前 Spec 目录不在 Git 仓库中' };
|
|
92
|
+
const gitRoot = path.resolve(top.output.trim());
|
|
93
|
+
const rootRelative = posix(path.relative(gitRoot, root));
|
|
94
|
+
if (rootRelative.startsWith('../') || path.isAbsolute(rootRelative)) {
|
|
95
|
+
return { ok: false, specRoot: root, gitRoot, files: [], error: 'Spec 目录不属于检测到的 Git 仓库' };
|
|
96
|
+
}
|
|
97
|
+
const pathspec = rootRelative ? `${rootRelative}/` : '.';
|
|
98
|
+
const listed = git(gitRoot, ['ls-files', '-z', '--', pathspec]);
|
|
99
|
+
if (!listed.ok) return { ok: false, specRoot: root, gitRoot, files: [], error: listed.error };
|
|
100
|
+
const prefix = rootRelative ? `${rootRelative}/` : '';
|
|
101
|
+
const files = listed.output.split('\0').filter(Boolean).map(posix).flatMap((gitPath) => {
|
|
102
|
+
if (prefix && !gitPath.startsWith(prefix)) return [];
|
|
103
|
+
const relative = prefix ? gitPath.slice(prefix.length) : gitPath;
|
|
104
|
+
return isManagedSpecToolPath(relative) ? [{ gitPath, relative }] : [];
|
|
105
|
+
});
|
|
106
|
+
return { ok: true, specRoot: root, gitRoot, files };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 将安装器工具从 Git 索引移除,文件仍留在磁盘供本地运行。
|
|
111
|
+
* 调用方必须先把对应规则写入 Spec 根 .gitignore。
|
|
112
|
+
*/
|
|
113
|
+
function untrackManagedSpecTools(specRoot) {
|
|
114
|
+
const plan = trackedManagedSpecTools(specRoot);
|
|
115
|
+
if (!plan.ok || plan.files.length === 0) return plan;
|
|
116
|
+
for (let offset = 0; offset < plan.files.length; offset += 100) {
|
|
117
|
+
const batch = plan.files.slice(offset, offset + 100).map((file) => file.gitPath);
|
|
118
|
+
const removed = git(plan.gitRoot, ['update-index', '--force-remove', '--', ...batch]);
|
|
119
|
+
if (!removed.ok) return { ...plan, ok: false, error: removed.error };
|
|
120
|
+
}
|
|
121
|
+
return { ...plan, untracked: plan.files.length };
|
|
122
|
+
}
|
|
123
|
+
|
|
11
124
|
function filesUnder(root, prefix = '') {
|
|
12
125
|
if (!fs.existsSync(root) || !fs.lstatSync(root).isDirectory()) return [];
|
|
13
126
|
return fs.readdirSync(root).flatMap(name => {
|
|
@@ -93,4 +206,16 @@ function maintenanceConflicts(root, spec, repos) {
|
|
|
93
206
|
return failures;
|
|
94
207
|
}
|
|
95
208
|
|
|
96
|
-
module.exports = {
|
|
209
|
+
module.exports = {
|
|
210
|
+
filesUnder,
|
|
211
|
+
regularFileWithin,
|
|
212
|
+
runtimeToolFiles,
|
|
213
|
+
specPackageIgnoreLines,
|
|
214
|
+
isManagedSpecToolPath,
|
|
215
|
+
trackedManagedSpecTools,
|
|
216
|
+
untrackManagedSpecTools,
|
|
217
|
+
samePath,
|
|
218
|
+
associationConflict,
|
|
219
|
+
workspaceCodeRepos,
|
|
220
|
+
maintenanceConflicts,
|
|
221
|
+
};
|
package/lib/skills-bundle.js
CHANGED
|
@@ -40,6 +40,7 @@ const OPSX_SKILL_DIRS = [
|
|
|
40
40
|
// Engineering KB 依赖技能(随 init 部署;propose/spec/archive 硬依赖)
|
|
41
41
|
'opsx-kb-config',
|
|
42
42
|
'opsx-ontology-query',
|
|
43
|
+
'opsx-ontology-ingest',
|
|
43
44
|
'opsx-kb-ingest',
|
|
44
45
|
// Spec 一致性校验技能
|
|
45
46
|
'opsx-consistency-check',
|
|
@@ -122,6 +123,7 @@ const KUNLUN_SLASH_COMMANDS = [
|
|
|
122
123
|
'archive',
|
|
123
124
|
'explore',
|
|
124
125
|
'ontology-query',
|
|
126
|
+
'ontology-ingest',
|
|
125
127
|
'kb-ingest',
|
|
126
128
|
'kb-config',
|
|
127
129
|
'consistency-check',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kld-sdd",
|
|
3
|
-
"version": "2.7.8-
|
|
3
|
+
"version": "2.7.8-6",
|
|
4
4
|
"description": "KLD SDD OpenSpec 项目初始化工具 - 一键部署 SDD skills",
|
|
5
5
|
"main": "lib/init.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"bench:check": "node test/benchmark-check.cjs",
|
|
12
|
-
"test": "node --test test/delivery-hooks.test.cjs test/execution-friction.test.cjs test/kb-fact-lifecycle.test.cjs test/workflow-usability.test.cjs test/check-efficiency.test.cjs && node test/external-key.cjs && node test/change-key.cjs && node test/modules-and-sdd-config.cjs && node test/active-changes.cjs && node test/hook-layouts.cjs && node test/spec-package-init.cjs && node test/prompt-spec-path.cjs && node test/spec-detect.cjs && node test/workspace-layout.cjs && node test/sync-repos-spec-choice.cjs && node test/ontology-release-blockers.cjs && node test/ontology-semantic-core.cjs && node test/ontology-identity-versioning.cjs && node test/ontology-identity-continuity.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 test/evidence-integrity-oracle.cjs test/evidence-integrity-wiring.cjs test/change-report-correctness.cjs test/report-metrics-correctness.cjs test/change-report-model-ui.cjs && node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/deploy-strategy.cjs && node test/uninstall.cjs && node --test test/maintenance.test.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 && node test/init-report-runtime.cjs && node test/init-output-verbosity.cjs && node --test test/usage-contract.test.cjs test/user-config.test.cjs test/usage-runtime-removal.test.cjs test/runtime/usage-reporter.test.cjs test/runtime/usage-reporting.test.cjs test/runtime/git-identity.test.cjs test/runtime/kb-state-location.test.cjs && node test/runtime/metrics-v3.test.cjs && node test/runtime/fixback.test.cjs && node test/progress-package-artifact.test.cjs && node --test test/progress-package-install.e2e.cjs && node test/consistency-check-core.cjs"
|
|
12
|
+
"test": "node --test test/delivery-hooks.test.cjs test/execution-friction.test.cjs test/kb-fact-lifecycle.test.cjs test/workflow-usability.test.cjs test/check-efficiency.test.cjs && node test/external-key.cjs && node test/change-key.cjs && node test/modules-and-sdd-config.cjs && node test/active-changes.cjs && node test/hook-layouts.cjs && node test/spec-package-init.cjs && node test/prompt-spec-path.cjs && node test/spec-detect.cjs && node test/workspace-layout.cjs && node test/sync-repos-spec-choice.cjs && node test/ontology-release-blockers.cjs && node test/ontology-semantic-core.cjs && node test/ontology-identity-versioning.cjs && node test/ontology-identity-continuity.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/spec-ontology-compatibility.cjs && node test/archive-package-producer.cjs && node --test test/evidence-integrity-oracle.cjs test/evidence-integrity-wiring.cjs test/change-report-correctness.cjs test/report-metrics-correctness.cjs test/change-report-model-ui.cjs && node test/validate-skills-bundle.cjs && node test/tool-profiles.cjs && node test/deploy-strategy.cjs && node test/uninstall.cjs && node --test test/maintenance.test.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 && node test/init-report-runtime.cjs && node test/init-output-verbosity.cjs && node --test test/usage-contract.test.cjs test/user-config.test.cjs test/usage-runtime-removal.test.cjs test/runtime/usage-reporter.test.cjs test/runtime/usage-reporting.test.cjs test/runtime/git-identity.test.cjs test/runtime/kb-state-location.test.cjs && node test/runtime/metrics-v3.test.cjs && node test/runtime/fixback.test.cjs && node test/progress-package-artifact.test.cjs && node --test test/progress-package-install.e2e.cjs && node test/consistency-check-core.cjs"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"kld",
|
|
@@ -37,6 +37,9 @@ function buildEndpoint(apiBase, spaceId, kbId, mode) {
|
|
|
37
37
|
if (mode === 'resolve') {
|
|
38
38
|
return `${base}/v1/spaces/${space}/knowledge-bases/${kb}/entities/resolve`;
|
|
39
39
|
}
|
|
40
|
+
if (mode === 'spec-generation') {
|
|
41
|
+
return `${base}/v1/spaces/${space}/knowledge-bases/${kb}/context/spec-generation`;
|
|
42
|
+
}
|
|
40
43
|
return `${base}/v1/spaces/${space}/knowledge-bases/${kb}/context/match-requirement`;
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -61,7 +64,14 @@ function compactContext(data, includeCandidates, mode = 'match') {
|
|
|
61
64
|
warnings: data.warnings,
|
|
62
65
|
clarificationQuestions: data.clarificationQuestions,
|
|
63
66
|
specGenerationContext: data.specGenerationContext,
|
|
67
|
+
matchedCapabilities: data.matchedCapabilities,
|
|
68
|
+
matchedScenarios: data.matchedScenarios,
|
|
69
|
+
businessObjects: data.businessObjects,
|
|
70
|
+
rules: data.rules,
|
|
71
|
+
specPatterns: data.specPatterns,
|
|
72
|
+
compatibilityMode: data.compatibilityMode,
|
|
64
73
|
supportEvidence: data.supportEvidence,
|
|
74
|
+
candidateEvidence: data.candidateEvidence,
|
|
65
75
|
oppositionEvidence: data.oppositionEvidence,
|
|
66
76
|
};
|
|
67
77
|
// Continuity resolve 必须透出 candidates;match 默认隐藏内部候选除非 --include-candidates
|
|
@@ -171,19 +181,40 @@ async function retrieveContext(args = parseArgs(process.argv), env = process.env
|
|
|
171
181
|
return { ...unavailableContext('engineering_kb_not_configured', 'KB 尚未配置,本地工作可以继续'), configured: false };
|
|
172
182
|
}
|
|
173
183
|
|
|
174
|
-
const
|
|
184
|
+
const requestedMode = String(args.mode || 'match').trim().toLowerCase();
|
|
185
|
+
const mode = requestedMode === 'resolve'
|
|
186
|
+
? 'resolve'
|
|
187
|
+
: (['spec-generation', 'spec_generation', 'generation'].includes(requestedMode)
|
|
188
|
+
? 'spec-generation' : 'match');
|
|
175
189
|
const timeoutMs = contextTimeout(args, env, 5000);
|
|
176
190
|
const payload = buildPayload(args, mode);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
let responseMode = mode;
|
|
192
|
+
let data;
|
|
193
|
+
try {
|
|
194
|
+
data = await requestJson(
|
|
195
|
+
buildEndpoint(apiBase, spaceId, kbId, mode),
|
|
196
|
+
payload,
|
|
197
|
+
token,
|
|
198
|
+
timeoutMs,
|
|
199
|
+
);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
const endpointUnavailable = /Not Found|HTTP 404/i.test(error.message);
|
|
202
|
+
if (mode !== 'spec-generation' || !endpointUnavailable) throw error;
|
|
203
|
+
data = await requestJson(
|
|
204
|
+
buildEndpoint(apiBase, spaceId, kbId, 'match'),
|
|
205
|
+
payload,
|
|
206
|
+
token,
|
|
207
|
+
timeoutMs,
|
|
208
|
+
);
|
|
209
|
+
responseMode = 'match';
|
|
210
|
+
}
|
|
183
211
|
return {
|
|
184
212
|
available: true,
|
|
185
213
|
mode,
|
|
214
|
+
responseMode,
|
|
215
|
+
remoteContextVerified: true,
|
|
186
216
|
...compactContext(data, Boolean(args['include-candidates']), mode),
|
|
217
|
+
compatibilityMode: responseMode === mode ? (data.compatibilityMode || 'NATIVE') : 'LEGACY_V2',
|
|
187
218
|
};
|
|
188
219
|
}
|
|
189
220
|
|
|
@@ -50,15 +50,18 @@ function cacheKey(changeName, context) {
|
|
|
50
50
|
return JSON.stringify([context.api.replace(/\/+$/, ''), context.spaceId, context.kbId, changeName]);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function assertIngestionResult(result) {
|
|
53
|
+
function assertIngestionResult(result, options = {}) {
|
|
54
54
|
if (result?.code != null && result.code !== 0) throw new Error(result.message || 'KB API 拒绝入库');
|
|
55
55
|
const job = result?.data || result;
|
|
56
56
|
if (!job || !['projecting', 'succeeded'].includes(job.status) || job.relationStatus !== 'committed') {
|
|
57
57
|
throw new Error(`INGEST_NOT_COMMITTED: ${job?.errorCode || job?.status || 'unknown'} ${job?.errorMessage || ''} job=${job?.jobId || 'unknown'}`);
|
|
58
58
|
}
|
|
59
|
-
if (Number(job.report?.version_conflicts || 0) > 0
|
|
59
|
+
if (Number(job.report?.version_conflicts || 0) > 0) {
|
|
60
60
|
throw new Error(`VERSION_CONFLICT: 事实包已保存为候选,当前事实未更新;请合并基线后重新发布。job=${job.jobId}`);
|
|
61
61
|
}
|
|
62
|
+
if (job.report?.publication_status === 'needs_review' && !options.allowNeedsReview) {
|
|
63
|
+
throw new Error(`REVIEW_REQUIRED: 事实包已保存为候选,当前事实未更新;请审核本体后重新发布。job=${job.jobId}`);
|
|
64
|
+
}
|
|
62
65
|
return job;
|
|
63
66
|
}
|
|
64
67
|
|
|
@@ -240,7 +243,7 @@ function preCheckProjectId(projectId, state, selectedTarget) {
|
|
|
240
243
|
* 5. materializeArchivePackage(生成 manifest + canonical-facts + conversion-report + zip)
|
|
241
244
|
* 6. 返回 zip 路径
|
|
242
245
|
*/
|
|
243
|
-
function buildPackageFromFolder(specRoot, changeName, options) {
|
|
246
|
+
function buildPackageFromFolder(specRoot, changeName, options = {}) {
|
|
244
247
|
const runtime = require('./ontology/runtime.cjs');
|
|
245
248
|
const archivePackage = require('./ontology/archive-package.cjs');
|
|
246
249
|
|
|
@@ -250,13 +253,18 @@ function buildPackageFromFolder(specRoot, changeName, options) {
|
|
|
250
253
|
profile: 'auto',
|
|
251
254
|
markPending: true,
|
|
252
255
|
});
|
|
256
|
+
const blockingDiagnostics = semanticResult.diagnostics.filter(d => d.severity === 'error');
|
|
257
|
+
const legacyIdentityOnly = blockingDiagnostics.length > 0
|
|
258
|
+
&& blockingDiagnostics.every(d => d.code === 'SEM_UUID_MISSING');
|
|
253
259
|
if (!semanticResult.valid) {
|
|
254
|
-
const errors =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
260
|
+
const errors = blockingDiagnostics.map(d => `${d.code}: ${d.message}`);
|
|
261
|
+
if (!legacyIdentityOnly) {
|
|
262
|
+
throw new Error(`语义校验失败:\n${errors.join('\n')}`);
|
|
263
|
+
}
|
|
264
|
+
console.warn('⚠️ 检测到历史产物缺少身份标识,将生成旁路映射和待审核本体包,不改写 Markdown。');
|
|
265
|
+
} else {
|
|
266
|
+
console.log(`✓ 语义校验通过 (profile: ${semanticResult.profile}, revision: ${semanticResult.revision})`);
|
|
258
267
|
}
|
|
259
|
-
console.log(`✓ 语义校验通过 (profile: ${semanticResult.profile}, revision: ${semanticResult.revision})`);
|
|
260
268
|
|
|
261
269
|
// 2. 创建临时 staging 目录
|
|
262
270
|
const stagingDir = path.join(
|
|
@@ -269,28 +277,55 @@ function buildPackageFromFolder(specRoot, changeName, options) {
|
|
|
269
277
|
const changeDir = path.join(specRoot, 'openspec', 'changes', changeName);
|
|
270
278
|
copyDirRecursive(changeDir, stagingDir);
|
|
271
279
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
280
|
+
let result;
|
|
281
|
+
if (legacyIdentityOnly) {
|
|
282
|
+
const approvedPath = path.join(changeDir, 'ontology', 'spec-ontology.json');
|
|
283
|
+
const approvedOntology = readJsonSafe(approvedPath);
|
|
284
|
+
const useApproved = approvedOntology?.review_status === 'confirmed'
|
|
285
|
+
&& approvedOntology?.publication_eligible === true;
|
|
286
|
+
if (useApproved) {
|
|
287
|
+
console.log('✓ 已发现审核通过的历史 Spec 本体,将校验内容一致性后发布');
|
|
288
|
+
}
|
|
289
|
+
result = archivePackage.materializeCompatibilityPackage({
|
|
290
|
+
projectRoot: specRoot,
|
|
291
|
+
archiveDir: stagingDir,
|
|
292
|
+
changeName,
|
|
293
|
+
facts: semanticResult.state,
|
|
294
|
+
specOntology: useApproved ? approvedOntology : null,
|
|
295
|
+
packagePath: zipPath,
|
|
296
|
+
reason: options.reason || '历史 Spec 兼容入库',
|
|
297
|
+
method: 'kb-upload-legacy-folder',
|
|
298
|
+
});
|
|
299
|
+
console.log(useApproved
|
|
300
|
+
? '✓ 已生成审核通过的 LEGACY_MATERIALIZED 发布包'
|
|
301
|
+
: '✓ 已生成 LEGACY_MATERIALIZED 本体候选包');
|
|
302
|
+
} else {
|
|
303
|
+
// 严格产物继续生成 confirmed snapshot 和双协议事实包。
|
|
304
|
+
runtime.createArchiveSnapshot(specRoot, changeName, stagingDir, semanticResult.state);
|
|
305
|
+
console.log('✓ 已生成 archive-ontology.json');
|
|
306
|
+
archivePackage.ensureProjectIdentity(specRoot);
|
|
307
|
+
result = archivePackage.materializeArchivePackage({
|
|
308
|
+
projectRoot: specRoot,
|
|
309
|
+
archiveDir: stagingDir,
|
|
310
|
+
archivePath: stagingDir,
|
|
311
|
+
changeName: changeName,
|
|
312
|
+
packagePath: zipPath,
|
|
313
|
+
reason: options.reason || '随时入库(文件夹上传)',
|
|
314
|
+
method: 'kb-upload-folder',
|
|
315
|
+
});
|
|
316
|
+
}
|
|
287
317
|
|
|
288
318
|
console.log(`✓ Archive Package 已生成`);
|
|
289
319
|
console.log(` archive_id: ${result.manifest.archive_id}`);
|
|
290
320
|
console.log(` project_id: ${result.manifest.project_id}`);
|
|
291
321
|
console.log(` files: ${result.manifest.files.length}`);
|
|
292
322
|
|
|
293
|
-
return {
|
|
323
|
+
return {
|
|
324
|
+
zipPath,
|
|
325
|
+
manifest: result.manifest,
|
|
326
|
+
stagingDir,
|
|
327
|
+
requiresReview: result.conversion_report?.status === 'materialized_pending_review',
|
|
328
|
+
};
|
|
294
329
|
} catch (err) {
|
|
295
330
|
// 出错时清理 staging
|
|
296
331
|
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
@@ -313,7 +348,7 @@ async function uploadZip(zipPath, state, options) {
|
|
|
313
348
|
|
|
314
349
|
try {
|
|
315
350
|
const result = await uploadFile(uploadUrl, zipPath, state.apiKey);
|
|
316
|
-
assertIngestionResult(result);
|
|
351
|
+
assertIngestionResult(result, options);
|
|
317
352
|
return result;
|
|
318
353
|
} catch (err) {
|
|
319
354
|
throw new Error(`上传失败: ${err.message}`);
|
|
@@ -438,7 +473,7 @@ async function main() {
|
|
|
438
473
|
|
|
439
474
|
// 5. 构建 Archive Package
|
|
440
475
|
console.log('');
|
|
441
|
-
const { zipPath, manifest, stagingDir } = buildPackageFromFolder(specRoot, changeName, {
|
|
476
|
+
const { zipPath, manifest, stagingDir, requiresReview } = buildPackageFromFolder(specRoot, changeName, {
|
|
442
477
|
reason: args.reason,
|
|
443
478
|
});
|
|
444
479
|
|
|
@@ -492,6 +527,7 @@ async function main() {
|
|
|
492
527
|
const result = await uploadZip(zipPath, state, {
|
|
493
528
|
spaceId: target.spaceId,
|
|
494
529
|
kbId: target.kbId,
|
|
530
|
+
allowNeedsReview: requiresReview,
|
|
495
531
|
});
|
|
496
532
|
|
|
497
533
|
console.log('\n── 上传结果 ──');
|
|
@@ -504,8 +540,17 @@ async function main() {
|
|
|
504
540
|
console.log(` change_id: ${manifest.change_id}`);
|
|
505
541
|
console.log(` files: ${manifest.files.length}`);
|
|
506
542
|
|
|
507
|
-
|
|
508
|
-
|
|
543
|
+
const publicationStatus = result?.data?.report?.publication_status
|
|
544
|
+
|| result?.report?.publication_status
|
|
545
|
+
|| '';
|
|
546
|
+
if (publicationStatus === 'needs_review') {
|
|
547
|
+
console.log('\n⚠️ 历史 Spec 已安全保存为候选,尚未进入 current 召回');
|
|
548
|
+
console.log(` → 审核 ${path.join(folderPath, 'ontology', 'spec-ontology.json')}`);
|
|
549
|
+
console.log(' → 运行 approve-spec-ontology 后再次上传即可发布');
|
|
550
|
+
} else {
|
|
551
|
+
// Published commit has been verified; a receipt does not imply projection completion.
|
|
552
|
+
updateKbStateCache(specRoot, changeName, archiveId, contentHash, cacheContext);
|
|
553
|
+
}
|
|
509
554
|
|
|
510
555
|
// === 投影状态提示 ===
|
|
511
556
|
// 上传成功后,KB 后端异步执行 vector embedding + AGE graph 投影。
|