@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
@@ -0,0 +1,200 @@
1
+ import { idFragment, uniqueRecordsById, uniqueStrings } from '../../native-import-utils.js';
2
+ import { sourceHash } from './bidirectionalTargetChangeRecordInternals.js';
3
+
4
+ export function summarizeSourceMapBackprojection(matches) {
5
+ const links = matches.flatMap((match) => match.sourceMapLinks ?? []);
6
+ return {
7
+ sourceMapBackedMatches: matches.filter((match) => (match.sourceMapLinks ?? []).length > 0).length,
8
+ sourceMapLinks: links.length,
9
+ sourceMapIds: uniqueStrings(links.map((link) => link.sourceMapId)),
10
+ sourceMapMappingIds: uniqueStrings(links.map((link) => link.sourceMapMappingId))
11
+ };
12
+ }
13
+
14
+ export function createRoundtripEvidence(context) {
15
+ const sourceMapLinks = uniqueRecordsById(context.sourceAnchorMatches.flatMap((match) => match.sourceMapLinks ?? []));
16
+ const targetProjectionLinks = uniqueRecordsById((context.targetChangeSet.changedRegions ?? [])
17
+ .flatMap((region) => array(region.metadata?.changedRegionProjection?.sourceMapLinks)));
18
+ const sourceAnchors = uniqueRecordsByKey(context.sourceAnchorMatches.flatMap((match) => match.sourceAnchors ?? []));
19
+ const lineageResolutions = uniqueRecordsById(context.sourceAnchorMatches.flatMap((match) => match.lineageResolutions ?? []));
20
+ return {
21
+ schema: 'frontier.lang.bidirectionalTargetChangeRoundtripEvidence.v1',
22
+ version: 1,
23
+ id: `roundtrip_evidence_${idFragment(context.id)}`,
24
+ evidenceId: context.evidenceId,
25
+ sourcePatchBundleId: context.sourcePatchBundleId,
26
+ historyRecordId: context.historyRecordId,
27
+ source: sourceIdentity(context.source),
28
+ target: targetChangeIdentity(context.targetChangeSet),
29
+ targetRegions: (context.targetChangeSet.changedRegions ?? []).map(targetRegionIdentity),
30
+ sourceAnchorMatches: context.sourceAnchorMatches.map(matchIdentity),
31
+ sourceAnchors,
32
+ sourceMapEvidence: {
33
+ ...context.sourceMapBackprojection,
34
+ sourceMapLinkIds: uniqueStrings(sourceMapLinks.map((link) => link.id)),
35
+ staleSourceMapLinkIds: uniqueStrings(context.targetPortability.staleSourceMapLinkIds),
36
+ targetProjectionSourceMapLinks: targetProjectionLinks.length,
37
+ targetProjectionSourceMapLinkIds: uniqueStrings(targetProjectionLinks.map((link) => link.id)),
38
+ targetProjectionSourceMapIds: uniqueStrings(targetProjectionLinks.map((link) => link.sourceMapId)),
39
+ targetProjectionSourceMapMappingIds: uniqueStrings(targetProjectionLinks.map((link) => link.sourceMapMappingId))
40
+ },
41
+ lineageEvidence: {
42
+ lineageResolutionIds: uniqueStrings(lineageResolutions.map((resolution) => resolution.id)),
43
+ lineageEventIds: uniqueStrings(lineageResolutions.flatMap((resolution) => resolution.traversedEventIds ?? [])),
44
+ lineageEvidenceIds: uniqueStrings(lineageResolutions.flatMap((resolution) => resolution.evidenceIds ?? [])),
45
+ lineageProofIds: uniqueStrings(lineageResolutions.flatMap((resolution) => resolution.proofIds ?? [])),
46
+ lineageReasonCodes: uniqueStrings(lineageResolutions.flatMap((resolution) => resolution.reasonCodes ?? []))
47
+ },
48
+ targetPortability: {
49
+ status: context.targetPortability.status,
50
+ action: context.targetPortability.action,
51
+ readiness: context.targetPortability.readiness,
52
+ reasonCodes: context.targetPortability.reasonCodes,
53
+ conflictKeys: context.targetPortability.conflictKeys
54
+ },
55
+ admission: {
56
+ status: context.readiness === 'blocked' ? 'blocked' : 'needs-review',
57
+ readiness: context.readiness,
58
+ action: context.targetPortability.action,
59
+ reasonCodes: context.reasons,
60
+ conflictKeys: uniqueStrings([
61
+ ...array(context.targetPortability.conflictKeys),
62
+ ...context.sourceAnchorMatches.flatMap((match) => match.conflictKeys ?? [])
63
+ ]),
64
+ evidenceIds: [context.evidenceId],
65
+ reviewRequired: true,
66
+ autoMergeClaim: false,
67
+ semanticEquivalenceClaim: false
68
+ },
69
+ reviewRequired: true,
70
+ autoMergeClaim: false,
71
+ semanticEquivalenceClaim: false
72
+ };
73
+ }
74
+
75
+ export function createSemanticMergeAdmissionEvidence(context) {
76
+ return {
77
+ schema: 'frontier.lang.bidirectionalTargetChangeSemanticMergeAdmission.v1',
78
+ version: 1,
79
+ id: `semantic_merge_admission_${idFragment(context.id)}_target_change`,
80
+ evidenceIds: [context.evidenceId],
81
+ sourcePatchBundleId: context.sourcePatchBundleId,
82
+ historyRecordId: context.historyRecordId,
83
+ targetChangeSetId: context.targetChangeSet.id,
84
+ targetPatchId: context.targetChangeSet.patch?.id,
85
+ targetMergeCandidateId: context.targetChangeSet.mergeCandidate?.id,
86
+ readiness: context.readiness,
87
+ status: context.readiness === 'blocked' ? 'blocked' : 'needs-review',
88
+ action: context.targetPortability.action,
89
+ reasonCodes: context.reasons,
90
+ conflictKeys: context.roundtripEvidence.admission.conflictKeys,
91
+ sourceAnchorMatchIds: uniqueStrings(context.sourceAnchorMatches.map((match) => match.id)),
92
+ sourceAnchorKeys: uniqueStrings(context.roundtripEvidence.sourceAnchors.map((anchor) => anchor.key)),
93
+ targetRegionKeys: uniqueStrings(context.roundtripEvidence.targetRegions.map((region) => region.key)),
94
+ sourceMapLinkIds: context.roundtripEvidence.sourceMapEvidence.sourceMapLinkIds,
95
+ sourceMapIds: context.roundtripEvidence.sourceMapEvidence.sourceMapIds,
96
+ sourceMapMappingIds: context.roundtripEvidence.sourceMapEvidence.sourceMapMappingIds,
97
+ staleSourceMapLinkIds: context.roundtripEvidence.sourceMapEvidence.staleSourceMapLinkIds,
98
+ targetProjectionSourceMapLinkIds: context.roundtripEvidence.sourceMapEvidence.targetProjectionSourceMapLinkIds,
99
+ targetProjectionSourceMapMappingIds: context.roundtripEvidence.sourceMapEvidence.targetProjectionSourceMapMappingIds,
100
+ lineageResolutionIds: context.roundtripEvidence.lineageEvidence.lineageResolutionIds,
101
+ reviewRequired: true,
102
+ autoMergeClaim: false,
103
+ semanticEquivalenceClaim: false
104
+ };
105
+ }
106
+
107
+ function sourceIdentity(source) {
108
+ return compactRecord({
109
+ importId: source?.id,
110
+ language: source?.language,
111
+ sourcePath: source?.sourcePath,
112
+ sourceHash: sourceHash(source),
113
+ nativeSourceId: source?.nativeSource?.id,
114
+ nativeAstId: source?.nativeAst?.id,
115
+ semanticIndexId: source?.semanticIndex?.id,
116
+ universalAstId: source?.universalAst?.id,
117
+ sourceMapIds: uniqueStrings((source?.sourceMaps ?? []).map((sourceMap) => sourceMap?.id))
118
+ });
119
+ }
120
+
121
+ function targetChangeIdentity(targetChangeSet) {
122
+ return compactRecord({
123
+ changeSetId: targetChangeSet.id,
124
+ language: targetChangeSet.language,
125
+ sourcePath: targetChangeSet.sourcePath,
126
+ beforeHash: targetChangeSet.beforeHash,
127
+ afterHash: targetChangeSet.afterHash,
128
+ beforeImportId: targetChangeSet.before?.id,
129
+ afterImportId: targetChangeSet.after?.id,
130
+ beforeNativeSourceId: targetChangeSet.before?.nativeSource?.id,
131
+ afterNativeSourceId: targetChangeSet.after?.nativeSource?.id,
132
+ beforeNativeAstId: targetChangeSet.before?.nativeAst?.id,
133
+ afterNativeAstId: targetChangeSet.after?.nativeAst?.id,
134
+ beforeSemanticIndexId: targetChangeSet.before?.semanticIndex?.id,
135
+ afterSemanticIndexId: targetChangeSet.after?.semanticIndex?.id,
136
+ patchId: targetChangeSet.patch?.id,
137
+ mergeCandidateId: targetChangeSet.mergeCandidate?.id,
138
+ sourceMapIds: uniqueStrings((targetChangeSet.sourceMaps ?? []).map((sourceMap) => sourceMap?.id))
139
+ });
140
+ }
141
+
142
+ function targetRegionIdentity(region) {
143
+ const projection = region.metadata?.changedRegionProjection;
144
+ return compactRecord({
145
+ id: region.id,
146
+ key: region.key,
147
+ conflictKey: region.conflictKey,
148
+ changeKind: region.changeKind,
149
+ regionKind: region.regionKind,
150
+ language: region.language,
151
+ sourcePath: region.sourcePath,
152
+ sourceHash: region.sourceHash,
153
+ symbolId: region.symbolId,
154
+ symbolName: region.symbolName ?? region.name,
155
+ sourceSpan: region.sourceSpan,
156
+ changedRegionProjectionId: projection?.id,
157
+ beforeIdentity: projection?.before?.identity,
158
+ afterIdentity: projection?.after?.identity,
159
+ sourceMapLinkIds: uniqueStrings(array(projection?.sourceMapLinks).map((link) => link.id)),
160
+ sourceMapIds: uniqueStrings(array(projection?.sourceMapLinks).map((link) => link.sourceMapId)),
161
+ sourceMapMappingIds: uniqueStrings(array(projection?.sourceMapLinks).map((link) => link.sourceMapMappingId))
162
+ });
163
+ }
164
+
165
+ function matchIdentity(match) {
166
+ return compactRecord({
167
+ id: match.id,
168
+ status: match.status,
169
+ targetRegionKey: match.targetRegion?.key,
170
+ targetRegionConflictKey: match.targetRegion?.conflictKey,
171
+ sourceAnchorKeys: uniqueStrings((match.sourceAnchors ?? []).map((anchor) => anchor.key)),
172
+ sourceMapLinkIds: uniqueStrings((match.sourceMapLinks ?? []).map((link) => link.id)),
173
+ sourceMapMappingIds: uniqueStrings((match.sourceMapLinks ?? []).map((link) => link.sourceMapMappingId)),
174
+ lineageResolutionIds: uniqueStrings((match.lineageResolutions ?? []).map((resolution) => resolution.id)),
175
+ portabilityStatus: match.portability?.status,
176
+ portabilityAction: match.portability?.action,
177
+ reasonCodes: match.reasonCodes,
178
+ conflictKeys: match.conflictKeys
179
+ });
180
+ }
181
+
182
+ function uniqueRecordsByKey(records) {
183
+ const seen = new Set();
184
+ const result = [];
185
+ for (const record of records ?? []) {
186
+ const key = record?.key ?? record?.id;
187
+ if (!key || seen.has(key)) continue;
188
+ seen.add(key);
189
+ result.push(record);
190
+ }
191
+ return result;
192
+ }
193
+
194
+ function array(value) {
195
+ return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value];
196
+ }
197
+
198
+ function compactRecord(value) {
199
+ return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
200
+ }
@@ -9,6 +9,7 @@ import { createSemanticHistoryRecord } from './semanticHistoryRecords.js';
9
9
  import { diffNativeSourceImports } from './diffNativeSourceImports.js';
