@qotaq/lalphgram 0.1.2 → 0.1.4

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.
Files changed (56) hide show
  1. package/README.md +12 -20
  2. package/dist/cjs/Main.js +18 -5
  3. package/dist/cjs/Main.js.map +1 -1
  4. package/dist/cjs/lib/AnalysisPrompts.js +2 -0
  5. package/dist/cjs/lib/AnalysisPrompts.js.map +1 -1
  6. package/dist/cjs/schemas/ProjectSchemas.js +4 -1
  7. package/dist/cjs/schemas/ProjectSchemas.js.map +1 -1
  8. package/dist/cjs/services/AutoMerge.js +6 -4
  9. package/dist/cjs/services/AutoMerge.js.map +1 -1
  10. package/dist/cjs/services/ChatMachine.js +3 -2
  11. package/dist/cjs/services/ChatMachine.js.map +1 -1
  12. package/dist/cjs/services/GitHubClient.js +2 -1
  13. package/dist/cjs/services/GitHubClient.js.map +1 -1
  14. package/dist/cjs/services/OctokitClient.js +5 -1
  15. package/dist/cjs/services/OctokitClient.js.map +1 -1
  16. package/dist/cjs/services/PlanSession.js +5 -3
  17. package/dist/cjs/services/PlanSession.js.map +1 -1
  18. package/dist/dts/lib/AnalysisPrompts.d.ts.map +1 -1
  19. package/dist/dts/schemas/ProjectSchemas.d.ts +8 -0
  20. package/dist/dts/schemas/ProjectSchemas.d.ts.map +1 -1
  21. package/dist/dts/services/AutoMerge.d.ts.map +1 -1
  22. package/dist/dts/services/ChatMachine.d.ts +1 -1
  23. package/dist/dts/services/ChatMachine.d.ts.map +1 -1
  24. package/dist/dts/services/EventLoop.d.ts +2 -2
  25. package/dist/dts/services/GitHubClient.d.ts +4 -0
  26. package/dist/dts/services/GitHubClient.d.ts.map +1 -1
  27. package/dist/dts/services/OctokitClient.d.ts +4 -0
  28. package/dist/dts/services/OctokitClient.d.ts.map +1 -1
  29. package/dist/dts/services/PlanSession.d.ts +1 -1
  30. package/dist/dts/services/PlanSession.d.ts.map +1 -1
  31. package/dist/dts/services/TrackerLayerMap.d.ts +1 -1
  32. package/dist/esm/Main.js +19 -6
  33. package/dist/esm/Main.js.map +1 -1
  34. package/dist/esm/lib/AnalysisPrompts.js +2 -0
  35. package/dist/esm/lib/AnalysisPrompts.js.map +1 -1
  36. package/dist/esm/schemas/ProjectSchemas.js +4 -1
  37. package/dist/esm/schemas/ProjectSchemas.js.map +1 -1
  38. package/dist/esm/services/AutoMerge.js +6 -4
  39. package/dist/esm/services/AutoMerge.js.map +1 -1
  40. package/dist/esm/services/ChatMachine.js +3 -2
  41. package/dist/esm/services/ChatMachine.js.map +1 -1
  42. package/dist/esm/services/GitHubClient.js +2 -1
  43. package/dist/esm/services/GitHubClient.js.map +1 -1
  44. package/dist/esm/services/OctokitClient.js +5 -1
  45. package/dist/esm/services/OctokitClient.js.map +1 -1
  46. package/dist/esm/services/PlanSession.js +5 -3
  47. package/dist/esm/services/PlanSession.js.map +1 -1
  48. package/package.json +1 -1
  49. package/src/Main.ts +18 -6
  50. package/src/lib/AnalysisPrompts.ts +2 -0
  51. package/src/schemas/ProjectSchemas.ts +2 -1
  52. package/src/services/AutoMerge.ts +25 -5
  53. package/src/services/ChatMachine.ts +8 -4
  54. package/src/services/GitHubClient.ts +3 -1
  55. package/src/services/OctokitClient.ts +3 -1
  56. package/src/services/PlanSession.ts +9 -4
package/src/Main.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  import { Command as CliCommand, Options, Prompt } from "@effect/cli"
7
7
  import { Command as PlatformCommand, FetchHttpClient, FileSystem, Path } from "@effect/platform"
8
8
  import { NodeContext, NodeRuntime } from "@effect/platform-node"
9
- import { Config, Console, Effect, Layer, Logger, LogLevel, Option, Stream } from "effect"
9
+ import { Config, Console, Effect, Layer, Logger, LogLevel, Option, Schema, Stream } from "effect"
10
10
  import { fileURLToPath } from "node:url"
11
11
  import { AppContext, AppContextLive } from "./services/AppContext.js"
12
12
  import { AppRuntimeConfig, RuntimeConfig } from "./services/AppRuntimeConfig.js"
@@ -17,7 +17,7 @@ import { PlanCommandBuilder } from "./services/PlanSession.js"
17
17
  import { TelegramConfig, TelegramConfigLive, TelegramConfigSchema } from "./services/TelegramConfig.js"
