@shapeshift-labs/frontier-lang-compiler 0.2.99 → 0.2.100
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/semantic-edit-bundle.d.ts +13 -1
- package/dist/declarations/semantic-edit-script.d.ts +34 -37
- package/dist/declarations/semantic-lineage.d.ts +63 -34
- package/dist/declarations/semantic-patch-bundle.d.ts +13 -0
- package/dist/internal/index-impl/declarationRecord.js +2 -2
- package/dist/internal/index-impl/inferSemanticLineageEvents.js +8 -0
- package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +56 -64
- package/dist/internal/index-impl/replaySemanticEditProjection.js +54 -22
- package/dist/internal/index-impl/semanticEditBundleAdmission.js +95 -12
- package/dist/internal/index-impl/semanticEditBundleIndex.js +16 -10
- package/dist/internal/index-impl/semanticEditSourceRanges.js +204 -0
- package/dist/internal/index-impl/semanticHistoryLineageResolution.js +35 -1
- package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +2 -2
- package/dist/internal/index-impl/semanticLineageInferenceMatching.js +150 -13
- package/dist/internal/index-impl/semanticLineageResolutionRecords.js +28 -1
- package/dist/internal/index-impl/semanticPatchBundleAdmission.js +122 -20
- package/dist/internal/index-impl/semanticPatchBundleLineageLinks.js +199 -0
- package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +6 -2
- package/dist/internal/index-impl/semanticPatchBundleRecords.js +28 -104
- package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +127 -0
- package/dist/internal/index-impl/sourceTextForSpan.js +4 -9
- package/dist/lightweight-dependency-relations.js +113 -7
- package/dist/native-import-utils.js +15 -1
- package/dist/native-region-scanner-js-helpers.js +61 -17
- package/dist/native-region-scanner-js.js +12 -4
- package/dist/semantic-import-regions.js +3 -3
- package/package.json +1 -1
|
@@ -22,7 +22,11 @@ export function resolveSemanticHistoryRecordLineage(record = {}, eventsOrMap = [
|
|
|
22
22
|
semanticAnchorKeys: resolveIndexKeys(sourceIndex.semanticAnchorKeys, byAnchorKey, options),
|
|
23
23
|
sourcePaths: uniqueStrings([
|
|
24
24
|
...array(sourceIndex.sourcePaths),
|
|
25
|
-
...resolutions.flatMap((resolution) => resolution
|
|
25
|
+
...resolutions.flatMap((resolution) => resolutionSourcePaths(resolution))
|
|
26
|
+
]),
|
|
27
|
+
lineageResolutionIds: uniqueStrings([
|
|
28
|
+
...array(sourceIndex.lineageResolutionIds),
|
|
29
|
+
...resolutions.map((resolution) => resolution.id)
|
|
26
30
|
]),
|
|
27
31
|
lineageEventIds: uniqueStrings([
|
|
28
32
|
...array(sourceIndex.lineageEventIds),
|
|
@@ -105,6 +109,13 @@ function createResolvedHistoryRecord(record, index, resolution) {
|
|
|
105
109
|
inactiveAnchorKeys: resolution.summary.inactiveAnchorKeys,
|
|
106
110
|
blockedAnchorKeys: resolution.summary.blockedAnchorKeys
|
|
107
111
|
},
|
|
112
|
+
lineageResolutionIds: resolution.summary.lineageResolutionIds,
|
|
113
|
+
lineageEventIds: resolution.summary.traversedEventIds,
|
|
114
|
+
terminalEventIds: resolution.summary.terminalEventIds,
|
|
115
|
+
sourcePaths: resolution.summary.sourcePaths,
|
|
116
|
+
evidenceIds: resolution.summary.evidenceIds,
|
|
117
|
+
proofIds: resolution.summary.proofIds,
|
|
118
|
+
reasonCodes: resolution.summary.reasonCodes,
|
|
108
119
|
autoMergeClaim: false,
|
|
109
120
|
semanticEquivalenceClaim: false
|
|
110
121
|
}
|
|
@@ -159,8 +170,11 @@ function summarizeSemanticHistoryLineageResolutions(resolutions, index, anchorIn
|
|
|
159
170
|
deletedAnchorKeys: uniqueStrings(anchorInventory.deleted.map((anchor) => anchor.key)),
|
|
160
171
|
unresolvedAnchorKeys: uniqueStrings(anchorInventory.unresolved.map((anchor) => anchor.key)),
|
|
161
172
|
blockedAnchorKeys: uniqueStrings(anchorInventory.blocked.map((anchor) => anchor.key)),
|
|
173
|
+
lineageResolutionIds: uniqueStrings(resolutions.map((resolution) => resolution.id)),
|
|
162
174
|
traversedEventIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.traversedEventIds)),
|
|
163
175
|
terminalEventIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.terminalEventIds)),
|
|
176
|
+
evidenceIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.evidenceIds)),
|
|
177
|
+
proofIds: uniqueStrings(resolutions.flatMap((resolution) => resolution.proofIds)),
|
|
164
178
|
reasonCodes: uniqueStrings(resolutions.flatMap((resolution) => resolution.reasonCodes))
|
|
165
179
|
};
|
|
166
180
|
}
|
|
@@ -225,8 +239,17 @@ function anchorEntry(anchor, resolution) {
|
|
|
225
239
|
sourcePath: anchor.sourcePath,
|
|
226
240
|
symbolId: anchor.symbolId,
|
|
227
241
|
symbolName: anchor.symbolName,
|
|
242
|
+
sourcePaths: resolutionSourcePaths(resolution, anchor),
|
|
243
|
+
lineageEventIds: uniqueStrings(resolution.traversedEventIds),
|
|
244
|
+
terminalEventIds: uniqueStrings(resolution.terminalEventIds),
|
|
245
|
+
evidenceIds: uniqueStrings(resolution.evidenceIds),
|
|
246
|
+
proofIds: uniqueStrings(resolution.proofIds),
|
|
247
|
+
crdtOperationIds: uniqueStrings(resolution.crdtOperationIds),
|
|
248
|
+
crdtHeads: uniqueStrings(resolution.crdtHeads),
|
|
249
|
+
lineageEventKinds: uniqueStrings(resolution.lineageEventKinds),
|
|
228
250
|
status: resolution.status,
|
|
229
251
|
resolutionId: resolution.id,
|
|
252
|
+
confidence: resolution.confidence,
|
|
230
253
|
reasonCodes: uniqueStrings(resolution.reasonCodes)
|
|
231
254
|
});
|
|
232
255
|
}
|
|
@@ -250,6 +273,17 @@ function uniqueAnchorEntries(entries) {
|
|
|
250
273
|
});
|
|
251
274
|
}
|
|
252
275
|
|
|
276
|
+
function resolutionSourcePaths(resolution, anchor) {
|
|
277
|
+
return uniqueStrings([
|
|
278
|
+
anchor?.sourcePath,
|
|
279
|
+
...array(anchor?.lineageSourcePaths),
|
|
280
|
+
resolution.query?.sourcePath,
|
|
281
|
+
resolution.startAnchor?.sourcePath,
|
|
282
|
+
...array(resolution.sourcePaths),
|
|
283
|
+
...resolution.currentAnchors.flatMap((entry) => [entry.sourcePath, ...array(entry.lineageSourcePaths)])
|
|
284
|
+
]);
|
|
285
|
+
}
|
|
286
|
+
|
|
253
287
|
function array(value) { return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value]; }
|
|
254
288
|
function uniqueStrings(values) { return uniqueRawStrings(array(values).map((value) => String(value ?? '')).filter(Boolean)); }
|
|
255
289
|
function firstString(...values) { return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean); }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{idFragment}from'../../native-import-utils.js';import{semanticOwnershipRegionForDeclaration}from'../../semantic-import-regions.js';import{createSemanticIndexRecord,hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
|
|
1
|
+
import{idFragment,caseSensitiveIdFragment}from'../../native-import-utils.js';import{semanticOwnershipRegionForDeclaration}from'../../semantic-import-regions.js';import{createSemanticIndexRecord,hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
|
|
2
2
|
import{relationPredicateForDeclaration}from'./relationPredicateForDeclaration.js';
|
|
3
3
|
export function semanticIndexFromNativeDeclarations(declarations, input, options) {
|
|
4
4
|
const documentId = `doc_${idFragment(input.sourcePath ?? input.language)}_${idFragment(input.sourceHash)}`;
|
|
@@ -9,7 +9,7 @@ export function semanticIndexFromNativeDeclarations(declarations, input, options
|
|
|
9
9
|
const facts = [];
|
|
10
10
|
const mappings = [];
|
|
11
11
|
for (const declaration of declarations) {
|
|
12
|
-
const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${
|
|
12
|
+
const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${caseSensitiveIdFragment(declaration.name)}`;
|
|
13
13
|
const occurrenceId = `occ_${idFragment(declaration.nativeNode.id)}_${declaration.role ?? 'definition'}`;
|
|
14
14
|
const ownershipRegion = semanticOwnershipRegionForDeclaration(input, {
|
|
15
15
|
...declaration,
|
|
@@ -8,8 +8,12 @@ export function matchExactAnchors(beforeSymbols, afterSymbols) {
|
|
|
8
8
|
const matchedAfterKeys = new Set();
|
|
9
9
|
for (const before of beforeSymbols) {
|
|
10
10
|
const after = afterByKey.get(before.anchor.key);
|
|
11
|
-
if (after
|
|
12
|
-
matched.push({
|
|
11
|
+
if (after) {
|
|
12
|
+
matched.push({
|
|
13
|
+
before: symbolSummary(before),
|
|
14
|
+
after: symbolSummary(after),
|
|
15
|
+
sourceSpanMoved: !anchorsSameLocation(before.anchor, after.anchor)
|
|
16
|
+
});
|
|
13
17
|
matchedAfterKeys.add(after.anchor.key);
|
|
14
18
|
} else {
|
|
15
19
|
unmatchedBefore.push(before);
|
|
@@ -27,21 +31,33 @@ export function matchLineageCandidates(beforeSymbols, afterSymbols, input, optio
|
|
|
27
31
|
const events = [];
|
|
28
32
|
const ambiguous = [];
|
|
29
33
|
const unmatchedBefore = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const rankedByBefore = beforeSymbols.map((before) => ({
|
|
35
|
+
before,
|
|
36
|
+
ranked: rankLineageCandidates(before, afterSymbols, options)
|
|
37
|
+
}));
|
|
38
|
+
const contendersByAfter = afterContenderIndex(rankedByBefore);
|
|
39
|
+
for (const entry of rankedByBefore) {
|
|
40
|
+
const before = entry.before;
|
|
41
|
+
const ranked = entry.ranked.filter((candidate) => !claimedAfter.has(candidate.after.anchor.key));
|
|
36
42
|
const best = ranked[0];
|
|
37
43
|
const runnerUp = ranked[1];
|
|
38
44
|
if (!best) {
|
|
39
45
|
unmatchedBefore.push(before);
|
|
40
46
|
continue;
|
|
41
47
|
}
|
|
48
|
+
const splitTargets = splitLineageCandidates(before, ranked, contendersByAfter, options);
|
|
49
|
+
if (splitTargets.length > 1) {
|
|
50
|
+
for (const candidate of splitTargets) claimedAfter.add(candidate.after.anchor.key);
|
|
51
|
+
events.push(inferredSplitEvent(before, splitTargets, input));
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const targetContention = ambiguousTargetContention(before, best, contendersByAfter, options);
|
|
55
|
+
if (targetContention.length > 0) {
|
|
56
|
+
ambiguous.push(ambiguousMatch(before, ranked, ['ambiguous-lineage-candidates', 'ambiguous-target-lineage-candidates']));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
42
59
|
if (runnerUp && best.score.confidence - runnerUp.score.confidence < options.ambiguityMargin) {
|
|
43
60
|
ambiguous.push(ambiguousMatch(before, ranked));
|
|
44
|
-
unmatchedBefore.push(before);
|
|
45
61
|
continue;
|
|
46
62
|
}
|
|
47
63
|
claimedAfter.add(best.after.anchor.key);
|
|
@@ -55,6 +71,14 @@ export function matchLineageCandidates(beforeSymbols, afterSymbols, input, optio
|
|
|
55
71
|
};
|
|
56
72
|
}
|
|
57
73
|
|
|
74
|
+
function rankLineageCandidates(before, afterSymbols, options) {
|
|
75
|
+
return afterSymbols
|
|
76
|
+
.filter((after) => before.anchor.key !== after.anchor.key)
|
|
77
|
+
.map((after) => ({ before, after, score: scoreLineagePair(before, after) }))
|
|
78
|
+
.filter((candidate) => candidate.score.confidence >= options.minConfidence)
|
|
79
|
+
.sort(compareCandidateScores);
|
|
80
|
+
}
|
|
81
|
+
|
|
58
82
|
export function symbolSummary(symbol) {
|
|
59
83
|
return {
|
|
60
84
|
key: symbol.anchor.key,
|
|
@@ -71,7 +95,7 @@ export function symbolSummary(symbol) {
|
|
|
71
95
|
};
|
|
72
96
|
}
|
|
73
97
|
|
|
74
|
-
function ambiguousMatch(before, ranked) {
|
|
98
|
+
function ambiguousMatch(before, ranked, reasonCodes = ['ambiguous-lineage-candidates']) {
|
|
75
99
|
return {
|
|
76
100
|
before: symbolSummary(before),
|
|
77
101
|
candidates: ranked.slice(0, 4).map((candidate) => ({
|
|
@@ -79,7 +103,7 @@ function ambiguousMatch(before, ranked) {
|
|
|
79
103
|
confidence: candidate.score.confidence,
|
|
80
104
|
reasons: candidate.score.reasons
|
|
81
105
|
})),
|
|
82
|
-
reasonCodes:
|
|
106
|
+
reasonCodes: uniqueStrings(reasonCodes)
|
|
83
107
|
};
|
|
84
108
|
}
|
|
85
109
|
|
|
@@ -94,7 +118,8 @@ function inferredEvent(before, after, score, input) {
|
|
|
94
118
|
&& before.anchor.symbolName !== after.anchor.symbolName;
|
|
95
119
|
const pathChanged = before.anchor.sourcePath !== after.anchor.sourcePath;
|
|
96
120
|
const spanMoved = JSON.stringify(before.anchor.sourceSpan ?? null) !== JSON.stringify(after.anchor.sourceSpan ?? null);
|
|
97
|
-
const
|
|
121
|
+
const recreated = !nameChanged && score.reasons.includes('delete-recreate-candidate');
|
|
122
|
+
const eventKind = nameChanged ? 'renamed' : recreated ? 'recreated' : 'moved';
|
|
98
123
|
return createSemanticLineageEvent({
|
|
99
124
|
id: `lineage_inferred_${idFragment(firstString(input.id, before.anchor.key))}_${idFragment(after.anchor.key)}`,
|
|
100
125
|
createdAt: input.generatedAt,
|
|
@@ -121,6 +146,53 @@ function inferredEvent(before, after, score, input) {
|
|
|
121
146
|
reasonCodes: score.reasons,
|
|
122
147
|
moved: pathChanged || spanMoved,
|
|
123
148
|
renamed: nameChanged,
|
|
149
|
+
recreated,
|
|
150
|
+
anchorKeyChanged: before.anchor.key !== after.anchor.key,
|
|
151
|
+
autoMergeClaim: false,
|
|
152
|
+
semanticEquivalenceClaim: false
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function inferredSplitEvent(before, candidates, input) {
|
|
158
|
+
const targets = candidates.map((candidate) => candidate.after);
|
|
159
|
+
const reasons = uniqueStrings([
|
|
160
|
+
...candidates.flatMap((candidate) => candidate.score.reasons),
|
|
161
|
+
'split-lineage-candidate'
|
|
162
|
+
]);
|
|
163
|
+
const confidence = Math.min(...candidates.map((candidate) => candidate.score.confidence));
|
|
164
|
+
const pathMatch = targets.every((target) => before.anchor.sourcePath === target.anchor.sourcePath);
|
|
165
|
+
const spanMoved = targets.some((target) => (
|
|
166
|
+
before.anchor.sourcePath !== target.anchor.sourcePath
|
|
167
|
+
|| JSON.stringify(before.anchor.sourceSpan ?? null) !== JSON.stringify(target.anchor.sourceSpan ?? null)
|
|
168
|
+
));
|
|
169
|
+
return createSemanticLineageEvent({
|
|
170
|
+
id: `lineage_split_${idFragment(firstString(input.id, before.anchor.key))}_${idFragment(targets.map((target) => target.anchor.key).join('_'))}`,
|
|
171
|
+
createdAt: input.generatedAt,
|
|
172
|
+
eventKind: 'split',
|
|
173
|
+
from: before.anchor,
|
|
174
|
+
to: targets.map((target) => target.anchor),
|
|
175
|
+
confidence,
|
|
176
|
+
actor: input.actor,
|
|
177
|
+
actorId: input.actorId,
|
|
178
|
+
actorRole: input.actorRole ?? 'semantic-lineage-inference',
|
|
179
|
+
operationId: input.operationId,
|
|
180
|
+
deps: input.deps,
|
|
181
|
+
heads: input.heads,
|
|
182
|
+
stateVector: input.stateVector,
|
|
183
|
+
evidenceIds: [input.evidenceId ?? `evidence_${idFragment(input.id ?? before.anchor.key)}_lineage_inference`],
|
|
184
|
+
signatureHashMatch: candidates.every((candidate) => candidate.score.reasons.includes('signature-hash-match')),
|
|
185
|
+
bodyHashMatch: candidates.every((candidate) => candidate.score.reasons.includes('body-hash-match')),
|
|
186
|
+
pathMatch,
|
|
187
|
+
sourceSpanMoved: spanMoved,
|
|
188
|
+
conflictKeys: uniqueStrings([before.anchor.key, ...targets.map((target) => target.anchor.key)]),
|
|
189
|
+
metadata: {
|
|
190
|
+
inferred: true,
|
|
191
|
+
algorithm: 'frontier.semantic-lineage-inference.v1',
|
|
192
|
+
reasonCodes: reasons,
|
|
193
|
+
split: true,
|
|
194
|
+
targetCount: targets.length,
|
|
195
|
+
candidateConfidences: candidates.map((candidate) => candidate.score.confidence),
|
|
124
196
|
autoMergeClaim: false,
|
|
125
197
|
semanticEquivalenceClaim: false
|
|
126
198
|
}
|
|
@@ -134,6 +206,7 @@ function scoreLineagePair(before, after) {
|
|
|
134
206
|
score += value;
|
|
135
207
|
reasons.push(reason);
|
|
136
208
|
};
|
|
209
|
+
const note = (reason) => reasons.push(reason);
|
|
137
210
|
if (before.anchor.key && before.anchor.key === after.anchor.key) add(0.4, 'anchor-key-match');
|
|
138
211
|
if (before.name && before.name === after.name) add(0.28, 'symbol-name-match');
|
|
139
212
|
if (before.kind && before.kind === after.kind) add(0.12, 'symbol-kind-match');
|
|
@@ -145,7 +218,67 @@ function scoreLineagePair(before, after) {
|
|
|
145
218
|
if (before.ownershipRegionKind && before.ownershipRegionKind === after.ownershipRegionKind) add(0.04, 'ownership-kind-match');
|
|
146
219
|
if (before.nativeAstNodeId && before.nativeAstNodeId === after.nativeAstNodeId) add(0.06, 'native-node-id-match');
|
|
147
220
|
if (before.anchor.sourcePath !== after.anchor.sourcePath && (before.name === after.name || before.signatureHash === after.signatureHash)) add(0.04, 'source-path-moved');
|
|
148
|
-
|
|
221
|
+
if (before.anchor.key && after.anchor.key && before.anchor.key !== after.anchor.key) note('anchor-key-changed');
|
|
222
|
+
if (sameSymbolSurface(before, after)) note('same-symbol-surface');
|
|
223
|
+
if (deleteRecreateCandidate(before, after, reasons)) note('delete-recreate-candidate');
|
|
224
|
+
return { confidence: Math.max(0, Math.min(1, Number(score.toFixed(3)))), reasons: uniqueStrings(reasons) };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function afterContenderIndex(rankedByBefore) {
|
|
228
|
+
const contenders = new Map();
|
|
229
|
+
for (const entry of rankedByBefore) {
|
|
230
|
+
for (const candidate of entry.ranked) {
|
|
231
|
+
const key = candidate.after.anchor.key;
|
|
232
|
+
if (!key) continue;
|
|
233
|
+
contenders.set(key, [...(contenders.get(key) ?? []), candidate]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return contenders;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function ambiguousTargetContention(before, candidate, contendersByAfter, options) {
|
|
240
|
+
return (contendersByAfter.get(candidate.after.anchor.key) ?? []).filter((contender) => (
|
|
241
|
+
contender.before.anchor.key !== before.anchor.key
|
|
242
|
+
&& candidate.score.confidence - contender.score.confidence < options.ambiguityMargin
|
|
243
|
+
));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function splitLineageCandidates(before, ranked, contendersByAfter, options) {
|
|
247
|
+
const best = ranked[0];
|
|
248
|
+
if (!best) return [];
|
|
249
|
+
const close = ranked.filter((candidate) => best.score.confidence - candidate.score.confidence < options.ambiguityMargin);
|
|
250
|
+
if (close.length < 2 || close.length > 4) return [];
|
|
251
|
+
if (!close.every((candidate) => hasStrongLineageEvidence(candidate.score))) return [];
|
|
252
|
+
if (!close.every((candidate) => splitNameEvidence(before, candidate.after))) return [];
|
|
253
|
+
if (close.some((candidate) => ambiguousTargetContention(before, candidate, contendersByAfter, options).length > 0)) return [];
|
|
254
|
+
return close;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function hasStrongLineageEvidence(score) {
|
|
258
|
+
return score.reasons.includes('signature-hash-match') || score.reasons.includes('body-hash-match');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function splitNameEvidence(before, after) {
|
|
262
|
+
const beforeName = normalizedName(before.name);
|
|
263
|
+
const afterName = normalizedName(after.name);
|
|
264
|
+
return Boolean(beforeName && afterName && beforeName !== afterName && afterName.includes(beforeName));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function deleteRecreateCandidate(before, after, reasons) {
|
|
268
|
+
return before.anchor.key !== after.anchor.key
|
|
269
|
+
&& sameSymbolSurface(before, after)
|
|
270
|
+
&& (reasons.includes('signature-hash-match') || reasons.includes('body-hash-match'));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function sameSymbolSurface(before, after) {
|
|
274
|
+
return Boolean(
|
|
275
|
+
before.name
|
|
276
|
+
&& before.name === after.name
|
|
277
|
+
&& before.kind
|
|
278
|
+
&& before.kind === after.kind
|
|
279
|
+
&& before.anchor.sourcePath
|
|
280
|
+
&& before.anchor.sourcePath === after.anchor.sourcePath
|
|
281
|
+
);
|
|
149
282
|
}
|
|
150
283
|
|
|
151
284
|
function anchorsSameLocation(before, after) {
|
|
@@ -162,6 +295,10 @@ function sourceSpanRangeSame(before, after) {
|
|
|
162
295
|
&& before.endColumn === after.endColumn;
|
|
163
296
|
}
|
|
164
297
|
|
|
298
|
+
function normalizedName(value) {
|
|
299
|
+
return String(value ?? '').replace(/[^A-Za-z0-9_$]+/g, '').toLowerCase();
|
|
300
|
+
}
|
|
301
|
+
|
|
165
302
|
function firstString(...values) {
|
|
166
303
|
return values.map((value) => value === undefined || value === null ? '' : String(value)).find(Boolean);
|
|
167
304
|
}
|
|
@@ -49,6 +49,7 @@ function createResolutionState(start) {
|
|
|
49
49
|
current: start ? [start] : [],
|
|
50
50
|
traversed: [],
|
|
51
51
|
terminal: [],
|
|
52
|
+
sourcePaths: strings(start?.sourcePath),
|
|
52
53
|
conflictKeys: [],
|
|
53
54
|
evidenceIds: [],
|
|
54
55
|
proofIds: [],
|
|
@@ -64,14 +65,16 @@ function createResolutionState(start) {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
function buildResolutionRecord(state, start, maxDepth, query, options) {
|
|
68
|
+
const currentAnchors = uniqueAnchors(state.current).map((anchor) => anchorWithLineageLinks(anchor, state, start, query));
|
|
67
69
|
const core = {
|
|
68
70
|
kind: 'frontier.lang.semanticLineageResolution',
|
|
69
71
|
version: 1,
|
|
70
72
|
query: compactRecord({ anchorKey: start?.key, anchorId: start?.id, sourcePath: start?.sourcePath, symbolName: start?.symbolName, maxDepth }),
|
|
71
73
|
startAnchor: start,
|
|
72
|
-
currentAnchors
|
|
74
|
+
currentAnchors,
|
|
73
75
|
traversedEventIds: uniqueStrings(state.traversed),
|
|
74
76
|
terminalEventIds: uniqueStrings(state.terminal),
|
|
77
|
+
sourcePaths: lineageSourcePaths(state, start, query, currentAnchors),
|
|
75
78
|
status: state.status,
|
|
76
79
|
confidence: clampConfidence(state.confidence),
|
|
77
80
|
conflictKeys: uniqueStrings(state.conflictKeys),
|
|
@@ -102,6 +105,7 @@ function applyLineageEvent(state, event, visitedEvents) {
|
|
|
102
105
|
state.operationIds.push(event.crdt?.operationId);
|
|
103
106
|
state.heads.push(...(event.crdt?.heads ?? []));
|
|
104
107
|
state.eventKinds.push(event.eventKind);
|
|
108
|
+
state.sourcePaths.push(event.from?.sourcePath, ...event.to.map((anchor) => anchor.sourcePath));
|
|
105
109
|
if (event.confidence !== undefined) state.confidence = state.confidence === undefined ? event.confidence : Math.min(state.confidence, event.confidence);
|
|
106
110
|
const matched = state.current.filter((anchor) => anchorsMatch(anchor, event.from));
|
|
107
111
|
const unmatched = state.current.filter((anchor) => !anchorsMatch(anchor, event.from));
|
|
@@ -178,6 +182,29 @@ function uniqueEvents(events) {
|
|
|
178
182
|
return true;
|
|
179
183
|
});
|
|
180
184
|
}
|
|
185
|
+
function anchorWithLineageLinks(anchor, state, start, query) {
|
|
186
|
+
return compactRecord({
|
|
187
|
+
...anchor,
|
|
188
|
+
lineageEventIds: uniqueStrings(state.traversed),
|
|
189
|
+
terminalLineageEventIds: uniqueStrings(state.terminal),
|
|
190
|
+
lineageSourcePaths: lineageSourcePaths(state, start, query, [anchor]),
|
|
191
|
+
evidenceIds: uniqueStrings(state.evidenceIds),
|
|
192
|
+
proofIds: uniqueStrings(state.proofIds),
|
|
193
|
+
crdtOperationIds: uniqueStrings(state.operationIds),
|
|
194
|
+
crdtHeads: uniqueStrings(state.heads),
|
|
195
|
+
lineageEventKinds: uniqueStrings(state.eventKinds),
|
|
196
|
+
lineageReasonCodes: uniqueStrings(state.reasonCodes)
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function lineageSourcePaths(state, start, query, anchors = []) {
|
|
200
|
+
return uniqueStrings([
|
|
201
|
+
query?.sourcePath,
|
|
202
|
+
start?.sourcePath,
|
|
203
|
+
...state.sourcePaths,
|
|
204
|
+
...array(anchors).map((anchor) => anchor?.sourcePath),
|
|
205
|
+
...array(anchors).flatMap((anchor) => anchor?.lineageSourcePaths ?? [])
|
|
206
|
+
]);
|
|
207
|
+
}
|
|
181
208
|
function positiveInteger(value, fallback) {
|
|
182
209
|
const number = Number(value);
|
|
183
210
|
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|
|
@@ -3,32 +3,42 @@ import { normalizeSemanticMergeReadiness, uniqueStrings } from '../../native-imp
|
|
|
3
3
|
export function createSemanticPatchBundleAdmission(input = {}, context = {}) {
|
|
4
4
|
const transformAdmission = semanticTransformAdmission(context);
|
|
5
5
|
const semanticEditAdmission = context.semanticEditAdmission ?? { status: 'none', action: 'none', readiness: 'needs-review', reasonCodes: [] };
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
const evidenceAdmission = autoMergeEvidenceAdmission(context, { transformAdmission, semanticEditAdmission });
|
|
7
|
+
const fallbackReadiness = fallbackAdmissionReadiness(transformAdmission, semanticEditAdmission, evidenceAdmission, context.readiness);
|
|
8
|
+
const inputReadiness = normalizeSemanticMergeReadiness(input.readiness ?? fallbackReadiness) ?? input.readiness ?? fallbackReadiness;
|
|
9
|
+
const readiness = hasPositiveApplyAction(transformAdmission, semanticEditAdmission) && evidenceAdmission.action !== 'admit'
|
|
10
|
+
? evidenceAdmission.readiness
|
|
11
|
+
: inputReadiness;
|
|
12
|
+
const computedStatus = admissionStatusForReadiness(readiness, transformAdmission, semanticEditAdmission, evidenceAdmission);
|
|
13
|
+
const status = input.status === 'admitted' && computedStatus !== 'admitted' ? computedStatus : input.status ?? computedStatus;
|
|
14
|
+
const computedAutoApplyCandidate = status === 'admitted' &&
|
|
15
|
+
hasPositiveApplyAction(transformAdmission, semanticEditAdmission) &&
|
|
16
|
+
evidenceAdmission.action === 'admit';
|
|
17
|
+
const autoApplyCandidate = input.autoApplyCandidate === true ? computedAutoApplyCandidate : input.autoApplyCandidate ?? computedAutoApplyCandidate;
|
|
18
|
+
const admittedWithoutPositiveProof = status === 'admitted' &&
|
|
19
|
+
hasPositiveApplyAction(transformAdmission, semanticEditAdmission) &&
|
|
20
|
+
evidenceAdmission.action !== 'admit';
|
|
13
21
|
return compactRecord({
|
|
14
22
|
status,
|
|
15
23
|
readiness,
|
|
16
|
-
reviewRequired: input.reviewRequired ?? status !== 'admitted',
|
|
24
|
+
reviewRequired: input.reviewRequired ?? (status !== 'admitted' || admittedWithoutPositiveProof),
|
|
17
25
|
autoMergeClaim: false,
|
|
18
26
|
autoApplyCandidate,
|
|
19
27
|
transformAdmission,
|
|
20
28
|
semanticEditAdmission,
|
|
29
|
+
evidenceAdmission,
|
|
21
30
|
reasonCodes: uniqueStrings([
|
|
22
31
|
...strings(input.reasonCodes),
|
|
23
32
|
...strings(context.source?.reasons),
|
|
24
33
|
...strings(context.mergeCandidate?.reasons),
|
|
25
34
|
...transformAdmission.reasonCodes,
|
|
26
|
-
...strings(semanticEditAdmission.reasonCodes)
|
|
27
|
-
|
|
35
|
+
...strings(semanticEditAdmission.reasonCodes),
|
|
36
|
+
...strings(evidenceAdmission.reasonCodes)
|
|
37
|
+
].filter(Boolean)),
|
|
28
38
|
conflictKeys: uniqueStrings([...strings(input.conflictKeys), ...context.conflictKeys]),
|
|
29
39
|
admittedAt: input.admittedAt,
|
|
30
40
|
reviewerId: input.reviewerId,
|
|
31
|
-
evidenceIds: uniqueStrings([...strings(input.evidenceIds), ...strings(transformAdmission.evidenceIds)]),
|
|
41
|
+
evidenceIds: uniqueStrings([...strings(input.evidenceIds), ...strings(transformAdmission.evidenceIds), ...strings(evidenceAdmission.evidenceIds)]),
|
|
32
42
|
metadata: input.metadata
|
|
33
43
|
});
|
|
34
44
|
}
|
|
@@ -74,7 +84,7 @@ function transformReasonCodes(input) {
|
|
|
74
84
|
!input.complete ? 'transform-evidence-incomplete' : undefined,
|
|
75
85
|
input.ready ? 'transform-auto-apply-candidate' : undefined,
|
|
76
86
|
...input.readinesses.map((readiness) => `transform-readiness:${readiness}`)
|
|
77
|
-
]);
|
|
87
|
+
].filter(Boolean));
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
function transformReadiness(value) {
|
|
@@ -87,21 +97,90 @@ function transformReadiness(value) {
|
|
|
87
97
|
return undefined;
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
function autoMergeEvidenceAdmission(context, admissions) {
|
|
101
|
+
const evidence = uniqueEvidenceRecords([
|
|
102
|
+
...array(context.evidenceRecords),
|
|
103
|
+
...array(context.evidence),
|
|
104
|
+
...array(context.source?.evidence),
|
|
105
|
+
...array(context.source?.patch?.evidence),
|
|
106
|
+
...array(context.source?.semanticPatch?.evidence),
|
|
107
|
+
...array(context.mergeCandidate?.evidence)
|
|
108
|
+
]);
|
|
109
|
+
const positiveApply = hasPositiveApplyAttempt(admissions.transformAdmission, admissions.semanticEditAdmission);
|
|
110
|
+
if (!positiveApply) return { status: 'none', action: 'none', readiness: 'needs-review', reasonCodes: [], evidenceIds: evidenceIds(evidence) };
|
|
111
|
+
const summary = summarizeAutoMergeEvidence(evidence);
|
|
112
|
+
const blocked = summary.failed > 0 || summary.conflict > 0;
|
|
113
|
+
const ready = !blocked && summary.stale === 0 && summary.passed > 0;
|
|
114
|
+
const status = blocked ? 'blocked' : summary.stale > 0 ? 'stale' : ready ? 'ready' : 'needs-review';
|
|
115
|
+
return compactRecord({
|
|
116
|
+
status,
|
|
117
|
+
action: blocked ? 'block' : status === 'stale' ? 'rerun-semantic-import' : ready ? 'admit' : 'review',
|
|
118
|
+
readiness: blocked ? 'blocked' : ready ? 'ready' : 'needs-review',
|
|
119
|
+
reasonCodes: autoMergeEvidenceReasonCodes(summary, status),
|
|
120
|
+
evidenceIds: summary.evidenceIds,
|
|
121
|
+
passed: summary.passed,
|
|
122
|
+
failed: summary.failed,
|
|
123
|
+
conflict: summary.conflict,
|
|
124
|
+
stale: summary.stale
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function summarizeAutoMergeEvidence(evidence) {
|
|
129
|
+
const testLike = evidence.filter(isAutoMergeTestEvidence);
|
|
130
|
+
const failed = testLike.filter((record) => evidenceStatus(record, ['failed', 'failure', 'error', 'blocked', 'rejected']));
|
|
131
|
+
const passed = testLike.filter((record) => evidenceStatus(record, ['passed', 'ok', 'success', 'succeeded', 'accepted', 'verified']));
|
|
132
|
+
const conflict = evidence.filter((record) => evidenceStatus(record, ['conflict', 'conflicted']) || record?.metadata?.conflict === true || strings(record?.reasonCodes ?? record?.reasons).some((reason) => reason.toLowerCase().includes('conflict')));
|
|
133
|
+
const stale = evidence.filter((record) => evidenceStatus(record, ['stale']) || record?.metadata?.stale === true || strings(record?.reasonCodes ?? record?.reasons).some((reason) => reason.toLowerCase().includes('stale')));
|
|
134
|
+
return {
|
|
135
|
+
evidenceIds: evidenceIds(evidence),
|
|
136
|
+
passed: passed.length,
|
|
137
|
+
failed: failed.length,
|
|
138
|
+
conflict: conflict.length,
|
|
139
|
+
stale: stale.length
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function autoMergeEvidenceReasonCodes(summary, status) {
|
|
144
|
+
return uniqueStrings([
|
|
145
|
+
summary.passed ? 'auto-merge-tests-passed' : undefined,
|
|
146
|
+
summary.passed === 0 ? 'auto-merge-tests-passed-evidence-missing' : undefined,
|
|
147
|
+
summary.failed ? 'auto-merge-tests-failed' : undefined,
|
|
148
|
+
summary.conflict ? 'auto-merge-conflict-evidence' : undefined,
|
|
149
|
+
summary.stale ? 'auto-merge-stale-evidence' : undefined,
|
|
150
|
+
status === 'ready' ? 'auto-merge-positive-proof' : undefined
|
|
151
|
+
].filter(Boolean));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function fallbackAdmissionReadiness(transformAdmission, semanticEditAdmission, evidenceAdmission, fallback) {
|
|
155
|
+
if ([transformAdmission.readiness, semanticEditAdmission.readiness, evidenceAdmission.readiness].includes('blocked')) return 'blocked';
|
|
156
|
+
if (hasSkipReadyAction(semanticEditAdmission)) return 'ready';
|
|
157
|
+
if (hasPositiveApplyAction(transformAdmission, semanticEditAdmission)) return evidenceAdmission.action === 'admit' ? 'ready' : evidenceAdmission.readiness;
|
|
93
158
|
return fallback;
|
|
94
159
|
}
|
|
95
160
|
|
|
96
|
-
function admissionStatusForReadiness(readiness, transformAdmission, semanticEditAdmission) {
|
|
161
|
+
function admissionStatusForReadiness(readiness, transformAdmission, semanticEditAdmission, evidenceAdmission) {
|
|
97
162
|
if (readiness === 'blocked') return 'blocked';
|
|
98
|
-
if (hasAdmissibleReadyAction(transformAdmission, semanticEditAdmission) && readiness === 'ready') return 'admitted';
|
|
163
|
+
if (hasAdmissibleReadyAction(transformAdmission, semanticEditAdmission, evidenceAdmission) && readiness === 'ready') return 'admitted';
|
|
99
164
|
return readiness === 'needs-review' ? 'needs-review' : 'proposed';
|
|
100
165
|
}
|
|
101
166
|
|
|
102
|
-
function hasAdmissibleReadyAction(transformAdmission, semanticEditAdmission) {
|
|
103
|
-
return
|
|
104
|
-
(semanticEditAdmission
|
|
167
|
+
function hasAdmissibleReadyAction(transformAdmission, semanticEditAdmission, evidenceAdmission) {
|
|
168
|
+
return hasSkipReadyAction(semanticEditAdmission) ||
|
|
169
|
+
(hasPositiveApplyAction(transformAdmission, semanticEditAdmission) && evidenceAdmission.action === 'admit');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function hasPositiveApplyAction(transformAdmission, semanticEditAdmission) {
|
|
173
|
+
return [transformAdmission.action, semanticEditAdmission.action].includes('admit');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function hasPositiveApplyAttempt(transformAdmission, semanticEditAdmission) {
|
|
177
|
+
return hasPositiveApplyAction(transformAdmission, semanticEditAdmission) ||
|
|
178
|
+
Number(semanticEditAdmission.summary?.acceptedClean ?? 0) > 0 ||
|
|
179
|
+
strings(transformAdmission.reasonCodes).includes('transform-readiness:auto-merge-candidate');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function hasSkipReadyAction(semanticEditAdmission) {
|
|
183
|
+
return semanticEditAdmission.action === 'skip' && semanticEditAdmission.readiness === 'ready' && semanticEditAdmission.reviewRequired === false;
|
|
105
184
|
}
|
|
106
185
|
|
|
107
186
|
function hasCrossLanguageTransform(index) {
|
|
@@ -109,6 +188,29 @@ function hasCrossLanguageTransform(index) {
|
|
|
109
188
|
return strings(index.transformTargetLanguages).some((target) => !source.has(target));
|
|
110
189
|
}
|
|
111
190
|
|
|
191
|
+
function isAutoMergeTestEvidence(record) {
|
|
192
|
+
const kind = String(record?.kind ?? record?.type ?? '').toLowerCase();
|
|
193
|
+
return ['test', 'tests', 'proof', 'gate', 'verification', 'check'].includes(kind);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function evidenceStatus(record, statuses) {
|
|
197
|
+
const status = String(record?.status ?? record?.outcome ?? '').toLowerCase();
|
|
198
|
+
return statuses.includes(status);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function uniqueEvidenceRecords(records) {
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
const result = [];
|
|
204
|
+
for (const record of records.filter(Boolean)) {
|
|
205
|
+
const key = record.id ?? JSON.stringify(record);
|
|
206
|
+
if (seen.has(key)) continue;
|
|
207
|
+
seen.add(key);
|
|
208
|
+
result.push(record);
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function evidenceIds(evidence) { return uniqueStrings(evidence.map((record) => record.id)); }
|
|
112
214
|
function array(value) { if (value === undefined || value === null) return []; return Array.isArray(value) ? value : [value]; }
|
|
113
215
|
function strings(value) { return array(value).map((entry) => String(entry ?? '')).filter(Boolean); }
|
|
114
216
|
function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0))); }
|