kld-sdd 2.7.8 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +26 -0
  2. package/USABILITY.md +72 -0
  3. package/bin/kld-sdd-init.js +718 -34
  4. package/lib/command-bridge.js +26 -17
  5. package/lib/deploy-codebuddy-hooks.js +19 -26
  6. package/lib/deploy-strategy.js +293 -0
  7. package/lib/hook-gate-core.js +7 -8
  8. package/lib/hook-install.js +75 -0
  9. package/lib/init.js +507 -726
  10. package/lib/maintenance.js +221 -0
  11. package/lib/managed-hook-hashes.json +24 -0
  12. package/lib/settings-merge.js +102 -25
  13. package/lib/skills-bundle.js +5 -5
  14. package/lib/spec-choice.js +85 -0
  15. package/lib/tool-profiles.js +2 -2
  16. package/lib/uninstall.js +453 -0
  17. package/package.json +7 -4
  18. package/skywalk-sdd/context-client.cjs +38 -7
  19. package/skywalk-sdd/index.cjs +153 -27
  20. package/skywalk-sdd/kb-sync-identity.cjs +1 -1
  21. package/skywalk-sdd/kb-upload.cjs +242 -85
  22. package/skywalk-sdd/lib/check-review.cjs +41 -0
  23. package/skywalk-sdd/lib/shared.cjs +62 -41
  24. package/skywalk-sdd/lib/test-execution.cjs +130 -0
  25. package/skywalk-sdd/lib/usage-reporter.cjs +17 -8
  26. package/skywalk-sdd/ontology/archive-package.cjs +141 -0
  27. package/skywalk-sdd/ontology/artifact-parser.cjs +24 -3
  28. package/skywalk-sdd/ontology/cli.cjs +53 -0
  29. package/skywalk-sdd/ontology/id.cjs +1 -1
  30. package/skywalk-sdd/ontology/identity-index.cjs +5 -1
  31. package/skywalk-sdd/ontology/resolve-spec-root.cjs +49 -0
  32. package/skywalk-sdd/ontology/schema.cjs +6 -0
  33. package/skywalk-sdd/ontology/sdd-config.cjs +33 -5
  34. package/skywalk-sdd/ontology/spec-detect.cjs +421 -0
  35. package/skywalk-sdd/ontology/spec-ontology.cjs +507 -0
  36. package/skywalk-sdd/ontology/workspace-layout.cjs +65 -17
  37. package/templates/git-hooks/commit-msg +17 -85
  38. package/templates/git-hooks/commit-msg-runner.cjs +44 -0
  39. package/templates/git-hooks/commit-msg-sdd-trailer.cjs +60 -69
  40. package/templates/git-hooks/consistency-check-core.cjs +92 -261
  41. package/templates/git-hooks/delivery-check.cjs +231 -0
  42. package/templates/git-hooks/delivery-snapshot.cjs +181 -0
  43. package/templates/git-hooks/hooks.config +8 -6
  44. package/templates/git-hooks/pre-commit +19 -5
  45. package/templates/git-hooks/pre-commit-consistency-check.cjs +13 -31
  46. package/templates/git-hooks/pre-push +19 -5
  47. package/templates/git-hooks/pre-push-consistency-check.cjs +36 -83
  48. package/templates/hooks/claude/hooks/sdd-post-tool.cjs +2 -2
  49. package/templates/hooks/codebuddy/hooks/hook-gate-core.cjs +7 -8
  50. package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +2 -2
  51. package/templates/openspec/proposal.md +1 -1
  52. package/templates/openspec/spec.md +8 -0
  53. package/templates/skills/kld-sdd/opsx-apply/SKILL.md +28 -24
  54. package/templates/skills/kld-sdd/opsx-apply/checklist.md +12 -3
  55. package/templates/skills/kld-sdd/opsx-apply/reference.md +21 -1
  56. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +20 -18
  57. package/templates/skills/kld-sdd/opsx-check/SKILL.md +18 -13
  58. package/templates/skills/kld-sdd/opsx-check/reference.md +3 -1
  59. package/templates/skills/kld-sdd/opsx-check/review-template.json +22 -0
  60. package/templates/skills/kld-sdd/opsx-check/reviewer.md +27 -6
  61. package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +34 -112
  62. package/templates/skills/kld-sdd/opsx-consistency-check/scripts/scripts.cjs +15 -14
  63. package/templates/skills/kld-sdd/opsx-kb-config/SKILL.md +93 -61
  64. package/templates/skills/kld-sdd/opsx-kb-config/reference.md +14 -12
  65. package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +16 -5
  66. package/templates/skills/kld-sdd/opsx-kb-ingest/reference.md +13 -5
  67. package/templates/skills/kld-sdd/opsx-kb-ingest/state.example.json +18 -0
  68. package/templates/skills/kld-sdd/opsx-ontology-ingest/SKILL.md +77 -0
  69. package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +5 -4
  70. package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +14 -1
  71. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +1 -1
  72. package/templates/skills/kld-sdd/opsx-propose/interaction-policy.md +9 -0
  73. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +5 -3
  74. package/templates/skills/kld-sdd/opsx-task/SKILL.md +2 -2
  75. package/index.js +0 -13
