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