@shapeshift-labs/frontier-lang-compiler 0.2.103 → 0.2.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +14 -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 +86 -0
  5. package/dist/declarations/js-ts-safe-merge.d.ts +131 -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/semantic-edit-replay-diagnostics.d.ts +12 -0
  11. package/dist/declarations/semantic-edit-script.d.ts +7 -4
  12. package/dist/declarations/semantic-patch-bundle-index.d.ts +45 -0
  13. package/dist/declarations/semantic-patch-bundle.d.ts +6 -4
  14. package/dist/declarations/semantic-sidecar-example.d.ts +18 -0
  15. package/dist/declarations/semantic-transform-identity.d.ts +3 -0
  16. package/dist/declarations/source-preservation.d.ts +72 -0
  17. package/dist/declarations/universal-capability.d.ts +4 -0
  18. package/dist/declarations/universal-conversion-artifacts.d.ts +61 -1
  19. package/dist/declarations/universal-conversion-compact-counts.d.ts +51 -0
  20. package/dist/declarations/universal-conversion-plan.d.ts +6 -1
  21. package/dist/declarations/universal-representation-coverage.d.ts +90 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.js +3 -0
  24. package/dist/internal/index-impl/bidirectionalExactSourceBackprojection.js +199 -0
  25. package/dist/internal/index-impl/bidirectionalSameLanguageSourceProjection.js +112 -0
  26. package/dist/internal/index-impl/bidirectionalSourceEditProjection.js +319 -0
  27. package/dist/internal/index-impl/bidirectionalSourceEditProjectionArtifacts.js +67 -0
  28. package/dist/internal/index-impl/bidirectionalTargetChangeRecordInternals.js +17 -5
  29. package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +58 -20
  30. package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +60 -7
  31. package/dist/internal/index-impl/createLightweightNativeImport.js +1 -0
  32. package/dist/internal/index-impl/createNativeSourcePreservation.js +28 -2
  33. package/dist/internal/index-impl/diffNativeSymbols.js +3 -3
  34. package/dist/internal/index-impl/nativeChangeProjectionSourceMapLinks.js +2 -0
  35. package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +43 -8
  36. package/dist/internal/index-impl/replaySemanticEditLineEndings.js +34 -0
  37. package/dist/internal/index-impl/replaySemanticEditProjection.js +39 -19
  38. package/dist/internal/index-impl/semanticEditBundleAdmission.js +7 -3
  39. package/dist/internal/index-impl/semanticEditBundleIndex.js +47 -1
  40. package/dist/internal/index-impl/semanticEditExplicitSourceReplacement.js +40 -0
  41. package/dist/internal/index-impl/semanticEditOperationCoverage.js +33 -3
  42. package/dist/internal/index-impl/semanticEditProjectionRecord.js +29 -0
  43. package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +39 -0
  44. package/dist/internal/index-impl/semanticEditReplaySourceReplacement.js +85 -0
  45. package/dist/internal/index-impl/semanticEditScripts.js +4 -0
  46. package/dist/internal/index-impl/semanticEditSourceRanges.js +27 -0
  47. package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +1 -0
  48. package/dist/internal/index-impl/semanticPatchBundleAdmission.js +41 -7
  49. package/dist/internal/index-impl/semanticPatchBundleRecords.js +16 -0
  50. package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +2 -0
  51. package/dist/internal/index-impl/semanticSidecarQuality.js +111 -0
  52. package/dist/internal/index-impl/semanticSourceEditDedupe.js +69 -9
  53. package/dist/internal/index-impl/semanticTransformIdentityRecords.js +85 -9
  54. package/dist/js-ts-safe-member-merge-result.js +158 -0
  55. package/dist/js-ts-safe-member-merge.js +265 -0
  56. package/dist/js-ts-safe-merge-analyze.js +279 -0
  57. package/dist/js-ts-safe-merge-composed.js +170 -0
  58. package/dist/js-ts-safe-merge-constants.js +50 -0
  59. package/dist/js-ts-safe-merge-context.js +118 -0
  60. package/dist/js-ts-safe-merge-ledger-validation.js +92 -0
  61. package/dist/js-ts-safe-merge-ledger.js +85 -0
  62. package/dist/js-ts-safe-merge-parse-declarations.js +210 -0
  63. package/dist/js-ts-safe-merge-parse-statements.js +155 -0
  64. package/dist/js-ts-safe-merge-plan.js +190 -0
  65. package/dist/js-ts-safe-merge.js +175 -0
  66. package/dist/js-ts-semantic-conflict-sidecar-constants.js +77 -0
  67. package/dist/js-ts-semantic-conflict-sidecar-detectors.js +195 -0
  68. package/dist/js-ts-semantic-conflict-sidecar-normalize.js +203 -0
  69. package/dist/js-ts-semantic-conflict-sidecar-utils.js +190 -0
  70. package/dist/js-ts-semantic-conflict-sidecars.js +81 -0
  71. package/dist/js-ts-semantic-merge-contract-helpers.js +128 -0
  72. package/dist/js-ts-semantic-merge-contracts.js +217 -0
  73. package/dist/js-ts-semantic-merge-member-containers.js +100 -0
  74. package/dist/js-ts-semantic-merge-member-keys.js +142 -0
  75. package/dist/js-ts-semantic-merge-member-segments.js +185 -0
  76. package/dist/js-ts-semantic-merge-member-source.js +82 -0
  77. package/dist/js-ts-semantic-merge-member-utils.js +18 -0
  78. package/dist/js-ts-semantic-merge-parse.js +16 -0
  79. package/dist/js-ts-semantic-merge.js +24 -0
  80. package/dist/lightweight-dependency-effects.js +51 -0
  81. package/dist/lightweight-dependency-language.js +12 -1
  82. package/dist/lightweight-dependency-relations.js +14 -27
  83. package/dist/native-region-scanner-core.js +33 -1
  84. package/dist/native-region-scanner-csharp.js +151 -0
  85. package/dist/native-region-scanner-dart.js +91 -0
  86. package/dist/native-region-scanner-dynamic.js +21 -151
  87. package/dist/native-region-scanner-functional.js +40 -13
  88. package/dist/native-region-scanner-java.js +97 -0
  89. package/dist/native-region-scanner-js-class.js +100 -0
  90. package/dist/native-region-scanner-js-helpers.js +28 -86
  91. package/dist/native-region-scanner-js-imports.js +121 -1
  92. package/dist/native-region-scanner-js-nested.js +96 -8
  93. package/dist/native-region-scanner-js-structure.js +27 -0
  94. package/dist/native-region-scanner-js-types.js +99 -0
  95. package/dist/native-region-scanner-js.js +70 -118
  96. package/dist/native-region-scanner-kotlin.js +94 -0
  97. package/dist/native-region-scanner-main.js +15 -181
  98. package/dist/native-region-scanner-php.js +80 -0
  99. package/dist/native-region-scanner-python.js +62 -0
  100. package/dist/native-region-scanner-ruby.js +72 -0
  101. package/dist/native-region-scanner-scala.js +91 -0
  102. package/dist/native-region-scanner-spans.js +74 -0
  103. package/dist/native-region-scanner-swift.js +155 -0
  104. package/dist/native-region-scanner.js +14 -10
  105. package/dist/native-source-ledger-helpers.js +195 -0
  106. package/dist/native-source-ledger.js +306 -0
  107. package/dist/native-source-preservation-scanner.js +4 -0
  108. package/dist/semantic-import-callsite-regions.js +136 -0
  109. package/dist/semantic-import-effect-regions.js +283 -0
  110. package/dist/semantic-import-regions.js +11 -2
  111. package/dist/semantic-import-sidecar-entry.js +16 -2
  112. package/dist/semantic-import-sidecar-types.d.ts +2 -0
  113. package/dist/semantic-sidecar-example.js +68 -0
  114. package/dist/universal-capability-matrix.js +23 -0
  115. package/dist/universal-conversion-artifact-query.js +79 -2
  116. package/dist/universal-conversion-artifact-semantic-edit.js +103 -0
  117. package/dist/universal-conversion-artifact-summary.js +33 -1
  118. package/dist/universal-conversion-artifacts.js +13 -48
  119. package/dist/universal-conversion-plan-scoring.js +21 -1
  120. package/dist/universal-conversion-plan-summary.js +30 -0
  121. package/dist/universal-conversion-plan.js +25 -9
  122. package/dist/universal-conversion-route-metadata.js +96 -0
  123. package/dist/universal-conversion-route-operations.js +7 -0
  124. package/dist/universal-representation-coverage.js +193 -0
  125. package/package.json +1 -1
