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

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
@@ -353,6 +353,27 @@ console.log(changeSet.mergeCandidate.readiness); // merge-admission classificati
353
353
 
354
354
  Use `diffNativeSourceImports` when the worker or runner already produced `importNativeSource` results. Changed regions include a `metadata.changedRegionProjection` envelope with before/after source hashes, source-map links, ownership keys, readiness, and `autoMergeClaim: false` so swarm admission tools can score or port patches without treating semantic metadata as proof. Body-only edits that the lightweight scanner cannot anchor to a symbol are still reported as file-level changed regions instead of being silently treated as safe.
355
355
 
356
+ Build a three-way semantic edit script when a coordinator has base, worker, and current-head source text. This is the first deterministic bridge from semantic sidecars to automatic merge admission:
357
+
358
+ ```js
359
+ import { createSemanticEditScript } from '@shapeshift-labs/frontier-lang-compiler';
360
+
361
+ const script = createSemanticEditScript({
362
+ language: 'typescript',
363
+ sourcePath: 'src/runtime.ts',
364
+ baseSourceText: 'export function step(v) { return v + 1; }\n',
365
+ workerSourceText: 'export function step(v) { return v + 2; }\n',
366
+ headSourceText: 'export function step(v) { return v + 1; }\n'
367
+ });
368
+
369
+ console.log(script.operations[0].kind); // "replaceBody"
370
+ console.log(script.operations[0].status); // "portable"
371
+ console.log(script.admission.status); // "auto-merge-candidate"
372
+ console.log(script.admission.autoMergeClaim); // false
373
+ ```
374
+
375
+ If the current head already changed the same semantic anchor, the script reports `conflict`; if the anchor moved or was renamed, it reports `needs-port` with a `reanchor` target; if no current head is supplied, it reports a review-only candidate. The script is intentionally an edit/admission record, not a proof of semantic equivalence.
376
+
356
377
  Bundle a native diff into a compact semantic patch record when a coordinator needs hashes, changed semantic regions, source-map links, proof/history/evidence IDs, and merge-admission status without loading whole source files:
357
378
 
