@shapeshift-labs/frontier-lang-compiler 0.2.80 → 0.2.81
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
|
@@ -933,6 +933,7 @@ The published Frontier package family is generated from one shared package catal
|
|
|
933
933
|
- [`@shapeshift-labs/frontier-realtime-server`](https://www.npmjs.com/package/@shapeshift-labs/frontier-realtime-server): Authoritative realtime room, tick, command validation, rate-limit, session, and snapshot-history runtime.
|
|
934
934
|
- [`@shapeshift-labs/frontier-realtime-websocket`](https://www.npmjs.com/package/@shapeshift-labs/frontier-realtime-websocket): WebSocket client, wire, and Node room-server transport for Frontier realtime.
|
|
935
935
|
- [`@shapeshift-labs/frontier-game`](https://www.npmjs.com/package/@shapeshift-labs/frontier-game): Game-facing entity, component, player, room, ownership, spatial interest, rollback, physics, and replication helpers above realtime.
|
|
936
|
+
- [`@shapeshift-labs/loom`](https://www.npmjs.com/package/@shapeshift-labs/loom): Repo-level semantic collaboration CLI for .loom workspaces, including init, scan, status, graph snapshots, projection plans, Frontier Lang delegation, Frontier Swarm delegation, and Frontier Framework delegation.
|
|
936
937
|
|
|
937
938
|
Package source repositories:
|
|
938
939
|
|
|
@@ -1026,3 +1027,4 @@ Package source repositories:
|
|
|
1026
1027
|
- [`siliconjungle/-shapeshift-labs-frontier-realtime-server`](https://github.com/siliconjungle/-shapeshift-labs-frontier-realtime-server)
|
|
1027
1028
|
- [`siliconjungle/-shapeshift-labs-frontier-realtime-websocket`](https://github.com/siliconjungle/-shapeshift-labs-frontier-realtime-websocket)
|
|
1028
1029
|
- [`siliconjungle/-shapeshift-labs-frontier-game`](https://github.com/siliconjungle/-shapeshift-labs-frontier-game)
|
|
1030
|
+
- [`siliconjungle/-shapeshift-labs-loom`](https://github.com/siliconjungle/-shapeshift-labs-loom)
|
|
@@ -40,6 +40,11 @@ export interface SemanticEditScriptOperation {
|
|
|
40
40
|
readonly symbolKind?: string;
|
|
41
41
|
readonly sourceSpan?: SourceSpan;
|
|
42
42
|
};
|
|
43
|
+
readonly spans?: {
|
|
44
|
+
readonly base?: SourceSpan;
|
|
45
|
+
readonly worker?: SourceSpan;
|
|
46
|
+
readonly head?: SourceSpan;
|
|
47
|
+
};
|
|
43
48
|
readonly hashes?: {
|
|
44
49
|
readonly baseSourceHash?: string;
|
|
45
50
|
readonly workerSourceHash?: string;
|
|
@@ -49,6 +54,7 @@ export interface SemanticEditScriptOperation {
|
|
|
49
54
|
readonly headSpanHash?: string;
|
|
50
55
|
readonly baseTextHash?: string;
|
|
51
56
|
readonly workerTextHash?: string;
|
|
57
|
+
readonly headTextHash?: string;
|
|
52
58
|
readonly beforeSignatureHash?: string;
|
|
53
59
|
readonly afterSignatureHash?: string;
|
|
54
60
|
};
|
|
@@ -114,6 +120,39 @@ export interface SemanticEditScript {
|
|
|
114
120
|
readonly metadata?: Record<string, unknown>;
|
|
115
121
|
}
|
|
116
122
|
|
|
123
|
+
export interface SemanticEditProjection {
|
|
124
|
+
readonly kind: 'frontier.lang.semanticEditProjection';
|
|
125
|
+
readonly version: 1;
|
|
126
|
+
readonly id: string;
|
|
127
|
+
readonly hash: string;
|
|
128
|
+
readonly scriptId?: string;
|
|
129
|
+
readonly status: 'projected' | 'blocked';
|
|
130
|
+
readonly sourcePath?: string;
|
|
131
|
+
readonly language?: FrontierSourceLanguage | string;
|
|
132
|
+
readonly baseHash?: string;
|
|
133
|
+
readonly workerHash?: string;
|
|
134
|
+
readonly headHash?: string;
|
|
135
|
+
readonly projectedHash?: string;
|
|
136
|
+
readonly appliedOperations: readonly string[];
|
|
137
|
+
readonly skippedOperations: readonly string[];
|
|
138
|
+
readonly sourceText?: string;
|
|
139
|
+
readonly admission: {
|
|
140
|
+
readonly status: 'auto-merge-candidate' | 'blocked';
|
|
141
|
+
readonly autoMergeClaim: false;
|
|
142
|
+
readonly semanticEquivalenceClaim: false;
|
|
143
|
+
readonly reasonCodes: readonly string[];
|
|
144
|
+
};
|
|
145
|
+
readonly metadata?: Record<string, unknown>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface ProjectSemanticEditScriptToSourceOptions {
|
|
149
|
+
readonly id?: string;
|
|
150
|
+
readonly script: SemanticEditScript;
|
|
151
|
+
readonly workerSourceText: string;
|
|
152
|
+
readonly headSourceText: string;
|
|
153
|
+
readonly metadata?: Record<string, unknown>;
|
|
154
|
+
}
|
|
155
|
+
|
|
117
156
|
export interface CreateSemanticEditScriptOptions {
|
|
118
157
|
readonly id?: string;
|
|
119
158
|
readonly language?: FrontierSourceLanguage | string;
|
|
@@ -141,3 +180,4 @@ export interface CreateSemanticEditScriptOptions {
|
|
|
141
180
|
|
|
142
181
|
export declare const SemanticEditScriptAdmissionStatuses: readonly SemanticEditScriptAdmissionStatus[];
|
|
143
182
|
export declare function createSemanticEditScript(input?: CreateSemanticEditScriptOptions): SemanticEditScript;
|
|
183
|
+
export declare function projectSemanticEditScriptToSource(input: ProjectSemanticEditScriptToSourceOptions): SemanticEditProjection;
|
package/dist/index.js
CHANGED
|
@@ -70,6 +70,7 @@ export { createSemanticPatchBundleRecord, querySemanticPatchBundleRecords, Seman
|
|
|
70
70
|
export { createSemanticMergeCandidateAdmissionRecord, decorateSemanticMergeCandidateForAdmission, querySemanticMergeCandidateAdmissionOverlaps, SemanticMergeCandidateProjectionRisks, semanticMergeCandidateReadinessSortKey, sortSemanticMergeCandidateAdmissionRecords } from './internal/index-impl/semanticMergeCandidateRecords.js';
|
|
71
71
|
export { querySemanticMergeConflictClasses, SemanticMergeConflictClasses, semanticMergeConflictRiskScore, sortSemanticMergeCandidatesByConflictRisk, summarizeSemanticMergeConflicts } from './internal/index-impl/semanticMergeConflicts.js';
|
|
72
72
|
export { createSemanticEditScript, SemanticEditScriptAdmissionStatuses } from './internal/index-impl/semanticEditScripts.js';
|
|
73
|
+
export { projectSemanticEditScriptToSource } from './internal/index-impl/projectSemanticEditScriptToSource.js';
|
|
73
74
|
export { queryUniversalConversionPlan } from './internal/index-impl/queryUniversalConversionPlan.js';
|
|
74
75
|
export { createSemanticAnchor, createSemanticLineageEvent, createSemanticLineageMap, querySemanticLineageEvents, SemanticLineageEventKinds } from './internal/index-impl/semanticLineageRecords.js';
|
|
75
76
|
export { resolveSemanticLineage, resolveSemanticLineageBatch, SemanticLineageResolutionStatuses } from './internal/index-impl/semanticLineageResolutionRecords.js';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { idFragment, uniqueStrings } from '../../native-import-utils.js';
|
|
3
|
+
|
|
4
|
+
export function projectSemanticEditScriptToSource(input = {}) {
|
|
5
|
+
const script = input.script;
|
|
6
|
+
const workerSourceText = input.workerSourceText;
|
|
7
|
+
const headSourceText = input.headSourceText;
|
|
8
|
+
const reasonCodes = [];
|
|
9
|
+
if (!script) throw new Error('projectSemanticEditScriptToSource requires a script');
|
|
10
|
+
if (script.admission?.status !== 'auto-merge-candidate') reasonCodes.push('script-not-auto-merge-candidate');
|
|
11
|
+
if (typeof workerSourceText !== 'string') reasonCodes.push('missing-worker-source-text');
|
|
12
|
+
if (typeof headSourceText !== 'string') reasonCodes.push('missing-head-source-text');
|
|
13
|
+
const edits = [];
|
|
14
|
+
for (const operation of script.operations ?? []) {
|
|
15
|
+
const edit = sourceEditForOperation(operation, workerSourceText, headSourceText);
|
|
16
|
+
if (edit.ok) edits.push(edit.value);
|
|
17
|
+
else reasonCodes.push(...edit.reasonCodes);
|
|
18
|
+
}
|
|
19
|
+
const blocked = reasonCodes.length > 0;
|
|
20
|
+
const sourceText = blocked ? undefined : applySourceEdits(headSourceText, edits);
|
|
21
|
+
const core = {
|
|
22
|
+
kind: 'frontier.lang.semanticEditProjection',
|
|
23
|
+
version: 1,
|
|
24
|
+
id: input.id ?? `semantic_edit_projection_${idFragment(script.id ?? script.hash ?? 'script')}`,
|
|
25
|
+
scriptId: script.id,
|
|
26
|
+
status: blocked ? 'blocked' : 'projected',
|
|
27
|
+
sourcePath: script.sourcePath,
|
|
28
|
+
language: script.language,
|
|
29
|
+
baseHash: script.baseHash,
|
|
30
|
+
workerHash: script.workerHash,
|
|
31
|
+
headHash: script.headHash,
|
|
32
|
+
projectedHash: sourceText === undefined ? undefined : hashSemanticValue(sourceText),
|
|
33
|
+
appliedOperations: blocked ? [] : edits.map((edit) => edit.operationId),
|
|
34
|
+
skippedOperations: blocked ? (script.operations ?? []).map((operation) => operation.id) : [],
|
|
35
|
+
sourceText,
|
|
36
|
+
admission: {
|
|
37
|
+
status: blocked ? 'blocked' : 'auto-merge-candidate',
|
|
38
|
+
autoMergeClaim: false,
|
|
39
|
+
semanticEquivalenceClaim: false,
|
|
40
|
+
reasonCodes: uniqueStrings(reasonCodes)
|
|
41
|
+
},
|
|
42
|
+
metadata: compactRecord({
|
|
43
|
+
autoMergeClaim: false,
|
|
44
|
+
semanticEquivalenceClaim: false,
|
|
45
|
+
editCount: edits.length,
|
|
46
|
+
...input.metadata
|
|
47
|
+
})
|
|
48
|
+
};
|
|
49
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function sourceEditForOperation(operation, workerSourceText, headSourceText) {
|
|
53
|
+
if (operation.status === 'already-applied') {
|
|
54
|
+
return { ok: true, value: { operationId: operation.id, start: 0, end: 0, replacement: '', alreadyApplied: true } };
|
|
55
|
+
}
|
|
56
|
+
if (operation.status !== 'portable') return { ok: false, reasonCodes: [`operation-not-portable:${operation.id}`] };
|
|
57
|
+
const workerOffsets = spanOffsets(workerSourceText, operation.spans?.worker);
|
|
58
|
+
const headOffsets = spanOffsets(headSourceText, operation.spans?.head ?? operation.spans?.base ?? operation.anchor?.sourceSpan);
|
|
59
|
+
const reasons = [];
|
|
60
|
+
if (!workerOffsets) reasons.push(`worker-span-not-resolvable:${operation.id}`);
|
|
61
|
+
if (!headOffsets) reasons.push(`head-span-not-resolvable:${operation.id}`);
|
|
62
|
+
if (reasons.length) return { ok: false, reasonCodes: reasons };
|
|
63
|
+
const replacement = workerSourceText.slice(workerOffsets.start, workerOffsets.end);
|
|
64
|
+
const current = headSourceText.slice(headOffsets.start, headOffsets.end);
|
|
65
|
+
if (operation.hashes?.workerTextHash && hashSemanticValue(replacement) !== operation.hashes.workerTextHash) {
|
|
66
|
+
reasons.push(`worker-span-hash-mismatch:${operation.id}`);
|
|
67
|
+
}
|
|
68
|
+
const expectedHeadHash = operation.hashes?.headTextHash ?? operation.hashes?.baseTextHash;
|
|
69
|
+
if (expectedHeadHash && hashSemanticValue(current) !== expectedHeadHash) {
|
|
70
|
+
reasons.push(`head-span-hash-mismatch:${operation.id}`);
|
|
71
|
+
}
|
|
72
|
+
if (reasons.length) return { ok: false, reasonCodes: reasons };
|
|
73
|
+
return { ok: true, value: { operationId: operation.id, start: headOffsets.start, end: headOffsets.end, replacement } };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function applySourceEdits(sourceText, edits) {
|
|
77
|
+
return edits.filter((edit) => !edit.alreadyApplied)
|
|
78
|
+
.sort((left, right) => right.start - left.start)
|
|
79
|
+
.reduce((text, edit) => text.slice(0, edit.start) + edit.replacement + text.slice(edit.end), sourceText);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function spanOffsets(sourceText, span) {
|
|
83
|
+
if (typeof sourceText !== 'string' || !span) return undefined;
|
|
84
|
+
if (typeof span.start === 'number' && typeof span.end === 'number' && span.end >= span.start) return { start: span.start, end: span.end };
|
|
85
|
+
if (typeof span.startLine !== 'number') return undefined;
|
|
86
|
+
const lineStarts = [0];
|
|
87
|
+
for (let index = 0; index < sourceText.length; index += 1) if (sourceText[index] === '\n') lineStarts.push(index + 1);
|
|
88
|
+
const startLine = Math.max(1, span.startLine);
|
|
89
|
+
const endLine = Math.max(startLine, typeof span.endLine === 'number' ? span.endLine : startLine);
|
|
90
|
+
const start = lineStarts[startLine - 1];
|
|
91
|
+
const endLineStart = lineStarts[endLine - 1];
|
|
92
|
+
if (start === undefined || endLineStart === undefined) return undefined;
|
|
93
|
+
const startColumn = Math.max(1, span.startColumn ?? 1) - 1;
|
|
94
|
+
const lineEnd = lineStarts[endLine] === undefined ? sourceText.length : lineStarts[endLine] - 1;
|
|
95
|
+
const endColumn = span.endColumn === undefined ? lineEnd - endLineStart : Math.max(1, span.endColumn) - 1;
|
|
96
|
+
return { start: start + startColumn, end: endLineStart + endColumn };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function compactRecord(value) {
|
|
100
|
+
return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
|
|
101
|
+
}
|
|
@@ -136,6 +136,7 @@ function semanticEditOperation(region, index, context, input) {
|
|
|
136
136
|
const kind = semanticEditOperationKind(region);
|
|
137
137
|
const baseText = spanText(context.base, baseSymbol?.sourceSpan ?? region.metadata?.changedRegionProjection?.before?.sourceSpan ?? region.sourceSpan);
|
|
138
138
|
const workerText = spanText(context.worker, workerSymbol?.sourceSpan ?? region.metadata?.changedRegionProjection?.after?.sourceSpan ?? region.sourceSpan);
|
|
139
|
+
const headText = spanText(context.head, headSymbol?.sourceSpan ?? region.metadata?.changedRegionProjection?.head?.sourceSpan ?? baseSymbol?.sourceSpan);
|
|
139
140
|
return compactRecord({
|
|
140
141
|
id: `semantic_edit_op_${idFragment(firstString(input.id, anchorKey, index))}`,
|
|
141
142
|
kind,
|
|
@@ -153,6 +154,11 @@ function semanticEditOperation(region, index, context, input) {
|
|
|
153
154
|
symbolKind: region.symbolKind ?? workerSymbol?.kind ?? baseSymbol?.kind,
|
|
154
155
|
sourceSpan: workerSymbol?.sourceSpan ?? region.sourceSpan
|
|
155
156
|
}),
|
|
157
|
+
spans: compactRecord({
|
|
158
|
+
base: baseSymbol?.sourceSpan ?? region.metadata?.changedRegionProjection?.before?.sourceSpan,
|
|
159
|
+
worker: workerSymbol?.sourceSpan ?? region.metadata?.changedRegionProjection?.after?.sourceSpan ?? region.sourceSpan,
|
|
160
|
+
head: headSymbol?.sourceSpan
|
|
161
|
+
}),
|
|
156
162
|
hashes: compactRecord({
|
|
157
163
|
baseSourceHash: context.workerChangeSet.beforeHash,
|
|
158
164
|
workerSourceHash: context.workerChangeSet.afterHash,
|
|
@@ -162,6 +168,7 @@ function semanticEditOperation(region, index, context, input) {
|
|
|
162
168
|
headSpanHash: headSymbol?.spanHash,
|
|
163
169
|
baseTextHash: baseText === undefined ? undefined : hashSemanticValue(baseText),
|
|
164
170
|
workerTextHash: workerText === undefined ? undefined : hashSemanticValue(workerText),
|
|
171
|
+
headTextHash: headText === undefined ? undefined : hashSemanticValue(headText),
|
|
165
172
|
beforeSignatureHash: workerSymbol?.beforeSignatureHash ?? baseSymbol?.signatureHash,
|
|
166
173
|
afterSignatureHash: workerSymbol?.afterSignatureHash ?? workerSymbol?.signatureHash
|
|
167
174
|
}),
|
package/package.json
CHANGED