@shapeshift-labs/frontier-lang-compiler 0.2.66 → 0.2.68

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 (48) hide show
  1. package/README.md +6 -2
  2. package/bench/smoke.mjs +15 -1
  3. package/bench/universal-fixture-suite.mjs +183 -0
  4. package/dist/declarations/native-project-admission.d.ts +115 -0
  5. package/dist/declarations/roundtrip-audit.d.ts +186 -0
  6. package/dist/declarations/roundtrip.d.ts +2 -53
  7. package/dist/declarations/runtime.d.ts +0 -11
  8. package/dist/declarations/semantic-history-records.d.ts +277 -0
  9. package/dist/declarations/semantic-history.d.ts +45 -92
  10. package/dist/declarations/semantic-slice-admission.d.ts +111 -0
  11. package/dist/declarations/semantic-slice.d.ts +36 -1
  12. package/dist/declarations/universal-conversion-plan.d.ts +59 -0
  13. package/dist/declarations/universal-runtime-capabilities.d.ts +171 -0
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +1 -0
  16. package/dist/internal/index-impl/createNativeRoundtripEvidence.js +54 -49
  17. package/dist/internal/index-impl/createSemanticSlice.js +1 -1
  18. package/dist/internal/index-impl/createSemanticSliceAdmissionRecord.js +10 -1
  19. package/dist/internal/index-impl/expandSemanticSliceSelection.js +0 -1
  20. package/dist/internal/index-impl/nativeRoundtripAudit.js +217 -0
  21. package/dist/internal/index-impl/projectImportAdmissionImportEvidence.js +160 -0
  22. package/dist/internal/index-impl/projectImportAdmissionLanguageSummaries.js +247 -0
  23. package/dist/internal/index-impl/projectImportAdmissionRanks.js +52 -0
  24. package/dist/internal/index-impl/projectImportAdmissionSummaries.js +46 -111
  25. package/dist/internal/index-impl/projectImportAdmissionTasks.js +239 -0
  26. package/dist/internal/index-impl/semanticHistoryRecordNormalizers.js +151 -0
  27. package/dist/internal/index-impl/semanticHistoryRecordOverlaps.js +113 -0
  28. package/dist/internal/index-impl/semanticHistoryRecords.js +210 -149
  29. package/dist/internal/index-impl/semanticSliceAdmissionSurface.js +142 -0
  30. package/dist/internal/index-impl/semanticSliceExpectationAssertions.js +100 -0
  31. package/dist/internal/index-impl/semanticSliceExpectationRecords.js +75 -0
  32. package/dist/internal/index-impl/semanticSliceExpectedAssertions.js +5 -2
  33. package/dist/internal/index-impl/testSemanticSlice.js +4 -1
  34. package/dist/language-adapter-package-contracts.js +12 -57
  35. package/dist/language-adapter-package-rows.js +116 -0
  36. package/dist/universal-conversion-plan-summary.js +42 -0
  37. package/dist/universal-conversion-plan.js +46 -40
  38. package/dist/universal-runtime-capabilities.js +92 -0
  39. package/dist/universal-runtime-host-selectors.js +192 -0
  40. package/dist/universal-runtime-profiles.js +109 -0
  41. package/dist/universal-runtime-route-records.js +162 -0
  42. package/examples/js-frontier-rust-workbench-client.mjs +58 -1
  43. package/examples/js-frontier-rust-workbench-convert.mjs +161 -0
  44. package/examples/js-frontier-rust-workbench-route-styles.mjs +126 -0
  45. package/examples/js-frontier-rust-workbench-route.mjs +190 -0
  46. package/examples/js-frontier-rust-workbench-styles.mjs +3 -38
  47. package/examples/js-frontier-rust-workbench.mjs +22 -128
  48. package/package.json +1 -1
