kld-sdd 2.6.21 → 2.7.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 (71) hide show
  1. package/README.md +12 -5
  2. package/USABILITY.md +84 -0
  3. package/bin/kld-sdd-init.js +3 -9
  4. package/kld-sdd-guide.html +16 -4
  5. package/lib/hook-gate-core.js +327 -0
  6. package/lib/init.js +241 -94
  7. package/lib/scale-thresholds.json +19 -0
  8. package/lib/skills-bundle.js +2 -1
  9. package/package.json +5 -3
  10. package/skywalk-sdd/context-client.cjs +50 -30
  11. package/skywalk-sdd/index.cjs +49 -16
  12. package/skywalk-sdd/kb-sync-identity.cjs +99 -451
  13. package/skywalk-sdd/kb-upload.cjs +67 -44
  14. package/skywalk-sdd/lib/git-identity.cjs +270 -0
  15. package/skywalk-sdd/lib/usage-contract.cjs +13 -81
  16. package/skywalk-sdd/lib/usage-reporter.cjs +235 -104
  17. package/skywalk-sdd/lib/user-config.cjs +21 -46
  18. package/skywalk-sdd/metrics-v3.cjs +2 -2
  19. package/skywalk-sdd/ontology/active-changes.cjs +2 -1
  20. package/skywalk-sdd/ontology/archive-package.cjs +1 -1
  21. package/skywalk-sdd/ontology/artifact-parser.cjs +8 -6
  22. package/skywalk-sdd/ontology/id.cjs +5 -4
  23. package/skywalk-sdd/ontology/identity-index.cjs +9 -4
  24. package/skywalk-sdd/ontology/list-changes.cjs +1 -1
  25. package/skywalk-sdd/ontology/runtime.cjs +7 -3
  26. package/skywalk-sdd/ontology/schema.cjs +2 -0
  27. package/skywalk-sdd/ontology/traceability-validator.cjs +74 -4
  28. package/skywalk-sdd/ontology/workspace-layout.cjs +25 -5
  29. package/skywalk-sdd/reporting/change-report-model.cjs +4 -3
  30. package/templates/git-hooks/commit-msg +71 -27
  31. package/templates/git-hooks/consistency-check-core.cjs +1087 -0
  32. package/templates/git-hooks/hooks.config +38 -0
  33. package/templates/git-hooks/pre-commit +62 -27
  34. package/templates/git-hooks/pre-commit-consistency-check.cjs +29 -332
  35. package/templates/git-hooks/pre-commit-sdd-check.cjs +98 -0
  36. package/templates/git-hooks/pre-push +70 -27
  37. package/templates/git-hooks/pre-push-consistency-check.cjs +61 -299
  38. package/templates/hooks/codebuddy/hooks/sdd-tdd-rhythm-gate.cjs +1 -1
  39. package/templates/openspec/tasks.md +3 -3
  40. package/templates/skills/kld-sdd/opsx-apply/SKILL.md +43 -6
  41. package/templates/skills/kld-sdd/opsx-apply/checklist.md +1 -1
  42. package/templates/skills/kld-sdd/opsx-apply/reference.md +1 -1
  43. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +9 -13
  44. package/templates/skills/kld-sdd/opsx-check/SKILL.md +54 -328
  45. package/templates/skills/kld-sdd/opsx-check/checklist.md +7 -4
  46. package/templates/skills/kld-sdd/opsx-check/reference.md +58 -0
  47. package/templates/skills/kld-sdd/opsx-check/result-template.json +52 -0
  48. package/templates/skills/kld-sdd/opsx-check/reviewer.md +100 -0
  49. package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +82 -451
  50. package/templates/skills/kld-sdd/opsx-consistency-check/{reference.md → references/reference.md} +0 -1
  51. package/templates/skills/kld-sdd/opsx-consistency-check/scripts/scripts.cjs +517 -0
  52. package/templates/skills/kld-sdd/opsx-design/SKILL.md +10 -30
  53. package/templates/skills/kld-sdd/opsx-design/checklist.md +3 -4
  54. package/templates/skills/kld-sdd/opsx-design/reference.md +1 -1
  55. package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +12 -74
  56. package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +7 -5
  57. package/templates/skills/kld-sdd/opsx-ontology-query/phase-1-prechange.md +2 -2
  58. package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +1 -1
  59. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +28 -73
  60. package/templates/skills/kld-sdd/opsx-propose/checklist.md +8 -8
  61. package/templates/skills/kld-sdd/opsx-propose/interaction-policy.md +28 -0
  62. package/templates/skills/kld-sdd/opsx-propose/reference.md +12 -46
  63. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +14 -61
  64. package/templates/skills/kld-sdd/opsx-spec/checklist.md +3 -3
  65. package/templates/skills/kld-sdd/opsx-task/SKILL.md +38 -32
  66. package/templates/skills/kld-sdd/opsx-task/checklist.md +5 -6
  67. package/templates/skills/kld-sdd/opsx-test/SKILL.md +2 -0
  68. package/templates/skills/kld-sdd/tdd-rules/rules/tdd-strategy-selection.md +1 -1
  69. package/lib/device-auth-cli.js +0 -130
  70. package/lib/device-auth.js +0 -345
  71. package/lib/init-account-binding.js +0 -108
