@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.
Files changed (134) hide show
  1. package/README.md +13 -0
  2. package/dist/declarations/bidirectional-target-change-source-edit.d.ts +30 -0
  3. package/dist/declarations/bidirectional-target-change.d.ts +10 -0
  4. package/dist/declarations/js-ts-safe-member-merge.d.ts +58 -0
  5. package/dist/declarations/js-ts-safe-merge.d.ts +120 -0
  6. package/dist/declarations/js-ts-semantic-conflict-sidecars.d.ts +235 -0
  7. package/dist/declarations/js-ts-semantic-merge-contracts.d.ts +287 -0
  8. package/dist/declarations/js-ts-semantic-merge.d.ts +4 -0
  9. package/dist/declarations/native-import-losses.d.ts +3 -0
  10. package/dist/declarations/native-project-admission-semantic-evidence.d.ts +34 -0
  11. package/dist/declarations/native-project-admission.d.ts +6 -10
  12. package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +12 -0
  13. package/dist/declarations/semantic-edit-script.d.ts +10 -4
  14. package/dist/declarations/semantic-patch-bundle-index.d.ts +45 -0
  15. package/dist/declarations/semantic-patch-bundle-overlaps.d.ts +1 -0
  16. package/dist/declarations/semantic-patch-bundle.d.ts +6 -4
  17. package/dist/declarations/semantic-sidecar-example.d.ts +18 -0
  18. package/dist/declarations/semantic-transform-identity.d.ts +3 -0
  19. package/dist/declarations/source-preservation.d.ts +72 -0
  20. package/dist/declarations/universal-capability.d.ts +4 -0
  21. package/dist/declarations/universal-conversion-artifacts.d.ts +61 -1
  22. package/dist/declarations/universal-conversion-compact-counts.d.ts +51 -0
  23. package/dist/declarations/universal-conversion-plan.d.ts +6 -1
  24. package/dist/declarations/universal-representation-coverage.d.ts +90 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.js +3 -0
  27. package/dist/internal/index-impl/bidirectionalExactSourceBackprojection.js +199 -0
  28. package/dist/internal/index-impl/bidirectionalSameLanguageSourceProjection.js +112 -0
  29. package/dist/internal/index-impl/bidirectionalSourceEditProjection.js +319 -0
  30. package/dist/internal/index-impl/bidirectionalSourceEditProjectionArtifacts.js +67 -0
  31. package/dist/internal/index-impl/bidirectionalTargetChangeRecordInternals.js +17 -5
  32. package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +58 -20
  33. package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +60 -7
  34. package/dist/internal/index-impl/createLightweightNativeImport.js +1 -0
  35. package/dist/internal/index-impl/createNativeSourcePreservation.js +28 -2
  36. package/dist/internal/index-impl/createProjectImportAdmissionRecord.js +14 -2
  37. package/dist/internal/index-impl/diffNativeSymbols.js +82 -1
  38. package/dist/internal/index-impl/nativeChangeProjectionSourceMapLinks.js +2 -0
  39. package/dist/internal/index-impl/projectImportAdmissionImportEvidence.js +1 -1
  40. package/dist/internal/index-impl/projectImportAdmissionSemanticWarnings.js +178 -0
  41. package/dist/internal/index-impl/projectImportAdmissionSummaries.js +22 -3
  42. package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +54 -69
  43. package/dist/internal/index-impl/replaySemanticEditLineEndings.js +34 -0
  44. package/dist/internal/index-impl/replaySemanticEditProjection.js +78 -78
  45. package/dist/internal/index-impl/semanticEditBundleAdmission.js +7 -3
  46. package/dist/internal/index-impl/semanticEditBundleIndex.js +47 -1
  47. package/dist/internal/index-impl/semanticEditExplicitSourceReplacement.js +40 -0
  48. package/dist/internal/index-impl/semanticEditImportProjection.js +53 -0
  49. package/dist/internal/index-impl/semanticEditOperationCoverage.js +33 -3
  50. package/dist/internal/index-impl/semanticEditProjectionRecord.js +108 -0
  51. package/dist/internal/index-impl/semanticEditReplayAnchors.js +63 -0
  52. package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +39 -0
  53. package/dist/internal/index-impl/semanticEditReplaySourceReplacement.js +85 -0
  54. package/dist/internal/index-impl/semanticEditScripts.js +4 -0
  55. package/dist/internal/index-impl/semanticEditSourceRanges.js +32 -0
  56. package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +1 -0
  57. package/dist/internal/index-impl/semanticPatchBundleAdmission.js +92 -9
  58. package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +33 -16
  59. package/dist/internal/index-impl/semanticPatchBundleRecords.js +16 -0
  60. package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +2 -0
  61. package/dist/internal/index-impl/semanticSidecarQuality.js +111 -0
  62. package/dist/internal/index-impl/semanticSourceEditDedupe.js +69 -9
  63. package/dist/internal/index-impl/semanticTransformIdentityRecords.js +85 -9
  64. package/dist/js-ts-safe-member-merge-result.js +158 -0
  65. package/dist/js-ts-safe-member-merge.js +202 -0
  66. package/dist/js-ts-safe-merge-analyze.js +279 -0
  67. package/dist/js-ts-safe-merge-constants.js +50 -0
  68. package/dist/js-ts-safe-merge-context.js +118 -0
  69. package/dist/js-ts-safe-merge-ledger-validation.js +92 -0
  70. package/dist/js-ts-safe-merge-ledger.js +85 -0
  71. package/dist/js-ts-safe-merge-parse-declarations.js +210 -0
  72. package/dist/js-ts-safe-merge-parse-statements.js +155 -0
  73. package/dist/js-ts-safe-merge-plan.js +190 -0
  74. package/dist/js-ts-safe-merge.js +175 -0
  75. package/dist/js-ts-semantic-conflict-sidecar-constants.js +77 -0
  76. package/dist/js-ts-semantic-conflict-sidecar-detectors.js +195 -0
  77. package/dist/js-ts-semantic-conflict-sidecar-normalize.js +203 -0
  78. package/dist/js-ts-semantic-conflict-sidecar-utils.js +190 -0
  79. package/dist/js-ts-semantic-conflict-sidecars.js +81 -0
  80. package/dist/js-ts-semantic-merge-contract-helpers.js +128 -0
  81. package/dist/js-ts-semantic-merge-contracts.js +217 -0
  82. package/dist/js-ts-semantic-merge-member-containers.js +100 -0
  83. package/dist/js-ts-semantic-merge-member-keys.js +142 -0
  84. package/dist/js-ts-semantic-merge-member-segments.js +185 -0
  85. package/dist/js-ts-semantic-merge-member-source.js +64 -0
  86. package/dist/js-ts-semantic-merge-member-utils.js +18 -0
  87. package/dist/js-ts-semantic-merge-parse.js +15 -0
  88. package/dist/js-ts-semantic-merge.js +21 -0
  89. package/dist/lightweight-dependency-effects.js +51 -0
  90. package/dist/lightweight-dependency-language.js +12 -1
  91. package/dist/lightweight-dependency-relations.js +14 -27
  92. package/dist/native-region-scanner-core.js +33 -1
  93. package/dist/native-region-scanner-csharp.js +151 -0
  94. package/dist/native-region-scanner-dart.js +91 -0
  95. package/dist/native-region-scanner-dynamic.js +21 -151
  96. package/dist/native-region-scanner-functional.js +40 -13
  97. package/dist/native-region-scanner-java.js +97 -0
  98. package/dist/native-region-scanner-js-class.js +100 -0
  99. package/dist/native-region-scanner-js-helpers.js +28 -86
  100. package/dist/native-region-scanner-js-imports.js +121 -1
  101. package/dist/native-region-scanner-js-nested.js +96 -8
  102. package/dist/native-region-scanner-js-structure.js +27 -0
  103. package/dist/native-region-scanner-js-types.js +99 -0
  104. package/dist/native-region-scanner-js.js +70 -118
  105. package/dist/native-region-scanner-kotlin.js +94 -0
  106. package/dist/native-region-scanner-main.js +15 -181
  107. package/dist/native-region-scanner-php.js +80 -0
  108. package/dist/native-region-scanner-python.js +62 -0
  109. package/dist/native-region-scanner-ruby.js +72 -0
  110. package/dist/native-region-scanner-scala.js +91 -0
  111. package/dist/native-region-scanner-spans.js +74 -0
  112. package/dist/native-region-scanner-swift.js +155 -0
  113. package/dist/native-region-scanner.js +14 -10
  114. package/dist/native-source-ledger-helpers.js +195 -0
  115. package/dist/native-source-ledger.js +306 -0
  116. package/dist/native-source-preservation-scanner.js +4 -0
  117. package/dist/semantic-import-callsite-regions.js +136 -0
  118. package/dist/semantic-import-effect-regions.js +283 -0
  119. package/dist/semantic-import-regions.js +11 -2
  120. package/dist/semantic-import-sidecar-entry.js +16 -2
  121. package/dist/semantic-import-sidecar-types.d.ts +2 -0
  122. package/dist/semantic-sidecar-example.js +68 -0
  123. package/dist/universal-capability-matrix.js +23 -0
  124. package/dist/universal-conversion-artifact-query.js +79 -2
  125. package/dist/universal-conversion-artifact-semantic-edit.js +103 -0
  126. package/dist/universal-conversion-artifact-summary.js +33 -1
  127. package/dist/universal-conversion-artifacts.js +13 -48
  128. package/dist/universal-conversion-plan-scoring.js +21 -1
  129. package/dist/universal-conversion-plan-summary.js +30 -0
  130. package/dist/universal-conversion-plan.js +25 -9
  131. package/dist/universal-conversion-route-metadata.js +96 -0
  132. package/dist/universal-conversion-route-operations.js +7 -0
  133. package/dist/universal-representation-coverage.js +193 -0
  134. 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 { semanticEditIdentityFields } from './semanticEditIdentityRecords.js';
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' && isJavaScriptLike(language)
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 = spanOffsets(headSourceText, operation.spans?.head ?? operation.spans?.base ?? operation.anchor?.sourceSpan);
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 replacement = scoped
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 = spanOffsets(headSourceText, operation.spans?.head ?? operation.spans?.base ?? operation.anchor?.sourceSpan);
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 { afterLineOffset, bodyContentRange, spanOffsets } from './semanticEditSourceRanges.js';
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 && isJavaScriptLike(language)
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), { currentSourceText, currentSymbols }))
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 spanRange = currentSymbolEditRange(edit, spanOffsets(context.currentSourceText, symbol?.sourceSpan), context.currentSourceText);
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, 'offset-reanchored-by-symbol'], context.currentSourceText);
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`], context.currentSourceText);
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, 'offset-reanchored-by-symbol'], context.currentSourceText);
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
- return replayEditRecord(edit, anchor ? 'conflict' : 'stale', undefined, [
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: edit.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
- const imported = normalizeNativeDiffImport({
142
- language: input.language,
143
- sourcePath: input.sourcePath,
144
- sourceText: input.currentSourceText,
145
- parser: input.parser
146
- }, input, 'current');
147
- return [...mapDiffSymbols(imported, createSemanticImportSidecar(imported)).values()];
148
- }
149
-
150
- function findCurrentSymbol(edit, symbols) {
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' : status === 'already-applied' ? 'skip' : status === 'stale' ? 'rerun-semantic-import' : status === 'blocked' ? 'block' : 'human-review',
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 isJavaScriptLike(language) { return language === 'javascript' || language === 'typescript'; }
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))); }