10
10
  import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
11
11
  import { attachBidirectionalMatchPortability, classifyBidirectionalTargetPortability } from './bidirectionalTargetPortability.js';
12
+ import { createRoundtripEvidence, createSemanticMergeAdmissionEvidence, summarizeSourceMapBackprojection } from './bidirectionalTargetRoundtripEvidence.js';
12
13
  import {
13
14
  anchorsFromSourceSidecar,
14
15
  classifyBidirectionalReadiness,
@@ -85,16 +86,54 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
85
86
  ...array(targetPortability.reasonCodes),
86
87
  ...sourceAnchorMatches.flatMap((match) => match.reasonCodes)
87
88
  ]);
88
- const evidence = [createBidirectionalEvidence({
89
+ const evidenceId = input.evidenceId ?? `evidence_${idFragment(id)}_bidirectional_target_change`;
90
+ const sourcePatchBundleId = input.sourcePatchBundleId ?? `semantic_patch_bundle_${idFragment(id)}_source_port`;
91
+ const historyRecordId = input.historyRecordId ?? `semantic_history_${idFragment(id)}_target_change`;
92
+ const sourceMapBackprojection = summarizeSourceMapBackprojection(sourceAnchorMatches);
93
+ const roundtripEvidence = createRoundtripEvidence({
89
94
  id,
90
- input,
95
+ evidenceId,
96
+ sourcePatchBundleId,
97
+ historyRecordId,
98
+ source,
99
+ targetChangeSet,
100
+ sourceAnchorMatches,
101
+ targetPortability,
102
+ readiness,
103
+ reasons,
104
+ sourceMapBackprojection
105
+ });
106
+ const semanticMergeAdmission = createSemanticMergeAdmissionEvidence({
107
+ id,
108
+ evidenceId,
109
+ sourcePatchBundleId,
110
+ historyRecordId,
111
+ targetChangeSet,
112
+ sourceAnchorMatches,
113
+ targetPortability,
114
+ readiness,
115
+ reasons,
116
+ roundtripEvidence
117
+ });
118
+ const bidirectionalEvidence = createBidirectionalEvidence({
119
+ id,
120
+ input: { ...input, evidenceId },
91
121
  source,
92
122
  targetChangeSet,
93
123
  sourceAnchorMatches,
94
124
  targetPortability,
95
125
  readiness,
96
126
  reasons
97
- })];
127
+ });
128
+ const evidence = [{
129
+ ...bidirectionalEvidence,
130
+ metadata: {
131
+ ...bidirectionalEvidence.metadata,
132
+ roundtripEvidenceId: roundtripEvidence.id,
133
+ roundtripEvidence,
134
+ semanticMergeAdmission
135
+ }
136
+ }];
98
137
  const sourceChangedRegions = sourceAnchorMatches.flatMap((match) => sourceRegionsForMatch(match, readiness));