@@ -12,6 +12,7 @@ const {
12
12
  loadKbState,
13
13
  resolveKbParams,
14
14
  stripBom,
15
+ httpGetJson,
15
16
  } = require('./lib/shared.cjs');
16
17
 
17
18
  // Re-export requestJson for external callers (API compatibility)
@@ -105,6 +106,7 @@ function buildPayload(args, mode) {
105
106
  }
106
107
 
107
108
  async function retrieveContext(args = parseArgs(process.argv), env = process.env) {
109
+ if (args.offline) return unavailableContext('engineering_kb_offline', '本次使用本地上下文,未验证远程基线');
108
110
  // 优先从共享 state 文件读取配置(与 opsx-ontology-query / opsx-kb-ingest 共用)
109
111
  let stateConfig = {};
110
112
  const stateFile = args['state-file'] || env.SDD_KB_STATE_FILE;
@@ -131,12 +133,17 @@ async function retrieveContext(args = parseArgs(process.argv), env = process.env
131
133
 
132
134
  const { apiBase: kbApiBase, token: kbToken, spaceId: kbSpaceId, kbId: kbKbId, targets: kbTargets } = resolveKbParams(stateConfig, env);
133
135
 
136
+ if (Boolean(args['space-id']) !== Boolean(args['kb-id'])) throw new Error('请同时指定 --space-id 和 --kb-id');
137
+ const explicitSpace = args['space-id'] || env.ENGINEERING_KB_SPACE_ID;
138
+ const explicitKb = args['kb-id'] || env.ENGINEERING_KB_KB_ID;
139
+ if (Boolean(explicitSpace) !== Boolean(explicitKb)) throw new Error('请同时配置目标 spaceId 和 kbId');
140
+ if (kbTargets.length > 1 && !explicitSpace) throw new Error('配置了多个 KB,请用 --space-id 和 --kb-id 明确本次查询目标');
134
141
  const apiBase = args['api-base'] || kbApiBase;
135
142
  const token = args.token || kbToken;
136
- const spaceId = args['space-id'] || kbSpaceId;
137
- const kbId = args['kb-id'] || kbKbId;
143
+ const spaceId = explicitSpace || kbSpaceId;
144
+ const kbId = explicitKb || kbKbId;
138
145
 
139
- // --check-only: 只检查 KB 配置是否就绪,不发起 API 请求
146
+ // --check-only performs an authenticated check against the selected KB.
140
147
  // 用于 SDD 各阶段(propose/spec/design/task/check)统一检测 KB 可用性,消除相对路径歧义
141
148
  if (args['check-only']) {
142
149
  const missing = [];
@@ -145,31 +152,27 @@ async function retrieveContext(args = parseArgs(process.argv), env = process.env
145
152
  if (!kbId) missing.push('kbId');
146
153
  if (missing.length > 0) {
147
154
  return {
148
- available: false,
149
- reason: 'engineering_kb_not_configured',
155
+ ...unavailableContext('engineering_kb_not_configured', 'KB 尚未配置,本地工作可以继续'),
156
+ configured: false,
150
157
  missing,
151
- hint: 'Run /opsx-kb-config to configure kb-state.json (in spec root)',
152
158
  };
153
159
  }
154
- return {
155
- available: true,
156
- api: apiBase,
157
- targets: kbTargets,
158
- };
160
+ const endpoint = `${String(apiBase).replace(/\/+$/, '')}/v1/spaces/${encodeURIComponent(spaceId)}`
161
+ + `/knowledge-bases/${encodeURIComponent(kbId)}/context/readiness`;
162
+ const response = await httpGetJson(endpoint, token, contextTimeout(args, env, 3000));
163
+ if (response?.code != null && response.code !== 0) throw new Error(response.message || 'KB readiness failed');
164
+ const readiness = response?.data || response;
165
+ if (readiness?.available !== true || readiness?.authorized !== true) throw new Error('KB 未返回有效的权限与就绪状态');
166
+ return { ...readiness, configured: true, api: apiBase, spaceId, kbId };
167
+
159
168
  }
160
169
 
161
- if (!spaceId || !kbId) {
162
- return {
163
- available: false,
164
- advisory: true,
165
- degraded: true,
166
- reason: 'engineering_kb_not_configured',
167
- hint: 'set ENGINEERING_KB_SPACE_ID and ENGINEERING_KB_KB_ID; do not fall back to local archive/',
168
- };
170
+ if (!spaceId || !kbId || !token) {
171
+ return { ...unavailableContext('engineering_kb_not_configured', 'KB 尚未配置,本地工作可以继续'), configured: false };
169
172
  }
170
173
 
171
174
  const mode = String(args.mode || 'match').trim().toLowerCase() === 'resolve' ? 'resolve' : 'match';
172
- const timeoutMs = Number(args.timeout || env.ENGINEERING_KB_TIMEOUT_MS || 30000);
175
+ const timeoutMs = contextTimeout(args, env, 5000);
173
176
  const payload = buildPayload(args, mode);
174
177
  const data = await requestJson(
175
178
  buildEndpoint(apiBase, spaceId, kbId, mode),
@@ -184,21 +187,38 @@ async function retrieveContext(args = parseArgs(process.argv), env = process.env
184
187
  };
185
188
  }
186
189
 
190
+ function contextTimeout(args, env, fallback) {
191
+ const timeout = Number(args.timeout || env.ENGINEERING_KB_TIMEOUT_MS || fallback);
192
+ if (!Number.isFinite(timeout) || timeout <= 0) throw new Error('timeout 必须是大于 0 的毫秒数');
193
+ return timeout;
194
+ }
195
+
196
+ function unavailableContext(reason, message) {
197
+ return {
198
+ available: false,
199
+ advisory: true,
200
+ degraded: true,
201
+ localWorkAllowed: true,
202
+ baselineVerified: false,
203
+ inheritanceAllowed: false,
204
+ reason,
205
+ message,
206
+ hint: '继续本地编写、检查和测试;保留已有身份与真实基线。发布前重新在线校验,不把历史参考当作 KB current。',
207
+ };
208
+ }
209
+
187
210
  async function main() {
188
211
  const args = parseArgs(process.argv);
212
+ let result;
189
213
  try {
190
- console.log(JSON.stringify(await retrieveContext(args), null, 2));
214
+ result = await retrieveContext(args);
191
215
  } catch (error) {
192
- console.log(JSON.stringify({
193
- available: false,
194
- advisory: true,
195
- degraded: true,
196
- reason: 'engineering_kb_unavailable',
197
- message: error.message,
198
- hint: 'KB unavailable: continue degraded; never scan local archive/ for inheritance UUIDs',
199
- }, null, 2));
200
- if (args.strict) process.exitCode = 1;
216
+ const reason = /\b(401|403|Unauthorized|Forbidden)\b/.test(error.message)
217
+ ? 'engineering_kb_access_denied' : 'engineering_kb_unavailable';
218
+ result = unavailableContext(reason, error.message);
201
219
  }
220
+ console.log(JSON.stringify(result, null, 2));
221
+ if (args.strict && result.available !== true) process.exitCode = 1;
202
222
  }
203
223
 
204
224
  if (require.main === module) {
@@ -244,7 +244,7 @@ function findActiveStage(projectRoot, criteria) {
244
244
  const matchingStarts = [];
245
245
  for (const changeDir of changeDirs) {
246
246
  const dir = path.join(eventsDir, changeDir);
247
- const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort().reverse();
247
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).reverse();
248
248
  for (const file of files) {
249
249
  const lines = fs.readFileSync(path.join(dir, file), 'utf-8').split('\n').filter(Boolean);
250
250
  for (let i = lines.length - 1; i >= 0; i--) {
@@ -357,7 +357,7 @@ function discoverTaskFiles(projectRoot, changeName) {
357
357
  return walkFiles(changeDir, filePath => {
358
358
  const fileName = path.basename(filePath).toLowerCase();
359
359
  return fileName === 'tasks.md' || fileName === 'task.md';
360
- }).sort();
360
+ }).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
361
361
  }
362
362
 
363
363
  function checklistEntriesOutsideCodeFences(lines) {
@@ -587,7 +587,7 @@ function scanTaskCompletionForArchiveDir(projectRoot, changeName, archiveDir) {
587
587
  const taskFiles = walkFiles(archiveDir, filePath => {
588
588
  const fileName = path.basename(filePath).toLowerCase();
589
589
  return fileName === 'tasks.md' || fileName === 'task.md';
590
- }).sort();
590
+ }).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
591
591
  return scanTaskCompletionFromFiles(normalizedRoot, changeName, taskFiles);
592
592
  }
593
593
 
@@ -796,7 +796,7 @@ function collectSpecFiles(changeRoot) {
796
796
  if (!changeRoot || !fs.existsSync(changeRoot)) return [];
797
797
  const specsDir = path.join(changeRoot, 'specs');
798
798
  if (fs.existsSync(specsDir) && fs.statSync(specsDir).isDirectory()) {
799
- return walkFiles(specsDir, filePath => path.basename(filePath).toLowerCase() === 'spec.md').sort();
799
+ return walkFiles(specsDir, filePath => path.basename(filePath).toLowerCase() === 'spec.md').sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
800
800
  }
801
801
  const rootSpec = path.join(changeRoot, 'spec.md');
802
802
  if (fs.existsSync(rootSpec)) return [rootSpec];
@@ -1503,7 +1503,7 @@ function readEventsRaw(dataDir, changeName) {
1503
1503
  if (!fs.existsSync(dir)) return [];
1504
1504
 
1505
1505
  const events = [];
1506
- const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort();
1506
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
1507
1507
  for (const file of files) {
1508
1508
  const lines = fs.readFileSync(path.join(dir, file), 'utf-8').split('\n').filter(Boolean);
1509
1509
  for (const line of lines) {
@@ -4859,7 +4859,7 @@ function buildReport(projectRoot, events, options = {}) {
4859
4859
  const scopedReport = scopeEventsForChangeReport(events, options.change);
4860
4860
  events = scopedReport.events;
4861
4861
  const archiveEvent = latestByTimestamp(events.filter(e => {
4862
- return e.command === 'archive' && e.type === 'stage_end';
4862
+ return e.command === 'archive' && e.type === 'stage_end' && e.details?.archive_result;
4863
4863
  }));
4864
4864
  const gitCommitTimeline = resolveGitCommitTimeline(
4865
4865
  events,
@@ -5475,7 +5475,8 @@ function cmdEnd(args, options = {}) {
5475
5475
  command: args.command,
5476
5476
  agent_type: args.agent || args['agent-type'],
5477
5477
  };
5478
- const startEvent = eventId
5478
+ // let:下方占位 change 迁移逻辑需要重新赋值 startEvent(const 会在运行时抛 TypeError)
5479
+ let startEvent = eventId
5479
5480
  ? searchEventInDataDir(dataDir, eventId)
5480
5481
  : findActiveStage(projectRoot, activeCriteria);
5481
5482
  if (!eventId && startEvent) {
@@ -5858,6 +5859,19 @@ function cmdEnd(args, options = {}) {
5858
5859
  if (!options.silent) {
5859
5860
  console.log(JSON.stringify(output, null, 2));
5860
5861
  }
5862
+ // kld-usage-flush: stage_end 落库后冲刷 pending 队列(fire-and-forget,不产生新事件)。
5863
+ // 覆盖"start 时网络故障/无身份,end 时已恢复"的同会话补报场景;失败不影响 output 与退出码。
5864
+ // promise 以不可枚举属性挂返回对象供测试 await,JSON.stringify 输出不变。
5865
+ try {
5866
+ const { flushPendingUsage } = require('./lib/usage-reporter.cjs');
5867
+ const usageFlush = flushPendingUsage({ projectRoot }).catch(() => null);
5868
+ Object.defineProperty(output, 'usageFlush', {
5869
+ value: usageFlush,
5870
+ enumerable: false,
5871
+ writable: false,
5872
+ configurable: true,
5873
+ });
5874
+ } catch { /* flush 不可用不阻塞 */ }
5861
5875
  return output;
5862
5876
  }
5863
5877
 
@@ -5947,7 +5961,7 @@ function normalizeRepositoryRelativeFiles(files, {
5947
5961
  }
5948
5962
  normalized.push(raw);
5949
5963
  }
5950
- return [...new Set(normalized)].sort();
5964
+ return [...new Set(normalized)].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
5951
5965
  }
5952
5966
 
5953
5967
  function normalizeStrictCheckResult(details, result) {
@@ -6399,13 +6413,13 @@ function normalizeStrictTestResult(args, details) {
6399
6413
 
6400
6414
  const taskId = String(args['task-id'] || args.task_id || input.task_id || '').trim();
6401
6415
  const verifiedTaskIds = Array.isArray(input.verified_task_ids)
6402
- ? [...new Set(input.verified_task_ids.map(value => String(value || '').trim()).filter(Boolean))].sort()
6416
+ ? [...new Set(input.verified_task_ids.map(value => String(value || '').trim()).filter(Boolean))].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
6403
6417
  : [];
6404
6418
  if (!taskId && verifiedTaskIds.length === 0) {
6405
6419
  fail('E_STRICT_FIELD_REQUIRED: strict test_result 必须提供 task_id 或 verified_task_ids');
6406
6420
  }
6407
6421
  if (taskId && !verifiedTaskIds.includes(taskId)) verifiedTaskIds.push(taskId);
6408
- verifiedTaskIds.sort();
6422
+ verifiedTaskIds.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
6409
6423
 
6410
6424
  const normalized = {
6411
6425
  run_id: requireStrictString(args['run-id'] || args.run_id || input.run_id, 'EVENT_RUN_ID_REQUIRED', 'run_id'),
@@ -7846,7 +7860,7 @@ function searchEventInDataDir(dataDir, eventId) {
7846
7860
 
7847
7861
  for (const changeDir of changeDirs) {
7848
7862
  const dir = path.join(eventsDir, changeDir);
7849
- const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort().reverse();
7863
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).reverse();
7850
7864
  for (const file of files) {
7851
7865
  const lines = fs.readFileSync(path.join(dir, file), 'utf-8').split('\n').filter(Boolean);
7852
7866
  for (const line of lines) {
@@ -7872,7 +7886,7 @@ function readAllEventEndsById(dataDir, eventId, changeName) {
7872
7886
 
7873
7887
  const scanDir = (dir) => {
7874
7888
  if (!fs.existsSync(dir)) return;
7875
- const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort().reverse();
7889
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).reverse();
7876
7890
  for (const file of files) {
7877
7891
  const lines = fs.readFileSync(path.join(dir, file), 'utf-8').split('\n').filter(Boolean);
7878
7892
  for (const line of lines) {
@@ -8161,18 +8175,37 @@ function cmdSemanticScan(args) {
8161
8175
 
8162
8176
  function cmdSemanticReconcile(args, options = {}) {
8163
8177
  const context = semanticCommandContext(args);
8178
+ const started = process.hrtime.bigint();
8164
8179
  const result = require('./ontology/runtime.cjs').reconcileChange(
8165
8180
  context.projectRoot,
8166
8181
  context.changeName,
8167
8182
  { profile: context.profile, markPending: Boolean(options.markPending) },
8168
8183
  );
8169
- console.log(JSON.stringify(semanticResultPayload(result), null, 2));
8184
+ const payload = semanticResultPayload(result);
8185
+ if (options.includeTasks) {
8186
+ const semanticFinished = process.hrtime.bigint();
8187
+ // A review packet avoids another CLI launch and a parent-agent document scan.
8188
+ // It is fresh input metadata, never a cached approval or a substitute for review.
8189
+ payload.task_completion = scanTaskCompletion(context.projectRoot, context.changeName);
8190
+ payload.review_inputs = {
8191
+ change_dir: path.join(context.projectRoot, 'openspec', 'changes', context.changeName),
8192
+ files: result.state.files,
8193
+ identity_history_hash: result.state.identity_history_hash,
8194
+ };
8195
+ const finished = process.hrtime.bigint();
8196
+ payload.timings_ms = {
8197
+ semantic: Number(semanticFinished - started) / 1e6,
8198
+ tasks: Number(finished - semanticFinished) / 1e6,
8199
+ total: Number(finished - started) / 1e6,
8200
+ };
8201
+ }
8202
+ console.log(JSON.stringify(payload, null, 2));
8170
8203
  if (!result.valid) process.exitCode = 1;
8171
8204
  return result;
8172
8205
  }
8173
8206
 
8174
8207
  function cmdSemanticCheck(args) {
8175
- return cmdSemanticReconcile(args, { markPending: true });
8208
+ return cmdSemanticReconcile(args, { markPending: true, includeTasks: Boolean(args['include-tasks']) });
8176
8209
  }
8177
8210
 
8178
8211
  function cmdSemanticStatus(args) {
@@ -8463,7 +8496,7 @@ SDD Telemetry CLI - 流程度量采集工具
8463
8496
  node skywalk-sdd/log.cjs semantic-identity --delta-state=modified --entity-id=<uuid> --predecessor-version=<uuid>
8464
8497
  node skywalk-sdd/log.cjs semantic-identity --delta-state=unchanged --entity-id=<uuid> --version-id=<uuid>
8465
8498
  node skywalk-sdd/log.cjs semantic-reconcile --project=<path> --change=<name> [--profile=auto|simple|full|strict]
8466
- node skywalk-sdd/log.cjs semantic-check --project=<path> --change=<name> [--profile=...]
8499
+ node skywalk-sdd/log.cjs semantic-check --project=<path> --change=<name> [--profile=...] [--include-tasks]
8467
8500
  node skywalk-sdd/log.cjs semantic-status --project=<path> --change=<name>
8468
8501
  node skywalk-sdd/log.cjs semantic-observe --project=<path> --change=<name> [--interval=1500]
8469
8502
 
@@ -8481,7 +8514,7 @@ SDD Telemetry CLI - 流程度量采集工具
8481
8514
  semantic-identity 生成或复用实体/版本 ID(8 位十六进制);无需网络和中央 ID 服务
8482
8515
  semantic-scan 只读解析并校验当前 Change
8483
8516
  semantic-reconcile 全量对账文件与本地工作态本体实例
8484
- semantic-check 全量对账并在通过时标记 pending
8517
+ semantic-check 全量对账并在通过时标记 pending;--include-tasks 同时返回任务状态、评审输入清单和程序耗时
8485
8518
  semantic-status 读取当前工作态本体实例
8486
8519
  semantic-observe 观察文件变化并同步;不提供 Hook 式控制,Check/Archive 仍会全量对账
8487
8520