@shapeshift-labs/frontier-lang-compiler 0.2.7 → 0.2.9

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
@@ -10,6 +10,7 @@ import {
10
10
  hashSemanticValue,
11
11
  nativeSourceNode,
12
12
  stableUniversalAstJson,
13
+ validateSourceMapRecord,
13
14
  validateUniversalAstEnvelope
14
15
  } from '@shapeshift-labs/frontier-lang-kernel';
15
16
  import { parseFrontierFile, parseFrontierSource } from '@shapeshift-labs/frontier-lang-parser';
@@ -52,6 +53,112 @@ const canonicalTargets = Object.freeze({
52
53
  h: 'c'
53
54
  });
54
55
 
56
+ const lossSeverityRank = Object.freeze({
57
+ none: 0,
58
+ info: 1,
59
+ warning: 2,
60
+ error: 3
61
+ });
62
+
63
+ const semanticMergeReadinessRank = Object.freeze({
64
+ ready: 0,
65
+ 'ready-with-losses': 1,
66
+ 'needs-review': 2,
67
+ blocked: 3
68
+ });
69
+
70
+ export const NativeImportTaxonomyKinds = Object.freeze([
71
+ 'exactAstImport',
72
+ 'declarationsOnly',
73
+ 'opaqueBodies',
74
+ 'macroExpansion',
75
+ 'preprocessor',
76
+ 'metaprogramming',
77
+ 'generatedCode',
78
+ 'sourcePreservation',
79
+ 'parserDiagnostics',
80
+ 'unsupportedSyntax',
81
+ 'partialSemanticIndex',
82
+ 'sourceMapApproximation'
83
+ ]);
84
+
85
+ export const NativeImportLossKinds = Object.freeze([
86
+ 'declarationOnlyCoverage',
87
+ 'opaqueNative',
88
+ 'macroExpansion',
89
+ 'preprocessor',
90
+ 'metaprogramming',
91
+ 'generatedCode',
92
+ 'sourcePreservation',
93
+ 'parserDiagnostic',
94
+ 'unsupportedSyntax',
95
+ 'partialSemanticIndex',
96
+ 'sourceMapApproximation'
97
+ ]);
98
+
99
+ export const NativeImportReadinessBySeverity = Object.freeze({
100
+ none: 'ready',
101
+ info: 'ready-with-losses',
102
+ warning: 'needs-review',
103
+ error: 'blocked'
104
+ });
105
+
106
+ export const NativeImportLanguageProfiles = Object.freeze([
107
+ nativeImportLanguageProfile('javascript', {
108
+ aliases: ['js', 'mjs', 'cjs', 'jsx'],
109
+ extensions: ['.js', '.mjs', '.cjs', '.jsx'],
110
+ parserAdapters: ['estree', 'babel', 'tree-sitter'],
111
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'dynamicRuntime']
112
+ }),
113
+ nativeImportLanguageProfile('typescript', {
114
+ aliases: ['ts', 'tsx'],
115
+ extensions: ['.ts', '.tsx'],
116
+ parserAdapters: ['typescript-compiler-api', 'babel', 'tree-sitter'],
117
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'unsupportedSyntax']
118
+ }),
119
+ nativeImportLanguageProfile('python', {
120
+ aliases: ['py'],
121
+ extensions: ['.py', '.pyi'],
122
+ parserAdapters: ['python-ast', 'libcst', 'parso', 'tree-sitter'],
123
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation', 'dynamicRuntime']
124
+ }),
125
+ nativeImportLanguageProfile('rust', {
126
+ aliases: ['rs'],
127
+ extensions: ['.rs'],
128
+ parserAdapters: ['syn', 'rust-analyzer-rowan', 'tree-sitter'],
129
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation']
130
+ }),
131
+ nativeImportLanguageProfile('c', {
132
+ aliases: ['h'],
133
+ extensions: ['.c', '.h'],
134
+ parserAdapters: ['clang', 'libclang', 'tree-sitter'],
135
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'preprocessor', 'sourceMapApproximation', 'sourcePreservation']
136
+ }),
137
+ nativeImportLanguageProfile('cpp', {
138
+ aliases: ['c++', 'cc', 'cxx', 'hpp'],
139
+ extensions: ['.cc', '.cpp', '.cxx', '.hpp', '.hh'],
140
+ parserAdapters: ['clang', 'libclang', 'tree-sitter'],
141
+ lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'preprocessor', 'metaprogramming', 'sourceMapApproximation', 'sourcePreservation']
142
+ }),
143
+ nativeImportLanguageProfile('java', { extensions: ['.java'], parserAdapters: ['javac', 'jdt', 'javaparser', 'tree-sitter'] }),
144
+ nativeImportLanguageProfile('go', { extensions: ['.go'], parserAdapters: ['go/parser', 'tree-sitter'] }),
145
+ nativeImportLanguageProfile('swift', { extensions: ['.swift'], parserAdapters: ['swift-syntax', 'tree-sitter'] }),
146
+ nativeImportLanguageProfile('csharp', { aliases: ['c#', 'cs'], extensions: ['.cs'], parserAdapters: ['roslyn', 'tree-sitter'] }),
147
+ nativeImportLanguageProfile('php', { extensions: ['.php'], parserAdapters: ['php-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'metaprogramming', 'sourceMapApproximation', 'sourcePreservation'] }),
148
+ nativeImportLanguageProfile('ruby', { aliases: ['rb'], extensions: ['.rb', '.rake'], parserAdapters: ['prism', 'ripper', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'metaprogramming', 'sourceMapApproximation', 'sourcePreservation'] }),
149
+ nativeImportLanguageProfile('kotlin', { aliases: ['kt', 'kts'], extensions: ['.kt', '.kts'], parserAdapters: ['kotlin-compiler', 'intellij-psi', 'tree-sitter'] }),
150
+ nativeImportLanguageProfile('scala', { aliases: ['sc'], extensions: ['.scala', '.sc'], parserAdapters: ['scala-compiler', 'scalameta', 'tree-sitter'] }),
151
+ nativeImportLanguageProfile('dart', { extensions: ['.dart'], parserAdapters: ['dart-analyzer', 'tree-sitter'] }),
152
+ nativeImportLanguageProfile('lua', { extensions: ['.lua'], parserAdapters: ['luaparse', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] }),
153
+ nativeImportLanguageProfile('shell', { aliases: ['sh', 'bash', 'zsh'], extensions: ['.sh', '.bash', '.zsh'], parserAdapters: ['bash-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] }),
154
+ nativeImportLanguageProfile('sql', { aliases: ['postgresql', 'postgres', 'mysql', 'sqlite'], extensions: ['.sql'], parserAdapters: ['sqlparser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'unsupportedSyntax', 'sourceMapApproximation', 'sourcePreservation'] }),
155
+ nativeImportLanguageProfile('zig', { extensions: ['.zig'], parserAdapters: ['zig-ast', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'generatedCode', 'sourceMapApproximation', 'sourcePreservation'] }),
156
+ nativeImportLanguageProfile('elixir', { aliases: ['ex', 'exs'], extensions: ['.ex', '.exs'], parserAdapters: ['elixir-quoted', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation'] }),
157
+ nativeImportLanguageProfile('erlang', { aliases: ['erl', 'hrl'], extensions: ['.erl', '.hrl'], parserAdapters: ['erl_parse', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'preprocessor', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation'] }),
158
+ nativeImportLanguageProfile('haskell', { aliases: ['hs'], extensions: ['.hs', '.lhs'], parserAdapters: ['ghc-api', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'macroExpansion', 'sourceMapApproximation', 'sourcePreservation'] }),
159
+ nativeImportLanguageProfile('r', { aliases: ['R'], extensions: ['.r', '.R'], parserAdapters: ['r-parser', 'tree-sitter'], lossKinds: ['declarationOnlyCoverage', 'opaqueNative', 'dynamicRuntime', 'sourceMapApproximation', 'sourcePreservation'] })
160
+ ]);
161
+
55
162
  export function normalizeCompileTarget(target) {
56
163
  const normalized = String(target ?? 'typescript').toLowerCase();
57
164
  const canonical = canonicalTargets[normalized] ?? normalized;
@@ -136,6 +243,298 @@ export function resolveCapabilityAdapters(document, target = 'typescript', optio
136
243
  });
137
244
  }
138
245
 
246
+ export function summarizeNativeImportLosses(losses = [], options = {}) {
247
+ const normalizedLosses = normalizeNativeLossRecords(losses);
248
+ const bySeverity = { info: 0, warning: 0, error: 0 };
249
+ const byKind = {};
250
+ const blockingLossIds = [];
251
+ const reviewLossIds = [];
252
+ const informationalLossIds = [];
253
+ let highestSeverity = 'none';
254
+
255
+ for (const loss of normalizedLosses) {
256
+ bySeverity[loss.severity] += 1;
257
+ byKind[loss.kind] = (byKind[loss.kind] ?? 0) + 1;
258
+ if (lossSeverityRank[loss.severity] > lossSeverityRank[highestSeverity]) {
259
+ highestSeverity = loss.severity;
260
+ }
261
+ if (loss.severity === 'error') blockingLossIds.push(loss.id);
262
+ else if (loss.severity === 'warning') reviewLossIds.push(loss.id);
263
+ else informationalLossIds.push(loss.id);
264
+ }
265
+
266
+ const failedEvidenceIds = (options.evidence ?? [])
267
+ .filter((record) => record?.status === 'failed')
268
+ .map((record) => record.id)
269
+ .filter(Boolean);
270
+ const exactAst = Boolean(options.exactAst) && normalizedLosses.length === 0;
271
+ const categories = uniqueStrings([
272
+ ...(exactAst ? ['exactAstImport'] : []),
273
+ ...normalizedLosses.map((loss) => nativeImportCategoryForLossKind(loss.kind))
274
+ ]);
275
+ const semanticMergeReadiness = failedEvidenceIds.length
276
+ ? 'blocked'
277
+ : NativeImportReadinessBySeverity[highestSeverity];
278
+ const readinessReasons = nativeImportReadinessReasons({
279
+ exactAst,
280
+ failedEvidenceIds,
281
+ blockingLossIds,
282
+ reviewLossIds,
283
+ informationalLossIds
284
+ });
285
+
286
+ return {
287
+ total: normalizedLosses.length,
288
+ hasLosses: normalizedLosses.length > 0,
289
+ exactAst,
290
+ highestSeverity,
291
+ semanticMergeReadiness,
292
+ readinessReasons,
293
+ categories,
294
+ bySeverity,
295
+ byKind,
296
+ blockingLossIds,
297
+ reviewLossIds,
298
+ informationalLossIds,
299
+ failedEvidenceIds,
300
+ parser: options.parser,
301
+ scanKind: options.scanKind,
302
+ semanticStatus: options.semanticStatus
303
+ };
304
+ }
305
+
306
+ export function classifyNativeImportReadiness(losses = [], options = {}) {
307
+ const summary = summarizeNativeImportLosses(losses, options);
308
+ return {
309
+ readiness: summary.semanticMergeReadiness,
310
+ reasons: summary.readinessReasons,
311
+ summary
312
+ };
313
+ }
314
+
315
+ export function createNativeImportCoverageMatrix(input = {}) {
316
+ const imports = input.imports ?? [];
317
+ const adapters = input.adapters ?? [];
318
+ const profiles = mergeNativeImportProfiles(input.languages ?? NativeImportLanguageProfiles, imports, adapters);
319
+ const languages = profiles.map((profile) => nativeImportCoverageForProfile(profile, imports, adapters));
320
+ const summary = languages.reduce((totals, entry) => {
321
+ totals.languages += 1;
322
+ if (entry.supportsLightweightScan) totals.lightweightScanners += 1;
323
+ if (entry.parserAdapters.length) totals.parserAdapterSlots += entry.parserAdapters.length;
324
+ totals.imports += entry.imports.total;
325
+ totals.symbols += entry.imports.symbols;
326
+ totals.sourceMaps += entry.imports.sourceMaps;
327
+ totals.sourceMapMappings += entry.imports.sourceMapMappings;
328
+ totals.losses += entry.imports.losses;
329
+ totals.byReadiness[entry.imports.readiness] = (totals.byReadiness[entry.imports.readiness] ?? 0) + 1;
330
+ for (const [kind, count] of Object.entries(entry.imports.lossKinds)) {
331
+ totals.lossKinds[kind] = (totals.lossKinds[kind] ?? 0) + count;
332
+ }
333
+ return totals;
334
+ }, {
335
+ languages: 0,
336
+ lightweightScanners: 0,
337
+ parserAdapterSlots: 0,
338
+ imports: 0,
339
+ symbols: 0,
340
+ sourceMaps: 0,
341
+ sourceMapMappings: 0,
342
+ losses: 0,
343
+ byReadiness: {},
344
+ lossKinds: {}
345
+ });
346
+ return {
347
+ kind: 'frontier.lang.nativeImportCoverageMatrix',
348
+ version: 1,
349
+ generatedAt: input.generatedAt ?? Date.now(),
350
+ languages,
351
+ summary,
352
+ metadata: {
353
+ compileTargets: [...FrontierCompileTargets],
354
+ note: 'Coverage is evidence and capability metadata, not a claim that every language feature is losslessly portable.'
355
+ }
356
+ };
357
+ }
358
+
359
+ export function createSemanticImportSidecar(importResult, options = {}) {
360
+ const imports = Array.isArray(importResult?.imports) ? importResult.imports : [importResult].filter(Boolean);
361
+ const importEntries = imports.map((imported, index) => semanticImportSidecarEntry(imported, index, options));
362
+ const symbols = importEntries.flatMap((entry) => entry.symbols);
363
+ const ownershipRegions = uniqueRecordsById(importEntries.flatMap((entry) => entry.ownershipRegions));
364
+ const sourceMaps = imports.flatMap((imported) => imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? []);
365
+ const sourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap?.mappings ?? []);
366
+ const losses = imports.flatMap((imported) => imported?.losses ?? []);
367
+ const evidence = uniqueRecordsById(imports.flatMap((imported) => imported?.evidence ?? []));
368
+ const mergeCandidates = imports.flatMap((imported) => imported?.mergeCandidates ?? []);
369
+ const lossSummary = summarizeNativeImportLosses(losses, { evidence });
370
+ const readiness = mergeCandidates.reduce(
371
+ (current, candidate) => maxSemanticMergeReadiness(current, candidate.readiness),
372
+ lossSummary.semanticMergeReadiness
373
+ );
374
+ const patchHints = ownershipRegions.map((region) => semanticPatchHintForRegion(region, readiness, options));
375
+ return {
376
+ kind: 'frontier.lang.semanticImportSidecar',
377
+ version: 1,
378
+ id: options.id ?? `semantic_import_${idFragment(importResult?.id ?? importResult?.projectRoot ?? imports[0]?.sourcePath ?? imports[0]?.language ?? 'source')}`,
379
+ generatedAt: options.generatedAt ?? Date.now(),
380
+ language: importResult?.language ?? (imports.length === 1 ? imports[0]?.language : 'mixed'),
381
+ projectRoot: importResult?.projectRoot,
382
+ imports: importEntries.map(({ ownershipRegions: _regions, symbols: _symbols, ...entry }) => entry),
383
+ symbols,
384
+ ownershipRegions,
385
+ sourceMaps: {
386
+ total: sourceMaps.length,
387
+ mappings: sourceMapMappings.length,
388
+ ids: sourceMaps.map((sourceMap) => sourceMap.id).filter(Boolean)
389
+ },
390
+ patchHints,
391
+ mergeCandidates: mergeCandidates.map((candidate) => ({
392
+ id: candidate.id,
393
+ readiness: candidate.readiness,
394
+ reasons: candidate.reasons ?? [],
395
+ risk: candidate.risk,
396
+ operationCount: candidate.operations?.length ?? candidate.patch?.operations?.length ?? 0
397
+ })),
398
+ losses: {
399
+ total: losses.length,
400
+ byKind: lossSummary.byKind,
401
+ bySeverity: lossSummary.bySeverity,
402
+ categories: lossSummary.categories,
403
+ blockingLossIds: lossSummary.blockingLossIds,
404
+ reviewLossIds: lossSummary.reviewLossIds
405
+ },
406
+ evidence: {
407
+ total: evidence.length,
408
+ failed: evidence.filter((record) => record.status === 'failed').map((record) => record.id),
409
+ ids: evidence.map((record) => record.id)
410
+ },
411
+ summary: {
412
+ imports: imports.length,
413
+ symbols: symbols.length,
414
+ ownershipRegions: ownershipRegions.length,
415
+ sourceMapMappings: sourceMapMappings.length,
416
+ readiness,
417
+ emptySemanticIndex: symbols.length === 0
418
+ },
419
+ metadata: {
420
+ note: 'Sidecar is source-addressable semantic evidence for merge admission; lightweight scanner regions remain review-required unless exact parser evidence upgrades readiness.',
421
+ ...options.metadata
422
+ }
423
+ };
424
+ }
425
+
426
+ export function createEstreeNativeImporterAdapter(options = {}) {
427
+ return createJavaScriptSyntaxImporterAdapter({
428
+ id: 'frontier.estree-native-importer',
429
+ language: 'javascript',
430
+ parser: 'estree',
431
+ supportedExtensions: ['.js', '.mjs', '.cjs', '.jsx'],
432
+ astFormat: 'estree',
433
+ ...options
434
+ });
435
+ }
436
+
437
+ export function createBabelNativeImporterAdapter(options = {}) {
438
+ return createJavaScriptSyntaxImporterAdapter({
439
+ id: 'frontier.babel-native-importer',
440
+ language: 'javascript',
441
+ parser: 'babel',
442
+ supportedExtensions: ['.js', '.mjs', '.cjs', '.jsx', '.ts', '.tsx'],
443
+ astFormat: 'babel',
444
+ defaultParserOptions: {
445
+ errorRecovery: true,
446
+ ranges: true,
447
+ sourceType: 'unambiguous',
448
+ plugins: ['typescript', 'jsx']
449
+ },
450
+ ...options
451
+ });
452
+ }
453
+
454
+ export function createTypeScriptCompilerNativeImporterAdapter(options = {}) {
455
+ return {
456
+ id: options.id ?? 'frontier.typescript-compiler-native-importer',
457
+ language: options.language ?? 'typescript',
458
+ parser: options.parser ?? 'typescript-compiler-api',
459
+ version: options.version,
460
+ capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
461
+ supportedExtensions: options.supportedExtensions ?? ['.ts', '.tsx', '.js', '.jsx'],
462
+ diagnostics: options.diagnostics,
463
+ parse(input) {
464
+ const ts = options.typescript ?? options.ts ?? input.options?.typescript ?? input.options?.ts;
465
+ const sourceFile = input.options?.sourceFile ?? input.options?.ast ?? options.sourceFile ?? createTypeScriptSourceFile(ts, input, options);
466
+ if (!sourceFile) {
467
+ return missingInjectedParserResult(input, {
468
+ parser: options.parser ?? 'typescript-compiler-api',
469
+ adapterId: options.id ?? 'frontier.typescript-compiler-native-importer',
470
+ message: 'createTypeScriptCompilerNativeImporterAdapter requires an injected TypeScript module, createSourceFile function, sourceFile, or adapterOptions.sourceFile.'
471
+ });
472
+ }
473
+ return createNativeImportFromTypeScriptAst(sourceFile, input, {
474
+ parser: options.parser ?? 'typescript-compiler-api',
475
+ astFormat: 'typescript-compiler-api',
476
+ ts,
477
+ maxNodes: options.maxNodes,
478
+ includeTokens: options.includeTokens
479
+ });
480
+ }
481
+ };
482
+ }
483
+
484
+ export function createTreeSitterNativeImporterAdapter(options = {}) {
485
+ return {
486
+ id: options.id ?? `frontier.tree-sitter-${idFragment(options.language ?? 'source')}-native-importer`,
487
+ language: options.language ?? 'source',
488
+ parser: options.parserName ?? options.parser ?? 'tree-sitter',
489
+ version: options.version,
490
+ capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
491
+ supportedExtensions: options.supportedExtensions ?? [],
492
+ diagnostics: options.diagnostics,
493
+ parse(input) {
494
+ const tree = input.options?.tree ?? options.tree ?? parseTreeSitterSource(input, options);
495
+ const root = tree?.rootNode ?? tree;
496
+ if (!root) {
497
+ return missingInjectedParserResult(input, {
498
+ parser: options.parserName ?? options.parser ?? 'tree-sitter',
499
+ adapterId: options.id ?? `frontier.tree-sitter-${idFragment(options.language ?? input.language)}-native-importer`,
500
+ message: 'createTreeSitterNativeImporterAdapter requires an injected tree-sitter parser/tree or adapterOptions.tree.'
501
+ });
502
+ }
503
+ return createNativeImportFromTreeSitter(root, input, {
504
+ parser: options.parserName ?? options.parser ?? 'tree-sitter',
505
+ astFormat: 'tree-sitter',
506
+ maxNodes: options.maxNodes
507
+ });
508
+ }
509
+ };
510
+ }
511
+
512
+ export async function importNativeProject(input = {}) {
513
+ const sources = input.sources ?? [];
514
+ const adapters = input.adapters ?? [];
515
+ const imports = [];
516
+ for (const [index, source] of sources.entries()) {
517
+ const adapter = source.adapter && typeof source.adapter === 'object'
518
+ ? source.adapter
519
+ : resolveNativeProjectAdapter(source, adapters, input);
520
+ if (adapter) {
521
+ imports.push(await runNativeImporterAdapter(adapter, {
522
+ ...source,
523
+ adapterOptions: source.adapterOptions ?? input.adapterOptions,
524
+ adapterMetadata: {
525
+ projectImportId: input.id,
526
+ sourceIndex: index,
527
+ ...input.adapterMetadata,
528
+ ...source.adapterMetadata
529
+ }
530
+ }));
531
+ } else {
532
+ imports.push(importNativeSource(source));
533
+ }
534
+ }
535
+ return createNativeProjectImportResult(input, imports);
536
+ }
537
+
139
538
  export async function runNativeImporterAdapter(adapter, input = {}) {
140
539
  const summary = normalizeNativeImporterAdapter(adapter);
141
540
  const language = input.language ?? summary.language;
@@ -255,11 +654,91 @@ export async function runNativeImporterAdapter(adapter, input = {}) {
255
654
  };
256
655
  }
257
656
 
657
+ export function projectNativeImportToSource(importResult, options = {}) {
658
+ if (!importResult || typeof importResult !== 'object') {
659
+ throw new Error('projectNativeImportToSource requires a native import result');
660
+ }
661
+ const context = nativeImportProjectionContext(importResult, options);
662
+ const candidateSource = nativeProjectionSourceCandidate(context, options);
663
+ const declarations = nativeProjectionDeclarations(importResult, context);
664
+ const preserveSource = options.preferPreservedSource !== false && candidateSource?.exact === true;
665
+ const mode = preserveSource ? 'preserved-source' : 'native-source-stubs';
666
+ const sourceText = preserveSource
667
+ ? candidateSource.sourceText
668
+ : renderNativeProjectionStubs(context, declarations, options);
669
+ const losses = preserveSource ? [] : nativeProjectionStubLosses(context, candidateSource, declarations, options);
670
+ const evidence = [{
671
+ id: options.evidenceId ?? `evidence_${context.idPart}_native_source_projection`,
672
+ kind: 'projection',
673
+ status: losses.some((loss) => loss.severity === 'error') ? 'failed' : 'passed',
674
+ path: context.sourcePath,
675
+ summary: preserveSource
676
+ ? `Preserved exact ${context.language} source for native projection.`
677
+ : `Projected ${context.language} native import to ${declarations.length} declaration stub(s).`,
678
+ metadata: {
679
+ mode,
680
+ language: context.language,
681
+ sourcePath: context.sourcePath,
682
+ expectedSourceHash: context.sourceHash,
683
+ providedSourceHash: candidateSource?.sourceHash,
684
+ sourceHashVerified: candidateSource?.hashVerified ?? false,
685
+ declarationCount: declarations.length
686
+ }
687
+ }];
688
+ const lossSummary = summarizeNativeImportLosses(losses, {
689
+ evidence,
690
+ parser: context.parser,
691
+ scanKind: 'native-source-projection',
692
+ semanticStatus: context.semanticStatus
693
+ });
694
+ const readiness = classifyNativeImportReadiness(losses, {
695
+ evidence,
696
+ parser: context.parser,
697
+ scanKind: 'native-source-projection',
698
+ semanticStatus: context.semanticStatus
699
+ });
700
+ const nativeImportLossSummary = importResult.metadata?.nativeImportLossSummary ?? summarizeNativeImportLosses(importResult.losses ?? context.nativeAst?.losses ?? [], {
701
+ evidence: importResult.evidence,
702
+ parser: context.parser,
703
+ semanticStatus: context.semanticStatus
704
+ });
705
+ return {
706
+ kind: 'frontier.lang.nativeSourceProjection',
707
+ version: 1,
708
+ id: options.id ?? `native_source_projection_${context.idPart}`,
709
+ language: context.language,
710
+ sourcePath: context.sourcePath,
711
+ sourceHash: context.sourceHash,
712
+ mode,
713
+ sourceText,
714
+ outputHash: hashSemanticValue(sourceText),
715
+ declarations,
716
+ losses,
717
+ lossSummary,
718
+ readiness,
719
+ evidence,
720
+ metadata: {
721
+ nativeImportId: importResult.id,
722
+ nativeSourceId: context.nativeSource?.id,
723
+ nativeAstId: context.nativeAst?.id,
724
+ semanticIndexId: context.semanticIndex?.id,
725
+ universalAstId: importResult.universalAst?.id,
726
+ exactSourceAvailable: candidateSource?.exact === true,
727
+ sourceTextAvailable: typeof candidateSource?.sourceText === 'string',
728
+ sourceHashVerified: candidateSource?.hashVerified ?? false,
729
+ nativeImportLossSummary,
730
+ ...options.metadata
731
+ }
732
+ };
733
+ }
734
+
258
735
  export function importNativeSource(input) {
259
736
  const language = input.language ?? input.nativeAst?.language;
260
737
  if (!language) throw new Error('importNativeSource requires a language or nativeAst.language');
261
738
  const sourcePath = input.sourcePath ?? input.nativeAst?.sourcePath;
262
739
  const sourceHash = input.sourceHash ?? input.nativeAst?.sourceHash ?? (input.sourceText ? hashSemanticValue(input.sourceText) : hashSemanticValue(input.nativeAst?.nodes ?? input.nativeAst ?? {}));
740
+ const targetPath = input.targetPath ?? input.target?.emitPath;
741
+ const targetHash = input.targetHash;
263
742
  const importIdPart = idFragment(input.id ?? input.nativeSourceId ?? sourcePath ?? language);
264
743
  const lightweight = !input.nativeAst && !input.nodes && input.sourceText
265
744
  ? createLightweightNativeImport({
@@ -295,7 +774,9 @@ export function importNativeSource(input) {
295
774
  }
296
775
  });
297
776
  const frontierNodeIds = input.frontierNodeIds ?? input.semanticNodes?.map((node) => node.id) ?? [];
298
- const losses = input.losses ?? nativeAst.losses ?? lightweight?.losses ?? [];
777
+ const semanticNodes = input.semanticNodes ?? [];
778
+ const semanticStatus = input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only');
779
+ const losses = normalizeNativeLossRecords(input.losses ?? nativeAst.losses ?? lightweight?.losses ?? []);
299
780
  const nativeSource = nativeSourceNode({
300
781
  id: input.nativeSourceId ?? `native_source_${importIdPart}`,
301
782
  name: input.name ?? sourcePath?.split(/[\\/]/).filter(Boolean).at(-1) ?? `${language}NativeSource`,
@@ -310,12 +791,11 @@ export function importNativeSource(input) {
310
791
  losses,
311
792
  target: input.target,
312
793
  metadata: {
313
- semanticStatus: input.semanticStatus ?? (input.semanticNodes?.length ? 'mapped' : 'native-only'),
794
+ semanticStatus,
314
795
  mappings: input.mappings ?? [],
315
796
  ...input.nativeSourceMetadata
316
797
  }
317
798
  });
318
- const semanticNodes = input.semanticNodes ?? [];
319
799
  const document = createDocument({
320
800
  id: input.documentId ?? `document_${importIdPart}`,
321
801
  name: input.documentName ?? nativeSource.name,
@@ -323,11 +803,11 @@ export function importNativeSource(input) {
323
803
  rootIds: input.rootIds,
324
804
  metadata: {
325
805
  sourceLanguage: language,
326
- semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
806
+ semanticStatus,
327
807
  ...input.documentMetadata
328
808
  }
329
809
  });
330
- const evidence = input.evidence ?? [{
810
+ const baseEvidence = input.evidence ?? [{
331
811
  id: input.evidenceId ?? `evidence_${importIdPart}_import`,
332
812
  kind: 'import',
333
813
  status: losses.some((loss) => loss.severity === 'error') ? 'failed' : 'passed',
@@ -336,9 +816,17 @@ export function importNativeSource(input) {
336
816
  metadata: {
337
817
  parser: nativeAst.parser,
338
818
  sourcePath,
339
- semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only')
819
+ semanticStatus
340
820
  }
341
821
  }];
822
+ const lossSummary = summarizeNativeImportLosses(losses, {
823
+ exactAst: Boolean(input.nativeAst || input.nodes),
824
+ evidence: baseEvidence,
825
+ parser: nativeAst.parser,
826
+ scanKind: lightweight?.metadata?.scanKind,
827
+ semanticStatus
828
+ });
829
+ const evidence = attachNativeImportLossSummary(baseEvidence, lossSummary);
342
830
  const semanticIndex = input.semanticIndex ?? lightweight?.semanticIndex;
343
831
  const sourceMapMappings = normalizeSourceMapMappings(
344
832
  input.mappings ?? lightweight?.mappings ?? inferSourceMapMappings({
@@ -353,16 +841,20 @@ export function importNativeSource(input) {
353
841
  nativeSource,
354
842
  evidence,
355
843
  losses,
356
- target: input.target
844
+ target: input.target,
845
+ targetPath,
846
+ targetHash
357
847
  }
358
848
  );
849
+ const inferredTargetPath = targetPath ?? commonGeneratedTargetPath(sourceMapMappings);
359
850
  const inferredSourceMaps = sourceMapMappings.length
360
851
  ? [createSourceMapRecord({
361
852
  id: input.sourceMapId ?? `source_map_${importIdPart}`,
362
853
  sourcePath,
363
854
  sourceHash,
364
855
  target: input.target,
365
- targetPath: input.target?.emitPath,
856
+ targetPath: inferredTargetPath,
857
+ targetHash,
366
858
  semanticIndexId: semanticIndex?.id,
367
859
  nativeAstId: nativeAst.id,
368
860
  nativeSourceId: nativeSource.id,
@@ -371,11 +863,26 @@ export function importNativeSource(input) {
371
863
  metadata: {
372
864
  sourceLanguage: language,
373
865
  parser: nativeAst.parser,
374
- semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only')
866
+ semanticStatus
375
867
  }
376
868
  })]
377
869
  : [];
378
- const sourceMaps = input.sourceMaps ?? inferredSourceMaps;
870
+ const sourceMaps = normalizeSourceMaps(input.sourceMaps ?? inferredSourceMaps, {
871
+ document,
872
+ nativeSources: [nativeSource],
873
+ nativeAst,
874
+ nativeSource,
875
+ semanticIndex,
876
+ evidence,
877
+ losses,
878
+ target: input.target,
879
+ targetPath: inferredTargetPath,
880
+ targetHash,
881
+ sourcePath,
882
+ sourceHash,
883
+ defaultSourceMapId: `source_map_${importIdPart}`
884
+ });
885
+ const resultSourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap.mappings ?? []);
379
886
  const universalAst = createUniversalAstEnvelope({
380
887
  id: input.universalAstId ?? `universal_ast_${importIdPart}`,
381
888
  document,
@@ -387,7 +894,8 @@ export function importNativeSource(input) {
387
894
  metadata: {
388
895
  sourceLanguage: language,
389
896
  sourcePath,
390
- semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
897
+ semanticStatus,
898
+ nativeImportLossSummary: lossSummary,
391
899
  ...input.universalAstMetadata
392
900
  }
393
901
  });
@@ -406,32 +914,35 @@ export function importNativeSource(input) {
406
914
  sourcePath,
407
915
  semanticIndexId: semanticIndex?.id,
408
916
  universalAstId: universalAst.id,
409
- sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id)
917
+ sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
918
+ nativeImportLossSummary: lossSummary
919
+ }
920
+ });
921
+ const importResult = createImportResult({
922
+ id: input.id ?? `import_${importIdPart}`,
923
+ language,
924
+ sourcePath,
925
+ document,
926
+ patch,
927
+ nativeAst,
928
+ semanticIndex,
929
+ universalAst,
930
+ sourceMaps,
931
+ losses,
932
+ evidence,
933
+ metadata: {
934
+ nativeSourceId: nativeSource.id,
935
+ semanticIndexId: semanticIndex?.id,
936
+ universalAstId: universalAst.id,
937
+ sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
938
+ semanticStatus,
939
+ mappings: resultSourceMapMappings,
940
+ nativeImportLossSummary: lossSummary,
941
+ ...input.metadata
410
942
  }
411
943
  });
412
944
  return {
413
- ...createImportResult({
414
- id: input.id ?? `import_${importIdPart}`,
415
- language,
416
- sourcePath,
417
- document,
418
- patch,
419
- nativeAst,
420
- semanticIndex,
421
- universalAst,
422
- sourceMaps,
423
- losses,
424
- evidence,
425
- metadata: {
426
- nativeSourceId: nativeSource.id,
427
- semanticIndexId: semanticIndex?.id,
428
- universalAstId: universalAst.id,
429
- sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
430
- semanticStatus: input.semanticStatus ?? (semanticNodes.length ? 'mapped' : 'native-only'),
431
- mappings: sourceMapMappings,
432
- ...input.metadata
433
- }
434
- }),
945
+ ...withNativeImportReadiness(importResult, lossSummary),
435
946
  nativeSource
436
947
  };
437
948
  }
@@ -459,6 +970,7 @@ function createLightweightNativeImport(input) {
459
970
  const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_lightweight_scan`;
460
971
 
461
972
  for (const declaration of declarations) {
973
+ const ownershipRegion = semanticOwnershipRegionForDeclaration(input, declaration, documentId);
462
974
  nodes[rootId].children.push(declaration.nodeId);
463
975
  nodes[declaration.nodeId] = {
464
976
  id: declaration.nodeId,
@@ -467,7 +979,11 @@ function createLightweightNativeImport(input) {
467
979
  span: declaration.span,
468
980
  value: declaration.name ?? declaration.importPath ?? null,
469
981
  fields: declaration.fields,
470
- metadata: declaration.metadata
982
+ metadata: {
983
+ ...declaration.metadata,
984
+ ownershipRegionId: ownershipRegion.id,
985
+ ownershipRegionKey: ownershipRegion.key
986
+ }
471
987
  };
472
988
  if (declaration.symbolId) {
473
989
  const occurrenceId = `occ_${idFragment(declaration.nodeId)}_def`;
@@ -479,7 +995,11 @@ function createLightweightNativeImport(input) {
479
995
  language: input.language,
480
996
  nativeAstNodeId: declaration.nodeId,
481
997
  signatureHash: hashSemanticValue([input.language, declaration.kind, declaration.name, declaration.fields ?? {}]),
482
- definitionSpan: declaration.span
998
+ definitionSpan: declaration.span,
999
+ metadata: {
1000
+ ownershipRegionId: ownershipRegion.id,
1001
+ ownershipRegionKey: ownershipRegion.key
1002
+ }
483
1003
  });
484
1004
  occurrences.push({
485
1005
  id: occurrenceId,
@@ -500,6 +1020,11 @@ function createLightweightNativeImport(input) {
500
1020
  predicate: 'nativeKind',
501
1021
  subjectId: declaration.symbolId,
502
1022
  value: declaration.languageKind
1023
+ }, {
1024
+ id: `fact_${idFragment(declaration.nodeId)}_ownership_region`,
1025
+ predicate: 'semanticOwnershipRegion',
1026
+ subjectId: declaration.symbolId,
1027
+ value: ownershipRegion
503
1028
  });
504
1029
  mappings.push({
505
1030
  id: `map_${idFragment(declaration.nodeId)}`,
@@ -509,6 +1034,7 @@ function createLightweightNativeImport(input) {
509
1034
  sourceSpan: declaration.span,
510
1035
  evidenceIds: [evidenceId],
511
1036
  lossIds: declaration.loss ? [declaration.loss.id] : [],
1037
+ ownershipRegionId: ownershipRegion.id,
512
1038
  precision: 'declaration'
513
1039
  });
514
1040
  }
@@ -554,6 +1080,314 @@ function createLightweightNativeImport(input) {
554
1080
  };
555
1081
  }
556
1082
 
1083
+ function nativeImportProjectionContext(importResult, options) {
1084
+ const nativeSource = options.nativeSource
1085
+ ?? importResult.nativeSource
1086
+ ?? importResult.nativeSources?.[0]
1087
+ ?? importResult.universalAst?.nativeSources?.[0];
1088
+ const nativeAst = options.nativeAst
1089
+ ?? importResult.nativeAst
1090
+ ?? nativeSource?.ast
1091
+ ?? importResult.universalAst?.nativeSources?.[0]?.ast;
1092
+ const semanticIndex = options.semanticIndex
1093
+ ?? importResult.semanticIndex
1094
+ ?? importResult.universalAst?.semanticIndex;
1095
+ const language = options.language
1096
+ ?? importResult.language
1097
+ ?? nativeSource?.language
1098
+ ?? nativeAst?.language
1099
+ ?? importResult.universalAst?.metadata?.sourceLanguage
1100
+ ?? 'source';
1101
+ const sourcePath = options.sourcePath
1102
+ ?? importResult.sourcePath
1103
+ ?? nativeSource?.sourcePath
1104
+ ?? nativeAst?.sourcePath
1105
+ ?? importResult.universalAst?.metadata?.sourcePath;
1106
+ const sourceHash = options.expectedSourceHash
1107
+ ?? importResult.sourceHash
1108
+ ?? nativeSource?.sourceHash
1109
+ ?? nativeAst?.sourceHash;
1110
+ return {
1111
+ nativeSource,
1112
+ nativeAst,
1113
+ semanticIndex,
1114
+ language,
1115
+ sourcePath,
1116
+ sourceHash,
1117
+ parser: options.parser ?? nativeAst?.parser ?? nativeSource?.parser,
1118
+ semanticStatus: options.semanticStatus ?? importResult.metadata?.semanticStatus ?? nativeSource?.metadata?.semanticStatus,
1119
+ idPart: idFragment(options.id ?? importResult.id ?? nativeSource?.id ?? sourcePath ?? language)
1120
+ };
1121
+ }
1122
+
1123
+ function nativeProjectionSourceCandidate(context, options) {
1124
+ const sourceText = options.sourceText ?? options.preservedSourceText ?? options.exactSourceText;
1125
+ if (typeof sourceText !== 'string') return undefined;
1126
+ const sourceHash = options.sourceHash ?? hashSemanticValue(sourceText);
1127
+ const hashVerified = Boolean(context.sourceHash);
1128
+ const exact = !context.sourceHash || sourceHash === context.sourceHash || options.verifySourceHash === false;
1129
+ return {
1130
+ sourceText,
1131
+ sourceHash,
1132
+ hashVerified,
1133
+ exact,
1134
+ mismatch: hashVerified && sourceHash !== context.sourceHash && options.verifySourceHash !== false
1135
+ };
1136
+ }
1137
+
1138
+ function nativeProjectionDeclarations(importResult, context) {
1139
+ const semanticIndex = context.semanticIndex;
1140
+ const occurrencesBySymbol = new Map();
1141
+ for (const occurrence of semanticIndex?.occurrences ?? []) {
1142
+ const list = occurrencesBySymbol.get(occurrence.symbolId) ?? [];
1143
+ list.push(occurrence);
1144
+ occurrencesBySymbol.set(occurrence.symbolId, list);
1145
+ }
1146
+ const declarations = (semanticIndex?.symbols ?? [])
1147
+ .filter((symbol) => !nativeProjectionImportOnlySymbol(symbol, occurrencesBySymbol.get(symbol.id)))
1148
+ .map((symbol) => {
1149
+ const occurrence = occurrencesBySymbol.get(symbol.id)?.find((item) => item.role !== 'import');
1150
+ const mapping = (importResult.sourceMaps ?? importResult.universalAst?.sourceMaps ?? [])
1151
+ .flatMap((sourceMap) => sourceMap.mappings ?? [])
1152
+ .find((item) => item.semanticSymbolId === symbol.id);
1153
+ return {
1154
+ name: symbol.name,
1155
+ kind: nativeProjectionDeclarationKind(symbol.kind),
1156
+ symbolId: symbol.id,
1157
+ nativeAstNodeId: symbol.nativeAstNodeId ?? occurrence?.nativeAstNodeId,
1158
+ sourceSpan: symbol.definitionSpan ?? occurrence?.span ?? mapping?.sourceSpan,
1159
+ ownershipRegionId: mapping?.ownershipRegionId ?? symbol.metadata?.ownershipRegionId,
1160
+ metadata: {
1161
+ semanticKind: symbol.kind,
1162
+ language: symbol.language,
1163
+ signatureHash: symbol.signatureHash
1164
+ }
1165
+ };
1166
+ })
1167
+ .filter((declaration) => declaration.name);
1168
+ if (declarations.length) return uniqueNativeProjectionDeclarations(declarations);
1169
+ return uniqueNativeProjectionDeclarations(Object.values(context.nativeAst?.nodes ?? {})
1170
+ .map((node) => {
1171
+ const name = typeof node.value === 'string' && node.value.trim() ? node.value.trim() : node.fields?.name;
1172
+ const kind = nativeProjectionKindForNode(node);
1173
+ if (!name || !kind) return undefined;
1174
+ return {
1175
+ name,
1176
+ kind,
1177
+ nativeAstNodeId: node.id,
1178
+ sourceSpan: node.span,
1179
+ ownershipRegionId: node.metadata?.ownershipRegionId,
1180
+ metadata: { nativeKind: node.kind, language: context.language }
1181
+ };
1182
+ })
1183
+ .filter(Boolean));
1184
+ }
1185
+
1186
+ function nativeProjectionImportOnlySymbol(symbol, occurrences = []) {
1187
+ if (String(symbol.id ?? '').includes(':import:')) return true;
1188
+ if (occurrences.length && occurrences.every((occurrence) => occurrence.role === 'import')) return true;
1189
+ return symbol.kind === 'module' && occurrences.some((occurrence) => occurrence.role === 'import');
1190
+ }
1191
+
1192
+ function nativeProjectionDeclarationKind(kind) {
1193
+ const normalized = String(kind ?? 'value').toLowerCase();
1194
+ if (normalized === 'function' || normalized === 'method' || normalized === 'procedure') return 'function';
1195
+ if (normalized === 'class') return 'class';
1196
+ if (normalized === 'interface' || normalized === 'protocol') return 'interface';
1197
+ if (normalized === 'trait') return 'trait';
1198
+ if (normalized === 'type' || normalized === 'struct' || normalized === 'enum' || normalized === 'record') return 'type';
1199
+ if (normalized === 'constant' || normalized === 'const') return 'constant';
1200
+ if (normalized === 'variable' || normalized === 'property' || normalized === 'field') return 'variable';
1201
+ if (normalized === 'module' || normalized === 'namespace' || normalized === 'package') return 'module';
1202
+ return normalized;
1203
+ }
1204
+
1205
+ function nativeProjectionKindForNode(node) {
1206
+ const kind = String(node?.kind ?? node?.languageKind ?? '').toLowerCase();
1207
+ if (/function|method|procedure|funcdecl|itemfn|fndeclaration|\bdef\b/.test(kind)) return 'function';
1208
+ if (/class/.test(kind)) return 'class';
1209
+ if (/interface|protocol/.test(kind)) return 'interface';
1210
+ if (/trait/.test(kind)) return 'trait';
1211
+ if (/struct|enum|record|typedef|typealias|type/.test(kind)) return 'type';
1212
+ if (/const|constant|macro|define/.test(kind)) return 'constant';
1213
+ if (/var|property|field/.test(kind)) return 'variable';
1214
+ if (/module|namespace|package/.test(kind)) return 'module';
1215
+ return undefined;
1216
+ }
1217
+
1218
+ function uniqueNativeProjectionDeclarations(declarations) {
1219
+ const seen = new Set();
1220
+ return declarations.filter((declaration) => {
1221
+ const key = `${declaration.kind}:${declaration.name}:${declaration.symbolId ?? declaration.nativeAstNodeId ?? ''}`;
1222
+ if (seen.has(key)) return false;
1223
+ seen.add(key);
1224
+ return true;
1225
+ });
1226
+ }
1227
+
1228
+ function nativeProjectionStubLosses(context, candidateSource, declarations, options) {
1229
+ const reason = candidateSource?.mismatch
1230
+ ? 'source-hash-mismatch'
1231
+ : options.preferPreservedSource === false && candidateSource?.sourceText
1232
+ ? 'preserved-source-disabled'
1233
+ : 'exact-source-unavailable';
1234
+ const message = candidateSource?.mismatch
1235
+ ? 'Provided native source text hash did not match the import result source hash; emitted declaration stubs instead of preserving stale source.'
1236
+ : options.preferPreservedSource === false && candidateSource?.sourceText
1237
+ ? 'Native source projection was asked to emit declaration stubs instead of preserving available source text.'
1238
+ : 'Exact native source text was not provided; emitted declaration stubs reconstructed from import metadata.';
1239
+ const losses = [nativeProjectionLoss(context, {
1240
+ id: `loss_${context.idPart}_native_source_stub`,
1241
+ kind: 'sourcePreservation',
1242
+ severity: 'warning',
1243
+ message,
1244
+ metadata: {
1245
+ reason,
1246
+ projectionMode: 'native-source-stubs',
1247
+ expectedSourceHash: context.sourceHash,
1248
+ providedSourceHash: candidateSource?.sourceHash
1249
+ }
1250
+ })];
1251
+ if (!declarations.length) {
1252
+ losses.push(nativeProjectionLoss(context, {
1253
+ id: `loss_${context.idPart}_native_source_stub_empty`,
1254
+ kind: 'declarationOnlyCoverage',
1255
+ severity: 'warning',
1256
+ message: 'Native import result did not expose semantic declarations for source stub generation.',
1257
+ metadata: { reason: 'no-stub-declarations', projectionMode: 'native-source-stubs' }
1258
+ }));
1259
+ }
1260
+ return losses;
1261
+ }
1262
+
1263
+ function nativeProjectionLoss(context, input) {
1264
+ const rootSpan = context.nativeAst?.nodes?.[context.nativeAst.rootId]?.span;
1265
+ return {
1266
+ id: input.id,
1267
+ severity: input.severity,
1268
+ phase: 'emit',
1269
+ sourceFormat: context.language,
1270
+ kind: input.kind,
1271
+ message: input.message,
1272
+ span: rootSpan ?? {
1273
+ sourceId: context.sourceHash,
1274
+ path: context.sourcePath,
1275
+ startLine: 1,
1276
+ startColumn: 1
1277
+ },
1278
+ metadata: {
1279
+ nativeSourceId: context.nativeSource?.id,
1280
+ nativeAstId: context.nativeAst?.id,
1281
+ parser: context.parser,
1282
+ ...input.metadata
1283
+ }
1284
+ };
1285
+ }
1286
+
1287
+ function renderNativeProjectionStubs(context, declarations, options) {
1288
+ const language = String(context.language ?? 'source').toLowerCase();
1289
+ const header = nativeProjectionStubHeader(language, context, options);
1290
+ let body;
1291
+ if (language === 'typescript' || language === 'ts') body = renderTypeScriptProjectionStubs(declarations);
1292
+ else if (language === 'javascript' || language === 'js') body = renderJavaScriptProjectionStubs(declarations);
1293
+ else if (language === 'python' || language === 'py') body = renderPythonProjectionStubs(declarations);
1294
+ else if (language === 'rust' || language === 'rs') body = renderRustProjectionStubs(declarations);
1295
+ else if (language === 'c' || language === 'cpp' || language === 'c++' || language === 'h') body = renderCProjectionStubs(declarations);
1296
+ else body = renderGenericProjectionStubs(declarations, language);
1297
+ return ensureTrailingNewline([header, body].filter(Boolean).join('\n'));
1298
+ }
1299
+
1300
+ function nativeProjectionStubHeader(language, context, options) {
1301
+ if (options.stubBanner === false) return '';
1302
+ if (typeof options.stubBanner === 'string') return ensureTrailingNewline(options.stubBanner).trimEnd();
1303
+ const comment = nativeProjectionLineComment(language);
1304
+ const suffix = context.sourcePath ? ` for ${oneLine(context.sourcePath)}` : '';
1305
+ return [
1306
+ `${comment} Frontier native source stubs${suffix}`,
1307
+ `${comment} Exact source text was unavailable; declarations are reconstructed from native import metadata.`
1308
+ ].join('\n');
1309
+ }
1310
+
1311
+ function nativeProjectionLineComment(language) {
1312
+ if (language === 'python' || language === 'py' || language === 'ruby' || language === 'rb' || language === 'shell' || language === 'sh') return '#';
1313
+ if (language === 'sql') return '--';
1314
+ return '//';
1315
+ }
1316
+
1317
+ function renderJavaScriptProjectionStubs(declarations) {
1318
+ const used = new Set();
1319
+ if (!declarations.length) return 'export {};';
1320
+ return declarations.map((declaration) => {
1321
+ const name = reserveUniqueId(safeProjectionIdentifier(declaration.name), used);
1322
+ if (declaration.kind === 'function') return `export function ${name}(...args) {\n throw new Error('Frontier native source stub: implementation unavailable.');\n}`;
1323
+ if (declaration.kind === 'class') return `export class ${name} {}`;
1324
+ return `export const ${name} = undefined;`;
1325
+ }).join('\n\n');
1326
+ }
1327
+
1328
+ function renderTypeScriptProjectionStubs(declarations) {
1329
+ const used = new Set();
1330
+ if (!declarations.length) return 'export {};';
1331
+ return declarations.map((declaration) => {
1332
+ const name = reserveUniqueId(safeProjectionIdentifier(declaration.name), used);
1333
+ if (declaration.kind === 'function') return `export function ${name}(...args: unknown[]): never {\n throw new Error('Frontier native source stub: implementation unavailable.');\n}`;
1334
+ if (declaration.kind === 'class') return `export class ${name} {}`;
1335
+ if (declaration.kind === 'interface') return `export interface ${name} {}`;
1336
+ if (declaration.kind === 'type' || declaration.kind === 'trait') return `export type ${name} = unknown;`;
1337
+ return `export const ${name}: unknown = undefined;`;
1338
+ }).join('\n\n');
1339
+ }
1340
+
1341
+ function renderPythonProjectionStubs(declarations) {
1342
+ if (!declarations.length) return 'pass';
1343
+ return declarations.map((declaration) => {
1344
+ const name = safeProjectionIdentifier(declaration.name);
1345
+ if (declaration.kind === 'function') return `def ${name}(*args, **kwargs):\n raise NotImplementedError("Frontier native source stub")`;
1346
+ if (declaration.kind === 'class') return `class ${name}:\n pass`;
1347
+ return `${name} = None`;
1348
+ }).join('\n\n');
1349
+ }
1350
+
1351
+ function renderRustProjectionStubs(declarations) {
1352
+ if (!declarations.length) return '';
1353
+ return declarations.map((declaration) => {
1354
+ const name = safeProjectionIdentifier(declaration.name);
1355
+ if (declaration.kind === 'function') return `pub fn ${name}() {\n unimplemented!(\"Frontier native source stub\");\n}`;
1356
+ if (declaration.kind === 'type' || declaration.kind === 'class') return `pub struct ${upperFirst(name)};`;
1357
+ return `pub const ${name.toUpperCase()}: () = ();`;
1358
+ }).join('\n\n');
1359
+ }
1360
+
1361
+ function renderCProjectionStubs(declarations) {
1362
+ if (!declarations.length) return '';
1363
+ return declarations.map((declaration) => {
1364
+ const name = safeProjectionIdentifier(declaration.name);
1365
+ if (declaration.kind === 'function') return `void ${name}(void);`;
1366
+ if (declaration.kind === 'type' || declaration.kind === 'class') return `typedef struct ${upperFirst(name)} ${upperFirst(name)};`;
1367
+ return `extern const int ${name};`;
1368
+ }).join('\n');
1369
+ }
1370
+
1371
+ function renderGenericProjectionStubs(declarations, language) {
1372
+ if (!declarations.length) return `${nativeProjectionLineComment(language)} no declarations available`;
1373
+ return declarations.map((declaration) => `${declaration.kind} ${declaration.name}`).join('\n');
1374
+ }
1375
+
1376
+ function safeProjectionIdentifier(name) {
1377
+ const text = String(name ?? 'value').split('.').at(-1).replace(/[^A-Za-z0-9_$]/g, '_');
1378
+ const identifier = text || 'value';
1379
+ return /^[A-Za-z_$]/.test(identifier) ? identifier : `_${identifier}`;
1380
+ }
1381
+
1382
+ function ensureTrailingNewline(value) {
1383
+ const text = String(value ?? '');
1384
+ return text.endsWith('\n') ? text : `${text}\n`;
1385
+ }
1386
+
1387
+ function oneLine(value) {
1388
+ return String(value ?? '').replace(/\s+/g, ' ').trim();
1389
+ }
1390
+
557
1391
  function scanNativeDeclarations(input) {
558
1392
  const language = String(input.language).toLowerCase();
559
1393
  if (language === 'javascript' || language === 'typescript') return scanJavaScriptLike(input);
@@ -566,28 +1400,79 @@ function scanNativeDeclarations(input) {
566
1400
  if (language === 'csharp' || language === 'c#') return scanCSharp(input);
567
1401
  if (language === 'php') return scanPhp(input);
568
1402
  if (language === 'ruby' || language === 'rb') return scanRuby(input);
1403
+ if (language === 'kotlin' || language === 'kt') return scanKotlin(input);
1404
+ if (language === 'scala' || language === 'sc') return scanScala(input);
1405
+ if (language === 'dart') return scanDart(input);
1406
+ if (language === 'lua') return scanLua(input);
1407
+ if (language === 'shell' || language === 'sh' || language === 'bash' || language === 'zsh') return scanShell(input);
1408
+ if (language === 'sql' || language === 'postgresql' || language === 'postgres' || language === 'mysql' || language === 'sqlite') return scanSql(input);
1409
+ if (language === 'zig') return scanZig(input);
1410
+ if (language === 'elixir' || language === 'ex' || language === 'exs') return scanElixir(input);
1411
+ if (language === 'erlang' || language === 'erl' || language === 'hrl') return scanErlang(input);
1412
+ if (language === 'haskell' || language === 'hs') return scanHaskell(input);
1413
+ if (language === 'r') return scanR(input);
569
1414
  return scanGenericDeclarations(input);
570
1415
  }
571
1416
 
572
1417
  function scanJavaScriptLike(input) {
573
1418
  const declarations = [];
1419
+ let currentClass;
1420
+ let classDepth = 0;
574
1421
  for (const { line, number } of sourceLines(input.sourceText)) {
575
1422
  const trimmed = line.trim();
1423
+ const declarationLine = trimmed.replace(/^(?:export\s+)?(?:declare\s+)?/, '');
576
1424
  let match;
577
- if ((match = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)/))) {
578
- declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
579
- } else if ((match = trimmed.match(/^(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/))) {
580
- declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'class', match[1], {}, trimmed.includes('{')));
581
- } else if ((match = trimmed.match(/^(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/))) {
582
- declarations.push(nativeDeclaration(input, number, 'InterfaceDeclaration', 'interface', match[1], {}, trimmed.includes('{')));
583
- } else if ((match = trimmed.match(/^(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/))) {
584
- declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
585
- } else if ((match = trimmed.match(/^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
586
- declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
587
- } else if ((match = trimmed.match(/^import\s+(?:.+?\s+from\s+)?['"]([^'"]+)['"]/))) {
1425
+ if ((match = trimmed.match(/^import\s+(?:.+?\s+from\s+)?['"]([^'"]+)['"]/))) {
588
1426
  declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
589
- } else if ((match = trimmed.match(/^export\s+\{[^}]*\}\s+from\s+['"]([^'"]+)['"]/))) {
1427
+ } else if ((match = trimmed.match(/^import\s*\(\s*['"]([^'"]+)['"]\s*\)/))) {
1428
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'DynamicImportExpression', 'module'));
1429
+ } else if ((match = trimmed.match(/^export\s+(?:\*\s+from|\{[^}]*\}\s+from)\s+['"]([^'"]+)['"]/))) {
590
1430
  declarations.push(nativeImportDeclaration(input, number, match[1], 'ExportFromDeclaration', 'module'));
1431
+ } else if ((match = declarationLine.match(/^(?:async\s+)?function\*?\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)/))) {
1432
+ declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, declarationLine.includes('{')));
1433
+ } else if ((match = trimmed.match(/^export\s+default\s+(?:async\s+)?function\*?\s*([A-Za-z_$][\w$]*)?\s*\(([^)]*)\)/))) {
1434
+ declarations.push(nativeDeclaration(input, number, 'ExportDefaultFunctionDeclaration', 'function', match[1] ?? 'default', { parameters: splitParameters(match[2]), exportDefault: true }, trimmed.includes('{')));
1435
+ } else if ((match = declarationLine.match(/^(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/))) {
1436
+ declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'class', match[1], {}, declarationLine.includes('{')));
1437
+ if (declarationLine.includes('{') && !declarationLine.includes('}')) {
1438
+ currentClass = match[1];
1439
+ classDepth = 0;
1440
+ }
1441
+ } else if ((match = declarationLine.match(/^interface\s+([A-Za-z_$][\w$]*)/))) {
1442
+ declarations.push(nativeDeclaration(input, number, 'InterfaceDeclaration', 'interface', match[1], {}, declarationLine.includes('{')));
1443
+ } else if ((match = declarationLine.match(/^(?:const\s+)?enum\s+([A-Za-z_$][\w$]*)/))) {
1444
+ declarations.push(nativeDeclaration(input, number, 'EnumDeclaration', 'type', match[1], {}, declarationLine.includes('{')));
1445
+ } else if ((match = declarationLine.match(/^(?:namespace|module)\s+([A-Za-z_$][\w$.]*)/))) {
1446
+ declarations.push(nativeDeclaration(input, number, 'ModuleDeclaration', 'module', match[1], {}, declarationLine.includes('{')));
1447
+ } else if ((match = declarationLine.match(/^type\s+([A-Za-z_$][\w$]*)\s*=/))) {
1448
+ declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
1449
+ } else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
1450
+ declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
1451
+ } else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b/))) {
1452
+ declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', 'variable', match[1], {}, false));
1453
+ } else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?function\*?\s*\(([^)]*)\)/))) {
1454
+ declarations.push(nativeDeclaration(input, number, 'CommonJsFunctionExport', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
1455
+ } else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=/))) {
1456
+ declarations.push(nativeDeclaration(input, number, 'CommonJsExport', 'variable', match[1], { export: 'commonjs' }, false));
1457
+ } 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])) {
1458
+ declarations.push(nativeDeclaration(input, number, 'MethodDefinition', 'method', `${currentClass}.${match[1]}`, {
1459
+ methodName: match[1],
1460
+ owner: currentClass,
1461
+ parameters: splitParameters(match[2])
1462
+ }, declarationLine.includes('{') || declarationLine.includes('=>')));
1463
+ } else if (currentClass && (match = declarationLine.match(/^(?:(?:public|private|protected|static|readonly|declare|accessor)\s+)*([A-Za-z_$][\w$]*)\s*(?::\s*([^=;{]+))?(?:[=;]|$)/))) {
1464
+ declarations.push(nativeDeclaration(input, number, 'PropertyDefinition', 'property', `${currentClass}.${match[1]}`, {
1465
+ propertyName: match[1],
1466
+ owner: currentClass,
1467
+ valueType: match[2]?.trim()
1468
+ }, false));
1469
+ }
1470
+ if (currentClass) {
1471
+ classDepth += braceDelta(trimmed);
1472
+ if (classDepth <= 0) {
1473
+ currentClass = undefined;
1474
+ classDepth = 0;
1475
+ }
591
1476
  }
592
1477
  }
