@shapeshift-labs/frontier-lang-compiler 0.2.131 → 0.2.133

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 CHANGED
@@ -188,6 +188,14 @@ artifacts keep `autoMergeClaim: false` and `semanticEquivalenceClaim: false`,
188
188
  but give coordinators machine-readable proof that the projected source matches
189
189
  the merge output and that applying the same projection again is a no-op.
190
190
 
191
+ When the top-level JS/TS ledger blocks only because an existing declaration
192
+ body or semantic fact changed, `safeMergeJsTsSource` can fall back to the
193
+ generic semantic edit script path. The fallback admits the merge only after the
194
+ script is an auto-merge candidate, the source projection succeeds, replay on
195
+ current head is `accepted-clean`, and replay on the projected source is
196
+ `already-applied`. Same-anchor head edits, stale anchors, and non-body conflicts
197
+ remain blocked for review.
198
+
191
199
  Project-level JS/TS safe merges compose the same file-level gates across a
192
200
  base/worker/head file set. They preserve head-only files, admit worker-only
193
201
  file additions when file additions are enabled, block conflicting same-path
@@ -109,6 +109,9 @@ export interface JsTsSafeMergeSummary {
109
109
  readonly gatesPassed: number;
110
110
  readonly memberRegions?: number;
111
111
  readonly memberAdditions?: number;
112
+ readonly semanticEditOperations?: number;
113
+ readonly semanticEditAppliedOperations?: number;
114
+ readonly semanticEditReplayStatus?: string;
112
115
  readonly composedPhases?: number;
113
116
  }
114
117
 
@@ -148,6 +148,7 @@ export function validateIndependentAdditions(base, workerPlan, headPlan, context
148
148
  validateCrossSideAddedNames(workerPlan, headPlan, context);
149
149
  validateCrossSideExportStarAdditions(workerPlan, headPlan, context);
150
150
  validateCrossSideImportAdditions(workerPlan, headPlan, context);
151
+ validateMergedImportShapes(base, workerPlan, headPlan, context);
151
152
  validateMergedImportAndDeclarationNames(base, workerPlan, headPlan, context);
152
153
  }
153
154
 
@@ -234,6 +235,32 @@ function validateCrossSideImportAdditions(workerPlan, headPlan, context) {
234
235
  }
235
236
  }
236
237
 
