@shapeshift-labs/frontier-lang-compiler 0.2.65 → 0.2.67
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.
- package/README.md +37 -8
- package/bench/smoke.mjs +15 -1
- package/bench/universal-fixture-suite.mjs +183 -0
- package/dist/declarations/import-adapter-core.d.ts +3 -0
- package/dist/declarations/native-project-admission.d.ts +133 -0
- package/dist/declarations/roundtrip-audit.d.ts +177 -0
- package/dist/declarations/roundtrip.d.ts +2 -53
- package/dist/declarations/semantic-history-records.d.ts +277 -0
- package/dist/declarations/semantic-history.d.ts +45 -92
- package/dist/declarations/semantic-merge-candidates.d.ts +200 -0
- package/dist/declarations/semantic-merge-conflicts.d.ts +12 -0
- package/dist/declarations/semantic-sidecar.d.ts +8 -3
- package/dist/declarations/semantic-slice-admission.d.ts +111 -0
- package/dist/declarations/semantic-slice.d.ts +36 -1
- package/dist/declarations/universal-conversion-plan.d.ts +59 -0
- package/dist/declarations/universal-runtime-capabilities.d.ts +171 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal/index-impl/attachExternalOwnership.js +18 -10
- package/dist/internal/index-impl/createNativeRoundtripEvidence.js +54 -49
- package/dist/internal/index-impl/createSemanticImportSidecar.js +6 -0
- package/dist/internal/index-impl/createSemanticSlice.js +4 -3
- package/dist/internal/index-impl/createSemanticSliceAdmissionRecord.js +10 -1
- package/dist/internal/index-impl/diffNativeSourceImports.js +3 -2
- package/dist/internal/index-impl/expandSemanticSliceSelection.js +0 -1
- package/dist/internal/index-impl/externalSemanticBase.js +1 -0
- package/dist/internal/index-impl/importExternalSemanticIndex.js +4 -0
- package/dist/internal/index-impl/nativeRoundtripAudit.js +217 -0
- package/dist/internal/index-impl/projectImportAdmissionImportEvidence.js +160 -0
- package/dist/internal/index-impl/projectImportAdmissionLanguageSummaries.js +247 -0
- package/dist/internal/index-impl/projectImportAdmissionRanks.js +52 -0
- package/dist/internal/index-impl/projectImportAdmissionSummaries.js +77 -117
- package/dist/internal/index-impl/projectImportAdmissionTasks.js +239 -0
- package/dist/internal/index-impl/semanticHistoryRecordNormalizers.js +151 -0
- package/dist/internal/index-impl/semanticHistoryRecordOverlaps.js +113 -0
- package/dist/internal/index-impl/semanticHistoryRecords.js +210 -149
- package/dist/internal/index-impl/semanticMergeCandidateRecordInternals.js +314 -0
- package/dist/internal/index-impl/semanticMergeCandidateRecords.js +241 -0
- package/dist/internal/index-impl/semanticSliceAdmissionSurface.js +142 -0
- package/dist/internal/index-impl/semanticSliceExpectationAssertions.js +100 -0
- package/dist/internal/index-impl/semanticSliceExpectationRecords.js +75 -0
- package/dist/internal/index-impl/semanticSliceExpectedAssertions.js +5 -2
- package/dist/internal/index-impl/testSemanticSlice.js +4 -1
- package/dist/internal/index-impl/withExternalEmptyLoss.js +1 -0
- package/dist/language-adapter-package-contracts.js +12 -57
- package/dist/language-adapter-package-rows.js +116 -0
- package/dist/lightweight-dependency-language.js +8 -0
- package/dist/native-region-scanner-core.js +42 -10
- package/dist/native-region-scanner-js-helpers.js +2 -0
- package/dist/native-region-scanner-js-imports.js +111 -0
- package/dist/native-region-scanner-js.js +111 -28
- package/dist/universal-conversion-plan-summary.js +42 -0
- package/dist/universal-conversion-plan.js +46 -40
- package/dist/universal-runtime-capabilities.js +92 -0
- package/dist/universal-runtime-host-selectors.js +192 -0
- package/dist/universal-runtime-profiles.js +109 -0
- package/dist/universal-runtime-route-records.js +162 -0
- package/examples/js-frontier-rust-workbench-adapters.mjs +89 -0
- package/examples/js-frontier-rust-workbench-bounds.mjs +4 -3
- package/examples/js-frontier-rust-workbench-client.mjs +135 -59
- package/examples/js-frontier-rust-workbench-convert.mjs +161 -0
- package/examples/js-frontier-rust-workbench-html.mjs +20 -13
- package/examples/js-frontier-rust-workbench-route-styles.mjs +126 -0
- package/examples/js-frontier-rust-workbench-route.mjs +190 -0
- package/examples/js-frontier-rust-workbench-styles.mjs +12 -54
- package/examples/js-frontier-rust-workbench.mjs +54 -214
- package/package.json +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
export function routeExplanationSummary(plan, routes) {
|
|
2
|
+
const missingEvidence = uniqueStrings(routes.flatMap((route) => route.missingEvidence));
|
|
3
|
+
const blockers = uniqueStrings(routes.flatMap((route) => route.blockers));
|
|
4
|
+
const review = uniqueStrings(routes.flatMap((route) => route.review));
|
|
5
|
+
const readiness = aggregateReadiness(routes.map((route) => route.semanticMerge.readiness));
|
|
6
|
+
const routeScores = routes.map((route) => route.semanticMerge.score).filter((score) => Number.isFinite(score));
|
|
7
|
+
return {
|
|
8
|
+
kind: 'frontier.workbench.routeExplanation',
|
|
9
|
+
version: 1,
|
|
10
|
+
planId: plan.id,
|
|
11
|
+
summary: {
|
|
12
|
+
routes: routes.length,
|
|
13
|
+
targetAdapterRoutes: plan.summary.targetAdapterRoutes,
|
|
14
|
+
missingEvidence: missingEvidence.length,
|
|
15
|
+
blockers: blockers.length,
|
|
16
|
+
reviewReasons: review.length,
|
|
17
|
+
autoMergeClaims: plan.summary.autoMergeClaims,
|
|
18
|
+
semanticEquivalenceClaims: plan.summary.semanticEquivalenceClaims
|
|
19
|
+
},
|
|
20
|
+
semanticMerge: {
|
|
21
|
+
readiness,
|
|
22
|
+
ready: readiness === 'ready' && missingEvidence.length === 0 && blockers.length === 0,
|
|
23
|
+
admissionAction: aggregateAdmissionAction(routes.map((route) => route.semanticMerge.admissionAction)),
|
|
24
|
+
score: routeScores.length ? Math.min(...routeScores) : 0,
|
|
25
|
+
risk: aggregateRisk(routes.map((route) => route.semanticMerge.risk)),
|
|
26
|
+
missingEvidence,
|
|
27
|
+
missingEvidenceDetails: missingEvidence.map(evidenceGapDetail),
|
|
28
|
+
blockers,
|
|
29
|
+
review,
|
|
30
|
+
autoMergeClaim: false,
|
|
31
|
+
semanticEquivalenceClaim: false
|
|
32
|
+
},
|
|
33
|
+
routes
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function explainRoute(route, context) {
|
|
38
|
+
const missingEvidence = routeMissingEvidence(route, context.projection);
|
|
39
|
+
const semanticReadiness = routeSemanticMergeReadiness(route, missingEvidence);
|
|
40
|
+
const semanticAdmissionAction = routeSemanticAdmissionAction(route, semanticReadiness, missingEvidence);
|
|
41
|
+
return {
|
|
42
|
+
id: route?.id ?? `conversion_${context.sourceLanguage}_to_${context.target}`,
|
|
43
|
+
sourceLanguage: route?.sourceLanguage ?? context.sourceLanguage,
|
|
44
|
+
target: route?.target ?? context.target,
|
|
45
|
+
mode: route?.mode ?? 'blocked',
|
|
46
|
+
routeAction: route?.routeAction ?? 'blocked',
|
|
47
|
+
readiness: route?.readiness ?? 'blocked',
|
|
48
|
+
priority: route?.priority ?? 'blocker',
|
|
49
|
+
adapter: route?.adapter,
|
|
50
|
+
adapterKind: route?.adapterKind,
|
|
51
|
+
targetPath: context.projection?.targetPath,
|
|
52
|
+
missingEvidence,
|
|
53
|
+
missingEvidenceDetails: missingEvidence.map(evidenceGapDetail),
|
|
54
|
+
blockers: route?.blockers ?? ['No conversion route was planned.'],
|
|
55
|
+
review: route?.review ?? [],
|
|
56
|
+
evidence: {
|
|
57
|
+
imports: route?.evidence?.imports ?? 0,
|
|
58
|
+
importReadiness: route?.evidence?.importReadiness,
|
|
59
|
+
symbols: route?.evidence?.symbols ?? 0,
|
|
60
|
+
sourceMapMappings: route?.evidence?.sourceMapMappings ?? 0,
|
|
61
|
+
parserRows: route?.evidence?.parserRows ?? 0,
|
|
62
|
+
mergeReadyParsers: route?.evidence?.mergeReadyParsers ?? 0,
|
|
63
|
+
targetSupported: route?.evidence?.targetSupported === true,
|
|
64
|
+
targetAdapter: route?.evidence?.targetAdapter ?? route?.adapter,
|
|
65
|
+
projectionEvidenceIds: (context.projection?.evidence ?? []).map((record) => record.id).filter(Boolean)
|
|
66
|
+
},
|
|
67
|
+
semanticMerge: {
|
|
68
|
+
readiness: semanticReadiness,
|
|
69
|
+
score: route?.mergeScore?.value ?? 0,
|
|
70
|
+
risk: routeSemanticRisk(route, semanticReadiness),
|
|
71
|
+
admissionAction: semanticAdmissionAction,
|
|
72
|
+
penalties: uniqueStrings([
|
|
73
|
+
...(route?.mergeScore?.penalties ?? []),
|
|
74
|
+
...missingEvidence.map((key) => `Missing evidence: ${key}`)
|
|
75
|
+
]).slice(0, 6),
|
|
76
|
+
autoMergeClaim: route?.autoMergeClaim === true,
|
|
77
|
+
semanticEquivalenceClaim: route?.semanticEquivalenceClaim === true
|
|
78
|
+
},
|
|
79
|
+
explanation: routeExplanationText(route, missingEvidence, semanticReadiness)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function routeMissingEvidence(route, projection) {
|
|
84
|
+
const missing = new Set(route?.missingEvidence ?? []);
|
|
85
|
+
if ((route?.evidence?.mergeReadyParsers ?? 0) === 0) missing.add('merge-ready-parser');
|
|
86
|
+
if (!hasProofEvidence(projection?.evidence ?? [])) missing.add('proof-or-replay-evidence');
|
|
87
|
+
if (!hasRuntimeAdapterEvidence(projection?.evidence ?? [])) missing.add('runtime-adapter-evidence');
|
|
88
|
+
return [...missing].sort();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function routeExplanationText(route, missingEvidence, semanticReadiness) {
|
|
92
|
+
if (!route) return 'No conversion route was planned for this target.';
|
|
93
|
+
const adapter = route.adapter ? ` through ${route.adapter}` : '';
|
|
94
|
+
const evidence = missingEvidence.length ? ` missing ${missingEvidence.join(', ')}` : ' with required evidence attached';
|
|
95
|
+
return `${route.sourceLanguage} -> ${route.target} uses ${route.mode}${adapter}; semantic merge readiness is ${semanticReadiness}${evidence}.`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function evidenceGapDetail(key) {
|
|
99
|
+
const details = {
|
|
100
|
+
'merge-ready-parser': {
|
|
101
|
+
key,
|
|
102
|
+
label: 'Merge-ready parser evidence',
|
|
103
|
+
status: 'missing',
|
|
104
|
+
summary: 'Attach exact parser, source range, token/trivia, and feature coverage evidence before merge admission.'
|
|
105
|
+
},
|
|
106
|
+
'proof-or-replay-evidence': {
|
|
107
|
+
key,
|
|
108
|
+
label: 'Proof or replay evidence',
|
|
109
|
+
status: 'missing',
|
|
110
|
+
summary: 'Attach proof, oracle, replay, or behavior test evidence for the projected route.'
|
|
111
|
+
},
|
|
112
|
+
'runtime-adapter-evidence': {
|
|
113
|
+
key,
|
|
114
|
+
label: 'Runtime adapter evidence',
|
|
115
|
+
status: 'missing',
|
|
116
|
+
summary: 'Attach runtime adapter evidence for imports, effects, host APIs, package modules, and target runtime shims.'
|
|
117
|
+
},
|
|
118
|
+
'source-preservation-hash': {
|
|
119
|
+
key,
|
|
120
|
+
label: 'Source preservation hash',
|
|
121
|
+
status: 'missing',
|
|
122
|
+
summary: 'Attach exact source preservation evidence so source text can be traced to merge candidates.'
|
|
123
|
+
},
|
|
124
|
+
'target-adapter': {
|
|
125
|
+
key,
|
|
126
|
+
label: 'Target adapter',
|
|
127
|
+
status: 'missing',
|
|
128
|
+
summary: 'Add a source-to-target projection adapter for emitted target code.'
|
|
129
|
+
},
|
|
130
|
+
'target-adapter-evidence': {
|
|
131
|
+
key,
|
|
132
|
+
label: 'Target adapter evidence',
|
|
133
|
+
status: 'missing',
|
|
134
|
+
summary: 'Attach run evidence from the host-owned target projection adapter.'
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
return details[key] ?? { key, label: key, status: 'missing', summary: 'Attach route evidence before merge admission.' };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function hasProofEvidence(evidence) {
|
|
141
|
+
return evidence.some((record) => /proof|oracle|replay|behavior|test|verification/.test(String(record.kind ?? record.metadata?.kind ?? record.type ?? '')));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function hasRuntimeAdapterEvidence(evidence) {
|
|
145
|
+
return evidence.some((record) => {
|
|
146
|
+
const capabilities = record.metadata?.capabilities ?? [];
|
|
147
|
+
return record.metadata?.runtimeAdapterEvidence === true
|
|
148
|
+
|| capabilities.includes('runtime-adapter')
|
|
149
|
+
|| /runtime/.test(String(record.kind ?? record.metadata?.kind ?? record.type ?? ''));
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function uniqueStrings(values) {
|
|
154
|
+
return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function aggregateReadiness(values) {
|
|
158
|
+
const rank = { ready: 0, 'ready-with-losses': 1, 'needs-review': 2, blocked: 3 };
|
|
159
|
+
return values.reduce((current, value) => (rank[value] ?? 3) > (rank[current] ?? 3) ? value : current, 'ready');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function routeSemanticMergeReadiness(route, missingEvidence) {
|
|
163
|
+
const base = route?.mergeScore?.readiness ?? route?.readiness ?? 'blocked';
|
|
164
|
+
if (!missingEvidence.length || base === 'blocked') return base;
|
|
165
|
+
return aggregateReadiness([base, 'needs-review']);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function routeSemanticAdmissionAction(route, readiness, missingEvidence) {
|
|
169
|
+
if ((route?.mergeScore?.action ?? route?.admissionAction) === 'reject' || readiness === 'blocked') return 'reject';
|
|
170
|
+
if (readiness === 'ready' && missingEvidence.length === 0) return route?.mergeScore?.action ?? 'admit';
|
|
171
|
+
return 'prioritize';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function routeSemanticRisk(route, readiness) {
|
|
175
|
+
if (readiness === 'blocked') return 'high';
|
|
176
|
+
if (readiness === 'needs-review') return 'medium';
|
|
177
|
+
return route?.mergeScore?.risk ?? 'low';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function aggregateAdmissionAction(values) {
|
|
181
|
+
if (values.includes('reject')) return 'reject';
|
|
182
|
+
if (values.includes('prioritize')) return 'prioritize';
|
|
183
|
+
return values[0] ?? 'prioritize';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function aggregateRisk(values) {
|
|
187
|
+
if (values.includes('high')) return 'high';
|
|
188
|
+
if (values.includes('medium')) return 'medium';
|
|
189
|
+
return values[0] ?? 'low';
|
|
190
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { workbenchRouteStyles } from './js-frontier-rust-workbench-route-styles.mjs';
|
|
2
|
+
|
|
1
3
|
export function workbenchStyles() {
|
|
2
4
|
return `
|
|
3
5
|
:root {
|
|
@@ -69,8 +71,8 @@ body {
|
|
|
69
71
|
.brand h1 { margin: 0; font-size: 15px; line-height: 1.1; letter-spacing: 0; }
|
|
70
72
|
.brand p { margin: 4px 0 0; color: var(--muted); font-size: 12px; }
|
|
71
73
|
|
|
72
|
-
.topActions { display: flex; align-items: center; gap: 8px; }
|
|
73
|
-
.iconButton, .segmented button, .
|
|
74
|
+
.topActions { display: flex; align-items: center; gap: 8px; margin: 0; }
|
|
75
|
+
.iconButton, .segmented button, .runButton {
|
|
74
76
|
height: 32px;
|
|
75
77
|
border: 1px solid var(--line);
|
|
76
78
|
background: var(--panel-2);
|
|
@@ -79,17 +81,7 @@ body {
|
|
|
79
81
|
}
|
|
80
82
|
.iconButton { width: 34px; font-size: 16px; cursor: pointer; }
|
|
81
83
|
.runButton { min-width: 64px; padding: 0 14px; cursor: pointer; color: var(--green); }
|
|
82
|
-
.
|
|
83
|
-
.modeGroup button {
|
|
84
|
-
width: 84px;
|
|
85
|
-
border-radius: 0;
|
|
86
|
-
font-size: 12px;
|
|
87
|
-
cursor: pointer;
|
|
88
|
-
}
|
|
89
|
-
.modeGroup button:first-child { border-radius: 7px 0 0 7px; }
|
|
90
|
-
.modeGroup button:last-child { border-radius: 0 7px 7px 0; border-left: 0; }
|
|
91
|
-
.modeGroup button[aria-pressed="true"] { background: #213127; color: var(--green); }
|
|
92
|
-
.iconButton:hover, .segmented button:hover, .modeGroup button:hover, .runButton:hover { background: var(--panel-3); }
|
|
84
|
+
.iconButton:hover, .segmented button:hover, .runButton:hover { background: var(--panel-3); }
|
|
93
85
|
|
|
94
86
|
.statusStrip {
|
|
95
87
|
position: fixed;
|
|
@@ -97,7 +89,7 @@ body {
|
|
|
97
89
|
z-index: 19;
|
|
98
90
|
height: var(--status-height);
|
|
99
91
|
display: grid;
|
|
100
|
-
grid-template-columns: repeat(
|
|
92
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
101
93
|
background: #0f1214;
|
|
102
94
|
overflow: hidden;
|
|
103
95
|
}
|
|
@@ -132,7 +124,7 @@ body {
|
|
|
132
124
|
height: calc(100dvh - var(--chrome-height));
|
|
133
125
|
min-height: 0;
|
|
134
126
|
display: grid;
|
|
135
|
-
grid-template-columns: minmax(
|
|
127
|
+
grid-template-columns: minmax(260px, 0.9fr) minmax(320px, 1.1fr) minmax(280px, 1fr) minmax(280px, 1fr);
|
|
136
128
|
overflow: hidden;
|
|
137
129
|
}
|
|
138
130
|
.pane {
|
|
@@ -189,6 +181,9 @@ textarea, .codeBlock {
|
|
|
189
181
|
overscroll-behavior: contain;
|
|
190
182
|
white-space: pre;
|
|
191
183
|
}
|
|
184
|
+
textarea[readonly] {
|
|
185
|
+
cursor: default;
|
|
186
|
+
}
|
|
192
187
|
|
|
193
188
|
.graphView {
|
|
194
189
|
padding: 12px;
|
|
@@ -230,44 +225,7 @@ textarea, .codeBlock {
|
|
|
230
225
|
font-size: 11px;
|
|
231
226
|
text-transform: uppercase;
|
|
232
227
|
}
|
|
233
|
-
|
|
234
|
-
display: grid;
|
|
235
|
-
gap: 6px;
|
|
236
|
-
margin: 0;
|
|
237
|
-
padding: 0;
|
|
238
|
-
list-style: none;
|
|
239
|
-
}
|
|
240
|
-
.edgeList li {
|
|
241
|
-
border-left: 2px solid var(--blue);
|
|
242
|
-
background: rgba(109, 180, 232, .08);
|
|
243
|
-
padding: 6px 8px;
|
|
244
|
-
color: var(--muted);
|
|
245
|
-
font-size: 12px;
|
|
246
|
-
overflow-wrap: anywhere;
|
|
247
|
-
}
|
|
248
|
-
.boundsGrid {
|
|
249
|
-
display: grid;
|
|
250
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
251
|
-
gap: 8px;
|
|
252
|
-
}
|
|
253
|
-
.boundCard {
|
|
254
|
-
border: 1px solid var(--line);
|
|
255
|
-
border-radius: 8px;
|
|
256
|
-
background: #12171a;
|
|
257
|
-
padding: 10px;
|
|
258
|
-
}
|
|
259
|
-
.boundCard strong {
|
|
260
|
-
display: block;
|
|
261
|
-
margin-bottom: 8px;
|
|
262
|
-
font-size: 12px;
|
|
263
|
-
}
|
|
264
|
-
.boundCard ul {
|
|
265
|
-
margin: 0;
|
|
266
|
-
padding-left: 16px;
|
|
267
|
-
color: var(--muted);
|
|
268
|
-
font-size: 11px;
|
|
269
|
-
line-height: 1.45;
|
|
270
|
-
}
|
|
228
|
+
${workbenchRouteStyles()}
|
|
271
229
|
.segmented { display: flex; gap: 0; }
|
|
272
230
|
.segmented button {
|
|
273
231
|
width: 58px;
|
|
@@ -291,7 +249,7 @@ textarea, .codeBlock {
|
|
|
291
249
|
}
|
|
292
250
|
.workspace {
|
|
293
251
|
grid-template-columns: 1fr;
|
|
294
|
-
grid-template-rows: repeat(
|
|
252
|
+
grid-template-rows: repeat(4, minmax(0, 1fr));
|
|
295
253
|
overflow: hidden;
|
|
296
254
|
}
|
|
297
255
|
.pane {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import http from 'node:http';
|
|
3
|
-
import {
|
|
4
|
-
import { conversionBounds } from './js-frontier-rust-workbench-bounds.mjs';
|
|
3
|
+
import { convertSource } from './js-frontier-rust-workbench-convert.mjs';
|
|
5
4
|
import { renderWorkbenchHtml } from './js-frontier-rust-workbench-html.mjs';
|
|
6
5
|
import { workbenchClientScript } from './js-frontier-rust-workbench-client.mjs';
|
|
7
6
|
import { workbenchStyles } from './js-frontier-rust-workbench-styles.mjs';
|
|
@@ -24,21 +23,61 @@ export class TodoStore {
|
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
`;
|
|
27
|
-
const sampleRustSource = `// Generated from Frontier semantic graph evidence.
|
|
28
|
-
pub fn add_todo() {
|
|
29
|
-
// port body from preserved TypeScript source
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
pub struct TodoStore;
|
|
33
|
-
`;
|
|
34
26
|
|
|
35
27
|
if (args.smoke) {
|
|
36
28
|
const result = convertSource(sampleSource, { sourceLanguage: 'typescript' });
|
|
37
29
|
assert(result.summary.symbols >= 2, 'expected semantic symbols');
|
|
38
|
-
assert(result.
|
|
39
|
-
|
|
40
|
-
assert(
|
|
41
|
-
|
|
30
|
+
assert(result.frontier.universalAst.valid, 'expected valid universal AST summary');
|
|
31
|
+
assert(result.projections.rust.output.includes('pub'), 'expected Rust projection');
|
|
32
|
+
assert(result.projections.python.output.includes('def add_todo'), 'expected Python projection');
|
|
33
|
+
assert(result.routeExplanation.routes.length === 2, 'expected route explanation for both targets');
|
|
34
|
+
assert(result.routeExplanation.semanticMerge.readiness === 'blocked', 'expected blocked semantic merge readiness');
|
|
35
|
+
assert(result.routeExplanation.semanticMerge.missingEvidence.includes('proof-or-replay-evidence'), 'expected missing proof evidence');
|
|
36
|
+
assert(result.routeExplanation.semanticMerge.missingEvidence.includes('merge-ready-parser'), 'expected missing parser evidence');
|
|
37
|
+
assert(result.routeExplanation.semanticMerge.missingEvidence.includes('runtime-adapter-evidence'), 'expected missing runtime adapter evidence');
|
|
38
|
+
const html = renderWorkbenchHtml({
|
|
39
|
+
sourceLanguage: 'typescript',
|
|
40
|
+
sources: { typescript: sampleSource },
|
|
41
|
+
result
|
|
42
|
+
});
|
|
43
|
+
const client = workbenchClientScript();
|
|
44
|
+
const styles = workbenchStyles();
|
|
45
|
+
assert(html.includes('id="convertForm"'), 'expected submit form');
|
|
46
|
+
assert(html.includes('id="typescriptInput"'), 'expected TypeScript input pane');
|
|
47
|
+
assert(html.includes('id="frontierJson"'), 'expected Frontier graph JSON pane');
|
|
48
|
+
assert(html.includes('id="rustOutput"'), 'expected Rust output pane');
|
|
49
|
+
assert(html.includes('id="pythonOutput"'), 'expected Python output pane');
|
|
50
|
+
assert(client.includes("addEventListener('submit'"), 'expected submit-based conversion listener');
|
|
51
|
+
assert(client.includes('Route Explanation'), 'expected route explanation rendering');
|
|
52
|
+
assert(styles.includes('grid-template-columns: minmax(260px, 0.9fr) minmax(320px, 1.1fr) minmax(280px, 1fr) minmax(280px, 1fr);'), 'expected four-pane desktop scaffold');
|
|
53
|
+
assert(styles.includes('overflow: auto'), 'expected scrollable pane bodies');
|
|
54
|
+
assert(styles.includes('.workspace {'), 'expected fixed workspace section');
|
|
55
|
+
assert(styles.includes('.routeCard'), 'expected route explanation styles');
|
|
56
|
+
console.log(JSON.stringify({
|
|
57
|
+
ok: true,
|
|
58
|
+
summary: result.summary,
|
|
59
|
+
projections: {
|
|
60
|
+
rust: { ok: result.projections.rust.ok, readiness: result.projections.rust.readiness },
|
|
61
|
+
python: { ok: result.projections.python.ok, readiness: result.projections.python.readiness }
|
|
62
|
+
},
|
|
63
|
+
routeExplanation: {
|
|
64
|
+
semanticMerge: result.routeExplanation.semanticMerge,
|
|
65
|
+
routes: result.routeExplanation.routes.map((route) => ({
|
|
66
|
+
target: route.target,
|
|
67
|
+
mode: route.mode,
|
|
68
|
+
routeAction: route.routeAction,
|
|
69
|
+
readiness: route.readiness,
|
|
70
|
+
admissionAction: route.semanticMerge.admissionAction,
|
|
71
|
+
missingEvidence: route.missingEvidence
|
|
72
|
+
}))
|
|
73
|
+
},
|
|
74
|
+
layout: {
|
|
75
|
+
submitBased: true,
|
|
76
|
+
panes: ['typescript', 'frontier', 'rust', 'python'],
|
|
77
|
+
independentScrollRegions: 4,
|
|
78
|
+
fixedSections: ['topbar', 'statusStrip', 'workspace']
|
|
79
|
+
}
|
|
80
|
+
}, null, 2));
|
|
42
81
|
} else {
|
|
43
82
|
const server = http.createServer(routeRequest);
|
|
44
83
|
server.listen(port, '127.0.0.1', () => {
|
|
@@ -51,7 +90,7 @@ async function routeRequest(request, response) {
|
|
|
51
90
|
if (request.method === 'GET' && request.url === '/') {
|
|
52
91
|
return send(response, 200, renderWorkbenchHtml({
|
|
53
92
|
sourceLanguage: 'typescript',
|
|
54
|
-
sources: { typescript: sampleSource
|
|
93
|
+
sources: { typescript: sampleSource },
|
|
55
94
|
result: convertSource(sampleSource, { sourceLanguage: 'typescript' })
|
|
56
95
|
}), 'text/html; charset=utf-8');
|
|
57
96
|
}
|
|
@@ -66,7 +105,7 @@ async function routeRequest(request, response) {
|
|
|
66
105
|
return sendJson(response, 200, {
|
|
67
106
|
ok: true,
|
|
68
107
|
result: convertSource(String(body.source ?? ''), {
|
|
69
|
-
sourceLanguage:
|
|
108
|
+
sourceLanguage: 'typescript'
|
|
70
109
|
})
|
|
71
110
|
});
|
|
72
111
|
}
|
|
@@ -76,205 +115,6 @@ async function routeRequest(request, response) {
|
|
|
76
115
|
}
|
|
77
116
|
}
|
|
78
117
|
|
|
79
|
-
function convertSource(source, options = {}) {
|
|
80
|
-
const sourceLanguage = normalizeSourceLanguage(options.sourceLanguage);
|
|
81
|
-
const target = sourceLanguage === 'rust' ? 'typescript' : 'rust';
|
|
82
|
-
const imported = importNativeSource({
|
|
83
|
-
language: sourceLanguage,
|
|
84
|
-
sourcePath: `workbench/input.${sourceExtension(sourceLanguage)}`,
|
|
85
|
-
sourceText: source
|
|
86
|
-
});
|
|
87
|
-
const projection = compileNativeSource(imported, {
|
|
88
|
-
target,
|
|
89
|
-
targetPath: `workbench/output.${targetExtension(target)}`,
|
|
90
|
-
targetAdapters: [createTsToRustWorkbenchAdapter(), createRustToTsWorkbenchAdapter()],
|
|
91
|
-
emitOnBlocked: true
|
|
92
|
-
});
|
|
93
|
-
const sidecar = createSemanticImportSidecar(imported, { generatedAt: 0, targetPath: projection.sourceMap?.targetPath });
|
|
94
|
-
return {
|
|
95
|
-
sourceHash: imported.nativeSource.sourceHash,
|
|
96
|
-
sourceLanguage,
|
|
97
|
-
summary: {
|
|
98
|
-
readiness: sidecar.summary.readiness,
|
|
99
|
-
symbols: imported.semanticIndex.symbols.length,
|
|
100
|
-
sourceMapMappings: imported.sourceMaps[0]?.mappings.length ?? 0,
|
|
101
|
-
losses: imported.losses.length,
|
|
102
|
-
patchHints: sidecar.patchHints.length
|
|
103
|
-
},
|
|
104
|
-
frontier: {
|
|
105
|
-
universalAst: universalAstSummary(imported),
|
|
106
|
-
semanticIndexId: imported.semanticIndex.id,
|
|
107
|
-
sidecarId: sidecar.id,
|
|
108
|
-
symbols: imported.semanticIndex.symbols.map(symbolSummary),
|
|
109
|
-
relations: relationSummaries(imported),
|
|
110
|
-
losses: imported.losses.map(lossSummary),
|
|
111
|
-
patchHints: sidecar.patchHints.map((hint) => ({
|
|
112
|
-
id: hint.id,
|
|
113
|
-
readiness: hint.readiness,
|
|
114
|
-
operations: hint.supportedOperations,
|
|
115
|
-
ownershipKey: hint.ownershipKey
|
|
116
|
-
}))
|
|
117
|
-
},
|
|
118
|
-
bounds: conversionBounds(sourceLanguage, target),
|
|
119
|
-
projection: {
|
|
120
|
-
target,
|
|
121
|
-
targetLanguage: target,
|
|
122
|
-
sourceLanguage,
|
|
123
|
-
mode: projection.outputMode,
|
|
124
|
-
readiness: projection.readiness.readiness,
|
|
125
|
-
ok: projection.ok,
|
|
126
|
-
output: projection.output,
|
|
127
|
-
sourceMapMappings: projection.sourceMap?.mappings.length ?? 0
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function createTsToRustWorkbenchAdapter() {
|
|
133
|
-
return {
|
|
134
|
-
id: 'frontier-lang-workbench-ts-to-rust',
|
|
135
|
-
sourceLanguage: 'typescript',
|
|
136
|
-
target: 'rust',
|
|
137
|
-
version: '0.0.0-demo',
|
|
138
|
-
capabilities: ['semantic-symbol-scaffold'],
|
|
139
|
-
coverage: {
|
|
140
|
-
readiness: 'ready',
|
|
141
|
-
handledLossKinds: ['opaqueNative', 'declarationOnlyCoverage', 'partialSemanticIndex', 'sourceMapApproximation', 'sourcePreservation'],
|
|
142
|
-
notes: ['Workbench adapter lowers TypeScript semantic symbols to Rust scaffolding and leaves bodies explicit.']
|
|
143
|
-
},
|
|
144
|
-
project(input) {
|
|
145
|
-
return {
|
|
146
|
-
output: rustFromSymbols(input.importResult.semanticIndex?.symbols ?? []),
|
|
147
|
-
readiness: 'ready',
|
|
148
|
-
evidence: [{
|
|
149
|
-
id: 'evidence_workbench_js_to_rust',
|
|
150
|
-
kind: 'projection',
|
|
151
|
-
status: 'passed',
|
|
152
|
-
summary: 'Workbench adapter projected TypeScript semantic symbols to Rust scaffolding.'
|
|
153
|
-
}]
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function createRustToTsWorkbenchAdapter() {
|
|
160
|
-
return {
|
|
161
|
-
id: 'frontier-lang-workbench-rust-to-ts',
|
|
162
|
-
sourceLanguage: 'rust',
|
|
163
|
-
target: 'typescript',
|
|
164
|
-
version: '0.0.0-demo',
|
|
165
|
-
capabilities: ['semantic-symbol-scaffold'],
|
|
166
|
-
coverage: {
|
|
167
|
-
readiness: 'ready',
|
|
168
|
-
handledLossKinds: ['opaqueNative', 'macroExpansion', 'declarationOnlyCoverage', 'partialSemanticIndex', 'sourceMapApproximation', 'sourcePreservation'],
|
|
169
|
-
notes: ['Workbench adapter lowers Rust semantic symbols to TypeScript scaffolding and leaves bodies explicit.']
|
|
170
|
-
},
|
|
171
|
-
project(input) {
|
|
172
|
-
return {
|
|
173
|
-
output: tsFromSymbols(input.importResult.semanticIndex?.symbols ?? []),
|
|
174
|
-
readiness: 'ready',
|
|
175
|
-
evidence: [{
|
|
176
|
-
id: 'evidence_workbench_rust_to_ts',
|
|
177
|
-
kind: 'projection',
|
|
178
|
-
status: 'passed',
|
|
179
|
-
summary: 'Workbench adapter projected Rust semantic symbols to TypeScript scaffolding.'
|
|
180
|
-
}]
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function rustFromSymbols(symbols) {
|
|
187
|
-
const lines = ['// Generated from Frontier semantic graph evidence.'];
|
|
188
|
-
for (const symbol of symbols) {
|
|
189
|
-
if (symbol.kind === 'class') lines.push('', `pub struct ${safeTypeName(symbol.name)};`);
|
|
190
|
-
if (symbol.kind === 'function' || symbol.kind === 'method') {
|
|
191
|
-
lines.push('', `pub fn ${safeRustName(symbol.name)}() {`, ' // port body from preserved TypeScript source', '}');
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return `${lines.join('\n').trim()}\n`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function tsFromSymbols(symbols) {
|
|
198
|
-
const lines = ['// Generated from Frontier semantic graph evidence.'];
|
|
199
|
-
for (const symbol of symbols) {
|
|
200
|
-
if (symbol.kind === 'type' || symbol.kind === 'class') lines.push('', `export class ${safeTypeName(symbol.name)} {}`);
|
|
201
|
-
if (symbol.kind === 'function' || symbol.kind === 'method') {
|
|
202
|
-
lines.push('', `export function ${safeJsName(symbol.name)}(...args: unknown[]) {`, " throw new Error('port body from preserved Rust source');", '}');
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return `${lines.join('\n').trim()}\n`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function universalAstSummary(imported) {
|
|
209
|
-
const summary = { valid: true, validationError: undefined };
|
|
210
|
-
let parsed = imported.universalAst;
|
|
211
|
-
try {
|
|
212
|
-
parsed = JSON.parse(writeUniversalAstJson(imported.universalAst));
|
|
213
|
-
} catch (error) {
|
|
214
|
-
summary.valid = false;
|
|
215
|
-
summary.validationError = String(error.message || error);
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
...summary,
|
|
219
|
-
kind: parsed.kind,
|
|
220
|
-
id: parsed.id,
|
|
221
|
-
nativeSources: parsed.nativeSources?.length ?? 0,
|
|
222
|
-
semanticSymbols: parsed.semanticIndex?.symbols?.length ?? 0,
|
|
223
|
-
sourceMaps: parsed.sourceMaps?.length ?? 0,
|
|
224
|
-
layers: Object.keys(parsed.layers ?? {})
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function symbolSummary(symbol) {
|
|
229
|
-
return {
|
|
230
|
-
id: symbol.id,
|
|
231
|
-
name: symbol.name,
|
|
232
|
-
kind: symbol.kind,
|
|
233
|
-
language: symbol.language,
|
|
234
|
-
region: symbol.metadata?.ownershipRegionKind,
|
|
235
|
-
ownershipKey: symbol.metadata?.ownershipRegionKey
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function relationSummaries(imported) {
|
|
240
|
-
const names = new Map(imported.semanticIndex.symbols.map((symbol) => [symbol.id, symbol.name]));
|
|
241
|
-
return imported.semanticIndex.relations.map((relation) => ({
|
|
242
|
-
id: relation.id,
|
|
243
|
-
predicate: relation.predicate,
|
|
244
|
-
label: `${names.get(relation.sourceId) ?? relation.sourceId} ${relation.predicate} ${names.get(relation.targetId) ?? relation.targetId}`
|
|
245
|
-
}));
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function lossSummary(loss) {
|
|
249
|
-
return { id: loss.id, kind: loss.kind, severity: loss.severity, message: loss.message };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function normalizeSourceLanguage(language) {
|
|
253
|
-
return String(language || 'typescript').toLowerCase() === 'rust' ? 'rust' : 'typescript';
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function targetExtension(target) {
|
|
257
|
-
return target === 'rust' ? 'rs' : 'ts';
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function sourceExtension(language) {
|
|
261
|
-
return language === 'rust' ? 'rs' : 'ts';
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function safeTypeName(name) {
|
|
265
|
-
const clean = String(name).replace(/[^A-Za-z0-9_]/g, '');
|
|
266
|
-
return clean && /^[A-Z]/.test(clean) ? clean : `Frontier${clean || 'Type'}`;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function safeRustName(name) {
|
|
270
|
-
return String(name).replace(/\./g, '_').replace(/([a-z0-9])([A-Z])/g, '$1_$2').replace(/[^A-Za-z0-9_]/g, '_').toLowerCase();
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function safeJsName(name) {
|
|
274
|
-
const clean = String(name).replace(/[^A-Za-z0-9_$]/g, '_');
|
|
275
|
-
return /^[A-Za-z_$]/.test(clean) ? clean : `frontier_${clean}`;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
118
|
function sendJson(response, status, value) {
|
|
279
119
|
return send(response, status, JSON.stringify(value), 'application/json; charset=utf-8');
|
|
280
120
|
}
|
package/package.json
CHANGED