@@ -10,6 +10,8 @@ import { diffNativeSourceImports } from './diffNativeSourceImports.js';
10
10
  import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
11
11
  import { attachBidirectionalMatchPortability, classifyBidirectionalTargetPortability } from './bidirectionalTargetPortability.js';
12
12
  import { createRoundtripEvidence, createSemanticMergeAdmissionEvidence, summarizeSourceMapBackprojection } from './bidirectionalTargetRoundtripEvidence.js';
13
+ import { createBidirectionalSourceEditProjection } from './bidirectionalSourceEditProjection.js';
14
+ import { createSameLanguageTargetSourceProjection } from './bidirectionalSameLanguageSourceProjection.js';
13
15
  import {
14
16
  anchorsFromSourceSidecar,
15
17
  classifyBidirectionalReadiness,
@@ -86,6 +88,20 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
86
88
  ...array(targetPortability.reasonCodes),
87
89
  ...sourceAnchorMatches.flatMap((match) => match.reasonCodes)
88
90
  ]);
91
+ const sourceMapEditProjection = createBidirectionalSourceEditProjection({
92
+ id,
93
+ source,
94
+ targetChangeSet,
95
+ sourceAnchorMatches,
96
+ targetPortability,
97
+ reasons
98
+ });
99
+ const sourceEditProjection = sourceMapEditProjection.sourceEditScript ? sourceMapEditProjection : createSameLanguageTargetSourceProjection({
100
+ id,
101
+ source,
102
+ targetChangeSet,
103
+ generatedAt: input.generatedAt
104
+ });
89
105
  const evidenceId = input.evidenceId ?? `evidence_${idFragment(id)}_bidirectional_target_change`;
