@shapeshift-labs/frontier-lang-compiler 0.2.22 → 0.2.24

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
@@ -262,6 +262,10 @@ export const NativeImportRegionTaxonomyKinds = Object.freeze([
262
262
  'call',
263
263
  'type',
264
264
  'effect',
265
+ 'property',
266
+ 'config',
267
+ 'content',
268
+ 'route',
265
269
  'generatedOutput'
266
270
  ]);
267
271
 
@@ -329,6 +333,14 @@ export const NativeImportLanguageProfiles = Object.freeze([
329
333
  nativeImportLanguageProfile('r', { aliases: ['R'], extensions: ['.r', '.R'], parserAdapters: ['r-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] })
330
334
  ]);
331
335
 
336
+ export const ExternalSemanticIndexFormats = Object.freeze([
337
+ 'frontier-semantic-index',
338
+ 'scip',
339
+ 'lsif',
340
+ 'lsp',
341
+ 'semanticdb'
342
+ ]);
343
+
332
344
  export function normalizeCompileTarget(target) {
333
345
  const normalized = String(target ?? 'typescript').toLowerCase();
334
346
  const canonical = canonicalTargets[normalized] ?? normalized;
@@ -544,6 +556,1216 @@ export function compileNativeSource(input, options = {}) {
544
556
  };
545
557
  }
546
558
 
559
+ export function importExternalSemanticIndex(input) {
560
+ const payload = input?.payload ?? input?.semanticIndex ?? input;
561
+ if (!payload || typeof payload !== 'object') {
562
+ throw new Error('importExternalSemanticIndex requires a payload object');
563
+ }
564
+ const format = normalizeExternalSemanticIndexFormat(input?.format ?? inferExternalSemanticIndexFormat(payload));
565
+ const idPart = idFragment(input?.id ?? input?.sourcePath ?? input?.projectRoot ?? format);
566
+ const context = {
567
+ format,
568
+ idPart,
569
+ language: normalizeExternalSemanticLanguage(input?.language ?? payload.language ?? payload.languageId),
570
+ sourcePath: input?.sourcePath ?? payload.sourcePath ?? payload.uri ?? payload.path,
571
+ sourceHash: input?.sourceHash ?? payload.sourceHash ?? payload.md5,
572
+ projectRoot: input?.projectRoot ?? payload.projectRoot ?? payload.project_root ?? payload.metadata?.projectRoot ?? payload.metadata?.project_root,
573
+ parser: input?.parser ?? `${format}.external-semantic-index`,
574
+ metadata: input?.metadata ?? {}
575
+ };
576
+ const normalized = normalizeExternalSemanticIndexPayload(payload, context);
577
+ const evidence = attachNativeImportLossSummary(
578
+ uniqueByEvidenceId([...(normalized.evidence ?? []), ...(input?.evidence ?? [])]),
579
+ summarizeNativeImportLosses(normalized.losses ?? [], {
580
+ evidence: [...(normalized.evidence ?? []), ...(input?.evidence ?? [])],
581
+ parser: context.parser,
582
+ scanKind: 'external-semantic-index',
583
+ semanticStatus: normalized.semanticStatus
584
+ })
585
+ );
586
+ const losses = normalizeNativeLossRecords(normalized.losses ?? []);
587
+ const semanticIndex = createSemanticIndexRecord({
588
+ id: input?.semanticIndexId ?? normalized.semanticIndexId ?? `index_${idPart}_${idFragment(format)}`,
589
+ repository: normalized.repository,
590
+ documents: normalized.documents,
591
+ symbols: normalized.symbols,
592
+ occurrences: normalized.occurrences,
593
+ relations: normalized.relations,
594
+ facts: normalized.facts,
595
+ evidence,
596
+ metadata: {
597
+ format,
598
+ parser: context.parser,
599
+ source: 'external-semantic-index',
600
+ projectRoot: context.projectRoot,
601
+ semanticStatus: normalized.semanticStatus,
602
+ ...normalized.metadata,
603
+ ...context.metadata
604
+ }
605
+ });
606
+ const sourceMapMappings = externalSemanticSourceMapMappings(semanticIndex, {
607
+ evidence,
608
+ losses,
609
+ sourcePath: context.sourcePath,
610
+ sourceHash: context.sourceHash
611
+ });
612
+ const sourceMaps = sourceMapMappings.length
613
+ ? [createSourceMapRecord({
614
+ id: input?.sourceMapId ?? `source_map_${idPart}_${idFragment(format)}`,
615
+ sourcePath: context.sourcePath,
616
+ sourceHash: context.sourceHash,
617
+ semanticIndexId: semanticIndex.id,
618
+ mappings: sourceMapMappings,
619
+ evidence,
620
+ metadata: {
621
+ format,
622
+ source: 'external-semantic-index',
623
+ projectRoot: context.projectRoot
624
+ }
625
+ })]
626
+ : [];
627
+ const document = createDocument({
628
+ id: input?.documentId ?? `document_${idPart}_${idFragment(format)}`,
629
+ name: input?.documentName ?? context.sourcePath ?? `${format} semantic index`,
630
+ nodes: [],
631
+ rootIds: [],
632
+ metadata: {
633
+ sourceLanguage: context.language,
634
+ sourcePath: context.sourcePath,
635
+ sourceHash: context.sourceHash,
636
+ semanticStatus: normalized.semanticStatus,
637
+ externalSemanticIndexFormat: format
638
+ }
639
+ });
640
+ const universalAst = createUniversalAstEnvelope({
641
+ id: input?.universalAstId ?? `universal_ast_${idPart}_${idFragment(format)}`,
642
+ document,
643
+ nativeSources: [],
644
+ semanticIndex,
645
+ sourceMaps,
646
+ losses,
647
+ evidence,
648
+ metadata: {
649
+ sourceLanguage: context.language,
650
+ sourcePath: context.sourcePath,
651
+ sourceHash: context.sourceHash,
652
+ projectRoot: context.projectRoot,
653
+ externalSemanticIndexFormat: format,
654
+ semanticStatus: normalized.semanticStatus,
655
+ ...input?.universalAstMetadata
656
+ }
657
+ });
658
+ const readiness = classifyNativeImportReadiness(losses, {
659
+ evidence,
660
+ parser: context.parser,
661
+ scanKind: 'external-semantic-index',
662
+ semanticStatus: normalized.semanticStatus
663
+ });
664
+ return {
665
+ kind: 'frontier.lang.externalSemanticIndexImport',
666
+ version: 1,
667
+ id: input?.id ?? `external_semantic_index_${idPart}_${idFragment(format)}`,
668
+ format,
669
+ language: context.language,
670
+ sourcePath: context.sourcePath,
671
+ projectRoot: context.projectRoot,
672
+ semanticIndex,
673
+ universalAst,
674
+ sourceMaps,
675
+ losses,
676
+ evidence,
677
+ readiness,
678
+ summary: {
679
+ documents: semanticIndex.documents.length,
680
+ symbols: semanticIndex.symbols.length,
681
+ occurrences: semanticIndex.occurrences.length,
682
+ relations: semanticIndex.relations.length,
683
+ facts: semanticIndex.facts.length,
684
+ sourceMapMappings: sourceMaps.reduce((sum, sourceMap) => sum + (sourceMap.mappings?.length ?? 0), 0),
685
+ losses: losses.length,
686
+ readiness: readiness.readiness
687
+ },
688
+ metadata: {
689
+ format,
690
+ parser: context.parser,
691
+ semanticStatus: normalized.semanticStatus,
692
+ payloadHash: hashSemanticValue(payload),
693
+ ...normalized.metadata,
694
+ ...context.metadata
695
+ }
696
+ };
697
+ }
698
+
699
+ function normalizeExternalSemanticIndexFormat(format) {
700
+ const normalized = String(format ?? 'frontier-semantic-index').trim().toLowerCase();
701
+ const aliases = {
702
+ frontier: 'frontier-semantic-index',
703
+ 'frontier.semantic-index': 'frontier-semantic-index',
704
+ 'frontier.lang.semanticindex': 'frontier-semantic-index',
705
+ scipindex: 'scip',
706
+ 'sourcegraph-scip': 'scip',
707
+ lsp: 'lsp',
708
+ 'language-server-protocol': 'lsp',
709
+ semanticdb: 'semanticdb',
710
+ 'scala-semanticdb': 'semanticdb'
711
+ };
712
+ return aliases[normalized] ?? normalized;
713
+ }
714
+
715
+ function inferExternalSemanticIndexFormat(payload) {
716
+ if (payload.kind === 'frontier.lang.semanticIndex') return 'frontier-semantic-index';
717
+ if (Array.isArray(payload) && payload.some((entry) => entry?.type === 'vertex' || entry?.type === 'edge')) return 'lsif';
718
+ if (Array.isArray(payload.vertices) || Array.isArray(payload.edges)) return 'lsif';
719
+ if (Array.isArray(payload.documents) && payload.documents.some((document) => document?.relative_path ?? document?.relativePath)) return 'scip';
720
+ if (payload.metadata?.project_root || payload.metadata?.projectRoot || payload.external_symbols || payload.externalSymbols) return 'scip';
721
+ if (Array.isArray(payload.documents) && payload.documents.some((document) => document?.symbols && document?.occurrences && (document?.uri || document?.md5 || document?.schema))) return 'semanticdb';
722
+ if (Array.isArray(payload.documentSymbols) || Array.isArray(payload.symbols) || payload.semanticTokens || payload.location || payload.range) return 'lsp';
723
+ return 'frontier-semantic-index';
724
+ }
725
+
726
+ function normalizeExternalSemanticIndexPayload(payload, context) {
727
+ if (context.format === 'frontier-semantic-index') return normalizeFrontierSemanticIndexPayload(payload, context);
728
+ if (context.format === 'scip') return normalizeScipPayload(payload, context);
729
+ if (context.format === 'lsif') return normalizeLsifPayload(payload, context);
730
+ if (context.format === 'lsp') return normalizeLspPayload(payload, context);
731
+ if (context.format === 'semanticdb') return normalizeSemanticDbPayload(payload, context);
732
+ return normalizeGenericExternalSemanticIndexPayload(payload, context);
733
+ }
734
+
735
+ function externalSemanticBase(context, metadata = {}) {
736
+ return {
737
+ repository: context.projectRoot ? { root: context.projectRoot } : undefined,
738
+ documents: [],
739
+ symbols: [],
740
+ occurrences: [],
741
+ relations: [],
742
+ facts: [],
743
+ evidence: [externalSemanticEvidence(context, 'passed', `Imported ${context.format} semantic index payload.`)],
744
+ losses: [externalSemanticCoverageLoss(context)],
745
+ semanticStatus: 'external-semantic-index',
746
+ metadata
747
+ };
748
+ }
749
+
750
+ function normalizeFrontierSemanticIndexPayload(payload, context) {
751
+ const result = externalSemanticBase(context, { sourceFormat: payload.kind ?? 'frontier.lang.semanticIndex' });
752
+ result.repository = payload.repository ?? result.repository;
753
+ result.documents = normalizeArray(payload.documents).map((document, index) => externalDocument(document, context, index));
754
+ result.symbols = normalizeArray(payload.symbols).map((symbol, index) => externalSymbol(symbol, context, index));
755
+ result.occurrences = normalizeArray(payload.occurrences).map((occurrence, index) => externalOccurrence(occurrence, context, index));
756
+ result.relations = normalizeArray(payload.relations).map((relation, index) => externalRelation(relation, context, index));
757
+ result.facts = normalizeArray(payload.facts).map((fact, index) => externalFact(fact, context, index));
758
+ result.evidence = uniqueByEvidenceId([...(payload.evidence ?? []), ...result.evidence]);
759
+ if (payload.metadata) result.metadata = { ...result.metadata, ...payload.metadata };
760
+ return withExternalEmptyLoss(result, context);
761
+ }
762
+
763
+ function normalizeScipPayload(payload, context) {
764
+ const result = externalSemanticBase(context, { sourceFormat: 'scip' });
765
+ const metadata = payload.metadata ?? {};
766
+ const projectRoot = context.projectRoot ?? metadata.project_root ?? metadata.projectRoot;
767
+ result.repository = projectRoot ? { root: projectRoot } : undefined;
768
+ const documents = normalizeArray(payload.documents);
769
+ for (const [documentIndex, document] of documents.entries()) {
770
+ const path = document.relative_path ?? document.relativePath ?? document.path ?? context.sourcePath ?? `scip-document-${documentIndex + 1}`;
771
+ const language = normalizeExternalSemanticLanguage(document.language ?? context.language);
772
+ const documentId = document.id ?? `doc_${idFragment(path)}`;
773
+ result.documents.push({
774
+ id: documentId,
775
+ path,
776
+ language,
777
+ sourceHash: document.sourceHash ?? document.md5 ?? context.sourceHash,
778
+ metadata: {
779
+ format: 'scip',
780
+ projectRoot,
781
+ textDocumentEncoding: metadata.text_document_encoding ?? metadata.textDocumentEncoding,
782
+ documentIndex
783
+ }
784
+ });
785
+ const documentSymbols = new Map();
786
+ for (const symbolInfo of [...normalizeArray(document.symbols), ...normalizeArray(payload.external_symbols ?? payload.externalSymbols)]) {
787
+ const symbolId = scipSymbolId(symbolInfo.symbol, context, normalizeArray(document.symbols).includes(symbolInfo) ? documentId : undefined);
788
+ if (!symbolId || documentSymbols.has(symbolId)) continue;
789
+ documentSymbols.set(symbolId, true);
790
+ result.symbols.push({
791
+ id: symbolId,
792
+ scheme: 'scip',
793
+ name: symbolInfo.display_name ?? symbolInfo.displayName ?? nameFromExternalSymbol(symbolInfo.symbol),
794
+ kind: normalizeExternalSymbolKind(symbolInfo.kind),
795
+ language,
796
+ signatureHash: hashSemanticValue([symbolInfo.symbol, symbolInfo.signature_documentation ?? symbolInfo.signatureDocumentation]),
797
+ metadata: {
798
+ format: 'scip',
799
+ rawSymbol: symbolInfo.symbol,
800
+ documentation: symbolInfo.documentation,
801
+ enclosingSymbol: symbolInfo.enclosing_symbol ?? symbolInfo.enclosingSymbol,
802
+ external: !normalizeArray(document.symbols).includes(symbolInfo)
803
+ }
804
+ });
805
+ result.facts.push(...scipSymbolFacts(symbolInfo, symbolId));
806
+ result.relations.push(...scipRelationshipRelations(symbolInfo, symbolId, context));
807
+ }
808
+ for (const [occurrenceIndex, occurrence] of normalizeArray(document.occurrences).entries()) {
809
+ const symbolId = scipSymbolId(occurrence.symbol, context, documentId);
810
+ if (!symbolId) continue;
811
+ const role = scipOccurrenceRole(occurrence.symbol_roles ?? occurrence.symbolRoles);
812
+ if (!documentSymbols.has(symbolId)) {
813
+ documentSymbols.set(symbolId, true);
814
+ result.symbols.push({
815
+ id: symbolId,
816
+ scheme: 'scip',
817
+ name: nameFromExternalSymbol(occurrence.symbol),
818
+ kind: scipSyntaxKind(occurrence.syntax_kind ?? occurrence.syntaxKind),
819
+ language,
820
+ metadata: { format: 'scip', rawSymbol: occurrence.symbol, inferredFromOccurrence: true }
821
+ });
822
+ }
823
+ result.occurrences.push({
824
+ id: occurrence.id ?? `occ_${idFragment(documentId)}_${occurrenceIndex + 1}`,
825
+ documentId,
826
+ symbolId,
827
+ role,
828
+ span: spanFromScipOccurrence(occurrence, path, context.sourceHash),
829
+ metadata: {
830
+ format: 'scip',
831
+ symbolRoles: occurrence.symbol_roles ?? occurrence.symbolRoles,
832
+ roleSet: scipOccurrenceRoleSet(occurrence.symbol_roles ?? occurrence.symbolRoles),
833
+ syntaxKind: occurrence.syntax_kind ?? occurrence.syntaxKind,
834
+ overrideDocumentation: occurrence.override_documentation ?? occurrence.overrideDocumentation
835
+ }
836
+ });
837
+ for (const diagnostic of normalizeArray(occurrence.diagnostics)) {
838
+ const scopedDiagnostic = { ...diagnostic, range: diagnostic.range ?? occurrence.range };
839
+ result.facts.push(externalDiagnosticFact(scopedDiagnostic, context, documentId, path, result.facts.length));
840
+ result.losses.push(externalDiagnosticLoss(scopedDiagnostic, context, path));
841
+ }
842
+ if (scipOccurrenceRoleSet(occurrence.symbol_roles ?? occurrence.symbolRoles).includes('generated')) {
843
+ result.losses.push({
844
+ id: `loss_${idFragment(documentId)}_${occurrenceIndex + 1}_generated_scip_occurrence`,
845
+ severity: 'warning',
846
+ phase: 'index',
847
+ sourceFormat: 'scip',
848
+ kind: 'generatedCode',
849
+ message: 'SCIP occurrence is marked generated; merge admission should review generated/source ownership before applying patches.',
850
+ span: spanFromScipOccurrence(occurrence, path, context.sourceHash),
851
+ semanticSymbolId: symbolId,
852
+ metadata: { format: 'scip', symbolRoles: occurrence.symbol_roles ?? occurrence.symbolRoles }
853
+ });
854
+ }
855
+ }
856
+ }
857
+ return withExternalEmptyLoss(result, context);
858
+ }
859
+
860
+ function normalizeLsifPayload(payload, context) {
861
+ const result = externalSemanticBase(context, { sourceFormat: 'lsif' });
862
+ const records = Array.isArray(payload) ? payload : [...normalizeArray(payload.vertices), ...normalizeArray(payload.edges)];
863
+ const vertices = new Map(records.filter((record) => record?.type === 'vertex').map((record) => [record.id, record]));
864
+ const edges = records.filter((record) => record?.type === 'edge');
865
+ const documentByVertex = new Map();
866
+ const documentIdByRange = new Map();
867
+ const resultSetByRange = new Map();
868
+ const monikerByOut = new Map();
869
+ const definitionRangeIds = new Set();
870
+ for (const vertex of vertices.values()) {
871
+ if (vertex.label === 'document') {
872
+ const path = uriToPath(vertex.uri) ?? vertex.uri ?? context.sourcePath ?? `lsif-document-${result.documents.length + 1}`;
873
+ const documentId = `doc_${idFragment(vertex.id ?? path)}`;
874
+ documentByVertex.set(vertex.id, { id: documentId, path, language: normalizeExternalSemanticLanguage(vertex.languageId ?? context.language) });
875
+ result.documents.push({
876
+ id: documentId,
877
+ path,
878
+ language: normalizeExternalSemanticLanguage(vertex.languageId ?? context.language),
879
+ metadata: { format: 'lsif', vertexId: vertex.id, uri: vertex.uri }
880
+ });
881
+ }
882
+ if (vertex.label === 'moniker') monikerByOut.set(vertex.id, vertex);
883
+ }
884
+ for (const edge of edges) {
885
+ if (edge.label === 'next') resultSetByRange.set(edge.outV, edge.inV);
886
+ if (edge.label === 'moniker') monikerByOut.set(edge.outV, vertices.get(edge.inV) ?? edge);
887
+ if (edge.label === 'contains') {
888
+ const document = documentByVertex.get(edge.outV);
889
+ if (document) {
890
+ for (const rangeId of normalizeArray(edge.inVs ?? edge.inV)) {
891
+ documentIdByRange.set(rangeId, document.id);
892
+ }
893
+ }
894
+ }
895
+ if (edge.label === 'item' && (edge.property === 'definitions' || edge.property === 'declarations')) {
896
+ for (const rangeId of normalizeArray(edge.inVs ?? edge.inV)) definitionRangeIds.add(rangeId);
897
+ }
898
+ }
899
+ const documentIds = result.documents.map((document) => document.id);
900
+ const defaultDocument = result.documents[0] ?? {
901
+ id: `doc_${idFragment(context.sourcePath ?? 'lsif')}`,
902
+ path: context.sourcePath ?? 'lsif:memory',
903
+ language: context.language
904
+ };
905
+ if (!result.documents.length) result.documents.push(defaultDocument);
906
+ for (const [vertexId, vertex] of vertices.entries()) {
907
+ if (vertex.label !== 'range') continue;
908
+ const resultSetId = resultSetByRange.get(vertexId);
909
+ const moniker = monikerByOut.get(resultSetId) ?? monikerByOut.get(vertexId);
910
+ const symbolId = moniker?.identifier
911
+ ? `symbol:lsif:${idFragment(moniker.scheme ?? moniker.kind ?? 'moniker')}:${idFragment(moniker.identifier)}`
912
+ : `symbol:lsif:${idFragment(resultSetId ?? vertexId)}`;
913
+ const documentId = documentIdByRange.get(vertexId) ?? documentIds[0] ?? defaultDocument.id;
914
+ const owningDocument = result.documents.find((document) => document.id === documentId) ?? defaultDocument;
915
+ const span = spanFromLspRange(vertex, owningDocument.path, context.sourceHash, 0);
916
+ if (!result.symbols.some((symbol) => symbol.id === symbolId)) {
917
+ result.symbols.push({
918
+ id: symbolId,
919
+ scheme: 'lsif',
920
+ name: moniker?.identifier ?? `range:${vertexId}`,
921
+ kind: moniker?.kind ?? 'symbol',
922
+ language: owningDocument.language,
923
+ definitionSpan: definitionRangeIds.has(vertexId) ? span : undefined,
924
+ metadata: { format: 'lsif', resultSetId, moniker }
925
+ });
926
+ }
927
+ result.occurrences.push({
928
+ id: `occ_${idFragment(vertexId)}`,
929
+ documentId,
930
+ symbolId,
931
+ role: definitionRangeIds.has(vertexId) ? 'definition' : 'reference',
932
+ span,
933
+ metadata: { format: 'lsif', vertexId, resultSetId }
934
+ });
935
+ }
936
+ for (const edge of edges) {
937
+ if (edge.label === 'textDocument/definition' || edge.label === 'textDocument/references' || edge.label === 'textDocument/declaration') {
938
+ result.relations.push({
939
+ id: `rel_${idFragment(edge.id ?? `${edge.outV}_${edge.inV}_${edge.label}`)}`,
940
+ sourceId: `lsif:${edge.outV}`,
941
+ predicate: edge.label,
942
+ targetId: `lsif:${edge.inV}`,
943
+ metadata: { format: 'lsif', edge }
944
+ });
945
+ }
946
+ }
947
+ return withExternalEmptyLoss(result, context);
948
+ }
949
+
950
+ function normalizeLspPayload(payload, context) {
951
+ const result = externalSemanticBase(context, { sourceFormat: 'lsp' });
952
+ const documents = normalizeLspDocuments(payload, context);
953
+ for (const [documentIndex, document] of documents.entries()) {
954
+ const sourcePath = uriToPath(document.uri) ?? document.sourcePath ?? document.path ?? context.sourcePath ?? `lsp-document-${documentIndex + 1}`;
955
+ const language = normalizeExternalSemanticLanguage(document.languageId ?? document.language ?? context.language);
956
+ const documentId = document.id ?? `doc_${idFragment(sourcePath)}`;
957
+ result.documents.push({
958
+ id: documentId,
959
+ path: sourcePath,
960
+ language,
961
+ sourceHash: document.sourceHash ?? context.sourceHash,
962
+ metadata: { format: 'lsp', uri: document.uri, documentIndex }
963
+ });
964
+ const symbols = normalizeArray(document.documentSymbols ?? document.symbols ?? payload.documentSymbols ?? payload.symbols);
965
+ for (const symbol of symbols) addLspSymbol(result, symbol, {
966
+ context,
967
+ documentId,
968
+ sourcePath,
969
+ language,
970
+ parentName: symbol.containerName
971
+ });
972
+ const semanticTokens = document.semanticTokens ?? payload.semanticTokens;
973
+ if (semanticTokens) addLspSemanticTokens(result, semanticTokens, { context, documentId, sourcePath, language });
974
+ for (const diagnostic of normalizeArray(document.diagnostics ?? payload.diagnostics)) {
975
+ result.facts.push(externalDiagnosticFact(diagnostic, context, documentId, sourcePath, result.facts.length));
976
+ result.losses.push(externalDiagnosticLoss(diagnostic, context, sourcePath));
977
+ }
978
+ }
979
+ return withExternalEmptyLoss(result, context);
980
+ }
981
+
982
+ function normalizeSemanticDbPayload(payload, context) {
983
+ const result = externalSemanticBase(context, { sourceFormat: 'semanticdb' });
984
+ const documents = normalizeArray(payload.documents ?? payload.textDocuments ?? payload);
985
+ for (const [documentIndex, document] of documents.entries()) {
986
+ const sourcePath = uriToPath(document.uri) ?? document.uri ?? document.path ?? context.sourcePath ?? `semanticdb-document-${documentIndex + 1}`;
987
+ const language = normalizeExternalSemanticLanguage(document.language ?? context.language ?? 'scala');
988
+ const documentId = document.id ?? `doc_${idFragment(sourcePath)}`;
989
+ result.documents.push({
990
+ id: documentId,
991
+ path: sourcePath,
992
+ language,
993
+ sourceHash: document.md5 ?? document.sourceHash ?? context.sourceHash,
994
+ metadata: { format: 'semanticdb', schema: document.schema, documentIndex }
995
+ });
996
+ for (const [symbolIndex, symbolInfo] of normalizeArray(document.symbols).entries()) {
997
+ const symbolId = semanticDbSymbolId(symbolInfo.symbol, context, documentId);
998
+ result.symbols.push({
999
+ id: symbolId,
1000
+ scheme: 'semanticdb',
1001
+ name: symbolInfo.display_name ?? symbolInfo.displayName ?? nameFromExternalSymbol(symbolInfo.symbol),
1002
+ kind: normalizeExternalSymbolKind(symbolInfo.kind),
1003
+ language,
1004
+ signatureHash: hashSemanticValue(symbolInfo.signature ?? symbolInfo.signature_documentation ?? symbolInfo.signatureDocumentation ?? symbolInfo),
1005
+ metadata: { format: 'semanticdb', symbolIndex, rawSymbol: symbolInfo.symbol, properties: symbolInfo.properties }
1006
+ });
1007
+ result.facts.push(...semanticDbSymbolFacts(symbolInfo, symbolId));
1008
+ }
1009
+ for (const [occurrenceIndex, occurrence] of normalizeArray(document.occurrences).entries()) {
1010
+ const symbolId = semanticDbSymbolId(occurrence.symbol, context, documentId);
1011
+ if (!result.symbols.some((symbol) => symbol.id === symbolId)) {
1012
+ result.symbols.push({
1013
+ id: symbolId,
1014
+ scheme: 'semanticdb',
1015
+ name: nameFromExternalSymbol(occurrence.symbol),
1016
+ kind: 'symbol',
1017
+ language,
1018
+ metadata: { format: 'semanticdb', inferredFromOccurrence: true, rawSymbol: occurrence.symbol }
1019
+ });
1020
+ }
1021
+ result.occurrences.push({
1022
+ id: occurrence.id ?? `occ_${idFragment(documentId)}_${occurrenceIndex + 1}`,
1023
+ documentId,
1024
+ symbolId,
1025
+ role: semanticDbOccurrenceRole(occurrence.role),
1026
+ span: spanFromSemanticDbRange(occurrence.range, sourcePath, document.md5 ?? context.sourceHash),
1027
+ metadata: { format: 'semanticdb', role: occurrence.role }
1028
+ });
1029
+ }
1030
+ for (const diagnostic of normalizeArray(document.diagnostics)) {
1031
+ result.facts.push(externalDiagnosticFact(diagnostic, context, documentId, sourcePath, result.facts.length));
1032
+ result.losses.push(externalDiagnosticLoss(diagnostic, context, sourcePath));
1033
+ }
1034
+ }
1035
+ return withExternalEmptyLoss(result, context);
1036
+ }
1037
+
1038
+ function normalizeGenericExternalSemanticIndexPayload(payload, context) {
1039
+ const result = externalSemanticBase(context, { sourceFormat: context.format, genericPayload: true });
1040
+ result.losses.push({
1041
+ id: `loss_${context.idPart}_${idFragment(context.format)}_unsupported_payload`,
1042
+ severity: 'warning',
1043
+ phase: 'index',
1044
+ sourceFormat: context.format,
1045
+ kind: 'unsupportedSemantic',
1046
+ message: `External semantic index format ${context.format} is not recognized; payload hash is preserved as evidence only.`,
1047
+ metadata: { format: context.format, payloadHash: hashSemanticValue(payload) }
1048
+ });
1049
+ return result;
1050
+ }
1051
+
1052
+ function normalizeArray(value) {
1053
+ if (value === undefined || value === null) return [];
1054
+ return Array.isArray(value) ? value : [value];
1055
+ }
1056
+
1057
+ function normalizeExternalSemanticLanguage(value) {
1058
+ if (value === undefined || value === null || value === '') return undefined;
1059
+ const raw = typeof value === 'number' ? externalLanguageNameByNumber[value] : String(value);
1060
+ return normalizeNativeLanguageId(raw);
1061
+ }
1062
+
1063
+ function externalDocument(document, context, index) {
1064
+ const path = document.path ?? document.uri ?? document.relative_path ?? document.relativePath ?? context.sourcePath ?? `external-document-${index + 1}`;
1065
+ return {
1066
+ id: document.id ?? `doc_${idFragment(path)}`,
1067
+ path: uriToPath(path) ?? path,
1068
+ language: normalizeExternalSemanticLanguage(document.language ?? document.languageId ?? context.language),
1069
+ sourceHash: document.sourceHash ?? document.md5 ?? context.sourceHash,
1070
+ metadata: { format: context.format, ...document.metadata }
1071
+ };
1072
+ }
1073
+
1074
+ function externalSymbol(symbol, context, index) {
1075
+ const id = symbol.id ?? symbol.symbolId ?? symbol.symbol ?? `symbol:${context.format}:${index + 1}`;
1076
+ return {
1077
+ ...symbol,
1078
+ id: String(id),
1079
+ scheme: symbol.scheme ?? context.format,
1080
+ name: symbol.name ?? symbol.display_name ?? symbol.displayName ?? nameFromExternalSymbol(id),
1081
+ kind: normalizeExternalSymbolKind(symbol.kind),
1082
+ language: normalizeExternalSemanticLanguage(symbol.language ?? context.language),
1083
+ definitionSpan: normalizeExternalSpan(symbol.definitionSpan ?? symbol.span, context.sourcePath, context.sourceHash),
1084
+ metadata: { format: context.format, rawSymbol: symbol.symbol, ...symbol.metadata }
1085
+ };
1086
+ }
1087
+
1088
+ function externalOccurrence(occurrence, context, index) {
1089
+ return {
1090
+ ...occurrence,
1091
+ id: occurrence.id ?? `occ_${context.idPart}_${index + 1}`,
1092
+ documentId: occurrence.documentId ?? occurrence.document_id ?? `doc_${idFragment(occurrence.path ?? context.sourcePath ?? context.format)}`,
1093
+ symbolId: occurrence.symbolId ?? occurrence.symbol_id ?? occurrence.symbol ?? `symbol:${context.format}:unknown`,
1094
+ role: normalizeExternalOccurrenceRole(occurrence.role),
1095
+ span: normalizeExternalSpan(occurrence.span ?? occurrence.range, occurrence.path ?? context.sourcePath, context.sourceHash),
1096
+ metadata: { format: context.format, ...occurrence.metadata }
1097
+ };
1098
+ }
1099
+
1100
+ function externalRelation(relation, context, index) {
1101
+ return {
1102
+ ...relation,
1103
+ id: relation.id ?? `rel_${context.idPart}_${index + 1}`,
1104
+ sourceId: relation.sourceId ?? relation.source_id ?? relation.subjectId ?? relation.subject_id ?? `external:${context.format}`,
1105
+ predicate: relation.predicate ?? relation.label ?? relation.kind ?? 'related',
1106
+ targetId: relation.targetId ?? relation.target_id ?? relation.objectId ?? relation.object_id ?? relation.symbol ?? `external:${context.format}`,
1107
+ metadata: { format: context.format, ...relation.metadata }
1108
+ };
1109
+ }
1110
+
1111
+ function externalFact(fact, context, index) {
1112
+ return {
1113
+ ...fact,
1114
+ id: fact.id ?? `fact_${context.idPart}_${index + 1}`,
1115
+ predicate: fact.predicate ?? fact.kind ?? 'externalFact',
1116
+ subjectId: fact.subjectId ?? fact.subject_id ?? fact.symbolId ?? fact.symbol_id ?? `external:${context.format}`,
1117
+ value: fact.value ?? fact.data ?? fact,
1118
+ metadata: { format: context.format, ...fact.metadata }
1119
+ };
1120
+ }
1121
+
1122
+ function externalSemanticEvidence(context, status, summary, metadata = {}) {
1123
+ return {
1124
+ id: `evidence_${context.idPart}_${idFragment(context.format)}_external_semantic_index`,
1125
+ kind: 'import',
1126
+ status,
1127
+ path: context.sourcePath,
1128
+ summary,
1129
+ metadata: {
1130
+ format: context.format,
1131
+ parser: context.parser,
1132
+ projectRoot: context.projectRoot,
1133
+ ...metadata
1134
+ }
1135
+ };
1136
+ }
1137
+
1138
+ function externalSemanticCoverageLoss(context) {
1139
+ return {
1140
+ id: `loss_${context.idPart}_${idFragment(context.format)}_partial_semantic_index`,
1141
+ severity: 'info',
1142
+ phase: 'index',
1143
+ sourceFormat: context.format,
1144
+ kind: 'partialSemanticIndex',
1145
+ 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.`,
1146
+ semanticIndexId: context.semanticIndexId,
1147
+ metadata: {
1148
+ format: context.format,
1149
+ parser: context.parser,
1150
+ source: 'external-semantic-index'
1151
+ }
1152
+ };
1153
+ }
1154
+
1155
+ function withExternalEmptyLoss(result, context) {
1156
+ if (!result.documents.length) {
1157
+ result.documents.push({
1158
+ id: `doc_${context.idPart}_${idFragment(context.format)}`,
1159
+ path: context.sourcePath ?? `${context.format}:memory`,
1160
+ language: context.language,
1161
+ sourceHash: context.sourceHash,
1162
+ metadata: { format: context.format, inferred: true }
1163
+ });
1164
+ }
1165
+ if (!result.symbols.length && !result.occurrences.length) {
1166
+ result.losses.push({
1167
+ id: `loss_${context.idPart}_${idFragment(context.format)}_empty_semantic_index`,
1168
+ severity: 'warning',
1169
+ phase: 'index',
1170
+ sourceFormat: context.format,
1171
+ kind: 'partialSemanticIndex',
1172
+ message: `${context.format} payload did not contain symbols or occurrences that Frontier can map.`,
1173
+ metadata: { format: context.format }
1174
+ });
1175
+ }
1176
+ attachExternalOwnership(result, context);
1177
+ result.symbols = uniqueRecordsById(result.symbols);
1178
+ result.occurrences = uniqueRecordsById(result.occurrences);
1179
+ result.relations = uniqueRecordsById(result.relations);
1180
+ result.facts = uniqueRecordsById(result.facts);
1181
+ result.losses = uniqueByLossId(result.losses);
1182
+ result.evidence = uniqueByEvidenceId(result.evidence);
1183
+ return result;
1184
+ }
1185
+
1186
+ function attachExternalOwnership(result, context) {
1187
+ const occurrencesBySymbol = new Map();
1188
+ for (const occurrence of result.occurrences) {
1189
+ if (!occurrencesBySymbol.has(occurrence.symbolId)) occurrencesBySymbol.set(occurrence.symbolId, []);
1190
+ occurrencesBySymbol.get(occurrence.symbolId).push(occurrence);
1191
+ result.relations.push({
1192
+ id: `rel_${idFragment(occurrence.documentId)}_${idFragment(occurrence.id)}_${idFragment(occurrence.role)}`,
1193
+ sourceId: occurrence.documentId,
1194
+ predicate: externalRelationPredicateForOccurrence(occurrence),
1195
+ targetId: occurrence.symbolId,
1196
+ metadata: {
1197
+ format: context.format,
1198
+ source: 'external-semantic-index',
1199
+ occurrenceId: occurrence.id,
1200
+ role: occurrence.role
1201
+ }
1202
+ });
1203
+ }
1204
+ result.symbols = result.symbols.map((symbol) => {
1205
+ const occurrences = occurrencesBySymbol.get(symbol.id) ?? [];
1206
+ const definition = occurrences.find((occurrence) => occurrence.role === 'definition') ?? occurrences[0];
1207
+ const sourceSpan = symbol.definitionSpan ?? definition?.span;
1208
+ const regionKind = semanticRegionKindForSymbol(symbol, undefined, undefined);
1209
+ const key = [
1210
+ 'external',
1211
+ symbol.language ?? context.language ?? 'unknown',
1212
+ sourceSpan?.path ?? context.sourcePath ?? 'memory',
1213
+ regionKind,
1214
+ symbol.name ?? symbol.id
1215
+ ].join('#');
1216
+ const region = {
1217
+ id: `region_${idFragment(key)}`,
1218
+ key,
1219
+ regionKind,
1220
+ granularity: 'symbol',
1221
+ language: symbol.language ?? context.language,
1222
+ documentId: definition?.documentId,
1223
+ sourcePath: sourceSpan?.path ?? context.sourcePath,
1224
+ sourceHash: context.sourceHash,
1225
+ symbolId: symbol.id,
1226
+ symbolName: symbol.name,
1227
+ symbolKind: symbol.kind,
1228
+ sourceSpan,
1229
+ precision: sourceSpan ? 'declaration' : 'unknown',
1230
+ mergePolicy: semanticRegionMergePolicy(regionKind),
1231
+ metadata: {
1232
+ format: context.format,
1233
+ source: 'external-semantic-index'
1234
+ }
1235
+ };
1236
+ result.facts.push({
1237
+ id: `fact_${idFragment(symbol.id)}_ownership_region`,
1238
+ predicate: 'semanticOwnershipRegion',
1239
+ subjectId: symbol.id,
1240
+ value: region
1241
+ }, {
1242
+ id: `fact_${idFragment(symbol.id)}_ownership_region_taxonomy`,
1243
+ predicate: 'semanticOwnershipRegionTaxonomy',
1244
+ subjectId: symbol.id,
1245
+ value: {
1246
+ regionKind: region.regionKind,
1247
+ granularity: region.granularity,
1248
+ key: region.key
1249
+ }
1250
+ });
1251
+ return {
1252
+ ...symbol,
1253
+ definitionSpan: symbol.definitionSpan ?? definition?.span,
1254
+ metadata: {
1255
+ ...symbol.metadata,
1256
+ ownershipRegionId: symbol.metadata?.ownershipRegionId ?? region.id,
1257
+ ownershipRegionKey: symbol.metadata?.ownershipRegionKey ?? region.key,
1258
+ ownershipRegionKind: symbol.metadata?.ownershipRegionKind ?? region.regionKind
1259
+ }
1260
+ };
1261
+ });
1262
+ }
1263
+
1264
+ function externalRelationPredicateForOccurrence(occurrence) {
1265
+ const role = String(occurrence.role ?? '').toLowerCase();
1266
+ if (role === 'definition' || role === 'declaration') return 'defines';
1267
+ if (role === 'import') return 'imports';
1268
+ if (role === 'write') return 'writes';
1269
+ if (role === 'read') return 'reads';
1270
+ return 'references';
1271
+ }
1272
+
1273
+ function externalSemanticSourceMapMappings(semanticIndex, context) {
1274
+ const symbolsById = new Map((semanticIndex.symbols ?? []).map((symbol) => [symbol.id, symbol]));
1275
+ const evidenceIds = (context.evidence ?? []).map((record) => record.id).filter(Boolean);
1276
+ const lossIds = (context.losses ?? []).map((loss) => loss.id).filter(Boolean);
1277
+ return (semanticIndex.occurrences ?? [])
1278
+ .filter((occurrence) => occurrence.span)
1279
+ .map((occurrence, index) => {
1280
+ const symbol = symbolsById.get(occurrence.symbolId);
1281
+ return {
1282
+ id: `map_${idFragment(occurrence.id ?? `${occurrence.symbolId}_${index + 1}`)}`,
1283
+ semanticSymbolId: occurrence.symbolId,
1284
+ semanticOccurrenceId: occurrence.id,
1285
+ sourceSpan: occurrence.span,
1286
+ evidenceIds,
1287
+ lossIds,
1288
+ ownershipRegionId: symbol?.metadata?.ownershipRegionId,
1289
+ ownershipRegionKey: symbol?.metadata?.ownershipRegionKey,
1290
+ ownershipRegionKind: symbol?.metadata?.ownershipRegionKind,
1291
+ precision: occurrence.span ? 'declaration' : 'unknown',
1292
+ metadata: {
1293
+ source: 'external-semantic-index'
1294
+ }
1295
+ };
1296
+ });
1297
+ }
1298
+
1299
+ function scipSymbolId(symbol, context, documentId) {
1300
+ if (!symbol) return undefined;
1301
+ const raw = String(symbol);
1302
+ if (raw.startsWith('symbol:')) return raw;
1303
+ const scope = /^local\b/i.test(raw) ? `${documentId ?? context.idPart}:` : '';
1304
+ return `symbol:scip:${idFragment(scope + raw)}`;
1305
+ }
1306
+
1307
+ function semanticDbSymbolId(symbol, context, documentId) {
1308
+ if (!symbol) return `symbol:semanticdb:${context.idPart}:unknown`;
1309
+ if (String(symbol).startsWith('symbol:')) return String(symbol);
1310
+ const scope = /^local\d+$/i.test(String(symbol)) ? `${documentId}:` : '';
1311
+ return `symbol:semanticdb:${idFragment(scope + symbol)}`;
1312
+ }
1313
+
1314
+ function nameFromExternalSymbol(symbol) {
1315
+ const value = String(symbol ?? 'symbol');
1316
+ const cleaned = value
1317
+ .replace(/^symbol:[^:]+:/, '')
1318
+ .replace(/[`'"]/g, '')
1319
+ .split(/[\/#.:() +]+/)
1320
+ .filter(Boolean)
1321
+ .at(-1);
1322
+ return cleaned || value;
1323
+ }
1324
+
1325
+ const externalSymbolKindByNumber = Object.freeze({
1326
+ 1: 'array',
1327
+ 2: 'assertion',
1328
+ 3: 'associatedType',
1329
+ 4: 'attribute',
1330
+ 7: 'class',
1331
+ 8: 'constant',
1332
+ 9: 'constructor',
1333
+ 11: 'enum',
1334
+ 12: 'enumMember',
1335
+ 13: 'event',
1336
+ 15: 'field',
1337
+ 16: 'file',
1338
+ 17: 'function',
1339
+ 21: 'interface',
1340
+ 25: 'macro',
1341
+ 26: 'method',
1342
+ 28: 'message',
1343
+ 29: 'module',
1344
+ 30: 'namespace',
1345
+ 35: 'package',
1346
+ 37: 'parameter',
1347
+ 41: 'property',
1348
+ 42: 'protocol',
1349
+ 49: 'struct',
1350
+ 53: 'trait',
1351
+ 54: 'type',
1352
+ 55: 'typeAlias',
1353
+ 58: 'typeParameter',
1354
+ 61: 'variable',
1355
+ 66: 'abstractMethod'
1356
+ });
1357
+
1358
+ const lspSymbolKindByNumber = Object.freeze({
1359
+ 1: 'file',
1360
+ 2: 'module',
1361
+ 3: 'namespace',
1362
+ 4: 'package',
1363
+ 5: 'class',
1364
+ 6: 'method',
1365
+ 7: 'property',
1366
+ 8: 'field',
1367
+ 9: 'constructor',
1368
+ 10: 'enum',
1369
+ 11: 'interface',
1370
+ 12: 'function',
1371
+ 13: 'variable',
1372
+ 14: 'constant',
1373
+ 22: 'enumMember',
1374
+ 23: 'struct',
1375
+ 26: 'typeParameter'
1376
+ });
1377
+
1378
+ const externalLanguageNameByNumber = Object.freeze({
1379
+ 1: 'csharp',
1380
+ 2: 'swift',
1381
+ 3: 'dart',
1382
+ 4: 'kotlin',
1383
+ 5: 'scala',
1384
+ 6: 'java',
1385
+ 15: 'python',
1386
+ 16: 'ruby',
1387
+ 17: 'elixir',
1388
+ 18: 'erlang',
1389
+ 19: 'php',
1390
+ 22: 'javascript',
1391
+ 23: 'typescript',
1392
+ 33: 'go',
1393
+ 34: 'c',
1394
+ 35: 'cpp',
1395
+ 38: 'zig',
1396
+ 40: 'rust',
1397
+ 44: 'haskell',
1398
+ 54: 'r',
1399
+ 69: 'sql'
1400
+ });
1401
+
1402
+ function normalizeExternalSymbolKind(kind) {
1403
+ if (kind === undefined || kind === null || kind === '') return 'symbol';
1404
+ if (typeof kind === 'number') return externalSymbolKindByNumber[kind] ?? lspSymbolKindByNumber[kind] ?? `kind${kind}`;
1405
+ return String(kind).replace(/^[A-Z_]+_/, '').replace(/^[A-Z]/, (letter) => letter.toLowerCase());
1406
+ }
1407
+
1408
+ function normalizeLspSymbolKind(kind) {
1409
+ if (typeof kind === 'number') return lspSymbolKindByNumber[kind] ?? `kind${kind}`;
1410
+ return normalizeExternalSymbolKind(kind);
1411
+ }
1412
+
1413
+ function scipSyntaxKind(kind) {
1414
+ const normalized = typeof kind === 'number' ? kind : Number(kind);
1415
+ if (normalized === 15 || normalized === 16) return 'function';
1416
+ if (normalized === 19 || normalized === 20) return 'type';
1417
+ if (normalized === 25 || normalized === 26) return 'module';
1418
+ if (normalized === 9 || normalized === 10 || normalized === 12) return 'variable';
1419
+ return 'symbol';
1420
+ }
1421
+
1422
+ function normalizeExternalOccurrenceRole(role) {
1423
+ const value = String(role ?? 'reference').toLowerCase();
1424
+ if (value.includes('def')) return 'definition';
1425
+ if (value.includes('decl')) return 'declaration';
1426
+ if (value.includes('import')) return 'import';
1427
+ if (value.includes('write')) return 'write';
1428
+ if (value.includes('read')) return 'read';
1429
+ return value === '2' ? 'definition' : value === '1' ? 'reference' : 'reference';
1430
+ }
1431
+
1432
+ function scipOccurrenceRole(value) {
1433
+ const role = Number(value ?? 0);
1434
+ if ((role & 0x1) > 0) return 'definition';
1435
+ if ((role & 0x2) > 0) return 'import';
1436
+ if ((role & 0x4) > 0) return 'write';
1437
+ if ((role & 0x8) > 0) return 'read';
1438
+ return 'reference';
1439
+ }
1440
+
1441
+ function scipOccurrenceRoleSet(value) {
1442
+ const role = Number(value ?? 0);
1443
+ const roles = [];
1444
+ if ((role & 0x1) > 0) roles.push('definition');
1445
+ if ((role & 0x2) > 0) roles.push('import');
1446
+ if ((role & 0x4) > 0) roles.push('write');
1447
+ if ((role & 0x8) > 0) roles.push('read');
1448
+ if ((role & 0x10) > 0) roles.push('generated');
1449
+ if ((role & 0x20) > 0) roles.push('test');
1450
+ if ((role & 0x40) > 0) roles.push('forwardDefinition');
1451
+ return roles.length ? roles : ['reference'];
1452
+ }
1453
+
1454
+ function semanticDbOccurrenceRole(value) {
1455
+ const role = String(value ?? 'reference').toLowerCase();
1456
+ if (role === '2' || role.includes('definition')) return 'definition';
1457
+ return 'reference';
1458
+ }
1459
+
1460
+ function scipRelationshipRelations(symbolInfo, symbolId, context) {
1461
+ return normalizeArray(symbolInfo.relationships).flatMap((relationship, index) => {
1462
+ const targetId = scipSymbolId(relationship.symbol, context);
1463
+ if (!targetId) return [];
1464
+ const predicates = [];
1465
+ if (relationship.is_reference ?? relationship.isReference) predicates.push('references');
1466
+ if (relationship.is_implementation ?? relationship.isImplementation) predicates.push('implements');
1467
+ if (relationship.is_type_definition ?? relationship.isTypeDefinition) predicates.push('typeDefinition');
1468
+ if (relationship.is_definition ?? relationship.isDefinition) predicates.push('definitionOf');
1469
+ return (predicates.length ? predicates : ['related']).map((predicate) => ({
1470
+ id: `rel_${idFragment(symbolId)}_${idFragment(targetId)}_${idFragment(predicate)}_${index + 1}`,
1471
+ sourceId: symbolId,
1472
+ predicate,
1473
+ targetId,
1474
+ metadata: { format: 'scip', relationship }
1475
+ }));
1476
+ });
1477
+ }
1478
+
1479
+ function scipSymbolFacts(symbolInfo, symbolId) {
1480
+ const facts = [];
1481
+ if (symbolInfo.documentation) {
1482
+ facts.push({
1483
+ id: `fact_${idFragment(symbolId)}_documentation`,
1484
+ predicate: 'documentation',
1485
+ subjectId: symbolId,
1486
+ value: normalizeArray(symbolInfo.documentation)
1487
+ });
1488
+ }
1489
+ const signature = symbolInfo.signature_documentation ?? symbolInfo.signatureDocumentation;
1490
+ if (signature) {
1491
+ facts.push({
1492
+ id: `fact_${idFragment(symbolId)}_signature`,
1493
+ predicate: 'signature',
1494
+ subjectId: symbolId,
1495
+ value: signature
1496
+ });
1497
+ }
1498
+ for (const [index, relationship] of normalizeArray(symbolInfo.relationships).entries()) {
1499
+ facts.push({
1500
+ id: `fact_${idFragment(symbolId)}_relationship_${index + 1}`,
1501
+ predicate: 'relationship',
1502
+ subjectId: symbolId,
1503
+ value: relationship
1504
+ });
1505
+ }
1506
+ return facts;
1507
+ }
1508
+
1509
+ function semanticDbSymbolFacts(symbolInfo, symbolId) {
1510
+ const facts = [];
1511
+ for (const key of ['signature', 'properties', 'annotations', 'access', 'language']) {
1512
+ if (symbolInfo[key] !== undefined) {
1513
+ facts.push({
1514
+ id: `fact_${idFragment(symbolId)}_${idFragment(key)}`,
1515
+ predicate: key,
1516
+ subjectId: symbolId,
1517
+ value: symbolInfo[key]
1518
+ });
1519
+ }
1520
+ }
1521
+ return facts;
1522
+ }
1523
+
1524
+ function normalizeLspDocuments(payload, context) {
1525
+ if (Array.isArray(payload.documents)) return payload.documents;
1526
+ if (payload.textDocument || payload.uri || payload.documentSymbols || payload.symbols || payload.semanticTokens || payload.diagnostics) {
1527
+ return [{
1528
+ ...payload,
1529
+ uri: payload.uri ?? payload.textDocument?.uri,
1530
+ languageId: payload.languageId ?? payload.language ?? context.language,
1531
+ documentSymbols: payload.documentSymbols,
1532
+ symbols: payload.symbols,
1533
+ semanticTokens: payload.semanticTokens,
1534
+ diagnostics: payload.diagnostics
1535
+ }];
1536
+ }
1537
+ return [{ uri: context.sourcePath, languageId: context.language }];
1538
+ }
1539
+
1540
+ function addLspSymbol(result, symbol, input) {
1541
+ const location = symbol.location ?? {};
1542
+ const range = symbol.range ?? location.range ?? symbol.selectionRange;
1543
+ const sourcePath = uriToPath(location.uri ?? symbol.uri) ?? input.sourcePath;
1544
+ const symbolName = symbol.name ?? symbol.containerName ?? `symbol_${result.symbols.length + 1}`;
1545
+ const symbolId = symbol.id ?? `symbol:lsp:${idFragment(input.language ?? 'unknown')}:${idFragment([input.parentName, symbolName].filter(Boolean).join('.'))}`;
1546
+ const ownershipSpan = spanFromLspRange(range, sourcePath, input.context.sourceHash, 0);
1547
+ const selectionSpan = spanFromLspRange(symbol.selectionRange ?? range, sourcePath, input.context.sourceHash, 0);
1548
+ if (!result.symbols.some((entry) => entry.id === symbolId)) {
1549
+ result.symbols.push({
1550
+ id: symbolId,
1551
+ scheme: 'lsp',
1552
+ name: symbolName,
1553
+ kind: normalizeLspSymbolKind(symbol.kind),
1554
+ language: input.language,
1555
+ definitionSpan: ownershipSpan,
1556
+ signatureHash: hashSemanticValue([symbolName, symbol.kind, symbol.detail]),
1557
+ metadata: {
1558
+ format: 'lsp',
1559
+ detail: symbol.detail,
1560
+ tags: symbol.tags,
1561
+ deprecated: symbol.deprecated,
1562
+ containerName: symbol.containerName,
1563
+ parentName: input.parentName
1564
+ }
1565
+ });
1566
+ }
1567
+ result.occurrences.push({
1568
+ id: `occ_${idFragment(symbolId)}_${result.occurrences.length + 1}`,
1569
+ documentId: input.documentId,
1570
+ symbolId,
1571
+ role: 'definition',
1572
+ span: selectionSpan,
1573
+ metadata: { format: 'lsp', range, selectionRange: symbol.selectionRange }
1574
+ });
1575
+ for (const child of normalizeArray(symbol.children)) {
1576
+ addLspSymbol(result, child, {
1577
+ ...input,
1578
+ parentName: [input.parentName, symbolName].filter(Boolean).join('.')
1579
+ });
1580
+ }
1581
+ }
1582
+
1583
+ function addLspSemanticTokens(result, semanticTokens, input) {
1584
+ const data = normalizeArray(semanticTokens.data);
1585
+ const legend = semanticTokens.legend ?? {};
1586
+ let line = 0;
1587
+ let character = 0;
1588
+ for (let index = 0; index + 4 < data.length; index += 5) {
1589
+ line += Number(data[index] ?? 0);
1590
+ character = Number(data[index] ?? 0) === 0 ? character + Number(data[index + 1] ?? 0) : Number(data[index + 1] ?? 0);
1591
+ const length = Number(data[index + 2] ?? 0);
1592
+ const tokenType = legend.tokenTypes?.[Number(data[index + 3] ?? 0)] ?? `tokenType${data[index + 3] ?? 0}`;
1593
+ const span = {
1594
+ path: input.sourcePath,
1595
+ startLine: line + 1,
1596
+ startColumn: character + 1,
1597
+ endLine: line + 1,
1598
+ endColumn: character + length + 1
1599
+ };
1600
+ result.facts.push({
1601
+ id: `fact_${idFragment(input.documentId)}_semantic_token_${index / 5 + 1}`,
1602
+ predicate: 'semanticToken',
1603
+ subjectId: input.documentId,
1604
+ value: {
1605
+ tokenType,
1606
+ tokenModifiers: semanticTokenModifiers(Number(data[index + 4] ?? 0), legend.tokenModifiers),
1607
+ span
1608
+ },
1609
+ metadata: { format: 'lsp' }
1610
+ });
1611
+ }
1612
+ }
1613
+
1614
+ function semanticTokenModifiers(bitset, modifiers = []) {
1615
+ const result = [];
1616
+ for (let index = 0; index < modifiers.length; index += 1) {
1617
+ if ((bitset & (1 << index)) > 0) result.push(modifiers[index]);
1618
+ }
1619
+ return result;
1620
+ }
1621
+
1622
+ function externalDiagnosticFact(diagnostic, context, documentId, sourcePath, index) {
1623
+ return {
1624
+ id: diagnostic.id ? `fact_${idFragment(diagnostic.id)}_diagnostic` : `fact_${context.idPart}_${idFragment(sourcePath)}_diagnostic_${index + 1}`,
1625
+ predicate: `${context.format}.diagnostic`,
1626
+ subjectId: documentId,
1627
+ value: {
1628
+ severity: diagnostic.severity,
1629
+ code: diagnostic.code,
1630
+ message: diagnostic.message,
1631
+ source: diagnostic.source,
1632
+ tags: diagnostic.tags,
1633
+ range: diagnostic.range,
1634
+ span: normalizeExternalSpan(diagnostic.range ?? diagnostic.span, sourcePath, context.sourceHash)
1635
+ },
1636
+ metadata: {
1637
+ format: context.format,
1638
+ source: 'external-semantic-index'
1639
+ }
1640
+ };
1641
+ }
1642
+
1643
+ function externalDiagnosticLoss(diagnostic, context, sourcePath) {
1644
+ const severity = externalDiagnosticSeverity(diagnostic.severity);
1645
+ return {
1646
+ id: diagnostic.id ?? `loss_${context.idPart}_${idFragment(diagnostic.code ?? diagnostic.message ?? severity)}_${idFragment(sourcePath)}`,
1647
+ severity,
1648
+ phase: 'index',
1649
+ sourceFormat: context.format,
1650
+ kind: severity === 'error' ? 'unsupportedSemantic' : 'partialSemanticIndex',
1651
+ message: String(diagnostic.message ?? `${context.format} diagnostic reported ${severity}.`),
1652
+ span: normalizeExternalSpan(diagnostic.range ?? diagnostic.span, sourcePath, context.sourceHash),
1653
+ metadata: {
1654
+ format: context.format,
1655
+ code: diagnostic.code,
1656
+ source: diagnostic.source,
1657
+ tags: diagnostic.tags
1658
+ }
1659
+ };
1660
+ }
1661
+
1662
+ function externalDiagnosticSeverity(value) {
1663
+ if (value === undefined || value === null || value === '') return 'error';
1664
+ const raw = String(value).toLowerCase();
1665
+ if (raw === '1' || raw.includes('error')) return 'error';
1666
+ if (raw === '3' || raw.includes('info') || raw.includes('hint')) return 'info';
1667
+ return 'warning';
1668
+ }
1669
+
1670
+ function spanFromScipOccurrence(occurrence, sourcePath, sourceHash) {
1671
+ if (occurrence.single_line_range || occurrence.singleLineRange) {
1672
+ const range = occurrence.single_line_range ?? occurrence.singleLineRange;
1673
+ return {
1674
+ sourceId: sourceHash,
1675
+ path: sourcePath,
1676
+ startLine: Number(range.line ?? 0) + 1,
1677
+ startColumn: Number(range.start_character ?? range.startCharacter ?? 0) + 1,
1678
+ endLine: Number(range.line ?? 0) + 1,
1679
+ endColumn: Number(range.end_character ?? range.endCharacter ?? 0) + 1
1680
+ };
1681
+ }
1682
+ if (occurrence.multi_line_range || occurrence.multiLineRange) {
1683
+ const range = occurrence.multi_line_range ?? occurrence.multiLineRange;
1684
+ return {
1685
+ sourceId: sourceHash,
1686
+ path: sourcePath,
1687
+ startLine: Number(range.start_line ?? range.startLine ?? 0) + 1,
1688
+ startColumn: Number(range.start_character ?? range.startCharacter ?? 0) + 1,
1689
+ endLine: Number(range.end_line ?? range.endLine ?? 0) + 1,
1690
+ endColumn: Number(range.end_character ?? range.endCharacter ?? 0) + 1
1691
+ };
1692
+ }
1693
+ const range = occurrence.range;
1694
+ if (Array.isArray(range) && range.length >= 3) {
1695
+ const startLine = Number(range[0] ?? 0);
1696
+ const startColumn = Number(range[1] ?? 0);
1697
+ const endLine = range.length >= 4 ? Number(range[2] ?? startLine) : startLine;
1698
+ const endColumn = range.length >= 4 ? Number(range[3] ?? startColumn) : Number(range[2] ?? startColumn);
1699
+ return {
1700
+ sourceId: sourceHash,
1701
+ path: sourcePath,
1702
+ startLine: startLine + 1,
1703
+ startColumn: startColumn + 1,
1704
+ endLine: endLine + 1,
1705
+ endColumn: endColumn + 1
1706
+ };
1707
+ }
1708
+ return undefined;
1709
+ }
1710
+
1711
+ function spanFromSemanticDbRange(range, sourcePath, sourceHash) {
1712
+ if (!range) return undefined;
1713
+ return {
1714
+ sourceId: sourceHash,
1715
+ path: sourcePath,
1716
+ startLine: Number(range.start_line ?? range.startLine ?? 0) + 1,
1717
+ startColumn: Number(range.start_character ?? range.startCharacter ?? 0) + 1,
1718
+ endLine: Number(range.end_line ?? range.endLine ?? 0) + 1,
1719
+ endColumn: Number(range.end_character ?? range.endCharacter ?? 0) + 1
1720
+ };
1721
+ }
1722
+
1723
+ function spanFromLspRange(range, sourcePath, sourceHash, base = 0) {
1724
+ if (!range) return undefined;
1725
+ const source = range.start && range.end ? range : { start: range, end: range.end ?? range };
1726
+ return {
1727
+ sourceId: sourceHash,
1728
+ path: sourcePath,
1729
+ startLine: Number(source.start?.line ?? source.startLine ?? 0) + (base === 0 ? 1 : 0),
1730
+ startColumn: Number(source.start?.character ?? source.startColumn ?? 0) + (base === 0 ? 1 : 0),
1731
+ endLine: Number(source.end?.line ?? source.endLine ?? source.start?.line ?? 0) + (base === 0 ? 1 : 0),
1732
+ endColumn: Number(source.end?.character ?? source.endColumn ?? source.start?.character ?? 0) + (base === 0 ? 1 : 0)
1733
+ };
1734
+ }
1735
+
1736
+ function normalizeExternalSpan(value, sourcePath, sourceHash) {
1737
+ if (!value) return undefined;
1738
+ if (Array.isArray(value)) {
1739
+ return spanFromScipOccurrence({ range: value }, sourcePath, sourceHash);
1740
+ }
1741
+ if (value.start || value.end || value.line !== undefined) return spanFromLspRange(value, sourcePath, sourceHash, 0);
1742
+ if (value.startLine !== undefined || value.start_line !== undefined) {
1743
+ return {
1744
+ sourceId: value.sourceId ?? sourceHash,
1745
+ path: value.path ?? sourcePath,
1746
+ start: value.start,
1747
+ end: value.end,
1748
+ startLine: Number(value.startLine ?? value.start_line),
1749
+ startColumn: value.startColumn ?? value.start_character,
1750
+ endLine: value.endLine ?? value.end_line,
1751
+ endColumn: value.endColumn ?? value.end_character
1752
+ };
1753
+ }
1754
+ return undefined;
1755
+ }
1756
+
1757
+ function uriToPath(uri) {
1758
+ if (typeof uri !== 'string') return undefined;
1759
+ if (uri.startsWith('file://')) {
1760
+ try {
1761
+ return decodeURIComponent(new URL(uri).pathname);
1762
+ } catch {
1763
+ return uri.replace(/^file:\/\//, '');
1764
+ }
1765
+ }
1766
+ return uri;
1767
+ }
1768
+
547
1769
  export function projectFrontierAst(document, target = 'typescript', options = {}) {
548
1770
  const normalized = normalizeCompileTarget(target);
549
1771
  const projector = projectors[normalized];
@@ -2801,10 +4023,23 @@ function scanJavaScriptLike(input) {
2801
4023
  const declarations = [];
2802
4024
  let currentClass;
2803
4025
  let classDepth = 0;
4026
+ let currentObject;
4027
+ const lexicalState = { inBlockComment: false, inTemplateString: false };
2804
4028
  for (const { line, number } of sourceLines(input.sourceText)) {
2805
- const trimmed = line.trim();
4029
+ const scanLine = jsDeclarationScanLine(line, lexicalState);
4030
+ const trimmed = scanLine.trim();
4031
+ if (!trimmed || jsCommentOnlyLine(trimmed)) continue;
2806
4032
  const declarationLine = trimmed.replace(/^(?:export\s+)?(?:declare\s+)?/, '');
2807
4033
  let match;
4034
+ if (currentObject) {
4035
+ const routeRecord = jsRouteRecordDeclaration(input, number, trimmed, currentObject);
4036
+ if (routeRecord) {
4037
+ declarations.push(routeRecord);
4038
+ } else {
4039
+ const property = jsObjectPropertyDeclaration(input, number, trimmed, currentObject);
4040
+ if (property) declarations.push(property);
4041
+ }
4042
+ }
2808
4043
  if ((match = trimmed.match(/^import\s+(?:.+?\s+from\s+)?['"]([^'"]+)['"]/))) {
2809
4044
  declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
2810
4045
  } else if ((match = trimmed.match(/^import\s*\(\s*['"]([^'"]+)['"]\s*\)/))) {
@@ -2832,11 +4067,20 @@ function scanJavaScriptLike(input) {
2832
4067
  } else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
2833
4068
  declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
2834
4069
  } else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b/))) {
2835
- declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', 'variable', match[1], {}, false));
4070
+ const initializerKind = jsInitializerKind(declarationLine);
4071
+ const regionKind = jsRegionKindForDeclarationName(match[1], declarationLine);
4072
+ declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', jsVariableSymbolKind(regionKind, initializerKind), match[1], {
4073
+ initializerKind
4074
+ }, jsVariableHasBody(initializerKind, declarationLine), {
4075
+ regionKind,
4076
+ metadata: { initializerKind }
4077
+ }));
4078
+ currentObject = jsObjectRegionContext(match[1], declarationLine, number, regionKind);
2836
4079
  } else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?function\*?\s*\(([^)]*)\)/))) {
2837
4080
  declarations.push(nativeDeclaration(input, number, 'CommonJsFunctionExport', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
2838
4081
  } else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=/))) {
2839
- declarations.push(nativeDeclaration(input, number, 'CommonJsExport', 'variable', match[1], { export: 'commonjs' }, false));
4082
+ const regionKind = jsRegionKindForDeclarationName(match[1], trimmed);
4083
+ declarations.push(nativeDeclaration(input, number, 'CommonJsExport', 'variable', match[1], { export: 'commonjs' }, false, { regionKind }));
2840
4084
  } else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|async|override|readonly|abstract|accessor|get|set)\s+)*(?:async\s+)?(?:get\s+|set\s+)?([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*(?::\s*[^={]+)?(?:\{|=>|$)/)) && !jsControlKeyword(match[1])) {
2841
4085
  declarations.push(nativeDeclaration(input, number, 'MethodDefinition', 'method', `${currentClass}.${match[1]}`, {
2842
4086
  methodName: match[1],
@@ -2857,10 +4101,224 @@ function scanJavaScriptLike(input) {
2857
4101
  classDepth = 0;
2858
4102
  }
2859
4103
  }
4104
+ if (currentObject) {
4105
+ if (number !== currentObject.startLine) currentObject.depth += jsContainerDelta(trimmed);
4106
+ if (currentObject.depth <= 0) currentObject = undefined;
4107
+ }
2860
4108
  }
2861
4109
  return declarations;
2862
4110
  }
2863
4111
 
4112
+ function jsCommentOnlyLine(trimmed) {
4113
+ return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*');
4114
+ }
4115
+
4116
+ function jsDeclarationScanLine(line, state) {
4117
+ let text = String(line ?? '');
4118
+ if (state.inTemplateString) {
4119
+ const close = findUnescapedBacktick(text, 0);
4120
+ if (close < 0) return '';
4121
+ text = text.slice(close + 1);
4122
+ state.inTemplateString = false;
4123
+ }
4124
+ if (state.inBlockComment) {
4125
+ const close = text.indexOf('*/');
4126
+ if (close < 0) return '';
4127
+ text = text.slice(close + 2);
4128
+ state.inBlockComment = false;
4129
+ }
4130
+ let output = '';
4131
+ let quote;
4132
+ let escaped = false;
4133
+ for (let index = 0; index < text.length; index += 1) {
4134
+ const char = text[index];
4135
+ const next = text[index + 1];
4136
+ if (quote) {
4137
+ output += char;
4138
+ if (escaped) {
4139
+ escaped = false;
4140
+ } else if (char === '\\') {
4141
+ escaped = true;
4142
+ } else if (char === quote) {
4143
+ quote = undefined;
4144
+ }
4145
+ continue;
4146
+ }
4147
+ if (char === '/' && next === '/') break;
4148
+ if (char === '/' && next === '*') {
4149
+ const close = text.indexOf('*/', index + 2);
4150
+ if (close < 0) {
4151
+ state.inBlockComment = true;
4152
+ break;
4153
+ }
4154
+ index = close + 1;
4155
+ continue;
4156
+ }
4157
+ if (char === '\'' || char === '"') {
4158
+ quote = char;
4159
+ output += char;
4160
+ continue;
4161
+ }
4162
+ if (char === '`') {
4163
+ const close = findUnescapedBacktick(text, index + 1);
4164
+ if (close < 0) {
4165
+ state.inTemplateString = true;
4166
+ output += '``';
4167
+ break;
4168
+ }
4169
+ output += text.slice(index, close + 1);
4170
+ index = close;
4171
+ continue;
4172
+ }
4173
+ output += char;
4174
+ }
4175
+ return output;
4176
+ }
4177
+
4178
+ function findUnescapedBacktick(text, startIndex) {
4179
+ let escaped = false;
4180
+ for (let index = startIndex; index < text.length; index += 1) {
4181
+ const char = text[index];
4182
+ if (escaped) {
4183
+ escaped = false;
4184
+ } else if (char === '\\') {
4185
+ escaped = true;
4186
+ } else if (char === '`') {
4187
+ return index;
4188
+ }
4189
+ }
4190
+ return -1;
4191
+ }
4192
+
4193
+ function jsObjectRegionContext(name, declarationLine, lineNumber, regionKind) {
4194
+ const initializerKind = jsInitializerKind(declarationLine);
4195
+ if (initializerKind !== 'object' && initializerKind !== 'array') return undefined;
4196
+ const depth = jsContainerDelta(declarationLine);
4197
+ if (depth <= 0) return undefined;
4198
+ return {
4199
+ name,
4200
+ regionKind: regionKind ?? jsRegionKindForDeclarationName(name, declarationLine),
4201
+ initializerKind,
4202
+ depth,
4203
+ startLine: lineNumber
4204
+ };
4205
+ }
4206
+
4207
+ function jsInitializerKind(line) {
4208
+ const initializer = String(line ?? '').split('=').slice(1).join('=').trim();
4209
+ if (!initializer) return 'unknown';
4210
+ if (/^(?:async\s+)?function\b/.test(initializer) || /=>/.test(initializer)) return 'function';
4211
+ if (initializer.startsWith('{')) return 'object';
4212
+ if (initializer.startsWith('[')) return 'array';
4213
+ if (/^new\s+/.test(initializer)) return 'instance';
4214
+ if (/^['"`]/.test(initializer)) return 'string';
4215
+ if (/^(?:true|false)\b/.test(initializer)) return 'boolean';
4216
+ if (/^[0-9]/.test(initializer)) return 'number';
4217
+ return 'expression';
4218
+ }
4219
+
4220
+ function jsVariableHasBody(initializerKind, declarationLine) {
4221
+ return initializerKind === 'function'
4222
+ || initializerKind === 'object'
4223
+ || initializerKind === 'array'
4224
+ || /\{/.test(String(declarationLine ?? ''));
4225
+ }
4226
+
4227
+ function jsVariableSymbolKind(regionKind, initializerKind) {
4228
+ if (regionKind === 'route') return 'route';
4229
+ if (initializerKind === 'function') return 'function';
4230
+ return 'variable';
4231
+ }
4232
+
4233
+ function jsRegionKindForDeclarationName(name, source = '') {
4234
+ const raw = `${name ?? ''} ${source ?? ''}`;
4235
+ const text = raw.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
4236
+ const compact = raw.toLowerCase();
4237
+ if (/\b(routes?|router|screens?|pages?|navigation|navitems?|menuitems?)\b/.test(text) || /(route|router|screen|page|navigation|navitem|menuitem)/.test(compact)) return 'route';
4238
+ if (/\b(config|settings|options|flags?|schema|manifest|registry|catalog)\b/.test(text) || /(config|settings|options|flags|schema|manifest|registry|catalog)/.test(compact)) return 'config';
4239
+ if (/\b(content|copy|docs?|legal|messages?|strings?|i18n|locale|translations?)\b/.test(text) || /(content|copy|docs|legal|messages|strings|i18n|locale|translations)/.test(compact)) return 'content';
4240
+ return undefined;
4241
+ }
4242
+
4243
+ function jsObjectPropertyDeclaration(input, lineNumber, trimmed, context) {
4244
+ if (/^[}\])]/.test(trimmed) || trimmed.startsWith('...')) return undefined;
4245
+ const methodMatch = trimmed.match(/^(?:(?:async|get|set)\s+)?(['"]?)([A-Za-z_$][\w$-]*)\1\s*\(([^)]*)\)\s*(?:[:\w\s<>\[\]]*)?(?:\{|=>|,|$)/);
4246
+ if (methodMatch && !jsControlKeyword(methodMatch[2])) {
4247
+ const name = `${context.name}.${methodMatch[2]}`;
4248
+ return nativeDeclaration(input, lineNumber, 'ObjectMethod', 'function', name, {
4249
+ owner: context.name,
4250
+ propertyName: methodMatch[2],
4251
+ parameters: splitParameters(methodMatch[3])
4252
+ }, true, {
4253
+ regionKind: jsPropertyRegionKind(context, methodMatch[2], 'function'),
4254
+ metadata: { owner: context.name, propertyName: methodMatch[2], initializerKind: 'function' }
4255
+ });
4256
+ }
4257
+ const propertyMatch = trimmed.match(/^(?:(['"])([^'"]+)\1|([A-Za-z_$][\w$-]*))\s*:\s*(.+?)(?:,)?$/);
4258
+ if (!propertyMatch) return undefined;
4259
+ const propertyName = propertyMatch[2] ?? propertyMatch[3];
4260
+ if (!propertyName || jsControlKeyword(propertyName)) return undefined;
4261
+ const value = propertyMatch[4].trim();
4262
+ const initializerKind = jsPropertyInitializerKind(value);
4263
+ const functionLike = initializerKind === 'function';
4264
+ const name = `${context.name}.${propertyName}`;
4265
+ return nativeDeclaration(input, lineNumber, functionLike ? 'ObjectFunctionProperty' : 'ObjectProperty', functionLike ? 'function' : 'property', name, {
4266
+ owner: context.name,
4267
+ propertyName,
4268
+ initializerKind
4269
+ }, functionLike || initializerKind === 'object' || initializerKind === 'array', {
4270
+ regionKind: jsPropertyRegionKind(context, propertyName, value),
4271
+ metadata: {
4272
+ owner: context.name,
4273
+ propertyName,
4274
+ initializerKind
4275
+ }
4276
+ });
4277
+ }
4278
+
4279
+ function jsRouteRecordDeclaration(input, lineNumber, trimmed, context) {
4280
+ if (context.regionKind !== 'route') return undefined;
4281
+ const match = trimmed.match(/\b(?:path|route|href|url)\s*:\s*(['"`])([^'"`]+)\1/);
4282
+ if (!match) return undefined;
4283
+ const routePath = match[2];
4284
+ return nativeDeclaration(input, lineNumber, 'RouteRecord', 'route', `${context.name}.${routePath}`, {
4285
+ owner: context.name,
4286
+ routePath
4287
+ }, true, {
4288
+ regionKind: 'route',
4289
+ metadata: { owner: context.name, routePath, initializerKind: 'object' }
4290
+ });
4291
+ }
4292
+
4293
+ function jsPropertyInitializerKind(value) {
4294
+ const text = String(value ?? '').trim();
4295
+ if (/^(?:async\s*)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(text)) return 'function';
4296
+ if (text.startsWith('{')) return 'object';
4297
+ if (text.startsWith('[')) return 'array';
4298
+ if (/^['"`]/.test(text)) return 'string';
4299
+ if (/^(?:true|false)\b/.test(text)) return 'boolean';
4300
+ if (/^[0-9]/.test(text)) return 'number';
4301
+ return 'expression';
4302
+ }
4303
+
4304
+ function jsPropertyRegionKind(context, propertyName, value) {
4305
+ const named = jsRegionKindForDeclarationName(propertyName, value);
4306
+ if (named) return named;
4307
+ if (context.regionKind === 'route') return 'route';
4308
+ if (context.regionKind === 'content') return 'content';
4309
+ if (context.regionKind === 'config') return 'config';
4310
+ return 'property';
4311
+ }
4312
+
4313
+ function jsContainerDelta(source) {
4314
+ let delta = 0;
4315
+ for (const char of String(source ?? '')) {
4316
+ if (char === '{' || char === '[') delta += 1;
4317
+ if (char === '}' || char === ']') delta -= 1;
4318
+ }
4319
+ return delta;
4320
+ }
4321
+
2864
4322
  function scanPython(input) {
2865
4323
  const declarations = [];
2866
4324
  for (const { line, number } of sourceLines(input.sourceText)) {
@@ -3560,7 +5018,7 @@ function rMetaName(source) {
3560
5018
  return match?.[1] ?? 'dynamic';
3561
5019
  }
3562
5020
 
3563
- function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false) {
5021
+ function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false, options = {}) {
3564
5022
  const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
3565
5023
  return {
3566
5024
  nodeId,
@@ -3571,7 +5029,9 @@ function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fi
3571
5029
  symbolId: `symbol:${input.language}:${idFragment(name)}`,
3572
5030
  span: spanForLine(input, lineNumber),
3573
5031
  fields,
3574
- metadata: { scan: 'lightweight-declaration', hasBody },
5032
+ metadata: { scan: 'lightweight-declaration', hasBody, ...options.metadata },
5033
+ ...(options.regionKind ? { regionKind: options.regionKind } : {}),
5034
+ ...(options.role ? { role: options.role } : {}),
3575
5035
  ...(hasBody ? { loss: opaqueBodyLoss(input, lineNumber, nodeId, name) } : {})
3576
5036
  };
3577
5037
  }
@@ -5431,6 +6891,8 @@ function semanticPatchHintForRegion(region, readiness, options = {}) {
5431
6891
 
5432
6892
  function semanticRegionKindForDeclaration(declaration) {
5433
6893
  if (declaration.role === 'import' || declaration.importPath) return 'import';
6894
+ if (declaration.regionKind) return normalizeNativeImportRegionKind(declaration.regionKind);
6895
+ if (declaration.metadata?.ownershipRegionKind) return normalizeNativeImportRegionKind(declaration.metadata.ownershipRegionKind);
5434
6896
  const kind = declaration.symbolKind ?? declaration.kind ?? declaration.nativeNode?.kind;
5435
6897
  if (semanticKindIsType(kind)) return 'type';
5436
6898
  if (semanticKindCanOwnBody(kind, declaration.span ?? declaration.nativeNode?.span)) return 'body';