Skip to content

Commit 39c46b0

Browse files
Merge pull request #1 from devweekends/feat/runner-start-approve
Runner: parser, start(), diff/approve/reject, tested with real runs
2 parents 82dae0b + 4363d43 commit 39c46b0

14 files changed

Lines changed: 767 additions & 63 deletions

File tree

‎runner/package.json‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
"prepare": "tsc",
1818
"typecheck": "tsc --noEmit",
1919
"test": "vitest run",
20-
"fake": "tsx scripts/fake.ts"
20+
"fake": "tsx scripts/fake.ts",
21+
"try": "tsx scripts/try.ts"
2122
},
2223
"dependencies": {
23-
"@codehands/contracts": "file:../../codehands-contracts"
24+
"@codehands/contracts": "file:../../codehands-contracts",
25+
"execa": "^10.2.0"
2426
},
2527
"devDependencies": {
2628
"@types/node": "^26.6.5",

‎runner/pnpm-lock.yaml‎

Lines changed: 134 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎runner/scripts/try.ts‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// pnpm try [repoPath] [prompt] [--approve | --reject]: one real run through the runner, one event per line.
2+
// Without a flag it asks approve / reject / keep at the end (or keeps, when not in a terminal).
3+
// Costs real money (capped by budgetUsd below). Run scripts/reset-fixture.sh afterwards.
4+
import { randomUUID } from "node:crypto";
5+
import { homedir } from "node:os";
6+
import { join } from "node:path";
7+
import { createInterface } from "node:readline/promises";
8+
import { createRunner } from "../src/index.js";
9+
10+
const flags = process.argv.slice(2).filter((a) => a.startsWith("--"));
11+
const [repoPath = join(homedir(), "codehands-fixtures", "date-bug"), prompt = "Fix the failing test. Run npm test to check."] =
12+
process.argv.slice(2).filter((a) => !a.startsWith("--"));
13+
14+
const runner = createRunner();
15+
const sessionId = randomUUID();
16+
let finished = false;
17+
for await (const e of runner.start({ sessionId, repoPath, prompt, agent: "claude-code", model: "sonnet", budgetUsd: 0.5 })) {
18+
const p = e.payload;
19+
const detail = p.reason ?? p.command ?? p.path ?? (typeof p.text === "string" ? p.text.slice(0, 80) : "") ?? "";
20+
const extra = e.type === "test.result" ? ` passed=${p.passed}` : e.type === "task.finished" ? ` $${p.total_cost_usd}` : "";
21+
console.log(`${String(e.seq).padStart(3)} ${e.type.padEnd(17)} ${String(detail).replace(/\s+/g, " ")}${extra}`);
22+
if (e.type === "task.finished") finished = true;
23+
}
24+
if (!finished) process.exit(1);
25+
26+
console.log(`\n${(await runner.diff(sessionId)).stat}\n`);
27+
28+
let choice = flags.includes("--approve") ? "a" : flags.includes("--reject") ? "r" : "k";
29+
if (!flags.length && process.stdin.isTTY) {
30+
const rl = createInterface({ input: process.stdin, output: process.stdout });
31+
choice = (await rl.question("approve, reject or keep? [a/r/k] ")).trim().toLowerCase()[0] ?? "k";
32+
rl.close();
33+
}
34+
35+
if (choice === "a") {
36+
const { branch, commit } = await runner.approve(sessionId, prompt);
37+
console.log(`approved: ${branch} @ ${commit.slice(0, 7)} (git log ${branch} --oneline -1)`);
38+
} else if (choice === "r") {
39+
await runner.reject(sessionId);
40+
console.log("rejected: worktree and branch deleted");
41+
} else {
42+
console.log("kept: the worktree is still there, see `git worktree list` in the repo");
43+
}

0 commit comments

Comments
 (0)