@shapeshift-labs/frontier-lang-compiler 0.2.17 → 0.2.18

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/README.md CHANGED
@@ -147,6 +147,25 @@ console.log(sidecar.ownershipRegions[0].key); // source#src/runtime.ts#class#Run
147
147
  console.log(sidecar.patchHints[0].supportedOperations); // source-region patch operations
148
148
  ```
149
149
 
150
+ Compare before/after native source imports from a worker patch and emit a semantic change set for admission scoring:
151
+
152
+ ```js
153
+ import { diffNativeSources } from '@shapeshift-labs/frontier-lang-compiler';
154
+
155
+ const changeSet = diffNativeSources({
156
+ language: 'javascript',
157
+ sourcePath: 'src/runtime.js',
158
+ beforeSourceText: 'export function step(frame) { return frame + 1; }\n',
159
+ afterSourceText: 'export function step(frame) { return frame + 2; }\n'
160
+ });
161
+
162
+ console.log(changeSet.changedSymbols[0]?.changeKind); // "modified"
163
+ console.log(changeSet.changedRegions[0]?.conflictKey); // semantic ownership key
164
+ console.log(changeSet.mergeCandidate.readiness); // merge-admission classification
165
+ ```
166
+
167
+ Use `diffNativeSourceImports` when the worker or runner already produced `importNativeSource` results. Body-only edits that the lightweight scanner cannot anchor to a symbol are still reported as file-level changed regions instead of being silently treated as safe.
168
+
150
169
  Project a native import back to source. Exact source is preserved when the import carries matching source-preservation evidence or when supplied text matches the import hash; otherwise the compiler emits declaration stubs with review-required loss evidence:
151
170
 
152
171
  ```js
package/dist/index.d.ts CHANGED
@@ -730,6 +730,100 @@ export interface SemanticImportSidecarOptions {
730
730
  readonly metadata?: Record<string, unknown>;
731
731
  }
732
732
 
