@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
@@ -0,0 +1,112 @@
1
+ import { idFragment, normalizeNativeLanguageId } from '../../native-import-utils.js';
2
+ import { nativeImportSourceText } from './nativeImportSourceText.js';
3
+ import { createSemanticEditScript } from './semanticEditScripts.js';
4
+ import { projectSemanticEditScriptToSource } from './projectSemanticEditScriptToSource.js';
5
+ import { replaySemanticEditProjection } from './replaySemanticEditProjection.js';
6
+
7
+ const backprojectionMode = 'same-language-target-source-edit';
8
+
9
+ export function createSameLanguageTargetSourceProjection(context = {}) {
10
+ if (!sameLanguageSamePath(context.source, context.targetChangeSet)) return {};
11
+ const sourceText = nativeImportSourceText(context.source);
12
+ const workerSourceText = nativeImportSourceText(context.targetChangeSet?.after);
13
+ if (typeof sourceText !== 'string' || typeof workerSourceText !== 'string') return {};
14
+ const script = createSemanticEditScript({
15
+ id: `semantic_edit_script_${idFragment(context.id)}_same_language_target`,
16
+ language: context.source.language,
17
+ sourcePath: context.source.sourcePath,
18
+ base: context.targetChangeSet.before,
19
+ worker: context.targetChangeSet.after,
20
+ head: context.source,
21
+ generatedAt: context.generatedAt,
22
+ metadata: {
23
+ sourceBackprojectionMode: backprojectionMode,
24
+ bidirectionalTargetChangeId: context.id,
25
+ targetChangeSetId: context.targetChangeSet.id
26
+ }
27
+ });
28
+ const projection = projectSemanticEditScriptToSource({
29
+ id: `semantic_edit_projection_${idFragment(context.id)}_same_language_target`,
30
+ script,
31
+ workerSourceText,
32
+ headSourceText: sourceText,
33
+ headSourcePath: context.source.sourcePath,
34
+ metadata: { sourceBackprojectionMode: backprojectionMode }
35
+ });
36
+ const replay = projection.status === 'projected'
37
+ ? replaySemanticEditProjection({
38
+ id: `semantic_edit_replay_${idFragment(context.id)}_same_language_target`,
39
+ projection,
40
+ currentSourceText: sourceText
41
+ })
42
+ : undefined;
43
+ const sourceProjectionHint = projectionHint(context, script, projection, replay);
44
+ return {
45
+ sourceEditScript: script,
46
+ sourceProjectionHint,
47
+ sourceEditProjection: projection,
48
+ sourceEditReplay: replay,
49
+ evidence: [sameLanguageEvidence(context, script, projection, replay)]
50
+ };
51
+ }
52
+
53
+ function projectionHint(context, script, projection, replay) {
54
+ const replayReady = ['accepted-clean', 'already-applied'].includes(replay?.status);
55
+ const ready = script.admission?.status === 'auto-merge-candidate' && projection?.status === 'projected' && replayReady;
56
+ return {
57
+ schema: 'frontier.lang.bidirectionalTargetChangeSourceEditProjectionHint.v1',
58
+ version: 1,
59
+ id: `source_projection_hint_${idFragment(context.id)}_same_language_target`,
60
+ scriptId: script.id,
61
+ status: ready ? 'auto-merge-candidate' : script.admission?.status ?? 'needs-port',
62
+ action: ready ? sourceAction(replay) : 'reanchor-or-human-port',
63
+ readiness: ready ? 'ready' : script.admission?.readiness ?? 'needs-review',
64
+ sourcePath: context.source.sourcePath,
65
+ sourceHash: script.headHash,
66
+ targetPath: context.targetChangeSet.sourcePath,
67
+ targetHash: context.targetChangeSet.afterHash,
68
+ targetChangeSetId: context.targetChangeSet.id,
69
+ targetPatchId: context.targetChangeSet.patch?.id,
70
+ targetMergeCandidateId: context.targetChangeSet.mergeCandidate?.id,
71
+ operationIds: (script.operations ?? []).map((operation) => operation.id),
72
+ reviewRequired: !ready,
73
+ autoMergeClaim: false,
74
+ semanticEquivalenceClaim: false,
75
+ reasonCodes: ready ? ['same-language-target-source-edit-replayed'] : script.admission?.reasonCodes,
76
+ sourceBackprojectionMode: backprojectionMode
77
+ };
78
+ }
79
+
80
+ function sameLanguageEvidence(context, script, projection, replay) {
81
+ const passed = projection?.status === 'projected' && ['accepted-clean', 'already-applied'].includes(replay?.status);
82
+ return {
83
+ id: `evidence_${idFragment(context.id)}_same_language_target_replay`,
84
+ kind: 'verification',
85
+ status: passed ? 'passed' : 'failed',
86
+ path: script.sourcePath,
87
+ summary: passed
88
+ ? 'Verified same-language target change by projecting and replaying it onto the source file.'
89
+ : 'Same-language target change did not project and replay cleanly onto the source file.',
90
+ metadata: {
91
+ schema: 'frontier.lang.bidirectionalSameLanguageTargetSourceEvidence.v1',
92
+ bidirectionalTargetChangeId: context.id,
93
+ sourceEditScriptId: script.id,
94
+ sourceEditProjectionId: projection?.id,
95
+ sourceEditReplayId: replay?.id,
96
+ projectionStatus: projection?.status,
97
+ replayStatus: replay?.status,
98
+ autoMergeClaim: false,
99
+ semanticEquivalenceClaim: false
100
+ }
101
+ };
102
+ }
103
+
104
+ function sameLanguageSamePath(source, targetChangeSet) {
105
+ const sourceLanguage = normalizeNativeLanguageId(source?.language);
106
+ const targetLanguage = normalizeNativeLanguageId(targetChangeSet?.language);
107
+ return Boolean(sourceLanguage && sourceLanguage === targetLanguage && source?.sourcePath && source.sourcePath === targetChangeSet?.sourcePath);
108
+ }
109
+
110
+ function sourceAction(replay) {
111
+ return replay?.status === 'already-applied' ? 'skip-source-backprojection' : 'admit-source-backprojection';
112
+ }
@@ -0,0 +1,319 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { idFragment, uniqueRecordsById, uniqueStrings } from '../../native-import-utils.js';
3
+ import { exactSourceBackprojectionForMatch } from './bidirectionalExactSourceBackprojection.js';
4
+ import { createBidirectionalSourceEditProjectionArtifacts } from './bidirectionalSourceEditProjectionArtifacts.js';
5
+ import { nativeImportSourceText } from './nativeImportSourceText.js';
6
+ import { summarizeSemanticEditOperations } from './semanticEditScriptClassification.js';
7
+ import { projectionCoveredContainerOperationIds, spanOffsets } from './semanticEditSourceRanges.js';
8
+
9
+ const exactSourceBackprojectionModes = ['same-language-exact-source-map', 'cross-language-explicit-source-replacement'];
10
+
11
+ export function createBidirectionalSourceEditProjection(context = {}) {
12
+ const source = context.source;
13
+ const targetChangeSet = context.targetChangeSet;
14
+ const matches = portableSingleAnchorMatches(context.sourceAnchorMatches, context.targetPortability);
15
+ if (!source || !targetChangeSet || matches.length === 0) return {};
16
+
17
+ const scriptId = `semantic_edit_script_${idFragment(context.id)}_source_projection`;
18
+ const sourceText = nativeImportSourceText(source);
19
+ const initialOperations = matches.map((match, index) => sourceEditOperationForMatch(match, index, context));
20
+ const operations = markCoveredByExactSourceRange(initialOperations, sourceText);
21
+ const sourceMapLinks = uniqueRecordsById(matches.flatMap((match) => match.sourceMapLinks));
22
+ const coveredOperationIds = projectionCoveredContainerOperationIds(operations, nativeImportSourceText(targetChangeSet.after));
23
+ const exactBackprojection = operations.length > 0 && operations.every((operation) => (
24
+ exactSourceBackprojectionModes.includes(operation.metadata?.sourceBackprojection?.mode) ||
25
+ operation.status === 'covered' ||
26
+ coveredOperationIds.has(operation.id) ||
27
+ operationCoveredByExactSourceRange(operation, operations, sourceText)
28
+ ));
29
+ const sourceProjectionHint = sourceProjectionHintRecord({ context, matches, operations, sourceMapLinks, scriptId });
30
+ const evidence = sourceEditProjectionEvidence({ context, operations, scriptId, sourceProjectionHint });
31
+ const reasonCodes = sourceEditAdmissionReasonCodes(context, matches, exactBackprojection);
32
+ const core = {
33
+ kind: 'frontier.lang.semanticEditScript',
34
+ version: 1,
35
+ schema: 'frontier.lang.semanticEditScript.v1',
36
+ id: scriptId,
37
+ stableId: `semantic_edit_script_${idFragment(hashSemanticValue([scriptId, operations]))}`,
38
+ language: source.language,
39
+ sourcePath: projectionSourcePath(source, matches),
40
+ baseHash: sourceHash(source),
41
+ workerChangeSetId: targetChangeSet.id,
42
+ operations,
43
+ summary: summarizeSemanticEditOperations(operations),
44
+ admission: {
45
+ status: exactBackprojection ? 'auto-merge-candidate' : 'needs-port',
46
+ action: exactBackprojection ? 'apply-source-map-backprojection' : 'reanchor-or-human-port',
47
+ reviewRequired: !exactBackprojection,
48
+ autoApplyCandidate: exactBackprojection,
49
+ autoMergeClaim: false,
50
+ semanticEquivalenceClaim: false,
51
+ reasonCodes,
52
+ conflictKeys: uniqueStrings(matches.flatMap((match) => match.conflictKeys)),
53
+ evidenceIds: evidence.map((record) => record.id)
54
+ },
55
+ evidence,
56
+ metadata: {
57
+ autoMergeClaim: false,
58
+ semanticEquivalenceClaim: false,
59
+ sourceProjectionHint,
60
+ bidirectionalTargetChangeId: context.id,
61
+ targetChangeSetId: targetChangeSet.id,
62
+ targetPatchId: targetChangeSet.patch?.id,
63
+ targetMergeCandidateId: targetChangeSet.mergeCandidate?.id,
64
+ targetLanguage: targetChangeSet.language,
65
+ targetPath: targetChangeSet.sourcePath,
66
+ targetPortabilityStatus: context.targetPortability?.status,
67
+ targetPortabilityAction: context.targetPortability?.action,
68
+ sourceMapBacked: true,
69
+ singleSourceAnchor: true,
70
+ sourceBackprojectionMode: sourceProjectionHint.sourceBackprojectionMode
71
+ }
72
+ };
73
+ const sourceEditScript = { ...core, hash: hashSemanticValue(core) };
74
+ const artifacts = createBidirectionalSourceEditProjectionArtifacts(context, sourceEditScript);
75
+ return {
76
+ sourceEditScript,
77
+ sourceProjectionHint,
78
+ sourceEditProjection: artifacts.sourceEditProjection,
79
+ sourceEditReplay: artifacts.sourceEditReplay,
80
+ evidence: [...evidence, ...array(artifacts.evidence)]
81
+ };
82
+ }
83
+
84
+ function sourceEditOperationForMatch(match, index, context) {
85
+ const anchor = match.sourceAnchors[0] ?? {};
86
+ const region = match.targetRegion ?? {};
87
+ const sourceMapLinks = uniqueRecordsById(match.sourceMapLinks);
88
+ const backprojection = exactSourceBackprojectionForMatch(match, context);
89
+ const sourceIdentity = {
90
+ sourcePath: anchor.sourcePath,
91
+ sourceHash: anchor.sourceHash,
92
+ anchorKey: anchor.key,
93
+ symbolId: anchor.symbolId,
94
+ symbolName: anchor.symbolName,
95
+ sourceSpan: anchor.sourceSpan
96
+ };
97
+ const core = compactRecord({
98
+ id: `semantic_edit_op_${idFragment([context.id, match.id, index + 1].join(':'))}`,
99
+ kind: semanticEditKind(region),
100
+ changeKind: region.changeKind,
101
+ anchor: compactRecord({
102
+ key: anchor.key,
103
+ conflictKey: anchor.key ?? region.conflictKey,
104
+ regionId: region.id,
105
+ regionKind: anchor.kind ?? region.regionKind,
106
+ granularity: 'symbol',
107
+ language: anchor.language ?? context.source?.language,
108
+ sourcePath: anchor.sourcePath,
109
+ symbolId: anchor.symbolId,
110
+ symbolName: anchor.symbolName,
111
+ sourceSpan: anchor.sourceSpan
112
+ }),
113
+ semanticKey: `bidirectional-source-edit:${anchor.key ?? match.id}`,
114
+ semanticIdentityHash: hashSemanticValue({ matchId: match.id, targetRegion: region.key, sourceIdentity }),
115
+ sourceIdentityHash: hashSemanticValue(sourceIdentity),
116
+ spans: {
117
+ base: backprojection?.sourceEditSpan ?? anchor.sourceSpan,
118
+ worker: backprojection?.targetAfterEditSpan ?? anchor.sourceSpan,
119
+ head: backprojection?.sourceEditSpan
120
+ },
121
+ hashes: {
122
+ baseSourceHash: anchor.sourceHash,
123
+ workerSourceHash: backprojection?.targetAfterSourceHash ?? sourceHash(context.source),
124
+ baseTextHash: backprojection?.sourceEditTextHash,
125
+ headTextHash: backprojection?.sourceEditTextHash,
126
+ workerTextHash: backprojection?.targetAfterEditTextHash
127
+ },
128
+ status: backprojection?.alreadyApplied ? 'already-applied' : 'portable',
129
+ readiness: backprojection ? 'ready' : 'needs-review',
130
+ confidence: match.portability?.confidence ?? match.confidence,
131
+ reasonCodes: uniqueStrings([
132
+ 'source-edit-script-projection-hint',
133
+ 'target-change-source-map-portable',
134
+ 'single-source-anchor-projection-hint',
135
+ backprojection?.alreadyApplied ? 'source-edit-already-applied' : undefined,
136
+ ...array(match.reasonCodes),
137
+ ...array(match.portability?.reasonCodes)
138
+ ]),
139
+ evidenceIds: uniqueStrings(sourceMapLinks.map((link) => link.id)),
140
+ metadata: {
141
+ autoMergeClaim: false,
142
+ semanticEquivalenceClaim: false,
143
+ bidirectionalTargetChangeId: context.id,
144
+ targetRegion: region,
145
+ sourceMapLinkIds: sourceMapLinks.map((link) => link.id),
146
+ sourceMapMappingIds: uniqueStrings(sourceMapLinks.map((link) => link.sourceMapMappingId)),
147
+ sourceBackprojection: backprojection
148
+ }
149
+ });
150
+ return { ...core, operationContentHash: hashSemanticValue(core) };
151
+ }
152
+
153
+ function sourceProjectionHintRecord(input) {
154
+ const { context, matches, operations, sourceMapLinks, scriptId } = input;
155
+ const source = context.source;
156
+ const targetChangeSet = context.targetChangeSet;
157
+ const exactBackprojection = operations.length > 0 && operations.every((operation) => (
158
+ exactSourceBackprojectionModes.includes(operation.metadata?.sourceBackprojection?.mode) || operation.status === 'covered'
159
+ ));
160
+ const core = {
161
+ schema: 'frontier.lang.bidirectionalTargetChangeSourceEditProjectionHint.v1',
162
+ version: 1,
163
+ id: `source_projection_hint_${idFragment(context.id)}`,
164
+ scriptId,
165
+ status: exactBackprojection ? 'auto-merge-candidate' : 'portable',
166
+ action: exactBackprojection ? 'apply-source-map-backprojection' : context.targetPortability?.action ?? 'port-with-source-map-review',
167
+ readiness: exactBackprojection ? 'ready' : 'needs-review',
168
+ sourcePath: projectionSourcePath(source, matches),
169
+ sourceHash: sourceHash(source),
170
+ targetPath: targetChangeSet.sourcePath,
171
+ targetHash: targetChangeSet.afterHash,
172
+ targetChangeSetId: targetChangeSet.id,
173
+ targetPatchId: targetChangeSet.patch?.id,
174
+ targetMergeCandidateId: targetChangeSet.mergeCandidate?.id,
175
+ sourceAnchorMatchIds: matches.map((match) => match.id),
176
+ sourceAnchorKeys: uniqueStrings(matches.flatMap((match) => match.sourceAnchors.map((anchor) => anchor.key))),
177
+ sourceMapLinkIds: sourceMapLinks.map((link) => link.id),
178
+ sourceMapIds: uniqueStrings(sourceMapLinks.map((link) => link.sourceMapId)),
179
+ sourceMapMappingIds: uniqueStrings(sourceMapLinks.map((link) => link.sourceMapMappingId)),
180
+ operationIds: operations.map((operation) => operation.id),
181
+ reviewRequired: !exactBackprojection,
182
+ autoMergeClaim: false,
183
+ semanticEquivalenceClaim: false,
184
+ reasonCodes: sourceEditAdmissionReasonCodes(context, matches, exactBackprojection),
185
+ sourceBackprojectionMode: exactBackprojection ? firstBackprojectionMode(operations) : 'review-only'
186
+ };
187
+ return { ...core, hash: hashSemanticValue(core) };
188
+ }
189
+
190
+ function sourceEditProjectionEvidence(input) {
191
+ const { context, operations, scriptId, sourceProjectionHint } = input;
192
+ return [{
193
+ id: `evidence_${idFragment(scriptId)}_source_projection_hint`,
194
+ kind: 'semantic-edit-script',
195
+ status: 'passed',
196
+ path: sourceProjectionHint.sourcePath,
197
+ summary: `Created source-side projection hint for ${operations.length} source-map-backed target edit operation(s).`,
198
+ metadata: {
199
+ schema: 'frontier.lang.bidirectionalTargetChangeSourceEditHintEvidence.v1',
200
+ bidirectionalTargetChangeId: context.id,
201
+ targetChangeSetId: context.targetChangeSet.id,
202
+ sourceProjectionHintId: sourceProjectionHint.id,
203
+ sourceEditScriptId: scriptId,
204
+ operationIds: sourceProjectionHint.operationIds,
205
+ sourceMapLinkIds: sourceProjectionHint.sourceMapLinkIds,
206
+ sourceMapMappingIds: sourceProjectionHint.sourceMapMappingIds,
207
+ autoMergeClaim: false,
208
+ semanticEquivalenceClaim: false
209
+ }
210
+ }];
211
+ }
212
+
213
+ function markCoveredByExactSourceRange(operations, sourceText) {
214
+ return operations.map((operation) => {
215
+ const coveredBy = exactSourceRangeCoveringOperation(operation, operations, sourceText);
216
+ if (!coveredBy) return operation;
217
+ return {
218
+ ...operation,
219
+ status: 'covered',
220
+ readiness: 'ready',
221
+ metadata: {
222
+ ...(operation.metadata ?? {}),
223
+ coveredByOperationIds: [coveredBy.id]
224
+ }
225
+ };
226
+ });
227
+ }
228
+
229
+ function operationCoveredByExactSourceRange(operation, operations, sourceText) { return Boolean(exactSourceRangeCoveringOperation(operation, operations, sourceText)); }
230
+
231
+ function exactSourceRangeCoveringOperation(operation, operations, sourceText) {
232
+ if (operation.metadata?.sourceBackprojection?.mode === 'same-language-exact-source-map') return undefined;
233
+ const operationRange = spanOffsets(sourceText, operation.spans?.base ?? operation.anchor?.sourceSpan);
234
+ if (!operationRange) return undefined;
235
+ return operations.find((candidate) => {
236
+ if (candidate === operation || candidate.status === 'covered') return false;
237
+ const backprojection = candidate.metadata?.sourceBackprojection;
238
+ const candidateRange = spanOffsets(sourceText, backprojection?.sourceEditSpan);
239
+ return backprojection?.mode === 'same-language-exact-source-map' &&
240
+ sameSourcePath(operation, candidate) &&
241
+ containedRange(operationRange, candidateRange);
242
+ });
243
+ }
244
+
245
+ function sameSourcePath(left, right) {
246
+ const leftPath = left.anchor?.sourcePath ?? left.insertion?.sourcePath;
247
+ const rightPath = right.anchor?.sourcePath ?? right.insertion?.sourcePath;
248
+ return !leftPath || !rightPath || leftPath === rightPath;
249
+ }
250
+
251
+ function containedRange(inner, outer) { return Boolean(inner && outer && outer.start <= inner.start && inner.end <= outer.end); }
252
+
253
+ function portableSingleAnchorMatches(matches = [], targetPortability = {}) {
254
+ return matches.filter((match) => (
255
+ match.status === 'matched' &&
256
+ match.sourceAnchors.length === 1 &&
257
+ match.sourceMapLinks.length > 0 &&
258
+ match.portability?.status === 'portable' &&
259
+ targetPortability.status === 'portable'
260
+ ));
261
+ }
262
+
263
+ function projectionReasonCodes(context, matches) {
264
+ return uniqueStrings([
265
+ 'source-edit-script-projection-hint',
266
+ 'target-change-source-map-portable',
267
+ 'target-edit-requires-source-review',
268
+ ...array(context.reasons),
269
+ ...array(context.targetPortability?.reasonCodes),
270
+ ...matches.flatMap((match) => [...array(match.reasonCodes), ...array(match.portability?.reasonCodes)])
271
+ ]);
272
+ }
273
+
274
+ function sourceEditAdmissionReasonCodes(context, matches, exactBackprojection) {
275
+ if (!exactBackprojection) return projectionReasonCodes(context, matches);
276
+ return uniqueStrings([
277
+ 'source-edit-script-projection-hint',
278
+ 'target-change-source-map-portable',
279
+ 'source-map-backprojection-verified',
280
+ ...array(context.targetPortability?.reasonCodes).filter((reason) => !reviewOnlyReason(reason)),
281
+ ...matches.flatMap((match) => [...array(match.reasonCodes), ...array(match.portability?.reasonCodes)])
282
+ .filter((reason) => !reviewOnlyReason(reason))
283
+ ]);
284
+ }
285
+
286
+ function targetRegionForMatch(match, context) {
287
+ return context.targetChangeSet.changedRegions.find((region) => region.id === match.targetRegion?.id)
288
+ ?? match.targetRegion;
289
+ }
290
+
291
+ function semanticEditKind(region = {}) {
292
+ const prefix = region.changeKind === 'added' ? 'add' : region.changeKind === 'removed' ? 'remove' : 'replace';
293
+ if (region.regionKind === 'body') return `${prefix}Body`;
294
+ if (region.regionKind === 'import') return `${prefix}Import`;
295
+ if (region.regionKind === 'property') return `${prefix}Property`;
296
+ return `${prefix}Region`;
297
+ }
298
+
299
+ function projectionSourcePath(source, matches) {
300
+ return source?.sourcePath ?? matches.find((match) => match.sourceAnchors[0]?.sourcePath)?.sourceAnchors[0]?.sourcePath;
301
+ }
302
+
303
+ function sourceHash(source) {
304
+ return source?.nativeSource?.sourceHash ?? source?.nativeAst?.sourceHash ?? source?.sourceHash;
305
+ }
306
+
307
+ function reviewOnlyReason(reason) {
308
+ return ['target-edit-requires-source-review', 'source-port-review-required', 'human-source-port-required'].includes(reason);
309
+ }
310
+
311
+ function firstBackprojectionMode(operations) {
312
+ return operations.find((operation) => exactSourceBackprojectionModes.includes(operation.metadata?.sourceBackprojection?.mode))
313
+ ?.metadata?.sourceBackprojection?.mode;
314
+ }
315
+ function array(value) { return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value]; }
316
+
317
+ function compactRecord(value) {
318
+ return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
319
+ }
@@ -0,0 +1,67 @@
1
+ import { idFragment } from '../../native-import-utils.js';
2
+ import { nativeImportSourceText } from './nativeImportSourceText.js';
3
+ import { projectSemanticEditScriptToSource } from './projectSemanticEditScriptToSource.js';
4
+ import { replaySemanticEditProjection } from './replaySemanticEditProjection.js';
5
+
6
+ export function createBidirectionalSourceEditProjectionArtifacts(context = {}, sourceEditScript) {
7
+ if (!isExactBackprojectionScript(sourceEditScript)) return {};
8
+ const sourceText = nativeImportSourceText(context.source);
9
+ const workerSourceText = nativeImportSourceText(context.targetChangeSet?.after);
10
+ if (typeof sourceText !== 'string' || typeof workerSourceText !== 'string') return {};
11
+ const projection = projectSemanticEditScriptToSource({
12
+ id: `semantic_edit_projection_${idFragment(context.id)}_source_backprojection`,
13
+ script: sourceEditScript,
14
+ workerSourceText,
15
+ headSourceText: sourceText,
16
+ metadata: {
17
+ targetLanguage: context.targetChangeSet.language,
18
+ targetPath: context.targetChangeSet.sourcePath,
19
+ targetHash: context.targetChangeSet.afterHash,
20
+ sourceMapIds: sourceEditScript.metadata?.sourceProjectionHint?.sourceMapIds,
21
+ sourceMapLinkIds: sourceEditScript.metadata?.sourceProjectionHint?.sourceMapLinkIds,
22
+ sourceMapMappingIds: sourceEditScript.metadata?.sourceProjectionHint?.sourceMapMappingIds
23
+ }
24
+ });
25
+ const replay = projection.status === 'projected'
26
+ ? replaySemanticEditProjection({
27
+ id: `semantic_edit_replay_${idFragment(context.id)}_source_backprojection`,
28
+ projection,
29
+ currentSourceText: sourceText
30
+ })
31
+ : undefined;
32
+ return {
33
+ sourceEditProjection: projection,
34
+ sourceEditReplay: replay,
35
+ evidence: [backprojectionEvidence(context, sourceEditScript, projection, replay)]
36
+ };
37
+ }
38
+
39
+ function backprojectionEvidence(context, script, projection, replay) {
40
+ const replayReady = replay?.status === 'accepted-clean' || replay?.status === 'already-applied';
41
+ const passed = projection?.status === 'projected' && replayReady;
42
+ return {
43
+ id: `evidence_${idFragment(context.id)}_source_backprojection_replay`,
44
+ kind: 'verification',
45
+ status: passed ? 'passed' : 'failed',
46
+ path: script.sourcePath,
47
+ summary: passed
48
+ ? 'Verified exact source-map backprojection by projecting and replaying the source edit.'
49
+ : 'Exact source-map backprojection did not project and replay cleanly.',
50
+ metadata: {
51
+ schema: 'frontier.lang.bidirectionalSourceBackprojectionEvidence.v1',
52
+ bidirectionalTargetChangeId: context.id,
53
+ sourceEditScriptId: script.id,
54
+ sourceEditProjectionId: projection?.id,
55
+ sourceEditReplayId: replay?.id,
56
+ projectionStatus: projection?.status,
57
+ replayStatus: replay?.status,
58
+ autoMergeClaim: false,
59
+ semanticEquivalenceClaim: false
60
+ }
61
+ };
62
+ }
63
+
64
+ function isExactBackprojectionScript(script) {
65
+ return script?.admission?.status === 'auto-merge-candidate'
66
+ && ['same-language-exact-source-map', 'cross-language-explicit-source-replacement'].includes(script?.metadata?.sourceBackprojectionMode);
67
+ }
@@ -8,7 +8,7 @@ import { resolveSemanticLineage } from './semanticLineageResolutionRecords.js';
8
8
 
