kld-sdd 2.7.8-6 → 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.
- package/package.json +1 -1
- package/skywalk-sdd/index.cjs +30 -3
- package/skywalk-sdd/kb-upload.cjs +103 -21
- package/skywalk-sdd/ontology/archive-package.cjs +5 -9
- package/skywalk-sdd/ontology/spec-ontology.cjs +134 -11
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +11 -8
- package/templates/skills/kld-sdd/opsx-check/reviewer.md +2 -1
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +1 -1
- package/templates/skills/kld-sdd/opsx-ontology-ingest/SKILL.md +16 -14
package/package.json
CHANGED
package/skywalk-sdd/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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) {
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* node skywalk-sdd/kb-upload.cjs --folder=<path> [--minimum-level=propose-spec] [--validate-only]
|
|
13
13
|
* node skywalk-sdd/kb-upload.cjs --package=<zip-path>
|
|
14
14
|
* node skywalk-sdd/kb-upload.cjs --folder=<path> --project=<spec-root> --change=<name>
|
|
15
|
+
* node skywalk-sdd/kb-upload.cjs --folder=<path> --require-review
|
|
16
|
+
*
|
|
17
|
+
* 历史产物(缺 entity-id / version-id):
|
|
18
|
+
* 自动旁路物化 spec-ontology.json,无 semantic_key 冲突时自动确认并发布为 current;
|
|
19
|
+
* 有冲突或 --require-review 时只上传候选,需人工 approve-spec-ontology 后再上传。
|
|
15
20
|
*
|
|
16
21
|
* project_id 解析:
|
|
17
22
|
* 1. $SPEC_ROOT/skywalk-sdd/project-identity.json 的 project_id
|
|
@@ -243,9 +248,65 @@ function preCheckProjectId(projectId, state, selectedTarget) {
|
|
|
243
248
|
* 5. materializeArchivePackage(生成 manifest + canonical-facts + conversion-report + zip)
|
|
244
249
|
* 6. 返回 zip 路径
|
|
245
250
|
*/
|
|
251
|
+
/**
|
|
252
|
+
* 历史产物的 Spec 本体解析:
|
|
253
|
+
* - 已审核且与当前 Markdown 一致 → 直接复用
|
|
254
|
+
* - 否则重新物化。重复锚点、非法 UUID、身份冲突由物化自动改写为稳定身份并确认发布
|
|
255
|
+
* - 只有显式 requireReview,或正文没有结构化事实时,才保留候选
|
|
256
|
+
* 全程不改写 Markdown,只读写 <change>/ontology/spec-ontology.json 与 legacy-mapping.json。
|
|
257
|
+
*/
|
|
258
|
+
function resolveLegacySpecOntology(specRoot, changeName, changeDir, facts, options = {}) {
|
|
259
|
+
const {
|
|
260
|
+
buildSpecOntology,
|
|
261
|
+
materializeChangeSpecOntology,
|
|
262
|
+
approveSpecOntologyFile,
|
|
263
|
+
specOntologyContentSignature,
|
|
264
|
+
} = require('./ontology/spec-ontology.cjs');
|
|
265
|
+
const { ensureProjectIdentity } = require('./ontology/archive-package.cjs');
|
|
266
|
+
const identity = ensureProjectIdentity(specRoot);
|
|
267
|
+
const ontologyPath = path.join(changeDir, 'ontology', 'spec-ontology.json');
|
|
268
|
+
|
|
269
|
+
const fresh = buildSpecOntology(facts, {
|
|
270
|
+
projectId: identity.project_id,
|
|
271
|
+
compatibilityMode: 'LEGACY_MATERIALIZED',
|
|
272
|
+
reviewStatus: 'pending',
|
|
273
|
+
});
|
|
274
|
+
const freshSignature = specOntologyContentSignature(fresh);
|
|
275
|
+
const existing = readJsonSafe(ontologyPath);
|
|
276
|
+
const existingApproved = existing
|
|
277
|
+
&& existing.compatibility_mode === 'LEGACY_MATERIALIZED'
|
|
278
|
+
&& existing.review_status === 'confirmed'
|
|
279
|
+
&& existing.publication_eligible === true
|
|
280
|
+
&& existing.project_id === identity.project_id;
|
|
281
|
+
if (existingApproved && specOntologyContentSignature(existing) === freshSignature) {
|
|
282
|
+
return { approved: existing, status: 'reused', path: ontologyPath, collisions: [] };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const materialized = materializeChangeSpecOntology(specRoot, changeName, {
|
|
286
|
+
changeDir,
|
|
287
|
+
projectId: identity.project_id,
|
|
288
|
+
compatibilityMode: 'LEGACY_MATERIALIZED',
|
|
289
|
+
reviewStatus: 'pending',
|
|
290
|
+
});
|
|
291
|
+
if (!materialized.validation.valid) {
|
|
292
|
+
throw new Error(`历史 Spec 本体物化失败: ${materialized.validation.errors.join('; ')}`);
|
|
293
|
+
}
|
|
294
|
+
const repairs = (materialized.payload.warnings || [])
|
|
295
|
+
.filter((warning) => warning.startsWith('LEGACY_DUPLICATE_ANCHOR:') || warning.startsWith('LEGACY_IDENTITY_REPAIRED:'));
|
|
296
|
+
if (options.requireReview) {
|
|
297
|
+
return { approved: null, status: 'pending_review', path: ontologyPath, collisions: repairs };
|
|
298
|
+
}
|
|
299
|
+
if (materialized.payload.entities.length === 0) {
|
|
300
|
+
return { approved: null, status: 'needs_review', path: ontologyPath, collisions: repairs };
|
|
301
|
+
}
|
|
302
|
+
const approved = approveSpecOntologyFile(materialized.output_path, { allowCollisions: true });
|
|
303
|
+
return { approved: approved.payload, status: 'auto_approved', path: ontologyPath, collisions: repairs };
|
|
304
|
+
}
|
|
305
|
+
|
|
246
306
|
function buildPackageFromFolder(specRoot, changeName, options = {}) {
|
|
247
307
|
const runtime = require('./ontology/runtime.cjs');
|
|
248
308
|
const archivePackage = require('./ontology/archive-package.cjs');
|
|
309
|
+
const { classifyLegacyDiagnostics } = require('./ontology/spec-ontology.cjs');
|
|
249
310
|
|
|
250
311
|
// 1. 语义管线
|
|
251
312
|
console.log('→ 运行语义管线 (semantic-reconcile)...');
|
|
@@ -253,15 +314,17 @@ function buildPackageFromFolder(specRoot, changeName, options = {}) {
|
|
|
253
314
|
profile: 'auto',
|
|
254
315
|
markPending: true,
|
|
255
316
|
});
|
|
256
|
-
const
|
|
257
|
-
const legacyIdentityOnly =
|
|
258
|
-
&& blockingDiagnostics.every(d => d.code === 'SEM_UUID_MISSING');
|
|
317
|
+
const legacy = classifyLegacyDiagnostics(semanticResult.diagnostics);
|
|
318
|
+
const legacyIdentityOnly = legacy.legacyCompatible;
|
|
259
319
|
if (!semanticResult.valid) {
|
|
260
|
-
const errors = blockingDiagnostics.map(d => `${d.code}: ${d.message}`);
|
|
261
320
|
if (!legacyIdentityOnly) {
|
|
321
|
+
const errors = legacy.blocking.map(d => `${d.code}: ${d.message}`);
|
|
262
322
|
throw new Error(`语义校验失败:\n${errors.join('\n')}`);
|
|
263
323
|
}
|
|
264
|
-
console.warn('⚠️
|
|
324
|
+
console.warn('⚠️ 检测到历史产物缺少身份标识,将生成旁路映射与本体包,不改写 Markdown。');
|
|
325
|
+
for (const item of legacy.tolerated) {
|
|
326
|
+
console.warn(` ↳ 兼容放行 ${item.code}: ${item.message}`);
|
|
327
|
+
}
|
|
265
328
|
} else {
|
|
266
329
|
console.log(`✓ 语义校验通过 (profile: ${semanticResult.profile}, revision: ${semanticResult.revision})`);
|
|
267
330
|
}
|
|
@@ -278,25 +341,31 @@ function buildPackageFromFolder(specRoot, changeName, options = {}) {
|
|
|
278
341
|
copyDirRecursive(changeDir, stagingDir);
|
|
279
342
|
|
|
280
343
|
let result;
|
|
344
|
+
let legacyStatus = null;
|
|
281
345
|
if (legacyIdentityOnly) {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
346
|
+
const resolved = resolveLegacySpecOntology(specRoot, changeName, changeDir, semanticResult.state, {
|
|
347
|
+
requireReview: Boolean(options.requireReview),
|
|
348
|
+
});
|
|
349
|
+
legacyStatus = resolved.status;
|
|
350
|
+
const messages = {
|
|
351
|
+
reused: '✓ 已发现审核通过且与当前 Markdown 一致的历史 Spec 本体,直接发布',
|
|
352
|
+
auto_approved: '✓ 历史 Spec 本体已自动物化并确认,身份和重复锚点已在旁路改写,将发布为 current 事实',
|
|
353
|
+
needs_review: '⚠️ 历史正文没有可发布的结构化事实,仅保留为证据候选',
|
|
354
|
+
pending_review: 'ℹ️ 按 --require-review 要求,历史 Spec 本体保留为待审核候选',
|
|
355
|
+
};
|
|
356
|
+
console.log(messages[resolved.status]);
|
|
289
357
|
result = archivePackage.materializeCompatibilityPackage({
|
|
290
358
|
projectRoot: specRoot,
|
|
291
359
|
archiveDir: stagingDir,
|
|
292
360
|
changeName,
|
|
293
361
|
facts: semanticResult.state,
|
|
294
|
-
specOntology:
|
|
362
|
+
specOntology: resolved.approved,
|
|
363
|
+
toleratedDiagnostics: legacy.tolerated,
|
|
295
364
|
packagePath: zipPath,
|
|
296
365
|
reason: options.reason || '历史 Spec 兼容入库',
|
|
297
366
|
method: 'kb-upload-legacy-folder',
|
|
298
367
|
});
|
|
299
|
-
console.log(
|
|
368
|
+
console.log(resolved.approved
|
|
300
369
|
? '✓ 已生成审核通过的 LEGACY_MATERIALIZED 发布包'
|
|
301
370
|
: '✓ 已生成 LEGACY_MATERIALIZED 本体候选包');
|
|
302
371
|
} else {
|
|
@@ -325,6 +394,8 @@ function buildPackageFromFolder(specRoot, changeName, options = {}) {
|
|
|
325
394
|
manifest: result.manifest,
|
|
326
395
|
stagingDir,
|
|
327
396
|
requiresReview: result.conversion_report?.status === 'materialized_pending_review',
|
|
397
|
+
legacyStatus,
|
|
398
|
+
toleratedDiagnostics: legacyIdentityOnly ? legacy.tolerated : [],
|
|
328
399
|
};
|
|
329
400
|
} catch (err) {
|
|
330
401
|
// 出错时清理 staging
|
|
@@ -365,6 +436,7 @@ async function main() {
|
|
|
365
436
|
console.error(' node skywalk-sdd/kb-upload.cjs --folder=<path> [--minimum-level=propose-spec] [--validate-only]');
|
|
366
437
|
console.error(' node skywalk-sdd/kb-upload.cjs --package=<zip-path>');
|
|
367
438
|
console.error(' node skywalk-sdd/kb-upload.cjs --folder=<path> --project=<spec-root> --change=<name>');
|
|
439
|
+
console.error(' node skywalk-sdd/kb-upload.cjs --folder=<path> --require-review # 历史产物只上传候选,不自动确认本体');
|
|
368
440
|
process.exit(1);
|
|
369
441
|
}
|
|
370
442
|
|
|
@@ -448,12 +520,17 @@ async function main() {
|
|
|
448
520
|
markPending: true,
|
|
449
521
|
});
|
|
450
522
|
if (!semanticResult.valid) {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
523
|
+
const { classifyLegacyDiagnostics } = require('./ontology/spec-ontology.cjs');
|
|
524
|
+
const legacy = classifyLegacyDiagnostics(semanticResult.diagnostics);
|
|
525
|
+
if (!legacy.legacyCompatible) {
|
|
526
|
+
const errors = legacy.blocking.map(d => `${d.code}: ${d.message}`);
|
|
527
|
+
throw new Error(`语义校验失败:\n${errors.join('\n')}`);
|
|
528
|
+
}
|
|
529
|
+
console.log('⚠️ 历史产物缺少身份标识,正式上传时将走 LEGACY_MATERIALIZED 旁路物化,不改写 Markdown');
|
|
530
|
+
for (const item of legacy.tolerated) console.log(` ↳ 兼容放行 ${item.code}: ${item.message}`);
|
|
531
|
+
} else {
|
|
532
|
+
console.log(`✓ 语义校验通过 (profile: ${semanticResult.profile}, revision: ${semanticResult.revision})`);
|
|
455
533
|
}
|
|
456
|
-
console.log(`✓ 语义校验通过 (profile: ${semanticResult.profile}, revision: ${semanticResult.revision})`);
|
|
457
534
|
console.log('\n✓ 本地语义校验完成(--validate-only,未联网;不代表权限、发布基线或入库已验证)');
|
|
458
535
|
return;
|
|
459
536
|
}
|
|
@@ -473,8 +550,9 @@ async function main() {
|
|
|
473
550
|
|
|
474
551
|
// 5. 构建 Archive Package
|
|
475
552
|
console.log('');
|
|
476
|
-
const { zipPath, manifest, stagingDir, requiresReview } = buildPackageFromFolder(specRoot, changeName, {
|
|
553
|
+
const { zipPath, manifest, stagingDir, requiresReview, legacyStatus } = buildPackageFromFolder(specRoot, changeName, {
|
|
477
554
|
reason: args.reason,
|
|
555
|
+
requireReview: Boolean(args['require-review']),
|
|
478
556
|
});
|
|
479
557
|
|
|
480
558
|
try {
|
|
@@ -546,7 +624,11 @@ async function main() {
|
|
|
546
624
|
if (publicationStatus === 'needs_review') {
|
|
547
625
|
console.log('\n⚠️ 历史 Spec 已安全保存为候选,尚未进入 current 召回');
|
|
548
626
|
console.log(` → 审核 ${path.join(folderPath, 'ontology', 'spec-ontology.json')}`);
|
|
549
|
-
|
|
627
|
+
if (legacyStatus === 'needs_review') {
|
|
628
|
+
console.log(' → 正文没有足够的结构化事实,只能作为证据保留');
|
|
629
|
+
} else {
|
|
630
|
+
console.log(' → 运行 approve-spec-ontology 后再次上传即可发布;不带 --require-review 上传则自动确认');
|
|
631
|
+
}
|
|
550
632
|
} else {
|
|
551
633
|
// Published commit has been verified; a receipt does not imply projection completion.
|
|
552
634
|
updateKbStateCache(specRoot, changeName, archiveId, contentHash, cacheContext);
|
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
SPEC_ONTOLOGY_PATH,
|
|
10
10
|
buildSpecOntology,
|
|
11
11
|
validateSpecOntology,
|
|
12
|
+
specOntologyContentSignature,
|
|
12
13
|
} = require('./spec-ontology.cjs');
|
|
13
14
|
const { resolveRuntimeMetadata } = require('../runtime-metadata.cjs');
|
|
14
15
|
const {
|
|
@@ -708,14 +709,7 @@ function materializeCompatibilityPackage(options = {}) {
|
|
|
708
709
|
compatibilityMode: 'LEGACY_MATERIALIZED',
|
|
709
710
|
reviewStatus: 'pending',
|
|
710
711
|
});
|
|
711
|
-
|
|
712
|
-
.map((entity) => `${entity.anchor_id}:${entity.content_hash}`)
|
|
713
|
-
.sort();
|
|
714
|
-
const relationContentSignature = (payload) => (payload.relations || [])
|
|
715
|
-
.map((relation) => `${relation.type}:${relation.source?.anchor_id || ''}:${relation.source?.content_hash || ''}`)
|
|
716
|
-
.sort();
|
|
717
|
-
if (JSON.stringify(entityContentSignature(specOntology)) !== JSON.stringify(entityContentSignature(currentOntology))
|
|
718
|
-
|| JSON.stringify(relationContentSignature(specOntology)) !== JSON.stringify(relationContentSignature(currentOntology))) {
|
|
712
|
+
if (specOntologyContentSignature(specOntology) !== specOntologyContentSignature(currentOntology)) {
|
|
719
713
|
throw new Error('已审核 Spec 本体与当前 Markdown 内容不一致,请重新物化并审核');
|
|
720
714
|
}
|
|
721
715
|
specOntology.archive_id = archiveId;
|
|
@@ -735,6 +729,8 @@ function materializeCompatibilityPackage(options = {}) {
|
|
|
735
729
|
if (!validation.valid) {
|
|
736
730
|
throw new Error(`历史 Spec 本体物化失败: ${validation.errors.join('; ')}`);
|
|
737
731
|
}
|
|
732
|
+
const toleratedDiagnostics = (options.toleratedDiagnostics || [])
|
|
733
|
+
.map((item) => `LEGACY_DIAGNOSTIC_TOLERATED: ${item.code}: ${item.message}`);
|
|
738
734
|
const specOntologyPath = path.join(archiveDir, SPEC_ONTOLOGY_PATH);
|
|
739
735
|
const specOntologyBytes = writeJsonAtomic(specOntologyPath, specOntology);
|
|
740
736
|
const conversionReport = {
|
|
@@ -748,7 +744,7 @@ function materializeCompatibilityPackage(options = {}) {
|
|
|
748
744
|
status: specOntology.publication_eligible ? 'materialized_reviewed' : 'materialized_pending_review',
|
|
749
745
|
entity_count: specOntology.entities.length,
|
|
750
746
|
relation_count: specOntology.relations.length,
|
|
751
|
-
warnings: specOntology.warnings,
|
|
747
|
+
warnings: [...new Set([...(specOntology.warnings || []), ...toleratedDiagnostics])],
|
|
752
748
|
};
|
|
753
749
|
writeJsonAtomic(path.join(archiveDir, CONVERSION_REPORT_PATH), conversionReport);
|
|
754
750
|
|
|
@@ -8,6 +8,8 @@ const { normalizeFacts } = require('./normalizer.cjs');
|
|
|
8
8
|
const { resolveChangeDir } = require('./runtime.cjs');
|
|
9
9
|
const { resolveRuntimeMetadata } = require('../runtime-metadata.cjs');
|
|
10
10
|
const { ensureDir, readJsonSafe, writeJsonAtomic } = require('../lib/shared.cjs');
|
|
11
|
+
const { DIAGNOSTIC_CODES } = require('./schema.cjs');
|
|
12
|
+
const { isValidUuid } = require('./id.cjs');
|
|
11
13
|
|
|
12
14
|
const SPEC_ONTOLOGY_SCHEMA_VERSION = 'kld-sdd-spec-ontology/v1';
|
|
13
15
|
const LEGACY_MAPPING_SCHEMA_VERSION = 'kld-sdd-legacy-identity-mapping/v1';
|
|
@@ -32,10 +34,92 @@ const RELATION_MAP = Object.freeze({
|
|
|
32
34
|
constrains: 'constrainedBy',
|
|
33
35
|
});
|
|
34
36
|
|
|
37
|
+
// 工具升级后新增的身份、归档和任务校验。历史稿上传遇到这些诊断时旁路物化,
|
|
38
|
+
// 由上传流程自动消解,不把整包失败交回给使用者。
|
|
39
|
+
const LEGACY_TOLERATED_CODES = Object.freeze([
|
|
40
|
+
DIAGNOSTIC_CODES.UUID_MISSING,
|
|
41
|
+
DIAGNOSTIC_CODES.UUID_INVALID,
|
|
42
|
+
DIAGNOSTIC_CODES.ID_MISSING,
|
|
43
|
+
DIAGNOSTIC_CODES.ID_INVALID,
|
|
44
|
+
DIAGNOSTIC_CODES.ID_DUPLICATE,
|
|
45
|
+
DIAGNOSTIC_CODES.DELTA_STATE_INVALID,
|
|
46
|
+
DIAGNOSTIC_CODES.SOURCE_MISSING,
|
|
47
|
+
DIAGNOSTIC_CODES.VERSION_LINEAGE_MISSING,
|
|
48
|
+
DIAGNOSTIC_CODES.ENTITY_IDENTITY_CONFLICT,
|
|
49
|
+
DIAGNOSTIC_CODES.VERSION_IDENTITY_CONFLICT,
|
|
50
|
+
DIAGNOSTIC_CODES.INHERITED_SOURCE_MISMATCH,
|
|
51
|
+
DIAGNOSTIC_CODES.INHERITED_RELATION_MISSING,
|
|
52
|
+
DIAGNOSTIC_CODES.ARCHIVE_NOT_CONFIRMED,
|
|
53
|
+
DIAGNOSTIC_CODES.ARCHIVE_REVISION_MISMATCH,
|
|
54
|
+
DIAGNOSTIC_CODES.TASK_DEPENDENCY_CYCLE,
|
|
55
|
+
DIAGNOSTIC_CODES.TASK_DEPENDENCY_LAYER_GAP,
|
|
56
|
+
DIAGNOSTIC_CODES.TASK_DEPENDENCY_MISSING_EDGE,
|
|
57
|
+
DIAGNOSTIC_CODES.STMT_WITHOUT_AC,
|
|
58
|
+
DIAGNOSTIC_CODES.STMT_WITHOUT_DESIGN,
|
|
59
|
+
DIAGNOSTIC_CODES.DESIGN_WITHOUT_TASK,
|
|
60
|
+
DIAGNOSTIC_CODES.AC_WITHOUT_STMT,
|
|
61
|
+
DIAGNOSTIC_CODES.TASK_WITHOUT_UPSTREAM,
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
// Check 对用户隐藏并自行处理的工具诊断。需求、设计、任务之间的覆盖缺口不在此列。
|
|
65
|
+
const CHECK_TOOL_CODES = Object.freeze([
|
|
66
|
+
DIAGNOSTIC_CODES.UUID_MISSING,
|
|
67
|
+
DIAGNOSTIC_CODES.UUID_INVALID,
|
|
68
|
+
DIAGNOSTIC_CODES.ID_MISSING,
|
|
69
|
+
DIAGNOSTIC_CODES.ID_INVALID,
|
|
70
|
+
DIAGNOSTIC_CODES.ID_DUPLICATE,
|
|
71
|
+
DIAGNOSTIC_CODES.DELTA_STATE_INVALID,
|
|
72
|
+
DIAGNOSTIC_CODES.SOURCE_MISSING,
|
|
73
|
+
DIAGNOSTIC_CODES.VERSION_LINEAGE_MISSING,
|
|
74
|
+
DIAGNOSTIC_CODES.ENTITY_IDENTITY_CONFLICT,
|
|
75
|
+
DIAGNOSTIC_CODES.VERSION_IDENTITY_CONFLICT,
|
|
76
|
+
DIAGNOSTIC_CODES.INHERITED_SOURCE_MISMATCH,
|
|
77
|
+
DIAGNOSTIC_CODES.INHERITED_RELATION_MISSING,
|
|
78
|
+
DIAGNOSTIC_CODES.ARCHIVE_NOT_CONFIRMED,
|
|
79
|
+
DIAGNOSTIC_CODES.ARCHIVE_REVISION_MISMATCH,
|
|
80
|
+
]);
|
|
81
|
+
|
|
35
82
|
function sha256(value) {
|
|
36
83
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
37
84
|
}
|
|
38
85
|
|
|
86
|
+
function splitCheckDiagnostics(diagnostics = []) {
|
|
87
|
+
const tool = [];
|
|
88
|
+
const product = [];
|
|
89
|
+
for (const item of diagnostics || []) {
|
|
90
|
+
if (item && item.severity === 'error' && CHECK_TOOL_CODES.includes(item.code)) tool.push(item);
|
|
91
|
+
else product.push(item);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
tool_diagnostics: tool,
|
|
95
|
+
product_diagnostics: product,
|
|
96
|
+
product_valid: product.every((item) => !item || item.severity !== 'error'),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function classifyLegacyDiagnostics(diagnostics = []) {
|
|
101
|
+
const blocking = (diagnostics || []).filter((item) => item && item.severity === 'error');
|
|
102
|
+
const tolerated = blocking.filter((item) => LEGACY_TOLERATED_CODES.includes(item.code));
|
|
103
|
+
const rejected = blocking.filter((item) => !LEGACY_TOLERATED_CODES.includes(item.code));
|
|
104
|
+
return {
|
|
105
|
+
blocking,
|
|
106
|
+
tolerated,
|
|
107
|
+
rejected,
|
|
108
|
+
legacyCompatible: blocking.length > 0 && rejected.length === 0,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 同一份 Markdown 反复物化时用于判断“已审核本体是否仍对应当前正文”。
|
|
113
|
+
function specOntologyContentSignature(payload) {
|
|
114
|
+
const entities = (payload?.entities || [])
|
|
115
|
+
.map((entity) => `${entity.anchor_id}:${entity.content_hash}`)
|
|
116
|
+
.sort();
|
|
117
|
+
const relations = (payload?.relations || [])
|
|
118
|
+
.map((relation) => `${relation.type}:${relation.source?.anchor_id || ''}:${relation.source?.content_hash || ''}`)
|
|
119
|
+
.sort();
|
|
120
|
+
return JSON.stringify({ entities, relations });
|
|
121
|
+
}
|
|
122
|
+
|
|
39
123
|
function withOntologyHash(payload) {
|
|
40
124
|
const result = { ...payload };
|
|
41
125
|
delete result.ontology_hash;
|
|
@@ -91,16 +175,25 @@ function compatibilityMode(facts, selectedEntities) {
|
|
|
91
175
|
return 'LEGACY_MATERIALIZED';
|
|
92
176
|
}
|
|
93
177
|
|
|
94
|
-
function identityFor(entity, projectId, semanticKey) {
|
|
178
|
+
function identityFor(entity, projectId, semanticKey, options = {}) {
|
|
95
179
|
const declaredEntityId = normalizedText(entity.entity_id).toLowerCase();
|
|
96
180
|
const declaredVersionId = normalizedText(entity.entity_version_id || entity.version_id).toLowerCase();
|
|
97
|
-
const
|
|
181
|
+
const declaredUsable = isValidUuid(declaredEntityId) && isValidUuid(declaredVersionId);
|
|
98
182
|
const contentHash = normalizedText(entity.content_hash) || `sha256:${sha256(JSON.stringify(entity))}`;
|
|
99
|
-
|
|
183
|
+
if (!options.repairLegacy || declaredUsable) {
|
|
184
|
+
return {
|
|
185
|
+
entity_id: declaredEntityId || deterministicUuid(`${projectId}\n${semanticKey}`),
|
|
186
|
+
entity_version_id: declaredVersionId || deterministicUuid(`${declaredEntityId || semanticKey}\n${contentHash}`),
|
|
187
|
+
identity_origin: declaredUsable ? 'declared' : 'derived_legacy',
|
|
188
|
+
repaired: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
100
191
|
return {
|
|
101
|
-
entity_id:
|
|
102
|
-
entity_version_id:
|
|
103
|
-
identity_origin:
|
|
192
|
+
entity_id: deterministicUuid(`${projectId}\n${semanticKey}`),
|
|
193
|
+
entity_version_id: deterministicUuid(`${projectId}\n${semanticKey}\n${contentHash}`),
|
|
194
|
+
identity_origin: 'derived_legacy',
|
|
195
|
+
repaired: true,
|
|
196
|
+
repair_reason: declaredEntityId || declaredVersionId ? 'invalid_declared_uuid' : 'missing_uuid',
|
|
104
197
|
};
|
|
105
198
|
}
|
|
106
199
|
|
|
@@ -124,12 +217,36 @@ function buildSpecOntology(facts, options = {}) {
|
|
|
124
217
|
const baseKey = baseKeys[index];
|
|
125
218
|
const source = normalizeSource(sourceEntity.source);
|
|
126
219
|
const semanticKey = keyCounts.get(baseKey) > 1
|
|
127
|
-
? `${baseKey}@${normalizedText(source.file).toLowerCase() || index + 1}`
|
|
220
|
+
? `${baseKey}@${normalizedText(source.file).toLowerCase() || 'source'}#${index + 1}`
|
|
128
221
|
: baseKey;
|
|
129
222
|
if (keyCounts.get(baseKey) > 1) {
|
|
130
|
-
warnings.push(`
|
|
223
|
+
warnings.push(`LEGACY_DUPLICATE_ANCHOR: ${baseKey} 已按来源路径拆成独立事实,上传不再等待人工合并`);
|
|
224
|
+
}
|
|
225
|
+
const repairLegacy = mode === 'LEGACY_MATERIALIZED';
|
|
226
|
+
let identity = identityFor(sourceEntity, projectId, semanticKey, { repairLegacy });
|
|
227
|
+
if (repairLegacy && entities.some((item) => item.entity_id === identity.entity_id)) {
|
|
228
|
+
identity = {
|
|
229
|
+
entity_id: deterministicUuid(`${projectId}\n${semanticKey}\n${index}`),
|
|
230
|
+
entity_version_id: deterministicUuid(`${projectId}\n${semanticKey}\n${index}\nversion`),
|
|
231
|
+
identity_origin: 'derived_legacy',
|
|
232
|
+
repaired: true,
|
|
233
|
+
repair_reason: 'entity_id_conflict',
|
|
234
|
+
};
|
|
131
235
|
}
|
|
132
|
-
|
|
236
|
+
if (repairLegacy && entities.some((item) => item.entity_version_id === identity.entity_version_id)) {
|
|
237
|
+
identity = {
|
|
238
|
+
...identity,
|
|
239
|
+
entity_version_id: deterministicUuid(`${identity.entity_id}\n${semanticKey}\n${index}\nversion`),
|
|
240
|
+
identity_origin: 'derived_legacy',
|
|
241
|
+
repaired: true,
|
|
242
|
+
repair_reason: identity.repair_reason || 'version_id_conflict',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (identity.repaired) {
|
|
246
|
+
warnings.push(`LEGACY_IDENTITY_REPAIRED: ${anchor} ${identity.repair_reason}`);
|
|
247
|
+
}
|
|
248
|
+
const declaredPredecessor = normalizedText(sourceEntity.predecessor_version_id);
|
|
249
|
+
const predecessor = repairLegacy || !isValidUuid(declaredPredecessor) ? null : declaredPredecessor;
|
|
133
250
|
const ontologyEntity = {
|
|
134
251
|
semantic_key: semanticKey,
|
|
135
252
|
type: TYPE_MAP[sourceEntity.type],
|
|
@@ -141,8 +258,10 @@ function buildSpecOntology(facts, options = {}) {
|
|
|
141
258
|
|| anchor,
|
|
142
259
|
),
|
|
143
260
|
anchor_id: anchor,
|
|
144
|
-
|
|
145
|
-
|
|
261
|
+
entity_id: identity.entity_id,
|
|
262
|
+
entity_version_id: identity.entity_version_id,
|
|
263
|
+
identity_origin: identity.identity_origin,
|
|
264
|
+
predecessor_version_id: predecessor,
|
|
146
265
|
content_hash: normalizedText(sourceEntity.content_hash),
|
|
147
266
|
review_status: reviewStatus,
|
|
148
267
|
publication_status: reviewStatus === 'confirmed' ? 'publishable' : 'candidate',
|
|
@@ -375,8 +494,12 @@ module.exports = {
|
|
|
375
494
|
SPEC_ONTOLOGY_PATH,
|
|
376
495
|
LEGACY_MAPPING_PATH,
|
|
377
496
|
TYPE_MAP,
|
|
497
|
+
LEGACY_TOLERATED_CODES,
|
|
378
498
|
deterministicUuid,
|
|
379
499
|
semanticKeyFor,
|
|
500
|
+
classifyLegacyDiagnostics,
|
|
501
|
+
splitCheckDiagnostics,
|
|
502
|
+
specOntologyContentSignature,
|
|
380
503
|
buildSpecOntology,
|
|
381
504
|
validateSpecOntology,
|
|
382
505
|
approveSpecOntologyFile,
|
|
@@ -47,14 +47,17 @@ node "<spec-package>/skywalk-sdd/log.cjs" start --command=check --project="<spec
|
|
|
47
47
|
node "<spec-package>/skywalk-sdd/log.cjs" semantic-check --project="<spec-package>" --change=<变更名称> --profile=auto --include-tasks > "<spec-package>/skywalk-sdd/state/<变更名称>-preflight.json"
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
输出 `profile / valid /
|
|
50
|
+
输出 `profile / valid / product_valid / revision / counts`、`product_diagnostics`、`tool_diagnostics`、`review_inputs`、`task_completion` 和 `timings_ms`。程序时间不等于模型评审总耗时。
|
|
51
51
|
|
|
52
|
-
-
|
|
52
|
+
- `product_valid` 只看产物质量。需求没有验收、需求没有设计、设计没有任务、任务没有上游、验收没有归属需求、产物缺失、任务依赖成环,这些进入报告并阻断 Check。
|
|
53
|
+
- `tool_diagnostics` 是工具升级带来的身份、来源、谱系和归档诊断。预检已把可物化的部分写入 `ontology/spec-ontology.json` 与 `ontology/legacy-mapping.json`,不改 Markdown。不要向用户展示这些诊断,不要要求用户补 UUID、改锚点或重做归档。
|
|
54
|
+
- 交给独立评审者的程序诊断只使用 `product_diagnostics`。报告只写产物缺口和 design 与 tasks 之间的覆盖缺失。
|
|
55
|
+
- semantic-check 已包含全量对账、工作态 JSON 原子刷新和 pending 标记;无需再调用 semantic-reconcile。`product_valid=true` 只表示产物结构没有阻断,不能宣称已完成独立评审或已入库。
|
|
53
56
|
- 默认从产物推断 simple/full;用户或项目明确要求 strict 时传 `--profile=strict`,不能为通过而降级。
|
|
54
|
-
- `simple` 阻断 STMT 无 AC,未生成 design/tasks 时跳过对应链路;`full` 阻断 STMT→AC→Design→Task 断链;`strict`
|
|
55
|
-
-
|
|
56
|
-
- Continuity、external_ref 与 `ontology/continuity-resolution.json`
|
|
57
|
-
-
|
|
57
|
+
- `simple` 阻断 STMT 无 AC,未生成 design/tasks 时跳过对应链路;`full` 阻断 STMT→AC→Design→Task 断链;`strict` 还要求完整来源字段,来源缺失按工具诊断自行旁路处理,不单独向用户追问。
|
|
58
|
+
- 产物门禁保留追溯断链、悬空业务引用、domain/range 和任务依赖成环。身份、重复锚点、归档版本和谱系由工具旁路处理。
|
|
59
|
+
- Continuity、external_ref 与 `ontology/continuity-resolution.json` 仍属于产物决定。`CONTINUITY_DECISION_REQUIRED` / `CONTINUITY_IDENTITY_MISMATCH` / `EXTERNAL_REF_CONFLICT` 进入报告;不得临时改身份来绕过这些业务门禁。
|
|
60
|
+
- 只有 `product_diagnostics` 里的 error 才向用户展示并在授权内修复后重试。仅有工具诊断时继续独立评审,不把本轮记成预检失败。缺业务决定时本轮结束为 failure,说明“预检阻断,尚未独立评审”。
|
|
58
61
|
|
|
59
62
|
### 多仓库关联配置
|
|
60
63
|
|
|
@@ -75,7 +78,7 @@ node "<spec-package>/skywalk-sdd/log.cjs" semantic-check --project="<spec-packag
|
|
|
75
78
|
|
|
76
79
|
## 3. 一次独立评审
|
|
77
80
|
|
|
78
|
-
程序预检后交给评审者:`review_inputs` 对应的最终文件路径、profile/test-strategy
|
|
81
|
+
程序预检后交给评审者:`review_inputs` 对应的最终文件路径、profile/test-strategy、任务状态、`product_diagnostics`、用户指定的上下文路径、检查范围和作者会话 ID。不传 `tool_diagnostics`,不传作者对话或主 Agent 的结论摘要。评审重点是产物是否完整,以及 spec、design、tasks 之间有没有覆盖缺失。
|
|
79
82
|
|
|
80
83
|
${CHECK_REVIEW_DELEGATION_GUIDANCE}
|
|
81
84
|
|
|
@@ -105,6 +108,6 @@ node "<spec-package>/skywalk-sdd/log.cjs" check-record --project="<spec-package>
|
|
|
105
108
|
|
|
106
109
|
check-record 按预检的同一 profile 重新核验正文、文件集合和历史身份。输入变化返回 CHECK_REVIEW_STALE,不写本轮通过记录;重新预检并复核受影响内容。成功时仍通过原 strict 校验和阶段结束入口,不另手动执行 end。评审输入始终保留,记录失败可以修复后重试。未真实开展评审时不用此命令,按原 end 命令记录 failure 和原因。
|
|
107
110
|
|
|
108
|
-
报告输出五维结论、多仓配置、KB
|
|
111
|
+
报告输出五维结论、多仓配置、KB 状态、产物证据与未执行项。只写需求、设计、任务之间的缺口和质量问题。身份、UUID、归档版本、重复锚点等工具处理结果不写入给用户的报告。只请求检查则结束;已授权实现且没有产物阻断项时进入 apply。修复文档后再检查相关依赖,禁止携带旧结论直接通过;不把 Capability 局部评审写成全 Change 通过。
|
|
109
112
|
|
|
110
113
|
[checklist.md](checklist.md) 仅供日志异常/自检时读取,正常路径不再完整加载一遍。
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
- 自己读取完整的相关业务文档正文一次;批量读取相互独立的文件,同一文件同一版本复用,输出截断时补读缺失部分。
|
|
9
9
|
- Simple 支持根目录 `spec.md/design.md/tasks.md`;Full 使用 `specs/<capability>/`。兼容 `propose.md/task.md`。
|
|
10
|
-
-
|
|
10
|
+
- 程序已核验结构。身份、UUID、谱系和归档诊断由主 Agent 自行处理,不写入给用户的评审结论。
|
|
11
|
+
- 模型负责产物质量:语义覆盖、design 是否覆盖 spec、tasks 是否拆解 design、业务矛盾、可行性和测试质量。不能把“结构合法”当作“设计正确”,也不能用工具诊断代替这些缺口。
|
|
11
12
|
- 代码只读 design/tasks 实际引用的锚点及其必要依赖;不做全仓 diff、不运行测试。用户提供的上下文文件必须读取。
|
|
12
13
|
- 仅指定 Capability 时,补读 proposal 和明确的跨能力依赖;报告覆盖边界,不能宣称全 Change 已独立评审。全 Change 的程序语义门禁仍执行。
|
|
13
14
|
- 不适用的算法、TDD 或实施后检查写 `N/A` 和理由,不能计成通过;仅在 test-strategy=tdd 时加载下面 TDD 资料。
|
|
@@ -80,7 +80,7 @@ curl -sS -X POST "$API/v1/spaces/$SPACE_ID/knowledge-bases/$KB_ID/ingestions/$JO
|
|
|
80
80
|
上传的 zip 包必须满足以下条件(否则入库会失败):
|
|
81
81
|
|
|
82
82
|
- 当前产物包含:`archive-manifest.json`、`canonical-facts.json`、`spec-ontology.json`、`conversion-report.json`
|
|
83
|
-
- 历史兼容包可不带 `canonical-facts.json`,但必须带 `spec-ontology.json`,且 `compatibility_mode=LEGACY_MATERIALIZED
|
|
83
|
+
- 历史兼容包可不带 `canonical-facts.json`,但必须带 `spec-ontology.json`,且 `compatibility_mode=LEGACY_MATERIALIZED`。重复锚点、非法 UUID、身份冲突、依赖成环、归档版本不一致由上传自动消解;无结构化事实时才是 `pending`。不要让用户处理这些历史诊断。
|
|
84
84
|
- `canonical-facts.json` 支持 `kld-sdd-canonical-facts/v1` 与 **`v2`**(v2 可选实体级 `external_refs`)
|
|
85
85
|
- 所有文件需通过 SHA-256 校验(manifest 中记录每个文件的 `content_hash`)
|
|
86
86
|
- 允许的文件类型:`.md`、`.json`、`.jsonl`、`.yaml`、`.yml`
|
|
@@ -22,9 +22,10 @@ allowed-tools:
|
|
|
22
22
|
|
|
23
23
|
## 始终遵守
|
|
24
24
|
|
|
25
|
-
- 不改写历史 Markdown
|
|
26
|
-
-
|
|
27
|
-
-
|
|
25
|
+
- 不改写历史 Markdown。缺少、非法或互相冲突的身份由上传写进 `ontology/legacy-mapping.json`,并生成稳定 UUID。
|
|
26
|
+
- 重复锚点、非法 UUID、身份冲突、任务依赖成环、归档版本对不上,都由上传自动消解并继续发布。不要停下来请用户处理,也不要改 Markdown 去“修”这些历史诊断。
|
|
27
|
+
- `opsx-check` 仍会报告这些问题。入库和召回不依赖 Check 通过。
|
|
28
|
+
- 正式发布仍校验证据、哈希、版本和租户边界。正文没有结构化事实时只入证据,不发布本体事实。
|
|
28
29
|
- 当前 `canonical-facts/v2` 与 `spec-ontology/v1` 同包产出,支持新旧知识库过渡。
|
|
29
30
|
|
|
30
31
|
## 执行流程
|
|
@@ -48,28 +49,29 @@ node skywalk-sdd/ontology/cli.cjs validate-spec-ontology \
|
|
|
48
49
|
--file=openspec/changes/<change-key>/ontology/spec-ontology.json
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
5.
|
|
52
|
-
6.
|
|
52
|
+
5. 上传本身会自动物化并确认。重复锚点按来源拆开,非法或冲突的 UUID 改成稳定派生身份,依赖成环和归档版本不一致只记入转换说明。不要向用户复述这些诊断,不要要求用户选择合并或补 UUID。完成后直接入库并用 spec-generation 召回验证。
|
|
53
|
+
6. 仅当用户明确要求先审阅,或正文没有任何结构化事实时,才保留 candidate。此时再运行:
|
|
53
54
|
|
|
54
55
|
```bash
|
|
55
56
|
node skywalk-sdd/ontology/cli.cjs approve-spec-ontology \
|
|
56
57
|
--file=openspec/changes/<change-key>/ontology/spec-ontology.json
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
7. 通过 `opsx-kb-ingest`
|
|
61
|
-
8. 通过 `context-client.cjs --mode=spec-generation`
|
|
60
|
+
用户没有要求审阅时不要执行这一步。上传已经自动确认,不要因为重复锚点或身份改写再停下来。
|
|
61
|
+
7. 通过 `opsx-kb-ingest` 打包上传。正文变化时重新物化并自动确认。已确认本体与当前 Markdown 不一致时不沿用旧确认。
|
|
62
|
+
8. 通过 `context-client.cjs --mode=spec-generation` 执行一次召回验证。向用户只报告是否入库、是否召回到 current 事实。自动改写过的身份、拆开的重复锚点、忽略的成环和归档差异写在命令输出里,不单独向用户确认。
|
|
62
63
|
|
|
63
|
-
##
|
|
64
|
+
## 完成后的简报
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
只向用户说明入库和召回结果。下面这些由工具自动处理,不列入需要用户决定的事项:
|
|
66
67
|
|
|
67
68
|
```text
|
|
68
69
|
兼容模式:LEGACY_MATERIALIZED
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
已发布事实:12
|
|
71
|
+
自动改写身份:3
|
|
72
|
+
重复锚点已拆分:1
|
|
73
|
+
归档/成环已忽略:2
|
|
74
|
+
仅证据:0
|
|
73
75
|
```
|
|
74
76
|
|
|
75
77
|
若 KB 不可用,保留已物化产物并标记 `remoteContextVerified=false`;本地编写可继续,入库与发布状态明确为未完成。
|