733
+ export type NativeSourceChangeKind = 'added' | 'removed' | 'modified' | 'unchanged';
734
+
735
+ export interface NativeSourceChangeSymbol {
736
+ readonly changeKind: NativeSourceChangeKind;
737
+ readonly key: string;
738
+ readonly id?: string;
739
+ readonly name?: string;
740
+ readonly kind?: string;
741
+ readonly language?: FrontierSourceLanguage | string;
742
+ readonly nativeAstNodeId?: string;
743
+ readonly semanticOccurrenceId?: string;
744
+ readonly sourceMapMappingId?: string;
745
+ readonly sourceSpan?: SourceSpan;
746
+ readonly beforeSignatureHash?: string;
747
+ readonly afterSignatureHash?: string;
748
+ readonly beforeSpanHash?: string;
749
+ readonly afterSpanHash?: string;
750
+ readonly beforeOwnershipKey?: string;
751
+ readonly afterOwnershipKey?: string;
752
+ readonly ownershipRegionId?: string;
753
+ readonly ownershipKey?: string;
754
+ readonly ownershipRegionKind?: NativeImportRegionTaxonomyKind;
755
+ readonly conflictKey: string;
756
+ readonly readiness: SemanticMergeReadiness;
757
+ }
758
+
759
+ export interface NativeSourceChangeRegion extends SemanticImportOwnershipRegion {
760
+ readonly changeKind: NativeSourceChangeKind;
761
+ readonly conflictKey: string;
762
+ }
763
+
764
+ export interface NativeSourceChangeSummary {
765
+ readonly sourceChanged: boolean;
766
+ readonly symbols: number;
767
+ readonly regions: number;
768
+ readonly addedSymbols: number;
769
+ readonly removedSymbols: number;
770
+ readonly modifiedSymbols: number;
771
+ readonly byRegionKind: Readonly<Record<string, number>>;
772
+ readonly byChangeKind: Readonly<Record<string, number>>;
773
+ }
774
+
775
+ export interface DiffNativeSourceImportsOptions {
776
+ readonly id?: string;
777
+ readonly language?: FrontierSourceLanguage | string;
778
+ readonly sourcePath?: string;
779
+ readonly parser?: string;
780
+ readonly before?: NativeSourceImportResult | ImportNativeSourceOptions;
781
+ readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
782
+ readonly beforeSourceHash?: string;
783
+ readonly afterSourceHash?: string;
784
+ readonly generatedAt?: number;
785
+ readonly regionPrefix?: string;
786
+ readonly evidenceId?: string;
787
+ readonly evidenceStatus?: EvidenceRecord['status'];
788
+ readonly patchId?: string;
789
+ readonly mergeCandidateId?: string;
790
+ readonly author?: string;
791
+ readonly metadata?: Record<string, unknown>;
792
+ }
793
+
794
+ export interface DiffNativeSourcesOptions extends Omit<DiffNativeSourceImportsOptions, 'before' | 'after'> {
795
+ readonly before?: NativeSourceImportResult | ImportNativeSourceOptions;
796
+ readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
797
+ readonly beforeSourceText?: string;
798
+ readonly afterSourceText?: string;
799
+ readonly beforeMetadata?: Record<string, unknown>;
800
+ readonly afterMetadata?: Record<string, unknown>;
801
+ }
802
+
803
+ export interface NativeSourceChangeSet {
804
+ readonly kind: 'frontier.lang.nativeSourceChangeSet';
805
+ readonly version: 1;
806
+ readonly id: string;
807
+ readonly language?: FrontierSourceLanguage | string;
808
+ readonly sourcePath?: string;
809
+ readonly before?: NativeSourceImportResult;
810
+ readonly after?: NativeSourceImportResult;
811
+ readonly beforeHash?: string;
812
+ readonly afterHash?: string;
813
+ readonly changedSymbols: readonly NativeSourceChangeSymbol[];
814
+ readonly changedRegions: readonly NativeSourceChangeRegion[];
815
+ readonly patch: SemanticPatchBundle;
816
+ readonly mergeCandidate: SemanticMergeCandidateRecord;
817
+ readonly evidence: readonly EvidenceRecord[];
818
+ readonly readiness: SemanticMergeReadiness;
819
+ readonly reasons: readonly string[];
820
+ readonly sourceMaps: readonly SourceMapRecord[];
821
+ readonly semanticIndex?: SemanticIndexRecord;
822
+ readonly losses: readonly NativeAstLossRecord[];
823
+ readonly summary: NativeSourceChangeSummary;
824
+ readonly metadata?: Record<string, unknown>;
825
+ }
826
+
733
827
  export type NativeImporterAdapterExactness =
734
828
  | 'exact-parser-ast'
735
829
  | 'parser-tree'
