scene-capability-engine 3.6.64 → 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 (125) hide show
  1. package/CHANGELOG.md +26 -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 +385 -8
  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-cli-invocation-examples.md +1 -0
  11. package/docs/magicball-project-portfolio-contract.md +125 -4
  12. package/docs/project-management/README.md +14 -0
  13. package/docs/project-management/assurance/backup.md +3 -0
  14. package/docs/project-management/assurance/config.md +3 -0
  15. package/docs/project-management/assurance/evidence/README.md +3 -0
  16. package/docs/project-management/assurance/incidents/README.md +3 -0
  17. package/docs/project-management/assurance/logs.md +3 -0
  18. package/docs/project-management/assurance/overview.md +3 -0
  19. package/docs/project-management/assurance/recovery/README.md +3 -0
  20. package/docs/project-management/assurance/resource.md +3 -0
  21. package/docs/project-management/assurance/runbooks/README.md +3 -0
  22. package/docs/project-management/delivery/acceptance/README.md +3 -0
  23. package/docs/project-management/delivery/acceptance/evidence/README.md +3 -0
  24. package/docs/project-management/delivery/acceptance/exceptions/README.md +3 -0
  25. package/docs/project-management/delivery/acceptance/reports/README.md +3 -0
  26. package/docs/project-management/delivery/documents/changes.md +3 -0
  27. package/docs/project-management/delivery/documents/issues.md +3 -0
  28. package/docs/project-management/delivery/documents/overview.md +3 -0
  29. package/docs/project-management/delivery/documents/planning.md +3 -0
  30. package/docs/project-management/delivery/documents/requirements.md +3 -0
  31. package/docs/project-management/delivery/documents/tracking.md +3 -0
  32. package/docs/project-management/delivery/handoffs/README.md +3 -0
  33. package/docs/project-management/delivery/handoffs/evidence/README.md +3 -0
  34. package/docs/project-management/delivery/handoffs/records/README.md +3 -0
  35. package/docs/project-management/delivery/overview.md +10 -0
  36. package/docs/project-management/delivery/releases/README.md +3 -0
  37. package/docs/project-management/delivery/releases/baselines/README.md +3 -0
  38. package/docs/project-management/delivery/releases/evidence/README.md +3 -0
  39. package/docs/project-management/delivery/tables/changes.md +3 -0
  40. package/docs/project-management/delivery/tables/issues.md +3 -0
  41. package/docs/project-management/delivery/tables/planning.md +3 -0
  42. package/docs/project-management/delivery/tables/requirements.md +3 -0
  43. package/docs/project-management/delivery/tables/tracking.md +3 -0
  44. package/docs/project-management/environment/agent-discovery.md +3 -0
  45. package/docs/project-management/environment/development.md +3 -0
  46. package/docs/project-management/environment/overview.md +10 -0
  47. package/docs/project-management/environment/testing.md +3 -0
  48. package/docs/project-management/environment/version-alignment.md +3 -0
  49. package/docs/quick-start-with-ai-tools.md +68 -308
  50. package/docs/releases/README.md +3 -0
  51. package/docs/releases/v3.6.65.md +25 -0
  52. package/docs/releases/v3.6.66.md +23 -0
  53. package/docs/releases/v3.6.67.md +23 -0
  54. package/docs/steering-governance.md +64 -2
  55. package/docs/zh/README.md +2 -2
  56. package/docs/zh/releases/README.md +3 -0
  57. package/docs/zh/releases/v3.6.65.md +25 -0
  58. package/docs/zh/releases/v3.6.66.md +23 -0
  59. package/docs/zh/releases/v3.6.67.md +23 -0
  60. package/lib/commands/adopt.js +24 -0
  61. package/lib/commands/native.js +158 -0
  62. package/lib/commands/project.js +96 -0
  63. package/lib/commands/semantic.js +1459 -0
  64. package/lib/commands/session.js +74 -3
  65. package/lib/commands/spec-bootstrap.js +10 -1
  66. package/lib/commands/spec-gate.js +10 -1
  67. package/lib/commands/spec-pipeline.js +10 -1
  68. package/lib/commands/studio.js +405 -30
  69. package/lib/commands/task.js +141 -7
  70. package/lib/governance/supreme-principles.js +530 -0
  71. package/lib/problem/problem-evaluator.js +4 -0
  72. package/lib/project/candidate-inspection-service.js +24 -1
  73. package/lib/project/portfolio-projection-service.js +315 -5
  74. package/lib/project/project-channel-output.js +94 -0
  75. package/lib/project/project-channel-projection.js +181 -0
  76. package/lib/project/root-onboarding-service.js +107 -7
  77. package/lib/project/semantic-shared-source-projection.js +150 -0
  78. package/lib/project/supervision-action-model.js +277 -0
  79. package/lib/project/supervision-projection-service.js +305 -5
  80. package/lib/project/target-resolution-service.js +70 -5
  81. package/lib/project/visibility-policy.js +93 -0
  82. package/lib/runtime/multi-spec-scene-session.js +8 -1
  83. package/lib/runtime/project-channel-context-store.js +387 -0
  84. package/lib/runtime/project-channel-context.js +406 -0
  85. package/lib/runtime/scene-session-binding.js +46 -0
  86. package/lib/runtime/session-store.js +186 -0
  87. package/lib/runtime/steering-contract.js +7 -1
  88. package/lib/semantic/archive-report.js +283 -0
  89. package/lib/semantic/archive-routing.js +67 -0
  90. package/lib/semantic/backflow-report.js +245 -0
  91. package/lib/semantic/capability-contract.js +30 -0
  92. package/lib/semantic/delta-export.js +145 -0
  93. package/lib/semantic/interaction-observer.js +254 -0
  94. package/lib/semantic/kernel-loader.js +881 -0
  95. package/lib/semantic/native-runtime.js +359 -0
  96. package/lib/semantic/progress-ledger.js +433 -0
  97. package/lib/semantic/replay-evaluator.js +382 -0
  98. package/lib/semantic/shared-publication.js +592 -0
  99. package/lib/semantic/shared-source-config.js +183 -0
  100. package/lib/semantic/shared-source-connect.js +139 -0
  101. package/lib/semantic/shared-source-discovery.js +98 -0
  102. package/lib/semantic/shared-sync-export.js +413 -0
  103. package/lib/semantic/shared-sync-intake.js +592 -0
  104. package/lib/semantic/shared-sync-merge.js +547 -0
  105. package/lib/semantic/shared-sync-release.js +463 -0
  106. package/lib/semantic/supreme-intent-report.js +300 -0
  107. package/lib/state/sce-state-store.js +1360 -0
  108. package/lib/steering/context-sync-manager.js +276 -25
  109. package/lib/studio/spec-intake-governor.js +39 -3
  110. package/lib/studio/task-envelope.js +35 -2
  111. package/lib/workspace/takeover-baseline.js +342 -83
  112. package/package.json +7 -2
  113. package/scripts/agent-governance-baseline-audit.js +395 -0
  114. package/scripts/clarification-first-audit.js +9 -9
  115. package/scripts/deprecated-entry-audit.js +240 -0
  116. package/scripts/release-doc-version-audit.js +24 -0
  117. package/scripts/release-posture-report.js +262 -0
  118. package/template/.sce/README.md +62 -228
  119. package/template/.sce/config/semantic-shared-sources.json +5 -0
  120. package/template/.sce/config/supreme-principles-policy.json +105 -0
  121. package/template/.sce/config/takeover-baseline.json +7 -0
  122. package/template/.sce/steering/CORE_PRINCIPLES.md +23 -63
  123. package/template/.sce/steering/CURRENT_CONTEXT.md +4 -0
  124. package/template/.sce/steering/RULES_GUIDE.md +17 -9
  125. package/template/README.md +32 -96
