@shapeshift-labs/frontier-lang-compiler 0.2.100 → 0.2.101

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 (43) hide show
  1. package/dist/declarations/bidirectional-target-change-evidence.d.ts +299 -0
  2. package/dist/declarations/bidirectional-target-change.d.ts +19 -120
  3. package/dist/declarations/native-project-admission.d.ts +43 -22
  4. package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +24 -0
  5. package/dist/declarations/semantic-edit-script.d.ts +20 -15
  6. package/dist/declarations/semantic-lineage.d.ts +3 -21
  7. package/dist/declarations/semantic-merge-candidates.d.ts +39 -0
  8. package/dist/declarations/semantic-sidecar-admission.d.ts +14 -0
  9. package/dist/declarations/semantic-sidecar.d.ts +12 -14
  10. package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +200 -0
  11. package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +62 -17
  12. package/dist/internal/index-impl/createNativeSourcePreservation.js +16 -1
  13. package/dist/internal/index-impl/createProjectImportAdmissionRecord.js +151 -1
  14. package/dist/internal/index-impl/createSemanticImportSidecar.js +5 -0
  15. package/dist/internal/index-impl/createSemanticImportSidecarAdmission.js +29 -11
  16. package/dist/internal/index-impl/nativeChangeProjectionEndpoint.js +56 -16
  17. package/dist/internal/index-impl/projectImportAdmissionMergeScore.js +26 -74
  18. package/dist/internal/index-impl/projectImportAdmissionProjectionCoverage.js +74 -0
  19. package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +39 -13
  20. package/dist/internal/index-impl/replaySemanticEditProjection.js +65 -23
  21. package/dist/internal/index-impl/semanticEditInsertionAnchors.js +8 -5
  22. package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +167 -0
  23. package/dist/internal/index-impl/semanticEditSourceRanges.js +94 -15
  24. package/dist/internal/index-impl/semanticHistoryLineageResolution.js +21 -2
  25. package/dist/internal/index-impl/semanticLineageHashEvidence.js +97 -0
  26. package/dist/internal/index-impl/semanticLineageInferenceMatching.js +8 -0
  27. package/dist/internal/index-impl/semanticLineageResolutionRecords.js +18 -1
  28. package/dist/internal/index-impl/semanticMergeCandidateRecords.js +22 -2
  29. package/dist/internal/index-impl/semanticMergeCandidateScoreFacets.js +221 -0
  30. package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +23 -1
  31. package/dist/internal/index-impl/sourcePreservationFromProjectionContext.js +9 -2
  32. package/dist/native-import-language-profiles.js +10 -2
  33. package/dist/native-region-scanner-js-helpers.js +8 -2
  34. package/dist/native-region-scanner-js-imports.js +7 -0
  35. package/dist/native-region-scanner-js.js +4 -4
  36. package/dist/native-region-scanner.js +2 -1
  37. package/dist/semantic-import-regions.js +6 -4
  38. package/dist/semantic-import-sidecar-admission-types.d.ts +14 -0
  39. package/dist/semantic-import-sidecar-entry.js +151 -7
  40. package/dist/semantic-import-sidecar-types.d.ts +18 -13
  41. package/dist/semantic-import-source-preservation-utils.js +55 -0
  42. package/dist/semantic-import-source-preservation.js +98 -3
  43. package/package.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { semanticImportSidecarQualityRecord } from '../../semantic-import-sidecar-entry.js';
2
+
1
3
  function sidecarSourcePaths(importEntries) {
2
4
  return [...new Set(importEntries.map((entry) => entry.sourcePath).filter(Boolean))];
3
5
  }
