lalph 0.2.9 → 0.2.11

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
@@ -151008,7 +151008,9 @@ Once you have completed your review, you should:
151008
151008
 
151009
151009
  ${options.gitFlow.reviewInstructions}
151010
151010
 
151011
- # Prevous instructions (only for context, do not repeat)
151011
+ **Everything should be done without interaction or asking for permission.**
151012
+
151013
+ # Previous instructions (only for context, do not repeat)
151012
151014
 
151013
151015
  ${options.prompt}`;
151014
151016
  const promptReviewCustom = (options) => `${options.prompt}
@@ -151166,6 +151168,139 @@ const withWorkerState = (projectId) => (effect) => AtomRegistry.use((registry) =
151166
151168
  }));
151167
151169
  });
151168
151170
 
151171
+ //#endregion
151172
+ //#region src/Projects.ts
151173
+ const layerProjectIdPrompt = effect$1(CurrentProjectId, gen(function* () {
151174
+ return (yield* selectProject).id;
151175
+ })).pipe(provide$3(Settings.layer), provide$3(CurrentIssueSource.layer));
151176
+ const allProjects = new Setting("projects", Array$1(Project$1));
151177
+ const getAllProjects = Settings.get(allProjects).pipe(map$8(getOrElse$1(() => [])));
151178
+ const projectById = fnUntraced(function* (projectId) {
151179
+ const projects = yield* getAllProjects;
151180
+ return findFirst$3(projects, (p) => p.id === projectId);
151181
+ });
151182
+ const allProjectsAtom = (function() {
151183
+ const read = Settings.runtime.atom(fnUntraced(function* () {
151184
+ const projects = yield* (yield* Settings).get(allProjects);
151185
+ return getOrElse$1(projects, () => []);
151186
+ }));
151187
+ const set = Settings.runtime.fn()(fnUntraced(function* (value, get) {
151188
+ yield* (yield* Settings).set(allProjects, some$2(value));
151189
+ get.refresh(read);
151190
+ }));
151191
+ return writable((get) => {
151192
+ get.mount(set);
151193
+ return get(read);
151194
+ }, (ctx, value) => {
151195
+ ctx.set(set, value);
151196
+ }, (r) => r(read));
151197
+ })();
151198
+ const projectAtom = family((projectId) => {
151199
+ const read = make(fnUntraced(function* (get) {
151200
+ const projects = yield* get.result(allProjectsAtom);
151201
+ return findFirst$3(projects, (p) => p.id === projectId);
151202
+ }));
151203
+ const set = Settings.runtime.fn()(fnUntraced(function* (value, get) {
151204
+ const projects = yield* get.result(allProjectsAtom);
151205
+ const updatedProjects = match$7(value, {
151206
+ onNone: () => filter$6(projects, (p) => p.id !== projectId),
151207
+ onSome: (project) => map$12(projects, (p) => p.id === projectId ? project : p)
151208
+ });
151209
+ get.set(allProjectsAtom, updatedProjects);
151210
+ }));
151211
+ return writable((get) => {
151212
+ get.mount(set);
151213
+ return get(read);
151214
+ }, (ctx, value) => {
151215
+ ctx.set(set, value);
151216
+ }, (refresh) => refresh(read));
151217
+ });
151218
+ const selectProject = gen(function* () {
151219
+ const projects = yield* getAllProjects;
151220
+ if (projects.length === 0) return yield* welcomeWizard;
151221
+ else if (projects.length === 1) {
151222
+ const project = projects[0];
151223
+ yield* log$1(`Using project: ${project.id}`);
151224
+ return project;
151225
+ }
151226
+ return yield* autoComplete({
151227
+ message: "Select a project:",
151228
+ choices: projects.map((p) => ({
151229
+ title: p.id,
151230
+ value: p
151231
+ }))
151232
+ });
151233
+ });
151234
+ const welcomeWizard = gen(function* () {
151235
+ const welcome = [
151236
+ " .--.",
151237
+ " |^()^| lalph",
151238
+ " '--'",
151239
+ "",
151240
+ "Welcome! Let's add your first project.",
151241
+ "Projects let you configure how lalph runs tasks.",
151242
+ ""
151243
+ ].join("\n");
151244
+ console.log(welcome);
151245
+ return yield* addOrUpdateProject();
151246
+ });
151247
+ const addOrUpdateProject = fnUntraced(function* (existing) {
151248
+ const projects = yield* getAllProjects;
151249
+ const id = existing ? existing.id : yield* text$2({
151250
+ message: "Project name",
151251
+ validate(input) {
151252
+ input = input.trim();
151253
+ if (input.length === 0) return fail$4("Project name cannot be empty");
151254
+ else if (projects.some((p) => p.id === input)) return fail$4("Project already exists");
151255
+ return succeed$1(input);
151256
+ }
151257
+ });
151258
+ const concurrency = yield* integer$2({
151259
+ message: "Concurrency (number of tasks to run in parallel)",
151260
+ min: 1
151261
+ });
151262
+ const targetBranch = pipe(yield* text$2({ message: "Target branch (leave empty to use HEAD)" }), trim, liftPredicate(isNonEmpty));
151263
+ const gitFlow = yield* select({
151264
+ message: "Git flow",
151265
+ choices: [{
151266
+ title: "Pull Request",
151267
+ description: "Create a pull request for each task",
151268
+ value: "pr"
151269
+ }, {
151270
+ title: "Commit",
151271
+ description: "Tasks are committed directly to the target branch",
151272
+ value: "commit"
151273
+ }]
151274
+ });
151275
+ const reviewAgent = yield* toggle({ message: "Enable review agent?" });
151276
+ const project = new Project$1({
151277
+ id: ProjectId.makeUnsafe(id),
151278
+ enabled: existing ? existing.enabled : true,
151279
+ concurrency,
151280
+ targetBranch,
151281
+ gitFlow,
151282
+ reviewAgent
151283
+ });
151284
+ yield* Settings.set(allProjects, some$2(existing ? projects.map((p) => p.id === project.id ? project : p) : [...projects, project]));
151285
+ const source = yield* IssueSource;
151286
+ yield* source.reset.pipe(provideService(CurrentProjectId, project.id));
151287
+ yield* source.settings(project.id);
151288
+ return project;
151289
+ });
151290
+
151291
+ //#endregion
151292
+ //#region src/shared/git.ts
151293
+ const parseBranch = (ref) => {
151294
+ const parts = ref.split("/");
151295
+ const remote = parts.length > 1 ? parts[0] : "origin";
151296
+ const branch = parts.length > 1 ? parts.slice(1).join("/") : parts[0];
151297
+ return {
151298
+ remote,
151299
+ branch,
151300
+ branchWithRemote: `${remote}/${branch}`
151301
+ };
151302
+ };
151303
+
151169
151304
  //#endregion
