@fullstackhouse/cerber 0.23.0 → 0.24.1
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/README.md +32 -12
- package/dist/cli/index.js +20 -1
- package/dist/core/artifact.js +11 -0
- package/dist/core/config.js +6 -0
- package/dist/core/notify.js +105 -0
- package/dist/core/refresh.js +40 -13
- package/dist/core/state.js +11 -1
- package/dist/runner/review.js +45 -28
- package/dist/server/daemon.js +60 -3
- package/dist/server/index.js +38 -5
- package/package.json +1 -1
- package/web/dist/assets/{index-D4LYByLi.js → index-DFhsdnD2.js} +59 -59
- package/web/dist/assets/{index-BG0S9CJ-.css → index-VLT71mrB.css} +1 -1
- package/web/dist/index.html +2 -2
package/dist/server/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { DaemonConfigSchema, configPath, loadConfig, saveConfig } from "../core/
|
|
|
15
15
|
import { refreshArtifact } from "../core/refresh.js";
|
|
16
16
|
import { TrustRuleError, describeRule, explainRule, parseTrustRule } from "../core/trust.js";
|
|
17
17
|
import { buildReviewPayload, computeCalibration } from "../core/send.js";
|
|
18
|
-
import { listArtifacts, loadArtifact, loadArtifactByKey,
|
|
18
|
+
import { listArtifacts, loadArtifact, loadArtifactByKey, saveArtifact, updateArtifactByKey, } from "../core/state.js";
|
|
19
19
|
import { isReviewRunning } from "../runner/inflight.js";
|
|
20
20
|
import { reviewPr } from "../runner/review.js";
|
|
21
21
|
import { runChatTurn } from "../runner/chat.js";
|
|
@@ -23,6 +23,20 @@ import { mergeConcurrentEdits, restoreReview } from "../core/revise.js";
|
|
|
23
23
|
import { progressWriter } from "./progress.js";
|
|
24
24
|
import { ChatTurnSchema } from "../core/artifact.js";
|
|
25
25
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
/**
|
|
27
|
+
* The statuses `PATCH /api/reviews/:key` will set: the two that are the user's
|
|
28
|
+
* own decision about a PR. Everything else is a fact some other path owns.
|
|
29
|
+
*/
|
|
30
|
+
const SETTLEABLE = ["reviewed", "skipped"];
|
|
31
|
+
/**
|
|
32
|
+
* Is an AI run rewriting this draft right now?
|
|
33
|
+
*
|
|
34
|
+
* Two questions, because neither alone is enough: `isReviewRunning` is an
|
|
35
|
+
* in-memory claim and cannot see a `cerber review` going in another terminal,
|
|
36
|
+
* while the persisted status cannot see a run this process started moments ago
|
|
37
|
+
* that has not written yet.
|
|
38
|
+
*/
|
|
39
|
+
const inFlight = (a) => a.status === "running" || isReviewRunning(a.id);
|
|
26
40
|
export async function buildApp(opts) {
|
|
27
41
|
const app = new Hono();
|
|
28
42
|
if (opts.token) {
|
|
@@ -251,6 +265,7 @@ export async function buildApp(opts) {
|
|
|
251
265
|
trusted: false,
|
|
252
266
|
sessionId: null,
|
|
253
267
|
trigger: "user",
|
|
268
|
+
reviewedSha: null,
|
|
254
269
|
},
|
|
255
270
|
sent: null,
|
|
256
271
|
refresh: null,
|
|
@@ -284,6 +299,14 @@ export async function buildApp(opts) {
|
|
|
284
299
|
// ---- Mutations (all local — artifact edits only) ----
|
|
285
300
|
app.patch("/api/reviews/:key", async (c) => {
|
|
286
301
|
const body = await c.req.json();
|
|
302
|
+
// Only the two statuses that are the user's own decision. The schema
|
|
303
|
+
// permits all seven, and taking them all meant this endpoint would happily
|
|
304
|
+
// write `status: "sent"` with no `sent` record and nothing submitted —
|
|
305
|
+
// a row that claims a review reached GitHub, which no honest path produces.
|
|
306
|
+
// `running`/`ready`/`failed` belong to the runner, `sent` to the send path.
|
|
307
|
+
if (body.status !== undefined && !SETTLEABLE.includes(body.status)) {
|
|
308
|
+
return c.json({ error: `status must be one of ${SETTLEABLE.join(", ")} — got ${body.status}` }, 400);
|
|
309
|
+
}
|
|
287
310
|
const updated = await updateArtifactByKey(c.req.param("key"), (a) => {
|
|
288
311
|
const next = { ...a };
|
|
289
312
|
if (body.status !== undefined) {
|
|
@@ -429,6 +452,7 @@ export async function buildApp(opts) {
|
|
|
429
452
|
trusted: false,
|
|
430
453
|
sessionId: null,
|
|
431
454
|
trigger: "user",
|
|
455
|
+
reviewedSha: null,
|
|
432
456
|
},
|
|
433
457
|
}));
|
|
434
458
|
// Minutes-long: run it detached and let the cockpit poll the artifact.
|
|
@@ -589,6 +613,13 @@ export async function buildApp(opts) {
|
|
|
589
613
|
if (artifact.sent) {
|
|
590
614
|
return c.json({ error: `already sent at ${artifact.sent.at}` }, 409);
|
|
591
615
|
}
|
|
616
|
+
// Not while a run is rewriting this draft. Vouching for a review that is
|
|
617
|
+
// being replaced under you is reason enough on its own; on top of that the
|
|
618
|
+
// run used to save `sent: null` back over the record when it landed, so the
|
|
619
|
+
// guard above would wave a *second* submission through afterwards.
|
|
620
|
+
if (inFlight(artifact)) {
|
|
621
|
+
return c.json({ error: "a review of this PR is running — wait for it to finish" }, 409);
|
|
622
|
+
}
|
|
592
623
|
const payload = buildReviewPayload(artifact, event);
|
|
593
624
|
try {
|
|
594
625
|
const { url } = await submitReview({ owner: artifact.pr.owner, repo: artifact.pr.repo, number: artifact.pr.number }, { event: payload.event, body: payload.body, comments: payload.comments, commitId: payload.commitId });
|
|
@@ -628,11 +659,13 @@ export async function buildApp(opts) {
|
|
|
628
659
|
}
|
|
629
660
|
return app;
|
|
630
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* Serve the cockpit. `reconcileRunning` is deliberately *not* called here: the
|
|
664
|
+
* caller must have done it before starting the daemon, because the daemon polls
|
|
665
|
+
* as soon as it is constructed. Doing it here as well would have marked that
|
|
666
|
+
* poll's own fresh run as an interrupted leftover.
|
|
667
|
+
*/
|
|
631
668
|
export async function startServer(opts) {
|
|
632
|
-
const cleared = await reconcileRunning();
|
|
633
|
-
if (cleared > 0) {
|
|
634
|
-
console.log(`Cleared ${cleared} AI run(s) left in flight by a previous process — start them again from the cockpit.`);
|
|
635
|
-
}
|
|
636
669
|
const app = await buildApp(opts);
|
|
637
670
|
serve({ fetch: app.fetch, port: opts.port, hostname: opts.host }, (info) => {
|
|
638
671
|
const tokenHint = opts.token ? `/?token=${opts.token}` : "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fullstackhouse/cerber",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "AI code-review cockpit: Claude reviews your PRs into local artifacts; nothing reaches GitHub until you say so.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Full Stack House (https://fullstack.house)",
|