@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,521 @@
1
+ /**
2
+ * Linear Task Sync Adapter
3
+ *
4
+ * Implements the TaskSyncAdapter interface for Linear issue operations.
5
+ * Maps between Stoneforge ExternalTask/ExternalTaskInput and Linear's
6
+ * GraphQL API using the LinearApiClient.
7
+ *
8
+ * Key design points:
9
+ * - Caches workflow states per team (fetched on first use)
10
+ * - Builds bidirectional lookup maps: stateTypeToStateId and stateIdToType
11
+ * - Refreshes state cache when a stateId lookup fails
12
+ * - Uses native Linear priority (0-4) instead of label-based priority
13
+ * - Maps status via workflow state TYPE, not state name
14
+ */
15
+ import { createLinearFieldMapConfig, linearPriorityToStoneforge, stoneforgePriorityToLinear, linearStateTypeToStatusLabel, } from './linear-field-map.js';
16
+ // ============================================================================
17
+ // Constants
18
+ // ============================================================================
19
+ /**
20
+ * The label name used to indicate a blocked task on Linear.
21
+ * Linear has no native blocked state — this label is added/removed
22
+ * by the adapter to signal blocked status.
23
+ *
24
+ * Added to ExternalTaskInput.labels by buildExternalLabels() when a task
25
+ * has status=blocked and the provider has no statusLabels config.
26
+ */
27
+ const BLOCKED_LABEL = 'blocked';
28
+ // ============================================================================
29
+ // LinearTaskAdapter
30
+ // ============================================================================
31
+ /**
32
+ * TaskSyncAdapter implementation for Linear.
33
+ *
34
+ * Provides bidirectional sync between Stoneforge tasks and Linear issues.
35
+ * Uses workflow state caching for efficient status mapping and handles
36
+ * Linear's native priority field directly (no label convention needed).
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const client = new LinearApiClient({ apiKey: 'lin_api_...' });
41
+ * const adapter = new LinearTaskAdapter(client);
42
+ *
43
+ * // Fetch a single issue
44
+ * const issue = await adapter.getIssue('ENG', 'uuid-here');
45
+ *
46
+ * // List recent changes
47
+ * const issues = await adapter.listIssuesSince('ENG', '2024-01-01T00:00:00Z');
48
+ * ```
49
+ */
50
+ export class LinearTaskAdapter {
51
+ api;
52
+ /** Cached workflow states keyed by team ID */
53
+ stateCacheByTeam = new Map();
54
+ /** Cached team lookups keyed by team key (e.g., "ENG") */
55
+ teamByKey = new Map();
56
+ /** Cached label name→ID lookup (workspace-level) */
57
+ labelsByName = new Map();
58
+ /** Whether labels have been fetched from the API */
59
+ labelsCached = false;
60
+ constructor(api) {
61
+ this.api = api;
62
+ }
63
+ // --------------------------------------------------------------------------
64
+ // TaskSyncAdapter Implementation
65
+ // --------------------------------------------------------------------------
66
+ /**
67
+ * Fetch a single Linear issue by external ID (UUID) and convert to ExternalTask.
68
+ *
69
+ * @param project - Team key (e.g., "ENG")
70
+ * @param externalId - Linear issue UUID
71
+ * @returns ExternalTask or null if not found
72
+ */
73
+ async getIssue(project, externalId) {
74
+ const issue = await this.api.getIssue(externalId);
75
+ if (!issue) {
76
+ return null;
77
+ }
78
+ return this.linearIssueToExternalTask(issue, project);
79
+ }
80
+ /**
81
+ * List Linear issues updated since a given timestamp.
82
+ *
83
+ * @param project - Team key (e.g., "ENG")
84
+ * @param since - ISO 8601 timestamp
85
+ * @returns Array of ExternalTask
86
+ */
87
+ async listIssuesSince(project, since) {
88
+ const issues = await this.api.listIssuesSince(project, since);
89
+ return issues.map((issue) => this.linearIssueToExternalTask(issue, project));
90
+ }
91
+ /**
92
+ * Create a new issue in Linear.
93
+ *
94
+ * Resolves the team key to a team ID, maps ExternalTaskInput fields to
95
+ * Linear mutation input (title, description, priority, stateId), and
96
+ * calls the API to create the issue.
97
+ *
98
+ * @param project - Team key (e.g., "ENG")
99
+ * @param issue - ExternalTaskInput with the fields to set
100
+ * @returns The created ExternalTask
101
+ */
102
+ async createIssue(project, issue) {
103
+ // Resolve team key to team ID
104
+ const team = await this.resolveTeam(project);
105
+ // Build create input (handles state type mapping including blocked→started)
106
+ const input = await this.buildCreateInput(team, issue);
107
+ // Resolve ALL labels to Linear label IDs:
108
+ // - "blocked" label (special handling for blocked state)
109
+ // - sf:type:* labels (task type labels synced as Linear labels)
110
+ // - sf:priority:* labels (synced alongside native priority for lossless round-tripping)
111
+ // - sf:status:* labels (synced alongside native workflow states for lossless round-tripping)
112
+ // - User tags (non-prefixed labels synced as Linear labels)
113
+ const labelIds = await this.resolveInputLabelIds(team.id, issue.labels);
114
+ if (labelIds.length > 0) {
115
+ input.labelIds = labelIds;
116
+ }
117
+ // Create issue via API
118
+ const created = await this.api.createIssue(input);
119
+ return this.linearIssueToExternalTask(created, project);
120
+ }
121
+ /**
122
+ * Update an existing issue in Linear.
123
+ *
124
+ * Maps partial ExternalTaskInput to Linear mutation input and calls
125
+ * the API to update the issue.
126
+ *
127
+ * @param project - Team key (e.g., "ENG")
128
+ * @param externalId - Linear issue UUID
129
+ * @param updates - Partial ExternalTaskInput with fields to update
130
+ * @returns The updated ExternalTask
131
+ */
132
+ async updateIssue(project, externalId, updates) {
133
+ // Resolve team for state mapping
134
+ const team = await this.resolveTeam(project);
135
+ // Build update input (handles state type mapping including blocked→started)
136
+ const input = await this.buildUpdateInput(team, updates);
137
+ // Compute the full set of label IDs for the update, comparing the desired
138
+ // labels against the current issue's labels. Only sends labelIds if the
139
+ // set actually changed (avoids unnecessary writes).
140
+ if (updates.labels !== undefined) {
141
+ const labelIds = await this.computeLabelIds(team.id, externalId, updates.labels);
142
+ if (labelIds !== undefined) {
143
+ input.labelIds = labelIds;
144
+ }
145
+ }
146
+ // Update issue via API
147
+ const updated = await this.api.updateIssue(externalId, input);
148
+ return this.linearIssueToExternalTask(updated, project);
149
+ }
150
+ /**
151
+ * Returns the Linear-specific TaskFieldMapConfig.
152
+ */
153
+ getFieldMapConfig() {
154
+ return createLinearFieldMapConfig();
155
+ }
156
+ // --------------------------------------------------------------------------
157
+ // Conversion: Linear Issue → ExternalTask
158
+ // --------------------------------------------------------------------------
159
+ /**
160
+ * Converts a Linear issue API response to the normalized ExternalTask format.
161
+ *
162
+ * Injects a synthetic sf:status:* label based on the workflow state type.
163
+ * This allows the generic field mapping system (stateToStatus) to extract
164
+ * granular statuses without the sync engine needing Linear-specific logic.
165
+ */
166
+ linearIssueToExternalTask(issue, project) {
167
+ // Map state type to open/closed for ExternalTask's binary state
168
+ const stateType = issue.state.type;
169
+ const isCompleted = stateType === 'completed' || stateType === 'canceled';
170
+ // Collect labels as string names
171
+ const labels = issue.labels.nodes.map((label) => label.name);
172
+ // Inject a sf:status:* label based on workflow state type.
173
+ // This communicates the granular Linear state through the generic label-based
174
+ // field mapping system, keeping the sync engine provider-agnostic.
175
+ const statusLabel = linearStateTypeToStatusLabel(stateType);
176
+ labels.push(statusLabel);
177
+ // Build assignees list (Linear supports single assignee)
178
+ const assignees = [];
179
+ if (issue.assignee) {
180
+ assignees.push(issue.assignee.name);
181
+ }
182
+ // Store Linear-specific data in raw for lossless round-tripping
183
+ const raw = {
184
+ linearPriority: issue.priority,
185
+ linearStateType: stateType,
186
+ linearStateId: issue.state.id,
187
+ linearStateName: issue.state.name,
188
+ linearIdentifier: issue.identifier,
189
+ linearTeamKey: issue.team.key,
190
+ linearTeamId: issue.team.id,
191
+ };
192
+ // Include archived info if present
193
+ if (issue.archivedAt) {
194
+ raw.linearArchivedAt = issue.archivedAt;
195
+ }
196
+ return {
197
+ externalId: issue.id,
198
+ url: issue.url,
199
+ provider: 'linear',
200
+ project,
201
+ title: issue.title,
202
+ body: issue.description ?? undefined,
203
+ state: isCompleted ? 'closed' : 'open',
204
+ labels,
205
+ assignees,
206
+ // Convert Linear native priority (0-4) to Stoneforge priority (1-5)
207
+ priority: linearPriorityToStoneforge(issue.priority),
208
+ createdAt: issue.createdAt,
209
+ updatedAt: issue.updatedAt,
210
+ closedAt: isCompleted ? issue.updatedAt : undefined,
211
+ raw,
212
+ };
213
+ }
214
+ // --------------------------------------------------------------------------
215
+ // Input Building: ExternalTaskInput → Linear API Input
216
+ // --------------------------------------------------------------------------
217
+ /**
218
+ * Builds a CreateIssueInput from an ExternalTaskInput.
219
+ */
220
+ async buildCreateInput(team, issue) {
221
+ const input = {
222
+ teamId: team.id,
223
+ title: issue.title,
224
+ };
225
+ // Map description
226
+ if (issue.body) {
227
+ input.description = issue.body;
228
+ }
229
+ // Map state to workflow state ID.
230
+ // Use a richer mapping that accounts for blocked tasks: the "blocked"
231
+ // label in the input signals that the task is blocked and should map
232
+ // to the 'started' state type (not 'unstarted'), since a blocked task
233
+ // was actively worked on and got stuck.
234
+ if (issue.state) {
235
+ const isBlocked = issue.labels?.includes(BLOCKED_LABEL) ?? false;
236
+ let targetStateType;
237
+ if (issue.state === 'closed') {
238
+ targetStateType = 'completed';
239
+ }
240
+ else if (isBlocked) {
241
+ targetStateType = 'started';
242
+ }
243
+ else {
244
+ targetStateType = 'unstarted';
245
+ }
246
+ const stateId = await this.resolveStateId(team.id, targetStateType);
247
+ if (stateId) {
248
+ input.stateId = stateId;
249
+ }
250
+ }
251
+ // Map priority: convert Stoneforge priority (1-5) to Linear native priority (0-4).
252
+ // If no priority is provided, default to Linear 0 (No priority).
253
+ if (issue.priority !== undefined) {
254
+ input.priority = stoneforgePriorityToLinear(issue.priority);
255
+ }
256
+ else {
257
+ input.priority = 0; // Linear "No priority"
258
+ }
259
+ return input;
260
+ }
261
+ /**
262
+ * Builds an UpdateIssueInput from a partial ExternalTaskInput.
263
+ */
264
+ async buildUpdateInput(team, updates) {
265
+ const input = {};
266
+ if (updates.title !== undefined) {
267
+ input.title = updates.title;
268
+ }
269
+ if (updates.body !== undefined) {
270
+ input.description = updates.body;
271
+ }
272
+ // Map state to workflow state ID.
273
+ // Use a richer mapping that accounts for blocked tasks: if the "blocked"
274
+ // label is in the update's labels, the task should map to 'started' state
275
+ // type (not 'unstarted'), since blocked tasks were in-progress and got stuck.
276
+ if (updates.state !== undefined) {
277
+ const isBlocked = updates.labels?.includes(BLOCKED_LABEL) ?? false;
278
+ let targetStateType;
279
+ if (updates.state === 'closed') {
280
+ targetStateType = 'completed';
281
+ }
282
+ else if (isBlocked) {
283
+ targetStateType = 'started';
284
+ }
285
+ else {
286
+ targetStateType = 'unstarted';
287
+ }
288
+ const stateId = await this.resolveStateId(team.id, targetStateType);
289
+ if (stateId) {
290
+ input.stateId = stateId;
291
+ }
292
+ }
293
+ // Map priority: convert Stoneforge priority (1-5) to Linear native priority (0-4)
294
+ if (updates.priority !== undefined) {
295
+ input.priority = stoneforgePriorityToLinear(updates.priority);
296
+ }
297
+ return input;
298
+ }
299
+ // --------------------------------------------------------------------------
300
+ // Workflow State Caching
301
+ // --------------------------------------------------------------------------
302
+ /**
303
+ * Resolves a team key (e.g., "ENG") to a LinearTeam object.
304
+ * Caches the team lookup.
305
+ */
306
+ async resolveTeam(teamKey) {
307
+ // Check cache first
308
+ const cached = this.teamByKey.get(teamKey);
309
+ if (cached) {
310
+ return cached;
311
+ }
312
+ // Fetch all teams and find the matching one
313
+ const teams = await this.api.getTeams();
314
+ for (const team of teams) {
315
+ this.teamByKey.set(team.key, team);
316
+ }
317
+ const team = this.teamByKey.get(teamKey);
318
+ if (!team) {
319
+ throw new Error(`Linear team with key "${teamKey}" not found. Available teams: ${teams.map((t) => t.key).join(', ')}`);
320
+ }
321
+ return team;
322
+ }
323
+ /**
324
+ * Gets or creates the workflow state cache for a team.
325
+ * Fetches workflow states from the API on first use.
326
+ */
327
+ async getStateCache(teamId) {
328
+ const cached = this.stateCacheByTeam.get(teamId);
329
+ if (cached) {
330
+ return cached;
331
+ }
332
+ return this.refreshStateCache(teamId);
333
+ }
334
+ /**
335
+ * Fetches workflow states from the API and rebuilds the cache.
336
+ */
337
+ async refreshStateCache(teamId) {
338
+ const states = await this.api.getTeamWorkflowStates(teamId);
339
+ const stateTypeToStateId = new Map();
340
+ const stateIdToType = new Map();
341
+ for (const state of states) {
342
+ // For stateTypeToStateId, use the first state matching each type
343
+ if (!stateTypeToStateId.has(state.type)) {
344
+ stateTypeToStateId.set(state.type, state.id);
345
+ }
346
+ // For stateIdToType, map every state ID
347
+ stateIdToType.set(state.id, state.type);
348
+ }
349
+ const cache = {
350
+ teamId,
351
+ stateTypeToStateId,
352
+ stateIdToType,
353
+ states,
354
+ };
355
+ this.stateCacheByTeam.set(teamId, cache);
356
+ return cache;
357
+ }
358
+ /**
359
+ * Resolves a Linear workflow state type to a state ID.
360
+ * Uses the cached states, refreshing if the type is not found.
361
+ */
362
+ async resolveStateId(teamId, stateType) {
363
+ // Try cached lookup
364
+ let cache = await this.getStateCache(teamId);
365
+ let stateId = cache.stateTypeToStateId.get(stateType);
366
+ if (stateId) {
367
+ return stateId;
368
+ }
369
+ // Cache miss — workflow states may have been modified. Refresh.
370
+ cache = await this.refreshStateCache(teamId);
371
+ stateId = cache.stateTypeToStateId.get(stateType);
372
+ return stateId;
373
+ }
374
+ /**
375
+ * Resolves a Linear state ID to a workflow state type.
376
+ * Uses the cached states, refreshing if the ID is not found.
377
+ */
378
+ async resolveStateType(teamId, stateId) {
379
+ // Try cached lookup
380
+ let cache = await this.getStateCache(teamId);
381
+ let stateType = cache.stateIdToType.get(stateId);
382
+ if (stateType) {
383
+ return stateType;
384
+ }
385
+ // Cache miss — workflow states may have been modified. Refresh.
386
+ cache = await this.refreshStateCache(teamId);
387
+ stateType = cache.stateIdToType.get(stateId);
388
+ return stateType;
389
+ }
390
+ // --------------------------------------------------------------------------
391
+ // Label Management
392
+ // --------------------------------------------------------------------------
393
+ /**
394
+ * Filters input labels to only those that should be synced as Linear labels.
395
+ *
396
+ * All label types are now synced to Linear for lossless round-tripping:
397
+ * - sf:priority:* labels (synced alongside native priority for round-tripping)
398
+ * - sf:status:* labels (synced alongside native workflow states for round-tripping)
399
+ * - "blocked" label (synced as a Linear label)
400
+ * - sf:type:* labels (task type — Linear has no native type concept)
401
+ * - User tags (non-prefixed labels)
402
+ *
403
+ * This method exists as an extension point for future label filtering needs.
404
+ */
405
+ filterSyncableLabels(labels) {
406
+ return labels.filter(() => {
407
+ return true;
408
+ });
409
+ }
410
+ /**
411
+ * Resolves a label name to a Linear label ID.
412
+ * Uses the label cache, fetching from the API on first call.
413
+ * Creates the label if it doesn't exist.
414
+ *
415
+ * @param labelName - The label name to resolve
416
+ * @param teamId - Team ID to associate with a newly created label
417
+ * @returns The label ID, or undefined if resolution fails
418
+ */
419
+ async resolveLabelId(labelName, teamId) {
420
+ // Check cache first
421
+ const cached = this.labelsByName.get(labelName);
422
+ if (cached) {
423
+ return cached;
424
+ }
425
+ // Fetch workspace labels if not already cached
426
+ if (!this.labelsCached) {
427
+ await this.refreshLabelCache();
428
+ }
429
+ // Check again after refresh
430
+ const labelId = this.labelsByName.get(labelName);
431
+ if (labelId) {
432
+ return labelId;
433
+ }
434
+ // Label doesn't exist — create it
435
+ try {
436
+ const created = await this.api.createLabel(labelName, teamId);
437
+ this.labelsByName.set(created.name, created.id);
438
+ return created.id;
439
+ }
440
+ catch {
441
+ // If label creation fails (e.g., permission issue), log but don't block
442
+ console.warn(`[LinearTaskAdapter] Failed to create "${labelName}" label in Linear.`);
443
+ return undefined;
444
+ }
445
+ }
446
+ /**
447
+ * Resolves all syncable labels from an ExternalTaskInput to Linear label IDs.
448
+ * All labels (including sf:priority:* and sf:status:*) are synced to Linear
449
+ * for lossless round-tripping alongside native fields.
450
+ *
451
+ * @param teamId - Team ID for label creation
452
+ * @param labels - Input labels from ExternalTaskInput
453
+ * @returns Array of resolved Linear label IDs
454
+ */
455
+ async resolveInputLabelIds(teamId, labels) {
456
+ if (!labels || labels.length === 0) {
457
+ return [];
458
+ }
459
+ const syncableLabels = this.filterSyncableLabels(labels);
460
+ if (syncableLabels.length === 0) {
461
+ return [];
462
+ }
463
+ const labelIds = [];
464
+ for (const labelName of syncableLabels) {
465
+ const labelId = await this.resolveLabelId(labelName, teamId);
466
+ if (labelId) {
467
+ labelIds.push(labelId);
468
+ }
469
+ }
470
+ return labelIds;
471
+ }
472
+ /**
473
+ * Computes the new labelIds for an issue update by comparing the desired
474
+ * labels against the current issue's labels on Linear.
475
+ *
476
+ * Handles all label types:
477
+ * - "blocked" label (added/removed based on blocked status)
478
+ * - sf:type:* labels (task type synced as Linear labels)
479
+ * - sf:priority:* labels (synced alongside native priority for lossless round-tripping)
480
+ * - sf:status:* labels (synced alongside native workflow states for lossless round-tripping)
481
+ * - User tags (synced as Linear labels)
482
+ *
483
+ * Returns undefined if no label change is needed (avoids unnecessary writes).
484
+ *
485
+ * @param teamId - Team ID (for label creation if needed)
486
+ * @param externalId - Issue UUID to fetch current labels from
487
+ * @param desiredLabels - The full set of desired labels from ExternalTaskInput
488
+ * @returns New labelIds array, or undefined if no change needed
489
+ */
490
+ async computeLabelIds(teamId, externalId, desiredLabels) {
491
+ // Resolve desired labels to Linear label IDs
492
+ const desiredLabelIds = await this.resolveInputLabelIds(teamId, desiredLabels);
493
+ // Fetch current issue to get existing label IDs
494
+ const currentIssue = await this.api.getIssue(externalId);
495
+ if (!currentIssue) {
496
+ return desiredLabelIds.length > 0 ? desiredLabelIds : undefined;
497
+ }
498
+ const currentLabelIds = currentIssue.labels.nodes.map((l) => l.id);
499
+ // Compare sets: check if they're identical
500
+ const currentSet = new Set(currentLabelIds);
501
+ const desiredSet = new Set(desiredLabelIds);
502
+ if (currentSet.size === desiredSet.size &&
503
+ [...currentSet].every((id) => desiredSet.has(id))) {
504
+ // No change needed
505
+ return undefined;
506
+ }
507
+ return desiredLabelIds;
508
+ }
509
+ /**
510
+ * Fetches all workspace labels and populates the label cache.
511
+ */
512
+ async refreshLabelCache() {
513
+ const labels = await this.api.getLabels();
514
+ this.labelsByName.clear();
515
+ for (const label of labels) {
516
+ this.labelsByName.set(label.name, label.id);
517
+ }
518
+ this.labelsCached = true;
519
+ }
520
+ }
521
+ //# sourceMappingURL=linear-task-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-task-adapter.js","sourceRoot":"","sources":["../../../../src/external-sync/providers/linear/linear-task-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Linear API Response Types
3
+ *
4
+ * Type definitions for Linear's GraphQL API responses.
5
+ * These types represent the subset of Linear's schema used by the sync provider.
6
+ *
7
+ * @see https://developers.linear.app/docs/graphql/working-with-the-graphql-api
8
+ */
9
+ /**
10
+ * Linear issue representation matching the fields we query.
11
+ *
12
+ * Priority values:
13
+ * 0 = No priority
14
+ * 1 = Urgent
15
+ * 2 = High
16
+ * 3 = Medium
17
+ * 4 = Low
18
+ */
19
+ export interface LinearIssue {
20
+ /** Unique UUID identifier */
21
+ readonly id: string;
22
+ /** Human-readable identifier (e.g., "ENG-123") */
23
+ readonly identifier: string;
24
+ /** Issue title */
25
+ readonly title: string;
26
+ /** Issue description (markdown) */
27
+ readonly description: string | null;
28
+ /** Priority level (0 = No priority, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low) */
29
+ readonly priority: number;
30
+ /** URL to view the issue in Linear */
31
+ readonly url: string;
32
+ /** Current workflow state */
33
+ readonly state: LinearWorkflowState;
34
+ /** Assigned user, or null if unassigned */
35
+ readonly assignee: LinearUser | null;
36
+ /** Team the issue belongs to */
37
+ readonly team: LinearTeam;
38
+ /** Labels attached to the issue */
39
+ readonly labels: {
40
+ readonly nodes: readonly LinearLabel[];
41
+ };
42
+ /** Creation timestamp (ISO 8601) */
43
+ readonly createdAt: string;
44
+ /** Last update timestamp (ISO 8601) */
45
+ readonly updatedAt: string;
46
+ /** Archive timestamp (ISO 8601), or null if not archived */
47
+ readonly archivedAt: string | null;
48
+ }
49
+ /**
50
+ * Linear workflow state.
51
+ *
52
+ * Each team has its own set of workflow states, but every state has a `type`
53
+ * that categorizes it into one of the standard lifecycle phases.
54
+ */
55
+ export interface LinearWorkflowState {
56
+ /** Unique UUID identifier */
57
+ readonly id: string;
58
+ /** Display name (e.g., "In Progress", "Done") */
59
+ readonly name: string;
60
+ /** Workflow state category */
61
+ readonly type: 'triage' | 'backlog' | 'unstarted' | 'started' | 'completed' | 'canceled';
62
+ }
63
+ /**
64
+ * Linear team representation.
65
+ */
66
+ export interface LinearTeam {
67
+ /** Unique UUID identifier */
68
+ readonly id: string;
69
+ /** Short key used in issue identifiers (e.g., "ENG") */
70
+ readonly key: string;
71
+ /** Display name */
72
+ readonly name: string;
73
+ }
74
+ /**
75
+ * Linear user representation.
76
+ */
77
+ export interface LinearUser {
78
+ /** Unique UUID identifier */
79
+ readonly id: string;
80
+ /** Display name */
81
+ readonly name: string;
82
+ /** Email address */
83
+ readonly email: string;
84
+ }
85
+ /**
86
+ * Linear label representation.
87
+ */
88
+ export interface LinearLabel {
89
+ /** Unique UUID identifier */
90
+ readonly id: string;
91
+ /** Label name */
92
+ readonly name: string;
93
+ }
94
+ /**
95
+ * Relay-style page info for cursor pagination.
96
+ */
97
+ export interface LinearPageInfo {
98
+ /** Whether there are more results after this page */
99
+ readonly hasNextPage: boolean;
100
+ /** Cursor to pass as `after` for the next page, or null if no more pages */
101
+ readonly endCursor: string | null;
102
+ }
103
+ /**
104
+ * Generic Relay-style connection type for paginated results.
105
+ *
106
+ * Uses `nodes` syntax (simpler than `edges`) for direct access to items.
107
+ */
108
+ export interface LinearConnection<T> {
109
+ /** The items in this page */
110
+ readonly nodes: readonly T[];
111
+ /** Pagination information */
112
+ readonly pageInfo: LinearPageInfo;
113
+ }
114
+ //# sourceMappingURL=linear-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-types.d.ts","sourceRoot":"","sources":["../../../../src/external-sync/providers/linear/linear-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;;;;;;;;GASG;AACH,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,kBAAkB;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,sCAAsC;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,EAAE,mBAAmB,CAAC;IACpC,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI,CAAC;IACrC,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAA;KAAE,CAAC;IAC5D,oCAAoC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;CAC1F;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,mBAAmB;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,mBAAmB;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAMD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,6BAA6B;IAC7B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;CACnC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Linear API Response Types
3
+ *
4
+ * Type definitions for Linear's GraphQL API responses.
5
+ * These types represent the subset of Linear's schema used by the sync provider.
6
+ *
7
+ * @see https://developers.linear.app/docs/graphql/working-with-the-graphql-api
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=linear-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-types.js","sourceRoot":"","sources":["../../../../src/external-sync/providers/linear/linear-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}