kld-sdd 2.7.8-5 → 2.7.8-7

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.
@@ -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 { specSourceLabel } = require('../skywalk-sdd/ontology/spec-detect.cjs');
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
  };
@@ -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 = { filesUnder, regularFileWithin, runtimeToolFiles, samePath, associationConflict, workspaceCodeRepos, maintenanceConflicts };
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
+ };
@@ -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-5",
3
+ "version": "2.7.8-7",
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 mode = String(args.mode || 'match').trim().toLowerCase() === 'resolve' ? 'resolve' : 'match';
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
- const data = await requestJson(
178
- buildEndpoint(apiBase, spaceId, kbId, mode),
179
- payload,
180
- token,
181
- timeoutMs,
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
 
@@ -7706,7 +7706,11 @@ function cmdCheckRecord(args) {
7706
7706
  const preflight = JSON.parse(fs.readFileSync(path.resolve(args['preflight-file']), 'utf8'));
7707
7707
  const review = JSON.parse(fs.readFileSync(path.resolve(args['review-file']), 'utf8'));
7708
7708
  const state = require('./ontology/runtime.cjs').reconcileChange(projectRoot, args.change, { profile: preflight.profile || 'auto', markPending: true });
7709
- if (!preflight.valid || !state.valid || preflight.revision !== state.revision
7709
+ const { splitCheckDiagnostics } = require('./ontology/spec-ontology.cjs');
7710
+ const preflightBlocked = preflight.product_valid === false
7711
+ || (preflight.product_valid == null && !splitCheckDiagnostics(preflight.diagnostics).product_valid);
7712
+ const stateBlocked = !splitCheckDiagnostics(state.diagnostics).product_valid;
7713
+ if (preflightBlocked || stateBlocked || preflight.revision !== state.revision
7710
7714
  || preflight.review_inputs?.identity_history_hash !== state.state.identity_history_hash
7711
7715
  || JSON.stringify(preflight.review_inputs?.files) !== JSON.stringify(state.state.files)) {
7712
7716
  throw new Error('CHECK_REVIEW_STALE: 文档或历史身份已变化,重新预检并评审受影响内容');
@@ -8239,16 +8243,20 @@ function cmdSemanticIdentity(args) {
8239
8243
  }
8240
8244
 
8241
8245
  function semanticResultPayload(result) {
8246
+ const split = require('./ontology/spec-ontology.cjs').splitCheckDiagnostics(result.diagnostics);
8242
8247
  return {
8243
8248
  change: result.state.change,
8244
8249
  profile: result.profile,
8245
8250
  valid: result.valid,
8251
+ product_valid: split.product_valid,
8246
8252
  review_status: result.state.review_status,
8247
8253
  revision: result.revision,
8248
8254
  changed: Boolean(result.changed),
8249
8255
  counts: result.counts,
8250
8256
  paths: result.paths,
8251
8257
  diagnostics: result.diagnostics,
8258
+ tool_diagnostics: split.tool_diagnostics,
8259
+ product_diagnostics: split.product_diagnostics,
8252
8260
  };
8253
8261
  }
8254
8262
 
@@ -8290,13 +8298,32 @@ function cmdSemanticReconcile(args, options = {}) {
8290
8298
  total: Number(finished - started) / 1e6,
8291
8299
  };
8292
8300
  }
8301
+ if (options.productGate && payload.tool_diagnostics.length > 0) {
8302
+ try {
8303
+ const repair = require('./ontology/spec-ontology.cjs').materializeChangeSpecOntology(
8304
+ context.projectRoot,
8305
+ context.changeName,
8306
+ );
8307
+ payload.tool_repair = {
8308
+ repaired: repair.validation.valid,
8309
+ output_path: repair.output_path,
8310
+ mapping_path: repair.mapping_path,
8311
+ };
8312
+ } catch (error) {
8313
+ payload.tool_repair = { repaired: false };
8314
+ }
8315
+ }
8293
8316
  console.log(JSON.stringify(payload, null, 2));
8294
- if (!result.valid) process.exitCode = 1;
8317
+ if (options.productGate) {
8318
+ if (!payload.product_valid) process.exitCode = 1;
8319
+ } else if (!result.valid) {
8320
+ process.exitCode = 1;
8321
+ }
8295
8322
  return result;
8296
8323
  }
8297
8324
 
8298
8325
  function cmdSemanticCheck(args) {
8299
- return cmdSemanticReconcile(args, { markPending: true, includeTasks: Boolean(args['include-tasks']) });
8326
+ return cmdSemanticReconcile(args, { markPending: true, includeTasks: Boolean(args['include-tasks']), productGate: true });
8300
8327
  }
8301
8328
 
8302
8329
  function cmdSemanticStatus(args) {