@shapeshift-labs/frontier-lang-compiler 0.2.48 → 0.2.50

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.
@@ -0,0 +1,287 @@
1
+ import { idFragment, uniqueStrings } from './native-import-utils.js';
2
+ import { createUniversalConversionPlan } from './universal-conversion-plan.js';
3
+ import { artifactIndex } from './universal-conversion-artifact-query.js';
4
+ import { createSemanticHistoryRecord } from './internal/index-impl/semanticHistoryRecords.js';
5
+ import { createSemanticPatchBundleRecord } from './internal/index-impl/semanticPatchBundleRecords.js';
6
+
7
+ export { queryUniversalConversionArtifacts } from './universal-conversion-artifact-query.js';
8
+
9
+ export function createUniversalConversionArtifacts(input = {}, options = {}) {
10
+ const generatedAt = options.generatedAt ?? input.generatedAt ?? Date.now();
11
+ const plan = input?.kind === 'frontier.lang.universalConversionPlan'
12
+ ? input
13
+ : input?.target && input?.sourceLanguage
14
+ ? undefined
15
+ : createUniversalConversionPlan(input);
16
+ const routes = selectRoutes(plan?.routes ?? (input?.target && input?.sourceLanguage ? [input] : []), options);
17
+ const routeArtifacts = routes.map((route) => createRouteArtifact(route, {
18
+ generatedAt,
19
+ planId: options.planId ?? plan?.id ?? route.mergeRefs?.planId,
20
+ metadata: options.metadata
21
+ }));
22
+ const historyRecords = routeArtifacts.map((artifact) => artifact.history);
23
+ const patchBundleRecords = routeArtifacts.map((artifact) => artifact.patchBundle);
24
+ const index = artifactIndex(routeArtifacts);
25
+ return {
26
+ kind: 'frontier.lang.universalConversionArtifacts',
27
+ version: 1,
28
+ schema: 'frontier.lang.universalConversionArtifacts.v1',
29
+ id: options.id ?? `universal_conversion_artifacts_${idFragment(index.routeIds.join('_') || plan?.id || 'routes')}`,
30
+ planId: plan?.id ?? options.planId,
31
+ generatedAt,
32
+ routeArtifacts,
33
+ historyRecords,
34
+ patchBundleRecords,
35
+ index,
36
+ summary: {
37
+ routes: routeArtifacts.length,
38
+ histories: historyRecords.length,
39
+ patchBundles: patchBundleRecords.length,
40
+ reviewRequired: routeArtifacts.filter((artifact) => artifact.reviewRequired).length,
41
+ blocked: routeArtifacts.filter((artifact) => artifact.admissionStatus === 'blocked').length,
42
+ autoMergeClaims: 0,
43
+ semanticEquivalenceClaims: 0
44
+ },
45
+ metadata: {
46
+ autoMergeClaim: false,
47
+ semanticEquivalenceClaim: false,
48
+ note: 'Materialized conversion artifacts are merge-review records. They preserve provenance and admission state but do not prove target semantic equivalence.',
49
+ ...options.metadata
50
+ }
51
+ };
52
+ }
53
+
54
+ function createRouteArtifact(route, options) {
55
+ const refs = route.mergeRefs ?? {};
56
+ const planId = options.planId ?? refs.planId;
57
+ const sources = normalizeSources(refs.sources, route);
58
+ const regions = routeRegions(route, refs, sources);
59
+ const sourceMapLinks = routeSourceMapLinks(route, refs, sources, regions);
60
+ const admissionStatus = routeAdmissionStatus(route);
61
+ const reasonCodes = routeReasonCodes(route);
62
+ const historyId = refs.historyIds?.[0] ?? `history_${route.id}`;
63
+ const patchBundleId = refs.patchBundleIds?.[0] ?? `semantic_patch_bundle_${route.id}`;
64
+ const history = createSemanticHistoryRecord({
65
+ id: historyId,
66
+ createdAt: options.generatedAt,
67
+ language: route.sourceLanguage,
68
+ sourcePath: sources[0]?.sourcePath,
69
+ sources,
70
+ ownershipRegions: regions,
71
+ semanticCandidates: routeSemanticCandidates(route, refs),
72
+ evidenceIds: refs.evidenceIds,
73
+ proofIds: refs.proofIds,
74
+ replayLinks: refs.replayLinks,
75
+ admission: {
76
+ status: admissionStatus,
77
+ readiness: route.readiness,
78
+ reasonCodes,
79
+ evidenceIds: refs.evidenceIds,
80
+ metadata: routeAdmissionMetadata(route, planId)
81
+ },
82
+ metadata: routeRecordMetadata(route, planId, options.metadata)
83
+ }, { id: historyId, createdAt: options.generatedAt });
84
+ const patchBundle = createSemanticPatchBundleRecord({
85
+ id: patchBundleId,
86
+ language: route.sourceLanguage,
87
+ sourcePath: sources[0]?.sourcePath,
88
+ sources,
89
+ changedRegions: regions,
90
+ sourceMapLinks,
91
+ evidenceIds: refs.evidenceIds,
92
+ proofIds: refs.proofIds,
93
+ historyIds: [history.id],
94
+ readiness: route.readiness,
95
+ conflictKeys: refs.conflictKeys,
96
+ admission: {
97
+ status: admissionStatus,
98
+ readiness: route.readiness,
99
+ reviewRequired: true,
100
+ reasonCodes,
101
+ conflictKeys: refs.conflictKeys,
102
+ evidenceIds: refs.evidenceIds,
103
+ metadata: routeAdmissionMetadata(route, planId)
104
+ },
105
+ metadata: routeRecordMetadata(route, planId, options.metadata)
106
+ }, { id: patchBundleId, createdAt: options.generatedAt });
107
+ const materialization = {
108
+ status: 'materialized',
109
+ plannedHistoryIds: refs.historyIds ?? [],
110
+ materializedHistoryIds: [history.id],
111
+ patchBundleIds: [patchBundle.id],
112
+ sourceMapLinkIds: patchBundle.index.sourceMapLinkIds,
113
+ evidenceIds: history.evidenceIds,
114
+ proofIds: history.proofIds,
115
+ autoMergeClaim: false,
116
+ semanticEquivalenceClaim: false
117
+ };
118
+ return {
119
+ kind: 'frontier.lang.universalConversionRouteArtifact',
120
+ version: 1,
121
+ schema: 'frontier.lang.universalConversionRouteArtifact.v1',
122
+ routeId: route.id,
123
+ planId,
124
+ sourceLanguage: route.sourceLanguage,
125
+ target: route.target,
126
+ mode: route.mode,
127
+ routeAction: route.routeAction,
128
+ priority: route.priority,
129
+ readiness: route.readiness,
130
+ admissionAction: route.admissionAction,
131
+ admissionStatus,
132
+ reviewRequired: true,
133
+ history,
134
+ patchBundle,
135
+ materialization,
136
+ mergeScore: route.mergeScore,
137
+ autoMergeClaim: false,
138
+ semanticEquivalenceClaim: false,
139
+ metadata: { ...routeRecordMetadata(route, planId, options.metadata), materialization }
140
+ };
141
+ }
142
+
143
+ function selectRoutes(routes, options) {
144
+ const selected = (routes ?? []).filter((route) => {
145
+ if (options.routeId && route.id !== options.routeId) return false;
146
+ if (options.sourceLanguage && route.sourceLanguage !== options.sourceLanguage) return false;
147
+ if (options.target && route.target !== options.target) return false;
148
+ if (options.mode && route.mode !== options.mode) return false;
149
+ if (options.readiness && route.readiness !== options.readiness) return false;
150
+ if (options.admissionAction && route.admissionAction !== options.admissionAction) return false;
151
+ return true;
152
+ });
153
+ return Number.isFinite(options.maxRoutes) ? selected.slice(0, Math.max(0, Number(options.maxRoutes))) : selected;
154
+ }
155
+
156
+ function normalizeSources(sources, route) {
157
+ return (sources?.length ? sources : [{}]).map((source, index) => ({
158
+ id: source.sourceId ?? source.id ?? `route_source_${idFragment(route.id)}_${index + 1}`,
159
+ importId: source.importId,
160
+ language: route.sourceLanguage,
161
+ sourcePath: source.sourcePath,
162
+ sourceHash: source.sourceHash,
163
+ baseHash: source.baseHash,
164
+ targetHash: source.targetHash,
165
+ metadata: {
166
+ routeId: route.id,
167
+ target: route.target,
168
+ mode: route.mode
169
+ }
170
+ }));
171
+ }
172
+
173
+ function routeRegions(route, refs, sources) {
174
+ const source = sources[0] ?? {};
175
+ const keys = refs.semanticOwnershipKeys?.length
176
+ ? refs.semanticOwnershipKeys
177
+ : [`conversion#${route.sourceLanguage ?? 'source'}#${route.target ?? 'target'}#${route.id}`];
178
+ return uniqueStrings(keys).map((key, index) => ({
179
+ id: `route_region_${idFragment(route.id)}_${index + 1}`,
180
+ key,
181
+ conflictKey: refs.conflictKeys?.[index] ?? refs.conflictKeys?.[0] ?? key,
182
+ changeKind: 'conversion-route',
183
+ regionKind: route.mode,
184
+ granularity: 'conversion-route',
185
+ precision: route.mode === 'preserve-source' ? 'exact-source' : 'semantic-route',
186
+ language: route.sourceLanguage,
187
+ sourcePath: source.sourcePath,
188
+ sourceHash: source.sourceHash,
189
+ sourceMapIds: refs.sourceMapIds,
190
+ sourceMapMappingIds: refs.sourceMapMappingIds,
191
+ admission: {
192
+ readiness: route.readiness,
193
+ action: route.admissionAction,
194
+ conflictKeys: refs.conflictKeys
195
+ },
196
+ metadata: {
197
+ routeId: route.id,
198
+ target: route.target,
199
+ mode: route.mode,
200
+ autoMergeClaim: false,
201
+ semanticEquivalenceClaim: false
202
+ }
203
+ }));
204
+ }
205
+
206
+ function routeSourceMapLinks(route, refs, sources, regions) {
207
+ const source = sources[0] ?? {};
208
+ const max = Math.max(refs.sourceMapIds?.length ?? 0, refs.sourceMapMappingIds?.length ?? 0);
209
+ return Array.from({ length: max }, (_, index) => ({
210
+ id: refs.sourceMapLinkIds?.[index] ?? `route_source_map_link_${idFragment(route.id)}_${index + 1}`,
211
+ sourceMapId: refs.sourceMapIds?.[index] ?? refs.sourceMapIds?.[0],
212
+ sourceMapMappingId: refs.sourceMapMappingIds?.[index],
213
+ sourcePath: source.sourcePath,
214
+ sourceHash: source.sourceHash,
215
+ targetPath: `${route.target}:${source.sourcePath ?? route.id}`,
216
+ precision: route.mode === 'preserve-source' ? 'exact-source' : 'semantic-route',
217
+ regionKey: regions[index % Math.max(1, regions.length)]?.key,
218
+ regionKind: route.mode
219
+ }));
220
+ }
221
+
222
+ function routeSemanticCandidates(route, refs) {
223
+ const ids = refs.mergeCandidateIds?.length ? refs.mergeCandidateIds : [`candidate_${route.id}`];
224
+ return uniqueStrings(ids).map((id) => ({
225
+ id,
226
+ sourcePath: refs.sources?.[0]?.sourcePath,
227
+ baseHash: refs.sources?.[0]?.baseHash,
228
+ targetHash: refs.sources?.[0]?.targetHash,
229
+ readiness: route.readiness,
230
+ conflictKeys: refs.conflictKeys ?? [],
231
+ ownershipKeys: refs.semanticOwnershipKeys ?? [],
232
+ evidenceIds: refs.evidenceIds ?? [],
233
+ proofIds: refs.proofIds ?? [],
234
+ replayIds: (refs.replayLinks ?? []).map((link) => link?.id).filter(Boolean),
235
+ metadata: {
236
+ routeId: route.id,
237
+ target: route.target,
238
+ mode: route.mode,
239
+ risk: route.mergeScore?.risk
240
+ }
241
+ }));
242
+ }
243
+
244
+ function routeAdmissionStatus(route) {
245
+ if (route.readiness === 'blocked' || route.admissionAction === 'reject') return 'blocked';
246
+ if (route.readiness === 'ready' && route.missingEvidence?.length === 0) return 'queued';
247
+ return 'needs-review';
248
+ }
249
+
250
+ function routeReasonCodes(route) {
251
+ return uniqueStrings([
252
+ `mode:${route.mode}`,
253
+ `action:${route.routeAction}`,
254
+ ...(route.missingEvidence ?? []).map((item) => `missing:${item}`),
255
+ ...(route.blockers ?? []).map((item) => `blocker:${item}`),
256
+ ...(route.review ?? []).map((item) => `review:${item}`)
257
+ ]);
258
+ }
259
+
260
+ function routeAdmissionMetadata(route, planId) {
261
+ return {
262
+ planId,
263
+ routeId: route.id,
264
+ routeAction: route.routeAction,
265
+ admissionAction: route.admissionAction,
266
+ priority: route.priority,
267
+ mergeScore: route.mergeScore,
268
+ autoMergeClaim: false,
269
+ semanticEquivalenceClaim: false
270
+ };
271
+ }
272
+
273
+ function routeRecordMetadata(route, planId, metadata) {
274
+ return {
275
+ planId,
276
+ routeId: route.id,
277
+ target: route.target,
278
+ mode: route.mode,
279
+ routeAction: route.routeAction,
280
+ missingEvidence: route.missingEvidence ?? [],
281
+ blockers: route.blockers ?? [],
282
+ review: route.review ?? [],
283
+ autoMergeClaim: false,
284
+ semanticEquivalenceClaim: false,
285
+ ...metadata
286
+ };
287
+ }
@@ -0,0 +1,125 @@
1
+ import {
2
+ idFragment,
3
+ normalizeNativeLanguageId,
4
+ uniqueStrings
5
+ } from './native-import-utils.js';
6
+
7
+ export function importsForConversionLanguage(imports, language) {
8
+ const ids = new Set([language?.language, ...(language?.aliases ?? [])].map(normalizeNativeLanguageId).filter(Boolean));
9
+ return (imports ?? []).filter((imported) => ids.has(normalizeNativeLanguageId(imported?.language ?? imported?.nativeAst?.language)));
10
+ }
11
+
12
+ export function conversionMergeRefs(input) {
13
+ const routeImports = input.imports ?? [];
14
+ const routeId = input.routeId;
15
+ const sourceMaps = routeImports.flatMap(sourceMapsForImport);
16
+ return {
17
+ planId: input.planId,
18
+ routeId,
19
+ historyIds: [`history_${routeId}`],
20
+ patchBundleIds: [],
21
+ patchIds: [],
22
+ mergeCandidateIds: uniqueStrings(routeImports.flatMap(mergeCandidateIds)),
23
+ replayLinks: routeImports.flatMap((imported) => imported?.replayLinks ?? imported?.universalAst?.replayLinks ?? []),
24
+ evidenceIds: uniqueStrings(routeImports.flatMap((imported) => evidenceRecords(imported).map((record) => record.id))),
25
+ proofIds: uniqueStrings(routeImports.flatMap(proofIdsForImport)),
26
+ sources: routeImports.map(conversionSourceRef),
27
+ semanticOwnershipKeys: uniqueStrings(routeImports.flatMap(semanticOwnershipKeysForImport)),
28
+ conflictKeys: uniqueStrings(routeImports.flatMap(conflictKeysForImport)),
29
+ sourceMapIds: uniqueStrings(sourceMaps.map((sourceMap) => sourceMap?.id)),
30
+ sourceMapMappingIds: uniqueStrings(sourceMaps.flatMap((sourceMap) => (sourceMap?.mappings ?? []).map((mapping) => mapping.id))),
31
+ sourceMapLinkIds: [],
32
+ readiness: input.readiness,
33
+ admissionStatus: input.admissionStatus,
34
+ metadata: {
35
+ plannedHistoryId: true,
36
+ note: 'Merge refs are compact route provenance for semantic history and patch-bundle builders; planned IDs are not proof records until materialized.'
37
+ }
38
+ };
39
+ }
40
+
41
+ function conversionSourceRef(imported) {
42
+ return {
43
+ sourceId: imported?.nativeSource?.id ?? imported?.id,
44
+ importId: imported?.id,
45
+ sourcePath: imported?.sourcePath ?? imported?.nativeSource?.sourcePath,
46
+ sourceHash: imported?.sourceHash
47
+ ?? imported?.nativeSource?.sourceHash
48
+ ?? imported?.metadata?.sourcePreservation?.sourceHash,
49
+ baseHash: imported?.baseHash ?? imported?.metadata?.baseHash,
50
+ targetHash: imported?.targetHash ?? imported?.metadata?.targetHash
51
+ };
52
+ }
53
+
54
+ function evidenceRecords(imported) {
55
+ return [
56
+ ...(imported?.evidence ?? []),
57
+ ...(imported?.universalAst?.evidence ?? []),
58
+ ...(imported?.patch?.evidence ?? [])
59
+ ].filter((record) => record?.id);
60
+ }
61
+
62
+ function proofIdsForImport(imported) {
63
+ return [
64
+ ...(imported?.proofIds ?? []),
65
+ ...evidenceRecords(imported).filter((record) => record?.kind === 'proof' || record?.type === 'proof').map((record) => record.id)
66
+ ];
67
+ }
68
+
69
+ function mergeCandidateIds(imported) {
70
+ return (imported?.mergeCandidates ?? []).map((candidate) => candidate.id);
71
+ }
72
+
73
+ function sourceMapsForImport(imported) {
74
+ return [
75
+ ...(imported?.sourceMaps ?? []),
76
+ ...(imported?.universalAst?.sourceMaps ?? [])
77
+ ];
78
+ }
79
+
80
+ function semanticOwnershipKeysForImport(imported) {
81
+ return uniqueStrings([
82
+ ...ownershipKeysFromCandidates(imported),
83
+ ...ownershipKeysFromSourceMaps(imported),
84
+ ...ownershipKeysFromSymbols(imported)
85
+ ]);
86
+ }
87
+
88
+ function conflictKeysForImport(imported) {
89
+ return uniqueStrings([
90
+ ...conflictKeysFromCandidates(imported),
91
+ ...ownershipKeysFromSourceMaps(imported)
92
+ ]);
93
+ }
94
+
95
+ function ownershipKeysFromCandidates(imported) {
96
+ return uniqueStrings((imported?.mergeCandidates ?? []).flatMap((candidate) => [
97
+ ...(candidate?.ownershipKeys ?? []),
98
+ ...(candidate?.semanticOwnershipKeys ?? []),
99
+ ...(candidate?.changedRegions ?? []).map((region) => region?.key)
100
+ ]));
101
+ }
102
+
103
+ function conflictKeysFromCandidates(imported) {
104
+ return uniqueStrings((imported?.mergeCandidates ?? []).flatMap((candidate) => [
105
+ ...(candidate?.conflictKeys ?? []),
106
+ ...(candidate?.changedRegions ?? []).flatMap((region) => [region?.conflictKey, ...(region?.admission?.conflictKeys ?? [])])
107
+ ]));
108
+ }
109
+
110
+ function ownershipKeysFromSourceMaps(imported) {
111
+ return uniqueStrings(sourceMapsForImport(imported).flatMap((sourceMap) => (sourceMap?.mappings ?? []).flatMap((mapping) => [
112
+ mapping?.ownershipRegionKey,
113
+ mapping?.ownershipRegionId,
114
+ mapping?.ownershipRegionKind ? `${mapping.sourceSpan?.path ?? imported?.sourcePath ?? 'source'}#${mapping.ownershipRegionKind}` : undefined
115
+ ])));
116
+ }
117
+
118
+ function ownershipKeysFromSymbols(imported) {
119
+ const symbols = imported?.semanticIndex?.symbols ?? imported?.universalAst?.semanticIndex?.symbols ?? [];
120
+ return uniqueStrings(symbols.flatMap((symbol) => [
121
+ symbol?.metadata?.ownershipRegionKey,
122
+ symbol?.metadata?.ownershipRegionId,
123
+ symbol?.metadata?.ownershipRegionKind ? `${symbol.language ?? imported?.language ?? 'source'}#${idFragment(symbol.name)}#${symbol.metadata.ownershipRegionKind}` : undefined
124
+ ]));
125
+ }
@@ -0,0 +1,134 @@
1
+ import { uniqueStrings } from './native-import-utils.js';
2
+
3
+ const readinessScore = Object.freeze({ ready: 100, 'ready-with-losses': 76, 'needs-review': 48, blocked: 0 });
4
+ const readinessRank = Object.freeze({ ready: 3, 'ready-with-losses': 2, 'needs-review': 1, blocked: 0 });
5
+ const actionRank = Object.freeze({ admit: 2, prioritize: 1, reject: 0 });
6
+ const modeRank = Object.freeze({
7
+ 'preserve-source': 5,
8
+ 'target-adapter': 4,
9
+ 'stub-only': 3,
10
+ 'semantic-index-only': 2,
11
+ blocked: 0
12
+ });
13
+ const componentWeights = Object.freeze({
14
+ importEvidence: 22,
15
+ parserEvidence: 18,
16
+ semanticIndex: 18,
17
+ projectionPath: 24,
18
+ proofEvidence: 18
19
+ });
20
+
21
+ export function conversionScoreComponents(language, targetCell, readiness, mode, evidence) {
22
+ return {
23
+ importEvidence: scoreComponent('importEvidence', readinessScore[language.imports.readiness] ?? 48, [
24
+ ...(language.imports.total ? [] : ['No source import evidence.']),
25
+ ...(language.imports.readiness === 'ready' ? [] : [`Import readiness is ${language.imports.readiness}.`])
26
+ ], { imports: language.imports.total, losses: language.imports.losses }),
27
+ parserEvidence: scoreComponent('parserEvidence', parserScore(language), [
28
+ ...(language.parser.rows ? [] : ['No parser feature row matched this language.']),
29
+ ...(language.parser.blockingFeatures ?? []).map((feature) => `Parser feature is blocked: ${feature}.`)
30
+ ], { rows: language.parser.rows, mergeReadyParsers: language.parser.mergeReadyParsers.length }),
31
+ semanticIndex: scoreComponent('semanticIndex', semanticIndexScore(language), [
32
+ ...(language.imports.symbols ? [] : ['No semantic symbols were imported.']),
33
+ ...(language.imports.sourceMapMappings ? [] : ['No source-map mappings were imported.'])
34
+ ], { symbols: language.imports.symbols, sourceMapMappings: language.imports.sourceMapMappings }),
35
+ projectionPath: scoreComponent('projectionPath', projectionPathScore(targetCell, mode, readiness), projectionPathReasons(targetCell, mode), {
36
+ mode,
37
+ lossClass: targetCell?.lossClass,
38
+ adapter: targetCell?.adapter,
39
+ readiness
40
+ }),
41
+ proofEvidence: scoreComponent('proofEvidence', proofEvidenceScore(evidence), proofEvidenceReasons(evidence), {
42
+ records: evidence.length,
43
+ passed: evidence.filter((record) => passedEvidence(record)).length,
44
+ failed: evidence.filter((record) => record?.status === 'failed').length
45
+ })
46
+ };
47
+ }
48
+
49
+ export function conversionMergeScore(input) {
50
+ const weighted = Object.values(input.components).reduce((sum, component) => sum + component.weightedScore, 0);
51
+ const weight = Object.values(input.components).reduce((sum, component) => sum + component.weight, 0);
52
+ const uncappedValue = Math.round(weight ? weighted * 100 / weight : 0);
53
+ const action = input.blockers.length || input.readiness === 'blocked' || input.mode === 'blocked' ? 'reject'
54
+ : input.readiness === 'ready' && input.mode !== 'stub-only' && input.mode !== 'semantic-index-only' ? 'admit'
55
+ : 'prioritize';
56
+ const value = action === 'reject' ? Math.min(35, uncappedValue) : uncappedValue;
57
+ return {
58
+ schema: 'frontier.lang.semanticMergeScore.v1',
59
+ version: 1,
60
+ value,
61
+ uncappedValue,
62
+ sortKey: value + (actionRank[action] ?? 0) * 1000 + (modeRank[input.mode] ?? 0) * 100 + (readinessRank[input.readiness] ?? 0) * 10,
63
+ higherIsBetter: true,
64
+ readiness: input.readiness,
65
+ risk: input.readiness === 'blocked' ? 'high' : input.readiness === 'needs-review' ? 'medium' : 'low',
66
+ action,
67
+ components: input.components,
68
+ penalties: uniqueStrings([
69
+ ...(action === 'reject' ? ['Conversion route is rejected until blockers are resolved.'] : []),
70
+ ...Object.values(input.components).flatMap((component) => component.score < 100 ? component.reasons : [])
71
+ ])
72
+ };
73
+ }
74
+
75
+ function parserScore(language) {
76
+ if (!language.parser.rows) return 0;
77
+ return Math.min(100, (readinessScore[language.parser.readiness] ?? 48) + Math.min(16, language.parser.mergeReadyParsers.length * 8));
78
+ }
79
+
80
+ function semanticIndexScore(language) {
81
+ const symbols = language.imports.symbols ?? 0;
82
+ const mappings = language.imports.sourceMapMappings ?? 0;
83
+ if (!symbols) return 0;
84
+ return Math.min(100, 62 + Math.min(22, symbols * 3) + Math.min(16, mappings * 2));
85
+ }
86
+
87
+ function projectionPathScore(targetCell, mode, readiness) {
88
+ if (mode === 'blocked') return 0;
89
+ if (mode === 'preserve-source') return 92;
90
+ if (mode === 'target-adapter') return Math.min(92, (readinessScore[readiness] ?? 48) + (targetCell?.adapter ? 12 : 0));
91
+ if (mode === 'stub-only') return 44;
92
+ if (mode === 'semantic-index-only') return 30;
93
+ return 10;
94
+ }
95
+
96
+ function proofEvidenceScore(evidence) {
97
+ if (!evidence.length) return 45;
98
+ const passed = evidence.filter((record) => passedEvidence(record)).length;
99
+ const failed = evidence.filter((record) => record?.status === 'failed').length;
100
+ return Math.max(0, Math.min(100, 55 + passed * 12 - failed * 35));
101
+ }
102
+
103
+ function projectionPathReasons(targetCell, mode) {
104
+ return uniqueStrings([
105
+ ...(targetCell?.reason ? [targetCell.reason] : []),
106
+ ...(mode === 'blocked' ? ['Projection path is blocked.'] : []),
107
+ ...(mode === 'semantic-index-only' ? ['Semantic index can guide review, but code emission needs a target adapter.'] : [])
108
+ ]);
109
+ }
110
+
111
+ function proofEvidenceReasons(evidence) {
112
+ return uniqueStrings([
113
+ ...(!evidence.length ? ['No proof, oracle, test, or replay evidence was attached to this conversion route.'] : []),
114
+ ...(evidence.filter((record) => record?.status === 'failed').length ? ['At least one attached evidence record failed.'] : [])
115
+ ]);
116
+ }
117
+
118
+ function scoreComponent(key, score, reasons, signals) {
119
+ const normalized = Math.max(0, Math.min(100, Math.round(score)));
120
+ const weight = componentWeights[key];
121
+ return {
122
+ key,
123
+ score: normalized,
124
+ weight,
125
+ weightedScore: normalized * weight / 100,
126
+ status: normalized >= 80 ? 'strong' : normalized >= 50 ? 'partial' : normalized > 0 ? 'weak' : 'blocked',
127
+ reasons: uniqueStrings(reasons),
128
+ signals
129
+ };
130
+ }
131
+
132
+ function passedEvidence(record) {
133
+ return record?.status === 'passed' || record?.status === 'ok' || record?.status === 'success';
134
+ }