3 Commits

Author SHA1 Message Date
temeddix de3ee25627 Install superpowers and use its review skill
Check / deno (pull_request) Successful in 33s
2026-09-13 23:29:46 +09:00
temeddix 3092c0ff6f Add the review guide 2026-09-13 23:29:46 +09:00
temeddix 8c2041bbc5 Split tokens (#1)
The agent and the bot are now two accounts, and the runner is one Deno script.

- `author-token` (was `gitea-token`): commits, pushes, and opens pull requests; the agent sees it as `GITEA_TOKEN`, and commits use that account's login and email.
- `reviewer-token`: posts comments and reviews as the bot; withheld from the agent's environment so it can never approve as the bot.
- A `pull_request` run posts a review instead of a comment: `REQUEST_CHANGES` when the response mentions `@bot`, `COMMENT` when the run failed, `APPROVED` otherwise. Gitea refuses self-approval, so the two accounts must differ.
- The reviewer checks the whole repository against `commons/code-rules`, which is fetched and embedded in the prompt, and requests changes for violations even when the diff did not cause them.
- `run.ts` replaces the three shell scripts plus `jq`, `envsubst`, and `ansifilter`; only `deno` is added to the install step, per the rules' Deno-over-Node policy. A `.gitea` workflow runs `deno fmt`, `lint`, and `check`.

Callers must rename `gitea-token` and add `reviewer-token` (`write:issue` and `write:repository` scopes). A rejection stays until the bot reviews again, so callers that want it lifted after a fix should trigger on `pull_request: [opened, synchronize]`.

Verified with a fake `claude` binary against this PR in an isolated `HOME`: git author configured from the token, prompt rendered with rules and comment history, events streamed, reviewer token absent from the agent's environment, review posted (then deleted).

Reviewed-on: #1
Co-authored-by: Danny Kim <temeddix@gmail.com>
Co-committed-by: Danny Kim <temeddix@gmail.com>
2026-09-13 14:15:23 +00:00
2 changed files with 32 additions and 12 deletions
+11 -9
View File
@@ -13,15 +13,17 @@ response ends, so background monitors, scheduled wake-ups, and queued tasks
never resume. Never promise future action and never claim to be waiting on a never resume. Never promise future action and never claim to be waiting on a
notification. notification.
For a `pull_request` event, review the PR without changing code. Your final For a `pull_request` event, review the PR without changing code, using the
response is posted as a pull request review from the bot account: it requests `requesting-code-review` skill from superpowers: run its code reviewer template
changes when it mentions `@bot` and approves otherwise. So include `@bot` with against the PR's base and head, and make its complete output your final response
instructions to fix the findings exactly when changes are needed, and never instead of the short comment style above. Check the whole repository against the
mention `@bot` when the PR is ready. For UI changes, check that the result is code rules at the end of this prompt, not only the diff; a violation is at least
aligned, clean, and pixel-perfect, and that included screenshots prove the Important even when the diff did not cause it. Your final response is posted as
intended result was achieved. Also check the whole repository, not only the a pull request review from the bot account: it requests changes when it mentions
diff, against the code rules at the end of this prompt, and request changes for `@bot` and approves otherwise, so the assessment instructs `@bot` to make the
every violation you find even when the diff did not cause it. fixes exactly when it is not `Yes`, and Minor issues alone never block. For UI
changes, check that the result is aligned, clean, and pixel-perfect, and that
included screenshots prove the intended result was achieved.
For an `issue_comment` or `pull_request_review_comment` event, treat the `body` For an `issue_comment` or `pull_request_review_comment` event, treat the `body`
in the triggering comment payload below as the user's exact instruction. in the triggering comment payload below as the user's exact instruction.
+21 -3
View File
@@ -68,16 +68,32 @@ async function postResult(body: string): Promise<void> {
}); });
} }
async function run(command: string, args: string[]): Promise<void> {
const { success, code } = await new Deno.Command(command, { args }).output();
if (!success) throw new Error(`${command} ${args[0]} exited with ${code}`);
}
// Commits belong to the same account as the pull request they end up in. // Commits belong to the same account as the pull request they end up in.
async function configureGitAuthor(): Promise<void> { async function configureGitAuthor(): Promise<void> {
const user: GiteaUser = await (await gitea(AUTHOR_TOKEN, "user")).json(); const user: GiteaUser = await (await gitea(AUTHOR_TOKEN, "user")).json();
for (const [key, value] of [["name", user.login], ["email", user.email]]) { for (const [key, value] of [["name", user.login], ["email", user.email]]) {
await new Deno.Command("git", { await run("git", ["config", "--global", `user.${key}`, value]);
args: ["config", "--global", `user.${key}`, value],
}).output();
} }
} }
// The superpowers plugin gives the agent its skills, including the code review
// one that the prompt asks for. Both installs are idempotent on the persisted
// home.
async function installSuperpowers(bot: string): Promise<void> {
const commands = bot === "claude"
? [
["plugin", "marketplace", "add", "obra/superpowers-marketplace"],
["plugin", "install", "-y", "superpowers@superpowers-marketplace"],
]
: [["plugin", "add", "superpowers@openai-curated-remote"]];
for (const args of commands) await run(bot, args);
}
async function renderPrompt(): Promise<string> { async function renderPrompt(): Promise<string> {
const comments: GiteaComment[] = await (await gitea( const comments: GiteaComment[] = await (await gitea(
REVIEWER_TOKEN, REVIEWER_TOKEN,
@@ -111,6 +127,7 @@ async function runClaude(prompt: string): Promise<string> {
); );
Deno.exit(1); Deno.exit(1);
} }
await installSuperpowers("claude");
const claude = new Deno.Command("claude", { const claude = new Deno.Command("claude", {
args: [ args: [
"--print", "--print",
@@ -174,6 +191,7 @@ async function runCodex(prompt: string): Promise<string> {
}) })
.output(); .output();
if (!loggedIn.success) await codexDeviceLogin(); if (!loggedIn.success) await codexDeviceLogin();
await installSuperpowers("codex");
const file = await Deno.makeTempFile(); const file = await Deno.makeTempFile();
const status = await new Deno.Command("codex", { const status = await new Deno.Command("codex", {
args: [ args: [