@shapeshift-labs/frontier-lang-compiler 0.2.21 → 0.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -76,6 +76,13 @@ const semanticMergeReadinessRank = Object.freeze({
76
76
  blocked: 3
77
77
  });
78
78
 
79
+ const nativeFeatureEvidenceRiskRank = Object.freeze({
80
+ low: 0,
81
+ medium: 1,
82
+ high: 2,
83
+ critical: 3
84
+ });
85
+
79
86
  export const NativeImportRoundtripReadinessStatuses = Object.freeze([
80
87
  'exact',
81
88
  'preserved-source',
@@ -136,6 +143,117 @@ export const NativeImportReadinessBySeverity = Object.freeze({
136
143
  error: 'blocked'
137
144
  });
138
145
 
146
+ export const NativeImportFeatureEvidencePolicies = Object.freeze({
147
+ macroExpansion: nativeImportFeatureEvidencePolicy('macroExpansion', {
148
+ category: 'macroExpansion',
149
+ risk: 'high',
150
+ minimumReadiness: 'needs-review',
151
+ requiredEvidenceKeys: ['macroDefinitionsHash', 'expandedSourceHash'],
152
+ recommendedEvidenceKeys: ['expansionMapId', 'sourceMapId', 'macroCallSites'],
153
+ notes: ['Macro-expanded code must retain a link from generated output back to macro call sites before semantic merges can be trusted.']
154
+ }),
155
+ macroHygiene: nativeImportFeatureEvidencePolicy('macroHygiene', {
156
+ category: 'macroExpansion',
157
+ risk: 'critical',
158
+ minimumReadiness: 'needs-review',
159
+ missingEvidenceReadiness: 'blocked',
160
+ requiredEvidenceKeys: ['hygieneContextHash', 'bindingMapId'],
161
+ recommendedEvidenceKeys: ['expansionMapId', 'captureSetHash'],
162
+ notes: ['Hygiene-sensitive macros can change binding identity even when emitted text looks equivalent.']
163
+ }),
164
+ preprocessor: nativeImportFeatureEvidencePolicy('preprocessor', {
165
+ category: 'preprocessor',
166
+ risk: 'high',
167
+ minimumReadiness: 'needs-review',
168
+ requiredEvidenceKeys: ['preprocessedOutputHash', 'definesHash'],
169
+ recommendedEvidenceKeys: ['includeGraphHash', 'conditionalBranches', 'sourceMapId'],
170
+ notes: ['Preprocessor imports need the active defines/includes and preprocessed output hash to make replayable claims.']
171
+ }),
172
+ conditionalCompilation: nativeImportFeatureEvidencePolicy('conditionalCompilation', {
173
+ category: 'conditionalCompilation',
174
+ risk: 'high',
175
+ minimumReadiness: 'needs-review',
176
+ requiredEvidenceKeys: ['activeBranches', 'inactiveBranchesHash'],
177
+ recommendedEvidenceKeys: ['compileTarget', 'featureFlags', 'preprocessedOutputHash'],
178
+ notes: ['Conditional branches that were not active still affect portability and conflict review.']
179
+ }),
180
+ metaprogramming: nativeImportFeatureEvidencePolicy('metaprogramming', {
181
+ category: 'metaprogramming',
182
+ risk: 'critical',
183
+ minimumReadiness: 'needs-review',
184
+ missingEvidenceReadiness: 'blocked',
185
+ requiredEvidenceKeys: ['generatedArtifactHash', 'generatorIdentity'],
186
+ recommendedEvidenceKeys: ['generatorInputsHash', 'generatedRanges', 'replayCommand'],
187
+ notes: ['Generated or metaprogrammed declarations need replayable generator identity and input evidence.']
188
+ }),
189
+ reflection: nativeImportFeatureEvidencePolicy('reflection', {
190
+ category: 'reflection',
191
+ risk: 'high',
192
+ minimumReadiness: 'needs-review',
193
+ requiredEvidenceKeys: ['reflectionSurface', 'runtimeContract'],
194
+ recommendedEvidenceKeys: ['observedMembers', 'fixtureIds', 'runtimeVersion'],
195
+ notes: ['Reflection-heavy code needs a declared runtime contract because static AST evidence is incomplete.']
196
+ }),
197
+ dynamicRuntime: nativeImportFeatureEvidencePolicy('dynamicRuntime', {
198
+ category: 'reflection',
199
+ risk: 'high',
200
+ minimumReadiness: 'needs-review',
201
+ requiredEvidenceKeys: ['runtimeContract'],
202
+ recommendedEvidenceKeys: ['fixtureIds', 'observedEffects', 'runtimeVersion'],
203
+ notes: ['Dynamic runtime behavior should stay review-required until fixtures or traces describe the observed contract.']
204
+ }),
205
+ dynamicDispatch: nativeImportFeatureEvidencePolicy('dynamicDispatch', {
206
+ category: 'overloadTypeInference',
207
+ risk: 'medium',
208
+ minimumReadiness: 'needs-review',
209
+ requiredEvidenceKeys: ['dispatchTargets'],
210
+ recommendedEvidenceKeys: ['callGraphId', 'typeEvidenceId', 'fixtureIds'],
211
+ notes: ['Dynamic dispatch needs candidate target evidence before call graph or porting claims are merge-ready.']
212
+ }),
213
+ generatedCode: nativeImportFeatureEvidencePolicy('generatedCode', {
214
+ category: 'generatedCode',
215
+ risk: 'high',
216
+ minimumReadiness: 'needs-review',
217
+ requiredEvidenceKeys: ['generatedArtifactHash', 'generatedRanges'],
218
+ recommendedEvidenceKeys: ['generatorIdentity', 'generatorInputsHash', 'sourceMapId'],
219
+ notes: ['Generated code must preserve generated ranges and artifact hashes so workers can avoid editing derived output blindly.']
220
+ }),
221
+ overloadResolution: nativeImportFeatureEvidencePolicy('overloadResolution', {
222
+ category: 'overloadTypeInference',
223
+ risk: 'medium',
224
+ minimumReadiness: 'needs-review',
225
+ requiredEvidenceKeys: ['resolvedOverloads'],
226
+ recommendedEvidenceKeys: ['typeEvidenceId', 'compilerVersion', 'callSiteSpans'],
227
+ notes: ['Overload-sensitive imports should record compiler/type evidence for each call site.']
228
+ }),
229
+ typeInference: nativeImportFeatureEvidencePolicy('typeInference', {
230
+ category: 'overloadTypeInference',
231
+ risk: 'medium',
232
+ minimumReadiness: 'needs-review',
233
+ requiredEvidenceKeys: ['inferredTypesHash'],
234
+ recommendedEvidenceKeys: ['typeEvidenceId', 'compilerVersion', 'symbolTableHash'],
235
+ notes: ['Inferred types need a stable type-evidence hash before cross-language projection can claim fidelity.']
236
+ }),
237
+ unsupportedSyntax: nativeImportFeatureEvidencePolicy('unsupportedSyntax', {
238
+ category: 'unsupportedSyntax',
239
+ risk: 'high',
240
+ minimumReadiness: 'needs-review',
241
+ missingEvidenceReadiness: 'blocked',
242
+ requiredEvidenceKeys: ['unsupportedSyntaxKind', 'sourceSpan'],
243
+ recommendedEvidenceKeys: ['parserDiagnosticId', 'nativeAstNodeId', 'sourceSnippetHash'],
244
+ notes: ['Unsupported syntax must remain anchored to source spans and parser diagnostics for later adapter work.']
245
+ }),
246
+ unsupportedSemantic: nativeImportFeatureEvidencePolicy('unsupportedSemantic', {
247
+ category: 'unsupportedSyntax',
248
+ risk: 'high',
249
+ minimumReadiness: 'needs-review',
250
+ missingEvidenceReadiness: 'blocked',
251
+ requiredEvidenceKeys: ['unsupportedSemanticKind', 'semanticSymbolId'],
252
+ recommendedEvidenceKeys: ['semanticIndexId', 'sourceMapId', 'reason'],
253
+ notes: ['Unsupported semantics should name the affected symbol so merge tools can isolate the unsafe region.']
254
+ })
255
+ });
256
+
139
257
  export const NativeImportRegionTaxonomyKinds = Object.freeze([
140
258
  'symbol',
141
259
  'declaration',
@@ -211,6 +329,14 @@ export const NativeImportLanguageProfiles = Object.freeze([
211
329
  nativeImportLanguageProfile('r', { aliases: ['R'], extensions: ['.r', '.R'], parserAdapters: ['r-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] })
212
330
  ]);
213
331
 
332
+ export const ExternalSemanticIndexFormats = Object.freeze([
333
+ 'frontier-semantic-index',
334
+ 'scip',
335
+ 'lsif',
336
+ 'lsp',
337
+ 'semanticdb'
338
+ ]);
339
+
214
340
  export function normalizeCompileTarget(target) {
215
341
  const normalized = String(target ?? 'typescript').toLowerCase();
216
342
  const canonical = canonicalTargets[normalized] ?? normalized;
@@ -426,6 +552,1216 @@ export function compileNativeSource(input, options = {}) {
426
552
  };
427
553
  }
428
554
 
555
+ export function importExternalSemanticIndex(input) {
556
+ const payload = input?.payload ?? input?.semanticIndex ?? input;
557
+ if (!payload || typeof payload !== 'object') {
558
+ throw new Error('importExternalSemanticIndex requires a payload object');
559
+ }
560
+ const format = normalizeExternalSemanticIndexFormat(input?.format ?? inferExternalSemanticIndexFormat(payload));
561
+ const idPart = idFragment(input?.id ?? input?.sourcePath ?? input?.projectRoot ?? format);
562
+ const context = {
563
+ format,
564
+ idPart,
565
+ language: normalizeExternalSemanticLanguage(input?.language ?? payload.language ?? payload.languageId),
566
+ sourcePath: input?.sourcePath ?? payload.sourcePath ?? payload.uri ?? payload.path,
567
+ sourceHash: input?.sourceHash ?? payload.sourceHash ?? payload.md5,
568
+ projectRoot: input?.projectRoot ?? payload.projectRoot ?? payload.project_root ?? payload.metadata?.projectRoot ?? payload.metadata?.project_root,
569
+ parser: input?.parser ?? `${format}.external-semantic-index`,
570
+ metadata: input?.metadata ?? {}
571
+ };
572
+ const normalized = normalizeExternalSemanticIndexPayload(payload, context);
573
+ const evidence = attachNativeImportLossSummary(
574
+ uniqueByEvidenceId([...(normalized.evidence ?? []), ...(input?.evidence ?? [])]),
575
+ summarizeNativeImportLosses(normalized.losses ?? [], {
576
+ evidence: [...(normalized.evidence ?? []), ...(input?.evidence ?? [])],
577
+ parser: context.parser,
578
+ scanKind: 'external-semantic-index',
579
+ semanticStatus: normalized.semanticStatus
580
+ })
581
+ );
582
+ const losses = normalizeNativeLossRecords(normalized.losses ?? []);
583
+ const semanticIndex = createSemanticIndexRecord({
584
+ id: input?.semanticIndexId ?? normalized.semanticIndexId ?? `index_${idPart}_${idFragment(format)}`,
585
+ repository: normalized.repository,
586
+ documents: normalized.documents,
587
+ symbols: normalized.symbols,
588
+ occurrences: normalized.occurrences,
589
+ relations: normalized.relations,
590
+ facts: normalized.facts,
591
+ evidence,
592
+ metadata: {
593
+ format,
594
+ parser: context.parser,
595
+ source: 'external-semantic-index',
596
+ projectRoot: context.projectRoot,
597
+ semanticStatus: normalized.semanticStatus,
598
+ ...normalized.metadata,
599
+ ...context.metadata
600
+ }
601
+ });
602
+ const sourceMapMappings = externalSemanticSourceMapMappings(semanticIndex, {
603
+ evidence,
604
+ losses,
605
+ sourcePath: context.sourcePath,
606
+ sourceHash: context.sourceHash
607
+ });
608
+ const sourceMaps = sourceMapMappings.length
609
+ ? [createSourceMapRecord({
610
+ id: input?.sourceMapId ?? `source_map_${idPart}_${idFragment(format)}`,
611
+ sourcePath: context.sourcePath,
612
+ sourceHash: context.sourceHash,
613
+ semanticIndexId: semanticIndex.id,
614
+ mappings: sourceMapMappings,
615
+ evidence,
616
+ metadata: {
617
+ format,
618
+ source: 'external-semantic-index',
619
+ projectRoot: context.projectRoot
620
+ }
621
+ })]
622
+ : [];
623
+ const document = createDocument({
624
+ id: input?.documentId ?? `document_${idPart}_${idFragment(format)}`,
625
+ name: input?.documentName ?? context.sourcePath ?? `${format} semantic index`,
626
+ nodes: [],
627
+ rootIds: [],
628
+ metadata: {
629
+ sourceLanguage: context.language,
630
+ sourcePath: context.sourcePath,
631
+ sourceHash: context.sourceHash,
632
+ semanticStatus: normalized.semanticStatus,
633
+ externalSemanticIndexFormat: format
634
+ }
635
+ });
636
+ const universalAst = createUniversalAstEnvelope({
637
+ id: input?.universalAstId ?? `universal_ast_${idPart}_${idFragment(format)}`,
638
+ document,
639
+ nativeSources: [],
640
+ semanticIndex,
641
+ sourceMaps,
642
+ losses,
643
+ evidence,
644
+ metadata: {
645
+ sourceLanguage: context.language,
646
+ sourcePath: context.sourcePath,
647
+ sourceHash: context.sourceHash,
648
+ projectRoot: context.projectRoot,
649
+ externalSemanticIndexFormat: format,
650
+ semanticStatus: normalized.semanticStatus,
651
+ ...input?.universalAstMetadata
652
+ }
653
+ });
654
+ const readiness = classifyNativeImportReadiness(losses, {
655
+ evidence,
656
+ parser: context.parser,
657
+ scanKind: 'external-semantic-index',
658
+ semanticStatus: normalized.semanticStatus
659
+ });
660
+ return {
661
+ kind: 'frontier.lang.externalSemanticIndexImport',
662
+ version: 1,
663
+ id: input?.id ?? `external_semantic_index_${idPart}_${idFragment(format)}`,
664
+ format,
665
+ language: context.language,
666
+ sourcePath: context.sourcePath,
667
+ projectRoot: context.projectRoot,
668
+ semanticIndex,
669
+ universalAst,
670
+ sourceMaps,
671
+ losses,
672
+ evidence,
673
+ readiness,
674
+ summary: {
675
+ documents: semanticIndex.documents.length,
676
+ symbols: semanticIndex.symbols.length,
677
+ occurrences: semanticIndex.occurrences.length,
678
+ relations: semanticIndex.relations.length,
679
+ facts: semanticIndex.facts.length,
680
+ sourceMapMappings: sourceMaps.reduce((sum, sourceMap) => sum + (sourceMap.mappings?.length ?? 0), 0),
681
+ losses: losses.length,
682
+ readiness: readiness.readiness
683
+ },
684
+ metadata: {
685
+ format,
686
+ parser: context.parser,
687
+ semanticStatus: normalized.semanticStatus,
688
+ payloadHash: hashSemanticValue(payload),
689
+ ...normalized.metadata,
690
+ ...context.metadata
691
+ }
692
+ };
693
+ }
694
+
695
+ function normalizeExternalSemanticIndexFormat(format) {
696
+ const normalized = String(format ?? 'frontier-semantic-index').trim().toLowerCase();
697
+ const aliases = {
698
+ frontier: 'frontier-semantic-index',
699
+ 'frontier.semantic-index': 'frontier-semantic-index',
700
+ 'frontier.lang.semanticindex': 'frontier-semantic-index',
701
+ scipindex: 'scip',
702
+ 'sourcegraph-scip': 'scip',
703
+ lsp: 'lsp',
704
+ 'language-server-protocol': 'lsp',
705
+ semanticdb: 'semanticdb',
706
+ 'scala-semanticdb': 'semanticdb'
707
+ };
708
+ return aliases[normalized] ?? normalized;
709
+ }
710
+
711
+ function inferExternalSemanticIndexFormat(payload) {
712
+ if (payload.kind === 'frontier.lang.semanticIndex') return 'frontier-semantic-index';
713
+ if (Array.isArray(payload) && payload.some((entry) => entry?.type === 'vertex' || entry?.type === 'edge')) return 'lsif';
714
+ if (Array.isArray(payload.vertices) || Array.isArray(payload.edges)) return 'lsif';
715
+ if (Array.isArray(payload.documents) && payload.documents.some((document) => document?.relative_path ?? document?.relativePath)) return 'scip';
716
+ if (payload.metadata?.project_root || payload.metadata?.projectRoot || payload.external_symbols || payload.externalSymbols) return 'scip';
717
+ if (Array.isArray(payload.documents) && payload.documents.some((document) => document?.symbols && document?.occurrences && (document?.uri || document?.md5 || document?.schema))) return 'semanticdb';
718
+ if (Array.isArray(payload.documentSymbols) || Array.isArray(payload.symbols) || payload.semanticTokens || payload.location || payload.range) return 'lsp';
719
+ return 'frontier-semantic-index';
720
+ }
721
+
722
+ function normalizeExternalSemanticIndexPayload(payload, context) {
723
+ if (context.format === 'frontier-semantic-index') return normalizeFrontierSemanticIndexPayload(payload, context);
724
+ if (context.format === 'scip') return normalizeScipPayload(payload, context);
725
+ if (context.format === 'lsif') return normalizeLsifPayload(payload, context);
726
+ if (context.format === 'lsp') return normalizeLspPayload(payload, context);
727
+ if (context.format === 'semanticdb') return normalizeSemanticDbPayload(payload, context);
728
+ return normalizeGenericExternalSemanticIndexPayload(payload, context);
729
+ }
730
+
731
+ function externalSemanticBase(context, metadata = {}) {
732
+ return {
733
+ repository: context.projectRoot ? { root: context.projectRoot } : undefined,
734
+ documents: [],
735
+ symbols: [],
736
+ occurrences: [],
737
+ relations: [],
738
+ facts: [],
739
+ evidence: [externalSemanticEvidence(context, 'passed', `Imported ${context.format} semantic index payload.`)],
740
+ losses: [externalSemanticCoverageLoss(context)],
741
+ semanticStatus: 'external-semantic-index',
742
+ metadata
743
+ };
744
+ }
745
+
746
+ function normalizeFrontierSemanticIndexPayload(payload, context) {
747
+ const result = externalSemanticBase(context, { sourceFormat: payload.kind ?? 'frontier.lang.semanticIndex' });
748
+ result.repository = payload.repository ?? result.repository;
749
+ result.documents = normalizeArray(payload.documents).map((document, index) => externalDocument(document, context, index));
750
+ result.symbols = normalizeArray(payload.symbols).map((symbol, index) => externalSymbol(symbol, context, index));
751
+ result.occurrences = normalizeArray(payload.occurrences).map((occurrence, index) => externalOccurrence(occurrence, context, index));
752
+ result.relations = normalizeArray(payload.relations).map((relation, index) => externalRelation(relation, context, index));
753
+ result.facts = normalizeArray(payload.facts).map((fact, index) => externalFact(fact, context, index));
754
+ result.evidence = uniqueByEvidenceId([...(payload.evidence ?? []), ...result.evidence]);
755
+ if (payload.metadata) result.metadata = { ...result.metadata, ...payload.metadata };
756
+ return withExternalEmptyLoss(result, context);
757
+ }
758
+
759
+ function normalizeScipPayload(payload, context) {
760
+ const result = externalSemanticBase(context, { sourceFormat: 'scip' });
761
+ const metadata = payload.metadata ?? {};
762
+ const projectRoot = context.projectRoot ?? metadata.project_root ?? metadata.projectRoot;
763
+ result.repository = projectRoot ? { root: projectRoot } : undefined;
764
+ const documents = normalizeArray(payload.documents);
765
+ for (const [documentIndex, document] of documents.entries()) {
766
+ const path = document.relative_path ?? document.relativePath ?? document.path ?? context.sourcePath ?? `scip-document-${documentIndex + 1}`;
767
+ const language = normalizeExternalSemanticLanguage(document.language ?? context.language);
768
+ const documentId = document.id ?? `doc_${idFragment(path)}`;
769
+ result.documents.push({
770
+ id: documentId,
771
+ path,
772
+ language,
773
+ sourceHash: document.sourceHash ?? document.md5 ?? context.sourceHash,
774
+ metadata: {
775
+ format: 'scip',
776
+ projectRoot,
777
+ textDocumentEncoding: metadata.text_document_encoding ?? metadata.textDocumentEncoding,
778
+ documentIndex
779
+ }
780
+ });
781
+ const documentSymbols = new Map();
782
+ for (const symbolInfo of [...normalizeArray(document.symbols), ...normalizeArray(payload.external_symbols ?? payload.externalSymbols)]) {
783
+ const symbolId = scipSymbolId(symbolInfo.symbol, context, normalizeArray(document.symbols).includes(symbolInfo) ? documentId : undefined);
784
+ if (!symbolId || documentSymbols.has(symbolId)) continue;
785
+ documentSymbols.set(symbolId, true);
786
+ result.symbols.push({
787
+ id: symbolId,
788
+ scheme: 'scip',
789
+ name: symbolInfo.display_name ?? symbolInfo.displayName ?? nameFromExternalSymbol(symbolInfo.symbol),
790
+ kind: normalizeExternalSymbolKind(symbolInfo.kind),
791
+ language,
792
+ signatureHash: hashSemanticValue([symbolInfo.symbol, symbolInfo.signature_documentation ?? symbolInfo.signatureDocumentation]),
793
+ metadata: {
794
+ format: 'scip',
795
+ rawSymbol: symbolInfo.symbol,
796
+ documentation: symbolInfo.documentation,
797
+ enclosingSymbol: symbolInfo.enclosing_symbol ?? symbolInfo.enclosingSymbol,
798
+ external: !normalizeArray(document.symbols).includes(symbolInfo)
799
+ }
800
+ });
801
+ result.facts.push(...scipSymbolFacts(symbolInfo, symbolId));
802
+ result.relations.push(...scipRelationshipRelations(symbolInfo, symbolId, context));
803
+ }
804
+ for (const [occurrenceIndex, occurrence] of normalizeArray(document.occurrences).entries()) {
805
+ const symbolId = scipSymbolId(occurrence.symbol, context, documentId);
806
+ if (!symbolId) continue;
807
+ const role = scipOccurrenceRole(occurrence.symbol_roles ?? occurrence.symbolRoles);
808
+ if (!documentSymbols.has(symbolId)) {
809
+ documentSymbols.set(symbolId, true);
810
+ result.symbols.push({
811
+ id: symbolId,
812
+ scheme: 'scip',
813
+ name: nameFromExternalSymbol(occurrence.symbol),
814
+ kind: scipSyntaxKind(occurrence.syntax_kind ?? occurrence.syntaxKind),
815
+ language,
816
+ metadata: { format: 'scip', rawSymbol: occurrence.symbol, inferredFromOccurrence: true }
817
+ });
818
+ }
819
+ result.occurrences.push({
820
+ id: occurrence.id ?? `occ_${idFragment(documentId)}_${occurrenceIndex + 1}`,
821
+ documentId,
822
+ symbolId,
823
+ role,
824
+ span: spanFromScipOccurrence(occurrence, path, context.sourceHash),
825
+ metadata: {
826
+ format: 'scip',
827
+ symbolRoles: occurrence.symbol_roles ?? occurrence.symbolRoles,
828
+ roleSet: scipOccurrenceRoleSet(occurrence.symbol_roles ?? occurrence.symbolRoles),
829
+ syntaxKind: occurrence.syntax_kind ?? occurrence.syntaxKind,
830
+ overrideDocumentation: occurrence.override_documentation ?? occurrence.overrideDocumentation
831
+ }
832
+ });
833
+ for (const diagnostic of normalizeArray(occurrence.diagnostics)) {
834
+ const scopedDiagnostic = { ...diagnostic, range: diagnostic.range ?? occurrence.range };
835
+ result.facts.push(externalDiagnosticFact(scopedDiagnostic, context, documentId, path, result.facts.length));
836
+ result.losses.push(externalDiagnosticLoss(scopedDiagnostic, context, path));
837
+ }
838
+ if (scipOccurrenceRoleSet(occurrence.symbol_roles ?? occurrence.symbolRoles).includes('generated')) {
839
+ result.losses.push({
840
+ id: `loss_${idFragment(documentId)}_${occurrenceIndex + 1}_generated_scip_occurrence`,
841
+ severity: 'warning',
842
+ phase: 'index',
843
+ sourceFormat: 'scip',
844
+ kind: 'generatedCode',
845
+ message: 'SCIP occurrence is marked generated; merge admission should review generated/source ownership before applying patches.',
846
+ span: spanFromScipOccurrence(occurrence, path, context.sourceHash),
847
+ semanticSymbolId: symbolId,
848
+ metadata: { format: 'scip', symbolRoles: occurrence.symbol_roles ?? occurrence.symbolRoles }
849
+ });
850
+ }
851
+ }
852
+ }
853
+ return withExternalEmptyLoss(result, context);
854
+ }
855
+
856
+ function normalizeLsifPayload(payload, context) {
857
+ const result = externalSemanticBase(context, { sourceFormat: 'lsif' });
858
+ const records = Array.isArray(payload) ? payload : [...normalizeArray(payload.vertices), ...normalizeArray(payload.edges)];
859
+ const vertices = new Map(records.filter((record) => record?.type === 'vertex').map((record) => [record.id, record]));
860
+ const edges = records.filter((record) => record?.type === 'edge');
861
+ const documentByVertex = new Map();
862
+ const documentIdByRange = new Map();
863
+ const resultSetByRange = new Map();
864
+ const monikerByOut = new Map();
865
+ const definitionRangeIds = new Set();
866
+ for (const vertex of vertices.values()) {
867
+ if (vertex.label === 'document') {
868
+ const path = uriToPath(vertex.uri) ?? vertex.uri ?? context.sourcePath ?? `lsif-document-${result.documents.length + 1}`;
869
+ const documentId = `doc_${idFragment(vertex.id ?? path)}`;
870
+ documentByVertex.set(vertex.id, { id: documentId, path, language: normalizeExternalSemanticLanguage(vertex.languageId ?? context.language) });
871
+ result.documents.push({
872
+ id: documentId,
873
+ path,
874
+ language: normalizeExternalSemanticLanguage(vertex.languageId ?? context.language),
875
+ metadata: { format: 'lsif', vertexId: vertex.id, uri: vertex.uri }
876
+ });
877
+ }
878
+ if (vertex.label === 'moniker') monikerByOut.set(vertex.id, vertex);
879
+ }
880
+ for (const edge of edges) {
881
+ if (edge.label === 'next') resultSetByRange.set(edge.outV, edge.inV);
882
+ if (edge.label === 'moniker') monikerByOut.set(edge.outV, vertices.get(edge.inV) ?? edge);
883
+ if (edge.label === 'contains') {
884
+ const document = documentByVertex.get(edge.outV);
885
+ if (document) {
886
+ for (const rangeId of normalizeArray(edge.inVs ?? edge.inV)) {
887
+ documentIdByRange.set(rangeId, document.id);
888
+ }
889
+ }
890
+ }
891
+ if (edge.label === 'item' && (edge.property === 'definitions' || edge.property === 'declarations')) {
892
+ for (const rangeId of normalizeArray(edge.inVs ?? edge.inV)) definitionRangeIds.add(rangeId);
893
+ }
894
+ }
895
+ const documentIds = result.documents.map((document) => document.id);
896
+ const defaultDocument = result.documents[0] ?? {
897
+ id: `doc_${idFragment(context.sourcePath ?? 'lsif')}`,
898
+ path: context.sourcePath ?? 'lsif:memory',
899
+ language: context.language
900
+ };
901
+ if (!result.documents.length) result.documents.push(defaultDocument);
902
+ for (const [vertexId, vertex] of vertices.entries()) {
903
+ if (vertex.label !== 'range') continue;
904
+ const resultSetId = resultSetByRange.get(vertexId);
905
+ const moniker = monikerByOut.get(resultSetId) ?? monikerByOut.get(vertexId);
906
+ const symbolId = moniker?.identifier
907
+ ? `symbol:lsif:${idFragment(moniker.scheme ?? moniker.kind ?? 'moniker')}:${idFragment(moniker.identifier)}`
908
+ : `symbol:lsif:${idFragment(resultSetId ?? vertexId)}`;
909
+ const documentId = documentIdByRange.get(vertexId) ?? documentIds[0] ?? defaultDocument.id;
910
+ const owningDocument = result.documents.find((document) => document.id === documentId) ?? defaultDocument;
911
+ const span = spanFromLspRange(vertex, owningDocument.path, context.sourceHash, 0);
912
+ if (!result.symbols.some((symbol) => symbol.id === symbolId)) {
913
+ result.symbols.push({
914
+ id: symbolId,
915
+ scheme: 'lsif',
916
+ name: moniker?.identifier ?? `range:${vertexId}`,
917
+ kind: moniker?.kind ?? 'symbol',
918
+ language: owningDocument.language,
919
+ definitionSpan: definitionRangeIds.has(vertexId) ? span : undefined,
920
+ metadata: { format: 'lsif', resultSetId, moniker }
921
+ });
922
+ }
923
+ result.occurrences.push({
924
+ id: `occ_${idFragment(vertexId)}`,
925
+ documentId,
926
+ symbolId,
927
+ role: definitionRangeIds.has(vertexId) ? 'definition' : 'reference',
928
+ span,
929
+ metadata: { format: 'lsif', vertexId, resultSetId }
930
+ });
931
+ }
932
+ for (const edge of edges) {
933
+ if (edge.label === 'textDocument/definition' || edge.label === 'textDocument/references' || edge.label === 'textDocument/declaration') {
934
+ result.relations.push({
935
+ id: `rel_${idFragment(edge.id ?? `${edge.outV}_${edge.inV}_${edge.label}`)}`,
936
+ sourceId: `lsif:${edge.outV}`,
937
+ predicate: edge.label,
938
+ targetId: `lsif:${edge.inV}`,
939
+ metadata: { format: 'lsif', edge }
940
+ });
941
+ }
942
+ }
943
+ return withExternalEmptyLoss(result, context);
944
+ }
945
+
946
+ function normalizeLspPayload(payload, context) {
947
+ const result = externalSemanticBase(context, { sourceFormat: 'lsp' });
948
+ const documents = normalizeLspDocuments(payload, context);
949
+ for (const [documentIndex, document] of documents.entries()) {
950
+ const sourcePath = uriToPath(document.uri) ?? document.sourcePath ?? document.path ?? context.sourcePath ?? `lsp-document-${documentIndex + 1}`;
951
+ const language = normalizeExternalSemanticLanguage(document.languageId ?? document.language ?? context.language);
952
+ const documentId = document.id ?? `doc_${idFragment(sourcePath)}`;
953
+ result.documents.push({
954
+ id: documentId,
955
+ path: sourcePath,
956
+ language,
957
+ sourceHash: document.sourceHash ?? context.sourceHash,
958
+ metadata: { format: 'lsp', uri: document.uri, documentIndex }
959
+ });
960
+ const symbols = normalizeArray(document.documentSymbols ?? document.symbols ?? payload.documentSymbols ?? payload.symbols);
961
+ for (const symbol of symbols) addLspSymbol(result, symbol, {
962
+ context,
963
+ documentId,
964
+ sourcePath,
965
+ language,
966
+ parentName: symbol.containerName
967
+ });
968
+ const semanticTokens = document.semanticTokens ?? payload.semanticTokens;
969
+ if (semanticTokens) addLspSemanticTokens(result, semanticTokens, { context, documentId, sourcePath, language });
970
+ for (const diagnostic of normalizeArray(document.diagnostics ?? payload.diagnostics)) {
971
+ result.facts.push(externalDiagnosticFact(diagnostic, context, documentId, sourcePath, result.facts.length));
972
+ result.losses.push(externalDiagnosticLoss(diagnostic, context, sourcePath));
973
+ }
974
+ }
975
+ return withExternalEmptyLoss(result, context);
976
+ }
977
+
978
+ function normalizeSemanticDbPayload(payload, context) {
979
+ const result = externalSemanticBase(context, { sourceFormat: 'semanticdb' });
980
+ const documents = normalizeArray(payload.documents ?? payload.textDocuments ?? payload);
981
+ for (const [documentIndex, document] of documents.entries()) {
982
+ const sourcePath = uriToPath(document.uri) ?? document.uri ?? document.path ?? context.sourcePath ?? `semanticdb-document-${documentIndex + 1}`;
983
+ const language = normalizeExternalSemanticLanguage(document.language ?? context.language ?? 'scala');
984
+ const documentId = document.id ?? `doc_${idFragment(sourcePath)}`;
985
+ result.documents.push({
986
+ id: documentId,
987
+ path: sourcePath,
988
+ language,
989
+ sourceHash: document.md5 ?? document.sourceHash ?? context.sourceHash,
990
+ metadata: { format: 'semanticdb', schema: document.schema, documentIndex }
991
+ });
992
+ for (const [symbolIndex, symbolInfo] of normalizeArray(document.symbols).entries()) {
993
+ const symbolId = semanticDbSymbolId(symbolInfo.symbol, context, documentId);
994
+ result.symbols.push({
995
+ id: symbolId,
996
+ scheme: 'semanticdb',
997
+ name: symbolInfo.display_name ?? symbolInfo.displayName ?? nameFromExternalSymbol(symbolInfo.symbol),
998
+ kind: normalizeExternalSymbolKind(symbolInfo.kind),
999
+ language,
1000
+ signatureHash: hashSemanticValue(symbolInfo.signature ?? symbolInfo.signature_documentation ?? symbolInfo.signatureDocumentation ?? symbolInfo),
1001
+ metadata: { format: 'semanticdb', symbolIndex, rawSymbol: symbolInfo.symbol, properties: symbolInfo.properties }
1002
+ });
1003
+ result.facts.push(...semanticDbSymbolFacts(symbolInfo, symbolId));
1004
+ }
1005
+ for (const [occurrenceIndex, occurrence] of normalizeArray(document.occurrences).entries()) {
1006
+ const symbolId = semanticDbSymbolId(occurrence.symbol, context, documentId);
1007
+ if (!result.symbols.some((symbol) => symbol.id === symbolId)) {
1008
+ result.symbols.push({
1009
+ id: symbolId,
1010
+ scheme: 'semanticdb',
1011
+ name: nameFromExternalSymbol(occurrence.symbol),
1012
+ kind: 'symbol',
1013
+ language,
1014
+ metadata: { format: 'semanticdb', inferredFromOccurrence: true, rawSymbol: occurrence.symbol }
1015
+ });
1016
+ }
1017
+ result.occurrences.push({
1018
+ id: occurrence.id ?? `occ_${idFragment(documentId)}_${occurrenceIndex + 1}`,
1019
+ documentId,
1020
+ symbolId,
1021
+ role: semanticDbOccurrenceRole(occurrence.role),
1022
+ span: spanFromSemanticDbRange(occurrence.range, sourcePath, document.md5 ?? context.sourceHash),
1023
+ metadata: { format: 'semanticdb', role: occurrence.role }
1024
+ });
1025
+ }
1026
+ for (const diagnostic of normalizeArray(document.diagnostics)) {
1027
+ result.facts.push(externalDiagnosticFact(diagnostic, context, documentId, sourcePath, result.facts.length));
1028
+ result.losses.push(externalDiagnosticLoss(diagnostic, context, sourcePath));
1029
+ }
1030
+ }
1031
+ return withExternalEmptyLoss(result, context);
1032
+ }
1033
+
1034
+ function normalizeGenericExternalSemanticIndexPayload(payload, context) {
1035
+ const result = externalSemanticBase(context, { sourceFormat: context.format, genericPayload: true });
1036
+ result.losses.push({
1037
+ id: `loss_${context.idPart}_${idFragment(context.format)}_unsupported_payload`,
1038
+ severity: 'warning',
1039
+ phase: 'index',
1040
+ sourceFormat: context.format,
1041
+ kind: 'unsupportedSemantic',
1042
+ message: `External semantic index format ${context.format} is not recognized; payload hash is preserved as evidence only.`,
1043
+ metadata: { format: context.format, payloadHash: hashSemanticValue(payload) }
1044
+ });
1045
+ return result;
1046
+ }
1047
+
1048
+ function normalizeArray(value) {
1049
+ if (value === undefined || value === null) return [];
1050
+ return Array.isArray(value) ? value : [value];
1051
+ }
1052
+
1053
+ function normalizeExternalSemanticLanguage(value) {
1054
+ if (value === undefined || value === null || value === '') return undefined;
1055
+ const raw = typeof value === 'number' ? externalLanguageNameByNumber[value] : String(value);
1056
+ return normalizeNativeLanguageId(raw);
1057
+ }
1058
+
1059
+ function externalDocument(document, context, index) {
1060
+ const path = document.path ?? document.uri ?? document.relative_path ?? document.relativePath ?? context.sourcePath ?? `external-document-${index + 1}`;
1061
+ return {
1062
+ id: document.id ?? `doc_${idFragment(path)}`,
1063
+ path: uriToPath(path) ?? path,
1064
+ language: normalizeExternalSemanticLanguage(document.language ?? document.languageId ?? context.language),
1065
+ sourceHash: document.sourceHash ?? document.md5 ?? context.sourceHash,
1066
+ metadata: { format: context.format, ...document.metadata }
1067
+ };
1068
+ }
1069
+
1070
+ function externalSymbol(symbol, context, index) {
1071
+ const id = symbol.id ?? symbol.symbolId ?? symbol.symbol ?? `symbol:${context.format}:${index + 1}`;
1072
+ return {
1073
+ ...symbol,
1074
+ id: String(id),
1075
+ scheme: symbol.scheme ?? context.format,
1076
+ name: symbol.name ?? symbol.display_name ?? symbol.displayName ?? nameFromExternalSymbol(id),
1077
+ kind: normalizeExternalSymbolKind(symbol.kind),
1078
+ language: normalizeExternalSemanticLanguage(symbol.language ?? context.language),
1079
+ definitionSpan: normalizeExternalSpan(symbol.definitionSpan ?? symbol.span, context.sourcePath, context.sourceHash),
1080
+ metadata: { format: context.format, rawSymbol: symbol.symbol, ...symbol.metadata }
1081
+ };
1082
+ }
1083
+
1084
+ function externalOccurrence(occurrence, context, index) {
1085
+ return {
1086
+ ...occurrence,
1087
+ id: occurrence.id ?? `occ_${context.idPart}_${index + 1}`,
1088
+ documentId: occurrence.documentId ?? occurrence.document_id ?? `doc_${idFragment(occurrence.path ?? context.sourcePath ?? context.format)}`,
1089
+ symbolId: occurrence.symbolId ?? occurrence.symbol_id ?? occurrence.symbol ?? `symbol:${context.format}:unknown`,
1090
+ role: normalizeExternalOccurrenceRole(occurrence.role),
1091
+ span: normalizeExternalSpan(occurrence.span ?? occurrence.range, occurrence.path ?? context.sourcePath, context.sourceHash),
1092
+ metadata: { format: context.format, ...occurrence.metadata }
1093
+ };
1094
+ }
1095
+
1096
+ function externalRelation(relation, context, index) {
1097
+ return {
1098
+ ...relation,
1099
+ id: relation.id ?? `rel_${context.idPart}_${index + 1}`,
1100
+ sourceId: relation.sourceId ?? relation.source_id ?? relation.subjectId ?? relation.subject_id ?? `external:${context.format}`,
1101
+ predicate: relation.predicate ?? relation.label ?? relation.kind ?? 'related',
1102
+ targetId: relation.targetId ?? relation.target_id ?? relation.objectId ?? relation.object_id ?? relation.symbol ?? `external:${context.format}`,
1103
+ metadata: { format: context.format, ...relation.metadata }
1104
+ };
1105
+ }
1106
+
1107
+ function externalFact(fact, context, index) {
1108
+ return {
1109
+ ...fact,
1110
+ id: fact.id ?? `fact_${context.idPart}_${index + 1}`,
1111
+ predicate: fact.predicate ?? fact.kind ?? 'externalFact',
1112
+ subjectId: fact.subjectId ?? fact.subject_id ?? fact.symbolId ?? fact.symbol_id ?? `external:${context.format}`,
1113
+ value: fact.value ?? fact.data ?? fact,
1114
+ metadata: { format: context.format, ...fact.metadata }
1115
+ };
1116
+ }
1117
+
1118
+ function externalSemanticEvidence(context, status, summary, metadata = {}) {
1119
+ return {
1120
+ id: `evidence_${context.idPart}_${idFragment(context.format)}_external_semantic_index`,
1121
+ kind: 'import',
1122
+ status,
1123
+ path: context.sourcePath,
1124
+ summary,
1125
+ metadata: {
1126
+ format: context.format,
1127
+ parser: context.parser,
1128
+ projectRoot: context.projectRoot,
1129
+ ...metadata
1130
+ }
1131
+ };
1132
+ }
1133
+
1134
+ function externalSemanticCoverageLoss(context) {
1135
+ return {
1136
+ id: `loss_${context.idPart}_${idFragment(context.format)}_partial_semantic_index`,
1137
+ severity: 'info',
1138
+ phase: 'index',
1139
+ sourceFormat: context.format,
1140
+ kind: 'partialSemanticIndex',
1141
+ message: `${context.format} payload imported symbols, occurrences, and facts as external semantic evidence; full parser AST, comments, trivia, and executable semantics still require a native parser adapter.`,
1142
+ semanticIndexId: context.semanticIndexId,
1143
+ metadata: {
1144
+ format: context.format,
1145
+ parser: context.parser,
1146
+ source: 'external-semantic-index'
1147
+ }
1148
+ };
1149
+ }
1150
+
1151
+ function withExternalEmptyLoss(result, context) {
1152
+ if (!result.documents.length) {
1153
+ result.documents.push({
1154
+ id: `doc_${context.idPart}_${idFragment(context.format)}`,
1155
+ path: context.sourcePath ?? `${context.format}:memory`,
1156
+ language: context.language,
1157
+ sourceHash: context.sourceHash,
1158
+ metadata: { format: context.format, inferred: true }
1159
+ });
1160
+ }
1161
+ if (!result.symbols.length && !result.occurrences.length) {
1162
+ result.losses.push({
1163
+ id: `loss_${context.idPart}_${idFragment(context.format)}_empty_semantic_index`,
1164
+ severity: 'warning',
1165
+ phase: 'index',
1166
+ sourceFormat: context.format,
1167
+ kind: 'partialSemanticIndex',
1168
+ message: `${context.format} payload did not contain symbols or occurrences that Frontier can map.`,
1169
+ metadata: { format: context.format }
1170
+ });
1171
+ }
1172
+ attachExternalOwnership(result, context);
1173
+ result.symbols = uniqueRecordsById(result.symbols);
1174
+ result.occurrences = uniqueRecordsById(result.occurrences);
1175
+ result.relations = uniqueRecordsById(result.relations);
1176
+ result.facts = uniqueRecordsById(result.facts);
1177
+ result.losses = uniqueByLossId(result.losses);
1178
+ result.evidence = uniqueByEvidenceId(result.evidence);
1179
+ return result;
1180
+ }
1181
+
1182
+ function attachExternalOwnership(result, context) {
1183
+ const occurrencesBySymbol = new Map();
1184
+ for (const occurrence of result.occurrences) {
1185
+ if (!occurrencesBySymbol.has(occurrence.symbolId)) occurrencesBySymbol.set(occurrence.symbolId, []);
1186
+ occurrencesBySymbol.get(occurrence.symbolId).push(occurrence);
1187
+ result.relations.push({
1188
+ id: `rel_${idFragment(occurrence.documentId)}_${idFragment(occurrence.id)}_${idFragment(occurrence.role)}`,
1189
+ sourceId: occurrence.documentId,
1190
+ predicate: externalRelationPredicateForOccurrence(occurrence),
1191
+ targetId: occurrence.symbolId,
1192
+ metadata: {
1193
+ format: context.format,
1194
+ source: 'external-semantic-index',
1195
+ occurrenceId: occurrence.id,
1196
+ role: occurrence.role
1197
+ }
1198
+ });
1199
+ }
1200
+ result.symbols = result.symbols.map((symbol) => {
1201
+ const occurrences = occurrencesBySymbol.get(symbol.id) ?? [];
1202
+ const definition = occurrences.find((occurrence) => occurrence.role === 'definition') ?? occurrences[0];
1203
+ const sourceSpan = symbol.definitionSpan ?? definition?.span;
1204
+ const regionKind = semanticRegionKindForSymbol(symbol, undefined, undefined);
1205
+ const key = [
1206
+ 'external',
1207
+ symbol.language ?? context.language ?? 'unknown',
1208
+ sourceSpan?.path ?? context.sourcePath ?? 'memory',
1209
+ regionKind,
1210
+ symbol.name ?? symbol.id
1211
+ ].join('#');
1212
+ const region = {
1213
+ id: `region_${idFragment(key)}`,
1214
+ key,
1215
+ regionKind,
1216
+ granularity: 'symbol',
1217
+ language: symbol.language ?? context.language,
1218
+ documentId: definition?.documentId,
1219
+ sourcePath: sourceSpan?.path ?? context.sourcePath,
1220
+ sourceHash: context.sourceHash,
1221
+ symbolId: symbol.id,
1222
+ symbolName: symbol.name,
1223
+ symbolKind: symbol.kind,
1224
+ sourceSpan,
1225
+ precision: sourceSpan ? 'declaration' : 'unknown',
1226
+ mergePolicy: semanticRegionMergePolicy(regionKind),
1227
+ metadata: {
1228
+ format: context.format,
1229
+ source: 'external-semantic-index'
1230
+ }
1231
+ };
1232
+ result.facts.push({
1233
+ id: `fact_${idFragment(symbol.id)}_ownership_region`,
1234
+ predicate: 'semanticOwnershipRegion',
1235
+ subjectId: symbol.id,
1236
+ value: region
1237
+ }, {
1238
+ id: `fact_${idFragment(symbol.id)}_ownership_region_taxonomy`,
1239
+ predicate: 'semanticOwnershipRegionTaxonomy',
1240
+ subjectId: symbol.id,
1241
+ value: {
1242
+ regionKind: region.regionKind,
1243
+ granularity: region.granularity,
1244
+ key: region.key
1245
+ }
1246
+ });
1247
+ return {
1248
+ ...symbol,
1249
+ definitionSpan: symbol.definitionSpan ?? definition?.span,
1250
+ metadata: {
1251
+ ...symbol.metadata,
1252
+ ownershipRegionId: symbol.metadata?.ownershipRegionId ?? region.id,
1253
+ ownershipRegionKey: symbol.metadata?.ownershipRegionKey ?? region.key,
1254
+ ownershipRegionKind: symbol.metadata?.ownershipRegionKind ?? region.regionKind
1255
+ }
1256
+ };
1257
+ });
1258
+ }
1259
+
1260
+ function externalRelationPredicateForOccurrence(occurrence) {
1261
+ const role = String(occurrence.role ?? '').toLowerCase();
1262
+ if (role === 'definition' || role === 'declaration') return 'defines';
1263
+ if (role === 'import') return 'imports';
1264
+ if (role === 'write') return 'writes';
1265
+ if (role === 'read') return 'reads';
1266
+ return 'references';
1267
+ }
1268
+
1269
+ function externalSemanticSourceMapMappings(semanticIndex, context) {
1270
+ const symbolsById = new Map((semanticIndex.symbols ?? []).map((symbol) => [symbol.id, symbol]));
1271
+ const evidenceIds = (context.evidence ?? []).map((record) => record.id).filter(Boolean);
1272
+ const lossIds = (context.losses ?? []).map((loss) => loss.id).filter(Boolean);
1273
+ return (semanticIndex.occurrences ?? [])
1274
+ .filter((occurrence) => occurrence.span)
1275
+ .map((occurrence, index) => {
1276
+ const symbol = symbolsById.get(occurrence.symbolId);
1277
+ return {
1278
+ id: `map_${idFragment(occurrence.id ?? `${occurrence.symbolId}_${index + 1}`)}`,
1279
+ semanticSymbolId: occurrence.symbolId,
1280
+ semanticOccurrenceId: occurrence.id,
1281
+ sourceSpan: occurrence.span,
1282
+ evidenceIds,
1283
+ lossIds,
1284
+ ownershipRegionId: symbol?.metadata?.ownershipRegionId,
1285
+ ownershipRegionKey: symbol?.metadata?.ownershipRegionKey,
1286
+ ownershipRegionKind: symbol?.metadata?.ownershipRegionKind,
1287
+ precision: occurrence.span ? 'declaration' : 'unknown',
1288
+ metadata: {
1289
+ source: 'external-semantic-index'
1290
+ }
1291
+ };
1292
+ });
1293
+ }
1294
+
1295
+ function scipSymbolId(symbol, context, documentId) {
1296
+ if (!symbol) return undefined;
1297
+ const raw = String(symbol);
1298
+ if (raw.startsWith('symbol:')) return raw;
1299
+ const scope = /^local\b/i.test(raw) ? `${documentId ?? context.idPart}:` : '';
1300
+ return `symbol:scip:${idFragment(scope + raw)}`;
1301
+ }
1302
+
1303
+ function semanticDbSymbolId(symbol, context, documentId) {
1304
+ if (!symbol) return `symbol:semanticdb:${context.idPart}:unknown`;
1305
+ if (String(symbol).startsWith('symbol:')) return String(symbol);
1306
+ const scope = /^local\d+$/i.test(String(symbol)) ? `${documentId}:` : '';
1307
+ return `symbol:semanticdb:${idFragment(scope + symbol)}`;
1308
+ }
1309
+
1310
+ function nameFromExternalSymbol(symbol) {
1311
+ const value = String(symbol ?? 'symbol');
1312
+ const cleaned = value
1313
+ .replace(/^symbol:[^:]+:/, '')
1314
+ .replace(/[`'"]/g, '')
1315
+ .split(/[\/#.:() +]+/)
1316
+ .filter(Boolean)
1317
+ .at(-1);
1318
+ return cleaned || value;
1319
+ }
1320
+
1321
+ const externalSymbolKindByNumber = Object.freeze({
1322
+ 1: 'array',
1323
+ 2: 'assertion',
1324
+ 3: 'associatedType',
1325
+ 4: 'attribute',
1326
+ 7: 'class',
1327
+ 8: 'constant',
1328
+ 9: 'constructor',
1329
+ 11: 'enum',
1330
+ 12: 'enumMember',
1331
+ 13: 'event',
1332
+ 15: 'field',
1333
+ 16: 'file',
1334
+ 17: 'function',
1335
+ 21: 'interface',
1336
+ 25: 'macro',
1337
+ 26: 'method',
1338
+ 28: 'message',
1339
+ 29: 'module',
1340
+ 30: 'namespace',
1341
+ 35: 'package',
1342
+ 37: 'parameter',
1343
+ 41: 'property',
1344
+ 42: 'protocol',
1345
+ 49: 'struct',
1346
+ 53: 'trait',
1347
+ 54: 'type',
1348
+ 55: 'typeAlias',
1349
+ 58: 'typeParameter',
1350
+ 61: 'variable',
1351
+ 66: 'abstractMethod'
1352
+ });
1353
+
1354
+ const lspSymbolKindByNumber = Object.freeze({
1355
+ 1: 'file',
1356
+ 2: 'module',
1357
+ 3: 'namespace',
1358
+ 4: 'package',
1359
+ 5: 'class',
1360
+ 6: 'method',
1361
+ 7: 'property',
1362
+ 8: 'field',
1363
+ 9: 'constructor',
1364
+ 10: 'enum',
1365
+ 11: 'interface',
1366
+ 12: 'function',
1367
+ 13: 'variable',
1368
+ 14: 'constant',
1369
+ 22: 'enumMember',
1370
+ 23: 'struct',
1371
+ 26: 'typeParameter'
1372
+ });
1373
+
1374
+ const externalLanguageNameByNumber = Object.freeze({
1375
+ 1: 'csharp',
1376
+ 2: 'swift',
1377
+ 3: 'dart',
1378
+ 4: 'kotlin',
1379
+ 5: 'scala',
1380
+ 6: 'java',
1381
+ 15: 'python',
1382
+ 16: 'ruby',
1383
+ 17: 'elixir',
1384
+ 18: 'erlang',
1385
+ 19: 'php',
1386
+ 22: 'javascript',
1387
+ 23: 'typescript',
1388
+ 33: 'go',
1389
+ 34: 'c',
1390
+ 35: 'cpp',
1391
+ 38: 'zig',
1392
+ 40: 'rust',
1393
+ 44: 'haskell',
1394
+ 54: 'r',
1395
+ 69: 'sql'
1396
+ });
1397
+
1398
+ function normalizeExternalSymbolKind(kind) {
1399
+ if (kind === undefined || kind === null || kind === '') return 'symbol';
1400
+ if (typeof kind === 'number') return externalSymbolKindByNumber[kind] ?? lspSymbolKindByNumber[kind] ?? `kind${kind}`;
1401
+ return String(kind).replace(/^[A-Z_]+_/, '').replace(/^[A-Z]/, (letter) => letter.toLowerCase());
1402
+ }
1403
+
1404
+ function normalizeLspSymbolKind(kind) {
1405
+ if (typeof kind === 'number') return lspSymbolKindByNumber[kind] ?? `kind${kind}`;
1406
+ return normalizeExternalSymbolKind(kind);
1407
+ }
1408
+
1409
+ function scipSyntaxKind(kind) {
1410
+ const normalized = typeof kind === 'number' ? kind : Number(kind);
1411
+ if (normalized === 15 || normalized === 16) return 'function';
1412
+ if (normalized === 19 || normalized === 20) return 'type';
1413
+ if (normalized === 25 || normalized === 26) return 'module';
1414
+ if (normalized === 9 || normalized === 10 || normalized === 12) return 'variable';
1415
+ return 'symbol';
1416
+ }
1417
+
1418
+ function normalizeExternalOccurrenceRole(role) {
1419
+ const value = String(role ?? 'reference').toLowerCase();
1420
+ if (value.includes('def')) return 'definition';
1421
+ if (value.includes('decl')) return 'declaration';
1422
+ if (value.includes('import')) return 'import';
1423
+ if (value.includes('write')) return 'write';
1424
+ if (value.includes('read')) return 'read';
1425
+ return value === '2' ? 'definition' : value === '1' ? 'reference' : 'reference';
1426
+ }
1427
+
1428
+ function scipOccurrenceRole(value) {
1429
+ const role = Number(value ?? 0);
1430
+ if ((role & 0x1) > 0) return 'definition';
1431
+ if ((role & 0x2) > 0) return 'import';
1432
+ if ((role & 0x4) > 0) return 'write';
1433
+ if ((role & 0x8) > 0) return 'read';
1434
+ return 'reference';
1435
+ }
1436
+
1437
+ function scipOccurrenceRoleSet(value) {
1438
+ const role = Number(value ?? 0);
1439
+ const roles = [];
1440
+ if ((role & 0x1) > 0) roles.push('definition');
1441
+ if ((role & 0x2) > 0) roles.push('import');
1442
+ if ((role & 0x4) > 0) roles.push('write');
1443
+ if ((role & 0x8) > 0) roles.push('read');
1444
+ if ((role & 0x10) > 0) roles.push('generated');
1445
+ if ((role & 0x20) > 0) roles.push('test');
1446
+ if ((role & 0x40) > 0) roles.push('forwardDefinition');
1447
+ return roles.length ? roles : ['reference'];
1448
+ }
1449
+
1450
+ function semanticDbOccurrenceRole(value) {
1451
+ const role = String(value ?? 'reference').toLowerCase();
1452
+ if (role === '2' || role.includes('definition')) return 'definition';
1453
+ return 'reference';
1454
+ }
1455
+
1456
+ function scipRelationshipRelations(symbolInfo, symbolId, context) {
1457
+ return normalizeArray(symbolInfo.relationships).flatMap((relationship, index) => {
1458
+ const targetId = scipSymbolId(relationship.symbol, context);
1459
+ if (!targetId) return [];
1460
+ const predicates = [];
1461
+ if (relationship.is_reference ?? relationship.isReference) predicates.push('references');
1462
+ if (relationship.is_implementation ?? relationship.isImplementation) predicates.push('implements');
1463
+ if (relationship.is_type_definition ?? relationship.isTypeDefinition) predicates.push('typeDefinition');
1464
+ if (relationship.is_definition ?? relationship.isDefinition) predicates.push('definitionOf');
1465
+ return (predicates.length ? predicates : ['related']).map((predicate) => ({
1466
+ id: `rel_${idFragment(symbolId)}_${idFragment(targetId)}_${idFragment(predicate)}_${index + 1}`,
1467
+ sourceId: symbolId,
1468
+ predicate,
1469
+ targetId,
1470
+ metadata: { format: 'scip', relationship }
1471
+ }));
1472
+ });
1473
+ }
1474
+
1475
+ function scipSymbolFacts(symbolInfo, symbolId) {
1476
+ const facts = [];
1477
+ if (symbolInfo.documentation) {
1478
+ facts.push({
1479
+ id: `fact_${idFragment(symbolId)}_documentation`,
1480
+ predicate: 'documentation',
1481
+ subjectId: symbolId,
1482
+ value: normalizeArray(symbolInfo.documentation)
1483
+ });
1484
+ }
1485
+ const signature = symbolInfo.signature_documentation ?? symbolInfo.signatureDocumentation;
1486
+ if (signature) {
1487
+ facts.push({
1488
+ id: `fact_${idFragment(symbolId)}_signature`,
1489
+ predicate: 'signature',
1490
+ subjectId: symbolId,
1491
+ value: signature
1492
+ });
1493
+ }
1494
+ for (const [index, relationship] of normalizeArray(symbolInfo.relationships).entries()) {
1495
+ facts.push({
1496
+ id: `fact_${idFragment(symbolId)}_relationship_${index + 1}`,
1497
+ predicate: 'relationship',
1498
+ subjectId: symbolId,
1499
+ value: relationship
1500
+ });
1501
+ }
1502
+ return facts;
1503
+ }
1504
+
1505
+ function semanticDbSymbolFacts(symbolInfo, symbolId) {
1506
+ const facts = [];
1507
+ for (const key of ['signature', 'properties', 'annotations', 'access', 'language']) {
1508
+ if (symbolInfo[key] !== undefined) {
1509
+ facts.push({
1510
+ id: `fact_${idFragment(symbolId)}_${idFragment(key)}`,
1511
+ predicate: key,
1512
+ subjectId: symbolId,
1513
+ value: symbolInfo[key]
1514
+ });
1515
+ }
1516
+ }
1517
+ return facts;
1518
+ }
1519
+
1520
+ function normalizeLspDocuments(payload, context) {
1521
+ if (Array.isArray(payload.documents)) return payload.documents;
1522
+ if (payload.textDocument || payload.uri || payload.documentSymbols || payload.symbols || payload.semanticTokens || payload.diagnostics) {
1523
+ return [{
1524
+ ...payload,
1525
+ uri: payload.uri ?? payload.textDocument?.uri,
1526
+ languageId: payload.languageId ?? payload.language ?? context.language,
1527
+ documentSymbols: payload.documentSymbols,
1528
+ symbols: payload.symbols,
1529
+ semanticTokens: payload.semanticTokens,
1530
+ diagnostics: payload.diagnostics
1531
+ }];
1532
+ }
1533
+ return [{ uri: context.sourcePath, languageId: context.language }];
1534
+ }
1535
+
1536
+ function addLspSymbol(result, symbol, input) {
1537
+ const location = symbol.location ?? {};
1538
+ const range = symbol.range ?? location.range ?? symbol.selectionRange;
1539
+ const sourcePath = uriToPath(location.uri ?? symbol.uri) ?? input.sourcePath;
1540
+ const symbolName = symbol.name ?? symbol.containerName ?? `symbol_${result.symbols.length + 1}`;
1541
+ const symbolId = symbol.id ?? `symbol:lsp:${idFragment(input.language ?? 'unknown')}:${idFragment([input.parentName, symbolName].filter(Boolean).join('.'))}`;
1542
+ const ownershipSpan = spanFromLspRange(range, sourcePath, input.context.sourceHash, 0);
1543
+ const selectionSpan = spanFromLspRange(symbol.selectionRange ?? range, sourcePath, input.context.sourceHash, 0);
1544
+ if (!result.symbols.some((entry) => entry.id === symbolId)) {
1545
+ result.symbols.push({
1546
+ id: symbolId,
1547
+ scheme: 'lsp',
1548
+ name: symbolName,
1549
+ kind: normalizeLspSymbolKind(symbol.kind),
1550
+ language: input.language,
1551
+ definitionSpan: ownershipSpan,
1552
+ signatureHash: hashSemanticValue([symbolName, symbol.kind, symbol.detail]),
1553
+ metadata: {
1554
+ format: 'lsp',
1555
+ detail: symbol.detail,
1556
+ tags: symbol.tags,
1557
+ deprecated: symbol.deprecated,
1558
+ containerName: symbol.containerName,
1559
+ parentName: input.parentName
1560
+ }
1561
+ });
1562
+ }
1563
+ result.occurrences.push({
1564
+ id: `occ_${idFragment(symbolId)}_${result.occurrences.length + 1}`,
1565
+ documentId: input.documentId,
1566
+ symbolId,
1567
+ role: 'definition',
1568
+ span: selectionSpan,
1569
+ metadata: { format: 'lsp', range, selectionRange: symbol.selectionRange }
1570
+ });
1571
+ for (const child of normalizeArray(symbol.children)) {
1572
+ addLspSymbol(result, child, {
1573
+ ...input,
1574
+ parentName: [input.parentName, symbolName].filter(Boolean).join('.')
1575
+ });
1576
+ }
1577
+ }
1578
+
1579
+ function addLspSemanticTokens(result, semanticTokens, input) {
1580
+ const data = normalizeArray(semanticTokens.data);
1581
+ const legend = semanticTokens.legend ?? {};
1582
+ let line = 0;
1583
+ let character = 0;
1584
+ for (let index = 0; index + 4 < data.length; index += 5) {
1585
+ line += Number(data[index] ?? 0);
1586
+ character = Number(data[index] ?? 0) === 0 ? character + Number(data[index + 1] ?? 0) : Number(data[index + 1] ?? 0);
1587
+ const length = Number(data[index + 2] ?? 0);
1588
+ const tokenType = legend.tokenTypes?.[Number(data[index + 3] ?? 0)] ?? `tokenType${data[index + 3] ?? 0}`;
1589
+ const span = {
1590
+ path: input.sourcePath,
1591
+ startLine: line + 1,
1592
+ startColumn: character + 1,
1593
+ endLine: line + 1,
1594
+ endColumn: character + length + 1
1595
+ };
1596
+ result.facts.push({
1597
+ id: `fact_${idFragment(input.documentId)}_semantic_token_${index / 5 + 1}`,
1598
+ predicate: 'semanticToken',
1599
+ subjectId: input.documentId,
1600
+ value: {
1601
+ tokenType,
1602
+ tokenModifiers: semanticTokenModifiers(Number(data[index + 4] ?? 0), legend.tokenModifiers),
1603
+ span
1604
+ },
1605
+ metadata: { format: 'lsp' }
1606
+ });
1607
+ }
1608
+ }
1609
+
1610
+ function semanticTokenModifiers(bitset, modifiers = []) {
1611
+ const result = [];
1612
+ for (let index = 0; index < modifiers.length; index += 1) {
1613
+ if ((bitset & (1 << index)) > 0) result.push(modifiers[index]);
1614
+ }
1615
+ return result;
1616
+ }
1617
+
1618
+ function externalDiagnosticFact(diagnostic, context, documentId, sourcePath, index) {
1619
+ return {
1620
+ id: diagnostic.id ? `fact_${idFragment(diagnostic.id)}_diagnostic` : `fact_${context.idPart}_${idFragment(sourcePath)}_diagnostic_${index + 1}`,
1621
+ predicate: `${context.format}.diagnostic`,
1622
+ subjectId: documentId,
1623
+ value: {
1624
+ severity: diagnostic.severity,
1625
+ code: diagnostic.code,
1626
+ message: diagnostic.message,
1627
+ source: diagnostic.source,
1628
+ tags: diagnostic.tags,
1629
+ range: diagnostic.range,
1630
+ span: normalizeExternalSpan(diagnostic.range ?? diagnostic.span, sourcePath, context.sourceHash)
1631
+ },
1632
+ metadata: {
1633
+ format: context.format,
1634
+ source: 'external-semantic-index'
1635
+ }
1636
+ };
1637
+ }
1638
+
1639
+ function externalDiagnosticLoss(diagnostic, context, sourcePath) {
1640
+ const severity = externalDiagnosticSeverity(diagnostic.severity);
1641
+ return {
1642
+ id: diagnostic.id ?? `loss_${context.idPart}_${idFragment(diagnostic.code ?? diagnostic.message ?? severity)}_${idFragment(sourcePath)}`,
1643
+ severity,
1644
+ phase: 'index',
1645
+ sourceFormat: context.format,
1646
+ kind: severity === 'error' ? 'unsupportedSemantic' : 'partialSemanticIndex',
1647
+ message: String(diagnostic.message ?? `${context.format} diagnostic reported ${severity}.`),
1648
+ span: normalizeExternalSpan(diagnostic.range ?? diagnostic.span, sourcePath, context.sourceHash),
1649
+ metadata: {
1650
+ format: context.format,
1651
+ code: diagnostic.code,
1652
+ source: diagnostic.source,
1653
+ tags: diagnostic.tags
1654
+ }
1655
+ };
1656
+ }
1657
+
1658
+ function externalDiagnosticSeverity(value) {
1659
+ if (value === undefined || value === null || value === '') return 'error';
1660
+ const raw = String(value).toLowerCase();
1661
+ if (raw === '1' || raw.includes('error')) return 'error';
1662
+ if (raw === '3' || raw.includes('info') || raw.includes('hint')) return 'info';
1663
+ return 'warning';
1664
+ }
1665
+
1666
+ function spanFromScipOccurrence(occurrence, sourcePath, sourceHash) {
1667
+ if (occurrence.single_line_range || occurrence.singleLineRange) {
1668
+ const range = occurrence.single_line_range ?? occurrence.singleLineRange;
1669
+ return {
1670
+ sourceId: sourceHash,
1671
+ path: sourcePath,
1672
+ startLine: Number(range.line ?? 0) + 1,
1673
+ startColumn: Number(range.start_character ?? range.startCharacter ?? 0) + 1,
1674
+ endLine: Number(range.line ?? 0) + 1,
1675
+ endColumn: Number(range.end_character ?? range.endCharacter ?? 0) + 1
1676
+ };
1677
+ }
1678
+ if (occurrence.multi_line_range || occurrence.multiLineRange) {
1679
+ const range = occurrence.multi_line_range ?? occurrence.multiLineRange;
1680
+ return {
1681
+ sourceId: sourceHash,
1682
+ path: sourcePath,
1683
+ startLine: Number(range.start_line ?? range.startLine ?? 0) + 1,
1684
+ startColumn: Number(range.start_character ?? range.startCharacter ?? 0) + 1,
1685
+ endLine: Number(range.end_line ?? range.endLine ?? 0) + 1,
1686
+ endColumn: Number(range.end_character ?? range.endCharacter ?? 0) + 1
1687
+ };
1688
+ }
1689
+ const range = occurrence.range;
1690
+ if (Array.isArray(range) && range.length >= 3) {
1691
+ const startLine = Number(range[0] ?? 0);
1692
+ const startColumn = Number(range[1] ?? 0);
1693
+ const endLine = range.length >= 4 ? Number(range[2] ?? startLine) : startLine;
1694
+ const endColumn = range.length >= 4 ? Number(range[3] ?? startColumn) : Number(range[2] ?? startColumn);
1695
+ return {
1696
+ sourceId: sourceHash,
1697
+ path: sourcePath,
1698
+ startLine: startLine + 1,
1699
+ startColumn: startColumn + 1,
1700
+ endLine: endLine + 1,
1701
+ endColumn: endColumn + 1
1702
+ };
1703
+ }
1704
+ return undefined;
1705
+ }
1706
+
1707
+ function spanFromSemanticDbRange(range, sourcePath, sourceHash) {
1708
+ if (!range) return undefined;
1709
+ return {
1710
+ sourceId: sourceHash,
1711
+ path: sourcePath,
1712
+ startLine: Number(range.start_line ?? range.startLine ?? 0) + 1,
1713
+ startColumn: Number(range.start_character ?? range.startCharacter ?? 0) + 1,
1714
+ endLine: Number(range.end_line ?? range.endLine ?? 0) + 1,
1715
+ endColumn: Number(range.end_character ?? range.endCharacter ?? 0) + 1
1716
+ };
1717
+ }
1718
+
1719
+ function spanFromLspRange(range, sourcePath, sourceHash, base = 0) {
1720
+ if (!range) return undefined;
1721
+ const source = range.start && range.end ? range : { start: range, end: range.end ?? range };
1722
+ return {
1723
+ sourceId: sourceHash,
1724
+ path: sourcePath,
1725
+ startLine: Number(source.start?.line ?? source.startLine ?? 0) + (base === 0 ? 1 : 0),
1726
+ startColumn: Number(source.start?.character ?? source.startColumn ?? 0) + (base === 0 ? 1 : 0),
1727
+ endLine: Number(source.end?.line ?? source.endLine ?? source.start?.line ?? 0) + (base === 0 ? 1 : 0),
1728
+ endColumn: Number(source.end?.character ?? source.endColumn ?? source.start?.character ?? 0) + (base === 0 ? 1 : 0)
1729
+ };
1730
+ }
1731
+
1732
+ function normalizeExternalSpan(value, sourcePath, sourceHash) {
1733
+ if (!value) return undefined;
1734
+ if (Array.isArray(value)) {
1735
+ return spanFromScipOccurrence({ range: value }, sourcePath, sourceHash);
1736
+ }
1737
+ if (value.start || value.end || value.line !== undefined) return spanFromLspRange(value, sourcePath, sourceHash, 0);
1738
+ if (value.startLine !== undefined || value.start_line !== undefined) {
1739
+ return {
1740
+ sourceId: value.sourceId ?? sourceHash,
1741
+ path: value.path ?? sourcePath,
1742
+ start: value.start,
1743
+ end: value.end,
1744
+ startLine: Number(value.startLine ?? value.start_line),
1745
+ startColumn: value.startColumn ?? value.start_character,
1746
+ endLine: value.endLine ?? value.end_line,
1747
+ endColumn: value.endColumn ?? value.end_character
1748
+ };
1749
+ }
1750
+ return undefined;
1751
+ }
1752
+
1753
+ function uriToPath(uri) {
1754
+ if (typeof uri !== 'string') return undefined;
1755
+ if (uri.startsWith('file://')) {
1756
+ try {
1757
+ return decodeURIComponent(new URL(uri).pathname);
1758
+ } catch {
1759
+ return uri.replace(/^file:\/\//, '');
1760
+ }
1761
+ }
1762
+ return uri;
1763
+ }
1764
+
429
1765
  export function projectFrontierAst(document, target = 'typescript', options = {}) {
430
1766
  const normalized = normalizeCompileTarget(target);
431
1767
  const projector = projectors[normalized];
@@ -473,6 +1809,70 @@ export function resolveCapabilityAdapters(document, target = 'typescript', optio
473
1809
  });
474
1810
  }
475
1811
 
1812
+ export function getNativeImportFeatureEvidencePolicy(kind) {
1813
+ const normalized = normalizeNativeLossKind({ kind }, 'warning');
1814
+ return NativeImportFeatureEvidencePolicies[normalized] ?? NativeImportFeatureEvidencePolicies[String(kind ?? '')];
1815
+ }
1816
+
1817
+ export function summarizeNativeImportFeatureEvidence(losses = [], options = {}) {
1818
+ const normalizedLosses = normalizeNativeLossRecords(losses);
1819
+ const evidence = options.evidence ?? [];
1820
+ const issues = [];
1821
+ const byKind = {};
1822
+ const byRisk = {};
1823
+ const policyKinds = [];
1824
+ let highestRisk = 'low';
1825
+ let semanticMergeReadiness = 'ready';
1826
+
1827
+ for (const loss of normalizedLosses) {
1828
+ const policy = getNativeImportFeatureEvidencePolicy(loss.kind);
1829
+ if (!policy) continue;
1830
+ byKind[policy.kind] = (byKind[policy.kind] ?? 0) + 1;
1831
+ byRisk[policy.risk] = (byRisk[policy.risk] ?? 0) + 1;
1832
+ if ((nativeFeatureEvidenceRiskRank[policy.risk] ?? 0) > (nativeFeatureEvidenceRiskRank[highestRisk] ?? 0)) {
1833
+ highestRisk = policy.risk;
1834
+ }
1835
+ policyKinds.push(policy.kind);
1836
+ semanticMergeReadiness = maxSemanticMergeReadiness(semanticMergeReadiness, policy.minimumReadiness);
1837
+ const missingRequiredEvidence = policy.requiredEvidenceKeys.filter((key) => !nativeImportFeatureEvidenceHasKey(loss, evidence, key));
1838
+ const presentRequiredEvidence = policy.requiredEvidenceKeys.filter((key) => !missingRequiredEvidence.includes(key));
1839
+ const presentRecommendedEvidence = policy.recommendedEvidenceKeys.filter((key) => nativeImportFeatureEvidenceHasKey(loss, evidence, key));
1840
+ if (missingRequiredEvidence.length) {
1841
+ semanticMergeReadiness = maxSemanticMergeReadiness(semanticMergeReadiness, policy.missingEvidenceReadiness);
1842
+ }
1843
+ issues.push({
1844
+ lossId: loss.id,
1845
+ kind: loss.kind,
1846
+ policyKind: policy.kind,
1847
+ risk: policy.risk,
1848
+ category: policy.category,
1849
+ readiness: missingRequiredEvidence.length ? policy.missingEvidenceReadiness : policy.minimumReadiness,
1850
+ missingRequiredEvidence,
1851
+ presentRequiredEvidence,
1852
+ presentRecommendedEvidence,
1853
+ evidenceIds: nativeImportFeatureEvidenceIds(loss, evidence, policy)
1854
+ });
1855
+ }
1856
+
1857
+ const missingRequiredEvidence = issues.flatMap((issue) => issue.missingRequiredEvidence.map((key) => ({
1858
+ lossId: issue.lossId,
1859
+ kind: issue.kind,
1860
+ policyKind: issue.policyKind,
1861
+ evidenceKey: key
1862
+ })));
1863
+ return {
1864
+ total: issues.length,
1865
+ policyKinds: uniqueStrings(policyKinds),
1866
+ byKind,
1867
+ byRisk,
1868
+ highestRisk: issues.length ? highestRisk : 'low',
1869
+ semanticMergeReadiness,
1870
+ missingRequiredEvidence,
1871
+ issues,
1872
+ reasons: nativeImportFeatureEvidenceReasons(issues)
1873
+ };
1874
+ }
1875
+
476
1876
  export function summarizeNativeImportLosses(losses = [], options = {}) {
477
1877
  const normalizedLosses = normalizeNativeLossRecords(losses);
478
1878
  const bySeverity = { info: 0, warning: 0, error: 0 };
@@ -512,6 +1912,9 @@ export function summarizeNativeImportLosses(losses = [], options = {}) {
512
1912
  reviewLossIds,
513
1913
  informationalLossIds
514
1914
  });
1915
+ const featureEvidence = summarizeNativeImportFeatureEvidence(normalizedLosses, {
1916
+ evidence: options.evidence
1917
+ });
515
1918
 
516
1919
  return {
517
1920
  total: normalizedLosses.length,
@@ -527,6 +1930,7 @@ export function summarizeNativeImportLosses(losses = [], options = {}) {
527
1930
  reviewLossIds,
528
1931
  informationalLossIds,
529
1932
  failedEvidenceIds,
1933
+ featureEvidence,
530
1934
  parser: options.parser,
531
1935
  scanKind: options.scanKind,
532
1936
  semanticStatus: options.semanticStatus
@@ -4112,6 +5516,61 @@ function nativeImportCategoryForLossKind(kind) {
4112
5516
  return String(kind ?? 'opaqueNative');
4113
5517
  }
4114
5518
 
5519
+ function nativeImportFeatureEvidencePolicy(kind, input = {}) {
5520
+ return Object.freeze({
5521
+ kind,
5522
+ category: input.category ?? nativeImportCategoryForLossKind(kind),
5523
+ risk: input.risk ?? 'medium',
5524
+ minimumReadiness: normalizeSemanticMergeReadiness(input.minimumReadiness) ?? 'needs-review',
5525
+ missingEvidenceReadiness: normalizeSemanticMergeReadiness(input.missingEvidenceReadiness) ?? 'needs-review',
5526
+ requiredEvidenceKeys: Object.freeze(uniqueStrings(input.requiredEvidenceKeys ?? [])),
5527
+ recommendedEvidenceKeys: Object.freeze(uniqueStrings(input.recommendedEvidenceKeys ?? [])),
5528
+ notes: Object.freeze(uniqueStrings(input.notes ?? []))
5529
+ });
5530
+ }
5531
+
5532
+ function nativeImportFeatureEvidenceHasKey(loss, evidence, key) {
5533
+ return nativeImportFeatureEvidenceValuePresent(nativeImportFeatureEvidenceValue(loss, key))
5534
+ || (evidence ?? []).some((record) => nativeImportFeatureEvidenceValuePresent(nativeImportFeatureEvidenceValue(record, key)));
5535
+ }
5536
+
5537
+ function nativeImportFeatureEvidenceValue(record, key) {
5538
+ if (!record || !key) return undefined;
5539
+ const candidates = [record, record.metadata].filter(Boolean);
5540
+ for (const candidate of candidates) {
5541
+ const direct = candidate[key];
5542
+ if (direct !== undefined) return direct;
5543
+ const dotted = String(key).split('.').reduce((current, part) => current?.[part], candidate);
5544
+ if (dotted !== undefined) return dotted;
5545
+ }
5546
+ return undefined;
5547
+ }
5548
+
5549
+ function nativeImportFeatureEvidenceValuePresent(value) {
5550
+ if (value === undefined || value === null) return false;
5551
+ if (typeof value === 'string') return value.trim().length > 0;
5552
+ if (Array.isArray(value)) return value.length > 0;
5553
+ if (typeof value === 'object') return Object.keys(value).length > 0;
5554
+ return true;
5555
+ }
5556
+
5557
+ function nativeImportFeatureEvidenceIds(loss, evidence, policy) {
5558
+ const keys = [...policy.requiredEvidenceKeys, ...policy.recommendedEvidenceKeys];
5559
+ return uniqueStrings((evidence ?? [])
5560
+ .filter((record) => keys.some((key) => nativeImportFeatureEvidenceValuePresent(nativeImportFeatureEvidenceValue(record, key))))
5561
+ .map((record) => record.id)
5562
+ .filter(Boolean)
5563
+ .concat(loss.evidenceIds ?? []));
5564
+ }
5565
+
5566
+ function nativeImportFeatureEvidenceReasons(issues) {
5567
+ return uniqueStrings((issues ?? []).flatMap((issue) => {
5568
+ const missing = issue.missingRequiredEvidence ?? [];
5569
+ if (!missing.length) return [];
5570
+ return [`${issue.kind} loss ${issue.lossId} is missing required evidence: ${missing.join(', ')}.`];
5571
+ }));
5572
+ }
5573
+
4115
5574
  function normalizeProjectionMatrixTargets(targets) {
4116
5575
  return uniqueStrings((Array.isArray(targets) ? targets : [targets])
4117
5576
  .map((target) => {