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 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 linearState = linear.states.find((s) => s.id === issue.stateId);
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.` : ""}` : "Update the pull request with your progress."}
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
- 2. Once you have saved the specification, your next job is to create an implementation
142034
- plan by breaking down the specification into smaller, manageable tasks and add
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.76";
142663
+ var version = "0.1.78";
142624
142664
 
142625
142665
  //#endregion
142626
142666
  //#region src/cli.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lalph",
3
3
  "type": "module",
4
- "version": "0.1.76",
4
+ "version": "0.1.78",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
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]))
@@ -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.` : ""}` : "Update the pull request with your progress."}
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
- 2. Once you have saved the specification, your next job is to create an implementation
186
- plan by breaking down the specification into smaller, manageable tasks and add
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).
@@ -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,