@shapeshift-labs/frontier-lang-compiler 0.2.132 → 0.2.134

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,16 @@ 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. The same fallback composes with declared unordered
198
+ member-addition regions, so a verified body edit can still merge alongside safe
199
+ interface, type, class, or object member additions.
200
+
191
201
  Project-level JS/TS safe merges compose the same file-level gates across a
192
202
  base/worker/head file set. They preserve head-only files, admit worker-only
193
203
  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
 
@@ -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,19 +13,28 @@ 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) {
19
25
  return composedBlockedResult(input, 'member-analysis', memberNeutralization.result, memberNeutralization.analysis);
20
26
  }
21
27
 
22
- const topLevelResult = safeMergeJsTsImportsAndDeclarations({
28
+ const topLevelInput = {
23
29
  ...input,
24
30
  baseSourceText: memberNeutralization.baseSourceText,
25
31
  workerSourceText: memberNeutralization.workerSourceText,
26
32
  headSourceText: memberNeutralization.headSourceText
27
- });
33
+ };
34
+ const topLevelLedgerResult = safeMergeJsTsImportsAndDeclarations(topLevelInput);
35
+ const topLevelResult = topLevelLedgerResult.status === JsTsSafeMergeStatuses.merged
36
+ ? topLevelLedgerResult
37
+ : semanticEditFallbackResult(topLevelInput, topLevelLedgerResult);
28
38
  if (topLevelResult.status !== JsTsSafeMergeStatuses.merged) {
29
39
  return composedBlockedResult(input, 'top-level', topLevelResult, memberNeutralization.analysis);
30
40
  }
@@ -55,7 +65,7 @@ function safeMergeJsTsSource(input = {}) {
55
65
  metadata: {
56
66
  ...topLevelResult.metadata,
57
67
  composed: {
58
- phases: ['top-level', 'member'],
68
+ phases: composedPhaseList(topLevelResult),
59
69
  memberRegions: memberNeutralization.analysis.preparedRegions.map((region) => ({
60
70
  kind: region.kind,
61
71
  name: region.name,
@@ -72,6 +82,13 @@ function safeMergeJsTsSource(input = {}) {
72
82
  };
73
83
  }
74
84
 
85
+ function composedPhaseList(topLevelResult) {
86
+ const topLevelPhases = topLevelResult.metadata?.composed?.phases;
87
+ if (Array.isArray(topLevelPhases) && topLevelPhases.length) return [...topLevelPhases, 'member'];
88
+ const topLevelPhase = topLevelResult.metadata?.composed?.phase;
89
+ return topLevelPhase ? [topLevelPhase, 'member'] : ['top-level', 'member'];
90
+ }
91
+
75
92
  function hasMemberMergePolicy(input) {
76
93
  const policy = input.policy ?? input.mergePolicy ?? input;
77
94
  const regions = Array.isArray(policy)
@@ -113,14 +130,20 @@ function composedBlockedResult(input, phase, result, memberAnalysis) {
113
130
  gatesPassed: result.gates?.filter((gate) => gate.status === 'passed').length ?? 0,
114
131
  memberRegions: memberAnalysis?.preparedRegions?.length ?? 0,
115
132
  memberAdditions: 0,
133
+ semanticEditOperations: result.summary?.semanticEditOperations,
134
+ semanticEditAppliedOperations: result.summary?.semanticEditAppliedOperations,
135
+ semanticEditReplayStatus: result.summary?.semanticEditReplayStatus,
116
136
  composedPhases: phase === 'top-level' ? 2 : 1
117
137
  },
118
138
  metadata: {
119
139
  composed: {
120
140
  phase,
121
- sourceKind: result.kind
141
+ sourceKind: result.kind,
142
+ topLevelPhase: result.metadata?.composed?.phase,
143
+ topLevelPhases: result.metadata?.composed?.phases
122
144
  }
123
- }
145
+ },
146
+ semanticArtifacts: result.semanticArtifacts
124
147
  };
125
148
  }
126
149
 
@@ -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.132",
3
+ "version": "0.2.134",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",