Compare commits

..

1 Commits

Author SHA1 Message Date
Jesse Vincent
505a13e629 docs(porting): correct Antigravity to the install-existing-plugin path
This session proved agy does not need a bespoke scaffold: `agy plugin
install <repo-url>` imports the Claude Code plugin's skills AND its
SessionStart hook, which agy runs — so the bootstrap fires the Shape-A way
with no new files (verified: brainstorming auto-triggers on agy 1.0.3).

Replace the doc's now-false 'agy ships a generated ANTIGRAVITY.md context
file via .antigravity-plugin/install.sh' framing throughout:

- Part 2: Antigravity is the marquee 'you may not need a new directory'
  case; install the existing plugin and run the acceptance test before
  building anything.
- Part 4: add the inverse-of-a-fork-warning (a derived harness may inherit
  the parent's hook execution); replace the routing row with
  'installs an existing plugin directly, including its hook'.
- Step 5 / Part 6: bootstrap escalation now leads with 'check for an
  inherited hook'; if a manifest names a context file, point it at the real
  using-superpowers/SKILL.md — never generate a wrapped copy at install time.
- Appendix A: add the Antigravity row. Appendix B: add two gotchas.

Note: references to references/antigravity-tools.md and tests/antigravity/
depend on PR #1657 landing on dev.
2026-06-01 10:48:12 -07:00
9 changed files with 217 additions and 295 deletions

View File

@@ -4,7 +4,7 @@ Superpowers is a complete software development methodology for your coding agent
## Quickstart ## Quickstart
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), [OpenCode](#opencode), [Pi](#pi). 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).
## How it works ## How it works
@@ -60,17 +60,6 @@ The Superpowers marketplace provides Superpowers and some other related plugins
/plugin install superpowers@superpowers-marketplace /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 ### Codex App
Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins). Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins).

View File

