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

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
@@ -73,27 +73,42 @@ export const NativeImportTaxonomyKinds = Object.freeze([
73
73
  'opaqueBodies',
74
74
  'macroExpansion',
75
75
  'preprocessor',
76
+ 'conditionalCompilation',
76
77
  'metaprogramming',
78
+ 'reflection',
77
79
  'generatedCode',
80
+ 'overloadTypeInference',
78
81
  'sourcePreservation',
82
+ 'commentsTrivia',
79
83
  'parserDiagnostics',
80
84
  'unsupportedSyntax',
81
85
  'partialSemanticIndex',
82
- 'sourceMapApproximation'
86
+ 'sourceMapApproximation',
87
+ 'targetProjectionLoss'
83
88
  ]);
84
89
 
85
90
  export const NativeImportLossKinds = Object.freeze([
86
91
  'declarationOnlyCoverage',
87
92
  'opaqueNative',
88
93
  'macroExpansion',
94
+ 'macroHygiene',
89
95
  'preprocessor',
96
+ 'conditionalCompilation',
90
97
  'metaprogramming',
98
+ 'reflection',
99
+ 'dynamicRuntime',
100
+ 'dynamicDispatch',
91
101
  'generatedCode',
102
+ 'overloadResolution',
103
+ 'typeInference',
92
104
  'sourcePreservation',
105
+ 'commentsTrivia',
93
106
  'parserDiagnostic',
94
107
  'unsupportedSyntax',
108
+ 'unsupportedSemantic',
95
109
  'partialSemanticIndex',
96
- 'sourceMapApproximation'
110
+ 'sourceMapApproximation',
111
+ 'targetProjectionLoss'
97
112
  ]);
98
113
 