@@ -15,10 +17,19 @@ function sidecarQualityWarning(code, message, action, sourcePaths) {
15
17
  export function createSemanticImportSidecarQuality(input) {
16
18
  const { importEntries, symbols, ownershipRegions, patchHints, proofSpec, evidence, readiness } = input;
17
19
  const expected = input.expected === true;
20
+ const expectedEmpty = input.expectedEmpty === true || input.semanticImportExpectedEmpty === true;
18
21
  const sourcePaths = sidecarSourcePaths(importEntries);
19
22
  const importCount = importEntries.length;
23
+ const record = semanticImportSidecarQualityRecord({
24
+ expected,
25
+ expectedEmpty,
26
+ importCount,
27
+ symbolCount: symbols.length,
28
+ sourcePaths
29
+ });
30
+ const expectedEmptySatisfied = record.classification === 'expected-empty';
20
31
  const warnings = [];
21
- if (expected && importCount === 0) warnings.push(sidecarQualityWarning(
32
+ if ((expected || expectedEmpty) && importCount === 0) warnings.push(sidecarQualityWarning(
22
33
  'expected-semantic-import-missing',
23
34
  'Semantic import was expected but no import entries were selected.',
24
35
  'check-semantic-import-include-globs-and-workspace-paths',
@@ -30,31 +41,31 @@ export function createSemanticImportSidecarQuality(input) {
30
41
  'run-native-import',
31
42
  sourcePaths
32
43
  ));
33
- if (expected && importCount > 0 && symbols.length === 0) warnings.push(sidecarQualityWarning(
44
+ if (!expectedEmptySatisfied && expected && importCount > 0 && symbols.length === 0) warnings.push(sidecarQualityWarning(
34
45
  'expected-semantic-import-empty',
35
46
  'Semantic import was expected but selected imports produced zero semantic symbols.',
36
47
  'rerun-importer-with-semantic-source-selection',
37
48
  sourcePaths
38
49
  ));
39
- if (importCount > 0 && symbols.length === 0) warnings.push(sidecarQualityWarning(
50
+ if (!expectedEmptySatisfied && importCount > 0 && symbols.length === 0) warnings.push(sidecarQualityWarning(
40
51
  'empty-semantic-index',
41
52
  'Semantic sidecar has import entries but no semantic symbols.',
42
53
  'rerun-importer-with-semantic-index',
43
54
  sourcePaths
44
55
  ));
45
- if (importCount > 0 && ownershipRegions.length === 0) warnings.push(sidecarQualityWarning(
56
+ if (!expectedEmptySatisfied && importCount > 0 && ownershipRegions.length === 0) warnings.push(sidecarQualityWarning(
46
57
  'missing-ownership-regions',
47
58
  'Semantic sidecar has no ownership regions for safe merge ownership.',
48
59
  'rerun-sidecar-generation-with-ownership-regions',
49
60
  sourcePaths
50
61
  ));
51
- if (importCount > 0 && patchHints.length === 0) warnings.push(sidecarQualityWarning(
62
+ if (!expectedEmptySatisfied && importCount > 0 && patchHints.length === 0) warnings.push(sidecarQualityWarning(
52
63
  'missing-patch-hints',
53
64
  'Semantic sidecar has no patch hints.',
54
65
  'generate-semantic-patch-hints',
55
66
  sourcePaths
56
67
  ));
57
- if (importCount > 0 && evidence.length === 0) warnings.push(sidecarQualityWarning(
68
+ if (!expectedEmptySatisfied && importCount > 0 && evidence.length === 0) warnings.push(sidecarQualityWarning(
58
69
  'empty-evidence',
59
70
  'Semantic sidecar has no evidence records.',
60
71
  'attach-semantic-import-evidence',
@@ -116,16 +127,18 @@ export function createSemanticImportSidecarQuality(input) {
116
127
  warning.code === 'missing-ownership-regions' ||
117
128
  warning.code === 'missing-patch-hints'
118
129
  ));
119
- const expectedMissingReasonCodes = expected
120
- ? emptyEvidenceWarnings.map((warning) => warning.code)
121
- : [];
122
- const expectedSatisfied = !expected || (
130
+ const expectedSatisfied = expectedEmpty
131
+ ? expectedEmptySatisfied
132
+ : !expected || (
123
133
  importCount > 0 &&
124
134
  symbols.length > 0 &&
125
135
  ownershipRegions.length > 0 &&
126
136
  patchHints.length > 0 &&
127
137
  evidence.length > 0
128
138
  );
139
+ const expectedMissingReasonCodes = (expected || expectedEmpty) && !expectedSatisfied
140
+ ? emptyEvidenceWarnings.map((warning) => warning.code)
141
+ : [];
129
142
  const proofSummary = {
130
143
  total: proofSpec.total,
131
144
  obligations: proofSpec.obligations,
@@ -143,12 +156,14 @@ export function createSemanticImportSidecarQuality(input) {
143
156
  return {
144
157
  schema: 'frontier.lang.semanticSidecarQuality.v1',
145
158
  expected,
159
+ expectedEmpty,
146
160
  expectedSatisfied,
147
161
  expectedMissingReasonCodes,
148
162
  selected: importCount > 0,
149
- eligible: importCount > 0 && emptyEvidenceWarnings.length === 0 && proofSpec.failed === 0 && readiness !== 'blocked',
163
+ eligible: record.classification === 'useful' && emptyEvidenceWarnings.length === 0 && proofSpec.failed === 0 && readiness !== 'blocked',
150
164
  imported: importCount > 0,
151
165
  importCount,
166
+ record,
152
167
  symbolCount: symbols.length,
153
168
  ownershipRegionCount: ownershipRegions.length,
154
169
  patchHintCount: patchHints.length,
@@ -164,6 +179,7 @@ export function createSemanticImportSidecarAdmission(quality, readiness) {
164
179
  return {
165
180
  schema: 'frontier.lang.semanticSidecarAdmission.v1',
166
181
  expected: quality.expected,
182
+ expectedEmpty: quality.expectedEmpty,
167
183
  expectedSatisfied: quality.expectedSatisfied,
168
184
  expectedMissingReasonCodes: quality.expectedMissingReasonCodes,
169
185
  selected: quality.selected,
@@ -172,6 +188,7 @@ export function createSemanticImportSidecarAdmission(quality, readiness) {
172
188
  importCount: quality.importCount,
173
189
  readiness,
174
190
  action: sidecarAdmissionAction(quality, readiness),
191
+ record: quality.record,
175
192
  counts: {
176
193
  symbols: quality.symbolCount,
177
194
  ownershipRegions: quality.ownershipRegionCount,
@@ -191,6 +208,7 @@ function sidecarAdmissionAction(quality, readiness) {
191
208
  if (!quality.imported) return 'reject-missing-imports';
192
209
  if (readiness === 'blocked') return 'reject-blocked';
193
210
  if (quality.proofSummary.failed > 0) return 'reject-failed-proof';
211
+ if (quality.record?.classification === 'expected-empty') return 'skip-expected-empty';
194
212
  if (quality.emptyEvidenceWarnings.length > 0) return 'reject-empty-evidence';
195
213
  if (!quality.eligible) return 'reject-quality';
196
214
  if (sidecarProofReviewObligations(quality.proofSummary) > 0) return 'review-proof-obligations';
@@ -1,4 +1,4 @@
1
- import{nativeChangeMappingTouchesRegion}from'./nativeChangeMappingTouchesRegion.js';import{nativeImportSourcePreservationRecord}from'./nativeImportSourcePreservationRecord.js';
1
+ import{uniqueStrings}from'../../native-import-utils.js';import{nativeChangeMappingTouchesRegion}from'./nativeChangeMappingTouchesRegion.js';import{nativeImportSourcePreservationRecord}from'./nativeImportSourcePreservationRecord.js';
2
2
  export function nativeChangeProjectionEndpoint(imported, sidecar, region, side) {
3
3
  if (!imported && !region) return undefined;
4
4
  const preservation = nativeImportSourcePreservationRecord(imported);
@@ -6,23 +6,63 @@ export function nativeChangeProjectionEndpoint(imported, sidecar, region, side)
6
6
  const regionMappings = sourceMaps
7
7
  .flatMap((sourceMap) => (sourceMap?.mappings ?? []).map((mapping) => ({ sourceMap, mapping })))
8
8
  .filter(({ mapping }) => nativeChangeMappingTouchesRegion(mapping, region, []));
9
+ const sourceMapIds = uniqueStrings(sourceMaps.map((sourceMap) => sourceMap?.id));
10
+ const sourceMapMappingIds = uniqueStrings(regionMappings.map(({ mapping }) => mapping?.id));
11
+ const importId = imported?.id;
12
+ const nativeSourceId = imported?.nativeSource?.id;
13
+ const nativeAstId = imported?.nativeAst?.id;
14
+ const semanticIndexId = imported?.semanticIndex?.id;
15
+ const universalAstId = imported?.universalAst?.id;
16
+ const sourcePath = imported?.sourcePath ?? region?.sourcePath;
17
+ const sourceHash = imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash ?? region?.sourceHash;
18
+ const sourcePreservationId = preservation?.id;
19
+ const exactSourceAvailable = preservation?.summary?.exactSourceAvailable === true;
20
+ const ownershipRegionId = region?.id;
21
+ const ownershipKey = region?.key;
22
+ const ownershipRegionKind = region?.regionKind;
23
+ const sourceSpan = region?.sourceSpan;
24
+ const identity = compactRecord({
25
+ schema: 'frontier.lang.nativeChangeProjectionEndpointIdentity.v1',
26
+ version: 1,
27
+ side,
28
+ importId,
29
+ nativeSourceId,
30
+ nativeAstId,
31
+ semanticIndexId,
32
+ universalAstId,
33
+ sourcePath,
34
+ sourceHash,
35
+ sourcePreservationId,
36
+ exactSourceAvailable,
37
+ ownershipRegionId,
38
+ ownershipKey,
39
+ ownershipRegionKind,
40
+ sourceSpan,
41
+ sourceMapIds,
42
+ sourceMapMappingIds
43
+ });
9
44
  return {
10
45
  side,
11
- importId: imported?.id,
46
+ importId,
12
47
  sidecarId: sidecar?.id,
13
- nativeSourceId: imported?.nativeSource?.id,
14
- nativeAstId: imported?.nativeAst?.id,
15
- semanticIndexId: imported?.semanticIndex?.id,
16
- universalAstId: imported?.universalAst?.id,
17
- sourcePath: imported?.sourcePath ?? region?.sourcePath,
18
- sourceHash: imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash ?? region?.sourceHash,
19
- sourcePreservationId: preservation?.id,
20
- exactSourceAvailable: preservation?.summary?.exactSourceAvailable === true,
21
- ownershipRegionId: region?.id,
22
- ownershipKey: region?.key,
23
- ownershipRegionKind: region?.regionKind,
24
- sourceSpan: region?.sourceSpan,
25
- sourceMapIds: sourceMaps.map((sourceMap) => sourceMap?.id).filter(Boolean),
26
- sourceMapMappingIds: regionMappings.map(({ mapping }) => mapping?.id).filter(Boolean)
48
+ nativeSourceId,
49
+ nativeAstId,
50
+ semanticIndexId,
51
+ universalAstId,
52
+ sourcePath,
53
+ sourceHash,
54
+ sourcePreservationId,
55
+ exactSourceAvailable,
56
+ ownershipRegionId,
57
+ ownershipKey,
58
+ ownershipRegionKind,
59
+ sourceSpan,
60
+ sourceMapIds,
61
+ sourceMapMappingIds,
62
+ identity
27
63
  };
28
64
  }
65
+
66
+ function compactRecord(value) {
67
+ return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
68
+ }
@@ -1,4 +1,5 @@
1
1
  import{uniqueRecordsById,uniqueStrings}from'../../native-import-utils.js';
2
+ import{targetProjectionCoverageSignals}from'./projectImportAdmissionProjectionCoverage.js';
2
3
 
3
4
  const scoreWeights=Object.freeze({
4
5
  semanticEvidence:22,
@@ -95,15 +96,35 @@ function sourcePreservationScore(input){
95
96
 
96
97
  function sourceFreshnessScore(input){
97
98
  const sourceCount=Math.max(input.sourceCount??0,input.sourcePreservation.total,1);
98
- const fresh=Math.max(0,sourceCount-input.sourcePreservation.stale);
99
+ const sourceStaleness=input.sourceStaleness??{};
100
+ const stale=sourceStaleness.stale??input.sourcePreservation.stale;
101
+ const contentHashStale=sourceStaleness.contentHashStale??input.sourcePreservation.contentHashStale??0;
102
+ const baseHashStale=sourceStaleness.baseHashStale??input.sourcePreservation.baseHashStale??0;
103
+ const dirtyWorkspace=sourceStaleness.dirtyWorkspace??input.sourcePreservation.dirtyWorkspace??0;
104
+ const fresh=Math.max(0,sourceCount-stale);
99
105
  const score=roundScore(fresh*100/sourceCount);
100
106
  return scoreComponent('sourceFreshness',score,[
101
- ...(input.sourcePreservation.stale?[
102
- `Project import has stale source hashes for ${input.sourcePreservation.stale} source(s).`
107
+ ...(contentHashStale?[
108
+ `Project import has stale content hashes for ${contentHashStale} source(s).`
109
+ ]:[]),
110
+ ...(baseHashStale?[
111
+ `Project import has stale base hashes for ${baseHashStale} source(s).`
112
+ ]:[]),
113
+ ...(!contentHashStale&&!baseHashStale&&stale?[
114
+ `Project import has stale source hashes for ${stale} source(s).`
115
+ ]:[]),
116
+ ...(dirtyWorkspace&&!stale?[
117
+ `Project workspace is marked dirty for ${dirtyWorkspace} source(s), but no content or base hash staleness was detected.`
103
118
  ]:[])
104
119
  ],{
105
- stale:input.sourcePreservation.stale,
106
- staleSourcePaths:input.sourcePreservation.staleSourcePaths,
120
+ stale,
121
+ contentHashStale,
122
+ baseHashStale,
123
+ dirtyWorkspace,
124
+ staleSourcePaths:sourceStaleness.staleSourcePaths??input.sourcePreservation.staleSourcePaths,
125
+ contentHashStaleSourcePaths:sourceStaleness.contentHashStaleSourcePaths??input.sourcePreservation.contentHashStaleSourcePaths??[],
126
+ baseHashStaleSourcePaths:sourceStaleness.baseHashStaleSourcePaths??input.sourcePreservation.baseHashStaleSourcePaths??[],
127
+ dirtyWorkspaceSourcePaths:sourceStaleness.dirtyWorkspaceSourcePaths??input.sourcePreservation.dirtyWorkspaceSourcePaths??[],
107
128
  checkedSources:sourceCount
108
129
  });
109
130
  }
@@ -186,75 +207,6 @@ function targetProjectionCoverageScore(input){
186
207
  ],coverage);
187
208
  }
188
209
 
189
- function targetProjectionCoverageSignals(input){
190
- const entries=targetProjectionEntries(input.projectResult,input.imports);
191
- const matrices=projectionMatrices(input.projectResult,input.imports);
192
- for(const matrix of matrices){
193
- for(const language of matrix?.languages??[]) entries.push(...(language?.targets??[]));
194
- }
195
- const sourceMapSummary=input.contract?.sourceMaps??{};
196
- const summary=matrices.reduce((current,matrix)=>{
197
- current.exactSourceProjection+=matrix?.summary?.exactSourceProjection??0;
198
- current.targetAdapterProjection+=matrix?.summary?.targetAdapterProjection??0;
199
- current.missingAdapters+=matrix?.summary?.missingAdapters??0;
200
- current.unsupportedTargetFeatures+=matrix?.summary?.unsupportedTargetFeatures??0;
201
- return current;
202
- },{exactSourceProjection:0,targetAdapterProjection:0,missingAdapters:0,unsupportedTargetFeatures:0});
203
- const targetEntries=entries.length;
204
- const supportedTargets=entries.filter((entry)=>entry?.supported===true).length;
205
- const adapterProjectionTargets=entries.filter((entry)=>
206
- entry?.lossClass==='targetAdapterProjection'||entry?.lossClass==='exactSourceProjection'||entry?.adapter||entry?.adapterKind==='targetProjection'
207
- ).length+summary.targetAdapterProjection+summary.exactSourceProjection;
208
- const readinessValues=entries.map((entry)=>readinessScore[entry?.readiness]??45);
209
- const readinessAverage=readinessValues.length?readinessValues.reduce((sum,value)=>sum+value,0)/readinessValues.length:0;
210
- return {
211
- targetEntries,
212
- supportedTargets,
213
- adapterProjectionTargets,
214
- exactSourceProjection:Math.max(summary.exactSourceProjection,input.sourcePreservation.exactSourceAvailable??0),
215
- targetAdapterProjection:summary.targetAdapterProjection,
216
- missingAdapters:summary.missingAdapters+entries.filter((entry)=>entry?.lossClass==='missingAdapter'||entry?.supported===false).length,
217
- unsupportedTargetFeatures:summary.unsupportedTargetFeatures+entries.filter((entry)=>entry?.lossClass==='unsupportedTargetFeatures').length,
218
- readinessScore:roundScore(readinessAverage),
219
- sourceMapMappings:sourceMapSummary.mappingCount??0,
220
- generatedRangeMappings:sourceMapSummary.generatedRangeMappings??0,
221
- targetPaths:sourceMapSummary.targetPaths?.length??0,
222
- adapterGeneratedRanges:input.contract?.adapterCoverage?.generatedRanges??0
223
- };
224
- }
225
-
226
- function targetProjectionEntries(projectResult,imports){
227
- return [
228
- projectResult?.targetCoverage,
229
- projectResult?.metadata?.targetCoverage,
230
- projectResult?.metadata?.targetProjectionCoverage,
231
- ...(projectResult?.targetCoverages??[]),
232
- ...(projectResult?.metadata?.targetCoverages??[]),
233
- ...(imports??[]).flatMap((imported)=>[
234
- imported?.targetCoverage,
235
- imported?.metadata?.targetCoverage,
236
- imported?.metadata?.targetProjectionCoverage,
237
- ...(imported?.targetCoverages??[]),
238
- ...(imported?.metadata?.targetCoverages??[])
239
- ])
240
- ].flatMap((entry)=>Array.isArray(entry)?entry:[entry]).filter((entry)=>entry&&typeof entry==='object'&&(entry.target||entry.lossClass||entry.supported!==undefined));
241
- }
242
-
243
- function projectionMatrices(projectResult,imports){
244
- return [
245
- projectResult?.projectionMatrix,
246
- projectResult?.metadata?.projectionMatrix,
247
- ...(projectResult?.projectionMatrices??[]),
248
- ...(projectResult?.metadata?.projectionMatrices??[]),
249
- ...(imports??[]).flatMap((imported)=>[
250
- imported?.projectionMatrix,
251
- imported?.metadata?.projectionMatrix,
252
- ...(imported?.projectionMatrices??[]),
253
- ...(imported?.metadata?.projectionMatrices??[])
254
- ])
255
- ].filter((matrix)=>matrix?.kind==='frontier.lang.projectionTargetLossMatrix'||Array.isArray(matrix?.languages));
256
- }
257
-
258
210
  function admissionEvidenceRecords(projectResult,imports){
259
211
  return uniqueRecordsById([
260
212
  ...(projectResult?.evidence??[]),
@@ -0,0 +1,74 @@
1
+ const readinessScore = Object.freeze({ ready: 100, 'ready-with-losses': 75, 'needs-review': 45, blocked: 0 });
2
+
3
+ export function targetProjectionCoverageSignals(input) {
4
+ const entries = targetProjectionEntries(input.projectResult, input.imports);
5
+ const matrices = projectionMatrices(input.projectResult, input.imports);
6
+ for (const matrix of matrices) {
7
+ for (const language of matrix?.languages ?? []) entries.push(...(language?.targets ?? []));
8
+ }
9
+ const sourceMapSummary = input.contract?.sourceMaps ?? {};
10
+ const summary = matrices.reduce((current, matrix) => {
11
+ current.exactSourceProjection += matrix?.summary?.exactSourceProjection ?? 0;
12
+ current.targetAdapterProjection += matrix?.summary?.targetAdapterProjection ?? 0;
13
+ current.missingAdapters += matrix?.summary?.missingAdapters ?? 0;
14
+ current.unsupportedTargetFeatures += matrix?.summary?.unsupportedTargetFeatures ?? 0;
15
+ return current;
16
+ }, { exactSourceProjection: 0, targetAdapterProjection: 0, missingAdapters: 0, unsupportedTargetFeatures: 0 });
17
+ const targetEntries = entries.length;
18
+ const supportedTargets = entries.filter((entry) => entry?.supported === true).length;
19
+ const adapterProjectionTargets = entries.filter((entry) =>
20
+ entry?.lossClass === 'targetAdapterProjection' || entry?.lossClass === 'exactSourceProjection' || entry?.adapter || entry?.adapterKind === 'targetProjection'
21
+ ).length + summary.targetAdapterProjection + summary.exactSourceProjection;
22
+ const readinessValues = entries.map((entry) => readinessScore[entry?.readiness] ?? 45);
23
+ const readinessAverage = readinessValues.length ? readinessValues.reduce((sum, value) => sum + value, 0) / readinessValues.length : 0;
24
+ return {
25
+ targetEntries,
26
+ supportedTargets,
27
+ adapterProjectionTargets,
28
+ exactSourceProjection: Math.max(summary.exactSourceProjection, input.sourcePreservation.exactSourceAvailable ?? 0),
29
+ targetAdapterProjection: summary.targetAdapterProjection,
30
+ missingAdapters: summary.missingAdapters + entries.filter((entry) => entry?.lossClass === 'missingAdapter' || entry?.supported === false).length,
31
+ unsupportedTargetFeatures: summary.unsupportedTargetFeatures + entries.filter((entry) => entry?.lossClass === 'unsupportedTargetFeatures').length,
32
+ readinessScore: roundScore(readinessAverage),
33
+ sourceMapMappings: sourceMapSummary.mappingCount ?? 0,
34
+ generatedRangeMappings: sourceMapSummary.generatedRangeMappings ?? 0,
35
+ targetPaths: sourceMapSummary.targetPaths?.length ?? 0,
36
+ adapterGeneratedRanges: input.contract?.adapterCoverage?.generatedRanges ?? 0
37
+ };
38
+ }
39
+
40
+ function targetProjectionEntries(projectResult, imports) {
41
+ return [
42
+ projectResult?.targetCoverage,
43
+ projectResult?.metadata?.targetCoverage,
44
+ projectResult?.metadata?.targetProjectionCoverage,
45
+ ...(projectResult?.targetCoverages ?? []),
46
+ ...(projectResult?.metadata?.targetCoverages ?? []),
47
+ ...(imports ?? []).flatMap((imported) => [
48
+ imported?.targetCoverage,
49
+ imported?.metadata?.targetCoverage,
50
+ imported?.metadata?.targetProjectionCoverage,
51
+ ...(imported?.targetCoverages ?? []),
52
+ ...(imported?.metadata?.targetCoverages ?? [])
53
+ ])
54
+ ].flatMap((entry) => Array.isArray(entry) ? entry : [entry]).filter((entry) => entry && typeof entry === 'object' && (entry.target || entry.lossClass || entry.supported !== undefined));
55
+ }
56
+
57
+ function projectionMatrices(projectResult, imports) {
58
+ return [
59
+ projectResult?.projectionMatrix,
60
+ projectResult?.metadata?.projectionMatrix,
61
+ ...(projectResult?.projectionMatrices ?? []),
62
+ ...(projectResult?.metadata?.projectionMatrices ?? []),
63
+ ...(imports ?? []).flatMap((imported) => [
64
+ imported?.projectionMatrix,
65
+ imported?.metadata?.projectionMatrix,
66
+ ...(imported?.projectionMatrices ?? []),
67
+ ...(imported?.metadata?.projectionMatrices ?? [])
68
+ ])
69
+ ].filter((matrix) => matrix?.kind === 'frontier.lang.projectionTargetLossMatrix' || Array.isArray(matrix?.languages));
70
+ }
71
+
72
+ function roundScore(value) {
73
+ return Math.round((Number.isFinite(value) ? value : 0) * 100) / 100;
74
+ }
@@ -1,5 +1,8 @@
1
1
  import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
- import { idFragment, uniqueStrings } from '../../native-import-utils.js';
2
+ import { idFragment, normalizeNativeLanguageId, uniqueStrings } from '../../native-import-utils.js';
3
+ import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
4
+ import { mapDiffSymbols } from './mapDiffSymbols.js';
5
+ import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
3
6
  import { semanticEditIdentityFields } from './semanticEditIdentityRecords.js';
4
7
  import {
5
8
  insertionOffset,
@@ -10,7 +13,6 @@ import {
10
13
  spanOffsets
11
14
  } from './semanticEditSourceRanges.js';
12
15
  import { applySourceEdits, dedupeSourceEdits, validateSourceEdits } from './semanticSourceEditDedupe.js';
13
-
14
16
  export function projectSemanticEditScriptToSource(input = {}) {
15
17
  const script = input.script;
16
18
  const workerSourceText = input.workerSourceText;
@@ -19,6 +21,15 @@ export function projectSemanticEditScriptToSource(input = {}) {
19
21
  if (!script) throw new Error('projectSemanticEditScriptToSource requires a script');
20
22
  if (typeof workerSourceText !== 'string') reasonCodes.push('missing-worker-source-text');
21
23
  if (typeof headSourceText !== 'string') reasonCodes.push('missing-head-source-text');
24
+ const language = normalizeNativeLanguageId(script.language);
25
+ const headSymbols = typeof headSourceText === 'string' && isJavaScriptLike(language)
26
+ ? sourceSymbolIndex({
27
+ sourceText: headSourceText,
28
+ sourcePath: input.headSourcePath ?? script.sourcePath,
29
+ language,
30
+ parser: input.parser
31
+ })
32
+ : [];
22
33
  const edits = [];
23
34
  const coveredOperationIds = [];
24
35
  const projectionCoveredOperationIds = projectionCoveredContainerOperationIds(script.operations ?? [], workerSourceText);
@@ -27,7 +38,10 @@ export function projectSemanticEditScriptToSource(input = {}) {
27
38
  coveredOperationIds.push(operation.id);
28
39
  continue;
29
40
  }
30
- const edit = sourceEditForOperation(operation, workerSourceText, headSourceText, index, input.headSourcePath);
41
+ const edit = sourceEditForOperation(operation, workerSourceText, headSourceText, index, {
42
+ headSourcePath: input.headSourcePath,
43
+ headSymbols
44
+ });
31
45
  if (edit.ok) edits.push(edit.value);
32
46
  else reasonCodes.push(...edit.reasonCodes);
33
47
  }
@@ -67,20 +81,20 @@ export function projectSemanticEditScriptToSource(input = {}) {
67
81
  appliedEditCount: deduped.edits.filter((edit) => !edit.alreadyApplied).length,
68
82
  alreadyAppliedEditCount: deduped.edits.filter((edit) => edit.alreadyApplied).length,
69
83
  dedupedEditCount: deduped.skippedOperationIds.length,
84
+ anchorMode: headSymbols.length ? 'javascript-like-symbols' : 'offsets',
70
85
  ...input.metadata
71
86
  })
72
87
  };
73
88
  return { ...core, hash: hashSemanticValue(core) };
74
89
  }
75
-
76
- function sourceEditForOperation(operation, workerSourceText, headSourceText, order, headSourcePath) {
77
- const identity = projectionIdentity(operation, headSourcePath);
90
+ function sourceEditForOperation(operation, workerSourceText, headSourceText, order, context) {
91
+ const identity = projectionIdentity(operation, context.headSourcePath);
78
92
  if (operation.status === 'already-applied') {
79
93
  return { ok: true, value: { ...identity, operationId: operation.id, order, start: 0, end: 0, replacement: '', current: '', alreadyApplied: true } };
80
94
  }
81
95
  if (operation.status !== 'portable') return { ok: false, reasonCodes: [`operation-not-portable:${operation.id}`] };
82
96
  if (operation.changeKind === 'added' || String(operation.kind ?? '').startsWith('add')) {
83
- return insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order);
97
+ return insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order, context);
84
98
  }
85
99
  if (operation.changeKind === 'removed' || String(operation.kind ?? '').startsWith('remove')) {
86
100
  return removalEditForOperation(operation, identity, headSourceText, order);
@@ -131,7 +145,6 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
131
145
  }
132
146
  };
133
147
  }
134
-
135
148
  function removalEditForOperation(operation, identity, headSourceText, order) {
136
149
  const headOffsets = spanOffsets(headSourceText, operation.spans?.head ?? operation.spans?.base ?? operation.anchor?.sourceSpan);
137
150
  const reasons = [];
@@ -158,12 +171,11 @@ function removalEditForOperation(operation, identity, headSourceText, order) {
158
171
  }
159
172
  };
160
173
  }
161
-
162
- function insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order) {
174
+ function insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order, context) {
163
175
  const workerOffsets = spanOffsets(workerSourceText, operation.spans?.worker);
164
176
  const reasons = [];
165
177
  if (!workerOffsets) reasons.push(`worker-span-not-resolvable:${operation.id}`);
166
- const insertion = insertionOffset(headSourceText, operation.insertion);
178
+ const insertion = insertionOffset(headSourceText, operation.insertion, { symbols: context.headSymbols });
167
179
  if (!insertion.ok) reasons.push(...insertion.reasonCodes.map((reason) => `${reason}:${operation.id}`));
168
180
  if (reasons.length) return { ok: false, reasonCodes: reasons };
169
181
  const spanText = workerSourceText.slice(workerOffsets.start, workerOffsets.end);
@@ -189,7 +201,6 @@ function insertionEditForOperation(operation, identity, workerSourceText, headSo
189
201
  }
190
202
  };
191
203
  }
192
-
193
204
  function projectionIdentity(operation, headSourcePath) {
194
205
  const identity = semanticEditIdentity(operation);
195
206
  const sourcePath = operation.reanchor?.toSourcePath ?? headSourcePath ?? operation.insertion?.sourcePath ?? identity.sourcePath;
@@ -201,7 +212,6 @@ function projectionIdentity(operation, headSourcePath) {
201
212
  : identity.targetSourcePath;
202
213
  return { ...identity, sourcePath, originalSourcePath, targetSourcePath };
203
214
  }
204
-
205
215
  function projectionEditRecord(edit) {
206
216
  const deletedTextHash = hashSemanticValue(edit.current);
207
217
  const replacementTextHash = hashSemanticValue(edit.replacement);
@@ -255,10 +265,25 @@ function projectionEditRecord(edit) {
255
265
  insertionAnchorKey: edit.insertion?.anchorKey,
256
266
  insertionAnchorSymbolName: edit.insertion?.anchorSymbolName,
257
267
  insertionAnchorSymbolKind: edit.insertion?.anchorSymbolKind,
268
+ insertionAnchorCandidates: edit.insertion?.anchorCandidates,
258
269
  replacementText: edit.replacement
259
270
  });
260
271
  }
261
272
 
273
+ function sourceSymbolIndex(input) {
274
+ try {
275
+ const imported = normalizeNativeDiffImport({
276
+ language: input.language,
277
+ sourcePath: input.sourcePath,
278
+ sourceText: input.sourceText,
279
+ parser: input.parser
280
+ }, input, 'head');
281
+ return [...mapDiffSymbols(imported, createSemanticImportSidecar(imported)).values()];
282
+ } catch {
283
+ return [];
284
+ }
285
+ }
286
+
262
287
  function semanticEditIdentity(operation) {
263
288
  const anchor = operation.anchor ?? {};
264
289
  return compactRecord({
@@ -288,6 +313,7 @@ function projectedSourcePath(script, edits) {
288
313
  return edits.map((edit) => edit.sourcePath).find(Boolean) ?? script.sourcePath;
289
314
  }
290
315
 
316
+ function isJavaScriptLike(language) { return language === 'javascript' || language === 'typescript'; }
291
317
  function compactRecord(value) {
292
318
  return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
293
319
  }