@shapeshift-labs/frontier-lang-compiler 0.2.23 → 0.2.25

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
@@ -191,10 +191,30 @@ const imported = importNativeSource({
191
191
  const sidecar = createSemanticImportSidecar(imported);
192
192
 
193
193
  console.log(sidecar.summary.emptySemanticIndex); // false when symbols were found
194
- console.log(sidecar.ownershipRegions[0].key); // source#src/runtime.ts#class#Runtime
194
+ console.log(sidecar.ownershipRegions[0].key); // source#src/runtime.ts#type#Runtime
195
195
  console.log(sidecar.patchHints[0].supportedOperations); // source-region patch operations
196
196
  ```
197
197
 
198
+ The built-in JavaScript/TypeScript lightweight scanner also emits review-required ownership regions for clear route/config/content/property shapes in exported objects and arrays:
199
+
200
+ ```js
201
+ const importedConfig = importNativeSource({
202
+ language: 'typescript',
203
+ sourcePath: 'src/routes.ts',
204
+ sourceText: `
205
+ export const appRoutes = [
206
+ { path: "/home", component: Home }
207
+ ];
208
+ export const siteContent = {
209
+ docs: { title: "Docs" }
210
+ };
211
+ `
212
+ });
213
+
214
+ const configSidecar = createSemanticImportSidecar(importedConfig);
215
+ console.log(configSidecar.regionTaxonomy.presentKinds); // includes "route" and "content"
216
+ ```
217
+
198
218
  Compare before/after native source imports from a worker patch and emit a semantic change set for admission scoring:
199
219
 
200
220
  ```js
@@ -209,10 +229,12 @@ const changeSet = diffNativeSources({
209
229
 
210
230
  console.log(changeSet.changedSymbols[0]?.changeKind); // "modified"
211
231
  console.log(changeSet.changedRegions[0]?.conflictKey); // semantic ownership key
232
+ console.log(changeSet.changedRegions[0]?.metadata.changedRegionProjection.reviewRequired); // true
233
+ console.log(changeSet.metadata.changedRegionProjectionSummary.autoMergeClaims); // 0
212
234
  console.log(changeSet.mergeCandidate.readiness); // merge-admission classification
213
235
  ```
214
236
 
215
- 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.
237
+ Use `diffNativeSourceImports` when the worker or runner already produced `importNativeSource` results. Changed regions include a `metadata.changedRegionProjection` envelope with before/after source hashes, source-map links, ownership keys, readiness, and `autoMergeClaim: false` so swarm admission tools can score or port patches without treating semantic metadata as proof. 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.
216
238
 
217
239
  Compile native source imports through the same reader/IR/writer facade that swarms use for sidecar evidence. Same-language targets preserve exact source when hashes match; cross-language targets emit declaration stubs until a real adapter provides stronger evidence:
218
240
 
package/bench/smoke.mjs CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  createProjectionTargetLossMatrix,
8
8
  createNativeSourcePreservation,
9
9
  createSemanticImportSidecar,
10
+ diffNativeSources,
10
11
  importExternalSemanticIndex,
11
12
  importNativeSource,
12
13
  projectNativeImportToSource,
@@ -135,6 +136,50 @@ const nativeTargetAdapterDurationMs = performance.now() - nativeTargetAdapterSta
135
136
  const nativeTargetAdapterBytes = nativeTargetAdapterCompiles.reduce((sum, result) => sum + result.output.length, 0);
136
137
  const nativeTargetAdapterSourceMaps = nativeTargetAdapterCompiles.reduce((sum, result) => sum + result.sourceMaps.length, 0);
137
138
 
139
+ const regionScanStart = performance.now();
140
+ const regionScanImports = [];
141
+ for (let index = 0; index < 100; index += 1) {
142
+ const imported = importNativeSource({
143
+ language: 'typescript',
144
+ sourcePath: `src/regions-${index}.ts`,
145
+ sourceText: `
146
+ export const appRoutes${index} = [
147
+ { path: "/${index}", component: Screen${index} },
148
+ { path: "/${index}/settings", component: Settings${index} }
149
+ ];
150
+ export const contentBlocks${index} = {
151
+ docs: { title: "Docs ${index}" },
152
+ legal: { title: "Legal ${index}" }
153
+ };
154
+ export const runtimeConfig${index} = {
155
+ limits: { count: ${index} },
156
+ resolve(id) { return id; }
157
+ };
158
+ export const helpers${index} = {
159
+ plain: ${index}
160
+ };
161
+ `
162
+ });
163
+ regionScanImports.push({ imported, sidecar: createSemanticImportSidecar(imported) });
164
+ }
165
+ const regionScanDurationMs = performance.now() - regionScanStart;
166
+ const regionScanSymbols = regionScanImports.reduce((sum, entry) => sum + entry.imported.semanticIndex.symbols.length, 0);
167
+ const regionScanOwnershipRegions = regionScanImports.reduce((sum, entry) => sum + entry.sidecar.ownershipRegions.length, 0);
168
+
169
+ const changeProjectionStart = performance.now();
170
+ const changeProjectionSets = [];
171
+ for (let index = 0; index < 80; index += 1) {
172
+ changeProjectionSets.push(diffNativeSources({
173
+ language: 'javascript',
174
+ sourcePath: `src/change-projection-${index}.js`,
175
+ beforeSourceText: `export function changeProjection${index}() { return ${index}; }\n`,
176
+ afterSourceText: `export function changeProjection${index}() { return ${index + 1}; }\nexport const changeProjectionFlag${index} = true;\n`
177
+ }));
178
+ }
179
+ const changeProjectionDurationMs = performance.now() - changeProjectionStart;
180
+ const changedRegionProjections = changeProjectionSets.reduce((sum, changeSet) => sum + changeSet.metadata.changedRegionProjectionSummary.withProjection, 0);
181
+ const changedRegionProjectionSourceMapLinks = changeProjectionSets.reduce((sum, changeSet) => sum + changeSet.metadata.changedRegionProjectionSummary.sourceMapLinks, 0);
182
+
138
183
  const externalSemanticStart = performance.now();
139
184
  const externalSemanticImports = [];
140
185
  for (let index = 0; index < 100; index += 1) {
@@ -208,6 +253,14 @@ console.log(JSON.stringify({
208
253
  nativeTargetAdapterBytes,
209
254
  nativeTargetAdapterSourceMaps,
210
255
  nativeTargetAdapterDurationMs: Number(nativeTargetAdapterDurationMs.toFixed(2)),
256
+ regionScanImports: regionScanImports.length,
257
+ regionScanSymbols,
258
+ regionScanOwnershipRegions,
259
+ regionScanDurationMs: Number(regionScanDurationMs.toFixed(2)),
260
+ changeProjectionSets: changeProjectionSets.length,
261
+ changedRegionProjections,
262
+ changedRegionProjectionSourceMapLinks,
263
+ changeProjectionDurationMs: Number(changeProjectionDurationMs.toFixed(2)),
211
264
  externalSemanticImports: externalSemanticImports.length,
212
265
  externalSemanticSymbols,
213
266
  externalSemanticMappings,
package/dist/index.d.ts CHANGED
@@ -140,6 +140,10 @@ export type NativeImportRegionTaxonomyKind =
140
140
  | 'call'
141
141
  | 'type'
142
142
  | 'effect'
143
+ | 'property'
144
+ | 'config'
145
+ | 'content'
146
+ | 'route'
143
147
  | 'generatedOutput'
144
148
  | string;
145
149
 
@@ -782,6 +786,91 @@ export interface SemanticImportSidecarOptions {
782
786
 
783
787
  export type NativeSourceChangeKind = 'added' | 'removed' | 'modified' | 'unchanged';
784
788
 
789
+ export interface NativeSourceChangeProjectionEndpoint {
790
+ readonly side: 'before' | 'after';
791
+ readonly importId?: string;
792
+ readonly sidecarId?: string;
793
+ readonly nativeSourceId?: string;
794
+ readonly nativeAstId?: string;
795
+ readonly semanticIndexId?: string;
796
+ readonly universalAstId?: string;
797
+ readonly sourcePath?: string;
798
+ readonly sourceHash?: string;
799
+ readonly sourcePreservationId?: string;
800
+ readonly exactSourceAvailable: boolean;
801
+ readonly ownershipRegionId?: string;
802
+ readonly ownershipKey?: string;
803
+ readonly ownershipRegionKind?: NativeImportRegionTaxonomyKind;
804
+ readonly sourceSpan?: SourceSpan;
805
+ readonly sourceMapIds: readonly string[];
806
+ readonly sourceMapMappingIds: readonly string[];
807
+ }
808
+
809
+ export interface NativeSourceChangeProjectionSourceMapLink {
810
+ readonly id: string;
811
+ readonly side: 'before' | 'after';
812
+ readonly sourceMapId?: string;
813
+ readonly sourceMapMappingId?: string;
814
+ readonly sourcePath?: string;
815
+ readonly sourceHash?: string;
816
+ readonly targetPath?: string;
817
+ readonly targetHash?: string;
818
+ readonly semanticSymbolId?: string;
819
+ readonly semanticOccurrenceId?: string;
820
+ readonly semanticNodeId?: string;
821
+ readonly nativeSourceId?: string;
822
+ readonly nativeAstNodeId?: string;
823
+ readonly precision?: string;
824
+ readonly sourceSpan?: SourceSpan;
825
+ readonly generatedSpan?: SourceMapMappingRecord['generatedSpan'];
826
+ readonly ownershipRegionId?: string;
827
+ readonly ownershipRegionKey?: string;
828
+ readonly ownershipRegionKind?: NativeImportRegionTaxonomyKind;
829
+ }
830
+
831
+ export interface NativeSourceChangeProjectionMetadata {
832
+ readonly schema: 'frontier.lang.changedRegionProjection.v1';
833
+ readonly id: string;
834
+ readonly reviewRequired: true;
835
+ readonly autoMergeClaim: false;
836
+ readonly changeKind: NativeSourceChangeKind;
837
+ readonly language?: FrontierSourceLanguage | string;
838
+ readonly sourcePath?: string;
839
+ readonly conflictKey: string;
840
+ readonly region: {
841
+ readonly id?: string;
842
+ readonly key?: string;
843
+ readonly kind?: NativeImportRegionTaxonomyKind;
844
+ readonly granularity?: string;
845
+ readonly precision?: string;
846
+ readonly sourceSpan?: SourceSpan;
847
+ readonly nativeAstNodeId?: string;
848
+ readonly symbolId?: string;
849
+ readonly symbolName?: string;
850
+ readonly symbolKind?: string;
851
+ };
852
+ readonly before?: NativeSourceChangeProjectionEndpoint;
853
+ readonly after?: NativeSourceChangeProjectionEndpoint;
854
+ readonly sourceMapLinks: readonly NativeSourceChangeProjectionSourceMapLink[];
855
+ readonly admission: {
856
+ readonly readiness: SemanticMergeReadiness;
857
+ readonly action: 'review-addition' | 'review-removal' | 'review-file' | 'review-port' | 'rerun-or-human-port' | string;
858
+ readonly reasons: readonly string[];
859
+ readonly conflictKeys: readonly string[];
860
+ };
861
+ }
862
+
863
+ export interface NativeSourceChangeProjectionSummary {
864
+ readonly schema: 'frontier.lang.changedRegionProjectionSummary.v1';
865
+ readonly total: number;
866
+ readonly withProjection: number;
867
+ readonly reviewRequired: number;
868
+ readonly autoMergeClaims: number;
869
+ readonly sourceMapLinks: number;
870
+ readonly byAction: Readonly<Record<string, number>>;
871
+ readonly byRegionKind: Readonly<Record<string, number>>;
872
+ }
873
+
785
874
  export interface NativeSourceChangeSymbol {
786
875
  readonly changeKind: NativeSourceChangeKind;
787
876
  readonly key: string;
@@ -809,6 +898,9 @@ export interface NativeSourceChangeSymbol {
809
898
  export interface NativeSourceChangeRegion extends SemanticImportOwnershipRegion {
810
899
  readonly changeKind: NativeSourceChangeKind;
811
900
  readonly conflictKey: string;
901
+ readonly metadata?: SemanticImportOwnershipRegion['metadata'] & {
902
+ readonly changedRegionProjection?: NativeSourceChangeProjectionMetadata;
903
+ };
812
904
  }
813
905
 
814
906
  export interface NativeSourceChangeSummary {
@@ -871,7 +963,9 @@ export interface NativeSourceChangeSet {
871
963
  readonly semanticIndex?: SemanticIndexRecord;
872
964
  readonly losses: readonly NativeAstLossRecord[];
873
965
  readonly summary: NativeSourceChangeSummary;
874
- readonly metadata?: Record<string, unknown>;
966
+ readonly metadata?: Record<string, unknown> & {
967
+ readonly changedRegionProjectionSummary?: NativeSourceChangeProjectionSummary;
968
+ };
875
969
  }
876
970
 
877
971
  export type NativeImporterAdapterExactness =
package/dist/index.js CHANGED
@@ -262,6 +262,10 @@ export const NativeImportRegionTaxonomyKinds = Object.freeze([
262
262
  'call',
263
263
  'type',
264
264
  'effect',
265
+ 'property',
266
+ 'config',
267
+ 'content',
268
+ 'route',
265
269
  'generatedOutput'
266
270
  ]);
267
271
 
@@ -3154,6 +3158,25 @@ export function diffNativeSourceImports(input) {
3154
3158
  if (sourceChanged && changedSymbols.length === 0 && changedRegions.length === 0) {
3155
3159
  changedRegions = [fileLevelNativeChangeRegion({ language, sourcePath, beforeHash, afterHash, before, after })];
3156
3160
  }
3161
+ const readiness = maxSemanticMergeReadiness(
3162
+ maxSemanticMergeReadiness(nativeImportReadiness(before), nativeImportReadiness(after)),
3163
+ sourceChanged && changedSymbols.length === 0 ? 'needs-review' : 'ready'
3164
+ );
3165
+ const reasons = nativeSourceChangeReasons({ before, after, beforeHash, afterHash, changedSymbols, changedRegions, readiness });
3166
+ changedRegions = attachNativeChangeRegionProjectionMetadata(changedRegions, {
3167
+ before,
3168
+ after,
3169
+ beforeSidecar,
3170
+ afterSidecar,
3171
+ changedSymbols,
3172
+ language,
3173
+ sourcePath,
3174
+ beforeHash,
3175
+ afterHash,
3176
+ readiness,
3177
+ reasons
3178
+ });
3179
+ const changedRegionProjectionSummary = summarizeNativeChangedRegionProjections(changedRegions);
3157
3180
  const evidence = [{
3158
3181
  id: input.evidenceId ?? `evidence_${idPart}_native_source_diff`,
3159
3182
  kind: 'import',
@@ -3168,14 +3191,10 @@ export function diffNativeSourceImports(input) {
3168
3191
  sourceChanged,
3169
3192
  addedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'added').length,
3170
3193
  removedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'removed').length,
3171
- modifiedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'modified').length
3194
+ modifiedSymbols: changedSymbols.filter((symbol) => symbol.changeKind === 'modified').length,
3195
+ changedRegionProjectionSummary
3172
3196
  }
3173
3197
  }];
3174
- const readiness = maxSemanticMergeReadiness(
3175
- maxSemanticMergeReadiness(nativeImportReadiness(before), nativeImportReadiness(after)),
3176
- sourceChanged && changedSymbols.length === 0 ? 'needs-review' : 'ready'
3177
- );
3178
- const reasons = nativeSourceChangeReasons({ before, after, beforeHash, afterHash, changedSymbols, changedRegions, readiness });
3179
3198
  const conflictKeys = uniqueStrings([
3180
3199
  ...changedSymbols.map((symbol) => symbol.conflictKey),
3181
3200
  ...changedRegions.map((region) => region.conflictKey ?? region.key ?? region.id),
@@ -3226,7 +3245,8 @@ export function diffNativeSourceImports(input) {
3226
3245
  beforeImportId: before?.id,
3227
3246
  afterImportId: after?.id,
3228
3247
  sourceChanged,
3229
- changeSummary: nativeSourceChangeSummary(changedSymbols, changedRegions, sourceChanged)
3248
+ changeSummary: nativeSourceChangeSummary(changedSymbols, changedRegions, sourceChanged),
3249
+ changedRegionProjectionSummary
3230
3250
  }
3231
3251
  });
3232
3252
  return {
@@ -3255,6 +3275,7 @@ export function diffNativeSourceImports(input) {
3255
3275
  afterSidecarId: afterSidecar?.id,
3256
3276
  beforeImportContract: before?.metadata?.importResultContract,
3257
3277
  afterImportContract: after?.metadata?.importResultContract,
3278
+ changedRegionProjectionSummary,
3258
3279
  ...input.metadata
3259
3280
  }
3260
3281
  };
@@ -3400,6 +3421,199 @@ function fileLevelNativeChangeRegion(input) {
3400
3421
  };
3401
3422
  }
3402
3423
 
3424
+ function attachNativeChangeRegionProjectionMetadata(regions, context) {
3425
+ return (regions ?? []).map((region) => {
3426
+ const projection = nativeChangedRegionProjectionMetadata(region, context);
3427
+ return {
3428
+ ...region,
3429
+ metadata: {
3430
+ ...(region.metadata ?? {}),
3431
+ changedRegionProjection: projection
3432
+ }
3433
+ };
3434
+ });
3435
+ }
3436
+
3437
+ function nativeChangedRegionProjectionMetadata(region, context) {
3438
+ const beforeRegion = findSemanticImportRegion(context.beforeSidecar, region);
3439
+ const afterRegion = findSemanticImportRegion(context.afterSidecar, region);
3440
+ const regionSymbols = (context.changedSymbols ?? []).filter((symbol) => nativeChangeSymbolTouchesRegion(symbol, region));
3441
+ const sourceMapLinks = uniqueRecordsById([
3442
+ ...nativeChangeProjectionSourceMapLinks(context.before, 'before', beforeRegion ?? region, regionSymbols),
3443
+ ...nativeChangeProjectionSourceMapLinks(context.after, 'after', afterRegion ?? region, regionSymbols)
3444
+ ]).slice(0, 24);
3445
+ const action = nativeChangedRegionProjectionAction(region, context.readiness);
3446
+ const conflictKeys = uniqueStrings([
3447
+ region.conflictKey,
3448
+ region.key ? `region:${region.key}` : undefined,
3449
+ region.id ? `region:${region.id}` : undefined,
3450
+ ...regionSymbols.map((symbol) => symbol.conflictKey)
3451
+ ].filter(Boolean));
3452
+ return {
3453
+ schema: 'frontier.lang.changedRegionProjection.v1',
3454
+ id: `changed_region_projection_${idFragment(region.conflictKey ?? region.key ?? region.id)}`,
3455
+ reviewRequired: true,
3456
+ autoMergeClaim: false,
3457
+ changeKind: region.changeKind,
3458
+ language: region.language ?? context.language,
3459
+ sourcePath: region.sourcePath ?? context.sourcePath,
3460
+ conflictKey: region.conflictKey,
3461
+ region: {
3462
+ id: region.id,
3463
+ key: region.key,
3464
+ kind: region.regionKind,
3465
+ granularity: region.granularity,
3466
+ precision: region.precision,
3467
+ sourceSpan: region.sourceSpan,
3468
+ nativeAstNodeId: region.nativeAstNodeId,
3469
+ symbolId: region.symbolId,
3470
+ symbolName: region.symbolName,
3471
+ symbolKind: region.symbolKind
3472
+ },
3473
+ before: nativeChangeProjectionEndpoint(context.before, context.beforeSidecar, beforeRegion ?? (region.changeKind === 'added' ? undefined : region), 'before'),
3474
+ after: nativeChangeProjectionEndpoint(context.after, context.afterSidecar, afterRegion ?? (region.changeKind === 'removed' ? undefined : region), 'after'),
3475
+ sourceMapLinks,
3476
+ admission: {
3477
+ readiness: context.readiness,
3478
+ action,
3479
+ reasons: context.reasons ?? [],
3480
+ conflictKeys
3481
+ }
3482
+ };
3483
+ }
3484
+
3485
+ function findSemanticImportRegion(sidecar, region) {
3486
+ return (sidecar?.ownershipRegions ?? []).find((candidate) => (
3487
+ (region.id && candidate.id === region.id) ||
3488
+ (region.key && candidate.key === region.key)
3489
+ ));
3490
+ }
3491
+
3492
+ function nativeChangeProjectionEndpoint(imported, sidecar, region, side) {
3493
+ if (!imported && !region) return undefined;
3494
+ const preservation = nativeImportSourcePreservationRecord(imported);
3495
+ const sourceMaps = imported?.sourceMaps ?? imported?.universalAst?.sourceMaps ?? [];
3496
+ const regionMappings = sourceMaps
3497
+ .flatMap((sourceMap) => (sourceMap?.mappings ?? []).map((mapping) => ({ sourceMap, mapping })))
3498
+ .filter(({ mapping }) => nativeChangeMappingTouchesRegion(mapping, region, []));
3499
+ return {
3500
+ side,
3501
+ importId: imported?.id,
3502
+ sidecarId: sidecar?.id,
3503
+ nativeSourceId: imported?.nativeSource?.id,
3504
+ nativeAstId: imported?.nativeAst?.id,
3505
+ semanticIndexId: imported?.semanticIndex?.id,
3506
+ universalAstId: imported?.universalAst?.id,
3507
+ sourcePath: imported?.sourcePath ?? region?.sourcePath,
3508
+ sourceHash: imported?.nativeSource?.sourceHash ?? imported?.nativeAst?.sourceHash ?? region?.sourceHash,
3509
+ sourcePreservationId: preservation?.id,
3510
+ exactSourceAvailable: preservation?.summary?.exactSourceAvailable === true,
3511
+ ownershipRegionId: region?.id,
3512
+ ownershipKey: region?.key,
3513
+ ownershipRegionKind: region?.regionKind,
3514
+ sourceSpan: region?.sourceSpan,
3515
+ sourceMapIds: sourceMaps.map((sourceMap) => sourceMap?.id).filter(Boolean),
3516
+ sourceMapMappingIds: regionMappings.map(({ mapping }) => mapping?.id).filter(Boolean)
3517
+ };
3518
+ }
3519
+
3520
+ function nativeChangeProjectionSourceMapLinks(imported, side, region, symbols) {
3521
+ if (!imported) return [];
3522
+ const sourceMaps = imported.sourceMaps ?? imported.universalAst?.sourceMaps ?? [];
3523
+ const links = [];
3524
+ for (const sourceMap of sourceMaps) {
3525
+ for (const mapping of sourceMap?.mappings ?? []) {
3526
+ if (!nativeChangeMappingTouchesRegion(mapping, region, symbols)) continue;
3527
+ links.push({
3528
+ id: `${side}:${sourceMap.id}:${mapping.id}`,
3529
+ side,
3530
+ sourceMapId: sourceMap.id,
3531
+ sourceMapMappingId: mapping.id,
3532
+ sourcePath: mapping.sourceSpan?.path ?? sourceMap.sourcePath ?? imported.sourcePath,
3533
+ sourceHash: sourceMap.sourceHash ?? imported.nativeSource?.sourceHash ?? imported.nativeAst?.sourceHash,
3534
+ targetPath: mapping.generatedSpan?.targetPath ?? sourceMap.targetPath,
3535
+ targetHash: mapping.generatedSpan?.targetHash ?? sourceMap.targetHash,
3536
+ semanticSymbolId: mapping.semanticSymbolId,
3537
+ semanticOccurrenceId: mapping.semanticOccurrenceId,
3538
+ semanticNodeId: mapping.semanticNodeId,
3539
+ nativeSourceId: mapping.nativeSourceId,
3540
+ nativeAstNodeId: mapping.nativeAstNodeId,
3541
+ precision: mapping.precision,
3542
+ sourceSpan: mapping.sourceSpan,
3543
+ generatedSpan: mapping.generatedSpan,
3544
+ ownershipRegionId: mapping.ownershipRegionId,
3545
+ ownershipRegionKey: mapping.ownershipRegionKey,
3546
+ ownershipRegionKind: mapping.ownershipRegionKind
3547
+ });
3548
+ }
3549
+ }
3550
+ return links;
3551
+ }
3552
+
3553
+ function nativeChangeMappingTouchesRegion(mapping, region, symbols) {
3554
+ if (!mapping || !region) return false;
3555
+ const symbolIds = new Set((symbols ?? []).map((symbol) => symbol.id).filter(Boolean));
3556
+ const occurrenceIds = new Set((symbols ?? []).map((symbol) => symbol.semanticOccurrenceId).filter(Boolean));
3557
+ const mappingIds = new Set((symbols ?? []).map((symbol) => symbol.sourceMapMappingId).filter(Boolean));
3558
+ if (mappingIds.has(mapping.id)) return true;
3559
+ if (region.id && mapping.ownershipRegionId === region.id) return true;
3560
+ if (region.key && mapping.ownershipRegionKey === region.key) return true;
3561
+ if (region.nativeAstNodeId && mapping.nativeAstNodeId === region.nativeAstNodeId) return true;
3562
+ if (symbolIds.has(mapping.semanticSymbolId)) return true;
3563
+ if (occurrenceIds.has(mapping.semanticOccurrenceId)) return true;
3564
+ if (region.granularity === 'file') {
3565
+ return !region.sourcePath || sourceSpanPathMatches(mapping.sourceSpan, region.sourcePath);
3566
+ }
3567
+ return false;
3568
+ }
3569
+
3570
+ function sourceSpanPathMatches(span, sourcePath) {
3571
+ if (!span || !sourcePath) return false;
3572
+ return span.path === sourcePath || span.sourceId === sourcePath;
3573
+ }
3574
+
3575
+ function nativeChangeSymbolTouchesRegion(symbol, region) {
3576
+ return Boolean(symbol && region && (
3577
+ (region.id && symbol.ownershipRegionId === region.id) ||
3578
+ (region.key && (
3579
+ symbol.ownershipKey === region.key ||
3580
+ symbol.beforeOwnershipKey === region.key ||
3581
+ symbol.afterOwnershipKey === region.key
3582
+ ))
3583
+ ));
3584
+ }
3585
+
3586
+ function nativeChangedRegionProjectionAction(region, readiness) {
3587
+ if (readiness === 'blocked') return 'rerun-or-human-port';
3588
+ if (region.changeKind === 'added') return 'review-addition';
3589
+ if (region.changeKind === 'removed') return 'review-removal';
3590
+ if (region.granularity === 'file') return 'review-file';
3591
+ return 'review-port';
3592
+ }
3593
+
3594
+ function nativeImportSourcePreservationRecord(imported) {
3595
+ return imported?.metadata?.sourcePreservation
3596
+ ?? imported?.nativeSource?.metadata?.sourcePreservation
3597
+ ?? imported?.nativeAst?.metadata?.sourcePreservation
3598
+ ?? imported?.universalAst?.metadata?.sourcePreservation;
3599
+ }
3600
+
3601
+ function summarizeNativeChangedRegionProjections(regions) {
3602
+ const projections = (regions ?? [])
3603
+ .map((region) => region?.metadata?.changedRegionProjection)
3604
+ .filter(Boolean);
3605
+ return {
3606
+ schema: 'frontier.lang.changedRegionProjectionSummary.v1',
3607
+ total: regions?.length ?? 0,
3608
+ withProjection: projections.length,
3609
+ reviewRequired: projections.filter((projection) => projection.reviewRequired === true).length,
3610
+ autoMergeClaims: projections.filter((projection) => projection.autoMergeClaim === true).length,
3611
+ sourceMapLinks: projections.reduce((sum, projection) => sum + (projection.sourceMapLinks?.length ?? 0), 0),
3612
+ byAction: countBy(projections.map((projection) => projection.admission?.action ?? 'unknown')),
3613
+ byRegionKind: countBy(projections.map((projection) => projection.region?.kind ?? 'unknown'))
3614
+ };
3615
+ }
3616
+
3403
3617
  function nativeImportReadiness(imported) {
3404
3618
  return imported?.metadata?.semanticMergeReadiness
3405
3619
  ?? imported?.metadata?.nativeImportLossSummary?.semanticMergeReadiness
@@ -3463,11 +3677,31 @@ function nativeChangeSpans(changedSymbols, changedRegions, input) {
3463
3677
  symbolId: region.symbolId,
3464
3678
  span: region.sourceSpan,
3465
3679
  conflictKey: region.conflictKey ?? `region:${region.key ?? region.id}`,
3466
- metadata: { changeKind: region.changeKind, regionKind: region.regionKind, granularity: region.granularity }
3680
+ metadata: {
3681
+ changeKind: region.changeKind,
3682
+ regionKind: region.regionKind,
3683
+ granularity: region.granularity,
3684
+ ...(region.metadata?.changedRegionProjection ? {
3685
+ changedRegionProjection: nativeChangedRegionProjectionSpanMetadata(region.metadata.changedRegionProjection)
3686
+ } : {})
3687
+ }
3467
3688
  }));
3468
3689
  return uniqueRecordsById([...symbolSpans, ...regionSpans]);
3469
3690
  }
3470
3691
 
3692
+ function nativeChangedRegionProjectionSpanMetadata(projection) {
3693
+ return {
3694
+ schema: projection.schema,
3695
+ id: projection.id,
3696
+ reviewRequired: projection.reviewRequired,
3697
+ autoMergeClaim: projection.autoMergeClaim,
3698
+ beforeSourceHash: projection.before?.sourceHash,
3699
+ afterSourceHash: projection.after?.sourceHash,
3700
+ sourceMapLinks: projection.sourceMapLinks?.length ?? 0,
3701
+ admission: projection.admission
3702
+ };
3703
+ }
3704
+
3471
3705
  function nativeSourceChangeSummary(changedSymbols, changedRegions, sourceChanged) {
3472
3706
  return {
3473
3707
  sourceChanged,
@@ -4019,10 +4253,23 @@ function scanJavaScriptLike(input) {
4019
4253
  const declarations = [];
4020
4254
  let currentClass;
4021
4255
  let classDepth = 0;
4256
+ let currentObject;
4257
+ const lexicalState = { inBlockComment: false, inTemplateString: false };
4022
4258
  for (const { line, number } of sourceLines(input.sourceText)) {
4023
- const trimmed = line.trim();
4259
+ const scanLine = jsDeclarationScanLine(line, lexicalState);
4260
+ const trimmed = scanLine.trim();
4261
+ if (!trimmed || jsCommentOnlyLine(trimmed)) continue;
4024
4262
  const declarationLine = trimmed.replace(/^(?:export\s+)?(?:declare\s+)?/, '');
4025
4263
  let match;
4264
+ if (currentObject) {
4265
+ const routeRecord = jsRouteRecordDeclaration(input, number, trimmed, currentObject);
4266
+ if (routeRecord) {
4267
+ declarations.push(routeRecord);
4268
+ } else {
4269
+ const property = jsObjectPropertyDeclaration(input, number, trimmed, currentObject);
4270
+ if (property) declarations.push(property);
4271
+ }
4272
+ }
4026
4273
  if ((match = trimmed.match(/^import\s+(?:.+?\s+from\s+)?['"]([^'"]+)['"]/))) {
4027
4274
  declarations.push(nativeImportDeclaration(input, number, match[1], 'ImportDeclaration', 'module'));
4028
4275
  } else if ((match = trimmed.match(/^import\s*\(\s*['"]([^'"]+)['"]\s*\)/))) {
@@ -4050,11 +4297,20 @@ function scanJavaScriptLike(input) {
4050
4297
  } else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(?([^=;]*)\)?\s*=>/))) {
4051
4298
  declarations.push(nativeDeclaration(input, number, 'VariableFunctionDeclaration', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
4052
4299
  } else if ((match = declarationLine.match(/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b/))) {
4053
- declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', 'variable', match[1], {}, false));
4300
+ const initializerKind = jsInitializerKind(declarationLine);
4301
+ const regionKind = jsRegionKindForDeclarationName(match[1], declarationLine);
4302
+ declarations.push(nativeDeclaration(input, number, 'VariableDeclaration', jsVariableSymbolKind(regionKind, initializerKind), match[1], {
4303
+ initializerKind
4304
+ }, jsVariableHasBody(initializerKind, declarationLine), {
4305
+ regionKind,
4306
+ metadata: { initializerKind }
4307
+ }));
4308
+ currentObject = jsObjectRegionContext(match[1], declarationLine, number, regionKind);
4054
4309
  } else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?function\*?\s*\(([^)]*)\)/))) {
4055
4310
  declarations.push(nativeDeclaration(input, number, 'CommonJsFunctionExport', 'function', match[1], { parameters: splitParameters(match[2]) }, true));
4056
4311
  } else if ((match = trimmed.match(/^(?:module\.)?exports\.([A-Za-z_$][\w$]*)\s*=/))) {
4057
- declarations.push(nativeDeclaration(input, number, 'CommonJsExport', 'variable', match[1], { export: 'commonjs' }, false));
4312
+ const regionKind = jsRegionKindForDeclarationName(match[1], trimmed);
4313
+ declarations.push(nativeDeclaration(input, number, 'CommonJsExport', 'variable', match[1], { export: 'commonjs' }, false, { regionKind }));
4058
4314
  } 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])) {
4059
4315
  declarations.push(nativeDeclaration(input, number, 'MethodDefinition', 'method', `${currentClass}.${match[1]}`, {
4060
4316
  methodName: match[1],
@@ -4075,10 +4331,224 @@ function scanJavaScriptLike(input) {
4075
4331
  classDepth = 0;
4076
4332
  }
4077
4333
  }
4334
+ if (currentObject) {
4335
+ if (number !== currentObject.startLine) currentObject.depth += jsContainerDelta(trimmed);
4336
+ if (currentObject.depth <= 0) currentObject = undefined;
4337
+ }
4078
4338
  }
4079
4339
  return declarations;
4080
4340
  }
4081
4341
 
4342
+ function jsCommentOnlyLine(trimmed) {
4343
+ return trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*');
4344
+ }
4345
+
4346
+ function jsDeclarationScanLine(line, state) {
4347
+ let text = String(line ?? '');
4348
+ if (state.inTemplateString) {
4349
+ const close = findUnescapedBacktick(text, 0);
4350
+ if (close < 0) return '';
4351
+ text = text.slice(close + 1);
4352
+ state.inTemplateString = false;
4353
+ }
4354
+ if (state.inBlockComment) {
4355
+ const close = text.indexOf('*/');
4356
+ if (close < 0) return '';
4357
+ text = text.slice(close + 2);
4358
+ state.inBlockComment = false;
4359
+ }
4360
+ let output = '';
4361
+ let quote;
4362
+ let escaped = false;
4363
+ for (let index = 0; index < text.length; index += 1) {
4364
+ const char = text[index];
4365
+ const next = text[index + 1];
4366
+ if (quote) {
4367
+ output += char;
4368
+ if (escaped) {
4369
+ escaped = false;
4370
+ } else if (char === '\\') {
4371
+ escaped = true;
4372
+ } else if (char === quote) {
4373
+ quote = undefined;
4374
+ }
4375
+ continue;
4376
+ }
4377
+ if (char === '/' && next === '/') break;
4378
+ if (char === '/' && next === '*') {
4379
+ const close = text.indexOf('*/', index + 2);
4380
+ if (close < 0) {
4381
+ state.inBlockComment = true;
4382
+ break;
4383
+ }
4384
+ index = close + 1;
4385
+ continue;
4386
+ }
4387
+ if (char === '\'' || char === '"') {
4388
+ quote = char;
4389
+ output += char;
4390
+ continue;
4391
+ }
4392
+ if (char === '`') {
4393
+ const close = findUnescapedBacktick(text, index + 1);
4394
+ if (close < 0) {
4395
+ state.inTemplateString = true;
4396
+ output += '``';
4397
+ break;
4398
+ }
4399
+ output += text.slice(index, close + 1);
4400
+ index = close;
4401
+ continue;
4402
+ }
4403
+ output += char;
4404
+ }
4405
+ return output;
4406
+ }
4407
+
4408
+ function findUnescapedBacktick(text, startIndex) {
4409
+ let escaped = false;
4410
+ for (let index = startIndex; index < text.length; index += 1) {
4411
+ const char = text[index];
4412
+ if (escaped) {
4413
+ escaped = false;
4414
+ } else if (char === '\\') {
4415
+ escaped = true;
4416
+ } else if (char === '`') {
4417
+ return index;
4418
+ }
4419
+ }
4420
+ return -1;
4421
+ }
4422
+
4423
+ function jsObjectRegionContext(name, declarationLine, lineNumber, regionKind) {
4424
+ const initializerKind = jsInitializerKind(declarationLine);
4425
+ if (initializerKind !== 'object' && initializerKind !== 'array') return undefined;
4426
+ const depth = jsContainerDelta(declarationLine);
4427
+ if (depth <= 0) return undefined;
4428
+ return {
4429
+ name,
4430
+ regionKind: regionKind ?? jsRegionKindForDeclarationName(name, declarationLine),
4431
+ initializerKind,
4432
+ depth,
4433
+ startLine: lineNumber
4434
+ };
4435
+ }
4436
+
4437
+ function jsInitializerKind(line) {
4438
+ const initializer = String(line ?? '').split('=').slice(1).join('=').trim();
4439
+ if (!initializer) return 'unknown';
4440
+ if (/^(?:async\s+)?function\b/.test(initializer) || /=>/.test(initializer)) return 'function';
4441
+ if (initializer.startsWith('{')) return 'object';
4442
+ if (initializer.startsWith('[')) return 'array';
4443
+ if (/^new\s+/.test(initializer)) return 'instance';
4444
+ if (/^['"`]/.test(initializer)) return 'string';
4445
+ if (/^(?:true|false)\b/.test(initializer)) return 'boolean';
4446
+ if (/^[0-9]/.test(initializer)) return 'number';
4447
+ return 'expression';
4448
+ }
4449
+
4450
+ function jsVariableHasBody(initializerKind, declarationLine) {
4451
+ return initializerKind === 'function'
4452
+ || initializerKind === 'object'
4453
+ || initializerKind === 'array'
4454
+ || /\{/.test(String(declarationLine ?? ''));
4455
+ }
4456
+
4457
+ function jsVariableSymbolKind(regionKind, initializerKind) {
4458
+ if (regionKind === 'route') return 'route';
4459
+ if (initializerKind === 'function') return 'function';
4460
+ return 'variable';
4461
+ }
4462
+
4463
+ function jsRegionKindForDeclarationName(name, source = '') {
4464
+ const raw = `${name ?? ''} ${source ?? ''}`;
4465
+ const text = raw.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
4466
+ const compact = raw.toLowerCase();
4467
+ if (/\b(routes?|router|screens?|pages?|navigation|navitems?|menuitems?)\b/.test(text) || /(route|router|screen|page|navigation|navitem|menuitem)/.test(compact)) return 'route';
4468
+ if (/\b(config|settings|options|flags?|schema|manifest|registry|catalog)\b/.test(text) || /(config|settings|options|flags|schema|manifest|registry|catalog)/.test(compact)) return 'config';
4469
+ if (/\b(content|copy|docs?|legal|messages?|strings?|i18n|locale|translations?)\b/.test(text) || /(content|copy|docs|legal|messages|strings|i18n|locale|translations)/.test(compact)) return 'content';
4470
+ return undefined;
4471
+ }
4472
+
4473
+ function jsObjectPropertyDeclaration(input, lineNumber, trimmed, context) {
4474
+ if (/^[}\])]/.test(trimmed) || trimmed.startsWith('...')) return undefined;
4475
+ const methodMatch = trimmed.match(/^(?:(?:async|get|set)\s+)?(['"]?)([A-Za-z_$][\w$-]*)\1\s*\(([^)]*)\)\s*(?:[:\w\s<>\[\]]*)?(?:\{|=>|,|$)/);
4476
+ if (methodMatch && !jsControlKeyword(methodMatch[2])) {
4477
+ const name = `${context.name}.${methodMatch[2]}`;
4478
+ return nativeDeclaration(input, lineNumber, 'ObjectMethod', 'function', name, {
4479
+ owner: context.name,
4480
+ propertyName: methodMatch[2],
4481
+ parameters: splitParameters(methodMatch[3])
4482
+ }, true, {
4483
+ regionKind: jsPropertyRegionKind(context, methodMatch[2], 'function'),
4484
+ metadata: { owner: context.name, propertyName: methodMatch[2], initializerKind: 'function' }
4485
+ });
4486
+ }
4487
+ const propertyMatch = trimmed.match(/^(?:(['"])([^'"]+)\1|([A-Za-z_$][\w$-]*))\s*:\s*(.+?)(?:,)?$/);
4488
+ if (!propertyMatch) return undefined;
4489
+ const propertyName = propertyMatch[2] ?? propertyMatch[3];
4490
+ if (!propertyName || jsControlKeyword(propertyName)) return undefined;
4491
+ const value = propertyMatch[4].trim();
4492
+ const initializerKind = jsPropertyInitializerKind(value);
4493
+ const functionLike = initializerKind === 'function';
4494
+ const name = `${context.name}.${propertyName}`;
4495
+ return nativeDeclaration(input, lineNumber, functionLike ? 'ObjectFunctionProperty' : 'ObjectProperty', functionLike ? 'function' : 'property', name, {
4496
+ owner: context.name,
4497
+ propertyName,
4498
+ initializerKind
4499
+ }, functionLike || initializerKind === 'object' || initializerKind === 'array', {
4500
+ regionKind: jsPropertyRegionKind(context, propertyName, value),
4501
+ metadata: {
4502
+ owner: context.name,
4503
+ propertyName,
4504
+ initializerKind
4505
+ }
4506
+ });
4507
+ }
4508
+
4509
+ function jsRouteRecordDeclaration(input, lineNumber, trimmed, context) {
4510
+ if (context.regionKind !== 'route') return undefined;
4511
+ const match = trimmed.match(/\b(?:path|route|href|url)\s*:\s*(['"`])([^'"`]+)\1/);
4512
+ if (!match) return undefined;
4513
+ const routePath = match[2];
4514
+ return nativeDeclaration(input, lineNumber, 'RouteRecord', 'route', `${context.name}.${routePath}`, {
4515
+ owner: context.name,
4516
+ routePath
4517
+ }, true, {
4518
+ regionKind: 'route',
4519
+ metadata: { owner: context.name, routePath, initializerKind: 'object' }
4520
+ });
4521
+ }
4522
+
4523
+ function jsPropertyInitializerKind(value) {
4524
+ const text = String(value ?? '').trim();
4525
+ if (/^(?:async\s*)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(text)) return 'function';
4526
+ if (text.startsWith('{')) return 'object';
4527
+ if (text.startsWith('[')) return 'array';
4528
+ if (/^['"`]/.test(text)) return 'string';
4529
+ if (/^(?:true|false)\b/.test(text)) return 'boolean';
4530
+ if (/^[0-9]/.test(text)) return 'number';
4531
+ return 'expression';
4532
+ }
4533
+
4534
+ function jsPropertyRegionKind(context, propertyName, value) {
4535
+ const named = jsRegionKindForDeclarationName(propertyName, value);
4536
+ if (named) return named;
4537
+ if (context.regionKind === 'route') return 'route';
4538
+ if (context.regionKind === 'content') return 'content';
4539
+ if (context.regionKind === 'config') return 'config';
4540
+ return 'property';
4541
+ }
4542
+
4543
+ function jsContainerDelta(source) {
4544
+ let delta = 0;
4545
+ for (const char of String(source ?? '')) {
4546
+ if (char === '{' || char === '[') delta += 1;
4547
+ if (char === '}' || char === ']') delta -= 1;
4548
+ }
4549
+ return delta;
4550
+ }
4551
+
4082
4552
  function scanPython(input) {
4083
4553
  const declarations = [];
4084
4554
  for (const { line, number } of sourceLines(input.sourceText)) {
@@ -4778,7 +5248,7 @@ function rMetaName(source) {
4778
5248
  return match?.[1] ?? 'dynamic';
4779
5249
  }
4780
5250
 
4781
- function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false) {
5251
+ function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fields = {}, hasBody = false, options = {}) {
4782
5252
  const nodeId = `native_${idFragment(languageKind)}_${lineNumber}_${idFragment(name)}`;
4783
5253
  return {
4784
5254
  nodeId,
@@ -4789,7 +5259,9 @@ function nativeDeclaration(input, lineNumber, languageKind, symbolKind, name, fi
4789
5259
  symbolId: `symbol:${input.language}:${idFragment(name)}`,
4790
5260
  span: spanForLine(input, lineNumber),
4791
5261
  fields,
4792
- metadata: { scan: 'lightweight-declaration', hasBody },
5262
+ metadata: { scan: 'lightweight-declaration', hasBody, ...options.metadata },
5263
+ ...(options.regionKind ? { regionKind: options.regionKind } : {}),
5264
+ ...(options.role ? { role: options.role } : {}),
4793
5265
  ...(hasBody ? { loss: opaqueBodyLoss(input, lineNumber, nodeId, name) } : {})
4794
5266
  };
4795
5267
  }
@@ -6649,6 +7121,8 @@ function semanticPatchHintForRegion(region, readiness, options = {}) {
6649
7121
 
6650
7122
  function semanticRegionKindForDeclaration(declaration) {
6651
7123
  if (declaration.role === 'import' || declaration.importPath) return 'import';
7124
+ if (declaration.regionKind) return normalizeNativeImportRegionKind(declaration.regionKind);
7125
+ if (declaration.metadata?.ownershipRegionKind) return normalizeNativeImportRegionKind(declaration.metadata.ownershipRegionKind);
6652
7126
  const kind = declaration.symbolKind ?? declaration.kind ?? declaration.nativeNode?.kind;
6653
7127
  if (semanticKindIsType(kind)) return 'type';
6654
7128
  if (semanticKindCanOwnBody(kind, declaration.span ?? declaration.nativeNode?.span)) return 'body';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",