9
9
  export function matchTargetRegion(context) {
10
10
  const explicit = explicitMappingForRegion(context.region, context.mappings);
11
- const mapped = explicit ? { anchors: sourceAnchorsForMapping(explicit, context.sourceAnchors), links: [] }
11
+ const mapped = explicit ? { anchors: sourceAnchorsForMapping(explicit, context.sourceAnchors, context.region), links: [] }
12
12
  : sourceMapAnchorsForRegion(context.region, context.sourceMaps, context.sourceAnchors);
13
13
  const candidateAnchors = mapped.anchors.length ? mapped.anchors : sourceAnchorsByName(context.region, context.sourceAnchors);
14
14
  const resolvedAnchors = candidateAnchors.flatMap((anchor) => resolveAnchor(anchor, context.lineage));
@@ -184,6 +184,8 @@ function sourceMapLinkForMapping(sourceMap, mapping) {
184
184
  precision: mapping.precision,
185
185
  sourceSpan: mapping.sourceSpan,
186
186
  generatedSpan: mapping.generatedSpan,
187
+ sourceReplacementText: mapping.sourceReplacementText,
188
+ sourceReplacementTextHash: mapping.sourceReplacementTextHash ?? mapping.sourceReplacementHash,
187
189
  regionKey: mapping.ownershipRegionKey,
188
190
  regionKind: mapping.ownershipRegionKind
189
191
  });
