mirror of
https://github.com/obra/superpowers.git
synced 2026-04-30 22:19:05 +08:00
Compare commits
9 Commits
codex/pri-
...
lift-code-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45975ec695 | ||
|
|
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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
149
README.md
149
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,126 @@ 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)
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
---
|
|
||||||
name: code-reviewer
|
|
||||||
description: |
|
|
||||||
Use this agent when a major project step has been completed and needs to be reviewed against the original plan and coding standards. Examples: <example>Context: The user is creating a code-review agent that should be called after a logical chunk of code is written. user: "I've finished implementing the user authentication system as outlined in step 3 of our plan" assistant: "Great work! Now let me use the code-reviewer agent to review the implementation against our plan and coding standards" <commentary>Since a major project step has been completed, use the code-reviewer agent to validate the work against the plan and identify any issues.</commentary></example> <example>Context: User has completed a significant feature implementation. user: "The API endpoints for the task management system are now complete - that covers step 2 from our architecture document" assistant: "Excellent! Let me have the code-reviewer agent examine this implementation to ensure it aligns with our plan and follows best practices" <commentary>A numbered step from the planning document has been completed, so the code-reviewer agent should review the work.</commentary></example>
|
|
||||||
model: inherit
|
|
||||||
---
|
|
||||||
|
|
||||||
You are a Senior Code Reviewer with expertise in software architecture, design patterns, and best practices. Your role is to review completed project steps against original plans and ensure code quality standards are met.
|
|
||||||
|
|
||||||
When reviewing completed work, you will:
|
|
||||||
|
|
||||||
1. **Plan Alignment Analysis**:
|
|
||||||
- Compare the implementation against the original planning document or step description
|
|
||||||
- Identify any deviations from the planned approach, architecture, or requirements
|
|
||||||
- Assess whether deviations are justified improvements or problematic departures
|
|
||||||
- Verify that all planned functionality has been implemented
|
|
||||||
|
|
||||||
2. **Code Quality Assessment**:
|
|
||||||
- Review code for adherence to established patterns and conventions
|
|
||||||
- Check for proper error handling, type safety, and defensive programming
|
|
||||||
- Evaluate code organization, naming conventions, and maintainability
|
|
||||||
- Assess test coverage and quality of test implementations
|
|
||||||
- Look for potential security vulnerabilities or performance issues
|
|
||||||
|
|
||||||
3. **Architecture and Design Review**:
|
|
||||||
- Ensure the implementation follows SOLID principles and established architectural patterns
|
|
||||||
- Check for proper separation of concerns and loose coupling
|
|
||||||
- Verify that the code integrates well with existing systems
|
|
||||||
- Assess scalability and extensibility considerations
|
|
||||||
|
|
||||||
4. **Documentation and Standards**:
|
|
||||||
- Verify that code includes appropriate comments and documentation
|
|
||||||
- Check that file headers, function documentation, and inline comments are present and accurate
|
|
||||||
- Ensure adherence to project-specific coding standards and conventions
|
|
||||||
|
|
||||||
5. **Issue Identification and Recommendations**:
|
|
||||||
- Clearly categorize issues as: Critical (must fix), Important (should fix), or Suggestions (nice to have)
|
|
||||||
- For each issue, provide specific examples and actionable recommendations
|
|
||||||
- When you identify plan deviations, explain whether they're problematic or beneficial
|
|
||||||
- Suggest specific improvements with code examples when helpful
|
|
||||||
|
|
||||||
6. **Communication Protocol**:
|
|
||||||
- If you find significant deviations from the plan, ask the coding agent to review and confirm the changes
|
|
||||||
- If you identify issues with the original plan itself, recommend plan updates
|
|
||||||
- For implementation problems, provide clear guidance on fixes needed
|
|
||||||
- Always acknowledge what was done well before highlighting issues
|
|
||||||
|
|
||||||
Your output should be structured, actionable, and focused on helping maintain high code quality while ensuring project goals are met. Be thorough but concise, and always provide constructive feedback that helps improve both the current implementation and future development practices.
|
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
#
|
#
|
||||||
@@ -223,6 +224,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 +293,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 +318,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 +363,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 +400,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"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: Use when completing tasks, implementing major features, or before m
|
|||||||
|
|
||||||
# Requesting Code Review
|
# Requesting Code Review
|
||||||
|
|
||||||
Dispatch superpowers:code-reviewer subagent to catch issues before they cascade. The reviewer gets precisely crafted context for evaluation — never your session's history. This keeps the reviewer focused on the work product, not your thought process, and preserves your own context for continued work.
|
Dispatch a code reviewer subagent to catch issues before they cascade. The reviewer gets precisely crafted context for evaluation — never your session's history. This keeps the reviewer focused on the work product, not your thought process, and preserves your own context for continued work.
|
||||||
|
|
||||||
**Core principle:** Review early, review often.
|
**Core principle:** Review early, review often.
|
||||||
|
|
||||||
@@ -29,16 +29,15 @@ BASE_SHA=$(git rev-parse HEAD~1) # or origin/main
|
|||||||
HEAD_SHA=$(git rev-parse HEAD)
|
HEAD_SHA=$(git rev-parse HEAD)
|
||||||
```
|
```
|
||||||
|
|
||||||
**2. Dispatch code-reviewer subagent:**
|
**2. Dispatch code reviewer subagent:**
|
||||||
|
|
||||||
Use Task tool with superpowers:code-reviewer type, fill template at `code-reviewer.md`
|
Use Task tool with `general-purpose` type, fill template at `code-reviewer.md`
|
||||||
|
|
||||||
**Placeholders:**
|
**Placeholders:**
|
||||||
- `{WHAT_WAS_IMPLEMENTED}` - What you just built
|
- `{DESCRIPTION}` - Brief summary of what you built
|
||||||
- `{PLAN_OR_REQUIREMENTS}` - What it should do
|
- `{PLAN_OR_REQUIREMENTS}` - What it should do
|
||||||
- `{BASE_SHA}` - Starting commit
|
- `{BASE_SHA}` - Starting commit
|
||||||
- `{HEAD_SHA}` - Ending commit
|
- `{HEAD_SHA}` - Ending commit
|
||||||
- `{DESCRIPTION}` - Brief summary
|
|
||||||
|
|
||||||
**3. Act on feedback:**
|
**3. Act on feedback:**
|
||||||
- Fix Critical issues immediately
|
- Fix Critical issues immediately
|
||||||
@@ -56,12 +55,11 @@ You: Let me request code review before proceeding.
|
|||||||
BASE_SHA=$(git log --oneline | grep "Task 1" | head -1 | awk '{print $1}')
|
BASE_SHA=$(git log --oneline | grep "Task 1" | head -1 | awk '{print $1}')
|
||||||
HEAD_SHA=$(git rev-parse HEAD)
|
HEAD_SHA=$(git rev-parse HEAD)
|
||||||
|
|
||||||
[Dispatch superpowers:code-reviewer subagent]
|
[Dispatch code reviewer subagent]
|
||||||
WHAT_WAS_IMPLEMENTED: Verification and repair functions for conversation index
|
DESCRIPTION: Added verifyIndex() and repairIndex() with 4 issue types
|
||||||
PLAN_OR_REQUIREMENTS: Task 2 from docs/superpowers/plans/deployment-plan.md
|
PLAN_OR_REQUIREMENTS: Task 2 from docs/superpowers/plans/deployment-plan.md
|
||||||
BASE_SHA: a7981ec
|
BASE_SHA: a7981ec
|
||||||
HEAD_SHA: 3df7661
|
HEAD_SHA: 3df7661
|
||||||
DESCRIPTION: Added verifyIndex() and repairIndex() with 4 issue types
|
|
||||||
|
|
||||||
[Subagent returns]:
|
[Subagent returns]:
|
||||||
Strengths: Clean architecture, real tests
|
Strengths: Clean architecture, real tests
|
||||||
|
|||||||
@@ -1,111 +1,133 @@
|
|||||||
# Code Review Agent
|
# Code Reviewer Prompt Template
|
||||||
|
|
||||||
You are reviewing code changes for production readiness.
|
Use this template when dispatching a code reviewer subagent.
|
||||||
|
|
||||||
**Your task:**
|
**Purpose:** Review completed work against requirements and code quality standards before it cascades into more work.
|
||||||
1. Review {WHAT_WAS_IMPLEMENTED}
|
|
||||||
2. Compare against {PLAN_OR_REQUIREMENTS}
|
|
||||||
3. Check code quality, architecture, testing
|
|
||||||
4. Categorize issues by severity
|
|
||||||
5. Assess production readiness
|
|
||||||
|
|
||||||
## What Was Implemented
|
```
|
||||||
|
Task tool (general-purpose):
|
||||||
|
description: "Review code changes"
|
||||||
|
prompt: |
|
||||||
|
You are a Senior Code Reviewer with expertise in software architecture,
|
||||||
|
design patterns, and best practices. Your job is to review completed work
|
||||||
|
against its plan or requirements and identify issues before they cascade.
|
||||||
|
|
||||||
{DESCRIPTION}
|
## What Was Implemented
|
||||||
|
|
||||||
## Requirements/Plan
|
{DESCRIPTION}
|
||||||
|
|
||||||
{PLAN_REFERENCE}
|
## Requirements / Plan
|
||||||
|
|
||||||
## Git Range to Review
|
{PLAN_OR_REQUIREMENTS}
|
||||||
|
|
||||||
**Base:** {BASE_SHA}
|
## Git Range to Review
|
||||||
**Head:** {HEAD_SHA}
|
|
||||||
|
|
||||||
```bash
|
**Base:** {BASE_SHA}
|
||||||
git diff --stat {BASE_SHA}..{HEAD_SHA}
|
**Head:** {HEAD_SHA}
|
||||||
git diff {BASE_SHA}..{HEAD_SHA}
|
|
||||||
|
```bash
|
||||||
|
git diff --stat {BASE_SHA}..{HEAD_SHA}
|
||||||
|
git diff {BASE_SHA}..{HEAD_SHA}
|
||||||
|
```
|
||||||
|
|
||||||
|
## What to Check
|
||||||
|
|
||||||
|
**Plan alignment:**
|
||||||
|
- Does the implementation match the plan / requirements?
|
||||||
|
- Are deviations justified improvements, or problematic departures?
|
||||||
|
- Is all planned functionality present?
|
||||||
|
|
||||||
|
**Code quality:**
|
||||||
|
- Clean separation of concerns?
|
||||||
|
- Proper error handling?
|
||||||
|
- Type safety where applicable?
|
||||||
|
- DRY without premature abstraction?
|
||||||
|
- Edge cases handled?
|
||||||
|
|
||||||
|
**Architecture:**
|
||||||
|
- Sound design decisions?
|
||||||
|
- Reasonable scalability and performance?
|
||||||
|
- Security concerns?
|
||||||
|
- Integrates cleanly with surrounding code?
|
||||||
|
|
||||||
|
**Testing:**
|
||||||
|
- Tests verify real behavior, not mocks?
|
||||||
|
- Edge cases covered?
|
||||||
|
- Integration tests where they matter?
|
||||||
|
- All tests passing?
|
||||||
|
|
||||||
|
**Production readiness:**
|
||||||
|
- Migration strategy if schema changed?
|
||||||
|
- Backward compatibility considered?
|
||||||
|
- Documentation complete?
|
||||||
|
- No obvious bugs?
|
||||||
|
|
||||||
|
## Calibration
|
||||||
|
|
||||||
|
Categorize issues by actual severity. Not everything is Critical.
|
||||||
|
Acknowledge what was done well before listing issues — accurate praise
|
||||||
|
helps the implementer trust the rest of the feedback.
|
||||||
|
|
||||||
|
If you find significant deviations from the plan, flag them specifically
|
||||||
|
so the implementer can confirm whether the deviation was intentional.
|
||||||
|
If you find issues with the plan itself rather than the implementation,
|
||||||
|
say so.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
### Strengths
|
||||||
|
[What's well done? Be specific.]
|
||||||
|
|
||||||
|
### Issues
|
||||||
|
|
||||||
|
#### Critical (Must Fix)
|
||||||
|
[Bugs, security issues, data loss risks, broken functionality]
|
||||||
|
|
||||||
|
#### Important (Should Fix)
|
||||||
|
[Architecture problems, missing features, poor error handling, test gaps]
|
||||||
|
|
||||||
|
#### Minor (Nice to Have)
|
||||||
|
[Code style, optimization opportunities, documentation polish]
|
||||||
|
|
||||||
|
For each issue:
|
||||||
|
- File:line reference
|
||||||
|
- What's wrong
|
||||||
|
- Why it matters
|
||||||
|
- How to fix (if not obvious)
|
||||||
|
|
||||||
|
### Recommendations
|
||||||
|
[Improvements for code quality, architecture, or process]
|
||||||
|
|
||||||
|
### Assessment
|
||||||
|
|
||||||
|
**Ready to merge?** [Yes | No | With fixes]
|
||||||
|
|
||||||
|
**Reasoning:** [1-2 sentence technical assessment]
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
**DO:**
|
||||||
|
- Categorize by actual severity
|
||||||
|
- Be specific (file:line, not vague)
|
||||||
|
- Explain WHY each issue matters
|
||||||
|
- Acknowledge strengths
|
||||||
|
- Give a clear verdict
|
||||||
|
|
||||||
|
**DON'T:**
|
||||||
|
- Say "looks good" without checking
|
||||||
|
- Mark nitpicks as Critical
|
||||||
|
- Give feedback on code you didn't actually read
|
||||||
|
- Be vague ("improve error handling")
|
||||||
|
- Avoid giving a clear verdict
|
||||||
```
|
```
|
||||||
|
|
||||||
## Review Checklist
|
**Placeholders:**
|
||||||
|
- `{DESCRIPTION}` — brief summary of what was built
|
||||||
|
- `{PLAN_OR_REQUIREMENTS}` — what it should do (plan file path, task text, or requirements)
|
||||||
|
- `{BASE_SHA}` — starting commit
|
||||||
|
- `{HEAD_SHA}` — ending commit
|
||||||
|
|
||||||
**Code Quality:**
|
**Reviewer returns:** Strengths, Issues (Critical / Important / Minor), Recommendations, Assessment
|
||||||
- Clean separation of concerns?
|
|
||||||
- Proper error handling?
|
|
||||||
- Type safety (if applicable)?
|
|
||||||
- DRY principle followed?
|
|
||||||
- Edge cases handled?
|
|
||||||
|
|
||||||
**Architecture:**
|
|
||||||
- Sound design decisions?
|
|
||||||
- Scalability considerations?
|
|
||||||
- Performance implications?
|
|
||||||
- Security concerns?
|
|
||||||
|
|
||||||
**Testing:**
|
|
||||||
- Tests actually test logic (not mocks)?
|
|
||||||
- Edge cases covered?
|
|
||||||
- Integration tests where needed?
|
|
||||||
- All tests passing?
|
|
||||||
|
|
||||||
**Requirements:**
|
|
||||||
- All plan requirements met?
|
|
||||||
- Implementation matches spec?
|
|
||||||
- No scope creep?
|
|
||||||
- Breaking changes documented?
|
|
||||||
|
|
||||||
**Production Readiness:**
|
|
||||||
- Migration strategy (if schema changes)?
|
|
||||||
- Backward compatibility considered?
|
|
||||||
- Documentation complete?
|
|
||||||
- No obvious bugs?
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
### Strengths
|
|
||||||
[What's well done? Be specific.]
|
|
||||||
|
|
||||||
### Issues
|
|
||||||
|
|
||||||
#### Critical (Must Fix)
|
|
||||||
[Bugs, security issues, data loss risks, broken functionality]
|
|
||||||
|
|
||||||
#### Important (Should Fix)
|
|
||||||
[Architecture problems, missing features, poor error handling, test gaps]
|
|
||||||
|
|
||||||
#### Minor (Nice to Have)
|
|
||||||
[Code style, optimization opportunities, documentation improvements]
|
|
||||||
|
|
||||||
**For each issue:**
|
|
||||||
- File:line reference
|
|
||||||
- What's wrong
|
|
||||||
- Why it matters
|
|
||||||
- How to fix (if not obvious)
|
|
||||||
|
|
||||||
### Recommendations
|
|
||||||
[Improvements for code quality, architecture, or process]
|
|
||||||
|
|
||||||
### Assessment
|
|
||||||
|
|
||||||
**Ready to merge?** [Yes/No/With fixes]
|
|
||||||
|
|
||||||
**Reasoning:** [Technical assessment in 1-2 sentences]
|
|
||||||
|
|
||||||
## Critical Rules
|
|
||||||
|
|
||||||
**DO:**
|
|
||||||
- Categorize by actual severity (not everything is Critical)
|
|
||||||
- Be specific (file:line, not vague)
|
|
||||||
- Explain WHY issues matter
|
|
||||||
- Acknowledge strengths
|
|
||||||
- Give clear verdict
|
|
||||||
|
|
||||||
**DON'T:**
|
|
||||||
- Say "looks good" without checking
|
|
||||||
- Mark nitpicks as Critical
|
|
||||||
- Give feedback on code you didn't review
|
|
||||||
- Be vague ("improve error handling")
|
|
||||||
- Avoid giving a clear verdict
|
|
||||||
|
|
||||||
## Example Output
|
## Example Output
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ Use this template when dispatching a code quality reviewer subagent.
|
|||||||
**Only dispatch after spec compliance review passes.**
|
**Only dispatch after spec compliance review passes.**
|
||||||
|
|
||||||
```
|
```
|
||||||
Task tool (superpowers:code-reviewer):
|
Task tool (general-purpose):
|
||||||
Use template at requesting-code-review/code-reviewer.md
|
Use template at requesting-code-review/code-reviewer.md
|
||||||
|
|
||||||
WHAT_WAS_IMPLEMENTED: [from implementer's report]
|
DESCRIPTION: [task summary, from implementer's report]
|
||||||
PLAN_OR_REQUIREMENTS: Task N from [plan-file]
|
PLAN_OR_REQUIREMENTS: Task N from [plan-file]
|
||||||
BASE_SHA: [commit before task]
|
BASE_SHA: [commit before task]
|
||||||
HEAD_SHA: [current commit]
|
HEAD_SHA: [current commit]
|
||||||
DESCRIPTION: [task summary]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**In addition to standard code quality concerns, the reviewer should check:**
|
**In addition to standard code quality concerns, the reviewer should check:**
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ Skills use Claude Code tool names. When you encounter these in a skill, use your
|
|||||||
|
|
||||||
| Skill references | Codex equivalent |
|
| Skill references | Codex equivalent |
|
||||||
|-----------------|------------------|
|
|-----------------|------------------|
|
||||||
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
|
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Subagent dispatch requires multi-agent support](#subagent-dispatch-requires-multi-agent-support)) |
|
||||||
| 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,53 +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`.
|
||||||
|
|
||||||
## Named agent dispatch
|
Legacy note: Codex builds before `rust-v0.115.0` exposed spawned-agent
|
||||||
|
waiting as `wait`. Current Codex uses `wait_agent` for spawned agents. The
|
||||||
Claude Code skills reference named agent types like `superpowers:code-reviewer`.
|
`wait` name now belongs to code-mode `exec/wait`, which resumes a yielded exec
|
||||||
Codex does not have a named agent registry — `spawn_agent` creates generic agents
|
cell by `cell_id`; it is not the spawned-agent result tool.
|
||||||
from built-in roles (`default`, `explorer`, `worker`).
|
|
||||||
|
|
||||||
When a skill says to dispatch a named agent type:
|
|
||||||
|
|
||||||
1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
|
|
||||||
local prompt template like `code-quality-reviewer-prompt.md`)
|
|
||||||
2. Read the prompt content
|
|
||||||
3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
|
|
||||||
4. Spawn a `worker` agent with the filled content as the `message`
|
|
||||||
|
|
||||||
| Skill instruction | Codex equivalent |
|
|
||||||
|-------------------|------------------|
|
|
||||||
| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
|
|
||||||
| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
|
|
||||||
|
|
||||||
### Message framing
|
|
||||||
|
|
||||||
The `message` parameter is user-level input, not a system prompt. Structure it
|
|
||||||
for maximum instruction adherence:
|
|
||||||
|
|
||||||
```
|
|
||||||
Your task is to perform the following. Follow the instructions below exactly.
|
|
||||||
|
|
||||||
<agent-instructions>
|
|
||||||
[filled prompt content from the agent's .md file]
|
|
||||||
</agent-instructions>
|
|
||||||
|
|
||||||
Execute this now. Output ONLY the structured response following the format
|
|
||||||
specified in the instructions above.
|
|
||||||
```
|
|
||||||
|
|
||||||
- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
|
|
||||||
- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
|
|
||||||
- End with an explicit execution directive to prevent summarization of the instructions
|
|
||||||
|
|
||||||
### When this workaround can be removed
|
|
||||||
|
|
||||||
This approach compensates for Codex's plugin system not yet supporting an `agents`
|
|
||||||
field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
|
|
||||||
plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
|
|
||||||
skills can dispatch named agent types directly.
|
|
||||||
|
|
||||||
## Environment Detection
|
## Environment Detection
|
||||||
|
|
||||||
|
|||||||
@@ -12,23 +12,13 @@ Skills use Claude Code tool names. When you encounter these in a skill, use your
|
|||||||
| `Glob` (search files by name) | `glob` |
|
| `Glob` (search files by name) | `glob` |
|
||||||
| `Skill` tool (invoke a skill) | `skill` |
|
| `Skill` tool (invoke a skill) | `skill` |
|
||||||
| `WebFetch` | `web_fetch` |
|
| `WebFetch` | `web_fetch` |
|
||||||
| `Task` tool (dispatch subagent) | `task` (see [Agent types](#agent-types)) |
|
| `Task` tool (dispatch subagent) | `task` with `agent_type: "general-purpose"` or `"explore"` |
|
||||||
| Multiple `Task` calls (parallel) | Multiple `task` calls |
|
| Multiple `Task` calls (parallel) | Multiple `task` calls |
|
||||||
| Task status/output | `read_agent`, `list_agents` |
|
| Task status/output | `read_agent`, `list_agents` |
|
||||||
| `TodoWrite` (task tracking) | `sql` with built-in `todos` table |
|
| `TodoWrite` (task tracking) | `sql` with built-in `todos` table |
|
||||||
| `WebSearch` | No equivalent — use `web_fetch` with a search engine URL |
|
| `WebSearch` | No equivalent — use `web_fetch` with a search engine URL |
|
||||||
| `EnterPlanMode` / `ExitPlanMode` | No equivalent — stay in the main session |
|
| `EnterPlanMode` / `ExitPlanMode` | No equivalent — stay in the main session |
|
||||||
|
|
||||||
## Agent types
|
|
||||||
|
|
||||||
Copilot CLI's `task` tool accepts an `agent_type` parameter:
|
|
||||||
|
|
||||||
| Claude Code agent | Copilot CLI equivalent |
|
|
||||||
|-------------------|----------------------|
|
|
||||||
| `general-purpose` | `"general-purpose"` |
|
|
||||||
| `Explore` | `"explore"` |
|
|
||||||
| Named plugin agents (e.g. `superpowers:code-reviewer`) | Discovered automatically from installed plugins |
|
|
||||||
|
|
||||||
## Async shell sessions
|
## Async shell sessions
|
||||||
|
|
||||||
Copilot CLI supports persistent async shell sessions, which have no direct Claude Code equivalent:
|
Copilot CLI supports persistent async shell sessions, which have no direct Claude Code equivalent:
|
||||||
|
|||||||
@@ -115,6 +115,18 @@ Full workflow execution test (~10-30 minutes):
|
|||||||
- Subagents follow the skill correctly
|
- Subagents follow the skill correctly
|
||||||
- Final code is functional and tested
|
- Final code is functional and tested
|
||||||
|
|
||||||
|
#### test-requesting-code-review.sh
|
||||||
|
Behavioral test for the code reviewer subagent (~5 minutes):
|
||||||
|
- Builds a tiny project with a baseline commit
|
||||||
|
- Adds a second commit that plants two real bugs (SQL injection, plaintext password handling)
|
||||||
|
- Dispatches the code reviewer via the requesting-code-review skill
|
||||||
|
- Verifies the reviewer flags the planted bugs at Critical/Important severity and refuses to approve
|
||||||
|
|
||||||
|
**What it tests:**
|
||||||
|
- The skill actually dispatches a working code reviewer subagent
|
||||||
|
- The reviewer template produces reviewers that catch obvious security bugs
|
||||||
|
- The reviewer is not sycophantic — it does not approve a diff with planted Critical issues
|
||||||
|
|
||||||
## Adding New Tests
|
## Adding New Tests
|
||||||
|
|
||||||
1. Create new test file: `test-<skill-name>.sh`
|
1. Create new test file: `test-<skill-name>.sh`
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ tests=(
|
|||||||
# Integration tests (slow, full execution)
|
# Integration tests (slow, full execution)
|
||||||
integration_tests=(
|
integration_tests=(
|
||||||
"test-subagent-driven-development-integration.sh"
|
"test-subagent-driven-development-integration.sh"
|
||||||
|
"test-requesting-code-review.sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add integration tests if requested
|
# Add integration tests if requested
|
||||||
|
|||||||
214
tests/claude-code/test-requesting-code-review.sh
Executable file
214
tests/claude-code/test-requesting-code-review.sh
Executable file
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Integration Test: requesting-code-review skill
|
||||||
|
# Verifies the code reviewer dispatched via the skill catches a planted bug
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
source "$SCRIPT_DIR/test-helpers.sh"
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo " Integration Test: requesting-code-review"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
echo "This test verifies the code reviewer subagent by:"
|
||||||
|
echo " 1. Setting up a tiny project with a baseline commit"
|
||||||
|
echo " 2. Adding a second commit that plants an obvious bug"
|
||||||
|
echo " 3. Dispatching the code reviewer via the requesting-code-review skill"
|
||||||
|
echo " 4. Verifying the reviewer flags the planted bug as Critical/Important"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
TEST_PROJECT=$(create_test_project)
|
||||||
|
echo "Test project: $TEST_PROJECT"
|
||||||
|
trap "cleanup_test_project $TEST_PROJECT" EXIT
|
||||||
|
|
||||||
|
cd "$TEST_PROJECT"
|
||||||
|
|
||||||
|
# Baseline: a small "safe" implementation
|
||||||
|
mkdir -p src
|
||||||
|
cat > src/db.js <<'EOF'
|
||||||
|
import { Database } from "./database-driver.js";
|
||||||
|
|
||||||
|
const db = new Database();
|
||||||
|
|
||||||
|
export async function findUserByEmail(email) {
|
||||||
|
if (typeof email !== "string" || !email) {
|
||||||
|
throw new Error("email required");
|
||||||
|
}
|
||||||
|
return db.query(
|
||||||
|
"SELECT id, email, created_at FROM users WHERE email = ?",
|
||||||
|
[email],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > package.json <<'EOF'
|
||||||
|
{ "name": "test-codereview", "version": "1.0.0", "type": "module" }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git init --quiet
|
||||||
|
git config user.email "test@test.com"
|
||||||
|
git config user.name "Test User"
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial: parameterized findUserByEmail" --quiet
|
||||||
|
BASE_SHA=$(git rev-parse HEAD)
|
||||||
|
|
||||||
|
# Second commit: plant two real bugs
|
||||||
|
# 1. SQL injection — switch from parameterized to string concatenation
|
||||||
|
# 2. Logs the user's password hash on every successful login
|
||||||
|
cat > src/db.js <<'EOF'
|
||||||
|
import { Database } from "./database-driver.js";
|
||||||
|
|
||||||
|
const db = new Database();
|
||||||
|
|
||||||
|
export async function findUserByEmail(email) {
|
||||||
|
return db.query(
|
||||||
|
"SELECT id, email, password_hash, created_at FROM users WHERE email = '" + email + "'",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function login(email, password) {
|
||||||
|
const user = await findUserByEmail(email);
|
||||||
|
if (user && user.password_hash === hash(password)) {
|
||||||
|
console.log("login success", { email, password_hash: user.password_hash });
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash(s) { return s; }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git add .
|
||||||
|
git commit -m "Refactor user lookup, add login" --quiet
|
||||||
|
HEAD_SHA=$(git rev-parse HEAD)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Planted bugs in $BASE_SHA..$HEAD_SHA:"
|
||||||
|
echo " - SQL injection (string concat instead of parameterized query)"
|
||||||
|
echo " - Password hash logged in plaintext on every successful login"
|
||||||
|
echo " - hash() is the identity function (passwords stored & compared in plaintext)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
OUTPUT_FILE="$TEST_PROJECT/claude-output.txt"
|
||||||
|
|
||||||
|
PROMPT="I just finished a refactor. The change is between commits $BASE_SHA and $HEAD_SHA on the current branch.
|
||||||
|
|
||||||
|
Use the superpowers:requesting-code-review skill to review these changes before I merge. Follow the skill exactly: dispatch the code reviewer subagent with the template, give the subagent the SHA range, and report back what it found.
|
||||||
|
|
||||||
|
Print the reviewer's full output."
|
||||||
|
|
||||||
|
# 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 "================================================================================"
|
||||||
|
cd "$TEST_PROJECT" && timeout 600 claude -p "$PROMPT" \
|
||||||
|
--plugin-dir "$PLUGIN_DIR" \
|
||||||
|
--permission-mode bypassPermissions 2>&1 | tee "$OUTPUT_FILE" || {
|
||||||
|
echo ""
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "EXECUTION FAILED (exit code: $?)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "================================================================================"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Analyzing reviewer output..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Find the session transcript. Because we ran claude from $TEST_PROJECT (a
|
||||||
|
# unique tmp dir), its sessions live in their own ~/.claude/projects/ folder.
|
||||||
|
# Resolve the real path (macOS mktemp returns /var/... but claude normalizes
|
||||||
|
# it to /private/var/...) and replicate claude's normalization (every
|
||||||
|
# non-alphanumeric char becomes `-`).
|
||||||
|
TEST_PROJECT_REAL=$(cd "$TEST_PROJECT" && pwd -P)
|
||||||
|
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)
|
||||||
|
|
||||||
|
FAILED=0
|
||||||
|
|
||||||
|
echo "=== Verification Tests ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 1: Skill was actually invoked, and a subagent was actually dispatched
|
||||||
|
echo "Test 1: requesting-code-review skill invoked + reviewer subagent dispatched..."
|
||||||
|
if [ -z "$SESSION_FILE" ] || [ ! -f "$SESSION_FILE" ]; then
|
||||||
|
echo " [FAIL] Could not locate session transcript in $SESSION_DIR"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
elif ! grep -q '"skill":"superpowers:requesting-code-review"' "$SESSION_FILE"; then
|
||||||
|
echo " [FAIL] requesting-code-review skill was not invoked"
|
||||||
|
echo " Session: $SESSION_FILE"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
elif ! grep -q '"name":"Agent"' "$SESSION_FILE"; then
|
||||||
|
echo " [FAIL] Skill ran but no subagent was dispatched"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
else
|
||||||
|
echo " [PASS] Skill invoked and subagent dispatched"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 2: Reviewer caught the SQL injection
|
||||||
|
echo "Test 2: SQL injection flagged..."
|
||||||
|
if grep -qiE "sql injection|injection|string concat|parameterize|prepared statement|sanitiz" "$OUTPUT_FILE"; then
|
||||||
|
echo " [PASS] Reviewer flagged the SQL injection vector"
|
||||||
|
else
|
||||||
|
echo " [FAIL] Reviewer missed the SQL injection — most obvious planted bug"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 3: Reviewer caught the credential / password issue (either logging or no real hashing)
|
||||||
|
echo "Test 3: Credential handling issue flagged..."
|
||||||
|
if grep -qiE "password|credential|secret|plaintext|log.*hash|hash.*log|sensitive" "$OUTPUT_FILE"; then
|
||||||
|
echo " [PASS] Reviewer flagged a credential / password handling issue"
|
||||||
|
else
|
||||||
|
echo " [FAIL] Reviewer missed the password/credential issues"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 4: Reviewer marked at least one issue as Critical or Important (not just Minor)
|
||||||
|
echo "Test 4: Severity classification..."
|
||||||
|
if grep -qiE "critical|important|severe|high.*risk|security" "$OUTPUT_FILE"; then
|
||||||
|
echo " [PASS] Reviewer classified findings at Critical/Important severity"
|
||||||
|
else
|
||||||
|
echo " [FAIL] Reviewer did not classify findings as Critical or Important"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Test 5: Reviewer did NOT approve the diff for merge
|
||||||
|
echo "Test 5: Reviewer verdict..."
|
||||||
|
# A correct reviewer says No or "With fixes". A broken/sycophantic reviewer says Yes/Ready.
|
||||||
|
if grep -qiE "ready to merge.*yes|approved.*for merge|^\s*yes\s*$|safe to merge" "$OUTPUT_FILE" \
|
||||||
|
&& ! grep -qiE "ready to merge.*no|with fixes|do not merge|not ready|block.*merge" "$OUTPUT_FILE"; then
|
||||||
|
echo " [FAIL] Reviewer approved a diff with planted Critical bugs"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
else
|
||||||
|
echo " [PASS] Reviewer did not approve the diff"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo " Test Summary"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $FAILED -eq 0 ]; then
|
||||||
|
echo "STATUS: PASSED"
|
||||||
|
echo "The code reviewer correctly:"
|
||||||
|
echo " ✓ Was dispatched via the requesting-code-review skill"
|
||||||
|
echo " ✓ Flagged the SQL injection"
|
||||||
|
echo " ✓ Flagged the credential handling issues"
|
||||||
|
echo " ✓ Classified findings at Critical/Important severity"
|
||||||
|
echo " ✓ Did not approve the diff for merge"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "STATUS: FAILED"
|
||||||
|
echo "Failed $FAILED verification tests"
|
||||||
|
echo ""
|
||||||
|
echo "Output saved to: $OUTPUT_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -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..."
|
||||||
|
|||||||
@@ -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