90
106
  const sourcePatchBundleId = input.sourcePatchBundleId ?? `semantic_patch_bundle_${idFragment(id)}_source_port`;
91
107
  const historyRecordId = input.historyRecordId ?? `semantic_history_${idFragment(id)}_target_change`;
@@ -101,7 +117,8 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
101
117
  targetPortability,
102
118
  readiness,
103
119
  reasons,
104
- sourceMapBackprojection
120
+ sourceMapBackprojection,
121
+ sourceEditProjection
105
122
  });
106
123
  const semanticMergeAdmission = createSemanticMergeAdmissionEvidence({
107
124
  id,
@@ -113,7 +130,8 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
113
130
  targetPortability,
114
131
  readiness,
115
132
  reasons,
116
- roundtripEvidence
133
+ roundtripEvidence,
134
+ sourceEditProjection
117
135
  });
118
136
  const bidirectionalEvidence = createBidirectionalEvidence({
119
137
  id,
@@ -131,9 +149,20 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
131
149
  ...bidirectionalEvidence.metadata,
132
150
  roundtripEvidenceId: roundtripEvidence.id,
133
151
  roundtripEvidence,
134
- semanticMergeAdmission
152
+ semanticMergeAdmission,
153
+ sourceEditProjectionId: sourceEditProjection.sourceEditProjection?.id,
154
+ sourceEditReplayId: sourceEditProjection.sourceEditReplay?.id,
155
+ sourceEditScriptId: sourceEditProjection.sourceEditScript?.id,
156
+ sourceProjectionHintId: sourceEditProjection.sourceProjectionHint?.id,
157
+ sourceProjectionHint: sourceEditProjection.sourceProjectionHint
135
158
  }
136
- }];
159
+ }, ...(sourceEditProjection.evidence ?? [])];
160
+ const sourceReplayStatus = sourceEditProjection.sourceEditReplay?.status;
161
+ const sourcePatchAdmission = sourceReplayStatus === 'accepted-clean'
162
+ ? { readiness: 'ready', reasonCodes: ['bidirectional-source-edit-replay-accepted-clean'] }
163
+ : sourceReplayStatus === 'already-applied'
164
+ ? { status: 'admitted', readiness: 'ready', reasonCodes: ['bidirectional-source-edit-replay-already-applied'] }
165
+ : { status: readiness === 'blocked' ? 'blocked' : 'needs-review', readiness };
137
166
  const sourceChangedRegions = sourceAnchorMatches.flatMap((match) => sourceRegionsForMatch(match, readiness));
138
167
  const sourcePatchBundle = createSemanticPatchBundleRecord({
139
168
  id: `${id}_source_port_projection`,
@@ -159,7 +188,10 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
159
188
  id: sourcePatchBundleId,
160
189
  patchId: targetChangeSet.patch?.id,
161
190
  mergeCandidateId: targetChangeSet.mergeCandidate?.id,
162
- admission: { status: readiness === 'blocked' ? 'blocked' : 'needs-review', readiness },
191
+ semanticEditScripts: [sourceEditProjection.sourceEditScript].filter(Boolean),
192
+ semanticEditProjections: [sourceEditProjection.sourceEditProjection].filter(Boolean),
193
+ semanticEditReplays: [sourceEditProjection.sourceEditReplay].filter(Boolean),
194
+ admission: sourcePatchAdmission,
163
195
  metadata: {
164
196
  source: 'createBidirectionalTargetChangeRecord',
165
197
  targetChangeSetId: targetChangeSet.id,
@@ -169,6 +201,11 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
169
201
  sourceMapBackprojection,
170
202
  roundtripEvidenceId: roundtripEvidence.id,
171
203
  semanticMergeAdmission,
204
+ sourceEditScriptId: sourceEditProjection.sourceEditScript?.id,
205
+ sourceEditProjectionId: sourceEditProjection.sourceEditProjection?.id,
206
+ sourceEditReplayId: sourceEditProjection.sourceEditReplay?.id,
207
+ sourceProjectionHintId: sourceEditProjection.sourceProjectionHint?.id,
208
+ sourceProjectionHint: sourceEditProjection.sourceProjectionHint,
172
209
  autoMergeClaim: false,
173
210
  semanticEquivalenceClaim: false
174
211
  }
@@ -191,7 +228,7 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
191
228
  conflictKeys: targetChangeSet.mergeCandidate?.conflictKeys,
192
229
  metadata: { direction: 'target-to-source' }
193
230
  }] : [],
