@shapeshift-labs/frontier-lang-compiler 0.2.100 → 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 (43) 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-replay-diagnostics.d.ts +24 -0
  5. package/dist/declarations/semantic-edit-script.d.ts +20 -15
  6. package/dist/declarations/semantic-lineage.d.ts +3 -21
  7. package/dist/declarations/semantic-merge-candidates.d.ts +39 -0
  8. package/dist/declarations/semantic-sidecar-admission.d.ts +14 -0
  9. package/dist/declarations/semantic-sidecar.d.ts +12 -14
  10. package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +200 -0
  11. package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +62 -17
  12. package/dist/internal/index-impl/createNativeSourcePreservation.js +16 -1
  13. package/dist/internal/index-impl/createProjectImportAdmissionRecord.js +151 -1
  14. package/dist/internal/index-impl/createSemanticImportSidecar.js +5 -0
  15. package/dist/internal/index-impl/createSemanticImportSidecarAdmission.js +29 -11
  16. package/dist/internal/index-impl/nativeChangeProjectionEndpoint.js +56 -16
  17. package/dist/internal/index-impl/projectImportAdmissionMergeScore.js +26 -74
  18. package/dist/internal/index-impl/projectImportAdmissionProjectionCoverage.js +74 -0
  19. package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +39 -13
  20. package/dist/internal/index-impl/replaySemanticEditProjection.js +65 -23
  21. package/dist/internal/index-impl/semanticEditInsertionAnchors.js +8 -5
  22. package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +167 -0
  23. package/dist/internal/index-impl/semanticEditSourceRanges.js +94 -15
  24. package/dist/internal/index-impl/semanticHistoryLineageResolution.js +21 -2
  25. package/dist/internal/index-impl/semanticLineageHashEvidence.js +97 -0
  26. package/dist/internal/index-impl/semanticLineageInferenceMatching.js +8 -0
  27. package/dist/internal/index-impl/semanticLineageResolutionRecords.js +18 -1
  28. package/dist/internal/index-impl/semanticMergeCandidateRecords.js +22 -2
  29. package/dist/internal/index-impl/semanticMergeCandidateScoreFacets.js +221 -0
  30. package/dist/internal/index-impl/semanticPatchBundleOverlaps.js +23 -1
  31. package/dist/internal/index-impl/sourcePreservationFromProjectionContext.js +9 -2
  32. package/dist/native-import-language-profiles.js +10 -2
  33. package/dist/native-region-scanner-js-helpers.js +8 -2
  34. package/dist/native-region-scanner-js-imports.js +7 -0
  35. package/dist/native-region-scanner-js.js +4 -4
  36. package/dist/native-region-scanner.js +2 -1
  37. package/dist/semantic-import-regions.js +6 -4
  38. package/dist/semantic-import-sidecar-admission-types.d.ts +14 -0
  39. package/dist/semantic-import-sidecar-entry.js +151 -7
  40. package/dist/semantic-import-sidecar-types.d.ts +18 -13
  41. package/dist/semantic-import-source-preservation-utils.js +55 -0
  42. package/dist/semantic-import-source-preservation.js +98 -3
  43. package/package.json +1 -1
@@ -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) state.status = classifyResolutionStatus(state);
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:semanticMergeConflictRiskScore(candidate),
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
  }
