lalph 0.3.85 → 0.3.86

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
@@ -9478,6 +9478,23 @@ const forEachSequential = (iterable, f, options) => suspend$4(() => {
9478
9478
  });
9479
9479
  const filterOrElse$1 = /* @__PURE__ */ dual(3, (self, predicate, orElse) => flatMap$6(self, (a) => predicate(a) ? succeed$7(a) : orElse(a)));
9480
9480
  /** @internal */
9481
+ const filter$6 = /* @__PURE__ */ dual((args) => isIterable(args[0]) && !isEffect$1(args[0]), (elements, predicate, options) => suspend$4(() => {
9482
+ const out = [];
9483
+ return as$2(forEach$5(elements, (a, i) => {
9484
+ const result = predicate(a, i);
9485
+ if (typeof result === "boolean") {
9486
+ if (result) out.push(a);
9487
+ return void_$4;
9488
+ }
9489
+ return map$12(result, (keep) => {
9490
+ if (keep) out.push(a);
9491
+ });
9492
+ }, {
9493
+ discard: true,
9494
+ concurrency: options?.concurrency
9495
+ }), out);
9496
+ }));
9497
+ /** @internal */
9481
9498
  const filterMapEffect$2 = /* @__PURE__ */ dual((args) => isIterable(args[0]) && !isEffect$1(args[0]), (elements, filter, options) => suspend$4(() => {
9482
9499
  const out = [];
9483
9500
  return as$2(forEach$5(elements, (a) => map$12(filter(a), (result) => {
@@ -14539,6 +14556,28 @@ const withFiber = withFiber$1;
14539
14556
  */
14540
14557
  const fromOption = fromOption$1;
14541
14558
  /**
14559
+ * Converts a yieldable value to an Effect.
14560
+ *
14561
+ * @example
14562
+ * ```ts
14563
+ * import { Effect } from "effect"
14564
+ * import * as Option from "effect/Option"
14565
+ *
14566
+ * // Option is yieldable in Effect
14567
+ * const program = Effect.gen(function*() {
14568
+ * const value = yield* Effect.fromYieldable(Option.some(42))
14569
+ * return value * 2
14570
+ * })
14571
+ *
14572
+ * Effect.runPromise(program).then(console.log)
14573
+ * // Output: 84
14574
+ * ```
14575
+ *
14576
+ * @since 4.0.0
14577
+ * @category Conversions
14578
+ */
14579
+ const fromYieldable = fromYieldable$1;
14580
+ /**
14542
14581
  * Chains effects to produce new `Effect` instances, useful for combining
14543
14582
  * operations that depend on previous results.
14544
14583
  *
@@ -14996,6 +15035,53 @@ const asSome = asSome$1;
14996
15035
  * @category Mapping
14997
15036
  */
14998
15037
  const asVoid = asVoid$2;
15038
+ /**
15039
+ * Combines two effects sequentially and applies a function to their results to
15040
+ * produce a single value.
15041
+ *
15042
+ * **When to Use**
15043
+ *
15044
+ * The `zipWith` function is similar to {@link zip}, but instead of returning a
15045
+ * tuple of results, it applies a provided function to the results of the two
15046
+ * effects, combining them into a single value.
15047
+ *
15048
+ * **Concurrency**
15049
+ *
15050
+ * By default, the effects are run sequentially. To execute them concurrently,
15051
+ * use the `{ concurrent: true }` option.
15052
+ *
15053
+ * @example
15054
+ * ```ts
15055
+ * // Title: Combining Effects with a Custom Function
15056
+ * import { Effect } from "effect"
15057
+ *
15058
+ * const task1 = Effect.succeed(1).pipe(
15059
+ * Effect.delay("200 millis"),
15060
+ * Effect.tap(Effect.log("task1 done"))
15061
+ * )
15062
+ * const task2 = Effect.succeed("hello").pipe(
15063
+ * Effect.delay("100 millis"),
15064
+ * Effect.tap(Effect.log("task2 done"))
15065
+ * )
15066
+ *
15067
+ * const task3 = Effect.zipWith(
15068
+ * task1,
15069
+ * task2,
15070
+ * // Combines results into a single value
15071
+ * (number, string) => number + string.length
15072
+ * )
15073
+ *
15074
+ * Effect.runPromise(task3).then(console.log)
15075
+ * // Output:
15076
+ * // timestamp=... level=INFO fiber=#3 message="task1 done"
15077
+ * // timestamp=... level=INFO fiber=#2 message="task2 done"
15078
+ * // 6
15079
+ * ```
15080
+ *
15081
+ * @since 2.0.0
15082
+ * @category Zipping
15083
+ */
15084
+ const zipWith = zipWith$1;
14999
15085
  const catch_$2 = catch_$3;
15000
15086
  /**
15001
15087
  * Catches and handles specific errors by their `_tag` field, which is used as a
@@ -15766,6 +15852,27 @@ const sleep = sleep$1;
15766
15852
  */
15767
15853
  const raceFirst = raceFirst$1;
15768
15854
  /**
15855
+ * Filters elements of an iterable using a predicate, refinement, or effectful
15856
+ * predicate.
15857
+ *
15858
+ * @example
15859
+ * ```ts
15860
+ * import { Effect } from "effect"
15861
+ *
15862
+ * // Sync predicate
15863
+ * const evens = Effect.filter([1, 2, 3, 4], (n) => n % 2 === 0)
15864
+ *
15865
+ * // Effectful predicate
15866
+ * const checked = Effect.filter([1, 2, 3], (n) => Effect.succeed(n > 1))
15867
+ *
15868
+ * // Use Effect.filterMapEffect for effectful Filter.Filter callbacks
15869
+ * ```
15870
+ *
15871
+ * @since 2.0.0
15872
+ * @category Filtering
15873
+ */
15874
+ const filter$5 = filter$6;
15875
+ /**
15769
15876
  * Effectfully filters and maps elements of an iterable with a `FilterEffect`.
15770
15877
  *
15771
15878
  * @since 4.0.0
@@ -49589,6 +49696,33 @@ const custom$1 = (initialState, handlers) => {
49589
49696
  * @since 4.0.0
49590
49697
  * @category constructors
49591
49698
  */
49699
+ const file$3 = (options = {}) => {
49700
+ const opts = {
49701
+ type: options.type ?? "file",
49702
+ message: options.message ?? `Choose a file`,
49703
+ startingPath: fromUndefinedOr(options.startingPath),
49704
+ maxPerPage: options.maxPerPage ?? 10,
49705
+ filter: options.filter ?? (() => succeed$3(true))
49706
+ };
49707
+ return custom$1(gen(function* () {
49708
+ const files = yield* getFileList(yield* resolveCurrentPath(none$4(), opts), opts);
49709
+ const confirm = Confirm.Hide();
49710
+ return {
49711
+ cursor: 0,
49712
+ files,
49713
+ path: none$4(),
49714
+ confirm
49715
+ };
49716
+ }), {
49717
+ render: handleFileRender(opts),
49718
+ process: handleFileProcess(opts),
49719
+ clear: handleFileClear(opts)
49720
+ });
49721
+ };
49722
+ /**
49723
+ * @since 4.0.0
49724
+ * @category constructors
49725
+ */
49592
49726
  const integer$3 = (options) => {
49593
49727
  const opts = {
49594
49728
  min: Number.NEGATIVE_INFINITY,
@@ -49793,6 +49927,41 @@ const clearOutputWithError = (outputText, columns, errorText) => {
49793
49927
  };
49794
49928
  const renderBeep = "\x07";
49795
49929
  const NEWLINE_REGEXP = /\r?\n/;
49930
+ const CONFIRM_MESSAGE = "The selected directory contains files. Would you like to traverse the selected directory?";
49931
+ const Confirm = /* @__PURE__ */ taggedEnum();
49932
+ const showConfirmation = /* @__PURE__ */ Confirm.$is("Show");
49933
+ const resolveCurrentPath = (path, options) => {
49934
+ if (isSome(path)) return succeed$3(path.value);
49935
+ if (isSome(options.startingPath)) {
49936
+ const startingPath = options.startingPath.value;
49937
+ return flatMap$4(FileSystem.asEffect(), (fs) => orDie$2(fs.exists(startingPath)).pipe(flatMap$4((exists) => exists ? void_$2 : die$2(`The provided starting path '${startingPath}' does not exist`)), as$1(startingPath)));
49938
+ }
49939
+ return sync(() => process.cwd());
49940
+ };
49941
+ const getFileList = /* @__PURE__ */ fnUntraced(function* (directory, options) {
49942
+ const fs = yield* FileSystem;
49943
+ const path = yield* Path$1;
49944
+ const files = yield* orDie$2(fs.readDirectory(directory)).pipe(map$9((files) => ["..", ...files]));
49945
+ return yield* filter$5(files, (file) => {
49946
+ const result = options.filter(file);
49947
+ return zipWith(isEffect(result) ? result : succeed$3(result), options.type === "directory" ? map$9(orDie$2(fs.stat(path.join(directory, file))), (info) => info.type === "Directory") : succeed$3(true), (a, b) => a && b);
49948
+ }, { concurrency: files.length });
49949
+ });
49950
+ const handleFileClear = (options) => {
49951
+ return fnUntraced(function* (state, _) {
49952
+ const columns = yield* (yield* Terminal).columns;
49953
+ const path = yield* Path$1;
49954
+ const figures = yield* platformFigures;
49955
+ const currentPath = yield* resolveCurrentPath(state.path, options);
49956
+ const selectedPath = state.files[state.cursor];
49957
+ const resolvedPath = path.resolve(currentPath, selectedPath);
49958
+ const resolvedPathText = `${figures.pointerSmall} ${resolvedPath}`;
49959
+ const isConfirming = showConfirmation(state.confirm);
49960
+ const promptText = isConfirming ? renderPrompt("(Y/n)", CONFIRM_MESSAGE, "?", figures.pointerSmall, { plain: true }) : renderPrompt("", options.message, figures.tick, figures.ellipsis, { plain: true });
49961
+ const filesText = isConfirming ? "" : renderFiles(state, state.files, figures, options, { plain: true });
49962
+ return eraseText(isConfirming ? `${promptText}\n${resolvedPathText}` : `${promptText}\n${resolvedPathText}\n${filesText}`, columns) + (eraseLine + cursorLeft);
49963
+ });
49964
+ };
49796
49965
  const renderPrompt = (confirm, message, leadingSymbol, trailingSymbol, options) => {
49797
49966
  const prefix = leadingSymbol + " ";
49798
49967
  const annotate = options?.plain === true ? (line) => line : annotateLine;
@@ -49803,6 +49972,137 @@ const renderPrompt = (confirm, message, leadingSymbol, trailingSymbol, options)
49803
49972
  }
49804
49973
  });
49805
49974
  };
49975
+ const renderPrefix = (state, toDisplay, currentIndex, length, figures, renderOptions) => {
49976
+ let prefix = " ";
49977
+ if (currentIndex === toDisplay.startIndex && toDisplay.startIndex > 0) prefix = figures.arrowUp;
49978
+ else if (currentIndex === toDisplay.endIndex - 1 && toDisplay.endIndex < length) prefix = figures.arrowDown;
49979
+ if (state.cursor === currentIndex) return renderOptions?.plain === true ? figures.pointer + prefix : annotate(figures.pointer, cyanBright) + prefix;
49980
+ return prefix + " ";
49981
+ };
49982
+ const renderFileName = (file, isSelected, renderOptions) => {
49983
+ if (renderOptions?.plain === true) return file;
49984
+ return isSelected ? annotate(file, combine(underlined, cyanBright)) : file;
49985
+ };
49986
+ const renderFiles = (state, files, figures, options, renderOptions) => {
49987
+ const length = files.length;
49988
+ const toDisplay = entriesToDisplay(state.cursor, length, options.maxPerPage);
49989
+ const documents = [];
49990
+ for (let index = toDisplay.startIndex; index < toDisplay.endIndex; index++) {
49991
+ const isSelected = state.cursor === index;
49992
+ const prefix = renderPrefix(state, toDisplay, index, length, figures, renderOptions);
49993
+ const fileName = renderFileName(files[index], isSelected, renderOptions);
49994
+ documents.push(prefix + fileName);
49995
+ }
49996
+ return documents.join("\n");
49997
+ };
49998
+ const renderFileNextFrame = /* @__PURE__ */ fnUntraced(function* (state, options) {
49999
+ const path = yield* Path$1;
50000
+ const figures = yield* platformFigures;
50001
+ const currentPath = yield* resolveCurrentPath(state.path, options);
50002
+ const selectedPath = state.files[state.cursor];
50003
+ const resolvedPath = path.resolve(currentPath, selectedPath);
50004
+ const resolvedPathMsg = annotate(figures.pointerSmall + " " + resolvedPath, blackBright);
50005
+ if (showConfirmation(state.confirm)) {
50006
+ const leadingSymbol = annotate("?", cyanBright);
50007
+ const trailingSymbol = annotate(figures.pointerSmall, blackBright);
50008
+ return cursorHide + renderPrompt(annotate("(Y/n)", blackBright), CONFIRM_MESSAGE, leadingSymbol, trailingSymbol) + "\n" + resolvedPathMsg;
50009
+ }
50010
+ const leadingSymbol = annotate(figures.tick, green$1);
50011
+ const trailingSymbol = annotate(figures.ellipsis, blackBright);
50012
+ const promptMsg = renderPrompt("", options.message, leadingSymbol, trailingSymbol);
50013
+ const files = renderFiles(state, state.files, figures, options);
50014
+ return cursorHide + promptMsg + "\n" + resolvedPathMsg + "\n" + files;
50015
+ });
50016
+ const renderFileSubmission = /* @__PURE__ */ fnUntraced(function* (value, options) {
50017
+ const figures = yield* platformFigures;
50018
+ const leadingSymbol = annotate(figures.tick, green$1);
50019
+ const trailingSymbol = annotate(figures.ellipsis, blackBright);
50020
+ return renderPrompt("", options.message, leadingSymbol, trailingSymbol) + " " + annotate(value, white) + "\n";
50021
+ });
50022
+ const handleFileRender = (options) => {
50023
+ return (_, action) => {
50024
+ return Action.$match(action, {
50025
+ Beep: () => succeed$3(renderBeep),
50026
+ NextFrame: ({ state }) => renderFileNextFrame(state, options),
50027
+ Submit: ({ value }) => renderFileSubmission(value, options)
50028
+ });
50029
+ };
50030
+ };
50031
+ const processFileCursorUp = (state) => {
50032
+ const cursor = state.cursor - 1;
50033
+ return succeed$3(Action.NextFrame({ state: {
50034
+ ...state,
50035
+ cursor: cursor < 0 ? state.files.length - 1 : cursor
50036
+ } }));
50037
+ };
50038
+ const processFileCursorDown = (state) => {
50039
+ return succeed$3(Action.NextFrame({ state: {
50040
+ ...state,
50041
+ cursor: (state.cursor + 1) % state.files.length
50042
+ } }));
50043
+ };
50044
+ const processSelection = /* @__PURE__ */ fnUntraced(function* (state, options) {
50045
+ const fs = yield* FileSystem;
50046
+ const path = yield* Path$1;
50047
+ const currentPath = yield* resolveCurrentPath(state.path, options);
50048
+ const selectedPath = state.files[state.cursor];
50049
+ const resolvedPath = path.resolve(currentPath, selectedPath);
50050
+ if ((yield* orDie$2(fs.stat(resolvedPath))).type === "Directory") {
50051
+ const files = yield* getFileList(resolvedPath, options);
50052
+ const filesWithoutParent = files.filter((file) => file !== "..");
50053
+ if (options.type === "directory" || options.type === "either") return filesWithoutParent.length === 0 ? Action.Submit({ value: resolvedPath }) : Action.NextFrame({ state: {
50054
+ ...state,
50055
+ confirm: Confirm.Show()
50056
+ } });
50057
+ return Action.NextFrame({ state: {
50058
+ cursor: 0,
50059
+ files,
50060
+ path: some$2(resolvedPath),
50061
+ confirm: Confirm.Hide()
50062
+ } });
50063
+ }
50064
+ return Action.Submit({ value: resolvedPath });
50065
+ });
50066
+ const handleFileProcess = (options) => {
50067
+ return fnUntraced(function* (input, state) {
50068
+ switch (input.key.name) {
50069
+ case "k":
50070
+ case "up": return yield* processFileCursorUp(state);
50071
+ case "j":
50072
+ case "down":
50073
+ case "tab": return yield* processFileCursorDown(state);
50074
+ case "enter":
50075
+ case "return": return yield* processSelection(state, options);
50076
+ case "y":
50077
+ case "t":
50078
+ if (showConfirmation(state.confirm)) {
50079
+ const path = yield* Path$1;
50080
+ const currentPath = yield* resolveCurrentPath(state.path, options);
50081
+ const selectedPath = state.files[state.cursor];
50082
+ const resolvedPath = path.resolve(currentPath, selectedPath);
50083
+ const files = yield* getFileList(resolvedPath, options);
50084
+ return Action.NextFrame({ state: {
50085
+ cursor: 0,
50086
+ files,
50087
+ path: some$2(resolvedPath),
50088
+ confirm: Confirm.Hide()
50089
+ } });
50090
+ }
50091
+ return Action.Beep();
50092
+ case "n":
50093
+ case "f":
50094
+ if (showConfirmation(state.confirm)) {
50095
+ const path = yield* Path$1;
50096
+ const currentPath = yield* resolveCurrentPath(state.path, options);
50097
+ const selectedPath = state.files[state.cursor];
50098
+ const resolvedPath = path.resolve(currentPath, selectedPath);
50099
+ return Action.Submit({ value: resolvedPath });
50100
+ }
50101
+ return Action.Beep();
50102
+ default: return Action.Beep();
50103
+ }
50104
+ });
50105
+ };
49806
50106
  const renderMultiSelectError = (state, pointer, renderOptions) => {
49807
50107
  if (isSome(state.error)) return match$7(state.error.value.split(NEWLINE_REGEXP), {
49808
50108
  onEmpty: () => "",
@@ -90884,15 +91184,27 @@ const PlatformServices = layer$16;
90884
91184
  //#endregion
90885
91185
  //#region src/domain/Project.ts
90886
91186
  const ProjectId = String$1.pipe(brand("lalph/ProjectId"));
90887
- var Project$2 = class extends Class$2("lalph/Project")({
91187
+ var Project$2 = class Project$2 extends Class$2("lalph/Project")({
90888
91188
  id: ProjectId,
90889
91189
  enabled: Boolean$2,
90890
91190
  targetBranch: Option(String$1),
90891
91191
  concurrency: Int.check(isGreaterThanOrEqualTo(1)),
90892
- gitFlow: Literals(["pr", "commit"]),
91192
+ gitFlow: Literals([
91193
+ "pr",
91194
+ "commit",
91195
+ "ralph"
91196
+ ]),
91197
+ ralphSpec: optional$3(String$1),
90893
91198
  researchAgent: Boolean$2.pipe(withDecodingDefault(() => false)),
90894
91199
  reviewAgent: Boolean$2
90895
- }) {};
91200
+ }) {
91201
+ update(updates) {
91202
+ return new Project$2({
91203
+ ...this,
91204
+ ...updates
91205
+ });
91206
+ }
91207
+ };
90896
91208
  //#endregion
90897
91209
  //#region src/shared/lalphDirectory.ts
90898
91210
  const findProjectRoot = fnUntraced(function* (cwd) {
@@ -91131,8 +91443,7 @@ ${prompt}`], {
91131
91443
  prompt,
91132
91444
  "--thinking",
91133
91445
  ...extraArgs,
91134
- "-f",
91135
- prdFilePath
91446
+ ...prdFilePath ? ["-f", prdFilePath] : []
91136
91447
  ], {
91137
91448
  extendEnv: true,
91138
91449
  env: { OPENCODE_PERMISSION: "{\"*\":\"allow\", \"question\":\"deny\"}" },
@@ -91140,9 +91451,9 @@ ${prompt}`], {
91140
91451
  stderr: "pipe",
91141
91452
  stdin: "inherit"
91142
91453
  }),
91143
- commandPlan: ({ prompt, prdFilePath, dangerous }) => make$45("opencode", ["--prompt", `@${prdFilePath}
91454
+ commandPlan: ({ prompt, prdFilePath, dangerous }) => make$45("opencode", ["--prompt", prdFilePath ? `@${prdFilePath}
91144
91455
 
91145
- ${prompt}`], {
91456
+ ${prompt}` : prompt], {
91146
91457
  extendEnv: true,
91147
91458
  ...dangerous ? { env: { OPENCODE_PERMISSION: "{\"*\":\"allow\"}" } } : {},
91148
91459
  stdout: "inherit",
@@ -91162,9 +91473,9 @@ ${prompt}`], {
91162
91473
  "AskUserQuestion",
91163
91474
  ...extraArgs,
91164
91475
  "--",
91165
- `@${prdFilePath}
91476
+ prdFilePath ? `@${prdFilePath}
91166
91477
 
91167
- ${prompt}`
91478
+ ${prompt}` : prompt
91168
91479
  ], {
91169
91480
  stdout: "pipe",
91170
91481
  stderr: "pipe",
@@ -91186,17 +91497,17 @@ ${prompt}`], {
91186
91497
  "exec",
91187
91498
  "--dangerously-bypass-approvals-and-sandbox",
91188
91499
  ...extraArgs,
91189
- `@${prdFilePath}
91500
+ prdFilePath ? `@${prdFilePath}
91190
91501
 
91191
- ${prompt}`
91502
+ ${prompt}` : prompt
91192
91503
  ], {
91193
91504
  stdout: "pipe",
91194
91505
  stderr: "pipe",
91195
91506
  stdin: "inherit"
91196
91507
  }),
91197
- commandPlan: ({ prompt, prdFilePath, dangerous }) => make$45("codex", [...dangerous ? ["--dangerously-bypass-approvals-and-sandbox"] : [], `@${prdFilePath}
91508
+ commandPlan: ({ prompt, prdFilePath, dangerous }) => make$45("codex", [...dangerous ? ["--dangerously-bypass-approvals-and-sandbox"] : [], prdFilePath ? `@${prdFilePath}
91198
91509
 
91199
- ${prompt}`], {
91510
+ ${prompt}` : prompt], {
91200
91511
  stdout: "inherit",
91201
91512
  stderr: "inherit",
91202
91513
  stdin: "inherit"
@@ -91209,9 +91520,9 @@ ${prompt}`], {
91209
91520
  "--dangerously-allow-all",
91210
91521
  "--stream-json-thinking",
91211
91522
  ...extraArgs,
91212
- `@${prdFilePath}
91523
+ prdFilePath ? `@${prdFilePath}
91213
91524
 
91214
- ${prompt}`
91525
+ ${prompt}` : prompt
91215
91526
  ], {
91216
91527
  stdout: "pipe",
91217
91528
  stderr: "pipe",
@@ -92548,6 +92859,9 @@ var Settings = class Settings extends Service$1()("lalph/Settings", { make: gen(
92548
92859
  static set(setting, value) {
92549
92860
  return Settings.use((_) => _.set(setting, value));
92550
92861
  }
92862
+ static update(setting, f) {
92863
+ return Settings.use((_) => _.get(setting).pipe(map$9(f), flatMap$4((v) => _.set(setting, v))));
92864
+ }
92551
92865
  static getProject(setting) {
92552
92866
  return Settings.use((_) => _.getProject(setting));
92553
92867
  }
@@ -92575,6 +92889,7 @@ var ProjectSetting = class {
92575
92889
  }
92576
92890
  };
92577
92891
  new Setting("selectedCliAgentId", Literals(allCliAgents.map((a) => a.id)));
92892
+ const allProjects = new Setting("projects", Array$1(Project$2));
92578
92893
  //#endregion
92579
92894
  //#region src/shared/schema.ts
92580
92895
  const withEncodeDefault = (defaultValue) => (schema) => optionalKey(schema).pipe(decodeTo(toType(schema), {
@@ -239422,23 +239737,28 @@ const getOrSelectIssueSource = gen(function* () {
239422
239737
  });
239423
239738
  var CurrentIssueSource = class CurrentIssueSource extends Service$1()("lalph/CurrentIssueSource") {
239424
239739
  static layer = effectServices(gen(function* () {
239740
+ const settings = yield* Settings;
239425
239741
  const source = yield* getOrSelectIssueSource;
239426
239742
  const build$1 = build(source.layer).pipe(map$9(get$15(IssueSource)), withSpan$1("CurrentIssueSource.build"));
239427
239743
  const ref = yield* fromAcquire(build$1);
239428
239744
  const services$2 = yield* services();
239429
239745
  const refresh = set$4(ref, build$1).pipe(provideServices$2(services$2));
239746
+ const unlessRalph = (projectId, orElse) => (effect) => settings.get(allProjects).pipe(map$9(filter$10((projects) => projects.some((p) => p.id === projectId && p.gitFlow === "ralph"))), flatMap$4(match$10({
239747
+ onNone: () => effect,
239748
+ onSome: () => orElse
239749
+ })));
239430
239750
  const proxy = IssueSource.of({
239431
- issues: (projectId) => get$6(ref).pipe(flatMap$4((source) => source.issues(projectId)), tapErrorTag("IssueSourceError", (e) => logWarning("Rebuilding issue source due to error", fail$7(e)).pipe(andThen(ignore$1(refresh)))), retry$3(refreshSchedule)),
239432
- createIssue: (projectId, options) => get$6(ref).pipe(flatMap$4((source) => source.createIssue(projectId, options))),
239433
- updateIssue: (options) => get$6(ref).pipe(flatMap$4((source) => source.updateIssue(options))),
239434
- cancelIssue: (projectId, issueId) => get$6(ref).pipe(flatMap$4((source) => source.cancelIssue(projectId, issueId))),
239751
+ issues: (projectId) => get$6(ref).pipe(flatMap$4((source) => source.issues(projectId)), tapErrorTag("IssueSourceError", (e) => logWarning("Rebuilding issue source due to error", fail$7(e)).pipe(andThen(ignore$1(refresh)))), retry$3(refreshSchedule), unlessRalph(projectId, succeed$3([]))),
239752
+ createIssue: (projectId, options) => get$6(ref).pipe(flatMap$4((source) => source.createIssue(projectId, options)), unlessRalph(projectId, interrupt$1)),
239753
+ updateIssue: (options) => get$6(ref).pipe(flatMap$4((source) => source.updateIssue(options)), unlessRalph(options.projectId, void_$2)),
239754
+ cancelIssue: (projectId, issueId) => get$6(ref).pipe(flatMap$4((source) => source.cancelIssue(projectId, issueId)), unlessRalph(projectId, void_$2)),
239435
239755
  reset: get$6(ref).pipe(flatMap$4((source) => source.reset)),
239436
- settings: (projectId) => get$6(ref).pipe(flatMap$4((source) => source.settings(projectId))),
239437
- info: (projectId) => get$6(ref).pipe(flatMap$4((source) => source.info(projectId))),
239756
+ settings: (projectId) => get$6(ref).pipe(flatMap$4((source) => source.settings(projectId)), unlessRalph(projectId, void_$2)),
239757
+ info: (projectId) => get$6(ref).pipe(flatMap$4((source) => source.info(projectId)), unlessRalph(projectId, void_$2)),
239438
239758
  issueCliAgentPreset: (issue) => get$6(ref).pipe(flatMap$4((source) => source.issueCliAgentPreset(issue))),
239439
239759
  updateCliAgentPreset: (preset) => get$6(ref).pipe(flatMap$4((source) => source.updateCliAgentPreset(preset))),
239440
239760
  cliAgentPresetInfo: (preset) => get$6(ref).pipe(flatMap$4((source) => source.cliAgentPresetInfo(preset))),
239441
- ensureInProgress: (projectId, issueId) => get$6(ref).pipe(flatMap$4((source) => source.ensureInProgress(projectId, issueId)))
239761
+ ensureInProgress: (projectId, issueId) => get$6(ref).pipe(flatMap$4((source) => source.ensureInProgress(projectId, issueId)), unlessRalph(projectId, void_$2))
239442
239762
  });
239443
239763
  return IssueSource.serviceMap(proxy).pipe(add$3(CurrentIssueSource, source));
239444
239764
  })).pipe(provide$3([Settings.layer, PlatformServices]));
@@ -239447,8 +239767,9 @@ const refreshSchedule = exponential(100, 1.5).pipe(either(spaced("30 seconds")))
239447
239767
  const issueSourceRuntime = atomRuntime(CurrentIssueSource.layer.pipe(orDie$3));
239448
239768
  const currentIssuesAtom = family((projectId) => pipe$1(issueSourceRuntime.atom(IssueSource.use((s) => s.issues(projectId)).pipe(withSpan$1("currentIssuesAtom"))), atomRuntime.withReactivity([`issues:${projectId}`]), withRefresh("30 seconds"), keepAlive));
239449
239769
  const getCurrentIssues = (projectId) => getResult$1(currentIssuesAtom(projectId), { suspendOnWaiting: true });
239450
- const checkForWork = gen(function* () {
239451
- if (!(yield* getCurrentIssues(yield* CurrentProjectId)).some((issue) => issue.state === "todo" && issue.blockedBy.length === 0)) return yield* new NoMoreWork({});
239770
+ const checkForWork = fnUntraced(function* (project) {
239771
+ if (project.gitFlow === "ralph") return;
239772
+ if (!(yield* getCurrentIssues(project.id)).some((issue) => issue.state === "todo" && issue.blockedBy.length === 0)) return yield* new NoMoreWork({});
239452
239773
  });
239453
239774
  const resetInProgress = gen(function* () {
239454
239775
  const source = yield* IssueSource;
@@ -239551,6 +239872,12 @@ Set \`githubPrNumber\` to the PR number if one exists, otherwise use \`null\`.
239551
239872
  - The pull request will contain the task id in the title or description.` : ""}
239552
239873
  - Use the "chooseTask" function to select the task you have chosen.
239553
239874
  ${options.gitFlow.requiresGithubPr ? `\n - Set \`githubPrNumber\` to the PR number if one exists, otherwise use \`null\`.` : "\n Leave `githubPrNumber` as null."}
239875
+ `;
239876
+ const promptChooseRalph = (options) => `- Read the spec file at \`${options.specFile}\` to understand the current project.
239877
+ - Choose the next most important task to work on from the specification.
239878
+ - If all of the tasks are complete then do nothing more. Otherwise, write the chosen task in a ".lalph/task.md" file.
239879
+
239880
+ Note: The task should be a specific, actionable item that can be completed in a reasonable amount of time.
239554
239881
  `;
239555
239882
  const keyInformation = (options) => `## Important: Adding new tasks
239556
239883
 
@@ -239664,6 +239991,28 @@ All steps must be done before the task can be considered complete.${options.task
239664
239991
  5. **After ${options.gitFlow.requiresGithubPr ? "pushing" : "committing"}** your changes, update current task to reflect any changes in the task state.
239665
239992
  - Rewrite the notes in the description to include only the key discoveries and information that could speed up future work on other tasks. Make sure to preserve important information such as specification file references.
239666
239993
  - If you believe the task is complete, update the \`state\` to "in-review".`;
239994
+ const promptRalph = (options) => `${options.task}
239995
+
239996
+ ## Project specification
239997
+
239998
+ Make sure to review the project specification at \`${options.specFile}\` for any key information that may help you with this task.
239999
+
240000
+ ### Instructions
240001
+
240002
+ All steps must be done before the task can be considered complete.
240003
+
240004
+ 1. ${options.gitFlow.setupInstructions({ githubPrNumber: void 0 })}
240005
+ 2. Implement the task.
240006
+ - Along the way, update the specification file with any important discoveries or issues found.
240007
+ 3. Run any checks / feedback loops, such as type checks, unit tests, or linting.
240008
+ 4. Update the specification implementation plan at \`${options.specFile}\` to reflect changes to task states.
240009
+ 4. ${options.gitFlow.commitInstructions({
240010
+ githubPrInstructions: sourceMeta.githubPrInstructions,
240011
+ githubPrNumber: void 0,
240012
+ taskId: "unknown",
240013
+ targetBranch: options.targetBranch
240014
+ })}
240015
+ `;
239667
240016
  const promptResearch = (options) => `Your job is to gather all the necessary information and details to complete the task described below. Do not make any code changes yet, your job is just to research and gather information.
239668
240017
 
239669
240018
  In the final report:
@@ -239717,6 +240066,18 @@ permission.
239717
240066
  5. If any specifications need updating based on your new understanding, update them.
239718
240067
 
239719
240068
  ${prdNotes(options)}`;
240069
+ const promptTimeoutRalph = (options) => `Your earlier attempt to complete the following task took too
240070
+ long and has timed out.
240071
+
240072
+ The following instructions should be done without interaction or asking for
240073
+ permission.
240074
+
240075
+ 1. Investigate why you think the task took too long. Research the codebase
240076
+ further to understand what is needed to complete the task.
240077
+ 2. Update the specification file at \`${options.specFile}\` to break the task
240078
+ down into smaller tasks, and include any important discoveries from your research.
240079
+ 3. Commit the changes to the specification file without pushing.
240080
+ `;
239720
240081
  const promptTimeoutClanka = (options) => `Your earlier attempt to complete the task with id \`${options.taskId}\` took too
239721
240082
  long and has timed out.
239722
240083
 
@@ -239822,13 +240183,16 @@ Make sure to setup dependencies between the tasks using the \`blockedBy\` field.
239822
240183
  return {
239823
240184
  promptChoose,
239824
240185
  promptChooseClanka,
240186
+ promptChooseRalph,
239825
240187
  prompt,
240188
+ promptRalph,
239826
240189
  promptClanka,
239827
240190
  promptResearch,
239828
240191
  promptReview,
239829
240192
  promptReviewCustom,
239830
240193
  promptTimeout,
239831
240194
  promptTimeoutClanka,
240195
+ promptTimeoutRalph,
239832
240196
  planPrompt,
239833
240197
  promptPlanTasks,
239834
240198
  promptPlanTasksClanka,
@@ -239927,7 +240291,6 @@ const withWorkerState = (projectId) => (effect) => AtomRegistry.use((registry) =
239927
240291
  const layerProjectIdPrompt = effect$1(CurrentProjectId, gen(function* () {
239928
240292
  return (yield* selectProject).id;
239929
240293
  })).pipe(provide$3(Settings.layer), provide$3(CurrentIssueSource.layer));
239930
- const allProjects = new Setting("projects", Array$1(Project$2));
239931
240294
  const getAllProjects = Settings.get(allProjects).pipe(map$9(getOrElse$2(() => [])));
239932
240295
  const projectById = fnUntraced(function* (projectId) {
239933
240296
  return findFirst$5(yield* getAllProjects, (p) => p.id === projectId);
@@ -239961,7 +240324,7 @@ const welcomeWizard = gen(function* () {
239961
240324
  console.log(welcome);
239962
240325
  return yield* addOrUpdateProject();
239963
240326
  });
239964
- const addOrUpdateProject = fnUntraced(function* (existing) {
240327
+ const addOrUpdateProject = fnUntraced(function* (existing, fromPlanMode = false) {
239965
240328
  const projects = yield* getAllProjects;
239966
240329
  const id = existing ? existing.id : yield* text$2({
239967
240330
  message: "Project name",
@@ -239982,18 +240345,29 @@ const addOrUpdateProject = fnUntraced(function* (existing) {
239982
240345
  }), trim, liftPredicate(isNonEmpty$1));
239983
240346
  const gitFlow = yield* select({
239984
240347
  message: "Git flow",
239985
- choices: [{
239986
- title: "Pull Request",
239987
- description: "Create a pull request for each task",
239988
- value: "pr",
239989
- selected: existing ? existing.gitFlow === "pr" : false
239990
- }, {
239991
- title: "Commit",
239992
- description: "Tasks are committed directly to the target branch",
239993
- value: "commit",
239994
- selected: existing ? existing.gitFlow === "commit" : false
239995
- }]
240348
+ choices: [
240349
+ {
240350
+ title: "Pull Request",
240351
+ description: "Create a pull request for each task",
240352
+ value: "pr",
240353
+ selected: existing ? existing.gitFlow === "pr" : false
240354
+ },
240355
+ {
240356
+ title: "Commit",
240357
+ description: "Tasks are committed directly to the target branch",
240358
+ value: "commit",
240359
+ selected: existing ? existing.gitFlow === "commit" : false
240360
+ },
240361
+ {
240362
+ title: "Ralph",
240363
+ description: "Tasks are determined from a spec file",
240364
+ value: "ralph",
240365
+ selected: existing ? existing.gitFlow === "ralph" : false
240366
+ }
240367
+ ]
239996
240368
  });
240369
+ let ralphSpec = none$4();
240370
+ if (gitFlow === "ralph" && !fromPlanMode) ralphSpec = yield* file$3({ message: "Path to Ralph spec file" }).pipe(fromYieldable, map$9(some$2));
239997
240371
  const researchAgent = yield* toggle({
239998
240372
  message: "Enable research agent?",
239999
240373
  initial: existing ? existing.researchAgent : true
@@ -240008,13 +240382,14 @@ const addOrUpdateProject = fnUntraced(function* (existing) {
240008
240382
  concurrency,
240009
240383
  targetBranch,
240010
240384
  gitFlow,
240385
+ ralphSpec: getOrUndefined$2(ralphSpec),
240011
240386
  researchAgent,
240012
240387
  reviewAgent
240013
240388
  });
240014
240389
  yield* Settings.set(allProjects, some$2(existing ? projects.map((p) => p.id === project.id ? project : p) : [...projects, project]));
240015
240390
  const source = yield* IssueSource;
240016
240391
  yield* source.reset.pipe(provideService$2(CurrentProjectId, project.id));
240017
- yield* source.settings(project.id);
240392
+ if (gitFlow !== "ralph") yield* source.settings(project.id);
240018
240393
  return project;
240019
240394
  });
240020
240395
  //#endregion
@@ -240350,6 +240725,15 @@ var Prd = class extends Service$1()("lalph/Prd", { make: gen(function* () {
240350
240725
  layer$32,
240351
240726
  CurrentIssueSource.layer
240352
240727
  ]));
240728
+ static layerNoop = succeed$4(this, {
240729
+ path: "",
240730
+ maybeRevertIssue: () => void_$2,
240731
+ revertUpdatedIssues: void_$2,
240732
+ flagUnmergable: () => void_$2,
240733
+ findById: () => succeed$3(null),
240734
+ setChosenIssueId: () => void_$2,
240735
+ setAutoMerge: () => void_$2
240736
+ });
240353
240737
  };
240354
240738
  //#endregion
240355
240739
  //#region src/TaskTools.ts
@@ -240497,7 +240881,7 @@ const runClanka = fnUntraced(function* (options) {
240497
240881
  return yield* stream.pipe(runDrain, as$1(""), catchTag$1("AgentFinished", (e) => succeed$3(e.summary)));
240498
240882
  }, scoped$1, (effect, options) => provide$1(effect, layerLocal({
240499
240883
  directory: options.directory,
240500
- tools: options.withChoose ? TaskChooseTools : TaskTools
240884
+ tools: options.mode === "ralph" ? void 0 : options.mode === "choose" ? TaskChooseTools : TaskTools
240501
240885
  }).pipe(merge$6(ClankaModels.get(options.model)))), provide$1([layerUndici, TaskToolsHandlers]));
240502
240886
  //#endregion
240503
240887
  //#region src/Agents/worker.ts
@@ -240523,13 +240907,14 @@ ${research}`
240523
240907
  }])
240524
240908
  }),
240525
240909
  stallTimeout: options.stallTimeout,
240526
- steer: options.steer
240910
+ steer: options.steer,
240911
+ mode: options.ralph ? "ralph" : "default"
240527
240912
  });
240528
240913
  return ExitCode(0);
240529
240914
  }
240530
240915
  return yield* pipe$1(options.preset.cliAgent.command({
240531
240916
  prompt: options.prompt,
240532
- prdFilePath: pathService.join(".lalph", "prd.yml"),
240917
+ prdFilePath: options.ralph ? void 0 : pathService.join(".lalph", "prd.yml"),
240533
240918
  extraArgs: options.preset.extraArgs
240534
240919
  }), setCwd(worktree.directory), options.preset.withCommandPrefix).pipe(worktree.execWithStallTimeout({
240535
240920
  cliAgent: options.preset.cliAgent,
@@ -240633,6 +241018,31 @@ But you **do not** need to git push your changes or switch branches.
240633
241018
  })
240634
241019
  });
240635
241020
  })).pipe(provide$3(layer$14));