99
138
  const sourcePatchBundle = createSemanticPatchBundleRecord({
100
139
  id: `${id}_source_port_projection`,
@@ -112,22 +151,30 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
112
151
  targetPatchId: targetChangeSet.patch?.id,
113
152
  targetMergeCandidateId: targetChangeSet.mergeCandidate?.id,
114
153
  targetPortability,
115
- sourceMapBackprojection: summarizeSourceMapBackprojection(sourceAnchorMatches)
154
+ sourceMapBackprojection,
155
+ roundtripEvidenceId: roundtripEvidence.id,
156
+ semanticMergeAdmission
116
157
  }
117
158
  }, {
118
- id: input.sourcePatchBundleId ?? `semantic_patch_bundle_${idFragment(id)}_source_port`,
159
+ id: sourcePatchBundleId,
119
160
  patchId: targetChangeSet.patch?.id,
120
161
  mergeCandidateId: targetChangeSet.mergeCandidate?.id,
121
162
  admission: { status: readiness === 'blocked' ? 'blocked' : 'needs-review', readiness },
122
163
  metadata: {
123
164
  source: 'createBidirectionalTargetChangeRecord',
165
+ targetChangeSetId: targetChangeSet.id,
166
+ targetPatchId: targetChangeSet.patch?.id,
167
+ targetMergeCandidateId: targetChangeSet.mergeCandidate?.id,
124
168
  targetPortability,
169
+ sourceMapBackprojection,
170
+ roundtripEvidenceId: roundtripEvidence.id,
171
+ semanticMergeAdmission,
125
172
  autoMergeClaim: false,
126
173
  semanticEquivalenceClaim: false
127
174
  }
128
175
  });
