mirror of
https://github.com/obra/superpowers.git
synced 2026-05-01 06:29:05 +08:00
Compare commits
10 Commits
codex/pri-
...
codex/open
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc6082d7ff | ||
|
|
772332a476 | ||
|
|
e795530c23 | ||
|
|
28fd7a8192 | ||
|
|
831f6f977c | ||
|
|
5745f0ea99 | ||
|
|
b1c15fd9f8 | ||
|
|
abb801b7ef | ||
|
|
88eb6679ae | ||
|
|
9b3045a8fa |
@@ -36,6 +36,9 @@
|
|||||||
"I've got an idea for something I'd like to build.",
|
"I've got an idea for something I'd like to build.",
|
||||||
"Let's add a feature to this project."
|
"Let's add a feature to this project."
|
||||||
],
|
],
|
||||||
|
"websiteURL": "https://github.com/obra/superpowers",
|
||||||
|
"privacyPolicyURL": "https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement",
|
||||||
|
"termsOfServiceURL": "https://docs.github.com/en/site-policy/github-terms/github-terms-of-service",
|
||||||
"brandColor": "#F59E0B",
|
"brandColor": "#F59E0B",
|
||||||
"composerIcon": "./assets/superpowers-small.svg",
|
"composerIcon": "./assets/superpowers-small.svg",
|
||||||
"logo": "./assets/app-icon.png",
|
"logo": "./assets/app-icon.png",
|
||||||
|
|||||||
23
.openclaw/index.js
Normal file
23
.openclaw/index.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const SUPERPOWERS_GUIDANCE = `## Superpowers
|
||||||
|
|
||||||
|
You have access to the Superpowers skill framework through OpenClaw's native skill system.
|
||||||
|
Before responding to software development work, check whether one of those skills applies.
|
||||||
|
If a Superpowers skill fits the task, invoke it before proceeding.
|
||||||
|
|
||||||
|
Start with \`using-superpowers\` when you need the overall workflow. Common follow-on
|
||||||
|
skills include \`brainstorming\`, \`writing-plans\`, \`test-driven-development\`,
|
||||||
|
\`systematic-debugging\`, \`dispatching-parallel-agents\`, and
|
||||||
|
\`verification-before-completion\`.
|
||||||
|
|
||||||
|
When Superpowers instructions mention generic tools, use the closest native OpenClaw
|
||||||
|
tool or workflow.`;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
id: "superpowers-openclaw",
|
||||||
|
name: "Superpowers for OpenClaw",
|
||||||
|
description: "Expose the Superpowers skill pack through OpenClaw's native plugin skill discovery.",
|
||||||
|
|
||||||
|
register(api) {
|
||||||
|
api.on("before_prompt_build", async () => ({ prependSystemContext: SUPERPOWERS_GUIDANCE }));
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -14,10 +14,14 @@ Add superpowers to the `plugin` array in your `opencode.json` (global or project
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Restart OpenCode. That's it — the plugin auto-installs and registers all skills.
|
Restart OpenCode. The plugin installs through OpenCode's plugin manager and
|
||||||
|
registers all skills.
|
||||||
|
|
||||||
Verify by asking: "Tell me about your superpowers"
|
Verify by asking: "Tell me about your superpowers"
|
||||||
|
|
||||||
|
OpenCode uses its own plugin install. If you also use Claude Code, Codex, or
|
||||||
|
another harness, install Superpowers separately for each one.
|
||||||
|
|
||||||
## Migrating from the old symlink-based install
|
## Migrating from the old symlink-based install
|
||||||
|
|
||||||
If you previously installed superpowers using `git clone` and symlinks, remove the old setup:
|
If you previously installed superpowers using `git clone` and symlinks, remove the old setup:
|
||||||
@@ -46,7 +50,10 @@ use skill tool to load superpowers/brainstorming
|
|||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
|
||||||
Superpowers updates automatically when you restart OpenCode.
|
OpenCode installs Superpowers through a git-backed package spec. Some OpenCode
|
||||||
|
and Bun versions pin that resolved git dependency in a lockfile or cache, so a
|
||||||
|
restart may not pick up the newest Superpowers commit. If updates do not appear,
|
||||||
|
clear OpenCode's package cache or reinstall the plugin.
|
||||||
|
|
||||||
To pin a specific version:
|
To pin a specific version:
|
||||||
|
|
||||||
@@ -64,6 +71,26 @@ To pin a specific version:
|
|||||||
2. Verify the plugin line in your `opencode.json`
|
2. Verify the plugin line in your `opencode.json`
|
||||||
3. Make sure you're running a recent version of OpenCode
|
3. Make sure you're running a recent version of OpenCode
|
||||||
|
|
||||||
|
### Windows install issues
|
||||||
|
|
||||||
|
Some Windows OpenCode builds have upstream installer issues with git-backed
|
||||||
|
plugin specs, including cache paths for `git+https` URLs and Bun not finding
|
||||||
|
`git.exe` even when it works in a normal terminal. If OpenCode cannot install
|
||||||
|
the plugin, try installing with system npm and pointing OpenCode at the local
|
||||||
|
package:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install superpowers@git+https://github.com/obra/superpowers.git --prefix "$HOME\.config\opencode"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use the installed package path in `opencode.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugin": ["~/.config/opencode/node_modules/superpowers"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Skills not found
|
### Skills not found
|
||||||
|
|
||||||
1. Use `skill` tool to list what's discovered
|
1. Use `skill` tool to list what's discovered
|
||||||
|
|||||||
@@ -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": "openclaw.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" }
|
||||||
],
|
],
|
||||||
|
|||||||
176
README.md
176
README.md
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
Superpowers is a complete software development methodology for your coding agents, built on top of a set of composable skills and some initial instructions that make sure your agent uses them.
|
Superpowers is a complete software development methodology for your coding agents, built on top of a set of composable skills and some initial instructions that make sure your agent uses them.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
Give your agent Superpowers: [Claude Code](#claude-code), [Codex CLI](#codex-cli), [Codex App](#codex-app), [Factory Droid](#factory-droid), [Gemini CLI](#gemini-cli), [OpenCode](#opencode), [Cursor](#cursor), [GitHub Copilot CLI](#github-copilot-cli).
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
It starts from the moment you fire up your coding agent. As soon as it sees that you're building something, it *doesn't* just jump into trying to write code. Instead, it steps back and asks you what you're really trying to do.
|
It starts from the moment you fire up your coding agent. As soon as it sees that you're building something, it *doesn't* just jump into trying to write code. Instead, it steps back and asks you what you're really trying to do.
|
||||||
@@ -26,95 +30,153 @@ Thanks!
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
**Note:** Installation differs by platform.
|
Installation differs by harness. If you use more than one, install Superpowers separately for each one.
|
||||||
|
|
||||||
### Claude Code Official Marketplace
|
### Claude Code
|
||||||
|
|
||||||
Superpowers is available via the [official Claude plugin marketplace](https://claude.com/plugins/superpowers)
|
Superpowers is available via the [official Claude plugin marketplace](https://claude.com/plugins/superpowers)
|
||||||
|
|
||||||
Install the plugin from Anthropic's official marketplace:
|
#### Official Marketplace
|
||||||
|
|
||||||
```bash
|
- Install the plugin from Anthropic's official marketplace:
|
||||||
/plugin install superpowers@claude-plugins-official
|
|
||||||
```
|
|
||||||
|
|
||||||
### Claude Code (Superpowers Marketplace)
|
```bash
|
||||||
|
/plugin install superpowers@claude-plugins-official
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Superpowers Marketplace
|
||||||
|
|
||||||
The Superpowers marketplace provides Superpowers and some other related plugins for Claude Code.
|
The Superpowers marketplace provides Superpowers and some other related plugins for Claude Code.
|
||||||
|
|
||||||
In Claude Code, register the marketplace first:
|
- Register the marketplace:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/plugin marketplace add obra/superpowers-marketplace
|
/plugin marketplace add obra/superpowers-marketplace
|
||||||
```
|
```
|
||||||
|
|
||||||
Then install the plugin from this marketplace:
|
- Install the plugin from this marketplace:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
/plugin install superpowers@superpowers-marketplace
|
/plugin install superpowers@superpowers-marketplace
|
||||||
```
|
```
|
||||||
|
|
||||||
### OpenAI Codex CLI
|
### Codex CLI
|
||||||
|
|
||||||
- Open plugin search interface
|
Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins).
|
||||||
|
|
||||||
```bash
|
- Open the plugin search interface:
|
||||||
/plugins
|
|
||||||
```
|
|
||||||
|
|
||||||
Search for Superpowers
|
```bash
|
||||||
|
/plugins
|
||||||
|
```
|
||||||
|
|
||||||
```bash
|
- Search for Superpowers:
|
||||||
superpowers
|
|
||||||
```
|
|
||||||
|
|
||||||
Select `Install Plugin`
|
```bash
|
||||||
|
superpowers
|
||||||
|
```
|
||||||
|
|
||||||
### OpenAI Codex App
|
- Select `Install Plugin`.
|
||||||
|
|
||||||
|
### Codex App
|
||||||
|
|
||||||
|
Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins).
|
||||||
|
|
||||||
- In the Codex app, click on Plugins in the sidebar.
|
- In the Codex app, click on Plugins in the sidebar.
|
||||||
- You should see `Superpowers` in the Coding section.
|
- You should see `Superpowers` in the Coding section.
|
||||||
- Click the `+` next to Superpowers and follow the prompts.
|
- Click the `+` next to Superpowers and follow the prompts.
|
||||||
|
|
||||||
|
### Factory Droid
|
||||||
|
|
||||||
### Cursor (via Plugin Marketplace)
|
- Register the marketplace:
|
||||||
|
|
||||||
In Cursor Agent chat, install from marketplace:
|
```bash
|
||||||
|
droid plugin marketplace add https://github.com/obra/superpowers
|
||||||
|
```
|
||||||
|
|
||||||
```text
|
- Install the plugin:
|
||||||
/add-plugin superpowers
|
|
||||||
```
|
|
||||||
|
|
||||||
or search for "superpowers" in the plugin marketplace.
|
```bash
|
||||||
|
droid plugin install superpowers@superpowers
|
||||||
### OpenCode
|
```
|
||||||
|
|
||||||
Tell OpenCode:
|
|
||||||
|
|
||||||
```
|
|
||||||
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.opencode/INSTALL.md
|
|
||||||
```
|
|
||||||
|
|
||||||
**Detailed docs:** [docs/README.opencode.md](docs/README.opencode.md)
|
|
||||||
|
|
||||||
### GitHub Copilot CLI
|
|
||||||
|
|
||||||
```bash
|
|
||||||
copilot plugin marketplace add obra/superpowers-marketplace
|
|
||||||
copilot plugin install superpowers@superpowers-marketplace
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gemini CLI
|
### Gemini CLI
|
||||||
|
|
||||||
```bash
|
- Install the extension:
|
||||||
gemini extensions install https://github.com/obra/superpowers
|
|
||||||
```
|
|
||||||
|
|
||||||
To update:
|
```bash
|
||||||
|
gemini extensions install https://github.com/obra/superpowers
|
||||||
|
```
|
||||||
|
|
||||||
```bash
|
- Update later:
|
||||||
gemini extensions update superpowers
|
|
||||||
```
|
```bash
|
||||||
|
gemini extensions update superpowers
|
||||||
|
```
|
||||||
|
|
||||||
|
### OpenCode
|
||||||
|
|
||||||
|
OpenCode uses its own plugin install; install Superpowers separately even if you
|
||||||
|
already use it in another harness.
|
||||||
|
|
||||||
|
- Tell OpenCode:
|
||||||
|
|
||||||
|
```
|
||||||
|
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.opencode/INSTALL.md
|
||||||
|
```
|
||||||
|
|
||||||
|
- Detailed docs: [docs/README.opencode.md](docs/README.opencode.md)
|
||||||
|
|
||||||
|
### OpenClaw
|
||||||
|
|
||||||
|
OpenClaw uses its native plugin system. Install Superpowers separately even if
|
||||||
|
you already use it in another harness.
|
||||||
|
|
||||||
|
- Clone and link the plugin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/obra/superpowers.git ~/.openclaw/vendor/superpowers
|
||||||
|
openclaw plugins install --link ~/.openclaw/vendor/superpowers --dangerously-force-unsafe-install
|
||||||
|
openclaw plugins enable superpowers-openclaw
|
||||||
|
openclaw gateway restart
|
||||||
|
```
|
||||||
|
|
||||||
|
The override is required because OpenClaw scans the linked plugin directory
|
||||||
|
during native plugin install. It flags `skills/writing-skills/render-graphs.js`,
|
||||||
|
an existing Superpowers skill-authoring helper, because that script shells out
|
||||||
|
to Graphviz. The helper is not part of the OpenClaw runtime hook; review the
|
||||||
|
source before using the override.
|
||||||
|
|
||||||
|
- Verify the plugin and skills are available:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw plugins info superpowers-openclaw --json
|
||||||
|
openclaw skills info using-superpowers --json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
- In Cursor Agent chat, install from marketplace:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/add-plugin superpowers
|
||||||
|
```
|
||||||
|
|
||||||
|
- Or search for "superpowers" in the plugin marketplace.
|
||||||
|
|
||||||
|
### GitHub Copilot CLI
|
||||||
|
|
||||||
|
- Register the marketplace:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin marketplace add obra/superpowers-marketplace
|
||||||
|
```
|
||||||
|
|
||||||
|
- Install the plugin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
copilot plugin install superpowers@superpowers-marketplace
|
||||||
|
```
|
||||||
|
|
||||||
## The Basic Workflow
|
## The Basic Workflow
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,14 @@ Add superpowers to the `plugin` array in your `opencode.json` (global or project
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Restart OpenCode. The plugin auto-installs via Bun and registers all skills automatically.
|
Restart OpenCode. The plugin installs through OpenCode's plugin manager and
|
||||||
|
registers all skills.
|
||||||
|
|
||||||
Verify by asking: "Tell me about your superpowers"
|
Verify by asking: "Tell me about your superpowers"
|
||||||
|
|
||||||
|
OpenCode uses its own plugin install. If you also use Claude Code, Codex, or
|
||||||
|
another harness, install Superpowers separately for each one.
|
||||||
|
|
||||||
### Migrating from the old symlink-based install
|
### Migrating from the old symlink-based install
|
||||||
|
|
||||||
If you previously installed superpowers using `git clone` and symlinks, remove the old setup:
|
If you previously installed superpowers using `git clone` and symlinks, remove the old setup:
|
||||||
@@ -78,7 +82,10 @@ Create project-specific skills in `.opencode/skills/` within your project.
|
|||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
|
||||||
Superpowers updates automatically when you restart OpenCode. The plugin is re-installed from the git repository on each launch.
|
OpenCode installs Superpowers through a git-backed package spec. Some OpenCode
|
||||||
|
and Bun versions pin that resolved git dependency in a lockfile or cache, so a
|
||||||
|
restart may not pick up the newest Superpowers commit. If updates do not appear,
|
||||||
|
clear OpenCode's package cache or reinstall the plugin.
|
||||||
|
|
||||||
To pin a specific version, use a branch or tag:
|
To pin a specific version, use a branch or tag:
|
||||||
|
|
||||||
@@ -112,6 +119,26 @@ Skills written for Claude Code are automatically adapted for OpenCode:
|
|||||||
2. Verify the plugin line in your `opencode.json` is correct
|
2. Verify the plugin line in your `opencode.json` is correct
|
||||||
3. Make sure you're running a recent version of OpenCode
|
3. Make sure you're running a recent version of OpenCode
|
||||||
|
|
||||||
|
### Windows install issues
|
||||||
|
|
||||||
|
Some Windows OpenCode builds have upstream installer issues with git-backed
|
||||||
|
plugin specs, including cache paths for `git+https` URLs and Bun not finding
|
||||||
|
`git.exe` even when it works in a normal terminal. If OpenCode cannot install
|
||||||
|
the plugin, try installing with system npm and pointing OpenCode at the local
|
||||||
|
package:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install superpowers@git+https://github.com/obra/superpowers.git --prefix "$HOME\.config\opencode"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use the installed package path in `opencode.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugin": ["~/.config/opencode/node_modules/superpowers"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Skills not found
|
### Skills not found
|
||||||
|
|
||||||
1. Use OpenCode's `skill` tool to list available skills
|
1. Use OpenCode's `skill` tool to list available skills
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"sessionStart": [
|
"sessionStart": [
|
||||||
{
|
{
|
||||||
"command": "./hooks/session-start"
|
"command": "./hooks/run-hook.cmd session-start"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
13
openclaw.plugin.json
Normal file
13
openclaw.plugin.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"id": "superpowers-openclaw",
|
||||||
|
"name": "Superpowers for OpenClaw",
|
||||||
|
"version": "5.0.7",
|
||||||
|
"description": "Expose the Superpowers skill pack through OpenClaw's native plugin skill discovery.",
|
||||||
|
"skills": [
|
||||||
|
"./skills"
|
||||||
|
],
|
||||||
|
"configSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,5 +2,10 @@
|
|||||||
"name": "superpowers",
|
"name": "superpowers",
|
||||||
"version": "5.0.7",
|
"version": "5.0.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": ".opencode/plugins/superpowers.js"
|
"main": ".opencode/plugins/superpowers.js",
|
||||||
|
"openclaw": {
|
||||||
|
"extensions": [
|
||||||
|
"./.openclaw/index.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
#
|
#
|
||||||
# Sync this superpowers checkout → prime-radiant-inc/openai-codex-plugins.
|
# Sync this superpowers checkout → prime-radiant-inc/openai-codex-plugins.
|
||||||
# Clones the fork fresh into a temp dir, rsyncs tracked upstream plugin content
|
# Clones the fork fresh into a temp dir, rsyncs tracked upstream plugin content
|
||||||
# (including committed Codex files under .codex-plugin/ and assets/), commits,
|
# (including committed Codex files under .codex-plugin/ and assets/), preserves
|
||||||
|
# OpenAI-owned marketplace metadata already in the destination plugin, commits,
|
||||||
# pushes a sync branch, and opens a PR.
|
# pushes a sync branch, and opens a PR.
|
||||||
# Path/user agnostic — auto-detects upstream from script location.
|
# Path/user agnostic — auto-detects upstream from script location.
|
||||||
#
|
#
|
||||||
@@ -51,6 +52,7 @@ EXCLUDES=(
|
|||||||
"/.gitattributes"
|
"/.gitattributes"
|
||||||
"/.github/"
|
"/.github/"
|
||||||
"/.gitignore"
|
"/.gitignore"
|
||||||
|
"/.openclaw/"
|
||||||
"/.opencode/"
|
"/.opencode/"
|
||||||
"/.version-bump.json"
|
"/.version-bump.json"
|
||||||
"/.worktrees/"
|
"/.worktrees/"
|
||||||
@@ -63,6 +65,7 @@ EXCLUDES=(
|
|||||||
"/GEMINI.md"
|
"/GEMINI.md"
|
||||||
"/RELEASE-NOTES.md"
|
"/RELEASE-NOTES.md"
|
||||||
"/gemini-extension.json"
|
"/gemini-extension.json"
|
||||||
|
"/openclaw.plugin.json"
|
||||||
"/package.json"
|
"/package.json"
|
||||||
|
|
||||||
# Directories not shipped by canonical Codex plugins
|
# Directories not shipped by canonical Codex plugins
|
||||||
@@ -223,6 +226,7 @@ fi
|
|||||||
DEST="$DEST_REPO/$DEST_REL"
|
DEST="$DEST_REPO/$DEST_REL"
|
||||||
PREVIEW_REPO="$DEST_REPO"
|
PREVIEW_REPO="$DEST_REPO"
|
||||||
PREVIEW_DEST="$DEST"
|
PREVIEW_DEST="$DEST"
|
||||||
|
SYNC_SOURCE=""
|
||||||
|
|
||||||
overlay_destination_paths() {
|
overlay_destination_paths() {
|
||||||
local repo="$1"
|
local repo="$1"
|
||||||
@@ -291,7 +295,7 @@ apply_to_preview_checkout() {
|
|||||||
mkdir -p "$PREVIEW_DEST"
|
mkdir -p "$PREVIEW_DEST"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$PREVIEW_DEST/"
|
rsync "${RSYNC_ARGS[@]}" "$SYNC_SOURCE/" "$PREVIEW_DEST/"
|
||||||
}
|
}
|
||||||
|
|
||||||
preview_checkout_has_changes() {
|
preview_checkout_has_changes() {
|
||||||
@@ -316,6 +320,36 @@ for pat in "${EXCLUDES[@]}"; do RSYNC_ARGS+=(--exclude="$pat"); done
|
|||||||
append_git_ignored_directory_excludes
|
append_git_ignored_directory_excludes
|
||||||
append_git_ignored_file_excludes
|
append_git_ignored_file_excludes
|
||||||
|
|
||||||
|
copy_preserved_destination_metadata() {
|
||||||
|
local destination="$1"
|
||||||
|
local source="$2"
|
||||||
|
local path
|
||||||
|
local rel
|
||||||
|
|
||||||
|
[[ -d "$destination/skills" ]] || return 0
|
||||||
|
|
||||||
|
while IFS= read -r -d '' path; do
|
||||||
|
rel="${path#"$destination"/}"
|
||||||
|
mkdir -p "$source/$(dirname "$rel")"
|
||||||
|
cp -p "$path" "$source/$rel"
|
||||||
|
done < <(find "$destination/skills" -path '*/agents/openai.yaml' -type f -print0)
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_sync_source() {
|
||||||
|
local destination="$1"
|
||||||
|
|
||||||
|
[[ -n "$CLEANUP_DIR" ]] || CLEANUP_DIR="$(mktemp -d)"
|
||||||
|
|
||||||
|
SYNC_SOURCE="$CLEANUP_DIR/source-overlay"
|
||||||
|
rm -rf "$SYNC_SOURCE"
|
||||||
|
mkdir -p "$SYNC_SOURCE"
|
||||||
|
|
||||||
|
rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$SYNC_SOURCE/" >/dev/null
|
||||||
|
copy_preserved_destination_metadata "$destination" "$SYNC_SOURCE"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_sync_source "$PREVIEW_DEST"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Dry run preview (always shown)
|
# Dry run preview (always shown)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -331,7 +365,7 @@ if [[ $BOOTSTRAP -eq 1 ]]; then
|
|||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Preview (rsync --dry-run) ==="
|
echo "=== Preview (rsync --dry-run) ==="
|
||||||
rsync "${RSYNC_ARGS[@]}" --dry-run --itemize-changes "$UPSTREAM/" "$PREVIEW_DEST/"
|
rsync "${RSYNC_ARGS[@]}" --dry-run --itemize-changes "$SYNC_SOURCE/" "$PREVIEW_DEST/"
|
||||||
echo "=== End preview ==="
|
echo "=== End preview ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -368,7 +402,7 @@ echo "Syncing upstream content..."
|
|||||||
if [[ $BOOTSTRAP -eq 1 ]]; then
|
if [[ $BOOTSTRAP -eq 1 ]]; then
|
||||||
mkdir -p "$DEST"
|
mkdir -p "$DEST"
|
||||||
fi
|
fi
|
||||||
rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$DEST/"
|
rsync "${RSYNC_ARGS[@]}" "$SYNC_SOURCE/" "$DEST/"
|
||||||
|
|
||||||
# Bail early if nothing actually changed
|
# Bail early if nothing actually changed
|
||||||
cd "$DEST_REPO"
|
cd "$DEST_REPO"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Skills use Claude Code tool names. When you encounter these in a skill, use your
|
|||||||
|-----------------|------------------|
|
|-----------------|------------------|
|
||||||
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
|
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
|
||||||
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
|
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
|
||||||
| Task returns result | `wait` |
|
| Task returns result | `wait_agent` |
|
||||||
| Task completes automatically | `close_agent` to free slot |
|
| Task completes automatically | `close_agent` to free slot |
|
||||||
| `TodoWrite` (task tracking) | `update_plan` |
|
| `TodoWrite` (task tracking) | `update_plan` |
|
||||||
| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
|
| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
|
||||||
@@ -22,7 +22,12 @@ Add to your Codex config (`~/.codex/config.toml`):
|
|||||||
multi_agent = true
|
multi_agent = true
|
||||||
```
|
```
|
||||||
|
|
||||||
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
|
This enables `spawn_agent`, `wait_agent`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
|
||||||
|
|
||||||
|
Legacy note: Codex builds before `rust-v0.115.0` exposed spawned-agent
|
||||||
|
waiting as `wait`. Current Codex uses `wait_agent` for spawned agents. The
|
||||||
|
`wait` name now belongs to code-mode `exec/wait`, which resumes a yielded exec
|
||||||
|
cell by `cell_id`; it is not the spawned-agent result tool.
|
||||||
|
|
||||||
## Named agent dispatch
|
## Named agent dispatch
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,29 @@ Skills use Claude Code tool names. When you encounter these in a skill, use your
|
|||||||
| `Skill` tool (invoke a skill) | `activate_skill` |
|
| `Skill` tool (invoke a skill) | `activate_skill` |
|
||||||
| `WebSearch` | `google_web_search` |
|
| `WebSearch` | `google_web_search` |
|
||||||
| `WebFetch` | `web_fetch` |
|
| `WebFetch` | `web_fetch` |
|
||||||
| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
|
| `Task` tool (dispatch subagent) | `@agent-name` (see [Subagent support](#subagent-support)) |
|
||||||
|
|
||||||
## No subagent support
|
## Subagent support
|
||||||
|
|
||||||
Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
|
Gemini CLI supports subagents natively via the `@` syntax. Use the built-in `@generalist` agent to dispatch any task — it has access to all tools and follows the prompt you provide.
|
||||||
|
|
||||||
|
When a skill says to dispatch a named agent type, use `@generalist` with the full prompt from the skill's prompt template:
|
||||||
|
|
||||||
|
| Skill instruction | Gemini CLI equivalent |
|
||||||
|
|-------------------|----------------------|
|
||||||
|
| `Task tool (superpowers:implementer)` | `@generalist` with the filled `implementer-prompt.md` template |
|
||||||
|
| `Task tool (superpowers:spec-reviewer)` | `@generalist` with the filled `spec-reviewer-prompt.md` template |
|
||||||
|
| `Task tool (superpowers:code-reviewer)` | `@code-reviewer` (bundled agent) or `@generalist` with the filled review prompt |
|
||||||
|
| `Task tool (superpowers:code-quality-reviewer)` | `@generalist` with the filled `code-quality-reviewer-prompt.md` template |
|
||||||
|
| `Task tool (general-purpose)` with inline prompt | `@generalist` with your inline prompt |
|
||||||
|
|
||||||
|
### Prompt filling
|
||||||
|
|
||||||
|
Skills provide prompt templates with placeholders like `{WHAT_WAS_IMPLEMENTED}` or `[FULL TEXT of task]`. Fill all placeholders and pass the complete prompt as the message to `@generalist`. The prompt template itself contains the agent's role, review criteria, and expected output format — `@generalist` will follow it.
|
||||||
|
|
||||||
|
### Parallel dispatch
|
||||||
|
|
||||||
|
Gemini CLI supports parallel subagent dispatch. When a skill asks you to dispatch multiple independent subagent tasks in parallel, request all of those `@generalist` or named subagent tasks together in the same prompt. Keep dependent tasks sequential, but do not serialize independent subagent tasks just to preserve a simpler history.
|
||||||
|
|
||||||
## Additional Gemini CLI tools
|
## Additional Gemini CLI tools
|
||||||
|
|
||||||
|
|||||||
@@ -135,8 +135,7 @@ EOF
|
|||||||
|
|
||||||
# Note: We use a longer timeout since this is integration testing
|
# Note: We use a longer timeout since this is integration testing
|
||||||
# Use --allowed-tools to enable tool usage in headless mode
|
# Use --allowed-tools to enable tool usage in headless mode
|
||||||
# IMPORTANT: Run from superpowers directory so local dev skills are available
|
PROMPT="Execute the implementation plan at docs/superpowers/plans/implementation-plan.md using the subagent-driven-development skill.
|
||||||
PROMPT="Change to directory $TEST_PROJECT and then execute the implementation plan at docs/superpowers/plans/implementation-plan.md using the subagent-driven-development skill.
|
|
||||||
|
|
||||||
IMPORTANT: Follow the skill exactly. I will be verifying that you:
|
IMPORTANT: Follow the skill exactly. I will be verifying that you:
|
||||||
1. Read the plan once at the beginning
|
1. Read the plan once at the beginning
|
||||||
@@ -147,9 +146,14 @@ IMPORTANT: Follow the skill exactly. I will be verifying that you:
|
|||||||
|
|
||||||
Begin now. Execute the plan."
|
Begin now. Execute the plan."
|
||||||
|
|
||||||
echo "Running Claude (output will be shown below and saved to $OUTPUT_FILE)..."
|
PLUGIN_DIR=$(cd "$SCRIPT_DIR/../.." && pwd)
|
||||||
|
|
||||||
|
# Run claude from inside the test project so its session JSONL lands in a
|
||||||
|
# project-specific directory under ~/.claude/projects/, isolated from any
|
||||||
|
# other concurrent claude sessions.
|
||||||
|
echo "Running Claude (plugin-dir: $PLUGIN_DIR, cwd: $TEST_PROJECT)..."
|
||||||
echo "================================================================================"
|
echo "================================================================================"
|
||||||
cd "$SCRIPT_DIR/../.." && timeout 1800 claude -p "$PROMPT" --allowed-tools=all --add-dir "$TEST_PROJECT" --permission-mode bypassPermissions 2>&1 | tee "$OUTPUT_FILE" || {
|
cd "$TEST_PROJECT" && timeout 1800 claude -p "$PROMPT" --plugin-dir "$PLUGIN_DIR" --allowed-tools=all --permission-mode bypassPermissions 2>&1 | tee "$OUTPUT_FILE" || {
|
||||||
echo ""
|
echo ""
|
||||||
echo "================================================================================"
|
echo "================================================================================"
|
||||||
echo "EXECUTION FAILED (exit code: $?)"
|
echo "EXECUTION FAILED (exit code: $?)"
|
||||||
@@ -161,13 +165,17 @@ echo ""
|
|||||||
echo "Execution complete. Analyzing results..."
|
echo "Execution complete. Analyzing results..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Find the session transcript
|
# Find the session transcript. Because we ran claude from $TEST_PROJECT (a
|
||||||
# Session files are in ~/.claude/projects/-<working-dir>/<session-id>.jsonl
|
# unique tmp dir), its sessions live in their own ~/.claude/projects/ folder
|
||||||
WORKING_DIR_ESCAPED=$(echo "$SCRIPT_DIR/../.." | sed 's/\//-/g' | sed 's/^-//')
|
# and we can pick the most-recent one without racing other concurrent sessions.
|
||||||
SESSION_DIR="$HOME/.claude/projects/$WORKING_DIR_ESCAPED"
|
# Resolve the real path because macOS mktemp returns /var/... but claude
|
||||||
|
# normalizes it to /private/var/... when naming the project dir.
|
||||||
# Find the most recent session file (created during this test run)
|
TEST_PROJECT_REAL=$(cd "$TEST_PROJECT" && pwd -P)
|
||||||
SESSION_FILE=$(find "$SESSION_DIR" -name "*.jsonl" -type f -mmin -60 2>/dev/null | sort -r | head -1)
|
# Claude normalizes the cwd to a directory name by replacing every non-alphanumeric
|
||||||
|
# character with `-` (so `_`, `.`, `/` all become `-`).
|
||||||
|
SESSION_DIR="$HOME/.claude/projects/$(echo "$TEST_PROJECT_REAL" | sed 's|[^a-zA-Z0-9]|-|g')"
|
||||||
|
# `|| true` prevents pipefail killing the script if ls gets SIGPIPE'd by head.
|
||||||
|
SESSION_FILE=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1 || true)
|
||||||
|
|
||||||
if [ -z "$SESSION_FILE" ]; then
|
if [ -z "$SESSION_FILE" ]; then
|
||||||
echo "ERROR: Could not find session transcript file"
|
echo "ERROR: Could not find session transcript file"
|
||||||
@@ -194,9 +202,9 @@ else
|
|||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Test 2: Subagents were used (Task tool)
|
# Test 2: Subagents were used (Agent / Task tool — name varies by harness version)
|
||||||
echo "Test 2: Subagents dispatched..."
|
echo "Test 2: Subagents dispatched..."
|
||||||
task_count=$(grep -c '"name":"Task"' "$SESSION_FILE" || echo "0")
|
task_count=$(grep -cE '"name":"(Agent|Task)"' "$SESSION_FILE" || echo "0")
|
||||||
if [ "$task_count" -ge 2 ]; then
|
if [ "$task_count" -ge 2 ]; then
|
||||||
echo " [PASS] $task_count subagents dispatched"
|
echo " [PASS] $task_count subagents dispatched"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -73,6 +73,19 @@ assert_matches() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_not_matches() {
|
||||||
|
local haystack="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
local description="$3"
|
||||||
|
|
||||||
|
if printf '%s' "$haystack" | grep -Eq -- "$pattern"; then
|
||||||
|
fail "$description"
|
||||||
|
echo " did not expect to match: $pattern"
|
||||||
|
else
|
||||||
|
pass "$description"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
assert_path_absent() {
|
assert_path_absent() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local description="$2"
|
local description="$2"
|
||||||
@@ -244,6 +257,22 @@ EOF
|
|||||||
commit_fixture "$repo" "Initial destination fixture"
|
commit_fixture "$repo" "Initial destination fixture"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_openai_agent_metadata_fixture() {
|
||||||
|
local repo="$1"
|
||||||
|
|
||||||
|
mkdir -p "$repo/plugins/superpowers/skills/example/agents"
|
||||||
|
|
||||||
|
cat > "$repo/plugins/superpowers/skills/example/agents/openai.yaml" <<'EOF'
|
||||||
|
interface:
|
||||||
|
display_name: "Example"
|
||||||
|
short_description: "Destination-owned OpenAI metadata"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git -C "$repo" add plugins/superpowers/skills/example/agents/openai.yaml
|
||||||
|
|
||||||
|
commit_fixture "$repo" "Add OpenAI agent metadata fixture"
|
||||||
|
}
|
||||||
|
|
||||||
dirty_tracked_destination_skill() {
|
dirty_tracked_destination_skill() {
|
||||||
local repo="$1"
|
local repo="$1"
|
||||||
|
|
||||||
@@ -261,6 +290,7 @@ write_synced_destination_fixture() {
|
|||||||
"$repo/plugins/superpowers/.codex-plugin" \
|
"$repo/plugins/superpowers/.codex-plugin" \
|
||||||
"$repo/plugins/superpowers/.private-journal" \
|
"$repo/plugins/superpowers/.private-journal" \
|
||||||
"$repo/plugins/superpowers/assets" \
|
"$repo/plugins/superpowers/assets" \
|
||||||
|
"$repo/plugins/superpowers/skills/example/agents" \
|
||||||
"$repo/plugins/superpowers/skills/example"
|
"$repo/plugins/superpowers/skills/example"
|
||||||
|
|
||||||
cat > "$repo/plugins/superpowers/.codex-plugin/plugin.json" <<EOF
|
cat > "$repo/plugins/superpowers/.codex-plugin/plugin.json" <<EOF
|
||||||
@@ -282,12 +312,19 @@ EOF
|
|||||||
Fixture content.
|
Fixture content.
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
cat > "$repo/plugins/superpowers/skills/example/agents/openai.yaml" <<'EOF'
|
||||||
|
interface:
|
||||||
|
display_name: "Example"
|
||||||
|
short_description: "Destination-owned OpenAI metadata"
|
||||||
|
EOF
|
||||||
|
|
||||||
printf 'tracked keep\n' > "$repo/plugins/superpowers/.private-journal/keep.txt"
|
printf 'tracked keep\n' > "$repo/plugins/superpowers/.private-journal/keep.txt"
|
||||||
|
|
||||||
git -C "$repo" add \
|
git -C "$repo" add \
|
||||||
plugins/superpowers/.codex-plugin/plugin.json \
|
plugins/superpowers/.codex-plugin/plugin.json \
|
||||||
plugins/superpowers/assets/app-icon.png \
|
plugins/superpowers/assets/app-icon.png \
|
||||||
plugins/superpowers/assets/superpowers-small.svg \
|
plugins/superpowers/assets/superpowers-small.svg \
|
||||||
|
plugins/superpowers/skills/example/agents/openai.yaml \
|
||||||
plugins/superpowers/skills/example/SKILL.md \
|
plugins/superpowers/skills/example/SKILL.md \
|
||||||
plugins/superpowers/.private-journal/keep.txt
|
plugins/superpowers/.private-journal/keep.txt
|
||||||
|
|
||||||
@@ -415,6 +452,7 @@ main() {
|
|||||||
local help_output
|
local help_output
|
||||||
local script_source
|
local script_source
|
||||||
local dirty_skill_path
|
local dirty_skill_path
|
||||||
|
local noop_openai_metadata_path
|
||||||
|
|
||||||
echo "=== Test: sync-to-codex-plugin dry-run regression ==="
|
echo "=== Test: sync-to-codex-plugin dry-run regression ==="
|
||||||
|
|
||||||
@@ -443,6 +481,7 @@ main() {
|
|||||||
|
|
||||||
init_repo "$dest"
|
init_repo "$dest"
|
||||||
write_destination_fixture "$dest"
|
write_destination_fixture "$dest"
|
||||||
|
add_openai_agent_metadata_fixture "$dest"
|
||||||
checkout_fixture_branch "$dest" "$dest_branch"
|
checkout_fixture_branch "$dest" "$dest_branch"
|
||||||
dirty_tracked_destination_skill "$dest"
|
dirty_tracked_destination_skill "$dest"
|
||||||
|
|
||||||
@@ -490,6 +529,7 @@ main() {
|
|||||||
preview_section="$(printf '%s\n' "$preview_output" | sed -n '/^=== Preview (rsync --dry-run) ===$/,/^=== End preview ===$/p')"
|
preview_section="$(printf '%s\n' "$preview_output" | sed -n '/^=== Preview (rsync --dry-run) ===$/,/^=== End preview ===$/p')"
|
||||||
stale_preview_section="$(printf '%s\n' "$stale_preview_output" | sed -n '/^=== Preview (rsync --dry-run) ===$/,/^=== End preview ===$/p')"
|
stale_preview_section="$(printf '%s\n' "$stale_preview_output" | sed -n '/^=== Preview (rsync --dry-run) ===$/,/^=== End preview ===$/p')"
|
||||||
dirty_skill_path="$dirty_apply_dest/plugins/superpowers/skills/example/SKILL.md"
|
dirty_skill_path="$dirty_apply_dest/plugins/superpowers/skills/example/SKILL.md"
|
||||||
|
noop_openai_metadata_path="$noop_apply_dest/plugins/superpowers/skills/example/agents/openai.yaml"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Preview assertions..."
|
echo "Preview assertions..."
|
||||||
@@ -505,6 +545,7 @@ main() {
|
|||||||
assert_not_contains "$preview_output" "Overlay file (.codex-plugin/plugin.json) will be regenerated" "Preview omits overlay regeneration note"
|
assert_not_contains "$preview_output" "Overlay file (.codex-plugin/plugin.json) will be regenerated" "Preview omits overlay regeneration note"
|
||||||
assert_not_contains "$preview_output" "Assets (superpowers-small.svg, app-icon.png) will be seeded from" "Preview omits assets seeding note"
|
assert_not_contains "$preview_output" "Assets (superpowers-small.svg, app-icon.png) will be seeded from" "Preview omits assets seeding note"
|
||||||
assert_contains "$preview_section" "skills/example/SKILL.md" "Preview reflects dirty tracked destination file"
|
assert_contains "$preview_section" "skills/example/SKILL.md" "Preview reflects dirty tracked destination file"
|
||||||
|
assert_not_matches "$preview_section" "\\*deleting +skills/example/agents/openai\\.yaml" "Preview preserves destination-owned OpenAI agent metadata"
|
||||||
assert_current_branch "$dest" "$dest_branch" "Preview leaves destination checkout on its original branch"
|
assert_current_branch "$dest" "$dest_branch" "Preview leaves destination checkout on its original branch"
|
||||||
assert_branch_absent "$dest" "sync/superpowers-*" "Preview does not create sync branch in destination checkout"
|
assert_branch_absent "$dest" "sync/superpowers-*" "Preview does not create sync branch in destination checkout"
|
||||||
|
|
||||||
@@ -542,6 +583,9 @@ Locally modified fixture content." "Dirty local apply preserves tracked working-
|
|||||||
assert_contains "$noop_apply_output" "No changes — embedded plugin was already in sync with upstream" "Clean no-op local apply reports no changes"
|
assert_contains "$noop_apply_output" "No changes — embedded plugin was already in sync with upstream" "Clean no-op local apply reports no changes"
|
||||||
assert_current_branch "$noop_apply_dest" "$noop_apply_dest_branch" "Clean no-op local apply leaves destination checkout on its original branch"
|
assert_current_branch "$noop_apply_dest" "$noop_apply_dest_branch" "Clean no-op local apply leaves destination checkout on its original branch"
|
||||||
assert_branch_absent "$noop_apply_dest" "sync/superpowers-*" "Clean no-op local apply does not create sync branch in destination checkout"
|
assert_branch_absent "$noop_apply_dest" "sync/superpowers-*" "Clean no-op local apply does not create sync branch in destination checkout"
|
||||||
|
assert_file_equals "$noop_openai_metadata_path" "interface:
|
||||||
|
display_name: \"Example\"
|
||||||
|
short_description: \"Destination-owned OpenAI metadata\"" "Clean no-op local apply preserves OpenAI agent metadata"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Missing manifest assertions..."
|
echo "Missing manifest assertions..."
|
||||||
|
|||||||
135
tests/openclaw/test-native-plugin.sh
Executable file
135
tests/openclaw/test-native-plugin.sh
Executable file
@@ -0,0 +1,135 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Test: OpenClaw native plugin package
|
||||||
|
# Verifies the Superpowers repo exposes a native OpenClaw plugin contract.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
echo "=== Test: OpenClaw native plugin package ==="
|
||||||
|
|
||||||
|
echo "Test 1: Checking OpenClaw manifest..."
|
||||||
|
node <<'NODE'
|
||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
const manifestPath = "openclaw.plugin.json";
|
||||||
|
if (!fs.existsSync(manifestPath)) {
|
||||||
|
throw new Error(`${manifestPath} is missing`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
||||||
|
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||||
|
if (manifest.id !== "superpowers-openclaw") {
|
||||||
|
throw new Error(`unexpected manifest id: ${manifest.id}`);
|
||||||
|
}
|
||||||
|
if (manifest.version !== pkg.version) {
|
||||||
|
throw new Error(`manifest version ${manifest.version} must match package version ${pkg.version}`);
|
||||||
|
}
|
||||||
|
if (!Array.isArray(manifest.skills) || manifest.skills.length !== 1 || manifest.skills[0] !== "./skills") {
|
||||||
|
throw new Error(`unexpected skills declaration: ${JSON.stringify(manifest.skills)}`);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!manifest.configSchema ||
|
||||||
|
manifest.configSchema.type !== "object" ||
|
||||||
|
manifest.configSchema.additionalProperties !== false ||
|
||||||
|
manifest.configSchema.properties !== undefined
|
||||||
|
) {
|
||||||
|
throw new Error(`manifest must declare an empty object configSchema: ${JSON.stringify(manifest.configSchema)}`);
|
||||||
|
}
|
||||||
|
if ("entrypoint" in manifest) {
|
||||||
|
throw new Error("OpenClaw entrypoints belong in package.json openclaw.extensions, not openclaw.plugin.json");
|
||||||
|
}
|
||||||
|
if ("hooks" in manifest) {
|
||||||
|
throw new Error("OpenClaw hook registration belongs in the runtime entrypoint, not openclaw.plugin.json");
|
||||||
|
}
|
||||||
|
NODE
|
||||||
|
echo " [PASS] Manifest declares plugin skills correctly"
|
||||||
|
|
||||||
|
echo "Test 2: Checking package OpenClaw extension metadata..."
|
||||||
|
node <<'NODE'
|
||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||||
|
if (pkg.name !== "superpowers") {
|
||||||
|
throw new Error(`package name changed unexpectedly: ${pkg.name}`);
|
||||||
|
}
|
||||||
|
if (pkg.type !== "module") {
|
||||||
|
throw new Error("OpenClaw runtime should preserve the repo's ESM package mode");
|
||||||
|
}
|
||||||
|
const extensions = pkg.openclaw?.extensions;
|
||||||
|
if (!Array.isArray(extensions) || extensions.length !== 1 || extensions[0] !== "./.openclaw/index.js") {
|
||||||
|
throw new Error(`unexpected openclaw.extensions: ${JSON.stringify(extensions)}`);
|
||||||
|
}
|
||||||
|
NODE
|
||||||
|
echo " [PASS] package.json points OpenClaw at the runtime entrypoint"
|
||||||
|
|
||||||
|
echo "Test 3: Checking runtime entrypoint syntax and hook behavior..."
|
||||||
|
node --check .openclaw/index.js
|
||||||
|
node <<'NODE'
|
||||||
|
const module = await import(new URL("./.openclaw/index.js", import.meta.url));
|
||||||
|
const plugin = module.default;
|
||||||
|
|
||||||
|
if (!plugin || plugin.id !== "superpowers-openclaw" || typeof plugin.register !== "function") {
|
||||||
|
throw new Error("OpenClaw runtime must default-export a plugin with register(api)");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hooks = [];
|
||||||
|
const api = {
|
||||||
|
pluginConfig: {},
|
||||||
|
rootDir: process.cwd(),
|
||||||
|
logger: { warn(message) { throw new Error(`unexpected warning: ${message}`); } },
|
||||||
|
on(name, handler) {
|
||||||
|
hooks.push({ name, handler });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
plugin.register(api);
|
||||||
|
if (hooks.length !== 1 || hooks[0].name !== "before_prompt_build") {
|
||||||
|
throw new Error(`unexpected hooks: ${JSON.stringify(hooks.map((hook) => hook.name))}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await hooks[0].handler();
|
||||||
|
if (!result?.prependSystemContext?.includes("Superpowers")) {
|
||||||
|
throw new Error("before_prompt_build hook must inject Superpowers guidance");
|
||||||
|
}
|
||||||
|
if (result.prependSystemContext.includes("~/.openclaw/skills")) {
|
||||||
|
throw new Error("native plugin guidance must not advertise managed skill symlinks");
|
||||||
|
}
|
||||||
|
NODE
|
||||||
|
echo " [PASS] Runtime entrypoint registers the prompt hook"
|
||||||
|
|
||||||
|
echo "Test 4: Checking README install flow..."
|
||||||
|
if ! grep -q 'openclaw plugins install --link ~/.openclaw/vendor/superpowers' README.md; then
|
||||||
|
echo " [FAIL] README must document linked OpenClaw plugin install"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -q -- '--dangerously-force-unsafe-install' README.md; then
|
||||||
|
echo " [FAIL] README must document OpenClaw scanner override required by existing helper scripts"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -q 'skills/writing-skills/render-graphs.js' README.md; then
|
||||||
|
echo " [FAIL] README must explain the exact Superpowers helper that triggers OpenClaw's scanner"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -q 'not part of the OpenClaw runtime hook' README.md; then
|
||||||
|
echo " [FAIL] README must explain that the flagged helper is not part of the OpenClaw runtime hook"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -q 'openclaw plugins enable superpowers-openclaw' README.md; then
|
||||||
|
echo " [FAIL] README must document enabling the OpenClaw plugin"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -q 'openclaw gateway restart' README.md; then
|
||||||
|
echo " [FAIL] README must document restarting the gateway"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if grep -q 'OPENCLAW_SKILLS_DIR\|ln -s\|AGENTS-snippet\|.openclaw/INSTALL.md' README.md; then
|
||||||
|
echo " [FAIL] README must not use the legacy symlink/snippet wrapper or extra install doc"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " [PASS] README documents native plugin commands"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== OpenClaw native plugin tests passed ==="
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Test: Skill Priority Resolution
|
# Test: Skill Priority Resolution
|
||||||
# Verifies that skills are resolved with correct priority: project > personal > superpowers
|
# Documents current OpenCode duplicate-name behavior for local and bundled
|
||||||
|
# skills. The desired local-shadowing behavior is tracked separately; this
|
||||||
|
# test keeps the integration suite honest without adding a plugin workaround.
|
||||||
# NOTE: These tests require OpenCode to be installed and configured
|
# NOTE: These tests require OpenCode to be installed and configured
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
OPENCODE_TEST_TIMEOUT_SECONDS="${OPENCODE_TEST_TIMEOUT_SECONDS:-120}"
|
||||||
|
|
||||||
echo "=== Test: Skill Priority Resolution ==="
|
echo "=== Test: Skill Priority Resolution ==="
|
||||||
|
|
||||||
@@ -96,103 +99,119 @@ if ! command -v opencode &> /dev/null; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test 2: Test that personal overrides superpowers
|
run_opencode() {
|
||||||
|
local result_var="$1"
|
||||||
|
local dir="$2"
|
||||||
|
local prompt="$3"
|
||||||
|
local command_output
|
||||||
|
local exit_code
|
||||||
|
|
||||||
|
set +e
|
||||||
|
command_output=$(cd "$dir" && timeout "${OPENCODE_TEST_TIMEOUT_SECONDS}s" opencode run --print-logs --format json "$prompt" 2>&1)
|
||||||
|
exit_code=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ $exit_code -eq 124 ]; then
|
||||||
|
echo " [FAIL] OpenCode timed out after ${OPENCODE_TEST_TIMEOUT_SECONDS}s"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $exit_code -ne 0 ]; then
|
||||||
|
echo " [FAIL] OpenCode returned non-zero exit code: $exit_code"
|
||||||
|
echo " Output was:"
|
||||||
|
awk 'NR <= 80 { print }' <<<"$command_output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "$result_var" '%s' "$command_output"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_contains() {
|
||||||
|
local output="$1"
|
||||||
|
local needle="$2"
|
||||||
|
local message="$3"
|
||||||
|
|
||||||
|
if [[ "$output" == *"$needle"* ]]; then
|
||||||
|
echo " [PASS] $message"
|
||||||
|
else
|
||||||
|
echo " [FAIL] $message"
|
||||||
|
echo " Expected to find: $needle"
|
||||||
|
echo " Output was:"
|
||||||
|
awk 'NR <= 80 { print }' <<<"$output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
first_skill_tool_event() {
|
||||||
|
awk '/"type":"tool_use"/ && /"tool":"skill"/ { print; exit }' <<<"$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
describe_priority_result() {
|
||||||
|
local output="$1"
|
||||||
|
local expected_marker="$2"
|
||||||
|
local fallback_marker="$3"
|
||||||
|
local pass_message="$4"
|
||||||
|
local known_bug_message="$5"
|
||||||
|
local loaded_skill
|
||||||
|
|
||||||
|
loaded_skill="$(first_skill_tool_event "$output")"
|
||||||
|
|
||||||
|
if [[ "$loaded_skill" == *"$expected_marker"* ]]; then
|
||||||
|
echo " [PASS] $pass_message"
|
||||||
|
elif [[ "$loaded_skill" == *"$fallback_marker"* ]]; then
|
||||||
|
echo " [INFO] $known_bug_message"
|
||||||
|
echo " [INFO] Tracked separately: OpenCode bundled skills can shadow local skills with duplicate native names"
|
||||||
|
else
|
||||||
|
echo " [FAIL] Could not verify priority marker in native skill tool output"
|
||||||
|
echo " Output was:"
|
||||||
|
awk 'NR <= 80 { print }' <<<"$output"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test 2: Document personal vs bundled superpowers priority
|
||||||
echo ""
|
echo ""
|
||||||
echo "Test 2: Testing personal > superpowers priority..."
|
echo "Test 2: Documenting personal vs superpowers priority..."
|
||||||
echo " Running from outside project directory..."
|
echo " Running from outside project directory..."
|
||||||
|
|
||||||
# Run from HOME (not in project) - should get personal version
|
run_opencode output "$HOME" "Call the skill tool with name \"priority-test\". Show the exact content including any PRIORITY_MARKER text."
|
||||||
cd "$HOME"
|
describe_priority_result \
|
||||||
output=$(timeout 60s opencode run --print-logs "Use the use_skill tool to load the priority-test skill. Show me the exact content including any PRIORITY_MARKER text." 2>&1) || {
|
"$output" \
|
||||||
exit_code=$?
|
"PRIORITY_MARKER_PERSONAL_VERSION" \
|
||||||
if [ $exit_code -eq 124 ]; then
|
"PRIORITY_MARKER_SUPERPOWERS_VERSION" \
|
||||||
echo " [FAIL] OpenCode timed out after 60s"
|
"Personal version loaded for duplicate native skill name" \
|
||||||
exit 1
|
"Current OpenCode behavior loaded bundled superpowers version instead of personal version"
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if echo "$output" | grep -qi "PRIORITY_MARKER_PERSONAL_VERSION"; then
|
# Test 3: Document project vs bundled superpowers priority
|
||||||
echo " [PASS] Personal version loaded (overrides superpowers)"
|
|
||||||
elif echo "$output" | grep -qi "PRIORITY_MARKER_SUPERPOWERS_VERSION"; then
|
|
||||||
echo " [FAIL] Superpowers version loaded instead of personal"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo " [WARN] Could not verify priority marker in output"
|
|
||||||
echo " Output snippet:"
|
|
||||||
echo "$output" | grep -i "priority\|personal\|superpowers" | head -10
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 3: Test that project overrides both personal and superpowers
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Test 3: Testing project > personal > superpowers priority..."
|
echo "Test 3: Documenting project vs personal/superpowers priority..."
|
||||||
echo " Running from project directory..."
|
echo " Running from project directory..."
|
||||||
|
|
||||||
# Run from project directory - should get project version
|
run_opencode output "$TEST_HOME/test-project" "Call the skill tool with name \"priority-test\". Show the exact content including any PRIORITY_MARKER text."
|
||||||
cd "$TEST_HOME/test-project"
|
describe_priority_result \
|
||||||
output=$(timeout 60s opencode run --print-logs "Use the use_skill tool to load the priority-test skill. Show me the exact content including any PRIORITY_MARKER text." 2>&1) || {
|
"$output" \
|
||||||
exit_code=$?
|
"PRIORITY_MARKER_PROJECT_VERSION" \
|
||||||
if [ $exit_code -eq 124 ]; then
|
"PRIORITY_MARKER_SUPERPOWERS_VERSION" \
|
||||||
echo " [FAIL] OpenCode timed out after 60s"
|
"Project version loaded for duplicate native skill name" \
|
||||||
exit 1
|
"Current OpenCode behavior loaded bundled superpowers version instead of project version"
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if echo "$output" | grep -qi "PRIORITY_MARKER_PROJECT_VERSION"; then
|
# Test 4: Test a non-colliding bundled superpowers skill is still available
|
||||||
echo " [PASS] Project version loaded (highest priority)"
|
|
||||||
elif echo "$output" | grep -qi "PRIORITY_MARKER_PERSONAL_VERSION"; then
|
|
||||||
echo " [FAIL] Personal version loaded instead of project"
|
|
||||||
exit 1
|
|
||||||
elif echo "$output" | grep -qi "PRIORITY_MARKER_SUPERPOWERS_VERSION"; then
|
|
||||||
echo " [FAIL] Superpowers version loaded instead of project"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo " [WARN] Could not verify priority marker in output"
|
|
||||||
echo " Output snippet:"
|
|
||||||
echo "$output" | grep -i "priority\|project\|personal" | head -10
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 4: Test explicit superpowers: prefix bypasses priority
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Test 4: Testing superpowers: prefix forces superpowers version..."
|
echo "Test 4: Testing non-colliding superpowers skill remains available..."
|
||||||
|
|
||||||
cd "$TEST_HOME/test-project"
|
mkdir -p "$SUPERPOWERS_SKILLS_DIR/superpowers-only-test"
|
||||||
output=$(timeout 60s opencode run --print-logs "Use the use_skill tool to load superpowers:priority-test specifically. Show me the exact content including any PRIORITY_MARKER text." 2>&1) || {
|
cat > "$SUPERPOWERS_SKILLS_DIR/superpowers-only-test/SKILL.md" <<'EOF'
|
||||||
exit_code=$?
|
---
|
||||||
if [ $exit_code -eq 124 ]; then
|
name: superpowers-only-test
|
||||||
echo " [FAIL] OpenCode timed out after 60s"
|
description: Superpowers-only priority test skill
|
||||||
exit 1
|
---
|
||||||
fi
|
# Superpowers Only Test Skill
|
||||||
}
|
|
||||||
|
|
||||||
if echo "$output" | grep -qi "PRIORITY_MARKER_SUPERPOWERS_VERSION"; then
|
PRIORITY_MARKER_SUPERPOWERS_ONLY_VERSION
|
||||||
echo " [PASS] superpowers: prefix correctly forces superpowers version"
|
EOF
|
||||||
elif echo "$output" | grep -qi "PRIORITY_MARKER_PROJECT_VERSION\|PRIORITY_MARKER_PERSONAL_VERSION"; then
|
|
||||||
echo " [FAIL] superpowers: prefix did not force superpowers version"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo " [WARN] Could not verify priority marker in output"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 5: Test explicit project: prefix
|
run_opencode output "$TEST_HOME/test-project" "Call the skill tool with name \"superpowers-only-test\". Show the exact content including any PRIORITY_MARKER text."
|
||||||
echo ""
|
assert_contains "$output" "PRIORITY_MARKER_SUPERPOWERS_ONLY_VERSION" "Non-colliding superpowers skill is still registered"
|
||||||
echo "Test 5: Testing project: prefix forces project version..."
|
|
||||||
|
|
||||||
cd "$HOME" # Run from outside project but with project: prefix
|
|
||||||
output=$(timeout 60s opencode run --print-logs "Use the use_skill tool to load project:priority-test specifically. Show me the exact content." 2>&1) || {
|
|
||||||
exit_code=$?
|
|
||||||
if [ $exit_code -eq 124 ]; then
|
|
||||||
echo " [FAIL] OpenCode timed out after 60s"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Note: This may fail since we're not in the project directory
|
|
||||||
# The project: prefix only works when in a project context
|
|
||||||
if echo "$output" | grep -qi "not found\|error"; then
|
|
||||||
echo " [PASS] project: prefix correctly fails when not in project context"
|
|
||||||
else
|
|
||||||
echo " [INFO] project: prefix behavior outside project context may vary"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== All priority tests passed ==="
|
echo "=== All priority tests passed ==="
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Test: Tools Functionality
|
# Test: Native Skill Tool Functionality
|
||||||
# Verifies that use_skill and find_skills tools work correctly
|
# Verifies that OpenCode's native skill tool can load personal, project,
|
||||||
|
# and bundled superpowers skills.
|
||||||
# NOTE: These tests require OpenCode to be installed and configured
|
# NOTE: These tests require OpenCode to be installed and configured
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
OPENCODE_TEST_TIMEOUT_SECONDS="${OPENCODE_TEST_TIMEOUT_SECONDS:-120}"
|
||||||
|
|
||||||
echo "=== Test: Tools Functionality ==="
|
echo "=== Test: Tools Functionality ==="
|
||||||
|
|
||||||
@@ -21,84 +23,73 @@ if ! command -v opencode &> /dev/null; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Test 1: Test find_skills tool via direct invocation
|
run_opencode() {
|
||||||
echo "Test 1: Testing find_skills tool..."
|
local result_var="$1"
|
||||||
echo " Running opencode with find_skills request..."
|
local dir="$2"
|
||||||
|
local prompt="$3"
|
||||||
|
local command_output
|
||||||
|
local exit_code
|
||||||
|
|
||||||
# Use timeout to prevent hanging, capture both stdout and stderr
|
set +e
|
||||||
output=$(timeout 60s opencode run --print-logs "Use the find_skills tool to list available skills. Just call the tool and show me the raw output." 2>&1) || {
|
command_output=$(cd "$dir" && timeout "${OPENCODE_TEST_TIMEOUT_SECONDS}s" opencode run --print-logs --format json "$prompt" 2>&1)
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
|
set -e
|
||||||
|
|
||||||
if [ $exit_code -eq 124 ]; then
|
if [ $exit_code -eq 124 ]; then
|
||||||
echo " [FAIL] OpenCode timed out after 60s"
|
echo " [FAIL] OpenCode timed out after ${OPENCODE_TEST_TIMEOUT_SECONDS}s"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo " [WARN] OpenCode returned non-zero exit code: $exit_code"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check for expected patterns in output
|
if [ $exit_code -ne 0 ]; then
|
||||||
if echo "$output" | grep -qi "superpowers:brainstorming\|superpowers:using-superpowers\|Available skills"; then
|
echo " [FAIL] OpenCode returned non-zero exit code: $exit_code"
|
||||||
echo " [PASS] find_skills tool discovered superpowers skills"
|
echo " Output was:"
|
||||||
else
|
awk 'NR <= 80 { print }' <<<"$command_output"
|
||||||
echo " [FAIL] find_skills did not return expected skills"
|
|
||||||
echo " Output was:"
|
|
||||||
echo "$output" | head -50
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if personal test skill was found
|
|
||||||
if echo "$output" | grep -qi "personal-test"; then
|
|
||||||
echo " [PASS] find_skills found personal test skill"
|
|
||||||
else
|
|
||||||
echo " [WARN] personal test skill not found in output (may be ok if tool returned subset)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 2: Test use_skill tool
|
|
||||||
echo ""
|
|
||||||
echo "Test 2: Testing use_skill tool..."
|
|
||||||
echo " Running opencode with use_skill request..."
|
|
||||||
|
|
||||||
output=$(timeout 60s opencode run --print-logs "Use the use_skill tool to load the personal-test skill and show me what you get." 2>&1) || {
|
|
||||||
exit_code=$?
|
|
||||||
if [ $exit_code -eq 124 ]; then
|
|
||||||
echo " [FAIL] OpenCode timed out after 60s"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo " [WARN] OpenCode returned non-zero exit code: $exit_code"
|
|
||||||
|
printf -v "$result_var" '%s' "$command_output"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for the skill marker we embedded
|
assert_contains() {
|
||||||
if echo "$output" | grep -qi "PERSONAL_SKILL_MARKER_12345\|Personal Test Skill\|Launching skill"; then
|
local output="$1"
|
||||||
echo " [PASS] use_skill loaded personal-test skill content"
|
local needle="$2"
|
||||||
else
|
local message="$3"
|
||||||
echo " [FAIL] use_skill did not load personal-test skill correctly"
|
|
||||||
echo " Output was:"
|
|
||||||
echo "$output" | head -50
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test 3: Test use_skill with superpowers: prefix
|
if [[ "$output" == *"$needle"* ]]; then
|
||||||
echo ""
|
echo " [PASS] $message"
|
||||||
echo "Test 3: Testing use_skill with superpowers: prefix..."
|
else
|
||||||
echo " Running opencode with superpowers:brainstorming skill..."
|
echo " [FAIL] $message"
|
||||||
|
echo " Expected to find: $needle"
|
||||||
output=$(timeout 60s opencode run --print-logs "Use the use_skill tool to load superpowers:brainstorming and tell me the first few lines of what you received." 2>&1) || {
|
echo " Output was:"
|
||||||
exit_code=$?
|
awk 'NR <= 80 { print }' <<<"$output"
|
||||||
if [ $exit_code -eq 124 ]; then
|
|
||||||
echo " [FAIL] OpenCode timed out after 60s"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo " [WARN] OpenCode returned non-zero exit code: $exit_code"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for expected content from brainstorming skill
|
# Test 1: Test personal skill loading via OpenCode's native skill tool
|
||||||
if echo "$output" | grep -qi "brainstorming\|Launching skill\|skill.*loaded"; then
|
echo "Test 1: Testing native skill tool with a personal skill..."
|
||||||
echo " [PASS] use_skill loaded superpowers:brainstorming skill"
|
echo " Running opencode with personal-test request..."
|
||||||
else
|
|
||||||
echo " [FAIL] use_skill did not load superpowers:brainstorming correctly"
|
run_opencode output "$TEST_HOME/test-project" "Call the skill tool with name \"personal-test\". Then print the PERSONAL_SKILL_MARKER_12345 marker."
|
||||||
echo " Output was:"
|
assert_contains "$output" '"tool":"skill"' "OpenCode called the native skill tool"
|
||||||
echo "$output" | head -50
|
assert_contains "$output" "PERSONAL_SKILL_MARKER_12345" "native skill tool loaded personal-test skill content"
|
||||||
exit 1
|
|
||||||
fi
|
# Test 2: Test project skill loading
|
||||||
|
echo ""
|
||||||
|
echo "Test 2: Testing native skill tool with a project skill..."
|
||||||
|
echo " Running opencode with project-test request..."
|
||||||
|
|
||||||
|
run_opencode output "$TEST_HOME/test-project" "Call the skill tool with name \"project-test\". Then print the PROJECT_SKILL_MARKER_67890 marker."
|
||||||
|
assert_contains "$output" "PROJECT_SKILL_MARKER_67890" "native skill tool loaded project-test skill content"
|
||||||
|
|
||||||
|
# Test 3: Test bundled superpowers skill loading
|
||||||
|
echo ""
|
||||||
|
echo "Test 3: Testing native skill tool with a superpowers skill..."
|
||||||
|
echo " Running opencode with brainstorming skill..."
|
||||||
|
|
||||||
|
run_opencode output "$TEST_HOME/test-project" "Call the skill tool with name \"brainstorming\". Then tell me the loaded skill title."
|
||||||
|
assert_contains "$output" '"name":"brainstorming"' "native skill tool loaded bundled brainstorming skill"
|
||||||
|
assert_contains "$output" "Brainstorming Ideas Into Designs" "brainstorming skill content was returned"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== All tools tests passed ==="
|
echo "=== All native skill tool tests passed ==="
|
||||||
|
|||||||
Reference in New Issue
Block a user