lalph 0.1.17 → 0.1.18

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
@@ -133880,7 +133880,13 @@ const LinearIssueSource = effect(IssueSource, gen(function* () {
133880
133880
  "started",
133881
133881
  "completed"
133882
133882
  ] } }
133883
- } })).pipe(mapEffect$1(fnUntraced(function* (issue) {
133883
+ } })).pipe(filter((issue) => {
133884
+ const completedAt = issue.completedAt;
133885
+ if (!completedAt) return true;
133886
+ const completed = makeUnsafe$3(completedAt);
133887
+ const threeDaysAgo = nowUnsafe().pipe(subtract({ days: 3 }));
133888
+ return isGreaterThanOrEqualTo$1(completed, threeDaysAgo);
133889
+ }), mapEffect$1(fnUntraced(function* (issue) {
133884
133890
  identifierMap.set(issue.identifier, issue.id);
133885
133891
  const state = linear.states.get(issue.stateId);
133886
133892
  const blockedBy = yield* runCollect(linear.blockedBy(issue));
@@ -133891,7 +133897,7 @@ const LinearIssueSource = effect(IssueSource, gen(function* () {
133891
133897
  priority: issue.priority,
133892
133898
  estimate: issue.estimate ?? null,
133893
133899
  stateId: issue.stateId,
133894
- complete: state.type === "completed",
133900
+ complete: state.type === "completed" || state.name.toLowerCase().includes("review"),
133895
133901
  blockedBy: blockedBy.map((i) => i.identifier),
133896
133902
  githubPrNumber: null
133897
133903
  });
@@ -140185,14 +140191,15 @@ const GithubIssueSource = effect(IssueSource, gen(function* () {
140185
140191
  labels: getOrUndefined(labelFilter$1)
140186
140192
  })).pipe(merge$2(recentlyClosed), filter((issue) => issue.pull_request === void 0), mapEffect$1(fnUntraced(function* (issue) {
140187
140193
  const dependencies = yield* listOpenBlockedBy(issue.number).pipe(runCollect);
140194
+ const stateId = issue.state === "closed" ? "closed" : hasLabel(issue.labels, "in-progress") ? "in-progress" : hasLabel(issue.labels, "in-review") ? "in-review" : "open";
140188
140195
  return new PrdIssue({
140189
140196
  id: `#${issue.number}`,
140190
140197
  title: issue.title,
140191
140198
  description: issue.body ?? "",
140192
140199
  priority: 0,
140193
140200
  estimate: null,
140194
- stateId: issue.state === "closed" ? "closed" : hasLabel(issue.labels, "in-progress") ? "in-progress" : hasLabel(issue.labels, "in-review") ? "in-review" : "open",
140195
- complete: issue.state === "closed",
140201
+ stateId,
140202
+ complete: stateId === "closed" || stateId === "in-review",
140196
140203
  blockedBy: dependencies.map((dep) => `#${dep.number}`),
140197
140204
  githubPrNumber: null
140198
140205
  });
@@ -140390,10 +140397,11 @@ To remove a task, simply delete the item from the prd.yml file.
140390
140397
  \`\`\`json
140391
140398
  ${JSON.stringify(PrdIssue.jsonSchema, null, 2)}
140392
140399
  \`\`\``;
140393
- const prompt = `# Instructions
140400
+ const promptChoose = `# Instructions
140394
140401
 
140395
- The following instructions should be done without interaction or asking for
140396
- permission.
140402
+ Your job is to choose the next task to work on from the prd.yml file. **DO NOT** implement the task yet.
140403
+
140404
+ The following instructions should be done without interaction or asking for permission.
140397
140405
 
140398
140406
  1. Decide which single task to work on next from the prd.yml file. This should
140399
140407
  be the task YOU decide as the most important to work on next, not just the
@@ -140403,7 +140411,39 @@ permission.
140403
140411
  2. **Before doing anything else**, mark the task as "in progress" by updating its
140404
140412
  \`stateId\` in the prd.yml file.
140405
140413
  This prevents other people or agents from working on the same task simultaneously.
140406
- 3. Check if there is an existing Github PR for the task, otherwise create a new
140414
+ 3. Research the task. If it seems like too many steps are needed to complete the task,
140415
+ break it down into smaller tasks and add them to the prd.yml file, marking the
140416
+ original task as "closed" by updating its \`stateId\`.
140417
+ 4. Once you have chosen a task of reasonable size, save its information in a
140418
+ "task.json" file alongside the prd.yml file. Use the following format:
140419
+
140420
+ \`\`\`json
140421
+ {
140422
+ "id": "task id",
140423
+ "todoStateId": "id of the todo state",
140424
+ "inProgressStateId": "id of the in progress state",
140425
+ "reviewStateId": "id of the review state"
140426
+ }
140427
+ \`\`\`
140428
+
140429
+ ## Important: Task sizing
140430
+
140431
+ If at any point you decide that a task is too large or complex to complete in a
140432
+ single iteration, break it down into smaller tasks and add them to the prd.yml
140433
+ file. Then, mark the original task as "closed" by updating its \`stateId\`.
140434
+
140435
+ Each task should be small and specific.
140436
+ Instead of creating tasks like "Refactor the authentication system", create
140437
+ smaller tasks like "Implement OAuth2 login endpoint", "Add JWT token refresh mechanism", etc.
140438
+
140439
+ ${prdNotes}`;
140440
+ const prompt = (taskId) => `# Instructions
140441
+
140442
+ The following instructions should be done without interaction or asking for
140443
+ permission.
140444
+
140445
+ 1. Your job is to complete the task with id \`${taskId}\` from the prd.yml file.
140446
+ 2. Check if there is an existing Github PR for the task, otherwise create a new
140407
140447
  branch for the task.
140408
140448
  - If there is an existing PR, checkout the branch for that PR.
140409
140449
  - If there is an existing PR, check if there are any new comments or requested
@@ -140412,10 +140452,7 @@ permission.
140412
140452
  HEAD as the base.
140413
140453
  - New branches should be named using the format \`{task id}/description\`.
140414
140454
  - When checking for PR reviews, make sure to check the "reviews" field and read ALL unresolved comments.
140415
- 4. Research the task. If it seems like too many steps are needed to complete the task,
140416
- break it down into smaller tasks and add them to the prd.yml file, marking the
140417
- original task as "closed" by updating its \`stateId\`.
140418
- Otherwise, implement the task.
140455
+ 4. Implement the task.
140419
140456
  5. Run any checks / feedback loops, such as type checks, unit tests, or linting.
140420
140457
  6. Create or update the pull request with your progress.
140421
140458
  ${sourceMeta.githubPrInstructions}
@@ -140429,19 +140466,6 @@ permission.
140429
140466
  - If you believe the task is complete, update the \`stateId\` for "review".
140430
140467
  Only if no "review" state exists, use a completed state.
140431
140468
 
140432
- Remember, only work on a single task at a time, that you decide is the most
140433
- important to work on next.
140434
-
140435
- ## Important: Task sizing
140436
-
140437
- If at any point you decide that a task is too large or complex to complete in a
140438
- single iteration, break it down into smaller tasks and add them to the prd.yml
140439
- file. Then, mark the original task as "closed" by updating its \`stateId\`.
140440
-
140441
- Each task should be small and specific.
140442
- Instead of creating tasks like "Refactor the authentication system", create
140443
- smaller tasks like "Implement OAuth2 login endpoint", "Add JWT token refresh mechanism", etc.
140444
-
140445
140469
  ## Handling blockers
140446
140470
 
140447
140471
  If for any reason you get stuck on a task, mark the task back as "todo" by updating its
@@ -140473,6 +140497,7 @@ Users idea / request: ${idea}
140473
140497
 
140474
140498
  ${prdNotes}`;
140475
140499
  return {
140500
+ promptChoose,
140476
140501
  prompt,
140477
140502
  planPrompt,
140478
140503
  planContinuePrompt: `# Instructions
@@ -140561,16 +140586,10 @@ var Prd = class extends Service()("lalph/Prd", { make: gen(function* () {
140561
140586
  stateId: issue.stateId,
140562
140587
  blockedBy: issue.blockedBy
140563
140588
  });
140564
- let entry = updatedIssues.get(issue.id);
140565
- if (!entry) {
140566
- entry = {
140567
- issue,
140568
- originalStateId: existing.stateId,
140569
- count: 0
140570
- };
140571
- updatedIssues.set(issue.id, entry);
140572
- }
140573
- entry.count++;
140589
+ if (!updatedIssues.has(issue.id)) updatedIssues.set(issue.id, {
140590
+ issue,
140591
+ originalStateId: existing.stateId
140592
+ });
140574
140593
  }
140575
140594
  yield* forEach$1(toRemove, (issueId) => source.cancelIssue(issueId), { concurrency: "unbounded" });
140576
140595
  current = yield* source.issues;
@@ -140583,28 +140602,35 @@ var Prd = class extends Service()("lalph/Prd", { make: gen(function* () {
140583
140602
  });
140584
140603
  })));
140585
140604
  }).pipe(uninterruptible);
140586
- const mergableGithubPrs = gen(function* () {
140587
- const yaml = yield* fs.readFileString(prdFile);
140588
- const updated = PrdIssue.arrayFromYaml(yaml);
140589
- const prs = empty$11();
140590
- for (const issue of updated) {
140591
- const entry = updatedIssues.get(issue.id ?? "");
140592
- if (!entry || !issue.githubPrNumber || issue.stateId === entry.originalStateId) continue;
140593
- prs.push(issue.githubPrNumber);
140594
- }
140595
- return prs;
140596
- });
140597
- const revertStateIds = (options) => suspend$2(() => forEach$1(updatedIssues.values(), ({ issue, originalStateId, count }) => options.reason === "error" || count === 1 ? source.updateIssue({
140598
- issueId: issue.id,
140599
- stateId: originalStateId
140600
- }) : void_$1, {
140601
- concurrency: "unbounded",
140602
- discard: true
140603
- }));
140604
140605
  return {
140605
140606
  path: prdFile,
140606
- mergableGithubPrs,
140607
- revertStateIds
140607
+ mergableGithubPrs: gen(function* () {
140608
+ const yaml = yield* fs.readFileString(prdFile);
140609
+ const updated = PrdIssue.arrayFromYaml(yaml);
140610
+ const prs = empty$11();
140611
+ for (const issue of updated) {
140612
+ const entry = updatedIssues.get(issue.id ?? "");
140613
+ if (!entry || !issue.githubPrNumber || issue.stateId === entry.originalStateId) continue;
140614
+ prs.push(issue.githubPrNumber);
140615
+ }
140616
+ return prs;
140617
+ }),
140618
+ revertStateIds: suspend$2(() => forEach$1(updatedIssues.values(), ({ issue, originalStateId }) => source.updateIssue({
140619
+ issueId: issue.id,
140620
+ stateId: originalStateId
140621
+ }), {
140622
+ concurrency: "unbounded",
140623
+ discard: true
140624
+ })),
140625
+ maybeRevertIssue: fnUntraced(function* (options) {
140626
+ const yaml = yield* fs.readFileString(prdFile);
140627
+ const issue = PrdIssue.arrayFromYaml(yaml).find((i) => i.id === options.issueId);
140628
+ if (!issue || issue.stateId === options.reviewStateId) return;
140629
+ yield* source.updateIssue({
140630
+ issueId: issue.id,
140631
+ stateId: options.todoStateId
140632
+ });
140633
+ })
140608
140634
  };
140609
140635
  }) }) {
140610
140636
  static layer = effect(this, this.make).pipe(provide$3(Worktree.layer));
@@ -140632,13 +140658,27 @@ const getOrSelectCliAgent = gen(function* () {
140632
140658
  //#endregion
140633
140659
  //#region src/Runner.ts
140634
140660
  const run = fnUntraced(function* (options) {
140661
+ const fs = yield* FileSystem;
140635
140662
  const pathService = yield* Path$1;
140636
140663
  const worktree = yield* Worktree;
140637
140664
  const promptGen = yield* PromptGen;
140638
140665
  const cliAgent = yield* getOrSelectCliAgent;
140639
140666
  const prd = yield* Prd;
140667
+ const chooseCommand = cliAgent.command({
140668
+ prompt: promptGen.promptChoose,
140669
+ prdFilePath: pathService.join(".lalph", "prd.yml")
140670
+ });
140671
+ yield* make$22(chooseCommand[0], chooseCommand.slice(1), {
140672
+ cwd: worktree.directory,
140673
+ extendEnv: true,
140674
+ stdout: "inherit",
140675
+ stderr: "inherit",
140676
+ stdin: "inherit"
140677
+ }).pipe(exitCode);
140678
+ const taskJson = yield* fs.readFileString(pathService.join(worktree.directory, ".lalph", "task.json"));
140679
+ const task = yield* decodeEffect(ChosenTask)(taskJson);
140640
140680
  const cliCommand = cliAgent.command({
140641
- prompt: promptGen.prompt,
140681
+ prompt: promptGen.prompt(task.id),
140642
140682
  prdFilePath: pathService.join(".lalph", "prd.yml")
140643
140683
  });
140644
140684
  const handle = yield* make$22(cliCommand[0], cliCommand.slice(1), {
@@ -140664,11 +140704,14 @@ const run = fnUntraced(function* (options) {
140664
140704
  const exitCode$1 = yield* handle.exitCode;
140665
140705
  yield* log$1(`Agent exited with code: ${exitCode$1}`);
140666
140706
  const prs = yield* prd.mergableGithubPrs;
140667
- if (prs.length === 0) yield* prd.revertStateIds({ reason: "inactivity" });
140707
+ if (prs.length === 0) yield* prd.maybeRevertIssue({
140708
+ ...task,
140709
+ issueId: task.id
140710
+ });
140668
140711
  else if (options.autoMerge) for (const pr of prs) yield* make$22`gh pr merge ${pr} -sd`.pipe(exitCode);
140669
140712
  }, onError(fnUntraced(function* () {
140670
140713
  const prd = yield* Prd;
140671
- yield* ignore(prd.revertStateIds({ reason: "error" }));
140714
+ yield* ignore(prd.revertStateIds);
140672
140715
  })), scoped$1, provide$1([
140673
140716
  PromptGen.layer,
140674
140717
  Prd.layer,
@@ -140677,6 +140720,12 @@ const run = fnUntraced(function* (options) {
140677
140720
  var RunnerStalled = class extends TaggedError("RunnerStalled") {
140678
140721
  message = "The runner has stalled due to inactivity.";
140679
140722
  };
140723
+ const ChosenTask = fromJsonString(Struct({
140724
+ id: String$1,
140725
+ todoStateId: String$1,
140726
+ inProgressStateId: String$1,
140727
+ reviewStateId: String$1
140728
+ }));
140680
140729
 
140681
140730
  //#endregion
140682
140731
  //#region src/Planner.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lalph",
3
3
  "type": "module",
4
- "version": "0.1.17",
4
+ "version": "0.1.18",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
package/src/Github.ts CHANGED
@@ -185,21 +185,22 @@ export const GithubIssueSource = Layer.effect(
185
185
  const dependencies = yield* listOpenBlockedBy(issue.number).pipe(
186
186
  Stream.runCollect,
187
187
  )
188
+ const stateId =
189
+ issue.state === "closed"
190
+ ? "closed"
191
+ : hasLabel(issue.labels, "in-progress")
192
+ ? "in-progress"
193
+ : hasLabel(issue.labels, "in-review")
194
+ ? "in-review"
195
+ : "open"
188
196
  return new PrdIssue({
189
197
  id: `#${issue.number}`,
190
198
  title: issue.title,
191
199
  description: issue.body ?? "",
192
200
  priority: 0,
193
201
  estimate: null,
194
- stateId:
195
- issue.state === "closed"
196
- ? "closed"
197
- : hasLabel(issue.labels, "in-progress")
198
- ? "in-progress"
199
- : hasLabel(issue.labels, "in-review")
200
- ? "in-review"
201
- : "open",
202
- complete: issue.state === "closed",
202
+ stateId,
203
+ complete: stateId === "closed" || stateId === "in-review",
203
204
  blockedBy: dependencies.map((dep) => `#${dep.number}`),
204
205
  githubPrNumber: null,
205
206
  })
package/src/Linear.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  ServiceMap,
7
7
  Option,
8
8
  RcMap,
9
+ DateTime,
9
10
  } from "effect"
10
11
  import {
11
12
  Connection,
@@ -168,6 +169,15 @@ export const LinearIssueSource = Layer.effect(
168
169
  }),
169
170
  )
170
171
  .pipe(
172
+ Stream.filter((issue) => {
173
+ const completedAt = issue.completedAt
174
+ if (!completedAt) return true
175
+ const completed = DateTime.makeUnsafe(completedAt)
176
+ const threeDaysAgo = DateTime.nowUnsafe().pipe(
177
+ DateTime.subtract({ days: 3 }),
178
+ )
179
+ return DateTime.isGreaterThanOrEqualTo(completed, threeDaysAgo)
180
+ }),
171
181
  Stream.mapEffect(
172
182
  Effect.fnUntraced(function* (issue) {
173
183
  identifierMap.set(issue.identifier, issue.id)
@@ -180,7 +190,9 @@ export const LinearIssueSource = Layer.effect(
180
190
  priority: issue.priority,
181
191
  estimate: issue.estimate ?? null,
182
192
  stateId: issue.stateId!,
183
- complete: state.type === "completed",
193
+ complete:
194
+ state.type === "completed" ||
195
+ state.name.toLowerCase().includes("review"),
184
196
  blockedBy: blockedBy.map((i) => i.identifier),
185
197
  githubPrNumber: null,
186
198
  })
package/src/Prd.ts CHANGED
@@ -30,7 +30,6 @@ export class Prd extends ServiceMap.Service<Prd>()("lalph/Prd", {
30
30
  {
31
31
  readonly issue: PrdIssue
32
32
  readonly originalStateId: string
33
- count: number
34
33
  }
35
34
  >()
36
35
 
@@ -84,12 +83,12 @@ export class Prd extends ServiceMap.Service<Prd>()("lalph/Prd", {
84
83
  blockedBy: issue.blockedBy,
85
84
  })
86
85
 
87
- let entry = updatedIssues.get(issue.id)
88
- if (!entry) {
89
- entry = { issue, originalStateId: existing.stateId, count: 0 }
90
- updatedIssues.set(issue.id, entry)
86
+ if (!updatedIssues.has(issue.id!)) {
87
+ updatedIssues.set(issue.id, {
88
+ issue,
89
+ originalStateId: existing.stateId,
90
+ })
91
91
  }
92
- entry.count++
93
92
  }
94
93
 
95
94
  yield* Effect.forEach(
@@ -129,24 +128,39 @@ export class Prd extends ServiceMap.Service<Prd>()("lalph/Prd", {
129
128
  return prs
130
129
  })
131
130
 
132
- const revertStateIds = (options: {
133
- readonly reason: "inactivity" | "error"
134
- }) =>
135
- Effect.suspend(() =>
136
- Effect.forEach(
137
- updatedIssues.values(),
138
- ({ issue, originalStateId, count }) =>
139
- options.reason === "error" || count === 1
140
- ? source.updateIssue({
141
- issueId: issue.id!,
142
- stateId: originalStateId,
143
- })
144
- : Effect.void,
145
- { concurrency: "unbounded", discard: true },
146
- ),
147
- )
131
+ const revertStateIds = Effect.suspend(() =>
132
+ Effect.forEach(
133
+ updatedIssues.values(),
134
+ ({ issue, originalStateId }) =>
135
+ source.updateIssue({
136
+ issueId: issue.id!,
137
+ stateId: originalStateId,
138
+ }),
139
+ { concurrency: "unbounded", discard: true },
140
+ ),
141
+ )
142
+ const maybeRevertIssue = Effect.fnUntraced(function* (options: {
143
+ readonly issueId: string
144
+ readonly todoStateId: string
145
+ readonly inProgressStateId: string
146
+ readonly reviewStateId: string
147
+ }) {
148
+ const yaml = yield* fs.readFileString(prdFile)
149
+ const updated = PrdIssue.arrayFromYaml(yaml)
150
+ const issue = updated.find((i) => i.id === options.issueId)
151
+ if (!issue || issue.stateId === options.reviewStateId) return
152
+ yield* source.updateIssue({
153
+ issueId: issue.id!,
154
+ stateId: options.todoStateId,
155
+ })
156
+ })
148
157
 
149
- return { path: prdFile, mergableGithubPrs, revertStateIds } as const
158
+ return {
159
+ path: prdFile,
160
+ mergableGithubPrs,
161
+ revertStateIds,
162
+ maybeRevertIssue,
163
+ } as const
150
164
  }),
151
165
  }) {
152
166
  static layer = Layer.effect(this, this.make).pipe(
package/src/PromptGen.ts CHANGED
@@ -38,10 +38,11 @@ To remove a task, simply delete the item from the prd.yml file.
38
38
  ${JSON.stringify(PrdIssue.jsonSchema, null, 2)}
39
39
  \`\`\``
40
40
 
41
- const prompt = `# Instructions
41
+ const promptChoose = `# Instructions
42
42
 
43
- The following instructions should be done without interaction or asking for
44
- permission.
43
+ Your job is to choose the next task to work on from the prd.yml file. **DO NOT** implement the task yet.
44
+
45
+ The following instructions should be done without interaction or asking for permission.
45
46
 
46
47
  1. Decide which single task to work on next from the prd.yml file. This should
47
48
  be the task YOU decide as the most important to work on next, not just the
@@ -51,7 +52,40 @@ permission.
51
52
  2. **Before doing anything else**, mark the task as "in progress" by updating its
52
53
  \`stateId\` in the prd.yml file.
53
54
  This prevents other people or agents from working on the same task simultaneously.
54
- 3. Check if there is an existing Github PR for the task, otherwise create a new
55
+ 3. Research the task. If it seems like too many steps are needed to complete the task,
56
+ break it down into smaller tasks and add them to the prd.yml file, marking the
57
+ original task as "closed" by updating its \`stateId\`.
58
+ 4. Once you have chosen a task of reasonable size, save its information in a
59
+ "task.json" file alongside the prd.yml file. Use the following format:
60
+
61
+ \`\`\`json
62
+ {
63
+ "id": "task id",
64
+ "todoStateId": "id of the todo state",
65
+ "inProgressStateId": "id of the in progress state",
66
+ "reviewStateId": "id of the review state"
67
+ }
68
+ \`\`\`
69
+
70
+ ## Important: Task sizing
71
+
72
+ If at any point you decide that a task is too large or complex to complete in a
73
+ single iteration, break it down into smaller tasks and add them to the prd.yml
74
+ file. Then, mark the original task as "closed" by updating its \`stateId\`.
75
+
76
+ Each task should be small and specific.
77
+ Instead of creating tasks like "Refactor the authentication system", create
78
+ smaller tasks like "Implement OAuth2 login endpoint", "Add JWT token refresh mechanism", etc.
79
+
80
+ ${prdNotes}`
81
+
82
+ const prompt = (taskId: string) => `# Instructions
83
+
84
+ The following instructions should be done without interaction or asking for
85
+ permission.
86
+
87
+ 1. Your job is to complete the task with id \`${taskId}\` from the prd.yml file.
88
+ 2. Check if there is an existing Github PR for the task, otherwise create a new
55
89
  branch for the task.
56
90
  - If there is an existing PR, checkout the branch for that PR.
57
91
  - If there is an existing PR, check if there are any new comments or requested
@@ -60,10 +94,7 @@ permission.
60
94
  HEAD as the base.
61
95
  - New branches should be named using the format \`{task id}/description\`.
62
96
  - When checking for PR reviews, make sure to check the "reviews" field and read ALL unresolved comments.
63
- 4. Research the task. If it seems like too many steps are needed to complete the task,
64
- break it down into smaller tasks and add them to the prd.yml file, marking the
65
- original task as "closed" by updating its \`stateId\`.
66
- Otherwise, implement the task.
97
+ 4. Implement the task.
67
98
  5. Run any checks / feedback loops, such as type checks, unit tests, or linting.
68
99
  6. Create or update the pull request with your progress.
69
100
  ${sourceMeta.githubPrInstructions}
@@ -77,19 +108,6 @@ permission.
77
108
  - If you believe the task is complete, update the \`stateId\` for "review".
78
109
  Only if no "review" state exists, use a completed state.
79
110
 
80
- Remember, only work on a single task at a time, that you decide is the most
81
- important to work on next.
82
-
83
- ## Important: Task sizing
84
-
85
- If at any point you decide that a task is too large or complex to complete in a
86
- single iteration, break it down into smaller tasks and add them to the prd.yml
87
- file. Then, mark the original task as "closed" by updating its \`stateId\`.
88
-
89
- Each task should be small and specific.
90
- Instead of creating tasks like "Refactor the authentication system", create
91
- smaller tasks like "Implement OAuth2 login endpoint", "Add JWT token refresh mechanism", etc.
92
-
93
111
  ## Handling blockers
94
112
 
95
113
  If for any reason you get stuck on a task, mark the task back as "todo" by updating its
@@ -141,7 +159,7 @@ ${prdNotes}`
141
159
 
142
160
  ${prdNotes}`
143
161
 
144
- return { prompt, planPrompt, planContinuePrompt } as const
162
+ return { promptChoose, prompt, planPrompt, planContinuePrompt } as const
145
163
  }),
146
164
  },
147
165
  ) {
package/src/Runner.ts CHANGED
@@ -1,4 +1,13 @@
1
- import { Data, DateTime, Duration, Effect, Path, Stream } from "effect"
1
+ import {
2
+ Data,
3
+ DateTime,
4
+ Duration,
5
+ Effect,
6
+ FileSystem,
7
+ Path,
8
+ Schema,
9
+ Stream,
10
+ } from "effect"
2
11
  import { PromptGen } from "./PromptGen.ts"
3
12
  import { Prd } from "./Prd.ts"
4
13
  import { ChildProcess } from "effect/unstable/process"
@@ -10,14 +19,33 @@ export const run = Effect.fnUntraced(
10
19
  readonly autoMerge: boolean
11
20
  readonly stallTimeout: Duration.Duration
12
21
  }) {
22
+ const fs = yield* FileSystem.FileSystem
13
23
  const pathService = yield* Path.Path
14
24
  const worktree = yield* Worktree
15
25
  const promptGen = yield* PromptGen
16
26
  const cliAgent = yield* getOrSelectCliAgent
17
27
  const prd = yield* Prd
18
28
 
29
+ const chooseCommand = cliAgent.command({
30
+ prompt: promptGen.promptChoose,
31
+ prdFilePath: pathService.join(".lalph", "prd.yml"),
32
+ })
33
+
34
+ yield* ChildProcess.make(chooseCommand[0]!, chooseCommand.slice(1), {
35
+ cwd: worktree.directory,
36
+ extendEnv: true,
37
+ stdout: "inherit",
38
+ stderr: "inherit",
39
+ stdin: "inherit",
40
+ }).pipe(ChildProcess.exitCode)
41
+
42
+ const taskJson = yield* fs.readFileString(
43
+ pathService.join(worktree.directory, ".lalph", "task.json"),
44
+ )
45
+ const task = yield* Schema.decodeEffect(ChosenTask)(taskJson)
46
+
19
47
  const cliCommand = cliAgent.command({
20
- prompt: promptGen.prompt,
48
+ prompt: promptGen.prompt(task.id),
21
49
  prdFilePath: pathService.join(".lalph", "prd.yml"),
22
50
  })
23
51
  const handle = yield* ChildProcess.make(
@@ -62,7 +90,10 @@ export const run = Effect.fnUntraced(
62
90
 
63
91
  const prs = yield* prd.mergableGithubPrs
64
92
  if (prs.length === 0) {
65
- yield* prd.revertStateIds({ reason: "inactivity" })
93
+ yield* prd.maybeRevertIssue({
94
+ ...task,
95
+ issueId: task.id,
96
+ })
66
97
  } else if (options.autoMerge) {
67
98
  for (const pr of prs) {
68
99
  yield* ChildProcess.make`gh pr merge ${pr} -sd`.pipe(
@@ -75,7 +106,7 @@ export const run = Effect.fnUntraced(
75
106
  Effect.onError(
76
107
  Effect.fnUntraced(function* () {
77
108
  const prd = yield* Prd
78
- yield* Effect.ignore(prd.revertStateIds({ reason: "error" }))
109
+ yield* Effect.ignore(prd.revertStateIds)
79
110
  }),
80
111
  ),
81
112
  Effect.scoped,
@@ -85,3 +116,12 @@ export const run = Effect.fnUntraced(
85
116
  export class RunnerStalled extends Data.TaggedError("RunnerStalled") {
86
117
  readonly message = "The runner has stalled due to inactivity."
87
118
  }
119
+
120
+ const ChosenTask = Schema.fromJsonString(
121
+ Schema.Struct({
122
+ id: Schema.String,
123
+ todoStateId: Schema.String,
124
+ inProgressStateId: Schema.String,
125
+ reviewStateId: Schema.String,
126
+ }),
127
+ )