lalph 0.1.76 → 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 +49 -9
- 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/PromptGen.ts +7 -3
- 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]));
|
|
@@ -141972,7 +141995,7 @@ ${prdNotes()}`;
|
|
|
141972
141995
|
- Add important discoveries about the codebase, or challenges faced to the task's
|
|
141973
141996
|
\`description\`. More details below.
|
|
141974
141997
|
4. Run any checks / feedback loops, such as type checks, unit tests, or linting.
|
|
141975
|
-
5. ${!options.githubPrNumber ? `Create a pull request for this task.${options.targetBranch ? ` The target branch for the PR should be \`${options.targetBranch}\`. If the target branch does not exist, create it first.` : ""}` : "
|
|
141998
|
+
5. ${!options.githubPrNumber ? `Create a pull request for this task.${options.targetBranch ? ` The target branch for the PR should be \`${options.targetBranch}\`. If the target branch does not exist, create it first.` : ""}` : "Commit and push your changes to the pull request."}
|
|
141976
141999
|
${sourceMeta.githubPrInstructions}
|
|
141977
142000
|
The PR description should include a summary of the changes made.
|
|
141978
142001
|
- **DO NOT** commit any of the files in the \`.lalph\` directory.
|
|
@@ -142030,11 +142053,15 @@ ${prdNotes(options)}`;
|
|
|
142030
142053
|
specification to fulfill the request and save it as a file.
|
|
142031
142054
|
First do some research to understand the request, then interview the user
|
|
142032
142055
|
to gather all the necessary requirements and details for the specification.
|
|
142033
|
-
|
|
142034
|
-
|
|
142056
|
+
- If the user asks you to update an existing specification, find the relevant
|
|
142057
|
+
specification file in the \`${options.specsDirectory}\` directory and update it
|
|
142058
|
+
accordingly.
|
|
142059
|
+
2. Once you have saved the specification, your next job is to create or update an
|
|
142060
|
+
implementation plan by breaking down the specification into smaller, manageable tasks and add
|
|
142035
142061
|
them to the prd.yml file.
|
|
142036
142062
|
For each task include in the description where to find the plan specification.
|
|
142037
142063
|
Read the "### Adding tasks" section below **extremely carefully** for guidelines on creating tasks.
|
|
142064
|
+
- **Important**: If updating an existing plan, make sure not to duplicate any existing tasks.
|
|
142038
142065
|
3. Wait until the tasks are saved, then setup task dependencies using the \`blockedBy\` field.
|
|
142039
142066
|
4. Start a subagent with a copy of this prompt, to review the plan and provide feedback or improvements.
|
|
142040
142067
|
5. Present the saved specification for review (include the full text in your response).
|
|
@@ -142361,7 +142388,9 @@ const commandRoot = make$27("lalph", {
|
|
|
142361
142388
|
const runConcurrency = Math.max(1, concurrency$1);
|
|
142362
142389
|
const semaphore = makeSemaphoreUnsafe(runConcurrency);
|
|
142363
142390
|
const fibers = yield* make$24();
|
|
142391
|
+
yield* resetInProgress.pipe(provide$1(source));
|
|
142364
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}`);
|
|
142365
142394
|
let iteration = 0;
|
|
142366
142395
|
let quit = false;
|
|
142367
142396
|
while (true) {
|
|
@@ -142403,6 +142432,7 @@ const run = fnUntraced(function* (options) {
|
|
|
142403
142432
|
const gh = yield* GithubCli;
|
|
142404
142433
|
const cliAgent = yield* getOrSelectCliAgent;
|
|
142405
142434
|
const prd = yield* Prd;
|
|
142435
|
+
const source = yield* IssueSource;
|
|
142406
142436
|
const exec = (template, ...args$1) => make$21({ cwd: worktree.directory })(template, ...args$1).pipe(exitCode);
|
|
142407
142437
|
const execWithStallTimeout = fnUntraced(function* (command) {
|
|
142408
142438
|
let lastOutputAt = yield* now;
|
|
@@ -142439,8 +142469,15 @@ const run = fnUntraced(function* (options) {
|
|
|
142439
142469
|
onTimeout: () => fail$4(new RunnerStalled())
|
|
142440
142470
|
}));
|
|
142441
142471
|
const taskJson = yield* fs.readFileString(pathService.join(worktree.directory, ".lalph", "task.json"));
|
|
142442
|
-
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
|
+
})));
|
|
142443
142476
|
taskId = chosenTask.id;
|
|
142477
|
+
yield* source.ensureInProgress(taskId).pipe(timeoutOrElse({
|
|
142478
|
+
duration: "1 minute",
|
|
142479
|
+
onTimeout: () => fail$4(new RunnerStalled())
|
|
142480
|
+
}));
|
|
142444
142481
|
yield* completeWith(options.startedDeferred, void_$1);
|
|
142445
142482
|
if (chosenTask.githubPrNumber) {
|
|
142446
142483
|
yield* exec`gh pr checkout ${chosenTask.githubPrNumber}`;
|
|
@@ -142485,6 +142522,9 @@ const run = fnUntraced(function* (options) {
|
|
|
142485
142522
|
var RunnerStalled = class extends TaggedError("RunnerStalled") {
|
|
142486
142523
|
message = "The runner has stalled due to inactivity.";
|
|
142487
142524
|
};
|
|
142525
|
+
var ChosenTaskNotFound = class extends TaggedError("ChosenTaskNotFound") {
|
|
142526
|
+
message = "The AI agent failed to choose a task.";
|
|
142527
|
+
};
|
|
142488
142528
|
const ChosenTask = fromJsonString(Struct({
|
|
142489
142529
|
id: String$1,
|
|
142490
142530
|
githubPrNumber: NullOr(Finite)
|
|
@@ -142620,7 +142660,7 @@ const commandSource = make$27("source").pipe(withDescription("Select the issue s
|
|
|
142620
142660
|
|
|
142621
142661
|
//#endregion
|
|
142622
142662
|
//#region package.json
|
|
142623
|
-
var version = "0.1.
|
|
142663
|
+
var version = "0.1.78";
|
|
142624
142664
|
|
|
142625
142665
|
//#endregion
|
|
142626
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/PromptGen.ts
CHANGED
|
@@ -117,7 +117,7 @@ ${prdNotes()}`
|
|
|
117
117
|
- Add important discoveries about the codebase, or challenges faced to the task's
|
|
118
118
|
\`description\`. More details below.
|
|
119
119
|
4. Run any checks / feedback loops, such as type checks, unit tests, or linting.
|
|
120
|
-
5. ${!options.githubPrNumber ? `Create a pull request for this task.${options.targetBranch ? ` The target branch for the PR should be \`${options.targetBranch}\`. If the target branch does not exist, create it first.` : ""}` : "
|
|
120
|
+
5. ${!options.githubPrNumber ? `Create a pull request for this task.${options.targetBranch ? ` The target branch for the PR should be \`${options.targetBranch}\`. If the target branch does not exist, create it first.` : ""}` : "Commit and push your changes to the pull request."}
|
|
121
121
|
${sourceMeta.githubPrInstructions}
|
|
122
122
|
The PR description should include a summary of the changes made.
|
|
123
123
|
- **DO NOT** commit any of the files in the \`.lalph\` directory.
|
|
@@ -182,11 +182,15 @@ ${prdNotes(options)}`
|
|
|
182
182
|
specification to fulfill the request and save it as a file.
|
|
183
183
|
First do some research to understand the request, then interview the user
|
|
184
184
|
to gather all the necessary requirements and details for the specification.
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
- If the user asks you to update an existing specification, find the relevant
|
|
186
|
+
specification file in the \`${options.specsDirectory}\` directory and update it
|
|
187
|
+
accordingly.
|
|
188
|
+
2. Once you have saved the specification, your next job is to create or update an
|
|
189
|
+
implementation plan by breaking down the specification into smaller, manageable tasks and add
|
|
187
190
|
them to the prd.yml file.
|
|
188
191
|
For each task include in the description where to find the plan specification.
|
|
189
192
|
Read the "### Adding tasks" section below **extremely carefully** for guidelines on creating tasks.
|
|
193
|
+
- **Important**: If updating an existing plan, make sure not to duplicate any existing tasks.
|
|
190
194
|
3. Wait until the tasks are saved, then setup task dependencies using the \`blockedBy\` field.
|
|
191
195
|
4. Start a subagent with a copy of this prompt, to review the plan and provide feedback or improvements.
|
|
192
196
|
5. Present the saved specification for review (include the full text in your response).
|
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,
|