129
176
  const historyRecord = createSemanticHistoryRecord({
130
- id: input.historyRecordId ?? `semantic_history_${idFragment(id)}_target_change`,
177
+ id: historyRecordId,
131
178
  importResult: source,
132
179
  language: source?.language,
133
180
  sourcePath: source?.sourcePath,
@@ -155,7 +202,9 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
155
202
  sourcePatchBundleId: sourcePatchBundle.id,
156
203
  targetChangeSetId: targetChangeSet.id,
157
204
  targetPortability,
158
- sourceMapBackprojection: summarizeSourceMapBackprojection(sourceAnchorMatches),
205
+ sourceMapBackprojection,
206
+ roundtripEvidenceId: roundtripEvidence.id,
207
+ semanticMergeAdmission,
159
208
  autoMergeClaim: false,
160
209
  semanticEquivalenceClaim: false
161
210
  }
@@ -172,6 +221,7 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
172
221
  targetChangeSet,
173
222
  sourceAnchorMatches,
174
223
  targetPortability,
224
+ roundtripEvidence,
175
225
  sourcePatchBundle,
176
226
  historyRecord,
177
227
  evidence,
@@ -185,6 +235,9 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
185
235
  deletedSourceAnchors: sourceAnchorMatches.filter((match) => match.status === 'deleted').length,
186
236
  sourceChangedRegions: sourceChangedRegions.length,
187
237
  sourceMapBackedMatches: sourceAnchorMatches.filter((match) => match.sourceMapLinks.length > 0).length,
238
+ sourceMapLinks: sourceMapBackprojection.sourceMapLinks,
239
+ sourceMapMappingIds: sourceMapBackprojection.sourceMapMappingIds.length,
240
+ lineageResolutions: roundtripEvidence.lineageEvidence.lineageResolutionIds.length,
188
241
  targetPortabilityStatus: targetPortability.status,
189
242
  portableTargetRegions: targetPortability.status === 'portable' ? targetPortability.targetChangedRegions : 0,
190
243
  staleTargetRegions: targetPortability.status === 'stale' ? targetPortability.targetChangedRegions : 0,
@@ -195,21 +248,13 @@ export function createBidirectionalTargetChangeRecord(input = {}, options = {})
195
248
  semanticEquivalenceClaim: false,
196
249
  reviewRequired: true,
197
250
  targetPortability,
251
+ roundtripEvidenceId: roundtripEvidence.id,
252
+ semanticMergeAdmission,
198
253
  ...input.metadata
199
254
  }
