@shapeshift-labs/frontier-lang-compiler 0.2.74 → 0.2.76

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
@@ -383,6 +383,45 @@ console.log(querySemanticPatchBundleRecords([bundle], { sourcePath: 'src/runtime
383
383
 
384
384
  Semantic patch bundle records keep compact refs and query indexes for base/target/source hashes, source paths, changed region keys, conflict keys, source-map IDs, evidence IDs, proof IDs, history IDs, readiness, and admission status. They intentionally omit `before`/`after` import payloads and source text; use the original diff result when a worker needs to re-read or re-project source.
385
385
 
386
+ Map an edited target-language file back to source-language semantic anchors for review. This is the reverse direction a swarm needs when a worker changes generated Rust/Python/etc. and the coordinator wants source-port evidence:
387
+
388
+ ```js
389
+ import {
390
+ createBidirectionalTargetChangeRecord,
391
+ importNativeSource
392
+ } from '@shapeshift-labs/frontier-lang-compiler';
393
+
394
+ const source = importNativeSource({
395
+ language: 'typescript',
396
+ sourcePath: 'src/counter.ts',
397
+ sourceText: 'export function add(count: number) { return count + 1; }\n'
398
+ });
399
+
400
+ const targetChange = createBidirectionalTargetChangeRecord({
401
+ source,
402
+ targetLanguage: 'rust',
403
+ targetPath: 'src/counter.rs',
404
+ baseTarget: {
405
+ language: 'rust',
406
+ sourcePath: 'src/counter.rs',
407
+ sourceText: 'pub fn add(count: i32) -> i32 { count + 1 }\n'
408
+ },
409
+ editedTarget: {
410
+ language: 'rust',
411
+ sourcePath: 'src/counter.rs',
412
+ sourceText: 'pub fn add(count: i32, step: i32) -> i32 { count + step }\n'
413
+ },
414
+ sourceAnchorMappings: [{ targetSymbolName: 'add', sourceSymbolName: 'add' }]
415
+ });
416
+
417
+ console.log(targetChange.sourceAnchorMatches[0].status); // "matched"
418
+ console.log(targetChange.sourcePatchBundle.admission.reviewRequired); // true
419
+ console.log(targetChange.historyRecord.index.ownershipKeys);
420
+ console.log(targetChange.metadata.semanticEquivalenceClaim); // false
421
+ ```
422
+
423
+ Bidirectional target-change records are merge-admission evidence, not transpiler proof. They keep target diffs, source anchor matches, optional lineage resolutions, source patch-bundle records, semantic history records, evidence IDs, readiness, and reason codes. They always keep `autoMergeClaim: false` and `semanticEquivalenceClaim: false`; unmatched or deleted anchors block the source-port route, while matched and ambiguous anchors still require human or verifier review.
424
+
386
425
  Store worker outputs as compact semantic history records when a coordinator needs to compare distributed changes without merging whole files:
387
426
 
388
427
  ```js
package/bench/smoke.mjs CHANGED
@@ -110,6 +110,10 @@ console.log(JSON.stringify({
110
110
  changedRegionProjections: sourceChangeMetrics.changedRegionProjections,
111
111
  changedRegionProjectionSourceMapLinks: sourceChangeMetrics.changedRegionProjectionSourceMapLinks,
112
112
  changeProjectionDurationMs: Number(sourceChangeMetrics.changeProjectionDurationMs.toFixed(2)),
113
+ bidirectionalTargetChanges: sourceChangeMetrics.bidirectionalTargetChanges,
114
+ bidirectionalTargetChangeMatches: sourceChangeMetrics.bidirectionalTargetChangeMatches,
115
+ bidirectionalTargetChangeBlocked: sourceChangeMetrics.bidirectionalTargetChangeBlocked,
116
+ bidirectionalTargetChangeDurationMs: Number(sourceChangeMetrics.bidirectionalTargetChangeDurationMs.toFixed(2)),
113
117
  externalSemanticImports: sourceChangeMetrics.externalSemanticImports,
114
118
  externalSemanticSymbols: sourceChangeMetrics.externalSemanticSymbols,
115
119
  externalSemanticMappings: sourceChangeMetrics.externalSemanticMappings,
@@ -1,6 +1,8 @@
1
1
  import { performance } from 'node:perf_hooks';
2
2
  import {
3
+ createBidirectionalTargetChangeRecord,
3
4
  createSemanticImportSidecar,
5
+ createSemanticLineageEvent,
4
6
  diffNativeSources,
5
7
  importExternalSemanticIndex,
6
8
  importNativeSource
@@ -10,6 +12,7 @@ export function measureSourceChangeSuites() {
10
12
  return {
11
13
  ...measureRegionScan(),
12
14
  ...measureChangeProjection(),
15
+ ...measureBidirectionalTargetChanges(),
13
16
  ...measureExternalSemanticImports()
14
17
  };
15
18
  }
@@ -72,6 +75,47 @@ function measureChangeProjection() {
72
75
  };
73
76
  }
74
77
 
78
+ function measureBidirectionalTargetChanges() {
79
+ const start = performance.now();
80
+ const source = importNativeSource({
81
+ language: 'typescript',
82
+ sourcePath: 'src/bidirectional-source.ts',
83
+ sourceText: 'export function advance(frame: number): number { return frame + 1; }\n'
84
+ });
85
+ const lineage = [createSemanticLineageEvent({
86
+ eventKind: 'moved',
87
+ from: { key: 'source#src/bidirectional-source.ts#body#advance', symbolName: 'advance' },
88
+ to: { key: 'source#src/runtime-core.ts#body#advanceFrame', symbolName: 'advanceFrame' }
89
+ })];
90
+ const records = [];
91
+ for (let index = 0; index < 60; index += 1) {
92
+ records.push(createBidirectionalTargetChangeRecord({
93
+ id: `bench_bidirectional_target_change_${index}`,
94
+ source,
95
+ targetLanguage: 'rust',
96
+ targetPath: `src/bidirectional-${index}.rs`,
97
+ baseTarget: {
98
+ language: 'rust',
99
+ sourcePath: `src/bidirectional-${index}.rs`,
100
+ sourceText: `pub fn advance(frame: i32) -> i32 { frame + ${index} }\n`
101
+ },
102
+ editedTarget: {
103
+ language: 'rust',
104
+ sourcePath: `src/bidirectional-${index}.rs`,
105
+ sourceText: `pub fn advance(frame: i32, delta: i32) -> i32 { frame + delta + ${index} }\n`
106
+ },
107
+ sourceAnchorMappings: [{ targetSymbolName: 'advance', sourceSymbolName: 'advance' }],
108
+ lineage: index % 4 === 0 ? lineage : []
109
+ }));
110
+ }
111
+ return {
112
+ bidirectionalTargetChanges: records.length,
113
+ bidirectionalTargetChangeMatches: records.reduce((sum, record) => sum + record.summary.sourceAnchorMatches, 0),
114
+ bidirectionalTargetChangeBlocked: records.filter((record) => record.readiness === 'blocked').length,
115
+ bidirectionalTargetChangeDurationMs: performance.now() - start
116
+ };
117
+ }
118
+
75
119
  function measureExternalSemanticImports() {
76
120
  const externalSemanticStart = performance.now();
77
121
  const externalSemanticImports = [];
@@ -0,0 +1,129 @@
1
+ import type {
2
+ EvidenceRecord,
3
+ FrontierSourceLanguage,
4
+ SemanticMergeReadiness,
5
+ SourceSpan
6
+ } from '@shapeshift-labs/frontier-lang-kernel';
7
+ import type { ImportNativeSourceOptions, NativeSourceImportResult } from './import-adapter-core.js';
8
+ import type { NativeSourceChangeRegion, NativeSourceChangeSet } from './native-diff.js';
9
+ import type { SemanticHistoryRecord } from './semantic-history.js';
10
+ import type { SemanticLineageEvent, SemanticLineageResolution } from './semantic-lineage.js';
11
+ import type { SemanticPatchBundleRecord } from './semantic-patch-bundle.js';
12
+
13
+ export type BidirectionalTargetChangeAnchorStatus = 'matched' | 'unmatched' | 'ambiguous' | 'deleted' | string;
14
+
15
+ export interface BidirectionalTargetChangeSourceAnchorMapping {
16
+ readonly targetAnchorKey?: string;
17
+ readonly targetRegionKey?: string;
18
+ readonly targetConflictKey?: string;
19
+ readonly targetKey?: string;
20
+ readonly targetSymbolName?: string;
21
+ readonly targetSymbolId?: string;
22
+ readonly sourceAnchorKey?: string;
23
+ readonly sourceRegionKey?: string;
24
+ readonly sourceConflictKey?: string;
25
+ readonly sourceKey?: string;
26
+ readonly sourceSymbolName?: string;
27
+ readonly sourceSymbolId?: string;
28
+ }
29
+
30
+ export interface BidirectionalTargetChangeAnchor {
31
+ readonly id?: string;
32
+ readonly key?: string;
33
+ readonly kind?: string;
34
+ readonly language?: FrontierSourceLanguage | string;
35
+ readonly sourcePath?: string;
36
+ readonly sourceHash?: string;
37
+ readonly symbolId?: string;
38
+ readonly symbolName?: string;
39
+ readonly sourceSpan?: SourceSpan;
40
+ readonly metadata?: Record<string, unknown>;
41
+ }
42
+
43
+ export interface BidirectionalTargetChangeSourceAnchorMatch {
44
+ readonly kind: 'frontier.lang.bidirectionalTargetChangeSourceAnchorMatch';
45
+ readonly version: 1;
46
+ readonly id: string;
47
+ readonly targetRegion: Partial<NativeSourceChangeRegion>;
48
+ readonly sourceAnchors: readonly BidirectionalTargetChangeAnchor[];
49
+ readonly lineageResolutions: readonly SemanticLineageResolution[];
50
+ readonly status: BidirectionalTargetChangeAnchorStatus;
51
+ readonly confidence?: number;
52
+ readonly reasonCodes: readonly string[];
53
+ readonly reviewRequired: true;
54
+ readonly autoMergeClaim: false;
55
+ readonly semanticEquivalenceClaim: false;
56
+ readonly conflictKeys: readonly string[];
57
+ }
58
+
59
+ export interface CreateBidirectionalTargetChangeRecordOptions {
60
+ readonly id?: string;
61
+ readonly source?: NativeSourceImportResult | ImportNativeSourceOptions;
62
+ readonly sourceImport?: NativeSourceImportResult | ImportNativeSourceOptions;
63
+ readonly baseSource?: NativeSourceImportResult | ImportNativeSourceOptions;
64
+ readonly sourceLanguage?: FrontierSourceLanguage | string;
65
+ readonly sourcePath?: string;
66
+ readonly sourceParser?: string;
67
+ readonly targetChangeSet?: NativeSourceChangeSet;
68
+ readonly targetChangeSetId?: string;
69
+ readonly language?: FrontierSourceLanguage | string;
70
+ readonly targetLanguage?: FrontierSourceLanguage | string;
71
+ readonly targetPath?: string;
72
+ readonly baseTarget?: NativeSourceImportResult | ImportNativeSourceOptions;
73
+ readonly beforeTarget?: NativeSourceImportResult | ImportNativeSourceOptions;
74
+ readonly before?: NativeSourceImportResult | ImportNativeSourceOptions;
75
+ readonly editedTarget?: NativeSourceImportResult | ImportNativeSourceOptions;
76
+ readonly afterTarget?: NativeSourceImportResult | ImportNativeSourceOptions;
77
+ readonly after?: NativeSourceImportResult | ImportNativeSourceOptions;
78
+ readonly sourceAnchorMappings?: readonly BidirectionalTargetChangeSourceAnchorMapping[];
79
+ readonly anchorMappings?: readonly BidirectionalTargetChangeSourceAnchorMapping[];
80
+ readonly lineage?: readonly SemanticLineageEvent[];
81
+ readonly lineageEvents?: readonly SemanticLineageEvent[];
82
+ readonly lineageMap?: unknown;
83
+ readonly targetEvidenceId?: string;
84
+ readonly targetPatchId?: string;
85
+ readonly targetMergeCandidateId?: string;
86
+ readonly sourceRegionPrefix?: string;
87
+ readonly sourcePatchBundleId?: string;
88
+ readonly historyRecordId?: string;
89
+ readonly evidenceId?: string;
90
+ readonly generatedAt?: number | string;
91
+ readonly metadata?: Record<string, unknown>;
92
+ }
93
+
94
+ export interface BidirectionalTargetChangeRecord {
95
+ readonly kind: 'frontier.lang.bidirectionalTargetChangeRecord';
96
+ readonly version: 1;
97
+ readonly id: string;
98
+ readonly sourceLanguage?: FrontierSourceLanguage | string;
99
+ readonly targetLanguage?: FrontierSourceLanguage | string;
100
+ readonly sourcePath?: string;
101
+ readonly targetPath?: string;
102
+ readonly sourceImport?: NativeSourceImportResult;
103
+ readonly targetChangeSet: NativeSourceChangeSet;
104
+ readonly sourceAnchorMatches: readonly BidirectionalTargetChangeSourceAnchorMatch[];
105
+ readonly sourcePatchBundle: SemanticPatchBundleRecord;
106
+ readonly historyRecord: SemanticHistoryRecord;
107
+ readonly evidence: readonly EvidenceRecord[];
108
+ readonly readiness: SemanticMergeReadiness | string;
109
+ readonly reasons: readonly string[];
110
+ readonly summary: {
111
+ readonly targetChangedRegions: number;
112
+ readonly sourceAnchorMatches: number;
113
+ readonly ambiguousMatches: number;
114
+ readonly unmatchedTargetRegions: number;
115
+ readonly deletedSourceAnchors: number;
116
+ readonly sourceChangedRegions: number;
117
+ };
118
+ readonly metadata: {
119
+ readonly autoMergeClaim: false;
120
+ readonly semanticEquivalenceClaim: false;
121
+ readonly reviewRequired: true;
122
+ readonly [key: string]: unknown;
123
+ };
124
+ }
125
+
126
+ export declare function createBidirectionalTargetChangeRecord(
127
+ input?: CreateBidirectionalTargetChangeRecordOptions,
128
+ options?: Record<string, unknown>
129
+ ): BidirectionalTargetChangeRecord;
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ export * from './declarations/semantic-merge-conflicts.js';
19
19
  export * from './declarations/semantic-lineage.js';
20
20
  export * from './declarations/semantic-history.js';
21
21
  export * from './declarations/semantic-patch-bundle.js';
22
+ export * from './declarations/bidirectional-target-change.js';
22
23
  export * from './declarations/semantic-impact.js';
23
24
  export * from './declarations/semantic-sidecar.js';
24
25
  export * from './declarations/native-diff.js';
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export { compileFrontierDocument } from './internal/index-impl/compileFrontierDo
4
4
  export { compileFrontierSource } from './internal/index-impl/compileFrontierSource.js';
5
5
  export { compileNativeSource } from './internal/index-impl/compileNativeSource.js';
6
6
  export { createBabelNativeImporterAdapter } from './internal/index-impl/createBabelNativeImporterAdapter.js';
7
+ export { createBidirectionalTargetChangeRecord } from './internal/index-impl/createBidirectionalTargetChangeRecord.js';
7
8
  export { createClangAstNativeImporterAdapter } from './internal/index-impl/createClangAstNativeImporterAdapter.js';
8
9
  export { createCSharpRoslynNativeImporterAdapter } from './internal/index-impl/createCSharpRoslynNativeImporterAdapter.js';
9
10
  export { createEstreeNativeImporterAdapter } from './internal/index-impl/createEstreeNativeImporterAdapter.js';
@@ -0,0 +1,198 @@
1
+ import {
2
+ idFragment,
3
+ maxSemanticMergeReadiness,
4
+ uniqueRecordsById,
5
+ uniqueStrings
6
+ } from '../../native-import-utils.js';
7
+ import { resolveSemanticLineage } from './semanticLineageResolutionRecords.js';
8
+
9
+ export function matchTargetRegion(context) {
10
+ const explicit = explicitMappingForRegion(context.region, context.mappings);
11
+ const candidateAnchors = explicit
12
+ ? sourceAnchorsForMapping(explicit, context.sourceAnchors)
13
+ : sourceAnchorsByName(context.region, context.sourceAnchors);
14
+ const resolvedAnchors = candidateAnchors.flatMap((anchor) => resolveAnchor(anchor, context.lineage));
15
+ const status = matchStatus(candidateAnchors, resolvedAnchors);
16
+ const reasonCodes = uniqueStrings([
17
+ explicit ? 'explicit-anchor-mapping' : 'inferred-by-symbol-name',
18
+ status === 'unmatched' ? 'source-anchor-not-found' : undefined,
19
+ status === 'ambiguous' ? 'source-anchor-ambiguous' : undefined,
20
+ status === 'deleted' ? 'source-anchor-deleted' : undefined,
21
+ 'human-source-port-required'
22
+ ]);
23
+ return {
24
+ kind: 'frontier.lang.bidirectionalTargetChangeSourceAnchorMatch',
25
+ version: 1,
26
+ id: `source_anchor_match_${idFragment(context.id)}_${context.index + 1}`,
27
+ targetRegion: compactRegion(context.region),
28
+ sourceAnchors: resolvedAnchors.map((entry) => entry.anchor),
29
+ lineageResolutions: uniqueRecordsById(resolvedAnchors.map((entry) => entry.resolution).filter(Boolean)),
30
+ status,
31
+ confidence: status === 'matched' && explicit ? 0.8 : status === 'matched' ? 0.58 : undefined,
32
+ reasonCodes,
33
+ reviewRequired: true,
34
+ autoMergeClaim: false,
35
+ semanticEquivalenceClaim: false,
36
+ conflictKeys: uniqueStrings([
37
+ context.region.conflictKey,
38
+ context.region.key,
39
+ ...resolvedAnchors.map((entry) => entry.anchor?.key),
40
+ status === 'unmatched' ? `target:${context.region.sourcePath ?? context.region.language ?? 'unknown'}` : undefined
41
+ ])
42
+ };
43
+ }
44
+
45
+ export function classifyBidirectionalReadiness(targetChangeSet, source, matches) {
46
+ let readiness = maxSemanticMergeReadiness(targetChangeSet.readiness, 'needs-review');
47
+ if (!source) readiness = 'blocked';
48
+ if (matches.some((match) => match.status === 'deleted')) readiness = 'blocked';
49
+ if (matches.every((match) => match.status === 'unmatched') && matches.length > 0) readiness = 'blocked';
50
+ return readiness;
51
+ }
52
+
53
+ export function sourceRegionsForMatch(match, readiness) {
54
+ const anchors = match.sourceAnchors.length ? match.sourceAnchors : [undefined];
55
+ return anchors.map((anchor, index) => compactRecord({
56
+ id: `source_port_region_${idFragment(match.id)}_${index + 1}`,
57
+ key: anchor?.key ?? `unmapped-target#${match.targetRegion.key ?? match.targetRegion.id}`,
58
+ conflictKey: anchor?.key ?? match.targetRegion.conflictKey ?? match.targetRegion.key,
59
+ changeKind: match.targetRegion.changeKind,
60
+ regionKind: anchor?.kind ?? match.targetRegion.regionKind,
61
+ granularity: 'symbol',
62
+ language: anchor?.language,
63
+ sourcePath: anchor?.sourcePath,
64
+ sourceHash: anchor?.sourceHash,
65
+ symbolId: anchor?.symbolId,
66
+ symbolName: anchor?.symbolName,
67
+ sourceSpan: anchor?.sourceSpan,
68
+ admission: {
69
+ readiness,
70
+ action: 'review-port-from-target-change',
71
+ reasonCodes: match.reasonCodes,
72
+ conflictKeys: match.conflictKeys
73
+ },
74
+ metadata: {
75
+ bidirectionalTargetChange: {
76
+ matchId: match.id,
77
+ targetRegion: match.targetRegion,
78
+ lineageResolutionIds: match.lineageResolutions.map((resolution) => resolution.id),
79
+ reviewRequired: true,
80
+ autoMergeClaim: false,
81
+ semanticEquivalenceClaim: false
82
+ }
83
+ }
84
+ }));
85
+ }
86
+
87
+ export function createBidirectionalEvidence(context) {
88
+ return {
89
+ id: context.input.evidenceId ?? `evidence_${idFragment(context.id)}_bidirectional_target_change`,
90
+ kind: 'semantic-merge',
91
+ status: 'passed',
92
+ path: context.source?.sourcePath ?? context.targetChangeSet.sourcePath,
93
+ summary: `Mapped ${context.targetChangeSet.changedRegions.length} target changed region(s) to ${matchedCount(context.sourceAnchorMatches)} source anchor match(es).`,
94
+ metadata: {
95
+ schema: 'frontier.lang.bidirectionalTargetChangeEvidence.v1',
96
+ sourceImportId: context.source?.id,
97
+ targetChangeSetId: context.targetChangeSet.id,
98
+ targetPatchId: context.targetChangeSet.patch?.id,
99
+ sourceAnchorMatchIds: context.sourceAnchorMatches.map((match) => match.id),
100
+ readiness: context.readiness,
101
+ reasons: context.reasons,
102
+ autoMergeClaim: false,
103
+ semanticEquivalenceClaim: false
104
+ }
105
+ };
106
+ }
107
+
108
+ export function anchorsFromSourceSidecar(sidecar, source) {
109
+ return uniqueRecordsById((sidecar.ownershipRegions ?? []).map((region) => compactRecord({
110
+ id: region.id,
111
+ key: region.key,
112
+ kind: region.regionKind,
113
+ language: region.language ?? source?.language,
114
+ sourcePath: region.sourcePath ?? source?.sourcePath,
115
+ sourceHash: region.sourceHash ?? sourceHash(source),
116
+ symbolId: region.symbolId,
117
+ symbolName: region.symbolName,
118
+ sourceSpan: region.sourceSpan,
119
+ metadata: region.metadata
120
+ })));
121
+ }
122
+
123
+ export function sourceHash(source) {
124
+ return source?.nativeSource?.sourceHash ?? source?.nativeAst?.sourceHash ?? source?.sourceHash;
125
+ }
126
+
127
+ function explicitMappingForRegion(region, mappings) {
128
+ return mappings.find((mapping) => {
129
+ const targetKeys = uniqueStrings([
130
+ mapping.targetAnchorKey,
131
+ mapping.targetRegionKey,
132
+ mapping.targetConflictKey,
133
+ mapping.targetKey
134
+ ]);
135
+ if (targetKeys.some((key) => key === region.key || key === region.conflictKey || key === region.id)) return true;
136
+ if (mapping.targetSymbolName && mapping.targetSymbolName === (region.symbolName ?? region.name)) return true;
137
+ return mapping.targetSymbolId && mapping.targetSymbolId === region.symbolId;
138
+ });
139
+ }
140
+
141
+ function sourceAnchorsForMapping(mapping, sourceAnchors) {
142
+ const keys = uniqueStrings([mapping.sourceAnchorKey, mapping.sourceRegionKey, mapping.sourceConflictKey, mapping.sourceKey]);
143
+ const matches = sourceAnchors.filter((anchor) => keys.includes(anchor.key) || keys.includes(anchor.id));
144
+ if (matches.length) return matches;
145
+ if (mapping.sourceSymbolName) return sourceAnchors.filter((anchor) => anchor.symbolName === mapping.sourceSymbolName);
146
+ if (mapping.sourceSymbolId) return sourceAnchors.filter((anchor) => anchor.symbolId === mapping.sourceSymbolId);
147
+ return [];
148
+ }
149
+
150
+ function sourceAnchorsByName(region, sourceAnchors) {
151
+ const names = uniqueStrings([region.symbolName, region.name, region.metadata?.changedRegionProjection?.region?.symbolName]);
152
+ if (names.length === 0) return [];
153
+ return sourceAnchors.filter((anchor) => names.includes(anchor.symbolName));
154
+ }
155
+
156
+ function resolveAnchor(anchor, lineage) {
157
+ if (!anchor) return [];
158
+ const resolution = array(lineage).length ? resolveSemanticLineage(lineage, { anchorKey: anchor.key }) : undefined;
159
+ if (!resolution || resolution.status === 'unchanged') return [{ anchor, resolution }];
160
+ if (resolution.status === 'deleted') return [{ anchor, resolution }];
161
+ if (resolution.currentAnchors.length === 0) return [{ anchor, resolution }];
162
+ return resolution.currentAnchors.map((current) => ({ anchor: { ...anchor, ...current }, resolution }));
163
+ }
164
+
165
+ function matchStatus(originalAnchors, resolvedAnchors) {
166
+ if (originalAnchors.length === 0 || resolvedAnchors.length === 0) return 'unmatched';
167
+ if (resolvedAnchors.some((entry) => entry.resolution?.status === 'deleted')) return 'deleted';
168
+ if (resolvedAnchors.length > 1 || resolvedAnchors.some((entry) => entry.resolution?.status === 'ambiguous')) return 'ambiguous';
169
+ return 'matched';
170
+ }
171
+
172
+ function matchedCount(matches) {
173
+ return matches.filter((match) => match.status === 'matched').length;
174
+ }
175
+
176
+ function compactRegion(region) {
177
+ return compactRecord({
178
+ id: region.id,
179
+ key: region.key,
180
+ conflictKey: region.conflictKey,
181
+ changeKind: region.changeKind,
182
+ regionKind: region.regionKind,
183
+ language: region.language,
184
+ sourcePath: region.sourcePath,
185
+ sourceHash: region.sourceHash,
186
+ symbolId: region.symbolId,
187
+ symbolName: region.symbolName ?? region.name,
188
+ sourceSpan: region.sourceSpan
189
+ });
190
+ }
191
+
192
+ function array(value) {
193
+ return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value];
194
+ }
195
+
196
+ function compactRecord(value) {
197
+ return Object.fromEntries(Object.entries(value ?? {}).filter(([, entry]) => entry !== undefined && (!Array.isArray(entry) || entry.length > 0)));
198
+ }
@@ -0,0 +1,169 @@
1
+ import {
2
+ idFragment,
3
+ uniqueStrings
4
+ } from '../../native-import-utils.js';
5
+ import { createSemanticImportSidecar } from './createSemanticImportSidecar.js';
6
+ import { createSemanticPatchBundleRecord } from './semanticPatchBundleRecords.js';
7
+ import { createSemanticHistoryRecord } from './semanticHistoryRecords.js';
8
+ import { diffNativeSourceImports } from './diffNativeSourceImports.js';
9
+ import { normalizeNativeDiffImport } from './normalizeNativeDiffImport.js';
10
+ import {
11
+ anchorsFromSourceSidecar,
12
+ classifyBidirectionalReadiness,
13
+ createBidirectionalEvidence,
14
+ matchTargetRegion,
15
+ sourceHash,
16
+ sourceRegionsForMatch
17
+ } from './bidirectionalTargetChangeRecordInternals.js';
18
+
19
+ export function createBidirectionalTargetChangeRecord(input = {}, options = {}) {
20
+ const source = normalizeNativeDiffImport(
21
+ input.source ?? input.sourceImport ?? input.baseSource,
22
+ { language: input.sourceLanguage, sourcePath: input.sourcePath, parser: input.sourceParser },
23
+ 'source'
24
+ );
25
+ const targetChangeSet = input.targetChangeSet ?? diffNativeSourceImports({
26
+ id: input.targetChangeSetId ?? input.id,
27
+ language: input.targetLanguage ?? input.language,
28
+ sourcePath: input.targetPath ?? input.sourcePath,
29
+ before: input.baseTarget ?? input.beforeTarget ?? input.before,
30
+ after: input.editedTarget ?? input.afterTarget ?? input.after,
31
+ evidenceId: input.targetEvidenceId,
32
+ patchId: input.targetPatchId,
33
+ mergeCandidateId: input.targetMergeCandidateId,
34
+ metadata: { direction: 'target-to-source', ...(input.targetMetadata ?? {}) }
35
+ });
36
+ const id = input.id ?? `bidirectional_target_change_${idFragment([
37
+ source?.sourcePath,
38
+ targetChangeSet.sourcePath,
39
+ targetChangeSet.id
40
+ ].filter(Boolean).join('_'))}`;
41
+ const sourceSidecar = source ? createSemanticImportSidecar(source, {
42
+ id: `sidecar_source_${idFragment(id)}`,
43
+ generatedAt: input.generatedAt,
44
+ regionPrefix: input.sourceRegionPrefix
45
+ }) : undefined;
46
+ const sourceAnchors = sourceSidecar ? anchorsFromSourceSidecar(sourceSidecar, source) : [];
47
+ const mappings = array(input.sourceAnchorMappings ?? input.anchorMappings);
48
+ const lineage = input.lineage ?? input.lineageEvents ?? input.lineageMap ?? [];
49
+ const sourceAnchorMatches = targetChangeSet.changedRegions.map((region, index) => matchTargetRegion({
50
+ id,
51
+ region,
52
+ index,
53
+ source,
54
+ sourceAnchors,
55
+ mappings,
56
+ lineage
57
+ }));
58
+ const readiness = classifyBidirectionalReadiness(targetChangeSet, source, sourceAnchorMatches);
59
+ const reasons = uniqueStrings([
60
+ 'source-port-review-required',
61
+ 'target-change-is-merge-evidence-not-proof',
62
+ ...array(targetChangeSet.reasons),
63
+ ...sourceAnchorMatches.flatMap((match) => match.reasonCodes)
64
+ ]);
65
+ const evidence = [createBidirectionalEvidence({
66
+ id,
67
+ input,
68
+ source,
69
+ targetChangeSet,
70
+ sourceAnchorMatches,
71
+ readiness,
72
+ reasons
73
+ })];
74
+ const sourceChangedRegions = sourceAnchorMatches.flatMap((match) => sourceRegionsForMatch(match, readiness));
75
+ const sourcePatchBundle = createSemanticPatchBundleRecord({
76
+ id: `${id}_source_port_projection`,
77
+ language: source?.language,
78
+ sourcePath: source?.sourcePath,
79
+ baseHash: sourceHash(source),
80
+ changedRegions: sourceChangedRegions,
81
+ evidence,
82
+ readiness,
83
+ reasons,
84
+ conflictKeys: uniqueStrings(sourceAnchorMatches.flatMap((match) => match.conflictKeys)),
85
+ metadata: {
86
+ projectionOnly: true,
87
+ targetChangeSetId: targetChangeSet.id,
88
+ targetPatchId: targetChangeSet.patch?.id,
89
+ targetMergeCandidateId: targetChangeSet.mergeCandidate?.id
90
+ }
91
+ }, {
92
+ id: input.sourcePatchBundleId ?? `semantic_patch_bundle_${idFragment(id)}_source_port`,
93
+ patchId: targetChangeSet.patch?.id,
94
+ mergeCandidateId: targetChangeSet.mergeCandidate?.id,
95
+ admission: { status: readiness === 'blocked' ? 'blocked' : 'needs-review', readiness },
96
+ metadata: {
97
+ source: 'createBidirectionalTargetChangeRecord',
98
+ autoMergeClaim: false,
99
+ semanticEquivalenceClaim: false
100
+ }
101
+ });
102
+ const historyRecord = createSemanticHistoryRecord({
103
+ id: input.historyRecordId ?? `semantic_history_${idFragment(id)}_target_change`,
104
+ importResult: source,
105
+ language: source?.language,
106
+ sourcePath: source?.sourcePath,
107
+ baseHash: sourceHash(source),
108
+ targetHash: sourceHash(source),
109
+ ownershipRegions: sourceChangedRegions,
110
+ semanticCandidates: [targetChangeSet.mergeCandidate].filter(Boolean),
111
+ evidence,
112
+ evidenceIds: evidence.map((record) => record.id),
113
+ patchAncestry: targetChangeSet.patch ? [{
114
+ patchId: targetChangeSet.patch.id,
115
+ baseHash: targetChangeSet.beforeHash,
116
+ targetHash: targetChangeSet.afterHash,
117
+ conflictKeys: targetChangeSet.mergeCandidate?.conflictKeys,
118
+ metadata: { direction: 'target-to-source' }
119
+ }] : [],
120
+ admission: { status: readiness === 'blocked' ? 'blocked' : 'needs-review', readiness, reasonCodes: reasons },
121
+ replayLinks: targetChangeSet.patch ? [{
122
+ id: `replay_${idFragment(targetChangeSet.patch.id)}`,
123
+ kind: 'patch',
124
+ path: targetChangeSet.patch.id
125
+ }] : [],
126
+ metadata: {
127
+ bidirectionalTargetChangeId: id,
128
+ sourcePatchBundleId: sourcePatchBundle.id,
129
+ targetChangeSetId: targetChangeSet.id,
130
+ autoMergeClaim: false,
131
+ semanticEquivalenceClaim: false
132
+ }
133
+ }, options);
134
+ return {
135
+ kind: 'frontier.lang.bidirectionalTargetChangeRecord',
136
+ version: 1,
137
+ id,
138
+ sourceLanguage: source?.language,
139
+ targetLanguage: targetChangeSet.language,
140
+ sourcePath: source?.sourcePath,
141
+ targetPath: targetChangeSet.sourcePath,
142
+ sourceImport: source,
143
+ targetChangeSet,
144
+ sourceAnchorMatches,
145
+ sourcePatchBundle,
146
+ historyRecord,
147
+ evidence,
148
+ readiness,
149
+ reasons,
150
+ summary: {
151
+ targetChangedRegions: targetChangeSet.changedRegions.length,
152
+ sourceAnchorMatches: sourceAnchorMatches.filter((match) => match.status === 'matched').length,
153
+ ambiguousMatches: sourceAnchorMatches.filter((match) => match.status === 'ambiguous').length,
154
+ unmatchedTargetRegions: sourceAnchorMatches.filter((match) => match.status === 'unmatched').length,
155
+ deletedSourceAnchors: sourceAnchorMatches.filter((match) => match.status === 'deleted').length,
156
+ sourceChangedRegions: sourceChangedRegions.length
157
+ },
158
+ metadata: {
159
+ autoMergeClaim: false,
160
+ semanticEquivalenceClaim: false,
161
+ reviewRequired: true,
162
+ ...input.metadata
163
+ }
164
+ };
165
+ }
166
+
167
+ function array(value) {
168
+ return value === undefined || value === null ? [] : Array.isArray(value) ? value : [value];
169
+ }
@@ -1,9 +1,9 @@
1
1
  const packageRows = [
2
- row('@shapeshift-labs/frontier-lang-typescript', '0.3.8', 'typescript', 'typescript-compiler-api', { target: 'typescript' }),
3
- row('@shapeshift-labs/frontier-lang-javascript', '0.2.8', 'javascript', 'estree', { target: 'javascript', formats: ['estree', 'babel'] }),
4
- row('@shapeshift-labs/frontier-lang-rust', '0.2.8', 'rust', 'rust-syn', { target: 'rust', proofKeys: ['parserAst', 'sourceMap', 'semanticSidecar', 'macroExpansionEvidence'] }),
5
- row('@shapeshift-labs/frontier-lang-python', '0.2.8', 'python', 'python-ast', { target: 'python', formats: ['python-ast', 'libcst'] }),
6
- row('@shapeshift-labs/frontier-lang-c', '0.2.8', 'c', 'clang-ast-json', { target: 'c', proofKeys: ['parserAst', 'sourceMap', 'semanticSidecar', 'compileCommandsHash', 'preprocessorRecordsHash'] }),
2
+ row('@shapeshift-labs/frontier-lang-typescript', '0.3.9', 'typescript', 'typescript-compiler-api', { target: 'typescript' }),
3
+ row('@shapeshift-labs/frontier-lang-javascript', '0.2.9', 'javascript', 'estree', { target: 'javascript', formats: ['estree', 'babel'] }),
4
+ row('@shapeshift-labs/frontier-lang-rust', '0.2.9', 'rust', 'rust-syn', { target: 'rust', proofKeys: ['parserAst', 'sourceMap', 'semanticSidecar', 'macroExpansionEvidence'] }),
5
+ row('@shapeshift-labs/frontier-lang-python', '0.2.9', 'python', 'python-ast', { target: 'python', formats: ['python-ast', 'libcst'] }),
6
+ row('@shapeshift-labs/frontier-lang-c', '0.2.9', 'c', 'clang-ast-json', { target: 'c', proofKeys: ['parserAst', 'sourceMap', 'semanticSidecar', 'compileCommandsHash', 'preprocessorRecordsHash'] }),
7
7
  platform('@shapeshift-labs/frontier-lang-java', '0.1.8', 'java', 'java-ast', ['semanticdb', 'lsp']),
8
8
  platform('@shapeshift-labs/frontier-lang-kotlin', '0.1.8', 'kotlin', 'kotlin-psi', ['semanticdb', 'lsp']),
9
9
  platform('@shapeshift-labs/frontier-lang-swift', '0.1.8', 'swift', 'swift-syntax', ['sourcekit-lsp', 'lsp']),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.74",
3
+ "version": "0.2.76",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -61,14 +61,14 @@
61
61
  "access": "public"
62
62
  },