241021
+ const GitFlowRalph = effect$1(GitFlow, gen(function* () {
241022
+ const workerState = yield* get$3((yield* CurrentWorkerState).state);
241023
+ return GitFlow.of({
241024
+ requiresGithubPr: false,
241025
+ branch: `lalph/worker-${workerState.id}`,
241026
+ setupInstructions: () => `You are already on a new branch for this task. You do not need to checkout any other branches.`,
241027
+ 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.
241028
+ - **DO NOT** commit any of the files in the \`.lalph\` directory.`,
241029
+ reviewInstructions: `You are already on the branch with their changes.
241030
+ After making any changes, **you must** commit them to the same branch.
241031
+ But you **do not** need to git push your changes or switch branches.
241032
+
241033
+ - **DO NOT** commit any of the files in the \`.lalph\` directory.
241034
+ - You have full permission to create git commits.`,
241035
+ postWork: fnUntraced(function* ({ worktree, targetBranch }) {
241036
+ if (!targetBranch) return yield* logWarning("GitFlowRalph: No target branch specified, skipping postWork.");
241037
+ const parsed = parseBranch(targetBranch);
241038
+ yield* worktree.exec`git fetch ${parsed.remote}`;
241039
+ yield* worktree.exec`git restore --worktree .`;
241040
+ if ((yield* worktree.exec`git rebase ${parsed.branchWithRemote}`) !== 0) return yield* new GitFlowError({ message: `Failed to rebase onto ${parsed.branchWithRemote}. Aborting task.` });
241041
+ if ((yield* worktree.exec`git push ${parsed.remote} ${`HEAD:${parsed.branch}`}`) !== 0) return yield* new GitFlowError({ message: `Failed to push changes to ${parsed.branchWithRemote}. Aborting task.` });
241042
+ }),
241043
+ autoMerge: () => void_$2
241044
+ });
241045
+ })).pipe(provide$3(layer$14));
240636
241046
  var GitFlowError = class extends TaggedError("GitFlowError") {};
