@shapeshift-labs/frontier-lang-compiler 0.2.6 → 0.2.8

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
@@ -4,11 +4,13 @@ import {
4
4
  createNativeAstRecord,
5
5
  createPatch,
6
6
  createSemanticIndexRecord,
7
+ createSourceMapRecord,
7
8
  createUniversalAstEnvelope,
8
9
  hashDocumentBase,
9
10
  hashSemanticValue,
10
11
  nativeSourceNode,
11
12
  stableUniversalAstJson,
13
+ validateSourceMapRecord,
12
14
  validateUniversalAstEnvelope
13
15
  } from '@shapeshift-labs/frontier-lang-kernel';
14
16
  import { parseFrontierFile, parseFrontierSource } from '@shapeshift-labs/frontier-lang-parser';
@@ -51,6 +53,56 @@ const canonicalTargets = Object.freeze({
51
53
  h: 'c'
52
54
  });
53
55
 
56
+ const lossSeverityRank = Object.freeze({
57
+ none: 0,
58
+ info: 1,
59
+ warning: 2,
60
+ error: 3
61
+ });
62
+
63
+ const semanticMergeReadinessRank = Object.freeze({
64
+ ready: 0,
65
+ 'ready-with-losses': 1,
66
+ 'needs-review': 2,
67
+ blocked: 3
68
+ });
69
+
70
+ export const NativeImportTaxonomyKinds = Object.freeze([
71
+ 'exactAstImport',
72
+ 'declarationsOnly',
73
+ 'opaqueBodies',
74
+ 'macroExpansion',
75
+ 'preprocessor',
76
+ 'metaprogramming',
77
+ 'generatedCode',
78
+ 'sourcePreservation',
79
+ 'parserDiagnostics',
80
+ 'unsupportedSyntax',
81
+ 'partialSemanticIndex',
82
+ 'sourceMapApproximation'
83
+ ]);
84
+
85
+ export const NativeImportLossKinds = Object.freeze([
86
+ 'declarationOnlyCoverage',
87
+ 'opaqueNative',
88
+ 'macroExpansion',
89
+ 'preprocessor',
90
+ 'metaprogramming',
91
+ 'generatedCode',
92
+ 'sourcePreservation',
93
+ 'parserDiagnostic',
94
+ 'unsupportedSyntax',
95
+ 'partialSemanticIndex',
96
+ 'sourceMapApproximation'
97
+ ]);
98
+
99
+ export const NativeImportReadinessBySeverity = Object.freeze({
100
+ none: 'ready',
101
+ info: 'ready-with-losses',
102
+ warning: 'needs-review',
103
+ error: 'blocked'
104
+ });
105
+
54
106
  export function normalizeCompileTarget(target) {
55
107
  const normalized = String(target ?? 'typescript').toLowerCase();
56
108
  const canonical = canonicalTargets[normalized] ?? normalized;
@@ -135,6 +187,187 @@ export function resolveCapabilityAdapters(document, target = 'typescript', optio
135
187
  });
136
188
  }
137
189
 
