@shapeshift-labs/frontier-lang-compiler 0.2.103 → 0.2.104

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.
Files changed (124) hide show
  1. package/README.md +13 -0
  2. package/dist/declarations/bidirectional-target-change-source-edit.d.ts +30 -0
  3. package/dist/declarations/bidirectional-target-change.d.ts +10 -0
  4. package/dist/declarations/js-ts-safe-member-merge.d.ts +58 -0
  5. package/dist/declarations/js-ts-safe-merge.d.ts +120 -0
  6. package/dist/declarations/js-ts-semantic-conflict-sidecars.d.ts +235 -0
  7. package/dist/declarations/js-ts-semantic-merge-contracts.d.ts +287 -0
  8. package/dist/declarations/js-ts-semantic-merge.d.ts +4 -0
  9. package/dist/declarations/native-import-losses.d.ts +3 -0
  10. package/dist/declarations/semantic-edit-replay-diagnostics.d.ts +12 -0
  11. package/dist/declarations/semantic-edit-script.d.ts +7 -4
  12. package/dist/declarations/semantic-patch-bundle-index.d.ts +45 -0
  13. package/dist/declarations/semantic-patch-bundle.d.ts +6 -4
  14. package/dist/declarations/semantic-sidecar-example.d.ts +18 -0
  15. package/dist/declarations/semantic-transform-identity.d.ts +3 -0
  16. package/dist/declarations/source-preservation.d.ts +72 -0
  17. package/dist/declarations/universal-capability.d.ts +4 -0
  18. package/dist/declarations/universal-conversion-artifacts.d.ts +61 -1
  19. package/dist/declarations/universal-conversion-compact-counts.d.ts +51 -0
  20. package/dist/declarations/universal-conversion-plan.d.ts +6 -1
  21. package/dist/declarations/universal-representation-coverage.d.ts +90 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.js +3 -0
  24. package/dist/internal/index-impl/bidirectionalExactSourceBackprojection.js +199 -0
  25. package/dist/internal/index-impl/bidirectionalSameLanguageSourceProjection.js +112 -0
  26. package/dist/internal/index-impl/bidirectionalSourceEditProjection.js +319 -0
  27. package/dist/internal/index-impl/bidirectionalSourceEditProjectionArtifacts.js +67 -0
  28. package/dist/internal/index-impl/bidirectionalTargetChangeRecordInternals.js +17 -5
  29. package/dist/internal/index-impl/bidirectionalTargetRoundtripEvidence.js +58 -20
  30. package/dist/internal/index-impl/createBidirectionalTargetChangeRecord.js +60 -7
  31. package/dist/internal/index-impl/createLightweightNativeImport.js +1 -0
  32. package/dist/internal/index-impl/createNativeSourcePreservation.js +28 -2
  33. package/dist/internal/index-impl/diffNativeSymbols.js +3 -3
  34. package/dist/internal/index-impl/nativeChangeProjectionSourceMapLinks.js +2 -0
  35. package/dist/internal/index-impl/projectSemanticEditScriptToSource.js +43 -8
  36. package/dist/internal/index-impl/replaySemanticEditLineEndings.js +34 -0
  37. package/dist/internal/index-impl/replaySemanticEditProjection.js +39 -19
  38. package/dist/internal/index-impl/semanticEditBundleAdmission.js +7 -3
  39. package/dist/internal/index-impl/semanticEditBundleIndex.js +47 -1
  40. package/dist/internal/index-impl/semanticEditExplicitSourceReplacement.js +40 -0
  41. package/dist/internal/index-impl/semanticEditOperationCoverage.js +33 -3
  42. package/dist/internal/index-impl/semanticEditProjectionRecord.js +29 -0
  43. package/dist/internal/index-impl/semanticEditReplayDiagnostics.js +39 -0
  44. package/dist/internal/index-impl/semanticEditReplaySourceReplacement.js +85 -0
  45. package/dist/internal/index-impl/semanticEditScripts.js +4 -0
  46. package/dist/internal/index-impl/semanticEditSourceRanges.js +27 -0
  47. package/dist/internal/index-impl/semanticIndexFromNativeDeclarations.js +1 -0
  48. package/dist/internal/index-impl/semanticPatchBundleAdmission.js +41 -7
  49. package/dist/internal/index-impl/semanticPatchBundleRecords.js +16 -0
  50. package/dist/internal/index-impl/semanticPatchBundleSourceRecords.js +2 -0
  51. package/dist/internal/index-impl/semanticSidecarQuality.js +111 -0
  52. package/dist/internal/index-impl/semanticSourceEditDedupe.js +69 -9
  53. package/dist/internal/index-impl/semanticTransformIdentityRecords.js +85 -9
  54. package/dist/js-ts-safe-member-merge-result.js +158 -0
  55. package/dist/js-ts-safe-member-merge.js +202 -0
  56. package/dist/js-ts-safe-merge-analyze.js +279 -0
  57. package/dist/js-ts-safe-merge-constants.js +50 -0
  58. package/dist/js-ts-safe-merge-context.js +118 -0
  59. package/dist/js-ts-safe-merge-ledger-validation.js +92 -0
  60. package/dist/js-ts-safe-merge-ledger.js +85 -0
  61. package/dist/js-ts-safe-merge-parse-declarations.js +210 -0
  62. package/dist/js-ts-safe-merge-parse-statements.js +155 -0
  63. package/dist/js-ts-safe-merge-plan.js +190 -0
  64. package/dist/js-ts-safe-merge.js +175 -0
  65. package/dist/js-ts-semantic-conflict-sidecar-constants.js +77 -0
  66. package/dist/js-ts-semantic-conflict-sidecar-detectors.js +195 -0
  67. package/dist/js-ts-semantic-conflict-sidecar-normalize.js +203 -0
  68. package/dist/js-ts-semantic-conflict-sidecar-utils.js +190 -0
  69. package/dist/js-ts-semantic-conflict-sidecars.js +81 -0
  70. package/dist/js-ts-semantic-merge-contract-helpers.js +128 -0
  71. package/dist/js-ts-semantic-merge-contracts.js +217 -0
  72. package/dist/js-ts-semantic-merge-member-containers.js +100 -0
  73. package/dist/js-ts-semantic-merge-member-keys.js +142 -0
  74. package/dist/js-ts-semantic-merge-member-segments.js +185 -0
  75. package/dist/js-ts-semantic-merge-member-source.js +64 -0
  76. package/dist/js-ts-semantic-merge-member-utils.js +18 -0
  77. package/dist/js-ts-semantic-merge-parse.js +15 -0
  78. package/dist/js-ts-semantic-merge.js +21 -0
  79. package/dist/lightweight-dependency-effects.js +51 -0
  80. package/dist/lightweight-dependency-language.js +12 -1
  81. package/dist/lightweight-dependency-relations.js +14 -27
  82. package/dist/native-region-scanner-core.js +33 -1
  83. package/dist/native-region-scanner-csharp.js +151 -0
  84. package/dist/native-region-scanner-dart.js +91 -0
  85. package/dist/native-region-scanner-dynamic.js +21 -151
  86. package/dist/native-region-scanner-functional.js +40 -13
  87. package/dist/native-region-scanner-java.js +97 -0
  88. package/dist/native-region-scanner-js-class.js +100 -0
  89. package/dist/native-region-scanner-js-helpers.js +28 -86
  90. package/dist/native-region-scanner-js-imports.js +121 -1
  91. package/dist/native-region-scanner-js-nested.js +96 -8
  92. package/dist/native-region-scanner-js-structure.js +27 -0
  93. package/dist/native-region-scanner-js-types.js +99 -0
  94. package/dist/native-region-scanner-js.js +70 -118
  95. package/dist/native-region-scanner-kotlin.js +94 -0
  96. package/dist/native-region-scanner-main.js +15 -181
  97. package/dist/native-region-scanner-php.js +80 -0
  98. package/dist/native-region-scanner-python.js +62 -0
  99. package/dist/native-region-scanner-ruby.js +72 -0
  100. package/dist/native-region-scanner-scala.js +91 -0
  101. package/dist/native-region-scanner-spans.js +74 -0
  102. package/dist/native-region-scanner-swift.js +155 -0
  103. package/dist/native-region-scanner.js +14 -10
  104. package/dist/native-source-ledger-helpers.js +195 -0
  105. package/dist/native-source-ledger.js +306 -0
  106. package/dist/native-source-preservation-scanner.js +4 -0
  107. package/dist/semantic-import-callsite-regions.js +136 -0
  108. package/dist/semantic-import-effect-regions.js +283 -0
  109. package/dist/semantic-import-regions.js +11 -2
  110. package/dist/semantic-import-sidecar-entry.js +16 -2
  111. package/dist/semantic-import-sidecar-types.d.ts +2 -0
  112. package/dist/semantic-sidecar-example.js +68 -0
  113. package/dist/universal-capability-matrix.js +23 -0
  114. package/dist/universal-conversion-artifact-query.js +79 -2
  115. package/dist/universal-conversion-artifact-semantic-edit.js +103 -0
  116. package/dist/universal-conversion-artifact-summary.js +33 -1
  117. package/dist/universal-conversion-artifacts.js +13 -48
  118. package/dist/universal-conversion-plan-scoring.js +21 -1
  119. package/dist/universal-conversion-plan-summary.js +30 -0
  120. package/dist/universal-conversion-plan.js +25 -9
  121. package/dist/universal-conversion-route-metadata.js +96 -0
  122. package/dist/universal-conversion-route-operations.js +7 -0
  123. package/dist/universal-representation-coverage.js +193 -0
  124. package/package.json +1 -1
