lalph 0.3.95 → 0.3.97
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 +2276 -2403
- package/package.json +3 -3
- package/src/Clanka.ts +3 -1
- package/src/CurrentIssueSource.ts +18 -29
- package/src/IssueSource.ts +76 -16
- package/src/Prd.ts +11 -24
- package/src/TaskTools.ts +1 -22
- package/src/cli.ts +1 -5
- package/src/commands/root.ts +30 -38
- package/src/domain/PrdIssue.ts +2 -0
- package/src/shared/runtime.ts +0 -9
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lalph",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.97",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"@linear/sdk": "^78.0.0",
|
|
45
45
|
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
|
|
46
46
|
"@octokit/types": "^16.0.0",
|
|
47
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
48
|
-
"clanka": "^0.2.
|
|
47
|
+
"@typescript/native-preview": "7.0.0-dev.20260321.1",
|
|
48
|
+
"clanka": "^0.2.26",
|
|
49
49
|
"concurrently": "^9.2.1",
|
|
50
50
|
"effect": "4.0.0-beta.36",
|
|
51
51
|
"husky": "^9.1.7",
|
package/src/Clanka.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Agent from "clanka/Agent"
|
|
2
|
+
import * as OutputFormatter from "clanka/OutputFormatter"
|
|
2
3
|
import {
|
|
3
4
|
Cause,
|
|
4
5
|
Config,
|
|
@@ -18,6 +19,7 @@ import { NodeHttpClient } from "@effect/platform-node"
|
|
|
18
19
|
import type { Prompt } from "effect/unstable/ai"
|
|
19
20
|
import { OpenAiClient, OpenAiEmbeddingModel } from "@effect/ai-openai"
|
|
20
21
|
import { Worktree } from "./Worktree.ts"
|
|
22
|
+
import { SemanticSearch } from "clanka"
|
|
21
23
|
|
|
22
24
|
export const ClankaMuxerLayer = Layer.effectDiscard(
|
|
23
25
|
Effect.gen(function* () {
|
|
@@ -8,15 +8,14 @@ import {
|
|
|
8
8
|
Schema,
|
|
9
9
|
ScopedRef,
|
|
10
10
|
ServiceMap,
|
|
11
|
+
SubscriptionRef,
|
|
11
12
|
} from "effect"
|
|
12
13
|
import { allProjects, CurrentProjectId, Setting, Settings } from "./Settings.ts"
|
|
13
14
|
import { LinearIssueSource } from "./Linear.ts"
|
|
14
15
|
import { Prompt } from "effect/unstable/cli"
|
|
15
16
|
import { GithubIssueSource } from "./Github.ts"
|
|
16
|
-
import { IssueSource } from "./IssueSource.ts"
|
|
17
|
+
import { IssuesChange, IssueSource } from "./IssueSource.ts"
|
|
17
18
|
import { PlatformServices } from "./shared/platform.ts"
|
|
18
|
-
import { atomRuntime } from "./shared/runtime.ts"
|
|
19
|
-
import { Atom, Reactivity } from "effect/unstable/reactivity"
|
|
20
19
|
import type { PrdIssue } from "./domain/PrdIssue.ts"
|
|
21
20
|
import type { Project, ProjectId } from "./domain/Project.ts"
|
|
22
21
|
import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
|
|
@@ -109,6 +108,16 @@ export class CurrentIssueSource extends ServiceMap.Service<
|
|
|
109
108
|
)
|
|
110
109
|
|
|
111
110
|
const proxy = IssueSource.of({
|
|
111
|
+
ref: (projectId) =>
|
|
112
|
+
ScopedRef.get(ref).pipe(
|
|
113
|
+
Effect.flatMap((source) => source.ref(projectId)),
|
|
114
|
+
unlessRalph(
|
|
115
|
+
projectId,
|
|
116
|
+
SubscriptionRef.make<IssuesChange>(
|
|
117
|
+
IssuesChange.Internal({ issues: [] }),
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
),
|
|
112
121
|
issues: (projectId) =>
|
|
113
122
|
ScopedRef.get(ref).pipe(
|
|
114
123
|
Effect.flatMap((source) => source.issues(projectId)),
|
|
@@ -186,35 +195,16 @@ const refreshSchedule = Schedule.exponential(100, 1.5).pipe(
|
|
|
186
195
|
Schedule.either(Schedule.spaced("30 seconds")),
|
|
187
196
|
)
|
|
188
197
|
|
|
189
|
-
// Atoms
|
|
190
|
-
|
|
191
|
-
export const issueSourceRuntime = atomRuntime(
|
|
192
|
-
CurrentIssueSource.layer.pipe(Layer.orDie),
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
export const currentIssuesAtom = Atom.family((projectId: ProjectId) =>
|
|
196
|
-
pipe(
|
|
197
|
-
issueSourceRuntime.atom(
|
|
198
|
-
IssueSource.use((s) => s.issues(projectId)).pipe(
|
|
199
|
-
Effect.withSpan("currentIssuesAtom"),
|
|
200
|
-
),
|
|
201
|
-
),
|
|
202
|
-
atomRuntime.withReactivity([`issues:${projectId}`]),
|
|
203
|
-
Atom.withRefresh("30 seconds"),
|
|
204
|
-
Atom.keepAlive,
|
|
205
|
-
),
|
|
206
|
-
)
|
|
207
|
-
|
|
208
198
|
// Helpers
|
|
209
199
|
|
|
210
200
|
const getCurrentIssues = (projectId: ProjectId) =>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
201
|
+
IssueSource.use((s) =>
|
|
202
|
+
pipe(s.ref(projectId), Effect.flatMap(SubscriptionRef.get)),
|
|
203
|
+
)
|
|
214
204
|
|
|
215
205
|
export const checkForWork = Effect.fnUntraced(function* (project: Project) {
|
|
216
206
|
if (project.gitFlow === "ralph") return
|
|
217
|
-
const issues = yield* getCurrentIssues(project.id)
|
|
207
|
+
const { issues } = yield* getCurrentIssues(project.id)
|
|
218
208
|
const hasIncomplete = issues.some(
|
|
219
209
|
(issue) => issue.state === "todo" && issue.blockedBy.length === 0,
|
|
220
210
|
)
|
|
@@ -225,9 +215,8 @@ export const checkForWork = Effect.fnUntraced(function* (project: Project) {
|
|
|
225
215
|
|
|
226
216
|
export const resetInProgress = Effect.gen(function* () {
|
|
227
217
|
const source = yield* IssueSource
|
|
228
|
-
const reactivity = yield* Reactivity.Reactivity
|
|
229
218
|
const projectId = yield* CurrentProjectId
|
|
230
|
-
const issues = yield* getCurrentIssues(projectId)
|
|
219
|
+
const { issues } = yield* getCurrentIssues(projectId)
|
|
231
220
|
const inProgress = issues.filter(
|
|
232
221
|
(issue): issue is PrdIssue & { id: string } =>
|
|
233
222
|
issue.state === "in-progress" && issue.id !== null,
|
|
@@ -242,7 +231,7 @@ export const resetInProgress = Effect.gen(function* () {
|
|
|
242
231
|
state: "todo",
|
|
243
232
|
}),
|
|
244
233
|
{ concurrency: 5, discard: true },
|
|
245
|
-
)
|
|
234
|
+
)
|
|
246
235
|
})
|
|
247
236
|
|
|
248
237
|
export class NoMoreWork extends Schema.ErrorClass<NoMoreWork>(
|
package/src/IssueSource.ts
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Array,
|
|
3
|
+
Data,
|
|
4
|
+
Duration,
|
|
5
|
+
Effect,
|
|
6
|
+
Option,
|
|
7
|
+
Schema,
|
|
8
|
+
ScopedCache,
|
|
9
|
+
ServiceMap,
|
|
10
|
+
Stream,
|
|
11
|
+
SubscriptionRef,
|
|
12
|
+
pipe,
|
|
13
|
+
} from "effect"
|
|
2
14
|
import type { PrdIssue } from "./domain/PrdIssue.ts"
|
|
3
|
-
import { Reactivity } from "effect/unstable/reactivity"
|
|
4
15
|
import type { ProjectId } from "./domain/Project.ts"
|
|
5
16
|
import type { CurrentProjectId, Settings } from "./Settings.ts"
|
|
6
17
|
import type { CliAgentPreset } from "./domain/CliAgentPreset.ts"
|
|
7
18
|
import type { Environment } from "effect/unstable/cli/Prompt"
|
|
8
19
|
import type { QuitError } from "effect/Terminal"
|
|
9
20
|
|
|
21
|
+
export type IssuesChange = Data.TaggedEnum<{
|
|
22
|
+
Internal: { issues: ReadonlyArray<PrdIssue> }
|
|
23
|
+
External: { issues: ReadonlyArray<PrdIssue> }
|
|
24
|
+
}>
|
|
25
|
+
export const IssuesChange = Data.taggedEnum<IssuesChange>()
|
|
26
|
+
|
|
10
27
|
export class IssueSource extends ServiceMap.Service<
|
|
11
28
|
IssueSource,
|
|
12
29
|
{
|
|
30
|
+
readonly ref: (
|
|
31
|
+
projectId: ProjectId,
|
|
32
|
+
) => Effect.Effect<SubscriptionRef.SubscriptionRef<IssuesChange>>
|
|
33
|
+
|
|
13
34
|
readonly issues: (
|
|
14
35
|
projectId: ProjectId,
|
|
15
36
|
) => Effect.Effect<ReadonlyArray<PrdIssue>, IssueSourceError>
|
|
@@ -71,31 +92,70 @@ export class IssueSource extends ServiceMap.Service<
|
|
|
71
92
|
) => Effect.Effect<void, IssueSourceError>
|
|
72
93
|
}
|
|
73
94
|
>()("lalph/IssueSource") {
|
|
74
|
-
static make(impl: IssueSource["Service"]) {
|
|
95
|
+
static make(impl: Omit<IssueSource["Service"], "ref">) {
|
|
75
96
|
return Effect.gen(function* () {
|
|
76
|
-
const
|
|
97
|
+
const refs = yield* ScopedCache.make({
|
|
98
|
+
lookup: Effect.fnUntraced(function* (projectId: ProjectId) {
|
|
99
|
+
const ref = yield* SubscriptionRef.make<IssuesChange>(
|
|
100
|
+
IssuesChange.Internal({
|
|
101
|
+
issues: yield* pipe(
|
|
102
|
+
impl.issues(projectId),
|
|
103
|
+
Effect.orElseSucceed(Array.empty),
|
|
104
|
+
),
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
yield* SubscriptionRef.changes(ref).pipe(
|
|
109
|
+
Stream.switchMap((_) =>
|
|
110
|
+
impl.issues(projectId).pipe(
|
|
111
|
+
Effect.tap((issues) =>
|
|
112
|
+
SubscriptionRef.set(ref, IssuesChange.External({ issues })),
|
|
113
|
+
),
|
|
114
|
+
Effect.delay(Duration.seconds(30)),
|
|
115
|
+
Stream.fromEffectDrain,
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
Stream.runDrain,
|
|
119
|
+
Effect.forkScoped,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return ref
|
|
123
|
+
}),
|
|
124
|
+
capacity: Number.MAX_SAFE_INTEGER,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const update = Effect.fnUntraced(function* (
|
|
128
|
+
projectId: ProjectId,
|
|
129
|
+
issues: ReadonlyArray<PrdIssue>,
|
|
130
|
+
) {
|
|
131
|
+
const ref = yield* ScopedCache.get(refs, projectId)
|
|
132
|
+
yield* SubscriptionRef.set(ref, IssuesChange.Internal({ issues }))
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const updateIssues = (projectId: ProjectId) =>
|
|
136
|
+
pipe(
|
|
137
|
+
impl.issues(projectId),
|
|
138
|
+
Effect.tap((issues) => update(projectId, issues)),
|
|
139
|
+
)
|
|
140
|
+
|
|
77
141
|
return IssueSource.of({
|
|
78
142
|
...impl,
|
|
143
|
+
ref: (projectId) => ScopedCache.get(refs, projectId),
|
|
144
|
+
issues: updateIssues,
|
|
79
145
|
createIssue: (projectId, issue) =>
|
|
80
|
-
|
|
81
|
-
{
|
|
82
|
-
issues: [projectId],
|
|
83
|
-
},
|
|
146
|
+
pipe(
|
|
84
147
|
impl.createIssue(projectId, issue),
|
|
148
|
+
Effect.tap(updateIssues(projectId)),
|
|
85
149
|
),
|
|
86
150
|
updateIssue: (options) =>
|
|
87
|
-
|
|
88
|
-
{
|
|
89
|
-
issues: [options.projectId],
|
|
90
|
-
},
|
|
151
|
+
pipe(
|
|
91
152
|
impl.updateIssue(options),
|
|
153
|
+
Effect.tap(updateIssues(options.projectId)),
|
|
92
154
|
),
|
|
93
155
|
cancelIssue: (projectId, issueId) =>
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
issues: [projectId],
|
|
97
|
-
},
|
|
156
|
+
pipe(
|
|
98
157
|
impl.cancelIssue(projectId, issueId),
|
|
158
|
+
Effect.tap(updateIssues(projectId)),
|
|
99
159
|
),
|
|
100
160
|
})
|
|
101
161
|
})
|
package/src/Prd.ts
CHANGED
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
Semaphore,
|
|
11
11
|
ServiceMap,
|
|
12
12
|
Stream,
|
|
13
|
+
SubscriptionRef,
|
|
13
14
|
} from "effect"
|
|
14
15
|
import { Worktree } from "./Worktree.ts"
|
|
15
16
|
import { PrdIssue } from "./domain/PrdIssue.ts"
|
|
16
17
|
import { IssueSource, IssueSourceError } from "./IssueSource.ts"
|
|
17
|
-
import {
|
|
18
|
-
import { CurrentIssueSource, currentIssuesAtom } from "./CurrentIssueSource.ts"
|
|
18
|
+
import { CurrentIssueSource } from "./CurrentIssueSource.ts"
|
|
19
19
|
import { CurrentProjectId, Settings } from "./Settings.ts"
|
|
20
20
|
|
|
21
21
|
export class Prd extends ServiceMap.Service<
|
|
@@ -46,9 +46,7 @@ export class Prd extends ServiceMap.Service<
|
|
|
46
46
|
const worktree = yield* Worktree
|
|
47
47
|
const pathService = yield* Path.Path
|
|
48
48
|
const fs = yield* FileSystem.FileSystem
|
|
49
|
-
const reactivity = yield* Reactivity.Reactivity
|
|
50
49
|
const source = yield* IssueSource
|
|
51
|
-
const registry = yield* AtomRegistry.AtomRegistry
|
|
52
50
|
const projectId = yield* CurrentProjectId
|
|
53
51
|
|
|
54
52
|
let chosenIssueId: string | null = null
|
|
@@ -60,10 +58,9 @@ export class Prd extends ServiceMap.Service<
|
|
|
60
58
|
const yaml = yield* fs.readFileString(prdFile)
|
|
61
59
|
return PrdIssue.arrayFromYaml(yaml)
|
|
62
60
|
})
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
{ suspendOnWaiting: true },
|
|
61
|
+
const issuesRef = yield* source.ref(projectId)
|
|
62
|
+
const getCurrentIssues = SubscriptionRef.get(issuesRef).pipe(
|
|
63
|
+
Effect.map((i) => i.issues),
|
|
67
64
|
)
|
|
68
65
|
|
|
69
66
|
const syncSemaphore = Semaphore.makeUnsafe(1)
|
|
@@ -203,7 +200,6 @@ export class Prd extends ServiceMap.Service<
|
|
|
203
200
|
{ concurrency: "unbounded" },
|
|
204
201
|
)
|
|
205
202
|
}).pipe(
|
|
206
|
-
reactivity.withBatch,
|
|
207
203
|
Effect.uninterruptible,
|
|
208
204
|
syncSemaphore.withPermit,
|
|
209
205
|
Effect.withSpan("Prd.sync"),
|
|
@@ -242,10 +238,10 @@ export class Prd extends ServiceMap.Service<
|
|
|
242
238
|
Effect.forkScoped,
|
|
243
239
|
)
|
|
244
240
|
|
|
245
|
-
yield*
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
)
|
|
241
|
+
yield* SubscriptionRef.changes(issuesRef).pipe(
|
|
242
|
+
Stream.runForEach((s) => updateSync(s.issues)),
|
|
243
|
+
Effect.forkScoped,
|
|
244
|
+
)
|
|
249
245
|
|
|
250
246
|
const findById = Effect.fnUntraced(function* (issueId: string) {
|
|
251
247
|
const current = yield* getCurrentIssues
|
|
@@ -277,22 +273,13 @@ export class Prd extends ServiceMap.Service<
|
|
|
277
273
|
static layerNoWorktree = Layer.effect(this, this.make)
|
|
278
274
|
static layer = this.layerNoWorktree.pipe(Layer.provideMerge(Worktree.layer))
|
|
279
275
|
static layerProvided = this.layer.pipe(
|
|
280
|
-
Layer.provide([
|
|
281
|
-
AtomRegistry.layer,
|
|
282
|
-
Reactivity.layer,
|
|
283
|
-
CurrentIssueSource.layer,
|
|
284
|
-
Settings.layer,
|
|
285
|
-
]),
|
|
276
|
+
Layer.provide([CurrentIssueSource.layer, Settings.layer]),
|
|
286
277
|
)
|
|
287
278
|
static layerLocal = this.layerNoWorktree.pipe(
|
|
288
279
|
Layer.provideMerge(Worktree.layerLocal),
|
|
289
280
|
)
|
|
290
281
|
static layerLocalProvided = this.layerLocal.pipe(
|
|
291
|
-
Layer.provide([
|
|
292
|
-
AtomRegistry.layer,
|
|
293
|
-
Reactivity.layer,
|
|
294
|
-
CurrentIssueSource.layer,
|
|
295
|
-
]),
|
|
282
|
+
Layer.provide([CurrentIssueSource.layer]),
|
|
296
283
|
)
|
|
297
284
|
static layerNoop = Layer.succeed(this, {
|
|
298
285
|
path: "",
|
package/src/TaskTools.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Deferred,
|
|
3
|
-
Effect,
|
|
4
|
-
MutableRef,
|
|
5
|
-
Option,
|
|
6
|
-
Random,
|
|
7
|
-
Schema,
|
|
8
|
-
ServiceMap,
|
|
9
|
-
Struct,
|
|
10
|
-
} from "effect"
|
|
1
|
+
import { Deferred, Effect, Random, Schema, ServiceMap, Struct } from "effect"
|
|
11
2
|
import { Tool, Toolkit } from "effect/unstable/ai"
|
|
12
3
|
import { PrdIssue } from "./domain/PrdIssue.ts"
|
|
13
4
|
import { IssueSource } from "./IssueSource.ts"
|
|
@@ -24,17 +15,6 @@ export class ChosenTaskDeferred extends ServiceMap.Reference(
|
|
|
24
15
|
},
|
|
25
16
|
) {}
|
|
26
17
|
|
|
27
|
-
export class CurrentTaskRef extends ServiceMap.Service<
|
|
28
|
-
CurrentTaskRef,
|
|
29
|
-
MutableRef.MutableRef<PrdIssue>
|
|
30
|
-
>()("lalph/TaskTools/CurrentTaskRef") {
|
|
31
|
-
static update(f: (prev: PrdIssue) => PrdIssue) {
|
|
32
|
-
return Effect.serviceOption(CurrentTaskRef).pipe(
|
|
33
|
-
Effect.map(Option.map((ref) => MutableRef.updateAndGet(ref, f))),
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
18
|
const Task = Schema.Struct({
|
|
39
19
|
id: Schema.String.annotate({
|
|
40
20
|
documentation: "The unique identifier of the task.",
|
|
@@ -180,7 +160,6 @@ export const TaskToolsHandlers = TaskToolsWithChoose.toLayer(
|
|
|
180
160
|
Effect.annotateLogs({ taskId: options.taskId }),
|
|
181
161
|
)
|
|
182
162
|
const projectId = yield* CurrentProjectId
|
|
183
|
-
yield* CurrentTaskRef.update((prev) => prev.update(options))
|
|
184
163
|
yield* source.updateIssue({
|
|
185
164
|
projectId,
|
|
186
165
|
issueId: options.taskId,
|
package/src/cli.ts
CHANGED
|
@@ -12,7 +12,6 @@ import { commandSource } from "./commands/source.ts"
|
|
|
12
12
|
import PackageJson from "../package.json" with { type: "json" }
|
|
13
13
|
import { TracingLayer } from "./Tracing.ts"
|
|
14
14
|
import { MinimumLogLevel } from "effect/References"
|
|
15
|
-
import { atomRuntime, lalphMemoMap } from "./shared/runtime.ts"
|
|
16
15
|
import { PlatformServices } from "./shared/platform.ts"
|
|
17
16
|
import { commandProjects } from "./commands/projects.ts"
|
|
18
17
|
import { commandSh } from "./commands/sh.ts"
|
|
@@ -32,14 +31,11 @@ commandRoot.pipe(
|
|
|
32
31
|
Command.provide(TracingLayer),
|
|
33
32
|
Command.provide(({ verbose }) => {
|
|
34
33
|
if (!verbose) return Layer.empty
|
|
35
|
-
|
|
36
|
-
atomRuntime.addGlobalLayer(logLevel)
|
|
37
|
-
return logLevel
|
|
34
|
+
return Layer.succeed(MinimumLogLevel, "All")
|
|
38
35
|
}),
|
|
39
36
|
Command.run({
|
|
40
37
|
version: PackageJson.version,
|
|
41
38
|
}),
|
|
42
39
|
Effect.provide(PlatformServices),
|
|
43
|
-
Effect.provideService(Layer.CurrentMemoMap, lalphMemoMap),
|
|
44
40
|
NodeRuntime.runMain,
|
|
45
41
|
)
|
package/src/commands/root.ts
CHANGED
|
@@ -9,16 +9,15 @@ import {
|
|
|
9
9
|
FileSystem,
|
|
10
10
|
Iterable,
|
|
11
11
|
Layer,
|
|
12
|
-
MutableRef,
|
|
13
12
|
Option,
|
|
14
13
|
Path,
|
|
15
14
|
PlatformError,
|
|
16
15
|
Result,
|
|
17
|
-
Schedule,
|
|
18
16
|
Schema,
|
|
19
17
|
Scope,
|
|
20
18
|
Semaphore,
|
|
21
19
|
Stream,
|
|
20
|
+
SubscriptionRef,
|
|
22
21
|
} from "effect"
|
|
23
22
|
import { PromptGen } from "../PromptGen.ts"
|
|
24
23
|
import { Prd } from "../Prd.ts"
|
|
@@ -28,7 +27,6 @@ import { IssueSource, IssueSourceError } from "../IssueSource.ts"
|
|
|
28
27
|
import {
|
|
29
28
|
checkForWork,
|
|
30
29
|
CurrentIssueSource,
|
|
31
|
-
currentIssuesAtom,
|
|
32
30
|
resetInProgress,
|
|
33
31
|
} from "../CurrentIssueSource.ts"
|
|
34
32
|
import { GithubCli } from "../Github/Cli.ts"
|
|
@@ -60,7 +58,6 @@ import type { TimeoutError } from "effect/Cause"
|
|
|
60
58
|
import type { ChildProcessSpawner } from "effect/unstable/process"
|
|
61
59
|
import type { AiError } from "effect/unstable/ai/AiError"
|
|
62
60
|
import type { PrdIssue } from "../domain/PrdIssue.ts"
|
|
63
|
-
import { CurrentTaskRef } from "../TaskTools.ts"
|
|
64
61
|
import type { OutputFormatter } from "clanka"
|
|
65
62
|
import { ClankaMuxerLayer, SemanticSearchLayer } from "../Clanka.ts"
|
|
66
63
|
import { agentResearcher } from "../Agents/researcher.ts"
|
|
@@ -250,14 +247,10 @@ const run = Effect.fnUntraced(
|
|
|
250
247
|
gitFlow,
|
|
251
248
|
})
|
|
252
249
|
|
|
253
|
-
const
|
|
254
|
-
chosenTask.prd.update({
|
|
255
|
-
state: "in-progress",
|
|
256
|
-
}),
|
|
257
|
-
)
|
|
250
|
+
const issueSemaphore = Semaphore.makeUnsafe(1)
|
|
258
251
|
const steer = yield* taskUpdateSteer({
|
|
259
252
|
issueId: taskId,
|
|
260
|
-
|
|
253
|
+
semaphore: issueSemaphore,
|
|
261
254
|
})
|
|
262
255
|
|
|
263
256
|
const exitCode = yield* agentWorker({
|
|
@@ -268,11 +261,7 @@ const run = Effect.fnUntraced(
|
|
|
268
261
|
research: researchResult,
|
|
269
262
|
steer,
|
|
270
263
|
currentTask: CurrentTask.task({ task: chosenTask.prd }),
|
|
271
|
-
}).pipe(
|
|
272
|
-
Effect.provideService(CurrentTaskRef, issueRef),
|
|
273
|
-
catchStallInReview,
|
|
274
|
-
Effect.withSpan("Main.agentWorker"),
|
|
275
|
-
)
|
|
264
|
+
}).pipe(catchStallInReview, Effect.withSpan("Main.agentWorker"))
|
|
276
265
|
yield* Effect.log(`Agent exited with code: ${exitCode}`)
|
|
277
266
|
|
|
278
267
|
// 3. Review task
|
|
@@ -823,18 +812,18 @@ export const commandRoot = Command.make("lalph", {
|
|
|
823
812
|
const watchTaskState = Effect.fnUntraced(function* (options: {
|
|
824
813
|
readonly issueId: string
|
|
825
814
|
}) {
|
|
826
|
-
const registry = yield* AtomRegistry.AtomRegistry
|
|
827
815
|
const projectId = yield* CurrentProjectId
|
|
816
|
+
const source = yield* IssueSource
|
|
817
|
+
const ref = yield* source.ref(projectId)
|
|
828
818
|
|
|
829
|
-
return yield*
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
Stream.runForEach((
|
|
837
|
-
const issue = issues.find((entry) => entry.id === options.issueId)
|
|
819
|
+
return yield* SubscriptionRef.changes(ref).pipe(
|
|
820
|
+
Stream.filterMap((issues) => {
|
|
821
|
+
if (issues._tag === "Internal") return Result.failVoid
|
|
822
|
+
return Result.succeed(
|
|
823
|
+
issues.issues.find((entry) => entry.id === options.issueId),
|
|
824
|
+
)
|
|
825
|
+
}),
|
|
826
|
+
Stream.runForEach((issue) => {
|
|
838
827
|
if (issue?.state === "in-progress" || issue?.state === "in-review") {
|
|
839
828
|
return Effect.void
|
|
840
829
|
}
|
|
@@ -851,26 +840,29 @@ const watchTaskState = Effect.fnUntraced(function* (options: {
|
|
|
851
840
|
|
|
852
841
|
const taskUpdateSteer = Effect.fnUntraced(function* (options: {
|
|
853
842
|
readonly issueId: string
|
|
854
|
-
readonly
|
|
843
|
+
readonly semaphore: Semaphore.Semaphore
|
|
855
844
|
}) {
|
|
856
|
-
const registry = yield* AtomRegistry.AtomRegistry
|
|
857
845
|
const projectId = yield* CurrentProjectId
|
|
846
|
+
const source = yield* IssueSource
|
|
847
|
+
const ref = yield* source.ref(projectId)
|
|
848
|
+
let current: PrdIssue | undefined = undefined
|
|
858
849
|
|
|
859
|
-
return
|
|
860
|
-
registry,
|
|
861
|
-
currentIssuesAtom(projectId),
|
|
862
|
-
).pipe(
|
|
863
|
-
Stream.drop(1),
|
|
864
|
-
Stream.retry(Schedule.forever),
|
|
865
|
-
Stream.orDie,
|
|
866
|
-
Stream.debounce(Duration.seconds(10)),
|
|
850
|
+
return SubscriptionRef.changes(ref).pipe(
|
|
867
851
|
Stream.filterMap((issues) => {
|
|
868
|
-
const issue = issues.find((entry) => entry.id === options.issueId)
|
|
852
|
+
const issue = issues.issues.find((entry) => entry.id === options.issueId)
|
|
869
853
|
if (!issue) return Result.failVoid
|
|
870
|
-
if (!
|
|
854
|
+
if (!current) {
|
|
855
|
+
current = issue
|
|
856
|
+
return Result.failVoid
|
|
857
|
+
}
|
|
858
|
+
if (!issue.isChangedComparedTo(current)) {
|
|
859
|
+
return Result.failVoid
|
|
860
|
+
}
|
|
861
|
+
current = issue
|
|
862
|
+
console.log("issue change", issues._tag)
|
|
863
|
+
if (issues._tag === "Internal") {
|
|
871
864
|
return Result.failVoid
|
|
872
865
|
}
|
|
873
|
-
MutableRef.set(options.current, issue)
|
|
874
866
|
return Result.succeed(`The task has been updated by the user. Here is the latest information:
|
|
875
867
|
|
|
876
868
|
# ${issue.title}
|
package/src/domain/PrdIssue.ts
CHANGED
|
@@ -107,6 +107,7 @@ export class PrdIssue extends Schema.Class<PrdIssue>("PrdIssue")({
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
update(options: {
|
|
110
|
+
readonly id?: string | undefined
|
|
110
111
|
readonly title?: string | undefined
|
|
111
112
|
readonly description?: string | undefined
|
|
112
113
|
readonly state?: PrdIssue["state"] | undefined
|
|
@@ -114,6 +115,7 @@ export class PrdIssue extends Schema.Class<PrdIssue>("PrdIssue")({
|
|
|
114
115
|
}): PrdIssue {
|
|
115
116
|
return new PrdIssue({
|
|
116
117
|
...this,
|
|
118
|
+
id: options.id ?? this.id,
|
|
117
119
|
title: options.title ?? this.title,
|
|
118
120
|
description: options.description ?? this.description,
|
|
119
121
|
state: options.state ?? this.state,
|
package/src/shared/runtime.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { Layer } from "effect"
|
|
2
|
-
import { Atom } from "effect/unstable/reactivity"
|
|
3
|
-
import { TracingLayer } from "../Tracing.ts"
|
|
4
|
-
|
|
5
|
-
export const lalphMemoMap = Layer.makeMemoMapUnsafe()
|
|
6
|
-
|
|
7
|
-
export const atomRuntime = Atom.context({ memoMap: lalphMemoMap })
|
|
8
|
-
|
|
9
|
-
atomRuntime.addGlobalLayer(TracingLayer)
|