@shapeshift-labs/frontier-lang-compiler 0.2.99 → 0.2.101

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.
Files changed (57) hide show
  1. package/dist/declarations/bidirectional-target-change-evidence.d.ts +299 -0
  2. package/dist/declarations/bidirectional-target-change.d.ts +19 -120
  3. package/dist/declarations/native-project-admission.d.ts +43 -22
  4. package/dist/declarations/semantic-edit-bundle.d.ts +13 -1
  5. package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +24 -0
  6. package/dist/declarations/semantic-edit-script.d.ts +53 -51
  7. package/dist/declarations/semantic-lineage.d.ts +62 -51
  8. package/dist/declarations/semantic-merge-candidates.d.ts +39 -0
  9. package/dist/declarations/semantic-patch-bundle.d.ts +13 -0
  10. package/dist/declarations/semantic-sidecar-admission.d.ts +14 -0
  11. package/dist/declarations/semantic-sidecar.d.ts +12 -14
  12. package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +200 -0
  13. package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +62 -17
  14. package/dist/internal/index-impl/createNativeSourcePreservation.js +16 -1
  15. package/dist/internal/index-impl/createProjectImportAdmissionRecord.js +151 -1
  16. package/dist/internal/index-impl/createSemanticImportSidecar.js +5 -0
  17. package/dist/internal/index-impl/createSemanticImportSidecarAdmission.js +29 -11
  18. package/dist/internal/index-impl/declarationRecord.js +2 -2
  19. package/dist/internal/index-impl/inferSemanticLineageEvents.js +8 -0
  20. package/dist/internal/index-impl/nativeChangeProjectionEndpoint.js +56 -16
  21. package/dist/internal/index-impl/projectImportAdmissionMergeScore.js +26 -74
  22. package/dist/internal/index-impl/projectImportAdmissionProjectionCoverage.js +74 -0
  23. package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +92 -74
  24. package/dist/internal/index-impl/replaySemanticEditProjection.js +114 -40
  25. package/dist/internal/index-impl/semanticEditBundleAdmission.js +95 -12
  26. package/dist/internal/index-impl/semanticEditBundleIndex.js +16 -10
  27. package/dist/internal/index-impl/semanticEditInsertionAnchors.js +8 -5
  28. package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +167 -0
  29. package/dist/internal/index-impl/semanticEditSourceRanges.js +283 -0
  30. package/dist/internal/index-impl/semanticHistoryLineageResolution.js +56 -3
  31. package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +2 -2
  32. package/dist/internal/index-impl/semanticLineageHashEvidence.js +97 -0
  33. package/dist/internal/index-impl/semanticLineageInferenceMatching.js +158 -13
  34. package/dist/internal/index-impl/semanticLineageResolutionRecords.js +46 -2
  35. package/dist/internal/index-impl/semanticMergeCandidateRecords.js +22 -2
  36. package/dist/internal/index-impl/semanticMergeCandidateScoreFacets.js +221 -0
  37. package/dist/internal/index-impl/semanticPatchBundleAdmission.js +122 -20
  38. package/dist/internal/index-impl/semanticPatchBundleLineageLinks.js +199 -0
  39. package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +29 -3
  40. package/dist/internal/index-impl/semanticPatchBundleRecords.js +28 -104
  41. package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +127 -0
  42. package/dist/internal/index-impl/sourcePreservationFromProjectionContext.js +9 -2
  43. package/dist/internal/index-impl/sourceTextForSpan.js +4 -9
  44. package/dist/lightweight-dependency-relations.js +113 -7
  45. package/dist/native-import-language-profiles.js +10 -2
  46. package/dist/native-import-utils.js +15 -1
  47. package/dist/native-region-scanner-js-helpers.js +68 -18
  48. package/dist/native-region-scanner-js-imports.js +7 -0
  49. package/dist/native-region-scanner-js.js +16 -8
  50. package/dist/native-region-scanner.js +2 -1
  51. package/dist/semantic-import-regions.js +8 -6
  52. package/dist/semantic-import-sidecar-admission-types.d.ts +14 -0
  53. package/dist/semantic-import-sidecar-entry.js +151 -7
  54. package/dist/semantic-import-sidecar-types.d.ts +18 -13
  55. package/dist/semantic-import-source-preservation-utils.js +55 -0
  56. package/dist/semantic-import-source-preservation.js +98 -3
  57. package/package.json +1 -1
