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 +5 -5
- package/src/refine-command.test.ts +37 -0
- package/src/refine-command.ts +14 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cueloop",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
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.
|
|
31
|
-
"@cueloop/client": "0.1.0-alpha.
|
|
32
|
-
"@cueloop/daemon": "0.1.0-alpha.
|
|
33
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
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
|
});
|
package/src/refine-command.ts
CHANGED
|
@@ -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
|
|
34
|
-
const fresh = all.filter(
|
|
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")
|
|
51
|
+
if (session.status === "resolved") analyzedState.set(session.id, analysisFingerprint(session));
|
|
49
52
|
}
|
|
50
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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
|
|
231
|
+
function readState(home: string): Map<string, string> {
|
|
229
232
|
try {
|
|
230
|
-
|
|
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
|
|
235
|
+
return new Map();
|
|
235
236
|
}
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
function
|
|
239
|
-
const state
|
|
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
|
}
|