@stoneforge/quarry 1.12.0 → 1.14.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 (199) hide show
  1. package/README.md +2 -0
  2. package/dist/api/quarry-api.d.ts +9 -1
  3. package/dist/api/quarry-api.d.ts.map +1 -1
  4. package/dist/api/quarry-api.js +21 -2
  5. package/dist/api/quarry-api.js.map +1 -1
  6. package/dist/api/types.d.ts +8 -1
  7. package/dist/api/types.d.ts.map +1 -1
  8. package/dist/api/types.js.map +1 -1
  9. package/dist/cli/commands/auto-link-helper.d.ts +33 -0
  10. package/dist/cli/commands/auto-link-helper.d.ts.map +1 -0
  11. package/dist/cli/commands/auto-link-helper.js +74 -0
  12. package/dist/cli/commands/auto-link-helper.js.map +1 -0
  13. package/dist/cli/commands/crud.d.ts +3 -0
  14. package/dist/cli/commands/crud.d.ts.map +1 -1
  15. package/dist/cli/commands/crud.js +144 -15
  16. package/dist/cli/commands/crud.js.map +1 -1
  17. package/dist/cli/commands/docs.js +2 -2
  18. package/dist/cli/commands/docs.js.map +1 -1
  19. package/dist/cli/commands/document.js +1 -1
  20. package/dist/cli/commands/document.js.map +1 -1
  21. package/dist/cli/commands/entity.js +1 -1
  22. package/dist/cli/commands/entity.js.map +1 -1
  23. package/dist/cli/commands/external-sync.d.ts +18 -0
  24. package/dist/cli/commands/external-sync.d.ts.map +1 -0
  25. package/dist/cli/commands/external-sync.js +2499 -0
  26. package/dist/cli/commands/external-sync.js.map +1 -0
  27. package/dist/cli/commands/library.js +1 -1
  28. package/dist/cli/commands/library.js.map +1 -1
  29. package/dist/cli/commands/message.js +2 -2
  30. package/dist/cli/commands/message.js.map +1 -1
  31. package/dist/cli/commands/serve.d.ts.map +1 -1
  32. package/dist/cli/commands/serve.js +2 -0
  33. package/dist/cli/commands/serve.js.map +1 -1
  34. package/dist/cli/commands/task.d.ts.map +1 -1
  35. package/dist/cli/commands/task.js +7 -4
  36. package/dist/cli/commands/task.js.map +1 -1
  37. package/dist/cli/commands/team.js +1 -1
  38. package/dist/cli/commands/team.js.map +1 -1
  39. package/dist/cli/commands/workflow.js +1 -1
  40. package/dist/cli/commands/workflow.js.map +1 -1
  41. package/dist/cli/runner.d.ts.map +1 -1
  42. package/dist/cli/runner.js +3 -0
  43. package/dist/cli/runner.js.map +1 -1
  44. package/dist/cli/utils/progress.d.ts +30 -0
  45. package/dist/cli/utils/progress.d.ts.map +1 -0
  46. package/dist/cli/utils/progress.js +47 -0
  47. package/dist/cli/utils/progress.js.map +1 -0
  48. package/dist/config/config.d.ts.map +1 -1
  49. package/dist/config/config.js +34 -0
  50. package/dist/config/config.js.map +1 -1
  51. package/dist/config/defaults.d.ts +13 -1
  52. package/dist/config/defaults.d.ts.map +1 -1
  53. package/dist/config/defaults.js +22 -0
  54. package/dist/config/defaults.js.map +1 -1
  55. package/dist/config/file.d.ts.map +1 -1
  56. package/dist/config/file.js +71 -0
  57. package/dist/config/file.js.map +1 -1
  58. package/dist/config/index.d.ts +3 -3
  59. package/dist/config/index.d.ts.map +1 -1
  60. package/dist/config/index.js +2 -2
  61. package/dist/config/index.js.map +1 -1
  62. package/dist/config/merge.d.ts.map +1 -1
  63. package/dist/config/merge.js +52 -1
  64. package/dist/config/merge.js.map +1 -1
  65. package/dist/config/types.d.ts +68 -1
  66. package/dist/config/types.d.ts.map +1 -1
  67. package/dist/config/types.js +33 -0
  68. package/dist/config/types.js.map +1 -1
  69. package/dist/config/validation.d.ts.map +1 -1
  70. package/dist/config/validation.js +64 -1
  71. package/dist/config/validation.js.map +1 -1
  72. package/dist/external-sync/adapters/document-sync-adapter.d.ts +150 -0
  73. package/dist/external-sync/adapters/document-sync-adapter.d.ts.map +1 -0
  74. package/dist/external-sync/adapters/document-sync-adapter.js +325 -0
  75. package/dist/external-sync/adapters/document-sync-adapter.js.map +1 -0
  76. package/dist/external-sync/adapters/task-sync-adapter.d.ts +177 -0
  77. package/dist/external-sync/adapters/task-sync-adapter.d.ts.map +1 -0
  78. package/dist/external-sync/adapters/task-sync-adapter.js +353 -0
  79. package/dist/external-sync/adapters/task-sync-adapter.js.map +1 -0
  80. package/dist/external-sync/auto-link.d.ts +66 -0
  81. package/dist/external-sync/auto-link.d.ts.map +1 -0
  82. package/dist/external-sync/auto-link.js +98 -0
  83. package/dist/external-sync/auto-link.js.map +1 -0
  84. package/dist/external-sync/conflict-resolver.d.ts +170 -0
  85. package/dist/external-sync/conflict-resolver.d.ts.map +1 -0
  86. package/dist/external-sync/conflict-resolver.js +580 -0
  87. package/dist/external-sync/conflict-resolver.js.map +1 -0
  88. package/dist/external-sync/index.d.ts +23 -0
  89. package/dist/external-sync/index.d.ts.map +1 -0
  90. package/dist/external-sync/index.js +24 -0
  91. package/dist/external-sync/index.js.map +1 -0
  92. package/dist/external-sync/provider-registry.d.ts +113 -0
  93. package/dist/external-sync/provider-registry.d.ts.map +1 -0
  94. package/dist/external-sync/provider-registry.js +205 -0
  95. package/dist/external-sync/provider-registry.js.map +1 -0
  96. package/dist/external-sync/providers/folder/folder-document-adapter.d.ts +97 -0
  97. package/dist/external-sync/providers/folder/folder-document-adapter.d.ts.map +1 -0
  98. package/dist/external-sync/providers/folder/folder-document-adapter.js +261 -0
  99. package/dist/external-sync/providers/folder/folder-document-adapter.js.map +1 -0
  100. package/dist/external-sync/providers/folder/folder-fs.d.ts +146 -0
  101. package/dist/external-sync/providers/folder/folder-fs.d.ts.map +1 -0
  102. package/dist/external-sync/providers/folder/folder-fs.js +300 -0
  103. package/dist/external-sync/providers/folder/folder-fs.js.map +1 -0
  104. package/dist/external-sync/providers/folder/folder-provider.d.ts +28 -0
  105. package/dist/external-sync/providers/folder/folder-provider.d.ts.map +1 -0
  106. package/dist/external-sync/providers/folder/folder-provider.js +87 -0
  107. package/dist/external-sync/providers/folder/folder-provider.js.map +1 -0
  108. package/dist/external-sync/providers/folder/index.d.ts +11 -0
  109. package/dist/external-sync/providers/folder/index.d.ts.map +1 -0
  110. package/dist/external-sync/providers/folder/index.js +13 -0
  111. package/dist/external-sync/providers/folder/index.js.map +1 -0
  112. package/dist/external-sync/providers/github/github-api.d.ts +271 -0
  113. package/dist/external-sync/providers/github/github-api.d.ts.map +1 -0
  114. package/dist/external-sync/providers/github/github-api.js +366 -0
  115. package/dist/external-sync/providers/github/github-api.js.map +1 -0
  116. package/dist/external-sync/providers/github/github-field-map.d.ts +76 -0
  117. package/dist/external-sync/providers/github/github-field-map.d.ts.map +1 -0
  118. package/dist/external-sync/providers/github/github-field-map.js +157 -0
  119. package/dist/external-sync/providers/github/github-field-map.js.map +1 -0
  120. package/dist/external-sync/providers/github/github-provider.d.ts +36 -0
  121. package/dist/external-sync/providers/github/github-provider.d.ts.map +1 -0
  122. package/dist/external-sync/providers/github/github-provider.js +212 -0
  123. package/dist/external-sync/providers/github/github-provider.js.map +1 -0
  124. package/dist/external-sync/providers/github/github-task-adapter.d.ts +135 -0
  125. package/dist/external-sync/providers/github/github-task-adapter.d.ts.map +1 -0
  126. package/dist/external-sync/providers/github/github-task-adapter.js +374 -0
  127. package/dist/external-sync/providers/github/github-task-adapter.js.map +1 -0
  128. package/dist/external-sync/providers/github/index.d.ts +12 -0
  129. package/dist/external-sync/providers/github/index.d.ts.map +1 -0
  130. package/dist/external-sync/providers/github/index.js +15 -0
  131. package/dist/external-sync/providers/github/index.js.map +1 -0
  132. package/dist/external-sync/providers/index.d.ts +13 -0
  133. package/dist/external-sync/providers/index.d.ts.map +1 -0
  134. package/dist/external-sync/providers/index.js +15 -0
  135. package/dist/external-sync/providers/index.js.map +1 -0
  136. package/dist/external-sync/providers/linear/index.d.ts +19 -0
  137. package/dist/external-sync/providers/linear/index.d.ts.map +1 -0
  138. package/dist/external-sync/providers/linear/index.js +19 -0
  139. package/dist/external-sync/providers/linear/index.js.map +1 -0
  140. package/dist/external-sync/providers/linear/linear-api.d.ts +252 -0
  141. package/dist/external-sync/providers/linear/linear-api.d.ts.map +1 -0
  142. package/dist/external-sync/providers/linear/linear-api.js +522 -0
  143. package/dist/external-sync/providers/linear/linear-api.js.map +1 -0
  144. package/dist/external-sync/providers/linear/linear-field-map.d.ts +135 -0
  145. package/dist/external-sync/providers/linear/linear-field-map.d.ts.map +1 -0
  146. package/dist/external-sync/providers/linear/linear-field-map.js +338 -0
  147. package/dist/external-sync/providers/linear/linear-field-map.js.map +1 -0
  148. package/dist/external-sync/providers/linear/linear-provider.d.ts +52 -0
  149. package/dist/external-sync/providers/linear/linear-provider.d.ts.map +1 -0
  150. package/dist/external-sync/providers/linear/linear-provider.js +169 -0
  151. package/dist/external-sync/providers/linear/linear-provider.js.map +1 -0
  152. package/dist/external-sync/providers/linear/linear-task-adapter.d.ts +190 -0
  153. package/dist/external-sync/providers/linear/linear-task-adapter.d.ts.map +1 -0
  154. package/dist/external-sync/providers/linear/linear-task-adapter.js +521 -0
  155. package/dist/external-sync/providers/linear/linear-task-adapter.js.map +1 -0
  156. package/dist/external-sync/providers/linear/linear-types.d.ts +114 -0
  157. package/dist/external-sync/providers/linear/linear-types.d.ts.map +1 -0
  158. package/dist/external-sync/providers/linear/linear-types.js +10 -0
  159. package/dist/external-sync/providers/linear/linear-types.js.map +1 -0
  160. package/dist/external-sync/providers/notion/index.d.ts +19 -0
  161. package/dist/external-sync/providers/notion/index.d.ts.map +1 -0
  162. package/dist/external-sync/providers/notion/index.js +20 -0
  163. package/dist/external-sync/providers/notion/index.js.map +1 -0
  164. package/dist/external-sync/providers/notion/notion-api.d.ts +253 -0
  165. package/dist/external-sync/providers/notion/notion-api.d.ts.map +1 -0
  166. package/dist/external-sync/providers/notion/notion-api.js +492 -0
  167. package/dist/external-sync/providers/notion/notion-api.js.map +1 -0
  168. package/dist/external-sync/providers/notion/notion-blocks.d.ts +93 -0
  169. package/dist/external-sync/providers/notion/notion-blocks.d.ts.map +1 -0
  170. package/dist/external-sync/providers/notion/notion-blocks.js +773 -0
  171. package/dist/external-sync/providers/notion/notion-blocks.js.map +1 -0
  172. package/dist/external-sync/providers/notion/notion-document-adapter.d.ts +176 -0
  173. package/dist/external-sync/providers/notion/notion-document-adapter.d.ts.map +1 -0
  174. package/dist/external-sync/providers/notion/notion-document-adapter.js +413 -0
  175. package/dist/external-sync/providers/notion/notion-document-adapter.js.map +1 -0
  176. package/dist/external-sync/providers/notion/notion-provider.d.ts +57 -0
  177. package/dist/external-sync/providers/notion/notion-provider.d.ts.map +1 -0
  178. package/dist/external-sync/providers/notion/notion-provider.js +159 -0
  179. package/dist/external-sync/providers/notion/notion-provider.js.map +1 -0
  180. package/dist/external-sync/providers/notion/notion-types.d.ts +388 -0
  181. package/dist/external-sync/providers/notion/notion-types.d.ts.map +1 -0
  182. package/dist/external-sync/providers/notion/notion-types.js +47 -0
  183. package/dist/external-sync/providers/notion/notion-types.js.map +1 -0
  184. package/dist/external-sync/sync-engine.d.ts +364 -0
  185. package/dist/external-sync/sync-engine.d.ts.map +1 -0
  186. package/dist/external-sync/sync-engine.js +1154 -0
  187. package/dist/external-sync/sync-engine.js.map +1 -0
  188. package/dist/index.d.ts +1 -0
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +2 -0
  191. package/dist/index.js.map +1 -1
  192. package/dist/server/index.js +8 -8
  193. package/dist/server/index.js.map +1 -1
  194. package/dist/services/inbox.js +1 -1
  195. package/dist/sync/hash.d.ts +5 -0
  196. package/dist/sync/hash.d.ts.map +1 -1
  197. package/dist/sync/hash.js +21 -2
  198. package/dist/sync/hash.js.map +1 -1
  199. package/package.json +10 -12
