@shapeshift-labs/frontier-lang-compiler 0.2.75 → 0.2.77

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
@@ -383,6 +383,45 @@ console.log(querySemanticPatchBundleRecords([bundle], { sourcePath: 'src/runtime
383
383
 
384
384
  Semantic patch bundle records keep compact refs and query indexes for base/target/source hashes, source paths, changed region keys, conflict keys, source-map IDs, evidence IDs, proof IDs, history IDs, readiness, and admission status. They intentionally omit `before`/`after` import payloads and source text; use the original diff result when a worker needs to re-read or re-project source.
385
385
 
386
+ Map an edited target-language file back to source-language semantic anchors for review. This is the reverse direction a swarm needs when a worker changes generated Rust/Python/etc. and the coordinator wants source-port evidence:
387
+
388
+ ```js
389
+ import {
390
+ createBidirectionalTargetChangeRecord,
391
+ importNativeSource
392
+ } from '@shapeshift-labs/frontier-lang-compiler';
393
+
394
+ const source = importNativeSource({
395
+ language: 'typescript',
396
+ sourcePath: 'src/counter.ts',
397
+ sourceText: 'export function add(count: number) { return count + 1; }\n'
398
+ });
399
+
400
+ const targetChange = createBidirectionalTargetChangeRecord({
401
+ source,
402
+ targetLanguage: 'rust',
403
+ targetPath: 'src/counter.rs',
404
+ baseTarget: {
405
+ language: 'rust',
406
+ sourcePath: 'src/counter.rs',
407
+ sourceText: 'pub fn add(count: i32) -> i32 { count + 1 }\n'
408
+ },
409
+ editedTarget: {
410
+ language: 'rust',
411
+ sourcePath: 'src/counter.rs',
412
+ sourceText: 'pub fn add(count: i32, step: i32) -> i32 { count + step }\n'
413
+ },
414
+ sourceAnchorMappings: [{ targetSymbolName: 'add', sourceSymbolName: 'add' }]
415
+ });
416
+
417
+ console.log(targetChange.sourceAnchorMatches[0].status); // "matched"
418
+ console.log(targetChange.sourcePatchBundle.admission.reviewRequired); // true
419
+ console.log(targetChange.historyRecord.index.ownershipKeys);
420
+ console.log(targetChange.metadata.semanticEquivalenceClaim); // false
421
+ ```
422
+
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
+
386
425
  Store worker outputs as compact semantic history records when a coordinator needs to compare distributed changes without merging whole files:
387
426
 
388
427
  ```js
@@ -448,6 +487,38 @@ console.log(resolution.currentAnchors[0].key); // current semantic anchor
448
487
  console.log(resolution.traversedEventIds); // compact history path
449
488
  ```
450
489
 
490
+ Infer conservative lineage events from two native imports when a file was moved,
491
+ a parser reports a rename, or an anchor disappeared:
492
+
493
+ ```js
494
+ import {
495
+ importNativeSource,
496
+ inferSemanticLineageEvents
497
+ } from '@shapeshift-labs/frontier-lang-compiler';
498
+
499
+ const before = importNativeSource({
500
+ language: 'typescript',
501
+ sourcePath: 'src/runtime.ts',
502
+ sourceText: 'export function step(value) { return value + 1; }\n'
503
+ });
504
+ const after = importNativeSource({
505
+ language: 'typescript',
506
+ sourcePath: 'src/runtime-core.ts',
507
+ sourceText: 'export function step(value) { return value + 1; }\n'
508
+ });
509
+
510
+ const inference = inferSemanticLineageEvents({ before, after });
511
+
512
+ console.log(inference.summary.moved); // 1
513
+ console.log(inference.events[0].metadata.autoMergeClaim); // false
514
+ console.log(inference.lineageMap.byAnchorKey); // old/current anchor index
515
+ ```
516
+
517
+ The inference API is intentionally conservative. Ambiguous matches are reported
518
+ as blocked, additions are kept separate from recreated lineage, and inferred
519
+ events always require review; richer parser adapters can improve confidence by
520
+ supplying stable signature hashes and source-map spans.
521
+
451
522
  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.
452
523
 
453
524
  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,6 +110,14 @@ 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)),
117
+ bidirectionalTargetChanges: sourceChangeMetrics.bidirectionalTargetChanges,
118
+ bidirectionalTargetChangeMatches: sourceChangeMetrics.bidirectionalTargetChangeMatches,
119
+ bidirectionalTargetChangeBlocked: sourceChangeMetrics.bidirectionalTargetChangeBlocked,
120
+ bidirectionalTargetChangeDurationMs: Number(sourceChangeMetrics.bidirectionalTargetChangeDurationMs.toFixed(2)),
113
121
  externalSemanticImports: sourceChangeMetrics.externalSemanticImports,
114
122
  externalSemanticSymbols: sourceChangeMetrics.externalSemanticSymbols,
115
123
  externalSemanticMappings: sourceChangeMetrics.externalSemanticMappings,
@@ -1,7 +1,10 @@
1
1
  import { performance } from 'node:perf_hooks';
2
2
  import {
3
+ createBidirectionalTargetChangeRecord,
3
4
  createSemanticImportSidecar,
5
+ createSemanticLineageEvent,
4
6
  diffNativeSources,
7
+ inferSemanticLineageEvents,
5
8
  importExternalSemanticIndex,
6
9
  importNativeSource
7
10
  } from '../dist/index.js';
@@ -10,6 +13,8 @@ export function measureSourceChangeSuites() {
10
13
  return {
11
14
  ...measureRegionScan(),
12
15
  ...measureChangeProjection(),
16
+ ...measureSemanticLineageInference(),
17
+ ...measureBidirectionalTargetChanges(),
13
18
  ...measureExternalSemanticImports()
14
19
  };
15
20
  }
@@ -72,6 +77,75 @@ function measureChangeProjection() {
72
77
  };
73
78
  }
