@enrichlayer/el-linear 1.10.0 → 1.15.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 (106) hide show
  1. package/README.md +126 -10
  2. package/claude-skills/linear-operations/SKILL.md +41 -1
  3. package/dist/auth/linear-credential.d.ts +27 -0
  4. package/dist/auth/linear-credential.js +1 -0
  5. package/dist/auth/oauth-app-config.d.ts +4 -3
  6. package/dist/auth/oauth-app-config.js +13 -2
  7. package/dist/auth/oauth-callback.d.ts +2 -3
  8. package/dist/auth/oauth-callback.js +2 -2
  9. package/dist/auth/oauth-client.d.ts +8 -2
  10. package/dist/auth/oauth-client.js +26 -0
  11. package/dist/auth/oauth-fs.d.ts +2 -1
  12. package/dist/auth/oauth-headless.d.ts +2 -1
  13. package/dist/auth/oauth-storage.d.ts +5 -1
  14. package/dist/auth/oauth-storage.js +1 -1
  15. package/dist/auth/oauth-token.d.ts +4 -3
  16. package/dist/auth/oauth-token.js +16 -4
  17. package/dist/auth/token-resolver.d.ts +14 -5
  18. package/dist/auth/token-resolver.js +6 -1
  19. package/dist/commands/batch.js +18 -21
  20. package/dist/commands/comments.js +5 -5
  21. package/dist/commands/config.js +178 -5
  22. package/dist/commands/init/aliases.js +1 -1
  23. package/dist/commands/init/defaults.d.ts +2 -1
  24. package/dist/commands/init/index.js +45 -35
  25. package/dist/commands/init/oauth.d.ts +4 -1
  26. package/dist/commands/init/oauth.js +22 -4
  27. package/dist/commands/init/shared.d.ts +24 -2
  28. package/dist/commands/init/shared.js +35 -4
  29. package/dist/commands/init/token.d.ts +3 -3
  30. package/dist/commands/init/token.js +5 -24
  31. package/dist/commands/init/workspace.d.ts +2 -1
  32. package/dist/commands/init/workspace.js +1 -1
  33. package/dist/commands/introspect.d.ts +27 -0
  34. package/dist/commands/introspect.js +178 -0
  35. package/dist/commands/issues/branch.js +9 -1
  36. package/dist/commands/issues/relations.d.ts +3 -14
  37. package/dist/commands/issues/relations.js +3 -3
  38. package/dist/commands/issues.js +222 -43
  39. package/dist/commands/labels.js +2 -1
  40. package/dist/commands/profile.js +1 -0
  41. package/dist/commands/projects.d.ts +2 -0
  42. package/dist/commands/projects.js +91 -7
  43. package/dist/commands/read-shortcut.d.ts +1 -1
  44. package/dist/commands/read-shortcut.js +28 -8
  45. package/dist/commands/refs.js +67 -8
  46. package/dist/commands/search.js +30 -5
  47. package/dist/commands/users.js +4 -2
  48. package/dist/config/config.d.ts +99 -1
  49. package/dist/config/config.js +264 -52
  50. package/dist/config/error-enrichment.d.ts +62 -0
  51. package/dist/config/error-enrichment.js +417 -0
  52. package/dist/config/issue-validation.d.ts +37 -0
  53. package/dist/config/issue-validation.js +63 -1
  54. package/dist/config/paths.d.ts +2 -8
  55. package/dist/config/paths.js +4 -2
  56. package/dist/config/resolver.d.ts +8 -1
  57. package/dist/config/resolver.js +9 -2
  58. package/dist/main.js +13 -1
  59. package/dist/queries/comments-types.d.ts +15 -9
  60. package/dist/queries/common.d.ts +2 -2
  61. package/dist/queries/common.js +8 -0
  62. package/dist/queries/documents-types.d.ts +4 -3
  63. package/dist/queries/introspect-types.d.ts +8 -7
  64. package/dist/queries/issues-types.d.ts +92 -27
  65. package/dist/queries/issues.d.ts +49 -10
  66. package/dist/queries/issues.js +125 -5
  67. package/dist/queries/labels-types.d.ts +7 -6
  68. package/dist/queries/project-milestones-types.d.ts +5 -4
  69. package/dist/queries/project-milestones.d.ts +1 -1
  70. package/dist/queries/projects-types.d.ts +8 -7
  71. package/dist/queries/releases-types.d.ts +5 -4
  72. package/dist/queries/search-types.d.ts +28 -12
  73. package/dist/queries/templates-types.d.ts +3 -2
  74. package/dist/types/linear.d.ts +13 -1
  75. package/dist/utils/auto-link-references.d.ts +3 -3
  76. package/dist/utils/auto-link-references.js +1 -10
  77. package/dist/utils/extract-field.d.ts +19 -0
  78. package/dist/utils/extract-field.js +99 -0
  79. package/dist/utils/file-service.d.ts +6 -13
  80. package/dist/utils/file-service.js +0 -2
  81. package/dist/utils/formatters/summary.js +6 -1
  82. package/dist/utils/graphql-issues-service.d.ts +101 -45
  83. package/dist/utils/graphql-issues-service.js +252 -39
  84. package/dist/utils/graphql-service.d.ts +10 -12
  85. package/dist/utils/graphql-service.js +0 -3
  86. package/dist/utils/issue-reference-extractor.d.ts +7 -0
  87. package/dist/utils/issue-reference-extractor.js +5 -3
  88. package/dist/utils/issues-service-bootstrap.d.ts +28 -0
  89. package/dist/utils/issues-service-bootstrap.js +27 -0
  90. package/dist/utils/linear-service.d.ts +21 -14
  91. package/dist/utils/linear-service.js +73 -11
  92. package/dist/utils/markdown-prosemirror.js +12 -12
  93. package/dist/utils/mention-resolver.js +1 -1
  94. package/dist/utils/output.d.ts +81 -3
  95. package/dist/utils/output.js +61 -6
  96. package/dist/utils/project-slug.d.ts +21 -0
  97. package/dist/utils/project-slug.js +45 -0
  98. package/dist/utils/protected-ranges.d.ts +14 -0
  99. package/dist/utils/protected-ranges.js +88 -2
  100. package/dist/utils/sanitize-for-log.d.ts +24 -0
  101. package/dist/utils/sanitize-for-log.js +38 -0
  102. package/dist/utils/table-formatter.js +24 -0
  103. package/dist/utils/validators.d.ts +7 -2
  104. package/dist/utils/validators.js +6 -0
  105. package/dist/utils/workspace-url.js +20 -4
  106. package/package.json +2 -2
