lalph 0.3.92 → 0.3.94
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 +657 -355
- package/package.json +8 -8
- package/src/CurrentIssueSource.ts +5 -0
- package/src/Github.ts +5 -0
- package/src/IssueSource.ts +5 -0
- package/src/Linear.ts +80 -46
- package/src/TaskTools.ts +29 -13
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.94",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -37,17 +37,17 @@
|
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@changesets/changelog-github": "^0.6.0",
|
|
39
39
|
"@changesets/cli": "^2.30.0",
|
|
40
|
-
"@effect/ai-openai": "4.0.0-beta.
|
|
41
|
-
"@effect/ai-openai-compat": "4.0.0-beta.
|
|
42
|
-
"@effect/language-service": "^0.
|
|
43
|
-
"@effect/platform-node": "4.0.0-beta.
|
|
40
|
+
"@effect/ai-openai": "4.0.0-beta.36",
|
|
41
|
+
"@effect/ai-openai-compat": "4.0.0-beta.36",
|
|
42
|
+
"@effect/language-service": "^0.81.0",
|
|
43
|
+
"@effect/platform-node": "4.0.0-beta.36",
|
|
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.20260320.1",
|
|
48
|
+
"clanka": "^0.2.21",
|
|
49
49
|
"concurrently": "^9.2.1",
|
|
50
|
-
"effect": "
|
|
50
|
+
"effect": "4.0.0-beta.36",
|
|
51
51
|
"husky": "^9.1.7",
|
|
52
52
|
"lint-staged": "^16.4.0",
|
|
53
53
|
"octokit": "^5.0.5",
|
|
@@ -121,6 +121,11 @@ export class CurrentIssueSource extends ServiceMap.Service<
|
|
|
121
121
|
Effect.retry(refreshSchedule),
|
|
122
122
|
unlessRalph(projectId, Effect.succeed([])),
|
|
123
123
|
),
|
|
124
|
+
findById: (projectId, issueId) =>
|
|
125
|
+
ScopedRef.get(ref).pipe(
|
|
126
|
+
Effect.flatMap((source) => source.findById(projectId, issueId)),
|
|
127
|
+
unlessRalph(projectId, Effect.succeed(null)),
|
|
128
|
+
),
|
|
124
129
|
createIssue: (projectId, options) =>
|
|
125
130
|
ScopedRef.get(ref).pipe(
|
|
126
131
|
Effect.flatMap((source) => source.createIssue(projectId, options)),
|
package/src/Github.ts
CHANGED
|
@@ -492,6 +492,11 @@ export const GithubIssueSource = Layer.effect(
|
|
|
492
492
|
const settings = yield* Cache.get(projectSettings, projectId)
|
|
493
493
|
return yield* issues(settings)
|
|
494
494
|
}),
|
|
495
|
+
findById: Effect.fnUntraced(function* (projectId, issueId) {
|
|
496
|
+
const settings = yield* Cache.get(projectSettings, projectId)
|
|
497
|
+
const projectIssues = yield* issues(settings)
|
|
498
|
+
return projectIssues.find((issue) => issue.id === issueId) ?? null
|
|
499
|
+
}),
|
|
495
500
|
createIssue: Effect.fnUntraced(
|
|
496
501
|
function* (projectId, issue) {
|
|
497
502
|
const { labelFilter, autoMergeLabelName } = yield* Cache.get(
|
package/src/IssueSource.ts
CHANGED
|
@@ -14,6 +14,11 @@ export class IssueSource extends ServiceMap.Service<
|
|
|
14
14
|
projectId: ProjectId,
|
|
15
15
|
) => Effect.Effect<ReadonlyArray<PrdIssue>, IssueSourceError>
|
|
16
16
|
|
|
17
|
+
readonly findById: (
|
|
18
|
+
projectId: ProjectId,
|
|
19
|
+
issueId: string,
|
|
20
|
+
) => Effect.Effect<PrdIssue | null, IssueSourceError>
|
|
21
|
+
|
|
17
22
|
readonly createIssue: (
|
|
18
23
|
projectId: ProjectId,
|
|
19
24
|
issue: PrdIssue,
|
package/src/Linear.ts
CHANGED
|
@@ -208,45 +208,48 @@ export const LinearIssueSource = Layer.effect(
|
|
|
208
208
|
const identifierMap = new Map<string, string>()
|
|
209
209
|
const presetMap = new Map<string, CliAgentPreset>()
|
|
210
210
|
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
211
|
+
const findState = (
|
|
212
|
+
teamId: string,
|
|
213
|
+
type: string,
|
|
214
|
+
names: Array<string> = [],
|
|
215
|
+
fallbackType = type,
|
|
216
|
+
) => {
|
|
217
|
+
const filtered = state.states.filter((s) => {
|
|
218
|
+
if (names.length === 0) return s.type === type
|
|
219
|
+
const name = s.name.toLowerCase()
|
|
220
|
+
return s.type === type && names.some((n) => name.includes(n))
|
|
221
|
+
})
|
|
222
|
+
const withTeamId = filtered.filter((s) => s.teamId === teamId)
|
|
223
|
+
if (withTeamId.length > 0) return withTeamId[0]!
|
|
224
|
+
const withoutTeamId = filtered.filter((s) => s.teamId === undefined)
|
|
225
|
+
if (withoutTeamId.length > 0) return withoutTeamId[0]!
|
|
226
|
+
return state.states.find((s) => s.type === fallbackType)!
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const statesForTeamId = memoize((teamId: string) => ({
|
|
230
|
+
backlog: findState(teamId, "backlog", ["backlog"]),
|
|
231
|
+
todo: findState(teamId, "unstarted", ["todo", "unstarted"]),
|
|
232
|
+
inProgress: findState(teamId, "started", ["progress", "started"]),
|
|
233
|
+
inReview: findState(teamId, "started", ["review"], "completed"),
|
|
234
|
+
done: findState(teamId, "completed"),
|
|
235
|
+
canceled: findState(teamId, "canceled"),
|
|
236
|
+
}))
|
|
237
|
+
|
|
238
|
+
const linearStateToPrdState = (
|
|
239
|
+
state: State,
|
|
240
|
+
teamId: string,
|
|
241
|
+
): PrdIssue["state"] => {
|
|
242
|
+
const states = statesForTeamId(teamId)
|
|
240
243
|
switch (state.id) {
|
|
241
|
-
case
|
|
244
|
+
case states.backlog.id:
|
|
242
245
|
return "backlog"
|
|
243
|
-
case
|
|
246
|
+
case states.todo.id:
|
|
244
247
|
return "todo"
|
|
245
|
-
case
|
|
248
|
+
case states.inProgress.id:
|
|
246
249
|
return "in-progress"
|
|
247
|
-
case
|
|
250
|
+
case states.inReview.id:
|
|
248
251
|
return "in-review"
|
|
249
|
-
case
|
|
252
|
+
case states.done.id:
|
|
250
253
|
return "done"
|
|
251
254
|
default:
|
|
252
255
|
if (state.type === "backlog") return "backlog"
|
|
@@ -256,28 +259,34 @@ export const LinearIssueSource = Layer.effect(
|
|
|
256
259
|
return "backlog"
|
|
257
260
|
}
|
|
258
261
|
}
|
|
259
|
-
const prdStateToLinearStateId = (
|
|
262
|
+
const prdStateToLinearStateId = (
|
|
263
|
+
state: PrdIssue["state"],
|
|
264
|
+
teamId: string,
|
|
265
|
+
): string => {
|
|
266
|
+
const states = statesForTeamId(teamId)
|
|
260
267
|
switch (state) {
|
|
261
268
|
case "backlog":
|
|
262
|
-
return
|
|
269
|
+
return states.backlog.id
|
|
263
270
|
case "todo":
|
|
264
|
-
return
|
|
271
|
+
return states.todo.id
|
|
265
272
|
case "in-progress":
|
|
266
|
-
return
|
|
273
|
+
return states.inProgress.id
|
|
267
274
|
case "in-review":
|
|
268
|
-
return
|
|
275
|
+
return states.inReview.id
|
|
269
276
|
case "done":
|
|
270
|
-
return
|
|
277
|
+
return states.done.id
|
|
271
278
|
}
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
const issues = ({
|
|
275
282
|
labelId,
|
|
276
283
|
projectId,
|
|
284
|
+
teamId,
|
|
277
285
|
autoMergeLabelId,
|
|
278
286
|
}: {
|
|
279
287
|
readonly labelId: Option.Option<string>
|
|
280
288
|
readonly projectId: string
|
|
289
|
+
readonly teamId: string
|
|
281
290
|
readonly autoMergeLabelId: Option.Option<string>
|
|
282
291
|
}) =>
|
|
283
292
|
linear.issues({ labelId, projectId }).pipe(
|
|
@@ -308,7 +317,7 @@ export const LinearIssueSource = Layer.effect(
|
|
|
308
317
|
description: issue.description ?? "",
|
|
309
318
|
priority: issue.priority,
|
|
310
319
|
estimate: issue.estimate ?? null,
|
|
311
|
-
state: linearStateToPrdState(issue.state),
|
|
320
|
+
state: linearStateToPrdState(issue.state, teamId),
|
|
312
321
|
blockedBy: issue.blockedBy.map((r) => r.issue.identifier),
|
|
313
322
|
autoMerge: autoMergeLabelId.pipe(
|
|
314
323
|
Option.map((labelId) => issue.labelIds.includes(labelId)),
|
|
@@ -325,10 +334,21 @@ export const LinearIssueSource = Layer.effect(
|
|
|
325
334
|
const settings = yield* Cache.get(projectSettings, projectId)
|
|
326
335
|
return yield* issues({
|
|
327
336
|
projectId: settings.project.id,
|
|
337
|
+
teamId: settings.teamId,
|
|
328
338
|
labelId: settings.labelId,
|
|
329
339
|
autoMergeLabelId: settings.autoMergeLabelId,
|
|
330
340
|
})
|
|
331
341
|
}),
|
|
342
|
+
findById: Effect.fnUntraced(function* (projectId, issueId) {
|
|
343
|
+
const settings = yield* Cache.get(projectSettings, projectId)
|
|
344
|
+
const projectIssues = yield* issues({
|
|
345
|
+
projectId: settings.project.id,
|
|
346
|
+
labelId: settings.labelId,
|
|
347
|
+
teamId: settings.teamId,
|
|
348
|
+
autoMergeLabelId: settings.autoMergeLabelId,
|
|
349
|
+
})
|
|
350
|
+
return projectIssues.find((issue) => issue.id === issueId) ?? null
|
|
351
|
+
}),
|
|
332
352
|
createIssue: Effect.fnUntraced(
|
|
333
353
|
function* (projectId, issue) {
|
|
334
354
|
const { teamId, labelId, autoMergeLabelId, project } =
|
|
@@ -346,7 +366,7 @@ export const LinearIssueSource = Layer.effect(
|
|
|
346
366
|
description: issue.description,
|
|
347
367
|
priority: issue.priority,
|
|
348
368
|
estimate: issue.estimate,
|
|
349
|
-
stateId: prdStateToLinearStateId(issue.state),
|
|
369
|
+
stateId: prdStateToLinearStateId(issue.state, teamId),
|
|
350
370
|
}),
|
|
351
371
|
)
|
|
352
372
|
const linearIssue = yield* linear.use(() => created.issue!)
|
|
@@ -382,7 +402,7 @@ export const LinearIssueSource = Layer.effect(
|
|
|
382
402
|
),
|
|
383
403
|
updateIssue: Effect.fnUntraced(
|
|
384
404
|
function* (options) {
|
|
385
|
-
const { autoMergeLabelId } = yield* Cache.get(
|
|
405
|
+
const { autoMergeLabelId, teamId } = yield* Cache.get(
|
|
386
406
|
projectSettings,
|
|
387
407
|
options.projectId,
|
|
388
408
|
)
|
|
@@ -403,7 +423,7 @@ export const LinearIssueSource = Layer.effect(
|
|
|
403
423
|
update.description = options.description
|
|
404
424
|
}
|
|
405
425
|
if (options.state) {
|
|
406
|
-
update.stateId = prdStateToLinearStateId(options.state)
|
|
426
|
+
update.stateId = prdStateToLinearStateId(options.state, teamId)
|
|
407
427
|
}
|
|
408
428
|
if (
|
|
409
429
|
options.autoMerge !== undefined &&
|
|
@@ -466,11 +486,13 @@ export const LinearIssueSource = Layer.effect(
|
|
|
466
486
|
Effect.mapError((cause) => new IssueSourceError({ cause })),
|
|
467
487
|
),
|
|
468
488
|
cancelIssue: Effect.fnUntraced(
|
|
469
|
-
function* (
|
|
489
|
+
function* (projectId, issueId) {
|
|
490
|
+
const { teamId } = yield* Cache.get(projectSettings, projectId)
|
|
491
|
+
const states = statesForTeamId(teamId)
|
|
470
492
|
const linearIssueId = identifierMap.get(issueId)!
|
|
471
493
|
yield* linear.use((c) =>
|
|
472
494
|
c.updateIssue(linearIssueId, {
|
|
473
|
-
stateId:
|
|
495
|
+
stateId: states.canceled.id,
|
|
474
496
|
}),
|
|
475
497
|
)
|
|
476
498
|
},
|
|
@@ -869,6 +891,7 @@ class LinearState extends Persistable.Class<{
|
|
|
869
891
|
id: Schema.String,
|
|
870
892
|
name: Schema.String,
|
|
871
893
|
type: Schema.String,
|
|
894
|
+
teamId: Schema.optional(Schema.String),
|
|
872
895
|
}),
|
|
873
896
|
),
|
|
874
897
|
viewer: Schema.Struct({
|
|
@@ -876,3 +899,14 @@ class LinearState extends Persistable.Class<{
|
|
|
876
899
|
}),
|
|
877
900
|
}),
|
|
878
901
|
}) {}
|
|
902
|
+
|
|
903
|
+
const memoize = <A, B>(f: (a: A) => B): ((a: A) => B) => {
|
|
904
|
+
const cache = new Map<A, B>()
|
|
905
|
+
return (a: A) => {
|
|
906
|
+
const cached = cache.get(a)
|
|
907
|
+
if (cached) return cached
|
|
908
|
+
const b = f(a)
|
|
909
|
+
cache.set(a, b)
|
|
910
|
+
return b
|
|
911
|
+
}
|
|
912
|
+
}
|
package/src/TaskTools.ts
CHANGED
|
@@ -35,20 +35,20 @@ export class CurrentTaskRef extends ServiceMap.Service<
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const
|
|
39
|
-
Schema.
|
|
40
|
-
|
|
41
|
-
documentation: "The unique identifier of the task.",
|
|
42
|
-
}),
|
|
43
|
-
...Struct.pick(PrdIssue.fields, [
|
|
44
|
-
"title",
|
|
45
|
-
"description",
|
|
46
|
-
"state",
|
|
47
|
-
"priority",
|
|
48
|
-
"blockedBy",
|
|
49
|
-
]),
|
|
38
|
+
const Task = Schema.Struct({
|
|
39
|
+
id: Schema.String.annotate({
|
|
40
|
+
documentation: "The unique identifier of the task.",
|
|
50
41
|
}),
|
|
51
|
-
|
|
42
|
+
...Struct.pick(PrdIssue.fields, [
|
|
43
|
+
"title",
|
|
44
|
+
"description",
|
|
45
|
+
"state",
|
|
46
|
+
"priority",
|
|
47
|
+
"blockedBy",
|
|
48
|
+
]),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const TaskList = Schema.Array(Task)
|
|
52
52
|
|
|
53
53
|
const toTaskListItem = (issue: PrdIssue) => ({
|
|
54
54
|
id: issue.id ?? "",
|
|
@@ -77,6 +77,14 @@ export class TaskTools extends Toolkit.make(
|
|
|
77
77
|
success: Schema.String,
|
|
78
78
|
dependencies: [CurrentProjectId],
|
|
79
79
|
}),
|
|
80
|
+
Tool.make("findTaskById", {
|
|
81
|
+
description: "Find a task by it's id. Returns null if not found.",
|
|
82
|
+
parameters: Schema.String.annotate({
|
|
83
|
+
identifier: "taskId",
|
|
84
|
+
}),
|
|
85
|
+
success: Schema.NullOr(Task),
|
|
86
|
+
dependencies: [CurrentProjectId],
|
|
87
|
+
}),
|
|
80
88
|
Tool.make("updateTask", {
|
|
81
89
|
description: "Update a task. Supports partial updates",
|
|
82
90
|
parameters: Schema.Struct({
|
|
@@ -159,6 +167,14 @@ export const TaskToolsHandlers = TaskToolsWithChoose.toLayer(
|
|
|
159
167
|
)
|
|
160
168
|
return taskId.id
|
|
161
169
|
}, Effect.orDie),
|
|
170
|
+
findTaskById: Effect.fn("TaskTools.findTaskById")(function* (taskId) {
|
|
171
|
+
yield* Effect.log(`Calling "findTaskById"`).pipe(
|
|
172
|
+
Effect.annotateLogs({ taskId }),
|
|
173
|
+
)
|
|
174
|
+
const projectId = yield* CurrentProjectId
|
|
175
|
+
const task = yield* source.findById(projectId, taskId)
|
|
176
|
+
return task ? toTaskListItem(task) : null
|
|
177
|
+
}, Effect.orDie),
|
|
162
178
|
updateTask: Effect.fn("TaskTools.updateTask")(function* (options) {
|
|
163
179
|
yield* Effect.log(`Calling "updateTask"`).pipe(
|
|
164
180
|
Effect.annotateLogs({ taskId: options.taskId }),
|