lalph 0.1.50 → 0.1.51
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/dist/cli.mjs +69 -34
- package/package.json +1 -1
- package/src/CreateIssue.ts +2 -0
- package/src/Github.ts +33 -1
- package/src/Linear.ts +43 -3
- package/src/Prd.ts +4 -0
- package/src/Runner.ts +7 -11
- package/src/cli.ts +0 -8
- package/src/domain/PrdIssue.ts +4 -0
package/dist/cli.mjs
CHANGED
|
@@ -60210,6 +60210,7 @@ var PrdIssue = class PrdIssue extends Class("PrdIssue")({
|
|
|
60210
60210
|
]).annotate({ description: "The state of the issue." }),
|
|
60211
60211
|
blockedBy: Array$1(String$1).annotate({ description: "An array of issue IDs that block this issue. These issues must be completed before this issue can be worked on." }),
|
|
60212
60212
|
complete: Boolean$2.annotate({ description: "Whether the issue is complete." }),
|
|
60213
|
+
autoMerge: Boolean$2.annotate({ description: "Whether the issue should be auto-merged when complete. Read-only field" }),
|
|
60213
60214
|
githubPrNumber: NullOr(Finite).annotate({ description: "The created or updated Github pull request number for this task." })
|
|
60214
60215
|
}) {
|
|
60215
60216
|
static Array = Array$1(this);
|
|
@@ -134468,6 +134469,7 @@ const LinearIssueSource = effect(IssueSource, gen(function* () {
|
|
|
134468
134469
|
const project = yield* getOrSelectProject;
|
|
134469
134470
|
const teamId = yield* getOrSelectTeamId(project);
|
|
134470
134471
|
const labelId = yield* getOrSelectLabel$1;
|
|
134472
|
+
const autoMergeLabelId = yield* getOrSelectAutoMergeLabel$1;
|
|
134471
134473
|
const identifierMap = /* @__PURE__ */ new Map();
|
|
134472
134474
|
const backlogState = linear.states.find((s) => s.type === "backlog" && s.name.toLowerCase().includes("backlog")) || linear.states.find((s) => s.type === "backlog");
|
|
134473
134475
|
const todoState = linear.states.find((s) => s.type === "unstarted" && (s.name.toLowerCase().includes("todo") || s.name.toLowerCase().includes("unstarted"))) || linear.states.find((s) => s.type === "unstarted");
|
|
@@ -134527,6 +134529,7 @@ const LinearIssueSource = effect(IssueSource, gen(function* () {
|
|
|
134527
134529
|
state,
|
|
134528
134530
|
complete: state === "in-review" || state === "done",
|
|
134529
134531
|
blockedBy: blockedBy.map((i) => i.identifier),
|
|
134532
|
+
autoMerge: autoMergeLabelId.pipe(map$11((labelId$1) => issue.labelIds.includes(labelId$1)), getOrElse(() => false)),
|
|
134530
134533
|
githubPrNumber: null
|
|
134531
134534
|
});
|
|
134532
134535
|
}), { concurrency: 10 }), runCollect, mapError$2((cause) => new IssueSourceError({ cause })));
|
|
@@ -134595,6 +134598,7 @@ const resetLinear = gen(function* () {
|
|
|
134595
134598
|
yield* selectedProjectId.set(none$3());
|
|
134596
134599
|
yield* selectedTeamId.set(none$3());
|
|
134597
134600
|
yield* selectedLabelId.set(none$3());
|
|
134601
|
+
yield* selectedAutoMergeLabelId.set(none$3());
|
|
134598
134602
|
});
|
|
134599
134603
|
var LinearError = class extends ErrorClass("lalph/LinearError")({
|
|
134600
134604
|
_tag: tag("LinearError"),
|
|
@@ -134655,10 +134659,32 @@ const labelIdSelect = gen(function* () {
|
|
|
134655
134659
|
return labelId;
|
|
134656
134660
|
});
|
|
134657
134661
|
const getOrSelectLabel$1 = gen(function* () {
|
|
134658
|
-
const
|
|
134659
|
-
if (isSome(
|
|
134662
|
+
const labelId = yield* selectedLabelId.get;
|
|
134663
|
+
if (isSome(labelId)) return labelId.value;
|
|
134660
134664
|
return yield* labelIdSelect;
|
|
134661
134665
|
});
|
|
134666
|
+
const selectedAutoMergeLabelId = new Setting("linear.selectedAutoMergeLabelId", Option(String$1));
|
|
134667
|
+
const autoMergeLabelIdSelect = gen(function* () {
|
|
134668
|
+
const linear = yield* Linear;
|
|
134669
|
+
const labels = yield* runCollect(linear.labels);
|
|
134670
|
+
const labelId = yield* select({
|
|
134671
|
+
message: "Select a label to mark issues for auto merge",
|
|
134672
|
+
choices: [{
|
|
134673
|
+
title: "Disabled",
|
|
134674
|
+
value: none$3()
|
|
134675
|
+
}].concat(labels.map((label) => ({
|
|
134676
|
+
title: label.name,
|
|
134677
|
+
value: some(label.id)
|
|
134678
|
+
})))
|
|
134679
|
+
});
|
|
134680
|
+
yield* selectedAutoMergeLabelId.set(some(labelId));
|
|
134681
|
+
return labelId;
|
|
134682
|
+
});
|
|
134683
|
+
const getOrSelectAutoMergeLabel$1 = gen(function* () {
|
|
134684
|
+
const labelId = yield* selectedAutoMergeLabelId.get;
|
|
134685
|
+
if (isSome(labelId)) return labelId.value;
|
|
134686
|
+
return yield* autoMergeLabelIdSelect;
|
|
134687
|
+
});
|
|
134662
134688
|
|
|
134663
134689
|
//#endregion
|
|
134664
134690
|
//#region node_modules/.pnpm/universal-user-agent@7.0.3/node_modules/universal-user-agent/index.js
|
|
@@ -140773,6 +140799,7 @@ const GithubIssueSource = effect(IssueSource, gen(function* () {
|
|
|
140773
140799
|
const github = yield* Github;
|
|
140774
140800
|
const [owner, repo] = (yield* make$21`gh repo view --json nameWithOwner -q ${".nameWithOwner"}`.pipe(string, option$1, flatMap((o) => o.pipe(map$11(trim), filter$5(isNonEmpty), fromOption)), mapError$2((_) => new GithubRepoNotFound()))).split("/");
|
|
140775
140801
|
const labelFilter$1 = yield* getOrSelectLabel;
|
|
140802
|
+
const autoMergeLabelName = yield* getOrSelectAutoMergeLabel;
|
|
140776
140803
|
const hasLabel = (label, name) => label.some((l) => typeof l === "string" ? l === name : l.name === name);
|
|
140777
140804
|
const listOpenBlockedBy = (issueId) => github.stream((rest, page) => rest.issues.listDependenciesBlockedBy({
|
|
140778
140805
|
owner,
|
|
@@ -140809,6 +140836,7 @@ const GithubIssueSource = effect(IssueSource, gen(function* () {
|
|
|
140809
140836
|
state,
|
|
140810
140837
|
complete: state === "done" || state === "in-review",
|
|
140811
140838
|
blockedBy: dependencies.map((dep) => `#${dep.number}`),
|
|
140839
|
+
autoMerge: autoMergeLabelName.pipe(map$11((labelName) => hasLabel(issue.labels, labelName)), getOrElse(() => false)),
|
|
140812
140840
|
githubPrNumber: null
|
|
140813
140841
|
});
|
|
140814
140842
|
}), { concurrency: 10 }), runCollect, mapError$2((cause) => new IssueSourceError({ cause })));
|
|
@@ -140928,7 +140956,19 @@ const getOrSelectLabel = gen(function* () {
|
|
|
140928
140956
|
if (isSome(label)) return label.value;
|
|
140929
140957
|
return yield* labelSelect;
|
|
140930
140958
|
});
|
|
140931
|
-
const
|
|
140959
|
+
const autoMergeLabel = new Setting("github.autoMergeLabel", Option(String$1));
|
|
140960
|
+
const autoMergeLabelSelect = gen(function* () {
|
|
140961
|
+
const label = yield* text$2({ message: "What label do you want to use for auto-mergable issues? (leave empty for none)" });
|
|
140962
|
+
const labelOption = some(label.trim()).pipe(filter$5(isNonEmpty));
|
|
140963
|
+
yield* autoMergeLabel.set(some(labelOption));
|
|
140964
|
+
return labelOption;
|
|
140965
|
+
});
|
|
140966
|
+
const getOrSelectAutoMergeLabel = gen(function* () {
|
|
140967
|
+
const label = yield* autoMergeLabel.get;
|
|
140968
|
+
if (isSome(label)) return label.value;
|
|
140969
|
+
return yield* autoMergeLabelSelect;
|
|
140970
|
+
});
|
|
140971
|
+
const resetGithub = labelFilter.set(none$3()).pipe(andThen(autoMergeLabel.set(none$3())));
|
|
140932
140972
|
const maybeNextPage = (page, linkHeader) => pipe(fromNullishOr$2(linkHeader), filter$5((_) => _.includes(`rel="next"`)), as$2(page + 1));
|
|
140933
140973
|
|
|
140934
140974
|
//#endregion
|
|
@@ -141281,21 +141321,24 @@ var Prd = class extends Service()("lalph/Prd", { make: gen(function* () {
|
|
|
141281
141321
|
});
|
|
141282
141322
|
});
|
|
141283
141323
|
const mergeConflictInstruction = "Next step: Rebase PR and resolve merge conflicts.";
|
|
141324
|
+
const flagUnmergable = fnUntraced(function* (options) {
|
|
141325
|
+
const issue = current.find((entry) => entry.id === options.issueId);
|
|
141326
|
+
if (!issue) return;
|
|
141327
|
+
const nextDescription = issue.description.includes(mergeConflictInstruction) ? issue.description : `${mergeConflictInstruction}\n\n${issue.description.trim()}`;
|
|
141328
|
+
yield* source.updateIssue({
|
|
141329
|
+
issueId: issue.id,
|
|
141330
|
+
description: nextDescription,
|
|
141331
|
+
state: "todo"
|
|
141332
|
+
});
|
|
141333
|
+
});
|
|
141334
|
+
const findById = (issueId) => sync(() => current.find((i) => i.id === issueId) ?? null);
|
|
141284
141335
|
return {
|
|
141285
141336
|
path: prdFile,
|
|
141286
141337
|
mergableGithubPrs,
|
|
141287
141338
|
revertStateIds,
|
|
141288
141339
|
maybeRevertIssue,
|
|
141289
|
-
flagUnmergable
|
|
141290
|
-
|
|
141291
|
-
if (!issue) return;
|
|
141292
|
-
const nextDescription = issue.description.includes(mergeConflictInstruction) ? issue.description : `${mergeConflictInstruction}\n\n${issue.description.trim()}`;
|
|
141293
|
-
yield* source.updateIssue({
|
|
141294
|
-
issueId: issue.id,
|
|
141295
|
-
description: nextDescription,
|
|
141296
|
-
state: "todo"
|
|
141297
|
-
});
|
|
141298
|
-
})
|
|
141340
|
+
flagUnmergable,
|
|
141341
|
+
findById
|
|
141299
141342
|
};
|
|
141300
141343
|
}) }) {
|
|
141301
141344
|
static layerNoWorktree = effect(this, this.make);
|
|
@@ -141331,14 +141374,8 @@ const run = fnUntraced(function* (options) {
|
|
|
141331
141374
|
const promptGen = yield* PromptGen;
|
|
141332
141375
|
const cliAgent = yield* getOrSelectCliAgent;
|
|
141333
141376
|
const prd = yield* Prd;
|
|
141334
|
-
const exec = (template, ...args$1) => make$21({
|
|
141335
|
-
|
|
141336
|
-
extendEnv: true
|
|
141337
|
-
})(template, ...args$1).pipe(exitCode);
|
|
141338
|
-
const execOutput = (template, ...args$1) => make$21({
|
|
141339
|
-
cwd: worktree.directory,
|
|
141340
|
-
extendEnv: true
|
|
141341
|
-
})(template, ...args$1).pipe(string, map$5((output) => output.trim()));
|
|
141377
|
+
const exec = (template, ...args$1) => make$21({ cwd: worktree.directory })(template, ...args$1).pipe(exitCode);
|
|
141378
|
+
const execOutput = (template, ...args$1) => make$21({ cwd: worktree.directory })(template, ...args$1).pipe(string, map$5((output) => output.trim()));
|
|
141342
141379
|
const execWithStallTimeout = fnUntraced(function* (command) {
|
|
141343
141380
|
let lastOutputAt = yield* now;
|
|
141344
141381
|
const stallTimeout = suspend$2(function loop() {
|
|
@@ -141385,29 +141422,28 @@ const run = fnUntraced(function* (options) {
|
|
|
141385
141422
|
onTimeout: () => fail$4(new RunnerStalled())
|
|
141386
141423
|
}));
|
|
141387
141424
|
const taskJson = yield* fs.readFileString(pathService.join(worktree.directory, ".lalph", "task.json"));
|
|
141388
|
-
const
|
|
141425
|
+
const taskId = (yield* decodeEffect(ChosenTask)(taskJson)).id;
|
|
141426
|
+
const task = yield* prd.findById(taskId);
|
|
141427
|
+
if (!task) return;
|
|
141389
141428
|
yield* completeWith(options.startedDeferred, void_$1);
|
|
141390
141429
|
const exitCode$1 = yield* execWithStallTimeout(cliAgent.command({
|
|
141391
141430
|
prompt: promptGen.prompt({
|
|
141392
|
-
taskId
|
|
141431
|
+
taskId,
|
|
141393
141432
|
targetBranch: getOrUndefined(options.targetBranch),
|
|
141394
141433
|
specsDirectory: options.specsDirectory
|
|
141395
141434
|
}),
|
|
141396
141435
|
prdFilePath: pathService.join(".lalph", "prd.yml")
|
|
141397
141436
|
})).pipe(timeout(options.runTimeout), catchTag("TimeoutError", fnUntraced(function* (error$1) {
|
|
141398
141437
|
yield* execWithStallTimeout(cliAgent.command({
|
|
141399
|
-
prompt: promptGen.promptTimeout({ taskId
|
|
141438
|
+
prompt: promptGen.promptTimeout({ taskId }),
|
|
141400
141439
|
prdFilePath: pathService.join(".lalph", "prd.yml")
|
|
141401
141440
|
}));
|
|
141402
141441
|
return yield* error$1;
|
|
141403
141442
|
})));
|
|
141404
141443
|
yield* log$1(`Agent exited with code: ${exitCode$1}`);
|
|
141405
141444
|
const prs = yield* prd.mergableGithubPrs;
|
|
141406
|
-
if (prs.length === 0) yield* prd.maybeRevertIssue({
|
|
141407
|
-
|
|
141408
|
-
issueId: task.id
|
|
141409
|
-
});
|
|
141410
|
-
else if (options.autoMerge) for (const pr of prs) {
|
|
141445
|
+
if (prs.length === 0) yield* prd.maybeRevertIssue({ issueId: taskId });
|
|
141446
|
+
else if (task.autoMerge) for (const pr of prs) {
|
|
141411
141447
|
if (isSome(options.targetBranch)) yield* exec`gh pr edit ${pr.prNumber} --base ${options.targetBranch.value}`;
|
|
141412
141448
|
if ((yield* exec`gh pr merge ${pr.prNumber} -sd`) !== 0) yield* prd.flagUnmergable({ issueId: pr.issueId });
|
|
141413
141449
|
}
|
|
@@ -141483,6 +141519,7 @@ title: Issue Title
|
|
|
141483
141519
|
priority: 3
|
|
141484
141520
|
estimate: null
|
|
141485
141521
|
blockedBy: []
|
|
141522
|
+
autoMerge: false
|
|
141486
141523
|
---
|
|
141487
141524
|
|
|
141488
141525
|
Describe the issue here.`;
|
|
@@ -141490,7 +141527,8 @@ const FrontMatterSchema = toCodecJson(Struct({
|
|
|
141490
141527
|
title: String$1,
|
|
141491
141528
|
priority: Finite,
|
|
141492
141529
|
estimate: NullOr(Finite),
|
|
141493
|
-
blockedBy: Array$1(String$1)
|
|
141530
|
+
blockedBy: Array$1(String$1),
|
|
141531
|
+
autoMerge: Boolean$2
|
|
141494
141532
|
}));
|
|
141495
141533
|
const createIssue = make$27("issue").pipe(withDescription("Create a new issue in the selected issue source"), withHandler(fnUntraced(function* () {
|
|
141496
141534
|
const source = yield* IssueSource;
|
|
@@ -141536,7 +141574,6 @@ const createIssue = make$27("issue").pipe(withDescription("Create a new issue in
|
|
|
141536
141574
|
//#region src/cli.ts
|
|
141537
141575
|
const iterations = integer("iterations").pipe(withDescription$1("Number of iterations to run, defaults to unlimited"), withAlias("i"), withDefault(Number.POSITIVE_INFINITY));
|
|
141538
141576
|
const concurrency = integer("concurrency").pipe(withDescription$1("Number of concurrent agents, defaults to 1"), withAlias("c"), withDefault(1));
|
|
141539
|
-
const autoMerge = boolean("auto-merge").pipe(withAlias("a"), withDescription$1("Automatically merge eligible PRs"));
|
|
141540
141577
|
const targetBranch = string$1("target-branch").pipe(withDescription$1("Target branch for PRs. Env variable: LALPH_TARGET_BRANCH"), withAlias("b"), withFallbackConfig(string$4("LALPH_TARGET_BRANCH")), optional);
|
|
141541
141578
|
const maxIterationMinutes = integer("max-minutes").pipe(withDescription$1("Maximum number of minutes to allow an iteration to run. Defaults to 90 minutes. Env variable: LALPH_MAX_MINUTES"), withFallbackConfig(int("LALPH_MAX_MINUTES")), withDefault(90));
|
|
141542
141579
|
const stallMinutes = integer("stall-minutes").pipe(withDescription$1("If no activity occurs for this many minutes, the iteration will be stopped. Defaults to 5 minutes. Env variable: LALPH_STALL_MINUTES"), withFallbackConfig(int("LALPH_STALL_MINUTES")), withDefault(5));
|
|
@@ -141545,13 +141582,12 @@ const reset = boolean("reset").pipe(withDescription$1("Reset the current issue s
|
|
|
141545
141582
|
const root = make$27("lalph", {
|
|
141546
141583
|
iterations,
|
|
141547
141584
|
concurrency,
|
|
141548
|
-
autoMerge,
|
|
141549
141585
|
targetBranch,
|
|
141550
141586
|
maxIterationMinutes,
|
|
141551
141587
|
stallMinutes,
|
|
141552
141588
|
reset,
|
|
141553
141589
|
specsDirectory
|
|
141554
|
-
}).pipe(withHandler(fnUntraced(function* ({ iterations: iterations$1, concurrency: concurrency$1,
|
|
141590
|
+
}).pipe(withHandler(fnUntraced(function* ({ iterations: iterations$1, concurrency: concurrency$1, targetBranch: targetBranch$1, maxIterationMinutes: maxIterationMinutes$1, stallMinutes: stallMinutes$1, reset: reset$2, specsDirectory: specsDirectory$1 }) {
|
|
141555
141591
|
if (reset$2) yield* resetCurrentIssueSource;
|
|
141556
141592
|
const source = yield* build(CurrentIssueSource.layer);
|
|
141557
141593
|
yield* getOrSelectCliAgent;
|
|
@@ -141570,7 +141606,6 @@ const root = make$27("lalph", {
|
|
|
141570
141606
|
const startedDeferred = yield* make$47();
|
|
141571
141607
|
yield* checkForWork.pipe(andThen(run({
|
|
141572
141608
|
startedDeferred,
|
|
141573
|
-
autoMerge: autoMerge$1,
|
|
141574
141609
|
targetBranch: targetBranch$1,
|
|
141575
141610
|
specsDirectory: specsDirectory$1,
|
|
141576
141611
|
stallTimeout: minutes(stallMinutes$1),
|
package/package.json
CHANGED
package/src/CreateIssue.ts
CHANGED
|
@@ -11,6 +11,7 @@ title: Issue Title
|
|
|
11
11
|
priority: 3
|
|
12
12
|
estimate: null
|
|
13
13
|
blockedBy: []
|
|
14
|
+
autoMerge: false
|
|
14
15
|
---
|
|
15
16
|
|
|
16
17
|
Describe the issue here.`
|
|
@@ -21,6 +22,7 @@ const FrontMatterSchema = Schema.toCodecJson(
|
|
|
21
22
|
priority: Schema.Finite,
|
|
22
23
|
estimate: Schema.NullOr(Schema.Finite),
|
|
23
24
|
blockedBy: Schema.Array(Schema.String),
|
|
25
|
+
autoMerge: Schema.Boolean,
|
|
24
26
|
}),
|
|
25
27
|
)
|
|
26
28
|
|
package/src/Github.ts
CHANGED
|
@@ -112,6 +112,7 @@ export const GithubIssueSource = Layer.effect(
|
|
|
112
112
|
)
|
|
113
113
|
const [owner, repo] = nameWithOwner.split("/") as [string, string]
|
|
114
114
|
const labelFilter = yield* getOrSelectLabel
|
|
115
|
+
const autoMergeLabelName = yield* getOrSelectAutoMergeLabel
|
|
115
116
|
|
|
116
117
|
const hasLabel = (
|
|
117
118
|
label: ReadonlyArray<
|
|
@@ -190,6 +191,10 @@ export const GithubIssueSource = Layer.effect(
|
|
|
190
191
|
state,
|
|
191
192
|
complete: state === "done" || state === "in-review",
|
|
192
193
|
blockedBy: dependencies.map((dep) => `#${dep.number}`),
|
|
194
|
+
autoMerge: autoMergeLabelName.pipe(
|
|
195
|
+
Option.map((labelName) => hasLabel(issue.labels, labelName)),
|
|
196
|
+
Option.getOrElse(() => false),
|
|
197
|
+
),
|
|
193
198
|
githubPrNumber: null,
|
|
194
199
|
})
|
|
195
200
|
}),
|
|
@@ -411,7 +416,34 @@ const getOrSelectLabel = Effect.gen(function* () {
|
|
|
411
416
|
return yield* labelSelect
|
|
412
417
|
})
|
|
413
418
|
|
|
414
|
-
|
|
419
|
+
// == auto merge label
|
|
420
|
+
|
|
421
|
+
const autoMergeLabel = new Setting(
|
|
422
|
+
"github.autoMergeLabel",
|
|
423
|
+
Schema.Option(Schema.String),
|
|
424
|
+
)
|
|
425
|
+
const autoMergeLabelSelect = Effect.gen(function* () {
|
|
426
|
+
const label = yield* Prompt.text({
|
|
427
|
+
message:
|
|
428
|
+
"What label do you want to use for auto-mergable issues? (leave empty for none)",
|
|
429
|
+
})
|
|
430
|
+
const labelOption = Option.some(label.trim()).pipe(
|
|
431
|
+
Option.filter(String.isNonEmpty),
|
|
432
|
+
)
|
|
433
|
+
yield* autoMergeLabel.set(Option.some(labelOption))
|
|
434
|
+
return labelOption
|
|
435
|
+
})
|
|
436
|
+
const getOrSelectAutoMergeLabel = Effect.gen(function* () {
|
|
437
|
+
const label = yield* autoMergeLabel.get
|
|
438
|
+
if (Option.isSome(label)) {
|
|
439
|
+
return label.value
|
|
440
|
+
}
|
|
441
|
+
return yield* autoMergeLabelSelect
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
export const resetGithub = labelFilter
|
|
445
|
+
.set(Option.none())
|
|
446
|
+
.pipe(Effect.andThen(autoMergeLabel.set(Option.none())))
|
|
415
447
|
|
|
416
448
|
// == helpers
|
|
417
449
|
|
package/src/Linear.ts
CHANGED
|
@@ -120,6 +120,7 @@ export const LinearIssueSource = Layer.effect(
|
|
|
120
120
|
const project = yield* getOrSelectProject
|
|
121
121
|
const teamId = yield* getOrSelectTeamId(project)
|
|
122
122
|
const labelId = yield* getOrSelectLabel
|
|
123
|
+
const autoMergeLabelId = yield* getOrSelectAutoMergeLabel
|
|
123
124
|
|
|
124
125
|
// Map of linear identifier to issue id
|
|
125
126
|
const identifierMap = new Map<string, string>()
|
|
@@ -231,6 +232,10 @@ export const LinearIssueSource = Layer.effect(
|
|
|
231
232
|
state,
|
|
232
233
|
complete: state === "in-review" || state === "done",
|
|
233
234
|
blockedBy: blockedBy.map((i) => i.identifier),
|
|
235
|
+
autoMerge: autoMergeLabelId.pipe(
|
|
236
|
+
Option.map((labelId) => issue.labelIds.includes(labelId)),
|
|
237
|
+
Option.getOrElse(() => false),
|
|
238
|
+
),
|
|
234
239
|
githubPrNumber: null,
|
|
235
240
|
})
|
|
236
241
|
}),
|
|
@@ -372,6 +377,7 @@ export const resetLinear = Effect.gen(function* () {
|
|
|
372
377
|
yield* selectedProjectId.set(Option.none())
|
|
373
378
|
yield* selectedTeamId.set(Option.none())
|
|
374
379
|
yield* selectedLabelId.set(Option.none())
|
|
380
|
+
yield* selectedAutoMergeLabelId.set(Option.none())
|
|
375
381
|
})
|
|
376
382
|
|
|
377
383
|
export class LinearError extends Schema.ErrorClass("lalph/LinearError")({
|
|
@@ -459,9 +465,43 @@ const labelIdSelect = Effect.gen(function* () {
|
|
|
459
465
|
return labelId
|
|
460
466
|
})
|
|
461
467
|
const getOrSelectLabel = Effect.gen(function* () {
|
|
462
|
-
const
|
|
463
|
-
if (Option.isSome(
|
|
464
|
-
return
|
|
468
|
+
const labelId = yield* selectedLabelId.get
|
|
469
|
+
if (Option.isSome(labelId)) {
|
|
470
|
+
return labelId.value
|
|
465
471
|
}
|
|
466
472
|
return yield* labelIdSelect
|
|
467
473
|
})
|
|
474
|
+
|
|
475
|
+
// Auto merge label selection
|
|
476
|
+
|
|
477
|
+
const selectedAutoMergeLabelId = new Setting(
|
|
478
|
+
"linear.selectedAutoMergeLabelId",
|
|
479
|
+
Schema.Option(Schema.String),
|
|
480
|
+
)
|
|
481
|
+
const autoMergeLabelIdSelect = Effect.gen(function* () {
|
|
482
|
+
const linear = yield* Linear
|
|
483
|
+
const labels = yield* Stream.runCollect(linear.labels)
|
|
484
|
+
const labelId = yield* Prompt.select({
|
|
485
|
+
message: "Select a label to mark issues for auto merge",
|
|
486
|
+
choices: [
|
|
487
|
+
{
|
|
488
|
+
title: "Disabled",
|
|
489
|
+
value: Option.none<string>(),
|
|
490
|
+
},
|
|
491
|
+
].concat(
|
|
492
|
+
labels.map((label) => ({
|
|
493
|
+
title: label.name,
|
|
494
|
+
value: Option.some(label.id),
|
|
495
|
+
})),
|
|
496
|
+
),
|
|
497
|
+
})
|
|
498
|
+
yield* selectedAutoMergeLabelId.set(Option.some(labelId))
|
|
499
|
+
return labelId
|
|
500
|
+
})
|
|
501
|
+
const getOrSelectAutoMergeLabel = Effect.gen(function* () {
|
|
502
|
+
const labelId = yield* selectedAutoMergeLabelId.get
|
|
503
|
+
if (Option.isSome(labelId)) {
|
|
504
|
+
return labelId.value
|
|
505
|
+
}
|
|
506
|
+
return yield* autoMergeLabelIdSelect
|
|
507
|
+
})
|
package/src/Prd.ts
CHANGED
|
@@ -193,12 +193,16 @@ export class Prd extends ServiceMap.Service<Prd>()("lalph/Prd", {
|
|
|
193
193
|
})
|
|
194
194
|
})
|
|
195
195
|
|
|
196
|
+
const findById = (issueId: string) =>
|
|
197
|
+
Effect.sync(() => current.find((i) => i.id === issueId) ?? null)
|
|
198
|
+
|
|
196
199
|
return {
|
|
197
200
|
path: prdFile,
|
|
198
201
|
mergableGithubPrs,
|
|
199
202
|
revertStateIds,
|
|
200
203
|
maybeRevertIssue,
|
|
201
204
|
flagUnmergable,
|
|
205
|
+
findById,
|
|
202
206
|
} as const
|
|
203
207
|
}),
|
|
204
208
|
}) {
|
package/src/Runner.ts
CHANGED
|
@@ -19,7 +19,6 @@ import { getOrSelectCliAgent } from "./CliAgent.ts"
|
|
|
19
19
|
export const run = Effect.fnUntraced(
|
|
20
20
|
function* (options: {
|
|
21
21
|
readonly startedDeferred: Deferred.Deferred<void>
|
|
22
|
-
readonly autoMerge: boolean
|
|
23
22
|
readonly targetBranch: Option.Option<string>
|
|
24
23
|
readonly specsDirectory: string
|
|
25
24
|
readonly stallTimeout: Duration.Duration
|
|
@@ -38,7 +37,6 @@ export const run = Effect.fnUntraced(
|
|
|
38
37
|
) =>
|
|
39
38
|
ChildProcess.make({
|
|
40
39
|
cwd: worktree.directory,
|
|
41
|
-
extendEnv: true,
|
|
42
40
|
})(template, ...args).pipe(ChildProcess.exitCode)
|
|
43
41
|
|
|
44
42
|
const execOutput = (
|
|
@@ -47,7 +45,6 @@ export const run = Effect.fnUntraced(
|
|
|
47
45
|
) =>
|
|
48
46
|
ChildProcess.make({
|
|
49
47
|
cwd: worktree.directory,
|
|
50
|
-
extendEnv: true,
|
|
51
48
|
})(template, ...args).pipe(
|
|
52
49
|
ChildProcess.string,
|
|
53
50
|
Effect.map((output) => output.trim()),
|
|
@@ -131,13 +128,15 @@ export const run = Effect.fnUntraced(
|
|
|
131
128
|
const taskJson = yield* fs.readFileString(
|
|
132
129
|
pathService.join(worktree.directory, ".lalph", "task.json"),
|
|
133
130
|
)
|
|
134
|
-
const
|
|
131
|
+
const taskId = (yield* Schema.decodeEffect(ChosenTask)(taskJson)).id
|
|
132
|
+
const task = yield* prd.findById(taskId)
|
|
133
|
+
if (!task) return
|
|
135
134
|
|
|
136
135
|
yield* Deferred.completeWith(options.startedDeferred, Effect.void)
|
|
137
136
|
|
|
138
137
|
const cliCommand = cliAgent.command({
|
|
139
138
|
prompt: promptGen.prompt({
|
|
140
|
-
taskId
|
|
139
|
+
taskId,
|
|
141
140
|
targetBranch: Option.getOrUndefined(options.targetBranch),
|
|
142
141
|
specsDirectory: options.specsDirectory,
|
|
143
142
|
}),
|
|
@@ -151,7 +150,7 @@ export const run = Effect.fnUntraced(
|
|
|
151
150
|
Effect.fnUntraced(function* (error) {
|
|
152
151
|
const timeoutCommand = cliAgent.command({
|
|
153
152
|
prompt: promptGen.promptTimeout({
|
|
154
|
-
taskId
|
|
153
|
+
taskId,
|
|
155
154
|
}),
|
|
156
155
|
prdFilePath: pathService.join(".lalph", "prd.yml"),
|
|
157
156
|
})
|
|
@@ -164,11 +163,8 @@ export const run = Effect.fnUntraced(
|
|
|
164
163
|
|
|
165
164
|
const prs = yield* prd.mergableGithubPrs
|
|
166
165
|
if (prs.length === 0) {
|
|
167
|
-
yield* prd.maybeRevertIssue({
|
|
168
|
-
|
|
169
|
-
issueId: task.id,
|
|
170
|
-
})
|
|
171
|
-
} else if (options.autoMerge) {
|
|
166
|
+
yield* prd.maybeRevertIssue({ issueId: taskId })
|
|
167
|
+
} else if (task.autoMerge) {
|
|
172
168
|
for (const pr of prs) {
|
|
173
169
|
if (Option.isSome(options.targetBranch)) {
|
|
174
170
|
yield* exec`gh pr edit ${pr.prNumber} --base ${options.targetBranch.value}`
|
package/src/cli.ts
CHANGED
|
@@ -37,11 +37,6 @@ const concurrency = Flag.integer("concurrency").pipe(
|
|
|
37
37
|
Flag.withDefault(1),
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
-
const autoMerge = Flag.boolean("auto-merge").pipe(
|
|
41
|
-
Flag.withAlias("a"),
|
|
42
|
-
Flag.withDescription("Automatically merge eligible PRs"),
|
|
43
|
-
)
|
|
44
|
-
|
|
45
40
|
const targetBranch = Flag.string("target-branch").pipe(
|
|
46
41
|
Flag.withDescription(
|
|
47
42
|
"Target branch for PRs. Env variable: LALPH_TARGET_BRANCH",
|
|
@@ -84,7 +79,6 @@ const reset = Flag.boolean("reset").pipe(
|
|
|
84
79
|
const root = Command.make("lalph", {
|
|
85
80
|
iterations,
|
|
86
81
|
concurrency,
|
|
87
|
-
autoMerge,
|
|
88
82
|
targetBranch,
|
|
89
83
|
maxIterationMinutes,
|
|
90
84
|
stallMinutes,
|
|
@@ -95,7 +89,6 @@ const root = Command.make("lalph", {
|
|
|
95
89
|
Effect.fnUntraced(function* ({
|
|
96
90
|
iterations,
|
|
97
91
|
concurrency,
|
|
98
|
-
autoMerge,
|
|
99
92
|
targetBranch,
|
|
100
93
|
maxIterationMinutes,
|
|
101
94
|
stallMinutes,
|
|
@@ -135,7 +128,6 @@ const root = Command.make("lalph", {
|
|
|
135
128
|
Effect.andThen(
|
|
136
129
|
run({
|
|
137
130
|
startedDeferred,
|
|
138
|
-
autoMerge,
|
|
139
131
|
targetBranch,
|
|
140
132
|
specsDirectory,
|
|
141
133
|
stallTimeout: Duration.minutes(stallMinutes),
|
package/src/domain/PrdIssue.ts
CHANGED
|
@@ -36,6 +36,10 @@ export class PrdIssue extends Schema.Class<PrdIssue>("PrdIssue")({
|
|
|
36
36
|
complete: Schema.Boolean.annotate({
|
|
37
37
|
description: "Whether the issue is complete.",
|
|
38
38
|
}),
|
|
39
|
+
autoMerge: Schema.Boolean.annotate({
|
|
40
|
+
description:
|
|
41
|
+
"Whether the issue should be auto-merged when complete. Read-only field",
|
|
42
|
+
}),
|
|
39
43
|
githubPrNumber: Schema.NullOr(Schema.Finite).annotate({
|
|
40
44
|
description:
|
|
41
45
|
"The created or updated Github pull request number for this task.",
|