@@ -0,0 +1,353 @@
1
+ /**
2
+ * Task Sync Adapter Utilities
3
+ *
4
+ * Shared field mapping logic for converting between Stoneforge tasks and
5
+ * external task representations (e.g., GitHub Issues, Linear issues).
6
+ *
7
+ * These utilities are provider-agnostic — each provider supplies its own
8
+ * TaskSyncFieldMapConfig that tells the adapter how to map statuses,
9
+ * priorities, task types, and labels to/from the external system.
10
+ *
11
+ * Key functions:
12
+ * - taskToExternalTask: Convert a Stoneforge Task → ExternalTaskInput for push
13
+ * - externalTaskToTaskUpdates: Convert an ExternalTask → Partial<Task> for pull
14
+ */
15
+ import { GITHUB_FIELD_MAP_CONFIG } from '../providers/github/github-field-map.js';
16
+ import { createLinearSyncFieldMapConfig, shouldAddBlockedLabel } from '../providers/linear/linear-field-map.js';
17
+ // ============================================================================
18
+ // Push: Stoneforge Task → External Task
19
+ // ============================================================================
20
+ /**
21
+ * Converts a Stoneforge Task into an ExternalTaskInput for creating/updating
22
+ * an issue in an external system.
23
+ *
24
+ * Handles:
25
+ * - Title mapping (1:1)
26
+ * - Status → state mapping via config.statusToState
27
+ * - Tags → labels (user tags preserved, sync-managed labels added)
28
+ * - Priority → label via config.priorityLabels
29
+ * - Task type → label via config.taskTypeLabels
30
+ * - Description hydration from descriptionRef document via api.get()
31
+ *
32
+ * Note: Assignees are intentionally NOT written to external systems.
33
+ * Stoneforge assignees are ephemeral agents (e.g., el-xxxx) that don't
34
+ * correspond to valid users on external platforms like GitHub.
35
+ *
36
+ * @param task - The Stoneforge task to convert
37
+ * @param config - Provider-specific field mapping configuration
38
+ * @param api - API with get() for hydrating description documents
39
+ * @returns ExternalTaskInput ready for the provider adapter
40
+ */
41
+ export async function taskToExternalTask(task, config, api) {
42
+ // Map title (1:1)
43
+ const title = task.title;
44
+ // Map status → state
45
+ const state = config.statusToState(task.status);
46
+ // Build labels from sync-managed fields + user tags
47
+ const labels = buildExternalLabels(task, config);
48
+ // Hydrate description from descriptionRef document
49
+ const body = await hydrateDescription(task.descriptionRef, api);
50
+ return {
51
+ title,
52
+ body,
53
+ state,
54
+ labels,
55
+ // Assignees are intentionally omitted — Stoneforge assignees are ephemeral
56
+ // agents (e.g., el-xxxx) that don't map to valid external system users.
57
+ assignees: undefined,
58
+ priority: task.priority,
59
+ };
60
+ }
61
+ // ============================================================================
62
+ // Pull: External Task → Stoneforge Task Updates
63
+ // ============================================================================
64
+ /**
65
+ * Converts an ExternalTask into a partial Task update object for applying
66
+ * external changes to a local Stoneforge task.
67
+ *
68
+ * Handles:
69
+ * - Title mapping (1:1)
70
+ * - State + labels → status mapping via config.stateToStatus
71
+ * - Labels → priority, taskType, tags (separating sync-managed from user labels)
72
+ * - Assignees (first assignee mapped if present)
73
+ *
74
+ * If existingTask is provided, only changed fields are returned (diff mode).
75
+ * If existingTask is undefined, all fields are returned (create mode).
76
+ *
77
+ * Note: Description (body) handling is NOT included in the returned partial.
78
+ * The caller (sync engine) is responsible for creating/updating the description
79
+ * Document and linking via descriptionRef, since that requires document creation
80
+ * which is a separate API operation.
81
+ *
82
+ * @param externalTask - The external task to convert
83
+ * @param existingTask - The existing local task (undefined for new tasks)
84
+ * @param config - Provider-specific field mapping configuration
85
+ * @returns Partial<Task> with only the changed fields (or all fields if no existingTask)
86
+ */
87
+ export function externalTaskToTaskUpdates(externalTask, existingTask, config) {
88
+ // Parse external labels into structured data
89
+ const parsed = parseExternalLabels(externalTask.labels, config);
90
+ // Map state + labels → status
91
+ const status = config.stateToStatus(externalTask.state, [...externalTask.labels]);
92
+ // Map priority: prefer native priority from the ExternalTask (set by providers
93
+ // with native priority support like Linear), fall back to label-based extraction,
94
+ // then default to medium (3) if neither is available.
95
+ const priority = externalTask.priority ?? parsed.priority ?? 3;
96
+ // Map task type from labels (default to 'task' if not found)
97
+ const taskType = parsed.taskType ?? 'task';
98
+ // User tags = all labels that aren't sync-managed
99
+ const tags = parsed.userTags;
100
+ // Build the full update object
101
+ const fullUpdate = {
102
+ title: externalTask.title,
103
+ status,
104
+ priority: priority,
105
+ taskType: taskType,
106
+ tags,
107
+ };
108
+ // Map first assignee if present
109
+ if (externalTask.assignees.length > 0) {
110
+ // Store assignee name in metadata for resolution by the sync engine
111
+ // The sync engine is responsible for looking up the EntityId
112
+ fullUpdate.metadata = {
113
+ _pendingAssignee: externalTask.assignees[0],
114
+ };
115
+ }
116
+ // Map external ref URL
117
+ fullUpdate.externalRef = externalTask.url;
118
+ // If no existing task, return full create input
119
+ if (!existingTask) {
120
+ return fullUpdate;
121
+ }
122
+ // Diff mode: only return changed fields
123
+ return diffTaskUpdates(existingTask, fullUpdate);
124
+ }
125
+ // ============================================================================
126
+ // Label Building (Push)
127
+ // ============================================================================
128
+ /**
129
+ * Builds the complete set of external labels for a task.
130
+ *
131
+ * Combines:
132
+ * 1. Sync-managed priority label (prefixed)
133
+ * 2. Sync-managed task type label (prefixed)
134
+ * 3. Sync-managed status label (prefixed, when config.statusLabels is present)
135
+ * 4. User tags from the task (not prefixed — these are user-owned)
136
+ */
137
+ export function buildExternalLabels(task, config) {
138
+ const labels = [];
139
+ const prefix = config.syncLabelPrefix;
140
+ // Add priority label
141
+ const priorityLabel = config.priorityLabels[task.priority];
142
+ if (priorityLabel) {
143
+ labels.push(`${prefix}${priorityLabel}`);
144
+ }
145
+ // Add task type label
146
+ const taskTypeLabel = config.taskTypeLabels[task.taskType];
147
+ if (taskTypeLabel) {
148
+ labels.push(`${prefix}${taskTypeLabel}`);
149
+ }
150
+ // Add status label (when config.statusLabels is present)
151
+ if (config.statusLabels) {
152
+ const statusLabel = config.statusLabels[task.status];
153
+ if (statusLabel) {
154
+ labels.push(`${prefix}${statusLabel}`);
155
+ }
156
+ }
157
+ // Add a plain "blocked" label when the task has blocked status.
158
+ // This label is used by provider adapters (e.g., LinearTaskAdapter) to detect
159
+ // blocked tasks and apply provider-specific handling (e.g., adding a native
160
+ // "blocked" label on Linear, mapping to 'started' state type instead of
161
+ // 'unstarted'). The label is always added regardless of statusLabels —
162
+ // adapters need this signal to properly handle blocked tasks.
163
+ if (shouldAddBlockedLabel(task.status)) {
164
+ labels.push('blocked');
165
+ }
166
+ // Add user tags (not prefixed — these are user-managed)
167
+ for (const tag of task.tags) {
168
+ labels.push(tag);
169
+ }
170
+ return labels;
171
+ }
172
+ /**
173
+ * Parses external labels into structured task field values.
174
+ *
175
+ * Separates sync-managed labels (prefixed) from user labels,
176
+ * and extracts priority, task type, and status values from the managed labels.
177
+ */
178
+ export function parseExternalLabels(labels, config) {
179
+ const prefix = config.syncLabelPrefix;
180
+ let priority;
181
+ let taskType;
182
+ let status;
183
+ const userTags = [];
184
+ // Build reverse lookup maps for priority and task type
185
+ const priorityByLabel = buildReverseLookup(config.priorityLabels);
186
+ const taskTypeByLabel = buildReverseLookup(config.taskTypeLabels);
187
+ // Build reverse lookup for status labels (when present)
188
+ const statusByLabel = config.statusLabels
189
+ ? buildReverseLookup(config.statusLabels)
190
+ : undefined;
191
+ for (const label of labels) {
192
+ if (label.startsWith(prefix)) {
193
+ // This is a sync-managed label — extract the value part
194
+ const value = label.slice(prefix.length);
195
+ // Check if it's a priority label
196
+ if (priorityByLabel.has(value)) {
197
+ priority = priorityByLabel.get(value);
198
+ continue;
199
+ }
200
+ // Check if it's a task type label
201
+ if (taskTypeByLabel.has(value)) {
202
+ taskType = taskTypeByLabel.get(value);
203
+ continue;
204
+ }
205
+ // Check if it's a status label
206
+ if (statusByLabel && statusByLabel.has(value)) {
207
+ status = statusByLabel.get(value);
208
+ continue;
209
+ }
210
+ // Sync-managed label we don't recognize — skip it
211
+ // (could be from a newer version or a different adapter)
212
+ continue;
213
+ }
214
+ // Not a sync-managed label — treat as user tag
215
+ userTags.push(label);
216
+ }
217
+ return { priority, taskType, status, userTags };
218
+ }
219
+ // ============================================================================
220
+ // Description Handling
221
+ // ============================================================================
222
+ /**
223
+ * Hydrates a task's description from its descriptionRef document.
224
+ *
225
+ * If the task has a descriptionRef, fetches the document via the API
226
+ * and returns its content as the body string. Returns undefined if
227
+ * no descriptionRef is set or the document is not found.
228
+ */
229
+ export async function hydrateDescription(descriptionRef, api) {
230
+ if (!descriptionRef) {
231
+ return undefined;
232
+ }
233
+ const doc = await api.get(descriptionRef);
234
+ if (!doc || doc.type !== 'document') {
235
+ return undefined;
236
+ }
237
+ return doc.content || undefined;
238
+ }
239
+ /**
240
+ * Resolves task assignee to an external-friendly name.
241
+ *
242
+ * @deprecated No longer used — Stoneforge assignees are ephemeral agents
243
+ * that don't map to valid users on external platforms. Assignee writing
244
+ * to external systems has been removed. This function is kept temporarily
245
+ * for reference but will be removed in a future cleanup.
246
+ */
247
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
248
+ async function resolveAssignees(task, api) {
249
+ if (!task.assignee) {
250
+ return [];
251
+ }
252
+ const entity = await api.get(task.assignee);
253
+ if (!entity || entity.type !== 'entity') {
254
+ return [];
255
+ }
256
+ // Entity has a 'name' field
257
+ const name = entity.name;
258
+ if (typeof name === 'string') {
259
+ return [name];
260
+ }
261
+ return [];
262
+ }
263
+ // ============================================================================
264
+ // Diff Utilities
265
+ // ============================================================================
266
+ /**
267
+ * Compares a full update against an existing task and returns only
268
+ * the fields that actually changed.
269
+ *
270
+ * This prevents unnecessary updates when pulling changes from external
271
+ * systems where the data hasn't actually changed.
272
+ */
273
+ export function diffTaskUpdates(existingTask, fullUpdate) {
274
+ const diff = {};
275
+ if (fullUpdate.title !== undefined && fullUpdate.title !== existingTask.title) {
276
+ diff.title = fullUpdate.title;
277
+ }
278
+ if (fullUpdate.status !== undefined && fullUpdate.status !== existingTask.status) {
279
+ diff.status = fullUpdate.status;
280
+ }
281
+ if (fullUpdate.priority !== undefined && fullUpdate.priority !== existingTask.priority) {
282
+ diff.priority = fullUpdate.priority;
283
+ }
284
+ if (fullUpdate.taskType !== undefined && fullUpdate.taskType !== existingTask.taskType) {
285
+ diff.taskType = fullUpdate.taskType;
286
+ }
287
+ if (fullUpdate.tags !== undefined && !arraysEqual(fullUpdate.tags, existingTask.tags)) {
288
+ diff.tags = fullUpdate.tags;
289
+ }
290
+ if (fullUpdate.externalRef !== undefined && fullUpdate.externalRef !== existingTask.externalRef) {
291
+ diff.externalRef = fullUpdate.externalRef;
292
+ }
293
+ // Include metadata if there's a pending assignee
294
+ if (fullUpdate.metadata !== undefined) {
295
+ const pendingAssignee = fullUpdate.metadata?._pendingAssignee;
296
+ if (pendingAssignee !== undefined) {
297
+ diff.metadata = fullUpdate.metadata;
298
+ }
299
+ }
300
+ return diff;
301
+ }
302
+ // ============================================================================
303
+ // Internal Helpers
304
+ // ============================================================================
305
+ /**
306
+ * Builds a reverse lookup map from a Record<K, V> to Map<V, K>.
307
+ * Used to look up priority/task type values from their label strings.
308
+ */
309
+ function buildReverseLookup(record) {
310
+ const map = new Map();
311
+ for (const [key, value] of Object.entries(record)) {
312
+ // Keys from Record<Priority, string> come as strings from Object.entries
313
+ // Need to convert numeric keys back to numbers
314
+ const typedKey = isNaN(Number(key)) ? key : Number(key);
315
+ map.set(value, typedKey);
316
+ }
317
+ return map;
318
+ }
319
+ /**
320
+ * Compares two arrays for shallow equality (order-sensitive).
321
+ */
322
+ function arraysEqual(a, b) {
323
+ if (a.length !== b.length)
324
+ return false;
325
+ // Sort copies for order-independent comparison
326
+ const sortedA = [...a].sort();
327
+ const sortedB = [...b].sort();
328
+ return sortedA.every((val, idx) => val === sortedB[idx]);
329
+ }
330
+ // ============================================================================
331
+ // Provider Field Map Config Lookup
332
+ // ============================================================================
333
+ /**
334
+ * Returns the TaskSyncFieldMapConfig for a given provider name.
335
+ *
336
+ * Shared utility used by both the CLI link-all command and the sync engine
337
+ * push path to get the correct field mapping for a provider.
338
+ *
339
+ * @param providerName - The provider name (e.g., 'github', 'linear')
340
+ * @returns The provider-specific TaskSyncFieldMapConfig
341
+ */
342
+ export function getFieldMapConfigForProvider(providerName) {
343
+ switch (providerName) {
344
+ case 'github':
345
+ return GITHUB_FIELD_MAP_CONFIG;
346
+ case 'linear':
347
+ return createLinearSyncFieldMapConfig();
348
+ default:
349
+ // Fallback to GitHub-style config for unknown providers
350
+ return GITHUB_FIELD_MAP_CONFIG;
351
+ }
352
+ }
353
+ //# sourceMappingURL=task-sync-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-sync-adapter.js","sourceRoot":"","sources":["../../../src/external-sync/adapters/task-sync-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Auto-Link Utility
3
+ *
4
+ * Creates an external issue on a configured provider and links it back to
5
+ * a Stoneforge task via `externalRef` and `_externalSync` metadata.
6
+ *
7
+ * Designed to be called after task creation in a fire-and-forget pattern.
8
+ * Never throws — all errors are caught and returned as `{ success: false, error }`.
9
+ */
10
+ import type { Task, ExternalProvider, ExternalSyncState, SyncDirection } from '@stoneforge/core';
11
+ import type { QuarryAPI } from '../api/types.js';
12
+ /**
13
+ * Parameters for autoLinkTask()
14
+ */
15
+ export interface AutoLinkTaskParams {
16
+ /** The newly created task to auto-link */
17
+ task: Task;
18
+ /** QuarryAPI instance for updating the task */
19
+ api: QuarryAPI;
20
+ /** Configured external provider instance (e.g., GitHub, Linear) */
21
+ provider: ExternalProvider;
22
+ /** Project identifier (e.g., 'owner/repo' for GitHub, team key for Linear) */
23
+ project: string;
24
+ /** Sync direction to set on the link */
25
+ direction: SyncDirection;
26
+ }
27
+ /**
28
+ * Result of autoLinkTask()
29
+ */
30
+ export interface AutoLinkTaskResult {
31
+ /** Whether the auto-link succeeded */
32
+ success: boolean;
33
+ /** The ExternalSyncState set on the task (only on success) */
34
+ syncState?: ExternalSyncState;
35
+ /** Error message (only on failure) */
36
+ error?: string;
37
+ }
38
+ /**
39
+ * Auto-link a newly created task to an external issue.
40
+ *
41
+ * Creates an issue on the configured provider and updates the Stoneforge task
42
+ * with `externalRef` and `_externalSync` metadata.
43
+ *
44
+ * **Never throws** — catches all errors and returns `{ success: false, error }`.
45
+ * Auto-link failure must not prevent task creation.
46
+ *
47
+ * @param params - The auto-link parameters
48
+ * @returns Result indicating success or failure with details
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const result = await autoLinkTask({
53
+ * task,
54
+ * api,
55
+ * provider: githubProvider,
56
+ * project: 'owner/repo',
57
+ * direction: 'bidirectional',
58
+ * });
59
+ *
60
+ * if (!result.success) {
61
+ * console.warn('Auto-link failed:', result.error);
62
+ * }
63
+ * ```
64
+ */
65
+ export declare function autoLinkTask(params: AutoLinkTaskParams): Promise<AutoLinkTaskResult>;
66
+ //# sourceMappingURL=auto-link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-link.d.ts","sourceRoot":"","sources":["../../src/external-sync/auto-link.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,IAAI,EACJ,gBAAgB,EAEhB,iBAAiB,EACjB,aAAa,EAEd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AASjD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,IAAI,EAAE,IAAI,CAAC;IACX,+CAA+C;IAC/C,GAAG,EAAE,SAAS,CAAC;IACf,mEAAmE;IACnE,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,SAAS,EAAE,aAAa,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAkE1F"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Auto-Link Utility
3
+ *
4
+ * Creates an external issue on a configured provider and links it back to
5
+ * a Stoneforge task via `externalRef` and `_externalSync` metadata.
6
+ *
7
+ * Designed to be called after task creation in a fire-and-forget pattern.
8
+ * Never throws — all errors are caught and returned as `{ success: false, error }`.
9
+ */
10
+ import { taskToExternalTask, getFieldMapConfigForProvider } from './adapters/task-sync-adapter.js';
11
+ const LOG_PREFIX = '[external-sync]';
12
+ // ============================================================================
13
+ // Auto-Link Function
14
+ // ============================================================================
15
+ /**
16
+ * Auto-link a newly created task to an external issue.
17
+ *
18
+ * Creates an issue on the configured provider and updates the Stoneforge task
19
+ * with `externalRef` and `_externalSync` metadata.
20
+ *
21
+ * **Never throws** — catches all errors and returns `{ success: false, error }`.
22
+ * Auto-link failure must not prevent task creation.
23
+ *
24
+ * @param params - The auto-link parameters
25
+ * @returns Result indicating success or failure with details
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const result = await autoLinkTask({
30
+ * task,
31
+ * api,
32
+ * provider: githubProvider,
33
+ * project: 'owner/repo',
34
+ * direction: 'bidirectional',
35
+ * });
36
+ *
37
+ * if (!result.success) {
38
+ * console.warn('Auto-link failed:', result.error);
39
+ * }
40
+ * ```
41
+ */
42
+ export async function autoLinkTask(params) {
43
+ const { task, api, provider, project, direction } = params;
44
+ try {
45
+ // 1. Get the provider's task adapter
46
+ const adapter = provider.getTaskAdapter?.();
47
+ if (!adapter) {
48
+ const message = `Provider '${provider.name}' does not support task sync`;
49
+ console.warn(`${LOG_PREFIX} Auto-link failed for task ${task.id}: ${message}`);
50
+ return { success: false, error: message };
51
+ }
52
+ // 2. Build full ExternalTaskInput via taskToExternalTask, with fallback
53
+ let issueInput;
54
+ try {
55
+ const fieldMapConfig = getFieldMapConfigForProvider(provider.name);
56
+ issueInput = await taskToExternalTask(task, fieldMapConfig, api);
57
+ }
58
+ catch (mappingErr) {
59
+ // Fallback to simplified input so auto-link still succeeds
60
+ console.warn(`${LOG_PREFIX} Full field mapping failed for task ${task.id}, using simplified input: ${mappingErr instanceof Error ? mappingErr.message : String(mappingErr)}`);
61
+ issueInput = {
62
+ title: task.title,
63
+ body: task.descriptionRef ? `Stoneforge task: ${task.id}` : undefined,
64
+ labels: task.tags ? [...task.tags] : undefined,
65
+ };
66
+ }
67
+ // 3. Create the external issue
68
+ const externalTask = await adapter.createIssue(project, issueInput);
69
+ // 4. Build the ExternalSyncState metadata
70
+ const syncState = {
71
+ provider: provider.name,
72
+ project,
73
+ externalId: externalTask.externalId,
74
+ url: externalTask.url,
75
+ direction,
76
+ adapterType: 'task',
77
+ };
78
+ // 5. Update the task with externalRef and _externalSync metadata
79
+ const existingMetadata = (task.metadata ?? {});
80
+ await api.update(task.id, {
81
+ externalRef: externalTask.url,
82
+ metadata: {
83
+ ...existingMetadata,
84
+ _externalSync: syncState,
85
+ },
86
+ });
87
+ console.info(`${LOG_PREFIX} Auto-linked task ${task.id} to ${provider.name} issue ${externalTask.externalId} (${externalTask.url})`);
88
+ // 6. Return success
89
+ return { success: true, syncState };
90
+ }
91
+ catch (err) {
92
+ // 7. Never throw — catch all errors and return failure
93
+ const message = err instanceof Error ? err.message : String(err);
94
+ console.warn(`${LOG_PREFIX} Auto-link failed for task ${task.id}: ${message}`);
95
+ return { success: false, error: message };
96
+ }
97
+ }
98
+ //# sourceMappingURL=auto-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-link.js","sourceRoot":"","sources":["../../src/external-sync/auto-link.ts"],"names":[],"mappings":"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"}