lalph 0.2.21 → 0.3.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lalph",
3
3
  "type": "module",
4
- "version": "0.2.21",
4
+ "version": "0.3.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -23,11 +23,11 @@
23
23
  "@changesets/changelog-github": "^0.5.2",
24
24
  "@changesets/cli": "^2.29.8",
25
25
  "@effect/language-service": "^0.72.0",
26
- "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node@a5fa8be",
26
+ "@effect/platform-node": "https://pkg.pr.new/Effect-TS/effect-smol/@effect/platform-node@39ecde9",
27
27
  "@linear/sdk": "^72.0.0",
28
28
  "@octokit/plugin-rest-endpoint-methods": "^17.0.0",
29
29
  "@octokit/types": "^16.0.0",
30
- "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@a5fa8be",
30
+ "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@39ecde9",
31
31
  "husky": "^9.1.7",
32
32
  "lint-staged": "^16.2.7",
33
33
  "octokit": "^5.0.5",
@@ -4,16 +4,13 @@ import { Prd } from "../Prd.ts"
4
4
  import { ChildProcess } from "effect/unstable/process"
5
5
  import { Worktree } from "../Worktree.ts"
6
6
  import { RunnerStalled } from "../domain/Errors.ts"
7
- import type { CliAgent } from "../domain/CliAgent.ts"
8
7
  import { makeWaitForFile } from "../shared/fs.ts"
9
8
  import { GitFlow } from "../GitFlow.ts"
9
+ import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
10
10
 
