@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 +48 -0
- package/bench/smoke.mjs +5 -0
- package/bench/source-change-suite.mjs +60 -1
- package/dist/declarations/bidirectional-target-change.d.ts +29 -0
- package/dist/declarations/native-diff.d.ts +3 -0
- package/dist/declarations/semantic-lineage.d.ts +96 -0
- package/dist/index.js +1 -0
- package/dist/internal/index-impl/bidirectionalTargetChangeRecordInternals.js +105 -6
- package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +25 -2
- package/dist/internal/index-impl/diffNativeSourceImports.js +16 -1
- package/dist/internal/index-impl/inferSemanticLineageEvents.js +214 -0
- package/dist/internal/index-impl/semanticLineageInferenceMatching.js +167 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
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 =
|
|
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