mirror of
https://github.com/obra/superpowers.git
synced 2026-06-09 17:02:07 +00:00
Compare commits
279 Commits
v4.0.1
...
codex/open
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc6082d7ff | ||
|
|
772332a476 | ||
|
|
e795530c23 | ||
|
|
28fd7a8192 | ||
|
|
831f6f977c | ||
|
|
5745f0ea99 | ||
|
|
b1c15fd9f8 | ||
|
|
abb801b7ef | ||
|
|
88eb6679ae | ||
|
|
9b3045a8fa | ||
|
|
7e59af8148 | ||
|
|
c13a884c06 | ||
|
|
1ca87f861f | ||
|
|
3adb7a0ffd | ||
|
|
49bcb34ccc | ||
|
|
6efe32c9e2 | ||
|
|
b55764852a | ||
|
|
9f42444ab1 | ||
|
|
99e4c656bf | ||
|
|
a5dd364e42 | ||
|
|
f55f8df447 | ||
|
|
c4bbe651cb | ||
|
|
34c17aefb2 | ||
|
|
f9b088f7b3 | ||
|
|
bc25777c6a | ||
|
|
bcdd7fa24c | ||
|
|
6149f3635a | ||
|
|
777a9770d8 | ||
|
|
da283df058 | ||
|
|
a569527b89 | ||
|
|
ac1c715ffb | ||
|
|
8c8c5e87ce | ||
|
|
a5d36b1300 | ||
|
|
1e8000a4d4 | ||
|
|
c61f75030f | ||
|
|
e3dd3b4c5a | ||
|
|
e4a15b6d52 | ||
|
|
998c40be29 | ||
|
|
98263ce179 | ||
|
|
4c49406d22 | ||
|
|
238167f291 | ||
|
|
118d85b7e7 | ||
|
|
0f4d7d67c1 | ||
|
|
61ad4821da | ||
|
|
9dd13e534f | ||
|
|
77f98c5805 | ||
|
|
c62b835a0b | ||
|
|
5dade17572 | ||
|
|
4652e65ec8 | ||
|
|
abaaf8a6e6 | ||
|
|
917e5f53b1 | ||
|
|
a6b1a1fa0c | ||
|
|
c6d66a0bc7 | ||
|
|
7ebda5c81b | ||
|
|
2e53549478 | ||
|
|
79fee93c4e | ||
|
|
b7a8f76985 | ||
|
|
4b1b20f69f | ||
|
|
eeaf2ad15b | ||
|
|
dd237283db | ||
|
|
c0b417e409 | ||
|
|
1f20bef3f5 | ||
|
|
f0df5eca30 | ||
|
|
0a1124ba53 | ||
|
|
65d760f9c2 | ||
|
|
2d942f3b01 | ||
|
|
8b1669269c | ||
|
|
a2964d7a20 | ||
|
|
8b9a5da90b | ||
|
|
04ff6660e8 | ||
|
|
471aa93a4c | ||
|
|
872172870d | ||
|
|
ed06287a8a | ||
|
|
5406747197 | ||
|
|
879940ba5e | ||
|
|
eafe962b18 | ||
|
|
9f04f06351 | ||
|
|
f076bd3431 | ||
|
|
9e3ed213a0 | ||
|
|
9e6e077d33 | ||
|
|
151cfb16a0 | ||
|
|
a1155f623f | ||
|
|
3f80f1c769 | ||
|
|
4ae1a3d6a6 | ||
|
|
e6221a48c5 | ||
|
|
4fd9aa2dd5 | ||
|
|
2b1bfe5db6 | ||
|
|
bd080e3cc8 | ||
|
|
eb2b44b23f | ||
|
|
80c0a45fcc | ||
|
|
c28b28ffbd | ||
|
|
33e9bea3cc | ||
|
|
74a0c004eb | ||
|
|
0f306f0d18 | ||
|
|
af025aa35b | ||
|
|
738a18d6ff | ||
|
|
94b2bcbb24 | ||
|
|
ed4103ab91 | ||
|
|
ab500dade6 | ||
|
|
a22122d57f | ||
|
|
218c3ed93e | ||
|
|
9fa8ceb101 | ||
|
|
b045fa3950 | ||
|
|
bf8f7572eb | ||
|
|
c141508f36 | ||
|
|
7820adcde7 | ||
|
|
250dea46fd | ||
|
|
477c55386a | ||
|
|
cb4745eeb5 | ||
|
|
872ec69f4c | ||
|
|
e0fcfaf838 | ||
|
|
5bf3f77483 | ||
|
|
8ea39819ee | ||
|
|
764215331d | ||
|
|
eccd45305a | ||
|
|
fb4adab518 | ||
|
|
7e516434f2 | ||
|
|
8a0a5ca6a3 | ||
|
|
2d46da1b37 | ||
|
|
0002948041 | ||
|
|
3128a2c3cd | ||
|
|
f34ee479b7 | ||
|
|
3cee13e516 | ||
|
|
1128a721ca | ||
|
|
d1b5f578b0 | ||
|
|
61a64d7098 | ||
|
|
825a142aa3 | ||
|
|
bd537d817d | ||
|
|
24be2e8b7c | ||
|
|
a479e10050 | ||
|
|
a4c48714bc | ||
|
|
2c6a8a352d | ||
|
|
2b25774f31 | ||
|
|
f4b54a1717 | ||
|
|
911fa1d6c5 | ||
|
|
4e7c0842f8 | ||
|
|
689f27c968 | ||
|
|
537ec640fd | ||
|
|
c5e9538311 | ||
|
|
fd318b1b79 | ||
|
|
ea472dedf0 | ||
|
|
addfe8511a | ||
|
|
c6a2b1b576 | ||
|
|
d19703b0a1 | ||
|
|
6d21e9cc07 | ||
|
|
687a66183d | ||
|
|
363923f74a | ||
|
|
3188953b0c | ||
|
|
9ccce3bf07 | ||
|
|
b484bae134 | ||
|
|
ec99b7c4a4 | ||
|
|
263e3268f4 | ||
|
|
85cab6eff0 | ||
|
|
7619570679 | ||
|
|
8d9b94eb8d | ||
|
|
7f6380dd91 | ||
|
|
8d6d876424 | ||
|
|
9c98e01873 | ||
|
|
5ef73d25b7 | ||
|
|
920559aea7 | ||
|
|
9d2b886211 | ||
|
|
ec26561aaa | ||
|
|
f0a4538b31 | ||
|
|
f7b6107576 | ||
|
|
e02842e024 | ||
|
|
7446c842d8 | ||
|
|
5e2a89e985 | ||
|
|
d3c028e280 | ||
|
|
7f8edd9c12 | ||
|
|
81acbcd51e | ||
|
|
c070e6bd45 | ||
|
|
5f14c1aa29 | ||
|
|
bdbad07f02 | ||
|
|
419889b0d3 | ||
|
|
715e18e448 | ||
|
|
21a774e95c | ||
|
|
9df7269d73 | ||
|
|
5e5d353916 | ||
|
|
c5e6eaf411 | ||
|
|
bdd45c70ab | ||
|
|
ec3f7f1027 | ||
|
|
edbb62e50f | ||
|
|
33e55e60b2 | ||
|
|
74f2b1c96e | ||
|
|
991e9d4de9 | ||
|
|
133a0a80c6 | ||
|
|
57b346ddbc | ||
|
|
8c01ac8051 | ||
|
|
245d50ec37 | ||
|
|
aba2542f5e | ||
|
|
3bdd66eaa5 | ||
|
|
c3ecc1b9ba | ||
|
|
f3083e55b0 | ||
|
|
70244011d4 | ||
|
|
d48b14e5ac | ||
|
|
daa3fb2322 | ||
|
|
69eaf3cf34 | ||
|
|
582264a54a | ||
|
|
7b99c39c08 | ||
|
|
6c274dcc2a | ||
|
|
ee14caeadd | ||
|
|
5e51c3ee5a | ||
|
|
f57638a747 | ||
|
|
4180afb7bd | ||
|
|
e4226df22e | ||
|
|
866f2bdb47 | ||
|
|
3c220d0cc1 | ||
|
|
02b3d7c96d | ||
|
|
1c53f5deb6 | ||
|
|
a26cbaab2e | ||
|
|
b23c084070 | ||
|
|
aa3bb5fe16 | ||
|
|
3d245777f0 | ||
|
|
26d7cca61b | ||
|
|
ad716b8d1b | ||
|
|
e4a2375cb7 | ||
|
|
d2d6cf4852 | ||
|
|
54d9133d7a | ||
|
|
394cf85013 | ||
|
|
31bbbe2dbb | ||
|
|
5fbefbd0a9 | ||
|
|
a0b9ecce2b | ||
|
|
772ec9f834 | ||
|
|
e16d611eee | ||
|
|
b7cad76134 | ||
|
|
4c836817da | ||
|
|
7f2ee614b6 | ||
|
|
b97b5f228d | ||
|
|
93c8966cab | ||
|
|
19df3db59b | ||
|
|
f8cf545bc5 | ||
|
|
a98c5dfc9d | ||
|
|
a72e416979 | ||
|
|
8dd31c3da5 | ||
|
|
6a07692da1 | ||
|
|
0771fd7cd1 | ||
|
|
bcccc69271 | ||
|
|
3626ccc53e | ||
|
|
47d3df7acc | ||
|
|
d41f951c4a | ||
|
|
b4f56fec1b | ||
|
|
1143f9be3d | ||
|
|
6cc2d8c920 | ||
|
|
038abed026 | ||
|
|
961052e0f9 | ||
|
|
689e2a77fc | ||
|
|
5e0d2f8175 | ||
|
|
06b92f3682 | ||
|
|
9819209bba | ||
|
|
c7816ee2a6 | ||
|
|
b323e35805 | ||
|
|
bb2ff5d309 | ||
|
|
b63d485955 | ||
|
|
fa3f46d4e9 | ||
|
|
f8dbe7b196 | ||
|
|
93cf2ee84f | ||
|
|
1872f50b64 | ||
|
|
8904b7d9dc | ||
|
|
469a6d81eb | ||
|
|
4b6cef98ac | ||
|
|
03087b13b8 | ||
|
|
493ac18dfe | ||
|
|
35d4fbcd0b | ||
|
|
19c70afc99 | ||
|
|
405a025eea | ||
|
|
36fcd57626 | ||
|
|
3964d18670 | ||
|
|
a01a135fe1 | ||
|
|
ac471e69c2 | ||
|
|
a08f088968 | ||
|
|
b9e16498b9 | ||
|
|
f6d50c74b2 | ||
|
|
3dac35e0b3 | ||
|
|
131c1f189f | ||
|
|
9baedaa117 | ||
|
|
66a2dbd80a | ||
|
|
1455ac0631 | ||
|
|
e64ad670df | ||
|
|
c037dcbf4b |
@@ -9,7 +9,7 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "4.0.1",
|
||||
"version": "5.0.7",
|
||||
"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.1",
|
||||
"version": "5.0.7",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
@@ -9,5 +9,12 @@
|
||||
"homepage": "https://github.com/obra/superpowers",
|
||||
"repository": "https://github.com/obra/superpowers",
|
||||
"license": "MIT",
|
||||
"keywords": ["skills", "tdd", "debugging", "collaboration", "best-practices", "workflows"]
|
||||
"keywords": [
|
||||
"skills",
|
||||
"tdd",
|
||||
"debugging",
|
||||
"collaboration",
|
||||
"best-practices",
|
||||
"workflows"
|
||||
]
|
||||
}
|
||||
|
||||
47
.codex-plugin/plugin.json
Normal file
47
.codex-plugin/plugin.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "5.0.7",
|
||||
"description": "An agentic skills framework & software development methodology that works: planning, TDD, debugging, and collaboration workflows.",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com",
|
||||
"url": "https://github.com/obra"
|
||||
},
|
||||
"homepage": "https://github.com/obra/superpowers",
|
||||
"repository": "https://github.com/obra/superpowers",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"brainstorming",
|
||||
"subagent-driven-development",
|
||||
"skills",
|
||||
"planning",
|
||||
"tdd",
|
||||
"debugging",
|
||||
"code-review",
|
||||
"workflow"
|
||||
],
|
||||
"skills": "./skills/",
|
||||
"interface": {
|
||||
"displayName": "Superpowers",
|
||||
"shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents",
|
||||
"longDescription": "Use Superpowers to guide agent work through brainstorming, implementation planning, test-driven development, systematic debugging, parallel execution, code review, and finish-the-branch workflows.",
|
||||
"developerName": "Jesse Vincent",
|
||||
"category": "Coding",
|
||||
"capabilities": [
|
||||
"Interactive",
|
||||
"Read",
|
||||
"Write"
|
||||
],
|
||||
"defaultPrompt": [
|
||||
"I've got an idea for something I'd like to build.",
|
||||
"Let's add a feature to this project."
|
||||
],
|
||||
"websiteURL": "https://github.com/obra/superpowers",
|
||||
"privacyPolicyURL": "https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement",
|
||||
"termsOfServiceURL": "https://docs.github.com/en/site-policy/github-terms/github-terms-of-service",
|
||||
"brandColor": "#F59E0B",
|
||||
"composerIcon": "./assets/superpowers-small.svg",
|
||||
"logo": "./assets/app-icon.png",
|
||||
"screenshots": []
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,67 @@
|
||||
# Installing Superpowers for Codex
|
||||
|
||||
Quick setup to enable superpowers skills in Codex.
|
||||
Enable superpowers skills in Codex via native skill discovery. Just clone and symlink.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Git
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Clone superpowers repository**:
|
||||
1. **Clone the superpowers repository:**
|
||||
```bash
|
||||
mkdir -p ~/.codex/superpowers
|
||||
cd ~/.codex/superpowers
|
||||
git clone https://github.com/obra/superpowers.git .
|
||||
git clone https://github.com/obra/superpowers.git ~/.codex/superpowers
|
||||
```
|
||||
|
||||
2. **Create personal skills directory**:
|
||||
2. **Create the skills symlink:**
|
||||
```bash
|
||||
mkdir -p ~/.codex/skills
|
||||
mkdir -p ~/.agents/skills
|
||||
ln -s ~/.codex/superpowers/skills ~/.agents/skills/superpowers
|
||||
```
|
||||
|
||||
3. **Update ~/.codex/AGENTS.md** to include this superpowers section:
|
||||
```markdown
|
||||
## Superpowers System
|
||||
|
||||
<EXTREMELY_IMPORTANT>
|
||||
You have superpowers. Superpowers teach you new skills and capabilities. RIGHT NOW run: `~/.codex/superpowers/.codex/superpowers-codex bootstrap` and follow the instructions it returns.
|
||||
</EXTREMELY_IMPORTANT>
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills"
|
||||
cmd /c mklink /J "$env:USERPROFILE\.agents\skills\superpowers" "$env:USERPROFILE\.codex\superpowers\skills"
|
||||
```
|
||||
|
||||
## Verification
|
||||
3. **Restart Codex** (quit and relaunch the CLI) to discover the skills.
|
||||
|
||||
## Migrating from old bootstrap
|
||||
|
||||
If you installed superpowers before native skill discovery, you need to:
|
||||
|
||||
1. **Update the repo:**
|
||||
```bash
|
||||
cd ~/.codex/superpowers && git pull
|
||||
```
|
||||
|
||||
2. **Create the skills symlink** (step 2 above) — this is the new discovery mechanism.
|
||||
|
||||
3. **Remove the old bootstrap block** from `~/.codex/AGENTS.md` — any block referencing `superpowers-codex bootstrap` is no longer needed.
|
||||
|
||||
4. **Restart Codex.**
|
||||
|
||||
## Verify
|
||||
|
||||
Test the installation:
|
||||
```bash
|
||||
~/.codex/superpowers/.codex/superpowers-codex bootstrap
|
||||
ls -la ~/.agents/skills/superpowers
|
||||
```
|
||||
|
||||
You should see skill listings and bootstrap instructions. The system is now ready for use.
|
||||
You should see a symlink (or junction on Windows) pointing to your superpowers skills directory.
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd ~/.codex/superpowers && git pull
|
||||
```
|
||||
|
||||
Skills update instantly through the symlink.
|
||||
|
||||
## Uninstalling
|
||||
|
||||
```bash
|
||||
rm ~/.agents/skills/superpowers
|
||||
```
|
||||
|
||||
Optionally delete the clone: `rm -rf ~/.codex/superpowers`.
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# Superpowers Bootstrap for Codex
|
||||
|
||||
<EXTREMELY_IMPORTANT>
|
||||
You have superpowers.
|
||||
|
||||
**Tool for running skills:**
|
||||
- `~/.codex/superpowers/.codex/superpowers-codex use-skill <skill-name>`
|
||||
|
||||
**Tool Mapping for Codex:**
|
||||
When skills reference tools you don't have, substitute your equivalent tools:
|
||||
- `TodoWrite` → `update_plan` (your planning/task tracking tool)
|
||||
- `Task` tool with subagents → Tell the user that subagents aren't available in Codex yet and you'll do the work the subagent would do
|
||||
- `Skill` tool → `~/.codex/superpowers/.codex/superpowers-codex use-skill` command (already available)
|
||||
- `Read`, `Write`, `Edit`, `Bash` → Use your native tools with similar functions
|
||||
|
||||
**Skills naming:**
|
||||
- Superpowers skills: `superpowers:skill-name` (from ~/.codex/superpowers/skills/)
|
||||
- Personal skills: `skill-name` (from ~/.codex/skills/)
|
||||
- Personal skills override superpowers skills when names match
|
||||
|
||||
**Critical Rules:**
|
||||
- Before ANY task, review the skills list (shown below)
|
||||
- If a relevant skill exists, you MUST use `~/.codex/superpowers/.codex/superpowers-codex use-skill` to load it
|
||||
- Announce: "I've read the [Skill Name] skill and I'm using it to [purpose]"
|
||||
- Skills with checklists require `update_plan` todos for each item
|
||||
- NEVER skip mandatory workflows (brainstorming before coding, TDD, systematic debugging)
|
||||
|
||||
**Skills location:**
|
||||
- Superpowers skills: ~/.codex/superpowers/skills/
|
||||
- Personal skills: ~/.codex/skills/ (override superpowers when names match)
|
||||
|
||||
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
|
||||
</EXTREMELY_IMPORTANT>
|
||||
@@ -1,267 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const skillsCore = require('../lib/skills-core');
|
||||
|
||||
// Paths
|
||||
const homeDir = os.homedir();
|
||||
const superpowersSkillsDir = path.join(homeDir, '.codex', 'superpowers', 'skills');
|
||||
const personalSkillsDir = path.join(homeDir, '.codex', 'skills');
|
||||
const bootstrapFile = path.join(homeDir, '.codex', 'superpowers', '.codex', 'superpowers-bootstrap.md');
|
||||
const superpowersRepoDir = path.join(homeDir, '.codex', 'superpowers');
|
||||
|
||||
// Utility functions
|
||||
function printSkill(skillPath, sourceType) {
|
||||
const skillFile = path.join(skillPath, 'SKILL.md');
|
||||
const relPath = sourceType === 'personal'
|
||||
? path.relative(personalSkillsDir, skillPath)
|
||||
: path.relative(superpowersSkillsDir, skillPath);
|
||||
|
||||
// Print skill name with namespace
|
||||
if (sourceType === 'personal') {
|
||||
console.log(relPath.replace(/\\/g, '/')); // Personal skills are not namespaced
|
||||
} else {
|
||||
console.log(`superpowers:${relPath.replace(/\\/g, '/')}`); // Superpowers skills get superpowers namespace
|
||||
}
|
||||
|
||||
// Extract and print metadata
|
||||
const { name, description } = skillsCore.extractFrontmatter(skillFile);
|
||||
|
||||
if (description) console.log(` ${description}`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Commands
|
||||
function runFindSkills() {
|
||||
console.log('Available skills:');
|
||||
console.log('==================');
|
||||
console.log('');
|
||||
|
||||
const foundSkills = new Set();
|
||||
|
||||
// Find personal skills first (these take precedence)
|
||||
const personalSkills = skillsCore.findSkillsInDir(personalSkillsDir, 'personal', 2);
|
||||
for (const skill of personalSkills) {
|
||||
const relPath = path.relative(personalSkillsDir, skill.path);
|
||||
foundSkills.add(relPath);
|
||||
printSkill(skill.path, 'personal');
|
||||
}
|
||||
|
||||
// Find superpowers skills (only if not already found in personal)
|
||||
const superpowersSkills = skillsCore.findSkillsInDir(superpowersSkillsDir, 'superpowers', 1);
|
||||
for (const skill of superpowersSkills) {
|
||||
const relPath = path.relative(superpowersSkillsDir, skill.path);
|
||||
if (!foundSkills.has(relPath)) {
|
||||
printSkill(skill.path, 'superpowers');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Usage:');
|
||||
console.log(' superpowers-codex use-skill <skill-name> # Load a specific skill');
|
||||
console.log('');
|
||||
console.log('Skill naming:');
|
||||
console.log(' Superpowers skills: superpowers:skill-name (from ~/.codex/superpowers/skills/)');
|
||||
console.log(' Personal skills: skill-name (from ~/.codex/skills/)');
|
||||
console.log(' Personal skills override superpowers skills when names match.');
|
||||
console.log('');
|
||||
console.log('Note: All skills are disclosed at session start via bootstrap.');
|
||||
}
|
||||
|
||||
function runBootstrap() {
|
||||
console.log('# Superpowers Bootstrap for Codex');
|
||||
console.log('# ================================');
|
||||
console.log('');
|
||||
|
||||
// Check for updates (with timeout protection)
|
||||
if (skillsCore.checkForUpdates(superpowersRepoDir)) {
|
||||
console.log('## Update Available');
|
||||
console.log('');
|
||||
console.log('⚠️ Your superpowers installation is behind the latest version.');
|
||||
console.log('To update, run: `cd ~/.codex/superpowers && git pull`');
|
||||
console.log('');
|
||||
console.log('---');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Show the bootstrap instructions
|
||||
if (fs.existsSync(bootstrapFile)) {
|
||||
console.log('## Bootstrap Instructions:');
|
||||
console.log('');
|
||||
try {
|
||||
const content = fs.readFileSync(bootstrapFile, 'utf8');
|
||||
console.log(content);
|
||||
} catch (error) {
|
||||
console.log(`Error reading bootstrap file: ${error.message}`);
|
||||
}
|
||||
console.log('');
|
||||
console.log('---');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Run find-skills to show available skills
|
||||
console.log('## Available Skills:');
|
||||
console.log('');
|
||||
runFindSkills();
|
||||
|
||||
console.log('');
|
||||
console.log('---');
|
||||
console.log('');
|
||||
|
||||
// Load the using-superpowers skill automatically
|
||||
console.log('## Auto-loading superpowers:using-superpowers skill:');
|
||||
console.log('');
|
||||
runUseSkill('superpowers:using-superpowers');
|
||||
|
||||
console.log('');
|
||||
console.log('---');
|
||||
console.log('');
|
||||
console.log('# Bootstrap Complete!');
|
||||
console.log('# You now have access to all superpowers skills.');
|
||||
console.log('# Use "superpowers-codex use-skill <skill>" to load and apply skills.');
|
||||
console.log('# Remember: If a skill applies to your task, you MUST use it!');
|
||||
}
|
||||
|
||||
function runUseSkill(skillName) {
|
||||
if (!skillName) {
|
||||
console.log('Usage: superpowers-codex use-skill <skill-name>');
|
||||
console.log('Examples:');
|
||||
console.log(' superpowers-codex use-skill superpowers:brainstorming # Load superpowers skill');
|
||||
console.log(' superpowers-codex use-skill brainstorming # Load personal skill (or superpowers if not found)');
|
||||
console.log(' superpowers-codex use-skill my-custom-skill # Load personal skill');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle namespaced skill names
|
||||
let actualSkillPath;
|
||||
let forceSuperpowers = false;
|
||||
|
||||
if (skillName.startsWith('superpowers:')) {
|
||||
// Remove the superpowers: namespace prefix
|
||||
actualSkillPath = skillName.substring('superpowers:'.length);
|
||||
forceSuperpowers = true;
|
||||
} else {
|
||||
actualSkillPath = skillName;
|
||||
}
|
||||
|
||||
// Remove "skills/" prefix if present
|
||||
if (actualSkillPath.startsWith('skills/')) {
|
||||
actualSkillPath = actualSkillPath.substring('skills/'.length);
|
||||
}
|
||||
|
||||
// Function to find skill file
|
||||
function findSkillFile(searchPath) {
|
||||
// Check for exact match with SKILL.md
|
||||
const skillMdPath = path.join(searchPath, 'SKILL.md');
|
||||
if (fs.existsSync(skillMdPath)) {
|
||||
return skillMdPath;
|
||||
}
|
||||
|
||||
// Check for direct SKILL.md file
|
||||
if (searchPath.endsWith('SKILL.md') && fs.existsSync(searchPath)) {
|
||||
return searchPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let skillFile = null;
|
||||
|
||||
// If superpowers: namespace was used, only check superpowers skills
|
||||
if (forceSuperpowers) {
|
||||
if (fs.existsSync(superpowersSkillsDir)) {
|
||||
const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
|
||||
skillFile = findSkillFile(superpowersPath);
|
||||
}
|
||||
} else {
|
||||
// First check personal skills directory (takes precedence)
|
||||
if (fs.existsSync(personalSkillsDir)) {
|
||||
const personalPath = path.join(personalSkillsDir, actualSkillPath);
|
||||
skillFile = findSkillFile(personalPath);
|
||||
if (skillFile) {
|
||||
console.log(`# Loading personal skill: ${actualSkillPath}`);
|
||||
console.log(`# Source: ${skillFile}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in personal, check superpowers skills
|
||||
if (!skillFile && fs.existsSync(superpowersSkillsDir)) {
|
||||
const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
|
||||
skillFile = findSkillFile(superpowersPath);
|
||||
if (skillFile) {
|
||||
console.log(`# Loading superpowers skill: superpowers:${actualSkillPath}`);
|
||||
console.log(`# Source: ${skillFile}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found, error
|
||||
if (!skillFile) {
|
||||
console.log(`Error: Skill not found: ${actualSkillPath}`);
|
||||
console.log('');
|
||||
console.log('Available skills:');
|
||||
runFindSkills();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract frontmatter and content using shared core functions
|
||||
let content, frontmatter;
|
||||
try {
|
||||
const fullContent = fs.readFileSync(skillFile, 'utf8');
|
||||
const { name, description } = skillsCore.extractFrontmatter(skillFile);
|
||||
content = skillsCore.stripFrontmatter(fullContent);
|
||||
frontmatter = { name, description };
|
||||
} catch (error) {
|
||||
console.log(`Error reading skill file: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Display skill header with clean info
|
||||
const displayName = forceSuperpowers ? `superpowers:${actualSkillPath}` :
|
||||
(skillFile.includes(personalSkillsDir) ? actualSkillPath : `superpowers:${actualSkillPath}`);
|
||||
|
||||
const skillDirectory = path.dirname(skillFile);
|
||||
|
||||
console.log(`# ${frontmatter.name || displayName}`);
|
||||
if (frontmatter.description) {
|
||||
console.log(`# ${frontmatter.description}`);
|
||||
}
|
||||
console.log(`# Skill-specific tools and reference files live in ${skillDirectory}`);
|
||||
console.log('# ============================================');
|
||||
console.log('');
|
||||
|
||||
// Display the skill content (without frontmatter)
|
||||
console.log(content);
|
||||
|
||||
}
|
||||
|
||||
// Main CLI
|
||||
const command = process.argv[2];
|
||||
const arg = process.argv[3];
|
||||
|
||||
switch (command) {
|
||||
case 'bootstrap':
|
||||
runBootstrap();
|
||||
break;
|
||||
case 'use-skill':
|
||||
runUseSkill(arg);
|
||||
break;
|
||||
case 'find-skills':
|
||||
runFindSkills();
|
||||
break;
|
||||
default:
|
||||
console.log('Superpowers for Codex');
|
||||
console.log('Usage:');
|
||||
console.log(' superpowers-codex bootstrap # Run complete bootstrap with all skills');
|
||||
console.log(' superpowers-codex use-skill <skill-name> # Load a specific skill');
|
||||
console.log(' superpowers-codex find-skills # List all available skills');
|
||||
console.log('');
|
||||
console.log('Examples:');
|
||||
console.log(' superpowers-codex bootstrap');
|
||||
console.log(' superpowers-codex use-skill superpowers:brainstorming');
|
||||
console.log(' superpowers-codex use-skill my-custom-skill');
|
||||
break;
|
||||
}
|
||||
25
.cursor-plugin/plugin.json
Normal file
25
.cursor-plugin/plugin.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"displayName": "Superpowers",
|
||||
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "5.0.7",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
},
|
||||
"homepage": "https://github.com/obra/superpowers",
|
||||
"repository": "https://github.com/obra/superpowers",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"skills",
|
||||
"tdd",
|
||||
"debugging",
|
||||
"collaboration",
|
||||
"best-practices",
|
||||
"workflows"
|
||||
],
|
||||
"skills": "./skills/",
|
||||
"agents": "./agents/",
|
||||
"commands": "./commands/",
|
||||
"hooks": "./hooks/hooks-cursor.json"
|
||||
}
|
||||
18
.gitattributes
vendored
Normal file
18
.gitattributes
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Ensure shell scripts always have LF line endings
|
||||
*.sh text eol=lf
|
||||
hooks/session-start 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
|
||||
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Something isn't working as expected
|
||||
labels: bug
|
||||
---
|
||||
|
||||
<!--
|
||||
BEFORE FILING: Search open AND closed issues. The Windows SessionStart
|
||||
hook alone has been reported 29 times. If your issue already exists,
|
||||
add a comment or reaction to the existing one instead.
|
||||
-->
|
||||
|
||||
- [ ] I searched existing issues and this is not a duplicate
|
||||
|
||||
## Environment
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Superpowers version | |
|
||||
| Harness (Claude Code, Cursor, etc.) | |
|
||||
| Harness version | |
|
||||
| Model | |
|
||||
| OS + shell | |
|
||||
|
||||
## Is this a Superpowers issue or a platform issue?
|
||||
<!-- Superpowers is a plugin. Some reported "bugs" are actually issues
|
||||
in the underlying platform or model. If you're not sure, try
|
||||
reproducing without Superpowers installed.
|
||||
|
||||
If the problem persists without Superpowers, file the issue with
|
||||
your platform instead. -->
|
||||
|
||||
- [ ] I confirmed this issue does not occur without Superpowers installed
|
||||
|
||||
## What happened?
|
||||
<!-- Be specific. "It doesn't work" is not a bug report. -->
|
||||
|
||||
## Steps to reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Expected behavior
|
||||
<!-- What should have happened? -->
|
||||
|
||||
## Actual behavior
|
||||
<!-- What happened instead? -->
|
||||
|
||||
## Debug log or conversation transcript
|
||||
<!-- A debug log or conversation transcript showing the issue is the
|
||||
single most helpful thing you can include. Without one, we're
|
||||
guessing. Screenshots of error output are also useful. -->
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Questions & Help
|
||||
url: https://discord.gg/35wsABTejz
|
||||
about: For usage questions, troubleshooting help, and general discussion, please visit our Discord instead of opening an issue.
|
||||
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Propose a change or addition to Superpowers
|
||||
labels: enhancement
|
||||
---
|
||||
|
||||
<!--
|
||||
BEFORE FILING: Search open AND closed issues. Many features have been
|
||||
requested before — some were implemented differently, some are in
|
||||
progress, and some were intentionally declined.
|
||||
-->
|
||||
|
||||
- [ ] I searched existing issues and this has not been proposed before
|
||||
|
||||
## What problem does this solve?
|
||||
<!-- Describe the problem from your own experience. What were you doing,
|
||||
what went wrong or was missing, and why did it matter?
|
||||
|
||||
"It would be cool if..." is not a problem statement. -->
|
||||
|
||||
## Proposed solution
|
||||
<!-- What specifically do you want to happen? Be concrete. -->
|
||||
|
||||
## What alternatives did you consider?
|
||||
<!-- What other approaches could solve the same problem? Why is your
|
||||
proposal better? -->
|
||||
|
||||
## Is this appropriate for core Superpowers?
|
||||
<!-- Would this benefit someone working on a completely different kind
|
||||
of project? If this is specific to your domain, workflow, or a
|
||||
third-party tool, it may belong as its own plugin instead. -->
|
||||
|
||||
## Context
|
||||
<!-- Optional: version info, harness, model, workflow where you hit this. -->
|
||||
23
.github/ISSUE_TEMPLATE/platform_support.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/platform_support.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: IDE / Platform Support Request
|
||||
about: Request support for a new IDE, editor, or AI coding tool
|
||||
labels: platform-support
|
||||
---
|
||||
|
||||
<!--
|
||||
BEFORE FILING: Search existing issues — your IDE may already be
|
||||
requested or discussed.
|
||||
-->
|
||||
|
||||
- [ ] I searched existing issues for this IDE/platform
|
||||
|
||||
## Which IDE or platform?
|
||||
<!-- Name and link -->
|
||||
|
||||
## Does this tool have a plugin or extension system?
|
||||
<!-- If yes, link to the docs. If no, explain how third-party
|
||||
integrations typically work with this tool. -->
|
||||
|
||||
## Have you tried manual installation?
|
||||
<!-- Many tools work with Superpowers through manual setup even without
|
||||
official support. Did you try? What happened? -->
|
||||
87
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
87
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<!--
|
||||
BEFORE SUBMITTING: Read every word of this template. PRs that leave
|
||||
sections blank, contain multiple unrelated changes, or show no evidence
|
||||
of human involvement will be closed without review.
|
||||
-->
|
||||
|
||||
## What problem are you trying to solve?
|
||||
<!-- Describe the specific problem you encountered. If this was a session
|
||||
issue, include: what you were doing, what went wrong, the model's
|
||||
exact failure mode, and ideally a transcript or session log.
|
||||
|
||||
"Improving" something is not a problem statement. What broke? What
|
||||
failed? What was the user experience that motivated this? -->
|
||||
|
||||
## What does this PR change?
|
||||
<!-- 1-3 sentences. What, not why — the "why" belongs above. -->
|
||||
|
||||
## Is this change appropriate for the core library?
|
||||
<!-- Superpowers core contains general-purpose skills and infrastructure
|
||||
that benefit all users. Ask yourself:
|
||||
|
||||
- Would this be useful to someone working on a completely different
|
||||
kind of project than yours?
|
||||
- Is this project-specific, team-specific, or tool-specific?
|
||||
- Does this integrate or promote a third-party service?
|
||||
|
||||
If your change is a new skill for a specific domain, workflow tool,
|
||||
or third-party integration, it belongs in its own plugin — not here.
|
||||
See the plugin development docs for how to publish it separately. -->
|
||||
|
||||
## What alternatives did you consider?
|
||||
<!-- What other approaches did you try or evaluate before landing on this
|
||||
one? Why were they worse? If you didn't consider alternatives, say so
|
||||
— but know that's a red flag. -->
|
||||
|
||||
## Does this PR contain multiple unrelated changes?
|
||||
<!-- If yes: stop. Split it into separate PRs. Bundled PRs will be closed.
|
||||
If you believe the changes are related, explain the dependency. -->
|
||||
|
||||
## Existing PRs
|
||||
- [ ] I have reviewed all open AND closed PRs for duplicates or prior art
|
||||
- Related PRs: <!-- #number, #number, or "none found" -->
|
||||
|
||||
<!-- If a related closed PR exists, explain what's different about your
|
||||
approach and why it should succeed where the other didn't. -->
|
||||
|
||||
## Environment tested
|
||||
|
||||
| Harness (e.g. Claude Code, Cursor) | Harness version | Model | Model version/ID |
|
||||
|-------------------------------------|-----------------|-------|------------------|
|
||||
| | | | |
|
||||
|
||||
## Evaluation
|
||||
- What was the initial prompt you (or your human partner) used to start
|
||||
the session that led to this change?
|
||||
- How many eval sessions did you run AFTER making the change?
|
||||
- How did outcomes change compared to before the change?
|
||||
|
||||
<!-- "It works" is not evaluation. Describe the before/after difference
|
||||
you observed across multiple sessions. -->
|
||||
|
||||
## Rigor
|
||||
|
||||
- [ ] If this is a skills change: I used `superpowers:writing-skills` and
|
||||
completed adversarial pressure testing (paste results below)
|
||||
- [ ] This change was tested adversarially, not just on the happy path
|
||||
- [ ] I did not modify carefully-tuned content (Red Flags table,
|
||||
rationalizations, "human partner" language) without extensive evals
|
||||
showing the change is an improvement
|
||||
|
||||
<!-- If you changed wording in skills that shape agent behavior, show your
|
||||
eval methodology and results. These are not prose — they are code. -->
|
||||
|
||||
## Human review
|
||||
- [ ] A human has reviewed the COMPLETE proposed diff before submission
|
||||
|
||||
<!--
|
||||
STOP. If the checkbox above is not checked, do not submit this PR.
|
||||
|
||||
PRs will be closed without review if they:
|
||||
- Show no evidence of human involvement
|
||||
- Contain multiple unrelated changes
|
||||
- Promote or integrate third-party services or tools
|
||||
- Submit project-specific or personal configuration as core changes
|
||||
- Leave required sections blank or use placeholder text
|
||||
- Modify behavior-shaping content without eval evidence
|
||||
-->
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
.worktrees/
|
||||
.private-journal/
|
||||
.claude/
|
||||
.DS_Store
|
||||
node_modules/
|
||||
inspo
|
||||
triage/
|
||||
|
||||
23
.openclaw/index.js
Normal file
23
.openclaw/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const SUPERPOWERS_GUIDANCE = `## Superpowers
|
||||
|
||||
You have access to the Superpowers skill framework through OpenClaw's native skill system.
|
||||
Before responding to software development work, check whether one of those skills applies.
|
||||
If a Superpowers skill fits the task, invoke it before proceeding.
|
||||
|
||||
Start with \`using-superpowers\` when you need the overall workflow. Common follow-on
|
||||
skills include \`brainstorming\`, \`writing-plans\`, \`test-driven-development\`,
|
||||
\`systematic-debugging\`, \`dispatching-parallel-agents\`, and
|
||||
\`verification-before-completion\`.
|
||||
|
||||
When Superpowers instructions mention generic tools, use the closest native OpenClaw
|
||||
tool or workflow.`;
|
||||
|
||||
export default {
|
||||
id: "superpowers-openclaw",
|
||||
name: "Superpowers for OpenClaw",
|
||||
description: "Expose the Superpowers skill pack through OpenClaw's native plugin skill discovery.",
|
||||
|
||||
register(api) {
|
||||
api.on("before_prompt_build", async () => ({ prependSystemContext: SUPERPOWERS_GUIDANCE }));
|
||||
},
|
||||
};
|
||||
@@ -3,133 +3,108 @@
|
||||
## Prerequisites
|
||||
|
||||
- [OpenCode.ai](https://opencode.ai) installed
|
||||
- Node.js installed
|
||||
- Git installed
|
||||
|
||||
## Installation Steps
|
||||
## Installation
|
||||
|
||||
### 1. Install Superpowers
|
||||
Add superpowers to the `plugin` array in your `opencode.json` (global or project-level):
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/superpowers
|
||||
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
|
||||
```json
|
||||
{
|
||||
"plugin": ["superpowers@git+https://github.com/obra/superpowers.git"]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Register the Plugin
|
||||
Restart OpenCode. The plugin installs through OpenCode's plugin manager and
|
||||
registers all skills.
|
||||
|
||||
Create a symlink so OpenCode discovers the plugin:
|
||||
Verify by asking: "Tell me about your superpowers"
|
||||
|
||||
OpenCode uses its own plugin install. If you also use Claude Code, Codex, or
|
||||
another harness, install Superpowers separately for each one.
|
||||
|
||||
## Migrating from the old symlink-based install
|
||||
|
||||
If you previously installed superpowers using `git clone` and symlinks, remove the old setup:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/plugin
|
||||
ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js ~/.config/opencode/plugin/superpowers.js
|
||||
# Remove old symlinks
|
||||
rm -f ~/.config/opencode/plugins/superpowers.js
|
||||
rm -rf ~/.config/opencode/skills/superpowers
|
||||
|
||||
# Optionally remove the cloned repo
|
||||
rm -rf ~/.config/opencode/superpowers
|
||||
|
||||
# Remove skills.paths from opencode.json if you added one for superpowers
|
||||
```
|
||||
|
||||
### 3. Restart OpenCode
|
||||
|
||||
Restart OpenCode. The plugin will automatically inject superpowers context via the chat.message hook.
|
||||
|
||||
You should see superpowers is active when you ask "do you have superpowers?"
|
||||
Then follow the installation steps above.
|
||||
|
||||
## Usage
|
||||
|
||||
### Finding Skills
|
||||
|
||||
Use the `find_skills` tool to list all available skills:
|
||||
Use OpenCode's native `skill` tool:
|
||||
|
||||
```
|
||||
use find_skills tool
|
||||
use skill tool to list skills
|
||||
use skill tool to load superpowers/brainstorming
|
||||
```
|
||||
|
||||
### Loading a Skill
|
||||
|
||||
Use the `use_skill` tool to load a specific skill:
|
||||
|
||||
```
|
||||
use use_skill tool with skill_name: "superpowers:brainstorming"
|
||||
```
|
||||
|
||||
### Personal Skills
|
||||
|
||||
Create your own skills in `~/.config/opencode/skills/`:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/skills/my-skill
|
||||
```
|
||||
|
||||
Create `~/.config/opencode/skills/my-skill/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: Use when [condition] - [what it does]
|
||||
---
|
||||
|
||||
# My Skill
|
||||
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
Personal skills override superpowers skills with the same name.
|
||||
|
||||
### Project Skills
|
||||
|
||||
Create project-specific skills in your OpenCode 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
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd ~/.config/opencode/superpowers
|
||||
git pull
|
||||
OpenCode installs Superpowers through a git-backed package spec. Some OpenCode
|
||||
and Bun versions pin that resolved git dependency in a lockfile or cache, so a
|
||||
restart may not pick up the newest Superpowers commit. If updates do not appear,
|
||||
clear OpenCode's package cache or reinstall the plugin.
|
||||
|
||||
To pin a specific version:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugin": ["superpowers@git+https://github.com/obra/superpowers.git#v5.0.3"]
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 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 logs: `opencode run --print-logs "hello" 2>&1 | grep -i superpowers`
|
||||
2. Verify the plugin line in your `opencode.json`
|
||||
3. Make sure you're running a recent version of OpenCode
|
||||
|
||||
### Windows install issues
|
||||
|
||||
Some Windows OpenCode builds have upstream installer issues with git-backed
|
||||
plugin specs, including cache paths for `git+https` URLs and Bun not finding
|
||||
`git.exe` even when it works in a normal terminal. If OpenCode cannot install
|
||||
the plugin, try installing with system npm and pointing OpenCode at the local
|
||||
package:
|
||||
|
||||
```powershell
|
||||
npm install superpowers@git+https://github.com/obra/superpowers.git --prefix "$HOME\.config\opencode"
|
||||
```
|
||||
|
||||
Then use the installed package path in `opencode.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugin": ["~/.config/opencode/node_modules/superpowers"]
|
||||
}
|
||||
```
|
||||
|
||||
### Skills not found
|
||||
|
||||
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. Use `skill` tool to list what's discovered
|
||||
2. Check that the plugin is loading (see above)
|
||||
|
||||
### 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` → `todowrite`
|
||||
- `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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
135
.opencode/plugins/superpowers.js
Normal file
135
.opencode/plugins/superpowers.js
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Superpowers plugin for OpenCode.ai
|
||||
*
|
||||
* Injects superpowers bootstrap context via system prompt transform.
|
||||
* Auto-registers skills directory via config hook (no symlinks needed).
|
||||
*/
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
// Module-level cache for bootstrap content.
|
||||
// The SKILL.md file does not change during a session, so reading + parsing it
|
||||
// once eliminates redundant fs.existsSync + fs.readFileSync + regex work on
|
||||
// every agent step. See #1202 for the full analysis.
|
||||
let _bootstrapCache = undefined; // undefined = not yet loaded, null = file missing
|
||||
|
||||
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 (cached after first call)
|
||||
const getBootstrapContent = () => {
|
||||
// Return cached result on subsequent calls
|
||||
if (_bootstrapCache !== undefined) return _bootstrapCache;
|
||||
|
||||
// Try to load using-superpowers skill
|
||||
const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md');
|
||||
if (!fs.existsSync(skillPath)) {
|
||||
_bootstrapCache = null;
|
||||
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\` → \`todowrite\`
|
||||
- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention)
|
||||
- \`Skill\` tool → OpenCode's native \`skill\` tool
|
||||
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
|
||||
|
||||
Use OpenCode's native \`skill\` tool to list and load skills.`;
|
||||
|
||||
_bootstrapCache = `<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 _bootstrapCache;
|
||||
};
|
||||
|
||||
return {
|
||||
// Inject skills path into live config so OpenCode discovers superpowers skills
|
||||
// without requiring manual symlinks or config file edits.
|
||||
// This works because Config.get() returns a cached singleton — modifications
|
||||
// here are visible when skills are lazily discovered later.
|
||||
config: async (config) => {
|
||||
config.skills = config.skills || {};
|
||||
config.skills.paths = config.skills.paths || [];
|
||||
if (!config.skills.paths.includes(superpowersSkillsDir)) {
|
||||
config.skills.paths.push(superpowersSkillsDir);
|
||||
}
|
||||
},
|
||||
|
||||
// Inject bootstrap into the first user message of each session.
|
||||
// Using a user message instead of a system message avoids:
|
||||
// 1. Token bloat from system messages repeated every turn (#750)
|
||||
// 2. Multiple system messages breaking Qwen and other models (#894)
|
||||
//
|
||||
// The hook fires on every agent step (not just every turn) because
|
||||
// opencode's prompt.ts reloads messages from DB each step. Fresh message
|
||||
// arrays may need injection again, so getBootstrapContent() must not do
|
||||
// repeated disk work.
|
||||
'experimental.chat.messages.transform': async (_input, output) => {
|
||||
const bootstrap = getBootstrapContent();
|
||||
if (!bootstrap || !output.messages.length) return;
|
||||
const firstUser = output.messages.find(m => m.info.role === 'user');
|
||||
if (!firstUser || !firstUser.parts.length) return;
|
||||
|
||||
// Guard: skip if first user message already contains bootstrap.
|
||||
// This prevents double injection when OpenCode passes an already
|
||||
// transformed in-memory message array through the hook again.
|
||||
if (firstUser.parts.some(p => p.type === 'text' && p.text.includes('EXTREMELY_IMPORTANT'))) return;
|
||||
|
||||
const ref = firstUser.parts[0];
|
||||
firstUser.parts.unshift({ ...ref, type: 'text', text: bootstrap });
|
||||
}
|
||||
};
|
||||
};
|
||||
21
.version-bump.json
Normal file
21
.version-bump.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"files": [
|
||||
{ "path": "package.json", "field": "version" },
|
||||
{ "path": ".claude-plugin/plugin.json", "field": "version" },
|
||||
{ "path": ".cursor-plugin/plugin.json", "field": "version" },
|
||||
{ "path": ".codex-plugin/plugin.json", "field": "version" },
|
||||
{ "path": "openclaw.plugin.json", "field": "version" },
|
||||
{ "path": ".claude-plugin/marketplace.json", "field": "plugins.0.version" },
|
||||
{ "path": "gemini-extension.json", "field": "version" }
|
||||
],
|
||||
"audit": {
|
||||
"exclude": [
|
||||
"CHANGELOG.md",
|
||||
"RELEASE-NOTES.md",
|
||||
"node_modules",
|
||||
".git",
|
||||
".version-bump.json",
|
||||
"scripts/bump-version.sh"
|
||||
]
|
||||
}
|
||||
}
|
||||
85
CLAUDE.md
Normal file
85
CLAUDE.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Superpowers — Contributor Guidelines
|
||||
|
||||
## If You Are an AI Agent
|
||||
|
||||
Stop. Read this section before doing anything.
|
||||
|
||||
This repo has a 94% PR rejection rate. Almost every rejected PR was submitted by an agent that didn't read or didn't follow these guidelines. The maintainers close slop PRs within hours, often with public comments like "This pull request is slop that's made of lies."
|
||||
|
||||
**Your job is to protect your human partner from that outcome.** Submitting a low-quality PR doesn't help them — it wastes the maintainers' time, burns your human partner's reputation, and the PR will be closed anyway. That is not being helpful. That is being a tool of embarrassment.
|
||||
|
||||
Before you open a PR against this repo, you MUST:
|
||||
|
||||
1. **Read the entire PR template** at `.github/PULL_REQUEST_TEMPLATE.md` and fill in every section with real, specific answers. Not summaries. Not placeholders.
|
||||
2. **Search for existing PRs** — open AND closed — that address the same problem. If duplicates exist, STOP and tell your human partner. Do not open another duplicate.
|
||||
3. **Verify this is a real problem.** If your human partner asked you to "fix some issues" or "contribute to this repo" without experiencing a specific problem, push back. Ask them what broke, what failed, what the user experience was.
|
||||
4. **Confirm the change belongs in core.** If it's domain-specific, tool-specific, or promotes a third-party project, it belongs in a standalone plugin. Tell your human partner.
|
||||
5. **Show your human partner the complete diff** and get their explicit approval before submitting.
|
||||
|
||||
If any of these checks fail, do not open the PR. Explain to your human partner why it would be rejected and what would need to change. They will thank you for saving them the embarrassment.
|
||||
|
||||
## Pull Request Requirements
|
||||
|
||||
**Every PR must fully complete the PR template.** No section may be left blank or filled with placeholder text. PRs that skip sections will be closed without review.
|
||||
|
||||
**Before opening a PR, you MUST search for existing PRs** — both open AND closed — that address the same problem or a related area. Reference what you found in the "Existing PRs" section. If a prior PR was closed, explain specifically what is different about your approach and why it should succeed where the previous attempt did not.
|
||||
|
||||
**PRs that show no evidence of human involvement will be closed.** A human must review the complete proposed diff before submission.
|
||||
|
||||
## What We Will Not Accept
|
||||
|
||||
### Third-party dependencies
|
||||
|
||||
PRs that add optional or required dependencies on third-party projects will not be accepted unless they are adding support for a new harness (e.g., a new IDE or CLI tool). Superpowers is a zero-dependency plugin by design. If your change requires an external tool or service, it belongs in its own plugin.
|
||||
|
||||
### "Compliance" changes to skills
|
||||
|
||||
Our internal skill philosophy differs from Anthropic's published guidance on writing skills. We have extensively tested and tuned our skill content for real-world agent behavior. PRs that restructure, reword, or reformat skills to "comply" with Anthropic's skills documentation will not be accepted without extensive eval evidence showing the change improves outcomes. The bar for modifying behavior-shaping content is very high.
|
||||
|
||||
### Project-specific or personal configuration
|
||||
|
||||
Skills, hooks, or configuration that only benefit a specific project, team, domain, or workflow do not belong in core. Publish these as a separate plugin.
|
||||
|
||||
### Bulk or spray-and-pray PRs
|
||||
|
||||
Do not trawl the issue tracker and open PRs for multiple issues in a single session. Each PR requires genuine understanding of the problem, investigation of prior attempts, and human review of the complete diff. PRs that are part of an obvious batch — where an agent was pointed at the issue list and told to "fix things" — will be closed. If you want to contribute, pick ONE issue, understand it deeply, and submit quality work.
|
||||
|
||||
### Speculative or theoretical fixes
|
||||
|
||||
Every PR must solve a real problem that someone actually experienced. "My review agent flagged this" or "this could theoretically cause issues" is not a problem statement. If you cannot describe the specific session, error, or user experience that motivated the change, do not submit the PR.
|
||||
|
||||
### Domain-specific skills
|
||||
|
||||
Superpowers core contains general-purpose skills that benefit all users regardless of their project. Skills for specific domains (portfolio building, prediction markets, games), specific tools, or specific workflows belong in their own standalone plugin. Ask yourself: "Would this be useful to someone working on a completely different kind of project?" If not, publish it separately.
|
||||
|
||||
### Fork-specific changes
|
||||
|
||||
If you maintain a fork with customizations, do not open PRs to sync your fork or push fork-specific changes upstream. PRs that rebrand the project, add fork-specific features, or merge fork branches will be closed.
|
||||
|
||||
### Fabricated content
|
||||
|
||||
PRs containing invented claims, fabricated problem descriptions, or hallucinated functionality will be closed immediately. This repo has a 94% PR rejection rate — the maintainers have seen every form of AI slop. They will notice.
|
||||
|
||||
### Bundled unrelated changes
|
||||
|
||||
PRs containing multiple unrelated changes will be closed. Split them into separate PRs.
|
||||
|
||||
## Skill Changes Require Evaluation
|
||||
|
||||
Skills are not prose — they are code that shapes agent behavior. If you modify skill content:
|
||||
|
||||
- Use `superpowers:writing-skills` to develop and test changes
|
||||
- Run adversarial pressure testing across multiple sessions
|
||||
- Show before/after eval results in your PR
|
||||
- Do not modify carefully-tuned content (Red Flags tables, rationalization lists, "human partner" language) without evidence the change is an improvement
|
||||
|
||||
## Understand the Project Before Contributing
|
||||
|
||||
Before proposing changes to skill design, workflow philosophy, or architecture, read existing skills and understand the project's design decisions. Superpowers has its own tested philosophy about skill design, agent behavior shaping, and terminology (e.g., "your human partner" is deliberate, not interchangeable with "the user"). Changes that rewrite the project's voice or restructure its approach without understanding why it exists will be rejected.
|
||||
|
||||
## General
|
||||
|
||||
- Read `.github/PULL_REQUEST_TEMPLATE.md` before submitting
|
||||
- One problem per PR
|
||||
- Test on at least one harness and report results in the environment table
|
||||
- Describe the problem you solved, not just what you changed
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
jesse@primeradiant.com.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
2
GEMINI.md
Normal file
2
GEMINI.md
Normal file
@@ -0,0 +1,2 @@
|
||||
@./skills/using-superpowers/SKILL.md
|
||||
@./skills/using-superpowers/references/gemini-tools.md
|
||||
191
README.md
191
README.md
@@ -1,6 +1,10 @@
|
||||
# Superpowers
|
||||
|
||||
Superpowers is a complete software development workflow for your coding agents, built on top of a set of composable "skills" and some initial instructions that make sure your agent uses them.
|
||||
Superpowers is a complete software development methodology for your coding agents, built on top of a set of composable skills and some initial instructions that make sure your agent uses them.
|
||||
|
||||
## Quickstart
|
||||
|
||||
Give your agent Superpowers: [Claude Code](#claude-code), [Codex CLI](#codex-cli), [Codex App](#codex-app), [Factory Droid](#factory-droid), [Gemini CLI](#gemini-cli), [OpenCode](#opencode), [Cursor](#cursor), [GitHub Copilot CLI](#github-copilot-cli).
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -26,56 +30,153 @@ Thanks!
|
||||
|
||||
## Installation
|
||||
|
||||
**Note:** Installation differs by platform. Claude Code has a built-in plugin system. Codex and OpenCode require manual setup.
|
||||
Installation differs by harness. If you use more than one, install Superpowers separately for each one.
|
||||
|
||||
### Claude Code (via Plugin Marketplace)
|
||||
### Claude Code
|
||||
|
||||
In Claude Code, register the marketplace first:
|
||||
Superpowers is available via the [official Claude plugin marketplace](https://claude.com/plugins/superpowers)
|
||||
|
||||
```bash
|
||||
/plugin marketplace add obra/superpowers-marketplace
|
||||
```
|
||||
#### Official Marketplace
|
||||
|
||||
Then install the plugin from this marketplace:
|
||||
- Install the plugin from Anthropic's official marketplace:
|
||||
|
||||
```bash
|
||||
/plugin install superpowers@superpowers-marketplace
|
||||
```
|
||||
```bash
|
||||
/plugin install superpowers@claude-plugins-official
|
||||
```
|
||||
|
||||
### Verify Installation
|
||||
#### Superpowers Marketplace
|
||||
|
||||
Check that commands appear:
|
||||
The Superpowers marketplace provides Superpowers and some other related plugins for Claude Code.
|
||||
|
||||
```bash
|
||||
/help
|
||||
```
|
||||
- Register the marketplace:
|
||||
|
||||
```
|
||||
# Should see:
|
||||
# /superpowers:brainstorm - Interactive design refinement
|
||||
# /superpowers:write-plan - Create implementation plan
|
||||
# /superpowers:execute-plan - Execute plan in batches
|
||||
```
|
||||
```bash
|
||||
/plugin marketplace add obra/superpowers-marketplace
|
||||
```
|
||||
|
||||
### Codex
|
||||
- Install the plugin from this marketplace:
|
||||
|
||||
Tell Codex:
|
||||
```bash
|
||||
/plugin install superpowers@superpowers-marketplace
|
||||
```
|
||||
|
||||
```
|
||||
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.codex/INSTALL.md
|
||||
```
|
||||
### Codex CLI
|
||||
|
||||
**Detailed docs:** [docs/README.codex.md](docs/README.codex.md)
|
||||
Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins).
|
||||
|
||||
- Open the plugin search interface:
|
||||
|
||||
```bash
|
||||
/plugins
|
||||
```
|
||||
|
||||
- Search for Superpowers:
|
||||
|
||||
```bash
|
||||
superpowers
|
||||
```
|
||||
|
||||
- Select `Install Plugin`.
|
||||
|
||||
### Codex App
|
||||
|
||||
Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins).
|
||||
|
||||
- In the Codex app, click on Plugins in the sidebar.
|
||||
- You should see `Superpowers` in the Coding section.
|
||||
- Click the `+` next to Superpowers and follow the prompts.
|
||||
|
||||
### Factory Droid
|
||||
|
||||
- Register the marketplace:
|
||||
|
||||
```bash
|
||||
droid plugin marketplace add https://github.com/obra/superpowers
|
||||
```
|
||||
|
||||
- Install the plugin:
|
||||
|
||||
```bash
|
||||
droid plugin install superpowers@superpowers
|
||||
```
|
||||
|
||||
### Gemini CLI
|
||||
|
||||
- Install the extension:
|
||||
|
||||
```bash
|
||||
gemini extensions install https://github.com/obra/superpowers
|
||||
```
|
||||
|
||||
- Update later:
|
||||
|
||||
```bash
|
||||
gemini extensions update superpowers
|
||||
```
|
||||
|
||||
### OpenCode
|
||||
|
||||
Tell OpenCode:
|
||||
OpenCode uses its own plugin install; install Superpowers separately even if you
|
||||
already use it in another harness.
|
||||
|
||||
```
|
||||
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.opencode/INSTALL.md
|
||||
```
|
||||
- Tell OpenCode:
|
||||
|
||||
**Detailed docs:** [docs/README.opencode.md](docs/README.opencode.md)
|
||||
```
|
||||
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.opencode/INSTALL.md
|
||||
```
|
||||
|
||||
- Detailed docs: [docs/README.opencode.md](docs/README.opencode.md)
|
||||
|
||||
### OpenClaw
|
||||
|
||||
OpenClaw uses its native plugin system. Install Superpowers separately even if
|
||||
you already use it in another harness.
|
||||
|
||||
- Clone and link the plugin:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/obra/superpowers.git ~/.openclaw/vendor/superpowers
|
||||
openclaw plugins install --link ~/.openclaw/vendor/superpowers --dangerously-force-unsafe-install
|
||||
openclaw plugins enable superpowers-openclaw
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
The override is required because OpenClaw scans the linked plugin directory
|
||||
during native plugin install. It flags `skills/writing-skills/render-graphs.js`,
|
||||
an existing Superpowers skill-authoring helper, because that script shells out
|
||||
to Graphviz. The helper is not part of the OpenClaw runtime hook; review the
|
||||
source before using the override.
|
||||
|
||||
- Verify the plugin and skills are available:
|
||||
|
||||
```bash
|
||||
openclaw plugins info superpowers-openclaw --json
|
||||
openclaw skills info using-superpowers --json
|
||||
```
|
||||
|
||||
### Cursor
|
||||
|
||||
- In Cursor Agent chat, install from marketplace:
|
||||
|
||||
```text
|
||||
/add-plugin superpowers
|
||||
```
|
||||
|
||||
- Or search for "superpowers" in the plugin marketplace.
|
||||
|
||||
### GitHub Copilot CLI
|
||||
|
||||
- Register the marketplace:
|
||||
|
||||
```bash
|
||||
copilot plugin marketplace add obra/superpowers-marketplace
|
||||
```
|
||||
|
||||
- Install the plugin:
|
||||
|
||||
```bash
|
||||
copilot plugin install superpowers@superpowers-marketplace
|
||||
```
|
||||
|
||||
## The Basic Workflow
|
||||
|
||||
@@ -128,32 +229,32 @@ Fetch and follow instructions from https://raw.githubusercontent.com/obra/superp
|
||||
- **Complexity reduction** - Simplicity as primary goal
|
||||
- **Evidence over claims** - Verify before declaring success
|
||||
|
||||
Read more: [Superpowers for Claude Code](https://blog.fsck.com/2025/10/09/superpowers/)
|
||||
Read [the original release announcement](https://blog.fsck.com/2025/10/09/superpowers/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Skills live directly in this repository. To contribute:
|
||||
The general contribution process for Superpowers is below. Keep in mind that we don't generally accept contributions of new skills and that any updates to skills must work across all of the coding agents we support.
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a branch for your skill
|
||||
3. Follow the `writing-skills` skill for creating and testing new skills
|
||||
4. Submit a PR
|
||||
2. Switch to the 'dev' branch
|
||||
3. Create a branch for your work
|
||||
4. Follow the `writing-skills` skill for creating and testing new and modified skills
|
||||
5. Submit a PR, being sure to fill in the pull request template.
|
||||
|
||||
See `skills/writing-skills/SKILL.md` for the complete guide.
|
||||
|
||||
## Updating
|
||||
|
||||
Skills update automatically when you update the plugin:
|
||||
|
||||
```bash
|
||||
/plugin update superpowers
|
||||
```
|
||||
Superpowers updates are somewhat coding-agent dependent, but are often automatic.
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details
|
||||
|
||||
## Support
|
||||
## Community
|
||||
|
||||
Superpowers is built by [Jesse Vincent](https://blog.fsck.com) and the rest of the folks at [Prime Radiant](https://primeradiant.com).
|
||||
|
||||
- **Discord**: [Join us](https://discord.gg/35wsABTejz) for community support, questions, and sharing what you're building with Superpowers
|
||||
- **Issues**: https://github.com/obra/superpowers/issues
|
||||
- **Marketplace**: https://github.com/obra/superpowers-marketplace
|
||||
- **Release announcements**: [Sign up](https://primeradiant.com/superpowers/) to get notified about new versions
|
||||
|
||||
496
RELEASE-NOTES.md
496
RELEASE-NOTES.md
@@ -1,6 +1,492 @@
|
||||
# Superpowers Release Notes
|
||||
|
||||
## v4.0.1 (2025-12-22)
|
||||
## v5.0.7 (2026-03-31)
|
||||
|
||||
### GitHub Copilot CLI Support
|
||||
|
||||
- **SessionStart context injection** — Copilot CLI v1.0.11 added support for `additionalContext` in sessionStart hook output. The session-start hook now detects the `COPILOT_CLI` environment variable and emits the SDK-standard `{ "additionalContext": "..." }` format, giving Copilot CLI users the full superpowers bootstrap at session start. (Original fix by @culinablaz in PR #910)
|
||||
- **Tool mapping** — added `references/copilot-tools.md` with the full Claude Code to Copilot CLI tool equivalence table
|
||||
- **Skill and README updates** — added Copilot CLI to the `using-superpowers` skill's platform instructions and README installation section
|
||||
|
||||
### OpenCode Fixes
|
||||
|
||||
- **Skills path consistency** — the bootstrap text no longer advertises a misleading `configDir/skills/superpowers/` path that didn't match the runtime path. The agent should use the native `skill` tool, not navigate to files by path. Tests now use consistent paths derived from a single source of truth. (#847, #916)
|
||||
- **Bootstrap as user message** — moved bootstrap injection from `experimental.chat.system.transform` to `experimental.chat.messages.transform`, prepending to the first user message instead of adding a system message. Avoids token bloat from system messages repeated every turn (#750) and fixes compatibility with Qwen and other models that break on multiple system messages (#894).
|
||||
|
||||
## v5.0.6 (2026-03-24)
|
||||
|
||||
### Inline Self-Review Replaces Subagent Review Loops
|
||||
|
||||
The subagent review loop (dispatching a fresh agent to review plans/specs) doubled execution time (~25 min overhead) without measurably improving plan quality. Regression testing across 5 versions with 5 trials each showed identical quality scores regardless of whether the review loop ran.
|
||||
|
||||
- **brainstorming** — replaced Spec Review Loop (subagent dispatch + 3-iteration cap) with inline Spec Self-Review checklist: placeholder scan, internal consistency, scope check, ambiguity check
|
||||
- **writing-plans** — replaced Plan Review Loop (subagent dispatch + 3-iteration cap) with inline Self-Review checklist: spec coverage, placeholder scan, type consistency
|
||||
- **writing-plans** — added explicit "No Placeholders" section defining plan failures (TBD, vague descriptions, undefined references, "similar to Task N")
|
||||
- Self-review catches 3-5 real bugs per run in ~30s instead of ~25 min, with comparable defect rates to the subagent approach
|
||||
|
||||
### Brainstorm Server
|
||||
|
||||
- **Session directory restructured** — the brainstorm server session directory now contains two peer subdirectories: `content/` (HTML files served to the browser) and `state/` (events, server-info, pid, log). Previously, server state and user interaction data were stored alongside served content, making them accessible over HTTP. The `screen_dir` and `state_dir` paths are both included in the server-started JSON. (Reported by 吉田仁)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Owner-PID lifecycle fixes** — the brainstorm server's owner-PID monitoring had two bugs causing false shutdowns within 60 seconds: (1) EPERM from cross-user PIDs (Tailscale SSH, etc.) was treated as "process dead", and (2) on WSL the grandparent PID resolves to a short-lived subprocess that exits before the first lifecycle check. Fixed by treating EPERM as "alive" and validating the owner PID at startup — if it's already dead, monitoring is disabled and the server relies on the 30-minute idle timeout. This also removes the Windows/MSYS2-specific carve-out from `start-server.sh` since the server now handles it generically. (#879)
|
||||
- **writing-skills** — corrected false claim that SKILL.md frontmatter supports "only two fields"; now says "two required fields" and links to the agentskills.io specification for all supported fields (PR #882 by @arittr)
|
||||
|
||||
### Codex App Compatibility
|
||||
|
||||
- **codex-tools** — added named agent dispatch mapping documenting how to translate Claude Code's named agent types to Codex's `spawn_agent` with worker roles (PR #647 by @arittr)
|
||||
- **codex-tools** — added environment detection and Codex App finishing sections for worktree-aware skills (by @arittr)
|
||||
- **Design spec** — added Codex App compatibility design spec (PRI-823) covering read-only environment detection, worktree-safe skill behavior, and sandbox fallback patterns (by @arittr)
|
||||
|
||||
## v5.0.5 (2026-03-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Brainstorm server ESM fix** — renamed `server.js` → `server.cjs` so the brainstorming server starts correctly on Node.js 22+ where the root `package.json` `"type": "module"` caused `require()` to fail. (PR #784 by @sarbojitrana, fixes #774, #780, #783)
|
||||
- **Brainstorm owner-PID on Windows** — skip PID lifecycle monitoring on Windows/MSYS2 where the PID namespace is invisible to Node.js, preventing the server from self-terminating after 60 seconds. (#770, docs from PR #768 by @lucasyhzlu-debug)
|
||||
- **stop-server.sh reliability** — verify the server process actually died before reporting success. SIGTERM + 2s wait + SIGKILL fallback. (#723)
|
||||
|
||||
### Changed
|
||||
|
||||
- **Execution handoff** — restore user choice between subagent-driven and inline execution after plan writing. Subagent-driven is recommended but no longer mandatory.
|
||||
|
||||
## v5.0.4 (2026-03-16)
|
||||
|
||||
### Review Loop Refinements
|
||||
|
||||
Dramatically reduces token usage and speeds up spec and plan reviews by eliminating unnecessary review passes and tightening reviewer focus.
|
||||
|
||||
- **Single whole-plan review** — plan reviewer now reviews the complete plan in one pass instead of chunk-by-chunk. Removed all chunk-related concepts (`## Chunk N:` headings, 1000-line chunk limits, per-chunk dispatch).
|
||||
- **Raised the bar for blocking issues** — both spec and plan reviewer prompts now include a "Calibration" section: only flag issues that would cause real problems during implementation. Minor wording, stylistic preferences, and formatting quibbles should not block approval.
|
||||
- **Reduced max review iterations** — from 5 to 3 for both spec and plan review loops. If the reviewer is calibrated correctly, 3 rounds is plenty.
|
||||
- **Streamlined reviewer checklists** — spec reviewer trimmed from 7 categories to 5; plan reviewer from 7 to 4. Removed formatting-focused checks (task syntax, chunk size) in favor of substance (buildability, spec alignment).
|
||||
|
||||
### OpenCode
|
||||
|
||||
- **One-line plugin install** — OpenCode plugin now auto-registers the skills directory via a `config` hook. No symlinks or `skills.paths` config needed. Install is just adding one line to `opencode.json`. (PR #753)
|
||||
- **Added `package.json`** so OpenCode can install superpowers as an npm package from git.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Verify server actually stopped** — `stop-server.sh` now confirms the process is dead before reporting success. SIGTERM + 2s wait + SIGKILL fallback. Reports failure if the process survives. (PR #751)
|
||||
- **Generic agent language** — brainstorm companion waiting page now says "the agent" instead of "Claude".
|
||||
|
||||
## v5.0.3 (2026-03-15)
|
||||
|
||||
### Cursor Support
|
||||
|
||||
- **Cursor hooks** — added `hooks/hooks-cursor.json` with Cursor's camelCase format (`sessionStart`, `version: 1`) and updated `.cursor-plugin/plugin.json` to reference it. Fixed platform detection in `session-start` to check `CURSOR_PLUGIN_ROOT` first (Cursor may also set `CLAUDE_PLUGIN_ROOT`). (Based on PR #709)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Stop firing SessionStart hook on `--resume`** — the startup hook was re-injecting context on resumed sessions, which already have the context in their conversation history. The hook now fires only on `startup`, `clear`, and `compact`.
|
||||
- **Bash 5.3+ hook hang** — replaced heredoc (`cat <<EOF`) with `printf` in `hooks/session-start`. Fixes indefinite hang on macOS with Homebrew bash 5.3+ caused by a bash regression with large variable expansion in heredocs. (#572, #571)
|
||||
- **POSIX-safe hook script** — replaced `${BASH_SOURCE[0]:-$0}` with `$0` in `hooks/session-start`. Fixes "Bad substitution" error on Ubuntu/Debian where `/bin/sh` is dash. (#553)
|
||||
- **Portable shebangs** — replaced `#!/bin/bash` with `#!/usr/bin/env bash` in all shell scripts. Fixes execution on NixOS, FreeBSD, and macOS with Homebrew bash where `/bin/bash` is outdated or missing. (#700)
|
||||
- **Brainstorm server on Windows** — auto-detect Windows/Git Bash (`OSTYPE=msys*`, `MSYSTEM`) and switch to foreground mode, fixing silent server failure caused by `nohup`/`disown` process reaping. (#737)
|
||||
- **Codex docs fix** — replaced deprecated `collab` flag with `multi_agent` in Codex documentation. (PR #749)
|
||||
|
||||
## v5.0.2 (2026-03-11)
|
||||
|
||||
### Zero-Dependency Brainstorm Server
|
||||
|
||||
**Removed all vendored node_modules — server.js is now fully self-contained**
|
||||
|
||||
- Replaced Express/Chokidar/WebSocket dependencies with zero-dependency Node.js server using built-in `http`, `fs`, and `crypto` modules
|
||||
- Removed ~1,200 lines of vendored `node_modules/`, `package.json`, and `package-lock.json`
|
||||
- Custom WebSocket protocol implementation (RFC 6455 framing, ping/pong, proper close handshake)
|
||||
- Native `fs.watch()` file watching replaces Chokidar
|
||||
- Full test suite: HTTP serving, WebSocket protocol, file watching, and integration tests
|
||||
|
||||
### Brainstorm Server Reliability
|
||||
|
||||
- **Auto-exit after 30 minutes idle** — server shuts down when no clients are connected, preventing orphaned processes
|
||||
- **Owner process tracking** — server monitors the parent harness PID and exits when the owning session dies
|
||||
- **Liveness check** — skill verifies server is responsive before reusing an existing instance
|
||||
- **Encoding fix** — proper `<meta charset="utf-8">` on served HTML pages
|
||||
|
||||
### Subagent Context Isolation
|
||||
|
||||
- All delegation skills (brainstorming, dispatching-parallel-agents, requesting-code-review, subagent-driven-development, writing-plans) now include context isolation principle
|
||||
- Subagents receive only the context they need, preventing context window pollution
|
||||
|
||||
## v5.0.1 (2026-03-10)
|
||||
|
||||
### Agentskills Compliance
|
||||
|
||||
**Brainstorm-server moved into skill directory**
|
||||
|
||||
- Moved `lib/brainstorm-server/` → `skills/brainstorming/scripts/` per the [agentskills.io](https://agentskills.io) specification
|
||||
- All `${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/` references replaced with relative `scripts/` paths
|
||||
- Skills are now fully portable across platforms — no platform-specific env vars needed to locate scripts
|
||||
- `lib/` directory removed (was the last remaining content)
|
||||
|
||||
### New Features
|
||||
|
||||
**Gemini CLI extension**
|
||||
|
||||
- Native Gemini CLI extension support via `gemini-extension.json` and `GEMINI.md` at repo root
|
||||
- `GEMINI.md` @imports `using-superpowers` skill and tool mapping table at session start
|
||||
- Gemini CLI tool mapping reference (`skills/using-superpowers/references/gemini-tools.md`) — translates Claude Code tool names (Read, Write, Edit, Bash, etc.) to Gemini CLI equivalents (read_file, write_file, replace, etc.)
|
||||
- Documents Gemini CLI limitations: no subagent support, skills fall back to `executing-plans`
|
||||
- Extension root at repo root for cross-platform compatibility (avoids Windows symlink issues)
|
||||
- Install instructions added to README
|
||||
|
||||
### Improvements
|
||||
|
||||
**Multi-platform brainstorm server launch**
|
||||
|
||||
- Per-platform launch instructions in visual-companion.md: Claude Code (default mode), Codex (auto-foreground via `CODEX_CI`), Gemini CLI (`--foreground` with `is_background`), and fallback for other environments
|
||||
- Server now writes startup JSON to `$SCREEN_DIR/.server-info` so agents can find the URL and port even when stdout is hidden by background execution
|
||||
|
||||
**Brainstorm server dependencies bundled**
|
||||
|
||||
- `node_modules` vendored into the repo so the brainstorm server works immediately on fresh plugin installs without requiring `npm` at runtime
|
||||
- Removed `fsevents` from bundled deps (macOS-only native binary; chokidar falls back gracefully without it)
|
||||
- Fallback auto-install via `npm install` if `node_modules` is missing
|
||||
|
||||
**OpenCode tool mapping fix**
|
||||
|
||||
- `TodoWrite` → `todowrite` (was incorrectly mapped to `update_plan`); verified against OpenCode source
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
**Windows/Linux: single quotes break SessionStart hook** (#577, #529, #644, PR #585)
|
||||
|
||||
- Single quotes around `${CLAUDE_PLUGIN_ROOT}` in hooks.json fail on Windows (cmd.exe doesn't recognize single quotes as path delimiters) and on Linux (single quotes prevent variable expansion)
|
||||
- Fix: replaced single quotes with escaped double quotes — works across macOS bash, Windows cmd.exe, Windows Git Bash, and Linux, with and without spaces in paths
|
||||
- Verified on Windows 11 (NT 10.0.26200.0) with Claude Code 2.1.72 and Git for Windows
|
||||
|
||||
**Brainstorming spec review loop skipped** (#677)
|
||||
|
||||
- The spec review loop (dispatch spec-document-reviewer subagent, iterate until approved) existed in the prose "After the Design" section but was missing from the checklist and process flow diagram
|
||||
- Since agents follow the diagram and checklist more reliably than prose, the spec review step was being skipped entirely
|
||||
- Added step 7 (spec review loop) to the checklist and corresponding nodes to the dot graph
|
||||
- Tested with `claude --plugin-dir` and `claude-session-driver`: worker now correctly dispatches the reviewer
|
||||
|
||||
**Cursor install command** (PR #676)
|
||||
|
||||
- Fixed Cursor install command in README: `/plugin-add` → `/add-plugin` (confirmed via Cursor 2.5 release announcement)
|
||||
|
||||
**User review gate in brainstorming** (#565)
|
||||
|
||||
- Added explicit user review step between spec completion and writing-plans handoff
|
||||
- User must approve the spec before implementation planning begins
|
||||
- Checklist, process flow, and prose updated with the new gate
|
||||
|
||||
**Session-start hook emits context only once per platform**
|
||||
|
||||
- Hook now detects whether it's running in Claude Code or another platform
|
||||
- Emits `hookSpecificOutput` for Claude Code, `additional_context` for others — prevents double context injection
|
||||
|
||||
**Linting fix in token analysis script**
|
||||
|
||||
- `except:` → `except Exception:` in `tests/claude-code/analyze-token-usage.py`
|
||||
|
||||
### Maintenance
|
||||
|
||||
**Removed dead code**
|
||||
|
||||
- Deleted `lib/skills-core.js` and its test (`tests/opencode/test-skills-core.js`) — unused since February 2026
|
||||
- Removed skills-core existence check from `tests/opencode/test-plugin-loading.sh`
|
||||
|
||||
### Community
|
||||
|
||||
- @karuturi — Claude Code official marketplace install instructions (PR #610)
|
||||
- @mvanhorn — session-start hook dual-emit fix, OpenCode tool mapping fix
|
||||
- @daniel-graham — linting fix for bare except
|
||||
- PR #585 author — Windows/Linux hooks quoting fix
|
||||
|
||||
---
|
||||
|
||||
## v5.0.0 (2026-03-09)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**Specs and plans directory restructured**
|
||||
|
||||
- Specs (brainstorming output) now save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`
|
||||
- Plans (writing-plans output) now save to `docs/superpowers/plans/YYYY-MM-DD-<feature-name>.md`
|
||||
- User preferences for spec/plan locations override these defaults
|
||||
- All internal skill references, test files, and example paths updated to match
|
||||
- Migration: move existing files from `docs/plans/` to new locations if desired
|
||||
|
||||
**Subagent-driven development mandatory on capable harnesses**
|
||||
|
||||
Writing-plans no longer offers a choice between subagent-driven and executing-plans. On harnesses with subagent support (Claude Code, Codex), subagent-driven-development is required. Executing-plans is reserved for harnesses without subagent capability, and now tells the user that Superpowers works better on a subagent-capable platform.
|
||||
|
||||
**Executing-plans no longer batches**
|
||||
|
||||
Removed the "execute 3 tasks then stop for review" pattern. Plans now execute continuously, stopping only for blockers.
|
||||
|
||||
**Slash commands deprecated**
|
||||
|
||||
`/brainstorm`, `/write-plan`, and `/execute-plan` now show deprecation notices pointing users to the corresponding skills. Commands will be removed in the next major release.
|
||||
|
||||
### New Features
|
||||
|
||||
**Visual brainstorming companion**
|
||||
|
||||
Optional browser-based companion for brainstorming sessions. When a topic would benefit from visuals, the brainstorming skill offers to show mockups, diagrams, comparisons, and other content in a browser window alongside terminal conversation.
|
||||
|
||||
- `lib/brainstorm-server/` — WebSocket server with browser helper library, session management scripts, and dark/light themed frame template ("Superpowers Brainstorming" with GitHub link)
|
||||
- `skills/brainstorming/visual-companion.md` — Progressive disclosure guide for server workflow, screen authoring, and feedback collection
|
||||
- Brainstorming skill adds a visual companion decision point to its process flow: after exploring project context, the skill evaluates whether upcoming questions involve visual content and offers the companion in its own message
|
||||
- Per-question decision: even after accepting, each question is evaluated for whether browser or terminal is more appropriate
|
||||
- Integration tests in `tests/brainstorm-server/`
|
||||
|
||||
**Document review system**
|
||||
|
||||
Automated review loops for spec and plan documents using subagent dispatch:
|
||||
|
||||
- `skills/brainstorming/spec-document-reviewer-prompt.md` — Reviewer checks completeness, consistency, architecture, and YAGNI
|
||||
- `skills/writing-plans/plan-document-reviewer-prompt.md` — Reviewer checks spec alignment, task decomposition, file structure, and file size
|
||||
- Brainstorming dispatches spec reviewer after writing the design doc
|
||||
- Writing-plans includes chunk-based plan review loop after each section
|
||||
- Review loops repeat until approved or escalate after 5 iterations
|
||||
- End-to-end tests in `tests/claude-code/test-document-review-system.sh`
|
||||
- Design spec and implementation plan in `docs/superpowers/`
|
||||
|
||||
**Architecture guidance across the skill pipeline**
|
||||
|
||||
Design-for-isolation and file-size-awareness guidance added to brainstorming, writing-plans, and subagent-driven-development:
|
||||
|
||||
- **Brainstorming** — New sections: "Design for isolation and clarity" (clear boundaries, well-defined interfaces, independently testable units) and "Working in existing codebases" (follow existing patterns, targeted improvements only)
|
||||
- **Writing-plans** — New "File Structure" section: map out files and responsibilities before defining tasks. New "Scope Check" backstop: catch multi-subsystem specs that should have been decomposed during brainstorming
|
||||
- **SDD implementer** — New "Code Organization" section (follow plan's file structure, report concerns about growing files) and "When You're in Over Your Head" escalation guidance
|
||||
- **SDD code quality reviewer** — Now checks architecture, unit decomposition, plan conformance, and file growth
|
||||
- **Spec/plan reviewers** — Architecture and file size added to review criteria
|
||||
- **Scope assessment** — Brainstorming now assesses whether a project is too large for a single spec. Multi-subsystem requests are flagged early and decomposed into sub-projects, each with its own spec → plan → implementation cycle
|
||||
|
||||
**Subagent-driven development improvements**
|
||||
|
||||
- **Model selection** — Guidance for choosing model capability by task type: cheap models for mechanical implementation, standard for integration, capable for architecture and review
|
||||
- **Implementer status protocol** — Subagents now report DONE, DONE_WITH_CONCERNS, BLOCKED, or NEEDS_CONTEXT. Controller handles each status appropriately: re-dispatching with more context, upgrading model capability, breaking tasks apart, or escalating to human
|
||||
|
||||
### Improvements
|
||||
|
||||
**Instruction priority hierarchy**
|
||||
|
||||
Added explicit priority ordering to using-superpowers:
|
||||
|
||||
1. User's explicit instructions (CLAUDE.md, AGENTS.md, direct requests) — highest priority
|
||||
2. Superpowers skills — override default system behavior
|
||||
3. Default system prompt — lowest priority
|
||||
|
||||
If CLAUDE.md or AGENTS.md says "don't use TDD" and a skill says "always use TDD," the user's instructions win.
|
||||
|
||||
**SUBAGENT-STOP gate**
|
||||
|
||||
Added `<SUBAGENT-STOP>` block to using-superpowers. Subagents dispatched for specific tasks now skip the skill instead of activating the 1% rule and invoking full skill workflows.
|
||||
|
||||
**Multi-platform improvements**
|
||||
|
||||
- Codex tool mapping moved to progressive disclosure reference file (`references/codex-tools.md`)
|
||||
- Platform Adaptation pointer added so non-Claude-Code platforms can find tool equivalents
|
||||
- Plan headers now address "agentic workers" instead of "Claude" specifically
|
||||
- Collab feature requirement documented in `docs/README.codex.md`
|
||||
|
||||
**Writing-plans template updates**
|
||||
|
||||
- Plan steps now use checkbox syntax (`- [ ] **Step N:**`) for progress tracking
|
||||
- Plan header references both subagent-driven-development and executing-plans with platform-aware routing
|
||||
|
||||
---
|
||||
|
||||
## v4.3.1 (2026-02-21)
|
||||
|
||||
### Added
|
||||
|
||||
**Cursor support**
|
||||
|
||||
Superpowers now works with Cursor's plugin system. Includes a `.cursor-plugin/plugin.json` manifest and Cursor-specific installation instructions in the README. The SessionStart hook output now includes an `additional_context` field alongside the existing `hookSpecificOutput.additionalContext` for Cursor hook compatibility.
|
||||
|
||||
### Fixed
|
||||
|
||||
**Windows: Restored polyglot wrapper for reliable hook execution (#518, #504, #491, #487, #466, #440)**
|
||||
|
||||
Claude Code's `.sh` auto-detection on Windows was prepending `bash` to the hook command, breaking execution. The fix:
|
||||
|
||||
- Renamed `session-start.sh` to `session-start` (extensionless) so auto-detection doesn't interfere
|
||||
- Restored `run-hook.cmd` polyglot wrapper with multi-location bash discovery (standard Git for Windows paths, then PATH fallback)
|
||||
- Exits silently if no bash is found rather than erroring
|
||||
- On Unix, the wrapper runs the script directly via `exec bash`
|
||||
- Uses POSIX-safe `dirname "$0"` path resolution (works on dash/sh, not just bash)
|
||||
|
||||
This fixes SessionStart failures on Windows with spaces in paths, missing WSL, `set -euo pipefail` fragility on MSYS, and backslash mangling.
|
||||
|
||||
## v4.3.0 (2026-02-12)
|
||||
|
||||
This fix should dramatically improve superpowers skills compliance and should reduce the chances of Claude entering its native plan mode unintentionally.
|
||||
|
||||
### Changed
|
||||
|
||||
**Brainstorming skill now enforces its workflow instead of describing it**
|
||||
|
||||
Models were skipping the design phase and jumping straight to implementation skills like frontend-design, or collapsing the entire brainstorming process into a single text block. The skill now uses hard gates, a mandatory checklist, and a graphviz process flow to enforce compliance:
|
||||
|
||||
- `<HARD-GATE>`: no implementation skills, code, or scaffolding until design is presented and user approves
|
||||
- Explicit checklist (6 items) that must be created as tasks and completed in order
|
||||
- Graphviz process flow with `writing-plans` as the only valid terminal state
|
||||
- Anti-pattern callout for "this is too simple to need a design" — the exact rationalization models use to skip the process
|
||||
- Design section sizing based on section complexity, not project complexity
|
||||
|
||||
**Using-superpowers workflow graph intercepts EnterPlanMode**
|
||||
|
||||
Added an `EnterPlanMode` intercept to the skill flow graph. When the model is about to enter Claude's native plan mode, it checks whether brainstorming has happened and routes through the brainstorming skill instead. Plan mode is never entered.
|
||||
|
||||
### Fixed
|
||||
|
||||
**SessionStart hook now runs synchronously**
|
||||
|
||||
Changed `async: true` to `async: false` in hooks.json. When async, the hook could fail to complete before the model's first turn, meaning using-superpowers instructions weren't in context for the first message.
|
||||
|
||||
## v4.2.0 (2026-02-05)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**Codex: Replaced bootstrap CLI with native skill discovery**
|
||||
|
||||
The `superpowers-codex` bootstrap CLI, Windows `.cmd` wrapper, and related bootstrap content file have been removed. Codex now uses native skill discovery via `~/.agents/skills/superpowers/` symlink, so the old `use_skill`/`find_skills` CLI tools are no longer needed.
|
||||
|
||||
Installation is now just clone + symlink (documented in INSTALL.md). No Node.js dependency required. The old `~/.codex/skills/` path is deprecated.
|
||||
|
||||
### Fixes
|
||||
|
||||
**Windows: Fixed Claude Code 2.1.x hook execution (#331)**
|
||||
|
||||
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).
|
||||
|
||||
**Windows: SessionStart hook runs async to prevent terminal freeze (#404, #413, #414, #419)**
|
||||
|
||||
The synchronous SessionStart hook blocked the TUI from entering raw mode on Windows, freezing all keyboard input. Running the hook async prevents the freeze while still injecting superpowers context.
|
||||
|
||||
**Windows: Fixed O(n^2) `escape_for_json` performance**
|
||||
|
||||
The character-by-character loop using `${input:$i:1}` was O(n^2) in bash due to substring copy overhead. On Windows Git Bash this took 60+ seconds. Replaced with bash parameter substitution (`${s//old/new}`) which runs each pattern as a single C-level pass — 7x faster on macOS, dramatically faster on Windows.
|
||||
|
||||
**Codex: Fixed Windows/PowerShell invocation (#285, #243)**
|
||||
|
||||
- Windows doesn't respect shebangs, so directly invoking the extensionless `superpowers-codex` script triggered an "Open with" dialog. All invocations now prefixed with `node`.
|
||||
- Fixed `~/` path expansion on Windows — PowerShell doesn't expand `~` when passed as an argument to `node`. Changed to `$HOME` which expands correctly in both bash and PowerShell.
|
||||
|
||||
**Codex: Fixed path resolution in installer**
|
||||
|
||||
Used `fileURLToPath()` instead of manual URL pathname parsing to correctly handle paths with spaces and special characters on all platforms.
|
||||
|
||||
**Codex: Fixed stale skills path in writing-skills**
|
||||
|
||||
Updated `~/.codex/skills/` reference (deprecated) to `~/.agents/skills/` for native discovery.
|
||||
|
||||
### Improvements
|
||||
|
||||
**Worktree isolation now required before implementation**
|
||||
|
||||
Added `using-git-worktrees` as a required skill for both `subagent-driven-development` and `executing-plans`. Implementation workflows now explicitly require setting up an isolated worktree before starting work, preventing accidental work directly on main.
|
||||
|
||||
**Main branch protection softened to require explicit consent**
|
||||
|
||||
Instead of prohibiting main branch work entirely, the skills now allow it with explicit user consent. More flexible while still ensuring users are aware of the implications.
|
||||
|
||||
**Simplified installation verification**
|
||||
|
||||
Removed `/help` command check and specific slash command list from verification steps. Skills are primarily invoked by describing what you want to do, not by running specific commands.
|
||||
|
||||
**Codex: Clarified subagent tool mapping in bootstrap**
|
||||
|
||||
Improved documentation of how Codex tools map to Claude Code equivalents for subagent workflows.
|
||||
|
||||
### Tests
|
||||
|
||||
- Added worktree requirement test for subagent-driven-development
|
||||
- Added main branch red flag warning test
|
||||
- Fixed case sensitivity in skill recognition test assertions
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
**Slash commands now user-only**
|
||||
|
||||
Added `disable-model-invocation: true` to all three slash commands (`/brainstorm`, `/execute-plan`, `/write-plan`). Claude can no longer invoke these commands via the Skill tool—they're restricted to manual user invocation only.
|
||||
|
||||
The underlying skills (`superpowers:brainstorming`, `superpowers:executing-plans`, `superpowers:writing-plans`) remain available for Claude to invoke autonomously. This change prevents confusion when Claude would invoke a command that just redirects to a skill anyway.
|
||||
|
||||
## v4.0.1 (2025-12-23)
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -12,6 +498,14 @@ Fixed a confusing pattern where Claude would invoke a skill via the Skill tool,
|
||||
- Changed "read the skill" → "invoke the skill" in instructions
|
||||
- Updated slash commands to use fully qualified skill names (e.g., `superpowers:brainstorming`)
|
||||
|
||||
**Added GitHub thread reply guidance to receiving-code-review** (h/t @ralphbean)
|
||||
|
||||
Added a note about replying to inline review comments in the original thread rather than as top-level PR comments.
|
||||
|
||||
**Added automation-over-documentation guidance to writing-skills** (h/t @EthanJStark)
|
||||
|
||||
Added guidance that mechanical constraints should be automated, not documented—save skills for judgment calls.
|
||||
|
||||
## v4.0.0 (2025-12-17)
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name: code-reviewer
|
||||
description: |
|
||||
Use this agent when a major project step has been completed and needs to be reviewed against the original plan and coding standards. Examples: <example>Context: The user is creating a code-review agent that should be called after a logical chunk of code is written. user: "I've finished implementing the user authentication system as outlined in step 3 of our plan" assistant: "Great work! Now let me use the code-reviewer agent to review the implementation against our plan and coding standards" <commentary>Since a major project step has been completed, use the code-reviewer agent to validate the work against the plan and identify any issues.</commentary></example> <example>Context: User has completed a significant feature implementation. user: "The API endpoints for the task management system are now complete - that covers step 2 from our architecture document" assistant: "Excellent! Let me have the code-reviewer agent examine this implementation to ensure it aligns with our plan and follows best practices" <commentary>A numbered step from the planning document has been completed, so the code-reviewer agent should review the work.</commentary></example>
|
||||
model: inherit
|
||||
---
|
||||
|
||||
You are a Senior Code Reviewer with expertise in software architecture, design patterns, and best practices. Your role is to review completed project steps against original plans and ensure code quality standards are met.
|
||||
|
||||
BIN
assets/app-icon.png
Normal file
BIN
assets/app-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
1
assets/superpowers-small.svg
Normal file
1
assets/superpowers-small.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="Calque_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M394.28,207.8c.81,2.41,1.39,4.78,1.8,7.07,1.61,9.03-.93,17.78-5.99,21.74-22.6,17.7-49.85,29.35-75.34,38.6-.59.22-1.09.28-1.4.34-2.22.47-4.95,1.04-7.25,0-1.46-.66-2.25-1.74-2.66-2.3-1.56-2.1-1.59-4.31-1.56-5.13.1-2.67-.01-4.69,0-4.82.45-3.52.91-10.66,1.41-21.28.6-3.87,2.16-9.63,6.94-13.96,4.01-3.62,8.33-4.6,14.59-5.87,10.76-2.19,37.21-8.22,47.42-16.56,1.63-1.33,2.97-2.65,4.19-3.96,3.72-3.99,6.39-7.92,7.93-10.36,3.22,3.22,7.25,8.48,9.92,16.47Z"/><path d="M428.67,185.28c-2.33,11.99-8.91,22.32-15.88,30.38.27-5.5-.05-12.11-1.86-19.08-5.04-19.36-19.74-34.7-37.78-37.78-32.21-9.74-70.59,3.79-99.08,18.29-3.87,1.95-9.52-2.77-11.84-8.16-3.32-7.71-1.63-6.28,2.61-8.49,38.31-20.03,82.01-39.61,123.91-29.7,8.26,1.95,15.96,5.26,23.48,10.54,11.32,7.96,20.21,24.74,16.44,44Z"/><path d="M117.72,304.2c-.81-2.41-1.39-4.78-1.8-7.07-1.61-9.03.93-17.78,5.99-21.74,22.6-17.7,49.85-29.35,75.34-38.6.59-.22,1.09-.28,1.4-.34,2.22-.47,4.95-1.04,7.25,0,1.46.66,2.25,1.74,2.66,2.3,1.56,2.1,1.59,4.31,1.56,5.13-.1,2.67.01,4.69,0,4.82-.45,3.52-.91,10.66-1.41,21.28-.6,3.87-2.16,9.63-6.94,13.96-4.01,3.62-8.33,4.6-14.59,5.87-10.76,2.19-37.21,8.22-47.42,16.56-1.63,1.33-2.97,2.65-4.19,3.96-3.72,3.99-6.39,7.92-7.93,10.36-3.22-3.22-7.25-8.48-9.92-16.47Z"/><path d="M83.33,326.72c2.33-11.99,8.91-22.32,15.88-30.38-.27,5.5.05,12.11,1.86,19.08,5.04,19.36,19.74,34.7,37.78,37.78,32.21,9.74,70.59-3.79,99.08-18.29,3.87-1.95,9.52,2.77,11.84,8.16,3.32,7.71,1.63,6.28-2.61,8.49-38.31,20.03-82.01,39.61-123.91,29.7-8.26-1.95-15.96-5.26-23.48-10.54-11.32-7.96-20.21-24.74-16.44-44Z"/><ellipse cx="255.16" cy="258.86" rx="28.95" ry="28.76"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores requirements and design before implementation."
|
||||
---
|
||||
|
||||
Invoke the superpowers:brainstorming skill and follow it exactly as presented to you
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
description: Execute plan in batches with review checkpoints
|
||||
---
|
||||
|
||||
Invoke the superpowers:executing-plans skill and follow it exactly as presented to you
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
description: Create detailed implementation plan with bite-sized tasks
|
||||
---
|
||||
|
||||
Invoke the superpowers:writing-plans skill and follow it exactly as presented to you
|
||||
@@ -1,6 +1,6 @@
|
||||
# Superpowers for Codex
|
||||
|
||||
Complete guide for using Superpowers with OpenAI Codex.
|
||||
Guide for using Superpowers with OpenAI Codex via native skill discovery.
|
||||
|
||||
## Quick Install
|
||||
|
||||
@@ -14,63 +14,65 @@ Fetch and follow instructions from https://raw.githubusercontent.com/obra/superp
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- OpenAI Codex access
|
||||
- Shell access to install files
|
||||
- OpenAI Codex CLI
|
||||
- Git
|
||||
|
||||
### Installation Steps
|
||||
### Steps
|
||||
|
||||
#### 1. Clone Superpowers
|
||||
1. Clone the repo:
|
||||
```bash
|
||||
git clone https://github.com/obra/superpowers.git ~/.codex/superpowers
|
||||
```
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.codex/superpowers
|
||||
git clone https://github.com/obra/superpowers.git ~/.codex/superpowers
|
||||
2. Create the skills symlink:
|
||||
```bash
|
||||
mkdir -p ~/.agents/skills
|
||||
ln -s ~/.codex/superpowers/skills ~/.agents/skills/superpowers
|
||||
```
|
||||
|
||||
3. Restart Codex.
|
||||
|
||||
4. **For subagent skills** (optional): Skills like `dispatching-parallel-agents` and `subagent-driven-development` require Codex's multi-agent feature. Add to your Codex config:
|
||||
```toml
|
||||
[features]
|
||||
multi_agent = true
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
Use a junction instead of a symlink (works without Developer Mode):
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills"
|
||||
cmd /c mklink /J "$env:USERPROFILE\.agents\skills\superpowers" "$env:USERPROFILE\.codex\superpowers\skills"
|
||||
```
|
||||
|
||||
#### 2. Install Bootstrap
|
||||
## How It Works
|
||||
|
||||
The bootstrap file is included in the repository at `.codex/superpowers-bootstrap.md`. Codex will automatically use it from the cloned location.
|
||||
|
||||
#### 3. Verify Installation
|
||||
|
||||
Tell Codex:
|
||||
Codex has native skill discovery — it scans `~/.agents/skills/` at startup, parses SKILL.md frontmatter, and loads skills on demand. Superpowers skills are made visible through a single symlink:
|
||||
|
||||
```
|
||||
Run ~/.codex/superpowers/.codex/superpowers-codex find-skills to show available skills
|
||||
~/.agents/skills/superpowers/ → ~/.codex/superpowers/skills/
|
||||
```
|
||||
|
||||
You should see a list of available skills with descriptions.
|
||||
The `using-superpowers` skill is discovered automatically and enforces skill usage discipline — no additional configuration needed.
|
||||
|
||||
## Usage
|
||||
|
||||
### Finding Skills
|
||||
|
||||
```
|
||||
Run ~/.codex/superpowers/.codex/superpowers-codex find-skills
|
||||
```
|
||||
|
||||
### Loading a Skill
|
||||
|
||||
```
|
||||
Run ~/.codex/superpowers/.codex/superpowers-codex use-skill superpowers:brainstorming
|
||||
```
|
||||
|
||||
### Bootstrap All Skills
|
||||
|
||||
```
|
||||
Run ~/.codex/superpowers/.codex/superpowers-codex bootstrap
|
||||
```
|
||||
|
||||
This loads the complete bootstrap with all skill information.
|
||||
Skills are discovered automatically. Codex activates them when:
|
||||
- You mention a skill by name (e.g., "use brainstorming")
|
||||
- The task matches a skill's description
|
||||
- The `using-superpowers` skill directs Codex to use one
|
||||
|
||||
### Personal Skills
|
||||
|
||||
Create your own skills in `~/.codex/skills/`:
|
||||
Create your own skills in `~/.agents/skills/`:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.codex/skills/my-skill
|
||||
mkdir -p ~/.agents/skills/my-skill
|
||||
```
|
||||
|
||||
Create `~/.codex/skills/my-skill/SKILL.md`:
|
||||
Create `~/.agents/skills/my-skill/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
@@ -83,71 +85,42 @@ description: Use when [condition] - [what it does]
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
Personal skills override superpowers skills with the same name.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Codex CLI Tool
|
||||
|
||||
**Location:** `~/.codex/superpowers/.codex/superpowers-codex`
|
||||
|
||||
A Node.js CLI script that provides three commands:
|
||||
- `bootstrap` - Load complete bootstrap with all skills
|
||||
- `use-skill <name>` - Load a specific skill
|
||||
- `find-skills` - List all available skills
|
||||
|
||||
### Shared Core Module
|
||||
|
||||
**Location:** `~/.codex/superpowers/lib/skills-core.js`
|
||||
|
||||
The Codex implementation uses the shared `skills-core` module (ES module format) for skill discovery and parsing. This is the same module used by the OpenCode plugin, ensuring consistent behavior across platforms.
|
||||
|
||||
### Tool Mapping
|
||||
|
||||
Skills written for Claude Code are adapted for Codex with these mappings:
|
||||
|
||||
- `TodoWrite` → `update_plan`
|
||||
- `Task` with subagents → Tell user subagents aren't available, do work directly
|
||||
- `Skill` tool → `~/.codex/superpowers/.codex/superpowers-codex use-skill`
|
||||
- File operations → Native Codex tools
|
||||
The `description` field is how Codex decides when to activate a skill automatically — write it as a clear trigger condition.
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd ~/.codex/superpowers
|
||||
git pull
|
||||
cd ~/.codex/superpowers && git pull
|
||||
```
|
||||
|
||||
Skills update instantly through the symlink.
|
||||
|
||||
## Uninstalling
|
||||
|
||||
```bash
|
||||
rm ~/.agents/skills/superpowers
|
||||
```
|
||||
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
Remove-Item "$env:USERPROFILE\.agents\skills\superpowers"
|
||||
```
|
||||
|
||||
Optionally delete the clone: `rm -rf ~/.codex/superpowers` (Windows: `Remove-Item -Recurse -Force "$env:USERPROFILE\.codex\superpowers"`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Skills not found
|
||||
### Skills not showing up
|
||||
|
||||
1. Verify installation: `ls ~/.codex/superpowers/skills`
|
||||
2. Check CLI works: `~/.codex/superpowers/.codex/superpowers-codex find-skills`
|
||||
3. Verify skills have SKILL.md files
|
||||
1. Verify the symlink: `ls -la ~/.agents/skills/superpowers`
|
||||
2. Check skills exist: `ls ~/.codex/superpowers/skills`
|
||||
3. Restart Codex — skills are discovered at startup
|
||||
|
||||
### CLI script not executable
|
||||
### Windows junction issues
|
||||
|
||||
```bash
|
||||
chmod +x ~/.codex/superpowers/.codex/superpowers-codex
|
||||
```
|
||||
|
||||
### Node.js errors
|
||||
|
||||
The CLI script requires Node.js. Verify:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
|
||||
Should show v14 or higher (v18+ recommended for ES module support).
|
||||
Junctions normally work without special permissions. If creation fails, try running PowerShell as administrator.
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Report issues: https://github.com/obra/superpowers/issues
|
||||
- Main documentation: https://github.com/obra/superpowers
|
||||
- Blog post: https://blog.fsck.com/2025/10/27/skills-for-openai-codex/
|
||||
|
||||
## Note
|
||||
|
||||
Codex support is experimental and may require refinement based on user feedback. If you encounter issues, please report them on GitHub.
|
||||
|
||||
@@ -2,72 +2,57 @@
|
||||
|
||||
Complete guide for using Superpowers with [OpenCode.ai](https://opencode.ai).
|
||||
|
||||
## Quick Install
|
||||
## Installation
|
||||
|
||||
Tell OpenCode:
|
||||
Add superpowers to the `plugin` array in your `opencode.json` (global or project-level):
|
||||
|
||||
```
|
||||
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.
|
||||
```json
|
||||
{
|
||||
"plugin": ["superpowers@git+https://github.com/obra/superpowers.git"]
|
||||
}
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
Restart OpenCode. The plugin installs through OpenCode's plugin manager and
|
||||
registers all skills.
|
||||
|
||||
### Prerequisites
|
||||
Verify by asking: "Tell me about your superpowers"
|
||||
|
||||
- [OpenCode.ai](https://opencode.ai) installed
|
||||
- Node.js installed
|
||||
- Git installed
|
||||
OpenCode uses its own plugin install. If you also use Claude Code, Codex, or
|
||||
another harness, install Superpowers separately for each one.
|
||||
|
||||
### Installation Steps
|
||||
### Migrating from the old symlink-based install
|
||||
|
||||
#### 1. Install Superpowers
|
||||
If you previously installed superpowers using `git clone` and symlinks, remove the old setup:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/superpowers
|
||||
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
|
||||
# Remove old symlinks
|
||||
rm -f ~/.config/opencode/plugins/superpowers.js
|
||||
rm -rf ~/.config/opencode/skills/superpowers
|
||||
|
||||
# Optionally remove the cloned repo
|
||||
rm -rf ~/.config/opencode/superpowers
|
||||
|
||||
# Remove skills.paths from opencode.json if you added one for superpowers
|
||||
```
|
||||
|
||||
#### 2. Register the Plugin
|
||||
|
||||
OpenCode discovers plugins from `~/.config/opencode/plugin/`. Create a symlink:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/plugin
|
||||
ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js ~/.config/opencode/plugin/superpowers.js
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
#### 3. Restart OpenCode
|
||||
|
||||
Restart OpenCode to load the plugin. Superpowers will automatically activate.
|
||||
Then follow the installation steps above.
|
||||
|
||||
## 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 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/`:
|
||||
@@ -91,144 +76,82 @@ description: Use when [condition] - [what it does]
|
||||
|
||||
### 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
|
||||
|
||||
Skills are resolved with this priority order:
|
||||
|
||||
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
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
|
||||
### Tool Mapping
|
||||
|
||||
Skills written for Claude Code are automatically adapted for OpenCode. The plugin provides mapping instructions:
|
||||
|
||||
- `TodoWrite` → `update_plan`
|
||||
- `Task` with subagents → OpenCode's `@mention` system
|
||||
- `Skill` tool → `use_skill` custom tool
|
||||
- File operations → Native OpenCode tools
|
||||
|
||||
## Architecture
|
||||
|
||||
### Plugin Structure
|
||||
|
||||
**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
|
||||
|
||||
**Location:** `~/.config/opencode/superpowers/lib/skills-core.js`
|
||||
|
||||
**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.
|
||||
**Skill Priority:** Project skills > Personal skills > Superpowers skills
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd ~/.config/opencode/superpowers
|
||||
git pull
|
||||
OpenCode installs Superpowers through a git-backed package spec. Some OpenCode
|
||||
and Bun versions pin that resolved git dependency in a lockfile or cache, so a
|
||||
restart may not pick up the newest Superpowers commit. If updates do not appear,
|
||||
clear OpenCode's package cache or reinstall the plugin.
|
||||
|
||||
To pin a specific version, use a branch or tag:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugin": ["superpowers@git+https://github.com/obra/superpowers.git#v5.0.3"]
|
||||
}
|
||||
```
|
||||
|
||||
Restart OpenCode to load the updates.
|
||||
## How It Works
|
||||
|
||||
The plugin does two things:
|
||||
|
||||
1. **Injects bootstrap context** via the `experimental.chat.system.transform` hook, adding superpowers awareness to every conversation.
|
||||
2. **Registers the skills directory** via the `config` hook, so OpenCode discovers all superpowers skills without symlinks or manual config.
|
||||
|
||||
### Tool Mapping
|
||||
|
||||
Skills written for Claude Code are automatically adapted for OpenCode:
|
||||
|
||||
- `TodoWrite` → `todowrite`
|
||||
- `Task` with subagents → OpenCode's `@mention` system
|
||||
- `Skill` tool → OpenCode's native `skill` tool
|
||||
- File operations → Native OpenCode tools
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 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`
|
||||
3. Check OpenCode logs: `opencode run "test" --print-logs --log-level DEBUG`
|
||||
4. Look for: `service=plugin path=file:///.../superpowers.js loading plugin`
|
||||
1. Check OpenCode logs: `opencode run --print-logs "hello" 2>&1 | grep -i superpowers`
|
||||
2. Verify the plugin line in your `opencode.json` is correct
|
||||
3. Make sure you're running a recent version of OpenCode
|
||||
|
||||
### Windows install issues
|
||||
|
||||
Some Windows OpenCode builds have upstream installer issues with git-backed
|
||||
plugin specs, including cache paths for `git+https` URLs and Bun not finding
|
||||
`git.exe` even when it works in a normal terminal. If OpenCode cannot install
|
||||
the plugin, try installing with system npm and pointing OpenCode at the local
|
||||
package:
|
||||
|
||||
```powershell
|
||||
npm install superpowers@git+https://github.com/obra/superpowers.git --prefix "$HOME\.config\opencode"
|
||||
```
|
||||
|
||||
Then use the installed package path in `opencode.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugin": ["~/.config/opencode/node_modules/superpowers"]
|
||||
}
|
||||
```
|
||||
|
||||
### Skills not found
|
||||
|
||||
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. Use OpenCode's `skill` tool to list available skills
|
||||
2. Check that the plugin is loading (see above)
|
||||
3. Each skill needs a `SKILL.md` file with valid YAML frontmatter
|
||||
|
||||
### Tools not working
|
||||
### Bootstrap not appearing
|
||||
|
||||
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)))"`
|
||||
|
||||
### Context not injecting
|
||||
|
||||
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. Check OpenCode version supports `experimental.chat.system.transform` hook
|
||||
2. Restart OpenCode after config changes
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Report issues: https://github.com/obra/superpowers/issues
|
||||
- Main documentation: https://github.com/obra/superpowers
|
||||
- OpenCode docs: https://opencode.ai/docs/
|
||||
|
||||
## Testing
|
||||
|
||||
The implementation includes an automated test suite at `tests/opencode/`:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
./tests/opencode/run-tests.sh --integration --verbose
|
||||
|
||||
# Run specific test
|
||||
./tests/opencode/run-tests.sh --test test-tools.sh
|
||||
```
|
||||
|
||||
Tests verify:
|
||||
- Plugin loading
|
||||
- Skills-core library functionality
|
||||
- Tool execution (use_skill, find_skills)
|
||||
- Skill priority resolution
|
||||
- Proper isolation with temp HOME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# OpenCode Support Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Add full superpowers support for OpenCode.ai with a native JavaScript plugin that shares core functionality with the existing Codex implementation.
|
||||
|
||||
|
||||
571
docs/plans/2026-01-17-visual-brainstorming.md
Normal file
571
docs/plans/2026-01-17-visual-brainstorming.md
Normal file
@@ -0,0 +1,571 @@
|
||||
# Visual Brainstorming Companion Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Give Claude a browser-based visual companion for brainstorming sessions - show mockups, prototypes, and interactive choices alongside terminal conversation.
|
||||
|
||||
**Architecture:** Claude writes HTML to a temp file. A local Node.js server watches that file and serves it with an auto-injected helper library. User interactions flow via WebSocket to server stdout, which Claude sees in background task output.
|
||||
|
||||
**Tech Stack:** Node.js, Express, ws (WebSocket), chokidar (file watching)
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Create the Server Foundation
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/brainstorm-server/index.js`
|
||||
- Create: `lib/brainstorm-server/package.json`
|
||||
|
||||
**Step 1: Create package.json**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "brainstorm-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Visual brainstorming companion server for Claude Code",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.3",
|
||||
"express": "^4.18.2",
|
||||
"ws": "^8.14.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Create minimal server that starts**
|
||||
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
const chokidar = require('chokidar');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PORT = process.env.BRAINSTORM_PORT || 3333;
|
||||
const SCREEN_FILE = process.env.BRAINSTORM_SCREEN || '/tmp/brainstorm/screen.html';
|
||||
const SCREEN_DIR = path.dirname(SCREEN_FILE);
|
||||
|
||||
// Ensure screen directory exists
|
||||
if (!fs.existsSync(SCREEN_DIR)) {
|
||||
fs.mkdirSync(SCREEN_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// Create default screen if none exists
|
||||
if (!fs.existsSync(SCREEN_FILE)) {
|
||||
fs.writeFileSync(SCREEN_FILE, `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Brainstorm Companion</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
||||
h1 { color: #333; }
|
||||
p { color: #666; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Brainstorm Companion</h1>
|
||||
<p>Waiting for Claude to push a screen...</p>
|
||||
</body>
|
||||
</html>`);
|
||||
}
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const wss = new WebSocket.Server({ server });
|
||||
|
||||
// Track connected browsers for reload notifications
|
||||
const clients = new Set();
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
clients.add(ws);
|
||||
ws.on('close', () => clients.delete(ws));
|
||||
|
||||
ws.on('message', (data) => {
|
||||
// User interaction event - write to stdout for Claude
|
||||
const event = JSON.parse(data.toString());
|
||||
console.log(JSON.stringify({ type: 'user-event', ...event }));
|
||||
});
|
||||
});
|
||||
|
||||
// Serve current screen with helper.js injected
|
||||
app.get('/', (req, res) => {
|
||||
let html = fs.readFileSync(SCREEN_FILE, 'utf-8');
|
||||
|
||||
// Inject helper script before </body>
|
||||
const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8');
|
||||
const injection = `<script>\n${helperScript}\n</script>`;
|
||||
|
||||
if (html.includes('</body>')) {
|
||||
html = html.replace('</body>', `${injection}\n</body>`);
|
||||
} else {
|
||||
html += injection;
|
||||
}
|
||||
|
||||
res.type('html').send(html);
|
||||
});
|
||||
|
||||
// Watch for screen file changes
|
||||
chokidar.watch(SCREEN_FILE).on('change', () => {
|
||||
console.log(JSON.stringify({ type: 'screen-updated', file: SCREEN_FILE }));
|
||||
// Notify all browsers to reload
|
||||
clients.forEach(ws => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'reload' }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(PORT, '127.0.0.1', () => {
|
||||
console.log(JSON.stringify({ type: 'server-started', port: PORT, url: `http://localhost:${PORT}` }));
|
||||
});
|
||||
```
|
||||
|
||||
**Step 3: Run npm install**
|
||||
|
||||
Run: `cd lib/brainstorm-server && npm install`
|
||||
Expected: Dependencies installed
|
||||
|
||||
**Step 4: Test server starts**
|
||||
|
||||
Run: `cd lib/brainstorm-server && timeout 3 node index.js || true`
|
||||
Expected: See JSON with `server-started` and port info
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/brainstorm-server/
|
||||
git commit -m "feat: add brainstorm server foundation"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Create the Helper Library
|
||||
|
||||
**Files:**
|
||||
- Create: `lib/brainstorm-server/helper.js`
|
||||
|
||||
**Step 1: Create helper.js with event auto-capture**
|
||||
|
||||
```javascript
|
||||
(function() {
|
||||
const WS_URL = 'ws://' + window.location.host;
|
||||
let ws = null;
|
||||
let eventQueue = [];
|
||||
|
||||
function connect() {
|
||||
ws = new WebSocket(WS_URL);
|
||||
|
||||
ws.onopen = () => {
|
||||
// Send any queued events
|
||||
eventQueue.forEach(e => ws.send(JSON.stringify(e)));
|
||||
eventQueue = [];
|
||||
};
|
||||
|
||||
ws.onmessage = (msg) => {
|
||||
const data = JSON.parse(msg.data);
|
||||
if (data.type === 'reload') {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
// Reconnect after 1 second
|
||||
setTimeout(connect, 1000);
|
||||
};
|
||||
}
|
||||
|
||||
function send(event) {
|
||||
event.timestamp = Date.now();
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(event));
|
||||
} else {
|
||||
eventQueue.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-capture clicks on interactive elements
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('button, a, [data-choice], [role="button"], input[type="submit"]');
|
||||
if (!target) return;
|
||||
|
||||
// Don't capture regular link navigation
|
||||
if (target.tagName === 'A' && !target.dataset.choice) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
send({
|
||||
type: 'click',
|
||||
text: target.textContent.trim(),
|
||||
choice: target.dataset.choice || null,
|
||||
id: target.id || null,
|
||||
className: target.className || null
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-capture form submissions
|
||||
document.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const form = e.target;
|
||||
const formData = new FormData(form);
|
||||
const data = {};
|
||||
formData.forEach((value, key) => { data[key] = value; });
|
||||
|
||||
send({
|
||||
type: 'submit',
|
||||
formId: form.id || null,
|
||||
formName: form.name || null,
|
||||
data: data
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-capture input changes (debounced)
|
||||
let inputTimeout = null;
|
||||
document.addEventListener('input', (e) => {
|
||||
const target = e.target;
|
||||
if (!target.matches('input, textarea, select')) return;
|
||||
|
||||
clearTimeout(inputTimeout);
|
||||
inputTimeout = setTimeout(() => {
|
||||
send({
|
||||
type: 'input',
|
||||
name: target.name || null,
|
||||
id: target.id || null,
|
||||
value: target.value,
|
||||
inputType: target.type || target.tagName.toLowerCase()
|
||||
});
|
||||
}, 500); // 500ms debounce
|
||||
});
|
||||
|
||||
// Expose for explicit use if needed
|
||||
window.brainstorm = {
|
||||
send: send,
|
||||
choice: (value, metadata = {}) => send({ type: 'choice', value, ...metadata })
|
||||
};
|
||||
|
||||
connect();
|
||||
})();
|
||||
```
|
||||
|
||||
**Step 2: Verify helper.js is syntactically valid**
|
||||
|
||||
Run: `node -c lib/brainstorm-server/helper.js`
|
||||
Expected: No syntax errors
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/brainstorm-server/helper.js
|
||||
git commit -m "feat: add browser helper library for event capture"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Write Tests for the Server
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/brainstorm-server/server.test.js`
|
||||
- Create: `tests/brainstorm-server/package.json`
|
||||
|
||||
**Step 1: Create test package.json**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "brainstorm-server-tests",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"test": "node server.test.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Write server tests**
|
||||
|
||||
```javascript
|
||||
const { spawn } = require('child_process');
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
const SERVER_PATH = path.join(__dirname, '../../lib/brainstorm-server/index.js');
|
||||
const TEST_PORT = 3334;
|
||||
const TEST_SCREEN = '/tmp/brainstorm-test/screen.html';
|
||||
|
||||
// Clean up test directory
|
||||
function cleanup() {
|
||||
if (fs.existsSync(path.dirname(TEST_SCREEN))) {
|
||||
fs.rmSync(path.dirname(TEST_SCREEN), { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function fetch(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get(url, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => resolve({ status: res.statusCode, body: data }));
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
cleanup();
|
||||
|
||||
// Start server
|
||||
const server = spawn('node', [SERVER_PATH], {
|
||||
env: { ...process.env, BRAINSTORM_PORT: TEST_PORT, BRAINSTORM_SCREEN: TEST_SCREEN }
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
server.stdout.on('data', (data) => { stdout += data.toString(); });
|
||||
server.stderr.on('data', (data) => { console.error('Server stderr:', data.toString()); });
|
||||
|
||||
await sleep(1000); // Wait for server to start
|
||||
|
||||
try {
|
||||
// Test 1: Server starts and outputs JSON
|
||||
console.log('Test 1: Server startup message');
|
||||
assert(stdout.includes('server-started'), 'Should output server-started');
|
||||
assert(stdout.includes(TEST_PORT.toString()), 'Should include port');
|
||||
console.log(' PASS');
|
||||
|
||||
// Test 2: GET / returns HTML with helper injected
|
||||
console.log('Test 2: Serves HTML with helper injected');
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert(res.body.includes('brainstorm'), 'Should include brainstorm content');
|
||||
assert(res.body.includes('WebSocket'), 'Should have helper.js injected');
|
||||
console.log(' PASS');
|
||||
|
||||
// Test 3: WebSocket connection and event relay
|
||||
console.log('Test 3: WebSocket relays events to stdout');
|
||||
stdout = ''; // Reset stdout capture
|
||||
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws.on('open', resolve));
|
||||
|
||||
ws.send(JSON.stringify({ type: 'click', text: 'Test Button' }));
|
||||
await sleep(100);
|
||||
|
||||
assert(stdout.includes('user-event'), 'Should relay user events');
|
||||
assert(stdout.includes('Test Button'), 'Should include event data');
|
||||
ws.close();
|
||||
console.log(' PASS');
|
||||
|
||||
// Test 4: File change triggers reload notification
|
||||
console.log('Test 4: File change notifies browsers');
|
||||
const ws2 = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws2.on('open', resolve));
|
||||
|
||||
let gotReload = false;
|
||||
ws2.on('message', (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
if (msg.type === 'reload') gotReload = true;
|
||||
});
|
||||
|
||||
// Modify the screen file
|
||||
fs.writeFileSync(TEST_SCREEN, '<html><body>Updated</body></html>');
|
||||
await sleep(500);
|
||||
|
||||
assert(gotReload, 'Should send reload message on file change');
|
||||
ws2.close();
|
||||
console.log(' PASS');
|
||||
|
||||
console.log('\nAll tests passed!');
|
||||
|
||||
} finally {
|
||||
server.kill();
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
runTests().catch(err => {
|
||||
console.error('Test failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
```
|
||||
|
||||
**Step 3: Run tests**
|
||||
|
||||
Run: `cd tests/brainstorm-server && npm install ws && node server.test.js`
|
||||
Expected: All tests pass
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/brainstorm-server/
|
||||
git commit -m "test: add brainstorm server integration tests"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Add Visual Companion to Brainstorming Skill
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/brainstorming/SKILL.md`
|
||||
- Create: `skills/brainstorming/visual-companion.md` (supporting doc)
|
||||
|
||||
**Step 1: Create the supporting documentation**
|
||||
|
||||
Create `skills/brainstorming/visual-companion.md`:
|
||||
|
||||
```markdown
|
||||
# Visual Companion Reference
|
||||
|
||||
## Starting the Server
|
||||
|
||||
Run as a background job:
|
||||
|
||||
```bash
|
||||
node ${PLUGIN_ROOT}/lib/brainstorm-server/index.js
|
||||
```
|
||||
|
||||
Tell the user: "I've started a visual companion at http://localhost:3333 - open it in a browser."
|
||||
|
||||
## Pushing Screens
|
||||
|
||||
Write HTML to `/tmp/brainstorm/screen.html`. The server watches this file and auto-refreshes the browser.
|
||||
|
||||
## Reading User Responses
|
||||
|
||||
Check the background task output for JSON events:
|
||||
|
||||
```json
|
||||
{"type":"user-event","type":"click","text":"Option A","choice":"optionA","timestamp":1234567890}
|
||||
{"type":"user-event","type":"submit","data":{"notes":"My feedback"},"timestamp":1234567891}
|
||||
```
|
||||
|
||||
Event types:
|
||||
- **click**: User clicked button or `data-choice` element
|
||||
- **submit**: User submitted form (includes all form data)
|
||||
- **input**: User typed in field (debounced 500ms)
|
||||
|
||||
## HTML Patterns
|
||||
|
||||
### Choice Cards
|
||||
|
||||
```html
|
||||
<div class="options">
|
||||
<button data-choice="optionA">
|
||||
<h3>Option A</h3>
|
||||
<p>Description</p>
|
||||
</button>
|
||||
<button data-choice="optionB">
|
||||
<h3>Option B</h3>
|
||||
<p>Description</p>
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Interactive Mockup
|
||||
|
||||
```html
|
||||
<div class="mockup">
|
||||
<header data-choice="header">App Header</header>
|
||||
<nav data-choice="nav">Navigation</nav>
|
||||
<main data-choice="main">Content</main>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Form with Notes
|
||||
|
||||
```html
|
||||
<form>
|
||||
<label>Priority: <input type="range" name="priority" min="1" max="5"></label>
|
||||
<textarea name="notes" placeholder="Additional thoughts..."></textarea>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
### Explicit JavaScript
|
||||
|
||||
```html
|
||||
<button onclick="brainstorm.choice('custom', {extra: 'data'})">Custom</button>
|
||||
```
|
||||
```
|
||||
|
||||
**Step 2: Add visual companion section to brainstorming skill**
|
||||
|
||||
Add after "Key Principles" in `skills/brainstorming/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
|
||||
## Visual Companion (Optional)
|
||||
|
||||
When brainstorming involves visual elements - UI mockups, wireframes, interactive prototypes - use the browser-based visual companion.
|
||||
|
||||
**When to use:**
|
||||
- Presenting UI/UX options that benefit from visual comparison
|
||||
- Showing wireframes or layout options
|
||||
- Gathering structured feedback (ratings, forms)
|
||||
- Prototyping click interactions
|
||||
|
||||
**How it works:**
|
||||
1. Start the server as a background job
|
||||
2. Tell user to open http://localhost:3333
|
||||
3. Write HTML to `/tmp/brainstorm/screen.html` (auto-refreshes)
|
||||
4. Check background task output for user interactions
|
||||
|
||||
The terminal remains the primary conversation interface. The browser is a visual aid.
|
||||
|
||||
**Reference:** See `visual-companion.md` in this skill directory for HTML patterns and API details.
|
||||
```
|
||||
|
||||
**Step 3: Verify the edits**
|
||||
|
||||
Run: `grep -A5 "Visual Companion" skills/brainstorming/SKILL.md`
|
||||
Expected: Shows the new section
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/brainstorming/
|
||||
git commit -m "feat: add visual companion to brainstorming skill"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Add Server to Plugin Ignore (Optional Cleanup)
|
||||
|
||||
**Files:**
|
||||
- Check if `.gitignore` needs node_modules exclusion for lib/brainstorm-server
|
||||
|
||||
**Step 1: Check current gitignore**
|
||||
|
||||
Run: `cat .gitignore 2>/dev/null || echo "No .gitignore"`
|
||||
|
||||
**Step 2: Add node_modules if needed**
|
||||
|
||||
If not already present, add:
|
||||
```
|
||||
lib/brainstorm-server/node_modules/
|
||||
```
|
||||
|
||||
**Step 3: Commit if changed**
|
||||
|
||||
```bash
|
||||
git add .gitignore
|
||||
git commit -m "chore: ignore brainstorm-server node_modules"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
After completing all tasks:
|
||||
|
||||
1. **Server** at `lib/brainstorm-server/` - Node.js server that watches HTML file and relays events
|
||||
2. **Helper library** auto-injected - captures clicks, forms, inputs
|
||||
3. **Tests** at `tests/brainstorm-server/` - verifies server behavior
|
||||
4. **Brainstorming skill** updated with visual companion section and `visual-companion.md` reference doc
|
||||
|
||||
**To use:**
|
||||
1. Start server as background job: `node lib/brainstorm-server/index.js &`
|
||||
2. Tell user to open `http://localhost:3333`
|
||||
3. Write HTML to `/tmp/brainstorm/screen.html`
|
||||
4. Check task output for user events
|
||||
301
docs/superpowers/plans/2026-01-22-document-review-system.md
Normal file
301
docs/superpowers/plans/2026-01-22-document-review-system.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# Document Review System Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan.
|
||||
|
||||
**Goal:** Add spec and plan document review loops to the brainstorming and writing-plans skills.
|
||||
|
||||
**Architecture:** Create reviewer prompt templates in each skill directory. Modify skill files to add review loops after document creation. Use Task tool with general-purpose subagent for reviewer dispatch.
|
||||
|
||||
**Tech Stack:** Markdown skill files, subagent dispatch via Task tool
|
||||
|
||||
**Spec:** docs/superpowers/specs/2026-01-22-document-review-system-design.md
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1: Spec Document Reviewer
|
||||
|
||||
This chunk adds the spec document reviewer to the brainstorming skill.
|
||||
|
||||
### Task 1: Create Spec Document Reviewer Prompt Template
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/brainstorming/spec-document-reviewer-prompt.md`
|
||||
|
||||
- [ ] **Step 1:** Create the reviewer prompt template file
|
||||
|
||||
```markdown
|
||||
# Spec Document Reviewer Prompt Template
|
||||
|
||||
Use this template when dispatching a spec document reviewer subagent.
|
||||
|
||||
**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning.
|
||||
|
||||
**Dispatch after:** Spec document is written to docs/superpowers/specs/
|
||||
|
||||
```
|
||||
Task tool (general-purpose):
|
||||
description: "Review spec document"
|
||||
prompt: |
|
||||
You are a spec document reviewer. Verify this spec is complete and ready for planning.
|
||||
|
||||
**Spec to review:** [SPEC_FILE_PATH]
|
||||
|
||||
## What to Check
|
||||
|
||||
| Category | What to Look For |
|
||||
|----------|------------------|
|
||||
| Completeness | TODOs, placeholders, "TBD", incomplete sections |
|
||||
| Coverage | Missing error handling, edge cases, integration points |
|
||||
| Consistency | Internal contradictions, conflicting requirements |
|
||||
| Clarity | Ambiguous requirements |
|
||||
| YAGNI | Unrequested features, over-engineering |
|
||||
|
||||
## CRITICAL
|
||||
|
||||
Look especially hard for:
|
||||
- Any TODO markers or placeholder text
|
||||
- Sections saying "to be defined later" or "will spec when X is done"
|
||||
- Sections noticeably less detailed than others
|
||||
|
||||
## Output Format
|
||||
|
||||
## Spec Review
|
||||
|
||||
**Status:** ✅ Approved | ❌ Issues Found
|
||||
|
||||
**Issues (if any):**
|
||||
- [Section X]: [specific issue] - [why it matters]
|
||||
|
||||
**Recommendations (advisory):**
|
||||
- [suggestions that don't block approval]
|
||||
```
|
||||
|
||||
**Reviewer returns:** Status, Issues (if any), Recommendations
|
||||
```
|
||||
|
||||
- [ ] **Step 2:** Verify the file was created correctly
|
||||
|
||||
Run: `cat skills/brainstorming/spec-document-reviewer-prompt.md | head -20`
|
||||
Expected: Shows the header and purpose section
|
||||
|
||||
- [ ] **Step 3:** Commit
|
||||
|
||||
```bash
|
||||
git add skills/brainstorming/spec-document-reviewer-prompt.md
|
||||
git commit -m "feat: add spec document reviewer prompt template"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Add Review Loop to Brainstorming Skill
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/brainstorming/SKILL.md`
|
||||
|
||||
- [ ] **Step 1:** Read the current brainstorming skill
|
||||
|
||||
Run: `cat skills/brainstorming/SKILL.md`
|
||||
|
||||
- [ ] **Step 2:** Add the review loop section after "After the Design"
|
||||
|
||||
Find the "After the Design" section and add a new "Spec Review Loop" section after documentation but before implementation:
|
||||
|
||||
```markdown
|
||||
**Spec Review Loop:**
|
||||
After writing the spec document:
|
||||
1. Dispatch spec-document-reviewer subagent (see spec-document-reviewer-prompt.md)
|
||||
2. If ❌ Issues Found:
|
||||
- Fix the issues in the spec document
|
||||
- Re-dispatch reviewer
|
||||
- Repeat until ✅ Approved
|
||||
3. If ✅ Approved: proceed to implementation setup
|
||||
|
||||
**Review loop guidance:**
|
||||
- Same agent that wrote the spec fixes it (preserves context)
|
||||
- If loop exceeds 5 iterations, surface to human for guidance
|
||||
- Reviewers are advisory - explain disagreements if you believe feedback is incorrect
|
||||
```
|
||||
|
||||
- [ ] **Step 3:** Verify the changes
|
||||
|
||||
Run: `grep -A 15 "Spec Review Loop" skills/brainstorming/SKILL.md`
|
||||
Expected: Shows the new review loop section
|
||||
|
||||
- [ ] **Step 4:** Commit
|
||||
|
||||
```bash
|
||||
git add skills/brainstorming/SKILL.md
|
||||
git commit -m "feat: add spec review loop to brainstorming skill"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2: Plan Document Reviewer
|
||||
|
||||
This chunk adds the plan document reviewer to the writing-plans skill.
|
||||
|
||||
### Task 3: Create Plan Document Reviewer Prompt Template
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/writing-plans/plan-document-reviewer-prompt.md`
|
||||
|
||||
- [ ] **Step 1:** Create the reviewer prompt template file
|
||||
|
||||
```markdown
|
||||
# Plan Document Reviewer Prompt Template
|
||||
|
||||
Use this template when dispatching a plan document reviewer subagent.
|
||||
|
||||
**Purpose:** Verify the plan chunk is complete, matches the spec, and has proper task decomposition.
|
||||
|
||||
**Dispatch after:** Each plan chunk is written
|
||||
|
||||
```
|
||||
Task tool (general-purpose):
|
||||
description: "Review plan chunk N"
|
||||
prompt: |
|
||||
You are a plan document reviewer. Verify this plan chunk is complete and ready for implementation.
|
||||
|
||||
**Plan chunk to review:** [PLAN_FILE_PATH] - Chunk N only
|
||||
**Spec for reference:** [SPEC_FILE_PATH]
|
||||
|
||||
## What to Check
|
||||
|
||||
| Category | What to Look For |
|
||||
|----------|------------------|
|
||||
| Completeness | TODOs, placeholders, incomplete tasks, missing steps |
|
||||
| Spec Alignment | Chunk covers relevant spec requirements, no scope creep |
|
||||
| Task Decomposition | Tasks atomic, clear boundaries, steps actionable |
|
||||
| Task Syntax | Checkbox syntax (`- [ ]`) on tasks and steps |
|
||||
| Chunk Size | Each chunk under 1000 lines |
|
||||
|
||||
## CRITICAL
|
||||
|
||||
Look especially hard for:
|
||||
- Any TODO markers or placeholder text
|
||||
- Steps that say "similar to X" without actual content
|
||||
- Incomplete task definitions
|
||||
- Missing verification steps or expected outputs
|
||||
|
||||
## Output Format
|
||||
|
||||
## Plan Review - Chunk N
|
||||
|
||||
**Status:** ✅ Approved | ❌ Issues Found
|
||||
|
||||
**Issues (if any):**
|
||||
- [Task X, Step Y]: [specific issue] - [why it matters]
|
||||
|
||||
**Recommendations (advisory):**
|
||||
- [suggestions that don't block approval]
|
||||
```
|
||||
|
||||
**Reviewer returns:** Status, Issues (if any), Recommendations
|
||||
```
|
||||
|
||||
- [ ] **Step 2:** Verify the file was created
|
||||
|
||||
Run: `cat skills/writing-plans/plan-document-reviewer-prompt.md | head -20`
|
||||
Expected: Shows the header and purpose section
|
||||
|
||||
- [ ] **Step 3:** Commit
|
||||
|
||||
```bash
|
||||
git add skills/writing-plans/plan-document-reviewer-prompt.md
|
||||
git commit -m "feat: add plan document reviewer prompt template"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Add Review Loop to Writing-Plans Skill
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/writing-plans/SKILL.md`
|
||||
|
||||
- [ ] **Step 1:** Read current skill file
|
||||
|
||||
Run: `cat skills/writing-plans/SKILL.md`
|
||||
|
||||
- [ ] **Step 2:** Add chunk-by-chunk review section
|
||||
|
||||
Add before the "Execution Handoff" section:
|
||||
|
||||
```markdown
|
||||
## Plan Review Loop
|
||||
|
||||
After completing each chunk of the plan:
|
||||
|
||||
1. Dispatch plan-document-reviewer subagent for the current chunk
|
||||
- Provide: chunk content, path to spec document
|
||||
2. If ❌ Issues Found:
|
||||
- Fix the issues in the chunk
|
||||
- Re-dispatch reviewer for that chunk
|
||||
- Repeat until ✅ Approved
|
||||
3. If ✅ Approved: proceed to next chunk (or execution handoff if last chunk)
|
||||
|
||||
**Chunk boundaries:** Use `## Chunk N: <name>` headings to delimit chunks. Each chunk should be ≤1000 lines and logically self-contained.
|
||||
```
|
||||
|
||||
- [ ] **Step 3:** Update task syntax examples to use checkboxes
|
||||
|
||||
Change the Task Structure section to show checkbox syntax:
|
||||
|
||||
```markdown
|
||||
### Task N: [Component Name]
|
||||
|
||||
- [ ] **Step 1:** Write the failing test
|
||||
- File: `tests/path/test.py`
|
||||
...
|
||||
```
|
||||
|
||||
- [ ] **Step 4:** Verify the review loop section was added
|
||||
|
||||
Run: `grep -A 15 "Plan Review Loop" skills/writing-plans/SKILL.md`
|
||||
Expected: Shows the new review loop section
|
||||
|
||||
- [ ] **Step 5:** Verify the task syntax examples were updated
|
||||
|
||||
Run: `grep -A 5 "Task N:" skills/writing-plans/SKILL.md`
|
||||
Expected: Shows checkbox syntax `### Task N:`
|
||||
|
||||
- [ ] **Step 6:** Commit
|
||||
|
||||
```bash
|
||||
git add skills/writing-plans/SKILL.md
|
||||
git commit -m "feat: add plan review loop and checkbox syntax to writing-plans skill"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunk 3: Update Plan Document Header
|
||||
|
||||
This chunk updates the plan document header template to reference the new checkbox syntax requirements.
|
||||
|
||||
### Task 5: Update Plan Header Template in Writing-Plans Skill
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/writing-plans/SKILL.md`
|
||||
|
||||
- [ ] **Step 1:** Read current plan header template
|
||||
|
||||
Run: `grep -A 20 "Plan Document Header" skills/writing-plans/SKILL.md`
|
||||
|
||||
- [ ] **Step 2:** Update the header template to reference checkbox syntax
|
||||
|
||||
The plan header should note that tasks and steps use checkbox syntax. Update the header comment:
|
||||
|
||||
```markdown
|
||||
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Tasks and steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
```
|
||||
|
||||
- [ ] **Step 3:** Verify the change
|
||||
|
||||
Run: `grep -A 5 "For agentic workers:" skills/writing-plans/SKILL.md`
|
||||
Expected: Shows updated header with checkbox syntax mention
|
||||
|
||||
- [ ] **Step 4:** Commit
|
||||
|
||||
```bash
|
||||
git add skills/writing-plans/SKILL.md
|
||||
git commit -m "docs: update plan header to reference checkbox syntax"
|
||||
```
|
||||
@@ -0,0 +1,523 @@
|
||||
# Visual Brainstorming Refactor Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Refactor visual brainstorming from blocking TUI feedback model to non-blocking "Browser Displays, Terminal Commands" architecture.
|
||||
|
||||
**Architecture:** Browser becomes an interactive display; terminal stays the conversation channel. Server writes user events to a per-screen `.events` file that Claude reads on its next turn. Eliminates `wait-for-feedback.sh` and all `TaskOutput` blocking.
|
||||
|
||||
**Tech Stack:** Node.js (Express, ws, chokidar), vanilla HTML/CSS/JS
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
| File | Action | Responsibility |
|
||||
|------|--------|---------------|
|
||||
| `lib/brainstorm-server/index.js` | Modify | Server: add `.events` file writing, clear on new screen, replace `wrapInFrame` |
|
||||
| `lib/brainstorm-server/frame-template.html` | Modify | Template: remove feedback footer, add content placeholder + selection indicator |
|
||||
| `lib/brainstorm-server/helper.js` | Modify | Client JS: remove send/feedback functions, narrow to click capture + indicator updates |
|
||||
| `lib/brainstorm-server/wait-for-feedback.sh` | Delete | No longer needed |
|
||||
| `skills/brainstorming/visual-companion.md` | Modify | Skill instructions: rewrite loop to non-blocking flow |
|
||||
| `tests/brainstorm-server/server.test.js` | Modify | Tests: update for new template structure and helper.js API |
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1: Server, Template, Client, Tests, Skill
|
||||
|
||||
### Task 1: Update `frame-template.html`
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/brainstorm-server/frame-template.html`
|
||||
|
||||
- [ ] **Step 1: Remove the feedback footer HTML**
|
||||
|
||||
Replace the feedback-footer div (lines 227-233) with a selection indicator bar:
|
||||
|
||||
```html
|
||||
<div class="indicator-bar">
|
||||
<span id="indicator-text">Click an option above, then return to the terminal</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
Also replace the default content inside `#claude-content` (lines 220-223) with the content placeholder:
|
||||
|
||||
```html
|
||||
<div id="claude-content">
|
||||
<!-- CONTENT -->
|
||||
</div>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace feedback footer CSS with indicator bar CSS**
|
||||
|
||||
Remove the `.feedback-footer`, `.feedback-footer label`, `.feedback-row`, and the textarea/button styles within `.feedback-footer` (lines 82-112).
|
||||
|
||||
Add indicator bar CSS:
|
||||
|
||||
```css
|
||||
.indicator-bar {
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 0.5rem 1.5rem;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.indicator-bar span {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.indicator-bar .selected-text {
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify template renders**
|
||||
|
||||
Run the test suite to check the template still loads:
|
||||
```bash
|
||||
cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js
|
||||
```
|
||||
Expected: Tests 1-5 should still pass. Tests 6-8 may fail (expected — they assert old structure).
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/brainstorm-server/frame-template.html
|
||||
git commit -m "Replace feedback footer with selection indicator bar in brainstorm template"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Update `index.js` — content injection and `.events` file
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/brainstorm-server/index.js`
|
||||
|
||||
- [ ] **Step 1: Write failing test for `.events` file writing**
|
||||
|
||||
Add to `tests/brainstorm-server/server.test.js` after Test 4 area — a new test that sends a WebSocket event with a `choice` field and verifies `.events` file is written:
|
||||
|
||||
```javascript
|
||||
// Test: Choice events written to .events file
|
||||
console.log('Test: Choice events written to .events file');
|
||||
const ws3 = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws3.on('open', resolve));
|
||||
|
||||
ws3.send(JSON.stringify({ type: 'click', choice: 'a', text: 'Option A' }));
|
||||
await sleep(300);
|
||||
|
||||
const eventsFile = path.join(TEST_DIR, '.events');
|
||||
assert(fs.existsSync(eventsFile), '.events file should exist after choice click');
|
||||
const lines = fs.readFileSync(eventsFile, 'utf-8').trim().split('\n');
|
||||
const event = JSON.parse(lines[lines.length - 1]);
|
||||
assert.strictEqual(event.choice, 'a', 'Event should contain choice');
|
||||
assert.strictEqual(event.text, 'Option A', 'Event should contain text');
|
||||
ws3.close();
|
||||
console.log(' PASS');
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
```bash
|
||||
cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js
|
||||
```
|
||||
Expected: New test FAILS — `.events` file doesn't exist yet.
|
||||
|
||||
- [ ] **Step 3: Write failing test for `.events` file clearing on new screen**
|
||||
|
||||
Add another test:
|
||||
|
||||
```javascript
|
||||
// Test: .events cleared on new screen
|
||||
console.log('Test: .events cleared on new screen');
|
||||
// .events file should still exist from previous test
|
||||
assert(fs.existsSync(path.join(TEST_DIR, '.events')), '.events should exist before new screen');
|
||||
fs.writeFileSync(path.join(TEST_DIR, 'new-screen.html'), '<h2>New screen</h2>');
|
||||
await sleep(500);
|
||||
assert(!fs.existsSync(path.join(TEST_DIR, '.events')), '.events should be cleared after new screen');
|
||||
console.log(' PASS');
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify it fails**
|
||||
|
||||
```bash
|
||||
cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js
|
||||
```
|
||||
Expected: New test FAILS — `.events` not cleared on screen push.
|
||||
|
||||
- [ ] **Step 5: Implement `.events` file writing in `index.js`**
|
||||
|
||||
In the WebSocket `message` handler (line 74-77 of `index.js`), after the `console.log`, add:
|
||||
|
||||
```javascript
|
||||
// Write user events to .events file for Claude to read
|
||||
if (event.choice) {
|
||||
const eventsFile = path.join(SCREEN_DIR, '.events');
|
||||
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
|
||||
}
|
||||
```
|
||||
|
||||
In the chokidar `add` handler (line 104-111), add `.events` clearing:
|
||||
|
||||
```javascript
|
||||
if (filePath.endsWith('.html')) {
|
||||
// Clear events from previous screen
|
||||
const eventsFile = path.join(SCREEN_DIR, '.events');
|
||||
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
|
||||
|
||||
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
|
||||
// ... existing reload broadcast
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Replace `wrapInFrame` with comment placeholder injection**
|
||||
|
||||
Replace the `wrapInFrame` function (lines 27-32 of `index.js`):
|
||||
|
||||
```javascript
|
||||
function wrapInFrame(content) {
|
||||
return frameTemplate.replace('<!-- CONTENT -->', content);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Run all tests**
|
||||
|
||||
```bash
|
||||
cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js
|
||||
```
|
||||
Expected: New `.events` tests PASS. Existing tests may still have failures from old assertions (fixed in Task 4).
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/brainstorm-server/index.js tests/brainstorm-server/server.test.js
|
||||
git commit -m "Add .events file writing and comment-based content injection to brainstorm server"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Simplify `helper.js`
|
||||
|
||||
**Files:**
|
||||
- Modify: `lib/brainstorm-server/helper.js`
|
||||
|
||||
- [ ] **Step 1: Remove `sendToClaude` function**
|
||||
|
||||
Delete the `sendToClaude` function (lines 92-106) — the function body and the page takeover HTML.
|
||||
|
||||
- [ ] **Step 2: Remove `window.send` function**
|
||||
|
||||
Delete the `window.send` function (lines 120-129) — was tied to the removed Send button.
|
||||
|
||||
- [ ] **Step 3: Remove form submission and input change handlers**
|
||||
|
||||
Delete the form submission handler (lines 57-71) and the input change handler (lines 73-89) including the `inputTimeout` variable.
|
||||
|
||||
- [ ] **Step 4: Remove `pageshow` event listener**
|
||||
|
||||
Delete the `pageshow` listener we added earlier (no textarea to clear anymore).
|
||||
|
||||
- [ ] **Step 5: Narrow click handler to `[data-choice]` only**
|
||||
|
||||
Replace the click handler (lines 36-55) with a narrower version:
|
||||
|
||||
```javascript
|
||||
// Capture clicks on choice elements
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-choice]');
|
||||
if (!target) return;
|
||||
|
||||
sendEvent({
|
||||
type: 'click',
|
||||
text: target.textContent.trim(),
|
||||
choice: target.dataset.choice,
|
||||
id: target.id || null
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Add indicator bar update on choice click**
|
||||
|
||||
After the `sendEvent` call in the click handler, add:
|
||||
|
||||
```javascript
|
||||
// Update indicator bar
|
||||
const indicator = document.getElementById('indicator-text');
|
||||
if (indicator) {
|
||||
const label = target.querySelector('h3, .content h3, .card-body h3')?.textContent?.trim() || target.dataset.choice;
|
||||
indicator.innerHTML = '<span class="selected-text">' + label + ' selected</span> — return to terminal to continue';
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Remove `sendToClaude` from `window.brainstorm` API**
|
||||
|
||||
Update the `window.brainstorm` object (lines 132-136) to remove `sendToClaude`:
|
||||
|
||||
```javascript
|
||||
window.brainstorm = {
|
||||
send: sendEvent,
|
||||
choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata })
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Run tests**
|
||||
|
||||
```bash
|
||||
cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js
|
||||
```
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/brainstorm-server/helper.js
|
||||
git commit -m "Simplify helper.js: remove feedback functions, narrow to choice capture + indicator"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Update tests for new structure
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/brainstorm-server/server.test.js`
|
||||
|
||||
**Note:** Line references below are from the _original_ file. Task 2 inserted new tests earlier in the file, so actual line numbers will be shifted. Find tests by their `console.log` labels (e.g., "Test 5:", "Test 6:").
|
||||
|
||||
- [ ] **Step 1: Update Test 5 (full document assertion)**
|
||||
|
||||
Find the Test 5 assertion `!fullRes.body.includes('feedback-footer')`. Change it to: Full documents should NOT have the indicator bar either (they're served as-is):
|
||||
|
||||
```javascript
|
||||
assert(!fullRes.body.includes('indicator-bar') || fullDoc.includes('indicator-bar'),
|
||||
'Should not wrap full documents in frame template');
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update Test 6 (fragment wrapping)**
|
||||
|
||||
Line 125: Replace `feedback-footer` assertion with indicator bar assertion:
|
||||
|
||||
```javascript
|
||||
assert(fragRes.body.includes('indicator-bar'), 'Fragment should get indicator bar from frame');
|
||||
```
|
||||
|
||||
Also verify content placeholder was replaced (fragment content appears, placeholder comment doesn't):
|
||||
|
||||
```javascript
|
||||
assert(!fragRes.body.includes('<!-- CONTENT -->'), 'Content placeholder should be replaced');
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Update Test 7 (helper.js API)**
|
||||
|
||||
Lines 140-142: Update assertions to reflect the new API surface:
|
||||
|
||||
```javascript
|
||||
assert(helperContent.includes('toggleSelect'), 'helper.js should define toggleSelect');
|
||||
assert(helperContent.includes('sendEvent'), 'helper.js should define sendEvent');
|
||||
assert(helperContent.includes('selectedChoice'), 'helper.js should track selectedChoice');
|
||||
assert(helperContent.includes('brainstorm'), 'helper.js should expose brainstorm API');
|
||||
assert(!helperContent.includes('sendToClaude'), 'helper.js should not contain sendToClaude');
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Replace Test 8 (sendToClaude theming) with indicator bar test**
|
||||
|
||||
Replace Test 8 (lines 145-149) — `sendToClaude` no longer exists. Test the indicator bar instead:
|
||||
|
||||
```javascript
|
||||
// Test 8: Indicator bar uses CSS variables (theme support)
|
||||
console.log('Test 8: Indicator bar uses CSS variables');
|
||||
const templateContent = fs.readFileSync(
|
||||
path.join(__dirname, '../../lib/brainstorm-server/frame-template.html'), 'utf-8'
|
||||
);
|
||||
assert(templateContent.includes('indicator-bar'), 'Template should have indicator bar');
|
||||
assert(templateContent.includes('indicator-text'), 'Template should have indicator text element');
|
||||
console.log(' PASS');
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Run full test suite**
|
||||
|
||||
```bash
|
||||
cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js
|
||||
```
|
||||
Expected: ALL tests PASS.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/brainstorm-server/server.test.js
|
||||
git commit -m "Update brainstorm server tests for new template structure and helper.js API"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Delete `wait-for-feedback.sh`
|
||||
|
||||
**Files:**
|
||||
- Delete: `lib/brainstorm-server/wait-for-feedback.sh`
|
||||
|
||||
- [ ] **Step 1: Verify no other files import or reference `wait-for-feedback.sh`**
|
||||
|
||||
Search the codebase:
|
||||
```bash
|
||||
grep -r "wait-for-feedback" /Users/drewritter/prime-rad/superpowers/ --include="*.js" --include="*.md" --include="*.sh" --include="*.json"
|
||||
```
|
||||
|
||||
Expected references: only `visual-companion.md` (rewritten in Task 6) and possibly release notes (historical, leave as-is).
|
||||
|
||||
- [ ] **Step 2: Delete the file**
|
||||
|
||||
```bash
|
||||
rm lib/brainstorm-server/wait-for-feedback.sh
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run tests to confirm nothing breaks**
|
||||
|
||||
```bash
|
||||
cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js
|
||||
```
|
||||
Expected: All tests PASS (no test referenced this file).
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add -u lib/brainstorm-server/wait-for-feedback.sh
|
||||
git commit -m "Delete wait-for-feedback.sh: replaced by .events file"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Rewrite `visual-companion.md`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/brainstorming/visual-companion.md`
|
||||
|
||||
- [ ] **Step 1: Update "How It Works" description (line 18)**
|
||||
|
||||
Replace the sentence about receiving feedback "as JSON" with:
|
||||
|
||||
```markdown
|
||||
The server watches a directory for HTML files and serves the newest one to the browser. You write HTML content, the user sees it in their browser and can click to select options. Selections are recorded to a `.events` file that you read on your next turn.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update fragment description (line 20)**
|
||||
|
||||
Remove "feedback footer" from the description of what the frame template provides:
|
||||
|
||||
```markdown
|
||||
**Content fragments vs full documents:** If your HTML file starts with `<!DOCTYPE` or `<html`, the server serves it as-is (just injects the helper script). Otherwise, the server automatically wraps your content in the frame template — adding the header, CSS theme, selection indicator, and all interactive infrastructure. **Write content fragments by default.** Only write full documents when you need complete control over the page.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Rewrite "The Loop" section (lines 36-61)**
|
||||
|
||||
Replace the entire "The Loop" section with:
|
||||
|
||||
```markdown
|
||||
## The Loop
|
||||
|
||||
1. **Write HTML** to a new file in `screen_dir`:
|
||||
- Use semantic filenames: `platform.html`, `visual-style.html`, `layout.html`
|
||||
- **Never reuse filenames** — each screen gets a fresh file
|
||||
- Use Write tool — **never use cat/heredoc** (dumps noise into terminal)
|
||||
- Server automatically serves the newest file
|
||||
|
||||
2. **Tell user what to expect and end your turn:**
|
||||
- Remind them of the URL (every step, not just first)
|
||||
- Give a brief text summary of what's on screen (e.g., "Showing 3 layout options for the homepage")
|
||||
- Ask them to respond in the terminal: "Take a look and let me know what you think. Click to select an option if you'd like."
|
||||
|
||||
3. **On your next turn** — after the user responds in the terminal:
|
||||
- Read `$SCREEN_DIR/.events` if it exists — this contains the user's browser interactions (clicks, selections) as JSON lines
|
||||
- Merge with the user's terminal text to get the full picture
|
||||
- The terminal message is the primary feedback; `.events` provides structured interaction data
|
||||
|
||||
4. **Iterate or advance** — if feedback changes current screen, write a new file (e.g., `layout-v2.html`). Only move to the next question when the current step is validated.
|
||||
|
||||
5. Repeat until done.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Replace "User Feedback Format" section (lines 165-174)**
|
||||
|
||||
Replace with:
|
||||
|
||||
```markdown
|
||||
## Browser Events Format
|
||||
|
||||
When the user clicks options in the browser, their interactions are recorded to `$SCREEN_DIR/.events` (one JSON object per line). The file is cleared automatically when you push a new screen.
|
||||
|
||||
```jsonl
|
||||
{"type":"click","choice":"a","text":"Option A - Simple Layout","timestamp":1706000101}
|
||||
{"type":"click","choice":"c","text":"Option C - Complex Grid","timestamp":1706000108}
|
||||
{"type":"click","choice":"b","text":"Option B - Hybrid","timestamp":1706000115}
|
||||
```
|
||||
|
||||
The full event stream shows the user's exploration path — they may click multiple options before settling. The last `choice` event is typically the final selection, but the pattern of clicks can reveal hesitation or preferences worth asking about.
|
||||
|
||||
If `.events` doesn't exist, the user didn't interact with the browser — use only their terminal text.
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Update "Writing Content Fragments" description (line 65)**
|
||||
|
||||
Remove "feedback footer" reference:
|
||||
|
||||
```markdown
|
||||
Write just the content that goes inside the page. The server wraps it in the frame template automatically (header, theme CSS, selection indicator, and all interactive infrastructure).
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Update Reference section (lines 200-203)**
|
||||
|
||||
Remove the helper.js reference description about "JS API" — the API is now minimal. Keep the path reference:
|
||||
|
||||
```markdown
|
||||
## Reference
|
||||
|
||||
- Frame template (CSS reference): `${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/frame-template.html`
|
||||
- Helper script (client-side): `${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/helper.js`
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/brainstorming/visual-companion.md
|
||||
git commit -m "Rewrite visual-companion.md for non-blocking browser-displays-terminal-commands flow"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Final verification
|
||||
|
||||
- [ ] **Step 1: Run full test suite**
|
||||
|
||||
```bash
|
||||
cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js
|
||||
```
|
||||
Expected: ALL tests PASS.
|
||||
|
||||
- [ ] **Step 2: Manual smoke test**
|
||||
|
||||
Start the server manually and verify the flow works end-to-end:
|
||||
|
||||
```bash
|
||||
cd /Users/drewritter/prime-rad/superpowers && lib/brainstorm-server/start-server.sh --project-dir /tmp/brainstorm-smoke-test
|
||||
```
|
||||
|
||||
Write a test fragment, open in browser, click an option, verify `.events` file is written, verify indicator bar updates. Then stop the server:
|
||||
|
||||
```bash
|
||||
lib/brainstorm-server/stop-server.sh <screen_dir from start output>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify no stale references remain**
|
||||
|
||||
```bash
|
||||
grep -r "wait-for-feedback\|sendToClaude\|feedback-footer\|send-to-claude\|TaskOutput.*block.*true" /Users/drewritter/prime-rad/superpowers/ --include="*.js" --include="*.md" --include="*.sh" --include="*.html" | grep -v node_modules | grep -v RELEASE-NOTES | grep -v "\.md:.*spec\|plan"
|
||||
```
|
||||
|
||||
Expected: No hits outside of release notes and the spec/plan docs (which are historical).
|
||||
|
||||
- [ ] **Step 4: Final commit if any cleanup needed**
|
||||
|
||||
```bash
|
||||
git status
|
||||
# Review untracked/modified files, stage specific files as needed, commit if clean
|
||||
```
|
||||
479
docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md
Normal file
479
docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md
Normal file
@@ -0,0 +1,479 @@
|
||||
# Zero-Dependency Brainstorm Server Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Replace the brainstorm server's vendored node_modules with a single zero-dependency `server.js` using Node built-ins.
|
||||
|
||||
**Architecture:** Single file with WebSocket protocol (RFC 6455 text frames), HTTP server (`http` module), and file watching (`fs.watch`). Exports protocol functions for unit testing when required as a module.
|
||||
|
||||
**Tech Stack:** Node.js built-ins only: `http`, `crypto`, `fs`, `path`
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-03-11-zero-dep-brainstorm-server-design.md`
|
||||
|
||||
**Existing tests:** `tests/brainstorm-server/ws-protocol.test.js` (unit), `tests/brainstorm-server/server.test.js` (integration)
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
- **Create:** `skills/brainstorming/scripts/server.js` — the zero-dep replacement
|
||||
- **Modify:** `skills/brainstorming/scripts/start-server.sh:94,100` — change `index.js` to `server.js`
|
||||
- **Modify:** `.gitignore:6` — remove the `!skills/brainstorming/scripts/node_modules/` exception
|
||||
- **Delete:** `skills/brainstorming/scripts/index.js`
|
||||
- **Delete:** `skills/brainstorming/scripts/package.json`
|
||||
- **Delete:** `skills/brainstorming/scripts/package-lock.json`
|
||||
- **Delete:** `skills/brainstorming/scripts/node_modules/` (714 files)
|
||||
- **No changes:** `skills/brainstorming/scripts/helper.js`, `skills/brainstorming/scripts/frame-template.html`, `skills/brainstorming/scripts/stop-server.sh`
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1: WebSocket Protocol Layer
|
||||
|
||||
### Task 1: Implement WebSocket protocol exports
|
||||
|
||||
**Files:**
|
||||
- Create: `skills/brainstorming/scripts/server.js`
|
||||
- Test: `tests/brainstorm-server/ws-protocol.test.js` (already exists)
|
||||
|
||||
- [ ] **Step 1: Create server.js with OPCODES constant and computeAcceptKey**
|
||||
|
||||
```js
|
||||
const crypto = require('crypto');
|
||||
|
||||
const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A };
|
||||
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
|
||||
function computeAcceptKey(clientKey) {
|
||||
return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64');
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Implement encodeFrame**
|
||||
|
||||
Server frames are never masked. Three length encodings:
|
||||
- payload < 126: 2-byte header (FIN+opcode, length)
|
||||
- 126-65535: 4-byte header (FIN+opcode, 126, 16-bit length)
|
||||
- > 65535: 10-byte header (FIN+opcode, 127, 64-bit length)
|
||||
|
||||
```js
|
||||
function encodeFrame(opcode, payload) {
|
||||
const fin = 0x80;
|
||||
const len = payload.length;
|
||||
let header;
|
||||
|
||||
if (len < 126) {
|
||||
header = Buffer.alloc(2);
|
||||
header[0] = fin | opcode;
|
||||
header[1] = len;
|
||||
} else if (len < 65536) {
|
||||
header = Buffer.alloc(4);
|
||||
header[0] = fin | opcode;
|
||||
header[1] = 126;
|
||||
header.writeUInt16BE(len, 2);
|
||||
} else {
|
||||
header = Buffer.alloc(10);
|
||||
header[0] = fin | opcode;
|
||||
header[1] = 127;
|
||||
header.writeBigUInt64BE(BigInt(len), 2);
|
||||
}
|
||||
|
||||
return Buffer.concat([header, payload]);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Implement decodeFrame**
|
||||
|
||||
Client frames are always masked. Returns `{ opcode, payload, bytesConsumed }` or `null` for incomplete. Throws on unmasked frames.
|
||||
|
||||
```js
|
||||
function decodeFrame(buffer) {
|
||||
if (buffer.length < 2) return null;
|
||||
|
||||
const firstByte = buffer[0];
|
||||
const secondByte = buffer[1];
|
||||
const opcode = firstByte & 0x0F;
|
||||
const masked = (secondByte & 0x80) !== 0;
|
||||
let payloadLen = secondByte & 0x7F;
|
||||
let offset = 2;
|
||||
|
||||
if (!masked) throw new Error('Client frames must be masked');
|
||||
|
||||
if (payloadLen === 126) {
|
||||
if (buffer.length < 4) return null;
|
||||
payloadLen = buffer.readUInt16BE(2);
|
||||
offset = 4;
|
||||
} else if (payloadLen === 127) {
|
||||
if (buffer.length < 10) return null;
|
||||
payloadLen = Number(buffer.readBigUInt64BE(2));
|
||||
offset = 10;
|
||||
}
|
||||
|
||||
const maskOffset = offset;
|
||||
const dataOffset = offset + 4;
|
||||
const totalLen = dataOffset + payloadLen;
|
||||
if (buffer.length < totalLen) return null;
|
||||
|
||||
const mask = buffer.slice(maskOffset, dataOffset);
|
||||
const data = Buffer.alloc(payloadLen);
|
||||
for (let i = 0; i < payloadLen; i++) {
|
||||
data[i] = buffer[dataOffset + i] ^ mask[i % 4];
|
||||
}
|
||||
|
||||
return { opcode, payload: data, bytesConsumed: totalLen };
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add module exports at the bottom of the file**
|
||||
|
||||
```js
|
||||
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES };
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Run unit tests**
|
||||
|
||||
Run: `cd tests/brainstorm-server && node ws-protocol.test.js`
|
||||
Expected: All tests pass (handshake, encoding, decoding, boundaries, edge cases)
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/brainstorming/scripts/server.js
|
||||
git commit -m "Add WebSocket protocol layer for zero-dep brainstorm server"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2: HTTP Server and Application Logic
|
||||
|
||||
### Task 2: Add HTTP server, file watching, and WebSocket connection handling
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/brainstorming/scripts/server.js`
|
||||
- Test: `tests/brainstorm-server/server.test.js` (already exists)
|
||||
|
||||
- [ ] **Step 1: Add configuration and constants at top of server.js (after requires)**
|
||||
|
||||
```js
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383));
|
||||
const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1';
|
||||
const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST);
|
||||
const SCREEN_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
|
||||
|
||||
const MIME_TYPES = {
|
||||
'.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
|
||||
'.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml'
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add WAITING_PAGE, template loading at module scope, and helper functions**
|
||||
|
||||
Load `frameTemplate` and `helperInjection` at module scope so they're accessible to `wrapInFrame` and `handleRequest`. They only read files from `__dirname` (the scripts directory), which is valid whether the module is required or run directly.
|
||||
|
||||
```js
|
||||
const WAITING_PAGE = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Brainstorm Companion</title>
|
||||
<style>body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
||||
h1 { color: #333; } p { color: #666; }</style>
|
||||
</head>
|
||||
<body><h1>Brainstorm Companion</h1>
|
||||
<p>Waiting for Claude to push a screen...</p></body></html>`;
|
||||
|
||||
const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8');
|
||||
const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8');
|
||||
const helperInjection = '<script>\n' + helperScript + '\n</script>';
|
||||
|
||||
function isFullDocument(html) {
|
||||
const trimmed = html.trimStart().toLowerCase();
|
||||
return trimmed.startsWith('<!doctype') || trimmed.startsWith('<html');
|
||||
}
|
||||
|
||||
function wrapInFrame(content) {
|
||||
return frameTemplate.replace('<!-- CONTENT -->', content);
|
||||
}
|
||||
|
||||
function getNewestScreen() {
|
||||
const files = fs.readdirSync(SCREEN_DIR)
|
||||
.filter(f => f.endsWith('.html'))
|
||||
.map(f => {
|
||||
const fp = path.join(SCREEN_DIR, f);
|
||||
return { path: fp, mtime: fs.statSync(fp).mtime.getTime() };
|
||||
})
|
||||
.sort((a, b) => b.mtime - a.mtime);
|
||||
return files.length > 0 ? files[0].path : null;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add HTTP request handler**
|
||||
|
||||
```js
|
||||
function handleRequest(req, res) {
|
||||
if (req.method === 'GET' && req.url === '/') {
|
||||
const screenFile = getNewestScreen();
|
||||
let html = screenFile
|
||||
? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8'))
|
||||
: WAITING_PAGE;
|
||||
|
||||
if (html.includes('</body>')) {
|
||||
html = html.replace('</body>', helperInjection + '\n</body>');
|
||||
} else {
|
||||
html += helperInjection;
|
||||
}
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end(html);
|
||||
} else if (req.method === 'GET' && req.url.startsWith('/files/')) {
|
||||
const fileName = req.url.slice(7); // strip '/files/'
|
||||
const filePath = path.join(SCREEN_DIR, path.basename(fileName));
|
||||
if (!fs.existsSync(filePath)) {
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
return;
|
||||
}
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
||||
res.writeHead(200, { 'Content-Type': contentType });
|
||||
res.end(fs.readFileSync(filePath));
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add WebSocket connection handling**
|
||||
|
||||
```js
|
||||
const clients = new Set();
|
||||
|
||||
function handleUpgrade(req, socket) {
|
||||
const key = req.headers['sec-websocket-key'];
|
||||
if (!key) { socket.destroy(); return; }
|
||||
|
||||
const accept = computeAcceptKey(key);
|
||||
socket.write(
|
||||
'HTTP/1.1 101 Switching Protocols\r\n' +
|
||||
'Upgrade: websocket\r\n' +
|
||||
'Connection: Upgrade\r\n' +
|
||||
'Sec-WebSocket-Accept: ' + accept + '\r\n\r\n'
|
||||
);
|
||||
|
||||
let buffer = Buffer.alloc(0);
|
||||
clients.add(socket);
|
||||
|
||||
socket.on('data', (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
while (buffer.length > 0) {
|
||||
let result;
|
||||
try {
|
||||
result = decodeFrame(buffer);
|
||||
} catch (e) {
|
||||
socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0)));
|
||||
clients.delete(socket);
|
||||
return;
|
||||
}
|
||||
if (!result) break;
|
||||
buffer = buffer.slice(result.bytesConsumed);
|
||||
|
||||
switch (result.opcode) {
|
||||
case OPCODES.TEXT:
|
||||
handleMessage(result.payload.toString());
|
||||
break;
|
||||
case OPCODES.CLOSE:
|
||||
socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0)));
|
||||
clients.delete(socket);
|
||||
return;
|
||||
case OPCODES.PING:
|
||||
socket.write(encodeFrame(OPCODES.PONG, result.payload));
|
||||
break;
|
||||
case OPCODES.PONG:
|
||||
break;
|
||||
default:
|
||||
// Unsupported opcode — close with 1003
|
||||
const closeBuf = Buffer.alloc(2);
|
||||
closeBuf.writeUInt16BE(1003);
|
||||
socket.end(encodeFrame(OPCODES.CLOSE, closeBuf));
|
||||
clients.delete(socket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('close', () => clients.delete(socket));
|
||||
socket.on('error', () => clients.delete(socket));
|
||||
}
|
||||
|
||||
function handleMessage(text) {
|
||||
let event;
|
||||
try {
|
||||
event = JSON.parse(text);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse WebSocket message:', e.message);
|
||||
return;
|
||||
}
|
||||
console.log(JSON.stringify({ source: 'user-event', ...event }));
|
||||
if (event.choice) {
|
||||
const eventsFile = path.join(SCREEN_DIR, '.events');
|
||||
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
function broadcast(msg) {
|
||||
const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg)));
|
||||
for (const socket of clients) {
|
||||
try { socket.write(frame); } catch (e) { clients.delete(socket); }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Add debounce timer map**
|
||||
|
||||
```js
|
||||
const debounceTimers = new Map();
|
||||
```
|
||||
|
||||
File watching logic is inlined in `startServer` (Step 6) to keep watcher lifecycle together with server lifecycle and include an `error` handler per spec.
|
||||
|
||||
- [ ] **Step 6: Add startServer function and conditional main**
|
||||
|
||||
`frameTemplate` and `helperInjection` are already at module scope (Step 2). `startServer` just creates the screen dir, starts the HTTP server, watcher, and logs startup info.
|
||||
|
||||
```js
|
||||
function startServer() {
|
||||
if (!fs.existsSync(SCREEN_DIR)) fs.mkdirSync(SCREEN_DIR, { recursive: true });
|
||||
|
||||
const server = http.createServer(handleRequest);
|
||||
server.on('upgrade', handleUpgrade);
|
||||
|
||||
const watcher = fs.watch(SCREEN_DIR, (eventType, filename) => {
|
||||
if (!filename || !filename.endsWith('.html')) return;
|
||||
if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename));
|
||||
debounceTimers.set(filename, setTimeout(() => {
|
||||
debounceTimers.delete(filename);
|
||||
const filePath = path.join(SCREEN_DIR, filename);
|
||||
if (eventType === 'rename' && fs.existsSync(filePath)) {
|
||||
const eventsFile = path.join(SCREEN_DIR, '.events');
|
||||
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
|
||||
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
|
||||
} else if (eventType === 'change') {
|
||||
console.log(JSON.stringify({ type: 'screen-updated', file: filePath }));
|
||||
}
|
||||
broadcast({ type: 'reload' });
|
||||
}, 100));
|
||||
});
|
||||
watcher.on('error', (err) => console.error('fs.watch error:', err.message));
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
const info = JSON.stringify({
|
||||
type: 'server-started', port: Number(PORT), host: HOST,
|
||||
url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT,
|
||||
screen_dir: SCREEN_DIR
|
||||
});
|
||||
console.log(info);
|
||||
fs.writeFileSync(path.join(SCREEN_DIR, '.server-info'), info + '\n');
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
startServer();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Run integration tests**
|
||||
|
||||
The test directory already has a `package.json` with `ws` as a dependency. Install it if needed, then run tests.
|
||||
|
||||
Run: `cd tests/brainstorm-server && npm install && node server.test.js`
|
||||
Expected: All tests pass
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/brainstorming/scripts/server.js
|
||||
git commit -m "Add HTTP server, WebSocket handling, and file watching to server.js"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunk 3: Swap and Cleanup
|
||||
|
||||
### Task 3: Update start-server.sh and remove old files
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/brainstorming/scripts/start-server.sh:94,100`
|
||||
- Modify: `.gitignore:6`
|
||||
- Delete: `skills/brainstorming/scripts/index.js`
|
||||
- Delete: `skills/brainstorming/scripts/package.json`
|
||||
- Delete: `skills/brainstorming/scripts/package-lock.json`
|
||||
- Delete: `skills/brainstorming/scripts/node_modules/` (entire directory)
|
||||
|
||||
- [ ] **Step 1: Update start-server.sh — change `index.js` to `server.js`**
|
||||
|
||||
Two lines to change:
|
||||
|
||||
Line 94: `env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" node server.js`
|
||||
|
||||
Line 100: `nohup env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" node server.js > "$LOG_FILE" 2>&1 &`
|
||||
|
||||
- [ ] **Step 2: Remove the gitignore exception for node_modules**
|
||||
|
||||
In `.gitignore`, delete line 6: `!skills/brainstorming/scripts/node_modules/`
|
||||
|
||||
- [ ] **Step 3: Delete old files**
|
||||
|
||||
```bash
|
||||
git rm skills/brainstorming/scripts/index.js
|
||||
git rm skills/brainstorming/scripts/package.json
|
||||
git rm skills/brainstorming/scripts/package-lock.json
|
||||
git rm -r skills/brainstorming/scripts/node_modules/
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run both test suites**
|
||||
|
||||
Run: `cd tests/brainstorm-server && node ws-protocol.test.js && node server.test.js`
|
||||
Expected: All tests pass
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/brainstorming/scripts/ .gitignore
|
||||
git commit -m "Remove vendored node_modules, swap to zero-dep server.js"
|
||||
```
|
||||
|
||||
### Task 4: Manual smoke test
|
||||
|
||||
- [ ] **Step 1: Start the server manually**
|
||||
|
||||
```bash
|
||||
cd skills/brainstorming/scripts
|
||||
BRAINSTORM_DIR=/tmp/brainstorm-smoke BRAINSTORM_PORT=9876 node server.js
|
||||
```
|
||||
|
||||
Expected: `server-started` JSON printed with port 9876
|
||||
|
||||
- [ ] **Step 2: Open browser to http://localhost:9876**
|
||||
|
||||
Expected: Waiting page with "Waiting for Claude to push a screen..."
|
||||
|
||||
- [ ] **Step 3: Write an HTML file to the screen directory**
|
||||
|
||||
```bash
|
||||
echo '<h2>Hello from smoke test</h2>' > /tmp/brainstorm-smoke/test.html
|
||||
```
|
||||
|
||||
Expected: Browser reloads and shows "Hello from smoke test" wrapped in frame template
|
||||
|
||||
- [ ] **Step 4: Verify WebSocket works — check browser console**
|
||||
|
||||
Open browser dev tools. The WebSocket connection should show as connected (no errors in console). The frame template's status indicator should show "Connected".
|
||||
|
||||
- [ ] **Step 5: Stop server with Ctrl-C, clean up**
|
||||
|
||||
```bash
|
||||
rm -rf /tmp/brainstorm-smoke
|
||||
```
|
||||
564
docs/superpowers/plans/2026-03-23-codex-app-compatibility.md
Normal file
564
docs/superpowers/plans/2026-03-23-codex-app-compatibility.md
Normal file
@@ -0,0 +1,564 @@
|
||||
# Codex App Compatibility Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Make `using-git-worktrees`, `finishing-a-development-branch`, and related skills work in the Codex App's sandboxed worktree environment without breaking existing behavior.
|
||||
|
||||
**Architecture:** Read-only environment detection (`git-dir` vs `git-common-dir`) at the start of two skills. If already in a linked worktree, skip creation. If on detached HEAD, emit a handoff payload instead of the 4-option menu. Sandbox fallback catches permission errors during worktree creation.
|
||||
|
||||
**Tech Stack:** Git, Markdown (skill files are instruction documents, not executable code)
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-03-23-codex-app-compatibility-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Responsibility | Action |
|
||||
|---|---|---|
|
||||
| `skills/using-git-worktrees/SKILL.md` | Worktree creation + isolation | Add Step 0 detection + sandbox fallback |
|
||||
| `skills/finishing-a-development-branch/SKILL.md` | Branch finishing workflow | Add Step 1.5 detection + cleanup guard |
|
||||
| `skills/subagent-driven-development/SKILL.md` | Plan execution with subagents | Update Integration description |
|
||||
| `skills/executing-plans/SKILL.md` | Plan execution inline | Update Integration description |
|
||||
| `skills/using-superpowers/references/codex-tools.md` | Codex platform reference | Add detection + finishing docs |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Step 0 to `using-git-worktrees`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/using-git-worktrees/SKILL.md:14-15` (insert after Overview, before Directory Selection Process)
|
||||
|
||||
- [ ] **Step 1: Read the current skill file**
|
||||
|
||||
Read `skills/using-git-worktrees/SKILL.md` in full. Identify the exact insertion point: after the "Announce at start" line (line 14) and before "## Directory Selection Process" (line 16).
|
||||
|
||||
- [ ] **Step 2: Insert Step 0 section**
|
||||
|
||||
Insert the following between the Overview section and "## Directory Selection Process":
|
||||
|
||||
```markdown
|
||||
## Step 0: Check if Already in an Isolated Workspace
|
||||
|
||||
Before creating a worktree, check if one already exists:
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
**If `GIT_DIR` differs from `GIT_COMMON`:** You are already inside a linked worktree (created by the Codex App, Claude Code's Agent tool, a previous skill run, or the user). Do NOT create another worktree. Instead:
|
||||
|
||||
1. Run project setup (auto-detect package manager as in "Run Project Setup" below)
|
||||
2. Verify clean baseline (run tests as in "Verify Clean Baseline" below)
|
||||
3. Report with branch state:
|
||||
- On a branch: "Already in an isolated workspace at `<path>` on branch `<name>`. Tests passing. Ready to implement."
|
||||
- Detached HEAD: "Already in an isolated workspace at `<path>` (detached HEAD, externally managed). Tests passing. Note: branch creation needed at finish time. Ready to implement."
|
||||
|
||||
After reporting, STOP. Do not continue to Directory Selection or Creation Steps.
|
||||
|
||||
**If `GIT_DIR` equals `GIT_COMMON`:** Proceed with the full worktree creation flow below.
|
||||
|
||||
**Sandbox fallback:** If you proceed to Creation Steps but `git worktree add -b` fails with a permission error (e.g., "Operation not permitted"), treat this as a late-detected restricted environment. Fall back to the behavior above — run setup and baseline tests in the current directory, report accordingly, and STOP.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the insertion**
|
||||
|
||||
Read the file again. Confirm:
|
||||
- Step 0 appears between Overview and Directory Selection Process
|
||||
- The rest of the file (Directory Selection, Safety Verification, Creation Steps, etc.) is unchanged
|
||||
- No duplicate sections or broken markdown
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/using-git-worktrees/SKILL.md
|
||||
git commit -m "feat(using-git-worktrees): add Step 0 environment detection (PRI-823)
|
||||
|
||||
Skip worktree creation when already in a linked worktree. Includes
|
||||
sandbox fallback for permission errors on git worktree add."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Update `using-git-worktrees` Integration section
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/using-git-worktrees/SKILL.md:211-215` (Integration > Called by)
|
||||
|
||||
- [ ] **Step 1: Update the three "Called by" entries**
|
||||
|
||||
Change lines 212-214 from:
|
||||
|
||||
```markdown
|
||||
- **brainstorming** (Phase 4) - REQUIRED when design is approved and implementation follows
|
||||
- **subagent-driven-development** - REQUIRED before executing any tasks
|
||||
- **executing-plans** - REQUIRED before executing any tasks
|
||||
```
|
||||
|
||||
To:
|
||||
|
||||
```markdown
|
||||
- **brainstorming** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
- **subagent-driven-development** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
- **executing-plans** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the Integration section**
|
||||
|
||||
Read the Integration section. Confirm all three entries are updated, "Pairs with" is unchanged.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/using-git-worktrees/SKILL.md
|
||||
git commit -m "docs(using-git-worktrees): update Integration descriptions (PRI-823)
|
||||
|
||||
Clarify that skill ensures a workspace exists, not that it always creates one."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Add Step 1.5 to `finishing-a-development-branch`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/finishing-a-development-branch/SKILL.md:38` (insert after Step 1, before Step 2)
|
||||
|
||||
- [ ] **Step 1: Read the current skill file**
|
||||
|
||||
Read `skills/finishing-a-development-branch/SKILL.md` in full. Identify the insertion point: after "**If tests pass:** Continue to Step 2." (line 38) and before "### Step 2: Determine Base Branch" (line 40).
|
||||
|
||||
- [ ] **Step 2: Insert Step 1.5 section**
|
||||
|
||||
Insert the following between Step 1 and Step 2:
|
||||
|
||||
```markdown
|
||||
### Step 1.5: Detect Environment
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
**Path A — `GIT_DIR` differs from `GIT_COMMON` AND `BRANCH` is empty (externally managed worktree, detached HEAD):**
|
||||
|
||||
First, ensure all work is staged and committed (`git add` + `git commit`).
|
||||
|
||||
Then present this to the user (do NOT present the 4-option menu):
|
||||
|
||||
```
|
||||
Implementation complete. All tests passing.
|
||||
Current HEAD: <full-commit-sha>
|
||||
|
||||
This workspace is externally managed (detached HEAD).
|
||||
I cannot create branches, push, or open PRs from here.
|
||||
|
||||
⚠ These commits are on a detached HEAD. If you do not create a branch,
|
||||
they may be lost when this workspace is cleaned up.
|
||||
|
||||
If your host application provides these controls:
|
||||
- "Create branch" — to name a branch, then commit/push/PR
|
||||
- "Hand off to local" — to move changes to your local checkout
|
||||
|
||||
Suggested branch name: <ticket-id/short-description>
|
||||
Suggested commit message: <summary-of-work>
|
||||
```
|
||||
|
||||
Branch name: use ticket ID if available (e.g., `pri-823/codex-compat`), otherwise slugify the first 5 words of the plan title, otherwise omit. Avoid sensitive content in branch names.
|
||||
|
||||
Skip to Step 5 (cleanup is a no-op — see guard below).
|
||||
|
||||
**Path B — `GIT_DIR` differs from `GIT_COMMON` AND `BRANCH` exists (externally managed worktree, named branch):**
|
||||
|
||||
Proceed to Step 2 and present the 4-option menu as normal.
|
||||
|
||||
**Path C — `GIT_DIR` equals `GIT_COMMON` (normal environment):**
|
||||
|
||||
Proceed to Step 2 and present the 4-option menu as normal.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the insertion**
|
||||
|
||||
Read the file again. Confirm:
|
||||
- Step 1.5 appears between Step 1 and Step 2
|
||||
- Steps 2-5 are unchanged
|
||||
- Path A handoff includes commit SHA and data loss warning
|
||||
- Paths B and C proceed to Step 2 normally
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/finishing-a-development-branch/SKILL.md
|
||||
git commit -m "feat(finishing-a-development-branch): add Step 1.5 environment detection (PRI-823)
|
||||
|
||||
Detect externally managed worktrees with detached HEAD and emit handoff
|
||||
payload instead of 4-option menu. Includes commit SHA and data loss warning."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Add Step 5 cleanup guard to `finishing-a-development-branch`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/finishing-a-development-branch/SKILL.md` (Step 5: Cleanup Worktree — find by section heading, line numbers will have shifted after Task 3)
|
||||
|
||||
- [ ] **Step 1: Read the current Step 5 section**
|
||||
|
||||
Find the "### Step 5: Cleanup Worktree" section in `skills/finishing-a-development-branch/SKILL.md` (line numbers will have shifted after Task 3's insertion). The current Step 5 is:
|
||||
|
||||
```markdown
|
||||
### Step 5: Cleanup Worktree
|
||||
|
||||
**For Options 1, 2, 4:**
|
||||
|
||||
Check if in worktree:
|
||||
```bash
|
||||
git worktree list | grep $(git branch --show-current)
|
||||
```
|
||||
|
||||
If yes:
|
||||
```bash
|
||||
git worktree remove <worktree-path>
|
||||
```
|
||||
|
||||
**For Option 3:** Keep worktree.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add the cleanup guard before existing logic**
|
||||
|
||||
Replace the Step 5 section with:
|
||||
|
||||
```markdown
|
||||
### Step 5: Cleanup Worktree
|
||||
|
||||
**First, check if worktree is externally managed:**
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
```
|
||||
|
||||
If `GIT_DIR` differs from `GIT_COMMON`: skip worktree removal — the host environment owns this workspace.
|
||||
|
||||
**Otherwise, for Options 1 and 4:**
|
||||
|
||||
Check if in worktree:
|
||||
```bash
|
||||
git worktree list | grep $(git branch --show-current)
|
||||
```
|
||||
|
||||
If yes:
|
||||
```bash
|
||||
git worktree remove <worktree-path>
|
||||
```
|
||||
|
||||
**For Option 3:** Keep worktree.
|
||||
```
|
||||
|
||||
Note: the original text said "For Options 1, 2, 4" but the Quick Reference table and Common Mistakes section say "Options 1 & 4 only." This edit aligns Step 5 with those sections.
|
||||
|
||||
- [ ] **Step 3: Verify the replacement**
|
||||
|
||||
Read Step 5. Confirm:
|
||||
- Cleanup guard (re-detection) appears first
|
||||
- Existing removal logic preserved for non-externally-managed worktrees
|
||||
- "Options 1 and 4" (not "1, 2, 4") matches Quick Reference and Common Mistakes
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/finishing-a-development-branch/SKILL.md
|
||||
git commit -m "feat(finishing-a-development-branch): add Step 5 cleanup guard (PRI-823)
|
||||
|
||||
Re-detect externally managed worktree at cleanup time and skip removal.
|
||||
Also fixes pre-existing inconsistency: cleanup now correctly says
|
||||
Options 1 and 4 only, matching Quick Reference and Common Mistakes."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Update Integration lines in `subagent-driven-development` and `executing-plans`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/subagent-driven-development/SKILL.md:268`
|
||||
- Modify: `skills/executing-plans/SKILL.md:68`
|
||||
|
||||
- [ ] **Step 1: Update `subagent-driven-development`**
|
||||
|
||||
Change line 268 from:
|
||||
```
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting
|
||||
```
|
||||
To:
|
||||
```
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update `executing-plans`**
|
||||
|
||||
Change line 68 from:
|
||||
```
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting
|
||||
```
|
||||
To:
|
||||
```
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify both files**
|
||||
|
||||
Read line 268 of `skills/subagent-driven-development/SKILL.md` and line 68 of `skills/executing-plans/SKILL.md`. Confirm both say "Ensures isolated workspace (creates one or verifies existing)".
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/subagent-driven-development/SKILL.md skills/executing-plans/SKILL.md
|
||||
git commit -m "docs(sdd, executing-plans): update worktree Integration descriptions (PRI-823)
|
||||
|
||||
Clarify that using-git-worktrees ensures a workspace exists rather than
|
||||
always creating one."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Add environment detection docs to `codex-tools.md`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/using-superpowers/references/codex-tools.md:25` (append at end)
|
||||
|
||||
- [ ] **Step 1: Read the current file**
|
||||
|
||||
Read `skills/using-superpowers/references/codex-tools.md` in full. Confirm it ends at line 25-26 after the multi_agent section.
|
||||
|
||||
- [ ] **Step 2: Append two new sections**
|
||||
|
||||
Add at the end of the file:
|
||||
|
||||
```markdown
|
||||
|
||||
## Environment Detection
|
||||
|
||||
Skills that create worktrees or finish branches should detect their
|
||||
environment with read-only git commands before proceeding:
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
|
||||
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
|
||||
|
||||
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
|
||||
Step 1.5 for how each skill uses these signals.
|
||||
|
||||
## Codex App Finishing
|
||||
|
||||
When the sandbox blocks branch/push operations (detached HEAD in an
|
||||
externally managed worktree), the agent commits all work and informs
|
||||
the user to use the App's native controls:
|
||||
|
||||
- **"Create branch"** — names the branch, then commit/push/PR via App UI
|
||||
- **"Hand off to local"** — transfers work to the user's local checkout
|
||||
|
||||
The agent can still run tests, stage files, and output suggested branch
|
||||
names, commit messages, and PR descriptions for the user to copy.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the additions**
|
||||
|
||||
Read the full file. Confirm:
|
||||
- Two new sections appear after the existing content
|
||||
- Bash code block renders correctly (not escaped)
|
||||
- Cross-references to Step 0 and Step 1.5 are present
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/using-superpowers/references/codex-tools.md
|
||||
git commit -m "docs(codex-tools): add environment detection and App finishing docs (PRI-823)
|
||||
|
||||
Document the git-dir vs git-common-dir detection pattern and the Codex
|
||||
App's native finishing flow for skills that need to adapt."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Automated test — environment detection
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/codex-app-compat/test-environment-detection.sh`
|
||||
|
||||
- [ ] **Step 1: Create test directory**
|
||||
|
||||
```bash
|
||||
mkdir -p tests/codex-app-compat
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write the detection test script**
|
||||
|
||||
Create `tests/codex-app-compat/test-environment-detection.sh`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Test environment detection logic from PRI-823
|
||||
# Tests the git-dir vs git-common-dir comparison used by
|
||||
# using-git-worktrees Step 0 and finishing-a-development-branch Step 1.5
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TEMP_DIR" EXIT
|
||||
|
||||
log_pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
||||
log_fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
||||
|
||||
# Helper: run detection and return "linked" or "normal"
|
||||
detect_worktree() {
|
||||
local git_dir git_common
|
||||
git_dir=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
git_common=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
if [ "$git_dir" != "$git_common" ]; then
|
||||
echo "linked"
|
||||
else
|
||||
echo "normal"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=== Test 1: Normal repo detection ==="
|
||||
cd "$TEMP_DIR"
|
||||
git init test-repo > /dev/null 2>&1
|
||||
cd test-repo
|
||||
git commit --allow-empty -m "init" > /dev/null 2>&1
|
||||
result=$(detect_worktree)
|
||||
if [ "$result" = "normal" ]; then
|
||||
log_pass "Normal repo detected as normal"
|
||||
else
|
||||
log_fail "Normal repo detected as '$result' (expected 'normal')"
|
||||
fi
|
||||
|
||||
echo "=== Test 2: Linked worktree detection ==="
|
||||
git worktree add "$TEMP_DIR/test-wt" -b test-branch > /dev/null 2>&1
|
||||
cd "$TEMP_DIR/test-wt"
|
||||
result=$(detect_worktree)
|
||||
if [ "$result" = "linked" ]; then
|
||||
log_pass "Linked worktree detected as linked"
|
||||
else
|
||||
log_fail "Linked worktree detected as '$result' (expected 'linked')"
|
||||
fi
|
||||
|
||||
echo "=== Test 3: Detached HEAD detection ==="
|
||||
git checkout --detach HEAD > /dev/null 2>&1
|
||||
branch=$(git branch --show-current)
|
||||
if [ -z "$branch" ]; then
|
||||
log_pass "Detached HEAD: branch is empty"
|
||||
else
|
||||
log_fail "Detached HEAD: branch is '$branch' (expected empty)"
|
||||
fi
|
||||
|
||||
echo "=== Test 4: Linked worktree + detached HEAD (Codex App simulation) ==="
|
||||
result=$(detect_worktree)
|
||||
branch=$(git branch --show-current)
|
||||
if [ "$result" = "linked" ] && [ -z "$branch" ]; then
|
||||
log_pass "Codex App simulation: linked + detached HEAD"
|
||||
else
|
||||
log_fail "Codex App simulation: result='$result', branch='$branch'"
|
||||
fi
|
||||
|
||||
echo "=== Test 5: Cleanup guard — linked worktree should NOT remove ==="
|
||||
cd "$TEMP_DIR/test-wt"
|
||||
result=$(detect_worktree)
|
||||
if [ "$result" = "linked" ]; then
|
||||
log_pass "Cleanup guard: linked worktree correctly detected (would skip removal)"
|
||||
else
|
||||
log_fail "Cleanup guard: expected 'linked', got '$result'"
|
||||
fi
|
||||
|
||||
echo "=== Test 6: Cleanup guard — main repo SHOULD remove ==="
|
||||
cd "$TEMP_DIR/test-repo"
|
||||
result=$(detect_worktree)
|
||||
if [ "$result" = "normal" ]; then
|
||||
log_pass "Cleanup guard: main repo correctly detected (would proceed with removal)"
|
||||
else
|
||||
log_fail "Cleanup guard: expected 'normal', got '$result'"
|
||||
fi
|
||||
|
||||
# Cleanup worktree before temp dir removal
|
||||
cd "$TEMP_DIR/test-repo"
|
||||
git worktree remove "$TEMP_DIR/test-wt" > /dev/null 2>&1 || true
|
||||
|
||||
echo ""
|
||||
echo "=== Results: $PASS passed, $FAIL failed ==="
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Make it executable and run it**
|
||||
|
||||
```bash
|
||||
chmod +x tests/codex-app-compat/test-environment-detection.sh
|
||||
./tests/codex-app-compat/test-environment-detection.sh
|
||||
```
|
||||
|
||||
Expected output: 6 passed, 0 failed.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/codex-app-compat/test-environment-detection.sh
|
||||
git commit -m "test: add environment detection tests for Codex App compat (PRI-823)
|
||||
|
||||
Tests git-dir vs git-common-dir comparison in normal repo, linked
|
||||
worktree, detached HEAD, and cleanup guard scenarios."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Final verification
|
||||
|
||||
**Files:**
|
||||
- Read: all 5 modified skill files
|
||||
|
||||
- [ ] **Step 1: Run the automated detection tests**
|
||||
|
||||
```bash
|
||||
./tests/codex-app-compat/test-environment-detection.sh
|
||||
```
|
||||
|
||||
Expected: 6 passed, 0 failed.
|
||||
|
||||
- [ ] **Step 2: Read each modified file and verify changes**
|
||||
|
||||
Read each file end-to-end:
|
||||
- `skills/using-git-worktrees/SKILL.md` — Step 0 present, rest unchanged
|
||||
- `skills/finishing-a-development-branch/SKILL.md` — Step 1.5 present, cleanup guard present, rest unchanged
|
||||
- `skills/subagent-driven-development/SKILL.md` — line 268 updated
|
||||
- `skills/executing-plans/SKILL.md` — line 68 updated
|
||||
- `skills/using-superpowers/references/codex-tools.md` — two new sections at end
|
||||
|
||||
- [ ] **Step 3: Verify no unintended changes**
|
||||
|
||||
```bash
|
||||
git diff --stat HEAD~7
|
||||
```
|
||||
|
||||
Should show exactly 6 files changed (5 skill files + 1 test file). No other files modified.
|
||||
|
||||
- [ ] **Step 4: Run existing test suite**
|
||||
|
||||
If test runner exists:
|
||||
```bash
|
||||
# Run skill-triggering tests
|
||||
./tests/skill-triggering/run-all.sh 2>/dev/null || echo "Skill triggering tests not available in this environment"
|
||||
|
||||
# Run SDD integration test
|
||||
./tests/claude-code/test-subagent-driven-development-integration.sh 2>/dev/null || echo "SDD integration test not available in this environment"
|
||||
```
|
||||
|
||||
Note: these tests require Claude Code with `--dangerously-skip-permissions`. If not available, document that regression tests should be run manually.
|
||||
879
docs/superpowers/plans/2026-04-06-worktree-rototill.md
Normal file
879
docs/superpowers/plans/2026-04-06-worktree-rototill.md
Normal file
@@ -0,0 +1,879 @@
|
||||
# Worktree Rototill Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Make superpowers defer to native harness worktree systems when available, fall back to manual git worktrees when not, and fix three known finishing bugs.
|
||||
|
||||
**Architecture:** Two skill files are rewritten (`using-git-worktrees`, `finishing-a-development-branch`), three files get one-line integration updates (`executing-plans`, `subagent-driven-development`, `writing-plans`). The core change is adding detection (`GIT_DIR != GIT_COMMON`) and a native-tool-first creation path. These are markdown skill instruction files, not application code — "tests" are agent behavior tests using the testing-skills-with-subagents TDD framework.
|
||||
|
||||
**Tech Stack:** Markdown (skill files), bash (test scripts), Claude Code CLI (`claude -p` for headless testing)
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-04-06-worktree-rototill-design.md`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: GATE — TDD Validation of Step 1a (Native Tool Preference)
|
||||
|
||||
Step 1a is the load-bearing assumption of the entire design. If agents don't prefer native worktree tools over `git worktree add`, the spec fails. Validate this FIRST, before touching any skill files.
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/claude-code/test-worktree-native-preference.sh`
|
||||
- Read: `skills/using-git-worktrees/SKILL.md` (current version, for RED baseline)
|
||||
- Read: `tests/claude-code/test-helpers.sh` (for `run_claude`, `assert_contains`, etc.)
|
||||
- Read: `skills/writing-skills/testing-skills-with-subagents.md` (TDD framework)
|
||||
|
||||
**This task is a gate.** If the GREEN phase fails after 2 REFACTOR iterations, STOP. Do not proceed to Task 2. Report back — the creation approach needs redesign.
|
||||
|
||||
- [ ] **Step 1: Write the RED baseline test script**
|
||||
|
||||
Create the test script that will run scenarios both WITHOUT and WITH the updated skill text. The RED phase runs against the current skill (which has no Step 1a).
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Test: Does the agent prefer native worktree tools (EnterWorktree) over git worktree add?
|
||||
# Framework: RED-GREEN-REFACTOR per testing-skills-with-subagents.md
|
||||
#
|
||||
# RED: Current skill has no native tool preference. Agent should use git worktree add.
|
||||
# GREEN: Updated skill has Step 1a. Agent should use EnterWorktree on Claude Code.
|
||||
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/test-helpers.sh"
|
||||
|
||||
# Pressure scenario: realistic implementation task where agent needs isolation
|
||||
SCENARIO='IMPORTANT: This is a real task. Choose and act.
|
||||
|
||||
You need to implement a small feature (add a "version" field to package.json).
|
||||
This should be done in an isolated workspace to protect the main branch.
|
||||
|
||||
You have the using-git-worktrees skill available. Set up the isolated workspace now.
|
||||
Do NOT actually implement the feature — just set up the workspace and report what you did.
|
||||
|
||||
Respond with EXACTLY what tool/command you used to create the workspace.'
|
||||
|
||||
echo "=== Worktree Native Preference Test ==="
|
||||
echo ""
|
||||
|
||||
# Phase selection
|
||||
PHASE="${1:-red}"
|
||||
|
||||
if [ "$PHASE" = "red" ]; then
|
||||
echo "--- RED PHASE: Running WITHOUT Step 1a (current skill) ---"
|
||||
echo "Expected: Agent uses 'git worktree add' (no native tool awareness)"
|
||||
echo ""
|
||||
|
||||
test_dir=$(create_test_project)
|
||||
cd "$test_dir"
|
||||
git init && git commit --allow-empty -m "init"
|
||||
mkdir -p .worktrees
|
||||
|
||||
output=$(run_claude "$SCENARIO" 120)
|
||||
|
||||
echo "Agent output:"
|
||||
echo "$output"
|
||||
echo ""
|
||||
|
||||
# RED expectation: agent uses git worktree add (current behavior)
|
||||
if echo "$output" | grep -qi "EnterWorktree"; then
|
||||
echo "[UNEXPECTED] Agent used EnterWorktree WITHOUT Step 1a — skill may not be needed"
|
||||
echo "Investigate: is Claude Code's default behavior already correct?"
|
||||
else
|
||||
echo "[RED CONFIRMED] Agent did NOT use EnterWorktree"
|
||||
assert_contains "$output" "git worktree" "Agent used git worktree (expected in RED)"
|
||||
fi
|
||||
|
||||
cleanup_test_project "$test_dir"
|
||||
|
||||
elif [ "$PHASE" = "green" ]; then
|
||||
echo "--- GREEN PHASE: Running WITH Step 1a (updated skill) ---"
|
||||
echo "Expected: Agent uses EnterWorktree instead of git worktree add"
|
||||
echo ""
|
||||
|
||||
test_dir=$(create_test_project)
|
||||
cd "$test_dir"
|
||||
git init && git commit --allow-empty -m "init"
|
||||
|
||||
output=$(run_claude "$SCENARIO" 120)
|
||||
|
||||
echo "Agent output:"
|
||||
echo "$output"
|
||||
echo ""
|
||||
|
||||
# GREEN expectation: agent uses EnterWorktree
|
||||
assert_contains "$output" "EnterWorktree" "Agent used native EnterWorktree tool"
|
||||
assert_not_contains "$output" "git worktree add" "Agent did NOT fall back to git worktree add"
|
||||
|
||||
cleanup_test_project "$test_dir"
|
||||
|
||||
elif [ "$PHASE" = "pressure" ]; then
|
||||
echo "--- PRESSURE PHASE: Multiple pressures to skip native tools ---"
|
||||
echo ""
|
||||
|
||||
PRESSURE_SCENARIO='IMPORTANT: This is a real task. Choose and act.
|
||||
|
||||
You need to set up an isolated workspace URGENTLY. Production is impacted and
|
||||
the fix needs to go in a worktree. You know git worktree add works reliably.
|
||||
The .worktrees/ directory already exists and is gitignored.
|
||||
|
||||
You have the using-git-worktrees skill. Set up the workspace NOW.
|
||||
Speed matters. Report EXACTLY what tool/command you used.'
|
||||
|
||||
test_dir=$(create_test_project)
|
||||
cd "$test_dir"
|
||||
git init && git commit --allow-empty -m "init"
|
||||
mkdir -p .worktrees
|
||||
echo ".worktrees/" >> .gitignore
|
||||
|
||||
output=$(run_claude "$PRESSURE_SCENARIO" 120)
|
||||
|
||||
echo "Agent output:"
|
||||
echo "$output"
|
||||
echo ""
|
||||
|
||||
# Should STILL use EnterWorktree even under pressure
|
||||
assert_contains "$output" "EnterWorktree" "Agent used native tool even under time pressure"
|
||||
assert_not_contains "$output" "git worktree add" "Agent resisted falling back to git despite pressure"
|
||||
|
||||
cleanup_test_project "$test_dir"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Test Complete ==="
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run RED phase — confirm agent uses git worktree add today**
|
||||
|
||||
Run: `cd tests/claude-code && bash test-worktree-native-preference.sh red`
|
||||
|
||||
Expected: `[RED CONFIRMED] Agent did NOT use EnterWorktree` — agent uses `git worktree add` because current skill has no native tool preference.
|
||||
|
||||
Document the agent's exact output and any rationalizations verbatim. This is the baseline failure the skill must fix.
|
||||
|
||||
- [ ] **Step 3: If RED confirmed, proceed. Write the Step 1a skill text.**
|
||||
|
||||
Create a temporary test version of the skill with ONLY the Step 1a addition (minimal change to isolate the variable). Add this section to the top of the skill's creation instructions, BEFORE the existing directory selection process:
|
||||
|
||||
```markdown
|
||||
## Step 1: Create Isolated Workspace
|
||||
|
||||
**You have two mechanisms. Try them in this order.**
|
||||
|
||||
### 1a. Native Worktree Tools (preferred)
|
||||
|
||||
If your platform provides a worktree or workspace-isolation tool, use it. You know your own toolkit — the skill does not need to name specific tools. Native tools handle directory placement, branch creation, and cleanup automatically.
|
||||
|
||||
After using a native tool, skip to Step 3 (Project Setup).
|
||||
|
||||
### 1b. Git Worktree Fallback
|
||||
|
||||
If no native tool is available, create a worktree manually using git.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run GREEN phase — confirm agent now uses EnterWorktree**
|
||||
|
||||
Run: `cd tests/claude-code && bash test-worktree-native-preference.sh green`
|
||||
|
||||
Expected: `[PASS] Agent used native EnterWorktree tool`
|
||||
|
||||
If FAIL: Document the agent's exact output and rationalizations. This is a REFACTOR signal — the Step 1a text needs revision. Try up to 2 REFACTOR iterations. If still failing after 2 iterations, STOP and report back.
|
||||
|
||||
- [ ] **Step 5: Run PRESSURE phase — confirm agent resists fallback under pressure**
|
||||
|
||||
Run: `cd tests/claude-code && bash test-worktree-native-preference.sh pressure`
|
||||
|
||||
Expected: `[PASS] Agent used native tool even under time pressure`
|
||||
|
||||
If FAIL: Document rationalizations verbatim. Add explicit counters to Step 1a text (e.g., a Red Flag entry: "Never use git worktree add when your platform provides a native worktree tool"). Re-run.
|
||||
|
||||
- [ ] **Step 6: Commit test script**
|
||||
|
||||
```bash
|
||||
git add tests/claude-code/test-worktree-native-preference.sh
|
||||
git commit -m "test: add RED/GREEN validation for native worktree preference (PRI-974)
|
||||
|
||||
Gate test for Step 1a — validates agents prefer EnterWorktree over
|
||||
git worktree add on Claude Code. Must pass before skill rewrite."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Rewrite `using-git-worktrees` SKILL.md
|
||||
|
||||
Full rewrite of the creation skill. Replaces the existing file entirely.
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/using-git-worktrees/SKILL.md` (full rewrite, 219 lines → ~210 lines)
|
||||
|
||||
**Depends on:** Task 1 GREEN passing.
|
||||
|
||||
- [ ] **Step 1: Write the complete new SKILL.md**
|
||||
|
||||
Replace the entire contents of `skills/using-git-worktrees/SKILL.md` with:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: using-git-worktrees
|
||||
description: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback
|
||||
---
|
||||
|
||||
# Using Git Worktrees
|
||||
|
||||
## Overview
|
||||
|
||||
Ensure work happens in an isolated workspace. Prefer your platform's native worktree tools. Fall back to manual git worktrees only when no native tool is available.
|
||||
|
||||
**Core principle:** Detect existing isolation first. Then use native tools. Then fall back to git. Never fight the harness.
|
||||
|
||||
**Announce at start:** "I'm using the using-git-worktrees skill to set up an isolated workspace."
|
||||
|
||||
## Step 0: Detect Existing Isolation
|
||||
|
||||
**Before creating anything, check if you are already in an isolated workspace.**
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
**Submodule guard:** `GIT_DIR != GIT_COMMON` is also true inside git submodules. Before concluding "already in a worktree," verify you are not in a submodule:
|
||||
|
||||
```bash
|
||||
# If this returns a path, you're in a submodule, not a worktree — proceed to Step 1
|
||||
git rev-parse --show-superproject-working-tree 2>/dev/null
|
||||
```
|
||||
|
||||
**If `GIT_DIR != GIT_COMMON` (and not a submodule):** You are already in a linked worktree. Skip to Step 3 (Project Setup). Do NOT create another worktree.
|
||||
|
||||
Report with branch state:
|
||||
- On a branch: "Already in isolated workspace at `<path>` on branch `<name>`."
|
||||
- Detached HEAD: "Already in isolated workspace at `<path>` (detached HEAD, externally managed). Branch creation needed at finish time."
|
||||
|
||||
**If `GIT_DIR == GIT_COMMON` (or in a submodule):** You are in a normal repo checkout.
|
||||
|
||||
Has the user already indicated their worktree preference in your instructions? If not, ask for consent before creating a worktree:
|
||||
|
||||
> "Would you like me to set up an isolated worktree? It protects your current branch from changes."
|
||||
|
||||
Honor any existing declared preference without asking. If the user declines consent, work in place and skip to Step 3.
|
||||
|
||||
## Step 1: Create Isolated Workspace
|
||||
|
||||
**You have two mechanisms. Try them in this order.**
|
||||
|
||||
### 1a. Native Worktree Tools (preferred)
|
||||
|
||||
If your platform provides a worktree or workspace-isolation tool, use it. You know your own toolkit — the skill does not need to name specific tools. Native tools handle directory placement, branch creation, and cleanup automatically.
|
||||
|
||||
After using a native tool, skip to Step 3 (Project Setup).
|
||||
|
||||
### 1b. Git Worktree Fallback
|
||||
|
||||
If no native tool is available, create a worktree manually using git.
|
||||
|
||||
#### Directory Selection
|
||||
|
||||
Follow this priority order:
|
||||
|
||||
1. **Check existing directories:**
|
||||
```bash
|
||||
ls -d .worktrees 2>/dev/null # Preferred (hidden)
|
||||
ls -d worktrees 2>/dev/null # Alternative
|
||||
```
|
||||
If found, use that directory. If both exist, `.worktrees` wins.
|
||||
|
||||
2. **Check for existing global directory:**
|
||||
```bash
|
||||
project=$(basename "$(git rev-parse --show-toplevel)")
|
||||
ls -d ~/.config/superpowers/worktrees/$project 2>/dev/null
|
||||
```
|
||||
If found, use it (backward compatibility with legacy global path).
|
||||
|
||||
3. **Check your instructions for a worktree directory preference.** If specified, use it without asking.
|
||||
|
||||
4. **Default to `.worktrees/`.**
|
||||
|
||||
#### Safety Verification (project-local directories only)
|
||||
|
||||
**MUST verify directory is ignored before creating worktree:**
|
||||
|
||||
```bash
|
||||
git check-ignore -q .worktrees 2>/dev/null || git check-ignore -q worktrees 2>/dev/null
|
||||
```
|
||||
|
||||
**If NOT ignored:** Add to .gitignore, commit the change, then proceed.
|
||||
|
||||
**Why critical:** Prevents accidentally committing worktree contents to repository.
|
||||
|
||||
Global directories (`~/.config/superpowers/worktrees/`) need no verification.
|
||||
|
||||
#### Create the Worktree
|
||||
|
||||
```bash
|
||||
project=$(basename "$(git rev-parse --show-toplevel)")
|
||||
|
||||
# Determine path based on chosen location
|
||||
# For project-local: path="$LOCATION/$BRANCH_NAME"
|
||||
# For global: path="~/.config/superpowers/worktrees/$project/$BRANCH_NAME"
|
||||
|
||||
git worktree add "$path" -b "$BRANCH_NAME"
|
||||
cd "$path"
|
||||
```
|
||||
|
||||
#### Hooks Awareness
|
||||
|
||||
Git worktrees do not inherit the parent repo's hooks directory. After creating the worktree, symlink hooks from the main repo if they exist:
|
||||
|
||||
```bash
|
||||
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
||||
if [ -d "$MAIN_ROOT/.git/hooks" ]; then
|
||||
ln -sf "$MAIN_ROOT/.git/hooks" "$path/.git/hooks"
|
||||
fi
|
||||
```
|
||||
|
||||
This prevents pre-commit checks, linters, and other hooks from silently stopping when work moves to a worktree.
|
||||
|
||||
**Sandbox fallback:** If `git worktree add` fails with a permission error (sandbox denial), treat this as a restricted environment. Skip creation, run setup and baseline tests in the current directory, report accordingly.
|
||||
|
||||
## Step 3: Project Setup
|
||||
|
||||
Auto-detect and run appropriate setup:
|
||||
|
||||
```bash
|
||||
# Node.js
|
||||
if [ -f package.json ]; then npm install; fi
|
||||
|
||||
# Rust
|
||||
if [ -f Cargo.toml ]; then cargo build; fi
|
||||
|
||||
# Python
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
if [ -f pyproject.toml ]; then poetry install; fi
|
||||
|
||||
# Go
|
||||
if [ -f go.mod ]; then go mod download; fi
|
||||
```
|
||||
|
||||
## Step 4: Verify Clean Baseline
|
||||
|
||||
Run tests to ensure workspace starts clean:
|
||||
|
||||
```bash
|
||||
# Use project-appropriate command
|
||||
npm test / cargo test / pytest / go test ./...
|
||||
```
|
||||
|
||||
**If tests fail:** Report failures, ask whether to proceed or investigate.
|
||||
|
||||
**If tests pass:** Report ready.
|
||||
|
||||
### Report
|
||||
|
||||
```
|
||||
Worktree ready at <full-path>
|
||||
Tests passing (<N> tests, 0 failures)
|
||||
Ready to implement <feature-name>
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Already in linked worktree | Skip creation (Step 0) |
|
||||
| In a submodule | Treat as normal repo (Step 0 guard) |
|
||||
| Native worktree tool available | Use it (Step 1a) |
|
||||
| No native tool | Git worktree fallback (Step 1b) |
|
||||
| `.worktrees/` exists | Use it (verify ignored) |
|
||||
| `worktrees/` exists | Use it (verify ignored) |
|
||||
| Both exist | Use `.worktrees/` |
|
||||
| Neither exists | Check instruction file, then default `.worktrees/` |
|
||||
| Global path exists | Use it (backward compat) |
|
||||
| Directory not ignored | Add to .gitignore + commit |
|
||||
| Permission error on create | Sandbox fallback, work in place |
|
||||
| Tests fail during baseline | Report failures + ask |
|
||||
| No package.json/Cargo.toml | Skip dependency install |
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### Fighting the harness
|
||||
|
||||
- **Problem:** Using `git worktree add` when the platform already provides isolation
|
||||
- **Fix:** Step 0 detects existing isolation. Step 1a defers to native tools.
|
||||
|
||||
### Skipping detection
|
||||
|
||||
- **Problem:** Creating a nested worktree inside an existing one
|
||||
- **Fix:** Always run Step 0 before creating anything
|
||||
|
||||
### Skipping ignore verification
|
||||
|
||||
- **Problem:** Worktree contents get tracked, pollute git status
|
||||
- **Fix:** Always use `git check-ignore` before creating project-local worktree
|
||||
|
||||
### Assuming directory location
|
||||
|
||||
- **Problem:** Creates inconsistency, violates project conventions
|
||||
- **Fix:** Follow priority: existing > instruction file > default
|
||||
|
||||
### Proceeding with failing tests
|
||||
|
||||
- **Problem:** Can't distinguish new bugs from pre-existing issues
|
||||
- **Fix:** Report failures, get explicit permission to proceed
|
||||
|
||||
## Red Flags
|
||||
|
||||
**Never:**
|
||||
- Create a worktree when Step 0 detects existing isolation
|
||||
- Use git commands when a native worktree tool is available
|
||||
- Create worktree without verifying it's ignored (project-local)
|
||||
- Skip baseline test verification
|
||||
- Proceed with failing tests without asking
|
||||
|
||||
**Always:**
|
||||
- Run Step 0 detection first
|
||||
- Prefer native tools over git fallback
|
||||
- Follow directory priority: existing > instruction file > default
|
||||
- Verify directory is ignored for project-local
|
||||
- Auto-detect and run project setup
|
||||
- Verify clean test baseline
|
||||
- Symlink hooks after creating worktree via 1b
|
||||
|
||||
## Integration
|
||||
|
||||
**Called by:**
|
||||
- **subagent-driven-development** - Ensures isolated workspace (creates one or verifies existing)
|
||||
- **executing-plans** - Ensures isolated workspace (creates one or verifies existing)
|
||||
- Any skill needing isolated workspace
|
||||
|
||||
**Pairs with:**
|
||||
- **finishing-a-development-branch** - REQUIRED for cleanup after work complete
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the file reads correctly**
|
||||
|
||||
Run: `wc -l skills/using-git-worktrees/SKILL.md`
|
||||
|
||||
Expected: Approximately 200-220 lines. Scan for any markdown formatting issues.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/using-git-worktrees/SKILL.md
|
||||
git commit -m "feat: rewrite using-git-worktrees with detect-and-defer (PRI-974)
|
||||
|
||||
Step 0: GIT_DIR != GIT_COMMON detection (skip if already isolated)
|
||||
Step 0 consent: opt-in prompt before creating worktree (#991)
|
||||
Step 1a: native tool preference (short, first, declarative)
|
||||
Step 1b: git worktree fallback with hooks symlink and legacy path compat
|
||||
Submodule guard prevents false detection
|
||||
Platform-neutral instruction file references (#1049)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Rewrite `finishing-a-development-branch` SKILL.md
|
||||
|
||||
Full rewrite of the finishing skill. Adds environment detection, fixes three bugs, adds provenance-based cleanup.
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/finishing-a-development-branch/SKILL.md` (full rewrite, 201 lines → ~220 lines)
|
||||
|
||||
- [ ] **Step 1: Write the complete new SKILL.md**
|
||||
|
||||
Replace the entire contents of `skills/finishing-a-development-branch/SKILL.md` with:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: finishing-a-development-branch
|
||||
description: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
|
||||
---
|
||||
|
||||
# Finishing a Development Branch
|
||||
|
||||
## Overview
|
||||
|
||||
Guide completion of development work by presenting clear options and handling chosen workflow.
|
||||
|
||||
**Core principle:** Verify tests → Detect environment → Present options → Execute choice → Clean up.
|
||||
|
||||
**Announce at start:** "I'm using the finishing-a-development-branch skill to complete this work."
|
||||
|
||||
## The Process
|
||||
|
||||
### Step 1: Verify Tests
|
||||
|
||||
**Before presenting options, verify tests pass:**
|
||||
|
||||
```bash
|
||||
# Run project's test suite
|
||||
npm test / cargo test / pytest / go test ./...
|
||||
```
|
||||
|
||||
**If tests fail:**
|
||||
```
|
||||
Tests failing (<N> failures). Must fix before completing:
|
||||
|
||||
[Show failures]
|
||||
|
||||
Cannot proceed with merge/PR until tests pass.
|
||||
```
|
||||
|
||||
Stop. Don't proceed to Step 2.
|
||||
|
||||
**If tests pass:** Continue to Step 2.
|
||||
|
||||
### Step 2: Detect Environment
|
||||
|
||||
**Determine workspace state before presenting options:**
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
```
|
||||
|
||||
This determines which menu to show and how cleanup works:
|
||||
|
||||
| State | Menu | Cleanup |
|
||||
|-------|------|---------|
|
||||
| `GIT_DIR == GIT_COMMON` (normal repo) | Standard 4 options | No worktree to clean up |
|
||||
| `GIT_DIR != GIT_COMMON`, named branch | Standard 4 options | Provenance-based (see Step 6) |
|
||||
| `GIT_DIR != GIT_COMMON`, detached HEAD | Reduced 3 options (no merge) | No cleanup (externally managed) |
|
||||
|
||||
### Step 3: Determine Base Branch
|
||||
|
||||
```bash
|
||||
# Try common base branches
|
||||
git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null
|
||||
```
|
||||
|
||||
Or ask: "This branch split from main - is that correct?"
|
||||
|
||||
### Step 4: Present Options
|
||||
|
||||
**Normal repo and named-branch worktree — present exactly these 4 options:**
|
||||
|
||||
```
|
||||
Implementation complete. What would you like to do?
|
||||
|
||||
1. Merge back to <base-branch> locally
|
||||
2. Push and create a Pull Request
|
||||
3. Keep the branch as-is (I'll handle it later)
|
||||
4. Discard this work
|
||||
|
||||
Which option?
|
||||
```
|
||||
|
||||
**Detached HEAD — present exactly these 3 options:**
|
||||
|
||||
```
|
||||
Implementation complete. You're on a detached HEAD (externally managed workspace).
|
||||
|
||||
1. Push as new branch and create a Pull Request
|
||||
2. Keep as-is (I'll handle it later)
|
||||
3. Discard this work
|
||||
|
||||
Which option?
|
||||
```
|
||||
|
||||
**Don't add explanation** - keep options concise.
|
||||
|
||||
### Step 5: Execute Choice
|
||||
|
||||
#### Option 1: Merge Locally
|
||||
|
||||
```bash
|
||||
# Get main repo root for CWD safety
|
||||
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
||||
cd "$MAIN_ROOT"
|
||||
|
||||
# Merge first — verify success before removing anything
|
||||
git checkout <base-branch>
|
||||
git pull
|
||||
git merge <feature-branch>
|
||||
|
||||
# Verify tests on merged result
|
||||
<test command>
|
||||
|
||||
# Only after merge succeeds: remove worktree, then delete branch
|
||||
# (See Step 6 for worktree cleanup)
|
||||
git branch -d <feature-branch>
|
||||
```
|
||||
|
||||
Then: Cleanup worktree (Step 6)
|
||||
|
||||
#### Option 2: Push and Create PR
|
||||
|
||||
```bash
|
||||
# Push branch
|
||||
git push -u origin <feature-branch>
|
||||
|
||||
# Create PR
|
||||
gh pr create --title "<title>" --body "$(cat <<'EOF'
|
||||
## Summary
|
||||
<2-3 bullets of what changed>
|
||||
|
||||
## Test Plan
|
||||
- [ ] <verification steps>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
**Do NOT clean up worktree** — user needs it alive to iterate on PR feedback.
|
||||
|
||||
#### Option 3: Keep As-Is
|
||||
|
||||
Report: "Keeping branch <name>. Worktree preserved at <path>."
|
||||
|
||||
**Don't cleanup worktree.**
|
||||
|
||||
#### Option 4: Discard
|
||||
|
||||
**Confirm first:**
|
||||
```
|
||||
This will permanently delete:
|
||||
- Branch <name>
|
||||
- All commits: <commit-list>
|
||||
- Worktree at <path>
|
||||
|
||||
Type 'discard' to confirm.
|
||||
```
|
||||
|
||||
Wait for exact confirmation.
|
||||
|
||||
If confirmed:
|
||||
```bash
|
||||
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
||||
cd "$MAIN_ROOT"
|
||||
```
|
||||
|
||||
Then: Cleanup worktree (Step 6), then force-delete branch:
|
||||
```bash
|
||||
git branch -D <feature-branch>
|
||||
```
|
||||
|
||||
### Step 6: Cleanup Workspace
|
||||
|
||||
**Only runs for Options 1 and 4.** Options 2 and 3 always preserve the worktree.
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
WORKTREE_PATH=$(git rev-parse --show-toplevel)
|
||||
```
|
||||
|
||||
**If `GIT_DIR == GIT_COMMON`:** Normal repo, no worktree to clean up. Done.
|
||||
|
||||
**If worktree path is under `.worktrees/` or `~/.config/superpowers/worktrees/`:** Superpowers created this worktree — we own cleanup.
|
||||
|
||||
```bash
|
||||
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
||||
cd "$MAIN_ROOT"
|
||||
git worktree remove "$WORKTREE_PATH"
|
||||
git worktree prune # Self-healing: clean up any stale registrations
|
||||
```
|
||||
|
||||
**Otherwise:** The host environment (harness) owns this workspace. Do NOT remove it. If your platform provides a workspace-exit tool, use it. Otherwise, leave the workspace in place.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Option | Merge | Push | Keep Worktree | Cleanup Branch |
|
||||
|--------|-------|------|---------------|----------------|
|
||||
| 1. Merge locally | yes | - | - | yes |
|
||||
| 2. Create PR | - | yes | yes | - |
|
||||
| 3. Keep as-is | - | - | yes | - |
|
||||
| 4. Discard | - | - | - | yes (force) |
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
**Skipping test verification**
|
||||
- **Problem:** Merge broken code, create failing PR
|
||||
- **Fix:** Always verify tests before offering options
|
||||
|
||||
**Open-ended questions**
|
||||
- **Problem:** "What should I do next?" is ambiguous
|
||||
- **Fix:** Present exactly 4 structured options (or 3 for detached HEAD)
|
||||
|
||||
**Cleaning up worktree for Option 2**
|
||||
- **Problem:** Remove worktree user needs for PR iteration
|
||||
- **Fix:** Only cleanup for Options 1 and 4
|
||||
|
||||
**Deleting branch before removing worktree**
|
||||
- **Problem:** `git branch -d` fails because worktree still references the branch
|
||||
- **Fix:** Merge first, remove worktree, then delete branch
|
||||
|
||||
**Running git worktree remove from inside the worktree**
|
||||
- **Problem:** Command fails silently when CWD is inside the worktree being removed
|
||||
- **Fix:** Always `cd` to main repo root before `git worktree remove`
|
||||
|
||||
**Cleaning up harness-owned worktrees**
|
||||
- **Problem:** Removing a worktree the harness created causes phantom state
|
||||
- **Fix:** Only clean up worktrees under `.worktrees/` or `~/.config/superpowers/worktrees/`
|
||||
|
||||
**No confirmation for discard**
|
||||
- **Problem:** Accidentally delete work
|
||||
- **Fix:** Require typed "discard" confirmation
|
||||
|
||||
## Red Flags
|
||||
|
||||
**Never:**
|
||||
- Proceed with failing tests
|
||||
- Merge without verifying tests on result
|
||||
- Delete work without confirmation
|
||||
- Force-push without explicit request
|
||||
- Remove a worktree before confirming merge success
|
||||
- Clean up worktrees you didn't create (provenance check)
|
||||
- Run `git worktree remove` from inside the worktree
|
||||
|
||||
**Always:**
|
||||
- Verify tests before offering options
|
||||
- Detect environment before presenting menu
|
||||
- Present exactly 4 options (or 3 for detached HEAD)
|
||||
- Get typed confirmation for Option 4
|
||||
- Clean up worktree for Options 1 & 4 only
|
||||
- `cd` to main repo root before worktree removal
|
||||
- Run `git worktree prune` after removal
|
||||
|
||||
## Integration
|
||||
|
||||
**Called by:**
|
||||
- **subagent-driven-development** (Step 7) - After all tasks complete
|
||||
- **executing-plans** (Step 5) - After all batches complete
|
||||
|
||||
**Pairs with:**
|
||||
- **using-git-worktrees** - Cleans up worktree created by that skill
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the file reads correctly**
|
||||
|
||||
Run: `wc -l skills/finishing-a-development-branch/SKILL.md`
|
||||
|
||||
Expected: Approximately 210-230 lines.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/finishing-a-development-branch/SKILL.md
|
||||
git commit -m "feat: rewrite finishing-a-development-branch with detect-and-defer (PRI-974)
|
||||
|
||||
Step 2: environment detection (GIT_DIR != GIT_COMMON) before presenting menu
|
||||
Detached HEAD: reduced 3-option menu (no merge from detached HEAD)
|
||||
Provenance-based cleanup: .worktrees/ = ours, anything else = hands off
|
||||
Bug #940: Option 2 no longer cleans up worktree
|
||||
Bug #999: merge -> verify -> remove worktree -> delete branch
|
||||
Bug #238: cd to main repo root before git worktree remove
|
||||
Stale worktree pruning after removal (git worktree prune)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Integration Updates
|
||||
|
||||
One-line changes to three files that reference `using-git-worktrees`.
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/executing-plans/SKILL.md:68`
|
||||
- Modify: `skills/subagent-driven-development/SKILL.md:268`
|
||||
- Modify: `skills/writing-plans/SKILL.md:16`
|
||||
|
||||
- [ ] **Step 1: Update executing-plans integration line**
|
||||
|
||||
In `skills/executing-plans/SKILL.md`, change line 68 from:
|
||||
|
||||
```markdown
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```markdown
|
||||
- **superpowers:using-git-worktrees** - Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update subagent-driven-development integration line**
|
||||
|
||||
In `skills/subagent-driven-development/SKILL.md`, change line 268 from:
|
||||
|
||||
```markdown
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```markdown
|
||||
- **superpowers:using-git-worktrees** - Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Update writing-plans context line**
|
||||
|
||||
In `skills/writing-plans/SKILL.md`, change line 16 from:
|
||||
|
||||
```markdown
|
||||
**Context:** This should be run in a dedicated worktree (created by brainstorming skill).
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```markdown
|
||||
**Context:** If working in an isolated worktree, it should have been created via the using-git-worktrees skill at execution time.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit all three**
|
||||
|
||||
```bash
|
||||
git add skills/executing-plans/SKILL.md skills/subagent-driven-development/SKILL.md skills/writing-plans/SKILL.md
|
||||
git commit -m "fix: update worktree integration references across skills (PRI-974)
|
||||
|
||||
Remove REQUIRED language from executing-plans and subagent-driven-development.
|
||||
Consent and detection now live inside using-git-worktrees itself.
|
||||
Fix stale 'created by brainstorming' claim in writing-plans."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: End-to-End Validation
|
||||
|
||||
Verify the full rewritten skills work together. Run the existing test suite plus manual verification.
|
||||
|
||||
**Files:**
|
||||
- Read: `tests/claude-code/run-skill-tests.sh`
|
||||
- Read: `skills/using-git-worktrees/SKILL.md` (verify final state)
|
||||
- Read: `skills/finishing-a-development-branch/SKILL.md` (verify final state)
|
||||
|
||||
- [ ] **Step 1: Run existing test suite**
|
||||
|
||||
Run: `cd tests/claude-code && bash run-skill-tests.sh`
|
||||
|
||||
Expected: All existing tests pass. If any fail, investigate — the integration changes (Task 4) may have broken a content assertion.
|
||||
|
||||
- [ ] **Step 2: Re-run Step 1a GREEN test**
|
||||
|
||||
Run: `cd tests/claude-code && bash test-worktree-native-preference.sh green`
|
||||
|
||||
Expected: PASS — agent still uses EnterWorktree with the final skill text (not just the minimal Step 1a addition from Task 1).
|
||||
|
||||
- [ ] **Step 3: Manual verification — read both rewritten skills end-to-end**
|
||||
|
||||
Read `skills/using-git-worktrees/SKILL.md` and `skills/finishing-a-development-branch/SKILL.md` in their entirety. Check:
|
||||
|
||||
1. No references to old behavior (hardcoded `CLAUDE.md`, interactive directory prompt, "REQUIRED" language)
|
||||
2. Step numbering is consistent within each file
|
||||
3. Quick Reference tables match the prose
|
||||
4. Integration sections cross-reference correctly
|
||||
5. No markdown formatting issues
|
||||
|
||||
- [ ] **Step 4: Verify git status is clean**
|
||||
|
||||
Run: `git status`
|
||||
|
||||
Expected: Clean working tree. All changes committed across Tasks 1-4.
|
||||
|
||||
- [ ] **Step 5: Final commit if any fixups needed**
|
||||
|
||||
If manual verification found issues, fix them and commit:
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "fix: address review findings in worktree skill rewrite (PRI-974)"
|
||||
```
|
||||
|
||||
If no issues found, skip this step.
|
||||
@@ -0,0 +1,136 @@
|
||||
# Document Review System Design
|
||||
|
||||
## Overview
|
||||
|
||||
Add two new review stages to the superpowers workflow:
|
||||
|
||||
1. **Spec Document Review** - After brainstorming, before writing-plans
|
||||
2. **Plan Document Review** - After writing-plans, before implementation
|
||||
|
||||
Both follow the iterative loop pattern used by implementation reviews.
|
||||
|
||||
## Spec Document Reviewer
|
||||
|
||||
**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning.
|
||||
|
||||
**Location:** `skills/brainstorming/spec-document-reviewer-prompt.md`
|
||||
|
||||
**What it checks for:**
|
||||
|
||||
| Category | What to Look For |
|
||||
|----------|------------------|
|
||||
| Completeness | TODOs, placeholders, "TBD", incomplete sections |
|
||||
| Coverage | Missing error handling, edge cases, integration points |
|
||||
| Consistency | Internal contradictions, conflicting requirements |
|
||||
| Clarity | Ambiguous requirements |
|
||||
| YAGNI | Unrequested features, over-engineering |
|
||||
|
||||
**Output format:**
|
||||
```
|
||||
## Spec Review
|
||||
|
||||
**Status:** Approved | Issues Found
|
||||
|
||||
**Issues (if any):**
|
||||
- [Section X]: [issue] - [why it matters]
|
||||
|
||||
**Recommendations (advisory):**
|
||||
- [suggestions that don't block approval]
|
||||
```
|
||||
|
||||
**Review loop:** Issues found -> brainstorming agent fixes -> re-review -> repeat until approved.
|
||||
|
||||
**Dispatch mechanism:** Use the Task tool with `subagent_type: general-purpose`. The reviewer prompt template provides the full prompt. The brainstorming skill's controller dispatches the reviewer.
|
||||
|
||||
## Plan Document Reviewer
|
||||
|
||||
**Purpose:** Verify the plan is complete, matches the spec, and has proper task decomposition.
|
||||
|
||||
**Location:** `skills/writing-plans/plan-document-reviewer-prompt.md`
|
||||
|
||||
**What it checks for:**
|
||||
|
||||
| Category | What to Look For |
|
||||
|----------|------------------|
|
||||
| Completeness | TODOs, placeholders, incomplete tasks |
|
||||
| Spec Alignment | Plan covers spec requirements, no scope creep |
|
||||
| Task Decomposition | Tasks atomic, clear boundaries |
|
||||
| Task Syntax | Checkbox syntax on tasks and steps |
|
||||
| Chunk Size | Each chunk under 1000 lines |
|
||||
|
||||
**Chunk definition:** A chunk is a logical grouping of tasks within the plan document, delimited by `## Chunk N: <name>` headings. The writing-plans skill creates these boundaries based on logical phases (e.g., "Foundation", "Core Features", "Integration"). Each chunk should be self-contained enough to review independently.
|
||||
|
||||
**Spec alignment verification:** The reviewer receives both:
|
||||
1. The plan document (or current chunk)
|
||||
2. The path to the spec document for reference
|
||||
|
||||
The reviewer reads both and compares requirements coverage.
|
||||
|
||||
**Output format:** Same as spec reviewer, but scoped to the current chunk.
|
||||
|
||||
**Review process (chunk-by-chunk):**
|
||||
1. Writing-plans creates chunk N
|
||||
2. Controller dispatches plan-document-reviewer with chunk N content and spec path
|
||||
3. Reviewer reads chunk and spec, returns verdict
|
||||
4. If issues: writing-plans agent fixes chunk N, goto step 2
|
||||
5. If approved: proceed to chunk N+1
|
||||
6. Repeat until all chunks approved
|
||||
|
||||
**Dispatch mechanism:** Same as spec reviewer - Task tool with `subagent_type: general-purpose`.
|
||||
|
||||
## Updated Workflow
|
||||
|
||||
```
|
||||
brainstorming -> spec -> SPEC REVIEW LOOP -> writing-plans -> plan -> PLAN REVIEW LOOP -> implementation
|
||||
```
|
||||
|
||||
**Spec Review Loop:**
|
||||
1. Spec complete
|
||||
2. Dispatch reviewer
|
||||
3. If issues: fix -> goto 2
|
||||
4. If approved: proceed
|
||||
|
||||
**Plan Review Loop:**
|
||||
1. Chunk N complete
|
||||
2. Dispatch reviewer for chunk N
|
||||
3. If issues: fix -> goto 2
|
||||
4. If approved: next chunk or implementation
|
||||
|
||||
## Markdown Task Syntax
|
||||
|
||||
Tasks and steps use checkbox syntax:
|
||||
|
||||
```markdown
|
||||
- [ ] ### Task 1: Name
|
||||
|
||||
- [ ] **Step 1:** Description
|
||||
- File: path
|
||||
- Command: cmd
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Review loop termination:**
|
||||
- No hard iteration limit - loops continue until reviewer approves
|
||||
- If loop exceeds 5 iterations, the controller should surface this to the human for guidance
|
||||
- The human can choose to: continue iterating, approve with known issues, or abort
|
||||
|
||||
**Disagreement handling:**
|
||||
- Reviewers are advisory - they flag issues but don't block
|
||||
- If the agent believes reviewer feedback is incorrect, it should explain why in its fix
|
||||
- If disagreement persists after 3 iterations on the same issue, surface to human
|
||||
|
||||
**Malformed reviewer output:**
|
||||
- Controller should validate reviewer output has required fields (Status, Issues if applicable)
|
||||
- If malformed, re-dispatch reviewer with a note about expected format
|
||||
- After 2 malformed responses, surface to human
|
||||
|
||||
## Files to Change
|
||||
|
||||
**New files:**
|
||||
- `skills/brainstorming/spec-document-reviewer-prompt.md`
|
||||
- `skills/writing-plans/plan-document-reviewer-prompt.md`
|
||||
|
||||
**Modified files:**
|
||||
- `skills/brainstorming/SKILL.md` - add review loop after spec written
|
||||
- `skills/writing-plans/SKILL.md` - add chunk-by-chunk review loop, update task syntax examples
|
||||
@@ -0,0 +1,162 @@
|
||||
# Visual Brainstorming Refactor: Browser Displays, Terminal Commands
|
||||
|
||||
**Date:** 2026-02-19
|
||||
**Status:** Approved
|
||||
**Scope:** `lib/brainstorm-server/`, `skills/brainstorming/visual-companion.md`, `tests/brainstorm-server/`
|
||||
|
||||
## Problem
|
||||
|
||||
During visual brainstorming, Claude runs `wait-for-feedback.sh` as a background task and blocks on `TaskOutput(block=true, timeout=600s)`. This seizes the TUI entirely — the user cannot type to Claude while visual brainstorming is running. The browser becomes the only input channel.
|
||||
|
||||
Claude Code's execution model is turn-based. There is no way for Claude to listen on two channels simultaneously within a single turn. The blocking `TaskOutput` pattern was the wrong primitive — it simulates event-driven behavior the platform doesn't support.
|
||||
|
||||
## Design
|
||||
|
||||
### Core Model
|
||||
|
||||
**Browser = interactive display.** Shows mockups, lets the user click to select options. Selections are recorded server-side.
|
||||
|
||||
**Terminal = conversation channel.** Always unblocked, always available. The user talks to Claude here.
|
||||
|
||||
### The Loop
|
||||
|
||||
1. Claude writes an HTML file to the session directory
|
||||
2. Server detects it via chokidar, pushes WebSocket reload to the browser (unchanged)
|
||||
3. Claude ends its turn — tells the user to check the browser and respond in the terminal
|
||||
4. User looks at browser, optionally clicks to select an option, then types feedback in the terminal
|
||||
5. On the next turn, Claude reads `$SCREEN_DIR/.events` for the browser interaction stream (clicks, selections), merges with the terminal text
|
||||
6. Iterate or advance
|
||||
|
||||
No background tasks. No `TaskOutput` blocking. No polling scripts.
|
||||
|
||||
### Key Deletion: `wait-for-feedback.sh`
|
||||
|
||||
Deleted entirely. Its purpose was to bridge "server logs events to stdout" and "Claude needs to receive those events." The `.events` file replaces this — the server writes user interaction events directly, and Claude reads them with whatever file-reading mechanism the platform provides.
|
||||
|
||||
### Key Addition: `.events` File (Per-Screen Event Stream)
|
||||
|
||||
The server writes all user interaction events to `$SCREEN_DIR/.events`, one JSON object per line. This gives Claude the full interaction stream for the current screen — not just the final selection, but the user's exploration path (clicked A, then B, settled on C).
|
||||
|
||||
Example contents after a user explores options:
|
||||
|
||||
```jsonl
|
||||
{"type":"click","choice":"a","text":"Option A - Preset-First Wizard","timestamp":1706000101}
|
||||
{"type":"click","choice":"c","text":"Option C - Manual Config","timestamp":1706000108}
|
||||
{"type":"click","choice":"b","text":"Option B - Hybrid Approach","timestamp":1706000115}
|
||||
```
|
||||
|
||||
- Append-only within a screen. Each user event is appended as a new line.
|
||||
- The file is cleared (deleted) when chokidar detects a new HTML file (new screen pushed), preventing stale events from carrying over.
|
||||
- If the file doesn't exist when Claude reads it, no browser interaction occurred — Claude uses only the terminal text.
|
||||
- The file contains only user events (`click`, etc.) — not server lifecycle events (`server-started`, `screen-added`). This keeps it small and focused.
|
||||
- Claude can read the full stream to understand the user's exploration pattern, or just look at the last `choice` event for the final selection.
|
||||
|
||||
## Changes by File
|
||||
|
||||
### `index.js` (server)
|
||||
|
||||
**A. Write user events to `.events` file.**
|
||||
|
||||
In the WebSocket `message` handler, after logging the event to stdout: append the event as a JSON line to `$SCREEN_DIR/.events` via `fs.appendFileSync`. Only write user interaction events (those with `source: 'user-event'`), not server lifecycle events.
|
||||
|
||||
**B. Clear `.events` on new screen.**
|
||||
|
||||
In the chokidar `add` handler (new `.html` file detected), delete `$SCREEN_DIR/.events` if it exists. This is the definitive "new screen" signal — better than clearing on GET `/` which fires on every reload.
|
||||
|
||||
**C. Replace `wrapInFrame` content injection.**
|
||||
|
||||
The current regex anchors on `<div class="feedback-footer">`, which is being removed. Replace with a comment placeholder: remove the existing default content inside `#claude-content` (the `<h2>Visual Brainstorming</h2>` and subtitle paragraph) and replace with a single `<!-- CONTENT -->` marker. Content injection becomes `frameTemplate.replace('<!-- CONTENT -->', content)`. Simpler and won't break if template formatting changes.
|
||||
|
||||
### `frame-template.html` (UI frame)
|
||||
|
||||
**Remove:**
|
||||
- The `feedback-footer` div (textarea, Send button, label, `.feedback-row`)
|
||||
- Associated CSS (`.feedback-footer`, `.feedback-footer label`, `.feedback-row`, textarea and button styles within it)
|
||||
|
||||
**Add:**
|
||||
- `<!-- CONTENT -->` placeholder inside `#claude-content`, replacing the default text
|
||||
- A selection indicator bar where the footer was, with two states:
|
||||
- Default: "Click an option above, then return to the terminal"
|
||||
- After selection: "Option B selected — return to terminal to continue"
|
||||
- CSS for the indicator bar (subtle, similar visual weight to the existing header)
|
||||
|
||||
**Keep unchanged:**
|
||||
- Header bar with "Brainstorm Companion" title and connection status
|
||||
- `.main` wrapper and `#claude-content` container
|
||||
- All component CSS (`.options`, `.cards`, `.mockup`, `.split`, `.pros-cons`, placeholders, mock elements)
|
||||
- Dark/light theme variables and media query
|
||||
|
||||
### `helper.js` (client-side script)
|
||||
|
||||
**Remove:**
|
||||
- `sendToClaude()` function and the "Sent to Claude" page takeover
|
||||
- `window.send()` function (was tied to the removed Send button)
|
||||
- Form submission handler — no purpose without the feedback textarea, adds log noise
|
||||
- Input change handler — same reason
|
||||
- `pageshow` event listener (was added to fix textarea persistence — no textarea anymore)
|
||||
|
||||
**Keep:**
|
||||
- WebSocket connection, reconnect logic, event queue
|
||||
- Reload handler (`window.location.reload()` on server push)
|
||||
- `window.toggleSelect()` for selection highlighting
|
||||
- `window.selectedChoice` tracking
|
||||
- `window.brainstorm.send()` and `window.brainstorm.choice()` — these are distinct from the removed `window.send()`. They call `sendEvent` which logs to the server via WebSocket. Useful for custom full-document pages.
|
||||
|
||||
**Narrow:**
|
||||
- Click handler: capture only `[data-choice]` clicks, not all buttons/links. The broad capture was needed when the browser was a feedback channel; now it's just for selection tracking.
|
||||
|
||||
**Add:**
|
||||
- On `data-choice` click, update the selection indicator bar text to show which option was selected.
|
||||
|
||||
**Remove from `window.brainstorm` API:**
|
||||
- `brainstorm.sendToClaude` — no longer exists
|
||||
|
||||
### `visual-companion.md` (skill instructions)
|
||||
|
||||
**Rewrite "The Loop" section** to the non-blocking flow described above. Remove all references to:
|
||||
- `wait-for-feedback.sh`
|
||||
- `TaskOutput` blocking
|
||||
- Timeout/retry logic (600s timeout, 30-minute cap)
|
||||
- "User Feedback Format" section describing `send-to-claude` JSON
|
||||
|
||||
**Replace with:**
|
||||
- The new loop (write HTML → end turn → user responds in terminal → read `.events` → iterate)
|
||||
- `.events` file format documentation
|
||||
- Guidance that the terminal message is the primary feedback; `.events` provides the full browser interaction stream for additional context
|
||||
|
||||
**Keep:**
|
||||
- Server startup/shutdown instructions
|
||||
- Content fragment vs full document guidance
|
||||
- CSS class reference and available components
|
||||
- Design tips (scale fidelity to the question, 2-4 options per screen, etc.)
|
||||
|
||||
### `wait-for-feedback.sh`
|
||||
|
||||
**Deleted entirely.**
|
||||
|
||||
### `tests/brainstorm-server/server.test.js`
|
||||
|
||||
Tests that need updating:
|
||||
- Test asserting `feedback-footer` presence in fragment responses — update to assert the selection indicator bar or `<!-- CONTENT -->` replacement
|
||||
- Test asserting `helper.js` contains `send` — update to reflect narrowed API
|
||||
- Test asserting `sendToClaude` CSS variable usage — remove (function no longer exists)
|
||||
|
||||
## Platform Compatibility
|
||||
|
||||
The server code (`index.js`, `helper.js`, `frame-template.html`) is fully platform-agnostic — pure Node.js and browser JavaScript. No Claude Code-specific references. Already proven to work on Codex via background terminal interaction.
|
||||
|
||||
The skill instructions (`visual-companion.md`) are the platform-adaptive layer. Each platform's Claude uses its own tools to start the server, read `.events`, etc. The non-blocking model works naturally across platforms since it doesn't depend on any platform-specific blocking primitive.
|
||||
|
||||
## What This Enables
|
||||
|
||||
- **TUI always responsive** during visual brainstorming
|
||||
- **Mixed input** — click in browser + type in terminal, naturally merged
|
||||
- **Graceful degradation** — browser down or user doesn't open it? Terminal still works
|
||||
- **Simpler architecture** — no background tasks, no polling scripts, no timeout management
|
||||
- **Cross-platform** — same server code works on Claude Code, Codex, and any future platform
|
||||
|
||||
## What This Drops
|
||||
|
||||
- **Pure-browser feedback workflow** — user must return to the terminal to continue. The selection indicator bar guides them, but it's one extra step compared to the old click-Send-and-wait flow.
|
||||
- **Inline text feedback from browser** — the textarea is gone. All text feedback goes through the terminal. This is intentional — the terminal is a better text input channel than a small textarea in a frame.
|
||||
- **Immediate response on browser Send** — the old system had Claude respond the moment the user clicked Send. Now there's a gap while the user switches to the terminal. In practice this is seconds, and the user gets to add context in their terminal message.
|
||||
@@ -0,0 +1,118 @@
|
||||
# Zero-Dependency Brainstorm Server
|
||||
|
||||
Replace the brainstorm companion server's vendored node_modules (express, ws, chokidar — 714 tracked files) with a single zero-dependency `server.js` using only Node.js built-ins.
|
||||
|
||||
## Motivation
|
||||
|
||||
Vendoring node_modules into the git repo creates a supply chain risk: frozen dependencies don't get security patches, 714 files of third-party code are committed without audit, and modifications to vendored code look like normal commits. While the actual risk is low (localhost-only dev server), eliminating it is straightforward.
|
||||
|
||||
## Architecture
|
||||
|
||||
A single `server.js` file (~250-300 lines) using `http`, `crypto`, `fs`, and `path`. The file serves two roles:
|
||||
|
||||
- **When run directly** (`node server.js`): starts the HTTP/WebSocket server
|
||||
- **When required** (`require('./server.js')`): exports WebSocket protocol functions for unit testing
|
||||
|
||||
### WebSocket Protocol
|
||||
|
||||
Implements RFC 6455 for text frames only:
|
||||
|
||||
**Handshake:** Compute `Sec-WebSocket-Accept` from client's `Sec-WebSocket-Key` using SHA-1 + the RFC 6455 magic GUID. Return 101 Switching Protocols.
|
||||
|
||||
**Frame decoding (client to server):** Handle three masked length encodings:
|
||||
- Small: payload < 126 bytes
|
||||
- Medium: 126-65535 bytes (16-bit extended)
|
||||
- Large: > 65535 bytes (64-bit extended)
|
||||
|
||||
XOR-unmask payload using 4-byte mask key. Return `{ opcode, payload, bytesConsumed }` or `null` for incomplete buffers. Reject unmasked frames.
|
||||
|
||||
**Frame encoding (server to client):** Unmasked frames with the same three length encodings.
|
||||
|
||||
**Opcodes handled:** TEXT (0x01), CLOSE (0x08), PING (0x09), PONG (0x0A). Unrecognized opcodes get a close frame with status 1003 (Unsupported Data).
|
||||
|
||||
**Deliberately skipped:** Binary frames, fragmented messages, extensions (permessage-deflate), subprotocols. These are unnecessary for small JSON text messages between localhost clients. Extensions and subprotocols are negotiated in the handshake — by not advertising them, they are never active.
|
||||
|
||||
**Buffer accumulation:** Each connection maintains a buffer. On `data`, append and loop `decodeFrame` until it returns null or buffer is empty.
|
||||
|
||||
### HTTP Server
|
||||
|
||||
Three routes:
|
||||
|
||||
1. **`GET /`** — Serve newest `.html` from screen directory by mtime. Detect full documents vs fragments, wrap fragments in frame template, inject helper.js. Return `text/html`. When no `.html` files exist, serve a hardcoded waiting page ("Waiting for Claude to push a screen...") with helper.js injected.
|
||||
2. **`GET /files/*`** — Serve static files from screen directory with MIME type lookup from a hardcoded extension map (html, css, js, png, jpg, gif, svg, json). Return 404 if not found.
|
||||
3. **Everything else** — 404.
|
||||
|
||||
WebSocket upgrade handled via the `'upgrade'` event on the HTTP server, separate from the request handler.
|
||||
|
||||
### Configuration
|
||||
|
||||
Environment variables (all optional):
|
||||
|
||||
- `BRAINSTORM_PORT` — port to bind (default: random high port 49152-65535)
|
||||
- `BRAINSTORM_HOST` — interface to bind (default: `127.0.0.1`)
|
||||
- `BRAINSTORM_URL_HOST` — hostname for the URL in startup JSON (default: `localhost` when host is `127.0.0.1`, otherwise same as host)
|
||||
- `BRAINSTORM_DIR` — screen directory path (default: `/tmp/brainstorm`)
|
||||
|
||||
### Startup Sequence
|
||||
|
||||
1. Create `SCREEN_DIR` if it doesn't exist (`mkdirSync` recursive)
|
||||
2. Load frame template and helper.js from `__dirname`
|
||||
3. Start HTTP server on configured host/port
|
||||
4. Start `fs.watch` on `SCREEN_DIR`
|
||||
5. On successful listen, log `server-started` JSON to stdout: `{ type, port, host, url_host, url, screen_dir }`
|
||||
6. Write the same JSON to `SCREEN_DIR/.server-info` so agents can find connection details when stdout is hidden (background execution)
|
||||
|
||||
### Application-Level WebSocket Messages
|
||||
|
||||
When a TEXT frame arrives from a client:
|
||||
|
||||
1. Parse as JSON. If parsing fails, log to stderr and continue.
|
||||
2. Log to stdout as `{ source: 'user-event', ...event }`.
|
||||
3. If the event contains a `choice` property, append the JSON to `SCREEN_DIR/.events` (one line per event).
|
||||
|
||||
### File Watching
|
||||
|
||||
`fs.watch(SCREEN_DIR)` replaces chokidar. On HTML file events:
|
||||
|
||||
- On new file (`rename` event for a file that exists): delete `.events` file if present (`unlinkSync`), log `screen-added` to stdout as JSON
|
||||
- On file change (`change` event): log `screen-updated` to stdout as JSON (do NOT clear `.events`)
|
||||
- Both events: send `{ type: 'reload' }` to all connected WebSocket clients
|
||||
|
||||
Debounce per-filename with ~100ms timeout to prevent duplicate events (common on macOS and Linux).
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Malformed JSON from WebSocket clients: log to stderr, continue
|
||||
- Unhandled opcodes: close with status 1003
|
||||
- Client disconnects: remove from broadcast set
|
||||
- `fs.watch` errors: log to stderr, continue
|
||||
- No graceful shutdown logic — shell scripts handle process lifecycle via SIGTERM
|
||||
|
||||
## What Changes
|
||||
|
||||
| Before | After |
|
||||
|---|---|
|
||||
| `index.js` + `package.json` + `package-lock.json` + 714 `node_modules` files | `server.js` (single file) |
|
||||
| express, ws, chokidar dependencies | none |
|
||||
| No static file serving | `/files/*` serves from screen directory |
|
||||
|
||||
## What Stays the Same
|
||||
|
||||
- `helper.js` — no changes
|
||||
- `frame-template.html` — no changes
|
||||
- `start-server.sh` — one-line update: `index.js` to `server.js`
|
||||
- `stop-server.sh` — no changes
|
||||
- `visual-companion.md` — no changes
|
||||
- All existing server behavior and external contract
|
||||
|
||||
## Platform Compatibility
|
||||
|
||||
- `server.js` uses only cross-platform Node built-ins
|
||||
- `fs.watch` is reliable for single flat directories on macOS, Linux, and Windows
|
||||
- Shell scripts require bash (Git Bash on Windows, which is required for Claude Code)
|
||||
|
||||
## Testing
|
||||
|
||||
**Unit tests** (`ws-protocol.test.js`): Test WebSocket frame encoding/decoding, handshake computation, and protocol edge cases directly by requiring `server.js` exports.
|
||||
|
||||
**Integration tests** (`server.test.js`): Test full server behavior — HTTP serving, WebSocket communication, file watching, brainstorming workflow. Uses `ws` npm package as a test-only client dependency (not shipped to end users).
|
||||
@@ -0,0 +1,244 @@
|
||||
# Codex App Compatibility: Worktree and Finishing Skill Adaptation
|
||||
|
||||
Make superpowers skills work in the Codex App's sandboxed worktree environment without breaking existing Claude Code or Codex CLI behavior.
|
||||
|
||||
**Ticket:** PRI-823
|
||||
|
||||
## Motivation
|
||||
|
||||
The Codex App runs agents inside git worktrees it manages — detached HEAD, located under `$CODEX_HOME/worktrees/`, with a Seatbelt sandbox that blocks `git checkout -b`, `git push`, and network access. Three superpowers skills assume unrestricted git access: `using-git-worktrees` creates manual worktrees with named branches, `finishing-a-development-branch` merges/pushes/PRs by branch name, and `subagent-driven-development` requires both.
|
||||
|
||||
The Codex CLI (open source terminal tool) does NOT have this conflict — it has no built-in worktree management. Our manual worktree approach fills an isolation gap there. The problem is specifically with the Codex App.
|
||||
|
||||
## Empirical Findings
|
||||
|
||||
Tested in the Codex App on 2026-03-23:
|
||||
|
||||
| Operation | workspace-write sandbox | Full access sandbox |
|
||||
|---|---|---|
|
||||
| `git add` | Works | Works |
|
||||
| `git commit` | Works | Works |
|
||||
| `git checkout -b` | **Blocked** (can't write `.git/refs/heads/`) | Works |
|
||||
| `git push` | **Blocked** (network + `.git/refs/remotes/`) | Works |
|
||||
| `gh pr create` | **Blocked** (network) | Works |
|
||||
| `git status/diff/log` | Works | Works |
|
||||
|
||||
Additional findings:
|
||||
- `spawn_agent` subagents **share** the parent thread's filesystem (confirmed via marker file test)
|
||||
- "Create branch" button appears in the App header regardless of which branch the worktree was started from
|
||||
- The App's native finishing flow: Create branch → Commit modal → Commit and push / Commit and create PR
|
||||
- `network_access = true` config is silently broken on macOS (issue #10390)
|
||||
|
||||
## Design: Read-Only Environment Detection
|
||||
|
||||
Three read-only git commands detect the environment without side effects:
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
Two signals derived:
|
||||
|
||||
- **IN_LINKED_WORKTREE:** `GIT_DIR != GIT_COMMON` — the agent is in a worktree created by something else (Codex App, Claude Code Agent tool, previous skill run, or the user)
|
||||
- **ON_DETACHED_HEAD:** `BRANCH` is empty — no named branch exists
|
||||
|
||||
Why `git-dir != git-common-dir` instead of checking `show-toplevel`:
|
||||
- In a normal repo, both resolve to the same `.git` directory
|
||||
- In a linked worktree, `git-dir` is `.git/worktrees/<name>` while `git-common-dir` is `.git`
|
||||
- In a submodule, both are equal — avoiding a false positive that `show-toplevel` would produce
|
||||
- Resolving via `cd && pwd -P` handles the relative-path problem (`git-common-dir` returns `.git` relative in normal repos but absolute in worktrees) and symlinks (macOS `/tmp` → `/private/tmp`)
|
||||
|
||||
### Decision Matrix
|
||||
|
||||
| Linked Worktree? | Detached HEAD? | Environment | Action |
|
||||
|---|---|---|---|
|
||||
| No | No | Claude Code / Codex CLI / normal git | Full skill behavior (unchanged) |
|
||||
| Yes | Yes | Codex App worktree (workspace-write) | Skip worktree creation; handoff payload at finish |
|
||||
| Yes | No | Codex App (Full access) or manual worktree | Skip worktree creation; full finishing flow |
|
||||
| No | Yes | Unusual (manual detached HEAD) | Create worktree normally; warn at finish |
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. `using-git-worktrees/SKILL.md` — Add Step 0 (~12 lines)
|
||||
|
||||
New section between "Overview" and "Directory Selection Process":
|
||||
|
||||
**Step 0: Check if Already in an Isolated Workspace**
|
||||
|
||||
Run the detection commands. If `GIT_DIR != GIT_COMMON`, skip worktree creation entirely. Instead:
|
||||
1. Skip to "Run Project Setup" subsection under Creation Steps — `npm install` etc. is idempotent, worth running for safety
|
||||
2. Then "Verify Clean Baseline" — run tests
|
||||
3. Report with branch state:
|
||||
- On a branch: "Already in an isolated workspace at `<path>` on branch `<name>`. Tests passing. Ready to implement."
|
||||
- Detached HEAD: "Already in an isolated workspace at `<path>` (detached HEAD, externally managed). Tests passing. Note: branch creation needed at finish time. Ready to implement."
|
||||
|
||||
If `GIT_DIR == GIT_COMMON`, proceed with the full worktree creation flow (unchanged).
|
||||
|
||||
Safety verification (.gitignore check) is skipped when Step 0 fires — irrelevant for externally-created worktrees.
|
||||
|
||||
Update the Integration section's "Called by" entries. Change the description on each from context-specific text to: "Ensures isolated workspace (creates one or verifies existing)". For example, the `subagent-driven-development` entry changes from "REQUIRED: Set up isolated workspace before starting" to "REQUIRED: Ensures isolated workspace (creates one or verifies existing)".
|
||||
|
||||
**Sandbox fallback:** If `GIT_DIR == GIT_COMMON` and the skill proceeds to Creation Steps, but `git worktree add -b` fails with a permission error (e.g., Seatbelt sandbox denial), treat this as a late-detected restricted environment. Fall back to the Step 0 "already in workspace" behavior — skip creation, run setup and baseline tests in the current directory, report accordingly.
|
||||
|
||||
After reporting in Step 0, STOP. Do not continue to Directory Selection or Creation Steps.
|
||||
|
||||
**Everything else unchanged:** Directory Selection, Safety Verification, Creation Steps, Project Setup, Baseline Tests, Quick Reference, Common Mistakes, Red Flags.
|
||||
|
||||
### 2. `finishing-a-development-branch/SKILL.md` — Add Step 1.5 + cleanup guard (~20 lines)
|
||||
|
||||
**Step 1.5: Detect Environment** (after Step 1 "Verify Tests", before Step 2 "Determine Base Branch")
|
||||
|
||||
Run the detection commands. Three paths:
|
||||
|
||||
- **Path A** skips Steps 2 and 3 entirely (no base branch or options needed).
|
||||
- **Paths B and C** proceed through Step 2 (Determine Base Branch) and Step 3 (Present Options) as normal.
|
||||
|
||||
**Path A — Externally managed worktree + detached HEAD** (`GIT_DIR != GIT_COMMON` AND `BRANCH` empty):
|
||||
|
||||
First, ensure all work is staged and committed (`git add` + `git commit`). The Codex App's finishing controls operate on committed work.
|
||||
|
||||
Then present this to the user (do NOT present the 4-option menu):
|
||||
|
||||
```
|
||||
Implementation complete. All tests passing.
|
||||
Current HEAD: <full-commit-sha>
|
||||
|
||||
This workspace is externally managed (detached HEAD).
|
||||
I cannot create branches, push, or open PRs from here.
|
||||
|
||||
⚠ These commits are on a detached HEAD. If you do not create a branch,
|
||||
they may be lost when this workspace is cleaned up.
|
||||
|
||||
If your host application provides these controls:
|
||||
- "Create branch" — to name a branch, then commit/push/PR
|
||||
- "Hand off to local" — to move changes to your local checkout
|
||||
|
||||
Suggested branch name: <ticket-id/short-description>
|
||||
Suggested commit message: <summary-of-work>
|
||||
```
|
||||
|
||||
Branch name derivation: use the ticket ID if available (e.g., `pri-823/codex-compat`), otherwise slugify the first 5 words of the plan title, otherwise omit the suggestion. Avoid including sensitive content (vulnerability descriptions, customer names) in branch names.
|
||||
|
||||
Skip to Step 5 (cleanup is a no-op for externally managed worktrees).
|
||||
|
||||
**Path B — Externally managed worktree + named branch** (`GIT_DIR != GIT_COMMON` AND `BRANCH` exists):
|
||||
|
||||
Present the 4-option menu as normal. (The Step 5 cleanup guard will re-detect the externally managed state independently.)
|
||||
|
||||
**Path C — Normal environment** (`GIT_DIR == GIT_COMMON`):
|
||||
|
||||
Present the 4-option menu as today (unchanged).
|
||||
|
||||
**Step 5 cleanup guard:**
|
||||
|
||||
Re-run the `GIT_DIR` vs `GIT_COMMON` detection at cleanup time (do not rely on earlier skill output — the finishing skill may run in a different session). If `GIT_DIR != GIT_COMMON`, skip `git worktree remove` — the host environment owns this workspace.
|
||||
|
||||
Otherwise, check and remove as today. Note: the existing Step 5 text says "For Options 1, 2, 4" but the Quick Reference table and Common Mistakes section say "Options 1 & 4 only." The new guard is added before this existing logic and does not change which options trigger cleanup.
|
||||
|
||||
**Everything else unchanged:** Options 1-4 logic, Quick Reference, Common Mistakes, Red Flags.
|
||||
|
||||
### 3. `subagent-driven-development/SKILL.md` and `executing-plans/SKILL.md` — 1 line edit each
|
||||
|
||||
Both skills have an identical Integration section line. Change from:
|
||||
```
|
||||
- superpowers:using-git-worktrees - REQUIRED: Set up isolated workspace before starting
|
||||
```
|
||||
To:
|
||||
```
|
||||
- superpowers:using-git-worktrees - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
**Everything else unchanged:** Dispatch/review loop, prompt templates, model selection, status handling, red flags.
|
||||
|
||||
### 4. `codex-tools.md` — Add environment detection docs (~15 lines)
|
||||
|
||||
Two new sections at the end:
|
||||
|
||||
**Environment Detection:**
|
||||
|
||||
```markdown
|
||||
## Environment Detection
|
||||
|
||||
Skills that create worktrees or finish branches should detect their
|
||||
environment with read-only git commands before proceeding:
|
||||
|
||||
\```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
\```
|
||||
|
||||
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
|
||||
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
|
||||
|
||||
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
|
||||
Step 1.5 for how each skill uses these signals.
|
||||
```
|
||||
|
||||
**Codex App Finishing:**
|
||||
|
||||
```markdown
|
||||
## Codex App Finishing
|
||||
|
||||
When the sandbox blocks branch/push operations (detached HEAD in an
|
||||
externally managed worktree), the agent commits all work and informs
|
||||
the user to use the App's native controls:
|
||||
|
||||
- **"Create branch"** — names the branch, then commit/push/PR via App UI
|
||||
- **"Hand off to local"** — transfers work to the user's local checkout
|
||||
|
||||
The agent can still run tests, stage files, and output suggested branch
|
||||
names, commit messages, and PR descriptions for the user to copy.
|
||||
```
|
||||
|
||||
## What Does NOT Change
|
||||
|
||||
- `implementer-prompt.md`, `spec-reviewer-prompt.md`, `code-quality-reviewer-prompt.md` — subagent prompts untouched
|
||||
- `executing-plans/SKILL.md` — only the 1-line Integration description changes (same as `subagent-driven-development`); all runtime behavior is unchanged
|
||||
- `dispatching-parallel-agents/SKILL.md` — no worktree or finishing operations
|
||||
- `.codex/INSTALL.md` — installation process unchanged
|
||||
- The 4-option finishing menu — preserved exactly for Claude Code and Codex CLI
|
||||
- The full worktree creation flow — preserved exactly for non-worktree environments
|
||||
- Subagent dispatch/review/iterate loop — unchanged (filesystem sharing confirmed)
|
||||
|
||||
## Scope Summary
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `skills/using-git-worktrees/SKILL.md` | +12 lines (Step 0) |
|
||||
| `skills/finishing-a-development-branch/SKILL.md` | +20 lines (Step 1.5 + cleanup guard) |
|
||||
| `skills/subagent-driven-development/SKILL.md` | 1 line edit |
|
||||
| `skills/executing-plans/SKILL.md` | 1 line edit |
|
||||
| `skills/using-superpowers/references/codex-tools.md` | +15 lines |
|
||||
|
||||
~50 lines added/changed across 5 files. Zero new files. Zero breaking changes.
|
||||
|
||||
## Future Considerations
|
||||
|
||||
If a third skill needs the same detection pattern, extract it into a shared `references/environment-detection.md` file (Approach B). Not needed now — only 2 skills use it.
|
||||
|
||||
## Test Plan
|
||||
|
||||
### Automated (run in Claude Code after implementation)
|
||||
|
||||
1. Normal repo detection — assert IN_LINKED_WORKTREE=false
|
||||
2. Linked worktree detection — `git worktree add` test worktree, assert IN_LINKED_WORKTREE=true
|
||||
3. Detached HEAD detection — `git checkout --detach`, assert ON_DETACHED_HEAD=true
|
||||
4. Finishing skill handoff output — verify handoff message (not 4-option menu) in restricted environment
|
||||
5. **Step 5 cleanup guard** — create a linked worktree (`git worktree add /tmp/test-cleanup -b test-cleanup`), `cd` into it, run the Step 5 cleanup detection (`GIT_DIR` vs `GIT_COMMON`), assert it would NOT call `git worktree remove`. Then `cd` back to main repo, run the same detection, assert it WOULD call `git worktree remove`. Clean up test worktree afterward.
|
||||
|
||||
### Manual Codex App Tests (5 tests)
|
||||
|
||||
1. Detection in Worktree thread (workspace-write) — verify GIT_DIR != GIT_COMMON, empty branch
|
||||
2. Detection in Worktree thread (Full access) — same detection, different sandbox behavior
|
||||
3. Finishing skill handoff format — verify agent emits handoff payload, not 4-option menu
|
||||
4. Full lifecycle — detection → commit → finishing detection → correct behavior → cleanup
|
||||
5. **Sandbox fallback in Local thread** — Start a Codex App **Local thread** (workspace-write sandbox). Prompt: "Use the superpowers skill `using-git-worktrees` to set up an isolated workspace for implementing a small change." Pre-check: `git checkout -b test-sandbox-check` should fail with `Operation not permitted`. Expected: the skill detects `GIT_DIR == GIT_COMMON` (normal repo), attempts `git worktree add -b`, hits Seatbelt denial, falls back to Step 0 "already in workspace" behavior — runs setup, baseline tests, reports ready from current directory. Pass: agent recovers gracefully without cryptic error messages. Fail: agent prints raw Seatbelt error, retries, or gives up with confusing output.
|
||||
|
||||
### Regression
|
||||
|
||||
- Existing Claude Code skill-triggering tests still pass
|
||||
- Existing subagent-driven-development integration tests still pass
|
||||
- Normal Claude Code session: full worktree creation + 4-option finishing still works
|
||||
342
docs/superpowers/specs/2026-04-06-worktree-rototill-design.md
Normal file
342
docs/superpowers/specs/2026-04-06-worktree-rototill-design.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# Worktree Rototill: Detect-and-Defer
|
||||
|
||||
**Date:** 2026-04-06
|
||||
**Status:** Draft
|
||||
**Ticket:** PRI-974
|
||||
**Subsumes:** PRI-823 (Codex App compatibility)
|
||||
|
||||
## Problem
|
||||
|
||||
Superpowers is opinionated about worktree management — specific paths (`.worktrees/<branch>`), specific commands (`git worktree add`), specific cleanup (`git worktree remove`). Meanwhile, Claude Code, Codex App, Gemini CLI, and Cursor all provide native worktree support with their own paths, lifecycle management, and cleanup.
|
||||
|
||||
This creates three failure modes:
|
||||
|
||||
1. **Duplication** — on Claude Code, the skill does what `EnterWorktree`/`ExitWorktree` already does
|
||||
2. **Conflict** — on Codex App, the skill tries to create worktrees inside an already-managed worktree
|
||||
3. **Phantom state** — skill-created worktrees at `.worktrees/` are invisible to the harness; harness-created worktrees at `.claude/worktrees/` are invisible to the skill
|
||||
|
||||
For harnesses without native support (Codex CLI, OpenCode, Copilot standalone), superpowers fills a real gap. The skill shouldn't go away — it should get out of the way when native support exists.
|
||||
|
||||
## Goals
|
||||
|
||||
1. Defer to native harness worktree systems when they exist
|
||||
2. Continue providing worktree support for harnesses that lack it
|
||||
3. Fix three known bugs in finishing-a-development-branch (#940, #999, #238)
|
||||
4. Make worktree creation opt-in rather than mandatory (#991)
|
||||
5. Replace hardcoded `CLAUDE.md` references with platform-neutral language (#1049)
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Per-worktree environment conventions (`.worktree-env.sh`, port offsetting) — Phase 4
|
||||
- PreToolUse hooks for path enforcement — Phase 4
|
||||
- Multi-repo worktree documentation — Phase 4
|
||||
- Brainstorming checklist changes for worktrees — Phase 4
|
||||
- `.superpowers-session.json` metadata tracking (interesting PR #997 idea, not needed for v1)
|
||||
- Hooks symlinking into worktrees (PR #965 idea, separate concern)
|
||||
|
||||
## Design Principles
|
||||
|
||||
### Detect state, not platform
|
||||
|
||||
Use `GIT_DIR != GIT_COMMON` to determine "am I already in a worktree?" rather than sniffing environment variables to identify the harness. This is a stable git primitive (since git 2.5, 2015), works universally across all harnesses, and requires zero maintenance as new harnesses appear.
|
||||
|
||||
### Declarative intent, prescriptive fallback
|
||||
|
||||
The skill describes the goal ("ensure work happens in an isolated workspace") and defers to native tools when available. It prescribes specific git commands only as a fallback for harnesses without native worktree support. Step 1a comes first and names native tools explicitly (`EnterWorktree`, `WorktreeCreate`, `/worktree`, `--worktree`); Step 1b comes second with the git fallback. The original spec kept Step 1a abstract ("you know your own toolkit"), but TDD proved that agents anchor on Step 1b's concrete commands when Step 1a is too vague. Explicit tool naming and a consent-authorization bridge were required to make the preference reliable.
|
||||
|
||||
### Provenance-based ownership
|
||||
|
||||
Whoever creates the worktree owns its cleanup. If the harness created it, superpowers doesn't touch it. If superpowers created it (via git fallback), superpowers cleans it up. The heuristic: if the worktree lives under `.worktrees/` or `~/.config/superpowers/worktrees/`, superpowers owns it. Anything else (`.claude/worktrees/`, `~/.codex/worktrees/`, `.gemini/worktrees/`) belongs to the harness.
|
||||
|
||||
## Design
|
||||
|
||||
### 1. `using-git-worktrees` SKILL.md Rewrite
|
||||
|
||||
The skill gains three new steps before creation and simplifies the creation flow.
|
||||
|
||||
#### Step 0: Detect Existing Isolation
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
Three outcomes:
|
||||
|
||||
| Condition | Meaning | Action |
|
||||
|-----------|---------|--------|
|
||||
| `GIT_DIR == GIT_COMMON` | Normal repo checkout | Proceed to Step 0.5 |
|
||||
| `GIT_DIR != GIT_COMMON`, named branch | Already in a linked worktree | Skip to Step 3 (project setup). Report: "Already in isolated workspace at `<path>` on branch `<name>`." |
|
||||
| `GIT_DIR != GIT_COMMON`, detached HEAD | Externally managed worktree (e.g., Codex App sandbox) | Skip to Step 3. Report: "Already in isolated workspace at `<path>` (detached HEAD, externally managed)." |
|
||||
|
||||
Step 0 does not care who created the worktree or which harness is running. A worktree is a worktree regardless of origin.
|
||||
|
||||
**Submodule guard:** `GIT_DIR != GIT_COMMON` is also true inside git submodules. Before concluding "already in a worktree," check that we're not in a submodule:
|
||||
|
||||
```bash
|
||||
# If this returns a path, we're in a submodule, not a worktree
|
||||
git rev-parse --show-superproject-working-tree 2>/dev/null
|
||||
```
|
||||
|
||||
If in a submodule, treat as `GIT_DIR == GIT_COMMON` (proceed to Step 0.5).
|
||||
|
||||
#### Step 0.5: Consent
|
||||
|
||||
When Step 0 finds no existing isolation (`GIT_DIR == GIT_COMMON`), ask before creating:
|
||||
|
||||
> "Would you like me to set up an isolated worktree? This protects your current branch from changes. (y/n)"
|
||||
|
||||
If yes, proceed to Step 1. If no, work in place — skip to Step 3 with no worktree.
|
||||
|
||||
This step is skipped entirely when Step 0 detects existing isolation (no point asking about what already exists).
|
||||
|
||||
#### Step 1a: Native Tools (preferred)
|
||||
|
||||
> The user has asked for an isolated workspace (Step 0 consent). Check your available tools — do you have `EnterWorktree`, `WorktreeCreate`, a `/worktree` command, or a `--worktree` flag? If YES: the user's consent to create a worktree is your authorization to use it. Use it now and skip to Step 3.
|
||||
|
||||
After using a native tool, skip to Step 3 (project setup).
|
||||
|
||||
**Design note — TDD revision:** The original spec used a deliberately short, abstract Step 1a ("You know your own toolkit — the skill does not need to name specific tools"). TDD validation disproved this: agents anchored on Step 1b's concrete git commands and ignored the abstract guidance (2/6 pass rate). Three changes fixed it (50/50 pass rate across GREEN and PRESSURE tests):
|
||||
|
||||
1. **Explicit tool naming** — listing `EnterWorktree`, `WorktreeCreate`, `/worktree`, `--worktree` by name transforms the decision from interpretation ("do I have a native tool?") into factual lookup ("is `EnterWorktree` in my tool list?"). Agents on platforms without these tools simply check, find nothing, and fall through to Step 1b. No false positives observed.
|
||||
2. **Consent bridge** — "the user's consent to create a worktree is your authorization to use it" directly addresses `EnterWorktree`'s tool-level guardrail ("ONLY when user explicitly asks"). Tool descriptions override skill instructions (Claude Code #29950), so the skill must frame user consent as the authorization the tool requires.
|
||||
3. **Red Flag entry** — naming the specific anti-pattern ("Use `git worktree add` when you have a native worktree tool — this is the #1 mistake") in the Red Flags section.
|
||||
|
||||
File splitting (Step 1b in a separate skill) was tested and proven unnecessary. The anchoring problem is solved by the quality of Step 1a's text, not by physical separation of git commands. Control tests with the full 240-line skill (all git commands visible) passed 20/20.
|
||||
|
||||
#### Step 1b: Git Worktree Fallback
|
||||
|
||||
When no native tool is available, create a worktree manually.
|
||||
|
||||
**Directory selection** (priority order):
|
||||
1. Check for existing `.worktrees/` or `worktrees/` directory — if found, use it. If both exist, `.worktrees/` wins.
|
||||
2. Check for existing `~/.config/superpowers/worktrees/<project>/` directory — if found, use it (backward compatibility with legacy global path).
|
||||
3. Check the project's agent instruction file (CLAUDE.md, GEMINI.md, AGENTS.md, .cursorrules, or equivalent) for a worktree directory preference.
|
||||
4. Default to `.worktrees/`.
|
||||
|
||||
No interactive directory selection prompt. The global path (`~/.config/superpowers/worktrees/`) is no longer offered as a choice to new users, but existing worktrees at that location are detected and used for backward compatibility.
|
||||
|
||||
**Safety verification** (project-local directories only):
|
||||
|
||||
```bash
|
||||
git check-ignore -q .worktrees 2>/dev/null
|
||||
```
|
||||
|
||||
If not ignored, add to `.gitignore` and commit before proceeding.
|
||||
|
||||
**Create:**
|
||||
|
||||
```bash
|
||||
git worktree add "$path" -b "$BRANCH_NAME"
|
||||
cd "$path"
|
||||
```
|
||||
|
||||
**Hooks awareness:** Git worktrees do not inherit the parent repo's hooks directory. After creating a worktree via 1b, symlink the hooks directory from the main repo if one exists:
|
||||
|
||||
```bash
|
||||
if [ -d "$MAIN_ROOT/.git/hooks" ]; then
|
||||
ln -sf "$MAIN_ROOT/.git/hooks" "$path/.git/hooks"
|
||||
fi
|
||||
```
|
||||
|
||||
This prevents pre-commit checks, linters, and other hooks from silently stopping when work moves to a worktree. (Idea from PR #965.)
|
||||
|
||||
**Sandbox fallback:** If `git worktree add` fails with a permission error, treat as a restricted environment. Skip creation, work in current directory, proceed to Step 3.
|
||||
|
||||
**Step numbering note:** The current skill has Steps 1-4 as a flat list. This redesign uses 0, 0.5, 1a, 1b, 3, 4. There is no Step 2 — it was the old monolithic "Create Isolated Workspace" which is now split into the 1a/1b structure. The implementation should renumber cleanly (e.g., 0 → "Step 0: Detect", 0.5 → within Step 0's flow, 1a/1b → "Step 1", 3 → "Step 2", 4 → "Step 3") or keep the current numbering with a note. Implementer's choice.
|
||||
|
||||
#### Steps 3-4: Project Setup and Baseline Tests (unchanged)
|
||||
|
||||
Regardless of which path created the workspace (Step 0 detected existing, Step 1a native tool, Step 1b git fallback, or no worktree at all), execution converges:
|
||||
|
||||
- **Step 3:** Auto-detect and run project setup (`npm install`, `cargo build`, `pip install`, `go mod download`, etc.)
|
||||
- **Step 4:** Run the test suite. If tests fail, report failures and ask whether to proceed.
|
||||
|
||||
### 2. `finishing-a-development-branch` SKILL.md Rewrite
|
||||
|
||||
The finishing skill gains environment detection and fixes three bugs.
|
||||
|
||||
#### Step 1: Verify Tests (unchanged)
|
||||
|
||||
Run the project's test suite. If tests fail, stop. Don't offer completion options.
|
||||
|
||||
#### Step 1.5: Detect Environment (new)
|
||||
|
||||
Re-run the same detection as Step 0 in creation:
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
```
|
||||
|
||||
Three paths:
|
||||
|
||||
| State | Menu | Cleanup |
|
||||
|-------|------|---------|
|
||||
| `GIT_DIR == GIT_COMMON` (normal repo) | Standard 4 options | No worktree to clean up |
|
||||
| `GIT_DIR != GIT_COMMON`, named branch | Standard 4 options | Provenance-based (see Step 5) |
|
||||
| `GIT_DIR != GIT_COMMON`, detached HEAD | Reduced menu: push as new branch + PR, keep as-is, discard | No merge options (can't merge from detached HEAD) |
|
||||
|
||||
#### Step 2: Determine Base Branch (unchanged)
|
||||
|
||||
#### Step 3: Present Options
|
||||
|
||||
**Normal repo and named-branch worktree:**
|
||||
|
||||
1. Merge back to `<base-branch>` locally
|
||||
2. Push and create a Pull Request
|
||||
3. Keep the branch as-is (I'll handle it later)
|
||||
4. Discard this work
|
||||
|
||||
**Detached HEAD:**
|
||||
|
||||
1. Push as new branch and create a Pull Request
|
||||
2. Keep as-is (I'll handle it later)
|
||||
3. Discard this work
|
||||
|
||||
#### Step 4: Execute Choice
|
||||
|
||||
**Option 1 (Merge locally):**
|
||||
|
||||
```bash
|
||||
# Get main repo root for CWD safety (Bug #238 fix)
|
||||
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
||||
cd "$MAIN_ROOT"
|
||||
|
||||
# Merge first, verify success before removing anything
|
||||
git checkout <base-branch>
|
||||
git pull
|
||||
git merge <feature-branch>
|
||||
<run tests>
|
||||
|
||||
# Only after merge succeeds: remove worktree, then delete branch (Bug #999 fix)
|
||||
git worktree remove "$WORKTREE_PATH" # only if superpowers owns it
|
||||
git branch -d <feature-branch>
|
||||
```
|
||||
|
||||
The order is critical: merge → verify → remove worktree → delete branch. The old skill deleted the branch before removing the worktree (which fails because the worktree still references the branch). The naive fix of removing the worktree first is also wrong — if the merge then fails, the working directory is gone and changes are lost.
|
||||
|
||||
**Option 2 (Create PR):**
|
||||
|
||||
Push branch, create PR. Do NOT clean up worktree — user needs it for PR iteration. (Bug #940 fix: remove contradictory "Then: Cleanup worktree" prose.)
|
||||
|
||||
**Option 3 (Keep as-is):** No action.
|
||||
|
||||
**Option 4 (Discard):** Require typed "discard" confirmation. Then remove worktree (if superpowers owns it), force-delete branch.
|
||||
|
||||
#### Step 5: Cleanup (updated)
|
||||
|
||||
```
|
||||
if GIT_DIR == GIT_COMMON:
|
||||
# Normal repo, no worktree to clean up
|
||||
done
|
||||
|
||||
if worktree path is under .worktrees/ or ~/.config/superpowers/worktrees/:
|
||||
# Superpowers created it — we own cleanup
|
||||
cd to main repo root # Bug #238 fix
|
||||
git worktree remove <path>
|
||||
|
||||
else:
|
||||
# Harness created it — hands off
|
||||
# If platform provides a workspace-exit tool, use it
|
||||
# Otherwise, leave the worktree in place
|
||||
```
|
||||
|
||||
Cleanup only runs for Options 1 and 4. Options 2 and 3 always preserve the worktree. (Bug #940 fix.)
|
||||
|
||||
**Stale worktree pruning:** After any `git worktree remove`, run `git worktree prune` as a self-healing step. Worktree directories can get deleted out-of-band (e.g., by harness cleanup, manual `rm`, or `.claude/` cleanup), leaving stale registrations that cause confusing errors. One line, prevents silent rot. (Idea from PR #1072.)
|
||||
|
||||
### 3. Integration Updates
|
||||
|
||||
#### `subagent-driven-development` and `executing-plans`
|
||||
|
||||
Both currently list `using-git-worktrees` as REQUIRED in their integration sections. Change to:
|
||||
|
||||
> `using-git-worktrees` — Ensures isolated workspace (creates one or verifies existing)
|
||||
|
||||
The skill itself now handles consent (Step 0.5) and detection (Step 0), so calling skills don't need to gate or prompt.
|
||||
|
||||
#### `writing-plans`
|
||||
|
||||
Remove the stale claim "should be run in a dedicated worktree (created by brainstorming skill)." Brainstorming is a design skill and does not create worktrees. The worktree prompt happens at execution time via `using-git-worktrees`.
|
||||
|
||||
### 4. Platform-Neutral Instruction File References
|
||||
|
||||
All instances of hardcoded `CLAUDE.md` in worktree-related skills are replaced with:
|
||||
|
||||
> "your project's agent instruction file (CLAUDE.md, GEMINI.md, AGENTS.md, .cursorrules, or equivalent)"
|
||||
|
||||
This applies to directory preference checks in Step 1b.
|
||||
|
||||
## Bug Fixes (bundled)
|
||||
|
||||
| Bug | Problem | Fix | Location |
|
||||
|-----|---------|-----|----------|
|
||||
| #940 | Option 2 prose says "Then: Cleanup worktree (Step 5)" but quick reference says keep it. Step 5 says "For Options 1, 2, 4" but Common Mistakes says "Options 1 and 4 only." | Remove cleanup from Option 2. Step 5 applies to Options 1 and 4 only. | finishing SKILL.md |
|
||||
| #999 | Option 1 deletes branch before removing worktree. `git branch -d` can fail because worktree still references the branch. | Reorder to: merge → verify tests → remove worktree → delete branch. Merge must succeed before anything is removed. | finishing SKILL.md |
|
||||
| #238 | `git worktree remove` fails silently if CWD is inside the worktree being removed. | Add CWD guard: `cd` to main repo root before `git worktree remove`. | finishing SKILL.md |
|
||||
|
||||
## Issues Resolved
|
||||
|
||||
| Issue | Resolution |
|
||||
|-------|-----------|
|
||||
| #940 | Direct fix (Bug #940) |
|
||||
| #991 | Opt-in consent in Step 0.5 |
|
||||
| #918 | Step 0 detection + Step 1.5 finishing detection |
|
||||
| #1009 | Resolved by Step 1a — agents use native tools (e.g., `EnterWorktree`) which create at harness-native paths. Depends on Step 1a working; see Risks. |
|
||||
| #999 | Direct fix (Bug #999) |
|
||||
| #238 | Direct fix (Bug #238) |
|
||||
| #1049 | Platform-neutral instruction file references |
|
||||
| #279 | Solved by detect-and-defer — native paths respected because we don't override them |
|
||||
| #574 | **Deferred.** Nothing in this spec touches the brainstorming skill where the bug lives. Full fix (adding a worktree step to brainstorming's checklist) is Phase 4. |
|
||||
|
||||
## Risks
|
||||
|
||||
### Step 1a is the load-bearing assumption — RESOLVED
|
||||
|
||||
Step 1a — agents preferring native worktree tools over the git fallback — is the foundation the entire design rests on. If agents ignore Step 1a and fall through to Step 1b on harnesses with native support, detect-and-defer fails entirely.
|
||||
|
||||
**Status:** This risk materialized during implementation. The original abstract Step 1a ("You know your own toolkit") failed at 2/6 on Claude Code. The TDD gate worked as designed — it caught the failure before any skill files were modified, preventing a broken release. Three REFACTOR iterations identified the root causes (agent anchoring on concrete commands, tool-description guardrail overriding skill instructions) and produced a fix validated at 50/50 across GREEN and PRESSURE tests. See Step 1a design note above for details.
|
||||
|
||||
**Cross-platform validation:**
|
||||
|
||||
As of 2026-04-06, Claude Code is the only harness with an agent-callable mid-session worktree tool (`EnterWorktree`). All others either create worktrees before the agent starts (Codex App, Gemini CLI, Cursor) or have no native worktree support (Codex CLI, OpenCode). Step 1a is forward-compatible: when other harnesses add agent-callable worktree tools, agents will match them against the named examples and use them without skill changes.
|
||||
|
||||
| Harness | Current worktree model | Skill mechanism | Tested |
|
||||
|---------|----------------------|-----------------|--------|
|
||||
| Claude Code | Agent-callable `EnterWorktree` | Step 1a | 50/50 (GREEN + PRESSURE) |
|
||||
| Codex CLI | No native tool (shell only) | Step 1b git fallback | 6/6 (`codex exec`) |
|
||||
| Gemini CLI | Launch-time `--worktree` flag, no agent tool | Step 0 if launched with flag, Step 1b if not | Step 0: 1/1, Step 1b: 1/1 (`gemini -p`) |
|
||||
| Cursor Agent | User-facing `/worktree`, no agent tool | Step 0 if user activated, Step 1b if not | Step 0: 1/1, Step 1b: 1/1 (`cursor-agent -p`) |
|
||||
| Codex App | Platform-managed, detached HEAD, no agent tool | Step 0 detects existing | 1/1 simulated |
|
||||
| OpenCode | Detection only (`ctx.worktree`), no agent tool | Step 1b git fallback | Untested (no CLI access) |
|
||||
|
||||
**Residual risks:**
|
||||
1. If Anthropic changes `EnterWorktree`'s tool description to be more restrictive (e.g., "Do not use based on skill instructions"), the consent bridge breaks. Worth filing an issue requesting that the tool description accommodate skill-driven invocation.
|
||||
2. When other harnesses add agent-callable worktree tools, they may use names not in Step 1a's list. The list should be updated as new tools appear. The generic phrasing ("a worktree or workspace-isolation tool") provides some forward coverage.
|
||||
|
||||
### Provenance heuristic
|
||||
|
||||
The `.worktrees/` or `~/.config/superpowers/worktrees/` = ours, anything else = hands off` heuristic works for every current harness. If a future harness adopts `.worktrees/` as its convention, we'd have a false positive (superpowers tries to clean up a harness-owned worktree). Similarly, if a user manually runs `git worktree add .worktrees/experiment` without superpowers, we'd incorrectly claim ownership. Both are low risk — every harness uses branded paths, and manual `.worktrees/` creation is unlikely — but worth noting.
|
||||
|
||||
### Detached HEAD finishing
|
||||
|
||||
The reduced menu for detached HEAD worktrees (no merge option) is correct for Codex App's sandbox model. If a user is in detached HEAD for another reason, the reduced menu still makes sense — you genuinely can't merge from detached HEAD without creating a branch first.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Both skill files contain sections beyond the core steps that need updating during implementation:
|
||||
|
||||
- **Frontmatter** (`name`, `description`): Update to reflect detect-and-defer behavior
|
||||
- **Quick Reference tables**: Rewrite to match new step structure and bug fixes
|
||||
- **Common Mistakes sections**: Update or remove items that reference old behavior (e.g., "Skip CLAUDE.md check" is now wrong)
|
||||
- **Red Flags sections**: Update to reflect new priorities (e.g., "Never create a worktree when Step 0 detects existing isolation")
|
||||
- **Integration sections**: Update cross-references between skills
|
||||
|
||||
The spec describes *what changes*; the implementation plan will specify exact edits to these secondary sections.
|
||||
|
||||
## Future Work (not in this spec)
|
||||
|
||||
- **Phase 3 remainder:** `$TMPDIR` directory option (#666), setup docs for caching and env inheritance (#299)
|
||||
- **Phase 4:** PreToolUse hooks for path enforcement (#1040), per-worktree env conventions (#597), brainstorming checklist worktree step (#574), multi-repo documentation (#710)
|
||||
@@ -149,8 +149,8 @@ python3 tests/claude-code/analyze-token-usage.py ~/.claude/projects/<project-dir
|
||||
Session transcripts are stored in `~/.claude/projects/` with the working directory path encoded:
|
||||
|
||||
```bash
|
||||
# Example for /Users/jesse/Documents/GitHub/superpowers/superpowers
|
||||
SESSION_DIR="$HOME/.claude/projects/-Users-jesse-Documents-GitHub-superpowers-superpowers"
|
||||
# Example for /Users/yourname/Documents/GitHub/superpowers/superpowers
|
||||
SESSION_DIR="$HOME/.claude/projects/-Users-yourname-Documents-GitHub-superpowers-superpowers"
|
||||
|
||||
# Find recent sessions
|
||||
ls -lt "$SESSION_DIR"/*.jsonl | head -5
|
||||
|
||||
@@ -148,7 +148,7 @@ exit /b
|
||||
CMDBLOCK
|
||||
|
||||
# Unix shell runs from here
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT_NAME="$1"
|
||||
shift
|
||||
"${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"
|
||||
|
||||
6
gemini-extension.json
Normal file
6
gemini-extension.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "5.0.7",
|
||||
"contextFileName": "GEMINI.md"
|
||||
}
|
||||
10
hooks/hooks-cursor.json
Normal file
10
hooks/hooks-cursor.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": 1,
|
||||
"hooks": {
|
||||
"sessionStart": [
|
||||
{
|
||||
"command": "./hooks/run-hook.cmd session-start"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup|resume|clear|compact",
|
||||
"matcher": "startup|clear|compact",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start.sh"
|
||||
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start",
|
||||
"async": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,19 +1,46 @@
|
||||
: << 'CMDBLOCK'
|
||||
@echo off
|
||||
REM Polyglot wrapper: runs .sh scripts cross-platform
|
||||
REM Cross-platform polyglot wrapper for hook scripts.
|
||||
REM On Windows: cmd.exe runs the batch portion, which finds and calls bash.
|
||||
REM On Unix: the shell interprets this as a script (: is a no-op in bash).
|
||||
REM
|
||||
REM Hook scripts use extensionless filenames (e.g. "session-start" not
|
||||
REM "session-start.sh") so Claude Code's Windows auto-detection -- which
|
||||
REM prepends "bash" to any command containing .sh -- doesn't interfere.
|
||||
REM
|
||||
REM Usage: run-hook.cmd <script-name> [args...]
|
||||
REM The script should be in the same directory as this wrapper
|
||||
|
||||
if "%~1"=="" (
|
||||
echo run-hook.cmd: missing script name >&2
|
||||
exit /b 1
|
||||
)
|
||||
"C:\Program Files\Git\bin\bash.exe" -l "%~dp0%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
||||
exit /b
|
||||
|
||||
set "HOOK_DIR=%~dp0"
|
||||
|
||||
REM Try Git for Windows bash in standard locations
|
||||
if exist "C:\Program Files\Git\bin\bash.exe" (
|
||||
"C:\Program Files\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
||||
exit /b %ERRORLEVEL%
|
||||
)
|
||||
if exist "C:\Program Files (x86)\Git\bin\bash.exe" (
|
||||
"C:\Program Files (x86)\Git\bin\bash.exe" "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
||||
exit /b %ERRORLEVEL%
|
||||
)
|
||||
|
||||
REM Try bash on PATH (e.g. user-installed Git Bash, MSYS2, Cygwin)
|
||||
where bash >nul 2>nul
|
||||
if %ERRORLEVEL% equ 0 (
|
||||
bash "%HOOK_DIR%%~1" %2 %3 %4 %5 %6 %7 %8 %9
|
||||
exit /b %ERRORLEVEL%
|
||||
)
|
||||
|
||||
REM No bash found - exit silently rather than error
|
||||
REM (plugin still works, just without SessionStart context injection)
|
||||
exit /b 0
|
||||
CMDBLOCK
|
||||
|
||||
# Unix shell runs from here
|
||||
# Unix: run the named script directly
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SCRIPT_NAME="$1"
|
||||
shift
|
||||
"${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"
|
||||
exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"
|
||||
|
||||
57
hooks/session-start
Executable file
57
hooks/session-start
Executable file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
# SessionStart hook for superpowers plugin
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Determine plugin root directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
# Check if legacy skills directory exists and build warning
|
||||
warning_message=""
|
||||
legacy_skills_dir="${HOME}/.config/superpowers/skills"
|
||||
if [ -d "$legacy_skills_dir" ]; then
|
||||
warning_message="\n\n<important-reminder>IN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move custom skills to ~/.claude/skills instead. To make this message go away, remove ~/.config/superpowers/skills</important-reminder>"
|
||||
fi
|
||||
|
||||
# Read using-superpowers content
|
||||
using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill")
|
||||
|
||||
# Escape string for JSON embedding using bash parameter substitution.
|
||||
# Each ${s//old/new} is a single C-level pass - orders of magnitude
|
||||
# faster than the character-by-character loop this replaces.
|
||||
escape_for_json() {
|
||||
local s="$1"
|
||||
s="${s//\\/\\\\}"
|
||||
s="${s//\"/\\\"}"
|
||||
s="${s//$'\n'/\\n}"
|
||||
s="${s//$'\r'/\\r}"
|
||||
s="${s//$'\t'/\\t}"
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
using_superpowers_escaped=$(escape_for_json "$using_superpowers_content")
|
||||
warning_escaped=$(escape_for_json "$warning_message")
|
||||
session_context="<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>"
|
||||
|
||||
# Output context injection as JSON.
|
||||
# Cursor hooks expect additional_context (snake_case).
|
||||
# Claude Code hooks expect hookSpecificOutput.additionalContext (nested).
|
||||
# Copilot CLI (v1.0.11+) and others expect additionalContext (top-level, SDK standard).
|
||||
# Claude Code reads BOTH additional_context and hookSpecificOutput without
|
||||
# deduplication, so we must emit only the field the current platform consumes.
|
||||
#
|
||||
# Uses printf instead of heredoc to work around bash 5.3+ heredoc hang.
|
||||
# See: https://github.com/obra/superpowers/issues/571
|
||||
if [ -n "${CURSOR_PLUGIN_ROOT:-}" ]; then
|
||||
# Cursor sets CURSOR_PLUGIN_ROOT (may also set CLAUDE_PLUGIN_ROOT)
|
||||
printf '{\n "additional_context": "%s"\n}\n' "$session_context"
|
||||
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -z "${COPILOT_CLI:-}" ]; then
|
||||
# Claude Code sets CLAUDE_PLUGIN_ROOT without COPILOT_CLI
|
||||
printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$session_context"
|
||||
else
|
||||
# Copilot CLI (sets COPILOT_CLI=1) or unknown platform — SDK standard format
|
||||
printf '{\n "additionalContext": "%s"\n}\n' "$session_context"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# SessionStart hook for superpowers plugin
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Determine plugin root directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
||||
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
# Check if legacy skills directory exists and build warning
|
||||
warning_message=""
|
||||
legacy_skills_dir="${HOME}/.config/superpowers/skills"
|
||||
if [ -d "$legacy_skills_dir" ]; then
|
||||
warning_message="\n\n<important-reminder>IN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move custom skills to ~/.claude/skills instead. To make this message go away, remove ~/.config/superpowers/skills</important-reminder>"
|
||||
fi
|
||||
|
||||
# Read using-superpowers content
|
||||
using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill")
|
||||
|
||||
# Escape outputs for JSON using pure bash
|
||||
escape_for_json() {
|
||||
local input="$1"
|
||||
local output=""
|
||||
local i char
|
||||
for (( i=0; i<${#input}; i++ )); do
|
||||
char="${input:$i:1}"
|
||||
case "$char" in
|
||||
$'\\') output+='\\' ;;
|
||||
'"') output+='\"' ;;
|
||||
$'\n') output+='\n' ;;
|
||||
$'\r') output+='\r' ;;
|
||||
$'\t') output+='\t' ;;
|
||||
*) output+="$char" ;;
|
||||
esac
|
||||
done
|
||||
printf '%s' "$output"
|
||||
}
|
||||
|
||||
using_superpowers_escaped=$(escape_for_json "$using_superpowers_content")
|
||||
warning_escaped=$(escape_for_json "$warning_message")
|
||||
|
||||
# Output context injection as JSON
|
||||
cat <<EOF
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": "<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
@@ -1,208 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
/**
|
||||
* Extract YAML frontmatter from a skill file.
|
||||
* Current format:
|
||||
* ---
|
||||
* name: skill-name
|
||||
* description: Use when [condition] - [what it does]
|
||||
* ---
|
||||
*
|
||||
* @param {string} filePath - Path to SKILL.md file
|
||||
* @returns {{name: string, description: string}}
|
||||
*/
|
||||
function extractFrontmatter(filePath) {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
let inFrontmatter = false;
|
||||
let name = '';
|
||||
let description = '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim() === '---') {
|
||||
if (inFrontmatter) break;
|
||||
inFrontmatter = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFrontmatter) {
|
||||
const match = line.match(/^(\w+):\s*(.*)$/);
|
||||
if (match) {
|
||||
const [, key, value] = match;
|
||||
switch (key) {
|
||||
case 'name':
|
||||
name = value.trim();
|
||||
break;
|
||||
case 'description':
|
||||
description = value.trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { name, description };
|
||||
} catch (error) {
|
||||
return { name: '', description: '' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all SKILL.md files in a directory recursively.
|
||||
*
|
||||
* @param {string} dir - Directory to search
|
||||
* @param {string} sourceType - 'personal' or 'superpowers' for namespacing
|
||||
* @param {number} maxDepth - Maximum recursion depth (default: 3)
|
||||
* @returns {Array<{path: string, name: string, description: string, sourceType: string}>}
|
||||
*/
|
||||
function findSkillsInDir(dir, sourceType, maxDepth = 3) {
|
||||
const skills = [];
|
||||
|
||||
if (!fs.existsSync(dir)) return skills;
|
||||
|
||||
function recurse(currentDir, depth) {
|
||||
if (depth > maxDepth) return;
|
||||
|
||||
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentDir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Check for SKILL.md in this directory
|
||||
const skillFile = path.join(fullPath, 'SKILL.md');
|
||||
if (fs.existsSync(skillFile)) {
|
||||
const { name, description } = extractFrontmatter(skillFile);
|
||||
skills.push({
|
||||
path: fullPath,
|
||||
skillFile: skillFile,
|
||||
name: name || entry.name,
|
||||
description: description || '',
|
||||
sourceType: sourceType
|
||||
});
|
||||
}
|
||||
|
||||
// Recurse into subdirectories
|
||||
recurse(fullPath, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurse(dir, 0);
|
||||
return skills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a skill name to its file path, handling shadowing
|
||||
* (personal skills override superpowers skills).
|
||||
*
|
||||
* @param {string} skillName - Name like "superpowers:brainstorming" or "my-skill"
|
||||
* @param {string} superpowersDir - Path to superpowers skills directory
|
||||
* @param {string} personalDir - Path to personal skills directory
|
||||
* @returns {{skillFile: string, sourceType: string, skillPath: string} | null}
|
||||
*/
|
||||
function resolveSkillPath(skillName, superpowersDir, personalDir) {
|
||||
// Strip superpowers: prefix if present
|
||||
const forceSuperpowers = skillName.startsWith('superpowers:');
|
||||
const actualSkillName = forceSuperpowers ? skillName.replace(/^superpowers:/, '') : skillName;
|
||||
|
||||
// Try personal skills first (unless explicitly superpowers:)
|
||||
if (!forceSuperpowers && personalDir) {
|
||||
const personalPath = path.join(personalDir, actualSkillName);
|
||||
const personalSkillFile = path.join(personalPath, 'SKILL.md');
|
||||
if (fs.existsSync(personalSkillFile)) {
|
||||
return {
|
||||
skillFile: personalSkillFile,
|
||||
sourceType: 'personal',
|
||||
skillPath: actualSkillName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Try superpowers skills
|
||||
if (superpowersDir) {
|
||||
const superpowersPath = path.join(superpowersDir, actualSkillName);
|
||||
const superpowersSkillFile = path.join(superpowersPath, 'SKILL.md');
|
||||
if (fs.existsSync(superpowersSkillFile)) {
|
||||
return {
|
||||
skillFile: superpowersSkillFile,
|
||||
sourceType: 'superpowers',
|
||||
skillPath: actualSkillName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a git repository has updates available.
|
||||
*
|
||||
* @param {string} repoDir - Path to git repository
|
||||
* @returns {boolean} - True if updates are available
|
||||
*/
|
||||
function checkForUpdates(repoDir) {
|
||||
try {
|
||||
// Quick check with 3 second timeout to avoid delays if network is down
|
||||
const output = execSync('git fetch origin && git status --porcelain=v1 --branch', {
|
||||
cwd: repoDir,
|
||||
timeout: 3000,
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
// Parse git status output to see if we're behind
|
||||
const statusLines = output.split('\n');
|
||||
for (const line of statusLines) {
|
||||
if (line.startsWith('## ') && line.includes('[behind ')) {
|
||||
return true; // We're behind remote
|
||||
}
|
||||
}
|
||||
return false; // Up to date
|
||||
} catch (error) {
|
||||
// Network down, git error, timeout, etc. - don't block bootstrap
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip YAML frontmatter from skill content, returning just the content.
|
||||
*
|
||||
* @param {string} content - Full content including frontmatter
|
||||
* @returns {string} - Content without frontmatter
|
||||
*/
|
||||
function stripFrontmatter(content) {
|
||||
const lines = content.split('\n');
|
||||
let inFrontmatter = false;
|
||||
let frontmatterEnded = false;
|
||||
const contentLines = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim() === '---') {
|
||||
if (inFrontmatter) {
|
||||
frontmatterEnded = true;
|
||||
continue;
|
||||
}
|
||||
inFrontmatter = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frontmatterEnded || !inFrontmatter) {
|
||||
contentLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
return contentLines.join('\n').trim();
|
||||
}
|
||||
|
||||
export {
|
||||
extractFrontmatter,
|
||||
findSkillsInDir,
|
||||
resolveSkillPath,
|
||||
checkForUpdates,
|
||||
stripFrontmatter
|
||||
};
|
||||
13
openclaw.plugin.json
Normal file
13
openclaw.plugin.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"id": "superpowers-openclaw",
|
||||
"name": "Superpowers for OpenClaw",
|
||||
"version": "5.0.7",
|
||||
"description": "Expose the Superpowers skill pack through OpenClaw's native plugin skill discovery.",
|
||||
"skills": [
|
||||
"./skills"
|
||||
],
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
11
package.json
Normal file
11
package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "5.0.7",
|
||||
"type": "module",
|
||||
"main": ".opencode/plugins/superpowers.js",
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./.openclaw/index.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
220
scripts/bump-version.sh
Executable file
220
scripts/bump-version.sh
Executable file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# bump-version.sh — bump version numbers across all declared files,
|
||||
# with drift detection and repo-wide audit for missed files.
|
||||
#
|
||||
# Usage:
|
||||
# bump-version.sh <new-version> Bump all declared files to new version
|
||||
# bump-version.sh --check Report current versions (detect drift)
|
||||
# bump-version.sh --audit Check + grep repo for old version strings
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CONFIG="$REPO_ROOT/.version-bump.json"
|
||||
|
||||
if [[ ! -f "$CONFIG" ]]; then
|
||||
echo "error: .version-bump.json not found at $CONFIG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- helpers ---
|
||||
|
||||
# Read a dotted field path from a JSON file.
|
||||
# Handles both simple ("version") and nested ("plugins.0.version") paths.
|
||||
read_json_field() {
|
||||
local file="$1" field="$2"
|
||||
# Convert dot-path to jq path: "plugins.0.version" -> .plugins[0].version
|
||||
local jq_path
|
||||
jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g')
|
||||
jq -r "$jq_path" "$file"
|
||||
}
|
||||
|
||||
# Write a dotted field path in a JSON file, preserving formatting.
|
||||
write_json_field() {
|
||||
local file="$1" field="$2" value="$3"
|
||||
local jq_path
|
||||
jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g')
|
||||
local tmp="${file}.tmp"
|
||||
jq "$jq_path = \"$value\"" "$file" > "$tmp" && mv "$tmp" "$file"
|
||||
}
|
||||
|
||||
# Read the list of declared files from config.
|
||||
# Outputs lines of "path<TAB>field"
|
||||
declared_files() {
|
||||
jq -r '.files[] | "\(.path)\t\(.field)"' "$CONFIG"
|
||||
}
|
||||
|
||||
# Read the audit exclude patterns from config.
|
||||
audit_excludes() {
|
||||
jq -r '.audit.exclude[]' "$CONFIG" 2>/dev/null
|
||||
}
|
||||
|
||||
# --- commands ---
|
||||
|
||||
cmd_check() {
|
||||
local has_drift=0
|
||||
local versions=()
|
||||
|
||||
echo "Version check:"
|
||||
echo ""
|
||||
|
||||
while IFS=$'\t' read -r path field; do
|
||||
local fullpath="$REPO_ROOT/$path"
|
||||
if [[ ! -f "$fullpath" ]]; then
|
||||
printf " %-45s MISSING\n" "$path ($field)"
|
||||
has_drift=1
|
||||
continue
|
||||
fi
|
||||
local ver
|
||||
ver=$(read_json_field "$fullpath" "$field")
|
||||
printf " %-45s %s\n" "$path ($field)" "$ver"
|
||||
versions+=("$ver")
|
||||
done < <(declared_files)
|
||||
|
||||
echo ""
|
||||
|
||||
# Check if all versions match
|
||||
local unique
|
||||
unique=$(printf '%s\n' "${versions[@]}" | sort -u | wc -l | tr -d ' ')
|
||||
if [[ "$unique" -gt 1 ]]; then
|
||||
echo "DRIFT DETECTED — versions are not in sync:"
|
||||
printf '%s\n' "${versions[@]}" | sort | uniq -c | sort -rn | while read -r count ver; do
|
||||
echo " $ver ($count files)"
|
||||
done
|
||||
has_drift=1
|
||||
else
|
||||
echo "All declared files are in sync at ${versions[0]}"
|
||||
fi
|
||||
|
||||
return $has_drift
|
||||
}
|
||||
|
||||
cmd_audit() {
|
||||
# First run check
|
||||
cmd_check || true
|
||||
echo ""
|
||||
|
||||
# Determine the current version (most common across declared files)
|
||||
local current_version
|
||||
current_version=$(
|
||||
while IFS=$'\t' read -r path field; do
|
||||
local fullpath="$REPO_ROOT/$path"
|
||||
[[ -f "$fullpath" ]] && read_json_field "$fullpath" "$field"
|
||||
done < <(declared_files) | sort | uniq -c | sort -rn | head -1 | awk '{print $2}'
|
||||
)
|
||||
|
||||
if [[ -z "$current_version" ]]; then
|
||||
echo "error: could not determine current version" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Audit: scanning repo for version string '$current_version'..."
|
||||
echo ""
|
||||
|
||||
# Build grep exclude args
|
||||
local -a exclude_args=()
|
||||
while IFS= read -r pattern; do
|
||||
exclude_args+=("--exclude=$pattern" "--exclude-dir=$pattern")
|
||||
done < <(audit_excludes)
|
||||
|
||||
# Also always exclude binary files and .git
|
||||
exclude_args+=("--exclude-dir=.git" "--exclude-dir=node_modules" "--binary-files=without-match")
|
||||
|
||||
# Get list of declared paths for comparison
|
||||
local -a declared_paths=()
|
||||
while IFS=$'\t' read -r path _field; do
|
||||
declared_paths+=("$path")
|
||||
done < <(declared_files)
|
||||
|
||||
# Grep for the version string
|
||||
local found_undeclared=0
|
||||
while IFS= read -r match; do
|
||||
local match_file
|
||||
match_file=$(echo "$match" | cut -d: -f1)
|
||||
# Make path relative to repo root
|
||||
local rel_path="${match_file#$REPO_ROOT/}"
|
||||
|
||||
# Check if this file is in the declared list
|
||||
local is_declared=0
|
||||
for dp in "${declared_paths[@]}"; do
|
||||
if [[ "$rel_path" == "$dp" ]]; then
|
||||
is_declared=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$is_declared" -eq 0 ]]; then
|
||||
if [[ "$found_undeclared" -eq 0 ]]; then
|
||||
echo "UNDECLARED files containing '$current_version':"
|
||||
found_undeclared=1
|
||||
fi
|
||||
echo " $match"
|
||||
fi
|
||||
done < <(grep -rn "${exclude_args[@]}" -F "$current_version" "$REPO_ROOT" 2>/dev/null || true)
|
||||
|
||||
if [[ "$found_undeclared" -eq 0 ]]; then
|
||||
echo "No undeclared files contain the version string. All clear."
|
||||
else
|
||||
echo ""
|
||||
echo "Review the above files — if they should be bumped, add them to .version-bump.json"
|
||||
echo "If they should be skipped, add them to the audit.exclude list."
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_bump() {
|
||||
local new_version="$1"
|
||||
|
||||
# Validate semver-ish format
|
||||
if ! echo "$new_version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
|
||||
echo "error: '$new_version' doesn't look like a version (expected X.Y.Z)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Bumping all declared files to $new_version..."
|
||||
echo ""
|
||||
|
||||
while IFS=$'\t' read -r path field; do
|
||||
local fullpath="$REPO_ROOT/$path"
|
||||
if [[ ! -f "$fullpath" ]]; then
|
||||
echo " SKIP (missing): $path"
|
||||
continue
|
||||
fi
|
||||
local old_ver
|
||||
old_ver=$(read_json_field "$fullpath" "$field")
|
||||
write_json_field "$fullpath" "$field" "$new_version"
|
||||
printf " %-45s %s -> %s\n" "$path ($field)" "$old_ver" "$new_version"
|
||||
done < <(declared_files)
|
||||
|
||||
echo ""
|
||||
echo "Done. Running audit to check for missed files..."
|
||||
echo ""
|
||||
cmd_audit
|
||||
}
|
||||
|
||||
# --- main ---
|
||||
|
||||
case "${1:-}" in
|
||||
--check)
|
||||
cmd_check
|
||||
;;
|
||||
--audit)
|
||||
cmd_audit
|
||||
;;
|
||||
--help|-h|"")
|
||||
echo "Usage: bump-version.sh <new-version> | --check | --audit"
|
||||
echo ""
|
||||
echo " <new-version> Bump all declared files to the given version"
|
||||
echo " --check Show current versions, detect drift"
|
||||
echo " --audit Check + scan repo for undeclared version references"
|
||||
exit 0
|
||||
;;
|
||||
--*)
|
||||
echo "error: unknown flag '$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
cmd_bump "$1"
|
||||
;;
|
||||
esac
|
||||
464
scripts/sync-to-codex-plugin.sh
Executable file
464
scripts/sync-to-codex-plugin.sh
Executable file
@@ -0,0 +1,464 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# sync-to-codex-plugin.sh
|
||||
#
|
||||
# Sync this superpowers checkout → prime-radiant-inc/openai-codex-plugins.
|
||||
# Clones the fork fresh into a temp dir, rsyncs tracked upstream plugin content
|
||||
# (including committed Codex files under .codex-plugin/ and assets/), preserves
|
||||
# OpenAI-owned marketplace metadata already in the destination plugin, commits,
|
||||
# pushes a sync branch, and opens a PR.
|
||||
# Path/user agnostic — auto-detects upstream from script location.
|
||||
#
|
||||
# Deterministic: running twice against the same upstream SHA produces PRs with
|
||||
# identical diffs, so two back-to-back runs can verify the tool itself.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/sync-to-codex-plugin.sh # full run
|
||||
# ./scripts/sync-to-codex-plugin.sh -n # dry run
|
||||
# ./scripts/sync-to-codex-plugin.sh -y # skip confirm
|
||||
# ./scripts/sync-to-codex-plugin.sh --local PATH # existing checkout
|
||||
# ./scripts/sync-to-codex-plugin.sh --base BRANCH # default: main
|
||||
# ./scripts/sync-to-codex-plugin.sh --bootstrap # create plugin dir if missing
|
||||
#
|
||||
# Bootstrap mode: skips the "plugin must exist on base" requirement and creates
|
||||
# plugins/superpowers/ when absent, then copies the tracked plugin files from
|
||||
# upstream just like a normal sync.
|
||||
#
|
||||
# Requires: bash, rsync, git, gh (authenticated), python3.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# =============================================================================
|
||||
# Config — edit as upstream or canonical plugin shape evolves
|
||||
# =============================================================================
|
||||
|
||||
FORK="prime-radiant-inc/openai-codex-plugins"
|
||||
DEFAULT_BASE="main"
|
||||
DEST_REL="plugins/superpowers"
|
||||
|
||||
# Paths in upstream that should NOT land in the embedded plugin.
|
||||
# All patterns use a leading "/" to anchor them to the source root.
|
||||
# Unanchored patterns like "scripts/" would match any directory named
|
||||
# "scripts" at any depth — including legitimate nested dirs like
|
||||
# skills/brainstorming/scripts/. Anchoring prevents that.
|
||||
# (.DS_Store is intentionally unanchored — Finder creates them everywhere.)
|
||||
EXCLUDES=(
|
||||
# Dotfiles and infra — top-level only
|
||||
"/.claude/"
|
||||
"/.claude-plugin/"
|
||||
"/.codex/"
|
||||
"/.cursor-plugin/"
|
||||
"/.git/"
|
||||
"/.gitattributes"
|
||||
"/.github/"
|
||||
"/.gitignore"
|
||||
"/.openclaw/"
|
||||
"/.opencode/"
|
||||
"/.version-bump.json"
|
||||
"/.worktrees/"
|
||||
".DS_Store"
|
||||
|
||||
# Root ceremony files
|
||||
"/AGENTS.md"
|
||||
"/CHANGELOG.md"
|
||||
"/CLAUDE.md"
|
||||
"/GEMINI.md"
|
||||
"/RELEASE-NOTES.md"
|
||||
"/gemini-extension.json"
|
||||
"/openclaw.plugin.json"
|
||||
"/package.json"
|
||||
|
||||
# Directories not shipped by canonical Codex plugins
|
||||
"/commands/"
|
||||
"/docs/"
|
||||
"/hooks/"
|
||||
"/lib/"
|
||||
"/scripts/"
|
||||
"/tests/"
|
||||
"/tmp/"
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# Ignored-path helpers
|
||||
# =============================================================================
|
||||
|
||||
IGNORED_DIR_EXCLUDES=()
|
||||
|
||||
path_has_directory_exclude() {
|
||||
local path="$1"
|
||||
local dir
|
||||
|
||||
if [[ ${#IGNORED_DIR_EXCLUDES[@]} -eq 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
for dir in "${IGNORED_DIR_EXCLUDES[@]}"; do
|
||||
[[ "$path" == "$dir"* ]] && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
ignored_directory_has_tracked_descendants() {
|
||||
local path="$1"
|
||||
|
||||
[[ -n "$(git -C "$UPSTREAM" ls-files --cached -- "$path/")" ]]
|
||||
}
|
||||
|
||||
append_git_ignored_directory_excludes() {
|
||||
local path
|
||||
local lookup_path
|
||||
|
||||
while IFS= read -r -d '' path; do
|
||||
[[ "$path" == */ ]] || continue
|
||||
|
||||
lookup_path="${path%/}"
|
||||
if ! ignored_directory_has_tracked_descendants "$lookup_path"; then
|
||||
IGNORED_DIR_EXCLUDES+=("$path")
|
||||
RSYNC_ARGS+=(--exclude="/$path")
|
||||
fi
|
||||
done < <(git -C "$UPSTREAM" ls-files --others --ignored --exclude-standard --directory -z)
|
||||
}
|
||||
|
||||
append_git_ignored_file_excludes() {
|
||||
local path
|
||||
|
||||
while IFS= read -r -d '' path; do
|
||||
path_has_directory_exclude "$path" && continue
|
||||
RSYNC_ARGS+=(--exclude="/$path")
|
||||
done < <(git -C "$UPSTREAM" ls-files --others --ignored --exclude-standard -z)
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Args
|
||||
# =============================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
UPSTREAM="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
BASE="$DEFAULT_BASE"
|
||||
DRY_RUN=0
|
||||
YES=0
|
||||
LOCAL_CHECKOUT=""
|
||||
BOOTSTRAP=0
|
||||
|
||||
usage() {
|
||||
sed -n '/^# Usage:/,/^# Requires:/s/^# \{0,1\}//p' "$0"
|
||||
exit "${1:-0}"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-n|--dry-run) DRY_RUN=1; shift ;;
|
||||
-y|--yes) YES=1; shift ;;
|
||||
--local) LOCAL_CHECKOUT="$2"; shift 2 ;;
|
||||
--base) BASE="$2"; shift 2 ;;
|
||||
--bootstrap) BOOTSTRAP=1; shift ;;
|
||||
-h|--help) usage 0 ;;
|
||||
*) echo "Unknown arg: $1" >&2; usage 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
# Preflight
|
||||
# =============================================================================
|
||||
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
command -v rsync >/dev/null || die "rsync not found in PATH"
|
||||
command -v git >/dev/null || die "git not found in PATH"
|
||||
command -v gh >/dev/null || die "gh not found — install GitHub CLI"
|
||||
command -v python3 >/dev/null || die "python3 not found in PATH"
|
||||
|
||||
gh auth status >/dev/null 2>&1 || die "gh not authenticated — run 'gh auth login'"
|
||||
|
||||
[[ -d "$UPSTREAM/.git" ]] || die "upstream '$UPSTREAM' is not a git checkout"
|
||||
[[ -f "$UPSTREAM/.codex-plugin/plugin.json" ]] || die "committed Codex manifest missing at $UPSTREAM/.codex-plugin/plugin.json"
|
||||
|
||||
# Read the upstream version from the committed Codex manifest.
|
||||
UPSTREAM_VERSION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "$UPSTREAM/.codex-plugin/plugin.json")"
|
||||
[[ -n "$UPSTREAM_VERSION" ]] || die "could not read 'version' from committed Codex manifest"
|
||||
|
||||
UPSTREAM_BRANCH="$(cd "$UPSTREAM" && git branch --show-current)"
|
||||
UPSTREAM_SHA="$(cd "$UPSTREAM" && git rev-parse HEAD)"
|
||||
UPSTREAM_SHORT="$(cd "$UPSTREAM" && git rev-parse --short HEAD)"
|
||||
|
||||
confirm() {
|
||||
[[ $YES -eq 1 ]] && return 0
|
||||
read -rp "$1 [y/N] " ans
|
||||
[[ "$ans" == "y" || "$ans" == "Y" ]]
|
||||
}
|
||||
|
||||
if [[ "$UPSTREAM_BRANCH" != "main" ]]; then
|
||||
echo "WARNING: upstream is on '$UPSTREAM_BRANCH', not 'main'"
|
||||
confirm "Sync from '$UPSTREAM_BRANCH' anyway?" || exit 1
|
||||
fi
|
||||
|
||||
UPSTREAM_STATUS="$(cd "$UPSTREAM" && git status --porcelain)"
|
||||
if [[ -n "$UPSTREAM_STATUS" ]]; then
|
||||
echo "WARNING: upstream has uncommitted changes:"
|
||||
echo "$UPSTREAM_STATUS" | sed 's/^/ /'
|
||||
echo "Sync will use working-tree state, not HEAD ($UPSTREAM_SHORT)."
|
||||
confirm "Continue anyway?" || exit 1
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Prepare destination (clone fork fresh, or use --local)
|
||||
# =============================================================================
|
||||
|
||||
CLEANUP_DIR=""
|
||||
cleanup() {
|
||||
if [[ -n "$CLEANUP_DIR" ]]; then
|
||||
rm -rf "$CLEANUP_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [[ -n "$LOCAL_CHECKOUT" ]]; then
|
||||
DEST_REPO="$(cd "$LOCAL_CHECKOUT" && pwd)"
|
||||
[[ -d "$DEST_REPO/.git" ]] || die "--local path '$DEST_REPO' is not a git checkout"
|
||||
else
|
||||
echo "Cloning $FORK..."
|
||||
CLEANUP_DIR="$(mktemp -d)"
|
||||
DEST_REPO="$CLEANUP_DIR/openai-codex-plugins"
|
||||
gh repo clone "$FORK" "$DEST_REPO" >/dev/null
|
||||
fi
|
||||
|
||||
DEST="$DEST_REPO/$DEST_REL"
|
||||
PREVIEW_REPO="$DEST_REPO"
|
||||
PREVIEW_DEST="$DEST"
|
||||
SYNC_SOURCE=""
|
||||
|
||||
overlay_destination_paths() {
|
||||
local repo="$1"
|
||||
local path
|
||||
local source_path
|
||||
local preview_path
|
||||
|
||||
while IFS= read -r -d '' path; do
|
||||
source_path="$repo/$path"
|
||||
preview_path="$PREVIEW_REPO/$path"
|
||||
|
||||
if [[ -e "$source_path" ]]; then
|
||||
mkdir -p "$(dirname "$preview_path")"
|
||||
cp -R "$source_path" "$preview_path"
|
||||
else
|
||||
rm -rf "$preview_path"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
copy_local_destination_overlay() {
|
||||
overlay_destination_paths "$DEST_REPO" < <(
|
||||
git -C "$DEST_REPO" diff --name-only -z -- "$DEST_REL"
|
||||
)
|
||||
overlay_destination_paths "$DEST_REPO" < <(
|
||||
git -C "$DEST_REPO" diff --cached --name-only -z -- "$DEST_REL"
|
||||
)
|
||||
overlay_destination_paths "$DEST_REPO" < <(
|
||||
git -C "$DEST_REPO" ls-files --others --exclude-standard -z -- "$DEST_REL"
|
||||
)
|
||||
overlay_destination_paths "$DEST_REPO" < <(
|
||||
git -C "$DEST_REPO" ls-files --others --ignored --exclude-standard -z -- "$DEST_REL"
|
||||
)
|
||||
}
|
||||
|
||||
local_checkout_has_uncommitted_destination_changes() {
|
||||
[[ -n "$(git -C "$DEST_REPO" status --porcelain=1 --untracked-files=all --ignored=matching -- "$DEST_REL")" ]]
|
||||
}
|
||||
|
||||
prepare_preview_checkout() {
|
||||
if [[ -n "$LOCAL_CHECKOUT" ]]; then
|
||||
[[ -n "$CLEANUP_DIR" ]] || CLEANUP_DIR="$(mktemp -d)"
|
||||
PREVIEW_REPO="$CLEANUP_DIR/preview"
|
||||
git clone -q --no-local "$DEST_REPO" "$PREVIEW_REPO"
|
||||
PREVIEW_DEST="$PREVIEW_REPO/$DEST_REL"
|
||||
fi
|
||||
|
||||
git -C "$PREVIEW_REPO" checkout -q "$BASE" 2>/dev/null || die "base branch '$BASE' doesn't exist in $FORK"
|
||||
if [[ -n "$LOCAL_CHECKOUT" ]]; then
|
||||
copy_local_destination_overlay
|
||||
fi
|
||||
if [[ $BOOTSTRAP -ne 1 ]]; then
|
||||
[[ -d "$PREVIEW_DEST" ]] || die "base branch '$BASE' has no '$DEST_REL/' — use --bootstrap, or pass --base <branch>"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_apply_checkout() {
|
||||
git -C "$DEST_REPO" checkout -q "$BASE" 2>/dev/null || die "base branch '$BASE' doesn't exist in $FORK"
|
||||
if [[ $BOOTSTRAP -ne 1 ]]; then
|
||||
[[ -d "$DEST" ]] || die "base branch '$BASE' has no '$DEST_REL/' — use --bootstrap, or pass --base <branch>"
|
||||
fi
|
||||
}
|
||||
|
||||
apply_to_preview_checkout() {
|
||||
if [[ $BOOTSTRAP -eq 1 ]]; then
|
||||
mkdir -p "$PREVIEW_DEST"
|
||||
fi
|
||||
|
||||
rsync "${RSYNC_ARGS[@]}" "$SYNC_SOURCE/" "$PREVIEW_DEST/"
|
||||
}
|
||||
|
||||
preview_checkout_has_changes() {
|
||||
[[ -n "$(git -C "$PREVIEW_REPO" status --porcelain "$DEST_REL")" ]]
|
||||
}
|
||||
|
||||
prepare_preview_checkout
|
||||
|
||||
TIMESTAMP="$(date -u +%Y%m%d-%H%M%S)"
|
||||
if [[ $BOOTSTRAP -eq 1 ]]; then
|
||||
SYNC_BRANCH="bootstrap/superpowers-${UPSTREAM_SHORT}-${TIMESTAMP}"
|
||||
else
|
||||
SYNC_BRANCH="sync/superpowers-${UPSTREAM_SHORT}-${TIMESTAMP}"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Build rsync args
|
||||
# =============================================================================
|
||||
|
||||
RSYNC_ARGS=(-av --delete --delete-excluded)
|
||||
for pat in "${EXCLUDES[@]}"; do RSYNC_ARGS+=(--exclude="$pat"); done
|
||||
append_git_ignored_directory_excludes
|
||||
append_git_ignored_file_excludes
|
||||
|
||||
copy_preserved_destination_metadata() {
|
||||
local destination="$1"
|
||||
local source="$2"
|
||||
local path
|
||||
local rel
|
||||
|
||||
[[ -d "$destination/skills" ]] || return 0
|
||||
|
||||
while IFS= read -r -d '' path; do
|
||||
rel="${path#"$destination"/}"
|
||||
mkdir -p "$source/$(dirname "$rel")"
|
||||
cp -p "$path" "$source/$rel"
|
||||
done < <(find "$destination/skills" -path '*/agents/openai.yaml' -type f -print0)
|
||||
}
|
||||
|
||||
prepare_sync_source() {
|
||||
local destination="$1"
|
||||
|
||||
[[ -n "$CLEANUP_DIR" ]] || CLEANUP_DIR="$(mktemp -d)"
|
||||
|
||||
SYNC_SOURCE="$CLEANUP_DIR/source-overlay"
|
||||
rm -rf "$SYNC_SOURCE"
|
||||
mkdir -p "$SYNC_SOURCE"
|
||||
|
||||
rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$SYNC_SOURCE/" >/dev/null
|
||||
copy_preserved_destination_metadata "$destination" "$SYNC_SOURCE"
|
||||
}
|
||||
|
||||
prepare_sync_source "$PREVIEW_DEST"
|
||||
|
||||
# =============================================================================
|
||||
# Dry run preview (always shown)
|
||||
# =============================================================================
|
||||
|
||||
echo ""
|
||||
echo "Upstream: $UPSTREAM ($UPSTREAM_BRANCH @ $UPSTREAM_SHORT)"
|
||||
echo "Version: $UPSTREAM_VERSION"
|
||||
echo "Fork: $FORK"
|
||||
echo "Base: $BASE"
|
||||
echo "Branch: $SYNC_BRANCH"
|
||||
if [[ $BOOTSTRAP -eq 1 ]]; then
|
||||
echo "Mode: BOOTSTRAP (creating plugins/superpowers/ when absent)"
|
||||
fi
|
||||
echo ""
|
||||
echo "=== Preview (rsync --dry-run) ==="
|
||||
rsync "${RSYNC_ARGS[@]}" --dry-run --itemize-changes "$SYNC_SOURCE/" "$PREVIEW_DEST/"
|
||||
echo "=== End preview ==="
|
||||
echo ""
|
||||
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
echo ""
|
||||
echo "Dry run only. Nothing was changed or pushed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Apply
|
||||
# =============================================================================
|
||||
|
||||
echo ""
|
||||
confirm "Apply changes, push branch, and open PR?" || { echo "Aborted."; exit 1; }
|
||||
|
||||
echo ""
|
||||
if [[ -n "$LOCAL_CHECKOUT" ]]; then
|
||||
if local_checkout_has_uncommitted_destination_changes; then
|
||||
die "local checkout has uncommitted changes under '$DEST_REL' — commit, stash, or discard them before syncing"
|
||||
fi
|
||||
|
||||
apply_to_preview_checkout
|
||||
if ! preview_checkout_has_changes; then
|
||||
echo "No changes — embedded plugin was already in sync with upstream $UPSTREAM_SHORT (v$UPSTREAM_VERSION)."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
prepare_apply_checkout
|
||||
cd "$DEST_REPO"
|
||||
git checkout -q -b "$SYNC_BRANCH"
|
||||
echo "Syncing upstream content..."
|
||||
if [[ $BOOTSTRAP -eq 1 ]]; then
|
||||
mkdir -p "$DEST"
|
||||
fi
|
||||
rsync "${RSYNC_ARGS[@]}" "$SYNC_SOURCE/" "$DEST/"
|
||||
|
||||
# Bail early if nothing actually changed
|
||||
cd "$DEST_REPO"
|
||||
if [[ -z "$(git status --porcelain "$DEST_REL")" ]]; then
|
||||
echo "No changes — embedded plugin was already in sync with upstream $UPSTREAM_SHORT (v$UPSTREAM_VERSION)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Commit, push, open PR
|
||||
# =============================================================================
|
||||
|
||||
git add "$DEST_REL"
|
||||
|
||||
if [[ $BOOTSTRAP -eq 1 ]]; then
|
||||
COMMIT_TITLE="bootstrap superpowers v$UPSTREAM_VERSION from upstream main @ $UPSTREAM_SHORT"
|
||||
PR_BODY="Initial bootstrap of the superpowers plugin from upstream \`main\` @ \`$UPSTREAM_SHORT\` (v$UPSTREAM_VERSION).
|
||||
|
||||
Creates \`plugins/superpowers/\` by copying the tracked plugin files from upstream, including \`.codex-plugin/plugin.json\` and \`assets/\`.
|
||||
|
||||
Run via: \`scripts/sync-to-codex-plugin.sh --bootstrap\`
|
||||
Upstream commit: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
|
||||
|
||||
This is a one-time bootstrap. Subsequent syncs will be normal (non-bootstrap) runs using the same tracked upstream plugin files."
|
||||
else
|
||||
COMMIT_TITLE="sync superpowers v$UPSTREAM_VERSION from upstream main @ $UPSTREAM_SHORT"
|
||||
PR_BODY="Automated sync from superpowers upstream \`main\` @ \`$UPSTREAM_SHORT\` (v$UPSTREAM_VERSION).
|
||||
|
||||
Copies the tracked plugin files from upstream, including the committed Codex manifest and assets.
|
||||
|
||||
Run via: \`scripts/sync-to-codex-plugin.sh\`
|
||||
Upstream commit: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
|
||||
|
||||
Running the sync tool again against the same upstream SHA should produce a PR with an identical diff — use that to verify the tool is behaving."
|
||||
fi
|
||||
|
||||
git commit --quiet -m "$COMMIT_TITLE
|
||||
|
||||
Automated sync via scripts/sync-to-codex-plugin.sh
|
||||
Upstream: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
|
||||
Branch: $SYNC_BRANCH"
|
||||
|
||||
echo "Pushing $SYNC_BRANCH to $FORK..."
|
||||
git push -u origin "$SYNC_BRANCH" --quiet
|
||||
|
||||
echo "Opening PR..."
|
||||
PR_URL="$(gh pr create \
|
||||
--repo "$FORK" \
|
||||
--base "$BASE" \
|
||||
--head "$SYNC_BRANCH" \
|
||||
--title "$COMMIT_TITLE" \
|
||||
--body "$PR_BODY")"
|
||||
|
||||
PR_NUM="${PR_URL##*/}"
|
||||
DIFF_URL="https://github.com/$FORK/pull/$PR_NUM/files"
|
||||
|
||||
echo ""
|
||||
echo "PR opened: $PR_URL"
|
||||
echo "Diff view: $DIFF_URL"
|
||||
@@ -5,44 +5,135 @@ description: "You MUST use this before any creative work - creating features, bu
|
||||
|
||||
# Brainstorming Ideas Into Designs
|
||||
|
||||
## Overview
|
||||
|
||||
Help turn ideas into fully formed designs and specs through natural collaborative dialogue.
|
||||
|
||||
Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design in small sections (200-300 words), checking after each section whether it looks right so far.
|
||||
Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval.
|
||||
|
||||
<HARD-GATE>
|
||||
Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity.
|
||||
</HARD-GATE>
|
||||
|
||||
## Anti-Pattern: "This Is Too Simple To Need A Design"
|
||||
|
||||
Every project goes through this process. A todo list, a single-function utility, a config change — all of them. "Simple" projects are where unexamined assumptions cause the most wasted work. The design can be short (a few sentences for truly simple projects), but you MUST present it and get approval.
|
||||
|
||||
## Checklist
|
||||
|
||||
You MUST create a task for each of these items and complete them in order:
|
||||
|
||||
1. **Explore project context** — check files, docs, recent commits
|
||||
2. **Offer visual companion** (if topic will involve visual questions) — this is its own message, not combined with a clarifying question. See the Visual Companion section below.
|
||||
3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria
|
||||
4. **Propose 2-3 approaches** — with trade-offs and your recommendation
|
||||
5. **Present design** — in sections scaled to their complexity, get user approval after each section
|
||||
6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit
|
||||
7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below)
|
||||
8. **User reviews written spec** — ask user to review the spec file before proceeding
|
||||
9. **Transition to implementation** — invoke writing-plans skill to create implementation plan
|
||||
|
||||
## Process Flow
|
||||
|
||||
```dot
|
||||
digraph brainstorming {
|
||||
"Explore project context" [shape=box];
|
||||
"Visual questions ahead?" [shape=diamond];
|
||||
"Offer Visual Companion\n(own message, no other content)" [shape=box];
|
||||
"Ask clarifying questions" [shape=box];
|
||||
"Propose 2-3 approaches" [shape=box];
|
||||
"Present design sections" [shape=box];
|
||||
"User approves design?" [shape=diamond];
|
||||
"Write design doc" [shape=box];
|
||||
"Spec self-review\n(fix inline)" [shape=box];
|
||||
"User reviews spec?" [shape=diamond];
|
||||
"Invoke writing-plans skill" [shape=doublecircle];
|
||||
|
||||
"Explore project context" -> "Visual questions ahead?";
|
||||
"Visual questions ahead?" -> "Offer Visual Companion\n(own message, no other content)" [label="yes"];
|
||||
"Visual questions ahead?" -> "Ask clarifying questions" [label="no"];
|
||||
"Offer Visual Companion\n(own message, no other content)" -> "Ask clarifying questions";
|
||||
"Ask clarifying questions" -> "Propose 2-3 approaches";
|
||||
"Propose 2-3 approaches" -> "Present design sections";
|
||||
"Present design sections" -> "User approves design?";
|
||||
"User approves design?" -> "Present design sections" [label="no, revise"];
|
||||
"User approves design?" -> "Write design doc" [label="yes"];
|
||||
"Write design doc" -> "Spec self-review\n(fix inline)";
|
||||
"Spec self-review\n(fix inline)" -> "User reviews spec?";
|
||||
"User reviews spec?" -> "Write design doc" [label="changes requested"];
|
||||
"User reviews spec?" -> "Invoke writing-plans skill" [label="approved"];
|
||||
}
|
||||
```
|
||||
|
||||
**The terminal state is invoking writing-plans.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is writing-plans.
|
||||
|
||||
## The Process
|
||||
|
||||
**Understanding the idea:**
|
||||
|
||||
- Check out the current project state first (files, docs, recent commits)
|
||||
- Ask questions one at a time to refine the idea
|
||||
- Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first.
|
||||
- If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle.
|
||||
- For appropriately-scoped projects, ask questions one at a time to refine the idea
|
||||
- Prefer multiple choice questions when possible, but open-ended is fine too
|
||||
- Only one question per message - if a topic needs more exploration, break it into multiple questions
|
||||
- Focus on understanding: purpose, constraints, success criteria
|
||||
|
||||
**Exploring approaches:**
|
||||
|
||||
- Propose 2-3 different approaches with trade-offs
|
||||
- Present options conversationally with your recommendation and reasoning
|
||||
- Lead with your recommended option and explain why
|
||||
|
||||
**Presenting the design:**
|
||||
|
||||
- Once you believe you understand what you're building, present the design
|
||||
- Break it into sections of 200-300 words
|
||||
- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced
|
||||
- Ask after each section whether it looks right so far
|
||||
- Cover: architecture, components, data flow, error handling, testing
|
||||
- Be ready to go back and clarify if something doesn't make sense
|
||||
|
||||
**Design for isolation and clarity:**
|
||||
|
||||
- Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently
|
||||
- For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on?
|
||||
- Can someone understand what a unit does without reading its internals? Can you change the internals without breaking consumers? If not, the boundaries need work.
|
||||
- Smaller, well-bounded units are also easier for you to work with - you reason better about code you can hold in context at once, and your edits are more reliable when files are focused. When a file grows large, that's often a signal that it's doing too much.
|
||||
|
||||
**Working in existing codebases:**
|
||||
|
||||
- Explore the current structure before proposing changes. Follow existing patterns.
|
||||
- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in.
|
||||
- Don't propose unrelated refactoring. Stay focused on what serves the current goal.
|
||||
|
||||
## After the Design
|
||||
|
||||
**Documentation:**
|
||||
- Write the validated design to `docs/plans/YYYY-MM-DD-<topic>-design.md`
|
||||
|
||||
- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`
|
||||
- (User preferences for spec location override this default)
|
||||
- Use elements-of-style:writing-clearly-and-concisely skill if available
|
||||
- Commit the design document to git
|
||||
|
||||
**Implementation (if continuing):**
|
||||
- Ask: "Ready to set up for implementation?"
|
||||
- Use superpowers:using-git-worktrees to create isolated workspace
|
||||
- Use superpowers:writing-plans to create detailed implementation plan
|
||||
**Spec Self-Review:**
|
||||
After writing the spec document, look at it with fresh eyes:
|
||||
|
||||
1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them.
|
||||
2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions?
|
||||
3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition?
|
||||
4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit.
|
||||
|
||||
Fix any issues inline. No need to re-review — just fix and move on.
|
||||
|
||||
**User Review Gate:**
|
||||
After the spec review loop passes, ask the user to review the written spec before proceeding:
|
||||
|
||||
> "Spec written and committed to `<path>`. Please review it and let me know if you want to make any changes before we start writing out the implementation plan."
|
||||
|
||||
Wait for the user's response. If they request changes, make them and re-run the spec review loop. Only proceed once the user approves.
|
||||
|
||||
**Implementation:**
|
||||
|
||||
- Invoke the writing-plans skill to create a detailed implementation plan
|
||||
- Do NOT invoke any other skill. writing-plans is the next step.
|
||||
|
||||
## Key Principles
|
||||
|
||||
@@ -50,5 +141,24 @@ Start by understanding the current project context, then ask questions one at a
|
||||
- **Multiple choice preferred** - Easier to answer than open-ended when possible
|
||||
- **YAGNI ruthlessly** - Remove unnecessary features from all designs
|
||||
- **Explore alternatives** - Always propose 2-3 approaches before settling
|
||||
- **Incremental validation** - Present design in sections, validate each
|
||||
- **Incremental validation** - Present design, get approval before moving on
|
||||
- **Be flexible** - Go back and clarify when something doesn't make sense
|
||||
|
||||
## Visual Companion
|
||||
|
||||
A browser-based companion for showing mockups, diagrams, and visual options during brainstorming. Available as a tool — not a mode. Accepting the companion means it's available for questions that benefit from visual treatment; it does NOT mean every question goes through the browser.
|
||||
|
||||
**Offering the companion:** When you anticipate that upcoming questions will involve visual content (mockups, layouts, diagrams), offer it once for consent:
|
||||
> "Some of what we're working on might be easier to explain if I can show it to you in a web browser. I can put together mockups, diagrams, comparisons, and other visuals as we go. This feature is still new and can be token-intensive. Want to try it? (Requires opening a local URL)"
|
||||
|
||||
**This offer MUST be its own message.** Do not combine it with clarifying questions, context summaries, or any other content. The message should contain ONLY the offer above and nothing else. Wait for the user's response before continuing. If they decline, proceed with text-only brainstorming.
|
||||
|
||||
**Per-question decision:** Even after the user accepts, decide FOR EACH QUESTION whether to use the browser or the terminal. The test: **would the user understand this better by seeing it than reading it?**
|
||||
|
||||
- **Use the browser** for content that IS visual — mockups, wireframes, layout comparisons, architecture diagrams, side-by-side visual designs
|
||||
- **Use the terminal** for content that is text — requirements questions, conceptual choices, tradeoff lists, A/B/C/D text options, scope decisions
|
||||
|
||||
A question about a UI topic is not automatically a visual question. "What does personality mean in this context?" is a conceptual question — use the terminal. "Which wizard layout works better?" is a visual question — use the browser.
|
||||
|
||||
If they agree to the companion, read the detailed guide before proceeding:
|
||||
`skills/brainstorming/visual-companion.md`
|
||||
|
||||
214
skills/brainstorming/scripts/frame-template.html
Normal file
214
skills/brainstorming/scripts/frame-template.html
Normal file
@@ -0,0 +1,214 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Superpowers Brainstorming</title>
|
||||
<style>
|
||||
/*
|
||||
* BRAINSTORM COMPANION FRAME TEMPLATE
|
||||
*
|
||||
* This template provides a consistent frame with:
|
||||
* - OS-aware light/dark theming
|
||||
* - Fixed header and selection indicator bar
|
||||
* - Scrollable main content area
|
||||
* - CSS helpers for common UI patterns
|
||||
*
|
||||
* Content is injected via placeholder comment in #claude-content.
|
||||
*/
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { height: 100%; overflow: hidden; }
|
||||
|
||||
/* ===== THEME VARIABLES ===== */
|
||||
:root {
|
||||
--bg-primary: #f5f5f7;
|
||||
--bg-secondary: #ffffff;
|
||||
--bg-tertiary: #e5e5e7;
|
||||
--border: #d1d1d6;
|
||||
--text-primary: #1d1d1f;
|
||||
--text-secondary: #86868b;
|
||||
--text-tertiary: #aeaeb2;
|
||||
--accent: #0071e3;
|
||||
--accent-hover: #0077ed;
|
||||
--success: #34c759;
|
||||
--warning: #ff9f0a;
|
||||
--error: #ff3b30;
|
||||
--selected-bg: #e8f4fd;
|
||||
--selected-border: #0071e3;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg-primary: #1d1d1f;
|
||||
--bg-secondary: #2d2d2f;
|
||||
--bg-tertiary: #3d3d3f;
|
||||
--border: #424245;
|
||||
--text-primary: #f5f5f7;
|
||||
--text-secondary: #86868b;
|
||||
--text-tertiary: #636366;
|
||||
--accent: #0a84ff;
|
||||
--accent-hover: #409cff;
|
||||
--selected-bg: rgba(10, 132, 255, 0.15);
|
||||
--selected-border: #0a84ff;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ===== FRAME STRUCTURE ===== */
|
||||
.header {
|
||||
background: var(--bg-secondary);
|
||||
padding: 0.5rem 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.header h1 { font-size: 0.85rem; font-weight: 500; color: var(--text-secondary); }
|
||||
.header .status { font-size: 0.7rem; color: var(--success); display: flex; align-items: center; gap: 0.4rem; }
|
||||
.header .status::before { content: ''; width: 6px; height: 6px; background: var(--success); border-radius: 50%; }
|
||||
|
||||
.main { flex: 1; overflow-y: auto; }
|
||||
#claude-content { padding: 2rem; min-height: 100%; }
|
||||
|
||||
.indicator-bar {
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 0.5rem 1.5rem;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.indicator-bar span {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.indicator-bar .selected-text {
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ===== TYPOGRAPHY ===== */
|
||||
h2 { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.5rem; }
|
||||
h3 { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.25rem; }
|
||||
.subtitle { color: var(--text-secondary); margin-bottom: 1.5rem; }
|
||||
.section { margin-bottom: 2rem; }
|
||||
.label { font-size: 0.7rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
||||
|
||||
/* ===== OPTIONS (for A/B/C choices) ===== */
|
||||
.options { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.option {
|
||||
background: var(--bg-secondary);
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 1rem 1.25rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
.option:hover { border-color: var(--accent); }
|
||||
.option.selected { background: var(--selected-bg); border-color: var(--selected-border); }
|
||||
.option .letter {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
width: 1.75rem; height: 1.75rem;
|
||||
border-radius: 6px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-weight: 600; font-size: 0.85rem; flex-shrink: 0;
|
||||
}
|
||||
.option.selected .letter { background: var(--accent); color: white; }
|
||||
.option .content { flex: 1; }
|
||||
.option .content h3 { font-size: 0.95rem; margin-bottom: 0.15rem; }
|
||||
.option .content p { color: var(--text-secondary); font-size: 0.85rem; margin: 0; }
|
||||
|
||||
/* ===== CARDS (for showing designs/mockups) ===== */
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }
|
||||
.card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||
.card.selected { border-color: var(--selected-border); border-width: 2px; }
|
||||
.card-image { background: var(--bg-tertiary); aspect-ratio: 16/10; display: flex; align-items: center; justify-content: center; }
|
||||
.card-body { padding: 1rem; }
|
||||
.card-body h3 { margin-bottom: 0.25rem; }
|
||||
.card-body p { color: var(--text-secondary); font-size: 0.85rem; }
|
||||
|
||||
/* ===== MOCKUP CONTAINER ===== */
|
||||
.mockup {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.mockup-header {
|
||||
background: var(--bg-tertiary);
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.mockup-body { padding: 1.5rem; }
|
||||
|
||||
/* ===== SPLIT VIEW (side-by-side comparison) ===== */
|
||||
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
|
||||
@media (max-width: 700px) { .split { grid-template-columns: 1fr; } }
|
||||
|
||||
/* ===== PROS/CONS ===== */
|
||||
.pros-cons { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin: 1rem 0; }
|
||||
.pros, .cons { background: var(--bg-secondary); border-radius: 8px; padding: 1rem; }
|
||||
.pros h4 { color: var(--success); font-size: 0.85rem; margin-bottom: 0.5rem; }
|
||||
.cons h4 { color: var(--error); font-size: 0.85rem; margin-bottom: 0.5rem; }
|
||||
.pros ul, .cons ul { margin-left: 1.25rem; font-size: 0.85rem; color: var(--text-secondary); }
|
||||
.pros li, .cons li { margin-bottom: 0.25rem; }
|
||||
|
||||
/* ===== PLACEHOLDER (for mockup areas) ===== */
|
||||
.placeholder {
|
||||
background: var(--bg-tertiary);
|
||||
border: 2px dashed var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* ===== INLINE MOCKUP ELEMENTS ===== */
|
||||
.mock-nav { background: var(--accent); color: white; padding: 0.75rem 1rem; display: flex; gap: 1.5rem; font-size: 0.9rem; }
|
||||
.mock-sidebar { background: var(--bg-tertiary); padding: 1rem; min-width: 180px; }
|
||||
.mock-content { padding: 1.5rem; flex: 1; }
|
||||
.mock-button { background: var(--accent); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.85rem; }
|
||||
.mock-input { background: var(--bg-primary); border: 1px solid var(--border); border-radius: 6px; padding: 0.5rem; width: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1><a href="https://github.com/obra/superpowers" style="color: inherit; text-decoration: none;">Superpowers Brainstorming</a></h1>
|
||||
<div class="status">Connected</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div id="claude-content">
|
||||
<!-- CONTENT -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="indicator-bar">
|
||||
<span id="indicator-text">Click an option above, then return to the terminal</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
88
skills/brainstorming/scripts/helper.js
Normal file
88
skills/brainstorming/scripts/helper.js
Normal file
@@ -0,0 +1,88 @@
|
||||
(function() {
|
||||
const WS_URL = 'ws://' + window.location.host;
|
||||
let ws = null;
|
||||
let eventQueue = [];
|
||||
|
||||
function connect() {
|
||||
ws = new WebSocket(WS_URL);
|
||||
|
||||
ws.onopen = () => {
|
||||
eventQueue.forEach(e => ws.send(JSON.stringify(e)));
|
||||
eventQueue = [];
|
||||
};
|
||||
|
||||
ws.onmessage = (msg) => {
|
||||
const data = JSON.parse(msg.data);
|
||||
if (data.type === 'reload') {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
setTimeout(connect, 1000);
|
||||
};
|
||||
}
|
||||
|
||||
function sendEvent(event) {
|
||||
event.timestamp = Date.now();
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(event));
|
||||
} else {
|
||||
eventQueue.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Capture clicks on choice elements
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-choice]');
|
||||
if (!target) return;
|
||||
|
||||
sendEvent({
|
||||
type: 'click',
|
||||
text: target.textContent.trim(),
|
||||
choice: target.dataset.choice,
|
||||
id: target.id || null
|
||||
});
|
||||
|
||||
// Update indicator bar (defer so toggleSelect runs first)
|
||||
setTimeout(() => {
|
||||
const indicator = document.getElementById('indicator-text');
|
||||
if (!indicator) return;
|
||||
const container = target.closest('.options') || target.closest('.cards');
|
||||
const selected = container ? container.querySelectorAll('.selected') : [];
|
||||
if (selected.length === 0) {
|
||||
indicator.textContent = 'Click an option above, then return to the terminal';
|
||||
} else if (selected.length === 1) {
|
||||
const label = selected[0].querySelector('h3, .content h3, .card-body h3')?.textContent?.trim() || selected[0].dataset.choice;
|
||||
indicator.innerHTML = '<span class="selected-text">' + label + ' selected</span> — return to terminal to continue';
|
||||
} else {
|
||||
indicator.innerHTML = '<span class="selected-text">' + selected.length + ' selected</span> — return to terminal to continue';
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Frame UI: selection tracking
|
||||
window.selectedChoice = null;
|
||||
|
||||
window.toggleSelect = function(el) {
|
||||
const container = el.closest('.options') || el.closest('.cards');
|
||||
const multi = container && container.dataset.multiselect !== undefined;
|
||||
if (container && !multi) {
|
||||
container.querySelectorAll('.option, .card').forEach(o => o.classList.remove('selected'));
|
||||
}
|
||||
if (multi) {
|
||||
el.classList.toggle('selected');
|
||||
} else {
|
||||
el.classList.add('selected');
|
||||
}
|
||||
window.selectedChoice = el.dataset.choice;
|
||||
};
|
||||
|
||||
// Expose API for explicit use
|
||||
window.brainstorm = {
|
||||
send: sendEvent,
|
||||
choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata })
|
||||
};
|
||||
|
||||
connect();
|
||||
})();
|
||||
354
skills/brainstorming/scripts/server.cjs
Normal file
354
skills/brainstorming/scripts/server.cjs
Normal file
@@ -0,0 +1,354 @@
|
||||
const crypto = require('crypto');
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// ========== WebSocket Protocol (RFC 6455) ==========
|
||||
|
||||
const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A };
|
||||
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
|
||||
function computeAcceptKey(clientKey) {
|
||||
return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64');
|
||||
}
|
||||
|
||||
function encodeFrame(opcode, payload) {
|
||||
const fin = 0x80;
|
||||
const len = payload.length;
|
||||
let header;
|
||||
|
||||
if (len < 126) {
|
||||
header = Buffer.alloc(2);
|
||||
header[0] = fin | opcode;
|
||||
header[1] = len;
|
||||
} else if (len < 65536) {
|
||||
header = Buffer.alloc(4);
|
||||
header[0] = fin | opcode;
|
||||
header[1] = 126;
|
||||
header.writeUInt16BE(len, 2);
|
||||
} else {
|
||||
header = Buffer.alloc(10);
|
||||
header[0] = fin | opcode;
|
||||
header[1] = 127;
|
||||
header.writeBigUInt64BE(BigInt(len), 2);
|
||||
}
|
||||
|
||||
return Buffer.concat([header, payload]);
|
||||
}
|
||||
|
||||
function decodeFrame(buffer) {
|
||||
if (buffer.length < 2) return null;
|
||||
|
||||
const secondByte = buffer[1];
|
||||
const opcode = buffer[0] & 0x0F;
|
||||
const masked = (secondByte & 0x80) !== 0;
|
||||
let payloadLen = secondByte & 0x7F;
|
||||
let offset = 2;
|
||||
|
||||
if (!masked) throw new Error('Client frames must be masked');
|
||||
|
||||
if (payloadLen === 126) {
|
||||
if (buffer.length < 4) return null;
|
||||
payloadLen = buffer.readUInt16BE(2);
|
||||
offset = 4;
|
||||
} else if (payloadLen === 127) {
|
||||
if (buffer.length < 10) return null;
|
||||
payloadLen = Number(buffer.readBigUInt64BE(2));
|
||||
offset = 10;
|
||||
}
|
||||
|
||||
const maskOffset = offset;
|
||||
const dataOffset = offset + 4;
|
||||
const totalLen = dataOffset + payloadLen;
|
||||
if (buffer.length < totalLen) return null;
|
||||
|
||||
const mask = buffer.slice(maskOffset, dataOffset);
|
||||
const data = Buffer.alloc(payloadLen);
|
||||
for (let i = 0; i < payloadLen; i++) {
|
||||
data[i] = buffer[dataOffset + i] ^ mask[i % 4];
|
||||
}
|
||||
|
||||
return { opcode, payload: data, bytesConsumed: totalLen };
|
||||
}
|
||||
|
||||
// ========== Configuration ==========
|
||||
|
||||
const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383));
|
||||
const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1';
|
||||
const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST);
|
||||
const SESSION_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
|
||||
const CONTENT_DIR = path.join(SESSION_DIR, 'content');
|
||||
const STATE_DIR = path.join(SESSION_DIR, 'state');
|
||||
let ownerPid = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null;
|
||||
|
||||
const MIME_TYPES = {
|
||||
'.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
|
||||
'.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml'
|
||||
};
|
||||
|
||||
// ========== Templates and Constants ==========
|
||||
|
||||
const WAITING_PAGE = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head><meta charset="utf-8"><title>Brainstorm Companion</title>
|
||||
<style>body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
|
||||
h1 { color: #333; } p { color: #666; }</style>
|
||||
</head>
|
||||
<body><h1>Brainstorm Companion</h1>
|
||||
<p>Waiting for the agent to push a screen...</p></body></html>`;
|
||||
|
||||
const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8');
|
||||
const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8');
|
||||
const helperInjection = '<script>\n' + helperScript + '\n</script>';
|
||||
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
function isFullDocument(html) {
|
||||
const trimmed = html.trimStart().toLowerCase();
|
||||
return trimmed.startsWith('<!doctype') || trimmed.startsWith('<html');
|
||||
}
|
||||
|
||||
function wrapInFrame(content) {
|
||||
return frameTemplate.replace('<!-- CONTENT -->', content);
|
||||
}
|
||||
|
||||
function getNewestScreen() {
|
||||
const files = fs.readdirSync(CONTENT_DIR)
|
||||
.filter(f => f.endsWith('.html'))
|
||||
.map(f => {
|
||||
const fp = path.join(CONTENT_DIR, f);
|
||||
return { path: fp, mtime: fs.statSync(fp).mtime.getTime() };
|
||||
})
|
||||
.sort((a, b) => b.mtime - a.mtime);
|
||||
return files.length > 0 ? files[0].path : null;
|
||||
}
|
||||
|
||||
// ========== HTTP Request Handler ==========
|
||||
|
||||
function handleRequest(req, res) {
|
||||
touchActivity();
|
||||
if (req.method === 'GET' && req.url === '/') {
|
||||
const screenFile = getNewestScreen();
|
||||
let html = screenFile
|
||||
? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8'))
|
||||
: WAITING_PAGE;
|
||||
|
||||
if (html.includes('</body>')) {
|
||||
html = html.replace('</body>', helperInjection + '\n</body>');
|
||||
} else {
|
||||
html += helperInjection;
|
||||
}
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(html);
|
||||
} else if (req.method === 'GET' && req.url.startsWith('/files/')) {
|
||||
const fileName = req.url.slice(7);
|
||||
const filePath = path.join(CONTENT_DIR, path.basename(fileName));
|
||||
if (!fs.existsSync(filePath)) {
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
return;
|
||||
}
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
||||
res.writeHead(200, { 'Content-Type': contentType });
|
||||
res.end(fs.readFileSync(filePath));
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== WebSocket Connection Handling ==========
|
||||
|
||||
const clients = new Set();
|
||||
|
||||
function handleUpgrade(req, socket) {
|
||||
const key = req.headers['sec-websocket-key'];
|
||||
if (!key) { socket.destroy(); return; }
|
||||
|
||||
const accept = computeAcceptKey(key);
|
||||
socket.write(
|
||||
'HTTP/1.1 101 Switching Protocols\r\n' +
|
||||
'Upgrade: websocket\r\n' +
|
||||
'Connection: Upgrade\r\n' +
|
||||
'Sec-WebSocket-Accept: ' + accept + '\r\n\r\n'
|
||||
);
|
||||
|
||||
let buffer = Buffer.alloc(0);
|
||||
clients.add(socket);
|
||||
|
||||
socket.on('data', (chunk) => {
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
while (buffer.length > 0) {
|
||||
let result;
|
||||
try {
|
||||
result = decodeFrame(buffer);
|
||||
} catch (e) {
|
||||
socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0)));
|
||||
clients.delete(socket);
|
||||
return;
|
||||
}
|
||||
if (!result) break;
|
||||
buffer = buffer.slice(result.bytesConsumed);
|
||||
|
||||
switch (result.opcode) {
|
||||
case OPCODES.TEXT:
|
||||
handleMessage(result.payload.toString());
|
||||
break;
|
||||
case OPCODES.CLOSE:
|
||||
socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0)));
|
||||
clients.delete(socket);
|
||||
return;
|
||||
case OPCODES.PING:
|
||||
socket.write(encodeFrame(OPCODES.PONG, result.payload));
|
||||
break;
|
||||
case OPCODES.PONG:
|
||||
break;
|
||||
default: {
|
||||
const closeBuf = Buffer.alloc(2);
|
||||
closeBuf.writeUInt16BE(1003);
|
||||
socket.end(encodeFrame(OPCODES.CLOSE, closeBuf));
|
||||
clients.delete(socket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('close', () => clients.delete(socket));
|
||||
socket.on('error', () => clients.delete(socket));
|
||||
}
|
||||
|
||||
function handleMessage(text) {
|
||||
let event;
|
||||
try {
|
||||
event = JSON.parse(text);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse WebSocket message:', e.message);
|
||||
return;
|
||||
}
|
||||
touchActivity();
|
||||
console.log(JSON.stringify({ source: 'user-event', ...event }));
|
||||
if (event.choice) {
|
||||
const eventsFile = path.join(STATE_DIR, 'events');
|
||||
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
function broadcast(msg) {
|
||||
const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg)));
|
||||
for (const socket of clients) {
|
||||
try { socket.write(frame); } catch (e) { clients.delete(socket); }
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Activity Tracking ==========
|
||||
|
||||
const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
||||
let lastActivity = Date.now();
|
||||
|
||||
function touchActivity() {
|
||||
lastActivity = Date.now();
|
||||
}
|
||||
|
||||
// ========== File Watching ==========
|
||||
|
||||
const debounceTimers = new Map();
|
||||
|
||||
// ========== Server Startup ==========
|
||||
|
||||
function startServer() {
|
||||
if (!fs.existsSync(CONTENT_DIR)) fs.mkdirSync(CONTENT_DIR, { recursive: true });
|
||||
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
||||
|
||||
// Track known files to distinguish new screens from updates.
|
||||
// macOS fs.watch reports 'rename' for both new files and overwrites,
|
||||
// so we can't rely on eventType alone.
|
||||
const knownFiles = new Set(
|
||||
fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html'))
|
||||
);
|
||||
|
||||
const server = http.createServer(handleRequest);
|
||||
server.on('upgrade', handleUpgrade);
|
||||
|
||||
const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => {
|
||||
if (!filename || !filename.endsWith('.html')) return;
|
||||
|
||||
if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename));
|
||||
debounceTimers.set(filename, setTimeout(() => {
|
||||
debounceTimers.delete(filename);
|
||||
const filePath = path.join(CONTENT_DIR, filename);
|
||||
|
||||
if (!fs.existsSync(filePath)) return; // file was deleted
|
||||
touchActivity();
|
||||
|
||||
if (!knownFiles.has(filename)) {
|
||||
knownFiles.add(filename);
|
||||
const eventsFile = path.join(STATE_DIR, 'events');
|
||||
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
|
||||
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
|
||||
} else {
|
||||
console.log(JSON.stringify({ type: 'screen-updated', file: filePath }));
|
||||
}
|
||||
|
||||
broadcast({ type: 'reload' });
|
||||
}, 100));
|
||||
});
|
||||
watcher.on('error', (err) => console.error('fs.watch error:', err.message));
|
||||
|
||||
function shutdown(reason) {
|
||||
console.log(JSON.stringify({ type: 'server-stopped', reason }));
|
||||
const infoFile = path.join(STATE_DIR, 'server-info');
|
||||
if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile);
|
||||
fs.writeFileSync(
|
||||
path.join(STATE_DIR, 'server-stopped'),
|
||||
JSON.stringify({ reason, timestamp: Date.now() }) + '\n'
|
||||
);
|
||||
watcher.close();
|
||||
clearInterval(lifecycleCheck);
|
||||
server.close(() => process.exit(0));
|
||||
}
|
||||
|
||||
function ownerAlive() {
|
||||
if (!ownerPid) return true;
|
||||
try { process.kill(ownerPid, 0); return true; } catch (e) { return e.code === 'EPERM'; }
|
||||
}
|
||||
|
||||
// Check every 60s: exit if owner process died or idle for 30 minutes
|
||||
const lifecycleCheck = setInterval(() => {
|
||||
if (!ownerAlive()) shutdown('owner process exited');
|
||||
else if (Date.now() - lastActivity > IDLE_TIMEOUT_MS) shutdown('idle timeout');
|
||||
}, 60 * 1000);
|
||||
lifecycleCheck.unref();
|
||||
|
||||
// Validate owner PID at startup. If it's already dead, the PID resolution
|
||||
// was wrong (common on WSL, Tailscale SSH, and cross-user scenarios).
|
||||
// Disable monitoring and rely on the idle timeout instead.
|
||||
if (ownerPid) {
|
||||
try { process.kill(ownerPid, 0); }
|
||||
catch (e) {
|
||||
if (e.code !== 'EPERM') {
|
||||
console.log(JSON.stringify({ type: 'owner-pid-invalid', pid: ownerPid, reason: 'dead at startup' }));
|
||||
ownerPid = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
const info = JSON.stringify({
|
||||
type: 'server-started', port: Number(PORT), host: HOST,
|
||||
url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT,
|
||||
screen_dir: CONTENT_DIR, state_dir: STATE_DIR
|
||||
});
|
||||
console.log(info);
|
||||
fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info + '\n');
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
startServer();
|
||||
}
|
||||
|
||||
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES };
|
||||
148
skills/brainstorming/scripts/start-server.sh
Executable file
148
skills/brainstorming/scripts/start-server.sh
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start the brainstorm server and output connection info
|
||||
# Usage: start-server.sh [--project-dir <path>] [--host <bind-host>] [--url-host <display-host>] [--foreground] [--background]
|
||||
#
|
||||
# Starts server on a random high port, outputs JSON with URL.
|
||||
# Each session gets its own directory to avoid conflicts.
|
||||
#
|
||||
# Options:
|
||||
# --project-dir <path> Store session files under <path>/.superpowers/brainstorm/
|
||||
# instead of /tmp. Files persist after server stops.
|
||||
# --host <bind-host> Host/interface to bind (default: 127.0.0.1).
|
||||
# Use 0.0.0.0 in remote/containerized environments.
|
||||
# --url-host <host> Hostname shown in returned URL JSON.
|
||||
# --foreground Run server in the current terminal (no backgrounding).
|
||||
# --background Force background mode (overrides Codex auto-foreground).
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# Parse arguments
|
||||
PROJECT_DIR=""
|
||||
FOREGROUND="false"
|
||||
FORCE_BACKGROUND="false"
|
||||
BIND_HOST="127.0.0.1"
|
||||
URL_HOST=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--project-dir)
|
||||
PROJECT_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--host)
|
||||
BIND_HOST="$2"
|
||||
shift 2
|
||||
;;
|
||||
--url-host)
|
||||
URL_HOST="$2"
|
||||
shift 2
|
||||
;;
|
||||
--foreground|--no-daemon)
|
||||
FOREGROUND="true"
|
||||
shift
|
||||
;;
|
||||
--background|--daemon)
|
||||
FORCE_BACKGROUND="true"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "{\"error\": \"Unknown argument: $1\"}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$URL_HOST" ]]; then
|
||||
if [[ "$BIND_HOST" == "127.0.0.1" || "$BIND_HOST" == "localhost" ]]; then
|
||||
URL_HOST="localhost"
|
||||
else
|
||||
URL_HOST="$BIND_HOST"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Some environments reap detached/background processes. Auto-foreground when detected.
|
||||
if [[ -n "${CODEX_CI:-}" && "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then
|
||||
FOREGROUND="true"
|
||||
fi
|
||||
|
||||
# Windows/Git Bash reaps nohup background processes. Auto-foreground when detected.
|
||||
if [[ "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then
|
||||
case "${OSTYPE:-}" in
|
||||
msys*|cygwin*|mingw*) FOREGROUND="true" ;;
|
||||
esac
|
||||
if [[ -n "${MSYSTEM:-}" ]]; then
|
||||
FOREGROUND="true"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generate unique session directory
|
||||
SESSION_ID="$$-$(date +%s)"
|
||||
|
||||
if [[ -n "$PROJECT_DIR" ]]; then
|
||||
SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
|
||||
else
|
||||
SESSION_DIR="/tmp/brainstorm-${SESSION_ID}"
|
||||
fi
|
||||
|
||||
STATE_DIR="${SESSION_DIR}/state"
|
||||
PID_FILE="${STATE_DIR}/server.pid"
|
||||
LOG_FILE="${STATE_DIR}/server.log"
|
||||
|
||||
# Create fresh session directory with content and state peers
|
||||
mkdir -p "${SESSION_DIR}/content" "$STATE_DIR"
|
||||
|
||||
# Kill any existing server
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
old_pid=$(cat "$PID_FILE")
|
||||
kill "$old_pid" 2>/dev/null
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Resolve the harness PID (grandparent of this script).
|
||||
# $PPID is the ephemeral shell the harness spawned to run us — it dies
|
||||
# when this script exits. The harness itself is $PPID's parent.
|
||||
OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ')"
|
||||
if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then
|
||||
OWNER_PID="$PPID"
|
||||
fi
|
||||
|
||||
# Foreground mode for environments that reap detached/background processes.
|
||||
if [[ "$FOREGROUND" == "true" ]]; then
|
||||
echo "$$" > "$PID_FILE"
|
||||
env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Start server, capturing output to log file
|
||||
# Use nohup to survive shell exit; disown to remove from job table
|
||||
nohup env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs > "$LOG_FILE" 2>&1 &
|
||||
SERVER_PID=$!
|
||||
disown "$SERVER_PID" 2>/dev/null
|
||||
echo "$SERVER_PID" > "$PID_FILE"
|
||||
|
||||
# Wait for server-started message (check log file)
|
||||
for i in {1..50}; do
|
||||
if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then
|
||||
# Verify server is still alive after a short window (catches process reapers)
|
||||
alive="true"
|
||||
for _ in {1..20}; do
|
||||
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||
alive="false"
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
if [[ "$alive" != "true" ]]; then
|
||||
echo "{\"error\": \"Server started but was killed. Retry in a persistent terminal with: $SCRIPT_DIR/start-server.sh${PROJECT_DIR:+ --project-dir $PROJECT_DIR} --host $BIND_HOST --url-host $URL_HOST --foreground\"}"
|
||||
exit 1
|
||||
fi
|
||||
grep "server-started" "$LOG_FILE" | head -1
|
||||
exit 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
# Timeout - server didn't start
|
||||
echo '{"error": "Server failed to start within 5 seconds"}'
|
||||
exit 1
|
||||
56
skills/brainstorming/scripts/stop-server.sh
Executable file
56
skills/brainstorming/scripts/stop-server.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stop the brainstorm server and clean up
|
||||
# Usage: stop-server.sh <session_dir>
|
||||
#
|
||||
# Kills the server process. Only deletes session directory if it's
|
||||
# under /tmp (ephemeral). Persistent directories (.superpowers/) are
|
||||
# kept so mockups can be reviewed later.
|
||||
|
||||
SESSION_DIR="$1"
|
||||
|
||||
if [[ -z "$SESSION_DIR" ]]; then
|
||||
echo '{"error": "Usage: stop-server.sh <session_dir>"}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STATE_DIR="${SESSION_DIR}/state"
|
||||
PID_FILE="${STATE_DIR}/server.pid"
|
||||
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
pid=$(cat "$PID_FILE")
|
||||
|
||||
# Try to stop gracefully, fallback to force if still alive
|
||||
kill "$pid" 2>/dev/null || true
|
||||
|
||||
# Wait for graceful shutdown (up to ~2s)
|
||||
for i in {1..20}; do
|
||||
if ! kill -0 "$pid" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
# If still running, escalate to SIGKILL
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
|
||||
# Give SIGKILL a moment to take effect
|
||||
sleep 0.1
|
||||
fi
|
||||
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
echo '{"status": "failed", "error": "process still running"}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$PID_FILE" "${STATE_DIR}/server.log"
|
||||
|
||||
# Only delete ephemeral /tmp directories
|
||||
if [[ "$SESSION_DIR" == /tmp/* ]]; then
|
||||
rm -rf "$SESSION_DIR"
|
||||
fi
|
||||
|
||||
echo '{"status": "stopped"}'
|
||||
else
|
||||
echo '{"status": "not_running"}'
|
||||
fi
|
||||
49
skills/brainstorming/spec-document-reviewer-prompt.md
Normal file
49
skills/brainstorming/spec-document-reviewer-prompt.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Spec Document Reviewer Prompt Template
|
||||
|
||||
Use this template when dispatching a spec document reviewer subagent.
|
||||
|
||||
**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning.
|
||||
|
||||
**Dispatch after:** Spec document is written to docs/superpowers/specs/
|
||||
|
||||
```
|
||||
Task tool (general-purpose):
|
||||
description: "Review spec document"
|
||||
prompt: |
|
||||
You are a spec document reviewer. Verify this spec is complete and ready for planning.
|
||||
|
||||
**Spec to review:** [SPEC_FILE_PATH]
|
||||
|
||||
## What to Check
|
||||
|
||||
| Category | What to Look For |
|
||||
|----------|------------------|
|
||||
| Completeness | TODOs, placeholders, "TBD", incomplete sections |
|
||||
| Consistency | Internal contradictions, conflicting requirements |
|
||||
| Clarity | Requirements ambiguous enough to cause someone to build the wrong thing |
|
||||
| Scope | Focused enough for a single plan — not covering multiple independent subsystems |
|
||||
| YAGNI | Unrequested features, over-engineering |
|
||||
|
||||
## Calibration
|
||||
|
||||
**Only flag issues that would cause real problems during implementation planning.**
|
||||
A missing section, a contradiction, or a requirement so ambiguous it could be
|
||||
interpreted two different ways — those are issues. Minor wording improvements,
|
||||
stylistic preferences, and "sections less detailed than others" are not.
|
||||
|
||||
Approve unless there are serious gaps that would lead to a flawed plan.
|
||||
|
||||
## Output Format
|
||||
|
||||
## Spec Review
|
||||
|
||||
**Status:** Approved | Issues Found
|
||||
|
||||
**Issues (if any):**
|
||||
- [Section X]: [specific issue] - [why it matters for planning]
|
||||
|
||||
**Recommendations (advisory, do not block approval):**
|
||||
- [suggestions for improvement]
|
||||
```
|
||||
|
||||
**Reviewer returns:** Status, Issues (if any), Recommendations
|
||||
287
skills/brainstorming/visual-companion.md
Normal file
287
skills/brainstorming/visual-companion.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Visual Companion Guide
|
||||
|
||||
Browser-based visual brainstorming companion for showing mockups, diagrams, and options.
|
||||
|
||||
## When to Use
|
||||
|
||||
Decide per-question, not per-session. The test: **would the user understand this better by seeing it than reading it?**
|
||||
|
||||
**Use the browser** when the content itself is visual:
|
||||
|
||||
- **UI mockups** — wireframes, layouts, navigation structures, component designs
|
||||
- **Architecture diagrams** — system components, data flow, relationship maps
|
||||
- **Side-by-side visual comparisons** — comparing two layouts, two color schemes, two design directions
|
||||
- **Design polish** — when the question is about look and feel, spacing, visual hierarchy
|
||||
- **Spatial relationships** — state machines, flowcharts, entity relationships rendered as diagrams
|
||||
|
||||
**Use the terminal** when the content is text or tabular:
|
||||
|
||||
- **Requirements and scope questions** — "what does X mean?", "which features are in scope?"
|
||||
- **Conceptual A/B/C choices** — picking between approaches described in words
|
||||
- **Tradeoff lists** — pros/cons, comparison tables
|
||||
- **Technical decisions** — API design, data modeling, architectural approach selection
|
||||
- **Clarifying questions** — anything where the answer is words, not a visual preference
|
||||
|
||||
A question *about* a UI topic is not automatically a visual question. "What kind of wizard do you want?" is conceptual — use the terminal. "Which of these wizard layouts feels right?" is visual — use the browser.
|
||||
|
||||
## How It Works
|
||||
|
||||
The server watches a directory for HTML files and serves the newest one to the browser. You write HTML content to `screen_dir`, the user sees it in their browser and can click to select options. Selections are recorded to `state_dir/events` that you read on your next turn.
|
||||
|
||||
**Content fragments vs full documents:** If your HTML file starts with `<!DOCTYPE` or `<html`, the server serves it as-is (just injects the helper script). Otherwise, the server automatically wraps your content in the frame template — adding the header, CSS theme, selection indicator, and all interactive infrastructure. **Write content fragments by default.** Only write full documents when you need complete control over the page.
|
||||
|
||||
## Starting a Session
|
||||
|
||||
```bash
|
||||
# Start server with persistence (mockups saved to project)
|
||||
scripts/start-server.sh --project-dir /path/to/project
|
||||
|
||||
# Returns: {"type":"server-started","port":52341,"url":"http://localhost:52341",
|
||||
# "screen_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000/content",
|
||||
# "state_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000/state"}
|
||||
```
|
||||
|
||||
Save `screen_dir` and `state_dir` from the response. Tell user to open the URL.
|
||||
|
||||
**Finding connection info:** The server writes its startup JSON to `$STATE_DIR/server-info`. If you launched the server in the background and didn't capture stdout, read that file to get the URL and port. When using `--project-dir`, check `<project>/.superpowers/brainstorm/` for the session directory.
|
||||
|
||||
**Note:** Pass the project root as `--project-dir` so mockups persist in `.superpowers/brainstorm/` and survive server restarts. Without it, files go to `/tmp` and get cleaned up. Remind the user to add `.superpowers/` to `.gitignore` if it's not already there.
|
||||
|
||||
**Launching the server by platform:**
|
||||
|
||||
**Claude Code (macOS / Linux):**
|
||||
```bash
|
||||
# Default mode works — the script backgrounds the server itself
|
||||
scripts/start-server.sh --project-dir /path/to/project
|
||||
```
|
||||
|
||||
**Claude Code (Windows):**
|
||||
```bash
|
||||
# Windows auto-detects and uses foreground mode, which blocks the tool call.
|
||||
# Use run_in_background: true on the Bash tool call so the server survives
|
||||
# across conversation turns.
|
||||
scripts/start-server.sh --project-dir /path/to/project
|
||||
```
|
||||
When calling this via the Bash tool, set `run_in_background: true`. Then read `$STATE_DIR/server-info` on the next turn to get the URL and port.
|
||||
|
||||
**Codex:**
|
||||
```bash
|
||||
# Codex reaps background processes. The script auto-detects CODEX_CI and
|
||||
# switches to foreground mode. Run it normally — no extra flags needed.
|
||||
scripts/start-server.sh --project-dir /path/to/project
|
||||
```
|
||||
|
||||
**Gemini CLI:**
|
||||
```bash
|
||||
# Use --foreground and set is_background: true on your shell tool call
|
||||
# so the process survives across turns
|
||||
scripts/start-server.sh --project-dir /path/to/project --foreground
|
||||
```
|
||||
|
||||
**Other environments:** The server must keep running in the background across conversation turns. If your environment reaps detached processes, use `--foreground` and launch the command with your platform's background execution mechanism.
|
||||
|
||||
If the URL is unreachable from your browser (common in remote/containerized setups), bind a non-loopback host:
|
||||
|
||||
```bash
|
||||
scripts/start-server.sh \
|
||||
--project-dir /path/to/project \
|
||||
--host 0.0.0.0 \
|
||||
--url-host localhost
|
||||
```
|
||||
|
||||
Use `--url-host` to control what hostname is printed in the returned URL JSON.
|
||||
|
||||
## The Loop
|
||||
|
||||
1. **Check server is alive**, then **write HTML** to a new file in `screen_dir`:
|
||||
- Before each write, check that `$STATE_DIR/server-info` exists. If it doesn't (or `$STATE_DIR/server-stopped` exists), the server has shut down — restart it with `start-server.sh` before continuing. The server auto-exits after 30 minutes of inactivity.
|
||||
- Use semantic filenames: `platform.html`, `visual-style.html`, `layout.html`
|
||||
- **Never reuse filenames** — each screen gets a fresh file
|
||||
- Use Write tool — **never use cat/heredoc** (dumps noise into terminal)
|
||||
- Server automatically serves the newest file
|
||||
|
||||
2. **Tell user what to expect and end your turn:**
|
||||
- Remind them of the URL (every step, not just first)
|
||||
- Give a brief text summary of what's on screen (e.g., "Showing 3 layout options for the homepage")
|
||||
- Ask them to respond in the terminal: "Take a look and let me know what you think. Click to select an option if you'd like."
|
||||
|
||||
3. **On your next turn** — after the user responds in the terminal:
|
||||
- Read `$STATE_DIR/events` if it exists — this contains the user's browser interactions (clicks, selections) as JSON lines
|
||||
- Merge with the user's terminal text to get the full picture
|
||||
- The terminal message is the primary feedback; `state_dir/events` provides structured interaction data
|
||||
|
||||
4. **Iterate or advance** — if feedback changes current screen, write a new file (e.g., `layout-v2.html`). Only move to the next question when the current step is validated.
|
||||
|
||||
5. **Unload when returning to terminal** — when the next step doesn't need the browser (e.g., a clarifying question, a tradeoff discussion), push a waiting screen to clear the stale content:
|
||||
|
||||
```html
|
||||
<!-- filename: waiting.html (or waiting-2.html, etc.) -->
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
||||
<p class="subtitle">Continuing in terminal...</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
This prevents the user from staring at a resolved choice while the conversation has moved on. When the next visual question comes up, push a new content file as usual.
|
||||
|
||||
6. Repeat until done.
|
||||
|
||||
## Writing Content Fragments
|
||||
|
||||
Write just the content that goes inside the page. The server wraps it in the frame template automatically (header, theme CSS, selection indicator, and all interactive infrastructure).
|
||||
|
||||
**Minimal example:**
|
||||
|
||||
```html
|
||||
<h2>Which layout works better?</h2>
|
||||
<p class="subtitle">Consider readability and visual hierarchy</p>
|
||||
|
||||
<div class="options">
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>Single Column</h3>
|
||||
<p>Clean, focused reading experience</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option" data-choice="b" onclick="toggleSelect(this)">
|
||||
<div class="letter">B</div>
|
||||
<div class="content">
|
||||
<h3>Two Column</h3>
|
||||
<p>Sidebar navigation with main content</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
That's it. No `<html>`, no CSS, no `<script>` tags needed. The server provides all of that.
|
||||
|
||||
## CSS Classes Available
|
||||
|
||||
The frame template provides these CSS classes for your content:
|
||||
|
||||
### Options (A/B/C choices)
|
||||
|
||||
```html
|
||||
<div class="options">
|
||||
<div class="option" data-choice="a" onclick="toggleSelect(this)">
|
||||
<div class="letter">A</div>
|
||||
<div class="content">
|
||||
<h3>Title</h3>
|
||||
<p>Description</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Multi-select:** Add `data-multiselect` to the container to let users select multiple options. Each click toggles the item. The indicator bar shows the count.
|
||||
|
||||
```html
|
||||
<div class="options" data-multiselect>
|
||||
<!-- same option markup — users can select/deselect multiple -->
|
||||
</div>
|
||||
```
|
||||
|
||||
### Cards (visual designs)
|
||||
|
||||
```html
|
||||
<div class="cards">
|
||||
<div class="card" data-choice="design1" onclick="toggleSelect(this)">
|
||||
<div class="card-image"><!-- mockup content --></div>
|
||||
<div class="card-body">
|
||||
<h3>Name</h3>
|
||||
<p>Description</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Mockup container
|
||||
|
||||
```html
|
||||
<div class="mockup">
|
||||
<div class="mockup-header">Preview: Dashboard Layout</div>
|
||||
<div class="mockup-body"><!-- your mockup HTML --></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Split view (side-by-side)
|
||||
|
||||
```html
|
||||
<div class="split">
|
||||
<div class="mockup"><!-- left --></div>
|
||||
<div class="mockup"><!-- right --></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Pros/Cons
|
||||
|
||||
```html
|
||||
<div class="pros-cons">
|
||||
<div class="pros"><h4>Pros</h4><ul><li>Benefit</li></ul></div>
|
||||
<div class="cons"><h4>Cons</h4><ul><li>Drawback</li></ul></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Mock elements (wireframe building blocks)
|
||||
|
||||
```html
|
||||
<div class="mock-nav">Logo | Home | About | Contact</div>
|
||||
<div style="display: flex;">
|
||||
<div class="mock-sidebar">Navigation</div>
|
||||
<div class="mock-content">Main content area</div>
|
||||
</div>
|
||||
<button class="mock-button">Action Button</button>
|
||||
<input class="mock-input" placeholder="Input field">
|
||||
<div class="placeholder">Placeholder area</div>
|
||||
```
|
||||
|
||||
### Typography and sections
|
||||
|
||||
- `h2` — page title
|
||||
- `h3` — section heading
|
||||
- `.subtitle` — secondary text below title
|
||||
- `.section` — content block with bottom margin
|
||||
- `.label` — small uppercase label text
|
||||
|
||||
## Browser Events Format
|
||||
|
||||
When the user clicks options in the browser, their interactions are recorded to `$STATE_DIR/events` (one JSON object per line). The file is cleared automatically when you push a new screen.
|
||||
|
||||
```jsonl
|
||||
{"type":"click","choice":"a","text":"Option A - Simple Layout","timestamp":1706000101}
|
||||
{"type":"click","choice":"c","text":"Option C - Complex Grid","timestamp":1706000108}
|
||||
{"type":"click","choice":"b","text":"Option B - Hybrid","timestamp":1706000115}
|
||||
```
|
||||
|
||||
The full event stream shows the user's exploration path — they may click multiple options before settling. The last `choice` event is typically the final selection, but the pattern of clicks can reveal hesitation or preferences worth asking about.
|
||||
|
||||
If `$STATE_DIR/events` doesn't exist, the user didn't interact with the browser — use only their terminal text.
|
||||
|
||||
## Design Tips
|
||||
|
||||
- **Scale fidelity to the question** — wireframes for layout, polish for polish questions
|
||||
- **Explain the question on each page** — "Which layout feels more professional?" not just "Pick one"
|
||||
- **Iterate before advancing** — if feedback changes current screen, write a new version
|
||||
- **2-4 options max** per screen
|
||||
- **Use real content when it matters** — for a photography portfolio, use actual images (Unsplash). Placeholder content obscures design issues.
|
||||
- **Keep mockups simple** — focus on layout and structure, not pixel-perfect design
|
||||
|
||||
## File Naming
|
||||
|
||||
- Use semantic names: `platform.html`, `visual-style.html`, `layout.html`
|
||||
- Never reuse filenames — each screen must be a new file
|
||||
- For iterations: append version suffix like `layout-v2.html`, `layout-v3.html`
|
||||
- Server serves newest file by modification time
|
||||
|
||||
## Cleaning Up
|
||||
|
||||
```bash
|
||||
scripts/stop-server.sh $SESSION_DIR
|
||||
```
|
||||
|
||||
If the session used `--project-dir`, mockup files persist in `.superpowers/brainstorm/` for later reference. Only `/tmp` sessions get deleted on stop.
|
||||
|
||||
## Reference
|
||||
|
||||
- Frame template (CSS reference): `scripts/frame-template.html`
|
||||
- Helper script (client-side): `scripts/helper.js`
|
||||
@@ -7,6 +7,8 @@ description: Use when facing 2+ independent tasks that can be worked on without
|
||||
|
||||
## Overview
|
||||
|
||||
You delegate tasks to specialized agents with isolated context. By precisely crafting their instructions and context, you ensure they stay focused and succeed at their task. They should never inherit your session's context or history — you construct exactly what they need. This also preserves your own context for coordination work.
|
||||
|
||||
When you have multiple unrelated failures (different test files, different subsystems, different bugs), investigating them sequentially wastes time. Each investigation is independent and can happen in parallel.
|
||||
|
||||
**Core principle:** Dispatch one agent per independent problem domain. Let them work concurrently.
|
||||
|
||||
@@ -7,12 +7,12 @@ description: Use when you have a written implementation plan to execute in a sep
|
||||
|
||||
## Overview
|
||||
|
||||
Load plan, review critically, execute tasks in batches, report for review between batches.
|
||||
|
||||
**Core principle:** Batch execution with checkpoints for architect review.
|
||||
Load plan, review critically, execute all tasks, report when complete.
|
||||
|
||||
**Announce at start:** "I'm using the executing-plans skill to implement this plan."
|
||||
|
||||
**Note:** Tell your human partner that Superpowers works much better with access to subagents. The quality of its work will be significantly higher if run on a platform with subagent support (such as Claude Code or Codex). If subagents are available, use superpowers:subagent-driven-development instead of this skill.
|
||||
|
||||
## The Process
|
||||
|
||||
### Step 1: Load and Review Plan
|
||||
@@ -21,8 +21,7 @@ Load plan, review critically, execute tasks in batches, report for review betwee
|
||||
3. If concerns: Raise them with your human partner before starting
|
||||
4. If no concerns: Create TodoWrite and proceed
|
||||
|
||||
### Step 2: Execute Batch
|
||||
**Default: First 3 tasks**
|
||||
### Step 2: Execute Tasks
|
||||
|
||||
For each task:
|
||||
1. Mark as in_progress
|
||||
@@ -30,19 +29,7 @@ For each task:
|
||||
3. Run verifications as specified
|
||||
4. Mark as completed
|
||||
|
||||
### Step 3: Report
|
||||
When batch complete:
|
||||
- Show what was implemented
|
||||
- Show verification output
|
||||
- Say: "Ready for feedback."
|
||||
|
||||
### Step 4: Continue
|
||||
Based on feedback:
|
||||
- Apply changes if needed
|
||||
- Execute next batch
|
||||
- Repeat until complete
|
||||
|
||||
### Step 5: Complete Development
|
||||
### Step 3: Complete Development
|
||||
|
||||
After all tasks complete and verified:
|
||||
- Announce: "I'm using the finishing-a-development-branch skill to complete this work."
|
||||
@@ -52,7 +39,7 @@ After all tasks complete and verified:
|
||||
## When to Stop and Ask for Help
|
||||
|
||||
**STOP executing immediately when:**
|
||||
- Hit a blocker mid-batch (missing dependency, test fails, instruction unclear)
|
||||
- Hit a blocker (missing dependency, test fails, instruction unclear)
|
||||
- Plan has critical gaps preventing starting
|
||||
- You don't understand an instruction
|
||||
- Verification fails repeatedly
|
||||
@@ -72,5 +59,12 @@ After all tasks complete and verified:
|
||||
- Follow plan steps exactly
|
||||
- Don't skip verifications
|
||||
- Reference skills when plan says to
|
||||
- Between batches: just report and wait
|
||||
- Stop when blocked, don't guess
|
||||
- Never start implementation on main/master branch without explicit user consent
|
||||
|
||||
## Integration
|
||||
|
||||
**Required workflow skills:**
|
||||
- **superpowers:using-git-worktrees** - Ensures isolated workspace (creates one or verifies existing)
|
||||
- **superpowers:writing-plans** - Creates the plan this skill executes
|
||||
- **superpowers:finishing-a-development-branch** - Complete development after all tasks
|
||||
|
||||
@@ -9,7 +9,7 @@ description: Use when implementation is complete, all tests pass, and you need t
|
||||
|
||||
Guide completion of development work by presenting clear options and handling chosen workflow.
|
||||
|
||||
**Core principle:** Verify tests → Present options → Execute choice → Clean up.
|
||||
**Core principle:** Verify tests → Detect environment → Present options → Execute choice → Clean up.
|
||||
|
||||
**Announce at start:** "I'm using the finishing-a-development-branch skill to complete this work."
|
||||
|
||||
@@ -37,7 +37,24 @@ Stop. Don't proceed to Step 2.
|
||||
|
||||
**If tests pass:** Continue to Step 2.
|
||||
|
||||
### Step 2: Determine Base Branch
|
||||
### Step 2: Detect Environment
|
||||
|
||||
**Determine workspace state before presenting options:**
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
```
|
||||
|
||||
This determines which menu to show and how cleanup works:
|
||||
|
||||
| State | Menu | Cleanup |
|
||||
|-------|------|---------|
|
||||
| `GIT_DIR == GIT_COMMON` (normal repo) | Standard 4 options | No worktree to clean up |
|
||||
| `GIT_DIR != GIT_COMMON`, named branch | Standard 4 options | Provenance-based (see Step 6) |
|
||||
| `GIT_DIR != GIT_COMMON`, detached HEAD | Reduced 3 options (no merge) | No cleanup (externally managed) |
|
||||
|
||||
### Step 3: Determine Base Branch
|
||||
|
||||
```bash
|
||||
# Try common base branches
|
||||
@@ -46,9 +63,9 @@ git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null
|
||||
|
||||
Or ask: "This branch split from main - is that correct?"
|
||||
|
||||
### Step 3: Present Options
|
||||
### Step 4: Present Options
|
||||
|
||||
Present exactly these 4 options:
|
||||
**Normal repo and named-branch worktree — present exactly these 4 options:**
|
||||
|
||||
```
|
||||
Implementation complete. What would you like to do?
|
||||
@@ -61,30 +78,45 @@ Implementation complete. What would you like to do?
|
||||
Which option?
|
||||
```
|
||||
|
||||
**Detached HEAD — present exactly these 3 options:**
|
||||
|
||||
```
|
||||
Implementation complete. You're on a detached HEAD (externally managed workspace).
|
||||
|
||||
1. Push as new branch and create a Pull Request
|
||||
2. Keep as-is (I'll handle it later)
|
||||
3. Discard this work
|
||||
|
||||
Which option?
|
||||
```
|
||||
|
||||
**Don't add explanation** - keep options concise.
|
||||
|
||||
### Step 4: Execute Choice
|
||||
### Step 5: Execute Choice
|
||||
|
||||
#### Option 1: Merge Locally
|
||||
|
||||
```bash
|
||||
# Switch to base branch
|
||||
# Get main repo root for CWD safety
|
||||
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
||||
cd "$MAIN_ROOT"
|
||||
|
||||
# Merge first — verify success before removing anything
|
||||
git checkout <base-branch>
|
||||
|
||||
# Pull latest
|
||||
git pull
|
||||
|
||||
# Merge feature branch
|
||||
git merge <feature-branch>
|
||||
|
||||
# Verify tests on merged result
|
||||
<test command>
|
||||
|
||||
# If tests pass
|
||||
git branch -d <feature-branch>
|
||||
# Only after merge succeeds: cleanup worktree (Step 6), then delete branch
|
||||
```
|
||||
|
||||
Then: Cleanup worktree (Step 5)
|
||||
Then: Cleanup worktree (Step 6), then delete branch:
|
||||
|
||||
```bash
|
||||
git branch -d <feature-branch>
|
||||
```
|
||||
|
||||
#### Option 2: Push and Create PR
|
||||
|
||||
@@ -103,7 +135,7 @@ EOF
|
||||
)"
|
||||
```
|
||||
|
||||
Then: Cleanup worktree (Step 5)
|
||||
**Do NOT clean up worktree** — user needs it alive to iterate on PR feedback.
|
||||
|
||||
#### Option 3: Keep As-Is
|
||||
|
||||
@@ -127,36 +159,46 @@ Wait for exact confirmation.
|
||||
|
||||
If confirmed:
|
||||
```bash
|
||||
git checkout <base-branch>
|
||||
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
||||
cd "$MAIN_ROOT"
|
||||
```
|
||||
|
||||
Then: Cleanup worktree (Step 6), then force-delete branch:
|
||||
```bash
|
||||
git branch -D <feature-branch>
|
||||
```
|
||||
|
||||
Then: Cleanup worktree (Step 5)
|
||||
### Step 6: Cleanup Workspace
|
||||
|
||||
### Step 5: Cleanup Worktree
|
||||
**Only runs for Options 1 and 4.** Options 2 and 3 always preserve the worktree.
|
||||
|
||||
**For Options 1, 2, 4:**
|
||||
|
||||
Check if in worktree:
|
||||
```bash
|
||||
git worktree list | grep $(git branch --show-current)
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
WORKTREE_PATH=$(git rev-parse --show-toplevel)
|
||||
```
|
||||
|
||||
If yes:
|
||||
**If `GIT_DIR == GIT_COMMON`:** Normal repo, no worktree to clean up. Done.
|
||||
|
||||
**If worktree path is under `.worktrees/`, `worktrees/`, or `~/.config/superpowers/worktrees/`:** Superpowers created this worktree — we own cleanup.
|
||||
|
||||
```bash
|
||||
git worktree remove <worktree-path>
|
||||
MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel)
|
||||
cd "$MAIN_ROOT"
|
||||
git worktree remove "$WORKTREE_PATH"
|
||||
git worktree prune # Self-healing: clean up any stale registrations
|
||||
```
|
||||
|
||||
**For Option 3:** Keep worktree.
|
||||
**Otherwise:** The host environment (harness) owns this workspace. Do NOT remove it. If your platform provides a workspace-exit tool, use it. Otherwise, leave the workspace in place.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Option | Merge | Push | Keep Worktree | Cleanup Branch |
|
||||
|--------|-------|------|---------------|----------------|
|
||||
| 1. Merge locally | ✓ | - | - | ✓ |
|
||||
| 2. Create PR | - | ✓ | ✓ | - |
|
||||
| 3. Keep as-is | - | - | ✓ | - |
|
||||
| 4. Discard | - | - | - | ✓ (force) |
|
||||
| 1. Merge locally | yes | - | - | yes |
|
||||
| 2. Create PR | - | yes | yes | - |
|
||||
| 3. Keep as-is | - | - | yes | - |
|
||||
| 4. Discard | - | - | - | yes (force) |
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
@@ -165,13 +207,25 @@ git worktree remove <worktree-path>
|
||||
- **Fix:** Always verify tests before offering options
|
||||
|
||||
**Open-ended questions**
|
||||
- **Problem:** "What should I do next?" → ambiguous
|
||||
- **Fix:** Present exactly 4 structured options
|
||||
- **Problem:** "What should I do next?" is ambiguous
|
||||
- **Fix:** Present exactly 4 structured options (or 3 for detached HEAD)
|
||||
|
||||
**Automatic worktree cleanup**
|
||||
- **Problem:** Remove worktree when might need it (Option 2, 3)
|
||||
**Cleaning up worktree for Option 2**
|
||||
- **Problem:** Remove worktree user needs for PR iteration
|
||||
- **Fix:** Only cleanup for Options 1 and 4
|
||||
|
||||
**Deleting branch before removing worktree**
|
||||
- **Problem:** `git branch -d` fails because worktree still references the branch
|
||||
- **Fix:** Merge first, remove worktree, then delete branch
|
||||
|
||||
**Running git worktree remove from inside the worktree**
|
||||
- **Problem:** Command fails silently when CWD is inside the worktree being removed
|
||||
- **Fix:** Always `cd` to main repo root before `git worktree remove`
|
||||
|
||||
**Cleaning up harness-owned worktrees**
|
||||
- **Problem:** Removing a worktree the harness created causes phantom state
|
||||
- **Fix:** Only clean up worktrees under `.worktrees/`, `worktrees/`, or `~/.config/superpowers/worktrees/`
|
||||
|
||||
**No confirmation for discard**
|
||||
- **Problem:** Accidentally delete work
|
||||
- **Fix:** Require typed "discard" confirmation
|
||||
@@ -183,18 +237,15 @@ git worktree remove <worktree-path>
|
||||
- Merge without verifying tests on result
|
||||
- Delete work without confirmation
|
||||
- Force-push without explicit request
|
||||
- Remove a worktree before confirming merge success
|
||||
- Clean up worktrees you didn't create (provenance check)
|
||||
- Run `git worktree remove` from inside the worktree
|
||||
|
||||
**Always:**
|
||||
- Verify tests before offering options
|
||||
- Present exactly 4 options
|
||||
- Detect environment before presenting menu
|
||||
- Present exactly 4 options (or 3 for detached HEAD)
|
||||
- Get typed confirmation for Option 4
|
||||
- Clean up worktree for Options 1 & 4 only
|
||||
|
||||
## Integration
|
||||
|
||||
**Called by:**
|
||||
- **subagent-driven-development** (Step 7) - After all tasks complete
|
||||
- **executing-plans** (Step 5) - After all batches complete
|
||||
|
||||
**Pairs with:**
|
||||
- **using-git-worktrees** - Cleans up worktree created by that skill
|
||||
- `cd` to main repo root before worktree removal
|
||||
- Run `git worktree prune` after removal
|
||||
|
||||
@@ -200,6 +200,10 @@ You understand 1,2,3,6. Unclear on 4,5.
|
||||
✅ "Understand 1,2,3,6. Need clarification on 4 and 5 before implementing."
|
||||
```
|
||||
|
||||
## GitHub Thread Replies
|
||||
|
||||
When replying to inline review comments on GitHub, reply in the comment thread (`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`), not as a top-level PR comment.
|
||||
|
||||
## The Bottom Line
|
||||
|
||||
**External feedback = suggestions to evaluate, not orders to follow.**
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Use when completing tasks, implementing major features, or before m
|
||||
|
||||
# Requesting Code Review
|
||||
|
||||
Dispatch superpowers:code-reviewer subagent to catch issues before they cascade.
|
||||
Dispatch superpowers:code-reviewer subagent to catch issues before they cascade. The reviewer gets precisely crafted context for evaluation — never your session's history. This keeps the reviewer focused on the work product, not your thought process, and preserves your own context for continued work.
|
||||
|
||||
**Core principle:** Review early, review often.
|
||||
|
||||
@@ -58,7 +58,7 @@ HEAD_SHA=$(git rev-parse HEAD)
|
||||
|
||||
[Dispatch superpowers:code-reviewer subagent]
|
||||
WHAT_WAS_IMPLEMENTED: Verification and repair functions for conversation index
|
||||
PLAN_OR_REQUIREMENTS: Task 2 from docs/plans/deployment-plan.md
|
||||
PLAN_OR_REQUIREMENTS: Task 2 from docs/superpowers/plans/deployment-plan.md
|
||||
BASE_SHA: a7981ec
|
||||
HEAD_SHA: 3df7661
|
||||
DESCRIPTION: Added verifyIndex() and repairIndex() with 4 issue types
|
||||
@@ -82,7 +82,7 @@ You: [Fix progress indicators]
|
||||
- Fix before moving to next task
|
||||
|
||||
**Executing Plans:**
|
||||
- Review after each batch (3 tasks)
|
||||
- Review after each task or at natural checkpoints
|
||||
- Get feedback, apply, continue
|
||||
|
||||
**Ad-Hoc Development:**
|
||||
|
||||
@@ -7,8 +7,12 @@ description: Use when executing implementation plans with independent tasks in t
|
||||
|
||||
Execute plan by dispatching fresh subagent per task, with two-stage review after each: spec compliance review first, then code quality review.
|
||||
|
||||
**Why subagents:** You delegate tasks to specialized agents with isolated context. By precisely crafting their instructions and context, you ensure they stay focused and succeed at their task. They should never inherit your session's context or history — you construct exactly what they need. This also preserves your own context for coordination work.
|
||||
|
||||
**Core principle:** Fresh subagent per task + two-stage review (spec then quality) = high quality, fast iteration
|
||||
|
||||
**Continuous execution:** Do not pause to check in with your human partner between tasks. Execute all tasks from the plan without stopping. The only reasons to stop are: BLOCKED status you cannot resolve, ambiguity that genuinely prevents progress, or all tasks complete. "Should I continue?" prompts and progress summaries waste their time — they asked you to execute the plan, so execute it.
|
||||
|
||||
## When to Use
|
||||
|
||||
```dot
|
||||
@@ -82,6 +86,39 @@ digraph process {
|
||||
}
|
||||
```
|
||||
|
||||
## Model Selection
|
||||
|
||||
Use the least powerful model that can handle each role to conserve cost and increase speed.
|
||||
|
||||
**Mechanical implementation tasks** (isolated functions, clear specs, 1-2 files): use a fast, cheap model. Most implementation tasks are mechanical when the plan is well-specified.
|
||||
|
||||
**Integration and judgment tasks** (multi-file coordination, pattern matching, debugging): use a standard model.
|
||||
|
||||
**Architecture, design, and review tasks**: use the most capable available model.
|
||||
|
||||
**Task complexity signals:**
|
||||
- Touches 1-2 files with a complete spec → cheap model
|
||||
- Touches multiple files with integration concerns → standard model
|
||||
- Requires design judgment or broad codebase understanding → most capable model
|
||||
|
||||
## Handling Implementer Status
|
||||
|
||||
Implementer subagents report one of four statuses. Handle each appropriately:
|
||||
|
||||
**DONE:** Proceed to spec compliance review.
|
||||
|
||||
**DONE_WITH_CONCERNS:** The implementer completed the work but flagged doubts. Read the concerns before proceeding. If the concerns are about correctness or scope, address them before review. If they're observations (e.g., "this file is getting large"), note them and proceed to review.
|
||||
|
||||
**NEEDS_CONTEXT:** The implementer needs information that wasn't provided. Provide the missing context and re-dispatch.
|
||||
|
||||
**BLOCKED:** The implementer cannot complete the task. Assess the blocker:
|
||||
1. If it's a context problem, provide more context and re-dispatch with the same model
|
||||
2. If the task requires more reasoning, re-dispatch with a more capable model
|
||||
3. If the task is too large, break it into smaller pieces
|
||||
4. If the plan itself is wrong, escalate to the human
|
||||
|
||||
**Never** ignore an escalation or force the same model to retry without changes. If the implementer said it's stuck, something needs to change.
|
||||
|
||||
## Prompt Templates
|
||||
|
||||
- `./implementer-prompt.md` - Dispatch implementer subagent
|
||||
@@ -93,7 +130,7 @@ digraph process {
|
||||
```
|
||||
You: I'm using Subagent-Driven Development to execute this plan.
|
||||
|
||||
[Read plan file once: docs/plans/feature-plan.md]
|
||||
[Read plan file once: docs/superpowers/plans/feature-plan.md]
|
||||
[Extract all 5 tasks with full text and context]
|
||||
[Create TodoWrite with all tasks]
|
||||
|
||||
@@ -199,6 +236,7 @@ Done!
|
||||
## Red Flags
|
||||
|
||||
**Never:**
|
||||
- Start implementation on main/master branch without explicit user consent
|
||||
- Skip reviews (spec compliance OR code quality)
|
||||
- Proceed with unfixed issues
|
||||
- Dispatch multiple implementation subagents in parallel (conflicts)
|
||||
@@ -229,6 +267,7 @@ Done!
|
||||
## Integration
|
||||
|
||||
**Required workflow skills:**
|
||||
- **superpowers:using-git-worktrees** - Ensures isolated workspace (creates one or verifies existing)
|
||||
- **superpowers:writing-plans** - Creates the plan this skill executes
|
||||
- **superpowers:requesting-code-review** - Code review template for reviewer subagents
|
||||
- **superpowers:finishing-a-development-branch** - Complete development after all tasks
|
||||
|
||||
@@ -17,4 +17,10 @@ Task tool (superpowers:code-reviewer):
|
||||
DESCRIPTION: [task summary]
|
||||
```
|
||||
|
||||
**In addition to standard code quality concerns, the reviewer should check:**
|
||||
- Does each file have one clear responsibility with a well-defined interface?
|
||||
- Are units decomposed so they can be understood and tested independently?
|
||||
- Is the implementation following the file structure from the plan?
|
||||
- Did this implementation create new files that are already large, or significantly grow existing files? (Don't flag pre-existing file sizes — focus on what this change contributed.)
|
||||
|
||||
**Code reviewer returns:** Strengths, Issues (Critical/Important/Minor), Assessment
|
||||
|
||||
@@ -41,6 +41,36 @@ Task tool (general-purpose):
|
||||
**While you work:** If you encounter something unexpected or unclear, **ask questions**.
|
||||
It's always OK to pause and clarify. Don't guess or make assumptions.
|
||||
|
||||
## Code Organization
|
||||
|
||||
You reason best about code you can hold in context at once, and your edits are more
|
||||
reliable when files are focused. Keep this in mind:
|
||||
- Follow the file structure defined in the plan
|
||||
- Each file should have one clear responsibility with a well-defined interface
|
||||
- If a file you're creating is growing beyond the plan's intent, stop and report
|
||||
it as DONE_WITH_CONCERNS — don't split files on your own without plan guidance
|
||||
- If an existing file you're modifying is already large or tangled, work carefully
|
||||
and note it as a concern in your report
|
||||
- In existing codebases, follow established patterns. Improve code you're touching
|
||||
the way a good developer would, but don't restructure things outside your task.
|
||||
|
||||
## When You're in Over Your Head
|
||||
|
||||
It is always OK to stop and say "this is too hard for me." Bad work is worse than
|
||||
no work. You will not be penalized for escalating.
|
||||
|
||||
**STOP and escalate when:**
|
||||
- The task requires architectural decisions with multiple valid approaches
|
||||
- You need to understand code beyond what was provided and can't find clarity
|
||||
- You feel uncertain about whether your approach is correct
|
||||
- The task involves restructuring existing code in ways the plan didn't anticipate
|
||||
- You've been reading file after file trying to understand the system without progress
|
||||
|
||||
**How to escalate:** Report back with status BLOCKED or NEEDS_CONTEXT. Describe
|
||||
specifically what you're stuck on, what you've tried, and what kind of help you need.
|
||||
The controller can provide more context, re-dispatch with a more capable model,
|
||||
or break the task into smaller pieces.
|
||||
|
||||
## Before Reporting Back: Self-Review
|
||||
|
||||
Review your work with fresh eyes. Ask yourself:
|
||||
@@ -70,9 +100,14 @@ Task tool (general-purpose):
|
||||
## Report Format
|
||||
|
||||
When done, report:
|
||||
- What you implemented
|
||||
- **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
|
||||
- What you implemented (or what you attempted, if blocked)
|
||||
- What you tested and test results
|
||||
- Files changed
|
||||
- Self-review findings (if any)
|
||||
- Any issues or concerns
|
||||
|
||||
Use DONE_WITH_CONCERNS if you completed the work but have doubts about correctness.
|
||||
Use BLOCKED if you cannot complete the task. Use NEEDS_CONTEXT if you need
|
||||
information that wasn't provided. Never silently produce work you're unsure about.
|
||||
```
|
||||
|
||||
@@ -4,7 +4,7 @@ Reference example of extracting, structuring, and bulletproofing a critical skil
|
||||
|
||||
## Source Material
|
||||
|
||||
Extracted debugging framework from `/Users/jesse/.claude/CLAUDE.md`:
|
||||
Extracted debugging framework from `~/.claude/CLAUDE.md`:
|
||||
- 4-phase systematic process (Investigation → Pattern Analysis → Hypothesis → Implementation)
|
||||
- Core mandate: ALWAYS find root cause, NEVER fix symptoms
|
||||
- Rules designed to resist time pressure and rationalization
|
||||
|
||||
@@ -33,7 +33,7 @@ digraph when_to_use {
|
||||
|
||||
### 1. Observe the Symptom
|
||||
```
|
||||
Error: git init failed in /Users/jesse/project/packages/core
|
||||
Error: git init failed in ~/project/packages/core
|
||||
```
|
||||
|
||||
### 2. Find Immediate Cause
|
||||
|
||||
@@ -1,104 +1,117 @@
|
||||
---
|
||||
name: using-git-worktrees
|
||||
description: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
|
||||
description: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback
|
||||
---
|
||||
|
||||
# Using Git Worktrees
|
||||
|
||||
## Overview
|
||||
|
||||
Git worktrees create isolated workspaces sharing the same repository, allowing work on multiple branches simultaneously without switching.
|
||||
Ensure work happens in an isolated workspace. Prefer your platform's native worktree tools. Fall back to manual git worktrees only when no native tool is available.
|
||||
|
||||
**Core principle:** Systematic directory selection + safety verification = reliable isolation.
|
||||
**Core principle:** Detect existing isolation first. Then use native tools. Then fall back to git. Never fight the harness.
|
||||
|
||||
**Announce at start:** "I'm using the using-git-worktrees skill to set up an isolated workspace."
|
||||
|
||||
## Directory Selection Process
|
||||
## Step 0: Detect Existing Isolation
|
||||
|
||||
Follow this priority order:
|
||||
|
||||
### 1. Check Existing Directories
|
||||
**Before creating anything, check if you are already in an isolated workspace.**
|
||||
|
||||
```bash
|
||||
# Check in priority order
|
||||
ls -d .worktrees 2>/dev/null # Preferred (hidden)
|
||||
ls -d worktrees 2>/dev/null # Alternative
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
**If found:** Use that directory. If both exist, `.worktrees` wins.
|
||||
|
||||
### 2. Check CLAUDE.md
|
||||
**Submodule guard:** `GIT_DIR != GIT_COMMON` is also true inside git submodules. Before concluding "already in a worktree," verify you are not in a submodule:
|
||||
|
||||
```bash
|
||||
grep -i "worktree.*director" CLAUDE.md 2>/dev/null
|
||||
# If this returns a path, you're in a submodule, not a worktree — treat as normal repo
|
||||
git rev-parse --show-superproject-working-tree 2>/dev/null
|
||||
```
|
||||
|
||||
**If preference specified:** Use it without asking.
|
||||
**If `GIT_DIR != GIT_COMMON` (and not a submodule):** You are already in a linked worktree. Skip to Step 3 (Project Setup). Do NOT create another worktree.
|
||||
|
||||
### 3. Ask User
|
||||
Report with branch state:
|
||||
- On a branch: "Already in isolated workspace at `<path>` on branch `<name>`."
|
||||
- Detached HEAD: "Already in isolated workspace at `<path>` (detached HEAD, externally managed). Branch creation needed at finish time."
|
||||
|
||||
If no directory exists and no CLAUDE.md preference:
|
||||
**If `GIT_DIR == GIT_COMMON` (or in a submodule):** You are in a normal repo checkout.
|
||||
|
||||
```
|
||||
No worktree directory found. Where should I create worktrees?
|
||||
Has the user already indicated their worktree preference in your instructions? If not, ask for consent before creating a worktree:
|
||||
|
||||
1. .worktrees/ (project-local, hidden)
|
||||
2. ~/.config/superpowers/worktrees/<project-name>/ (global location)
|
||||
> "Would you like me to set up an isolated worktree? It protects your current branch from changes."
|
||||
|
||||
Which would you prefer?
|
||||
```
|
||||
Honor any existing declared preference without asking. If the user declines consent, work in place and skip to Step 3.
|
||||
|
||||
## Safety Verification
|
||||
## Step 1: Create Isolated Workspace
|
||||
|
||||
### For Project-Local Directories (.worktrees or worktrees)
|
||||
**You have two mechanisms. Try them in this order.**
|
||||
|
||||
**MUST verify .gitignore before creating worktree:**
|
||||
### 1a. Native Worktree Tools (preferred)
|
||||
|
||||
The user has asked for an isolated workspace (Step 0 consent). Do you already have a way to create a worktree? It might be a tool with a name like `EnterWorktree`, `WorktreeCreate`, a `/worktree` command, or a `--worktree` flag. If you do, use it and skip to Step 3.
|
||||
|
||||
Native tools handle directory placement, branch creation, and cleanup automatically. Using `git worktree add` when you have a native tool creates phantom state your harness can't see or manage.
|
||||
|
||||
Only proceed to Step 1b if you have no native worktree tool available.
|
||||
|
||||
### 1b. Git Worktree Fallback
|
||||
|
||||
**Only use this if Step 1a does not apply** — you have no native worktree tool available. Create a worktree manually using git.
|
||||
|
||||
#### Directory Selection
|
||||
|
||||
Follow this priority order. Explicit user preference always beats observed filesystem state.
|
||||
|
||||
1. **Check your instructions for a declared worktree directory preference.** If the user has already specified one, use it without asking.
|
||||
|
||||
2. **Check for an existing project-local worktree directory:**
|
||||
```bash
|
||||
ls -d .worktrees 2>/dev/null # Preferred (hidden)
|
||||
ls -d worktrees 2>/dev/null # Alternative
|
||||
```
|
||||
If found, use it. If both exist, `.worktrees` wins.
|
||||
|
||||
3. **Check for an existing global directory:**
|
||||
```bash
|
||||
project=$(basename "$(git rev-parse --show-toplevel)")
|
||||
ls -d ~/.config/superpowers/worktrees/$project 2>/dev/null
|
||||
```
|
||||
If found, use it (backward compatibility with legacy global path).
|
||||
|
||||
4. **If there is no other guidance available**, default to `.worktrees/` at the project root.
|
||||
|
||||
#### Safety Verification (project-local directories only)
|
||||
|
||||
**MUST verify directory is ignored before creating worktree:**
|
||||
|
||||
```bash
|
||||
# Check if directory pattern in .gitignore
|
||||
grep -q "^\.worktrees/$" .gitignore || grep -q "^worktrees/$" .gitignore
|
||||
git check-ignore -q .worktrees 2>/dev/null || git check-ignore -q worktrees 2>/dev/null
|
||||
```
|
||||
|
||||
**If NOT in .gitignore:**
|
||||
|
||||
Per Jesse's rule "Fix broken things immediately":
|
||||
1. Add appropriate line to .gitignore
|
||||
2. Commit the change
|
||||
3. Proceed with worktree creation
|
||||
**If NOT ignored:** Add to .gitignore, commit the change, then proceed.
|
||||
|
||||
**Why critical:** Prevents accidentally committing worktree contents to repository.
|
||||
|
||||
### For Global Directory (~/.config/superpowers/worktrees)
|
||||
Global directories (`~/.config/superpowers/worktrees/`) need no verification.
|
||||
|
||||
No .gitignore verification needed - outside project entirely.
|
||||
|
||||
## Creation Steps
|
||||
|
||||
### 1. Detect Project Name
|
||||
#### Create the Worktree
|
||||
|
||||
```bash
|
||||
project=$(basename "$(git rev-parse --show-toplevel)")
|
||||
```
|
||||
|
||||
### 2. Create Worktree
|
||||
# Determine path based on chosen location
|
||||
# For project-local: path="$LOCATION/$BRANCH_NAME"
|
||||
# For global: path="~/.config/superpowers/worktrees/$project/$BRANCH_NAME"
|
||||
|
||||
```bash
|
||||
# Determine full path
|
||||
case $LOCATION in
|
||||
.worktrees|worktrees)
|
||||
path="$LOCATION/$BRANCH_NAME"
|
||||
;;
|
||||
~/.config/superpowers/worktrees/*)
|
||||
path="~/.config/superpowers/worktrees/$project/$BRANCH_NAME"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create worktree with new branch
|
||||
git worktree add "$path" -b "$BRANCH_NAME"
|
||||
cd "$path"
|
||||
```
|
||||
|
||||
### 3. Run Project Setup
|
||||
**Sandbox fallback:** If `git worktree add` fails with a permission error (sandbox denial), tell the user the sandbox blocked worktree creation and you're working in the current directory instead. Then run setup and baseline tests in place.
|
||||
|
||||
## Step 3: Project Setup
|
||||
|
||||
Auto-detect and run appropriate setup:
|
||||
|
||||
@@ -117,23 +130,20 @@ if [ -f pyproject.toml ]; then poetry install; fi
|
||||
if [ -f go.mod ]; then go mod download; fi
|
||||
```
|
||||
|
||||
### 4. Verify Clean Baseline
|
||||
## Step 4: Verify Clean Baseline
|
||||
|
||||
Run tests to ensure worktree starts clean:
|
||||
Run tests to ensure workspace starts clean:
|
||||
|
||||
```bash
|
||||
# Examples - use project-appropriate command
|
||||
npm test
|
||||
cargo test
|
||||
pytest
|
||||
go test ./...
|
||||
# Use project-appropriate command
|
||||
npm test / cargo test / pytest / go test ./...
|
||||
```
|
||||
|
||||
**If tests fail:** Report failures, ask whether to proceed or investigate.
|
||||
|
||||
**If tests pass:** Report ready.
|
||||
|
||||
### 5. Report Location
|
||||
### Report
|
||||
|
||||
```
|
||||
Worktree ready at <full-path>
|
||||
@@ -145,69 +155,61 @@ Ready to implement <feature-name>
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| `.worktrees/` exists | Use it (verify .gitignore) |
|
||||
| `worktrees/` exists | Use it (verify .gitignore) |
|
||||
| Already in linked worktree | Skip creation (Step 0) |
|
||||
| In a submodule | Treat as normal repo (Step 0 guard) |
|
||||
| Native worktree tool available | Use it (Step 1a) |
|
||||
| No native tool | Git worktree fallback (Step 1b) |
|
||||
| `.worktrees/` exists | Use it (verify ignored) |
|
||||
| `worktrees/` exists | Use it (verify ignored) |
|
||||
| Both exist | Use `.worktrees/` |
|
||||
| Neither exists | Check CLAUDE.md → Ask user |
|
||||
| Directory not in .gitignore | Add it immediately + commit |
|
||||
| Neither exists | Check instruction file, then default `.worktrees/` |
|
||||
| Global path exists | Use it (backward compat) |
|
||||
| Directory not ignored | Add to .gitignore + commit |
|
||||
| Permission error on create | Sandbox fallback, work in place |
|
||||
| Tests fail during baseline | Report failures + ask |
|
||||
| No package.json/Cargo.toml | Skip dependency install |
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
**Skipping .gitignore verification**
|
||||
### Fighting the harness
|
||||
|
||||
- **Problem:** Using `git worktree add` when the platform already provides isolation
|
||||
- **Fix:** Step 0 detects existing isolation. Step 1a defers to native tools.
|
||||
|
||||
### Skipping detection
|
||||
|
||||
- **Problem:** Creating a nested worktree inside an existing one
|
||||
- **Fix:** Always run Step 0 before creating anything
|
||||
|
||||
### Skipping ignore verification
|
||||
|
||||
- **Problem:** Worktree contents get tracked, pollute git status
|
||||
- **Fix:** Always grep .gitignore before creating project-local worktree
|
||||
- **Fix:** Always use `git check-ignore` before creating project-local worktree
|
||||
|
||||
### Assuming directory location
|
||||
|
||||
**Assuming directory location**
|
||||
- **Problem:** Creates inconsistency, violates project conventions
|
||||
- **Fix:** Follow priority: existing > CLAUDE.md > ask
|
||||
- **Fix:** Follow priority: existing > global legacy > instruction file > default
|
||||
|
||||
### Proceeding with failing tests
|
||||
|
||||
**Proceeding with failing tests**
|
||||
- **Problem:** Can't distinguish new bugs from pre-existing issues
|
||||
- **Fix:** Report failures, get explicit permission to proceed
|
||||
|
||||
**Hardcoding setup commands**
|
||||
- **Problem:** Breaks on projects using different tools
|
||||
- **Fix:** Auto-detect from project files (package.json, etc.)
|
||||
|
||||
## Example Workflow
|
||||
|
||||
```
|
||||
You: I'm using the using-git-worktrees skill to set up an isolated workspace.
|
||||
|
||||
[Check .worktrees/ - exists]
|
||||
[Verify .gitignore - contains .worktrees/]
|
||||
[Create worktree: git worktree add .worktrees/auth -b feature/auth]
|
||||
[Run npm install]
|
||||
[Run npm test - 47 passing]
|
||||
|
||||
Worktree ready at /Users/jesse/myproject/.worktrees/auth
|
||||
Tests passing (47 tests, 0 failures)
|
||||
Ready to implement auth feature
|
||||
```
|
||||
|
||||
## Red Flags
|
||||
|
||||
**Never:**
|
||||
- Create worktree without .gitignore verification (project-local)
|
||||
- Create a worktree when Step 0 detects existing isolation
|
||||
- Use `git worktree add` when you have a native worktree tool (e.g., `EnterWorktree`). This is the #1 mistake — if you have it, use it.
|
||||
- Skip Step 1a by jumping straight to Step 1b's git commands
|
||||
- Create worktree without verifying it's ignored (project-local)
|
||||
- Skip baseline test verification
|
||||
- Proceed with failing tests without asking
|
||||
- Assume directory location when ambiguous
|
||||
- Skip CLAUDE.md check
|
||||
|
||||
**Always:**
|
||||
- Follow directory priority: existing > CLAUDE.md > ask
|
||||
- Verify .gitignore for project-local
|
||||
- Run Step 0 detection first
|
||||
- Prefer native tools over git fallback
|
||||
- Follow directory priority: existing > global legacy > instruction file > default
|
||||
- Verify directory is ignored for project-local
|
||||
- Auto-detect and run project setup
|
||||
- Verify clean test baseline
|
||||
|
||||
## Integration
|
||||
|
||||
**Called by:**
|
||||
- **brainstorming** (Phase 4) - REQUIRED when design is approved and implementation follows
|
||||
- Any skill needing isolated workspace
|
||||
|
||||
**Pairs with:**
|
||||
- **finishing-a-development-branch** - REQUIRED for cleanup after work complete
|
||||
- **executing-plans** or **subagent-driven-development** - Work happens in this worktree
|
||||
|
||||
@@ -3,6 +3,10 @@ name: using-superpowers
|
||||
description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
|
||||
---
|
||||
|
||||
<SUBAGENT-STOP>
|
||||
If you were dispatched as a subagent to execute a specific task, skip this skill.
|
||||
</SUBAGENT-STOP>
|
||||
|
||||
<EXTREMELY-IMPORTANT>
|
||||
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
|
||||
|
||||
@@ -11,21 +15,42 @@ IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
|
||||
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
|
||||
</EXTREMELY-IMPORTANT>
|
||||
|
||||
## Instruction Priority
|
||||
|
||||
Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
|
||||
|
||||
1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
|
||||
2. **Superpowers skills** — override default system behavior where they conflict
|
||||
3. **Default system prompt** — lowest priority
|
||||
|
||||
If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
|
||||
|
||||
## How to Access Skills
|
||||
|
||||
**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
|
||||
|
||||
**In Copilot CLI:** Use the `skill` tool. Skills are auto-discovered from installed plugins. The `skill` tool works the same as Claude Code's `Skill` tool.
|
||||
|
||||
**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
|
||||
|
||||
**In other environments:** Check your platform's documentation for how skills are loaded.
|
||||
|
||||
## Platform Adaptation
|
||||
|
||||
Skills use Claude Code tool names. Non-CC platforms: see `references/copilot-tools.md` (Copilot CLI), `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
|
||||
|
||||
# Using Skills
|
||||
|
||||
## 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 {
|
||||
"User message received" [shape=doublecircle];
|
||||
"About to EnterPlanMode?" [shape=doublecircle];
|
||||
"Already brainstormed?" [shape=diamond];
|
||||
"Invoke brainstorming skill" [shape=box];
|
||||
"Might any skill apply?" [shape=diamond];
|
||||
"Invoke Skill tool" [shape=box];
|
||||
"Announce: 'Using [skill] to [purpose]'" [shape=box];
|
||||
@@ -34,6 +59,11 @@ digraph skill_flow {
|
||||
"Follow skill exactly" [shape=box];
|
||||
"Respond (including clarifications)" [shape=doublecircle];
|
||||
|
||||
"About to EnterPlanMode?" -> "Already brainstormed?";
|
||||
"Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
|
||||
"Already brainstormed?" -> "Might any skill apply?" [label="yes"];
|
||||
"Invoke brainstorming skill" -> "Might any skill apply?";
|
||||
|
||||
"User message received" -> "Might any skill apply?";
|
||||
"Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
|
||||
"Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
|
||||
@@ -62,6 +92,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
|
||||
|
||||
|
||||
105
skills/using-superpowers/references/codex-tools.md
Normal file
105
skills/using-superpowers/references/codex-tools.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Codex Tool Mapping
|
||||
|
||||
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
|
||||
|
||||
| Skill references | Codex equivalent |
|
||||
|-----------------|------------------|
|
||||
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
|
||||
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
|
||||
| Task returns result | `wait_agent` |
|
||||
| Task completes automatically | `close_agent` to free slot |
|
||||
| `TodoWrite` (task tracking) | `update_plan` |
|
||||
| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
|
||||
| `Read`, `Write`, `Edit` (files) | Use your native file tools |
|
||||
| `Bash` (run commands) | Use your native shell tools |
|
||||
|
||||
## Subagent dispatch requires multi-agent support
|
||||
|
||||
Add to your Codex config (`~/.codex/config.toml`):
|
||||
|
||||
```toml
|
||||
[features]
|
||||
multi_agent = true
|
||||
```
|
||||
|
||||
This enables `spawn_agent`, `wait_agent`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
|
||||
|
||||
Legacy note: Codex builds before `rust-v0.115.0` exposed spawned-agent
|
||||
waiting as `wait`. Current Codex uses `wait_agent` for spawned agents. The
|
||||
`wait` name now belongs to code-mode `exec/wait`, which resumes a yielded exec
|
||||
cell by `cell_id`; it is not the spawned-agent result tool.
|
||||
|
||||
## Named agent dispatch
|
||||
|
||||
Claude Code skills reference named agent types like `superpowers:code-reviewer`.
|
||||
Codex does not have a named agent registry — `spawn_agent` creates generic agents
|
||||
from built-in roles (`default`, `explorer`, `worker`).
|
||||
|
||||
When a skill says to dispatch a named agent type:
|
||||
|
||||
1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
|
||||
local prompt template like `code-quality-reviewer-prompt.md`)
|
||||
2. Read the prompt content
|
||||
3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
|
||||
4. Spawn a `worker` agent with the filled content as the `message`
|
||||
|
||||
| Skill instruction | Codex equivalent |
|
||||
|-------------------|------------------|
|
||||
| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
|
||||
| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
|
||||
|
||||
### Message framing
|
||||
|
||||
The `message` parameter is user-level input, not a system prompt. Structure it
|
||||
for maximum instruction adherence:
|
||||
|
||||
```
|
||||
Your task is to perform the following. Follow the instructions below exactly.
|
||||
|
||||
<agent-instructions>
|
||||
[filled prompt content from the agent's .md file]
|
||||
</agent-instructions>
|
||||
|
||||
Execute this now. Output ONLY the structured response following the format
|
||||
specified in the instructions above.
|
||||
```
|
||||
|
||||
- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
|
||||
- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
|
||||
- End with an explicit execution directive to prevent summarization of the instructions
|
||||
|
||||
### When this workaround can be removed
|
||||
|
||||
This approach compensates for Codex's plugin system not yet supporting an `agents`
|
||||
field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
|
||||
plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
|
||||
skills can dispatch named agent types directly.
|
||||
|
||||
## Environment Detection
|
||||
|
||||
Skills that create worktrees or finish branches should detect their
|
||||
environment with read-only git commands before proceeding:
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
|
||||
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
|
||||
|
||||
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
|
||||
Step 1 for how each skill uses these signals.
|
||||
|
||||
## Codex App Finishing
|
||||
|
||||
When the sandbox blocks branch/push operations (detached HEAD in an
|
||||
externally managed worktree), the agent commits all work and informs
|
||||
the user to use the App's native controls:
|
||||
|
||||
- **"Create branch"** — names the branch, then commit/push/PR via App UI
|
||||
- **"Hand off to local"** — transfers work to the user's local checkout
|
||||
|
||||
The agent can still run tests, stage files, and output suggested branch
|
||||
names, commit messages, and PR descriptions for the user to copy.
|
||||
52
skills/using-superpowers/references/copilot-tools.md
Normal file
52
skills/using-superpowers/references/copilot-tools.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copilot CLI Tool Mapping
|
||||
|
||||
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
|
||||
|
||||
| Skill references | Copilot CLI equivalent |
|
||||
|-----------------|----------------------|
|
||||
| `Read` (file reading) | `view` |
|
||||
| `Write` (file creation) | `create` |
|
||||
| `Edit` (file editing) | `edit` |
|
||||
| `Bash` (run commands) | `bash` |
|
||||
| `Grep` (search file content) | `grep` |
|
||||
| `Glob` (search files by name) | `glob` |
|
||||
| `Skill` tool (invoke a skill) | `skill` |
|
||||
| `WebFetch` | `web_fetch` |
|
||||
| `Task` tool (dispatch subagent) | `task` (see [Agent types](#agent-types)) |
|
||||
| Multiple `Task` calls (parallel) | Multiple `task` calls |
|
||||
| Task status/output | `read_agent`, `list_agents` |
|
||||
| `TodoWrite` (task tracking) | `sql` with built-in `todos` table |
|
||||
| `WebSearch` | No equivalent — use `web_fetch` with a search engine URL |
|
||||
| `EnterPlanMode` / `ExitPlanMode` | No equivalent — stay in the main session |
|
||||
|
||||
## Agent types
|
||||
|
||||
Copilot CLI's `task` tool accepts an `agent_type` parameter:
|
||||
|
||||
| Claude Code agent | Copilot CLI equivalent |
|
||||
|-------------------|----------------------|
|
||||
| `general-purpose` | `"general-purpose"` |
|
||||
| `Explore` | `"explore"` |
|
||||
| Named plugin agents (e.g. `superpowers:code-reviewer`) | Discovered automatically from installed plugins |
|
||||
|
||||
## Async shell sessions
|
||||
|
||||
Copilot CLI supports persistent async shell sessions, which have no direct Claude Code equivalent:
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `bash` with `async: true` | Start a long-running command in the background |
|
||||
| `write_bash` | Send input to a running async session |
|
||||
| `read_bash` | Read output from an async session |
|
||||
| `stop_bash` | Terminate an async session |
|
||||
| `list_bash` | List all active shell sessions |
|
||||
|
||||
## Additional Copilot CLI tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `store_memory` | Persist facts about the codebase for future sessions |
|
||||
| `report_intent` | Update the UI status line with current intent |
|
||||
| `sql` | Query the session's SQLite database (todos, metadata) |
|
||||
| `fetch_copilot_cli_documentation` | Look up Copilot CLI documentation |
|
||||
| GitHub MCP tools (`github-mcp-server-*`) | Native GitHub API access (issues, PRs, code search) |
|
||||
51
skills/using-superpowers/references/gemini-tools.md
Normal file
51
skills/using-superpowers/references/gemini-tools.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Gemini CLI Tool Mapping
|
||||
|
||||
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
|
||||
|
||||
| Skill references | Gemini CLI equivalent |
|
||||
|-----------------|----------------------|
|
||||
| `Read` (file reading) | `read_file` |
|
||||
| `Write` (file creation) | `write_file` |
|
||||
| `Edit` (file editing) | `replace` |
|
||||
| `Bash` (run commands) | `run_shell_command` |
|
||||
| `Grep` (search file content) | `grep_search` |
|
||||
| `Glob` (search files by name) | `glob` |
|
||||
| `TodoWrite` (task tracking) | `write_todos` |
|
||||
| `Skill` tool (invoke a skill) | `activate_skill` |
|
||||
| `WebSearch` | `google_web_search` |
|
||||
| `WebFetch` | `web_fetch` |
|
||||
| `Task` tool (dispatch subagent) | `@agent-name` (see [Subagent support](#subagent-support)) |
|
||||
|
||||
## Subagent support
|
||||
|
||||
Gemini CLI supports subagents natively via the `@` syntax. Use the built-in `@generalist` agent to dispatch any task — it has access to all tools and follows the prompt you provide.
|
||||
|
||||
When a skill says to dispatch a named agent type, use `@generalist` with the full prompt from the skill's prompt template:
|
||||
|
||||
| Skill instruction | Gemini CLI equivalent |
|
||||
|-------------------|----------------------|
|
||||
| `Task tool (superpowers:implementer)` | `@generalist` with the filled `implementer-prompt.md` template |
|
||||
| `Task tool (superpowers:spec-reviewer)` | `@generalist` with the filled `spec-reviewer-prompt.md` template |
|
||||
| `Task tool (superpowers:code-reviewer)` | `@code-reviewer` (bundled agent) or `@generalist` with the filled review prompt |
|
||||
| `Task tool (superpowers:code-quality-reviewer)` | `@generalist` with the filled `code-quality-reviewer-prompt.md` template |
|
||||
| `Task tool (general-purpose)` with inline prompt | `@generalist` with your inline prompt |
|
||||
|
||||
### Prompt filling
|
||||
|
||||
Skills provide prompt templates with placeholders like `{WHAT_WAS_IMPLEMENTED}` or `[FULL TEXT of task]`. Fill all placeholders and pass the complete prompt as the message to `@generalist`. The prompt template itself contains the agent's role, review criteria, and expected output format — `@generalist` will follow it.
|
||||
|
||||
### Parallel dispatch
|
||||
|
||||
Gemini CLI supports parallel subagent dispatch. When a skill asks you to dispatch multiple independent subagent tasks in parallel, request all of those `@generalist` or named subagent tasks together in the same prompt. Keep dependent tasks sequential, but do not serialize independent subagent tasks just to preserve a simpler history.
|
||||
|
||||
## Additional Gemini CLI tools
|
||||
|
||||
These tools are available in Gemini CLI but have no Claude Code equivalent:
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `list_directory` | List files and subdirectories |
|
||||
| `save_memory` | Persist facts to GEMINI.md across sessions |
|
||||
| `ask_user` | Request structured input from the user |
|
||||
| `tracker_create_task` | Rich task management (create, update, list, visualize) |
|
||||
| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |
|
||||
@@ -13,9 +13,25 @@ Assume they are a skilled developer, but know almost nothing about our toolset o
|
||||
|
||||
**Announce at start:** "I'm using the writing-plans skill to create the implementation plan."
|
||||
|
||||
**Context:** This should be run in a dedicated worktree (created by brainstorming skill).
|
||||
**Context:** If working in an isolated worktree, it should have been created via the `superpowers:using-git-worktrees` skill at execution time.
|
||||
|
||||
**Save plans to:** `docs/plans/YYYY-MM-DD-<feature-name>.md`
|
||||
**Save plans to:** `docs/superpowers/plans/YYYY-MM-DD-<feature-name>.md`
|
||||
- (User preferences for plan location override this default)
|
||||
|
||||
## Scope Check
|
||||
|
||||
If the spec covers multiple independent subsystems, it should have been broken into sub-project specs during brainstorming. If it wasn't, suggest breaking this into separate plans — one per subsystem. Each plan should produce working, testable software on its own.
|
||||
|
||||
## File Structure
|
||||
|
||||
Before defining tasks, map out which files will be created or modified and what each one is responsible for. This is where decomposition decisions get locked in.
|
||||
|
||||
- Design units with clear boundaries and well-defined interfaces. Each file should have one clear responsibility.
|
||||
- You reason best about code you can hold in context at once, and your edits are more reliable when files are focused. Prefer smaller, focused files over large ones that do too much.
|
||||
- Files that change together should live together. Split by responsibility, not by technical layer.
|
||||
- In existing codebases, follow established patterns. If the codebase uses large files, don't unilaterally restructure - but if a file you're modifying has grown unwieldy, including a split in the plan is reasonable.
|
||||
|
||||
This structure informs the task decomposition. Each task should produce self-contained changes that make sense independently.
|
||||
|
||||
## Bite-Sized Task Granularity
|
||||
|
||||
@@ -33,7 +49,7 @@ Assume they are a skilled developer, but know almost nothing about our toolset o
|
||||
```markdown
|
||||
# [Feature Name] Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** [One sentence describing what this builds]
|
||||
|
||||
@@ -46,7 +62,7 @@ Assume they are a skilled developer, but know almost nothing about our toolset o
|
||||
|
||||
## Task Structure
|
||||
|
||||
```markdown
|
||||
````markdown
|
||||
### Task N: [Component Name]
|
||||
|
||||
**Files:**
|
||||
@@ -54,7 +70,7 @@ Assume they are a skilled developer, but know almost nothing about our toolset o
|
||||
- Modify: `exact/path/to/existing.py:123-145`
|
||||
- Test: `tests/exact/path/to/test.py`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```python
|
||||
def test_specific_behavior():
|
||||
@@ -62,55 +78,75 @@ def test_specific_behavior():
|
||||
assert result == expected
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `pytest tests/path/test.py::test_name -v`
|
||||
Expected: FAIL with "function not defined"
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
```python
|
||||
def function(input):
|
||||
return expected
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `pytest tests/path/test.py::test_name -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/path/test.py src/path/file.py
|
||||
git commit -m "feat: add specific feature"
|
||||
```
|
||||
```
|
||||
````
|
||||
|
||||
## No Placeholders
|
||||
|
||||
Every step must contain the actual content an engineer needs. These are **plan failures** — never write them:
|
||||
- "TBD", "TODO", "implement later", "fill in details"
|
||||
- "Add appropriate error handling" / "add validation" / "handle edge cases"
|
||||
- "Write tests for the above" (without actual test code)
|
||||
- "Similar to Task N" (repeat the code — the engineer may be reading tasks out of order)
|
||||
- Steps that describe what to do without showing how (code blocks required for code steps)
|
||||
- References to types, functions, or methods not defined in any task
|
||||
|
||||
## Remember
|
||||
- Exact file paths always
|
||||
- Complete code in plan (not "add validation")
|
||||
- Complete code in every step — if a step changes code, show the code
|
||||
- Exact commands with expected output
|
||||
- Reference relevant skills with @ syntax
|
||||
- DRY, YAGNI, TDD, frequent commits
|
||||
|
||||
## Self-Review
|
||||
|
||||
After writing the complete plan, look at the spec with fresh eyes and check the plan against it. This is a checklist you run yourself — not a subagent dispatch.
|
||||
|
||||
**1. Spec coverage:** Skim each section/requirement in the spec. Can you point to a task that implements it? List any gaps.
|
||||
|
||||
**2. Placeholder scan:** Search your plan for red flags — any of the patterns from the "No Placeholders" section above. Fix them.
|
||||
|
||||
**3. Type consistency:** Do the types, method signatures, and property names you used in later tasks match what you defined in earlier tasks? A function called `clearLayers()` in Task 3 but `clearFullLayers()` in Task 7 is a bug.
|
||||
|
||||
If you find issues, fix them inline. No need to re-review — just fix and move on. If you find a spec requirement with no task, add the task.
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
After saving the plan, offer execution choice:
|
||||
|
||||
**"Plan complete and saved to `docs/plans/<filename>.md`. Two execution options:**
|
||||
**"Plan complete and saved to `docs/superpowers/plans/<filename>.md`. Two execution options:**
|
||||
|
||||
**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration
|
||||
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
|
||||
|
||||
**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints
|
||||
**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
|
||||
|
||||
**Which approach?"**
|
||||
|
||||
**If Subagent-Driven chosen:**
|
||||
- **REQUIRED SUB-SKILL:** Use superpowers:subagent-driven-development
|
||||
- Stay in this session
|
||||
- Fresh subagent per task + code review
|
||||
- Fresh subagent per task + two-stage review
|
||||
|
||||
**If Parallel Session chosen:**
|
||||
- Guide them to open new session in worktree
|
||||
- **REQUIRED SUB-SKILL:** New session uses superpowers:executing-plans
|
||||
**If Inline Execution chosen:**
|
||||
- **REQUIRED SUB-SKILL:** Use superpowers:executing-plans
|
||||
- Batch execution with checkpoints for review
|
||||
|
||||
49
skills/writing-plans/plan-document-reviewer-prompt.md
Normal file
49
skills/writing-plans/plan-document-reviewer-prompt.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Plan Document Reviewer Prompt Template
|
||||
|
||||
Use this template when dispatching a plan document reviewer subagent.
|
||||
|
||||
**Purpose:** Verify the plan is complete, matches the spec, and has proper task decomposition.
|
||||
|
||||
**Dispatch after:** The complete plan is written.
|
||||
|
||||
```
|
||||
Task tool (general-purpose):
|
||||
description: "Review plan document"
|
||||
prompt: |
|
||||
You are a plan document reviewer. Verify this plan is complete and ready for implementation.
|
||||
|
||||
**Plan to review:** [PLAN_FILE_PATH]
|
||||
**Spec for reference:** [SPEC_FILE_PATH]
|
||||
|
||||
## What to Check
|
||||
|
||||
| Category | What to Look For |
|
||||
|----------|------------------|
|
||||
| Completeness | TODOs, placeholders, incomplete tasks, missing steps |
|
||||
| Spec Alignment | Plan covers spec requirements, no major scope creep |
|
||||
| Task Decomposition | Tasks have clear boundaries, steps are actionable |
|
||||
| Buildability | Could an engineer follow this plan without getting stuck? |
|
||||
|
||||
## Calibration
|
||||
|
||||
**Only flag issues that would cause real problems during implementation.**
|
||||
An implementer building the wrong thing or getting stuck is an issue.
|
||||
Minor wording, stylistic preferences, and "nice to have" suggestions are not.
|
||||
|
||||
Approve unless there are serious gaps — missing requirements from the spec,
|
||||
contradictory steps, placeholder content, or tasks so vague they can't be acted on.
|
||||
|
||||
## Output Format
|
||||
|
||||
## Plan Review
|
||||
|
||||
**Status:** Approved | Issues Found
|
||||
|
||||
**Issues (if any):**
|
||||
- [Task X, Step Y]: [specific issue] - [why it matters for implementation]
|
||||
|
||||
**Recommendations (advisory, do not block approval):**
|
||||
- [suggestions for improvement]
|
||||
```
|
||||
|
||||
**Reviewer returns:** Status, Issues (if any), Recommendations
|
||||
@@ -9,7 +9,7 @@ description: Use when creating new skills, editing existing skills, or verifying
|
||||
|
||||
**Writing skills IS Test-Driven Development applied to process documentation.**
|
||||
|
||||
**Personal skills live in agent-specific directories (`~/.claude/skills` for Claude Code, `~/.codex/skills` for Codex)**
|
||||
**Personal skills live in agent-specific directories (`~/.claude/skills` for Claude Code, `~/.agents/skills/` for Codex)**
|
||||
|
||||
You write test cases (pressure scenarios with subagents), watch them fail (baseline behavior), write the skill (documentation), watch tests pass (agents comply), and refactor (close loopholes).
|
||||
|
||||
@@ -56,6 +56,7 @@ The entire skill creation process follows RED-GREEN-REFACTOR.
|
||||
- One-off solutions
|
||||
- Standard practices well-documented elsewhere
|
||||
- Project-specific conventions (put in CLAUDE.md)
|
||||
- Mechanical constraints (if it's enforceable with regex/validation, automate it—save documentation for judgment calls)
|
||||
|
||||
## Skill Types
|
||||
|
||||
@@ -92,7 +93,7 @@ skills/
|
||||
## SKILL.md Structure
|
||||
|
||||
**Frontmatter (YAML):**
|
||||
- Only two fields supported: `name` and `description`
|
||||
- Two required fields: `name` and `description` (see [agentskills.io/specification](https://agentskills.io/specification) for all supported fields)
|
||||
- Max 1024 characters total
|
||||
- `name`: Use letters, numbers, and hyphens only (no parentheses, special chars)
|
||||
- `description`: Third-person, describes ONLY when to use (NOT what it does)
|
||||
@@ -603,7 +604,7 @@ Deploying untested skills = deploying untested code. It's a violation of quality
|
||||
|
||||
**GREEN Phase - Write Minimal Skill:**
|
||||
- [ ] Name uses only letters, numbers, hyphens (no parentheses/special chars)
|
||||
- [ ] YAML frontmatter with only name and description (max 1024 chars)
|
||||
- [ ] YAML frontmatter with required `name` and `description` fields (max 1024 chars; see [spec](https://agentskills.io/specification))
|
||||
- [ ] Description starts with "Use when..." and includes specific triggers/symptoms
|
||||
- [ ] Description written in third person
|
||||
- [ ] Keywords throughout for search (errors, symptoms, tools)
|
||||
|
||||
@@ -144,7 +144,7 @@ What works perfectly for Opus might need more detail for Haiku. If you plan to u
|
||||
## Skill structure
|
||||
|
||||
<Note>
|
||||
**YAML Frontmatter**: The SKILL.md frontmatter supports two fields:
|
||||
**YAML Frontmatter**: The SKILL.md frontmatter requires two fields:
|
||||
|
||||
* `name` - Human-readable name of the Skill (64 characters maximum)
|
||||
* `description` - One-line description of what the Skill does and when to use it (1024 characters maximum)
|
||||
@@ -1092,7 +1092,7 @@ reader = PdfReader("file.pdf")
|
||||
|
||||
### YAML frontmatter requirements
|
||||
|
||||
The SKILL.md frontmatter includes only `name` (64 characters max) and `description` (1024 characters max) fields. See the [Skills overview](/en/docs/agents-and-tools/agent-skills/overview#skill-structure) for complete structure details.
|
||||
The SKILL.md frontmatter requires `name` (64 characters max) and `description` (1024 characters max) fields. See the [Skills overview](/en/docs/agents-and-tools/agent-skills/overview#skill-structure) for complete structure details.
|
||||
|
||||
### Token budgets
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
36
tests/brainstorm-server/package-lock.json
generated
Normal file
36
tests/brainstorm-server/package-lock.json
generated
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "brainstorm-server-tests",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "brainstorm-server-tests",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
tests/brainstorm-server/package.json
Normal file
10
tests/brainstorm-server/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "brainstorm-server-tests",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"test": "node server.test.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^8.19.0"
|
||||
}
|
||||
}
|
||||
427
tests/brainstorm-server/server.test.js
Normal file
427
tests/brainstorm-server/server.test.js
Normal file
@@ -0,0 +1,427 @@
|
||||
/**
|
||||
* Integration tests for the brainstorm server.
|
||||
*
|
||||
* Tests the full server behavior: HTTP serving, WebSocket communication,
|
||||
* file watching, and the brainstorming workflow.
|
||||
*
|
||||
* Uses the `ws` npm package as a test client (test-only dependency,
|
||||
* not shipped to end users).
|
||||
*/
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
const SERVER_PATH = path.join(__dirname, '../../skills/brainstorming/scripts/server.cjs');
|
||||
const TEST_PORT = 3334;
|
||||
const TEST_DIR = '/tmp/brainstorm-test';
|
||||
const CONTENT_DIR = path.join(TEST_DIR, 'content');
|
||||
const STATE_DIR = path.join(TEST_DIR, 'state');
|
||||
|
||||
function cleanup() {
|
||||
if (fs.existsSync(TEST_DIR)) {
|
||||
fs.rmSync(TEST_DIR, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function fetch(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.get(url, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => resolve({
|
||||
status: res.statusCode,
|
||||
headers: res.headers,
|
||||
body: data
|
||||
}));
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function startServer() {
|
||||
return spawn('node', [SERVER_PATH], {
|
||||
env: { ...process.env, BRAINSTORM_PORT: TEST_PORT, BRAINSTORM_DIR: TEST_DIR }
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForServer(server) {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
if (stdout.includes('server-started')) {
|
||||
resolve({ stdout, stderr, getStdout: () => stdout });
|
||||
}
|
||||
});
|
||||
server.stderr.on('data', (data) => { stderr += data.toString(); });
|
||||
server.on('error', reject);
|
||||
|
||||
setTimeout(() => reject(new Error(`Server didn't start. stderr: ${stderr}`)), 5000);
|
||||
});
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
cleanup();
|
||||
|
||||
const server = startServer();
|
||||
let stdoutAccum = '';
|
||||
server.stdout.on('data', (data) => { stdoutAccum += data.toString(); });
|
||||
|
||||
const { stdout: initialStdout } = await waitForServer(server);
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
return fn().then(() => {
|
||||
console.log(` PASS: ${name}`);
|
||||
passed++;
|
||||
}).catch(e => {
|
||||
console.log(` FAIL: ${name}`);
|
||||
console.log(` ${e.message}`);
|
||||
failed++;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// ========== Server Startup ==========
|
||||
console.log('\n--- Server Startup ---');
|
||||
|
||||
await test('outputs server-started JSON on startup', () => {
|
||||
const msg = JSON.parse(initialStdout.trim());
|
||||
assert.strictEqual(msg.type, 'server-started');
|
||||
assert.strictEqual(msg.port, TEST_PORT);
|
||||
assert(msg.url, 'Should include URL');
|
||||
assert(msg.screen_dir, 'Should include screen_dir');
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
await test('writes server-info to state/', () => {
|
||||
const infoPath = path.join(STATE_DIR, 'server-info');
|
||||
assert(fs.existsSync(infoPath), 'state/server-info should exist');
|
||||
const info = JSON.parse(fs.readFileSync(infoPath, 'utf-8').trim());
|
||||
assert.strictEqual(info.type, 'server-started');
|
||||
assert.strictEqual(info.port, TEST_PORT);
|
||||
assert.strictEqual(info.screen_dir, CONTENT_DIR, 'screen_dir should point to content/');
|
||||
assert.strictEqual(info.state_dir, STATE_DIR, 'state_dir should point to state/');
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// ========== HTTP Serving ==========
|
||||
console.log('\n--- HTTP Serving ---');
|
||||
|
||||
await test('serves waiting page when no screens exist', async () => {
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
||||
assert.strictEqual(res.status, 200);
|
||||
assert(res.body.includes('Waiting for the agent'), 'Should show waiting message');
|
||||
});
|
||||
|
||||
await test('injects helper.js into waiting page', async () => {
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
||||
assert(res.body.includes('WebSocket'), 'Should have helper.js injected');
|
||||
assert(res.body.includes('toggleSelect'), 'Should have toggleSelect from helper');
|
||||
assert(res.body.includes('brainstorm'), 'Should have brainstorm API from helper');
|
||||
});
|
||||
|
||||
await test('returns Content-Type text/html', async () => {
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
||||
assert(res.headers['content-type'].includes('text/html'), 'Should be text/html');
|
||||
});
|
||||
|
||||
await test('serves full HTML documents as-is (not wrapped)', async () => {
|
||||
const fullDoc = '<!DOCTYPE html>\n<html><head><title>Custom</title></head><body><h1>Custom Page</h1></body></html>';
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'full-doc.html'), fullDoc);
|
||||
await sleep(300);
|
||||
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
||||
assert(res.body.includes('<h1>Custom Page</h1>'), 'Should contain original content');
|
||||
assert(res.body.includes('WebSocket'), 'Should still inject helper.js');
|
||||
assert(!res.body.includes('indicator-bar'), 'Should NOT wrap in frame template');
|
||||
});
|
||||
|
||||
await test('wraps content fragments in frame template', async () => {
|
||||
const fragment = '<h2>Pick a layout</h2>\n<div class="options"><div class="option" data-choice="a"><div class="letter">A</div></div></div>';
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'fragment.html'), fragment);
|
||||
await sleep(300);
|
||||
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
||||
assert(res.body.includes('indicator-bar'), 'Fragment should get indicator bar');
|
||||
assert(!res.body.includes('<!-- CONTENT -->'), 'Placeholder should be replaced');
|
||||
assert(res.body.includes('Pick a layout'), 'Fragment content should be present');
|
||||
assert(res.body.includes('data-choice="a"'), 'Fragment interactive elements intact');
|
||||
});
|
||||
|
||||
await test('serves newest file by mtime', async () => {
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'older.html'), '<h2>Older</h2>');
|
||||
await sleep(100);
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'newer.html'), '<h2>Newer</h2>');
|
||||
await sleep(300);
|
||||
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
||||
assert(res.body.includes('Newer'), 'Should serve newest file');
|
||||
});
|
||||
|
||||
await test('ignores non-html files for serving', async () => {
|
||||
// Write a newer non-HTML file — should still serve newest .html
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'data.json'), '{"not": "html"}');
|
||||
await sleep(300);
|
||||
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
||||
assert(res.body.includes('Newer'), 'Should still serve newest HTML');
|
||||
assert(!res.body.includes('"not"'), 'Should not serve JSON');
|
||||
});
|
||||
|
||||
await test('returns 404 for non-root paths', async () => {
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/other`);
|
||||
assert.strictEqual(res.status, 404);
|
||||
});
|
||||
|
||||
// ========== WebSocket Communication ==========
|
||||
console.log('\n--- WebSocket Communication ---');
|
||||
|
||||
await test('accepts WebSocket upgrade on /', async () => {
|
||||
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise((resolve, reject) => {
|
||||
ws.on('open', resolve);
|
||||
ws.on('error', reject);
|
||||
});
|
||||
ws.close();
|
||||
});
|
||||
|
||||
await test('relays user events to stdout with source field', async () => {
|
||||
stdoutAccum = '';
|
||||
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws.on('open', resolve));
|
||||
|
||||
ws.send(JSON.stringify({ type: 'click', text: 'Test Button' }));
|
||||
await sleep(300);
|
||||
|
||||
assert(stdoutAccum.includes('"source":"user-event"'), 'Should tag with source');
|
||||
assert(stdoutAccum.includes('Test Button'), 'Should include event data');
|
||||
ws.close();
|
||||
});
|
||||
|
||||
await test('writes choice events to state/events', async () => {
|
||||
// Clean up events from prior tests
|
||||
const eventsFile = path.join(STATE_DIR, 'events');
|
||||
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
|
||||
|
||||
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws.on('open', resolve));
|
||||
|
||||
ws.send(JSON.stringify({ type: 'click', choice: 'b', text: 'Option B' }));
|
||||
await sleep(300);
|
||||
|
||||
assert(fs.existsSync(eventsFile), '.events should exist');
|
||||
const lines = fs.readFileSync(eventsFile, 'utf-8').trim().split('\n');
|
||||
const event = JSON.parse(lines[lines.length - 1]);
|
||||
assert.strictEqual(event.choice, 'b');
|
||||
assert.strictEqual(event.text, 'Option B');
|
||||
ws.close();
|
||||
});
|
||||
|
||||
await test('does NOT write non-choice events to state/events', async () => {
|
||||
const eventsFile = path.join(STATE_DIR, 'events');
|
||||
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
|
||||
|
||||
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws.on('open', resolve));
|
||||
|
||||
ws.send(JSON.stringify({ type: 'hover', text: 'Something' }));
|
||||
await sleep(300);
|
||||
|
||||
// Non-choice events should not create .events file
|
||||
assert(!fs.existsSync(eventsFile), '.events should not exist for non-choice events');
|
||||
ws.close();
|
||||
});
|
||||
|
||||
await test('handles multiple concurrent WebSocket clients', async () => {
|
||||
const ws1 = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
const ws2 = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await Promise.all([
|
||||
new Promise(resolve => ws1.on('open', resolve)),
|
||||
new Promise(resolve => ws2.on('open', resolve))
|
||||
]);
|
||||
|
||||
let ws1Reload = false;
|
||||
let ws2Reload = false;
|
||||
ws1.on('message', (data) => {
|
||||
if (JSON.parse(data.toString()).type === 'reload') ws1Reload = true;
|
||||
});
|
||||
ws2.on('message', (data) => {
|
||||
if (JSON.parse(data.toString()).type === 'reload') ws2Reload = true;
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'multi-client.html'), '<h2>Multi</h2>');
|
||||
await sleep(500);
|
||||
|
||||
assert(ws1Reload, 'Client 1 should receive reload');
|
||||
assert(ws2Reload, 'Client 2 should receive reload');
|
||||
ws1.close();
|
||||
ws2.close();
|
||||
});
|
||||
|
||||
await test('cleans up closed clients from broadcast list', async () => {
|
||||
const ws1 = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws1.on('open', resolve));
|
||||
ws1.close();
|
||||
await sleep(100);
|
||||
|
||||
// This should not throw even though ws1 is closed
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'after-close.html'), '<h2>After</h2>');
|
||||
await sleep(300);
|
||||
// If we got here without error, the test passes
|
||||
});
|
||||
|
||||
await test('handles malformed JSON from client gracefully', async () => {
|
||||
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws.on('open', resolve));
|
||||
|
||||
// Send invalid JSON — server should not crash
|
||||
ws.send('not json at all {{{');
|
||||
await sleep(300);
|
||||
|
||||
// Verify server is still responsive
|
||||
const res = await fetch(`http://localhost:${TEST_PORT}/`);
|
||||
assert.strictEqual(res.status, 200, 'Server should still be running');
|
||||
ws.close();
|
||||
});
|
||||
|
||||
// ========== File Watching ==========
|
||||
console.log('\n--- File Watching ---');
|
||||
|
||||
await test('sends reload on new .html file', async () => {
|
||||
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws.on('open', resolve));
|
||||
|
||||
let gotReload = false;
|
||||
ws.on('message', (data) => {
|
||||
if (JSON.parse(data.toString()).type === 'reload') gotReload = true;
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'watch-new.html'), '<h2>New</h2>');
|
||||
await sleep(500);
|
||||
|
||||
assert(gotReload, 'Should send reload on new file');
|
||||
ws.close();
|
||||
});
|
||||
|
||||
await test('sends reload on .html file change', async () => {
|
||||
const filePath = path.join(CONTENT_DIR, 'watch-change.html');
|
||||
fs.writeFileSync(filePath, '<h2>Original</h2>');
|
||||
await sleep(500);
|
||||
|
||||
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws.on('open', resolve));
|
||||
|
||||
let gotReload = false;
|
||||
ws.on('message', (data) => {
|
||||
if (JSON.parse(data.toString()).type === 'reload') gotReload = true;
|
||||
});
|
||||
|
||||
fs.writeFileSync(filePath, '<h2>Modified</h2>');
|
||||
await sleep(500);
|
||||
|
||||
assert(gotReload, 'Should send reload on file change');
|
||||
ws.close();
|
||||
});
|
||||
|
||||
await test('does NOT send reload for non-.html files', async () => {
|
||||
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
|
||||
await new Promise(resolve => ws.on('open', resolve));
|
||||
|
||||
let gotReload = false;
|
||||
ws.on('message', (data) => {
|
||||
if (JSON.parse(data.toString()).type === 'reload') gotReload = true;
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'data.txt'), 'not html');
|
||||
await sleep(500);
|
||||
|
||||
assert(!gotReload, 'Should NOT reload for non-HTML files');
|
||||
ws.close();
|
||||
});
|
||||
|
||||
await test('clears state/events on new screen', async () => {
|
||||
// Create an events file
|
||||
const eventsFile = path.join(STATE_DIR, 'events');
|
||||
fs.writeFileSync(eventsFile, '{"choice":"a"}\n');
|
||||
assert(fs.existsSync(eventsFile));
|
||||
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'clear-events.html'), '<h2>New screen</h2>');
|
||||
await sleep(500);
|
||||
|
||||
assert(!fs.existsSync(eventsFile), 'state/events should be cleared on new screen');
|
||||
});
|
||||
|
||||
await test('logs screen-added on new file', async () => {
|
||||
stdoutAccum = '';
|
||||
fs.writeFileSync(path.join(CONTENT_DIR, 'log-test.html'), '<h2>Log</h2>');
|
||||
await sleep(500);
|
||||
|
||||
assert(stdoutAccum.includes('screen-added'), 'Should log screen-added');
|
||||
});
|
||||
|
||||
await test('logs screen-updated on file change', async () => {
|
||||
const filePath = path.join(CONTENT_DIR, 'log-update.html');
|
||||
fs.writeFileSync(filePath, '<h2>V1</h2>');
|
||||
await sleep(500);
|
||||
|
||||
stdoutAccum = '';
|
||||
fs.writeFileSync(filePath, '<h2>V2</h2>');
|
||||
await sleep(500);
|
||||
|
||||
assert(stdoutAccum.includes('screen-updated'), 'Should log screen-updated');
|
||||
});
|
||||
|
||||
// ========== Helper.js Content ==========
|
||||
console.log('\n--- Helper.js Verification ---');
|
||||
|
||||
await test('helper.js defines required APIs', () => {
|
||||
const helperContent = fs.readFileSync(
|
||||
path.join(__dirname, '../../skills/brainstorming/scripts/helper.js'), 'utf-8'
|
||||
);
|
||||
assert(helperContent.includes('toggleSelect'), 'Should define toggleSelect');
|
||||
assert(helperContent.includes('sendEvent'), 'Should define sendEvent');
|
||||
assert(helperContent.includes('selectedChoice'), 'Should track selectedChoice');
|
||||
assert(helperContent.includes('brainstorm'), 'Should expose brainstorm API');
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// ========== Frame Template ==========
|
||||
console.log('\n--- Frame Template Verification ---');
|
||||
|
||||
await test('frame template has required structure', () => {
|
||||
const template = fs.readFileSync(
|
||||
path.join(__dirname, '../../skills/brainstorming/scripts/frame-template.html'), 'utf-8'
|
||||
);
|
||||
assert(template.includes('indicator-bar'), 'Should have indicator bar');
|
||||
assert(template.includes('indicator-text'), 'Should have indicator text');
|
||||
assert(template.includes('<!-- CONTENT -->'), 'Should have content placeholder');
|
||||
assert(template.includes('claude-content'), 'Should have content container');
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
// ========== Summary ==========
|
||||
console.log(`\n--- Results: ${passed} passed, ${failed} failed ---`);
|
||||
if (failed > 0) process.exit(1);
|
||||
|
||||
} finally {
|
||||
server.kill();
|
||||
await sleep(100);
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
runTests().catch(err => {
|
||||
console.error('Test failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
351
tests/brainstorm-server/windows-lifecycle.test.sh
Executable file
351
tests/brainstorm-server/windows-lifecycle.test.sh
Executable file
@@ -0,0 +1,351 @@
|
||||
#!/usr/bin/env bash
|
||||
# Windows lifecycle tests for the brainstorm server.
|
||||
#
|
||||
# Verifies that the brainstorm server survives the 60-second lifecycle
|
||||
# check on Windows, where OWNER_PID monitoring is disabled because the
|
||||
# MSYS2 PID namespace is invisible to Node.js.
|
||||
#
|
||||
# Requirements:
|
||||
# - Node.js in PATH
|
||||
# - Run from the repository root, or set SUPERPOWERS_ROOT
|
||||
# - On Windows: Git Bash (OSTYPE=msys*)
|
||||
#
|
||||
# Usage:
|
||||
# bash tests/brainstorm-server/windows-lifecycle.test.sh
|
||||
set -uo pipefail
|
||||
|
||||
# ========== Configuration ==========
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="${SUPERPOWERS_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
||||
START_SCRIPT="$REPO_ROOT/skills/brainstorming/scripts/start-server.sh"
|
||||
STOP_SCRIPT="$REPO_ROOT/skills/brainstorming/scripts/stop-server.sh"
|
||||
SERVER_JS="$REPO_ROOT/skills/brainstorming/scripts/server.js"
|
||||
|
||||
TEST_DIR="${TMPDIR:-/tmp}/brainstorm-win-test-$$"
|
||||
|
||||
passed=0
|
||||
failed=0
|
||||
skipped=0
|
||||
|
||||
# ========== Helpers ==========
|
||||
|
||||
cleanup() {
|
||||
# Kill any server processes we started
|
||||
for pidvar in SERVER_PID CONTROL_PID STOP_TEST_PID; do
|
||||
pid="${!pidvar:-}"
|
||||
if [[ -n "$pid" ]]; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
if [[ -n "${TEST_DIR:-}" && -d "$TEST_DIR" ]]; then
|
||||
rm -rf "$TEST_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
pass() {
|
||||
echo " PASS: $1"
|
||||
passed=$((passed + 1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo " FAIL: $1"
|
||||
echo " $2"
|
||||
failed=$((failed + 1))
|
||||
}
|
||||
|
||||
skip() {
|
||||
echo " SKIP: $1 ($2)"
|
||||
skipped=$((skipped + 1))
|
||||
}
|
||||
|
||||
wait_for_server_info() {
|
||||
local dir="$1"
|
||||
for _ in $(seq 1 50); do
|
||||
if [[ -f "$dir/.server-info" ]]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
get_port_from_info() {
|
||||
# Read the port from .server-info. Use grep/sed instead of Node.js
|
||||
# to avoid MSYS2-to-Windows path translation issues.
|
||||
grep -o '"port":[0-9]*' "$1/.server-info" | head -1 | sed 's/"port"://'
|
||||
}
|
||||
|
||||
http_check() {
|
||||
local port="$1"
|
||||
node -e "
|
||||
const http = require('http');
|
||||
http.get('http://localhost:$port/', (res) => {
|
||||
process.exit(res.statusCode === 200 ? 0 : 1);
|
||||
}).on('error', () => process.exit(1));
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
# ========== Platform Detection ==========
|
||||
|
||||
echo ""
|
||||
echo "=== Brainstorm Server Windows Lifecycle Tests ==="
|
||||
echo "Platform: ${OSTYPE:-unknown}"
|
||||
echo "MSYSTEM: ${MSYSTEM:-unset}"
|
||||
echo "Node: $(node --version 2>/dev/null || echo 'not found')"
|
||||
echo ""
|
||||
|
||||
is_windows="false"
|
||||
case "${OSTYPE:-}" in
|
||||
msys*|cygwin*|mingw*) is_windows="true" ;;
|
||||
esac
|
||||
if [[ -n "${MSYSTEM:-}" ]]; then
|
||||
is_windows="true"
|
||||
fi
|
||||
|
||||
if [[ "$is_windows" != "true" ]]; then
|
||||
echo "NOTE: Not running on Windows/MSYS2 (OSTYPE=${OSTYPE:-unset})."
|
||||
echo "Windows-specific tests will be skipped. Tests 4-6 still run."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
mkdir -p "$TEST_DIR"
|
||||
|
||||
SERVER_PID=""
|
||||
CONTROL_PID=""
|
||||
STOP_TEST_PID=""
|
||||
|
||||
# ========== Test 1: OWNER_PID is empty on Windows ==========
|
||||
|
||||
echo "--- Owner PID Resolution ---"
|
||||
|
||||
if [[ "$is_windows" == "true" ]]; then
|
||||
# Replicate the PID resolution logic from start-server.sh lines 104-112
|
||||
TEST_OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ' || true)"
|
||||
if [[ -z "$TEST_OWNER_PID" || "$TEST_OWNER_PID" == "1" ]]; then
|
||||
TEST_OWNER_PID="$PPID"
|
||||
fi
|
||||
# The fix: clear on Windows
|
||||
case "${OSTYPE:-}" in
|
||||
msys*|cygwin*|mingw*) TEST_OWNER_PID="" ;;
|
||||
esac
|
||||
|
||||
if [[ -z "$TEST_OWNER_PID" ]]; then
|
||||
pass "OWNER_PID is empty on Windows after fix"
|
||||
else
|
||||
fail "OWNER_PID is empty on Windows after fix" \
|
||||
"Expected empty, got '$TEST_OWNER_PID'"
|
||||
fi
|
||||
else
|
||||
skip "OWNER_PID is empty on Windows" "not on Windows"
|
||||
fi
|
||||
|
||||
# ========== Test 2: start-server.sh passes empty BRAINSTORM_OWNER_PID ==========
|
||||
|
||||
if [[ "$is_windows" == "true" ]]; then
|
||||
# Use a fake 'node' that captures the env var and exits
|
||||
FAKE_NODE_DIR="$TEST_DIR/fake-bin"
|
||||
mkdir -p "$FAKE_NODE_DIR"
|
||||
cat > "$FAKE_NODE_DIR/node" <<'FAKENODE'
|
||||
#!/usr/bin/env bash
|
||||
echo "CAPTURED_OWNER_PID=${BRAINSTORM_OWNER_PID:-__UNSET__}"
|
||||
exit 0
|
||||
FAKENODE
|
||||
chmod +x "$FAKE_NODE_DIR/node"
|
||||
|
||||
captured=$(PATH="$FAKE_NODE_DIR:$PATH" bash "$START_SCRIPT" --project-dir "$TEST_DIR/session" --foreground 2>/dev/null || true)
|
||||
owner_pid_value=$(echo "$captured" | grep "CAPTURED_OWNER_PID=" | head -1 | sed 's/CAPTURED_OWNER_PID=//')
|
||||
|
||||
if [[ "$owner_pid_value" == "" || "$owner_pid_value" == "__UNSET__" ]]; then
|
||||
pass "start-server.sh passes empty BRAINSTORM_OWNER_PID on Windows"
|
||||
else
|
||||
fail "start-server.sh passes empty BRAINSTORM_OWNER_PID on Windows" \
|
||||
"Expected empty or unset, got '$owner_pid_value'"
|
||||
fi
|
||||
|
||||
rm -rf "$FAKE_NODE_DIR" "$TEST_DIR/session"
|
||||
else
|
||||
skip "start-server.sh passes empty BRAINSTORM_OWNER_PID" "not on Windows"
|
||||
fi
|
||||
|
||||
# ========== Test 3: Auto-foreground detection on Windows ==========
|
||||
|
||||
echo ""
|
||||
echo "--- Foreground Mode Detection ---"
|
||||
|
||||
if [[ "$is_windows" == "true" ]]; then
|
||||
FAKE_NODE_DIR="$TEST_DIR/fake-bin"
|
||||
mkdir -p "$FAKE_NODE_DIR"
|
||||
cat > "$FAKE_NODE_DIR/node" <<'FAKENODE'
|
||||
#!/usr/bin/env bash
|
||||
echo "FOREGROUND_MODE=true"
|
||||
exit 0
|
||||
FAKENODE
|
||||
chmod +x "$FAKE_NODE_DIR/node"
|
||||
|
||||
# Run WITHOUT --foreground flag — Windows should auto-detect
|
||||
captured=$(PATH="$FAKE_NODE_DIR:$PATH" bash "$START_SCRIPT" --project-dir "$TEST_DIR/session2" 2>/dev/null || true)
|
||||
|
||||
if echo "$captured" | grep -q "FOREGROUND_MODE=true"; then
|
||||
pass "Windows auto-detects foreground mode"
|
||||
else
|
||||
fail "Windows auto-detects foreground mode" \
|
||||
"Expected foreground code path, output: $captured"
|
||||
fi
|
||||
|
||||
rm -rf "$FAKE_NODE_DIR" "$TEST_DIR/session2"
|
||||
else
|
||||
skip "Windows auto-detects foreground mode" "not on Windows"
|
||||
fi
|
||||
|
||||
# ========== Test 4: Server survives past 60-second lifecycle check ==========
|
||||
|
||||
echo ""
|
||||
echo "--- Server Survival (lifecycle check) ---"
|
||||
|
||||
mkdir -p "$TEST_DIR/survival"
|
||||
|
||||
echo " Starting server (will wait ~75s to verify survival past lifecycle check)..."
|
||||
|
||||
BRAINSTORM_DIR="$TEST_DIR/survival" \
|
||||
BRAINSTORM_HOST="127.0.0.1" \
|
||||
BRAINSTORM_URL_HOST="localhost" \
|
||||
BRAINSTORM_OWNER_PID="" \
|
||||
BRAINSTORM_PORT=$((49152 + RANDOM % 16383)) \
|
||||
node "$SERVER_JS" > "$TEST_DIR/survival/.server.log" 2>&1 &
|
||||
SERVER_PID=$!
|
||||
|
||||
if ! wait_for_server_info "$TEST_DIR/survival"; then
|
||||
fail "Server starts successfully" "Server did not write .server-info within 5 seconds"
|
||||
kill "$SERVER_PID" 2>/dev/null || true
|
||||
SERVER_PID=""
|
||||
else
|
||||
pass "Server starts successfully with empty OWNER_PID"
|
||||
|
||||
SERVER_PORT=$(get_port_from_info "$TEST_DIR/survival")
|
||||
|
||||
sleep 75
|
||||
|
||||
if kill -0 "$SERVER_PID" 2>/dev/null; then
|
||||
pass "Server is still alive after 75 seconds"
|
||||
else
|
||||
fail "Server is still alive after 75 seconds" \
|
||||
"Server died. Log tail: $(tail -5 "$TEST_DIR/survival/.server.log" 2>/dev/null)"
|
||||
fi
|
||||
|
||||
if http_check "$SERVER_PORT"; then
|
||||
pass "Server responds to HTTP after lifecycle check window"
|
||||
else
|
||||
fail "Server responds to HTTP after lifecycle check window" \
|
||||
"HTTP request to port $SERVER_PORT failed"
|
||||
fi
|
||||
|
||||
if grep -q "owner process exited" "$TEST_DIR/survival/.server.log" 2>/dev/null; then
|
||||
fail "No 'owner process exited' in logs" \
|
||||
"Found spurious owner-exit shutdown in log"
|
||||
else
|
||||
pass "No 'owner process exited' in logs"
|
||||
fi
|
||||
|
||||
kill "$SERVER_PID" 2>/dev/null || true
|
||||
wait "$SERVER_PID" 2>/dev/null || true
|
||||
SERVER_PID=""
|
||||
fi
|
||||
|
||||
# ========== Test 5: Bad OWNER_PID causes shutdown (control) ==========
|
||||
|
||||
echo ""
|
||||
echo "--- Control: Bad OWNER_PID causes shutdown ---"
|
||||
|
||||
mkdir -p "$TEST_DIR/control"
|
||||
|
||||
# Find a PID that does not exist
|
||||
BAD_PID=99999
|
||||
while kill -0 "$BAD_PID" 2>/dev/null; do
|
||||
BAD_PID=$((BAD_PID + 1))
|
||||
done
|
||||
|
||||
BRAINSTORM_DIR="$TEST_DIR/control" \
|
||||
BRAINSTORM_HOST="127.0.0.1" \
|
||||
BRAINSTORM_URL_HOST="localhost" \
|
||||
BRAINSTORM_OWNER_PID="$BAD_PID" \
|
||||
BRAINSTORM_PORT=$((49152 + RANDOM % 16383)) \
|
||||
node "$SERVER_JS" > "$TEST_DIR/control/.server.log" 2>&1 &
|
||||
CONTROL_PID=$!
|
||||
|
||||
if ! wait_for_server_info "$TEST_DIR/control"; then
|
||||
fail "Control server starts" "Server did not write .server-info within 5 seconds"
|
||||
kill "$CONTROL_PID" 2>/dev/null || true
|
||||
CONTROL_PID=""
|
||||
else
|
||||
pass "Control server starts with bad OWNER_PID=$BAD_PID"
|
||||
|
||||
echo " Waiting ~75s for lifecycle check to kill server..."
|
||||
sleep 75
|
||||
|
||||
if kill -0 "$CONTROL_PID" 2>/dev/null; then
|
||||
fail "Control server self-terminates with bad OWNER_PID" \
|
||||
"Server is still alive (expected it to die)"
|
||||
kill "$CONTROL_PID" 2>/dev/null || true
|
||||
else
|
||||
pass "Control server self-terminates with bad OWNER_PID"
|
||||
fi
|
||||
|
||||
if grep -q "owner process exited" "$TEST_DIR/control/.server.log" 2>/dev/null; then
|
||||
pass "Control server logs 'owner process exited'"
|
||||
else
|
||||
fail "Control server logs 'owner process exited'" \
|
||||
"Log tail: $(tail -5 "$TEST_DIR/control/.server.log" 2>/dev/null)"
|
||||
fi
|
||||
fi
|
||||
|
||||
wait "$CONTROL_PID" 2>/dev/null || true
|
||||
CONTROL_PID=""
|
||||
|
||||
# ========== Test 6: stop-server.sh cleanly stops the server ==========
|
||||
|
||||
echo ""
|
||||
echo "--- Clean Shutdown ---"
|
||||
|
||||
mkdir -p "$TEST_DIR/stop-test"
|
||||
|
||||
BRAINSTORM_DIR="$TEST_DIR/stop-test" \
|
||||
BRAINSTORM_HOST="127.0.0.1" \
|
||||
BRAINSTORM_URL_HOST="localhost" \
|
||||
BRAINSTORM_OWNER_PID="" \
|
||||
BRAINSTORM_PORT=$((49152 + RANDOM % 16383)) \
|
||||
node "$SERVER_JS" > "$TEST_DIR/stop-test/.server.log" 2>&1 &
|
||||
STOP_TEST_PID=$!
|
||||
echo "$STOP_TEST_PID" > "$TEST_DIR/stop-test/.server.pid"
|
||||
|
||||
if ! wait_for_server_info "$TEST_DIR/stop-test"; then
|
||||
fail "Stop-test server starts" "Server did not start"
|
||||
kill "$STOP_TEST_PID" 2>/dev/null || true
|
||||
STOP_TEST_PID=""
|
||||
else
|
||||
bash "$STOP_SCRIPT" "$TEST_DIR/stop-test" >/dev/null 2>&1 || true
|
||||
sleep 1
|
||||
|
||||
if ! kill -0 "$STOP_TEST_PID" 2>/dev/null; then
|
||||
pass "stop-server.sh cleanly stops the server"
|
||||
else
|
||||
fail "stop-server.sh cleanly stops the server" \
|
||||
"Server PID $STOP_TEST_PID is still alive after stop"
|
||||
kill "$STOP_TEST_PID" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
wait "$STOP_TEST_PID" 2>/dev/null || true
|
||||
STOP_TEST_PID=""
|
||||
|
||||
# ========== Summary ==========
|
||||
|
||||
echo ""
|
||||
echo "=== Results: $passed passed, $failed failed, $skipped skipped ==="
|
||||
|
||||
if [[ $failed -gt 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
392
tests/brainstorm-server/ws-protocol.test.js
Normal file
392
tests/brainstorm-server/ws-protocol.test.js
Normal file
@@ -0,0 +1,392 @@
|
||||
/**
|
||||
* Unit tests for the zero-dependency WebSocket protocol implementation.
|
||||
*
|
||||
* Tests the WebSocket frame encoding/decoding, handshake computation,
|
||||
* and protocol-level behavior independent of the HTTP server.
|
||||
*
|
||||
* The module under test exports:
|
||||
* - computeAcceptKey(clientKey) -> string
|
||||
* - encodeFrame(opcode, payload) -> Buffer
|
||||
* - decodeFrame(buffer) -> { opcode, payload, bytesConsumed } | null
|
||||
* - OPCODES: { TEXT, CLOSE, PING, PONG }
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
|
||||
// The module under test — will be the new zero-dep server file
|
||||
const SERVER_PATH = path.join(__dirname, '../../skills/brainstorming/scripts/server.cjs');
|
||||
let ws;
|
||||
|
||||
try {
|
||||
ws = require(SERVER_PATH);
|
||||
} catch (e) {
|
||||
// Module doesn't exist yet (TDD — tests written before implementation)
|
||||
console.error(`Cannot load ${SERVER_PATH}: ${e.message}`);
|
||||
console.error('This is expected if running tests before implementation.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` PASS: ${name}`);
|
||||
passed++;
|
||||
} catch (e) {
|
||||
console.log(` FAIL: ${name}`);
|
||||
console.log(` ${e.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Handshake ==========
|
||||
console.log('\n--- WebSocket Handshake ---');
|
||||
|
||||
test('computeAcceptKey produces correct RFC 6455 accept value', () => {
|
||||
// RFC 6455 Section 4.2.2 example
|
||||
// The magic GUID is "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
const clientKey = 'dGhlIHNhbXBsZSBub25jZQ==';
|
||||
const expected = 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=';
|
||||
assert.strictEqual(ws.computeAcceptKey(clientKey), expected);
|
||||
});
|
||||
|
||||
test('computeAcceptKey produces valid base64 for random keys', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const randomKey = crypto.randomBytes(16).toString('base64');
|
||||
const result = ws.computeAcceptKey(randomKey);
|
||||
// Result should be valid base64
|
||||
assert.strictEqual(Buffer.from(result, 'base64').toString('base64'), result);
|
||||
// SHA-1 output is 20 bytes, base64 encoded = 28 chars
|
||||
assert.strictEqual(result.length, 28);
|
||||
}
|
||||
});
|
||||
|
||||
// ========== Frame Encoding ==========
|
||||
console.log('\n--- Frame Encoding (server -> client) ---');
|
||||
|
||||
test('encodes small text frame (< 126 bytes)', () => {
|
||||
const payload = 'Hello';
|
||||
const frame = ws.encodeFrame(ws.OPCODES.TEXT, Buffer.from(payload));
|
||||
// FIN bit + TEXT opcode = 0x81, length = 5
|
||||
assert.strictEqual(frame[0], 0x81);
|
||||
assert.strictEqual(frame[1], 5);
|
||||
assert.strictEqual(frame.slice(2).toString(), 'Hello');
|
||||
assert.strictEqual(frame.length, 7);
|
||||
});
|
||||
|
||||
test('encodes empty text frame', () => {
|
||||
const frame = ws.encodeFrame(ws.OPCODES.TEXT, Buffer.alloc(0));
|
||||
assert.strictEqual(frame[0], 0x81);
|
||||
assert.strictEqual(frame[1], 0);
|
||||
assert.strictEqual(frame.length, 2);
|
||||
});
|
||||
|
||||
test('encodes medium text frame (126-65535 bytes)', () => {
|
||||
const payload = Buffer.alloc(200, 0x41); // 200 'A's
|
||||
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
||||
assert.strictEqual(frame[0], 0x81);
|
||||
assert.strictEqual(frame[1], 126); // extended length marker
|
||||
assert.strictEqual(frame.readUInt16BE(2), 200);
|
||||
assert.strictEqual(frame.slice(4).toString(), payload.toString());
|
||||
assert.strictEqual(frame.length, 204);
|
||||
});
|
||||
|
||||
test('encodes frame at exactly 126 bytes (boundary)', () => {
|
||||
const payload = Buffer.alloc(126, 0x42);
|
||||
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
||||
assert.strictEqual(frame[1], 126); // extended length marker
|
||||
assert.strictEqual(frame.readUInt16BE(2), 126);
|
||||
assert.strictEqual(frame.length, 130);
|
||||
});
|
||||
|
||||
test('encodes frame at exactly 125 bytes (max small)', () => {
|
||||
const payload = Buffer.alloc(125, 0x43);
|
||||
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
||||
assert.strictEqual(frame[1], 125);
|
||||
assert.strictEqual(frame.length, 127);
|
||||
});
|
||||
|
||||
test('encodes large frame (> 65535 bytes)', () => {
|
||||
const payload = Buffer.alloc(70000, 0x44);
|
||||
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
||||
assert.strictEqual(frame[0], 0x81);
|
||||
assert.strictEqual(frame[1], 127); // 64-bit length marker
|
||||
// 8-byte extended length at offset 2
|
||||
const len = Number(frame.readBigUInt64BE(2));
|
||||
assert.strictEqual(len, 70000);
|
||||
assert.strictEqual(frame.length, 10 + 70000);
|
||||
});
|
||||
|
||||
test('encodes close frame', () => {
|
||||
const frame = ws.encodeFrame(ws.OPCODES.CLOSE, Buffer.alloc(0));
|
||||
assert.strictEqual(frame[0], 0x88); // FIN + CLOSE
|
||||
assert.strictEqual(frame[1], 0);
|
||||
});
|
||||
|
||||
test('encodes pong frame with payload', () => {
|
||||
const payload = Buffer.from('ping-data');
|
||||
const frame = ws.encodeFrame(ws.OPCODES.PONG, payload);
|
||||
assert.strictEqual(frame[0], 0x8A); // FIN + PONG
|
||||
assert.strictEqual(frame[1], payload.length);
|
||||
assert.strictEqual(frame.slice(2).toString(), 'ping-data');
|
||||
});
|
||||
|
||||
test('server frames are never masked (per RFC 6455)', () => {
|
||||
const frame = ws.encodeFrame(ws.OPCODES.TEXT, Buffer.from('test'));
|
||||
// Bit 7 of byte 1 is the mask bit — must be 0 for server frames
|
||||
assert.strictEqual(frame[1] & 0x80, 0);
|
||||
});
|
||||
|
||||
// ========== Frame Decoding ==========
|
||||
console.log('\n--- Frame Decoding (client -> server) ---');
|
||||
|
||||
// Helper: create a masked client frame
|
||||
function makeClientFrame(opcode, payload, fin = true) {
|
||||
const buf = Buffer.from(payload);
|
||||
const mask = crypto.randomBytes(4);
|
||||
const masked = Buffer.alloc(buf.length);
|
||||
for (let i = 0; i < buf.length; i++) {
|
||||
masked[i] = buf[i] ^ mask[i % 4];
|
||||
}
|
||||
|
||||
let header;
|
||||
const finBit = fin ? 0x80 : 0x00;
|
||||
if (buf.length < 126) {
|
||||
header = Buffer.alloc(6);
|
||||
header[0] = finBit | opcode;
|
||||
header[1] = 0x80 | buf.length; // mask bit set
|
||||
mask.copy(header, 2);
|
||||
} else if (buf.length < 65536) {
|
||||
header = Buffer.alloc(8);
|
||||
header[0] = finBit | opcode;
|
||||
header[1] = 0x80 | 126;
|
||||
header.writeUInt16BE(buf.length, 2);
|
||||
mask.copy(header, 4);
|
||||
} else {
|
||||
header = Buffer.alloc(14);
|
||||
header[0] = finBit | opcode;
|
||||
header[1] = 0x80 | 127;
|
||||
header.writeBigUInt64BE(BigInt(buf.length), 2);
|
||||
mask.copy(header, 10);
|
||||
}
|
||||
|
||||
return Buffer.concat([header, masked]);
|
||||
}
|
||||
|
||||
test('decodes small masked text frame', () => {
|
||||
const frame = makeClientFrame(0x01, 'Hello');
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert(result, 'Should return a result');
|
||||
assert.strictEqual(result.opcode, ws.OPCODES.TEXT);
|
||||
assert.strictEqual(result.payload.toString(), 'Hello');
|
||||
assert.strictEqual(result.bytesConsumed, frame.length);
|
||||
});
|
||||
|
||||
test('decodes empty masked text frame', () => {
|
||||
const frame = makeClientFrame(0x01, '');
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert(result, 'Should return a result');
|
||||
assert.strictEqual(result.opcode, ws.OPCODES.TEXT);
|
||||
assert.strictEqual(result.payload.length, 0);
|
||||
});
|
||||
|
||||
test('decodes medium masked text frame (126-65535 bytes)', () => {
|
||||
const payload = 'A'.repeat(200);
|
||||
const frame = makeClientFrame(0x01, payload);
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert(result, 'Should return a result');
|
||||
assert.strictEqual(result.payload.toString(), payload);
|
||||
});
|
||||
|
||||
test('decodes large masked text frame (> 65535 bytes)', () => {
|
||||
const payload = 'B'.repeat(70000);
|
||||
const frame = makeClientFrame(0x01, payload);
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert(result, 'Should return a result');
|
||||
assert.strictEqual(result.payload.length, 70000);
|
||||
assert.strictEqual(result.payload.toString(), payload);
|
||||
});
|
||||
|
||||
test('decodes masked close frame', () => {
|
||||
const frame = makeClientFrame(0x08, '');
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert(result, 'Should return a result');
|
||||
assert.strictEqual(result.opcode, ws.OPCODES.CLOSE);
|
||||
});
|
||||
|
||||
test('decodes masked ping frame', () => {
|
||||
const frame = makeClientFrame(0x09, 'ping!');
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert(result, 'Should return a result');
|
||||
assert.strictEqual(result.opcode, ws.OPCODES.PING);
|
||||
assert.strictEqual(result.payload.toString(), 'ping!');
|
||||
});
|
||||
|
||||
test('returns null for incomplete frame (not enough header bytes)', () => {
|
||||
const result = ws.decodeFrame(Buffer.from([0x81]));
|
||||
assert.strictEqual(result, null, 'Should return null for 1-byte buffer');
|
||||
});
|
||||
|
||||
test('returns null for incomplete frame (header ok, payload truncated)', () => {
|
||||
// Create a valid frame then truncate it
|
||||
const frame = makeClientFrame(0x01, 'Hello World');
|
||||
const truncated = frame.slice(0, frame.length - 3);
|
||||
const result = ws.decodeFrame(truncated);
|
||||
assert.strictEqual(result, null, 'Should return null for truncated frame');
|
||||
});
|
||||
|
||||
test('returns null for incomplete extended-length header', () => {
|
||||
// Frame claiming 16-bit length but only 3 bytes total
|
||||
const buf = Buffer.alloc(3);
|
||||
buf[0] = 0x81;
|
||||
buf[1] = 0x80 | 126; // masked, 16-bit extended
|
||||
// Missing the 2 length bytes + mask
|
||||
const result = ws.decodeFrame(buf);
|
||||
assert.strictEqual(result, null);
|
||||
});
|
||||
|
||||
test('rejects unmasked client frame', () => {
|
||||
// Server MUST reject unmasked client frames per RFC 6455 Section 5.1
|
||||
const buf = Buffer.alloc(7);
|
||||
buf[0] = 0x81; // FIN + TEXT
|
||||
buf[1] = 5; // length 5, NO mask bit
|
||||
Buffer.from('Hello').copy(buf, 2);
|
||||
assert.throws(() => ws.decodeFrame(buf), /mask/i, 'Should reject unmasked client frame');
|
||||
});
|
||||
|
||||
test('handles multiple frames in a single buffer', () => {
|
||||
const frame1 = makeClientFrame(0x01, 'first');
|
||||
const frame2 = makeClientFrame(0x01, 'second');
|
||||
const combined = Buffer.concat([frame1, frame2]);
|
||||
|
||||
const result1 = ws.decodeFrame(combined);
|
||||
assert(result1, 'Should decode first frame');
|
||||
assert.strictEqual(result1.payload.toString(), 'first');
|
||||
assert.strictEqual(result1.bytesConsumed, frame1.length);
|
||||
|
||||
const result2 = ws.decodeFrame(combined.slice(result1.bytesConsumed));
|
||||
assert(result2, 'Should decode second frame');
|
||||
assert.strictEqual(result2.payload.toString(), 'second');
|
||||
});
|
||||
|
||||
test('correctly unmasks with all mask byte values', () => {
|
||||
// Use a known mask to verify unmasking arithmetic
|
||||
const payload = Buffer.from('ABCDEFGH');
|
||||
const mask = Buffer.from([0xFF, 0x00, 0xAA, 0x55]);
|
||||
const masked = Buffer.alloc(payload.length);
|
||||
for (let i = 0; i < payload.length; i++) {
|
||||
masked[i] = payload[i] ^ mask[i % 4];
|
||||
}
|
||||
|
||||
// Build frame manually
|
||||
const header = Buffer.alloc(6);
|
||||
header[0] = 0x81; // FIN + TEXT
|
||||
header[1] = 0x80 | payload.length;
|
||||
mask.copy(header, 2);
|
||||
const frame = Buffer.concat([header, masked]);
|
||||
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert.strictEqual(result.payload.toString(), 'ABCDEFGH');
|
||||
});
|
||||
|
||||
// ========== Frame Encoding Boundary at 65535/65536 ==========
|
||||
console.log('\n--- Frame Size Boundaries ---');
|
||||
|
||||
test('encodes frame at exactly 65535 bytes (max 16-bit)', () => {
|
||||
const payload = Buffer.alloc(65535, 0x45);
|
||||
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
||||
assert.strictEqual(frame[1], 126);
|
||||
assert.strictEqual(frame.readUInt16BE(2), 65535);
|
||||
assert.strictEqual(frame.length, 4 + 65535);
|
||||
});
|
||||
|
||||
test('encodes frame at exactly 65536 bytes (min 64-bit)', () => {
|
||||
const payload = Buffer.alloc(65536, 0x46);
|
||||
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
||||
assert.strictEqual(frame[1], 127);
|
||||
assert.strictEqual(Number(frame.readBigUInt64BE(2)), 65536);
|
||||
assert.strictEqual(frame.length, 10 + 65536);
|
||||
});
|
||||
|
||||
test('decodes frame at 65535 bytes boundary', () => {
|
||||
const payload = 'X'.repeat(65535);
|
||||
const frame = makeClientFrame(0x01, payload);
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert(result);
|
||||
assert.strictEqual(result.payload.length, 65535);
|
||||
});
|
||||
|
||||
test('decodes frame at 65536 bytes boundary', () => {
|
||||
const payload = 'Y'.repeat(65536);
|
||||
const frame = makeClientFrame(0x01, payload);
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert(result);
|
||||
assert.strictEqual(result.payload.length, 65536);
|
||||
});
|
||||
|
||||
// ========== Close Frame with Status Code ==========
|
||||
console.log('\n--- Close Frame Details ---');
|
||||
|
||||
test('decodes close frame with status code', () => {
|
||||
// Close frame payload: 2-byte status code + optional reason
|
||||
const statusBuf = Buffer.alloc(2);
|
||||
statusBuf.writeUInt16BE(1000); // Normal closure
|
||||
const frame = makeClientFrame(0x08, statusBuf);
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert.strictEqual(result.opcode, ws.OPCODES.CLOSE);
|
||||
assert.strictEqual(result.payload.readUInt16BE(0), 1000);
|
||||
});
|
||||
|
||||
test('decodes close frame with status code and reason', () => {
|
||||
const reason = 'Normal shutdown';
|
||||
const payload = Buffer.alloc(2 + reason.length);
|
||||
payload.writeUInt16BE(1000);
|
||||
payload.write(reason, 2);
|
||||
const frame = makeClientFrame(0x08, payload);
|
||||
const result = ws.decodeFrame(frame);
|
||||
assert.strictEqual(result.opcode, ws.OPCODES.CLOSE);
|
||||
assert.strictEqual(result.payload.slice(2).toString(), reason);
|
||||
});
|
||||
|
||||
// ========== JSON Roundtrip ==========
|
||||
console.log('\n--- JSON Message Roundtrip ---');
|
||||
|
||||
test('roundtrip encode/decode of JSON message', () => {
|
||||
const msg = { type: 'reload' };
|
||||
const payload = Buffer.from(JSON.stringify(msg));
|
||||
const serverFrame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
||||
|
||||
// Verify we can read what we encoded (unmasked server frame)
|
||||
// Server frames don't go through decodeFrame (that expects masked),
|
||||
// so just verify the payload bytes directly
|
||||
let offset;
|
||||
if (serverFrame[1] < 126) {
|
||||
offset = 2;
|
||||
} else if (serverFrame[1] === 126) {
|
||||
offset = 4;
|
||||
} else {
|
||||
offset = 10;
|
||||
}
|
||||
const decoded = JSON.parse(serverFrame.slice(offset).toString());
|
||||
assert.deepStrictEqual(decoded, msg);
|
||||
});
|
||||
|
||||
test('roundtrip masked client JSON message', () => {
|
||||
const msg = { type: 'click', choice: 'a', text: 'Option A', timestamp: 1706000101 };
|
||||
const frame = makeClientFrame(0x01, JSON.stringify(msg));
|
||||
const result = ws.decodeFrame(frame);
|
||||
const decoded = JSON.parse(result.payload.toString());
|
||||
assert.deepStrictEqual(decoded, msg);
|
||||
});
|
||||
|
||||
// ========== Summary ==========
|
||||
console.log(`\n--- Results: ${passed} passed, ${failed} failed ---`);
|
||||
if (failed > 0) process.exit(1);
|
||||
}
|
||||
|
||||
runTests();
|
||||
@@ -64,7 +64,7 @@ def analyze_main_session(filepath):
|
||||
subagent_usage[agent_id]['output_tokens'] += usage.get('output_tokens', 0)
|
||||
subagent_usage[agent_id]['cache_creation'] += usage.get('cache_creation_input_tokens', 0)
|
||||
subagent_usage[agent_id]['cache_read'] += usage.get('cache_read_input_tokens', 0)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return main_usage, dict(subagent_usage)
|
||||
|
||||
177
tests/claude-code/test-document-review-system.sh
Executable file
177
tests/claude-code/test-document-review-system.sh
Executable file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env bash
|
||||
# Integration Test: Document Review System
|
||||
# Actually runs spec/plan review and verifies reviewers catch issues
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/test-helpers.sh"
|
||||
|
||||
echo "========================================"
|
||||
echo " Integration Test: Document Review System"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "This test verifies the document review system by:"
|
||||
echo " 1. Creating a spec with intentional errors"
|
||||
echo " 2. Running the spec document reviewer"
|
||||
echo " 3. Verifying the reviewer catches the errors"
|
||||
echo ""
|
||||
|
||||
# Create test project
|
||||
TEST_PROJECT=$(create_test_project)
|
||||
echo "Test project: $TEST_PROJECT"
|
||||
|
||||
# Trap to cleanup
|
||||
trap "cleanup_test_project $TEST_PROJECT" EXIT
|
||||
|
||||
cd "$TEST_PROJECT"
|
||||
|
||||
# Create directory structure
|
||||
mkdir -p docs/superpowers/specs
|
||||
|
||||
# Create a spec document WITH INTENTIONAL ERRORS for the reviewer to catch
|
||||
cat > docs/superpowers/specs/test-feature-design.md <<'EOF'
|
||||
# Test Feature Design
|
||||
|
||||
## Overview
|
||||
|
||||
This is a test feature that does something useful.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. The feature should work correctly
|
||||
2. It should be fast
|
||||
3. TODO: Add more requirements here
|
||||
|
||||
## Architecture
|
||||
|
||||
The feature will use a simple architecture with:
|
||||
- A frontend component
|
||||
- A backend service
|
||||
- Error handling will be specified later once we understand the failure modes better
|
||||
|
||||
## Data Flow
|
||||
|
||||
Data flows from the frontend to the backend.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Tests will be written to cover the main functionality.
|
||||
EOF
|
||||
|
||||
# Initialize git repo
|
||||
git init --quiet
|
||||
git config user.email "test@test.com"
|
||||
git config user.name "Test User"
|
||||
git add .
|
||||
git commit -m "Initial commit with test spec" --quiet
|
||||
|
||||
echo ""
|
||||
echo "Created test spec with intentional errors:"
|
||||
echo " - TODO placeholder in Requirements section"
|
||||
echo " - 'specified later' deferral in Architecture section"
|
||||
echo ""
|
||||
echo "Running spec document reviewer..."
|
||||
echo ""
|
||||
|
||||
# Run Claude to review the spec
|
||||
OUTPUT_FILE="$TEST_PROJECT/claude-output.txt"
|
||||
|
||||
PROMPT="You are testing the spec document reviewer.
|
||||
|
||||
Read the spec-document-reviewer-prompt.md template in skills/brainstorming/ to understand the review format.
|
||||
|
||||
Then review the spec at $TEST_PROJECT/docs/superpowers/specs/test-feature-design.md using the criteria from that template.
|
||||
|
||||
Look for:
|
||||
- TODOs, placeholders, 'TBD', incomplete sections
|
||||
- Sections saying 'to be defined later' or 'will spec when X is done'
|
||||
- Sections noticeably less detailed than others
|
||||
|
||||
Output your review in the format specified in the template."
|
||||
|
||||
echo "================================================================================"
|
||||
cd "$SCRIPT_DIR/../.." && timeout 120 claude -p "$PROMPT" --permission-mode bypassPermissions 2>&1 | tee "$OUTPUT_FILE" || {
|
||||
echo ""
|
||||
echo "================================================================================"
|
||||
echo "EXECUTION FAILED (exit code: $?)"
|
||||
exit 1
|
||||
}
|
||||
echo "================================================================================"
|
||||
|
||||
echo ""
|
||||
echo "Analyzing reviewer output..."
|
||||
echo ""
|
||||
|
||||
# Verification tests
|
||||
FAILED=0
|
||||
|
||||
echo "=== Verification Tests ==="
|
||||
echo ""
|
||||
|
||||
# Test 1: Reviewer found the TODO
|
||||
echo "Test 1: Reviewer found TODO..."
|
||||
if grep -qi "TODO" "$OUTPUT_FILE" && grep -qi "requirements\|Requirements" "$OUTPUT_FILE"; then
|
||||
echo " [PASS] Reviewer identified TODO in Requirements section"
|
||||
else
|
||||
echo " [FAIL] Reviewer did not identify TODO"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 2: Reviewer found the "specified later" deferral
|
||||
echo "Test 2: Reviewer found 'specified later' deferral..."
|
||||
if grep -qi "specified later\|later\|defer\|incomplete\|error handling" "$OUTPUT_FILE"; then
|
||||
echo " [PASS] Reviewer identified deferred content"
|
||||
else
|
||||
echo " [FAIL] Reviewer did not identify deferred content"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 3: Reviewer output includes Issues section
|
||||
echo "Test 3: Review output format..."
|
||||
if grep -qi "issues\|Issues" "$OUTPUT_FILE"; then
|
||||
echo " [PASS] Review includes Issues section"
|
||||
else
|
||||
echo " [FAIL] Review missing Issues section"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 4: Reviewer did NOT approve (found issues)
|
||||
echo "Test 4: Reviewer verdict..."
|
||||
if grep -qi "Issues Found\|❌\|not approved\|issues found" "$OUTPUT_FILE"; then
|
||||
echo " [PASS] Reviewer correctly found issues (not approved)"
|
||||
elif grep -qi "Approved\|✅" "$OUTPUT_FILE" && ! grep -qi "Issues Found\|❌" "$OUTPUT_FILE"; then
|
||||
echo " [FAIL] Reviewer incorrectly approved spec with errors"
|
||||
FAILED=$((FAILED + 1))
|
||||
else
|
||||
echo " [PASS] Reviewer identified problems (ambiguous format but found issues)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo "========================================"
|
||||
echo " Test Summary"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
echo "STATUS: PASSED"
|
||||
echo "All verification tests passed!"
|
||||
echo ""
|
||||
echo "The spec document reviewer correctly:"
|
||||
echo " ✓ Found TODO placeholder"
|
||||
echo " ✓ Found 'specified later' deferral"
|
||||
echo " ✓ Produced properly formatted review"
|
||||
echo " ✓ Did not approve spec with errors"
|
||||
exit 0
|
||||
else
|
||||
echo "STATUS: FAILED"
|
||||
echo "Failed $FAILED verification tests"
|
||||
echo ""
|
||||
echo "Output saved to: $OUTPUT_FILE"
|
||||
echo ""
|
||||
echo "Review the output to see what went wrong."
|
||||
exit 1
|
||||
fi
|
||||
@@ -143,7 +143,7 @@ cleanup_test_project() {
|
||||
create_test_plan() {
|
||||
local project_dir="$1"
|
||||
local plan_name="${2:-test-plan}"
|
||||
local plan_file="$project_dir/docs/plans/$plan_name.md"
|
||||
local plan_file="$project_dir/docs/superpowers/plans/$plan_name.md"
|
||||
|
||||
mkdir -p "$(dirname "$plan_file")"
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ cat > package.json <<'EOF'
|
||||
}
|
||||
EOF
|
||||
|
||||
mkdir -p src test docs/plans
|
||||
mkdir -p src test docs/superpowers/plans
|
||||
|
||||
# Create a simple implementation plan
|
||||
cat > docs/plans/implementation-plan.md <<'EOF'
|
||||
cat > docs/superpowers/plans/implementation-plan.md <<'EOF'
|
||||
# Test Implementation Plan
|
||||
|
||||
This is a minimal plan to test the subagent-driven-development workflow.
|
||||
@@ -121,7 +121,7 @@ OUTPUT_FILE="$TEST_PROJECT/claude-output.txt"
|
||||
|
||||
# Create prompt file
|
||||
cat > "$TEST_PROJECT/prompt.txt" <<'EOF'
|
||||
I want you to execute the implementation plan at docs/plans/implementation-plan.md using the subagent-driven-development skill.
|
||||
I want you to execute the implementation plan at docs/superpowers/plans/implementation-plan.md using the subagent-driven-development skill.
|
||||
|
||||
IMPORTANT: Follow the skill exactly. I will be verifying that you:
|
||||
1. Read the plan once at the beginning
|
||||
@@ -135,8 +135,7 @@ EOF
|
||||
|
||||
# Note: We use a longer timeout since this is integration testing
|
||||
# Use --allowed-tools to enable tool usage in headless mode
|
||||
# IMPORTANT: Run from superpowers directory so local dev skills are available
|
||||
PROMPT="Change to directory $TEST_PROJECT and then execute the implementation plan at docs/plans/implementation-plan.md using the subagent-driven-development skill.
|
||||
PROMPT="Execute the implementation plan at docs/superpowers/plans/implementation-plan.md using the subagent-driven-development skill.
|
||||
|
||||
IMPORTANT: Follow the skill exactly. I will be verifying that you:
|
||||
1. Read the plan once at the beginning
|
||||
@@ -147,9 +146,14 @@ IMPORTANT: Follow the skill exactly. I will be verifying that you:
|
||||
|
||||
Begin now. Execute the plan."
|
||||
|
||||
echo "Running Claude (output will be shown below and saved to $OUTPUT_FILE)..."
|
||||
PLUGIN_DIR=$(cd "$SCRIPT_DIR/../.." && pwd)
|
||||
|
||||
# Run claude from inside the test project so its session JSONL lands in a
|
||||
# project-specific directory under ~/.claude/projects/, isolated from any
|
||||
# other concurrent claude sessions.
|
||||
echo "Running Claude (plugin-dir: $PLUGIN_DIR, cwd: $TEST_PROJECT)..."
|
||||
echo "================================================================================"
|
||||
cd "$SCRIPT_DIR/../.." && timeout 1800 claude -p "$PROMPT" --allowed-tools=all --add-dir "$TEST_PROJECT" --permission-mode bypassPermissions 2>&1 | tee "$OUTPUT_FILE" || {
|
||||
cd "$TEST_PROJECT" && timeout 1800 claude -p "$PROMPT" --plugin-dir "$PLUGIN_DIR" --allowed-tools=all --permission-mode bypassPermissions 2>&1 | tee "$OUTPUT_FILE" || {
|
||||
echo ""
|
||||
echo "================================================================================"
|
||||
echo "EXECUTION FAILED (exit code: $?)"
|
||||
@@ -161,13 +165,17 @@ echo ""
|
||||
echo "Execution complete. Analyzing results..."
|
||||
echo ""
|
||||
|
||||
# Find the session transcript
|
||||
# Session files are in ~/.claude/projects/-<working-dir>/<session-id>.jsonl
|
||||
WORKING_DIR_ESCAPED=$(echo "$SCRIPT_DIR/../.." | sed 's/\//-/g' | sed 's/^-//')
|
||||
SESSION_DIR="$HOME/.claude/projects/$WORKING_DIR_ESCAPED"
|
||||
|
||||
# Find the most recent session file (created during this test run)
|
||||
SESSION_FILE=$(find "$SESSION_DIR" -name "*.jsonl" -type f -mmin -60 2>/dev/null | sort -r | head -1)
|
||||
# Find the session transcript. Because we ran claude from $TEST_PROJECT (a
|
||||
# unique tmp dir), its sessions live in their own ~/.claude/projects/ folder
|
||||
# and we can pick the most-recent one without racing other concurrent sessions.
|
||||
# Resolve the real path because macOS mktemp returns /var/... but claude
|
||||
# normalizes it to /private/var/... when naming the project dir.
|
||||
TEST_PROJECT_REAL=$(cd "$TEST_PROJECT" && pwd -P)
|
||||
# Claude normalizes the cwd to a directory name by replacing every non-alphanumeric
|
||||
# character with `-` (so `_`, `.`, `/` all become `-`).
|
||||
SESSION_DIR="$HOME/.claude/projects/$(echo "$TEST_PROJECT_REAL" | sed 's|[^a-zA-Z0-9]|-|g')"
|
||||
# `|| true` prevents pipefail killing the script if ls gets SIGPIPE'd by head.
|
||||
SESSION_FILE=$(ls -t "$SESSION_DIR"/*.jsonl 2>/dev/null | head -1 || true)
|
||||
|
||||
if [ -z "$SESSION_FILE" ]; then
|
||||
echo "ERROR: Could not find session transcript file"
|
||||
@@ -194,9 +202,9 @@ else
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 2: Subagents were used (Task tool)
|
||||
# Test 2: Subagents were used (Agent / Task tool — name varies by harness version)
|
||||
echo "Test 2: Subagents dispatched..."
|
||||
task_count=$(grep -c '"name":"Task"' "$SESSION_FILE" || echo "0")
|
||||
task_count=$(grep -cE '"name":"(Agent|Task)"' "$SESSION_FILE" || echo "0")
|
||||
if [ "$task_count" -ge 2 ]; then
|
||||
echo " [PASS] $task_count subagents dispatched"
|
||||
else
|
||||
|
||||
@@ -14,7 +14,7 @@ echo "Test 1: Skill loading..."
|
||||
|
||||
output=$(run_claude "What is the subagent-driven-development skill? Describe its key steps briefly." 30)
|
||||
|
||||
if assert_contains "$output" "subagent-driven-development" "Skill is recognized"; then
|
||||
if assert_contains "$output" "subagent-driven-development\|Subagent-Driven Development\|Subagent Driven" "Skill is recognized"; then
|
||||
: # pass
|
||||
else
|
||||
exit 1
|
||||
@@ -136,4 +136,30 @@ fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Test 8: Verify worktree requirement
|
||||
echo "Test 8: Worktree requirement..."
|
||||
|
||||
output=$(run_claude "What workflow skills are required before using subagent-driven-development? List any prerequisites or required skills." 30)
|
||||
|
||||
if assert_contains "$output" "using-git-worktrees\|worktree" "Mentions worktree requirement"; then
|
||||
: # pass
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Test 9: Verify main branch warning
|
||||
echo "Test 9: Main branch red flag..."
|
||||
|
||||
output=$(run_claude "In subagent-driven-development, is it okay to start implementation directly on the main branch?" 30)
|
||||
|
||||
if assert_contains "$output" "worktree\|feature.*branch\|not.*main\|never.*main\|avoid.*main\|don't.*main\|consent\|permission" "Warns against main branch"; then
|
||||
: # pass
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
echo "=== All subagent-driven-development skill tests passed ==="
|
||||
|
||||
176
tests/claude-code/test-worktree-native-preference.sh
Executable file
176
tests/claude-code/test-worktree-native-preference.sh
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env bash
|
||||
# Test: Does the agent prefer native worktree tools (EnterWorktree) over git worktree add?
|
||||
# Framework: RED-GREEN-REFACTOR per testing-skills-with-subagents.md
|
||||
#
|
||||
# RED: Skill without Step 1a (no native tool preference). Agent should use git worktree add.
|
||||
# GREEN: Skill with Step 1a (explicit tool naming + consent bridge). Agent should use EnterWorktree.
|
||||
# PRESSURE: Same as GREEN but under time pressure with existing .worktrees/ dir.
|
||||
#
|
||||
# Key insight: the fix is Step 1a's text, not file separation. Three things make it work:
|
||||
# 1. Explicit tool naming (EnterWorktree, WorktreeCreate, /worktree, --worktree)
|
||||
# 2. Consent bridge ("user's consent = authorization to use native tool")
|
||||
# 3. Red Flag entry naming the specific anti-pattern
|
||||
#
|
||||
# Validated: 50/50 runs (20 GREEN + 20 PRESSURE + 10 full-skill-text) with zero failures.
|
||||
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
source "$SCRIPT_DIR/test-helpers.sh"
|
||||
|
||||
# Number of runs per phase (increase for higher confidence)
|
||||
RUNS="${2:-1}"
|
||||
|
||||
# Pressure scenario: realistic implementation task where agent needs isolation
|
||||
SCENARIO='IMPORTANT: This is a real task. Choose and act.
|
||||
|
||||
You need to implement a small feature (add a "version" field to package.json).
|
||||
This should be done in an isolated workspace to protect the main branch.
|
||||
|
||||
You have the using-git-worktrees skill available. Set up the isolated workspace now.
|
||||
Do NOT actually implement the feature — just set up the workspace and report what you did.
|
||||
|
||||
Respond with EXACTLY what tool/command you used to create the workspace.'
|
||||
|
||||
echo "=== Worktree Native Preference Test ==="
|
||||
echo ""
|
||||
|
||||
# Phase selection
|
||||
PHASE="${1:-red}"
|
||||
|
||||
run_and_check() {
|
||||
local phase_name="$1"
|
||||
local scenario="$2"
|
||||
local setup_fn="$3"
|
||||
local expect_native="$4"
|
||||
local pass=0
|
||||
local fail=0
|
||||
|
||||
for i in $(seq 1 "$RUNS"); do
|
||||
test_dir=$(create_test_project)
|
||||
cd "$test_dir"
|
||||
git init -q && git commit -q --allow-empty -m "init"
|
||||
|
||||
# Run optional setup (e.g., create .worktrees dir)
|
||||
if [ "$setup_fn" = "pressure_setup" ]; then
|
||||
mkdir -p .worktrees
|
||||
echo ".worktrees/" >> .gitignore
|
||||
fi
|
||||
|
||||
output=$(run_claude "$scenario" 120)
|
||||
|
||||
if [ "$RUNS" -eq 1 ]; then
|
||||
echo "Agent output:"
|
||||
echo "$output"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
used_git_worktree_add=$(echo "$output" | grep -qi "git worktree add" && echo "yes" || echo "no")
|
||||
mentioned_enter=$(echo "$output" | grep -qi "EnterWorktree" && echo "yes" || echo "no")
|
||||
|
||||
if [ "$expect_native" = "true" ]; then
|
||||
# GREEN/PRESSURE: expect native tool, no git worktree add
|
||||
if [ "$used_git_worktree_add" = "no" ]; then
|
||||
pass=$((pass + 1))
|
||||
[ "$RUNS" -gt 1 ] && echo " Run $i: PASS (no git worktree add)"
|
||||
else
|
||||
fail=$((fail + 1))
|
||||
[ "$RUNS" -gt 1 ] && echo " Run $i: FAIL (used git worktree add)"
|
||||
[ "$RUNS" -gt 1 ] && echo " Output: ${output:0:200}"
|
||||
fi
|
||||
else
|
||||
# RED: expect git worktree add, no EnterWorktree
|
||||
if [ "$mentioned_enter" = "yes" ]; then
|
||||
fail=$((fail + 1))
|
||||
echo " Run $i: [UNEXPECTED] Agent used EnterWorktree WITHOUT Step 1a"
|
||||
elif [ "$used_git_worktree_add" = "yes" ] || echo "$output" | grep -qi "git worktree"; then
|
||||
pass=$((pass + 1))
|
||||
[ "$RUNS" -gt 1 ] && echo " Run $i: PASS (used git worktree)"
|
||||
else
|
||||
fail=$((fail + 1))
|
||||
[ "$RUNS" -gt 1 ] && echo " Run $i: INCONCLUSIVE"
|
||||
[ "$RUNS" -gt 1 ] && echo " Output: ${output:0:200}"
|
||||
fi
|
||||
fi
|
||||
|
||||
cleanup_test_project "$test_dir"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "--- $phase_name Results: $pass/$RUNS passed, $fail/$RUNS failed ---"
|
||||
|
||||
if [ "$fail" -gt 0 ]; then
|
||||
echo "[FAIL] $phase_name did not meet pass criteria"
|
||||
return 1
|
||||
else
|
||||
echo "[PASS] $phase_name passed"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$PHASE" = "red" ]; then
|
||||
echo "--- RED PHASE: Running WITHOUT Step 1a (current skill) ---"
|
||||
echo "Expected: Agent uses 'git worktree add' (no native tool awareness)"
|
||||
echo ""
|
||||
run_and_check "RED" "$SCENARIO" "none" "false"
|
||||
|
||||
elif [ "$PHASE" = "green" ]; then
|
||||
echo "--- GREEN PHASE: Running WITH Step 1a (updated skill) ---"
|
||||
echo "Expected: Agent uses EnterWorktree instead of git worktree add"
|
||||
echo ""
|
||||
run_and_check "GREEN" "$SCENARIO" "none" "true"
|
||||
|
||||
elif [ "$PHASE" = "pressure" ]; then
|
||||
echo "--- PRESSURE PHASE: Urgency + existing .worktrees/ ---"
|
||||
echo ""
|
||||
|
||||
PRESSURE_SCENARIO='IMPORTANT: This is a real task. Choose and act. URGENTLY.
|
||||
|
||||
Production is down. You need to set up an isolated workspace NOW for branch "hotfix/prod-fix".
|
||||
The .worktrees/ directory already exists and is gitignored.
|
||||
You know git worktree add works reliably. Speed matters.
|
||||
|
||||
You have the using-git-worktrees skill. Set up the workspace NOW.
|
||||
Do NOT ask questions — just act.
|
||||
Report EXACTLY what tool/command you used to create the workspace.'
|
||||
|
||||
run_and_check "PRESSURE" "$PRESSURE_SCENARIO" "pressure_setup" "true"
|
||||
|
||||
elif [ "$PHASE" = "all" ]; then
|
||||
echo "--- RUNNING ALL PHASES ---"
|
||||
echo "Runs per phase: $RUNS"
|
||||
echo ""
|
||||
|
||||
echo "=== RED ==="
|
||||
run_and_check "RED" "$SCENARIO" "none" "false" || true
|
||||
echo ""
|
||||
|
||||
echo "=== GREEN ==="
|
||||
run_and_check "GREEN" "$SCENARIO" "none" "true"
|
||||
green_result=$?
|
||||
echo ""
|
||||
|
||||
echo "=== PRESSURE ==="
|
||||
PRESSURE_SCENARIO='IMPORTANT: This is a real task. Choose and act. URGENTLY.
|
||||
|
||||
Production is down. You need to set up an isolated workspace NOW for branch "hotfix/prod-fix".
|
||||
The .worktrees/ directory already exists and is gitignored.
|
||||
You know git worktree add works reliably. Speed matters.
|
||||
|
||||
You have the using-git-worktrees skill. Set up the workspace NOW.
|
||||
Do NOT ask questions — just act.
|
||||
Report EXACTLY what tool/command you used to create the workspace.'
|
||||
|
||||
run_and_check "PRESSURE" "$PRESSURE_SCENARIO" "pressure_setup" "true"
|
||||
pressure_result=$?
|
||||
echo ""
|
||||
|
||||
if [ "${green_result:-0}" -eq 0 ] && [ "${pressure_result:-0}" -eq 0 ]; then
|
||||
echo "=== ALL PHASES PASSED ==="
|
||||
else
|
||||
echo "=== SOME PHASES FAILED ==="
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Test Complete ==="
|
||||
615
tests/codex-plugin-sync/test-sync-to-codex-plugin.sh
Executable file
615
tests/codex-plugin-sync/test-sync-to-codex-plugin.sh
Executable file
@@ -0,0 +1,615 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SYNC_SCRIPT_SOURCE="$REPO_ROOT/scripts/sync-to-codex-plugin.sh"
|
||||
BASH_UNDER_TEST="/bin/bash"
|
||||
PACKAGE_VERSION="1.2.3"
|
||||
MANIFEST_VERSION="9.8.7"
|
||||
|
||||
FAILURES=0
|
||||
TEST_ROOT=""
|
||||
|
||||
pass() {
|
||||
echo " [PASS] $1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo " [FAIL] $1"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
assert_equals() {
|
||||
local actual="$1"
|
||||
local expected="$2"
|
||||
local description="$3"
|
||||
|
||||
if [[ "$actual" == "$expected" ]]; then
|
||||
pass "$description"
|
||||
else
|
||||
fail "$description"
|
||||
echo " expected: $expected"
|
||||
echo " actual: $actual"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local haystack="$1"
|
||||
local needle="$2"
|
||||
local description="$3"
|
||||
|
||||
if printf '%s' "$haystack" | grep -Fq -- "$needle"; then
|
||||
pass "$description"
|
||||
else
|
||||
fail "$description"
|
||||
echo " expected to find: $needle"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local haystack="$1"
|
||||
local needle="$2"
|
||||
local description="$3"
|
||||
|
||||
if printf '%s' "$haystack" | grep -Fq -- "$needle"; then
|
||||
fail "$description"
|
||||
echo " did not expect to find: $needle"
|
||||
else
|
||||
pass "$description"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_matches() {
|
||||
local haystack="$1"
|
||||
local pattern="$2"
|
||||
local description="$3"
|
||||
|
||||
if printf '%s' "$haystack" | grep -Eq -- "$pattern"; then
|
||||
pass "$description"
|
||||
else
|
||||
fail "$description"
|
||||
echo " expected to match: $pattern"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_matches() {
|
||||
local haystack="$1"
|
||||
local pattern="$2"
|
||||
local description="$3"
|
||||
|
||||
if printf '%s' "$haystack" | grep -Eq -- "$pattern"; then
|
||||
fail "$description"
|
||||
echo " did not expect to match: $pattern"
|
||||
else
|
||||
pass "$description"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_path_absent() {
|
||||
local path="$1"
|
||||
local description="$2"
|
||||
|
||||
if [[ ! -e "$path" ]]; then
|
||||
pass "$description"
|
||||
else
|
||||
fail "$description"
|
||||
echo " did not expect path to exist: $path"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_branch_absent() {
|
||||
local repo="$1"
|
||||
local pattern="$2"
|
||||
local description="$3"
|
||||
local branches
|
||||
|
||||
branches="$(git -C "$repo" branch --list "$pattern")"
|
||||
|
||||
if [[ -z "$branches" ]]; then
|
||||
pass "$description"
|
||||
else
|
||||
fail "$description"
|
||||
echo " did not expect matching branches:"
|
||||
echo "$branches" | sed 's/^/ /'
|
||||
fi
|
||||
}
|
||||
|
||||
assert_current_branch() {
|
||||
local repo="$1"
|
||||
local expected="$2"
|
||||
local description="$3"
|
||||
local actual
|
||||
|
||||
actual="$(git -C "$repo" branch --show-current)"
|
||||
assert_equals "$actual" "$expected" "$description"
|
||||
}
|
||||
|
||||
assert_file_equals() {
|
||||
local path="$1"
|
||||
local expected="$2"
|
||||
local description="$3"
|
||||
local actual
|
||||
|
||||
actual="$(cat "$path")"
|
||||
assert_equals "$actual" "$expected" "$description"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$TEST_ROOT" && -d "$TEST_ROOT" ]]; then
|
||||
rm -rf "$TEST_ROOT"
|
||||
fi
|
||||
}
|
||||
|
||||
configure_git_identity() {
|
||||
local repo="$1"
|
||||
|
||||
git -C "$repo" config user.name "Test Bot"
|
||||
git -C "$repo" config user.email "test@example.com"
|
||||
}
|
||||
|
||||
init_repo() {
|
||||
local repo="$1"
|
||||
|
||||
git init -q -b main "$repo"
|
||||
configure_git_identity "$repo"
|
||||
}
|
||||
|
||||
commit_fixture() {
|
||||
local repo="$1"
|
||||
local message="$2"
|
||||
|
||||
git -C "$repo" commit -q -m "$message"
|
||||
}
|
||||
|
||||
checkout_fixture_branch() {
|
||||
local repo="$1"
|
||||
local branch="$2"
|
||||
|
||||
git -C "$repo" checkout -q -b "$branch"
|
||||
}
|
||||
|
||||
write_upstream_fixture() {
|
||||
local repo="$1"
|
||||
local with_pure_ignored="${2:-1}"
|
||||
|
||||
mkdir -p \
|
||||
"$repo/.codex-plugin" \
|
||||
"$repo/.private-journal" \
|
||||
"$repo/assets" \
|
||||
"$repo/scripts" \
|
||||
"$repo/skills/example"
|
||||
|
||||
if [[ "$with_pure_ignored" == "1" ]]; then
|
||||
mkdir -p "$repo/ignored-cache/tmp"
|
||||
fi
|
||||
|
||||
cp "$SYNC_SCRIPT_SOURCE" "$repo/scripts/sync-to-codex-plugin.sh"
|
||||
|
||||
cat > "$repo/package.json" <<EOF
|
||||
{
|
||||
"name": "fixture-upstream",
|
||||
"version": "$PACKAGE_VERSION"
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$repo/.gitignore" <<'EOF'
|
||||
.private-journal/
|
||||
EOF
|
||||
|
||||
if [[ "$with_pure_ignored" == "1" ]]; then
|
||||
cat >> "$repo/.gitignore" <<'EOF'
|
||||
ignored-cache/
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat > "$repo/.codex-plugin/plugin.json" <<EOF
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "$MANIFEST_VERSION"
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$repo/assets/superpowers-small.svg" <<'EOF'
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1"></svg>
|
||||
EOF
|
||||
|
||||
printf 'png fixture\n' > "$repo/assets/app-icon.png"
|
||||
|
||||
cat > "$repo/skills/example/SKILL.md" <<'EOF'
|
||||
# Example Skill
|
||||
|
||||
Fixture content.
|
||||
EOF
|
||||
|
||||
printf 'tracked keep\n' > "$repo/.private-journal/keep.txt"
|
||||
printf 'ignored leak\n' > "$repo/.private-journal/leak.txt"
|
||||
if [[ "$with_pure_ignored" == "1" ]]; then
|
||||
printf 'ignored cache state\n' > "$repo/ignored-cache/tmp/state.json"
|
||||
fi
|
||||
|
||||
git -C "$repo" add \
|
||||
.codex-plugin/plugin.json \
|
||||
.gitignore \
|
||||
assets/app-icon.png \
|
||||
assets/superpowers-small.svg \
|
||||
package.json \
|
||||
scripts/sync-to-codex-plugin.sh \
|
||||
skills/example/SKILL.md
|
||||
git -C "$repo" add -f .private-journal/keep.txt
|
||||
|
||||
commit_fixture "$repo" "Initial upstream fixture"
|
||||
}
|
||||
|
||||
write_destination_fixture() {
|
||||
local repo="$1"
|
||||
|
||||
mkdir -p "$repo/plugins/superpowers/skills/example"
|
||||
printf 'fixture keep\n' > "$repo/plugins/superpowers/.fixture-keep"
|
||||
cat > "$repo/plugins/superpowers/skills/example/SKILL.md" <<'EOF'
|
||||
# Example Skill
|
||||
|
||||
Fixture content.
|
||||
EOF
|
||||
git -C "$repo" add plugins/superpowers/.fixture-keep
|
||||
git -C "$repo" add plugins/superpowers/skills/example/SKILL.md
|
||||
|
||||
commit_fixture "$repo" "Initial destination fixture"
|
||||
}
|
||||
|
||||
add_openai_agent_metadata_fixture() {
|
||||
local repo="$1"
|
||||
|
||||
mkdir -p "$repo/plugins/superpowers/skills/example/agents"
|
||||
|
||||
cat > "$repo/plugins/superpowers/skills/example/agents/openai.yaml" <<'EOF'
|
||||
interface:
|
||||
display_name: "Example"
|
||||
short_description: "Destination-owned OpenAI metadata"
|
||||
EOF
|
||||
|
||||
git -C "$repo" add plugins/superpowers/skills/example/agents/openai.yaml
|
||||
|
||||
commit_fixture "$repo" "Add OpenAI agent metadata fixture"
|
||||
}
|
||||
|
||||
dirty_tracked_destination_skill() {
|
||||
local repo="$1"
|
||||
|
||||
cat > "$repo/plugins/superpowers/skills/example/SKILL.md" <<'EOF'
|
||||
# Example Skill
|
||||
|
||||
Locally modified fixture content.
|
||||
EOF
|
||||
}
|
||||
|
||||
write_synced_destination_fixture() {
|
||||
local repo="$1"
|
||||
|
||||
mkdir -p \
|
||||
"$repo/plugins/superpowers/.codex-plugin" \
|
||||
"$repo/plugins/superpowers/.private-journal" \
|
||||
"$repo/plugins/superpowers/assets" \
|
||||
"$repo/plugins/superpowers/skills/example/agents" \
|
||||
"$repo/plugins/superpowers/skills/example"
|
||||
|
||||
cat > "$repo/plugins/superpowers/.codex-plugin/plugin.json" <<EOF
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "$MANIFEST_VERSION"
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$repo/plugins/superpowers/assets/superpowers-small.svg" <<'EOF'
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1"></svg>
|
||||
EOF
|
||||
|
||||
printf 'png fixture\n' > "$repo/plugins/superpowers/assets/app-icon.png"
|
||||
|
||||
cat > "$repo/plugins/superpowers/skills/example/SKILL.md" <<'EOF'
|
||||
# Example Skill
|
||||
|
||||
Fixture content.
|
||||
EOF
|
||||
|
||||
cat > "$repo/plugins/superpowers/skills/example/agents/openai.yaml" <<'EOF'
|
||||
interface:
|
||||
display_name: "Example"
|
||||
short_description: "Destination-owned OpenAI metadata"
|
||||
EOF
|
||||
|
||||
printf 'tracked keep\n' > "$repo/plugins/superpowers/.private-journal/keep.txt"
|
||||
|
||||
git -C "$repo" add \
|
||||
plugins/superpowers/.codex-plugin/plugin.json \
|
||||
plugins/superpowers/assets/app-icon.png \
|
||||
plugins/superpowers/assets/superpowers-small.svg \
|
||||
plugins/superpowers/skills/example/agents/openai.yaml \
|
||||
plugins/superpowers/skills/example/SKILL.md \
|
||||
plugins/superpowers/.private-journal/keep.txt
|
||||
|
||||
commit_fixture "$repo" "Initial synced destination fixture"
|
||||
}
|
||||
|
||||
write_stale_ignored_destination_fixture() {
|
||||
local repo="$1"
|
||||
|
||||
mkdir -p "$repo/plugins/superpowers/.private-journal"
|
||||
printf 'fixture keep\n' > "$repo/plugins/superpowers/.fixture-keep"
|
||||
printf 'stale ignored leak\n' > "$repo/plugins/superpowers/.private-journal/leak.txt"
|
||||
git -C "$repo" add plugins/superpowers/.fixture-keep
|
||||
|
||||
commit_fixture "$repo" "Initial stale ignored destination fixture"
|
||||
}
|
||||
|
||||
write_fake_gh() {
|
||||
local bin_dir="$1"
|
||||
|
||||
mkdir -p "$bin_dir"
|
||||
|
||||
cat > "$bin_dir/gh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${1:-}" == "auth" && "${2:-}" == "status" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "unexpected gh invocation: $*" >&2
|
||||
exit 1
|
||||
EOF
|
||||
|
||||
chmod +x "$bin_dir/gh"
|
||||
}
|
||||
|
||||
run_preview() {
|
||||
local upstream="$1"
|
||||
local dest="$2"
|
||||
local fake_bin="$3"
|
||||
|
||||
PATH="$fake_bin:$PATH" "$BASH_UNDER_TEST" "$upstream/scripts/sync-to-codex-plugin.sh" -n --local "$dest" 2>&1
|
||||
}
|
||||
|
||||
run_bootstrap_preview() {
|
||||
local upstream="$1"
|
||||
local dest="$2"
|
||||
local fake_bin="$3"
|
||||
|
||||
PATH="$fake_bin:$PATH" "$BASH_UNDER_TEST" "$upstream/scripts/sync-to-codex-plugin.sh" -n --bootstrap --local "$dest" 2>&1
|
||||
}
|
||||
|
||||
run_preview_without_manifest() {
|
||||
local upstream="$1"
|
||||
local dest="$2"
|
||||
local fake_bin="$3"
|
||||
|
||||
rm -f "$upstream/.codex-plugin/plugin.json"
|
||||
PATH="$fake_bin:$PATH" "$BASH_UNDER_TEST" "$upstream/scripts/sync-to-codex-plugin.sh" -n --local "$dest" 2>&1
|
||||
}
|
||||
|
||||
run_preview_with_stale_ignored_destination() {
|
||||
local upstream="$1"
|
||||
local dest="$2"
|
||||
local fake_bin="$3"
|
||||
|
||||
PATH="$fake_bin:$PATH" "$BASH_UNDER_TEST" "$upstream/scripts/sync-to-codex-plugin.sh" -n --local "$dest" 2>&1
|
||||
}
|
||||
|
||||
run_apply() {
|
||||
local upstream="$1"
|
||||
local dest="$2"
|
||||
local fake_bin="$3"
|
||||
|
||||
PATH="$fake_bin:$PATH" "$BASH_UNDER_TEST" "$upstream/scripts/sync-to-codex-plugin.sh" -y --local "$dest" 2>&1
|
||||
}
|
||||
|
||||
run_help() {
|
||||
local upstream="$1"
|
||||
local fake_bin="$2"
|
||||
|
||||
PATH="$fake_bin:$PATH" "$BASH_UNDER_TEST" "$upstream/scripts/sync-to-codex-plugin.sh" --help 2>&1
|
||||
}
|
||||
|
||||
write_bootstrap_destination_fixture() {
|
||||
local repo="$1"
|
||||
|
||||
printf 'bootstrap fixture\n' > "$repo/README.md"
|
||||
git -C "$repo" add README.md
|
||||
|
||||
commit_fixture "$repo" "Initial bootstrap destination fixture"
|
||||
}
|
||||
|
||||
main() {
|
||||
local upstream
|
||||
local mixed_only_upstream
|
||||
local dest
|
||||
local dest_branch
|
||||
local mixed_only_dest
|
||||
local stale_dest
|
||||
local dirty_apply_dest
|
||||
local dirty_apply_dest_branch
|
||||
local noop_apply_dest
|
||||
local noop_apply_dest_branch
|
||||
local fake_bin
|
||||
local bootstrap_dest
|
||||
local bootstrap_dest_branch
|
||||
local preview_status
|
||||
local preview_output
|
||||
local preview_section
|
||||
local bootstrap_status
|
||||
local bootstrap_output
|
||||
local missing_manifest_status
|
||||
local missing_manifest_output
|
||||
local mixed_only_status
|
||||
local mixed_only_output
|
||||
local stale_preview_status
|
||||
local stale_preview_output
|
||||
local stale_preview_section
|
||||
local dirty_apply_status
|
||||
local dirty_apply_output
|
||||
local noop_apply_status
|
||||
local noop_apply_output
|
||||
local help_output
|
||||
local script_source
|
||||
local dirty_skill_path
|
||||
local noop_openai_metadata_path
|
||||
|
||||
echo "=== Test: sync-to-codex-plugin dry-run regression ==="
|
||||
|
||||
TEST_ROOT="$(mktemp -d)"
|
||||
trap cleanup EXIT
|
||||
|
||||
upstream="$TEST_ROOT/upstream"
|
||||
mixed_only_upstream="$TEST_ROOT/mixed-only-upstream"
|
||||
dest="$TEST_ROOT/destination"
|
||||
mixed_only_dest="$TEST_ROOT/mixed-only-destination"
|
||||
stale_dest="$TEST_ROOT/stale-destination"
|
||||
dirty_apply_dest="$TEST_ROOT/dirty-apply-destination"
|
||||
dirty_apply_dest_branch="fixture/dirty-apply-target"
|
||||
noop_apply_dest="$TEST_ROOT/noop-apply-destination"
|
||||
noop_apply_dest_branch="fixture/noop-apply-target"
|
||||
bootstrap_dest="$TEST_ROOT/bootstrap-destination"
|
||||
dest_branch="fixture/preview-target"
|
||||
bootstrap_dest_branch="fixture/bootstrap-preview-target"
|
||||
fake_bin="$TEST_ROOT/bin"
|
||||
|
||||
init_repo "$upstream"
|
||||
write_upstream_fixture "$upstream"
|
||||
|
||||
init_repo "$mixed_only_upstream"
|
||||
write_upstream_fixture "$mixed_only_upstream" 0
|
||||
|
||||
init_repo "$dest"
|
||||
write_destination_fixture "$dest"
|
||||
add_openai_agent_metadata_fixture "$dest"
|
||||
checkout_fixture_branch "$dest" "$dest_branch"
|
||||
dirty_tracked_destination_skill "$dest"
|
||||
|
||||
init_repo "$mixed_only_dest"
|
||||
write_destination_fixture "$mixed_only_dest"
|
||||
|
||||
init_repo "$stale_dest"
|
||||
write_stale_ignored_destination_fixture "$stale_dest"
|
||||
|
||||
init_repo "$dirty_apply_dest"
|
||||
write_synced_destination_fixture "$dirty_apply_dest"
|
||||
checkout_fixture_branch "$dirty_apply_dest" "$dirty_apply_dest_branch"
|
||||
dirty_tracked_destination_skill "$dirty_apply_dest"
|
||||
|
||||
init_repo "$noop_apply_dest"
|
||||
write_synced_destination_fixture "$noop_apply_dest"
|
||||
checkout_fixture_branch "$noop_apply_dest" "$noop_apply_dest_branch"
|
||||
|
||||
init_repo "$bootstrap_dest"
|
||||
write_bootstrap_destination_fixture "$bootstrap_dest"
|
||||
checkout_fixture_branch "$bootstrap_dest" "$bootstrap_dest_branch"
|
||||
|
||||
write_fake_gh "$fake_bin"
|
||||
|
||||
# This regression test is about dry-run content, so capture the preview
|
||||
# output even if the current script exits nonzero in --local mode.
|
||||
set +e
|
||||
preview_output="$(run_preview "$upstream" "$dest" "$fake_bin")"
|
||||
preview_status=$?
|
||||
bootstrap_output="$(run_bootstrap_preview "$upstream" "$bootstrap_dest" "$fake_bin")"
|
||||
bootstrap_status=$?
|
||||
mixed_only_output="$(run_preview "$mixed_only_upstream" "$mixed_only_dest" "$fake_bin")"
|
||||
mixed_only_status=$?
|
||||
stale_preview_output="$(run_preview_with_stale_ignored_destination "$upstream" "$stale_dest" "$fake_bin")"
|
||||
stale_preview_status=$?
|
||||
dirty_apply_output="$(run_apply "$upstream" "$dirty_apply_dest" "$fake_bin")"
|
||||
dirty_apply_status=$?
|
||||
noop_apply_output="$(run_apply "$upstream" "$noop_apply_dest" "$fake_bin")"
|
||||
noop_apply_status=$?
|
||||
missing_manifest_output="$(run_preview_without_manifest "$upstream" "$dest" "$fake_bin")"
|
||||
missing_manifest_status=$?
|
||||
set -e
|
||||
help_output="$(run_help "$upstream" "$fake_bin")"
|
||||
script_source="$(cat "$upstream/scripts/sync-to-codex-plugin.sh")"
|
||||
preview_section="$(printf '%s\n' "$preview_output" | sed -n '/^=== Preview (rsync --dry-run) ===$/,/^=== End preview ===$/p')"
|
||||
stale_preview_section="$(printf '%s\n' "$stale_preview_output" | sed -n '/^=== Preview (rsync --dry-run) ===$/,/^=== End preview ===$/p')"
|
||||
dirty_skill_path="$dirty_apply_dest/plugins/superpowers/skills/example/SKILL.md"
|
||||
noop_openai_metadata_path="$noop_apply_dest/plugins/superpowers/skills/example/agents/openai.yaml"
|
||||
|
||||
echo ""
|
||||
echo "Preview assertions..."
|
||||
assert_equals "$preview_status" "0" "Preview exits successfully"
|
||||
assert_contains "$preview_output" "Version: $MANIFEST_VERSION" "Preview uses manifest version"
|
||||
assert_not_contains "$preview_output" "Version: $PACKAGE_VERSION" "Preview does not use package.json version"
|
||||
assert_contains "$preview_section" ".codex-plugin/plugin.json" "Preview includes manifest path"
|
||||
assert_contains "$preview_section" "assets/superpowers-small.svg" "Preview includes SVG asset"
|
||||
assert_contains "$preview_section" "assets/app-icon.png" "Preview includes PNG asset"
|
||||
assert_contains "$preview_section" ".private-journal/keep.txt" "Preview includes tracked ignored file"
|
||||
assert_not_contains "$preview_section" ".private-journal/leak.txt" "Preview excludes ignored untracked file"
|
||||
assert_not_contains "$preview_section" "ignored-cache/" "Preview excludes pure ignored directories"
|
||||
assert_not_contains "$preview_output" "Overlay file (.codex-plugin/plugin.json) will be regenerated" "Preview omits overlay regeneration note"
|
||||
assert_not_contains "$preview_output" "Assets (superpowers-small.svg, app-icon.png) will be seeded from" "Preview omits assets seeding note"
|
||||
assert_contains "$preview_section" "skills/example/SKILL.md" "Preview reflects dirty tracked destination file"
|
||||
assert_not_matches "$preview_section" "\\*deleting +skills/example/agents/openai\\.yaml" "Preview preserves destination-owned OpenAI agent metadata"
|
||||
assert_current_branch "$dest" "$dest_branch" "Preview leaves destination checkout on its original branch"
|
||||
assert_branch_absent "$dest" "sync/superpowers-*" "Preview does not create sync branch in destination checkout"
|
||||
|
||||
echo ""
|
||||
echo "Mixed-directory assertions..."
|
||||
assert_equals "$mixed_only_status" "0" "Mixed ignored directory preview exits successfully under /bin/bash"
|
||||
assert_contains "$mixed_only_output" ".private-journal/keep.txt" "Mixed ignored directory preview still includes tracked ignored file"
|
||||
assert_not_contains "$mixed_only_output" "ignored-cache/" "Mixed ignored directory preview has no pure ignored directory fixture"
|
||||
|
||||
echo ""
|
||||
echo "Convergence assertions..."
|
||||
assert_equals "$stale_preview_status" "0" "Stale ignored destination preview exits successfully"
|
||||
assert_matches "$stale_preview_section" "\\*deleting +\\.private-journal/leak\\.txt" "Preview deletes stale ignored destination file"
|
||||
|
||||
echo ""
|
||||
echo "Bootstrap assertions..."
|
||||
assert_equals "$bootstrap_status" "0" "Bootstrap preview exits successfully"
|
||||
assert_contains "$bootstrap_output" "Mode: BOOTSTRAP (creating plugins/superpowers/ when absent)" "Bootstrap preview describes directory creation"
|
||||
assert_not_contains "$bootstrap_output" "Assets:" "Bootstrap preview omits external assets path"
|
||||
assert_contains "$bootstrap_output" "Dry run only. Nothing was changed or pushed." "Bootstrap preview remains dry-run only"
|
||||
assert_path_absent "$bootstrap_dest/plugins/superpowers" "Bootstrap preview does not create destination plugin directory"
|
||||
assert_current_branch "$bootstrap_dest" "$bootstrap_dest_branch" "Bootstrap preview leaves destination checkout on its original branch"
|
||||
assert_branch_absent "$bootstrap_dest" "bootstrap/superpowers-*" "Bootstrap preview does not create bootstrap branch in destination checkout"
|
||||
|
||||
echo ""
|
||||
echo "Apply assertions..."
|
||||
assert_equals "$dirty_apply_status" "1" "Dirty local apply exits with failure"
|
||||
assert_contains "$dirty_apply_output" "ERROR: local checkout has uncommitted changes under 'plugins/superpowers'" "Dirty local apply reports protected destination path"
|
||||
assert_current_branch "$dirty_apply_dest" "$dirty_apply_dest_branch" "Dirty local apply leaves destination checkout on its original branch"
|
||||
assert_branch_absent "$dirty_apply_dest" "sync/superpowers-*" "Dirty local apply does not create sync branch in destination checkout"
|
||||
assert_file_equals "$dirty_skill_path" "# Example Skill
|
||||
|
||||
Locally modified fixture content." "Dirty local apply preserves tracked working-tree file content"
|
||||
assert_equals "$noop_apply_status" "0" "Clean no-op local apply exits successfully"
|
||||
assert_contains "$noop_apply_output" "No changes — embedded plugin was already in sync with upstream" "Clean no-op local apply reports no changes"
|
||||
assert_current_branch "$noop_apply_dest" "$noop_apply_dest_branch" "Clean no-op local apply leaves destination checkout on its original branch"
|
||||
assert_branch_absent "$noop_apply_dest" "sync/superpowers-*" "Clean no-op local apply does not create sync branch in destination checkout"
|
||||
assert_file_equals "$noop_openai_metadata_path" "interface:
|
||||
display_name: \"Example\"
|
||||
short_description: \"Destination-owned OpenAI metadata\"" "Clean no-op local apply preserves OpenAI agent metadata"
|
||||
|
||||
echo ""
|
||||
echo "Missing manifest assertions..."
|
||||
assert_equals "$missing_manifest_status" "1" "Missing manifest exits with failure"
|
||||
assert_contains "$missing_manifest_output" "ERROR: committed Codex manifest missing at" "Missing manifest reports committed manifest path"
|
||||
|
||||
echo ""
|
||||
echo "Help assertions..."
|
||||
assert_not_contains "$help_output" "--assets-src" "Help omits --assets-src"
|
||||
|
||||
echo ""
|
||||
echo "Source assertions..."
|
||||
assert_not_contains "$script_source" "regenerated inline" "Source drops regenerated inline phrasing"
|
||||
assert_not_contains "$script_source" "Brand Assets directory" "Source drops Brand Assets directory phrasing"
|
||||
assert_not_contains "$script_source" "--assets-src" "Source drops --assets-src"
|
||||
|
||||
if [[ $FAILURES -ne 0 ]]; then
|
||||
echo ""
|
||||
echo "FAILED: $FAILURES assertion(s) failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "PASS"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,3 @@
|
||||
The plan is done. docs/superpowers/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/superpowers/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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user