@@ -0,0 +1,190 @@
1
+ import { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds } from './js-ts-safe-merge-constants.js';
2
+ import { addConflict, detectLineEnding, normalizeLineEndings } from './js-ts-safe-merge-context.js';
3
+ import { importSpecifierCanonical } from './js-ts-safe-merge-ledger.js';
4
+
5
+ export function createSourceMergePlan(base, worker, head, workerPlan, headPlan, context) {
6
+ const edits = [];
7
+ let importSpecifierAdditions = 0;
8
+ for (const baseEntry of base.entries.filter((entry) => entry.kind === 'import')) {
9
+ const workerAdditions = workerPlan.importAdditions.get(baseEntry.key) ?? [];
10
+ const headAdditions = headPlan.importAdditions.get(baseEntry.key) ?? [];
11
+ if (!workerAdditions.length && !headAdditions.length) continue;
12
+ importSpecifierAdditions += workerAdditions.length + headAdditions.length;
13
+ const headEntry = head.entries.find((entry) => entry.key === baseEntry.key);
14
+ if (!headEntry) {
15
+ addConflict(context, {
16
+ code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
17
+ gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
18
+ side: 'head',
19
+ message: 'Head source is missing a base import anchor.',
20
+ details: { key: baseEntry.key }
21
+ });
22
+ continue;
23
+ }
24
+ edits.push({
25
+ kind: 'replace-import',
26
+ start: headEntry.start,
27
+ end: headEntry.end,
28
+ text: renderImportStatement(baseEntry.importInfo, mergedImportSpecifiers(baseEntry.importInfo.specifiers, workerAdditions, headAdditions))
29
+ });
30
+ }
31
+
32
+ const insertionGroups = variantInsertionGroups(worker, workerPlan.baseKeys, 'worker', context);
33
+ const headInsertionGroups = variantInsertionGroups(head, headPlan.baseKeys, 'head', context);
34
+ const headInsertionGroupsByAnchor = new Map(headInsertionGroups.map((group) => [insertionGroupKey(group), group]));
35
+ const headEntriesByKey = new Map(head.entries.map((entry) => [entry.key, entry]));
36
+ let topLevelDeclarationAdditions = 0;
37
+ for (const group of insertionGroups) {
38
+ const anchor = headEntriesByKey.get(group.anchorKey);
39
+ if (!anchor) {
40
+ addConflict(context, {
41
+ code: JsTsSafeMergeConflictCodes.insertionAnchorMissing,
42
+ gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
43
+ side: 'head',
44
+ message: 'Head source is missing a declaration insertion anchor.',
45
+ details: { anchorKey: group.anchorKey, mode: group.mode }
46
+ });
47
+ continue;
48
+ }
49
+ const headGroup = headInsertionGroupsByAnchor.get(insertionGroupKey(group));
50
+ const insertionSpan = declarationInsertionSpan(head.sourceText, anchor, group.mode, headGroup);
51
+ const entries = mergedDeclarationEntries(group.entries, headGroup?.entries ?? []);
52
+ edits.push({
53
+ kind: 'insert-declarations',
54
+ start: insertionSpan.start,
55
+ end: insertionSpan.end,
56
+ text: declarationInsertionText(entries, detectLineEnding(head.sourceText))
57
+ });
58
+ topLevelDeclarationAdditions += group.entries.length;
59
+ }
60
+
61
+ return {
62
+ edits,
63
+ importSpecifierAdditions,
64
+ topLevelDeclarationAdditions
65
+ };
66
+ }
67
+
68
+ function variantInsertionGroups(variant, baseKeys, side, context) {
69
+ const groups = [];
70
+ let index = 0;
71
+ while (index < variant.entries.length) {
72
+ const entry = variant.entries[index];
73
+ if (baseKeys.has(entry.key)) {
74
+ index += 1;
75
+ continue;
76
+ }
77
+ const start = index;
78
+ while (index < variant.entries.length && !baseKeys.has(variant.entries[index].key)) index += 1;
79
+ const entries = variant.entries.slice(start, index);
80
+ const previousBase = findPreviousBaseEntry(variant.entries, start, baseKeys);
81
+ const nextBase = findNextBaseEntry(variant.entries, index, baseKeys);
82
+ if (!previousBase && !nextBase) {
83
+ addConflict(context, {
84
+ code: JsTsSafeMergeConflictCodes.ambiguousInsertionPoint,
85
+ gateId: JsTsSafeMergeGateIds.resolvedInsertionAnchors,
86
+ side,
87
+ message: `${side} additions have no stable base declaration or import anchor.`,
88
+ details: { entries: entries.map((item) => item.names?.[0] ?? item.key) }
89
+ });
90
+ continue;
91
+ }
92
+ groups.push(previousBase
93
+ ? { mode: 'after', anchorKey: previousBase.key, entries }
94
+ : { mode: 'before', anchorKey: nextBase.key, entries });
95
+ }
96
+ return groups;
97
+ }
98
+
99
+ function insertionGroupKey(group) {
100
+ return `${group.mode}:${group.anchorKey}`;
101
+ }
102
+
103
+ function findPreviousBaseEntry(entries, startIndex, baseKeys) {
104
+ for (let index = startIndex - 1; index >= 0; index -= 1) {
105
+ if (baseKeys.has(entries[index].key)) return entries[index];
106
+ }
107
+ return undefined;
108
+ }
109
+
110
+ function findNextBaseEntry(entries, startIndex, baseKeys) {
111
+ for (let index = startIndex; index < entries.length; index += 1) {
112
+ if (baseKeys.has(entries[index].key)) return entries[index];
113
+ }
114
+ return undefined;
115
+ }
116
+
117
+ export function applySourceMergePlan(sourceText, mergePlan) {
118
+ return [...mergePlan.edits]
119
+ .sort((left, right) => right.start - left.start || right.end - left.end)
120
+ .reduce((current, edit) => `${current.slice(0, edit.start)}${edit.text}${current.slice(edit.end)}`, sourceText);
121
+ }
122
+
123
+ function mergedImportSpecifiers(baseSpecifiers, workerAdditions, headAdditions) {
124
+ const seen = new Set(baseSpecifiers.map((specifier) => specifier.canonical));
125
+ const additions = [];
126
+ for (const specifier of [...workerAdditions, ...headAdditions]) {
127
+ if (seen.has(specifier.canonical)) continue;
128
+ seen.add(specifier.canonical);
129
+ additions.push(specifier);
130
+ }
131
+ additions.sort((left, right) => left.canonical.localeCompare(right.canonical));
132
+ return [...baseSpecifiers, ...additions];
133
+ }
134
+
135
+ function renderImportStatement(importInfo, specifiers) {
136
+ const clause = [];
137
+ if (importInfo.defaultLocalName) clause.push(importInfo.defaultLocalName);
138
+ if (importInfo.namespaceLocalName) clause.push(`* as ${importInfo.namespaceLocalName}`);
139
+ if (specifiers.length) clause.push(`{ ${specifiers.map(importSpecifierCanonical).join(', ')} }`);
140
+ const importType = importInfo.typeOnly ? 'type ' : '';
141
+ return `import ${importType}${clause.join(', ')} from ${importInfo.quote}${importInfo.moduleSpecifier}${importInfo.quote};`;
142
+ }
143
+
144
+ function declarationInsertionText(entries, lineEnding) {
145
+ return entries
146
+ .map((entry) => normalizeLineEndings(entry.text.trimEnd(), lineEnding))
147
+ .map((text) => text.endsWith(lineEnding) ? text : `${text}${lineEnding}`)
148
+ .join('');
149
+ }
150
+
151
+ function mergedDeclarationEntries(workerEntries, headEntries) {
152
+ const entriesByKey = new Map();
153
+ for (const entry of [...workerEntries, ...headEntries]) {
154
+ const key = `${entry.kind}:${entry.key}:${entry.text.trim()}`;
155
+ if (!entriesByKey.has(key)) entriesByKey.set(key, entry);
156
+ }
157
+ return [...entriesByKey.values()].sort((left, right) => {
158
+ const keyOrder = left.key.localeCompare(right.key);
159
+ if (keyOrder !== 0) return keyOrder;
160
+ return left.text.trim().localeCompare(right.text.trim());
161
+ });
162
+ }
163
+
164
+ function declarationInsertionSpan(sourceText, anchor, mode, headGroup) {
165
+ if (!headGroup?.entries.length) {
166
+ const offset = mode === 'after'
167
+ ? offsetAfterEntryLine(sourceText, anchor)
168
+ : offsetBeforeEntryLine(sourceText, anchor);
169
+ return { start: offset, end: offset };
170
+ }
171
+ if (mode === 'after') {
172
+ return {
173
+ start: offsetAfterEntryLine(sourceText, anchor),
174
+ end: offsetAfterEntryLine(sourceText, headGroup.entries[headGroup.entries.length - 1])
175
+ };
176
+ }
177
+ return {
178
+ start: offsetBeforeEntryLine(sourceText, headGroup.entries[0]),
179
+ end: offsetBeforeEntryLine(sourceText, anchor)
180
+ };
181
+ }
182
+
183
+ function offsetAfterEntryLine(sourceText, entry) {
184
+ const lineEnd = sourceText.indexOf('\n', entry.end);
185
+ return lineEnd === -1 ? sourceText.length : lineEnd + 1;
186
+ }
187
+
188
+ function offsetBeforeEntryLine(sourceText, entry) {
189
+ return sourceText.lastIndexOf('\n', Math.max(0, entry.start - 1)) + 1;
190
+ }
@@ -0,0 +1,175 @@
1
+ import { analyzeVariantLedger, validateIndependentAdditions } from './js-ts-safe-merge-analyze.js';
2
+ import { addConflict, blockedResult, compactRecord, createMergeContext, gatesFor } from './js-ts-safe-merge-context.js';
3
+ import {
4
+ JsTsSafeMergeConflictCodes,
5
+ JsTsSafeMergeGateIds,
6
+ JsTsSafeMergeStatuses,
7
+ jsTsSafeMergeGateOrder
8
+ } from './js-ts-safe-merge-constants.js';
9
+ import { indexBaseLedger, scanJsTsTopLevelLedger, validateLedgerUniqueness } from './js-ts-safe-merge-ledger.js';
10
+ import { applySourceMergePlan, createSourceMergePlan } from './js-ts-safe-merge-plan.js';
11
+
12
+ export { JsTsSafeMergeConflictCodes, JsTsSafeMergeGateIds, JsTsSafeMergeStatuses };
13
+
14
+ export function safeMergeJsTsImportsAndDeclarations(input = {}) {
15
+ const context = createMergeContext(input);
16
+ const baseSourceText = input.baseSourceText;
17
+ const workerSourceText = input.workerSourceText;
18
+ const headSourceText = input.headSourceText;
19
+
20
+ if (typeof baseSourceText !== 'string' || typeof workerSourceText !== 'string' || typeof headSourceText !== 'string') {
21
+ addConflict(context, {
22
+ code: JsTsSafeMergeConflictCodes.invalidInput,
23
+ gateId: JsTsSafeMergeGateIds.parseLedger,
24
+ message: 'baseSourceText, workerSourceText, and headSourceText must be strings.',
25
+ details: {
26
+ baseSourceText: typeof baseSourceText,
27
+ workerSourceText: typeof workerSourceText,
28
+ headSourceText: typeof headSourceText
29
+ }
30
+ });
31
+ return blockedResult(context);
32
+ }
33
+
34
+ validateStaleSourceHashes(input, context);
35
+ validateSourceLedgerSpans(input, context);
36
+ if (context.conflicts.length) return blockedResult(context);
37
+
38
+ const base = scanJsTsTopLevelLedger(baseSourceText, 'base', context);
39
+ const worker = scanJsTsTopLevelLedger(workerSourceText, 'worker', context);
40
+ const head = scanJsTsTopLevelLedger(headSourceText, 'head', context);
41
+ if (context.conflicts.length) return blockedResult(context, { base, worker, head });
42
+
43
+ validateLedgerUniqueness(base, context);
44
+ validateLedgerUniqueness(worker, context);
45
+ validateLedgerUniqueness(head, context);
46
+ if (context.conflicts.length) return blockedResult(context, { base, worker, head });
47
+
48
+ const baseIndex = indexBaseLedger(base, context);
49
+ if (context.conflicts.length) return blockedResult(context, { base, worker, head });
50
+
51
+ const workerPlan = analyzeVariantLedger(base, worker, baseIndex, 'worker', context);
52
+ const headPlan = analyzeVariantLedger(base, head, baseIndex, 'head', context);
53
+ validateIndependentAdditions(base, workerPlan, headPlan, context);
54
+ if (context.conflicts.length) return blockedResult(context, { base, worker, head });
55
+
56
+ const mergePlan = createSourceMergePlan(base, worker, head, workerPlan, headPlan, context);
57
+ if (context.conflicts.length) return blockedResult(context, { base, worker, head });
58
+
59
+ const mergedSourceText = applySourceMergePlan(headSourceText, mergePlan);
60
+ const merged = scanJsTsTopLevelLedger(mergedSourceText, 'merged', context);
61
+ if (!context.conflicts.length) validateLedgerUniqueness(merged, context);
62
+ if (context.conflicts.length) return blockedResult(context, { base, worker, head, merged });
63
+
64
+ return {
65
+ kind: 'frontier.lang.jsTsSafeMerge',
66
+ version: 1,
67
+ schema: 'frontier.lang.jsTsSafeMerge.v1',
68
+ id: context.id,
69
+ status: JsTsSafeMergeStatuses.merged,
70
+ sourcePath: context.sourcePath,
71
+ language: context.language,
72
+ mergedSourceText,
73
+ outputSourceText: mergedSourceText,
74
+ conflicts: [],
75
+ gates: gatesFor(context),
76
+ admission: {
77
+ status: 'auto-merge-candidate',
78
+ action: 'apply',
79
+ reviewRequired: false,
80
+ autoApplyCandidate: true,
81
+ autoMergeClaim: false,
82
+ semanticEquivalenceClaim: false,
83
+ reasonCodes: []
84
+ },
85
+ summary: {
86
+ importSpecifierAdditions: mergePlan.importSpecifierAdditions,
87
+ topLevelDeclarationAdditions: mergePlan.topLevelDeclarationAdditions,
88
+ changedExistingDeclarations: 0,
89
+ conflicts: 0,
90
+ gatesPassed: jsTsSafeMergeGateOrder.length
91
+ },
92
+ metadata: compactRecord({
93
+ workerChangeSetId: input.workerChangeSetId,
94
+ headChangeSetId: input.headChangeSetId,
95
+ baseHash: input.baseHash,
96
+ workerHash: input.workerHash,
97
+ headHash: input.headHash,
98
+ expectedSourceHash: input.expectedSourceHash,
99
+ currentSourceHash: input.currentSourceHash
100
+ })
101
+ };
102
+ }
103
+
104
+ function validateStaleSourceHashes(input, context) {
105
+ for (const pair of [
106
+ ['expectedSourceHash', 'currentSourceHash', 'head'],
107
+ ['expectedHeadHash', 'headHash', 'head'],
108
+ ['expectedBaseHash', 'baseHash', 'base'],
109
+ ['expectedWorkerHash', 'workerHash', 'worker']
110
+ ]) {
111
+ const [expectedField, currentField, side] = pair;
112
+ const expected = input[expectedField];
113
+ const current = input[currentField];
114
+ if (typeof expected !== 'string' || typeof current !== 'string' || expected === current) continue;
115
+ addConflict(context, {
116
+ code: JsTsSafeMergeConflictCodes.staleSourceHash,
117
+ gateId: JsTsSafeMergeGateIds.parseLedger,
118
+ side,
119
+ message: `${side} source hash is stale for safe merge anchors.`,
120
+ details: { expectedField, currentField, expected, current }
121
+ });
122
+ }
123
+ }
124
+
125
+ function validateSourceLedgerSpans(input, context) {
126
+ if (!input.requireSourceLedgerSpans && !input.sourceLedgers && !input.sourceLedger) return;
127
+ for (const side of ['base', 'worker', 'head']) {
128
+ const ledger = sourceLedgerForSide(input, side);
129
+ if (!ledger) {
130
+ addConflict(context, {
131
+ code: JsTsSafeMergeConflictCodes.missingSourceLedgerSpan,
132
+ gateId: JsTsSafeMergeGateIds.parseLedger,
133
+ side,
134
+ message: `${side} source is missing source ledger span evidence.`,
135
+ details: { side, missing: 'source-ledger' }
136
+ });
137
+ continue;
138
+ }
139
+ const spans = Array.isArray(ledger.spans) ? ledger.spans : Array.isArray(ledger.entries) ? ledger.entries : [];
140
+ if (!spans.length) {
141
+ addConflict(context, {
142
+ code: JsTsSafeMergeConflictCodes.missingSourceLedgerSpan,
143
+ gateId: JsTsSafeMergeGateIds.parseLedger,
144
+ side,
145
+ message: `${side} source ledger does not include span entries.`,
146
+ details: { side, missing: 'spans' }
147
+ });
148
+ continue;
149
+ }
150
+ const missingIndex = spans.findIndex((entry) => !hasLedgerSpan(entry));
151
+ if (missingIndex >= 0) {
152
+ addConflict(context, {
153
+ code: JsTsSafeMergeConflictCodes.missingSourceLedgerSpan,
154
+ gateId: JsTsSafeMergeGateIds.parseLedger,
155
+ side,
156
+ message: `${side} source ledger contains an entry without a source span.`,
157
+ details: { side, index: missingIndex, id: spans[missingIndex]?.id, kind: spans[missingIndex]?.kind }
158
+ });
159
+ }
160
+ }
161
+ }
162
+
163
+ function sourceLedgerForSide(input, side) {
164
+ return input.sourceLedgers?.[side]
165
+ ?? input[`${side}SourceLedger`]
166
+ ?? (input.sourceLedgerSide === side ? input.sourceLedger : undefined);
167
+ }
168
+
169
+ function hasLedgerSpan(entry) {
170
+ const span = entry?.span ?? entry?.sourceSpan;
171
+ if (!span) return false;
172
+ const hasOffsets = Number.isFinite(span.start) && Number.isFinite(span.end);
173
+ const hasLines = Number.isFinite(span.startLine) && Number.isFinite(span.endLine);
174
+ return hasOffsets || hasLines;
175
+ }
@@ -0,0 +1,77 @@
1
+ export const JsTsSemanticConflictSidecarClasses = Object.freeze([
2
+ 'same-region',
3
+ 'delete-modify',
4
+ 'duplicate-export',
5
+ 'duplicate-member',
6
+ 'ordered-list-conflict',
7
+ 'parser-ledger-loss',
8
+ 'stale-source-hash',
9
+ 'unsupported-syntax'
10
+ ]);
11
+
12
+ export const classDefaults = Object.freeze({
13
+ 'same-region': {
14
+ severity: 'error',
15
+ risk: 'high',
16
+ readiness: 'blocked',
17
+ reasonCodes: ['same-region-concurrent-edit'],
18
+ suggestedOutcome: 'manual-merge-required'
19
+ },
20
+ 'delete-modify': {
21
+ severity: 'error',
22
+ risk: 'high',
23
+ readiness: 'blocked',
24
+ reasonCodes: ['delete-modify-same-region'],
25
+ suggestedOutcome: 'choose-delete-or-port-modification'
26
+ },
27
+ 'duplicate-export': {
28
+ severity: 'error',
29
+ risk: 'high',
30
+ readiness: 'blocked',
31
+ reasonCodes: ['duplicate-export-name'],
32
+ suggestedOutcome: 'rename-or-remove-duplicate-export'
33
+ },
34
+ 'duplicate-member': {
35
+ severity: 'error',
36
+ risk: 'high',
37
+ readiness: 'blocked',
38
+ reasonCodes: ['duplicate-member-name'],
39
+ suggestedOutcome: 'rename-or-remove-duplicate-member'
40
+ },
41
+ 'ordered-list-conflict': {
42
+ severity: 'warning',
43
+ risk: 'medium',
44
+ readiness: 'needs-review',
45
+ reasonCodes: ['ordered-list-concurrent-position'],
46
+ suggestedOutcome: 'preserve-intended-order-with-human-review'
47
+ },
48
+ 'parser-ledger-loss': {
49
+ severity: 'error',
50
+ risk: 'high',
51
+ readiness: 'blocked',
52
+ reasonCodes: ['parser-or-ledger-loss'],
53
+ suggestedOutcome: 'rerun-parser-and-ledger-before-merge'
54
+ },
55
+ 'stale-source-hash': {
56
+ severity: 'error',
57
+ risk: 'high',
58
+ readiness: 'blocked',
59
+ reasonCodes: ['stale-source-hash'],
60
+ suggestedOutcome: 'rerun-semantic-import-before-merge'
61
+ },
62
+ 'unsupported-syntax': {
63
+ severity: 'error',
64
+ risk: 'high',
65
+ readiness: 'blocked',
66
+ reasonCodes: ['unsupported-js-ts-syntax'],
67
+ suggestedOutcome: 'fall-back-to-textual-review'
68
+ }
69
+ });
70
+
71
+ export const severityRank = Object.freeze({ info: 1, warning: 2, error: 3 });
72
+ export const riskRank = Object.freeze({ low: 1, medium: 2, high: 3 });
73
+ export const orderedChangeKinds = /^(?:insert|add|move|reorder|replace|modify|update)$/i;
74
+ export const deleteChangeKinds = /^(?:delete|remove|removed|deleted)$/i;
75
+ export const modifyChangeKinds = /^(?:modify|modified|replace|update|edit|move|reorder|insert|add)$/i;
76
+ export const parserLedgerLossPattern = /(?:parser|parse|ledger|source[-_ ]?map|anchor|projection).*(?:loss|lost|missing|failed|error)|(?:loss|lost|missing).*(?:parser|ledger|source[-_ ]?map|anchor|projection)/i;
77
+ export const unsupportedSyntaxPattern = /unsupported|unhandled|unknown syntax|syntax unsupported/i;
@@ -0,0 +1,195 @@
1
+ import { idFragment, normalizeSemanticMergeReadiness, uniqueStrings } from './native-import-utils.js';
2
+ import { classDefaults, JsTsSemanticConflictSidecarClasses, orderedChangeKinds, parserLedgerLossPattern, unsupportedSyntaxPattern } from './js-ts-semantic-conflict-sidecar-constants.js';
3
+ import { affectedFromEntries, normalizeAffected, normalizeChange, normalizeDeclaration, sameChangeRegion } from './js-ts-semantic-conflict-sidecar-normalize.js';
4
+ import { array, changePairs, compactRecord, duplicateGroups, firstString, isDeleteChange, isInsertChange, isModifyChange, lossReasonCodes, lossText, normalizeRisk, normalizeSeverity, strings } from './js-ts-semantic-conflict-sidecar-utils.js';
5
+
6
+ export function explicitSidecars(input, context) {
7
+ return [
8
+ ...array(input.conflicts),
9
+ ...array(input.sidecars),
10
+ ...array(input.conflictSidecars)
11
+ ]
12
+ .filter((record) => JsTsSemanticConflictSidecarClasses.includes(record?.class))
13
+ .map((record) => conflictSidecar(record.class, record, context));
14
+ }
15
+
16
+ export function sameRegionSidecars(changes, context) {
17
+ const records = [];
18
+ for (const [left, right] of changePairs(changes)) {
19
+ if (!sameChangeRegion(left, right)) continue;
20
+ records.push(conflictSidecar('same-region', {
21
+ id: `js_ts_conflict_${idFragment(left.id)}_${idFragment(right.id)}_same_region`,
22
+ affected: affectedFromEntries([left, right], context),
23
+ reasonCodes: ['same-region-concurrent-edit', ...left.reasonCodes, ...right.reasonCodes],
24
+ metadata: { leftChangeId: left.id, rightChangeId: right.id, leftChangeKind: left.changeKind, rightChangeKind: right.changeKind }
25
+ }, context));
26
+ }
27
+ return records;
28
+ }
29
+
30
+ export function deleteModifySidecars(changes, context) {
31
+ const records = [];
32
+ for (const [left, right] of changePairs(changes)) {
33
+ if (!sameChangeRegion(left, right)) continue;
34
+ if (!(isDeleteChange(left) && isModifyChange(right)) && !(isDeleteChange(right) && isModifyChange(left))) continue;
35
+ const deleted = isDeleteChange(left) ? left : right;
36
+ const modified = deleted === left ? right : left;
37
+ records.push(conflictSidecar('delete-modify', {
38
+ id: `js_ts_conflict_${idFragment(deleted.id)}_${idFragment(modified.id)}_delete_modify`,
39
+ affected: affectedFromEntries([deleted, modified], context),
40
+ reasonCodes: ['delete-modify-same-region', ...deleted.reasonCodes, ...modified.reasonCodes],
41
+ metadata: { deletedChangeId: deleted.id, modifiedChangeId: modified.id, deletedChangeKind: deleted.changeKind, modifiedChangeKind: modified.changeKind }
42
+ }, context));
43
+ }
44
+ return records;
45
+ }
46
+
47
+ export function duplicateDeclarationSidecars(input, changes, context) {
48
+ const declarations = [
49
+ ...array(input.declarations),
50
+ ...array(input.exports).map((entry) => ({ ...entry, exported: true })),
51
+ ...array(input.members).map((entry) => ({ ...entry, member: true })),
52
+ ...array(input.symbols),
53
+ ...changes.filter((change) => isInsertChange(change) && (change.memberName || change.exportName || change.symbolName))
54
+ ].map((entry, index) => normalizeDeclaration(entry, index, context)).filter((entry) => entry.name);
55
+ return [
56
+ ...duplicateGroups(declarations.filter((entry) => entry.exported), (entry) => [entry.sourcePath, entry.name].join('|'))
57
+ .map((group) => duplicateRecord('duplicate-export', group, context)),
58
+ ...duplicateGroups(declarations.filter((entry) => entry.member || entry.containerKey), (entry) => [entry.sourcePath, entry.containerKey, entry.name].join('|'))
59
+ .map((group) => duplicateRecord('duplicate-member', group, context))
60
+ ];
61
+ }
62
+
63
+ export function orderedListSidecars(input, changes, context) {
64
+ const explicitLists = array(input.orderedLists).flatMap((list, listIndex) =>
65
+ array(list.changes ?? list.operations ?? list.edits).map((change, changeIndex) => normalizeChange({
66
+ ...change,
67
+ listKey: change.listKey ?? list.key ?? list.id,
68
+ orderedListKey: change.orderedListKey ?? list.key ?? list.id,
69
+ sourcePath: change.sourcePath ?? list.sourcePath,
70
+ sourceSpan: change.sourceSpan ?? list.sourceSpan,
71
+ id: change.id ?? `${list.id ?? list.key ?? 'ordered_list'}_${changeIndex}`,
72
+ side: change.side ?? change.author ?? `list_${listIndex}`
73
+ }, context, `ordered_list_${listIndex}_${changeIndex}`))
74
+ );
75
+ const orderedChanges = [...changes, ...explicitLists].filter((change) =>
76
+ change.listKey && orderedChangeKinds.test(change.changeKind)
77
+ );
78
+ const groups = duplicateGroups(orderedChanges, (change) => [
79
+ change.sourcePath,
80
+ change.listKey,
81
+ firstString(change.index, change.position, change.orderKey, change.beforeKey, change.afterKey, change.anchorKey)
82
+ ].join('|'));
83
+ return groups.map((group) => conflictSidecar('ordered-list-conflict', {
84
+ id: `js_ts_conflict_${idFragment(group[0].listKey)}_${idFragment(firstString(group[0].index, group[0].position, group[0].orderKey, 'position'))}_ordered_list`,
85
+ affected: affectedFromEntries(group, context),
86
+ reasonCodes: ['ordered-list-concurrent-position', ...group.flatMap((entry) => entry.reasonCodes)],
87
+ metadata: {
88
+ listKey: group[0].listKey,
89
+ position: firstString(group[0].index, group[0].position, group[0].orderKey, group[0].beforeKey, group[0].afterKey),
90
+ changeIds: group.map((entry) => entry.id)
91
+ }
92
+ }, context));
93
+ }
94
+
95
+ export function parserLedgerLossSidecars(input, context) {
96
+ const losses = [
97
+ ...array(input.parserLosses),
98
+ ...array(input.ledgerLosses),
99
+ ...array(input.losses),
100
+ ...array(input.diagnostics)
101
+ ].filter((entry) => parserLedgerLossPattern.test(lossText(entry)) || entry?.kind === 'parser-ledger-loss');
102
+ if (!losses.length) return [];
103
+ return [conflictSidecar('parser-ledger-loss', {
104
+ id: `js_ts_conflict_${idFragment(context.sourcePath ?? context.language ?? 'source')}_parser_ledger_loss`,
105
+ severity: losses.some((entry) => entry.severity === 'error' || entry.status === 'failed') ? 'error' : undefined,
106
+ affected: affectedFromEntries(losses, context),
107
+ reasonCodes: ['parser-or-ledger-loss', ...losses.flatMap(lossReasonCodes)],
108
+ metadata: { lossIds: uniqueStrings(losses.map((entry) => entry.id)), lossKinds: uniqueStrings(losses.map((entry) => entry.kind ?? entry.code)) }
109
+ }, context)];
110
+ }
111
+
112
+ export function staleSourceHashSidecars(input, context) {
113
+ if (!context.expectedSourceHash || !context.currentSourceHash) return [];
114
+ if (context.expectedSourceHash === context.currentSourceHash && input.staleSourceHash !== true) return [];
115
+ return [conflictSidecar('stale-source-hash', {
116
+ id: `js_ts_conflict_${idFragment(context.sourcePath ?? context.language ?? 'source')}_stale_source_hash`,
117
+ affected: affectedFromEntries([{ sourcePath: context.sourcePath, sourceHash: context.currentSourceHash, key: context.sourcePath }], context),
118
+ reasonCodes: ['stale-source-hash', 'expected-source-hash-mismatch'],
119
+ metadata: { expectedSourceHash: context.expectedSourceHash, currentSourceHash: context.currentSourceHash }
120
+ }, context)];
121
+ }
122
+
123
+ export function unsupportedSyntaxSidecars(input, context) {
124
+ const entries = [
125
+ ...array(input.unsupportedSyntax),
126
+ ...array(input.syntaxUnsupported),
127
+ ...array(input.syntaxLosses),
128
+ ...array(input.losses).filter((entry) => unsupportedSyntaxPattern.test(lossText(entry)))
129
+ ];
130
+ if (!entries.length) return [];
131
+ return entries.map((entry, index) => conflictSidecar('unsupported-syntax', {
132
+ id: entry.id ?? `js_ts_conflict_${idFragment(context.sourcePath ?? context.language ?? 'source')}_${index + 1}_unsupported_syntax`,
133
+ affected: affectedFromEntries([entry], context),
134
+ reasonCodes: ['unsupported-js-ts-syntax', ...lossReasonCodes(entry)],
135
+ metadata: compactRecord({
136
+ syntaxKind: entry.kind ?? entry.syntaxKind,
137
+ parser: entry.parser,
138
+ message: entry.message ?? entry.summary
139
+ })
140
+ }, context));
141
+ }
142
+
143
+ export function duplicateRecord(conflictClass, group, context) {
144
+ return conflictSidecar(conflictClass, {
145
+ id: `js_ts_conflict_${idFragment(group[0].sourcePath ?? context.sourcePath ?? 'source')}_${idFragment(group[0].containerKey ?? 'export')}_${idFragment(group[0].name)}_${conflictClass.replace(/-/g, '_')}`,
146
+ affected: affectedFromEntries(group, context),
147
+ reasonCodes: [conflictClass === 'duplicate-member' ? 'duplicate-member-name' : 'duplicate-export-name'],
148
+ metadata: {
149
+ duplicateName: group[0].name,
150
+ containerKey: group[0].containerKey,
151
+ declarationIds: group.map((entry) => entry.id)
152
+ }
153
+ }, context);
154
+ }
155
+
156
+ export function conflictSidecar(conflictClass, input, context) {
157
+ const defaults = classDefaults[conflictClass] ?? classDefaults['same-region'];
158
+ const affected = normalizeAffected(input.affected ?? input, context);
159
+ const reasonCodes = uniqueStrings([
160
+ ...defaults.reasonCodes,
161
+ ...strings(input.reasonCode),
162
+ ...strings(input.reasonCodes)
163
+ ]);
164
+ const severity = normalizeSeverity(input.severity) ?? defaults.severity;
165
+ const risk = normalizeRisk(input.risk) ?? defaults.risk;
166
+ const readiness = normalizeSemanticMergeReadiness(input.readiness) ?? defaults.readiness;
167
+ const suggestedOutcome = input.suggestedOutcome ?? input.outcome ?? defaults.suggestedOutcome;
168
+ const id = input.id ?? `js_ts_conflict_${idFragment(conflictClass)}_${idFragment([
169
+ ...affected.keys,
170
+ ...affected.sourcePaths,
171
+ ...reasonCodes
172
+ ].join('_'))}`;
173
+ return {
174
+ kind: 'frontier.lang.jsTsSemanticMergeConflictSidecar',
175
+ version: 1,
176
+ schema: 'frontier.lang.jsTsSemanticMergeConflictSidecar.v1',
177
+ id,
178
+ class: conflictClass,
179
+ severity,
180
+ risk,
181
+ readiness,
182
+ affected,
183
+ reasonCodes,
184
+ suggestedOutcome,
185
+ explanation: {
186
+ class: conflictClass,
187
+ severity,
188
+ risk,
189
+ affected,
190
+ reasonCodes,
191
+ suggestedOutcome
192
+ },
193
+ metadata: compactRecord(input.metadata)
194
+ };
195
+ }