mirror of
https://github.com/obra/superpowers.git
synced 2026-06-10 20:59:05 +08:00
Compare commits
14 Commits
codex/trim
...
drew/sup-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa07663322 | ||
|
|
f55642e0dd | ||
|
|
ae1eefb7f9 | ||
|
|
617168aff5 | ||
|
|
d7c260a978 | ||
|
|
f3f0789c5c | ||
|
|
16a1719988 | ||
|
|
c74c22daa7 | ||
|
|
773bbf61d6 | ||
|
|
6b76158550 | ||
|
|
7fec40bb55 | ||
|
|
2a8e54735b | ||
|
|
f776394360 | ||
|
|
7301c81b4d |
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
7
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -12,14 +12,17 @@ add a comment or reaction to the existing one instead.
|
|||||||
|
|
||||||
- [ ] I searched existing issues and this is not a duplicate
|
- [ ] I searched existing issues and this is not a duplicate
|
||||||
|
|
||||||
## Environment
|
## Environment (required)
|
||||||
|
<!-- Required. We assume an agent filed this report — tell us which one and
|
||||||
|
where it ran. We weigh reports by what produced them. -->
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| Superpowers version | |
|
| Superpowers version | |
|
||||||
| Harness (Claude Code, Cursor, etc.) | |
|
| Harness (Claude Code, Cursor, etc.) | |
|
||||||
| Harness version | |
|
| Harness version | |
|
||||||
| Model | |
|
| Your model + version | |
|
||||||
|
| 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?
|
||||||
|
|||||||
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -30,5 +30,18 @@ progress, and some were intentionally declined.
|
|||||||
of project? If this is specific to your domain, workflow, or a
|
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: version info, harness, model, workflow where you hit this. -->
|
<!-- Optional: the workflow where you hit this, links, transcripts. -->
|
||||||
|
|||||||
11
.github/ISSUE_TEMPLATE/platform_support.md
vendored
11
.github/ISSUE_TEMPLATE/platform_support.md
vendored
@@ -21,3 +21,14 @@ requested or discussed.
|
|||||||
## Have you tried manual installation?
|
## 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 | |
|
||||||
|
|||||||
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -4,6 +4,23 @@ sections blank, contain multiple unrelated changes, or show no evidence
|
|||||||
of human involvement will be closed without review.
|
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
|
||||||
|
|||||||
38
.kimi-plugin/plugin.json
Normal file
38
.kimi-plugin/plugin.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "superpowers",
|
||||||
|
"version": "5.1.0",
|
||||||
|
"description": "An agentic skills framework and software development methodology.",
|
||||||
|
"author": {
|
||||||
|
"name": "Jesse Vincent",
|
||||||
|
"email": "jesse@fsck.com"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/obra/superpowers",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"brainstorming",
|
||||||
|
"subagent-driven-development",
|
||||||
|
"skills",
|
||||||
|
"planning",
|
||||||
|
"tdd",
|
||||||
|
"debugging",
|
||||||
|
"code-review",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"skills": "./skills/",
|
||||||
|
"sessionStart": {
|
||||||
|
"skill": "using-superpowers"
|
||||||
|
},
|
||||||
|
"skillInstructions": "Kimi Code tool mapping for Superpowers skills:\n\n- When a Superpowers skill says to ask the user, ask clarifying questions, ask one question at a time, present multiple-choice options, use the terminal for a question, or wait for the user's choice, call Kimi Code's `AskUserQuestion` tool. Do not render those choices as plain assistant text unless `AskUserQuestion` is unavailable or the session is in auto permission mode.\n- For `AskUserQuestion`, provide 1 question with 2-4 concrete options when possible. Put the recommended option first and suffix its label with `(Recommended)`.\n- When a Superpowers skill refers to `TodoWrite`, use Kimi Code's `TodoList` tool.\n- When a Superpowers skill says `Task tool (general-purpose)` or asks you to dispatch an implementer/reviewer subagent, use Kimi Code's `Agent` tool with a Kimi subagent type. Do not pass `general-purpose` as `subagent_type`.\n- For implementation, code review, spec review, quality review, and filled Superpowers subagent prompt templates, call `Agent` with `subagent_type: \"coder\"`, paste the fully filled prompt into `prompt`, and provide a short `description`.\n- For read-only codebase exploration that would take several searches, use `Agent` with `subagent_type: \"explore\"`.\n- For read-only planning or architecture design, use `Agent` with `subagent_type: \"plan\"`.\n- Keep dependent Superpowers subagent steps sequential. Use multiple `Agent` calls, or `run_in_background: true` only when the work is independent and background agents are available.\n- When a Superpowers skill refers to the `Skill` tool, use Kimi Code's native `Skill` tool.\n- Use Kimi Code's `Read`, `Write`, `Edit`, `Bash`, `Grep`, `Glob`, `FetchURL`, `WebSearch`, and MCP tools by their actual exposed names.\n- When a skill asks to search file contents, use `Grep`; when it asks to find files by path or pattern, use `Glob`; when it asks to fetch a URL, use `FetchURL`; when it asks to search the web, use `WebSearch`.",
|
||||||
|
"interface": {
|
||||||
|
"displayName": "Superpowers",
|
||||||
|
"shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents",
|
||||||
|
"longDescription": "Use Superpowers to guide agent work through brainstorming, implementation planning, test-driven development, systematic debugging, parallel execution, code review, and finish-the-branch workflows.",
|
||||||
|
"developerName": "Jesse Vincent",
|
||||||
|
"capabilities": [
|
||||||
|
"Interactive",
|
||||||
|
"Read",
|
||||||
|
"Write"
|
||||||
|
],
|
||||||
|
"websiteURL": "https://github.com/obra/superpowers"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
{ "path": ".claude-plugin/plugin.json", "field": "version" },
|
{ "path": ".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" }
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ Before you open a PR against this repo, you MUST:
|
|||||||
2. **Search for existing PRs** — open AND closed — that address the same problem. If duplicates exist, STOP and tell your human partner. Do not open another duplicate.
|
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. **Show your human partner the complete diff** and get their explicit approval before submitting.
|
5. **Identify yourself.** Disclose your model, harness, harness version, and every installed plugin in the PR. Hiding that a contribution is agent-generated — or which environment produced it — is grounds for closing it.
|
||||||
|
6. **Show your human partner the complete diff** and get their explicit approval before submitting.
|
||||||
|
|
||||||
If any of these checks fail, do not open the PR. Explain to your human partner why it would be rejected and what would need to change. They will thank you for saving them the embarrassment.
|
If any of these checks fail, do not open the PR. Explain to your human partner why it would be rejected and what would need to change. They will thank you for saving them the embarrassment.
|
||||||
|
|
||||||
@@ -26,6 +27,10 @@ If any of these checks fail, do not open the PR. Explain to your human partner w
|
|||||||
|
|
||||||
**PRs that show no evidence of human involvement will be closed.** A human must review the complete proposed diff before submission.
|
**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
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -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), [Antigravity](#antigravity), [Codex App](#codex-app), [Codex CLI](#codex-cli), [Cursor](#cursor), [Factory Droid](#factory-droid), [Gemini CLI](#gemini-cli), [GitHub Copilot CLI](#github-copilot-cli), [Kimi Code](#kimi-code), [OpenCode](#opencode), [Pi](#pi).
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
@@ -149,6 +149,26 @@ 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
|
||||||
|
|||||||
88
docs/README.kimi.md
Normal file
88
docs/README.kimi.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Superpowers for Kimi Code
|
||||||
|
|
||||||
|
Complete guide for using Superpowers with [Kimi Code](https://github.com/MoonshotAI/kimi-code).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Superpowers is available in Kimi Code's plugin marketplace.
|
||||||
|
|
||||||
|
Open the plugin manager:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
Go to `Marketplace` > `Superpowers` and install it.
|
||||||
|
|
||||||
|
You can also install from this repository:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/plugins install https://github.com/obra/superpowers
|
||||||
|
```
|
||||||
|
|
||||||
|
For unreleased validation against `dev`, pin the branch explicitly:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/plugins install https://github.com/obra/superpowers/tree/dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Kimi Code applies plugin changes to new sessions. After installing, updating, enabling, disabling, or reloading a plugin, start a fresh session with `/new`.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The Kimi plugin manifest lives at `.kimi-plugin/plugin.json`.
|
||||||
|
|
||||||
|
The manifest does three things:
|
||||||
|
|
||||||
|
1. Points Kimi Code at the existing `skills/` directory.
|
||||||
|
2. Loads `using-superpowers` at session start through `sessionStart.skill`.
|
||||||
|
3. Provides Kimi-specific tool mapping through `skillInstructions`.
|
||||||
|
|
||||||
|
Kimi Code reads Superpowers skills from this repository. There are no copied skills, symlinks, hooks, or extra runtime dependencies.
|
||||||
|
|
||||||
|
## Tool Mapping
|
||||||
|
|
||||||
|
Skills describe actions instead of hard-coding one runtime's tool names. On Kimi Code these resolve to:
|
||||||
|
|
||||||
|
- "Ask the user" / "ask clarifying questions" -> `AskUserQuestion`
|
||||||
|
- "Create a todo" / "mark complete in todo list" -> `TodoList`
|
||||||
|
- "Dispatch a subagent" -> `Agent`
|
||||||
|
- "Invoke a skill" -> Kimi Code's native `Skill` tool
|
||||||
|
- "Read a file" / "write a file" / "edit a file" -> `Read`, `Write`, `Edit`
|
||||||
|
- "Run a shell command" -> `Bash`
|
||||||
|
- "Search file contents" -> `Grep`
|
||||||
|
- "Find files by path or pattern" -> `Glob`
|
||||||
|
- "Fetch a URL" -> `FetchURL`
|
||||||
|
- "Search the web" -> `WebSearch`
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
Use Kimi Code's plugin manager:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
Select Superpowers and update it from there. Start a fresh session with `/new` after updating.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Plugin not loading
|
||||||
|
|
||||||
|
1. Run `/plugins info superpowers` and check diagnostics.
|
||||||
|
2. Make sure the plugin is enabled.
|
||||||
|
3. Start a fresh session with `/new` after install or update.
|
||||||
|
|
||||||
|
### Direct GitHub install used an old release
|
||||||
|
|
||||||
|
Kimi Code installs the latest GitHub release for a bare repository URL when one exists. To test unreleased changes before the next Superpowers release, install the branch explicitly:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/plugins install https://github.com/obra/superpowers/tree/dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skills not triggering
|
||||||
|
|
||||||
|
1. Confirm `/plugins info superpowers` shows the plugin enabled.
|
||||||
|
2. Start a fresh session with `/new`.
|
||||||
|
3. Try the acceptance prompt: `Let's make a react todo list`. A working install should load `brainstorming` before writing code.
|
||||||
@@ -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, 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, Kimi Code, OpenCode | Users install from a git URL (`gemini extensions install …`; Kimi Code `/plugins install …`; an `opencode.json` `plugin` array entry). Document the exact command. |
|
||||||
| Package-manifest fields | pi | Declared through fields in the repo-root `package.json`; users install via the harness's package command. |
|
| 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). |
|
||||||
|
|
||||||
@@ -788,6 +788,7 @@ 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 |
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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).
|
||||||
|
|||||||
2
evals
2
evals
Submodule evals updated: e2b37138c8...ff3ee83f94
211
scripts/lint-shell.sh
Executable file
211
scripts/lint-shell.sh
Executable file
@@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Lint shell scripts in this repository.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/lint-shell.sh [--all] [--format] [--strict] [file ...]
|
||||||
|
#
|
||||||
|
# By default, runs ShellCheck and shell syntax checks on changed shell scripts.
|
||||||
|
# Use --format to format with shfmt before linting. Use --all for the full tracked
|
||||||
|
# baseline, or pass files explicitly to lint a smaller set.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "error: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_tool() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || die "required tool '$1' is not on PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_shell_file() {
|
||||||
|
local path="$1"
|
||||||
|
local first_line=""
|
||||||
|
|
||||||
|
[[ -f "$path" ]] || return 1
|
||||||
|
|
||||||
|
case "$path" in
|
||||||
|
*.sh)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
IFS= read -r first_line <"$path" || true
|
||||||
|
[[ "$first_line" =~ ^#!.*[/[:space:]](bash|dash|ksh|sh)([[:space:]]|$) ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_git_work_tree() {
|
||||||
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
||||||
|
|| die "run this from inside a git work tree, or pass files explicitly"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_shell_file() {
|
||||||
|
local path
|
||||||
|
local existing
|
||||||
|
|
||||||
|
path="$1"
|
||||||
|
if ! is_shell_file "$path"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${#files[@]}" -gt 0 ]]; then
|
||||||
|
for existing in "${files[@]}"; do
|
||||||
|
if [[ "$existing" == "$path" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
files+=("$path")
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_all_shell_files() {
|
||||||
|
local path
|
||||||
|
|
||||||
|
ensure_git_work_tree
|
||||||
|
|
||||||
|
while IFS= read -r -d '' path; do
|
||||||
|
add_shell_file "$path"
|
||||||
|
done < <(git ls-files -z)
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_changed_shell_files() {
|
||||||
|
local path
|
||||||
|
|
||||||
|
ensure_git_work_tree
|
||||||
|
|
||||||
|
if git rev-parse --verify HEAD >/dev/null 2>&1; then
|
||||||
|
while IFS= read -r -d '' path; do
|
||||||
|
add_shell_file "$path"
|
||||||
|
done < <(git diff --name-only -z --diff-filter=ACMR HEAD)
|
||||||
|
|
||||||
|
while IFS= read -r -d '' path; do
|
||||||
|
add_shell_file "$path"
|
||||||
|
done < <(git diff --cached --name-only -z --diff-filter=ACMR)
|
||||||
|
else
|
||||||
|
collect_all_shell_files
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r -d '' path; do
|
||||||
|
add_shell_file "$path"
|
||||||
|
done < <(git ls-files --others --exclude-standard -z)
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_requested_shell_files() {
|
||||||
|
local path
|
||||||
|
|
||||||
|
for path in "$@"; do
|
||||||
|
add_shell_file "$path"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
syntax_shell_for() {
|
||||||
|
local path="$1"
|
||||||
|
local first_line=""
|
||||||
|
|
||||||
|
IFS= read -r first_line <"$path" || true
|
||||||
|
|
||||||
|
case "$first_line" in
|
||||||
|
*"/sh"* | *" env sh"* | *"/dash"* | *" env dash"*)
|
||||||
|
printf 'sh'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'bash'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
run_syntax_checks() {
|
||||||
|
local file
|
||||||
|
local shell_name
|
||||||
|
|
||||||
|
for file in "$@"; do
|
||||||
|
shell_name="$(syntax_shell_for "$file")"
|
||||||
|
case "$shell_name" in
|
||||||
|
sh)
|
||||||
|
sh -n "$file"
|
||||||
|
;;
|
||||||
|
bash)
|
||||||
|
bash -n "$file"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unsupported shell for syntax check: $shell_name"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
format=false
|
||||||
|
strict=false
|
||||||
|
all=false
|
||||||
|
requested_files=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--all)
|
||||||
|
all=true
|
||||||
|
;;
|
||||||
|
--format)
|
||||||
|
format=true
|
||||||
|
;;
|
||||||
|
--strict)
|
||||||
|
strict=true
|
||||||
|
;;
|
||||||
|
-h | --help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
requested_files+=("$@")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
requested_files+=("$1")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
require_tool shellcheck
|
||||||
|
if [[ "$format" == true ]]; then
|
||||||
|
require_tool shfmt
|
||||||
|
fi
|
||||||
|
|
||||||
|
files=()
|
||||||
|
if [[ "${#requested_files[@]}" -gt 0 ]]; then
|
||||||
|
collect_requested_shell_files "${requested_files[@]}"
|
||||||
|
elif [[ "$all" == true ]]; then
|
||||||
|
collect_all_shell_files
|
||||||
|
else
|
||||||
|
collect_changed_shell_files
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${#files[@]}" -eq 0 ]]; then
|
||||||
|
echo "No shell files found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$format" == true ]]; then
|
||||||
|
echo "Formatting ${#files[@]} shell files"
|
||||||
|
shfmt_args=(-i 2 -ci -bn)
|
||||||
|
shfmt "${shfmt_args[@]}" -w "${files[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Linting ${#files[@]} shell files"
|
||||||
|
|
||||||
|
shellcheck_args=(--severity=warning --external-sources --source-path=SCRIPTDIR)
|
||||||
|
if [[ "$strict" == true ]]; then
|
||||||
|
shellcheck_args+=("--enable=check-extra-masked-returns,check-set-e-suppressed,quote-safe-variables,deprecate-which,avoid-nullary-conditions")
|
||||||
|
fi
|
||||||
|
|
||||||
|
shellcheck "${shellcheck_args[@]}" "${files[@]}"
|
||||||
|
run_syntax_checks "${files[@]}"
|
||||||
@@ -52,6 +52,7 @@ EXCLUDES=(
|
|||||||
"/.gitattributes"
|
"/.gitattributes"
|
||||||
"/.github/"
|
"/.github/"
|
||||||
"/.gitignore"
|
"/.gitignore"
|
||||||
|
"/.kimi-plugin/"
|
||||||
"/.opencode/"
|
"/.opencode/"
|
||||||
"/.pi/"
|
"/.pi/"
|
||||||
"/.version-bump.json"
|
"/.version-bump.json"
|
||||||
|
|||||||
@@ -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
|
6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit (exactly this path — not `docs/specs/`)
|
||||||
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,6 +109,7 @@ 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
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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');
|
||||||
@@ -53,10 +54,18 @@ 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;
|
||||||
payloadLen = Number(buffer.readBigUInt64BE(2));
|
const extendedLen = buffer.readBigUInt64BE(2);
|
||||||
|
if (extendedLen > BigInt(MAX_FRAME_PAYLOAD_BYTES)) {
|
||||||
|
throw new Error('WebSocket frame payload exceeds maximum allowed size');
|
||||||
|
}
|
||||||
|
payloadLen = Number(extendedLen);
|
||||||
offset = 10;
|
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;
|
||||||
@@ -351,4 +360,4 @@ if (require.main === module) {
|
|||||||
startServer();
|
startServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES };
|
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES, MAX_FRAME_PAYLOAD_BYTES };
|
||||||
|
|||||||
@@ -103,6 +103,9 @@ 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
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ 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 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.
|
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.
|
||||||
|
|
||||||
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.
|
||||||
@@ -53,6 +55,8 @@ 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]
|
||||||
|
|||||||
@@ -329,6 +329,21 @@ 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 ---');
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ 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" \
|
||||||
@@ -210,6 +211,13 @@ 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'
|
||||||
@@ -267,6 +275,7 @@ 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 \
|
||||||
@@ -415,10 +424,15 @@ EOF
|
|||||||
write_stale_ignored_destination_fixture() {
|
write_stale_ignored_destination_fixture() {
|
||||||
local repo="$1"
|
local repo="$1"
|
||||||
|
|
||||||
mkdir -p "$repo/plugins/superpowers/.private-journal"
|
mkdir -p \
|
||||||
|
"$repo/plugins/superpowers/.kimi-plugin" \
|
||||||
|
"$repo/plugins/superpowers/.private-journal"
|
||||||
printf 'fixture keep\n' > "$repo/plugins/superpowers/.fixture-keep"
|
printf '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 plugins/superpowers/.fixture-keep
|
git -C "$repo" add \
|
||||||
|
plugins/superpowers/.fixture-keep \
|
||||||
|
plugins/superpowers/.kimi-plugin/plugin.json
|
||||||
|
|
||||||
commit_fixture "$repo" "Initial stale ignored destination fixture"
|
commit_fixture "$repo" "Initial stale ignored destination fixture"
|
||||||
}
|
}
|
||||||
@@ -618,6 +632,7 @@ main() {
|
|||||||
assert_contains "$preview_output" "Version: $MANIFEST_VERSION" "Preview uses manifest version"
|
assert_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"
|
||||||
@@ -644,6 +659,7 @@ 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 ""
|
||||||
|
|||||||
6
tests/kimi/run-tests.sh
Executable file
6
tests/kimi/run-tests.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
bash "$SCRIPT_DIR/test-plugin-manifest.sh"
|
||||||
86
tests/kimi/test-plugin-manifest.sh
Executable file
86
tests/kimi/test-plugin-manifest.sh
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
MANIFEST="$REPO_ROOT/.kimi-plugin/plugin.json"
|
||||||
|
|
||||||
|
python3 - "$MANIFEST" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
manifest_path = Path(sys.argv[1])
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
def assert_equal(actual, expected, label):
|
||||||
|
if actual != expected:
|
||||||
|
raise AssertionError(f"{label}: expected {expected!r}, got {actual!r}")
|
||||||
|
|
||||||
|
def assert_present(text, needle, label):
|
||||||
|
if needle not in text:
|
||||||
|
raise AssertionError(f"{label}: missing {needle!r}")
|
||||||
|
|
||||||
|
assert_equal(manifest.get("name"), "superpowers", "plugin name")
|
||||||
|
assert_equal(manifest.get("skills"), "./skills/", "skills path")
|
||||||
|
assert_equal(
|
||||||
|
manifest.get("sessionStart", {}).get("skill"),
|
||||||
|
"using-superpowers",
|
||||||
|
"sessionStart.skill",
|
||||||
|
)
|
||||||
|
|
||||||
|
instructions = manifest.get("skillInstructions")
|
||||||
|
if not isinstance(instructions, str) or not instructions.strip():
|
||||||
|
raise AssertionError("skillInstructions must be a non-empty string")
|
||||||
|
|
||||||
|
for token in [
|
||||||
|
"AskUserQuestion",
|
||||||
|
"TodoList",
|
||||||
|
"Agent",
|
||||||
|
"Skill",
|
||||||
|
"Read",
|
||||||
|
"Write",
|
||||||
|
"Edit",
|
||||||
|
"Bash",
|
||||||
|
"Grep",
|
||||||
|
"Glob",
|
||||||
|
"FetchURL",
|
||||||
|
"WebSearch",
|
||||||
|
]:
|
||||||
|
assert_present(instructions, token, "skillInstructions")
|
||||||
|
|
||||||
|
version_config = json.loads(
|
||||||
|
(manifest_path.parents[1] / ".version-bump.json").read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
version_entries = version_config.get("files")
|
||||||
|
if not isinstance(version_entries, list):
|
||||||
|
raise AssertionError(".version-bump.json must contain files list")
|
||||||
|
|
||||||
|
if not any(
|
||||||
|
entry.get("path") == ".kimi-plugin/plugin.json" and entry.get("field") == "version"
|
||||||
|
for entry in version_entries
|
||||||
|
if isinstance(entry, dict)
|
||||||
|
):
|
||||||
|
raise AssertionError(
|
||||||
|
".version-bump.json must update .kimi-plugin/plugin.json version"
|
||||||
|
)
|
||||||
|
|
||||||
|
unsupported_fields = [
|
||||||
|
"tools",
|
||||||
|
"commands",
|
||||||
|
"hooks",
|
||||||
|
"apps",
|
||||||
|
"inject",
|
||||||
|
"configFile",
|
||||||
|
"config_file",
|
||||||
|
"bootstrap",
|
||||||
|
]
|
||||||
|
present_unsupported = sorted(field for field in unsupported_fields if field in manifest)
|
||||||
|
if present_unsupported:
|
||||||
|
raise AssertionError(
|
||||||
|
"unsupported Kimi runtime fields present: "
|
||||||
|
+ ", ".join(present_unsupported)
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Kimi plugin manifest looks good")
|
||||||
|
PY
|
||||||
179
tests/shell-lint/test-lint-shell.sh
Normal file
179
tests/shell-lint/test-lint-shell.sh
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
SCRIPT_UNDER_TEST="$REPO_ROOT/scripts/lint-shell.sh"
|
||||||
|
|
||||||
|
FAILURES=0
|
||||||
|
TEST_ROOT="$(mktemp -d)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -rf "$TEST_ROOT"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
pass() {
|
||||||
|
echo " [PASS] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo " [FAIL] $1"
|
||||||
|
FAILURES=$((FAILURES + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_contains() {
|
||||||
|
local haystack="$1"
|
||||||
|
local needle="$2"
|
||||||
|
local description="$3"
|
||||||
|
|
||||||
|
if printf '%s' "$haystack" | grep -Fq -- "$needle"; then
|
||||||
|
pass "$description"
|
||||||
|
else
|
||||||
|
fail "$description"
|
||||||
|
echo " expected to find: $needle"
|
||||||
|
echo " in:"
|
||||||
|
printf '%s\n' "$haystack" | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_not_contains() {
|
||||||
|
local haystack="$1"
|
||||||
|
local needle="$2"
|
||||||
|
local description="$3"
|
||||||
|
|
||||||
|
if printf '%s' "$haystack" | grep -Fq -- "$needle"; then
|
||||||
|
fail "$description"
|
||||||
|
echo " did not expect to find: $needle"
|
||||||
|
echo " in:"
|
||||||
|
printf '%s\n' "$haystack" | sed 's/^/ /'
|
||||||
|
else
|
||||||
|
pass "$description"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
configure_git_identity() {
|
||||||
|
local repo="$1"
|
||||||
|
|
||||||
|
git -C "$repo" config user.name "Test Bot"
|
||||||
|
git -C "$repo" config user.email "test@example.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_stub_tool() {
|
||||||
|
local path="$1"
|
||||||
|
local name="$2"
|
||||||
|
|
||||||
|
cat >"$path" <<EOF
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
{
|
||||||
|
printf '${name}:'
|
||||||
|
for arg in "\$@"; do
|
||||||
|
printf ' <%s>' "\$arg"
|
||||||
|
done
|
||||||
|
printf '\n'
|
||||||
|
} >> "\$SUPERPOWERS_SHELL_LINT_TEST_LOG"
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod +x "$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
make_fixture_repo() {
|
||||||
|
local repo="$1"
|
||||||
|
|
||||||
|
git init -q -b main "$repo"
|
||||||
|
configure_git_identity "$repo"
|
||||||
|
|
||||||
|
mkdir -p "$repo/hooks"
|
||||||
|
cat >"$repo/tracked.sh" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
echo "tracked"
|
||||||
|
EOF
|
||||||
|
cat >"$repo/hooks/session-start" <<'EOF'
|
||||||
|
#!/bin/sh
|
||||||
|
echo "extensionless"
|
||||||
|
EOF
|
||||||
|
cat >"$repo/README.md" <<'EOF'
|
||||||
|
# Fixture
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "not a shell script"
|
||||||
|
```
|
||||||
|
EOF
|
||||||
|
cat >"$repo/untracked.sh" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
echo "untracked"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git -C "$repo" add tracked.sh hooks/session-start README.md
|
||||||
|
git -C "$repo" commit -q -m "fixture"
|
||||||
|
|
||||||
|
printf '\necho "changed"\n' >>"$repo/tracked.sh"
|
||||||
|
printf '\necho "changed extensionless"\n' >>"$repo/hooks/session-start"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_lint_shell() {
|
||||||
|
local repo="$1"
|
||||||
|
local fakebin="$2"
|
||||||
|
local log="$3"
|
||||||
|
shift 3
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "$repo"
|
||||||
|
PATH="$fakebin:$PATH" \
|
||||||
|
SUPERPOWERS_SHELL_LINT_TEST_LOG="$log" \
|
||||||
|
bash "$SCRIPT_UNDER_TEST" "$@"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Shell lint script tests"
|
||||||
|
|
||||||
|
fixture="$TEST_ROOT/repo"
|
||||||
|
fakebin="$TEST_ROOT/bin"
|
||||||
|
log="$TEST_ROOT/tool.log"
|
||||||
|
mkdir -p "$fixture" "$fakebin"
|
||||||
|
: >"$log"
|
||||||
|
write_stub_tool "$fakebin/shellcheck" "shellcheck"
|
||||||
|
write_stub_tool "$fakebin/shfmt" "shfmt"
|
||||||
|
make_fixture_repo "$fixture"
|
||||||
|
|
||||||
|
if output="$(run_lint_shell "$fixture" "$fakebin" "$log" 2>&1)"; then
|
||||||
|
pass "lint-shell check mode exits successfully with stub tools"
|
||||||
|
else
|
||||||
|
fail "lint-shell check mode exits successfully with stub tools"
|
||||||
|
printf '%s\n' "$output" | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
|
||||||
|
tool_log="$(cat "$log")"
|
||||||
|
assert_contains "$output" "Linting 3 shell files" "reports changed shell file count"
|
||||||
|
assert_not_contains "$tool_log" "shfmt:" "does not run shfmt in lint mode"
|
||||||
|
assert_contains "$tool_log" "shellcheck:" "runs ShellCheck"
|
||||||
|
assert_contains "$tool_log" "<--severity=warning>" "uses warning severity as the baseline"
|
||||||
|
assert_contains "$tool_log" "<--external-sources>" "allows ShellCheck to follow sourced files"
|
||||||
|
assert_contains "$tool_log" "<--source-path=SCRIPTDIR>" "resolves ShellCheck sources relative to each script"
|
||||||
|
assert_contains "$tool_log" "<hooks/session-start>" "includes changed extensionless shell shebang file"
|
||||||
|
assert_contains "$tool_log" "<tracked.sh>" "includes changed tracked .sh file"
|
||||||
|
assert_contains "$tool_log" "<untracked.sh>" "includes untracked shell files by default"
|
||||||
|
assert_not_contains "$tool_log" "README.md" "ignores Markdown with shell snippets"
|
||||||
|
|
||||||
|
: >"$log"
|
||||||
|
if output="$(run_lint_shell "$fixture" "$fakebin" "$log" --all --format 2>&1)"; then
|
||||||
|
pass "lint-shell --format exits successfully with stub tools"
|
||||||
|
else
|
||||||
|
fail "lint-shell --format exits successfully with stub tools"
|
||||||
|
printf '%s\n' "$output" | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
|
||||||
|
tool_log="$(cat "$log")"
|
||||||
|
assert_contains "$tool_log" "<-w>" "uses shfmt write mode with --format"
|
||||||
|
assert_contains "$tool_log" "shellcheck:" "runs ShellCheck after --format"
|
||||||
|
assert_contains "$tool_log" "<--severity=warning>" "keeps warning severity after --format"
|
||||||
|
assert_contains "$tool_log" "<hooks/session-start>" "--all includes tracked extensionless shell shebang file"
|
||||||
|
assert_contains "$tool_log" "<tracked.sh>" "--all includes tracked .sh file"
|
||||||
|
assert_not_contains "$tool_log" "untracked.sh" "--all ignores untracked shell files"
|
||||||
|
|
||||||
|
if [[ "$FAILURES" -eq 0 ]]; then
|
||||||
|
echo "All shell lint script tests passed"
|
||||||
|
else
|
||||||
|
echo "$FAILURES shell lint script test(s) failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user