@@ -222,19 +224,29 @@ function explicitMappingForRegion(region, mappings) {
222
224
  });
223
225
  }
224
226
 
225
- function sourceAnchorsForMapping(mapping, sourceAnchors) {
227
+ function sourceAnchorsForMapping(mapping, sourceAnchors, region) {
226
228
  const keys = uniqueStrings([mapping.sourceAnchorKey, mapping.sourceRegionKey, mapping.sourceConflictKey, mapping.sourceKey]);
227
229
  const matches = sourceAnchors.filter((anchor) => keys.includes(anchor.key) || keys.includes(anchor.id));
228
230
  if (matches.length) return matches;
229
- if (mapping.sourceSymbolName) return sourceAnchors.filter((anchor) => anchor.symbolName === mapping.sourceSymbolName);
230
- if (mapping.sourceSymbolId) return sourceAnchors.filter((anchor) => anchor.symbolId === mapping.sourceSymbolId);
231
+ const compatible = sourceAnchors.filter((anchor) => sourceAnchorCompatibleWithRegion(anchor, region));
232
+ if (mapping.sourceSymbolName) return compatible.filter((anchor) => anchor.symbolName === mapping.sourceSymbolName);
233
+ if (mapping.sourceSymbolId) return compatible.filter((anchor) => anchor.symbolId === mapping.sourceSymbolId);
231
234
  return [];
232
235
  }
233
236
 
234
237
  function sourceAnchorsByName(region, sourceAnchors) {
235
238
  const names = namesForRegion(region);
236
239
  if (names.length === 0) return [];
237
- return sourceAnchors.filter((anchor) => names.includes(anchor.symbolName));
240
+ return sourceAnchors
241
+ .filter((anchor) => sourceAnchorCompatibleWithRegion(anchor, region))
242
+ .filter((anchor) => names.includes(anchor.symbolName));
243
+ }
244
+
245
+ function sourceAnchorCompatibleWithRegion(anchor, region) {
246
+ const sourceKind = String(anchor?.kind ?? '').toLowerCase();
247
+ const targetKind = String(region?.regionKind ?? '').toLowerCase();
248
+ if (sourceKind !== 'export') return true;
249
+ return targetKind === 'export' || targetKind === 'module';
238
250
  }
239
251
 
240
252
  function namesForRegion(region) {
@@ -52,27 +52,37 @@ export function createRoundtripEvidence(context) {
52
52
  reasonCodes: context.targetPortability.reasonCodes,
53
53
  conflictKeys: context.targetPortability.conflictKeys
54
54
  },
55
- admission: {
56
- status: context.readiness === 'blocked' ? 'blocked' : 'needs-review',
57
- readiness: context.readiness,
58
- action: context.targetPortability.action,
59
- reasonCodes: context.reasons,
60
- conflictKeys: uniqueStrings([
61
- ...array(context.targetPortability.conflictKeys),
62
- ...context.sourceAnchorMatches.flatMap((match) => match.conflictKeys ?? [])
63
- ]),
64
- evidenceIds: [context.evidenceId],
65
- reviewRequired: true,
66
- autoMergeClaim: false,
67
- semanticEquivalenceClaim: false
68
- },
69
- reviewRequired: true,
55
+ admission: admissionRecord(context),
56
+ reviewRequired: !verifiedSourceBackprojection(context),
57
+ autoMergeClaim: false,
58
+ semanticEquivalenceClaim: false
59
+ };
60
+ }
61
+
62
+ function admissionRecord(context) {
63
+ const verified = verifiedSourceBackprojection(context);
64
+ const verifiedReason = verifiedSourceBackprojectionReason(context);
65
+ const action = sourceBackprojectionAction(context);
66
+ return {
67
+ status: verified ? 'ready' : context.readiness === 'blocked' ? 'blocked' : 'needs-review',
68
+ readiness: verified ? 'ready' : context.readiness,
69
+ action: verified ? action : context.targetPortability.action,
70
+ reasonCodes: uniqueStrings([...array(context.reasons), verifiedReason]),
71
+ conflictKeys: uniqueStrings([
72
+ ...array(context.targetPortability.conflictKeys),
73
+ ...context.sourceAnchorMatches.flatMap((match) => match.conflictKeys ?? [])
74
+ ]),
75
+ evidenceIds: uniqueStrings([context.evidenceId, ...array(context.sourceEditProjection?.evidence).map((record) => record.id)]),
76
+ reviewRequired: !verified,
70
77
  autoMergeClaim: false,
71
78
  semanticEquivalenceClaim: false
72
79
  };
73
80
  }