593
1478
  return declarations;
@@ -676,17 +1561,41 @@ function scanJava(input) {
676
1561
 
677
1562
  function scanGo(input) {
678
1563
  const declarations = [];
1564
+ let inImportBlock = false;
679
1565
  for (const { line, number } of sourceLines(input.sourceText)) {
680
1566
  const trimmed = line.trim();
681
1567
  let match;
1568
+ if (inImportBlock) {
1569
+ if (trimmed === ')') {
1570
+ inImportBlock = false;
1571
+ } else if ((match = trimmed.match(/^(?:(?:[A-Za-z_]\w*|[_.])\s+)?["']([^"']+)["']/))) {
1572
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportSpec', 'package'));
1573
+ }
1574
+ continue;
1575
+ }
682
1576
  if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*)/))) {
683
1577
  declarations.push(nativeDeclaration(input, number, 'PackageClause', 'package', match[1], {}, false));
684
- } else if ((match = trimmed.match(/^import\s+(?:[A-Za-z_]\w*\s+)?["']([^"']+)["']/))) {
1578
+ } else if (/^import\s*\(/.test(trimmed)) {
1579
+ inImportBlock = true;
1580
+ } else if ((match = trimmed.match(/^import\s+(?:(?:[A-Za-z_]\w*|[_.])\s+)?["']([^"']+)["']/))) {
685
1581
  declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportSpec', 'package'));
1582
+ } else if ((match = trimmed.match(/^type\s+([A-Za-z_]\w*)\s*=\s*(.+)$/))) {
1583
+ declarations.push(nativeDeclaration(input, number, 'TypeAlias', 'type', match[1], { target: match[2].trim() }, false));
686
1584
  } else if ((match = trimmed.match(/^type\s+([A-Za-z_]\w*)\s+(struct|interface)\b/))) {
687
1585
  declarations.push(nativeDeclaration(input, number, match[2] === 'struct' ? 'TypeSpecStruct' : 'TypeSpecInterface', 'type', match[1], {}, trimmed.includes('{')));
688
- } else if ((match = trimmed.match(/^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
689
- declarations.push(nativeDeclaration(input, number, 'FuncDecl', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
1586
+ } else if ((match = trimmed.match(/^func\s+\(([^)]*)\)\s*([A-Za-z_]\w*)(?:\s*\[([^\]]+)\])?\s*\(([^)]*)\)/))) {
1587
+ const receiver = parseGoReceiver(match[1]);
1588
+ declarations.push(nativeDeclaration(input, number, 'MethodDecl', 'method', goReceiverMethodName(receiver, match[2]), {
1589
+ methodName: match[2],
1590
+ receiver,
1591
+ typeParameters: splitTypeParameters(match[3]),
1592
+ parameters: splitParameters(match[4])
1593
+ }, trimmed.includes('{')));
1594
+ } else if ((match = trimmed.match(/^func\s+([A-Za-z_]\w*)(?:\s*\[([^\]]+)\])?\s*\(([^)]*)\)/))) {
1595
+ declarations.push(nativeDeclaration(input, number, 'FuncDecl', 'function', match[1], {
1596
+ typeParameters: splitTypeParameters(match[2]),
1597
+ parameters: splitParameters(match[3])
1598
+ }, trimmed.includes('{')));
690
1599
  } else if ((match = trimmed.match(/^var\s+([A-Za-z_]\w*)\b/))) {
691
1600
  declarations.push(nativeDeclaration(input, number, 'VarDecl', 'variable', match[1], {}, false));
692
1601
  } else if ((match = trimmed.match(/^const\s+([A-Za-z_]\w*)\b/))) {
@@ -698,17 +1607,35 @@ function scanGo(input) {
698
1607
 
699
1608
  function scanSwift(input) {
700
1609
  const declarations = [];
1610
+ const protocols = new Set();
701
1611
  for (const { line, number } of sourceLines(input.sourceText)) {
702
1612
  const trimmed = line.trim();
1613
+ const declarationLine = trimmed.replace(/^(?:@[A-Za-z_][\w.]+(?:\([^)]*\))?\s+)*/, '');
703
1614
  let match;
704
- if ((match = trimmed.match(/^import\s+([A-Za-z_]\w*)/))) {
1615
+ if ((match = declarationLine.match(/^import\s+(?:(?:struct|class|enum|protocol|func|var)\s+)?([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
705
1616
  declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDecl', 'module'));
706
- } else if ((match = trimmed.match(/^(?:(?:public|private|fileprivate|internal|open|final)\s+)*(struct|class|enum|protocol|actor|extension)\s+([A-Za-z_]\w*)/))) {
707
- declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Decl`, swiftSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
708
- } else if ((match = trimmed.match(/^(?:(?:public|private|fileprivate|internal|open|static|class|mutating)\s+)*func\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
709
- declarations.push(nativeDeclaration(input, number, 'FunctionDecl', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
710
- } else if ((match = trimmed.match(/^(?:let|var)\s+([A-Za-z_]\w*)\b/))) {
711
- declarations.push(nativeDeclaration(input, number, 'ValueBindingDecl', 'variable', match[1], {}, false));
1617
+ } else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open|final|indirect)\s+)*(struct|class|enum|protocol|actor)\s+([A-Za-z_]\w*)/))) {
1618
+ if (match[1] === 'protocol') protocols.add(match[2]);
1619
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Decl`, swiftSymbolKind(match[1]), match[2], {}, declarationLine.includes('{')));
1620
+ } else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open)\s+)*extension\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)(.*)$/))) {
1621
+ const extensionFields = parseSwiftExtensionTail(match[2]);
1622
+ const isProtocolExtension = protocols.has(match[1]) || /Protocol$/.test(match[1]);
1623
+ declarations.push(nativeDeclaration(input, number, isProtocolExtension ? 'ProtocolExtensionDecl' : 'ExtensionDecl', 'implementation', `${match[1]}.${isProtocolExtension ? 'protocolExtension' : 'extension'}`, {
1624
+ extendedType: match[1],
1625
+ ...extensionFields
1626
+ }, declarationLine.includes('{')));
1627
+ } 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*\(([^)]*)\)/))) {
1628
+ declarations.push(nativeDeclaration(input, number, 'FunctionDecl', 'function', unquoteSwiftIdentifier(match[1]), {
1629
+ typeParameters: splitTypeParameters(match[2]),
1630
+ parameters: splitParameters(match[3])
1631
+ }, declarationLine.includes('{')));
1632
+ } 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*([^={]+))?/))) {
1633
+ declarations.push(nativeDeclaration(input, number, 'PropertyDecl', 'property', match[2], {
1634
+ binding: match[1],
1635
+ valueType: match[3]?.trim()
1636
+ }, declarationLine.includes('{') || declarationLine.includes('=>')));
1637
+ } else if ((match = declarationLine.match(/^(?:(?:public|private(?:\([^)]*\))?|fileprivate|internal|open)\s+)*typealias\s+([A-Za-z_]\w*)\b(?:\s*=\s*(.+))?/))) {
1638
+ declarations.push(nativeDeclaration(input, number, 'TypealiasDecl', 'type', match[1], { target: match[2]?.trim() }, false));
712
1639
  }
