Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "genesis-tools",
"version": "1.0.114",
"version": "1.0.116",
"description": "Plugins for GenesisTools CLI development and management",
"owner": {
"name": "genesiscz",
Expand All @@ -11,7 +11,7 @@
{
"name": "genesis-tools",
"description": "Skills and utilities for working with GenesisTools CLI toolkit. Provides guidance for discovering, executing, and troubleshooting genesis tools with integrated workflow support.",
"version": "1.0.114",
"version": "1.0.116",
"author": {
"name": "GenesisTools"
},
Expand Down
17 changes: 12 additions & 5 deletions .claude/skills/babysit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ Write it to `/tmp/cc/<project>/<session>/babysit/run.json` at the start; the scr
| Field | Default | Notes |
|---|---|---|
| `prs` | asked | PR numbers. Small PRs first; never a PR another session owns (step 3.1). |
| `worker` per PR | `grok` | `grok` (model `grok-4.7-build-fast`, `--auth subscription`) or `codex` (model `gpt-6.1-sol`, effort `medium`, `--write ask`). Mix freely; one worker per PR. |
| `worker` per PR | `grok` | `grok` (model `grok-4.7-build-fast`, `--auth subscription`) or `codex` (model `gpt-6.1-sol`, `--write ask`). Mix freely; one worker per PR. |
| `effort` per PR | from the work | Codex `--effort`: `low`, `medium`, `high`, `xhigh` or `max`. A fix round: `medium`, `high` for a Critical or a cross-file finding. An independent review (step 7a): no effort; `--mode review` picks high or xhigh from the diff size and gates the depth (handoff-to, "Effort is the review's depth"). |
| `mode` | from `scripts/mode.ts` | `worktree` or `unified`, step 2. |
| `maxParallel` | 3 | Workers at once. The master is the bottleneck, not the workers. |
| `post` | ask | Post the round summary and replies after the push (review-loop step 4), or hold them in the vault. |
Expand Down Expand Up @@ -175,7 +176,7 @@ summary go to `.claude/work/babysit/` inside that cwd (ignored by git; Grok's ja

- Grok: `tools grok spawn --name bs-<N>-grok --cwd <cwd> -m grok-4.7-build-fast --auth subscription
--label "babysit pr<N>" --prompt-file <brief>` with Bash `run_in_background` (it blocks for the whole turn).
- Codex: `tools codex spawn --name bs-<N>-sol --model gpt-6.1-sol --effort medium --write ask --cwd <cwd> --session
- Codex: `tools codex spawn --name bs-<N>-sol --model gpt-6.1-sol --effort <run card effort> --write ask --cwd <cwd> --session
<bus session> --label "babysit pr<N>" --prompt-file <brief>`.

The `--label` tags every cost-ledger row of the session, so a run's spend is one query later (step 9).
Expand Down Expand Up @@ -321,9 +322,15 @@ A round counts only when it fixed or answered real findings. The round policy (s
rounds: after two rounds with no Critical or High, switch to merge prep (review-loop "Round policy"), and land by the
merge bar. When the user asks for a dedicated review of a PR, before or after its merge, run an independent read-only
review: `bun
.claude/skills/babysit/scripts/review-brief.ts --run <dir> --pr <N> --wt <worktree> [--post-merge] [--swift]`, then
`steer.ts` with the printed file to a worker that did NOT fix this PR (Sol; Astra only for a risky cross-system PR,
by the model rule in step 1). Judge its findings like a bot's. Before a merge they become the next fix round (`fill-brief --judge <review
.claude/skills/babysit/scripts/review-brief.ts --run <dir> --pr <N> --wt <worktree> [--post-merge] [--swift]`. It
prints the brief path and then the spawn line: `tools codex spawn --mode review --base origin/<PR base>` (after a
merge `--base <merge commit>^ --paths <the PR's files>`) with the brief as `--prompt-file`, for a worker that did NOT
fix this PR (Sol; Astra only for a risky cross-system PR, by the model rule in step 1). Leave out `--effort`: the
harness picks high or xhigh from the diff size, gives the worker the manifest and the tier's contract, and gates the
depth after the turn (one steer with the unread files, then one rerun a tier higher as `bs-<N>-review-<tier>`, then
`shallow`). Read the result with `tools codex review-depth bs-<N>-review`; a `shallow` verdict is not a finished
review. Every run of the round is one command away: `tools codex reviews --pr <N>` (or `--label babysit`). Judge its
findings like a bot's. Before a merge they become the next fix round (`fill-brief --judge <review
file> --dedicated-review`); after a merge they go to a follow-up PR from master (2026-10-14: #518 carried #509's after-merge findings).
Never count a reply-only round, or a label, as a fix round: on 2026-10-14 the report said "#500 merged after 3
rounds" when GitHub showed two round summaries.
Expand Down
82 changes: 72 additions & 10 deletions .claude/skills/babysit/scripts/review-brief.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
/**
* The brief for an independent read-only review round, which runs when the user asks for one (SKILL.md 7a): on an
* open PR, or on a PR that already merged. Its findings become the next fix round (`fill-brief --judge`)
* or, after a merge, a follow-up PR. Writes `<run>/steer-review-<pr>.md` and prints the path for steer.ts.
* or, after a merge, a follow-up PR. Writes `<run>/steer-review-<pr>.md`, prints its path, then the
* `tools codex spawn --mode review` line that starts the reviewer with it.
*
* bun review-brief.ts --run <dir> --pr 517 --wt <worktree> [--base master] [--post-merge] [--swift] [--fresh]
*
* The depth rules (read every file, probe each finding, the coverage line) are not in this brief: `--mode review`
* gives the worker its tier's contract and the diff manifest, and gates the depth after the turn
* (`tools codex reviews --pr <N>` lists every run).
*
* `--fresh`: a PR opened minutes ago, reviewed next to the bots (the first round of a newly cut PR).
*
* Hand-edited copies of this text sent a reviewer to another PR's description (2026-10-14: a sed pass left
Expand All @@ -14,6 +19,7 @@

import { join } from "node:path";
import { SafeJSON } from "@genesiscz/utils/json";
import { shellWord } from "@genesiscz/utils/shell/quote";
import { flag, has, loadRun, reviewScratch } from "./run";

export interface ReviewBriefInput {
Expand Down Expand Up @@ -46,20 +52,55 @@ export function reviewBrief({ pr, title, wt, base, scratch, postMerge, swift, fr
fresh
? `- The PR has no answered threads yet. Do not wait for the bots and do not read their threads: review the code on your own.`
: `- Earlier review threads were already answered; read them with \`tools github review ${pr} --llm\` so you do not report a finding that was fixed or deliberately declined (a declined one is fair game only with new evidence).`,
`- Check every claim: run the relevant tests (\`timeout 600 bun scripts/test.ts <test files>\`) or a small read-only probe under \`${scratch}\`, and paste the real output. A finding without evidence is a guess; say so.`,
`- Tests in this repository run as \`timeout 600 bun scripts/test.ts <test files>\`; put any read-only probe under \`${scratch}\`.`,
...(swift
? [
"- Swift: do not run `swift build` or `swift test` (the lead runs them); a Swift claim needs a code quote as evidence.",
]
: []),
`- Write \`${join(wt, ".claude/work/babysit", `${pr}-review.md`)}\` (create the folder): one section per finding with severity (Critical, High, Medium, Low), file:line, the defect, the evidence, why it matters, and the smallest fix. End with a one-line verdict.`,
`- Write \`${join(wt, ".claude/work/babysit", `${pr}-review.md`)}\` (create the folder): the same numbered findings, \`Ran\`, \`Files reviewed\` and \`Not reviewed\` lines as your final message, with the evidence under each finding, then a one-line verdict.`,
"- Change NO source file, commit nothing, post nothing to GitHub. Do NOT run `git checkout`, `git switch`, `bun install` or the hook installer. No app builds, installs, launches or relaunches. Another reviewer may read this same folder, so write only your own review file.",
"",
"Then stop with a report: the finding count by severity and the review file path, then RESULT / AT / CHANGED / VERIFY / OPEN.",
];
return `${lines.join("\n")}\n`;
}

export interface ReviewSpawnInput {
pr: number;
wt: string;
/** The PR's base branch, without `origin/`. */
base: string;
briefPath: string;
/** After a merge the worktree sits at master: the manifest is the PR's files since its merge commit's parent. */
postMerge?: { mergeCommit: string; files: string[] };
}

/**
* The reviewer's spawn line: `--mode review` with the base, so the harness computes the manifest and gates the depth.
* Every value is one shell word: a file name, branch or path from the repository may hold a space or `$(...)`, and
* the lead runs this line as printed.
*/
export function reviewSpawnCommand({ pr, wt, base, briefPath, postMerge }: ReviewSpawnInput): string {
const target = postMerge
? ["--base", `${postMerge.mergeCommit}^`, "--paths", ...postMerge.files]
Comment thread
genesiscz marked this conversation as resolved.
: ["--base", `origin/${base}`];
const argv = [
...["tools", "codex", "spawn", "--mode", "review", "--pr", String(pr), ...target],
...["--name", `bs-${pr}-review`, "--model", "gpt-6.1-sol", "--write", "ask", "--cwd", wt],
...["--label", `babysit pr${pr} review`, "--prompt-file", briefPath],
];

return argv.map(shellWord).join(" ");
}

interface PrView {
title: string;
baseRefName?: string;
mergeCommit?: { oid?: string } | null;
files?: Array<{ path: string }>;
}

if (import.meta.main) {
const run = loadRun();
const pr = Number(flag("--pr"));
Expand All @@ -72,30 +113,51 @@ if (import.meta.main) {
process.exit(2);
}

const view = Bun.spawnSync(["gh", "pr", "view", String(pr), "--repo", run.repo, "--json", "title"], {
stdout: "pipe",
stderr: "pipe",
});
const view = Bun.spawnSync(
["gh", "pr", "view", String(pr), "--repo", run.repo, "--json", "title,baseRefName,mergeCommit,files"],
{ stdout: "pipe", stderr: "pipe" }
);

if (view.exitCode !== 0) {
console.error(`✖ gh pr view ${pr}: ${view.stderr.toString().trim()}`);
process.exit(1);
}

const { title } = SafeJSON.parse(view.stdout.toString(), { strict: true }) as { title: string };
const { title, baseRefName, mergeCommit, files } = SafeJSON.parse(view.stdout.toString(), {
strict: true,
}) as PrView;
const base = flag("--base") ?? baseRefName ?? "master";
const postMerge = has("--post-merge");
const out = join(run.dir, `steer-review-${pr}.md`);
await Bun.write(
out,
reviewBrief({
pr,
title,
wt,
base: flag("--base") ?? "master",
base,
scratch: reviewScratch(run.dir, pr),
postMerge: has("--post-merge"),
postMerge,
swift: has("--swift"),
fresh: has("--fresh"),
})
);
console.log(out);

if (postMerge && !mergeCommit?.oid) {
console.error(`✖ PR #${pr} has no merge commit, so a post-merge review has no base.`);
process.exit(1);
}

console.log(
reviewSpawnCommand({
pr,
wt,
base,
briefPath: out,
...(postMerge && mergeCommit?.oid
? { postMerge: { mergeCommit: mergeCommit.oid, files: (files ?? []).map((file) => file.path) } }
: {}),
})
);
}
55 changes: 54 additions & 1 deletion .claude/skills/babysit/scripts/scripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { appendEvent, readEvents, renderMarkdown } from "./ledger";
import { continueVersionPick } from "./pick-unified";
import { summarize, testEvidence } from "./plant";
import { hitsInAddedLines, probeAddedLines } from "./probe";
import { reviewBrief } from "./review-brief";
import { reviewBrief, reviewSpawnCommand } from "./review-brief";
import { loadRun, runPaths } from "./run";
import { trySwiftSlot } from "./swift-slot";
import { parseSwiftTargets, typecheckPackage } from "./swift-typecheck";
Expand Down Expand Up @@ -656,6 +656,59 @@ describe("independent review brief", () => {
expect(fresh).not.toContain("already answered");
expect(reviewBrief(input)).toContain("found nothing more");
});

test("the reviewer starts in --mode review against the PR base, and the brief leaves depth to its contract", () => {
const input = {
pr: 498,
title: "t",
wt: "/w",
base: "feat/parent",
scratch: "/s",
postMerge: false,
swift: false,
};
expect(reviewBrief(input)).not.toContain("A finding without evidence is a guess");
expect(reviewBrief(input)).toContain("`Files reviewed` and `Not reviewed` lines");

expect(
reviewSpawnCommand({ pr: 498, wt: "/w", base: "feat/parent", briefPath: "/run/steer-review-498.md" })
).toBe(
"tools codex spawn --mode review --pr 498 --base origin/feat/parent --name bs-498-review --model gpt-6.1-sol --write ask --cwd /w --label 'babysit pr498 review' --prompt-file /run/steer-review-498.md"
);
expect(
reviewSpawnCommand({
pr: 498,
wt: "/w",
base: "master",
briefPath: "/b.md",
postMerge: { mergeCommit: "abc123", files: ["src/a.ts", "src/b.ts"] },
})
).toContain("--mode review --pr 498 --base 'abc123^' --paths src/a.ts src/b.ts --name bs-498-review");
});

test("every value of the spawn line stays one literal shell word: spaces and $(...) in names do not split or run", () => {
const command = reviewSpawnCommand({
pr: 498,
wt: "/tmp/my wt",
base: "feat/$(id)",
briefPath: "/run/brief file.md",
postMerge: { mergeCommit: "abc123", files: ["src/my file.ts", "$(touch /tmp/review-probe).ts"] },
});
expect(command).toContain("--paths 'src/my file.ts' '$(touch /tmp/review-probe).ts' --name");
expect(command).toContain("--cwd '/tmp/my wt'");
expect(command).toContain("--prompt-file '/run/brief file.md'");

// The shell itself reads it back as the intended argv, without running the substitution.
const parsed = Bun.spawnSync(["sh", "-c", `printf '%s\\n' ${command}`], { env: process.env, stdout: "pipe" });
const argv = parsed.stdout.toString().trimEnd().split("\n");
expect(argv).toContain("src/my file.ts");
expect(argv).toContain("$(touch /tmp/review-probe).ts");
expect(argv).toContain("/tmp/my wt");
expect(argv[argv.indexOf("--label") + 1]).toBe("babysit pr498 review");
expect(reviewSpawnCommand({ pr: 498, wt: "/w", base: "feat/$(id)", briefPath: "/b.md" })).toContain(
"--base 'origin/feat/$(id)'"
);
});
});
describe("planted-regression summary", () => {
test("reads bun counts and the XCTest total, and says so when there is none", () => {
Expand Down
2 changes: 1 addition & 1 deletion plugins/genesis-tools/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "genesis-tools",
"version": "1.0.114",
"version": "1.0.116",
"description": "Skills and utilities for working with GenesisTools CLI toolkit. Provides guidance for discovering, executing, and troubleshooting genesis tools with integrated workflow support.",
"author": {
"name": "GenesisTools"
Expand Down
18 changes: 17 additions & 1 deletion plugins/genesis-tools/skills/handoff-to/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,26 @@ Set a model explicitly; an Astra parent should not make every worker Astra.
|---|---|---|
| Mechanical extraction, formatting, simple lookups, or a fully specified small edit | `gpt-6-luna` | low |
| Bounded repository exploration, tracing an established call path, or gathering evidence | `gpt-6-luna` | medium |
| Normal implementation, reproducible debugging, and code review | `gpt-6.1-sol` | medium; high for complex reviews |
| Normal implementation and reproducible debugging | `gpt-6.1-sol` | medium |
| Code review of a PR or branch | `gpt-6.1-sol` | `--mode review --base <ref>`: high up to 15 files and 800 lines, xhigh above |
| Difficult cross-system reasoning, ambiguous failures, or an evidence-backed escalation | `gpt-6-astra` | high |

`gpt-6.1-sol` accepts `low` to `max` effort only; `none` and `minimal` return an error, as on Astra.

**Effort is the review's depth: spawn every Codex review with `--mode review --base <ref>`.** Measured on
2026-10-22 with nine Sol reviews at `medium`: 1 to 4 minutes each, 8 to 26 tool calls, and 358 to 2,399
reasoning tokens, also for a 125-file, 6,200-line PR. The findings were real, but no worker could have read
every file. `--mode review` now does the depth work the brief used to ask for: it computes the manifest
(`git diff --numstat <base>...HEAD`), gives the worker its tier's review contract (read every hunk, callers and
tests, probes, the `Files reviewed: n of N` line), picks the tier from the diff size when `--effort` is omitted
(high up to about 15 files and 800 lines, xhigh above; `max` only when you name it for a risky cross-system PR;
`low` and `medium` need `--allow-shallow`), and proposes a split by folder above about 60 files (`--paths`).
After each turn it gates the depth (coverage against the manifest, reasoning tokens per 100 changed lines,
finding shape): a miss steers the same session once with the unread files, a second miss reruns one tier higher
in a new session (`<name>-<tier>`), and after that the review is `shallow`, never done. The brief names the
milestone, the PR and the negative constraints; it no longer restates depth rules. See every review run with
`tools codex reviews [--since 24h] [--pr <n>] [--label <text>] [--verdict shallow]`, and one in depth with
`tools codex review-depth <name>`.
Astra is the escalation tier, not the default for exploration. Keep the cheaper worker's
findings and failed verification when escalating. If a model is unavailable, disclose the
fallback and select the nearest suitable tier instead of silently choosing Astra.
Expand Down
Loading
Loading