scene-capability-engine 3.6.51 → 3.6.53

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.
@@ -18,6 +18,15 @@ const TEMPORARY_MITIGATION_TAG = 'temporary-mitigation';
18
18
  const DEFAULT_ERRORBOOK_REGISTRY_CONFIG = '.sce/config/errorbook-registry.json';
19
19
  const DEFAULT_ERRORBOOK_REGISTRY_CACHE = '.sce/errorbook/registry-cache.json';
20
20
  const DEFAULT_ERRORBOOK_REGISTRY_EXPORT = '.sce/errorbook/exports/errorbook-registry-export.json';
21
+ const DEFAULT_PROJECT_SHARED_ERRORBOOK_REGISTRY = '.sce/knowledge/errorbook/project-shared-registry.json';
22
+ const DEFAULT_PROJECT_SHARED_ERRORBOOK_STATUSES = Object.freeze(['verified', 'promoted']);
23
+ const DEFAULT_PROJECT_SHARED_ERRORBOOK_MIN_QUALITY = 75;
24
+ const DEFAULT_PROJECT_SHARED_ERRORBOOK_PROJECTION = Object.freeze({
25
+ enabled: true,
26
+ file: DEFAULT_PROJECT_SHARED_ERRORBOOK_REGISTRY,
27
+ statuses: [...DEFAULT_PROJECT_SHARED_ERRORBOOK_STATUSES],
28
+ min_quality: DEFAULT_PROJECT_SHARED_ERRORBOOK_MIN_QUALITY
29
+ });
21
30
  const STATUS_RANK = Object.freeze({
22
31
  deprecated: 0,
23
32
  candidate: 1,
@@ -107,6 +116,10 @@ function nowIso() {
107
116
  return new Date().toISOString();
108
117
  }
109
118
 
119
+ function cloneJson(value) {
120
+ return JSON.parse(JSON.stringify(value));
121
+ }
122
+
110
123
  function normalizeText(value) {
111
124
  if (typeof value !== 'string') {
112
125
  return '';
@@ -1036,6 +1049,26 @@ function normalizeStatusList(values = [], fallback = ['promoted']) {
1036
1049
  return unique;
1037
1050
  }
1038
1051
 
1052
+ function normalizeProjectSharedProjection(input = {}) {
1053
+ const candidate = input && typeof input === 'object' && !Array.isArray(input)
1054
+ ? input
1055
+ : {};
1056
+ const minQualityCandidate = candidate.min_quality ?? candidate.minQuality;
1057
+ const minQuality = Number.isFinite(Number(minQualityCandidate))
1058
+ ? Math.max(0, Math.min(100, Number(minQualityCandidate)))
1059
+ : DEFAULT_PROJECT_SHARED_ERRORBOOK_MIN_QUALITY;
1060
+
1061
+ return {
1062
+ enabled: normalizeBoolean(candidate.enabled, true),
1063
+ file: normalizeText(candidate.file || candidate.out || candidate.path || DEFAULT_PROJECT_SHARED_ERRORBOOK_REGISTRY),
1064
+ statuses: normalizeStatusList(
1065
+ candidate.statuses || candidate.status || DEFAULT_PROJECT_SHARED_ERRORBOOK_STATUSES,
1066
+ DEFAULT_PROJECT_SHARED_ERRORBOOK_STATUSES
1067
+ ),
1068
+ min_quality: minQuality
1069
+ };
1070
+ }
1071
+
1039
1072
  function normalizeRegistrySource(input = {}) {
1040
1073
  const candidate = input || {};
1041
1074
  const name = normalizeText(candidate.name) || 'default';
@@ -1067,7 +1100,8 @@ async function readErrorbookRegistryConfig(paths, fileSystem = fs) {
1067
1100
  enabled: false,
1068
1101
  search_mode: 'cache',
1069
1102
  cache_file: DEFAULT_ERRORBOOK_REGISTRY_CACHE,
1070
- sources: []
1103
+ sources: [],
1104
+ project_shared_projection: cloneJson(DEFAULT_PROJECT_SHARED_ERRORBOOK_PROJECTION)
1071
1105
  };
1072
1106
  if (!await fileSystem.pathExists(paths.configFile)) {
1073
1107
  return fallback;
@@ -1083,7 +1117,55 @@ async function readErrorbookRegistryConfig(paths, fileSystem = fs) {
1083
1117
  enabled: normalizeBoolean(payload.enabled, true),
1084
1118
  search_mode: normalizeRegistryMode(payload.search_mode || payload.searchMode, 'cache'),
1085
1119
  cache_file: normalizeText(payload.cache_file || payload.cacheFile || DEFAULT_ERRORBOOK_REGISTRY_CACHE),
1086
- sources
1120
+ sources,
1121
+ project_shared_projection: normalizeProjectSharedProjection(
1122
+ payload.project_shared_projection || payload.projectSharedProjection
1123
+ )
1124
+ };
1125
+ }
1126
+
1127
+ async function refreshProjectSharedErrorbookProjection(projectPath, fileSystem = fs, options = {}) {
1128
+ const registryPaths = resolveErrorbookRegistryPaths(projectPath, {
1129
+ configPath: options.config
1130
+ });
1131
+ const registryConfig = await readErrorbookRegistryConfig(registryPaths, fileSystem);
1132
+ const projection = normalizeProjectSharedProjection(
1133
+ options.projectSharedProjection || registryConfig.project_shared_projection
1134
+ );
1135
+ const projectionFile = resolveProjectPath(
1136
+ projectPath,
1137
+ projection.file,
1138
+ DEFAULT_PROJECT_SHARED_ERRORBOOK_REGISTRY
1139
+ );
1140
+
1141
+ if (projection.enabled !== true) {
1142
+ return {
1143
+ enabled: false,
1144
+ refreshed: false,
1145
+ file: projectionFile,
1146
+ statuses: projection.statuses,
1147
+ min_quality: projection.min_quality,
1148
+ total_entries: 0
1149
+ };
1150
+ }
1151
+
1152
+ const exportResult = await runErrorbookExportCommand({
1153
+ out: projection.file,
1154
+ status: projection.statuses.join(','),
1155
+ minQuality: projection.min_quality,
1156
+ silent: true
1157
+ }, {
1158
+ projectPath,
1159
+ fileSystem
1160
+ });
1161
+
1162
+ return {
1163
+ enabled: true,
1164
+ refreshed: true,
1165
+ file: exportResult.out_file,
1166
+ statuses: exportResult.statuses,
1167
+ min_quality: exportResult.min_quality,
1168
+ total_entries: exportResult.total_entries
1087
1169
  };
1088
1170
  }
1089
1171
 
@@ -1322,6 +1404,7 @@ async function searchRegistryRemote(options = {}, dependencies = {}) {
1322
1404
 
1323
1405
  const warnings = [];
1324
1406
  let shardSources = [];
1407
+ const localFileSource = !isHttpSource(source.source);
1325
1408
  if (source.index_url) {
1326
1409
  try {
1327
1410
  const indexPayload = await loadRegistryPayload(projectPath, source.index_url, fileSystem);
@@ -1332,7 +1415,10 @@ async function searchRegistryRemote(options = {}, dependencies = {}) {
1332
1415
  }
1333
1416
 
1334
1417
  if (shardSources.length === 0) {
1335
- if (allowRemoteFullscan) {
1418
+ if (localFileSource) {
1419
+ shardSources = [source.source];
1420
+ warnings.push('local registry file source scanned directly without index');
1421
+ } else if (allowRemoteFullscan) {
1336
1422
  shardSources = [source.source];
1337
1423
  warnings.push('remote index unavailable; fallback to full-source scan');
1338
1424
  } else {
@@ -1769,13 +1855,15 @@ async function runErrorbookRecordCommand(options = {}, dependencies = {}) {
1769
1855
  const incidentLoop = await syncIncidentLoopForRecord(paths, normalized, entry, {
1770
1856
  nowIso: entry.updated_at
1771
1857
  }, fileSystem);
1858
+ const projectSharedProjection = await refreshProjectSharedErrorbookProjection(projectPath, fileSystem, options);
1772
1859
 
1773
1860
  const result = {
1774
1861
  mode: 'errorbook-record',
1775
1862
  created,
1776
1863
  deduplicated,
1777
1864
  entry,
1778
- incident_loop: incidentLoop
1865
+ incident_loop: incidentLoop,
1866
+ project_shared_projection: projectSharedProjection
1779
1867
  };
1780
1868
 
1781
1869
  if (options.json) {
@@ -2575,11 +2663,13 @@ async function runErrorbookPromoteCommand(options = {}, dependencies = {}) {
2575
2663
  }
2576
2664
  index.entries.sort((left, right) => `${right.updated_at}`.localeCompare(`${left.updated_at}`));
2577
2665
  await writeErrorbookIndex(paths, index, fileSystem);
2666
+ const projectSharedProjection = await refreshProjectSharedErrorbookProjection(projectPath, fileSystem, options);
2578
2667
 
2579
2668
  const result = {
2580
2669
  mode: 'errorbook-promote',
2581
2670
  promoted: true,
2582
- entry
2671
+ entry,
2672
+ project_shared_projection: projectSharedProjection
2583
2673
  };
2584
2674
 
2585
2675
  if (options.json) {
@@ -2672,11 +2762,13 @@ async function runErrorbookDeprecateCommand(options = {}, dependencies = {}) {
2672
2762
  }
2673
2763
  index.entries.sort((left, right) => `${right.updated_at}`.localeCompare(`${left.updated_at}`));
2674
2764
  await writeErrorbookIndex(paths, index, fileSystem);
2765
+ const projectSharedProjection = await refreshProjectSharedErrorbookProjection(projectPath, fileSystem, options);
2675
2766
 
2676
2767
  const result = {
2677
2768
  mode: 'errorbook-deprecate',
2678
2769
  deprecated: true,
2679
- entry
2770
+ entry,
2771
+ project_shared_projection: projectSharedProjection
2680
2772
  };
2681
2773
 
2682
2774
  if (options.json) {
@@ -2744,11 +2836,13 @@ async function runErrorbookRequalifyCommand(options = {}, dependencies = {}) {
2744
2836
  }
2745
2837
  index.entries.sort((left, right) => `${right.updated_at}`.localeCompare(`${left.updated_at}`));
2746
2838
  await writeErrorbookIndex(paths, index, fileSystem);
2839
+ const projectSharedProjection = await refreshProjectSharedErrorbookProjection(projectPath, fileSystem, options);
2747
2840
 
2748
2841
  const result = {
2749
2842
  mode: 'errorbook-requalify',
2750
2843
  requalified: true,
2751
- entry
2844
+ entry,
2845
+ project_shared_projection: projectSharedProjection
2752
2846
  };
2753
2847
 
2754
2848
  if (options.json) {
@@ -3009,6 +3103,8 @@ module.exports = {
3009
3103
  DEFAULT_ERRORBOOK_REGISTRY_CONFIG,
3010
3104
  DEFAULT_ERRORBOOK_REGISTRY_CACHE,
3011
3105
  DEFAULT_ERRORBOOK_REGISTRY_EXPORT,
3106
+ DEFAULT_PROJECT_SHARED_ERRORBOOK_REGISTRY,
3107
+ DEFAULT_PROJECT_SHARED_ERRORBOOK_PROJECTION,
3012
3108
  HIGH_RISK_SIGNAL_TAGS,
3013
3109
  DEBUG_EVIDENCE_TAGS,
3014
3110
  DEFAULT_PROMOTE_MIN_QUALITY,
@@ -3035,5 +3131,6 @@ module.exports = {
3035
3131
  runErrorbookReleaseGateCommand,
3036
3132
  runErrorbookDeprecateCommand,
3037
3133
  runErrorbookRequalifyCommand,
3134
+ refreshProjectSharedErrorbookProjection,
3038
3135
  registerErrorbookCommands
3039
3136
  };
@@ -37,18 +37,44 @@ function isFreshProjectionCache(cacheRecord, ttlSeconds) {
37
37
  return (Date.now() - generatedAt) <= maxAgeMs;
38
38
  }
39
39
 
40
- async function buildApplicationHome(graph = {}, dependencies = {}) {
40
+ function getRuntimeProjectionState(graph = {}) {
41
41
  const bundle = graph.bundle || {};
42
42
  const runtimeRelease = graph.runtime_release || {};
43
- const sceneBindings = Array.isArray(graph.scene_bindings) ? graph.scene_bindings : [];
44
43
  const metadata = bundle.metadata && typeof bundle.metadata === 'object' ? bundle.metadata : {};
45
44
  const installation = metadata.runtime_installation && typeof metadata.runtime_installation === 'object'
46
45
  ? metadata.runtime_installation
47
46
  : {};
47
+ const runtimeActivation = metadata.runtime_activation && typeof metadata.runtime_activation === 'object'
48
+ ? metadata.runtime_activation
49
+ : {};
48
50
  const serviceCatalog = metadata.service_catalog && typeof metadata.service_catalog === 'object'
49
51
  ? metadata.service_catalog
50
52
  : {};
51
53
  const releases = Array.isArray(serviceCatalog.releases) ? serviceCatalog.releases : [];
54
+ const installStatus = normalizeString(installation.status) || 'not-installed';
55
+ const installedReleaseId = installStatus === 'installed'
56
+ ? (normalizeString(installation.release_id) || null)
57
+ : null;
58
+ const activeReleaseId = normalizeString(
59
+ bundle.runtime_release_id
60
+ || runtimeRelease.release_id
61
+ || runtimeActivation.active_release_id
62
+ ) || null;
63
+
64
+ return {
65
+ installation,
66
+ releases,
67
+ installStatus,
68
+ installedReleaseId,
69
+ activeReleaseId
70
+ };
71
+ }
72
+
73
+ async function buildApplicationHome(graph = {}, dependencies = {}) {
74
+ const bundle = graph.bundle || {};
75
+ const runtimeRelease = graph.runtime_release || {};
76
+ const sceneBindings = Array.isArray(graph.scene_bindings) ? graph.scene_bindings : [];
77
+ const state = getRuntimeProjectionState(graph);
52
78
  return {
53
79
  mode: 'application-home',
54
80
  query: {
@@ -60,8 +86,10 @@ async function buildApplicationHome(graph = {}, dependencies = {}) {
60
86
  environment: bundle.environment || null,
61
87
  release_status: runtimeRelease.release_status || null,
62
88
  runtime_status: runtimeRelease.runtime_status || null,
63
- install_status: installation.status || 'not-installed',
64
- release_count: releases.length
89
+ install_status: state.installStatus,
90
+ installed_release_id: state.installedReleaseId,
91
+ active_release_id: state.activeReleaseId,
92
+ release_count: state.releases.length
65
93
  },
66
94
  relations: {
67
95
  runtime_release_id: bundle.runtime_release_id || null,
@@ -69,20 +97,22 @@ async function buildApplicationHome(graph = {}, dependencies = {}) {
69
97
  engineering_project_id: bundle.engineering_project_id || null,
70
98
  default_scene_id: bundle.default_scene_id || null
71
99
  },
72
- items: releases,
100
+ items: state.releases,
73
101
  view_model: {
74
102
  projection: 'application',
75
103
  app_id: bundle.app_id || null,
76
104
  app_key: bundle.app_key || null,
77
105
  app_name: bundle.app_name || null,
78
106
  entrypoint: runtimeRelease.entrypoint || null,
79
- current_release: installation.release_id || runtimeRelease.release_id || null,
80
- current_environment: bundle.environment || installation.current_environment || null,
81
- install_root: installation.install_root || null,
107
+ current_release: state.activeReleaseId,
108
+ installed_release_id: state.installedReleaseId,
109
+ active_release_id: state.activeReleaseId,
110
+ current_environment: bundle.environment || state.installation.current_environment || null,
111
+ install_root: state.installation.install_root || null,
82
112
  scene_binding_count: sceneBindings.length,
83
- release_count: releases.length
113
+ release_count: state.releases.length
84
114
  },
85
- mb_status: runtimeRelease.runtime_status || installation.status || bundle.status || 'unknown'
115
+ mb_status: runtimeRelease.runtime_status || state.installStatus || bundle.status || 'unknown'
86
116
  };
87
117
  }
88
118
 
@@ -50,6 +50,10 @@ const ACTIVE_TEXT_SCAN_EXCLUDES = Object.freeze([
50
50
 
51
51
  const LEGACY_REFERENCE_REGEX = /\.kiro(?:[\\/]|-workspaces\b)/;
52
52
  const MULTI_AGENT_CONFIG_REFERENCE = '.sce/config/multi-agent.json';
53
+ const ERRORBOOK_REGISTRY_CONFIG_PATH = '.sce/config/errorbook-registry.json';
54
+ const PROJECT_SHARED_ERRORBOOK_REGISTRY_PATH = '.sce/knowledge/errorbook/project-shared-registry.json';
55
+ const PROJECT_SHARED_ERRORBOOK_SOURCE_NAME = 'project-shared';
56
+ const ADOPTION_CONFIG_PATH = '.sce/adoption-config.json';
53
57
 
54
58
  const REQUIRED_GITIGNORE_RULES = Object.freeze([
55
59
  { rule: '.sce/steering/CURRENT_CONTEXT.md', sample: '.sce/steering/CURRENT_CONTEXT.md' },
@@ -377,6 +381,250 @@ function validateMultiAgentConfig(payload) {
377
381
  return violations;
378
382
  }
379
383
 
384
+ function validateErrorbookRegistryConfig(payload) {
385
+ const violations = [];
386
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
387
+ violations.push('errorbook registry config must be a JSON object');
388
+ return violations;
389
+ }
390
+
391
+ if (typeof payload.enabled !== 'boolean') {
392
+ violations.push('errorbook registry config must declare boolean field "enabled"');
393
+ } else if (payload.enabled !== true) {
394
+ violations.push('errorbook registry config must keep "enabled" set to true under co-work baseline');
395
+ }
396
+
397
+ if (typeof payload.cache_file !== 'string' || !payload.cache_file.trim()) {
398
+ violations.push('errorbook registry config must declare non-empty field "cache_file"');
399
+ }
400
+
401
+ const projection = payload.project_shared_projection;
402
+ if (!projection || typeof projection !== 'object' || Array.isArray(projection)) {
403
+ violations.push('errorbook registry config must declare object field "project_shared_projection"');
404
+ } else {
405
+ if (projection.enabled !== true) {
406
+ violations.push('errorbook registry config must keep project_shared_projection.enabled=true under co-work baseline');
407
+ }
408
+ if (typeof projection.file !== 'string' || !projection.file.trim()) {
409
+ violations.push('errorbook registry config must declare non-empty project_shared_projection.file');
410
+ }
411
+ if (!Array.isArray(projection.statuses) || projection.statuses.length === 0) {
412
+ violations.push('errorbook registry config must declare non-empty project_shared_projection.statuses');
413
+ }
414
+ if (!Number.isFinite(Number(projection.min_quality))) {
415
+ violations.push('errorbook registry config must declare numeric project_shared_projection.min_quality');
416
+ }
417
+ }
418
+
419
+ const sources = Array.isArray(payload.sources) ? payload.sources : [];
420
+ if (sources.length === 0) {
421
+ violations.push('errorbook registry config must declare at least one registry source');
422
+ return violations;
423
+ }
424
+
425
+ const enabledSources = sources.filter((item) => {
426
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
427
+ return false;
428
+ }
429
+ const enabled = item.enabled !== false;
430
+ const source = typeof item.url === 'string' && item.url.trim()
431
+ ? item.url.trim()
432
+ : typeof item.file === 'string' && item.file.trim()
433
+ ? item.file.trim()
434
+ : typeof item.path === 'string' && item.path.trim()
435
+ ? item.path.trim()
436
+ : '';
437
+ return enabled && Boolean(source);
438
+ });
439
+
440
+ if (enabledSources.length === 0) {
441
+ violations.push('errorbook registry config must keep at least one enabled source with a non-empty url/file');
442
+ }
443
+
444
+ if (projection && typeof projection === 'object' && !Array.isArray(projection)) {
445
+ const hasProjectSharedSource = enabledSources.some((item) => {
446
+ const name = typeof item.name === 'string' ? item.name.trim() : '';
447
+ const file = typeof item.file === 'string' ? item.file.trim() : '';
448
+ const sourcePath = typeof item.path === 'string' ? item.path.trim() : '';
449
+ return name === PROJECT_SHARED_ERRORBOOK_SOURCE_NAME
450
+ || file === projection.file
451
+ || sourcePath === projection.file;
452
+ });
453
+ if (!hasProjectSharedSource) {
454
+ violations.push('errorbook registry config must keep an enabled project-shared source aligned with project_shared_projection.file');
455
+ }
456
+ }
457
+
458
+ return violations;
459
+ }
460
+
461
+ async function inspectErrorbookRegistry(projectRoot, gitSnapshot, options = {}, dependencies = {}) {
462
+ const fileSystem = dependencies.fileSystem || fs;
463
+ const configPath = path.join(projectRoot, '.sce', 'config', 'errorbook-registry.json');
464
+ const exists = await fileSystem.pathExists(configPath);
465
+ const report = {
466
+ file: ERRORBOOK_REGISTRY_CONFIG_PATH,
467
+ exists,
468
+ valid: false,
469
+ enabled: null,
470
+ enabled_source_count: 0,
471
+ project_shared_projection_file: PROJECT_SHARED_ERRORBOOK_REGISTRY_PATH,
472
+ project_shared_projection_exists: false,
473
+ project_shared_projection_tracked: null,
474
+ warnings: [],
475
+ violations: [],
476
+ passed: true,
477
+ reason: 'passed'
478
+ };
479
+
480
+ if (!exists) {
481
+ report.passed = false;
482
+ report.reason = 'missing-config';
483
+ report.violations.push('shared errorbook registry config is missing');
484
+ return report;
485
+ }
486
+
487
+ let payload;
488
+ try {
489
+ payload = await fileSystem.readJson(configPath);
490
+ } catch (error) {
491
+ report.passed = false;
492
+ report.reason = 'invalid-json';
493
+ report.violations.push(`invalid errorbook registry config: ${error.message}`);
494
+ return report;
495
+ }
496
+
497
+ const validationErrors = validateErrorbookRegistryConfig(payload);
498
+ report.valid = validationErrors.length === 0;
499
+ report.enabled = typeof payload.enabled === 'boolean' ? payload.enabled : null;
500
+ report.project_shared_projection_file = payload
501
+ && payload.project_shared_projection
502
+ && typeof payload.project_shared_projection.file === 'string'
503
+ && payload.project_shared_projection.file.trim()
504
+ ? normalizeRelativePath(projectRoot, payload.project_shared_projection.file.trim()) || payload.project_shared_projection.file.trim()
505
+ : PROJECT_SHARED_ERRORBOOK_REGISTRY_PATH;
506
+ report.enabled_source_count = Array.isArray(payload.sources)
507
+ ? payload.sources.filter((item) => {
508
+ if (!item || typeof item !== 'object' || Array.isArray(item) || item.enabled === false) {
509
+ return false;
510
+ }
511
+ return (
512
+ (typeof item.url === 'string' && item.url.trim())
513
+ || (typeof item.file === 'string' && item.file.trim())
514
+ || (typeof item.path === 'string' && item.path.trim())
515
+ );
516
+ }).length
517
+ : 0;
518
+
519
+ if (validationErrors.length > 0) {
520
+ report.passed = false;
521
+ report.reason = 'invalid-config';
522
+ report.violations.push(...validationErrors);
523
+ return report;
524
+ }
525
+
526
+ const projectionFile = report.project_shared_projection_file;
527
+ const projectionAbsolutePath = path.isAbsolute(projectionFile)
528
+ ? projectionFile
529
+ : path.join(projectRoot, projectionFile);
530
+ report.project_shared_projection_exists = await fileSystem.pathExists(projectionAbsolutePath);
531
+ if (!report.project_shared_projection_exists) {
532
+ report.passed = false;
533
+ report.reason = 'missing-project-shared-projection';
534
+ report.violations.push(`shared project errorbook projection file is missing: ${projectionFile}`);
535
+ return report;
536
+ }
537
+
538
+ try {
539
+ const projectionPayload = await fileSystem.readJson(projectionAbsolutePath);
540
+ if (!projectionPayload || typeof projectionPayload !== 'object' || Array.isArray(projectionPayload)) {
541
+ report.violations.push(`shared project errorbook projection must be a JSON object: ${projectionFile}`);
542
+ } else if (!Array.isArray(projectionPayload.entries)) {
543
+ report.violations.push(`shared project errorbook projection must declare entries[]: ${projectionFile}`);
544
+ }
545
+ } catch (error) {
546
+ report.violations.push(`invalid shared project errorbook projection: ${error.message}`);
547
+ }
548
+
549
+ if (gitSnapshot && gitSnapshot.available === true) {
550
+ report.project_shared_projection_tracked = gitSnapshot.tracked_files.has(projectionFile);
551
+ if (report.project_shared_projection_tracked !== true) {
552
+ report.violations.push(`shared project errorbook projection must be tracked by git: ${projectionFile}`);
553
+ }
554
+ }
555
+
556
+ if (report.violations.length > 0) {
557
+ report.passed = false;
558
+ report.reason = 'invalid-config';
559
+ }
560
+
561
+ return report;
562
+ }
563
+
564
+ async function inspectErrorbookConvergence(projectRoot, options = {}, dependencies = {}) {
565
+ const fileSystem = dependencies.fileSystem || fs;
566
+ const configPath = path.join(projectRoot, '.sce', 'adoption-config.json');
567
+ const exists = await fileSystem.pathExists(configPath);
568
+ const report = {
569
+ file: ADOPTION_CONFIG_PATH,
570
+ exists,
571
+ managed_project: exists,
572
+ warnings: [],
573
+ violations: [],
574
+ passed: true,
575
+ reason: exists ? 'passed' : 'not-managed'
576
+ };
577
+
578
+ if (!exists) {
579
+ return report;
580
+ }
581
+
582
+ let payload;
583
+ try {
584
+ payload = await fileSystem.readJson(configPath);
585
+ } catch (error) {
586
+ report.passed = false;
587
+ report.reason = 'invalid-json';
588
+ report.violations.push(`invalid adoption config: ${error.message}`);
589
+ return report;
590
+ }
591
+
592
+ const convergence = payload
593
+ && payload.defaults
594
+ && payload.defaults.errorbook_convergence
595
+ && typeof payload.defaults.errorbook_convergence === 'object'
596
+ && !Array.isArray(payload.defaults.errorbook_convergence)
597
+ ? payload.defaults.errorbook_convergence
598
+ : null;
599
+
600
+ if (!convergence) {
601
+ report.passed = false;
602
+ report.reason = 'missing-convergence';
603
+ report.violations.push('managed adoption baseline is missing defaults.errorbook_convergence');
604
+ return report;
605
+ }
606
+
607
+ if (convergence.enabled !== true) {
608
+ report.violations.push('managed adoption baseline must keep errorbook_convergence.enabled=true');
609
+ }
610
+ if (convergence.canonical_mechanism !== 'errorbook') {
611
+ report.violations.push('managed adoption baseline must keep errorbook_convergence.canonical_mechanism=errorbook');
612
+ }
613
+ if (convergence.disallow_parallel_mechanisms !== true) {
614
+ report.violations.push('managed adoption baseline must keep errorbook_convergence.disallow_parallel_mechanisms=true');
615
+ }
616
+ if (convergence.strategy !== 'absorb_into_sce_errorbook') {
617
+ report.violations.push('managed adoption baseline must keep errorbook_convergence.strategy=absorb_into_sce_errorbook');
618
+ }
619
+
620
+ if (report.violations.length > 0) {
621
+ report.passed = false;
622
+ report.reason = 'convergence-drift';
623
+ }
624
+
625
+ return report;
626
+ }
627
+
380
628
  async function inspectMultiAgentConfig(projectRoot, scanResult, options = {}, dependencies = {}) {
381
629
  const fileSystem = dependencies.fileSystem || fs;
382
630
  const configPath = path.join(projectRoot, '.sce', 'config', 'multi-agent.json');
@@ -497,6 +745,8 @@ async function auditCollabGovernance(projectRoot = process.cwd(), options = {},
497
745
  const runtimeTracking = inspectRuntimeTracking(gitSnapshot);
498
746
  const scanResult = await scanActiveTextReferences(projectRoot, gitSnapshot.tracked_files, options, dependencies);
499
747
  const multiAgent = await inspectMultiAgentConfig(projectRoot, scanResult, options, dependencies);
748
+ const errorbookRegistry = await inspectErrorbookRegistry(projectRoot, gitSnapshot, options, dependencies);
749
+ const errorbookConvergence = await inspectErrorbookConvergence(projectRoot, options, dependencies);
500
750
  const steeringBoundary = inspectSteeringBoundary(projectRoot);
501
751
 
502
752
  const legacyReferences = {
@@ -521,6 +771,8 @@ async function auditCollabGovernance(projectRoot = process.cwd(), options = {},
521
771
  gitignore,
522
772
  runtime_tracking: runtimeTracking,
523
773
  multi_agent: multiAgent,
774
+ errorbook_registry: errorbookRegistry,
775
+ errorbook_convergence: errorbookConvergence,
524
776
  legacy_references: legacyReferences,
525
777
  steering_boundary: steeringBoundary,
526
778
  summary: {
@@ -528,6 +780,8 @@ async function auditCollabGovernance(projectRoot = process.cwd(), options = {},
528
780
  tracked_runtime_files: runtimeTracking.tracked_runtime_files.length,
529
781
  multi_agent_warnings: multiAgent.warnings.length,
530
782
  multi_agent_violations: multiAgent.violations.length,
783
+ errorbook_registry_violations: errorbookRegistry.violations.length,
784
+ errorbook_convergence_violations: errorbookConvergence.violations.length,
531
785
  legacy_reference_count: legacyReferences.matches.length,
532
786
  steering_boundary_violations: steeringBoundary.violations.length
533
787
  },
@@ -541,12 +795,16 @@ async function auditCollabGovernance(projectRoot = process.cwd(), options = {},
541
795
  report.warnings.push(...(gitSnapshot.available === true ? gitSnapshot.warnings : []));
542
796
  report.warnings.push(...runtimeTracking.warnings);
543
797
  report.warnings.push(...multiAgent.warnings);
798
+ report.warnings.push(...errorbookRegistry.warnings);
799
+ report.warnings.push(...errorbookConvergence.warnings);
544
800
  report.warnings.push(...legacyReferences.warnings);
545
801
  report.warnings.push(...steeringBoundary.warnings);
546
802
 
547
803
  report.violations.push(...gitignore.violations);
548
804
  report.violations.push(...runtimeTracking.violations);
549
805
  report.violations.push(...multiAgent.violations);
806
+ report.violations.push(...errorbookRegistry.violations);
807
+ report.violations.push(...errorbookConvergence.violations);
550
808
  report.violations.push(...legacyReferences.violations);
551
809
  report.violations.push(
552
810
  ...steeringBoundary.violations.map((item) => `steering boundary violation: ${item.path || item.name}`)
@@ -568,8 +826,11 @@ module.exports = {
568
826
  RUNTIME_TRACKED_PATTERNS,
569
827
  auditCollabGovernance,
570
828
  inspectGitignore,
829
+ inspectErrorbookConvergence,
830
+ inspectErrorbookRegistry,
571
831
  inspectMultiAgentConfig,
572
832
  inspectRuntimeTracking,
573
833
  scanActiveTextReferences,
834
+ validateErrorbookRegistryConfig,
574
835
  validateMultiAgentConfig
575
836
  };