@@ -0,0 +1,221 @@
1
+ import { uniqueStrings } from '../../native-import-utils.js';
2
+
3
+ const scoreFacetWeights = Object.freeze({ ownership: 18, staleStatus: 20, testEvidence: 18, overlap: 18, size: 10, semanticSidecarQuality: 16 });
4
+
5
+ export function semanticMergeCandidateScoreFacets(input) {
6
+ const components = {
7
+ ownership: ownershipFacet(input),
8
+ staleStatus: staleStatusFacet(input),
9
+ testEvidence: testEvidenceFacet(input),
10
+ overlap: overlapFacet(input),
11
+ size: sizeFacet(input),
12
+ semanticSidecarQuality: semanticSidecarQualityFacet(input)
13
+ };
14
+ const weightedTotal = Object.values(components).reduce((sum, component) => sum + component.weightedScore, 0);
15
+ const weightTotal = Object.values(components).reduce((sum, component) => sum + component.weight, 0);
16
+ const value = roundScore(weightTotal ? weightedTotal * 100 / weightTotal : 0);
17
+ const weakFacets = Object.values(components).filter((component) => component.status === 'weak').map((component) => component.key);
18
+ const blockedFacets = Object.values(components).filter((component) => component.status === 'blocked').map((component) => component.key);
19
+ const lowestScore = Math.min(...Object.values(components).map((component) => component.score));
20
+ return {
21
+ schema: 'frontier.lang.semanticMergeCandidateScoreFacets.v1',
22
+ version: 1,
23
+ higherIsBetter: true,
24
+ value,
25
+ risk: value < 50 || blockedFacets.length ? 'high' : value < 80 || weakFacets.length ? 'medium' : 'low',
26
+ components,
27
+ summary: {
28
+ value,
29
+ risk: value < 50 || blockedFacets.length ? 'high' : value < 80 || weakFacets.length ? 'medium' : 'low',
30
+ lowestScore,
31
+ weakFacets,
32
+ blockedFacets,
33
+ availableFacets: Object.values(components).filter((component) => component.signals.available !== false).map((component) => component.key)
34
+ }
35
+ };
36
+ }
37
+
38
+ function ownershipFacet(input) {
39
+ const regions = input.changedSemanticRegions;
40
+ const keyedRegions = regions.filter((region) => region.key || region.conflictKey);
41
+ const kindedRegions = regions.filter((region) => region.regionKind);
42
+ const spannedRegions = regions.filter((region) => region.sourceSpan);
43
+ const score = regions.length
44
+ ? roundScore(keyedRegions.length * 70 / regions.length + kindedRegions.length * 15 / regions.length + spannedRegions.length * 15 / regions.length)
45
+ : input.conflictKeys.length ? 45 : 70;
46
+ return scoreFacet('ownership', score, [
47
+ ...(regions.length === 0 ? ['missing-changed-semantic-regions'] : []),
48
+ ...(regions.length && keyedRegions.length < regions.length ? ['missing-ownership-keys'] : [])
49
+ ], {
50
+ changedSemanticRegions: regions.length,
51
+ keyedRegions: keyedRegions.length,
52
+ regionKinds: uniqueStrings(regions.map((region) => region.regionKind)),
53
+ conflictKeys: input.conflictKeys,
54
+ ownershipKeys: uniqueStrings(regions.map((region) => region.key))
55
+ });
56
+ }
57
+
58
+ function staleStatusFacet(input) {
59
+ const staleEvidence = input.evidenceRecords.filter((record) => evidenceStatus(record) === 'stale' || record?.metadata?.stale === true);
60
+ const sourceHashVerified = [
61
+ input.source?.metadata?.sourceHashVerified,
62
+ input.source?.before?.metadata?.sourceHashVerified,
63
+ input.source?.after?.metadata?.sourceHashVerified,
64
+ input.candidate?.metadata?.sourceHashVerified,
65
+ input.patch?.metadata?.sourceHashVerified
66
+ ].filter((value) => value !== undefined);
67
+ const staleProofs = semanticSidecarQuality(input)?.proofSummary?.stale ?? 0;
68
+ const sourceHashStale = sourceHashVerified.includes(false);
69
+ const score = sourceHashStale ? 0 : staleEvidence.length ? 35 : staleProofs > 0 ? 55 : 100;
70
+ return scoreFacet('staleStatus', score, [
71
+ ...(sourceHashStale ? ['stale-source-hash'] : []),
72
+ ...(staleEvidence.length ? ['stale-evidence'] : []),
73
+ ...(staleProofs > 0 ? ['stale-sidecar-proof-obligations'] : [])
74
+ ], {
75
+ staleEvidenceIds: staleEvidence.map((record) => record.id).filter(Boolean),
76
+ staleProofObligations: staleProofs,
77
+ sourceHashVerified
78
+ });
79
+ }
80
+
81
+ function testEvidenceFacet(input) {
82
+ const failed = input.evidenceRecords.filter((record) => evidenceStatus(record) === 'failed');
83
+ const stale = input.evidenceRecords.filter((record) => evidenceStatus(record) === 'stale');
84
+ const pending = input.evidenceRecords.filter((record) => ['pending', 'assumed', 'unknown'].includes(evidenceStatus(record)));
85
+ const passed = input.evidenceRecords.filter((record) => ['passed', 'ok', 'success'].includes(evidenceStatus(record)));
86
+ const score = failed.length ? 0 : stale.length ? 45 : pending.length ? 65 : input.proofIds.length ? 100 : input.evidenceIds.length ? 82 : 35;
87
+ return scoreFacet('testEvidence', score, [
88
+ ...(failed.length ? ['failed-evidence'] : []),
89
+ ...(stale.length ? ['stale-evidence'] : []),
90
+ ...(pending.length ? ['pending-evidence'] : []),
91
+ ...(!input.evidenceIds.length && !input.proofIds.length ? ['missing-evidence'] : [])
92
+ ], {
93
+ evidenceIds: input.evidenceIds,
94
+ proofIds: input.proofIds,
95
+ evidenceRecords: input.evidenceRecords.length,
96
+ passedEvidenceIds: passed.map((record) => record.id).filter(Boolean),
97
+ failedEvidenceIds: failed.map((record) => record.id).filter(Boolean),
98
+ staleEvidenceIds: stale.map((record) => record.id).filter(Boolean),
99
+ pendingEvidenceIds: pending.map((record) => record.id).filter(Boolean)
100
+ });
101
+ }
102
+
103
+ function overlapFacet(input) {
104
+ const high = input.overlaps.filter((overlap) => overlap.risk === 'high').length;
105
+ const medium = input.overlaps.filter((overlap) => overlap.risk === 'medium').length;
106
+ const score = clampScore(100 - high * 45 - medium * 25 - Math.max(0, input.overlaps.length - high - medium) * 15);
107
+ return scoreFacet('overlap', score, [
108
+ ...(input.overlaps.length ? ['overlapping-semantic-regions'] : [])
109
+ ], {
110
+ overlaps: input.overlaps.length,
111
+ highRiskOverlaps: high,
112
+ mediumRiskOverlaps: medium,
113
+ conflictKeys: uniqueStrings(input.overlaps.flatMap((overlap) => overlap.conflictKeys ?? [])),
114
+ byKind: countBy(input.overlaps.map((overlap) => overlap.overlapKind))
115
+ });
116
+ }
117
+
118
+ function sizeFacet(input) {
119
+ const operationCount = array(input.candidate?.operations ?? input.patch?.operations).length;
120
+ const changedSemanticRegions = input.changedSemanticRegions.length;
121
+ const conflictKeys = input.conflictKeys.length;
122
+ const score = clampScore(100 - Math.max(0, changedSemanticRegions - 3) * 8 - Math.max(0, operationCount - 3) * 4 - Math.max(0, conflictKeys - 4) * 3);
123
+ return scoreFacet('size', score, [
124
+ ...(changedSemanticRegions > 3 ? ['large-changed-region-set'] : []),
125
+ ...(operationCount > 3 ? ['large-operation-set'] : []),
126
+ ...(conflictKeys > 4 ? ['many-conflict-keys'] : [])
127
+ ], { changedSemanticRegions, operationCount, conflictKeys });
128
+ }
129
+
130
+ function semanticSidecarQualityFacet(input) {
131
+ const quality = semanticSidecarQuality(input);
132
+ if (!quality) return scoreFacet('semanticSidecarQuality', 100, [], { available: false });
133
+ const proofSummary = quality.proofSummary ?? {};
134
+ const warningCodes = uniqueStrings([...(quality.warnings ?? []).map((warning) => warning.code), ...strings(quality.expectedMissingReasonCodes)]);
135
+ const reviewProofs = (proofSummary.open ?? 0) + (proofSummary.stale ?? 0) + (proofSummary.assumed ?? 0) + (proofSummary.externalToolRequired ?? 0) + (proofSummary.unknown ?? 0);
136
+ const score = clampScore(
137
+ (quality.imported === false || quality.selected === false ? 25 : 100)
138
+ - (quality.eligible === false ? 35 : 0)
139
+ - Math.min(35, (quality.warningCount ?? warningCodes.length ?? 0) * 7)
140
+ - Math.min(40, (proofSummary.failed ?? 0) * 20)
141
+ - Math.min(25, reviewProofs * 5)
142
+ );
143
+ return scoreFacet('semanticSidecarQuality', score, [
144
+ ...(quality.imported === false || quality.selected === false ? ['semantic-sidecar-not-imported'] : []),
145
+ ...(quality.eligible === false ? ['semantic-sidecar-not-eligible'] : []),
146
+ ...warningCodes
147
+ ], {
148
+ available: true,
149
+ expected: quality.expected,
150
+ expectedSatisfied: quality.expectedSatisfied,
151
+ selected: quality.selected,
152
+ imported: quality.imported,
153
+ eligible: quality.eligible,
154
+ importCount: quality.importCount,
155
+ symbolCount: quality.symbolCount,
156
+ ownershipRegionCount: quality.ownershipRegionCount,
157
+ patchHintCount: quality.patchHintCount,
158
+ evidenceCount: quality.evidenceCount,
159
+ warningCount: quality.warningCount ?? warningCodes.length,
160
+ warningCodes,
161
+ proofSummary
162
+ });
163
+ }
164
+
165
+ function semanticSidecarQuality(input) {
166
+ return [
167
+ input.candidate?.semanticSidecarQuality,
168
+ input.candidate?.sidecarQuality,
169
+ input.candidate?.semanticSidecar?.quality,
170
+ input.candidate?.sidecar?.quality,
171
+ input.candidate?.metadata?.semanticSidecarQuality,
172
+ input.candidate?.metadata?.sidecarQuality,
173
+ input.source?.semanticSidecarQuality,
174
+ input.source?.sidecarQuality,
175
+ input.source?.semanticSidecar?.quality,
176
+ input.source?.sidecar?.quality,
177
+ input.source?.metadata?.semanticSidecarQuality,
178
+ input.source?.metadata?.sidecarQuality,
179
+ input.patch?.semanticSidecarQuality,
180
+ input.patch?.metadata?.semanticSidecarQuality
181
+ ].find(looksLikeSemanticSidecarQuality);
182
+ }
183
+
184
+ function looksLikeSemanticSidecarQuality(value) {
185
+ return value && typeof value === 'object' && (
186
+ value.schema === 'frontier.lang.semanticSidecarQuality.v1'
187
+ || value.eligible !== undefined
188
+ || value.imported !== undefined
189
+ || value.proofSummary !== undefined
190
+ || value.warningCount !== undefined
191
+ );
192
+ }
193
+
194
+ function scoreFacet(key, score, reasonCodes, signals) {
195
+ const normalizedScore = clampScore(score);
196
+ const weight = scoreFacetWeights[key] ?? 1;
197
+ return {
198
+ key,
199
+ score: normalizedScore,
200
+ weight,
201
+ weightedScore: roundScore(normalizedScore * weight / 100),
202
+ status: facetStatus(normalizedScore),
203
+ reasonCodes: uniqueStrings(reasonCodes),
204
+ signals: compactRecord(signals)
205
+ };
206
+ }
207
+
208
+ function facetStatus(score) {
209
+ if (score <= 0) return 'blocked';
210
+ if (score < 50) return 'weak';
211
+ if (score < 80) return 'partial';
212
+ return 'strong';
213
+ }
214
+
215
+ function evidenceStatus(record) { return String(record?.status ?? record?.metadata?.status ?? '').toLowerCase(); }
216
+ function countBy(values) { const counts = {}; for (const value of values ?? []) { const key = String(value ?? 'unknown'); counts[key] = (counts[key] ?? 0) + 1; } return counts; }
217
+ function clampScore(value) { return Math.max(0, Math.min(100, roundScore(value))); }
218
+ function roundScore(value) { return Math.round((Number.isFinite(value) ? value : 0) * 100) / 100; }
219
+ function array(value) { if (value === undefined || value === null) return []; return Array.isArray(value) ? value : [value]; }
220
+ function strings(value) { return array(value).map((entry) => String(entry ?? '')).filter(Boolean); }
221
+ function compactRecord(value) { return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0))); }
@@ -90,7 +90,7 @@ export function querySemanticPatchBundleOverlaps(records,query={}){
90
90
  }
