Compare commits

..

1 Commits

Author SHA1 Message Date
Drew Ritter
57aa869f53 docs: clarify opencode install caveats 2026-04-27 12:18:55 -07:00
15 changed files with 226 additions and 584 deletions

View File

@@ -36,9 +36,6 @@
"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",

View File

@@ -1,23 +0,0 @@
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 }));
},
};

View File

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

161
README.md
View File

@@ -2,10 +2,6 @@
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.
@@ -30,153 +26,98 @@ Thanks!
## Installation ## Installation
Installation differs by harness. If you use more than one, install Superpowers separately for each one. **Note:** Installation differs by platform.
### Claude Code ### Claude Code Official Marketplace
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)
#### Official Marketplace Install the plugin from Anthropic's official marketplace:
- Install the plugin from Anthropic's official marketplace: ```bash
/plugin install superpowers@claude-plugins-official
```
```bash ### Claude Code (Superpowers Marketplace)
/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.
- Register the marketplace: In Claude Code, register the marketplace first:
```bash ```bash
/plugin marketplace add obra/superpowers-marketplace /plugin marketplace add obra/superpowers-marketplace
``` ```
- Install the plugin from this marketplace: Then install the plugin from this marketplace:
```bash ```bash
/plugin install superpowers@superpowers-marketplace /plugin install superpowers@superpowers-marketplace
``` ```
### Codex CLI ### OpenAI Codex CLI
Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins). - Open plugin search interface
- Open the plugin search interface: ```bash
/plugins
```
```bash Search for Superpowers
/plugins
```
- Search for Superpowers: ```bash
superpowers
```
```bash Select `Install Plugin`
superpowers
```
- Select `Install Plugin`. ### OpenAI Codex App
### 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
- Register the marketplace: ### Cursor (via Plugin Marketplace)
```bash In Cursor Agent chat, install from marketplace:
droid plugin marketplace add https://github.com/obra/superpowers
```
- Install the plugin: ```text
/add-plugin superpowers
```
```bash or search for "superpowers" in the plugin marketplace.
droid plugin install superpowers@superpowers
```
### Gemini CLI
- Install the extension:
```bash
gemini extensions install https://github.com/obra/superpowers
```
- Update later:
```bash
gemini extensions update superpowers
```
### 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
already use it in another harness. already use it in another harness.
- Tell OpenCode: Tell OpenCode:
``` ```
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.opencode/INSTALL.md 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) **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 ### GitHub Copilot CLI
- Register the marketplace: ```bash
copilot plugin marketplace add obra/superpowers-marketplace
copilot plugin install superpowers@superpowers-marketplace
```
```bash ### Gemini CLI
copilot plugin marketplace add obra/superpowers-marketplace
```
- Install the plugin: ```bash
gemini extensions install https://github.com/obra/superpowers
```
```bash To update:
copilot plugin install superpowers@superpowers-marketplace
``` ```bash
gemini extensions update superpowers
```
## The Basic Workflow ## The Basic Workflow

View File

@@ -3,7 +3,7 @@
"hooks": { "hooks": {
"sessionStart": [ "sessionStart": [
{ {
"command": "./hooks/run-hook.cmd session-start" "command": "./hooks/session-start"
} }
] ]
} }

View File

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

View File

@@ -2,10 +2,5 @@
"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"
]
}
} }

View File

@@ -4,8 +4,7 @@
# #
# 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/), preserves # (including committed Codex files under .codex-plugin/ and assets/), commits,
# 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.
# #
@@ -52,7 +51,6 @@ EXCLUDES=(
"/.gitattributes" "/.gitattributes"
"/.github/" "/.github/"
"/.gitignore" "/.gitignore"
"/.openclaw/"
"/.opencode/" "/.opencode/"
"/.version-bump.json" "/.version-bump.json"
"/.worktrees/" "/.worktrees/"
@@ -65,7 +63,6 @@ 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
@@ -226,7 +223,6 @@ 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"
@@ -295,7 +291,7 @@ apply_to_preview_checkout() {
mkdir -p "$PREVIEW_DEST" mkdir -p "$PREVIEW_DEST"
fi fi
rsync "${RSYNC_ARGS[@]}" "$SYNC_SOURCE/" "$PREVIEW_DEST/" rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$PREVIEW_DEST/"
} }
preview_checkout_has_changes() { preview_checkout_has_changes() {
@@ -320,36 +316,6 @@ 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)
# ============================================================================= # =============================================================================
@@ -365,7 +331,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 "$SYNC_SOURCE/" "$PREVIEW_DEST/" rsync "${RSYNC_ARGS[@]}" --dry-run --itemize-changes "$UPSTREAM/" "$PREVIEW_DEST/"
echo "=== End preview ===" echo "=== End preview ==="
echo "" echo ""
@@ -402,7 +368,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[@]}" "$SYNC_SOURCE/" "$DEST/" rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$DEST/"
# Bail early if nothing actually changed # Bail early if nothing actually changed
cd "$DEST_REPO" cd "$DEST_REPO"