358
379
  ```js
@@ -0,0 +1,143 @@
1
+ import type {
2
+ EvidenceRecord,
3
+ FrontierSourceLanguage,
4
+ SemanticMergeReadiness,
5
+ SourceSpan
6
+ } from '@shapeshift-labs/frontier-lang-kernel';
7
+ import type { ImportNativeSourceOptions, NativeSourceImportResult } from './import-adapter-core.js';
8
+
9
+ export type SemanticEditScriptOperationStatus =
10
+ | 'candidate'
11
+ | 'portable'
12
+ | 'already-applied'
13
+ | 'needs-port'
14
+ | 'conflict'
15
+ | 'stale'
16
+ | 'blocked';
17
+
18
+ export type SemanticEditScriptAdmissionStatus =
19
+ | 'auto-merge-candidate'
20
+ | 'needs-port'
21
+ | 'conflict'
22
+ | 'stale'
23
+ | 'blocked'
24
+ | 'evidence-only';
25
+
26
+ export interface SemanticEditScriptOperation {
27
+ readonly id: string;
28
+ readonly kind: string;
29
+ readonly changeKind?: string;
30
+ readonly anchor: {
31
+ readonly key?: string;
32
+ readonly conflictKey?: string;
33
+ readonly regionId?: string;
34
+ readonly regionKind?: string;
35
+ readonly granularity?: string;
36
+ readonly language?: FrontierSourceLanguage | string;
37
+ readonly sourcePath?: string;
38
+ readonly symbolId?: string;
39
+ readonly symbolName?: string;
40
+ readonly symbolKind?: string;
41
+ readonly sourceSpan?: SourceSpan;
42
+ };
43
+ readonly hashes?: {
44
+ readonly baseSourceHash?: string;
45
+ readonly workerSourceHash?: string;
46
+ readonly headSourceHash?: string;
47
+ readonly baseSpanHash?: string;
48
+ readonly workerSpanHash?: string;
49
+ readonly headSpanHash?: string;
50
+ readonly baseTextHash?: string;
51
+ readonly workerTextHash?: string;
52
+ readonly beforeSignatureHash?: string;
53
+ readonly afterSignatureHash?: string;
54
+ };
55
+ readonly status: SemanticEditScriptOperationStatus;
56
+ readonly reanchor?: {
57
+ readonly fromAnchorKey?: string;
58
+ readonly toAnchorKey?: string;
59
+ readonly lineageStatus?: string;
60
+ readonly traversedEventIds?: readonly string[];
61
+ };
62
+ readonly readiness: SemanticMergeReadiness | string;
63
+ readonly confidence: number;
64
+ readonly reasonCodes: readonly string[];
65
+ readonly evidenceIds?: readonly string[];
66
+ readonly metadata?: Record<string, unknown>;
67
+ }
68
+
69
+ export interface SemanticEditScriptSummary {
70
+ readonly operations: number;
71
+ readonly byStatus: Readonly<Record<string, number>>;
72
+ readonly byKind: Readonly<Record<string, number>>;
73
+ readonly portable: number;
74
+ readonly alreadyApplied: number;
75
+ readonly needsPort: number;
76
+ readonly conflicts: number;
77
+ readonly stale: number;
78
+ readonly blocked: number;
79
+ readonly candidates: number;
80
+ readonly autoMergeCandidates: number;
81
+ }
82
+
83
+ export interface SemanticEditScriptAdmission {
84
+ readonly status: SemanticEditScriptAdmissionStatus;
85
+ readonly action: 'run-gates-and-apply' | 'reanchor-or-human-port' | 'record-evidence' | 'block' | string;
86
+ readonly reviewRequired: boolean;
87
+ readonly autoApplyCandidate: boolean;
88
+ readonly autoMergeClaim: false;
89
+ readonly semanticEquivalenceClaim: false;
90
+ readonly reasonCodes: readonly string[];
91
+ readonly conflictKeys: readonly string[];
92
+ readonly evidenceIds: readonly string[];
93
+ }
94
+
95
+ export interface SemanticEditScript {
96
+ readonly kind: 'frontier.lang.semanticEditScript';
97
+ readonly version: 1;
98
+ readonly schema: 'frontier.lang.semanticEditScript.v1';
99
+ readonly id: string;
100
+ readonly stableId: string;
101
+ readonly hash: string;
102
+ readonly language?: FrontierSourceLanguage | string;
103
+ readonly sourcePath?: string;
104
+ readonly baseHash?: string;
105
+ readonly workerHash?: string;
106
+ readonly headHash?: string;
107
+ readonly workerChangeSetId: string;
108
+ readonly headChangeSetId?: string;
109
+ readonly lineageInferenceId?: string;
110
+ readonly operations: readonly SemanticEditScriptOperation[];
111
+ readonly summary: SemanticEditScriptSummary;
112
+ readonly admission: SemanticEditScriptAdmission;
113
+ readonly evidence: readonly EvidenceRecord[];
114
+ readonly metadata?: Record<string, unknown>;
115
+ }
116
+
117
+ export interface CreateSemanticEditScriptOptions {
118
+ readonly id?: string;
119
+ readonly language?: FrontierSourceLanguage | string;
120
+ readonly sourcePath?: string;
121
+ readonly parser?: string;
122
+ readonly base?: NativeSourceImportResult | ImportNativeSourceOptions;
123
+ readonly before?: NativeSourceImportResult | ImportNativeSourceOptions;
124
+ readonly worker?: NativeSourceImportResult | ImportNativeSourceOptions;
125
+ readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
126
+ readonly head?: NativeSourceImportResult | ImportNativeSourceOptions;
127
+ readonly current?: NativeSourceImportResult | ImportNativeSourceOptions;
128
+ readonly baseSourceText?: string;
129
+ readonly beforeSourceText?: string;
130
+ readonly workerSourceText?: string;
131
+ readonly afterSourceText?: string;
132
+ readonly headSourceText?: string;
133
+ readonly currentSourceText?: string;
134
+ readonly baseSourceHash?: string;
135
+ readonly workerSourceHash?: string;
136
+ readonly headSourceHash?: string;
137
+ readonly generatedAt?: number;
138
+ readonly evidenceId?: string;
139
+ readonly metadata?: Record<string, unknown>;
140
+ }
141
+
142
+ export declare const SemanticEditScriptAdmissionStatuses: readonly SemanticEditScriptAdmissionStatus[];
143
+ export declare function createSemanticEditScript(input?: CreateSemanticEditScriptOptions): SemanticEditScript;
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ export * from './declarations/source-preservation.js';
16
16
  export * from './declarations/semantic-sidecar-admission.js';
17
17
  export * from './declarations/semantic-merge-candidates.js';
18
18
  export * from './declarations/semantic-merge-conflicts.js';
19
+ export * from './declarations/semantic-edit-script.js';
19
20
  export * from './declarations/semantic-lineage.js';
20
21
  export * from './declarations/semantic-history.js';
21
22
  export * from './declarations/semantic-patch-bundle.js';
package/dist/index.js CHANGED
@@ -69,6 +69,7 @@ export { queryProjectionReadinessMatrix } from './internal/index-impl/queryProje
69
69
  export { createSemanticPatchBundleRecord, querySemanticPatchBundleRecords, SemanticPatchBundleAdmissionStatuses } from './internal/index-impl/semanticPatchBundleRecords.js';
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
+ export { createSemanticEditScript, SemanticEditScriptAdmissionStatuses } from './internal/index-impl/semanticEditScripts.js';
72
73
  export { queryUniversalConversionPlan } from './internal/index-impl/queryUniversalConversionPlan.js';
73
74
  export { createSemanticAnchor, createSemanticLineageEvent, createSemanticLineageMap, querySemanticLineageEvents, SemanticLineageEventKinds } from './internal/index-impl/semanticLineageRecords.js';
74
75
  export { resolveSemanticLineage, resolveSemanticLineageBatch, SemanticLineageResolutionStatuses } from './internal/index-impl/semanticLineageResolutionRecords.js';
@@ -0,0 +1,135 @@
1
+ import { uniqueStrings } from '../../native-import-utils.js';
2
+ import { resolveSemanticLineage } from './semanticLineageResolutionRecords.js';
3
+
4
+ export const SemanticEditScriptAdmissionStatuses = Object.freeze([
5
+ 'auto-merge-candidate',
6
+ 'needs-port',
7
+ 'conflict',
8
+ 'stale',
9
+ 'blocked',
10
+ 'evidence-only'
11
+ ]);
12
+
13
+ export function classifySemanticEdit(input) {
14
+ if (!input.context.head) return editStatus('candidate', 'needs-review', 0.66, ['head-source-not-provided']);
15
+ if (!input.anchorKey) return editStatus('blocked', 'blocked', 0, ['missing-semantic-anchor']);
16
+ if (input.context.workerChangeSet.beforeHash &&
17
+ input.context.headChangeSet?.afterHash === input.context.workerChangeSet.beforeHash &&
18
+ sameSourcePath(input.context.base, input.context.head)) {
19
+ return editStatus('portable', 'ready', 0.95, ['head-source-matches-base']);
20
+ }
21
+ if (input.region.changeKind === 'added') return classifyAddedRegion(input);
22
+ if (!input.baseSymbol) return classifyMissingBaseAnchor(input);
23
+ if (!input.headSymbol) return classifyMissingHeadAnchor(input);
24
+ if (input.headSymbol.spanHash && input.baseSymbol.spanHash && input.headSymbol.spanHash === input.baseSymbol.spanHash) {
25
+ return editStatus('portable', 'ready', 0.9, ['head-anchor-matches-base']);
26
+ }
27
+ if (input.headSymbol.spanHash && input.workerSymbol?.spanHash && input.headSymbol.spanHash === input.workerSymbol.spanHash) {
28
+ return editStatus('already-applied', 'ready', 0.92, ['head-anchor-matches-worker']);
29
+ }
30
+ return editStatus('conflict', 'blocked', 0.2, ['head-anchor-changed-since-base']);
31
+ }
32
+
33
+ export function summarizeSemanticEditOperations(operations) {
34
+ const byStatus = countBy(operations.map((operation) => operation.status));
35
+ const byKind = countBy(operations.map((operation) => operation.kind));
36
+ return {
37
+ operations: operations.length,
38
+ byStatus,
39
+ byKind,
40
+ portable: byStatus.portable ?? 0,
41
+ alreadyApplied: byStatus['already-applied'] ?? 0,
42
+ needsPort: byStatus['needs-port'] ?? 0,
43
+ conflicts: byStatus.conflict ?? 0,
44
+ stale: byStatus.stale ?? 0,
45
+ blocked: byStatus.blocked ?? 0,
46
+ candidates: byStatus.candidate ?? 0,
47
+ autoMergeCandidates: (byStatus.portable ?? 0) + (byStatus['already-applied'] ?? 0)
48
+ };
49
+ }
50
+
51
+ export function semanticEditAdmission(input) {
52
+ const status = admissionStatus(input.summary);
53
+ return {
54
+ status,
55
+ action: admissionAction(status),
56
+ reviewRequired: status !== 'auto-merge-candidate',
57
+ autoApplyCandidate: status === 'auto-merge-candidate',
58
+ autoMergeClaim: false,
59
+ semanticEquivalenceClaim: false,
60
+ reasonCodes: uniqueStrings([
61
+ ...input.workerChangeSet.reasons,
62
+ ...(input.headChangeSet?.reasons ?? []),
63
+ ...input.operations.flatMap((operation) => operation.reasonCodes ?? [])
64
+ ]),
65
+ conflictKeys: uniqueStrings(input.operations.map((operation) => operation.anchor?.conflictKey).filter(Boolean)),
66
+ evidenceIds: uniqueStrings(input.operations.flatMap((operation) => operation.evidenceIds ?? []))
67
+ };
68
+ }
69
+
70
+ function classifyAddedRegion(input) {
71
+ if (!input.headSymbol) return editStatus('portable', 'ready', 0.86, ['added-anchor-absent-from-head']);
72
+ if (input.headSymbol.spanHash && input.workerSymbol?.spanHash && input.headSymbol.spanHash === input.workerSymbol.spanHash) {
73
+ return editStatus('already-applied', 'ready', 0.88, ['added-anchor-already-present-in-head']);
74
+ }
75
+ return editStatus('conflict', 'blocked', 0.25, ['added-anchor-already-exists-in-head']);
76
+ }
77
+
78
+ function classifyMissingBaseAnchor(input) {
79
+ const conflictKey = input.region.conflictKey ?? `region:${input.anchorKey}`;
80
+ if (input.context.headChangedConflictKeys.has(conflictKey)) {
81
+ return editStatus('conflict', 'blocked', 0.18, ['base-anchor-missing-and-head-changed-same-region']);
82
+ }
83
+ return editStatus('stale', 'needs-review', 0.25, ['base-anchor-missing']);
84
+ }
85
+
86
+ function classifyMissingHeadAnchor(input) {
87
+ const resolved = input.context.headLineage
88
+ ? resolveSemanticLineage(input.context.headLineage.lineageMap, { anchorKey: input.anchorKey })
89
+ : undefined;
90
+ if (resolved?.status === 'resolved' && resolved.currentAnchors.length === 1) {
91
+ return editStatus('needs-port', 'needs-review', 0.72, ['anchor-moved-or-renamed'], {
92
+ fromAnchorKey: input.anchorKey,
93
+ toAnchorKey: resolved.currentAnchors[0].key,
94
+ lineageStatus: resolved.status,
95
+ traversedEventIds: resolved.traversedEventIds
96
+ }, resolved.evidenceIds);
97
+ }
98
+ if (resolved?.status === 'ambiguous') {
99
+ return editStatus('blocked', 'blocked', 0.1, ['anchor-lineage-ambiguous'], { fromAnchorKey: input.anchorKey, lineageStatus: resolved.status }, resolved.evidenceIds);
100
+ }
101
+ if (resolved?.status === 'deleted') {
102
+ return editStatus('blocked', 'blocked', 0.1, ['anchor-deleted-in-head'], { fromAnchorKey: input.anchorKey, lineageStatus: resolved.status }, resolved.evidenceIds);
103
+ }
104
+ return editStatus('stale', 'needs-review', 0.25, ['head-anchor-missing']);
105
+ }
106
+
107
+ function editStatus(status, readiness, confidence, reasonCodes, reanchor, evidenceIds = []) {
108
+ return { status, readiness, confidence, reasonCodes, reanchor, evidenceIds };
109
+ }
110
+
111
+ function admissionStatus(summary) {
112
+ if (summary.operations === 0) return 'evidence-only';
113
+ if (summary.blocked > 0) return 'blocked';
114
+ if (summary.conflicts > 0) return 'conflict';
115
+ if (summary.stale > 0) return 'stale';
116
+ if (summary.needsPort > 0 || summary.candidates > 0) return 'needs-port';
117
+ return 'auto-merge-candidate';
118
+ }
119
+
120
+ function admissionAction(status) {
121
+ if (status === 'auto-merge-candidate') return 'run-gates-and-apply';
122
+ if (status === 'needs-port') return 'reanchor-or-human-port';
123
+ if (status === 'evidence-only') return 'record-evidence';
124
+ return 'block';
125
+ }
126
+
127
+ function countBy(values) {
128
+ const result = {};
129
+ for (const value of values.filter(Boolean)) result[value] = (result[value] ?? 0) + 1;
130
+ return result;
131
+ }
132
+
133
+ function sameSourcePath(left, right) {
134
+ return (left?.sourcePath ?? left?.nativeSource?.sourcePath) === (right?.sourcePath ?? right?.nativeSource?.sourcePath);
135
+ }
@@ -0,0 +1,220 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { idFragment, uniqueStrings } from '../../native-import-utils.js';
3
+ import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
4
+ import { diffNativeSourceImports } from './diffNativeSourceImports.js';
5
+ import { inferSemanticLineageEvents } from './inferSemanticLineageEvents.js';
6
+ import { mapDiffSymbols } from './mapDiffSymbols.js';
7
+ import { nativeImportSourceText } from './nativeImportSourceText.js';
8
+ import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
9
+ import {
10
+ classifySemanticEdit,
11
+ SemanticEditScriptAdmissionStatuses,
12
+ semanticEditAdmission,
13
+ summarizeSemanticEditOperations
14
+ } from './semanticEditScriptClassification.js';
15
+ import { sourceTextForSpan } from './sourceTextForSpan.js';
16
+
17
+ export { SemanticEditScriptAdmissionStatuses };
18
+
19
+ export function createSemanticEditScript(input = {}, options = {}) {
20
+ const language = input.language ?? input.worker?.language ?? input.after?.language ?? input.base?.language ?? input.before?.language;
21
+ if (!language) throw new Error('createSemanticEditScript requires a language');
22
+ const sourcePath = input.sourcePath ?? input.worker?.sourcePath ?? input.after?.sourcePath ?? input.base?.sourcePath ?? input.before?.sourcePath;
23
+ const base = normalizeNativeDiffImport(sourceInput(input, 'base'), { ...input, language, sourcePath }, 'base');
24
+ const worker = normalizeNativeDiffImport(sourceInput(input, 'worker'), { ...input, language, sourcePath }, 'worker');
25
+ if (!base || !worker) throw new Error('createSemanticEditScript requires base and worker source inputs');
26
+ const head = normalizeNativeDiffImport(sourceInput(input, 'head'), { ...input, language, sourcePath }, 'head');
27
+ const workerChangeSet = diffNativeSourceImports({
28
+ ...input,
29
+ language,
30
+ sourcePath,
31
+ before: base,
32
+ after: worker,
33
+ id: input.workerChangeSetId ?? `${input.id ?? 'semantic_edit'}_worker`
34
+ });
35
+ const headChangeSet = head ? diffNativeSourceImports({
36
+ ...input,
37
+ language,
38
+ sourcePath,
39
+ before: base,
40
+ after: head,
41
+ id: input.headChangeSetId ?? `${input.id ?? 'semantic_edit'}_head`
42
+ }) : undefined;
43
+ const headLineage = head ? inferSemanticLineageEvents({
44
+ before: base,
45
+ after: head,
46
+ id: input.lineageInferenceId ?? `${input.id ?? 'semantic_edit'}_head_lineage`,
47
+ language,
48
+ sourcePath,
49
+ generatedAt: input.generatedAt,
50
+ metadata: { source: 'createSemanticEditScript' }
51
+ }) : undefined;
52
+ const context = createEditContext({ base, worker, head, workerChangeSet, headChangeSet, headLineage });
53
+ const operations = workerChangeSet.changedRegions.map((region, index) => semanticEditOperation(region, index, context, input));
54
+ const summary = summarizeSemanticEditOperations(operations);
55
+ const admission = semanticEditAdmission({ operations, summary, head, workerChangeSet, headChangeSet, input });
56
+ const evidence = semanticEditEvidence({ input, language, sourcePath, workerChangeSet, headChangeSet, headLineage, summary, admission });
57
+ const core = {
58
+ kind: 'frontier.lang.semanticEditScript',
59
+ version: 1,
60
+ schema: 'frontier.lang.semanticEditScript.v1',
61
+ language,
62
+ sourcePath,
63
+ baseHash: workerChangeSet.beforeHash,
64
+ workerHash: workerChangeSet.afterHash,
65
+ headHash: headChangeSet?.afterHash,
66
+ workerChangeSetId: workerChangeSet.id,
67
+ headChangeSetId: headChangeSet?.id,
68
+ lineageInferenceId: headLineage?.id,
69
+ operations,
70
+ summary,
71
+ admission,
72
+ evidence,
73
+ metadata: compactRecord({
74
+ autoMergeClaim: false,
75
+ semanticEquivalenceClaim: false,
76
+ workerReadiness: workerChangeSet.readiness,
77
+ headReadiness: headChangeSet?.readiness,
78
+ workerReasons: workerChangeSet.reasons,
79
+ headReasons: headChangeSet?.reasons,
80
+ ...input.metadata,
81
+ ...options.metadata
82
+ })
83
+ };
84
+ const hash = hashSemanticValue(core);
85
+ const id = input.id ?? `semantic_edit_script_${idFragment(firstString(sourcePath, language, hash))}`;
86
+ return { ...core, id, stableId: `semantic_edit_script_${idFragment(hash)}`, hash };
87
+ }
88
+
89
+ function sourceInput(input, side) {
90
+ if (side === 'base') return input.base ?? input.before ?? sourceTextInput(input, 'base') ?? sourceTextInput(input, 'before');
91
+ if (side === 'worker') return input.worker ?? input.after ?? sourceTextInput(input, 'worker') ?? sourceTextInput(input, 'after');
92
+ return input.head ?? input.current ?? sourceTextInput(input, 'head') ?? sourceTextInput(input, 'current');
93
+ }
94
+
95
+ function sourceTextInput(input, side) {
96
+ const sourceText = input[`${side}SourceText`];
97
+ if (sourceText === undefined) return undefined;
98
+ return {
99
+ language: input.language,
100
+ sourcePath: input[`${side}SourcePath`] ?? input.sourcePath,
101
+ sourceText,
102
+ sourceHash: input[`${side}SourceHash`],
103
+ parser: input.parser,
104
+ metadata: input[`${side}Metadata`]
105
+ };
106
+ }
107
+
108
+ function createEditContext(input) {
109
+ return {
110
+ ...input,
111
+ baseSymbols: symbolsByAnchor(input.base),
112
+ workerSymbols: symbolsByAnchor(input.worker),
113
+ headSymbols: symbolsByAnchor(input.head),
114
+ headChangedConflictKeys: new Set(input.headChangeSet?.conflictKeys ?? [])
115
+ };
116
+ }
117
+
118
+ function symbolsByAnchor(imported) {
119
+ if (!imported) return new Map();
120
+ const sidecar = createSemanticImportSidecar(imported, { id: `semantic_edit_sidecar_${idFragment(imported.id ?? imported.sourcePath ?? 'source')}` });
121
+ const result = new Map();
122
+ for (const symbol of mapDiffSymbols(imported, sidecar).values()) {
123
+ for (const key of uniqueStrings([symbol.ownershipKey, symbol.key, symbol.id])) {
124
+ result.set(key, symbol);
125
+ }
126
+ }
127
+ return result;
128
+ }
129
+
130
+ function semanticEditOperation(region, index, context, input) {
131
+ const anchorKey = region.key ?? region.conflictKey ?? region.id;
132
+ const baseSymbol = context.baseSymbols.get(anchorKey);
133
+ const workerSymbol = context.workerSymbols.get(anchorKey);
134
+ const headSymbol = context.headSymbols.get(anchorKey);
135
+ const classification = classifySemanticEdit({ region, anchorKey, baseSymbol, workerSymbol, headSymbol, context });
136
+ const kind = semanticEditOperationKind(region);
137
+ const baseText = spanText(context.base, baseSymbol?.sourceSpan ?? region.metadata?.changedRegionProjection?.before?.sourceSpan ?? region.sourceSpan);
138
+ const workerText = spanText(context.worker, workerSymbol?.sourceSpan ?? region.metadata?.changedRegionProjection?.after?.sourceSpan ?? region.sourceSpan);
139
+ return compactRecord({
140
+ id: `semantic_edit_op_${idFragment(firstString(input.id, anchorKey, index))}`,
141
+ kind,
142
+ changeKind: region.changeKind,
143
+ anchor: compactRecord({
144
+ key: anchorKey,
145
+ conflictKey: region.conflictKey ?? `region:${anchorKey}`,
146
+ regionId: region.id,
147
+ regionKind: region.regionKind,
148
+ granularity: region.granularity,
149
+ language: region.language ?? context.workerChangeSet.language,
150
+ sourcePath: region.sourcePath ?? context.workerChangeSet.sourcePath,
151
+ symbolId: region.symbolId ?? workerSymbol?.id ?? baseSymbol?.id,
152
+ symbolName: region.symbolName ?? workerSymbol?.name ?? baseSymbol?.name,
153
+ symbolKind: region.symbolKind ?? workerSymbol?.kind ?? baseSymbol?.kind,
154
+ sourceSpan: workerSymbol?.sourceSpan ?? region.sourceSpan
155
+ }),
156
+ hashes: compactRecord({
157
+ baseSourceHash: context.workerChangeSet.beforeHash,
158
+ workerSourceHash: context.workerChangeSet.afterHash,
159
+ headSourceHash: context.headChangeSet?.afterHash,
160
+ baseSpanHash: baseSymbol?.spanHash,
161
+ workerSpanHash: workerSymbol?.spanHash,
162
+ headSpanHash: headSymbol?.spanHash,
163
+ baseTextHash: baseText === undefined ? undefined : hashSemanticValue(baseText),
164
+ workerTextHash: workerText === undefined ? undefined : hashSemanticValue(workerText),
165
+ beforeSignatureHash: workerSymbol?.beforeSignatureHash ?? baseSymbol?.signatureHash,
166
+ afterSignatureHash: workerSymbol?.afterSignatureHash ?? workerSymbol?.signatureHash
167
+ }),
168
+ status: classification.status,
169
+ reanchor: classification.reanchor,
170
+ readiness: classification.readiness,
171
+ confidence: classification.confidence,
172
+ reasonCodes: classification.reasonCodes,
173
+ evidenceIds: classification.evidenceIds,
174
+ metadata: {
175
+ autoMergeClaim: false,
176
+ semanticEquivalenceClaim: false
177
+ }
178
+ });
179
+ }
180
+
181
+ function semanticEditOperationKind(region) {
182
+ const prefix = region.changeKind === 'added' ? 'add' : region.changeKind === 'removed' ? 'remove' : 'replace';
183
+ const kind = String(region.regionKind ?? region.granularity ?? 'region');
184
+ if (kind === 'body') return `${prefix}Body`;
185
+ if (kind === 'import') return `${prefix}Import`;
186
+ if (kind === 'type') return `${prefix}TypeDeclaration`;
187
+ if (kind === 'property') return `${prefix}Property`;
188
+ return `${prefix}Region`;
189
+ }
190
+
191
+ function spanText(imported, span) {
192
+ return sourceTextForSpan(nativeImportSourceText(imported), span);
193
+ }
194
+
195
+ function semanticEditEvidence(input) {
196
+ return [{
197
+ id: input.input.evidenceId ?? `evidence_${idFragment(input.input.id ?? input.sourcePath ?? 'semantic_edit')}_semantic_edit_script`,
198
+ kind: 'semantic-edit-script',
199
+ status: input.admission.status === 'blocked' || input.admission.status === 'conflict' ? 'needs-review' : 'passed',
200
+ path: input.sourcePath,
201
+ summary: `Created semantic edit script with ${input.summary.operations} operation(s): ${input.admission.status}.`,
202
+ metadata: {
203
+ workerChangeSetId: input.workerChangeSet.id,
204
+ headChangeSetId: input.headChangeSet?.id,
205
+ lineageInferenceId: input.headLineage?.id,
206
+ summary: input.summary,
207
+ admissionStatus: input.admission.status,
208
+ autoMergeClaim: false,
209
+ semanticEquivalenceClaim: false
210
+ }
211
+ }];
212
+ }
213
+
214
+ function firstString(...values) {
215
+ return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean);
216
+ }
217
+
218
+ function compactRecord(value) {
219
+ return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
220
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.79",
3
+ "version": "0.2.80",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",