@@ -124,10 +124,19 @@ and reuse that fallback wording when it doesn't.
### You may not need a new directory at all ### You may not need a new directory at all
Some "new harnesses" are really existing integrations under a different Some "new harnesses" are really existing integrations under a different
installer. Factory's Droid, for example, consumes the Claude Code plugin via its installer. Factory's Droid consumes the Claude Code plugin via its own `plugin
own `plugin install` command and needs no new files here. Before building, install` command and needs no new files here. Antigravity (`agy`) goes further:
check whether the harness can simply load an existing manifest. A port that adds `agy plugin install <repo-url>` imports the Claude Code plugin's skills **and its
nothing to this repo but a paragraph in the README is a perfectly good outcome. `SessionStart` hook, which agy then runs** — so the bootstrap fires the Shape-A
way (Part 4) with no new manifest, hook config, or installer. Its entire
integration is a tool-mapping reference plus a README section.
**So before building anything, install the existing Claude Code (or Gemini)
plugin into the harness and run the acceptance test (Part 3).** If the harness
already loads the skills and runs the hook, you are done — a port that adds
nothing to this repo but a tool mapping and a README paragraph is a perfectly
good outcome. Don't assume a derived harness needs its own bootstrap mechanism
until you've watched the existing one fail.
--- ---
@@ -204,6 +213,14 @@ had to do every one of these):
`@`-include syntax and *still not honor them the same way*. Verify with a marker; `@`-include syntax and *still not honor them the same way*. Verify with a marker;
never assume the parent's recipe transfers. never assume the parent's recipe transfers.
**The inverse also bites: a derived harness may inherit *more* than you expect.**
Antigravity runs the Claude Code plugin's `SessionStart` hook even though `agy`'s
own plugin docs describe a different model — and we nearly built a whole
context-file scaffold on the false premise that it had no session-start hook.
Test the cheap path first: install the existing Claude Code (or Gemini) plugin
and run the acceptance test (Part 3) *before* writing any harness-specific
bootstrap.
Then route to a shape: Then route to a shape:
- Shell command at session start whose stdout is read → **Shape A**. - Shell command at session start whose stdout is read → **Shape A**.
@@ -290,10 +307,12 @@ part of the installed extension** — never substitute "edit the user's global
| 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/`) | | 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 | | 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`) | | 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) | | installs an existing harness's plugin directly, including its hook | none — no new files | Antigravity (`agy plugin install <repo-url>` imports the Claude Code `skills/` + `hooks/` and runs the `SessionStart` hook), Factory Droid |
Most real harnesses fit one row cleanly; the last is the hybrid case (rule 2 still Most real harnesses fit one row cleanly. The last row is the cheapest outcome and
holds — the bootstrap rides the install mechanism, never a user-config edit). the one to rule out first (Part 2): if the harness just runs the existing plugin,
you write almost nothing. Rule 2 still holds in every row — the bootstrap rides
the install mechanism, never a user-config edit.
--- ---
@@ -480,8 +499,9 @@ path field is **ignored** — one real harness only scanned a `skills/` sitting
to `plugin.json`); an API/registration call (OpenCode, pi); or you stage an 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 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 point the installer at the staging dir (verify the installer *dereferences* the
symlink and copies the real files — confirm with `agy plugin validate`/`install` symlink and copies the real files — confirm with the harness's own
or the equivalent before relying on it). A `skills` path field is *not* portable. `validate`/`install` command before relying on it). A `skills` path field is *not*
portable.
Where the mapping lives depends on shape: Where the mapping lives depends on shape:
@@ -524,11 +544,14 @@ honors the rule rather than breaking it. Distinguish three cases:
the file-read tool when the skill applies** — the sanctioned mechanism here, the file-read tool when the skill applies** — the sanctioned mechanism here,
the way `references/pi-tools.md` states it. the way `references/pi-tools.md` states it.
**For the bootstrap itself, prefer a declared context file (Part 6).** If the **For the bootstrap itself, first check for an inherited hook (Part 2).** If the
harness has a `contextFileName`-style manifest field — as Antigravity does — harness runs the existing Claude Code plugin's `SessionStart` hook — Antigravity
ship a generated context file through the installer: it's guaranteed-loaded and does; test it before assuming otherwise — the bootstrap is already delivered and
carries both the `using-superpowers` content and the tool mapping. That is the you ship nothing. Only if it doesn't, and the harness has a `contextFileName`-style
strong, preferred path. manifest field, point that field at the real `using-superpowers/SKILL.md` (or a
tiny committed `@`-include file like `GEMINI.md`). **Never generate a wrapped copy
of the bootstrap at install time:** a generated copy needs an installer and drifts
from source. You get to name the file, so name the real one.
**Fallback — the surfaced skill index.** If there's no context-file field but **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, the harness surfaces each installed skill's name + description at session start,
@@ -677,7 +700,7 @@ it. Distribution differs per harness ecosystem — find yours:
| 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). | | 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, OpenCode | Users install from a git URL (`gemini extensions install …`; an `opencode.json` `plugin` array entry). Document the exact command. | | Git-URL extension install | Gemini, OpenCode | Users install from a git URL (`gemini extensions 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. | | 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). | | Existing plugin, installed directly | Antigravity (`agy`), Factory Droid | The harness installs another harness's committed plugin from a git URL — `agy plugin install <repo-url>` imports the Claude Code `skills/` + `hooks/` and runs the `SessionStart` hook. No installer script, no new manifest: document the one-line install command. |
Then: Then:
@@ -685,29 +708,33 @@ Then:
bootstrap a file the installer *recognizes*, never a user-config edit.** A bootstrap a file the installer *recognizes*, never a user-config edit.** A
`plugin install` typically copies only the components it knows about `plugin install` typically copies only the components it knows about
(skills/agents/commands/mcp/hooks/context) and discards anything else, so a (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 file the manifest doesn't declare just vanishes from the install. The fix is
fix is **not** to give up and write into the user's config (**rule 2**) — it's **not** to give up and write into the user's config (**rule 2**) — it's to make
to declare the bootstrap as a recognized component. In escalation order: the bootstrap a recognized component. In escalation order:
- **Ship a context file the manifest declares.** If the harness has a - **Check for an inherited hook first.** If `plugin install` imports an existing
`contextFileName`-style field (an extension-declared file it loads every Claude Code plugin's `hooks/` and the harness runs the `SessionStart` hook,
session), that is the strongest clean bootstrap: declare it, and the installer the bootstrap is already delivered and you ship nothing. Antigravity does
preserves it *and* the harness loads it. Generate it at install time from the exactly this — `agy plugin install <repo-url>` brings in the skills and the
live `using-superpowers/SKILL.md` + the tool mapping (wrapped in hook, and a clean session loads `using-superpowers`, triggers `brainstorming`,
`<EXTREMELY_IMPORTANT>`) so the installed bootstrap never drifts. This is what and enters the brainstorming flow before any code. Verify with the acceptance
`.antigravity-plugin/install.sh` does — `agy plugin install` reports test. This is the cheapest and most robust path; rule it out before the rest.
`✔ context : ANTIGRAVITY.md`, and a clean session reads `using-superpowers`'s - **Otherwise, if the manifest declares a context file, point it at the real
SKILL.md, loads `brainstorming`, and enters the brainstorming flow before any skill.** A `contextFileName`-style field names a file the installer preserves
code. **Verify with a marker** that the installer keeps the file and the and the harness loads every session. Point it straight at
harness loads it: one porter wrongly concluded it couldn't, because they `using-superpowers/SKILL.md`, or at a tiny committed `@`-include file like
shipped the file *without* declaring `contextFileName` and it was stripped as `GEMINI.md` if the harness expands includes (prove the expansion — Shape C
unrecognized. caveat). **Do not generate a wrapped copy of the bootstrap at install time:**
that needs an installer and can drift from source. You get to name the file,
so name the real one. **Verify with a marker** that the installer keeps the
file and the harness loads it — an undeclared file is stripped as unrecognized.
- **Otherwise lean on the installed `using-superpowers` skill itself.** If the - **Otherwise lean on the installed `using-superpowers` skill itself.** If the
harness surfaces each installed skill's name + description at session start, harness surfaces each installed skill's name + description at session start,
the `using-superpowers` description ("Use when starting any conversation…") the `using-superpowers` description ("Use when starting any conversation…")
can prompt the model to load it — installing the skill *is* the bootstrap. 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 Softer (no guaranteed wrapper; it carries triggering but not the tool mapping
— see Step 5), so prefer the declared context file when available. — see Step 5), so prefer an inherited hook or a declared context file when
- If neither works, the harness cannot be cleanly supported yet — **say so** available.
- If none works, the harness cannot be cleanly supported yet — **say so**
and raise it, rather than hand-editing the user's config. and raise it, rather than hand-editing the user's config.
- **Write install docs.** A `docs/README.<harness>.md` and/or a - **Write install docs.** A `docs/README.<harness>.md` and/or a
@@ -755,9 +782,10 @@ Two rules this enforces, which you must respect:
- Don't write per-OS variants of the hook script. One extensionless bash script - Don't write per-OS variants of the hook script. One extensionless bash script
plus the polyglot wrapper covers all three platforms. plus the polyglot wrapper covers all three platforms.
`hooks/run-hook.cmd` itself is the authoritative implementation — read it. See `hooks/run-hook.cmd` itself is the authoritative implementation — read it.
`docs/windows/polyglot-hooks.md` for the background and rationale behind the (`docs/windows/polyglot-hooks.md` covers the background and rationale but
dispatcher pattern. describes an earlier per-script `.cmd`/`.sh` variant, so trust the code over that
doc where they differ.)
--- ---
@@ -784,6 +812,7 @@ Use this as the live index; when in doubt, read the files, not this table.
| Harness | Entry point | Bootstrap mechanism | Tool mapping | Tests | Distribution | | 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 | | 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 |
| Antigravity (`agy`) | none — installs the Claude Code plugin | `agy plugin install <repo-url>` imports `skills/` + `hooks/` and runs the Claude Code `SessionStart` hook | no `Skill` tool; load via `view_file` on `SKILL.md`; `references/antigravity-tools.md` | `tests/antigravity/` | `agy plugin install <git-url>` (README) |
| 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`) | | 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 | | 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/` | — | | Copilot CLI | (shares Claude Code hook path; `COPILOT_CLI` env) | shell hook → `hooks/session-start` (`additionalContext`) | `references/copilot-tools.md` | `tests/hooks/` | — |
@@ -795,6 +824,14 @@ Use this as the live index; when in doubt, read the files, not this table.
- **Opt-in isn't a port.** If your human partner has to do anything per session - **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. to get Superpowers, the acceptance test fails. Re-read Part 2.
- **Assuming a derived harness lacks the parent's hook.** Antigravity runs the
Claude Code `SessionStart` hook; we nearly shipped a whole context-file scaffold
on the false premise that it didn't. Install the existing plugin and run the
acceptance test before building any bootstrap mechanism (Part 2).
- **Generating a wrapped bootstrap copy at install time.** If a manifest's
`contextFileName` lets you name the file, point it at the real
`using-superpowers/SKILL.md` (or a committed `@`-include file). A generated copy
needs an installer and drifts from source (Part 6).
- **Wrong JSON field → silent failure or double injection.** Shape A only. - **Wrong JSON field → silent failure or double injection.** Shape A only.
Confirm the exact field/nesting; Claude Code reads two fields without dedup. 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` - **Hook-config schema varies per harness.** Shape A. Cursor's `hooks-cursor.json`