190
+ export function summarizeNativeImportLosses(losses = [], options = {}) {
191
+ const normalizedLosses = normalizeNativeLossRecords(losses);
192
+ const bySeverity = { info: 0, warning: 0, error: 0 };
193
+ const byKind = {};
194
+ const blockingLossIds = [];
195
+ const reviewLossIds = [];
196
+ const informationalLossIds = [];
197
+ let highestSeverity = 'none';
198
+
199
+ for (const loss of normalizedLosses) {
200
+ bySeverity[loss.severity] += 1;
201
+ byKind[loss.kind] = (byKind[loss.kind] ?? 0) + 1;
202
+ if (lossSeverityRank[loss.severity] > lossSeverityRank[highestSeverity]) {
203
+ highestSeverity = loss.severity;
204
+ }
205
+ if (loss.severity === 'error') blockingLossIds.push(loss.id);
206
+ else if (loss.severity === 'warning') reviewLossIds.push(loss.id);
207
+ else informationalLossIds.push(loss.id);
208
+ }
209
+
210
+ const failedEvidenceIds = (options.evidence ?? [])
211
+ .filter((record) => record?.status === 'failed')
212
+ .map((record) => record.id)
213
+ .filter(Boolean);
214
+ const exactAst = Boolean(options.exactAst) && normalizedLosses.length === 0;
215
+ const categories = uniqueStrings([
216
+ ...(exactAst ? ['exactAstImport'] : []),
217
+ ...normalizedLosses.map((loss) => nativeImportCategoryForLossKind(loss.kind))
218
+ ]);
219
+ const semanticMergeReadiness = failedEvidenceIds.length
220
+ ? 'blocked'
221
+ : NativeImportReadinessBySeverity[highestSeverity];
222
+ const readinessReasons = nativeImportReadinessReasons({
223
+ exactAst,
224
+ failedEvidenceIds,
225
+ blockingLossIds,
226
+ reviewLossIds,
227
+ informationalLossIds
228
+ });
229
+
230
+ return {
231
+ total: normalizedLosses.length,
232
+ hasLosses: normalizedLosses.length > 0,
233
+ exactAst,
234
+ highestSeverity,
235
+ semanticMergeReadiness,
236
+ readinessReasons,
237
+ categories,
238
+ bySeverity,
239
+ byKind,
240
+ blockingLossIds,
241
+ reviewLossIds,
242
+ informationalLossIds,
243
+ failedEvidenceIds,
244
+ parser: options.parser,
245
+ scanKind: options.scanKind,
246
+ semanticStatus: options.semanticStatus
247
+ };
248
+ }
249
+
250
+ export function classifyNativeImportReadiness(losses = [], options = {}) {
251
+ const summary = summarizeNativeImportLosses(losses, options);
252
+ return {
253
+ readiness: summary.semanticMergeReadiness,
254
+ reasons: summary.readinessReasons,
255
+ summary
256
+ };
257
+ }
258
+
259
+ export function createEstreeNativeImporterAdapter(options = {}) {
260
+ return createJavaScriptSyntaxImporterAdapter({
261
+ id: 'frontier.estree-native-importer',
262
+ language: 'javascript',
263
+ parser: 'estree',
264
+ supportedExtensions: ['.js', '.mjs', '.cjs', '.jsx'],
265
+ astFormat: 'estree',
266
+ ...options
267
+ });
268
+ }
269
+
270
+ export function createBabelNativeImporterAdapter(options = {}) {
271
+ return createJavaScriptSyntaxImporterAdapter({
272
+ id: 'frontier.babel-native-importer',
273
+ language: 'javascript',
274
+ parser: 'babel',
275
+ supportedExtensions: ['.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx'],
276
+ astFormat: 'babel',
277
+ defaultParserOptions: {
278
+ errorRecovery: true,
279
+ ranges: true,
280
+ sourceType: 'unambiguous',
281
+ plugins: ['typescript', 'jsx']
282
+ },
283
+ ...options
284
+ });
285
+ }
286
+
287
+ export function createTypeScriptCompilerNativeImporterAdapter(options = {}) {
288
+ return {
289
+ id: options.id ?? 'frontier.typescript-compiler-native-importer',
290
+ language: options.language ?? 'typescript',
291
+ parser: options.parser ?? 'typescript-compiler-api',
292
+ version: options.version,
293
+ capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
294
+ supportedExtensions: options.supportedExtensions ?? ['.ts', '.tsx', '.js', '.jsx'],
295
+ diagnostics: options.diagnostics,
296
+ parse(input) {
297
+ const ts = options.typescript ?? options.ts ?? input.options?.typescript ?? input.options?.ts;
298
+ const sourceFile = input.options?.sourceFile ?? input.options?.ast ?? options.sourceFile ?? createTypeScriptSourceFile(ts, input, options);
299
+ if (!sourceFile) {
300
+ return missingInjectedParserResult(input, {
301
+ parser: options.parser ?? 'typescript-compiler-api',
302
+ adapterId: options.id ?? 'frontier.typescript-compiler-native-importer',
303
+ message: 'createTypeScriptCompilerNativeImporterAdapter requires an injected TypeScript module, createSourceFile function, sourceFile, or adapterOptions.sourceFile.'
304
+ });
305
+ }
306
+ return createNativeImportFromTypeScriptAst(sourceFile, input, {
307
+ parser: options.parser ?? 'typescript-compiler-api',
308
+ astFormat: 'typescript-compiler-api',
309
+ ts,
310
+ maxNodes: options.maxNodes,
311
+ includeTokens: options.includeTokens
312
+ });
313
+ }
314
+ };
315
+ }
316
+
317
+ export function createTreeSitterNativeImporterAdapter(options = {}) {
318
+ return {
319
+ id: options.id ?? `frontier.tree-sitter-${idFragment(options.language ?? 'source')}-native-importer`,
320
+ language: options.language ?? 'source',
321
+ parser: options.parserName ?? options.parser ?? 'tree-sitter',
322
+ version: options.version,
323
+ capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
324
+ supportedExtensions: options.supportedExtensions ?? [],
325
+ diagnostics: options.diagnostics,
326
+ parse(input) {
327
+ const tree = input.options?.tree ?? options.tree ?? parseTreeSitterSource(input, options);
328
+ const root = tree?.rootNode ?? tree;
329
+ if (!root) {
330
+ return missingInjectedParserResult(input, {
331
+ parser: options.parserName ?? options.parser ?? 'tree-sitter',
332
+ adapterId: options.id ?? `frontier.tree-sitter-${idFragment(options.language ?? input.language)}-native-importer`,
333
+ message: 'createTreeSitterNativeImporterAdapter requires an injected tree-sitter parser/tree or adapterOptions.tree.'
334
+ });
335
+ }
336
+ return createNativeImportFromTreeSitter(root, input, {
337
+ parser: options.parserName ?? options.parser ?? 'tree-sitter',
338
+ astFormat: 'tree-sitter',
339
+ maxNodes: options.maxNodes
340
+ });
341
+ }
342
+ };
343
+ }
344
+
345
+ export async function importNativeProject(input = {}) {
346
+ const sources = input.sources ?? [];
347
+ const adapters = input.adapters ?? [];
348
+ const imports = [];
349
+ for (const [index, source] of sources.entries()) {
350
+ const adapter = source.adapter && typeof source.adapter === 'object'
351
+ ? source.adapter
352
+ : resolveNativeProjectAdapter(source, adapters, input);
353
+ if (adapter) {
354
+ imports.push(await runNativeImporterAdapter(adapter, {
355
+ ...source,
356
+ adapterOptions: source.adapterOptions ?? input.adapterOptions,
357
+ adapterMetadata: {
358
+ projectImportId: input.id,
359
+ sourceIndex: index,
360
+ ...input.adapterMetadata,
361
+ ...source.adapterMetadata
362
+ }
363
+ }));
364
+ } else {
365
+ imports.push(importNativeSource(source));
366
+ }
367
+ }
368
+ return createNativeProjectImportResult(input, imports);
369
+ }
370
+
138
371
  export async function runNativeImporterAdapter(adapter, input = {}) {
139
372
  const summary = normalizeNativeImporterAdapter(adapter);
140
373
  const language = input.language ?? summary.language;
@@ -259,6 +492,8 @@ export function importNativeSource(input) {
259
492
  if (!language) throw new Error('importNativeSource requires a language or nativeAst.language');
260
493
  const sourcePath = input.sourcePath ?? input.nativeAst?.sourcePath;
261
494
  const sourceHash = input.sourceHash ?? input.nativeAst?.sourceHash ?? (input.sourceText ? hashSemanticValue(input.sourceText) : hashSemanticValue(input.nativeAst?.nodes ?? input.nativeAst ?? {}));
495
+ const targetPath = input.targetPath ?? input.target?.emitPath;
496
+ const targetHash = input.targetHash;
262
497
  const importIdPart = idFragment(input.id ?? input.nativeSourceId ?? sourcePath ?? language);
263
498
  const lightweight = !input.nativeAst && !input.nodes && input.sourceText
264
499
  ? createLightweightNativeImport({
@@ -294,7 +529,9 @@ export function importNativeSource(input) {
294
529
  }
295
530
  });
296
531
  const frontierNodeIds = input.frontierNodeIds ?? input.semanticNodes?.map((node) => node.id) ?? [];
297
- const losses = input.losses ?? nativeAst.losses ?? lightweight?.losses ?? [];
532
+ const semanticNodes = input.semanticNodes ?? [];
533
+ const semanticStatus = input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only');
534
+ const losses = normalizeNativeLossRecords(input.losses ?? nativeAst.losses ?? lightweight?.losses ?? []);
298
535
  const nativeSource = nativeSourceNode({
299
536
  id: input.nativeSourceId ?? `native_source_${importIdPart}`,
300
537
  name: input.name ?? sourcePath?.split(/[\\/]/).filter(Boolean).at(-1) ?? `${language}NativeSource`,
@@ -309,12 +546,11 @@ export function importNativeSource(input) {
309
546
  losses,
310
547
  target: input.target,
311
548
  metadata: {
312
- semanticStatus: input.semanticStatus ?? (input.semanticNodes?.length ? 'mapped' : 'native-only'),
549
+ semanticStatus,
313
550
  mappings: input.mappings ?? [],
314
551
  ...input.nativeSourceMetadata
315
552
  }
316
553
  });
317
- const semanticNodes = input.semanticNodes ?? [];
318
554
  const document = createDocument({
319
555
  id: input.documentId ?? `document_${importIdPart}`,
320
556
  name: input.documentName ?? nativeSource.name,
@@ -322,11 +558,11 @@ export function importNativeSource(input) {
322
558
  rootIds: input.rootIds,
323
559
  metadata: {
324
560
  sourceLanguage: language,
325
- semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
561
+ semanticStatus,
326
562
  ...input.documentMetadata
327
563
  }
328
564
  });
329
- const evidence = input.evidence ?? [{
565
+ const baseEvidence = input.evidence ?? [{
330
566
  id: input.evidenceId ?? `evidence_${importIdPart}_import`,
331
567
  kind: 'import',
332
568
  status: losses.some((loss) => loss.severity === 'error') ? 'failed' : 'passed',
@@ -335,21 +571,86 @@ export function importNativeSource(input) {
335
571
  metadata: {
336
572
  parser: nativeAst.parser,
337
573
  sourcePath,
338
- semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only')
574
+ semanticStatus
339
575
  }
340
576
  }];
577
+ const lossSummary = summarizeNativeImportLosses(losses, {
578
+ exactAst: Boolean(input.nativeAst || input.nodes),
579
+ evidence: baseEvidence,
580
+ parser: nativeAst.parser,
581
+ scanKind: lightweight?.metadata?.scanKind,
582
+ semanticStatus
583
+ });
584
+ const evidence = attachNativeImportLossSummary(baseEvidence, lossSummary);
341
585
  const semanticIndex = input.semanticIndex ?? lightweight?.semanticIndex;
586
+ const sourceMapMappings = normalizeSourceMapMappings(
587
+ input.mappings ?? lightweight?.mappings ?? inferSourceMapMappings({
588
+ semanticIndex,
589
+ nativeAst,
590
+ nativeSource,
591
+ evidence
592
+ }),
593
+ {
594
+ semanticIndex,
595
+ nativeAst,
596
+ nativeSource,
597
+ evidence,
598
+ losses,
599
+ target: input.target,
600
+ targetPath,
601
+ targetHash
602
+ }
603
+ );
604
+ const inferredTargetPath = targetPath ?? commonGeneratedTargetPath(sourceMapMappings);
605
+ const inferredSourceMaps = sourceMapMappings.length
606
+ ? [createSourceMapRecord({
607
+ id: input.sourceMapId ?? `source_map_${importIdPart}`,
608
+ sourcePath,
609
+ sourceHash,
610
+ target: input.target,
611
+ targetPath: inferredTargetPath,
612
+ targetHash,
613
+ semanticIndexId: semanticIndex?.id,
614
+ nativeAstId: nativeAst.id,
615
+ nativeSourceId: nativeSource.id,
616
+ mappings: sourceMapMappings,
617
+ evidence,
618
+ metadata: {
619
+ sourceLanguage: language,
620
+ parser: nativeAst.parser,
621
+ semanticStatus
622
+ }
623
+ })]
624
+ : [];
625
+ const sourceMaps = normalizeSourceMaps(input.sourceMaps ?? inferredSourceMaps, {
626
+ document,
627
+ nativeSources: [nativeSource],
628
+ nativeAst,
629
+ nativeSource,
630
+ semanticIndex,
631
+ evidence,
632
+ losses,
633
+ target: input.target,
634
+ targetPath: inferredTargetPath,
635
+ targetHash,
636
+ sourcePath,
637
+ sourceHash,
638
+ defaultSourceMapId: `source_map_${importIdPart}`
639
+ });
640
+ const resultSourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap.mappings ?? []);
342
641
  const universalAst = createUniversalAstEnvelope({
343
642
  id: input.universalAstId ?? `universal_ast_${importIdPart}`,
344
643
  document,
345
644
  nativeSources: [nativeSource],
346
645
  semanticIndex,
646
+ sourceMaps,
347
647
  losses,
348
648
  evidence,
349
649
  metadata: {
350
650
  sourceLanguage: language,
351
651
  sourcePath,
352
- semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
652
+ semanticStatus,
653
+ nativeImportLossSummary: lossSummary,
353
654
  ...input.universalAstMetadata
354
655
  }
355
656
  });
@@ -363,29 +664,40 @@ export function importNativeSource(input) {
363
664
  touches: [{ id: node.id, access: node.kind === 'nativeSource' ? 'evidence' : 'schema' }]
364
665
  })),
365
666
  evidence,
366
- metadata: { sourceLanguage: language, sourcePath, semanticIndexId: semanticIndex?.id, universalAstId: universalAst.id }
667
+ metadata: {
668
+ sourceLanguage: language,
669
+ sourcePath,
670
+ semanticIndexId: semanticIndex?.id,
671
+ universalAstId: universalAst.id,
672
+ sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
673
+ nativeImportLossSummary: lossSummary
674
+ }
675
+ });
676
+ const importResult = createImportResult({
677
+ id: input.id ?? `import_${importIdPart}`,
678
+ language,
679
+ sourcePath,
680
+ document,
681
+ patch,
682
+ nativeAst,
683
+ semanticIndex,
684
+ universalAst,
685
+ sourceMaps,
686
+ losses,
687
+ evidence,
688
+ metadata: {
689
+ nativeSourceId: nativeSource.id,
690
+ semanticIndexId: semanticIndex?.id,
691
+ universalAstId: universalAst.id,
692
+ sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
693
+ semanticStatus,
694
+ mappings: resultSourceMapMappings,
695
+ nativeImportLossSummary: lossSummary,
696
+ ...input.metadata
697
+ }
367
698
  });
368
699
  return {
369
- ...createImportResult({
370
- id: input.id ?? `import_${importIdPart}`,
371
- language,
372
- sourcePath,
373
- document,
374
- patch,
375
- nativeAst,
376
- semanticIndex,
377
- universalAst,
378
- losses,
379
- evidence,
380
- metadata: {
381
- nativeSourceId: nativeSource.id,
382
- semanticIndexId: semanticIndex?.id,
383
- universalAstId: universalAst.id,
384
- semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
385
- mappings: input.mappings ?? [],
386
- ...input.metadata
387
- }
388
- }),
700
+ ...withNativeImportReadiness(importResult, lossSummary),
389
701
  nativeSource
390
702
  };
391
703
  }
@@ -409,6 +721,8 @@ function createLightweightNativeImport(input) {
409
721
  const occurrences = [];
410
722
  const relations = [];
411
723
  const facts = [];
724
+ const mappings = [];
725
+ const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_lightweight_scan`;
412
726
 
413
727
  for (const declaration of declarations) {
414
728
  nodes[rootId].children.push(declaration.nodeId);
@@ -422,6 +736,7 @@ function createLightweightNativeImport(input) {
422
736
  metadata: declaration.metadata
423
737
  };
424
738
  if (declaration.symbolId) {
739
+ const occurrenceId = `occ_${idFragment(declaration.nodeId)}_def`;
425
740
  symbols.push({
426
741
  id: declaration.symbolId,
427
742
  scheme: 'frontier',
@@ -433,7 +748,7 @@ function createLightweightNativeImport(input) {
433
748
  definitionSpan: declaration.span
434
749
  });
435
750
  occurrences.push({
436
- id: `occ_${idFragment(declaration.nodeId)}_def`,
751
+ id: occurrenceId,
437
752
  documentId,
438
753
  symbolId: declaration.symbolId,
439
754
  role: declaration.role ?? 'definition',
@@ -452,9 +767,20 @@ function createLightweightNativeImport(input) {
452
767
  subjectId: declaration.symbolId,
453
768
  value: declaration.languageKind
454
769
  });
770
+ mappings.push({
771
+ id: `map_${idFragment(declaration.nodeId)}`,
772
+ nativeAstNodeId: declaration.nodeId,
773
+ semanticSymbolId: declaration.symbolId,
774
+ semanticOccurrenceId: occurrenceId,
775
+ sourceSpan: declaration.span,
776
+ evidenceIds: [evidenceId],
777
+ lossIds: declaration.loss ? [declaration.loss.id] : [],
778
+ precision: 'declaration'
779
+ });
455
780
  }
456
781
  if (declaration.loss) losses.push(declaration.loss);
457
782
  }
783
+ losses.push(...lightweightCoverageLosses(input, declarations));
458
784
 
459
785
  const semanticIndex = createSemanticIndexRecord({
460
786
  id: `index_${idFragment(input.sourcePath ?? input.language)}`,
@@ -469,7 +795,7 @@ function createLightweightNativeImport(input) {
469
795
  relations,
470
796
  facts,
471
797
  evidence: [{
472
- id: `evidence_${idFragment(input.sourcePath ?? input.language)}_lightweight_scan`,
798
+ id: evidenceId,
473
799
  kind: 'import',
474
800
  status: 'passed',
475
801
  path: input.sourcePath,
@@ -489,6 +815,7 @@ function createLightweightNativeImport(input) {
489
815
  nodes,
490
816
  losses,
491
817
  semanticIndex,
818
+ mappings,
492
819
  metadata: { parser, scanKind: 'lightweight-declaration-scan', declarationCount: declarations.length }
493
820
  };
494
821
  }
@@ -499,6 +826,23 @@ function scanNativeDeclarations(input) {
499
826
  if (language === 'python') return scanPython(input);
500
827
  if (language === 'rust') return scanRust(input);
501
828
  if (language === 'c' || language === 'cpp' || language === 'c++') return scanCLike(input);
829
+ if (language === 'java') return scanJava(input);
830
+ if (language === 'go') return scanGo(input);
831
+ if (language === 'swift') return scanSwift(input);
832
+ if (language === 'csharp' || language === 'c#') return scanCSharp(input);
833
+ if (language === 'php') return scanPhp(input);
834
+ if (language === 'ruby' || language === 'rb') return scanRuby(input);
835
+ if (language === 'kotlin' || language === 'kt') return scanKotlin(input);
836
+ if (language === 'scala' || language === 'sc') return scanScala(input);
837
+ if (language === 'dart') return scanDart(input);
838
+ if (language === 'lua') return scanLua(input);
839
+ if (language === 'shell' || language === 'sh' || language === 'bash' || language === 'zsh') return scanShell(input);
840
+ if (language === 'sql' || language === 'postgresql' || language === 'postgres' || language === 'mysql' || language === 'sqlite') return scanSql(input);
841
+ if (language === 'zig') return scanZig(input);
842
+ if (language === 'elixir' || language === 'ex' || language === 'exs') return scanElixir(input);
843
+ if (language === 'erlang' || language === 'erl' || language === 'hrl') return scanErlang(input);
844
+ if (language === 'haskell' || language === 'hs') return scanHaskell(input);
845
+ if (language === 'r') return scanR(input);
502
846
  return scanGenericDeclarations(input);
503
847
  }
504
848
 
@@ -588,116 +932,1057 @@ function scanCLike(input) {
588
932
  return declarations;
589
933
  }
590
934
 
591
- function scanGenericDeclarations(input) {
592
- return sourceLines(input.sourceText)
593
- .filter(({ line }) => /\b(function|class|struct|enum|trait|interface|def)\b/.test(line))
594
- .map(({ line, number }) => nativeDeclaration(input, number, 'NativeDeclaration', 'variable', idFragment(line.trim()).slice(0, 40), { source: line.trim() }, true));
935
+ function scanJava(input) {
936
+ const declarations = [];
937
+ for (const { line, number } of sourceLines(input.sourceText)) {
938
+ const trimmed = line.trim();
939
+ let match;
940
+ if ((match = trimmed.match(/^package\s+([A-Za-z_][\w.]*);/))) {
941
+ declarations.push(nativeDeclaration(input, number, 'PackageDeclaration', 'package', match[1], {}, false));
942
+ } else if ((match = trimmed.match(/^import\s+(?:static\s+)?([A-Za-z_][\w.*]*);/))) {
943
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'package'));
944
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|abstract|final|static|sealed|non-sealed)\s+)*(class|interface|enum|record|@interface)\s+([A-Za-z_$][\w$]*)/))) {
945
+ const kind = match[1] === '@interface' ? 'AnnotationDeclaration' : `${upperFirst(match[1])}Declaration`;
946
+ declarations.push(nativeDeclaration(input, number, kind, javaSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
947
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|abstract|final|static|synchronized|native)\s+)*(?:<[^>]+>\s+)?[A-Za-z_$][\w$<>\[\].?,\s]*\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*(?:throws\s+[^{]+)?(?:\{|;)?$/))) {
948
+ declarations.push(nativeDeclaration(input, number, 'MethodDeclaration', 'method', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
949
+ }
950
+ }
951
+ return declarations;
595
952
  }
596
953
 
597
- function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false) {
598
- const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
599
- return {
600
- nodeId,
601
- kind: languageKind,
602
- languageKind: `${input.language}.${languageKind}`,
603
- name,
604
- symbolKind,
605
- symbolId: `symbol:${input.language}:${idFragment(name)}`,
606
- span: spanForLine(input, lineNumber),
607
- fields,
608
- metadata: { scan: 'lightweight-declaration', hasBody },
609
- ...(hasBody ? { loss: opaqueBodyLoss(input, lineNumber, nodeId, name) } : {})
610
- };
954
+ function scanGo(input) {
955
+ const declarations = [];
956
+ for (const { line, number } of sourceLines(input.sourceText)) {
957
+ const trimmed = line.trim();
958
+ let match;
959
+ if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*)/))) {
960
+ declarations.push(nativeDeclaration(input, number, 'PackageClause', 'package', match[1], {}, false));
961
+ } else if ((match = trimmed.match(/^import\s+(?:[A-Za-z_]\w*\s+)?["']([^"']+)["']/))) {
962
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportSpec', 'package'));
963
+ } else if ((match = trimmed.match(/^type\s+([A-Za-z_]\w*)\s+(struct|interface)\b/))) {
964
+ declarations.push(nativeDeclaration(input, number, match[2] === 'struct' ? 'TypeSpecStruct' : 'TypeSpecInterface', 'type', match[1], {}, trimmed.includes('{')));
965
+ } else if ((match = trimmed.match(/^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
966
+ declarations.push(nativeDeclaration(input, number, 'FuncDecl', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
967
+ } else if ((match = trimmed.match(/^var\s+([A-Za-z_]\w*)\b/))) {
968
+ declarations.push(nativeDeclaration(input, number, 'VarDecl', 'variable', match[1], {}, false));
969
+ } else if ((match = trimmed.match(/^const\s+([A-Za-z_]\w*)\b/))) {
970
+ declarations.push(nativeDeclaration(input, number, 'ConstDecl', 'constant', match[1], {}, false));
971
+ }
972
+ }
973
+ return declarations;
611
974
  }
612
975
 
613
- function nativeImportDeclaration(input, lineNumber, importPath, languageKind, symbolKind) {
614
- const name = String(importPath);
615
- const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
616
- return {
617
- nodeId,
618
- kind: languageKind,
619
- languageKind: `${input.language}.${languageKind}`,
620
- name,
621
- symbolKind,
622
- symbolId: `symbol:${input.language}:import:${idFragment(name)}`,
623
- role: 'import',
624
- importPath: name,
625
- span: spanForLine(input, lineNumber),
626
- fields: { importPath: name },
627
- metadata: { scan: 'lightweight-import' }
628
- };
976
+ function scanSwift(input) {
977
+ const declarations = [];
978
+ for (const { line, number } of sourceLines(input.sourceText)) {
979
+ const trimmed = line.trim();
980
+ let match;
981
+ if ((match = trimmed.match(/^import\s+([A-Za-z_]\w*)/))) {
982
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDecl', 'module'));
983
+ } else if ((match = trimmed.match(/^(?:(?:public|private|fileprivate|internal|open|final)\s+)*(struct|class|enum|protocol|actor|extension)\s+([A-Za-z_]\w*)/))) {
984
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Decl`, swiftSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
985
+ } else if ((match = trimmed.match(/^(?:(?:public|private|fileprivate|internal|open|static|class|mutating)\s+)*func\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
986
+ declarations.push(nativeDeclaration(input, number, 'FunctionDecl', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
987
+ } else if ((match = trimmed.match(/^(?:let|var)\s+([A-Za-z_]\w*)\b/))) {
988
+ declarations.push(nativeDeclaration(input, number, 'ValueBindingDecl', 'variable', match[1], {}, false));
989
+ }
990
+ }
991
+ return declarations;
629
992
  }
630
993
 
631
- function nativeMacroLoss(input, lineNumber, source, kind, name = idFragment(source).slice(0, 40)) {
632
- const nodeId = `native_${kind}_${lineNumber}_${idFragment(name)}`;
633
- return {
634
- nodeId,
635
- kind: kind === 'preprocessor' ? 'PreprocessorDirective' : 'MacroInvocation',
636
- languageKind: `${input.language}.${kind}`,
637
- name,
638
- symbolKind: 'constant',
639
- symbolId: `symbol:${input.language}:${kind}:${idFragment(name)}`,
640
- span: spanForLine(input, lineNumber),
641
- fields: { source },
642
- metadata: { scan: 'lightweight-macro' },
643
- loss: {
644
- id: `loss_${idFragment(nodeId)}`,
645
- severity: 'warning',
646
- phase: 'read',
647
- sourceFormat: input.language,
648
- kind,
649
- message: `${input.language} ${kind} retained as native source; expansion is not evaluated by the lightweight importer.`,
650
- span: spanForLine(input, lineNumber),
651
- nodeId
994
+ function scanCSharp(input) {
995
+ const declarations = [];
996
+ for (const { line, number } of sourceLines(input.sourceText)) {
997
+ const trimmed = line.trim();
998
+ let match;
999
+ if ((match = trimmed.match(/^using\s+(?:static\s+)?([A-Za-z_][\w.]*)\s*;/))) {
1000
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'UsingDirective', 'namespace'));
1001
+ } else if ((match = trimmed.match(/^namespace\s+([A-Za-z_][\w.]*)/))) {
1002
+ declarations.push(nativeDeclaration(input, number, 'NamespaceDeclaration', 'namespace', match[1], {}, trimmed.includes('{')));
1003
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|abstract|sealed|static|partial|readonly)\s+)*(class|interface|struct|enum|record)\s+([A-Za-z_]\w*)/))) {
1004
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, csharpSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
1005
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|virtual|override|async|partial|sealed|abstract|extern)\s+)*[A-Za-z_][\w<>\[\].?,\s]*\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(?:\{|;)?$/))) {
1006
+ declarations.push(nativeDeclaration(input, number, 'MethodDeclaration', 'method', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
652
1007
  }
653
- };
1008
+ }
1009
+ return declarations;
654
1010
  }
655
1011
 
