lalph 0.3.85 → 0.3.87
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 +621 -95
- package/package.json +1 -1
- package/src/Agents/chooser.ts +1 -1
- package/src/Agents/chooserRalph.ts +57 -0
- package/src/Agents/planner.ts +4 -1
- package/src/Agents/reviewer.ts +3 -1
- package/src/Agents/timeout.ts +39 -11
- package/src/Agents/worker.ts +5 -1
- package/src/Clanka.ts +7 -2
- package/src/CurrentIssueSource.ts +31 -5
- package/src/GitFlow.ts +56 -0
- package/src/Prd.ts +9 -0
- package/src/Projects.ts +22 -16
- package/src/PromptGen.ts +57 -17
- package/src/Settings.ts +14 -1
- package/src/commands/plan.ts +35 -15
- package/src/commands/projects/rm.ts +2 -2
- package/src/commands/projects/toggle.ts +2 -2
- package/src/commands/root.ts +229 -21
- package/src/domain/CliAgent.ts +33 -15
- package/src/domain/Project.ts +10 -2
package/src/commands/plan.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { Worktree } from "../Worktree.ts"
|
|
|
14
14
|
import { Command, Flag } from "effect/unstable/cli"
|
|
15
15
|
import { CurrentIssueSource } from "../CurrentIssueSource.ts"
|
|
16
16
|
import { commandRoot } from "./root.ts"
|
|
17
|
-
import { CurrentProjectId, Settings } from "../Settings.ts"
|
|
17
|
+
import { allProjects, CurrentProjectId, Settings } from "../Settings.ts"
|
|
18
18
|
import { addOrUpdateProject, selectProject } from "../Projects.ts"
|
|
19
19
|
import { agentPlanner } from "../Agents/planner.ts"
|
|
20
20
|
import { agentTasker } from "../Agents/tasker.ts"
|
|
@@ -82,7 +82,7 @@ export const commandPlan = Command.make("plan", {
|
|
|
82
82
|
// possible
|
|
83
83
|
yield* Effect.gen(function* () {
|
|
84
84
|
const project = withNewProject
|
|
85
|
-
? yield* addOrUpdateProject()
|
|
85
|
+
? yield* addOrUpdateProject(undefined, true)
|
|
86
86
|
: yield* selectProject
|
|
87
87
|
const { specsDirectory } = yield* commandRoot
|
|
88
88
|
const preset = yield* selectCliAgentPreset
|
|
@@ -93,6 +93,7 @@ export const commandPlan = Command.make("plan", {
|
|
|
93
93
|
targetBranch: project.targetBranch,
|
|
94
94
|
dangerous,
|
|
95
95
|
preset,
|
|
96
|
+
ralph: project.gitFlow === "ralph",
|
|
96
97
|
}).pipe(Effect.provideService(CurrentProjectId, project.id))
|
|
97
98
|
}).pipe(
|
|
98
99
|
Effect.provide([
|
|
@@ -116,16 +117,19 @@ const plan = Effect.fnUntraced(
|
|
|
116
117
|
readonly targetBranch: Option.Option<string>
|
|
117
118
|
readonly dangerous: boolean
|
|
118
119
|
readonly preset: CliAgentPreset
|
|
120
|
+
readonly ralph: boolean
|
|
119
121
|
}) {
|
|
120
122
|
const fs = yield* FileSystem.FileSystem
|
|
121
123
|
const pathService = yield* Path.Path
|
|
122
124
|
const worktree = yield* Worktree
|
|
125
|
+
const projectId = yield* CurrentProjectId
|
|
123
126
|
|
|
124
127
|
yield* agentPlanner({
|
|
125
128
|
plan: options.plan,
|
|
126
129
|
specsDirectory: options.specsDirectory,
|
|
127
130
|
dangerous: options.dangerous,
|
|
128
131
|
preset: options.preset,
|
|
132
|
+
ralph: options.ralph,
|
|
129
133
|
})
|
|
130
134
|
|
|
131
135
|
const planDetails = yield* pipe(
|
|
@@ -136,6 +140,19 @@ const plan = Effect.fnUntraced(
|
|
|
136
140
|
Effect.mapError(() => new SpecNotFound()),
|
|
137
141
|
)
|
|
138
142
|
|
|
143
|
+
if (options.ralph) {
|
|
144
|
+
yield* Settings.update(
|
|
145
|
+
allProjects,
|
|
146
|
+
Option.map((projects) =>
|
|
147
|
+
projects.map((p) =>
|
|
148
|
+
p.id === projectId
|
|
149
|
+
? p.update({ ralphSpec: planDetails.specification })
|
|
150
|
+
: p,
|
|
151
|
+
),
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
139
156
|
if (Option.isSome(options.targetBranch)) {
|
|
140
157
|
yield* commitAndPushSpecification({
|
|
141
158
|
specsDirectory: options.specsDirectory,
|
|
@@ -143,13 +160,15 @@ const plan = Effect.fnUntraced(
|
|
|
143
160
|
})
|
|
144
161
|
}
|
|
145
162
|
|
|
146
|
-
|
|
163
|
+
if (!options.ralph) {
|
|
164
|
+
yield* Effect.log("Converting specification into tasks")
|
|
147
165
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
166
|
+
yield* agentTasker({
|
|
167
|
+
specificationPath: planDetails.specification,
|
|
168
|
+
specsDirectory: options.specsDirectory,
|
|
169
|
+
preset: options.preset,
|
|
170
|
+
})
|
|
171
|
+
}
|
|
153
172
|
|
|
154
173
|
if (!worktree.inExisting) {
|
|
155
174
|
yield* pipe(
|
|
@@ -163,13 +182,14 @@ const plan = Effect.fnUntraced(
|
|
|
163
182
|
}
|
|
164
183
|
},
|
|
165
184
|
Effect.scoped,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
185
|
+
(effect, options) =>
|
|
186
|
+
Effect.provide(effect, [
|
|
187
|
+
PromptGen.layer,
|
|
188
|
+
options.ralph ? Prd.layerNoop : Prd.layerProvided,
|
|
189
|
+
Worktree.layer,
|
|
190
|
+
Settings.layer,
|
|
191
|
+
CurrentIssueSource.layer,
|
|
192
|
+
]),
|
|
173
193
|
)
|
|
174
194
|
|
|
175
195
|
export class SpecNotFound extends Data.TaggedError("SpecNotFound") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect, FileSystem, Option, Path } from "effect"
|
|
2
2
|
import { Command } from "effect/unstable/cli"
|
|
3
|
-
import {
|
|
4
|
-
import { Settings } from "../../Settings.ts"
|
|
3
|
+
import { getAllProjects, selectProject } from "../../Projects.ts"
|
|
4
|
+
import { allProjects, Settings } from "../../Settings.ts"
|
|
5
5
|
import { CurrentIssueSource } from "../../CurrentIssueSource.ts"
|
|
6
6
|
|
|
7
7
|
export const commandProjectsRm = Command.make("rm").pipe(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Array, Effect, Option } from "effect"
|
|
2
2
|
import { Command, Prompt } from "effect/unstable/cli"
|
|
3
|
-
import {
|
|
4
|
-
import { Settings } from "../../Settings.ts"
|
|
3
|
+
import { getAllProjects } from "../../Projects.ts"
|
|
4
|
+
import { allProjects, Settings } from "../../Settings.ts"
|
|
5
5
|
import { Project } from "../../domain/Project.ts"
|
|
6
6
|
|
|
7
7
|
export const commandProjectsToggle = Command.make("toggle").pipe(
|
package/src/commands/root.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
Deferred,
|
|
4
4
|
Duration,
|
|
5
5
|
Effect,
|
|
6
|
+
Fiber,
|
|
6
7
|
FiberSet,
|
|
7
8
|
FileSystem,
|
|
8
9
|
Iterable,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
Scope,
|
|
18
19
|
Semaphore,
|
|
19
20
|
Stream,
|
|
21
|
+
Unify,
|
|
20
22
|
} from "effect"
|
|
21
23
|
import { PromptGen } from "../PromptGen.ts"
|
|
22
24
|
import { Prd } from "../Prd.ts"
|
|
@@ -35,7 +37,7 @@ import { agentChooser, ChosenTaskNotFound } from "../Agents/chooser.ts"
|
|
|
35
37
|
import { RunnerStalled, TaskStateChanged } from "../domain/Errors.ts"
|
|
36
38
|
import { agentReviewer } from "../Agents/reviewer.ts"
|
|
37
39
|
import { agentTimeout } from "../Agents/timeout.ts"
|
|
38
|
-
import { CurrentProjectId, Settings } from "../Settings.ts"
|
|
40
|
+
import { allProjects, CurrentProjectId, Settings } from "../Settings.ts"
|
|
39
41
|
import { Atom, AtomRegistry, Reactivity } from "effect/unstable/reactivity"
|
|
40
42
|
import {
|
|
41
43
|
activeWorkerLoggingAtom,
|
|
@@ -43,7 +45,13 @@ import {
|
|
|
43
45
|
withWorkerState,
|
|
44
46
|
} from "../Workers.ts"
|
|
45
47
|
import { WorkerStatus } from "../domain/WorkerState.ts"
|
|
46
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
GitFlow,
|
|
50
|
+
GitFlowCommit,
|
|
51
|
+
GitFlowError,
|
|
52
|
+
GitFlowPR,
|
|
53
|
+
GitFlowRalph,
|
|
54
|
+
} from "../GitFlow.ts"
|
|
47
55
|
import { getAllProjects, welcomeWizard } from "../Projects.ts"
|
|
48
56
|
import type { Project } from "../domain/Project.ts"
|
|
49
57
|
import { getDefaultCliAgentPreset } from "../Presets.ts"
|
|
@@ -57,6 +65,7 @@ import { CurrentTaskRef } from "../TaskTools.ts"
|
|
|
57
65
|
import type { OutputFormatter } from "clanka"
|
|
58
66
|
import { ClankaMuxerLayer, SemanticSearchLayer } from "../Clanka.ts"
|
|
59
67
|
import { agentResearcher } from "../Agents/researcher.ts"
|
|
68
|
+
import { agentChooserRalph } from "../Agents/chooserRalph.ts"
|
|
60
69
|
|
|
61
70
|
// Main iteration run logic
|
|
62
71
|
|
|
@@ -254,10 +263,12 @@ const run = Effect.fnUntraced(
|
|
|
254
263
|
|
|
255
264
|
const exitCode = yield* agentWorker({
|
|
256
265
|
stallTimeout: options.stallTimeout,
|
|
266
|
+
system: promptGen.systemClanka(options),
|
|
257
267
|
preset: taskPreset,
|
|
258
268
|
prompt: instructions,
|
|
259
269
|
research: researchResult,
|
|
260
270
|
steer,
|
|
271
|
+
ralph: false,
|
|
261
272
|
}).pipe(
|
|
262
273
|
Effect.provideService(CurrentTaskRef, issueRef),
|
|
263
274
|
catchStallInReview,
|
|
@@ -284,6 +295,7 @@ const run = Effect.fnUntraced(
|
|
|
284
295
|
stallTimeout: options.stallTimeout,
|
|
285
296
|
preset: taskPreset,
|
|
286
297
|
instructions,
|
|
298
|
+
ralph: false,
|
|
287
299
|
}).pipe(catchStallInReview, Effect.withSpan("Main.agentReviewer"))
|
|
288
300
|
|
|
289
301
|
yield* source.updateIssue({
|
|
@@ -299,7 +311,7 @@ const run = Effect.fnUntraced(
|
|
|
299
311
|
specsDirectory: options.specsDirectory,
|
|
300
312
|
stallTimeout: options.stallTimeout,
|
|
301
313
|
preset: taskPreset,
|
|
302
|
-
task: chosenTask.prd,
|
|
314
|
+
task: { _tag: "task", task: chosenTask.prd },
|
|
303
315
|
}),
|
|
304
316
|
),
|
|
305
317
|
Effect.raceFirst(watchTaskState({ issueId: taskId })),
|
|
@@ -336,6 +348,173 @@ const run = Effect.fnUntraced(
|
|
|
336
348
|
}),
|
|
337
349
|
)
|
|
338
350
|
|
|
351
|
+
const runRalph = Effect.fnUntraced(
|
|
352
|
+
function* (options: {
|
|
353
|
+
readonly targetBranch: Option.Option<string>
|
|
354
|
+
readonly stallTimeout: Duration.Duration
|
|
355
|
+
readonly runTimeout: Duration.Duration
|
|
356
|
+
readonly research: boolean
|
|
357
|
+
readonly review: boolean
|
|
358
|
+
readonly specFile: string
|
|
359
|
+
}): Effect.fn.Return<
|
|
360
|
+
void,
|
|
361
|
+
| PlatformError.PlatformError
|
|
362
|
+
| Schema.SchemaError
|
|
363
|
+
| IssueSourceError
|
|
364
|
+
| QuitError
|
|
365
|
+
| GitFlowError
|
|
366
|
+
| ChosenTaskNotFound
|
|
367
|
+
| RunnerStalled
|
|
368
|
+
| TimeoutError
|
|
369
|
+
| AiError,
|
|
370
|
+
| CurrentProjectId
|
|
371
|
+
| ChildProcessSpawner.ChildProcessSpawner
|
|
372
|
+
| Settings
|
|
373
|
+
| Reactivity.Reactivity
|
|
374
|
+
| GithubCli
|
|
375
|
+
| IssueSource
|
|
376
|
+
| Prompt.Environment
|
|
377
|
+
| AtomRegistry.AtomRegistry
|
|
378
|
+
| GitFlow
|
|
379
|
+
| CurrentWorkerState
|
|
380
|
+
| PromptGen
|
|
381
|
+
| Prd
|
|
382
|
+
| Worktree
|
|
383
|
+
| ClankaModels
|
|
384
|
+
| OutputFormatter.Muxer
|
|
385
|
+
| Scope.Scope
|
|
386
|
+
> {
|
|
387
|
+
const worktree = yield* Worktree
|
|
388
|
+
const gitFlow = yield* GitFlow
|
|
389
|
+
const currentWorker = yield* CurrentWorkerState
|
|
390
|
+
const registry = yield* AtomRegistry.AtomRegistry
|
|
391
|
+
const projectId = yield* CurrentProjectId
|
|
392
|
+
|
|
393
|
+
const preset = yield* getDefaultCliAgentPreset
|
|
394
|
+
|
|
395
|
+
// ensure cleanup of branch after run
|
|
396
|
+
yield* Effect.addFinalizer(
|
|
397
|
+
Effect.fnUntraced(function* () {
|
|
398
|
+
const currentBranchName = yield* worktree
|
|
399
|
+
.currentBranch(worktree.directory)
|
|
400
|
+
.pipe(Effect.option, Effect.map(Option.getOrUndefined))
|
|
401
|
+
if (!currentBranchName) return
|
|
402
|
+
|
|
403
|
+
// enter detached state
|
|
404
|
+
yield* worktree.exec`git checkout --detach ${currentBranchName}`
|
|
405
|
+
// delete the branch
|
|
406
|
+
yield* worktree.exec`git branch -D ${currentBranchName}`
|
|
407
|
+
}, Effect.ignore()),
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
// 1. Choose task
|
|
411
|
+
// --------------
|
|
412
|
+
|
|
413
|
+
registry.update(currentWorker.state, (s) =>
|
|
414
|
+
s.transitionTo(WorkerStatus.ChoosingTask()),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
const chosenTask = yield* agentChooserRalph({
|
|
418
|
+
stallTimeout: options.stallTimeout,
|
|
419
|
+
preset,
|
|
420
|
+
specFile: options.specFile,
|
|
421
|
+
}).pipe(
|
|
422
|
+
Effect.tapErrorTag(
|
|
423
|
+
"ChosenTaskNotFound",
|
|
424
|
+
Effect.fnUntraced(function* () {
|
|
425
|
+
// Disable project when all tasks are done
|
|
426
|
+
yield* Settings.update(
|
|
427
|
+
allProjects,
|
|
428
|
+
Option.map((projects) =>
|
|
429
|
+
projects.map((p) =>
|
|
430
|
+
p.id === projectId ? p.update({ enabled: false }) : p,
|
|
431
|
+
),
|
|
432
|
+
),
|
|
433
|
+
)
|
|
434
|
+
}),
|
|
435
|
+
),
|
|
436
|
+
Effect.withSpan("Main.chooser"),
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
yield* Effect.gen(function* () {
|
|
440
|
+
//
|
|
441
|
+
// 2. Work on task
|
|
442
|
+
// -----------------------
|
|
443
|
+
|
|
444
|
+
registry.update(currentWorker.state, (s) =>
|
|
445
|
+
s.transitionTo(WorkerStatus.Working({ issueId: "ralph" })),
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
let researchResult = Option.none<string>()
|
|
449
|
+
// if (options.research) {
|
|
450
|
+
// researchResult = yield* agentResearcher({
|
|
451
|
+
// task: chosenTask.prd,
|
|
452
|
+
// specsDirectory: options.specsDirectory,
|
|
453
|
+
// stallTimeout: options.stallTimeout,
|
|
454
|
+
// preset: taskPreset,
|
|
455
|
+
// })
|
|
456
|
+
// }
|
|
457
|
+
|
|
458
|
+
const promptGen = yield* PromptGen
|
|
459
|
+
const instructions = promptGen.promptRalph({
|
|
460
|
+
task: chosenTask,
|
|
461
|
+
specFile: options.specFile,
|
|
462
|
+
targetBranch: Option.getOrUndefined(options.targetBranch),
|
|
463
|
+
gitFlow,
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
const exitCode = yield* agentWorker({
|
|
467
|
+
stallTimeout: options.stallTimeout,
|
|
468
|
+
preset,
|
|
469
|
+
prompt: instructions,
|
|
470
|
+
research: researchResult,
|
|
471
|
+
ralph: true,
|
|
472
|
+
}).pipe(Effect.withSpan("Main.worker"))
|
|
473
|
+
yield* Effect.log(`Agent exited with code: ${exitCode}`)
|
|
474
|
+
|
|
475
|
+
// 3. Review task
|
|
476
|
+
// -----------------------
|
|
477
|
+
|
|
478
|
+
if (options.review) {
|
|
479
|
+
registry.update(currentWorker.state, (s) =>
|
|
480
|
+
s.transitionTo(WorkerStatus.Reviewing({ issueId: "ralph" })),
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
yield* agentReviewer({
|
|
484
|
+
specsDirectory: "",
|
|
485
|
+
stallTimeout: options.stallTimeout,
|
|
486
|
+
preset,
|
|
487
|
+
instructions,
|
|
488
|
+
ralph: true,
|
|
489
|
+
}).pipe(Effect.withSpan("Main.review"))
|
|
490
|
+
}
|
|
491
|
+
}).pipe(
|
|
492
|
+
Effect.timeout(options.runTimeout),
|
|
493
|
+
Effect.tapErrorTag("TimeoutError", () =>
|
|
494
|
+
agentTimeout({
|
|
495
|
+
specsDirectory: "",
|
|
496
|
+
stallTimeout: options.stallTimeout,
|
|
497
|
+
preset,
|
|
498
|
+
task: { _tag: "ralph", task: chosenTask, specFile: options.specFile },
|
|
499
|
+
}),
|
|
500
|
+
),
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
yield* gitFlow.postWork({
|
|
504
|
+
worktree,
|
|
505
|
+
targetBranch: Option.getOrUndefined(options.targetBranch),
|
|
506
|
+
issueId: "",
|
|
507
|
+
})
|
|
508
|
+
},
|
|
509
|
+
Effect.scoped,
|
|
510
|
+
Effect.provide(
|
|
511
|
+
SemanticSearchLayer.pipe(
|
|
512
|
+
Layer.provideMerge([Prd.layerNoop, Worktree.layer]),
|
|
513
|
+
),
|
|
514
|
+
{ local: true },
|
|
515
|
+
),
|
|
516
|
+
)
|
|
517
|
+
|
|
339
518
|
const runProject = Effect.fnUntraced(
|
|
340
519
|
function* (options: {
|
|
341
520
|
readonly iterations: number
|
|
@@ -370,26 +549,52 @@ const runProject = Effect.fnUntraced(
|
|
|
370
549
|
const currentIteration = iteration
|
|
371
550
|
|
|
372
551
|
const startedDeferred = yield* Deferred.make<void>()
|
|
373
|
-
|
|
374
|
-
|
|
552
|
+
let ralphDone = false
|
|
553
|
+
|
|
554
|
+
const gitFlow = options.project.gitFlow
|
|
555
|
+
const isRalph = gitFlow === "ralph"
|
|
556
|
+
const gitFlowLayer =
|
|
557
|
+
gitFlow === "commit"
|
|
558
|
+
? GitFlowCommit
|
|
559
|
+
: gitFlow === "ralph"
|
|
560
|
+
? GitFlowRalph
|
|
561
|
+
: GitFlowPR
|
|
562
|
+
const fiber = yield* checkForWork(options.project).pipe(
|
|
375
563
|
Effect.andThen(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
564
|
+
Unify.unify(
|
|
565
|
+
isRalph
|
|
566
|
+
? runRalph({
|
|
567
|
+
targetBranch: options.project.targetBranch,
|
|
568
|
+
stallTimeout: options.stallTimeout,
|
|
569
|
+
runTimeout: options.runTimeout,
|
|
570
|
+
review: options.project.reviewAgent,
|
|
571
|
+
research: options.project.researchAgent,
|
|
572
|
+
specFile: options.project.ralphSpec!,
|
|
573
|
+
})
|
|
574
|
+
: run({
|
|
575
|
+
startedDeferred,
|
|
576
|
+
targetBranch: options.project.targetBranch,
|
|
577
|
+
specsDirectory: options.specsDirectory,
|
|
578
|
+
stallTimeout: options.stallTimeout,
|
|
579
|
+
runTimeout: options.runTimeout,
|
|
580
|
+
review: options.project.reviewAgent,
|
|
581
|
+
research: options.project.researchAgent,
|
|
582
|
+
}),
|
|
583
|
+
).pipe(
|
|
584
|
+
Effect.provide(gitFlowLayer, { local: true }),
|
|
389
585
|
withWorkerState(options.project.id),
|
|
390
586
|
),
|
|
391
587
|
),
|
|
392
588
|
Effect.catchTags({
|
|
589
|
+
ChosenTaskNotFound(_error) {
|
|
590
|
+
if (isRalph) {
|
|
591
|
+
ralphDone = true
|
|
592
|
+
return Effect.log(
|
|
593
|
+
`No more work to process for Ralph, ending after ${currentIteration + 1} iteration(s).`,
|
|
594
|
+
)
|
|
595
|
+
}
|
|
596
|
+
return Effect.void
|
|
597
|
+
},
|
|
393
598
|
NoMoreWork(_error) {
|
|
394
599
|
if (isFinite) {
|
|
395
600
|
// If we have a finite number of iterations, we exit when no more
|
|
@@ -419,9 +624,12 @@ const runProject = Effect.fnUntraced(
|
|
|
419
624
|
Effect.ensuring(Deferred.completeWith(startedDeferred, Effect.void)),
|
|
420
625
|
FiberSet.run(fibers),
|
|
421
626
|
)
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
627
|
+
if (isRalph) {
|
|
628
|
+
yield* Fiber.await(fiber)
|
|
629
|
+
if (ralphDone) break
|
|
630
|
+
} else {
|
|
631
|
+
yield* Deferred.await(startedDeferred)
|
|
632
|
+
}
|
|
425
633
|
iteration++
|
|
426
634
|
}
|
|
427
635
|
|
package/src/domain/CliAgent.ts
CHANGED
|
@@ -14,12 +14,12 @@ export class CliAgent<const Id extends string> extends Data.Class<{
|
|
|
14
14
|
outputTransformer?: OutputTransformer | undefined
|
|
15
15
|
command?: (options: {
|
|
16
16
|
readonly prompt: string
|
|
17
|
-
readonly prdFilePath: string
|
|
17
|
+
readonly prdFilePath: string | undefined
|
|
18
18
|
readonly extraArgs: ReadonlyArray<string>
|
|
19
19
|
}) => ChildProcess.Command
|
|
20
20
|
commandPlan: (options: {
|
|
21
21
|
readonly prompt: string
|
|
22
|
-
readonly prdFilePath: string
|
|
22
|
+
readonly prdFilePath: string | undefined
|
|
23
23
|
readonly dangerous: boolean
|
|
24
24
|
}) => ChildProcess.Command
|
|
25
25
|
}> {}
|
|
@@ -36,9 +36,11 @@ const clanka = new CliAgent({
|
|
|
36
36
|
"opencode",
|
|
37
37
|
[
|
|
38
38
|
"--prompt",
|
|
39
|
-
|
|
39
|
+
prdFilePath
|
|
40
|
+
? `@${prdFilePath}
|
|
40
41
|
|
|
41
|
-
${prompt}
|
|
42
|
+
${prompt}`
|
|
43
|
+
: prompt,
|
|
42
44
|
],
|
|
43
45
|
{
|
|
44
46
|
extendEnv: true,
|
|
@@ -62,7 +64,13 @@ const opencode = new CliAgent({
|
|
|
62
64
|
command: ({ prompt, prdFilePath, extraArgs }) =>
|
|
63
65
|
ChildProcess.make(
|
|
64
66
|
"opencode",
|
|
65
|
-
[
|
|
67
|
+
[
|
|
68
|
+
"run",
|
|
69
|
+
prompt,
|
|
70
|
+
"--thinking",
|
|
71
|
+
...extraArgs,
|
|
72
|
+
...(prdFilePath ? ["-f", prdFilePath] : []),
|
|
73
|
+
],
|
|
66
74
|
{
|
|
67
75
|
extendEnv: true,
|
|
68
76
|
env: {
|
|
@@ -78,9 +86,11 @@ const opencode = new CliAgent({
|
|
|
78
86
|
"opencode",
|
|
79
87
|
[
|
|
80
88
|
"--prompt",
|
|
81
|
-
|
|
89
|
+
prdFilePath
|
|
90
|
+
? `@${prdFilePath}
|
|
82
91
|
|
|
83
|
-
${prompt}
|
|
92
|
+
${prompt}`
|
|
93
|
+
: prompt,
|
|
84
94
|
],
|
|
85
95
|
{
|
|
86
96
|
extendEnv: true,
|
|
@@ -113,9 +123,11 @@ const claude = new CliAgent({
|
|
|
113
123
|
"AskUserQuestion",
|
|
114
124
|
...extraArgs,
|
|
115
125
|
"--",
|
|
116
|
-
|
|
126
|
+
prdFilePath
|
|
127
|
+
? `@${prdFilePath}
|
|
117
128
|
|
|
118
|
-
${prompt}
|
|
129
|
+
${prompt}`
|
|
130
|
+
: prompt,
|
|
119
131
|
],
|
|
120
132
|
{
|
|
121
133
|
stdout: "pipe",
|
|
@@ -151,9 +163,11 @@ const codex = new CliAgent({
|
|
|
151
163
|
"exec",
|
|
152
164
|
"--dangerously-bypass-approvals-and-sandbox",
|
|
153
165
|
...extraArgs,
|
|
154
|
-
|
|
166
|
+
prdFilePath
|
|
167
|
+
? `@${prdFilePath}
|
|
155
168
|
|
|
156
|
-
${prompt}
|
|
169
|
+
${prompt}`
|
|
170
|
+
: prompt,
|
|
157
171
|
],
|
|
158
172
|
{
|
|
159
173
|
stdout: "pipe",
|
|
@@ -166,9 +180,11 @@ ${prompt}`,
|
|
|
166
180
|
"codex",
|
|
167
181
|
[
|
|
168
182
|
...(dangerous ? ["--dangerously-bypass-approvals-and-sandbox"] : []),
|
|
169
|
-
|
|
183
|
+
prdFilePath
|
|
184
|
+
? `@${prdFilePath}
|
|
170
185
|
|
|
171
|
-
${prompt}
|
|
186
|
+
${prompt}`
|
|
187
|
+
: prompt,
|
|
172
188
|
],
|
|
173
189
|
{
|
|
174
190
|
stdout: "inherit",
|
|
@@ -188,9 +204,11 @@ const amp = new CliAgent({
|
|
|
188
204
|
"--dangerously-allow-all",
|
|
189
205
|
"--stream-json-thinking",
|
|
190
206
|
...extraArgs,
|
|
191
|
-
|
|
207
|
+
prdFilePath
|
|
208
|
+
? `@${prdFilePath}
|
|
192
209
|
|
|
193
|
-
${prompt}
|
|
210
|
+
${prompt}`
|
|
211
|
+
: prompt,
|
|
194
212
|
],
|
|
195
213
|
{
|
|
196
214
|
stdout: "pipe",
|
package/src/domain/Project.ts
CHANGED
|
@@ -8,7 +8,15 @@ export class Project extends Schema.Class<Project>("lalph/Project")({
|
|
|
8
8
|
enabled: Schema.Boolean,
|
|
9
9
|
targetBranch: Schema.Option(Schema.String),
|
|
10
10
|
concurrency: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
|
|
11
|
-
gitFlow: Schema.Literals(["pr", "commit"]),
|
|
11
|
+
gitFlow: Schema.Literals(["pr", "commit", "ralph"]),
|
|
12
|
+
ralphSpec: Schema.optional(Schema.String),
|
|
12
13
|
researchAgent: Schema.Boolean.pipe(Schema.withDecodingDefault(() => false)),
|
|
13
14
|
reviewAgent: Schema.Boolean,
|
|
14
|
-
}) {
|
|
15
|
+
}) {
|
|
16
|
+
update(updates: Partial<Project>): Project {
|
|
17
|
+
return new Project({
|
|
18
|
+
...this,
|
|
19
|
+
...updates,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|