View File

@@ -1,8 +1,6 @@
# Cross-Platform Polyglot Hooks for Claude Code # Cross-Platform Polyglot Hooks for Claude Code
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`. Claude Code plugins need hooks that work on Windows, macOS, and Linux. This document explains the polyglot wrapper technique that makes this possible.
> **Authoritative source:** `hooks/run-hook.cmd` is the canonical implementation. When this document and the code diverge, trust the code.
## The Problem ## The Problem
@@ -12,22 +10,52 @@ Claude Code runs hook commands through the system's default shell:
This creates several challenges: This creates several challenges:
1. **Script execution**: Windows CMD can't execute `.sh` files directly 1. **Script execution**: Windows CMD can't execute `.sh` files directly - it tries to open them in a text editor
2. **Path format**: Windows uses backslashes (`C:\path`), Unix uses forward slashes (`/path`) 2. **Path format**: Windows uses backslashes (`C:\path`), Unix uses forward slashes (`/path`)
3. **Environment variables**: `$VAR` syntax doesn't work in CMD 3. **Environment variables**: `$VAR` syntax doesn't work in CMD
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 4. **No `bash` in PATH**: Even with Git Bash installed, `bash` isn't in the PATH when CMD runs
## The Solution: Extensionless Scripts + Single Generic Dispatcher ## The Solution: Polyglot `.cmd` Wrapper
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. A polyglot script is valid syntax in multiple languages simultaneously. Our wrapper is valid in both CMD and bash:
### File Structure ```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
``` ```
hooks/ hooks/
├── hooks.json # Points to run-hook.cmd with extensionless script name ├── hooks.json # Points to the .cmd wrapper
├── run-hook.cmd # Cross-platform dispatcher (the polyglot wrapper) ├── session-start.cmd # Polyglot wrapper (cross-platform entry point)
└── session-start # Actual hook logic — extensionless bash script └── session-start.sh # Actual hook logic (bash script)
``` ```
### hooks.json ### hooks.json
@@ -37,12 +65,11 @@ hooks/
"hooks": { "hooks": {
"SessionStart": [ "SessionStart": [
{ {
"matcher": "startup|clear|compact", "matcher": "startup|resume|clear|compact",
"hooks": [ "hooks": [
{ {
"type": "command", "type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start", "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.cmd\""
"async": false
} }
] ]
} }
@@ -51,63 +78,41 @@ hooks/
} }
``` ```
The path is quoted because `${CLAUDE_PLUGIN_ROOT}` may contain spaces. Note: The path must be quoted because `${CLAUDE_PLUGIN_ROOT}` may contain spaces on Windows (e.g., `C:\Program Files\...`).
## How `run-hook.cmd` Works at a High Level ## Requirements
`run-hook.cmd` is a polyglot script: Windows treats the first block as batch ### Windows
commands, while Unix shells treat that block as a no-op heredoc and continue - **Git for Windows** must be installed (provides `bash.exe` and `cygpath`)
after it. - Default installation path: `C:\Program Files\Git\bin\bash.exe`
- If Git is installed elsewhere, the wrapper needs modification
Do not copy an implementation from this document. Read `hooks/run-hook.cmd` ### Unix (macOS/Linux)
directly when changing the dispatcher, and run `tests/hooks/test-session-start.sh` - Standard bash or sh shell
afterward. - The `.cmd` file must have execute permission (`chmod +x`)
### 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 ## Writing Cross-Platform Hook Scripts
Your hook logic goes in the extensionless script file. A few portable patterns: Your actual hook logic goes in the `.sh` file. To ensure it works on Windows (via Git Bash):
### Do ### Do:
- Use pure bash builtins when possible - Use pure bash builtins when possible
- Use `$(command)` instead of backticks - Use `$(command)` instead of backticks
- Quote all variable expansions: `"$VAR"` - Quote all variable expansions: `"$VAR"`
- Use `printf` or here-docs for output
### Avoid ### Avoid:
- Relying on PATH-dependent tools without fallbacks (the hook runs without `-l`, so login-shell PATH is not set) - External commands that may not be in PATH (sed, awk, grep)
- Giving scripts a `.sh` extension — this triggers Claude Code's Windows auto-prepend - If you must use them, they're available in Git Bash but ensure PATH is set up (use `bash -l`)
### Example: JSON escaping without external tools ### Example: JSON Escaping Without sed/awk
Instead of:
```bash
escaped=$(echo "$content" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}')
```
Use pure bash:
```bash ```bash
escape_for_json() { escape_for_json() {
local input="$1" local input="$1"
@@ -128,21 +133,80 @@ 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 ## Troubleshooting
### "bash is not recognized" ### "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.
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`. ### "cygpath: command not found" or "dirname: command not found"
Bash isn't running as a login shell. Ensure `-l` flag is used.
### Hook runs on Unix but does nothing on Windows ### 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.
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. ### Script opens in text editor instead of running
The hooks.json is pointing directly to the `.sh` file. Point to the `.cmd` wrapper instead.
### Hook doesn't fire at all ### Works in terminal but not as hook
Claude Code may run hooks differently. Test by simulating the hook environment:
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. ```powershell
$env:CLAUDE_PLUGIN_ROOT = "C:\path\to\plugin"
cmd /c "C:\path\to\plugin\hooks\session-start.cmd"
```
## Related Issues ## Related Issues
- [anthropics/claude-code#9758](https://github.com/anthropics/claude-code/issues/9758) `.sh` scripts open in editor on Windows - [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#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

View File

@@ -107,23 +107,10 @@ if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then
OWNER_PID="$PPID" OWNER_PID="$PPID"
fi 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. # Foreground mode for environments that reap detached/background processes.
if [[ "$FOREGROUND" == "true" ]]; then if [[ "$FOREGROUND" == "true" ]]; then
env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs & echo "$$" > "$PID_FILE"
SERVER_PID=$! env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs
echo "$SERVER_PID" > "$PID_FILE"
wait "$SERVER_PID"
exit $? exit $?
fi fi

View File

@@ -123,6 +123,16 @@ git branch -d <feature-branch>
```bash ```bash
# Push branch # Push branch
git push -u origin <feature-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. **Do NOT clean up worktree** — user needs it alive to iterate on PR feedback.

View File

@@ -41,7 +41,7 @@ If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "alw
## Platform Adaptation ## 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), [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. 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.
# Using Skills # Using Skills

View File

@@ -1,96 +0,0 @@
# 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 (`spec-reviewer`, `code-quality-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.

View File

@@ -1,16 +0,0 @@
#!/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 ==="

View File

@@ -1,53 +0,0 @@
#!/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)"