Compare commits

..

1 Commits

Author SHA1 Message Date
Drew Ritter
867238cfc1 Add shell lint script 2026-06-01 14:12:54 -07:00
24 changed files with 151 additions and 445 deletions

View File

@@ -12,17 +12,14 @@ add a comment or reaction to the existing one instead.
- [ ] I searched existing issues and this is not a duplicate - [ ] I searched existing issues and this is not a duplicate
## Environment (required) ## Environment
<!-- 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 | | Field | Value |
|-------|-------| |-------|-------|
| Superpowers version | | | Superpowers version | |
| Harness (Claude Code, Cursor, etc.) | | | Harness (Claude Code, Cursor, etc.) | |
| Harness version | | | Harness version | |
| Your model + version | | | Model | |
| All plugins installed | |
| OS + shell | | | OS + shell | |
## Is this a Superpowers issue or a platform issue? ## Is this a Superpowers issue or a platform issue?

View File

@@ -30,18 +30,5 @@ progress, and some were intentionally declined.
of project? If this is specific to your domain, workflow, or a of project? If this is specific to your domain, workflow, or a
third-party tool, it may belong as its own plugin instead. --> 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 ## Context
<!-- Optional: the workflow where you hit this, links, transcripts. --> <!-- Optional: version info, harness, model, workflow where you hit this. -->

View File

@@ -21,14 +21,3 @@ requested or discussed.
## Have you tried manual installation? ## Have you tried manual installation?
<!-- Many tools work with Superpowers through manual setup even without <!-- Many tools work with Superpowers through manual setup even without
official support. Did you try? What happened? --> 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 | |

View File

@@ -4,23 +4,6 @@ sections blank, contain multiple unrelated changes, or show no evidence
of human involvement will be closed without review. 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? ## What problem are you trying to solve?
<!-- Describe the specific problem you encountered. If this was a session <!-- Describe the specific problem you encountered. If this was a session
issue, include: what you were doing, what went wrong, the model's issue, include: what you were doing, what went wrong, the model's

View File

@@ -1,38 +0,0 @@
{
"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"
}
}

View File

@@ -4,7 +4,6 @@
{ "path": ".claude-plugin/plugin.json", "field": "version" }, { "path": ".claude-plugin/plugin.json", "field": "version" },
{ "path": ".cursor-plugin/plugin.json", "field": "version" }, { "path": ".cursor-plugin/plugin.json", "field": "version" },
{ "path": ".codex-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": ".claude-plugin/marketplace.json", "field": "plugins.0.version" },
{ "path": "gemini-extension.json", "field": "version" } { "path": "gemini-extension.json", "field": "version" }
], ],

View File