194
- admission: { status: readiness === 'blocked' ? 'blocked' : 'needs-review', readiness, reasonCodes: reasons },
231
+ admission: { status: semanticMergeAdmission.status, readiness: semanticMergeAdmission.readiness, reasonCodes: semanticMergeAdmission.reasonCodes },
195
232
  replayLinks: targetChangeSet.patch ? [{
196
233
  id: `replay_${idFragment(targetChangeSet.patch.id)}`,
197
234
  kind: 'patch',
@@ -205,6 +242,11 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
205
242
  sourceMapBackprojection,
206
243
  roundtripEvidenceId: roundtripEvidence.id,
207
244
  semanticMergeAdmission,
245
+ sourceEditProjectionId: sourceEditProjection.sourceEditProjection?.id,
246
+ sourceEditReplayId: sourceEditProjection.sourceEditReplay?.id,
247
+ sourceEditScriptId: sourceEditProjection.sourceEditScript?.id,
248
+ sourceProjectionHintId: sourceEditProjection.sourceProjectionHint?.id,
249
+ sourceProjectionHint: sourceEditProjection.sourceProjectionHint,
208
250
  autoMergeClaim: false,
209
251
  semanticEquivalenceClaim: false
210
252
  }
@@ -222,6 +264,10 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
222
264
  sourceAnchorMatches,
223
265
  targetPortability,
224
266
  roundtripEvidence,
267
+ sourceEditScript: sourceEditProjection.sourceEditScript,
268
+ sourceEditProjection: sourceEditProjection.sourceEditProjection,
269
+ sourceEditReplay: sourceEditProjection.sourceEditReplay,
270
+ sourceProjectionHint: sourceEditProjection.sourceProjectionHint,
225
271
  sourcePatchBundle,
226
272
  historyRecord,
227
273
  evidence,
@@ -237,6 +283,8 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
237
283
  sourceMapBackedMatches: sourceAnchorMatches.filter((match) => match.sourceMapLinks.length > 0).length,
238
284
  sourceMapLinks: sourceMapBackprojection.sourceMapLinks,
239
285
  sourceMapMappingIds: sourceMapBackprojection.sourceMapMappingIds.length,
286
+ sourceEditScripts: sourceEditProjection.sourceEditScript ? 1 : 0,
287
+ sourceProjectionHints: sourceEditProjection.sourceProjectionHint ? 1 : 0,
240
288
  lineageResolutions: roundtripEvidence.lineageEvidence.lineageResolutionIds.length,
241
289
  targetPortabilityStatus: targetPortability.status,
242
290
  portableTargetRegions: targetPortability.status === 'portable' ? targetPortability.targetChangedRegions : 0,
@@ -246,10 +294,15 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
246
294
  metadata: {
247
295
  autoMergeClaim: false,
248
296
  semanticEquivalenceClaim: false,
249
- reviewRequired: true,
297
+ reviewRequired: sourcePatchBundle.admission.reviewRequired,
250
298
  targetPortability,
251
299
  roundtripEvidenceId: roundtripEvidence.id,
252
300
  semanticMergeAdmission,
301
+ sourceEditScriptId: sourceEditProjection.sourceEditScript?.id,
302
+ sourceEditProjectionId: sourceEditProjection.sourceEditProjection?.id,
303
+ sourceEditReplayId: sourceEditProjection.sourceEditReplay?.id,
304
+ sourceProjectionHintId: sourceEditProjection.sourceProjectionHint?.id,
305
+ sourceProjectionHint: sourceEditProjection.sourceProjectionHint,
253
306
  ...input.metadata
254
307
  }
255
308
  };