240637
241047
  //#endregion
240638
241048
  //#region src/Agents/chooser.ts
@@ -240651,10 +241061,10 @@ const agentChooser = fnUntraced(function* (options) {
240651
241061
  model: options.preset.extraArgs.join(" "),
240652
241062
  prompt: promptGen.promptChooseClanka({ gitFlow }),
240653
241063
  stallTimeout: options.stallTimeout,
240654
- withChoose: true
240655
- }).pipe(provideService$2(ChosenTaskDeferred, deferred), flatMap$4(() => fail$6(new ChosenTaskNotFound())), raceFirst(_await(deferred)));
241064
+ mode: "choose"
241065
+ }).pipe(provideService$2(ChosenTaskDeferred, deferred), flatMap$4(() => fail$6(new ChosenTaskNotFound$1())), raceFirst(_await(deferred)));
240656
241066
  const prdTask = yield* prd.findById(result.taskId);
240657
- if (!prdTask) return yield* new ChosenTaskNotFound();
241067
+ if (!prdTask) return yield* new ChosenTaskNotFound$1();
240658
241068
  return {
240659
241069
  id: result.taskId,
240660
241070
  githubPrNumber: result.githubPrNumber ?? null,
@@ -240670,16 +241080,16 @@ const agentChooser = fnUntraced(function* (options) {
240670
241080
  duration: options.stallTimeout,
240671
241081
  onTimeout: () => fail$6(new RunnerStalled())
240672
241082
  }), raceFirst(taskJsonCreated));