@@ -14,16 +14,20 @@ export function createSemanticEditBundleAdmission(input = {}, options = {}) {
14
14
  const scripts = array(input.semanticEditScripts ?? input.scripts ?? input.semanticEditScript);
15
15
  const projections = array(input.semanticEditProjections ?? input.projections ?? input.semanticEditProjection);
16
16
  const replays = array(input.semanticEditReplays ?? input.replays ?? input.semanticEditReplay);
17
- const summary = summarizeSemanticEditBundle(scripts, projections, replays);
18
- const status = input.status ?? options.status ?? semanticEditBundleStatus(summary);
17
+ const evidence = evidenceRecords(input, options);
18
+ const summary = summarizeSemanticEditBundle(scripts, projections, replays, evidence);
19
+ const computedStatus = semanticEditBundleStatus(summary);
20
+ const status = safeStatus(input.status ?? options.status, computedStatus, summary);
19
21
  const readiness = normalizeSemanticMergeReadiness(input.readiness ?? options.readiness ?? readinessForStatus(status))
20
22
  ?? input.readiness ?? options.readiness ?? readinessForStatus(status);
23
+ const positiveAutoApplyCandidate = status === 'ready' && hasPositiveAutoMergeProof(summary);
24
+ const computedReviewRequired = !['ready', 'already-applied', 'none'].includes(status) || (status === 'ready' && !positiveAutoApplyCandidate);
21
25
  return compactRecord({
22
26
  status,
23
- action: input.action ?? options.action ?? actionForStatus(status),
27
+ action: safeAction(input.action ?? options.action, status, positiveAutoApplyCandidate),
24
28
  readiness,
25
- reviewRequired: input.reviewRequired ?? !['ready', 'already-applied', 'none'].includes(status),
26
- autoApplyCandidate: input.autoApplyCandidate ?? status === 'ready',
29
+ reviewRequired: input.reviewRequired === true || computedReviewRequired,
30
+ autoApplyCandidate: input.autoApplyCandidate === false ? false : positiveAutoApplyCandidate,
27
31
  autoMergeClaim: false,
28
32
  semanticEquivalenceClaim: false,
29
33
  reasonCodes: uniqueStrings([
@@ -31,7 +35,7 @@ export function createSemanticEditBundleAdmission(input = {}, options = {}) {
31
35
  ...strings(options.reasonCodes),
32
36
  ...summary.reasonCodes,
33
37
  ...derivedReasonCodes(summary, status)
34
- ]),
38
+ ].filter(Boolean)),
35
39
  sourcePaths: summary.sourcePaths,
36
40
  scriptIds: summary.scriptIds,
37
41
  projectionIds: summary.projectionIds,
@@ -41,7 +45,7 @@ export function createSemanticEditBundleAdmission(input = {}, options = {}) {
41
45
  });
42
46
  }
43
47
 