@@ -14,8 +14,7 @@ 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. 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. 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. 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. **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. 5. **Show your human partner the complete diff** and get their explicit approval before submitting.
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. 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.
@@ -27,10 +26,6 @@ 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. **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 ## What We Will Not Accept
### Third-party dependencies ### Third-party dependencies

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), [Kimi Code](#kimi-code), [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), [OpenCode](#opencode), [Pi](#pi).
## How it works ## How it works
@@ -149,26 +149,6 @@ Superpowers is available via the [official Codex plugin marketplace](https://git
copilot plugin install superpowers@superpowers-marketplace 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
OpenCode uses its own plugin install; install Superpowers separately even if you OpenCode uses its own plugin install; install Superpowers separately even if you

View File

@@ -1,88 +0,0 @@
# 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.

View File

@@ -675,7 +675,7 @@ it. Distribution differs per harness ecosystem — find yours:
|---|---|---| |---|---|---|
| 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`. | | 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). | | 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. | | 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). | | 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). |
@@ -755,9 +755,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.)
--- ---
@@ -788,7 +789,6 @@ Use this as the live index; when in doubt, read the files, not this table.
| 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/` | — |
| Gemini CLI | `gemini-extension.json` + `GEMINI.md` | instructions file `@`-includes bootstrap + mapping | `references/gemini-tools.md` | — | `gemini extensions install` | | 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 | | 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 | | 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 |

View File

@@ -12,7 +12,6 @@ Live in `tests/`. Currently:
- `tests/brainstorm-server/` — node test suite for the brainstorm server JS code. - `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/opencode/` — bash tests for OpenCode plugin loading, bootstrap caching, and tool registration.
- `tests/codex-plugin-sync/` — bash sync verification. - `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-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.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). - `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).

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

2
evals

Submodule evals updated: ff3ee83f94...e2b37138c8

View File

@@ -52,7 +52,6 @@ EXCLUDES=(
"/.gitattributes" "/.gitattributes"
"/.github/" "/.github/"
"/.gitignore" "/.gitignore"
"/.kimi-plugin/"
"/.opencode/" "/.opencode/"
"/.pi/" "/.pi/"
"/.version-bump.json" "/.version-bump.json"

View File

@@ -26,7 +26,7 @@ You MUST create a task for each of these items and complete them in order:
3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria 3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
4. **Propose 2-3 approaches** — with trade-offs and your recommendation 4. **Propose 2-3 approaches** — with trade-offs and your recommendation
5. **Present design** — in sections scaled to their complexity, get user approval after each section 5. **Present design** — in sections scaled to their complexity, get user approval after each section
6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit (exactly this path — not `docs/specs/`) 6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit
7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) 7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below)
8. **User reviews written spec** — ask user to review the spec file before proceeding 8. **User reviews written spec** — ask user to review the spec file before proceeding
9. **Transition to implementation** — invoke writing-plans skill to create implementation plan 9. **Transition to implementation** — invoke writing-plans skill to create implementation plan
@@ -109,7 +109,6 @@ digraph brainstorming {
**Documentation:** **Documentation:**
- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` - Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`
- The `docs/superpowers/` prefix is the convention; do not shorten it to `docs/specs/`
- (User preferences for spec location override this default) - (User preferences for spec location override this default)
- Use elements-of-style:writing-clearly-and-concisely skill if available - Use elements-of-style:writing-clearly-and-concisely skill if available
- Commit the design document to git - Commit the design document to git

View File

@@ -7,7 +7,6 @@ const path = require('path');
const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A }; const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A };
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const MAX_FRAME_PAYLOAD_BYTES = 10 * 1024 * 1024;
function computeAcceptKey(clientKey) { function computeAcceptKey(clientKey) {
return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64'); return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64');
@@ -54,18 +53,10 @@ function decodeFrame(buffer) {
offset = 4; offset = 4;
} else if (payloadLen === 127) { } else if (payloadLen === 127) {
if (buffer.length < 10) return null; if (buffer.length < 10) return null;
const extendedLen = buffer.readBigUInt64BE(2); payloadLen = Number(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; offset = 10;
} }
if (payloadLen > MAX_FRAME_PAYLOAD_BYTES) {
throw new Error('WebSocket frame payload exceeds maximum allowed size');
}
const maskOffset = offset; const maskOffset = offset;
const dataOffset = offset + 4; const dataOffset = offset + 4;
const totalLen = dataOffset + payloadLen; const totalLen = dataOffset + payloadLen;
@@ -360,4 +351,4 @@ if (require.main === module) {
startServer(); startServer();
} }
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES, MAX_FRAME_PAYLOAD_BYTES }; module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES };

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