@@ -1212,6 +1306,8 @@ export declare function createTreeSitterNativeImporterAdapter(options?: TreeSitt
1212
1306
  export declare function runNativeImporterAdapter(adapter: NativeImporterAdapter, input: RunNativeImporterAdapterOptions): Promise<NativeImporterAdapterImportResult>;
1213
1307
  export declare function projectNativeImportToSource(importResult: NativeSourceImportResult | NativeProjectImportResult, options?: ProjectNativeImportToSourceOptions): NativeSourceProjectionResult;
1214
1308
  export declare function importNativeSource(input: ImportNativeSourceOptions): NativeSourceImportResult;
1309
+ export declare function diffNativeSources(input: DiffNativeSourcesOptions): NativeSourceChangeSet;
1310
+ export declare function diffNativeSourceImports(input: DiffNativeSourceImportsOptions): NativeSourceChangeSet;
1215
1311
  export declare function importNativeProject(input: ImportNativeProjectOptions): Promise<NativeProjectImportResult>;
1216
1312
  export declare function createUniversalAstFromDocument(document: FrontierLangDocument, input?: {
1217
1313
  readonly id?: string;
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  createNativeAstRecord,
5
5
  createPatch,
6
6
  createSemanticIndexRecord,
7
+ createSemanticMergeCandidateRecord,
7
8
  createSourceMapRecord,
8
9
  createUniversalAstEnvelope,
9
10
  hashDocumentBase,
@@ -1416,6 +1417,410 @@ export function importNativeSource(input) {
1416
1417
  };
1417
1418
  }
1418
1419
 
1420
+ export function diffNativeSources(input) {
1421
+ const language = input.language ?? input.before?.language ?? input.after?.language;
1422
+ if (!language) throw new Error('diffNativeSources requires a language');
1423
+ const sourcePath = input.sourcePath ?? input.before?.sourcePath ?? input.after?.sourcePath;
1424
+ return diffNativeSourceImports({
1425
+ ...input,
1426
+ before: input.before ?? (input.beforeSourceText === undefined ? undefined : {
1427
+ language,
1428
+ sourcePath,
1429
+ sourceText: input.beforeSourceText,
1430
+ sourceHash: input.beforeSourceHash,
1431
+ parser: input.parser,
1432
+ metadata: input.beforeMetadata
1433
+ }),
1434
+ after: input.after ?? (input.afterSourceText === undefined ? undefined : {
1435
+ language,
1436
+ sourcePath,
1437
+ sourceText: input.afterSourceText,
1438
+ sourceHash: input.afterSourceHash,
1439
+ parser: input.parser,
1440
+ metadata: input.afterMetadata
1441
+ })
1442
+ });
1443
+ }
1444
+
1445
+ export function diffNativeSourceImports(input) {
1446
+ const before = normalizeNativeDiffImport(input.before, input, 'before');
1447
+ const after = normalizeNativeDiffImport(input.after, input, 'after');
1448
+ if (!before && !after) throw new Error('diffNativeSourceImports requires before or after native source input');
1449
+ const language = input.language ?? after?.language ?? before?.language;
1450
+ const sourcePath = input.sourcePath ?? after?.sourcePath ?? before?.sourcePath;
1451
+ const beforeHash = before?.nativeSource?.sourceHash ?? before?.nativeAst?.sourceHash ?? before?.sourceHash;
1452
+ const afterHash = after?.nativeSource?.sourceHash ?? after?.nativeAst?.sourceHash ?? after?.sourceHash;
1453
+ const idPart = idFragment(input.id ?? sourcePath ?? language ?? 'native_source_change');
1454
+ const beforeSidecar = before ? createSemanticImportSidecar(before, { id: `sidecar_before_${idPart}`, generatedAt: input.generatedAt, regionPrefix: input.regionPrefix }) : undefined;
1455
+ const afterSidecar = after ? createSemanticImportSidecar(after, { id: `sidecar_after_${idPart}`, generatedAt: input.generatedAt, regionPrefix: input.regionPrefix }) : undefined;
1456
+ const beforeSymbols = mapDiffSymbols(before, beforeSidecar);
1457
+ const afterSymbols = mapDiffSymbols(after, afterSidecar);
1458
+ const changedSymbols = diffNativeSymbols(beforeSymbols, afterSymbols);
1459
+ let changedRegions = diffNativeOwnershipRegions(beforeSidecar, afterSidecar, changedSymbols);
1460
+ const sourceChanged = Boolean(beforeHash && afterHash && beforeHash !== afterHash);
1461
+ if (sourceChanged && changedSymbols.length === 0 && changedRegions.length === 0) {
1462
+ changedRegions = [fileLevelNativeChangeRegion({ language, sourcePath, beforeHash, afterHash, before, after })];
1463
+ }
1464
+ const evidence = [{
1465
+ id: input.evidenceId ?? `evidence_${idPart}_native_source_diff`,
1466
+ kind: 'import',
1467
+ status: input.evidenceStatus ?? 'passed',
1468
+ path: sourcePath,
1469
+ summary: `Compared ${language ?? 'native'} source imports: ${changedSymbols.length} changed symbol(s), ${changedRegions.length} changed region(s).`,
1470
+ metadata: {
1471
+ beforeImportId: before?.id,
1472
+ afterImportId: after?.id,
1473
+ beforeHash,
1474
+ afterHash,
1475
+ sourceChanged,
1476
+ addedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'added').length,
1477
+ removedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'removed').length,
1478
+ modifiedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'modified').length
1479
+ }
1480
+ }];
1481
+ const readiness = maxSemanticMergeReadiness(
1482
+ maxSemanticMergeReadiness(nativeImportReadiness(before), nativeImportReadiness(after)),
1483
+ sourceChanged && changedSymbols.length === 0 ? 'needs-review' : 'ready'
1484
+ );
1485
+ const reasons = nativeSourceChangeReasons({ before, after, beforeHash, afterHash, changedSymbols, changedRegions, readiness });
1486
+ const conflictKeys = uniqueStrings([
1487
+ ...changedSymbols.map((symbol) => symbol.conflictKey),
1488
+ ...changedRegions.map((region) => region.conflictKey ?? region.key ?? region.id),
1489
+ ...(sourcePath ? [`source:${sourcePath}`] : [])
1490
+ ]);
1491
+ const touches = changedRegions.map((region) => ({ id: region.id, access: 'write' }));
1492
+ const operations = [
1493
+ ...(after?.nativeSource ? [{ op: 'upsertNode', node: after.nativeSource, touches: touches.length ? touches : [{ id: after.nativeSource.id, access: 'evidence' }] }] : []),
1494
+ ...(!after && before?.nativeSource ? [{ op: 'removeNode', id: before.nativeSource.id, touches: touches.length ? touches : [{ id: before.nativeSource.id, access: 'evidence' }] }] : []),
1495
+ { op: 'addEvidence', evidence: evidence[0], touches }
1496
+ ];
1497
+ const patch = createPatch({
1498
+ id: input.patchId ?? `patch_${idPart}_native_source_diff`,
1499
+ baseHash: beforeHash,
1500
+ targetHash: afterHash,
1501
+ author: input.author ?? '@shapeshift-labs/frontier-lang-compiler/diffNativeSourceImports',
1502
+ risk: readiness === 'blocked' ? 'high' : readiness === 'needs-review' ? 'medium' : 'low',
1503
+ operations,
1504
+ evidence,
1505
+ metadata: {
1506
+ sourceLanguage: language,
1507
+ sourcePath,
1508
+ beforeImportId: before?.id,
1509
+ afterImportId: after?.id,
1510
+ beforeHash,
1511
+ afterHash,
1512
+ changedSymbols: changedSymbols.length,
1513
+ changedRegions: changedRegions.length
1514
+ }
1515
+ });
1516
+ const mergeCandidate = createSemanticMergeCandidateRecord({
1517
+ id: input.mergeCandidateId ?? `merge_candidate_${idPart}_native_source_diff`,
1518
+ importResultId: after?.id ?? before?.id,
1519
+ patchId: patch.id,
1520
+ language,
1521
+ sourcePath,
1522
+ baseHash: beforeHash,
1523
+ targetHash: afterHash,
1524
+ touchedSymbols: changedSymbols.map(nativeChangeTouchedSymbol),
1525
+ touchedSemanticNodes: [],
1526
+ nativeSpans: nativeChangeSpans(changedSymbols, changedRegions, { language, sourcePath }),
1527
+ conflictKeys,
1528
+ readiness,
1529
+ reasons,
1530
+ evidence,
1531
+ metadata: {
1532
+ kind: 'native-source-change-set',
1533
+ beforeImportId: before?.id,
1534
+ afterImportId: after?.id,
1535
+ sourceChanged,
1536
+ changeSummary: nativeSourceChangeSummary(changedSymbols, changedRegions, sourceChanged)
1537
+ }
1538
+ });
1539
+ return {
1540
+ kind: 'frontier.lang.nativeSourceChangeSet',
1541
+ version: 1,
1542
+ id: input.id ?? `native_source_change_${idPart}`,
1543
+ language,
1544
+ sourcePath,
1545
+ before,
1546
+ after,
1547
+ beforeHash,
1548
+ afterHash,
1549
+ changedSymbols,
1550
+ changedRegions,
1551
+ patch,
1552
+ mergeCandidate,
1553
+ evidence,
1554
+ readiness,
1555
+ reasons,
1556
+ sourceMaps: uniqueRecordsById([...(before?.sourceMaps ?? []), ...(after?.sourceMaps ?? [])]),
1557
+ semanticIndex: after?.semanticIndex ?? before?.semanticIndex,
1558
+ losses: uniqueByLossId([...(before?.losses ?? []), ...(after?.losses ?? [])]),
1559
+ summary: nativeSourceChangeSummary(changedSymbols, changedRegions, sourceChanged),
1560
+ metadata: {
1561
+ beforeSidecarId: beforeSidecar?.id,
1562
+ afterSidecarId: afterSidecar?.id,
1563
+ beforeImportContract: before?.metadata?.importResultContract,
1564
+ afterImportContract: after?.metadata?.importResultContract,
1565
+ ...input.metadata
1566
+ }
1567
+ };
1568
+ }
1569
+
1570
+ function normalizeNativeDiffImport(value, input, side) {
1571
+ if (!value) return undefined;
1572
+ if (value.kind === 'frontier.lang.importResult' && value.nativeSource) return value;
1573
+ return importNativeSource({
1574
+ ...value,
1575
+ language: value.language ?? input.language,
1576
+ sourcePath: value.sourcePath ?? input.sourcePath,
1577
+ parser: value.parser ?? input.parser,
1578
+ metadata: {
1579
+ ...(value.metadata ?? {}),
1580
+ nativeSourceDiffSide: side
1581
+ }
1582
+ });
1583
+ }
1584
+
1585
+ function mapDiffSymbols(imported, sidecar) {
1586
+ const symbolsById = new Map((imported?.semanticIndex?.symbols ?? []).map((symbol) => [symbol.id, symbol]));
1587
+ const mappingsBySymbolId = new Map((imported?.sourceMaps ?? [])
1588
+ .flatMap((sourceMap) => sourceMap.mappings ?? [])
1589
+ .filter((mapping) => mapping.semanticSymbolId)
1590
+ .map((mapping) => [mapping.semanticSymbolId, mapping]));
1591
+ const sourceText = nativeImportSourceText(imported);
1592
+ const entries = sidecar?.symbols?.length
1593
+ ? sidecar.symbols
1594
+ : (imported?.semanticIndex?.symbols ?? []).map((symbol) => ({ id: symbol.id, name: symbol.name, kind: symbol.kind, sourceSpan: symbol.definitionSpan }));
1595
+ const result = new Map();
1596
+ for (const entry of entries) {
1597
+ const symbol = symbolsById.get(entry.id) ?? {};
1598
+ const mapping = mappingsBySymbolId.get(entry.id);
1599
+ const sourceSpan = entry.sourceSpan ?? mapping?.sourceSpan ?? symbol.definitionSpan;
1600
+ const key = nativeDiffSymbolKey(entry, imported);
1601
+ result.set(key, {
1602
+ key,
1603
+ id: entry.id,
1604
+ name: entry.name ?? symbol.name,
1605
+ kind: entry.kind ?? symbol.kind,
1606
+ language: entry.language ?? symbol.language ?? imported?.language,
1607
+ nativeAstNodeId: entry.nativeAstNodeId ?? symbol.nativeAstNodeId ?? mapping?.nativeAstNodeId,
1608
+ semanticOccurrenceId: entry.semanticOccurrenceId ?? mapping?.semanticOccurrenceId,
1609
+ sourceMapMappingId: entry.sourceMapMappingId ?? mapping?.id,
1610
+ sourceSpan,
1611
+ signatureHash: entry.signatureHash ?? symbol.signatureHash,
1612
+ ownershipRegionId: entry.ownershipRegionId ?? symbol.metadata?.ownershipRegionId ?? mapping?.ownershipRegionId,
1613
+ ownershipKey: entry.ownershipKey ?? symbol.metadata?.ownershipRegionKey ?? mapping?.ownershipRegionKey,
1614
+ ownershipRegionKind: entry.ownershipRegionKind ?? symbol.metadata?.ownershipRegionKind ?? mapping?.ownershipRegionKind,
1615
+ spanHash: hashNativeSpanText(sourceText, sourceSpan),
1616
+ sourcePath: sourceSpan?.path ?? imported?.sourcePath,
1617
+ sourceHash: imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash,
1618
+ readiness: entry.readiness ?? imported?.metadata?.semanticMergeReadiness ?? imported?.mergeCandidates?.[0]?.readiness ?? 'needs-review'
1619
+ });
1620
+ }
1621
+ return result;
1622
+ }
1623
+
1624
+ function diffNativeSymbols(beforeSymbols, afterSymbols) {
1625
+ const keys = uniqueStrings([...beforeSymbols.keys(), ...afterSymbols.keys()]).sort();
1626
+ const changed = [];
1627
+ for (const key of keys) {
1628
+ const before = beforeSymbols.get(key);
1629
+ const after = afterSymbols.get(key);
1630
+ const changeKind = !before ? 'added' : !after ? 'removed' : nativeDiffSymbolChanged(before, after) ? 'modified' : 'unchanged';
1631
+ if (changeKind === 'unchanged') continue;
1632
+ const current = after ?? before;
1633
+ changed.push({
1634
+ changeKind,
1635
+ key,
1636
+ id: current.id,
1637
+ name: current.name,
1638
+ kind: current.kind,
1639
+ language: current.language,
1640
+ nativeAstNodeId: current.nativeAstNodeId,
1641
+ semanticOccurrenceId: current.semanticOccurrenceId,
1642
+ sourceMapMappingId: current.sourceMapMappingId,
1643
+ sourceSpan: current.sourceSpan,
1644
+ beforeSignatureHash: before?.signatureHash,
1645
+ afterSignatureHash: after?.signatureHash,
1646
+ beforeSpanHash: before?.spanHash,
1647
+ afterSpanHash: after?.spanHash,
1648
+ beforeOwnershipKey: before?.ownershipKey,
1649
+ afterOwnershipKey: after?.ownershipKey,
1650
+ ownershipRegionId: current.ownershipRegionId,
1651
+ ownershipKey: current.ownershipKey,
1652
+ ownershipRegionKind: current.ownershipRegionKind,
1653
+ conflictKey: current.ownershipKey ? `region:${current.ownershipKey}` : `symbol:${current.id ?? key}`,
1654
+ readiness: maxSemanticMergeReadiness(before?.readiness ?? 'ready', after?.readiness ?? 'ready')
1655
+ });
1656
+ }
1657
+ return changed;
1658
+ }
1659
+
1660
+ function nativeDiffSymbolChanged(before, after) {
1661
+ if ((before.signatureHash ?? '') !== (after.signatureHash ?? '')) return true;
1662
+ if ((before.spanHash ?? '') !== (after.spanHash ?? '')) return true;
1663
+ if ((before.ownershipKey ?? '') !== (after.ownershipKey ?? '')) return true;
1664
+ if ((before.nativeAstNodeId ?? '') !== (after.nativeAstNodeId ?? '')) return true;
1665
+ return false;
1666
+ }
1667
+
1668
+ function diffNativeOwnershipRegions(beforeSidecar, afterSidecar, changedSymbols) {
1669
+ const changedRegionIds = new Set(changedSymbols.map((symbol) => symbol.ownershipRegionId).filter(Boolean));
1670
+ const changedRegionKeys = new Set([
1671
+ ...changedSymbols.map((symbol) => symbol.beforeOwnershipKey).filter(Boolean),
1672
+ ...changedSymbols.map((symbol) => symbol.afterOwnershipKey).filter(Boolean)
1673
+ ]);
1674
+ const regions = uniqueRecordsById([...(beforeSidecar?.ownershipRegions ?? []), ...(afterSidecar?.ownershipRegions ?? [])])
1675
+ .filter((region) => changedRegionIds.has(region.id) || changedRegionKeys.has(region.key));
1676
+ return regions.map((region) => ({
1677
+ ...region,
1678
+ changeKind: changedSymbols.some((symbol) => symbol.changeKind === 'added' && symbol.afterOwnershipKey === region.key)
1679
+ ? 'added'
1680
+ : changedSymbols.some((symbol) => symbol.changeKind === 'removed' && symbol.beforeOwnershipKey === region.key)
1681
+ ? 'removed'
1682
+ : 'modified',
1683
+ conflictKey: region.key ? `region:${region.key}` : `region:${region.id}`
1684
+ }));
1685
+ }
1686
+
1687
+ function fileLevelNativeChangeRegion(input) {
1688
+ const sourcePath = input.sourcePath ?? input.after?.sourcePath ?? input.before?.sourcePath;
1689
+ const key = ['source', sourcePath ?? `${input.language}:memory`, 'file'].join('#');
1690
+ return {
1691
+ id: `region_${idFragment(key)}`,
1692
+ key,
1693
+ changeKind: 'modified',
1694
+ regionKind: 'body',
1695
+ granularity: 'file',
1696
+ language: input.language,
1697
+ sourcePath,
1698
+ sourceHash: input.afterHash ?? input.beforeHash,
1699
+ precision: 'unknown',
1700
+ mergePolicy: 'file-level-review-required',
1701
+ conflictKey: `region:${key}`,
1702
+ metadata: {
1703
+ reason: 'source-hash-changed-without-symbol-diff',
1704
+ beforeHash: input.beforeHash,
1705
+ afterHash: input.afterHash
1706
+ }
1707
+ };
1708
+ }
1709
+
1710
+ function nativeImportReadiness(imported) {
1711
+ return imported?.metadata?.semanticMergeReadiness
1712
+ ?? imported?.metadata?.nativeImportLossSummary?.semanticMergeReadiness
1713
+ ?? imported?.mergeCandidates?.[0]?.readiness
1714
+ ?? 'ready';
1715
+ }
1716
+
1717
+ function nativeSourceChangeReasons(input) {
1718
+ if (!input.before) return ['Native source was added.'];
1719
+ if (!input.after) return ['Native source was removed.'];
1720
+ if (input.changedSymbols.length) {
1721
+ return [`Native source changed ${input.changedSymbols.length} symbol(s) across ${input.changedRegions.length} ownership region(s).`];
1722
+ }
1723
+ if (input.beforeHash && input.afterHash && input.beforeHash !== input.afterHash) {
1724
+ return ['Native source hash changed without declaration-level symbol changes; file-level review is required.'];
1725
+ }
1726
+ return ['Native source imports are semantically unchanged at available scanner precision.'];
1727
+ }
1728
+
1729
+ function nativeChangeTouchedSymbol(symbol) {
1730
+ return {
1731
+ id: symbol.id ?? symbol.key,
1732
+ name: symbol.name,
1733
+ kind: symbol.kind,
1734
+ nativeAstNodeId: symbol.nativeAstNodeId,
1735
+ span: symbol.sourceSpan,
1736
+ conflictKey: symbol.conflictKey,
1737
+ metadata: {
1738
+ changeKind: symbol.changeKind,
1739
+ beforeSignatureHash: symbol.beforeSignatureHash,
1740
+ afterSignatureHash: symbol.afterSignatureHash,
1741
+ beforeSpanHash: symbol.beforeSpanHash,
1742
+ afterSpanHash: symbol.afterSpanHash,
1743
+ ownershipRegionId: symbol.ownershipRegionId,
1744
+ ownershipRegionKind: symbol.ownershipRegionKind
1745
+ }
1746
+ };
1747
+ }
1748
+
1749
+ function nativeChangeSpans(changedSymbols, changedRegions, input) {
1750
+ const symbolSpans = changedSymbols
1751
+ .filter((symbol) => symbol.sourceSpan)
1752
+ .map((symbol) => ({
1753
+ id: `native_span_${idFragment(symbol.id ?? symbol.key)}`,
1754
+ sourceId: symbol.sourceSpan?.sourceId,
1755
+ path: symbol.sourceSpan?.path ?? input.sourcePath,
1756
+ language: symbol.language ?? input.language,
1757
+ nativeAstNodeId: symbol.nativeAstNodeId,
1758
+ symbolId: symbol.id,
1759
+ span: symbol.sourceSpan,
1760
+ conflictKey: symbol.conflictKey,
1761
+ metadata: { changeKind: symbol.changeKind }
1762
+ }));
1763
+ const regionSpans = changedRegions
1764
+ .filter((region) => region.sourceSpan || region.sourcePath)
1765
+ .map((region) => ({
1766
+ id: `native_span_${idFragment(region.id)}`,
1767
+ path: region.sourceSpan?.path ?? region.sourcePath ?? input.sourcePath,
1768
+ language: region.language ?? input.language,
1769
+ nativeAstNodeId: region.nativeAstNodeId,
1770
+ symbolId: region.symbolId,
1771
+ span: region.sourceSpan,
1772
+ conflictKey: region.conflictKey ?? `region:${region.key ?? region.id}`,
1773
+ metadata: { changeKind: region.changeKind, regionKind: region.regionKind, granularity: region.granularity }
1774
+ }));
1775
+ return uniqueRecordsById([...symbolSpans, ...regionSpans]);
1776
+ }
1777
+
1778
+ function nativeSourceChangeSummary(changedSymbols, changedRegions, sourceChanged) {
1779
+ return {
1780
+ sourceChanged,
1781
+ symbols: changedSymbols.length,
1782
+ regions: changedRegions.length,
1783
+ addedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'added').length,
1784
+ removedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'removed').length,
1785
+ modifiedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'modified').length,
1786
+ byRegionKind: countBy(changedRegions.map((region) => region.regionKind ?? 'unknown')),
1787
+ byChangeKind: countBy([...changedSymbols.map((symbol) => symbol.changeKind), ...changedRegions.map((region) => region.changeKind)])
1788
+ };
1789
+ }
1790
+
1791
+ function nativeDiffSymbolKey(symbol, imported) {
1792
+ return [
1793
+ symbol.language ?? imported?.language,
1794
+ symbol.kind ?? 'symbol',
1795
+ symbol.name ?? symbol.id
1796
+ ].map((part) => String(part ?? '').trim()).join(':');
1797
+ }
1798
+
1799
+ function nativeImportSourceText(imported) {
1800
+ return imported?.metadata?.sourcePreservation?.sourceText
1801
+ ?? imported?.nativeSource?.metadata?.sourcePreservation?.sourceText
1802
+ ?? imported?.nativeAst?.metadata?.sourcePreservation?.sourceText
1803
+ ?? imported?.universalAst?.metadata?.sourcePreservation?.sourceText;
1804
+ }
1805
+
1806
+ function hashNativeSpanText(sourceText, span) {
1807
+ const text = sourceTextForSpan(sourceText, span);
1808
+ return text === undefined ? undefined : hashSemanticValue(text);
1809
+ }
1810
+
1811
+ function sourceTextForSpan(sourceText, span) {
1812
+ if (typeof sourceText !== 'string' || !span) return undefined;
1813
+ if (typeof span.start === 'number' && typeof span.end === 'number' && span.end >= span.start) {
1814
+ return sourceText.slice(span.start, span.end);
1815
+ }
1816
+ if (typeof span.startLine === 'number') {
1817
+ const lines = sourceText.split(/\r?\n/);
1818
+ const endLine = typeof span.endLine === 'number' && span.endLine >= span.startLine ? span.endLine : span.startLine;
1819
+ return lines.slice(span.startLine - 1, endLine).join('\n');
1820
+ }
1821
+ return undefined;
1822
+ }
1823
+
1419
1824
  function createLightweightNativeImport(input) {
1420
1825
  const parser = input.parser ?? `${input.language}.lightweight-declaration-scan`;
1421
1826
  const rootId = 'native_root';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.17",
3
+ "version": "0.2.18",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",