200
255
  };
201
256
  }
202
257
 
203
- function summarizeSourceMapBackprojection(matches) {
204
- const links = matches.flatMap((match) => match.sourceMapLinks ?? []);
205
- return {
206
- sourceMapBackedMatches: matches.filter((match) => (match.sourceMapLinks ?? []).length > 0).length,
207
- sourceMapLinks: links.length,
208
- sourceMapIds: uniqueStrings(links.map((link) => link.sourceMapId)),
209
- sourceMapMappingIds: uniqueStrings(links.map((link) => link.sourceMapMappingId))
210
- };
211
- }
212
-
213
258
  function array(value) {
214
259
  return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value];
215
260
  }
@@ -1,4 +1,4 @@
1
- import{idFragment}from'../../native-import-utils.js';import{detectNewlineStyle,scanPreservedSourceDirectives,scanPreservedSourceTokens}from'../../native-region-scanner.js';import{hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
1
+ import{countBy,idFragment,uniqueStrings}from'../../native-import-utils.js';import{detectNewlineStyle,scanPreservedSourceDirectives,scanPreservedSourceTokens}from'../../native-region-scanner.js';import{hashSemanticValue}from'@shapeshift-labs/frontier-lang-kernel';
2
2
  export function createNativeSourcePreservation(options) {
3
3
  if (!options || typeof options.sourceText !== 'string') {
4
4
  throw new Error('createNativeSourcePreservation requires sourceText');
@@ -26,6 +26,16 @@ export function createNativeSourcePreservation(options) {
26
26
  maxDirectives: options.maxDirectives
27
27
  });
28
28
  const directives = directiveScan.directives;
29
+ const triviaByKind = countBy(tokensAndTrivia.trivia.map((entry) => entry.kind ?? 'unknown'));
30
+ const directivesByKind = countBy(directives.map((entry) => entry.kind ?? 'directive'));
31
+ const directiveKinds = uniqueStrings(directives.map((entry) => entry.kind ?? 'directive'));
32
+ const commentSpanIds = tokensAndTrivia.trivia
33
+ .filter((entry) => entry.kind === 'comment')
34
+ .map((entry) => entry.id)
35
+ .filter(Boolean);
36
+ const directiveSpanIds = directives
37
+ .map((entry) => entry.id)
38
+ .filter(Boolean);
29
39
  const newline = detectNewlineStyle(sourceText);
30
40
  return {
31
41
  kind: 'frontier.lang.nativeSourcePreservation',
@@ -48,6 +58,11 @@ export function createNativeSourcePreservation(options) {
48
58
  directives: directives.length,
49
59
  comments: tokensAndTrivia.trivia.filter((entry) => entry.kind === 'comment').length,
50
60
  whitespace: tokensAndTrivia.trivia.filter((entry) => entry.kind === 'whitespace' || entry.kind === 'newline').length,
61
+ triviaByKind,
62
+ directivesByKind,
63
+ directiveKinds,
64
+ commentSpanIds,
65
+ directiveSpanIds,
51
66
  exactSourceAvailable: options.includeSourceText !== false,
52
67
  truncated: tokensAndTrivia.truncated || directiveScan.truncated
53
68
  },
@@ -22,7 +22,8 @@ export function createProjectImportAdmissionRecord(projectResult,options={}){
22
22
  const importSummaries=projectAdmissionImports(imports,contract?.sources??[],mergeCandidates);
23
23
  const languages=admissionLanguages(importSummaries);
24
24
  const semanticEvidence=admissionSemanticEvidence(projectResult,imports,importSummaries);
25
- const sourcePreservation=admissionSourcePreservation(importSummaries,contract);
25
+ const sourceStaleness=admissionSourceStaleness(imports,importSummaries,contract);
26
+ const sourcePreservation=admissionSourcePreservationWithStaleness(admissionSourcePreservation(importSummaries,contract),sourceStaleness);
26
27
  const ownership=admissionOwnership(contract,mergeCandidates);
27
28
  const mergeCandidateRisk=admissionMergeCandidates(projectResult,imports,mergeCandidates,lossSummary);
28
29
  const readiness=maxSemanticMergeReadiness(
@@ -54,6 +55,7 @@ export function createProjectImportAdmissionRecord(projectResult,options={}){
54
55
  readiness,
55
56
  sourceCount:imports.length,
56
57
  semanticEvidence,
58
+ sourceStaleness,
57
59
  sourcePreservation,
58
60
  ownership,
59
61
  mergeCandidateRisk,
@@ -71,6 +73,7 @@ export function createProjectImportAdmissionRecord(projectResult,options={}){
71
73
  sourceCount:imports.length,
72
74
  languages,
73
75
  semanticEvidence,
76
+ sourceStaleness,
74
77
  sourcePreservation,
75
78
  ownership,
76
79
  mergeCandidates:mergeCandidateRisk,
@@ -89,3 +92,150 @@ export function createProjectImportAdmissionRecord(projectResult,options={}){
89
92
  }
90
93
  };
91
94
  }
95
+
96
+ function admissionSourceStaleness(imports,importSummaries,contract){
97
+ const total=Math.max(imports.length,importSummaries.length,contract?.sources?.length??0);
98
+ const records=[];
99
+ for(let index=0;index<total;index+=1){
100
+ const record=sourceStalenessRecord(imports[index],importSummaries[index],contract?.sources?.[index]);
101
+ if(record.reasonCodes.length) records.push(record);
102
+ }
103
+ const staleRecords=records.filter((record)=>record.staleByContentHash||record.staleByBaseHash);
104
+ const contentHashRecords=records.filter((record)=>record.staleByContentHash);
105
+ const baseHashRecords=records.filter((record)=>record.staleByBaseHash);
106
+ const dirtyWorkspaceRecords=records.filter((record)=>record.dirtyWorkspace);
107
+ const unverifiedRecords=records.filter((record)=>record.unverifiedSourceHash);
108
+ return {
109
+ total,
110
+ stale:staleRecords.length,
111
+ contentHashStale:contentHashRecords.length,
112
+ baseHashStale:baseHashRecords.length,
113
+ dirtyWorkspace:dirtyWorkspaceRecords.length,
114
+ unverified:unverifiedRecords.length,
115
+ staleSourcePaths:sourcePaths(staleRecords),
116
+ contentHashStaleSourcePaths:sourcePaths(contentHashRecords),
117
+ baseHashStaleSourcePaths:sourcePaths(baseHashRecords),
118
+ dirtyWorkspaceSourcePaths:sourcePaths(dirtyWorkspaceRecords),
119
+ unverifiedSourcePaths:sourcePaths(unverifiedRecords),
120
+ records
121
+ };
122
+ }
123
+
124
+ function sourceStalenessRecord(imported,summary,source){
125
+ const nativeSource=imported?.nativeSource;
126
+ const nativeAst=imported?.nativeAst??nativeSource?.ast;
127
+ const preservation=imported?.metadata?.sourcePreservation
128
+ ??nativeSource?.metadata?.sourcePreservation
129
+ ??nativeAst?.metadata?.sourcePreservation
130
+ ??imported?.universalAst?.metadata?.sourcePreservation;
131
+ const metadata=[
132
+ imported?.metadata,
133
+ nativeSource?.metadata,
134
+ nativeAst?.metadata,
135
+ preservation?.metadata,
136
+ imported?.universalAst?.metadata,
137
+ imported?.patch?.metadata
138
+ ].filter((entry)=>entry&&typeof entry==='object');
139
+ const lossReasons=(imported?.losses??nativeAst?.losses??[]).flatMap((loss)=>[
140
+ loss?.reason,
141
+ loss?.metadata?.reason,
142
+ ...(Array.isArray(loss?.reasonCodes)?loss.reasonCodes:[]),
143
+ ...(Array.isArray(loss?.metadata?.reasonCodes)?loss.metadata.reasonCodes:[])
144
+ ]);
145
+ const observedReasonCodes=uniqueStrings([
146
+ ...metadata.flatMap((entry)=>[
147
+ entry.reason,
148
+ ...(Array.isArray(entry.reasonCodes)?entry.reasonCodes:[]),
149
+ ...(Array.isArray(entry.reasons)?entry.reasons:[])
150
+ ]),
151
+ ...lossReasons
152
+ ]);
153
+ const sourcePath=firstString(summary?.sourcePath,source?.sourcePath,imported?.sourcePath,nativeSource?.sourcePath,nativeAst?.sourcePath,preservation?.sourcePath);
154
+ const sourceHash=firstString(summary?.sourceHash,source?.sourceHash,imported?.sourceHash,nativeSource?.sourceHash,nativeAst?.sourceHash,preservation?.sourceHash);
155
+ const declaredSourceHash=firstMetadataString(metadata,'declaredSourceHash');
156
+ const contentHash=firstMetadataString(metadata,'currentContentHash','actualContentHash','contentHash')??sourceHash;
157
+ const declaredContentHash=firstMetadataString(metadata,'declaredContentHash','expectedContentHash','expectedSourceHash')??declaredSourceHash;
158
+ const sourceHashMismatch=Boolean(declaredSourceHash&&sourceHash&&declaredSourceHash!==sourceHash);
159
+ const contentHashMismatch=Boolean(declaredContentHash&&contentHash&&declaredContentHash!==contentHash);
160
+ const preservationHashMismatch=Boolean(preservation?.sourceHash&&sourceHash&&preservation.sourceHash!==sourceHash);
161
+ const sourceHashVerifiedFalse=metadata.some((entry)=>entry.sourceHashVerified===false);
162
+ const staleByContentHash=sourceHashMismatch
163
+ ||contentHashMismatch
164
+ ||preservationHashMismatch
165
+ ||(sourceHashVerifiedFalse&&Boolean(declaredSourceHash||declaredContentHash));
166
+ const baseHash=firstString(imported?.baseHash,imported?.patch?.baseHash,firstMetadataString(metadata,'baseHash'));
167
+ const expectedBaseHash=firstMetadataString(metadata,'expectedBaseHash','declaredBaseHash','sourceBaseHash');
168
+ const currentBaseHash=firstMetadataString(metadata,'currentBaseHash','actualBaseHash','headBaseHash','workspaceBaseHash');
169
+ const staleByBaseHash=Boolean(
170
+ expectedBaseHash&&currentBaseHash&&expectedBaseHash!==currentBaseHash
171
+ )||metadata.some((entry)=>entry.baseHashVerified===false)||observedReasonCodes.includes('base-hash-mismatch');
172
+ const dirtyWorkspace=metadata.some((entry)=>
173
+ entry.dirtyWorkspace===true
174
+ ||entry.workspaceDirty===true
175
+ ||entry.worktreeDirty===true
176
+ ||entry.dirtyWorktree===true
177
+ )||observedReasonCodes.some((reason)=>reason==='dirty-workspace'||reason==='workspace-dirty'||reason==='dirty-worktree');
178
+ const unverifiedSourceHash=sourceHashVerifiedFalse&&!staleByContentHash;
179
+ const reasonCodes=uniqueStrings([
180
+ ...(sourceHashMismatch?['source-hash-mismatch']:[]),
181
+ ...(contentHashMismatch?['content-hash-mismatch']:[]),
182
+ ...(preservationHashMismatch?['source-preservation-hash-mismatch']:[]),
183
+ ...(staleByContentHash&&sourceHashVerifiedFalse?['source-hash-unverified']:[]),
184
+ ...(staleByBaseHash?['base-hash-mismatch']:[]),
185
+ ...(dirtyWorkspace?['dirty-workspace']:[]),
186
+ ...(unverifiedSourceHash?['source-hash-unverified']:[])
187
+ ]);
188
+ return {
189
+ ...(sourcePath?{sourcePath}:{}),
190
+ ...(sourceHash?{sourceHash}:{}),
191
+ ...(declaredSourceHash?{declaredSourceHash}:{}),
192
+ ...(contentHash?{contentHash}:{}),
193
+ ...(declaredContentHash?{declaredContentHash}:{}),
194
+ ...(baseHash?{baseHash}:{}),
195
+ ...(expectedBaseHash?{expectedBaseHash}:{}),
196
+ ...(currentBaseHash?{currentBaseHash}:{}),
197
+ staleByContentHash,
198
+ staleByBaseHash,
199
+ dirtyWorkspace,
200
+ unverifiedSourceHash,
201
+ reasonCodes
202
+ };
203
+ }
204
+
205
+ function admissionSourcePreservationWithStaleness(sourcePreservation,sourceStaleness){
206
+ const stale=sourceStaleness.stale;
207
+ const quality=stale>0?'stale':sourcePreservation.quality==='stale'
208
+ ? sourcePreservation.missing>0?'missing':sourcePreservation.lossy>0||sourcePreservation.truncated?'lossy':sourcePreservation.total===0?'empty':'exact'
209
+ : sourcePreservation.quality;
210
+ return {
211
+ ...sourcePreservation,
212
+ quality,
213
+ stale,
214
+ staleSourcePaths:sourceStaleness.staleSourcePaths,
215
+ contentHashStale:sourceStaleness.contentHashStale,
216
+ baseHashStale:sourceStaleness.baseHashStale,
217
+ dirtyWorkspace:sourceStaleness.dirtyWorkspace,
218
+ contentHashStaleSourcePaths:sourceStaleness.contentHashStaleSourcePaths,
219
+ baseHashStaleSourcePaths:sourceStaleness.baseHashStaleSourcePaths,
220
+ dirtyWorkspaceSourcePaths:sourceStaleness.dirtyWorkspaceSourcePaths
221
+ };
222
+ }
223
+
224
+ function sourcePaths(records){
225
+ return uniqueStrings(records.map((record)=>record.sourcePath).filter(Boolean));
226
+ }
227
+
228
+ function firstMetadataString(metadata,...keys){
229
+ for(const key of keys){
230
+ const value=firstString(...metadata.map((entry)=>entry?.[key]));
231
+ if(value) return value;
232
+ }
233
+ return undefined;
234
+ }
235
+
236
+ function firstString(...values){
237
+ for(const value of values){
238
+ if(value!==undefined&&value!==null&&String(value)) return String(value);
239
+ }
240
+ return undefined;
241
+ }
@@ -29,6 +29,7 @@ export function createSemanticImportSidecar(importResult, options = {}) {
29
29
  const patchHints = ownershipRegions.map((region) => semanticPatchHintForRegion(region, readiness, options));
30
30
  const quality = createSemanticImportSidecarQuality({
31
31
  expected: options.expected === true || options.semanticImportExpected === true,
32
+ expectedEmpty: options.expectedEmpty === true || options.semanticImportExpectedEmpty === true,
32
33
  importEntries,
33
34
  symbols,
34
35
  ownershipRegions,
@@ -130,8 +131,12 @@ export function createSemanticImportSidecar(importResult, options = {}) {
130
131
  patchHints: patchHints.length,
131
132
  evidenceWarnings: quality.emptyEvidenceWarnings.length,
132
133
  semanticImportExpected: quality.expected,
134
+ semanticImportExpectedEmpty: quality.expectedEmpty,
133
135
  semanticImportExpectedSatisfied: quality.expectedSatisfied,
134
136
  semanticImportExpectedMissingReasonCodes: quality.expectedMissingReasonCodes,
137
+ semanticImportRecordClassification: quality.record.classification,
138
+ semanticImportRecordReasonCode: quality.record.reasonCode,
139
+ semanticImportRecordAction: quality.record.action,
135
140
  readiness,
136
141
  emptySemanticIndex: symbols.length === 0
137
142
  },