@shapeshift-labs/frontier-lang-compiler 0.2.102 → 0.2.104
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/README.md +13 -0
- package/dist/declarations/bidirectional-target-change-source-edit.d.ts +30 -0
- package/dist/declarations/bidirectional-target-change.d.ts +10 -0
- package/dist/declarations/js-ts-safe-member-merge.d.ts +58 -0
- package/dist/declarations/js-ts-safe-merge.d.ts +120 -0
- package/dist/declarations/js-ts-semantic-conflict-sidecars.d.ts +235 -0
- package/dist/declarations/js-ts-semantic-merge-contracts.d.ts +287 -0
- package/dist/declarations/js-ts-semantic-merge.d.ts +4 -0
- package/dist/declarations/native-import-losses.d.ts +3 -0
- package/dist/declarations/native-project-admission-semantic-evidence.d.ts +34 -0
- package/dist/declarations/native-project-admission.d.ts +6 -10
- package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +12 -0
- package/dist/declarations/semantic-edit-script.d.ts +10 -4
- package/dist/declarations/semantic-patch-bundle-index.d.ts +45 -0
- package/dist/declarations/semantic-patch-bundle-overlaps.d.ts +1 -0
- package/dist/declarations/semantic-patch-bundle.d.ts +6 -4
- package/dist/declarations/semantic-sidecar-example.d.ts +18 -0
- package/dist/declarations/semantic-transform-identity.d.ts +3 -0
- package/dist/declarations/source-preservation.d.ts +72 -0
- package/dist/declarations/universal-capability.d.ts +4 -0
- package/dist/declarations/universal-conversion-artifacts.d.ts +61 -1
- package/dist/declarations/universal-conversion-compact-counts.d.ts +51 -0
- package/dist/declarations/universal-conversion-plan.d.ts +6 -1
- package/dist/declarations/universal-representation-coverage.d.ts +90 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/internal/index-impl/bidirectionalExactSourceBackprojection.js +199 -0
- package/dist/internal/index-impl/bidirectionalSameLanguageSourceProjection.js +112 -0
- package/dist/internal/index-impl/bidirectionalSourceEditProjection.js +319 -0
- package/dist/internal/index-impl/bidirectionalSourceEditProjectionArtifacts.js +67 -0
- package/dist/internal/index-impl/bidirectionalTargetChangeRecordInternals.js +17 -5
- package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +58 -20
- package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +60 -7
- package/dist/internal/index-impl/createLightweightNativeImport.js +1 -0
- package/dist/internal/index-impl/createNativeSourcePreservation.js +28 -2
- package/dist/internal/index-impl/createProjectImportAdmissionRecord.js +14 -2
- package/dist/internal/index-impl/diffNativeSymbols.js +82 -1
- package/dist/internal/index-impl/nativeChangeProjectionSourceMapLinks.js +2 -0
- package/dist/internal/index-impl/projectImportAdmissionImportEvidence.js +1 -1
- package/dist/internal/index-impl/projectImportAdmissionSemanticWarnings.js +178 -0
- package/dist/internal/index-impl/projectImportAdmissionSummaries.js +22 -3
- package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +54 -69
- package/dist/internal/index-impl/replaySemanticEditLineEndings.js +34 -0
- package/dist/internal/index-impl/replaySemanticEditProjection.js +78 -78
- package/dist/internal/index-impl/semanticEditBundleAdmission.js +7 -3
- package/dist/internal/index-impl/semanticEditBundleIndex.js +47 -1
- package/dist/internal/index-impl/semanticEditExplicitSourceReplacement.js +40 -0
- package/dist/internal/index-impl/semanticEditImportProjection.js +53 -0
- package/dist/internal/index-impl/semanticEditOperationCoverage.js +33 -3
- package/dist/internal/index-impl/semanticEditProjectionRecord.js +108 -0
- package/dist/internal/index-impl/semanticEditReplayAnchors.js +63 -0
- package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +39 -0
- package/dist/internal/index-impl/semanticEditReplaySourceReplacement.js +85 -0
- package/dist/internal/index-impl/semanticEditScripts.js +4 -0
- package/dist/internal/index-impl/semanticEditSourceRanges.js +32 -0
- package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +1 -0
- package/dist/internal/index-impl/semanticPatchBundleAdmission.js +92 -9
- package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +33 -16
- package/dist/internal/index-impl/semanticPatchBundleRecords.js +16 -0
- package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +2 -0
- package/dist/internal/index-impl/semanticSidecarQuality.js +111 -0
- package/dist/internal/index-impl/semanticSourceEditDedupe.js +69 -9
- package/dist/internal/index-impl/semanticTransformIdentityRecords.js +85 -9
- package/dist/js-ts-safe-member-merge-result.js +158 -0
- package/dist/js-ts-safe-member-merge.js +202 -0
- package/dist/js-ts-safe-merge-analyze.js +279 -0
- package/dist/js-ts-safe-merge-constants.js +50 -0
- package/dist/js-ts-safe-merge-context.js +118 -0
- package/dist/js-ts-safe-merge-ledger-validation.js +92 -0
- package/dist/js-ts-safe-merge-ledger.js +85 -0
- package/dist/js-ts-safe-merge-parse-declarations.js +210 -0
- package/dist/js-ts-safe-merge-parse-statements.js +155 -0
- package/dist/js-ts-safe-merge-plan.js +190 -0
- package/dist/js-ts-safe-merge.js +175 -0
- package/dist/js-ts-semantic-conflict-sidecar-constants.js +77 -0
- package/dist/js-ts-semantic-conflict-sidecar-detectors.js +195 -0
- package/dist/js-ts-semantic-conflict-sidecar-normalize.js +203 -0
- package/dist/js-ts-semantic-conflict-sidecar-utils.js +190 -0
- package/dist/js-ts-semantic-conflict-sidecars.js +81 -0
- package/dist/js-ts-semantic-merge-contract-helpers.js +128 -0
- package/dist/js-ts-semantic-merge-contracts.js +217 -0
- package/dist/js-ts-semantic-merge-member-containers.js +100 -0
- package/dist/js-ts-semantic-merge-member-keys.js +142 -0
- package/dist/js-ts-semantic-merge-member-segments.js +185 -0
- package/dist/js-ts-semantic-merge-member-source.js +64 -0
- package/dist/js-ts-semantic-merge-member-utils.js +18 -0
- package/dist/js-ts-semantic-merge-parse.js +15 -0
- package/dist/js-ts-semantic-merge.js +21 -0
- package/dist/lightweight-dependency-effects.js +51 -0
- package/dist/lightweight-dependency-language.js +12 -1
- package/dist/lightweight-dependency-relations.js +14 -27
- package/dist/native-region-scanner-core.js +33 -1
- package/dist/native-region-scanner-csharp.js +151 -0
- package/dist/native-region-scanner-dart.js +91 -0
- package/dist/native-region-scanner-dynamic.js +21 -151
- package/dist/native-region-scanner-functional.js +40 -13
- package/dist/native-region-scanner-java.js +97 -0
- package/dist/native-region-scanner-js-class.js +100 -0
- package/dist/native-region-scanner-js-helpers.js +28 -86
- package/dist/native-region-scanner-js-imports.js +121 -1
- package/dist/native-region-scanner-js-nested.js +96 -8
- package/dist/native-region-scanner-js-structure.js +27 -0
- package/dist/native-region-scanner-js-types.js +99 -0
- package/dist/native-region-scanner-js.js +70 -118
- package/dist/native-region-scanner-kotlin.js +94 -0
- package/dist/native-region-scanner-main.js +15 -181
- package/dist/native-region-scanner-php.js +80 -0
- package/dist/native-region-scanner-python.js +62 -0
- package/dist/native-region-scanner-ruby.js +72 -0
- package/dist/native-region-scanner-scala.js +91 -0
- package/dist/native-region-scanner-spans.js +74 -0
- package/dist/native-region-scanner-swift.js +155 -0
- package/dist/native-region-scanner.js +14 -10
- package/dist/native-source-ledger-helpers.js +195 -0
- package/dist/native-source-ledger.js +306 -0
- package/dist/native-source-preservation-scanner.js +4 -0
- package/dist/semantic-import-callsite-regions.js +136 -0
- package/dist/semantic-import-effect-regions.js +283 -0
- package/dist/semantic-import-regions.js +11 -2
- package/dist/semantic-import-sidecar-entry.js +16 -2
- package/dist/semantic-import-sidecar-types.d.ts +2 -0
- package/dist/semantic-sidecar-example.js +68 -0
- package/dist/universal-capability-matrix.js +23 -0
- package/dist/universal-conversion-artifact-query.js +79 -2
- package/dist/universal-conversion-artifact-semantic-edit.js +103 -0
- package/dist/universal-conversion-artifact-summary.js +33 -1
- package/dist/universal-conversion-artifacts.js +13 -48
- package/dist/universal-conversion-plan-scoring.js +21 -1
- package/dist/universal-conversion-plan-summary.js +30 -0
- package/dist/universal-conversion-plan.js +25 -9
- package/dist/universal-conversion-route-metadata.js +96 -0
- package/dist/universal-conversion-route-operations.js +7 -0
- package/dist/universal-representation-coverage.js +193 -0
- package/package.json +1 -1
|
@@ -3,7 +3,10 @@ import { idFragment, normalizeNativeLanguageId, uniqueStrings } from '../../nati
|
|
|
3
3
|
import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
|
|
4
4
|
import { mapDiffSymbols } from './mapDiffSymbols.js';
|
|
5
5
|
import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
|
|
6
|
-
import {
|
|
6
|
+
import { alreadyAppliedImportEditForOperation } from './semanticEditImportProjection.js';
|
|
7
|
+
import { explicitSourceReplacementEditForOperation } from './semanticEditExplicitSourceReplacement.js';
|
|
8
|
+
import { projectionEditRecord } from './semanticEditProjectionRecord.js';
|
|
9
|
+
import { findCurrentSymbol } from './semanticEditReplayAnchors.js';
|
|
7
10
|
import {
|
|
8
11
|
insertionOffset,
|
|
9
12
|
insertionReplacement,
|
|
@@ -22,7 +25,7 @@ export function projectSemanticEditScriptToSource(input = {}) {
|
|
|
22
25
|
if (typeof workerSourceText !== 'string') reasonCodes.push('missing-worker-source-text');
|
|
23
26
|
if (typeof headSourceText !== 'string') reasonCodes.push('missing-head-source-text');
|
|
24
27
|
const language = normalizeNativeLanguageId(script.language);
|
|
25
|
-
const headSymbols = typeof headSourceText === 'string' &&
|
|
28
|
+
const headSymbols = typeof headSourceText === 'string' && language
|
|
26
29
|
? sourceSymbolIndex({
|
|
27
30
|
sourceText: headSourceText,
|
|
28
31
|
sourcePath: input.headSourcePath ?? script.sourcePath,
|
|
@@ -40,7 +43,8 @@ export function projectSemanticEditScriptToSource(input = {}) {
|
|
|
40
43
|
}
|
|
41
44
|
const edit = sourceEditForOperation(operation, workerSourceText, headSourceText, index, {
|
|
42
45
|
headSourcePath: input.headSourcePath,
|
|
43
|
-
headSymbols
|
|
46
|
+
headSymbols,
|
|
47
|
+
symbolIndexAvailable: headSymbols.length > 0
|
|
44
48
|
});
|
|
45
49
|
if (edit.ok) edits.push(edit.value);
|
|
46
50
|
else reasonCodes.push(...edit.reasonCodes);
|
|
@@ -93,14 +97,16 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
|
|
|
93
97
|
return { ok: true, value: { ...identity, operationId: operation.id, order, start: 0, end: 0, replacement: '', current: '', alreadyApplied: true } };
|
|
94
98
|
}
|
|
95
99
|
if (operation.status !== 'portable') return { ok: false, reasonCodes: [`operation-not-portable:${operation.id}`] };
|
|
100
|
+
const explicit = explicitSourceReplacementEditForOperation(operation, identity, headSourceText, order);
|
|
101
|
+
if (explicit) return explicit;
|
|
96
102
|
if (operation.changeKind === 'added' || String(operation.kind ?? '').startsWith('add')) {
|
|
97
103
|
return insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order, context);
|
|
98
104
|
}
|
|
99
105
|
if (operation.changeKind === 'removed' || String(operation.kind ?? '').startsWith('remove')) {
|
|
100
|
-
return removalEditForOperation(operation, identity, headSourceText, order);
|
|
106
|
+
return removalEditForOperation(operation, identity, headSourceText, order, context);
|
|
101
107
|
}
|
|
102
108
|
const workerOffsets = spanOffsets(workerSourceText, operation.spans?.worker);
|
|
103
|
-
const headOffsets =
|
|
109
|
+
const headOffsets = headOffsetsForOperation(operation, identity, headSourceText, context);
|
|
104
110
|
const reasons = [];
|
|
105
111
|
if (!workerOffsets) reasons.push(`worker-span-not-resolvable:${operation.id}`);
|
|
106
112
|
if (!headOffsets) reasons.push(`head-span-not-resolvable:${operation.id}`);
|
|
@@ -116,9 +122,12 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
|
|
|
116
122
|
}
|
|
117
123
|
if (reasons.length) return { ok: false, reasonCodes: reasons };
|
|
118
124
|
const scoped = scopedBodyReplacement(operation, headSourceText, workerSourceText, headOffsets, workerOffsets);
|
|
119
|
-
const
|
|
125
|
+
const rawReplacement = scoped
|
|
120
126
|
? workerSourceText.slice(scoped.worker.start, scoped.worker.end)
|
|
121
127
|
: anchorReplacement;
|
|
128
|
+
const replacement = operation.metadata?.sourceBackprojection?.lineEndingStable
|
|
129
|
+
? normalizeReplacementLineEndings(rawReplacement, anchorCurrent)
|
|
130
|
+
: rawReplacement;
|
|
122
131
|
const current = scoped
|
|
123
132
|
? headSourceText.slice(scoped.head.start, scoped.head.end)
|
|
124
133
|
: anchorCurrent;
|
|
@@ -145,8 +154,8 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
|
|
|
145
154
|
}
|
|
146
155
|
};
|
|
147
156
|
}
|
|
148
|
-
function removalEditForOperation(operation, identity, headSourceText, order) {
|
|
149
|
-
const headOffsets =
|
|
157
|
+
function removalEditForOperation(operation, identity, headSourceText, order, context) {
|
|
158
|
+
const headOffsets = headOffsetsForOperation(operation, identity, headSourceText, context);
|
|
150
159
|
const reasons = [];
|
|
151
160
|
if (!headOffsets) reasons.push(`head-span-not-resolvable:${operation.id}`);
|
|
152
161
|
if (reasons.length) return { ok: false, reasonCodes: reasons };
|
|
@@ -171,18 +180,53 @@ function removalEditForOperation(operation, identity, headSourceText, order) {
|
|
|
171
180
|
}
|
|
172
181
|
};
|
|
173
182
|
}
|
|
183
|
+
|
|
184
|
+
function headOffsetsForOperation(operation, identity, headSourceText, context) {
|
|
185
|
+
const span = operation.spans?.head ?? operation.spans?.base ?? operation.anchor?.sourceSpan;
|
|
186
|
+
const spanRange = spanOffsets(headSourceText, span);
|
|
187
|
+
const symbol = context.symbolIndexAvailable ? findCurrentSymbol(identity, context.headSymbols) : undefined;
|
|
188
|
+
const symbolRange = spanOffsets(headSourceText, symbol?.sourceSpan);
|
|
189
|
+
if (!symbolRange) return spanRange;
|
|
190
|
+
if (!spanRange || sameRange(spanRange, symbolRange)) return symbolRange;
|
|
191
|
+
const expectedHash = operation.hashes?.headTextHash ?? operation.hashes?.baseTextHash;
|
|
192
|
+
if (expectedHash && rangeHash(headSourceText, symbolRange) === expectedHash) return symbolRange;
|
|
193
|
+
if (expectedHash && rangeHash(headSourceText, spanRange) === expectedHash) return spanRange;
|
|
194
|
+
return spanRange;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function rangeHash(sourceText, range) {
|
|
198
|
+
return range && typeof sourceText === 'string'
|
|
199
|
+
? hashSemanticValue(sourceText.slice(range.start, range.end))
|
|
200
|
+
: undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function sameRange(left, right) {
|
|
204
|
+
return left?.start === right?.start && left?.end === right?.end;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function normalizeReplacementLineEndings(replacement, current) {
|
|
208
|
+
const newline = current.includes('\r\n') ? '\r\n' : current.includes('\r') ? '\r' : '\n';
|
|
209
|
+
return String(replacement ?? '').replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n/g, newline);
|
|
210
|
+
}
|
|
211
|
+
|
|
174
212
|
function insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order, context) {
|
|
175
213
|
const workerOffsets = spanOffsets(workerSourceText, operation.spans?.worker);
|
|
176
214
|
const reasons = [];
|
|
177
215
|
if (!workerOffsets) reasons.push(`worker-span-not-resolvable:${operation.id}`);
|
|
178
|
-
const insertion = insertionOffset(headSourceText, operation.insertion, { symbols: context.headSymbols });
|
|
179
|
-
if (!insertion.ok) reasons.push(...insertion.reasonCodes.map((reason) => `${reason}:${operation.id}`));
|
|
180
216
|
if (reasons.length) return { ok: false, reasonCodes: reasons };
|
|
181
217
|
const spanText = workerSourceText.slice(workerOffsets.start, workerOffsets.end);
|
|
182
218
|
if (operation.hashes?.workerTextHash && hashSemanticValue(spanText) !== operation.hashes.workerTextHash) {
|
|
183
219
|
reasons.push(`worker-span-hash-mismatch:${operation.id}`);
|
|
184
220
|
}
|
|
185
221
|
if (reasons.length) return { ok: false, reasonCodes: reasons };
|
|
222
|
+
const alreadyAppliedImport = alreadyAppliedImportEditForOperation(operation, identity, spanText, headSourceText, workerOffsets, order, context);
|
|
223
|
+
if (alreadyAppliedImport) return { ok: true, value: alreadyAppliedImport };
|
|
224
|
+
const insertion = insertionOffset(headSourceText, operation.insertion, {
|
|
225
|
+
symbols: context.headSymbols,
|
|
226
|
+
symbolIndexAvailable: context.symbolIndexAvailable
|
|
227
|
+
});
|
|
228
|
+
if (!insertion.ok) reasons.push(...insertion.reasonCodes.map((reason) => `${reason}:${operation.id}`));
|
|
229
|
+
if (reasons.length) return { ok: false, reasonCodes: reasons };
|
|
186
230
|
return {
|
|
187
231
|
ok: true,
|
|
188
232
|
value: {
|
|
@@ -212,64 +256,6 @@ function projectionIdentity(operation, headSourcePath) {
|
|
|
212
256
|
: identity.targetSourcePath;
|
|
213
257
|
return { ...identity, sourcePath, originalSourcePath, targetSourcePath };
|
|
214
258
|
}
|
|
215
|
-
function projectionEditRecord(edit) {
|
|
216
|
-
const deletedTextHash = hashSemanticValue(edit.current);
|
|
217
|
-
const replacementTextHash = hashSemanticValue(edit.replacement);
|
|
218
|
-
const identity = semanticEditIdentityFields(edit);
|
|
219
|
-
return compactRecord({
|
|
220
|
-
operationId: edit.operationId,
|
|
221
|
-
status: edit.alreadyApplied ? 'already-applied' : 'applied',
|
|
222
|
-
kind: edit.kind,
|
|
223
|
-
editKind: edit.editKind,
|
|
224
|
-
changeKind: edit.changeKind,
|
|
225
|
-
anchorKey: edit.anchorKey,
|
|
226
|
-
conflictKey: edit.conflictKey,
|
|
227
|
-
regionId: edit.regionId,
|
|
228
|
-
regionKind: edit.regionKind,
|
|
229
|
-
sourcePath: edit.sourcePath,
|
|
230
|
-
originalSourcePath: edit.originalSourcePath,
|
|
231
|
-
targetAnchorKey: edit.targetAnchorKey,
|
|
232
|
-
targetSourcePath: edit.targetSourcePath,
|
|
233
|
-
targetSymbolName: edit.targetSymbolName,
|
|
234
|
-
targetSymbolKind: edit.targetSymbolKind,
|
|
235
|
-
symbolId: edit.symbolId,
|
|
236
|
-
symbolName: edit.symbolName,
|
|
237
|
-
symbolKind: edit.symbolKind,
|
|
238
|
-
...identity,
|
|
239
|
-
operationContentHash: edit.operationContentHash,
|
|
240
|
-
editContentHash: hashSemanticValue(compactRecord({
|
|
241
|
-
semanticIdentityHash: identity.semanticIdentityHash,
|
|
242
|
-
sourceRangeKind: edit.sourceRangeKind,
|
|
243
|
-
deletedTextHash,
|
|
244
|
-
replacementTextHash,
|
|
245
|
-
status: edit.alreadyApplied ? 'already-applied' : 'applied'
|
|
246
|
-
})),
|
|
247
|
-
sourceRangeKind: edit.sourceRangeKind,
|
|
248
|
-
headStart: edit.start,
|
|
249
|
-
headEnd: edit.end,
|
|
250
|
-
workerStart: edit.workerStart,
|
|
251
|
-
workerEnd: edit.workerEnd,
|
|
252
|
-
editOrder: edit.order,
|
|
253
|
-
headAnchorStart: edit.headAnchorStart,
|
|
254
|
-
headAnchorEnd: edit.headAnchorEnd,
|
|
255
|
-
workerAnchorStart: edit.workerAnchorStart,
|
|
256
|
-
workerAnchorEnd: edit.workerAnchorEnd,
|
|
257
|
-
deletedBytes: edit.current.length,
|
|
258
|
-
replacementBytes: edit.replacement.length,
|
|
259
|
-
deletedTextHash,
|
|
260
|
-
replacementTextHash,
|
|
261
|
-
anchorDeletedTextHash: edit.anchorDeletedTextHash,
|
|
262
|
-
anchorReplacementTextHash: edit.anchorReplacementTextHash,
|
|
263
|
-
replacementSpanTextHash: hashSemanticValue(edit.replacementSpanText ?? edit.replacement),
|
|
264
|
-
insertionMode: edit.insertion?.mode,
|
|
265
|
-
insertionAnchorKey: edit.insertion?.anchorKey,
|
|
266
|
-
insertionAnchorSymbolName: edit.insertion?.anchorSymbolName,
|
|
267
|
-
insertionAnchorSymbolKind: edit.insertion?.anchorSymbolKind,
|
|
268
|
-
insertionAnchorCandidates: edit.insertion?.anchorCandidates,
|
|
269
|
-
replacementText: edit.replacement
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
259
|
function sourceSymbolIndex(input) {
|
|
274
260
|
try {
|
|
275
261
|
const imported = normalizeNativeDiffImport({
|
|
@@ -313,7 +299,6 @@ function projectedSourcePath(script, edits) {
|
|
|
313
299
|
return edits.map((edit) => edit.sourcePath).find(Boolean) ?? script.sourcePath;
|
|
314
300
|
}
|
|
315
301
|
|
|
316
|
-
function isJavaScriptLike(language) { return language === 'javascript' || language === 'typescript'; }
|
|
317
302
|
function compactRecord(value) {
|
|
318
303
|
return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
|
|
319
304
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function replayReplacementText(edit, status, range, sourceText) {
|
|
2
|
+
const replacement = edit.replacementText;
|
|
3
|
+
if (status !== 'applied'
|
|
4
|
+
|| typeof replacement !== 'string'
|
|
5
|
+
|| !/[\r\n]/.test(replacement)
|
|
6
|
+
|| typeof sourceText !== 'string') return replacement;
|
|
7
|
+
return replacement.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n/g, replayLineEnding(sourceText, range));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function replayLineEnding(sourceText, range) {
|
|
11
|
+
const offset = Math.max(0, Math.min(sourceText.length, range?.start ?? 0));
|
|
12
|
+
return lineEndingInText(range ? sourceText.slice(range.start, range.end) : '')
|
|
13
|
+
?? nearbyLineEnding(sourceText, offset)
|
|
14
|
+
?? lineEndingInText(sourceText)
|
|
15
|
+
?? '\n';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function lineEndingInText(value) { return /\r\n|\r|\n/.exec(value)?.[0]; }
|
|
19
|
+
|
|
20
|
+
function nearbyLineEnding(sourceText, offset) {
|
|
21
|
+
for (let distance = 0; distance <= sourceText.length; distance += 1) {
|
|
22
|
+
const ending = lineEndingAt(sourceText, offset - distance - 1) ?? lineEndingAt(sourceText, offset + distance);
|
|
23
|
+
if (ending) return ending;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function lineEndingAt(sourceText, index) {
|
|
29
|
+
const char = sourceText[index];
|
|
30
|
+
if (char === '\n') return sourceText[index - 1] === '\r' ? '\r\n' : '\n';
|
|
31
|
+
return char === '\r' ? sourceText[index + 1] === '\n' ? '\r\n' : '\r' : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { replayReplacementText };
|
|
@@ -3,8 +3,17 @@ import { idFragment, normalizeNativeLanguageId, uniqueStrings } from '../../nati
|
|
|
3
3
|
import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
|
|
4
4
|
import { mapDiffSymbols } from './mapDiffSymbols.js';
|
|
5
5
|
import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
|
|
6
|
+
import { replayReplacementText } from './replaySemanticEditLineEndings.js';
|
|
6
7
|
import { replayDiagnostics, replayEditDiagnostics, replayEditsWithOverlapDiagnostics } from './semanticEditReplayDiagnostics.js';
|
|
7
|
-
import {
|
|
8
|
+
import { explicitSourceReplacementReplayRange } from './semanticEditReplaySourceReplacement.js';
|
|
9
|
+
import {
|
|
10
|
+
findCurrentSymbol,
|
|
11
|
+
findInsertionAnchor,
|
|
12
|
+
hasSymbolAnchorIdentity,
|
|
13
|
+
insertionAnchorCandidates,
|
|
14
|
+
insertionRange
|
|
15
|
+
} from './semanticEditReplayAnchors.js';
|
|
16
|
+
import { bodyContentRange, removalRange, spanOffsets } from './semanticEditSourceRanges.js';
|
|
8
17
|
|
|
9
18
|
export function replaySemanticEditProjection(input = {}) {
|
|
10
19
|
const projection = input.projection ?? input.semanticEditProjection;
|
|
@@ -15,11 +24,15 @@ export function replaySemanticEditProjection(input = {}) {
|
|
|
15
24
|
const reasonCodes = baseReasonCodes(projection, currentSourceText);
|
|
16
25
|
const currentHash = typeof currentSourceText === 'string' ? hashSemanticValue(currentSourceText) : undefined;
|
|
17
26
|
if (input.currentSourceHash && currentHash !== input.currentSourceHash) reasonCodes.push('current-source-hash-mismatch');
|
|
18
|
-
const currentSymbols = currentSourceText &&
|
|
27
|
+
const currentSymbols = currentSourceText && language
|
|
19
28
|
? currentSymbolIndex({ currentSourceText, sourcePath, language, parser: input.parser })
|
|
20
29
|
: [];
|
|
21
30
|
const replayedEdits = projection.status === 'projected' && typeof currentSourceText === 'string'
|
|
22
|
-
? (projection.edits ?? []).map((edit, index) => replayProjectionEdit(projectionEditWithOrder(edit, index), {
|
|
31
|
+
? (projection.edits ?? []).map((edit, index) => replayProjectionEdit(projectionEditWithOrder(edit, index), {
|
|
32
|
+
currentSourceText,
|
|
33
|
+
currentSymbols,
|
|
34
|
+
symbolIndexAvailable: currentSymbols.length > 0
|
|
35
|
+
}))
|
|
23
36
|
: [];
|
|
24
37
|
const edits = replayEditsWithOverlapDiagnostics(replayedEdits);
|
|
25
38
|
const status = replayStatus(reasonCodes, edits, projection);
|
|
@@ -69,23 +82,36 @@ function replayProjectionEdit(edit, context) {
|
|
|
69
82
|
const headRange = { start: edit.headStart, end: edit.headEnd };
|
|
70
83
|
const offset = checkRange(edit, headRange, context.currentSourceText, 'head-offset');
|
|
71
84
|
const symbol = findCurrentSymbol(edit, context.currentSymbols);
|
|
72
|
-
const
|
|
85
|
+
const symbolRange = spanOffsets(context.currentSourceText, symbol?.sourceSpan);
|
|
86
|
+
const explicitRange = explicitSourceReplacementReplayRange(edit, symbolRange, context.currentSourceText);
|
|
87
|
+
const spanRange = explicitRange?.range ?? currentSymbolEditRange(edit, symbolRange, context.currentSourceText);
|
|
88
|
+
const reanchorReason = explicitRange?.reasonCode ?? 'offset-reanchored-by-symbol';
|
|
89
|
+
const explicitConflictReasons = explicitRange?.conflictReasonCodes ?? [];
|
|
73
90
|
if (symbol && spanRange && !sameRange(headRange, spanRange)) {
|
|
74
91
|
const moved = checkRange(edit, spanRange, context.currentSourceText, currentSymbolRangeLabel(edit));
|
|
75
|
-
if (moved) return replayEditRecord(edit, moved.status, moved.range, [moved.reason,
|
|
92
|
+
if (moved) return replayEditRecord(edit, moved.status, replayAppliedRange(edit, moved.range, context.currentSourceText), [moved.reason, reanchorReason], context.currentSourceText);
|
|
93
|
+
if (offset && containedRange(headRange, spanRange)) {
|
|
94
|
+
return replayEditRecord(edit, offset.status, offset.range, [offset.reason, 'offset-contained-in-current-symbol'], context.currentSourceText);
|
|
95
|
+
}
|
|
76
96
|
if (edit.editKind === 'delete' && offset && rangesOverlap(headRange, spanRange)) {
|
|
77
97
|
return replayEditRecord(edit, offset.status, offset.range, [offset.reason], context.currentSourceText);
|
|
78
98
|
}
|
|
79
|
-
return replayEditRecord(edit, 'conflict', spanRange, [`${currentSymbolRangeLabel(edit)}-content-mismatch
|
|
99
|
+
return replayEditRecord(edit, 'conflict', spanRange, [`${currentSymbolRangeLabel(edit)}-content-mismatch`, ...explicitConflictReasons], context.currentSourceText);
|
|
80
100
|
}
|
|
81
101
|
if (offset) return replayEditRecord(edit, offset.status, offset.range, [offset.reason], context.currentSourceText);
|
|
82
102
|
const anchored = checkRange(edit, spanRange, context.currentSourceText, currentSymbolRangeLabel(edit));
|
|
83
|
-
if (anchored) return replayEditRecord(edit, anchored.status, anchored.range, [anchored.reason,
|
|
103
|
+
if (anchored) return replayEditRecord(edit, anchored.status, replayAppliedRange(edit, anchored.range, context.currentSourceText), [anchored.reason, reanchorReason], context.currentSourceText);
|
|
84
104
|
return replayEditRecord(edit, symbol ? 'conflict' : 'stale', spanRange, [
|
|
85
|
-
symbol ? `${currentSymbolRangeLabel(edit)}-content-mismatch` : 'current-symbol-anchor-missing'
|
|
105
|
+
symbol ? `${currentSymbolRangeLabel(edit)}-content-mismatch` : 'current-symbol-anchor-missing',
|
|
106
|
+
...explicitConflictReasons
|
|
86
107
|
], context.currentSourceText);
|
|
87
108
|
}
|
|
88
109
|
|
|
110
|
+
function replayAppliedRange(edit, range, sourceText) {
|
|
111
|
+
if (edit.editKind !== 'delete' || !range || typeof sourceText !== 'string') return range;
|
|
112
|
+
return removalRange(sourceText, range);
|
|
113
|
+
}
|
|
114
|
+
|
|
89
115
|
function replayInsertionEdit(edit, context) {
|
|
90
116
|
const inserted = findCurrentSymbol(edit, context.currentSymbols);
|
|
91
117
|
const insertedRange = spanOffsets(context.currentSourceText, inserted?.sourceSpan);
|
|
@@ -97,7 +123,8 @@ function replayInsertionEdit(edit, context) {
|
|
|
97
123
|
const anchor = findInsertionAnchor(edit, context.currentSymbols);
|
|
98
124
|
const range = insertionRange(edit, anchor?.candidate, anchor?.symbol, context.currentSourceText);
|
|
99
125
|
if (range) return replayEditRecord(edit, 'applied', range, [anchor ? 'current-insertion-anchor' : `current-${edit.insertionMode}`], context.currentSourceText);
|
|
100
|
-
|
|
126
|
+
const missingStableAnchor = context.symbolIndexAvailable && insertionAnchorCandidates(edit).some(hasSymbolAnchorIdentity);
|
|
127
|
+
return replayEditRecord(edit, anchor || missingStableAnchor ? 'conflict' : 'stale', undefined, [
|
|
101
128
|
anchor ? 'current-insertion-anchor-unusable' : 'current-insertion-anchor-missing'
|
|
102
129
|
], context.currentSourceText);
|
|
103
130
|
}
|
|
@@ -106,15 +133,32 @@ function checkRange(edit, range, sourceText, label) {
|
|
|
106
133
|
if (!range || range.end < range.start) return undefined;
|
|
107
134
|
const current = sourceText.slice(range.start, range.end);
|
|
108
135
|
const currentHash = hashSemanticValue(current);
|
|
136
|
+
const currentLineEndingStableText = lineEndingStableText(current);
|
|
137
|
+
const currentLineEndingStableHash = currentLineEndingStableText === undefined
|
|
138
|
+
? undefined
|
|
139
|
+
: hashSemanticValue(currentLineEndingStableText);
|
|
109
140
|
if (edit.replacementSpanTextHash && currentHash === edit.replacementSpanTextHash) return { status: 'already-applied', range, reason: `${label}-matches-replacement-span` };
|
|
110
141
|
if (edit.replacementTextHash && currentHash === edit.replacementTextHash) return { status: 'already-applied', range, reason: `${label}-matches-replacement` };
|
|
111
142
|
if (current === edit.replacementText) return { status: 'already-applied', range, reason: `${label}-matches-replacement-text` };
|
|
143
|
+
if (edit.replacementSpanTextLineEndingStableHash && currentLineEndingStableHash === edit.replacementSpanTextLineEndingStableHash) {
|
|
144
|
+
return { status: 'already-applied', range, reason: `${label}-matches-replacement-span-line-ending-stable` };
|
|
145
|
+
}
|
|
146
|
+
if (edit.replacementTextLineEndingStableHash && currentLineEndingStableHash === edit.replacementTextLineEndingStableHash) {
|
|
147
|
+
return { status: 'already-applied', range, reason: `${label}-matches-replacement-line-ending-stable` };
|
|
148
|
+
}
|
|
149
|
+
if (typeof edit.replacementText === 'string' && currentLineEndingStableText === lineEndingStableText(edit.replacementText)) {
|
|
150
|
+
return { status: 'already-applied', range, reason: `${label}-matches-replacement-text-line-ending-stable` };
|
|
151
|
+
}
|
|
112
152
|
if (edit.deletedTextHash && currentHash === edit.deletedTextHash) return { status: 'applied', range, reason: `${label}-matches-deleted` };
|
|
153
|
+
if (edit.deletedTextLineEndingStableHash && currentLineEndingStableHash === edit.deletedTextLineEndingStableHash) {
|
|
154
|
+
return { status: 'applied', range, reason: `${label}-matches-deleted-line-ending-stable` };
|
|
155
|
+
}
|
|
113
156
|
return undefined;
|
|
114
157
|
}
|
|
115
158
|
|
|
116
159
|
function replayEditRecord(edit, status, range, reasonCodes, sourceText) {
|
|
117
160
|
const normalizedReasonCodes = reasonList(reasonCodes);
|
|
161
|
+
const replacementText = replayReplacementText(edit, status, range, sourceText);
|
|
118
162
|
return compactRecord({
|
|
119
163
|
operationId: edit.operationId,
|
|
120
164
|
semanticKey: edit.semanticKey,
|
|
@@ -130,79 +174,25 @@ function replayEditRecord(edit, status, range, reasonCodes, sourceText) {
|
|
|
130
174
|
status,
|
|
131
175
|
start: range?.start,
|
|
132
176
|
end: range?.end,
|
|
133
|
-
replacementBytes: edit.replacementBytes,
|
|
134
|
-
replacementText
|
|
177
|
+
replacementBytes: typeof replacementText === 'string' ? replacementText.length : edit.replacementBytes,
|
|
178
|
+
replacementText,
|
|
135
179
|
reasonCodes: normalizedReasonCodes,
|
|
136
180
|
diagnostics: replayEditDiagnostics(edit, status, range, normalizedReasonCodes, sourceText)
|
|
137
181
|
});
|
|
138
182
|
}
|
|
139
183
|
|
|
140
184
|
function currentSymbolIndex(input) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const exact = symbols.find((symbol) => [symbol.ownershipKey, symbol.key, symbol.id].some((key) => key && [
|
|
152
|
-
edit.anchorKey,
|
|
153
|
-
edit.targetAnchorKey,
|
|
154
|
-
edit.symbolId
|
|
155
|
-
].includes(key)));
|
|
156
|
-
if (exact) return exact;
|
|
157
|
-
const name = edit.targetSymbolName ?? edit.symbolName;
|
|
158
|
-
const kind = edit.targetSymbolKind ?? edit.symbolKind;
|
|
159
|
-
return symbols.find((symbol) => symbol.name === name && (!kind || symbol.kind === kind));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function findInsertionAnchor(edit, symbols) {
|
|
163
|
-
for (const candidate of insertionAnchorCandidates(edit)) {
|
|
164
|
-
const symbol = findInsertionAnchorSymbol(candidate, symbols);
|
|
165
|
-
if (symbol) return { candidate, symbol };
|
|
185
|
+
try {
|
|
186
|
+
const imported = normalizeNativeDiffImport({
|
|
187
|
+
language: input.language,
|
|
188
|
+
sourcePath: input.sourcePath,
|
|
189
|
+
sourceText: input.currentSourceText,
|
|
190
|
+
parser: input.parser
|
|
191
|
+
}, input, 'current');
|
|
192
|
+
return [...mapDiffSymbols(imported, createSemanticImportSidecar(imported)).values()];
|
|
193
|
+
} catch {
|
|
194
|
+
return [];
|
|
166
195
|
}
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function findInsertionAnchorSymbol(candidate, symbols) {
|
|
171
|
-
const keys = [candidate.anchorKey, candidate.anchorSymbolId].filter(Boolean);
|
|
172
|
-
return symbols.find((symbol) => [symbol.ownershipKey, symbol.key, symbol.id].some((key) => key && keys.includes(key)))
|
|
173
|
-
?? symbols.find((symbol) => symbol.name === candidate.anchorSymbolName && (!candidate.anchorSymbolKind || symbol.kind === candidate.anchorSymbolKind));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function insertionAnchorCandidates(edit) {
|
|
177
|
-
const primary = {
|
|
178
|
-
mode: edit.insertionMode,
|
|
179
|
-
anchorKey: edit.insertionAnchorKey,
|
|
180
|
-
anchorSymbolName: edit.insertionAnchorSymbolName,
|
|
181
|
-
anchorSymbolKind: edit.insertionAnchorSymbolKind
|
|
182
|
-
};
|
|
183
|
-
const seen = new Set();
|
|
184
|
-
const result = [];
|
|
185
|
-
for (const candidate of [primary, ...(Array.isArray(edit.insertionAnchorCandidates) ? edit.insertionAnchorCandidates : [])]) {
|
|
186
|
-
if (!candidate || (candidate.mode !== 'before' && candidate.mode !== 'after')) continue;
|
|
187
|
-
const key = [candidate.mode, candidate.anchorKey, candidate.anchorSymbolId, candidate.anchorSymbolName, candidate.anchorSymbolKind].join('\0');
|
|
188
|
-
if (seen.has(key)) continue;
|
|
189
|
-
seen.add(key);
|
|
190
|
-
result.push(candidate);
|
|
191
|
-
}
|
|
192
|
-
return result;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function insertionRange(edit, candidate, anchor, sourceText) {
|
|
196
|
-
if (edit.insertionMode === 'file-start') return { start: 0, end: 0 };
|
|
197
|
-
if (edit.insertionMode === 'file-end') return { start: sourceText.length, end: sourceText.length };
|
|
198
|
-
const mode = candidate?.mode ?? edit.insertionMode;
|
|
199
|
-
const anchorRange = spanOffsets(sourceText, anchor?.sourceSpan);
|
|
200
|
-
if (!anchorRange) return undefined;
|
|
201
|
-
if (mode === 'before') return { start: anchorRange.start, end: anchorRange.start };
|
|
202
|
-
if (mode === 'after') {
|
|
203
|
-
return { start: afterLineOffset(sourceText, anchorRange.end), end: afterLineOffset(sourceText, anchorRange.end) };
|
|
204
|
-
}
|
|
205
|
-
return undefined;
|
|
206
196
|
}
|
|
207
197
|
|
|
208
198
|
function currentSymbolEditRange(edit, symbolRange, sourceText) {
|
|
@@ -212,6 +202,7 @@ function currentSymbolEditRange(edit, symbolRange, sourceText) {
|
|
|
212
202
|
}
|
|
213
203
|
|
|
214
204
|
function currentSymbolRangeLabel(edit) {
|
|
205
|
+
if (edit.sourceRangeKind === 'cross-language-explicit-source-replacement') return 'current-symbol-explicit-source-replacement';
|
|
215
206
|
return edit.sourceRangeKind === 'body-content' ? 'current-symbol-body' : 'current-symbol-anchor';
|
|
216
207
|
}
|
|
217
208
|
|
|
@@ -227,10 +218,11 @@ function replayStatus(reasonCodes, edits, projection) {
|
|
|
227
218
|
|
|
228
219
|
function replayAdmission(status, reasonCodes, edits) {
|
|
229
220
|
const apply = status === 'accepted-clean';
|
|
221
|
+
const skip = status === 'already-applied';
|
|
230
222
|
return {
|
|
231
223
|
status,
|
|
232
|
-
action: apply ? 'apply' :
|
|
233
|
-
reviewRequired: !apply,
|
|
224
|
+
action: apply ? 'apply' : skip ? 'skip' : status === 'stale' ? 'rerun-semantic-import' : status === 'blocked' ? 'block' : 'human-review',
|
|
225
|
+
reviewRequired: !(apply || skip),
|
|
234
226
|
autoApplyCandidate: apply,
|
|
235
227
|
autoMergeClaim: false,
|
|
236
228
|
semanticEquivalenceClaim: false,
|
|
@@ -294,6 +286,14 @@ function rangesOverlap(left, right) {
|
|
|
294
286
|
return Boolean(left && right && left.start < right.end && right.start < left.end);
|
|
295
287
|
}
|
|
296
288
|
|
|
297
|
-
function
|
|
289
|
+
function containedRange(inner, outer) {
|
|
290
|
+
return Boolean(inner && outer && outer.start <= inner.start && inner.end <= outer.end);
|
|
291
|
+
}
|
|
292
|
+
|
|
298
293
|
function reasonList(values) { return uniqueStrings((values ?? []).filter(Boolean)); }
|
|
294
|
+
function lineEndingStableText(value) {
|
|
295
|
+
if (typeof value !== 'string') return undefined;
|
|
296
|
+
const normalized = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
297
|
+
return normalized.length > 1 && normalized.endsWith('\n') ? normalized.slice(0, -1) : normalized;
|
|
298
|
+
}
|
|
299
299
|
function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0))); }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeSemanticMergeReadiness, uniqueStrings } from '../../native-import-utils.js';
|
|
2
|
+
import { summarizeSemanticSidecarQuality } from './semanticSidecarQuality.js';
|
|
2
3
|
|
|
3
4
|
export const SemanticEditBundleAdmissionStatuses = Object.freeze([
|
|
4
5
|
'none',
|
|
@@ -15,7 +16,7 @@ export function createSemanticEditBundleAdmission(input = {}, options = {}) {
|
|
|
15
16
|
const projections = array(input.semanticEditProjections ?? input.projections ?? input.semanticEditProjection);
|
|
16
17
|
const replays = array(input.semanticEditReplays ?? input.replays ?? input.semanticEditReplay);
|
|
17
18
|
const evidence = evidenceRecords(input, options);
|
|
18
|
-
const summary = summarizeSemanticEditBundle(scripts, projections, replays, evidence);
|
|
19
|
+
const summary = summarizeSemanticEditBundle(scripts, projections, replays, evidence, input, options);
|
|
19
20
|
const computedStatus = semanticEditBundleStatus(summary);
|
|
20
21
|
const status = safeStatus(input.status ?? options.status, computedStatus, summary);
|
|
21
22
|
const readiness = normalizeSemanticMergeReadiness(input.readiness ?? options.readiness ?? readinessForStatus(status))
|
|
@@ -45,7 +46,7 @@ export function createSemanticEditBundleAdmission(input = {}, options = {}) {
|
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
function summarizeSemanticEditBundle(scripts, projections, replays, evidence) {
|
|
49
|
+
function summarizeSemanticEditBundle(scripts, projections, replays, evidence, input, options) {
|
|
49
50
|
const scriptStatusEntries = scripts.map((script) => script.admission?.status);
|
|
50
51
|
const projectionStatusEntries = projections.flatMap((projection) => [projection.status, projection.admission?.status]);
|
|
51
52
|
const replayStatusEntries = replays.map((replay) => replay.status);
|
|
@@ -54,6 +55,7 @@ function summarizeSemanticEditBundle(scripts, projections, replays, evidence) {
|
|
|
54
55
|
const replayStatuses = uniqueStrings(strings(replayStatusEntries));
|
|
55
56
|
const replayActions = uniqueStrings(strings(replays.map((replay) => replay.admission?.action)));
|
|
56
57
|
const evidenceSummary = summarizeEvidence(evidence);
|
|
58
|
+
const sidecarQuality = summarizeSemanticSidecarQuality([input, options, ...scripts, ...projections, ...replays]);
|
|
57
59
|
return {
|
|
58
60
|
scripts: scripts.length,
|
|
59
61
|
projections: projections.length,
|
|
@@ -82,11 +84,13 @@ function summarizeSemanticEditBundle(scripts, projections, replays, evidence) {
|
|
|
82
84
|
failedTestEvidence: evidenceSummary.failed,
|
|
83
85
|
conflictEvidence: evidenceSummary.conflict,
|
|
84
86
|
staleEvidence: evidenceSummary.stale,
|
|
87
|
+
semanticSidecarQuality: sidecarQuality,
|
|
85
88
|
reasonCodes: uniqueStrings([
|
|
86
89
|
...scripts.flatMap((script) => strings(script.admission?.reasonCodes)),
|
|
87
90
|
...projections.flatMap((projection) => strings(projection.admission?.reasonCodes)),
|
|
88
91
|
...replays.flatMap((replay) => strings(replay.admission?.reasonCodes)),
|
|
89
|
-
...evidenceSummary.reasonCodes
|
|
92
|
+
...evidenceSummary.reasonCodes,
|
|
93
|
+
...sidecarQuality.warningCodes
|
|
90
94
|
])
|
|
91
95
|
};
|
|
92
96
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { uniqueStrings } from '../../native-import-utils.js';
|
|
2
|
+
import { summarizeSemanticSidecarQuality } from './semanticSidecarQuality.js';
|
|
2
3
|
|
|
3
4
|
export function semanticEditRecordIndex(scripts, projections, replays, source = {}) {
|
|
4
5
|
const operations = scripts.flatMap((script) => array(script.operations));
|
|
@@ -6,6 +7,8 @@ export function semanticEditRecordIndex(scripts, projections, replays, source =
|
|
|
6
7
|
const replayEdits = replays.flatMap((replay) => array(replay.edits));
|
|
7
8
|
const index = source.index ?? {};
|
|
8
9
|
const summary = source.metadata?.semanticEditSummary ?? {};
|
|
10
|
+
const sidecarQuality = summarizeSemanticSidecarQuality([source, ...scripts, ...projections, ...replays]);
|
|
11
|
+
const priorSidecarQuality = summary.semanticSidecarQuality ?? {};
|
|
9
12
|
return {
|
|
10
13
|
semanticEditScriptIds: uniqueStrings([...strings(source.semanticEditScriptIds), ...strings(source.semanticEditScriptId), ...strings(index.semanticEditScriptIds), ...strings(summary.scriptIds), ...scripts.map((script) => script.id), ...replays.map((replay) => replay.scriptId)]),
|
|
11
14
|
semanticEditProjectionIds: uniqueStrings([...strings(source.semanticEditProjectionIds), ...strings(source.semanticEditProjectionId), ...strings(index.semanticEditProjectionIds), ...strings(summary.projectionIds), ...projections.map((projection) => projection.id), ...replays.map((replay) => replay.projectionId)]),
|
|
@@ -15,8 +18,10 @@ export function semanticEditRecordIndex(scripts, projections, replays, source =
|
|
|
15
18
|
semanticEditReplayEditCount: replayEdits.length,
|
|
16
19
|
semanticEditReplayStatuses: uniqueStrings([...strings(source.semanticEditReplayStatuses), ...strings(index.semanticEditReplayStatuses), ...strings(summary.replayStatuses), ...replays.map((replay) => replay.status)]),
|
|
17
20
|
semanticEditReplayActions: uniqueStrings([...strings(source.semanticEditReplayActions), ...strings(index.semanticEditReplayActions), ...strings(summary.replayActions), ...replays.map((replay) => replay.admission?.action)]),
|
|
21
|
+
semanticEditReplayReasonCodes: uniqueStrings([...strings(source.semanticEditReplayReasonCodes), ...strings(index.semanticEditReplayReasonCodes), ...strings(summary.replayReasonCodes), ...replays.flatMap(replayReasonCodes)]),
|
|
18
22
|
semanticEditReplayCurrentHashes: uniqueStrings([...strings(source.semanticEditReplayCurrentHashes), ...strings(index.semanticEditReplayCurrentHashes), ...strings(summary.replayCurrentHashes), ...strings(summary.semanticEditReplayCurrentHashes), ...replays.map((replay) => replay.currentHash)]),
|
|
19
23
|
semanticEditReplayOutputHashes: uniqueStrings([...strings(source.semanticEditReplayOutputHashes), ...strings(index.semanticEditReplayOutputHashes), ...strings(summary.replayOutputHashes), ...strings(summary.semanticEditReplayOutputHashes), ...replays.map((replay) => replay.outputHash)]),
|
|
24
|
+
sourceBackprojectionModes: uniqueStrings([...strings(source.sourceBackprojectionModes), ...strings(index.sourceBackprojectionModes), ...strings(summary.sourceBackprojectionModes), ...scripts.flatMap(scriptBackprojectionModes), ...projections.map((projection) => projection.metadata?.sourceBackprojectionMode), ...replays.map((replay) => replay.metadata?.sourceBackprojectionMode)]),
|
|
20
25
|
semanticEditKeys: uniqueStrings([...strings(source.semanticEditKeys), ...strings(index.semanticEditKeys), ...strings(summary.semanticEditKeys), ...operations.map((operation) => operation.semanticKey), ...edits.map((edit) => edit.semanticKey), ...replayEdits.map((edit) => edit.semanticKey)]),
|
|
21
26
|
semanticIdentityHashes: uniqueStrings([...strings(source.semanticIdentityHashes), ...strings(index.semanticIdentityHashes), ...strings(summary.semanticIdentityHashes), ...operations.map((operation) => operation.semanticIdentityHash), ...edits.map((edit) => edit.semanticIdentityHash), ...replayEdits.map((edit) => edit.semanticIdentityHash)]),
|
|
22
27
|
sourceIdentityHashes: uniqueStrings([...strings(source.sourceIdentityHashes), ...strings(index.sourceIdentityHashes), ...strings(summary.sourceIdentityHashes), ...operations.map((operation) => operation.sourceIdentityHash), ...edits.map((edit) => edit.sourceIdentityHash), ...replayEdits.map((edit) => edit.sourceIdentityHash)]),
|
|
@@ -24,7 +29,15 @@ export function semanticEditRecordIndex(scripts, projections, replays, source =
|
|
|
24
29
|
editContentHashes: uniqueStrings([...strings(source.editContentHashes), ...strings(index.editContentHashes), ...strings(summary.editContentHashes), ...edits.map((edit) => edit.editContentHash), ...replayEdits.map((edit) => edit.editContentHash)]),
|
|
25
30
|
anchorKeys: uniqueStrings([...strings(source.anchorKeys), ...strings(index.anchorKeys), ...strings(summary.anchorKeys), ...operations.map((operation) => operation.anchor?.key), ...edits.map((edit) => edit.anchorKey), ...replayEdits.map((edit) => edit.anchorKey)]),
|
|
26
31
|
conflictKeys: uniqueStrings([...strings(source.conflictKeys), ...strings(index.conflictKeys), ...strings(summary.conflictKeys), ...operations.map((operation) => operation.anchor?.conflictKey), ...edits.map((edit) => edit.conflictKey), ...replayEdits.map((edit) => edit.conflictKey)]),
|
|
27
|
-
projectedSourcePaths: uniqueStrings([...strings(source.projectedSourcePaths), ...strings(index.projectedSourcePaths), ...strings(summary.projectedSourcePaths), ...projections.map((projection) => projection.sourcePath), ...edits.flatMap((edit) => [edit.sourcePath, edit.targetSourcePath]), ...replays.map((replay) => replay.sourcePath), ...replayEdits.map((edit) => edit.sourcePath)])
|
|
32
|
+
projectedSourcePaths: uniqueStrings([...strings(source.projectedSourcePaths), ...strings(index.projectedSourcePaths), ...strings(summary.projectedSourcePaths), ...projections.map((projection) => projection.sourcePath), ...edits.flatMap((edit) => [edit.sourcePath, edit.targetSourcePath]), ...replays.map((replay) => replay.sourcePath), ...replayEdits.map((edit) => edit.sourcePath)]),
|
|
33
|
+
semanticEditSidecarQualityRecords: maxCount(sidecarQuality.records, source.semanticEditSidecarQualityRecords, index.semanticEditSidecarQualityRecords, priorSidecarQuality.records),
|
|
34
|
+
semanticEditSidecarSymbolCount: maxCount(sidecarQuality.symbols, source.semanticEditSidecarSymbolCount, index.semanticEditSidecarSymbolCount, priorSidecarQuality.symbols),
|
|
35
|
+
semanticEditSidecarOwnershipRegionCount: maxCount(sidecarQuality.ownershipRegions, source.semanticEditSidecarOwnershipRegionCount, index.semanticEditSidecarOwnershipRegionCount, priorSidecarQuality.ownershipRegions),
|
|
36
|
+
semanticEditSidecarPatchHintCount: maxCount(sidecarQuality.patchHints, source.semanticEditSidecarPatchHintCount, index.semanticEditSidecarPatchHintCount, priorSidecarQuality.patchHints),
|
|
37
|
+
semanticEditSidecarWarningCount: maxCount(sidecarQuality.warnings, source.semanticEditSidecarWarningCount, index.semanticEditSidecarWarningCount, priorSidecarQuality.warnings),
|
|
38
|
+
semanticEditSidecarZeroRecordWarningCount: maxCount(sidecarQuality.zeroRecordWarnings, source.semanticEditSidecarZeroRecordWarningCount, index.semanticEditSidecarZeroRecordWarningCount, priorSidecarQuality.zeroRecordWarnings),
|
|
39
|
+
semanticEditSidecarWarningCodes: uniqueStrings([...strings(source.semanticEditSidecarWarningCodes), ...strings(index.semanticEditSidecarWarningCodes), ...strings(priorSidecarQuality.warningCodes), ...sidecarQuality.warningCodes]),
|
|
40
|
+
semanticEditSidecarZeroRecordWarningCodes: uniqueStrings([...strings(source.semanticEditSidecarZeroRecordWarningCodes), ...strings(index.semanticEditSidecarZeroRecordWarningCodes), ...strings(priorSidecarQuality.zeroRecordWarningCodes), ...sidecarQuality.zeroRecordWarningCodes])
|
|
28
41
|
};
|
|
29
42
|
}
|
|
30
43
|
|
|
@@ -36,8 +49,10 @@ export function semanticEditSummary(index) {
|
|
|
36
49
|
replayIds: index.semanticEditReplayIds,
|
|
37
50
|
replayStatuses: index.semanticEditReplayStatuses,
|
|
38
51
|
replayActions: index.semanticEditReplayActions,
|
|
52
|
+
replayReasonCodes: index.semanticEditReplayReasonCodes,
|
|
39
53
|
replayCurrentHashes: index.semanticEditReplayCurrentHashes,
|
|
40
54
|
replayOutputHashes: index.semanticEditReplayOutputHashes,
|
|
55
|
+
sourceBackprojectionModes: index.sourceBackprojectionModes,
|
|
41
56
|
semanticEditKeys: index.semanticEditKeys,
|
|
42
57
|
semanticIdentityHashes: index.semanticIdentityHashes,
|
|
43
58
|
sourceIdentityHashes: index.sourceIdentityHashes,
|
|
@@ -46,6 +61,16 @@ export function semanticEditSummary(index) {
|
|
|
46
61
|
anchorKeys: index.anchorKeys,
|
|
47
62
|
conflictKeys: index.conflictKeys,
|
|
48
63
|
projectedSourcePaths: index.projectedSourcePaths,
|
|
64
|
+
semanticSidecarQuality: {
|
|
65
|
+
records: index.semanticEditSidecarQualityRecords,
|
|
66
|
+
symbols: index.semanticEditSidecarSymbolCount,
|
|
67
|
+
ownershipRegions: index.semanticEditSidecarOwnershipRegionCount,
|
|
68
|
+
patchHints: index.semanticEditSidecarPatchHintCount,
|
|
69
|
+
warnings: index.semanticEditSidecarWarningCount,
|
|
70
|
+
zeroRecordWarnings: index.semanticEditSidecarZeroRecordWarningCount,
|
|
71
|
+
warningCodes: index.semanticEditSidecarWarningCodes,
|
|
72
|
+
zeroRecordWarningCodes: index.semanticEditSidecarZeroRecordWarningCodes
|
|
73
|
+
},
|
|
49
74
|
replayEditCount: index.semanticEditReplayEditCount
|
|
50
75
|
});
|
|
51
76
|
}
|
|
@@ -57,6 +82,27 @@ function replayOperationIds(replays) {
|
|
|
57
82
|
...array(replay.edits).map((edit) => edit.operationId)
|
|
58
83
|
]);
|
|
59
84
|
}
|
|
85
|
+
function replayReasonCodes(replay) {
|
|
86
|
+
return [
|
|
87
|
+
...array(replay.admission?.reasonCodes),
|
|
88
|
+
...array(replay.summary?.reasonCodes),
|
|
89
|
+
...array(replay.diagnostics).flatMap((diagnostic) => array(diagnostic.reasonCodes)),
|
|
90
|
+
...array(replay.edits).flatMap((edit) => array(edit.reasonCodes))
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
function scriptBackprojectionModes(script) {
|
|
94
|
+
return [
|
|
95
|
+
script.metadata?.sourceBackprojectionMode,
|
|
96
|
+
script.metadata?.sourceProjectionHint?.sourceBackprojectionMode,
|
|
97
|
+
...array(script.operations).map((operation) => operation.metadata?.sourceBackprojection?.mode)
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
function maxCount(...values) {
|
|
101
|
+
return Math.max(0, ...values.map((value) => {
|
|
102
|
+
const count = Number(value ?? 0);
|
|
103
|
+
return Number.isFinite(count) ? count : 0;
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
60
106
|
function array(value) { if (value === undefined || value === null) return []; return Array.isArray(value) ? value : [value]; }
|
|
61
107
|
function strings(value) { return array(value).map((entry) => String(entry ?? '')).filter(Boolean); }
|
|
62
108
|
function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0))); }
|