238
+ function validateMergedImportShapes(base, workerPlan, headPlan, context) {
239
+ for (const entry of base.entries.filter((item) => item.kind === 'import')) {
240
+ const additions = [
241
+ ...(workerPlan.importAdditions.get(entry.key) ?? []),
242
+ ...(headPlan.importAdditions.get(entry.key) ?? [])
243
+ ];
244
+ const namespaceAddition = additions.find((specifier) => specifier.additionKind === 'namespace');
245
+ if (!namespaceAddition) continue;
246
+ const namedAdditions = additions.filter((specifier) => !specifier.additionKind);
247
+ if (!entry.importInfo.specifiers.length && !namedAdditions.length) continue;
248
+ addConflict(context, {
249
+ code: JsTsSafeMergeConflictCodes.importShapeChanged,
250
+ gateId: JsTsSafeMergeGateIds.independentImportSpecifiers,
251
+ message: 'Merged imports would combine namespace and named import specifiers.',
252
+ details: {
253
+ key: entry.key,
254
+ namespace: namespaceAddition.localName,
255
+ namedSpecifiers: [
256
+ ...entry.importInfo.specifiers.map((specifier) => specifier.canonical),
257
+ ...namedAdditions.map((specifier) => specifier.canonical)
258
+ ]
259
+ }
260
+ });
261
+ }
262
+ }
263
+
237
264
  function validateMergedImportAndDeclarationNames(base, workerPlan, headPlan, context) {
238
265
  const topLevelBindingNames = new Set();
239
266
  for (const entry of base.entries.filter((item) => item.kind !== 'import')) {
@@ -5,6 +5,7 @@ import {
5
5
  jsTsSafeMergeGateOrder
6
6
  } from './js-ts-safe-merge-constants.js';
7
7
  import { safeMergeJsTsImportsAndDeclarations } from './js-ts-safe-merge.js';
8
+ import { semanticEditFallbackResult } from './js-ts-safe-merge-semantic-edit-fallback.js';
8
9
  import { createJsTsSafeMergeSemanticArtifacts } from './js-ts-safe-merge-semantic-artifacts.js';
9
10
  import {
10
11
  applyJsTsPreparedMemberAdditions,
@@ -12,7 +13,12 @@ import {
12
13
  } from './js-ts-safe-member-merge.js';
13
14
 
14
15
  function safeMergeJsTsSource(input = {}) {
15
- if (!hasMemberMergePolicy(input)) return safeMergeJsTsImportsAndDeclarations(input);
16
+ if (!hasMemberMergePolicy(input)) {
17
+ const topLevelResult = safeMergeJsTsImportsAndDeclarations(input);
18
+ return topLevelResult.status === JsTsSafeMergeStatuses.merged
19
+ ? topLevelResult
20
+ : semanticEditFallbackResult(input, topLevelResult);
21
+ }
16
22
 
17
23
  const memberNeutralization = neutralizeJsTsSafeMemberMergeSources(input);
18
24
  if (!memberNeutralization.ok) {
@@ -0,0 +1,263 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { createSemanticEditScript } from './internal/index-impl/semanticEditScripts.js';
3
+ import { projectSemanticEditScriptToSource } from './internal/index-impl/projectSemanticEditScriptToSource.js';
4
+ import { replaySemanticEditProjection } from './internal/index-impl/replaySemanticEditProjection.js';
5
+ import { JsTsSafeMergeConflictCodes, JsTsSafeMergeStatuses } from './js-ts-safe-merge-constants.js';
6
+ import { idFragment, uniqueStrings } from './native-import-utils.js';
7
+
8
+ function semanticEditFallbackResult(input, topLevelResult) {
9
+ if (!shouldTrySemanticEditFallback(topLevelResult)) return topLevelResult;
10
+ const artifacts = createSemanticEditFallbackArtifacts(input, topLevelResult);
11
+ if (artifacts.status !== 'verified') return semanticEditFallbackBlockedResult(input, topLevelResult, artifacts);
12
+ const mergedSourceText = artifacts.projection.sourceText;
13
+ const gates = semanticEditGates(artifacts);
14
+ return {
15
+ ...topLevelResult,
16
+ status: JsTsSafeMergeStatuses.merged,
17
+ mergedSourceText,
18
+ outputSourceText: mergedSourceText,
19
+ conflicts: [],
20
+ gates,
21
+ admission: {
22
+ status: 'auto-merge-candidate',
23
+ action: 'apply',
24
+ reviewRequired: false,
25
+ autoApplyCandidate: true,
26
+ autoMergeClaim: false,
27
+ semanticEquivalenceClaim: false,
28
+ reasonCodes: []
29
+ },
30
+ summary: {
31
+ ...topLevelResult.summary,
32
+ conflicts: 0,
33
+ gatesPassed: gates.filter((gate) => gate.status === 'passed').length,
34
+ semanticEditOperations: artifacts.script.summary.operations,
35
+ semanticEditAppliedOperations: artifacts.replay.summary.applied,
36
+ semanticEditReplayStatus: artifacts.replay.status,
37
+ composedPhases: 2
38
+ },
39
+ metadata: {
40
+ ...topLevelResult.metadata,
41
+ composed: {
42
+ phase: 'semantic-edit-fallback',
43
+ phases: ['top-level-ledger', 'semantic-edit'],
44
+ originalReasonCodes: topLevelResult.admission?.reasonCodes ?? []
45
+ }
46
+ },
47
+ semanticArtifacts: artifacts
48
+ };
49
+ }
50
+
51
+ function shouldTrySemanticEditFallback(result) {
52
+ const conflicts = result.conflicts ?? [];
53
+ return conflicts.length > 0 && conflicts.every((conflict) => conflict.code === JsTsSafeMergeConflictCodes.changedExistingDeclaration);
54
+ }
55
+
56
+ function createSemanticEditFallbackArtifacts(input, topLevelResult) {
57
+ try {
58
+ const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
59
+ const language = input.language ?? topLevelResult.language ?? 'typescript';
60
+ const sourcePath = input.sourcePath ?? topLevelResult.sourcePath ?? 'inline.ts';
61
+ const script = createSemanticEditScript({
62
+ ...input,
63
+ id: `${id}_semantic_edit`,
64
+ language,
65
+ sourcePath
66
+ });
67
+ const projection = projectSemanticEditScriptToSource({
68
+ id: `${id}_semantic_edit_projection`,
69
+ script,
70
+ workerSourceText: input.workerSourceText,
71
+ headSourceText: input.headSourceText,
72
+ headSourcePath: sourcePath,
73
+ parser: input.parser
74
+ });
75
+ const replay = replaySemanticEditProjection({
76
+ id: `${id}_semantic_edit_replay`,
77
+ projection,
78
+ currentSourceText: input.headSourceText,
79
+ currentSourcePath: sourcePath,
80
+ language,
81
+ parser: input.parser
82
+ });
83
+ const alreadyAppliedReplay = replaySemanticEditProjection({
84
+ id: `${id}_semantic_edit_already_applied`,
85
+ projection,
86
+ currentSourceText: projection.sourceText,
87
+ currentSourcePath: sourcePath,
88
+ currentSourceHash: projection.projectedHash,
89
+ language,
90
+ parser: input.parser
91
+ });
92
+ return semanticEditArtifacts({
93
+ id,
94
+ language,
95
+ sourcePath,
96
+ script,
97
+ projection,
98
+ replay,
99
+ alreadyAppliedReplay,
100
+ topLevelResult
101
+ });
102
+ } catch (error) {
103
+ return blockedSemanticEditArtifacts(input, topLevelResult, ['semantic-edit-fallback-error'], error);
104
+ }
105
+ }
106
+
107
+ function semanticEditArtifacts(input) {
108
+ const reasonCodes = semanticEditArtifactReasonCodes(input);
109
+ const status = reasonCodes.length ? 'blocked' : 'verified';
110
+ const core = {
111
+ kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
112
+ version: 1,
113
+ schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
114
+ id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(input.id)}`,
115
+ sourcePath: input.sourcePath,
116
+ language: input.language,
117
+ status,
118
+ script: input.script,
119
+ projection: input.projection,
120
+ replay: input.replay,
121
+ alreadyAppliedReplay: input.alreadyAppliedReplay,
122
+ admission: {
123
+ status: status === 'verified' ? 'auto-merge-candidate' : 'blocked',
124
+ action: status === 'verified' ? 'apply' : 'human-review',
125
+ reviewRequired: status !== 'verified',
126
+ autoApplyCandidate: status === 'verified',
127
+ autoMergeClaim: false,
128
+ semanticEquivalenceClaim: false,
129
+ reasonCodes
130
+ },
131
+ summary: {
132
+ operations: input.script.summary.operations,
133
+ edits: input.projection.edits.length,
134
+ replayStatus: input.replay.status,
135
+ alreadyAppliedReplayStatus: input.alreadyAppliedReplay.status,
136
+ projectedSourceMatchesMerged: input.projection.sourceText === input.replay.outputSourceText,
137
+ replayOutputMatchesMerged: input.replay.outputSourceText === input.projection.sourceText
138
+ },
139
+ evidence: [{
140
+ id: `evidence_${idFragment(input.id)}_js_ts_semantic_edit_replay`,
141
+ kind: 'js-ts-semantic-edit-replay',
142
+ status: status === 'verified' ? 'passed' : 'needs-review',
143
+ path: input.sourcePath,
144
+ summary: status === 'verified'
145
+ ? `JS/TS semantic edit replay verified ${input.script.summary.operations} operation(s).`
146
+ : `JS/TS semantic edit replay requires review: ${reasonCodes.join(', ')}.`
147
+ }],
148
+ metadata: {
149
+ autoMergeClaim: false,
150
+ semanticEquivalenceClaim: false,
151
+ source: 'js-ts-semantic-edit-fallback',
152
+ originalReasonCodes: input.topLevelResult.admission?.reasonCodes ?? []
153
+ }
154
+ };
155
+ return { ...core, hash: hashSemanticValue(core) };
156
+ }
157
+
158
+ function semanticEditArtifactReasonCodes(input) {
159
+ const scriptReady = input.script.admission.status === 'auto-merge-candidate';
160
+ const projectionReady = input.projection.status === 'projected';
161
+ const replayReady = input.replay.status === 'accepted-clean';
162
+ const alreadyAppliedReady = input.alreadyAppliedReplay.status === 'already-applied';
163
+ return uniqueStrings([
164
+ scriptReady ? undefined : `semantic-edit-script-${input.script.admission.status}`,
165
+ projectionReady ? undefined : `semantic-edit-projection-${input.projection.status}`,
166
+ replayReady ? undefined : `semantic-edit-replay-${input.replay.status}`,
167
+ input.replay.outputSourceText !== input.projection.sourceText ? 'semantic-edit-replay-output-mismatch' : undefined,
168
+ alreadyAppliedReady ? undefined : `semantic-edit-already-applied-${input.alreadyAppliedReplay.status}`,
169
+ ...(scriptReady ? [] : input.script.admission.reasonCodes),
170
+ ...(projectionReady ? [] : input.projection.admission.reasonCodes),
171
+ ...(replayReady ? [] : input.replay.admission.reasonCodes),
172
+ ...(alreadyAppliedReady ? [] : input.alreadyAppliedReplay.admission.reasonCodes)
173
+ ]);
174
+ }
175
+
176
+ function blockedSemanticEditArtifacts(input, topLevelResult, reasonCodes, error) {
177
+ const id = String(input.id ?? topLevelResult.id ?? 'js_ts_safe_merge');
178
+ const core = {
179
+ kind: 'frontier.lang.jsTsSafeMergeSemanticArtifacts',
180
+ version: 1,
181
+ schema: 'frontier.lang.jsTsSafeMergeSemanticArtifacts.v1',
182
+ id: `js_ts_safe_merge_semantic_edit_artifacts_${idFragment(id)}`,
183
+ sourcePath: input.sourcePath ?? topLevelResult.sourcePath,
184
+ language: input.language ?? topLevelResult.language ?? 'typescript',
185
+ status: 'blocked',
186
+ admission: {
187
+ status: 'blocked',
188
+ action: 'human-review',
189
+ reviewRequired: true,
190
+ autoApplyCandidate: false,
191
+ autoMergeClaim: false,
192
+ semanticEquivalenceClaim: false,
193
+ reasonCodes
194
+ },
195
+ summary: {
196
+ operations: 0,
197
+ edits: 0,
198
+ replayStatus: 'blocked',
199
+ alreadyAppliedReplayStatus: 'blocked',
200
+ projectedSourceMatchesMerged: false,
201
+ replayOutputMatchesMerged: false
202
+ },
203
+ metadata: {
204
+ source: 'js-ts-semantic-edit-fallback',
205
+ error: error?.message
206
+ }
207
+ };
208
+ return { ...core, hash: hashSemanticValue(core) };
209
+ }
210
+
211
+ function semanticEditFallbackBlockedResult(input, topLevelResult, artifacts) {
212
+ const reasonCodes = artifacts.admission.reasonCodes.length
213
+ ? artifacts.admission.reasonCodes
214
+ : topLevelResult.admission?.reasonCodes ?? [];
215
+ const gates = semanticEditGates(artifacts);
216
+ const conflict = {
217
+ code: JsTsSafeMergeConflictCodes.changedExistingDeclaration,
218
+ gateId: 'semantic-edit-replay',
219
+ message: 'JS/TS semantic edit fallback did not verify a clean replay.',
220
+ side: 'worker',
221
+ sourcePath: input.sourcePath ?? topLevelResult.sourcePath,
222
+ details: { reasonCodes }
223
+ };
224
+ return {
225
+ ...topLevelResult,
226
+ conflicts: [conflict],
227
+ gates,
228
+ admission: {
229
+ status: 'blocked',
230
+ action: 'human-review',
231
+ reviewRequired: true,
232
+ autoApplyCandidate: false,
233
+ autoMergeClaim: false,
234
+ semanticEquivalenceClaim: false,
235
+ reasonCodes
236
+ },
237
+ summary: {
238
+ ...topLevelResult.summary,
239
+ conflicts: 1,
240
+ gatesPassed: gates.filter((gate) => gate.status === 'passed').length,
241
+ semanticEditOperations: artifacts.summary.operations,
242
+ semanticEditReplayStatus: artifacts.summary.replayStatus
243
+ },
244
+ semanticArtifacts: artifacts
245
+ };
246
+ }
247
+
248
+ function semanticEditGates(artifacts) {
249
+ return [
250
+ gate('semantic-edit-script', artifacts.script?.admission?.status === 'auto-merge-candidate', artifacts.script?.admission?.reasonCodes),
251
+ gate('semantic-edit-projection', artifacts.projection?.status === 'projected', artifacts.projection?.admission?.reasonCodes),
252
+ gate('semantic-edit-replay', artifacts.replay?.status === 'accepted-clean', artifacts.replay?.admission?.reasonCodes),
253
+ gate('semantic-edit-already-applied', artifacts.alreadyAppliedReplay?.status === 'already-applied', artifacts.alreadyAppliedReplay?.admission?.reasonCodes)
254
+ ];
255
+ }
256
+
257
+ function gate(id, passed, reasonCodes = []) {
258
+ return { id, status: passed ? 'passed' : 'blocked', reasonCodes: passed ? [] : uniqueStrings(reasonCodes) };
259
+ }
260
+
261
+ export {
262
+ semanticEditFallbackResult
263
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.131",
3
+ "version": "0.2.133",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",