@@ -0,0 +1,592 @@
1
+ const path = require('path');
2
+ const http = require('http');
3
+ const https = require('https');
4
+ const fs = require('fs-extra');
5
+ const {
6
+ SHARED_LIBRARY_ROOT,
7
+ resolveSharedLibraryEntryRelativePath,
8
+ upsertSharedLibraryIndex
9
+ } = require('./shared-publication');
10
+
11
+ const SEMANTIC_SHARED_SOURCE_CONFIG = '.sce/config/semantic-shared-sources.json';
12
+ const SEMANTIC_SHARED_SYNC_INTAKE_API_VERSION = 'sce.semantic.shared-sync.intake/v0.1';
13
+ const SEMANTIC_SHARED_SYNC_BUNDLE_API_VERSION = 'sce.semantic.shared-sync.bundle/v0.1';
14
+ const SEMANTIC_SHARED_SYNC_SHARD_API_VERSION = 'sce.semantic.shared-sync.shard/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 parsed;
29
+ }
30
+
31
+ function normalizeBoolean(value, fallback = false) {
32
+ if (typeof value === 'boolean') {
33
+ return value;
34
+ }
35
+ const normalized = normalizeString(`${value || ''}`).toLowerCase();
36
+ if (!normalized) {
37
+ return fallback;
38
+ }
39
+ if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
40
+ return true;
41
+ }
42
+ if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
43
+ return false;
44
+ }
45
+ return fallback;
46
+ }
47
+
48
+ function isObject(value) {
49
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
50
+ }
51
+
52
+ function toProjectRelative(projectPath, targetPath) {
53
+ return path.relative(projectPath, targetPath).replace(/\\/g, '/');
54
+ }
55
+
56
+ function resolveSemanticSharedSourceConfigPath(projectPath = process.cwd(), configPath = '') {
57
+ const normalized = normalizeString(configPath) || SEMANTIC_SHARED_SOURCE_CONFIG;
58
+ return path.isAbsolute(normalized)
59
+ ? normalized
60
+ : path.resolve(projectPath, normalized);
61
+ }
62
+
63
+ function resolveDefaultSemanticSharedSyncIntakeOutFile(specId = '') {
64
+ const normalizedSpecId = normalizeString(specId);
65
+ if (!normalizedSpecId) {
66
+ return '';
67
+ }
68
+ return `.sce/specs/${normalizedSpecId}/intake/semantic-shared/latest.json`;
69
+ }
70
+
71
+ function isHttpSource(source = '') {
72
+ return /^https?:\/\//i.test(normalizeString(source));
73
+ }
74
+
75
+ function fetchJsonFromHttp(source, timeoutMs = 15000) {
76
+ const normalized = normalizeString(source);
77
+ if (!normalized) {
78
+ return Promise.reject(new Error('shared sync source is required'));
79
+ }
80
+ const client = normalized.startsWith('https://') ? https : http;
81
+ return new Promise((resolve, reject) => {
82
+ const request = client.get(normalized, {
83
+ timeout: timeoutMs,
84
+ headers: {
85
+ Accept: 'application/json'
86
+ }
87
+ }, (response) => {
88
+ const chunks = [];
89
+ response.on('data', (chunk) => chunks.push(chunk));
90
+ response.on('end', () => {
91
+ const body = Buffer.concat(chunks).toString('utf8');
92
+ if (response.statusCode < 200 || response.statusCode >= 300) {
93
+ reject(new Error(`shared sync source responded ${response.statusCode}`));
94
+ return;
95
+ }
96
+ try {
97
+ resolve(JSON.parse(body));
98
+ } catch (error) {
99
+ reject(new Error(`shared sync source returned invalid JSON: ${error.message}`));
100
+ }
101
+ });
102
+ });
103
+ request.on('timeout', () => {
104
+ request.destroy(new Error('shared sync source request timed out'));
105
+ });
106
+ request.on('error', reject);
107
+ });
108
+ }
109
+
110
+ async function loadJsonSource(projectPath, source, fileSystem = fs) {
111
+ const normalized = normalizeString(source);
112
+ if (!normalized) {
113
+ throw new Error('shared sync source is required');
114
+ }
115
+ if (isHttpSource(normalized)) {
116
+ return fetchJsonFromHttp(normalized);
117
+ }
118
+ const absolutePath = path.isAbsolute(normalized)
119
+ ? normalized
120
+ : path.resolve(projectPath, normalized);
121
+ if (!await fileSystem.pathExists(absolutePath)) {
122
+ throw new Error(`shared sync source file not found: ${source}`);
123
+ }
124
+ return fileSystem.readJson(absolutePath);
125
+ }
126
+
127
+ async function readSemanticSharedSourceConfig(projectPath, options = {}, fileSystem = fs) {
128
+ const configFile = resolveSemanticSharedSourceConfigPath(projectPath, options.config);
129
+ if (!await fileSystem.pathExists(configFile)) {
130
+ return {
131
+ enabled: true,
132
+ mirror_root: SHARED_LIBRARY_ROOT,
133
+ sources: []
134
+ };
135
+ }
136
+ const payload = await fileSystem.readJson(configFile);
137
+ if (!isObject(payload)) {
138
+ throw new Error(`semantic shared source config must be a JSON object: ${configFile}`);
139
+ }
140
+ return payload;
141
+ }
142
+
143
+ function inferSourceRootFromSource(source = '') {
144
+ const normalized = normalizeString(source);
145
+ if (!normalized) {
146
+ return '';
147
+ }
148
+ if (isHttpSource(normalized)) {
149
+ const sceMarkerIndex = normalized.indexOf('/.sce/');
150
+ if (sceMarkerIndex > 0) {
151
+ return normalized.slice(0, sceMarkerIndex);
152
+ }
153
+ try {
154
+ const parsed = new URL(normalized);
155
+ const pathname = parsed.pathname || '';
156
+ const markerIndex = pathname.indexOf('/.sce/');
157
+ if (markerIndex > -1) {
158
+ parsed.pathname = pathname.slice(0, markerIndex) || '/';
159
+ parsed.search = '';
160
+ parsed.hash = '';
161
+ return parsed.toString().replace(/\/$/, '');
162
+ }
163
+ } catch (_error) {
164
+ return '';
165
+ }
166
+ return normalizeString(normalized.replace(/\/[^/]*$/, ''));
167
+ }
168
+
169
+ const normalizedPath = path.resolve(normalized);
170
+ const marker = `${path.sep}.sce${path.sep}`;
171
+ const markerIndex = normalizedPath.indexOf(marker);
172
+ if (markerIndex > -1) {
173
+ return normalizedPath.slice(0, markerIndex);
174
+ }
175
+ return path.dirname(normalizedPath);
176
+ }
177
+
178
+ function joinSourceRoot(sourceRoot = '', relativePath = '') {
179
+ const normalizedRoot = normalizeString(sourceRoot);
180
+ const normalizedRelativePath = normalizeString(relativePath).replace(/\\/g, '/').replace(/^\/+/, '');
181
+ if (!normalizedRoot || !normalizedRelativePath) {
182
+ return '';
183
+ }
184
+ if (isHttpSource(normalizedRoot)) {
185
+ return `${normalizedRoot.replace(/\/+$/, '')}/${normalizedRelativePath}`;
186
+ }
187
+ return path.resolve(normalizedRoot, normalizedRelativePath);
188
+ }
189
+
190
+ function resolveShardSource(sourceRoot = '', shardFile = '', bundleSource = '') {
191
+ const normalizedShardFile = normalizeString(shardFile);
192
+ if (!normalizedShardFile) {
193
+ return '';
194
+ }
195
+ if (isHttpSource(normalizedShardFile) || path.isAbsolute(normalizedShardFile)) {
196
+ return normalizedShardFile;
197
+ }
198
+ const normalizedRoot = normalizeString(sourceRoot) || inferSourceRootFromSource(bundleSource);
199
+ if (normalizedRoot) {
200
+ return joinSourceRoot(normalizedRoot, normalizedShardFile);
201
+ }
202
+ if (isHttpSource(bundleSource)) {
203
+ return new URL(normalizedShardFile, bundleSource).toString();
204
+ }
205
+ return path.resolve(path.dirname(bundleSource), normalizedShardFile);
206
+ }
207
+
208
+ function normalizeSourceCandidate(candidate = {}) {
209
+ if (!isObject(candidate)) {
210
+ return null;
211
+ }
212
+ const bundle = normalizeString(candidate.bundle || candidate.source || candidate.url || candidate.file);
213
+ if (!bundle) {
214
+ return null;
215
+ }
216
+ return {
217
+ name: normalizeString(candidate.name) || 'semantic-shared',
218
+ enabled: normalizeBoolean(candidate.enabled, true),
219
+ bundle,
220
+ root: normalizeString(candidate.root || candidate.base || candidate.base_url || candidate.base_path),
221
+ required_release_state: normalizeString(candidate.required_release_state || candidate.requiredReleaseState)
222
+ };
223
+ }
224
+
225
+ function resolveRequiredReleaseState(options = {}, configuredSource = null) {
226
+ return normalizeString(options.requiredReleaseState || options.required_release_state)
227
+ || normalizeString(configuredSource && configuredSource.required_release_state);
228
+ }
229
+
230
+ function readBundleReleaseState(bundlePayload = {}) {
231
+ if (!isObject(bundlePayload)) {
232
+ return '';
233
+ }
234
+ if (isObject(bundlePayload.governance)) {
235
+ return normalizeString(
236
+ bundlePayload.governance.release_state
237
+ || bundlePayload.governance.publish_state
238
+ || bundlePayload.governance.state
239
+ );
240
+ }
241
+ return normalizeString(bundlePayload.release_state);
242
+ }
243
+
244
+ function resolveConfiguredSemanticSource(config = {}, requestedSourceName = '') {
245
+ const sources = Array.isArray(config.sources)
246
+ ? config.sources.map((item) => normalizeSourceCandidate(item)).filter(Boolean)
247
+ : [];
248
+ if (!sources.length) {
249
+ return null;
250
+ }
251
+ const requestedName = normalizeString(requestedSourceName);
252
+ if (requestedName) {
253
+ return sources.find((item) => item.enabled && item.name === requestedName) || null;
254
+ }
255
+ return sources.find((item) => item.enabled) || null;
256
+ }
257
+
258
+ async function readJsonIfExists(fileSystem, absolutePath, fallback) {
259
+ if (!await fileSystem.pathExists(absolutePath)) {
260
+ return fallback;
261
+ }
262
+ try {
263
+ return await fileSystem.readJson(absolutePath);
264
+ } catch (_error) {
265
+ return fallback;
266
+ }
267
+ }
268
+
269
+ function comparePublishedAt(left = {}, right = {}) {
270
+ const leftTime = Date.parse(left.published_at || '') || 0;
271
+ const rightTime = Date.parse(right.published_at || '') || 0;
272
+ return leftTime - rightTime;
273
+ }
274
+
275
+ async function syncSemanticSharedBundle(options = {}, dependencies = {}) {
276
+ const projectPath = dependencies.projectPath || process.cwd();
277
+ const fileSystem = dependencies.fileSystem || fs;
278
+ const config = await readSemanticSharedSourceConfig(projectPath, {
279
+ config: options.config
280
+ }, fileSystem);
281
+ if (normalizeBoolean(config.enabled, true) === false) {
282
+ throw new Error('semantic shared source config is disabled');
283
+ }
284
+
285
+ const configuredSource = resolveConfiguredSemanticSource(config, options.sourceName);
286
+ const source = normalizeString(options.source)
287
+ || normalizeString(configuredSource && configuredSource.bundle);
288
+ if (!source) {
289
+ throw new Error('shared sync source is required (use --source or configure .sce/config/semantic-shared-sources.json)');
290
+ }
291
+ const sourceName = normalizeString(options.sourceName)
292
+ || normalizeString(configuredSource && configuredSource.name)
293
+ || 'semantic-shared';
294
+ const sourceRoot = normalizeString(options.sourceRoot)
295
+ || normalizeString(configuredSource && configuredSource.root)
296
+ || inferSourceRootFromSource(source);
297
+ const requiredReleaseState = resolveRequiredReleaseState(options, configuredSource);
298
+
299
+ const bundlePayload = await loadJsonSource(projectPath, source, fileSystem);
300
+ if (!isObject(bundlePayload) || normalizeString(bundlePayload.api_version) !== SEMANTIC_SHARED_SYNC_BUNDLE_API_VERSION) {
301
+ throw new Error(`invalid semantic shared sync bundle: ${source}`);
302
+ }
303
+
304
+ const bundleLibraries = Array.isArray(bundlePayload.libraries) ? bundlePayload.libraries : [];
305
+ const specId = normalizeString(options.spec)
306
+ || normalizeString(bundlePayload.spec_id);
307
+ const limit = normalizeInteger(options.limit, 100);
308
+ const generatedAt = new Date().toISOString();
309
+ const releaseState = readBundleReleaseState(bundlePayload) || null;
310
+ const imported = [];
311
+ const skipped = [];
312
+ const blocked = [];
313
+
314
+ if (requiredReleaseState && releaseState !== requiredReleaseState) {
315
+ blocked.push({
316
+ reason: 'release-state-mismatch',
317
+ required_release_state: requiredReleaseState,
318
+ actual_release_state: releaseState
319
+ });
320
+ }
321
+
322
+ if (blocked.length === 0) {
323
+ for (const library of bundleLibraries.slice(0, limit > 0 ? limit : bundleLibraries.length)) {
324
+ const targetLibrary = normalizeString(library && library.target_library);
325
+ const shardFile = normalizeString(library && library.shard_file);
326
+ const shardSource = resolveShardSource(sourceRoot, shardFile, source);
327
+ if (!targetLibrary || !shardSource) {
328
+ blocked.push({
329
+ target_library: targetLibrary || null,
330
+ reason: 'missing-target-library-or-shard-source',
331
+ shard_file: shardFile || null
332
+ });
333
+ continue;
334
+ }
335
+
336
+ let shardPayload;
337
+ try {
338
+ shardPayload = await loadJsonSource(projectPath, shardSource, fileSystem);
339
+ } catch (error) {
340
+ blocked.push({
341
+ target_library: targetLibrary,
342
+ reason: 'failed-to-load-shard',
343
+ shard_file: shardFile || null,
344
+ shard_source: shardSource,
345
+ detail: error.message
346
+ });
347
+ continue;
348
+ }
349
+ if (!isObject(shardPayload) || normalizeString(shardPayload.api_version) !== SEMANTIC_SHARED_SYNC_SHARD_API_VERSION) {
350
+ blocked.push({
351
+ target_library: targetLibrary,
352
+ reason: 'invalid-shard-payload',
353
+ shard_file: shardFile || null,
354
+ shard_source: shardSource
355
+ });
356
+ continue;
357
+ }
358
+
359
+ const entries = Array.isArray(shardPayload.entries) ? shardPayload.entries : [];
360
+ for (const entry of entries) {
361
+ const capabilityId = normalizeString(entry && entry.capability_id);
362
+ const lessonId = normalizeString(entry && entry.lesson_id);
363
+ const normalizedTargetLibrary = normalizeString(entry && entry.target_library) || targetLibrary;
364
+ if (!capabilityId || !normalizedTargetLibrary) {
365
+ blocked.push({
366
+ capability_id: capabilityId || null,
367
+ lesson_id: lessonId || null,
368
+ target_library: normalizedTargetLibrary || null,
369
+ reason: 'missing-capability-or-library'
370
+ });
371
+ continue;
372
+ }
373
+
374
+ const entryRelativePath = resolveSharedLibraryEntryRelativePath(normalizedTargetLibrary, capabilityId);
375
+ const entryAbsolutePath = path.join(projectPath, entryRelativePath);
376
+ const existingEntry = await readJsonIfExists(fileSystem, entryAbsolutePath, null);
377
+ if (existingEntry && comparePublishedAt(existingEntry, entry) >= 0) {
378
+ skipped.push({
379
+ capability_id: capabilityId,
380
+ lesson_id: lessonId || null,
381
+ target_library: normalizedTargetLibrary,
382
+ reason: 'stale-or-duplicate'
383
+ });
384
+ continue;
385
+ }
386
+
387
+ const nextEntry = {
388
+ ...entry,
389
+ target_library: normalizedTargetLibrary,
390
+ intake: {
391
+ imported_at: generatedAt,
392
+ source_name: sourceName,
393
+ source_bundle: source,
394
+ source_root: sourceRoot || null,
395
+ source_shard: shardSource,
396
+ source_shard_file: shardFile || null
397
+ }
398
+ };
399
+
400
+ await fileSystem.ensureDir(path.dirname(entryAbsolutePath));
401
+ await fileSystem.writeJson(entryAbsolutePath, nextEntry, { spaces: 2 });
402
+ const indexPointers = await upsertSharedLibraryIndex(normalizedTargetLibrary, {
403
+ capability_id: capabilityId,
404
+ lesson_id: lessonId || null,
405
+ project_id: normalizeString(nextEntry.project_id) || null,
406
+ channel_id: normalizeString(nextEntry.channel_id) || null,
407
+ session_id: normalizeString(nextEntry.session_id) || null,
408
+ scene_id: normalizeString(nextEntry.scene_id) || null,
409
+ spec_id: normalizeString(nextEntry.spec_id) || null,
410
+ published_at: normalizeString(nextEntry.published_at) || generatedAt,
411
+ entry_file: entryRelativePath,
412
+ source_manifest: source,
413
+ receipt_id: normalizeString(nextEntry.receipt_id) || null
414
+ }, {
415
+ projectPath,
416
+ fileSystem
417
+ });
418
+
419
+ imported.push({
420
+ capability_id: capabilityId,
421
+ lesson_id: lessonId || null,
422
+ target_library: normalizedTargetLibrary,
423
+ entry_file: entryRelativePath,
424
+ index_file: indexPointers.library_index_file,
425
+ shard_source: shardSource
426
+ });
427
+ }
428
+ }
429
+ }
430
+
431
+ const payload = {
432
+ api_version: SEMANTIC_SHARED_SYNC_INTAKE_API_VERSION,
433
+ mode: 'semantic-sync-shared',
434
+ success: blocked.length === 0,
435
+ generated_at: generatedAt,
436
+ source,
437
+ source_name: sourceName,
438
+ source_root: sourceRoot || null,
439
+ release_state: releaseState,
440
+ required_release_state: requiredReleaseState || null,
441
+ release_state_ok: requiredReleaseState ? releaseState === requiredReleaseState : true,
442
+ spec_id: specId || null,
443
+ mirror_root: normalizeString(config.mirror_root) || SHARED_LIBRARY_ROOT,
444
+ totals: {
445
+ libraries: bundleLibraries.length,
446
+ imported: imported.length,
447
+ skipped: skipped.length,
448
+ blocked: blocked.length
449
+ },
450
+ imported,
451
+ skipped,
452
+ blocked
453
+ };
454
+
455
+ const outFile = normalizeString(options.outFile || options.out_file)
456
+ || resolveDefaultSemanticSharedSyncIntakeOutFile(specId);
457
+ if (outFile) {
458
+ const absoluteOutPath = path.isAbsolute(outFile)
459
+ ? outFile
460
+ : path.join(projectPath, outFile);
461
+ await fileSystem.ensureDir(path.dirname(absoluteOutPath));
462
+ await fileSystem.writeJson(absoluteOutPath, payload, { spaces: 2 });
463
+ payload.out_file = toProjectRelative(projectPath, absoluteOutPath);
464
+ }
465
+
466
+ return payload;
467
+ }
468
+
469
+ async function inspectSemanticSharedSource(options = {}, dependencies = {}) {
470
+ const projectPath = dependencies.projectPath || process.cwd();
471
+ const fileSystem = dependencies.fileSystem || fs;
472
+ const config = await readSemanticSharedSourceConfig(projectPath, {
473
+ config: options.config
474
+ }, fileSystem);
475
+ const warnings = [];
476
+ const errors = [];
477
+ if (normalizeBoolean(config.enabled, true) === false) {
478
+ warnings.push('semantic shared source config is disabled');
479
+ }
480
+
481
+ const configuredSource = resolveConfiguredSemanticSource(config, options.sourceName);
482
+ const source = normalizeString(options.source)
483
+ || normalizeString(configuredSource && configuredSource.bundle);
484
+ if (!source) {
485
+ errors.push('shared sync source is required');
486
+ return {
487
+ api_version: SEMANTIC_SHARED_SYNC_INTAKE_API_VERSION,
488
+ mode: 'semantic-health-shared-source',
489
+ success: false,
490
+ source: null,
491
+ source_name: normalizeString(options.sourceName) || null,
492
+ bundle_ok: false,
493
+ shard_sources_checked: 0,
494
+ shard_sources_ok: 0,
495
+ total_entries: 0,
496
+ warnings,
497
+ errors
498
+ };
499
+ }
500
+ const sourceName = normalizeString(options.sourceName)
501
+ || normalizeString(configuredSource && configuredSource.name)
502
+ || 'semantic-shared';
503
+ const sourceRoot = normalizeString(options.sourceRoot)
504
+ || normalizeString(configuredSource && configuredSource.root)
505
+ || inferSourceRootFromSource(source);
506
+ const requiredReleaseState = resolveRequiredReleaseState(options, configuredSource);
507
+
508
+ let bundlePayload = null;
509
+ let bundleOk = false;
510
+ let totalEntries = 0;
511
+ let releaseState = null;
512
+ let releaseStateOk = true;
513
+ const shardReports = [];
514
+ try {
515
+ bundlePayload = await loadJsonSource(projectPath, source, fileSystem);
516
+ if (!isObject(bundlePayload) || normalizeString(bundlePayload.api_version) !== SEMANTIC_SHARED_SYNC_BUNDLE_API_VERSION) {
517
+ errors.push(`invalid semantic shared sync bundle: ${source}`);
518
+ } else {
519
+ bundleOk = true;
520
+ releaseState = readBundleReleaseState(bundlePayload) || null;
521
+ if (requiredReleaseState && releaseState !== requiredReleaseState) {
522
+ releaseStateOk = false;
523
+ errors.push(`required release state ${requiredReleaseState} but got ${releaseState || 'unreleased'}`);
524
+ }
525
+ }
526
+ } catch (error) {
527
+ errors.push(`failed to load bundle (${source}): ${error.message}`);
528
+ }
529
+
530
+ if (bundleOk) {
531
+ const bundleLibraries = Array.isArray(bundlePayload.libraries) ? bundlePayload.libraries : [];
532
+ for (const library of bundleLibraries) {
533
+ const targetLibrary = normalizeString(library && library.target_library);
534
+ const shardFile = normalizeString(library && library.shard_file);
535
+ const shardSource = resolveShardSource(sourceRoot, shardFile, source);
536
+ const report = {
537
+ target_library: targetLibrary || null,
538
+ shard_file: shardFile || null,
539
+ shard_source: shardSource || null,
540
+ ok: false,
541
+ entries: 0,
542
+ error: null
543
+ };
544
+ if (!targetLibrary || !shardSource) {
545
+ report.error = 'missing-target-library-or-shard-source';
546
+ shardReports.push(report);
547
+ continue;
548
+ }
549
+ try {
550
+ const shardPayload = await loadJsonSource(projectPath, shardSource, fileSystem);
551
+ if (!isObject(shardPayload) || normalizeString(shardPayload.api_version) !== SEMANTIC_SHARED_SYNC_SHARD_API_VERSION) {
552
+ report.error = 'invalid-shard-payload';
553
+ } else {
554
+ report.ok = true;
555
+ report.entries = Array.isArray(shardPayload.entries) ? shardPayload.entries.length : 0;
556
+ totalEntries += report.entries;
557
+ }
558
+ } catch (error) {
559
+ report.error = error.message;
560
+ }
561
+ shardReports.push(report);
562
+ }
563
+ }
564
+
565
+ return {
566
+ api_version: SEMANTIC_SHARED_SYNC_INTAKE_API_VERSION,
567
+ mode: 'semantic-health-shared-source',
568
+ success: errors.length === 0 && shardReports.every((item) => item.ok),
569
+ source,
570
+ source_name: sourceName,
571
+ source_root: sourceRoot || null,
572
+ release_state: releaseState,
573
+ required_release_state: requiredReleaseState || null,
574
+ release_state_ok: releaseStateOk,
575
+ bundle_ok: bundleOk,
576
+ shard_sources_checked: shardReports.length,
577
+ shard_sources_ok: shardReports.filter((item) => item.ok).length,
578
+ total_entries: totalEntries,
579
+ warnings,
580
+ errors,
581
+ shards: shardReports
582
+ };
583
+ }
584
+
585
+ module.exports = {
586
+ SEMANTIC_SHARED_SOURCE_CONFIG,
587
+ SEMANTIC_SHARED_SYNC_INTAKE_API_VERSION,
588
+ resolveSemanticSharedSourceConfigPath,
589
+ resolveDefaultSemanticSharedSyncIntakeOutFile,
590
+ syncSemanticSharedBundle,
591
+ inspectSemanticSharedSource
592
+ };