scene-capability-engine 3.6.65 → 3.6.67

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 (121) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +17 -6
  3. package/README.zh.md +18 -6
  4. package/bin/scene-capability-engine.js +4 -0
  5. package/docs/README.md +2 -2
  6. package/docs/command-reference.md +382 -6
  7. package/docs/document-governance.md +3 -2
  8. package/docs/integration-modes.md +62 -478
  9. package/docs/integration-philosophy.md +56 -263
  10. package/docs/magicball-project-portfolio-contract.md +114 -2
  11. package/docs/project-management/README.md +14 -0
  12. package/docs/project-management/assurance/backup.md +3 -0
  13. package/docs/project-management/assurance/config.md +3 -0
  14. package/docs/project-management/assurance/evidence/README.md +3 -0
  15. package/docs/project-management/assurance/incidents/README.md +3 -0
  16. package/docs/project-management/assurance/logs.md +3 -0
  17. package/docs/project-management/assurance/overview.md +3 -0
  18. package/docs/project-management/assurance/recovery/README.md +3 -0
  19. package/docs/project-management/assurance/resource.md +3 -0
  20. package/docs/project-management/assurance/runbooks/README.md +3 -0
  21. package/docs/project-management/delivery/acceptance/README.md +3 -0
  22. package/docs/project-management/delivery/acceptance/evidence/README.md +3 -0
  23. package/docs/project-management/delivery/acceptance/exceptions/README.md +3 -0
  24. package/docs/project-management/delivery/acceptance/reports/README.md +3 -0
  25. package/docs/project-management/delivery/documents/changes.md +3 -0
  26. package/docs/project-management/delivery/documents/issues.md +3 -0
  27. package/docs/project-management/delivery/documents/overview.md +3 -0
  28. package/docs/project-management/delivery/documents/planning.md +3 -0
  29. package/docs/project-management/delivery/documents/requirements.md +3 -0
  30. package/docs/project-management/delivery/documents/tracking.md +3 -0
  31. package/docs/project-management/delivery/handoffs/README.md +3 -0
  32. package/docs/project-management/delivery/handoffs/evidence/README.md +3 -0
  33. package/docs/project-management/delivery/handoffs/records/README.md +3 -0
  34. package/docs/project-management/delivery/overview.md +10 -0
  35. package/docs/project-management/delivery/releases/README.md +3 -0
  36. package/docs/project-management/delivery/releases/baselines/README.md +3 -0
  37. package/docs/project-management/delivery/releases/evidence/README.md +3 -0
  38. package/docs/project-management/delivery/tables/changes.md +3 -0
  39. package/docs/project-management/delivery/tables/issues.md +3 -0
  40. package/docs/project-management/delivery/tables/planning.md +3 -0
  41. package/docs/project-management/delivery/tables/requirements.md +3 -0
  42. package/docs/project-management/delivery/tables/tracking.md +3 -0
  43. package/docs/project-management/environment/agent-discovery.md +3 -0
  44. package/docs/project-management/environment/development.md +3 -0
  45. package/docs/project-management/environment/overview.md +10 -0
  46. package/docs/project-management/environment/testing.md +3 -0
  47. package/docs/project-management/environment/version-alignment.md +3 -0
  48. package/docs/quick-start-with-ai-tools.md +68 -308
  49. package/docs/releases/README.md +2 -0
  50. package/docs/releases/v3.6.66.md +23 -0
  51. package/docs/releases/v3.6.67.md +23 -0
  52. package/docs/steering-governance.md +64 -2
  53. package/docs/zh/README.md +2 -2
  54. package/docs/zh/releases/README.md +2 -0
  55. package/docs/zh/releases/v3.6.66.md +23 -0
  56. package/docs/zh/releases/v3.6.67.md +23 -0
  57. package/lib/commands/adopt.js +24 -0
  58. package/lib/commands/native.js +158 -0
  59. package/lib/commands/project.js +95 -0
  60. package/lib/commands/semantic.js +1459 -0
  61. package/lib/commands/session.js +74 -3
  62. package/lib/commands/spec-bootstrap.js +10 -1
  63. package/lib/commands/spec-gate.js +10 -1
  64. package/lib/commands/spec-pipeline.js +10 -1
  65. package/lib/commands/studio.js +405 -30
  66. package/lib/commands/task.js +141 -7
  67. package/lib/governance/supreme-principles.js +530 -0
  68. package/lib/problem/problem-evaluator.js +4 -0
  69. package/lib/project/candidate-inspection-service.js +24 -1
  70. package/lib/project/portfolio-projection-service.js +315 -5
  71. package/lib/project/project-channel-output.js +94 -0
  72. package/lib/project/project-channel-projection.js +181 -0
  73. package/lib/project/root-onboarding-service.js +60 -8
  74. package/lib/project/semantic-shared-source-projection.js +150 -0
  75. package/lib/project/supervision-action-model.js +277 -0
  76. package/lib/project/supervision-projection-service.js +305 -5
  77. package/lib/project/target-resolution-service.js +70 -5
  78. package/lib/project/visibility-policy.js +93 -0
  79. package/lib/runtime/multi-spec-scene-session.js +8 -1
  80. package/lib/runtime/project-channel-context-store.js +387 -0
  81. package/lib/runtime/project-channel-context.js +406 -0
  82. package/lib/runtime/scene-session-binding.js +46 -0
  83. package/lib/runtime/session-store.js +186 -0
  84. package/lib/runtime/steering-contract.js +7 -1
  85. package/lib/semantic/archive-report.js +283 -0
  86. package/lib/semantic/archive-routing.js +67 -0
  87. package/lib/semantic/backflow-report.js +245 -0
  88. package/lib/semantic/capability-contract.js +30 -0
  89. package/lib/semantic/delta-export.js +145 -0
  90. package/lib/semantic/interaction-observer.js +254 -0
  91. package/lib/semantic/kernel-loader.js +881 -0
  92. package/lib/semantic/native-runtime.js +359 -0
  93. package/lib/semantic/progress-ledger.js +433 -0
  94. package/lib/semantic/replay-evaluator.js +382 -0
  95. package/lib/semantic/shared-publication.js +592 -0
  96. package/lib/semantic/shared-source-config.js +183 -0
  97. package/lib/semantic/shared-source-connect.js +139 -0
  98. package/lib/semantic/shared-source-discovery.js +98 -0
  99. package/lib/semantic/shared-sync-export.js +413 -0
  100. package/lib/semantic/shared-sync-intake.js +592 -0
  101. package/lib/semantic/shared-sync-merge.js +547 -0
  102. package/lib/semantic/shared-sync-release.js +463 -0
  103. package/lib/semantic/supreme-intent-report.js +300 -0
  104. package/lib/state/sce-state-store.js +1360 -0
  105. package/lib/steering/context-sync-manager.js +276 -25
  106. package/lib/studio/spec-intake-governor.js +39 -3
  107. package/lib/studio/task-envelope.js +35 -2
  108. package/lib/workspace/takeover-baseline.js +342 -83
  109. package/package.json +7 -2
  110. package/scripts/agent-governance-baseline-audit.js +395 -0
  111. package/scripts/clarification-first-audit.js +9 -9
  112. package/scripts/deprecated-entry-audit.js +240 -0
  113. package/scripts/release-posture-report.js +262 -0
  114. package/template/.sce/README.md +62 -228
  115. package/template/.sce/config/semantic-shared-sources.json +5 -0
  116. package/template/.sce/config/supreme-principles-policy.json +105 -0
  117. package/template/.sce/config/takeover-baseline.json +7 -0
  118. package/template/.sce/steering/CORE_PRINCIPLES.md +23 -63
  119. package/template/.sce/steering/CURRENT_CONTEXT.md +4 -0
  120. package/template/.sce/steering/RULES_GUIDE.md +17 -9
  121. package/template/README.md +32 -96
