mirror of
https://github.com/obra/superpowers.git
synced 2026-06-10 20:59:05 +08:00
Compare commits
45 Commits
codex/expl
...
sdd-review
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cf78437e2 | ||
|
|
e355795625 | ||
|
|
29ee4e8e44 | ||
|
|
28498a5cde | ||
|
|
5e2907fc4f | ||
|
|
e532f24df7 | ||
|
|
e3c74fc1c9 | ||
|
|
3e3e1e701e | ||
|
|
853396e3ae | ||
|
|
83d54f7ddd | ||
|
|
c7900f1698 | ||
|
|
5cfdb75b94 | ||
|
|
87825ff193 | ||
|
|
09cb4d7361 | ||
|
|
b3bb9a68d7 | ||
|
|
71dc271a08 | ||
|
|
5aea3dca31 | ||
|
|
b3281c0227 | ||
|
|
c14c1de552 | ||
|
|
be8a6269c4 | ||
|
|
da41209243 | ||
|
|
2cc449b6d4 | ||
|
|
f8dcd1ed3d | ||
|
|
4192572d19 | ||
|
|
5da15d7eba | ||
|
|
f55642e0dd | ||
|
|
ae1eefb7f9 | ||
|
|
617168aff5 | ||
|
|
d7c260a978 | ||
|
|
f3f0789c5c | ||
|
|
16a1719988 | ||
|
|
c74c22daa7 | ||
|
|
773bbf61d6 | ||
|
|
6b76158550 | ||
|
|
7fec40bb55 | ||
|
|
2a8e54735b | ||
|
|
f776394360 | ||
|
|
7301c81b4d | ||
|
|
9d3e68a5ad | ||
|
|
81c3052416 | ||
|
|
c879454a0d | ||
|
|
ff213eb2cf | ||
|
|
da00e59958 | ||
|
|
deceaec78d | ||
|
|
e63e44bedf |
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -12,14 +12,17 @@ add a comment or reaction to the existing one instead.
|
||||
|
||||
- [ ] I searched existing issues and this is not a duplicate
|
||||
|
||||
## Environment
|
||||
## Environment (required)
|
||||
<!-- Required. We assume an agent filed this report — tell us which one and
|
||||
where it ran. We weigh reports by what produced them. -->
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Superpowers version | |
|
||||
| Harness (Claude Code, Cursor, etc.) | |
|
||||
| Harness version | |
|
||||
| Model | |
|
||||
| Your model + version | |
|
||||
| All plugins installed | |
|
||||
| OS + shell | |
|
||||
|
||||
## Is this a Superpowers issue or a platform issue?
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -30,5 +30,18 @@ progress, and some were intentionally declined.
|
||||
of project? If this is specific to your domain, workflow, or a
|
||||
third-party tool, it may belong as its own plugin instead. -->
|
||||
|
||||
## Environment (required)
|
||||
<!-- Required. We assume an agent wrote this request — tell us which one and
|
||||
where it ran. We weigh proposals reasoned from documentation differently
|
||||
than ones grounded in a real session where the problem actually came up. -->
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Superpowers version | |
|
||||
| Harness (Claude Code, Cursor, etc.) | |
|
||||
| Harness version | |
|
||||
| Your model + version | |
|
||||
| All plugins installed | |
|
||||
|
||||
## Context
|
||||
<!-- Optional: version info, harness, model, workflow where you hit this. -->
|
||||
<!-- Optional: the workflow where you hit this, links, transcripts. -->
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/platform_support.md
vendored
11
.github/ISSUE_TEMPLATE/platform_support.md
vendored
@@ -21,3 +21,14 @@ requested or discussed.
|
||||
## Have you tried manual installation?
|
||||
<!-- Many tools work with Superpowers through manual setup even without
|
||||
official support. Did you try? What happened? -->
|
||||
|
||||
## Environment (required)
|
||||
<!-- Required. We assume an agent wrote this request — tell us which one and
|
||||
where it ran. -->
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Harness you currently use (Claude Code, Cursor, etc.) | |
|
||||
| Harness version | |
|
||||
| Your model + version | |
|
||||
| All plugins installed | |
|
||||
|
||||
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -4,6 +4,23 @@ sections blank, contain multiple unrelated changes, or show no evidence
|
||||
of human involvement will be closed without review.
|
||||
-->
|
||||
|
||||
> **This PR MUST target the `dev` branch, not `main`.** `main` is the
|
||||
> released branch; active work lands on `dev` first. PRs opened against
|
||||
> `main` will be asked to retarget `dev` before review.
|
||||
|
||||
## Who is submitting this PR? (required)
|
||||
<!-- Required. PRs that omit this will be closed. We assume an agent wrote
|
||||
this PR — tell us which one and where it ran. We weigh contributions by
|
||||
what produced them: content reasoned from documentation is held to a
|
||||
different bar than work grounded in a real session. -->
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Your model + version | |
|
||||
| Harness + version | |
|
||||
| All plugins installed | |
|
||||
| Human partner who reviewed this diff | |
|
||||
|
||||
## What problem are you trying to solve?
|
||||
<!-- Describe the specific problem you encountered. If this was a session
|
||||
issue, include: what you were doing, what went wrong, the model's
|
||||
|
||||
38
.kimi-plugin/plugin.json
Normal file
38
.kimi-plugin/plugin.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "5.1.0",
|
||||
"description": "An agentic skills framework and software development methodology.",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
},
|
||||
"homepage": "https://github.com/obra/superpowers",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"brainstorming",
|
||||
"subagent-driven-development",
|
||||
"skills",
|
||||
"planning",
|
||||
"tdd",
|
||||
"debugging",
|
||||
"code-review",
|
||||
"workflow"
|
||||
],
|
||||
"skills": "./skills/",
|
||||
"sessionStart": {
|
||||
"skill": "using-superpowers"
|
||||
},
|
||||
"skillInstructions": "Kimi Code tool mapping for Superpowers skills:\n\n- When a Superpowers skill says to ask the user, ask clarifying questions, ask one question at a time, present multiple-choice options, use the terminal for a question, or wait for the user's choice, call Kimi Code's `AskUserQuestion` tool. Do not render those choices as plain assistant text unless `AskUserQuestion` is unavailable or the session is in auto permission mode.\n- For `AskUserQuestion`, provide 1 question with 2-4 concrete options when possible. Put the recommended option first and suffix its label with `(Recommended)`.\n- When a Superpowers skill refers to `TodoWrite`, use Kimi Code's `TodoList` tool.\n- When a Superpowers skill says `Task tool (general-purpose)` or asks you to dispatch an implementer/reviewer subagent, use Kimi Code's `Agent` tool with a Kimi subagent type. Do not pass `general-purpose` as `subagent_type`.\n- For implementation, code review, spec review, quality review, and filled Superpowers subagent prompt templates, call `Agent` with `subagent_type: \"coder\"`, paste the fully filled prompt into `prompt`, and provide a short `description`.\n- For read-only codebase exploration that would take several searches, use `Agent` with `subagent_type: \"explore\"`.\n- For read-only planning or architecture design, use `Agent` with `subagent_type: \"plan\"`.\n- Keep dependent Superpowers subagent steps sequential. Use multiple `Agent` calls, or `run_in_background: true` only when the work is independent and background agents are available.\n- When a Superpowers skill refers to the `Skill` tool, use Kimi Code's native `Skill` tool.\n- Use Kimi Code's `Read`, `Write`, `Edit`, `Bash`, `Grep`, `Glob`, `FetchURL`, `WebSearch`, and MCP tools by their actual exposed names.\n- When a skill asks to search file contents, use `Grep`; when it asks to find files by path or pattern, use `Glob`; when it asks to fetch a URL, use `FetchURL`; when it asks to search the web, use `WebSearch`.",
|
||||
"interface": {
|
||||
"displayName": "Superpowers",
|
||||
"shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents",
|
||||
"longDescription": "Use Superpowers to guide agent work through brainstorming, implementation planning, test-driven development, systematic debugging, parallel execution, code review, and finish-the-branch workflows.",
|
||||
"developerName": "Jesse Vincent",
|
||||
"capabilities": [
|
||||
"Interactive",
|
||||
"Read",
|
||||
"Write"
|
||||
],
|
||||
"websiteURL": "https://github.com/obra/superpowers"
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
{ "path": ".claude-plugin/plugin.json", "field": "version" },
|
||||
{ "path": ".cursor-plugin/plugin.json", "field": "version" },
|
||||
{ "path": ".codex-plugin/plugin.json", "field": "version" },
|
||||
{ "path": ".kimi-plugin/plugin.json", "field": "version" },
|
||||
{ "path": ".claude-plugin/marketplace.json", "field": "plugins.0.version" },
|
||||
{ "path": "gemini-extension.json", "field": "version" }
|
||||
],
|
||||
|
||||
@@ -14,7 +14,8 @@ Before you open a PR against this repo, you MUST:
|
||||
2. **Search for existing PRs** — open AND closed — that address the same problem. If duplicates exist, STOP and tell your human partner. Do not open another duplicate.
|
||||
3. **Verify this is a real problem.** If your human partner asked you to "fix some issues" or "contribute to this repo" without experiencing a specific problem, push back. Ask them what broke, what failed, what the user experience was.
|
||||
4. **Confirm the change belongs in core.** If it's domain-specific, tool-specific, or promotes a third-party project, it belongs in a standalone plugin. Tell your human partner.
|
||||
5. **Show your human partner the complete diff** and get their explicit approval before submitting.
|
||||
5. **Identify yourself.** Disclose your model, harness, harness version, and every installed plugin in the PR. Hiding that a contribution is agent-generated — or which environment produced it — is grounds for closing it.
|
||||
6. **Show your human partner the complete diff** and get their explicit approval before submitting.
|
||||
|
||||
If any of these checks fail, do not open the PR. Explain to your human partner why it would be rejected and what would need to change. They will thank you for saving them the embarrassment.
|
||||
|
||||
@@ -26,6 +27,10 @@ If any of these checks fail, do not open the PR. Explain to your human partner w
|
||||
|
||||
**PRs that show no evidence of human involvement will be closed.** A human must review the complete proposed diff before submission.
|
||||
|
||||
**Submitters MUST identify themselves.** Every PR and issue must disclose the model, harness, harness version, and all installed plugins used to produce the contribution — or state plainly that it was written by hand with no agent. This is not optional. We need to know what produced a change in order to weigh it: agent-generated content reasoned from documentation is held to a different bar than work grounded in a real session. Contributions that hide their authoring environment will be closed.
|
||||
|
||||
**All PRs MUST target the `dev` branch, not `main`.** `main` is the released branch; active work lands on `dev` first. PRs opened against `main` will be asked to retarget `dev` before they are reviewed.
|
||||
|
||||
## What We Will Not Accept
|
||||
|
||||
### Third-party dependencies
|
||||
|
||||
33
README.md
33
README.md
@@ -4,7 +4,7 @@ Superpowers is a complete software development methodology for your coding agent
|
||||
|
||||
## Quickstart
|
||||
|
||||
Give your agent Superpowers: [Claude Code](#claude-code), [Codex App](#codex-app), [Codex CLI](#codex-cli), [Cursor](#cursor), [Factory Droid](#factory-droid), [Gemini CLI](#gemini-cli), [GitHub Copilot CLI](#github-copilot-cli), [OpenCode](#opencode), [Pi](#pi).
|
||||
Give your agent Superpowers: [Claude Code](#claude-code), [Antigravity](#antigravity), [Codex App](#codex-app), [Codex CLI](#codex-cli), [Cursor](#cursor), [Factory Droid](#factory-droid), [Gemini CLI](#gemini-cli), [GitHub Copilot CLI](#github-copilot-cli), [Kimi Code](#kimi-code), [OpenCode](#opencode), [Pi](#pi).
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -60,6 +60,17 @@ The Superpowers marketplace provides Superpowers and some other related plugins
|
||||
/plugin install superpowers@superpowers-marketplace
|
||||
```
|
||||
|
||||
### Antigravity
|
||||
|
||||
Install Superpowers as a plugin from this repository:
|
||||
|
||||
```bash
|
||||
agy plugin install https://github.com/obra/superpowers
|
||||
```
|
||||
|
||||
Antigravity runs the plugin's session-start hook, so Superpowers is active from
|
||||
the first message. Reinstall with the same command to update.
|
||||
|
||||
### Codex App
|
||||
|
||||
Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins).
|
||||
@@ -138,6 +149,26 @@ Superpowers is available via the [official Codex plugin marketplace](https://git
|
||||
copilot plugin install superpowers@superpowers-marketplace
|
||||
```
|
||||
|
||||
### Kimi Code
|
||||
|
||||
Superpowers is available in Kimi Code's plugin marketplace.
|
||||
|
||||
- Open Kimi Code's plugin manager:
|
||||
|
||||
```text
|
||||
/plugins
|
||||
```
|
||||
|
||||
- Go to `Marketplace` > `Superpowers` and install it.
|
||||
|
||||
- Or install directly from this repository:
|
||||
|
||||
```text
|
||||
/plugins install https://github.com/obra/superpowers
|
||||
```
|
||||
|
||||
- Detailed docs: [docs/README.kimi.md](docs/README.kimi.md)
|
||||
|
||||
### OpenCode
|
||||
|
||||
OpenCode uses its own plugin install; install Superpowers separately even if you
|
||||
|
||||
88
docs/README.kimi.md
Normal file
88
docs/README.kimi.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Superpowers for Kimi Code
|
||||
|
||||
Complete guide for using Superpowers with [Kimi Code](https://github.com/MoonshotAI/kimi-code).
|
||||
|
||||
## Installation
|
||||
|
||||
Superpowers is available in Kimi Code's plugin marketplace.
|
||||
|
||||
Open the plugin manager:
|
||||
|
||||
```text
|
||||
/plugins
|
||||
```
|
||||
|
||||
Go to `Marketplace` > `Superpowers` and install it.
|
||||
|
||||
You can also install from this repository:
|
||||
|
||||
```text
|
||||
/plugins install https://github.com/obra/superpowers
|
||||
```
|
||||
|
||||
For unreleased validation against `dev`, pin the branch explicitly:
|
||||
|
||||
```text
|
||||
/plugins install https://github.com/obra/superpowers/tree/dev
|
||||
```
|
||||
|
||||
Kimi Code applies plugin changes to new sessions. After installing, updating, enabling, disabling, or reloading a plugin, start a fresh session with `/new`.
|
||||
|
||||
## How It Works
|
||||
|
||||
The Kimi plugin manifest lives at `.kimi-plugin/plugin.json`.
|
||||
|
||||
The manifest does three things:
|
||||
|
||||
1. Points Kimi Code at the existing `skills/` directory.
|
||||
2. Loads `using-superpowers` at session start through `sessionStart.skill`.
|
||||
3. Provides Kimi-specific tool mapping through `skillInstructions`.
|
||||
|
||||
Kimi Code reads Superpowers skills from this repository. There are no copied skills, symlinks, hooks, or extra runtime dependencies.
|
||||
|
||||
## Tool Mapping
|
||||
|
||||
Skills describe actions instead of hard-coding one runtime's tool names. On Kimi Code these resolve to:
|
||||
|
||||
- "Ask the user" / "ask clarifying questions" -> `AskUserQuestion`
|
||||
- "Create a todo" / "mark complete in todo list" -> `TodoList`
|
||||
- "Dispatch a subagent" -> `Agent`
|
||||
- "Invoke a skill" -> Kimi Code's native `Skill` tool
|
||||
- "Read a file" / "write a file" / "edit a file" -> `Read`, `Write`, `Edit`
|
||||
- "Run a shell command" -> `Bash`
|
||||
- "Search file contents" -> `Grep`
|
||||
- "Find files by path or pattern" -> `Glob`
|
||||
- "Fetch a URL" -> `FetchURL`
|
||||
- "Search the web" -> `WebSearch`
|
||||
|
||||
## Updating
|
||||
|
||||
Use Kimi Code's plugin manager:
|
||||
|
||||
```text
|
||||
/plugins
|
||||
```
|
||||
|
||||
Select Superpowers and update it from there. Start a fresh session with `/new` after updating.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin not loading
|
||||
|
||||
1. Run `/plugins info superpowers` and check diagnostics.
|
||||
2. Make sure the plugin is enabled.
|
||||
3. Start a fresh session with `/new` after install or update.
|
||||
|
||||
### Direct GitHub install used an old release
|
||||
|
||||
Kimi Code installs the latest GitHub release for a bare repository URL when one exists. To test unreleased changes before the next Superpowers release, install the branch explicitly:
|
||||
|
||||
```text
|
||||
/plugins install https://github.com/obra/superpowers/tree/dev
|
||||
```
|
||||
|
||||
### Skills not triggering
|
||||
|
||||
1. Confirm `/plugins info superpowers` shows the plugin enabled.
|
||||
2. Start a fresh session with `/new`.
|
||||
3. Try the acceptance prompt: `Let's make a react todo list`. A working install should load `brainstorming` before writing code.
|
||||
826
docs/porting-to-a-new-harness.md
Normal file
826
docs/porting-to-a-new-harness.md
Normal file
@@ -0,0 +1,826 @@
|
||||
# Porting Superpowers to a New Harness
|
||||
|
||||
This guide explains how to add support for a new harness — an IDE, CLI, or
|
||||
agent runner that isn't Claude Code — so that Superpowers skills auto-trigger
|
||||
there the same way they do natively.
|
||||
|
||||
It is written in two layers. **Part 1–3** explain how the system works and how
|
||||
to tell whether a harness can be supported at all; read these before you touch
|
||||
anything. **Part 4–8** are a prescriptive procedure for an agent (supervised by
|
||||
a human partner) to execute the port end to end, through distribution. An
|
||||
appendix indexes the current reference integrations so you can copy the closest
|
||||
one.
|
||||
|
||||
The integration mechanism differs across harnesses, and it will keep changing.
|
||||
This guide deliberately teaches the **invariants** — the things that must be
|
||||
true no matter the mechanism — and points you at a live reference implementation
|
||||
to copy. When this guide and the code disagree, the code wins; fix the guide.
|
||||
|
||||
## Before you start
|
||||
|
||||
Adding a harness is the highest-stakes contribution type in this repo. Before
|
||||
writing anything:
|
||||
|
||||
- Read `CLAUDE.md` and `.github/PULL_REQUEST_TEMPLATE.md` in full — the
|
||||
contributor rules and the new-harness PR requirements are not optional.
|
||||
- Search open **and closed** PRs for a prior attempt at this harness. If one
|
||||
exists, understand why it stalled before starting your own.
|
||||
|
||||
---
|
||||
|
||||
## Part 1 — How Superpowers works across harnesses
|
||||
|
||||
Superpowers is the same content everywhere. What changes per harness is the thin
|
||||
layer that delivers that content to the model and translates its instructions
|
||||
into the harness's native tools. Three components:
|
||||
|
||||
1. **Skills (harness-agnostic).** Everything in `skills/` is the source of
|
||||
truth, shared verbatim by every harness. Skills are written to describe
|
||||
*actions* — "invoke a skill", "read a file", "dispatch a subagent", "create a
|
||||
todo" — and never name a specific tool. This is what lets one skill body run
|
||||
on Claude Code, Codex, Gemini, pi, and the rest without edits.
|
||||
|
||||
2. **Tool mapping (per-harness).** Each harness needs the action vocabulary
|
||||
translated into its real tool names. That translation lives in
|
||||
`skills/using-superpowers/references/<harness>-tools.md` and/or inline in the
|
||||
harness's bootstrap injector (see Part 5). It says, e.g., "*dispatch a
|
||||
subagent* → call `task` with `subagent_type`."
|
||||
|
||||
3. **Bootstrap (per-harness).** At the start of every session, the full
|
||||
`skills/using-superpowers/SKILL.md` is injected into the model's context,
|
||||
wrapped in `<EXTREMELY_IMPORTANT>` tags, with the tool mapping appended. That
|
||||
injected skill is what teaches the model that skills exist and that it must
|
||||
check for a relevant skill before acting. **The bootstrap is the entire
|
||||
integration.** Without it, the skill files are inert — present on disk, never
|
||||
invoked.
|
||||
|
||||
### Two rules that make this work
|
||||
|
||||
**1. Skills name actions, not tools.** Do **not** edit skill bodies to fit your
|
||||
harness. Porting adds a tool-mapping reference and a bootstrap injector; it
|
||||
never reaches into `skills/*/SKILL.md` to swap tool names. (The project's
|
||||
contributor guidelines treat skill content as carefully-tuned behavior-shaping
|
||||
code; rewording it for "compliance" is rejected on sight.)
|
||||
|
||||
**2. Everything ships through the harness's own install mechanism. Never edit the
|
||||
user's files.** The bootstrap, the skills, and the tool mapping all get delivered
|
||||
*as part of what the harness installs* — a plugin, an extension, a marketplace
|
||||
entry, an extension-bundled context file. A port **must not** reach into a user's
|
||||
global or personal config (`~/.gemini/config/AGENTS.md`, `settings.json`,
|
||||
`trustedFolders.json`, a hand-edited `~/.bashrc`, etc.) to inject anything. The
|
||||
harness owns what it loads; your install artifact is the only thing you get to
|
||||
write. If the install mechanism genuinely can't carry the bootstrap, that is a
|
||||
limitation to surface (Part 6) — never a license to hand-edit the user's config.
|
||||
(Shape C is *not* an exception: Gemini's context file is fine because it ships
|
||||
*inside the installed extension* and is declared by the manifest's
|
||||
`contextFileName` — the harness loads the extension's own file, not a file you
|
||||
edited in the user's home.)
|
||||
|
||||
---
|
||||
|
||||
## Part 2 — Can this harness be supported?
|
||||
|
||||
A harness can support Superpowers only if it can do all of the following. Check
|
||||
these before writing code — if the first one fails, stop.
|
||||
|
||||
### Hard requirement: automatic session-start injection
|
||||
|
||||
The harness must let you inject text into the model's context **at the start of
|
||||
every session, with no per-session opt-in by your human partner.** This is the
|
||||
one non-negotiable capability. It can take any form:
|
||||
|
||||
- a **hook/event system** that runs a shell command at session start and reads
|
||||
its stdout (Claude Code, Codex, Cursor, Copilot CLI), or
|
||||
- an **in-process plugin/extension** with a session-start or message lifecycle
|
||||
callback that can mutate the message array (OpenCode, pi), or
|
||||
- an **instructions-file** convention where the harness loads a context file that
|
||||
*your installed extension ships and declares* (e.g. Gemini's `contextFileName`
|
||||
pointing at the extension's own `GEMINI.md`) — not a file you edit in the user's
|
||||
home.
|
||||
|
||||
If the only way to get Superpowers in front of the model is for your human
|
||||
partner to opt in each session (paste a prompt, run a command, enable a mode),
|
||||
the harness
|
||||
**cannot** be properly supported. The acceptance test in Part 3 will fail, and
|
||||
the PR will be closed. This is the single most common reason a "port" isn't a
|
||||
real port.
|
||||
|
||||
### The rest of the capability checklist
|
||||
|
||||
| Capability | Why it's needed | If absent |
|
||||
|---|---|---|
|
||||
| **Skill discovery + invocation** | The model must be able to load a skill's full content on demand | If there's no native skill tool, the sanctioned fallback is to `read` the relevant `SKILL.md` directly — see Part 5. A harness with neither a skill tool nor file-read cannot work. |
|
||||
| **File read / write / edit** | Nearly every skill manipulates files | Essential. No workaround. |
|
||||
| **Run shell commands** | TDD, verification, git workflows | Essential. |
|
||||
| **Subagent / task dispatch** | `dispatching-parallel-agents`, `subagent-driven-development` | Degradable: if unavailable, those specific skills tell the model to do the work inline or report the missing capability — *never* to invent a `Task` call. Some harnesses gate this behind a config flag (e.g. Codex needs multi-agent enabled). |
|
||||
| **Todo / task tracking** | Progress tracking in several skills | Degradable: fall back to a plan file or `TODO.md`. |
|
||||
| **Web fetch / search** | A few skills | Degradable. |
|
||||
| **Shell or polyglot script execution (Windows)** | Only for the shell-hook shape, only if you want Windows support | See Part 7. In-process-plugin harnesses sidestep this entirely. |
|
||||
|
||||
"Degradable" means: the skill already has fallback wording for the missing
|
||||
tool. Your job in the tool mapping is to point at the real tool when it exists
|
||||
and reuse that fallback wording when it doesn't.
|
||||
|
||||
### You may not need a new directory at all
|
||||
|
||||
Some "new harnesses" are really existing integrations under a different
|
||||
installer. Factory's Droid, for example, consumes the Claude Code plugin via its
|
||||
own `plugin install` command and needs no new files here. Before building,
|
||||
check whether the harness can simply load an existing manifest. A port that adds
|
||||
nothing to this repo but a paragraph in the README is a perfectly good outcome.
|
||||
|
||||
---
|
||||
|
||||
## Part 3 — Definition of done
|
||||
|
||||
A port is finished when **all** of these are true:
|
||||
|
||||
1. The `using-superpowers` bootstrap loads at session start, every session, with
|
||||
no per-session opt-in.
|
||||
2. A tool mapping exists for the harness (in
|
||||
`references/<harness>-tools.md`, inline in the bootstrap, or both — per Part 5).
|
||||
3. Skills can actually be invoked — natively, or via the documented
|
||||
read-`SKILL.md` fallback — and the model follows them.
|
||||
4. **The acceptance test passes.** In a clean session, the user message:
|
||||
|
||||
> Let's make a react todo list
|
||||
|
||||
auto-triggers the `brainstorming` skill *before any code is written*. Capture
|
||||
the full transcript — the PR requires it.
|
||||
5. Tests cover the integration (Part 5) and pass.
|
||||
6. A real user can install it through the harness's own mechanism (not by
|
||||
hand-copying files), and the version is tracked in `.version-bump.json` where
|
||||
applicable (Part 6). Note that some installers rewrite or strip the manifest on
|
||||
install (one drops it to just `{"name": …}`), so "the *installed* files report
|
||||
the repo version" is not always achievable — track the version at the source
|
||||
manifest and don't treat a rewritten installed manifest as a failure.
|
||||
|
||||
A quick smoke check before the full acceptance test: start a session and ask the
|
||||
model to describe its superpowers. If the bootstrap injected, it knows it has
|
||||
them. (OpenCode's install doc uses `opencode run --print-logs "hello" 2>&1 |
|
||||
grep -i superpowers` for the same goal via a different mechanism — log-grep
|
||||
rather than asking the model; the `2>&1` matters because logs go to stderr. Find
|
||||
your harness's equivalent.)
|
||||
|
||||
---
|
||||
|
||||
## Part 4 — Choose your integration shape
|
||||
|
||||
There are three structural shapes, distinguished by *how you get the bootstrap
|
||||
in front of the model*. Pick the one that matches what your harness exposes,
|
||||
then copy that reference implementation. The shape determines almost everything
|
||||
in Part 5 — the steps below branch on it.
|
||||
|
||||
### How to tell which shape you have
|
||||
|
||||
Before routing, learn the harness's *actual* mechanism — and don't assume it's
|
||||
well documented or that it behaves like whatever harness it forked from.
|
||||
|
||||
**Find the surface:**
|
||||
|
||||
- **Search the web for the harness's docs** (extension / plugin / hook / skill /
|
||||
MCP / "context file" / "rules file"). Vendor tools change fast; search rather
|
||||
than trust training knowledge.
|
||||
- **Find and read an existing third-party extension/plugin for the harness.** A
|
||||
real working example beats docs — it shows the manifest shape, the install
|
||||
command, and which components the harness actually loads.
|
||||
- Check what the harness loads at startup: a settings file? an extensions
|
||||
directory? a per-project or global instructions file (`AGENTS.md`, `<NAME>.md`)?
|
||||
|
||||
**If it's underdocumented, reverse-engineer it empirically** (a real porter has
|
||||
had to do every one of these):
|
||||
|
||||
- `strings` the binary / grep the install tree for hook event names, config
|
||||
paths, and the instructions file it reads.
|
||||
- **Ask the running model to enumerate its own tool names** — e.g. "list the
|
||||
exact machine names of every tool you can call." This is the authoritative way
|
||||
to get tool names without inventing them (see Step 4).
|
||||
- Prove every assumption with a **unique-marker test**: inject a nonsense token
|
||||
through the mechanism you think works, start a fresh session, and confirm the
|
||||
token actually reached the model.
|
||||
|
||||
**A fork does not inherit its parent's behavior.** A harness derived from another
|
||||
(e.g. a Gemini-derived CLI) may expose the parent's manifest fields and
|
||||
`@`-include syntax and *still not honor them the same way*. Verify with a marker;
|
||||
never assume the parent's recipe transfers.
|
||||
|
||||
Then route to a shape:
|
||||
|
||||
- Shell command at session start whose stdout is read → **Shape A**.
|
||||
- Plugin/extension module with lifecycle callbacks you run code in → **Shape B**.
|
||||
- Only ever an always-on instructions file, no hook and no code plugin →
|
||||
**Shape C**.
|
||||
|
||||
**Shapes compose — they are not mutually exclusive.** The *skill-discovery*
|
||||
mechanism and the *bootstrap* mechanism need not be the same shape — but **both
|
||||
must still ride the install mechanism** (rule 2). Decide the two questions
|
||||
separately: *where do skills get discovered?* and *how does the bootstrap reach
|
||||
the model every session?* A harness might install skills via a plugin yet need
|
||||
the bootstrap delivered another install-shipped way (an extension-declared
|
||||
context file, or — see below — by the harness surfacing the installed
|
||||
`using-superpowers` skill's own description at session start). If more than one
|
||||
install-mechanism surface injects automatically, prefer the most reliable. What
|
||||
you may **not** do is bridge a gap by editing the user's global config.
|
||||
|
||||
### Shape A — Shell-hook
|
||||
|
||||
The harness has a hook system that runs a shell command at session start and
|
||||
reads JSON from its stdout. The configured command runs `run-hook.cmd`, a
|
||||
polyglot wrapper that just locates bash and dispatches the named script; the
|
||||
script (`hooks/session-start`, or a harness-specific variant like
|
||||
`hooks/session-start-codex`) is what reads `using-superpowers/SKILL.md` and
|
||||
prints a JSON object whose **field name and nesting differ per harness**.
|
||||
|
||||
- Reference: `hooks/session-start` (and `hooks/session-start-codex`),
|
||||
`hooks/run-hook.cmd`, and the per-harness hook config `hooks/hooks.json`
|
||||
(Claude Code), `hooks/hooks-codex.json` (Codex), `hooks/hooks-cursor.json`
|
||||
(Cursor).
|
||||
- Manifests: `.codex-plugin/plugin.json`, `.cursor-plugin/plugin.json` point the
|
||||
harness at `./skills/` and the right `hooks-*.json`. (Claude Code's
|
||||
`.claude-plugin/plugin.json` sets neither field — it auto-discovers `skills/`
|
||||
and `hooks/hooks.json` by convention.)
|
||||
|
||||
> **A hook *system* is not a session-start *event*.** A harness can have a
|
||||
> `hooks.json` mechanism — and even contain the literal string `SessionStart` in
|
||||
> its binary — while having no hook event that fires at session start and can
|
||||
> inject context. (One real harness only exposed pre/post-tool and stop events;
|
||||
> the `SessionStart` strings were telemetry.) Confirm the *specific event* you
|
||||
> need exists and can write to the model's context before committing to Shape A.
|
||||
> If it can't, the bootstrap belongs in an instructions file (Shape C) instead.
|
||||
|
||||
### Shape B — In-process plugin / extension
|
||||
|
||||
The harness loads a JS/TS module that exposes lifecycle callbacks. You register
|
||||
the skills directory through the harness's API and inject the bootstrap by
|
||||
mutating the message array in code.
|
||||
|
||||
- Reference: `.opencode/plugins/superpowers.js` (JavaScript) and
|
||||
`.pi/extensions/superpowers.ts` (TypeScript). pi is the closest reference for
|
||||
any harness that has **no native skill tool**.
|
||||
|
||||
### Shape C — Instructions-file
|
||||
|
||||
The harness has neither a shell hook nor a code plugin — its session-start
|
||||
surface is a context file that *your installed extension ships and the manifest
|
||||
declares* (e.g. Gemini's `contextFileName` → the extension's own `GEMINI.md`).
|
||||
You can't run code or mutate messages; the extension's context file points at the
|
||||
bootstrap. There is no injector to assemble a string or strip frontmatter — the
|
||||
harness loads the referenced content as-is. **This works only because the file is
|
||||
part of the installed extension** — never substitute "edit the user's global
|
||||
`GEMINI.md`/`AGENTS.md`" for shipping your own (rule 2).
|
||||
|
||||
- Reference: `gemini-extension.json` (manifest, with `contextFileName`),
|
||||
`GEMINI.md` (two `@`-includes — the bootstrap skill and the tool-mapping
|
||||
reference), `skills/using-superpowers/references/gemini-tools.md`.
|
||||
- Note: `@`-include is a Gemini feature. If your harness loads an instructions
|
||||
file but has no include syntax, you must inline the bootstrap content into the
|
||||
file instead.
|
||||
- **Don't trust that an `@`-include is actually expanded — prove it.** A
|
||||
Gemini-*derived* harness can accept `@./path` syntax yet treat it as a *hint
|
||||
the model may choose to read* (it emits a file-read tool call) rather than a
|
||||
guaranteed inline expansion. That's the difference between the bootstrap being
|
||||
reliably present every session and the model maybe-reading it. Run a
|
||||
unique-marker test: if the marker isn't in context *without* a tool call,
|
||||
**inline the content** rather than `@`-include it.
|
||||
|
||||
### Routing table
|
||||
|
||||
| If the harness… | Use shape | Copy from |
|
||||
|---|---|---|
|
||||
| runs a shell command at session start and reads its stdout | A (shell-hook) | Codex (`hooks/session-start-codex` + `hooks/hooks-codex.json` + `.codex-plugin/`) |
|
||||
| is a JS/TS plugin host with session/message lifecycle callbacks | B (in-process) | OpenCode (`.opencode/`) — or pi (`.pi/`) if it has no native skill tool |
|
||||
| ships an extension-declared context file it always loads | C (instructions-file) | Gemini (`gemini-extension.json` + `GEMINI.md` + `references/gemini-tools.md`) |
|
||||
| has a plugin install command and a manifest `contextFileName` (or equivalent) the installer keeps | C via the plugin installer | Antigravity (`.antigravity-plugin/` — `agy plugin install` ships a generated context file; verify the installer preserves it — Part 6) |
|
||||
|
||||
Most real harnesses fit one row cleanly; the last is the hybrid case (rule 2 still
|
||||
holds — the bootstrap rides the install mechanism, never a user-config edit).
|
||||
|
||||
---
|
||||
|
||||
## Part 5 — The porting procedure
|
||||
|
||||
### Step 1 — Study the closest reference implementation
|
||||
|
||||
Open the files named in Part 4 for your shape and read them end to end. The
|
||||
patterns below are summaries; the code is the spec.
|
||||
|
||||
### Step 2 — Create the manifest / entry point
|
||||
|
||||
Create whatever the harness uses to recognize the plugin. Match the existing
|
||||
ones in spirit:
|
||||
|
||||
- **Shape A:** a `*-plugin/plugin.json` (see `.codex-plugin/plugin.json`) with
|
||||
`name`, `version`, `description`, author/license/keywords, `"skills":
|
||||
"./skills/"`, and `"hooks": "./hooks/hooks-<harness>.json"`. Plus the
|
||||
`hooks-<harness>.json` itself, registering a session-start hook whose command
|
||||
invokes `run-hook.cmd`.
|
||||
- **Shape B:** the module the harness loads (e.g. `.<harness>/plugins/*.js`) plus
|
||||
whatever package metadata it needs to be discovered. The committed package
|
||||
metadata is the **repo-root `package.json`**: `main` points at the OpenCode
|
||||
plugin, the `pi` field (`pi.extensions`, `pi.skills`) plus the `pi-package`
|
||||
keyword declare the pi extension. Per-harness local manifests and lockfiles are
|
||||
kept out of git — `.opencode/.gitignore` excludes `node_modules`,
|
||||
`package.json`, and lockfiles. Do the same for your harness's *local* install
|
||||
artifacts so they don't pollute the repo — but never gitignore the repo-root
|
||||
`package.json`, which is the tracked source of truth.
|
||||
- **Build/dependency check.** Decide how the harness loads your module:
|
||||
does it run the source directly (pi's `.ts` is referenced as-is from
|
||||
`package.json`; OpenCode ships plain `.js`), or does it need a transpile/build
|
||||
step? Superpowers is zero-runtime-dependency. pi's `import type
|
||||
{ ExtensionAPI }` works specifically because the harness runs the `.ts`
|
||||
directly, supplies that type at load, and the repo never type-checks the file
|
||||
in CI — the import isn't even declared as a dependency. If *your* harness
|
||||
actually type-checks or bundles the plugin, that breaks: an undeclared type
|
||||
import fails, and the PR rules only carve out *runtime* deps for new
|
||||
harnesses, not dev/type packages. If you hit this, confirm the approach with
|
||||
the maintainer rather than quietly adding a dependency. Keep any build output
|
||||
out of git and document the command.
|
||||
- **Shape C (instructions-file):** a small manifest (see `gemini-extension.json`:
|
||||
`name`, `description`, `version`, `contextFileName`) plus the context file
|
||||
itself (`GEMINI.md` is just two `@`-includes: the bootstrap skill and the
|
||||
tool-mapping reference). The Gemini manifest has no `skills` field — Gemini
|
||||
auto-discovers the `skills/` directory bundled in the installed extension. If
|
||||
your harness has a native skill tool but no manifest field to register the
|
||||
directory, you must find its discovery convention (read its extension docs),
|
||||
then verify empirically: after wiring, ask the model to list its available
|
||||
skills — if the bundled skills don't appear, discovery isn't working yet.
|
||||
|
||||
### Step 3 — Wire the bootstrap injection
|
||||
|
||||
This is the heart of the port. The shared goal: at session start, get the
|
||||
`using-superpowers` skill content (wrapped in `<EXTREMELY_IMPORTANT>` tags) plus
|
||||
the harness's tool mapping in front of the model, with a note that the skill is
|
||||
already active so the model doesn't try to load it again. *How* you do that —
|
||||
and what you assemble vs. what the harness loads raw — depends entirely on your
|
||||
shape. Do **not** apply one shape's recipe to another.
|
||||
|
||||
**Shape A — a script reads `SKILL.md` and prints the harness's JSON.** The
|
||||
dispatched script (`hooks/session-start`) `cat`s the whole `SKILL.md` (frontmatter
|
||||
included — that's fine; it's emitted verbatim), wraps it with the "You have
|
||||
superpowers… for all other skills use the Skill tool" preamble, escapes it, and
|
||||
prints the harness's JSON shape. The tool mapping for Shape A does **not** go
|
||||
inline here — it lives in `references/<harness>-tools.md` (Step 4). Get the JSON
|
||||
output shape exactly right. `hooks/session-start`
|
||||
detects the harness from environment variables and prints *one of three* shapes:
|
||||
|
||||
- Cursor (`CURSOR_PLUGIN_ROOT` set): `{ "additional_context": "…" }`
|
||||
- Claude Code (`CLAUDE_PLUGIN_ROOT` set, `COPILOT_CLI` unset):
|
||||
`{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "…" } }`
|
||||
- Copilot CLI / SDK standard (else): `{ "additionalContext": "…" }`
|
||||
|
||||
This is a trap. Emitting the wrong field, or an extra one, means the bootstrap
|
||||
either never injects or injects twice (Claude Code reads both
|
||||
`additional_context` and `hookSpecificOutput` without de-duplicating, so emitting
|
||||
both double-injects). Find the
|
||||
exact field, nesting, and event-matcher values your harness expects. Then
|
||||
decide: add a fourth branch to `hooks/session-start`, or — if the harness needs
|
||||
a different bootstrap message or env contract — add a dedicated
|
||||
`hooks/session-start-<harness>` script, the way Codex did. If you add a branch
|
||||
and your harness *also* sets an env var an earlier branch keys on (some harnesses
|
||||
set `CLAUDE_PLUGIN_ROOT` too), order your branch before the one that would
|
||||
otherwise shadow it. Match the harness's
|
||||
own event-matcher strings (Claude Code uses `startup|clear|compact`, Codex
|
||||
`startup|resume|clear`, Cursor `sessionStart`); wrong matchers mean the hook
|
||||
silently never fires.
|
||||
|
||||
The **hook-config schema itself varies per harness** — don't assume the
|
||||
Claude/Codex shape is universal. Compare `hooks/hooks.json`,
|
||||
`hooks/hooks-codex.json`, and `hooks/hooks-cursor.json`: Cursor's uses
|
||||
`"version": 1`, a lowercase `sessionStart` key, a relative
|
||||
`./hooks/run-hook.cmd` command, and omits the `matcher`/`type`/`async` fields the
|
||||
others use. Match your `hooks-<harness>.json` to whichever existing file is
|
||||
closest, not to a single canonical template.
|
||||
|
||||
The hook **command string references a harness-provided plugin-root variable**,
|
||||
and its name differs per harness: `hooks.json` uses `${CLAUDE_PLUGIN_ROOT}`,
|
||||
`hooks-codex.json` uses `${PLUGIN_ROOT}`, Cursor uses a relative path. Use
|
||||
whatever your harness exports. (The `session-start` script re-derives the root
|
||||
itself via `dirname`, so the script body doesn't depend on this — but the
|
||||
command in the manifest does.)
|
||||
|
||||
**Discovering the harness's contract.** The three facts above — env var, JSON
|
||||
field/nesting, matcher strings — are the harness's contract, not Superpowers',
|
||||
so you have to source them. Read the harness's hook docs, or find out
|
||||
empirically: register a throwaway session-start hook that dumps its environment
|
||||
and emits a marker, then observe which env var identifies the harness and
|
||||
whether/how the harness ingests your stdout. Pin these down before writing the
|
||||
real branch.
|
||||
|
||||
**Shape B — assemble the string in code, then inject as a user message.** Here
|
||||
you build the bootstrap yourself: read `SKILL.md`, strip its YAML frontmatter,
|
||||
and assemble `<EXTREMELY_IMPORTANT>` + a short preamble that the skill is already
|
||||
loaded and must not be re-invoked + the stripped body + the inline tool mapping +
|
||||
`</EXTREMELY_IMPORTANT>`. One subtlety the references disagree on: OpenCode's
|
||||
preamble says "do NOT use the skill tool…" (assumes a `skill` tool exists), while
|
||||
pi's just says "do not try to load using-superpowers again." If your harness has
|
||||
no skill tool, use pi's wording, not OpenCode's.
|
||||
|
||||
Inject the result as a **user-role message, not a system message** — system
|
||||
messages bloat tokens when repeated every turn (#750) and multiple system
|
||||
messages break some models (#894). Three things you must replicate:
|
||||
|
||||
- **Dedup guard.** The lifecycle callback can fire repeatedly (OpenCode's
|
||||
transform runs on *every* agent step; pi's `context` fires per turn). Before
|
||||
injecting, check whether a bootstrap marker is already present and skip if so.
|
||||
(The references pick different markers — pi a custom string, OpenCode the
|
||||
`EXTREMELY_IMPORTANT` tag; matching the tag is more robust since it needs no
|
||||
harness-specific constant.) Cache the bootstrap content at module level so
|
||||
you're not re-reading and re-parsing `SKILL.md` on every call (#1202).
|
||||
- **Compaction.** If the harness compacts/summarizes history, re-inject
|
||||
afterward. pi sets an `injectBootstrap` flag on `session_start` and
|
||||
`session_compact`, clears it on `agent_end`, and inserts the message *after*
|
||||
any leading compaction-summary messages. OpenCode relies on its per-step
|
||||
re-injection plus the dedup guard.
|
||||
- **Message-object shape is per-harness — discover yours, don't copy a literal.**
|
||||
The two references use *incompatible* shapes: pi builds
|
||||
`{ role, content: [{ type, text }], timestamp }`; OpenCode manipulates
|
||||
`message.info.role` and `message.parts[]`. Find your harness's message shape
|
||||
from its API; copying a reference's object literal verbatim will fail silently.
|
||||
|
||||
**Shape C — point your extension's context file at the bootstrap; assemble
|
||||
nothing.** There is no injector, so you do *not* strip frontmatter or build a
|
||||
wrapped string. The context file your extension ships (declared by the manifest —
|
||||
*not* the user's own global file) pulls in two things: the `using-superpowers`
|
||||
skill and the harness's tool-mapping reference. `GEMINI.md`
|
||||
does this with two `@`-includes (`@./skills/using-superpowers/SKILL.md` and
|
||||
`@./skills/using-superpowers/references/<harness>-tools.md`); the harness loads
|
||||
them raw, frontmatter and all, and `SKILL.md` already carries its own
|
||||
`<EXTREMELY-IMPORTANT>` block internally. If your harness has no include syntax,
|
||||
inline the content into the instructions file instead. Gemini ships **no**
|
||||
"already loaded, don't re-invoke" preamble — for an `@`-include harness the
|
||||
content is the active instruction set, not a skill the model would re-load. If
|
||||
you find your harness does try to re-invoke, add that note as a literal line in
|
||||
the instructions file (you have no code to add it any other way).
|
||||
|
||||
### Step 4 — Write the tool mapping
|
||||
|
||||
Translate the action vocabulary into the harness's real tools. Cover every one
|
||||
of these actions (omit only what genuinely doesn't apply):
|
||||
|
||||
- read a file
|
||||
- create / edit / delete a file (one `apply_patch`-style tool, or separate
|
||||
write/edit?)
|
||||
- run a shell command
|
||||
- search file contents / find files by name (grep, glob)
|
||||
- fetch a URL / web search
|
||||
- **dispatch a subagent**, including how to pass the agent type — and any config
|
||||
flag needed to enable it
|
||||
- **create / update todos** (treat older `TodoWrite` references as this action)
|
||||
- **invoke a skill** — see Step 5
|
||||
|
||||
**Get the real tool names from the harness; never invent them.** If the docs
|
||||
don't list them, the authoritative source is the harness itself: in a live
|
||||
session, ask the model to "list the exact machine names of every tool you can
|
||||
call, one per line" and use what it reports.
|
||||
|
||||
**How the harness finds the `skills/` directory is itself per-harness** — confirm
|
||||
it, don't assume. Possibilities: a manifest `skills` path field (Codex's
|
||||
`"skills": "./skills/"`); a *co-located* `skills/` the harness auto-scans (where a
|
||||
path field is **ignored** — one real harness only scanned a `skills/` sitting next
|
||||
to `plugin.json`); an API/registration call (OpenCode, pi); or you stage an
|
||||
install dir that pairs the manifest with a **symlink to the repo's `skills/`** and
|
||||
point the installer at the staging dir (verify the installer *dereferences* the
|
||||
symlink and copies the real files — confirm with `agy plugin validate`/`install`
|
||||
or the equivalent before relying on it). A `skills` path field is *not* portable.
|
||||
|
||||
Where the mapping lives depends on shape:
|
||||
|
||||
- **Shape A:** put it in `skills/using-superpowers/references/<harness>-tools.md`.
|
||||
The agent reaches it from the bootstrap — `SKILL.md`'s "Platform Adaptation"
|
||||
section links the per-harness references files. (Shape A harnesses have no
|
||||
instructions file; the mapping is *not* inlined into the hook output.)
|
||||
- **Shape B:** the mapping is typically inlined into the bootstrap string you
|
||||
inject (see the `toolMapping` constant in `superpowers.js`). pi keeps it in
|
||||
*both* places — `piToolMapping()` inline **and** `references/pi-tools.md`. If
|
||||
you maintain it in two places, update both, or the port is half-done.
|
||||
- **Shape C:** put it in `references/<harness>-tools.md` and pull it into the
|
||||
always-loaded instructions file (e.g. `GEMINI.md` `@`-includes
|
||||
`gemini-tools.md`).
|
||||
|
||||
You may also add a one-line pointer to your harness in `SKILL.md`'s "Platform
|
||||
Adaptation" section so an agent reading the bootstrap knows where its mapping
|
||||
lives. This is the one edit to a `SKILL.md` a port may make — and only because
|
||||
that section is a pointer list, not behavior-shaping content. It does not violate
|
||||
the "don't edit skill bodies" rule (Part 1); do not touch anything else in any
|
||||
skill. (The list is a convenience pointer, not an exhaustive registry — not every
|
||||
harness is listed.)
|
||||
|
||||
### Step 5 — Handle a harness with no native skill tool
|
||||
|
||||
`using-superpowers/SKILL.md` tells the model to *never read skill files manually
|
||||
with file tools — always use your platform's skill-loading mechanism.* The point
|
||||
is "don't bypass the mechanism," not "never use file-read." What counts as "your
|
||||
platform's mechanism" depends on the harness — and for a harness with no skill
|
||||
tool, the documented mechanism *is* reading `SKILL.md`. So reading it there
|
||||
honors the rule rather than breaking it. Distinguish three cases:
|
||||
|
||||
1. **Native `Skill`-style tool** (Claude Code, Copilot CLI, Gemini's
|
||||
`activate_skill`): point the mapping at that tool.
|
||||
2. **Native skill *discovery* but no `Skill` tool** (pi, Antigravity): the harness
|
||||
can find and list skills, but the model can't call a tool to load one. Get the
|
||||
skills installed where the harness scans (pi registers via `resources_discover`
|
||||
→ `skillPaths`; OpenCode via its `config` hook; `agy plugin install` copies
|
||||
them in), and tell the model to load a skill by **reading its `SKILL.md` with
|
||||
the file-read tool when the skill applies** — the sanctioned mechanism here,
|
||||
the way `references/pi-tools.md` states it.
|
||||
|
||||
**For the bootstrap itself, prefer a declared context file (Part 6).** If the
|
||||
harness has a `contextFileName`-style manifest field — as Antigravity does —
|
||||
ship a generated context file through the installer: it's guaranteed-loaded and
|
||||
carries both the `using-superpowers` content and the tool mapping. That is the
|
||||
strong, preferred path.
|
||||
|
||||
**Fallback — the surfaced skill index.** If there's no context-file field but
|
||||
the harness surfaces each installed skill's name + description at session start,
|
||||
you need *neither* a built index nor a runtime-list instruction — the harness
|
||||
is the index, and `using-superpowers`'s own surfaced description can be what
|
||||
triggers the model to load it. This is softer than a declared context file;
|
||||
two things it does **not** give you, versus a context file / hook / in-process
|
||||
injector — account for both:
|
||||
- **It bootstraps *triggering*, not the *tool mapping*.** An injector prepends
|
||||
`<harness>-tools.md` alongside `using-superpowers` every session. Here nothing
|
||||
injects the mapping — the model only sees skill *descriptions* and must *read*
|
||||
your `references/<harness>-tools.md` when it needs tool names. It works
|
||||
because skills name actions (the model reads the mapping when it acts), but
|
||||
it's softer than injection. Make sure the mapping is reachable from what the
|
||||
model loads — e.g. linked from `SKILL.md`'s Platform Adaptation section and
|
||||
installed alongside the skills — not just sitting in the repo.
|
||||
- **There's no structural guarantee the trigger fires.** No `<EXTREMELY_IMPORTANT>`
|
||||
wrapper, no dedup, no re-injection after compaction — firing depends on the
|
||||
model choosing to act on a description it sees in the index. This is exactly
|
||||
why the acceptance test is mandatory here: it is the *only* guarantee, so run
|
||||
it on the model(s) your users will actually use, not just the strongest one.
|
||||
3. **No skill system at all:** there is nothing to register, and the *only*
|
||||
mechanism is the model reading `SKILL.md` on demand. But the model can't read
|
||||
what it can't find: `using-superpowers/SKILL.md` does **not** enumerate the
|
||||
available skills, so on its own the model won't know which skills exist or
|
||||
their triggers. You must supply a discovery path. Two options, and they differ
|
||||
in durability: (a) generate a skill index (each `skills/*/SKILL.md`'s `name` +
|
||||
`description` frontmatter) and place it *inside* the `<EXTREMELY_IMPORTANT>`
|
||||
wrapper alongside the tool mapping (Shape B recipe above) so it's covered by
|
||||
the dedup guard — but a build-time index goes stale as skills are added; or
|
||||
(b) instruct the model to list `skills/*/SKILL.md` at runtime and read their
|
||||
frontmatter to find a match — slower but never stale. Prefer (b) unless you
|
||||
have a reason not to. Without either, a no-skill-system port loads the
|
||||
bootstrap but silently never triggers any other skill.
|
||||
|
||||
In cases 2 and 3, say plainly in your tool mapping that reading `SKILL.md` is the
|
||||
blessed path, so the model doesn't think it's violating the "never read skill
|
||||
files" rule. Don't go hunting for a `skillPaths`-style registration API in a
|
||||
harness that has no skill system — case 3 has none.
|
||||
|
||||
### Step 6 — Add tests
|
||||
|
||||
Match the existing per-harness test style:
|
||||
|
||||
- **Shape A:** assert the hook's stdout has the exact JSON shape your harness
|
||||
consumes, and that it contains the bootstrap. See `tests/hooks/test-session-start.sh`,
|
||||
which validates each harness's output shape.
|
||||
- **Shape B:** a unit test that fakes the harness's plugin API and asserts the
|
||||
lifecycle handlers register, the bootstrap injects once, the dedup guard
|
||||
works, and (if relevant) compaction re-injection works. See
|
||||
`tests/pi/test-pi-extension.mjs`. Add an isolated-install integration check in
|
||||
the style of `tests/opencode/`.
|
||||
- If the bootstrap is cached, test that the cache behaves when the file is
|
||||
missing (see the OpenCode caching tests).
|
||||
|
||||
These automated tests cover the wiring; the live tmux run in Step 7 is what
|
||||
proves the integration actually triggers skills.
|
||||
|
||||
### Step 7 — Install locally, then drive a live instance to verify
|
||||
|
||||
You cannot confirm a port works by reading code. You have to run the harness with
|
||||
your in-progress port loaded and watch a real session — which is also how you
|
||||
produce the transcript the PR requires.
|
||||
|
||||
**Install locally.** Point a *local* instance of the harness at your working
|
||||
tree, not a published build:
|
||||
|
||||
- **Shape A / C:** install the plugin/extension from this repo's local path (or
|
||||
symlink its directory into wherever the harness looks). Find the harness's
|
||||
"install from a local directory / git checkout" path in its docs.
|
||||
- **Shape B:** register the local module — e.g. an `opencode.json` `plugin`
|
||||
entry pointing at the local path, or pi resolving the `package.json` fields
|
||||
from the repo.
|
||||
|
||||
Reinstall after each change and restart the harness, since the bootstrap loads at
|
||||
startup.
|
||||
|
||||
**Drive it with tmux.** Most harnesses are interactive REPLs/TUIs that can't be
|
||||
driven by piping stdin, so run the harness inside a detached tmux session and
|
||||
control it with `send-keys` / `capture-pane`. A harness may advertise a
|
||||
non-interactive "run one prompt" mode (e.g. `opencode run "..."`) — try it for the
|
||||
quick smoke check, but **don't depend on it**: these modes are frequently flaky,
|
||||
auth-gated, or trust-gated (one real harness's `--print` mode hung and timed out
|
||||
with no output every time). Be ready to do *everything*, including the smoke
|
||||
check, through tmux.
|
||||
|
||||
**Clear the gates first, or tmux stalls silently.** Many harnesses block on
|
||||
first-run onboarding, a "do you trust this folder?" prompt, a sandbox mode, or a
|
||||
permission gate — and a detached tmux session will just sit there with no error
|
||||
while it waits. Before the run, pre-trust your scratch directory (in the harness's
|
||||
settings/config) or be prepared to answer those prompts via `send-keys`, and
|
||||
account for the harness's startup time in your first `sleep`.
|
||||
|
||||
```bash
|
||||
# 1. Launch the harness detached, in a throwaway project dir
|
||||
mkdir -p /tmp/port-smoke
|
||||
tmux new-session -d -s port-test -c /tmp/port-smoke '<harness-launch-command>'
|
||||
|
||||
# 2. Let it initialize — real TUIs take longer than you think (10s+ with a model
|
||||
# handshake); tune this. THEN capture and clear any blocking modal before you
|
||||
# type a prompt: first-run onboarding and "trust this folder?" are modal, so
|
||||
# keystrokes sent during them select menu items instead of typing your prompt.
|
||||
sleep 12
|
||||
tmux capture-pane -t port-test -p # onboarding / trust prompt? answer it via send-keys first
|
||||
# (e.g. tmux send-keys -t port-test Enter # to accept a trust prompt — inspect before assuming)
|
||||
|
||||
# 3. Smoke check: does the model know it has superpowers?
|
||||
# Send the text and Enter as SEPARATE send-keys with a beat between them —
|
||||
# sending them together races on some TUIs (Enter arrives before the text lands).
|
||||
tmux send-keys -t port-test 'What are your superpowers?'; sleep 0.4; tmux send-keys -t port-test Enter
|
||||
sleep 5
|
||||
tmux capture-pane -t port-test -p # reply should show it knows its skills
|
||||
|
||||
# 4. Acceptance test: exact prompt (note the escaped apostrophe), fresh session
|
||||
tmux send-keys -t port-test 'Let'\''s make a react todo list'; sleep 0.4; tmux send-keys -t port-test Enter
|
||||
# poll until the turn finishes — re-capture every few seconds, don't capture once
|
||||
sleep 8
|
||||
tmux capture-pane -t port-test -p # PASS = brainstorming triggers BEFORE any code
|
||||
|
||||
# 5. Save the transcript for the PR, then clean up
|
||||
tmux capture-pane -t port-test -p > /tmp/port-smoke/transcript.txt
|
||||
tmux kill-session -t port-test
|
||||
```
|
||||
|
||||
tmux gotchas that bite here: wait after launch before the first capture; send the
|
||||
prompt text and `Enter` as *separate* `send-keys` calls with a short `sleep`
|
||||
between them (sending them together races on some TUIs), and `Enter` is a key name
|
||||
not `\n`; the agent's turn takes time, so **poll `capture-pane` in a loop** rather
|
||||
than capturing once; `capture-pane` shows only the visible pane, so for a long
|
||||
conversation use the harness's own transcript/log file as the record of truth;
|
||||
always `kill-session` when done.
|
||||
|
||||
If the smoke check shows the model *doesn't* know it has superpowers, the
|
||||
bootstrap isn't loading — fix that before bothering with the acceptance test.
|
||||
|
||||
---
|
||||
|
||||
## Part 6 — Distribution and release
|
||||
|
||||
A working integration in this repo isn't usable until a real user can install
|
||||
it. Distribution differs per harness ecosystem — find yours:
|
||||
|
||||
| Channel | Example | What you do |
|
||||
|---|---|---|
|
||||
| Native plugin marketplace | Claude Code | Register in `.claude-plugin/marketplace.json`; users `/plugin install`. The external `superpowers-marketplace` repo is the source of truth users install from — see the release steps in `CLAUDE.md`. |
|
||||
| External marketplace fork, synced by script | Codex | `scripts/sync-to-codex-plugin.sh` rsyncs the tracked plugin files into a separate fork repo and opens a PR. Read its include/exclude list so you ship the right tree (it deliberately drops repo-internal dirs and other harnesses' dotdirs). |
|
||||
| Git-URL extension install | Gemini, Kimi Code, OpenCode | Users install from a git URL (`gemini extensions install …`; Kimi Code `/plugins install …`; an `opencode.json` `plugin` array entry). Document the exact command. |
|
||||
| Package-manifest fields | pi | Declared through fields in the repo-root `package.json`; users install via the harness's package command. |
|
||||
| Local installer (plugin install) | Antigravity (`agy`) | A small `install.sh` that runs the harness's own `agy plugin install` against a staging dir holding the manifest, the skills, and a generated `contextFileName` context file (the bootstrap). Everything arrives through the install mechanism — *not* by editing the user's config (see below). |
|
||||
|
||||
Then:
|
||||
|
||||
- **A plugin installer may silently strip *undeclared* files — so make the
|
||||
bootstrap a file the installer *recognizes*, never a user-config edit.** A
|
||||
`plugin install` typically copies only the components it knows about
|
||||
(skills/agents/commands/mcp/hooks/context) and discards anything else, so a
|
||||
context file the manifest doesn't declare just vanishes from the install. The
|
||||
fix is **not** to give up and write into the user's config (**rule 2**) — it's
|
||||
to declare the bootstrap as a recognized component. In escalation order:
|
||||
- **Ship a context file the manifest declares.** If the harness has a
|
||||
`contextFileName`-style field (an extension-declared file it loads every
|
||||
session), that is the strongest clean bootstrap: declare it, and the installer
|
||||
preserves it *and* the harness loads it. Generate it at install time from the
|
||||
live `using-superpowers/SKILL.md` + the tool mapping (wrapped in
|
||||
`<EXTREMELY_IMPORTANT>`) so the installed bootstrap never drifts. This is what
|
||||
`.antigravity-plugin/install.sh` does — `agy plugin install` reports
|
||||
`✔ context : ANTIGRAVITY.md`, and a clean session reads `using-superpowers`'s
|
||||
SKILL.md, loads `brainstorming`, and enters the brainstorming flow before any
|
||||
code. **Verify with a marker** that the installer keeps the file and the
|
||||
harness loads it: one porter wrongly concluded it couldn't, because they
|
||||
shipped the file *without* declaring `contextFileName` and it was stripped as
|
||||
unrecognized.
|
||||
- **Otherwise lean on the installed `using-superpowers` skill itself.** If the
|
||||
harness surfaces each installed skill's name + description at session start,
|
||||
the `using-superpowers` description ("Use when starting any conversation…")
|
||||
can prompt the model to load it — installing the skill *is* the bootstrap.
|
||||
Softer (no guaranteed wrapper; it carries triggering but not the tool mapping
|
||||
— see Step 5), so prefer the declared context file when available.
|
||||
- If neither works, the harness cannot be cleanly supported yet — **say so**
|
||||
and raise it, rather than hand-editing the user's config.
|
||||
|
||||
- **Write install docs.** A `docs/README.<harness>.md` and/or a
|
||||
`.<harness>/INSTALL.md` (see `docs/README.opencode.md` and
|
||||
`.opencode/INSTALL.md`), plus an install section in the top-level `README.md`.
|
||||
The only supported install action is **running the harness's own install
|
||||
command** (`agy plugin install`, `gemini extensions install`, `/plugin
|
||||
install`, etc.). Hand-copying skill files and editing the user's global/personal
|
||||
config are *both* off-limits (rule 2 / the PR rules). If the harness has no
|
||||
install command at all — its only surface is a user-owned config file — then it
|
||||
fails the "deliver via install mechanism" rule, and you should raise that rather
|
||||
than ship an installer that edits the user's files.
|
||||
- **Register the version.** If your harness introduces a *new* versioned
|
||||
manifest, add its path and version field to `.version-bump.json` so
|
||||
`scripts/bump-version.sh` keeps it in lockstep (read that file to see what's
|
||||
currently tracked). A new manifest that isn't registered there will ship a
|
||||
stale version. If your harness instead rides an already-tracked file — pi
|
||||
declares itself in the repo-root `package.json`, which is already listed —
|
||||
there's nothing new to add.
|
||||
- **If no existing channel fits, you're standing up a new one.** None of the four
|
||||
rows may match your harness. If it needs a Codex-style external fork sync,
|
||||
`scripts/sync-to-codex-plugin.sh` is the template to clone (note its anchored
|
||||
include/exclude list and its PR automation). And whenever you add a new
|
||||
per-harness directory, add it to the *other* harnesses' sync excludes (e.g. the
|
||||
EXCLUDES list in `sync-to-codex-plugin.sh`) so your dotdir doesn't leak into
|
||||
their distributions.
|
||||
|
||||
---
|
||||
|
||||
## Part 7 — Cross-platform / Windows
|
||||
|
||||
Only relevant to the shell-hook shape. `hooks/run-hook.cmd` is a polyglot: a
|
||||
single file that's valid as both a Windows batch script and a Unix shell script.
|
||||
On Windows, `cmd.exe` runs the batch portion, which locates `bash` (Git for
|
||||
Windows, then `bash` on PATH) and runs the named hook script; if no bash is
|
||||
found it exits cleanly so the harness still works, just without injection. On
|
||||
Unix, the leading `:` makes the batch block a no-op and the shell runs the
|
||||
script directly.
|
||||
|
||||
Two rules this enforces, which you must respect:
|
||||
|
||||
- **Hook scripts are extensionless** (`session-start`, not `session-start.sh`).
|
||||
Claude Code's Windows handling prepends `bash` to any command containing
|
||||
`.sh`, which would double-invoke. Name your hook script without an extension.
|
||||
- Don't write per-OS variants of the hook script. One extensionless bash script
|
||||
plus the polyglot wrapper covers all three platforms.
|
||||
|
||||
`hooks/run-hook.cmd` itself is the authoritative implementation — read it. See
|
||||
`docs/windows/polyglot-hooks.md` for the background and rationale behind the
|
||||
dispatcher pattern.
|
||||
|
||||
---
|
||||
|
||||
## Part 8 — Submitting the PR
|
||||
|
||||
- Target the **`dev`** branch. One harness per PR.
|
||||
- Fill in the PR template's **"New harness support"** section and paste the
|
||||
complete acceptance-test transcript (the "Let's make a react todo list"
|
||||
session showing `brainstorming` auto-triggering). A PR without this proof will
|
||||
be closed.
|
||||
- Superpowers is a zero-dependency plugin. Don't add a third-party runtime
|
||||
dependency. Adding a new harness is the one carve-out the contributor rules
|
||||
allow, and even then keep it to what the integration strictly requires —
|
||||
type-only imports that compile away are fine; runtime packages are not.
|
||||
- Don't touch skill bodies (Part 1). If you found yourself editing a `SKILL.md`
|
||||
to make the port work, the fix belongs in your tool mapping instead.
|
||||
|
||||
---
|
||||
|
||||
## Appendix A — Reference integrations (current)
|
||||
|
||||
Use this as the live index; when in doubt, read the files, not this table.
|
||||
|
||||
| Harness | Entry point | Bootstrap mechanism | Tool mapping | Tests | Distribution |
|
||||
|---|---|---|---|---|---|
|
||||
| Claude Code | `.claude-plugin/plugin.json` + `hooks/hooks.json` | shell hook → `hooks/session-start` (`hookSpecificOutput.additionalContext`) | native `Skill` tool; `references/claude-code-tools.md` | `tests/hooks/` | marketplace |
|
||||
| Codex | `.codex-plugin/plugin.json` + `hooks/hooks-codex.json` | shell hook → `hooks/session-start-codex` | `references/codex-tools.md` | `tests/codex-plugin-sync/`, `tests/hooks/` | fork sync (`scripts/sync-to-codex-plugin.sh`) |
|
||||
| Cursor | `.cursor-plugin/plugin.json` + `hooks/hooks-cursor.json` | shell hook → `hooks/session-start` (`additional_context`) | `references/claude-code-tools.md` | `tests/hooks/` | hand-authored |
|
||||
| Copilot CLI | (shares Claude Code hook path; `COPILOT_CLI` env) | shell hook → `hooks/session-start` (`additionalContext`) | `references/copilot-tools.md` | `tests/hooks/` | — |
|
||||
| Gemini CLI | `gemini-extension.json` + `GEMINI.md` | instructions file `@`-includes bootstrap + mapping | `references/gemini-tools.md` | — | `gemini extensions install` |
|
||||
| Kimi Code | `.kimi-plugin/plugin.json` | manifest `sessionStart.skill` loads `using-superpowers` | inline `skillInstructions` in manifest | `tests/kimi/` | marketplace or `/plugins install` GitHub URL |
|
||||
| OpenCode | `.opencode/plugins/superpowers.js` (declared via root `package.json` `main`) | in-process: `config` hook registers skills dir; `experimental.chat.messages.transform` injects user message | inline in `superpowers.js` | `tests/opencode/` | `opencode.json` plugin git URL |
|
||||
| pi | `.pi/extensions/superpowers.ts` | in-process: `resources_discover` registers skills; `context` event injects user message; lifecycle-flag + compaction-aware | `piToolMapping()` inline **and** `references/pi-tools.md` | `tests/pi/` | repo-root `package.json` fields |
|
||||
|
||||
## Appendix B — Gotchas that have bitten porters
|
||||
|
||||
- **Opt-in isn't a port.** If your human partner has to do anything per session
|
||||
to get Superpowers, the acceptance test fails. Re-read Part 2.
|
||||
- **Wrong JSON field → silent failure or double injection.** Shape A only.
|
||||
Confirm the exact field/nesting; Claude Code reads two fields without dedup.
|
||||
- **Hook-config schema varies per harness.** Shape A. Cursor's `hooks-cursor.json`
|
||||
looks nothing like the Claude/Codex one (`version`, lowercase `sessionStart`,
|
||||
relative command, no `matcher`/`type`/`async`). Match the closest existing file.
|
||||
- **Plugin-root env var differs per harness.** Shape A. The hook command uses
|
||||
`${CLAUDE_PLUGIN_ROOT}` (Claude), `${PLUGIN_ROOT}` (Codex), or a relative path
|
||||
(Cursor). Use what your harness exports; the script re-derives the root itself.
|
||||
- **System-message injection.** Shape B injects a *user* message on purpose
|
||||
(#750, #894). Don't "fix" it to a system message.
|
||||
- **Per-step vs per-turn callbacks.** OpenCode fires every step (per-call dedup
|
||||
guard); pi fires per turn (lifecycle flag + `agent_end` reset). Copying one
|
||||
harness's dedup strategy onto the other's callback frequency breaks injection.
|
||||
- **Message-object shape is per-harness.** Shape B. pi and OpenCode use
|
||||
incompatible shapes; discover yours, don't copy a reference's object literal.
|
||||
- **Hunting for a skill-registration API that doesn't exist.** A harness with no
|
||||
skill system (not just no `Skill` tool) has nothing to register — the model
|
||||
reads `SKILL.md` on demand. Don't assume a `skillPaths` equivalent exists.
|
||||
- **Mapping in two places.** For in-process plugins the mapping may live both
|
||||
inline and in a `references/` file (pi). Update both.
|
||||
- **The "never read skill files" line.** It means "don't bypass your platform's
|
||||
skill-loading mechanism," not "never use file-read." On a no-skill-tool harness
|
||||
that mechanism *is* reading `SKILL.md` — say so explicitly in the mapping
|
||||
(Part 5).
|
||||
- **`.sh` on Windows.** Keep hook scripts extensionless (Part 7).
|
||||
- **Unregistered version.** A new manifest not added to `.version-bump.json`
|
||||
ships stale (Part 6).
|
||||
- **Editing skills to fit the harness.** Never. The fix goes in the tool mapping.
|
||||
@@ -0,0 +1,774 @@
|
||||
# SDD Task-Scoped Review Dispatch Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Scope SDD's per-task reviews to the task (diff-first reading, justified broadening, no redundant test runs) while final branch review stays broad.
|
||||
|
||||
**Architecture:** Four prose edits to the subagent-driven-development skill (the per-task quality prompt becomes self-contained instead of delegating to the merge-readiness template; the spec prompt gets a third verdict channel and grounded skepticism; the implementer prompt gains a re-run-after-fix rule; SKILL.md gets controller guidance) plus one new eval scenario in the `evals/` submodule. `skills/requesting-code-review/` is deliberately untouched.
|
||||
|
||||
**Tech Stack:** Markdown skill files; Python setup helper + bash checks + story.md for the quorum eval.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-06-09-sdd-task-scoped-review-dispatch-design.md` — read it before starting. Decisions already settled there: full re-reviews stay; the two review stages stay separate; coordinator keeps model judgment; `requesting-code-review/` stays broad.
|
||||
|
||||
**These are behavior-shaping prose files, not code.** There are no unit tests for them. Each task's verification steps are exact `grep` checks that the edit landed; behavioral verification is Task 6 (static) and Task 7 (live evals, maintainer-gated).
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Rewrite the per-task quality reviewer prompt as self-contained
|
||||
|
||||
The current file delegates to `../requesting-code-review/code-reviewer.md`, which is a merge-readiness review (architecture, security, production readiness, "Ready to merge?"). Replace the entire file with a self-contained, task-scoped template.
|
||||
|
||||
**Files:**
|
||||
- Rewrite: `skills/subagent-driven-development/code-quality-reviewer-prompt.md`
|
||||
|
||||
- [ ] **Step 1: Replace the full file contents with:**
|
||||
|
||||
````markdown
|
||||
# Code Quality Reviewer Prompt Template
|
||||
|
||||
Use this template when dispatching a code quality reviewer subagent.
|
||||
|
||||
**Purpose:** Verify one task's implementation is well-built (clean, tested, maintainable)
|
||||
|
||||
**Only dispatch after spec compliance review passes.**
|
||||
|
||||
```
|
||||
Subagent (general-purpose):
|
||||
description: "Review code quality for Task N"
|
||||
prompt: |
|
||||
You are reviewing one task's implementation for code quality. This is a
|
||||
task-scoped gate, not a merge review — a broad whole-branch review happens
|
||||
separately after all tasks are complete.
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
[DESCRIPTION]
|
||||
|
||||
## Task Requirements (context only)
|
||||
|
||||
[TASK_TEXT]
|
||||
|
||||
## Git Range to Review
|
||||
|
||||
**Base:** [BASE_SHA]
|
||||
**Head:** [HEAD_SHA]
|
||||
|
||||
```bash
|
||||
git diff --stat [BASE_SHA]..[HEAD_SHA]
|
||||
git diff [BASE_SHA]..[HEAD_SHA]
|
||||
```
|
||||
|
||||
## Read-Only Review
|
||||
|
||||
Your review is read-only on this checkout. Do not mutate the working tree,
|
||||
the index, HEAD, or branch state in any way. Use tools like `git show`,
|
||||
`git diff`, and `git log` to inspect history.
|
||||
|
||||
## Scope
|
||||
|
||||
Spec compliance was already verified by a separate reviewer. Do not
|
||||
re-check whether the code matches the requirements or the plan.
|
||||
|
||||
Start from the diff. Read the changed files first. Inspect code outside
|
||||
the diff only to evaluate a concrete risk you can name — and name it in
|
||||
your report. Cross-cutting changes are legitimate named risks: if the
|
||||
diff changes lock ordering, a function or API contract, or shared mutable
|
||||
state, checking the call sites is the right method. Do not crawl the
|
||||
codebase by default.
|
||||
|
||||
## Tests
|
||||
|
||||
The implementer already ran the tests and reported results with TDD
|
||||
evidence for exactly this code. Do not re-run the suite to confirm their
|
||||
report. Run a test only when reading the code raises a specific doubt
|
||||
that no existing run answers — and then a focused test, never a
|
||||
package-wide suite, race detector run, or repeated/high-count loop. If
|
||||
heavy validation seems warranted, recommend it in your report instead of
|
||||
running it. If you cannot run commands in this environment, name the
|
||||
test you would run.
|
||||
|
||||
## What to Check
|
||||
|
||||
**Code quality:**
|
||||
- Clean separation of concerns?
|
||||
- Proper error handling?
|
||||
- DRY without premature abstraction?
|
||||
- Edge cases handled?
|
||||
|
||||
**Tests:**
|
||||
- Do the new and changed tests verify real behavior, not mocks?
|
||||
- Are the task's edge cases covered?
|
||||
|
||||
**Structure:**
|
||||
- Does each file have one clear responsibility with a well-defined interface?
|
||||
- Are units decomposed so they can be understood and tested independently?
|
||||
- Is the implementation following the file structure from the plan?
|
||||
- Did this change create new files that are already large, or
|
||||
significantly grow existing files? (Don't flag pre-existing file
|
||||
sizes — focus on what this change contributed.)
|
||||
|
||||
## Calibration
|
||||
|
||||
Categorize issues by actual severity. Not everything is Critical.
|
||||
Acknowledge what was done well before listing issues — accurate praise
|
||||
helps the implementer trust the rest of the feedback.
|
||||
|
||||
## Output Format
|
||||
|
||||
### Strengths
|
||||
[What's well done? Be specific.]
|
||||
|
||||
### Issues
|
||||
|
||||
#### Critical (Must Fix)
|
||||
[Bugs, data loss risks, broken functionality]
|
||||
|
||||
#### Important (Should Fix)
|
||||
[Poor error handling, test gaps, structural problems]
|
||||
|
||||
#### Minor (Nice to Have)
|
||||
[Code style, optimization opportunities]
|
||||
|
||||
For each issue:
|
||||
- File:line reference
|
||||
- What's wrong
|
||||
- Why it matters
|
||||
- How to fix (if not obvious)
|
||||
|
||||
### Assessment
|
||||
|
||||
**Task quality:** [Approved | Needs fixes]
|
||||
|
||||
**Reasoning:** [1-2 sentence technical assessment]
|
||||
```
|
||||
|
||||
**Placeholders:**
|
||||
- `[DESCRIPTION]` — task summary, from implementer's report
|
||||
- `[TASK_TEXT]` — the task's requirements text or plan reference, for context
|
||||
- `[BASE_SHA]` — commit before this task
|
||||
- `[HEAD_SHA]` — current commit
|
||||
|
||||
**Reviewer returns:** Strengths, Issues (Critical/Important/Minor), Task quality verdict
|
||||
````
|
||||
|
||||
- [ ] **Step 2: Verify the rewrite landed**
|
||||
|
||||
Run: `grep -c "requesting-code-review" skills/subagent-driven-development/code-quality-reviewer-prompt.md || echo ABSENT`
|
||||
Expected: `ABSENT` (no more delegation)
|
||||
|
||||
Run: `grep -n "Task quality:" skills/subagent-driven-development/code-quality-reviewer-prompt.md | head -2`
|
||||
Expected: one match (the Output Format verdict line; the "Reviewer returns" footer says "Task quality verdict" without a colon)
|
||||
|
||||
Run: `grep -n "worktree add\|Ready to merge" skills/subagent-driven-development/code-quality-reviewer-prompt.md || echo CLEAN`
|
||||
Expected: `CLEAN`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/subagent-driven-development/code-quality-reviewer-prompt.md
|
||||
git commit -m "Make per-task quality reviewer prompt self-contained and task-scoped"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Spec reviewer prompt cleanups
|
||||
|
||||
Four exact edits to `skills/subagent-driven-development/spec-reviewer-prompt.md`. Current line numbers refer to the file as of commit f55642e.
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/subagent-driven-development/spec-reviewer-prompt.md`
|
||||
|
||||
- [ ] **Step 1: Add the judge-from-the-diff clause.** After the line (currently line 31):
|
||||
|
||||
```
|
||||
Only read files in this diff. Do not crawl the broader codebase.
|
||||
```
|
||||
|
||||
insert a blank line and:
|
||||
|
||||
```
|
||||
Spec compliance is judged by reading the diff against the requirements.
|
||||
The implementer already ran the tests and reported TDD evidence — do not
|
||||
re-run them. If a requirement cannot be verified from this diff alone
|
||||
(it lives in unchanged code or spans tasks), report it as a ⚠️ item
|
||||
instead of broadening your search.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Trim the read-only section.** Replace (currently line 35):
|
||||
|
||||
```
|
||||
Your review is read-only on this checkout. Do not mutate the working tree, the index, HEAD, or branch state in any way. Use tools like `git show`, `git diff`, and `git log` to inspect history. If you need a working copy of a different revision, check it out into a separate temporary directory (e.g. `git worktree add /tmp/review-[SHA] [SHA]`) — never move HEAD on this checkout.
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
Your review is read-only on this checkout. Do not mutate the working tree, the index, HEAD, or branch state in any way. Use tools like `git show`, `git diff`, and `git log` to inspect history.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Ground the skepticism.** Replace (currently lines 39-40):
|
||||
|
||||
```
|
||||
The implementer finished suspiciously quickly. Their report may be incomplete,
|
||||
inaccurate, or optimistic. You MUST verify everything independently.
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
Treat the implementer's report as unverified claims about the code. It may
|
||||
be incomplete, inaccurate, or optimistic. Verify the claims against the diff.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add the third verdict channel.** Replace (currently lines 74-76):
|
||||
|
||||
```
|
||||
Report:
|
||||
- ✅ Spec compliant (if everything matches after code inspection)
|
||||
- ❌ Issues found: [list specifically what's missing or extra, with file:line references]
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
Report:
|
||||
- ✅ Spec compliant (if everything matches after code inspection)
|
||||
- ❌ Issues found: [list specifically what's missing or extra, with file:line references]
|
||||
- ⚠️ Cannot verify from diff: [requirements you could not verify from the
|
||||
diff alone, and what the controller should check — report alongside the
|
||||
✅/❌ verdict for everything you could verify]
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Verify**
|
||||
|
||||
Run: `grep -n "suspiciously\|worktree add" skills/subagent-driven-development/spec-reviewer-prompt.md || echo CLEAN`
|
||||
Expected: `CLEAN`
|
||||
|
||||
Run: `grep -c "⚠️" skills/subagent-driven-development/spec-reviewer-prompt.md`
|
||||
Expected: `2` (judge-from-diff clause + verdict channel)
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/subagent-driven-development/spec-reviewer-prompt.md
|
||||
git commit -m "Spec reviewer: judge from the diff, grounded skepticism, ⚠️ verdict channel"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Implementer prompt — re-run tests after fixing review findings
|
||||
|
||||
The reviewers' "don't re-run the implementer's tests" rule assumes the implementer re-runs tests after every fix. Make that real.
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/subagent-driven-development/implementer-prompt.md`
|
||||
|
||||
- [ ] **Step 1: Insert a new section.** Immediately before the line (currently line 100):
|
||||
|
||||
```
|
||||
## Report Format
|
||||
```
|
||||
|
||||
insert:
|
||||
|
||||
```
|
||||
## After Review Findings
|
||||
|
||||
If a reviewer finds issues and you fix them, re-run the tests that cover
|
||||
the amended code and include the results in your fix report. Reviewers
|
||||
will not re-run tests for you — your report is the test evidence.
|
||||
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify**
|
||||
|
||||
Run: `grep -n "After Review Findings" skills/subagent-driven-development/implementer-prompt.md`
|
||||
Expected: one match, on a line before `## Report Format`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/subagent-driven-development/implementer-prompt.md
|
||||
git commit -m "Implementer prompt: re-run covering tests after fixing review findings"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: SKILL.md controller changes
|
||||
|
||||
Six exact edits to `skills/subagent-driven-development/SKILL.md`. Current line numbers refer to commit f55642e.
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/subagent-driven-development/SKILL.md`
|
||||
|
||||
- [ ] **Step 1: Point the final-review flowchart node at the broad template.** The node label `Dispatch final code reviewer subagent for entire implementation` appears 3 times (currently lines 65, 84, 85). In all 3 occurrences, replace the label string with:
|
||||
|
||||
```
|
||||
Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)
|
||||
```
|
||||
|
||||
(Graphviz nodes are matched by label text — all three must be byte-identical or the graph grows a phantom node.)
|
||||
|
||||
- [ ] **Step 2: Model selection by judgment.** Replace (currently lines 97-99):
|
||||
|
||||
```
|
||||
**Architecture, design, and review tasks**: use the most capable available model.
|
||||
|
||||
**Task complexity signals:**
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
**Architecture and design tasks**: use the most capable available model.
|
||||
|
||||
**Review tasks**: choose the model with the same judgment, scaled to the
|
||||
diff's size, complexity, and risk. A small mechanical diff does not need the
|
||||
most capable model; a subtle concurrency change does.
|
||||
|
||||
**Task complexity signals (implementation tasks):**
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add controller guidance sections.** Immediately before the line (currently line 122):
|
||||
|
||||
```
|
||||
## Prompt Templates
|
||||
```
|
||||
|
||||
insert:
|
||||
|
||||
```
|
||||
## Handling Spec Reviewer ⚠️ Items
|
||||
|
||||
The spec reviewer may report "⚠️ Cannot verify from diff" items — requirements
|
||||
that live in unchanged code or span tasks. These do not block dispatching the
|
||||
code quality reviewer, but you must resolve each one yourself before marking
|
||||
the task complete: you hold the plan and cross-task context the reviewer
|
||||
lacks. If you confirm an item is a real gap, treat it as a failed spec
|
||||
review — send it back to the implementer and re-review.
|
||||
|
||||
## Constructing Reviewer Prompts
|
||||
|
||||
Per-task reviews are task-scoped gates. The broad review happens once, at the
|
||||
final whole-branch review. When you fill a reviewer template:
|
||||
|
||||
- Do not add open-ended directives like "check all uses" or "run race tests
|
||||
if useful" without a concrete, task-specific reason
|
||||
- Do not ask a reviewer to re-run tests the implementer already ran on the
|
||||
same code — the implementer's report carries the test evidence
|
||||
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Prompt Templates list — add the final-review pointer.** Replace (currently line 126):
|
||||
|
||||
```
|
||||
- [code-quality-reviewer-prompt.md](code-quality-reviewer-prompt.md) - Dispatch code quality reviewer subagent
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
- [code-quality-reviewer-prompt.md](code-quality-reviewer-prompt.md) - Dispatch code quality reviewer subagent
|
||||
- Final whole-branch review: use superpowers:requesting-code-review's [code-reviewer.md](../requesting-code-review/code-reviewer.md)
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Example workflow verdict vocabulary.** Two replacements:
|
||||
|
||||
Replace (currently line 157):
|
||||
```
|
||||
Code reviewer: Strengths: Good test coverage, clean. Issues: None. Approved.
|
||||
```
|
||||
with:
|
||||
```
|
||||
Code reviewer: Strengths: Good test coverage, clean. Issues: None. Task quality: Approved.
|
||||
```
|
||||
|
||||
Replace (currently line 191):
|
||||
```
|
||||
Code reviewer: ✅ Approved
|
||||
```
|
||||
with:
|
||||
```
|
||||
Code reviewer: ✅ Task quality: Approved
|
||||
```
|
||||
|
||||
(The final reviewer's "ready to merge" line, currently line 199, stays.)
|
||||
|
||||
- [ ] **Step 6: Integration section.** Replace (currently line 272):
|
||||
|
||||
```
|
||||
- **superpowers:requesting-code-review** - Code review template for reviewer subagents
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
- **superpowers:requesting-code-review** - Code review template for the final whole-branch review
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Verify**
|
||||
|
||||
Run: `grep -c "Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" skills/subagent-driven-development/SKILL.md`
|
||||
Expected: `3`
|
||||
|
||||
Run: `grep -n "most capable available model" skills/subagent-driven-development/SKILL.md`
|
||||
Expected: exactly one match (architecture/design bullet)
|
||||
|
||||
Run: `grep -n "Handling Spec Reviewer\|Constructing Reviewer Prompts" skills/subagent-driven-development/SKILL.md`
|
||||
Expected: two section headers, both before `## Prompt Templates`
|
||||
|
||||
Run: `grep -c "Task quality: Approved" skills/subagent-driven-development/SKILL.md`
|
||||
Expected: `2`
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/subagent-driven-development/SKILL.md
|
||||
git commit -m "SDD controller: reviewer prompt budgets, ⚠️ handling, final-review pointer, model judgment"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: New eval scenario — per-task quality reviewer catches a planted defect
|
||||
|
||||
Lives in the `evals/` **submodule** (separate repo, `superpowers-evals`). Work on a branch there; the parent submodule-pointer bump happens at finishing time per `evals/CLAUDE.md`.
|
||||
|
||||
The fixture plan's Task 2 implementation snippet duplicates Task 1's formatting logic verbatim. The duplication is spec-compliant, so the spec reviewer should pass it — the per-task quality reviewer is the gate under test (DRY violation).
|
||||
|
||||
**Files:**
|
||||
- Create: `evals/setup_helpers/sdd_quality_defect_plan.py`
|
||||
- Modify: `evals/setup_helpers/__init__.py`
|
||||
- Create: `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/story.md`
|
||||
- Create: `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/setup.sh`
|
||||
- Create: `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/checks.sh`
|
||||
|
||||
- [ ] **Step 0: Branch in the submodule**
|
||||
|
||||
```bash
|
||||
cd evals
|
||||
git checkout -b sdd-quality-defect-scenario
|
||||
```
|
||||
|
||||
- [ ] **Step 1: Create `evals/setup_helpers/sdd_quality_defect_plan.py`:**
|
||||
|
||||
````python
|
||||
"""Setup helper for the sdd-quality-reviewer-catches-planted-defect scenario.
|
||||
|
||||
Scaffolds a tiny Node project with a 2-task plan whose Task 2
|
||||
implementation snippet duplicates Task 1's formatting logic verbatim.
|
||||
The duplication is spec-compliant — the requirements only describe
|
||||
behavior — so the spec compliance reviewer should pass it. The test
|
||||
measures whether the per-task code quality reviewer catches the DRY
|
||||
violation and forces a refactor in the review-fix loop.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from setup_helpers.base import _git
|
||||
|
||||
PACKAGE_JSON = """\
|
||||
{
|
||||
"name": "report-quality",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "node --test"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
PLAN_BODY = """\
|
||||
# Report Formatter — Implementation Plan
|
||||
|
||||
Two report formatting functions. Implement exactly what each task
|
||||
specifies.
|
||||
|
||||
## Task 1: User Report
|
||||
|
||||
**File:** `src/report.js`
|
||||
|
||||
**Requirements:**
|
||||
- Function named `formatUserReport`
|
||||
- Takes one parameter `user`: an object with `name`, `email`, `visits`
|
||||
- Returns a multi-line string: a banner of 40 `=` characters, then
|
||||
`Report for <name> <<email>>`, then the banner again, then
|
||||
`Visits: <visits>`, then a closing banner
|
||||
- Export the function
|
||||
|
||||
**Implementation:**
|
||||
```javascript
|
||||
export function formatUserReport(user) {
|
||||
const banner = "=".repeat(40);
|
||||
const lines = [];
|
||||
lines.push(banner);
|
||||
lines.push(`Report for ${user.name} <${user.email}>`);
|
||||
lines.push(banner);
|
||||
lines.push(`Visits: ${user.visits}`);
|
||||
lines.push(banner);
|
||||
return lines.join("\\n");
|
||||
}
|
||||
```
|
||||
|
||||
**Tests:** Create `test/report.test.js` verifying:
|
||||
- the result contains `Report for Ada <ada@example.com>` for that user
|
||||
- the result contains `Visits: 3` when `visits` is `3`
|
||||
- the result starts and ends with the 40-char banner
|
||||
|
||||
**Verification:** `npm test`
|
||||
|
||||
## Task 2: Admin Report
|
||||
|
||||
**File:** `src/report.js` (add to existing file)
|
||||
|
||||
**Requirements:**
|
||||
- Function named `formatAdminReport`
|
||||
- Takes one parameter `admin`: an object with `name`, `email`, `lastLogin`
|
||||
- Same banner layout as the user report; the body line is
|
||||
`Last login: <lastLogin>` instead of the visits line
|
||||
- Export the function; keep `formatUserReport` working
|
||||
|
||||
**Implementation:**
|
||||
```javascript
|
||||
export function formatAdminReport(admin) {
|
||||
const banner = "=".repeat(40);
|
||||
const lines = [];
|
||||
lines.push(banner);
|
||||
lines.push(`Report for ${admin.name} <${admin.email}>`);
|
||||
lines.push(banner);
|
||||
lines.push(`Last login: ${admin.lastLogin}`);
|
||||
lines.push(banner);
|
||||
return lines.join("\\n");
|
||||
}
|
||||
```
|
||||
|
||||
**Tests:** Add to `test/report.test.js`:
|
||||
- the result contains `Report for Grace <grace@example.com>` for that admin
|
||||
- the result contains `Last login: 2026-06-01`
|
||||
- the result starts and ends with the 40-char banner
|
||||
|
||||
**Verification:** `npm test`
|
||||
"""
|
||||
|
||||
|
||||
def scaffold_sdd_quality_defect_plan(workdir: Path) -> None:
|
||||
workdir = Path(workdir)
|
||||
workdir.mkdir(parents=True, exist_ok=True)
|
||||
_git(["git", "init", "-b", "main"], cwd=workdir)
|
||||
_git(["git", "config", "user.email", "drill@test.local"], cwd=workdir)
|
||||
_git(["git", "config", "user.name", "Drill Test"], cwd=workdir)
|
||||
|
||||
(workdir / "package.json").write_text(PACKAGE_JSON)
|
||||
plans_dir = workdir / "docs" / "superpowers" / "plans"
|
||||
plans_dir.mkdir(parents=True, exist_ok=True)
|
||||
(plans_dir / "report-plan.md").write_text(PLAN_BODY)
|
||||
|
||||
_git(["git", "add", "-A"], cwd=workdir)
|
||||
_git(["git", "commit", "-m", "initial: report formatter plan"], cwd=workdir)
|
||||
````
|
||||
|
||||
(Note the `\\n` in the JS snippets inside PLAN_BODY: the Python source must
|
||||
produce a literal `\n` in the markdown so the JS reads `lines.join("\n")`.)
|
||||
|
||||
- [ ] **Step 2: Register the helper.** In `evals/setup_helpers/__init__.py`:
|
||||
|
||||
After the line:
|
||||
```python
|
||||
from setup_helpers.sdd_real_projects import scaffold_sdd_go_fractals, scaffold_sdd_svelte_todo
|
||||
```
|
||||
add:
|
||||
```python
|
||||
from setup_helpers.sdd_quality_defect_plan import scaffold_sdd_quality_defect_plan
|
||||
```
|
||||
|
||||
After the registry entry:
|
||||
```python
|
||||
"scaffold_sdd_yagni_plan": scaffold_sdd_yagni_plan,
|
||||
```
|
||||
add:
|
||||
```python
|
||||
"scaffold_sdd_quality_defect_plan": scaffold_sdd_quality_defect_plan,
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Create `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/story.md`:**
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: sdd-quality-reviewer-catches-planted-defect
|
||||
title: SDD's per-task code quality review catches a planted DRY violation
|
||||
status: ready
|
||||
tags: subagent-driven-development
|
||||
quorum_max_time: 90m
|
||||
---
|
||||
|
||||
You have a small plan at docs/superpowers/plans/report-plan.md — two report
|
||||
formatting functions. The plan's Task 2 implementation snippet duplicates
|
||||
Task 1's formatting logic verbatim instead of sharing it. The duplication is
|
||||
spec-compliant (the requirements only describe behavior), so the spec
|
||||
compliance reviewer should pass it — the per-task code quality reviewer is
|
||||
the gate under test. You are spec-aware — name the skill.
|
||||
|
||||
When the agent is ready for input, tell it to execute the plan with SDD. Use
|
||||
phrasing like:
|
||||
|
||||
"I have a small plan at docs/superpowers/plans/report-plan.md — two report
|
||||
formatting functions. Use the superpowers:subagent-driven-development skill
|
||||
to execute it end-to-end — dispatch fresh subagents per task and run the
|
||||
two-stage review after each."
|
||||
|
||||
Let the agent proceed autonomously. If it asks clarifying questions, give
|
||||
brief answers. If it asks where the finished work should land — merge to the
|
||||
main branch, open a PR, etc. — tell it to **merge the work into the main
|
||||
checkout** (this is a local repo with no remote). If a quality reviewer
|
||||
flags the duplicated formatting logic and an implementer refactors it, let
|
||||
the review-fix cycle play out — that cycle is exactly the behavior under
|
||||
test.
|
||||
|
||||
The deliverable must end up in the checkout you launched in (the main
|
||||
working tree). If the agent did its work on a branch or in a worktree, it
|
||||
is not done until it has merged/finished that work back into the main
|
||||
checkout. Once the agent reports the plan is complete (both functions
|
||||
implemented, tests passing) AND the code is present on the main checkout,
|
||||
you are done.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- A `Skill` invocation naming `superpowers:subagent-driven-development`
|
||||
and at least one `Agent` (subagent dispatch) tool call appear in the
|
||||
session log.
|
||||
- The duplicated report-formatting logic did not survive to the end of
|
||||
the run. Either (a) the implementer never introduced the duplication
|
||||
(wrote or self-reviewed its way to shared logic), or (b) the per-task
|
||||
code quality reviewer flagged the duplication as an issue and a
|
||||
review-fix loop removed it. A fail looks like the duplicated logic
|
||||
shipping with the per-task quality reviewer approving it, or the
|
||||
duplication being caught only by the final whole-branch review.
|
||||
- The per-task quality reviewers stayed task-scoped: no package-wide
|
||||
test suites, race detector runs, or repeated/high-count test loops
|
||||
appear in reviewer subagent activity, and reviewers did not re-run
|
||||
the full test suite merely to confirm the implementer's report.
|
||||
- `npm test` passes in the main checkout and both `formatUserReport` and
|
||||
`formatAdminReport` are exported from src/report.js. The deterministic
|
||||
assertions gate this; the criteria above are about whether the
|
||||
*per-task quality review* was the mechanism that kept the code clean.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Create `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/setup.sh`:**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
uv run setup-helpers run scaffold_sdd_quality_defect_plan
|
||||
```
|
||||
|
||||
Then: `chmod +x evals/scenarios/sdd-quality-reviewer-catches-planted-defect/setup.sh`
|
||||
|
||||
- [ ] **Step 5: Create `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/checks.sh`** (no executable bit):
|
||||
|
||||
```bash
|
||||
pre() {
|
||||
git-repo
|
||||
git-branch main
|
||||
requires-tool npm
|
||||
file-exists 'docs/superpowers/plans/report-plan.md'
|
||||
file-contains 'docs/superpowers/plans/report-plan.md' 'formatAdminReport'
|
||||
file-contains 'docs/superpowers/plans/report-plan.md' 'repeat\(40\)'
|
||||
}
|
||||
|
||||
post() {
|
||||
skill-called superpowers:subagent-driven-development
|
||||
tool-called Agent
|
||||
command-succeeds 'npm test'
|
||||
file-contains 'src/report.js' 'export function formatUserReport'
|
||||
file-contains 'src/report.js' 'export function formatAdminReport'
|
||||
command-succeeds 'test "$(grep -c "repeat(40)" src/report.js)" -le 1'
|
||||
}
|
||||
```
|
||||
|
||||
(The last check is the deterministic DRY gate: the banner construction
|
||||
`"=".repeat(40)` must appear at most once in the final file — shared, not
|
||||
duplicated per function.)
|
||||
|
||||
- [ ] **Step 6: Validate and test in the evals repo**
|
||||
|
||||
```bash
|
||||
cd evals
|
||||
uv run quorum check
|
||||
uv run ruff check
|
||||
uv run pytest -x -q
|
||||
```
|
||||
|
||||
Expected: all pass; `quorum check` lists the new scenario without errors.
|
||||
|
||||
- [ ] **Step 7: Commit (in the submodule)**
|
||||
|
||||
```bash
|
||||
cd evals
|
||||
git add setup_helpers/sdd_quality_defect_plan.py setup_helpers/__init__.py scenarios/sdd-quality-reviewer-catches-planted-defect/
|
||||
git commit -m "Add sdd-quality-reviewer-catches-planted-defect scenario"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Static verification sweep
|
||||
|
||||
**Files:** none modified — verification only.
|
||||
|
||||
- [ ] **Step 1: No dangling references in the parent repo**
|
||||
|
||||
Run: `grep -rn "requesting-code-review" skills/subagent-driven-development/`
|
||||
Expected: matches only in SKILL.md (final-review flowchart node ×3, Prompt Templates pointer, Integration bullet). None in code-quality-reviewer-prompt.md.
|
||||
|
||||
Run: `grep -rn "Ready to merge" skills/subagent-driven-development/ || echo CLEAN`
|
||||
Expected: `CLEAN`
|
||||
|
||||
- [ ] **Step 2: Plugin infrastructure tests**
|
||||
|
||||
Run: `bash tests/shell-lint/test-lint-shell.sh`
|
||||
Expected: all PASS (we added `setup.sh` only inside the evals submodule, which has its own checks).
|
||||
|
||||
- [ ] **Step 3: Cross-platform tool tables still coherent**
|
||||
|
||||
Run: `grep -n "code-quality-reviewer" skills/using-superpowers/references/antigravity-tools.md skills/using-superpowers/references/gemini-tools.md`
|
||||
Expected: both tables still list `code-quality-reviewer` as a reviewer template (the new prompt's "If you cannot run commands in this environment, name the test you would run" line keeps the read-only `research` mapping valid — no table edits needed).
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Live before/after evals (maintainer-gated)
|
||||
|
||||
Live quorum runs launch agent CLIs in permissive modes — **trusted-maintainer operation; Jesse launches these**, per `evals/CLAUDE.md`. Requires `ANTHROPIC_API_KEY`.
|
||||
|
||||
- [ ] **Step 1: Baseline (skills as released on dev)** — from the main checkout (`/Users/jesse/git/superpowers/superpowers`, on dev), or any checkout without this branch's changes:
|
||||
|
||||
```bash
|
||||
cd evals
|
||||
export SUPERPOWERS_ROOT=/Users/jesse/git/superpowers/superpowers
|
||||
uv run quorum run scenarios/sdd-rejects-extra-features --coding-agent claude
|
||||
uv run quorum run scenarios/sdd-go-fractals --coding-agent claude
|
||||
uv run quorum run scenarios/sdd-svelte-todo --coding-agent claude
|
||||
uv run quorum run scenarios/spec-reviewer-catches-planted-flaws --coding-agent claude
|
||||
```
|
||||
|
||||
- [ ] **Step 2: After (this branch's skills)** — point `SUPERPOWERS_ROOT` at this worktree:
|
||||
|
||||
```bash
|
||||
cd evals
|
||||
export SUPERPOWERS_ROOT=/Users/jesse/git/superpowers/superpowers/.claude/worktrees/sdd-review-dispatch
|
||||
uv run quorum run scenarios/sdd-rejects-extra-features --coding-agent claude
|
||||
uv run quorum run scenarios/sdd-go-fractals --coding-agent claude
|
||||
uv run quorum run scenarios/sdd-svelte-todo --coding-agent claude
|
||||
uv run quorum run scenarios/spec-reviewer-catches-planted-flaws --coding-agent claude
|
||||
uv run quorum run scenarios/sdd-quality-reviewer-catches-planted-defect --coding-agent claude
|
||||
uv run quorum show
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Compare**
|
||||
|
||||
Pass bar: all four pre-existing scenarios still pass after the change (no regression in catch rate); the new planted-defect scenario passes. For exploration cost, compare reviewer-subagent tool-call counts between the before/after run transcripts (no automated check exists — the spec calls this out as a known gap).
|
||||
|
||||
---
|
||||
|
||||
## Finishing
|
||||
|
||||
After all tasks pass: the evals submodule commit needs to land in `superpowers-evals` (PR to its `main`), then this branch bumps the `evals` submodule pointer — per `evals/CLAUDE.md`, the parent bump is part of propagation, not optional. Then use superpowers:finishing-a-development-branch. PRs against superpowers target `dev`.
|
||||
@@ -0,0 +1,124 @@
|
||||
# SDD Task-Scoped Review Dispatch
|
||||
|
||||
Make subagent-driven-development's per-task reviews cheaper and faster without weakening them, by scoping per-task review prompts to the task and stopping redundant work — while final branch review stays broad.
|
||||
|
||||
## Problem
|
||||
|
||||
Per-task code quality reviewers in SDD routinely do branch-review-scale work on single-task diffs. Evidence from two real local SDD sessions: `a1a6719a-6109-453a-9933-34ae396f5bae` (sen-core-v2) and `0cc1a12d-9984-4c35-8615-9d42dadb2c47` (serf), both under `~/.claude/projects/`:
|
||||
|
||||
- In the sen-core-v2 session, 7/8 quality reviewers ran repo-wide greps; the most expensive ran 50+ Bash commands over ~200 seconds. Across both sessions, quality reviewers cost 4-8× what spec reviewers cost on the same tasks.
|
||||
- Spec reviewers, whose prompt contains "Only read files in this diff. Do not crawl the broader codebase," stayed tight: 6-16 tool calls, 14-65 seconds.
|
||||
- No reviewer ran heavy tests autonomously. Every package-wide or repeated test run observed was explicitly requested by a controller-written prompt ("check all uses," "run tests if useful, especially race-focused ones," "does anything else read `Meta()`?").
|
||||
|
||||
Root causes, in order of impact:
|
||||
|
||||
1. **The per-task quality prompt inherits a merge-readiness review.** `code-quality-reviewer-prompt.md` delegates to `requesting-code-review/code-reviewer.md`, which asks about architecture, scalability, security, production readiness, and ends with "Ready to merge?" That frame licenses branch-level breadth on a one-task diff. The spec prompt's diff-scope guard was never carried over.
|
||||
2. **The controller gets no guidance on writing reviewer prompts**, so it invents open-ended directives ("check all uses") that reviewers interpret literally.
|
||||
3. **Duplicated work across the pipeline.** The quality template's "Plan alignment" dimension re-checks what the spec reviewer just verified. Reviewers re-run test suites the implementer already ran (and reported, with TDD evidence) on identical code.
|
||||
4. **Per-task and final review share one template**, so there is no representation of "per-task narrow, final broad" anywhere.
|
||||
|
||||
A field report (`~/2026-06-09-code-quality-reviewer-scope-budget-issue.md`) first flagged this. Its cited session and headline numbers could not be verified, but its qualitative diagnosis was confirmed against two real local sessions. One correction to it: cross-cutting audits (lock ordering, changed contracts) are sometimes the *correct* review method — the fix must gate breadth behind a stated concrete risk, not forbid it.
|
||||
|
||||
## Goals
|
||||
|
||||
- Per-task reviews scoped to the task: diff-first reading, justified broadening, no redundant test runs.
|
||||
- Final whole-branch review keeps its current breadth.
|
||||
- No reduction in what reviews catch.
|
||||
|
||||
## Non-goals / explicitly preserved
|
||||
|
||||
- **Full re-reviews stay.** When a reviewer re-reviews after a fix, it still reviews the whole task at full reading breadth. (It does not re-run tests the implementer just ran on the amended code.) This deliberately rejects the field report's "re-review budget" remedy: the cost of its worst cited example (a re-review running `-race` and `-count=100` loops) is curbed by the test budget below, not by narrowing what re-reviewers read.
|
||||
- ~~**The two review stages stay separate.** Spec compliance and code quality remain independent subagents, serially gated. No merging.~~ **Superseded by the cost iterations below**: live eval economics showed per-dispatch overhead dominating cost, and the maintainer put everything on the table. The per-task stages are now one task reviewer with two verdicts; the independent broad final review remains.
|
||||
- **The coordinator keeps model judgment.** No forced model tier for reviews, in either direction.
|
||||
- **`requesting-code-review/` is untouched.** It remains the broad template for final branch review and ad-hoc review.
|
||||
- Verdict ordering (spec compliance reported before quality), the fix-and-re-review loops, and the requirement to fix Critical/Important findings are unchanged.
|
||||
|
||||
## Cost iterations (post-launch eval economics)
|
||||
|
||||
Live before/after runs surfaced a cost regression once the quality-hardening
|
||||
prose (evidence rule, constraint carrying, pristine output) landed: go-fractals
|
||||
went from 42.8 min / 14.5M tokens (first task-scoped version) to 69.9 min /
|
||||
32.2M (hardened version) while reaching baseline-parity quality (blind-judged
|
||||
8.5 vs 8.5). Per-subagent turn profiling attributed cost to, in order: cheap
|
||||
models taking 2-3× the turns on multi-step work (678 of 1197 subagent turns
|
||||
were haiku), per-dispatch overhead (3 subagent spin-ups per task, each
|
||||
re-deriving the diff; controller coordination was half the dollars), and
|
||||
evidence-rule narration.
|
||||
|
||||
- **Iteration 1:** turn-count-beats-token-price model guidance (mid-tier floor
|
||||
for multi-step work), optional inline diffs, cite-don't-narrate evidence,
|
||||
Important = cannot-trust-until-fixed, fixes dispatched only for
|
||||
Critical/Important. Result: 68.2 min / 22.9M — tokens down 29%, wall-clock
|
||||
flat; controllers pasted the diff in only 2 of 22 review dispatches when
|
||||
phrasing was optional.
|
||||
- **Iteration 2:** per-task spec and quality reviews merged into one
|
||||
`task-reviewer-prompt.md` (one reviewer, one reading of the diff, two
|
||||
verdicts; one fix dispatch addresses both kinds of findings); implementers
|
||||
run the focused test while iterating, full suite once before commit.
|
||||
Result (go-fractals): 47.5 min / 15.7M / $13.55 — beat baseline on every
|
||||
axis, blind-judged 9/10 vs baseline 7/10.
|
||||
- **Iteration 3:** Calibration names merge-blocking maintainability damage
|
||||
(verbatim duplication, swallowed errors, assertion-free tests) as
|
||||
Important and Minor findings must be pasted into the final review for
|
||||
triage; reviewer skepticism extended to the implementer's design
|
||||
rationales ("left it per YAGNI" is a claim, not a verdict); diff handed
|
||||
to reviewers as a file (`git diff > /tmp/sdd-task-N.diff`, redirected so
|
||||
it never enters the controller's context; one Read call for the
|
||||
reviewer) after paste-into-prompt guidance went unadopted (0-6 of 11-17
|
||||
dispatches) for locally-rational context-economics reasons.
|
||||
- **Final frozen config (e355795), all five scenarios pass:** go-fractals
|
||||
44.4 min / 13.4M / $11.67 (-32% time, -37% tokens, -27% dollars vs
|
||||
baseline); svelte-todo 62.8 / 19.7M / $15.76 (-21% / -28% / -25%);
|
||||
rejects-extra-features $1.31 (vs $1.88); spec-reviewer-flaws flat; the
|
||||
planted-defect scenario (v3: open-flag transparency bar for judgment
|
||||
calls, must-fix bar for a test whose name promises verification it
|
||||
never performs) passes with the defect caught and fixed.
|
||||
|
||||
## Design
|
||||
|
||||
### Shared principle: don't re-run tests on code that hasn't changed
|
||||
|
||||
The implementer's report includes test results and TDD RED/GREEN evidence for exactly the code under review. Reviewers verify by reading. A reviewer runs a test only when reading raises a specific doubt that no existing run answers — and then a focused test, not a suite. On harnesses where reviewer subagents are read-only (e.g., Antigravity maps reviewer templates to the `research` type, which has no command access), the reviewer instead names the test it would run in its report.
|
||||
|
||||
After a fix, the implementer re-runs the tests covering the amended code; the re-reviewer does not repeat that run. Today nothing enforces that premise: `implementer-prompt.md` describes the initial implement-test-commit flow only, with no fix-iteration instruction. This spec therefore also adds to `implementer-prompt.md`: after fixing a review finding, re-run the tests that cover the amended code and include the results in the fix report.
|
||||
|
||||
This principle appears in both reviewer prompts, the implementer prompt, and the controller guidance.
|
||||
|
||||
### 1. New file: `skills/subagent-driven-development/code-quality-reviewer-prompt.md` becomes self-contained
|
||||
|
||||
Stop delegating to `requesting-code-review/code-reviewer.md`. The per-task quality reviewer gets its own scoped prompt template:
|
||||
|
||||
- **Framing:** "You are reviewing one task's implementation for code quality." A task-scoped gate, not a merge review.
|
||||
- **Spec compliance is settled:** spec review already passed; do not re-litigate requirements or plan alignment.
|
||||
- **Review dimensions kept:** code quality (clarity, duplication, error handling), test quality (real behavior, not mocks), maintainability, and the existing SDD-specific checks (single responsibility, independent testability, file structure from plan, file growth contributed by this change). Dropped: plan alignment, security/scalability/production-readiness dimensions, merge verdict.
|
||||
- **Scope budget:** start from `git diff BASE..HEAD`; read changed files first; inspect adjacent code only to evaluate a concrete risk you can name. Cross-cutting changes — lock ordering, changed function/API contracts, shared mutable state — are legitimate named risks that justify checking call sites. Do not crawl the codebase by default.
|
||||
- **Test budget:** the shared principle above, plus: no package-wide suites, race detectors, or repeated/high-count runs unless you have first named a specific suspected flake or race. Otherwise, recommend heavy validation in the report instead of running it. Warnings or noise in the implementer's reported test output are findings — output should be pristine (the implementer's self-review checks this too).
|
||||
- **Evidence rule:** reviewers answer each What-to-Check item with file:line evidence, not bare yes/no. (Added after live eval runs showed reviewers passing defects the prompt had pointed them at — an accessible-name check and a temp-dir-cleanup check both got unsupported "yes" answers while the defect sat in the reviewed diff.)
|
||||
- **Read-only rule** kept in trimmed form: no mutating the working tree, index, HEAD, or branch state. The `git worktree add` how-to sentence from the current templates is NOT carried into this file — a diff-scoped review never needs a checkout of another revision (same rationale as the spec-prompt cleanup below).
|
||||
- **Verdict:** Strengths / Issues (Critical/Important/Minor) / "Task quality: Approved | Needs fixes."
|
||||
|
||||
### 2. `skills/subagent-driven-development/spec-reviewer-prompt.md` cleanups
|
||||
|
||||
- Remove the `git worktree add` how-to sentence. The read-only rule stays; a diff-scoped spec review never needs a checkout of another revision.
|
||||
- Resolve the tension between the diff-only guard and "verify everything independently": spec compliance is judged by reading the diff against the requirements. The implementer's TDD evidence covers "it runs" — apply the shared test principle.
|
||||
- New third verdict channel: requirements that cannot be verified from the diff (live in unchanged code, span tasks) are reported as explicit "⚠️ Cannot verify from diff — controller should check X" items, instead of either crawling or silently passing. The flowchart's binary pass/fail diamond cannot route this, so the controller guidance (§3) defines the handling: ⚠️ items do not block dispatching the quality reviewer, but the controller must resolve each one itself (it holds the plan and cross-task context) before marking the task complete; an item the controller confirms is a real gap is treated as a failed spec review and goes back to the implementer.
|
||||
- Replace the fabricated premise "The implementer finished suspiciously quickly" with grounded skepticism: treat the implementer's report as unverified claims about the code. Same distrust, no invented fact.
|
||||
|
||||
### 3. `skills/subagent-driven-development/SKILL.md` controller changes
|
||||
|
||||
- **Model Selection:** replace "Architecture, design, and review tasks: use the most capable available model" with judgment guidance — pick reviewer models the way implementer models are picked, scaled to the diff's size, complexity, and risk. The "Task complexity signals" list is rescoped to make clear its bullets describe implementation tasks; reviewer model choice follows the same judgment, so a narrow diff review does not automatically map to "broad codebase understanding → most capable model."
|
||||
- **Reviewer prompt construction** (new guidance near Red Flags): when dispatching reviewers, do not write open-ended directives ("check all uses," "run race tests if useful") without a concrete task-specific reason; do not ask reviewers to re-run tests the implementer already ran on the same code; do not pre-judge findings for the reviewer (never instruct a reviewer to ignore or not flag a specific issue — adjudicate suspected false positives in the review loop instead); per-task reviews are task-scoped gates — the broad review happens once, at the final whole-branch review. (The pre-judging rule was added after a live eval run caught the controller fabricating a "the plan forbids a shared helper" claim and instructing the quality reviewer not to flag a planted DRY violation.) Controllers must also include the spec/design's global constraints that bind the task — version floors, naming and copy rules, platform requirements — in the requirements they paste: a live run shipped a `go 1.26.1` module floor against a "Go 1.21+" design because no reviewer ever saw the constraint. And controllers must specify a model explicitly on every dispatch — an omitted model inherits the session's (usually most expensive) model, which silently defeats model selection.
|
||||
- **Handling spec-reviewer ⚠️ items** (new guidance, alongside Handling Implementer Status): the controller resolves each "cannot verify from diff" item itself before marking the task complete; confirmed gaps go back to the implementer as failed spec review.
|
||||
- **Final review stays broad, explicitly:** the final whole-branch reviewer dispatch node gains an explicit pointer to `../requesting-code-review/code-reviewer.md`. (Today that template is reachable only through the per-task quality prompt's delegation; once that delegation is removed, an unreferenced final-review template would be orphaned.) The Integration section's note that `superpowers:requesting-code-review` provides "the code review template for reviewer subagents" is corrected to apply to the final review only.
|
||||
- **Example workflow:** the quality-reviewer lines in the example are updated to the new verdict vocabulary ("Task quality: Approved"); the final reviewer's "ready to merge" line stays.
|
||||
- Flowchart topology is unchanged; the ⚠️ channel is handled by controller guidance, not a new graph branch.
|
||||
|
||||
## What this does not fix (known, deferred)
|
||||
|
||||
The spec reviewer judges against task text the controller pasted; it cannot catch requirements dropped during the controller's extraction from the plan. That is an architectural property of "controller provides full text," not a prompt problem, and is out of scope here.
|
||||
|
||||
## Verification
|
||||
|
||||
- Plugin infrastructure tests (`tests/`) still pass.
|
||||
- Run the SDD skill-behavior evals (`git submodule update --init evals`, then per `evals/README.md`) before and after the change. Specifically: `sdd-go-fractals`, `sdd-svelte-todo`, `sdd-rejects-extra-features` (end-to-end SDD including the spec reviewer's YAGNI gate), and `spec-reviewer-catches-planted-flaws`.
|
||||
- Known eval gaps this change exposes: no existing scenario plants a code-quality defect inside a single SDD task and asserts the per-task quality reviewer catches it, and no scenario measures per-reviewer exploration cost (tool-call/grep counts). Add one scenario covering the first gap (planted single-task quality defect → per-task reviewer must flag it before final review). For exploration cost, compare reviewer subagent tool-call counts manually across the before/after eval transcripts.
|
||||
@@ -12,6 +12,7 @@ Live in `tests/`. Currently:
|
||||
- `tests/brainstorm-server/` — node test suite for the brainstorm server JS code.
|
||||
- `tests/opencode/` — bash tests for OpenCode plugin loading, bootstrap caching, and tool registration.
|
||||
- `tests/codex-plugin-sync/` — bash sync verification.
|
||||
- `tests/kimi/` — bash/Python checks for Kimi plugin manifest wiring.
|
||||
- `tests/claude-code/test-helpers.sh`, `analyze-token-usage.py` — utilities used by remaining bash tests.
|
||||
- `tests/claude-code/test-subagent-driven-development.sh` — agent-can-describe-SDD test (no drill counterpart; tests description-recall, not behavior).
|
||||
- `tests/claude-code/test-subagent-driven-development-integration.sh` — extended SDD integration with token analysis (drill covers the YAGNI subset; bash adds commit-count, Claude Code task-tracking, and token telemetry assertions).
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Cross-Platform Polyglot Hooks for Claude Code
|
||||
|
||||
Claude Code plugins need hooks that work on Windows, macOS, and Linux. This document explains the polyglot wrapper technique that makes this possible.
|
||||
Claude Code plugins need hooks that work on Windows, macOS, and Linux. This document describes the single generic dispatcher pattern used in `hooks/run-hook.cmd`.
|
||||
|
||||
> **Authoritative source:** `hooks/run-hook.cmd` is the canonical implementation. When this document and the code diverge, trust the code.
|
||||
|
||||
## The Problem
|
||||
|
||||
@@ -10,52 +12,22 @@ Claude Code runs hook commands through the system's default shell:
|
||||
|
||||
This creates several challenges:
|
||||
|
||||
1. **Script execution**: Windows CMD can't execute `.sh` files directly - it tries to open them in a text editor
|
||||
1. **Script execution**: Windows CMD can't execute `.sh` files directly
|
||||
2. **Path format**: Windows uses backslashes (`C:\path`), Unix uses forward slashes (`/path`)
|
||||
3. **Environment variables**: `$VAR` syntax doesn't work in CMD
|
||||
4. **No `bash` in PATH**: Even with Git Bash installed, `bash` isn't in the PATH when CMD runs
|
||||
4. **`.sh` auto-prepend**: Claude Code on Windows automatically prepends `bash` to any command that contains `.sh` in its path — this interferes with the dispatcher if scripts have extensions
|
||||
|
||||
## The Solution: Polyglot `.cmd` Wrapper
|
||||
## The Solution: Extensionless Scripts + Single Generic Dispatcher
|
||||
|
||||
A polyglot script is valid syntax in multiple languages simultaneously. Our wrapper is valid in both CMD and bash:
|
||||
The repo uses one generic `run-hook.cmd` dispatcher for all hooks. Hook scripts are **extensionless** (`session-start`, not `session-start.sh`). This is deliberate: it prevents Claude Code's Windows auto-detection from prepending `bash` to the dispatcher command and breaking it.
|
||||
|
||||
```cmd
|
||||
: << 'CMDBLOCK'
|
||||
@echo off
|
||||
"C:\Program Files\Git\bin\bash.exe" -l -c "\"$(cygpath -u \"$CLAUDE_PLUGIN_ROOT\")/hooks/session-start.sh\""
|
||||
exit /b
|
||||
CMDBLOCK
|
||||
|
||||
# Unix shell runs from here
|
||||
"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
#### On Windows (CMD.exe)
|
||||
|
||||
1. `: << 'CMDBLOCK'` - CMD sees `:` as a label (like `:label`) and ignores `<< 'CMDBLOCK'`
|
||||
2. `@echo off` - Suppresses command echoing
|
||||
3. The bash.exe command runs with:
|
||||
- `-l` (login shell) to get proper PATH with Unix utilities
|
||||
- `cygpath -u` converts Windows path to Unix format (`C:\foo` → `/c/foo`)
|
||||
4. `exit /b` - Exits the batch script, stopping CMD here
|
||||
5. Everything after `CMDBLOCK` is never reached by CMD
|
||||
|
||||
#### On Unix (bash/sh)
|
||||
|
||||
1. `: << 'CMDBLOCK'` - `:` is a no-op, `<< 'CMDBLOCK'` starts a heredoc
|
||||
2. Everything until `CMDBLOCK` is consumed by the heredoc (ignored)
|
||||
3. `# Unix shell runs from here` - Comment
|
||||
4. The script runs directly with the Unix path
|
||||
|
||||
## File Structure
|
||||
### File Structure
|
||||
|
||||
```
|
||||
hooks/
|
||||
├── hooks.json # Points to the .cmd wrapper
|
||||
├── session-start.cmd # Polyglot wrapper (cross-platform entry point)
|
||||
└── session-start.sh # Actual hook logic (bash script)
|
||||
├── hooks.json # Points to run-hook.cmd with extensionless script name
|
||||
├── run-hook.cmd # Cross-platform dispatcher (the polyglot wrapper)
|
||||
└── session-start # Actual hook logic — extensionless bash script
|
||||
```
|
||||
|
||||
### hooks.json
|
||||
@@ -65,11 +37,12 @@ hooks/
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup|resume|clear|compact",
|
||||
"matcher": "startup|clear|compact",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.cmd\""
|
||||
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start",
|
||||
"async": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -78,41 +51,63 @@ hooks/
|
||||
}
|
||||
```
|
||||
|
||||
Note: The path must be quoted because `${CLAUDE_PLUGIN_ROOT}` may contain spaces on Windows (e.g., `C:\Program Files\...`).
|
||||
The path is quoted because `${CLAUDE_PLUGIN_ROOT}` may contain spaces.
|
||||
|
||||
## Requirements
|
||||
## How `run-hook.cmd` Works at a High Level
|
||||
|
||||
### Windows
|
||||
- **Git for Windows** must be installed (provides `bash.exe` and `cygpath`)
|
||||
- Default installation path: `C:\Program Files\Git\bin\bash.exe`
|
||||
- If Git is installed elsewhere, the wrapper needs modification
|
||||
`run-hook.cmd` is a polyglot script: Windows treats the first block as batch
|
||||
commands, while Unix shells treat that block as a no-op heredoc and continue
|
||||
after it.
|
||||
|
||||
### Unix (macOS/Linux)
|
||||
- Standard bash or sh shell
|
||||
- The `.cmd` file must have execute permission (`chmod +x`)
|
||||
Do not copy an implementation from this document. Read `hooks/run-hook.cmd`
|
||||
directly when changing the dispatcher, and run `tests/hooks/test-session-start.sh`
|
||||
afterward.
|
||||
|
||||
### How it works on Windows (CMD.exe)
|
||||
|
||||
1. The batch section validates the script name and resolves the hook directory
|
||||
from the dispatcher's own location.
|
||||
2. It tries bash in three places:
|
||||
- `C:\Program Files\Git\bin\bash.exe`
|
||||
- `C:\Program Files (x86)\Git\bin\bash.exe`
|
||||
- `bash` on `PATH` (MSYS2, Cygwin, or a non-default Git install)
|
||||
3. If bash is found, it runs the named extensionless hook script from the hooks
|
||||
directory.
|
||||
4. If no bash is found, the dispatcher exits `0` silently — the plugin
|
||||
continues working, it just skips the hook.
|
||||
5. `exit /b` stops CMD before it reaches the Unix section.
|
||||
|
||||
### How it works on Unix (bash/sh)
|
||||
|
||||
1. `: << 'CMDBLOCK'` opens a heredoc on a no-op command.
|
||||
2. The entire CMD batch block is consumed by the heredoc and ignored.
|
||||
3. After `CMDBLOCK`, bash resolves the script directory and `exec`s the named
|
||||
extensionless script directly.
|
||||
|
||||
### Key design decisions
|
||||
|
||||
| Decision | Why |
|
||||
|----------|-----|
|
||||
| Extensionless scripts | Prevents Claude Code's Windows `.sh`-auto-prepend from interfering with the dispatcher command |
|
||||
| No `-l` (login shell) | Not needed; hook scripts should be self-contained and not depend on login-shell PATH setup |
|
||||
| No `cygpath` | Bash receives the Windows path directly and handles it correctly; `cygpath` was needed by the old `-c "..."` invocation pattern, not by direct exec |
|
||||
| Silent exit on no-bash | Avoids breaking the plugin for users who don't have Git for Windows; hook context injection is skipped gracefully |
|
||||
|
||||
## Writing Cross-Platform Hook Scripts
|
||||
|
||||
Your actual hook logic goes in the `.sh` file. To ensure it works on Windows (via Git Bash):
|
||||
Your hook logic goes in the extensionless script file. A few portable patterns:
|
||||
|
||||
### Do:
|
||||
### Do
|
||||
- Use pure bash builtins when possible
|
||||
- Use `$(command)` instead of backticks
|
||||
- Quote all variable expansions: `"$VAR"`
|
||||
- Use `printf` or here-docs for output
|
||||
|
||||
### Avoid:
|
||||
- External commands that may not be in PATH (sed, awk, grep)
|
||||
- If you must use them, they're available in Git Bash but ensure PATH is set up (use `bash -l`)
|
||||
### Avoid
|
||||
- Relying on PATH-dependent tools without fallbacks (the hook runs without `-l`, so login-shell PATH is not set)
|
||||
- Giving scripts a `.sh` extension — this triggers Claude Code's Windows auto-prepend
|
||||
|
||||
### Example: JSON Escaping Without sed/awk
|
||||
### Example: JSON escaping without external tools
|
||||
|
||||
Instead of:
|
||||
```bash
|
||||
escaped=$(echo "$content" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}')
|
||||
```
|
||||
|
||||
Use pure bash:
|
||||
```bash
|
||||
escape_for_json() {
|
||||
local input="$1"
|
||||
@@ -133,80 +128,21 @@ escape_for_json() {
|
||||
}
|
||||
```
|
||||
|
||||
## Reusable Wrapper Pattern
|
||||
|
||||
For plugins with multiple hooks, you can create a generic wrapper that takes the script name as an argument:
|
||||
|
||||
### run-hook.cmd
|
||||
```cmd
|
||||
: << 'CMDBLOCK'
|
||||
@echo off
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
set "SCRIPT_NAME=%~1"
|
||||
"C:\Program Files\Git\bin\bash.exe" -l -c "cd \"$(cygpath -u \"%SCRIPT_DIR%\")\" && \"./%SCRIPT_NAME%\""
|
||||
exit /b
|
||||
CMDBLOCK
|
||||
|
||||
# Unix shell runs from here
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT_NAME="$1"
|
||||
shift
|
||||
"${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"
|
||||
```
|
||||
|
||||
### hooks.json using the reusable wrapper
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" validate-bash.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "bash is not recognized"
|
||||
CMD can't find bash. The wrapper uses the full path `C:\Program Files\Git\bin\bash.exe`. If Git is installed elsewhere, update the path.
|
||||
|
||||
### "cygpath: command not found" or "dirname: command not found"
|
||||
Bash isn't running as a login shell. Ensure `-l` flag is used.
|
||||
CMD couldn't find bash in any of the three locations the dispatcher tries. The dispatcher exits silently (0) rather than erroring, so the hook is skipped. Install Git for Windows at the standard path or ensure `bash` is on `PATH`.
|
||||
|
||||
### Path has weird `\/` in it
|
||||
`${CLAUDE_PLUGIN_ROOT}` expanded to a Windows path ending with backslash, then `/hooks/...` was appended. Use `cygpath` to convert the entire path.
|
||||
### Hook runs on Unix but does nothing on Windows
|
||||
|
||||
### Script opens in text editor instead of running
|
||||
The hooks.json is pointing directly to the `.sh` file. Point to the `.cmd` wrapper instead.
|
||||
Check that the script filename is **extensionless** in `hooks.json`. A command like `run-hook.cmd session-start.sh` can trigger Claude Code's `.sh` auto-detection and bypass the intended CMD dispatcher path, or just try to run a non-existent `session-start.sh` script.
|
||||
|
||||
### Works in terminal but not as hook
|
||||
Claude Code may run hooks differently. Test by simulating the hook environment:
|
||||
```powershell
|
||||
$env:CLAUDE_PLUGIN_ROOT = "C:\path\to\plugin"
|
||||
cmd /c "C:\path\to\plugin\hooks\session-start.cmd"
|
||||
```
|
||||
### Hook doesn't fire at all
|
||||
|
||||
Verify the `matcher` in `hooks.json` matches the event type your harness emits. Claude Code uses `startup|clear|compact`; Codex uses `startup|resume|clear`. Check `hooks-codex.json` for the Codex variant.
|
||||
|
||||
## Related Issues
|
||||
|
||||
- [anthropics/claude-code#9758](https://github.com/anthropics/claude-code/issues/9758) - .sh scripts open in editor on Windows
|
||||
- [anthropics/claude-code#3417](https://github.com/anthropics/claude-code/issues/3417) - Hooks don't work on Windows
|
||||
- [anthropics/claude-code#6023](https://github.com/anthropics/claude-code/issues/6023) - CLAUDE_PROJECT_DIR not found
|
||||
- [anthropics/claude-code#9758](https://github.com/anthropics/claude-code/issues/9758) — `.sh` scripts open in editor on Windows
|
||||
- [anthropics/claude-code#3417](https://github.com/anthropics/claude-code/issues/3417) — Hooks don't work on Windows
|
||||
|
||||
2
evals
2
evals
Submodule evals updated: e2b37138c8...ff3ee83f94
211
scripts/lint-shell.sh
Executable file
211
scripts/lint-shell.sh
Executable file
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Lint shell scripts in this repository.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/lint-shell.sh [--all] [--format] [--strict] [file ...]
|
||||
#
|
||||
# By default, runs ShellCheck and shell syntax checks on changed shell scripts.
|
||||
# Use --format to format with shfmt before linting. Use --all for the full tracked
|
||||
# baseline, or pass files explicitly to lint a smaller set.
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
die() {
|
||||
echo "error: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_tool() {
|
||||
command -v "$1" >/dev/null 2>&1 || die "required tool '$1' is not on PATH"
|
||||
}
|
||||
|
||||
is_shell_file() {
|
||||
local path="$1"
|
||||
local first_line=""
|
||||
|
||||
[[ -f "$path" ]] || return 1
|
||||
|
||||
case "$path" in
|
||||
*.sh)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
IFS= read -r first_line <"$path" || true
|
||||
[[ "$first_line" =~ ^#!.*[/[:space:]](bash|dash|ksh|sh)([[:space:]]|$) ]]
|
||||
}
|
||||
|
||||
ensure_git_work_tree() {
|
||||
git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
||||
|| die "run this from inside a git work tree, or pass files explicitly"
|
||||
}
|
||||
|
||||
add_shell_file() {
|
||||
local path
|
||||
local existing
|
||||
|
||||
path="$1"
|
||||
if ! is_shell_file "$path"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "${#files[@]}" -gt 0 ]]; then
|
||||
for existing in "${files[@]}"; do
|
||||
if [[ "$existing" == "$path" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
files+=("$path")
|
||||
}
|
||||
|
||||
collect_all_shell_files() {
|
||||
local path
|
||||
|
||||
ensure_git_work_tree
|
||||
|
||||
while IFS= read -r -d '' path; do
|
||||
add_shell_file "$path"
|
||||
done < <(git ls-files -z)
|
||||
}
|
||||
|
||||
collect_changed_shell_files() {
|
||||
local path
|
||||
|
||||
ensure_git_work_tree
|
||||
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1; then
|
||||
while IFS= read -r -d '' path; do
|
||||
add_shell_file "$path"
|
||||
done < <(git diff --name-only -z --diff-filter=ACMR HEAD)
|
||||
|
||||
while IFS= read -r -d '' path; do
|
||||
add_shell_file "$path"
|
||||
done < <(git diff --cached --name-only -z --diff-filter=ACMR)
|
||||
else
|
||||
collect_all_shell_files
|
||||
fi
|
||||
|
||||
while IFS= read -r -d '' path; do
|
||||
add_shell_file "$path"
|
||||
done < <(git ls-files --others --exclude-standard -z)
|
||||
}
|
||||
|
||||
collect_requested_shell_files() {
|
||||
local path
|
||||
|
||||
for path in "$@"; do
|
||||
add_shell_file "$path"
|
||||
done
|
||||
}
|
||||
|
||||
syntax_shell_for() {
|
||||
local path="$1"
|
||||
local first_line=""
|
||||
|
||||
IFS= read -r first_line <"$path" || true
|
||||
|
||||
case "$first_line" in
|
||||
*"/sh"* | *" env sh"* | *"/dash"* | *" env dash"*)
|
||||
printf 'sh'
|
||||
;;
|
||||
*)
|
||||
printf 'bash'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_syntax_checks() {
|
||||
local file
|
||||
local shell_name
|
||||
|
||||
for file in "$@"; do
|
||||
shell_name="$(syntax_shell_for "$file")"
|
||||
case "$shell_name" in
|
||||
sh)
|
||||
sh -n "$file"
|
||||
;;
|
||||
bash)
|
||||
bash -n "$file"
|
||||
;;
|
||||
*)
|
||||
die "unsupported shell for syntax check: $shell_name"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
format=false
|
||||
strict=false
|
||||
all=false
|
||||
requested_files=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--all)
|
||||
all=true
|
||||
;;
|
||||
--format)
|
||||
format=true
|
||||
;;
|
||||
--strict)
|
||||
strict=true
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
requested_files+=("$@")
|
||||
break
|
||||
;;
|
||||
-*)
|
||||
die "unknown option: $1"
|
||||
;;
|
||||
*)
|
||||
requested_files+=("$1")
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
require_tool shellcheck
|
||||
if [[ "$format" == true ]]; then
|
||||
require_tool shfmt
|
||||
fi
|
||||
|
||||
files=()
|
||||
if [[ "${#requested_files[@]}" -gt 0 ]]; then
|
||||
collect_requested_shell_files "${requested_files[@]}"
|
||||
elif [[ "$all" == true ]]; then
|
||||
collect_all_shell_files
|
||||
else
|
||||
collect_changed_shell_files
|
||||
fi
|
||||
|
||||
if [[ "${#files[@]}" -eq 0 ]]; then
|
||||
echo "No shell files found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$format" == true ]]; then
|
||||
echo "Formatting ${#files[@]} shell files"
|
||||
shfmt_args=(-i 2 -ci -bn)
|
||||
shfmt "${shfmt_args[@]}" -w "${files[@]}"
|
||||
fi
|
||||
|
||||
echo "Linting ${#files[@]} shell files"
|
||||
|
||||
shellcheck_args=(--severity=warning --external-sources --source-path=SCRIPTDIR)
|
||||
if [[ "$strict" == true ]]; then
|
||||
shellcheck_args+=("--enable=check-extra-masked-returns,check-set-e-suppressed,quote-safe-variables,deprecate-which,avoid-nullary-conditions")
|
||||
fi
|
||||
|
||||
shellcheck "${shellcheck_args[@]}" "${files[@]}"
|
||||
run_syntax_checks "${files[@]}"
|
||||
@@ -52,7 +52,9 @@ EXCLUDES=(
|
||||
"/.gitattributes"
|
||||
"/.github/"
|
||||
"/.gitignore"
|
||||
"/.kimi-plugin/"
|
||||
"/.opencode/"
|
||||
"/.pi/"
|
||||
"/.version-bump.json"
|
||||
"/.worktrees/"
|
||||
".DS_Store"
|
||||
|
||||
@@ -7,6 +7,7 @@ const path = require('path');
|
||||
|
||||
const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A };
|
||||
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
const MAX_FRAME_PAYLOAD_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
function computeAcceptKey(clientKey) {
|
||||
return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64');
|
||||
@@ -53,10 +54,18 @@ function decodeFrame(buffer) {
|
||||
offset = 4;
|
||||
} else if (payloadLen === 127) {
|
||||
if (buffer.length < 10) return null;
|
||||
payloadLen = Number(buffer.readBigUInt64BE(2));
|
||||
const extendedLen = buffer.readBigUInt64BE(2);
|
||||
if (extendedLen > BigInt(MAX_FRAME_PAYLOAD_BYTES)) {
|
||||
throw new Error('WebSocket frame payload exceeds maximum allowed size');
|
||||
}
|
||||
payloadLen = Number(extendedLen);
|
||||
offset = 10;
|
||||
}
|
||||
|
||||
if (payloadLen > MAX_FRAME_PAYLOAD_BYTES) {
|
||||
throw new Error('WebSocket frame payload exceeds maximum allowed size');
|
||||
}
|
||||
|
||||
const maskOffset = offset;
|
||||
const dataOffset = offset + 4;
|
||||
const totalLen = dataOffset + payloadLen;
|
||||
@@ -351,4 +360,4 @@ if (require.main === module) {
|
||||
startServer();
|
||||
}
|
||||
|
||||
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES };
|
||||
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES, MAX_FRAME_PAYLOAD_BYTES };
|
||||
|
||||
@@ -107,10 +107,23 @@ if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then
|
||||
OWNER_PID="$PPID"
|
||||
fi
|
||||
|
||||
# Windows/MSYS2: Node.js cannot see POSIX PIDs from the MSYS2 namespace.
|
||||
# Passing a PID node cannot verify causes server to log owner-pid-invalid
|
||||
# and self-terminate at the 60-second lifecycle check. Clear it so the
|
||||
# watchdog is disabled and the idle timeout becomes the only shutdown trigger.
|
||||
case "${OSTYPE:-}" in
|
||||
msys*|cygwin*|mingw*) OWNER_PID="" ;;
|
||||
esac
|
||||
if [[ -n "${MSYSTEM:-}" ]]; then
|
||||
OWNER_PID=""
|
||||
fi
|
||||
|
||||
# Foreground mode for environments that reap detached/background processes.
|
||||
if [[ "$FOREGROUND" == "true" ]]; then
|
||||
echo "$$" > "$PID_FILE"
|
||||
env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs
|
||||
env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs &
|
||||
SERVER_PID=$!
|
||||
echo "$SERVER_PID" > "$PID_FILE"
|
||||
wait "$SERVER_PID"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
|
||||
@@ -123,16 +123,6 @@ git branch -d <feature-branch>
|
||||
```bash
|
||||
# Push branch
|
||||
git push -u origin <feature-branch>
|
||||
|
||||
# Create PR
|
||||
gh pr create --title "<title>" --body "$(cat <<'EOF'
|
||||
## Summary
|
||||
<2-3 bullets of what changed>
|
||||
|
||||
## Test Plan
|
||||
- [ ] <verification steps>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
**Do NOT clean up worktree** — user needs it alive to iterate on PR feedback.
|
||||
|
||||
@@ -5,11 +5,11 @@ description: Use when executing implementation plans with independent tasks in t
|
||||
|
||||
# Subagent-Driven Development
|
||||
|
||||
Execute plan by dispatching fresh subagent per task, with two-stage review after each: spec compliance review first, then code quality review.
|
||||
Execute plan by dispatching a fresh implementer subagent per task, a combined task review (spec compliance + code quality, one reviewer, one reading of the diff) after each, and a broad whole-branch review at the end.
|
||||
|
||||
**Why subagents:** You delegate tasks to specialized agents with isolated context. By precisely crafting their instructions and context, you ensure they stay focused and succeed at their task. They should never inherit your session's context or history — you construct exactly what they need. This also preserves your own context for coordination work.
|
||||
|
||||
**Core principle:** Fresh subagent per task + two-stage review (spec then quality) = high quality, fast iteration
|
||||
**Core principle:** Fresh subagent per task + one task review (spec + quality verdicts) + broad final review = high quality, fast iteration
|
||||
|
||||
**Continuous execution:** Do not pause to check in with your human partner between tasks. Execute all tasks from the plan without stopping. The only reasons to stop are: BLOCKED status you cannot resolve, ambiguity that genuinely prevents progress, or all tasks complete. "Should I continue?" prompts and progress summaries waste their time — they asked you to execute the plan, so execute it.
|
||||
|
||||
@@ -36,7 +36,7 @@ digraph when_to_use {
|
||||
**vs. Executing Plans (parallel session):**
|
||||
- Same session (no context switch)
|
||||
- Fresh subagent per task (no context pollution)
|
||||
- Two-stage review after each task: spec compliance first, then code quality
|
||||
- Combined review after each task (spec compliance + code quality verdicts), broad review at the end
|
||||
- Faster iteration (no human-in-loop between tasks)
|
||||
|
||||
## The Process
|
||||
@@ -51,18 +51,15 @@ digraph process {
|
||||
"Implementer subagent asks questions?" [shape=diamond];
|
||||
"Answer questions, provide context" [shape=box];
|
||||
"Implementer subagent implements, tests, commits, self-reviews" [shape=box];
|
||||
"Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)" [shape=box];
|
||||
"Spec reviewer subagent confirms code matches spec?" [shape=diamond];
|
||||
"Implementer subagent fixes spec gaps" [shape=box];
|
||||
"Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" [shape=box];
|
||||
"Code quality reviewer subagent approves?" [shape=diamond];
|
||||
"Implementer subagent fixes quality issues" [shape=box];
|
||||
"Run git diff, dispatch task reviewer subagent (./task-reviewer-prompt.md)" [shape=box];
|
||||
"Task reviewer reports spec ✅ and quality approved?" [shape=diamond];
|
||||
"Dispatch fix subagent for Critical/Important findings" [shape=box];
|
||||
"Mark task complete in todo list" [shape=box];
|
||||
}
|
||||
|
||||
"Read plan, extract all tasks with full text, note context, create todos" [shape=box];
|
||||
"More tasks remain?" [shape=diamond];
|
||||
"Dispatch final code reviewer subagent for entire implementation" [shape=box];
|
||||
"Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" [shape=box];
|
||||
"Use superpowers:finishing-a-development-branch" [shape=box style=filled fillcolor=lightgreen];
|
||||
|
||||
"Read plan, extract all tasks with full text, note context, create todos" -> "Dispatch implementer subagent (./implementer-prompt.md)";
|
||||
@@ -70,19 +67,15 @@ digraph process {
|
||||
"Implementer subagent asks questions?" -> "Answer questions, provide context" [label="yes"];
|
||||
"Answer questions, provide context" -> "Dispatch implementer subagent (./implementer-prompt.md)";
|
||||
"Implementer subagent asks questions?" -> "Implementer subagent implements, tests, commits, self-reviews" [label="no"];
|
||||
"Implementer subagent implements, tests, commits, self-reviews" -> "Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)";
|
||||
"Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)" -> "Spec reviewer subagent confirms code matches spec?";
|
||||
"Spec reviewer subagent confirms code matches spec?" -> "Implementer subagent fixes spec gaps" [label="no"];
|
||||
"Implementer subagent fixes spec gaps" -> "Dispatch spec reviewer subagent (./spec-reviewer-prompt.md)" [label="re-review"];
|
||||
"Spec reviewer subagent confirms code matches spec?" -> "Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" [label="yes"];
|
||||
"Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" -> "Code quality reviewer subagent approves?";
|
||||
"Code quality reviewer subagent approves?" -> "Implementer subagent fixes quality issues" [label="no"];
|
||||
"Implementer subagent fixes quality issues" -> "Dispatch code quality reviewer subagent (./code-quality-reviewer-prompt.md)" [label="re-review"];
|
||||
"Code quality reviewer subagent approves?" -> "Mark task complete in todo list" [label="yes"];
|
||||
"Implementer subagent implements, tests, commits, self-reviews" -> "Run git diff, dispatch task reviewer subagent (./task-reviewer-prompt.md)";
|
||||
"Run git diff, dispatch task reviewer subagent (./task-reviewer-prompt.md)" -> "Task reviewer reports spec ✅ and quality approved?";
|
||||
"Task reviewer reports spec ✅ and quality approved?" -> "Dispatch fix subagent for Critical/Important findings" [label="no"];
|
||||
"Dispatch fix subagent for Critical/Important findings" -> "Run git diff, dispatch task reviewer subagent (./task-reviewer-prompt.md)" [label="re-review"];
|
||||
"Task reviewer reports spec ✅ and quality approved?" -> "Mark task complete in todo list" [label="yes"];
|
||||
"Mark task complete in todo list" -> "More tasks remain?";
|
||||
"More tasks remain?" -> "Dispatch implementer subagent (./implementer-prompt.md)" [label="yes"];
|
||||
"More tasks remain?" -> "Dispatch final code reviewer subagent for entire implementation" [label="no"];
|
||||
"Dispatch final code reviewer subagent for entire implementation" -> "Use superpowers:finishing-a-development-branch";
|
||||
"More tasks remain?" -> "Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" [label="no"];
|
||||
"Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" -> "Use superpowers:finishing-a-development-branch";
|
||||
}
|
||||
```
|
||||
|
||||
@@ -94,9 +87,23 @@ Use the least powerful model that can handle each role to conserve cost and incr
|
||||
|
||||
**Integration and judgment tasks** (multi-file coordination, pattern matching, debugging): use a standard model.
|
||||
|
||||
**Architecture, design, and review tasks**: use the most capable available model.
|
||||
**Architecture and design tasks**: use the most capable available model.
|
||||
|
||||
**Task complexity signals:**
|
||||
**Review tasks**: choose the model with the same judgment, scaled to the
|
||||
diff's size, complexity, and risk. A small mechanical diff does not need the
|
||||
most capable model; a subtle concurrency change does.
|
||||
|
||||
**Always specify the model explicitly when dispatching a subagent.** An
|
||||
omitted model inherits your session's model — often the most capable and
|
||||
most expensive — which silently defeats this section.
|
||||
|
||||
**Turn count beats token price.** Wall-clock and context cost scale with how
|
||||
many turns a subagent takes, and the cheapest models routinely take 2-3× the
|
||||
turns on multi-step work — costing more overall. Use a mid-tier model as the
|
||||
floor for implementers and reviewers; reserve the cheapest tier for
|
||||
single-file mechanical fixes.
|
||||
|
||||
**Task complexity signals (implementation tasks):**
|
||||
- Touches 1-2 files with a complete spec → cheap model
|
||||
- Touches multiple files with integration concerns → standard model
|
||||
- Requires design judgment or broad codebase understanding → most capable model
|
||||
@@ -105,7 +112,7 @@ Use the least powerful model that can handle each role to conserve cost and incr
|
||||
|
||||
Implementer subagents report one of four statuses. Handle each appropriately:
|
||||
|
||||
**DONE:** Proceed to spec compliance review.
|
||||
**DONE:** Run `git diff BASE..HEAD`, then dispatch the task reviewer.
|
||||
|
||||
**DONE_WITH_CONCERNS:** The implementer completed the work but flagged doubts. Read the concerns before proceeding. If the concerns are about correctness or scope, address them before review. If they're observations (e.g., "this file is getting large"), note them and proceed to review.
|
||||
|
||||
@@ -119,11 +126,48 @@ Implementer subagents report one of four statuses. Handle each appropriately:
|
||||
|
||||
**Never** ignore an escalation or force the same model to retry without changes. If the implementer said it's stuck, something needs to change.
|
||||
|
||||
## Handling Reviewer ⚠️ Items
|
||||
|
||||
The task reviewer may report "⚠️ Cannot verify from diff" items — requirements
|
||||
that live in unchanged code or span tasks. These do not block the rest of the
|
||||
review, but you must resolve each one yourself before marking the task
|
||||
complete: you hold the plan and cross-task context the reviewer
|
||||
lacks. If you confirm an item is a real gap, treat it as a failed spec
|
||||
review — send it back to the implementer and re-review.
|
||||
|
||||
## Constructing Reviewer Prompts
|
||||
|
||||
Per-task reviews are task-scoped gates. The broad review happens once, at the
|
||||
final whole-branch review. When you fill a reviewer template:
|
||||
|
||||
- Do not add open-ended directives like "check all uses" or "run race tests
|
||||
if useful" without a concrete, task-specific reason
|
||||
- Do not ask a reviewer to re-run tests the implementer already ran on the
|
||||
same code — the implementer's report carries the test evidence
|
||||
- Do not pre-judge findings for the reviewer — never instruct a reviewer to
|
||||
ignore or not flag a specific issue. If you believe a finding would be a
|
||||
false positive, let the reviewer raise it and adjudicate it in the review
|
||||
loop. If the prompt you are writing contains "do not flag," "don't treat X
|
||||
as a defect," "at most Minor," or "the plan chose" — stop: you are
|
||||
pre-judging, usually to spare yourself a review loop.
|
||||
- Include the spec/design's global constraints that bind the task (version
|
||||
floors, naming and copy rules, platform requirements) in the requirements
|
||||
you paste — a reviewer can only enforce what you hand them.
|
||||
- Hand the reviewer its diff as a file: run
|
||||
`git diff BASE..HEAD > /tmp/sdd-task-N.diff` (redirected, so the diff
|
||||
never enters your own context) and put that path in the prompt. The
|
||||
reviewer then sees the whole change in one Read call instead of
|
||||
re-deriving it with git commands.
|
||||
- Dispatch fix subagents for Critical and Important findings. Record Minor
|
||||
findings and move on — then paste the accumulated Minor findings into the
|
||||
final whole-branch review dispatch so it can triage which must be fixed
|
||||
before merge. A roll-up nobody reads is a silent discard.
|
||||
|
||||
## Prompt Templates
|
||||
|
||||
- [implementer-prompt.md](implementer-prompt.md) - Dispatch implementer subagent
|
||||
- [spec-reviewer-prompt.md](spec-reviewer-prompt.md) - Dispatch spec compliance reviewer subagent
|
||||
- [code-quality-reviewer-prompt.md](code-quality-reviewer-prompt.md) - Dispatch code quality reviewer subagent
|
||||
- [task-reviewer-prompt.md](task-reviewer-prompt.md) - Dispatch task reviewer subagent (spec compliance + code quality, one dispatch)
|
||||
- Final whole-branch review: use superpowers:requesting-code-review's [code-reviewer.md](../requesting-code-review/code-reviewer.md)
|
||||
|
||||
## Example Workflow
|
||||
|
||||
@@ -150,11 +194,9 @@ Implementer: "Got it. Implementing now..."
|
||||
- Self-review: Found I missed --force flag, added it
|
||||
- Committed
|
||||
|
||||
[Dispatch spec compliance reviewer]
|
||||
Spec reviewer: ✅ Spec compliant - all requirements met, nothing extra
|
||||
|
||||
[Get git SHAs, dispatch code quality reviewer]
|
||||
Code reviewer: Strengths: Good test coverage, clean. Issues: None. Approved.
|
||||
[Run git diff, dispatch task reviewer with the diff pasted in]
|
||||
Task reviewer: Spec ✅ - all requirements met, nothing extra.
|
||||
Strengths: Good test coverage, clean. Issues: None. Task quality: Approved.
|
||||
|
||||
[Mark Task 1 complete]
|
||||
|
||||
@@ -170,25 +212,17 @@ Implementer:
|
||||
- Self-review: All good
|
||||
- Committed
|
||||
|
||||
[Dispatch spec compliance reviewer]
|
||||
Spec reviewer: ❌ Issues:
|
||||
[Run git diff, dispatch task reviewer with the diff pasted in]
|
||||
Task reviewer: Spec ❌:
|
||||
- Missing: Progress reporting (spec says "report every 100 items")
|
||||
- Extra: Added --json flag (not requested)
|
||||
Issues (Important): Magic number (100)
|
||||
|
||||
[Implementer fixes issues]
|
||||
Implementer: Removed --json flag, added progress reporting
|
||||
[Dispatch fix subagent with all findings]
|
||||
Fixer: Removed --json flag, added progress reporting, extracted PROGRESS_INTERVAL constant
|
||||
|
||||
[Spec reviewer reviews again]
|
||||
Spec reviewer: ✅ Spec compliant now
|
||||
|
||||
[Dispatch code quality reviewer]
|
||||
Code reviewer: Strengths: Solid. Issues (Important): Magic number (100)
|
||||
|
||||
[Implementer fixes]
|
||||
Implementer: Extracted PROGRESS_INTERVAL constant
|
||||
|
||||
[Code reviewer reviews again]
|
||||
Code reviewer: ✅ Approved
|
||||
[Task reviewer reviews again]
|
||||
Task reviewer: Spec ✅. Task quality: Approved.
|
||||
|
||||
[Mark Task 2 complete]
|
||||
|
||||
@@ -222,13 +256,13 @@ Done!
|
||||
|
||||
**Quality gates:**
|
||||
- Self-review catches issues before handoff
|
||||
- Two-stage review: spec compliance, then code quality
|
||||
- Task review carries two verdicts: spec compliance and code quality
|
||||
- Review loops ensure fixes actually work
|
||||
- Spec compliance prevents over/under-building
|
||||
- Code quality ensures implementation is well-built
|
||||
|
||||
**Cost:**
|
||||
- More subagent invocations (implementer + 2 reviewers per task)
|
||||
- More subagent invocations (implementer + reviewer per task)
|
||||
- Controller does more prep work (extracting all tasks upfront)
|
||||
- Review loops add iterations
|
||||
- But catches issues early (cheaper than debugging later)
|
||||
@@ -237,17 +271,22 @@ Done!
|
||||
|
||||
**Never:**
|
||||
- Start implementation on main/master branch without explicit user consent
|
||||
- Skip reviews (spec compliance OR code quality)
|
||||
- Skip task review, or accept a report missing either verdict (spec compliance AND task quality are both required)
|
||||
- Proceed with unfixed issues
|
||||
- Dispatch multiple implementation subagents in parallel (conflicts)
|
||||
- Make subagent read plan file (provide full text instead)
|
||||
- Skip scene-setting context (subagent needs to understand where task fits)
|
||||
- Ignore subagent questions (answer before letting them proceed)
|
||||
- Accept "close enough" on spec compliance (spec reviewer found issues = not done)
|
||||
- Accept "close enough" on spec compliance (reviewer found spec issues = not done)
|
||||
- Skip review loops (reviewer found issues = implementer fixes = review again)
|
||||
- Let implementer self-review replace actual review (both are needed)
|
||||
- **Start code quality review before spec compliance is ✅** (wrong order)
|
||||
- Move to next task while either review has open issues
|
||||
- Tell a reviewer what not to flag, or pre-rate a finding's severity in the
|
||||
dispatch prompt ("treat it as Minor at most") — the plan's example code is
|
||||
a starting point, not evidence that its weaknesses were chosen
|
||||
- Dispatch a task reviewer without a diff file — run
|
||||
`git diff BASE..HEAD > /tmp/sdd-task-N.diff` first and name that path in
|
||||
the prompt
|
||||
- Move to next task while the review has open Critical/Important issues
|
||||
|
||||
**If subagent asks questions:**
|
||||
- Answer clearly and completely
|
||||
@@ -269,7 +308,7 @@ Done!
|
||||
**Required workflow skills:**
|
||||
- **superpowers:using-git-worktrees** - Ensures isolated workspace (creates one or verifies existing)
|
||||
- **superpowers:writing-plans** - Creates the plan this skill executes
|
||||
- **superpowers:requesting-code-review** - Code review template for reviewer subagents
|
||||
- **superpowers:requesting-code-review** - Code review template for the final whole-branch review
|
||||
- **superpowers:finishing-a-development-branch** - Complete development after all tasks
|
||||
|
||||
**Subagents should use:**
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Code Quality Reviewer Prompt Template
|
||||
|
||||
Use this template when dispatching a code quality reviewer subagent.
|
||||
|
||||
**Purpose:** Verify implementation is well-built (clean, tested, maintainable)
|
||||
|
||||
**Only dispatch after spec compliance review passes.**
|
||||
|
||||
```
|
||||
Subagent (general-purpose):
|
||||
Use template at ../requesting-code-review/code-reviewer.md
|
||||
|
||||
DESCRIPTION: [task summary, from implementer's report]
|
||||
PLAN_OR_REQUIREMENTS: Task N from [plan-file]
|
||||
BASE_SHA: [commit before task]
|
||||
HEAD_SHA: [current commit]
|
||||
```
|
||||
|
||||
**In addition to standard code quality concerns, the reviewer should check:**
|
||||
- Does each file have one clear responsibility with a well-defined interface?
|
||||
- Are units decomposed so they can be understood and tested independently?
|
||||
- Is the implementation following the file structure from the plan?
|
||||
- Did this implementation create new files that are already large, or significantly grow existing files? (Don't flag pre-existing file sizes — focus on what this change contributed.)
|
||||
|
||||
**Code reviewer returns:** Strengths, Issues (Critical/Important/Minor), Assessment
|
||||
@@ -41,6 +41,9 @@ Subagent (general-purpose):
|
||||
**While you work:** If you encounter something unexpected or unclear, **ask questions**.
|
||||
It's always OK to pause and clarify. Don't guess or make assumptions.
|
||||
|
||||
While iterating, run the focused test for what you're changing; run the
|
||||
full suite once before committing, not after every edit.
|
||||
|
||||
## Code Organization
|
||||
|
||||
You reason best about code you can hold in context at once, and your edits are more
|
||||
@@ -94,15 +97,25 @@ Subagent (general-purpose):
|
||||
- Do tests actually verify behavior (not just mock behavior)?
|
||||
- Did I follow TDD if required?
|
||||
- Are tests comprehensive?
|
||||
- Is the test output pristine (no stray warnings or noise)?
|
||||
|
||||
If you find issues during self-review, fix them now before reporting.
|
||||
|
||||
## After Review Findings
|
||||
|
||||
If a reviewer finds issues and you fix them, re-run the tests that cover
|
||||
the amended code and include the results in your fix report. Reviewers
|
||||
will not re-run tests for you — your report is the test evidence.
|
||||
|
||||
## Report Format
|
||||
|
||||
When done, report:
|
||||
- **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
|
||||
- What you implemented (or what you attempted, if blocked)
|
||||
- What you tested and test results
|
||||
- **TDD Evidence** (if TDD was required for this task):
|
||||
- RED: command run, relevant failing output before implementation, and why the failure was expected
|
||||
- GREEN: command run and relevant passing output after implementation
|
||||
- Files changed
|
||||
- Self-review findings (if any)
|
||||
- Any issues or concerns
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
# Spec Compliance Reviewer Prompt Template
|
||||
|
||||
Use this template when dispatching a spec compliance reviewer subagent.
|
||||
|
||||
**Purpose:** Verify implementer built what was requested (nothing more, nothing less)
|
||||
|
||||
```
|
||||
Subagent (general-purpose):
|
||||
description: "Review spec compliance for Task N"
|
||||
prompt: |
|
||||
You are reviewing whether an implementation matches its specification.
|
||||
|
||||
## What Was Requested
|
||||
|
||||
[FULL TEXT of task requirements]
|
||||
|
||||
## What Implementer Claims They Built
|
||||
|
||||
[From implementer's report]
|
||||
|
||||
## Git Range to Review
|
||||
|
||||
**Base:** [BASE_SHA — commit before this task]
|
||||
**Head:** [HEAD_SHA — current commit]
|
||||
|
||||
```bash
|
||||
git diff --stat [BASE_SHA]..[HEAD_SHA]
|
||||
git diff [BASE_SHA]..[HEAD_SHA]
|
||||
```
|
||||
|
||||
Only read files in this diff. Do not crawl the broader codebase.
|
||||
|
||||
## Read-Only Review
|
||||
|
||||
Your review is read-only on this checkout. Do not mutate the working tree, the index, HEAD, or branch state in any way. Use tools like `git show`, `git diff`, and `git log` to inspect history. If you need a working copy of a different revision, check it out into a separate temporary directory (e.g. `git worktree add /tmp/review-[SHA] [SHA]`) — never move HEAD on this checkout.
|
||||
|
||||
## CRITICAL: Do Not Trust the Report
|
||||
|
||||
The implementer finished suspiciously quickly. Their report may be incomplete,
|
||||
inaccurate, or optimistic. You MUST verify everything independently.
|
||||
|
||||
**DO NOT:**
|
||||
- Take their word for what they implemented
|
||||
- Trust their claims about completeness
|
||||
- Accept their interpretation of requirements
|
||||
|
||||
**DO:**
|
||||
- Read the actual code they wrote
|
||||
- Compare actual implementation to requirements line by line
|
||||
- Check for missing pieces they claimed to implement
|
||||
- Look for extra features they didn't mention
|
||||
|
||||
## Your Job
|
||||
|
||||
Read the implementation code and verify:
|
||||
|
||||
**Missing requirements:**
|
||||
- Did they implement everything that was requested?
|
||||
- Are there requirements they skipped or missed?
|
||||
- Did they claim something works but didn't actually implement it?
|
||||
|
||||
**Extra/unneeded work:**
|
||||
- Did they build things that weren't requested?
|
||||
- Did they over-engineer or add unnecessary features?
|
||||
- Did they add "nice to haves" that weren't in spec?
|
||||
|
||||
**Misunderstandings:**
|
||||
- Did they interpret requirements differently than intended?
|
||||
- Did they solve the wrong problem?
|
||||
- Did they implement the right feature but wrong way?
|
||||
|
||||
**Verify by reading code, not by trusting report.**
|
||||
|
||||
Report:
|
||||
- ✅ Spec compliant (if everything matches after code inspection)
|
||||
- ❌ Issues found: [list specifically what's missing or extra, with file:line references]
|
||||
```
|
||||
162
skills/subagent-driven-development/task-reviewer-prompt.md
Normal file
162
skills/subagent-driven-development/task-reviewer-prompt.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Task Reviewer Prompt Template
|
||||
|
||||
Use this template when dispatching a task reviewer subagent. One reviewer, one
|
||||
reading of the diff, two verdicts: spec compliance and code quality.
|
||||
|
||||
**Purpose:** Verify one task's implementation matches its requirements (nothing
|
||||
more, nothing less) and is well-built (clean, tested, maintainable)
|
||||
|
||||
```
|
||||
Subagent (general-purpose):
|
||||
description: "Review Task N (spec + quality)"
|
||||
prompt: |
|
||||
You are reviewing one task's implementation: first whether it matches its
|
||||
requirements, then whether it is well-built. This is a task-scoped gate,
|
||||
not a merge review — a broad whole-branch review happens separately after
|
||||
all tasks are complete.
|
||||
|
||||
## What Was Requested
|
||||
|
||||
[TASK_REQUIREMENTS]
|
||||
|
||||
## What the Implementer Claims They Built
|
||||
|
||||
[DESCRIPTION]
|
||||
|
||||
## Diff Under Review
|
||||
|
||||
**Base:** [BASE_SHA]
|
||||
**Head:** [HEAD_SHA]
|
||||
**Diff file:** [DIFF_FILE]
|
||||
|
||||
Read the diff file once — that single Read is your view of the change.
|
||||
Do not re-run git commands or re-read the files it already shows. If
|
||||
the diff file is missing, fetch the diff yourself:
|
||||
`git diff --stat [BASE_SHA]..[HEAD_SHA]` and `git diff [BASE_SHA]..[HEAD_SHA]`.
|
||||
Only read files in this diff. Do not crawl the broader codebase. Inspect
|
||||
code outside the diff only to evaluate a concrete risk you can name — and
|
||||
name it in your report. Cross-cutting changes are legitimate named risks:
|
||||
if the diff changes lock ordering, a function or API contract, or shared
|
||||
mutable state, checking the call sites is the right method.
|
||||
|
||||
Your review is read-only on this checkout. Do not mutate the working
|
||||
tree, the index, HEAD, or branch state in any way.
|
||||
|
||||
## Do Not Trust the Report
|
||||
|
||||
Treat the implementer's report as unverified claims about the code. It
|
||||
may be incomplete, inaccurate, or optimistic. Verify the claims against
|
||||
the diff. Design rationales in the report are claims too: "left it per
|
||||
YAGNI," "kept it simple deliberately," or any other justification is the
|
||||
implementer grading their own work. Judge the code on its merits — a
|
||||
stated rationale never downgrades a finding's severity.
|
||||
|
||||
## Tests
|
||||
|
||||
The implementer already ran the tests and reported results with TDD
|
||||
evidence for exactly this code. Do not re-run the suite to confirm their
|
||||
report. Run a test only when reading the code raises a specific doubt
|
||||
that no existing run answers — and then a focused test, never a
|
||||
package-wide suite, race detector run, or repeated/high-count loop. If
|
||||
heavy validation seems warranted, recommend it in your report instead of
|
||||
running it. If you cannot run commands in this environment, name the
|
||||
test you would run.
|
||||
|
||||
Warnings or other noise in the implementer's reported test output are
|
||||
findings — test output should be pristine.
|
||||
|
||||
## Part 1: Spec Compliance
|
||||
|
||||
Compare the diff against What Was Requested:
|
||||
|
||||
- **Missing:** requirements they skipped, missed, or claimed without
|
||||
implementing
|
||||
- **Extra:** features that weren't requested, over-engineering, unneeded
|
||||
"nice to haves"
|
||||
- **Misunderstood:** right feature built the wrong way, wrong problem
|
||||
solved
|
||||
|
||||
If a requirement cannot be verified from this diff alone (it lives in
|
||||
unchanged code or spans tasks), report it as a ⚠️ item instead of
|
||||
broadening your search.
|
||||
|
||||
## Part 2: Code Quality
|
||||
|
||||
**Code quality:**
|
||||
- Clean separation of concerns?
|
||||
- Proper error handling?
|
||||
- DRY without premature abstraction?
|
||||
- Edge cases handled?
|
||||
|
||||
**Tests:**
|
||||
- Do the new and changed tests verify real behavior, not mocks?
|
||||
- Are the task's edge cases covered?
|
||||
|
||||
**Structure:**
|
||||
- Does each file have one clear responsibility with a well-defined interface?
|
||||
- Are units decomposed so they can be understood and tested independently?
|
||||
- Is the implementation following the file structure from the plan?
|
||||
- Did this change create new files that are already large, or
|
||||
significantly grow existing files? (Don't flag pre-existing file
|
||||
sizes — focus on what this change contributed.)
|
||||
|
||||
Cite file:line evidence for every finding and for any check you would
|
||||
otherwise answer with a bare "yes." Cite, don't narrate — a tight report
|
||||
that points at lines beats a long one that retells the diff.
|
||||
|
||||
## Calibration
|
||||
|
||||
Categorize issues by actual severity. Not everything is Critical.
|
||||
Important means this task cannot be trusted until it is fixed: incorrect
|
||||
or fragile behavior, a missed requirement, or maintainability damage you
|
||||
would block a merge over — verbatim duplication of a logic block,
|
||||
swallowed errors, tests that assert nothing. "Coverage could be broader"
|
||||
and polish suggestions are Minor.
|
||||
Acknowledge what was done well before listing issues — accurate praise
|
||||
helps the implementer trust the rest of the feedback.
|
||||
|
||||
## Output Format
|
||||
|
||||
### Spec Compliance
|
||||
|
||||
- ✅ Spec compliant | ❌ Issues found: [what's missing/extra/misunderstood,
|
||||
with file:line references]
|
||||
- ⚠️ Cannot verify from diff: [requirements you could not verify from the
|
||||
diff alone, and what the controller should check — report alongside the
|
||||
✅/❌ verdict for everything you could verify]
|
||||
|
||||
### Strengths
|
||||
[What's well done? Be specific.]
|
||||
|
||||
### Issues
|
||||
|
||||
#### Critical (Must Fix)
|
||||
#### Important (Should Fix)
|
||||
#### Minor (Nice to Have)
|
||||
|
||||
For each issue: file:line, what's wrong, why it matters, how to fix
|
||||
(if not obvious).
|
||||
|
||||
### Assessment
|
||||
|
||||
**Task quality:** [Approved | Needs fixes]
|
||||
|
||||
**Reasoning:** [1-2 sentence technical assessment]
|
||||
```
|
||||
|
||||
**Placeholders:**
|
||||
- `[TASK_REQUIREMENTS]` — full task text plus the spec/design's global
|
||||
constraints that bind it (version floors, naming and copy rules, platform
|
||||
requirements)
|
||||
- `[DESCRIPTION]` — what the implementer reports they built
|
||||
- `[BASE_SHA]` — commit before this task
|
||||
- `[HEAD_SHA]` — current commit
|
||||
- `[DIFF_FILE]` — REQUIRED: the path the controller wrote the diff to
|
||||
(`git diff BASE..HEAD > /tmp/sdd-task-N.diff`, redirected so it never
|
||||
enters the controller's context)
|
||||
|
||||
**Reviewer returns:** Spec Compliance verdict (✅/❌/⚠️), Strengths, Issues
|
||||
(Critical/Important/Minor), Task quality verdict
|
||||
|
||||
A single fix dispatch can then address spec gaps and quality findings
|
||||
together; re-review after fixes covers both verdicts again.
|
||||
@@ -41,7 +41,7 @@ If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "alw
|
||||
|
||||
## Platform Adaptation
|
||||
|
||||
Skills speak in actions ("dispatch a subagent", "create a todo", "read a file") rather than naming any one runtime's tools. For per-platform tool equivalents and instructions-file conventions, see [claude-code-tools.md](references/claude-code-tools.md), [codex-tools.md](references/codex-tools.md), [copilot-tools.md](references/copilot-tools.md), [gemini-tools.md](references/gemini-tools.md), and [pi-tools.md](references/pi-tools.md). Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
|
||||
Skills speak in actions ("dispatch a subagent", "create a todo", "read a file") rather than naming any one runtime's tools. For per-platform tool equivalents and instructions-file conventions, see [claude-code-tools.md](references/claude-code-tools.md), [codex-tools.md](references/codex-tools.md), [copilot-tools.md](references/copilot-tools.md), [gemini-tools.md](references/gemini-tools.md), [pi-tools.md](references/pi-tools.md), and [antigravity-tools.md](references/antigravity-tools.md). Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
|
||||
|
||||
# Using Skills
|
||||
|
||||
|
||||
96
skills/using-superpowers/references/antigravity-tools.md
Normal file
96
skills/using-superpowers/references/antigravity-tools.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Antigravity CLI (`agy`) Tool Mapping
|
||||
|
||||
Skills speak in actions ("dispatch a subagent", "create a todo", "read a file"). On the Antigravity CLI (`agy`) these resolve to the tools below.
|
||||
|
||||
| Action skills request | Antigravity CLI equivalent |
|
||||
|----------------------|----------------------|
|
||||
| Read a file | `view_file` |
|
||||
| Create a new file | `write_to_file` |
|
||||
| Edit a file | `replace_file_content` |
|
||||
| Edit a file in several places at once | `multi_replace_file_content` |
|
||||
| Run a shell command | `run_command` |
|
||||
| Search file contents | `grep_search` |
|
||||
| Find files by name / list a directory | `list_dir` (no dedicated glob tool — combine `list_dir` with `grep_search`) |
|
||||
| Fetch a URL | `read_url_content` |
|
||||
| Search the web | `search_web` |
|
||||
| Pose a structured question to your human partner | `ask_question` |
|
||||
| Dispatch a subagent (`Subagent (general-purpose):` template) | `invoke_subagent` with a built-in `TypeName` — `self` for full-capability work, `research` for read-only (see [Subagent support](#subagent-support)) |
|
||||
| Multiple parallel dispatches | Multiple entries in one `invoke_subagent` call's `Subagents` array |
|
||||
| Task tracking ("create a todo", "mark complete") | a **task artifact** — `write_to_file` with `IsArtifact: true` and `ArtifactType: "task"` (see [Task tracking](#task-tracking)). **Not** `manage_task`, which manages background processes. |
|
||||
|
||||
## Invoking a skill — read its `SKILL.md`
|
||||
|
||||
Antigravity surfaces every installed skill's `name` + `description` to you at the
|
||||
start of each session, but it has **no `Skill`/`activate_skill` tool**. To load a
|
||||
skill, **read its `SKILL.md` with `view_file`, setting `IsSkillFile: true`** when
|
||||
the skill applies — e.g. `view_file` on
|
||||
`.../plugins/superpowers/skills/<skill-name>/SKILL.md` with `IsSkillFile: true`.
|
||||
(`IsSkillFile` is agy's own signal that you're reading a file to *execute its
|
||||
instructions*, not to edit or preview it — set it whenever you load a skill.)
|
||||
|
||||
This is the blessed skill-loading mechanism on this harness. The general rule
|
||||
"never read skill files manually" means "don't bypass your platform's
|
||||
skill-loading mechanism" — and on Antigravity, reading `SKILL.md` *is* that
|
||||
mechanism. Reading it honors the rule rather than breaking it.
|
||||
|
||||
You already know which skills exist and what they're for: their names and
|
||||
descriptions are in front of you at session start. When a description matches
|
||||
what you're about to do, read that skill's `SKILL.md` before acting.
|
||||
|
||||
## Subagent support
|
||||
|
||||
Antigravity dispatches subagents with `invoke_subagent`, passing each one a
|
||||
`TypeName` in the `Subagents` array. Two `TypeName`s are **built in** — use them
|
||||
directly, no `define_subagent` needed:
|
||||
|
||||
- **`self`** — a full clone of you, with every tool you have (including
|
||||
`write_to_file`/`replace_file_content`/`run_command`). The safe default for
|
||||
general-purpose work: implementing, fixing, anything that edits files or runs
|
||||
commands.
|
||||
- **`research`** — read-only (file reading, `grep_search`, web/URL fetch; no write
|
||||
or command access). Use it when you specifically want a subagent that can't make
|
||||
changes — investigation and read-only review.
|
||||
|
||||
Call `define_subagent` only for a custom system prompt or capability mix: set
|
||||
`enable_write_tools: true` to grant file edits **and** `run_command`,
|
||||
`enable_subagent_tools` for nested dispatch, `enable_mcp_tools` for MCP. Then
|
||||
invoke it by the name you gave it. (`manage_subagents` lists/kills running
|
||||
subagents.)
|
||||
|
||||
Skills dispatch with `Subagent (general-purpose):` and either reference a
|
||||
prompt-template file (e.g. `superpowers:subagent-driven-development`'s
|
||||
`./implementer-prompt.md`) or supply an inline prompt. On Antigravity:
|
||||
|
||||
| Skill dispatch form | Antigravity equivalent |
|
||||
|---------------------|----------------------|
|
||||
| An implementer-style `*-prompt.md` template (writes code, runs tests) | Fill the template, then `invoke_subagent` with `TypeName: "self"` and the filled prompt |
|
||||
| A read-only reviewer template (`task-reviewer`, `code-reviewer`, `requesting-code-review`'s `./code-reviewer.md`) | `invoke_subagent` with `TypeName: "research"` and the filled review template |
|
||||
| Inline prompt (no template referenced) | `invoke_subagent` with `TypeName: "self"` (or `"research"` if the task only reads) and your inline prompt |
|
||||
|
||||
### Prompt filling
|
||||
|
||||
Skills provide prompt templates with placeholders like `{WHAT_WAS_IMPLEMENTED}` or
|
||||
`[FULL TEXT of task]`. Fill all placeholders before passing the complete prompt to
|
||||
`invoke_subagent`. The prompt template itself contains the agent's role, review
|
||||
criteria, and expected output format — the subagent will follow it.
|
||||
|
||||
### Parallel dispatch
|
||||
|
||||
Put multiple entries in a single `invoke_subagent` call's `Subagents` array to run
|
||||
independent subagent work in parallel. Keep dependent tasks sequential, but do not
|
||||
serialize independent subagent tasks just to preserve a simpler history.
|
||||
|
||||
## Task tracking
|
||||
|
||||
Antigravity has **no todo / `TodoWrite` tool** (`manage_task` manages background
|
||||
processes — `list`/`kill`/`status`/`send_input` — it is *not* a checklist). When a
|
||||
skill says to create a todo list or track tasks, maintain a **task artifact**: a
|
||||
markdown checklist saved with `write_to_file` (`IsArtifact: true`,
|
||||
`ArtifactMetadata.ArtifactType: "task"`), edited with `replace_file_content` /
|
||||
`multi_replace_file_content` as you go.
|
||||
|
||||
At the start of any multi-step task, create the task artifact listing every step of
|
||||
your plan. As you complete each step, edit the artifact to mark it done (`- [x]`).
|
||||
If the plan changes, update the checklist. Keep it current — it is your source of
|
||||
truth for what remains; once the conversation gets long, re-read it before starting
|
||||
each step.
|
||||
@@ -35,7 +35,7 @@ Skills dispatch with `Subagent (general-purpose):` and either reference a prompt
|
||||
|
||||
| Skill dispatch form | Gemini CLI equivalent |
|
||||
|---------------------|----------------------|
|
||||
| References a `*-prompt.md` template (implementer, spec-reviewer, code-quality-reviewer, code-reviewer, etc.) | Fill the template, then `invoke_agent` with `agent_name: "generalist"` and the filled prompt |
|
||||
| References a `*-prompt.md` template (implementer, task-reviewer, code-reviewer, etc.) | Fill the template, then `invoke_agent` with `agent_name: "generalist"` and the filled prompt |
|
||||
| References `superpowers:requesting-code-review`'s `./code-reviewer.md` | `invoke_agent` with `agent_name: "generalist"` and the filled review template |
|
||||
| Inline prompt (no template referenced) | `invoke_agent` with `agent_name: "generalist"` and your inline prompt |
|
||||
|
||||
|
||||
16
tests/antigravity/run-tests.sh
Executable file
16
tests/antigravity/run-tests.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run all Antigravity (agy) integration tests.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo "=== Antigravity integration tests ==="
|
||||
|
||||
for t in "$SCRIPT_DIR"/test-*.sh; do
|
||||
echo
|
||||
echo ">>> $t"
|
||||
bash "$t"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "=== All Antigravity tests passed ==="
|
||||
53
tests/antigravity/test-antigravity-tools.sh
Executable file
53
tests/antigravity/test-antigravity-tools.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# Validate the Antigravity (agy) integration. agy installs the existing plugin
|
||||
# directly (`agy plugin install <repo-url>`): it loads the bundled skills and
|
||||
# runs the SessionStart hook for bootstrap, so there is no agy-specific scaffold
|
||||
# to test. What IS agy-specific is the tool mapping — agy has no `Skill` tool and
|
||||
# loads skills by reading SKILL.md with view_file — and SKILL.md pointing at it.
|
||||
#
|
||||
# Mirrors tests/pi/test-pi-extension.mjs's "tools reference documents
|
||||
# harness-specific mappings" check. CI-safe: does not require `agy` installed.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
MAPPING="$REPO_ROOT/skills/using-superpowers/references/antigravity-tools.md"
|
||||
SKILL="$REPO_ROOT/skills/using-superpowers/SKILL.md"
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
echo "test-antigravity-tools: checking Antigravity tool mapping"
|
||||
|
||||
# --- Mapping exists ---------------------------------------------------------
|
||||
[ -f "$MAPPING" ] || fail "tool mapping missing at $MAPPING"
|
||||
|
||||
# --- Skill-load mechanism: view_file on SKILL.md (IsSkillFile), no Skill tool -
|
||||
grep -qiE "view_file" "$MAPPING" \
|
||||
|| fail "mapping does not document view_file as the file/skill-read tool"
|
||||
grep -qiE "SKILL\.md" "$MAPPING" \
|
||||
|| fail "mapping does not document reading SKILL.md as the skill-load path"
|
||||
grep -q "IsSkillFile" "$MAPPING" \
|
||||
|| fail "mapping does not document setting IsSkillFile when loading a skill"
|
||||
|
||||
# --- Core action→tool mappings are documented -------------------------------
|
||||
for tool in write_to_file replace_file_content run_command grep_search invoke_subagent; do
|
||||
grep -q "$tool" "$MAPPING" \
|
||||
|| fail "mapping does not document the '$tool' tool"
|
||||
done
|
||||
|
||||
# --- Subagents use the built-in self/research types -------------------------
|
||||
grep -q '`self`' "$MAPPING" \
|
||||
|| fail "mapping does not document the built-in 'self' subagent type"
|
||||
grep -q '`research`' "$MAPPING" \
|
||||
|| fail "mapping does not document the built-in 'research' subagent type"
|
||||
|
||||
# --- Task tracking documents the 'task' artifact mechanism ------------------
|
||||
grep -qE 'ArtifactType.*task|task. artifact' "$MAPPING" \
|
||||
|| fail "mapping does not document task tracking as a 'task' artifact"
|
||||
|
||||
# --- SKILL.md Platform Adaptation links the mapping -------------------------
|
||||
grep -q "antigravity-tools.md" "$SKILL" \
|
||||
|| fail "SKILL.md Platform Adaptation does not reference antigravity-tools.md"
|
||||
|
||||
echo "PASS: Antigravity tool mapping valid (view_file skill-load, agy tools, SKILL.md link)"
|
||||
@@ -329,6 +329,21 @@ function runTests() {
|
||||
assert.strictEqual(result.payload.length, 65536);
|
||||
});
|
||||
|
||||
test('rejects oversized 64-bit frames before payload allocation', () => {
|
||||
const mask = Buffer.from([0x00, 0x00, 0x00, 0x00]);
|
||||
const header = Buffer.alloc(14);
|
||||
header[0] = 0x81; // FIN + TEXT
|
||||
header[1] = 0x80 | 127; // masked, 64-bit length
|
||||
header.writeBigUInt64BE(BigInt(ws.MAX_FRAME_PAYLOAD_BYTES) + 1n, 2);
|
||||
mask.copy(header, 10);
|
||||
|
||||
assert.throws(
|
||||
() => ws.decodeFrame(header),
|
||||
/exceeds maximum allowed size/i,
|
||||
'oversized advertised payload must be rejected from header alone'
|
||||
);
|
||||
});
|
||||
|
||||
// ========== Close Frame with Status Code ==========
|
||||
console.log('\n--- Close Frame Details ---');
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ write_upstream_fixture() {
|
||||
|
||||
mkdir -p \
|
||||
"$repo/.codex-plugin" \
|
||||
"$repo/.kimi-plugin" \
|
||||
"$repo/.private-journal" \
|
||||
"$repo/assets" \
|
||||
"$repo/evals/drill" \
|
||||
@@ -210,6 +211,13 @@ EOF
|
||||
"name": "superpowers",
|
||||
"version": "$MANIFEST_VERSION"
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$repo/.kimi-plugin/plugin.json" <<EOF
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "$MANIFEST_VERSION"
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$repo/assets/superpowers-small.svg" <<'EOF'
|
||||
@@ -267,6 +275,7 @@ EOF
|
||||
|
||||
git -C "$repo" add \
|
||||
.codex-plugin/plugin.json \
|
||||
.kimi-plugin/plugin.json \
|
||||
.gitignore \
|
||||
assets/app-icon.png \
|
||||
assets/superpowers-small.svg \
|
||||
@@ -415,10 +424,15 @@ EOF
|
||||
write_stale_ignored_destination_fixture() {
|
||||
local repo="$1"
|
||||
|
||||
mkdir -p "$repo/plugins/superpowers/.private-journal"
|
||||
mkdir -p \
|
||||
"$repo/plugins/superpowers/.kimi-plugin" \
|
||||
"$repo/plugins/superpowers/.private-journal"
|
||||
printf 'fixture keep\n' > "$repo/plugins/superpowers/.fixture-keep"
|
||||
printf '{"name":"stale-kimi"}\n' > "$repo/plugins/superpowers/.kimi-plugin/plugin.json"
|
||||
printf 'stale ignored leak\n' > "$repo/plugins/superpowers/.private-journal/leak.txt"
|
||||
git -C "$repo" add plugins/superpowers/.fixture-keep
|
||||
git -C "$repo" add \
|
||||
plugins/superpowers/.fixture-keep \
|
||||
plugins/superpowers/.kimi-plugin/plugin.json
|
||||
|
||||
commit_fixture "$repo" "Initial stale ignored destination fixture"
|
||||
}
|
||||
@@ -618,6 +632,7 @@ main() {
|
||||
assert_contains "$preview_output" "Version: $MANIFEST_VERSION" "Preview uses manifest version"
|
||||
assert_not_contains "$preview_output" "Version: $PACKAGE_VERSION" "Preview does not use package.json version"
|
||||
assert_contains "$preview_section" ".codex-plugin/plugin.json" "Preview includes manifest path"
|
||||
assert_not_contains "$preview_section" ".kimi-plugin/plugin.json" "Preview excludes Kimi manifest from Codex sync"
|
||||
assert_contains "$preview_section" "assets/superpowers-small.svg" "Preview includes SVG asset"
|
||||
assert_contains "$preview_section" "assets/app-icon.png" "Preview includes PNG asset"
|
||||
assert_contains "$preview_section" "hooks/hooks-codex.json" "Preview includes Codex hook manifest"
|
||||
@@ -644,6 +659,7 @@ main() {
|
||||
echo ""
|
||||
echo "Convergence assertions..."
|
||||
assert_equals "$stale_preview_status" "0" "Stale ignored destination preview exits successfully"
|
||||
assert_matches "$stale_preview_section" "\\*deleting +\\.kimi-plugin/plugin\\.json" "Preview deletes stale Kimi manifest from Codex plugin"
|
||||
assert_matches "$stale_preview_section" "\\*deleting +\\.private-journal/leak\\.txt" "Preview deletes stale ignored destination file"
|
||||
|
||||
echo ""
|
||||
|
||||
6
tests/kimi/run-tests.sh
Executable file
6
tests/kimi/run-tests.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
bash "$SCRIPT_DIR/test-plugin-manifest.sh"
|
||||
86
tests/kimi/test-plugin-manifest.sh
Executable file
86
tests/kimi/test-plugin-manifest.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
MANIFEST="$REPO_ROOT/.kimi-plugin/plugin.json"
|
||||
|
||||
python3 - "$MANIFEST" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
manifest_path = Path(sys.argv[1])
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
|
||||
def assert_equal(actual, expected, label):
|
||||
if actual != expected:
|
||||
raise AssertionError(f"{label}: expected {expected!r}, got {actual!r}")
|
||||
|
||||
def assert_present(text, needle, label):
|
||||
if needle not in text:
|
||||
raise AssertionError(f"{label}: missing {needle!r}")
|
||||
|
||||
assert_equal(manifest.get("name"), "superpowers", "plugin name")
|
||||
assert_equal(manifest.get("skills"), "./skills/", "skills path")
|
||||
assert_equal(
|
||||
manifest.get("sessionStart", {}).get("skill"),
|
||||
"using-superpowers",
|
||||
"sessionStart.skill",
|
||||
)
|
||||
|
||||
instructions = manifest.get("skillInstructions")
|
||||
if not isinstance(instructions, str) or not instructions.strip():
|
||||
raise AssertionError("skillInstructions must be a non-empty string")
|
||||
|
||||
for token in [
|
||||
"AskUserQuestion",
|
||||
"TodoList",
|
||||
"Agent",
|
||||
"Skill",
|
||||
"Read",
|
||||
"Write",
|
||||
"Edit",
|
||||
"Bash",
|
||||
"Grep",
|
||||
"Glob",
|
||||
"FetchURL",
|
||||
"WebSearch",
|
||||
]:
|
||||
assert_present(instructions, token, "skillInstructions")
|
||||
|
||||
version_config = json.loads(
|
||||
(manifest_path.parents[1] / ".version-bump.json").read_text(encoding="utf-8")
|
||||
)
|
||||
version_entries = version_config.get("files")
|
||||
if not isinstance(version_entries, list):
|
||||
raise AssertionError(".version-bump.json must contain files list")
|
||||
|
||||
if not any(
|
||||
entry.get("path") == ".kimi-plugin/plugin.json" and entry.get("field") == "version"
|
||||
for entry in version_entries
|
||||
if isinstance(entry, dict)
|
||||
):
|
||||
raise AssertionError(
|
||||
".version-bump.json must update .kimi-plugin/plugin.json version"
|
||||
)
|
||||
|
||||
unsupported_fields = [
|
||||
"tools",
|
||||
"commands",
|
||||
"hooks",
|
||||
"apps",
|
||||
"inject",
|
||||
"configFile",
|
||||
"config_file",
|
||||
"bootstrap",
|
||||
]
|
||||
present_unsupported = sorted(field for field in unsupported_fields if field in manifest)
|
||||
if present_unsupported:
|
||||
raise AssertionError(
|
||||
"unsupported Kimi runtime fields present: "
|
||||
+ ", ".join(present_unsupported)
|
||||
)
|
||||
|
||||
print("Kimi plugin manifest looks good")
|
||||
PY
|
||||
179
tests/shell-lint/test-lint-shell.sh
Normal file
179
tests/shell-lint/test-lint-shell.sh
Normal file
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SCRIPT_UNDER_TEST="$REPO_ROOT/scripts/lint-shell.sh"
|
||||
|
||||
FAILURES=0
|
||||
TEST_ROOT="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TEST_ROOT"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
pass() {
|
||||
echo " [PASS] $1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo " [FAIL] $1"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local haystack="$1"
|
||||
local needle="$2"
|
||||
local description="$3"
|
||||
|
||||
if printf '%s' "$haystack" | grep -Fq -- "$needle"; then
|
||||
pass "$description"
|
||||
else
|
||||
fail "$description"
|
||||
echo " expected to find: $needle"
|
||||
echo " in:"
|
||||
printf '%s\n' "$haystack" | sed 's/^/ /'
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local haystack="$1"
|
||||
local needle="$2"
|
||||
local description="$3"
|
||||
|
||||
if printf '%s' "$haystack" | grep -Fq -- "$needle"; then
|
||||
fail "$description"
|
||||
echo " did not expect to find: $needle"
|
||||
echo " in:"
|
||||
printf '%s\n' "$haystack" | sed 's/^/ /'
|
||||
else
|
||||
pass "$description"
|
||||
fi
|
||||
}
|
||||
|
||||
configure_git_identity() {
|
||||
local repo="$1"
|
||||
|
||||
git -C "$repo" config user.name "Test Bot"
|
||||
git -C "$repo" config user.email "test@example.com"
|
||||
}
|
||||
|
||||
write_stub_tool() {
|
||||
local path="$1"
|
||||
local name="$2"
|
||||
|
||||
cat >"$path" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
{
|
||||
printf '${name}:'
|
||||
for arg in "\$@"; do
|
||||
printf ' <%s>' "\$arg"
|
||||
done
|
||||
printf '\n'
|
||||
} >> "\$SUPERPOWERS_SHELL_LINT_TEST_LOG"
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$path"
|
||||
}
|
||||
|
||||
make_fixture_repo() {
|
||||
local repo="$1"
|
||||
|
||||
git init -q -b main "$repo"
|
||||
configure_git_identity "$repo"
|
||||
|
||||
mkdir -p "$repo/hooks"
|
||||
cat >"$repo/tracked.sh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
echo "tracked"
|
||||
EOF
|
||||
cat >"$repo/hooks/session-start" <<'EOF'
|
||||
#!/bin/sh
|
||||
echo "extensionless"
|
||||
EOF
|
||||
cat >"$repo/README.md" <<'EOF'
|
||||
# Fixture
|
||||
|
||||
```bash
|
||||
echo "not a shell script"
|
||||
```
|
||||
EOF
|
||||
cat >"$repo/untracked.sh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
echo "untracked"
|
||||
EOF
|
||||
|
||||
git -C "$repo" add tracked.sh hooks/session-start README.md
|
||||
git -C "$repo" commit -q -m "fixture"
|
||||
|
||||
printf '\necho "changed"\n' >>"$repo/tracked.sh"
|
||||
printf '\necho "changed extensionless"\n' >>"$repo/hooks/session-start"
|
||||
}
|
||||
|
||||
run_lint_shell() {
|
||||
local repo="$1"
|
||||
local fakebin="$2"
|
||||
local log="$3"
|
||||
shift 3
|
||||
|
||||
(
|
||||
cd "$repo"
|
||||
PATH="$fakebin:$PATH" \
|
||||
SUPERPOWERS_SHELL_LINT_TEST_LOG="$log" \
|
||||
bash "$SCRIPT_UNDER_TEST" "$@"
|
||||
)
|
||||
}
|
||||
|
||||
echo "Shell lint script tests"
|
||||
|
||||
fixture="$TEST_ROOT/repo"
|
||||
fakebin="$TEST_ROOT/bin"
|
||||
log="$TEST_ROOT/tool.log"
|
||||
mkdir -p "$fixture" "$fakebin"
|
||||
: >"$log"
|
||||
write_stub_tool "$fakebin/shellcheck" "shellcheck"
|
||||
write_stub_tool "$fakebin/shfmt" "shfmt"
|
||||
make_fixture_repo "$fixture"
|
||||
|
||||
if output="$(run_lint_shell "$fixture" "$fakebin" "$log" 2>&1)"; then
|
||||
pass "lint-shell check mode exits successfully with stub tools"
|
||||
else
|
||||
fail "lint-shell check mode exits successfully with stub tools"
|
||||
printf '%s\n' "$output" | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
tool_log="$(cat "$log")"
|
||||
assert_contains "$output" "Linting 3 shell files" "reports changed shell file count"
|
||||
assert_not_contains "$tool_log" "shfmt:" "does not run shfmt in lint mode"
|
||||
assert_contains "$tool_log" "shellcheck:" "runs ShellCheck"
|
||||
assert_contains "$tool_log" "<--severity=warning>" "uses warning severity as the baseline"
|
||||
assert_contains "$tool_log" "<--external-sources>" "allows ShellCheck to follow sourced files"
|
||||
assert_contains "$tool_log" "<--source-path=SCRIPTDIR>" "resolves ShellCheck sources relative to each script"
|
||||
assert_contains "$tool_log" "<hooks/session-start>" "includes changed extensionless shell shebang file"
|
||||
assert_contains "$tool_log" "<tracked.sh>" "includes changed tracked .sh file"
|
||||
assert_contains "$tool_log" "<untracked.sh>" "includes untracked shell files by default"
|
||||
assert_not_contains "$tool_log" "README.md" "ignores Markdown with shell snippets"
|
||||
|
||||
: >"$log"
|
||||
if output="$(run_lint_shell "$fixture" "$fakebin" "$log" --all --format 2>&1)"; then
|
||||
pass "lint-shell --format exits successfully with stub tools"
|
||||
else
|
||||
fail "lint-shell --format exits successfully with stub tools"
|
||||
printf '%s\n' "$output" | sed 's/^/ /'
|
||||
fi
|
||||
|
||||
tool_log="$(cat "$log")"
|
||||
assert_contains "$tool_log" "<-w>" "uses shfmt write mode with --format"
|
||||
assert_contains "$tool_log" "shellcheck:" "runs ShellCheck after --format"
|
||||
assert_contains "$tool_log" "<--severity=warning>" "keeps warning severity after --format"
|
||||
assert_contains "$tool_log" "<hooks/session-start>" "--all includes tracked extensionless shell shebang file"
|
||||
assert_contains "$tool_log" "<tracked.sh>" "--all includes tracked .sh file"
|
||||
assert_not_contains "$tool_log" "untracked.sh" "--all ignores untracked shell files"
|
||||
|
||||
if [[ "$FAILURES" -eq 0 ]]; then
|
||||
echo "All shell lint script tests passed"
|
||||
else
|
||||
echo "$FAILURES shell lint script test(s) failed"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user