cueloop 0.1.0-alpha.50 → 0.1.0-alpha.51

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.50",
3
+ "version": "0.1.0-alpha.51",
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": {
@@ -27,10 +27,10 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@cueloop/adapters": "0.1.0-alpha.50",
31
- "@cueloop/client": "0.1.0-alpha.50",
32
- "@cueloop/daemon": "0.1.0-alpha.50",
33
- "@cueloop/schema": "0.1.0-alpha.50"
30
+ "@cueloop/adapters": "0.1.0-alpha.51",
31
+ "@cueloop/client": "0.1.0-alpha.51",
32
+ "@cueloop/daemon": "0.1.0-alpha.51",
33
+ "@cueloop/schema": "0.1.0-alpha.51"
34
34
  },
35
35
  "engines": {
36
36
  "bun": ">=1.3.0"
@@ -114,4 +114,41 @@ describe("refineCommand", () => {
114
114
  // Assert
115
115
  expect(readFileSync(reportPath, "utf8")).toContain("1 sessions analyzed (4 total).");
116
116
  });
117
+
118
+ test("re-analyzes a resolved session that is reopened and resolved again with new feedback", async () => {
119
+ // Arrange
120
+ const home = tempHome();
121
+ const store = new SessionStore(home);
122
+ store.upsert(
123
+ session("ses_x", {
124
+ verdict: verdict("approve"),
125
+ status: "resolved",
126
+ annotations: [annotation("first", "one")],
127
+ }),
128
+ );
129
+ const reportPath = join(reportsDir(home), "report.md");
130
+
131
+ // Act
132
+ await refineCommand(["--home", home]);
133
+
134
+ // Assert
135
+ expect(readFileSync(reportPath, "utf8")).toContain("1 sessions analyzed (1 total).");
136
+
137
+ // Arrange
138
+ store.upsert(
139
+ session("ses_x", {
140
+ verdict: { ...verdict("request_changes"), resolvedAt: "2026-08-21T09:00:00Z" },
141
+ status: "resolved",
142
+ annotations: [annotation("first", "one"), annotation("second", "two")],
143
+ }),
144
+ );
145
+
146
+ // Act
147
+ await refineCommand(["--home", home]);
148
+
149
+ // Assert
150
+ const secondReport = readFileSync(reportPath, "utf8");
151
+ expect(secondReport).toContain("1 sessions analyzed (1 total).");
152
+ expect(secondReport).toContain("two");
153
+ });
117
154
  });
@@ -4,6 +4,7 @@ import { cueloopHome, reportsDir } from "@cueloop/daemon/paths";
4
4
  import { SessionStore } from "@cueloop/daemon/store";
5
5
  import {
6
6
  LATEST_REPORT_FILENAME,
7
+ parseRefineState,
7
8
  pruneExpiredReports,
8
9
  resolveCleanupPeriodDays,
9
10
  timestampedReportFilename,
@@ -30,8 +31,10 @@ export async function refineCommand(argv: string[]): Promise<number> {
30
31
  store.recover();
31
32
  const all = store.list();
32
33
 
33
- const seen = readSeen(home);
34
- const fresh = all.filter((session) => !seen.has(session.id));
34
+ const analyzedState = readState(home);
35
+ const fresh = all.filter(
36
+ (session) => analyzedState.get(session.id) !== analysisFingerprint(session),
37
+ );
35
38
  const analyzed = fresh.filter(hasReviewSignal).slice(0, limit);
36
39
 
37
40
  const markdown = buildRefineReport(analyzed, all.length, new Date(nowMs).toISOString());
@@ -45,9 +48,9 @@ export async function refineCommand(argv: string[]): Promise<number> {
45
48
  writeFileSync(timestampedPath, markdown);
46
49
 
47
50
  for (const session of analyzed) {
48
- if (session.status === "resolved") seen.add(session.id);
51
+ if (session.status === "resolved") analyzedState.set(session.id, analysisFingerprint(session));
49
52
  }
50
- writeSeen(home, seen);
53
+ writeState(home, analyzedState);
51
54
 
52
55
  console.log(
53
56
  JSON.stringify(
@@ -217,25 +220,23 @@ function parseLimit(raw: string | undefined): number {
217
220
  return Number.isInteger(value) && value > 0 ? value : DEFAULT_SESSION_LIMIT;
218
221
  }
219
222
 
220
- interface RefineState {
221
- seenSessionIds: string[];
223
+ function analysisFingerprint(session: ReviewSession): string {
224
+ return `${session.revisions.length}:${session.annotations.length}:${session.verdict?.resolvedAt ?? "pending"}`;
222
225
  }
223
226
 
224
227
  function statePath(home: string): string {
225
228
  return join(home, "refine-state.json");
226
229
  }
227
230
 
228
- function readSeen(home: string): Set<string> {
231
+ function readState(home: string): Map<string, string> {
229
232
  try {
230
- const parsed = JSON.parse(readFileSync(statePath(home), "utf8")) as Partial<RefineState>;
231
- const ids = Array.isArray(parsed.seenSessionIds) ? parsed.seenSessionIds : [];
232
- return new Set(ids.filter((id): id is string => typeof id === "string"));
233
+ return parseRefineState(JSON.parse(readFileSync(statePath(home), "utf8")));
233
234
  } catch {
234
- return new Set();
235
+ return new Map();
235
236
  }
236
237
  }
237
238
 
238
- function writeSeen(home: string, seen: Set<string>): void {
239
- const state: RefineState = { seenSessionIds: [...seen] };
239
+ function writeState(home: string, analyzed: Map<string, string>): void {
240
+ const state = { analyzed: Object.fromEntries(analyzed) };
240
241
  writeFileSync(statePath(home), JSON.stringify(state, null, 2));
241
242
  }