@shapeshift-labs/frontier-lang-compiler 0.2.96 → 0.2.98

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.
@@ -10,6 +10,7 @@ export type SemanticEditScriptOperationStatus =
10
10
  | 'candidate'
11
11
  | 'portable'
12
12
  | 'already-applied'
13
+ | 'covered'
13
14
  | 'needs-port'
14
15
  | 'conflict'
15
16
  | 'stale'
@@ -82,6 +83,7 @@ export interface SemanticEditScriptSummary {
82
83
  readonly conflicts: number;
83
84
  readonly stale: number;
84
85
  readonly blocked: number;
86
+ readonly covered?: number;
85
87
  readonly candidates: number;
86
88
  readonly autoMergeCandidates: number;
87
89
  readonly semanticKeys?: readonly string[];
@@ -1,6 +1,7 @@
1
1
  import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
2
  import { idFragment, uniqueStrings } from '../../native-import-utils.js';
3
3
  import { semanticEditIdentityFields } from './semanticEditIdentityRecords.js';
4
+ import { applySourceEdits, dedupeSourceEdits, validateSourceEdits } from './semanticSourceEditDedupe.js';
4
5
 
5
6
  export function projectSemanticEditScriptToSource(input = {}) {
6
7
  const script = input.script;
@@ -12,7 +13,12 @@ export function projectSemanticEditScriptToSource(input = {}) {
12
13
  if (typeof workerSourceText !== 'string') reasonCodes.push('missing-worker-source-text');
13
14
  if (typeof headSourceText !== 'string') reasonCodes.push('missing-head-source-text');
14
15
  const edits = [];
16
+ const coveredOperationIds = [];
15
17
  for (const [index, operation] of (script.operations ?? []).entries()) {
18
+ if (operation.status === 'covered') {
19
+ coveredOperationIds.push(operation.id);
20
+ continue;
21
+ }
16
22
  const edit = sourceEditForOperation(operation, workerSourceText, headSourceText, index);
17
23
  if (edit.ok) edits.push(edit.value);
18
24
  else reasonCodes.push(...edit.reasonCodes);
@@ -34,7 +40,7 @@ export function projectSemanticEditScriptToSource(input = {}) {
34
40
  headHash: script.headHash,
35
41
  projectedHash: sourceText === undefined ? undefined : hashSemanticValue(sourceText),
36
42
  appliedOperations: blocked ? [] : deduped.edits.map((edit) => edit.operationId),
37
- skippedOperations: blocked ? (script.operations ?? []).map((operation) => operation.id) : deduped.skippedOperationIds,
43
+ skippedOperations: blocked ? (script.operations ?? []).map((operation) => operation.id) : uniqueStrings([...coveredOperationIds, ...deduped.skippedOperationIds]),
38
44
  edits: blocked ? [] : deduped.edits.map(projectionEditRecord),
39
45
  sourceText,
40
46
  admission: {
@@ -65,6 +71,9 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
65
71
  if (operation.changeKind === 'added' || String(operation.kind ?? '').startsWith('add')) {
66
72
  return insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order);
67
73
  }
74
+ if (operation.changeKind === 'removed' || String(operation.kind ?? '').startsWith('remove')) {
75
+ return removalEditForOperation(operation, identity, headSourceText, order);
76
+ }
68
77
  const workerOffsets = spanOffsets(workerSourceText, operation.spans?.worker);
69
78
  const headOffsets = spanOffsets(headSourceText, operation.spans?.head ?? operation.spans?.base ?? operation.anchor?.sourceSpan);
70
79
  const reasons = [];
@@ -98,6 +107,33 @@ function sourceEditForOperation(operation, workerSourceText, headSourceText, ord
98
107
  };
99
108
  }
100
109
 
110
+ function removalEditForOperation(operation, identity, headSourceText, order) {
111
+ const headOffsets = spanOffsets(headSourceText, operation.spans?.head ?? operation.spans?.base ?? operation.anchor?.sourceSpan);
112
+ const reasons = [];
113
+ if (!headOffsets) reasons.push(`head-span-not-resolvable:${operation.id}`);
114
+ if (reasons.length) return { ok: false, reasonCodes: reasons };
115
+ const rawCurrent = headSourceText.slice(headOffsets.start, headOffsets.end);
116
+ const expectedHeadHash = operation.hashes?.headTextHash ?? operation.hashes?.baseTextHash;
117
+ if (expectedHeadHash && hashSemanticValue(rawCurrent) !== expectedHeadHash) {
118
+ reasons.push(`head-span-hash-mismatch:${operation.id}`);
119
+ }
120
+ if (reasons.length) return { ok: false, reasonCodes: reasons };
121
+ const range = removalRange(headSourceText, headOffsets);
122
+ return {
123
+ ok: true,
124
+ value: {
125
+ operationId: operation.id,
126
+ order,
127
+ ...identity,
128
+ editKind: 'delete',
129
+ start: range.start,
130
+ end: range.end,
131
+ current: headSourceText.slice(range.start, range.end),
132
+ replacement: ''
133
+ }
134
+ };
135
+ }
136
+
101
137
  function insertionEditForOperation(operation, identity, workerSourceText, headSourceText, order) {
102
138
  const workerOffsets = spanOffsets(workerSourceText, operation.spans?.worker);
103
139
  const reasons = [];
@@ -207,44 +243,6 @@ function semanticEditIdentity(operation) {
207
243
  });
208
244
  }
209
245
 
210
- function applySourceEdits(sourceText, edits) {
211
- return edits.filter((edit) => !edit.alreadyApplied)
212
- .sort(sourceEditSort)
213
- .reduce((text, edit) => text.slice(0, edit.start) + edit.replacement + text.slice(edit.end), sourceText);
214
- }
215
-
216
- function dedupeSourceEdits(edits) {
217
- const seen = new Map();
218
- const result = [];
219
- const skippedOperationIds = [];
220
- for (const edit of edits) {
221
- const key = duplicateEditKey(edit);
222
- if (key && seen.has(key)) {
223
- skippedOperationIds.push(edit.operationId);
224
- continue;
225
- }
226
- if (key) seen.set(key, edit.operationId);
227
- result.push(edit);
228
- }
229
- return { edits: result, skippedOperationIds };
230
- }
231
-
232
- function duplicateEditKey(edit) {
233
- if (edit.editKind !== 'insert') return undefined;
234
- return [
235
- 'insert',
236
- edit.start,
237
- edit.end,
238
- edit.insertion?.mode,
239
- edit.insertion?.anchorKey,
240
- hashSemanticValue(edit.replacementSpanText ?? edit.replacement)
241
- ].join(':');
242
- }
243
-
244
- function sourceEditSort(left, right) {
245
- return right.start - left.start || right.end - left.end || (right.order ?? 0) - (left.order ?? 0);
246
- }
247
-
248
246
  function projectedSourcePath(script, edits) {
249
247
  return edits.map((edit) => edit.sourcePath).find(Boolean) ?? script.sourcePath;
250
248
  }
@@ -278,6 +276,13 @@ function insertionOffset(sourceText, insertion) {
278
276
  return { ok: false, reasonCodes: ['insertion-mode-unsupported'] };
279
277
  }
280
278
 
279
+ function removalRange(sourceText, span) {
280
+ const range = { ...span };
281
+ if (range.end < sourceText.length && sourceText[range.end] === '\n') range.end += 1;
282
+ else if (range.start > 0 && sourceText[range.start - 1] === '\n') range.start -= 1;
283
+ return range;
284
+ }
285
+
281
286
  function insertionReplacement(text, sourceText, offset) {
282
287
  let replacement = String(text ?? '');
283
288
  if (offset > 0 && sourceText[offset - 1] !== '\n') replacement = `\n${replacement}`;
@@ -291,23 +296,6 @@ function afterLineOffset(sourceText, offset) {
291
296
  return sourceText[offset] === '\n' ? offset + 1 : offset;
292
297
  }
293
298
 
294
- function validateSourceEdits(edits) {
295
- const reasons = [];
296
- const ordered = edits.filter((edit) => !edit.alreadyApplied).sort((left, right) => left.start - right.start || left.end - right.end);
297
- for (let index = 1; index < ordered.length; index += 1) {
298
- const previous = ordered[index - 1];
299
- const current = ordered[index];
300
- if (editsOverlap(previous, current)) reasons.push(`source-edit-overlap:${previous.operationId}:${current.operationId}`);
301
- }
302
- return uniqueStrings(reasons);
303
- }
304
-
305
- function editsOverlap(left, right) {
306
- if (left.start === left.end) return right.start < left.start && left.start < right.end;
307
- if (right.start === right.end) return left.start < right.start && right.start < left.end;
308
- return left.start < right.end && right.start < left.end;
309
- }
310
-
311
299
  function compactRecord(value) {
312
300
  return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
313
301
  }
@@ -0,0 +1,141 @@
1
+ import { uniqueStrings } from '../../native-import-utils.js';
2
+ import { nativeImportSourceText } from './nativeImportSourceText.js';
3
+
4
+ export function markCoveredSemanticEditOperations(operations, context) {
5
+ const sourceText = {
6
+ base: nativeImportSourceText(context.base),
7
+ worker: nativeImportSourceText(context.worker)
8
+ };
9
+ return (operations ?? []).map((operation) => {
10
+ const coveredBy = coveredByChildOperations(operation, operations, sourceText);
11
+ if (!coveredBy.length) return operation;
12
+ return {
13
+ ...operation,
14
+ status: 'covered',
15
+ readiness: 'ready',
16
+ confidence: Math.max(operation.confidence ?? 0, 0.82),
17
+ reasonCodes: uniqueStrings([...(operation.reasonCodes ?? []), 'container-covered-by-child-edits']),
18
+ evidenceIds: uniqueStrings(operation.evidenceIds ?? []),
19
+ metadata: {
20
+ ...(operation.metadata ?? {}),
21
+ coveredByOperationIds: coveredBy.map((child) => child.id)
22
+ }
23
+ };
24
+ });
25
+ }
26
+
27
+ function coveredByChildOperations(container, operations, sourceText) {
28
+ if (!isCoverableContainer(container)) return [];
29
+ const containerBase = spanOffsets(sourceText.base, container.spans?.base);
30
+ const containerWorker = spanOffsets(sourceText.worker, container.spans?.worker);
31
+ if (!containerBase || !containerWorker) return [];
32
+ const childEdits = (operations ?? [])
33
+ .filter((operation) => operation.id !== container.id)
34
+ .map((operation) => childEdit(operation, sourceText, containerBase))
35
+ .filter(Boolean);
36
+ if (!childEdits.length) return [];
37
+ const baseText = sourceText.base.slice(containerBase.start, containerBase.end);
38
+ const workerText = sourceText.worker.slice(containerWorker.start, containerWorker.end);
39
+ return applyLocalEdits(baseText, childEdits) === workerText ? childEdits.map((edit) => edit.operation) : [];
40
+ }
41
+
42
+ function isCoverableContainer(operation) {
43
+ if (operation.changeKind !== 'modified') return false;
44
+ if (!operation.spans?.base || !operation.spans?.worker) return false;
45
+ const kind = String(operation.anchor?.regionKind ?? operation.regionKind ?? '');
46
+ return kind === 'type' || kind === 'config' || kind === 'content' || kind === 'route' || kind === 'property';
47
+ }
48
+
49
+ function childEdit(operation, sourceText, containerBase) {
50
+ if (!['portable', 'already-applied'].includes(operation.status)) return undefined;
51
+ if (operation.changeKind === 'modified') return replacementChildEdit(operation, sourceText, containerBase);
52
+ if (operation.changeKind === 'added') return insertionChildEdit(operation, sourceText, containerBase);
53
+ if (operation.changeKind === 'removed') return removalChildEdit(operation, sourceText, containerBase);
54
+ return undefined;
55
+ }
56
+
57
+ function replacementChildEdit(operation, sourceText, containerBase) {
58
+ const base = spanOffsets(sourceText.base, operation.spans?.base);
59
+ const worker = spanOffsets(sourceText.worker, operation.spans?.worker);
60
+ if (!contained(base, containerBase) || !worker) return undefined;
61
+ return {
62
+ operation,
63
+ start: base.start - containerBase.start,
64
+ end: base.end - containerBase.start,
65
+ replacement: sourceText.worker.slice(worker.start, worker.end)
66
+ };
67
+ }
68
+
69
+ function insertionChildEdit(operation, sourceText, containerBase) {
70
+ const worker = spanOffsets(sourceText.worker, operation.spans?.worker);
71
+ const offset = insertionOffset(sourceText.base, operation.insertion, containerBase);
72
+ if (!worker || offset === undefined) return undefined;
73
+ const baseText = sourceText.base.slice(containerBase.start, containerBase.end);
74
+ const replacement = insertionReplacement(sourceText.worker.slice(worker.start, worker.end), baseText, offset);
75
+ return { operation, start: offset, end: offset, replacement };
76
+ }
77
+
78
+ function removalChildEdit(operation, sourceText, containerBase) {
79
+ const base = spanOffsets(sourceText.base, operation.spans?.base);
80
+ if (!contained(base, containerBase)) return undefined;
81
+ const range = removalRange(sourceText.base, base, containerBase);
82
+ return { operation, start: range.start - containerBase.start, end: range.end - containerBase.start, replacement: '' };
83
+ }
84
+
85
+ function insertionOffset(sourceText, insertion, containerBase) {
86
+ const mode = insertion?.mode;
87
+ if (mode === 'file-start') return 0;
88
+ if (mode === 'file-end') return containerBase.end - containerBase.start;
89
+ const span = spanOffsets(sourceText, insertion?.baseSpan ?? insertion?.headSpan);
90
+ if (!contained(span, containerBase)) return undefined;
91
+ if (mode === 'before') return span.start - containerBase.start;
92
+ if (mode === 'after') return afterLineOffset(sourceText, span.end) - containerBase.start;
93
+ return undefined;
94
+ }
95
+
96
+ function applyLocalEdits(sourceText, edits) {
97
+ return edits
98
+ .sort((left, right) => right.start - left.start || right.end - left.end)
99
+ .reduce((text, edit) => text.slice(0, edit.start) + edit.replacement + text.slice(edit.end), sourceText);
100
+ }
101
+
102
+ function spanOffsets(sourceText, span) {
103
+ if (typeof sourceText !== 'string' || !span) return undefined;
104
+ if (typeof span.start === 'number' && typeof span.end === 'number' && span.end >= span.start) return { start: span.start, end: span.end };
105
+ if (typeof span.startLine !== 'number') return undefined;
106
+ const lineStarts = [0];
107
+ for (let index = 0; index < sourceText.length; index += 1) if (sourceText[index] === '\n') lineStarts.push(index + 1);
108
+ const startLine = Math.max(1, span.startLine);
109
+ const endLine = Math.max(startLine, typeof span.endLine === 'number' ? span.endLine : startLine);
110
+ const start = lineStarts[startLine - 1];
111
+ const endLineStart = lineStarts[endLine - 1];
112
+ if (start === undefined || endLineStart === undefined) return undefined;
113
+ const startColumn = Math.max(1, span.startColumn ?? 1) - 1;
114
+ const lineEnd = lineStarts[endLine] === undefined ? sourceText.length : lineStarts[endLine] - 1;
115
+ const endColumn = span.endColumn === undefined ? lineEnd - endLineStart : Math.max(1, span.endColumn) - 1;
116
+ return { start: start + startColumn, end: endLineStart + endColumn };
117
+ }
118
+
119
+ function contained(inner, outer) {
120
+ return Boolean(inner && outer && outer.start <= inner.start && inner.end <= outer.end);
121
+ }
122
+
123
+ function insertionReplacement(text, sourceText, offset) {
124
+ let replacement = String(text ?? '');
125
+ if (offset > 0 && sourceText[offset - 1] !== '\n') replacement = `\n${replacement}`;
126
+ if (offset < sourceText.length && !replacement.endsWith('\n')) replacement += '\n';
127
+ if (offset === sourceText.length && sourceText && !sourceText.endsWith('\n')) replacement = `\n${replacement}`;
128
+ if (offset === sourceText.length && !replacement.endsWith('\n')) replacement += '\n';
129
+ return replacement;
130
+ }
131
+
132
+ function removalRange(sourceText, span, container) {
133
+ const range = { ...span };
134
+ if (range.end < container.end && sourceText[range.end] === '\n') range.end += 1;
135
+ else if (range.start > container.start && sourceText[range.start - 1] === '\n') range.start -= 1;
136
+ return range;
137
+ }
138
+
139
+ function afterLineOffset(sourceText, offset) {
140
+ return sourceText[offset] === '\n' ? offset + 1 : offset;
141
+ }
@@ -43,6 +43,7 @@ export function summarizeSemanticEditOperations(operations) {
43
43
  conflicts: byStatus.conflict ?? 0,
44
44
  stale: byStatus.stale ?? 0,
45
45
  blocked: byStatus.blocked ?? 0,
46
+ covered: byStatus.covered ?? 0,
46
47
  candidates: byStatus.candidate ?? 0,
47
48
  autoMergeCandidates: (byStatus.portable ?? 0) + (byStatus['already-applied'] ?? 0),
48
49
  semanticKeys: uniqueStrings(operations.map((operation) => operation.semanticKey).filter(Boolean)),
@@ -13,6 +13,7 @@ import {
13
13
  summarizeSemanticEditOperations
14
14
  } from './semanticEditScriptClassification.js';
15
15
  import { semanticEditInsertionAnchor } from './semanticEditInsertionAnchors.js';
16
+ import { markCoveredSemanticEditOperations } from './semanticEditOperationCoverage.js';
16
17
  import { sourceTextForSpan } from './sourceTextForSpan.js';
17
18
  import { semanticEditIdentityFields, semanticEditOperationContentHash } from './semanticEditIdentityRecords.js';
18
19
 
@@ -52,7 +53,10 @@ export function createSemanticEditScript(input = {}, options = {}) {
52
53
  metadata: { source: 'createSemanticEditScript' }
53
54
  }) : undefined;
54
55
  const context = createEditContext({ base, worker, head, workerChangeSet, headChangeSet, headLineage });
55
- const operations = workerChangeSet.changedRegions.map((region, index) => semanticEditOperation(region, index, context, input));
56
+ const operations = markCoveredSemanticEditOperations(
57
+ workerChangeSet.changedRegions.map((region, index) => semanticEditOperation(region, index, context, input)),
58
+ context
59
+ );
56
60
  const summary = summarizeSemanticEditOperations(operations);
57
61
  const admission = semanticEditAdmission({ operations, summary, head, workerChangeSet, headChangeSet, input });
58
62
  const evidence = semanticEditEvidence({ input, language, sourcePath, workerChangeSet, headChangeSet, headLineage, summary, admission });
@@ -169,7 +173,7 @@ function semanticEditOperation(region, index, context, input) {
169
173
  const identityRecord = semanticEditIdentityRecord({ kind, region, anchor });
170
174
  const identity = semanticEditIdentityFields(identityRecord);
171
175
  return compactRecord({
172
- id: `semantic_edit_op_${idFragment([input.id ?? 'semantic_edit', anchorKey, index].join(':'))}`,
176
+ id: `semantic_edit_op_${idFragment([index, anchorKey, input.id ?? 'semantic_edit'].join(':'))}`,
173
177
  kind,
174
178
  changeKind: region.changeKind,
175
179
  anchor,
@@ -0,0 +1,99 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { uniqueStrings } from '../../native-import-utils.js';
3
+
4
+ export function applySourceEdits(sourceText, edits) {
5
+ return edits.filter((edit) => !edit.alreadyApplied)
6
+ .sort(sourceEditSort)
7
+ .reduce((text, edit) => text.slice(0, edit.start) + edit.replacement + text.slice(edit.end), sourceText);
8
+ }
9
+
10
+ export function dedupeSourceEdits(edits) {
11
+ const exact = dedupeExactInsertions(edits);
12
+ const covered = removeCoveredContainerReplacements(exact.edits);
13
+ return {
14
+ edits: covered.edits,
15
+ skippedOperationIds: [...exact.skippedOperationIds, ...covered.skippedOperationIds]
16
+ };
17
+ }
18
+
19
+ export function validateSourceEdits(edits) {
20
+ const reasons = [];
21
+ const ordered = edits.filter((edit) => !edit.alreadyApplied).sort((left, right) => left.start - right.start || left.end - right.end);
22
+ for (let index = 1; index < ordered.length; index += 1) {
23
+ const previous = ordered[index - 1];
24
+ const current = ordered[index];
25
+ if (editsOverlap(previous, current)) reasons.push(`source-edit-overlap:${previous.operationId}:${current.operationId}`);
26
+ }
27
+ return uniqueStrings(reasons);
28
+ }
29
+
30
+ function dedupeExactInsertions(edits) {
31
+ const seen = new Map();
32
+ const result = [];
33
+ const skippedOperationIds = [];
34
+ for (const edit of edits) {
35
+ const key = duplicateEditKey(edit);
36
+ if (key && seen.has(key)) {
37
+ skippedOperationIds.push(edit.operationId);
38
+ continue;
39
+ }
40
+ if (key) seen.set(key, edit.operationId);
41
+ result.push(edit);
42
+ }
43
+ return { edits: result, skippedOperationIds };
44
+ }
45
+
46
+ function removeCoveredContainerReplacements(edits) {
47
+ const skippedOperationIds = [];
48
+ const result = [];
49
+ for (const edit of edits) {
50
+ if (containerReplacementCoveredByInsertions(edit, edits)) {
51
+ skippedOperationIds.push(edit.operationId);
52
+ continue;
53
+ }
54
+ result.push(edit);
55
+ }
56
+ return { edits: result, skippedOperationIds };
57
+ }
58
+
59
+ function containerReplacementCoveredByInsertions(edit, edits) {
60
+ if (edit.editKind !== 'replace' || edit.alreadyApplied) return false;
61
+ const insertions = containedInsertions(edit, edits);
62
+ if (!insertions.length) return false;
63
+ const localEdits = insertions.map((insertion) => ({
64
+ ...insertion,
65
+ start: insertion.start - edit.start,
66
+ end: insertion.end - edit.start
67
+ }));
68
+ return applySourceEdits(edit.current, localEdits) === edit.replacement;
69
+ }
70
+
71
+ function containedInsertions(container, edits) {
72
+ return edits
73
+ .filter((edit) => edit.editKind === 'insert' && !edit.alreadyApplied)
74
+ .filter((edit) => edit.operationId !== container.operationId)
75
+ .filter((edit) => container.start <= edit.start && edit.end <= container.end)
76
+ .sort((left, right) => left.start - right.start || (left.order ?? 0) - (right.order ?? 0));
77
+ }
78
+
79
+ function duplicateEditKey(edit) {
80
+ if (edit.editKind !== 'insert') return undefined;
81
+ return [
82
+ 'insert',
83
+ edit.start,
84
+ edit.end,
85
+ edit.insertion?.mode,
86
+ edit.insertion?.anchorKey,
87
+ hashSemanticValue(edit.replacementSpanText ?? edit.replacement)
88
+ ].join(':');
89
+ }
90
+
91
+ function sourceEditSort(left, right) {
92
+ return right.start - left.start || right.end - left.end || (right.order ?? 0) - (left.order ?? 0);
93
+ }
94
+
95
+ function editsOverlap(left, right) {
96
+ if (left.start === left.end) return right.start < left.start && left.start < right.end;
97
+ if (right.start === right.end) return left.start < right.start && right.start < left.end;
98
+ return left.start < right.end && right.start < left.end;
99
+ }
@@ -160,11 +160,16 @@ function jsDeclarationWithSourceSpan(input, declaration, lines) {
160
160
  startLine,
161
161
  endLine,
162
162
  startColumn: declaration.span?.startColumn ?? 1,
163
- endColumn: declaration.span?.endColumn ?? endLineText.length + 1
163
+ endColumn: declarationEndColumn(declaration, startLine, endLine, endLineText)
164
164
  }
165
165
  };
166
166
  }
167
167
 
168
+ function declarationEndColumn(declaration, startLine, endLine, endLineText) {
169
+ if (endLine !== startLine) return endLineText.length + 1;
170
+ return declaration.span?.endColumn ?? endLineText.length + 1;
171
+ }
172
+
168
173
  function jsBalancedDeclarationEndLine(input, lines, startLine) {
169
174
  const state = { inBlockComment: false, inTemplateString: false };
170
175
  let depth = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.96",
3
+ "version": "0.2.98",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",