@fullstackhouse/cerber 0.31.0 → 0.32.0

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 CHANGED
@@ -23,6 +23,16 @@ That gate is the whole point, and the reason this is not another review bot:
23
23
 
24
24
  Named after Cerberus, the gatekeeper: cerber guards what gets merged.
25
25
 
26
+ **The whole loop in seventeen seconds** — five PRs already drafted when you sit
27
+ down, the one that isn't an approval, straight to its blocker, arguing with the
28
+ reviewer until it rewrites its own finding, dropping the comment you disagree
29
+ with, and sending what's left as your review:
30
+
31
+ ![The cockpit: an inbox of five drafted reviews; opening the one with a blocker; jumping to the finding through the walkthrough rail; asking the reviewer whether the bug is real and watching it revise its own comment; dropping a second comment; then sending the review to GitHub.](docs/demo.gif)
32
+
33
+ Same recording in full colour, if the GIF's palette is fighting the code:
34
+ [`docs/demo.mp4`](docs/demo.mp4).
35
+
26
36
  **A drafted review, as you read it** — verdict and confidence beside the PR
27
37
  title, the walkthrough rail on the left, a summary written for someone who has
28
38
  not read the diff:
package/dist/cli/index.js CHANGED
@@ -29,8 +29,8 @@ const { version } = createRequire(import.meta.url)("../../package.json");
29
29
  * poll. `allowDegraded` is for `serve --no-poll`, which is still useful for
30
30
  * reading artifacts already on disk.
31
31
  */
32
- async function requireTools(allowDegraded = false) {
33
- const checks = await preflight();
32
+ async function requireTools(allowDegraded = false, opts = {}) {
33
+ const checks = await preflight(opts);
34
34
  if (!checks.some(isBlocking))
35
35
  return;
36
36
  const message = formatBlockers(checks);
@@ -58,7 +58,8 @@ program
58
58
  .option("-t, --trust", "let the review run commands in the checkout (tests, typecheck, git log) whatever the trust config says")
59
59
  .option("--no-trust", "keep the review read-only even if a trust rule matches")
60
60
  .action(async (prs, opts) => {
61
- await requireTools();
61
+ // `--no-source` never clones the PR head, so a missing `git` is not its problem.
62
+ await requireTools(false, { git: opts.source });
62
63
  let refs;
63
64
  if (opts.awaitingMe) {
64
65
  const found = await searchAwaitingMe(opts.repo);
@@ -384,6 +385,8 @@ program
384
385
  for (const c of checks) {
385
386
  if (c.status === "ok") {
386
387
  console.log(` ✔ ${c.name.padEnd(22)} ${c.detail ?? ""}`.trimEnd());
388
+ if (c.note)
389
+ console.log(` ${c.note}`);
387
390
  }
388
391
  else {
389
392
  console.log(` ✗ ${c.name.padEnd(22)} ${c.status === "missing" ? "not installed" : "not logged in"}`);
@@ -431,7 +434,7 @@ program
431
434
  }
432
435
  // Without `gh` the cockpit binds, polls into an error loop and shows an
433
436
  // empty queue; `--no-poll` is the one shape that still works offline.
434
- await requireTools(!opts.poll);
437
+ await requireTools(!opts.poll, { git: opts.source });
435
438
  const threshold = Math.min(100, Math.max(50, Number(opts.autoSendThreshold) || 90));
436
439
  if (opts.autoSend) {
437
440
  console.log(`⚠ auto-send is ON: APPROVE verdicts with confidence ≥ ${threshold}% will be sent to GitHub as you, without a per-review confirmation.\n` +
@@ -54,7 +54,17 @@ export async function checkClaude() {
54
54
  fix: "Run `claude` once and log in — a review is drafted through your Claude Code session.",
55
55
  };
56
56
  }
57
- return { name: "claude (Claude Code)", status: "ok", detail: version.out.split("\n")[0] };
57
+ // `claude --version` answers whether or not anyone is logged in, and the only
58
+ // honest way to test the session is to spend a turn on it — too expensive for
59
+ // something that runs before every review. So the check says what it knows
60
+ // and names where the real answer shows up, rather than implying it verified
61
+ // a login it never looked at.
62
+ return {
63
+ name: "claude (Claude Code)",
64
+ status: "ok",
65
+ detail: version.out.split("\n")[0],
66
+ note: "installed; login not checked — a logged-out CLI fails on the first review, with claude's own message",
67
+ };
58
68
  }
59
69
  export async function checkGit() {
60
70
  const version = await run("git", ["--version"]);
@@ -67,9 +77,16 @@ export async function checkGit() {
67
77
  }
68
78
  return { name: "git", status: "ok", detail: version.out.split("\n")[0] };
69
79
  }
70
- /** Everything cerber shells out to, checked in parallel. */
71
- export async function preflight() {
72
- return Promise.all([checkGh(), checkClaude(), checkGit()]);
80
+ /**
81
+ * Everything cerber shells out to for this command, checked in parallel.
82
+ *
83
+ * `git` is only reached when a run clones the PR head, so `--no-source` must
84
+ * not be blocked by its absence: that mode reviews the diff alone and never
85
+ * touches a checkout.
86
+ */
87
+ export async function preflight(opts = {}) {
88
+ const needsGit = opts.git ?? true;
89
+ return Promise.all([checkGh(), checkClaude(), ...(needsGit ? [checkGit()] : [])]);
73
90
  }
74
91
  /** The blocking checks, formatted as the message printed instead of failing later. */
75
92
  export function formatBlockers(checks) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullstackhouse/cerber",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
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
  "keywords": [