656
- function opaqueBodyLoss(input, lineNumber, nodeId, name) {
657
- return {
658
- id: `loss_${idFragment(nodeId)}_body`,
659
- severity: 'info',
660
- phase: 'read',
661
- sourceFormat: input.language,
662
- kind: 'opaqueNative',
663
- message: `Body for ${name} is retained as native source by the lightweight declaration importer.`,
664
- span: spanForLine(input, lineNumber),
665
- nodeId
666
- };
1012
+ function scanPhp(input) {
1013
+ const declarations = [];
1014
+ for (const { line, number } of sourceLines(input.sourceText)) {
1015
+ const trimmed = line.trim().replace(/^<\?php\s*/, '');
1016
+ let match;
1017
+ if ((match = trimmed.match(/^namespace\s+([A-Za-z_][\w\\]*)\s*;/))) {
1018
+ declarations.push(nativeDeclaration(input, number, 'NamespaceDefinition', 'namespace', match[1], {}, false));
1019
+ } else if ((match = trimmed.match(/^use\s+([A-Za-z_][\w\\]*)(?:\s+as\s+([A-Za-z_]\w*))?\s*;/))) {
1020
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'UseDeclaration', 'namespace'));
1021
+ } else if ((match = trimmed.match(/^(?:(?:abstract|final|readonly)\s+)*(class|interface|trait|enum)\s+([A-Za-z_]\w*)/))) {
1022
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, phpSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
1023
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|static|final|abstract)\s+)*function\s+&?\s*([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
1024
+ declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
1025
+ }
1026
+ }
1027
+ return declarations;
667
1028
  }
668
1029
 
669
- function sourceLines(sourceText) {
670
- return String(sourceText ?? '').split(/\r?\n/).map((line, index) => ({ line, number: index + 1 }));
1030
+ function scanRuby(input) {
1031
+ const declarations = [];
1032
+ for (const { line, number } of sourceLines(input.sourceText)) {
1033
+ const trimmed = line.trim();
1034
+ let match;
1035
+ if ((match = trimmed.match(/^(?:require|load)\s+['"]([^'"]+)['"]/))) {
1036
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'Require', 'module'));
1037
+ } else if ((match = trimmed.match(/^module\s+([A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)/))) {
1038
+ declarations.push(nativeDeclaration(input, number, 'Module', 'module', match[1], {}, true));
1039
+ } else if ((match = trimmed.match(/^class\s+([A-Za-z_]\w*(?:::[A-Za-z_]\w*)*)/))) {
1040
+ declarations.push(nativeDeclaration(input, number, 'Class', 'class', match[1], {}, true));
1041
+ } else if ((match = trimmed.match(/^def\s+(?:self\.)?([A-Za-z_]\w*[!?=]?)\s*(?:\(([^)]*)\)|([^#=]*))?/))) {
1042
+ declarations.push(nativeDeclaration(input, number, 'Def', 'method', match[1], { parameters: splitParameters(match[2] ?? match[3]) }, true));
1043
+ }
1044
+ }
1045
+ return declarations;
671
1046
  }
672
1047
 
673
- function spanForLine(input, lineNumber) {
674
- return {
675
- sourceId: input.sourceHash,
676
- path: input.sourcePath,
677
- startLine: lineNumber,
678
- endLine: lineNumber,
679
- startColumn: 1
680
- };
1048
+ function scanKotlin(input) {
1049
+ const declarations = [];
1050
+ for (const { line, number } of sourceLines(input.sourceText)) {
1051
+ const trimmed = line.trim();
1052
+ let match;
1053
+ if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
1054
+ declarations.push(nativeDeclaration(input, number, 'PackageHeader', 'package', match[1], {}, false));
1055
+ } else if ((match = trimmed.match(/^import\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*(?:\.\*)?)(?:\s+as\s+[A-Za-z_]\w*)?$/))) {
1056
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDirective', 'package'));
1057
+ } else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual|open|final|abstract|sealed|data|value)\s+)*(?:(enum|annotation)\s+)?(class|interface|object)\s+([A-Za-z_]\w*)/))) {
1058
+ declarations.push(nativeDeclaration(input, number, kotlinDeclarationKind(match[2], match[1]), kotlinSymbolKind(match[2], match[1]), match[3], {}, trimmed.includes('{')));
1059
+ } else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual|open|final|abstract|inline|tailrec|operator|infix|external|suspend|override)\s+)*fun\s+(?:<[^>]+>\s*)?(?:[A-Za-z_][\w.<>?]*\.)?([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
1060
+ declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=')));
1061
+ } else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual)\s+)*typealias\s+([A-Za-z_]\w*)\s*=/))) {
1062
+ declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
1063
+ } else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual|open|final|abstract|override|const|lateinit)\s+)*(?:val|var)\s+([A-Za-z_]\w*)\b/))) {
1064
+ declarations.push(nativeDeclaration(input, number, 'PropertyDeclaration', 'variable', match[1], {}, false));
1065
+ }
1066
+ }
1067
+ return declarations;
681
1068
  }
682
1069
 
