@stoneforge/quarry 1.10.2 → 1.13.0

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 (137) hide show
  1. package/README.md +3 -1
  2. package/dist/cli/commands/admin.d.ts.map +1 -1
  3. package/dist/cli/commands/admin.js +313 -3
  4. package/dist/cli/commands/admin.js.map +1 -1
  5. package/dist/cli/commands/auto-link-helper.d.ts +33 -0
  6. package/dist/cli/commands/auto-link-helper.d.ts.map +1 -0
  7. package/dist/cli/commands/auto-link-helper.js +73 -0
  8. package/dist/cli/commands/auto-link-helper.js.map +1 -0
  9. package/dist/cli/commands/crud.d.ts +1 -0
  10. package/dist/cli/commands/crud.d.ts.map +1 -1
  11. package/dist/cli/commands/crud.js +44 -5
  12. package/dist/cli/commands/crud.js.map +1 -1
  13. package/dist/cli/commands/docs.d.ts +1 -0
  14. package/dist/cli/commands/docs.d.ts.map +1 -1
  15. package/dist/cli/commands/docs.js +81 -1
  16. package/dist/cli/commands/docs.js.map +1 -1
  17. package/dist/cli/commands/external-sync.d.ts +17 -0
  18. package/dist/cli/commands/external-sync.d.ts.map +1 -0
  19. package/dist/cli/commands/external-sync.js +1647 -0
  20. package/dist/cli/commands/external-sync.js.map +1 -0
  21. package/dist/cli/commands/log.d.ts +18 -0
  22. package/dist/cli/commands/log.d.ts.map +1 -0
  23. package/dist/cli/commands/log.js +282 -0
  24. package/dist/cli/commands/log.js.map +1 -0
  25. package/dist/cli/commands/metrics.d.ts +9 -0
  26. package/dist/cli/commands/metrics.d.ts.map +1 -0
  27. package/dist/cli/commands/metrics.js +219 -0
  28. package/dist/cli/commands/metrics.js.map +1 -0
  29. package/dist/cli/runner.d.ts.map +1 -1
  30. package/dist/cli/runner.js +8 -0
  31. package/dist/cli/runner.js.map +1 -1
  32. package/dist/config/config.d.ts.map +1 -1
  33. package/dist/config/config.js +28 -0
  34. package/dist/config/config.js.map +1 -1
  35. package/dist/config/defaults.d.ts +13 -1
  36. package/dist/config/defaults.d.ts.map +1 -1
  37. package/dist/config/defaults.js +21 -0
  38. package/dist/config/defaults.js.map +1 -1
  39. package/dist/config/file.d.ts.map +1 -1
  40. package/dist/config/file.js +61 -0
  41. package/dist/config/file.js.map +1 -1
  42. package/dist/config/index.d.ts +3 -3
  43. package/dist/config/index.d.ts.map +1 -1
  44. package/dist/config/index.js +2 -2
  45. package/dist/config/index.js.map +1 -1
  46. package/dist/config/merge.d.ts.map +1 -1
  47. package/dist/config/merge.js +46 -1
  48. package/dist/config/merge.js.map +1 -1
  49. package/dist/config/types.d.ts +63 -1
  50. package/dist/config/types.d.ts.map +1 -1
  51. package/dist/config/types.js +30 -0
  52. package/dist/config/types.js.map +1 -1
  53. package/dist/config/validation.d.ts.map +1 -1
  54. package/dist/config/validation.js +51 -1
  55. package/dist/config/validation.js.map +1 -1
  56. package/dist/external-sync/adapters/task-sync-adapter.d.ts +177 -0
  57. package/dist/external-sync/adapters/task-sync-adapter.d.ts.map +1 -0
  58. package/dist/external-sync/adapters/task-sync-adapter.js +353 -0
  59. package/dist/external-sync/adapters/task-sync-adapter.js.map +1 -0
  60. package/dist/external-sync/auto-link.d.ts +66 -0
  61. package/dist/external-sync/auto-link.d.ts.map +1 -0
  62. package/dist/external-sync/auto-link.js +98 -0
  63. package/dist/external-sync/auto-link.js.map +1 -0
  64. package/dist/external-sync/conflict-resolver.d.ts +170 -0
  65. package/dist/external-sync/conflict-resolver.d.ts.map +1 -0
  66. package/dist/external-sync/conflict-resolver.js +580 -0
  67. package/dist/external-sync/conflict-resolver.js.map +1 -0
  68. package/dist/external-sync/index.d.ts +20 -0
  69. package/dist/external-sync/index.d.ts.map +1 -0
  70. package/dist/external-sync/index.js +20 -0
  71. package/dist/external-sync/index.js.map +1 -0
  72. package/dist/external-sync/provider-registry.d.ts +109 -0
  73. package/dist/external-sync/provider-registry.d.ts.map +1 -0
  74. package/dist/external-sync/provider-registry.js +188 -0
  75. package/dist/external-sync/provider-registry.js.map +1 -0
  76. package/dist/external-sync/providers/github/github-api.d.ts +271 -0
  77. package/dist/external-sync/providers/github/github-api.d.ts.map +1 -0
  78. package/dist/external-sync/providers/github/github-api.js +366 -0
  79. package/dist/external-sync/providers/github/github-api.js.map +1 -0
  80. package/dist/external-sync/providers/github/github-field-map.d.ts +76 -0
  81. package/dist/external-sync/providers/github/github-field-map.d.ts.map +1 -0
  82. package/dist/external-sync/providers/github/github-field-map.js +157 -0
  83. package/dist/external-sync/providers/github/github-field-map.js.map +1 -0
  84. package/dist/external-sync/providers/github/github-provider.d.ts +36 -0
  85. package/dist/external-sync/providers/github/github-provider.d.ts.map +1 -0
  86. package/dist/external-sync/providers/github/github-provider.js +212 -0
  87. package/dist/external-sync/providers/github/github-provider.js.map +1 -0
  88. package/dist/external-sync/providers/github/github-task-adapter.d.ts +135 -0
  89. package/dist/external-sync/providers/github/github-task-adapter.d.ts.map +1 -0
  90. package/dist/external-sync/providers/github/github-task-adapter.js +374 -0
  91. package/dist/external-sync/providers/github/github-task-adapter.js.map +1 -0
  92. package/dist/external-sync/providers/github/index.d.ts +12 -0
  93. package/dist/external-sync/providers/github/index.d.ts.map +1 -0
  94. package/dist/external-sync/providers/github/index.js +15 -0
  95. package/dist/external-sync/providers/github/index.js.map +1 -0
  96. package/dist/external-sync/providers/index.d.ts +9 -0
  97. package/dist/external-sync/providers/index.d.ts.map +1 -0
  98. package/dist/external-sync/providers/index.js +10 -0
  99. package/dist/external-sync/providers/index.js.map +1 -0
  100. package/dist/external-sync/providers/linear/index.d.ts +19 -0
  101. package/dist/external-sync/providers/linear/index.d.ts.map +1 -0
  102. package/dist/external-sync/providers/linear/index.js +19 -0
  103. package/dist/external-sync/providers/linear/index.js.map +1 -0
  104. package/dist/external-sync/providers/linear/linear-api.d.ts +252 -0
  105. package/dist/external-sync/providers/linear/linear-api.d.ts.map +1 -0
  106. package/dist/external-sync/providers/linear/linear-api.js +522 -0
  107. package/dist/external-sync/providers/linear/linear-api.js.map +1 -0
  108. package/dist/external-sync/providers/linear/linear-field-map.d.ts +135 -0
  109. package/dist/external-sync/providers/linear/linear-field-map.d.ts.map +1 -0
  110. package/dist/external-sync/providers/linear/linear-field-map.js +338 -0
  111. package/dist/external-sync/providers/linear/linear-field-map.js.map +1 -0
  112. package/dist/external-sync/providers/linear/linear-provider.d.ts +52 -0
  113. package/dist/external-sync/providers/linear/linear-provider.d.ts.map +1 -0
  114. package/dist/external-sync/providers/linear/linear-provider.js +169 -0
  115. package/dist/external-sync/providers/linear/linear-provider.js.map +1 -0
  116. package/dist/external-sync/providers/linear/linear-task-adapter.d.ts +190 -0
  117. package/dist/external-sync/providers/linear/linear-task-adapter.d.ts.map +1 -0
  118. package/dist/external-sync/providers/linear/linear-task-adapter.js +521 -0
  119. package/dist/external-sync/providers/linear/linear-task-adapter.js.map +1 -0
  120. package/dist/external-sync/providers/linear/linear-types.d.ts +114 -0
  121. package/dist/external-sync/providers/linear/linear-types.d.ts.map +1 -0
  122. package/dist/external-sync/providers/linear/linear-types.js +10 -0
  123. package/dist/external-sync/providers/linear/linear-types.js.map +1 -0
  124. package/dist/external-sync/sync-engine.d.ts +298 -0
  125. package/dist/external-sync/sync-engine.d.ts.map +1 -0
  126. package/dist/external-sync/sync-engine.js +785 -0
  127. package/dist/external-sync/sync-engine.js.map +1 -0
  128. package/dist/index.d.ts +1 -0
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +2 -0
  131. package/dist/index.js.map +1 -1
  132. package/dist/services/inbox.js +1 -1
  133. package/dist/sync/hash.d.ts +5 -0
  134. package/dist/sync/hash.d.ts.map +1 -1
  135. package/dist/sync/hash.js +21 -2
  136. package/dist/sync/hash.js.map +1 -1
  137. package/package.json +11 -5