@@ -0,0 +1,547 @@
1
+ const path = require('path');
2
+ const http = require('http');
3
+ const https = require('https');
4
+ const fs = require('fs-extra');
5
+ const {
6
+ SEMANTIC_SHARED_SYNC_BUNDLE_API_VERSION,
7
+ SEMANTIC_SHARED_SYNC_INDEX_API_VERSION,
8
+ SEMANTIC_SHARED_SYNC_SHARD_API_VERSION,
9
+ resolveDefaultSemanticSharedSyncBundleOutFile,
10
+ resolveDefaultSemanticSharedSyncIndexOutFile,
11
+ resolveDefaultSemanticSharedSyncReceiptOutFile
12
+ } = require('./shared-sync-export');
13
+
14
+ const SEMANTIC_SHARED_SYNC_MERGE_RECEIPT_API_VERSION = 'sce.semantic.shared-sync.merge-receipt/v0.1';
15
+
16
+ function normalizeString(value) {
17
+ if (typeof value !== 'string') {
18
+ return '';
19
+ }
20
+ return value.trim();
21
+ }
22
+
23
+ function normalizeInteger(value, fallback = 0) {
24
+ const parsed = Number.parseInt(`${value}`, 10);
25
+ if (!Number.isFinite(parsed) || parsed <= 0) {
26
+ return fallback;
27
+ }
28
+ return fallback > 0 ? parsed : parsed;
29
+ }
30
+
31
+ function normalizeStringList(...values) {
32
+ const results = [];
33
+ for (const value of values) {
34
+ if (Array.isArray(value)) {
35
+ for (const item of value) {
36
+ const normalized = normalizeString(item);
37
+ if (normalized) {
38
+ results.push(normalized);
39
+ }
40
+ }
41
+ continue;
42
+ }
43
+ if (typeof value === 'string') {
44
+ value.split(',')
45
+ .map((item) => normalizeString(item))
46
+ .filter(Boolean)
47
+ .forEach((item) => results.push(item));
48
+ }
49
+ }
50
+ return Array.from(new Set(results));
51
+ }
52
+
53
+ function isObject(value) {
54
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
55
+ }
56
+
57
+ function isHttpSource(source = '') {
58
+ return /^https?:\/\//i.test(normalizeString(source));
59
+ }
60
+
61
+ function toProjectRelative(projectPath, targetPath) {
62
+ return path.relative(projectPath, targetPath).replace(/\\/g, '/');
63
+ }
64
+
65
+ function resolveDefaultSemanticSharedMergeBaseDir(specId = '') {
66
+ const normalizedSpecId = normalizeString(specId);
67
+ if (!normalizedSpecId) {
68
+ return '';
69
+ }
70
+ return `.sce/specs/${normalizedSpecId}/registry/semantic-shared`;
71
+ }
72
+
73
+ function resolveDefaultSemanticSharedMergeBundleOutFile(specId = '') {
74
+ const baseDir = resolveDefaultSemanticSharedMergeBaseDir(specId);
75
+ return baseDir ? `${baseDir}/latest.bundle.json` : '';
76
+ }
77
+
78
+ function resolveDefaultSemanticSharedMergeIndexOutFile(specId = '') {
79
+ const baseDir = resolveDefaultSemanticSharedMergeBaseDir(specId);
80
+ return baseDir ? `${baseDir}/latest.index.json` : '';
81
+ }
82
+
83
+ function resolveDefaultSemanticSharedMergeReceiptOutFile(specId = '') {
84
+ const baseDir = resolveDefaultSemanticSharedMergeBaseDir(specId);
85
+ return baseDir ? `${baseDir}/latest.receipt.json` : '';
86
+ }
87
+
88
+ function resolveDefaultSemanticSharedMergeShardOutFile(specId = '', targetLibrary = '') {
89
+ const baseDir = resolveDefaultSemanticSharedMergeBaseDir(specId);
90
+ const normalizedLibrary = normalizeString(targetLibrary);
91
+ return baseDir && normalizedLibrary
92
+ ? `${baseDir}/shards/${normalizedLibrary}.json`
93
+ : '';
94
+ }
95
+
96
+ function fetchJsonFromHttp(source, timeoutMs = 15000) {
97
+ const normalized = normalizeString(source);
98
+ if (!normalized) {
99
+ return Promise.reject(new Error('shared merge source is required'));
100
+ }
101
+ const client = normalized.startsWith('https://') ? https : http;
102
+ return new Promise((resolve, reject) => {
103
+ const request = client.get(normalized, {
104
+ timeout: timeoutMs,
105
+ headers: {
106
+ Accept: 'application/json'
107
+ }
108
+ }, (response) => {
109
+ const chunks = [];
110
+ response.on('data', (chunk) => chunks.push(chunk));
111
+ response.on('end', () => {
112
+ const body = Buffer.concat(chunks).toString('utf8');
113
+ if (response.statusCode < 200 || response.statusCode >= 300) {
114
+ reject(new Error(`shared merge source responded ${response.statusCode}`));
115
+ return;
116
+ }
117
+ try {
118
+ resolve(JSON.parse(body));
119
+ } catch (error) {
120
+ reject(new Error(`shared merge source returned invalid JSON: ${error.message}`));
121
+ }
122
+ });
123
+ });
124
+ request.on('timeout', () => {
125
+ request.destroy(new Error('shared merge source request timed out'));
126
+ });
127
+ request.on('error', reject);
128
+ });
129
+ }
130
+
131
+ async function loadJsonSource(projectPath, source, fileSystem = fs) {
132
+ const normalized = normalizeString(source);
133
+ if (!normalized) {
134
+ throw new Error('shared merge source is required');
135
+ }
136
+ if (isHttpSource(normalized)) {
137
+ return fetchJsonFromHttp(normalized);
138
+ }
139
+ const absolutePath = path.isAbsolute(normalized)
140
+ ? normalized
141
+ : path.resolve(projectPath, normalized);
142
+ if (!await fileSystem.pathExists(absolutePath)) {
143
+ throw new Error(`shared merge source file not found: ${source}`);
144
+ }
145
+ return fileSystem.readJson(absolutePath);
146
+ }
147
+
148
+ function inferSourceRootFromSource(source = '') {
149
+ const normalized = normalizeString(source);
150
+ if (!normalized) {
151
+ return '';
152
+ }
153
+ if (isHttpSource(normalized)) {
154
+ const sceMarkerIndex = normalized.indexOf('/.sce/');
155
+ if (sceMarkerIndex > 0) {
156
+ return normalized.slice(0, sceMarkerIndex);
157
+ }
158
+ return normalizeString(normalized.replace(/\/[^/]*$/, ''));
159
+ }
160
+ const normalizedPath = path.resolve(normalized);
161
+ const marker = `${path.sep}.sce${path.sep}`;
162
+ const markerIndex = normalizedPath.indexOf(marker);
163
+ if (markerIndex > -1) {
164
+ return normalizedPath.slice(0, markerIndex);
165
+ }
166
+ return path.dirname(normalizedPath);
167
+ }
168
+
169
+ function resolveShardSource(sourceRoot = '', shardFile = '', bundleSource = '') {
170
+ const normalizedShardFile = normalizeString(shardFile);
171
+ if (!normalizedShardFile) {
172
+ return '';
173
+ }
174
+ if (isHttpSource(normalizedShardFile) || path.isAbsolute(normalizedShardFile)) {
175
+ return normalizedShardFile;
176
+ }
177
+ const normalizedRoot = normalizeString(sourceRoot) || inferSourceRootFromSource(bundleSource);
178
+ if (isHttpSource(normalizedRoot)) {
179
+ return `${normalizedRoot.replace(/\/+$/, '')}/${normalizedShardFile.replace(/^\/+/, '')}`;
180
+ }
181
+ if (normalizedRoot) {
182
+ return path.resolve(normalizedRoot, normalizedShardFile);
183
+ }
184
+ if (isHttpSource(bundleSource)) {
185
+ return new URL(normalizedShardFile, bundleSource).toString();
186
+ }
187
+ return path.resolve(path.dirname(bundleSource), normalizedShardFile);
188
+ }
189
+
190
+ async function readSemanticSharedSourceConfig(projectPath, configPath = '', fileSystem = fs) {
191
+ const normalizedConfigPath = normalizeString(configPath) || '.sce/config/semantic-shared-sources.json';
192
+ const absoluteConfigPath = path.isAbsolute(normalizedConfigPath)
193
+ ? normalizedConfigPath
194
+ : path.resolve(projectPath, normalizedConfigPath);
195
+ if (!await fileSystem.pathExists(absoluteConfigPath)) {
196
+ return {
197
+ enabled: true,
198
+ sources: []
199
+ };
200
+ }
201
+ const payload = await fileSystem.readJson(absoluteConfigPath);
202
+ if (!isObject(payload)) {
203
+ throw new Error(`semantic shared source config must be a JSON object: ${absoluteConfigPath}`);
204
+ }
205
+ return payload;
206
+ }
207
+
208
+ function resolveConfiguredSources(config = {}) {
209
+ const sources = Array.isArray(config.sources) ? config.sources : [];
210
+ return sources
211
+ .filter((item) => isObject(item) && item.enabled !== false)
212
+ .map((item) => ({
213
+ name: normalizeString(item.name) || 'semantic-shared',
214
+ bundle: normalizeString(item.bundle || item.source || item.url || item.file),
215
+ root: normalizeString(item.root || item.base || item.base_url || item.base_path)
216
+ }))
217
+ .filter((item) => item.bundle);
218
+ }
219
+
220
+ function comparePublishedAt(left = {}, right = {}) {
221
+ const leftTime = Date.parse(left.published_at || '') || 0;
222
+ const rightTime = Date.parse(right.published_at || '') || 0;
223
+ return leftTime - rightTime;
224
+ }
225
+
226
+ function assessCentralMergeEntryGate(entry = {}) {
227
+ const publication = isObject(entry.publication) ? entry.publication : {};
228
+ const publishState = normalizeString(publication.publish_state);
229
+ const blockedReason = normalizeString(publication.blocked_reason);
230
+ const blockedGate = isObject(publication.blocked_gate) ? publication.blocked_gate : null;
231
+ const reasons = [];
232
+
233
+ if (publishState && publishState !== 'published-shared') {
234
+ reasons.push({
235
+ reason: 'entry-not-shared-ready',
236
+ publish_state: publishState
237
+ });
238
+ }
239
+ if (blockedReason) {
240
+ reasons.push({
241
+ reason: 'entry-has-blocked-reason',
242
+ blocked_reason: blockedReason
243
+ });
244
+ }
245
+ if (blockedGate) {
246
+ reasons.push({
247
+ reason: 'entry-has-blocked-gate',
248
+ blocked_gate: blockedGate
249
+ });
250
+ }
251
+
252
+ return {
253
+ allowed: reasons.length === 0,
254
+ reasons
255
+ };
256
+ }
257
+
258
+ async function mergeSemanticSharedBundles(options = {}, dependencies = {}) {
259
+ const projectPath = dependencies.projectPath || process.cwd();
260
+ const fileSystem = dependencies.fileSystem || fs;
261
+ const config = await readSemanticSharedSourceConfig(projectPath, options.config, fileSystem);
262
+ const configuredSources = resolveConfiguredSources(config);
263
+ const explicitSources = normalizeStringList(options.source, options.sources);
264
+ const sourceRefs = explicitSources.length > 0
265
+ ? explicitSources.map((source, index) => ({
266
+ name: `source-${index + 1}`,
267
+ bundle: source,
268
+ root: ''
269
+ }))
270
+ : configuredSources;
271
+
272
+ if (sourceRefs.length === 0) {
273
+ throw new Error('at least one shared merge source is required (use --source/--sources or configure semantic-shared-sources.json)');
274
+ }
275
+
276
+ const specId = normalizeString(options.spec);
277
+ const limit = normalizeInteger(options.limit, 0);
278
+ const generatedAt = new Date().toISOString();
279
+ const candidateEntriesByKey = new Map();
280
+ const blocked = [];
281
+ const superseded = [];
282
+ let candidateEntries = 0;
283
+
284
+ const sourceReports = [];
285
+ for (const sourceRef of sourceRefs) {
286
+ const bundleSource = normalizeString(sourceRef.bundle);
287
+ const sourceRoot = normalizeString(sourceRef.root) || inferSourceRootFromSource(bundleSource);
288
+ const sourceReport = {
289
+ source: bundleSource,
290
+ source_name: normalizeString(sourceRef.name) || null,
291
+ bundle_ok: false,
292
+ shards_ok: 0,
293
+ entries_seen: 0
294
+ };
295
+
296
+ let bundlePayload;
297
+ try {
298
+ bundlePayload = await loadJsonSource(projectPath, bundleSource, fileSystem);
299
+ } catch (error) {
300
+ blocked.push({
301
+ source: bundleSource,
302
+ reason: 'failed-to-load-bundle',
303
+ detail: error.message
304
+ });
305
+ sourceReports.push(sourceReport);
306
+ continue;
307
+ }
308
+ if (!isObject(bundlePayload) || normalizeString(bundlePayload.api_version) !== SEMANTIC_SHARED_SYNC_BUNDLE_API_VERSION) {
309
+ blocked.push({
310
+ source: bundleSource,
311
+ reason: 'invalid-bundle-payload'
312
+ });
313
+ sourceReports.push(sourceReport);
314
+ continue;
315
+ }
316
+ sourceReport.bundle_ok = true;
317
+
318
+ const libraries = Array.isArray(bundlePayload.libraries) ? bundlePayload.libraries : [];
319
+ for (const library of libraries) {
320
+ const targetLibrary = normalizeString(library && library.target_library);
321
+ const shardSource = resolveShardSource(sourceRoot, normalizeString(library && library.shard_file), bundleSource);
322
+ if (!targetLibrary || !shardSource) {
323
+ blocked.push({
324
+ source: bundleSource,
325
+ target_library: targetLibrary || null,
326
+ reason: 'missing-target-library-or-shard-source'
327
+ });
328
+ continue;
329
+ }
330
+ let shardPayload;
331
+ try {
332
+ shardPayload = await loadJsonSource(projectPath, shardSource, fileSystem);
333
+ } catch (error) {
334
+ blocked.push({
335
+ source: bundleSource,
336
+ target_library: targetLibrary,
337
+ reason: 'failed-to-load-shard',
338
+ detail: error.message
339
+ });
340
+ continue;
341
+ }
342
+ if (!isObject(shardPayload) || normalizeString(shardPayload.api_version) !== SEMANTIC_SHARED_SYNC_SHARD_API_VERSION) {
343
+ blocked.push({
344
+ source: bundleSource,
345
+ target_library: targetLibrary,
346
+ reason: 'invalid-shard-payload'
347
+ });
348
+ continue;
349
+ }
350
+ sourceReport.shards_ok += 1;
351
+ const entries = Array.isArray(shardPayload.entries) ? shardPayload.entries : [];
352
+ for (const entry of entries) {
353
+ const capabilityId = normalizeString(entry && entry.capability_id);
354
+ const entryTargetLibrary = normalizeString(entry && entry.target_library) || targetLibrary;
355
+ if (!capabilityId || !entryTargetLibrary) {
356
+ blocked.push({
357
+ source: bundleSource,
358
+ target_library: entryTargetLibrary || null,
359
+ reason: 'missing-capability-or-library'
360
+ });
361
+ continue;
362
+ }
363
+ const gate = assessCentralMergeEntryGate(entry);
364
+ if (!gate.allowed) {
365
+ blocked.push({
366
+ source: bundleSource,
367
+ capability_id: capabilityId,
368
+ lesson_id: normalizeString(entry && entry.lesson_id) || null,
369
+ target_library: entryTargetLibrary,
370
+ reason: 'entry-failed-central-merge-gate',
371
+ gate
372
+ });
373
+ continue;
374
+ }
375
+ candidateEntries += 1;
376
+ sourceReport.entries_seen += 1;
377
+ const key = `${entryTargetLibrary}::${capabilityId}`;
378
+ const nextEntry = {
379
+ ...entry,
380
+ target_library: entryTargetLibrary,
381
+ merge: {
382
+ merged_at: generatedAt,
383
+ merged_source: bundleSource,
384
+ merged_source_name: sourceRef.name || null,
385
+ merged_shard_source: shardSource
386
+ }
387
+ };
388
+ const previous = candidateEntriesByKey.get(key);
389
+ if (!previous) {
390
+ candidateEntriesByKey.set(key, nextEntry);
391
+ continue;
392
+ }
393
+ if (comparePublishedAt(previous, nextEntry) <= 0) {
394
+ superseded.push({
395
+ capability_id: capabilityId,
396
+ target_library: entryTargetLibrary,
397
+ kept_source: bundleSource,
398
+ dropped_source: normalizeString(previous.merge && previous.merge.merged_source) || null
399
+ });
400
+ candidateEntriesByKey.set(key, nextEntry);
401
+ } else {
402
+ superseded.push({
403
+ capability_id: capabilityId,
404
+ target_library: entryTargetLibrary,
405
+ kept_source: normalizeString(previous.merge && previous.merge.merged_source) || null,
406
+ dropped_source: bundleSource
407
+ });
408
+ }
409
+ }
410
+ }
411
+ sourceReports.push(sourceReport);
412
+ }
413
+
414
+ let mergedEntries = Array.from(candidateEntriesByKey.values());
415
+ mergedEntries.sort((left, right) => {
416
+ const libraryCompare = normalizeString(left.target_library).localeCompare(normalizeString(right.target_library));
417
+ if (libraryCompare !== 0) {
418
+ return libraryCompare;
419
+ }
420
+ return (Date.parse(right.published_at || '') || 0) - (Date.parse(left.published_at || '') || 0);
421
+ });
422
+ if (limit > 0) {
423
+ mergedEntries = mergedEntries.slice(0, limit);
424
+ }
425
+
426
+ const byLibrary = new Map();
427
+ for (const entry of mergedEntries) {
428
+ const targetLibrary = normalizeString(entry.target_library);
429
+ if (!byLibrary.has(targetLibrary)) {
430
+ byLibrary.set(targetLibrary, []);
431
+ }
432
+ byLibrary.get(targetLibrary).push(entry);
433
+ }
434
+
435
+ const bundleOutFile = normalizeString(options.outFile || options.out_file)
436
+ || resolveDefaultSemanticSharedMergeBundleOutFile(specId);
437
+ const indexOutFile = bundleOutFile
438
+ ? bundleOutFile.replace(/\.bundle\.json$/i, '.index.json')
439
+ : resolveDefaultSemanticSharedMergeIndexOutFile(specId);
440
+ const receiptOutFile = bundleOutFile
441
+ ? bundleOutFile.replace(/\.bundle\.json$/i, '.receipt.json')
442
+ : resolveDefaultSemanticSharedMergeReceiptOutFile(specId);
443
+
444
+ const libraries = [];
445
+ const indexShards = {};
446
+ for (const [targetLibrary, entries] of byLibrary.entries()) {
447
+ const shardFile = specId
448
+ ? resolveDefaultSemanticSharedMergeShardOutFile(specId, targetLibrary)
449
+ : '';
450
+ const shardPayload = {
451
+ api_version: SEMANTIC_SHARED_SYNC_SHARD_API_VERSION,
452
+ generated_at: generatedAt,
453
+ target_library: targetLibrary,
454
+ total_entries: entries.length,
455
+ entries
456
+ };
457
+ if (shardFile) {
458
+ const absoluteShardPath = path.join(projectPath, shardFile);
459
+ await fileSystem.ensureDir(path.dirname(absoluteShardPath));
460
+ await fileSystem.writeJson(absoluteShardPath, shardPayload, { spaces: 2 });
461
+ }
462
+ libraries.push({
463
+ target_library: targetLibrary,
464
+ shard_file: shardFile || null,
465
+ entry_count: entries.length,
466
+ latest_published_at: normalizeString(entries[0] && entries[0].published_at) || null
467
+ });
468
+ indexShards[targetLibrary] = {
469
+ shard_file: shardFile || null,
470
+ entry_count: entries.length,
471
+ latest_published_at: normalizeString(entries[0] && entries[0].published_at) || null
472
+ };
473
+ }
474
+
475
+ const bundlePayload = {
476
+ api_version: SEMANTIC_SHARED_SYNC_BUNDLE_API_VERSION,
477
+ mode: 'semantic-merge-shared',
478
+ success: blocked.length === 0,
479
+ generated_at: generatedAt,
480
+ spec_id: specId || null,
481
+ mirror_root: '.sce/knowledge/semantic-shared',
482
+ totals: {
483
+ sources: sourceRefs.length,
484
+ candidate_entries: candidateEntries,
485
+ merged_entries: mergedEntries.length,
486
+ superseded: superseded.length,
487
+ blocked: blocked.length
488
+ },
489
+ sources: sourceReports,
490
+ libraries,
491
+ blocked
492
+ };
493
+
494
+ const indexPayload = {
495
+ api_version: SEMANTIC_SHARED_SYNC_INDEX_API_VERSION,
496
+ generated_at: generatedAt,
497
+ spec_id: specId || null,
498
+ bundle_file: bundleOutFile || null,
499
+ shard_count: libraries.length,
500
+ shards: indexShards
501
+ };
502
+
503
+ const receiptPayload = {
504
+ api_version: SEMANTIC_SHARED_SYNC_MERGE_RECEIPT_API_VERSION,
505
+ mode: 'semantic-merge-shared',
506
+ success: blocked.length === 0,
507
+ generated_at: generatedAt,
508
+ spec_id: specId || null,
509
+ totals: bundlePayload.totals,
510
+ sources: sourceReports,
511
+ libraries,
512
+ superseded,
513
+ blocked
514
+ };
515
+
516
+ if (bundleOutFile) {
517
+ const absoluteBundlePath = path.join(projectPath, bundleOutFile);
518
+ await fileSystem.ensureDir(path.dirname(absoluteBundlePath));
519
+ await fileSystem.writeJson(absoluteBundlePath, bundlePayload, { spaces: 2 });
520
+ }
521
+ if (indexOutFile) {
522
+ const absoluteIndexPath = path.join(projectPath, indexOutFile);
523
+ await fileSystem.ensureDir(path.dirname(absoluteIndexPath));
524
+ await fileSystem.writeJson(absoluteIndexPath, indexPayload, { spaces: 2 });
525
+ }
526
+ if (receiptOutFile) {
527
+ const absoluteReceiptPath = path.join(projectPath, receiptOutFile);
528
+ await fileSystem.ensureDir(path.dirname(absoluteReceiptPath));
529
+ await fileSystem.writeJson(absoluteReceiptPath, receiptPayload, { spaces: 2 });
530
+ }
531
+
532
+ return {
533
+ ...receiptPayload,
534
+ out_file: bundleOutFile || null,
535
+ index_file: indexOutFile || null,
536
+ receipt_file: receiptOutFile || null
537
+ };
538
+ }
539
+
540
+ module.exports = {
541
+ SEMANTIC_SHARED_SYNC_MERGE_RECEIPT_API_VERSION,
542
+ resolveDefaultSemanticSharedMergeBundleOutFile,
543
+ resolveDefaultSemanticSharedMergeIndexOutFile,
544
+ resolveDefaultSemanticSharedMergeReceiptOutFile,
545
+ resolveDefaultSemanticSharedMergeShardOutFile,
546
+ mergeSemanticSharedBundles
547
+ };