99
114
  export const NativeImportReadinessBySeverity = Object.freeze({
@@ -103,6 +118,62 @@ export const NativeImportReadinessBySeverity = Object.freeze({
103
118
  error: 'blocked'
104
119
  });
105
120
 
121
+ export const NativeImportLanguageProfiles = Object.freeze([
122
+ nativeImportLanguageProfile('javascript', {
123
+ aliases: ['js', 'mjs', 'cjs', 'jsx'],
124
+ extensions: ['.js', '.mjs', '.cjs', '.jsx'],
125
+ parserAdapters: ['estree', 'babel', 'tree-sitter'],
126
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'dynamicRuntime']
127
+ }),
128
+ nativeImportLanguageProfile('typescript', {
129
+ aliases: ['ts', 'tsx'],
130
+ extensions: ['.ts', '.tsx'],
131
+ parserAdapters: ['typescript-compiler-api', 'babel', 'tree-sitter'],
132
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'unsupportedSyntax']
133
+ }),
134
+ nativeImportLanguageProfile('python', {
135
+ aliases: ['py'],
136
+ extensions: ['.py', '.pyi'],
137
+ parserAdapters: ['python-ast', 'libcst', 'parso', 'tree-sitter'],
138
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'dynamicRuntime']
139
+ }),
140
+ nativeImportLanguageProfile('rust', {
141
+ aliases: ['rs'],
142
+ extensions: ['.rs'],
143
+ parserAdapters: ['syn', 'rust-analyzer-rowan', 'tree-sitter'],
144
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation']
145
+ }),
146
+ nativeImportLanguageProfile('c', {
147
+ aliases: ['h'],
148
+ extensions: ['.c', '.h'],
149
+ parserAdapters: ['clang', 'libclang', 'tree-sitter'],
150
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'preprocessor', 'sourceMapApproximation', 'sourcePreservation']
151
+ }),
152
+ nativeImportLanguageProfile('cpp', {
153
+ aliases: ['c++', 'cc', 'cxx', 'hpp'],
154
+ extensions: ['.cc', '.cpp', '.cxx', '.hpp', '.hh'],
155
+ parserAdapters: ['clang', 'libclang', 'tree-sitter'],
156
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'preprocessor', 'metaprogramming', 'sourceMapApproximation', 'sourcePreservation']
157
+ }),
158
+ nativeImportLanguageProfile('java', { extensions: ['.java'], parserAdapters: ['javac', 'jdt', 'javaparser', 'tree-sitter'] }),
159
+ nativeImportLanguageProfile('go', { extensions: ['.go'], parserAdapters: ['go/parser', 'tree-sitter'] }),
160
+ nativeImportLanguageProfile('swift', { extensions: ['.swift'], parserAdapters: ['swift-syntax', 'tree-sitter'] }),
161
+ nativeImportLanguageProfile('csharp', { aliases: ['c#', 'cs'], extensions: ['.cs'], parserAdapters: ['roslyn', 'tree-sitter'] }),
162
+ nativeImportLanguageProfile('php', { extensions: ['.php'], parserAdapters: ['php-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'metaprogramming', 'sourceMapApproximation', 'sourcePreservation'] }),
163
+ nativeImportLanguageProfile('ruby', { aliases: ['rb'], extensions: ['.rb', '.rake'], parserAdapters: ['prism', 'ripper', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'metaprogramming', 'sourceMapApproximation', 'sourcePreservation'] }),
164
+ nativeImportLanguageProfile('kotlin', { aliases: ['kt', 'kts'], extensions: ['.kt', '.kts'], parserAdapters: ['kotlin-compiler', 'intellij-psi', 'tree-sitter'] }),
165
+ nativeImportLanguageProfile('scala', { aliases: ['sc'], extensions: ['.scala', '.sc'], parserAdapters: ['scala-compiler', 'scalameta', 'tree-sitter'] }),
166
+ nativeImportLanguageProfile('dart', { extensions: ['.dart'], parserAdapters: ['dart-analyzer', 'tree-sitter'] }),
167
+ nativeImportLanguageProfile('lua', { extensions: ['.lua'], parserAdapters: ['luaparse', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] }),
168
+ nativeImportLanguageProfile('shell', { aliases: ['sh', 'bash', 'zsh'], extensions: ['.sh', '.bash', '.zsh'], parserAdapters: ['bash-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] }),
169
+ nativeImportLanguageProfile('sql', { aliases: ['postgresql', 'postgres', 'mysql', 'sqlite'], extensions: ['.sql'], parserAdapters: ['sqlparser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'unsupportedSyntax', 'sourceMapApproximation', 'sourcePreservation'] }),
170
+ nativeImportLanguageProfile('zig', { extensions: ['.zig'], parserAdapters: ['zig-ast', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'generatedCode', 'sourceMapApproximation', 'sourcePreservation'] }),
171
+ nativeImportLanguageProfile('elixir', { aliases: ['ex', 'exs'], extensions: ['.ex', '.exs'], parserAdapters: ['elixir-quoted', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation'] }),
172
+ nativeImportLanguageProfile('erlang', { aliases: ['erl', 'hrl'], extensions: ['.erl', '.hrl'], parserAdapters: ['erl_parse', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'preprocessor', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation'] }),
173
+ nativeImportLanguageProfile('haskell', { aliases: ['hs'], extensions: ['.hs', '.lhs'], parserAdapters: ['ghc-api', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation'] }),
174
+ nativeImportLanguageProfile('r', { aliases: ['R'], extensions: ['.r', '.R'], parserAdapters: ['r-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] })
175
+ ]);
176
+
106
177
  export function normalizeCompileTarget(target) {
107
178
  const normalized = String(target ?? 'typescript').toLowerCase();
108
179
  const canonical = canonicalTargets[normalized] ?? normalized;
@@ -256,6 +327,181 @@ export function classifyNativeImportReadiness(losses = [], options = {}) {
256
327
  };
257
328
  }
258
329
 
330
+ export function createNativeImportCoverageMatrix(input = {}) {
331
+ const imports = input.imports ?? [];
332
+ const adapters = input.adapters ?? [];
333
+ const profiles = mergeNativeImportProfiles(input.languages ?? NativeImportLanguageProfiles, imports, adapters);
334
+ const languages = profiles.map((profile) => nativeImportCoverageForProfile(profile, imports, adapters));
335
+ const summary = languages.reduce((totals, entry) => {
336
+ totals.languages += 1;
337
+ if (entry.supportsLightweightScan) totals.lightweightScanners += 1;
338
+ if (entry.parserAdapters.length) totals.parserAdapterSlots += entry.parserAdapters.length;
339
+ totals.imports += entry.imports.total;
340
+ totals.symbols += entry.imports.symbols;
341
+ totals.sourceMaps += entry.imports.sourceMaps;
342
+ totals.sourceMapMappings += entry.imports.sourceMapMappings;
343
+ totals.losses += entry.imports.losses;
344
+ totals.byReadiness[entry.imports.readiness] = (totals.byReadiness[entry.imports.readiness] ?? 0) + 1;
345
+ for (const [kind, count] of Object.entries(entry.imports.lossKinds)) {
346
+ totals.lossKinds[kind] = (totals.lossKinds[kind] ?? 0) + count;
347
+ }
348
+ return totals;
349
+ }, {
350
+ languages: 0,
351
+ lightweightScanners: 0,
352
+ parserAdapterSlots: 0,
353
+ imports: 0,
354
+ symbols: 0,
355
+ sourceMaps: 0,
356
+ sourceMapMappings: 0,
357
+ losses: 0,
358
+ byReadiness: {},
359
+ lossKinds: {}
360
+ });
361
+ return {
362
+ kind: 'frontier.lang.nativeImportCoverageMatrix',
363
+ version: 1,
364
+ generatedAt: input.generatedAt ?? Date.now(),
365
+ languages,
366
+ summary,
367
+ metadata: {
368
+ compileTargets: [...FrontierCompileTargets],
369
+ note: 'Coverage is evidence and capability metadata, not a claim that every language feature is losslessly portable.'
370
+ }
371
+ };
372
+ }
373
+
374
+ export function createNativeSourcePreservation(options) {
375
+ if (!options || typeof options.sourceText !== 'string') {
376
+ throw new Error('createNativeSourcePreservation requires sourceText');
377
+ }
378
+ const language = options.language ?? 'source';
379
+ const sourceText = options.sourceText;
380
+ const computedSourceHash = hashSemanticValue(sourceText);
381
+ const declaredSourceHash = options.sourceHash;
382
+ const sourceHash = computedSourceHash;
383
+ const tokensAndTrivia = scanPreservedSourceTokens(sourceText, {
384
+ language,
385
+ sourcePath: options.sourcePath,
386
+ sourceHash,
387
+ includeTokens: options.includeTokens !== false,
388
+ includeTrivia: options.includeTrivia !== false,
389
+ maxTokens: options.maxTokens,
390
+ maxTrivia: options.maxTrivia
391
+ });
392
+ const directiveScan = options.includeDirectives === false
393
+ ? { directives: [], truncated: false }
394
+ : scanPreservedSourceDirectives(sourceText, {
395
+ language,
396
+ sourcePath: options.sourcePath,
397
+ sourceHash,
398
+ maxDirectives: options.maxDirectives
399
+ });
400
+ const directives = directiveScan.directives;
401
+ const newline = detectNewlineStyle(sourceText);
402
+ return {
403
+ kind: 'frontier.lang.nativeSourcePreservation',
404
+ version: 1,
405
+ id: options.id ?? `native_source_preservation_${idFragment(options.sourcePath ?? language)}_${idFragment(sourceHash)}`,
406
+ language,
407
+ sourcePath: options.sourcePath,
408
+ sourceHash,
409
+ sourceBytes: Buffer.byteLength(sourceText, options.encoding ?? 'utf8'),
410
+ lineCount: sourceText.length ? sourceText.split(/\r\n|\r|\n/).length : 0,
411
+ newline,
412
+ encoding: options.encoding ?? 'utf8',
413
+ ...(options.includeSourceText === false ? {} : { sourceText }),
414
+ tokens: tokensAndTrivia.tokens,
415
+ trivia: tokensAndTrivia.trivia,
416
+ directives,
417
+ summary: {
418
+ tokens: tokensAndTrivia.tokens.length,
419
+ trivia: tokensAndTrivia.trivia.length,
420
+ directives: directives.length,
421
+ comments: tokensAndTrivia.trivia.filter((entry) => entry.kind === 'comment').length,
422
+ whitespace: tokensAndTrivia.trivia.filter((entry) => entry.kind === 'whitespace' || entry.kind === 'newline').length,
423
+ exactSourceAvailable: options.includeSourceText !== false,
424
+ truncated: tokensAndTrivia.truncated || directiveScan.truncated
425
+ },
426
+ metadata: {
427
+ preservation: 'source-text-token-trivia-directive-evidence',
428
+ tokenization: 'frontier-lightweight-lexical-scan',
429
+ ...(declaredSourceHash ? {
430
+ declaredSourceHash,
431
+ sourceHashVerified: declaredSourceHash === computedSourceHash
432
+ } : {}),
433
+ ...options.metadata
434
+ }
435
+ };
436
+ }
437
+
438
+ export function createSemanticImportSidecar(importResult, options = {}) {
439
+ const imports = Array.isArray(importResult?.imports) ? importResult.imports : [importResult].filter(Boolean);
440
+ const importEntries = imports.map((imported, index) => semanticImportSidecarEntry(imported, index, options));
441
+ const symbols = importEntries.flatMap((entry) => entry.symbols);
442
+ const ownershipRegions = uniqueRecordsById(importEntries.flatMap((entry) => entry.ownershipRegions));
443
+ const sourceMaps = imports.flatMap((imported) => imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? []);
444
+ const sourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap?.mappings ?? []);
445
+ const losses = imports.flatMap((imported) => imported?.losses ?? []);
446
+ const evidence = uniqueRecordsById(imports.flatMap((imported) => imported?.evidence ?? []));
447
+ const mergeCandidates = imports.flatMap((imported) => imported?.mergeCandidates ?? []);
448
+ const lossSummary = summarizeNativeImportLosses(losses, { evidence });
449
+ const readiness = mergeCandidates.reduce(
450
+ (current, candidate) => maxSemanticMergeReadiness(current, candidate.readiness),
451
+ lossSummary.semanticMergeReadiness
452
+ );
453
+ const patchHints = ownershipRegions.map((region) => semanticPatchHintForRegion(region, readiness, options));
454
+ return {
455
+ kind: 'frontier.lang.semanticImportSidecar',
456
+ version: 1,
457
+ id: options.id ?? `semantic_import_${idFragment(importResult?.id ?? importResult?.projectRoot ?? imports[0]?.sourcePath ?? imports[0]?.language ?? 'source')}`,
458
+ generatedAt: options.generatedAt ?? Date.now(),
459
+ language: importResult?.language ?? (imports.length === 1 ? imports[0]?.language : 'mixed'),
460
+ projectRoot: importResult?.projectRoot,
461
+ imports: importEntries.map(({ ownershipRegions: _regions, symbols: _symbols, ...entry }) => entry),
462
+ symbols,
463
+ ownershipRegions,
464
+ sourceMaps: {
465
+ total: sourceMaps.length,
466
+ mappings: sourceMapMappings.length,
467
+ ids: sourceMaps.map((sourceMap) => sourceMap.id).filter(Boolean)
468
+ },
469
+ patchHints,
470
+ mergeCandidates: mergeCandidates.map((candidate) => ({
471
+ id: candidate.id,
472
+ readiness: candidate.readiness,
473
+ reasons: candidate.reasons ?? [],
474
+ risk: candidate.risk,
475
+ operationCount: candidate.operations?.length ?? candidate.patch?.operations?.length ?? 0
476
+ })),
477
+ losses: {
478
+ total: losses.length,
479
+ byKind: lossSummary.byKind,
480
+ bySeverity: lossSummary.bySeverity,
481
+ categories: lossSummary.categories,
482
+ blockingLossIds: lossSummary.blockingLossIds,
483
+ reviewLossIds: lossSummary.reviewLossIds
484
+ },
485
+ evidence: {
486
+ total: evidence.length,
487
+ failed: evidence.filter((record) => record.status === 'failed').map((record) => record.id),
488
+ ids: evidence.map((record) => record.id)
489
+ },
490
+ summary: {
491
+ imports: imports.length,
492
+ symbols: symbols.length,
493
+ ownershipRegions: ownershipRegions.length,
494
+ sourceMapMappings: sourceMapMappings.length,
495
+ readiness,
496
+ emptySemanticIndex: symbols.length === 0
497
+ },
498
+ metadata: {
499
+ note: 'Sidecar is source-addressable semantic evidence for merge admission; lightweight scanner regions remain review-required unless exact parser evidence upgrades readiness.',
500
+ ...options.metadata
501
+ }
502
+ };
503
+ }
504
+
259
505
  export function createEstreeNativeImporterAdapter(options = {}) {
260
506
  return createJavaScriptSyntaxImporterAdapter({
261
507
  id: 'frontier.estree-native-importer',
@@ -291,6 +537,20 @@ export function createTypeScriptCompilerNativeImporterAdapter(options = {}) {
291
537
  parser: options.parser ?? 'typescript-compiler-api',
292
538
  version: options.version,
293
539
  capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
540
+ coverage: nativeImporterAdapterCoverage({
541
+ exactness: 'exact-parser-ast',
542
+ exactAst: true,
543
+ tokens: false,
544
+ trivia: false,
545
+ diagnostics: true,
546
+ sourceRanges: true,
547
+ generatedRanges: false,
548
+ semanticCoverage: declarationSemanticCoverage(),
549
+ notes: [
550
+ 'Normalizes a caller-owned TypeScript SourceFile into native AST nodes and declaration-level semantic index records.',
551
+ 'Type resolution, reference resolution, control flow, generated ranges, and parser token/trivia streams require host-supplied adapter evidence.'
552
+ ]
553
+ }, options.coverage),
294
554
  supportedExtensions: options.supportedExtensions ?? ['.ts', '.tsx', '.js', '.jsx'],
295
555
  diagnostics: options.diagnostics,
296
556
  parse(input) {
@@ -321,6 +581,20 @@ export function createTreeSitterNativeImporterAdapter(options = {}) {
321
581
  parser: options.parserName ?? options.parser ?? 'tree-sitter',
322
582
  version: options.version,
323
583
  capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
584
+ coverage: nativeImporterAdapterCoverage({
585
+ exactness: 'parser-tree',
586
+ exactAst: true,
587
+ tokens: false,
588
+ trivia: false,
589
+ diagnostics: true,
590
+ sourceRanges: true,
591
+ generatedRanges: false,
592
+ semanticCoverage: declarationSemanticCoverage(),
593
+ notes: [
594
+ 'Normalizes a caller-owned tree-sitter tree into native AST nodes and declaration-level semantic index records.',
595
+ 'The built-in wrapper walks named syntax nodes; exact token/trivia streams and generated ranges require adapter-specific evidence.'
596
+ ]
597
+ }, options.coverage),
324
598
  supportedExtensions: options.supportedExtensions ?? [],
325
599
  diagnostics: options.diagnostics,
326
600
  parse(input) {
@@ -425,7 +699,14 @@ export async function runNativeImporterAdapter(adapter, input = {}) {
425
699
  parseResult.losses,
426
700
  diagnostics.map((diagnostic, index) => adapterDiagnosticToLoss(diagnostic, index, summary, parseInput))
427
701
  );
428
- const sourceEvidence = adapterDiagnosticsEvidence(summary, diagnostics, {
702
+ const adapterSummary = {
703
+ ...summary,
704
+ coverage: observeNativeImporterAdapterCoverage(summary.coverage, parseResult, {
705
+ diagnostics,
706
+ losses
707
+ })
708
+ };
709
+ const sourceEvidence = adapterDiagnosticsEvidence(adapterSummary, diagnostics, {
429
710
  language,
430
711
  parser,
431
712
  parserVersion,
@@ -447,8 +728,9 @@ export async function runNativeImporterAdapter(adapter, input = {}) {
447
728
  metadata: {
448
729
  adapterId: summary.id,
449
730
  adapterVersion: summary.version,
450
- adapterCapabilities: summary.capabilities,
451
- supportedExtensions: summary.supportedExtensions,
731
+ adapterCapabilities: adapterSummary.capabilities,
732
+ adapterCoverage: adapterSummary.coverage,
733
+ supportedExtensions: adapterSummary.supportedExtensions,
452
734
  diagnostics: diagnostics.map(serializableDiagnostic),
453
735
  ...input.metadata,
454
736
  ...parseResult.metadata
@@ -457,6 +739,7 @@ export async function runNativeImporterAdapter(adapter, input = {}) {
457
739
  adapterId: summary.id,
458
740
  adapterVersion: summary.version,
459
741
  parser,
742
+ adapterCoverage: adapterSummary.coverage,
460
743
  ...input.nativeAstMetadata,
461
744
  ...parseResult.nativeAstMetadata
462
745
  },
@@ -464,6 +747,7 @@ export async function runNativeImporterAdapter(adapter, input = {}) {
464
747
  adapterId: summary.id,
465
748
  adapterVersion: summary.version,
466
749
  parser,
750
+ adapterCoverage: adapterSummary.coverage,
467
751
  ...input.nativeSourceMetadata,
468
752
  ...parseResult.nativeSourceMetadata
469
753
  },
@@ -482,26 +766,119 @@ export async function runNativeImporterAdapter(adapter, input = {}) {
482
766
  };
483
767
  return {
484
768
  ...importNativeSource(importInput),
485
- adapter: summary,
769
+ adapter: adapterSummary,
486
770
  diagnostics
487
771
  };
488
772
  }
489
773
 
774
+ export function projectNativeImportToSource(importResult, options = {}) {
775
+ if (!importResult || typeof importResult !== 'object') {
776
+ throw new Error('projectNativeImportToSource requires a native import result');
777
+ }
778
+ const context = nativeImportProjectionContext(importResult, options);
779
+ const candidateSource = nativeProjectionSourceCandidate(context, options);
780
+ const declarations = nativeProjectionDeclarations(importResult, context);
781
+ const preserveSource = options.preferPreservedSource !== false && candidateSource?.exact === true;
782
+ const mode = preserveSource ? 'preserved-source' : 'native-source-stubs';
783
+ const sourceText = preserveSource
784
+ ? candidateSource.sourceText
785
+ : renderNativeProjectionStubs(context, declarations, options);
786
+ const losses = preserveSource ? [] : nativeProjectionStubLosses(context, candidateSource, declarations, options);
787
+ const evidence = [{
788
+ id: options.evidenceId ?? `evidence_${context.idPart}_native_source_projection`,
789
+ kind: 'projection',
790
+ status: losses.some((loss) => loss.severity === 'error') ? 'failed' : 'passed',
791
+ path: context.sourcePath,
792
+ summary: preserveSource
793
+ ? `Preserved exact ${context.language} source for native projection.`
794
+ : `Projected ${context.language} native import to ${declarations.length} declaration stub(s).`,
795
+ metadata: {
796
+ mode,
797
+ language: context.language,
798
+ sourcePath: context.sourcePath,
799
+ expectedSourceHash: context.sourceHash,
800
+ providedSourceHash: candidateSource?.sourceHash,
801
+ sourcePreservationId: candidateSource?.sourcePreservationId,
802
+ sourceHashVerified: candidateSource?.hashVerified ?? false,
803
+ declarationCount: declarations.length
804
+ }
805
+ }];
806
+ const lossSummary = summarizeNativeImportLosses(losses, {
807
+ evidence,
808
+ parser: context.parser,
809
+ scanKind: 'native-source-projection',
810
+ semanticStatus: context.semanticStatus
811
+ });
812
+ const readiness = classifyNativeImportReadiness(losses, {
813
+ evidence,
814
+ parser: context.parser,
815
+ scanKind: 'native-source-projection',
816
+ semanticStatus: context.semanticStatus
817
+ });
818
+ const nativeImportLossSummary = importResult.metadata?.nativeImportLossSummary ?? summarizeNativeImportLosses(importResult.losses ?? context.nativeAst?.losses ?? [], {
819
+ evidence: importResult.evidence,
820
+ parser: context.parser,
821
+ semanticStatus: context.semanticStatus
822
+ });
823
+ return {
824
+ kind: 'frontier.lang.nativeSourceProjection',
825
+ version: 1,
826
+ id: options.id ?? `native_source_projection_${context.idPart}`,
827
+ language: context.language,
828
+ sourcePath: context.sourcePath,
829
+ sourceHash: context.sourceHash,
830
+ mode,
831
+ sourceText,
832
+ outputHash: hashSemanticValue(sourceText),
833
+ declarations,
834
+ losses,
835
+ lossSummary,
836
+ readiness,
837
+ evidence,
838
+ metadata: {
839
+ nativeImportId: importResult.id,
840
+ nativeSourceId: context.nativeSource?.id,
841
+ nativeAstId: context.nativeAst?.id,
842
+ semanticIndexId: context.semanticIndex?.id,
843
+ universalAstId: importResult.universalAst?.id,
844
+ exactSourceAvailable: candidateSource?.exact === true,
845
+ sourceTextAvailable: typeof candidateSource?.sourceText === 'string',
846
+ sourcePreservationId: candidateSource?.sourcePreservationId,
847
+ sourceHashVerified: candidateSource?.hashVerified ?? false,
848
+ nativeImportLossSummary,
849
+ ...options.metadata
850
+ }
851
+ };
852
+ }
853
+
490
854
  export function importNativeSource(input) {
491
855
  const language = input.language ?? input.nativeAst?.language;
492
856
  if (!language) throw new Error('importNativeSource requires a language or nativeAst.language');
493
857
  const sourcePath = input.sourcePath ?? input.nativeAst?.sourcePath;
494
- const sourceHash = input.sourceHash ?? input.nativeAst?.sourceHash ?? (input.sourceText ? hashSemanticValue(input.sourceText) : hashSemanticValue(input.nativeAst?.nodes ?? input.nativeAst ?? {}));
858
+ const declaredSourceHash = input.sourceHash ?? input.nativeAst?.sourceHash;
859
+ const sourceHash = typeof input.sourceText === 'string'
860
+ ? hashSemanticValue(input.sourceText)
861
+ : declaredSourceHash ?? hashSemanticValue(input.nativeAst?.nodes ?? input.nativeAst ?? {});
495
862
  const targetPath = input.targetPath ?? input.target?.emitPath;
496
863
  const targetHash = input.targetHash;
497
864
  const importIdPart = idFragment(input.id ?? input.nativeSourceId ?? sourcePath ?? language);
865
+ const sourcePreservation = input.sourcePreservation ?? (typeof input.sourceText === 'string'
866
+ ? createNativeSourcePreservation({
867
+ language,
868
+ sourcePath,
869
+ sourceHash: declaredSourceHash,
870
+ sourceText: input.sourceText,
871
+ metadata: { importIdPart }
872
+ })
873
+ : undefined);
498
874
  const lightweight = !input.nativeAst && !input.nodes && input.sourceText
499
875
  ? createLightweightNativeImport({
500
876
  language,
501
877
  sourceText: input.sourceText,
502
878
  sourcePath,
503
879
  sourceHash,
504
- parser: input.parser
880
+ parser: input.parser,
881
+ sourcePreservation
505
882
  })
506
883
  : undefined;
507
884
  const nativeAst = input.nativeAst ?? createNativeAstRecord({
@@ -524,6 +901,15 @@ export function importNativeSource(input) {
524
901
  losses: input.losses ?? lightweight?.losses,
525
902
  metadata: {
526
903
  ...(input.sourceText ? { sourceBytes: input.sourceText.length } : {}),
904
+ ...(sourcePreservation ? {
905
+ sourcePreservationId: sourcePreservation.id,
906
+ sourcePreservationSummary: sourcePreservation.summary,
907
+ sourcePreservation
908
+ } : {}),
909
+ ...(declaredSourceHash && declaredSourceHash !== sourceHash ? {
910
+ declaredSourceHash,
911
+ sourceHashVerified: false
912
+ } : {}),
527
913
  ...lightweight?.metadata,
528
914
  ...input.nativeAstMetadata
529
915
  }
@@ -548,6 +934,14 @@ export function importNativeSource(input) {
548
934
  metadata: {
549
935
  semanticStatus,
550
936
  mappings: input.mappings ?? [],
937
+ ...(sourcePreservation ? {
938
+ sourcePreservationId: sourcePreservation.id,
939
+ sourcePreservation
940
+ } : {}),
941
+ ...(declaredSourceHash && declaredSourceHash !== sourceHash ? {
942
+ declaredSourceHash,
943
+ sourceHashVerified: false
944
+ } : {}),
551
945
  ...input.nativeSourceMetadata
552
946
  }
553
947
  });
@@ -571,7 +965,16 @@ export function importNativeSource(input) {
571
965
  metadata: {
572
966
  parser: nativeAst.parser,
573
967
  sourcePath,
574
- semanticStatus
968
+ semanticStatus,
969
+ ...(sourcePreservation ? {
970
+ sourcePreservationId: sourcePreservation.id,
971
+ sourcePreservationSummary: sourcePreservation.summary
972
+ } : {})
973
+ ,
974
+ ...(declaredSourceHash && declaredSourceHash !== sourceHash ? {
975
+ declaredSourceHash,
976
+ sourceHashVerified: false
977
+ } : {})
575
978
  }
576
979
  }];
577
980
  const lossSummary = summarizeNativeImportLosses(losses, {
@@ -651,6 +1054,14 @@ export function importNativeSource(input) {
651
1054
  sourcePath,
652
1055
  semanticStatus,
653
1056
  nativeImportLossSummary: lossSummary,
1057
+ ...(sourcePreservation ? {
1058
+ sourcePreservationId: sourcePreservation.id,
1059
+ sourcePreservation
1060
+ } : {}),
1061
+ ...(declaredSourceHash && declaredSourceHash !== sourceHash ? {
1062
+ declaredSourceHash,
1063
+ sourceHashVerified: false
1064
+ } : {}),
654
1065
  ...input.universalAstMetadata
655
1066
  }
656
1067
  });
@@ -670,6 +1081,14 @@ export function importNativeSource(input) {
670
1081
  semanticIndexId: semanticIndex?.id,
671
1082
  universalAstId: universalAst.id,
672
1083
  sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
1084
+ ...(sourcePreservation ? {
1085
+ sourcePreservationId: sourcePreservation.id,
1086
+ sourcePreservationSummary: sourcePreservation.summary
1087
+ } : {}),
1088
+ ...(declaredSourceHash && declaredSourceHash !== sourceHash ? {
1089
+ declaredSourceHash,
1090
+ sourceHashVerified: false
1091
+ } : {}),
673
1092
  nativeImportLossSummary: lossSummary
674
1093
  }
675
1094
  });
@@ -692,6 +1111,14 @@ export function importNativeSource(input) {
692
1111
  sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
693
1112
  semanticStatus,
694
1113
  mappings: resultSourceMapMappings,
1114
+ ...(sourcePreservation ? {
1115
+ sourcePreservationId: sourcePreservation.id,
1116
+ sourcePreservation
1117
+ } : {}),
1118
+ ...(declaredSourceHash && declaredSourceHash !== sourceHash ? {
1119
+ declaredSourceHash,
1120
+ sourceHashVerified: false
1121
+ } : {}),
695
1122
  nativeImportLossSummary: lossSummary,
696
1123
  ...input.metadata
697
1124
  }
@@ -725,6 +1152,7 @@ function createLightweightNativeImport(input) {
725
1152
  const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_lightweight_scan`;
726
1153
 
727
1154
  for (const declaration of declarations) {
1155
+ const ownershipRegion = semanticOwnershipRegionForDeclaration(input, declaration, documentId);
728
1156
  nodes[rootId].children.push(declaration.nodeId);
729
1157
  nodes[declaration.nodeId] = {
730
1158
  id: declaration.nodeId,
@@ -733,7 +1161,11 @@ function createLightweightNativeImport(input) {
733
1161
  span: declaration.span,
734
1162
  value: declaration.name ?? declaration.importPath ?? null,
735
1163
  fields: declaration.fields,
736
- metadata: declaration.metadata
1164
+ metadata: {
1165
+ ...declaration.metadata,
1166
+ ownershipRegionId: ownershipRegion.id,
1167
+ ownershipRegionKey: ownershipRegion.key
1168
+ }
737
1169
  };
738
1170
  if (declaration.symbolId) {
739
1171
  const occurrenceId = `occ_${idFragment(declaration.nodeId)}_def`;
@@ -745,7 +1177,11 @@ function createLightweightNativeImport(input) {
745
1177
  language: input.language,
746
1178
  nativeAstNodeId: declaration.nodeId,
747
1179
  signatureHash: hashSemanticValue([input.language, declaration.kind, declaration.name, declaration.fields ?? {}]),
748
- definitionSpan: declaration.span
1180
+ definitionSpan: declaration.span,
1181
+ metadata: {
1182
+ ownershipRegionId: ownershipRegion.id,
1183
+ ownershipRegionKey: ownershipRegion.key
1184
+ }
749
1185
  });
750
1186
  occurrences.push({
751
1187
  id: occurrenceId,
@@ -766,6 +1202,11 @@ function createLightweightNativeImport(input) {
766
1202
  predicate: 'nativeKind',
767
1203
  subjectId: declaration.symbolId,
768
1204
  value: declaration.languageKind
1205
+ }, {
1206
+ id: `fact_${idFragment(declaration.nodeId)}_ownership_region`,
1207
+ predicate: 'semanticOwnershipRegion',
1208
+ subjectId: declaration.symbolId,
1209
+ value: ownershipRegion
769
1210
  });
770
1211
  mappings.push({
771
1212
  id: `map_${idFragment(declaration.nodeId)}`,
@@ -775,12 +1216,13 @@ function createLightweightNativeImport(input) {
775
1216
  sourceSpan: declaration.span,
776
1217
  evidenceIds: [evidenceId],
777
1218
  lossIds: declaration.loss ? [declaration.loss.id] : [],
1219
+ ownershipRegionId: ownershipRegion.id,
778
1220
  precision: 'declaration'
779
1221
  });
780
1222
  }
781
1223
  if (declaration.loss) losses.push(declaration.loss);
782
1224
  }
783
- losses.push(...lightweightCoverageLosses(input, declarations));
1225
+ losses.push(...lightweightCoverageLosses(input, declarations, input.sourcePreservation));
784
1226
 
785
1227
  const semanticIndex = createSemanticIndexRecord({
786
1228
  id: `index_${idFragment(input.sourcePath ?? input.language)}`,
@@ -816,10 +1258,339 @@ function createLightweightNativeImport(input) {
816
1258
  losses,
817
1259
  semanticIndex,
818
1260
  mappings,
819
- metadata: { parser, scanKind: 'lightweight-declaration-scan', declarationCount: declarations.length }
1261
+ metadata: {
1262
+ parser,
1263
+ scanKind: 'lightweight-declaration-scan',
1264
+ declarationCount: declarations.length,
1265
+ ...(input.sourcePreservation ? {
1266
+ sourcePreservationId: input.sourcePreservation.id,
1267
+ sourcePreservationSummary: input.sourcePreservation.summary
1268
+ } : {})
1269
+ }
1270
+ };
1271
+ }
1272
+
1273
+ function nativeImportProjectionContext(importResult, options) {
1274
+ const nativeSource = options.nativeSource
1275
+ ?? importResult.nativeSource
1276
+ ?? importResult.nativeSources?.[0]
1277
+ ?? importResult.universalAst?.nativeSources?.[0];
1278
+ const nativeAst = options.nativeAst
1279
+ ?? importResult.nativeAst
1280
+ ?? nativeSource?.ast
1281
+ ?? importResult.universalAst?.nativeSources?.[0]?.ast;
1282
+ const semanticIndex = options.semanticIndex
1283
+ ?? importResult.semanticIndex
1284
+ ?? importResult.universalAst?.semanticIndex;
1285
+ const language = options.language
1286
+ ?? importResult.language
1287
+ ?? nativeSource?.language
1288
+ ?? nativeAst?.language
1289
+ ?? importResult.universalAst?.metadata?.sourceLanguage
1290
+ ?? 'source';
1291
+ const sourcePath = options.sourcePath
1292
+ ?? importResult.sourcePath
1293
+ ?? nativeSource?.sourcePath
1294
+ ?? nativeAst?.sourcePath
1295
+ ?? importResult.universalAst?.metadata?.sourcePath;
1296
+ const sourceHash = options.expectedSourceHash
1297
+ ?? importResult.sourceHash
1298
+ ?? nativeSource?.sourceHash
1299
+ ?? nativeAst?.sourceHash;
1300
+ return {
1301
+ nativeSource,
1302
+ nativeAst,
1303
+ semanticIndex,
1304
+ language,
1305
+ sourcePath,
1306
+ sourceHash,
1307
+ parser: options.parser ?? nativeAst?.parser ?? nativeSource?.parser,
1308
+ semanticStatus: options.semanticStatus ?? importResult.metadata?.semanticStatus ?? nativeSource?.metadata?.semanticStatus,
1309
+ idPart: idFragment(options.id ?? importResult.id ?? nativeSource?.id ?? sourcePath ?? language)
820
1310
  };
821
1311
  }
822
1312
 
1313
+ function nativeProjectionSourceCandidate(context, options) {
1314
+ const preservation = sourcePreservationFromProjectionContext(context);
1315
+ const explicitSourceText = options.sourceText ?? options.preservedSourceText ?? options.exactSourceText;
1316
+ const sourceText = explicitSourceText ?? preservation?.sourceText;
1317
+ if (typeof sourceText !== 'string') return undefined;
1318
+ const computedSourceHash = hashSemanticValue(sourceText);
1319
+ const declaredSourceHash = options.sourceHash ?? (explicitSourceText === undefined ? preservation?.sourceHash : undefined);
1320
+ const sourceHash = computedSourceHash;
1321
+ const hashVerified = Boolean(context.sourceHash);
1322
+ const exact = !context.sourceHash || sourceHash === context.sourceHash || options.verifySourceHash === false;
1323
+ return {
1324
+ sourceText,
1325
+ sourceHash,
1326
+ declaredSourceHash,
1327
+ hashVerified,
1328
+ exact,
1329
+ mismatch: hashVerified && sourceHash !== context.sourceHash && options.verifySourceHash !== false,
1330
+ sourcePreservationId: preservation?.id
1331
+ };
1332
+ }
1333
+
1334
+ function sourcePreservationFromProjectionContext(context) {
1335
+ return context.nativeSource?.metadata?.sourcePreservation
1336
+ ?? context.nativeAst?.metadata?.sourcePreservation
1337
+ ?? context.nativeSource?.ast?.metadata?.sourcePreservation;
1338
+ }
1339
+
1340
+ function nativeProjectionDeclarations(importResult, context) {
1341
+ const semanticIndex = context.semanticIndex;
1342
+ const occurrencesBySymbol = new Map();
1343
+ for (const occurrence of semanticIndex?.occurrences ?? []) {
1344
+ const list = occurrencesBySymbol.get(occurrence.symbolId) ?? [];
1345
+ list.push(occurrence);
1346
+ occurrencesBySymbol.set(occurrence.symbolId, list);
1347
+ }
1348
+ const declarations = (semanticIndex?.symbols ?? [])
1349
+ .filter((symbol) => !nativeProjectionImportOnlySymbol(symbol, occurrencesBySymbol.get(symbol.id)))
1350
+ .map((symbol) => {
1351
+ const occurrence = occurrencesBySymbol.get(symbol.id)?.find((item) => item.role !== 'import');
1352
+ const mapping = (importResult.sourceMaps ?? importResult.universalAst?.sourceMaps ?? [])
1353
+ .flatMap((sourceMap) => sourceMap.mappings ?? [])
1354
+ .find((item) => item.semanticSymbolId === symbol.id);
1355
+ return {
1356
+ name: symbol.name,
1357
+ kind: nativeProjectionDeclarationKind(symbol.kind),
1358
+ symbolId: symbol.id,
1359
+ nativeAstNodeId: symbol.nativeAstNodeId ?? occurrence?.nativeAstNodeId,
1360
+ sourceSpan: symbol.definitionSpan ?? occurrence?.span ?? mapping?.sourceSpan,
1361
+ ownershipRegionId: mapping?.ownershipRegionId ?? symbol.metadata?.ownershipRegionId,
1362
+ metadata: {
1363
+ semanticKind: symbol.kind,
1364
+ language: symbol.language,
1365
+ signatureHash: symbol.signatureHash
1366
+ }
1367
+ };
1368
+ })
1369
+ .filter((declaration) => declaration.name);
1370
+ if (declarations.length) return uniqueNativeProjectionDeclarations(declarations);
1371
+ return uniqueNativeProjectionDeclarations(Object.values(context.nativeAst?.nodes ?? {})
1372
+ .map((node) => {
1373
+ const name = typeof node.value === 'string' && node.value.trim() ? node.value.trim() : node.fields?.name;
1374
+ const kind = nativeProjectionKindForNode(node);
1375
+ if (!name || !kind) return undefined;
1376
+ return {
1377
+ name,
1378
+ kind,
1379
+ nativeAstNodeId: node.id,
1380
+ sourceSpan: node.span,
1381
+ ownershipRegionId: node.metadata?.ownershipRegionId,
1382
+ metadata: { nativeKind: node.kind, language: context.language }
1383
+ };
1384
+ })
1385
+ .filter(Boolean));
1386
+ }
1387
+
1388
+ function nativeProjectionImportOnlySymbol(symbol, occurrences = []) {
1389
+ if (String(symbol.id ?? '').includes(':import:')) return true;
1390
+ if (occurrences.length && occurrences.every((occurrence) => occurrence.role === 'import')) return true;
1391
+ return symbol.kind === 'module' && occurrences.some((occurrence) => occurrence.role === 'import');
1392
+ }
1393
+
1394
+ function nativeProjectionDeclarationKind(kind) {
1395
+ const normalized = String(kind ?? 'value').toLowerCase();
1396
+ if (normalized === 'function' || normalized === 'method' || normalized === 'procedure') return 'function';
1397
+ if (normalized === 'class') return 'class';
1398
+ if (normalized === 'interface' || normalized === 'protocol') return 'interface';
1399
+ if (normalized === 'trait') return 'trait';
1400
+ if (normalized === 'type' || normalized === 'struct' || normalized === 'enum' || normalized === 'record') return 'type';
1401
+ if (normalized === 'constant' || normalized === 'const') return 'constant';
1402
+ if (normalized === 'variable' || normalized === 'property' || normalized === 'field') return 'variable';
1403
+ if (normalized === 'module' || normalized === 'namespace' || normalized === 'package') return 'module';
1404
+ return normalized;
1405
+ }
1406
+
1407
+ function nativeProjectionKindForNode(node) {
1408
+ const kind = String(node?.kind ?? node?.languageKind ?? '').toLowerCase();
1409
+ if (/function|method|procedure|funcdecl|itemfn|fndeclaration|\bdef\b/.test(kind)) return 'function';
1410
+ if (/class/.test(kind)) return 'class';
1411
+ if (/interface|protocol/.test(kind)) return 'interface';
1412
+ if (/trait/.test(kind)) return 'trait';
1413
+ if (/struct|enum|record|typedef|typealias|type/.test(kind)) return 'type';
1414
+ if (/const|constant|macro|define/.test(kind)) return 'constant';
1415
+ if (/var|property|field/.test(kind)) return 'variable';
1416
+ if (/module|namespace|package/.test(kind)) return 'module';
1417
+ return undefined;
1418
+ }
1419
+
1420
+ function uniqueNativeProjectionDeclarations(declarations) {
1421
+ const seen = new Set();
1422
+ return declarations.filter((declaration) => {
1423
+ const key = `${declaration.kind}:${declaration.name}:${declaration.symbolId ?? declaration.nativeAstNodeId ?? ''}`;
1424
+ if (seen.has(key)) return false;
1425
+ seen.add(key);
1426
+ return true;
1427
+ });
1428
+ }
1429
+
1430
+ function nativeProjectionStubLosses(context, candidateSource, declarations, options) {
1431
+ const reason = candidateSource?.mismatch
1432
+ ? 'source-hash-mismatch'
1433
+ : options.preferPreservedSource === false && candidateSource?.sourceText
1434
+ ? 'preserved-source-disabled'
1435
+ : 'exact-source-unavailable';
1436
+ const message = candidateSource?.mismatch
1437
+ ? 'Provided native source text hash did not match the import result source hash; emitted declaration stubs instead of preserving stale source.'
1438
+ : options.preferPreservedSource === false && candidateSource?.sourceText
1439
+ ? 'Native source projection was asked to emit declaration stubs instead of preserving available source text.'
1440
+ : 'Exact native source text was not provided; emitted declaration stubs reconstructed from import metadata.';
1441
+ const losses = [nativeProjectionLoss(context, {
1442
+ id: `loss_${context.idPart}_native_source_stub`,
1443
+ kind: candidateSource?.mismatch ? 'sourcePreservation' : 'targetProjectionLoss',
1444
+ severity: 'warning',
1445
+ message,
1446
+ metadata: {
1447
+ reason,
1448
+ projectionMode: 'native-source-stubs',
1449
+ expectedSourceHash: context.sourceHash,
1450
+ providedSourceHash: candidateSource?.sourceHash,
1451
+ declaredSourceHash: candidateSource?.declaredSourceHash
1452
+ }
1453
+ })];
1454
+ if (!declarations.length) {
1455
+ losses.push(nativeProjectionLoss(context, {
1456
+ id: `loss_${context.idPart}_native_source_stub_empty`,
1457
+ kind: 'declarationOnlyCoverage',
1458
+ severity: 'warning',
1459
+ message: 'Native import result did not expose semantic declarations for source stub generation.',
1460
+ metadata: { reason: 'no-stub-declarations', projectionMode: 'native-source-stubs' }
1461
+ }));
1462
+ }
1463
+ return losses;
1464
+ }
1465
+
1466
+ function nativeProjectionLoss(context, input) {
1467
+ const rootSpan = context.nativeAst?.nodes?.[context.nativeAst.rootId]?.span;
1468
+ return {
1469
+ id: input.id,
1470
+ severity: input.severity,
1471
+ phase: 'emit',
1472
+ sourceFormat: context.language,
1473
+ kind: input.kind,
1474
+ message: input.message,
1475
+ span: rootSpan ?? {
1476
+ sourceId: context.sourceHash,
1477
+ path: context.sourcePath,
1478
+ startLine: 1,
1479
+ startColumn: 1
1480
+ },
1481
+ metadata: {
1482
+ nativeSourceId: context.nativeSource?.id,
1483
+ nativeAstId: context.nativeAst?.id,
1484
+ parser: context.parser,
1485
+ ...input.metadata
1486
+ }
1487
+ };
1488
+ }
1489
+
1490
+ function renderNativeProjectionStubs(context, declarations, options) {
1491
+ const language = String(context.language ?? 'source').toLowerCase();
1492
+ const header = nativeProjectionStubHeader(language, context, options);
1493
+ let body;
1494
+ if (language === 'typescript' || language === 'ts') body = renderTypeScriptProjectionStubs(declarations);
1495
+ else if (language === 'javascript' || language === 'js') body = renderJavaScriptProjectionStubs(declarations);
1496
+ else if (language === 'python' || language === 'py') body = renderPythonProjectionStubs(declarations);
1497
+ else if (language === 'rust' || language === 'rs') body = renderRustProjectionStubs(declarations);
1498
+ else if (language === 'c' || language === 'cpp' || language === 'c++' || language === 'h') body = renderCProjectionStubs(declarations);
1499
+ else body = renderGenericProjectionStubs(declarations, language);
1500
+ return ensureTrailingNewline([header, body].filter(Boolean).join('\n'));
1501
+ }
1502
+
1503
+ function nativeProjectionStubHeader(language, context, options) {
1504
+ if (options.stubBanner === false) return '';
1505
+ if (typeof options.stubBanner === 'string') return ensureTrailingNewline(options.stubBanner).trimEnd();
1506
+ const comment = nativeProjectionLineComment(language);
1507
+ const suffix = context.sourcePath ? ` for ${oneLine(context.sourcePath)}` : '';
1508
+ return [
1509
+ `${comment} Frontier native source stubs${suffix}`,
1510
+ `${comment} Exact source text was unavailable; declarations are reconstructed from native import metadata.`
1511
+ ].join('\n');
1512
+ }
1513
+
1514
+ function nativeProjectionLineComment(language) {
1515
+ if (language === 'python' || language === 'py' || language === 'ruby' || language === 'rb' || language === 'shell' || language === 'sh') return '#';
1516
+ if (language === 'sql') return '--';
1517
+ return '//';
1518
+ }
1519
+
1520
+ function renderJavaScriptProjectionStubs(declarations) {
1521
+ const used = new Set();
1522
+ if (!declarations.length) return 'export {};';
1523
+ return declarations.map((declaration) => {
1524
+ const name = reserveUniqueId(safeProjectionIdentifier(declaration.name), used);
1525
+ if (declaration.kind === 'function') return `export function ${name}(...args) {\n throw new Error('Frontier native source stub: implementation unavailable.');\n}`;
1526
+ if (declaration.kind === 'class') return `export class ${name} {}`;
1527
+ return `export const ${name} = undefined;`;
1528
+ }).join('\n\n');
1529
+ }
1530
+
1531
+ function renderTypeScriptProjectionStubs(declarations) {
1532
+ const used = new Set();
1533
+ if (!declarations.length) return 'export {};';
1534
+ return declarations.map((declaration) => {
1535
+ const name = reserveUniqueId(safeProjectionIdentifier(declaration.name), used);
1536
+ if (declaration.kind === 'function') return `export function ${name}(...args: unknown[]): never {\n throw new Error('Frontier native source stub: implementation unavailable.');\n}`;
1537
+ if (declaration.kind === 'class') return `export class ${name} {}`;
1538
+ if (declaration.kind === 'interface') return `export interface ${name} {}`;
1539
+ if (declaration.kind === 'type' || declaration.kind === 'trait') return `export type ${name} = unknown;`;
1540
+ return `export const ${name}: unknown = undefined;`;
1541
+ }).join('\n\n');
1542
+ }
1543
+
1544
+ function renderPythonProjectionStubs(declarations) {
1545
+ if (!declarations.length) return 'pass';
1546
+ return declarations.map((declaration) => {
1547
+ const name = safeProjectionIdentifier(declaration.name);
1548
+ if (declaration.kind === 'function') return `def ${name}(*args, **kwargs):\n raise NotImplementedError("Frontier native source stub")`;
1549
+ if (declaration.kind === 'class') return `class ${name}:\n pass`;
1550
+ return `${name} = None`;
1551
+ }).join('\n\n');
1552
+ }
1553
+
1554
+ function renderRustProjectionStubs(declarations) {
1555
+ if (!declarations.length) return '';
1556
+ return declarations.map((declaration) => {
1557
+ const name = safeProjectionIdentifier(declaration.name);
1558
+ if (declaration.kind === 'function') return `pub fn ${name}() {\n unimplemented!(\"Frontier native source stub\");\n}`;
1559
+ if (declaration.kind === 'type' || declaration.kind === 'class') return `pub struct ${upperFirst(name)};`;
1560
+ return `pub const ${name.toUpperCase()}: () = ();`;
1561
+ }).join('\n\n');
1562
+ }
1563
+
1564
+ function renderCProjectionStubs(declarations) {
1565
+ if (!declarations.length) return '';
1566
+ return declarations.map((declaration) => {
1567
+ const name = safeProjectionIdentifier(declaration.name);
1568
+ if (declaration.kind === 'function') return `void ${name}(void);`;
1569
+ if (declaration.kind === 'type' || declaration.kind === 'class') return `typedef struct ${upperFirst(name)} ${upperFirst(name)};`;
1570
+ return `extern const int ${name};`;
1571
+ }).join('\n');
1572
+ }
1573
+
1574
+ function renderGenericProjectionStubs(declarations, language) {
1575
+ if (!declarations.length) return `${nativeProjectionLineComment(language)} no declarations available`;
1576
+ return declarations.map((declaration) => `${declaration.kind} ${declaration.name}`).join('\n');
1577
+ }
1578
+
1579
+ function safeProjectionIdentifier(name) {
1580
+ const text = String(name ?? 'value').split('.').at(-1).replace(/[^A-Za-z0-9_$]/g, '_');
1581
+ const identifier = text || 'value';
1582
+ return /^[A-Za-z_$]/.test(identifier) ? identifier : `_${identifier}`;
1583
+ }
1584
+
1585
+ function ensureTrailingNewline(value) {
1586
+ const text = String(value ?? '');
1587
+ return text.endsWith('\n') ? text : `${text}\n`;
1588
+ }
1589
+
1590
+ function oneLine(value) {
1591
+ return String(value ?? '').replace(/\s+/g, ' ').trim();
1592
+ }
1593
+
823
1594
  function scanNativeDeclarations(input) {
824
1595
  const language = String(input.language).toLowerCase();
825
1596
  if (language === 'javascript' || language === 'typescript') return scanJavaScriptLike(input);
@@ -848,23 +1619,63 @@ function scanNativeDeclarations(input) {
848
1619
 
849
1620
  function scanJavaScriptLike(input) {
850
1621
  const declarations = [];
1622
+ let currentClass;
1623
+ let classDepth = 0;
851
1624
  for (const { line, number } of sourceLines(input.sourceText)) {
852
1625
  const trimmed = line.trim();
1626
+ const declarationLine = trimmed.replace(/^(?:export\s+)?(?:declare\s+)?/, '');
853
1627
  let match;
854
- if ((match = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)/))) {
855
- declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
856
- } else if ((match = trimmed.match(/^(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/))) {
857
- declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'class', match[1], {}, trimmed.includes('{')));
858
- } else if ((match = trimmed.match(/^(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/))) {
859
- declarations.push(nativeDeclaration(input, number, 'InterfaceDeclaration', 'interface', match[1], {}, trimmed.includes('{')));
860
- } else if ((match = trimmed.match(/^(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/))) {
861
- declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
862
- } else if ((match = trimmed.match(/^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
863
- declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
864
- } else if ((match = trimmed.match(/^import\s+(?:.+?\s+from\s+)?['"]([^'"]+)['"]/))) {
1628
+ if ((match = trimmed.match(/^import\s+(?:.+?\s+from\s+)?['"]([^'"]+)['"]/))) {
865
1629
  declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
866
- } else if ((match = trimmed.match(/^export\s+\{[^}]*\}\s+from\s+['"]([^'"]+)['"]/))) {
1630
+ } else if ((match = trimmed.match(/^import\s*\(\s*['"]([^'"]+)['"]\s*\)/))) {
1631
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'DynamicImportExpression', 'module'));
1632
+ } else if ((match = trimmed.match(/^export\s+(?:\*\s+from|\{[^}]*\}\s+from)\s+['"]([^'"]+)['"]/))) {
867
1633
  declarations.push(nativeImportDeclaration(input, number, match[1], 'ExportFromDeclaration', 'module'));
1634
+ } else if ((match = declarationLine.match(/^(?:async\s+)?function\*?\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)/))) {
1635
+ declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, declarationLine.includes('{')));
1636
+ } else if ((match = trimmed.match(/^export\s+default\s+(?:async\s+)?function\*?\s*([A-Za-z_$][\w$]*)?\s*\(([^)]*)\)/))) {
1637
+ declarations.push(nativeDeclaration(input, number, 'ExportDefaultFunctionDeclaration', 'function', match[1] ?? 'default', { parameters: splitParameters(match[2]), exportDefault: true }, trimmed.includes('{')));
1638
+ } else if ((match = declarationLine.match(/^(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/))) {
1639
+ declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'class', match[1], {}, declarationLine.includes('{')));
1640
+ if (declarationLine.includes('{') && !declarationLine.includes('}')) {
1641
+ currentClass = match[1];
1642
+ classDepth = 0;
1643
+ }
1644
+ } else if ((match = declarationLine.match(/^interface\s+([A-Za-z_$][\w$]*)/))) {
1645
+ declarations.push(nativeDeclaration(input, number, 'InterfaceDeclaration', 'interface', match[1], {}, declarationLine.includes('{')));
1646
+ } else if ((match = declarationLine.match(/^(?:const\s+)?enum\s+([A-Za-z_$][\w$]*)/))) {
1647
+ declarations.push(nativeDeclaration(input, number, 'EnumDeclaration', 'type', match[1], {}, declarationLine.includes('{')));
1648
+ } else if ((match = declarationLine.match(/^(?:namespace|module)\s+([A-Za-z_$][\w$.]*)/))) {
1649
+ declarations.push(nativeDeclaration(input, number, 'ModuleDeclaration', 'module', match[1], {}, declarationLine.includes('{')));
1650
+ } else if ((match = declarationLine.match(/^type\s+([A-Za-z_$][\w$]*)\s*=/))) {
1651
+ declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
1652
+ } else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
1653
+ declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
1654
+ } else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b/))) {
1655
+ declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', 'variable', match[1], {}, false));
1656
+ } else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?function\*?\s*\(([^)]*)\)/))) {
1657
+ declarations.push(nativeDeclaration(input, number, 'CommonJsFunctionExport', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
1658
+ } else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=/))) {
1659
+ declarations.push(nativeDeclaration(input, number, 'CommonJsExport', 'variable', match[1], { export: 'commonjs' }, false));
1660
+ } else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|async|override|readonly|abstract|accessor|get|set)\s+)*(?:async\s+)?(?:get\s+|set\s+)?([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*(?::\s*[^={]+)?(?:\{|=>|$)/)) && !jsControlKeyword(match[1])) {
1661
+ declarations.push(nativeDeclaration(input, number, 'MethodDefinition', 'method', `${currentClass}.${match[1]}`, {
1662
+ methodName: match[1],
1663
+ owner: currentClass,
1664
+ parameters: splitParameters(match[2])
1665
+ }, declarationLine.includes('{') || declarationLine.includes('=>')));
1666
+ } else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|readonly|declare|accessor)\s+)*([A-Za-z_$][\w$]*)\s*(?::\s*([^=;{]+))?(?:[=;]|$)/))) {
1667
+ declarations.push(nativeDeclaration(input, number, 'PropertyDefinition', 'property', `${currentClass}.${match[1]}`, {
1668
+ propertyName: match[1],
1669
+ owner: currentClass,
1670
+ valueType: match[2]?.trim()
1671
+ }, false));
1672
+ }
1673
+ if (currentClass) {
1674
+ classDepth += braceDelta(trimmed);
1675
+ if (classDepth <= 0) {
1676
+ currentClass = undefined;
1677
+ classDepth = 0;
1678
+ }
868
1679
  }
869
1680
  }
870
1681
  return declarations;
@@ -953,17 +1764,41 @@ function scanJava(input) {
953
1764
 
954
1765
  function scanGo(input) {
955
1766
  const declarations = [];
1767
+ let inImportBlock = false;
956
1768
  for (const { line, number } of sourceLines(input.sourceText)) {
957
1769
  const trimmed = line.trim();
958
1770
  let match;
1771
+ if (inImportBlock) {
1772
+ if (trimmed === ')') {
1773
+ inImportBlock = false;
1774
+ } else if ((match = trimmed.match(/^(?:(?:[A-Za-z_]\w*|[_.])\s+)?["']([^"']+)["']/))) {
1775
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportSpec', 'package'));
1776
+ }
1777
+ continue;
1778
+ }
959
1779
  if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*)/))) {
960
1780
  declarations.push(nativeDeclaration(input, number, 'PackageClause', 'package', match[1], {}, false));
961
- } else if ((match = trimmed.match(/^import\s+(?:[A-Za-z_]\w*\s+)?["']([^"']+)["']/))) {
1781
+ } else if (/^import\s*\(/.test(trimmed)) {
1782
+ inImportBlock = true;
1783
+ } else if ((match = trimmed.match(/^import\s+(?:(?:[A-Za-z_]\w*|[_.])\s+)?["']([^"']+)["']/))) {
962
1784
  declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportSpec', 'package'));
1785
+ } else if ((match = trimmed.match(/^type\s+([A-Za-z_]\w*)\s*=\s*(.+)$/))) {
1786
+ declarations.push(nativeDeclaration(input, number, 'TypeAlias', 'type', match[1], { target: match[2].trim() }, false));
963
1787
  } else if ((match = trimmed.match(/^type\s+([A-Za-z_]\w*)\s+(struct|interface)\b/))) {
964
1788
  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('{')));
1789
+ } else if ((match = trimmed.match(/^func\s+\(([^)]*)\)\s*([A-Za-z_]\w*)(?:\s*\[([^\]]+)\])?\s*\(([^)]*)\)/))) {
1790
+ const receiver = parseGoReceiver(match[1]);
1791
+ declarations.push(nativeDeclaration(input, number, 'MethodDecl', 'method', goReceiverMethodName(receiver, match[2]), {
1792
+ methodName: match[2],
1793
+ receiver,
1794
+ typeParameters: splitTypeParameters(match[3]),
1795
+ parameters: splitParameters(match[4])
1796
+ }, trimmed.includes('{')));
1797
+ } else if ((match = trimmed.match(/^func\s+([A-Za-z_]\w*)(?:\s*\[([^\]]+)\])?\s*\(([^)]*)\)/))) {
1798
+ declarations.push(nativeDeclaration(input, number, 'FuncDecl', 'function', match[1], {
1799
+ typeParameters: splitTypeParameters(match[2]),
1800
+ parameters: splitParameters(match[3])
1801
+ }, trimmed.includes('{')));
967
1802
  } else if ((match = trimmed.match(/^var\s+([A-Za-z_]\w*)\b/))) {
968
1803
  declarations.push(nativeDeclaration(input, number, 'VarDecl', 'variable', match[1], {}, false));
969
1804
  } else if ((match = trimmed.match(/^const\s+([A-Za-z_]\w*)\b/))) {
@@ -975,17 +1810,35 @@ function scanGo(input) {
975
1810
 
976
1811
  function scanSwift(input) {
977
1812
  const declarations = [];
1813
+ const protocols = new Set();
978
1814
  for (const { line, number } of sourceLines(input.sourceText)) {
979
1815
  const trimmed = line.trim();
1816
+ const declarationLine = trimmed.replace(/^(?:@[A-Za-z_][\w.]+(?:\([^)]*\))?\s+)*/, '');
980
1817
  let match;
981
- if ((match = trimmed.match(/^import\s+([A-Za-z_]\w*)/))) {
1818
+ if ((match = declarationLine.match(/^import\s+(?:(?:struct|class|enum|protocol|func|var)\s+)?([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
982
1819
  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));
1820
+ } else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open|final|indirect)\s+)*(struct|class|enum|protocol|actor)\s+([A-Za-z_]\w*)/))) {
1821
+ if (match[1] === 'protocol') protocols.add(match[2]);
1822
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Decl`, swiftSymbolKind(match[1]), match[2], {}, declarationLine.includes('{')));
1823
+ } else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open)\s+)*extension\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)(.*)$/))) {
1824
+ const extensionFields = parseSwiftExtensionTail(match[2]);
1825
+ const isProtocolExtension = protocols.has(match[1]) || /Protocol$/.test(match[1]);
1826
+ declarations.push(nativeDeclaration(input, number, isProtocolExtension ? 'ProtocolExtensionDecl' : 'ExtensionDecl', 'implementation', `${match[1]}.${isProtocolExtension ? 'protocolExtension' : 'extension'}`, {
1827
+ extendedType: match[1],
1828
+ ...extensionFields
1829
+ }, declarationLine.includes('{')));
1830
+ } else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open|static|class|mutating|nonmutating|override|required|convenience|isolated|nonisolated)\s+)*func\s+([A-Za-z_]\w*|`[^`]+`)(?:\s*<([^>]+)>)?\s*\(([^)]*)\)/))) {
1831
+ declarations.push(nativeDeclaration(input, number, 'FunctionDecl', 'function', unquoteSwiftIdentifier(match[1]), {
1832
+ typeParameters: splitTypeParameters(match[2]),
1833
+ parameters: splitParameters(match[3])
1834
+ }, declarationLine.includes('{')));
1835
+ } else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open|static|class|final|lazy|weak|unowned|override|required|nonisolated)\s+)*(let|var)\s+([A-Za-z_]\w*)\b(?::\s*([^={]+))?/))) {
1836
+ declarations.push(nativeDeclaration(input, number, 'PropertyDecl', 'property', match[2], {
1837
+ binding: match[1],
1838
+ valueType: match[3]?.trim()
1839
+ }, declarationLine.includes('{') || declarationLine.includes('=>')));
1840
+ } else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open)\s+)*typealias\s+([A-Za-z_]\w*)\b(?:\s*=\s*(.+))?/))) {
1841
+ declarations.push(nativeDeclaration(input, number, 'TypealiasDecl', 'type', match[1], { target: match[2]?.trim() }, false));
989
1842
  }