18
18
 
19
19
  const lalphNotifyCommand = CliCommand.make(
20
- "lalph-notify",
20
+ "lalphgram",
21
21
  {
22
22
  interval: Options.integer("interval").pipe(
23
23
  Options.withDefault(30),
@@ -156,9 +156,14 @@ const lalphNotifyCommand = CliCommand.make(
156
156
  })
157
157
  ).pipe(CliCommand.withDescription("Zero-config notification service using lalph project config"))
158
158
 
159
- const cli = CliCommand.run(lalphNotifyCommand, {
160
- name: "lalph-notify",
161
- version: "1.0.0"
159
+ const PackageJsonVersion = Schema.Struct({ version: Schema.String })
160
+
161
+ const readVersion = Effect.gen(function*() {
162
+ const fs = yield* FileSystem.FileSystem
163
+ const pathService = yield* Path.Path
164
+ const dir = pathService.dirname(fileURLToPath(import.meta.url))
165
+ const content = yield* fs.readFileString(pathService.join(dir, "..", "..", "package.json"))
166
+ return Schema.decodeUnknownSync(PackageJsonVersion)(JSON.parse(content)).version
162
167
  })
163
168
 
164
169
  const logLevelLayer = Layer.unwrapEffect(
@@ -168,7 +173,14 @@ const logLevelLayer = Layer.unwrapEffect(
168
173
  )
169
174
  )
170
175
 
171
- Effect.suspend(() => cli(process.argv)).pipe(
176
+ Effect.gen(function*() {
177
+ const version = yield* readVersion
178
+ const cli = CliCommand.run(lalphNotifyCommand, {
179
+ name: "lalphgram",
180
+ version
181
+ })
182
+ yield* Effect.suspend(() => cli(process.argv))
183
+ }).pipe(
172
184
  Effect.provide(TelegramConfigLive),
173
185
  Effect.provide(AppContextLive),
174
186
  Effect.provide(FetchHttpClient.layer),
@@ -13,6 +13,7 @@ export const getAnalysisPrompt = (planType: string): string => {
13
13
  switch (planType) {
14
14
  case "Feature":
15
15
  return `\
16
+
16
17
  Now analyze the spec you just wrote. Create the following files:
17
18
 
18
19
  1. \`.specs/analysis.md\` — high-level design summary in plain text:
@@ -28,6 +29,7 @@ Now analyze the spec you just wrote. Create the following files:
28
29
  - **Act**: the single effect/function call under test
29
30
  - **Assert**: expected outcome and why this case matters
30
31
 
32
+ Be extremely concise. Sacrifice grammar for the sake of concision.
31
33
  Do NOT display file contents in your response. The system will send them to the user.`
32
34
  case "Bug":
33
35
  return `\
@@ -14,5 +14,6 @@ export class LalphProject extends Schema.Class<LalphProject>("LalphProject")({
14
14
  targetBranch: Schema.Option(Schema.String),
15
15
  concurrency: Schema.Int.pipe(Schema.positive()),
16
16
  gitFlow: Schema.Literal("pr", "commit"),
17
- reviewAgent: Schema.Boolean
17
+ reviewAgent: Schema.Boolean,
18
+ labelFilter: Schema.optionalWith(Schema.String, { default: () => "lalph" })
18
19
  }) {}
@@ -43,11 +43,31 @@ const makeRepoFromFullName = (fullName: string) =>
43
43
  html_url: ""
44
44
  })
45
45
 
46
- const isCISuccess = (state: string, checkRuns: ReadonlyArray<{ readonly conclusion: string | null }>) => {
47
- if (checkRuns.length === 0) return state !== "failure"
48
- const allChecksCompleted = Array.every(checkRuns, (cr) => cr.conclusion !== null)
49
- const allChecksPassed = Array.every(checkRuns, (cr) => cr.conclusion === "success" || cr.conclusion === "skipped")
50
- return allChecksCompleted && allChecksPassed && state !== "failure"
46
+ const isBillingFailure = (checkRun: {
47
+ readonly conclusion: string | null
48
+ readonly output: { readonly summary: string | null } | null
49
+ }) =>
50
+ checkRun.conclusion === "failure" &&
51
+ checkRun.output?.summary !== null &&
52
+ checkRun.output?.summary !== undefined &&
53
+ (checkRun.output.summary.includes("account payments have failed") ||
54
+ checkRun.output.summary.includes("spending limit"))
55
+
56
+ const isCISuccess = (
57
+ state: string,
58
+ checkRuns: ReadonlyArray<{
59
+ readonly conclusion: string | null
60
+ readonly output: { readonly summary: string | null } | null
61
+ }>
62
+ ) => {
63
+ const nonBillingRuns = Array.filter(checkRuns, (cr) => !isBillingFailure(cr))
64
+ if (nonBillingRuns.length === 0) return state !== "failure" || checkRuns.length > 0
65
+ const allChecksCompleted = Array.every(nonBillingRuns, (cr) => cr.conclusion !== null)
66
+ const allChecksPassed = Array.every(
67
+ nonBillingRuns,
68
+ (cr) => cr.conclusion === "success" || cr.conclusion === "skipped"
69
+ )
70
+ return allChecksCompleted && allChecksPassed
51
71
  }
52
72
 
53
73
  /**
@@ -471,11 +471,15 @@ export const chatMachine = Machine.make(
471
471
  yield* Effect.log("Plan collection done, starting session").pipe(
472
472
  Effect.annotateLogs("planText", joinedText)
473
473
  )
474
- const totalProjects = yield* projectStore.listProjects.pipe(
475
- Effect.map((ps) => ps.length),
476
- Effect.orElseSucceed(() => 1)
474
+ const projects = yield* projectStore.listProjects.pipe(
475
+ Effect.orElseSucceed((): ReadonlyArray<{ id: string; labelFilter: string }> => [])
477
476
  )
478
- yield* planSession.start(joinedText, totalProjects > 1 ? state.projectId : undefined).pipe(
477
+ const currentProject = projects.find((p) => p.id === state.projectId)
478
+ yield* planSession.start(
479
+ joinedText,
480
+ projects.length > 1 ? state.projectId : undefined,
481
+ projects.length > 1 ? currentProject?.labelFilter : undefined
482
+ ).pipe(
479
483
  Effect.tapError((err) => notifier.sendMessage(`Plan error: ${err.message}`)),
480
484
  Effect.orElseSucceed(() => undefined)
481
485
  )
@@ -25,6 +25,7 @@ export interface GitHubCheckRun {
25
25
  readonly status: string
26
26
  readonly conclusion: string | null
27
27
  readonly html_url: string
28
+ readonly output: { readonly title: string | null; readonly summary: string | null } | null
28
29
  }
29
30
 
30
31
  /**
@@ -242,7 +243,8 @@ export const GitHubClientLive = Layer.effect(
242
243
  name: cr.name,
243
244
  status: cr.status,
244
245
  conclusion: cr.conclusion,
245
- html_url: cr.htmlUrl
246
+ html_url: cr.htmlUrl,
247
+ output: cr.output
246
248
  }))
247
249
  })),
248
250
  Effect.mapError((err) =>
@@ -105,6 +105,7 @@ export interface OctokitCheckRun {
105
105
  readonly status: string
106
106
  readonly conclusion: string | null
107
107
  readonly htmlUrl: string
108
+ readonly output: { readonly title: string | null; readonly summary: string | null } | null
108
109
  }
109
110
 
110
111
  /**
@@ -548,7 +549,8 @@ export const OctokitClientLive = Layer.effect(
548
549
  name: cr.name,
549
550
  status: cr.status,
550
551
  conclusion: cr.conclusion ?? null,
551
- htmlUrl: cr.html_url ?? ""
552
+ htmlUrl: cr.html_url ?? "",
553
+ output: cr.output ? { title: cr.output.title ?? null, summary: cr.output.summary ?? null } : null
552
554
  }))
553
555
  )
554
556
  )
@@ -109,7 +109,11 @@ interface ActiveSession {
109
109
  * @category services
110
110
  */
111
111
  export interface PlanSessionService {
112
- readonly start: (planText: string, projectId?: string | undefined) => Effect.Effect<void, PlanSessionError>
112
+ readonly start: (
113
+ planText: string,
114
+ projectId?: string | undefined,
115
+ labelFilter?: string | undefined
116
+ ) => Effect.Effect<void, PlanSessionError>
113
117
  readonly answer: (text: string) => Effect.Effect<void, PlanSessionError>
114
118
  readonly sendFollowUp: (text: string) => Effect.Effect<void, PlanSessionError>
115
119
  readonly interrupt: (text: string) => Effect.Effect<void, PlanSessionError>
@@ -196,7 +200,7 @@ export const PlanSessionLive = Layer.scoped(
196
200
 
197
201
  yield* Effect.addFinalizer(() => closeActiveSession)
198
202
 
199
- const start = (planText: string, projectId?: string | undefined) =>
203
+ const start = (planText: string, projectId?: string | undefined, labelFilter?: string | undefined) =>
200
204
  Effect.gen(function*() {
201
205
  const current = yield* Ref.get(sessionRef)
202
206
  if (Option.isSome(current)) {
@@ -251,8 +255,9 @@ export const PlanSessionLive = Layer.scoped(
251
255
  if (projectId != null) {
252
256
  const encoder = new TextEncoder()
253
257
  yield* Queue.offer(stdinQueue, encoder.encode(projectId + "\n"))
254
- yield* Effect.log("Pre-answered project prompt").pipe(
255
- Effect.annotateLogs({ projectId })
258
+ yield* Queue.offer(stdinQueue, encoder.encode((labelFilter ?? "") + "\n"))
259
+ yield* Effect.log("Pre-answered project and label prompts").pipe(
260
+ Effect.annotateLogs({ projectId, labelFilter: labelFilter ?? "(empty)" })
256
261
  )
257
262
  }
258
263