@shapeshift-labs/frontier-lang-compiler 0.2.90 → 0.2.92
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 +2 -0
- package/dist/declarations/semantic-edit-script.d.ts +77 -0
- package/dist/declarations/semantic-patch-bundle.d.ts +25 -0
- package/dist/index.js +1 -0
- package/dist/internal/index-impl/replaySemanticEditProjection.js +191 -0
- package/dist/internal/index-impl/semanticPatchBundleAdmission.js +97 -0
- package/dist/internal/index-impl/semanticPatchBundleRecords.js +6 -19
- package/dist/internal/index-impl/semanticTransformIdentityRecords.js +4 -0
- package/package.json +1 -1
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)
|
|
@@ -191,6 +191,71 @@ export interface SemanticEditProjection {
|
|
|
191
191
|
readonly metadata?: Record<string, unknown>;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
export type SemanticEditReplayStatus =
|
|
195
|
+
| 'accepted-clean'
|
|
196
|
+
| 'already-applied'
|
|
197
|
+
| 'conflict'
|
|
198
|
+
| 'stale'
|
|
199
|
+
| 'blocked'
|
|
200
|
+
| 'needs-port'
|
|
201
|
+
| 'evidence-only';
|
|
202
|
+
|
|
203
|
+
export interface SemanticEditReplayEdit {
|
|
204
|
+
readonly operationId?: string;
|
|
205
|
+
readonly semanticKey?: string;
|
|
206
|
+
readonly semanticIdentityHash?: string;
|
|
207
|
+
readonly sourceIdentityHash?: string;
|
|
208
|
+
readonly editContentHash?: string;
|
|
209
|
+
readonly sourcePath?: string;
|
|
210
|
+
readonly symbolName?: string;
|
|
211
|
+
readonly symbolKind?: string;
|
|
212
|
+
readonly status: 'applied' | 'already-applied' | 'conflict' | 'stale' | 'blocked' | string;
|
|
213
|
+
readonly start?: number;
|
|
214
|
+
readonly end?: number;
|
|
215
|
+
readonly replacementBytes?: number;
|
|
216
|
+
readonly replacementText?: string;
|
|
217
|
+
readonly reasonCodes: readonly string[];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface SemanticEditReplay {
|
|
221
|
+
readonly kind: 'frontier.lang.semanticEditReplay';
|
|
222
|
+
readonly version: 1;
|
|
223
|
+
readonly schema: 'frontier.lang.semanticEditReplay.v1';
|
|
224
|
+
readonly id: string;
|
|
225
|
+
readonly hash: string;
|
|
226
|
+
readonly projectionId?: string;
|
|
227
|
+
readonly scriptId?: string;
|
|
228
|
+
readonly sourcePath?: string;
|
|
229
|
+
readonly language?: FrontierSourceLanguage | string;
|
|
230
|
+
readonly currentHash?: string;
|
|
231
|
+
readonly projectedHash?: string;
|
|
232
|
+
readonly outputHash?: string;
|
|
233
|
+
readonly status: SemanticEditReplayStatus;
|
|
234
|
+
readonly edits: readonly SemanticEditReplayEdit[];
|
|
235
|
+
readonly appliedOperations: readonly string[];
|
|
236
|
+
readonly skippedOperations: readonly string[];
|
|
237
|
+
readonly admission: {
|
|
238
|
+
readonly status: SemanticEditReplayStatus;
|
|
239
|
+
readonly action: 'apply' | 'skip' | 'rerun-semantic-import' | 'human-review' | 'block' | string;
|
|
240
|
+
readonly reviewRequired: boolean;
|
|
241
|
+
readonly autoApplyCandidate: boolean;
|
|
242
|
+
readonly autoMergeClaim: false;
|
|
243
|
+
readonly semanticEquivalenceClaim: false;
|
|
244
|
+
readonly reasonCodes: readonly string[];
|
|
245
|
+
};
|
|
246
|
+
readonly outputSourceText?: string;
|
|
247
|
+
readonly summary: {
|
|
248
|
+
readonly edits: number;
|
|
249
|
+
readonly applied: number;
|
|
250
|
+
readonly alreadyApplied: number;
|
|
251
|
+
readonly conflicts: number;
|
|
252
|
+
readonly stale: number;
|
|
253
|
+
readonly blocked: number;
|
|
254
|
+
readonly reasonCodes: readonly string[];
|
|
255
|
+
};
|
|
256
|
+
readonly metadata?: Record<string, unknown>;
|
|
257
|
+
}
|
|
258
|
+
|
|
194
259
|
export interface ProjectSemanticEditScriptToSourceOptions {
|
|
195
260
|
readonly id?: string;
|
|
196
261
|
readonly script: SemanticEditScript;
|
|
@@ -200,6 +265,17 @@ export interface ProjectSemanticEditScriptToSourceOptions {
|
|
|
200
265
|
readonly metadata?: Record<string, unknown>;
|
|
201
266
|
}
|
|
202
267
|
|
|
268
|
+
export interface ReplaySemanticEditProjectionOptions {
|
|
269
|
+
readonly id?: string;
|
|
270
|
+
readonly projection: SemanticEditProjection;
|
|
271
|
+
readonly currentSourceText: string;
|
|
272
|
+
readonly currentSourcePath?: string;
|
|
273
|
+
readonly currentSourceHash?: string;
|
|
274
|
+
readonly language?: FrontierSourceLanguage | string;
|
|
275
|
+
readonly parser?: string;
|
|
276
|
+
readonly metadata?: Record<string, unknown>;
|
|
277
|
+
}
|
|
278
|
+
|
|
203
279
|
export interface CreateSemanticEditScriptOptions {
|
|
204
280
|
readonly id?: string;
|
|
205
281
|
readonly language?: FrontierSourceLanguage | string;
|
|
@@ -229,3 +305,4 @@ export interface CreateSemanticEditScriptOptions {
|
|
|
229
305
|
export declare const SemanticEditScriptAdmissionStatuses: readonly SemanticEditScriptAdmissionStatus[];
|
|
230
306
|
export declare function createSemanticEditScript(input?: CreateSemanticEditScriptOptions): SemanticEditScript;
|
|
231
307
|
export declare function projectSemanticEditScriptToSource(input: ProjectSemanticEditScriptToSourceOptions): SemanticEditProjection;
|
|
308
|
+
export declare function replaySemanticEditProjection(input: ReplaySemanticEditProjectionOptions): SemanticEditReplay;
|
|
@@ -81,6 +81,8 @@ export interface SemanticPatchBundleAdmission {
|
|
|
81
81
|
readonly readiness: SemanticMergeReadiness | string;
|
|
82
82
|
readonly reviewRequired: boolean;
|
|
83
83
|
readonly autoMergeClaim: false;
|
|
84
|
+
readonly autoApplyCandidate?: boolean;
|
|
85
|
+
readonly transformAdmission?: SemanticPatchBundleTransformAdmission;
|
|
84
86
|
readonly reasonCodes?: readonly string[];
|
|
85
87
|
readonly conflictKeys?: readonly string[];
|
|
86
88
|
readonly admittedAt?: number | string;
|
|
@@ -89,6 +91,23 @@ export interface SemanticPatchBundleAdmission {
|
|
|
89
91
|
readonly metadata?: Record<string, unknown>;
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
export interface SemanticPatchBundleTransformAdmission {
|
|
95
|
+
readonly status: 'none' | 'ready' | 'needs-review' | 'blocked' | string;
|
|
96
|
+
readonly action: 'none' | 'admit' | 'review' | 'block' | string;
|
|
97
|
+
readonly readiness: SemanticMergeReadiness | string;
|
|
98
|
+
readonly crossLanguage?: boolean;
|
|
99
|
+
readonly reasonCodes?: readonly string[];
|
|
100
|
+
readonly transformIds?: readonly string[];
|
|
101
|
+
readonly transformKeys?: readonly string[];
|
|
102
|
+
readonly contentHashes?: readonly string[];
|
|
103
|
+
readonly projectionIdentityHashes?: readonly string[];
|
|
104
|
+
readonly sourceLanguages?: readonly string[];
|
|
105
|
+
readonly targetLanguages?: readonly string[];
|
|
106
|
+
readonly sourcePaths?: readonly string[];
|
|
107
|
+
readonly targetPaths?: readonly string[];
|
|
108
|
+
readonly evidenceIds?: readonly string[];
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
export interface SemanticPatchBundleRecordIndex {
|
|
93
112
|
readonly baseHashes: readonly string[];
|
|
94
113
|
readonly targetHashes: readonly string[];
|
|
@@ -116,6 +135,8 @@ export interface SemanticPatchBundleRecordIndex {
|
|
|
116
135
|
readonly semanticTransformIdentityHashes: readonly string[];
|
|
117
136
|
readonly semanticTransformContentHashes: readonly string[];
|
|
118
137
|
readonly projectionIdentityHashes: readonly string[];
|
|
138
|
+
readonly semanticTransformReadinesses: readonly string[];
|
|
139
|
+
readonly semanticTransformEvidenceIds: readonly string[];
|
|
119
140
|
readonly transformSourceLanguages: readonly string[];
|
|
120
141
|
readonly transformTargetLanguages: readonly string[];
|
|
121
142
|
readonly transformSourcePaths: readonly string[];
|
|
@@ -263,6 +284,10 @@ export interface SemanticPatchBundleRecordQuery {
|
|
|
263
284
|
readonly semanticTransformContentHashes?: readonly string[];
|
|
264
285
|
readonly projectionIdentityHash?: string | readonly string[];
|
|
265
286
|
readonly projectionIdentityHashes?: readonly string[];
|
|
287
|
+
readonly semanticTransformReadiness?: string | readonly string[];
|
|
288
|
+
readonly semanticTransformReadinesses?: readonly string[];
|
|
289
|
+
readonly semanticTransformEvidenceId?: string | readonly string[];
|
|
290
|
+
readonly semanticTransformEvidenceIds?: readonly string[];
|
|
266
291
|
readonly transformSourceLanguage?: string | readonly string[];
|
|
267
292
|
readonly transformSourceLanguages?: readonly string[];
|
|
268
293
|
readonly transformTargetLanguage?: string | readonly string[];
|
package/dist/index.js
CHANGED
|
@@ -73,6 +73,7 @@ export { createSemanticMergeCandidateAdmissionRecord, decorateSemanticMergeCandi
|
|
|
73
73
|
export { querySemanticMergeConflictClasses, SemanticMergeConflictClasses, semanticMergeConflictRiskScore, sortSemanticMergeCandidatesByConflictRisk, summarizeSemanticMergeConflicts } from './internal/index-impl/semanticMergeConflicts.js';
|
|
74
74
|
export { createSemanticEditScript, SemanticEditScriptAdmissionStatuses } from './internal/index-impl/semanticEditScripts.js';
|
|
75
75
|
export { projectSemanticEditScriptToSource } from './internal/index-impl/projectSemanticEditScriptToSource.js';
|
|
76
|
+
export { replaySemanticEditProjection } from './internal/index-impl/replaySemanticEditProjection.js';
|
|
76
77
|
export { queryUniversalConversionPlan } from './internal/index-impl/queryUniversalConversionPlan.js';
|
|
77
78
|
export { createSemanticAnchor, createSemanticLineageEvent, createSemanticLineageMap, querySemanticLineageEvents, SemanticLineageEventKinds } from './internal/index-impl/semanticLineageRecords.js';
|
|
78
79
|
export { resolveSemanticLineage, resolveSemanticLineageBatch, SemanticLineageResolutionStatuses } from './internal/index-impl/semanticLineageResolutionRecords.js';
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
|
|
2
|
+
import { idFragment, normalizeNativeLanguageId, uniqueStrings } from '../../native-import-utils.js';
|
|
3
|
+
import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
|
|
4
|
+
import { mapDiffSymbols } from './mapDiffSymbols.js';
|
|
5
|
+
import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
|
|
6
|
+
|
|
7
|
+
export function replaySemanticEditProjection(input = {}) {
|
|
8
|
+
const projection = input.projection ?? input.semanticEditProjection;
|
|
9
|
+
if (!projection) throw new Error('replaySemanticEditProjection requires a projection');
|
|
10
|
+
const currentSourceText = input.currentSourceText ?? input.headSourceText;
|
|
11
|
+
const sourcePath = input.currentSourcePath ?? input.headSourcePath ?? projection.sourcePath;
|
|
12
|
+
const language = normalizeNativeLanguageId(input.language ?? projection.language);
|
|
13
|
+
const reasonCodes = baseReasonCodes(projection, currentSourceText);
|
|
14
|
+
const currentHash = typeof currentSourceText === 'string' ? hashSemanticValue(currentSourceText) : undefined;
|
|
15
|
+
if (input.currentSourceHash && currentHash !== input.currentSourceHash) reasonCodes.push('current-source-hash-mismatch');
|
|
16
|
+
const currentSymbols = currentSourceText && isJavaScriptLike(language)
|
|
17
|
+
? currentSymbolIndex({ currentSourceText, sourcePath, language, parser: input.parser })
|
|
18
|
+
: [];
|
|
19
|
+
const edits = projection.status === 'projected' && typeof currentSourceText === 'string'
|
|
20
|
+
? (projection.edits ?? []).map((edit) => replayProjectionEdit(edit, { currentSourceText, currentSymbols }))
|
|
21
|
+
: [];
|
|
22
|
+
const status = replayStatus(reasonCodes, edits, projection);
|
|
23
|
+
const outputSourceText = replayOutputSource(status, currentSourceText, edits);
|
|
24
|
+
const core = {
|
|
25
|
+
kind: 'frontier.lang.semanticEditReplay',
|
|
26
|
+
version: 1,
|
|
27
|
+
schema: 'frontier.lang.semanticEditReplay.v1',
|
|
28
|
+
id: input.id ?? `semantic_edit_replay_${idFragment(projection.id ?? sourcePath ?? language ?? 'projection')}`,
|
|
29
|
+
projectionId: projection.id,
|
|
30
|
+
scriptId: projection.scriptId,
|
|
31
|
+
sourcePath,
|
|
32
|
+
language,
|
|
33
|
+
currentHash,
|
|
34
|
+
projectedHash: projection.projectedHash,
|
|
35
|
+
outputHash: outputSourceText === undefined ? undefined : hashSemanticValue(outputSourceText),
|
|
36
|
+
status,
|
|
37
|
+
edits,
|
|
38
|
+
appliedOperations: edits.filter((edit) => edit.status === 'applied').map((edit) => edit.operationId).filter(Boolean),
|
|
39
|
+
skippedOperations: edits.filter((edit) => edit.status !== 'applied').map((edit) => edit.operationId).filter(Boolean),
|
|
40
|
+
admission: replayAdmission(status, reasonCodes, edits),
|
|
41
|
+
outputSourceText,
|
|
42
|
+
summary: replaySummary(edits, reasonCodes),
|
|
43
|
+
metadata: compactRecord({
|
|
44
|
+
autoMergeClaim: false,
|
|
45
|
+
semanticEquivalenceClaim: false,
|
|
46
|
+
anchorMode: currentSymbols.length ? 'javascript-like-symbols' : 'offsets',
|
|
47
|
+
...input.metadata
|
|
48
|
+
})
|
|
49
|
+
};
|
|
50
|
+
return { ...core, hash: hashSemanticValue(core) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function replayProjectionEdit(edit, context) {
|
|
54
|
+
if (edit.status === 'already-applied') return replayEditRecord(edit, 'already-applied', undefined, ['projection-edit-already-applied']);
|
|
55
|
+
if (typeof edit.replacementText !== 'string') return replayEditRecord(edit, 'blocked', undefined, ['missing-replacement-text']);
|
|
56
|
+
const offset = checkRange(edit, { start: edit.headStart, end: edit.headEnd }, context.currentSourceText, 'head-offset');
|
|
57
|
+
if (offset) return replayEditRecord(edit, offset.status, offset.range, [offset.reason]);
|
|
58
|
+
const symbol = findCurrentSymbol(edit, context.currentSymbols);
|
|
59
|
+
const spanRange = spanOffsets(context.currentSourceText, symbol?.sourceSpan);
|
|
60
|
+
const anchored = checkRange(edit, spanRange, context.currentSourceText, 'current-symbol-anchor');
|
|
61
|
+
if (anchored) return replayEditRecord(edit, anchored.status, anchored.range, [anchored.reason, 'offset-reanchored-by-symbol']);
|
|
62
|
+
return replayEditRecord(edit, symbol ? 'conflict' : 'stale', spanRange, [
|
|
63
|
+
symbol ? 'current-symbol-anchor-content-mismatch' : 'current-symbol-anchor-missing'
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkRange(edit, range, sourceText, label) {
|
|
68
|
+
if (!range || range.end < range.start) return undefined;
|
|
69
|
+
const current = sourceText.slice(range.start, range.end);
|
|
70
|
+
const currentHash = hashSemanticValue(current);
|
|
71
|
+
if (edit.deletedTextHash && currentHash === edit.deletedTextHash) return { status: 'applied', range, reason: `${label}-matches-deleted` };
|
|
72
|
+
if (edit.replacementTextHash && currentHash === edit.replacementTextHash) return { status: 'already-applied', range, reason: `${label}-matches-replacement` };
|
|
73
|
+
if (current === edit.replacementText) return { status: 'already-applied', range, reason: `${label}-matches-replacement-text` };
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function replayEditRecord(edit, status, range, reasonCodes) {
|
|
78
|
+
return compactRecord({
|
|
79
|
+
operationId: edit.operationId,
|
|
80
|
+
semanticKey: edit.semanticKey,
|
|
81
|
+
semanticIdentityHash: edit.semanticIdentityHash,
|
|
82
|
+
sourceIdentityHash: edit.sourceIdentityHash,
|
|
83
|
+
editContentHash: edit.editContentHash,
|
|
84
|
+
sourcePath: edit.targetSourcePath ?? edit.sourcePath,
|
|
85
|
+
symbolName: edit.targetSymbolName ?? edit.symbolName,
|
|
86
|
+
symbolKind: edit.targetSymbolKind ?? edit.symbolKind,
|
|
87
|
+
status,
|
|
88
|
+
start: range?.start,
|
|
89
|
+
end: range?.end,
|
|
90
|
+
replacementBytes: edit.replacementBytes,
|
|
91
|
+
replacementText: edit.replacementText,
|
|
92
|
+
reasonCodes: reasonList(reasonCodes)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function currentSymbolIndex(input) {
|
|
97
|
+
const imported = normalizeNativeDiffImport({
|
|
98
|
+
language: input.language,
|
|
99
|
+
sourcePath: input.sourcePath,
|
|
100
|
+
sourceText: input.currentSourceText,
|
|
101
|
+
parser: input.parser
|
|
102
|
+
}, input, 'current');
|
|
103
|
+
return [...mapDiffSymbols(imported, createSemanticImportSidecar(imported)).values()];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function findCurrentSymbol(edit, symbols) {
|
|
107
|
+
const exact = symbols.find((symbol) => [symbol.ownershipKey, symbol.key, symbol.id].some((key) => key && [
|
|
108
|
+
edit.anchorKey,
|
|
109
|
+
edit.targetAnchorKey,
|
|
110
|
+
edit.symbolId
|
|
111
|
+
].includes(key)));
|
|
112
|
+
if (exact) return exact;
|
|
113
|
+
const name = edit.targetSymbolName ?? edit.symbolName;
|
|
114
|
+
const kind = edit.targetSymbolKind ?? edit.symbolKind;
|
|
115
|
+
return symbols.find((symbol) => symbol.name === name && (!kind || symbol.kind === kind));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function replayStatus(reasonCodes, edits, projection) {
|
|
119
|
+
if (reasonCodes.some((reason) => reason !== 'current-source-hash-mismatch')) return 'blocked';
|
|
120
|
+
if (!edits.length && !(projection.edits ?? []).length) return 'evidence-only';
|
|
121
|
+
if (edits.some((edit) => edit.status === 'blocked')) return 'blocked';
|
|
122
|
+
if (edits.some((edit) => edit.status === 'conflict')) return 'conflict';
|
|
123
|
+
if (edits.some((edit) => edit.status === 'stale')) return 'stale';
|
|
124
|
+
if (edits.every((edit) => edit.status === 'already-applied')) return 'already-applied';
|
|
125
|
+
return edits.every((edit) => edit.status === 'applied' || edit.status === 'already-applied') ? 'accepted-clean' : 'needs-port';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function replayAdmission(status, reasonCodes, edits) {
|
|
129
|
+
const apply = status === 'accepted-clean';
|
|
130
|
+
return {
|
|
131
|
+
status,
|
|
132
|
+
action: apply ? 'apply' : status === 'already-applied' ? 'skip' : status === 'stale' ? 'rerun-semantic-import' : status === 'blocked' ? 'block' : 'human-review',
|
|
133
|
+
reviewRequired: !apply,
|
|
134
|
+
autoApplyCandidate: apply,
|
|
135
|
+
autoMergeClaim: false,
|
|
136
|
+
semanticEquivalenceClaim: false,
|
|
137
|
+
reasonCodes: reasonList([...reasonCodes, ...edits.flatMap((edit) => edit.reasonCodes ?? [])])
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function replaySummary(edits, reasonCodes) {
|
|
142
|
+
return {
|
|
143
|
+
edits: edits.length,
|
|
144
|
+
applied: edits.filter((edit) => edit.status === 'applied').length,
|
|
145
|
+
alreadyApplied: edits.filter((edit) => edit.status === 'already-applied').length,
|
|
146
|
+
conflicts: edits.filter((edit) => edit.status === 'conflict').length,
|
|
147
|
+
stale: edits.filter((edit) => edit.status === 'stale').length,
|
|
148
|
+
blocked: edits.filter((edit) => edit.status === 'blocked').length,
|
|
149
|
+
reasonCodes: reasonList([...reasonCodes, ...edits.flatMap((edit) => edit.reasonCodes ?? [])])
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function replayOutputSource(status, sourceText, edits) {
|
|
154
|
+
if (typeof sourceText !== 'string') return undefined;
|
|
155
|
+
if (status === 'already-applied') return sourceText;
|
|
156
|
+
if (status !== 'accepted-clean') return undefined;
|
|
157
|
+
return edits.filter((edit) => edit.status === 'applied')
|
|
158
|
+
.sort((left, right) => right.start - left.start)
|
|
159
|
+
.reduce((text, edit) => text.slice(0, edit.start) + editReplacement(edit, edits) + text.slice(edit.end), sourceText);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function editReplacement(edit, edits) {
|
|
163
|
+
return edits.find((candidate) => candidate.operationId === edit.operationId)?.replacementText ?? '';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function baseReasonCodes(projection, currentSourceText) {
|
|
167
|
+
return reasonList([
|
|
168
|
+
projection.status !== 'projected' ? 'projection-not-projected' : undefined,
|
|
169
|
+
projection.admission?.status !== 'auto-merge-candidate' ? 'projection-not-auto-merge-candidate' : undefined,
|
|
170
|
+
typeof currentSourceText !== 'string' ? 'missing-current-source-text' : undefined
|
|
171
|
+
]);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function spanOffsets(sourceText, span) {
|
|
175
|
+
if (typeof sourceText !== 'string' || !span) return undefined;
|
|
176
|
+
if (typeof span.start === 'number' && typeof span.end === 'number' && span.end >= span.start) return { start: span.start, end: span.end };
|
|
177
|
+
if (typeof span.startLine !== 'number') return undefined;
|
|
178
|
+
const starts = [0];
|
|
179
|
+
for (let index = 0; index < sourceText.length; index += 1) if (sourceText[index] === '\n') starts.push(index + 1);
|
|
180
|
+
const startLine = Math.max(1, span.startLine);
|
|
181
|
+
const endLine = Math.max(startLine, typeof span.endLine === 'number' ? span.endLine : startLine);
|
|
182
|
+
const lineStart = starts[startLine - 1];
|
|
183
|
+
const endLineStart = starts[endLine - 1];
|
|
184
|
+
if (lineStart === undefined || endLineStart === undefined) return undefined;
|
|
185
|
+
const lineEnd = starts[endLine] === undefined ? sourceText.length : starts[endLine] - 1;
|
|
186
|
+
return { start: lineStart + Math.max(0, (span.startColumn ?? 1) - 1), end: endLineStart + (span.endColumn === undefined ? lineEnd - endLineStart : Math.max(0, span.endColumn - 1)) };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isJavaScriptLike(language) { return language === 'javascript' || language === 'typescript'; }
|
|
190
|
+
function reasonList(values) { return uniqueStrings((values ?? []).filter(Boolean)); }
|
|
191
|
+
function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0))); }
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { normalizeSemanticMergeReadiness, uniqueStrings } from '../../native-import-utils.js';
|
|
2
|
+
|
|
3
|
+
export function createSemanticPatchBundleAdmission(input = {}, context = {}) {
|
|
4
|
+
const transformAdmission = semanticTransformAdmission(context);
|
|
5
|
+
const fallbackReadiness = transformAdmission.readiness === 'ready' ? 'ready' : context.readiness;
|
|
6
|
+
const readiness = normalizeSemanticMergeReadiness(input.readiness ?? fallbackReadiness) ?? input.readiness ?? fallbackReadiness;
|
|
7
|
+
const status = input.status ?? admissionStatusForReadiness(readiness, transformAdmission);
|
|
8
|
+
const autoApplyCandidate = input.autoApplyCandidate ?? (status === 'admitted' && transformAdmission.action === 'admit');
|
|
9
|
+
return compactRecord({
|
|
10
|
+
status,
|
|
11
|
+
readiness,
|
|
12
|
+
reviewRequired: input.reviewRequired ?? status !== 'admitted',
|
|
13
|
+
autoMergeClaim: false,
|
|
14
|
+
autoApplyCandidate,
|
|
15
|
+
transformAdmission,
|
|
16
|
+
reasonCodes: uniqueStrings([
|
|
17
|
+
...strings(input.reasonCodes),
|
|
18
|
+
...strings(context.source?.reasons),
|
|
19
|
+
...strings(context.mergeCandidate?.reasons),
|
|
20
|
+
...transformAdmission.reasonCodes
|
|
21
|
+
]),
|
|
22
|
+
conflictKeys: uniqueStrings([...strings(input.conflictKeys), ...context.conflictKeys]),
|
|
23
|
+
admittedAt: input.admittedAt,
|
|
24
|
+
reviewerId: input.reviewerId,
|
|
25
|
+
evidenceIds: uniqueStrings([...strings(input.evidenceIds), ...strings(transformAdmission.evidenceIds)]),
|
|
26
|
+
metadata: input.metadata
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function semanticTransformAdmission(context) {
|
|
31
|
+
const records = array(context.semanticTransformIdentities);
|
|
32
|
+
const index = context.semanticTransformIndex ?? {};
|
|
33
|
+
if (!records.length && !strings(index.semanticTransformIds).length) {
|
|
34
|
+
return { status: 'none', action: 'none', readiness: 'needs-review', reasonCodes: [] };
|
|
35
|
+
}
|
|
36
|
+
const readinesses = uniqueStrings([...strings(index.semanticTransformReadinesses), ...records.map((record) => record.readiness)]);
|
|
37
|
+
const normalizedReadinesses = uniqueStrings(readinesses.map(transformReadiness).filter(Boolean));
|
|
38
|
+
const blocked = normalizedReadinesses.includes('blocked');
|
|
39
|
+
const complete = strings(index.semanticTransformContentHashes).length > 0 &&
|
|
40
|
+
strings(index.projectionIdentityHashes).length > 0 &&
|
|
41
|
+
strings(index.transformSourceLanguages).length > 0 &&
|
|
42
|
+
strings(index.transformTargetLanguages).length > 0 &&
|
|
43
|
+
strings(index.transformSourcePaths).length > 0 &&
|
|
44
|
+
strings(index.transformTargetPaths).length > 0;
|
|
45
|
+
const ready = !blocked && complete && (normalizedReadinesses.length === 0 || normalizedReadinesses.every((entry) => entry === 'ready'));
|
|
46
|
+
const status = blocked ? 'blocked' : ready ? 'ready' : 'needs-review';
|
|
47
|
+
return compactRecord({
|
|
48
|
+
status,
|
|
49
|
+
action: blocked ? 'block' : ready ? 'admit' : 'review',
|
|
50
|
+
readiness: blocked ? 'blocked' : ready ? 'ready' : 'needs-review',
|
|
51
|
+
crossLanguage: hasCrossLanguageTransform(index),
|
|
52
|
+
reasonCodes: transformReasonCodes({ blocked, complete, ready, readinesses, index }),
|
|
53
|
+
transformIds: strings(index.semanticTransformIds),
|
|
54
|
+
transformKeys: strings(index.semanticTransformKeys),
|
|
55
|
+
contentHashes: strings(index.semanticTransformContentHashes),
|
|
56
|
+
projectionIdentityHashes: strings(index.projectionIdentityHashes),
|
|
57
|
+
sourceLanguages: strings(index.transformSourceLanguages),
|
|
58
|
+
targetLanguages: strings(index.transformTargetLanguages),
|
|
59
|
+
sourcePaths: strings(index.transformSourcePaths),
|
|
60
|
+
targetPaths: strings(index.transformTargetPaths),
|
|
61
|
+
evidenceIds: strings(index.semanticTransformEvidenceIds)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function transformReasonCodes(input) {
|
|
66
|
+
return uniqueStrings([
|
|
67
|
+
input.blocked ? 'transform-readiness-blocked' : undefined,
|
|
68
|
+
!input.complete ? 'transform-evidence-incomplete' : undefined,
|
|
69
|
+
input.ready ? 'transform-auto-apply-candidate' : undefined,
|
|
70
|
+
...input.readinesses.map((readiness) => `transform-readiness:${readiness}`)
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function transformReadiness(value) {
|
|
75
|
+
const normalized = normalizeSemanticMergeReadiness(value);
|
|
76
|
+
if (normalized) return normalized === 'ready-with-losses' ? 'needs-review' : normalized;
|
|
77
|
+
const status = String(value ?? '').toLowerCase();
|
|
78
|
+
if (['auto-merge-candidate', 'portable', 'projected', 'applied'].includes(status)) return 'ready';
|
|
79
|
+
if (['conflict', 'blocked', 'stale', 'rejected'].includes(status)) return 'blocked';
|
|
80
|
+
if (['needs-port', 'review', 'needs-review', 'candidate'].includes(status)) return 'needs-review';
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function admissionStatusForReadiness(readiness, transformAdmission) {
|
|
85
|
+
if (readiness === 'blocked') return 'blocked';
|
|
86
|
+
if (transformAdmission.action === 'admit' && readiness === 'ready') return 'admitted';
|
|
87
|
+
return readiness === 'needs-review' ? 'needs-review' : 'proposed';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function hasCrossLanguageTransform(index) {
|
|
91
|
+
const source = new Set(strings(index.transformSourceLanguages));
|
|
92
|
+
return strings(index.transformTargetLanguages).some((target) => !source.has(target));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function array(value) { if (value === undefined || value === null) return []; return Array.isArray(value) ? value : [value]; }
|
|
96
|
+
function strings(value) { return array(value).map((entry) => String(entry ?? '')).filter(Boolean); }
|
|
97
|
+
function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0))); }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import{idFragment,normalizeSemanticMergeReadiness,uniqueStrings}from'../../native-import-utils.js';
|
|
2
|
+
import{createSemanticPatchBundleAdmission}from'./semanticPatchBundleAdmission.js';
|
|
2
3
|
import{normalizeSemanticTransformIdentityRecords,semanticTransformInputs,semanticTransformRecordIndex,semanticTransformSummary}from'./semanticTransformIdentityRecords.js';
|
|
3
4
|
|
|
4
5
|
export const SemanticPatchBundleAdmissionStatuses=Object.freeze(['proposed','queued','admitted','needs-review','blocked','rejected']);
|
|
@@ -42,7 +43,7 @@ export function createSemanticPatchBundleRecord(input={},options={}){
|
|
|
42
43
|
...changedRegions.flatMap((region)=>[region.conflictKey,...array(region.admission?.conflictKeys)]),
|
|
43
44
|
...(source.metadata?.semanticMergeConflictSummary?.conflictKeys??[])
|
|
44
45
|
]);
|
|
45
|
-
const admission=
|
|
46
|
+
const admission=createSemanticPatchBundleAdmission(options.admission??source.admission,{readiness,conflictKeys,source,mergeCandidate,semanticTransformIndex,semanticTransformIdentities});
|
|
46
47
|
const id=options.id??(source.kind==='frontier.lang.semanticPatchBundleRecord'?source.id:undefined)
|
|
47
48
|
??`semantic_patch_bundle_${idFragment(firstString(source.id,patchId,mergeCandidateId,source.sourcePath,source.language,'record'))}`;
|
|
48
49
|
const language=options.language??source.language??mergeCandidate?.language??sources.find((item)=>item.language)?.language;
|
|
@@ -184,23 +185,6 @@ function normalizeSourceMapLinks(links){
|
|
|
184
185
|
return result;
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
function normalizeAdmission(input={},context){
|
|
188
|
-
const readiness=normalizeSemanticMergeReadiness(input.readiness??context.readiness)??input.readiness??context.readiness;
|
|
189
|
-
const status=input.status??admissionStatusForReadiness(readiness);
|
|
190
|
-
return compactRecord({
|
|
191
|
-
status,
|
|
192
|
-
readiness,
|
|
193
|
-
reviewRequired:input.reviewRequired??status!=='admitted',
|
|
194
|
-
autoMergeClaim:false,
|
|
195
|
-
reasonCodes:uniqueStrings([...strings(input.reasonCodes),...strings(context.source.reasons),...strings(context.mergeCandidate?.reasons)]),
|
|
196
|
-
conflictKeys:uniqueStrings([...strings(input.conflictKeys),...context.conflictKeys]),
|
|
197
|
-
admittedAt:input.admittedAt,
|
|
198
|
-
reviewerId:input.reviewerId,
|
|
199
|
-
evidenceIds:uniqueStrings(input.evidenceIds),
|
|
200
|
-
metadata:input.metadata
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
188
|
function recordIndex(parts){
|
|
205
189
|
const semanticEditIndex=parts.semanticEditIndex??semanticEditRecordIndex([],[]);
|
|
206
190
|
const semanticTransformIndex=parts.semanticTransformIndex??semanticTransformRecordIndex([],parts);
|
|
@@ -231,6 +215,8 @@ function recordIndex(parts){
|
|
|
231
215
|
semanticTransformIdentityHashes:semanticTransformIndex.semanticTransformIdentityHashes,
|
|
232
216
|
semanticTransformContentHashes:semanticTransformIndex.semanticTransformContentHashes,
|
|
233
217
|
projectionIdentityHashes:semanticTransformIndex.projectionIdentityHashes,
|
|
218
|
+
semanticTransformReadinesses:semanticTransformIndex.semanticTransformReadinesses,
|
|
219
|
+
semanticTransformEvidenceIds:semanticTransformIndex.semanticTransformEvidenceIds,
|
|
234
220
|
transformSourceLanguages:semanticTransformIndex.transformSourceLanguages,
|
|
235
221
|
transformTargetLanguages:semanticTransformIndex.transformTargetLanguages,
|
|
236
222
|
transformSourcePaths:semanticTransformIndex.transformSourcePaths,
|
|
@@ -273,6 +259,8 @@ function matchesRecord(record,query){
|
|
|
273
259
|
&&matchAny(queryValues(query.semanticTransformIdentityHash,query.semanticTransformIdentityHashes),index.semanticTransformIdentityHashes)
|
|
274
260
|
&&matchAny(queryValues(query.semanticTransformContentHash,query.semanticTransformContentHashes),index.semanticTransformContentHashes)
|
|
275
261
|
&&matchAny(queryValues(query.projectionIdentityHash,query.projectionIdentityHashes),index.projectionIdentityHashes)
|
|
262
|
+
&&matchAny(queryValues(query.semanticTransformReadiness,query.semanticTransformReadinesses),index.semanticTransformReadinesses)
|
|
263
|
+
&&matchAny(queryValues(query.semanticTransformEvidenceId,query.semanticTransformEvidenceIds),index.semanticTransformEvidenceIds)
|
|
276
264
|
&&matchAny(queryValues(query.transformSourceLanguage,query.transformSourceLanguages),index.transformSourceLanguages)
|
|
277
265
|
&&matchAny(queryValues(query.transformTargetLanguage,query.transformTargetLanguages),index.transformTargetLanguages)
|
|
278
266
|
&&matchAny(queryValues(query.transformSourcePath,query.transformSourcePaths),index.transformSourcePaths)
|
|
@@ -281,7 +269,6 @@ function matchesRecord(record,query){
|
|
|
281
269
|
&&matchAny(queryValues(query.admissionStatus,query.admissionStatuses),index.admissionStatuses);
|
|
282
270
|
}
|
|
283
271
|
|
|
284
|
-
function admissionStatusForReadiness(readiness){return readiness==='blocked'?'blocked':readiness==='needs-review'?'needs-review':'proposed';}
|
|
285
272
|
function semanticEditRecordIndex(scripts,projections,source={}){
|
|
286
273
|
const operations=scripts.flatMap((script)=>array(script.operations));
|
|
287
274
|
const edits=projections.flatMap((projection)=>array(projection.edits));
|
|
@@ -104,6 +104,8 @@ export function semanticTransformRecordIndex(records, source = {}) {
|
|
|
104
104
|
semanticTransformIdentityHashes: uniqueStrings([...strings(source.semanticTransformIdentityHashes), ...strings(index.semanticTransformIdentityHashes), ...records.map((record) => record.transformIdentityHash)]),
|
|
105
105
|
semanticTransformContentHashes: uniqueStrings([...strings(source.semanticTransformContentHashes), ...strings(index.semanticTransformContentHashes), ...records.map((record) => record.transformContentHash)]),
|
|
106
106
|
projectionIdentityHashes: uniqueStrings([...strings(source.projectionIdentityHashes), ...strings(index.projectionIdentityHashes), ...records.map((record) => record.projectionIdentityHash)]),
|
|
107
|
+
semanticTransformReadinesses: uniqueStrings([...strings(source.semanticTransformReadinesses), ...strings(index.semanticTransformReadinesses), ...records.map((record) => record.readiness)]),
|
|
108
|
+
semanticTransformEvidenceIds: uniqueStrings([...strings(source.semanticTransformEvidenceIds), ...strings(index.semanticTransformEvidenceIds), ...records.flatMap((record) => record.evidenceIds)]),
|
|
107
109
|
transformSourceLanguages: uniqueStrings([...strings(source.transformSourceLanguages), ...strings(index.transformSourceLanguages), ...records.map((record) => record.sourceLanguage)]),
|
|
108
110
|
transformTargetLanguages: uniqueStrings([...strings(source.transformTargetLanguages), ...strings(index.transformTargetLanguages), ...records.map((record) => record.targetLanguage)]),
|
|
109
111
|
transformSourcePaths: uniqueStrings([...strings(source.transformSourcePaths), ...strings(index.transformSourcePaths), ...records.map((record) => record.sourcePath)]),
|
|
@@ -119,6 +121,8 @@ export function semanticTransformSummary(index) {
|
|
|
119
121
|
identityHashes: index.semanticTransformIdentityHashes,
|
|
120
122
|
contentHashes: index.semanticTransformContentHashes,
|
|
121
123
|
projectionIdentityHashes: index.projectionIdentityHashes,
|
|
124
|
+
readinesses: index.semanticTransformReadinesses,
|
|
125
|
+
evidenceIds: index.semanticTransformEvidenceIds,
|
|
122
126
|
sourceLanguages: index.transformSourceLanguages,
|
|
123
127
|
targetLanguages: index.transformTargetLanguages,
|
|
124
128
|
targetPaths: index.transformTargetPaths
|
package/package.json
CHANGED