@@ -53,6 +53,7 @@ export function createLightweightNativeImport(input) {
53
53
  signatureHash: hashSemanticValue([input.language, declaration.kind, declaration.name, declaration.fields ?? {}]),
54
54
  definitionSpan: declaration.span,
55
55
  metadata: {
56
+ ...declaration.metadata,
56
57
  ownershipRegionId: ownershipRegion.id,
57
58
  ownershipRegionKey: ownershipRegion.key,
58
59
  ownershipRegionKind: ownershipRegion.regionKind
@@ -1,4 +1,13 @@
1
- import{countBy,idFragment,uniqueStrings}from'../../native-import-utils.js';import{detectNewlineStyle,scanPreservedSourceDirectives,scanPreservedSourceTokens}from'../../native-region-scanner.js';import{hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { countBy, idFragment, uniqueStrings } from '../../native-import-utils.js';
3
+ import {
4
+ detectNewlineStyle,
5
+ isJavaScriptTypeScriptSource,
6
+ scanJavaScriptTypeScriptSourceLedger,
7
+ scanPreservedSourceDirectives,
8
+ scanPreservedSourceTokens
9
+ } from '../../native-region-scanner.js';
10
+
2
11
  export function createNativeSourcePreservation(options) {
3
12
  if (!options || typeof options.sourceText !== 'string') {
4
13
  throw new Error('createNativeSourcePreservation requires sourceText');
@@ -26,6 +35,14 @@ export function createNativeSourcePreservation(options) {
26
35
  maxDirectives: options.maxDirectives
27
36
  });
28
37
  const directives = directiveScan.directives;
38
+ const ledger = options.includeSourceLedger === false || !isJavaScriptTypeScriptSource(language, options.sourcePath)
39
+ ? undefined
40
+ : scanJavaScriptTypeScriptSourceLedger(sourceText, {
41
+ language,
42
+ sourcePath: options.sourcePath,
43
+ sourceHash,
44
+ maxLedgerSpans: options.maxLedgerSpans
45
+ });
29
46
  const triviaByKind = countBy(tokensAndTrivia.trivia.map((entry) => entry.kind ?? 'unknown'));
30
47
  const directivesByKind = countBy(directives.map((entry) => entry.kind ?? 'directive'));
31
48
  const directiveKinds = uniqueStrings(directives.map((entry) => entry.kind ?? 'directive'));
@@ -52,23 +69,32 @@ export function createNativeSourcePreservation(options) {
52
69
  tokens: tokensAndTrivia.tokens,
53
70
  trivia: tokensAndTrivia.trivia,
54
71
  directives,
72
+ ...(ledger ? { ledger } : {}),
55
73
  summary: {
56
74
  tokens: tokensAndTrivia.tokens.length,
57
75
  trivia: tokensAndTrivia.trivia.length,
58
76
  directives: directives.length,
59
77
  comments: tokensAndTrivia.trivia.filter((entry) => entry.kind === 'comment').length,
60
78
  whitespace: tokensAndTrivia.trivia.filter((entry) => entry.kind === 'whitespace' || entry.kind === 'newline').length,
79
+ ...(ledger ? {
80
+ ledger: ledger.summary,
81
+ sourceMapComments: ledger.summary.sourceMapComments,
82
+ protectedRegions: ledger.summary.protectedRegions,
83
+ importExportSpans: ledger.summary.importExportSpans,
84
+ braces: ledger.summary.braces
85
+ } : {}),
61
86
  triviaByKind,
62
87
  directivesByKind,
63
88
  directiveKinds,
64
89
  commentSpanIds,
65
90
  directiveSpanIds,
66
91
  exactSourceAvailable: options.includeSourceText !== false,
67
- truncated: tokensAndTrivia.truncated || directiveScan.truncated
92
+ truncated: tokensAndTrivia.truncated || directiveScan.truncated || Boolean(ledger?.summary?.truncated)
68
93
  },
69
94
  metadata: {
70
95
  preservation: 'source-text-token-trivia-directive-evidence',
71
96
  tokenization: 'frontier-lightweight-lexical-scan',
97
+ ...(ledger ? { sourceLedger: 'frontier-lightweight-js-ts-source-ledger' } : {}),
72
98
  ...(declaredSourceHash ? {
73
99
  declaredSourceHash,
74
100
  sourceHashVerified: declaredSourceHash === computedSourceHash
@@ -59,7 +59,7 @@ function nativeDiffSymbolHasOwnChange(symbol) {
59
59
 
60
60
  function nativeDiffSymbolIsMorePreciseNestedChange(candidate, container) {
61
61
  if (candidate.changeKind === 'unchanged') return false;
62
- if (nativeDiffSymbolIsContainer(candidate)) return false;
62
+ if (nativeDiffSymbolIsContainer(candidate) && !nativeDiffSymbolIsMember(candidate)) return false;
63
63
  if ((candidate.ownershipKey ?? '') === (container.ownershipKey ?? '')) return false;
64
64
  if (!nativeDiffAnySpanContains(container, candidate)) return false;
65
65
  return nativeDiffNestedSymbolName(candidate, container) || nativeDiffSymbolIsMember(candidate);
@@ -118,5 +118,5 @@ function nativeDiffKind(value) {
118
118
  return String(value ?? '').toLowerCase();
119
119
  }
120
120
 
121
- const nativeDiffContainerKinds = new Set(['type', 'class', 'interface', 'trait', 'protocol', 'struct', 'enum', 'record']);
122
- const nativeDiffMemberKinds = new Set(['body', 'method', 'function', 'property', 'declaration']);
121
+ const nativeDiffContainerKinds = new Set(['type', 'class', 'interface', 'trait', 'protocol', 'struct', 'enum', 'record', 'body', 'function', 'method', 'export']);
122
+ const nativeDiffMemberKinds = new Set(['body', 'method', 'function', 'property', 'declaration', 'call', 'effect', 'controlflow', 'mutation']);
@@ -23,6 +23,8 @@ export function nativeChangeProjectionSourceMapLinks(imported, side, region, sym
23
23
  precision: mapping.precision,
24
24
  sourceSpan: mapping.sourceSpan,
25
25
  generatedSpan: mapping.generatedSpan,
26
+ sourceReplacementText: mapping.sourceReplacementText,
27
+ sourceReplacementTextHash: mapping.sourceReplacementTextHash ?? mapping.sourceReplacementHash,
26
28
  ownershipRegionId: mapping.ownershipRegionId,
27
29
  ownershipRegionKey: mapping.ownershipRegionKey,
28
30
  ownershipRegionKind: mapping.ownershipRegionKind
@@ -4,7 +4,9 @@ import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
4
4
  import { mapDiffSymbols } from './mapDiffSymbols.js';
5
5
  import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
6
6
  import { alreadyAppliedImportEditForOperation } from './semanticEditImportProjection.js';
7
+ import { explicitSourceReplacementEditForOperation } from './semanticEditExplicitSourceReplacement.js';
7
8
  import { projectionEditRecord } from './semanticEditProjectionRecord.js';
9
+ import { findCurrentSymbol } from './semanticEditReplayAnchors.js';
8
10
  import {
9
11
  insertionOffset,
10
12
  insertionReplacement,
@@ -23,7 +25,7 @@ export function projectSemanticEditScriptToSource(input = {}) {
23
25
  if (typeof workerSourceText !== 'string') reasonCodes.push('missing-worker-source-text');
24
26
  if (typeof headSourceText !== 'string') reasonCodes.push('missing-head-source-text');
25
27
  const language = normalizeNativeLanguageId(script.language);
26
- const headSymbols = typeof headSourceText === 'string' && isJavaScriptLike(language)
28
+ const headSymbols = typeof headSourceText === 'string' && language
27
29
  ? sourceSymbolIndex({
28
30
  sourceText: headSourceText,
29
31
  sourcePath: input.headSourcePath ?? script.sourcePath,
@@ -42,7 +44,7 @@ export function projectSemanticEditScriptToSource(input = {}) {
42
44
  const edit = sourceEditForOperation(operation, workerSourceText, headSourceText, index, {
43
45
  headSourcePath: input.headSourcePath,
44
46
  headSymbols,
45
- symbolIndexAvailable: isJavaScriptLike(language)
47
+ symbolIndexAvailable: headSymbols.length > 0
46
48
  });
47
49
  if (edit.ok) edits.push(edit.value);
48
50
  else reasonCodes.push(...edit.reasonCodes);
@@ -95,14 +97,16 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
95
97
  return { ok: true, value: { ...identity, operationId: operation.id, order, start: 0, end: 0, replacement: '', current: '', alreadyApplied: true } };
96
98
  }
97
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;
98
102
  if (operation.changeKind === 'added' || String(operation.kind ?? '').startsWith('add')) {
99
103
  return insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order, context);
100
104
  }
101
105
  if (operation.changeKind === 'removed' || String(operation.kind ?? '').startsWith('remove')) {
102
- return removalEditForOperation(operation, identity, headSourceText, order);
106
+ return removalEditForOperation(operation, identity, headSourceText, order, context);
103
107
  }
104
108
  const workerOffsets = spanOffsets(workerSourceText, operation.spans?.worker);
105
- const headOffsets = spanOffsets(headSourceText, operation.spans?.head ?? operation.spans?.base ?? operation.anchor?.sourceSpan);
109
+ const headOffsets = headOffsetsForOperation(operation, identity, headSourceText, context);
106
110
  const reasons = [];
107
111
  if (!workerOffsets) reasons.push(`worker-span-not-resolvable:${operation.id}`);
108
112
  if (!headOffsets) reasons.push(`head-span-not-resolvable:${operation.id}`);
@@ -118,9 +122,12 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
118
122
  }
119
123
  if (reasons.length) return { ok: false, reasonCodes: reasons };
120
124
  const scoped = scopedBodyReplacement(operation, headSourceText, workerSourceText, headOffsets, workerOffsets);
121
- const replacement = scoped
125
+ const rawReplacement = scoped
122
126
  ? workerSourceText.slice(scoped.worker.start, scoped.worker.end)
123
127
  : anchorReplacement;
128
+ const replacement = operation.metadata?.sourceBackprojection?.lineEndingStable
129
+ ? normalizeReplacementLineEndings(rawReplacement, anchorCurrent)
130
+ : rawReplacement;
124
131
  const current = scoped
125
132
  ? headSourceText.slice(scoped.head.start, scoped.head.end)
126
133
  : anchorCurrent;
@@ -147,8 +154,8 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
147
154
  }
148
155
  };
149
156
  }
150
- function removalEditForOperation(operation, identity, headSourceText, order) {
151
- 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);
152
159
  const reasons = [];
153
160
  if (!headOffsets) reasons.push(`head-span-not-resolvable:${operation.id}`);
154
161
  if (reasons.length) return { ok: false, reasonCodes: reasons };
@@ -173,6 +180,35 @@ function removalEditForOperation(operation, identity, headSourceText, order) {
173
180
  }
174
181
  };
175
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
+
176
212
  function insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order, context) {
177
213
  const workerOffsets = spanOffsets(workerSourceText, operation.spans?.worker);
178
214
  const reasons = [];
@@ -263,7 +299,6 @@ function projectedSourcePath(script, edits) {
263
299
  return edits.map((edit) => edit.sourcePath).find(Boolean) ?? script.sourcePath;
264
300
  }
265
301
 
266
- function isJavaScriptLike(language) { return language === 'javascript' || language === 'typescript'; }
267
302
  function compactRecord(value) {
268
303
  return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
269
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,7 +3,9 @@ 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';
8
+ import { explicitSourceReplacementReplayRange } from './semanticEditReplaySourceReplacement.js';
7
9
  import {
8
10
  findCurrentSymbol,
9
11
  findInsertionAnchor,
@@ -22,14 +24,14 @@ export function replaySemanticEditProjection(input = {}) {
22
24
  const reasonCodes = baseReasonCodes(projection, currentSourceText);
23
25
  const currentHash = typeof currentSourceText === 'string' ? hashSemanticValue(currentSourceText) : undefined;
24
26
  if (input.currentSourceHash && currentHash !== input.currentSourceHash) reasonCodes.push('current-source-hash-mismatch');
25
- const currentSymbols = currentSourceText && isJavaScriptLike(language)
27
+ const currentSymbols = currentSourceText && language
26
28
  ? currentSymbolIndex({ currentSourceText, sourcePath, language, parser: input.parser })
27
29
  : [];
28
30
  const replayedEdits = projection.status === 'projected' && typeof currentSourceText === 'string'
29
31
  ? (projection.edits ?? []).map((edit, index) => replayProjectionEdit(projectionEditWithOrder(edit, index), {
30
32
  currentSourceText,
31
33
  currentSymbols,
32
- symbolIndexAvailable: isJavaScriptLike(language)
34
+ symbolIndexAvailable: currentSymbols.length > 0
33
35
  }))
34
36
  : [];
35
37
  const edits = replayEditsWithOverlapDiagnostics(replayedEdits);
@@ -80,20 +82,28 @@ function replayProjectionEdit(edit, context) {
80
82
  const headRange = { start: edit.headStart, end: edit.headEnd };
81
83
  const offset = checkRange(edit, headRange, context.currentSourceText, 'head-offset');
82
84
  const symbol = findCurrentSymbol(edit, context.currentSymbols);
83
- 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 ?? [];
84
90
  if (symbol && spanRange && !sameRange(headRange, spanRange)) {
85
91
  const moved = checkRange(edit, spanRange, context.currentSourceText, currentSymbolRangeLabel(edit));
86
- if (moved) return replayEditRecord(edit, moved.status, replayAppliedRange(edit, moved.range, context.currentSourceText), [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
+ }
87
96
  if (edit.editKind === 'delete' && offset && rangesOverlap(headRange, spanRange)) {
88
97
  return replayEditRecord(edit, offset.status, offset.range, [offset.reason], context.currentSourceText);
89
98
  }
90
- return replayEditRecord(edit, 'conflict', spanRange, [`${currentSymbolRangeLabel(edit)}-content-mismatch`], context.currentSourceText);
99
+ return replayEditRecord(edit, 'conflict', spanRange, [`${currentSymbolRangeLabel(edit)}-content-mismatch`, ...explicitConflictReasons], context.currentSourceText);
91
100
  }
92
101
  if (offset) return replayEditRecord(edit, offset.status, offset.range, [offset.reason], context.currentSourceText);
93
102
  const anchored = checkRange(edit, spanRange, context.currentSourceText, currentSymbolRangeLabel(edit));
94
- if (anchored) return replayEditRecord(edit, anchored.status, replayAppliedRange(edit, anchored.range, context.currentSourceText), [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);
95
104
  return replayEditRecord(edit, symbol ? 'conflict' : 'stale', spanRange, [
96
- symbol ? `${currentSymbolRangeLabel(edit)}-content-mismatch` : 'current-symbol-anchor-missing'
105
+ symbol ? `${currentSymbolRangeLabel(edit)}-content-mismatch` : 'current-symbol-anchor-missing',
106
+ ...explicitConflictReasons
97
107
  ], context.currentSourceText);
98
108
  }
99
109
 
@@ -148,6 +158,7 @@ function checkRange(edit, range, sourceText, label) {
148
158
 
149
159
  function replayEditRecord(edit, status, range, reasonCodes, sourceText) {
150
160
  const normalizedReasonCodes = reasonList(reasonCodes);
161
+ const replacementText = replayReplacementText(edit, status, range, sourceText);
151
162
  return compactRecord({
152
163
  operationId: edit.operationId,
153
164
  semanticKey: edit.semanticKey,
@@ -163,21 +174,25 @@ function replayEditRecord(edit, status, range, reasonCodes, sourceText) {
163
174
  status,
164
175
  start: range?.start,
165
176
  end: range?.end,
166
- replacementBytes: edit.replacementBytes,
167
- replacementText: edit.replacementText,
177
+ replacementBytes: typeof replacementText === 'string' ? replacementText.length : edit.replacementBytes,
178
+ replacementText,
168
179
  reasonCodes: normalizedReasonCodes,
169
180
  diagnostics: replayEditDiagnostics(edit, status, range, normalizedReasonCodes, sourceText)
170
181
  });
171
182
  }
172
183
 
173
184
  function currentSymbolIndex(input) {
174
- const imported = normalizeNativeDiffImport({
175
- language: input.language,
176
- sourcePath: input.sourcePath,
177
- sourceText: input.currentSourceText,
178
- parser: input.parser
179
- }, input, 'current');
180
- return [...mapDiffSymbols(imported, createSemanticImportSidecar(imported)).values()];
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 [];
195
+ }
181
196
  }
182
197
 
183
198
  function currentSymbolEditRange(edit, symbolRange, sourceText) {
@@ -187,6 +202,7 @@ function currentSymbolEditRange(edit, symbolRange, sourceText) {
187
202
  }
188
203
 
189
204
  function currentSymbolRangeLabel(edit) {
205
+ if (edit.sourceRangeKind === 'cross-language-explicit-source-replacement') return 'current-symbol-explicit-source-replacement';
190
206
  return edit.sourceRangeKind === 'body-content' ? 'current-symbol-body' : 'current-symbol-anchor';
191
207
  }
192
208
 
@@ -202,10 +218,11 @@ function replayStatus(reasonCodes, edits, projection) {
202
218
 
203
219
  function replayAdmission(status, reasonCodes, edits) {
204
220
  const apply = status === 'accepted-clean';
221
+ const skip = status === 'already-applied';
205
222
  return {
206
223
  status,
207
- action: apply ? 'apply' : status === 'already-applied' ? 'skip' : status === 'stale' ? 'rerun-semantic-import' : status === 'blocked' ? 'block' : 'human-review',
208
- reviewRequired: !apply,
224
+ action: apply ? 'apply' : skip ? 'skip' : status === 'stale' ? 'rerun-semantic-import' : status === 'blocked' ? 'block' : 'human-review',
225
+ reviewRequired: !(apply || skip),
209
226
  autoApplyCandidate: apply,
210
227
  autoMergeClaim: false,
211
228
  semanticEquivalenceClaim: false,
@@ -269,7 +286,10 @@ function rangesOverlap(left, right) {
269
286
  return Boolean(left && right && left.start < right.end && right.start < left.end);
270
287
  }
271
288
 
272
- 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
+
273
293
  function reasonList(values) { return uniqueStrings((values ?? []).filter(Boolean)); }
274
294
  function lineEndingStableText(value) {
275
295
  if (typeof value !== 'string') return undefined;
@@ -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
  }