@@ -0,0 +1,580 @@
1
+ /**
2
+ * Conflict Detection and Resolution Module
3
+ *
4
+ * Detects conflicts during external sync (both local and remote changed since
5
+ * last sync) and resolves them using configurable strategies:
6
+ *
7
+ * - last_write_wins: Compare updatedAt timestamps, most recent wins
8
+ * - local_wins: Stoneforge version always wins
9
+ * - remote_wins: External version always wins
10
+ * - manual: Tag element with sync-conflict for human resolution
11
+ *
12
+ * Supports field-level merge: when different fields changed on each side,
13
+ * both changes are applied automatically. Only same-field changes are conflicts.
14
+ *
15
+ * @module conflict-resolver
16
+ */
17
+ import { createHash } from 'crypto';
18
+ import { computeContentHashSync } from '../sync/hash.js';
19
+ // ============================================================================
20
+ // Conflict Detection
21
+ // ============================================================================
22
+ /**
23
+ * Detect whether a conflict exists between a local element and a remote item.
24
+ *
25
+ * A conflict exists when BOTH of these are true:
26
+ * - Local changed: current element content hash differs from lastPushedHash
27
+ * - Remote changed: current external item hash differs from lastPulledHash
28
+ *
29
+ * If a fieldMapConfig is provided along with baselines, performs field-level
30
+ * analysis to determine which specific fields conflict.
31
+ *
32
+ * @param localElement - The current local element
33
+ * @param remoteItem - The current external item
34
+ * @param syncState - The element's external sync state (from metadata._externalSync)
35
+ * @param options - Optional field-level detection options
36
+ * @returns ConflictInfo if both sides changed, or null if no conflict
37
+ */
38
+ export function detectConflict(localElement, remoteItem, syncState, options) {
39
+ // Check if local changed: current hash differs from lastPushedHash
40
+ const localChanged = hasLocalChanged(localElement, syncState);
41
+ // Check if remote changed: compute a hash of the remote item's relevant fields
42
+ // and compare against lastPulledHash
43
+ const remoteChanged = hasRemoteChanged(remoteItem, syncState);
44
+ // No conflict if only one side (or neither) changed
45
+ if (!localChanged || !remoteChanged) {
46
+ return null;
47
+ }
48
+ // Both sides changed — we have a conflict
49
+ // Perform field-level analysis if possible
50
+ const fieldAnalysis = analyzeFieldChanges(localElement, remoteItem, options);
51
+ return {
52
+ elementId: localElement.id,
53
+ externalId: remoteItem.externalId,
54
+ provider: syncState.provider,
55
+ project: syncState.project,
56
+ localUpdatedAt: localElement.updatedAt,
57
+ remoteUpdatedAt: remoteItem.updatedAt,
58
+ localChangedFields: fieldAnalysis.localChangedFields,
59
+ remoteChangedFields: fieldAnalysis.remoteChangedFields,
60
+ conflictingFields: fieldAnalysis.conflictingFields,
61
+ canFieldMerge: fieldAnalysis.canFieldMerge,
62
+ };
63
+ }
64
+ /**
65
+ * Check whether the local element has changed since last push.
66
+ * Compares current content hash against the stored lastPushedHash.
67
+ */
68
+ function hasLocalChanged(localElement, syncState) {
69
+ if (!syncState.lastPushedHash) {
70
+ // Never pushed — consider it changed
71
+ return true;
72
+ }
73
+ const currentHash = computeContentHashSync(localElement);
74
+ return currentHash.hash !== syncState.lastPushedHash;
75
+ }
76
+ /**
77
+ * Check whether the remote item has changed since last pull.
78
+ * Computes a content hash of the remote item's normalized fields
79
+ * and compares against the stored lastPulledHash.
80
+ */
81
+ function hasRemoteChanged(remoteItem, syncState) {
82
+ if (!syncState.lastPulledHash) {
83
+ // Never pulled — consider it changed
84
+ return true;
85
+ }
86
+ const currentHash = computeExternalItemHash(remoteItem);
87
+ return currentHash !== syncState.lastPulledHash;
88
+ }
89
+ /**
90
+ * Compute a deterministic hash for an external item's content fields.
91
+ * Used for change detection on the remote side.
92
+ *
93
+ * Normalizes the item to a consistent shape for hashing:
94
+ * sorted keys, JSON stringified, SHA256.
95
+ */
96
+ export function computeExternalItemHash(item) {
97
+ // Extract the content-relevant fields (exclude volatile fields like raw)
98
+ const hashableFields = {
99
+ title: item.title,
100
+ body: item.body ?? '',
101
+ state: item.state,
102
+ labels: [...item.labels].sort(),
103
+ assignees: [...item.assignees].sort(),
104
+ };
105
+ // Sort keys for determinism and hash
106
+ const sortedKeys = Object.keys(hashableFields).sort();
107
+ const sorted = {};
108
+ for (const key of sortedKeys) {
109
+ sorted[key] = hashableFields[key];
110
+ }
111
+ const input = `external:${JSON.stringify(sorted)}`;
112
+ return hashString(input);
113
+ }
114
+ /**
115
+ * Analyze which fields changed on each side.
116
+ *
117
+ * When baselines and field map config are provided, performs precise
118
+ * field-level diffing. Otherwise, falls back to reporting the entire
119
+ * change as a single conflict.
120
+ */
121
+ function analyzeFieldChanges(localElement, remoteItem, options) {
122
+ const fieldMapConfig = options?.fieldMapConfig;
123
+ const localBaseline = options?.localBaseline;
124
+ const remoteBaseline = options?.remoteBaseline;
125
+ // Without field map config or baselines, we can't do field-level analysis
126
+ if (!fieldMapConfig || !localBaseline || !remoteBaseline) {
127
+ return {
128
+ localChangedFields: ['*'],
129
+ remoteChangedFields: ['*'],
130
+ conflictingFields: ['*'],
131
+ canFieldMerge: false,
132
+ };
133
+ }
134
+ const localChangedFields = [];
135
+ const remoteChangedFields = [];
136
+ // Check each mapped field for changes
137
+ for (const mapping of fieldMapConfig.fields) {
138
+ const localField = mapping.localField;
139
+ const externalField = mapping.externalField;
140
+ // Check if local changed for this field
141
+ const localCurrent = getFieldValue(localElement, localField);
142
+ const localBaselineValue = localBaseline[localField];
143
+ if (!deepEqual(localCurrent, localBaselineValue)) {
144
+ localChangedFields.push(localField);
145
+ }
146
+ // Check if remote changed for this field
147
+ const remoteCurrent = getExternalFieldValue(remoteItem, externalField);
148
+ const remoteBaselineValue = remoteBaseline[externalField];
149
+ if (!deepEqual(remoteCurrent, remoteBaselineValue)) {
150
+ remoteChangedFields.push(externalField);
151
+ }
152
+ }
153
+ // Find conflicting fields — fields that changed on BOTH sides
154
+ // We need to map between local and external field names
155
+ const conflictingFields = findConflictingFields(localChangedFields, remoteChangedFields, fieldMapConfig.fields);
156
+ return {
157
+ localChangedFields,
158
+ remoteChangedFields,
159
+ conflictingFields,
160
+ // Can field-merge if at least some fields don't conflict
161
+ canFieldMerge: conflictingFields.length < localChangedFields.length ||
162
+ conflictingFields.length < remoteChangedFields.length,
163
+ };
164
+ }
165
+ /**
166
+ * Find fields that changed on BOTH sides by cross-referencing the
167
+ * field mapping to identify overlapping changes.
168
+ */
169
+ function findConflictingFields(localChangedFields, remoteChangedFields, mappings) {
170
+ const conflicting = [];
171
+ for (const mapping of mappings) {
172
+ const localChanged = localChangedFields.includes(mapping.localField);
173
+ const remoteChanged = remoteChangedFields.includes(mapping.externalField);
174
+ if (localChanged && remoteChanged) {
175
+ // Same mapped field changed on both sides — conflict
176
+ conflicting.push(mapping.localField);
177
+ }
178
+ }
179
+ return conflicting;
180
+ }
181
+ // ============================================================================
182
+ // Conflict Resolution
183
+ // ============================================================================
184
+ /**
185
+ * Resolve a conflict using the specified strategy.
186
+ *
187
+ * Strategies:
188
+ * - last_write_wins: Compare updatedAt timestamps, most recent wins
189
+ * - local_wins: Stoneforge version always takes precedence
190
+ * - remote_wins: External version always takes precedence
191
+ * - manual: Mark for human resolution
192
+ *
193
+ * When field-level merge is possible (different fields changed on each side),
194
+ * non-conflicting fields are merged automatically regardless of strategy.
195
+ * The strategy only applies to fields that actually conflict (same field
196
+ * changed on both sides).
197
+ *
198
+ * @param conflict - The detected conflict info
199
+ * @param strategy - The resolution strategy to apply
200
+ * @param localElement - Current local element
201
+ * @param remoteItem - Current remote item
202
+ * @param options - Optional field map config for field-level merge
203
+ * @returns ResolvedChanges describing what to apply
204
+ */
205
+ export function resolveConflict(conflict, strategy, localElement, remoteItem, options) {
206
+ // If we can do field-level merge and have the elements, try it
207
+ if (conflict.canFieldMerge &&
208
+ localElement &&
209
+ remoteItem &&
210
+ options?.fieldMapConfig) {
211
+ return resolveWithFieldMerge(conflict, strategy, localElement, remoteItem, options.fieldMapConfig);
212
+ }
213
+ // Full-element resolution (no field-level merge)
214
+ switch (strategy) {
215
+ case 'last_write_wins':
216
+ return resolveLastWriteWins(conflict, localElement, remoteItem);
217
+ case 'local_wins':
218
+ return resolveLocalWins(conflict);
219
+ case 'remote_wins':
220
+ return resolveRemoteWins(conflict, remoteItem);
221
+ case 'manual':
222
+ return resolveManual(conflict, localElement, remoteItem);
223
+ default:
224
+ // Exhaustive check
225
+ return resolveManual(conflict, localElement, remoteItem);
226
+ }
227
+ }
228
+ /**
229
+ * Resolve a conflict using last-write-wins strategy.
230
+ * Compares updatedAt timestamps; the most recent wins.
231
+ */
232
+ function resolveLastWriteWins(conflict, localElement, remoteItem) {
233
+ const localTime = new Date(conflict.localUpdatedAt).getTime();
234
+ const remoteTime = new Date(conflict.remoteUpdatedAt).getTime();
235
+ if (localTime >= remoteTime) {
236
+ // Local is newer or same time — local wins
237
+ return {
238
+ conflict,
239
+ strategy: 'last_write_wins',
240
+ resolved: true,
241
+ winner: 'local',
242
+ // No local updates needed; remote should be updated from local
243
+ remoteUpdates: localElement ? extractPushableFields(localElement) : undefined,
244
+ };
245
+ }
246
+ else {
247
+ // Remote is newer — remote wins
248
+ return {
249
+ conflict,
250
+ strategy: 'last_write_wins',
251
+ resolved: true,
252
+ winner: 'remote',
253
+ localUpdates: remoteItem ? extractPullableFields(remoteItem) : undefined,
254
+ };
255
+ }
256
+ }
257
+ /**
258
+ * Resolve a conflict with local-wins strategy.
259
+ * Stoneforge version always takes precedence.
260
+ */
261
+ function resolveLocalWins(conflict) {
262
+ return {
263
+ conflict,
264
+ strategy: 'local_wins',
265
+ resolved: true,
266
+ winner: 'local',
267
+ // Remote needs to be updated with local values; caller handles the push
268
+ };
269
+ }
270
+ /**
271
+ * Resolve a conflict with remote-wins strategy.
272
+ * External version always takes precedence.
273
+ */
274
+ function resolveRemoteWins(conflict, remoteItem) {
275
+ return {
276
+ conflict,
277
+ strategy: 'remote_wins',
278
+ resolved: true,
279
+ winner: 'remote',
280
+ localUpdates: remoteItem ? extractPullableFields(remoteItem) : undefined,
281
+ };
282
+ }
283
+ /**
284
+ * Mark a conflict for manual resolution.
285
+ * Stores both versions so a human can choose.
286
+ */
287
+ function resolveManual(conflict, localElement, remoteItem) {
288
+ return {
289
+ conflict,
290
+ strategy: 'manual',
291
+ resolved: false,
292
+ manualConflict: {
293
+ local: localElement ? extractPushableFields(localElement) : {},
294
+ remote: remoteItem ? extractPullableFields(remoteItem) : {},
295
+ },
296
+ };
297
+ }
298
+ /**
299
+ * Resolve with field-level merge.
300
+ *
301
+ * Non-conflicting fields are merged automatically:
302
+ * - Fields only changed locally → keep local, push to remote
303
+ * - Fields only changed remotely → pull remote, apply locally
304
+ *
305
+ * For conflicting fields (same field changed on both sides),
306
+ * the strategy determines the winner.
307
+ */
308
+ function resolveWithFieldMerge(conflict, strategy, localElement, remoteItem, fieldMapConfig) {
309
+ const localUpdates = {};
310
+ const remoteUpdates = {};
311
+ // Build lookup sets for quick membership checks
312
+ const localChangedSet = new Set(conflict.localChangedFields);
313
+ const remoteChangedSet = new Set(conflict.remoteChangedFields);
314
+ const conflictingSet = new Set(conflict.conflictingFields);
315
+ for (const mapping of fieldMapConfig.fields) {
316
+ const localField = mapping.localField;
317
+ const externalField = mapping.externalField;
318
+ const localChanged = localChangedSet.has(localField);
319
+ const remoteChanged = remoteChangedSet.has(externalField);
320
+ if (localChanged && !remoteChanged) {
321
+ // Only local changed — push local value to remote
322
+ const localValue = getFieldValue(localElement, localField);
323
+ remoteUpdates[externalField] = localValue;
324
+ }
325
+ else if (remoteChanged && !localChanged) {
326
+ // Only remote changed — pull remote value to local
327
+ const remoteValue = getExternalFieldValue(remoteItem, externalField);
328
+ localUpdates[localField] = remoteValue;
329
+ }
330
+ else if (conflictingSet.has(localField)) {
331
+ // Both changed this field — apply strategy
332
+ resolveFieldConflict(strategy, conflict, mapping, localElement, remoteItem, localUpdates, remoteUpdates);
333
+ }
334
+ // If neither changed, no action needed
335
+ }
336
+ // If strategy is manual and there are conflicting fields, mark as unresolved
337
+ if (strategy === 'manual' && conflict.conflictingFields.length > 0) {
338
+ const localConflictValues = {};
339
+ const remoteConflictValues = {};
340
+ for (const field of conflict.conflictingFields) {
341
+ localConflictValues[field] = getFieldValue(localElement, field);
342
+ // Find corresponding external field
343
+ const mapping = fieldMapConfig.fields.find((m) => m.localField === field);
344
+ if (mapping) {
345
+ remoteConflictValues[mapping.externalField] = getExternalFieldValue(remoteItem, mapping.externalField);
346
+ }
347
+ }
348
+ return {
349
+ conflict,
350
+ strategy,
351
+ resolved: false,
352
+ winner: 'merged',
353
+ localUpdates: Object.keys(localUpdates).length > 0 ? localUpdates : undefined,
354
+ remoteUpdates: Object.keys(remoteUpdates).length > 0 ? remoteUpdates : undefined,
355
+ manualConflict: {
356
+ local: localConflictValues,
357
+ remote: remoteConflictValues,
358
+ },
359
+ };
360
+ }
361
+ return {
362
+ conflict,
363
+ strategy,
364
+ resolved: true,
365
+ winner: 'merged',
366
+ localUpdates: Object.keys(localUpdates).length > 0 ? localUpdates : undefined,
367
+ remoteUpdates: Object.keys(remoteUpdates).length > 0 ? remoteUpdates : undefined,
368
+ };
369
+ }
370
+ /**
371
+ * Resolve a single field conflict using the given strategy.
372
+ */
373
+ function resolveFieldConflict(strategy, conflict, mapping, localElement, remoteItem, localUpdates, remoteUpdates) {
374
+ const localValue = getFieldValue(localElement, mapping.localField);
375
+ const remoteValue = getExternalFieldValue(remoteItem, mapping.externalField);
376
+ switch (strategy) {
377
+ case 'last_write_wins': {
378
+ const localTime = new Date(conflict.localUpdatedAt).getTime();
379
+ const remoteTime = new Date(conflict.remoteUpdatedAt).getTime();
380
+ if (localTime >= remoteTime) {
381
+ remoteUpdates[mapping.externalField] = localValue;
382
+ }
383
+ else {
384
+ localUpdates[mapping.localField] = remoteValue;
385
+ }
386
+ break;
387
+ }
388
+ case 'local_wins':
389
+ remoteUpdates[mapping.externalField] = localValue;
390
+ break;
391
+ case 'remote_wins':
392
+ localUpdates[mapping.localField] = remoteValue;
393
+ break;
394
+ case 'manual':
395
+ // Don't auto-resolve — handled by the caller
396
+ break;
397
+ }
398
+ }
399
+ // ============================================================================
400
+ // Manual Conflict Resolution
401
+ // ============================================================================
402
+ /** Tag applied to elements with unresolved sync conflicts */
403
+ export const SYNC_CONFLICT_TAG = 'sync-conflict';
404
+ /**
405
+ * Apply manual conflict resolution metadata to an element.
406
+ * Tags the element with 'sync-conflict' and stores both versions
407
+ * in metadata._externalSync.conflict.
408
+ *
409
+ * @param element - The element to mark as conflicted
410
+ * @param conflict - The conflict info
411
+ * @param localValues - Local field values at time of conflict
412
+ * @param remoteValues - Remote field values at time of conflict
413
+ * @returns Updated metadata and tags to apply to the element
414
+ */
415
+ export function applyManualConflict(element, conflict, localValues, remoteValues) {
416
+ // Add sync-conflict tag if not already present
417
+ const tags = element.tags.includes(SYNC_CONFLICT_TAG)
418
+ ? [...element.tags]
419
+ : [...element.tags, SYNC_CONFLICT_TAG];
420
+ // Store both versions in metadata._externalSync.conflict
421
+ const existingSyncState = element.metadata._externalSync;
422
+ const metadata = {
423
+ ...element.metadata,
424
+ _externalSync: {
425
+ ...existingSyncState,
426
+ conflict: {
427
+ local: localValues,
428
+ remote: remoteValues,
429
+ detectedAt: new Date().toISOString(),
430
+ elementId: conflict.elementId,
431
+ externalId: conflict.externalId,
432
+ provider: conflict.provider,
433
+ project: conflict.project,
434
+ },
435
+ },
436
+ };
437
+ return { metadata, tags };
438
+ }
439
+ /**
440
+ * Resolve a manual conflict by choosing a side.
441
+ * Clears the sync-conflict tag and conflict metadata.
442
+ *
443
+ * Returns the updates to apply to the element. The caller is
444
+ * responsible for applying these and pushing/pulling as needed.
445
+ *
446
+ * @param element - The element with the conflict
447
+ * @param keep - Which side to keep: 'local' or 'remote'
448
+ * @returns Updated metadata, tags, and field values from the chosen side
449
+ */
450
+ export function resolveManualConflict(element, keep) {
451
+ // Extract the stored conflict data
452
+ const syncState = element.metadata._externalSync;
453
+ const conflictData = syncState?.conflict;
454
+ // Get field values from the chosen side
455
+ const fieldValues = conflictData
456
+ ? keep === 'local'
457
+ ? { ...conflictData.local }
458
+ : { ...conflictData.remote }
459
+ : {};
460
+ // Remove sync-conflict tag
461
+ const tags = element.tags.filter((t) => t !== SYNC_CONFLICT_TAG);
462
+ // Remove conflict metadata, keep the rest of _externalSync
463
+ const cleanedSyncState = { ...syncState };
464
+ delete cleanedSyncState.conflict;
465
+ const metadata = {
466
+ ...element.metadata,
467
+ _externalSync: cleanedSyncState,
468
+ };
469
+ return { metadata, tags, fieldValues };
470
+ }
471
+ // ============================================================================
472
+ // Conversion to ExternalSyncConflict
473
+ // ============================================================================
474
+ /**
475
+ * Convert a ConflictInfo and ResolvedChanges into an ExternalSyncConflict
476
+ * suitable for inclusion in sync result reports.
477
+ *
478
+ * @param conflict - The detected conflict info
479
+ * @param resolved - The resolution result
480
+ * @returns ExternalSyncConflict for reporting
481
+ */
482
+ export function toExternalSyncConflict(conflict, resolved) {
483
+ return {
484
+ elementId: conflict.elementId,
485
+ externalId: conflict.externalId,
486
+ provider: conflict.provider,
487
+ project: conflict.project,
488
+ localUpdatedAt: conflict.localUpdatedAt,
489
+ remoteUpdatedAt: conflict.remoteUpdatedAt,
490
+ strategy: resolved.strategy,
491
+ resolved: resolved.resolved,
492
+ winner: resolved.winner === 'merged' ? 'local' : resolved.winner,
493
+ };
494
+ }
495
+ // ============================================================================
496
+ // Helper Functions
497
+ // ============================================================================
498
+ /**
499
+ * Extract pushable fields from a local element (fields relevant to external sync).
500
+ */
501
+ function extractPushableFields(element) {
502
+ const fields = {};
503
+ // Extract common sync-relevant fields
504
+ if ('title' in element)
505
+ fields.title = element.title;
506
+ if ('status' in element)
507
+ fields.status = element.status;
508
+ if ('tags' in element)
509
+ fields.tags = element.tags;
510
+ if ('priority' in element)
511
+ fields.priority = element.priority;
512
+ if ('taskType' in element)
513
+ fields.taskType = element.taskType;
514
+ if ('assignee' in element)
515
+ fields.assignee = element.assignee;
516
+ return fields;
517
+ }
518
+ /**
519
+ * Extract pullable fields from a remote item (fields relevant to local update).
520
+ */
521
+ function extractPullableFields(item) {
522
+ return {
523
+ title: item.title,
524
+ body: item.body,
525
+ state: item.state,
526
+ labels: [...item.labels],
527
+ assignees: [...item.assignees],
528
+ };
529
+ }
530
+ /**
531
+ * Get a field value from a local element by field name.
532
+ */
533
+ function getFieldValue(element, field) {
534
+ return element[field];
535
+ }
536
+ /**
537
+ * Get a field value from an external item by field name.
538
+ */
539
+ function getExternalFieldValue(item, field) {
540
+ return item[field];
541
+ }
542
+ /**
543
+ * Deep equality comparison for field values.
544
+ * Handles primitives, arrays, and plain objects.
545
+ */
546
+ function deepEqual(a, b) {
547
+ if (a === b)
548
+ return true;
549
+ if (a === null || b === null)
550
+ return a === b;
551
+ if (a === undefined || b === undefined)
552
+ return a === b;
553
+ if (typeof a !== typeof b)
554
+ return false;
555
+ if (Array.isArray(a) && Array.isArray(b)) {
556
+ if (a.length !== b.length)
557
+ return false;
558
+ return a.every((item, index) => deepEqual(item, b[index]));
559
+ }
560
+ if (typeof a === 'object' && typeof b === 'object') {
561
+ const aObj = a;
562
+ const bObj = b;
563
+ const aKeys = Object.keys(aObj).sort();
564
+ const bKeys = Object.keys(bObj).sort();
565
+ if (aKeys.length !== bKeys.length)
566
+ return false;
567
+ return aKeys.every((key, i) => key === bKeys[i] && deepEqual(aObj[key], bObj[key]));
568
+ }
569
+ return false;
570
+ }
571
+ /**
572
+ * Compute a simple hash of a string using SHA-256 (synchronous via crypto).
573
+ * Uses the same approach as computeContentHashSync in the sync module.
574
+ */
575
+ function hashString(input) {
576
+ const hash = createHash('sha256');
577
+ hash.update(input);
578
+ return hash.digest('hex');
579
+ }
580
+ //# sourceMappingURL=conflict-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conflict-resolver.js","sourceRoot":"","sources":["../../src/external-sync/conflict-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * External Sync — public exports
3
+ *
4
+ * Central module for external service synchronization (GitHub, Linear, etc.).
5
+ * Re-exports the provider registry, sync engine, provider implementations,
6
+ * and future conflict resolver modules.
7
+ */
8
+ export { ProviderRegistry, createProviderRegistry, createDefaultProviderRegistry, createConfiguredProviderRegistry, } from './provider-registry.js';
9
+ export type { ProviderAdapterEntry } from './provider-registry.js';
10
+ export { detectConflict, resolveConflict, resolveManualConflict, applyManualConflict, toExternalSyncConflict, computeExternalItemHash, SYNC_CONFLICT_TAG, } from './conflict-resolver.js';
11
+ export type { ConflictInfo, ResolvedChanges, DetectConflictOptions, } from './conflict-resolver.js';
12
+ export { SyncEngine, createSyncEngine } from './sync-engine.js';
13
+ export type { SyncOptions, SyncEngineAPI, SyncEngineSettings, SyncConflictResolver, ConflictResolution, SyncEngineConfig, } from './sync-engine.js';
14
+ export { createGitHubProvider, createGitHubPlaceholderProvider, GitHubTaskAdapter, GITHUB_FIELD_MAP_CONFIG, GITHUB_PRIORITY_LABELS, GITHUB_TASK_TYPE_LABELS, GITHUB_SYNC_LABEL_PREFIX, statusToGitHubState, gitHubStateToStatus, GitHubApiClient, GitHubApiError, isGitHubApiError, } from './providers/github/index.js';
15
+ export type { GitHubIssue, GitHubLabel, GitHubUser, GitHubApiClientOptions, ListIssuesOptions, CreateIssueInput, UpdateIssueInput, RateLimitInfo, } from './providers/github/index.js';
16
+ export { autoLinkTask } from './auto-link.js';
17
+ export type { AutoLinkTaskParams, AutoLinkTaskResult } from './auto-link.js';
18
+ export { createLinearProvider, createLinearPlaceholderProvider, } from './providers/linear/index.js';
19
+ export type { CreateLinearProviderOptions } from './providers/linear/index.js';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/external-sync/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,6BAA6B,EAC7B,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,OAAO,EACL,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,YAAY,EACZ,eAAe,EACf,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAChE,YAAY,EACV,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,oBAAoB,EACpB,+BAA+B,EAC/B,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,sBAAsB,EACtB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,GACd,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG7E,OAAO,EACL,oBAAoB,EACpB,+BAA+B,GAChC,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * External Sync — public exports
3
+ *
4
+ * Central module for external service synchronization (GitHub, Linear, etc.).
5
+ * Re-exports the provider registry, sync engine, provider implementations,
6
+ * and future conflict resolver modules.
7
+ */
8
+ // Provider registry
9
+ export { ProviderRegistry, createProviderRegistry, createDefaultProviderRegistry, createConfiguredProviderRegistry, } from './provider-registry.js';
10
+ // Conflict resolver
11
+ export { detectConflict, resolveConflict, resolveManualConflict, applyManualConflict, toExternalSyncConflict, computeExternalItemHash, SYNC_CONFLICT_TAG, } from './conflict-resolver.js';
12
+ // Sync engine
13
+ export { SyncEngine, createSyncEngine } from './sync-engine.js';
14
+ // GitHub provider
15
+ export { createGitHubProvider, createGitHubPlaceholderProvider, GitHubTaskAdapter, GITHUB_FIELD_MAP_CONFIG, GITHUB_PRIORITY_LABELS, GITHUB_TASK_TYPE_LABELS, GITHUB_SYNC_LABEL_PREFIX, statusToGitHubState, gitHubStateToStatus, GitHubApiClient, GitHubApiError, isGitHubApiError, } from './providers/github/index.js';
16
+ // Auto-link utility
17
+ export { autoLinkTask } from './auto-link.js';
18
+ // Linear provider
19
+ export { createLinearProvider, createLinearPlaceholderProvider, } from './providers/linear/index.js';
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/external-sync/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oBAAoB;AACpB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,6BAA6B,EAC7B,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAGhC,oBAAoB;AACpB,OAAO,EACL,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAOhC,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAUhE,kBAAkB;AAClB,OAAO,EACL,oBAAoB,EACpB,+BAA+B,EAC/B,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AAYrC,oBAAoB;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,kBAAkB;AAClB,OAAO,EACL,oBAAoB,EACpB,+BAA+B,GAChC,MAAM,6BAA6B,CAAC"}