91
91
 
92
92
  function sharedIndex(left,right,options){
93
- return{
93
+ const shared={
94
94
  operationContentHashes:intersect(left.operationContentHashes,right.operationContentHashes),
95
95
  editContentHashes:intersect(left.editContentHashes,right.editContentHashes),
96
96
  semanticEditKeys:intersect(left.semanticEditKeys,right.semanticEditKeys),
@@ -110,6 +110,28 @@ function sharedIndex(left,right,options){
110
110
  baseHashes:intersect(left.baseHashes,right.baseHashes),
111
111
  targetHashes:intersect(left.targetHashes,right.targetHashes)
112
112
  };
113
+ const scopedEdit=hasSharedEditScope(shared);
114
+ const scopedSource=hasSharedSourceScope(shared);
115
+ return{
116
+ ...shared,
117
+ operationContentHashes:scopedEdit?shared.operationContentHashes:[],
118
+ editContentHashes:scopedEdit?shared.editContentHashes:[],
119
+ semanticEditKeys:scopedSource?shared.semanticEditKeys:[],
120
+ semanticIdentityHashes:scopedSource?shared.semanticIdentityHashes:[],
121
+ semanticEditReplayCurrentHashes:scopedSource?shared.semanticEditReplayCurrentHashes:[],
122
+ semanticEditReplayOutputHashes:scopedEdit?shared.semanticEditReplayOutputHashes:[],
123
+ semanticTransformContentHashes:shared.projectionIdentityHashes.length?shared.semanticTransformContentHashes:[],
124
+ semanticTransformIdentityHashes:shared.projectionIdentityHashes.length?shared.semanticTransformIdentityHashes:[]
125
+ };
126
+ }
127
+
128
+ function hasSharedEditScope(shared){
129
+ return Boolean(shared.regionKeys.length||shared.conflictKeys.length||shared.sourceIdentityHashes.length
130
+ ||(shared.sourcePaths.length&&(shared.semanticEditKeys.length||shared.semanticIdentityHashes.length)));
131
+ }
132
+
133
+ function hasSharedSourceScope(shared){
134
+ return Boolean(shared.sourcePaths.length||shared.regionKeys.length||shared.conflictKeys.length||shared.sourceIdentityHashes.length);
113
135
  }
114
136
 
115
137
  function overlapAdmission(shared,{leftIndex,rightIndex,options}){
@@ -1,5 +1,12 @@
1
1
  export function sourcePreservationFromProjectionContext(context) {
2
- return context.nativeSource?.metadata?.sourcePreservation
2
+ return context.sourcePreservation
3
+ ?? context.metadata?.sourcePreservation
4
+ ?? context.importResult?.metadata?.sourcePreservation
5
+ ?? context.importResult?.nativeSource?.metadata?.sourcePreservation
6
+ ?? context.importResult?.nativeAst?.metadata?.sourcePreservation
7
+ ?? context.nativeSource?.metadata?.sourcePreservation
3
8
  ?? context.nativeAst?.metadata?.sourcePreservation
4
- ?? context.nativeSource?.ast?.metadata?.sourcePreservation;
9
+ ?? context.nativeSource?.ast?.metadata?.sourcePreservation
10
+ ?? context.universalAst?.metadata?.sourcePreservation
11
+ ?? context.importResult?.universalAst?.metadata?.sourcePreservation;
5
12
  }
@@ -17,13 +17,21 @@ export const NativeImportLanguageProfiles = Object.freeze([
17
17
  aliases: ['js', 'mjs', 'cjs', 'jsx'],
18
18
  extensions: ['.js', '.mjs', '.cjs', '.jsx'],
19
19
  parserAdapters: ['estree', 'babel', 'tree-sitter'],
20
- lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'dynamicRuntime']
20
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'dynamicRuntime'],
21
+ notes: [
22
+ 'lightweight scanner records declarations only; exact parser adapters must be injected by the host',
23
+ '.jsx sources are classified as javascript for declaration scanning; JSX element trees remain opaque without host parser evidence'
24
+ ]
21
25
  }),
22
26
  nativeImportLanguageProfile('typescript', {
23
27
  aliases: ['ts', 'tsx'],
24
28
  extensions: ['.ts', '.tsx'],
25
29
  parserAdapters: ['typescript-compiler-api', 'babel', 'tree-sitter'],
26
- lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'unsupportedSyntax']
30
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'unsupportedSyntax'],
31
+ notes: [
32
+ 'lightweight scanner records declarations only; exact parser adapters must be injected by the host',
33
+ '.tsx sources are classified as typescript for declaration scanning; JSX element trees remain opaque without host parser evidence'
34
+ ]
27
35
  }),