11
11
  export const agentChooser = Effect.fnUntraced(function* (options: {
12
12
  readonly stallTimeout: Duration.Duration
13
- readonly commandPrefix: (
14
- command: ChildProcess.Command,
15
- ) => ChildProcess.Command
16
- readonly cliAgent: CliAgent
13
+ readonly preset: CliAgentPreset
17
14
  }) {
18
15
  const fs = yield* FileSystem.FileSystem
19
16
  const pathService = yield* Path.Path
@@ -29,13 +26,16 @@ export const agentChooser = Effect.fnUntraced(function* (options: {
29
26
  )
30
27
 
31
28
  yield* pipe(
32
- options.cliAgent.command({
29
+ options.preset.cliAgent.command({
33
30
  prompt: promptGen.promptChoose({ gitFlow }),
34
31
  prdFilePath: pathService.join(".lalph", "prd.yml"),
32
+ extraArgs: options.preset.extraArgs,
35
33
  }),
36
34
  ChildProcess.setCwd(worktree.directory),
37
- options.commandPrefix,
38
- worktree.execWithWorkerOutput({ cliAgent: options.cliAgent }),
35
+ options.preset.withCommandPrefix,
36
+ worktree.execWithWorkerOutput({
37
+ cliAgent: options.preset.cliAgent,
38
+ }),
39
39
  Effect.timeoutOrElse({
40
40
  duration: options.stallTimeout,
41
41
  onTimeout: () => Effect.fail(new RunnerStalled()),
@@ -2,29 +2,27 @@ import { Effect, Path, pipe } from "effect"
2
2
  import { PromptGen } from "../PromptGen.ts"
3
3
  import { ChildProcess } from "effect/unstable/process"
4
4
  import { Worktree } from "../Worktree.ts"
5
- import type { CliAgent } from "../domain/CliAgent.ts"
5
+ import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
6
6
 
7
7
  export const agentPlanner = Effect.fnUntraced(function* (options: {
8
8
  readonly plan: string
9
9
  readonly specsDirectory: string
10
- readonly commandPrefix: (
11
- command: ChildProcess.Command,
12
- ) => ChildProcess.Command
13
10
  readonly dangerous: boolean
14
- readonly cliAgent: CliAgent
11
+ readonly preset: CliAgentPreset
15
12
  }) {
16
13
  const pathService = yield* Path.Path
17
14
  const worktree = yield* Worktree
18
15
  const promptGen = yield* PromptGen
19
16
 
20
17
  yield* pipe(
21
- options.cliAgent.commandPlan({
18
+ options.preset.cliAgent.commandPlan({
22
19
  prompt: promptGen.planPrompt(options),
23
20
  prdFilePath: pathService.join(".lalph", "prd.yml"),
24
21
  dangerous: options.dangerous,
22
+ extraArgs: options.preset.extraArgs,
25
23
  }),
26
24
  ChildProcess.setCwd(worktree.directory),
27
- options.commandPrefix,
25
+ options.preset.withCommandPrefix,
28
26
  ChildProcess.exitCode,
29
27
  )
30
28
  })
@@ -2,16 +2,13 @@ import { Duration, Effect, FileSystem, Option, Path, pipe } from "effect"
2
2
  import { PromptGen } from "../PromptGen.ts"
3
3
  import { ChildProcess } from "effect/unstable/process"
4
4
  import { Worktree } from "../Worktree.ts"
5
- import type { CliAgent } from "../domain/CliAgent.ts"
6
5
  import { GitFlow } from "../GitFlow.ts"
6
+ import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
7
7
 
8
8
  export const agentReviewer = Effect.fnUntraced(function* (options: {
9
9
  readonly specsDirectory: string
10
10
  readonly stallTimeout: Duration.Duration
11
- readonly cliAgent: CliAgent
12
- readonly commandPrefix: (
13
- command: ChildProcess.Command,
14
- ) => ChildProcess.Command
11
+ readonly preset: CliAgentPreset
15
12
  readonly instructions: string
16
13
  }) {
17
14
  const fs = yield* FileSystem.FileSystem
@@ -26,7 +23,7 @@ export const agentReviewer = Effect.fnUntraced(function* (options: {
26
23
  )
27
24
 
28
25
  const cliCommand = pipe(
29
- options.cliAgent.command({
26
+ options.preset.cliAgent.command({
30
27
  prompt: Option.match(customInstructions, {
31
28
  onNone: () =>
32
29
  promptGen.promptReview({
@@ -40,14 +37,15 @@ export const agentReviewer = Effect.fnUntraced(function* (options: {
40
37
  }),
41
38
  }),
42
39
  prdFilePath: pathService.join(".lalph", "prd.yml"),
40
+ extraArgs: options.preset.extraArgs,
43
41
  }),
44
42
  ChildProcess.setCwd(worktree.directory),
45
- options.commandPrefix,
43
+ options.preset.withCommandPrefix,
46
44
  )
47
45
 
48
46
  return yield* cliCommand.pipe(
49
47
  worktree.execWithStallTimeout({
50
- cliAgent: options.cliAgent,
48
+ cliAgent: options.preset.cliAgent,
51
49
  stallTimeout: options.stallTimeout,
52
50
  }),
53
51
  )
@@ -2,30 +2,28 @@ import { Effect, Path, pipe } from "effect"
2
2
  import { PromptGen } from "../PromptGen.ts"
3
3
  import { ChildProcess } from "effect/unstable/process"
4
4
  import { Worktree } from "../Worktree.ts"
5
- import type { CliAgent } from "../domain/CliAgent.ts"
5
+ import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
6
6
 
7
7
  export const agentTasker = Effect.fnUntraced(function* (options: {
8
8
  readonly specsDirectory: string
9
9
  readonly specificationPath: string
10
- readonly commandPrefix: (
11
- command: ChildProcess.Command,
12
- ) => ChildProcess.Command
13
- readonly cliAgent: CliAgent
10
+ readonly preset: CliAgentPreset
14
11
  }) {
15
12
  const pathService = yield* Path.Path
16
13
  const worktree = yield* Worktree
17
14
  const promptGen = yield* PromptGen
18
15
 
19
16
  return yield* pipe(
20
- options.cliAgent.command({
17
+ options.preset.cliAgent.command({
21
18
  prompt: promptGen.promptPlanTasks({
22
19
  specsDirectory: options.specsDirectory,
23
20
  specificationPath: options.specificationPath,
24
21
  }),
25
22
  prdFilePath: pathService.join(".lalph", "prd.yml"),
23
+ extraArgs: options.preset.extraArgs,
26
24
  }),
27
25
  ChildProcess.setCwd(worktree.directory),
28
- options.commandPrefix,
29
- worktree.execWithOutput(options),
26
+ options.preset.withCommandPrefix,
27
+ worktree.execWithOutput({ cliAgent: options.preset.cliAgent }),
30
28
  )
31
29
  })
@@ -2,16 +2,13 @@ import { Duration, Effect, Path, pipe } from "effect"
2
2
  import { PromptGen } from "../PromptGen.ts"
3
3
  import { ChildProcess } from "effect/unstable/process"
4
4
  import { Worktree } from "../Worktree.ts"
5
- import type { CliAgent } from "../domain/CliAgent.ts"
6
5
  import type { PrdIssue } from "../domain/PrdIssue.ts"
6
+ import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
7
7
 
8
8
  export const agentTimeout = Effect.fnUntraced(function* (options: {
9
9
  readonly specsDirectory: string
10
10
  readonly stallTimeout: Duration.Duration
11
- readonly cliAgent: CliAgent
12
- readonly commandPrefix: (
13
- command: ChildProcess.Command,
14
- ) => ChildProcess.Command
11
+ readonly preset: CliAgentPreset
15
12
  readonly task: PrdIssue
16
13
  }) {
17
14
  const pathService = yield* Path.Path
@@ -19,19 +16,20 @@ export const agentTimeout = Effect.fnUntraced(function* (options: {
19
16
  const promptGen = yield* PromptGen
20
17
 
21
18
  const timeoutCommand = pipe(
22
- options.cliAgent.command({
19
+ options.preset.cliAgent.command({
23
20
  prompt: promptGen.promptTimeout({
24
21
  taskId: options.task.id!,
25
22
  specsDirectory: options.specsDirectory,
26
23
  }),
27
24
  prdFilePath: pathService.join(".lalph", "prd.yml"),
25
+ extraArgs: options.preset.extraArgs,
28
26
  }),
29
27
  ChildProcess.setCwd(worktree.directory),
30
- options.commandPrefix,
28
+ options.preset.withCommandPrefix,
31
29
  )
32
30
  return yield* timeoutCommand.pipe(
33
31
  worktree.execWithStallTimeout({
34
- cliAgent: options.cliAgent,
32
+ cliAgent: options.preset.cliAgent,
35
33
  stallTimeout: options.stallTimeout,
36
34
  }),
37
35
  )
@@ -1,31 +1,29 @@
1
1
  import { Duration, Effect, Path, pipe } from "effect"
2
2
  import { ChildProcess } from "effect/unstable/process"
3
3
  import { Worktree } from "../Worktree.ts"
4
- import type { CliAgent } from "../domain/CliAgent.ts"
4
+ import type { CliAgentPreset } from "../domain/CliAgentPreset.ts"
5
5
 
6
6
  export const agentWorker = Effect.fnUntraced(function* (options: {
7
7
  readonly stallTimeout: Duration.Duration
8
- readonly cliAgent: CliAgent
9
- readonly commandPrefix: (
10
- command: ChildProcess.Command,
11
- ) => ChildProcess.Command
8
+ readonly preset: CliAgentPreset
12
9
  readonly prompt: string
13
10
  }) {
14
11
  const pathService = yield* Path.Path
15
12
  const worktree = yield* Worktree
16
13
 
17
14
  const cliCommand = pipe(
18
- options.cliAgent.command({
15
+ options.preset.cliAgent.command({
19
16
  prompt: options.prompt,
20
17
  prdFilePath: pathService.join(".lalph", "prd.yml"),
18
+ extraArgs: [],
21
19
  }),
22
20
  ChildProcess.setCwd(worktree.directory),
23
- options.commandPrefix,
21
+ options.preset.withCommandPrefix,
24
22
  )
25
23
 
26
24
  return yield* cliCommand.pipe(
27
25
  worktree.execWithStallTimeout({
28
- cliAgent: options.cliAgent,
26
+ cliAgent: options.preset.cliAgent,
29
27
  stallTimeout: options.stallTimeout,
30
28
  }),
31
29
  )
@@ -124,6 +124,18 @@ export class CurrentIssueSource extends ServiceMap.Service<
124
124
  ScopedRef.get(ref).pipe(
125
125
  Effect.flatMap((source) => source.info(projectId)),
126
126
  ),
127
+ issueCliAgentPreset: (issue) =>
128
+ ScopedRef.get(ref).pipe(
129
+ Effect.flatMap((source) => source.issueCliAgentPreset(issue)),
130
+ ),
131
+ updateCliAgentPreset: (preset) =>
132
+ ScopedRef.get(ref).pipe(
133
+ Effect.flatMap((source) => source.updateCliAgentPreset(preset)),
134
+ ),
135
+ cliAgentPresetInfo: (preset) =>
136
+ ScopedRef.get(ref).pipe(
137
+ Effect.flatMap((source) => source.cliAgentPresetInfo(preset)),
138
+ ),
127
139
  ensureInProgress: (projectId, issueId) =>
128
140
  ScopedRef.get(ref).pipe(
129
141
  Effect.flatMap((source) =>
package/src/Github/Cli.ts CHANGED
@@ -100,7 +100,7 @@ ${reviewCommentsMd}`
100
100
  const reviewsXml = reviews
101
101
  .map(
102
102
  (review) => `<review author="${review.author.login}">
103
- <body><![CDATA[${review.body}]]></body>
103
+ <body>${review.body}</body>
104
104
  </review>`,
105
105
  )
106
106
  .join("\n")
@@ -152,8 +152,8 @@ const renderReviewComments = (
152
152
  ) => `<comment author="${comment.author.login}" path="${comment.path}"${
153
153
  comment.originalLine ? ` originalLine="${comment.originalLine}"` : ""
154
154
  }>
155
- <diffHunk><![CDATA[${comment.diffHunk}]]></diffHunk>
156
- <body><![CDATA[${comment.body}]]></body>${
155
+ <diffHunk>${comment.diffHunk}</diffHunk>
156
+ <body>${comment.body}</body>${
157
157
  followup.length > 0
158
158
  ? `
159
159
 
@@ -161,7 +161,7 @@ const renderReviewComments = (
161
161
  .map(
162
162
  (fc) => `
163
163
  <comment author="${fc.author.login}">
164
- <body><![CDATA[${fc.body}]]></body>
164
+ <body>${fc.body}</body>
165
165
  </comment>`,
166
166
  )
167
167
  .join("")}
@@ -173,7 +173,7 @@ const renderReviewComments = (
173
173
  const renderGeneralComment = (
174
174
  comment: Comment,
175
175
  ) => ` <comment author="${comment.author.login}">
176
- <body><![CDATA[${comment.body}]]></body>
176
+ <body>${comment.body}</body>
177
177
  </comment>`
178
178
 
179
179
  // Schema definitions and GraphQL query
package/src/Github.ts CHANGED
@@ -24,6 +24,8 @@ import { TokenManager } from "./Github/TokenManager.ts"
24
24
  import { GithubCli } from "./Github/Cli.ts"
25
25
  import { Reactivity } from "effect/unstable/reactivity"
26
26
  import type { ProjectId } from "./domain/Project.ts"
27
+ import type { CliAgentPreset } from "./domain/CliAgentPreset.ts"
28
+ import { getPresetsWithMetadata } from "./Presets.ts"
27
29
 
28
30
  export class GithubError extends Data.TaggedError("GithubError")<{
29
31
  readonly cause: unknown
@@ -158,6 +160,9 @@ export const GithubIssueSource = Layer.effect(
158
160
  Stream.filter((issue) => issue.state_reason !== "not_planned"),
159
161
  )
160
162
 
163
+ const presets = yield* getPresetsWithMetadata("github", PresetMetadata)
164
+ const issuePresetMap = new Map<string, CliAgentPreset>()
165
+
161
166
  const issues = (options: {
162
167
  readonly labelFilter: Option.Option<string>
163
168
  readonly autoMergeLabelName: Option.Option<string>
@@ -177,6 +182,7 @@ export const GithubIssueSource = Layer.effect(
177
182
  Stream.filter((issue) => issue.pull_request === undefined),
178
183
  Stream.mapEffect(
179
184
  Effect.fnUntraced(function* (issue) {
185
+ const id = `#${issue.number}`
180
186
  const dependencies = yield* listOpenBlockedBy(issue.number).pipe(
181
187
  Stream.runCollect,
182
188
  )
@@ -188,8 +194,16 @@ export const GithubIssueSource = Layer.effect(
188
194
  : hasLabel(issue.labels, "in-review")
189
195
  ? "in-review"
190
196
  : "todo"
197
+
198
+ const preset = presets.find(({ metadata }) =>
199
+ hasLabel(issue.labels, metadata.label),
200
+ )
201
+ if (preset) {
202
+ issuePresetMap.set(id, preset.preset)
203
+ }
204
+
191
205
  return new PrdIssue({
192
- id: `#${issue.number}`,
206
+ id,
193
207
  title: issue.title,
194
208
  description: issue.body ?? "",
195
209
  priority: 0,
@@ -436,6 +450,28 @@ export const GithubIssueSource = Layer.effect(
436
450
  })}`,
437
451
  )
438
452
  }),
453
+ issueCliAgentPreset: (issue) =>
454
+ Effect.sync(() =>
455
+ Option.fromUndefinedOr(issuePresetMap.get(issue.id!)),
456
+ ),
457
+ updateCliAgentPreset: Effect.fnUntraced(function* (preset) {
458
+ const label = yield* Prompt.text({
459
+ message: "Enter a label for this preset",
460
+ validate(value) {
461
+ value = value.trim()
462
+ if (value.length === 0) {
463
+ return Effect.fail("Label cannot be empty")
464
+ }
465
+ return Effect.succeed(value)
466
+ },
467
+ })
468
+ return yield* preset.addMetadata("github", PresetMetadata, { label })
469
+ }),
470
+ cliAgentPresetInfo: Effect.fnUntraced(function* (preset) {
471
+ const metadata = yield* preset.decodeMetadata("github", PresetMetadata)
472
+ if (Option.isNone(metadata)) return
473
+ console.log(` Label: ${metadata.value.label}`)
474
+ }),
439
475
  ensureInProgress: Effect.fnUntraced(
440
476
  function* (_project, issueId) {
441
477
  const issueNumber = Number(issueId.slice(1))
@@ -520,6 +556,12 @@ const getOrSelectAutoMergeLabel = Effect.gen(function* () {
520
556
  return yield* autoMergeLabelSelect
521
557
  })
522
558
 
559
+ // == preset metadata
560
+
561
+ const PresetMetadata = Schema.Struct({
562
+ label: Schema.NonEmptyString,
563
+ })
564
+
523
565
  // == helpers
524
566
 
525
567
  const maybeNextPage = (page: number, linkHeader?: string) =>
@@ -1,8 +1,11 @@
1
- import { Effect, Schema, ServiceMap } from "effect"
1
+ import { Effect, Option, Schema, ServiceMap } from "effect"
2
2
  import type { PrdIssue } from "./domain/PrdIssue.ts"
3
3
  import { Reactivity } from "effect/unstable/reactivity"
4
4
  import type { ProjectId } from "./domain/Project.ts"
5
5
  import type { CurrentProjectId, Settings } from "./Settings.ts"
6
+ import type { CliAgentPreset } from "./domain/CliAgentPreset.ts"
7
+ import type { Environment } from "effect/unstable/cli/Prompt"
8
+ import type { QuitError } from "effect/Terminal"
6
9
 
7
10
  export class IssueSource extends ServiceMap.Service<
8
11
  IssueSource,
@@ -43,6 +46,20 @@ export class IssueSource extends ServiceMap.Service<
43
46
  projectId: ProjectId,
44
47
  ) => Effect.Effect<void, IssueSourceError>
45
48
 
49
+ readonly issueCliAgentPreset: (
50
+ issue: PrdIssue,
51
+ ) => Effect.Effect<Option.Option<CliAgentPreset>, IssueSourceError>
52
+ readonly updateCliAgentPreset: (
53
+ preset: CliAgentPreset,
54
+ ) => Effect.Effect<
55
+ CliAgentPreset,
56
+ IssueSourceError | QuitError,
57
+ Environment
58
+ >
59
+ readonly cliAgentPresetInfo: (
60
+ preset: CliAgentPreset,
61
+ ) => Effect.Effect<void, IssueSourceError>
62
+
46
63
  readonly ensureInProgress: (
47
64
  projectId: ProjectId,
48
65
  issueId: string,
package/src/Linear.ts CHANGED
@@ -29,6 +29,8 @@ import {
29
29
  } from "./domain/LinearIssues.ts"
30
30
  import { Reactivity } from "effect/unstable/reactivity"
31
31
  import type { ProjectId } from "./domain/Project.ts"
32
+ import { getPresetsWithMetadata } from "./Presets.ts"
33
+ import type { CliAgentPreset } from "./domain/CliAgentPreset.ts"
32
34
 
33
35
  class Linear extends ServiceMap.Service<Linear>()("lalph/Linear", {
34
36
  make: Effect.gen(function* () {
@@ -174,8 +176,11 @@ export const LinearIssueSource = Layer.effect(
174
176
  capacity: Number.POSITIVE_INFINITY,
175
177
  })
176
178
 
179
+ const presets = yield* getPresetsWithMetadata("linear", PresetMetadata)
180
+
177
181
  // Map of linear identifier to issue id
178
182
  const identifierMap = new Map<string, string>()
183
+ const presetMap = new Map<string, CliAgentPreset>()
179
184
 
180
185
  const backlogState =
181
186
  linear.states.find(
@@ -258,6 +263,13 @@ export const LinearIssueSource = Layer.effect(
258
263
  return pipe(
259
264
  Array.filter(issues, (issue) => {
260
265
  identifierMap.set(issue.identifier, issue.id)
266
+ const preset = presets.find((p) =>
267
+ issue.labelIds.includes(p.metadata.labelId),
268
+ )
269
+ if (preset) {
270
+ presetMap.set(issue.identifier, preset.preset)
271
+ }
272
+
261
273
  const completedAt = issue.completedAt
262
274
  if (!completedAt) return true
263
275
  return DateTime.isGreaterThanOrEqualTo(completedAt, threeDaysAgo)
@@ -477,11 +489,44 @@ export const LinearIssueSource = Layer.effect(
477
489
  },
478
490
  Effect.mapError((cause) => new IssueSourceError({ cause })),
479
491
  ),
492
+ issueCliAgentPreset: (_issue) =>
493
+ Effect.sync(() => Option.fromUndefinedOr(presetMap.get(_issue.id!))),
494
+ updateCliAgentPreset: Effect.fnUntraced(function* (preset) {
495
+ const labels = yield* Stream.runCollect(linear.labels).pipe(
496
+ Effect.mapError((cause) => new IssueSourceError({ cause })),
497
+ )
498
+ const labelId = yield* Prompt.autoComplete({
499
+ message: "Select a label for this preset",
500
+ choices: labels.map((label) => ({
501
+ title: label.name,
502
+ value: label.id,
503
+ })),
504
+ })
505
+ return yield* preset.addMetadata("linear", PresetMetadata, {
506
+ labelId,
507
+ })
508
+ }),
509
+ cliAgentPresetInfo: Effect.fnUntraced(
510
+ function* (preset) {
511
+ const metadata = yield* preset.decodeMetadata(
512
+ "linear",
513
+ PresetMetadata,
514
+ )
515
+ if (Option.isNone(metadata)) return
516
+ const label = yield* linear.labels.pipe(
517
+ Stream.filter((label) => label.id === metadata.value.labelId),
518
+ Stream.runHead,
519
+ )
520
+ if (Option.isNone(label)) return
521
+ console.log(` Label: ${label.value.name}`)
522
+ },
523
+ Effect.mapError((cause) => new IssueSourceError({ cause })),
524
+ ),
480
525
  // linear api writes and reflected immediately in reads, so no-op
481
526
  ensureInProgress: () => Effect.void,
482
527
  })
483
528
  }),
484
- ).pipe(Layer.provide([Linear.layer, Reactivity.layer]))
529
+ ).pipe(Layer.provide([Linear.layer, Reactivity.layer, Settings.layer]))
485
530
 
486
531
  export class LinearError extends Schema.ErrorClass("lalph/LinearError")({
487
532
  _tag: Schema.tag("LinearError"),
@@ -615,6 +660,12 @@ const getOrSelectAutoMergeLabel = Effect.gen(function* () {
615
660
  return yield* autoMergeLabelIdSelect
616
661
  })
617
662
 
663
+ // preset metadata schema
664
+
665
+ const PresetMetadata = Schema.Struct({
666
+ labelId: Schema.String,
667
+ })
668
+
618
669
  // graphql queries
619
670
  const issueQueryFields = `
620
671
  id