From 5a1a5656ac70eba86feebc6249ac932cb4e0a1bd Mon Sep 17 00:00:00 2001 From: Danny Kim Date: Wed, 16 Sep 2026 13:26:01 +0900 Subject: [PATCH] Restore Superpowers reviews --- .gitea/workflows/check.yml | 6 +++++- prompt.md | 43 ++++++++++++++++++++++---------------- run.ts | 18 +++++++++++++++- superpowers.ts | 42 +++++++++++++++++++++++++++++++++++++ superpowers_test.ts | 40 +++++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 superpowers.ts create mode 100644 superpowers_test.ts diff --git a/.gitea/workflows/check.yml b/.gitea/workflows/check.yml index 293c973..d950876 100644 --- a/.gitea/workflows/check.yml +++ b/.gitea/workflows/check.yml @@ -14,4 +14,8 @@ jobs: - uses: denoland/setup-deno@v2 - - run: deno fmt --check . && deno lint . && deno check run.ts + - run: >- + deno fmt --check . && + deno lint . && + deno check run.ts && + deno test diff --git a/prompt.md b/prompt.md index 8983ab1..d084f77 100644 --- a/prompt.md +++ b/prompt.md @@ -19,24 +19,31 @@ author's push credentials. Read the code there, run its checks and tests when they bear on the task, and push from there. For a `pull_request` event, and for a comment on a pull request that asks you to -review it, review the PR without changing code against its base and head. Run -the project's checks on the head and treat a failure as at least Important. -Check the whole repository against the code rules at the end of this prompt, not -only the diff; a violation is at least Important even when the diff did not -cause it. Write the complete review, and nothing else, to the file -`${REVIEW_PATH}`: it is posted verbatim as a pull request review from the bot -account, and your final response is not posted at all. The file's first line -must be exactly the verdict and nothing else: `Approved` when the head is ready -to merge, `Changes requested` otherwise. The mark in front of it is added when -posting, so write the words alone; any other first line is posted as a plain -comment, which wastes the run. Minor issues alone never block, and neither does -a finding the author has answered in the comment history below as intended or a -false alarm, once the code or docs make that clear. When the verdict is -`Changes requested`, the second line names what must change in one line, -addressed to the author; the author's own agent picks the fixes up, so never ask -`@bot` to make them. For UI changes, check that the result is aligned, clean, -and pixel-perfect, and that included screenshots prove the intended result was -achieved. +review it, invoke the installed `superpowers:requesting-code-review` skill +before inspecting the PR. You are the reviewer that has already been dispatched, +so run the skill's code reviewer template yourself instead of dispatching +another reviewer. Use the pull request and triggering instruction as its +description and requirements, and review the exact base and head SHAs without +changing code. Run the project's required checks on the head and treat a real +failure as at least Important. The bot automation workflow itself is not a +project check: ignore its skipped or canceled duplicate/automatic runs, and +never reject a PR because the current review run is unfinished. Only a failed +required check for the reviewed head blocks approval. Check the whole repository +against the code rules at the end of this prompt, not only the diff; a violation +is at least Important even when the diff did not cause it. Write the complete +review, and nothing else, to the file `${REVIEW_PATH}`: it is posted verbatim as +a pull request review from the bot account, and your final response is not +posted at all. The file's first line must be exactly the verdict and nothing +else: `Approved` when the head is ready to merge, `Changes requested` otherwise. +The mark in front of it is added when posting, so write the words alone; any +other first line is posted as a plain comment, which wastes the run. Minor +issues alone never block, and neither does a finding the author has answered in +the comment history below as intended or a false alarm, once the code or docs +make that clear. When the verdict is `Changes requested`, the second line names +what must change in one line, addressed to the author; the author's own agent +picks the fixes up, so never ask `@bot` to make them. For UI changes, check that +the result is aligned, clean, and pixel-perfect, and that included screenshots +prove the intended result was achieved. Every finding that belongs to one line of the diff goes on that line instead of into the body. Write those to `${ANCHORS_PATH}` as a JSON array, each entry diff --git a/run.ts b/run.ts index cc60a93..06d3f66 100644 --- a/run.ts +++ b/run.ts @@ -4,6 +4,11 @@ // so it appears as the bot account. import { TextLineStream } from "jsr:@std/streams@1/text-line-stream"; import { retryInvalidToken } from "./auth.ts"; +import { + type BotType, + parseBotType, + superpowersInstallCommands, +} from "./superpowers.ts"; type GiteaUser = { login: string; email: string }; type GiteaComment = { user: GiteaUser; created_at: string; body: string }; @@ -18,6 +23,7 @@ const API = env("GITEA_API_URL"); const REPO = env("GITEA_REPOSITORY"); const INDEX = env("ISSUE_INDEX"); const EVENT = env("EVENT_NAME"); +const BOT = parseBotType(env("BOT_TYPE")); const AUTHOR_TOKEN = env("GITEA_TOKEN"); const REVIEWER_TOKEN = env("REVIEWER_TOKEN"); const RULES_PATH = "repos/commons/code-rules/raw/README.md"; @@ -146,6 +152,14 @@ async function run(command: string, args: string[]): Promise { if (!success) throw new Error(`${command} ${args[0]} exited with ${code}`); } +// The reviewer uses Superpowers' requesting-code-review template. Both plugin +// installers are idempotent on the persisted bot home. +async function installSuperpowers(bot: BotType): Promise { + for (const { command, args } of superpowersInstallCommands(bot)) { + await run(command, args); + } +} + // Commits belong to the same account as the pull request they end up in. async function configureGitAuthor(): Promise { const user: GiteaUser = await (await gitea(AUTHOR_TOKEN, "user")).json(); @@ -188,6 +202,7 @@ async function runClaude(prompt: string): Promise { "Run `claude setup-token` locally and set the `bot-token` action input.", ); } + await installSuperpowers("claude"); const claude = new Deno.Command("claude", { args: [ "--print", @@ -257,6 +272,7 @@ async function runCodex(prompt: string): Promise { }) .output(); if (!loggedIn.success) await codexDeviceLogin(); + await installSuperpowers("codex"); const file = await Deno.makeTempFile(); const attempt = async () => { const codex = new Deno.Command("codex", { @@ -294,7 +310,7 @@ async function runCodex(prompt: string): Promise { try { await configureGitAuthor(); const prompt = await renderPrompt(); - const result = env("BOT_TYPE") === "claude" + const result = BOT === "claude" ? await runClaude(prompt) : await runCodex(prompt); await postResult(result); diff --git a/superpowers.ts b/superpowers.ts new file mode 100644 index 0000000..4093bc3 --- /dev/null +++ b/superpowers.ts @@ -0,0 +1,42 @@ +export type BotType = "claude" | "codex"; + +export type InstallCommand = { + command: string; + args: string[]; +}; + +export function parseBotType(value: string): BotType { + if (value === "claude" || value === "codex") return value; + throw new Error(`unsupported bot type: ${value}`); +} + +export function superpowersInstallCommands( + bot: BotType, +): InstallCommand[] { + if (bot === "claude") { + return [ + { + command: "claude", + args: [ + "plugin", + "marketplace", + "add", + "obra/superpowers-marketplace", + ], + }, + { + command: "claude", + args: [ + "plugin", + "install", + "-y", + "superpowers@superpowers-marketplace", + ], + }, + ]; + } + return [{ + command: "codex", + args: ["plugin", "add", "superpowers@openai-curated-remote"], + }]; +} diff --git a/superpowers_test.ts b/superpowers_test.ts new file mode 100644 index 0000000..b6c679c --- /dev/null +++ b/superpowers_test.ts @@ -0,0 +1,40 @@ +import { assertEquals, assertThrows } from "jsr:@std/assert@1"; +import { parseBotType, superpowersInstallCommands } from "./superpowers.ts"; + +Deno.test("installs Superpowers from the Codex marketplace", () => { + assertEquals(superpowersInstallCommands("codex"), [{ + command: "codex", + args: ["plugin", "add", "superpowers@openai-curated-remote"], + }]); +}); + +Deno.test("installs Superpowers from the Claude marketplace", () => { + assertEquals(superpowersInstallCommands("claude"), [ + { + command: "claude", + args: [ + "plugin", + "marketplace", + "add", + "obra/superpowers-marketplace", + ], + }, + { + command: "claude", + args: [ + "plugin", + "install", + "-y", + "superpowers@superpowers-marketplace", + ], + }, + ]); +}); + +Deno.test("rejects an unsupported bot type", () => { + assertThrows( + () => parseBotType("other"), + Error, + "unsupported bot type: other", + ); +}); -- 2.52.0