28
36
  nativeImportLanguageProfile('python', {
29
37
  aliases: ['py'],
@@ -4,7 +4,6 @@ import {
4
4
  splitParameters
5
5
  } from './native-region-scanner-core.js';
6
6
  import { jsImportDeclarations } from './native-region-scanner-js-imports.js';
7
-
8
7
  function jsCommentOnlyLine(trimmed) {
9
8
  return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*');
10
9
  }
@@ -128,7 +127,7 @@ function jsExportedContainerDeclaration(input, lineNumber, trimmed) {
128
127
  }
129
128
 
130
129
  function jsExportedFunctionWrapperDeclaration(input, lineNumber, trimmed) {
131
- const match = trimmed.match(/^export\s+default\s+((?:React\.)?(?:forwardRef|memo|lazy|observer))\s*(?:<[^>]+>)?\s*\(\s*(.+)$/);
130
+ const match = trimmed.match(/^export\s+default\s+((?:React\.)?(?:forwardRef|memo|lazy|observer)|Object\.freeze)\s*(?:<[^>]+>)?\s*\(\s*(.+)$/);
132
131
  if (!match) return undefined;
133
132
  const wrapper = match[1];
134
133
  const argument = match[2].trim();
@@ -148,6 +147,13 @@ function jsExportedFunctionWrapperDeclaration(input, lineNumber, trimmed) {
148
147
  parameters: splitParameters(functionMatch[1] ?? functionMatch[2])
149
148
  }, true);
150
149
  }
150
+ const classMatch = argument.match(/^(?:abstract\s+)?class\b(?:\s+(?!(?:extends|implements)\b)([A-Za-z_$][\w$]*))?/);
151
+ if (classMatch) {
152
+ return nativeDeclaration(input, lineNumber, 'ExportDefaultClassWrapperDeclaration', 'class', classMatch[1] ?? 'default', {
153
+ exportDefault: true,
154
+ wrapper
155
+ }, true);
156
+ }
151
157
  const aliasMatch = argument.match(/^([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*(?:[,)]|$)/);
152
158
  if (!aliasMatch) return undefined;
153
159
  return nativeDeclaration(input, lineNumber, 'ExportDefaultWrappedAlias', 'variable', 'default', {
@@ -44,6 +44,13 @@ export function jsImportDeclarations(input, lineNumber, trimmed) {
44
44
  importedName: 'default',
45
45
  importKind: trimmed.includes('import') ? 'dynamic-import-binding' : 'commonjs-require'
46
46
  }]);
47
+ const sideEffectRequireMatch = trimmed.match(/^require\s*\(\s*(['"])([^'"]+)\1\s*\)\s*;?$/);
48
+ if (sideEffectRequireMatch) {
49
+ return jsImportModuleDeclarations(input, lineNumber, sideEffectRequireMatch[2], 'CommonJsSideEffectRequireDeclaration', [], {
50
+ sideEffectOnly: true,
51
+ importKind: 'commonjs-require'
52
+ });
53
+ }
47
54
  return [];
48
55
  }
49
56
 
@@ -126,18 +126,18 @@ function scanJavaScriptLike(input) {
126
126
  } else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=/))) {
127
127
  const regionKind = jsRegionKindForDeclarationName(match[1], trimmed);
128
128
  pushDeclaration(nativeDeclaration(input, number, 'CommonJsExport', 'variable', match[1], { export: 'commonjs' }, false, { regionKind }));
129
- } else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|async|override|readonly|abstract|accessor|get|set)\s+)*(?:async\s+)?(?:get\s+|set\s+)?([A-Za-z_$][\w$]*)\??\s*(?:<[^({;]+>)?\s*\(([^)]*)\)\s*(?::\s*[^={]+)?(?:\{|=>|$)/)) && !jsControlKeyword(match[1])) {
129
+ } else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|async|override|readonly|abstract|accessor|get|set)\s+)*(?:async\s+)?(?:get\s+|set\s+)?(#?[A-Za-z_$][\w$]*)\??\s*(?:<[^({;]+>)?\s*\(([^)]*)\)\s*(?::\s*[^={]+)?(?:\{|=>|$)/)) && !jsControlKeyword(match[1])) {
130
130
  pushDeclaration(nativeDeclaration(input, number, 'MethodDefinition', 'method', `${currentClass}.${match[1]}`, {
131
131
  methodName: match[1],
132
132
  owner: currentClass,
133
133
  parameters: splitParameters(match[2])
134
134
  }, declarationLine.includes('{') || declarationLine.includes('=>')));
135
- } else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|readonly|declare|accessor)\s+)*([A-Za-z_$][\w$]*)[?!]?\s*(?::\s*([^=;{]+))?(?:[=;]|$)/))) {
135
+ } else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|readonly|declare|accessor)\s+)*(#?[A-Za-z_$][\w$]*)[?!]?\s*(?::\s*([^=;{]+))?(?:[=;]|$)/))) {
136
136
  pushDeclaration(nativeDeclaration(input, number, 'PropertyDefinition', 'property', `${currentClass}.${match[1]}`, {
137
137
  propertyName: match[1],
138
138
  owner: currentClass,
139
139
  valueType: match[2]?.trim()
140
- }, false));
140
+ }, false, { regionKind: 'property' }));
141
141
  }
