cueloop 0.1.0-alpha.69 → 0.1.0-alpha.70

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cueloop",
3
- "version": "0.1.0-alpha.69",
3
+ "version": "0.1.0-alpha.70",
4
4
  "description": "Terminal review surface for coding agents: annotate plans and diffs, return a verdict, send structured feedback back",
5
5
  "homepage": "https://github.com/mmurakaru/cueloop#readme",
6
6
  "bugs": {
@@ -30,10 +30,10 @@
30
30
  "build:binary": "bun build --compile --external cpu-features ./src/main.ts --outfile dist/cueloop"
31
31
  },
32
32
  "dependencies": {
33
- "@cueloop/adapters": "0.1.0-alpha.69",
34
- "@cueloop/client": "0.1.0-alpha.69",
35
- "@cueloop/daemon": "0.1.0-alpha.69",
36
- "@cueloop/schema": "0.1.0-alpha.69",
33
+ "@cueloop/adapters": "0.1.0-alpha.70",
34
+ "@cueloop/client": "0.1.0-alpha.70",
35
+ "@cueloop/daemon": "0.1.0-alpha.70",
36
+ "@cueloop/schema": "0.1.0-alpha.70",
37
37
  "valibot": "^1.4.2"
38
38
  },
39
39
  "engines": {
package/src/main.ts CHANGED
@@ -24,7 +24,7 @@ import { sessionCommand } from "./session-commands";
24
24
  import { CLI_VERSION } from "./version";
25
25
  import { DaemonClient } from "@cueloop/daemon/client";
26
26
  import { workingTreeDiff } from "@cueloop/daemon/working-tree";
27
- import type { ReviewSession } from "@cueloop/schema";
27
+ import type { Thread } from "@cueloop/schema";
28
28
  import { openReview, resolveWorkspace } from "@cueloop/daemon/review";
29
29
 
30
30
  const argv = process.argv.slice(2);
@@ -162,13 +162,13 @@ function openSelector(parsed: ParsedArgs): string | undefined {
162
162
  * one caller that needs a scope-specific hint (a clean working tree).
163
163
  */
164
164
  async function openReviewOfKind(
165
- match: (session: ReviewSession) => boolean,
165
+ match: (session: Thread) => boolean,
166
166
  label: string,
167
167
  selector: string | undefined,
168
168
  emptyMessage?: string,
169
169
  ): Promise<number> {
170
170
  const client = await DaemonClient.connect({ autostart: true });
171
- let sessions: ReviewSession[];
171
+ let sessions: Thread[];
172
172
 
173
173
  try {
174
174
  sessions = await client.sessionList();
@@ -1,7 +1,7 @@
1
1
  /** Primitive-first open resolution over a plain session list: latest-pending default, by-id, and fuzzy-by-title (exact wins, unique substring wins, else ambiguous). Pure over the array - no daemon, no TUI. */
2
2
 
3
3
  import { describe, expect, test } from "bun:test";
4
- import type { ArtifactType, ReviewSession } from "@cueloop/schema";
4
+ import type { ArtifactType, Thread } from "@cueloop/schema";
5
5
  import {
6
6
  isDiffReview,
7
7
  isPlanReview,
@@ -20,7 +20,7 @@ function session(overrides: {
20
20
  status?: "pending" | "resolved";
21
21
  createdAt?: string;
22
22
  pr?: string;
23
- }): ReviewSession {
23
+ }): Thread {
24
24
  return {
25
25
  schemaVersion: "1",
26
26
  id: overrides.id,
@@ -16,7 +16,7 @@
16
16
  * ever resolves to its own artifact kind.
17
17
  */
18
18
 
19
- import type { ReviewSession } from "@cueloop/schema";
19
+ import type { Thread } from "@cueloop/schema";
20
20
 
21
21
  /**
22
22
  * The primitive scopes, kept disjoint on purpose so a single session never resolves
@@ -25,19 +25,19 @@ import type { ReviewSession } from "@cueloop/schema";
25
25
  * diff scope must exclude those - otherwise `cueloop diff` and `cueloop
26
26
  * review` would both open the same pending PR review.
27
27
  */
28
- export function isPlanReview(session: ReviewSession): boolean {
28
+ export function isPlanReview(session: Thread): boolean {
29
29
  return session.artifact.type === "plan";
30
30
  }
31
- export function isReplyReview(session: ReviewSession): boolean {
31
+ export function isReplyReview(session: Thread): boolean {
32
32
  return session.artifact.type === "reply";
33
33
  }
34
- export function isPrototypeReview(session: ReviewSession): boolean {
34
+ export function isPrototypeReview(session: Thread): boolean {
35
35
  return session.artifact.type === "prototype";
36
36
  }
37
- export function isDiffReview(session: ReviewSession): boolean {
37
+ export function isDiffReview(session: Thread): boolean {
38
38
  return session.artifact.type === "diff" && session.artifact.meta.pr === undefined;
39
39
  }
40
- export function isPrReview(session: ReviewSession): boolean {
40
+ export function isPrReview(session: Thread): boolean {
41
41
  return session.artifact.type === "diff" && session.artifact.meta.pr !== undefined;
42
42
  }
43
43
 
@@ -48,7 +48,7 @@ export function isSessionId(value: string): boolean {
48
48
 
49
49
  export interface OpenTargetQuery {
50
50
  /** Only sessions this predicate accepts are eligible - the primitive's artifact scope. */
51
- match: (session: ReviewSession) => boolean;
51
+ match: (session: Thread) => boolean;
52
52
  /** Positional id-or-title selector; absent means "latest pending". */
53
53
  selector?: string;
54
54
  }
@@ -60,11 +60,11 @@ export type OpenTarget =
60
60
  | { kind: "ambiguous"; selector: string; titles: string[] };
61
61
 
62
62
  /** Most-recent-first, so the head of a sorted list is always the latest. */
63
- function newestFirst(left: ReviewSession, right: ReviewSession): number {
63
+ function newestFirst(left: Thread, right: Thread): number {
64
64
  return right.createdAt.localeCompare(left.createdAt);
65
65
  }
66
66
 
67
- export function resolveOpenTarget(sessions: ReviewSession[], query: OpenTargetQuery): OpenTarget {
67
+ export function resolveOpenTarget(sessions: Thread[], query: OpenTargetQuery): OpenTarget {
68
68
  const scoped = sessions.filter(query.match);
69
69
 
70
70
  if (query.selector === undefined) {
package/src/pr.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * half for agents and scripts.
7
7
  */
8
8
 
9
- import type { ReviewSession, VerdictKind } from "@cueloop/schema";
9
+ import type { Thread, VerdictKind } from "@cueloop/schema";
10
10
  import { DaemonClient } from "@cueloop/daemon/client";
11
11
  import { openReview } from "@cueloop/daemon/review";
12
12
  import { parseArgs } from "./args";
@@ -116,7 +116,7 @@ export async function reviewPostCommand(argv: string[]): Promise<number> {
116
116
  return postVerdict(session, pr);
117
117
  }
118
118
 
119
- async function getSession(id: string): Promise<ReviewSession> {
119
+ async function getSession(id: string): Promise<Thread> {
120
120
  const client = await DaemonClient.connect({ autostart: true });
121
121
 
122
122
  try {
@@ -127,7 +127,7 @@ async function getSession(id: string): Promise<ReviewSession> {
127
127
  }
128
128
 
129
129
  /** Post the resolved session's verdict to the PR: feedback.md is the review body. */
130
- async function postVerdict(session: ReviewSession, pr: string): Promise<number> {
130
+ async function postVerdict(session: Thread, pr: string): Promise<number> {
131
131
  const verdict = session.verdict!;
132
132
  const result = await gh([
133
133
  "pr",
@@ -4,8 +4,8 @@ import { describe, expect, test } from "bun:test";
4
4
  import { mkdtempSync, readFileSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
- import type { Annotation, ReviewSession, Verdict, VerdictKind } from "@cueloop/schema";
8
- import { SessionStore } from "@cueloop/daemon/store";
7
+ import type { Annotation, Thread, Verdict, VerdictKind } from "@cueloop/schema";
8
+ import { ThreadStore } from "@cueloop/daemon/store";
9
9
  import { reportsDir } from "@cueloop/daemon/paths";
10
10
  import { buildRefineReport, refineCommand } from "./refine-command";
11
11
 
@@ -29,8 +29,8 @@ function verdict(kind: VerdictKind): Verdict {
29
29
 
30
30
  function session(
31
31
  id: string,
32
- overrides: Partial<ReviewSession> & { type?: ReviewSession["artifact"]["type"] } = {},
33
- ): ReviewSession {
32
+ overrides: Partial<Thread> & { type?: Thread["artifact"]["type"] } = {},
33
+ ): Thread {
34
34
  const { type, ...rest } = overrides;
35
35
 
36
36
  return {
@@ -87,7 +87,7 @@ describe("refineCommand", () => {
87
87
  test("writes a report, skips signal-free sessions, and consumes only resolved sessions", async () => {
88
88
  // Arrange
89
89
  const home = tempHome();
90
- const store = new SessionStore(home);
90
+ const store = new ThreadStore(home);
91
91
 
92
92
  store.upsert(session("ses_a", { verdict: verdict("approve"), status: "resolved" }));
93
93
  store.upsert(
@@ -120,7 +120,7 @@ describe("refineCommand", () => {
120
120
  test("re-analyzes a resolved session that is reopened and resolved again with new feedback", async () => {
121
121
  // Arrange
122
122
  const home = tempHome();
123
- const store = new SessionStore(home);
123
+ const store = new ThreadStore(home);
124
124
 
125
125
  store.upsert(
126
126
  session("ses_x", {
@@ -1,7 +1,7 @@
1
1
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { cueloopHome, reportsDir } from "@cueloop/daemon/paths";
4
- import { SessionStore } from "@cueloop/daemon/store";
4
+ import { ThreadStore } from "@cueloop/daemon/store";
5
5
  import {
6
6
  LATEST_REPORT_FILENAME,
7
7
  parseRefineState,
@@ -9,7 +9,7 @@ import {
9
9
  resolveCleanupPeriodDays,
10
10
  timestampedReportFilename,
11
11
  } from "@cueloop/daemon/retention";
12
- import { isAgentNote, type Annotation, type ReviewSession } from "@cueloop/schema";
12
+ import { isAgentNote, type Annotation, type Thread } from "@cueloop/schema";
13
13
  import { parseArgs, stringFlag } from "./args";
14
14
 
15
15
  const DEFAULT_SESSION_LIMIT = 200;
@@ -18,7 +18,7 @@ const BODY_LIMIT = 100;
18
18
 
19
19
  interface AnnotatedEntry {
20
20
  annotation: Annotation;
21
- session: ReviewSession;
21
+ session: Thread;
22
22
  }
23
23
 
24
24
  export async function refineCommand(argv: string[]): Promise<number> {
@@ -27,7 +27,7 @@ export async function refineCommand(argv: string[]): Promise<number> {
27
27
  const limit = parseLimit(stringFlag(flags, "limit"));
28
28
  const nowMs = Date.now();
29
29
 
30
- const store = new SessionStore(home);
30
+ const store = new ThreadStore(home);
31
31
 
32
32
  store.recover();
33
33
  const all = store.list();
@@ -72,7 +72,7 @@ export async function refineCommand(argv: string[]): Promise<number> {
72
72
  }
73
73
 
74
74
  export function buildRefineReport(
75
- analyzed: ReviewSession[],
75
+ analyzed: Thread[],
76
76
  totalCount: number,
77
77
  generatedAt: string,
78
78
  ): string {
@@ -137,13 +137,13 @@ export function buildRefineReport(
137
137
  return lines.join("\n");
138
138
  }
139
139
 
140
- function hasReviewSignal(session: ReviewSession): boolean {
140
+ function hasReviewSignal(session: Thread): boolean {
141
141
  return (
142
142
  session.verdict !== null || session.annotations.some((annotation) => !isAgentNote(annotation))
143
143
  );
144
144
  }
145
145
 
146
- function flattenReviewAnnotations(sessions: ReviewSession[]): AnnotatedEntry[] {
146
+ function flattenReviewAnnotations(sessions: Thread[]): AnnotatedEntry[] {
147
147
  const entries: AnnotatedEntry[] = [];
148
148
 
149
149
  for (const session of sessions) {
@@ -194,13 +194,13 @@ function tally<T>(items: T[], keyOf: (item: T) => string): Map<string, number> {
194
194
  return counts;
195
195
  }
196
196
 
197
- function primitiveLabel(session: ReviewSession): string {
197
+ function primitiveLabel(session: Thread): string {
198
198
  if (session.artifact.type === "diff" && session.artifact.meta.pr) return "pull request";
199
199
 
200
200
  return session.artifact.type;
201
201
  }
202
202
 
203
- function verdictLabel(session: ReviewSession): string {
203
+ function verdictLabel(session: Thread): string {
204
204
  switch (session.verdict?.kind) {
205
205
  case "approve":
206
206
  return "approve";
@@ -239,7 +239,7 @@ function parseLimit(raw: string | undefined): number {
239
239
  return Number.isInteger(value) && value > 0 ? value : DEFAULT_SESSION_LIMIT;
240
240
  }
241
241
 
242
- function analysisFingerprint(session: ReviewSession): string {
242
+ function analysisFingerprint(session: Thread): string {
243
243
  return `${session.revisions.length}:${session.annotations.length}:${session.verdict?.resolvedAt ?? "pending"}`;
244
244
  }
245
245
 
@@ -4,12 +4,12 @@ import {
4
4
  historyFromLinear,
5
5
  SCHEMA_VERSION,
6
6
  type Annotation,
7
- type ReviewSession,
7
+ type Thread,
8
8
  } from "@cueloop/schema";
9
9
  import type { SessionClient } from "@cueloop/daemon/client";
10
10
  import { pullSession, shareSession, type PullDeps, type ShareDeps } from "./share-command";
11
11
 
12
- function sessionFixture(id: string, overrides: Partial<ReviewSession> = {}): ReviewSession {
12
+ function sessionFixture(id: string, overrides: Partial<Thread> = {}): Thread {
13
13
  return {
14
14
  schemaVersion: SCHEMA_VERSION,
15
15
  id,
@@ -40,12 +40,13 @@ const unimplemented = (member: string) => () =>
40
40
  Promise.reject(new Error(`fakeClient does not implement ${member}`));
41
41
 
42
42
  /** A SessionClient that answers get/list from a fixed list and records the share/merge primitives. */
43
- function fakeClient(sessions: ReviewSession[]): SessionClient {
43
+ function fakeClient(sessions: Thread[]): SessionClient {
44
44
  return {
45
45
  onEvent: () => () => {},
46
46
  subscribe: async () => {},
47
47
  sessionGet: async (id: string) => sessions.find((session) => session.id === id)!,
48
48
  sessionList: async () => sessions,
49
+ sessionComment: unimplemented("sessionComment"),
49
50
  sessionAnnotate: unimplemented("sessionAnnotate"),
50
51
  sessionRemoveAnnotation: unimplemented("sessionRemoveAnnotation"),
51
52
  sessionSetWorkingCopy: unimplemented("sessionSetWorkingCopy"),
@@ -113,7 +114,7 @@ describe(shareSession, () => {
113
114
  test("publishes the named session and reports the copied ssh line", async () => {
114
115
  // Arrange
115
116
  const publish = mock(
116
- async (session: ReviewSession) => (
117
+ async (session: Thread) => (
117
118
  expect(session.id).toBe("ses_2"),
118
119
  { line: "ssh p_abc123xy@cueloop.dev", copied: true }
119
120
  ),
@@ -135,7 +136,7 @@ describe(shareSession, () => {
135
136
  test("without an id, shares the most recent session", async () => {
136
137
  // Arrange
137
138
  const publish = mock(
138
- async (session: ReviewSession) => (
139
+ async (session: Thread) => (
139
140
  expect(session.id).toBe("ses_2"),
140
141
  { line: "ssh p_zzzzzzzz@cueloop.dev", copied: true }
141
142
  ),
@@ -211,7 +212,7 @@ describe(shareSession, () => {
211
212
  });
212
213
  });
213
214
 
214
- function pullDepsSpy(remote: ReviewSession): PullDeps & { lines: string[] } {
215
+ function pullDepsSpy(remote: Thread): PullDeps & { lines: string[] } {
215
216
  const lines: string[] = [];
216
217
 
217
218
  return {
@@ -15,7 +15,7 @@ import {
15
15
  type ShareResult,
16
16
  type ShareTarget,
17
17
  } from "@cueloop/client";
18
- import type { ReviewSession } from "@cueloop/schema";
18
+ import type { Thread } from "@cueloop/schema";
19
19
 
20
20
  export interface ShareParams {
21
21
  sessionId?: string;
@@ -27,12 +27,12 @@ export interface ShareParams {
27
27
  }
28
28
 
29
29
  export interface ShareDeps {
30
- publish: (session: ReviewSession, target: ShareTarget) => Promise<ShareResult>;
30
+ publish: (session: Thread, target: ShareTarget) => Promise<ShareResult>;
31
31
  out: (message: string) => void;
32
32
  }
33
33
 
34
34
  export interface PullDeps {
35
- pull: (shareId: string, target: ShareTarget) => Promise<ReviewSession>;
35
+ pull: (shareId: string, target: ShareTarget) => Promise<Thread>;
36
36
  out: (message: string) => void;
37
37
  }
38
38
 
@@ -122,10 +122,7 @@ export async function pullSession(
122
122
  }
123
123
 
124
124
  /** The named session, or the most recent one when no id is given. */
125
- async function pickSession(
126
- client: SessionClient,
127
- sessionId?: string,
128
- ): Promise<ReviewSession | null> {
125
+ async function pickSession(client: SessionClient, sessionId?: string): Promise<Thread | null> {
129
126
  if (sessionId) return client.sessionGet(sessionId);
130
127
  const sessions = await client.sessionList();
131
128
 
@@ -136,7 +133,7 @@ async function pickSession(
136
133
  async function pickSharedSession(
137
134
  client: SessionClient,
138
135
  sessionId?: string,
139
- ): Promise<ReviewSession | null> {
136
+ ): Promise<Thread | null> {
140
137
  if (sessionId) {
141
138
  const session = await client.sessionGet(sessionId);
142
139