lalph 0.1.77 → 0.1.78
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 +42 -6
- package/package.json +1 -1
- package/src/Github.ts +20 -0
- package/src/IssueSource.ts +23 -0
- package/src/Linear.ts +6 -1
- package/src/commands/root.ts +27 -2
package/dist/cli.mjs
CHANGED
|
@@ -135161,6 +135161,18 @@ var IssueSourceError = class extends ErrorClass("lalph/IssueSourceError")({
|
|
|
135161
135161
|
const checkForWork = gen(function* () {
|
|
135162
135162
|
if (!(yield* (yield* IssueSource).issues).some((issue) => issue.state === "todo" && issue.blockedBy.length === 0)) return yield* new NoMoreWork({});
|
|
135163
135163
|
});
|
|
135164
|
+
const resetInProgress = gen(function* () {
|
|
135165
|
+
const source = yield* IssueSource;
|
|
135166
|
+
const inProgress = (yield* source.issues).filter((issue) => issue.state === "in-progress" && issue.id !== null);
|
|
135167
|
+
if (inProgress.length === 0) return;
|
|
135168
|
+
yield* forEach$1(inProgress, (issue) => source.updateIssue({
|
|
135169
|
+
issueId: issue.id,
|
|
135170
|
+
state: "todo"
|
|
135171
|
+
}), {
|
|
135172
|
+
concurrency: 5,
|
|
135173
|
+
discard: true
|
|
135174
|
+
});
|
|
135175
|
+
});
|
|
135164
135176
|
var NoMoreWork = class extends ErrorClass("lalph/Prd/NoMoreWork")({ _tag: tag("NoMoreWork") }) {
|
|
135165
135177
|
message = "No more work to be done!";
|
|
135166
135178
|
};
|
|
@@ -135260,9 +135272,8 @@ const LinearIssueSource = effect(IssueSource, gen(function* () {
|
|
|
135260
135272
|
return isGreaterThanOrEqualTo$1(completed, threeDaysAgo);
|
|
135261
135273
|
}), mapEffect$1(fnUntraced(function* (issue) {
|
|
135262
135274
|
identifierMap.set(issue.identifier, issue.id);
|
|
135263
|
-
const
|
|
135264
|
-
const blockedBy = yield* runCollect(linear.blockedBy(issue));
|
|
135265
|
-
const state = linearStateToPrdState(linearState);
|
|
135275
|
+
const state = linearStateToPrdState(linear.states.find((s) => s.id === issue.stateId));
|
|
135276
|
+
const blockedBy = state === "todo" ? yield* runCollect(linear.blockedBy(issue)) : [];
|
|
135266
135277
|
return new PrdIssue({
|
|
135267
135278
|
id: issue.identifier,
|
|
135268
135279
|
title: issue.title,
|
|
@@ -135333,7 +135344,8 @@ const LinearIssueSource = effect(IssueSource, gen(function* () {
|
|
|
135333
135344
|
cancelIssue: fnUntraced(function* (issueId) {
|
|
135334
135345
|
const linearIssueId = identifierMap.get(issueId);
|
|
135335
135346
|
yield* linear.use((c) => c.updateIssue(linearIssueId, { stateId: canceledState.id }));
|
|
135336
|
-
}, mapError$2((cause) => new IssueSourceError({ cause })))
|
|
135347
|
+
}, mapError$2((cause) => new IssueSourceError({ cause }))),
|
|
135348
|
+
ensureInProgress: () => void_$1
|
|
135337
135349
|
});
|
|
135338
135350
|
})).pipe(provide$3(Linear.layer));
|
|
135339
135351
|
const resetLinear = gen(function* () {
|
|
@@ -141802,6 +141814,17 @@ const GithubIssueSource = effect(IssueSource, gen(function* () {
|
|
|
141802
141814
|
issue_number: Number(issueId.slice(1)),
|
|
141803
141815
|
state: "closed"
|
|
141804
141816
|
});
|
|
141817
|
+
}, mapError$2((cause) => new IssueSourceError({ cause }))),
|
|
141818
|
+
ensureInProgress: fnUntraced(function* (issueId) {
|
|
141819
|
+
const issueNumber = Number(issueId.slice(1));
|
|
141820
|
+
yield* pipe(github.request((rest) => rest.issues.get({
|
|
141821
|
+
owner: cli.owner,
|
|
141822
|
+
repo: cli.repo,
|
|
141823
|
+
issue_number: issueNumber
|
|
141824
|
+
})), repeat({
|
|
141825
|
+
until: (r) => hasLabel(r.data.labels, "in-progress"),
|
|
141826
|
+
schedule: spaced("1 second")
|
|
141827
|
+
}));
|
|
141805
141828
|
}, mapError$2((cause) => new IssueSourceError({ cause })))
|
|
141806
141829
|
});
|
|
141807
141830
|
})).pipe(provide$3([Github.layer, GithubCli.layer]));
|
|
@@ -142365,7 +142388,9 @@ const commandRoot = make$27("lalph", {
|
|
|
142365
142388
|
const runConcurrency = Math.max(1, concurrency$1);
|
|
142366
142389
|
const semaphore = makeSemaphoreUnsafe(runConcurrency);
|
|
142367
142390
|
const fibers = yield* make$24();
|
|
142391
|
+
yield* resetInProgress.pipe(provide$1(source));
|
|
142368
142392
|
yield* log$1(`Executing ${iterationsDisplay} iteration(s) with concurrency ${runConcurrency}`);
|
|
142393
|
+
if (isSome(targetBranch$1)) yield* log$1(`Using target branch: ${targetBranch$1.value}`);
|
|
142369
142394
|
let iteration = 0;
|
|
142370
142395
|
let quit = false;
|
|
142371
142396
|
while (true) {
|
|
@@ -142407,6 +142432,7 @@ const run = fnUntraced(function* (options) {
|
|
|
142407
142432
|
const gh = yield* GithubCli;
|
|
142408
142433
|
const cliAgent = yield* getOrSelectCliAgent;
|
|
142409
142434
|
const prd = yield* Prd;
|
|
142435
|
+
const source = yield* IssueSource;
|
|
142410
142436
|
const exec = (template, ...args$1) => make$21({ cwd: worktree.directory })(template, ...args$1).pipe(exitCode);
|
|
142411
142437
|
const execWithStallTimeout = fnUntraced(function* (command) {
|
|
142412
142438
|
let lastOutputAt = yield* now;
|
|
@@ -142443,8 +142469,15 @@ const run = fnUntraced(function* (options) {
|
|
|
142443
142469
|
onTimeout: () => fail$4(new RunnerStalled())
|
|
142444
142470
|
}));
|
|
142445
142471
|
const taskJson = yield* fs.readFileString(pathService.join(worktree.directory, ".lalph", "task.json"));
|
|
142446
|
-
const chosenTask = yield* decodeEffect(ChosenTask)(taskJson)
|
|
142472
|
+
const chosenTask = yield* decodeEffect(ChosenTask)(taskJson).pipe(mapError$2(() => new ChosenTaskNotFound()), tap(fnUntraced(function* (task$1) {
|
|
142473
|
+
if ((yield* prd.findById(task$1.id))?.state === "in-progress") return;
|
|
142474
|
+
return yield* new ChosenTaskNotFound();
|
|
142475
|
+
})));
|
|
142447
142476
|
taskId = chosenTask.id;
|
|
142477
|
+
yield* source.ensureInProgress(taskId).pipe(timeoutOrElse({
|
|
142478
|
+
duration: "1 minute",
|
|
142479
|
+
onTimeout: () => fail$4(new RunnerStalled())
|
|
142480
|
+
}));
|
|
142448
142481
|
yield* completeWith(options.startedDeferred, void_$1);
|
|
142449
142482
|
if (chosenTask.githubPrNumber) {
|
|
142450
142483
|
yield* exec`gh pr checkout ${chosenTask.githubPrNumber}`;
|
|
@@ -142489,6 +142522,9 @@ const run = fnUntraced(function* (options) {
|
|
|
142489
142522
|
var RunnerStalled = class extends TaggedError("RunnerStalled") {
|
|
142490
142523
|
message = "The runner has stalled due to inactivity.";
|
|
142491
142524
|
};
|
|
142525
|
+
var ChosenTaskNotFound = class extends TaggedError("ChosenTaskNotFound") {
|
|
142526
|
+
message = "The AI agent failed to choose a task.";
|
|
142527
|
+
};
|
|
142492
142528
|
const ChosenTask = fromJsonString(Struct({
|
|
142493
142529
|
id: String$1,
|
|
142494
142530
|
githubPrNumber: NullOr(Finite)
|
|
@@ -142624,7 +142660,7 @@ const commandSource = make$27("source").pipe(withDescription("Select the issue s
|
|
|
142624
142660
|
|
|
142625
142661
|
//#endregion
|
|
142626
142662
|
//#region package.json
|
|
142627
|
-
var version = "0.1.
|
|
142663
|
+
var version = "0.1.78";
|
|
142628
142664
|
|
|
142629
142665
|
//#endregion
|
|
142630
142666
|
//#region src/cli.ts
|
package/package.json
CHANGED
package/src/Github.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Option,
|
|
9
9
|
pipe,
|
|
10
10
|
RcMap,
|
|
11
|
+
Schedule,
|
|
11
12
|
Schema,
|
|
12
13
|
ServiceMap,
|
|
13
14
|
Stream,
|
|
@@ -369,6 +370,25 @@ export const GithubIssueSource = Layer.effect(
|
|
|
369
370
|
},
|
|
370
371
|
Effect.mapError((cause) => new IssueSourceError({ cause })),
|
|
371
372
|
),
|
|
373
|
+
ensureInProgress: Effect.fnUntraced(
|
|
374
|
+
function* (issueId: string) {
|
|
375
|
+
const issueNumber = Number(issueId.slice(1))
|
|
376
|
+
yield* pipe(
|
|
377
|
+
github.request((rest) =>
|
|
378
|
+
rest.issues.get({
|
|
379
|
+
owner: cli.owner,
|
|
380
|
+
repo: cli.repo,
|
|
381
|
+
issue_number: issueNumber,
|
|
382
|
+
}),
|
|
383
|
+
),
|
|
384
|
+
Effect.repeat({
|
|
385
|
+
until: (r) => hasLabel(r.data.labels, "in-progress"),
|
|
386
|
+
schedule: Schedule.spaced("1 second"),
|
|
387
|
+
}),
|
|
388
|
+
)
|
|
389
|
+
},
|
|
390
|
+
Effect.mapError((cause) => new IssueSourceError({ cause })),
|
|
391
|
+
),
|
|
372
392
|
})
|
|
373
393
|
}),
|
|
374
394
|
).pipe(Layer.provide([Github.layer, GithubCli.layer]))
|
package/src/IssueSource.ts
CHANGED
|
@@ -21,6 +21,10 @@ export class IssueSource extends ServiceMap.Service<
|
|
|
21
21
|
readonly cancelIssue: (
|
|
22
22
|
issueId: string,
|
|
23
23
|
) => Effect.Effect<void, IssueSourceError>
|
|
24
|
+
|
|
25
|
+
readonly ensureInProgress: (
|
|
26
|
+
issueId: string,
|
|
27
|
+
) => Effect.Effect<void, IssueSourceError>
|
|
24
28
|
}
|
|
25
29
|
>()("lalph/IssueSource") {}
|
|
26
30
|
|
|
@@ -42,6 +46,25 @@ export const checkForWork = Effect.gen(function* () {
|
|
|
42
46
|
}
|
|
43
47
|
})
|
|
44
48
|
|
|
49
|
+
export const resetInProgress = Effect.gen(function* () {
|
|
50
|
+
const source = yield* IssueSource
|
|
51
|
+
const issues = yield* source.issues
|
|
52
|
+
const inProgress = issues.filter(
|
|
53
|
+
(issue): issue is PrdIssue & { id: string } =>
|
|
54
|
+
issue.state === "in-progress" && issue.id !== null,
|
|
55
|
+
)
|
|
56
|
+
if (inProgress.length === 0) return
|
|
57
|
+
yield* Effect.forEach(
|
|
58
|
+
inProgress,
|
|
59
|
+
(issue) =>
|
|
60
|
+
source.updateIssue({
|
|
61
|
+
issueId: issue.id,
|
|
62
|
+
state: "todo",
|
|
63
|
+
}),
|
|
64
|
+
{ concurrency: 5, discard: true },
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
45
68
|
export class NoMoreWork extends Schema.ErrorClass<NoMoreWork>(
|
|
46
69
|
"lalph/Prd/NoMoreWork",
|
|
47
70
|
)({
|
package/src/Linear.ts
CHANGED
|
@@ -230,8 +230,11 @@ export const LinearIssueSource = Layer.effect(
|
|
|
230
230
|
const linearState = linear.states.find(
|
|
231
231
|
(s) => s.id === issue.stateId,
|
|
232
232
|
)!
|
|
233
|
-
const blockedBy = yield* Stream.runCollect(linear.blockedBy(issue))
|
|
234
233
|
const state = linearStateToPrdState(linearState)
|
|
234
|
+
const blockedBy =
|
|
235
|
+
state === "todo"
|
|
236
|
+
? yield* Stream.runCollect(linear.blockedBy(issue))
|
|
237
|
+
: []
|
|
235
238
|
return new PrdIssue({
|
|
236
239
|
id: issue.identifier,
|
|
237
240
|
title: issue.title,
|
|
@@ -377,6 +380,8 @@ export const LinearIssueSource = Layer.effect(
|
|
|
377
380
|
},
|
|
378
381
|
Effect.mapError((cause) => new IssueSourceError({ cause })),
|
|
379
382
|
),
|
|
383
|
+
// linear api writes and reflected immediately in reads, so no-op
|
|
384
|
+
ensureInProgress: () => Effect.void,
|
|
380
385
|
})
|
|
381
386
|
}),
|
|
382
387
|
).pipe(Layer.provide(Linear.layer))
|
package/src/commands/root.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { ChildProcess } from "effect/unstable/process"
|
|
|
24
24
|
import { Worktree } from "../Worktree.ts"
|
|
25
25
|
import { getCommandPrefix, getOrSelectCliAgent } from "./agent.ts"
|
|
26
26
|
import { Flag, CliError, Command } from "effect/unstable/cli"
|
|
27
|
-
import { checkForWork } from "../IssueSource.ts"
|
|
27
|
+
import { checkForWork, IssueSource, resetInProgress } from "../IssueSource.ts"
|
|
28
28
|
import { CurrentIssueSource } from "../IssueSources.ts"
|
|
29
29
|
import { GithubCli } from "../Github/Cli.ts"
|
|
30
30
|
|
|
@@ -124,9 +124,14 @@ export const commandRoot = Command.make("lalph", {
|
|
|
124
124
|
const semaphore = Effect.makeSemaphoreUnsafe(runConcurrency)
|
|
125
125
|
const fibers = yield* FiberSet.make()
|
|
126
126
|
|
|
127
|
+
yield* resetInProgress.pipe(Effect.provide(source))
|
|
128
|
+
|
|
127
129
|
yield* Effect.log(
|
|
128
130
|
`Executing ${iterationsDisplay} iteration(s) with concurrency ${runConcurrency}`,
|
|
129
131
|
)
|
|
132
|
+
if (Option.isSome(targetBranch)) {
|
|
133
|
+
yield* Effect.log(`Using target branch: ${targetBranch.value}`)
|
|
134
|
+
}
|
|
130
135
|
|
|
131
136
|
let iteration = 0
|
|
132
137
|
let quit = false
|
|
@@ -217,6 +222,7 @@ const run = Effect.fnUntraced(
|
|
|
217
222
|
const gh = yield* GithubCli
|
|
218
223
|
const cliAgent = yield* getOrSelectCliAgent
|
|
219
224
|
const prd = yield* Prd
|
|
225
|
+
const source = yield* IssueSource
|
|
220
226
|
|
|
221
227
|
const exec = (
|
|
222
228
|
template: TemplateStringsArray,
|
|
@@ -314,8 +320,23 @@ const run = Effect.fnUntraced(
|
|
|
314
320
|
const taskJson = yield* fs.readFileString(
|
|
315
321
|
pathService.join(worktree.directory, ".lalph", "task.json"),
|
|
316
322
|
)
|
|
317
|
-
const chosenTask = yield* Schema.decodeEffect(ChosenTask)(taskJson)
|
|
323
|
+
const chosenTask = yield* Schema.decodeEffect(ChosenTask)(taskJson).pipe(
|
|
324
|
+
Effect.mapError(() => new ChosenTaskNotFound()),
|
|
325
|
+
Effect.tap(
|
|
326
|
+
Effect.fnUntraced(function* (task) {
|
|
327
|
+
const prdTask = yield* prd.findById(task.id)
|
|
328
|
+
if (prdTask?.state === "in-progress") return
|
|
329
|
+
return yield* new ChosenTaskNotFound()
|
|
330
|
+
}),
|
|
331
|
+
),
|
|
332
|
+
)
|
|
318
333
|
taskId = chosenTask.id
|
|
334
|
+
yield* source.ensureInProgress(taskId).pipe(
|
|
335
|
+
Effect.timeoutOrElse({
|
|
336
|
+
duration: "1 minute",
|
|
337
|
+
onTimeout: () => Effect.fail(new RunnerStalled()),
|
|
338
|
+
}),
|
|
339
|
+
)
|
|
319
340
|
|
|
320
341
|
yield* Deferred.completeWith(options.startedDeferred, Effect.void)
|
|
321
342
|
|
|
@@ -407,6 +428,10 @@ class RunnerStalled extends Data.TaggedError("RunnerStalled") {
|
|
|
407
428
|
readonly message = "The runner has stalled due to inactivity."
|
|
408
429
|
}
|
|
409
430
|
|
|
431
|
+
class ChosenTaskNotFound extends Data.TaggedError("ChosenTaskNotFound") {
|
|
432
|
+
readonly message = "The AI agent failed to choose a task."
|
|
433
|
+
}
|
|
434
|
+
|
|
410
435
|
const ChosenTask = Schema.fromJsonString(
|
|
411
436
|
Schema.Struct({
|
|
412
437
|
id: Schema.String,
|