240673
- return yield* pipe$1(fs.readFileString(pathService.join(worktree.directory, ".lalph", "task.json")), flatMap$4(decodeEffect(ChosenTask)), mapError$2((_) => new ChosenTaskNotFound()), flatMap$4(fnUntraced(function* (task) {
241083
+ return yield* pipe$1(fs.readFileString(pathService.join(worktree.directory, ".lalph", "task.json")), flatMap$4(decodeEffect(ChosenTask)), mapError$2((_) => new ChosenTaskNotFound$1()), flatMap$4(fnUntraced(function* (task) {
240674
241084
  const prdTask = yield* prd.findById(task.id);
240675
241085
  if (prdTask) return {
240676
241086
  ...task,
240677
241087
  prd: prdTask
240678
241088
  };
240679
- return yield* new ChosenTaskNotFound();
241089
+ return yield* new ChosenTaskNotFound$1();
240680
241090
  })));
240681
241091
  });
240682
- var ChosenTaskNotFound = class extends TaggedError("ChosenTaskNotFound") {
241092
+ var ChosenTaskNotFound$1 = class extends TaggedError("ChosenTaskNotFound") {
240683
241093
  message = "The AI agent failed to choose a task.";
240684
241094
  };
240685
241095
  const ChosenTask = fromJsonString(Struct$2({
@@ -240699,7 +241109,7 @@ const agentReviewer = fnUntraced(function* (options) {
240699
241109
  yield* runClanka({
240700
241110
  directory: worktree.directory,
240701
241111
  model: options.preset.extraArgs.join(" "),
240702
- system: promptGen.systemClanka(options),
241112
+ system: options.ralph ? void 0 : promptGen.systemClanka(options),
240703
241113
  prompt: match$10(customInstructions, {
240704
241114
  onNone: () => promptGen.promptReview({
240705
241115
  prompt: options.instructions,
@@ -240711,7 +241121,8 @@ const agentReviewer = fnUntraced(function* (options) {
240711
241121
  removePrdNotes: true
240712
241122
  })
240713
241123
  }),
240714
- stallTimeout: options.stallTimeout
241124
+ stallTimeout: options.stallTimeout,
241125
+ mode: options.ralph ? "ralph" : "default"
240715
241126
  });
240716
241127
  return ExitCode(0);
240717
241128
  }
@@ -240744,21 +241155,28 @@ const agentTimeout = fnUntraced(function* (options) {
240744
241155
  yield* runClanka({
240745
241156
  directory: worktree.directory,
240746
241157
  model: options.preset.extraArgs.join(" "),
240747
- system: promptGen.systemClanka(options),
240748
- prompt: promptGen.promptTimeoutClanka({
240749
- taskId: options.task.id,
241158
+ system: options.task._tag === "ralph" ? void 0 : promptGen.systemClanka(options),
241159
+ prompt: options.task._tag === "ralph" ? promptGen.promptTimeoutRalph({
241160
+ task: options.task.task,
241161
+ specFile: options.task.specFile
241162
+ }) : promptGen.promptTimeoutClanka({
241163
+ taskId: options.task.task.id,
240750
241164
  specsDirectory: options.specsDirectory
240751
241165
  }),
240752
- stallTimeout: options.stallTimeout
241166
+ stallTimeout: options.stallTimeout,
241167
+ mode: options.task._tag === "ralph" ? "ralph" : "default"
240753
241168
  });
240754
241169
  return ExitCode(0);
240755
241170
  }
240756
241171
  return yield* pipe$1(options.preset.cliAgent.command({
240757
- prompt: promptGen.promptTimeout({
240758
- taskId: options.task.id,
241172
+ prompt: options.task._tag === "ralph" ? promptGen.promptTimeoutRalph({
241173
+ task: options.task.task,
241174
+ specFile: options.task.specFile
241175
+ }) : promptGen.promptTimeout({
241176
+ taskId: options.task.task.id,
240759
241177
  specsDirectory: options.specsDirectory
240760
241178
  }),
240761
- prdFilePath: pathService.join(".lalph", "prd.yml"),
241179
+ prdFilePath: options.task._tag === "ralph" ? void 0 : pathService.join(".lalph", "prd.yml"),
240762
241180
  extraArgs: options.preset.extraArgs
240763
241181
  }), setCwd(worktree.directory), options.preset.withCommandPrefix).pipe(worktree.execWithStallTimeout({
240764
241182
  cliAgent: options.preset.cliAgent,
@@ -240780,6 +241198,33 @@ const agentResearcher = fnUntraced(function* (options) {
240780
241198
  }).pipe(asSome);
240781
241199
  });
240782
241200
  //#endregion
241201
+ //#region src/Agents/chooserRalph.ts
241202
+ const agentChooserRalph = fnUntraced(function* (options) {
241203
+ const fs = yield* FileSystem;
241204
+ const pathService = yield* Path$1;
241205
+ const worktree = yield* Worktree;
241206
+ const promptGen = yield* PromptGen;
241207
+ if (!options.preset.cliAgent.command) yield* runClanka({
241208
+ directory: worktree.directory,
241209
+ model: options.preset.extraArgs.join(" "),
241210
+ prompt: promptGen.promptChooseRalph({ specFile: options.specFile }),
241211
+ stallTimeout: options.stallTimeout,
241212
+ mode: "ralph"
241213
+ });
241214
+ else yield* pipe$1(options.preset.cliAgent.command({
241215
+ prompt: promptGen.promptChooseRalph({ specFile: options.specFile }),
241216
+ prdFilePath: void 0,
241217
+ extraArgs: options.preset.extraArgs
241218
+ }), setCwd(worktree.directory), options.preset.withCommandPrefix, worktree.execWithWorkerOutput({ cliAgent: options.preset.cliAgent }), timeoutOrElse({
241219
+ duration: options.stallTimeout,
241220
+ onTimeout: () => fail$6(new RunnerStalled())
241221
+ }));
241222
+ return yield* pipe$1(fs.readFileString(pathService.join(worktree.directory, ".lalph", "task.md")), mapError$2((_) => new ChosenTaskNotFound()));
241223
+ });
241224
+ var ChosenTaskNotFound = class extends TaggedError("ChosenTaskNotFound") {
241225
+ message = "The AI agent failed to choose a task.";
241226
+ };
241227
+ //#endregion
240783
241228
  //#region src/commands/root.ts
240784
241229
  const run = fnUntraced(function* (options) {
240785
241230
  const projectId = yield* CurrentProjectId;
@@ -240871,10 +241316,12 @@ const run = fnUntraced(function* (options) {
240871
241316
  });
240872
241317
  yield* log$1(`Agent exited with code: ${yield* agentWorker({
240873
241318
  stallTimeout: options.stallTimeout,
241319
+ system: promptGen.systemClanka(options),
240874
241320
  preset: taskPreset,
240875
241321
  prompt: instructions,
240876
241322
  research: researchResult,
240877
- steer
241323
+ steer,
241324
+ ralph: false
240878
241325
  }).pipe(provideService$2(CurrentTaskRef, issueRef), catchStallInReview, withSpan$1("Main.agentWorker"))}`);
240879
241326
  if (options.review) {
240880
241327
  yield* source.updateIssue({
@@ -240887,7 +241334,8 @@ const run = fnUntraced(function* (options) {
240887
241334
  specsDirectory: options.specsDirectory,
240888
241335
  stallTimeout: options.stallTimeout,
240889
241336
  preset: taskPreset,
240890
- instructions
241337
+ instructions,
241338
+ ralph: false
240891
241339
  }).pipe(catchStallInReview, withSpan$1("Main.agentReviewer"));
240892
241340
  yield* source.updateIssue({
240893
241341
  projectId,
@@ -240899,7 +241347,10 @@ const run = fnUntraced(function* (options) {
240899
241347
  specsDirectory: options.specsDirectory,
240900
241348
  stallTimeout: options.stallTimeout,
240901
241349
  preset: taskPreset,
240902
- task: chosenTask.prd
241350
+ task: {
241351
+ _tag: "task",
241352
+ task: chosenTask.prd
241353
+ }
240903
241354
  })), raceFirst(watchTaskState({ issueId: taskId })), as$1(false), catchTag$1("TaskStateChanged", (error) => log$1(`Task ${error.issueId} moved to ${error.state}; cancelling run.`).pipe(as$1(true))))) return;
240904
241355
  yield* gitFlow.postWork({
240905
241356
  worktree,
@@ -240913,6 +241364,69 @@ const run = fnUntraced(function* (options) {
240913
241364
  });
240914
241365
  else yield* prd.maybeRevertIssue({ issueId: taskId });
240915
241366
  }, scoped$1, provide$1(SemanticSearchLayer.pipe(provideMerge(Prd.layer)), { local: true }));
241367
+ const runRalph = fnUntraced(function* (options) {
241368
+ const worktree = yield* Worktree;
241369
+ const gitFlow = yield* GitFlow;
241370
+ const currentWorker = yield* CurrentWorkerState;
241371
+ const registry = yield* AtomRegistry;
241372
+ const projectId = yield* CurrentProjectId;
241373
+ const preset = yield* getDefaultCliAgentPreset;
241374
+ yield* addFinalizer(fnUntraced(function* () {
241375
+ const currentBranchName = yield* worktree.currentBranch(worktree.directory).pipe(option$1, map$9(getOrUndefined$2));
241376
+ if (!currentBranchName) return;
241377
+ yield* worktree.exec`git checkout --detach ${currentBranchName}`;
241378
+ yield* worktree.exec`git branch -D ${currentBranchName}`;
241379
+ }, ignore$1()));
241380
+ registry.update(currentWorker.state, (s) => s.transitionTo(WorkerStatus.ChoosingTask()));
241381
+ const chosenTask = yield* agentChooserRalph({
241382
+ stallTimeout: options.stallTimeout,
241383
+ preset,
241384
+ specFile: options.specFile
241385
+ }).pipe(tapErrorTag("ChosenTaskNotFound", fnUntraced(function* () {
241386
+ yield* Settings.update(allProjects, map$16((projects) => projects.map((p) => p.id === projectId ? p.update({ enabled: false }) : p)));
241387
+ })), withSpan$1("Main.chooser"));
241388
+ yield* gen(function* () {
241389
+ registry.update(currentWorker.state, (s) => s.transitionTo(WorkerStatus.Working({ issueId: "ralph" })));
241390
+ let researchResult = none$4();
241391
+ const instructions = (yield* PromptGen).promptRalph({
241392
+ task: chosenTask,
241393
+ specFile: options.specFile,
241394
+ targetBranch: getOrUndefined$2(options.targetBranch),
241395
+ gitFlow
241396
+ });
241397
+ yield* log$1(`Agent exited with code: ${yield* agentWorker({
241398
+ stallTimeout: options.stallTimeout,
241399
+ preset,
241400
+ prompt: instructions,
241401
+ research: researchResult,
241402
+ ralph: true
241403
+ }).pipe(withSpan$1("Main.worker"))}`);
241404
+ if (options.review) {
241405
+ registry.update(currentWorker.state, (s) => s.transitionTo(WorkerStatus.Reviewing({ issueId: "ralph" })));
241406
+ yield* agentReviewer({
241407
+ specsDirectory: "",
241408
+ stallTimeout: options.stallTimeout,
241409
+ preset,
241410
+ instructions,
241411
+ ralph: true
241412
+ }).pipe(withSpan$1("Main.review"));
241413
+ }
241414
+ }).pipe(timeout(options.runTimeout), tapErrorTag("TimeoutError", () => agentTimeout({
241415
+ specsDirectory: "",
241416
+ stallTimeout: options.stallTimeout,
241417
+ preset,
241418
+ task: {
241419
+ _tag: "ralph",
241420
+ task: chosenTask,
241421
+ specFile: options.specFile
241422
+ }
241423
+ })));
241424
+ yield* gitFlow.postWork({
241425
+ worktree,
241426
+ targetBranch: getOrUndefined$2(options.targetBranch),
241427
+ issueId: ""
241428
+ });
241429
+ }, scoped$1, provide$1(SemanticSearchLayer.pipe(provideMerge([Prd.layerNoop, Worktree.layer])), { local: true }));
240916
241430
  const runProject = fnUntraced(function* (options) {
240917
241431
  const isFinite = Number.isFinite(options.iterations);
240918
241432
  const iterationsDisplay = isFinite ? options.iterations : "unlimited";
@@ -240929,7 +241443,18 @@ const runProject = fnUntraced(function* (options) {
240929
241443
  if (quit || isFinite && iteration >= iterations) break;
240930
241444
  const currentIteration = iteration;
240931
241445
  const startedDeferred = yield* make$85();
240932
- yield* checkForWork.pipe(andThen(run({
241446
+ let ralphDone = false;
241447
+ const gitFlow = options.project.gitFlow;
241448
+ const isRalph = gitFlow === "ralph";
241449
+ const gitFlowLayer = gitFlow === "commit" ? GitFlowCommit : gitFlow === "ralph" ? GitFlowRalph : GitFlowPR;
241450
+ const fiber = yield* checkForWork(options.project).pipe(andThen(unify(isRalph ? runRalph({
241451
+ targetBranch: options.project.targetBranch,
241452
+ stallTimeout: options.stallTimeout,
241453
+ runTimeout: options.runTimeout,
241454
+ review: options.project.reviewAgent,
241455
+ research: options.project.researchAgent,
241456
+ specFile: options.project.ralphSpec
241457
+ }) : run({
240933
241458
  startedDeferred,
240934
241459
  targetBranch: options.project.targetBranch,
240935
241460
  specsDirectory: options.specsDirectory,
@@ -240937,7 +241462,14 @@ const runProject = fnUntraced(function* (options) {
240937
241462
  runTimeout: options.runTimeout,
240938
241463
  review: options.project.reviewAgent,
240939
241464
  research: options.project.researchAgent
240940
- }).pipe(provide$1(options.project.gitFlow === "commit" ? GitFlowCommit : GitFlowPR, { local: true }), withWorkerState(options.project.id))), catchTags$1({
241465
+ })).pipe(provide$1(gitFlowLayer, { local: true }), withWorkerState(options.project.id))), catchTags$1({
241466
+ ChosenTaskNotFound(_error) {
241467
+ if (isRalph) {
241468
+ ralphDone = true;
241469
+ return log$1(`No more work to process for Ralph, ending after ${currentIteration + 1} iteration(s).`);
241470
+ }
241471
+ return void_$2;
241472
+ },
240941
241473
  NoMoreWork(_error) {
240942
241474
  if (isFinite) {
240943
241475
  iterations = currentIteration;
@@ -240950,7 +241482,10 @@ const runProject = fnUntraced(function* (options) {
240950
241482
  return void_$2;
240951
241483
  }
240952
241484
  }), catchCause$1((cause) => logWarning(cause).pipe(andThen(sleep(seconds(10))))), ensuring$2(semaphore.release(1)), ensuring$2(completeWith(startedDeferred, void_$2)), run$2(fibers));
240953
- yield* _await(startedDeferred);
241485
+ if (isRalph) {
241486
+ yield* await_(fiber);
241487
+ if (ralphDone) break;
241488
+ } else yield* _await(startedDeferred);
240954
241489
  iteration++;
240955
241490
  }
240956
241491
  yield* awaitEmpty(fibers);
@@ -241027,7 +241562,7 @@ const agentPlanner = fnUntraced(function* (options) {
241027
241562
  const spawner = yield* ChildProcessSpawner;
241028
241563
  yield* pipe$1(options.preset.cliAgent.commandPlan({
241029
241564
  prompt: promptGen.planPrompt(options),
241030
- prdFilePath: pathService.join(".lalph", "prd.yml"),
241565
+ prdFilePath: options.ralph ? void 0 : pathService.join(".lalph", "prd.yml"),
241031
241566
  dangerous: options.dangerous
241032
241567
  }), setCwd(worktree.directory), options.preset.withCommandPrefix, spawner.exitCode);
241033
241568
  });
@@ -241144,7 +241679,7 @@ const commandPlan = make$58("plan", {
241144
241679
  return pipe$1(editor.saveTemp(thePlan.value, { suffix: ".md" }), flatMap$4((file) => log$1(`Saved your plan to: ${file}`)), ignore$1);
241145
241680
  });
241146
241681
  yield* gen(function* () {
241147
- const project = withNewProject ? yield* addOrUpdateProject() : yield* selectProject;
241682
+ const project = withNewProject ? yield* addOrUpdateProject(void 0, true) : yield* selectProject;
241148
241683
  const { specsDirectory } = yield* commandRoot;
241149
241684
  const preset = yield* selectCliAgentPreset;
241150
241685
  yield* plan({
@@ -241152,7 +241687,8 @@ const commandPlan = make$58("plan", {
241152
241687
  specsDirectory,
241153
241688
  targetBranch: project.targetBranch,
241154
241689
  dangerous,
241155
- preset
241690
+ preset,
241691
+ ralph: project.gitFlow === "ralph"
241156
241692
  }).pipe(provideService$2(CurrentProjectId, project.id));
241157
241693
  }).pipe(provide$1([
241158
241694
  Settings.layer,
@@ -241164,27 +241700,32 @@ const plan = fnUntraced(function* (options) {
241164
241700
  const fs = yield* FileSystem;
241165
241701
  const pathService = yield* Path$1;
241166
241702
  const worktree = yield* Worktree;
241703
+ const projectId = yield* CurrentProjectId;
241167
241704
  yield* agentPlanner({
241168
241705
  plan: options.plan,
241169
241706
  specsDirectory: options.specsDirectory,
241170
241707
  dangerous: options.dangerous,
241171
- preset: options.preset
241708
+ preset: options.preset,
241709
+ ralph: options.ralph
241172
241710
  });
241173
241711
  const planDetails = yield* pipe$1(fs.readFileString(pathService.join(worktree.directory, ".lalph", "plan.json")), flatMap$4(decodeEffect(PlanDetails)), mapError$2(() => new SpecNotFound()));
241712
+ if (options.ralph) yield* Settings.update(allProjects, map$16((projects) => projects.map((p) => p.id === projectId ? p.update({ ralphSpec: planDetails.specification }) : p)));
241174
241713
  if (isSome(options.targetBranch)) yield* commitAndPushSpecification({
241175
241714
  specsDirectory: options.specsDirectory,
241176
241715
  targetBranch: options.targetBranch.value
241177
241716
  });
241178
- yield* log$1("Converting specification into tasks");
241179
- yield* agentTasker({
241180
- specificationPath: planDetails.specification,
241181
- specsDirectory: options.specsDirectory,
241182
- preset: options.preset
241183
- });
241717
+ if (!options.ralph) {
241718
+ yield* log$1("Converting specification into tasks");
241719
+ yield* agentTasker({
241720
+ specificationPath: planDetails.specification,
241721
+ specsDirectory: options.specsDirectory,
241722
+ preset: options.preset
241723
+ });
241724
+ }
241184
241725
  if (!worktree.inExisting) yield* pipe$1(fs.copy(pathService.join(worktree.directory, options.specsDirectory), options.specsDirectory, { overwrite: true }), ignore$1);
241185
- }, scoped$1, provide$1([
241726
+ }, scoped$1, (effect, options) => provide$1(effect, [
241186
241727
  PromptGen.layer,
241187
- Prd.layerProvided,
241728
+ options.ralph ? Prd.layerNoop : Prd.layerProvided,
241188
241729
  Worktree.layer,
241189
241730
  Settings.layer,
241190
241731
  CurrentIssueSource.layer
@@ -241289,7 +241830,7 @@ const commandEdit = make$58("edit").pipe(withDescription("Open the selected proj
241289
241830
  const commandSource = make$58("source").pipe(withDescription("Select the issue source to use (e.g. GitHub Issues or Linear). This applies to all projects."), withHandler(() => selectIssueSource), provide(Settings.layer));
241290
241831
  //#endregion
241291
241832
  //#region package.json
241292
- var version = "0.3.85";
241833
+ var version = "0.3.86";
241293
241834
  //#endregion
241294
241835
  //#region src/commands/projects/ls.ts
241295
241836
  const commandProjectsLs = make$58("ls").pipe(withDescription("List configured projects and how they run (enabled state, concurrency, branch, git flow, review agent)."), withHandler(fnUntraced(function* () {