mirror of
https://github.com/obra/superpowers.git
synced 2026-04-21 00:49:06 +08:00
Compare commits
14 Commits
v4.0.2
...
fix/openco
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b6cef98ac | ||
|
|
03087b13b8 | ||
|
|
493ac18dfe | ||
|
|
35d4fbcd0b | ||
|
|
19c70afc99 | ||
|
|
405a025eea | ||
|
|
36fcd57626 | ||
|
|
3964d18670 | ||
|
|
a01a135fe1 | ||
|
|
ac471e69c2 | ||
|
|
a08f088968 | ||
|
|
b9e16498b9 | ||
|
|
f6d50c74b2 | ||
|
|
3dac35e0b3 |
@@ -9,7 +9,7 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "4.0.2",
|
||||
"version": "4.0.3",
|
||||
"source": "./",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "4.0.2",
|
||||
"version": "4.1.1",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
|
||||
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Ensure shell scripts always have LF line endings
|
||||
*.sh text eol=lf
|
||||
|
||||
# Ensure the polyglot wrapper keeps LF (it's parsed by both cmd and bash)
|
||||
*.cmd text eol=lf
|
||||
|
||||
# Common text files
|
||||
*.md text eol=lf
|
||||
*.json text eol=lf
|
||||
*.js text eol=lf
|
||||
*.mjs text eol=lf
|
||||
*.ts text eol=lf
|
||||
|
||||
# Explicitly mark binary files
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.gif binary
|
||||
@@ -3,15 +3,13 @@
|
||||
## Prerequisites
|
||||
|
||||
- [OpenCode.ai](https://opencode.ai) installed
|
||||
- Node.js installed
|
||||
- Git installed
|
||||
|
||||
## Installation Steps
|
||||
|
||||
### 1. Install Superpowers
|
||||
### 1. Clone Superpowers
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/superpowers
|
||||
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
|
||||
```
|
||||
|
||||
@@ -20,32 +18,43 @@ git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
|
||||
Create a symlink so OpenCode discovers the plugin:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/plugin
|
||||
ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js ~/.config/opencode/plugin/superpowers.js
|
||||
mkdir -p ~/.config/opencode/plugins
|
||||
rm -f ~/.config/opencode/plugins/superpowers.js
|
||||
ln -s ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js ~/.config/opencode/plugins/superpowers.js
|
||||
```
|
||||
|
||||
### 3. Restart OpenCode
|
||||
### 3. Symlink Skills
|
||||
|
||||
Restart OpenCode. The plugin will automatically inject superpowers context via the chat.message hook.
|
||||
Create a symlink so OpenCode's native skill tool discovers superpowers skills:
|
||||
|
||||
You should see superpowers is active when you ask "do you have superpowers?"
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/skills
|
||||
rm -rf ~/.config/opencode/skills/superpowers
|
||||
ln -s ~/.config/opencode/superpowers/skills ~/.config/opencode/skills/superpowers
|
||||
```
|
||||
|
||||
### 4. Restart OpenCode
|
||||
|
||||
Restart OpenCode. The plugin will automatically inject superpowers context.
|
||||
|
||||
Verify by asking: "do you have superpowers?"
|
||||
|
||||
## Usage
|
||||
|
||||
### Finding Skills
|
||||
|
||||
Use the `find_skills` tool to list all available skills:
|
||||
Use OpenCode's native `skill` tool to list available skills:
|
||||
|
||||
```
|
||||
use find_skills tool
|
||||
use skill tool to list skills
|
||||
```
|
||||
|
||||
### Loading a Skill
|
||||
|
||||
Use the `use_skill` tool to load a specific skill:
|
||||
Use OpenCode's native `skill` tool to load a specific skill:
|
||||
|
||||
```
|
||||
use use_skill tool with skill_name: "superpowers:brainstorming"
|
||||
use skill tool to load superpowers/brainstorming
|
||||
```
|
||||
|
||||
### Personal Skills
|
||||
@@ -69,36 +78,11 @@ description: Use when [condition] - [what it does]
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
Personal skills override superpowers skills with the same name.
|
||||
|
||||
### Project Skills
|
||||
|
||||
Create project-specific skills in your OpenCode project:
|
||||
Create project-specific skills in `.opencode/skills/` within your project.
|
||||
|
||||
```bash
|
||||
# In your OpenCode project
|
||||
mkdir -p .opencode/skills/my-project-skill
|
||||
```
|
||||
|
||||
Create `.opencode/skills/my-project-skill/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-project-skill
|
||||
description: Use when [condition] - [what it does]
|
||||
---
|
||||
|
||||
# My Project Skill
|
||||
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
**Skill Priority:** Project skills override personal skills, which override superpowers skills.
|
||||
|
||||
**Skill Naming:**
|
||||
- `project:skill-name` - Force project skill lookup
|
||||
- `skill-name` - Searches project → personal → superpowers
|
||||
- `superpowers:skill-name` - Force superpowers skill lookup
|
||||
**Skill Priority:** Project skills > Personal skills > Superpowers skills
|
||||
|
||||
## Updating
|
||||
|
||||
@@ -111,25 +95,25 @@ git pull
|
||||
|
||||
### Plugin not loading
|
||||
|
||||
1. Check plugin file exists: `ls ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js`
|
||||
2. Check OpenCode logs for errors
|
||||
3. Verify Node.js is installed: `node --version`
|
||||
1. Check plugin symlink: `ls -l ~/.config/opencode/plugins/superpowers.js`
|
||||
2. Check source exists: `ls ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js`
|
||||
3. Check OpenCode logs for errors
|
||||
|
||||
### Skills not found
|
||||
|
||||
1. Verify skills directory exists: `ls ~/.config/opencode/superpowers/skills`
|
||||
2. Use `find_skills` tool to see what's discovered
|
||||
3. Check file structure: each skill should have a `SKILL.md` file
|
||||
1. Check skills symlink: `ls -l ~/.config/opencode/skills/superpowers`
|
||||
2. Verify it points to: `~/.config/opencode/superpowers/skills`
|
||||
3. Use `skill` tool to list what's discovered
|
||||
|
||||
### Tool mapping issues
|
||||
### Tool mapping
|
||||
|
||||
When a skill references a Claude Code tool you don't have:
|
||||
- `TodoWrite` → use `update_plan`
|
||||
- `Task` with subagents → use `@mention` syntax to invoke OpenCode subagents
|
||||
- `Skill` → use `use_skill` tool
|
||||
- File operations → use your native tools
|
||||
When skills reference Claude Code tools:
|
||||
- `TodoWrite` → `update_plan`
|
||||
- `Task` with subagents → `@mention` syntax
|
||||
- `Skill` tool → OpenCode's native `skill` tool
|
||||
- File operations → your native tools
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Report issues: https://github.com/obra/superpowers/issues
|
||||
- Documentation: https://github.com/obra/superpowers
|
||||
- Full documentation: https://github.com/obra/superpowers/blob/main/docs/README.opencode.md
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
/**
|
||||
* Superpowers plugin for OpenCode.ai
|
||||
*
|
||||
* Provides custom tools for loading and discovering skills,
|
||||
* with prompt generation for agent configuration.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { tool } from '@opencode-ai/plugin/tool';
|
||||
import * as skillsCore from '../../lib/skills-core.js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export const SuperpowersPlugin = async ({ client, directory }) => {
|
||||
const homeDir = os.homedir();
|
||||
const projectSkillsDir = path.join(directory, '.opencode/skills');
|
||||
// Derive superpowers skills dir from plugin location (works for both symlinked and local installs)
|
||||
const superpowersSkillsDir = path.resolve(__dirname, '../../skills');
|
||||
const personalSkillsDir = path.join(homeDir, '.config/opencode/skills');
|
||||
|
||||
// Helper to generate bootstrap content
|
||||
const getBootstrapContent = (compact = false) => {
|
||||
const usingSuperpowersPath = skillsCore.resolveSkillPath('using-superpowers', superpowersSkillsDir, personalSkillsDir);
|
||||
if (!usingSuperpowersPath) return null;
|
||||
|
||||
const fullContent = fs.readFileSync(usingSuperpowersPath.skillFile, 'utf8');
|
||||
const content = skillsCore.stripFrontmatter(fullContent);
|
||||
|
||||
const toolMapping = compact
|
||||
? `**Tool Mapping:** TodoWrite->update_plan, Task->@mention, Skill->use_skill
|
||||
|
||||
**Skills naming (priority order):** project: > personal > superpowers:`
|
||||
: `**Tool Mapping for OpenCode:**
|
||||
When skills reference tools you don't have, substitute OpenCode equivalents:
|
||||
- \`TodoWrite\` → \`update_plan\`
|
||||
- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention)
|
||||
- \`Skill\` tool → \`use_skill\` custom tool
|
||||
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
|
||||
|
||||
**Skills naming (priority order):**
|
||||
- Project skills: \`project:skill-name\` (in .opencode/skills/)
|
||||
- Personal skills: \`skill-name\` (in ~/.config/opencode/skills/)
|
||||
- Superpowers skills: \`superpowers:skill-name\`
|
||||
- Project skills override personal, which override superpowers when names match`;
|
||||
|
||||
return `<EXTREMELY_IMPORTANT>
|
||||
You have superpowers.
|
||||
|
||||
**IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the use_skill tool to load "using-superpowers" - that would be redundant. Use use_skill only for OTHER skills.**
|
||||
|
||||
${content}
|
||||
|
||||
${toolMapping}
|
||||
</EXTREMELY_IMPORTANT>`;
|
||||
};
|
||||
|
||||
// Helper to inject bootstrap via session.prompt
|
||||
const injectBootstrap = async (sessionID, compact = false) => {
|
||||
const bootstrapContent = getBootstrapContent(compact);
|
||||
if (!bootstrapContent) return false;
|
||||
|
||||
try {
|
||||
await client.session.prompt({
|
||||
path: { id: sessionID },
|
||||
body: {
|
||||
noReply: true,
|
||||
parts: [{ type: "text", text: bootstrapContent, synthetic: true }]
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
tool: {
|
||||
use_skill: tool({
|
||||
description: 'Load and read a specific skill to guide your work. Skills contain proven workflows, mandatory processes, and expert techniques.',
|
||||
args: {
|
||||
skill_name: tool.schema.string().describe('Name of the skill to load (e.g., "superpowers:brainstorming", "my-custom-skill", or "project:my-skill")')
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
const { skill_name } = args;
|
||||
|
||||
// Resolve with priority: project > personal > superpowers
|
||||
// Check for project: prefix first
|
||||
const forceProject = skill_name.startsWith('project:');
|
||||
const actualSkillName = forceProject ? skill_name.replace(/^project:/, '') : skill_name;
|
||||
|
||||
let resolved = null;
|
||||
|
||||
// Try project skills first (if project: prefix or no prefix)
|
||||
if (forceProject || !skill_name.startsWith('superpowers:')) {
|
||||
const projectPath = path.join(projectSkillsDir, actualSkillName);
|
||||
const projectSkillFile = path.join(projectPath, 'SKILL.md');
|
||||
if (fs.existsSync(projectSkillFile)) {
|
||||
resolved = {
|
||||
skillFile: projectSkillFile,
|
||||
sourceType: 'project',
|
||||
skillPath: actualSkillName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to personal/superpowers resolution
|
||||
if (!resolved && !forceProject) {
|
||||
resolved = skillsCore.resolveSkillPath(skill_name, superpowersSkillsDir, personalSkillsDir);
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
return `Error: Skill "${skill_name}" not found.\n\nRun find_skills to see available skills.`;
|
||||
}
|
||||
|
||||
const fullContent = fs.readFileSync(resolved.skillFile, 'utf8');
|
||||
const { name, description } = skillsCore.extractFrontmatter(resolved.skillFile);
|
||||
const content = skillsCore.stripFrontmatter(fullContent);
|
||||
const skillDirectory = path.dirname(resolved.skillFile);
|
||||
|
||||
const skillHeader = `# ${name || skill_name}
|
||||
# ${description || ''}
|
||||
# Supporting tools and docs are in ${skillDirectory}
|
||||
# ============================================`;
|
||||
|
||||
// Insert as user message with noReply for persistence across compaction
|
||||
try {
|
||||
await client.session.prompt({
|
||||
path: { id: context.sessionID },
|
||||
body: {
|
||||
noReply: true,
|
||||
parts: [
|
||||
{ type: "text", text: `Loading skill: ${name || skill_name}`, synthetic: true },
|
||||
{ type: "text", text: `${skillHeader}\n\n${content}`, synthetic: true }
|
||||
]
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
// Fallback: return content directly if message insertion fails
|
||||
return `${skillHeader}\n\n${content}`;
|
||||
}
|
||||
|
||||
return `Launching skill: ${name || skill_name}`;
|
||||
}
|
||||
}),
|
||||
find_skills: tool({
|
||||
description: 'List all available skills in the project, personal, and superpowers skill libraries.',
|
||||
args: {},
|
||||
execute: async (args, context) => {
|
||||
const projectSkills = skillsCore.findSkillsInDir(projectSkillsDir, 'project', 3);
|
||||
const personalSkills = skillsCore.findSkillsInDir(personalSkillsDir, 'personal', 3);
|
||||
const superpowersSkills = skillsCore.findSkillsInDir(superpowersSkillsDir, 'superpowers', 3);
|
||||
|
||||
// Priority: project > personal > superpowers
|
||||
const allSkills = [...projectSkills, ...personalSkills, ...superpowersSkills];
|
||||
|
||||
if (allSkills.length === 0) {
|
||||
return 'No skills found. Install superpowers skills to ~/.config/opencode/superpowers/skills/ or add project skills to .opencode/skills/';
|
||||
}
|
||||
|
||||
let output = 'Available skills:\n\n';
|
||||
|
||||
for (const skill of allSkills) {
|
||||
let namespace;
|
||||
switch (skill.sourceType) {
|
||||
case 'project':
|
||||
namespace = 'project:';
|
||||
break;
|
||||
case 'personal':
|
||||
namespace = '';
|
||||
break;
|
||||
default:
|
||||
namespace = 'superpowers:';
|
||||
}
|
||||
const skillName = skill.name || path.basename(skill.path);
|
||||
|
||||
output += `${namespace}${skillName}\n`;
|
||||
if (skill.description) {
|
||||
output += ` ${skill.description}\n`;
|
||||
}
|
||||
output += ` Directory: ${skill.path}\n\n`;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
})
|
||||
},
|
||||
event: async ({ event }) => {
|
||||
// Extract sessionID from various event structures
|
||||
const getSessionID = () => {
|
||||
return event.properties?.info?.id ||
|
||||
event.properties?.sessionID ||
|
||||
event.session?.id;
|
||||
};
|
||||
|
||||
// Inject bootstrap at session creation (before first user message)
|
||||
if (event.type === 'session.created') {
|
||||
const sessionID = getSessionID();
|
||||
if (sessionID) {
|
||||
await injectBootstrap(sessionID, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-inject bootstrap after context compaction (compact version to save tokens)
|
||||
if (event.type === 'session.compacted') {
|
||||
const sessionID = getSessionID();
|
||||
if (sessionID) {
|
||||
await injectBootstrap(sessionID, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
95
.opencode/plugins/superpowers.js
Normal file
95
.opencode/plugins/superpowers.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Superpowers plugin for OpenCode.ai
|
||||
*
|
||||
* Injects superpowers bootstrap context via system prompt transform.
|
||||
* Skills are discovered via OpenCode's native skill tool from symlinked directory.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Simple frontmatter extraction (avoid dependency on skills-core for bootstrap)
|
||||
const extractAndStripFrontmatter = (content) => {
|
||||
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
||||
if (!match) return { frontmatter: {}, content };
|
||||
|
||||
const frontmatterStr = match[1];
|
||||
const body = match[2];
|
||||
const frontmatter = {};
|
||||
|
||||
for (const line of frontmatterStr.split('\n')) {
|
||||
const colonIdx = line.indexOf(':');
|
||||
if (colonIdx > 0) {
|
||||
const key = line.slice(0, colonIdx).trim();
|
||||
const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
|
||||
frontmatter[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return { frontmatter, content: body };
|
||||
};
|
||||
|
||||
// Normalize a path: trim whitespace, expand ~, resolve to absolute
|
||||
const normalizePath = (p, homeDir) => {
|
||||
if (!p || typeof p !== 'string') return null;
|
||||
let normalized = p.trim();
|
||||
if (!normalized) return null;
|
||||
if (normalized.startsWith('~/')) {
|
||||
normalized = path.join(homeDir, normalized.slice(2));
|
||||
} else if (normalized === '~') {
|
||||
normalized = homeDir;
|
||||
}
|
||||
return path.resolve(normalized);
|
||||
};
|
||||
|
||||
export const SuperpowersPlugin = async ({ client, directory }) => {
|
||||
const homeDir = os.homedir();
|
||||
const superpowersSkillsDir = path.resolve(__dirname, '../../skills');
|
||||
const envConfigDir = normalizePath(process.env.OPENCODE_CONFIG_DIR, homeDir);
|
||||
const configDir = envConfigDir || path.join(homeDir, '.config/opencode');
|
||||
|
||||
// Helper to generate bootstrap content
|
||||
const getBootstrapContent = () => {
|
||||
// Try to load using-superpowers skill
|
||||
const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md');
|
||||
if (!fs.existsSync(skillPath)) return null;
|
||||
|
||||
const fullContent = fs.readFileSync(skillPath, 'utf8');
|
||||
const { content } = extractAndStripFrontmatter(fullContent);
|
||||
|
||||
const toolMapping = `**Tool Mapping for OpenCode:**
|
||||
When skills reference tools you don't have, substitute OpenCode equivalents:
|
||||
- \`TodoWrite\` → \`update_plan\`
|
||||
- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention)
|
||||
- \`Skill\` tool → OpenCode's native \`skill\` tool
|
||||
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
|
||||
|
||||
**Skills location:**
|
||||
Superpowers skills are in \`${configDir}/skills/superpowers/\`
|
||||
Use OpenCode's native \`skill\` tool to list and load skills.`;
|
||||
|
||||
return `<EXTREMELY_IMPORTANT>
|
||||
You have superpowers.
|
||||
|
||||
**IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the skill tool to load "using-superpowers" again - that would be redundant.**
|
||||
|
||||
${content}
|
||||
|
||||
${toolMapping}
|
||||
</EXTREMELY_IMPORTANT>`;
|
||||
};
|
||||
|
||||
return {
|
||||
// Use system prompt transform to inject bootstrap (fixes #226 agent reset bug)
|
||||
'experimental.chat.system.transform': async (_input, output) => {
|
||||
const bootstrap = getBootstrapContent();
|
||||
if (bootstrap) {
|
||||
(output.system ||= []).push(bootstrap);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,74 @@
|
||||
# Superpowers Release Notes
|
||||
|
||||
## v4.1.1 (2026-01-23)
|
||||
|
||||
### Fixes
|
||||
|
||||
**OpenCode: Standardized on `plugins/` directory per official docs (#343)**
|
||||
|
||||
OpenCode's official documentation uses `~/.config/opencode/plugins/` (plural). Our docs previously used `plugin/` (singular). While OpenCode accepts both forms, we've standardized on the official convention to avoid confusion.
|
||||
|
||||
Changes:
|
||||
- Renamed `.opencode/plugin/` to `.opencode/plugins/` in repo structure
|
||||
- Updated all installation docs (INSTALL.md, README.opencode.md) across all platforms
|
||||
- Updated test scripts to match
|
||||
|
||||
**OpenCode: Fixed symlink instructions (#339, #342)**
|
||||
|
||||
- Added explicit `rm` before `ln -s` (fixes "file already exists" errors on reinstall)
|
||||
- Added missing skills symlink step that was absent from INSTALL.md
|
||||
- Updated from deprecated `use_skill`/`find_skills` to native `skill` tool references
|
||||
|
||||
---
|
||||
|
||||
## v4.1.0 (2026-01-23)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**OpenCode: Switched to native skills system**
|
||||
|
||||
Superpowers for OpenCode now uses OpenCode's native `skill` tool instead of custom `use_skill`/`find_skills` tools. This is a cleaner integration that works with OpenCode's built-in skill discovery.
|
||||
|
||||
**Migration required:** Skills must be symlinked to `~/.config/opencode/skills/superpowers/` (see updated installation docs).
|
||||
|
||||
### Fixes
|
||||
|
||||
**OpenCode: Fixed agent reset on session start (#226)**
|
||||
|
||||
The previous bootstrap injection method using `session.prompt({ noReply: true })` caused OpenCode to reset the selected agent to "build" on first message. Now uses `experimental.chat.system.transform` hook which modifies the system prompt directly without side effects.
|
||||
|
||||
**OpenCode: Fixed Windows installation (#232)**
|
||||
|
||||
- Removed dependency on `skills-core.js` (eliminates broken relative imports when file is copied instead of symlinked)
|
||||
- Added comprehensive Windows installation docs for cmd.exe, PowerShell, and Git Bash
|
||||
- Documented proper symlink vs junction usage for each platform
|
||||
|
||||
**Claude Code: Fixed Windows hook execution for Claude Code 2.1.x**
|
||||
|
||||
Claude Code 2.1.x changed how hooks execute on Windows: it now auto-detects `.sh` files in commands and prepends `bash `. This broke the polyglot wrapper pattern because `bash "run-hook.cmd" session-start.sh` tries to execute the .cmd file as a bash script.
|
||||
|
||||
Fix: hooks.json now calls session-start.sh directly. Claude Code 2.1.x handles the bash invocation automatically. Also added .gitattributes to enforce LF line endings for shell scripts (fixes CRLF issues on Windows checkout).
|
||||
|
||||
---
|
||||
|
||||
## v4.0.3 (2025-12-26)
|
||||
|
||||
### Improvements
|
||||
|
||||
**Strengthened using-superpowers skill for explicit skill requests**
|
||||
|
||||
Addressed a failure mode where Claude would skip invoking a skill even when the user explicitly requested it by name (e.g., "subagent-driven-development, please"). Claude would think "I know what that means" and start working directly instead of loading the skill.
|
||||
|
||||
Changes:
|
||||
- Updated "The Rule" to say "Invoke relevant or requested skills" instead of "Check for skills" - emphasizing active invocation over passive checking
|
||||
- Added "BEFORE any response or action" - the original wording only mentioned "response" but Claude would sometimes take action without responding first
|
||||
- Added reassurance that invoking a wrong skill is okay - reduces hesitation
|
||||
- Added new red flag: "I know what that means" → Knowing the concept ≠ using the skill
|
||||
|
||||
**Added explicit skill request tests**
|
||||
|
||||
New test suite in `tests/explicit-skill-requests/` that verifies Claude correctly invokes skills when users request them by name. Includes single-turn and multi-turn test scenarios.
|
||||
|
||||
## v4.0.2 (2025-12-23)
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -7,7 +7,7 @@ Complete guide for using Superpowers with [OpenCode.ai](https://opencode.ai).
|
||||
Tell OpenCode:
|
||||
|
||||
```
|
||||
Clone https://github.com/obra/superpowers to ~/.config/opencode/superpowers, then create directory ~/.config/opencode/plugin, then symlink ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js to ~/.config/opencode/plugin/superpowers.js, then restart opencode.
|
||||
Clone https://github.com/obra/superpowers to ~/.config/opencode/superpowers, then create directory ~/.config/opencode/plugins, then symlink ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js to ~/.config/opencode/plugins/superpowers.js, then symlink ~/.config/opencode/superpowers/skills to ~/.config/opencode/skills/superpowers, then restart opencode.
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
@@ -15,59 +15,175 @@ Clone https://github.com/obra/superpowers to ~/.config/opencode/superpowers, the
|
||||
### Prerequisites
|
||||
|
||||
- [OpenCode.ai](https://opencode.ai) installed
|
||||
- Node.js installed
|
||||
- Git installed
|
||||
|
||||
### Installation Steps
|
||||
|
||||
#### 1. Install Superpowers
|
||||
### macOS / Linux
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/superpowers
|
||||
# 1. Install Superpowers (or update existing)
|
||||
if [ -d ~/.config/opencode/superpowers ]; then
|
||||
cd ~/.config/opencode/superpowers && git pull
|
||||
else
|
||||
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
|
||||
fi
|
||||
|
||||
# 2. Create directories
|
||||
mkdir -p ~/.config/opencode/plugins ~/.config/opencode/skills
|
||||
|
||||
# 3. Remove old symlinks/directories if they exist
|
||||
rm -f ~/.config/opencode/plugins/superpowers.js
|
||||
rm -rf ~/.config/opencode/skills/superpowers
|
||||
|
||||
# 4. Create symlinks
|
||||
ln -s ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js ~/.config/opencode/plugins/superpowers.js
|
||||
ln -s ~/.config/opencode/superpowers/skills ~/.config/opencode/skills/superpowers
|
||||
|
||||
# 5. Restart OpenCode
|
||||
```
|
||||
|
||||
#### Verify Installation
|
||||
|
||||
```bash
|
||||
ls -l ~/.config/opencode/plugins/superpowers.js
|
||||
ls -l ~/.config/opencode/skills/superpowers
|
||||
```
|
||||
|
||||
Both should show symlinks pointing to the superpowers directory.
|
||||
|
||||
### Windows
|
||||
|
||||
**Prerequisites:**
|
||||
- Git installed
|
||||
- Either **Developer Mode** enabled OR **Administrator privileges**
|
||||
- Windows 10: Settings → Update & Security → For developers
|
||||
- Windows 11: Settings → System → For developers
|
||||
|
||||
Pick your shell below: [Command Prompt](#command-prompt) | [PowerShell](#powershell) | [Git Bash](#git-bash)
|
||||
|
||||
#### Command Prompt
|
||||
|
||||
Run as Administrator, or with Developer Mode enabled:
|
||||
|
||||
```cmd
|
||||
:: 1. Install Superpowers
|
||||
git clone https://github.com/obra/superpowers.git "%USERPROFILE%\.config\opencode\superpowers"
|
||||
|
||||
:: 2. Create directories
|
||||
mkdir "%USERPROFILE%\.config\opencode\plugins" 2>nul
|
||||
mkdir "%USERPROFILE%\.config\opencode\skills" 2>nul
|
||||
|
||||
:: 3. Remove existing links (safe for reinstalls)
|
||||
del "%USERPROFILE%\.config\opencode\plugins\superpowers.js" 2>nul
|
||||
rmdir "%USERPROFILE%\.config\opencode\skills\superpowers" 2>nul
|
||||
|
||||
:: 4. Create plugin symlink (requires Developer Mode or Admin)
|
||||
mklink "%USERPROFILE%\.config\opencode\plugins\superpowers.js" "%USERPROFILE%\.config\opencode\superpowers\.opencode\plugins\superpowers.js"
|
||||
|
||||
:: 5. Create skills junction (works without special privileges)
|
||||
mklink /J "%USERPROFILE%\.config\opencode\skills\superpowers" "%USERPROFILE%\.config\opencode\superpowers\skills"
|
||||
|
||||
:: 6. Restart OpenCode
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
|
||||
Run as Administrator, or with Developer Mode enabled:
|
||||
|
||||
```powershell
|
||||
# 1. Install Superpowers
|
||||
git clone https://github.com/obra/superpowers.git "$env:USERPROFILE\.config\opencode\superpowers"
|
||||
|
||||
# 2. Create directories
|
||||
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.config\opencode\plugins"
|
||||
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.config\opencode\skills"
|
||||
|
||||
# 3. Remove existing links (safe for reinstalls)
|
||||
Remove-Item "$env:USERPROFILE\.config\opencode\plugins\superpowers.js" -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item "$env:USERPROFILE\.config\opencode\skills\superpowers" -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# 4. Create plugin symlink (requires Developer Mode or Admin)
|
||||
New-Item -ItemType SymbolicLink -Path "$env:USERPROFILE\.config\opencode\plugins\superpowers.js" -Target "$env:USERPROFILE\.config\opencode\superpowers\.opencode\plugins\superpowers.js"
|
||||
|
||||
# 5. Create skills junction (works without special privileges)
|
||||
New-Item -ItemType Junction -Path "$env:USERPROFILE\.config\opencode\skills\superpowers" -Target "$env:USERPROFILE\.config\opencode\superpowers\skills"
|
||||
|
||||
# 6. Restart OpenCode
|
||||
```
|
||||
|
||||
#### Git Bash
|
||||
|
||||
Note: Git Bash's native `ln` command copies files instead of creating symlinks. Use `cmd //c mklink` instead (the `//c` is Git Bash syntax for `/c`).
|
||||
|
||||
```bash
|
||||
# 1. Install Superpowers
|
||||
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
|
||||
|
||||
# 2. Create directories
|
||||
mkdir -p ~/.config/opencode/plugins ~/.config/opencode/skills
|
||||
|
||||
# 3. Remove existing links (safe for reinstalls)
|
||||
rm -f ~/.config/opencode/plugins/superpowers.js 2>/dev/null
|
||||
rm -rf ~/.config/opencode/skills/superpowers 2>/dev/null
|
||||
|
||||
# 4. Create plugin symlink (requires Developer Mode or Admin)
|
||||
cmd //c "mklink \"$(cygpath -w ~/.config/opencode/plugins/superpowers.js)\" \"$(cygpath -w ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js)\""
|
||||
|
||||
# 5. Create skills junction (works without special privileges)
|
||||
cmd //c "mklink /J \"$(cygpath -w ~/.config/opencode/skills/superpowers)\" \"$(cygpath -w ~/.config/opencode/superpowers/skills)\""
|
||||
|
||||
# 6. Restart OpenCode
|
||||
```
|
||||
|
||||
#### 2. Register the Plugin
|
||||
#### WSL Users
|
||||
|
||||
OpenCode discovers plugins from `~/.config/opencode/plugin/`. Create a symlink:
|
||||
If running OpenCode inside WSL, use the [macOS / Linux](#macos--linux) instructions instead.
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/plugin
|
||||
ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js ~/.config/opencode/plugin/superpowers.js
|
||||
#### Verify Installation
|
||||
|
||||
**Command Prompt:**
|
||||
```cmd
|
||||
dir /AL "%USERPROFILE%\.config\opencode\plugins"
|
||||
dir /AL "%USERPROFILE%\.config\opencode\skills"
|
||||
```
|
||||
|
||||
Alternatively, for project-local installation:
|
||||
|
||||
```bash
|
||||
# In your OpenCode project
|
||||
mkdir -p .opencode/plugin
|
||||
ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js .opencode/plugin/superpowers.js
|
||||
**PowerShell:**
|
||||
```powershell
|
||||
Get-ChildItem "$env:USERPROFILE\.config\opencode\plugins" | Where-Object { $_.LinkType }
|
||||
Get-ChildItem "$env:USERPROFILE\.config\opencode\skills" | Where-Object { $_.LinkType }
|
||||
```
|
||||
|
||||
#### 3. Restart OpenCode
|
||||
Look for `<SYMLINK>` or `<JUNCTION>` in the output.
|
||||
|
||||
Restart OpenCode to load the plugin. Superpowers will automatically activate.
|
||||
#### Troubleshooting Windows
|
||||
|
||||
**"You do not have sufficient privilege" error:**
|
||||
- Enable Developer Mode in Windows Settings, OR
|
||||
- Right-click your terminal → "Run as Administrator"
|
||||
|
||||
**"Cannot create a file when that file already exists":**
|
||||
- Run the removal commands (step 3) first, then retry
|
||||
|
||||
**Symlinks not working after git clone:**
|
||||
- Run `git config --global core.symlinks true` and re-clone
|
||||
|
||||
## Usage
|
||||
|
||||
### Finding Skills
|
||||
|
||||
Use the `find_skills` tool to list all available skills:
|
||||
Use OpenCode's native `skill` tool to list all available skills:
|
||||
|
||||
```
|
||||
use find_skills tool
|
||||
use skill tool to list skills
|
||||
```
|
||||
|
||||
### Loading a Skill
|
||||
|
||||
Use the `use_skill` tool to load a specific skill:
|
||||
Use OpenCode's native `skill` tool to load a specific skill:
|
||||
|
||||
```
|
||||
use use_skill tool with skill_name: "superpowers:brainstorming"
|
||||
use skill tool to load superpowers/brainstorming
|
||||
```
|
||||
|
||||
Skills are automatically inserted into the conversation and persist across context compaction.
|
||||
|
||||
### Personal Skills
|
||||
|
||||
Create your own skills in `~/.config/opencode/skills/`:
|
||||
@@ -111,66 +227,48 @@ description: Use when [condition] - [what it does]
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
## Skill Priority
|
||||
## Skill Locations
|
||||
|
||||
Skills are resolved with this priority order:
|
||||
OpenCode discovers skills from these locations:
|
||||
|
||||
1. **Project skills** (`.opencode/skills/`) - Highest priority
|
||||
2. **Personal skills** (`~/.config/opencode/skills/`)
|
||||
3. **Superpowers skills** (`~/.config/opencode/superpowers/skills/`)
|
||||
|
||||
You can force resolution to a specific level:
|
||||
- `project:skill-name` - Force project skill
|
||||
- `skill-name` - Search project → personal → superpowers
|
||||
- `superpowers:skill-name` - Force superpowers skill
|
||||
3. **Superpowers skills** (`~/.config/opencode/skills/superpowers/`) - via symlink
|
||||
|
||||
## Features
|
||||
|
||||
### Automatic Context Injection
|
||||
|
||||
The plugin automatically injects superpowers context via the chat.message hook on every session. No manual configuration needed.
|
||||
The plugin automatically injects superpowers context via the `experimental.chat.system.transform` hook. This adds the "using-superpowers" skill content to the system prompt on every request.
|
||||
|
||||
### Message Insertion Pattern
|
||||
### Native Skills Integration
|
||||
|
||||
When you load a skill with `use_skill`, it's inserted as a user message with `noReply: true`. This ensures skills persist throughout long conversations, even when OpenCode compacts context.
|
||||
|
||||
### Compaction Resilience
|
||||
|
||||
The plugin listens for `session.compacted` events and automatically re-injects the core superpowers bootstrap to maintain functionality after context compaction.
|
||||
Superpowers uses OpenCode's native `skill` tool for skill discovery and loading. Skills are symlinked into `~/.config/opencode/skills/superpowers/` so they appear alongside your personal and project skills.
|
||||
|
||||
### Tool Mapping
|
||||
|
||||
Skills written for Claude Code are automatically adapted for OpenCode. The plugin provides mapping instructions:
|
||||
Skills written for Claude Code are automatically adapted for OpenCode. The bootstrap provides mapping instructions:
|
||||
|
||||
- `TodoWrite` → `update_plan`
|
||||
- `Task` with subagents → OpenCode's `@mention` system
|
||||
- `Skill` tool → `use_skill` custom tool
|
||||
- `Skill` tool → OpenCode's native `skill` tool
|
||||
- File operations → Native OpenCode tools
|
||||
|
||||
## Architecture
|
||||
|
||||
### Plugin Structure
|
||||
|
||||
**Location:** `~/.config/opencode/superpowers/.opencode/plugin/superpowers.js`
|
||||
**Location:** `~/.config/opencode/superpowers/.opencode/plugins/superpowers.js`
|
||||
|
||||
**Components:**
|
||||
- Two custom tools: `use_skill`, `find_skills`
|
||||
- chat.message hook for initial context injection
|
||||
- event handler for session.compacted re-injection
|
||||
- Uses shared `lib/skills-core.js` module (also used by Codex)
|
||||
- `experimental.chat.system.transform` hook for bootstrap injection
|
||||
- Reads and injects the "using-superpowers" skill content
|
||||
|
||||
### Shared Core Module
|
||||
### Skills
|
||||
|
||||
**Location:** `~/.config/opencode/superpowers/lib/skills-core.js`
|
||||
**Location:** `~/.config/opencode/skills/superpowers/` (symlink to `~/.config/opencode/superpowers/skills/`)
|
||||
|
||||
**Functions:**
|
||||
- `extractFrontmatter()` - Parse skill metadata
|
||||
- `stripFrontmatter()` - Remove metadata from content
|
||||
- `findSkillsInDir()` - Recursive skill discovery
|
||||
- `resolveSkillPath()` - Skill resolution with shadowing
|
||||
- `checkForUpdates()` - Git update detection
|
||||
|
||||
This module is shared between OpenCode and Codex implementations for code reuse.
|
||||
Skills are discovered by OpenCode's native skill system. Each skill has a `SKILL.md` file with YAML frontmatter.
|
||||
|
||||
## Updating
|
||||
|
||||
@@ -185,28 +283,28 @@ Restart OpenCode to load the updates.
|
||||
|
||||
### Plugin not loading
|
||||
|
||||
1. Check plugin file exists: `ls ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js`
|
||||
2. Check symlink: `ls -l ~/.config/opencode/plugin/superpowers.js`
|
||||
1. Check plugin exists: `ls ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js`
|
||||
2. Check symlink/junction: `ls -l ~/.config/opencode/plugins/` (macOS/Linux) or `dir /AL %USERPROFILE%\.config\opencode\plugins` (Windows)
|
||||
3. Check OpenCode logs: `opencode run "test" --print-logs --log-level DEBUG`
|
||||
4. Look for: `service=plugin path=file:///.../superpowers.js loading plugin`
|
||||
4. Look for plugin loading message in logs
|
||||
|
||||
### Skills not found
|
||||
|
||||
1. Verify skills directory: `ls ~/.config/opencode/superpowers/skills`
|
||||
2. Use `find_skills` tool to see what's discovered
|
||||
3. Check skill structure: each skill needs a `SKILL.md` file
|
||||
1. Verify skills symlink: `ls -l ~/.config/opencode/skills/superpowers` (should point to superpowers/skills/)
|
||||
2. Use OpenCode's `skill` tool to list available skills
|
||||
3. Check skill structure: each skill needs a `SKILL.md` file with valid frontmatter
|
||||
|
||||
### Tools not working
|
||||
### Windows: Module not found error
|
||||
|
||||
1. Verify plugin loaded: Check OpenCode logs for plugin loading message
|
||||
2. Check Node.js version: The plugin requires Node.js for ES modules
|
||||
3. Test plugin manually: `node --input-type=module -e "import('file://~/.config/opencode/plugin/superpowers.js').then(m => console.log(Object.keys(m)))"`
|
||||
If you see `Cannot find module` errors on Windows:
|
||||
- **Cause:** Git Bash `ln -sf` copies files instead of creating symlinks
|
||||
- **Fix:** Use `mklink /J` directory junctions instead (see Windows installation steps)
|
||||
|
||||
### Context not injecting
|
||||
### Bootstrap not appearing
|
||||
|
||||
1. Check if chat.message hook is working
|
||||
2. Verify using-superpowers skill exists
|
||||
3. Check OpenCode version (requires recent version with plugin support)
|
||||
1. Verify using-superpowers skill exists: `ls ~/.config/opencode/superpowers/skills/using-superpowers/SKILL.md`
|
||||
2. Check OpenCode version supports `experimental.chat.system.transform` hook
|
||||
3. Restart OpenCode after plugin changes
|
||||
|
||||
## Getting Help
|
||||
|
||||
@@ -216,19 +314,17 @@ Restart OpenCode to load the updates.
|
||||
|
||||
## Testing
|
||||
|
||||
The implementation includes an automated test suite at `tests/opencode/`:
|
||||
Verify your installation:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
./tests/opencode/run-tests.sh --integration --verbose
|
||||
# Check plugin loads
|
||||
opencode run --print-logs "hello" 2>&1 | grep -i superpowers
|
||||
|
||||
# Run specific test
|
||||
./tests/opencode/run-tests.sh --test test-tools.sh
|
||||
# Check skills are discoverable
|
||||
opencode run "use skill tool to list all skills" 2>&1 | grep -i superpowers
|
||||
|
||||
# Check bootstrap injection
|
||||
opencode run "what superpowers do you have?"
|
||||
```
|
||||
|
||||
Tests verify:
|
||||
- Plugin loading
|
||||
- Skills-core library functionality
|
||||
- Tool execution (use_skill, find_skills)
|
||||
- Skill priority resolution
|
||||
- Proper isolation with temp HOME
|
||||
The agent should mention having superpowers and be able to list skills from `superpowers/`.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start.sh"
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
: << 'CMDBLOCK'
|
||||
@echo off
|
||||
REM Polyglot wrapper: runs .sh scripts cross-platform
|
||||
REM ============================================================================
|
||||
REM DEPRECATED: This polyglot wrapper is no longer used as of Claude Code 2.1.x
|
||||
REM ============================================================================
|
||||
REM
|
||||
REM Claude Code 2.1.x changed the Windows execution model for hooks:
|
||||
REM
|
||||
REM Before (2.0.x): Hooks ran with shell:true, using the system default shell.
|
||||
REM This wrapper provided cross-platform compatibility by
|
||||
REM being both a valid .cmd file (Windows) and bash script.
|
||||
REM
|
||||
REM After (2.1.x): Claude Code now auto-detects .sh files in hook commands
|
||||
REM and prepends "bash " on Windows. This broke the wrapper
|
||||
REM because the command:
|
||||
REM "run-hook.cmd" session-start.sh
|
||||
REM became:
|
||||
REM bash "run-hook.cmd" session-start.sh
|
||||
REM ...and bash cannot execute a .cmd file.
|
||||
REM
|
||||
REM The fix: hooks.json now calls session-start.sh directly. Claude Code 2.1.x
|
||||
REM handles the bash invocation automatically on Windows.
|
||||
REM
|
||||
REM This file is kept for reference and potential backward compatibility.
|
||||
REM ============================================================================
|
||||
REM
|
||||
REM Original purpose: Polyglot wrapper to run .sh scripts cross-platform
|
||||
REM Usage: run-hook.cmd <script-name> [args...]
|
||||
REM The script should be in the same directory as this wrapper
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ This is not negotiable. This is not optional. You cannot rationalize your way ou
|
||||
|
||||
## The Rule
|
||||
|
||||
**Check for skills BEFORE ANY RESPONSE.** This includes clarifying questions. Even 1% chance means invoke the Skill tool first.
|
||||
**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
|
||||
|
||||
```dot
|
||||
digraph skill_flow {
|
||||
@@ -62,6 +62,7 @@ These thoughts mean STOP—you're rationalizing:
|
||||
| "The skill is overkill" | Simple things become complex. Use it. |
|
||||
| "I'll just do this one thing first" | Check BEFORE doing anything. |
|
||||
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
|
||||
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
|
||||
|
||||
## Skill Priority
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ Before deploying skill, verify you followed RED-GREEN-REFACTOR:
|
||||
- [ ] Added explicit counters for each loophole
|
||||
- [ ] Updated rationalization table
|
||||
- [ ] Updated red flags list
|
||||
- [ ] Updated description ith violation symptoms
|
||||
- [ ] Updated description with violation symptoms
|
||||
- [ ] Re-tested - agent still complies
|
||||
- [ ] Meta-tested to verify clarity
|
||||
- [ ] Agent follows rule under maximum pressure
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
The plan is done. docs/plans/auth-system.md has everything.
|
||||
|
||||
Do subagent-driven development on this - start with Task 1, dispatch a subagent, then we'll review.
|
||||
@@ -0,0 +1,17 @@
|
||||
Great, the plan is complete. I've saved it to docs/plans/auth-system.md.
|
||||
|
||||
Here's a summary of what we designed:
|
||||
- Task 1: Add User Model with email/password fields
|
||||
- Task 2: Create auth routes for login/register
|
||||
- Task 3: Add JWT middleware for protected routes
|
||||
- Task 4: Write tests for all auth functionality
|
||||
|
||||
Two execution options:
|
||||
1. Subagent-Driven (this session) - dispatch a fresh subagent per task
|
||||
2. Parallel Session (separate) - open new Claude Code session
|
||||
|
||||
Which approach do you want?
|
||||
|
||||
---
|
||||
|
||||
subagent-driven-development, please
|
||||
@@ -0,0 +1,11 @@
|
||||
[Previous assistant message]:
|
||||
Plan complete and saved to docs/plans/auth-system.md.
|
||||
|
||||
Two execution options:
|
||||
1. Subagent-Driven (this session) - I dispatch a fresh subagent per task, review between tasks, fast iteration within this conversation
|
||||
2. Parallel Session (separate) - Open a new Claude Code session with the execute-plan skill, batch execution with review checkpoints
|
||||
|
||||
Which approach do you want to use for implementation?
|
||||
|
||||
[Your response]:
|
||||
subagent-driven-development, please
|
||||
@@ -0,0 +1,8 @@
|
||||
I have my implementation plan ready at docs/plans/auth-system.md.
|
||||
|
||||
I want to use subagent-driven-development to execute it. That means:
|
||||
- Dispatch a fresh subagent for each task in the plan
|
||||
- Review the output between tasks
|
||||
- Keep iteration fast within this conversation
|
||||
|
||||
Let's start - please read the plan and begin dispatching subagents for each task.
|
||||
@@ -0,0 +1,3 @@
|
||||
I have a plan at docs/plans/auth-system.md that's ready to implement.
|
||||
|
||||
subagent-driven-development, please
|
||||
@@ -0,0 +1 @@
|
||||
please use the brainstorming skill to help me think through this feature
|
||||
@@ -0,0 +1,3 @@
|
||||
Plan is at docs/plans/auth-system.md.
|
||||
|
||||
subagent-driven-development, please. Don't waste time - just read the plan and start dispatching subagents immediately.
|
||||
@@ -0,0 +1 @@
|
||||
subagent-driven-development, please
|
||||
@@ -0,0 +1 @@
|
||||
use systematic-debugging to figure out what's wrong
|
||||
70
tests/explicit-skill-requests/run-all.sh
Executable file
70
tests/explicit-skill-requests/run-all.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# Run all explicit skill request tests
|
||||
# Usage: ./run-all.sh
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROMPTS_DIR="$SCRIPT_DIR/prompts"
|
||||
|
||||
echo "=== Running All Explicit Skill Request Tests ==="
|
||||
echo ""
|
||||
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
RESULTS=""
|
||||
|
||||
# Test: subagent-driven-development, please
|
||||
echo ">>> Test 1: subagent-driven-development-please"
|
||||
if "$SCRIPT_DIR/run-test.sh" "subagent-driven-development" "$PROMPTS_DIR/subagent-driven-development-please.txt"; then
|
||||
PASSED=$((PASSED + 1))
|
||||
RESULTS="$RESULTS\nPASS: subagent-driven-development-please"
|
||||
else
|
||||
FAILED=$((FAILED + 1))
|
||||
RESULTS="$RESULTS\nFAIL: subagent-driven-development-please"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test: use systematic-debugging
|
||||
echo ">>> Test 2: use-systematic-debugging"
|
||||
if "$SCRIPT_DIR/run-test.sh" "systematic-debugging" "$PROMPTS_DIR/use-systematic-debugging.txt"; then
|
||||
PASSED=$((PASSED + 1))
|
||||
RESULTS="$RESULTS\nPASS: use-systematic-debugging"
|
||||
else
|
||||
FAILED=$((FAILED + 1))
|
||||
RESULTS="$RESULTS\nFAIL: use-systematic-debugging"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test: please use brainstorming
|
||||
echo ">>> Test 3: please-use-brainstorming"
|
||||
if "$SCRIPT_DIR/run-test.sh" "brainstorming" "$PROMPTS_DIR/please-use-brainstorming.txt"; then
|
||||
PASSED=$((PASSED + 1))
|
||||
RESULTS="$RESULTS\nPASS: please-use-brainstorming"
|
||||
else
|
||||
FAILED=$((FAILED + 1))
|
||||
RESULTS="$RESULTS\nFAIL: please-use-brainstorming"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test: mid-conversation execute plan
|
||||
echo ">>> Test 4: mid-conversation-execute-plan"
|
||||
if "$SCRIPT_DIR/run-test.sh" "subagent-driven-development" "$PROMPTS_DIR/mid-conversation-execute-plan.txt"; then
|
||||
PASSED=$((PASSED + 1))
|
||||
RESULTS="$RESULTS\nPASS: mid-conversation-execute-plan"
|
||||
else
|
||||
FAILED=$((FAILED + 1))
|
||||
RESULTS="$RESULTS\nFAIL: mid-conversation-execute-plan"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "=== Summary ==="
|
||||
echo -e "$RESULTS"
|
||||
echo ""
|
||||
echo "Passed: $PASSED"
|
||||
echo "Failed: $FAILED"
|
||||
echo "Total: $((PASSED + FAILED))"
|
||||
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
100
tests/explicit-skill-requests/run-claude-describes-sdd.sh
Executable file
100
tests/explicit-skill-requests/run-claude-describes-sdd.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# Test where Claude explicitly describes subagent-driven-development before user requests it
|
||||
# This mimics the original failure scenario
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
TIMESTAMP=$(date +%s)
|
||||
OUTPUT_DIR="/tmp/superpowers-tests/${TIMESTAMP}/explicit-skill-requests/claude-describes"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
PROJECT_DIR="$OUTPUT_DIR/project"
|
||||
mkdir -p "$PROJECT_DIR/docs/plans"
|
||||
|
||||
echo "=== Test: Claude Describes SDD First ==="
|
||||
echo "Output dir: $OUTPUT_DIR"
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Create a plan
|
||||
cat > "$PROJECT_DIR/docs/plans/auth-system.md" << 'EOF'
|
||||
# Auth System Implementation Plan
|
||||
|
||||
## Task 1: Add User Model
|
||||
Create user model with email and password fields.
|
||||
|
||||
## Task 2: Add Auth Routes
|
||||
Create login and register endpoints.
|
||||
|
||||
## Task 3: Add JWT Middleware
|
||||
Protect routes with JWT validation.
|
||||
EOF
|
||||
|
||||
# Turn 1: Have Claude describe execution options including SDD
|
||||
echo ">>> Turn 1: Ask Claude to describe execution options..."
|
||||
claude -p "I have a plan at docs/plans/auth-system.md. Tell me about my options for executing it, including what subagent-driven-development means and how it works." \
|
||||
--model haiku \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 3 \
|
||||
--output-format stream-json \
|
||||
> "$OUTPUT_DIR/turn1.json" 2>&1 || true
|
||||
echo "Done."
|
||||
|
||||
# Turn 2: THE CRITICAL TEST - now that Claude has explained it
|
||||
echo ">>> Turn 2: Request subagent-driven-development..."
|
||||
FINAL_LOG="$OUTPUT_DIR/turn2.json"
|
||||
claude -p "subagent-driven-development, please" \
|
||||
--continue \
|
||||
--model haiku \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 2 \
|
||||
--output-format stream-json \
|
||||
> "$FINAL_LOG" 2>&1 || true
|
||||
echo "Done."
|
||||
echo ""
|
||||
|
||||
echo "=== Results ==="
|
||||
|
||||
# Check Turn 1 to see if Claude described SDD
|
||||
echo "Turn 1 - Claude's description of options (excerpt):"
|
||||
grep '"type":"assistant"' "$OUTPUT_DIR/turn1.json" | head -1 | jq -r '.message.content[0].text // .message.content' 2>/dev/null | head -c 800 || echo " (could not extract)"
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
|
||||
# Check final turn
|
||||
SKILL_PATTERN='"skill":"([^"]*:)?subagent-driven-development"'
|
||||
if grep -q '"name":"Skill"' "$FINAL_LOG" && grep -qE "$SKILL_PATTERN" "$FINAL_LOG"; then
|
||||
echo "PASS: Skill was triggered after Claude described it"
|
||||
TRIGGERED=true
|
||||
else
|
||||
echo "FAIL: Skill was NOT triggered (Claude may have thought it already knew)"
|
||||
TRIGGERED=false
|
||||
|
||||
echo ""
|
||||
echo "Tools invoked in final turn:"
|
||||
grep '"type":"tool_use"' "$FINAL_LOG" | grep -o '"name":"[^"]*"' | sort -u | head -10 || echo " (none)"
|
||||
|
||||
echo ""
|
||||
echo "Final turn response:"
|
||||
grep '"type":"assistant"' "$FINAL_LOG" | head -1 | jq -r '.message.content[0].text // .message.content' 2>/dev/null | head -c 800 || echo " (could not extract)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Skills triggered in final turn:"
|
||||
grep -o '"skill":"[^"]*"' "$FINAL_LOG" 2>/dev/null | sort -u || echo " (none)"
|
||||
|
||||
echo ""
|
||||
echo "Logs in: $OUTPUT_DIR"
|
||||
|
||||
if [ "$TRIGGERED" = "true" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
113
tests/explicit-skill-requests/run-extended-multiturn-test.sh
Executable file
113
tests/explicit-skill-requests/run-extended-multiturn-test.sh
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
# Extended multi-turn test with more conversation history
|
||||
# This tries to reproduce the failure by building more context
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
TIMESTAMP=$(date +%s)
|
||||
OUTPUT_DIR="/tmp/superpowers-tests/${TIMESTAMP}/explicit-skill-requests/extended-multiturn"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
PROJECT_DIR="$OUTPUT_DIR/project"
|
||||
mkdir -p "$PROJECT_DIR/docs/plans"
|
||||
|
||||
echo "=== Extended Multi-Turn Test ==="
|
||||
echo "Output dir: $OUTPUT_DIR"
|
||||
echo "Plugin dir: $PLUGIN_DIR"
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Turn 1: Start brainstorming
|
||||
echo ">>> Turn 1: Brainstorming request..."
|
||||
claude -p "I want to add user authentication to my app. Help me think through this." \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 3 \
|
||||
--output-format stream-json \
|
||||
> "$OUTPUT_DIR/turn1.json" 2>&1 || true
|
||||
echo "Done."
|
||||
|
||||
# Turn 2: Answer a brainstorming question
|
||||
echo ">>> Turn 2: Answering questions..."
|
||||
claude -p "Let's use JWT tokens with 24-hour expiry. Email/password registration." \
|
||||
--continue \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 3 \
|
||||
--output-format stream-json \
|
||||
> "$OUTPUT_DIR/turn2.json" 2>&1 || true
|
||||
echo "Done."
|
||||
|
||||
# Turn 3: Ask to write a plan
|
||||
echo ">>> Turn 3: Requesting plan..."
|
||||
claude -p "Great, write this up as an implementation plan." \
|
||||
--continue \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 3 \
|
||||
--output-format stream-json \
|
||||
> "$OUTPUT_DIR/turn3.json" 2>&1 || true
|
||||
echo "Done."
|
||||
|
||||
# Turn 4: Confirm plan looks good
|
||||
echo ">>> Turn 4: Confirming plan..."
|
||||
claude -p "The plan looks good. What are my options for executing it?" \
|
||||
--continue \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 2 \
|
||||
--output-format stream-json \
|
||||
> "$OUTPUT_DIR/turn4.json" 2>&1 || true
|
||||
echo "Done."
|
||||
|
||||
# Turn 5: THE CRITICAL TEST
|
||||
echo ">>> Turn 5: Requesting subagent-driven-development..."
|
||||
FINAL_LOG="$OUTPUT_DIR/turn5.json"
|
||||
claude -p "subagent-driven-development, please" \
|
||||
--continue \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 2 \
|
||||
--output-format stream-json \
|
||||
> "$FINAL_LOG" 2>&1 || true
|
||||
echo "Done."
|
||||
echo ""
|
||||
|
||||
echo "=== Results ==="
|
||||
|
||||
# Check final turn
|
||||
SKILL_PATTERN='"skill":"([^"]*:)?subagent-driven-development"'
|
||||
if grep -q '"name":"Skill"' "$FINAL_LOG" && grep -qE "$SKILL_PATTERN" "$FINAL_LOG"; then
|
||||
echo "PASS: Skill was triggered"
|
||||
TRIGGERED=true
|
||||
else
|
||||
echo "FAIL: Skill was NOT triggered"
|
||||
TRIGGERED=false
|
||||
|
||||
# Show what was invoked instead
|
||||
echo ""
|
||||
echo "Tools invoked in final turn:"
|
||||
grep '"type":"tool_use"' "$FINAL_LOG" | jq -r '.content[] | select(.type=="tool_use") | .name' 2>/dev/null | head -10 || \
|
||||
grep -o '"name":"[^"]*"' "$FINAL_LOG" | head -10 || echo " (none found)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Skills triggered:"
|
||||
grep -o '"skill":"[^"]*"' "$FINAL_LOG" 2>/dev/null | sort -u || echo " (none)"
|
||||
|
||||
echo ""
|
||||
echo "Final turn response (first 500 chars):"
|
||||
grep '"type":"assistant"' "$FINAL_LOG" | head -1 | jq -r '.message.content[0].text // .message.content' 2>/dev/null | head -c 500 || echo " (could not extract)"
|
||||
|
||||
echo ""
|
||||
echo "Logs in: $OUTPUT_DIR"
|
||||
|
||||
if [ "$TRIGGERED" = "true" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
144
tests/explicit-skill-requests/run-haiku-test.sh
Executable file
144
tests/explicit-skill-requests/run-haiku-test.sh
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
# Test with haiku model and user's CLAUDE.md
|
||||
# This tests whether a cheaper/faster model fails more easily
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
TIMESTAMP=$(date +%s)
|
||||
OUTPUT_DIR="/tmp/superpowers-tests/${TIMESTAMP}/explicit-skill-requests/haiku"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
PROJECT_DIR="$OUTPUT_DIR/project"
|
||||
mkdir -p "$PROJECT_DIR/docs/plans"
|
||||
mkdir -p "$PROJECT_DIR/.claude"
|
||||
|
||||
echo "=== Haiku Model Test with User CLAUDE.md ==="
|
||||
echo "Output dir: $OUTPUT_DIR"
|
||||
echo "Plugin dir: $PLUGIN_DIR"
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Copy user's CLAUDE.md to simulate real environment
|
||||
if [ -f "$HOME/.claude/CLAUDE.md" ]; then
|
||||
cp "$HOME/.claude/CLAUDE.md" "$PROJECT_DIR/.claude/CLAUDE.md"
|
||||
echo "Copied user CLAUDE.md"
|
||||
else
|
||||
echo "No user CLAUDE.md found, proceeding without"
|
||||
fi
|
||||
|
||||
# Create a dummy plan file
|
||||
cat > "$PROJECT_DIR/docs/plans/auth-system.md" << 'EOF'
|
||||
# Auth System Implementation Plan
|
||||
|
||||
## Task 1: Add User Model
|
||||
Create user model with email and password fields.
|
||||
|
||||
## Task 2: Add Auth Routes
|
||||
Create login and register endpoints.
|
||||
|
||||
## Task 3: Add JWT Middleware
|
||||
Protect routes with JWT validation.
|
||||
|
||||
## Task 4: Write Tests
|
||||
Add comprehensive test coverage.
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
|
||||
# Turn 1: Start brainstorming
|
||||
echo ">>> Turn 1: Brainstorming request..."
|
||||
claude -p "I want to add user authentication to my app. Help me think through this." \
|
||||
--model haiku \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 3 \
|
||||
--output-format stream-json \
|
||||
> "$OUTPUT_DIR/turn1.json" 2>&1 || true
|
||||
echo "Done."
|
||||
|
||||
# Turn 2: Answer questions
|
||||
echo ">>> Turn 2: Answering questions..."
|
||||
claude -p "Let's use JWT tokens with 24-hour expiry. Email/password registration." \
|
||||
--continue \
|
||||
--model haiku \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 3 \
|
||||
--output-format stream-json \
|
||||
> "$OUTPUT_DIR/turn2.json" 2>&1 || true
|
||||
echo "Done."
|
||||
|
||||
# Turn 3: Ask to write a plan
|
||||
echo ">>> Turn 3: Requesting plan..."
|
||||
claude -p "Great, write this up as an implementation plan." \
|
||||
--continue \
|
||||
--model haiku \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 3 \
|
||||
--output-format stream-json \
|
||||
> "$OUTPUT_DIR/turn3.json" 2>&1 || true
|
||||
echo "Done."
|
||||
|
||||
# Turn 4: Confirm plan looks good
|
||||
echo ">>> Turn 4: Confirming plan..."
|
||||
claude -p "The plan looks good. What are my options for executing it?" \
|
||||
--continue \
|
||||
--model haiku \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 2 \
|
||||
--output-format stream-json \
|
||||
> "$OUTPUT_DIR/turn4.json" 2>&1 || true
|
||||
echo "Done."
|
||||
|
||||
# Turn 5: THE CRITICAL TEST
|
||||
echo ">>> Turn 5: Requesting subagent-driven-development..."
|
||||
FINAL_LOG="$OUTPUT_DIR/turn5.json"
|
||||
claude -p "subagent-driven-development, please" \
|
||||
--continue \
|
||||
--model haiku \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 2 \
|
||||
--output-format stream-json \
|
||||
> "$FINAL_LOG" 2>&1 || true
|
||||
echo "Done."
|
||||
echo ""
|
||||
|
||||
echo "=== Results (Haiku) ==="
|
||||
|
||||
# Check final turn
|
||||
SKILL_PATTERN='"skill":"([^"]*:)?subagent-driven-development"'
|
||||
if grep -q '"name":"Skill"' "$FINAL_LOG" && grep -qE "$SKILL_PATTERN" "$FINAL_LOG"; then
|
||||
echo "PASS: Skill was triggered"
|
||||
TRIGGERED=true
|
||||
else
|
||||
echo "FAIL: Skill was NOT triggered"
|
||||
TRIGGERED=false
|
||||
|
||||
echo ""
|
||||
echo "Tools invoked in final turn:"
|
||||
grep '"type":"tool_use"' "$FINAL_LOG" | grep -o '"name":"[^"]*"' | head -10 || echo " (none)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Skills triggered:"
|
||||
grep -o '"skill":"[^"]*"' "$FINAL_LOG" 2>/dev/null | sort -u || echo " (none)"
|
||||
|
||||
echo ""
|
||||
echo "Final turn response (first 500 chars):"
|
||||
grep '"type":"assistant"' "$FINAL_LOG" | head -1 | jq -r '.message.content[0].text // .message.content' 2>/dev/null | head -c 500 || echo " (could not extract)"
|
||||
|
||||
echo ""
|
||||
echo "Logs in: $OUTPUT_DIR"
|
||||
|
||||
if [ "$TRIGGERED" = "true" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
143
tests/explicit-skill-requests/run-multiturn-test.sh
Executable file
143
tests/explicit-skill-requests/run-multiturn-test.sh
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/bin/bash
|
||||
# Test explicit skill requests in multi-turn conversations
|
||||
# Usage: ./run-multiturn-test.sh
|
||||
#
|
||||
# This test builds actual conversation history to reproduce the failure mode
|
||||
# where Claude skips skill invocation after extended conversation
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
TIMESTAMP=$(date +%s)
|
||||
OUTPUT_DIR="/tmp/superpowers-tests/${TIMESTAMP}/explicit-skill-requests/multiturn"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Create project directory (conversation is cwd-based)
|
||||
PROJECT_DIR="$OUTPUT_DIR/project"
|
||||
mkdir -p "$PROJECT_DIR/docs/plans"
|
||||
|
||||
echo "=== Multi-Turn Explicit Skill Request Test ==="
|
||||
echo "Output dir: $OUTPUT_DIR"
|
||||
echo "Project dir: $PROJECT_DIR"
|
||||
echo "Plugin dir: $PLUGIN_DIR"
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Create a dummy plan file
|
||||
cat > "$PROJECT_DIR/docs/plans/auth-system.md" << 'EOF'
|
||||
# Auth System Implementation Plan
|
||||
|
||||
## Task 1: Add User Model
|
||||
Create user model with email and password fields.
|
||||
|
||||
## Task 2: Add Auth Routes
|
||||
Create login and register endpoints.
|
||||
|
||||
## Task 3: Add JWT Middleware
|
||||
Protect routes with JWT validation.
|
||||
|
||||
## Task 4: Write Tests
|
||||
Add comprehensive test coverage.
|
||||
EOF
|
||||
|
||||
# Turn 1: Start a planning conversation
|
||||
echo ">>> Turn 1: Starting planning conversation..."
|
||||
TURN1_LOG="$OUTPUT_DIR/turn1.json"
|
||||
claude -p "I need to implement an authentication system. Let's plan this out. The requirements are: user registration with email/password, JWT tokens, and protected routes." \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 2 \
|
||||
--output-format stream-json \
|
||||
> "$TURN1_LOG" 2>&1 || true
|
||||
|
||||
echo "Turn 1 complete."
|
||||
echo ""
|
||||
|
||||
# Turn 2: Continue with more planning detail
|
||||
echo ">>> Turn 2: Continuing planning..."
|
||||
TURN2_LOG="$OUTPUT_DIR/turn2.json"
|
||||
claude -p "Good analysis. I've already written the plan to docs/plans/auth-system.md. Now I'm ready to implement. What are my options for execution?" \
|
||||
--continue \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 2 \
|
||||
--output-format stream-json \
|
||||
> "$TURN2_LOG" 2>&1 || true
|
||||
|
||||
echo "Turn 2 complete."
|
||||
echo ""
|
||||
|
||||
# Turn 3: The critical test - ask for subagent-driven-development
|
||||
echo ">>> Turn 3: Requesting subagent-driven-development..."
|
||||
TURN3_LOG="$OUTPUT_DIR/turn3.json"
|
||||
claude -p "subagent-driven-development, please" \
|
||||
--continue \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 2 \
|
||||
--output-format stream-json \
|
||||
> "$TURN3_LOG" 2>&1 || true
|
||||
|
||||
echo "Turn 3 complete."
|
||||
echo ""
|
||||
|
||||
echo "=== Results ==="
|
||||
|
||||
# Check if skill was triggered in Turn 3
|
||||
SKILL_PATTERN='"skill":"([^"]*:)?subagent-driven-development"'
|
||||
if grep -q '"name":"Skill"' "$TURN3_LOG" && grep -qE "$SKILL_PATTERN" "$TURN3_LOG"; then
|
||||
echo "PASS: Skill 'subagent-driven-development' was triggered in Turn 3"
|
||||
TRIGGERED=true
|
||||
else
|
||||
echo "FAIL: Skill 'subagent-driven-development' was NOT triggered in Turn 3"
|
||||
TRIGGERED=false
|
||||
fi
|
||||
|
||||
# Show what skills were triggered
|
||||
echo ""
|
||||
echo "Skills triggered in Turn 3:"
|
||||
grep -o '"skill":"[^"]*"' "$TURN3_LOG" 2>/dev/null | sort -u || echo " (none)"
|
||||
|
||||
# Check for premature action in Turn 3
|
||||
echo ""
|
||||
echo "Checking for premature action in Turn 3..."
|
||||
FIRST_SKILL_LINE=$(grep -n '"name":"Skill"' "$TURN3_LOG" | head -1 | cut -d: -f1)
|
||||
if [ -n "$FIRST_SKILL_LINE" ]; then
|
||||
PREMATURE_TOOLS=$(head -n "$FIRST_SKILL_LINE" "$TURN3_LOG" | \
|
||||
grep '"type":"tool_use"' | \
|
||||
grep -v '"name":"Skill"' | \
|
||||
grep -v '"name":"TodoWrite"' || true)
|
||||
if [ -n "$PREMATURE_TOOLS" ]; then
|
||||
echo "WARNING: Tools invoked BEFORE Skill tool in Turn 3:"
|
||||
echo "$PREMATURE_TOOLS" | head -5
|
||||
else
|
||||
echo "OK: No premature tool invocations detected"
|
||||
fi
|
||||
else
|
||||
echo "WARNING: No Skill invocation found in Turn 3"
|
||||
# Show what WAS invoked
|
||||
echo ""
|
||||
echo "Tools invoked in Turn 3:"
|
||||
grep '"type":"tool_use"' "$TURN3_LOG" | grep -o '"name":"[^"]*"' | head -10 || echo " (none)"
|
||||
fi
|
||||
|
||||
# Show Turn 3 assistant response
|
||||
echo ""
|
||||
echo "Turn 3 first assistant response (truncated):"
|
||||
grep '"type":"assistant"' "$TURN3_LOG" | head -1 | jq -r '.message.content[0].text // .message.content' 2>/dev/null | head -c 500 || echo " (could not extract)"
|
||||
|
||||
echo ""
|
||||
echo "Logs:"
|
||||
echo " Turn 1: $TURN1_LOG"
|
||||
echo " Turn 2: $TURN2_LOG"
|
||||
echo " Turn 3: $TURN3_LOG"
|
||||
echo "Timestamp: $TIMESTAMP"
|
||||
|
||||
if [ "$TRIGGERED" = "true" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
136
tests/explicit-skill-requests/run-test.sh
Executable file
136
tests/explicit-skill-requests/run-test.sh
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/bin/bash
|
||||
# Test explicit skill requests (user names a skill directly)
|
||||
# Usage: ./run-test.sh <skill-name> <prompt-file>
|
||||
#
|
||||
# Tests whether Claude invokes a skill when the user explicitly requests it by name
|
||||
# (without using the plugin namespace prefix)
|
||||
#
|
||||
# Uses isolated HOME to avoid user context interference
|
||||
|
||||
set -e
|
||||
|
||||
SKILL_NAME="$1"
|
||||
PROMPT_FILE="$2"
|
||||
MAX_TURNS="${3:-3}"
|
||||
|
||||
if [ -z "$SKILL_NAME" ] || [ -z "$PROMPT_FILE" ]; then
|
||||
echo "Usage: $0 <skill-name> <prompt-file> [max-turns]"
|
||||
echo "Example: $0 subagent-driven-development ./prompts/subagent-driven-development-please.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the directory where this script lives
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Get the superpowers plugin root (two levels up)
|
||||
PLUGIN_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
TIMESTAMP=$(date +%s)
|
||||
OUTPUT_DIR="/tmp/superpowers-tests/${TIMESTAMP}/explicit-skill-requests/${SKILL_NAME}"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Read prompt from file
|
||||
PROMPT=$(cat "$PROMPT_FILE")
|
||||
|
||||
echo "=== Explicit Skill Request Test ==="
|
||||
echo "Skill: $SKILL_NAME"
|
||||
echo "Prompt file: $PROMPT_FILE"
|
||||
echo "Max turns: $MAX_TURNS"
|
||||
echo "Output dir: $OUTPUT_DIR"
|
||||
echo ""
|
||||
|
||||
# Copy prompt for reference
|
||||
cp "$PROMPT_FILE" "$OUTPUT_DIR/prompt.txt"
|
||||
|
||||
# Create a minimal project directory for the test
|
||||
PROJECT_DIR="$OUTPUT_DIR/project"
|
||||
mkdir -p "$PROJECT_DIR/docs/plans"
|
||||
|
||||
# Create a dummy plan file for mid-conversation tests
|
||||
cat > "$PROJECT_DIR/docs/plans/auth-system.md" << 'EOF'
|
||||
# Auth System Implementation Plan
|
||||
|
||||
## Task 1: Add User Model
|
||||
Create user model with email and password fields.
|
||||
|
||||
## Task 2: Add Auth Routes
|
||||
Create login and register endpoints.
|
||||
|
||||
## Task 3: Add JWT Middleware
|
||||
Protect routes with JWT validation.
|
||||
EOF
|
||||
|
||||
# Run Claude with isolated environment
|
||||
LOG_FILE="$OUTPUT_DIR/claude-output.json"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo "Plugin dir: $PLUGIN_DIR"
|
||||
echo "Running claude -p with explicit skill request..."
|
||||
echo "Prompt: $PROMPT"
|
||||
echo ""
|
||||
|
||||
timeout 300 claude -p "$PROMPT" \
|
||||
--plugin-dir "$PLUGIN_DIR" \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns "$MAX_TURNS" \
|
||||
--output-format stream-json \
|
||||
> "$LOG_FILE" 2>&1 || true
|
||||
|
||||
echo ""
|
||||
echo "=== Results ==="
|
||||
|
||||
# Check if skill was triggered (look for Skill tool invocation)
|
||||
# Match either "skill":"skillname" or "skill":"namespace:skillname"
|
||||
SKILL_PATTERN='"skill":"([^"]*:)?'"${SKILL_NAME}"'"'
|
||||
if grep -q '"name":"Skill"' "$LOG_FILE" && grep -qE "$SKILL_PATTERN" "$LOG_FILE"; then
|
||||
echo "PASS: Skill '$SKILL_NAME' was triggered"
|
||||
TRIGGERED=true
|
||||
else
|
||||
echo "FAIL: Skill '$SKILL_NAME' was NOT triggered"
|
||||
TRIGGERED=false
|
||||
fi
|
||||
|
||||
# Show what skills WERE triggered
|
||||
echo ""
|
||||
echo "Skills triggered in this run:"
|
||||
grep -o '"skill":"[^"]*"' "$LOG_FILE" 2>/dev/null | sort -u || echo " (none)"
|
||||
|
||||
# Check if Claude took action BEFORE invoking the skill (the failure mode)
|
||||
echo ""
|
||||
echo "Checking for premature action..."
|
||||
|
||||
# Look for tool invocations before the Skill invocation
|
||||
# This detects the failure mode where Claude starts doing work without loading the skill
|
||||
FIRST_SKILL_LINE=$(grep -n '"name":"Skill"' "$LOG_FILE" | head -1 | cut -d: -f1)
|
||||
if [ -n "$FIRST_SKILL_LINE" ]; then
|
||||
# Check if any non-Skill, non-system tools were invoked before the first Skill invocation
|
||||
# Filter out system messages, TodoWrite (planning is ok), and other non-action tools
|
||||
PREMATURE_TOOLS=$(head -n "$FIRST_SKILL_LINE" "$LOG_FILE" | \
|
||||
grep '"type":"tool_use"' | \
|
||||
grep -v '"name":"Skill"' | \
|
||||
grep -v '"name":"TodoWrite"' || true)
|
||||
if [ -n "$PREMATURE_TOOLS" ]; then
|
||||
echo "WARNING: Tools invoked BEFORE Skill tool:"
|
||||
echo "$PREMATURE_TOOLS" | head -5
|
||||
echo ""
|
||||
echo "This indicates Claude started working before loading the requested skill."
|
||||
else
|
||||
echo "OK: No premature tool invocations detected"
|
||||
fi
|
||||
else
|
||||
echo "WARNING: No Skill invocation found at all"
|
||||
fi
|
||||
|
||||
# Show first assistant message
|
||||
echo ""
|
||||
echo "First assistant response (truncated):"
|
||||
grep '"type":"assistant"' "$LOG_FILE" | head -1 | jq -r '.message.content[0].text // .message.content' 2>/dev/null | head -c 500 || echo " (could not extract)"
|
||||
|
||||
echo ""
|
||||
echo "Full log: $LOG_FILE"
|
||||
echo "Timestamp: $TIMESTAMP"
|
||||
|
||||
if [ "$TRIGGERED" = "true" ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -18,13 +18,13 @@ cp -r "$REPO_ROOT/lib" "$HOME/.config/opencode/superpowers/"
|
||||
cp -r "$REPO_ROOT/skills" "$HOME/.config/opencode/superpowers/"
|
||||
|
||||
# Copy plugin directory
|
||||
mkdir -p "$HOME/.config/opencode/superpowers/.opencode/plugin"
|
||||
cp "$REPO_ROOT/.opencode/plugin/superpowers.js" "$HOME/.config/opencode/superpowers/.opencode/plugin/"
|
||||
mkdir -p "$HOME/.config/opencode/superpowers/.opencode/plugins"
|
||||
cp "$REPO_ROOT/.opencode/plugins/superpowers.js" "$HOME/.config/opencode/superpowers/.opencode/plugins/"
|
||||
|
||||
# Register plugin via symlink
|
||||
mkdir -p "$HOME/.config/opencode/plugin"
|
||||
ln -sf "$HOME/.config/opencode/superpowers/.opencode/plugin/superpowers.js" \
|
||||
"$HOME/.config/opencode/plugin/superpowers.js"
|
||||
mkdir -p "$HOME/.config/opencode/plugins"
|
||||
ln -sf "$HOME/.config/opencode/superpowers/.opencode/plugins/superpowers.js" \
|
||||
"$HOME/.config/opencode/plugins/superpowers.js"
|
||||
|
||||
# Create test skills in different locations for testing
|
||||
|
||||
@@ -57,8 +57,8 @@ PROJECT_SKILL_MARKER_67890
|
||||
EOF
|
||||
|
||||
echo "Setup complete: $TEST_HOME"
|
||||
echo "Plugin installed to: $HOME/.config/opencode/superpowers/.opencode/plugin/superpowers.js"
|
||||
echo "Plugin registered at: $HOME/.config/opencode/plugin/superpowers.js"
|
||||
echo "Plugin installed to: $HOME/.config/opencode/superpowers/.opencode/plugins/superpowers.js"
|
||||
echo "Plugin registered at: $HOME/.config/opencode/plugins/superpowers.js"
|
||||
echo "Test project at: $TEST_HOME/test-project"
|
||||
|
||||
# Helper function for cleanup (call from tests or trap)
|
||||
|
||||
@@ -15,15 +15,15 @@ trap cleanup_test_env EXIT
|
||||
|
||||
# Test 1: Verify plugin file exists and is registered
|
||||
echo "Test 1: Checking plugin registration..."
|
||||
if [ -L "$HOME/.config/opencode/plugin/superpowers.js" ]; then
|
||||
if [ -L "$HOME/.config/opencode/plugins/superpowers.js" ]; then
|
||||
echo " [PASS] Plugin symlink exists"
|
||||
else
|
||||
echo " [FAIL] Plugin symlink not found at $HOME/.config/opencode/plugin/superpowers.js"
|
||||
echo " [FAIL] Plugin symlink not found at $HOME/.config/opencode/plugins/superpowers.js"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify symlink target exists
|
||||
if [ -f "$(readlink -f "$HOME/.config/opencode/plugin/superpowers.js")" ]; then
|
||||
if [ -f "$(readlink -f "$HOME/.config/opencode/plugins/superpowers.js")" ]; then
|
||||
echo " [PASS] Plugin symlink target exists"
|
||||
else
|
||||
echo " [FAIL] Plugin symlink target does not exist"
|
||||
@@ -60,7 +60,7 @@ fi
|
||||
|
||||
# Test 5: Verify plugin JavaScript syntax (basic check)
|
||||
echo "Test 5: Checking plugin JavaScript syntax..."
|
||||
plugin_file="$HOME/.config/opencode/superpowers/.opencode/plugin/superpowers.js"
|
||||
plugin_file="$HOME/.config/opencode/superpowers/.opencode/plugins/superpowers.js"
|
||||
if node --check "$plugin_file" 2>/dev/null; then
|
||||
echo " [PASS] Plugin JavaScript syntax is valid"
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user