74
81
 
75
82
  export function createSemanticMergeAdmissionEvidence(context) {
83
+ const verified = verifiedSourceBackprojection(context);
84
+ const verifiedReason = verifiedSourceBackprojectionReason(context);
85
+ const action = sourceBackprojectionAction(context);
76
86
  return {
77
87
  schema: 'frontier.lang.bidirectionalTargetChangeSemanticMergeAdmission.v1',
78
88
  version: 1,
@@ -83,10 +93,10 @@ export function createSemanticMergeAdmissionEvidence(context) {
83
93
  targetChangeSetId: context.targetChangeSet.id,
84
94
  targetPatchId: context.targetChangeSet.patch?.id,
85
95
  targetMergeCandidateId: context.targetChangeSet.mergeCandidate?.id,
86
- readiness: context.readiness,
87
- status: context.readiness === 'blocked' ? 'blocked' : 'needs-review',
88
- action: context.targetPortability.action,
89
- reasonCodes: context.reasons,
96
+ readiness: verified ? 'ready' : context.readiness,
97
+ status: verified ? 'ready' : context.readiness === 'blocked' ? 'blocked' : 'needs-review',
98
+ action: verified ? action : context.targetPortability.action,
99
+ reasonCodes: uniqueStrings([...array(context.reasons), verifiedReason]),
90
100
  conflictKeys: context.roundtripEvidence.admission.conflictKeys,
91
101
  sourceAnchorMatchIds: uniqueStrings(context.sourceAnchorMatches.map((match) => match.id)),
92
102
  sourceAnchorKeys: uniqueStrings(context.roundtripEvidence.sourceAnchors.map((anchor) => anchor.key)),
@@ -98,12 +108,40 @@ export function createSemanticMergeAdmissionEvidence(context) {
98
108
  targetProjectionSourceMapLinkIds: context.roundtripEvidence.sourceMapEvidence.targetProjectionSourceMapLinkIds,
99
109
  targetProjectionSourceMapMappingIds: context.roundtripEvidence.sourceMapEvidence.targetProjectionSourceMapMappingIds,
100
110
  lineageResolutionIds: context.roundtripEvidence.lineageEvidence.lineageResolutionIds,
101
- reviewRequired: true,
111
+ sourceEditScriptId: context.sourceEditProjection?.sourceEditScript?.id,
112
+ sourceEditProjectionId: context.sourceEditProjection?.sourceEditProjection?.id,
113
+ sourceEditReplayId: context.sourceEditProjection?.sourceEditReplay?.id,
114
+ sourceBackprojectionMode: context.sourceEditProjection?.sourceProjectionHint?.sourceBackprojectionMode,
115
+ reviewRequired: !verified,
102
116
  autoMergeClaim: false,
103
117
  semanticEquivalenceClaim: false
104
118
  };
105
119
  }
106
120
 
121
+ function verifiedSourceBackprojection(context) {
122
+ return verifiedSourceBackprojectionModes().includes(context.sourceEditProjection?.sourceProjectionHint?.sourceBackprojectionMode)
123
+ && context.sourceEditProjection?.sourceEditProjection?.status === 'projected'
124
+ && ['accepted-clean', 'already-applied'].includes(context.sourceEditProjection?.sourceEditReplay?.status);
125
+ }
126
+
127
+ function verifiedSourceBackprojectionModes() {
128
+ return ['same-language-exact-source-map', 'same-language-target-source-edit', 'cross-language-explicit-source-replacement'];
129
+ }
130
+
131
+ function verifiedSourceBackprojectionReason(context) {
132
+ const mode = context.sourceEditProjection?.sourceProjectionHint?.sourceBackprojectionMode;
133
+ if (mode === 'cross-language-explicit-source-replacement' && verifiedSourceBackprojection(context)) return 'verified-cross-language-explicit-source-replacement';
134
+ if (mode === 'same-language-target-source-edit' && verifiedSourceBackprojection(context)) return 'verified-same-language-target-source-edit';
135
+ if (mode === 'same-language-exact-source-map' && verifiedSourceBackprojection(context)) return 'verified-source-map-backprojection';
136
+ return undefined;
137
+ }
138
+
139
+ function sourceBackprojectionAction(context) {
140
+ return context.sourceEditProjection?.sourceEditReplay?.status === 'already-applied'
141
+ ? 'skip-source-backprojection'
142
+ : 'admit-source-backprojection';
143
+ }
144
+
107
145
  function sourceIdentity(source) {
108
146
  return compactRecord({
109
147
  importId: source?.id,