@shapeshift-labs/frontier-lang-compiler 0.2.100 → 0.2.102
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/dist/declarations/bidirectional-target-change-evidence.d.ts +299 -0
- package/dist/declarations/bidirectional-target-change.d.ts +19 -120
- package/dist/declarations/import-adapter-core.d.ts +6 -0
- package/dist/declarations/native-project-admission.d.ts +43 -22
- package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +24 -0
- package/dist/declarations/semantic-edit-script.d.ts +20 -15
- package/dist/declarations/semantic-lineage.d.ts +3 -21
- package/dist/declarations/semantic-merge-candidates.d.ts +39 -0
- package/dist/declarations/semantic-sidecar-admission.d.ts +14 -0
- package/dist/declarations/semantic-sidecar.d.ts +12 -14
- package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +200 -0
- package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +62 -17
- package/dist/internal/index-impl/createLightweightNativeImport.js +9 -1
- package/dist/internal/index-impl/createNativeSourcePreservation.js +16 -1
- package/dist/internal/index-impl/createProjectImportAdmissionRecord.js +151 -1
- package/dist/internal/index-impl/createSemanticImportSidecar.js +5 -0
- package/dist/internal/index-impl/createSemanticImportSidecarAdmission.js +29 -11
- package/dist/internal/index-impl/importNativeSource.js +14 -14
- package/dist/internal/index-impl/nativeChangeProjectionEndpoint.js +56 -16
- package/dist/internal/index-impl/nativeImportSemanticIndex.js +33 -0
- package/dist/internal/index-impl/projectImportAdmissionMergeScore.js +26 -74
- package/dist/internal/index-impl/projectImportAdmissionProjectionCoverage.js +74 -0
- package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +39 -13
- package/dist/internal/index-impl/replaySemanticEditProjection.js +65 -23
- package/dist/internal/index-impl/semanticEditInsertionAnchors.js +8 -5
- package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +167 -0
- package/dist/internal/index-impl/semanticEditSourceRanges.js +94 -15
- package/dist/internal/index-impl/semanticHistoryLineageResolution.js +21 -2
- package/dist/internal/index-impl/semanticLineageHashEvidence.js +97 -0
- package/dist/internal/index-impl/semanticLineageInferenceMatching.js +8 -0
- package/dist/internal/index-impl/semanticLineageResolutionRecords.js +18 -1
- package/dist/internal/index-impl/semanticMergeCandidateRecords.js +22 -2
- package/dist/internal/index-impl/semanticMergeCandidateScoreFacets.js +221 -0
- package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +23 -1
- package/dist/internal/index-impl/sourcePreservationFromProjectionContext.js +9 -2
- package/dist/native-import-language-profiles.js +10 -2
- package/dist/native-region-scanner-js-helpers.js +8 -2
- package/dist/native-region-scanner-js-imports.js +7 -0
- package/dist/native-region-scanner-js.js +4 -4
- package/dist/native-region-scanner.js +2 -1
- package/dist/semantic-import-regions.js +18 -5
- package/dist/semantic-import-sidecar-admission-types.d.ts +14 -0
- package/dist/semantic-import-sidecar-entry.js +151 -7
- package/dist/semantic-import-sidecar-types.d.ts +18 -13
- package/dist/semantic-import-source-preservation-utils.js +55 -0
- package/dist/semantic-import-source-preservation.js +98 -3
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ export function spanOffsets(sourceText, span) {
|
|
|
24
24
|
const endLineStart = lineStarts[endLine - 1];
|
|
25
25
|
if (start === undefined || endLineStart === undefined) return undefined;
|
|
26
26
|
const startColumn = Math.max(1, span.startColumn ?? 1) - 1;
|
|
27
|
-
const lineEnd =
|
|
27
|
+
const lineEnd = lineContentEndOffset(sourceText, lineStarts[endLine]);
|
|
28
28
|
const endColumn = span.endColumn === undefined ? lineEnd - endLineStart : Math.max(1, span.endColumn) - 1;
|
|
29
29
|
return { start: start + startColumn, end: endLineStart + endColumn };
|
|
30
30
|
}
|
|
@@ -49,36 +49,41 @@ export function bodyContentRange(sourceText, range) {
|
|
|
49
49
|
return pair ? { start: pair.open + 1, end: pair.close } : undefined;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export function insertionOffset(sourceText, insertion) {
|
|
52
|
+
export function insertionOffset(sourceText, insertion, context = {}) {
|
|
53
53
|
if (typeof sourceText !== 'string') return { ok: false, reasonCodes: ['missing-head-source-text'] };
|
|
54
54
|
const mode = insertion?.mode;
|
|
55
55
|
if (mode === 'file-start') return { ok: true, offset: 0 };
|
|
56
56
|
if (mode === 'file-end') return { ok: true, offset: sourceText.length };
|
|
57
|
-
const
|
|
58
|
-
if (!range) return { ok: false, reasonCodes: ['insertion-anchor-not-resolvable'] };
|
|
59
|
-
if (mode === 'before') return { ok: true, offset: range.start };
|
|
60
|
-
if (mode === 'after') return { ok: true, offset: afterLineOffset(sourceText, range.end) };
|
|
57
|
+
const resolved = insertionAnchorResolution(sourceText, insertion, context);
|
|
58
|
+
if (!resolved?.range) return { ok: false, reasonCodes: ['insertion-anchor-not-resolvable'] };
|
|
59
|
+
if (resolved.mode === 'before') return { ok: true, offset: resolved.range.start };
|
|
60
|
+
if (resolved.mode === 'after') return { ok: true, offset: afterLineOffset(sourceText, resolved.range.end) };
|
|
61
61
|
return { ok: false, reasonCodes: ['insertion-mode-unsupported'] };
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export function removalRange(sourceText, span) {
|
|
65
65
|
const range = { ...span };
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const next = lineBreakEndOffset(sourceText, range.end);
|
|
67
|
+
if (next !== range.end) range.end = next;
|
|
68
|
+
else {
|
|
69
|
+
const previous = previousLineBreakStartOffset(sourceText, range.start);
|
|
70
|
+
if (previous !== range.start) range.start = previous;
|
|
71
|
+
}
|
|
68
72
|
return range;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
export function insertionReplacement(text, sourceText, offset) {
|
|
72
76
|
let replacement = String(text ?? '');
|
|
73
|
-
|
|
74
|
-
if (offset
|
|
75
|
-
if (offset
|
|
76
|
-
if (offset === sourceText.length && !
|
|
77
|
+
const newline = sourceLineEnding(sourceText);
|
|
78
|
+
if (offset > 0 && !isLineBreak(sourceText[offset - 1])) replacement = `${newline}${replacement}`;
|
|
79
|
+
if (offset < sourceText.length && !endsWithLineBreak(replacement)) replacement += newline;
|
|
80
|
+
if (offset === sourceText.length && sourceText && !endsWithLineBreak(sourceText)) replacement = `${newline}${replacement}`;
|
|
81
|
+
if (offset === sourceText.length && !endsWithLineBreak(replacement)) replacement += newline;
|
|
77
82
|
return replacement;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
export function afterLineOffset(sourceText, offset) {
|
|
81
|
-
return sourceText
|
|
86
|
+
return lineBreakEndOffset(sourceText, offset);
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
function isProjectionCoverableContainer(operation) {
|
|
@@ -112,15 +117,89 @@ function containedRange(inner, outer) {
|
|
|
112
117
|
|
|
113
118
|
function insertionRemovalRange(sourceText, span, container) {
|
|
114
119
|
const range = { ...span };
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
const next = lineBreakEndOffset(sourceText, range.end);
|
|
121
|
+
if (next !== range.end && next <= container.end) range.end = next;
|
|
122
|
+
else {
|
|
123
|
+
const previous = previousLineBreakStartOffset(sourceText, range.start);
|
|
124
|
+
if (previous !== range.start && previous >= container.start) range.start = previous;
|
|
125
|
+
}
|
|
117
126
|
return range;
|
|
118
127
|
}
|
|
119
128
|
|
|
129
|
+
function lineContentEndOffset(sourceText, nextLineStart) {
|
|
130
|
+
if (nextLineStart === undefined) return sourceText.length;
|
|
131
|
+
const lineBreakStart = sourceText[nextLineStart - 2] === '\r' ? nextLineStart - 2 : nextLineStart - 1;
|
|
132
|
+
return Math.max(0, lineBreakStart);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function lineBreakEndOffset(sourceText, offset) {
|
|
136
|
+
if (sourceText[offset] === '\r' && sourceText[offset + 1] === '\n') return offset + 2;
|
|
137
|
+
if (isLineBreak(sourceText[offset])) return offset + 1;
|
|
138
|
+
return offset;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function previousLineBreakStartOffset(sourceText, offset) {
|
|
142
|
+
if (sourceText[offset - 1] === '\n') return sourceText[offset - 2] === '\r' ? offset - 2 : offset - 1;
|
|
143
|
+
if (sourceText[offset - 1] === '\r') return offset - 1;
|
|
144
|
+
return offset;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function sourceLineEnding(sourceText) {
|
|
148
|
+
if (sourceText.includes('\r\n')) return '\r\n';
|
|
149
|
+
return sourceText.includes('\r') ? '\r' : '\n';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function endsWithLineBreak(value) {
|
|
153
|
+
return isLineBreak(value[value.length - 1]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isLineBreak(char) {
|
|
157
|
+
return char === '\n' || char === '\r';
|
|
158
|
+
}
|
|
159
|
+
|
|
120
160
|
function isBodyReplacement(operation) {
|
|
121
161
|
return operation.changeKind === 'modified' && (operation.kind === 'replaceBody' || operation.anchor?.regionKind === 'body');
|
|
122
162
|
}
|
|
123
163
|
|
|
164
|
+
function insertionAnchorResolution(sourceText, insertion, context) {
|
|
165
|
+
const candidates = insertionAnchorCandidates(insertion);
|
|
166
|
+
for (const candidate of candidates) {
|
|
167
|
+
const symbol = insertionAnchorSymbol(candidate, context.symbols);
|
|
168
|
+
const range = spanOffsets(sourceText, symbol?.sourceSpan);
|
|
169
|
+
if (range) return { mode: candidate.mode, range };
|
|
170
|
+
}
|
|
171
|
+
for (const candidate of candidates) {
|
|
172
|
+
const range = spanOffsets(sourceText, candidate.headSpan);
|
|
173
|
+
if (range) return { mode: candidate.mode, range };
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function insertionAnchorCandidates(insertion) {
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
const result = [];
|
|
181
|
+
for (const candidate of [insertion, ...(Array.isArray(insertion?.anchorCandidates) ? insertion.anchorCandidates : [])]) {
|
|
182
|
+
if (!candidate || (candidate.mode !== 'before' && candidate.mode !== 'after')) continue;
|
|
183
|
+
const key = [candidate.mode, candidate.anchorKey, candidate.anchorSymbolId, candidate.anchorSymbolName, candidate.anchorSymbolKind].join('\0');
|
|
184
|
+
if (seen.has(key)) continue;
|
|
185
|
+
seen.add(key);
|
|
186
|
+
result.push(candidate);
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function insertionAnchorSymbol(candidate, symbols) {
|
|
192
|
+
const symbolList = Array.isArray(symbols)
|
|
193
|
+
? symbols
|
|
194
|
+
: symbols?.values
|
|
195
|
+
? [...symbols.values()]
|
|
196
|
+
: [];
|
|
197
|
+
const keys = [candidate.anchorKey, candidate.anchorSymbolId].filter(Boolean);
|
|
198
|
+
const exact = symbolList.find((symbol) => [symbol.ownershipKey, symbol.key, symbol.id].some((key) => key && keys.includes(key)));
|
|
199
|
+
if (exact) return exact;
|
|
200
|
+
return symbolList.find((symbol) => symbol.name === candidate.anchorSymbolName && (!candidate.anchorSymbolKind || symbol.kind === candidate.anchorSymbolKind));
|
|
201
|
+
}
|
|
202
|
+
|
|
124
203
|
function trailingBodyCloseOffset(sourceText, range) {
|
|
125
204
|
if (typeof sourceText !== 'string' || !range) return undefined;
|
|
126
205
|
let index = range.end - 1;
|
|
@@ -137,12 +137,17 @@ function resolveIndexKeys(values, resolutions, options) {
|
|
|
137
137
|
const key = String(value);
|
|
138
138
|
const resolution = resolutions.get(key);
|
|
139
139
|
if (!resolution) return [key];
|
|
140
|
+
const current = resolution.currentAnchors.map((anchor) => anchor.key).filter(Boolean);
|
|
140
141
|
if (resolution.status === 'deleted' && options.keepDeletedAnchors !== true) return [];
|
|
141
142
|
if (resolution.status === 'cycle' && options.keepBlockedAnchors !== true) return [];
|
|
142
143
|
if (resolution.status === 'max-depth' && options.keepBlockedAnchors !== true) return [];
|
|
143
144
|
if (resolution.status === 'not-found' && options.keepUnresolvedAnchors !== true) return [];
|
|
145
|
+
if (resolution.status === 'ambiguous' && resolutionHasInactiveTerminal(resolution)) {
|
|
146
|
+
if (options.keepCandidateAnchors === true) return current.length ? current : [key];
|
|
147
|
+
if (options.keepDeletedAnchors === true || options.keepInactiveAnchors === true) return [key];
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
144
150
|
if (resolution.status === 'ambiguous' && options.keepCandidateAnchors === false) return [];
|
|
145
|
-
const current = resolution.currentAnchors.map((anchor) => anchor.key).filter(Boolean);
|
|
146
151
|
return current.length ? current : [key];
|
|
147
152
|
}));
|
|
148
153
|
}
|
|
@@ -193,7 +198,10 @@ function createAnchorInventory(resolutions) {
|
|
|
193
198
|
const current = resolution.currentAnchors.map((anchor) => anchorEntry(anchor, resolution));
|
|
194
199
|
if (resolution.status === 'ambiguous') {
|
|
195
200
|
inventory.candidate.push(...current);
|
|
196
|
-
if (start)
|
|
201
|
+
if (start) {
|
|
202
|
+
inventory.inactive.push(start);
|
|
203
|
+
if (resolutionHasDeletedTerminal(resolution)) inventory.deleted.push(start);
|
|
204
|
+
}
|
|
197
205
|
continue;
|
|
198
206
|
}
|
|
199
207
|
if (resolution.status === 'deleted') {
|
|
@@ -254,6 +262,17 @@ function anchorEntry(anchor, resolution) {
|
|
|
254
262
|
});
|
|
255
263
|
}
|
|
256
264
|
|
|
265
|
+
function resolutionHasInactiveTerminal(resolution) {
|
|
266
|
+
return array(resolution.terminalEventIds).length > 0
|
|
267
|
+
|| resolutionHasDeletedTerminal(resolution)
|
|
268
|
+
|| array(resolution.reasonCodes).some((code) => code === 'lineage-event-without-target-anchor' || code === 'inactive-anchor-has-active-candidates');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function resolutionHasDeletedTerminal(resolution) {
|
|
272
|
+
return array(resolution.lineageEventKinds).includes('deleted')
|
|
273
|
+
|| array(resolution.reasonCodes).includes('anchor-deleted');
|
|
274
|
+
}
|
|
275
|
+
|
|
257
276
|
function queryAnchor(resolution) {
|
|
258
277
|
return compactRecord({
|
|
259
278
|
key: resolution.query?.anchorKey,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { uniqueStrings } from '../../native-import-utils.js';
|
|
2
|
+
|
|
3
|
+
export function addIdentityHashEvidence(before, after, add, note) {
|
|
4
|
+
const matches = matchingIdentityHashReasons(before, after);
|
|
5
|
+
if (matches.length === 0) return;
|
|
6
|
+
if (!compatibleLineageSurface(before, after)) {
|
|
7
|
+
note('identity-hash-match-surface-mismatch');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const primary = matches.includes('semantic-identity-hash-match')
|
|
11
|
+
? 'semantic-identity-hash-match'
|
|
12
|
+
: matches.includes('source-identity-hash-match')
|
|
13
|
+
? 'source-identity-hash-match'
|
|
14
|
+
: 'identity-hash-match';
|
|
15
|
+
add(0.62, primary);
|
|
16
|
+
for (const reason of matches) {
|
|
17
|
+
if (reason !== primary) note(reason);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function addSourceHashEvidence(before, after, add, note, reasons, sameSymbolSurface) {
|
|
22
|
+
const beforeHash = firstString(before.anchor.sourceHash, before.sourceHash);
|
|
23
|
+
const afterHash = firstString(after.anchor.sourceHash, after.sourceHash);
|
|
24
|
+
if (!beforeHash || !afterHash) return;
|
|
25
|
+
if (beforeHash !== afterHash) {
|
|
26
|
+
note('source-hash-changed');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!hasSourceHashSupport(before, after, reasons, sameSymbolSurface)) {
|
|
30
|
+
note('source-hash-match-without-lineage-support');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
add(0.04, 'source-hash-match');
|
|
34
|
+
if (before.anchor.sourcePath && after.anchor.sourcePath && before.anchor.sourcePath !== after.anchor.sourcePath) {
|
|
35
|
+
note('source-hash-preserved-across-path');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function hashEvidenceSummary(reasons) {
|
|
40
|
+
return {
|
|
41
|
+
semanticIdentityHashMatch: reasons.includes('semantic-identity-hash-match'),
|
|
42
|
+
sourceIdentityHashMatch: reasons.includes('source-identity-hash-match'),
|
|
43
|
+
identityHashMatch: reasons.includes('identity-hash-match'),
|
|
44
|
+
sourceHashMatch: reasons.includes('source-hash-match'),
|
|
45
|
+
signatureHashMatch: reasons.includes('signature-hash-match'),
|
|
46
|
+
bodyHashMatch: reasons.includes('body-hash-match')
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function matchingIdentityHashReasons(before, after) {
|
|
51
|
+
const beforeHashes = identityHashEntries(before);
|
|
52
|
+
const afterHashes = new Map(identityHashEntries(after).map((entry) => [entry.value, entry.reason]));
|
|
53
|
+
const reasons = [];
|
|
54
|
+
for (const entry of beforeHashes) {
|
|
55
|
+
const afterReason = afterHashes.get(entry.value);
|
|
56
|
+
if (!afterReason) continue;
|
|
57
|
+
reasons.push(identityHashMatchReason(entry.reason, afterReason));
|
|
58
|
+
}
|
|
59
|
+
return uniqueStrings(reasons);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function identityHashEntries(symbol) {
|
|
63
|
+
return [
|
|
64
|
+
{ reason: 'semantic-identity-hash-match', value: firstString(symbol.semanticIdentityHash, symbol.anchor.metadata?.semanticIdentityHash) },
|
|
65
|
+
{ reason: 'source-identity-hash-match', value: firstString(symbol.sourceIdentityHash, symbol.anchor.metadata?.sourceIdentityHash) },
|
|
66
|
+
{ reason: 'identity-hash-match', value: firstString(symbol.identityHash, symbol.anchor.metadata?.identityHash) }
|
|
67
|
+
].filter((entry) => entry.value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function identityHashMatchReason(beforeReason, afterReason) {
|
|
71
|
+
if (beforeReason === afterReason) return beforeReason;
|
|
72
|
+
if (beforeReason === 'semantic-identity-hash-match' || afterReason === 'semantic-identity-hash-match') return 'semantic-identity-hash-match';
|
|
73
|
+
if (beforeReason === 'source-identity-hash-match' || afterReason === 'source-identity-hash-match') return 'source-identity-hash-match';
|
|
74
|
+
return 'identity-hash-match';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function compatibleLineageSurface(before, after) {
|
|
78
|
+
return (!before.language || !after.language || before.language === after.language)
|
|
79
|
+
&& (!before.kind || !after.kind || before.kind === after.kind)
|
|
80
|
+
&& (!before.anchor.kind || !after.anchor.kind || before.anchor.kind === after.anchor.kind);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hasSourceHashSupport(before, after, reasons, sameSymbolSurface) {
|
|
84
|
+
return reasons.some((reason) => [
|
|
85
|
+
'semantic-identity-hash-match',
|
|
86
|
+
'source-identity-hash-match',
|
|
87
|
+
'identity-hash-match',
|
|
88
|
+
'signature-hash-match',
|
|
89
|
+
'body-hash-match',
|
|
90
|
+
'symbol-name-match'
|
|
91
|
+
].includes(reason))
|
|
92
|
+
|| sameSymbolSurface(before, after);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function firstString(...values) {
|
|
96
|
+
return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean);
|
|
97
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { idFragment, uniqueStrings } from '../../native-import-utils.js';
|
|
2
|
+
import { addIdentityHashEvidence, addSourceHashEvidence, hashEvidenceSummary } from './semanticLineageHashEvidence.js';
|
|
2
3
|
import { createSemanticLineageEvent } from './semanticLineageRecords.js';
|
|
3
4
|
|
|
4
5
|
export function matchExactAnchors(beforeSymbols, afterSymbols) {
|
|
@@ -88,6 +89,9 @@ export function symbolSummary(symbol) {
|
|
|
88
89
|
language: symbol.language,
|
|
89
90
|
sourcePath: symbol.anchor.sourcePath,
|
|
90
91
|
sourceHash: symbol.anchor.sourceHash,
|
|
92
|
+
identityHash: firstString(symbol.identityHash, symbol.anchor.metadata?.identityHash),
|
|
93
|
+
semanticIdentityHash: firstString(symbol.semanticIdentityHash, symbol.anchor.metadata?.semanticIdentityHash),
|
|
94
|
+
sourceIdentityHash: firstString(symbol.sourceIdentityHash, symbol.anchor.metadata?.sourceIdentityHash),
|
|
91
95
|
sourceSpan: symbol.anchor.sourceSpan,
|
|
92
96
|
signatureHash: symbol.signatureHash,
|
|
93
97
|
bodyHash: symbol.spanHash,
|
|
@@ -144,6 +148,7 @@ function inferredEvent(before, after, score, input) {
|
|
|
144
148
|
inferred: true,
|
|
145
149
|
algorithm: 'frontier.semantic-lineage-inference.v1',
|
|
146
150
|
reasonCodes: score.reasons,
|
|
151
|
+
hashEvidence: hashEvidenceSummary(score.reasons),
|
|
147
152
|
moved: pathChanged || spanMoved,
|
|
148
153
|
renamed: nameChanged,
|
|
149
154
|
recreated,
|
|
@@ -190,6 +195,7 @@ function inferredSplitEvent(before, candidates, input) {
|
|
|
190
195
|
inferred: true,
|
|
191
196
|
algorithm: 'frontier.semantic-lineage-inference.v1',
|
|
192
197
|
reasonCodes: reasons,
|
|
198
|
+
hashEvidence: hashEvidenceSummary(reasons),
|
|
193
199
|
split: true,
|
|
194
200
|
targetCount: targets.length,
|
|
195
201
|
candidateConfidences: candidates.map((candidate) => candidate.score.confidence),
|
|
@@ -210,10 +216,12 @@ function scoreLineagePair(before, after) {
|
|
|
210
216
|
if (before.anchor.key && before.anchor.key === after.anchor.key) add(0.4, 'anchor-key-match');
|
|
211
217
|
if (before.name && before.name === after.name) add(0.28, 'symbol-name-match');
|
|
212
218
|
if (before.kind && before.kind === after.kind) add(0.12, 'symbol-kind-match');
|
|
219
|
+
addIdentityHashEvidence(before, after, add, note);
|
|
213
220
|
if (before.signatureHash && before.signatureHash === after.signatureHash) add(0.52, 'signature-hash-match');
|
|
214
221
|
if (before.spanHash && before.spanHash === after.spanHash) add(0.22, 'body-hash-match');
|
|
215
222
|
if (before.anchor.kind && before.anchor.kind === after.anchor.kind) add(0.06, 'anchor-kind-match');
|
|
216
223
|
if (before.anchor.sourcePath && before.anchor.sourcePath === after.anchor.sourcePath) add(0.04, 'source-path-match');
|
|
224
|
+
addSourceHashEvidence(before, after, add, note, reasons, sameSymbolSurface);
|
|
217
225
|
if (sourceSpanRangeSame(before.anchor.sourceSpan, after.anchor.sourceSpan)) add(0.18, 'source-span-range-match');
|
|
218
226
|
if (before.ownershipRegionKind && before.ownershipRegionKind === after.ownershipRegionKind) add(0.04, 'ownership-kind-match');
|
|
219
227
|
if (before.nativeAstNodeId && before.nativeAstNodeId === after.nativeAstNodeId) add(0.06, 'native-node-id-match');
|
|
@@ -36,7 +36,12 @@ export function resolveSemanticLineage(eventsOrMap = [], query = {}, options = {
|
|
|
36
36
|
state.status = 'max-depth';
|
|
37
37
|
state.reasonCodes.push('max-depth');
|
|
38
38
|
}
|
|
39
|
-
if (!state.cycle && !state.maxDepthHit)
|
|
39
|
+
if (!state.cycle && !state.maxDepthHit) {
|
|
40
|
+
state.status = classifyResolutionStatus(state);
|
|
41
|
+
if (state.status === 'ambiguous' && state.terminal.length > 0 && state.current.length > 0) {
|
|
42
|
+
state.reasonCodes.push('inactive-anchor-has-active-candidates');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
40
45
|
return buildResolutionRecord(state, start, maxDepth, resolutionQuery, options);
|
|
41
46
|
}
|
|
42
47
|
|
|
@@ -105,6 +110,7 @@ function applyLineageEvent(state, event, visitedEvents) {
|
|
|
105
110
|
state.operationIds.push(event.crdt?.operationId);
|
|
106
111
|
state.heads.push(...(event.crdt?.heads ?? []));
|
|
107
112
|
state.eventKinds.push(event.eventKind);
|
|
113
|
+
state.reasonCodes.push(...lineageEventReasonCodes(event));
|
|
108
114
|
state.sourcePaths.push(event.from?.sourcePath, ...event.to.map((anchor) => anchor.sourcePath));
|
|
109
115
|
if (event.confidence !== undefined) state.confidence = state.confidence === undefined ? event.confidence : Math.min(state.confidence, event.confidence);
|
|
110
116
|
const matched = state.current.filter((anchor) => anchorsMatch(anchor, event.from));
|
|
@@ -142,6 +148,7 @@ function nextLineageEvents(anchors, byFromKey, byFromId) {
|
|
|
142
148
|
|
|
143
149
|
function classifyResolutionStatus(state) {
|
|
144
150
|
if (state.current.length === 0 && state.traversed.length > 0) return 'deleted';
|
|
151
|
+
if (state.terminal.length > 0 && state.current.length > 0) return 'ambiguous';
|
|
145
152
|
if (state.eventKinds.includes('recreated')) return 'recreated';
|
|
146
153
|
if (state.current.length > 1 || state.eventKinds.includes('split') || state.eventKinds.includes('merged')) return 'ambiguous';
|
|
147
154
|
if (state.traversed.length > 0) return 'resolved';
|
|
@@ -205,6 +212,16 @@ function lineageSourcePaths(state, start, query, anchors = []) {
|
|
|
205
212
|
...array(anchors).flatMap((anchor) => anchor?.lineageSourcePaths ?? [])
|
|
206
213
|
]);
|
|
207
214
|
}
|
|
215
|
+
function lineageEventReasonCodes(event) {
|
|
216
|
+
return uniqueStrings([
|
|
217
|
+
...array(event.reasonCodes),
|
|
218
|
+
...array(event.metadata?.reasonCodes),
|
|
219
|
+
event.evidence?.signatureHashMatch ? 'signature-hash-match' : undefined,
|
|
220
|
+
event.evidence?.bodyHashMatch ? 'body-hash-match' : undefined,
|
|
221
|
+
event.evidence?.pathMatch ? 'source-path-match' : undefined,
|
|
222
|
+
event.evidence?.sourceSpanMoved ? 'source-span-moved' : undefined
|
|
223
|
+
]);
|
|
224
|
+
}
|
|
208
225
|
function positiveInteger(value, fallback) {
|
|
209
226
|
const number = Number(value);
|
|
210
227
|
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import{idFragment,normalizeSemanticMergeReadiness,uniqueStrings}from'../../native-import-utils.js';
|
|
2
2
|
import{semanticMergeConflictRiskScore}from'./semanticMergeConflicts.js';
|
|
3
3
|
import{inferProjectionRisk,internalOverlaps,normalizeChangedSemanticRegions,normalizeProjectionRisk,projectionRiskRank,queryAdmissionOverlaps,summarizeOverlaps}from'./semanticMergeCandidateRecordInternals.js';
|
|
4
|
+
import{semanticMergeCandidateScoreFacets}from'./semanticMergeCandidateScoreFacets.js';
|
|
4
5
|
|
|
5
6
|
export const SemanticMergeCandidateProjectionRisks=Object.freeze(['low','medium','high','unknown']);
|
|
6
7
|
|
|
@@ -74,6 +75,21 @@ export function createSemanticMergeCandidateAdmissionRecord(input={},options={})
|
|
|
74
75
|
const projectionRisk=normalizeProjectionRisk(options.projectionRisk??candidate.projectionRisk??candidate.risk)
|
|
75
76
|
??inferProjectionRisk({readiness,candidate,patch,changedSemanticRegions,conflictKeys});
|
|
76
77
|
const overlaps=internalOverlaps(changedSemanticRegions);
|
|
78
|
+
const conflictRiskScore=semanticMergeConflictRiskScore(candidate);
|
|
79
|
+
const scoreFacets=semanticMergeCandidateScoreFacets({
|
|
80
|
+
source,
|
|
81
|
+
candidate,
|
|
82
|
+
patch,
|
|
83
|
+
readiness,
|
|
84
|
+
projectionRisk,
|
|
85
|
+
evidenceRecords,
|
|
86
|
+
evidenceIds,
|
|
87
|
+
proofIds,
|
|
88
|
+
changedSemanticRegions,
|
|
89
|
+
conflictKeys,
|
|
90
|
+
overlaps,
|
|
91
|
+
conflictRiskScore
|
|
92
|
+
});
|
|
77
93
|
const readinessSortKey=semanticMergeCandidateReadinessSortKey({
|
|
78
94
|
readiness,
|
|
79
95
|
projectionRisk,
|
|
@@ -106,11 +122,13 @@ export function createSemanticMergeCandidateAdmissionRecord(input={},options={})
|
|
|
106
122
|
evidenceIds,
|
|
107
123
|
proofIds,
|
|
108
124
|
overlapSummary:summarizeOverlaps(overlaps),
|
|
125
|
+
scoreFacets,
|
|
109
126
|
admission:{
|
|
110
127
|
readiness,
|
|
111
128
|
reviewRequired:readiness!=='ready'||projectionRisk!=='low'||overlaps.length>0,
|
|
112
129
|
action:admissionAction({readiness,projectionRisk,overlaps}),
|
|
113
130
|
sortKey:readinessSortKey,
|
|
131
|
+
scoreFacets,
|
|
114
132
|
reasonCodes:uniqueStrings([
|
|
115
133
|
...strings(options.reasonCodes),
|
|
116
134
|
...strings(source.reasons),
|
|
@@ -145,11 +163,12 @@ export function createSemanticMergeCandidateAdmissionRecord(input={},options={})
|
|
|
145
163
|
overlaps:overlaps.length,
|
|
146
164
|
readiness,
|
|
147
165
|
projectionRisk,
|
|
148
|
-
reviewRequired:readiness!=='ready'||projectionRisk!=='low'||overlaps.length>0
|
|
166
|
+
reviewRequired:readiness!=='ready'||projectionRisk!=='low'||overlaps.length>0,
|
|
167
|
+
scoreFacets:scoreFacets.summary
|
|
149
168
|
},
|
|
150
169
|
metadata:compactRecord({
|
|
151
170
|
sourceChangeSetId:source.kind==='frontier.lang.nativeSourceChangeSet'?source.id:undefined,
|
|
152
|
-
conflictRiskScore
|
|
171
|
+
conflictRiskScore,
|
|
153
172
|
conflictSummary:candidate.conflictSummary??candidate.metadata?.conflictSummary??source.metadata?.semanticMergeConflictSummary,
|
|
154
173
|
changedRegionProjectionSummary:source.metadata?.changedRegionProjectionSummary??candidate.metadata?.changedRegionProjectionSummary,
|
|
155
174
|
compact:true,
|
|
@@ -170,6 +189,7 @@ export function decorateSemanticMergeCandidateForAdmission(input={},options={}){
|
|
|
170
189
|
proofIds:admissionRecord.proofIds,
|
|
171
190
|
projectionRisk:admissionRecord.projectionRisk,
|
|
172
191
|
readinessSortKey:admissionRecord.readinessSortKey,
|
|
192
|
+
scoreFacets:admissionRecord.scoreFacets,
|
|
173
193
|
mergeAdmission:admissionRecord
|
|
174
194
|
};
|
|
175
195
|
}
|