713
1640
  }
714
1641
  return declarations;
@@ -719,14 +1646,36 @@ function scanCSharp(input) {
719
1646
  for (const { line, number } of sourceLines(input.sourceText)) {
720
1647
  const trimmed = line.trim();
721
1648
  let match;
722
- if ((match = trimmed.match(/^using\s+(?:static\s+)?([A-Za-z_][\w.]*)\s*;/))) {
1649
+ if ((match = trimmed.match(/^using\s+([A-Za-z_]\w*)\s*=\s*(.+?)\s*;/))) {
1650
+ declarations.push(nativeDeclaration(input, number, 'UsingAliasDirective', 'type', match[1], { target: match[2].trim() }, false));
1651
+ } else if ((match = trimmed.match(/^using\s+(?:static\s+)?([A-Za-z_][\w.]*)\s*;/))) {
723
1652
  declarations.push(nativeImportDeclaration(input, number, match[1], 'UsingDirective', 'namespace'));
724
1653
  } else if ((match = trimmed.match(/^namespace\s+([A-Za-z_][\w.]*)/))) {
725
1654
  declarations.push(nativeDeclaration(input, number, 'NamespaceDeclaration', 'namespace', match[1], {}, trimmed.includes('{')));
726
- } 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*)/))) {
727
- declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, csharpSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
728
- } 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*(?:\{|;)?$/))) {
729
- declarations.push(nativeDeclaration(input, number, 'MethodDeclaration', 'method', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
1655
+ } else if ((match = trimmed.match(/^(?:(?:public|protected|private|internal|static|unsafe|new)\s+)*delegate\s+(.+?)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*;/))) {
1656
+ declarations.push(nativeDeclaration(input, number, 'DelegateDeclaration', 'type', match[2], {
1657
+ returnType: match[1].trim(),
1658
+ parameters: splitParameters(match[3])
1659
+ }, false));
1660
+ } 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*)/))) {
1661
+ declarations.push(nativeDeclaration(input, number, csharpDeclarationKind(match[1]), csharpSymbolKind(match[1]), match[2], { csharpKind: match[1].replace(/\s+/g, ' ') }, trimmed.includes('{')));
1662
+ } 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*(?:=>.*|\{|;)?$/))) {
1663
+ const parameters = splitParameters(match[2]);
1664
+ const extensionReceiver = csharpExtensionReceiver(parameters);
1665
+ declarations.push(nativeDeclaration(input, number, extensionReceiver ? 'ExtensionMethodDeclaration' : 'MethodDeclaration', 'method', match[1], {
1666
+ parameters,
1667
+ ...(extensionReceiver ? { extensionReceiver } : {})
1668
+ }, trimmed.includes('{') || trimmed.includes('=>')));
1669
+ } 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*(?:[;{=]|=>)/))) {
1670
+ declarations.push(nativeDeclaration(input, number, 'EventDeclaration', 'event', match[2], {
1671
+ eventType: match[1].trim(),
1672
+ accessors: csharpAccessors(trimmed)
1673
+ }, trimmed.includes('{')));
1674
+ } 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*(?:\{|=>)/))) {
1675
+ declarations.push(nativeDeclaration(input, number, 'PropertyDeclaration', 'property', match[2], {
1676
+ propertyType: match[1].trim(),
1677
+ accessors: csharpAccessors(trimmed)
1678
+ }, trimmed.includes('{') || trimmed.includes('=>')));
730
1679
  }