@@ -0,0 +1,217 @@
1
+ import{countBy,uniqueStrings}from'../../native-import-utils.js';
2
+
3
+ const precisionRank=Object.freeze({exact:0,line:1,declaration:2,estimated:3,unknown:4,none:5});
4
+
5
+ export function createRoundtripAudit(input){
6
+ const disposition=roundtripAuditDisposition(input);
7
+ return{
8
+ schema:'frontier.lang.nativeRoundtripAuditSignal',
9
+ version:1,
10
+ disposition,
11
+ claim:roundtripAuditClaim(disposition),
12
+ sourceLanguage:input.sourceLanguage,
13
+ target:input.target,
14
+ sameLanguage:input.sameLanguage,
15
+ outputMode:input.outputMode,
16
+ projectionMode:input.projectionMode,
17
+ sourceHashVerified:input.sourceHashVerified,
18
+ outputSourceMapPrecision:input.sourceMapEvidence.precision,
19
+ universalSourceMapPrecision:input.universalSourceMapEvidence.precision,
20
+ targetProjectionAdapterId:input.targetProjection?.adapter?.id,
21
+ targetCoverageLossClass:selectedTargetCoverageLossClass(input),
22
+ reviewRequired:input.semanticMerge!=='ready',
23
+ semanticMergeReadiness:input.semanticMerge,
24
+ semanticEquivalenceClaim:false,
25
+ autoMergeClaim:false,
26
+ paths:roundtripAuditPaths(input,disposition),
27
+ sourcePreservation:input.sourcePreservation,
28
+ generatedStubs:summarizeGeneratedStubs(input,disposition),
29
+ adapterProjection:summarizeAdapterProjection(input,disposition),
30
+ sourceMaps:summarizeRouteSourceMaps(input),
31
+ hashChecks:input.hashChecks,
32
+ commentsTrivia:summarizeCommentsTrivia(input.sourcePreservation),
33
+ targetCoverage:summarizeTargetCoverage(input),
34
+ semanticEquivalence:summarizeSemanticEquivalence(),
35
+ blockingLossCount:input.lossSummary.blockingLossIds.length,
36
+ reviewLossCount:input.lossSummary.reviewLossIds.length,
37
+ reasonCodes:uniqueStrings([
38
+ `status:${input.status}`,
39
+ `semantic:${input.semanticMerge}`,
40
+ `output:${input.outputMode??'unknown'}`,
41
+ `projection:${input.projectionMode??'unknown'}`,
42
+ input.sourceHashVerified?'source-hash:verified':'source-hash:unverified',
43
+ `output-source-map:${input.sourceMapEvidence.precision}`,
44
+ `universal-source-map:${input.universalSourceMapEvidence.precision}`,
45
+ selectedTargetCoverageLossClass(input)?`target-loss:${selectedTargetCoverageLossClass(input)}`:undefined,
46
+ matrixTargetCoverageLossClass(input)?`target-matrix-loss:${matrixTargetCoverageLossClass(input)}`:undefined,
47
+ input.targetProjection?.adapter?.id?`target-adapter:${input.targetProjection.adapter.id}`:undefined,
48
+ input.lossSummary.blockingLossIds.length?'losses:blocking':undefined,
49
+ input.lossSummary.reviewLossIds.length?'losses:review':undefined
50
+ ])
51
+ };
52
+ }
53
+ function roundtripAuditDisposition(input){
54
+ if(input.outputMode==='target-adapter')return'adapter-projected';
55
+ if(input.projectionMode==='native-source-stubs'||input.outputMode==='target-stubs')return'stub-only';
56
+ if(input.sourceHashVerified&&input.outputMode==='preserved-source'&&input.hasOutputSourceMaps&&input.sourceMapEvidence.precision==='exact')return'reversible';
57
+ if(input.sourceHashVerified||input.projectionMode==='preserved-source')return'preserved-source';
58
+ return'review-required';
59
+ }
60
+ function roundtripAuditClaim(disposition){
61
+ if(disposition==='reversible')return'source-text-reversible';
62
+ if(disposition==='preserved-source')return'source-preserved';
63
+ if(disposition==='stub-only')return'declaration-stubs-only';
64
+ if(disposition==='adapter-projected')return'host-adapter-projected';
65
+ return'review-required';
66
+ }
67
+ function roundtripAuditPaths(input,disposition){
68
+ const reversibleAvailable=input.sourceHashVerified&&input.outputMode==='preserved-source'&&input.hasOutputSourceMaps&&input.sourceMapEvidence.precision==='exact';
69
+ const preservedAvailable=input.sourceHashVerified||input.projectionMode==='preserved-source';
70
+ const stubAvailable=input.projectionMode==='native-source-stubs'||input.outputMode==='target-stubs';
71
+ const adapterAvailable=input.outputMode==='target-adapter'||Boolean(input.targetProjection?.adapter?.id);
72
+ return{
73
+ reversible:routePathSignal(disposition==='reversible',reversibleAvailable,[
74
+ input.sourceHashVerified?'source-hash:verified':'source-hash:unverified',
75
+ input.hasOutputSourceMaps?'output-source-map:present':'output-source-map:missing',
76
+ `output-source-map:${input.sourceMapEvidence.precision}`
77
+ ]),
78
+ preservedSource:routePathSignal(disposition==='preserved-source',preservedAvailable,[
79
+ `projection:${input.projectionMode??'unknown'}`,
80
+ input.sourceHashVerified?'source-hash:verified':'source-hash:unverified'
81
+ ]),
82
+ stubOnly:routePathSignal(disposition==='stub-only',stubAvailable,[
83
+ `projection:${input.projectionMode??'unknown'}`,
84
+ `output:${input.outputMode??'unknown'}`
85
+ ]),
86
+ adapterProjected:routePathSignal(disposition==='adapter-projected',adapterAvailable,[
87
+ `output:${input.outputMode??'unknown'}`,
88
+ input.targetProjection?.adapter?.id?`target-adapter:${input.targetProjection.adapter.id}`:undefined
89
+ ])
90
+ };
91
+ }
92
+ function routePathSignal(selected,available,reasonCodes){
93
+ return{
94
+ selected,
95
+ available,
96
+ reasonCodes:uniqueStrings([
97
+ selected?'path:selected':undefined,
98
+ available?'path:available':'path:not-available',
99
+ ...reasonCodes
100
+ ])
101
+ };
102
+ }
103
+ function summarizeGeneratedStubs(input,disposition){
104
+ const declarations=input.targetProjection?[]:input.projection?.declarations??[];
105
+ const projectionDeclarations=input.projection?.declarations??[];
106
+ return{
107
+ available:input.projectionMode==='native-source-stubs'||input.outputMode==='target-stubs'||projectionDeclarations.length>0,
108
+ selected:disposition==='stub-only',
109
+ projectionMode:input.projectionMode,
110
+ outputMode:input.outputMode,
111
+ declarationCount:projectionDeclarations.length,
112
+ emittedDeclarationCount:declarations.length,
113
+ declarationKinds:countBy(projectionDeclarations.map((declaration)=>declaration?.kind??'unknown')),
114
+ declarationsWithSourceSpan:projectionDeclarations.filter((declaration)=>declaration?.sourceSpan).length,
115
+ symbolIds:uniqueStrings(projectionDeclarations.map((declaration)=>declaration?.symbolId)),
116
+ nativeAstNodeIds:uniqueStrings(projectionDeclarations.map((declaration)=>declaration?.nativeAstNodeId)),
117
+ lossCount:input.projection?.losses?.length??0
118
+ };
119
+ }
120
+ function summarizeAdapterProjection(input,disposition){
121
+ const targetProjection=input.targetProjection;
122
+ return{
123
+ available:Boolean(targetProjection),
124
+ selected:disposition==='adapter-projected',
125
+ id:targetProjection?.id,
126
+ adapterId:targetProjection?.adapter?.id,
127
+ adapterVersion:targetProjection?.adapter?.version,
128
+ outputMode:targetProjection?.outputMode,
129
+ readiness:targetProjection?.readiness?.readiness,
130
+ lossCount:targetProjection?.losses?.length??0,
131
+ evidenceIds:evidenceIds(targetProjection?.evidence),
132
+ sourceMaps:summarizeSourceMaps(targetProjection?.sourceMaps??[])
133
+ };
134
+ }
135
+ function summarizeRouteSourceMaps(input){
136
+ return{
137
+ hasOutputSourceMaps:input.hasOutputSourceMaps,
138
+ output:input.sourceMapEvidence,
139
+ universal:input.universalSourceMapEvidence,
140
+ outputExact:input.sourceMapEvidence.precision==='exact',
141
+ outputEstimated:input.sourceMapEvidence.precision==='estimated'||numeric(input.sourceMapEvidence.byPrecision?.estimated)>0,
142
+ universalEstimated:input.universalSourceMapEvidence.precision==='estimated'||numeric(input.universalSourceMapEvidence.byPrecision?.estimated)>0
143
+ };
144
+ }
145
+ function summarizeCommentsTrivia(sourcePreservation){
146
+ return{
147
+ comments:sourcePreservation.comments,
148
+ trivia:sourcePreservation.trivia,
149
+ directives:sourcePreservation.directives,
150
+ tokens:sourcePreservation.tokens,
151
+ whitespace:sourcePreservation.whitespace,
152
+ truncated:sourcePreservation.truncated,
153
+ exactSourceAvailable:sourcePreservation.exactSourceAvailable,
154
+ sourceTextAvailable:sourcePreservation.sourceTextAvailable
155
+ };
156
+ }
157
+ function summarizeTargetCoverage(input){
158
+ const coverage=input.targetCoverage;
159
+ const lossClass=selectedTargetCoverageLossClass(input);
160
+ return{
161
+ target:input.target??coverage?.target??input.targetProjection?.target,
162
+ supported:adapterProjectionSelected(input)?true:coverage?.supported,
163
+ readiness:coverage?.readiness??input.targetProjection?.readiness?.readiness,
164
+ lossClass,
165
+ adapterId:coverage?.adapter??input.targetProjection?.adapter?.id,
166
+ adapterKind:adapterProjectionSelected(input)?'targetProjection':coverage?.adapterKind,
167
+ adapterVersion:coverage?.adapterVersion??input.targetProjection?.adapter?.version,
168
+ lossKinds:uniqueStrings(coverage?.lossKinds),
169
+ categories:uniqueStrings(coverage?.categories),
170
+ reason:coverage?.reason,
171
+ notes:uniqueStrings(coverage?.notes)
172
+ };
173
+ }
174
+ function selectedTargetCoverageLossClass(input){
175
+ const projectedLossClass=input.targetProjection?.coverage?.lossClass
176
+ ??input.targetProjection?.metadata?.targetProjectionCoverage?.lossClass
177
+ ??input.targetProjection?.metadata?.lossClass;
178
+ if(projectedLossClass)return projectedLossClass;
179
+ if(adapterProjectionSelected(input))return'targetAdapterProjection';
180
+ return input.targetCoverage?.lossClass;
181
+ }
182
+ function matrixTargetCoverageLossClass(input){
183
+ const selected=selectedTargetCoverageLossClass(input);
184
+ const matrix=input.targetCoverage?.lossClass;
185
+ return matrix&&matrix!==selected?matrix:undefined;
186
+ }
187
+ function adapterProjectionSelected(input){
188
+ return input.outputMode==='target-adapter'||Boolean(input.targetProjection?.adapter?.id);
189
+ }
190
+ function summarizeSemanticEquivalence(){
191
+ return{
192
+ claimed:false,
193
+ evidenceIds:[],
194
+ reasonCode:'proof-adapter:not-provided'
195
+ };
196
+ }
197
+ function evidenceIds(evidence){return uniqueStrings((evidence??[]).map((record)=>record?.id));}
198
+ function summarizeSourceMaps(sourceMaps){
199
+ const maps=sourceMaps??[];
200
+ const mappings=maps.flatMap((sourceMap)=>sourceMap?.mappings??[]);
201
+ const precisions=mappings.map((mapping)=>normalizePrecision(mapping?.precision));
202
+ return{
203
+ total:maps.length,
204
+ ids:uniqueStrings(maps.map((sourceMap)=>sourceMap?.id)),
205
+ mappings:mappings.length,
206
+ precision:worstPrecision(precisions),
207
+ byPrecision:countBy(precisions),
208
+ byOrigin:countBy(mappings.map((mapping)=>mapping?.metadata?.sourceMapOrigin??'native-import')),
209
+ withSourceSpan:mappings.filter((mapping)=>mapping?.sourceSpan).length,
210
+ withGeneratedSpan:mappings.filter((mapping)=>mapping?.generatedSpan).length,
211
+ withSemanticSymbol:mappings.filter((mapping)=>mapping?.semanticSymbolId||mapping?.semanticNodeId).length,
212
+ targetPaths:uniqueStrings(maps.map((sourceMap)=>sourceMap?.targetPath).concat(mappings.map((mapping)=>mapping?.generatedSpan?.targetPath)))
213
+ };
214
+ }
215
+ function normalizePrecision(value){const precision=String(value??'unknown');return Object.prototype.hasOwnProperty.call(precisionRank,precision)?precision:'unknown';}
216
+ function worstPrecision(precisions){if(!precisions.length)return'none';return precisions.reduce((worst,next)=>precisionRank[next]>precisionRank[worst]?next:worst,'exact');}
217
+ function numeric(value,fallback=0){const number=Number(value);return Number.isFinite(number)?number:fallback;}
@@ -0,0 +1,160 @@
1
+ import{uniqueStrings}from'../../native-import-utils.js';
2
+ import{nativeImportCategoryForLossKind}from'./nativeImportCategoryForLossKind.js';
3
+
4
+ export function importLosses(imported){
5
+ const nativeAst=imported?.nativeAst??imported?.nativeSource?.ast;
6
+ const losses=[
7
+ ...(imported?.losses??[]),
8
+ ...(nativeAst?.losses??[]),
9
+ ...(imported?.universalAst?.losses??[])
10
+ ];
11
+ const seen=new Set();
12
+ const result=[];
13
+ for(const loss of losses){
14
+ const id=loss?.id??`loss_${result.length+1}`;
15
+ if(seen.has(id)) continue;
16
+ seen.add(id);
17
+ result.push(loss?.id?loss:{...loss,id});
18
+ }
19
+ return result;
20
+ }
21
+
22
+ export function sourceLossClasses(imported,losses){
23
+ const lossSummary=imported?.metadata?.nativeImportLossSummary
24
+ ??imported?.nativeSource?.metadata?.nativeImportLossSummary
25
+ ??imported?.nativeAst?.metadata?.nativeImportLossSummary
26
+ ??imported?.universalAst?.metadata?.nativeImportLossSummary;
27
+ return uniqueStrings([
28
+ ...(lossSummary?.categories??[]),
29
+ ...(losses??[]).map(lossClassForLoss),
30
+ ...sourceProjectionLossClasses(imported)
31
+ ].filter(Boolean));
32
+ }
33
+
34
+ function lossClassForLoss(loss){
35
+ return loss?.metadata?.targetLossClass
36
+ ??loss?.metadata?.lossClass
37
+ ??loss?.metadata?.lossCategory
38
+ ??nativeImportCategoryForLossKind(loss?.kind);
39
+ }
40
+
41
+ function sourceProjectionLossClasses(imported){
42
+ const matrixClasses=projectionMatricesForImport(imported).flatMap((matrix)=>[
43
+ ...Object.entries(matrix?.summary?.byLossClass??{}).filter(([,count])=>count>0).map(([lossClass])=>lossClass),
44
+ ...(matrix?.languages??[]).flatMap((language)=>[
45
+ ...Object.entries(language?.summary?.byLossClass??{}).filter(([,count])=>count>0).map(([lossClass])=>lossClass),
46
+ ...(language?.targets??[]).map((target)=>target?.lossClass)
47
+ ])
48
+ ]);
49
+ return uniqueStrings([
50
+ ...targetProjectionEntriesForImport(imported).map((entry)=>entry.lossClass),
51
+ ...matrixClasses
52
+ ].filter(Boolean));
53
+ }
54
+
55
+ function targetProjectionEntriesForImport(imported){
56
+ return [
57
+ imported?.targetCoverage,
58
+ imported?.metadata?.targetCoverage,
59
+ imported?.metadata?.targetProjectionCoverage,
60
+ ...(imported?.targetCoverages??[]),
61
+ ...(imported?.metadata?.targetCoverages??[])
62
+ ].flatMap((entry)=>Array.isArray(entry)?entry:[entry])
63
+ .filter((entry)=>entry&&typeof entry==='object'&&(entry.target||entry.lossClass||entry.supported!==undefined));
64
+ }
65
+
66
+ function projectionMatricesForImport(imported){
67
+ return [
68
+ imported?.projectionMatrix,
69
+ imported?.metadata?.projectionMatrix,
70
+ ...(imported?.projectionMatrices??[]),
71
+ ...(imported?.metadata?.projectionMatrices??[])
72
+ ].filter((matrix)=>matrix?.kind==='frontier.lang.projectionTargetLossMatrix'||Array.isArray(matrix?.languages));
73
+ }
74
+
75
+ export function summarizeParserEvidence(imported,source,losses){
76
+ const nativeAst=imported?.nativeAst??imported?.nativeSource?.ast;
77
+ const rootNode=nativeAst?.nodes?.[nativeAst?.rootId];
78
+ const coverage=imported?.adapter?.coverage
79
+ ??imported?.metadata?.adapterCoverage
80
+ ??imported?.nativeSource?.adapter?.coverage
81
+ ??nativeAst?.metadata?.adapterCoverage;
82
+ const observed=coverage?.observed;
83
+ const effective=coverage?.capabilityEvidence?.effective;
84
+ const diagnostics=[
85
+ ...(imported?.diagnostics??[]),
86
+ ...(imported?.nativeSource?.diagnostics??[])
87
+ ];
88
+ const parserDiagnosticLosses=(losses??[]).filter((loss)=>
89
+ loss?.kind==='parserDiagnostic'
90
+ || loss?.phase==='parse'&&loss?.severity==='error'
91
+ || loss?.metadata?.parserDiagnostic===true
92
+ );
93
+ const parser=source?.parser
94
+ ??imported?.adapter?.parser
95
+ ??coverage?.parser
96
+ ??nativeAst?.parser
97
+ ??imported?.metadata?.parser
98
+ ??imported?.nativeSource?.parser;
99
+ return {
100
+ parser,
101
+ astFormat:imported?.metadata?.astFormat??nativeAst?.metadata?.astFormat,
102
+ exactness:coverage?.exactness??effective?.exactness??observed?.exactness,
103
+ semanticCoverageLevel:coverage?.semanticCoverage?.level??effective?.semanticCoverage?.level??observed?.semanticCoverage?.level,
104
+ exactAst:Boolean(coverage?.exactAst??effective?.exactAst??observed?.exactAst),
105
+ tokens:Boolean(coverage?.tokens??effective?.tokens??observed?.tokens),
106
+ trivia:Boolean(coverage?.trivia??effective?.trivia??observed?.trivia),
107
+ diagnostics:Boolean(coverage?.diagnostics??effective?.diagnostics)||(diagnostics.length+parserDiagnosticLosses.length)>0,
108
+ diagnosticCount:diagnostics.length+parserDiagnosticLosses.length+(observed?.parserDiagnostics??0),
109
+ sourceRanges:Boolean(coverage?.sourceRanges??effective?.sourceRanges??observed?.sourceRanges),
110
+ generatedRanges:Boolean(coverage?.generatedRanges??effective?.generatedRanges??observed?.generatedRanges),
111
+ evidenceRecords:source?.evidenceCount??imported?.evidence?.length??0,
112
+ missing:Boolean(
113
+ imported?.metadata?.missingInjectedParser
114
+ || nativeAst?.metadata?.missingInjectedParser
115
+ || rootNode?.kind==='MissingInjectedParser'
116
+ || rootNode?.metadata?.reason==='missing-injected-parser'
117
+ || parserDiagnosticLosses.some((loss)=>loss?.metadata?.reason==='missing-injected-parser'||loss?.metadata?.missingInjectedParser)
118
+ )
119
+ };
120
+ }
121
+
122
+ export function compactAdmissionSource(imported,index){
123
+ const semanticIndex=imported?.semanticIndex??imported?.universalAst?.semanticIndex;
124
+ const nativeAst=imported?.nativeAst??imported?.nativeSource?.ast;
125
+ const sourceMaps=imported?.sourceMaps??imported?.universalAst?.sourceMaps??[];
126
+ return {
127
+ id:imported?.id??`import_${index+1}`,
128
+ language:imported?.language??imported?.nativeSource?.language??nativeAst?.language,
129
+ sourcePath:imported?.sourcePath??imported?.nativeSource?.sourcePath??nativeAst?.sourcePath,
130
+ sourceHash:imported?.nativeSource?.sourceHash??nativeAst?.sourceHash,
131
+ parser:nativeAst?.parser??imported?.nativeSource?.parser,
132
+ sourceMapIds:sourceMaps.map((sourceMap)=>sourceMap.id).filter(Boolean),
133
+ sourceMapMappings:sourceMaps.reduce((sum,sourceMap)=>sum+(sourceMap.mappings?.length??0),0),
134
+ symbolCount:semanticIndex?.symbols?.length??0,
135
+ lossCount:imported?.losses?.length??nativeAst?.losses?.length??0,
136
+ evidenceCount:imported?.evidence?.length??0,
137
+ readiness:imported?.metadata?.semanticMergeReadiness??imported?.mergeCandidates?.[0]?.readiness
138
+ };
139
+ }
140
+
141
+ export function summarizeImportPreservation(imported,source){
142
+ const nativeAst=imported?.nativeAst??imported?.nativeSource?.ast;
143
+ const record=imported?.metadata?.sourcePreservation
144
+ ??imported?.nativeSource?.metadata?.sourcePreservation
145
+ ??nativeAst?.metadata?.sourcePreservation
146
+ ??imported?.universalAst?.metadata?.sourcePreservation;
147
+ const sourceHash=source?.sourceHash??imported?.nativeSource?.sourceHash??nativeAst?.sourceHash;
148
+ const sourcePreservationLosses=(imported?.losses??nativeAst?.losses??[]).filter((loss)=>loss.kind==='sourcePreservation');
149
+ const stale=imported?.metadata?.sourceHashVerified===false
150
+ ||imported?.nativeSource?.metadata?.sourceHashVerified===false
151
+ ||nativeAst?.metadata?.sourceHashVerified===false
152
+ ||record?.metadata?.sourceHashVerified===false
153
+ ||Boolean(record?.sourceHash&&sourceHash&&record.sourceHash!==sourceHash);
154
+ const missing=!record;
155
+ const truncated=record?.summary?.truncated===true;
156
+ const exactSourceAvailable=record?.summary?.exactSourceAvailable===true;
157
+ const quality=stale?'stale':missing?'missing':truncated||!exactSourceAvailable||sourcePreservationLosses.length?'lossy':'exact';
158
+ return {quality,missing,stale,truncated,exactSourceAvailable,lossCount:sourcePreservationLosses.length,id:record?.id};
159
+ }
160
+
@@ -0,0 +1,247 @@
1
+ import{countBy,maxSemanticMergeReadiness,uniqueStrings}from'../../native-import-utils.js';
2
+ import{candidateRisk,maxPreservationQuality,maxRisk,maxTaskPriority,readinessSort,roundScore,taskPriorityRank}from'./projectImportAdmissionRanks.js';
3
+
4
+ export function admissionLanguages(importSummaries){
5
+ const grouped=new Map();
6
+ for(const entry of importSummaries){
7
+ const key=entry.language??'unknown';
8
+ if(!grouped.has(key)) grouped.set(key,emptyLanguageRow(key));
9
+ const row=grouped.get(key);
10
+ row.sourceCount+=1;
11
+ row.sourcePaths.push(entry.sourcePath);
12
+ row.sources.push(entry);
13
+ row.readiness=maxSemanticMergeReadiness(row.readiness,entry.readiness);
14
+ row.semanticSymbols+=entry.semanticCounts.symbols;
15
+ if(entry.emptySemanticEvidence) row.emptySemanticEvidenceSources+=1;
16
+ row.sourcePreservationQuality=maxPreservationQuality(row.sourcePreservationQuality,entry.sourcePreservation.quality);
17
+ if(entry.sourcePreservation.stale&&entry.sourcePath) row.staleSourcePaths.push(entry.sourcePath);
18
+ row.mergeCandidates+=entry.mergeCandidates.length;
19
+ row.highestRisk=maxRisk(row.highestRisk,entry.mergeCandidates.reduce((current,candidate)=>maxRisk(current,candidateRisk(candidate)),'low'));
20
+ }
21
+ const rows=[...grouped.values()].map((row)=>{
22
+ const sources=row.sources;
23
+ return {
24
+ language:row.language,
25
+ sourceCount:row.sourceCount,
26
+ sourcePaths:uniqueStrings(row.sourcePaths.filter(Boolean)),
27
+ readiness:row.readiness,
28
+ semanticSymbols:row.semanticSymbols,
29
+ emptySemanticEvidenceSources:row.emptySemanticEvidenceSources,
30
+ sourcePreservationQuality:row.sourcePreservationQuality,
31
+ staleSourcePaths:uniqueStrings(row.staleSourcePaths.filter(Boolean)),
32
+ mergeCandidates:row.mergeCandidates,
33
+ highestRisk:row.highestRisk,
34
+ byReadiness:countBy(sources.map((source)=>source.readiness)),
35
+ byLossClass:lossClassCounts(sources),
36
+ parserEvidence:parserEvidenceSummary(sources),
37
+ semanticMergeScore:semanticMergeScoreSummary(sources),
38
+ topMissingEvidence:topMissingEvidence(sources),
39
+ nextMissingTasks:topTaskHints(sources)
40
+ };
41
+ }).sort((left,right)=>left.language.localeCompare(right.language));
42
+ return {
43
+ total:rows.length,
44
+ byReadiness:countBy(rows.map((row)=>row.readiness)),
45
+ bySourceReadiness:countBy(importSummaries.map((entry)=>entry.readiness)),
46
+ byLossClass:lossClassCounts(importSummaries),
47
+ bySourcePreservationQuality:countBy(rows.map((row)=>row.sourcePreservationQuality)),
48
+ parserEvidence:parserEvidenceSummary(importSummaries),
49
+ semanticMergeScore:semanticMergeScoreSummary(importSummaries),
50
+ topMissingEvidence:topMissingEvidence(importSummaries),
51
+ nextMissingTasks:topTaskHints(importSummaries),
52
+ sourceRows:importSummaries.map(compactAdmissionSourceRow),
53
+ readinessRows:languageReadinessRows(importSummaries),
54
+ rows
55
+ };
56
+ }
57
+
58
+ function compactAdmissionSourceRow(entry){
59
+ return {
60
+ id:entry.id,
61
+ language:entry.language,
62
+ sourcePath:entry.sourcePath,
63
+ sourceHash:entry.sourceHash,
64
+ readiness:entry.readiness,
65
+ parser:entry.parserEvidence.parser,
66
+ parserEvidence:entry.parserEvidence,
67
+ semanticCounts:entry.semanticCounts,
68
+ semanticSymbols:entry.semanticCounts.symbols,
69
+ emptySemanticEvidence:entry.emptySemanticEvidence,
70
+ sourcePreservationQuality:entry.sourcePreservation.quality,
71
+ lossClasses:entry.lossClasses,
72
+ semanticMergeScore:entry.semanticMergeScore,
73
+ missingEvidence:entry.missingEvidence,
74
+ nextMissingTask:entry.nextMissingTasks[0],
75
+ nextMissingTasks:entry.nextMissingTasks
76
+ };
77
+ }
78
+
79
+ function languageReadinessRows(importSummaries){
80
+ const grouped=new Map();
81
+ for(const entry of importSummaries){
82
+ const key=`${entry.language??'unknown'}\u0000${entry.readiness??'needs-review'}`;
83
+ if(!grouped.has(key)) grouped.set(key,{language:entry.language??'unknown',readiness:entry.readiness??'needs-review',sources:[]});
84
+ grouped.get(key).sources.push(entry);
85
+ }
86
+ return [...grouped.values()].map((group)=>sourceGroupSummary(group.language,group.readiness,group.sources))
87
+ .sort((left,right)=>left.language.localeCompare(right.language)||readinessSort(left.readiness)-readinessSort(right.readiness));
88
+ }
89
+
90
+ function sourceGroupSummary(language,readiness,sources){
91
+ return {
92
+ language,
93
+ readiness,
94
+ sourceCount:sources.length,
95
+ sourcePaths:uniqueStrings(sources.map((source)=>source.sourcePath).filter(Boolean)),
96
+ byLossClass:lossClassCounts(sources),
97
+ parserEvidence:parserEvidenceSummary(sources),
98
+ semanticMergeScore:semanticMergeScoreSummary(sources),
99
+ topMissingEvidence:topMissingEvidence(sources),
100
+ nextMissingTasks:topTaskHints(sources)
101
+ };
102
+ }
103
+
104
+ function lossClassCounts(sources){
105
+ return countBy((sources??[]).flatMap((source)=>source.lossClasses?.length?source.lossClasses:['none']));
106
+ }
107
+
108
+ function parserEvidenceSummary(sources){
109
+ const records=(sources??[]).map((source)=>source.parserEvidence??{});
110
+ return {
111
+ parsers:uniqueStrings(records.map((record)=>record.parser).filter(Boolean)),
112
+ byParser:countBy(records.map((record)=>record.parser??'unknown')),
113
+ byExactness:countBy(records.map((record)=>record.exactness??'unknown')),
114
+ semanticCoverageLevels:uniqueStrings(records.map((record)=>record.semanticCoverageLevel).filter(Boolean)),
115
+ exactAstSources:records.filter((record)=>record.exactAst).length,
116
+ tokenSources:records.filter((record)=>record.tokens).length,
117
+ triviaSources:records.filter((record)=>record.trivia).length,
118
+ sourceRangeSources:records.filter((record)=>record.sourceRanges).length,
119
+ generatedRangeSources:records.filter((record)=>record.generatedRanges).length,
120
+ diagnosticsSources:records.filter((record)=>(record.diagnosticCount??0)>0).length,
121
+ missingParserSources:records.filter((record)=>record.missing).length,
122
+ evidenceRecords:records.reduce((sum,record)=>sum+(record.evidenceRecords??0),0)
123
+ };
124
+ }
125
+
126
+ function semanticMergeScoreSummary(sources){
127
+ const scored=(sources??[])
128
+ .filter((source)=>Number.isFinite(source.semanticMergeScore))
129
+ .sort((left,right)=>left.semanticMergeScore-right.semanticMergeScore||String(left.sourcePath??'').localeCompare(String(right.sourcePath??'')));
130
+ const values=scored.map((source)=>source.semanticMergeScore);
131
+ if(!values.length){
132
+ return {sourceCount:0,min:0,max:0,average:0,sortKey:0,lowestSourcePaths:[]};
133
+ }
134
+ const min=Math.min(...values);
135
+ const max=Math.max(...values);
136
+ const average=roundScore(values.reduce((sum,value)=>sum+value,0)/values.length);
137
+ return {
138
+ sourceCount:values.length,
139
+ min,
140
+ max,
141
+ average,
142
+ sortKey:roundScore(average*100-(100-min)),
143
+ lowestSourcePaths:uniqueStrings(scored.slice(0,5).map((source)=>source.sourcePath).filter(Boolean))
144
+ };
145
+ }
146
+
147
+ function topMissingEvidence(sources,limit=5){
148
+ const grouped=new Map();
149
+ for(const source of sources??[]){
150
+ for(const hint of source.missingEvidence??[]){
151
+ const key=hint.evidenceKey??hint.task;
152
+ if(!grouped.has(key)){
153
+ grouped.set(key,{
154
+ evidenceKey:hint.evidenceKey,
155
+ task:hint.task,
156
+ count:0,
157
+ sourcePaths:[],
158
+ languages:[],
159
+ lossIds:[],
160
+ lossKinds:[],
161
+ lossClasses:[],
162
+ readiness:'ready'
163
+ });
164
+ }
165
+ const entry=grouped.get(key);
166
+ entry.count+=hint.count??1;
167
+ entry.sourcePaths.push(...(hint.sourcePaths??[]));
168
+ entry.languages.push(...(hint.languages??[]));
169
+ entry.lossIds.push(...(hint.lossIds??[]));
170
+ entry.lossKinds.push(...(hint.lossKinds??[]));
171
+ entry.lossClasses.push(...(hint.lossClasses??[]));
172
+ entry.readiness=maxSemanticMergeReadiness(entry.readiness,hint.readiness??source.readiness??'ready');
173
+ }
174
+ }
175
+ return [...grouped.values()].map((entry)=>({
176
+ ...entry,
177
+ sourcePaths:uniqueStrings(entry.sourcePaths),
178
+ languages:uniqueStrings(entry.languages),
179
+ lossIds:uniqueStrings(entry.lossIds),
180
+ lossKinds:uniqueStrings(entry.lossKinds),
181
+ lossClasses:uniqueStrings(entry.lossClasses)
182
+ })).sort((left,right)=>
183
+ readinessSort(right.readiness)-readinessSort(left.readiness)
184
+ || right.count-left.count
185
+ || String(left.evidenceKey??left.task).localeCompare(String(right.evidenceKey??right.task))
186
+ ).slice(0,limit);
187
+ }
188
+
189
+ function topTaskHints(sources,limit=6){
190
+ const grouped=new Map();
191
+ for(const source of sources??[]){
192
+ for(const hint of source.nextMissingTasks??[]){
193
+ const key=hint.id??hint.task;
194
+ if(!grouped.has(key)){
195
+ grouped.set(key,{
196
+ id:hint.id,
197
+ task:hint.task,
198
+ reason:hint.reason,
199
+ priority:hint.priority??'normal',
200
+ readiness:'ready',
201
+ count:0,
202
+ sourcePaths:[],
203
+ languages:[],
204
+ lossClasses:[],
205
+ evidenceKeys:[]
206
+ });
207
+ }
208
+ const entry=grouped.get(key);
209
+ entry.count+=hint.count??1;
210
+ entry.priority=maxTaskPriority(entry.priority,hint.priority??'normal');
211
+ entry.readiness=maxSemanticMergeReadiness(entry.readiness,hint.readiness??source.readiness??'ready');
212
+ entry.sourcePaths.push(...(hint.sourcePaths??[]));
213
+ entry.languages.push(...(hint.languages??[]));
214
+ entry.lossClasses.push(...(hint.lossClasses??[]));
215
+ entry.evidenceKeys.push(...(hint.evidenceKeys??[]));
216
+ }
217
+ }
218
+ return [...grouped.values()].map((entry)=>({
219
+ ...entry,
220
+ sourcePaths:uniqueStrings(entry.sourcePaths),
221
+ languages:uniqueStrings(entry.languages),
222
+ lossClasses:uniqueStrings(entry.lossClasses),
223
+ evidenceKeys:uniqueStrings(entry.evidenceKeys)
224
+ })).sort((left,right)=>
225
+ taskPriorityRank[right.priority]-taskPriorityRank[left.priority]
226
+ || readinessSort(right.readiness)-readinessSort(left.readiness)
227
+ || right.count-left.count
228
+ || String(left.task).localeCompare(String(right.task))
229
+ ).slice(0,limit);
230
+ }
231
+
232
+ function emptyLanguageRow(language){
233
+ return {
234
+ language,
235
+ sourceCount:0,
236
+ sourcePaths:[],
237
+ sources:[],
238
+ readiness:'ready',
239
+ semanticSymbols:0,
240
+ emptySemanticEvidenceSources:0,
241
+ sourcePreservationQuality:'exact',
242
+ staleSourcePaths:[],
243
+ mergeCandidates:0,
244
+ highestRisk:'low'
245
+ };
246
+ }
247
+
@@ -0,0 +1,52 @@
1
+ export const readinessRank=Object.freeze({ready:0,'ready-with-losses':1,'needs-review':2,blocked:3});
2
+ export const sourceScoreByReadiness=Object.freeze({ready:100,'ready-with-losses':75,'needs-review':45,blocked:0});
3
+ export const riskRank=Object.freeze({low:0,medium:1,unknown:2,high:3});
4
+ export const taskPriorityRank=Object.freeze({low:0,normal:1,high:2,critical:3,blocker:4});
5
+ export const preservationRank=Object.freeze({exact:0,lossy:1,missing:2,stale:3,empty:4});
6
+
7
+
8
+ export function taskPriorityForReadiness(readiness){
9
+ if(readiness==='blocked') return 'blocker';
10
+ if(readiness==='needs-review') return 'high';
11
+ if(readiness==='ready-with-losses') return 'normal';
12
+ return 'low';
13
+ }
14
+
15
+ export function maxTaskPriority(left,right){
16
+ return taskPriorityRank[left]>=taskPriorityRank[right]?left:right;
17
+ }
18
+
19
+ export function readinessSort(readiness){
20
+ return readinessRank[readiness]??readinessRank['needs-review'];
21
+ }
22
+
23
+ export function clampScore(value){
24
+ return Math.max(0,Math.min(100,roundScore(value)));
25
+ }
26
+
27
+ export function roundScore(value){
28
+ return Math.round(value*100)/100;
29
+ }
30
+
31
+ export function candidateRisk(candidate,patch){
32
+ return normalizeRisk(candidate?.risk)??normalizeRisk(patch?.risk)??readinessRisk(candidate?.readiness);
33
+ }
34
+
35
+ export function maxRisk(left,right){
36
+ return riskRank[left]>=riskRank[right]?left:right;
37
+ }
38
+
39
+ export function maxPreservationQuality(left,right){
40
+ return preservationRank[left]>=preservationRank[right]?left:right;
41
+ }
42
+
43
+ export function readinessRisk(readiness){
44
+ if(readiness==='blocked') return 'high';
45
+ if(readiness==='needs-review'||readiness==='ready-with-losses') return 'medium';
46
+ return 'low';
47
+ }
48
+
49
+ export function normalizeRisk(value){
50
+ const risk=String(value??'').toLowerCase();
51
+ return Object.prototype.hasOwnProperty.call(riskRank,risk)?risk:undefined;
52
+ }