@@ -1313,28 +1313,32 @@ function acquireEventWriteLock(dataDir, changeName, options = {}) {
1313
1313
  created_at: nowISO(),
1314
1314
  change: safeChangeName(changeName),
1315
1315
  };
1316
- let descriptor;
1316
+ const candidatePath = `${lockPath}.${token}.tmp`;
1317
1317
  try {
1318
- descriptor = fs.openSync(lockPath, 'wx');
1319
- fs.writeFileSync(descriptor, JSON.stringify(payload), 'utf8');
1320
- fs.closeSync(descriptor);
1318
+ fs.writeFileSync(candidatePath, JSON.stringify(payload), { encoding: 'utf8', flag: 'wx', mode: 0o600 });
1319
+ // Publish a complete payload atomically. A contender must never observe an
1320
+ // empty lock and remove it while its owner is still initializing it.
1321
+ fs.linkSync(candidatePath, lockPath);
1321
1322
  return { ...payload, path: lockPath };
1322
1323
  } catch (error) {
1323
- if (descriptor !== undefined) fs.closeSync(descriptor);
1324
1324
  if (error?.code !== 'EEXIST') throw error;
1325
1325
  let existing = null;
1326
1326
  try {
1327
1327
  existing = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
1328
1328
  } catch {}
1329
1329
  const createdAt = Date.parse(existing?.created_at || '');
1330
- const stale = !Number.isFinite(createdAt)
1331
- || Date.now() - createdAt > staleMs
1332
- || !isProcessAlive(Number(existing?.pid));
1330
+ let age = 0;
1331
+ try { age = Date.now() - fs.statSync(lockPath).mtimeMs; } catch {}
1332
+ const stale = existing && Number.isFinite(createdAt)
1333
+ ? !isProcessAlive(Number(existing.pid))
1334
+ : age > staleMs;
1333
1335
  if (stale) {
1334
1336
  try { fs.rmSync(lockPath, { force: true }); } catch {}
1335
1337
  continue;
1336
1338
  }
1337
1339
  Atomics.wait(EVENT_LOCK_WAIT_BUFFER, 0, 0, 10);
1340
+ } finally {
1341
+ try { fs.unlinkSync(candidatePath); } catch {}
1338
1342
  }
1339
1343
  }
1340
1344
  const error = new Error(`EVENT_WRITE_LOCK_TIMEOUT: 等待 change "${changeName}" 的事件写锁超时`);