@@ -41,12 +41,23 @@ export const FILTERED_SEARCH_ISSUES_QUERY = `
41
41
  }
42
42
  }
43
43
  `;
44
+ /**
45
+ * Batch-resolves a search's team/project/assignee/delegate filter inputs.
46
+ *
47
+ * The `projects` block is `@include`-gated: Linear treats a null filter
48
+ * comparator as "no filter", so an always-on block with an unset
49
+ * `$projectName` would fetch an arbitrary project. (Search resolves a UUID
50
+ * `--project` directly without consulting this block, so only the
51
+ * name-resolution arm is needed here.)
52
+ */
44
53
  export const BATCH_RESOLVE_FOR_SEARCH_QUERY = `
45
54
  query BatchResolveForSearch(
46
55
  $teamKey: String
47
56
  $teamName: String
48
57
  $projectName: String
58
+ $hasProjectName: Boolean = false
49
59
  $assigneeEmail: String
60
+ $delegateEmail: String
50
61
  ) {
51
62
  teams(
52
63
  filter: {
@@ -64,7 +75,10 @@ export const BATCH_RESOLVE_FOR_SEARCH_QUERY = `
64
75
  }
65
76
  }
66
77
 
67
- projects(filter: { name: { eqIgnoreCase: $projectName } }, first: 1) {
78
+ projects(
79
+ filter: { name: { eqIgnoreCase: $projectName } }
80
+ first: 1
81
+ ) @include(if: $hasProjectName) {
68
82
  nodes {
69
83
  id
70
84
  name
@@ -78,6 +92,14 @@ export const BATCH_RESOLVE_FOR_SEARCH_QUERY = `
78
92
  email
79
93
  }
80
94
  }
95
+
96
+ delegates: users(filter: { email: { eq: $delegateEmail } }, first: 1) {
97
+ nodes {
98
+ id
99
+ name
100
+ email
101
+ }
102
+ }
81
103
  }
82
104
  `;
83
105
  export const GET_ISSUE_BY_ID_QUERY = `
@@ -102,14 +124,46 @@ export const GET_ISSUE_BY_IDENTIFIER_QUERY = `
102
124
  }
103
125
  }
104
126
  `;
127
+ /**
128
+ * Batch-resolves an update's project/milestone/issue inputs.
129
+ *
130
+ * The project and milestone blocks are `@include`-gated for the same
131
+ * reason as `BATCH_RESOLVE_FOR_CREATE_QUERY`: a null filter comparator is
132
+ * a no-op that returns an arbitrary record. A UUID `--project` is resolved
133
+ * by `id` (`projectsById`) so its milestones are still fetched for
134
+ * `--project-milestone` name resolution; a name uses `projectsByName`.
135
+ */
105
136
  export const BATCH_RESOLVE_FOR_UPDATE_QUERY = `
106
137
  query BatchResolveForUpdate(
107
138
  $projectName: String
139
+ $projectId: ID
140
+ $hasProjectName: Boolean = false
141
+ $hasProjectId: Boolean = false
108
142
  $teamKey: String
109
143
  $issueNumber: Float
110
144
  $milestoneName: String
145
+ $hasMilestoneName: Boolean = false
111
146
  ) {
112
- projects(filter: { name: { eqIgnoreCase: $projectName } }, first: 1) {
147
+ projectsByName: projects(
148
+ filter: { name: { eqIgnoreCase: $projectName } }
149
+ first: 1
150
+ ) @include(if: $hasProjectName) {
151
+ nodes {
152
+ id
153
+ name
154
+ projectMilestones {
155
+ nodes {
156
+ id
157
+ name
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ projectsById: projects(
164
+ filter: { id: { eq: $projectId } }
165
+ first: 1
166
+ ) @include(if: $hasProjectId) {
113
167
  nodes {
114
168
  id
115
169
  name
@@ -125,7 +179,7 @@ export const BATCH_RESOLVE_FOR_UPDATE_QUERY = `
125
179
  milestones: projectMilestones(
126
180
  filter: { name: { eq: $milestoneName } }
127
181
  first: 1
128
- ) {
182
+ ) @include(if: $hasMilestoneName) {
129
183
  nodes {
130
184
  id
131
185
  name
@@ -209,14 +263,37 @@ export const DELETE_ISSUE_MUTATION = `
209
263
  }
210
264
  }
211
265
  `;
266
+ /**
267
+ * Batch-resolves a create's team/project/milestone/parent inputs in one
268
+ * round-trip.
269
+ *
270
+ * The project and milestone blocks are gated behind `@include` directives
271
+ * rather than left always-on. Linear treats a null filter comparator
272
+ * (`{ name: { eqIgnoreCase: null } }`) as "no filter" — so an always-on
273
+ * `projects` block with an unset `$projectName` returns an *arbitrary*
274
+ * project, which the caller would then mistake for the user's project
275
+ * (e.g. auto-switching the team to that unrelated project's team). The
276
+ * `has*` booleans ensure a block is fetched only when its input exists.
277
+ *
278
+ * A UUID `--project` is resolved by `id` (`projectsById`) so its team
279
+ * associations are still fetched for team-project validation; a name is
280
+ * resolved by `name` (`projectsByName`). The two are mutually exclusive,
281
+ * but GraphQL forbids two same-aliased fields with differing arguments,
282
+ * so they carry distinct aliases and the service folds whichever ran
283
+ * into `resolveResult.projects`.
284
+ */
212
285
  export const BATCH_RESOLVE_FOR_CREATE_QUERY = `
213
286
  query BatchResolveForCreate(
214
287
  $teamKey: String
215
288
  $teamName: String
216
289
  $projectName: String
290
+ $projectId: ID
291
+ $hasProjectName: Boolean = false
292
+ $hasProjectId: Boolean = false
217
293
  $parentTeamKey: String
218
294
  $parentIssueNumber: Float
219
295
  $milestoneName: String
296
+ $hasMilestoneName: Boolean = false
220
297
  ) {
221
298
  teams(
222
299
  filter: {
@@ -234,7 +311,26 @@ export const BATCH_RESOLVE_FOR_CREATE_QUERY = `
234
311
  }
235
312
  }
236
313
 
237
- projects(filter: { name: { eqIgnoreCase: $projectName } }, first: 1) {
314
+ projectsByName: projects(
315
+ filter: { name: { eqIgnoreCase: $projectName } }
316
+ first: 1
317
+ ) @include(if: $hasProjectName) {
318
+ nodes {
319
+ id
320
+ name
321
+ teams {
322
+ nodes { id key }
323
+ }
324
+ projectMilestones {
325
+ nodes { id name }
326
+ }
327
+ }
328
+ }
329
+
330
+ projectsById: projects(
331
+ filter: { id: { eq: $projectId } }
332
+ first: 1
333
+ ) @include(if: $hasProjectId) {
238
334
  nodes {
239
335
  id
240
336
  name
@@ -250,7 +346,7 @@ export const BATCH_RESOLVE_FOR_CREATE_QUERY = `
250
346
  milestones: projectMilestones(
251
347
  filter: { name: { eq: $milestoneName } }
252
348
  first: 1
253
- ) {
349
+ ) @include(if: $hasMilestoneName) {
254
350
  nodes {
255
351
  id
256
352
  name
@@ -332,6 +428,30 @@ export const GET_ISSUE_TEAM_QUERY = `
332
428
  }
333
429
  }
334
430
  `;
431
+ export const GET_ISSUE_START_CONTEXT_QUERY = `
432
+ query GetIssueStartContext($id: String!) {
433
+ issue(id: $id) {
434
+ id
435
+ identifier
436
+ state { id name type }
437
+ team { id key name }
438
+ delegate { id name url }
439
+ }
440
+ }
441
+ `;
442
+ export const TEAM_STARTED_STATUSES_QUERY = `
443
+ query TeamStartedStatuses($teamId: String!) {
444
+ team(id: $teamId) {
445
+ states(filter: { type: { eq: "started" } }) {
446
+ nodes {
447
+ id
448
+ name
449
+ position
450
+ }
451
+ }
452
+ }
453
+ }
454
+ `;
335
455
  export const ISSUE_RELATION_CREATE_MUTATION = `
336
456
  mutation IssueRelationCreate($input: IssueRelationCreateInput!) {
337
457
  issueRelationCreate(input: $input) {
@@ -2,17 +2,17 @@
2
2
  * Typed response shapes for the queries in `./labels.ts`.
3
3
  * See `./issues-types.ts` for the rationale (ALL-937).
4
4
  */
5
- export interface LabelTeamRef {
5
+ interface LabelTeamRef {
6
6
  id: string;
7
7
  key: string;
8
8
  name: string;
9
9
  }
10
- export interface LabelParentRef {
10
+ interface LabelParentRef {
11
11
  id: string;
12
12
  name: string;
13
13
  }
14
14
  /** Mirrors the issueLabel selection set on `CREATE_LABEL_MUTATION`. */
15
- export interface CreatedLabelNode {
15
+ interface CreatedLabelNode {
16
16
  id: string;
17
17
  name: string;
18
18
  color: string;
@@ -20,7 +20,7 @@ export interface CreatedLabelNode {
20
20
  parent: LabelParentRef | null;
21
21
  }
22
22
  /** Mirrors the issueLabel selection set on `RETIRE_LABEL_MUTATION`. */
23
- export interface RetiredLabelNode {
23
+ interface RetiredLabelNode {
24
24
  id: string;
25
25
  name: string;
26
26
  color: string;
@@ -28,13 +28,13 @@ export interface RetiredLabelNode {
28
28
  team: LabelTeamRef | null;
29
29
  }
30
30
  /** Mirrors the issueLabel selection set on `RESTORE_LABEL_MUTATION`. */
31
- export interface RestoredLabelNode {
31
+ interface RestoredLabelNode {
32
32
  id: string;
33
33
  name: string;
34
34
  color: string;
35
35
  team: LabelTeamRef | null;
36
36
  }
37
- export interface ParentLabelNode {
37
+ interface ParentLabelNode {
38
38
  id: string;
39
39
  name: string;
40
40
  isGroup: boolean;
@@ -62,3 +62,4 @@ export interface RestoreLabelResponse {
62
62
  issueLabel: RestoredLabelNode | null;
63
63
  };
64
64
  }
65
+ export {};
@@ -8,7 +8,7 @@
8
8
  * consumer reads only what was actually selected.
9
9
  */
10
10
  import type { IssueNode } from "./issues-types.js";
11
- export interface MilestoneProjectRef {
11
+ interface MilestoneProjectRef {
12
12
  id: string;
13
13
  name: string;
14
14
  }
@@ -16,7 +16,7 @@ export interface MilestoneProjectRef {
16
16
  * Mirrors the milestone selection set in `LIST_PROJECT_MILESTONES_QUERY`
17
17
  * — full milestone fields without the project-back-reference.
18
18
  */
19
- export interface MilestoneListNode {
19
+ interface MilestoneListNode {
20
20
  id: string;
21
21
  name: string;
22
22
  description: string | null;
@@ -29,7 +29,7 @@ export interface MilestoneListNode {
29
29
  * Mirrors the milestone selection set in `GET_PROJECT_MILESTONE_BY_ID_QUERY`
30
30
  * — list shape + project + issues connection.
31
31
  */
32
- export interface MilestoneDetailNode extends MilestoneListNode {
32
+ interface MilestoneDetailNode extends MilestoneListNode {
33
33
  project: MilestoneProjectRef;
34
34
  issues: {
35
35
  nodes: IssueNode[];
@@ -50,7 +50,7 @@ export interface MilestoneLookupNode {
50
50
  * Mirrors the milestone selection set on the create / update mutations —
51
51
  * list shape + project (no issues connection).
52
52
  */
53
- export interface MutatedMilestoneNode extends MilestoneListNode {
53
+ interface MutatedMilestoneNode extends MilestoneListNode {
54
54
  project: MilestoneProjectRef;
55
55
  }
56
56
  export interface ListProjectMilestonesResponse {
@@ -89,3 +89,4 @@ export interface UpdateProjectMilestoneResponse {
89
89
  projectMilestone: MutatedMilestoneNode | null;
90
90
  };
91
91
  }
92
+ export {};
@@ -1,5 +1,5 @@
1
1
  export declare const LIST_PROJECT_MILESTONES_QUERY = "\n query ListProjectMilestones($projectId: String!, $first: Int!) {\n project(id: $projectId) {\n id\n name\n projectMilestones(first: $first) {\n nodes {\n id\n name\n description\n targetDate\n sortOrder\n createdAt\n updatedAt\n }\n }\n }\n }\n";
2
- export declare const GET_PROJECT_MILESTONE_BY_ID_QUERY = "\n query GetProjectMilestone($id: String!, $issuesFirst: Int) {\n projectMilestone(id: $id) {\n id\n name\n description\n targetDate\n sortOrder\n createdAt\n updatedAt\n project {\n id\n name\n }\n issues(first: $issuesFirst) {\n nodes {\n \n \n id\n identifier\n title\n description\n summary { content generationStatus }\n branchName\n priority\n estimate\n dueDate\n url\n createdAt\n updatedAt\n\n \n state {\n id\n name\n }\n\n \n assignee {\n id\n name\n url\n }\n\n \n team {\n id\n key\n name\n }\n\n \n project {\n id\n name\n }\n\n \n labels {\n nodes {\n id\n name\n }\n }\n\n \n cycle {\n id\n name\n number\n }\n\n \n projectMilestone {\n id\n name\n targetDate\n }\n\n \n parent {\n id\n identifier\n title\n }\n\n \n children {\n nodes {\n id\n identifier\n title\n }\n }\n\n\n }\n }\n }\n }\n";
2
+ export declare const GET_PROJECT_MILESTONE_BY_ID_QUERY = "\n query GetProjectMilestone($id: String!, $issuesFirst: Int) {\n projectMilestone(id: $id) {\n id\n name\n description\n targetDate\n sortOrder\n createdAt\n updatedAt\n project {\n id\n name\n }\n issues(first: $issuesFirst) {\n nodes {\n \n \n id\n identifier\n title\n description\n summary { content generationStatus }\n branchName\n priority\n estimate\n dueDate\n url\n createdAt\n updatedAt\n\n \n state {\n id\n name\n }\n\n \n assignee {\n id\n name\n url\n }\n\n \n delegate {\n id\n name\n url\n }\n\n \n team {\n id\n key\n name\n }\n\n \n project {\n id\n name\n }\n\n \n labels {\n nodes {\n id\n name\n }\n }\n\n \n cycle {\n id\n name\n number\n }\n\n \n projectMilestone {\n id\n name\n targetDate\n }\n\n \n parent {\n id\n identifier\n title\n }\n\n \n children {\n nodes {\n id\n identifier\n title\n }\n }\n\n\n }\n }\n }\n }\n";
3
3
  export declare const FIND_PROJECT_MILESTONE_BY_NAME_SCOPED = "\n query FindProjectMilestoneScoped($name: String!, $projectId: String!) {\n project(id: $projectId) {\n projectMilestones(filter: { name: { eq: $name } }, first: 10) {\n nodes {\n id\n name\n targetDate\n sortOrder\n project {\n id\n name\n }\n }\n }\n }\n }\n";
4
4
  export declare const FIND_PROJECT_MILESTONE_BY_NAME_GLOBAL = "\n query FindProjectMilestoneGlobal($name: String!) {\n projectMilestones(filter: { name: { eq: $name } }, first: 10) {\n nodes {\n id\n name\n targetDate\n sortOrder\n project {\n id\n name\n }\n }\n }\n }\n";
5
5
  export declare const CREATE_PROJECT_MILESTONE_MUTATION = "\n mutation CreateProjectMilestone($projectId: String!, $name: String!, $description: String, $targetDate: TimelessDate) {\n projectMilestoneCreate(input: {\n projectId: $projectId\n name: $name\n description: $description\n targetDate: $targetDate\n }) {\n success\n projectMilestone {\n id\n name\n description\n targetDate\n sortOrder\n createdAt\n updatedAt\n project {\n id\n name\n }\n }\n }\n }\n";
@@ -2,12 +2,12 @@
2
2
  * Typed response shapes for the queries in `./projects.ts`.
3
3
  * See `./issues-types.ts` for the rationale (ALL-937).
4
4
  */
5
- export interface ProjectTeamRef {
5
+ interface ProjectTeamRef {
6
6
  id: string;
7
7
  key: string;
8
8
  name: string;
9
9
  }
10
- export interface ProjectBaseNode {
10
+ interface ProjectBaseNode {
11
11
  id: string;
12
12
  name: string;
13
13
  teams: {
@@ -18,15 +18,15 @@ export interface ProjectBaseNode {
18
18
  * Mirrors the project selection set used by `SEARCH_PROJECTS_BY_NAME_QUERY`
19
19
  * and `CREATE_PROJECT_MUTATION` — base fields + state.
20
20
  */
21
- export interface ProjectWithStateNode extends ProjectBaseNode {
21
+ interface ProjectWithStateNode extends ProjectBaseNode {
22
22
  state: string;
23
23
  }
24
- export interface ProjectIssueRef {
24
+ interface ProjectIssueRef {
25
25
  id: string;
26
26
  identifier: string;
27
27
  title: string;
28
28
  }
29
- export interface ProjectWithIssuesNode extends ProjectBaseNode {
29
+ interface ProjectWithIssuesNode extends ProjectBaseNode {
30
30
  issues: {
31
31
  nodes: ProjectIssueRef[];
32
32
  };
@@ -59,10 +59,10 @@ export interface UpdateProjectResponse {
59
59
  project: ProjectBaseNode | null;
60
60
  };
61
61
  }
62
- export interface ProjectArchiveEntity {
62
+ interface ProjectArchiveEntity {
63
63
  id: string;
64
64
  }
65
- export interface ProjectArchivePayload {
65
+ interface ProjectArchivePayload {
66
66
  success: boolean;
67
67
  lastSyncId: number;
68
68
  entity: ProjectArchiveEntity | null;
@@ -73,3 +73,4 @@ export interface ArchiveProjectResponse {
73
73
  export interface DeleteProjectResponse {
74
74
  projectDelete: ProjectArchivePayload;
75
75
  }
76
+ export {};
@@ -2,17 +2,17 @@
2
2
  * Typed response shapes for the queries in `./releases.ts`.
3
3
  * See `./issues-types.ts` for the rationale (ALL-937).
4
4
  */
5
- export interface ReleaseStageRef {
5
+ interface ReleaseStageRef {
6
6
  id: string;
7
7
  name: string;
8
8
  type: string;
9
9
  color: string | null;
10
10
  }
11
- export interface ReleasePipelineRef {
11
+ interface ReleasePipelineRef {
12
12
  id: string;
13
13
  name: string;
14
14
  }
15
- export interface ReleaseDocumentRef {
15
+ interface ReleaseDocumentRef {
16
16
  id: string;
17
17
  title: string;
18
18
  slugId: string;
@@ -56,7 +56,7 @@ export interface CreatedReleaseNode {
56
56
  createdAt: string;
57
57
  updatedAt: string;
58
58
  }
59
- export interface ReleasePipelineNode {
59
+ interface ReleasePipelineNode {
60
60
  id: string;
61
61
  name: string;
62
62
  stages: {
@@ -82,3 +82,4 @@ export interface GetReleasePipelinesResponse {
82
82
  nodes: ReleasePipelineNode[];
83
83
  };
84
84
  }
85
+ export {};
@@ -3,11 +3,12 @@
3
3
  * templates listing in `commands/search.ts`. See `./issues-types.ts`
4
4
  * for the rationale (ALL-937).
5
5
  */
6
- export interface SearchIssueRef {
6
+ import type { LinearPriority } from "../types/linear.js";
7
+ interface SearchIssueRef {
7
8
  id: string;
8
9
  identifier: string;
9
10
  title: string;
10
- priority: number | null;
11
+ priority: LinearPriority | null;
11
12
  state: {
12
13
  id: string;
13
14
  name: string;
@@ -27,17 +28,17 @@ export interface SearchIssueRef {
27
28
  name: string;
28
29
  } | null;
29
30
  }
30
- export interface SearchProjectRef {
31
+ interface SearchProjectRef {
31
32
  id: string;
32
33
  name: string;
33
34
  state: string;
34
35
  }
35
- export interface SearchInitiativeRef {
36
+ interface SearchInitiativeRef {
36
37
  id: string;
37
38
  name: string;
38
39
  status: string | null;
39
40
  }
40
- export interface SearchDocumentRef {
41
+ interface SearchDocumentRef {
41
42
  id: string;
42
43
  title: string;
43
44
  slugId: string | null;
@@ -47,18 +48,32 @@ export interface SearchDocumentRef {
47
48
  } | null;
48
49
  }
49
50
  /**
50
- * Mirrors a single semanticSearch result. The discriminator is `type`,
51
- * but Linear returns *all* sub-objects nullable per row — only the one
52
- * matching `type` is populated. Modelled as fully-optional sub-objects
53
- * to match runtime reality.
51
+ * A single semanticSearch result. Discriminated union on `type` — given
52
+ * a row, only the field named by `type` is meaningful (the other three
53
+ * are absent / null on the wire). Consumers `switch (r.type)` and access
54
+ * the corresponding payload directly; the compiler statically rejects
55
+ * cross-arm access like `r.project` in a `case "issue"` branch.
56
+ *
57
+ * The payload itself is `| null` because Linear can return null when
58
+ * the underlying entity was deleted between indexing and query time —
59
+ * that's an orthogonal concern from the discriminant.
60
+ *
61
+ * The four `type` literals match Linear's `semanticSearch.results.type`
62
+ * enum verbatim — keep in lock-step with the GraphQL schema's enum.
54
63
  */
55
- export interface SemanticSearchResult {
56
- type: string;
64
+ export type SemanticSearchResult = {
65
+ type: "issue";
57
66
  issue: SearchIssueRef | null;
67
+ } | {
68
+ type: "project";
58
69
  project: SearchProjectRef | null;
70
+ } | {
71
+ type: "initiative";
59
72
  initiative: SearchInitiativeRef | null;
73
+ } | {
74
+ type: "document";
60
75
  document: SearchDocumentRef | null;
61
- }
76
+ };
62
77
  export interface SemanticSearchResponse {
63
78
  semanticSearch: {
64
79
  results: SemanticSearchResult[];
@@ -84,3 +99,4 @@ export interface SearchTemplateNode {
84
99
  export interface SearchTemplatesResponse {
85
100
  templates: SearchTemplateNode[];
86
101
  }
102
+ export {};
@@ -6,12 +6,12 @@
6
6
  * than wrapped in `{ nodes: ... }`. The response type below mirrors
7
7
  * what the API actually returns, not the connection convention.
8
8
  */
9
- export interface TemplateTeamRef {
9
+ interface TemplateTeamRef {
10
10
  id: string;
11
11
  key: string;
12
12
  name: string;
13
13
  }
14
- export interface TemplateCreatorRef {
14
+ interface TemplateCreatorRef {
15
15
  id: string;
16
16
  name: string;
17
17
  }
@@ -59,3 +59,4 @@ export interface DeleteTemplateResponse {
59
59
  lastSyncId?: number;
60
60
  };
61
61
  }
62
+ export {};
@@ -3,6 +3,17 @@
3
3
  * These represent the public API surface — GraphQL responses are
4
4
  * transformed into these shapes at the service boundary.
5
5
  */
6
+ /**
7
+ * Linear priority levels — `0` = no priority, `1` = urgent, `2` = high,
8
+ * `3` = medium (alias: normal), `4` = low. Sourced from Linear's
9
+ * `Issue.priority` enum; the GraphQL API returns a number in this range.
10
+ * Use this instead of `number` at every API boundary so an out-of-range
11
+ * value (`9`) fails to compile.
12
+ *
13
+ * The runtime parser is `validatePriority` in `src/utils/validators.ts`
14
+ * — it narrows arbitrary `string` input to this union.
15
+ */
16
+ export type LinearPriority = 0 | 1 | 2 | 3 | 4;
6
17
  interface TeamRef {
7
18
  id: string;
8
19
  key: string;
@@ -87,6 +98,7 @@ export interface LinearIssue {
87
98
  comments?: LinearComment[];
88
99
  createdAt: string;
89
100
  cycle?: CycleRef;
101
+ delegate?: UserRef;
90
102
  description?: string;
91
103
  dueDate?: string;
92
104
  embeds?: import("../utils/embed-parser.js").Embed[];
@@ -95,7 +107,7 @@ export interface LinearIssue {
95
107
  identifier: string;
96
108
  labels: LabelRef[];
97
109
  parentIssue?: IssueRef;
98
- priority: number;
110
+ priority: LinearPriority;
99
111
  project?: ProjectRef;
100
112
  projectMilestone?: MilestoneRef;
101
113
  state?: StateRef;
@@ -1,20 +1,20 @@
1
1
  import type { GraphQLService } from "./graphql-service.js";
2
2
  import { type IssueRelationType } from "./issue-reference-extractor.js";
3
3
  import type { LinearService } from "./linear-service.js";
4
- export interface AutoLinkLinked {
4
+ interface AutoLinkLinked {
5
5
  identifier: string;
6
6
  /** True when source/target were swapped on the create (e.g. "blocked by X" → X→source) */
7
7
  reverse: boolean;
8
8
  title: string;
9
9
  type: IssueRelationType;
10
10
  }
11
- export interface AutoLinkSkipped {
11
+ interface AutoLinkSkipped {
12
12
  existingType: string;
13
13
  identifier: string;
14
14
  /** Type that would have been created had the reference been new */
15
15
  inferredType: IssueRelationType;
16
16
  }
17
- export interface AutoLinkFailed {
17
+ interface AutoLinkFailed {
18
18
  identifier: string;
19
19
  reason: string;
20
20
  }
@@ -1,14 +1,5 @@
1
1
  import { GET_ISSUE_RELATIONS_QUERY, ISSUE_RELATION_CREATE_MUTATION, } from "../queries/issues.js";
2
- import { extractIssueReferences, } from "./issue-reference-extractor.js";
3
- function specificity(ref) {
4
- if (ref.type === "duplicate") {
5
- return 3;
6
- }
7
- if (ref.type === "blocks") {
8
- return 2;
9
- }
10
- return 1;
11
- }
2
+ import { extractIssueReferences, specificity, } from "./issue-reference-extractor.js";
12
3
  /**
13
4
  * Merge candidates from multiple sources (description, comments) into one list,
14
5
  * keeping the strongest (most-specific) inference per identifier. The first source
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Extract a named section from a Linear issue's markdown description.
3
+ *
4
+ * Linear issue bodies follow a small set of repeated structures —
5
+ * "Why we need this", "Done when", "Scope", "Out of scope", etc. —
6
+ * usually as `##` or `###` headers, sometimes as bold pseudo-headers
7
+ * (`**Done when**`). Agents end up repeatedly piping
8
+ * `el-linear issues read X | python -c '...desc.find("Done when")...'`
9
+ * to pull one section out. This helper centralizes that.
10
+ *
11
+ * Matching is case-insensitive on the header text. The first matching
12
+ * header wins. Content is everything between that header and the next
13
+ * header line (any `#{1,6}` or bold pseudo-header at line start) or EOF,
14
+ * trimmed of surrounding whitespace.
15
+ *
16
+ * Returns `null` when the field isn't found, so callers can distinguish
17
+ * "missing" from "empty body".
18
+ */
19
+ export declare function extractField(body: string, fieldName: string): string | null;