@shapeshift-labs/frontier-lang-compiler 0.2.76 → 0.2.78

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
@@ -422,6 +422,22 @@ console.log(targetChange.metadata.semanticEquivalenceClaim); // false
422
422
 
423
423
  Bidirectional target-change records are merge-admission evidence, not transpiler proof. They keep target diffs, source anchor matches, optional lineage resolutions, source patch-bundle records, semantic history records, evidence IDs, readiness, and reason codes. They always keep `autoMergeClaim: false` and `semanticEquivalenceClaim: false`; unmatched or deleted anchors block the source-port route, while matched and ambiguous anchors still require human or verifier review.
424
424
 
425
+ When the target source came from a Frontier/native projection, pass the generated-output `sourceMaps` back into `createBidirectionalTargetChangeRecord`. The record will match target changed regions by generated span or generated name, emit `sourceMapLinks`, include `sourceMapBackedMatches` in the summary/evidence, and carry source-map mapping IDs into the source patch bundle index:
426
+
427
+ ```js
428
+ const sourceMapBacked = createBidirectionalTargetChangeRecord({
429
+ source,
430
+ targetLanguage: 'rust',
431
+ targetPath: 'src/counter.rs',
432
+ baseTarget,
433
+ editedTarget,
434
+ sourceMaps: [projection.sourceMap]
435
+ });
436
+
437
+ console.log(sourceMapBacked.summary.sourceMapBackedMatches);
438
+ console.log(sourceMapBacked.sourcePatchBundle.index.sourceMapMappingIds);
439
+ ```
440
+
425
441
  Store worker outputs as compact semantic history records when a coordinator needs to compare distributed changes without merging whole files:
426
442
 
427
443
  ```js
@@ -487,6 +503,38 @@ console.log(resolution.currentAnchors[0].key); // current semantic anchor
487
503
  console.log(resolution.traversedEventIds); // compact history path
488
504
  ```
489
505
 
506
+ Infer conservative lineage events from two native imports when a file was moved,
507
+ a parser reports a rename, or an anchor disappeared:
508
+
509
+ ```js
510
+ import {
511
+ importNativeSource,
512
+ inferSemanticLineageEvents
513
+ } from '@shapeshift-labs/frontier-lang-compiler';
514
+
515
+ const before = importNativeSource({
516
+ language: 'typescript',
517
+ sourcePath: 'src/runtime.ts',
518
+ sourceText: 'export function step(value) { return value + 1; }\n'
519
+ });
520
+ const after = importNativeSource({
521
+ language: 'typescript',
522
+ sourcePath: 'src/runtime-core.ts',
523
+ sourceText: 'export function step(value) { return value + 1; }\n'
524
+ });
525
+
526
+ const inference = inferSemanticLineageEvents({ before, after });
527
+
528
+ console.log(inference.summary.moved); // 1
529
+ console.log(inference.events[0].metadata.autoMergeClaim); // false
530
+ console.log(inference.lineageMap.byAnchorKey); // old/current anchor index
531
+ ```
532
+
533
+ The inference API is intentionally conservative. Ambiguous matches are reported
534
+ as blocked, additions are kept separate from recreated lineage, and inferred
535
+ events always require review; richer parser adapters can improve confidence by
536
+ supplying stable signature hashes and source-map spans.
537
+
490
538
  Resolver output is merge-admission evidence: it helps a swarm compare old worker bundles against current semantic anchors after code moved, split, or was deleted. It is not proof that the projected code is correct or semantically equivalent; admission still needs tests, source evidence, review status, and conflict scoring.
491
539
 
492
540
  Extract a surgical semantic slice when a worker only needs one symbol, region, native AST node, or source path:
