@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,522 @@
1
+ /**
2
+ * Linear GraphQL API Client
3
+ *
4
+ * Pure fetch-based client for Linear issue operations via GraphQL.
5
+ * Supports API key authentication, rate limit handling, cursor pagination,
6
+ * and typed error responses.
7
+ *
8
+ * No external dependencies — uses only the standard fetch API.
9
+ *
10
+ * @see https://developers.linear.app/docs/graphql/working-with-the-graphql-api
11
+ */
12
+ // ============================================================================
13
+ // Error Types
14
+ // ============================================================================
15
+ /**
16
+ * Typed error for Linear API failures.
17
+ * Wraps fetch errors with status code, GraphQL errors, and rate limit info.
18
+ */
19
+ export class LinearApiError extends Error {
20
+ /** HTTP status code from Linear's response */
21
+ status;
22
+ /** GraphQL errors from the response (if any) */
23
+ graphqlErrors;
24
+ /** Rate limit information at the time of the error */
25
+ rateLimit;
26
+ constructor(message, status, graphqlErrors = [], rateLimit = null, cause) {
27
+ super(message);
28
+ this.name = 'LinearApiError';
29
+ this.status = status;
30
+ this.graphqlErrors = graphqlErrors;
31
+ this.rateLimit = rateLimit;
32
+ this.cause = cause;
33
+ if (Error.captureStackTrace) {
34
+ Error.captureStackTrace(this, LinearApiError);
35
+ }
36
+ }
37
+ /**
38
+ * Whether this error is due to rate limiting.
39
+ */
40
+ get isRateLimited() {
41
+ return this.status === 429 || (this.rateLimit !== null && this.rateLimit.remaining === 0);
42
+ }
43
+ /**
44
+ * Whether this error is due to authentication failure.
45
+ */
46
+ get isAuthError() {
47
+ return this.status === 401;
48
+ }
49
+ /**
50
+ * Returns a JSON-serializable representation of the error.
51
+ */
52
+ toJSON() {
53
+ return {
54
+ name: this.name,
55
+ message: this.message,
56
+ status: this.status,
57
+ graphqlErrors: this.graphqlErrors,
58
+ rateLimit: this.rateLimit,
59
+ };
60
+ }
61
+ }
62
+ /**
63
+ * Type guard for LinearApiError.
64
+ */
65
+ export function isLinearApiError(error) {
66
+ return error instanceof LinearApiError;
67
+ }
68
+ // ============================================================================
69
+ // Constants
70
+ // ============================================================================
71
+ /** Linear GraphQL API endpoint */
72
+ const LINEAR_API_URL = 'https://api.linear.app/graphql';
73
+ /** Default threshold of remaining requests before logging a warning */
74
+ const DEFAULT_RATE_LIMIT_WARNING_THRESHOLD = 100;
75
+ /** Common issue fields fragment for GraphQL queries */
76
+ const ISSUE_FIELDS = `
77
+ id
78
+ identifier
79
+ title
80
+ description
81
+ priority
82
+ url
83
+ state {
84
+ id
85
+ name
86
+ type
87
+ }
88
+ assignee {
89
+ id
90
+ name
91
+ email
92
+ }
93
+ team {
94
+ id
95
+ key
96
+ name
97
+ }
98
+ labels {
99
+ nodes {
100
+ id
101
+ name
102
+ }
103
+ }
104
+ createdAt
105
+ updatedAt
106
+ archivedAt
107
+ `;
108
+ // ============================================================================
109
+ // Client Implementation
110
+ // ============================================================================
111
+ /**
112
+ * Fetch-based Linear GraphQL API client for issue operations.
113
+ *
114
+ * Features:
115
+ * - API key authentication (no Bearer prefix)
116
+ * - Rate limit tracking with warnings when approaching limit
117
+ * - Typed errors with GraphQL error details
118
+ * - Relay-style cursor pagination
119
+ * - Partial error handling (data + errors)
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const client = new LinearApiClient({ apiKey: 'lin_api_...' });
124
+ * const viewer = await client.getViewer();
125
+ * const issues = await client.listIssuesSince('ENG', '2024-01-01T00:00:00Z');
126
+ * ```
127
+ */
128
+ export class LinearApiClient {
129
+ apiKey;
130
+ rateLimitWarningThreshold;
131
+ /** Most recently observed rate limit info (updated after each request) */
132
+ lastRateLimit = null;
133
+ constructor(options) {
134
+ if (!options.apiKey) {
135
+ throw new Error('Linear API key is required');
136
+ }
137
+ this.apiKey = options.apiKey;
138
+ this.rateLimitWarningThreshold =
139
+ options.rateLimitWarningThreshold ?? DEFAULT_RATE_LIMIT_WARNING_THRESHOLD;
140
+ }
141
+ /**
142
+ * Returns the most recently observed rate limit info, or null if no requests have been made.
143
+ */
144
+ getRateLimit() {
145
+ return this.lastRateLimit;
146
+ }
147
+ // --------------------------------------------------------------------------
148
+ // Core GraphQL Method
149
+ // --------------------------------------------------------------------------
150
+ /**
151
+ * Performs a GraphQL request to the Linear API.
152
+ *
153
+ * Handles authentication, rate limit parsing, and error responses.
154
+ * For 200 responses that include both `data` and `errors`, it logs warnings
155
+ * for partial errors but returns the data.
156
+ *
157
+ * @throws {LinearApiError} On network errors, non-200 responses, or responses with only errors.
158
+ */
159
+ async graphql(query, variables) {
160
+ let response;
161
+ try {
162
+ response = await fetch(LINEAR_API_URL, {
163
+ method: 'POST',
164
+ headers: {
165
+ Authorization: this.apiKey,
166
+ 'Content-Type': 'application/json',
167
+ },
168
+ body: JSON.stringify({ query, variables }),
169
+ });
170
+ }
171
+ catch (err) {
172
+ throw new LinearApiError(`Network error requesting Linear API: ${err instanceof Error ? err.message : String(err)}`, 0, [], null, err instanceof Error ? err : undefined);
173
+ }
174
+ // Parse rate limit headers from every response
175
+ const rateLimit = parseRateLimitHeaders(response.headers);
176
+ if (rateLimit) {
177
+ this.lastRateLimit = rateLimit;
178
+ this.checkRateLimitWarning(rateLimit);
179
+ }
180
+ // Handle non-200 HTTP responses
181
+ if (!response.ok) {
182
+ let errorMessage;
183
+ let graphqlErrors = [];
184
+ try {
185
+ const body = (await response.json());
186
+ graphqlErrors = body.errors ?? [];
187
+ errorMessage =
188
+ graphqlErrors.length > 0
189
+ ? graphqlErrors.map((e) => e.message).join('; ')
190
+ : body.message ?? `Linear API error: ${response.status} ${response.statusText}`;
191
+ }
192
+ catch {
193
+ errorMessage = `Linear API error: ${response.status} ${response.statusText}`;
194
+ }
195
+ // Special messaging for rate limit exhaustion
196
+ if (response.status === 429 || (rateLimit && rateLimit.remaining === 0)) {
197
+ const resetDate = rateLimit ? new Date(rateLimit.reset * 1000).toISOString() : 'unknown';
198
+ errorMessage = `Linear API rate limit exhausted. Resets at ${resetDate}. ${errorMessage}`;
199
+ }
200
+ throw new LinearApiError(`Linear GraphQL request failed: ${errorMessage}`, response.status, graphqlErrors, rateLimit);
201
+ }
202
+ // Parse JSON response
203
+ let body;
204
+ try {
205
+ body = (await response.json());
206
+ }
207
+ catch (err) {
208
+ throw new LinearApiError(`Failed to parse Linear API response as JSON: ${err instanceof Error ? err.message : String(err)}`, response.status, [], rateLimit, err instanceof Error ? err : undefined);
209
+ }
210
+ // Handle GraphQL errors
211
+ if (body.errors && body.errors.length > 0) {
212
+ if (!body.data) {
213
+ // Complete failure — no data returned
214
+ throw new LinearApiError(`Linear GraphQL errors: ${body.errors.map((e) => e.message).join('; ')}`, response.status, body.errors, rateLimit);
215
+ }
216
+ // Partial errors — data exists alongside errors. Log warning but return data.
217
+ console.warn(`[LinearApiClient] Partial GraphQL errors: ${body.errors.map((e) => e.message).join('; ')}`);
218
+ }
219
+ if (!body.data) {
220
+ throw new LinearApiError('Linear API returned a response with no data', response.status, body.errors ?? [], rateLimit);
221
+ }
222
+ return body.data;
223
+ }
224
+ // --------------------------------------------------------------------------
225
+ // Public API Methods
226
+ // --------------------------------------------------------------------------
227
+ /**
228
+ * Fetch the authenticated user's identity.
229
+ * Useful for testing the API connection.
230
+ *
231
+ * @returns Viewer info with id, name, and email.
232
+ */
233
+ async getViewer() {
234
+ const query = `
235
+ query Viewer {
236
+ viewer {
237
+ id
238
+ name
239
+ email
240
+ }
241
+ }
242
+ `;
243
+ const data = await this.graphql(query);
244
+ return data.viewer;
245
+ }
246
+ /**
247
+ * List all teams accessible to the authenticated user.
248
+ *
249
+ * @returns Array of teams with id, key, and name.
250
+ */
251
+ async getTeams() {
252
+ const query = `
253
+ query Teams {
254
+ teams {
255
+ nodes {
256
+ id
257
+ key
258
+ name
259
+ }
260
+ }
261
+ }
262
+ `;
263
+ const data = await this.graphql(query);
264
+ return data.teams.nodes;
265
+ }
266
+ /**
267
+ * Fetch all workflow states for a specific team.
268
+ * Needed for mapping between Linear state types and Stoneforge statuses.
269
+ *
270
+ * @param teamId - UUID of the team.
271
+ * @returns Array of workflow states.
272
+ */
273
+ async getTeamWorkflowStates(teamId) {
274
+ const query = `
275
+ query TeamWorkflowStates($teamId: String!) {
276
+ team(id: $teamId) {
277
+ states {
278
+ nodes {
279
+ id
280
+ name
281
+ type
282
+ }
283
+ }
284
+ }
285
+ }
286
+ `;
287
+ const data = await this.graphql(query, { teamId });
288
+ return data.team.states.nodes;
289
+ }
290
+ /**
291
+ * Fetch all labels (issue labels) in the workspace.
292
+ * Labels in Linear are workspace-scoped and can be filtered by team.
293
+ *
294
+ * @returns Array of all labels with id and name.
295
+ */
296
+ async getLabels() {
297
+ const query = `
298
+ query IssueLabels {
299
+ issueLabels {
300
+ nodes {
301
+ id
302
+ name
303
+ }
304
+ }
305
+ }
306
+ `;
307
+ const data = await this.graphql(query);
308
+ return data.issueLabels.nodes;
309
+ }
310
+ /**
311
+ * Create a new label in the workspace, optionally associated with a team.
312
+ *
313
+ * @param name - Label name (e.g., "blocked")
314
+ * @param teamId - Optional team ID to associate the label with
315
+ * @returns The created label with id and name.
316
+ */
317
+ async createLabel(name, teamId) {
318
+ const query = `
319
+ mutation CreateLabel($input: IssueLabelCreateInput!) {
320
+ issueLabelCreate(input: $input) {
321
+ success
322
+ issueLabel {
323
+ id
324
+ name
325
+ }
326
+ }
327
+ }
328
+ `;
329
+ const input = { name };
330
+ if (teamId) {
331
+ input.teamId = teamId;
332
+ }
333
+ const data = await this.graphql(query, { input });
334
+ if (!data.issueLabelCreate.success) {
335
+ throw new LinearApiError(`Linear issueLabelCreate mutation returned success: false for label "${name}"`, 200, [], this.lastRateLimit);
336
+ }
337
+ return data.issueLabelCreate.issueLabel;
338
+ }
339
+ /**
340
+ * Fetch a single issue by UUID or identifier (e.g., "ENG-123").
341
+ *
342
+ * @param issueId - UUID or human-readable identifier.
343
+ * @returns The issue, or null if not found.
344
+ */
345
+ async getIssue(issueId) {
346
+ const query = `
347
+ query Issue($issueId: String!) {
348
+ issue(id: $issueId) {
349
+ ${ISSUE_FIELDS}
350
+ }
351
+ }
352
+ `;
353
+ try {
354
+ const data = await this.graphql(query, { issueId });
355
+ return data.issue;
356
+ }
357
+ catch (err) {
358
+ // Linear returns errors for not-found issues rather than null
359
+ if (err instanceof LinearApiError && err.graphqlErrors.length > 0) {
360
+ const notFound = err.graphqlErrors.some((e) => e.message.toLowerCase().includes('not found') ||
361
+ e.extensions?.code === 'RESOURCE_NOT_FOUND');
362
+ if (notFound) {
363
+ return null;
364
+ }
365
+ }
366
+ throw err;
367
+ }
368
+ }
369
+ /**
370
+ * List issues for a team updated since a given timestamp.
371
+ * Automatically paginates through all results using cursor pagination.
372
+ *
373
+ * @param teamKey - Team key (e.g., "ENG").
374
+ * @param since - ISO 8601 timestamp. Only issues updated at or after this time are returned.
375
+ * @returns Array of all matching issues.
376
+ */
377
+ async listIssuesSince(teamKey, since) {
378
+ const query = `
379
+ query ListIssues($teamKey: String!, $since: DateTimeOrDuration!, $after: String) {
380
+ issues(
381
+ filter: {
382
+ team: { key: { eq: $teamKey } }
383
+ updatedAt: { gte: $since }
384
+ }
385
+ first: 50
386
+ after: $after
387
+ ) {
388
+ nodes {
389
+ ${ISSUE_FIELDS}
390
+ }
391
+ pageInfo {
392
+ hasNextPage
393
+ endCursor
394
+ }
395
+ }
396
+ }
397
+ `;
398
+ const allIssues = [];
399
+ let after = null;
400
+ // eslint-disable-next-line no-constant-condition
401
+ while (true) {
402
+ const variables = { teamKey, since };
403
+ if (after) {
404
+ variables.after = after;
405
+ }
406
+ const data = await this.graphql(query, variables);
407
+ allIssues.push(...data.issues.nodes);
408
+ if (!data.issues.pageInfo.hasNextPage) {
409
+ break;
410
+ }
411
+ after = data.issues.pageInfo.endCursor;
412
+ }
413
+ return allIssues;
414
+ }
415
+ /**
416
+ * Create a new issue in Linear.
417
+ *
418
+ * @param input - Issue creation input (teamId and title required).
419
+ * @returns The created issue.
420
+ */
421
+ async createIssue(input) {
422
+ const query = `
423
+ mutation CreateIssue($input: IssueCreateInput!) {
424
+ issueCreate(input: $input) {
425
+ success
426
+ issue {
427
+ ${ISSUE_FIELDS}
428
+ }
429
+ }
430
+ }
431
+ `;
432
+ const data = await this.graphql(query, { input });
433
+ if (!data.issueCreate.success) {
434
+ throw new LinearApiError('Linear issueCreate mutation returned success: false', 200, [], this.lastRateLimit);
435
+ }
436
+ return data.issueCreate.issue;
437
+ }
438
+ /**
439
+ * Update an existing issue in Linear.
440
+ *
441
+ * @param issueId - UUID of the issue to update.
442
+ * @param input - Fields to update.
443
+ * @returns The updated issue.
444
+ */
445
+ async updateIssue(issueId, input) {
446
+ const query = `
447
+ mutation UpdateIssue($issueId: String!, $input: IssueUpdateInput!) {
448
+ issueUpdate(id: $issueId, input: $input) {
449
+ success
450
+ issue {
451
+ ${ISSUE_FIELDS}
452
+ }
453
+ }
454
+ }
455
+ `;
456
+ const data = await this.graphql(query, { issueId, input });
457
+ if (!data.issueUpdate.success) {
458
+ throw new LinearApiError('Linear issueUpdate mutation returned success: false', 200, [], this.lastRateLimit);
459
+ }
460
+ return data.issueUpdate.issue;
461
+ }
462
+ // --------------------------------------------------------------------------
463
+ // Internal: Rate Limit Handling
464
+ // --------------------------------------------------------------------------
465
+ /**
466
+ * Logs a warning when rate limit is approaching exhaustion.
467
+ */
468
+ checkRateLimitWarning(rateLimit) {
469
+ if (rateLimit.remaining <= this.rateLimitWarningThreshold && rateLimit.remaining > 0) {
470
+ const resetDate = new Date(rateLimit.reset * 1000);
471
+ console.warn(`[LinearApiClient] Rate limit warning: ${rateLimit.remaining}/${rateLimit.limit} requests remaining. ` +
472
+ `Resets at ${resetDate.toISOString()}.`);
473
+ }
474
+ }
475
+ }
476
+ // ============================================================================
477
+ // Utility Functions
478
+ // ============================================================================
479
+ /**
480
+ * Parses rate limit information from Linear response headers.
481
+ *
482
+ * Linear uses these headers:
483
+ * - X-RateLimit-Requests-Limit
484
+ * - X-RateLimit-Requests-Remaining
485
+ * - X-RateLimit-Requests-Reset (epoch seconds)
486
+ *
487
+ * Also checks standard headers as fallback:
488
+ * - X-RateLimit-Limit
489
+ * - X-RateLimit-Remaining
490
+ * - X-RateLimit-Reset
491
+ *
492
+ * @returns Parsed rate limit info, or null if headers are not present.
493
+ */
494
+ function parseRateLimitHeaders(headers) {
495
+ // Try Linear-specific headers first
496
+ let limit = headers.get('X-RateLimit-Requests-Limit');
497
+ let remaining = headers.get('X-RateLimit-Requests-Remaining');
498
+ let reset = headers.get('X-RateLimit-Requests-Reset');
499
+ // Fall back to standard headers
500
+ if (limit === null || remaining === null || reset === null) {
501
+ limit = headers.get('X-RateLimit-Limit');
502
+ remaining = headers.get('X-RateLimit-Remaining');
503
+ reset = headers.get('X-RateLimit-Reset');
504
+ }
505
+ if (limit === null || remaining === null || reset === null) {
506
+ return null;
507
+ }
508
+ const parsedLimit = parseInt(limit, 10);
509
+ const parsedRemaining = parseInt(remaining, 10);
510
+ const parsedReset = parseInt(reset, 10);
511
+ if (isNaN(parsedLimit) || isNaN(parsedRemaining) || isNaN(parsedReset)) {
512
+ return null;
513
+ }
514
+ return {
515
+ limit: parsedLimit,
516
+ remaining: parsedRemaining,
517
+ reset: parsedReset,
518
+ };
519
+ }
520
+ // Export utility function for testing
521
+ export { parseRateLimitHeaders };
522
+ //# sourceMappingURL=linear-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-api.js","sourceRoot":"","sources":["../../../../src/external-sync/providers/linear/linear-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Linear Field Mapping Configuration
3
+ *
4
+ * Defines how Stoneforge task fields map to Linear issue fields.
5
+ *
6
+ * Key differences from GitHub:
7
+ * - Linear has native priority (0-4) — no label-based priority convention needed
8
+ * - Linear has workflow state types — status mapping uses state types, not labels
9
+ * - Task types use the same label convention as GitHub (type:bug, etc.)
10
+ *
11
+ * Priority mapping (Linear 0-4 ↔ Stoneforge 1-5):
12
+ * Linear 1 (Urgent) ↔ Stoneforge 1 (critical)
13
+ * Linear 2 (High) ↔ Stoneforge 2 (high)
14
+ * Linear 3 (Medium) ↔ Stoneforge 3 (medium)
15
+ * Linear 4 (Low) ↔ Stoneforge 4 (low)
16
+ * Linear 0 (No priority) ↔ Stoneforge 5 (minimal)
17
+ *
18
+ * Status mapping via workflow state TYPE (not name):
19
+ * Pull: triage/backlog → backlog, unstarted → open, started → in_progress,
20
+ * completed → closed, canceled → closed (closeReason: "canceled")
21
+ * Push: open → unstarted, in_progress/review → started,
22
+ * blocked → started (add "blocked" label), deferred/backlog → backlog,
23
+ * closed → completed
24
+ */
25
+ import type { TaskStatus, Priority } from '@stoneforge/core';
26
+ import type { TaskFieldMapConfig } from '@stoneforge/core';
27
+ import type { TaskSyncFieldMapConfig } from '../../adapters/task-sync-adapter.js';
28
+ import type { LinearWorkflowState } from './linear-types.js';
29
+ /**
30
+ * Maps Stoneforge TaskStatus values to label strings.
31
+ * Linear doesn't natively use label-based status, but the adapter injects
32
+ * sf:status:* labels into ExternalTask.labels to communicate granular workflow
33
+ * state types through the generic field mapping system.
34
+ *
35
+ * This keeps the sync engine provider-agnostic — it reads status from labels
36
+ * the same way for both GitHub (user-managed labels) and Linear (adapter-injected).
37
+ */
38
+ export declare const LINEAR_STATUS_LABELS: Record<string, string>;
39
+ /**
40
+ * Maps Stoneforge priority (1-5) to Linear priority (0-4).
41
+ *
42
+ * Stoneforge 1 (critical) → Linear 1 (Urgent)
43
+ * Stoneforge 2 (high) → Linear 2 (High)
44
+ * Stoneforge 3 (medium) → Linear 3 (Medium)
45
+ * Stoneforge 4 (low) → Linear 4 (Low)
46
+ * Stoneforge 5 (minimal) → Linear 0 (No priority)
47
+ */
48
+ export declare function stoneforgePriorityToLinear(priority: Priority): number;
49
+ /**
50
+ * Maps Linear priority (0-4) to Stoneforge priority (1-5).
51
+ *
52
+ * Linear 1 (Urgent) → Stoneforge 1 (critical)
53
+ * Linear 2 (High) → Stoneforge 2 (high)
54
+ * Linear 3 (Medium) → Stoneforge 3 (medium)
55
+ * Linear 4 (Low) → Stoneforge 4 (low)
56
+ * Linear 0 (No priority) → Stoneforge 5 (minimal)
57
+ */
58
+ export declare function linearPriorityToStoneforge(priority: number): Priority;
59
+ /**
60
+ * Linear workflow state type as used in the API.
61
+ */
62
+ export type LinearStateType = LinearWorkflowState['type'];
63
+ /**
64
+ * Maps a Linear workflow state type to a Stoneforge TaskStatus.
65
+ *
66
+ * Pull direction:
67
+ * triage → backlog
68
+ * backlog → backlog
69
+ * unstarted → open
70
+ * started → in_progress
71
+ * completed → closed
72
+ * canceled → closed
73
+ */
74
+ export declare function linearStateTypeToStatus(stateType: LinearStateType): {
75
+ status: TaskStatus;
76
+ closeReason?: string;
77
+ };
78
+ /**
79
+ * Maps a Stoneforge TaskStatus to a Linear workflow state type.
80
+ *
81
+ * Push direction:
82
+ * open → unstarted
83
+ * in_progress → started
84
+ * review → started
85
+ * blocked → started (caller should add "blocked" label)
86
+ * deferred → backlog
87
+ * backlog → backlog
88
+ * closed → completed
89
+ * tombstone → completed (treat as done)
90
+ */
91
+ export declare function statusToLinearStateType(status: TaskStatus): LinearStateType;
92
+ /**
93
+ * Returns true if the given Stoneforge status should add a "blocked" label
94
+ * in Linear (since Linear has no native blocked state).
95
+ */
96
+ export declare function shouldAddBlockedLabel(status: TaskStatus): boolean;
97
+ /**
98
+ * Maps a Linear workflow state type to a sync label string (sf:status:*).
99
+ *
100
+ * The Linear adapter injects this label into ExternalTask.labels so the
101
+ * generic field mapping system (stateToStatus) can extract granular statuses
102
+ * without the sync engine needing to know about Linear-specific state types.
103
+ *
104
+ * Mapping:
105
+ * triage → sf:status:backlog
106
+ * backlog → sf:status:backlog
107
+ * unstarted → sf:status:open
108
+ * started → sf:status:in-progress
109
+ * completed → sf:status:closed
110
+ * canceled → sf:status:closed
111
+ */
112
+ export declare function linearStateTypeToStatusLabel(stateType: LinearStateType): string;
113
+ /**
114
+ * Creates the Linear-specific TaskFieldMapConfig.
115
+ *
116
+ * This config describes the field mapping at the type level. The actual
117
+ * runtime mapping logic lives in the adapter and the priority/status
118
+ * functions above.
119
+ */
120
+ export declare function createLinearFieldMapConfig(): TaskFieldMapConfig;
121
+ /**
122
+ * Creates the Linear-specific TaskSyncFieldMapConfig used by the shared
123
+ * task-sync-adapter utilities (taskToExternalTask, externalTaskToTaskUpdates).
124
+ *
125
+ * Priority labels are emitted alongside Linear's native priority field for
126
+ * lossless round-tripping. This ensures the exact Stoneforge priority value
127
+ * is preserved (e.g., P5 "minimal" is distinct from Linear's "No priority").
128
+ *
129
+ * Status mapping uses adapter-injected sf:status:* labels. The Linear adapter
130
+ * injects these labels based on the workflow state type (e.g., started →
131
+ * sf:status:in-progress), allowing the generic stateToStatus function to
132
+ * extract granular statuses without coupling the sync engine to Linear.
133
+ */
134
+ export declare function createLinearSyncFieldMapConfig(): TaskSyncFieldMapConfig;
135
+ //# sourceMappingURL=linear-field-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-field-map.d.ts","sourceRoot":"","sources":["../../../../src/external-sync/providers/linear/linear-field-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAM7D;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CASvD,CAAC;AAMF;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAerE;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAerE;AAMD;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,eAAe,GAAG;IACnE,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAiBA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,GAAG,eAAe,CAqB3E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAEjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,eAAe,GAAG,MAAM,CAiB/E;AAMD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,IAAI,kBAAkB,CAiD/D;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,8BAA8B,IAAI,sBAAsB,CAgEvE"}