74
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
+
108
+ function measureBidirectionalTargetChanges() {
109
+ const start = performance.now();
110
+ const source = importNativeSource({
111
+ language: 'typescript',
112
+ sourcePath: 'src/bidirectional-source.ts',
113
+ sourceText: 'export function advance(frame: number): number { return frame + 1; }\n'
114
+ });
115
+ const lineage = [createSemanticLineageEvent({
116
+ eventKind: 'moved',
117
+ from: { key: 'source#src/bidirectional-source.ts#body#advance', symbolName: 'advance' },
118
+ to: { key: 'source#src/runtime-core.ts#body#advanceFrame', symbolName: 'advanceFrame' }
119
+ })];
120
+ const records = [];
121
+ for (let index = 0; index < 60; index += 1) {
122
+ records.push(createBidirectionalTargetChangeRecord({
123
+ id: `bench_bidirectional_target_change_${index}`,
124
+ source,
125
+ targetLanguage: 'rust',
126
+ targetPath: `src/bidirectional-${index}.rs`,
127
+ baseTarget: {
128
+ language: 'rust',
129
+ sourcePath: `src/bidirectional-${index}.rs`,
130
+ sourceText: `pub fn advance(frame: i32) -> i32 { frame + ${index} }\n`
131
+ },
132
+ editedTarget: {
133
+ language: 'rust',
134
+ sourcePath: `src/bidirectional-${index}.rs`,
135
+ sourceText: `pub fn advance(frame: i32, delta: i32) -> i32 { frame + delta + ${index} }\n`
136
+ },
137
+ sourceAnchorMappings: [{ targetSymbolName: 'advance', sourceSymbolName: 'advance' }],
138
+ lineage: index % 4 === 0 ? lineage : []
139
+ }));
140
+ }
141
+ return {
142
+ bidirectionalTargetChanges: records.length,
143
+ bidirectionalTargetChangeMatches: records.reduce((sum, record) => sum + record.summary.sourceAnchorMatches, 0),
144
+ bidirectionalTargetChangeBlocked: records.filter((record) => record.readiness === 'blocked').length,
145
+ bidirectionalTargetChangeDurationMs: performance.now() - start
146
+ };
147
+ }
148
+
75
149
  function measureExternalSemanticImports() {
76
150
  const externalSemanticStart = performance.now();
77
151
  const externalSemanticImports = [];
@@ -0,0 +1,129 @@
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
+ import type { NativeSourceChangeRegion, NativeSourceChangeSet } from './native-diff.js';
9
+ import type { SemanticHistoryRecord } from './semantic-history.js';
10
+ import type { SemanticLineageEvent, SemanticLineageResolution } from './semantic-lineage.js';
11
+ import type { SemanticPatchBundleRecord } from './semantic-patch-bundle.js';
12
+
13
+ export type BidirectionalTargetChangeAnchorStatus = 'matched' | 'unmatched' | 'ambiguous' | 'deleted' | string;
14
+
15
+ export interface BidirectionalTargetChangeSourceAnchorMapping {
16
+ readonly targetAnchorKey?: string;
17
+ readonly targetRegionKey?: string;
18
+ readonly targetConflictKey?: string;
19
+ readonly targetKey?: string;
20
+ readonly targetSymbolName?: string;
21
+ readonly targetSymbolId?: string;
22
+ readonly sourceAnchorKey?: string;
23
+ readonly sourceRegionKey?: string;
24
+ readonly sourceConflictKey?: string;
25
+ readonly sourceKey?: string;
26
+ readonly sourceSymbolName?: string;
27
+ readonly sourceSymbolId?: string;
28
+ }
29
+
30
+ export interface BidirectionalTargetChangeAnchor {
31
+ readonly id?: string;
32
+ readonly key?: string;
33
+ readonly kind?: string;
34
+ readonly language?: FrontierSourceLanguage | string;
35
+ readonly sourcePath?: string;
36
+ readonly sourceHash?: string;
37
+ readonly symbolId?: string;
38
+ readonly symbolName?: string;
39
+ readonly sourceSpan?: SourceSpan;
40
+ readonly metadata?: Record<string, unknown>;
41
+ }
42
+
43
+ export interface BidirectionalTargetChangeSourceAnchorMatch {
44
+ readonly kind: 'frontier.lang.bidirectionalTargetChangeSourceAnchorMatch';
45
+ readonly version: 1;
46
+ readonly id: string;
47
+ readonly targetRegion: Partial<NativeSourceChangeRegion>;
48
+ readonly sourceAnchors: readonly BidirectionalTargetChangeAnchor[];
49
+ readonly lineageResolutions: readonly SemanticLineageResolution[];
50
+ readonly status: BidirectionalTargetChangeAnchorStatus;
51
+ readonly confidence?: number;
52
+ readonly reasonCodes: readonly string[];
53
+ readonly reviewRequired: true;
54
+ readonly autoMergeClaim: false;
55
+ readonly semanticEquivalenceClaim: false;
56
+ readonly conflictKeys: readonly string[];
57
+ }
58
+
59
+ export interface CreateBidirectionalTargetChangeRecordOptions {
60
+ readonly id?: string;
61
+ readonly source?: NativeSourceImportResult | ImportNativeSourceOptions;
62
+ readonly sourceImport?: NativeSourceImportResult | ImportNativeSourceOptions;
63
+ readonly baseSource?: NativeSourceImportResult | ImportNativeSourceOptions;
64
+ readonly sourceLanguage?: FrontierSourceLanguage | string;
65
+ readonly sourcePath?: string;
66
+ readonly sourceParser?: string;
67
+ readonly targetChangeSet?: NativeSourceChangeSet;
68
+ readonly targetChangeSetId?: string;
69
+ readonly language?: FrontierSourceLanguage | string;
70
+ readonly targetLanguage?: FrontierSourceLanguage | string;
71
+ readonly targetPath?: string;
72
+ readonly baseTarget?: NativeSourceImportResult | ImportNativeSourceOptions;
73
+ readonly beforeTarget?: NativeSourceImportResult | ImportNativeSourceOptions;
74
+ readonly before?: NativeSourceImportResult | ImportNativeSourceOptions;
75
+ readonly editedTarget?: NativeSourceImportResult | ImportNativeSourceOptions;
76
+ readonly afterTarget?: NativeSourceImportResult | ImportNativeSourceOptions;
77
+ readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
78
+ readonly sourceAnchorMappings?: readonly BidirectionalTargetChangeSourceAnchorMapping[];
79
+ readonly anchorMappings?: readonly BidirectionalTargetChangeSourceAnchorMapping[];
80
+ readonly lineage?: readonly SemanticLineageEvent[];
81
+ readonly lineageEvents?: readonly SemanticLineageEvent[];
82
+ readonly lineageMap?: unknown;
83
+ readonly targetEvidenceId?: string;
84
+ readonly targetPatchId?: string;
85
+ readonly targetMergeCandidateId?: string;
86
+ readonly sourceRegionPrefix?: string;
87
+ readonly sourcePatchBundleId?: string;
88
+ readonly historyRecordId?: string;
89
+ readonly evidenceId?: string;
90
+ readonly generatedAt?: number | string;
91
+ readonly metadata?: Record<string, unknown>;
92
+ }
93
+
94
+ export interface BidirectionalTargetChangeRecord {
95
+ readonly kind: 'frontier.lang.bidirectionalTargetChangeRecord';
96
+ readonly version: 1;
97
+ readonly id: string;
98
+ readonly sourceLanguage?: FrontierSourceLanguage | string;
99
+ readonly targetLanguage?: FrontierSourceLanguage | string;
100
+ readonly sourcePath?: string;
101
+ readonly targetPath?: string;
102
+ readonly sourceImport?: NativeSourceImportResult;
103
+ readonly targetChangeSet: NativeSourceChangeSet;
104
+ readonly sourceAnchorMatches: readonly BidirectionalTargetChangeSourceAnchorMatch[];
105
+ readonly sourcePatchBundle: SemanticPatchBundleRecord;
106
+ readonly historyRecord: SemanticHistoryRecord;
107
+ readonly evidence: readonly EvidenceRecord[];
108
+ readonly readiness: SemanticMergeReadiness | string;
109
+ readonly reasons: readonly string[];
110
+ readonly summary: {
111
+ readonly targetChangedRegions: number;
112
+ readonly sourceAnchorMatches: number;
113
+ readonly ambiguousMatches: number;
114
+ readonly unmatchedTargetRegions: number;
115
+ readonly deletedSourceAnchors: number;
116
+ readonly sourceChangedRegions: number;
117
+ };
118
+ readonly metadata: {
119
+ readonly autoMergeClaim: false;
120
+ readonly semanticEquivalenceClaim: false;
121
+ readonly reviewRequired: true;
122
+ readonly [key: string]: unknown;
123
+ };
124
+ }
125
+
126
+ export declare function createBidirectionalTargetChangeRecord(
127
+ input?: CreateBidirectionalTargetChangeRecordOptions,
128
+ options?: Record<string, unknown>
129
+ ): BidirectionalTargetChangeRecord;
@@ -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.d.ts CHANGED
@@ -19,6 +19,7 @@ export * from './declarations/semantic-merge-conflicts.js';
19
19
  export * from './declarations/semantic-lineage.js';
20
20
  export * from './declarations/semantic-history.js';
21
21
  export * from './declarations/semantic-patch-bundle.js';
22
+ export * from './declarations/bidirectional-target-change.js';
22
23
  export * from './declarations/semantic-impact.js';
23
24
  export * from './declarations/semantic-sidecar.js';
24
25
  export * from './declarations/native-diff.js';
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export { compileFrontierDocument } from './internal/index-impl/compileFrontierDo
4
4
  export { compileFrontierSource } from './internal/index-impl/compileFrontierSource.js';
5
5
  export { compileNativeSource } from './internal/index-impl/compileNativeSource.js';
6
6
  export { createBabelNativeImporterAdapter } from './internal/index-impl/createBabelNativeImporterAdapter.js';
7
+ export { createBidirectionalTargetChangeRecord } from './internal/index-impl/createBidirectionalTargetChangeRecord.js';
7
8
  export { createClangAstNativeImporterAdapter } from './internal/index-impl/createClangAstNativeImporterAdapter.js';
8
9
  export { createCSharpRoslynNativeImporterAdapter } from './internal/index-impl/createCSharpRoslynNativeImporterAdapter.js';
9
10
  export { createEstreeNativeImporterAdapter } from './internal/index-impl/createEstreeNativeImporterAdapter.js';
@@ -45,6 +46,7 @@ export { getNativeParserAstFormatProfile } from './internal/index-impl/getNative
45
46
  export { importExternalSemanticIndex } from './internal/index-impl/importExternalSemanticIndex.js';
46
47
  export { importNativeProject } from './internal/index-impl/importNativeProject.js';
47
48
  export { importNativeSource } from './internal/index-impl/importNativeSource.js';
49
+ export { inferSemanticLineageEvents } from './internal/index-impl/inferSemanticLineageEvents.js';
48
50
  export { createLanguageAdapterPackageContract, getLanguageAdapterPackageContract, LanguageAdapterPackageContracts, LanguageAdapterPackageReleaseReadinessStatuses, queryLanguageAdapterPackageContracts, summarizeLanguageAdapterPackageContracts } from './language-adapter-package-contracts.js';
49
51
  export { NativeImportFeatureEvidencePolicies } from './internal/index-impl/NativeImportFeatureEvidencePolicies.js';
50
52
  export { NativeImportLanguageProfiles } from './coverage-matrix-profiles.js';
@@ -0,0 +1,198 @@
1
+ import {
2
+ idFragment,
3
+ maxSemanticMergeReadiness,
4
+ uniqueRecordsById,
5
+ uniqueStrings
6
+ } from '../../native-import-utils.js';
7
+ import { resolveSemanticLineage } from './semanticLineageResolutionRecords.js';
8
+
9
+ export function matchTargetRegion(context) {
10
+ const explicit = explicitMappingForRegion(context.region, context.mappings);
11
+ const candidateAnchors = explicit
12
+ ? sourceAnchorsForMapping(explicit, context.sourceAnchors)
13
+ : sourceAnchorsByName(context.region, context.sourceAnchors);
14
+ const resolvedAnchors = candidateAnchors.flatMap((anchor) => resolveAnchor(anchor, context.lineage));
15
+ const status = matchStatus(candidateAnchors, resolvedAnchors);
16
+ const reasonCodes = uniqueStrings([
17
+ explicit ? 'explicit-anchor-mapping' : 'inferred-by-symbol-name',
18
+ status === 'unmatched' ? 'source-anchor-not-found' : undefined,
19
+ status === 'ambiguous' ? 'source-anchor-ambiguous' : undefined,
20
+ status === 'deleted' ? 'source-anchor-deleted' : undefined,
21
+ 'human-source-port-required'
22
+ ]);
23
+ return {
24
+ kind: 'frontier.lang.bidirectionalTargetChangeSourceAnchorMatch',
25
+ version: 1,
26
+ id: `source_anchor_match_${idFragment(context.id)}_${context.index + 1}`,
27
+ targetRegion: compactRegion(context.region),
28
+ sourceAnchors: resolvedAnchors.map((entry) => entry.anchor),
29
+ lineageResolutions: uniqueRecordsById(resolvedAnchors.map((entry) => entry.resolution).filter(Boolean)),
30
+ status,
31
+ confidence: status === 'matched' && explicit ? 0.8 : status === 'matched' ? 0.58 : undefined,
32
+ reasonCodes,
33
+ reviewRequired: true,
34
+ autoMergeClaim: false,
35
+ semanticEquivalenceClaim: false,
36
+ conflictKeys: uniqueStrings([
37
+ context.region.conflictKey,
38
+ context.region.key,
39
+ ...resolvedAnchors.map((entry) => entry.anchor?.key),
40
+ status === 'unmatched' ? `target:${context.region.sourcePath ?? context.region.language ?? 'unknown'}` : undefined
41
+ ])
42
+ };
43
+ }
44
+
45
+ export function classifyBidirectionalReadiness(targetChangeSet, source, matches) {
46
+ let readiness = maxSemanticMergeReadiness(targetChangeSet.readiness, 'needs-review');
47
+ if (!source) readiness = 'blocked';
48
+ if (matches.some((match) => match.status === 'deleted')) readiness = 'blocked';
49
+ if (matches.every((match) => match.status === 'unmatched') && matches.length > 0) readiness = 'blocked';
50
+ return readiness;
51
+ }
52
+
53
+ export function sourceRegionsForMatch(match, readiness) {
54
+ const anchors = match.sourceAnchors.length ? match.sourceAnchors : [undefined];
55
+ return anchors.map((anchor, index) => compactRecord({
56
+ id: `source_port_region_${idFragment(match.id)}_${index + 1}`,
57
+ key: anchor?.key ?? `unmapped-target#${match.targetRegion.key ?? match.targetRegion.id}`,
58
+ conflictKey: anchor?.key ?? match.targetRegion.conflictKey ?? match.targetRegion.key,
59
+ changeKind: match.targetRegion.changeKind,
60
+ regionKind: anchor?.kind ?? match.targetRegion.regionKind,
61
+ granularity: 'symbol',
62
+ language: anchor?.language,
63
+ sourcePath: anchor?.sourcePath,
64
+ sourceHash: anchor?.sourceHash,
65
+ symbolId: anchor?.symbolId,
66
+ symbolName: anchor?.symbolName,
67
+ sourceSpan: anchor?.sourceSpan,
68
+ admission: {
69
+ readiness,
70
+ action: 'review-port-from-target-change',
71
+ reasonCodes: match.reasonCodes,
72
+ conflictKeys: match.conflictKeys
73
+ },
74
+ metadata: {
75
+ bidirectionalTargetChange: {
76
+ matchId: match.id,
77
+ targetRegion: match.targetRegion,
78
+ lineageResolutionIds: match.lineageResolutions.map((resolution) => resolution.id),
79
+ reviewRequired: true,
80
+ autoMergeClaim: false,
81
+ semanticEquivalenceClaim: false
82
+ }
83
+ }
84
+ }));
85
+ }
86
+
87
+ export function createBidirectionalEvidence(context) {
88
+ return {
89
+ id: context.input.evidenceId ?? `evidence_${idFragment(context.id)}_bidirectional_target_change`,
90
+ kind: 'semantic-merge',
91
+ status: 'passed',
92
+ path: context.source?.sourcePath ?? context.targetChangeSet.sourcePath,
93
+ summary: `Mapped ${context.targetChangeSet.changedRegions.length} target changed region(s) to ${matchedCount(context.sourceAnchorMatches)} source anchor match(es).`,
94
+ metadata: {
95
+ schema: 'frontier.lang.bidirectionalTargetChangeEvidence.v1',
96
+ sourceImportId: context.source?.id,
97
+ targetChangeSetId: context.targetChangeSet.id,
98
+ targetPatchId: context.targetChangeSet.patch?.id,
99
+ sourceAnchorMatchIds: context.sourceAnchorMatches.map((match) => match.id),
100
+ readiness: context.readiness,
101
+ reasons: context.reasons,
102
+ autoMergeClaim: false,
103
+ semanticEquivalenceClaim: false
104
+ }
105
+ };
106
+ }
107
+
108
+ export function anchorsFromSourceSidecar(sidecar, source) {
109
+ return uniqueRecordsById((sidecar.ownershipRegions ?? []).map((region) => compactRecord({
110
+ id: region.id,
111
+ key: region.key,
112
+ kind: region.regionKind,
113
+ language: region.language ?? source?.language,
114
+ sourcePath: region.sourcePath ?? source?.sourcePath,
115
+ sourceHash: region.sourceHash ?? sourceHash(source),
116
+ symbolId: region.symbolId,
117
+ symbolName: region.symbolName,
118
+ sourceSpan: region.sourceSpan,
119
+ metadata: region.metadata
120
+ })));
121
+ }
122
+
123
+ export function sourceHash(source) {
124
+ return source?.nativeSource?.sourceHash ?? source?.nativeAst?.sourceHash ?? source?.sourceHash;
125
+ }
126
+
127
+ function explicitMappingForRegion(region, mappings) {
128
+ return mappings.find((mapping) => {
129
+ const targetKeys = uniqueStrings([
130
+ mapping.targetAnchorKey,
131
+ mapping.targetRegionKey,
132
+ mapping.targetConflictKey,
133
+ mapping.targetKey
134
+ ]);
135
+ if (targetKeys.some((key) => key === region.key || key === region.conflictKey || key === region.id)) return true;
136
+ if (mapping.targetSymbolName && mapping.targetSymbolName === (region.symbolName ?? region.name)) return true;
137
+ return mapping.targetSymbolId && mapping.targetSymbolId === region.symbolId;
138
+ });
139
+ }
140
+
141
+ function sourceAnchorsForMapping(mapping, sourceAnchors) {
142
+ const keys = uniqueStrings([mapping.sourceAnchorKey, mapping.sourceRegionKey, mapping.sourceConflictKey, mapping.sourceKey]);
143
+ const matches = sourceAnchors.filter((anchor) => keys.includes(anchor.key) || keys.includes(anchor.id));
144
+ if (matches.length) return matches;
145
+ if (mapping.sourceSymbolName) return sourceAnchors.filter((anchor) => anchor.symbolName === mapping.sourceSymbolName);
146
+ if (mapping.sourceSymbolId) return sourceAnchors.filter((anchor) => anchor.symbolId === mapping.sourceSymbolId);
147
+ return [];
148
+ }
149
+
150
+ function sourceAnchorsByName(region, sourceAnchors) {
151
+ const names = uniqueStrings([region.symbolName, region.name, region.metadata?.changedRegionProjection?.region?.symbolName]);
152
+ if (names.length === 0) return [];
153
+ return sourceAnchors.filter((anchor) => names.includes(anchor.symbolName));
154
+ }
155
+
156
+ function resolveAnchor(anchor, lineage) {
157
+ if (!anchor) return [];
158
+ const resolution = array(lineage).length ? resolveSemanticLineage(lineage, { anchorKey: anchor.key }) : undefined;
159
+ if (!resolution || resolution.status === 'unchanged') return [{ anchor, resolution }];
160
+ if (resolution.status === 'deleted') return [{ anchor, resolution }];
161
+ if (resolution.currentAnchors.length === 0) return [{ anchor, resolution }];
162
+ return resolution.currentAnchors.map((current) => ({ anchor: { ...anchor, ...current }, resolution }));
163
+ }
164
+
165
+ function matchStatus(originalAnchors, resolvedAnchors) {
166
+ if (originalAnchors.length === 0 || resolvedAnchors.length === 0) return 'unmatched';
167
+ if (resolvedAnchors.some((entry) => entry.resolution?.status === 'deleted')) return 'deleted';
168
+ if (resolvedAnchors.length > 1 || resolvedAnchors.some((entry) => entry.resolution?.status === 'ambiguous')) return 'ambiguous';
169
+ return 'matched';
170
+ }
171
+
172
+ function matchedCount(matches) {
173
+ return matches.filter((match) => match.status === 'matched').length;
174
+ }
175
+
176
+ function compactRegion(region) {
177
+ return compactRecord({
178
+ id: region.id,
179
+ key: region.key,
180
+ conflictKey: region.conflictKey,
181
+ changeKind: region.changeKind,
182
+ regionKind: region.regionKind,
183
+ language: region.language,
184
+ sourcePath: region.sourcePath,
185
+ sourceHash: region.sourceHash,
186
+ symbolId: region.symbolId,
187
+ symbolName: region.symbolName ?? region.name,
188
+ sourceSpan: region.sourceSpan
189
+ });
190
+ }
191
+
192
+ function array(value) {
193
+ return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value];
194
+ }
195
+
196
+ function compactRecord(value) {
197
+ return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
198
+ }
@@ -0,0 +1,169 @@
1
+ import {
2
+ idFragment,
3
+ uniqueStrings
4
+ } from '../../native-import-utils.js';
5
+ import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
6
+ import { createSemanticPatchBundleRecord } from './semanticPatchBundleRecords.js';
7
+ import { createSemanticHistoryRecord } from './semanticHistoryRecords.js';
8
+ import { diffNativeSourceImports } from './diffNativeSourceImports.js';
9
+ import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
10
+ import {
11
+ anchorsFromSourceSidecar,
12
+ classifyBidirectionalReadiness,
13
+ createBidirectionalEvidence,
14
+ matchTargetRegion,
15
+ sourceHash,
16
+ sourceRegionsForMatch
17
+ } from './bidirectionalTargetChangeRecordInternals.js';
18
+
19
+ export function createBidirectionalTargetChangeRecord(input = {}, options = {}) {
20
+ const source = normalizeNativeDiffImport(
21
+ input.source ?? input.sourceImport ?? input.baseSource,
22
+ { language: input.sourceLanguage, sourcePath: input.sourcePath, parser: input.sourceParser },
23
+ 'source'
24
+ );
25
+ const targetChangeSet = input.targetChangeSet ?? diffNativeSourceImports({
26
+ id: input.targetChangeSetId ?? input.id,
27
+ language: input.targetLanguage ?? input.language,
28
+ sourcePath: input.targetPath ?? input.sourcePath,
29
+ before: input.baseTarget ?? input.beforeTarget ?? input.before,
30
+ after: input.editedTarget ?? input.afterTarget ?? input.after,
31
+ evidenceId: input.targetEvidenceId,
32
+ patchId: input.targetPatchId,
33
+ mergeCandidateId: input.targetMergeCandidateId,
34
+ metadata: { direction: 'target-to-source', ...(input.targetMetadata ?? {}) }
35
+ });
36
+ const id = input.id ?? `bidirectional_target_change_${idFragment([
37
+ source?.sourcePath,
38
+ targetChangeSet.sourcePath,
39
+ targetChangeSet.id
40
+ ].filter(Boolean).join('_'))}`;
41
+ const sourceSidecar = source ? createSemanticImportSidecar(source, {
42
+ id: `sidecar_source_${idFragment(id)}`,
43
+ generatedAt: input.generatedAt,
44
+ regionPrefix: input.sourceRegionPrefix
45
+ }) : undefined;
46
+ const sourceAnchors = sourceSidecar ? anchorsFromSourceSidecar(sourceSidecar, source) : [];
47
+ const mappings = array(input.sourceAnchorMappings ?? input.anchorMappings);
48
+ const lineage = input.lineage ?? input.lineageEvents ?? input.lineageMap ?? [];
49
+ const sourceAnchorMatches = targetChangeSet.changedRegions.map((region, index) => matchTargetRegion({
50
+ id,
51
+ region,
52
+ index,
53
+ source,
54
+ sourceAnchors,
55
+ mappings,
56
+ lineage
57
+ }));
58
+ const readiness = classifyBidirectionalReadiness(targetChangeSet, source, sourceAnchorMatches);
59
+ const reasons = uniqueStrings([
60
+ 'source-port-review-required',
61
+ 'target-change-is-merge-evidence-not-proof',
62
+ ...array(targetChangeSet.reasons),
63
+ ...sourceAnchorMatches.flatMap((match) => match.reasonCodes)
64
+ ]);
65
+ const evidence = [createBidirectionalEvidence({
66
+ id,
67
+ input,
68
+ source,
69
+ targetChangeSet,
70
+ sourceAnchorMatches,
71
+ readiness,
72
+ reasons
73
+ })];
74
+ const sourceChangedRegions = sourceAnchorMatches.flatMap((match) => sourceRegionsForMatch(match, readiness));
75
+ const sourcePatchBundle = createSemanticPatchBundleRecord({
76
+ id: `${id}_source_port_projection`,
77
+ language: source?.language,
78
+ sourcePath: source?.sourcePath,
79
+ baseHash: sourceHash(source),
80
+ changedRegions: sourceChangedRegions,
81
+ evidence,
82
+ readiness,
83
+ reasons,
84
+ conflictKeys: uniqueStrings(sourceAnchorMatches.flatMap((match) => match.conflictKeys)),
85
+ metadata: {
86
+ projectionOnly: true,
87
+ targetChangeSetId: targetChangeSet.id,
88
+ targetPatchId: targetChangeSet.patch?.id,
89
+ targetMergeCandidateId: targetChangeSet.mergeCandidate?.id
90
+ }
91
+ }, {
92
+ id: input.sourcePatchBundleId ?? `semantic_patch_bundle_${idFragment(id)}_source_port`,
93
+ patchId: targetChangeSet.patch?.id,
94
+ mergeCandidateId: targetChangeSet.mergeCandidate?.id,
95
+ admission: { status: readiness === 'blocked' ? 'blocked' : 'needs-review', readiness },
96
+ metadata: {
97
+ source: 'createBidirectionalTargetChangeRecord',
98
+ autoMergeClaim: false,
99
+ semanticEquivalenceClaim: false
100
+ }
101
+ });
102
+ const historyRecord = createSemanticHistoryRecord({
103
+ id: input.historyRecordId ?? `semantic_history_${idFragment(id)}_target_change`,
104
+ importResult: source,
105
+ language: source?.language,
106
+ sourcePath: source?.sourcePath,
107
+ baseHash: sourceHash(source),
108
+ targetHash: sourceHash(source),
109
+ ownershipRegions: sourceChangedRegions,
110
+ semanticCandidates: [targetChangeSet.mergeCandidate].filter(Boolean),
111
+ evidence,
112
+ evidenceIds: evidence.map((record) => record.id),
113
+ patchAncestry: targetChangeSet.patch ? [{
114
+ patchId: targetChangeSet.patch.id,
115
+ baseHash: targetChangeSet.beforeHash,
116
+ targetHash: targetChangeSet.afterHash,
117
+ conflictKeys: targetChangeSet.mergeCandidate?.conflictKeys,
118
+ metadata: { direction: 'target-to-source' }
119
+ }] : [],
120
+ admission: { status: readiness === 'blocked' ? 'blocked' : 'needs-review', readiness, reasonCodes: reasons },
121
+ replayLinks: targetChangeSet.patch ? [{
122
+ id: `replay_${idFragment(targetChangeSet.patch.id)}`,
123
+ kind: 'patch',
124
+ path: targetChangeSet.patch.id
125
+ }] : [],
126
+ metadata: {
127
+ bidirectionalTargetChangeId: id,
128
+ sourcePatchBundleId: sourcePatchBundle.id,
129
+ targetChangeSetId: targetChangeSet.id,
130
+ autoMergeClaim: false,
131
+ semanticEquivalenceClaim: false
132
+ }
133
+ }, options);
134
+ return {
135
+ kind: 'frontier.lang.bidirectionalTargetChangeRecord',
136
+ version: 1,
137
+ id,
138
+ sourceLanguage: source?.language,
139
+ targetLanguage: targetChangeSet.language,
140
+ sourcePath: source?.sourcePath,
141
+ targetPath: targetChangeSet.sourcePath,
142
+ sourceImport: source,
143
+ targetChangeSet,
144
+ sourceAnchorMatches,
145
+ sourcePatchBundle,
146
+ historyRecord,
147
+ evidence,
148
+ readiness,
149
+ reasons,
150
+ summary: {
151
+ targetChangedRegions: targetChangeSet.changedRegions.length,
152
+ sourceAnchorMatches: sourceAnchorMatches.filter((match) => match.status === 'matched').length,
153
+ ambiguousMatches: sourceAnchorMatches.filter((match) => match.status === 'ambiguous').length,
154
+ unmatchedTargetRegions: sourceAnchorMatches.filter((match) => match.status === 'unmatched').length,
155
+ deletedSourceAnchors: sourceAnchorMatches.filter((match) => match.status === 'deleted').length,
156
+ sourceChangedRegions: sourceChangedRegions.length
157
+ },
158
+ metadata: {
159
+ autoMergeClaim: false,
160
+ semanticEquivalenceClaim: false,
161
+ reviewRequired: true,
162
+ ...input.metadata
163
+ }
164
+ };
165
+ }
166
+
167
+ function array(value) {
168
+ return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value];
169
+ }
@@ -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.75",
3
+ "version": "0.2.77",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",