@@ -1539,7 +1543,15 @@ function readAllEvents(dataDir) {
1539
1543
  /**
1540
1544
  * 计算单个 change 的四维指标
1541
1545
  */
1546
+ function normalizeLifecycleEvents(events) {
1547
+ // Older clients stored run notifications as stage_end with a different event ID.
1548
+ // Preserve the event and its wire kind without counting it as another stage.
1549
+ return events.map(event => event.type === 'stage_end' && event.event_kind === 'run_ended'
1550
+ ? { ...event, type: 'run_end' } : event);
1551
+ }
1552
+
1542
1553
  function computeChangeMetrics(changeName, events) {
1554
+ events = normalizeLifecycleEvents(events);
1543
1555
  const changeEvents = events.filter(e => e.change === changeName && !e.orphan);
1544
1556
  if (changeEvents.length === 0) return null;
1545
1557
 
@@ -1868,6 +1880,7 @@ function createReworkBucket(command, capability) {
1868
1880
  }
1869
1881
 
1870
1882
  function summarizeStageExecutions(events) {
1883
+ events = normalizeLifecycleEvents(events);
1871
1884
  const stageEvents = events.filter(e => !e.orphan && (e.type === 'stage_start' || e.type === 'stage_end'));
1872
1885
  const starts = stageEvents
1873
1886
  .filter(e => e.type === 'stage_start')
@@ -3329,6 +3342,7 @@ function computeTelemetryHealthMetrics(events, options = {}) {
3329
3342
  }
3330
3343
 
3331
3344
  function computePdfMvpMetrics(events, options = {}) {
3345
+ events = normalizeLifecycleEvents(events);
3332
3346
  if (options.level === 'capability' || options.capability) {
3333
3347
  return computeCapabilityMetrics(options.capability, events, options);
3334
3348
  }
@@ -4856,6 +4870,7 @@ function tddPairFinding(pair) {
4856
4870
  }
4857
4871
 
4858
4872
  function buildReport(projectRoot, events, options = {}) {
4873
+ events = normalizeLifecycleEvents(events);
4859
4874
  const scopedReport = scopeEventsForChangeReport(events, options.change);
4860
4875
  events = scopedReport.events;
4861
4876
  const archiveEvent = latestByTimestamp(events.filter(e => {
@@ -5208,6 +5223,7 @@ function filterEventsByDate(events, dateFrom, dateTo) {
5208
5223
  }
5209
5224
 
5210
5225
  function computeDoctorReport(events, options = {}) {
5226
+ events = normalizeLifecycleEvents(events);
5211
5227
  const scopedEvents = options.change
5212
5228
  ? events.filter(e => e.change === options.change)
5213
5229
  : events;
@@ -5418,12 +5434,12 @@ function cmdStart(args) {
5418
5434
 
5419
5435
  const ONTOLOGY_AUTHORING_STAGES = new Set(['propose', 'spec', 'design', 'task', 'check']);
5420
5436
 
5421
- function syncWorkingOntologyForStage(projectRoot, changeName, command, result) {
5437
+ function syncWorkingOntologyForStage(projectRoot, changeName, command, result, verifiedReconciliation = null) {
5422
5438
  if (!ONTOLOGY_AUTHORING_STAGES.has(command) || result === 'failure') return null;
5423
5439
  if (!changeName || changeName === 'general') return null;
5424
5440
  const changeDir = getChangeDir(projectRoot, changeName);
5425
5441
  if (!fs.existsSync(changeDir)) return null;
5426
- const reconciled = require('./ontology/runtime.cjs').reconcileChange(
5442
+ const reconciled = verifiedReconciliation || require('./ontology/runtime.cjs').reconcileChange(
5427
5443
  projectRoot,
5428
5444
  changeName,
5429
5445
  {
@@ -5592,7 +5608,8 @@ function cmdEnd(args, options = {}) {
5592
5608
 
5593
5609
  let semanticState = null;
5594
5610
  try {
5595
- semanticState = syncWorkingOntologyForStage(projectRoot, change, command, result);
5611
+ semanticState = syncWorkingOntologyForStage(projectRoot, change, command, result,
5612
+ command === 'check' ? options.verifiedReconciliation : null);
5596
5613
  if (semanticState) {
5597
5614
  details = {
5598
5615
  ...details,
@@ -5779,7 +5796,7 @@ function cmdEnd(args, options = {}) {
5779
5796
  appendEvent(dataDir, event.change, cleanOptionalFields({
5780
5797
  schema_version: SCHEMA_VERSION,
5781
5798
  event_id: generateEventId(),
5782
- type: 'stage_end',
5799
+ type: 'run_end',
5783
5800
  event_kind: 'run_ended',
5784
5801
  source: event.source,
5785
5802
  command: 'archive',
@@ -6434,8 +6451,10 @@ function normalizeStrictTestResult(args, details) {
6434
6451
  failure_type: failureType,
6435
6452
  expected_failure: expectedFailure,
6436
6453
  evidence_tier: 'strict',
6454
+ ...(input.execution ? { execution: input.execution } : {}),
6437
6455
  valid_red: tddPhase === 'red'
6438
6456
  ? expectedFailure && exitCode !== 0 && ['assertion', 'contract'].includes(failureType)
6457
+ && (!input.execution || (input.counts_known && Number(input.failed) > 0))
6439
6458
  : false,
6440
6459
  };
6441
6460
  if (input.counts_known) {
@@ -6453,6 +6472,7 @@ function isValidCompletionTestResults(testResults) {
6453
6472
  if (!testResults || typeof testResults !== 'object') return false;
6454
6473
  if (!['green', 'refactor', 'regression'].includes(testResults.tdd_phase)) return false;
6455
6474
  if (testResults.exit_code !== 0 || testResults.failure_type !== 'none') return false;
6475
+ if (testResults.execution && (!testResults.counts_known || !(testResults.passed > 0))) return false;
6456
6476
  if (testResults.counts_known === true && Number(testResults.failed) !== 0) return false;
6457
6477
  return true;
6458
6478
  }
@@ -7348,18 +7368,6 @@ function cmdRecord(args) {
7348
7368
  try {
7349
7369
  details = parseJsonOption(args, 'details-json', 'details-file', projectRoot, {});
7350
7370
  detailsFilePath = args['details-file'] || args.details_file || null;
7351
- // 默认清理 details-file,避免项目根目录被临时 JSON 污染;--keep-details 保留调试用
7352
- if (detailsFilePath && !args['keep-details'] && !args.keep_details) {
7353
- try {
7354
- const fileToRemove = normalizeDetailsFilePath(detailsFilePath, projectRoot);
7355
- if (fs.existsSync(fileToRemove)) {
7356
- fs.unlinkSync(fileToRemove);
7357
- }
7358
- } catch (cleanupErr) {
7359
- // 清理失败不阻塞主流程
7360
- try { console.error(`[telemetry] details-file 清理失败(不阻塞): ${cleanupErr.message}`); } catch {}
7361
- }
7362
- }
7363
7371
  } catch (err) {
7364
7372
  fail(`details JSON 解析失败: ${err.message}`);
7365
7373
  }
@@ -7467,6 +7475,9 @@ function cmdRecord(args) {
7467
7475
  };
7468
7476
  }
7469
7477
  if (strict && type === 'test_result') {
7478
+ if (details?.test_results?.execution) {
7479
+ require('./lib/test-execution.cjs').verifyCapture(details.test_results, projectRoot);
7480
+ }
7470
7481
  const testResults = normalizeStrictTestResult(args, details);
7471
7482
  strictRunId = testResults.run_id;
7472
7483
  strictTaskId = testResults.task_id;
@@ -7560,6 +7571,7 @@ function cmdRecord(args) {
7560
7571
  const taskId = strictTaskId || details?.task_id || null;
7561
7572
  if (taskId) runCheckTaskSync(projectRoot, change, taskId);
7562
7573
  } catch (taskErr) {
7574
+ if (args['require-projection']) throw taskErr;
7563
7575
  try { console.error(`[telemetry] tasks.md 主任务状态自动同步失败(不阻塞): ${taskErr.message}`); } catch {}
7564
7576
  }
7565
7577
  };
@@ -7636,6 +7648,89 @@ function cmdRecord(args) {
7636
7648
  } catch (error) {
7637
7649
  fail(error.message || String(error));
7638
7650
  }
7651
+ // Keep retry input until validation, persistence and task projection all succeed.
7652
+ if (detailsFilePath && !args['keep-details'] && !args.keep_details) {
7653
+ try { fs.unlinkSync(normalizeDetailsFilePath(detailsFilePath, projectRoot)); }
7654
+ catch (error) {
7655
+ if (error.code !== 'ENOENT') console.error(`[telemetry] details-file 清理失败(不阻塞): ${error.message}`);
7656
+ }
7657
+ }
7658
+ if (!args.quiet) console.log(JSON.stringify(output, null, 2));
7659
+ return output;
7660
+ }
7661
+
7662
+ function cmdTestRun(args) {
7663
+ const projectRoot = normalizeProjectRoot(args.project || process.cwd());
7664
+ requireValidChangeName(args.change);
7665
+ requireStrictString(args['session-id'], 'TEST_SESSION_REQUIRED', 'session-id');
7666
+ const completion = args['completion-file']
7667
+ ? JSON.parse(fs.readFileSync(path.resolve(args['completion-file']), 'utf8')) : [];
7668
+ const declared = String(args['task-ids'] || args['task-id'] || '').split(',').map(x => x.trim());
7669
+ if (!Array.isArray(completion) || completion.some(item => !declared.includes(item.task_id)
7670
+ || typeof item.tdd_required !== 'boolean'
7671
+ || (!Array.isArray(item.files) && !item.no_file_change_reason))) {
7672
+ throw new Error('TEST_COMPLETION_INVALID: 完成信息须关联本轮明确任务,并包含实际文件及 tdd_required');
7673
+ }
7674
+ for (const item of completion) {
7675
+ normalizeRepositoryRelativeFiles(item.files || []);
7676
+ runCheckTaskSync(projectRoot, args.change, item.task_id, true);
7677
+ }
7678
+ const captured = require('./lib/test-execution.cjs').captureTest(args, projectRoot, args.change);
7679
+ const record = cmdRecord({ ...args, quiet: true, strict: true, type: 'test_result',
7680
+ command: 'test', project: projectRoot, change: args.change, 'run-id': captured.runId,
7681
+ source: args.source || 'test-run', summary: args.summary || `执行测试:${captured.testResults.command}`,
7682
+ result: captured.testResults.result, details: undefined,
7683
+ 'details-file': captured.receipt, 'keep-details': true,
7684
+ });
7685
+ const completed = [];
7686
+ if (isValidCompletionTestResults(captured.testResults)) {
7687
+ for (const item of completion) {
7688
+ const updated = cmdRecord({ ...args, quiet: true, strict: true, type: 'task_update', command: 'apply',
7689
+ 'require-projection': true,
7690
+ project: projectRoot, 'run-id': `${captured.runId}:${item.task_id}`, 'task-id': item.task_id,
7691
+ status: 'completed', result: 'success', source: args.source || 'test-run', summary: `${item.task_id} 通过本轮测试`,
7692
+ 'details-json': JSON.stringify({ task_update: { ...item, test_event_id: record.event_id } }),
7693
+ });
7694
+ completed.push({ task_id: item.task_id, event_id: updated.event_id, test_event_id: record.event_id });
7695
+ }
7696
+ }
7697
+ const output = { ...record, test_results: captured.testResults, receipt: captured.receipt, completed_tasks: completed };
7698
+ console.log(JSON.stringify(output, null, 2));
7699
+ process.exitCode = captured.testResults.exit_code;
7700
+ return output;
7701
+ }
7702
+
7703
+ function cmdCheckRecord(args) {
7704
+ const projectRoot = normalizeProjectRoot(args.project || process.cwd());
7705
+ requireValidChangeName(args.change);
7706
+ const preflight = JSON.parse(fs.readFileSync(path.resolve(args['preflight-file']), 'utf8'));
7707
+ const review = JSON.parse(fs.readFileSync(path.resolve(args['review-file']), 'utf8'));
7708
+ const state = require('./ontology/runtime.cjs').reconcileChange(projectRoot, args.change, { profile: preflight.profile || 'auto', markPending: true });
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
7714
+ || preflight.review_inputs?.identity_history_hash !== state.state.identity_history_hash
7715
+ || JSON.stringify(preflight.review_inputs?.files) !== JSON.stringify(state.state.files)) {
7716
+ throw new Error('CHECK_REVIEW_STALE: 文档或历史身份已变化,重新预检并评审受影响内容');
7717
+ }
7718
+ const events = readEvents(getDataDir(projectRoot), args.change);
7719
+ const beforeApply = !events.some(e => (e.type === 'stage_start' && ['apply', 'test', 'archive'].includes(e.command)) || ['test_result', 'task_update', 'build_result'].includes(e.type));
7720
+ const previous = events.filter(e => e.type === 'check_result').slice(-1)[0]?.details?.check_results;
7721
+ const assembled = require('./lib/check-review.cjs').assembleReview(review, scanTaskCompletion(projectRoot, args.change), {
7722
+ beforeApply, previousBeforeApply: previous?.fixed_before_apply, revision: preflight.revision,
7723
+ });
7724
+ const record = cmdRecord({ ...args, quiet: true, strict: true, type: 'check_result', command: 'check',
7725
+ project: projectRoot, result: assembled.result, 'run-id': args['run-id'] || generateEventId(),
7726
+ source: args.source || 'opsx-command', summary: args.summary || '按实际评审项生成 Check 结果',
7727
+ 'session-id': args['session-id'] || review.reviewer?.parent_session_id || review.reviewer?.review_session_id,
7728
+ 'details-json': JSON.stringify(assembled.details),
7729
+ });
7730
+ const ended = args['end-event-id'] ? cmdEnd({ ...args, 'event-id': args['end-event-id'], command: 'check',
7731
+ project: projectRoot, result: assembled.result, summary: args.summary || 'Check 已记录,复核文档状态',
7732
+ }, { silent: true, verifiedReconciliation: state }) : null;
7733
+ const output = { ...record, result: assembled.result, check_results: assembled.details.check_results, stage_end: ended };
7639
7734
  console.log(JSON.stringify(output, null, 2));
7640
7735
  return output;
7641
7736
  }
@@ -8148,16 +8243,20 @@ function cmdSemanticIdentity(args) {
8148
8243
  }
8149
8244
 
8150
8245
  function semanticResultPayload(result) {
8246
+ const split = require('./ontology/spec-ontology.cjs').splitCheckDiagnostics(result.diagnostics);
8151
8247
  return {
8152
8248
  change: result.state.change,
8153
8249
  profile: result.profile,
8154
8250
  valid: result.valid,
8251
+ product_valid: split.product_valid,
8155
8252
  review_status: result.state.review_status,
8156
8253
  revision: result.revision,
8157
8254
  changed: Boolean(result.changed),
8158
8255
  counts: result.counts,
8159
8256
  paths: result.paths,
8160
8257
  diagnostics: result.diagnostics,
8258
+ tool_diagnostics: split.tool_diagnostics,
8259
+ product_diagnostics: split.product_diagnostics,
8161
8260
  };
8162
8261
  }
8163
8262
 
@@ -8199,13 +8298,32 @@ function cmdSemanticReconcile(args, options = {}) {
8199
8298
  total: Number(finished - started) / 1e6,
8200
8299
  };
8201
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
+ }
8202
8316
  console.log(JSON.stringify(payload, null, 2));
8203
- 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
+ }
8204
8322
  return result;
8205
8323
  }
8206
8324
 
8207
8325
  function cmdSemanticCheck(args) {
8208
- return cmdSemanticReconcile(args, { markPending: true, includeTasks: Boolean(args['include-tasks']) });
8326
+ return cmdSemanticReconcile(args, { markPending: true, includeTasks: Boolean(args['include-tasks']), productGate: true });
8209
8327
  }
8210
8328
 
8211
8329
  function cmdSemanticStatus(args) {
@@ -8424,6 +8542,12 @@ function main() {
8424
8542
  case 'record':
8425
8543
  cmdRecord(flags);
8426
8544
  break;
8545
+ case 'test-run':
8546
+ cmdTestRun(flags);
8547
+ break;
8548
+ case 'check-record':
8549
+ cmdCheckRecord(flags);
8550
+ break;
8427
8551
  case 'metrics':
8428
8552
  cmdMetrics(flags);
8429
8553
  break;
@@ -8504,6 +8628,8 @@ SDD Telemetry CLI - 流程度量采集工具
8504
8628
  start 记录 SDD 阶段开始,返回 event_id
8505
8629
  end 记录 SDD 阶段结束,关联 event_id
8506
8630
  record 记录 task_update/check_result/test_result 等结构化事件
8631
+ test-run 执行 --command-json 参数数组,采集退出码、测试计数、原始输出与任务关联
8632
+ check-record 从 --review-file 与 --preflight-file 计算并记录 Check 结果
8507
8633
  metrics 查询度量指标(四维分析)
8508
8634
  report 生成只读度量报告(不写入事件)
8509
8635
  doctor 诊断 Telemetry 数据质量
@@ -8511,7 +8637,7 @@ SDD Telemetry CLI - 流程度量采集工具
8511
8637
  check-task 扫描变更目录 tasks.md 并勾选指定 task_id
8512
8638
  archive-docs 将 Simple/Full spec 变更真实移动到 openspec/changes/archive/,并可结束 archive 阶段生成报告
8513
8639
  kb-upload 知识库上传(文件夹自动打包 / zip 直传),支持随时入库
8514
- semantic-identity 生成或复用实体/版本 ID(8 位十六进制);无需网络和中央 ID 服务
8640
+ semantic-identity 生成或复用实体/版本 ID(完整 UUID,界面可显示短标识);无需网络和中央 ID 服务
8515
8641
  semantic-scan 只读解析并校验当前 Change
8516
8642
  semantic-reconcile 全量对账文件与本地工作态本体实例
8517
8643
  semantic-check 全量对账并在通过时标记 pending;--include-tasks 同时返回任务状态、评审输入清单和程序耗时
@@ -266,7 +266,7 @@ function initKbConfig(specRoot, args = {}) {
266
266
  throw new Error('KB apiKey 未配置(kb-state.json 中缺少 apiKey)。');
267
267
  }
268
268
  if (!kbParams.spaceId || !kbParams.kbId) {
269
- throw new Error('KB targets 未配置(kb-state.json 中缺少 targets[0].spaceId/kbId)。');
269
+ throw new Error('KB 上传目标未配置(请检查 kb-state.json 的 targets 与 uploadTarget)。');
270
270
  }
271
271
 
272
272
  // 4. 校验 project_id 与 spaceKey 一致性