diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..7387a83b3
--- /dev/null
+++ b/.gitattributes
@@ -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
diff --git a/.opencode/plugin/superpowers.js b/.opencode/plugin/superpowers.js
index 3b441f956..8ac993462 100644
--- a/.opencode/plugin/superpowers.js
+++ b/.opencode/plugin/superpowers.js
@@ -1,73 +1,81 @@
/**
* Superpowers plugin for OpenCode.ai
*
- * Provides custom tools for loading and discovering skills,
- * with prompt generation for agent configuration.
+ * 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';
-import { tool } from '@opencode-ai/plugin/tool';
-import * as skillsCore from '../../lib/skills-core.js';
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;
- // Expand ~ to home directory
if (normalized.startsWith('~/')) {
normalized = path.join(homeDir, normalized.slice(2));
} else if (normalized === '~') {
normalized = homeDir;
}
- // Resolve to absolute path
return path.resolve(normalized);
};
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');
- // Respect OPENCODE_CONFIG_DIR if set, otherwise fall back to default
const envConfigDir = normalizePath(process.env.OPENCODE_CONFIG_DIR, homeDir);
const configDir = envConfigDir || path.join(homeDir, '.config/opencode');
- const personalSkillsDir = path.join(configDir, '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 getBootstrapContent = () => {
+ // Try to load using-superpowers skill
+ const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md');
+ if (!fs.existsSync(skillPath)) return null;
- const toolMapping = compact
- ? `**Tool Mapping:** TodoWrite->update_plan, Task->@mention, Skill->use_skill
+ const fullContent = fs.readFileSync(skillPath, 'utf8');
+ const { content } = extractAndStripFrontmatter(fullContent);
-**Skills naming (priority order):** project: > personal > superpowers:`
- : `**Tool Mapping for OpenCode:**
+ 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 → \`use_skill\` custom tool
+- \`Skill\` tool → OpenCode's native \`skill\` 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 ${configDir}/skills/)
-- Superpowers skills: \`superpowers:skill-name\`
-- Project skills override personal, which override superpowers when names match`;
+**Skills location:**
+Superpowers skills are in \`${configDir}/skills/superpowers/\`
+Use OpenCode's native \`skill\` tool to list and load skills.`;
return `
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.**
+**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}
@@ -75,159 +83,12 @@ ${toolMapping}
`;
};
- // 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: {
- agent: context.agent,
- 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 ${superpowersSkillsDir}/ or add personal skills to ${personalSkillsDir}/`;
- }
-
- 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);
- }
+ // 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);
}
}
};
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 5ab95451d..f13c6846d 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -1,5 +1,35 @@
# Superpowers Release Notes
+## Unreleased
+
+### 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
diff --git a/docs/README.opencode.md b/docs/README.opencode.md
index 122fe55ea..4370625a8 100644
--- a/docs/README.opencode.md
+++ b/docs/README.opencode.md
@@ -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/plugin, then symlink ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js to ~/.config/opencode/plugin/superpowers.js, then symlink ~/.config/opencode/superpowers/skills to ~/.config/opencode/skills/superpowers, then restart opencode.
```
## Manual Installation
@@ -15,59 +15,171 @@ 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
+### macOS / Linux
-#### 1. Install Superpowers
+```bash
+# 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/plugin ~/.config/opencode/skills
+
+# 3. Create symlinks (safe for reinstalls - ln -sf overwrites)
+ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js ~/.config/opencode/plugin/superpowers.js
+ln -sf ~/.config/opencode/superpowers/skills ~/.config/opencode/skills/superpowers
+
+# 4. Restart OpenCode
+```
+
+#### Verify Installation
```bash
-mkdir -p ~/.config/opencode/superpowers
-git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
+ls -l ~/.config/opencode/plugin/superpowers.js
+ls -l ~/.config/opencode/skills/superpowers
```
-#### 2. Register the Plugin
+Both should show symlinks pointing to the superpowers directory.
-OpenCode discovers plugins from `~/.config/opencode/plugin/`. Create a symlink:
+### Windows
-```bash
-mkdir -p ~/.config/opencode/plugin
-ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js ~/.config/opencode/plugin/superpowers.js
+**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\plugin" 2>nul
+mkdir "%USERPROFILE%\.config\opencode\skills" 2>nul
+
+:: 3. Remove existing links (safe for reinstalls)
+del "%USERPROFILE%\.config\opencode\plugin\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\plugin\superpowers.js" "%USERPROFILE%\.config\opencode\superpowers\.opencode\plugin\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
```
-Alternatively, for project-local installation:
+#### 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\plugin"
+New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.config\opencode\skills"
+
+# 3. Remove existing links (safe for reinstalls)
+Remove-Item "$env:USERPROFILE\.config\opencode\plugin\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\plugin\superpowers.js" -Target "$env:USERPROFILE\.config\opencode\superpowers\.opencode\plugin\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
-# In your OpenCode project
-mkdir -p .opencode/plugin
-ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js .opencode/plugin/superpowers.js
+# 1. Install Superpowers
+git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
+
+# 2. Create directories
+mkdir -p ~/.config/opencode/plugin ~/.config/opencode/skills
+
+# 3. Remove existing links (safe for reinstalls)
+rm -f ~/.config/opencode/plugin/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/plugin/superpowers.js)\" \"$(cygpath -w ~/.config/opencode/superpowers/.opencode/plugin/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
+```
+
+#### WSL Users
+
+If running OpenCode inside WSL, use the [macOS / Linux](#macos--linux) instructions instead.
+
+#### Verify Installation
+
+**Command Prompt:**
+```cmd
+dir /AL "%USERPROFILE%\.config\opencode\plugin"
+dir /AL "%USERPROFILE%\.config\opencode\skills"
+```
+
+**PowerShell:**
+```powershell
+Get-ChildItem "$env:USERPROFILE\.config\opencode\plugin" | Where-Object { $_.LinkType }
+Get-ChildItem "$env:USERPROFILE\.config\opencode\skills" | Where-Object { $_.LinkType }
```
-#### 3. Restart OpenCode
+Look for `` or `` in the output.
+
+#### 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
-Restart OpenCode to load the plugin. Superpowers will automatically activate.
+**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,40 +223,31 @@ 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.
-
-### Message Insertion Pattern
+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.
-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.
+### Native Skills Integration
-### 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
@@ -154,23 +257,14 @@ Skills written for Claude Code are automatically adapted for OpenCode. The plugi
**Location:** `~/.config/opencode/superpowers/.opencode/plugin/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)
-
-### Shared Core Module
+- `experimental.chat.system.transform` hook for bootstrap injection
+- Reads and injects the "using-superpowers" skill content
-**Location:** `~/.config/opencode/superpowers/lib/skills-core.js`
+### Skills
-**Functions:**
-- `extractFrontmatter()` - Parse skill metadata
-- `stripFrontmatter()` - Remove metadata from content
-- `findSkillsInDir()` - Recursive skill discovery
-- `resolveSkillPath()` - Skill resolution with shadowing
-- `checkForUpdates()` - Git update detection
+**Location:** `~/.config/opencode/skills/superpowers/` (symlink to `~/.config/opencode/superpowers/skills/`)
-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 +279,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/plugin/superpowers.js`
+2. Check symlink/junction: `ls -l ~/.config/opencode/plugin/` (macOS/Linux) or `dir /AL %USERPROFILE%\.config\opencode\plugin` (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 +310,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
+
+# Check skills are discoverable
+opencode run "use skill tool to list all skills" 2>&1 | grep -i superpowers
-# Run specific test
-./tests/opencode/run-tests.sh --test test-tools.sh
+# 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/`.
diff --git a/hooks/hooks.json b/hooks/hooks.json
index d1745650c..17e0ac87b 100644
--- a/hooks/hooks.json
+++ b/hooks/hooks.json
@@ -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"
}
]
}
diff --git a/hooks/run-hook.cmd b/hooks/run-hook.cmd
index 8d8458fcb..b2a8b3acc 100755
--- a/hooks/run-hook.cmd
+++ b/hooks/run-hook.cmd
@@ -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 [args...]
REM The script should be in the same directory as this wrapper