683
- function splitParameters(raw) {
684
- return String(raw ?? '')
685
- .split(',')
686
- .map((part) => part.trim())
687
- .filter(Boolean);
1070
+ function scanScala(input) {
1071
+ const declarations = [];
1072
+ for (const { line, number } of sourceLines(input.sourceText)) {
1073
+ const trimmed = line.trim();
1074
+ let match;
1075
+ if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
1076
+ declarations.push(nativeDeclaration(input, number, 'PackageClause', 'package', match[1], {}, false));
1077
+ } else if ((match = trimmed.match(/^import\s+(.+?);?$/))) {
1078
+ declarations.push(nativeImportDeclaration(input, number, match[1].trim(), 'Import', 'package'));
1079
+ } else if ((match = trimmed.match(/^(?:(?:private|protected|final|sealed|abstract|case|implicit|lazy|override|inline|transparent|open)\s+)*(class|trait|object|enum)\s+([A-Za-z_]\w*)/))) {
1080
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Def`, scalaSymbolKind(match[1]), match[2], {}, trimmed.includes('{') || trimmed.includes(':')));
1081
+ } else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|override|inline)\s+)*def\s+([A-Za-z_]\w*)\s*(?:\[[^\]]+\])?\s*\(([^)]*)\)/))) {
1082
+ declarations.push(nativeDeclaration(input, number, 'DefDef', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=')));
1083
+ } else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|opaque)\s+)*type\s+([A-Za-z_]\w*)\b/))) {
1084
+ declarations.push(nativeDeclaration(input, number, 'TypeDef', 'type', match[1], {}, false));
1085
+ } else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|lazy|override|inline)\s+)*(?:val|var)\s+([A-Za-z_]\w*)\b/))) {
1086
+ declarations.push(nativeDeclaration(input, number, 'ValDef', 'variable', match[1], {}, false));
1087
+ }
1088
+ }
1089
+ return declarations;
688
1090
  }
689
1091
 
690
- export function createUniversalAstFromDocument(document, input = {}) {
691
- return createUniversalAstEnvelope({
692
- id: input.id ?? `universal_ast_${idFragment(document.id)}`,
693
- document,
694
- semanticIndex: input.semanticIndex,
695
- evidence: input.evidence ?? [],
696
- metadata: input.metadata
697
- });
1092
+ function scanDart(input) {
1093
+ const declarations = [];
1094
+ for (const { line, number } of sourceLines(input.sourceText)) {
1095
+ const trimmed = line.trim();
1096
+ let match;
1097
+ if ((match = trimmed.match(/^(?:import|export)\s+['"]([^'"]+)['"]/))) {
1098
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'UriBasedDirective', 'library'));
1099
+ } else if ((match = trimmed.match(/^part\s+['"]([^'"]+)['"]/))) {
1100
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'PartDirective', 'library'));
1101
+ } else if ((match = trimmed.match(/^(?:(?:abstract|base|final|interface|sealed)\s+)*(class|mixin|enum)\s+([A-Za-z_]\w*)/))) {
1102
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, dartSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
1103
+ } else if ((match = trimmed.match(/^extension\s+([A-Za-z_]\w*)\s+on\s+.+\{/))) {
1104
+ declarations.push(nativeDeclaration(input, number, 'ExtensionDeclaration', 'implementation', match[1], {}, true));
1105
+ } else if ((match = trimmed.match(/^typedef\s+([A-Za-z_]\w*)\b/))) {
1106
+ declarations.push(nativeDeclaration(input, number, 'TypeAlias', 'type', match[1], {}, false));
1107
+ } else if ((match = trimmed.match(/^(?:(?:external|static)\s+)*(?:[A-Za-z_]\w*(?:<[^>]+>)?\??|void)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(?:async\s*)?(?:\{|=>|;)/))) {
1108
+ declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=>')));
1109
+ } else if ((match = trimmed.match(/^(?:(?:static|external|late)\s+)*(?:const|final|var)\s+(?:[A-Za-z_]\w*(?:<[^>]+>)?\??\s+)?([A-Za-z_]\w*)\b/))) {
1110
+ declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', 'variable', match[1], {}, false));
1111
+ }
1112
+ }
1113
+ return declarations;
698
1114
  }
699
1115
 
700
- export function readUniversalAstJson(source) {
1116
+ function scanLua(input) {
1117
+ const declarations = [];
1118
+ for (const { line, number } of sourceLines(input.sourceText)) {
1119
+ const trimmed = line.trim();
1120
+ let match;
1121
+ if ((match = trimmed.match(/^(?:local\s+[A-Za-z_]\w*\s*=\s*)?require\s*\(?\s*['"]([^'"]+)['"]\s*\)?/))) {
1122
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'RequireCall', 'module'));
1123
+ } else if ((match = trimmed.match(/^(?:local\s+)?function\s+([A-Za-z_]\w*(?:[.:][A-Za-z_]\w*)*)\s*\(([^)]*)\)/))) {
1124
+ declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
1125
+ } else if ((match = trimmed.match(/^(?:local\s+)?([A-Za-z_]\w*(?:[.:][A-Za-z_]\w*)*)\s*=\s*function\s*\(([^)]*)\)/))) {
1126
+ declarations.push(nativeDeclaration(input, number, 'FunctionAssignment', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
1127
+ } else if ((match = trimmed.match(/^local\s+([A-Za-z_]\w*)\s*=\s*(?:\{|\w+)/))) {
1128
+ declarations.push(nativeDeclaration(input, number, 'LocalDeclaration', 'variable', match[1], {}, false));
1129
+ }
1130
+ }
1131
+ return declarations;
1132
+ }
1133
+
1134
+ function scanShell(input) {
1135
+ const declarations = [];
1136
+ for (const { line, number } of sourceLines(input.sourceText)) {
1137
+ const trimmed = line.trim();
1138
+ let match;
1139
+ if ((match = trimmed.match(/^(?:source|\.)\s+(?:"([^"]+)"|'([^']+)'|([./A-Za-z0-9_-][\w./-]*))(?:\s|$)/))) {
1140
+ declarations.push(nativeImportDeclaration(input, number, match[1] ?? match[2] ?? match[3], 'SourceCommand', 'file'));
1141
+ } else if ((match = trimmed.match(/^function\s+([A-Za-z_][\w-]*)\s*(?:\(\s*\))?\s*(?:\{|$)/))) {
1142
+ declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], {}, true));
1143
+ } else if ((match = trimmed.match(/^([A-Za-z_][\w-]*)\s*\(\s*\)\s*(?:\{|$)/))) {
1144
+ declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], {}, true));
1145
+ } else if ((match = trimmed.match(/^(?:export\s+)?(?:readonly\s+)?([A-Za-z_]\w*)=/))) {
1146
+ declarations.push(nativeDeclaration(input, number, 'VariableAssignment', 'variable', match[1], {}, false));
1147
+ } else if ((match = trimmed.match(/^alias\s+([A-Za-z_][\w-]*)=/))) {
1148
+ declarations.push(nativeDeclaration(input, number, 'AliasDeclaration', 'function', match[1], {}, false));
1149
+ }
1150
+ }
1151
+ return declarations;
1152
+ }
1153
+
1154
+ function scanSql(input) {
1155
+ const declarations = [];
1156
+ for (const { line, number } of sourceLines(input.sourceText)) {
1157
+ const trimmed = line.trim();
1158
+ let match;
1159
+ if ((match = trimmed.match(/^CREATE\s+EXTENSION\s+(?:IF\s+NOT\s+EXISTS\s+)?((?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$-]*))/i))) {
1160
+ declarations.push(nativeImportDeclaration(input, number, normalizeSqlIdentifier(match[1]), 'CreateExtensionStatement', 'extension'));
1161
+ } else if ((match = trimmed.match(/^CREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP(?:ORARY)?\s+)?((?:UNIQUE\s+)?INDEX|MATERIALIZED\s+VIEW|TABLE|VIEW|FUNCTION|PROCEDURE|TRIGGER|SCHEMA|TYPE)\s+(?:IF\s+NOT\s+EXISTS\s+)?((?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*)(?:\s*\.\s*(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*))?)/i))) {
1162
+ const objectKind = match[1].toUpperCase().replace(/\s+/g, ' ');
1163
+ declarations.push(nativeDeclaration(input, number, sqlLanguageKind(objectKind), sqlSymbolKind(objectKind), normalizeSqlIdentifier(match[2]), { objectKind }, trimmed.includes('(')));
1164
+ }
1165
+ }
1166
+ return declarations;
1167
+ }
1168
+
1169
+ function scanZig(input) {
1170
+ const declarations = [];
1171
+ for (const { line, number } of sourceLines(input.sourceText)) {
1172
+ const trimmed = line.trim();
1173
+ let match;
1174
+ if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?(?:const|var)\s+([A-Za-z_]\w*)\s*=\s*@import\(\s*["']([^"']+)["']\s*\)\s*;?/))) {
1175
+ declarations.push(nativeImportDeclaration(input, number, match[2], 'ImportDeclaration', 'module'));
1176
+ } else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?usingnamespace\s+@import\(\s*["']([^"']+)["']\s*\)\s*;?/))) {
1177
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'UsingNamespaceImport', 'module'));
1178
+ } else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?const\s+([A-Za-z_]\w*)\s*=\s*(?:extern\s+)?(struct|enum|union|opaque|error)\b/))) {
1179
+ declarations.push(nativeDeclaration(input, number, `Const${upperFirst(match[2])}Declaration`, 'type', match[1], { zigKind: match[2] }, trimmed.includes('{')));
1180
+ } else if ((match = trimmed.match(/^(?:(?:pub|export|extern|inline)\s+)*(?:fn)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
1181
+ declarations.push(nativeDeclaration(input, number, 'FnDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
1182
+ } else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?const\s+([A-Za-z_]\w*)\b/))) {
1183
+ declarations.push(nativeDeclaration(input, number, 'ConstDeclaration', 'constant', match[1], {}, false));
1184
+ } else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?var\s+([A-Za-z_]\w*)\b/))) {
1185
+ declarations.push(nativeDeclaration(input, number, 'VarDeclaration', 'variable', match[1], {}, false));
1186
+ }
1187
+ if (/^\s*comptime\b|@(?:cImport|compileError|field|hasDecl|hasField|setEvalBranchQuota|This|Type|typeInfo)\b/.test(trimmed)) {
1188
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'generatedCode', zigMetaName(trimmed)));
1189
+ }
1190
+ }
1191
+ return declarations;
1192
+ }
1193
+
1194
+ function scanElixir(input) {
1195
+ const declarations = [];
1196
+ let currentModule;
1197
+ for (const { line, number } of sourceLines(input.sourceText)) {
1198
+ const trimmed = line.trim();
1199
+ let match;
1200
+ let recordedMeta = false;
1201
+ if ((match = trimmed.match(/^defmodule\s+([A-Z]\w*(?:\.[A-Z]\w*)*)\s+do\b/))) {
1202
+ currentModule = match[1];
1203
+ declarations.push(nativeDeclaration(input, number, 'ModuleDefinition', 'module', match[1], {}, true));
1204
+ } else if ((match = trimmed.match(/^(?:alias|import|require)\s+([A-Z]\w*(?:\.[A-Z]\w*)*)/))) {
1205
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDirective', 'module'));
1206
+ } else if ((match = trimmed.match(/^use\s+([A-Z]\w*(?:\.[A-Z]\w*)*)/))) {
1207
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', match[1]));
1208
+ recordedMeta = true;
1209
+ } else if ((match = trimmed.match(/^(defmacro|defmacrop|defguard|defguardp|defdelegate)\s+([A-Za-z_]\w*[!?]?)/))) {
1210
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', match[2]));
1211
+ recordedMeta = true;
1212
+ } else if ((match = trimmed.match(/^defp?\s+([A-Za-z_]\w*[!?]?)\s*(?:\(([^)]*)\)|([^,]*))?/))) {
1213
+ declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], { parameters: splitParameters(match[2] ?? match[3]) }, /\bdo\b/.test(trimmed)));
1214
+ } else if (trimmed.startsWith('defstruct')) {
1215
+ declarations.push(nativeDeclaration(input, number, 'StructDefinition', 'type', currentModule ?? `struct_${number}`, {}, true));
1216
+ } else if ((match = trimmed.match(/^@(type|typep|opaque|callback)\s+([A-Za-z_]\w*[!?]?)/))) {
1217
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Attribute`, match[1] === 'callback' ? 'function' : 'type', match[2], {}, false));
1218
+ }
1219
+ if (!recordedMeta && /(?:\bquote\s+do\b|\bunquote(?:_splicing)?\b|@(?:before_compile|after_compile|on_definition|derive)\b)/.test(trimmed)) {
1220
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', elixirMetaName(trimmed)));
1221
+ }
1222
+ }
1223
+ return declarations;
1224
+ }
1225
+
1226
+ function scanErlang(input) {
1227
+ const declarations = [];
1228
+ const seenFunctions = new Set();
1229
+ for (const { line, number } of sourceLines(input.sourceText)) {
1230
+ const trimmed = line.trim();
1231
+ let match;
1232
+ let recordedMacro = false;
1233
+ if ((match = trimmed.match(/^-module\(([a-z][A-Za-z0-9_@]*)\)\./))) {
1234
+ declarations.push(nativeDeclaration(input, number, 'ModuleAttribute', 'module', match[1], {}, false));
1235
+ } else if ((match = trimmed.match(/^-include(?:_lib)?\(["']([^"']+)["']\)\./))) {
1236
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'IncludeAttribute', 'module'));
1237
+ } else if ((match = trimmed.match(/^-import\(([a-z][A-Za-z0-9_@]*)\s*,/))) {
1238
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportAttribute', 'module'));
1239
+ } else if ((match = trimmed.match(/^-behaviou?r\(([a-z][A-Za-z0-9_@]*)\)\./))) {
1240
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'BehaviourAttribute', 'module'));
1241
+ } else if ((match = trimmed.match(/^-record\(([a-z][A-Za-z0-9_@]*)\s*,/))) {
1242
+ declarations.push(nativeDeclaration(input, number, 'RecordAttribute', 'type', match[1], {}, false));
1243
+ } else if ((match = trimmed.match(/^-(type|opaque)\s+([a-z][A-Za-z0-9_@]*)\s*\(/))) {
1244
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Attribute`, 'type', match[2], {}, false));
1245
+ } else if ((match = trimmed.match(/^-callback\s+([a-z][A-Za-z0-9_@]*)\s*\(/))) {
1246
+ declarations.push(nativeDeclaration(input, number, 'CallbackAttribute', 'function', match[1], {}, false));
1247
+ } else if ((match = trimmed.match(/^-define\(([^,\s)]+)/))) {
1248
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'preprocessor', match[1]));
1249
+ recordedMacro = true;
1250
+ } else if (/^-compile\([^)]*parse_transform/.test(trimmed)) {
1251
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'generatedCode', 'parse_transform'));
1252
+ recordedMacro = true;
1253
+ } else if ((match = trimmed.match(/^([a-z][A-Za-z0-9_@]*|'[^']+')\s*\(([^)]*)\)\s*(?:when\s+.*?)?->/))) {
1254
+ const name = erlangAtomName(match[1]);
1255
+ if (!seenFunctions.has(name)) {
1256
+ seenFunctions.add(name);
1257
+ declarations.push(nativeDeclaration(input, number, 'FunctionClause', 'function', name, { parameters: splitParameters(match[2]) }, true));
1258
+ }
1259
+ }
1260
+ if (!recordedMacro && /(^|[^A-Za-z0-9_])\?[A-Za-z_]\w*/.test(trimmed)) {
1261
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', erlangMacroName(trimmed)));
1262
+ }
1263
+ }
1264
+ return declarations;
1265
+ }
1266
+
1267
+ function scanHaskell(input) {
1268
+ const declarations = [];
1269
+ const seenFunctions = new Set();
1270
+ for (const { line, number } of sourceLines(input.sourceText)) {
1271
+ const trimmed = line.trim();
1272
+ let match;
1273
+ if (/^#\s*(?:if|ifdef|ifndef|else|elif|endif|define|include)\b/.test(trimmed)) {
1274
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'preprocessor', haskellMetaName(trimmed)));
1275
+ } else if ((match = trimmed.match(/^\{-#\s+LANGUAGE\s+(.+?)#-\}/))) {
1276
+ const extensions = match[1].split(',').map((part) => part.trim());
1277
+ if (extensions.some((extension) => /^(TemplateHaskell|QuasiQuotes|CPP)$/.test(extension))) {
1278
+ declarations.push(nativeMacroLoss(input, number, trimmed, extensions.includes('CPP') ? 'preprocessor' : 'macroExpansion', extensions.join('_')));
1279
+ }
1280
+ } else if (/^\$\(|\[[a-zA-Z]*\||\b\$\([^)]+\)/.test(trimmed)) {
1281
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', haskellMetaName(trimmed)));
1282
+ } else if ((match = trimmed.match(/^module\s+([A-Z][A-Za-z0-9_.']*)\b/))) {
1283
+ declarations.push(nativeDeclaration(input, number, 'ModuleDeclaration', 'module', match[1], {}, false));
1284
+ } else if ((match = trimmed.match(/^import\s+(?:safe\s+)?(?:qualified\s+)?([A-Z][A-Za-z0-9_.']*)/))) {
1285
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
1286
+ } else if ((match = trimmed.match(/^foreign\s+import\s+([A-Za-z_]\w*)/))) {
1287
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ForeignImportDeclaration', 'foreign'));
1288
+ } else if ((match = trimmed.match(/^(data|newtype|type)\s+([A-Z][A-Za-z0-9_']*)\b/))) {
1289
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, 'type', match[2], {}, /(?:where|=)/.test(trimmed)));
1290
+ } else if ((match = trimmed.match(/^class\s+(?:\([^)]*\)\s*=>\s*)?([A-Z][A-Za-z0-9_']*)\b/))) {
1291
+ declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'type', match[1], {}, /\bwhere\b/.test(trimmed)));
1292
+ } else if ((match = trimmed.match(/^([a-z_][A-Za-z0-9_']*)\s*::\s*(.+)$/))) {
1293
+ seenFunctions.add(match[1]);
1294
+ declarations.push(nativeDeclaration(input, number, 'FunctionSignature', 'function', match[1], { signature: match[2].trim() }, false));
1295
+ } else if ((match = trimmed.match(/^([a-z_][A-Za-z0-9_']*)\b[^=]*=/))) {
1296
+ if (!seenFunctions.has(match[1])) {
1297
+ seenFunctions.add(match[1]);
1298
+ declarations.push(nativeDeclaration(input, number, 'FunctionBinding', 'function', match[1], {}, true));
1299
+ }
1300
+ }
1301
+ }
1302
+ return declarations;
1303
+ }
1304
+
1305
+ function scanR(input) {
1306
+ const declarations = [];
1307
+ for (const { line, number } of sourceLines(input.sourceText)) {
1308
+ const trimmed = line.trim();
1309
+ let match;
1310
+ if ((match = trimmed.match(/^(?:library|require)\s*\(\s*["']?([A-Za-z_][\w.-]*)["']?/))) {
1311
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'LibraryCall', 'package'));
1312
+ } else if ((match = trimmed.match(/^importFrom\s*\(\s*["']?([A-Za-z_][\w.-]*)["']?/))) {
1313
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportFromCall', 'package'));
1314
+ } else if ((match = trimmed.match(/^source\s*\(\s*["']([^"']+)["']/))) {
1315
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'SourceCall', 'module'));
1316
+ } else if ((match = trimmed.match(/^([A-Za-z_][\w.]*)\s*(?:<-|=)\s*function\s*\(([^)]*)\)/))) {
1317
+ declarations.push(nativeDeclaration(input, number, 'FunctionAssignment', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
1318
+ } else if ((match = trimmed.match(/^([A-Za-z_][\w.]*)\s*<-\s*R6Class\s*\(\s*["']([^"']+)["']/))) {
1319
+ declarations.push(nativeDeclaration(input, number, 'R6ClassDeclaration', 'class', match[2] || match[1], { binding: match[1] }, true));
1320
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[2] || match[1]));
1321
+ } else if ((match = trimmed.match(/^setClass\s*\(\s*["']([^"']+)["']/))) {
1322
+ declarations.push(nativeDeclaration(input, number, 'S4ClassDeclaration', 'class', match[1], {}, true));
1323
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[1]));
1324
+ } else if ((match = trimmed.match(/^setGeneric\s*\(\s*["']([^"']+)["']/))) {
1325
+ declarations.push(nativeDeclaration(input, number, 'S4GenericDeclaration', 'function', match[1], {}, true));
1326
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[1]));
1327
+ } else if ((match = trimmed.match(/^setMethod\s*\(\s*["']([^"']+)["']/))) {
1328
+ declarations.push(nativeDeclaration(input, number, 'S4MethodDeclaration', 'method', match[1], {}, true));
1329
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicDispatch', match[1]));
1330
+ } else if ((match = trimmed.match(/^([A-Z][A-Za-z0-9_.]*)\s*(?:<-|=)\s*/))) {
1331
+ declarations.push(nativeDeclaration(input, number, 'ConstantAssignment', 'constant', match[1], {}, false));
1332
+ }
1333
+ if (/(?:eval|parse|substitute|quote|bquote|assign)\s*\(|<<-|\{\{|!!!|!!/.test(trimmed)) {
1334
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', rMetaName(trimmed)));
1335
+ }
1336
+ }
1337
+ return declarations;
1338
+ }
1339
+
1340
+ function scanGenericDeclarations(input) {
1341
+ return sourceLines(input.sourceText)
1342
+ .filter(({ line }) => /\b(function|class|struct|enum|trait|interface|def)\b/.test(line))
1343
+ .map(({ line, number }) => nativeDeclaration(input, number, 'NativeDeclaration', 'variable', idFragment(line.trim()).slice(0, 40), { source: line.trim() }, true));
1344
+ }
1345
+
1346
+ function upperFirst(value) {
1347
+ return String(value).charAt(0).toUpperCase() + String(value).slice(1);
1348
+ }
1349
+
1350
+ function javaSymbolKind(kind) {
1351
+ if (kind === 'interface' || kind === '@interface') return 'interface';
1352
+ if (kind === 'enum' || kind === 'record') return 'type';
1353
+ return 'class';
1354
+ }
1355
+
1356
+ function swiftSymbolKind(kind) {
1357
+ if (kind === 'protocol') return 'protocol';
1358
+ if (kind === 'extension') return 'implementation';
1359
+ if (kind === 'struct' || kind === 'enum' || kind === 'actor') return 'type';
1360
+ return 'class';
1361
+ }
1362
+
1363
+ function csharpSymbolKind(kind) {
1364
+ if (kind === 'interface') return 'interface';
1365
+ if (kind === 'struct' || kind === 'enum' || kind === 'record') return 'type';
1366
+ return 'class';
1367
+ }
1368
+
1369
+ function phpSymbolKind(kind) {
1370
+ if (kind === 'interface') return 'interface';
1371
+ if (kind === 'trait') return 'trait';
1372
+ if (kind === 'enum') return 'type';
1373
+ return 'class';
1374
+ }
1375
+
1376
+ function kotlinDeclarationKind(kind, prefix) {
1377
+ if (prefix === 'enum') return 'EnumClassDeclaration';
1378
+ if (prefix === 'annotation') return 'AnnotationClassDeclaration';
1379
+ return `${upperFirst(kind)}Declaration`;
1380
+ }
1381
+
1382
+ function kotlinSymbolKind(kind, prefix) {
1383
+ if (kind === 'interface') return 'interface';
1384
+ if (kind === 'object') return 'module';
1385
+ if (prefix === 'enum' || prefix === 'annotation') return 'type';
1386
+ return 'class';
1387
+ }
1388
+
1389
+ function scalaSymbolKind(kind) {
1390
+ if (kind === 'trait') return 'trait';
1391
+ if (kind === 'object') return 'module';
1392
+ if (kind === 'enum') return 'type';
1393
+ return 'class';
1394
+ }
1395
+
1396
+ function dartSymbolKind(kind) {
1397
+ if (kind === 'mixin') return 'trait';
1398
+ if (kind === 'enum') return 'type';
1399
+ return 'class';
1400
+ }
1401
+
1402
+ function sqlSymbolKind(kind) {
1403
+ if (kind === 'FUNCTION' || kind === 'PROCEDURE' || kind === 'TRIGGER') return 'function';
1404
+ if (kind.includes('INDEX')) return 'index';
1405
+ if (kind === 'SCHEMA') return 'namespace';
1406
+ return 'type';
1407
+ }
1408
+
1409
+ function sqlLanguageKind(kind) {
1410
+ return `Create${kind.toLowerCase().split(/\s+/).map(upperFirst).join('')}Statement`;
1411
+ }
1412
+
1413
+ function normalizeSqlIdentifier(value) {
1414
+ return String(value)
1415
+ .split(/\s*\.\s*/)
1416
+ .map((part) => part.replace(/^"|"$/g, '').replace(/^`|`$/g, '').replace(/^\[|\]$/g, ''))
1417
+ .join('.');
1418
+ }
1419
+
1420
+ function zigMetaName(source) {
1421
+ const match = source.match(/@([A-Za-z_]\w*)|^\s*(comptime)\b/);
1422
+ return match?.[1] ?? match?.[2] ?? 'comptime';
1423
+ }
1424
+
1425
+ function elixirMetaName(source) {
1426
+ const match = source.match(/@([A-Za-z_]\w*)|\b(unquote(?:_splicing)?|quote)\b/);
1427
+ return match?.[1] ?? match?.[2] ?? 'macro';
1428
+ }
1429
+
1430
+ function erlangAtomName(value) {
1431
+ return String(value).startsWith("'") ? String(value).slice(1, -1) : String(value);
1432
+ }
1433
+
1434
+ function erlangMacroName(source) {
1435
+ const match = source.match(/\?([A-Za-z_]\w*)/);
1436
+ return match?.[1] ?? 'macro';
1437
+ }
1438
+
1439
+ function haskellMetaName(source) {
1440
+ return idFragment(source).slice(0, 40);
1441
+ }
1442
+
1443
+ function rMetaName(source) {
1444
+ const match = source.match(/([A-Za-z_][\w.]*)\s*\(/);
1445
+ return match?.[1] ?? 'dynamic';
1446
+ }
1447
+
1448
+ function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false) {
1449
+ const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
1450
+ return {
1451
+ nodeId,
1452
+ kind: languageKind,
1453
+ languageKind: `${input.language}.${languageKind}`,
1454
+ name,
1455
+ symbolKind,
1456
+ symbolId: `symbol:${input.language}:${idFragment(name)}`,
1457
+ span: spanForLine(input, lineNumber),
1458
+ fields,
1459
+ metadata: { scan: 'lightweight-declaration', hasBody },
1460
+ ...(hasBody ? { loss: opaqueBodyLoss(input, lineNumber, nodeId, name) } : {})
1461
+ };
1462
+ }
1463
+
1464
+ function nativeImportDeclaration(input, lineNumber, importPath, languageKind, symbolKind) {
1465
+ const name = String(importPath);
1466
+ const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
1467
+ return {
1468
+ nodeId,
1469
+ kind: languageKind,
1470
+ languageKind: `${input.language}.${languageKind}`,
1471
+ name,
1472
+ symbolKind,
1473
+ symbolId: `symbol:${input.language}:import:${idFragment(name)}`,
1474
+ role: 'import',
1475
+ importPath: name,
1476
+ span: spanForLine(input, lineNumber),
1477
+ fields: { importPath: name },
1478
+ metadata: { scan: 'lightweight-import' }
1479
+ };
1480
+ }
1481
+
1482
+ function nativeMacroLoss(input, lineNumber, source, kind, name = idFragment(source).slice(0, 40)) {
1483
+ const nodeId = `native_${kind}_${lineNumber}_${idFragment(name)}`;
1484
+ return {
1485
+ nodeId,
1486
+ kind: kind === 'preprocessor' ? 'PreprocessorDirective' : 'MacroInvocation',
1487
+ languageKind: `${input.language}.${kind}`,
1488
+ name,
1489
+ symbolKind: 'constant',
1490
+ symbolId: `symbol:${input.language}:${kind}:${idFragment(name)}`,
1491
+ span: spanForLine(input, lineNumber),
1492
+ fields: { source },
1493
+ metadata: { scan: 'lightweight-macro' },
1494
+ loss: {
1495
+ id: `loss_${idFragment(nodeId)}`,
1496
+ severity: 'warning',
1497
+ phase: 'read',
1498
+ sourceFormat: input.language,
1499
+ kind,
1500
+ message: `${input.language} ${kind} retained as native source; expansion is not evaluated by the lightweight importer.`,
1501
+ span: spanForLine(input, lineNumber),
1502
+ nodeId
1503
+ }
1504
+ };
1505
+ }
1506
+
1507
+ function opaqueBodyLoss(input, lineNumber, nodeId, name) {
1508
+ return {
1509
+ id: `loss_${idFragment(nodeId)}_body`,
1510
+ severity: 'info',
1511
+ phase: 'read',
1512
+ sourceFormat: input.language,
1513
+ kind: 'opaqueNative',
1514
+ message: `Body for ${name} is retained as native source by the lightweight declaration importer.`,
1515
+ span: spanForLine(input, lineNumber),
1516
+ nodeId
1517
+ };
1518
+ }
1519
+
1520
+ function lightweightCoverageLosses(input, declarations) {
1521
+ const id = idFragment(input.sourcePath ?? input.language);
1522
+ const span = declarations[0]?.span ?? {
1523
+ sourceId: input.sourceHash,
1524
+ path: input.sourcePath,
1525
+ startLine: 1,
1526
+ startColumn: 1
1527
+ };
1528
+ return [
1529
+ {
1530
+ id: `loss_${id}_declaration_only_coverage`,
1531
+ severity: 'info',
1532
+ phase: 'read',
1533
+ sourceFormat: input.language,
1534
+ kind: 'declarationOnlyCoverage',
1535
+ message: 'Lightweight importer scanned declarations and imports only; expressions, control flow, and full type checking were not evaluated.',
1536
+ span
1537
+ },
1538
+ {
1539
+ id: `loss_${id}_partial_semantic_index`,
1540
+ severity: 'info',
1541
+ phase: 'index',
1542
+ sourceFormat: input.language,
1543
+ kind: 'partialSemanticIndex',
1544
+ message: 'Semantic index contains lightweight declaration/import facts only; references, calls, resolved types, and cross-file links may be missing.',
1545
+ span
1546
+ },
1547
+ {
1548
+ id: `loss_${id}_source_map_approximation`,
1549
+ severity: 'info',
1550
+ phase: 'map',
1551
+ sourceFormat: input.language,
1552
+ kind: 'sourceMapApproximation',
1553
+ message: 'Source-map spans are declaration or line estimates; exact token ranges require a parser adapter.',
1554
+ span
1555
+ },
1556
+ {
1557
+ id: `loss_${id}_source_preservation`,
1558
+ severity: 'warning',
1559
+ phase: 'read',
1560
+ sourceFormat: input.language,
1561
+ kind: 'sourcePreservation',
1562
+ message: 'Comments, whitespace, token order, directives, and formatting are not preserved by the lightweight importer.',
1563
+ span
1564
+ }
1565
+ ];
1566
+ }
1567
+
1568
+ function sourceLines(sourceText) {
1569
+ return String(sourceText ?? '').split(/\r?\n/).map((line, index) => ({ line, number: index + 1 }));
1570
+ }
1571
+
1572
+ function spanForLine(input, lineNumber) {
1573
+ return {
1574
+ sourceId: input.sourceHash,
1575
+ path: input.sourcePath,
1576
+ startLine: lineNumber,
1577
+ endLine: lineNumber,
1578
+ startColumn: 1
1579
+ };
1580
+ }
1581
+
1582
+ function splitParameters(raw) {
1583
+ return String(raw ?? '')
1584
+ .split(',')
1585
+ .map((part) => part.trim())
1586
+ .filter(Boolean);
1587
+ }
1588
+
1589
+ function inferSourceMapMappings(input) {
1590
+ const semanticIndex = input.semanticIndex;
1591
+ const nativeAst = input.nativeAst;
1592
+ const nativeSource = input.nativeSource;
1593
+ const evidenceIds = [
1594
+ ...(semanticIndex?.evidence ?? []).map((record) => record.id),
1595
+ ...(input.evidence ?? []).map((record) => record.id)
1596
+ ];
1597
+ const symbolsById = new Map((semanticIndex?.symbols ?? []).map((symbol) => [symbol.id, symbol]));
1598
+
1599
+ if (semanticIndex?.occurrences?.length) {
1600
+ return semanticIndex.occurrences
1601
+ .filter((occurrence) => occurrence.nativeAstNodeId || occurrence.span)
1602
+ .map((occurrence) => {
1603
+ const symbol = symbolsById.get(occurrence.symbolId);
1604
+ const nativeNode = occurrence.nativeAstNodeId ? nativeAst?.nodes?.[occurrence.nativeAstNodeId] : undefined;
1605
+ return {
1606
+ id: `map_${idFragment(occurrence.id)}`,
1607
+ nativeSourceId: nativeSource?.id,
1608
+ nativeAstNodeId: occurrence.nativeAstNodeId,
1609
+ semanticSymbolId: occurrence.symbolId,
1610
+ semanticOccurrenceId: occurrence.id,
1611
+ semanticNodeId: occurrence.semanticNodeId ?? symbol?.semanticNodeId,
1612
+ sourceSpan: occurrence.span ?? nativeNode?.span,
1613
+ evidenceIds,
1614
+ lossIds: lossIdsForNativeNode(input.losses ?? nativeAst?.losses ?? [], occurrence.nativeAstNodeId),
1615
+ precision: occurrence.span || nativeNode?.span ? 'declaration' : 'unknown'
1616
+ };
1617
+ });
1618
+ }
1619
+
1620
+ return Object.values(nativeAst?.nodes ?? {})
1621
+ .filter((node) => node.span)
1622
+ .map((node) => ({
1623
+ id: `map_${idFragment(node.id)}`,
1624
+ nativeSourceId: nativeSource?.id,
1625
+ nativeAstNodeId: node.id,
1626
+ sourceSpan: node.span,
1627
+ evidenceIds,
1628
+ lossIds: lossIdsForNativeNode(input.losses ?? nativeAst?.losses ?? [], node.id),
1629
+ precision: 'line'
1630
+ }));
1631
+ }
1632
+
1633
+ function normalizeSourceMapMappings(mappings, context) {
1634
+ if (mappings === undefined || mappings === null) return [];
1635
+ if (!Array.isArray(mappings)) {
1636
+ throw new Error('Source-map mappings must be an array');
1637
+ }
1638
+ const semanticIndex = context.semanticIndex;
1639
+ const nativeAst = context.nativeAst;
1640
+ const nativeSource = context.nativeSource;
1641
+ const symbolsById = new Map((semanticIndex?.symbols ?? []).map((symbol) => [symbol.id, symbol]));
1642
+ const occurrencesById = new Map((semanticIndex?.occurrences ?? []).map((occurrence) => [occurrence.id, occurrence]));
1643
+ const evidenceIds = uniqueStrings([
1644
+ ...(semanticIndex?.evidence ?? []).map((record) => record.id),
1645
+ ...(context.evidence ?? []).map((record) => record.id),
1646
+ ...(context.sourceMapEvidence ?? []).map((record) => record.id)
1647
+ ]);
1648
+ const usedMappingIds = new Set();
1649
+ return mappings
1650
+ .map((mapping, index) => {
1651
+ if (!mapping || typeof mapping !== 'object') {
1652
+ throw new Error(`Source-map mapping ${index + 1} must be an object`);
1653
+ }
1654
+ const occurrence = mapping.semanticOccurrenceId ? occurrencesById.get(mapping.semanticOccurrenceId) : undefined;
1655
+ const symbol = mapping.semanticSymbolId ? symbolsById.get(mapping.semanticSymbolId) : occurrence ? symbolsById.get(occurrence.symbolId) : undefined;
1656
+ const nativeAstNodeId = mapping.nativeAstNodeId ?? occurrence?.nativeAstNodeId;
1657
+ const nativeNode = nativeAstNodeId ? nativeAst?.nodes?.[nativeAstNodeId] : undefined;
1658
+ const sourceSpan = mapping.sourceSpan ?? occurrence?.span ?? nativeNode?.span;
1659
+ const target = mapping.target ?? mapping.generatedSpan?.target ?? context.target;
1660
+ const generatedSpan = normalizeGeneratedSpan(mapping.generatedSpan, target, context.targetPath, context.targetHash);
1661
+ if (
1662
+ !nativeAstNodeId &&
1663
+ !mapping.semanticNodeId &&
1664
+ !mapping.semanticSymbolId &&
1665
+ !mapping.semanticOccurrenceId &&
1666
+ !sourceSpan &&
1667
+ !generatedSpan &&
1668
+ !mapping.generatedName
1669
+ ) {
1670
+ throw new Error(`Source-map mapping ${index + 1} must reference a native AST node, semantic node, symbol, occurrence, source span, or generated span`);
1671
+ }
1672
+ const normalizedMapping = {
1673
+ ...mapping,
1674
+ nativeSourceId: mapping.nativeSourceId ?? nativeSource?.id,
1675
+ nativeAstNodeId,
1676
+ semanticSymbolId: mapping.semanticSymbolId ?? occurrence?.symbolId,
1677
+ semanticOccurrenceId: mapping.semanticOccurrenceId ?? occurrence?.id,
1678
+ semanticNodeId: mapping.semanticNodeId ?? occurrence?.semanticNodeId ?? symbol?.semanticNodeId,
1679
+ sourceSpan,
1680
+ generatedSpan,
1681
+ target,
1682
+ evidenceIds: normalizeReferenceIds(mapping.evidenceIds, evidenceIds),
1683
+ lossIds: normalizeReferenceIds(mapping.lossIds, lossIdsForNativeNode(context.losses ?? nativeAst?.losses ?? [], nativeAstNodeId)),
1684
+ precision: normalizeSourceMapPrecision(mapping.precision, sourceSpan, generatedSpan)
1685
+ };
1686
+ return {
1687
+ ...normalizedMapping,
1688
+ id: reserveUniqueId(sourceMapMappingBaseId(normalizedMapping, index), usedMappingIds)
1689
+ };
1690
+ });
1691
+ }
1692
+
1693
+ function lossIdsForNativeNode(losses, nativeAstNodeId) {
1694
+ if (!nativeAstNodeId) return [];
1695
+ return (losses ?? [])
1696
+ .filter((loss) => loss.nodeId === nativeAstNodeId)
1697
+ .map((loss) => loss.id);
1698
+ }
1699
+
1700
+ function normalizeSourceMaps(sourceMaps, context) {
1701
+ if (sourceMaps === undefined || sourceMaps === null) return [];
1702
+ if (!Array.isArray(sourceMaps)) {
1703
+ throw new Error('Native import sourceMaps must be an array');
1704
+ }
1705
+ const usedSourceMapIds = new Set();
1706
+ return sourceMaps.map((sourceMap, index) => {
1707
+ if (!sourceMap || typeof sourceMap !== 'object') {
1708
+ throw new Error(`Native import source map ${index + 1} must be an object`);
1709
+ }
1710
+ const id = reserveUniqueId(String(sourceMap.id ?? `${context.defaultSourceMapId}_${index + 1}`), usedSourceMapIds);
1711
+ const evidence = uniqueRecordsById([...(sourceMap.evidence ?? []), ...(context.evidence ?? [])]);
1712
+ const target = sourceMap.target ?? context.target;
1713
+ const targetPath = sourceMap.targetPath ?? context.targetPath;
1714
+ const targetHash = sourceMap.targetHash ?? context.targetHash;
1715
+ const mappings = normalizeSourceMapMappings(sourceMap.mappings ?? [], {
1716
+ ...context,
1717
+ target,
1718
+ targetPath,
1719
+ targetHash,
1720
+ sourceMapEvidence: evidence
1721
+ });
1722
+ const normalized = createSourceMapRecord({
1723
+ ...sourceMap,
1724
+ id,
1725
+ sourcePath: sourceMap.sourcePath ?? context.sourcePath,
1726
+ sourceHash: sourceMap.sourceHash ?? context.sourceHash,
1727
+ target,
1728
+ targetPath: targetPath ?? commonGeneratedTargetPath(mappings),
1729
+ targetHash,
1730
+ semanticIndexId: sourceMap.semanticIndexId ?? context.semanticIndex?.id,
1731
+ nativeAstId: sourceMap.nativeAstId ?? context.nativeAst?.id,
1732
+ nativeSourceId: sourceMap.nativeSourceId ?? context.nativeSource?.id,
1733
+ mappings,
1734
+ evidence
1735
+ });
1736
+ const issues = validateSourceMapRecord(normalized, {
1737
+ document: context.document,
1738
+ nativeSources: context.nativeSources,
1739
+ nativeAst: context.nativeAst,
1740
+ semanticIndex: context.semanticIndex,
1741
+ losses: context.losses,
1742
+ evidence: context.evidence
1743
+ });
1744
+ if (issues.length) {
1745
+ throw new Error(`Invalid Frontier native source map ${normalized.id}: ${issues.join('; ')}`);
1746
+ }
1747
+ return normalized;
1748
+ });
1749
+ }
1750
+
1751
+ function normalizeGeneratedSpan(generatedSpan, target, targetPath, targetHash) {
1752
+ if (!generatedSpan) return generatedSpan;
1753
+ return {
1754
+ ...generatedSpan,
1755
+ target: generatedSpan.target ?? target,
1756
+ targetPath: generatedSpan.targetPath ?? target?.emitPath ?? targetPath,
1757
+ targetHash: generatedSpan.targetHash ?? targetHash
1758
+ };
1759
+ }
1760
+
1761
+ function normalizeSourceMapPrecision(value, sourceSpan, generatedSpan) {
1762
+ const explicit = value === undefined || value === null ? '' : String(value).trim();
1763
+ if (explicit) {
1764
+ const normalized = explicit.toLowerCase();
1765
+ if (normalized === 'exact' || normalized === 'declaration' || normalized === 'line' || normalized === 'estimated' || normalized === 'unknown') return normalized;
1766
+ if (normalized === 'estimate' || normalized === 'approx' || normalized === 'approximate' || normalized === 'approximated') return 'estimated';
1767
+ return explicit;
1768
+ }
1769
+ if (hasExactSpan(sourceSpan) && hasExactSpan(generatedSpan)) return 'exact';
1770
+ if (sourceSpan?.startLine && generatedSpan?.startLine) return 'line';
1771
+ if (sourceSpan?.startLine) return 'declaration';
1772
+ if (generatedSpan?.startLine) return 'line';
1773
+ return 'unknown';
1774
+ }
1775
+
1776
+ function hasExactSpan(span) {
1777
+ return Boolean(span && (
1778
+ (typeof span.start === 'number' && typeof span.end === 'number') ||
1779
+ (
1780
+ typeof span.startLine === 'number' &&
1781
+ typeof span.startColumn === 'number' &&
1782
+ typeof span.endLine === 'number' &&
1783
+ typeof span.endColumn === 'number'
1784
+ )
1785
+ ));
1786
+ }
1787
+
1788
+ function normalizeReferenceIds(value, fallback = []) {
1789
+ if (value === undefined || value === null) return uniqueStrings(fallback);
1790
+ return uniqueStrings(Array.isArray(value) ? value : [value]);
1791
+ }
1792
+
1793
+ function sourceMapMappingBaseId(mapping, index) {
1794
+ const explicit = mapping.id === undefined || mapping.id === null ? '' : String(mapping.id).trim();
1795
+ if (explicit) return explicit;
1796
+ const span = mapping.sourceSpan ?? mapping.generatedSpan;
1797
+ const spanPart = span
1798
+ ? `${span.path ?? span.targetPath ?? span.sourceId ?? 'span'}_${span.start ?? span.startLine ?? ''}_${span.end ?? span.endLine ?? ''}`
1799
+ : undefined;
1800
+ return `map_${idFragment([
1801
+ mapping.nativeAstNodeId,
1802
+ mapping.semanticOccurrenceId,
1803
+ mapping.semanticSymbolId,
1804
+ mapping.semanticNodeId,
1805
+ spanPart,
1806
+ index + 1
1807
+ ].filter(Boolean).join('_'))}`;
1808
+ }
1809
+
1810
+ function reserveUniqueId(baseId, usedIds) {
1811
+ const safeBase = String(baseId || 'id');
1812
+ if (!usedIds.has(safeBase)) {
1813
+ usedIds.add(safeBase);
1814
+ return safeBase;
1815
+ }
1816
+ let index = 2;
1817
+ while (usedIds.has(`${safeBase}_${index}`)) index += 1;
1818
+ const id = `${safeBase}_${index}`;
1819
+ usedIds.add(id);
1820
+ return id;
1821
+ }
1822
+
1823
+ function commonGeneratedTargetPath(mappings) {
1824
+ const paths = uniqueStrings((mappings ?? [])
1825
+ .map((mapping) => mapping.generatedSpan?.targetPath ?? mapping.target?.emitPath)
1826
+ .filter(Boolean));
1827
+ return paths.length === 1 ? paths[0] : undefined;
1828
+ }
1829
+
1830
+ function uniqueRecordsById(records) {
1831
+ const seen = new Set();
1832
+ const result = [];
1833
+ for (const record of records ?? []) {
1834
+ if (!record?.id || seen.has(record.id)) continue;
1835
+ seen.add(record.id);
1836
+ result.push(record);
1837
+ }
1838
+ return result;
1839
+ }
1840
+
1841
+ function normalizeNativeLossRecords(losses) {
1842
+ return (Array.isArray(losses) ? losses : [losses])
1843
+ .filter((loss) => loss !== undefined && loss !== null)
1844
+ .map((loss, index) => normalizeNativeLossRecord(loss, `loss_${index + 1}`));
1845
+ }
1846
+
1847
+ function normalizeNativeLossRecord(loss, fallbackId) {
1848
+ const record = typeof loss === 'string' ? { message: loss } : loss ?? {};
1849
+ const severity = normalizeLossSeverity(record.severity);
1850
+ const kind = normalizeNativeLossKind(record, severity);
1851
+ const category = nativeImportCategoryForLossKind(kind);
1852
+ return {
1853
+ ...record,
1854
+ id: String(record.id ?? fallbackId),
1855
+ severity,
1856
+ kind,
1857
+ message: String(record.message ?? `${kind} loss during native import.`),
1858
+ metadata: {
1859
+ lossCategory: category,
1860
+ semanticMergeAdmission: semanticMergeAdmissionForSeverity(severity),
1861
+ dashboardSeverity: severity,
1862
+ ...record.metadata
1863
+ }
1864
+ };
1865
+ }
1866
+
1867
+ function normalizeLossSeverity(value) {
1868
+ const severity = String(value ?? 'warning').toLowerCase();
1869
+ if (severity === 'error') return 'error';
1870
+ if (severity === 'info') return 'info';
1871
+ return 'warning';
1872
+ }
1873
+
1874
+ function normalizeNativeLossKind(loss, severity) {
1875
+ const kind = String(loss.kind ?? '').trim();
1876
+ if (kind) return kind;
1877
+ if (loss.metadata?.diagnosticId || loss.metadata?.diagnosticCode) {
1878
+ return severity === 'error' ? 'unsupportedSyntax' : 'parserDiagnostic';
1879
+ }
1880
+ return severity === 'error' ? 'unsupportedSyntax' : 'opaqueNative';
1881
+ }
1882
+
1883
+ function nativeImportCategoryForLossKind(kind) {
1884
+ if (kind === 'declarationOnlyCoverage') return 'declarationsOnly';
1885
+ if (kind === 'opaqueNative') return 'opaqueBodies';
1886
+ if (kind === 'macroExpansion') return 'macroExpansion';
1887
+ if (kind === 'preprocessor' || kind === 'conditionalCompilation' || kind === 'macroHygiene') return 'preprocessor';
1888
+ if (kind === 'metaprogramming' || kind === 'reflection' || kind === 'dynamicDispatch' || kind === 'dynamicRuntime') return 'metaprogramming';
1889
+ if (kind === 'generatedCode') return 'generatedCode';
1890
+ if (kind === 'sourcePreservation' || kind === 'nonRoundTrippable') return 'sourcePreservation';
1891
+ if (kind === 'parserDiagnostic') return 'parserDiagnostics';
1892
+ if (kind === 'unsupportedSyntax' || kind === 'unsupportedSemantic') return 'unsupportedSyntax';
1893
+ if (kind === 'partialSemanticIndex') return 'partialSemanticIndex';
1894
+ if (kind === 'sourceMapApproximation') return 'sourceMapApproximation';
1895
+ return String(kind ?? 'opaqueNative');
1896
+ }
1897
+
1898
+ function semanticMergeAdmissionForSeverity(severity) {
1899
+ if (severity === 'error') return 'blocked';
1900
+ if (severity === 'warning') return 'review';
1901
+ return 'disclose';
1902
+ }
1903
+
1904
+ function nativeImportReadinessReasons(input) {
1905
+ if (input.failedEvidenceIds.length) {
1906
+ return [`Failed native import evidence prevents merge: ${input.failedEvidenceIds.join(', ')}`];
1907
+ }
1908
+ if (input.blockingLossIds.length) {
1909
+ return [`Native import error loss(es) block semantic merge: ${input.blockingLossIds.join(', ')}`];
1910
+ }
1911
+ if (input.reviewLossIds.length) {
1912
+ return [`Native import warning loss(es) require review: ${input.reviewLossIds.join(', ')}`];
1913
+ }
1914
+ if (input.informationalLossIds.length) {
1915
+ return [`Native import recorded informational loss(es): ${input.informationalLossIds.join(', ')}`];
1916
+ }
1917
+ if (input.exactAst) return ['Native import supplied exact AST coverage with no recorded loss.'];
1918
+ return ['Native import has no recorded loss.'];
1919
+ }
1920
+
1921
+ function attachNativeImportLossSummary(evidence, lossSummary) {
1922
+ return (evidence ?? []).map((record) => ({
1923
+ ...record,
1924
+ metadata: {
1925
+ ...record.metadata,
1926
+ nativeImportLossSummary: lossSummary,
1927
+ semanticMergeReadiness: lossSummary.semanticMergeReadiness,
1928
+ lossCategories: lossSummary.categories
1929
+ }
1930
+ }));
1931
+ }
1932
+
1933
+ function withNativeImportReadiness(importResult, lossSummary) {
1934
+ const mergeCandidates = (importResult.mergeCandidates ?? []).map((candidate) => {
1935
+ const readiness = maxSemanticMergeReadiness(candidate.readiness, lossSummary.semanticMergeReadiness);
1936
+ return {
1937
+ ...candidate,
1938
+ readiness,
1939
+ reasons: uniqueStrings([
1940
+ ...(candidate.reasons ?? []),
1941
+ ...lossSummary.readinessReasons
1942
+ ]),
1943
+ metadata: {
1944
+ ...candidate.metadata,
1945
+ nativeImportLossSummary: lossSummary,
1946
+ severityReadiness: lossSummary.semanticMergeReadiness,
1947
+ finalReadiness: readiness,
1948
+ lossCategories: lossSummary.categories,
1949
+ lossSeverityCounts: lossSummary.bySeverity,
1950
+ lossKindCounts: lossSummary.byKind
1951
+ }
1952
+ };
1953
+ });
1954
+ return {
1955
+ ...importResult,
1956
+ mergeCandidates,
1957
+ metadata: {
1958
+ ...importResult.metadata,
1959
+ nativeImportLossSummary: lossSummary,
1960
+ semanticMergeReadiness: mergeCandidates[0]?.readiness ?? lossSummary.semanticMergeReadiness,
1961
+ lossCategories: lossSummary.categories,
1962
+ lossSeverityCounts: lossSummary.bySeverity,
1963
+ lossKindCounts: lossSummary.byKind
1964
+ }
1965
+ };
1966
+ }
1967
+
1968
+ function maxSemanticMergeReadiness(left, right) {
1969
+ const leftRank = semanticMergeReadinessRank[left] ?? semanticMergeReadinessRank['needs-review'];
1970
+ const rightRank = semanticMergeReadinessRank[right] ?? semanticMergeReadinessRank['needs-review'];
1971
+ return leftRank >= rightRank ? left : right;
1972
+ }
1973
+
1974
+ export function createUniversalAstFromDocument(document, input = {}) {
1975
+ return createUniversalAstEnvelope({
1976
+ id: input.id ?? `universal_ast_${idFragment(document.id)}`,
1977
+ document,
1978
+ semanticIndex: input.semanticIndex,
1979
+ sourceMaps: input.sourceMaps ?? [],
1980
+ evidence: input.evidence ?? [],
1981
+ metadata: input.metadata
1982
+ });
1983
+ }
1984
+
1985
+ export function readUniversalAstJson(source) {
701
1986
  const envelope = JSON.parse(source);
702
1987
  const issues = validateUniversalAstEnvelope(envelope);
703
1988
  if (issues.length > 0) {
@@ -718,6 +2003,536 @@ export function emitForTarget(document, target = 'typescript', options = {}) {
718
2003
  return renderTargetAst(projectFrontierAst(document, target, options), target);
719
2004
  }
720
2005
 
2006
+ function createJavaScriptSyntaxImporterAdapter(options) {
2007
+ return {
2008
+ id: options.id,
2009
+ language: options.language,
2010
+ parser: options.parser,
2011
+ version: options.version,
2012
+ capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
2013
+ supportedExtensions: options.supportedExtensions,
2014
+ diagnostics: options.diagnostics,
2015
+ parse(input) {
2016
+ const ast = input.options?.ast ?? input.options?.nativeAst ?? options.ast ?? parseJavaScriptSyntax(input, options);
2017
+ if (!ast) {
2018
+ return missingInjectedParserResult(input, {
2019
+ parser: options.parser,
2020
+ adapterId: options.id,
2021
+ message: `${options.id} requires an injected parse function, parserModule.parse, ast, or adapterOptions.ast.`
2022
+ });
2023
+ }
2024
+ const parseDiagnostics = normalizeParserErrors(ast.errors, input, options);
2025
+ return createNativeImportFromSyntaxAst(ast, input, {
2026
+ parser: options.parser,
2027
+ astFormat: options.astFormat,
2028
+ maxNodes: options.maxNodes,
2029
+ diagnostics: parseDiagnostics
2030
+ });
2031
+ }
2032
+ };
2033
+ }
2034
+
2035
+ function parseJavaScriptSyntax(input, options) {
2036
+ const parse = options.parse ?? options.parserModule?.parse ?? options.babelParser?.parse;
2037
+ if (typeof parse !== 'function') return undefined;
2038
+ const parserOptions = {
2039
+ sourceFilename: input.sourcePath,
2040
+ ...(options.defaultParserOptions ?? {}),
2041
+ ...(typeof options.parserOptions === 'function'
2042
+ ? options.parserOptions(input)
2043
+ : options.parserOptions ?? {}),
2044
+ ...(input.options?.parserOptions ?? {})
2045
+ };
2046
+ return parse(input.sourceText, parserOptions);
2047
+ }
2048
+
2049
+ function createTypeScriptSourceFile(ts, input, options) {
2050
+ if (typeof options.createSourceFile === 'function') return options.createSourceFile(input, ts);
2051
+ if (!ts || typeof ts.createSourceFile !== 'function') return undefined;
2052
+ const scriptTarget = options.scriptTarget ?? ts.ScriptTarget?.Latest ?? ts.ScriptTarget?.ESNext ?? 99;
2053
+ const scriptKind = options.scriptKind ?? inferTypeScriptScriptKind(ts, input);
2054
+ return ts.createSourceFile(input.sourcePath ?? 'frontier-input.ts', input.sourceText, scriptTarget, true, scriptKind);
2055
+ }
2056
+
2057
+ function inferTypeScriptScriptKind(ts, input) {
2058
+ const path = String(input.sourcePath ?? '').toLowerCase();
2059
+ if (path.endsWith('.tsx')) return ts.ScriptKind?.TSX;
2060
+ if (path.endsWith('.jsx')) return ts.ScriptKind?.JSX;
2061
+ if (path.endsWith('.js') || path.endsWith('.mjs') || path.endsWith('.cjs')) return ts.ScriptKind?.JS;
2062
+ return ts.ScriptKind?.TS;
2063
+ }
2064
+
2065
+ function parseTreeSitterSource(input, options) {
2066
+ const parser = options.parserInstance ?? options.treeSitterParser ?? options.parser;
2067
+ if (parser && typeof parser.parse === 'function') return parser.parse(input.sourceText);
2068
+ if (typeof options.parse === 'function') return options.parse(input);
2069
+ return undefined;
2070
+ }
2071
+
2072
+ function createNativeImportFromSyntaxAst(ast, input, options) {
2073
+ const root = normalizeSyntaxAstRoot(ast, options.astFormat);
2074
+ if (!root) {
2075
+ return missingInjectedParserResult(input, {
2076
+ parser: options.parser,
2077
+ adapterId: input.adapterId,
2078
+ message: 'Injected AST did not contain an object root node.'
2079
+ });
2080
+ }
2081
+ const context = createAstNormalizationContext(input, options);
2082
+ visitSyntaxAstNode(root, context, 'root');
2083
+ if (context.truncated) {
2084
+ context.losses.push(truncatedAstLoss(input, context, options));
2085
+ }
2086
+ const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
2087
+ return {
2088
+ rootId: context.rootId,
2089
+ nodes: context.nodes,
2090
+ semanticIndex: semantic.semanticIndex,
2091
+ mappings: semantic.mappings,
2092
+ losses: mergeNativeLosses(context.losses, options.diagnostics?.map((diagnostic, index) => adapterDiagnosticToLoss(diagnostic, index, {
2093
+ id: input.adapterId,
2094
+ version: input.adapterVersion
2095
+ }, input)) ?? []),
2096
+ evidence: semantic.evidence,
2097
+ diagnostics: options.diagnostics,
2098
+ metadata: {
2099
+ astFormat: options.astFormat,
2100
+ parser: options.parser,
2101
+ normalizedNodeCount: Object.keys(context.nodes).length,
2102
+ declarationCount: context.declarations.length,
2103
+ truncated: context.truncated
2104
+ }
2105
+ };
2106
+ }
2107
+
2108
+ function createNativeImportFromTypeScriptAst(sourceFile, input, options) {
2109
+ const context = createAstNormalizationContext(input, options);
2110
+ visitTypeScriptAstNode(sourceFile, sourceFile, context, 'root', options.ts);
2111
+ if (context.truncated) {
2112
+ context.losses.push(truncatedAstLoss(input, context, options));
2113
+ }
2114
+ const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
2115
+ return {
2116
+ rootId: context.rootId,
2117
+ nodes: context.nodes,
2118
+ semanticIndex: semantic.semanticIndex,
2119
+ mappings: semantic.mappings,
2120
+ losses: context.losses,
2121
+ evidence: semantic.evidence,
2122
+ metadata: {
2123
+ astFormat: options.astFormat,
2124
+ parser: options.parser,
2125
+ normalizedNodeCount: Object.keys(context.nodes).length,
2126
+ declarationCount: context.declarations.length,
2127
+ truncated: context.truncated
2128
+ }
2129
+ };
2130
+ }
2131
+
2132
+ function createNativeImportFromTreeSitter(root, input, options) {
2133
+ const context = createAstNormalizationContext(input, options);
2134
+ visitTreeSitterNode(root, context, 'root');
2135
+ if (context.truncated) {
2136
+ context.losses.push(truncatedAstLoss(input, context, options));
2137
+ }
2138
+ const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
2139
+ return {
2140
+ rootId: context.rootId,
2141
+ nodes: context.nodes,
2142
+ semanticIndex: semantic.semanticIndex,
2143
+ mappings: semantic.mappings,
2144
+ losses: context.losses,
2145
+ evidence: semantic.evidence,
2146
+ metadata: {
2147
+ astFormat: options.astFormat,
2148
+ parser: options.parser,
2149
+ normalizedNodeCount: Object.keys(context.nodes).length,
2150
+ declarationCount: context.declarations.length,
2151
+ truncated: context.truncated
2152
+ }
2153
+ };
2154
+ }
2155
+
2156
+ function createAstNormalizationContext(input, options) {
2157
+ return {
2158
+ input,
2159
+ options,
2160
+ maxNodes: Number.isFinite(options.maxNodes) ? Math.max(1, options.maxNodes) : 5000,
2161
+ counter: 0,
2162
+ objectIds: new WeakMap(),
2163
+ nodes: {},
2164
+ declarations: [],
2165
+ losses: [],
2166
+ rootId: undefined,
2167
+ truncated: false
2168
+ };
2169
+ }
2170
+
2171
+ function normalizeSyntaxAstRoot(ast, astFormat) {
2172
+ if (!ast || typeof ast !== 'object') return undefined;
2173
+ if (astFormat === 'babel' && ast.program && typeof ast.program === 'object') return ast.program;
2174
+ return ast;
2175
+ }
2176
+
2177
+ function visitSyntaxAstNode(node, context, propertyPath) {
2178
+ if (!isSyntaxAstNode(node) || context.truncated) return undefined;
2179
+ if (context.objectIds.has(node)) return context.objectIds.get(node);
2180
+ if (context.counter >= context.maxNodes) {
2181
+ context.truncated = true;
2182
+ return undefined;
2183
+ }
2184
+ const id = nativeNodeId(context, node.type ?? node.kind ?? 'Node', node.loc, propertyPath);
2185
+ context.objectIds.set(node, id);
2186
+ if (!context.rootId) context.rootId = id;
2187
+ const children = [];
2188
+ const fields = primitiveSyntaxFields(node);
2189
+ for (const [key, value] of Object.entries(node)) {
2190
+ if (ignoredSyntaxField(key)) continue;
2191
+ if (Array.isArray(value)) {
2192
+ value.forEach((entry, index) => {
2193
+ const childId = visitSyntaxAstNode(entry, context, `${propertyPath}.${key}[${index}]`);
2194
+ if (childId) children.push(childId);
2195
+ });
2196
+ } else {
2197
+ const childId = visitSyntaxAstNode(value, context, `${propertyPath}.${key}`);
2198
+ if (childId) children.push(childId);
2199
+ }
2200
+ }
2201
+ const declaration = syntaxDeclaration(node, id, context.input, context.options);
2202
+ const nativeNode = {
2203
+ id,
2204
+ kind: String(node.type ?? node.kind ?? 'Node'),
2205
+ languageKind: `${context.input.language}.${node.type ?? node.kind ?? 'Node'}`,
2206
+ span: spanFromLoc(node.loc, context.input),
2207
+ value: declaration?.name ?? literalSyntaxValue(node),
2208
+ fields,
2209
+ children,
2210
+ metadata: {
2211
+ astFormat: context.options.astFormat,
2212
+ propertyPath,
2213
+ start: numberOrUndefined(node.start),
2214
+ end: numberOrUndefined(node.end),
2215
+ range: Array.isArray(node.range) ? node.range.slice(0, 2) : undefined
2216
+ }
2217
+ };
2218
+ context.nodes[id] = nativeNode;
2219
+ if (declaration) context.declarations.push({ ...declaration, nativeNode });
2220
+ return id;
2221
+ }
2222
+
2223
+ function visitTypeScriptAstNode(node, sourceFile, context, propertyPath, ts) {
2224
+ if (!node || typeof node !== 'object' || context.truncated) return undefined;
2225
+ if (context.objectIds.has(node)) return context.objectIds.get(node);
2226
+ if (context.counter >= context.maxNodes) {
2227
+ context.truncated = true;
2228
+ return undefined;
2229
+ }
2230
+ const kind = typeScriptKindName(node, ts);
2231
+ const span = spanFromTypeScriptNode(node, sourceFile);
2232
+ const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
2233
+ context.objectIds.set(node, id);
2234
+ if (!context.rootId) context.rootId = id;
2235
+ const children = [];
2236
+ const visitChild = (child) => {
2237
+ const childId = visitTypeScriptAstNode(child, sourceFile, context, `${propertyPath}.${children.length}`, ts);
2238
+ if (childId) children.push(childId);
2239
+ };
2240
+ if (ts && typeof ts.forEachChild === 'function') {
2241
+ ts.forEachChild(node, visitChild);
2242
+ } else if (typeof node.forEachChild === 'function') {
2243
+ node.forEachChild(visitChild);
2244
+ } else if (Array.isArray(node.children)) {
2245
+ node.children.forEach(visitChild);
2246
+ }
2247
+ const declaration = typeScriptDeclaration(node, kind, id, context.input, context.options);
2248
+ const nativeNode = {
2249
+ id,
2250
+ kind,
2251
+ languageKind: `${context.input.language}.${kind}`,
2252
+ span,
2253
+ value: declaration?.name ?? typeScriptNodeValue(node),
2254
+ fields: primitiveTypeScriptFields(node, kind),
2255
+ children,
2256
+ metadata: {
2257
+ astFormat: context.options.astFormat,
2258
+ propertyPath,
2259
+ pos: numberOrUndefined(node.pos),
2260
+ end: numberOrUndefined(node.end)
2261
+ }
2262
+ };
2263
+ context.nodes[id] = nativeNode;
2264
+ if (declaration) context.declarations.push({ ...declaration, nativeNode });
2265
+ return id;
2266
+ }
2267
+
2268
+ function visitTreeSitterNode(node, context, propertyPath) {
2269
+ if (!node || typeof node !== 'object' || context.truncated) return undefined;
2270
+ if (context.objectIds.has(node)) return context.objectIds.get(node);
2271
+ if (context.counter >= context.maxNodes) {
2272
+ context.truncated = true;
2273
+ return undefined;
2274
+ }
2275
+ const kind = String(node.type ?? node.kind ?? 'node');
2276
+ const span = spanFromTreeSitterNode(node, context.input);
2277
+ const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
2278
+ context.objectIds.set(node, id);
2279
+ if (!context.rootId) context.rootId = id;
2280
+ const children = [];
2281
+ const rawChildren = Array.isArray(node.namedChildren)
2282
+ ? node.namedChildren
2283
+ : Array.isArray(node.children)
2284
+ ? node.children
2285
+ : [];
2286
+ rawChildren.forEach((child, index) => {
2287
+ const childId = visitTreeSitterNode(child, context, `${propertyPath}.children[${index}]`);
2288
+ if (childId) children.push(childId);
2289
+ });
2290
+ const declaration = treeSitterDeclaration(node, kind, id, context.input, context.options);
2291
+ const nativeNode = {
2292
+ id,
2293
+ kind,
2294
+ languageKind: `${context.input.language}.${kind}`,
2295
+ span,
2296
+ value: declaration?.name ?? shortNodeText(node),
2297
+ fields: {
2298
+ named: Boolean(node.isNamed ?? node.named),
2299
+ missing: Boolean(node.isMissing),
2300
+ error: Boolean(node.hasError || kind === 'ERROR')
2301
+ },
2302
+ children,
2303
+ metadata: {
2304
+ astFormat: context.options.astFormat,
2305
+ propertyPath,
2306
+ startIndex: numberOrUndefined(node.startIndex),
2307
+ endIndex: numberOrUndefined(node.endIndex)
2308
+ }
2309
+ };
2310
+ context.nodes[id] = nativeNode;
2311
+ if (declaration) context.declarations.push({ ...declaration, nativeNode });
2312
+ if (node.hasError || kind === 'ERROR') {
2313
+ context.losses.push({
2314
+ id: `loss_${idFragment(id)}_tree_sitter_error`,
2315
+ severity: 'error',
2316
+ phase: 'parse',
2317
+ sourceFormat: context.input.language,
2318
+ kind: 'unsupportedSyntax',
2319
+ message: 'Tree-sitter reported a parse error node.',
2320
+ span,
2321
+ nodeId: id
2322
+ });
2323
+ }
2324
+ return id;
2325
+ }
2326
+
2327
+ function semanticIndexFromNativeDeclarations(declarations, input, options) {
2328
+ const documentId = `doc_${idFragment(input.sourcePath ?? input.language)}_${idFragment(input.sourceHash)}`;
2329
+ const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}_import`;
2330
+ const symbols = [];
2331
+ const occurrences = [];
2332
+ const relations = [];
2333
+ const facts = [];
2334
+ const mappings = [];
2335
+ for (const declaration of declarations) {
2336
+ const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${idFragment(declaration.name)}`;
2337
+ const occurrenceId = `occ_${idFragment(declaration.nativeNode.id)}_${declaration.role ?? 'definition'}`;
2338
+ symbols.push({
2339
+ id: symbolId,
2340
+ scheme: 'frontier',
2341
+ name: declaration.name,
2342
+ kind: declaration.symbolKind,
2343
+ language: input.language,
2344
+ nativeAstNodeId: declaration.nativeNode.id,
2345
+ signatureHash: hashSemanticValue([input.language, declaration.nativeNode.kind, declaration.name, declaration.nativeNode.fields ?? {}]),
2346
+ definitionSpan: declaration.nativeNode.span
2347
+ });
2348
+ occurrences.push({
2349
+ id: occurrenceId,
2350
+ documentId,
2351
+ symbolId,
2352
+ role: declaration.role ?? 'definition',
2353
+ span: declaration.nativeNode.span,
2354
+ nativeAstNodeId: declaration.nativeNode.id
2355
+ });
2356
+ relations.push({
2357
+ id: `rel_${idFragment(documentId)}_${idFragment(declaration.nativeNode.id)}`,
2358
+ sourceId: documentId,
2359
+ predicate: relationPredicateForDeclaration(declaration),
2360
+ targetId: symbolId
2361
+ });
2362
+ facts.push({
2363
+ id: `fact_${idFragment(declaration.nativeNode.id)}_kind`,
2364
+ predicate: 'nativeKind',
2365
+ subjectId: symbolId,
2366
+ value: declaration.nativeNode.languageKind
2367
+ });
2368
+ mappings.push({
2369
+ id: `map_${idFragment(declaration.nativeNode.id)}`,
2370
+ nativeAstNodeId: declaration.nativeNode.id,
2371
+ semanticSymbolId: symbolId,
2372
+ semanticOccurrenceId: occurrenceId,
2373
+ sourceSpan: declaration.nativeNode.span,
2374
+ evidenceIds: [evidenceId],
2375
+ lossIds: [],
2376
+ precision: declaration.nativeNode.span ? 'declaration' : 'unknown'
2377
+ });
2378
+ }
2379
+ const evidence = [{
2380
+ id: evidenceId,
2381
+ kind: 'import',
2382
+ status: 'passed',
2383
+ path: input.sourcePath,
2384
+ summary: `Normalized ${options.astFormat ?? options.parser} native AST with ${declarations.length} declaration(s).`,
2385
+ metadata: {
2386
+ parser: options.parser,
2387
+ astFormat: options.astFormat,
2388
+ language: input.language,
2389
+ sourceHash: input.sourceHash
2390
+ }
2391
+ }];
2392
+ return {
2393
+ semanticIndex: createSemanticIndexRecord({
2394
+ id: `index_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}`,
2395
+ documents: [{
2396
+ id: documentId,
2397
+ path: input.sourcePath ?? `${input.language}:memory`,
2398
+ language: input.language,
2399
+ sourceHash: input.sourceHash
2400
+ }],
2401
+ symbols,
2402
+ occurrences,
2403
+ relations,
2404
+ facts,
2405
+ evidence,
2406
+ metadata: {
2407
+ parser: options.parser,
2408
+ astFormat: options.astFormat,
2409
+ coverage: 'native-ast-declarations'
2410
+ }
2411
+ }),
2412
+ mappings,
2413
+ evidence
2414
+ };
2415
+ }
2416
+
2417
+ function createNativeProjectImportResult(input, imports) {
2418
+ const idPart = idFragment(input.id ?? input.projectRoot ?? 'native_project');
2419
+ const nodes = {};
2420
+ const rootIds = [];
2421
+ const semanticIndex = mergeSemanticIndexes(imports, input, idPart);
2422
+ const nativeSources = [];
2423
+ const sourceMaps = [];
2424
+ const losses = [];
2425
+ const evidence = [];
2426
+ const mergeCandidates = [];
2427
+ const operations = [];
2428
+ for (const result of imports) {
2429
+ for (const node of Object.values(result.document?.nodes ?? {})) {
2430
+ nodes[node.id] = node;
2431
+ }
2432
+ rootIds.push(...(result.document?.rootIds ?? []));
2433
+ if (result.nativeSource) nativeSources.push(result.nativeSource);
2434
+ sourceMaps.push(...(result.sourceMaps ?? []));
2435
+ losses.push(...(result.losses ?? []));
2436
+ evidence.push(...(result.evidence ?? []));
2437
+ mergeCandidates.push(...(result.mergeCandidates ?? []));
2438
+ operations.push(...(result.patch?.operations ?? []));
2439
+ }
2440
+ const document = createDocument({
2441
+ id: input.documentId ?? `document_${idPart}`,
2442
+ name: input.documentName ?? input.name ?? 'NativeProject',
2443
+ nodes: Object.values(nodes),
2444
+ rootIds: uniqueStrings(rootIds),
2445
+ metadata: {
2446
+ sourceLanguage: input.language ?? 'mixed',
2447
+ semanticStatus: losses.some((loss) => loss.severity === 'error') ? 'partial' : 'mapped',
2448
+ projectRoot: input.projectRoot,
2449
+ sourceCount: imports.length,
2450
+ ...input.documentMetadata
2451
+ }
2452
+ });
2453
+ const universalAst = createUniversalAstEnvelope({
2454
+ id: input.universalAstId ?? `universal_ast_${idPart}`,
2455
+ document,
2456
+ nativeSources,
2457
+ semanticIndex,
2458
+ sourceMaps,
2459
+ losses: uniqueByLossId(losses),
2460
+ evidence: uniqueByEvidenceId(evidence),
2461
+ metadata: {
2462
+ sourceLanguage: input.language ?? 'mixed',
2463
+ projectRoot: input.projectRoot,
2464
+ sourceCount: imports.length,
2465
+ ...input.universalAstMetadata
2466
+ }
2467
+ });
2468
+ const patch = createPatch({
2469
+ id: input.patchId ?? `patch_${idPart}_project_import`,
2470
+ author: input.author ?? '@shapeshift-labs/frontier-lang-compiler/importNativeProject',
2471
+ risk: losses.some((loss) => loss.severity === 'error') ? 'high' : losses.some((loss) => loss.severity === 'warning') ? 'medium' : 'low',
2472
+ operations,
2473
+ evidence: uniqueByEvidenceId(evidence),
2474
+ metadata: {
2475
+ semanticIndexId: semanticIndex?.id,
2476
+ universalAstId: universalAst.id,
2477
+ sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
2478
+ sourceCount: imports.length
2479
+ }
2480
+ });
2481
+ return {
2482
+ kind: 'frontier.lang.projectImportResult',
2483
+ version: 1,
2484
+ id: input.id ?? `project_import_${idPart}`,
2485
+ language: input.language ?? 'mixed',
2486
+ projectRoot: input.projectRoot,
2487
+ imports,
2488
+ document,
2489
+ patch,
2490
+ nativeSources,
2491
+ semanticIndex,
2492
+ universalAst,
2493
+ sourceMaps,
2494
+ losses: uniqueByLossId(losses),
2495
+ evidence: uniqueByEvidenceId(evidence),
2496
+ mergeCandidates,
2497
+ metadata: {
2498
+ sourceCount: imports.length,
2499
+ sourcePaths: imports.map((result) => result.sourcePath).filter(Boolean),
2500
+ ...input.metadata
2501
+ }
2502
+ };
2503
+ }
2504
+
2505
+ function mergeSemanticIndexes(imports, input, idPart) {
2506
+ const indexes = imports.map((result) => result.semanticIndex ?? result.universalAst?.semanticIndex).filter(Boolean);
2507
+ if (!indexes.length) return undefined;
2508
+ return createSemanticIndexRecord({
2509
+ id: input.semanticIndexId ?? `index_${idPart}_project`,
2510
+ documents: indexes.flatMap((index) => index.documents ?? []),
2511
+ symbols: indexes.flatMap((index) => index.symbols ?? []),
2512
+ occurrences: indexes.flatMap((index) => index.occurrences ?? []),
2513
+ relations: indexes.flatMap((index) => index.relations ?? []),
2514
+ facts: indexes.flatMap((index) => index.facts ?? []),
2515
+ evidence: indexes.flatMap((index) => index.evidence ?? []),
2516
+ metadata: {
2517
+ projectRoot: input.projectRoot,
2518
+ sourceCount: imports.length,
2519
+ mergedIndexCount: indexes.length
2520
+ }
2521
+ });
2522
+ }
2523
+
2524
+ function resolveNativeProjectAdapter(source, adapters, input) {
2525
+ if (typeof input.adapterResolver === 'function') return input.adapterResolver(source, adapters);
2526
+ const language = source.language;
2527
+ const sourcePath = source.sourcePath ?? '';
2528
+ return adapters.find((adapter) => {
2529
+ if (source.adapter && adapter.id === source.adapter) return true;
2530
+ if (language && adapter.language !== language) return false;
2531
+ const extensions = adapter.supportedExtensions ?? [];
2532
+ return !extensions.length || extensions.some((extension) => sourcePath.toLowerCase().endsWith(extension.toLowerCase()));
2533
+ });
2534
+ }
2535
+
721
2536
  function normalizeNativeImporterAdapter(adapter) {
722
2537
  if (!adapter || typeof adapter !== 'object') {
723
2538
  throw new Error('Native importer adapter must be an object');
@@ -859,6 +2674,336 @@ function serializableDiagnostic(diagnostic) {
859
2674
  };
860
2675
  }
861
2676
 
2677
+ function missingInjectedParserResult(input, details) {
2678
+ const rootId = `native_${idFragment(details.adapterId ?? details.parser)}_missing_parser`;
2679
+ const diagnostic = {
2680
+ severity: 'error',
2681
+ code: 'adapter.parser.missing',
2682
+ phase: 'parse',
2683
+ kind: 'unsupportedSyntax',
2684
+ message: details.message,
2685
+ path: input.sourcePath,
2686
+ metadata: {
2687
+ adapterId: details.adapterId,
2688
+ parser: details.parser
2689
+ }
2690
+ };
2691
+ return {
2692
+ rootId,
2693
+ nodes: {
2694
+ [rootId]: {
2695
+ id: rootId,
2696
+ kind: 'MissingInjectedParser',
2697
+ languageKind: `${input.language}.missingInjectedParser`,
2698
+ value: details.parser,
2699
+ metadata: {
2700
+ adapterId: details.adapterId,
2701
+ parser: details.parser,
2702
+ reason: 'missing-injected-parser'
2703
+ }
2704
+ }
2705
+ },
2706
+ diagnostics: [diagnostic],
2707
+ losses: [{
2708
+ id: `loss_${idFragment(rootId)}`,
2709
+ severity: 'error',
2710
+ phase: 'parse',
2711
+ sourceFormat: input.language,
2712
+ kind: 'unsupportedSyntax',
2713
+ message: details.message,
2714
+ nodeId: rootId,
2715
+ metadata: {
2716
+ adapterId: details.adapterId,
2717
+ parser: details.parser
2718
+ }
2719
+ }],
2720
+ metadata: {
2721
+ parser: details.parser,
2722
+ adapterId: details.adapterId,
2723
+ missingInjectedParser: true
2724
+ }
2725
+ };
2726
+ }
2727
+
2728
+ function normalizeParserErrors(errors, input, options) {
2729
+ return (errors ?? []).map((error, index) => ({
2730
+ id: `diagnostic_${idFragment(options.parser)}_parser_error_${index + 1}`,
2731
+ severity: 'error',
2732
+ code: error.code ?? error.reasonCode,
2733
+ phase: 'parse',
2734
+ kind: 'unsupportedSyntax',
2735
+ message: String(error.message ?? 'Parser reported a syntax error.'),
2736
+ path: input.sourcePath,
2737
+ span: spanFromLoc(error.loc ? { start: error.loc, end: error.loc } : undefined, input),
2738
+ metadata: {
2739
+ parser: options.parser,
2740
+ reasonCode: error.reasonCode
2741
+ }
2742
+ }));
2743
+ }
2744
+
2745
+ function nativeNodeId(context, kind, loc, propertyPath) {
2746
+ context.counter += 1;
2747
+ const start = loc?.start;
2748
+ const line = start?.line ?? 'x';
2749
+ const column = start?.column ?? 'x';
2750
+ return `native_${idFragment(kind)}_${idFragment(line)}_${idFragment(column)}_${context.counter}_${idFragment(propertyPath)}`;
2751
+ }
2752
+
2753
+ function isSyntaxAstNode(value) {
2754
+ return Boolean(value && typeof value === 'object' && typeof (value.type ?? value.kind) === 'string');
2755
+ }
2756
+
2757
+ function ignoredSyntaxField(key) {
2758
+ return key === 'type'
2759
+ || key === 'kind'
2760
+ || key === 'loc'
2761
+ || key === 'start'
2762
+ || key === 'end'
2763
+ || key === 'range'
2764
+ || key === 'comments'
2765
+ || key === 'leadingComments'
2766
+ || key === 'trailingComments'
2767
+ || key === 'innerComments'
2768
+ || key === 'tokens'
2769
+ || key === 'extra'
2770
+ || key === 'parent';
2771
+ }
2772
+
2773
+ function primitiveSyntaxFields(node) {
2774
+ const fields = {};
2775
+ for (const key of ['name', 'operator', 'sourceType', 'async', 'generator', 'computed', 'static', 'exportKind', 'importKind', 'optional']) {
2776
+ if (typeof node[key] === 'string' || typeof node[key] === 'number' || typeof node[key] === 'boolean' || node[key] === null) {
2777
+ fields[key] = node[key];
2778
+ }
2779
+ }
2780
+ const literal = literalSyntaxValue(node);
2781
+ if (literal !== undefined) fields.literal = literal;
2782
+ if (node.source && typeof node.source === 'object' && typeof node.source.value === 'string') fields.source = node.source.value;
2783
+ return fields;
2784
+ }
2785
+
2786
+ function literalSyntaxValue(node) {
2787
+ if (node.value === null || typeof node.value === 'string' || typeof node.value === 'number' || typeof node.value === 'boolean') return node.value;
2788
+ return undefined;
2789
+ }
2790
+
2791
+ function spanFromLoc(loc, input) {
2792
+ if (!loc?.start) return undefined;
2793
+ return {
2794
+ sourceId: input.sourceHash,
2795
+ path: input.sourcePath ?? loc.filename,
2796
+ startLine: loc.start.line,
2797
+ startColumn: typeof loc.start.column === 'number' ? loc.start.column + 1 : undefined,
2798
+ endLine: loc.end?.line,
2799
+ endColumn: typeof loc.end?.column === 'number' ? loc.end.column + 1 : undefined
2800
+ };
2801
+ }
2802
+
2803
+ function syntaxDeclaration(node, nativeNodeId, input) {
2804
+ const kind = String(node.type ?? node.kind ?? '');
2805
+ if (kind === 'ImportDeclaration') {
2806
+ const name = node.source?.value;
2807
+ if (typeof name === 'string') return declarationRecord(input, nativeNodeId, name, 'module', 'import');
2808
+ }
2809
+ if (kind === 'ExportNamedDeclaration' || kind === 'ExportAllDeclaration') {
2810
+ const name = node.source?.value;
2811
+ if (typeof name === 'string') return declarationRecord(input, nativeNodeId, name, 'module', 'export');
2812
+ }
2813
+ if (kind === 'FunctionDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'function');
2814
+ if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'class');
2815
+ if (kind === 'TSInterfaceDeclaration' || kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'interface');
2816
+ if (kind === 'TSTypeAliasDeclaration' || kind === 'TypeAliasDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'type');
2817
+ if (kind === 'VariableDeclarator') return namedDeclaration(input, nativeNodeId, node.id, 'variable');
2818
+ return undefined;
2819
+ }
2820
+
2821
+ function typeScriptDeclaration(node, kind, nativeNodeId, input) {
2822
+ if (kind === 'ImportDeclaration' || kind === 'ImportEqualsDeclaration') {
2823
+ const name = stringFromTsExpression(node.moduleSpecifier) ?? stringFromTsExpression(node.externalModuleReference?.expression);
2824
+ if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
2825
+ }
2826
+ if (kind === 'FunctionDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'function');
2827
+ if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'class');
2828
+ if (kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'interface');
2829
+ if (kind === 'TypeAliasDeclaration' || kind === 'EnumDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'type');
2830
+ if (kind === 'VariableDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'variable');
2831
+ if (kind === 'MethodDeclaration' || kind === 'MethodSignature') return namedDeclaration(input, nativeNodeId, node.name, 'method');
2832
+ return undefined;
2833
+ }
2834
+
2835
+ function treeSitterDeclaration(node, kind, nativeNodeId, input) {
2836
+ if (/import|include|use/.test(kind)) {
2837
+ const name = treeSitterFieldText(node, 'path') ?? treeSitterFieldText(node, 'source') ?? shortNodeText(node);
2838
+ if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
2839
+ }
2840
+ if (/function|method|fn_item|function_declaration/.test(kind)) {
2841
+ const name = treeSitterFieldText(node, 'name');
2842
+ if (name) return declarationRecord(input, nativeNodeId, name, 'function', 'definition');
2843
+ }
2844
+ if (/class/.test(kind)) {
2845
+ const name = treeSitterFieldText(node, 'name');
2846
+ if (name) return declarationRecord(input, nativeNodeId, name, 'class', 'definition');
2847
+ }
2848
+ if (/interface/.test(kind)) {
2849
+ const name = treeSitterFieldText(node, 'name');
2850
+ if (name) return declarationRecord(input, nativeNodeId, name, 'interface', 'definition');
2851
+ }
2852
+ if (/struct|enum|type/.test(kind)) {
2853
+ const name = treeSitterFieldText(node, 'name');
2854
+ if (name) return declarationRecord(input, nativeNodeId, name, 'type', 'definition');
2855
+ }
2856
+ return undefined;
2857
+ }
2858
+
2859
+ function namedDeclaration(input, nativeNodeId, nameNode, symbolKind) {
2860
+ const name = identifierName(nameNode);
2861
+ return name ? declarationRecord(input, nativeNodeId, name, symbolKind, 'definition') : undefined;
2862
+ }
2863
+
2864
+ function declarationRecord(input, nativeNodeId, name, symbolKind, role = 'definition') {
2865
+ return {
2866
+ name: String(name),
2867
+ symbolKind,
2868
+ role,
2869
+ symbolId: `symbol:${input.language}:${role === 'import' ? 'import:' : ''}${idFragment(name)}`,
2870
+ nativeNodeId
2871
+ };
2872
+ }
2873
+
2874
+ function relationPredicateForDeclaration(declaration) {
2875
+ if (declaration.role === 'import') return 'imports';
2876
+ if (declaration.role === 'export') return 'exports';
2877
+ return 'defines';
2878
+ }
2879
+
2880
+ function identifierName(node) {
2881
+ if (!node) return undefined;
2882
+ if (typeof node === 'string') return node;
2883
+ if (typeof node.name === 'string') return node.name;
2884
+ if (typeof node.escapedText === 'string') return node.escapedText;
2885
+ if (typeof node.text === 'string') return node.text;
2886
+ if (node.type === 'Identifier' && typeof node.value === 'string') return node.value;
2887
+ return undefined;
2888
+ }
2889
+
2890
+ function stringFromTsExpression(node) {
2891
+ if (!node) return undefined;
2892
+ if (typeof node.text === 'string') return node.text;
2893
+ if (typeof node.value === 'string') return node.value;
2894
+ return identifierName(node);
2895
+ }
2896
+
2897
+ function typeScriptKindName(node, ts) {
2898
+ if (typeof node.kindName === 'string') return node.kindName;
2899
+ if (ts?.SyntaxKind && node.kind !== undefined) return ts.SyntaxKind[node.kind] ?? `SyntaxKind${node.kind}`;
2900
+ if (typeof node.kind === 'string') return node.kind;
2901
+ return `SyntaxKind${node.kind ?? 'Unknown'}`;
2902
+ }
2903
+
2904
+ function spanFromTypeScriptNode(node, sourceFile) {
2905
+ const start = typeof node.getStart === 'function' ? node.getStart(sourceFile) : node.pos;
2906
+ const end = typeof node.getEnd === 'function' ? node.getEnd() : node.end;
2907
+ if (typeof start !== 'number' || typeof sourceFile?.getLineAndCharacterOfPosition !== 'function') return undefined;
2908
+ const startPos = sourceFile.getLineAndCharacterOfPosition(start);
2909
+ const endPos = typeof end === 'number' ? sourceFile.getLineAndCharacterOfPosition(end) : undefined;
2910
+ return {
2911
+ sourceId: sourceFile.sourceHash,
2912
+ path: sourceFile.fileName,
2913
+ startLine: startPos.line + 1,
2914
+ startColumn: startPos.character + 1,
2915
+ endLine: endPos ? endPos.line + 1 : undefined,
2916
+ endColumn: endPos ? endPos.character + 1 : undefined
2917
+ };
2918
+ }
2919
+
2920
+ function typeScriptNodeValue(node) {
2921
+ return identifierName(node.name) ?? stringFromTsExpression(node.moduleSpecifier) ?? undefined;
2922
+ }
2923
+
2924
+ function primitiveTypeScriptFields(node, kind) {
2925
+ const fields = { kind };
2926
+ const name = identifierName(node.name);
2927
+ if (name) fields.name = name;
2928
+ const moduleSpecifier = stringFromTsExpression(node.moduleSpecifier);
2929
+ if (moduleSpecifier) fields.moduleSpecifier = moduleSpecifier;
2930
+ return fields;
2931
+ }
2932
+
2933
+ function spanFromTreeSitterNode(node, input) {
2934
+ const start = node.startPosition;
2935
+ if (!start) return undefined;
2936
+ const end = node.endPosition;
2937
+ return {
2938
+ sourceId: input.sourceHash,
2939
+ path: input.sourcePath,
2940
+ startLine: start.row + 1,
2941
+ startColumn: start.column + 1,
2942
+ endLine: end ? end.row + 1 : undefined,
2943
+ endColumn: end ? end.column + 1 : undefined
2944
+ };
2945
+ }
2946
+
2947
+ function treeSitterFieldText(node, field) {
2948
+ if (typeof node.childForFieldName !== 'function') return undefined;
2949
+ return shortNodeText(node.childForFieldName(field));
2950
+ }
2951
+
2952
+ function shortNodeText(node) {
2953
+ if (!node || typeof node.text !== 'string') return undefined;
2954
+ const text = node.text.trim();
2955
+ if (!text || text.length > 160) return undefined;
2956
+ return text.replace(/^['"]|['"]$/g, '');
2957
+ }
2958
+
2959
+ function truncatedAstLoss(input, context, options) {
2960
+ return {
2961
+ id: `loss_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}_truncated`,
2962
+ severity: 'warning',
2963
+ phase: 'read',
2964
+ sourceFormat: input.language,
2965
+ kind: 'opaqueNative',
2966
+ message: `Native AST normalization stopped after ${context.maxNodes} node(s).`,
2967
+ metadata: {
2968
+ parser: options.parser,
2969
+ astFormat: options.astFormat,
2970
+ maxNodes: context.maxNodes
2971
+ }
2972
+ };
2973
+ }
2974
+
2975
+ function uniqueStrings(values) {
2976
+ return [...new Set((values ?? []).map((value) => String(value)).filter(Boolean))];
2977
+ }
2978
+
2979
+ function uniqueByLossId(values) {
2980
+ const seen = new Set();
2981
+ const result = [];
2982
+ for (const value of values ?? []) {
2983
+ const id = value?.id ?? `loss_${result.length + 1}`;
2984
+ if (seen.has(id)) continue;
2985
+ seen.add(id);
2986
+ result.push(value.id ? value : { ...value, id });
2987
+ }
2988
+ return result;
2989
+ }
2990
+
2991
+ function uniqueByEvidenceId(values) {
2992
+ const seen = new Set();
2993
+ const result = [];
2994
+ for (const value of values ?? []) {
2995
+ const id = value?.id ?? `evidence_${result.length + 1}`;
2996
+ if (seen.has(id)) continue;
2997
+ seen.add(id);
2998
+ result.push(value.id ? value : { ...value, id });
2999
+ }
3000
+ return result;
3001
+ }
3002
+
3003
+ function numberOrUndefined(value) {
3004
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
3005
+ }
3006
+
862
3007
  function idFragment(value) {
863
3008
  return String(value ?? 'native')
864
3009
  .toLowerCase()