151170
151305
  //#region src/Worktree.ts
151171
151306
  var Worktree = class extends Service$1()("lalph/Worktree", { make: gen(function* () {
@@ -151186,17 +151321,16 @@ var Worktree = class extends Service$1()("lalph/Worktree", { make: gen(function*
151186
151321
  }));
151187
151322
  yield* make$23`git worktree add ${directory} -d HEAD`.pipe(exitCode);
151188
151323
  yield* fs.makeDirectory(pathService.join(directory, ".lalph"), { recursive: true });
151189
- const setupPath = pathService.resolve("scripts", "worktree-setup.sh");
151190
- yield* seedSetupScript(setupPath);
151191
- yield* seedCheckoutScript(pathService.resolve("scripts", "checkout-setup.sh"));
151192
- if (yield* fs.exists(setupPath)) yield* make$23({
151193
- cwd: directory,
151194
- shell: process.env.SHELL ?? true
151195
- })`${setupPath}`.pipe(exitCode);
151324
+ const execHelpers = yield* makeExecHelpers({ directory });
151325
+ yield* setupWorktree({
151326
+ directory,
151327
+ exec: execHelpers.exec,
151328
+ pathService
151329
+ });
151196
151330
  return {
151197
151331
  directory,
151198
151332
  inExisting,
151199
- ...yield* makeExecHelpers({ directory })
151333
+ ...execHelpers
151200
151334
  };
151201
151335
  }).pipe(withSpan("Worktree.build")) }) {
151202
151336
  static layer = effect$1(this, this.make);
@@ -151221,11 +151355,31 @@ const seedSetupScript = fnUntraced(function* (setupPath) {
151221
151355
  yield* fs.writeFileString(setupPath, setupScriptTemplate(baseBranch));
151222
151356
  yield* fs.chmod(setupPath, 493);
151223
151357
  });
151224
- const seedCheckoutScript = fnUntraced(function* (setupPath) {
151358
+ const setupWorktree = fnUntraced(function* (options) {
151225
151359
  const fs = yield* FileSystem;
151226
- if (yield* fs.exists(setupPath)) return;
151227
- yield* fs.writeFileString(setupPath, checkoutScriptTemplate);
151228
- yield* fs.chmod(setupPath, 493);
151360
+ const targetBranch = yield* getTargetBranch;
151361
+ const shouldUseWorktree = isSome(targetBranch);
151362
+ if (shouldUseWorktree) {
151363
+ const parsed = parseBranch(targetBranch.value);
151364
+ if ((yield* options.exec`git checkout ${parsed.branchWithRemote}`) !== 0) {
151365
+ yield* options.exec`git checkout -b ${parsed.branch}`;
151366
+ yield* options.exec`git push -u ${parsed.remote} ${parsed.branch}`;
151367
+ }
151368
+ }
151369
+ const setupPath = shouldUseWorktree ? options.pathService.join(options.directory, "scripts", "worktree-setup.sh") : options.pathService.resolve("scripts", "worktree-setup.sh");
151370
+ yield* seedSetupScript(setupPath);
151371
+ if (yield* fs.exists(setupPath)) {
151372
+ const setupCwd = shouldUseWorktree ? options.directory : options.pathService.resolve(".");
151373
+ yield* make$23({
151374
+ cwd: setupCwd,
151375
+ shell: process.env.SHELL ?? true
151376
+ })`${setupPath}`.pipe(exitCode);
151377
+ }
151378
+ });
151379
+ const getTargetBranch = gen(function* () {
151380
+ const project = yield* projectById(yield* CurrentProjectId);
151381
+ if (isNone(project)) return none$4();
151382
+ return project.value.targetBranch;
151229
151383
  });
151230
151384
  const discoverBaseBranch = gen(function* () {
151231
151385
  const originHead = yield* make$23`git symbolic-ref --short refs/remotes/origin/HEAD`.pipe(string, catch_$1((_) => succeed$1("")), map$8((output) => output.trim()));
@@ -151241,13 +151395,6 @@ git checkout origin/${baseBranch}
151241
151395
 
151242
151396
  # Seeded by lalph. Customize this to prepare new worktrees.
151243
151397
  `;
151244
- const checkoutScriptTemplate = `#!/usr/bin/env bash
151245
- set -euo pipefail
151246
-
151247
- pnpm install || true
151248
-
151249
- # Seeded by lalph. Customize this to prepare branches after checkout.
151250
- `;
151251
151398
  const makeExecHelpers = fnUntraced(function* (options) {
151252
151399
  const spawner = yield* ChildProcessSpawner;
151253
151400
  const provide = provideService(ChildProcessSpawner, spawner);
@@ -151557,19 +151704,6 @@ const makeWaitForFile = gen(function* () {
151557
151704
  return (directory, name) => pipe(fs.watch(directory), filter$2((e) => pathService.basename(e.path) === name), runHead);
151558
151705
  });
151559
151706
 
151560
- //#endregion
151561
- //#region src/shared/git.ts
151562
- const parseBranch = (ref) => {
151563
- const parts = ref.split("/");
151564
- const remote = parts.length > 1 ? parts[0] : "origin";
151565
- const branch = parts.length > 1 ? parts.slice(1).join("/") : parts[0];
151566
- return {
151567
- remote,
151568
- branch,
151569
- branchWithRemote: `${remote}/${branch}`
151570
- };
151571
- };
151572
-
151573
151707
  //#endregion
151574
151708
  //#region src/GitFlow.ts
151575
151709
  var GitFlow = class extends Service$1()("lalph/GitFlow") {};
@@ -151584,7 +151718,10 @@ const GitFlowPR = succeed$2(GitFlow, GitFlow.of({
151584
151718
  - **DO NOT** commit any of the files in the \`.lalph\` directory.
151585
151719
  - You have full permission to push branches, create PRs or create git commits.`,
151586
151720
  reviewInstructions: `You are already on the PR branch with their changes.
151587
- After making any changes, commit and push them to the same pull request.`,
151721
+ After making any changes, commit and push them to the same pull request.
151722
+
151723
+ - **DO NOT** commit any of the files in the \`.lalph\` directory.
151724
+ - You have full permission to push branches, create PRs or create git commits.`,
151588
151725
  postWork: () => void_$1,
151589
151726
  autoMerge: fnUntraced(function* (options) {
151590
151727
  const prd = yield* Prd;
@@ -151613,7 +151750,10 @@ const GitFlowCommit = effect$1(GitFlow, gen(function* () {
151613
151750
  commitInstructions: () => `When you have completed your changes, **you must** commit them to the current local branch. Do not git push your changes or switch branches.
151614
151751
  - **DO NOT** commit any of the files in the \`.lalph\` directory.`,
151615
151752
  reviewInstructions: `You are already on the branch with their changes.
151616
- After making any changes, commit them to the same branch. Do not git push your changes or switch branches.`,
151753
+ After making any changes, commit them to the same branch. Do not git push your changes or switch branches.
151754
+
151755
+ - **DO NOT** commit any of the files in the \`.lalph\` directory.
151756
+ - You have full permission to create git commits.`,
151617
151757
  postWork: fnUntraced(function* ({ worktree, targetBranch, issueId }) {
151618
151758
  if (!targetBranch) return yield* logWarning("GitFlowCommit: No target branch specified, skipping postWork.");
151619
151759
  const prd = yield* Prd;
@@ -151722,126 +151862,6 @@ const agentTimeout = fnUntraced(function* (options) {
151722
151862
  }));
151723
151863
  });
151724
151864
 
151725
- //#endregion
151726
- //#region src/Projects.ts
151727
- const layerProjectIdPrompt = effect$1(CurrentProjectId, gen(function* () {
151728
- return (yield* selectProject).id;
151729
- })).pipe(provide$3(Settings.layer), provide$3(CurrentIssueSource.layer));
151730
- const allProjects = new Setting("projects", Array$1(Project$1));
151731
- const getAllProjects = Settings.get(allProjects).pipe(map$8(getOrElse$1(() => [])));
151732
- const projectById = fnUntraced(function* (projectId) {
151733
- const projects = yield* getAllProjects;
151734
- return findFirst$3(projects, (p) => p.id === projectId);
151735
- });
151736
- const allProjectsAtom = (function() {
151737
- const read = Settings.runtime.atom(fnUntraced(function* () {
151738
- const projects = yield* (yield* Settings).get(allProjects);
151739
- return getOrElse$1(projects, () => []);
151740
- }));
151741
- const set = Settings.runtime.fn()(fnUntraced(function* (value, get) {
151742
- yield* (yield* Settings).set(allProjects, some$2(value));
151743
- get.refresh(read);
151744
- }));
151745
- return writable((get) => {
151746
- get.mount(set);
151747
- return get(read);
151748
- }, (ctx, value) => {
151749
- ctx.set(set, value);
151750
- }, (r) => r(read));
151751
- })();
151752
- const projectAtom = family((projectId) => {
151753
- const read = make(fnUntraced(function* (get) {
151754
- const projects = yield* get.result(allProjectsAtom);
151755
- return findFirst$3(projects, (p) => p.id === projectId);
151756
- }));
151757
- const set = Settings.runtime.fn()(fnUntraced(function* (value, get) {
151758
- const projects = yield* get.result(allProjectsAtom);
151759
- const updatedProjects = match$7(value, {
151760
- onNone: () => filter$6(projects, (p) => p.id !== projectId),
151761
- onSome: (project) => map$12(projects, (p) => p.id === projectId ? project : p)
151762
- });
151763
- get.set(allProjectsAtom, updatedProjects);
151764
- }));
151765
- return writable((get) => {
151766
- get.mount(set);
151767
- return get(read);
151768
- }, (ctx, value) => {
151769
- ctx.set(set, value);
151770
- }, (refresh) => refresh(read));
151771
- });
151772
- const selectProject = gen(function* () {
151773
- const projects = yield* getAllProjects;
151774
- if (projects.length === 0) return yield* welcomeWizard;
151775
- else if (projects.length === 1) {
151776
- const project = projects[0];
151777
- yield* log$1(`Using project: ${project.id}`);
151778
- return project;
151779
- }
151780
- return yield* autoComplete({
151781
- message: "Select a project:",
151782
- choices: projects.map((p) => ({
151783
- title: p.id,
151784
- value: p
151785
- }))
151786
- });
151787
- });
151788
- const welcomeWizard = gen(function* () {
151789
- const welcome = [
151790
- " .--.",
151791
- " |^()^| lalph",
151792
- " '--'",
151793
- "",
151794
- "Welcome! Let's add your first project.",
151795
- "Projects let you configure how lalph runs tasks.",
151796
- ""
151797
- ].join("\n");
151798
- console.log(welcome);
151799
- return yield* addOrUpdateProject();
151800
- });
151801
- const addOrUpdateProject = fnUntraced(function* (existing) {
151802
- const projects = yield* getAllProjects;
151803
- const id = existing ? existing.id : yield* text$2({
151804
- message: "Project name",
151805
- validate(input) {
151806
- input = input.trim();
151807
- if (input.length === 0) return fail$4("Project name cannot be empty");
151808
- else if (projects.some((p) => p.id === input)) return fail$4("Project already exists");
151809
- return succeed$1(input);
151810
- }
151811
- });
151812
- const concurrency = yield* integer$2({
151813
- message: "Concurrency (number of tasks to run in parallel)",
151814
- min: 1
151815
- });
151816
- const targetBranch = pipe(yield* text$2({ message: "Target branch (leave empty to use HEAD)" }), trim, liftPredicate(isNonEmpty));
151817
- const gitFlow = yield* select({
151818
- message: "Git flow",
151819
- choices: [{
151820
- title: "Pull Request",
151821
- description: "Create a pull request for each task",
151822
- value: "pr"
151823
- }, {
151824
- title: "Commit",
151825
- description: "Tasks are committed directly to the target branch",
151826
- value: "commit"
151827
- }]
151828
- });
151829
- const reviewAgent = yield* toggle({ message: "Enable review agent?" });
151830
- const project = new Project$1({
151831
- id: ProjectId.makeUnsafe(id),
151832
- enabled: existing ? existing.enabled : true,
151833
- concurrency,
151834
- targetBranch,
151835
- gitFlow,
151836
- reviewAgent
151837
- });
151838
- yield* Settings.set(allProjects, some$2(existing ? projects.map((p) => p.id === project.id ? project : p) : [...projects, project]));
151839
- const source = yield* IssueSource;
151840
- yield* source.reset.pipe(provideService(CurrentProjectId, project.id));
151841
- yield* source.settings(project.id);
151842
- return project;
151843
- });
151844
-
151845
151865
  //#endregion
151846
151866
  //#region src/commands/root.ts
151847
151867
  const run = fnUntraced(function* (options) {
@@ -151856,19 +151876,6 @@ const run = fnUntraced(function* (options) {
151856
151876
  const gitFlow = yield* GitFlow;
151857
151877
  const currentWorker = yield* CurrentWorkerState;
151858
151878
  const registry = yield* AtomRegistry;
151859
- if (isSome(options.targetBranch)) {
151860
- const parsed = parseBranch(options.targetBranch.value);
151861
- if ((yield* worktree.exec`git checkout ${parsed.branchWithRemote}`) !== 0) {
151862
- yield* worktree.exec`git checkout -b ${parsed.branch}`;
151863
- yield* worktree.exec`git push -u ${parsed.remote} ${parsed.branch}`;
151864
- }
151865
- }
151866
- if (gitFlow.branch) {
151867
- yield* worktree.exec`git branch -D ${gitFlow.branch}`;
151868
- yield* worktree.exec`git checkout -b ${gitFlow.branch}`;
151869
- }
151870
- const checkoutScript = pathService.resolve("scripts", "checkout-setup.sh");
151871
- if (yield* fs.exists(checkoutScript)) yield* worktree.exec`${checkoutScript}`;
151872
151879
  yield* addFinalizer(fnUntraced(function* () {
151873
151880
  const currentBranchName = yield* worktree.currentBranch(worktree.directory).pipe(option$1, map$8(getOrUndefined));
151874
151881
  if (!currentBranchName) return;
@@ -151971,7 +151978,7 @@ const runProject = fnUntraced(function* (options) {
151971
151978
  commandPrefix: options.commandPrefix,
151972
151979
  review: options.project.reviewAgent
151973
151980
  }).pipe(provide$1(options.project.gitFlow === "commit" ? GitFlowCommit : GitFlowPR, { local: true }), withWorkerState(options.project.id))), catchTags({
151974
- NoMoreWork(_) {
151981
+ NoMoreWork(_error) {
151975
151982
  if (isFinite) {
151976
151983
  iterations = currentIteration;
151977
151984
  return log$1(`No more work to process, ending after ${currentIteration} iteration(s).`);
@@ -151979,7 +151986,7 @@ const runProject = fnUntraced(function* (options) {
151979
151986
  const log = size$3(fibers) <= 1 ? log$1("No more work to process, waiting 30 seconds...") : void_$1;
151980
151987
  return andThen(log, sleep(seconds(30)));
151981
151988
  },
151982
- QuitError(_) {
151989
+ QuitError(_error) {
151983
151990
  quit = true;
151984
151991
  return void_$1;
151985
151992
  }
@@ -152086,7 +152093,7 @@ const commandPlanTasks = make$35("tasks", { specificationPath }).pipe(withDescri
152086
152093
  Settings.layer,
152087
152094
  PromptGen.layer,
152088
152095
  Prd.layerProvided.pipe(provide$3(layerProjectIdPrompt)),
152089
- Worktree.layer
152096
+ Worktree.layer.pipe(provide$3(layerProjectIdPrompt))
152090
152097
  ]))));
152091
152098
 
152092
152099
  //#endregion
@@ -152112,11 +152119,6 @@ const plan = fnUntraced(function* (options) {
152112
152119
  const pathService = yield* Path$1;
152113
152120
  const worktree = yield* Worktree;
152114
152121
  const cliAgent = yield* getOrSelectCliAgent;
152115
- const exec = (template, ...args) => exitCode(make$23({
152116
- cwd: worktree.directory,
152117
- extendEnv: true
152118
- })(template, ...args));
152119
- if (isSome(options.targetBranch)) yield* exec`git checkout ${options.targetBranch.value.includes("/") ? options.targetBranch.value : `origin/${options.targetBranch.value}`}`;
152120
152122
  yield* agentPlanner({
152121
152123
  specsDirectory: options.specsDirectory,
152122
152124
  commandPrefix: options.commandPrefix,
@@ -152134,7 +152136,7 @@ const plan = fnUntraced(function* (options) {
152134
152136
  }, scoped$1, provide$1([
152135
152137
  PromptGen.layer,
152136
152138
  Prd.layerProvided,
152137
- Worktree.layer,
152139
+ Worktree.layer.pipe(provide$3(layerProjectIdPrompt)),
152138
152140
  Settings.layer,
152139
152141
  CurrentIssueSource.layer
152140
152142
  ]));
@@ -152224,7 +152226,7 @@ const commandSource = make$35("source").pipe(withDescription("Select the issue s
152224
152226
 
152225
152227
  //#endregion
152226
152228
  //#region package.json
152227
- var version = "0.2.9";
152229
+ var version = "0.2.11";
152228
152230
 
152229
152231
  //#endregion
152230
152232
  //#region src/commands/projects/ls.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lalph",
3
3
  "type": "module",
4
- "version": "0.2.9",
4
+ "version": "0.2.11",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
package/src/GitFlow.ts CHANGED
@@ -74,7 +74,10 @@ export const GitFlowPR = Layer.succeed(
74
74
  - You have full permission to push branches, create PRs or create git commits.`,
75
75
 
76
76
  reviewInstructions: `You are already on the PR branch with their changes.
77
- After making any changes, commit and push them to the same pull request.`,
77
+ After making any changes, commit and push them to the same pull request.
78
+
79
+ - **DO NOT** commit any of the files in the \`.lalph\` directory.
80
+ - You have full permission to push branches, create PRs or create git commits.`,
78
81
 
79
82
  postWork: () => Effect.void,
80
83
  autoMerge: Effect.fnUntraced(function* (options) {
@@ -126,7 +129,10 @@ export const GitFlowCommit = Layer.effect(
126
129
  - **DO NOT** commit any of the files in the \`.lalph\` directory.`,
127
130
 
128
131
  reviewInstructions: `You are already on the branch with their changes.
129
- After making any changes, commit them to the same branch. Do not git push your changes or switch branches.`,
132
+ After making any changes, commit them to the same branch. Do not git push your changes or switch branches.
133
+
134
+ - **DO NOT** commit any of the files in the \`.lalph\` directory.
135
+ - You have full permission to create git commits.`,
130
136
 
131
137
  postWork: Effect.fnUntraced(function* ({
132
138
  worktree,
package/src/PromptGen.ts CHANGED
@@ -198,7 +198,9 @@ Once you have completed your review, you should:
198
198
 
199
199
  ${options.gitFlow.reviewInstructions}
200
200
 
201
- # Prevous instructions (only for context, do not repeat)
201
+ **Everything should be done without interaction or asking for permission.**
202
+
203
+ # Previous instructions (only for context, do not repeat)
202
204
 
203
205
  ${options.prompt}`
204
206
 
package/src/Worktree.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  Layer,
10
10
  Option,
11
11
  Path,
12
+ PlatformError,
12
13
  Schema,
13
14
  ServiceMap,
14
15
  Stream,
@@ -18,6 +19,9 @@ import { RunnerStalled } from "./domain/Errors.ts"
18
19
  import type { CliAgent } from "./domain/CliAgent.ts"
19
20
  import { constWorkerMaxOutputChunks, CurrentWorkerState } from "./Workers.ts"
20
21
  import { AtomRegistry } from "effect/unstable/reactivity"
22
+ import { CurrentProjectId } from "./Settings.ts"
23
+ import { projectById } from "./Projects.ts"
24
+ import { parseBranch } from "./shared/git.ts"
21
25
 
22
26
  export class Worktree extends ServiceMap.Service<Worktree>()("lalph/Worktree", {
23
27
  make: Effect.gen(function* () {
@@ -52,22 +56,17 @@ export class Worktree extends ServiceMap.Service<Worktree>()("lalph/Worktree", {
52
56
  recursive: true,
53
57
  })
54
58
 
55
- const setupPath = pathService.resolve("scripts", "worktree-setup.sh")
56
- yield* seedSetupScript(setupPath)
57
- yield* seedCheckoutScript(
58
- pathService.resolve("scripts", "checkout-setup.sh"),
59
- )
60
- if (yield* fs.exists(setupPath)) {
61
- yield* ChildProcess.make({
62
- cwd: directory,
63
- shell: process.env.SHELL ?? true,
64
- })`${setupPath}`.pipe(ChildProcess.exitCode)
65
- }
59
+ const execHelpers = yield* makeExecHelpers({ directory })
60
+ yield* setupWorktree({
61
+ directory,
62
+ exec: execHelpers.exec,
63
+ pathService,
64
+ })
66
65
 
67
66
  return {
68
67
  directory,
69
68
  inExisting,
70
- ...(yield* makeExecHelpers({ directory })),
69
+ ...execHelpers,
71
70
  } as const
72
71
  }).pipe(Effect.withSpan("Worktree.build")),
73
72
  }) {
@@ -107,13 +106,53 @@ const seedSetupScript = Effect.fnUntraced(function* (setupPath: string) {
107
106
  yield* fs.chmod(setupPath, 0o755)
108
107
  })
109
108
 
110
- const seedCheckoutScript = Effect.fnUntraced(function* (setupPath: string) {
109
+ const setupWorktree = Effect.fnUntraced(function* (options: {
110
+ readonly directory: string
111
+ readonly exec: (
112
+ template: TemplateStringsArray,
113
+ ...args: Array<string | number | boolean>
114
+ ) => Effect.Effect<ChildProcessSpawner.ExitCode, PlatformError.PlatformError>
115
+ readonly pathService: Path.Path
116
+ }) {
111
117
  const fs = yield* FileSystem.FileSystem
118
+ const targetBranch = yield* getTargetBranch
119
+ const shouldUseWorktree = Option.isSome(targetBranch)
120
+
121
+ if (shouldUseWorktree) {
122
+ const parsed = parseBranch(targetBranch.value)
123
+ const code = yield* options.exec`git checkout ${parsed.branchWithRemote}`
124
+ if (code !== 0) {
125
+ yield* options.exec`git checkout -b ${parsed.branch}`
126
+ yield* options.exec`git push -u ${parsed.remote} ${parsed.branch}`
127
+ }
128
+ }
112
129
 
113
- if (yield* fs.exists(setupPath)) return
130
+ const setupPath = shouldUseWorktree
131
+ ? options.pathService.join(
132
+ options.directory,
133
+ "scripts",
134
+ "worktree-setup.sh",
135
+ )
136
+ : options.pathService.resolve("scripts", "worktree-setup.sh")
137
+ yield* seedSetupScript(setupPath)
138
+ if (yield* fs.exists(setupPath)) {
139
+ const setupCwd = shouldUseWorktree
140
+ ? options.directory
141
+ : options.pathService.resolve(".")
142
+ yield* ChildProcess.make({
143
+ cwd: setupCwd,
144
+ shell: process.env.SHELL ?? true,
145
+ })`${setupPath}`.pipe(ChildProcess.exitCode)
146
+ }
147
+ })
114
148
 
115
- yield* fs.writeFileString(setupPath, checkoutScriptTemplate)
116
- yield* fs.chmod(setupPath, 0o755)
149
+ const getTargetBranch = Effect.gen(function* () {
150
+ const projectId = yield* CurrentProjectId
151
+ const project = yield* projectById(projectId)
152
+ if (Option.isNone(project)) {
153
+ return Option.none<string>()
154
+ }
155
+ return project.value.targetBranch
117
156
  })
118
157
 
119
158
  const discoverBaseBranch = Effect.gen(function* () {
@@ -148,14 +187,6 @@ git checkout origin/${baseBranch}
148
187
 
149
188
  # Seeded by lalph. Customize this to prepare new worktrees.
150
189
  `
151
- const checkoutScriptTemplate = `#!/usr/bin/env bash
152
- set -euo pipefail
153
-
154
- pnpm install || true
155
-
156
- # Seeded by lalph. Customize this to prepare branches after checkout.
157
- `
158
-
159
190
  const makeExecHelpers = Effect.fnUntraced(function* (options: {
160
191
  readonly directory: string
161
192
  }) {
@@ -54,7 +54,7 @@ export const commandPlanTasks = Command.make("tasks", {
54
54
  Settings.layer,
55
55
  PromptGen.layer,
56
56
  Prd.layerProvided.pipe(Layer.provide(layerProjectIdPrompt)),
57
- Worktree.layer,
57
+ Worktree.layer.pipe(Layer.provide(layerProjectIdPrompt)),
58
58
  ]),
59
59
  ),
60
60
  ),
@@ -1,14 +1,27 @@
1
- import { Data, Effect, FileSystem, Option, Path, pipe, Schema } from "effect"
1
+ import {
2
+ Data,
3
+ Effect,
4
+ FileSystem,
5
+ Layer,
6
+ Option,
7
+ Path,
8
+ pipe,
9
+ Schema,
10
+ } from "effect"
2
11
  import { PromptGen } from "../PromptGen.ts"
3
12
  import { Prd } from "../Prd.ts"
4
- import { ChildProcess } from "effect/unstable/process"
5
13
  import { Worktree } from "../Worktree.ts"
14
+ import type { ChildProcess } from "effect/unstable/process"
6
15
  import { getCommandPrefix, getOrSelectCliAgent } from "./agent.ts"
7
16
  import { Command, Flag } from "effect/unstable/cli"
8
17
  import { CurrentIssueSource } from "../CurrentIssueSource.ts"
9
18
  import { commandRoot } from "./root.ts"
10
19
  import { CurrentProjectId, Settings } from "../Settings.ts"
11
- import { addOrUpdateProject, selectProject } from "../Projects.ts"
20
+ import {
21
+ addOrUpdateProject,
22
+ layerProjectIdPrompt,
23
+ selectProject,
24
+ } from "../Projects.ts"
12
25
  import { agentPlanner } from "../Agents/planner.ts"
13
26
  import { agentTasker } from "../Agents/tasker.ts"
14
27
  import { commandPlanTasks } from "./plan/tasks.ts"
@@ -64,24 +77,6 @@ const plan = Effect.fnUntraced(
64
77
  const worktree = yield* Worktree
65
78
  const cliAgent = yield* getOrSelectCliAgent
66
79
 
67
- const exec = (
68
- template: TemplateStringsArray,
69
- ...args: Array<string | number | boolean>
70
- ) =>
71
- ChildProcess.exitCode(
72
- ChildProcess.make({
73
- cwd: worktree.directory,
74
- extendEnv: true,
75
- })(template, ...args),
76
- )
77
-
78
- if (Option.isSome(options.targetBranch)) {
79
- const targetWithRemote = options.targetBranch.value.includes("/")
80
- ? options.targetBranch.value
81
- : `origin/${options.targetBranch.value}`
82
- yield* exec`git checkout ${targetWithRemote}`
83
- }
84
-
85
80
  yield* agentPlanner({
86
81
  specsDirectory: options.specsDirectory,
87
82
  commandPrefix: options.commandPrefix,
@@ -119,7 +114,7 @@ const plan = Effect.fnUntraced(
119
114
  Effect.provide([
120
115
  PromptGen.layer,
121
116
  Prd.layerProvided,
122
- Worktree.layer,
117
+ Worktree.layer.pipe(Layer.provide(layerProjectIdPrompt)),
123
118
  Settings.layer,
124
119
  CurrentIssueSource.layer,
125
120
  ]),
@@ -36,7 +36,6 @@ import {
36
36
  } from "../Workers.ts"
37
37
  import { WorkerStatus } from "../domain/WorkerState.ts"
38
38
  import { GitFlow, GitFlowCommit, GitFlowPR } from "../GitFlow.ts"
39
- import { parseBranch } from "../shared/git.ts"
40
39
  import { getAllProjects, welcomeWizard } from "../Projects.ts"
41
40
  import type { Project } from "../domain/Project.ts"
42
41
 
@@ -66,24 +65,6 @@ const run = Effect.fnUntraced(
66
65
  const currentWorker = yield* CurrentWorkerState
67
66
  const registry = yield* AtomRegistry.AtomRegistry
68
67
 
69
- if (Option.isSome(options.targetBranch)) {
70
- const parsed = parseBranch(options.targetBranch.value)
71
- const code = yield* worktree.exec`git checkout ${parsed.branchWithRemote}`
72
- if (code !== 0) {
73
- yield* worktree.exec`git checkout -b ${parsed.branch}`
74
- yield* worktree.exec`git push -u ${parsed.remote} ${parsed.branch}`
75
- }
76
- }
77
- if (gitFlow.branch) {
78
- yield* worktree.exec`git branch -D ${gitFlow.branch}`
79
- yield* worktree.exec`git checkout -b ${gitFlow.branch}`
80
- }
81
-
82
- const checkoutScript = pathService.resolve("scripts", "checkout-setup.sh")
83
- if (yield* fs.exists(checkoutScript)) {
84
- yield* worktree.exec`${checkoutScript}`
85
- }
86
-
87
68
  // ensure cleanup of branch after run
88
69
  yield* Effect.addFinalizer(
89
70
  Effect.fnUntraced(function* () {
@@ -284,7 +265,7 @@ const runProject = Effect.fnUntraced(
284
265
  ),
285
266
  ),
286
267
  Effect.catchTags({
287
- NoMoreWork(_) {
268
+ NoMoreWork(_error) {
288
269
  if (isFinite) {
289
270
  // If we have a finite number of iterations, we exit when no more
290
271
  // work is found
@@ -299,7 +280,7 @@ const runProject = Effect.fnUntraced(
299
280
  : Effect.void
300
281
  return Effect.andThen(log, Effect.sleep(Duration.seconds(30)))
301
282
  },
302
- QuitError(_) {
283
+ QuitError(_error) {
303
284
  quit = true
304
285
  return Effect.void
305
286
  },