731
1680
  }
732
1681
  return declarations;
@@ -768,35 +1717,387 @@ function scanRuby(input) {
768
1717
  return declarations;
769
1718
  }
770
1719
 
771
- function scanGenericDeclarations(input) {
772
- return sourceLines(input.sourceText)
773
- .filter(({ line }) => /\b(function|class|struct|enum|trait|interface|def)\b/.test(line))
774
- .map(({ line, number }) => nativeDeclaration(input, number, 'NativeDeclaration', 'variable', idFragment(line.trim()).slice(0, 40), { source: line.trim() }, true));
1720
+ function scanKotlin(input) {
1721
+ const declarations = [];
1722
+ for (const { line, number } of sourceLines(input.sourceText)) {
1723
+ const trimmed = line.trim();
1724
+ let match;
1725
+ if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
1726
+ declarations.push(nativeDeclaration(input, number, 'PackageHeader', 'package', match[1], {}, false));
1727
+ } else if ((match = trimmed.match(/^import\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*(?:\.\*)?)(?:\s+as\s+[A-Za-z_]\w*)?$/))) {
1728
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDirective', 'package'));
1729
+ } else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual|open|final|abstract|sealed|data|value)\s+)*(?:(enum|annotation)\s+)?(class|interface|object)\s+([A-Za-z_]\w*)/))) {
1730
+ declarations.push(nativeDeclaration(input, number, kotlinDeclarationKind(match[2], match[1]), kotlinSymbolKind(match[2], match[1]), match[3], {}, trimmed.includes('{')));
1731
+ } else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual|open|final|abstract|inline|tailrec|operator|infix|external|suspend|override)\s+)*fun\s+(?:<[^>]+>\s*)?(?:[A-Za-z_][\w.<>?]*\.)?([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
1732
+ declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=')));
1733
+ } else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual)\s+)*typealias\s+([A-Za-z_]\w*)\s*=/))) {
1734
+ declarations.push(nativeDeclaration(input, number, 'TypeAliasDeclaration', 'type', match[1], {}, false));
1735
+ } else if ((match = trimmed.match(/^(?:(?:public|private|protected|internal|expect|actual|open|final|abstract|override|const|lateinit)\s+)*(?:val|var)\s+([A-Za-z_]\w*)\b/))) {
1736
+ declarations.push(nativeDeclaration(input, number, 'PropertyDeclaration', 'variable', match[1], {}, false));
1737
+ }
1738
+ }
1739
+ return declarations;
775
1740
  }
776
1741
 