990
1843
  }
991
1844
  return declarations;
@@ -996,14 +1849,36 @@ function scanCSharp(input) {
996
1849
  for (const { line, number } of sourceLines(input.sourceText)) {
997
1850
  const trimmed = line.trim();
998
1851
  let match;
999
- if ((match = trimmed.match(/^using\s+(?:static\s+)?([A-Za-z_][\w.]*)\s*;/))) {
1852
+ if ((match = trimmed.match(/^using\s+([A-Za-z_]\w*)\s*=\s*(.+?)\s*;/))) {
1853
+ declarations.push(nativeDeclaration(input, number, 'UsingAliasDirective', 'type', match[1], { target: match[2].trim() }, false));
1854
+ } else if ((match = trimmed.match(/^using\s+(?:static\s+)?([A-Za-z_][\w.]*)\s*;/))) {
1000
1855
  declarations.push(nativeImportDeclaration(input, number, match[1], 'UsingDirective', 'namespace'));
1001
1856
  } else if ((match = trimmed.match(/^namespace\s+([A-Za-z_][\w.]*)/))) {
1002
1857
  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('{')));
1858
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|unsafe|new)\s+)*delegate\s+(.+?)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*;/))) {
1859
+ declarations.push(nativeDeclaration(input, number, 'DelegateDeclaration', 'type', match[2], {
1860
+ returnType: match[1].trim(),
1861
+ parameters: splitParameters(match[3])
1862
+ }, false));
1863
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|abstract|sealed|static|partial|readonly|ref|unsafe)\s+)*(class|interface|struct|enum|record(?:\s+(?:class|struct))?)\s+([A-Za-z_]\w*)/))) {
1864
+ declarations.push(nativeDeclaration(input, number, csharpDeclarationKind(match[1]), csharpSymbolKind(match[1]), match[2], { csharpKind: match[1].replace(/\s+/g, ' ') }, trimmed.includes('{')));
1865
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|virtual|override|async|partial|sealed|abstract|extern|new|unsafe|readonly)\s+)*(?:[A-Za-z_][\w<>\[\].?,\s]*\??|void)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(?:=>.*|\{|;)?$/))) {
1866
+ const parameters = splitParameters(match[2]);
1867
+ const extensionReceiver = csharpExtensionReceiver(parameters);
1868
+ declarations.push(nativeDeclaration(input, number, extensionReceiver ? 'ExtensionMethodDeclaration' : 'MethodDeclaration', 'method', match[1], {
1869
+ parameters,
1870
+ ...(extensionReceiver ? { extensionReceiver } : {})
1871
+ }, trimmed.includes('{') || trimmed.includes('=>')));
1872
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|virtual|override|abstract|sealed|new|unsafe)\s+)*event\s+(.+?)\s+([A-Za-z_]\w*)\s*(?:[;{=]|=>)/))) {
1873
+ declarations.push(nativeDeclaration(input, number, 'EventDeclaration', 'event', match[2], {
1874
+ eventType: match[1].trim(),
1875
+ accessors: csharpAccessors(trimmed)
1876
+ }, trimmed.includes('{')));
1877
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|virtual|override|abstract|sealed|new|required|readonly|unsafe)\s+)*([A-Za-z_][\w<>\[\].?,\s]*\??)\s+([A-Za-z_]\w*)\s*(?:\{|=>)/))) {
1878
+ declarations.push(nativeDeclaration(input, number, 'PropertyDeclaration', 'property', match[2], {
1879
+ propertyType: match[1].trim(),
1880
+ accessors: csharpAccessors(trimmed)
1881
+ }, trimmed.includes('{') || trimmed.includes('=>')));
1007
1882
  }