@@ -11,8 +11,6 @@ Execute plan by dispatching fresh subagent per task, with two-stage review after
**Core principle:** Fresh subagent per task + two-stage review (spec then quality) = high quality, fast iteration **Core principle:** Fresh subagent per task + two-stage review (spec then quality) = high quality, fast iteration
**Proportionality:** Review fanout scales with the change. When the entire plan is one trivial, fully-specified mechanical change — a one-line edit, a log statement, a constant bump — implement it directly (or with a single implementer subagent), verify it, and commit. Skip the review subagents and the final reviewer: a diff with no room for interpretation has nothing for a spec or quality review to catch, and three dispatches for one line cost more than the change itself. When in doubt whether a change is trivial, it is not — run the full pipeline. Within a multi-task plan, run the full pipeline for every task regardless of size; this exception applies only when the whole plan is one trivial change.
**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. **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.
## When to Use ## When to Use
@@ -63,16 +61,11 @@ digraph process {
} }
"Read plan, extract all tasks with full text, note context, create todos" [shape=box]; "Read plan, extract all tasks with full text, note context, create todos" [shape=box];
"Entire plan = one trivial mechanical change?" [shape=diamond];
"Implement directly, verify, commit (no review fanout)" [shape=box];
"More tasks remain?" [shape=diamond]; "More tasks remain?" [shape=diamond];
"Dispatch final code reviewer subagent for entire implementation" [shape=box]; "Dispatch final code reviewer subagent for entire implementation" [shape=box];
"Use superpowers:finishing-a-development-branch" [shape=box style=filled fillcolor=lightgreen]; "Use superpowers:finishing-a-development-branch" [shape=box style=filled fillcolor=lightgreen];
"Read plan, extract all tasks with full text, note context, create todos" -> "Entire plan = one trivial mechanical change?"; "Read plan, extract all tasks with full text, note context, create todos" -> "Dispatch implementer subagent (./implementer-prompt.md)";
"Entire plan = one trivial mechanical change?" -> "Implement directly, verify, commit (no review fanout)" [label="yes — see Proportionality"];
"Implement directly, verify, commit (no review fanout)" -> "Use superpowers:finishing-a-development-branch";
"Entire plan = one trivial mechanical change?" -> "Dispatch implementer subagent (./implementer-prompt.md)" [label="no"];
"Dispatch implementer subagent (./implementer-prompt.md)" -> "Implementer subagent asks questions?"; "Dispatch implementer subagent (./implementer-prompt.md)" -> "Implementer subagent asks questions?";
"Implementer subagent asks questions?" -> "Answer questions, provide context" [label="yes"]; "Implementer subagent asks questions?" -> "Answer questions, provide context" [label="yes"];
"Answer questions, provide context" -> "Dispatch implementer subagent (./implementer-prompt.md)"; "Answer questions, provide context" -> "Dispatch implementer subagent (./implementer-prompt.md)";
@@ -244,7 +237,7 @@ Done!
**Never:** **Never:**
- Start implementation on main/master branch without explicit user consent - Start implementation on main/master branch without explicit user consent
- Skip reviews (spec compliance OR code quality) on a non-trivial task — the Proportionality exception covers only a plan that is one trivial mechanical change - Skip reviews (spec compliance OR code quality)
- Proceed with unfixed issues - Proceed with unfixed issues
- Dispatch multiple implementation subagents in parallel (conflicts) - Dispatch multiple implementation subagents in parallel (conflicts)
- Make subagent read plan file (provide full text instead) - Make subagent read plan file (provide full text instead)

View File

@@ -103,9 +103,6 @@ Subagent (general-purpose):
- **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT - **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
- What you implemented (or what you attempted, if blocked) - What you implemented (or what you attempted, if blocked)
- What you tested and test results - 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 - Files changed
- Self-review findings (if any) - Self-review findings (if any)
- Any issues or concerns - Any issues or concerns

View File

@@ -7,12 +7,10 @@ description: Use when you have a spec or requirements for a multi-step task, bef
## Overview ## Overview
Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to execute: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits. Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to know: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits.
Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well. Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well.
**Plans reference the spec; they never restate it.** The spec owns the WHAT and WHY — requirements, acceptance criteria, design decisions. The plan owns the HOW — tasks, files, code, commands. Cite the spec by path in the header and by section where a task needs context. Re-deriving spec content inline doubles the documents and lets them drift apart. "Zero context" means the engineer can execute each step mechanically; it does not mean the plan repeats what the spec already says — they can read the spec at the cited path.
**Announce at start:** "I'm using the writing-plans skill to create the implementation plan." **Announce at start:** "I'm using the writing-plans skill to create the implementation plan."
**Context:** If working in an isolated worktree, it should have been created via the `superpowers:using-git-worktrees` skill at execution time. **Context:** If working in an isolated worktree, it should have been created via the `superpowers:using-git-worktrees` skill at execution time.
@@ -55,8 +53,6 @@ This structure informs the task decomposition. Each task should produce self-con
**Goal:** [One sentence describing what this builds] **Goal:** [One sentence describing what this builds]
**Spec:** [Path to the spec doc, e.g. `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` — requirements and design decisions live there; do not restate them here]
**Architecture:** [2-3 sentences about approach] **Architecture:** [2-3 sentences about approach]
**Tech Stack:** [Key technologies/libraries] **Tech Stack:** [Key technologies/libraries]