63
63
  "dependencies": {
64
- "@shapeshift-labs/frontier-lang-c": "0.2.8",
65
- "@shapeshift-labs/frontier-lang-checker": "0.3.7",
66
- "@shapeshift-labs/frontier-lang-javascript": "0.2.8",
64
+ "@shapeshift-labs/frontier-lang-c": "0.2.9",
65
+ "@shapeshift-labs/frontier-lang-checker": "0.3.8",
66
+ "@shapeshift-labs/frontier-lang-javascript": "0.2.9",
67
67
  "@shapeshift-labs/frontier-lang-kernel": "0.3.12",
68
- "@shapeshift-labs/frontier-lang-parser": "0.3.7",
69
- "@shapeshift-labs/frontier-lang-python": "0.2.8",
70
- "@shapeshift-labs/frontier-lang-rust": "0.2.8",
71
- "@shapeshift-labs/frontier-lang-typescript": "0.3.8"
68
+ "@shapeshift-labs/frontier-lang-parser": "0.3.8",
69
+ "@shapeshift-labs/frontier-lang-python": "0.2.9",
70
+ "@shapeshift-labs/frontier-lang-rust": "0.2.9",
71
+ "@shapeshift-labs/frontier-lang-typescript": "0.3.9"
72
72
  },
73
73
  "devDependencies": {
74
74
  "typescript": "^5.9.3"