lalph 0.1.65 → 0.1.67

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
@@ -4851,6 +4851,40 @@ const as$2 = /* @__PURE__ */ dual(2, (self$1, b) => map$11(self$1, () => b));
4851
4851
  */
4852
4852
  const flatMap$3 = /* @__PURE__ */ dual(2, (self$1, f) => isNone(self$1) ? none$3() : f(self$1.value));
4853
4853
  /**
4854
+ * Flattens an `Option` of `Option` into a single `Option`.
4855
+ *
4856
+ * **Details**
4857
+ *
4858
+ * This function takes an `Option` that wraps another `Option` and flattens it
4859
+ * into a single `Option`. If the outer `Option` is `Some`, the function
4860
+ * extracts the inner `Option`. If the outer `Option` is `None`, the result
4861
+ * remains `None`.
4862
+ *
4863
+ * This is useful for simplifying nested `Option` structures that may arise
4864
+ * during functional operations.
4865
+ *
4866
+ * @example
4867
+ * ```ts
4868
+ * import { Option } from "effect"
4869
+ *
4870
+ * const nested = Option.some(Option.some("value"))
4871
+ * console.log(Option.flatten(nested))
4872
+ * // Output: { _id: 'Option', _tag: 'Some', value: 'value' }
4873
+ *
4874
+ * const nestedNone = Option.some(Option.none())
4875
+ * console.log(Option.flatten(nestedNone))
4876
+ * // Output: { _id: 'Option', _tag: 'None' }
4877
+ *
4878
+ * const outerNone = Option.none<Option.Option<string>>()
4879
+ * console.log(Option.flatten(outerNone))
4880
+ * // Output: { _id: 'Option', _tag: 'None' }
4881
+ * ```
4882
+ *
4883
+ * @category Sequencing
4884
+ * @since 2.0.0
4885
+ */
4886
+ const flatten$2 = /* @__PURE__ */ flatMap$3(identity);
4887
+ /**
4854
4888
  * Converts an `Option` into an `Array`.
4855
4889
  *
4856
4890
  * **Details**
@@ -50898,19 +50932,19 @@ const layer$15 = /* @__PURE__ */ effect(ChildProcessSpawner, make$20);
50898
50932
  const flattenCommand = (command) => {
50899
50933
  const commands = [];
50900
50934
  const pipeOptions = [];
50901
- const flatten$2 = (cmd) => {
50935
+ const flatten$3 = (cmd) => {
50902
50936
  switch (cmd._tag) {
50903
50937
  case "StandardCommand":
50904
50938
  commands.push(cmd);
50905
50939
  break;
50906
50940
  case "PipedCommand":
50907
- flatten$2(cmd.left);
50941
+ flatten$3(cmd.left);
50908
50942
  pipeOptions.push(cmd.options);
50909
- flatten$2(cmd.right);
50943
+ flatten$3(cmd.right);
50910
50944
  break;
50911
50945
  }
50912
50946
  };
50913
- flatten$2(command);
50947
+ flatten$3(command);
50914
50948
  if (commands.length === 0) throw new Error("flattenCommand produced empty commands array");
50915
50949
  const [first, ...rest] = commands;
50916
50950
  return {
@@ -141347,7 +141381,10 @@ var ReviewThreads = class extends Class("github/ReviewThreads")({ edges: Array$1
141347
141381
  //#region src/Github/Cli.ts
141348
141382
  var GithubCli = class extends Service()("lalph/Github/Cli", { make: gen(function* () {
141349
141383
  const spawner = yield* ChildProcessSpawner;
141350
- const [owner, repo] = (yield* make$21`gh repo view --json nameWithOwner -q ${".nameWithOwner"}`.pipe(string, option$1, flatMap((o) => o.pipe(map$11(trim), filter$5(isNonEmpty), fromOption)))).split("/");
141384
+ const [owner, repo] = (yield* make$21`gh repo view --json nameWithOwner -q ${".nameWithOwner"}`.pipe(string, option$1, flatMap((o) => {
141385
+ const candidate = o.pipe(map$11(trim), filter$5(isNonEmpty));
141386
+ return isSome(candidate) ? succeed$1(candidate.value) : fail$4(new GithubCliRepoNotFound());
141387
+ }))).split("/");
141351
141388
  const reviewComments = (pr) => make$21`gh api graphql -f owner=${owner} -f repo=${repo} -F pr=${pr} -f query=${githubReviewCommentsQuery}`.pipe(string, flatMap(decodeEffect(CommentsFromJson)), map$5((data) => {
141352
141389
  return {
141353
141390
  comments: data.data.repository.pullRequest.comments.edges.map((edge) => edge.node),
@@ -141389,6 +141426,9 @@ ${generalCommentsXml}
141389
141426
  }) }) {
141390
141427
  static layer = effect(this, this.make);
141391
141428
  };
141429
+ var GithubCliRepoNotFound = class extends TaggedError("GithubCliRepoNotFound") {
141430
+ message = "GitHub repository not found. Ensure the current directory is inside a git repo with a GitHub remote.";
141431
+ };
141392
141432
  const renderReviewComments = (comment, followup) => `<comment author="${comment.author.login}" path="${comment.path}"${comment.originalLine ? ` originalLine="${comment.originalLine}"` : ""}>
141393
141433
  <diffHunk><![CDATA[${comment.diffHunk}]]></diffHunk>
141394
141434
  <body><![CDATA[${comment.body}]]></body>${followup.length > 0 ? `
@@ -142080,7 +142120,25 @@ var Prd = class extends Service()("lalph/Prd", { make: gen(function* () {
142080
142120
  };
142081
142121
 
142082
142122
  //#endregion
142083
- //#region src/CliAgent.ts
142123
+ //#region src/commands/agent.ts
142124
+ const commandPrefixSetting = new Setting("commandPrefix", Option(String$1));
142125
+ const parseCommandPrefix = (value) => {
142126
+ const parts = value.trim().split(/\s+/).filter((part) => part.length > 0);
142127
+ return isArrayNonEmpty(parts) ? prefix(parts[0], parts.slice(1)) : identity;
142128
+ };
142129
+ const normalizePrefix = (value) => some(value.trim()).pipe(filter$5(isNonEmpty));
142130
+ const promptForCommandPrefix = gen(function* () {
142131
+ const prefixOption = normalizePrefix(yield* text$2({ message: "Command prefix for agent commands? (leave empty for none)" }));
142132
+ yield* commandPrefixSetting.set(some(prefixOption));
142133
+ return prefixOption;
142134
+ });
142135
+ const getCommandPrefix = gen(function* () {
142136
+ const stored = yield* commandPrefixSetting.get;
142137
+ return match$7(flatten$2(stored), {
142138
+ onNone: () => identity,
142139
+ onSome: parseCommandPrefix
142140
+ });
142141
+ });
142084
142142
  const selectCliAgent = gen(function* () {
142085
142143
  const agent = yield* select({
142086
142144
  message: "Select the CLI agent to use",
@@ -142090,6 +142148,7 @@ const selectCliAgent = gen(function* () {
142090
142148
  }))
142091
142149
  });
142092
142150
  yield* selectedCliAgentId.set(some(agent.id));
142151
+ yield* promptForCommandPrefix;
142093
142152
  return agent;
142094
142153
  });
142095
142154
  const getOrSelectCliAgent = gen(function* () {
@@ -142097,6 +142156,7 @@ const getOrSelectCliAgent = gen(function* () {
142097
142156
  if (isSome(selectedAgent)) return selectedAgent.value;
142098
142157
  return yield* selectCliAgent;
142099
142158
  });
142159
+ const commandAgent = make$27("agent").pipe(withDescription("Select the CLI agent to use"), withHandler(() => selectCliAgent));
142100
142160
 
142101
142161
  //#endregion
142102
142162
  //#region src/commands/root.ts
@@ -142109,10 +142169,6 @@ const targetBranch = string$1("target-branch").pipe(withDescription$1("Target br
142109
142169
  const maxIterationMinutes = integer("max-minutes").pipe(withDescription$1("Maximum number of minutes to allow an iteration to run. Defaults to 90 minutes. Env variable: LALPH_MAX_MINUTES"), withFallbackConfig(int("LALPH_MAX_MINUTES")), withDefault(90));
142110
142170
  const stallMinutes = integer("stall-minutes").pipe(withDescription$1("If no activity occurs for this many minutes, the iteration will be stopped. Defaults to 5 minutes. Env variable: LALPH_STALL_MINUTES"), withFallbackConfig(int("LALPH_STALL_MINUTES")), withDefault(5));
142111
142171
  const specsDirectory = directory("specs").pipe(withDescription$1("Directory to store plan specifications. Env variable: LALPH_SPECS"), withAlias("s"), withFallbackConfig(string$4("LALPH_SPECS")), withDefault(".specs"));
142112
- const commandPrefix = string$1("command-prefix").pipe(withDescription$1(`Prefix to add before the agent command (i.e. "docker sandbox run"). Env variable: LALPH_COMMAND_PREFIX`), withAlias("p"), withFallbackConfig(string$4("LALPH_COMMAND_PREFIX")), map((s) => {
142113
- const parts = s.trim().split(/\s+/).filter((part) => part.length > 0);
142114
- return isArrayNonEmpty(parts) ? prefix(parts[0], parts.slice(1)) : identity;
142115
- }), withDefault(identity));
142116
142172
  const reset = boolean("reset").pipe(withDescription$1("Reset the current issue source before running"), withAlias("r"));
142117
142173
  const commandRoot = make$27("lalph", {
142118
142174
  iterations,
@@ -142121,10 +142177,10 @@ const commandRoot = make$27("lalph", {
142121
142177
  maxIterationMinutes,
142122
142178
  stallMinutes,
142123
142179
  reset,
142124
- specsDirectory,
142125
- commandPrefix
142126
- }).pipe(withHandler(fnUntraced(function* ({ iterations: iterations$1, concurrency: concurrency$1, targetBranch: targetBranch$1, maxIterationMinutes: maxIterationMinutes$1, stallMinutes: stallMinutes$1, specsDirectory: specsDirectory$1, commandPrefix: commandPrefix$1 }) {
142180
+ specsDirectory
142181
+ }).pipe(withHandler(fnUntraced(function* ({ iterations: iterations$1, concurrency: concurrency$1, targetBranch: targetBranch$1, maxIterationMinutes: maxIterationMinutes$1, stallMinutes: stallMinutes$1, specsDirectory: specsDirectory$1 }) {
142127
142182
  const source = yield* build(CurrentIssueSource.layer);
142183
+ const commandPrefix = yield* getCommandPrefix;
142128
142184
  yield* getOrSelectCliAgent;
142129
142185
  const isFinite$3 = Number.isFinite(iterations$1);
142130
142186
  const iterationsDisplay = isFinite$3 ? iterations$1 : "unlimited";
@@ -142145,7 +142201,7 @@ const commandRoot = make$27("lalph", {
142145
142201
  specsDirectory: specsDirectory$1,
142146
142202
  stallTimeout: minutes(stallMinutes$1),
142147
142203
  runTimeout: minutes(maxIterationMinutes$1),
142148
- commandPrefix: commandPrefix$1
142204
+ commandPrefix
142149
142205
  })), catchFilter((e) => e._tag === "NoMoreWork" || e._tag === "QuitError" ? fail$8(e) : e, (e) => logWarning(fail$5(e))), catchTags({
142150
142206
  NoMoreWork(_) {
142151
142207
  if (isFinite$3) {
@@ -142264,11 +142320,11 @@ const ChosenTask = fromJsonString(Struct({
142264
142320
  //#endregion
142265
142321
  //#region src/commands/plan.ts
142266
142322
  const commandPlan = make$27("plan").pipe(withDescription("Iterate on an issue plan and create PRD tasks"), withHandler(fnUntraced(function* () {
142267
- const { specsDirectory: specsDirectory$1, targetBranch: targetBranch$1, commandPrefix: commandPrefix$1 } = yield* commandRoot;
142323
+ const { specsDirectory: specsDirectory$1, targetBranch: targetBranch$1 } = yield* commandRoot;
142268
142324
  yield* plan({
142269
142325
  specsDirectory: specsDirectory$1,
142270
142326
  targetBranch: targetBranch$1,
142271
- commandPrefix: commandPrefix$1
142327
+ commandPrefix: yield* getCommandPrefix
142272
142328
  }).pipe(provide$1(CurrentIssueSource.layer));
142273
142329
  })));
142274
142330
  const plan = fnUntraced(function* (options) {
@@ -142384,13 +142440,9 @@ const commandShell = make$27("shell").pipe(withDescription("Enter an interactive
142384
142440
  //#region src/commands/source.ts
142385
142441
  const commandSource = make$27("source").pipe(withDescription("Select the issue source to use"), withHandler(() => selectIssueSource));
142386
142442
 
142387
- //#endregion
142388
- //#region src/commands/agent.ts
142389
- const commandAgent = make$27("agent").pipe(withDescription("Select the CLI agent to use"), withHandler(() => selectCliAgent));
142390
-
142391
142443
  //#endregion
142392
142444
  //#region package.json
142393
- var version = "0.1.65";
142445
+ var version = "0.1.67";
142394
142446
 
142395
142447
  //#endregion
142396
142448
  //#region src/cli.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lalph",
3
3
  "type": "module",
4
- "version": "0.1.65",
4
+ "version": "0.1.67",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
package/src/Github/Cli.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Effect, Layer, Option, Schema, ServiceMap, String } from "effect"
1
+ import { Data, Effect, Layer, Option, Schema, ServiceMap, String } from "effect"
2
2
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
3
3
  import {
4
4
  CommentsData,
@@ -16,13 +16,15 @@ export class GithubCli extends ServiceMap.Service<GithubCli>()(
16
16
  yield* ChildProcess.make`gh repo view --json nameWithOwner -q ${".nameWithOwner"}`.pipe(
17
17
  ChildProcess.string,
18
18
  Effect.option,
19
- Effect.flatMap((o) =>
20
- o.pipe(
19
+ Effect.flatMap((o) => {
20
+ const candidate = o.pipe(
21
21
  Option.map(String.trim),
22
22
  Option.filter(String.isNonEmpty),
23
- Effect.fromOption,
24
- ),
25
- ),
23
+ )
24
+ return Option.isSome(candidate)
25
+ ? Effect.succeed(candidate.value)
26
+ : Effect.fail(new GithubCliRepoNotFound())
27
+ }),
26
28
  )
27
29
  const [owner, repo] = nameWithOwner.split("/") as [string, string]
28
30
 
@@ -102,6 +104,13 @@ ${generalCommentsXml}
102
104
  static layer = Layer.effect(this, this.make)
103
105
  }
104
106
 
107
+ export class GithubCliRepoNotFound extends Data.TaggedError(
108
+ "GithubCliRepoNotFound",
109
+ ) {
110
+ readonly message =
111
+ "GitHub repository not found. Ensure the current directory is inside a git repo with a GitHub remote."
112
+ }
113
+
105
114
  // markdown helper functions
106
115
 
107
116
  const renderReviewComments = (
@@ -1,5 +1,68 @@
1
- import { Command } from "effect/unstable/cli"
2
- import { selectCliAgent } from "../CliAgent.ts"
1
+ import { Array, Effect, Option, Schema, String, identity } from "effect"
2
+ import { Command, Prompt } from "effect/unstable/cli"
3
+ import { ChildProcess } from "effect/unstable/process"
4
+ import { allCliAgents } from "../domain/CliAgent.ts"
5
+ import { Setting, selectedCliAgentId } from "../Settings.ts"
6
+
7
+ const commandPrefixSetting = new Setting(
8
+ "commandPrefix",
9
+ Schema.Option(Schema.String),
10
+ )
11
+
12
+ const parseCommandPrefix = (value: string) => {
13
+ const parts = value
14
+ .trim()
15
+ .split(/\s+/)
16
+ .filter((part) => part.length > 0)
17
+ return Array.isArrayNonEmpty(parts)
18
+ ? ChildProcess.prefix(parts[0], parts.slice(1))
19
+ : identity<ChildProcess.Command>
20
+ }
21
+
22
+ const normalizePrefix = (value: string) =>
23
+ Option.some(value.trim()).pipe(Option.filter(String.isNonEmpty))
24
+
25
+ export const promptForCommandPrefix = Effect.gen(function* () {
26
+ const prefix = yield* Prompt.text({
27
+ message: "Command prefix for agent commands? (leave empty for none)",
28
+ })
29
+ const prefixOption = normalizePrefix(prefix)
30
+ yield* commandPrefixSetting.set(Option.some(prefixOption))
31
+ return prefixOption
32
+ })
33
+
34
+ export const getCommandPrefix = Effect.gen(function* () {
35
+ const stored = yield* commandPrefixSetting.get
36
+ return Option.match(Option.flatten(stored), {
37
+ onNone: () => identity<ChildProcess.Command>,
38
+ onSome: parseCommandPrefix,
39
+ })
40
+ })
41
+
42
+ export const selectCliAgent = Effect.gen(function* () {
43
+ const agent = yield* Prompt.select({
44
+ message: "Select the CLI agent to use",
45
+ choices: allCliAgents.map((agent) => ({
46
+ title: agent.name,
47
+ value: agent,
48
+ })),
49
+ })
50
+ yield* selectedCliAgentId.set(Option.some(agent.id))
51
+ yield* promptForCommandPrefix
52
+ return agent
53
+ })
54
+
55
+ export const getOrSelectCliAgent = Effect.gen(function* () {
56
+ const selectedAgent = (yield* selectedCliAgentId.get).pipe(
57
+ Option.filterMap((id) =>
58
+ Array.findFirst(allCliAgents, (agent) => agent.id === id),
59
+ ),
60
+ )
61
+ if (Option.isSome(selectedAgent)) {
62
+ return selectedAgent.value
63
+ }
64
+ return yield* selectCliAgent
65
+ })
3
66
 
4
67
  export const commandAgent = Command.make("agent").pipe(
5
68
  Command.withDescription("Select the CLI agent to use"),
@@ -3,7 +3,7 @@ import { PromptGen } from "../PromptGen.ts"
3
3
  import { Prd } from "../Prd.ts"
4
4
  import { ChildProcess } from "effect/unstable/process"
5
5
  import { Worktree } from "../Worktree.ts"
6
- import { getOrSelectCliAgent } from "../CliAgent.ts"
6
+ import { getCommandPrefix, getOrSelectCliAgent } from "./agent.ts"
7
7
  import { Command } from "effect/unstable/cli"
8
8
  import { CurrentIssueSource } from "../IssueSources.ts"
9
9
  import { commandRoot } from "./root.ts"
@@ -12,7 +12,8 @@ export const commandPlan = Command.make("plan").pipe(
12
12
  Command.withDescription("Iterate on an issue plan and create PRD tasks"),
13
13
  Command.withHandler(
14
14
  Effect.fnUntraced(function* () {
15
- const { specsDirectory, targetBranch, commandPrefix } = yield* commandRoot
15
+ const { specsDirectory, targetBranch } = yield* commandRoot
16
+ const commandPrefix = yield* getCommandPrefix
16
17
  yield* plan({ specsDirectory, targetBranch, commandPrefix }).pipe(
17
18
  Effect.provide(CurrentIssueSource.layer),
18
19
  )
@@ -1,5 +1,4 @@
1
1
  import {
2
- Array,
3
2
  Cause,
4
3
  Config,
5
4
  Data,
@@ -10,7 +9,6 @@ import {
10
9
  FiberSet,
11
10
  FileSystem,
12
11
  Filter,
13
- identity,
14
12
  Iterable,
15
13
  Layer,
16
14
  Option,
@@ -23,7 +21,7 @@ import { PromptGen } from "../PromptGen.ts"
23
21
  import { Prd } from "../Prd.ts"
24
22
  import { ChildProcess } from "effect/unstable/process"
25
23
  import { Worktree } from "../Worktree.ts"
26
- import { getOrSelectCliAgent } from "../CliAgent.ts"
24
+ import { getCommandPrefix, getOrSelectCliAgent } from "./agent.ts"
27
25
  import { Flag, CliError, Command } from "effect/unstable/cli"
28
26
  import { checkForWork } from "../IssueSource.ts"
29
27
  import { CurrentIssueSource } from "../IssueSources.ts"
@@ -91,24 +89,6 @@ const specsDirectory = Flag.directory("specs").pipe(
91
89
  Flag.withDefault(".specs"),
92
90
  )
93
91
 
94
- const commandPrefix = Flag.string("command-prefix").pipe(
95
- Flag.withDescription(
96
- `Prefix to add before the agent command (i.e. "docker sandbox run"). Env variable: LALPH_COMMAND_PREFIX`,
97
- ),
98
- Flag.withAlias("p"),
99
- Flag.withFallbackConfig(Config.string("LALPH_COMMAND_PREFIX")),
100
- Flag.map((s) => {
101
- const parts = s
102
- .trim()
103
- .split(/\s+/)
104
- .filter((part) => part.length > 0)
105
- return Array.isArrayNonEmpty(parts)
106
- ? ChildProcess.prefix(parts[0], parts.slice(1))
107
- : identity
108
- }),
109
- Flag.withDefault(identity<ChildProcess.Command>),
110
- )
111
-
112
92
  // handled in cli.ts
113
93
  const reset = Flag.boolean("reset").pipe(
114
94
  Flag.withDescription("Reset the current issue source before running"),
@@ -123,7 +103,6 @@ export const commandRoot = Command.make("lalph", {
123
103
  stallMinutes,
124
104
  reset,
125
105
  specsDirectory,
126
- commandPrefix,
127
106
  }).pipe(
128
107
  Command.withHandler(
129
108
  Effect.fnUntraced(function* ({
@@ -133,9 +112,9 @@ export const commandRoot = Command.make("lalph", {
133
112
  maxIterationMinutes,
134
113
  stallMinutes,
135
114
  specsDirectory,
136
- commandPrefix,
137
115
  }) {
138
116
  const source = yield* Layer.build(CurrentIssueSource.layer)
117
+ const commandPrefix = yield* getCommandPrefix
139
118
  yield* getOrSelectCliAgent
140
119
 
141
120
  const isFinite = Number.isFinite(iterations)
package/src/CliAgent.ts DELETED
@@ -1,28 +0,0 @@
1
- import { Array, Effect, Option } from "effect"
2
- import { Prompt } from "effect/unstable/cli"
3
- import { allCliAgents } from "./domain/CliAgent.ts"
4
- import { selectedCliAgentId } from "./Settings.ts"
5
-
6
- export const selectCliAgent = Effect.gen(function* () {
7
- const agent = yield* Prompt.select({
8
- message: "Select the CLI agent to use",
9
- choices: allCliAgents.map((agent) => ({
10
- title: agent.name,
11
- value: agent,
12
- })),
13
- })
14
- yield* selectedCliAgentId.set(Option.some(agent.id))
15
- return agent
16
- })
17
-
18
- export const getOrSelectCliAgent = Effect.gen(function* () {
19
- const selectedAgent = (yield* selectedCliAgentId.get).pipe(
20
- Option.filterMap((id) =>
21
- Array.findFirst(allCliAgents, (agent) => agent.id === id),
22
- ),
23
- )
24
- if (Option.isSome(selectedAgent)) {
25
- return selectedAgent.value
26
- }
27
- return yield* selectCliAgent
28
- })