package/bench/smoke.mjs CHANGED
@@ -110,8 +110,13 @@ console.log(JSON.stringify({
110
110
  changedRegionProjections: sourceChangeMetrics.changedRegionProjections,
111
111
  changedRegionProjectionSourceMapLinks: sourceChangeMetrics.changedRegionProjectionSourceMapLinks,
112
112
  changeProjectionDurationMs: Number(sourceChangeMetrics.changeProjectionDurationMs.toFixed(2)),
113
+ semanticLineageInferences: sourceChangeMetrics.semanticLineageInferences,
114
+ semanticLineageEvents: sourceChangeMetrics.semanticLineageEvents,
115
+ semanticLineageDeleted: sourceChangeMetrics.semanticLineageDeleted,
116
+ semanticLineageInferenceDurationMs: Number(sourceChangeMetrics.semanticLineageInferenceDurationMs.toFixed(2)),
113
117
  bidirectionalTargetChanges: sourceChangeMetrics.bidirectionalTargetChanges,
114
118
  bidirectionalTargetChangeMatches: sourceChangeMetrics.bidirectionalTargetChangeMatches,
119
+ bidirectionalTargetChangeSourceMapBacked: sourceChangeMetrics.bidirectionalTargetChangeSourceMapBacked,
115
120
  bidirectionalTargetChangeBlocked: sourceChangeMetrics.bidirectionalTargetChangeBlocked,
116
121
  bidirectionalTargetChangeDurationMs: Number(sourceChangeMetrics.bidirectionalTargetChangeDurationMs.toFixed(2)),
117
122
  externalSemanticImports: sourceChangeMetrics.externalSemanticImports,
@@ -4,6 +4,7 @@ import {
4
4
  createSemanticImportSidecar,
5
5
  createSemanticLineageEvent,
6
6
  diffNativeSources,
7
+ inferSemanticLineageEvents,
7
8
  importExternalSemanticIndex,
8
9
  importNativeSource
9
10
  } from '../dist/index.js';
@@ -12,6 +13,7 @@ export function measureSourceChangeSuites() {
12
13
  return {
13
14
  ...measureRegionScan(),
14
15
  ...measureChangeProjection(),
16
+ ...measureSemanticLineageInference(),
15
17
  ...measureBidirectionalTargetChanges(),
16
18
  ...measureExternalSemanticImports()
17
19
  };
@@ -75,6 +77,34 @@ function measureChangeProjection() {
75
77
  };
76
78
  }
77
79
 
80
+ function measureSemanticLineageInference() {
81
+ const start = performance.now();
82
+ const inferences = [];
83
+ for (let index = 0; index < 80; index += 1) {
84
+ inferences.push(inferSemanticLineageEvents({
85
+ id: `bench_semantic_lineage_inference_${index}`,
86
+ before: {
87
+ language: 'typescript',
88
+ sourcePath: `src/lineage-before-${index}.ts`,
89
+ sourceText: `export function lineageBench${index}(value) { return value + ${index}; }\n`
90
+ },
91
+ after: {
92
+ language: 'typescript',
93
+ sourcePath: index % 5 === 0 ? `src/lineage-before-${index}.ts` : `src/lineage-after-${index}.ts`,
94
+ sourceText: index % 6 === 0
95
+ ? `export const lineageBenchDeleted${index} = true;\n`
96
+ : `export function lineageBench${index}(value) { return value + ${index}; }\n`
97
+ }
98
+ }));
99
+ }
100
+ return {
101
+ semanticLineageInferences: inferences.length,
102
+ semanticLineageEvents: inferences.reduce((sum, inference) => sum + inference.events.length, 0),
103
+ semanticLineageDeleted: inferences.reduce((sum, inference) => sum + inference.summary.deleted, 0),
104
+ semanticLineageInferenceDurationMs: performance.now() - start
105
+ };
106
+ }
107
+
78
108
  function measureBidirectionalTargetChanges() {
79
109
  const start = performance.now();
80
110
  const source = importNativeSource({
@@ -82,6 +112,8 @@ function measureBidirectionalTargetChanges() {
82
112
  sourcePath: 'src/bidirectional-source.ts',
83
113
  sourceText: 'export function advance(frame: number): number { return frame + 1; }\n'
84
114
  });
115
+ const sourceSymbol = source.semanticIndex.symbols.find((symbol) => symbol.name === 'advance');
116
+ const sourceMapping = source.sourceMaps[0].mappings.find((mapping) => mapping.semanticSymbolId === sourceSymbol.id);
85
117
  const lineage = [createSemanticLineageEvent({
86
118
  eventKind: 'moved',
87
119
  from: { key: 'source#src/bidirectional-source.ts#body#advance', symbolName: 'advance' },
@@ -104,18 +136,45 @@ function measureBidirectionalTargetChanges() {
104
136
  sourcePath: `src/bidirectional-${index}.rs`,
105
137
  sourceText: `pub fn advance(frame: i32, delta: i32) -> i32 { frame + delta + ${index} }\n`
106
138
  },
107
- sourceAnchorMappings: [{ targetSymbolName: 'advance', sourceSymbolName: 'advance' }],
139
+ ...(index % 2 === 0
140
+ ? { sourceMaps: [rustSourceMap(source, sourceSymbol, sourceMapping, index)] }
141
+ : { sourceAnchorMappings: [{ targetSymbolName: 'advance', sourceSymbolName: 'advance' }] }),
108
142
  lineage: index % 4 === 0 ? lineage : []
109
143
  }));
110
144
  }
111
145
  return {
112
146
  bidirectionalTargetChanges: records.length,
113
147
  bidirectionalTargetChangeMatches: records.reduce((sum, record) => sum + record.summary.sourceAnchorMatches, 0),
148
+ bidirectionalTargetChangeSourceMapBacked: records.reduce((sum, record) => sum + record.summary.sourceMapBackedMatches, 0),
114
149
  bidirectionalTargetChangeBlocked: records.filter((record) => record.readiness === 'blocked').length,
115
150
  bidirectionalTargetChangeDurationMs: performance.now() - start
116
151
  };
117
152
  }
118
153
 
154
+ function rustSourceMap(source, sourceSymbol, sourceMapping, index) {
155
+ const targetPath = `src/bidirectional-${index}.rs`;
156
+ return {
157
+ kind: 'frontier.lang.sourceMap',
158
+ version: 1,
159
+ id: `bench_source_map_bidirectional_${index}`,
160
+ sourcePath: source.sourcePath,
161
+ sourceHash: source.nativeSource.sourceHash,
162
+ target: 'rust',
163
+ targetPath,
164
+ mappings: [{
165
+ id: `bench_map_advance_${index}`,
166
+ semanticSymbolId: sourceSymbol.id,
167
+ nativeAstNodeId: sourceSymbol.nativeAstNodeId,
168
+ sourceSpan: sourceMapping.sourceSpan,
169
+ generatedSpan: { path: targetPath, target: 'rust', targetPath, startLine: 1, startColumn: 1, endLine: 1, endColumn: 80, generatedName: 'advance' },
170
+ target: 'rust',
171
+ generatedName: 'advance',
172
+ precision: 'declaration',
173
+ preservation: 'declaration'
174
+ }]
175
+ };
176
+ }
177
+
119
178
  function measureExternalSemanticImports() {
120
179
  const externalSemanticStart = performance.now();
121
180
  const externalSemanticImports = [];
@@ -2,6 +2,8 @@ import type {
2
2
  EvidenceRecord,
3
3
  FrontierSourceLanguage,
4
4
  SemanticMergeReadiness,
5
+ SourceMapMappingRecord,
6
+ SourceMapRecord,
5
7
  SourceSpan
6
8
  } from '@shapeshift-labs/frontier-lang-kernel';
7
9
  import type { ImportNativeSourceOptions, NativeSourceImportResult } from './import-adapter-core.js';
@@ -27,6 +29,26 @@ export interface BidirectionalTargetChangeSourceAnchorMapping {
27
29
  readonly sourceSymbolId?: string;
28
30
  }
29
31
 
32
+ export interface BidirectionalTargetChangeSourceMapLink {
33
+ readonly id: string;
34
+ readonly sourceMapId?: string;
35
+ readonly sourceMapMappingId?: string;
36
+ readonly sourcePath?: string;
37
+ readonly sourceHash?: string;
38
+ readonly targetPath?: string;
39
+ readonly targetHash?: string;
40
+ readonly semanticSymbolId?: string;
41
+ readonly semanticOccurrenceId?: string;
42
+ readonly semanticNodeId?: string;
43
+ readonly nativeSourceId?: string;
44
+ readonly nativeAstNodeId?: string;
45
+ readonly precision?: string;
46
+ readonly sourceSpan?: SourceSpan;
47
+ readonly generatedSpan?: SourceMapMappingRecord['generatedSpan'];
48
+ readonly regionKey?: string;
49
+ readonly regionKind?: string;
50
+ }
51
+
30
52
  export interface BidirectionalTargetChangeAnchor {
31
53
  readonly id?: string;
32
54
  readonly key?: string;
@@ -47,6 +69,7 @@ export interface BidirectionalTargetChangeSourceAnchorMatch {
47
69
  readonly targetRegion: Partial<NativeSourceChangeRegion>;
48
70
  readonly sourceAnchors: readonly BidirectionalTargetChangeAnchor[];
49
71
  readonly lineageResolutions: readonly SemanticLineageResolution[];
72
+ readonly sourceMapLinks: readonly BidirectionalTargetChangeSourceMapLink[];
50
73
  readonly status: BidirectionalTargetChangeAnchorStatus;
51
74
  readonly confidence?: number;
52
75
  readonly reasonCodes: readonly string[];
@@ -77,6 +100,11 @@ export interface CreateBidirectionalTargetChangeRecordOptions {
77
100
  readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
78
101
  readonly sourceAnchorMappings?: readonly BidirectionalTargetChangeSourceAnchorMapping[];
79
102
  readonly anchorMappings?: readonly BidirectionalTargetChangeSourceAnchorMapping[];
103
+ readonly sourceMaps?: readonly SourceMapRecord[];
104
+ readonly projectionSourceMaps?: readonly SourceMapRecord[];
105
+ readonly targetSourceMaps?: readonly SourceMapRecord[];
106
+ readonly targetProjectionSourceMaps?: readonly SourceMapRecord[];
107
+ readonly targetCompileResult?: { readonly sourceMaps?: readonly SourceMapRecord[]; readonly sourceMap?: SourceMapRecord };
80
108
  readonly lineage?: readonly SemanticLineageEvent[];
81
109
  readonly lineageEvents?: readonly SemanticLineageEvent[];
82
110
  readonly lineageMap?: unknown;
@@ -114,6 +142,7 @@ export interface BidirectionalTargetChangeRecord {
114
142
  readonly unmatchedTargetRegions: number;
115
143
  readonly deletedSourceAnchors: number;
116
144
  readonly sourceChangedRegions: number;
145
+ readonly sourceMapBackedMatches: number;
117
146
  };
118
147
  readonly metadata: {
119
148
  readonly autoMergeClaim: false;
@@ -40,6 +40,7 @@ import type { UniversalCapabilityLanguageRow, UniversalCapabilityMatrix, Univers
40
40
  import type { NativeImportContractSource, NativeImportSourcePreservationRecordSummary, NativeImportSourcePreservationContract, NativeImportAdapterCoverageRecordSummary, NativeImportAdapterCoverageContract, NativeImportRegionSummary, NativeImportSourceMapSummary, NativeImportReadinessContract, NativeImportResultContract, NativeImportResultContractOptions } from './native-import-contracts.js';
41
41
  import type { NativeSourceTokenKind, NativeSourcePreservedToken, NativeSourcePreservedDirective, NativeSourcePreservation, CreateNativeSourcePreservationOptions } from './source-preservation.js';
42
42
  import type { SemanticImportOwnershipRegion, SemanticImportSidecarSymbol, SemanticImportRegionTaxonomySummary, SemanticImportPatchHint, SemanticImportSidecarImportEntry, SemanticImportSidecarSourcePreservationRecord, SemanticImportSidecarUniversalAstLayerSummary, SemanticImportSidecarProofSpecSummary, SemanticImportSidecarParadigmSemanticsSummary, SemanticImportSidecar, SemanticImportSidecarOptions } from './semantic-sidecar.js';
43
+ import type { SemanticLineageInferenceResult } from './semantic-lineage.js';
43
44
  import type { SemanticMergeCandidateWithConflicts, SemanticMergeConflictSummary } from './semantic-merge-conflicts.js';
44
45
  import type { SemanticSliceInput, CreateSemanticSliceOptions, SemanticSliceSourceMapLink, SemanticSliceSourceFile, SemanticSliceExpectedAssertion, SemanticSlice, TestSemanticSliceOptions, SemanticSliceTestAssertion, SemanticSliceTestResult } from './semantic-slice.js';
45
46
  import type { NativeImporterAdapterExactness, NativeImporterAdapterSemanticCoverage, NativeImporterAdapterCoverageSnapshot, NativeImporterAdapterCoverageObserved, NativeImporterAdapterCoverageCapabilityRow, NativeImporterAdapterCoverageCapabilityEvidence, NativeImporterAdapterCoverageSummary, NativeImporterAdapterCoverageInput } from './adapter-coverage.js';
@@ -227,6 +228,7 @@ export interface NativeSourceChangeSet {
227
228
  readonly patch: SemanticPatchBundle;
228
229
  readonly mergeCandidate: SemanticMergeCandidateWithConflicts;
229
230
  readonly evidence: readonly EvidenceRecord[];
231
+ readonly lineageInference?: SemanticLineageInferenceResult;
230
232
  readonly readiness: SemanticMergeReadiness;
231
233
  readonly reasons: readonly string[];
232
234
  readonly sourceMaps: readonly SourceMapRecord[];
@@ -236,5 +238,6 @@ export interface NativeSourceChangeSet {
236
238
  readonly metadata?: Record<string, unknown> & {
237
239
  readonly changedRegionProjectionSummary?: NativeSourceChangeProjectionSummary;
238
240
  readonly semanticMergeConflictSummary?: SemanticMergeConflictSummary;
241
+ readonly semanticLineageInferenceSummary?: SemanticLineageInferenceResult['summary'];
239
242
  };
240
243
  }
@@ -1,4 +1,6 @@
1
1
  import type { FrontierSourceLanguage, SourceSpan } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import type { EvidenceRecord, SemanticMergeReadiness } from '@shapeshift-labs/frontier-lang-kernel';
3
+ import type { ImportNativeSourceOptions, NativeSourceImportResult } from './import-adapter-core.js';
2
4
 
3
5
  export type SemanticLineageEventKind = 'unchanged' | 'moved' | 'renamed' | 'split' | 'merged' | 'deleted' | 'recreated' | 'unknown' | string;
4
6
  export type SemanticLineageResolutionStatus = 'unchanged' | 'resolved' | 'ambiguous' | 'deleted' | 'recreated' | 'cycle' | 'max-depth' | 'not-found' | string;
@@ -183,11 +185,105 @@ export interface SemanticLineageQuery {
183
185
  readonly evidenceId?: string | readonly string[];
184
186
  }
185
187
 
188
+ export interface SemanticLineageInferredAnchorSummary {
189
+ readonly key?: string;
190
+ readonly id?: string;
191
+ readonly name?: string;
192
+ readonly kind?: string;
193
+ readonly language?: FrontierSourceLanguage | string;
194
+ readonly sourcePath?: string;
195
+ readonly sourceHash?: string;
196
+ readonly sourceSpan?: SourceSpan;
197
+ readonly signatureHash?: string;
198
+ readonly bodyHash?: string;
199
+ readonly ownershipRegionKind?: string;
200
+ }
201
+
202
+ export interface SemanticLineageAmbiguousCandidate {
203
+ readonly after: SemanticLineageInferredAnchorSummary;
204
+ readonly confidence: number;
205
+ readonly reasons: readonly string[];
206
+ }
207
+
208
+ export interface SemanticLineageAmbiguousMatch {
209
+ readonly before: SemanticLineageInferredAnchorSummary;
210
+ readonly candidates: readonly SemanticLineageAmbiguousCandidate[];
211
+ readonly reasonCodes: readonly string[];
212
+ }
213
+
214
+ export interface SemanticLineageInferenceResult {
215
+ readonly kind: 'frontier.lang.semanticLineageInference';
216
+ readonly version: 1;
217
+ readonly id: string;
218
+ readonly language?: FrontierSourceLanguage | string;
219
+ readonly sourcePath?: string;
220
+ readonly beforeImportId?: string;
221
+ readonly afterImportId?: string;
222
+ readonly beforeHash?: string;
223
+ readonly afterHash?: string;
224
+ readonly events: readonly SemanticLineageEvent[];
225
+ readonly lineageMap: SemanticLineageMap;
226
+ readonly evidence: readonly EvidenceRecord[];
227
+ readonly unmatched: {
228
+ readonly removed: readonly SemanticLineageInferredAnchorSummary[];
229
+ readonly added: readonly SemanticLineageInferredAnchorSummary[];
230
+ readonly ambiguous: readonly SemanticLineageAmbiguousMatch[];
231
+ };
232
+ readonly summary: {
233
+ readonly beforeSymbols: number;
234
+ readonly afterSymbols: number;
235
+ readonly unchangedAnchors: number;
236
+ readonly inferredEvents: number;
237
+ readonly moved: number;
238
+ readonly renamed: number;
239
+ readonly deleted: number;
240
+ readonly ambiguous: number;
241
+ readonly unmatchedAdded: number;
242
+ readonly minConfidence: number;
243
+ readonly ambiguityMargin: number;
244
+ };
245
+ readonly readiness: SemanticMergeReadiness;
246
+ readonly reasons: readonly string[];
247
+ readonly metadata: {
248
+ readonly autoMergeClaim: false;
249
+ readonly semanticEquivalenceClaim: false;
250
+ readonly reviewRequired: true;
251
+ readonly [key: string]: unknown;
252
+ };
253
+ }
254
+
255
+ export interface InferSemanticLineageEventsOptions {
256
+ readonly id?: string;
257
+ readonly before?: NativeSourceImportResult | ImportNativeSourceOptions;
258
+ readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
259
+ readonly language?: FrontierSourceLanguage | string;
260
+ readonly sourcePath?: string;
261
+ readonly parser?: string;
262
+ readonly generatedAt?: number | string;
263
+ readonly regionPrefix?: string;
264
+ readonly evidenceId?: string;
265
+ readonly lineageMapId?: string;
266
+ readonly minConfidence?: number;
267
+ readonly ambiguityMargin?: number;
268
+ readonly includeDeleted?: boolean;
269
+ readonly deletedConfidence?: number;
270
+ readonly readiness?: SemanticMergeReadiness;
271
+ readonly actor?: SemanticLineageActor | string;
272
+ readonly actorId?: string;
273
+ readonly actorRole?: string;
274
+ readonly operationId?: string;
275
+ readonly deps?: readonly string[] | string;
276
+ readonly heads?: readonly string[] | string;
277
+ readonly stateVector?: Record<string, number>;
278
+ readonly metadata?: Record<string, unknown>;
279
+ }
280
+
186
281
  export declare const SemanticLineageEventKinds: readonly SemanticLineageEventKind[];
187
282
  export declare const SemanticLineageResolutionStatuses: readonly SemanticLineageResolutionStatus[];
188
283
  export declare function createSemanticAnchor(input?: SemanticAnchor | string, defaults?: Partial<SemanticAnchor>): SemanticAnchor | undefined;
189
284
  export declare function createSemanticLineageEvent(input?: CreateSemanticLineageEventInput, options?: { readonly id?: string; readonly createdAt?: number | string; readonly actor?: SemanticLineageActor | string; readonly actorId?: string; readonly actorRole?: string }): SemanticLineageEvent;
190
285
  export declare function createSemanticLineageMap(events?: readonly (SemanticLineageEvent | CreateSemanticLineageEventInput)[], options?: { readonly id?: string; readonly generatedAt?: number | string }): SemanticLineageMap;
286
+ export declare function inferSemanticLineageEvents(input?: InferSemanticLineageEventsOptions, options?: { readonly metadata?: Record<string, unknown>; readonly deletedConfidence?: number }): SemanticLineageInferenceResult;
191
287
  export declare function querySemanticLineageEvents(events: SemanticLineageEvent | readonly SemanticLineageEvent[], query?: SemanticLineageQuery): readonly SemanticLineageEvent[];
192
288
  export declare function resolveSemanticLineage(eventsOrMap?: SemanticLineageMap | readonly (SemanticLineageEvent | CreateSemanticLineageEventInput)[], query?: SemanticLineageResolutionQuery | SemanticAnchor | string, options?: { readonly id?: string; readonly generatedAt?: number | string; readonly maxDepth?: number; readonly metadata?: Record<string, unknown> }): SemanticLineageResolution;
193
289
  export declare function resolveSemanticLineageBatch(eventsOrMap?: SemanticLineageMap | readonly (SemanticLineageEvent | CreateSemanticLineageEventInput)[], queries?: readonly (SemanticLineageResolutionQuery | SemanticAnchor | string)[], options?: { readonly generatedAt?: number | string; readonly maxDepth?: number; readonly metadata?: Record<string, unknown> }): readonly SemanticLineageResolution[];
package/dist/index.js CHANGED
@@ -46,6 +46,7 @@ export { getNativeParserAstFormatProfile } from './internal/index-impl/getNative
46
46
  export { importExternalSemanticIndex } from './internal/index-impl/importExternalSemanticIndex.js';
47
47
  export { importNativeProject } from './internal/index-impl/importNativeProject.js';
48
48
  export { importNativeSource } from './internal/index-impl/importNativeSource.js';
49
+ export { inferSemanticLineageEvents } from './internal/index-impl/inferSemanticLineageEvents.js';
49
50
  export { createLanguageAdapterPackageContract, getLanguageAdapterPackageContract, LanguageAdapterPackageContracts, LanguageAdapterPackageReleaseReadinessStatuses, queryLanguageAdapterPackageContracts, summarizeLanguageAdapterPackageContracts } from './language-adapter-package-contracts.js';
50
51
  export { NativeImportFeatureEvidencePolicies } from './internal/index-impl/NativeImportFeatureEvidencePolicies.js';
51
52
  export { NativeImportLanguageProfiles } from './coverage-matrix-profiles.js';
@@ -8,13 +8,14 @@ import { resolveSemanticLineage } from './semanticLineageResolutionRecords.js';
8
8
 
9
9
  export function matchTargetRegion(context) {
10
10
  const explicit = explicitMappingForRegion(context.region, context.mappings);
11
- const candidateAnchors = explicit
12
- ? sourceAnchorsForMapping(explicit, context.sourceAnchors)
13
- : sourceAnchorsByName(context.region, context.sourceAnchors);
11
+ const mapped = explicit ? { anchors: sourceAnchorsForMapping(explicit, context.sourceAnchors), links: [] }
12
+ : sourceMapAnchorsForRegion(context.region, context.sourceMaps, context.sourceAnchors);
13
+ const candidateAnchors = mapped.anchors.length ? mapped.anchors : sourceAnchorsByName(context.region, context.sourceAnchors);
14
14
  const resolvedAnchors = candidateAnchors.flatMap((anchor) => resolveAnchor(anchor, context.lineage));
15
15
  const status = matchStatus(candidateAnchors, resolvedAnchors);
16
16
  const reasonCodes = uniqueStrings([
17
- explicit ? 'explicit-anchor-mapping' : 'inferred-by-symbol-name',
17
+ explicit ? 'explicit-anchor-mapping' : mapped.links.length ? 'source-map-generated-anchor' : 'inferred-by-symbol-name',
18
+ mapped.links.length ? 'mapped-by-generated-source-map' : undefined,
18
19
  status === 'unmatched' ? 'source-anchor-not-found' : undefined,
19
20
  status === 'ambiguous' ? 'source-anchor-ambiguous' : undefined,
20
21
  status === 'deleted' ? 'source-anchor-deleted' : undefined,
@@ -27,8 +28,9 @@ export function matchTargetRegion(context) {
27
28
  targetRegion: compactRegion(context.region),
28
29
  sourceAnchors: resolvedAnchors.map((entry) => entry.anchor),
29
30
  lineageResolutions: uniqueRecordsById(resolvedAnchors.map((entry) => entry.resolution).filter(Boolean)),
31
+ sourceMapLinks: mapped.links,
30
32
  status,
31
- confidence: status === 'matched' && explicit ? 0.8 : status === 'matched' ? 0.58 : undefined,
33
+ confidence: status === 'matched' && explicit ? 0.8 : status === 'matched' && mapped.links.length ? 0.72 : status === 'matched' ? 0.58 : undefined,
32
34
  reasonCodes,
33
35
  reviewRequired: true,
34
36
  autoMergeClaim: false,
@@ -37,6 +39,7 @@ export function matchTargetRegion(context) {
37
39
  context.region.conflictKey,
38
40
  context.region.key,
39
41
  ...resolvedAnchors.map((entry) => entry.anchor?.key),
42
+ ...mapped.links.map((link) => link.sourceMapMappingId && `source-map:${link.sourceMapMappingId}`),
40
43
  status === 'unmatched' ? `target:${context.region.sourcePath ?? context.region.language ?? 'unknown'}` : undefined
41
44
  ])
42
45
  };
@@ -65,6 +68,7 @@ export function sourceRegionsForMatch(match, readiness) {
65
68
  symbolId: anchor?.symbolId,
66
69
  symbolName: anchor?.symbolName,
67
70
  sourceSpan: anchor?.sourceSpan,
71
+ sourceMapLinks: match.sourceMapLinks,
68
72
  admission: {
69
73
  readiness,
70
74
  action: 'review-port-from-target-change',
@@ -75,6 +79,7 @@ export function sourceRegionsForMatch(match, readiness) {
75
79
  bidirectionalTargetChange: {
76
80
  matchId: match.id,
77
81
  targetRegion: match.targetRegion,
82
+ sourceMapLinkIds: match.sourceMapLinks.map((link) => link.id),
78
83
  lineageResolutionIds: match.lineageResolutions.map((resolution) => resolution.id),
79
84
  reviewRequired: true,
80
85
  autoMergeClaim: false,
@@ -97,6 +102,8 @@ export function createBidirectionalEvidence(context) {
97
102
  targetChangeSetId: context.targetChangeSet.id,
98
103
  targetPatchId: context.targetChangeSet.patch?.id,
99
104
  sourceAnchorMatchIds: context.sourceAnchorMatches.map((match) => match.id),
105
+ sourceMapBackedMatches: context.sourceAnchorMatches.filter((match) => match.sourceMapLinks.length > 0).length,
106
+ sourceMapLinkIds: context.sourceAnchorMatches.flatMap((match) => match.sourceMapLinks.map((link) => link.id)),
100
107
  readiness: context.readiness,
101
108
  reasons: context.reasons,
102
109
  autoMergeClaim: false,
@@ -105,6 +112,77 @@ export function createBidirectionalEvidence(context) {
105
112
  };
106
113
  }
107
114
 
115
+ function sourceMapAnchorsForRegion(region, sourceMaps, sourceAnchors) {
116
+ const links = [];
117
+ const anchors = [];
118
+ for (const sourceMap of sourceMaps ?? []) {
119
+ for (const mapping of sourceMap?.mappings ?? []) {
120
+ if (!mappingMatchesTargetRegion(mapping, sourceMap, region)) continue;
121
+ const link = sourceMapLinkForMapping(sourceMap, mapping);
122
+ links.push(link);
123
+ anchors.push(...anchorsForSourceMapMapping(mapping, link, sourceAnchors));
124
+ }
125
+ }
126
+ return { anchors: uniqueRecordsById(anchors), links: uniqueRecordsById(links) };
127
+ }
128
+
129
+ function anchorsForSourceMapMapping(mapping, link, sourceAnchors) {
130
+ const matches = sourceAnchors.filter((anchor) =>
131
+ (mapping.semanticSymbolId && anchor.symbolId === mapping.semanticSymbolId)
132
+ || (mapping.semanticNodeId && anchor.id === mapping.semanticNodeId)
133
+ || (mapping.nativeAstNodeId && anchor.metadata?.nativeAstNodeId === mapping.nativeAstNodeId)
134
+ || (mapping.ownershipRegionKey && anchor.key === mapping.ownershipRegionKey)
135
+ );
136
+ if (matches.length) return matches;
137
+ return [compactRecord({
138
+ id: mapping.semanticSymbolId ?? mapping.semanticNodeId ?? mapping.nativeAstNodeId ?? link.id,
139
+ key: mapping.ownershipRegionKey ?? mapping.semanticSymbolId ?? mapping.semanticNodeId ?? link.id,
140
+ kind: mapping.ownershipRegionKind ?? 'source-map',
141
+ language: link.sourceSpan?.language,
142
+ sourcePath: link.sourcePath,
143
+ sourceHash: link.sourceHash,
144
+ symbolId: mapping.semanticSymbolId,
145
+ symbolName: mapping.generatedName ?? mapping.generatedSpan?.generatedName,
146
+ sourceSpan: mapping.sourceSpan,
147
+ metadata: { sourceMapId: link.sourceMapId, sourceMapMappingId: link.sourceMapMappingId }
148
+ })];
149
+ }
150
+
151
+ function mappingMatchesTargetRegion(mapping, sourceMap, region) {
152
+ if (!targetPathMatches(mapping, sourceMap, region)) return false;
153
+ if (mapping.generatedName && namesForRegion(region).includes(mapping.generatedName)) return true;
154
+ if (mapping.generatedSpan?.generatedName && namesForRegion(region).includes(mapping.generatedSpan.generatedName)) return true;
155
+ return spansOverlap(mapping.generatedSpan, region.sourceSpan);
156
+ }
157
+
158
+ function targetPathMatches(mapping, sourceMap, region) {
159
+ const targetPaths = uniqueStrings([mapping.generatedSpan?.targetPath, mapping.generatedSpan?.path, sourceMap?.targetPath]);
160
+ const regionPath = region.sourcePath ?? region.sourceSpan?.path;
161
+ return !regionPath || targetPaths.length === 0 || targetPaths.includes(regionPath);
162
+ }
163
+
164
+ function sourceMapLinkForMapping(sourceMap, mapping) {
165
+ return compactRecord({
166
+ id: `source_map_link_${idFragment(sourceMap?.id ?? 'map')}_${idFragment(mapping.id ?? mapping.semanticSymbolId ?? 'mapping')}`,
167
+ sourceMapId: sourceMap?.id,
168
+ sourceMapMappingId: mapping.id,
169
+ sourcePath: mapping.sourceSpan?.path ?? sourceMap?.sourcePath,
170
+ sourceHash: mapping.sourceSpan?.sourceId ?? sourceMap?.sourceHash,
171
+ targetPath: mapping.generatedSpan?.targetPath ?? sourceMap?.targetPath,
172
+ targetHash: mapping.generatedSpan?.targetHash ?? sourceMap?.targetHash,
173
+ semanticSymbolId: mapping.semanticSymbolId,
174
+ semanticOccurrenceId: mapping.semanticOccurrenceId,
175
+ semanticNodeId: mapping.semanticNodeId,
176
+ nativeSourceId: mapping.nativeSourceId,
177
+ nativeAstNodeId: mapping.nativeAstNodeId,
178
+ precision: mapping.precision,
179
+ sourceSpan: mapping.sourceSpan,
180
+ generatedSpan: mapping.generatedSpan,
181
+ regionKey: mapping.ownershipRegionKey,
182
+ regionKind: mapping.ownershipRegionKind
183
+ });
184
+ }
185
+
108
186
  export function anchorsFromSourceSidecar(sidecar, source) {
109
187
  return uniqueRecordsById((sidecar.ownershipRegions ?? []).map((region) => compactRecord({
110
188
  id: region.id,
@@ -148,11 +226,15 @@ function sourceAnchorsForMapping(mapping, sourceAnchors) {
148
226
  }
149
227
 
150
228
  function sourceAnchorsByName(region, sourceAnchors) {
151
- const names = uniqueStrings([region.symbolName, region.name, region.metadata?.changedRegionProjection?.region?.symbolName]);
229
+ const names = namesForRegion(region);
152
230
  if (names.length === 0) return [];
153
231
  return sourceAnchors.filter((anchor) => names.includes(anchor.symbolName));
154
232
  }
155
233
 
234
+ function namesForRegion(region) {
235
+ return uniqueStrings([region.symbolName, region.name, region.metadata?.changedRegionProjection?.region?.symbolName]);
236
+ }
237
+
156
238
  function resolveAnchor(anchor, lineage) {
157
239
  if (!anchor) return [];
158
240
  const resolution = array(lineage).length ? resolveSemanticLineage(lineage, { anchorKey: anchor.key }) : undefined;
@@ -189,6 +271,23 @@ function compactRegion(region) {
189
271
  });
190
272
  }
191
273
 
274
+ function spansOverlap(left, right) {
275
+ if (!left || !right) return false;
276
+ if (!sourcePathsCompatible(left, right)) return false;
277
+ const leftStart = Number(left.startLine ?? 0);
278
+ const leftEnd = Number(left.endLine ?? left.startLine ?? 0);
279
+ const rightStart = Number(right.startLine ?? 0);
280
+ const rightEnd = Number(right.endLine ?? right.startLine ?? 0);
281
+ if (!leftStart || !rightStart) return false;
282
+ return leftStart <= rightEnd && rightStart <= leftEnd;
283
+ }
284
+
285
+ function sourcePathsCompatible(left, right) {
286
+ const leftPath = left.targetPath ?? left.path;
287
+ const rightPath = right.targetPath ?? right.path;
288
+ return !leftPath || !rightPath || leftPath === rightPath;
289
+ }
290
+
192
291
  function array(value) {
193
292
  return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value];
194
293
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  idFragment,
3
+ uniqueRecordsById,
3
4
  uniqueStrings
4
5
  } from '../../native-import-utils.js';
5
6
  import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
@@ -45,6 +46,14 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
45
46
  }) : undefined;
46
47
  const sourceAnchors = sourceSidecar ? anchorsFromSourceSidecar(sourceSidecar, source) : [];
47
48
  const mappings = array(input.sourceAnchorMappings ?? input.anchorMappings);
49
+ const sourceMaps = uniqueRecordsById([
50
+ ...array(input.sourceMaps),
51
+ ...array(input.projectionSourceMaps),
52
+ ...array(input.targetSourceMaps),
53
+ ...array(input.targetProjectionSourceMaps),
54
+ ...array(input.targetCompileResult?.sourceMaps),
55
+ input.targetCompileResult?.sourceMap
56
+ ]);
48
57
  const lineage = input.lineage ?? input.lineageEvents ?? input.lineageMap ?? [];
49
58
  const sourceAnchorMatches = targetChangeSet.changedRegions.map((region, index) => matchTargetRegion({
50
59
  id,
@@ -53,6 +62,7 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
53
62
  source,
54
63
  sourceAnchors,
55
64
  mappings,
65
+ sourceMaps,
56
66
  lineage
57
67
  }));
58
68
  const readiness = classifyBidirectionalReadiness(targetChangeSet, source, sourceAnchorMatches);
@@ -86,7 +96,8 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
86
96
  projectionOnly: true,
87
97
  targetChangeSetId: targetChangeSet.id,
88
98
  targetPatchId: targetChangeSet.patch?.id,
89
- targetMergeCandidateId: targetChangeSet.mergeCandidate?.id
99
+ targetMergeCandidateId: targetChangeSet.mergeCandidate?.id,
100
+ sourceMapBackprojection: summarizeSourceMapBackprojection(sourceAnchorMatches)
90
101
  }
91
102
  }, {
92
103
  id: input.sourcePatchBundleId ?? `semantic_patch_bundle_${idFragment(id)}_source_port`,
@@ -127,6 +138,7 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
127
138
  bidirectionalTargetChangeId: id,
128
139
  sourcePatchBundleId: sourcePatchBundle.id,
129
140
  targetChangeSetId: targetChangeSet.id,
141
+ sourceMapBackprojection: summarizeSourceMapBackprojection(sourceAnchorMatches),
130
142
  autoMergeClaim: false,
131
143
  semanticEquivalenceClaim: false
132
144
  }
@@ -153,7 +165,8 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
153
165
  ambiguousMatches: sourceAnchorMatches.filter((match) => match.status === 'ambiguous').length,
154
166
  unmatchedTargetRegions: sourceAnchorMatches.filter((match) => match.status === 'unmatched').length,
155
167
  deletedSourceAnchors: sourceAnchorMatches.filter((match) => match.status === 'deleted').length,
156
- sourceChangedRegions: sourceChangedRegions.length
168
+ sourceChangedRegions: sourceChangedRegions.length,
169
+ sourceMapBackedMatches: sourceAnchorMatches.filter((match) => match.sourceMapLinks.length > 0).length
157
170
  },
158
171
  metadata: {
159
172
  autoMergeClaim: false,
@@ -164,6 +177,16 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
164
177
  };
165
178
  }
166
179
 
180
+ function summarizeSourceMapBackprojection(matches) {
181
+ const links = matches.flatMap((match) => match.sourceMapLinks ?? []);
182
+ return {
183
+ sourceMapBackedMatches: matches.filter((match) => (match.sourceMapLinks ?? []).length > 0).length,
184
+ sourceMapLinks: links.length,
185
+ sourceMapIds: uniqueStrings(links.map((link) => link.sourceMapId)),
186
+ sourceMapMappingIds: uniqueStrings(links.map((link) => link.sourceMapMappingId))
187
+ };
188
+ }
189
+
167
190
  function array(value) {
168
191
  return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value];
169
192
  }
@@ -1,5 +1,6 @@
1
1
  import{idFragment,maxSemanticMergeReadiness,uniqueByLossId,uniqueRecordsById,uniqueStrings}from'../../native-import-utils.js';import{createPatch,createSemanticMergeCandidateRecord}from'@shapeshift-labs/frontier-lang-kernel';
2
2
  import{attachNativeChangeRegionProjectionMetadata}from'./attachNativeChangeRegionProjectionMetadata.js';import{classifyNativeSourceMergeConflicts}from'./semanticMergeConflicts.js';import{createSemanticImportSidecar}from'./createSemanticImportSidecar.js';import{diffNativeOwnershipRegions}from'./diffNativeOwnershipRegions.js';import{diffNativeSymbols}from'./diffNativeSymbols.js';import{fileLevelNativeChangeRegion}from'./fileLevelNativeChangeRegion.js';import{mapDiffSymbols}from'./mapDiffSymbols.js';import{nativeChangeSpans}from'./nativeChangeSpans.js';import{nativeChangeTouchedSymbol}from'./nativeChangeTouchedSymbol.js';import{nativeImportReadiness}from'./nativeImportReadiness.js';import{nativeSourceChangeReasons}from'./nativeSourceChangeReasons.js';import{nativeSourceChangeSummary}from'./nativeSourceChangeSummary.js';import{normalizeNativeDiffImport}from'./normalizeNativeDiffImport.js';import{summarizeNativeChangedRegionProjections}from'./summarizeNativeChangedRegionProjections.js';
3
+ import{inferSemanticLineageEvents}from'./inferSemanticLineageEvents.js';
3
4
  import{decorateSemanticMergeCandidateForAdmission}from'./semanticMergeCandidateRecords.js';
4
5
  export function diffNativeSourceImports(input) {
5
6
  const before = normalizeNativeDiffImport(input.before, input, 'before');
@@ -39,6 +40,17 @@ export function diffNativeSourceImports(input) {
39
40
  reasons
40
41
  });
41
42
  const changedRegionProjectionSummary = summarizeNativeChangedRegionProjections(changedRegions);
43
+ const lineageInference = inferSemanticLineageEvents({
44
+ before,
45
+ after,
46
+ id: `${idPart}_native_source_diff`,
47
+ language,
48
+ sourcePath,
49
+ generatedAt: input.generatedAt,
50
+ regionPrefix: input.regionPrefix,
51
+ evidenceId: `evidence_${idPart}_semantic_lineage_inference`,
52
+ metadata: { source: 'diffNativeSourceImports' }
53
+ });
42
54
  const evidence = [{
43
55
  id: input.evidenceId ?? `evidence_${idPart}_native_source_diff`,
44
56
  kind: 'import',
@@ -54,7 +66,8 @@ export function diffNativeSourceImports(input) {
54
66
  addedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'added').length,
55
67
  removedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'removed').length,
56
68
  modifiedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'modified').length,
57
- changedRegionProjectionSummary
69
+ changedRegionProjectionSummary,
70
+ semanticLineageInferenceSummary: lineageInference.summary
58
71
  }
59
72
  }];
60
73
  const conflictKeys = uniqueStrings([
@@ -148,6 +161,7 @@ export function diffNativeSourceImports(input) {
148
161
  patch,
149
162
  mergeCandidate,
150
163
  evidence,
164
+ lineageInference,
151
165
  readiness,
152
166
  reasons,
153
167
  sourceMaps: uniqueRecordsById([...(before?.sourceMaps ?? []), ...(after?.sourceMaps ?? [])]),
@@ -160,6 +174,7 @@ export function diffNativeSourceImports(input) {
160
174
  beforeImportContract: before?.metadata?.importResultContract,
161
175
  afterImportContract: after?.metadata?.importResultContract,
162
176
  changedRegionProjectionSummary,
177
+ semanticLineageInferenceSummary: lineageInference.summary,
163
178
  semanticMergeConflictSummary: mergeConflictProfile.conflictSummary,
164
179
  ...input.metadata
165
180
  }
@@ -0,0 +1,214 @@
1
+ import { idFragment, maxSemanticMergeReadiness, uniqueStrings } from '../../native-import-utils.js';
2
+ import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
3
+ import { createSemanticAnchor, createSemanticLineageEvent, createSemanticLineageMap } from './semanticLineageRecords.js';
4
+ import { matchExactAnchors, matchLineageCandidates, symbolSummary } from './semanticLineageInferenceMatching.js';
5
+ import { mapDiffSymbols } from './mapDiffSymbols.js';
6
+ import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
7
+
8
+ const DEFAULT_MIN_CONFIDENCE = 0.74;
9
+ const DEFAULT_AMBIGUITY_MARGIN = 0.08;
10
+
11
+ export function inferSemanticLineageEvents(input = {}, options = {}) {
12
+ const before = normalizeNativeDiffImport(input.before, input, 'before');
13
+ const after = normalizeNativeDiffImport(input.after, input, 'after');
14
+ if (!before && !after) throw new Error('inferSemanticLineageEvents requires before or after native source input');
15
+ const language = input.language ?? after?.language ?? before?.language;
16
+ const sourcePath = input.sourcePath ?? after?.sourcePath ?? before?.sourcePath;
17
+ const beforeHash = before?.nativeSource?.sourceHash ?? before?.nativeAst?.sourceHash ?? before?.sourceHash;
18
+ const afterHash = after?.nativeSource?.sourceHash ?? after?.nativeAst?.sourceHash ?? after?.sourceHash;
19
+ const idPart = idFragment(input.id ?? sourcePath ?? language ?? 'semantic_lineage_inference');
20
+ const beforeSidecar = before ? createSemanticImportSidecar(before, { id: `lineage_sidecar_before_${idPart}`, generatedAt: input.generatedAt, regionPrefix: input.regionPrefix }) : undefined;
21
+ const afterSidecar = after ? createSemanticImportSidecar(after, { id: `lineage_sidecar_after_${idPart}`, generatedAt: input.generatedAt, regionPrefix: input.regionPrefix }) : undefined;
22
+ const beforeSymbols = [...mapDiffSymbols(before, beforeSidecar).values()].map((symbol) => lineageSymbol(symbol, before)).filter((symbol) => symbol.anchor?.key);
23
+ const afterSymbols = [...mapDiffSymbols(after, afterSidecar).values()].map((symbol) => lineageSymbol(symbol, after)).filter((symbol) => symbol.anchor?.key);
24
+ const exact = matchExactAnchors(beforeSymbols, afterSymbols);
25
+ const candidates = matchLineageCandidates(exact.unmatchedBefore, exact.unmatchedAfter, input, {
26
+ minConfidence: minConfidence(input),
27
+ ambiguityMargin: ambiguityMargin(input)
28
+ });
29
+ const deleted = input.includeDeleted === false
30
+ ? []
31
+ : candidates.unmatchedBefore.map((symbol) => deletedEvent(symbol, input, options, { before, after }));
32
+ const events = [...candidates.events, ...deleted].filter(Boolean);
33
+ const evidence = [lineageInferenceEvidence({
34
+ input,
35
+ idPart,
36
+ sourcePath,
37
+ language,
38
+ before,
39
+ after,
40
+ beforeHash,
41
+ afterHash,
42
+ exact,
43
+ candidates,
44
+ deleted,
45
+ events
46
+ })];
47
+ const lineageMap = createSemanticLineageMap(events, {
48
+ id: input.lineageMapId ?? `semantic_lineage_map_${idPart}`,
49
+ generatedAt: input.generatedAt
50
+ });
51
+ const readiness = inferenceReadiness({ events, deleted, ambiguous: candidates.ambiguous, added: candidates.unmatchedAfter }, input);
52
+ return {
53
+ kind: 'frontier.lang.semanticLineageInference',
54
+ version: 1,
55
+ id: input.id ?? `semantic_lineage_inference_${idPart}`,
56
+ language,
57
+ sourcePath,
58
+ beforeImportId: before?.id,
59
+ afterImportId: after?.id,
60
+ beforeHash,
61
+ afterHash,
62
+ events,
63
+ lineageMap,
64
+ evidence,
65
+ unmatched: {
66
+ removed: candidates.unmatchedBefore.map(symbolSummary),
67
+ added: candidates.unmatchedAfter.map(symbolSummary),
68
+ ambiguous: candidates.ambiguous
69
+ },
70
+ summary: {
71
+ beforeSymbols: beforeSymbols.length,
72
+ afterSymbols: afterSymbols.length,
73
+ unchangedAnchors: exact.matched.length,
74
+ inferredEvents: events.length,
75
+ moved: events.filter((event) => event.eventKind === 'moved').length,
76
+ renamed: events.filter((event) => event.eventKind === 'renamed').length,
77
+ deleted: deleted.length,
78
+ ambiguous: candidates.ambiguous.length,
79
+ unmatchedAdded: candidates.unmatchedAfter.length,
80
+ minConfidence: minConfidence(input),
81
+ ambiguityMargin: ambiguityMargin(input)
82
+ },
83
+ readiness,
84
+ reasons: inferenceReasons({ events, deleted, ambiguous: candidates.ambiguous, added: candidates.unmatchedAfter, readiness }),
85
+ metadata: {
86
+ autoMergeClaim: false,
87
+ semanticEquivalenceClaim: false,
88
+ reviewRequired: true,
89
+ beforeSidecarId: beforeSidecar?.id,
90
+ afterSidecarId: afterSidecar?.id,
91
+ note: 'Inferred lineage is refactoring-aware merge evidence, not proof of semantic equivalence.',
92
+ ...input.metadata,
93
+ ...options.metadata
94
+ }
95
+ };
96
+ }
97
+
98
+ function deletedEvent(symbol, input, options, context) {
99
+ return createSemanticLineageEvent({
100
+ id: `lineage_deleted_${idFragment(firstString(input.id, symbol.anchor.key))}`,
101
+ createdAt: input.generatedAt,
102
+ eventKind: 'deleted',
103
+ from: symbol.anchor,
104
+ confidence: deletedConfidence(input, options, context),
105
+ actor: input.actor,
106
+ actorId: input.actorId,
107
+ actorRole: input.actorRole ?? 'semantic-lineage-inference',
108
+ evidenceIds: [input.evidenceId ?? `evidence_${idFragment(input.id ?? symbol.anchor.key)}_lineage_inference`],
109
+ conflictKeys: [symbol.anchor.key],
110
+ metadata: {
111
+ inferred: true,
112
+ algorithm: 'frontier.semantic-lineage-inference.v1',
113
+ reasonCodes: ['anchor-removed-from-after-import'],
114
+ deletionEvidenceScope: deletionEvidenceScope(context),
115
+ autoMergeClaim: false,
116
+ semanticEquivalenceClaim: false
117
+ }
118
+ });
119
+ }
120
+
121
+ function deletedConfidence(input, options, context) {
122
+ if (options.deletedConfidence !== undefined) return options.deletedConfidence;
123
+ if (input.deletedConfidence !== undefined) return input.deletedConfidence;
124
+ return deletionEvidenceScope(context) === 'same-source-file' ? 0.8 : 0.55;
125
+ }
126
+
127
+ function deletionEvidenceScope(context) {
128
+ const beforePath = context.before?.sourcePath ?? context.before?.nativeSource?.sourcePath;
129
+ const afterPath = context.after?.sourcePath ?? context.after?.nativeSource?.sourcePath;
130
+ return beforePath && afterPath && beforePath === afterPath ? 'same-source-file' : 'partial-or-moved-scope';
131
+ }
132
+
133
+ function lineageSymbol(symbol, imported) {
134
+ const anchor = createSemanticAnchor({
135
+ key: symbol.ownershipKey ?? symbol.key ?? symbol.id,
136
+ kind: symbol.ownershipRegionKind ?? symbol.kind,
137
+ language: symbol.language ?? imported?.language,
138
+ sourcePath: symbol.sourcePath ?? imported?.sourcePath,
139
+ sourceHash: symbol.sourceHash ?? imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash,
140
+ symbolId: symbol.id,
141
+ symbolName: symbol.name,
142
+ signatureHash: symbol.signatureHash,
143
+ bodyHash: symbol.spanHash,
144
+ sourceSpan: symbol.sourceSpan,
145
+ metadata: {
146
+ nativeAstNodeId: symbol.nativeAstNodeId,
147
+ semanticOccurrenceId: symbol.semanticOccurrenceId,
148
+ sourceMapMappingId: symbol.sourceMapMappingId,
149
+ ownershipRegionId: symbol.ownershipRegionId,
150
+ ownershipRegionKind: symbol.ownershipRegionKind
151
+ }
152
+ }, { language: imported?.language, sourcePath: imported?.sourcePath });
153
+ return { ...symbol, anchor };
154
+ }
155
+
156
+ function lineageInferenceEvidence(input) {
157
+ return {
158
+ id: input.input.evidenceId ?? `evidence_${input.idPart}_lineage_inference`,
159
+ kind: 'import',
160
+ status: 'passed',
161
+ path: input.sourcePath,
162
+ summary: `Inferred ${input.events.length} semantic lineage event(s) from ${input.before?.id ?? 'before'} to ${input.after?.id ?? 'after'}.`,
163
+ metadata: {
164
+ algorithm: 'frontier.semantic-lineage-inference.v1',
165
+ beforeImportId: input.before?.id,
166
+ afterImportId: input.after?.id,
167
+ beforeHash: input.beforeHash,
168
+ afterHash: input.afterHash,
169
+ language: input.language,
170
+ unchangedAnchors: input.exact.matched.length,
171
+ inferredEvents: input.events.length,
172
+ deleted: input.deleted.length,
173
+ ambiguous: input.candidates.ambiguous.length,
174
+ unmatchedAdded: input.candidates.unmatchedAfter.length,
175
+ autoMergeClaim: false,
176
+ semanticEquivalenceClaim: false
177
+ }
178
+ };
179
+ }
180
+
181
+ function inferenceReadiness(input, options) {
182
+ if (input.ambiguous.length) return 'blocked';
183
+ let readiness = 'ready';
184
+ if (input.events.length || input.added.length) readiness = maxSemanticMergeReadiness(readiness, 'needs-review');
185
+ return maxSemanticMergeReadiness(readiness, options.readiness ?? 'ready');
186
+ }
187
+
188
+ function inferenceReasons(input) {
189
+ return uniqueStrings([
190
+ input.events.length ? 'semantic-lineage-inferred' : undefined,
191
+ input.deleted.length ? 'deleted-anchor-lineage-inferred' : undefined,
192
+ input.added.length ? 'unmatched-added-anchor-review' : undefined,
193
+ input.ambiguous.length ? 'ambiguous-lineage-candidates' : undefined,
194
+ input.readiness === 'blocked' ? 'lineage-inference-blocked' : undefined,
195
+ 'no-auto-merge-claim'
196
+ ].filter(Boolean));
197
+ }
198
+
199
+ function minConfidence(input) {
200
+ return numeric(input.minConfidence, DEFAULT_MIN_CONFIDENCE);
201
+ }
202
+
203
+ function ambiguityMargin(input) {
204
+ return numeric(input.ambiguityMargin, DEFAULT_AMBIGUITY_MARGIN);
205
+ }
206
+
207
+ function numeric(value, fallback) {
208
+ const number = Number(value);
209
+ return Number.isFinite(number) ? number : fallback;
210
+ }
211
+
212
+ function firstString(...values) {
213
+ return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean);
214
+ }
@@ -0,0 +1,167 @@
1
+ import { idFragment, uniqueStrings } from '../../native-import-utils.js';
2
+ import { createSemanticLineageEvent } from './semanticLineageRecords.js';
3
+
4
+ export function matchExactAnchors(beforeSymbols, afterSymbols) {
5
+ const afterByKey = new Map(afterSymbols.map((symbol) => [symbol.anchor.key, symbol]));
6
+ const matched = [];
7
+ const unmatchedBefore = [];
8
+ const matchedAfterKeys = new Set();
9
+ for (const before of beforeSymbols) {
10
+ const after = afterByKey.get(before.anchor.key);
11
+ if (after && anchorsSameLocation(before.anchor, after.anchor)) {
12
+ matched.push({ before: symbolSummary(before), after: symbolSummary(after) });
13
+ matchedAfterKeys.add(after.anchor.key);
14
+ } else {
15
+ unmatchedBefore.push(before);
16
+ }
17
+ }
18
+ return {
19
+ matched,
20
+ unmatchedBefore,
21
+ unmatchedAfter: afterSymbols.filter((symbol) => !matchedAfterKeys.has(symbol.anchor.key))
22
+ };
23
+ }
24
+
25
+ export function matchLineageCandidates(beforeSymbols, afterSymbols, input, options) {
26
+ const claimedAfter = new Set();
27
+ const events = [];
28
+ const ambiguous = [];
29
+ const unmatchedBefore = [];
30
+ for (const before of beforeSymbols) {
31
+ const ranked = afterSymbols
32
+ .filter((after) => !claimedAfter.has(after.anchor.key))
33
+ .map((after) => ({ after, score: scoreLineagePair(before, after) }))
34
+ .filter((candidate) => candidate.score.confidence >= options.minConfidence)
35
+ .sort(compareCandidateScores);
36
+ const best = ranked[0];
37
+ const runnerUp = ranked[1];
38
+ if (!best) {
39
+ unmatchedBefore.push(before);
40
+ continue;
41
+ }
42
+ if (runnerUp && best.score.confidence - runnerUp.score.confidence < options.ambiguityMargin) {
43
+ ambiguous.push(ambiguousMatch(before, ranked));
44
+ unmatchedBefore.push(before);
45
+ continue;
46
+ }
47
+ claimedAfter.add(best.after.anchor.key);
48
+ events.push(inferredEvent(before, best.after, best.score, input));
49
+ }
50
+ return {
51
+ events,
52
+ ambiguous,
53
+ unmatchedBefore,
54
+ unmatchedAfter: afterSymbols.filter((symbol) => !claimedAfter.has(symbol.anchor.key))
55
+ };
56
+ }
57
+
58
+ export function symbolSummary(symbol) {
59
+ return {
60
+ key: symbol.anchor.key,
61
+ id: symbol.id,
62
+ name: symbol.name,
63
+ kind: symbol.kind,
64
+ language: symbol.language,
65
+ sourcePath: symbol.anchor.sourcePath,
66
+ sourceHash: symbol.anchor.sourceHash,
67
+ sourceSpan: symbol.anchor.sourceSpan,
68
+ signatureHash: symbol.signatureHash,
69
+ bodyHash: symbol.spanHash,
70
+ ownershipRegionKind: symbol.ownershipRegionKind
71
+ };
72
+ }
73
+
74
+ function ambiguousMatch(before, ranked) {
75
+ return {
76
+ before: symbolSummary(before),
77
+ candidates: ranked.slice(0, 4).map((candidate) => ({
78
+ after: symbolSummary(candidate.after),
79
+ confidence: candidate.score.confidence,
80
+ reasons: candidate.score.reasons
81
+ })),
82
+ reasonCodes: ['ambiguous-lineage-candidates']
83
+ };
84
+ }
85
+
86
+ function compareCandidateScores(left, right) {
87
+ return right.score.confidence - left.score.confidence
88
+ || String(left.after.anchor.key).localeCompare(String(right.after.anchor.key));
89
+ }
90
+
91
+ function inferredEvent(before, after, score, input) {
92
+ const nameChanged = before.anchor.symbolName
93
+ && after.anchor.symbolName
94
+ && before.anchor.symbolName !== after.anchor.symbolName;
95
+ const pathChanged = before.anchor.sourcePath !== after.anchor.sourcePath;
96
+ const spanMoved = JSON.stringify(before.anchor.sourceSpan ?? null) !== JSON.stringify(after.anchor.sourceSpan ?? null);
97
+ const eventKind = nameChanged ? 'renamed' : 'moved';
98
+ return createSemanticLineageEvent({
99
+ id: `lineage_inferred_${idFragment(firstString(input.id, before.anchor.key))}_${idFragment(after.anchor.key)}`,
100
+ createdAt: input.generatedAt,
101
+ eventKind,
102
+ from: before.anchor,
103
+ to: after.anchor,
104
+ confidence: score.confidence,
105
+ actor: input.actor,
106
+ actorId: input.actorId,
107
+ actorRole: input.actorRole ?? 'semantic-lineage-inference',
108
+ operationId: input.operationId,
109
+ deps: input.deps,
110
+ heads: input.heads,
111
+ stateVector: input.stateVector,
112
+ evidenceIds: [input.evidenceId ?? `evidence_${idFragment(input.id ?? before.anchor.key)}_lineage_inference`],
113
+ signatureHashMatch: score.reasons.includes('signature-hash-match'),
114
+ bodyHashMatch: score.reasons.includes('body-hash-match'),
115
+ pathMatch: !pathChanged,
116
+ sourceSpanMoved: pathChanged || spanMoved,
117
+ conflictKeys: uniqueStrings([before.anchor.key, after.anchor.key]),
118
+ metadata: {
119
+ inferred: true,
120
+ algorithm: 'frontier.semantic-lineage-inference.v1',
121
+ reasonCodes: score.reasons,
122
+ moved: pathChanged || spanMoved,
123
+ renamed: nameChanged,
124
+ autoMergeClaim: false,
125
+ semanticEquivalenceClaim: false
126
+ }
127
+ });
128
+ }
129
+
130
+ function scoreLineagePair(before, after) {
131
+ const reasons = [];
132
+ let score = 0;
133
+ const add = (value, reason) => {
134
+ score += value;
135
+ reasons.push(reason);
136
+ };
137
+ if (before.anchor.key && before.anchor.key === after.anchor.key) add(0.4, 'anchor-key-match');
138
+ if (before.name && before.name === after.name) add(0.28, 'symbol-name-match');
139
+ if (before.kind && before.kind === after.kind) add(0.12, 'symbol-kind-match');
140
+ if (before.signatureHash && before.signatureHash === after.signatureHash) add(0.52, 'signature-hash-match');
141
+ if (before.spanHash && before.spanHash === after.spanHash) add(0.22, 'body-hash-match');
142
+ if (before.anchor.kind && before.anchor.kind === after.anchor.kind) add(0.06, 'anchor-kind-match');
143
+ if (before.anchor.sourcePath && before.anchor.sourcePath === after.anchor.sourcePath) add(0.04, 'source-path-match');
144
+ if (sourceSpanRangeSame(before.anchor.sourceSpan, after.anchor.sourceSpan)) add(0.18, 'source-span-range-match');
145
+ if (before.ownershipRegionKind && before.ownershipRegionKind === after.ownershipRegionKind) add(0.04, 'ownership-kind-match');
146
+ if (before.nativeAstNodeId && before.nativeAstNodeId === after.nativeAstNodeId) add(0.06, 'native-node-id-match');
147
+ if (before.anchor.sourcePath !== after.anchor.sourcePath && (before.name === after.name || before.signatureHash === after.signatureHash)) add(0.04, 'source-path-moved');
148
+ return { confidence: Math.max(0, Math.min(1, Number(score.toFixed(3)))), reasons };
149
+ }
150
+
151
+ function anchorsSameLocation(before, after) {
152
+ return (before.sourcePath ?? '') === (after.sourcePath ?? '')
153
+ && JSON.stringify(before.sourceSpan ?? null) === JSON.stringify(after.sourceSpan ?? null);
154
+ }
155
+
156
+ function sourceSpanRangeSame(before, after) {
157
+ return before
158
+ && after
159
+ && before.startLine === after.startLine
160
+ && before.startColumn === after.startColumn
161
+ && before.endLine === after.endLine
162
+ && before.endColumn === after.endColumn;
163
+ }
164
+
165
+ function firstString(...values) {
166
+ return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean);
167
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.76",
3
+ "version": "0.2.78",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",