View File

@@ -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_agent` | | Task returns result | `wait` |
| 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,12 +22,7 @@ Add to your Codex config (`~/.codex/config.toml`):
multi_agent = true multi_agent = true
``` ```
This enables `spawn_agent`, `wait_agent`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`. This enables `spawn_agent`, `wait`, 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

View File

@@ -14,29 +14,11 @@ 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) | `@agent-name` (see [Subagent support](#subagent-support)) | | `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
## Subagent support ## No subagent support
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. 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`.
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

View File

@@ -135,7 +135,8 @@ 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
PROMPT="Execute the implementation plan at docs/superpowers/plans/implementation-plan.md using the subagent-driven-development skill. # IMPORTANT: Run from superpowers directory so local dev skills are available
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
@@ -146,14 +147,9 @@ IMPORTANT: Follow the skill exactly. I will be verifying that you:
Begin now. Execute the plan." Begin now. Execute the plan."
PLUGIN_DIR=$(cd "$SCRIPT_DIR/../.." && pwd) echo "Running Claude (output will be shown below and saved to $OUTPUT_FILE)..."
# 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 "$TEST_PROJECT" && timeout 1800 claude -p "$PROMPT" --plugin-dir "$PLUGIN_DIR" --allowed-tools=all --permission-mode bypassPermissions 2>&1 | tee "$OUTPUT_FILE" || { cd "$SCRIPT_DIR/../.." && timeout 1800 claude -p "$PROMPT" --allowed-tools=all --add-dir "$TEST_PROJECT" --permission-mode bypassPermissions 2>&1 | tee "$OUTPUT_FILE" || {
echo "" echo ""
echo "================================================================================" echo "================================================================================"
echo "EXECUTION FAILED (exit code: $?)" echo "EXECUTION FAILED (exit code: $?)"
@@ -165,17 +161,13 @@ echo ""
echo "Execution complete. Analyzing results..." echo "Execution complete. Analyzing results..."
echo "" echo ""
# Find the session transcript. Because we ran claude from $TEST_PROJECT (a # Find the session transcript
# unique tmp dir), its sessions live in their own ~/.claude/projects/ folder # Session files are in ~/.claude/projects/-<working-dir>/<session-id>.jsonl
# and we can pick the most-recent one without racing other concurrent sessions. WORKING_DIR_ESCAPED=$(echo "$SCRIPT_DIR/../.." | sed 's/\//-/g' | sed 's/^-//')
# Resolve the real path because macOS mktemp returns /var/... but claude SESSION_DIR="$HOME/.claude/projects/$WORKING_DIR_ESCAPED"
# normalizes it to /private/var/... when naming the project dir.
TEST_PROJECT_REAL=$(cd "$TEST_PROJECT" && pwd -P) # Find the most recent session file (created during this test run)
# Claude normalizes the cwd to a directory name by replacing every non-alphanumeric SESSION_FILE=$(find "$SESSION_DIR" -name "*.jsonl" -type f -mmin -60 2>/dev/null | sort -r | head -1)
# 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"
@@ -202,9 +194,9 @@ else
fi fi
echo "" echo ""
# Test 2: Subagents were used (Agent / Task tool — name varies by harness version) # Test 2: Subagents were used (Task tool)
echo "Test 2: Subagents dispatched..." echo "Test 2: Subagents dispatched..."
task_count=$(grep -cE '"name":"(Agent|Task)"' "$SESSION_FILE" || echo "0") task_count=$(grep -c '"name":"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

View File

@@ -73,19 +73,6 @@ 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"
@@ -257,22 +244,6 @@ 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"
@@ -290,7 +261,6 @@ 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
@@ -312,19 +282,12 @@ 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
@@ -452,7 +415,6 @@ 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 ==="
@@ -481,7 +443,6 @@ 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"
@@ -529,7 +490,6 @@ 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..."
@@ -545,7 +505,6 @@ 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"
@@ -583,9 +542,6 @@ 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..."

View File

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

View File

@@ -1,13 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Test: Skill Priority Resolution # Test: Skill Priority Resolution
# Documents current OpenCode duplicate-name behavior for local and bundled # Verifies that skills are resolved with correct priority: project > personal > superpowers
# 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 ==="
@@ -99,119 +96,103 @@ if ! command -v opencode &> /dev/null; then
exit 0 exit 0
fi fi
run_opencode() { # Test 2: Test that personal overrides superpowers
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: Documenting personal vs superpowers priority..." echo "Test 2: Testing personal > superpowers priority..."
echo " Running from outside project directory..." echo " Running from outside project directory..."
run_opencode output "$HOME" "Call the skill tool with name \"priority-test\". Show the exact content including any PRIORITY_MARKER text." # Run from HOME (not in project) - should get personal version
describe_priority_result \ cd "$HOME"
"$output" \ 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) || {
"PRIORITY_MARKER_PERSONAL_VERSION" \ exit_code=$?
"PRIORITY_MARKER_SUPERPOWERS_VERSION" \ if [ $exit_code -eq 124 ]; then
"Personal version loaded for duplicate native skill name" \ echo " [FAIL] OpenCode timed out after 60s"
"Current OpenCode behavior loaded bundled superpowers version instead of personal version" exit 1
fi
}
# Test 3: Document project vs bundled superpowers priority if echo "$output" | grep -qi "PRIORITY_MARKER_PERSONAL_VERSION"; then
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: Documenting project vs personal/superpowers priority..." echo "Test 3: Testing project > personal > superpowers priority..."
echo " Running from project directory..." echo " Running from project directory..."
run_opencode output "$TEST_HOME/test-project" "Call the skill tool with name \"priority-test\". Show the exact content including any PRIORITY_MARKER text." # Run from project directory - should get project version
describe_priority_result \ cd "$TEST_HOME/test-project"
"$output" \ 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) || {
"PRIORITY_MARKER_PROJECT_VERSION" \ exit_code=$?
"PRIORITY_MARKER_SUPERPOWERS_VERSION" \ if [ $exit_code -eq 124 ]; then
"Project version loaded for duplicate native skill name" \ echo " [FAIL] OpenCode timed out after 60s"
"Current OpenCode behavior loaded bundled superpowers version instead of project version" exit 1
fi
}
# Test 4: Test a non-colliding bundled superpowers skill is still available if echo "$output" | grep -qi "PRIORITY_MARKER_PROJECT_VERSION"; then
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 non-colliding superpowers skill remains available..." echo "Test 4: Testing superpowers: prefix forces superpowers version..."
mkdir -p "$SUPERPOWERS_SKILLS_DIR/superpowers-only-test" cd "$TEST_HOME/test-project"
cat > "$SUPERPOWERS_SKILLS_DIR/superpowers-only-test/SKILL.md" <<'EOF' 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) || {
--- exit_code=$?
name: superpowers-only-test if [ $exit_code -eq 124 ]; then
description: Superpowers-only priority test skill echo " [FAIL] OpenCode timed out after 60s"
--- exit 1
# Superpowers Only Test Skill fi
}
PRIORITY_MARKER_SUPERPOWERS_ONLY_VERSION if echo "$output" | grep -qi "PRIORITY_MARKER_SUPERPOWERS_VERSION"; then
EOF echo " [PASS] superpowers: prefix correctly forces superpowers version"
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
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." # Test 5: Test explicit project: prefix
assert_contains "$output" "PRIORITY_MARKER_SUPERPOWERS_ONLY_VERSION" "Non-colliding superpowers skill is still registered" echo ""
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 ==="

View File

@@ -1,12 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Test: Native Skill Tool Functionality # Test: Tools Functionality
# Verifies that OpenCode's native skill tool can load personal, project, # Verifies that use_skill and find_skills tools work correctly
# 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 ==="
@@ -23,73 +21,84 @@ if ! command -v opencode &> /dev/null; then
exit 0 exit 0
fi fi
run_opencode() { # Test 1: Test find_skills tool via direct invocation
local result_var="$1" echo "Test 1: Testing find_skills tool..."
local dir="$2" echo " Running opencode with find_skills request..."
local prompt="$3"
local command_output
local exit_code
set +e # Use timeout to prevent hanging, capture both stdout and stderr
command_output=$(cd "$dir" && timeout "${OPENCODE_TEST_TIMEOUT_SECONDS}s" opencode run --print-logs --format json "$prompt" 2>&1) 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) || {
exit_code=$? exit_code=$?
set -e
if [ $exit_code -eq 124 ]; then if [ $exit_code -eq 124 ]; then
echo " [FAIL] OpenCode timed out after ${OPENCODE_TEST_TIMEOUT_SECONDS}s" echo " [FAIL] OpenCode timed out after 60s"
exit 1 exit 1
fi fi
echo " [WARN] OpenCode returned non-zero exit code: $exit_code"
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() { # Check for expected patterns in output
local output="$1" if echo "$output" | grep -qi "superpowers:brainstorming\|superpowers:using-superpowers\|Available skills"; then
local needle="$2" echo " [PASS] find_skills tool discovered superpowers skills"
local message="$3" else
echo " [FAIL] find_skills did not return expected skills"
echo " Output was:"
echo "$output" | head -50
exit 1
fi
if [[ "$output" == *"$needle"* ]]; then # Check if personal test skill was found
echo " [PASS] $message" if echo "$output" | grep -qi "personal-test"; then
else echo " [PASS] find_skills found personal test skill"
echo " [FAIL] $message" else
echo " Expected to find: $needle" echo " [WARN] personal test skill not found in output (may be ok if tool returned subset)"
echo " Output was:" fi
awk 'NR <= 80 { print }' <<<"$output"
# 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"
} }
# Test 1: Test personal skill loading via OpenCode's native skill tool # Check for the skill marker we embedded
echo "Test 1: Testing native skill tool with a personal skill..." if echo "$output" | grep -qi "PERSONAL_SKILL_MARKER_12345\|Personal Test Skill\|Launching skill"; then
echo " Running opencode with personal-test request..." echo " [PASS] use_skill loaded personal-test skill content"
else
echo " [FAIL] use_skill did not load personal-test skill correctly"
echo " Output was:"
echo "$output" | head -50
exit 1
fi
run_opencode output "$TEST_HOME/test-project" "Call the skill tool with name \"personal-test\". Then print the PERSONAL_SKILL_MARKER_12345 marker." # Test 3: Test use_skill with superpowers: prefix
assert_contains "$output" '"tool":"skill"' "OpenCode called the native skill tool"
assert_contains "$output" "PERSONAL_SKILL_MARKER_12345" "native skill tool loaded personal-test skill content"
# Test 2: Test project skill loading
echo "" echo ""
echo "Test 2: Testing native skill tool with a project skill..." echo "Test 3: Testing use_skill with superpowers: prefix..."
echo " Running opencode with project-test request..." echo " Running opencode with superpowers:brainstorming skill..."
run_opencode output "$TEST_HOME/test-project" "Call the skill tool with name \"project-test\". Then print the PROJECT_SKILL_MARKER_67890 marker." 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) || {
assert_contains "$output" "PROJECT_SKILL_MARKER_67890" "native skill tool loaded project-test skill content" exit_code=$?
if [ $exit_code -eq 124 ]; then
echo " [FAIL] OpenCode timed out after 60s"
exit 1
fi
echo " [WARN] OpenCode returned non-zero exit code: $exit_code"
}
# Test 3: Test bundled superpowers skill loading # Check for expected content from brainstorming skill
echo "" if echo "$output" | grep -qi "brainstorming\|Launching skill\|skill.*loaded"; then
echo "Test 3: Testing native skill tool with a superpowers skill..." echo " [PASS] use_skill loaded superpowers:brainstorming skill"
echo " Running opencode with brainstorming skill..." else
echo " [FAIL] use_skill did not load superpowers:brainstorming correctly"
run_opencode output "$TEST_HOME/test-project" "Call the skill tool with name \"brainstorming\". Then tell me the loaded skill title." echo " Output was:"
assert_contains "$output" '"name":"brainstorming"' "native skill tool loaded bundled brainstorming skill" echo "$output" | head -50
assert_contains "$output" "Brainstorming Ideas Into Designs" "brainstorming skill content was returned" exit 1
fi
echo "" echo ""
echo "=== All native skill tool tests passed ===" echo "=== All tools tests passed ==="