@shapeshift-labs/frontier-lang-compiler 0.2.80 → 0.2.82

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,54 @@ export interface SemanticEditScript {
114
120
  readonly metadata?: Record<string, unknown>;
115
121
  }
116
122
 
123
+ export interface SemanticEditProjectionEdit {
124
+ readonly operationId?: string;
125
+ readonly status: 'applied' | 'already-applied';
126
+ readonly headStart: number;
127
+ readonly headEnd: number;
128
+ readonly workerStart?: number;
129
+ readonly workerEnd?: number;
130
+ readonly deletedBytes: number;
131
+ readonly replacementBytes: number;
132
+ readonly deletedTextHash?: string;
133
+ readonly replacementTextHash?: string;
134
+ readonly replacementText?: string;
135
+ }
136
+
137
+ export interface SemanticEditProjection {
138
+ readonly kind: 'frontier.lang.semanticEditProjection';
139
+ readonly version: 1;
140
+ readonly id: string;
141
+ readonly hash: string;
142
+ readonly scriptId?: string;
143
+ readonly status: 'projected' | 'blocked';
144
+ readonly sourcePath?: string;
145
+ readonly language?: FrontierSourceLanguage | string;
146
+ readonly baseHash?: string;
147
+ readonly workerHash?: string;
148
+ readonly headHash?: string;
149
+ readonly projectedHash?: string;
150
+ readonly appliedOperations: readonly string[];
151
+ readonly skippedOperations: readonly string[];
152
+ readonly edits: readonly SemanticEditProjectionEdit[];
153
+ readonly sourceText?: string;
154
+ readonly admission: {
155
+ readonly status: 'auto-merge-candidate' | 'blocked';
156
+ readonly autoMergeClaim: false;
157
+ readonly semanticEquivalenceClaim: false;
158
+ readonly reasonCodes: readonly string[];
159
+ };
160
+ readonly metadata?: Record<string, unknown>;
161
+ }
162
+
163
+ export interface ProjectSemanticEditScriptToSourceOptions {
164
+ readonly id?: string;
165
+ readonly script: SemanticEditScript;
166
+ readonly workerSourceText: string;
167
+ readonly headSourceText: string;
168
+ readonly metadata?: Record<string, unknown>;
169
+ }
170
+
117
171
  export interface CreateSemanticEditScriptOptions {
118
172
  readonly id?: string;
119
173
  readonly language?: FrontierSourceLanguage | string;
@@ -141,3 +195,4 @@ export interface CreateSemanticEditScriptOptions {
141
195
 
142
196
  export declare const SemanticEditScriptAdmissionStatuses: readonly SemanticEditScriptAdmissionStatus[];
143
197
  export declare function createSemanticEditScript(input?: CreateSemanticEditScriptOptions): SemanticEditScript;
198
+ 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,131 @@
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
+ edits: blocked ? [] : edits.map(projectionEditRecord),
36
+ sourceText,
37
+ admission: {
38
+ status: blocked ? 'blocked' : 'auto-merge-candidate',
39
+ autoMergeClaim: false,
40
+ semanticEquivalenceClaim: false,
41
+ reasonCodes: uniqueStrings(reasonCodes)
42
+ },
43
+ metadata: compactRecord({
44
+ autoMergeClaim: false,
45
+ semanticEquivalenceClaim: false,
46
+ editCount: edits.length,
47
+ appliedEditCount: edits.filter((edit) => !edit.alreadyApplied).length,
48
+ alreadyAppliedEditCount: edits.filter((edit) => edit.alreadyApplied).length,
49
+ ...input.metadata
50
+ })
51
+ };
52
+ return { ...core, hash: hashSemanticValue(core) };
53
+ }
54
+
55
+ function sourceEditForOperation(operation, workerSourceText, headSourceText) {
56
+ if (operation.status === 'already-applied') {
57
+ return { ok: true, value: { operationId: operation.id, start: 0, end: 0, replacement: '', current: '', alreadyApplied: true } };
58
+ }
59
+ if (operation.status !== 'portable') return { ok: false, reasonCodes: [`operation-not-portable:${operation.id}`] };
60
+ const workerOffsets = spanOffsets(workerSourceText, operation.spans?.worker);
61
+ const headOffsets = spanOffsets(headSourceText, operation.spans?.head ?? operation.spans?.base ?? operation.anchor?.sourceSpan);
62
+ const reasons = [];
63
+ if (!workerOffsets) reasons.push(`worker-span-not-resolvable:${operation.id}`);
64
+ if (!headOffsets) reasons.push(`head-span-not-resolvable:${operation.id}`);
65
+ if (reasons.length) return { ok: false, reasonCodes: reasons };
66
+ const replacement = workerSourceText.slice(workerOffsets.start, workerOffsets.end);
67
+ const current = headSourceText.slice(headOffsets.start, headOffsets.end);
68
+ if (operation.hashes?.workerTextHash && hashSemanticValue(replacement) !== operation.hashes.workerTextHash) {
69
+ reasons.push(`worker-span-hash-mismatch:${operation.id}`);
70
+ }
71
+ const expectedHeadHash = operation.hashes?.headTextHash ?? operation.hashes?.baseTextHash;
72
+ if (expectedHeadHash && hashSemanticValue(current) !== expectedHeadHash) {
73
+ reasons.push(`head-span-hash-mismatch:${operation.id}`);
74
+ }
75
+ if (reasons.length) return { ok: false, reasonCodes: reasons };
76
+ return {
77
+ ok: true,
78
+ value: {
79
+ operationId: operation.id,
80
+ start: headOffsets.start,
81
+ end: headOffsets.end,
82
+ workerStart: workerOffsets.start,
83
+ workerEnd: workerOffsets.end,
84
+ replacement,
85
+ current
86
+ }
87
+ };
88
+ }
89
+
90
+ function projectionEditRecord(edit) {
91
+ return compactRecord({
92
+ operationId: edit.operationId,
93
+ status: edit.alreadyApplied ? 'already-applied' : 'applied',
94
+ headStart: edit.start,
95
+ headEnd: edit.end,
96
+ workerStart: edit.workerStart,
97
+ workerEnd: edit.workerEnd,
98
+ deletedBytes: edit.current.length,
99
+ replacementBytes: edit.replacement.length,
100
+ deletedTextHash: hashSemanticValue(edit.current),
101
+ replacementTextHash: hashSemanticValue(edit.replacement),
102
+ replacementText: edit.replacement
103
+ });
104
+ }
105
+
106
+ function applySourceEdits(sourceText, edits) {
107
+ return edits.filter((edit) => !edit.alreadyApplied)
108
+ .sort((left, right) => right.start - left.start)
109
+ .reduce((text, edit) => text.slice(0, edit.start) + edit.replacement + text.slice(edit.end), sourceText);
110
+ }
111
+
112
+ function spanOffsets(sourceText, span) {
113
+ if (typeof sourceText !== 'string' || !span) return undefined;
114
+ if (typeof span.start === 'number' && typeof span.end === 'number' && span.end >= span.start) return { start: span.start, end: span.end };
115
+ if (typeof span.startLine !== 'number') return undefined;
116
+ const lineStarts = [0];
117
+ for (let index = 0; index < sourceText.length; index += 1) if (sourceText[index] === '\n') lineStarts.push(index + 1);
118
+ const startLine = Math.max(1, span.startLine);
119
+ const endLine = Math.max(startLine, typeof span.endLine === 'number' ? span.endLine : startLine);
120
+ const start = lineStarts[startLine - 1];
121
+ const endLineStart = lineStarts[endLine - 1];
122
+ if (start === undefined || endLineStart === undefined) return undefined;
123
+ const startColumn = Math.max(1, span.startColumn ?? 1) - 1;
124
+ const lineEnd = lineStarts[endLine] === undefined ? sourceText.length : lineStarts[endLine] - 1;
125
+ const endColumn = span.endColumn === undefined ? lineEnd - endLineStart : Math.max(1, span.endColumn) - 1;
126
+ return { start: start + startColumn, end: endLineStart + endColumn };
127
+ }
128
+
129
+ function compactRecord(value) {
130
+ return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
131
+ }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.80",
3
+ "version": "0.2.82",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",