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.
- package/CHANGELOG.md +26 -0
- package/README.md +17 -6
- package/README.zh.md +18 -6
- package/bin/scene-capability-engine.js +4 -0
- package/docs/README.md +2 -2
- package/docs/command-reference.md +385 -8
- package/docs/document-governance.md +3 -2
- package/docs/integration-modes.md +62 -478
- package/docs/integration-philosophy.md +56 -263
- package/docs/magicball-cli-invocation-examples.md +1 -0
- package/docs/magicball-project-portfolio-contract.md +125 -4
- package/docs/project-management/README.md +14 -0
- package/docs/project-management/assurance/backup.md +3 -0
- package/docs/project-management/assurance/config.md +3 -0
- package/docs/project-management/assurance/evidence/README.md +3 -0
- package/docs/project-management/assurance/incidents/README.md +3 -0
- package/docs/project-management/assurance/logs.md +3 -0
- package/docs/project-management/assurance/overview.md +3 -0
- package/docs/project-management/assurance/recovery/README.md +3 -0
- package/docs/project-management/assurance/resource.md +3 -0
- package/docs/project-management/assurance/runbooks/README.md +3 -0
- package/docs/project-management/delivery/acceptance/README.md +3 -0
- package/docs/project-management/delivery/acceptance/evidence/README.md +3 -0
- package/docs/project-management/delivery/acceptance/exceptions/README.md +3 -0
- package/docs/project-management/delivery/acceptance/reports/README.md +3 -0
- package/docs/project-management/delivery/documents/changes.md +3 -0
- package/docs/project-management/delivery/documents/issues.md +3 -0
- package/docs/project-management/delivery/documents/overview.md +3 -0
- package/docs/project-management/delivery/documents/planning.md +3 -0
- package/docs/project-management/delivery/documents/requirements.md +3 -0
- package/docs/project-management/delivery/documents/tracking.md +3 -0
- package/docs/project-management/delivery/handoffs/README.md +3 -0
- package/docs/project-management/delivery/handoffs/evidence/README.md +3 -0
- package/docs/project-management/delivery/handoffs/records/README.md +3 -0
- package/docs/project-management/delivery/overview.md +10 -0
- package/docs/project-management/delivery/releases/README.md +3 -0
- package/docs/project-management/delivery/releases/baselines/README.md +3 -0
- package/docs/project-management/delivery/releases/evidence/README.md +3 -0
- package/docs/project-management/delivery/tables/changes.md +3 -0
- package/docs/project-management/delivery/tables/issues.md +3 -0
- package/docs/project-management/delivery/tables/planning.md +3 -0
- package/docs/project-management/delivery/tables/requirements.md +3 -0
- package/docs/project-management/delivery/tables/tracking.md +3 -0
- package/docs/project-management/environment/agent-discovery.md +3 -0
- package/docs/project-management/environment/development.md +3 -0
- package/docs/project-management/environment/overview.md +10 -0
- package/docs/project-management/environment/testing.md +3 -0
- package/docs/project-management/environment/version-alignment.md +3 -0
- package/docs/quick-start-with-ai-tools.md +68 -308
- package/docs/releases/README.md +3 -0
- package/docs/releases/v3.6.65.md +25 -0
- package/docs/releases/v3.6.66.md +23 -0
- package/docs/releases/v3.6.67.md +23 -0
- package/docs/steering-governance.md +64 -2
- package/docs/zh/README.md +2 -2
- package/docs/zh/releases/README.md +3 -0
- package/docs/zh/releases/v3.6.65.md +25 -0
- package/docs/zh/releases/v3.6.66.md +23 -0
- package/docs/zh/releases/v3.6.67.md +23 -0
- package/lib/commands/adopt.js +24 -0
- package/lib/commands/native.js +158 -0
- package/lib/commands/project.js +96 -0
- package/lib/commands/semantic.js +1459 -0
- package/lib/commands/session.js +74 -3
- package/lib/commands/spec-bootstrap.js +10 -1
- package/lib/commands/spec-gate.js +10 -1
- package/lib/commands/spec-pipeline.js +10 -1
- package/lib/commands/studio.js +405 -30
- package/lib/commands/task.js +141 -7
- package/lib/governance/supreme-principles.js +530 -0
- package/lib/problem/problem-evaluator.js +4 -0
- package/lib/project/candidate-inspection-service.js +24 -1
- package/lib/project/portfolio-projection-service.js +315 -5
- package/lib/project/project-channel-output.js +94 -0
- package/lib/project/project-channel-projection.js +181 -0
- package/lib/project/root-onboarding-service.js +107 -7
- package/lib/project/semantic-shared-source-projection.js +150 -0
- package/lib/project/supervision-action-model.js +277 -0
- package/lib/project/supervision-projection-service.js +305 -5
- package/lib/project/target-resolution-service.js +70 -5
- package/lib/project/visibility-policy.js +93 -0
- package/lib/runtime/multi-spec-scene-session.js +8 -1
- package/lib/runtime/project-channel-context-store.js +387 -0
- package/lib/runtime/project-channel-context.js +406 -0
- package/lib/runtime/scene-session-binding.js +46 -0
- package/lib/runtime/session-store.js +186 -0
- package/lib/runtime/steering-contract.js +7 -1
- package/lib/semantic/archive-report.js +283 -0
- package/lib/semantic/archive-routing.js +67 -0
- package/lib/semantic/backflow-report.js +245 -0
- package/lib/semantic/capability-contract.js +30 -0
- package/lib/semantic/delta-export.js +145 -0
- package/lib/semantic/interaction-observer.js +254 -0
- package/lib/semantic/kernel-loader.js +881 -0
- package/lib/semantic/native-runtime.js +359 -0
- package/lib/semantic/progress-ledger.js +433 -0
- package/lib/semantic/replay-evaluator.js +382 -0
- package/lib/semantic/shared-publication.js +592 -0
- package/lib/semantic/shared-source-config.js +183 -0
- package/lib/semantic/shared-source-connect.js +139 -0
- package/lib/semantic/shared-source-discovery.js +98 -0
- package/lib/semantic/shared-sync-export.js +413 -0
- package/lib/semantic/shared-sync-intake.js +592 -0
- package/lib/semantic/shared-sync-merge.js +547 -0
- package/lib/semantic/shared-sync-release.js +463 -0
- package/lib/semantic/supreme-intent-report.js +300 -0
- package/lib/state/sce-state-store.js +1360 -0
- package/lib/steering/context-sync-manager.js +276 -25
- package/lib/studio/spec-intake-governor.js +39 -3
- package/lib/studio/task-envelope.js +35 -2
- package/lib/workspace/takeover-baseline.js +342 -83
- package/package.json +7 -2
- package/scripts/agent-governance-baseline-audit.js +395 -0
- package/scripts/clarification-first-audit.js +9 -9
- package/scripts/deprecated-entry-audit.js +240 -0
- package/scripts/release-doc-version-audit.js +24 -0
- package/scripts/release-posture-report.js +262 -0
- package/template/.sce/README.md +62 -228
- package/template/.sce/config/semantic-shared-sources.json +5 -0
- package/template/.sce/config/supreme-principles-policy.json +105 -0
- package/template/.sce/config/takeover-baseline.json +7 -0
- package/template/.sce/steering/CORE_PRINCIPLES.md +23 -63
- package/template/.sce/steering/CURRENT_CONTEXT.md +4 -0
- package/template/.sce/steering/RULES_GUIDE.md +17 -9
- 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
|
+
};
|