View File

@@ -329,21 +329,6 @@ function runTests() {
assert.strictEqual(result.payload.length, 65536); 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 ========== // ========== Close Frame with Status Code ==========
console.log('\n--- Close Frame Details ---'); console.log('\n--- Close Frame Details ---');

View File

@@ -175,7 +175,6 @@ write_upstream_fixture() {
mkdir -p \ mkdir -p \
"$repo/.codex-plugin" \ "$repo/.codex-plugin" \
"$repo/.kimi-plugin" \
"$repo/.private-journal" \ "$repo/.private-journal" \
"$repo/assets" \ "$repo/assets" \
"$repo/evals/drill" \ "$repo/evals/drill" \
@@ -211,13 +210,6 @@ EOF
"name": "superpowers", "name": "superpowers",
"version": "$MANIFEST_VERSION" "version": "$MANIFEST_VERSION"
} }
EOF
cat > "$repo/.kimi-plugin/plugin.json" <<EOF
{
"name": "superpowers",
"version": "$MANIFEST_VERSION"
}
EOF EOF
cat > "$repo/assets/superpowers-small.svg" <<'EOF' cat > "$repo/assets/superpowers-small.svg" <<'EOF'
@@ -275,7 +267,6 @@ EOF
git -C "$repo" add \ git -C "$repo" add \
.codex-plugin/plugin.json \ .codex-plugin/plugin.json \
.kimi-plugin/plugin.json \
.gitignore \ .gitignore \
assets/app-icon.png \ assets/app-icon.png \
assets/superpowers-small.svg \ assets/superpowers-small.svg \
@@ -424,15 +415,10 @@ EOF
write_stale_ignored_destination_fixture() { write_stale_ignored_destination_fixture() {
local repo="$1" local repo="$1"
mkdir -p \ mkdir -p "$repo/plugins/superpowers/.private-journal"
"$repo/plugins/superpowers/.kimi-plugin" \
"$repo/plugins/superpowers/.private-journal"
printf 'fixture keep\n' > "$repo/plugins/superpowers/.fixture-keep" 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" printf 'stale ignored leak\n' > "$repo/plugins/superpowers/.private-journal/leak.txt"
git -C "$repo" add \ git -C "$repo" add plugins/superpowers/.fixture-keep
plugins/superpowers/.fixture-keep \
plugins/superpowers/.kimi-plugin/plugin.json
commit_fixture "$repo" "Initial stale ignored destination fixture" commit_fixture "$repo" "Initial stale ignored destination fixture"
} }
@@ -632,7 +618,6 @@ main() {
assert_contains "$preview_output" "Version: $MANIFEST_VERSION" "Preview uses manifest version" 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_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_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/superpowers-small.svg" "Preview includes SVG asset"
assert_contains "$preview_section" "assets/app-icon.png" "Preview includes PNG 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" assert_contains "$preview_section" "hooks/hooks-codex.json" "Preview includes Codex hook manifest"
@@ -659,7 +644,6 @@ main() {
echo "" echo ""
echo "Convergence assertions..." echo "Convergence assertions..."
assert_equals "$stale_preview_status" "0" "Stale ignored destination preview exits successfully" 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" assert_matches "$stale_preview_section" "\\*deleting +\\.private-journal/leak\\.txt" "Preview deletes stale ignored destination file"
echo "" echo ""

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
bash "$SCRIPT_DIR/test-plugin-manifest.sh"

View File

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