44
- function summarizeSemanticEditBundle(scripts, projections, replays) {
48
+ function summarizeSemanticEditBundle(scripts, projections, replays, evidence) {
45
49
  const scriptStatusEntries = scripts.map((script) => script.admission?.status);
46
50
  const projectionStatusEntries = projections.flatMap((projection) => [projection.status, projection.admission?.status]);
47
51
  const replayStatusEntries = replays.map((replay) => replay.status);
@@ -49,11 +53,14 @@ function summarizeSemanticEditBundle(scripts, projections, replays) {
49
53
  const projectionStatuses = uniqueStrings(strings(projectionStatusEntries));
50
54
  const replayStatuses = uniqueStrings(strings(replayStatusEntries));
51
55
  const replayActions = uniqueStrings(strings(replays.map((replay) => replay.admission?.action)));
56
+ const evidenceSummary = summarizeEvidence(evidence);
52
57
  return {
53
58
  scripts: scripts.length,
54
59
  projections: projections.length,
55
60
  replays: replays.length,
56
61
  files: sourcePaths(scripts, projections, replays).length,
62
+ portableScripts: scripts.filter((script) => script.admission?.status === 'auto-merge-candidate').length,
63
+ portableProjections: projections.filter((projection) => projection.status === 'projected' && projection.admission?.status === 'auto-merge-candidate').length,
57
64
  acceptedClean: replays.filter((replay) => replay.status === 'accepted-clean').length,
58
65
  alreadyApplied: replays.filter((replay) => replay.status === 'already-applied').length,
59
66
  conflicts: countStatuses(scriptStatusEntries, replayStatusEntries, ['conflict']),
@@ -70,30 +77,43 @@ function summarizeSemanticEditBundle(scripts, projections, replays) {
70
77
  scriptIds: uniqueStrings(scripts.map((script) => script.id)),
71
78
  projectionIds: uniqueStrings(projections.map((projection) => projection.id)),
72
79
  replayIds: uniqueStrings(replays.map((replay) => replay.id)),
80
+ evidenceIds: evidenceSummary.evidenceIds,
81
+ passedTestEvidence: evidenceSummary.passed,
82
+ failedTestEvidence: evidenceSummary.failed,
83
+ conflictEvidence: evidenceSummary.conflict,
84
+ staleEvidence: evidenceSummary.stale,
73
85
  reasonCodes: uniqueStrings([
74
86
  ...scripts.flatMap((script) => strings(script.admission?.reasonCodes)),
75
87
  ...projections.flatMap((projection) => strings(projection.admission?.reasonCodes)),
76
- ...replays.flatMap((replay) => strings(replay.admission?.reasonCodes))
88
+ ...replays.flatMap((replay) => strings(replay.admission?.reasonCodes)),
89
+ ...evidenceSummary.reasonCodes
77
90
  ])
78
91
  };
79
92
  }
80
93
 
81
94
  function semanticEditBundleStatus(summary) {
82
95
  const total = summary.scripts + summary.projections + summary.replays;
96
+ if (summary.blocked || summary.projectionBlocked || summary.failedTestEvidence) return 'blocked';
97
+ if (summary.conflicts || summary.conflictEvidence) return 'conflict';
98
+ if (summary.stale || summary.staleEvidence) return 'stale';
83
99
  if (total === 0) return 'none';
84
- if (summary.blocked || summary.projectionBlocked) return 'blocked';
85
- if (summary.conflicts) return 'conflict';
86
- if (summary.stale) return 'stale';
87
100
  if (!summary.replays || summary.needsReview) return 'needs-review';
88
101
  if (summary.acceptedClean === 0 && summary.alreadyApplied === summary.replays) return 'already-applied';
89
- return summary.acceptedClean + summary.alreadyApplied === summary.replays ? 'ready' : 'needs-review';
102
+ return hasPositiveAutoMergeProof(summary) ? 'ready' : 'needs-review';
90
103
  }
91
104
 
92
105
  function derivedReasonCodes(summary, status) {
93
106
  return [
94
107
  summary.scripts && !summary.projections ? 'semantic-edit-projection-missing' : undefined,
95
108
  (summary.scripts || summary.projections) && !summary.replays ? 'semantic-edit-replay-missing' : undefined,
109
+ summary.scripts && summary.portableScripts !== summary.scripts ? 'semantic-edit-script-not-portable' : undefined,
110
+ summary.projections && summary.portableProjections !== summary.projections ? 'semantic-edit-projection-not-portable' : undefined,
111
+ summary.acceptedClean && !summary.passedTestEvidence ? 'semantic-edit-tests-passed-evidence-missing' : undefined,
112
+ summary.failedTestEvidence ? 'semantic-edit-tests-failed' : undefined,
113
+ summary.conflictEvidence ? 'semantic-edit-conflict-evidence' : undefined,
114
+ summary.staleEvidence ? 'semantic-edit-stale-evidence' : undefined,
96
115
  status === 'ready' ? 'semantic-edit-replay-accepted-clean' : undefined,
116
+ status === 'ready' ? 'semantic-edit-positive-auto-merge-proof' : undefined,
97
117
  status === 'already-applied' ? 'semantic-edit-replay-already-applied' : undefined,
98
118
  status === 'blocked' ? 'semantic-edit-blocked' : undefined,
99
119
  status === 'conflict' ? 'semantic-edit-conflict' : undefined,
@@ -101,6 +121,19 @@ function derivedReasonCodes(summary, status) {
101
121
  ];
102
122
  }
103
123
 
124
+ function hasPositiveAutoMergeProof(summary) {
125
+ return summary.acceptedClean > 0 &&
126
+ summary.acceptedClean + summary.alreadyApplied === summary.replays &&
127
+ summary.scripts > 0 &&
128
+ summary.projections > 0 &&
129
+ summary.portableScripts === summary.scripts &&
130
+ summary.portableProjections === summary.projections &&
131
+ summary.passedTestEvidence > 0 &&
132
+ summary.failedTestEvidence === 0 &&
133
+ summary.conflictEvidence === 0 &&
134
+ summary.staleEvidence === 0;
135
+ }
136
+
104
137
  function readinessForStatus(status) {
105
138
  if (['ready', 'already-applied'].includes(status)) return 'ready';
106
139
  if (['blocked', 'conflict'].includes(status)) return 'blocked';
@@ -116,6 +149,20 @@ function actionForStatus(status) {
116
149
  return 'review';
117
150
  }
118
151
 
152
+ function safeStatus(requested, computed, summary) {
153
+ if (!requested) return computed;
154
+ if (requested === 'ready' && !hasPositiveAutoMergeProof(summary)) return computed;
155
+ if (requested === 'already-applied' && computed !== 'already-applied') return computed;
156
+ if (['blocked', 'conflict', 'stale'].includes(requested)) return requested;
157
+ return computed;
158
+ }
159
+
160
+ function safeAction(requested, status, positiveAutoApplyCandidate) {
161
+ if (requested === 'admit' && !positiveAutoApplyCandidate) return actionForStatus(status);
162
+ if (requested === 'skip' && status !== 'already-applied') return actionForStatus(status);
163
+ return requested ?? actionForStatus(status);
164
+ }
165
+
119
166
  function sourcePaths(scripts, projections, replays) {
120
167
  return uniqueStrings(strings([
121
168
  ...scripts.map((script) => script.sourcePath),
@@ -132,6 +179,42 @@ function countStatuses(...args) {
132
179
  return statuses.filter((status) => needles.has(status)).length;
133
180
  }
134
181
 
182
+ function evidenceRecords(...sources) {
183
+ return sources.flatMap((source) => [
184
+ ...array(source?.evidence),
185
+ ...array(source?.testEvidence),
186
+ ...array(source?.testResults),
187
+ ...array(source?.gateEvidence),
188
+ ...array(source?.proofEvidence)
189
+ ]).filter(Boolean);
190
+ }
191
+
192
+ function summarizeEvidence(evidence) {
193
+ const testLike = evidence.filter(isAutoMergeTestEvidence);
194
+ const conflict = evidence.filter((record) => evidenceStatus(record, ['conflict', 'conflicted']) || record?.metadata?.conflict === true || strings(record?.reasonCodes ?? record?.reasons).some((reason) => reason.toLowerCase().includes('conflict')));
195
+ const stale = evidence.filter((record) => evidenceStatus(record, ['stale']) || record?.metadata?.stale === true || strings(record?.reasonCodes ?? record?.reasons).some((reason) => reason.toLowerCase().includes('stale')));
196
+ const failed = testLike.filter((record) => evidenceStatus(record, ['failed', 'failure', 'error', 'blocked', 'rejected']));
197
+ const passed = testLike.filter((record) => evidenceStatus(record, ['passed', 'ok', 'success', 'succeeded', 'accepted', 'verified']));
198
+ return {
199
+ evidenceIds: uniqueStrings(evidence.map((record) => record.id)),
200
+ passed: passed.length,
201
+ failed: failed.length,
202
+ conflict: conflict.length,
203
+ stale: stale.length,
204
+ reasonCodes: uniqueStrings(evidence.flatMap((record) => strings(record.reasonCodes ?? record.reasons)))
205
+ };
206
+ }
207
+
208
+ function isAutoMergeTestEvidence(record) {
209
+ const kind = String(record?.kind ?? record?.type ?? '').toLowerCase();
210
+ return ['test', 'tests', 'proof', 'gate', 'verification', 'check'].includes(kind);
211
+ }
212
+
213
+ function evidenceStatus(record, statuses) {
214
+ const status = String(record?.status ?? record?.outcome ?? '').toLowerCase();
215
+ return statuses.includes(status);
216
+ }
217
+
135
218
  function array(value) { if (value === undefined || value === null) return []; return Array.isArray(value) ? value : [value]; }
136
219
  function strings(value) { return array(value).map((entry) => String(entry ?? '')).filter(Boolean); }
137
220
  function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0))); }
@@ -15,16 +15,16 @@ export function semanticEditRecordIndex(scripts, projections, replays, source =
15
15
  semanticEditReplayEditCount: replayEdits.length,
16
16
  semanticEditReplayStatuses: uniqueStrings([...strings(source.semanticEditReplayStatuses), ...strings(index.semanticEditReplayStatuses), ...strings(summary.replayStatuses), ...replays.map((replay) => replay.status)]),
17
17
  semanticEditReplayActions: uniqueStrings([...strings(source.semanticEditReplayActions), ...strings(index.semanticEditReplayActions), ...strings(summary.replayActions), ...replays.map((replay) => replay.admission?.action)]),
18
- semanticEditReplayCurrentHashes: uniqueStrings([...strings(source.semanticEditReplayCurrentHashes), ...strings(index.semanticEditReplayCurrentHashes), ...replays.map((replay) => replay.currentHash)]),
19
- semanticEditReplayOutputHashes: uniqueStrings([...strings(source.semanticEditReplayOutputHashes), ...strings(index.semanticEditReplayOutputHashes), ...replays.map((replay) => replay.outputHash)]),
20
- semanticEditKeys: uniqueStrings([...strings(source.semanticEditKeys), ...strings(index.semanticEditKeys), ...operations.map((operation) => operation.semanticKey), ...edits.map((edit) => edit.semanticKey), ...replayEdits.map((edit) => edit.semanticKey)]),
21
- semanticIdentityHashes: uniqueStrings([...strings(source.semanticIdentityHashes), ...strings(index.semanticIdentityHashes), ...operations.map((operation) => operation.semanticIdentityHash), ...edits.map((edit) => edit.semanticIdentityHash), ...replayEdits.map((edit) => edit.semanticIdentityHash)]),
22
- sourceIdentityHashes: uniqueStrings([...strings(source.sourceIdentityHashes), ...strings(index.sourceIdentityHashes), ...operations.map((operation) => operation.sourceIdentityHash), ...edits.map((edit) => edit.sourceIdentityHash), ...replayEdits.map((edit) => edit.sourceIdentityHash)]),
23
- operationContentHashes: uniqueStrings([...strings(source.operationContentHashes), ...strings(index.operationContentHashes), ...operations.map((operation) => operation.operationContentHash), ...edits.map((edit) => edit.operationContentHash)]),
24
- editContentHashes: uniqueStrings([...strings(source.editContentHashes), ...strings(index.editContentHashes), ...edits.map((edit) => edit.editContentHash), ...replayEdits.map((edit) => edit.editContentHash)]),
25
- anchorKeys: uniqueStrings([...operations.map((operation) => operation.anchor?.key), ...edits.map((edit) => edit.anchorKey)]),
26
- conflictKeys: uniqueStrings([...operations.map((operation) => operation.anchor?.conflictKey), ...edits.map((edit) => edit.conflictKey)]),
27
- projectedSourcePaths: uniqueStrings([...projections.map((projection) => projection.sourcePath), ...edits.flatMap((edit) => [edit.sourcePath, edit.targetSourcePath]), ...replays.map((replay) => replay.sourcePath), ...replayEdits.map((edit) => edit.sourcePath)])
18
+ semanticEditReplayCurrentHashes: uniqueStrings([...strings(source.semanticEditReplayCurrentHashes), ...strings(index.semanticEditReplayCurrentHashes), ...strings(summary.replayCurrentHashes), ...strings(summary.semanticEditReplayCurrentHashes), ...replays.map((replay) => replay.currentHash)]),
19
+ semanticEditReplayOutputHashes: uniqueStrings([...strings(source.semanticEditReplayOutputHashes), ...strings(index.semanticEditReplayOutputHashes), ...strings(summary.replayOutputHashes), ...strings(summary.semanticEditReplayOutputHashes), ...replays.map((replay) => replay.outputHash)]),
20
+ semanticEditKeys: uniqueStrings([...strings(source.semanticEditKeys), ...strings(index.semanticEditKeys), ...strings(summary.semanticEditKeys), ...operations.map((operation) => operation.semanticKey), ...edits.map((edit) => edit.semanticKey), ...replayEdits.map((edit) => edit.semanticKey)]),
21
+ semanticIdentityHashes: uniqueStrings([...strings(source.semanticIdentityHashes), ...strings(index.semanticIdentityHashes), ...strings(summary.semanticIdentityHashes), ...operations.map((operation) => operation.semanticIdentityHash), ...edits.map((edit) => edit.semanticIdentityHash), ...replayEdits.map((edit) => edit.semanticIdentityHash)]),
22
+ sourceIdentityHashes: uniqueStrings([...strings(source.sourceIdentityHashes), ...strings(index.sourceIdentityHashes), ...strings(summary.sourceIdentityHashes), ...operations.map((operation) => operation.sourceIdentityHash), ...edits.map((edit) => edit.sourceIdentityHash), ...replayEdits.map((edit) => edit.sourceIdentityHash)]),
23
+ operationContentHashes: uniqueStrings([...strings(source.operationContentHashes), ...strings(index.operationContentHashes), ...strings(summary.operationContentHashes), ...operations.map((operation) => operation.operationContentHash), ...edits.map((edit) => edit.operationContentHash), ...replayEdits.map((edit) => edit.operationContentHash)]),
24
+ editContentHashes: uniqueStrings([...strings(source.editContentHashes), ...strings(index.editContentHashes), ...strings(summary.editContentHashes), ...edits.map((edit) => edit.editContentHash), ...replayEdits.map((edit) => edit.editContentHash)]),
25
+ anchorKeys: uniqueStrings([...strings(source.anchorKeys), ...strings(index.anchorKeys), ...strings(summary.anchorKeys), ...operations.map((operation) => operation.anchor?.key), ...edits.map((edit) => edit.anchorKey), ...replayEdits.map((edit) => edit.anchorKey)]),
26
+ conflictKeys: uniqueStrings([...strings(source.conflictKeys), ...strings(index.conflictKeys), ...strings(summary.conflictKeys), ...operations.map((operation) => operation.anchor?.conflictKey), ...edits.map((edit) => edit.conflictKey), ...replayEdits.map((edit) => edit.conflictKey)]),
27
+ projectedSourcePaths: uniqueStrings([...strings(source.projectedSourcePaths), ...strings(index.projectedSourcePaths), ...strings(summary.projectedSourcePaths), ...projections.map((projection) => projection.sourcePath), ...edits.flatMap((edit) => [edit.sourcePath, edit.targetSourcePath]), ...replays.map((replay) => replay.sourcePath), ...replayEdits.map((edit) => edit.sourcePath)])
28
28
  };
29
29
  }
30
30
 
@@ -36,9 +36,15 @@ export function semanticEditSummary(index) {
36
36
  replayIds: index.semanticEditReplayIds,
37
37
  replayStatuses: index.semanticEditReplayStatuses,
38
38
  replayActions: index.semanticEditReplayActions,
39
+ replayCurrentHashes: index.semanticEditReplayCurrentHashes,
40
+ replayOutputHashes: index.semanticEditReplayOutputHashes,
39
41
  semanticEditKeys: index.semanticEditKeys,
42
+ semanticIdentityHashes: index.semanticIdentityHashes,
43
+ sourceIdentityHashes: index.sourceIdentityHashes,
40
44
  operationContentHashes: index.operationContentHashes,
41
45
  editContentHashes: index.editContentHashes,
46
+ anchorKeys: index.anchorKeys,
47
+ conflictKeys: index.conflictKeys,
42
48
  projectedSourcePaths: index.projectedSourcePaths,
43
49
  replayEditCount: index.semanticEditReplayEditCount
44
50
  });
@@ -7,13 +7,16 @@ export function semanticEditInsertionAnchor(region, context) {
7
7
  .filter((symbol) => hasSymbol(context.baseSymbols, symbol));
8
8
  const before = nearestBefore(workers, workerSymbol);
9
9
  const after = nearestAfter(workers, workerSymbol);
10
- const anchor = before
11
- ? insertionFromSymbol('after', before, context, 'nearest-previous-base-symbol')
12
- : after
13
- ? insertionFromSymbol('before', after, context, 'nearest-next-base-symbol')
14
- : fallbackInsertion(region, context, 'no-neighbor-base-symbol');
10
+ const anchorCandidates = [
11
+ before ? insertionFromSymbol('after', before, context, 'nearest-previous-base-symbol') : undefined,
12
+ after ? insertionFromSymbol('before', after, context, 'nearest-next-base-symbol') : undefined
13
+ ].filter(Boolean);
14
+ const anchor = anchorCandidates.find((candidate) => candidate.headSpan)
15
+ ?? anchorCandidates[0]
16
+ ?? fallbackInsertion(region, context, 'no-neighbor-base-symbol');
15
17
  return compactRecord({
16
18
  ...anchor,
19
+ anchorCandidates,
17
20
  insertedSymbolId: workerSymbol.id,
18
21
  insertedSymbolName: workerSymbol.name,
19
22
  insertedSymbolKind: workerSymbol.kind,
@@ -0,0 +1,167 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { uniqueStrings } from '../../native-import-utils.js';
3
+
4
+ export function replayEditDiagnostics(edit, status, range, reasonCodes, sourceText) {
5
+ if (status === 'applied' || status === 'already-applied') return [];
6
+ return reasonCodes.map((code) => replayDiagnostic(code, {
7
+ scope: 'edit',
8
+ status,
9
+ operationId: edit.operationId,
10
+ sourcePath: edit.targetSourcePath ?? edit.sourcePath,
11
+ symbolName: edit.targetSymbolName ?? edit.symbolName,
12
+ symbolKind: edit.targetSymbolKind ?? edit.symbolKind,
13
+ editKind: edit.editKind,
14
+ start: range?.start,
15
+ end: range?.end,
16
+ expectedHash: replayDiagnosticExpectedHash(code, edit),
17
+ actualHash: replayDiagnosticActualHash(range, sourceText),
18
+ replacementHash: edit.replacementTextHash ?? edit.replacementSpanTextHash
19
+ }));
20
+ }
21
+
22
+ export function replayEditsWithOverlapDiagnostics(edits) {
23
+ const overlapDiagnostics = new Map();
24
+ const ordered = edits
25
+ .filter((edit) => edit.status === 'applied' && hasNumericReplayRange(edit))
26
+ .sort((left, right) => left.start - right.start || left.end - right.end || (left.editOrder ?? 0) - (right.editOrder ?? 0));
27
+ for (let leftIndex = 0; leftIndex < ordered.length; leftIndex += 1) {
28
+ for (let rightIndex = leftIndex + 1; rightIndex < ordered.length; rightIndex += 1) {
29
+ const left = ordered[leftIndex];
30
+ const right = ordered[rightIndex];
31
+ if (!rangesOverlap(left, right)) continue;
32
+ const operationIds = [left.operationId, right.operationId].filter(Boolean);
33
+ const fallbackId = `${left.editOrder ?? leftIndex}:${right.editOrder ?? rightIndex}`;
34
+ const code = `replay-edit-overlap:${operationIds.join(':') || fallbackId}`;
35
+ appendOverlapDiagnostic(overlapDiagnostics, left, code, operationIds);
36
+ appendOverlapDiagnostic(overlapDiagnostics, right, code, operationIds);
37
+ }
38
+ }
39
+ if (!overlapDiagnostics.size) return edits;
40
+ return edits.map((edit) => {
41
+ const diagnostics = overlapDiagnostics.get(edit);
42
+ if (!diagnostics) return edit;
43
+ return diagnostics.reduce((record, diagnostic) => appendReplayEditDiagnostic(record, diagnostic), edit);
44
+ });
45
+ }
46
+
47
+ export function replayDiagnostics(input) {
48
+ return uniqueDiagnostics([
49
+ ...reasonList(input.reasonCodes).map((code) => replayDiagnostic(code, {
50
+ scope: 'replay',
51
+ status: input.status,
52
+ sourcePath: input.sourcePath,
53
+ expectedHash: code === 'current-source-hash-mismatch' ? input.expectedCurrentHash : undefined,
54
+ actualHash: code === 'current-source-hash-mismatch' ? input.currentHash : undefined
55
+ })),
56
+ ...input.edits.flatMap((edit) => edit.diagnostics ?? [])
57
+ ]);
58
+ }
59
+
60
+ function appendOverlapDiagnostic(overlapDiagnostics, edit, code, operationIds) {
61
+ const diagnostics = overlapDiagnostics.get(edit) ?? [];
62
+ diagnostics.push(replayDiagnostic(code, {
63
+ scope: 'edit',
64
+ status: 'conflict',
65
+ operationId: edit.operationId,
66
+ sourcePath: edit.sourcePath,
67
+ symbolName: edit.symbolName,
68
+ symbolKind: edit.symbolKind,
69
+ editKind: edit.editKind,
70
+ start: edit.start,
71
+ end: edit.end,
72
+ overlapOperationIds: operationIds
73
+ }));
74
+ overlapDiagnostics.set(edit, diagnostics);
75
+ }
76
+
77
+ function appendReplayEditDiagnostic(edit, diagnostic) {
78
+ return compactRecord({
79
+ ...edit,
80
+ status: edit.status === 'applied' ? 'conflict' : edit.status,
81
+ reasonCodes: reasonList([...(edit.reasonCodes ?? []), diagnostic.code]),
82
+ diagnostics: uniqueDiagnostics([...(edit.diagnostics ?? []), diagnostic])
83
+ });
84
+ }
85
+
86
+ function replayDiagnostic(code, context) {
87
+ const category = replayDiagnosticCategory(code, context.status);
88
+ return compactRecord({
89
+ code,
90
+ category,
91
+ severity: replayDiagnosticSeverity(code, category, context.status),
92
+ scope: context.scope,
93
+ status: context.status,
94
+ operationId: context.operationId,
95
+ sourcePath: context.sourcePath,
96
+ symbolName: context.symbolName,
97
+ symbolKind: context.symbolKind,
98
+ editKind: context.editKind,
99
+ start: context.start,
100
+ end: context.end,
101
+ expectedHash: context.expectedHash,
102
+ actualHash: context.actualHash,
103
+ replacementHash: context.replacementHash,
104
+ overlapOperationIds: context.overlapOperationIds
105
+ });
106
+ }
107
+
108
+ function replayDiagnosticCategory(code, status) {
109
+ if (code.includes('overlap')) return 'overlap';
110
+ if (code.startsWith('missing-current-source') || code.startsWith('missing-head-source') || code.startsWith('missing-worker-source')) return 'missing-source';
111
+ if (code === 'current-symbol-anchor-missing' || code.includes('anchor-missing') || code.includes('anchor-unusable')) return 'stale-anchor';
112
+ if (code.includes('content-mismatch') || code.includes('hash-mismatch') || code.includes('span-not-resolvable') || code.startsWith('projection-not-') || code === 'missing-replacement-text') return 'projection-mismatch';
113
+ if (code.includes('reanchored')) return 'reanchored';
114
+ if (code.includes('matches-')) return 'matched-source';
115
+ if (status === 'stale') return 'stale-anchor';
116
+ if (status === 'conflict' || status === 'blocked') return 'projection-mismatch';
117
+ return 'replay';
118
+ }
119
+
120
+ function replayDiagnosticSeverity(code, category, status) {
121
+ if (code === 'current-source-hash-mismatch' && (status === 'accepted-clean' || status === 'already-applied')) return 'warning';
122
+ if (category === 'matched-source' || category === 'reanchored' || status === 'applied' || status === 'already-applied') return 'info';
123
+ if (category === 'overlap' || category === 'missing-source' || category === 'stale-anchor' || category === 'projection-mismatch') return 'error';
124
+ return status === 'accepted-clean' ? 'info' : 'warning';
125
+ }
126
+
127
+ function replayDiagnosticExpectedHash(code, edit) {
128
+ if (code.includes('matches-replacement')) return edit.replacementTextHash ?? edit.replacementSpanTextHash;
129
+ if (code.includes('content-mismatch') || code.includes('matches-deleted')) return edit.deletedTextHash ?? edit.anchorDeletedTextHash;
130
+ return undefined;
131
+ }
132
+
133
+ function replayDiagnosticActualHash(range, sourceText) {
134
+ if (!range || typeof sourceText !== 'string') return undefined;
135
+ return hashSemanticValue(sourceText.slice(range.start, range.end));
136
+ }
137
+
138
+ function uniqueDiagnostics(diagnostics) {
139
+ const seen = new Set();
140
+ const result = [];
141
+ for (const diagnostic of diagnostics.filter(Boolean)) {
142
+ const key = [
143
+ diagnostic.scope,
144
+ diagnostic.operationId,
145
+ diagnostic.status,
146
+ diagnostic.code,
147
+ diagnostic.start,
148
+ diagnostic.end,
149
+ (diagnostic.overlapOperationIds ?? []).join('|')
150
+ ].join(':');
151
+ if (seen.has(key)) continue;
152
+ seen.add(key);
153
+ result.push(diagnostic);
154
+ }
155
+ return result;
156
+ }
157
+
158
+ function rangesOverlap(left, right) {
159
+ return Boolean(left && right && left.start < right.end && right.start < left.end);
160
+ }
161
+
162
+ function hasNumericReplayRange(edit) {
163
+ return typeof edit.start === 'number' && typeof edit.end === 'number';
164
+ }
165
+
166
+ function reasonList(values) { return uniqueStrings((values ?? []).filter(Boolean)); }
167
+ function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0))); }