142
142
  if (currentClass) {
143
143
  classDepth += braceDelta(trimmed);
@@ -271,7 +271,7 @@ function jsInlineClassMemberDeclarations(input, lineNumber, declarationLine, cla
271
271
  if (open < 0 || close <= open) return [];
272
272
  const body = declarationLine.slice(open + 1, close);
273
273
  const declarations = [];
274
- for (const match of body.matchAll(/(?:(?:public|private|protected|static|async|override|readonly|abstract|accessor|get|set)\s+)*(?:async\s+)?(?:get\s+|set\s+)?([A-Za-z_$][\w$]*)\??\s*(?:<[^({;]+>)?\s*\(([^)]*)\)\s*(?::\s*[^={;]+)?\s*(?:\{|=>)/g)) {
274
+ for (const match of body.matchAll(/(?:(?:public|private|protected|static|async|override|readonly|abstract|accessor|get|set)\s+)*(?:async\s+)?(?:get\s+|set\s+)?(#?[A-Za-z_$][\w$]*)\??\s*(?:<[^({;]+>)?\s*\(([^)]*)\)\s*(?::\s*[^={;]+)?\s*(?:\{|=>)/g)) {
275
275
  if (jsControlKeyword(match[1])) continue;
276
276
  declarations.push(nativeDeclaration(input, lineNumber, 'MethodDefinition', 'method', `${className}.${match[1]}`, {
277
277
  methodName: match[1],
@@ -26,6 +26,7 @@ import {
26
26
  scanHaskell,
27
27
  scanR
28
28
  } from './native-region-scanner-functional.js';
29
+ import { normalizeNativeLanguageId } from './native-import-utils.js';
29
30
  export { lightweightCoverageLosses } from './native-region-scanner-core.js';
30
31
  export {
31
32
  detectNewlineStyle,
@@ -34,7 +35,7 @@ export {
34
35
  } from './native-source-preservation-scanner.js';
35
36
 
36
37
  function scanNativeDeclarations(input) {
37
- const language = String(input.language).toLowerCase();
38
+ const language = normalizeNativeLanguageId(input.language) || String(input.language).toLowerCase();
38
39
  if (language === 'javascript' || language === 'typescript') return scanJavaScriptLike(input);
39
40
  if (language === 'python') return scanPython(input);
40
41
  if (language === 'rust') return scanRust(input);
@@ -20,20 +20,20 @@ function semanticOwnershipRegionForSymbol(imported, symbol, mapping, nativeNode,
20
20
  const language = symbol.language ?? imported?.language ?? imported?.nativeAst?.language ?? imported?.nativeSource?.language;
21
21
  const sourceSpan = mapping?.sourceSpan ?? symbol.definitionSpan ?? nativeNode?.span;
22
22
  const regionKind = semanticRegionKindForSymbol(symbol, mapping, nativeNode);
23
- const key = [
23
+ const key = symbol?.metadata?.ownershipRegionKey ?? [
24
24
  options.regionPrefix ?? 'source',
25
25
  sourcePath ?? `${language}:memory`,
26
26
  regionKind,
27
27
  symbol.name ?? symbol.id
28
28
  ].map((part) => String(part).replace(/\s+/g, ' ').trim()).join('#');
29
29
  return {
30
- id: `region_${caseSensitiveIdFragment(key)}`,
30
+ id: symbol?.metadata?.ownershipRegionId ?? `region_${caseSensitiveIdFragment(key)}`,
31
31
  key,
32
32
  regionKind,
33
33
  granularity: 'symbol',
34
34
  language,
35
35
  sourcePath,
36
- sourceHash: imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash,
36
+ sourceHash: imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash ?? imported?.sourceHash,
37
37
  symbolId: symbol.id,
38
38
  symbolName: symbol.name,
39
39
  symbolKind: symbol.kind,
@@ -76,17 +76,19 @@ function semanticOwnershipRegionForDeclaration(input, declaration, documentId) {
76
76
  }
77
77
 
78
78
  function semanticPatchHintForRegion(region, readiness, options = {}) {
79
+ const supportedOperations = semanticRegionSupportedOperations(region);
79
80
  return {
80
81
  id: `hint_${idFragment(region.id)}`,
81
82
  kind: 'source-region-patch',
82
83
  ownershipRegionId: region.id,
83
84
  ownershipKey: region.key,
85
+ operation: supportedOperations[0] ?? 'replace-region',
84
86
  sourcePath: region.sourcePath,
85
87
  sourceHash: region.sourceHash,
86
88
  sourceSpan: region.sourceSpan,
87
89
  readiness,
88
90
  precision: region.precision,
89
- supportedOperations: semanticRegionSupportedOperations(region),
91
+ supportedOperations,
90
92
  projection: {
91
93
  sourceLanguage: region.language,
92
94
  targetPath: options.targetPath ?? region.sourcePath,
@@ -8,6 +8,16 @@ export interface SemanticImportSidecarQualityWarning {
8
8
  readonly sourcePaths: readonly string[];
9
9
  }
10
10
 
11
+ export type SemanticImportSidecarRecordClassification = 'useful' | 'expected-empty' | 'unexpectedly-empty' | 'missing' | string;
12
+
13
+ export interface SemanticImportSidecarQualityRecord {
14
+ readonly classification: SemanticImportSidecarRecordClassification;
15
+ readonly reasonCode: string;
16
+ readonly message: string;
17
+ readonly action: string;
18
+ readonly sourcePaths: readonly string[];
19
+ }
20
+
11
21
  export interface SemanticImportSidecarProofAdmissionSummary {
12
22
  readonly total: number;
13
23
  readonly obligations: number;
@@ -26,12 +36,14 @@ export interface SemanticImportSidecarProofAdmissionSummary {
26
36
  export interface SemanticImportSidecarQuality {
27
37
  readonly schema: 'frontier.lang.semanticSidecarQuality.v1';
28
38
  readonly expected: boolean;
39
+ readonly expectedEmpty: boolean;
29
40
  readonly expectedSatisfied: boolean;
30
41
  readonly expectedMissingReasonCodes: readonly string[];
31
42
  readonly selected: boolean;
32
43
  readonly eligible: boolean;
33
44
  readonly imported: boolean;
34
45
  readonly importCount: number;
46
+ readonly record: SemanticImportSidecarQualityRecord;
35
47
  readonly symbolCount: number;
36
48
  readonly ownershipRegionCount: number;
37
49
  readonly patchHintCount: number;
@@ -45,6 +57,7 @@ export interface SemanticImportSidecarQuality {
45
57
  export interface SemanticImportSidecarAdmission {
46
58
  readonly schema: 'frontier.lang.semanticSidecarAdmission.v1';
47
59
  readonly expected: boolean;
60
+ readonly expectedEmpty: boolean;
48
61
  readonly expectedSatisfied: boolean;
49
62
  readonly expectedMissingReasonCodes: readonly string[];
50
63
  readonly selected: boolean;
@@ -53,6 +66,7 @@ export interface SemanticImportSidecarAdmission {
53
66
  readonly importCount: number;
54
67
  readonly readiness: SemanticMergeReadiness;
55
68
  readonly action: string;
69
+ readonly record: SemanticImportSidecarQualityRecord;
56
70
  readonly counts: {
57
71
  readonly symbols: number;
58
72
  readonly ownershipRegions: number;