1008
1883
  }
1009
1884
  return declarations;
@@ -1347,6 +2222,48 @@ function upperFirst(value) {
1347
2222
  return String(value).charAt(0).toUpperCase() + String(value).slice(1);
1348
2223
  }
1349
2224
 
2225
+ function parseGoReceiver(raw) {
2226
+ const value = String(raw ?? '').trim();
2227
+ const match = value.match(/^(?:(\w+)\s+)?(.+)$/);
2228
+ const rawType = String(match?.[2] ?? value).trim();
2229
+ return {
2230
+ raw: value,
2231
+ ...(match?.[1] ? { name: match[1] } : {}),
2232
+ rawType,
2233
+ type: normalizeGoReceiverType(rawType)
2234
+ };
2235
+ }
2236
+
2237
+ function normalizeGoReceiverType(rawType) {
2238
+ return String(rawType ?? '')
2239
+ .trim()
2240
+ .replace(/^[*&\s]+/, '')
2241
+ .replace(/\[[^\]]+\]/g, '')
2242
+ .replace(/\s+/g, ' ');
2243
+ }
2244
+
2245
+ function goReceiverMethodName(receiver, methodName) {
2246
+ return receiver?.type ? `${receiver.type}.${methodName}` : methodName;
2247
+ }
2248
+
2249
+ function parseSwiftExtensionTail(rawTail) {
2250
+ let tail = String(rawTail ?? '').split('{')[0].trim();
2251
+ const fields = {};
2252
+ const whereMatch = tail.match(/\bwhere\b(.+)$/);
2253
+ if (whereMatch) {
2254
+ fields.constraints = whereMatch[1].trim();
2255
+ tail = tail.slice(0, whereMatch.index).trim();
2256
+ }
2257
+ if (tail.startsWith(':')) {
2258
+ fields.conformances = tail.slice(1).split(',').map((part) => part.trim()).filter(Boolean);
2259
+ }
2260
+ return fields;
2261
+ }
2262
+
2263
+ function unquoteSwiftIdentifier(identifier) {
2264
+ return String(identifier).replace(/^`|`$/g, '');
2265
+ }
2266
+
1350
2267
  function javaSymbolKind(kind) {
1351
2268
  if (kind === 'interface' || kind === '@interface') return 'interface';
1352
2269
  if (kind === 'enum' || kind === 'record') return 'type';
@@ -1361,11 +2278,29 @@ function swiftSymbolKind(kind) {
1361
2278
  }
1362
2279
 
1363
2280
  function csharpSymbolKind(kind) {
1364
- if (kind === 'interface') return 'interface';
1365
- if (kind === 'struct' || kind === 'enum' || kind === 'record') return 'type';
2281
+ const normalized = String(kind).replace(/\s+/g, ' ');
2282
+ if (normalized === 'interface') return 'interface';
2283
+ if (normalized === 'struct' || normalized === 'enum' || normalized.startsWith('record')) return 'type';
1366
2284
  return 'class';
1367
2285
  }
1368
2286
 
2287
+ function csharpDeclarationKind(kind) {
2288
+ const normalized = String(kind).replace(/\s+/g, ' ');
2289
+ if (normalized === 'record struct') return 'RecordStructDeclaration';
2290
+ if (normalized === 'record class') return 'RecordClassDeclaration';
2291
+ if (normalized === 'record') return 'RecordDeclaration';
2292
+ return `${upperFirst(normalized)}Declaration`;
2293
+ }
2294
+
2295
+ function csharpExtensionReceiver(parameters) {
2296
+ const match = String(parameters?.[0] ?? '').match(/^this\s+(.+?)\s+([A-Za-z_]\w*)$/);
2297
+ return match ? { type: match[1].trim(), name: match[2] } : undefined;
2298
+ }
2299
+
2300
+ function csharpAccessors(source) {
2301
+ return uniqueStrings([...String(source ?? '').matchAll(/\b(get|set|init|add|remove)\b/g)].map((match) => match[1]));
2302
+ }
2303
+
1369
2304
  function phpSymbolKind(kind) {
1370
2305
  if (kind === 'interface') return 'interface';
1371
2306
  if (kind === 'trait') return 'trait';
@@ -1517,7 +2452,7 @@ function opaqueBodyLoss(input, lineNumber, nodeId, name) {
1517
2452
  };
1518
2453
  }
1519
2454
 
1520
- function lightweightCoverageLosses(input, declarations) {
2455
+ function lightweightCoverageLosses(input, declarations, sourcePreservation) {
1521
2456
  const id = idFragment(input.sourcePath ?? input.language);
1522
2457
  const span = declarations[0]?.span ?? {
1523
2458
  sourceId: input.sourceHash,
@@ -1559,12 +2494,275 @@ function lightweightCoverageLosses(input, declarations) {
1559
2494
  phase: 'read',
1560
2495
  sourceFormat: input.language,
1561
2496
  kind: 'sourcePreservation',
1562
- message: 'Comments, whitespace, token order, directives, and formatting are not preserved by the lightweight importer.',
1563
- span
2497
+ message: sourcePreservation
2498
+ ? 'Comments, whitespace, token order, directives, and formatting are preserved as opaque native source evidence; exact structural edits still require a parser adapter.'
2499
+ : 'Comments, whitespace, token order, directives, and formatting are not preserved by the lightweight importer.',
2500
+ span,
2501
+ metadata: sourcePreservation ? {
2502
+ sourcePreservationId: sourcePreservation.id,
2503
+ sourcePreservationSummary: sourcePreservation.summary
2504
+ } : undefined
1564
2505
  }
1565
2506
  ];
1566
2507
  }
1567
2508
 
2509
+ function scanPreservedSourceTokens(sourceText, input) {
2510
+ const tokens = [];
2511
+ const trivia = [];
2512
+ const includeTokens = input.includeTokens !== false;
2513
+ const includeTrivia = input.includeTrivia !== false;
2514
+ const maxTokens = Number.isFinite(input.maxTokens) ? Math.max(0, input.maxTokens) : 20000;
2515
+ const maxTrivia = Number.isFinite(input.maxTrivia) ? Math.max(0, input.maxTrivia) : 20000;
2516
+ let offset = 0;
2517
+ let line = 1;
2518
+ let column = 1;
2519
+ let truncated = false;
2520
+ const push = (target, kind, text, start) => {
2521
+ if ((target === tokens && !includeTokens) || (target === trivia && !includeTrivia)) return;
2522
+ const max = target === tokens ? maxTokens : maxTrivia;
2523
+ if (target.length >= max) {
2524
+ truncated = true;
2525
+ return;
2526
+ }
2527
+ target.push(preservedSourceSegment({
2528
+ index: target.length,
2529
+ kind,
2530
+ text,
2531
+ start,
2532
+ end: { offset, line, column },
2533
+ sourceHash: input.sourceHash,
2534
+ sourcePath: input.sourcePath
2535
+ }));
2536
+ };
2537
+ while (offset < sourceText.length) {
2538
+ const start = { offset, line, column };
2539
+ const char = sourceText[offset];
2540
+ const next = sourceText[offset + 1];
2541
+ if (char === '\r' || char === '\n') {
2542
+ const text = char === '\r' && next === '\n' ? '\r\n' : char;
2543
+ offset += text.length;
2544
+ line += 1;
2545
+ column = 1;
2546
+ push(trivia, 'newline', text, start);
2547
+ continue;
2548
+ }
2549
+ if (char === ' ' || char === '\t' || char === '\v' || char === '\f') {
2550
+ let text = '';
2551
+ while (offset < sourceText.length && /[ \t\v\f]/.test(sourceText[offset])) {
2552
+ text += sourceText[offset];
2553
+ offset += 1;
2554
+ column += 1;
2555
+ }
2556
+ push(trivia, 'whitespace', text, start);
2557
+ continue;
2558
+ }
2559
+ if (char === '/' && next === '/') {
2560
+ let text = '';
2561
+ while (offset < sourceText.length && sourceText[offset] !== '\n' && sourceText[offset] !== '\r') {
2562
+ text += sourceText[offset];
2563
+ offset += 1;
2564
+ column += 1;
2565
+ }
2566
+ push(trivia, 'comment', text, start);
2567
+ continue;
2568
+ }
2569
+ if (char === '/' && next === '*') {
2570
+ let text = '';
2571
+ while (offset < sourceText.length) {
2572
+ const current = sourceText[offset];
2573
+ text += current;
2574
+ offset += 1;
2575
+ if (current === '\n') {
2576
+ line += 1;
2577
+ column = 1;
2578
+ } else {
2579
+ column += 1;
2580
+ }
2581
+ if (current === '*' && sourceText[offset] === '/') {
2582
+ text += '/';
2583
+ offset += 1;
2584
+ column += 1;
2585
+ break;
2586
+ }
2587
+ }
2588
+ push(trivia, 'comment', text, start);
2589
+ continue;
2590
+ }
2591
+ if (char === '#' && isHashCommentLanguage(input.language)) {
2592
+ let text = '';
2593
+ while (offset < sourceText.length && sourceText[offset] !== '\n' && sourceText[offset] !== '\r') {
2594
+ text += sourceText[offset];
2595
+ offset += 1;
2596
+ column += 1;
2597
+ }
2598
+ push(trivia, preservedHashLineKind(text), text, start);
2599
+ continue;
2600
+ }
2601
+ if (char === '"' || char === '\'' || char === '`') {
2602
+ const quote = char;
2603
+ let text = char;
2604
+ offset += 1;
2605
+ column += 1;
2606
+ let escaped = false;
2607
+ while (offset < sourceText.length) {
2608
+ const current = sourceText[offset];
2609
+ text += current;
2610
+ offset += 1;
2611
+ if (current === '\n') {
2612
+ line += 1;
2613
+ column = 1;
2614
+ } else {
2615
+ column += 1;
2616
+ }
2617
+ if (escaped) {
2618
+ escaped = false;
2619
+ } else if (current === '\\') {
2620
+ escaped = true;
2621
+ } else if (current === quote) {
2622
+ break;
2623
+ }
2624
+ }
2625
+ push(tokens, 'string', text, start);
2626
+ continue;
2627
+ }
2628
+ if (/[0-9]/.test(char)) {
2629
+ let text = '';
2630
+ while (offset < sourceText.length && /[0-9a-fA-F_xXoObBeE.+-]/.test(sourceText[offset])) {
2631
+ text += sourceText[offset];
2632
+ offset += 1;
2633
+ column += 1;
2634
+ }
2635
+ push(tokens, 'number', text, start);
2636
+ continue;
2637
+ }
2638
+ if (isIdentifierStart(char)) {
2639
+ let text = '';
2640
+ while (offset < sourceText.length && isIdentifierPart(sourceText[offset])) {
2641
+ text += sourceText[offset];
2642
+ offset += 1;
2643
+ column += 1;
2644
+ }
2645
+ push(tokens, preservedKeywordSet.has(text) ? 'keyword' : 'identifier', text, start);
2646
+ continue;
2647
+ }
2648
+ let text = char;
2649
+ if (/[=+\-*/%&|^!<>?:.]/.test(char)) {
2650
+ while (offset + text.length < sourceText.length && /[=+\-*/%&|^!<>?:.]/.test(sourceText[offset + text.length])) text += sourceText[offset + text.length];
2651
+ offset += text.length;
2652
+ column += text.length;
2653
+ push(tokens, 'operator', text, start);
2654
+ } else {
2655
+ offset += 1;
2656
+ column += 1;
2657
+ push(tokens, /[()[\]{};,]/.test(char) ? 'punctuation' : 'unknown', text, start);
2658
+ }
2659
+ }
2660
+ return { tokens, trivia, truncated };
2661
+ }
2662
+
2663
+ function scanPreservedSourceDirectives(sourceText, input) {
2664
+ const directives = [];
2665
+ const maxDirectives = Number.isFinite(input.maxDirectives) ? Math.max(0, input.maxDirectives) : 20000;
2666
+ let truncated = false;
2667
+ let offset = 0;
2668
+ for (const { line, number } of sourceLines(sourceText)) {
2669
+ const trimmed = line.trim();
2670
+ const directiveKind = preservedDirectiveKind(trimmed, input.language);
2671
+ if (directiveKind) {
2672
+ if (directives.length >= maxDirectives) {
2673
+ truncated = true;
2674
+ offset += line.length + 1;
2675
+ continue;
2676
+ }
2677
+ const startColumn = Math.max(1, line.indexOf(trimmed) + 1);
2678
+ directives.push({
2679
+ id: `directive_${idFragment(input.sourcePath ?? input.language)}_${directives.length + 1}`,
2680
+ kind: directiveKind,
2681
+ text: trimmed,
2682
+ textHash: hashSemanticValue(trimmed),
2683
+ span: {
2684
+ sourceId: input.sourceHash,
2685
+ path: input.sourcePath,
2686
+ start: offset + startColumn - 1,
2687
+ end: offset + startColumn - 1 + trimmed.length,
2688
+ startLine: number,
2689
+ startColumn,
2690
+ endLine: number,
2691
+ endColumn: startColumn + trimmed.length
2692
+ },
2693
+ metadata: { language: input.language }
2694
+ });
2695
+ }
2696
+ offset += line.length + 1;
2697
+ }
2698
+ return { directives, truncated };
2699
+ }
2700
+
2701
+ function preservedSourceSegment(input) {
2702
+ const id = `${input.kind}_${input.index + 1}_${idFragment(input.start.offset)}`;
2703
+ return {
2704
+ id,
2705
+ kind: input.kind,
2706
+ text: input.text,
2707
+ textHash: hashSemanticValue(input.text),
2708
+ span: {
2709
+ sourceId: input.sourceHash,
2710
+ path: input.sourcePath,
2711
+ start: input.start.offset,
2712
+ end: input.end.offset,
2713
+ startLine: input.start.line,
2714
+ startColumn: input.start.column,
2715
+ endLine: input.end.line,
2716
+ endColumn: input.end.column
2717
+ }
2718
+ };
2719
+ }
2720
+
2721
+ function preservedDirectiveKind(trimmed, language) {
2722
+ if (!trimmed) return undefined;
2723
+ if (/^#\s*(include|define|if|ifdef|ifndef|elif|else|endif|pragma)\b/.test(trimmed)) return 'preprocessor';
2724
+ if (/^#!\s*/.test(trimmed)) return 'shebang';
2725
+ if (/^['"]use strict['"];?$/.test(trimmed)) return 'runtime-directive';
2726
+ if (/^(import|export|package|module|namespace|use|using|from|require)\b/.test(trimmed)) return 'module-directive';
2727
+ if (normalizeNativeLanguageId(language) === 'python' && /^from\s+\S+\s+import\b/.test(trimmed)) return 'module-directive';
2728
+ return undefined;
2729
+ }
2730
+
2731
+ function preservedHashLineKind(text) {
2732
+ return preservedDirectiveKind(String(text).trim(), 'c') ? 'directive' : 'comment';
2733
+ }
2734
+
2735
+ function isHashCommentLanguage(language) {
2736
+ return ['python', 'ruby', 'shell', 'bash', 'zsh', 'r', 'perl', 'yaml', 'toml'].includes(normalizeNativeLanguageId(language));
2737
+ }
2738
+
2739
+ function detectNewlineStyle(sourceText) {
2740
+ const crlf = (sourceText.match(/\r\n/g) ?? []).length;
2741
+ const normalized = sourceText.replace(/\r\n/g, '');
2742
+ const lf = (normalized.match(/\n/g) ?? []).length;
2743
+ const cr = (normalized.match(/\r/g) ?? []).length;
2744
+ const kinds = [crlf ? 'crlf' : undefined, lf ? 'lf' : undefined, cr ? 'cr' : undefined].filter(Boolean);
2745
+ if (!kinds.length) return 'none';
2746
+ if (kinds.length > 1 || cr) return 'mixed';
2747
+ return kinds[0];
2748
+ }
2749
+
2750
+ const preservedKeywordSet = new Set([
2751
+ 'abstract', 'as', 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'def', 'defer',
2752
+ 'do', 'else', 'enum', 'export', 'extends', 'extern', 'false', 'final', 'fn', 'for', 'from', 'func', 'function',
2753
+ 'if', 'impl', 'import', 'in', 'interface', 'let', 'match', 'mod', 'module', 'mut', 'namespace', 'new', 'nil',
2754
+ 'none', 'null', 'package', 'private', 'protected', 'pub', 'public', 'return', 'self', 'static', 'struct',
2755
+ 'switch', 'this', 'throw', 'trait', 'true', 'try', 'type', 'use', 'using', 'var', 'while', 'yield'
2756
+ ]);
2757
+
2758
+ function isIdentifierStart(char) {
2759
+ return /[A-Za-z_$]/.test(char ?? '');
2760
+ }
2761
+
2762
+ function isIdentifierPart(char) {
2763
+ return /[A-Za-z0-9_$]/.test(char ?? '');
2764
+ }
2765
+
1568
2766
  function sourceLines(sourceText) {
1569
2767
  return String(sourceText ?? '').split(/\r?\n/).map((line, index) => ({ line, number: index + 1 }));
1570
2768
  }
@@ -1586,6 +2784,23 @@ function splitParameters(raw) {
1586
2784
  .filter(Boolean);
1587
2785
  }
1588
2786
 
2787
+ function splitTypeParameters(raw) {
2788
+ return splitParameters(raw);
2789
+ }
2790
+
2791
+ function braceDelta(source) {
2792
+ let delta = 0;
2793
+ for (const char of String(source ?? '')) {
2794
+ if (char === '{') delta += 1;
2795
+ if (char === '}') delta -= 1;
2796
+ }
2797
+ return delta;
2798
+ }
2799
+
2800
+ function jsControlKeyword(value) {
2801
+ return ['if', 'for', 'while', 'switch', 'catch', 'with'].includes(String(value));
2802
+ }
2803
+
1589
2804
  function inferSourceMapMappings(input) {
1590
2805
  const semanticIndex = input.semanticIndex;
1591
2806
  const nativeAst = input.nativeAst;
@@ -1887,14 +3102,261 @@ function nativeImportCategoryForLossKind(kind) {
1887
3102
  if (kind === 'preprocessor' || kind === 'conditionalCompilation' || kind === 'macroHygiene') return 'preprocessor';
1888
3103
  if (kind === 'metaprogramming' || kind === 'reflection' || kind === 'dynamicDispatch' || kind === 'dynamicRuntime') return 'metaprogramming';
1889
3104
  if (kind === 'generatedCode') return 'generatedCode';
1890
- if (kind === 'sourcePreservation' || kind === 'nonRoundTrippable') return 'sourcePreservation';
3105
+ if (kind === 'overloadResolution' || kind === 'typeInference') return 'overloadTypeInference';
3106
+ if (kind === 'sourcePreservation' || kind === 'commentsTrivia' || kind === 'nonRoundTrippable') return 'sourcePreservation';
1891
3107
  if (kind === 'parserDiagnostic') return 'parserDiagnostics';
1892
3108
  if (kind === 'unsupportedSyntax' || kind === 'unsupportedSemantic') return 'unsupportedSyntax';
1893
3109
  if (kind === 'partialSemanticIndex') return 'partialSemanticIndex';
1894
3110
  if (kind === 'sourceMapApproximation') return 'sourceMapApproximation';
3111
+ if (kind === 'targetProjectionLoss') return 'targetProjectionLoss';
1895
3112
  return String(kind ?? 'opaqueNative');
1896
3113
  }
1897
3114
 
3115
+ function nativeImportLanguageProfile(language, input = {}) {
3116
+ const lossKinds = input.lossKinds ?? ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation'];
3117
+ return Object.freeze({
3118
+ language,
3119
+ aliases: Object.freeze(uniqueStrings(input.aliases ?? [])),
3120
+ extensions: Object.freeze(uniqueStrings(input.extensions ?? [])),
3121
+ supportsLightweightScan: input.supportsLightweightScan !== false,
3122
+ parserAdapters: Object.freeze(uniqueStrings(input.parserAdapters ?? ['tree-sitter'])),
3123
+ projectionTargets: Object.freeze(uniqueStrings(input.projectionTargets ?? FrontierCompileTargets)),
3124
+ knownLossKinds: Object.freeze(uniqueStrings(lossKinds)),
3125
+ defaultReadiness: input.defaultReadiness ?? 'needs-review',
3126
+ notes: Object.freeze(uniqueStrings(input.notes ?? ['lightweight scanner records declarations only; exact parser adapters must be injected by the host']))
3127
+ });
3128
+ }
3129
+
3130
+ function mergeNativeImportProfiles(languages, imports, adapters) {
3131
+ const profilesByLanguage = new Map();
3132
+ for (const profile of languages) {
3133
+ const normalized = normalizeNativeLanguageId(profile.language ?? profile);
3134
+ profilesByLanguage.set(normalized, normalizeNativeImportLanguageProfile(profile, normalized));
3135
+ }
3136
+ for (const imported of imports) {
3137
+ const normalized = normalizeNativeLanguageId(imported?.language ?? imported?.nativeAst?.language);
3138
+ if (!normalized || profilesByLanguage.has(normalized)) continue;
3139
+ profilesByLanguage.set(normalized, nativeImportLanguageProfile(normalized, {
3140
+ supportsLightweightScan: false,
3141
+ parserAdapters: [],
3142
+ defaultReadiness: 'blocked',
3143
+ lossKinds: ['unsupportedSyntax'],
3144
+ notes: ['language appeared in import evidence but has no declared Frontier coverage profile']
3145
+ }));
3146
+ }
3147
+ for (const adapter of adapters) {
3148
+ const normalized = normalizeNativeLanguageId(adapter?.language);
3149
+ if (!normalized) continue;
3150
+ const existing = profilesByLanguage.get(normalized) ?? nativeImportLanguageProfile(normalized, { supportsLightweightScan: false, parserAdapters: [] });
3151
+ profilesByLanguage.set(normalized, {
3152
+ ...existing,
3153
+ parserAdapters: uniqueStrings([...(existing.parserAdapters ?? []), adapter.parser ?? adapter.id].filter(Boolean))
3154
+ });
3155
+ }
3156
+ return [...profilesByLanguage.values()].sort((left, right) => left.language.localeCompare(right.language));
3157
+ }
3158
+
3159
+ function normalizeNativeImportLanguageProfile(profile, fallbackLanguage) {
3160
+ const language = normalizeNativeLanguageId(profile.language ?? fallbackLanguage);
3161
+ return {
3162
+ language,
3163
+ aliases: uniqueStrings(profile.aliases ?? []),
3164
+ extensions: uniqueStrings(profile.extensions ?? []),
3165
+ supportsLightweightScan: profile.supportsLightweightScan !== false,
3166
+ parserAdapters: uniqueStrings(profile.parserAdapters ?? []),
3167
+ projectionTargets: uniqueStrings(profile.projectionTargets ?? FrontierCompileTargets),
3168
+ knownLossKinds: uniqueStrings(profile.knownLossKinds ?? profile.lossKinds ?? []),
3169
+ defaultReadiness: profile.defaultReadiness ?? 'needs-review',
3170
+ notes: uniqueStrings(profile.notes ?? [])
3171
+ };
3172
+ }
3173
+
3174
+ function nativeImportCoverageForProfile(profile, imports, adapters) {
3175
+ const aliases = new Set([profile.language, ...(profile.aliases ?? [])].map(normalizeNativeLanguageId).filter(Boolean));
3176
+ const matchingImports = imports.filter((imported) => aliases.has(normalizeNativeLanguageId(imported?.language ?? imported?.nativeAst?.language)));
3177
+ const matchingAdapters = adapters.filter((adapter) => aliases.has(normalizeNativeLanguageId(adapter?.language)));
3178
+ const lossSummary = summarizeNativeImportLosses(matchingImports.flatMap((imported) => imported?.losses ?? []), {
3179
+ evidence: matchingImports.flatMap((imported) => imported?.evidence ?? [])
3180
+ });
3181
+ const readiness = matchingImports.length
3182
+ ? lossSummary.semanticMergeReadiness
3183
+ : profile.supportsLightweightScan ? profile.defaultReadiness : 'blocked';
3184
+ const importedParsers = uniqueStrings(matchingImports.map((imported) => imported?.nativeAst?.parser ?? imported?.parser ?? imported?.metadata?.parser).filter(Boolean));
3185
+ const sourceMaps = matchingImports.flatMap((imported) => imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? []);
3186
+ return {
3187
+ language: profile.language,
3188
+ aliases: profile.aliases,
3189
+ extensions: profile.extensions,
3190
+ supportsLightweightScan: profile.supportsLightweightScan,
3191
+ parserAdapters: uniqueStrings([...(profile.parserAdapters ?? []), ...matchingAdapters.map((adapter) => adapter.parser ?? adapter.id).filter(Boolean)]),
3192
+ projectionTargets: profile.projectionTargets,
3193
+ knownLossKinds: uniqueStrings([...(profile.knownLossKinds ?? []), ...Object.keys(lossSummary.byKind)]),
3194
+ defaultReadiness: profile.defaultReadiness,
3195
+ notes: profile.notes,
3196
+ imports: {
3197
+ total: matchingImports.length,
3198
+ parsers: importedParsers,
3199
+ readiness,
3200
+ readinessReasons: matchingImports.length ? lossSummary.readinessReasons : nativeImportCoverageReasons(profile),
3201
+ symbols: matchingImports.reduce((sum, imported) => sum + (imported?.semanticIndex?.symbols?.length ?? imported?.universalAst?.semanticIndex?.symbols?.length ?? 0), 0),
3202
+ sourceMaps: sourceMaps.length,
3203
+ sourceMapMappings: sourceMaps.reduce((sum, sourceMap) => sum + (sourceMap?.mappings?.length ?? 0), 0),
3204
+ losses: lossSummary.total,
3205
+ lossKinds: lossSummary.byKind,
3206
+ lossCategories: lossSummary.categories
3207
+ }
3208
+ };
3209
+ }
3210
+
3211
+ function semanticImportSidecarEntry(imported, index, options) {
3212
+ const semanticIndex = imported?.semanticIndex ?? imported?.universalAst?.semanticIndex;
3213
+ const nativeAst = imported?.nativeAst ?? imported?.nativeSource?.ast;
3214
+ const sourceMaps = imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? [];
3215
+ const sourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap?.mappings ?? []);
3216
+ const mappingsBySymbolId = new Map();
3217
+ for (const mapping of sourceMapMappings) {
3218
+ if (mapping.semanticSymbolId && !mappingsBySymbolId.has(mapping.semanticSymbolId)) {
3219
+ mappingsBySymbolId.set(mapping.semanticSymbolId, mapping);
3220
+ }
3221
+ }
3222
+ const symbols = [];
3223
+ const regions = [];
3224
+ for (const symbol of semanticIndex?.symbols ?? []) {
3225
+ const mapping = mappingsBySymbolId.get(symbol.id);
3226
+ const nativeNode = symbol.nativeAstNodeId ? nativeAst?.nodes?.[symbol.nativeAstNodeId] : undefined;
3227
+ const region = semanticOwnershipRegionForSymbol(imported, symbol, mapping, nativeNode, options);
3228
+ regions.push(region);
3229
+ symbols.push({
3230
+ id: symbol.id,
3231
+ name: symbol.name,
3232
+ kind: symbol.kind,
3233
+ language: symbol.language ?? imported?.language,
3234
+ nativeAstNodeId: symbol.nativeAstNodeId,
3235
+ semanticOccurrenceId: mapping?.semanticOccurrenceId,
3236
+ sourceMapMappingId: mapping?.id,
3237
+ sourceSpan: mapping?.sourceSpan ?? symbol.definitionSpan ?? nativeNode?.span,
3238
+ signatureHash: symbol.signatureHash,
3239
+ ownershipRegionId: region.id,
3240
+ ownershipKey: region.key,
3241
+ readiness: imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review'
3242
+ });
3243
+ }
3244
+ return {
3245
+ id: imported?.id ?? `import_${index + 1}`,
3246
+ language: imported?.language,
3247
+ sourcePath: imported?.sourcePath ?? imported?.nativeSource?.sourcePath ?? nativeAst?.sourcePath,
3248
+ sourceHash: imported?.nativeSource?.sourceHash ?? nativeAst?.sourceHash,
3249
+ parser: imported?.nativeAst?.parser ?? nativeAst?.parser,
3250
+ nativeSourceId: imported?.nativeSource?.id,
3251
+ nativeAstId: nativeAst?.id,
3252
+ semanticIndexId: semanticIndex?.id,
3253
+ universalAstId: imported?.universalAst?.id,
3254
+ symbolCount: symbols.length,
3255
+ sourceMapCount: sourceMaps.length,
3256
+ sourceMapMappingCount: sourceMapMappings.length,
3257
+ readiness: imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review',
3258
+ emptySemanticIndex: symbols.length === 0,
3259
+ symbols,
3260
+ ownershipRegions: uniqueRecordsById(regions)
3261
+ };
3262
+ }
3263
+
3264
+ function semanticOwnershipRegionForSymbol(imported, symbol, mapping, nativeNode, options = {}) {
3265
+ const sourcePath = mapping?.sourceSpan?.path ?? symbol.definitionSpan?.path ?? nativeNode?.span?.path ?? imported?.sourcePath ?? imported?.nativeSource?.sourcePath ?? imported?.nativeAst?.sourcePath;
3266
+ const language = symbol.language ?? imported?.language ?? imported?.nativeAst?.language ?? imported?.nativeSource?.language;
3267
+ const sourceSpan = mapping?.sourceSpan ?? symbol.definitionSpan ?? nativeNode?.span;
3268
+ const key = [
3269
+ options.regionPrefix ?? 'source',
3270
+ sourcePath ?? `${language}:memory`,
3271
+ symbol.kind ?? 'symbol',
3272
+ symbol.name ?? symbol.id
3273
+ ].map((part) => String(part).replace(/\s+/g, ' ').trim()).join('#');
3274
+ return {
3275
+ id: `region_${idFragment(key)}`,
3276
+ key,
3277
+ granularity: 'symbol',
3278
+ language,
3279
+ sourcePath,
3280
+ sourceHash: imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash,
3281
+ symbolId: symbol.id,
3282
+ symbolName: symbol.name,
3283
+ symbolKind: symbol.kind,
3284
+ nativeAstNodeId: symbol.nativeAstNodeId ?? nativeNode?.id,
3285
+ sourceSpan,
3286
+ precision: mapping?.precision ?? (sourceSpan ? 'declaration' : 'unknown'),
3287
+ mergePolicy: 'single-writer-review-required'
3288
+ };
3289
+ }
3290
+
3291
+ function semanticOwnershipRegionForDeclaration(input, declaration, documentId) {
3292
+ const name = declaration.name ?? declaration.importPath ?? declaration.nodeId ?? declaration.nativeNode?.id;
3293
+ const kind = declaration.symbolKind ?? declaration.kind ?? declaration.nativeNode?.kind ?? 'symbol';
3294
+ const sourcePath = declaration.span?.path ?? declaration.nativeNode?.span?.path ?? input.sourcePath ?? `${input.language}:memory`;
3295
+ const key = ['source', sourcePath, kind, name].map((part) => String(part).replace(/\s+/g, ' ').trim()).join('#');
3296
+ return {
3297
+ id: `region_${idFragment(key)}`,
3298
+ key,
3299
+ granularity: 'symbol',
3300
+ language: input.language,
3301
+ documentId,
3302
+ sourcePath,
3303
+ sourceHash: input.sourceHash,
3304
+ symbolId: declaration.symbolId,
3305
+ symbolName: name,
3306
+ symbolKind: kind,
3307
+ nativeAstNodeId: declaration.nodeId ?? declaration.nativeNode?.id,
3308
+ sourceSpan: declaration.span ?? declaration.nativeNode?.span,
3309
+ precision: declaration.span || declaration.nativeNode?.span ? 'declaration' : 'unknown',
3310
+ mergePolicy: 'single-writer-review-required'
3311
+ };
3312
+ }
3313
+
3314
+ function semanticPatchHintForRegion(region, readiness, options = {}) {
3315
+ return {
3316
+ id: `hint_${idFragment(region.id)}`,
3317
+ kind: 'source-region-patch',
3318
+ ownershipRegionId: region.id,
3319
+ ownershipKey: region.key,
3320
+ sourcePath: region.sourcePath,
3321
+ sourceHash: region.sourceHash,
3322
+ sourceSpan: region.sourceSpan,
3323
+ readiness,
3324
+ precision: region.precision,
3325
+ supportedOperations: ['replace-region', 'insert-before-region', 'insert-after-region'],
3326
+ projection: {
3327
+ sourceLanguage: region.language,
3328
+ targetPath: options.targetPath ?? region.sourcePath,
3329
+ requiresSourceMap: true
3330
+ }
3331
+ };
3332
+ }
3333
+
3334
+ function nativeImportCoverageReasons(profile) {
3335
+ if (!profile.supportsLightweightScan) return ['No built-in scanner coverage profile; host must provide an exact adapter or mark unsupported.'];
3336
+ return ['Built-in coverage is declaration-level only; use injected parser adapters for exact AST/CST, tokens, trivia, type resolution, and round-trip evidence.'];
3337
+ }
3338
+
3339
+ function normalizeNativeLanguageId(value) {
3340
+ if (!value) return '';
3341
+ const text = String(value).trim().toLowerCase();
3342
+ if (text === 'js' || text === 'mjs' || text === 'cjs' || text === 'jsx') return 'javascript';
3343
+ if (text === 'ts' || text === 'tsx') return 'typescript';
3344
+ if (text === 'py' || text === 'pyi') return 'python';
3345
+ if (text === 'rs') return 'rust';
3346
+ if (text === 'h') return 'c';
3347
+ if (text === 'c++' || text === 'cc' || text === 'cxx' || text === 'hpp' || text === 'hh') return 'cpp';
3348
+ if (text === 'c#' || text === 'cs') return 'csharp';
3349
+ if (text === 'rb' || text === 'rake') return 'ruby';
3350
+ if (text === 'kt' || text === 'kts') return 'kotlin';
3351
+ if (text === 'sc') return 'scala';
3352
+ if (text === 'sh' || text === 'bash' || text === 'zsh') return 'shell';
3353
+ if (text === 'postgresql' || text === 'postgres' || text === 'mysql' || text === 'sqlite') return 'sql';
3354
+ if (text === 'ex' || text === 'exs') return 'elixir';
3355
+ if (text === 'erl' || text === 'hrl') return 'erlang';
3356
+ if (text === 'hs' || text === 'lhs') return 'haskell';
3357
+ return text;
3358
+ }
3359
+
1898
3360
  function semanticMergeAdmissionForSeverity(severity) {
1899
3361
  if (severity === 'error') return 'blocked';
1900
3362
  if (severity === 'warning') return 'review';
@@ -2010,6 +3472,20 @@ function createJavaScriptSyntaxImporterAdapter(options) {
2010
3472
  parser: options.parser,
2011
3473
  version: options.version,
2012
3474
  capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
3475
+ coverage: nativeImporterAdapterCoverage({
3476
+ exactness: 'exact-parser-ast',
3477
+ exactAst: true,
3478
+ tokens: false,
3479
+ trivia: false,
3480
+ diagnostics: true,
3481
+ sourceRanges: true,
3482
+ generatedRanges: false,
3483
+ semanticCoverage: declarationSemanticCoverage(),
3484
+ notes: [
3485
+ 'Normalizes a caller-owned ESTree/Babel-compatible AST into native AST nodes and declaration-level semantic index records.',
3486
+ 'The wrapper ignores parser token/trivia/comment arrays unless a host adapter explicitly maps them into preservation evidence.'
3487
+ ]
3488
+ }, options.coverage),
2013
3489
  supportedExtensions: options.supportedExtensions,
2014
3490
  diagnostics: options.diagnostics,
2015
3491
  parse(input) {
@@ -2335,6 +3811,19 @@ function semanticIndexFromNativeDeclarations(declarations, input, options) {
2335
3811
  for (const declaration of declarations) {
2336
3812
  const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${idFragment(declaration.name)}`;
2337
3813
  const occurrenceId = `occ_${idFragment(declaration.nativeNode.id)}_${declaration.role ?? 'definition'}`;
3814
+ const ownershipRegion = semanticOwnershipRegionForDeclaration(input, {
3815
+ ...declaration,
3816
+ nodeId: declaration.nativeNode.id,
3817
+ kind: declaration.nativeNode.kind,
3818
+ languageKind: declaration.nativeNode.languageKind,
3819
+ span: declaration.nativeNode.span,
3820
+ symbolId
3821
+ }, documentId);
3822
+ declaration.nativeNode.metadata = {
3823
+ ...declaration.nativeNode.metadata,
3824
+ ownershipRegionId: ownershipRegion.id,
3825
+ ownershipRegionKey: ownershipRegion.key
3826
+ };
2338
3827
  symbols.push({
2339
3828
  id: symbolId,
2340
3829
  scheme: 'frontier',
@@ -2343,7 +3832,11 @@ function semanticIndexFromNativeDeclarations(declarations, input, options) {
2343
3832
  language: input.language,
2344
3833
  nativeAstNodeId: declaration.nativeNode.id,
2345
3834
  signatureHash: hashSemanticValue([input.language, declaration.nativeNode.kind, declaration.name, declaration.nativeNode.fields ?? {}]),
2346
- definitionSpan: declaration.nativeNode.span
3835
+ definitionSpan: declaration.nativeNode.span,
3836
+ metadata: {
3837
+ ownershipRegionId: ownershipRegion.id,
3838
+ ownershipRegionKey: ownershipRegion.key
3839
+ }
2347
3840
  });
2348
3841
  occurrences.push({
2349
3842
  id: occurrenceId,
@@ -2364,6 +3857,11 @@ function semanticIndexFromNativeDeclarations(declarations, input, options) {
2364
3857
  predicate: 'nativeKind',
2365
3858
  subjectId: symbolId,
2366
3859
  value: declaration.nativeNode.languageKind
3860
+ }, {
3861
+ id: `fact_${idFragment(declaration.nativeNode.id)}_ownership_region`,
3862
+ predicate: 'semanticOwnershipRegion',
3863
+ subjectId: symbolId,
3864
+ value: ownershipRegion
2367
3865
  });
2368
3866
  mappings.push({
2369
3867
  id: `map_${idFragment(declaration.nativeNode.id)}`,
@@ -2373,6 +3871,7 @@ function semanticIndexFromNativeDeclarations(declarations, input, options) {
2373
3871
  sourceSpan: declaration.nativeNode.span,
2374
3872
  evidenceIds: [evidenceId],
2375
3873
  lossIds: [],
3874
+ ownershipRegionId: ownershipRegion.id,
2376
3875
  precision: declaration.nativeNode.span ? 'declaration' : 'unknown'
2377
3876
  });
2378
3877
  }
@@ -2437,6 +3936,14 @@ function createNativeProjectImportResult(input, imports) {
2437
3936
  mergeCandidates.push(...(result.mergeCandidates ?? []));
2438
3937
  operations.push(...(result.patch?.operations ?? []));
2439
3938
  }
3939
+ const uniqueLosses = uniqueByLossId(losses);
3940
+ const uniqueEvidence = uniqueByEvidenceId(evidence);
3941
+ const nativeImportLossSummary = summarizeNativeImportLosses(uniqueLosses, {
3942
+ evidence: uniqueEvidence,
3943
+ scanKind: 'native-project-import',
3944
+ semanticStatus: uniqueLosses.some((loss) => loss.severity === 'error') ? 'partial' : 'mapped'
3945
+ });
3946
+ const sourcePreservationSummary = summarizeProjectSourcePreservation(imports);
2440
3947
  const document = createDocument({
2441
3948
  id: input.documentId ?? `document_${idPart}`,
2442
3949
  name: input.documentName ?? input.name ?? 'NativeProject',
@@ -2447,6 +3954,8 @@ function createNativeProjectImportResult(input, imports) {
2447
3954
  semanticStatus: losses.some((loss) => loss.severity === 'error') ? 'partial' : 'mapped',
2448
3955
  projectRoot: input.projectRoot,
2449
3956
  sourceCount: imports.length,
3957
+ nativeImportLossSummary,
3958
+ sourcePreservationSummary,
2450
3959
  ...input.documentMetadata
2451
3960
  }
2452
3961
  });
@@ -2456,12 +3965,14 @@ function createNativeProjectImportResult(input, imports) {
2456
3965
  nativeSources,
2457
3966
  semanticIndex,
2458
3967
  sourceMaps,
2459
- losses: uniqueByLossId(losses),
2460
- evidence: uniqueByEvidenceId(evidence),
3968
+ losses: uniqueLosses,
3969
+ evidence: uniqueEvidence,
2461
3970
  metadata: {
2462
3971
  sourceLanguage: input.language ?? 'mixed',
2463
3972
  projectRoot: input.projectRoot,
2464
3973
  sourceCount: imports.length,
3974
+ nativeImportLossSummary,
3975
+ sourcePreservationSummary,
2465
3976
  ...input.universalAstMetadata
2466
3977
  }
2467
3978
  });
@@ -2470,12 +3981,14 @@ function createNativeProjectImportResult(input, imports) {
2470
3981
  author: input.author ?? '@shapeshift-labs/frontier-lang-compiler/importNativeProject',
2471
3982
  risk: losses.some((loss) => loss.severity === 'error') ? 'high' : losses.some((loss) => loss.severity === 'warning') ? 'medium' : 'low',
2472
3983
  operations,
2473
- evidence: uniqueByEvidenceId(evidence),
3984
+ evidence: uniqueEvidence,
2474
3985
  metadata: {
2475
3986
  semanticIndexId: semanticIndex?.id,
2476
3987
  universalAstId: universalAst.id,
2477
3988
  sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
2478
- sourceCount: imports.length
3989
+ sourceCount: imports.length,
3990
+ nativeImportLossSummary,
3991
+ sourcePreservationSummary
2479
3992
  }
2480
3993
  });
2481
3994
  return {
@@ -2491,17 +4004,35 @@ function createNativeProjectImportResult(input, imports) {
2491
4004
  semanticIndex,
2492
4005
  universalAst,
2493
4006
  sourceMaps,
2494
- losses: uniqueByLossId(losses),
2495
- evidence: uniqueByEvidenceId(evidence),
4007
+ losses: uniqueLosses,
4008
+ evidence: uniqueEvidence,
2496
4009
  mergeCandidates,
2497
4010
  metadata: {
2498
4011
  sourceCount: imports.length,
2499
4012
  sourcePaths: imports.map((result) => result.sourcePath).filter(Boolean),
4013
+ nativeImportLossSummary,
4014
+ sourcePreservationSummary,
2500
4015
  ...input.metadata
2501
4016
  }
2502
4017
  };
2503
4018
  }
2504
4019
 
4020
+ function summarizeProjectSourcePreservation(imports) {
4021
+ const records = imports
4022
+ .map((result) => result.metadata?.sourcePreservation ?? result.nativeSource?.metadata?.sourcePreservation ?? result.nativeAst?.metadata?.sourcePreservation)
4023
+ .filter(Boolean);
4024
+ return {
4025
+ total: records.length,
4026
+ exactSourceAvailable: records.filter((record) => record.summary?.exactSourceAvailable).length,
4027
+ sourceBytes: records.reduce((sum, record) => sum + (record.sourceBytes ?? 0), 0),
4028
+ tokens: records.reduce((sum, record) => sum + (record.summary?.tokens ?? record.tokens?.length ?? 0), 0),
4029
+ trivia: records.reduce((sum, record) => sum + (record.summary?.trivia ?? record.trivia?.length ?? 0), 0),
4030
+ directives: records.reduce((sum, record) => sum + (record.summary?.directives ?? record.directives?.length ?? 0), 0),
4031
+ truncated: records.some((record) => record.summary?.truncated === true),
4032
+ ids: records.map((record) => record.id).filter(Boolean)
4033
+ };
4034
+ }
4035
+
2505
4036
  function mergeSemanticIndexes(imports, input, idPart) {
2506
4037
  const indexes = imports.map((result) => result.semanticIndex ?? result.universalAst?.semanticIndex).filter(Boolean);
2507
4038
  if (!indexes.length) return undefined;
@@ -2547,9 +4078,15 @@ function normalizeNativeImporterAdapter(adapter) {
2547
4078
  parser: String(adapter.parser),
2548
4079
  version: adapter.version === undefined ? undefined : String(adapter.version)
2549
4080
  };
4081
+ const capabilities = normalizeStringList(adapter.capabilities);
2550
4082
  return Object.freeze({
2551
4083
  ...summaryInput,
2552
- capabilities: normalizeStringList(adapter.capabilities),
4084
+ capabilities,
4085
+ coverage: normalizeNativeImporterAdapterCoverage(adapter.coverage, {
4086
+ capabilities,
4087
+ language: adapter.language,
4088
+ parser: String(adapter.parser)
4089
+ }),
2553
4090
  supportedExtensions: normalizeStringList(adapter.supportedExtensions).map((extension) => extension.startsWith('.') ? extension.toLowerCase() : `.${extension.toLowerCase()}`),
2554
4091
  diagnostics: normalizeAdapterDiagnostics(adapter.diagnostics, summaryInput, {
2555
4092
  language: adapter.language,
@@ -2559,6 +4096,138 @@ function normalizeNativeImporterAdapter(adapter) {
2559
4096
  });
2560
4097
  }
2561
4098
 
4099
+ function nativeImporterAdapterCoverage(defaults = {}, overrides = {}) {
4100
+ return {
4101
+ ...defaults,
4102
+ ...overrides,
4103
+ semanticCoverage: {
4104
+ ...(defaults.semanticCoverage ?? {}),
4105
+ ...(overrides.semanticCoverage ?? {})
4106
+ },
4107
+ notes: uniqueStrings([...(defaults.notes ?? []), ...(overrides.notes ?? [])])
4108
+ };
4109
+ }
4110
+
4111
+ function normalizeNativeImporterAdapterCoverage(value = {}, context = {}) {
4112
+ const capabilities = new Set(normalizeStringList(context.capabilities).map((capability) => capability.toLowerCase()));
4113
+ const hasCapability = (...names) => names.some((name) => capabilities.has(String(name).toLowerCase()));
4114
+ const exactAst = Boolean(value.exactAst ?? hasCapability('exactAst', 'exactAstImport'));
4115
+ const sourceRanges = Boolean(value.sourceRanges ?? hasCapability('sourceRanges', 'sourceRange', 'ranges', 'sourceMaps'));
4116
+ const generatedRanges = Boolean(value.generatedRanges ?? hasCapability('generatedRanges', 'generatedRange', 'generatedSourceMaps'));
4117
+ const diagnostics = Boolean(value.diagnostics ?? hasCapability('diagnostics', 'parserDiagnostics'));
4118
+ return Object.freeze({
4119
+ exactness: String(value.exactness ?? inferredAdapterExactness(exactAst, capabilities)),
4120
+ exactAst,
4121
+ tokens: Boolean(value.tokens ?? hasCapability('tokens', 'tokenStream')),
4122
+ trivia: Boolean(value.trivia ?? hasCapability('trivia', 'comments', 'formatting')),
4123
+ diagnostics,
4124
+ sourceRanges,
4125
+ generatedRanges,
4126
+ semanticCoverage: normalizeNativeImporterSemanticCoverage(value.semanticCoverage, {
4127
+ capabilities,
4128
+ sourceRanges,
4129
+ generatedRanges
4130
+ }),
4131
+ notes: uniqueStrings(value.notes ?? inferredAdapterCoverageNotes(context, {
4132
+ exactAst,
4133
+ sourceRanges,
4134
+ generatedRanges,
4135
+ diagnostics
4136
+ }))
4137
+ });
4138
+ }
4139
+
4140
+ function observeNativeImporterAdapterCoverage(coverage, parseResult = {}, context = {}) {
4141
+ const nodes = parseResult.nativeAst?.nodes ?? parseResult.nodes ?? {};
4142
+ const nodeList = Object.values(nodes);
4143
+ const sourceMapMappings = parseResult.sourceMaps?.flatMap((sourceMap) => sourceMap.mappings ?? []) ?? parseResult.mappings ?? [];
4144
+ const semanticIndex = parseResult.semanticIndex;
4145
+ const semanticSymbols = semanticIndex?.symbols?.length ?? 0;
4146
+ const observedSourceRanges = nodeList.some((node) => Boolean(node?.span)) || sourceMapMappings.some((mapping) => Boolean(mapping?.sourceSpan));
4147
+ const observedGeneratedRanges = sourceMapMappings.some((mapping) => Boolean(mapping?.generatedSpan));
4148
+ const observedSemanticCoverage = normalizeNativeImporterSemanticCoverage({
4149
+ ...coverage.semanticCoverage,
4150
+ level: semanticSymbols
4151
+ ? maxSemanticCoverageLevel(coverage.semanticCoverage?.level, 'declaration-index')
4152
+ : coverage.semanticCoverage?.level,
4153
+ declarations: coverage.semanticCoverage?.declarations || semanticSymbols > 0,
4154
+ symbols: coverage.semanticCoverage?.symbols || semanticSymbols > 0
4155
+ }, {});
4156
+ return Object.freeze({
4157
+ ...coverage,
4158
+ diagnostics: coverage.diagnostics || (context.diagnostics?.length ?? 0) > 0,
4159
+ sourceRanges: coverage.sourceRanges || observedSourceRanges,
4160
+ generatedRanges: coverage.generatedRanges || observedGeneratedRanges,
4161
+ semanticCoverage: observedSemanticCoverage,
4162
+ observed: {
4163
+ diagnostics: context.diagnostics?.length ?? 0,
4164
+ losses: context.losses?.length ?? 0,
4165
+ nativeAstNodes: nodeList.length,
4166
+ semanticSymbols,
4167
+ sourceMapMappings: sourceMapMappings.length,
4168
+ sourceRanges: observedSourceRanges,
4169
+ generatedRanges: observedGeneratedRanges
4170
+ }
4171
+ });
4172
+ }
4173
+
4174
+ function declarationSemanticCoverage() {
4175
+ return {
4176
+ level: 'declaration-index',
4177
+ declarations: true,
4178
+ symbols: true,
4179
+ references: false,
4180
+ types: false,
4181
+ controlFlow: false
4182
+ };
4183
+ }
4184
+
4185
+ function normalizeNativeImporterSemanticCoverage(value = {}, context = {}) {
4186
+ const capabilities = context.capabilities ?? new Set();
4187
+ const hasCapability = (...names) => names.some((name) => capabilities.has(String(name).toLowerCase()));
4188
+ const declarations = Boolean(value.declarations ?? hasCapability('semanticIndex', 'declarations'));
4189
+ const symbols = Boolean(value.symbols ?? declarations);
4190
+ const references = Boolean(value.references ?? hasCapability('references', 'referenceIndex'));
4191
+ const types = Boolean(value.types ?? hasCapability('types', 'typeResolution', 'typeChecking'));
4192
+ const controlFlow = Boolean(value.controlFlow ?? hasCapability('controlFlow', 'cfg'));
4193
+ return Object.freeze({
4194
+ level: String(value.level ?? inferredSemanticCoverageLevel({ declarations, symbols, references, types, controlFlow })),
4195
+ declarations,
4196
+ symbols,
4197
+ references,
4198
+ types,
4199
+ controlFlow
4200
+ });
4201
+ }
4202
+
4203
+ function inferredAdapterExactness(exactAst, capabilities) {
4204
+ if (exactAst) return 'exact-parser-ast';
4205
+ if (capabilities.has('nativeast')) return 'adapter-reported-native-ast';
4206
+ return 'loss-aware-native-ast';
4207
+ }
4208
+
4209
+ function inferredSemanticCoverageLevel(input) {
4210
+ if (input.references || input.types || input.controlFlow) return 'semantic-index';
4211
+ if (input.declarations || input.symbols) return 'declaration-index';
4212
+ return 'native-ast';
4213
+ }
4214
+
4215
+ function maxSemanticCoverageLevel(left, right) {
4216
+ const ranks = { 'native-ast': 0, 'declaration-index': 1, 'semantic-index': 2 };
4217
+ const leftRank = ranks[left] ?? 0;
4218
+ const rightRank = ranks[right] ?? 0;
4219
+ return rightRank > leftRank ? right : left;
4220
+ }
4221
+
4222
+ function inferredAdapterCoverageNotes(context, coverage) {
4223
+ const notes = [];
4224
+ if (!coverage.exactAst) notes.push('Adapter did not declare exact parser AST/CST coverage; import readiness depends on losses and evidence.');
4225
+ if (!coverage.generatedRanges) notes.push('Adapter does not declare generated-range coverage unless parse output includes generated spans.');
4226
+ if (!coverage.diagnostics) notes.push('Adapter did not declare parser diagnostics support.');
4227
+ if (context.language && context.parser) notes.push(`Coverage summary applies to ${context.language} via ${context.parser}.`);
4228
+ return notes;
4229
+ }
4230
+
2562
4231
  function normalizeStringList(value) {
2563
4232
  if (value === undefined || value === null) return [];
2564
4233
  if (Array.isArray(value)) return value.map((item) => String(item)).filter(Boolean);
@@ -2652,6 +4321,7 @@ function adapterDiagnosticsEvidence(adapter, diagnostics, input) {
2652
4321
  parserVersion: input.parserVersion,
2653
4322
  sourceHash: input.sourceHash,
2654
4323
  capabilities: adapter.capabilities,
4324
+ coverage: adapter.coverage,
2655
4325
  supportedExtensions: adapter.supportedExtensions,
2656
4326
  diagnostics: diagnostics.map(serializableDiagnostic),
2657
4327
  errors,