777
- function upperFirst(value) {
778
- return String(value).charAt(0).toUpperCase() + String(value).slice(1);
1742
+ function scanScala(input) {
1743
+ const declarations = [];
1744
+ for (const { line, number } of sourceLines(input.sourceText)) {
1745
+ const trimmed = line.trim();
1746
+ let match;
1747
+ if ((match = trimmed.match(/^package\s+([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/))) {
1748
+ declarations.push(nativeDeclaration(input, number, 'PackageClause', 'package', match[1], {}, false));
1749
+ } else if ((match = trimmed.match(/^import\s+(.+?);?$/))) {
1750
+ declarations.push(nativeImportDeclaration(input, number, match[1].trim(), 'Import', 'package'));
1751
+ } else if ((match = trimmed.match(/^(?:(?:private|protected|final|sealed|abstract|case|implicit|lazy|override|inline|transparent|open)\s+)*(class|trait|object|enum)\s+([A-Za-z_]\w*)/))) {
1752
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Def`, scalaSymbolKind(match[1]), match[2], {}, trimmed.includes('{') || trimmed.includes(':')));
1753
+ } else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|override|inline)\s+)*def\s+([A-Za-z_]\w*)\s*(?:\[[^\]]+\])?\s*\(([^)]*)\)/))) {
1754
+ declarations.push(nativeDeclaration(input, number, 'DefDef', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=')));
1755
+ } else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|opaque)\s+)*type\s+([A-Za-z_]\w*)\b/))) {
1756
+ declarations.push(nativeDeclaration(input, number, 'TypeDef', 'type', match[1], {}, false));
1757
+ } else if ((match = trimmed.match(/^(?:(?:private|protected|final|implicit|lazy|override|inline)\s+)*(?:val|var)\s+([A-Za-z_]\w*)\b/))) {
1758
+ declarations.push(nativeDeclaration(input, number, 'ValDef', 'variable', match[1], {}, false));
1759
+ }
1760
+ }
1761
+ return declarations;
779
1762
  }
780
1763
 
781
- function javaSymbolKind(kind) {
782
- if (kind === 'interface' || kind === '@interface') return 'interface';
783
- if (kind === 'enum' || kind === 'record') return 'type';
784
- return 'class';
1764
+ function scanDart(input) {
1765
+ const declarations = [];
1766
+ for (const { line, number } of sourceLines(input.sourceText)) {
1767
+ const trimmed = line.trim();
1768
+ let match;
1769
+ if ((match = trimmed.match(/^(?:import|export)\s+['"]([^'"]+)['"]/))) {
1770
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'UriBasedDirective', 'library'));
1771
+ } else if ((match = trimmed.match(/^part\s+['"]([^'"]+)['"]/))) {
1772
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'PartDirective', 'library'));
1773
+ } else if ((match = trimmed.match(/^(?:(?:abstract|base|final|interface|sealed)\s+)*(class|mixin|enum)\s+([A-Za-z_]\w*)/))) {
1774
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, dartSymbolKind(match[1]), match[2], {}, trimmed.includes('{')));
1775
+ } else if ((match = trimmed.match(/^extension\s+([A-Za-z_]\w*)\s+on\s+.+\{/))) {
1776
+ declarations.push(nativeDeclaration(input, number, 'ExtensionDeclaration', 'implementation', match[1], {}, true));
1777
+ } else if ((match = trimmed.match(/^typedef\s+([A-Za-z_]\w*)\b/))) {
1778
+ declarations.push(nativeDeclaration(input, number, 'TypeAlias', 'type', match[1], {}, false));
1779
+ } else if ((match = trimmed.match(/^(?:(?:external|static)\s+)*(?:[A-Za-z_]\w*(?:<[^>]+>)?\??|void)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*(?:async\s*)?(?:\{|=>|;)/))) {
1780
+ declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{') || trimmed.includes('=>')));
1781
+ } else if ((match = trimmed.match(/^(?:(?:static|external|late)\s+)*(?:const|final|var)\s+(?:[A-Za-z_]\w*(?:<[^>]+>)?\??\s+)?([A-Za-z_]\w*)\b/))) {
1782
+ declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', 'variable', match[1], {}, false));
1783
+ }
1784
+ }
1785
+ return declarations;
785
1786
  }
786
1787
 
787
- function swiftSymbolKind(kind) {
788
- if (kind === 'protocol') return 'protocol';
1788
+ function scanLua(input) {
1789
+ const declarations = [];
1790
+ for (const { line, number } of sourceLines(input.sourceText)) {
1791
+ const trimmed = line.trim();
1792
+ let match;
1793
+ if ((match = trimmed.match(/^(?:local\s+[A-Za-z_]\w*\s*=\s*)?require\s*\(?\s*['"]([^'"]+)['"]\s*\)?/))) {
1794
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'RequireCall', 'module'));
1795
+ } else if ((match = trimmed.match(/^(?:local\s+)?function\s+([A-Za-z_]\w*(?:[.:][A-Za-z_]\w*)*)\s*\(([^)]*)\)/))) {
1796
+ declarations.push(nativeDeclaration(input, number, 'FunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
1797
+ } else if ((match = trimmed.match(/^(?:local\s+)?([A-Za-z_]\w*(?:[.:][A-Za-z_]\w*)*)\s*=\s*function\s*\(([^)]*)\)/))) {
1798
+ declarations.push(nativeDeclaration(input, number, 'FunctionAssignment', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
1799
+ } else if ((match = trimmed.match(/^local\s+([A-Za-z_]\w*)\s*=\s*(?:\{|\w+)/))) {
1800
+ declarations.push(nativeDeclaration(input, number, 'LocalDeclaration', 'variable', match[1], {}, false));
1801
+ }
1802
+ }
1803
+ return declarations;
1804
+ }
1805
+
1806
+ function scanShell(input) {
1807
+ const declarations = [];
1808
+ for (const { line, number } of sourceLines(input.sourceText)) {
1809
+ const trimmed = line.trim();
1810
+ let match;
1811
+ if ((match = trimmed.match(/^(?:source|\.)\s+(?:"([^"]+)"|'([^']+)'|([./A-Za-z0-9_-][\w./-]*))(?:\s|$)/))) {
1812
+ declarations.push(nativeImportDeclaration(input, number, match[1] ?? match[2] ?? match[3], 'SourceCommand', 'file'));
1813
+ } else if ((match = trimmed.match(/^function\s+([A-Za-z_][\w-]*)\s*(?:\(\s*\))?\s*(?:\{|$)/))) {
1814
+ declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], {}, true));
1815
+ } else if ((match = trimmed.match(/^([A-Za-z_][\w-]*)\s*\(\s*\)\s*(?:\{|$)/))) {
1816
+ declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], {}, true));
1817
+ } else if ((match = trimmed.match(/^(?:export\s+)?(?:readonly\s+)?([A-Za-z_]\w*)=/))) {
1818
+ declarations.push(nativeDeclaration(input, number, 'VariableAssignment', 'variable', match[1], {}, false));
1819
+ } else if ((match = trimmed.match(/^alias\s+([A-Za-z_][\w-]*)=/))) {
1820
+ declarations.push(nativeDeclaration(input, number, 'AliasDeclaration', 'function', match[1], {}, false));
1821
+ }
1822
+ }
1823
+ return declarations;
1824
+ }
1825
+
1826
+ function scanSql(input) {
1827
+ const declarations = [];
1828
+ for (const { line, number } of sourceLines(input.sourceText)) {
1829
+ const trimmed = line.trim();
1830
+ let match;
1831
+ if ((match = trimmed.match(/^CREATE\s+EXTENSION\s+(?:IF\s+NOT\s+EXISTS\s+)?((?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$-]*))/i))) {
1832
+ declarations.push(nativeImportDeclaration(input, number, normalizeSqlIdentifier(match[1]), 'CreateExtensionStatement', 'extension'));
1833
+ } else if ((match = trimmed.match(/^CREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP(?:ORARY)?\s+)?((?:UNIQUE\s+)?INDEX|MATERIALIZED\s+VIEW|TABLE|VIEW|FUNCTION|PROCEDURE|TRIGGER|SCHEMA|TYPE)\s+(?:IF\s+NOT\s+EXISTS\s+)?((?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*)(?:\s*\.\s*(?:"[^"]+"|`[^`]+`|\[[^\]]+\]|[A-Za-z_][\w$]*))?)/i))) {
1834
+ const objectKind = match[1].toUpperCase().replace(/\s+/g, ' ');
1835
+ declarations.push(nativeDeclaration(input, number, sqlLanguageKind(objectKind), sqlSymbolKind(objectKind), normalizeSqlIdentifier(match[2]), { objectKind }, trimmed.includes('(')));
1836
+ }
1837
+ }
1838
+ return declarations;
1839
+ }
1840
+
1841
+ function scanZig(input) {
1842
+ const declarations = [];
1843
+ for (const { line, number } of sourceLines(input.sourceText)) {
1844
+ const trimmed = line.trim();
1845
+ let match;
1846
+ if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?(?:const|var)\s+([A-Za-z_]\w*)\s*=\s*@import\(\s*["']([^"']+)["']\s*\)\s*;?/))) {
1847
+ declarations.push(nativeImportDeclaration(input, number, match[2], 'ImportDeclaration', 'module'));
1848
+ } else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?usingnamespace\s+@import\(\s*["']([^"']+)["']\s*\)\s*;?/))) {
1849
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'UsingNamespaceImport', 'module'));
1850
+ } else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?const\s+([A-Za-z_]\w*)\s*=\s*(?:extern\s+)?(struct|enum|union|opaque|error)\b/))) {
1851
+ declarations.push(nativeDeclaration(input, number, `Const${upperFirst(match[2])}Declaration`, 'type', match[1], { zigKind: match[2] }, trimmed.includes('{')));
1852
+ } else if ((match = trimmed.match(/^(?:(?:pub|export|extern|inline)\s+)*(?:fn)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)/))) {
1853
+ declarations.push(nativeDeclaration(input, number, 'FnDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
1854
+ } else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?const\s+([A-Za-z_]\w*)\b/))) {
1855
+ declarations.push(nativeDeclaration(input, number, 'ConstDeclaration', 'constant', match[1], {}, false));
1856
+ } else if ((match = trimmed.match(/^(?:(?:pub|export)\s+)?var\s+([A-Za-z_]\w*)\b/))) {
1857
+ declarations.push(nativeDeclaration(input, number, 'VarDeclaration', 'variable', match[1], {}, false));
1858
+ }
1859
+ if (/^\s*comptime\b|@(?:cImport|compileError|field|hasDecl|hasField|setEvalBranchQuota|This|Type|typeInfo)\b/.test(trimmed)) {
1860
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'generatedCode', zigMetaName(trimmed)));
1861
+ }
1862
+ }
1863
+ return declarations;
1864
+ }
1865
+
1866
+ function scanElixir(input) {
1867
+ const declarations = [];
1868
+ let currentModule;
1869
+ for (const { line, number } of sourceLines(input.sourceText)) {
1870
+ const trimmed = line.trim();
1871
+ let match;
1872
+ let recordedMeta = false;
1873
+ if ((match = trimmed.match(/^defmodule\s+([A-Z]\w*(?:\.[A-Z]\w*)*)\s+do\b/))) {
1874
+ currentModule = match[1];
1875
+ declarations.push(nativeDeclaration(input, number, 'ModuleDefinition', 'module', match[1], {}, true));
1876
+ } else if ((match = trimmed.match(/^(?:alias|import|require)\s+([A-Z]\w*(?:\.[A-Z]\w*)*)/))) {
1877
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDirective', 'module'));
1878
+ } else if ((match = trimmed.match(/^use\s+([A-Z]\w*(?:\.[A-Z]\w*)*)/))) {
1879
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', match[1]));
1880
+ recordedMeta = true;
1881
+ } else if ((match = trimmed.match(/^(defmacro|defmacrop|defguard|defguardp|defdelegate)\s+([A-Za-z_]\w*[!?]?)/))) {
1882
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', match[2]));
1883
+ recordedMeta = true;
1884
+ } else if ((match = trimmed.match(/^defp?\s+([A-Za-z_]\w*[!?]?)\s*(?:\(([^)]*)\)|([^,]*))?/))) {
1885
+ declarations.push(nativeDeclaration(input, number, 'FunctionDefinition', 'function', match[1], { parameters: splitParameters(match[2] ?? match[3]) }, /\bdo\b/.test(trimmed)));
1886
+ } else if (trimmed.startsWith('defstruct')) {
1887
+ declarations.push(nativeDeclaration(input, number, 'StructDefinition', 'type', currentModule ?? `struct_${number}`, {}, true));
1888
+ } else if ((match = trimmed.match(/^@(type|typep|opaque|callback)\s+([A-Za-z_]\w*[!?]?)/))) {
1889
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Attribute`, match[1] === 'callback' ? 'function' : 'type', match[2], {}, false));
1890
+ }
1891
+ if (!recordedMeta && /(?:\bquote\s+do\b|\bunquote(?:_splicing)?\b|@(?:before_compile|after_compile|on_definition|derive)\b)/.test(trimmed)) {
1892
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', elixirMetaName(trimmed)));
1893
+ }
1894
+ }
1895
+ return declarations;
1896
+ }
1897
+
1898
+ function scanErlang(input) {
1899
+ const declarations = [];
1900
+ const seenFunctions = new Set();
1901
+ for (const { line, number } of sourceLines(input.sourceText)) {
1902
+ const trimmed = line.trim();
1903
+ let match;
1904
+ let recordedMacro = false;
1905
+ if ((match = trimmed.match(/^-module\(([a-z][A-Za-z0-9_@]*)\)\./))) {
1906
+ declarations.push(nativeDeclaration(input, number, 'ModuleAttribute', 'module', match[1], {}, false));
1907
+ } else if ((match = trimmed.match(/^-include(?:_lib)?\(["']([^"']+)["']\)\./))) {
1908
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'IncludeAttribute', 'module'));
1909
+ } else if ((match = trimmed.match(/^-import\(([a-z][A-Za-z0-9_@]*)\s*,/))) {
1910
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportAttribute', 'module'));
1911
+ } else if ((match = trimmed.match(/^-behaviou?r\(([a-z][A-Za-z0-9_@]*)\)\./))) {
1912
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'BehaviourAttribute', 'module'));
1913
+ } else if ((match = trimmed.match(/^-record\(([a-z][A-Za-z0-9_@]*)\s*,/))) {
1914
+ declarations.push(nativeDeclaration(input, number, 'RecordAttribute', 'type', match[1], {}, false));
1915
+ } else if ((match = trimmed.match(/^-(type|opaque)\s+([a-z][A-Za-z0-9_@]*)\s*\(/))) {
1916
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Attribute`, 'type', match[2], {}, false));
1917
+ } else if ((match = trimmed.match(/^-callback\s+([a-z][A-Za-z0-9_@]*)\s*\(/))) {
1918
+ declarations.push(nativeDeclaration(input, number, 'CallbackAttribute', 'function', match[1], {}, false));
1919
+ } else if ((match = trimmed.match(/^-define\(([^,\s)]+)/))) {
1920
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'preprocessor', match[1]));
1921
+ recordedMacro = true;
1922
+ } else if (/^-compile\([^)]*parse_transform/.test(trimmed)) {
1923
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'generatedCode', 'parse_transform'));
1924
+ recordedMacro = true;
1925
+ } else if ((match = trimmed.match(/^([a-z][A-Za-z0-9_@]*|'[^']+')\s*\(([^)]*)\)\s*(?:when\s+.*?)?->/))) {
1926
+ const name = erlangAtomName(match[1]);
1927
+ if (!seenFunctions.has(name)) {
1928
+ seenFunctions.add(name);
1929
+ declarations.push(nativeDeclaration(input, number, 'FunctionClause', 'function', name, { parameters: splitParameters(match[2]) }, true));
1930
+ }
1931
+ }
1932
+ if (!recordedMacro && /(^|[^A-Za-z0-9_])\?[A-Za-z_]\w*/.test(trimmed)) {
1933
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', erlangMacroName(trimmed)));
1934
+ }
1935
+ }
1936
+ return declarations;
1937
+ }
1938
+
1939
+ function scanHaskell(input) {
1940
+ const declarations = [];
1941
+ const seenFunctions = new Set();
1942
+ for (const { line, number } of sourceLines(input.sourceText)) {
1943
+ const trimmed = line.trim();
1944
+ let match;
1945
+ if (/^#\s*(?:if|ifdef|ifndef|else|elif|endif|define|include)\b/.test(trimmed)) {
1946
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'preprocessor', haskellMetaName(trimmed)));
1947
+ } else if ((match = trimmed.match(/^\{-#\s+LANGUAGE\s+(.+?)#-\}/))) {
1948
+ const extensions = match[1].split(',').map((part) => part.trim());
1949
+ if (extensions.some((extension) => /^(TemplateHaskell|QuasiQuotes|CPP)$/.test(extension))) {
1950
+ declarations.push(nativeMacroLoss(input, number, trimmed, extensions.includes('CPP') ? 'preprocessor' : 'macroExpansion', extensions.join('_')));
1951
+ }
1952
+ } else if (/^\$\(|\[[a-zA-Z]*\||\b\$\([^)]+\)/.test(trimmed)) {
1953
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'macroExpansion', haskellMetaName(trimmed)));
1954
+ } else if ((match = trimmed.match(/^module\s+([A-Z][A-Za-z0-9_.']*)\b/))) {
1955
+ declarations.push(nativeDeclaration(input, number, 'ModuleDeclaration', 'module', match[1], {}, false));
1956
+ } else if ((match = trimmed.match(/^import\s+(?:safe\s+)?(?:qualified\s+)?([A-Z][A-Za-z0-9_.']*)/))) {
1957
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
1958
+ } else if ((match = trimmed.match(/^foreign\s+import\s+([A-Za-z_]\w*)/))) {
1959
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ForeignImportDeclaration', 'foreign'));
1960
+ } else if ((match = trimmed.match(/^(data|newtype|type)\s+([A-Z][A-Za-z0-9_']*)\b/))) {
1961
+ declarations.push(nativeDeclaration(input, number, `${upperFirst(match[1])}Declaration`, 'type', match[2], {}, /(?:where|=)/.test(trimmed)));
1962
+ } else if ((match = trimmed.match(/^class\s+(?:\([^)]*\)\s*=>\s*)?([A-Z][A-Za-z0-9_']*)\b/))) {
1963
+ declarations.push(nativeDeclaration(input, number, 'ClassDeclaration', 'type', match[1], {}, /\bwhere\b/.test(trimmed)));
1964
+ } else if ((match = trimmed.match(/^([a-z_][A-Za-z0-9_']*)\s*::\s*(.+)$/))) {
1965
+ seenFunctions.add(match[1]);
1966
+ declarations.push(nativeDeclaration(input, number, 'FunctionSignature', 'function', match[1], { signature: match[2].trim() }, false));
1967
+ } else if ((match = trimmed.match(/^([a-z_][A-Za-z0-9_']*)\b[^=]*=/))) {
1968
+ if (!seenFunctions.has(match[1])) {
1969
+ seenFunctions.add(match[1]);
1970
+ declarations.push(nativeDeclaration(input, number, 'FunctionBinding', 'function', match[1], {}, true));
1971
+ }
1972
+ }
1973
+ }
1974
+ return declarations;
1975
+ }
1976
+
1977
+ function scanR(input) {
1978
+ const declarations = [];
1979
+ for (const { line, number } of sourceLines(input.sourceText)) {
1980
+ const trimmed = line.trim();
1981
+ let match;
1982
+ if ((match = trimmed.match(/^(?:library|require)\s*\(\s*["']?([A-Za-z_][\w.-]*)["']?/))) {
1983
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'LibraryCall', 'package'));
1984
+ } else if ((match = trimmed.match(/^importFrom\s*\(\s*["']?([A-Za-z_][\w.-]*)["']?/))) {
1985
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportFromCall', 'package'));
1986
+ } else if ((match = trimmed.match(/^source\s*\(\s*["']([^"']+)["']/))) {
1987
+ declarations.push(nativeImportDeclaration(input, number, match[1], 'SourceCall', 'module'));
1988
+ } else if ((match = trimmed.match(/^([A-Za-z_][\w.]*)\s*(?:<-|=)\s*function\s*\(([^)]*)\)/))) {
1989
+ declarations.push(nativeDeclaration(input, number, 'FunctionAssignment', 'function', match[1], { parameters: splitParameters(match[2]) }, trimmed.includes('{')));
1990
+ } else if ((match = trimmed.match(/^([A-Za-z_][\w.]*)\s*<-\s*R6Class\s*\(\s*["']([^"']+)["']/))) {
1991
+ declarations.push(nativeDeclaration(input, number, 'R6ClassDeclaration', 'class', match[2] || match[1], { binding: match[1] }, true));
1992
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[2] || match[1]));
1993
+ } else if ((match = trimmed.match(/^setClass\s*\(\s*["']([^"']+)["']/))) {
1994
+ declarations.push(nativeDeclaration(input, number, 'S4ClassDeclaration', 'class', match[1], {}, true));
1995
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[1]));
1996
+ } else if ((match = trimmed.match(/^setGeneric\s*\(\s*["']([^"']+)["']/))) {
1997
+ declarations.push(nativeDeclaration(input, number, 'S4GenericDeclaration', 'function', match[1], {}, true));
1998
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', match[1]));
1999
+ } else if ((match = trimmed.match(/^setMethod\s*\(\s*["']([^"']+)["']/))) {
2000
+ declarations.push(nativeDeclaration(input, number, 'S4MethodDeclaration', 'method', match[1], {}, true));
2001
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicDispatch', match[1]));
2002
+ } else if ((match = trimmed.match(/^([A-Z][A-Za-z0-9_.]*)\s*(?:<-|=)\s*/))) {
2003
+ declarations.push(nativeDeclaration(input, number, 'ConstantAssignment', 'constant', match[1], {}, false));
2004
+ }
2005
+ if (/(?:eval|parse|substitute|quote|bquote|assign)\s*\(|<<-|\{\{|!!!|!!/.test(trimmed)) {
2006
+ declarations.push(nativeMacroLoss(input, number, trimmed, 'dynamicRuntime', rMetaName(trimmed)));
2007
+ }
2008
+ }
2009
+ return declarations;
2010
+ }
2011
+
2012
+ function scanGenericDeclarations(input) {
2013
+ return sourceLines(input.sourceText)
2014
+ .filter(({ line }) => /\b(function|class|struct|enum|trait|interface|def)\b/.test(line))
2015
+ .map(({ line, number }) => nativeDeclaration(input, number, 'NativeDeclaration', 'variable', idFragment(line.trim()).slice(0, 40), { source: line.trim() }, true));
2016
+ }
2017
+
2018
+ function upperFirst(value) {
2019
+ return String(value).charAt(0).toUpperCase() + String(value).slice(1);
2020
+ }
2021
+
2022
+ function parseGoReceiver(raw) {
2023
+ const value = String(raw ?? '').trim();
2024
+ const match = value.match(/^(?:(\w+)\s+)?(.+)$/);
2025
+ const rawType = String(match?.[2] ?? value).trim();
2026
+ return {
2027
+ raw: value,
2028
+ ...(match?.[1] ? { name: match[1] } : {}),
2029
+ rawType,
2030
+ type: normalizeGoReceiverType(rawType)
2031
+ };
2032
+ }
2033
+
2034
+ function normalizeGoReceiverType(rawType) {
2035
+ return String(rawType ?? '')
2036
+ .trim()
2037
+ .replace(/^[*&\s]+/, '')
2038
+ .replace(/\[[^\]]+\]/g, '')
2039
+ .replace(/\s+/g, ' ');
2040
+ }
2041
+
2042
+ function goReceiverMethodName(receiver, methodName) {
2043
+ return receiver?.type ? `${receiver.type}.${methodName}` : methodName;
2044
+ }
2045
+
2046
+ function parseSwiftExtensionTail(rawTail) {
2047
+ let tail = String(rawTail ?? '').split('{')[0].trim();
2048
+ const fields = {};
2049
+ const whereMatch = tail.match(/\bwhere\b(.+)$/);
2050
+ if (whereMatch) {
2051
+ fields.constraints = whereMatch[1].trim();
2052
+ tail = tail.slice(0, whereMatch.index).trim();
2053
+ }
2054
+ if (tail.startsWith(':')) {
2055
+ fields.conformances = tail.slice(1).split(',').map((part) => part.trim()).filter(Boolean);
2056
+ }
2057
+ return fields;
2058
+ }
2059
+
2060
+ function unquoteSwiftIdentifier(identifier) {
2061
+ return String(identifier).replace(/^`|`$/g, '');
2062
+ }
2063
+
2064
+ function javaSymbolKind(kind) {
2065
+ if (kind === 'interface' || kind === '@interface') return 'interface';
2066
+ if (kind === 'enum' || kind === 'record') return 'type';
2067
+ return 'class';
2068
+ }
2069
+
2070
+ function swiftSymbolKind(kind) {
2071
+ if (kind === 'protocol') return 'protocol';
789
2072
  if (kind === 'extension') return 'implementation';
790
2073
  if (kind === 'struct' || kind === 'enum' || kind === 'actor') return 'type';
791
2074
  return 'class';
792
2075
  }
793
2076
 
794
2077
  function csharpSymbolKind(kind) {
795
- if (kind === 'interface') return 'interface';
796
- if (kind === 'struct' || kind === 'enum' || kind === 'record') return 'type';
2078
+ const normalized = String(kind).replace(/\s+/g, ' ');
2079
+ if (normalized === 'interface') return 'interface';
2080
+ if (normalized === 'struct' || normalized === 'enum' || normalized.startsWith('record')) return 'type';
797
2081
  return 'class';
798
2082
  }
799
2083
 
2084
+ function csharpDeclarationKind(kind) {
2085
+ const normalized = String(kind).replace(/\s+/g, ' ');
2086
+ if (normalized === 'record struct') return 'RecordStructDeclaration';
2087
+ if (normalized === 'record class') return 'RecordClassDeclaration';
2088
+ if (normalized === 'record') return 'RecordDeclaration';
2089
+ return `${upperFirst(normalized)}Declaration`;
2090
+ }
2091
+
2092
+ function csharpExtensionReceiver(parameters) {
2093
+ const match = String(parameters?.[0] ?? '').match(/^this\s+(.+?)\s+([A-Za-z_]\w*)$/);
2094
+ return match ? { type: match[1].trim(), name: match[2] } : undefined;
2095
+ }
2096
+
2097
+ function csharpAccessors(source) {
2098
+ return uniqueStrings([...String(source ?? '').matchAll(/\b(get|set|init|add|remove)\b/g)].map((match) => match[1]));
2099
+ }
2100
+
800
2101
  function phpSymbolKind(kind) {
801
2102
  if (kind === 'interface') return 'interface';
802
2103
  if (kind === 'trait') return 'trait';
@@ -804,6 +2105,78 @@ function phpSymbolKind(kind) {
804
2105
  return 'class';
805
2106
  }
806
2107
 
2108
+ function kotlinDeclarationKind(kind, prefix) {
2109
+ if (prefix === 'enum') return 'EnumClassDeclaration';
2110
+ if (prefix === 'annotation') return 'AnnotationClassDeclaration';
2111
+ return `${upperFirst(kind)}Declaration`;
2112
+ }
2113
+
2114
+ function kotlinSymbolKind(kind, prefix) {
2115
+ if (kind === 'interface') return 'interface';
2116
+ if (kind === 'object') return 'module';
2117
+ if (prefix === 'enum' || prefix === 'annotation') return 'type';
2118
+ return 'class';
2119
+ }
2120
+
2121
+ function scalaSymbolKind(kind) {
2122
+ if (kind === 'trait') return 'trait';
2123
+ if (kind === 'object') return 'module';
2124
+ if (kind === 'enum') return 'type';
2125
+ return 'class';
2126
+ }
2127
+
2128
+ function dartSymbolKind(kind) {
2129
+ if (kind === 'mixin') return 'trait';
2130
+ if (kind === 'enum') return 'type';
2131
+ return 'class';
2132
+ }
2133
+
2134
+ function sqlSymbolKind(kind) {
2135
+ if (kind === 'FUNCTION' || kind === 'PROCEDURE' || kind === 'TRIGGER') return 'function';
2136
+ if (kind.includes('INDEX')) return 'index';
2137
+ if (kind === 'SCHEMA') return 'namespace';
2138
+ return 'type';
2139
+ }
2140
+
2141
+ function sqlLanguageKind(kind) {
2142
+ return `Create${kind.toLowerCase().split(/\s+/).map(upperFirst).join('')}Statement`;
2143
+ }
2144
+
2145
+ function normalizeSqlIdentifier(value) {
2146
+ return String(value)
2147
+ .split(/\s*\.\s*/)
2148
+ .map((part) => part.replace(/^"|"$/g, '').replace(/^`|`$/g, '').replace(/^\[|\]$/g, ''))
2149
+ .join('.');
2150
+ }
2151
+
2152
+ function zigMetaName(source) {
2153
+ const match = source.match(/@([A-Za-z_]\w*)|^\s*(comptime)\b/);
2154
+ return match?.[1] ?? match?.[2] ?? 'comptime';
2155
+ }
2156
+
2157
+ function elixirMetaName(source) {
2158
+ const match = source.match(/@([A-Za-z_]\w*)|\b(unquote(?:_splicing)?|quote)\b/);
2159
+ return match?.[1] ?? match?.[2] ?? 'macro';
2160
+ }
2161
+
2162
+ function erlangAtomName(value) {
2163
+ return String(value).startsWith("'") ? String(value).slice(1, -1) : String(value);
2164
+ }
2165
+
2166
+ function erlangMacroName(source) {
2167
+ const match = source.match(/\?([A-Za-z_]\w*)/);
2168
+ return match?.[1] ?? 'macro';
2169
+ }
2170
+
2171
+ function haskellMetaName(source) {
2172
+ return idFragment(source).slice(0, 40);
2173
+ }
2174
+
2175
+ function rMetaName(source) {
2176
+ const match = source.match(/([A-Za-z_][\w.]*)\s*\(/);
2177
+ return match?.[1] ?? 'dynamic';
2178
+ }
2179
+
807
2180
  function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false) {
808
2181
  const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
809
2182
  return {
@@ -945,6 +2318,23 @@ function splitParameters(raw) {
945
2318
  .filter(Boolean);
946
2319
  }
947
2320
 
2321
+ function splitTypeParameters(raw) {
2322
+ return splitParameters(raw);
2323
+ }
2324
+
2325
+ function braceDelta(source) {
2326
+ let delta = 0;
2327
+ for (const char of String(source ?? '')) {
2328
+ if (char === '{') delta += 1;
2329
+ if (char === '}') delta -= 1;
2330
+ }
2331
+ return delta;
2332
+ }
2333
+
2334
+ function jsControlKeyword(value) {
2335
+ return ['if', 'for', 'while', 'switch', 'catch', 'with'].includes(String(value));
2336
+ }
2337
+
948
2338
  function inferSourceMapMappings(input) {
949
2339
  const semanticIndex = input.semanticIndex;
950
2340
  const nativeAst = input.nativeAst;
@@ -990,33 +2380,61 @@ function inferSourceMapMappings(input) {
990
2380
  }
991
2381
 
992
2382
  function normalizeSourceMapMappings(mappings, context) {
2383
+ if (mappings === undefined || mappings === null) return [];
2384
+ if (!Array.isArray(mappings)) {
2385
+ throw new Error('Source-map mappings must be an array');
2386
+ }
993
2387
  const semanticIndex = context.semanticIndex;
994
2388
  const nativeAst = context.nativeAst;
995
2389
  const nativeSource = context.nativeSource;
996
2390
  const symbolsById = new Map((semanticIndex?.symbols ?? []).map((symbol) => [symbol.id, symbol]));
997
2391
  const occurrencesById = new Map((semanticIndex?.occurrences ?? []).map((occurrence) => [occurrence.id, occurrence]));
998
- const evidenceIds = (context.evidence ?? []).map((record) => record.id);
999
- return (mappings ?? [])
1000
- .filter((mapping) => mapping && typeof mapping === 'object')
2392
+ const evidenceIds = uniqueStrings([
2393
+ ...(semanticIndex?.evidence ?? []).map((record) => record.id),
2394
+ ...(context.evidence ?? []).map((record) => record.id),
2395
+ ...(context.sourceMapEvidence ?? []).map((record) => record.id)
2396
+ ]);
2397
+ const usedMappingIds = new Set();
2398
+ return mappings
1001
2399
  .map((mapping, index) => {
2400
+ if (!mapping || typeof mapping !== 'object') {
2401
+ throw new Error(`Source-map mapping ${index + 1} must be an object`);
2402
+ }
1002
2403
  const occurrence = mapping.semanticOccurrenceId ? occurrencesById.get(mapping.semanticOccurrenceId) : undefined;
1003
2404
  const symbol = mapping.semanticSymbolId ? symbolsById.get(mapping.semanticSymbolId) : occurrence ? symbolsById.get(occurrence.symbolId) : undefined;
1004
2405
  const nativeAstNodeId = mapping.nativeAstNodeId ?? occurrence?.nativeAstNodeId;
1005
2406
  const nativeNode = nativeAstNodeId ? nativeAst?.nodes?.[nativeAstNodeId] : undefined;
1006
2407
  const sourceSpan = mapping.sourceSpan ?? occurrence?.span ?? nativeNode?.span;
1007
- return {
2408
+ const target = mapping.target ?? mapping.generatedSpan?.target ?? context.target;
2409
+ const generatedSpan = normalizeGeneratedSpan(mapping.generatedSpan, target, context.targetPath, context.targetHash);
2410
+ if (
2411
+ !nativeAstNodeId &&
2412
+ !mapping.semanticNodeId &&
2413
+ !mapping.semanticSymbolId &&
2414
+ !mapping.semanticOccurrenceId &&
2415
+ !sourceSpan &&
2416
+ !generatedSpan &&
2417
+ !mapping.generatedName
2418
+ ) {
2419
+ throw new Error(`Source-map mapping ${index + 1} must reference a native AST node, semantic node, symbol, occurrence, source span, or generated span`);
2420
+ }
2421
+ const normalizedMapping = {
1008
2422
  ...mapping,
1009
- id: mapping.id ?? `map_${idFragment(nativeAstNodeId ?? mapping.semanticSymbolId ?? mapping.semanticNodeId ?? index + 1)}`,
1010
2423
  nativeSourceId: mapping.nativeSourceId ?? nativeSource?.id,
1011
2424
  nativeAstNodeId,
1012
2425
  semanticSymbolId: mapping.semanticSymbolId ?? occurrence?.symbolId,
1013
2426
  semanticOccurrenceId: mapping.semanticOccurrenceId ?? occurrence?.id,
1014
2427
  semanticNodeId: mapping.semanticNodeId ?? occurrence?.semanticNodeId ?? symbol?.semanticNodeId,
1015
2428
  sourceSpan,
1016
- target: mapping.target ?? context.target,
1017
- evidenceIds: mapping.evidenceIds ?? evidenceIds,
1018
- lossIds: mapping.lossIds ?? lossIdsForNativeNode(context.losses ?? nativeAst?.losses ?? [], nativeAstNodeId),
1019
- precision: mapping.precision ?? (sourceSpan ? 'declaration' : 'unknown')
2429
+ generatedSpan,
2430
+ target,
2431
+ evidenceIds: normalizeReferenceIds(mapping.evidenceIds, evidenceIds),
2432
+ lossIds: normalizeReferenceIds(mapping.lossIds, lossIdsForNativeNode(context.losses ?? nativeAst?.losses ?? [], nativeAstNodeId)),
2433
+ precision: normalizeSourceMapPrecision(mapping.precision, sourceSpan, generatedSpan)
2434
+ };
2435
+ return {
2436
+ ...normalizedMapping,
2437
+ id: reserveUniqueId(sourceMapMappingBaseId(normalizedMapping, index), usedMappingIds)
1020
2438
  };
1021
2439
  });
1022
2440
  }
@@ -1028,80 +2446,1152 @@ function lossIdsForNativeNode(losses, nativeAstNodeId) {
1028
2446
  .map((loss) => loss.id);
1029
2447
  }
1030
2448
 
1031
- export function createUniversalAstFromDocument(document, input = {}) {
1032
- return createUniversalAstEnvelope({
1033
- id: input.id ?? `universal_ast_${idFragment(document.id)}`,
1034
- document,
1035
- semanticIndex: input.semanticIndex,
1036
- sourceMaps: input.sourceMaps ?? [],
1037
- evidence: input.evidence ?? [],
1038
- metadata: input.metadata
2449
+ function normalizeSourceMaps(sourceMaps, context) {
2450
+ if (sourceMaps === undefined || sourceMaps === null) return [];
2451
+ if (!Array.isArray(sourceMaps)) {
2452
+ throw new Error('Native import sourceMaps must be an array');
2453
+ }
2454
+ const usedSourceMapIds = new Set();
2455
+ return sourceMaps.map((sourceMap, index) => {
2456
+ if (!sourceMap || typeof sourceMap !== 'object') {
2457
+ throw new Error(`Native import source map ${index + 1} must be an object`);
2458
+ }
2459
+ const id = reserveUniqueId(String(sourceMap.id ?? `${context.defaultSourceMapId}_${index + 1}`), usedSourceMapIds);
2460
+ const evidence = uniqueRecordsById([...(sourceMap.evidence ?? []), ...(context.evidence ?? [])]);
2461
+ const target = sourceMap.target ?? context.target;
2462
+ const targetPath = sourceMap.targetPath ?? context.targetPath;
2463
+ const targetHash = sourceMap.targetHash ?? context.targetHash;
2464
+ const mappings = normalizeSourceMapMappings(sourceMap.mappings ?? [], {
2465
+ ...context,
2466
+ target,
2467
+ targetPath,
2468
+ targetHash,
2469
+ sourceMapEvidence: evidence
2470
+ });
2471
+ const normalized = createSourceMapRecord({
2472
+ ...sourceMap,
2473
+ id,
2474
+ sourcePath: sourceMap.sourcePath ?? context.sourcePath,
2475
+ sourceHash: sourceMap.sourceHash ?? context.sourceHash,
2476
+ target,
2477
+ targetPath: targetPath ?? commonGeneratedTargetPath(mappings),
2478
+ targetHash,
2479
+ semanticIndexId: sourceMap.semanticIndexId ?? context.semanticIndex?.id,
2480
+ nativeAstId: sourceMap.nativeAstId ?? context.nativeAst?.id,
2481
+ nativeSourceId: sourceMap.nativeSourceId ?? context.nativeSource?.id,
2482
+ mappings,
2483
+ evidence
2484
+ });
2485
+ const issues = validateSourceMapRecord(normalized, {
2486
+ document: context.document,
2487
+ nativeSources: context.nativeSources,
2488
+ nativeAst: context.nativeAst,
2489
+ semanticIndex: context.semanticIndex,
2490
+ losses: context.losses,
2491
+ evidence: context.evidence
2492
+ });
2493
+ if (issues.length) {
2494
+ throw new Error(`Invalid Frontier native source map ${normalized.id}: ${issues.join('; ')}`);
2495
+ }
2496
+ return normalized;
1039
2497
  });
1040
2498
  }
1041
2499
 
1042
- export function readUniversalAstJson(source) {
1043
- const envelope = JSON.parse(source);
1044
- const issues = validateUniversalAstEnvelope(envelope);
1045
- if (issues.length > 0) {
1046
- throw new Error(`Invalid Frontier universal AST JSON: ${issues.join('; ')}`);
2500
+ function normalizeGeneratedSpan(generatedSpan, target, targetPath, targetHash) {
2501
+ if (!generatedSpan) return generatedSpan;
2502
+ return {
2503
+ ...generatedSpan,
2504
+ target: generatedSpan.target ?? target,
2505
+ targetPath: generatedSpan.targetPath ?? target?.emitPath ?? targetPath,
2506
+ targetHash: generatedSpan.targetHash ?? targetHash
2507
+ };
2508
+ }
2509
+
2510
+ function normalizeSourceMapPrecision(value, sourceSpan, generatedSpan) {
2511
+ const explicit = value === undefined || value === null ? '' : String(value).trim();
2512
+ if (explicit) {
2513
+ const normalized = explicit.toLowerCase();
2514
+ if (normalized === 'exact' || normalized === 'declaration' || normalized === 'line' || normalized === 'estimated' || normalized === 'unknown') return normalized;
2515
+ if (normalized === 'estimate' || normalized === 'approx' || normalized === 'approximate' || normalized === 'approximated') return 'estimated';
2516
+ return explicit;
1047
2517
  }
1048
- return envelope;
2518
+ if (hasExactSpan(sourceSpan) && hasExactSpan(generatedSpan)) return 'exact';
2519
+ if (sourceSpan?.startLine && generatedSpan?.startLine) return 'line';
2520
+ if (sourceSpan?.startLine) return 'declaration';
2521
+ if (generatedSpan?.startLine) return 'line';
2522
+ return 'unknown';
1049
2523
  }
1050
2524
 
1051
- export function writeUniversalAstJson(envelope) {
1052
- const issues = validateUniversalAstEnvelope(envelope);
1053
- if (issues.length > 0) {
1054
- throw new Error(`Invalid Frontier universal AST envelope: ${issues.join('; ')}`);
2525
+ function hasExactSpan(span) {
2526
+ return Boolean(span && (
2527
+ (typeof span.start === 'number' && typeof span.end === 'number') ||
2528
+ (
2529
+ typeof span.startLine === 'number' &&
2530
+ typeof span.startColumn === 'number' &&
2531
+ typeof span.endLine === 'number' &&
2532
+ typeof span.endColumn === 'number'
2533
+ )
2534
+ ));
2535
+ }
2536
+
2537
+ function normalizeReferenceIds(value, fallback = []) {
2538
+ if (value === undefined || value === null) return uniqueStrings(fallback);
2539
+ return uniqueStrings(Array.isArray(value) ? value : [value]);
2540
+ }
2541
+
2542
+ function sourceMapMappingBaseId(mapping, index) {
2543
+ const explicit = mapping.id === undefined || mapping.id === null ? '' : String(mapping.id).trim();
2544
+ if (explicit) return explicit;
2545
+ const span = mapping.sourceSpan ?? mapping.generatedSpan;
2546
+ const spanPart = span
2547
+ ? `${span.path ?? span.targetPath ?? span.sourceId ?? 'span'}_${span.start ?? span.startLine ?? ''}_${span.end ?? span.endLine ?? ''}`
2548
+ : undefined;
2549
+ return `map_${idFragment([
2550
+ mapping.nativeAstNodeId,
2551
+ mapping.semanticOccurrenceId,
2552
+ mapping.semanticSymbolId,
2553
+ mapping.semanticNodeId,
2554
+ spanPart,
2555
+ index + 1
2556
+ ].filter(Boolean).join('_'))}`;
2557
+ }
2558
+
2559
+ function reserveUniqueId(baseId, usedIds) {
2560
+ const safeBase = String(baseId || 'id');
2561
+ if (!usedIds.has(safeBase)) {
2562
+ usedIds.add(safeBase);
2563
+ return safeBase;
1055
2564
  }
1056
- return stableUniversalAstJson(envelope);
2565
+ let index = 2;
2566
+ while (usedIds.has(`${safeBase}_${index}`)) index += 1;
2567
+ const id = `${safeBase}_${index}`;
2568
+ usedIds.add(id);
2569
+ return id;
1057
2570
  }
1058
2571
 
1059
- export function emitForTarget(document, target = 'typescript', options = {}) {
1060
- return renderTargetAst(projectFrontierAst(document, target, options), target);
2572
+ function commonGeneratedTargetPath(mappings) {
2573
+ const paths = uniqueStrings((mappings ?? [])
2574
+ .map((mapping) => mapping.generatedSpan?.targetPath ?? mapping.target?.emitPath)
2575
+ .filter(Boolean));
2576
+ return paths.length === 1 ? paths[0] : undefined;
1061
2577
  }
1062
2578
 
1063
- function normalizeNativeImporterAdapter(adapter) {
1064
- if (!adapter || typeof adapter !== 'object') {
1065
- throw new Error('Native importer adapter must be an object');
2579
+ function uniqueRecordsById(records) {
2580
+ const seen = new Set();
2581
+ const result = [];
2582
+ for (const record of records ?? []) {
2583
+ if (!record?.id || seen.has(record.id)) continue;
2584
+ seen.add(record.id);
2585
+ result.push(record);
1066
2586
  }
1067
- if (!adapter.id) throw new Error('Native importer adapter requires an id');
1068
- if (!adapter.language) throw new Error(`Native importer adapter ${adapter.id} requires a language`);
1069
- if (!adapter.parser) throw new Error(`Native importer adapter ${adapter.id} requires a parser`);
1070
- if (typeof adapter.parse !== 'function') throw new Error(`Native importer adapter ${adapter.id} requires a parse function`);
1071
- const summaryInput = {
1072
- id: String(adapter.id),
1073
- language: adapter.language,
1074
- parser: String(adapter.parser),
1075
- version: adapter.version === undefined ? undefined : String(adapter.version)
2587
+ return result;
2588
+ }
2589
+
2590
+ function normalizeNativeLossRecords(losses) {
2591
+ return (Array.isArray(losses) ? losses : [losses])
2592
+ .filter((loss) => loss !== undefined && loss !== null)
2593
+ .map((loss, index) => normalizeNativeLossRecord(loss, `loss_${index + 1}`));
2594
+ }
2595
+
2596
+ function normalizeNativeLossRecord(loss, fallbackId) {
2597
+ const record = typeof loss === 'string' ? { message: loss } : loss ?? {};
2598
+ const severity = normalizeLossSeverity(record.severity);
2599
+ const kind = normalizeNativeLossKind(record, severity);
2600
+ const category = nativeImportCategoryForLossKind(kind);
2601
+ return {
2602
+ ...record,
2603
+ id: String(record.id ?? fallbackId),
2604
+ severity,
2605
+ kind,
2606
+ message: String(record.message ?? `${kind} loss during native import.`),
2607
+ metadata: {
2608
+ lossCategory: category,
2609
+ semanticMergeAdmission: semanticMergeAdmissionForSeverity(severity),
2610
+ dashboardSeverity: severity,
2611
+ ...record.metadata
2612
+ }
1076
2613
  };
1077
- return Object.freeze({
1078
- ...summaryInput,
1079
- capabilities: normalizeStringList(adapter.capabilities),
1080
- supportedExtensions: normalizeStringList(adapter.supportedExtensions).map((extension) => extension.startsWith('.') ? extension.toLowerCase() : `.${extension.toLowerCase()}`),
1081
- diagnostics: normalizeAdapterDiagnostics(adapter.diagnostics, summaryInput, {
1082
- language: adapter.language,
1083
- parser: String(adapter.parser),
1084
- parserVersion: adapter.version === undefined ? undefined : String(adapter.version)
1085
- }, 'adapter')
1086
- });
1087
2614
  }
1088
2615
 
1089
- function normalizeStringList(value) {
1090
- if (value === undefined || value === null) return [];
1091
- if (Array.isArray(value)) return value.map((item) => String(item)).filter(Boolean);
1092
- return [String(value)].filter(Boolean);
2616
+ function normalizeLossSeverity(value) {
2617
+ const severity = String(value ?? 'warning').toLowerCase();
2618
+ if (severity === 'error') return 'error';
2619
+ if (severity === 'info') return 'info';
2620
+ return 'warning';
1093
2621
  }
1094
2622
 
1095
- function normalizeAdapterDiagnostics(value, adapter, input, scope = 'diagnostic') {
1096
- if (value === undefined || value === null) return [];
1097
- const diagnostics = Array.isArray(value) ? value : [value];
1098
- return diagnostics.map((diagnostic, index) => {
1099
- const normalized = typeof diagnostic === 'string' ? { message: diagnostic } : diagnostic ?? {};
1100
- const severity = normalizeDiagnosticSeverity(normalized.severity);
1101
- return Object.freeze({
1102
- id: normalized.id ?? `diagnostic_${idFragment(adapter.id)}_${idFragment(scope)}_${index + 1}`,
1103
- severity,
1104
- code: normalized.code,
2623
+ function normalizeNativeLossKind(loss, severity) {
2624
+ const kind = String(loss.kind ?? '').trim();
2625
+ if (kind) return kind;
2626
+ if (loss.metadata?.diagnosticId || loss.metadata?.diagnosticCode) {
2627
+ return severity === 'error' ? 'unsupportedSyntax' : 'parserDiagnostic';
2628
+ }
2629
+ return severity === 'error' ? 'unsupportedSyntax' : 'opaqueNative';
2630
+ }
2631
+
2632
+ function nativeImportCategoryForLossKind(kind) {
2633
+ if (kind === 'declarationOnlyCoverage') return 'declarationsOnly';
2634
+ if (kind === 'opaqueNative') return 'opaqueBodies';
2635
+ if (kind === 'macroExpansion') return 'macroExpansion';
2636
+ if (kind === 'preprocessor' || kind === 'conditionalCompilation' || kind === 'macroHygiene') return 'preprocessor';
2637
+ if (kind === 'metaprogramming' || kind === 'reflection' || kind === 'dynamicDispatch' || kind === 'dynamicRuntime') return 'metaprogramming';
2638
+ if (kind === 'generatedCode') return 'generatedCode';
2639
+ if (kind === 'sourcePreservation' || kind === 'nonRoundTrippable') return 'sourcePreservation';
2640
+ if (kind === 'parserDiagnostic') return 'parserDiagnostics';
2641
+ if (kind === 'unsupportedSyntax' || kind === 'unsupportedSemantic') return 'unsupportedSyntax';
2642
+ if (kind === 'partialSemanticIndex') return 'partialSemanticIndex';
2643
+ if (kind === 'sourceMapApproximation') return 'sourceMapApproximation';
2644
+ return String(kind ?? 'opaqueNative');
2645
+ }
2646
+
2647
+ function nativeImportLanguageProfile(language, input = {}) {
2648
+ const lossKinds = input.lossKinds ?? ['declarationOnlyCoverage', 'opaqueNative', 'sourceMapApproximation', 'sourcePreservation'];
2649
+ return Object.freeze({
2650
+ language,
2651
+ aliases: Object.freeze(uniqueStrings(input.aliases ?? [])),
2652
+ extensions: Object.freeze(uniqueStrings(input.extensions ?? [])),
2653
+ supportsLightweightScan: input.supportsLightweightScan !== false,
2654
+ parserAdapters: Object.freeze(uniqueStrings(input.parserAdapters ?? ['tree-sitter'])),
2655
+ projectionTargets: Object.freeze(uniqueStrings(input.projectionTargets ?? FrontierCompileTargets)),
2656
+ knownLossKinds: Object.freeze(uniqueStrings(lossKinds)),
2657
+ defaultReadiness: input.defaultReadiness ?? 'needs-review',
2658
+ notes: Object.freeze(uniqueStrings(input.notes ?? ['lightweight scanner records declarations only; exact parser adapters must be injected by the host']))
2659
+ });
2660
+ }
2661
+
2662
+ function mergeNativeImportProfiles(languages, imports, adapters) {
2663
+ const profilesByLanguage = new Map();
2664
+ for (const profile of languages) {
2665
+ const normalized = normalizeNativeLanguageId(profile.language ?? profile);
2666
+ profilesByLanguage.set(normalized, normalizeNativeImportLanguageProfile(profile, normalized));
2667
+ }
2668
+ for (const imported of imports) {
2669
+ const normalized = normalizeNativeLanguageId(imported?.language ?? imported?.nativeAst?.language);
2670
+ if (!normalized || profilesByLanguage.has(normalized)) continue;
2671
+ profilesByLanguage.set(normalized, nativeImportLanguageProfile(normalized, {
2672
+ supportsLightweightScan: false,
2673
+ parserAdapters: [],
2674
+ defaultReadiness: 'blocked',
2675
+ lossKinds: ['unsupportedSyntax'],
2676
+ notes: ['language appeared in import evidence but has no declared Frontier coverage profile']
2677
+ }));
2678
+ }
2679
+ for (const adapter of adapters) {
2680
+ const normalized = normalizeNativeLanguageId(adapter?.language);
2681
+ if (!normalized) continue;
2682
+ const existing = profilesByLanguage.get(normalized) ?? nativeImportLanguageProfile(normalized, { supportsLightweightScan: false, parserAdapters: [] });
2683
+ profilesByLanguage.set(normalized, {
2684
+ ...existing,
2685
+ parserAdapters: uniqueStrings([...(existing.parserAdapters ?? []), adapter.parser ?? adapter.id].filter(Boolean))
2686
+ });
2687
+ }
2688
+ return [...profilesByLanguage.values()].sort((left, right) => left.language.localeCompare(right.language));
2689
+ }
2690
+
2691
+ function normalizeNativeImportLanguageProfile(profile, fallbackLanguage) {
2692
+ const language = normalizeNativeLanguageId(profile.language ?? fallbackLanguage);
2693
+ return {
2694
+ language,
2695
+ aliases: uniqueStrings(profile.aliases ?? []),
2696
+ extensions: uniqueStrings(profile.extensions ?? []),
2697
+ supportsLightweightScan: profile.supportsLightweightScan !== false,
2698
+ parserAdapters: uniqueStrings(profile.parserAdapters ?? []),
2699
+ projectionTargets: uniqueStrings(profile.projectionTargets ?? FrontierCompileTargets),
2700
+ knownLossKinds: uniqueStrings(profile.knownLossKinds ?? profile.lossKinds ?? []),
2701
+ defaultReadiness: profile.defaultReadiness ?? 'needs-review',
2702
+ notes: uniqueStrings(profile.notes ?? [])
2703
+ };
2704
+ }
2705
+
2706
+ function nativeImportCoverageForProfile(profile, imports, adapters) {
2707
+ const aliases = new Set([profile.language, ...(profile.aliases ?? [])].map(normalizeNativeLanguageId).filter(Boolean));
2708
+ const matchingImports = imports.filter((imported) => aliases.has(normalizeNativeLanguageId(imported?.language ?? imported?.nativeAst?.language)));
2709
+ const matchingAdapters = adapters.filter((adapter) => aliases.has(normalizeNativeLanguageId(adapter?.language)));
2710
+ const lossSummary = summarizeNativeImportLosses(matchingImports.flatMap((imported) => imported?.losses ?? []), {
2711
+ evidence: matchingImports.flatMap((imported) => imported?.evidence ?? [])
2712
+ });
2713
+ const readiness = matchingImports.length
2714
+ ? lossSummary.semanticMergeReadiness
2715
+ : profile.supportsLightweightScan ? profile.defaultReadiness : 'blocked';
2716
+ const importedParsers = uniqueStrings(matchingImports.map((imported) => imported?.nativeAst?.parser ?? imported?.parser ?? imported?.metadata?.parser).filter(Boolean));
2717
+ const sourceMaps = matchingImports.flatMap((imported) => imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? []);
2718
+ return {
2719
+ language: profile.language,
2720
+ aliases: profile.aliases,
2721
+ extensions: profile.extensions,
2722
+ supportsLightweightScan: profile.supportsLightweightScan,
2723
+ parserAdapters: uniqueStrings([...(profile.parserAdapters ?? []), ...matchingAdapters.map((adapter) => adapter.parser ?? adapter.id).filter(Boolean)]),
2724
+ projectionTargets: profile.projectionTargets,
2725
+ knownLossKinds: uniqueStrings([...(profile.knownLossKinds ?? []), ...Object.keys(lossSummary.byKind)]),
2726
+ defaultReadiness: profile.defaultReadiness,
2727
+ notes: profile.notes,
2728
+ imports: {
2729
+ total: matchingImports.length,
2730
+ parsers: importedParsers,
2731
+ readiness,
2732
+ readinessReasons: matchingImports.length ? lossSummary.readinessReasons : nativeImportCoverageReasons(profile),
2733
+ symbols: matchingImports.reduce((sum, imported) => sum + (imported?.semanticIndex?.symbols?.length ?? imported?.universalAst?.semanticIndex?.symbols?.length ?? 0), 0),
2734
+ sourceMaps: sourceMaps.length,
2735
+ sourceMapMappings: sourceMaps.reduce((sum, sourceMap) => sum + (sourceMap?.mappings?.length ?? 0), 0),
2736
+ losses: lossSummary.total,
2737
+ lossKinds: lossSummary.byKind,
2738
+ lossCategories: lossSummary.categories
2739
+ }
2740
+ };
2741
+ }
2742
+
2743
+ function semanticImportSidecarEntry(imported, index, options) {
2744
+ const semanticIndex = imported?.semanticIndex ?? imported?.universalAst?.semanticIndex;
2745
+ const nativeAst = imported?.nativeAst ?? imported?.nativeSource?.ast;
2746
+ const sourceMaps = imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? [];
2747
+ const sourceMapMappings = sourceMaps.flatMap((sourceMap) => sourceMap?.mappings ?? []);
2748
+ const mappingsBySymbolId = new Map();
2749
+ for (const mapping of sourceMapMappings) {
2750
+ if (mapping.semanticSymbolId && !mappingsBySymbolId.has(mapping.semanticSymbolId)) {
2751
+ mappingsBySymbolId.set(mapping.semanticSymbolId, mapping);
2752
+ }
2753
+ }
2754
+ const symbols = [];
2755
+ const regions = [];
2756
+ for (const symbol of semanticIndex?.symbols ?? []) {
2757
+ const mapping = mappingsBySymbolId.get(symbol.id);
2758
+ const nativeNode = symbol.nativeAstNodeId ? nativeAst?.nodes?.[symbol.nativeAstNodeId] : undefined;
2759
+ const region = semanticOwnershipRegionForSymbol(imported, symbol, mapping, nativeNode, options);
2760
+ regions.push(region);
2761
+ symbols.push({
2762
+ id: symbol.id,
2763
+ name: symbol.name,
2764
+ kind: symbol.kind,
2765
+ language: symbol.language ?? imported?.language,
2766
+ nativeAstNodeId: symbol.nativeAstNodeId,
2767
+ semanticOccurrenceId: mapping?.semanticOccurrenceId,
2768
+ sourceMapMappingId: mapping?.id,
2769
+ sourceSpan: mapping?.sourceSpan ?? symbol.definitionSpan ?? nativeNode?.span,
2770
+ signatureHash: symbol.signatureHash,
2771
+ ownershipRegionId: region.id,
2772
+ ownershipKey: region.key,
2773
+ readiness: imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review'
2774
+ });
2775
+ }
2776
+ return {
2777
+ id: imported?.id ?? `import_${index + 1}`,
2778
+ language: imported?.language,
2779
+ sourcePath: imported?.sourcePath ?? imported?.nativeSource?.sourcePath ?? nativeAst?.sourcePath,
2780
+ sourceHash: imported?.nativeSource?.sourceHash ?? nativeAst?.sourceHash,
2781
+ parser: imported?.nativeAst?.parser ?? nativeAst?.parser,
2782
+ nativeSourceId: imported?.nativeSource?.id,
2783
+ nativeAstId: nativeAst?.id,
2784
+ semanticIndexId: semanticIndex?.id,
2785
+ universalAstId: imported?.universalAst?.id,
2786
+ symbolCount: symbols.length,
2787
+ sourceMapCount: sourceMaps.length,
2788
+ sourceMapMappingCount: sourceMapMappings.length,
2789
+ readiness: imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review',
2790
+ emptySemanticIndex: symbols.length === 0,
2791
+ symbols,
2792
+ ownershipRegions: uniqueRecordsById(regions)
2793
+ };
2794
+ }
2795
+
2796
+ function semanticOwnershipRegionForSymbol(imported, symbol, mapping, nativeNode, options = {}) {
2797
+ const sourcePath = mapping?.sourceSpan?.path ?? symbol.definitionSpan?.path ?? nativeNode?.span?.path ?? imported?.sourcePath ?? imported?.nativeSource?.sourcePath ?? imported?.nativeAst?.sourcePath;
2798
+ const language = symbol.language ?? imported?.language ?? imported?.nativeAst?.language ?? imported?.nativeSource?.language;
2799
+ const sourceSpan = mapping?.sourceSpan ?? symbol.definitionSpan ?? nativeNode?.span;
2800
+ const key = [
2801
+ options.regionPrefix ?? 'source',
2802
+ sourcePath ?? `${language}:memory`,
2803
+ symbol.kind ?? 'symbol',
2804
+ symbol.name ?? symbol.id
2805
+ ].map((part) => String(part).replace(/\s+/g, ' ').trim()).join('#');
2806
+ return {
2807
+ id: `region_${idFragment(key)}`,
2808
+ key,
2809
+ granularity: 'symbol',
2810
+ language,
2811
+ sourcePath,
2812
+ sourceHash: imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash,
2813
+ symbolId: symbol.id,
2814
+ symbolName: symbol.name,
2815
+ symbolKind: symbol.kind,
2816
+ nativeAstNodeId: symbol.nativeAstNodeId ?? nativeNode?.id,
2817
+ sourceSpan,
2818
+ precision: mapping?.precision ?? (sourceSpan ? 'declaration' : 'unknown'),
2819
+ mergePolicy: 'single-writer-review-required'
2820
+ };
2821
+ }
2822
+
2823
+ function semanticOwnershipRegionForDeclaration(input, declaration, documentId) {
2824
+ const name = declaration.name ?? declaration.importPath ?? declaration.nodeId ?? declaration.nativeNode?.id;
2825
+ const kind = declaration.symbolKind ?? declaration.kind ?? declaration.nativeNode?.kind ?? 'symbol';
2826
+ const sourcePath = declaration.span?.path ?? declaration.nativeNode?.span?.path ?? input.sourcePath ?? `${input.language}:memory`;
2827
+ const key = ['source', sourcePath, kind, name].map((part) => String(part).replace(/\s+/g, ' ').trim()).join('#');
2828
+ return {
2829
+ id: `region_${idFragment(key)}`,
2830
+ key,
2831
+ granularity: 'symbol',
2832
+ language: input.language,
2833
+ documentId,
2834
+ sourcePath,
2835
+ sourceHash: input.sourceHash,
2836
+ symbolId: declaration.symbolId,
2837
+ symbolName: name,
2838
+ symbolKind: kind,
2839
+ nativeAstNodeId: declaration.nodeId ?? declaration.nativeNode?.id,
2840
+ sourceSpan: declaration.span ?? declaration.nativeNode?.span,
2841
+ precision: declaration.span || declaration.nativeNode?.span ? 'declaration' : 'unknown',
2842
+ mergePolicy: 'single-writer-review-required'
2843
+ };
2844
+ }
2845
+
2846
+ function semanticPatchHintForRegion(region, readiness, options = {}) {
2847
+ return {
2848
+ id: `hint_${idFragment(region.id)}`,
2849
+ kind: 'source-region-patch',
2850
+ ownershipRegionId: region.id,
2851
+ ownershipKey: region.key,
2852
+ sourcePath: region.sourcePath,
2853
+ sourceHash: region.sourceHash,
2854
+ sourceSpan: region.sourceSpan,
2855
+ readiness,
2856
+ precision: region.precision,
2857
+ supportedOperations: ['replace-region', 'insert-before-region', 'insert-after-region'],
2858
+ projection: {
2859
+ sourceLanguage: region.language,
2860
+ targetPath: options.targetPath ?? region.sourcePath,
2861
+ requiresSourceMap: true
2862
+ }
2863
+ };
2864
+ }
2865
+
2866
+ function nativeImportCoverageReasons(profile) {
2867
+ if (!profile.supportsLightweightScan) return ['No built-in scanner coverage profile; host must provide an exact adapter or mark unsupported.'];
2868
+ return ['Built-in coverage is declaration-level only; use injected parser adapters for exact AST/CST, tokens, trivia, type resolution, and round-trip evidence.'];
2869
+ }
2870
+
2871
+ function normalizeNativeLanguageId(value) {
2872
+ if (!value) return '';
2873
+ const text = String(value).trim().toLowerCase();
2874
+ if (text === 'js' || text === 'mjs' || text === 'cjs' || text === 'jsx') return 'javascript';
2875
+ if (text === 'ts' || text === 'tsx') return 'typescript';
2876
+ if (text === 'py' || text === 'pyi') return 'python';
2877
+ if (text === 'rs') return 'rust';
2878
+ if (text === 'h') return 'c';
2879
+ if (text === 'c++' || text === 'cc' || text === 'cxx' || text === 'hpp' || text === 'hh') return 'cpp';
2880
+ if (text === 'c#' || text === 'cs') return 'csharp';
2881
+ if (text === 'rb' || text === 'rake') return 'ruby';
2882
+ if (text === 'kt' || text === 'kts') return 'kotlin';
2883
+ if (text === 'sc') return 'scala';
2884
+ if (text === 'sh' || text === 'bash' || text === 'zsh') return 'shell';
2885
+ if (text === 'postgresql' || text === 'postgres' || text === 'mysql' || text === 'sqlite') return 'sql';
2886
+ if (text === 'ex' || text === 'exs') return 'elixir';
2887
+ if (text === 'erl' || text === 'hrl') return 'erlang';
2888
+ if (text === 'hs' || text === 'lhs') return 'haskell';
2889
+ return text;
2890
+ }
2891
+
2892
+ function semanticMergeAdmissionForSeverity(severity) {
2893
+ if (severity === 'error') return 'blocked';
2894
+ if (severity === 'warning') return 'review';
2895
+ return 'disclose';
2896
+ }
2897
+
2898
+ function nativeImportReadinessReasons(input) {
2899
+ if (input.failedEvidenceIds.length) {
2900
+ return [`Failed native import evidence prevents merge: ${input.failedEvidenceIds.join(', ')}`];
2901
+ }
2902
+ if (input.blockingLossIds.length) {
2903
+ return [`Native import error loss(es) block semantic merge: ${input.blockingLossIds.join(', ')}`];
2904
+ }
2905
+ if (input.reviewLossIds.length) {
2906
+ return [`Native import warning loss(es) require review: ${input.reviewLossIds.join(', ')}`];
2907
+ }
2908
+ if (input.informationalLossIds.length) {
2909
+ return [`Native import recorded informational loss(es): ${input.informationalLossIds.join(', ')}`];
2910
+ }
2911
+ if (input.exactAst) return ['Native import supplied exact AST coverage with no recorded loss.'];
2912
+ return ['Native import has no recorded loss.'];
2913
+ }
2914
+
2915
+ function attachNativeImportLossSummary(evidence, lossSummary) {
2916
+ return (evidence ?? []).map((record) => ({
2917
+ ...record,
2918
+ metadata: {
2919
+ ...record.metadata,
2920
+ nativeImportLossSummary: lossSummary,
2921
+ semanticMergeReadiness: lossSummary.semanticMergeReadiness,
2922
+ lossCategories: lossSummary.categories
2923
+ }
2924
+ }));
2925
+ }
2926
+
2927
+ function withNativeImportReadiness(importResult, lossSummary) {
2928
+ const mergeCandidates = (importResult.mergeCandidates ?? []).map((candidate) => {
2929
+ const readiness = maxSemanticMergeReadiness(candidate.readiness, lossSummary.semanticMergeReadiness);
2930
+ return {
2931
+ ...candidate,
2932
+ readiness,
2933
+ reasons: uniqueStrings([
2934
+ ...(candidate.reasons ?? []),
2935
+ ...lossSummary.readinessReasons
2936
+ ]),
2937
+ metadata: {
2938
+ ...candidate.metadata,
2939
+ nativeImportLossSummary: lossSummary,
2940
+ severityReadiness: lossSummary.semanticMergeReadiness,
2941
+ finalReadiness: readiness,
2942
+ lossCategories: lossSummary.categories,
2943
+ lossSeverityCounts: lossSummary.bySeverity,
2944
+ lossKindCounts: lossSummary.byKind
2945
+ }
2946
+ };
2947
+ });
2948
+ return {
2949
+ ...importResult,
2950
+ mergeCandidates,
2951
+ metadata: {
2952
+ ...importResult.metadata,
2953
+ nativeImportLossSummary: lossSummary,
2954
+ semanticMergeReadiness: mergeCandidates[0]?.readiness ?? lossSummary.semanticMergeReadiness,
2955
+ lossCategories: lossSummary.categories,
2956
+ lossSeverityCounts: lossSummary.bySeverity,
2957
+ lossKindCounts: lossSummary.byKind
2958
+ }
2959
+ };
2960
+ }
2961
+
2962
+ function maxSemanticMergeReadiness(left, right) {
2963
+ const leftRank = semanticMergeReadinessRank[left] ?? semanticMergeReadinessRank['needs-review'];
2964
+ const rightRank = semanticMergeReadinessRank[right] ?? semanticMergeReadinessRank['needs-review'];
2965
+ return leftRank >= rightRank ? left : right;
2966
+ }
2967
+
2968
+ export function createUniversalAstFromDocument(document, input = {}) {
2969
+ return createUniversalAstEnvelope({
2970
+ id: input.id ?? `universal_ast_${idFragment(document.id)}`,
2971
+ document,
2972
+ semanticIndex: input.semanticIndex,
2973
+ sourceMaps: input.sourceMaps ?? [],
2974
+ evidence: input.evidence ?? [],
2975
+ metadata: input.metadata
2976
+ });
2977
+ }
2978
+
2979
+ export function readUniversalAstJson(source) {
2980
+ const envelope = JSON.parse(source);
2981
+ const issues = validateUniversalAstEnvelope(envelope);
2982
+ if (issues.length > 0) {
2983
+ throw new Error(`Invalid Frontier universal AST JSON: ${issues.join('; ')}`);
2984
+ }
2985
+ return envelope;
2986
+ }
2987
+
2988
+ export function writeUniversalAstJson(envelope) {
2989
+ const issues = validateUniversalAstEnvelope(envelope);
2990
+ if (issues.length > 0) {
2991
+ throw new Error(`Invalid Frontier universal AST envelope: ${issues.join('; ')}`);
2992
+ }
2993
+ return stableUniversalAstJson(envelope);
2994
+ }
2995
+
2996
+ export function emitForTarget(document, target = 'typescript', options = {}) {
2997
+ return renderTargetAst(projectFrontierAst(document, target, options), target);
2998
+ }
2999
+
3000
+ function createJavaScriptSyntaxImporterAdapter(options) {
3001
+ return {
3002
+ id: options.id,
3003
+ language: options.language,
3004
+ parser: options.parser,
3005
+ version: options.version,
3006
+ capabilities: uniqueStrings(['nativeAst', 'semanticIndex', 'sourceMaps', 'diagnostics', ...(options.capabilities ?? [])]),
3007
+ supportedExtensions: options.supportedExtensions,
3008
+ diagnostics: options.diagnostics,
3009
+ parse(input) {
3010
+ const ast = input.options?.ast ?? input.options?.nativeAst ?? options.ast ?? parseJavaScriptSyntax(input, options);
3011
+ if (!ast) {
3012
+ return missingInjectedParserResult(input, {
3013
+ parser: options.parser,
3014
+ adapterId: options.id,
3015
+ message: `${options.id} requires an injected parse function, parserModule.parse, ast, or adapterOptions.ast.`
3016
+ });
3017
+ }
3018
+ const parseDiagnostics = normalizeParserErrors(ast.errors, input, options);
3019
+ return createNativeImportFromSyntaxAst(ast, input, {
3020
+ parser: options.parser,
3021
+ astFormat: options.astFormat,
3022
+ maxNodes: options.maxNodes,
3023
+ diagnostics: parseDiagnostics
3024
+ });
3025
+ }
3026
+ };
3027
+ }
3028
+
3029
+ function parseJavaScriptSyntax(input, options) {
3030
+ const parse = options.parse ?? options.parserModule?.parse ?? options.babelParser?.parse;
3031
+ if (typeof parse !== 'function') return undefined;
3032
+ const parserOptions = {
3033
+ sourceFilename: input.sourcePath,
3034
+ ...(options.defaultParserOptions ?? {}),
3035
+ ...(typeof options.parserOptions === 'function'
3036
+ ? options.parserOptions(input)
3037
+ : options.parserOptions ?? {}),
3038
+ ...(input.options?.parserOptions ?? {})
3039
+ };
3040
+ return parse(input.sourceText, parserOptions);
3041
+ }
3042
+
3043
+ function createTypeScriptSourceFile(ts, input, options) {
3044
+ if (typeof options.createSourceFile === 'function') return options.createSourceFile(input, ts);
3045
+ if (!ts || typeof ts.createSourceFile !== 'function') return undefined;
3046
+ const scriptTarget = options.scriptTarget ?? ts.ScriptTarget?.Latest ?? ts.ScriptTarget?.ESNext ?? 99;
3047
+ const scriptKind = options.scriptKind ?? inferTypeScriptScriptKind(ts, input);
3048
+ return ts.createSourceFile(input.sourcePath ?? 'frontier-input.ts', input.sourceText, scriptTarget, true, scriptKind);
3049
+ }
3050
+
3051
+ function inferTypeScriptScriptKind(ts, input) {
3052
+ const path = String(input.sourcePath ?? '').toLowerCase();
3053
+ if (path.endsWith('.tsx')) return ts.ScriptKind?.TSX;
3054
+ if (path.endsWith('.jsx')) return ts.ScriptKind?.JSX;
3055
+ if (path.endsWith('.js') || path.endsWith('.mjs') || path.endsWith('.cjs')) return ts.ScriptKind?.JS;
3056
+ return ts.ScriptKind?.TS;
3057
+ }
3058
+
3059
+ function parseTreeSitterSource(input, options) {
3060
+ const parser = options.parserInstance ?? options.treeSitterParser ?? options.parser;
3061
+ if (parser && typeof parser.parse === 'function') return parser.parse(input.sourceText);
3062
+ if (typeof options.parse === 'function') return options.parse(input);
3063
+ return undefined;
3064
+ }
3065
+
3066
+ function createNativeImportFromSyntaxAst(ast, input, options) {
3067
+ const root = normalizeSyntaxAstRoot(ast, options.astFormat);
3068
+ if (!root) {
3069
+ return missingInjectedParserResult(input, {
3070
+ parser: options.parser,
3071
+ adapterId: input.adapterId,
3072
+ message: 'Injected AST did not contain an object root node.'
3073
+ });
3074
+ }
3075
+ const context = createAstNormalizationContext(input, options);
3076
+ visitSyntaxAstNode(root, context, 'root');
3077
+ if (context.truncated) {
3078
+ context.losses.push(truncatedAstLoss(input, context, options));
3079
+ }
3080
+ const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
3081
+ return {
3082
+ rootId: context.rootId,
3083
+ nodes: context.nodes,
3084
+ semanticIndex: semantic.semanticIndex,
3085
+ mappings: semantic.mappings,
3086
+ losses: mergeNativeLosses(context.losses, options.diagnostics?.map((diagnostic, index) => adapterDiagnosticToLoss(diagnostic, index, {
3087
+ id: input.adapterId,
3088
+ version: input.adapterVersion
3089
+ }, input)) ?? []),
3090
+ evidence: semantic.evidence,
3091
+ diagnostics: options.diagnostics,
3092
+ metadata: {
3093
+ astFormat: options.astFormat,
3094
+ parser: options.parser,
3095
+ normalizedNodeCount: Object.keys(context.nodes).length,
3096
+ declarationCount: context.declarations.length,
3097
+ truncated: context.truncated
3098
+ }
3099
+ };
3100
+ }
3101
+
3102
+ function createNativeImportFromTypeScriptAst(sourceFile, input, options) {
3103
+ const context = createAstNormalizationContext(input, options);
3104
+ visitTypeScriptAstNode(sourceFile, sourceFile, context, 'root', options.ts);
3105
+ if (context.truncated) {
3106
+ context.losses.push(truncatedAstLoss(input, context, options));
3107
+ }
3108
+ const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
3109
+ return {
3110
+ rootId: context.rootId,
3111
+ nodes: context.nodes,
3112
+ semanticIndex: semantic.semanticIndex,
3113
+ mappings: semantic.mappings,
3114
+ losses: context.losses,
3115
+ evidence: semantic.evidence,
3116
+ metadata: {
3117
+ astFormat: options.astFormat,
3118
+ parser: options.parser,
3119
+ normalizedNodeCount: Object.keys(context.nodes).length,
3120
+ declarationCount: context.declarations.length,
3121
+ truncated: context.truncated
3122
+ }
3123
+ };
3124
+ }
3125
+
3126
+ function createNativeImportFromTreeSitter(root, input, options) {
3127
+ const context = createAstNormalizationContext(input, options);
3128
+ visitTreeSitterNode(root, context, 'root');
3129
+ if (context.truncated) {
3130
+ context.losses.push(truncatedAstLoss(input, context, options));
3131
+ }
3132
+ const semantic = semanticIndexFromNativeDeclarations(context.declarations, input, options);
3133
+ return {
3134
+ rootId: context.rootId,
3135
+ nodes: context.nodes,
3136
+ semanticIndex: semantic.semanticIndex,
3137
+ mappings: semantic.mappings,
3138
+ losses: context.losses,
3139
+ evidence: semantic.evidence,
3140
+ metadata: {
3141
+ astFormat: options.astFormat,
3142
+ parser: options.parser,
3143
+ normalizedNodeCount: Object.keys(context.nodes).length,
3144
+ declarationCount: context.declarations.length,
3145
+ truncated: context.truncated
3146
+ }
3147
+ };
3148
+ }
3149
+
3150
+ function createAstNormalizationContext(input, options) {
3151
+ return {
3152
+ input,
3153
+ options,
3154
+ maxNodes: Number.isFinite(options.maxNodes) ? Math.max(1, options.maxNodes) : 5000,
3155
+ counter: 0,
3156
+ objectIds: new WeakMap(),
3157
+ nodes: {},
3158
+ declarations: [],
3159
+ losses: [],
3160
+ rootId: undefined,
3161
+ truncated: false
3162
+ };
3163
+ }
3164
+
3165
+ function normalizeSyntaxAstRoot(ast, astFormat) {
3166
+ if (!ast || typeof ast !== 'object') return undefined;
3167
+ if (astFormat === 'babel' && ast.program && typeof ast.program === 'object') return ast.program;
3168
+ return ast;
3169
+ }
3170
+
3171
+ function visitSyntaxAstNode(node, context, propertyPath) {
3172
+ if (!isSyntaxAstNode(node) || context.truncated) return undefined;
3173
+ if (context.objectIds.has(node)) return context.objectIds.get(node);
3174
+ if (context.counter >= context.maxNodes) {
3175
+ context.truncated = true;
3176
+ return undefined;
3177
+ }
3178
+ const id = nativeNodeId(context, node.type ?? node.kind ?? 'Node', node.loc, propertyPath);
3179
+ context.objectIds.set(node, id);
3180
+ if (!context.rootId) context.rootId = id;
3181
+ const children = [];
3182
+ const fields = primitiveSyntaxFields(node);
3183
+ for (const [key, value] of Object.entries(node)) {
3184
+ if (ignoredSyntaxField(key)) continue;
3185
+ if (Array.isArray(value)) {
3186
+ value.forEach((entry, index) => {
3187
+ const childId = visitSyntaxAstNode(entry, context, `${propertyPath}.${key}[${index}]`);
3188
+ if (childId) children.push(childId);
3189
+ });
3190
+ } else {
3191
+ const childId = visitSyntaxAstNode(value, context, `${propertyPath}.${key}`);
3192
+ if (childId) children.push(childId);
3193
+ }
3194
+ }
3195
+ const declaration = syntaxDeclaration(node, id, context.input, context.options);
3196
+ const nativeNode = {
3197
+ id,
3198
+ kind: String(node.type ?? node.kind ?? 'Node'),
3199
+ languageKind: `${context.input.language}.${node.type ?? node.kind ?? 'Node'}`,
3200
+ span: spanFromLoc(node.loc, context.input),
3201
+ value: declaration?.name ?? literalSyntaxValue(node),
3202
+ fields,
3203
+ children,
3204
+ metadata: {
3205
+ astFormat: context.options.astFormat,
3206
+ propertyPath,
3207
+ start: numberOrUndefined(node.start),
3208
+ end: numberOrUndefined(node.end),
3209
+ range: Array.isArray(node.range) ? node.range.slice(0, 2) : undefined
3210
+ }
3211
+ };
3212
+ context.nodes[id] = nativeNode;
3213
+ if (declaration) context.declarations.push({ ...declaration, nativeNode });
3214
+ return id;
3215
+ }
3216
+
3217
+ function visitTypeScriptAstNode(node, sourceFile, context, propertyPath, ts) {
3218
+ if (!node || typeof node !== 'object' || context.truncated) return undefined;
3219
+ if (context.objectIds.has(node)) return context.objectIds.get(node);
3220
+ if (context.counter >= context.maxNodes) {
3221
+ context.truncated = true;
3222
+ return undefined;
3223
+ }
3224
+ const kind = typeScriptKindName(node, ts);
3225
+ const span = spanFromTypeScriptNode(node, sourceFile);
3226
+ const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
3227
+ context.objectIds.set(node, id);
3228
+ if (!context.rootId) context.rootId = id;
3229
+ const children = [];
3230
+ const visitChild = (child) => {
3231
+ const childId = visitTypeScriptAstNode(child, sourceFile, context, `${propertyPath}.${children.length}`, ts);
3232
+ if (childId) children.push(childId);
3233
+ };
3234
+ if (ts && typeof ts.forEachChild === 'function') {
3235
+ ts.forEachChild(node, visitChild);
3236
+ } else if (typeof node.forEachChild === 'function') {
3237
+ node.forEachChild(visitChild);
3238
+ } else if (Array.isArray(node.children)) {
3239
+ node.children.forEach(visitChild);
3240
+ }
3241
+ const declaration = typeScriptDeclaration(node, kind, id, context.input, context.options);
3242
+ const nativeNode = {
3243
+ id,
3244
+ kind,
3245
+ languageKind: `${context.input.language}.${kind}`,
3246
+ span,
3247
+ value: declaration?.name ?? typeScriptNodeValue(node),
3248
+ fields: primitiveTypeScriptFields(node, kind),
3249
+ children,
3250
+ metadata: {
3251
+ astFormat: context.options.astFormat,
3252
+ propertyPath,
3253
+ pos: numberOrUndefined(node.pos),
3254
+ end: numberOrUndefined(node.end)
3255
+ }
3256
+ };
3257
+ context.nodes[id] = nativeNode;
3258
+ if (declaration) context.declarations.push({ ...declaration, nativeNode });
3259
+ return id;
3260
+ }
3261
+
3262
+ function visitTreeSitterNode(node, context, propertyPath) {
3263
+ if (!node || typeof node !== 'object' || context.truncated) return undefined;
3264
+ if (context.objectIds.has(node)) return context.objectIds.get(node);
3265
+ if (context.counter >= context.maxNodes) {
3266
+ context.truncated = true;
3267
+ return undefined;
3268
+ }
3269
+ const kind = String(node.type ?? node.kind ?? 'node');
3270
+ const span = spanFromTreeSitterNode(node, context.input);
3271
+ const id = nativeNodeId(context, kind, { start: { line: span?.startLine, column: span?.startColumn } }, propertyPath);
3272
+ context.objectIds.set(node, id);
3273
+ if (!context.rootId) context.rootId = id;
3274
+ const children = [];
3275
+ const rawChildren = Array.isArray(node.namedChildren)
3276
+ ? node.namedChildren
3277
+ : Array.isArray(node.children)
3278
+ ? node.children
3279
+ : [];
3280
+ rawChildren.forEach((child, index) => {
3281
+ const childId = visitTreeSitterNode(child, context, `${propertyPath}.children[${index}]`);
3282
+ if (childId) children.push(childId);
3283
+ });
3284
+ const declaration = treeSitterDeclaration(node, kind, id, context.input, context.options);
3285
+ const nativeNode = {
3286
+ id,
3287
+ kind,
3288
+ languageKind: `${context.input.language}.${kind}`,
3289
+ span,
3290
+ value: declaration?.name ?? shortNodeText(node),
3291
+ fields: {
3292
+ named: Boolean(node.isNamed ?? node.named),
3293
+ missing: Boolean(node.isMissing),
3294
+ error: Boolean(node.hasError || kind === 'ERROR')
3295
+ },
3296
+ children,
3297
+ metadata: {
3298
+ astFormat: context.options.astFormat,
3299
+ propertyPath,
3300
+ startIndex: numberOrUndefined(node.startIndex),
3301
+ endIndex: numberOrUndefined(node.endIndex)
3302
+ }
3303
+ };
3304
+ context.nodes[id] = nativeNode;
3305
+ if (declaration) context.declarations.push({ ...declaration, nativeNode });
3306
+ if (node.hasError || kind === 'ERROR') {
3307
+ context.losses.push({
3308
+ id: `loss_${idFragment(id)}_tree_sitter_error`,
3309
+ severity: 'error',
3310
+ phase: 'parse',
3311
+ sourceFormat: context.input.language,
3312
+ kind: 'unsupportedSyntax',
3313
+ message: 'Tree-sitter reported a parse error node.',
3314
+ span,
3315
+ nodeId: id
3316
+ });
3317
+ }
3318
+ return id;
3319
+ }
3320
+
3321
+ function semanticIndexFromNativeDeclarations(declarations, input, options) {
3322
+ const documentId = `doc_${idFragment(input.sourcePath ?? input.language)}_${idFragment(input.sourceHash)}`;
3323
+ const evidenceId = `evidence_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}_import`;
3324
+ const symbols = [];
3325
+ const occurrences = [];
3326
+ const relations = [];
3327
+ const facts = [];
3328
+ const mappings = [];
3329
+ for (const declaration of declarations) {
3330
+ const symbolId = declaration.symbolId ?? `symbol:${input.language}:${declaration.role === 'import' ? 'import:' : ''}${idFragment(declaration.name)}`;
3331
+ const occurrenceId = `occ_${idFragment(declaration.nativeNode.id)}_${declaration.role ?? 'definition'}`;
3332
+ const ownershipRegion = semanticOwnershipRegionForDeclaration(input, {
3333
+ ...declaration,
3334
+ nodeId: declaration.nativeNode.id,
3335
+ kind: declaration.nativeNode.kind,
3336
+ languageKind: declaration.nativeNode.languageKind,
3337
+ span: declaration.nativeNode.span,
3338
+ symbolId
3339
+ }, documentId);
3340
+ declaration.nativeNode.metadata = {
3341
+ ...declaration.nativeNode.metadata,
3342
+ ownershipRegionId: ownershipRegion.id,
3343
+ ownershipRegionKey: ownershipRegion.key
3344
+ };
3345
+ symbols.push({
3346
+ id: symbolId,
3347
+ scheme: 'frontier',
3348
+ name: declaration.name,
3349
+ kind: declaration.symbolKind,
3350
+ language: input.language,
3351
+ nativeAstNodeId: declaration.nativeNode.id,
3352
+ signatureHash: hashSemanticValue([input.language, declaration.nativeNode.kind, declaration.name, declaration.nativeNode.fields ?? {}]),
3353
+ definitionSpan: declaration.nativeNode.span,
3354
+ metadata: {
3355
+ ownershipRegionId: ownershipRegion.id,
3356
+ ownershipRegionKey: ownershipRegion.key
3357
+ }
3358
+ });
3359
+ occurrences.push({
3360
+ id: occurrenceId,
3361
+ documentId,
3362
+ symbolId,
3363
+ role: declaration.role ?? 'definition',
3364
+ span: declaration.nativeNode.span,
3365
+ nativeAstNodeId: declaration.nativeNode.id
3366
+ });
3367
+ relations.push({
3368
+ id: `rel_${idFragment(documentId)}_${idFragment(declaration.nativeNode.id)}`,
3369
+ sourceId: documentId,
3370
+ predicate: relationPredicateForDeclaration(declaration),
3371
+ targetId: symbolId
3372
+ });
3373
+ facts.push({
3374
+ id: `fact_${idFragment(declaration.nativeNode.id)}_kind`,
3375
+ predicate: 'nativeKind',
3376
+ subjectId: symbolId,
3377
+ value: declaration.nativeNode.languageKind
3378
+ }, {
3379
+ id: `fact_${idFragment(declaration.nativeNode.id)}_ownership_region`,
3380
+ predicate: 'semanticOwnershipRegion',
3381
+ subjectId: symbolId,
3382
+ value: ownershipRegion
3383
+ });
3384
+ mappings.push({
3385
+ id: `map_${idFragment(declaration.nativeNode.id)}`,
3386
+ nativeAstNodeId: declaration.nativeNode.id,
3387
+ semanticSymbolId: symbolId,
3388
+ semanticOccurrenceId: occurrenceId,
3389
+ sourceSpan: declaration.nativeNode.span,
3390
+ evidenceIds: [evidenceId],
3391
+ lossIds: [],
3392
+ ownershipRegionId: ownershipRegion.id,
3393
+ precision: declaration.nativeNode.span ? 'declaration' : 'unknown'
3394
+ });
3395
+ }
3396
+ const evidence = [{
3397
+ id: evidenceId,
3398
+ kind: 'import',
3399
+ status: 'passed',
3400
+ path: input.sourcePath,
3401
+ summary: `Normalized ${options.astFormat ?? options.parser} native AST with ${declarations.length} declaration(s).`,
3402
+ metadata: {
3403
+ parser: options.parser,
3404
+ astFormat: options.astFormat,
3405
+ language: input.language,
3406
+ sourceHash: input.sourceHash
3407
+ }
3408
+ }];
3409
+ return {
3410
+ semanticIndex: createSemanticIndexRecord({
3411
+ id: `index_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}`,
3412
+ documents: [{
3413
+ id: documentId,
3414
+ path: input.sourcePath ?? `${input.language}:memory`,
3415
+ language: input.language,
3416
+ sourceHash: input.sourceHash
3417
+ }],
3418
+ symbols,
3419
+ occurrences,
3420
+ relations,
3421
+ facts,
3422
+ evidence,
3423
+ metadata: {
3424
+ parser: options.parser,
3425
+ astFormat: options.astFormat,
3426
+ coverage: 'native-ast-declarations'
3427
+ }
3428
+ }),
3429
+ mappings,
3430
+ evidence
3431
+ };
3432
+ }
3433
+
3434
+ function createNativeProjectImportResult(input, imports) {
3435
+ const idPart = idFragment(input.id ?? input.projectRoot ?? 'native_project');
3436
+ const nodes = {};
3437
+ const rootIds = [];
3438
+ const semanticIndex = mergeSemanticIndexes(imports, input, idPart);
3439
+ const nativeSources = [];
3440
+ const sourceMaps = [];
3441
+ const losses = [];
3442
+ const evidence = [];
3443
+ const mergeCandidates = [];
3444
+ const operations = [];
3445
+ for (const result of imports) {
3446
+ for (const node of Object.values(result.document?.nodes ?? {})) {
3447
+ nodes[node.id] = node;
3448
+ }
3449
+ rootIds.push(...(result.document?.rootIds ?? []));
3450
+ if (result.nativeSource) nativeSources.push(result.nativeSource);
3451
+ sourceMaps.push(...(result.sourceMaps ?? []));
3452
+ losses.push(...(result.losses ?? []));
3453
+ evidence.push(...(result.evidence ?? []));
3454
+ mergeCandidates.push(...(result.mergeCandidates ?? []));
3455
+ operations.push(...(result.patch?.operations ?? []));
3456
+ }
3457
+ const document = createDocument({
3458
+ id: input.documentId ?? `document_${idPart}`,
3459
+ name: input.documentName ?? input.name ?? 'NativeProject',
3460
+ nodes: Object.values(nodes),
3461
+ rootIds: uniqueStrings(rootIds),
3462
+ metadata: {
3463
+ sourceLanguage: input.language ?? 'mixed',
3464
+ semanticStatus: losses.some((loss) => loss.severity === 'error') ? 'partial' : 'mapped',
3465
+ projectRoot: input.projectRoot,
3466
+ sourceCount: imports.length,
3467
+ ...input.documentMetadata
3468
+ }
3469
+ });
3470
+ const universalAst = createUniversalAstEnvelope({
3471
+ id: input.universalAstId ?? `universal_ast_${idPart}`,
3472
+ document,
3473
+ nativeSources,
3474
+ semanticIndex,
3475
+ sourceMaps,
3476
+ losses: uniqueByLossId(losses),
3477
+ evidence: uniqueByEvidenceId(evidence),
3478
+ metadata: {
3479
+ sourceLanguage: input.language ?? 'mixed',
3480
+ projectRoot: input.projectRoot,
3481
+ sourceCount: imports.length,
3482
+ ...input.universalAstMetadata
3483
+ }
3484
+ });
3485
+ const patch = createPatch({
3486
+ id: input.patchId ?? `patch_${idPart}_project_import`,
3487
+ author: input.author ?? '@shapeshift-labs/frontier-lang-compiler/importNativeProject',
3488
+ risk: losses.some((loss) => loss.severity === 'error') ? 'high' : losses.some((loss) => loss.severity === 'warning') ? 'medium' : 'low',
3489
+ operations,
3490
+ evidence: uniqueByEvidenceId(evidence),
3491
+ metadata: {
3492
+ semanticIndexId: semanticIndex?.id,
3493
+ universalAstId: universalAst.id,
3494
+ sourceMapIds: sourceMaps.map((sourceMap) => sourceMap.id),
3495
+ sourceCount: imports.length
3496
+ }
3497
+ });
3498
+ return {
3499
+ kind: 'frontier.lang.projectImportResult',
3500
+ version: 1,
3501
+ id: input.id ?? `project_import_${idPart}`,
3502
+ language: input.language ?? 'mixed',
3503
+ projectRoot: input.projectRoot,
3504
+ imports,
3505
+ document,
3506
+ patch,
3507
+ nativeSources,
3508
+ semanticIndex,
3509
+ universalAst,
3510
+ sourceMaps,
3511
+ losses: uniqueByLossId(losses),
3512
+ evidence: uniqueByEvidenceId(evidence),
3513
+ mergeCandidates,
3514
+ metadata: {
3515
+ sourceCount: imports.length,
3516
+ sourcePaths: imports.map((result) => result.sourcePath).filter(Boolean),
3517
+ ...input.metadata
3518
+ }
3519
+ };
3520
+ }
3521
+
3522
+ function mergeSemanticIndexes(imports, input, idPart) {
3523
+ const indexes = imports.map((result) => result.semanticIndex ?? result.universalAst?.semanticIndex).filter(Boolean);
3524
+ if (!indexes.length) return undefined;
3525
+ return createSemanticIndexRecord({
3526
+ id: input.semanticIndexId ?? `index_${idPart}_project`,
3527
+ documents: indexes.flatMap((index) => index.documents ?? []),
3528
+ symbols: indexes.flatMap((index) => index.symbols ?? []),
3529
+ occurrences: indexes.flatMap((index) => index.occurrences ?? []),
3530
+ relations: indexes.flatMap((index) => index.relations ?? []),
3531
+ facts: indexes.flatMap((index) => index.facts ?? []),
3532
+ evidence: indexes.flatMap((index) => index.evidence ?? []),
3533
+ metadata: {
3534
+ projectRoot: input.projectRoot,
3535
+ sourceCount: imports.length,
3536
+ mergedIndexCount: indexes.length
3537
+ }
3538
+ });
3539
+ }
3540
+
3541
+ function resolveNativeProjectAdapter(source, adapters, input) {
3542
+ if (typeof input.adapterResolver === 'function') return input.adapterResolver(source, adapters);
3543
+ const language = source.language;
3544
+ const sourcePath = source.sourcePath ?? '';
3545
+ return adapters.find((adapter) => {
3546
+ if (source.adapter && adapter.id === source.adapter) return true;
3547
+ if (language && adapter.language !== language) return false;
3548
+ const extensions = adapter.supportedExtensions ?? [];
3549
+ return !extensions.length || extensions.some((extension) => sourcePath.toLowerCase().endsWith(extension.toLowerCase()));
3550
+ });
3551
+ }
3552
+
3553
+ function normalizeNativeImporterAdapter(adapter) {
3554
+ if (!adapter || typeof adapter !== 'object') {
3555
+ throw new Error('Native importer adapter must be an object');
3556
+ }
3557
+ if (!adapter.id) throw new Error('Native importer adapter requires an id');
3558
+ if (!adapter.language) throw new Error(`Native importer adapter ${adapter.id} requires a language`);
3559
+ if (!adapter.parser) throw new Error(`Native importer adapter ${adapter.id} requires a parser`);
3560
+ if (typeof adapter.parse !== 'function') throw new Error(`Native importer adapter ${adapter.id} requires a parse function`);
3561
+ const summaryInput = {
3562
+ id: String(adapter.id),
3563
+ language: adapter.language,
3564
+ parser: String(adapter.parser),
3565
+ version: adapter.version === undefined ? undefined : String(adapter.version)
3566
+ };
3567
+ return Object.freeze({
3568
+ ...summaryInput,
3569
+ capabilities: normalizeStringList(adapter.capabilities),
3570
+ supportedExtensions: normalizeStringList(adapter.supportedExtensions).map((extension) => extension.startsWith('.') ? extension.toLowerCase() : `.${extension.toLowerCase()}`),
3571
+ diagnostics: normalizeAdapterDiagnostics(adapter.diagnostics, summaryInput, {
3572
+ language: adapter.language,
3573
+ parser: String(adapter.parser),
3574
+ parserVersion: adapter.version === undefined ? undefined : String(adapter.version)
3575
+ }, 'adapter')
3576
+ });
3577
+ }
3578
+
3579
+ function normalizeStringList(value) {
3580
+ if (value === undefined || value === null) return [];
3581
+ if (Array.isArray(value)) return value.map((item) => String(item)).filter(Boolean);
3582
+ return [String(value)].filter(Boolean);
3583
+ }
3584
+
3585
+ function normalizeAdapterDiagnostics(value, adapter, input, scope = 'diagnostic') {
3586
+ if (value === undefined || value === null) return [];
3587
+ const diagnostics = Array.isArray(value) ? value : [value];
3588
+ return diagnostics.map((diagnostic, index) => {
3589
+ const normalized = typeof diagnostic === 'string' ? { message: diagnostic } : diagnostic ?? {};
3590
+ const severity = normalizeDiagnosticSeverity(normalized.severity);
3591
+ return Object.freeze({
3592
+ id: normalized.id ?? `diagnostic_${idFragment(adapter.id)}_${idFragment(scope)}_${index + 1}`,
3593
+ severity,
3594
+ code: normalized.code,
1105
3595
  phase: normalized.phase ?? 'parse',
1106
3596
  kind: normalized.kind,
1107
3597
  message: String(normalized.message ?? `${adapter.id} reported a ${severity} diagnostic.`),
@@ -1201,6 +3691,336 @@ function serializableDiagnostic(diagnostic) {
1201
3691
  };
1202
3692
  }
1203
3693
 
3694
+ function missingInjectedParserResult(input, details) {
3695
+ const rootId = `native_${idFragment(details.adapterId ?? details.parser)}_missing_parser`;
3696
+ const diagnostic = {
3697
+ severity: 'error',
3698
+ code: 'adapter.parser.missing',
3699
+ phase: 'parse',
3700
+ kind: 'unsupportedSyntax',
3701
+ message: details.message,
3702
+ path: input.sourcePath,
3703
+ metadata: {
3704
+ adapterId: details.adapterId,
3705
+ parser: details.parser
3706
+ }
3707
+ };
3708
+ return {
3709
+ rootId,
3710
+ nodes: {
3711
+ [rootId]: {
3712
+ id: rootId,
3713
+ kind: 'MissingInjectedParser',
3714
+ languageKind: `${input.language}.missingInjectedParser`,
3715
+ value: details.parser,
3716
+ metadata: {
3717
+ adapterId: details.adapterId,
3718
+ parser: details.parser,
3719
+ reason: 'missing-injected-parser'
3720
+ }
3721
+ }
3722
+ },
3723
+ diagnostics: [diagnostic],
3724
+ losses: [{
3725
+ id: `loss_${idFragment(rootId)}`,
3726
+ severity: 'error',
3727
+ phase: 'parse',
3728
+ sourceFormat: input.language,
3729
+ kind: 'unsupportedSyntax',
3730
+ message: details.message,
3731
+ nodeId: rootId,
3732
+ metadata: {
3733
+ adapterId: details.adapterId,
3734
+ parser: details.parser
3735
+ }
3736
+ }],
3737
+ metadata: {
3738
+ parser: details.parser,
3739
+ adapterId: details.adapterId,
3740
+ missingInjectedParser: true
3741
+ }
3742
+ };
3743
+ }
3744
+
3745
+ function normalizeParserErrors(errors, input, options) {
3746
+ return (errors ?? []).map((error, index) => ({
3747
+ id: `diagnostic_${idFragment(options.parser)}_parser_error_${index + 1}`,
3748
+ severity: 'error',
3749
+ code: error.code ?? error.reasonCode,
3750
+ phase: 'parse',
3751
+ kind: 'unsupportedSyntax',
3752
+ message: String(error.message ?? 'Parser reported a syntax error.'),
3753
+ path: input.sourcePath,
3754
+ span: spanFromLoc(error.loc ? { start: error.loc, end: error.loc } : undefined, input),
3755
+ metadata: {
3756
+ parser: options.parser,
3757
+ reasonCode: error.reasonCode
3758
+ }
3759
+ }));
3760
+ }
3761
+
3762
+ function nativeNodeId(context, kind, loc, propertyPath) {
3763
+ context.counter += 1;
3764
+ const start = loc?.start;
3765
+ const line = start?.line ?? 'x';
3766
+ const column = start?.column ?? 'x';
3767
+ return `native_${idFragment(kind)}_${idFragment(line)}_${idFragment(column)}_${context.counter}_${idFragment(propertyPath)}`;
3768
+ }
3769
+
3770
+ function isSyntaxAstNode(value) {
3771
+ return Boolean(value && typeof value === 'object' && typeof (value.type ?? value.kind) === 'string');
3772
+ }
3773
+
3774
+ function ignoredSyntaxField(key) {
3775
+ return key === 'type'
3776
+ || key === 'kind'
3777
+ || key === 'loc'
3778
+ || key === 'start'
3779
+ || key === 'end'
3780
+ || key === 'range'
3781
+ || key === 'comments'
3782
+ || key === 'leadingComments'
3783
+ || key === 'trailingComments'
3784
+ || key === 'innerComments'
3785
+ || key === 'tokens'
3786
+ || key === 'extra'
3787
+ || key === 'parent';
3788
+ }
3789
+
3790
+ function primitiveSyntaxFields(node) {
3791
+ const fields = {};
3792
+ for (const key of ['name', 'operator', 'sourceType', 'async', 'generator', 'computed', 'static', 'exportKind', 'importKind', 'optional']) {
3793
+ if (typeof node[key] === 'string' || typeof node[key] === 'number' || typeof node[key] === 'boolean' || node[key] === null) {
3794
+ fields[key] = node[key];
3795
+ }
3796
+ }
3797
+ const literal = literalSyntaxValue(node);
3798
+ if (literal !== undefined) fields.literal = literal;
3799
+ if (node.source && typeof node.source === 'object' && typeof node.source.value === 'string') fields.source = node.source.value;
3800
+ return fields;
3801
+ }
3802
+
3803
+ function literalSyntaxValue(node) {
3804
+ if (node.value === null || typeof node.value === 'string' || typeof node.value === 'number' || typeof node.value === 'boolean') return node.value;
3805
+ return undefined;
3806
+ }
3807
+
3808
+ function spanFromLoc(loc, input) {
3809
+ if (!loc?.start) return undefined;
3810
+ return {
3811
+ sourceId: input.sourceHash,
3812
+ path: input.sourcePath ?? loc.filename,
3813
+ startLine: loc.start.line,
3814
+ startColumn: typeof loc.start.column === 'number' ? loc.start.column + 1 : undefined,
3815
+ endLine: loc.end?.line,
3816
+ endColumn: typeof loc.end?.column === 'number' ? loc.end.column + 1 : undefined
3817
+ };
3818
+ }
3819
+
3820
+ function syntaxDeclaration(node, nativeNodeId, input) {
3821
+ const kind = String(node.type ?? node.kind ?? '');
3822
+ if (kind === 'ImportDeclaration') {
3823
+ const name = node.source?.value;
3824
+ if (typeof name === 'string') return declarationRecord(input, nativeNodeId, name, 'module', 'import');
3825
+ }
3826
+ if (kind === 'ExportNamedDeclaration' || kind === 'ExportAllDeclaration') {
3827
+ const name = node.source?.value;
3828
+ if (typeof name === 'string') return declarationRecord(input, nativeNodeId, name, 'module', 'export');
3829
+ }
3830
+ if (kind === 'FunctionDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'function');
3831
+ if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'class');
3832
+ if (kind === 'TSInterfaceDeclaration' || kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'interface');
3833
+ if (kind === 'TSTypeAliasDeclaration' || kind === 'TypeAliasDeclaration') return namedDeclaration(input, nativeNodeId, node.id, 'type');
3834
+ if (kind === 'VariableDeclarator') return namedDeclaration(input, nativeNodeId, node.id, 'variable');
3835
+ return undefined;
3836
+ }
3837
+
3838
+ function typeScriptDeclaration(node, kind, nativeNodeId, input) {
3839
+ if (kind === 'ImportDeclaration' || kind === 'ImportEqualsDeclaration') {
3840
+ const name = stringFromTsExpression(node.moduleSpecifier) ?? stringFromTsExpression(node.externalModuleReference?.expression);
3841
+ if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
3842
+ }
3843
+ if (kind === 'FunctionDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'function');
3844
+ if (kind === 'ClassDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'class');
3845
+ if (kind === 'InterfaceDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'interface');
3846
+ if (kind === 'TypeAliasDeclaration' || kind === 'EnumDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'type');
3847
+ if (kind === 'VariableDeclaration') return namedDeclaration(input, nativeNodeId, node.name, 'variable');
3848
+ if (kind === 'MethodDeclaration' || kind === 'MethodSignature') return namedDeclaration(input, nativeNodeId, node.name, 'method');
3849
+ return undefined;
3850
+ }
3851
+
3852
+ function treeSitterDeclaration(node, kind, nativeNodeId, input) {
3853
+ if (/import|include|use/.test(kind)) {
3854
+ const name = treeSitterFieldText(node, 'path') ?? treeSitterFieldText(node, 'source') ?? shortNodeText(node);
3855
+ if (name) return declarationRecord(input, nativeNodeId, name, 'module', 'import');
3856
+ }
3857
+ if (/function|method|fn_item|function_declaration/.test(kind)) {
3858
+ const name = treeSitterFieldText(node, 'name');
3859
+ if (name) return declarationRecord(input, nativeNodeId, name, 'function', 'definition');
3860
+ }
3861
+ if (/class/.test(kind)) {
3862
+ const name = treeSitterFieldText(node, 'name');
3863
+ if (name) return declarationRecord(input, nativeNodeId, name, 'class', 'definition');
3864
+ }
3865
+ if (/interface/.test(kind)) {
3866
+ const name = treeSitterFieldText(node, 'name');
3867
+ if (name) return declarationRecord(input, nativeNodeId, name, 'interface', 'definition');
3868
+ }
3869
+ if (/struct|enum|type/.test(kind)) {
3870
+ const name = treeSitterFieldText(node, 'name');
3871
+ if (name) return declarationRecord(input, nativeNodeId, name, 'type', 'definition');
3872
+ }
3873
+ return undefined;
3874
+ }
3875
+
3876
+ function namedDeclaration(input, nativeNodeId, nameNode, symbolKind) {
3877
+ const name = identifierName(nameNode);
3878
+ return name ? declarationRecord(input, nativeNodeId, name, symbolKind, 'definition') : undefined;
3879
+ }
3880
+
3881
+ function declarationRecord(input, nativeNodeId, name, symbolKind, role = 'definition') {
3882
+ return {
3883
+ name: String(name),
3884
+ symbolKind,
3885
+ role,
3886
+ symbolId: `symbol:${input.language}:${role === 'import' ? 'import:' : ''}${idFragment(name)}`,
3887
+ nativeNodeId
3888
+ };
3889
+ }
3890
+
3891
+ function relationPredicateForDeclaration(declaration) {
3892
+ if (declaration.role === 'import') return 'imports';
3893
+ if (declaration.role === 'export') return 'exports';
3894
+ return 'defines';
3895
+ }
3896
+
3897
+ function identifierName(node) {
3898
+ if (!node) return undefined;
3899
+ if (typeof node === 'string') return node;
3900
+ if (typeof node.name === 'string') return node.name;
3901
+ if (typeof node.escapedText === 'string') return node.escapedText;
3902
+ if (typeof node.text === 'string') return node.text;
3903
+ if (node.type === 'Identifier' && typeof node.value === 'string') return node.value;
3904
+ return undefined;
3905
+ }
3906
+
3907
+ function stringFromTsExpression(node) {
3908
+ if (!node) return undefined;
3909
+ if (typeof node.text === 'string') return node.text;
3910
+ if (typeof node.value === 'string') return node.value;
3911
+ return identifierName(node);
3912
+ }
3913
+
3914
+ function typeScriptKindName(node, ts) {
3915
+ if (typeof node.kindName === 'string') return node.kindName;
3916
+ if (ts?.SyntaxKind && node.kind !== undefined) return ts.SyntaxKind[node.kind] ?? `SyntaxKind${node.kind}`;
3917
+ if (typeof node.kind === 'string') return node.kind;
3918
+ return `SyntaxKind${node.kind ?? 'Unknown'}`;
3919
+ }
3920
+
3921
+ function spanFromTypeScriptNode(node, sourceFile) {
3922
+ const start = typeof node.getStart === 'function' ? node.getStart(sourceFile) : node.pos;
3923
+ const end = typeof node.getEnd === 'function' ? node.getEnd() : node.end;
3924
+ if (typeof start !== 'number' || typeof sourceFile?.getLineAndCharacterOfPosition !== 'function') return undefined;
3925
+ const startPos = sourceFile.getLineAndCharacterOfPosition(start);
3926
+ const endPos = typeof end === 'number' ? sourceFile.getLineAndCharacterOfPosition(end) : undefined;
3927
+ return {
3928
+ sourceId: sourceFile.sourceHash,
3929
+ path: sourceFile.fileName,
3930
+ startLine: startPos.line + 1,
3931
+ startColumn: startPos.character + 1,
3932
+ endLine: endPos ? endPos.line + 1 : undefined,
3933
+ endColumn: endPos ? endPos.character + 1 : undefined
3934
+ };
3935
+ }
3936
+
3937
+ function typeScriptNodeValue(node) {
3938
+ return identifierName(node.name) ?? stringFromTsExpression(node.moduleSpecifier) ?? undefined;
3939
+ }
3940
+
3941
+ function primitiveTypeScriptFields(node, kind) {
3942
+ const fields = { kind };
3943
+ const name = identifierName(node.name);
3944
+ if (name) fields.name = name;
3945
+ const moduleSpecifier = stringFromTsExpression(node.moduleSpecifier);
3946
+ if (moduleSpecifier) fields.moduleSpecifier = moduleSpecifier;
3947
+ return fields;
3948
+ }
3949
+
3950
+ function spanFromTreeSitterNode(node, input) {
3951
+ const start = node.startPosition;
3952
+ if (!start) return undefined;
3953
+ const end = node.endPosition;
3954
+ return {
3955
+ sourceId: input.sourceHash,
3956
+ path: input.sourcePath,
3957
+ startLine: start.row + 1,
3958
+ startColumn: start.column + 1,
3959
+ endLine: end ? end.row + 1 : undefined,
3960
+ endColumn: end ? end.column + 1 : undefined
3961
+ };
3962
+ }
3963
+
3964
+ function treeSitterFieldText(node, field) {
3965
+ if (typeof node.childForFieldName !== 'function') return undefined;
3966
+ return shortNodeText(node.childForFieldName(field));
3967
+ }
3968
+
3969
+ function shortNodeText(node) {
3970
+ if (!node || typeof node.text !== 'string') return undefined;
3971
+ const text = node.text.trim();
3972
+ if (!text || text.length > 160) return undefined;
3973
+ return text.replace(/^['"]|['"]$/g, '');
3974
+ }
3975
+
3976
+ function truncatedAstLoss(input, context, options) {
3977
+ return {
3978
+ id: `loss_${idFragment(input.sourcePath ?? input.language)}_${idFragment(options.astFormat ?? options.parser)}_truncated`,
3979
+ severity: 'warning',
3980
+ phase: 'read',
3981
+ sourceFormat: input.language,
3982
+ kind: 'opaqueNative',
3983
+ message: `Native AST normalization stopped after ${context.maxNodes} node(s).`,
3984
+ metadata: {
3985
+ parser: options.parser,
3986
+ astFormat: options.astFormat,
3987
+ maxNodes: context.maxNodes
3988
+ }
3989
+ };
3990
+ }
3991
+
3992
+ function uniqueStrings(values) {
3993
+ return [...new Set((values ?? []).map((value) => String(value)).filter(Boolean))];
3994
+ }
3995
+
3996
+ function uniqueByLossId(values) {
3997
+ const seen = new Set();
3998
+ const result = [];
3999
+ for (const value of values ?? []) {
4000
+ const id = value?.id ?? `loss_${result.length + 1}`;
4001
+ if (seen.has(id)) continue;
4002
+ seen.add(id);
4003
+ result.push(value.id ? value : { ...value, id });
4004
+ }
4005
+ return result;
4006
+ }
4007
+
4008
+ function uniqueByEvidenceId(values) {
4009
+ const seen = new Set();
4010
+ const result = [];
4011
+ for (const value of values ?? []) {
4012
+ const id = value?.id ?? `evidence_${result.length + 1}`;
4013
+ if (seen.has(id)) continue;
4014
+ seen.add(id);
4015
+ result.push(value.id ? value : { ...value, id });
4016
+ }
4017
+ return result;
4018
+ }
4019
+
4020
+ function numberOrUndefined(value) {
4021
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
4022
+ }
4023
+
1204
4024
  function idFragment(value) {
1205
4025
  return String(value ?? 'native')
1206
4026
  .toLowerCase()