📦 deps(skills): sync superpowers
This commit is contained in:
parent
2f2d34abe2
commit
5b9c1e36be
|
|
@ -27,7 +27,7 @@ You MUST create a task for each of these items and complete them in order:
|
||||||
4. **Propose 2-3 approaches** — with trade-offs and your recommendation
|
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
|
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
|
6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit
|
||||||
7. **Spec review loop** — dispatch spec-document-reviewer subagent with precisely crafted review context (never your session history); fix issues and re-dispatch until approved (max 3 iterations, then surface to human)
|
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
|
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
|
9. **Transition to implementation** — invoke writing-plans skill to create implementation plan
|
||||||
|
|
||||||
|
|
@ -43,8 +43,7 @@ digraph brainstorming {
|
||||||
"Present design sections" [shape=box];
|
"Present design sections" [shape=box];
|
||||||
"User approves design?" [shape=diamond];
|
"User approves design?" [shape=diamond];
|
||||||
"Write design doc" [shape=box];
|
"Write design doc" [shape=box];
|
||||||
"Spec review loop" [shape=box];
|
"Spec self-review\n(fix inline)" [shape=box];
|
||||||
"Spec review passed?" [shape=diamond];
|
|
||||||
"User reviews spec?" [shape=diamond];
|
"User reviews spec?" [shape=diamond];
|
||||||
"Invoke writing-plans skill" [shape=doublecircle];
|
"Invoke writing-plans skill" [shape=doublecircle];
|
||||||
|
|
||||||
|
|
@ -57,10 +56,8 @@ digraph brainstorming {
|
||||||
"Present design sections" -> "User approves design?";
|
"Present design sections" -> "User approves design?";
|
||||||
"User approves design?" -> "Present design sections" [label="no, revise"];
|
"User approves design?" -> "Present design sections" [label="no, revise"];
|
||||||
"User approves design?" -> "Write design doc" [label="yes"];
|
"User approves design?" -> "Write design doc" [label="yes"];
|
||||||
"Write design doc" -> "Spec review loop";
|
"Write design doc" -> "Spec self-review\n(fix inline)";
|
||||||
"Spec review loop" -> "Spec review passed?";
|
"Spec self-review\n(fix inline)" -> "User reviews spec?";
|
||||||
"Spec review passed?" -> "Spec review loop" [label="issues found,\nfix and re-dispatch"];
|
|
||||||
"Spec review passed?" -> "User reviews spec?" [label="approved"];
|
|
||||||
"User reviews spec?" -> "Write design doc" [label="changes requested"];
|
"User reviews spec?" -> "Write design doc" [label="changes requested"];
|
||||||
"User reviews spec?" -> "Invoke writing-plans skill" [label="approved"];
|
"User reviews spec?" -> "Invoke writing-plans skill" [label="approved"];
|
||||||
}
|
}
|
||||||
|
|
@ -116,12 +113,15 @@ digraph brainstorming {
|
||||||
- Use elements-of-style:writing-clearly-and-concisely skill if available
|
- Use elements-of-style:writing-clearly-and-concisely skill if available
|
||||||
- Commit the design document to git
|
- Commit the design document to git
|
||||||
|
|
||||||
**Spec Review Loop:**
|
**Spec Self-Review:**
|
||||||
After writing the spec document:
|
After writing the spec document, look at it with fresh eyes:
|
||||||
|
|
||||||
1. Dispatch spec-document-reviewer subagent (see spec-document-reviewer-prompt.md)
|
1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them.
|
||||||
2. If Issues Found: fix, re-dispatch, repeat until Approved
|
2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions?
|
||||||
3. If loop exceeds 3 iterations, surface to human for guidance
|
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:**
|
**User Review Gate:**
|
||||||
After the spec review loop passes, ask the user to review the written spec before proceeding:
|
After the spec review loop passes, ask the user to review the written spec before proceeding:
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,10 @@ function decodeFrame(buffer) {
|
||||||
const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383));
|
const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383));
|
||||||
const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1';
|
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 URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST);
|
||||||
const SCREEN_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
|
const SESSION_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
|
||||||
const OWNER_PID = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null;
|
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 = {
|
const MIME_TYPES = {
|
||||||
'.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
|
'.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
|
||||||
|
|
@ -112,10 +114,10 @@ function wrapInFrame(content) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNewestScreen() {
|
function getNewestScreen() {
|
||||||
const files = fs.readdirSync(SCREEN_DIR)
|
const files = fs.readdirSync(CONTENT_DIR)
|
||||||
.filter(f => f.endsWith('.html'))
|
.filter(f => f.endsWith('.html'))
|
||||||
.map(f => {
|
.map(f => {
|
||||||
const fp = path.join(SCREEN_DIR, f);
|
const fp = path.join(CONTENT_DIR, f);
|
||||||
return { path: fp, mtime: fs.statSync(fp).mtime.getTime() };
|
return { path: fp, mtime: fs.statSync(fp).mtime.getTime() };
|
||||||
})
|
})
|
||||||
.sort((a, b) => b.mtime - a.mtime);
|
.sort((a, b) => b.mtime - a.mtime);
|
||||||
|
|
@ -142,7 +144,7 @@ function handleRequest(req, res) {
|
||||||
res.end(html);
|
res.end(html);
|
||||||
} else if (req.method === 'GET' && req.url.startsWith('/files/')) {
|
} else if (req.method === 'GET' && req.url.startsWith('/files/')) {
|
||||||
const fileName = req.url.slice(7);
|
const fileName = req.url.slice(7);
|
||||||
const filePath = path.join(SCREEN_DIR, path.basename(fileName));
|
const filePath = path.join(CONTENT_DIR, path.basename(fileName));
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
res.writeHead(404);
|
res.writeHead(404);
|
||||||
res.end('Not found');
|
res.end('Not found');
|
||||||
|
|
@ -230,7 +232,7 @@ function handleMessage(text) {
|
||||||
touchActivity();
|
touchActivity();
|
||||||
console.log(JSON.stringify({ source: 'user-event', ...event }));
|
console.log(JSON.stringify({ source: 'user-event', ...event }));
|
||||||
if (event.choice) {
|
if (event.choice) {
|
||||||
const eventsFile = path.join(SCREEN_DIR, '.events');
|
const eventsFile = path.join(STATE_DIR, 'events');
|
||||||
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
|
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -258,32 +260,33 @@ const debounceTimers = new Map();
|
||||||
// ========== Server Startup ==========
|
// ========== Server Startup ==========
|
||||||
|
|
||||||
function startServer() {
|
function startServer() {
|
||||||
if (!fs.existsSync(SCREEN_DIR)) fs.mkdirSync(SCREEN_DIR, { recursive: true });
|
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.
|
// Track known files to distinguish new screens from updates.
|
||||||
// macOS fs.watch reports 'rename' for both new files and overwrites,
|
// macOS fs.watch reports 'rename' for both new files and overwrites,
|
||||||
// so we can't rely on eventType alone.
|
// so we can't rely on eventType alone.
|
||||||
const knownFiles = new Set(
|
const knownFiles = new Set(
|
||||||
fs.readdirSync(SCREEN_DIR).filter(f => f.endsWith('.html'))
|
fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html'))
|
||||||
);
|
);
|
||||||
|
|
||||||
const server = http.createServer(handleRequest);
|
const server = http.createServer(handleRequest);
|
||||||
server.on('upgrade', handleUpgrade);
|
server.on('upgrade', handleUpgrade);
|
||||||
|
|
||||||
const watcher = fs.watch(SCREEN_DIR, (eventType, filename) => {
|
const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => {
|
||||||
if (!filename || !filename.endsWith('.html')) return;
|
if (!filename || !filename.endsWith('.html')) return;
|
||||||
|
|
||||||
if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename));
|
if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename));
|
||||||
debounceTimers.set(filename, setTimeout(() => {
|
debounceTimers.set(filename, setTimeout(() => {
|
||||||
debounceTimers.delete(filename);
|
debounceTimers.delete(filename);
|
||||||
const filePath = path.join(SCREEN_DIR, filename);
|
const filePath = path.join(CONTENT_DIR, filename);
|
||||||
|
|
||||||
if (!fs.existsSync(filePath)) return; // file was deleted
|
if (!fs.existsSync(filePath)) return; // file was deleted
|
||||||
touchActivity();
|
touchActivity();
|
||||||
|
|
||||||
if (!knownFiles.has(filename)) {
|
if (!knownFiles.has(filename)) {
|
||||||
knownFiles.add(filename);
|
knownFiles.add(filename);
|
||||||
const eventsFile = path.join(SCREEN_DIR, '.events');
|
const eventsFile = path.join(STATE_DIR, 'events');
|
||||||
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
|
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
|
||||||
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
|
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -297,10 +300,10 @@ function startServer() {
|
||||||
|
|
||||||
function shutdown(reason) {
|
function shutdown(reason) {
|
||||||
console.log(JSON.stringify({ type: 'server-stopped', reason }));
|
console.log(JSON.stringify({ type: 'server-stopped', reason }));
|
||||||
const infoFile = path.join(SCREEN_DIR, '.server-info');
|
const infoFile = path.join(STATE_DIR, 'server-info');
|
||||||
if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile);
|
if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile);
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(SCREEN_DIR, '.server-stopped'),
|
path.join(STATE_DIR, 'server-stopped'),
|
||||||
JSON.stringify({ reason, timestamp: Date.now() }) + '\n'
|
JSON.stringify({ reason, timestamp: Date.now() }) + '\n'
|
||||||
);
|
);
|
||||||
watcher.close();
|
watcher.close();
|
||||||
|
|
@ -309,8 +312,8 @@ function startServer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ownerAlive() {
|
function ownerAlive() {
|
||||||
if (!OWNER_PID) return true;
|
if (!ownerPid) return true;
|
||||||
try { process.kill(OWNER_PID, 0); return true; } catch (e) { return false; }
|
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
|
// Check every 60s: exit if owner process died or idle for 30 minutes
|
||||||
|
|
@ -320,14 +323,27 @@ function startServer() {
|
||||||
}, 60 * 1000);
|
}, 60 * 1000);
|
||||||
lifecycleCheck.unref();
|
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, () => {
|
server.listen(PORT, HOST, () => {
|
||||||
const info = JSON.stringify({
|
const info = JSON.stringify({
|
||||||
type: 'server-started', port: Number(PORT), host: HOST,
|
type: 'server-started', port: Number(PORT), host: HOST,
|
||||||
url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT,
|
url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT,
|
||||||
screen_dir: SCREEN_DIR
|
screen_dir: CONTENT_DIR, state_dir: STATE_DIR
|
||||||
});
|
});
|
||||||
console.log(info);
|
console.log(info);
|
||||||
fs.writeFileSync(path.join(SCREEN_DIR, '.server-info'), info + '\n');
|
fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info + '\n');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,16 +78,17 @@ fi
|
||||||
SESSION_ID="$$-$(date +%s)"
|
SESSION_ID="$$-$(date +%s)"
|
||||||
|
|
||||||
if [[ -n "$PROJECT_DIR" ]]; then
|
if [[ -n "$PROJECT_DIR" ]]; then
|
||||||
SCREEN_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
|
SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
|
||||||
else
|
else
|
||||||
SCREEN_DIR="/tmp/brainstorm-${SESSION_ID}"
|
SESSION_DIR="/tmp/brainstorm-${SESSION_ID}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PID_FILE="${SCREEN_DIR}/.server.pid"
|
STATE_DIR="${SESSION_DIR}/state"
|
||||||
LOG_FILE="${SCREEN_DIR}/.server.log"
|
PID_FILE="${STATE_DIR}/server.pid"
|
||||||
|
LOG_FILE="${STATE_DIR}/server.log"
|
||||||
|
|
||||||
# Create fresh session directory
|
# Create fresh session directory with content and state peers
|
||||||
mkdir -p "$SCREEN_DIR"
|
mkdir -p "${SESSION_DIR}/content" "$STATE_DIR"
|
||||||
|
|
||||||
# Kill any existing server
|
# Kill any existing server
|
||||||
if [[ -f "$PID_FILE" ]]; then
|
if [[ -f "$PID_FILE" ]]; then
|
||||||
|
|
@ -106,22 +107,16 @@ if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then
|
||||||
OWNER_PID="$PPID"
|
OWNER_PID="$PPID"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# On Windows/MSYS2, the MSYS2 PID namespace is invisible to Node.js.
|
|
||||||
# Skip owner-PID monitoring — the 30-minute idle timeout prevents orphans.
|
|
||||||
case "${OSTYPE:-}" in
|
|
||||||
msys*|cygwin*|mingw*) OWNER_PID="" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Foreground mode for environments that reap detached/background processes.
|
# Foreground mode for environments that reap detached/background processes.
|
||||||
if [[ "$FOREGROUND" == "true" ]]; then
|
if [[ "$FOREGROUND" == "true" ]]; then
|
||||||
echo "$$" > "$PID_FILE"
|
echo "$$" > "$PID_FILE"
|
||||||
env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs
|
env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start server, capturing output to log file
|
# Start server, capturing output to log file
|
||||||
# Use nohup to survive shell exit; disown to remove from job table
|
# Use nohup to survive shell exit; disown to remove from job table
|
||||||
nohup env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs > "$LOG_FILE" 2>&1 &
|
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=$!
|
SERVER_PID=$!
|
||||||
disown "$SERVER_PID" 2>/dev/null
|
disown "$SERVER_PID" 2>/dev/null
|
||||||
echo "$SERVER_PID" > "$PID_FILE"
|
echo "$SERVER_PID" > "$PID_FILE"
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Stop the brainstorm server and clean up
|
# Stop the brainstorm server and clean up
|
||||||
# Usage: stop-server.sh <screen_dir>
|
# Usage: stop-server.sh <session_dir>
|
||||||
#
|
#
|
||||||
# Kills the server process. Only deletes session directory if it's
|
# Kills the server process. Only deletes session directory if it's
|
||||||
# under /tmp (ephemeral). Persistent directories (.superpowers/) are
|
# under /tmp (ephemeral). Persistent directories (.superpowers/) are
|
||||||
# kept so mockups can be reviewed later.
|
# kept so mockups can be reviewed later.
|
||||||
|
|
||||||
SCREEN_DIR="$1"
|
SESSION_DIR="$1"
|
||||||
|
|
||||||
if [[ -z "$SCREEN_DIR" ]]; then
|
if [[ -z "$SESSION_DIR" ]]; then
|
||||||
echo '{"error": "Usage: stop-server.sh <screen_dir>"}'
|
echo '{"error": "Usage: stop-server.sh <session_dir>"}'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PID_FILE="${SCREEN_DIR}/.server.pid"
|
STATE_DIR="${SESSION_DIR}/state"
|
||||||
|
PID_FILE="${STATE_DIR}/server.pid"
|
||||||
|
|
||||||
if [[ -f "$PID_FILE" ]]; then
|
if [[ -f "$PID_FILE" ]]; then
|
||||||
pid=$(cat "$PID_FILE")
|
pid=$(cat "$PID_FILE")
|
||||||
|
|
@ -42,11 +43,11 @@ if [[ -f "$PID_FILE" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$PID_FILE" "${SCREEN_DIR}/.server.log"
|
rm -f "$PID_FILE" "${STATE_DIR}/server.log"
|
||||||
|
|
||||||
# Only delete ephemeral /tmp directories
|
# Only delete ephemeral /tmp directories
|
||||||
if [[ "$SCREEN_DIR" == /tmp/* ]]; then
|
if [[ "$SESSION_DIR" == /tmp/* ]]; then
|
||||||
rm -rf "$SCREEN_DIR"
|
rm -rf "$SESSION_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo '{"status": "stopped"}'
|
echo '{"status": "stopped"}'
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ A question *about* a UI topic is not automatically a visual question. "What kind
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
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.
|
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.
|
**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.
|
||||||
|
|
||||||
|
|
@ -37,12 +37,13 @@ The server watches a directory for HTML files and serves the newest one to the b
|
||||||
scripts/start-server.sh --project-dir /path/to/project
|
scripts/start-server.sh --project-dir /path/to/project
|
||||||
|
|
||||||
# Returns: {"type":"server-started","port":52341,"url":"http://localhost:52341",
|
# Returns: {"type":"server-started","port":52341,"url":"http://localhost:52341",
|
||||||
# "screen_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000"}
|
# "screen_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000/content",
|
||||||
|
# "state_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000/state"}
|
||||||
```
|
```
|
||||||
|
|
||||||
Save `screen_dir` from the response. Tell user to open the URL.
|
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 `$SCREEN_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.
|
**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.
|
**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.
|
||||||
|
|
||||||
|
|
@ -61,7 +62,7 @@ scripts/start-server.sh --project-dir /path/to/project
|
||||||
# across conversation turns.
|
# across conversation turns.
|
||||||
scripts/start-server.sh --project-dir /path/to/project
|
scripts/start-server.sh --project-dir /path/to/project
|
||||||
```
|
```
|
||||||
When calling this via the Bash tool, set `run_in_background: true`. Then read `$SCREEN_DIR/.server-info` on the next turn to get the URL and port.
|
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:**
|
**Codex:**
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -93,7 +94,7 @@ Use `--url-host` to control what hostname is printed in the returned URL JSON.
|
||||||
## The Loop
|
## The Loop
|
||||||
|
|
||||||
1. **Check server is alive**, then **write HTML** to a new file in `screen_dir`:
|
1. **Check server is alive**, then **write HTML** to a new file in `screen_dir`:
|
||||||
- Before each write, check that `$SCREEN_DIR/.server-info` exists. If it doesn't (or `.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.
|
- 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`
|
- Use semantic filenames: `platform.html`, `visual-style.html`, `layout.html`
|
||||||
- **Never reuse filenames** — each screen gets a fresh file
|
- **Never reuse filenames** — each screen gets a fresh file
|
||||||
- Use Write tool — **never use cat/heredoc** (dumps noise into terminal)
|
- Use Write tool — **never use cat/heredoc** (dumps noise into terminal)
|
||||||
|
|
@ -105,9 +106,9 @@ Use `--url-host` to control what hostname is printed in the returned URL JSON.
|
||||||
- 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."
|
- 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:
|
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
|
- 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
|
- Merge with the user's terminal text to get the full picture
|
||||||
- The terminal message is the primary feedback; `.events` provides structured interaction data
|
- 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.
|
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.
|
||||||
|
|
||||||
|
|
@ -244,7 +245,7 @@ The frame template provides these CSS classes for your content:
|
||||||
|
|
||||||
## Browser Events Format
|
## 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.
|
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
|
```jsonl
|
||||||
{"type":"click","choice":"a","text":"Option A - Simple Layout","timestamp":1706000101}
|
{"type":"click","choice":"a","text":"Option A - Simple Layout","timestamp":1706000101}
|
||||||
|
|
@ -254,7 +255,7 @@ When the user clicks options in the browser, their interactions are recorded to
|
||||||
|
|
||||||
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.
|
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.
|
If `$STATE_DIR/events` doesn't exist, the user didn't interact with the browser — use only their terminal text.
|
||||||
|
|
||||||
## Design Tips
|
## Design Tips
|
||||||
|
|
||||||
|
|
@ -275,7 +276,7 @@ If `.events` doesn't exist, the user didn't interact with the browser — use on
|
||||||
## Cleaning Up
|
## Cleaning Up
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/stop-server.sh $SCREEN_DIR
|
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.
|
If the session used `--project-dir`, mockup files persist in `.superpowers/brainstorm/` for later reference. Only `/tmp` sessions get deleted on stop.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Skills use Claude Code tool names. When you encounter these in a skill, use your
|
||||||
|
|
||||||
| Skill references | Codex equivalent |
|
| Skill references | Codex equivalent |
|
||||||
|-----------------|------------------|
|
|-----------------|------------------|
|
||||||
| `Task` tool (dispatch subagent) | `spawn_agent` |
|
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
|
||||||
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
|
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
|
||||||
| Task returns result | `wait` |
|
| Task returns result | `wait` |
|
||||||
| Task completes automatically | `close_agent` to free slot |
|
| Task completes automatically | `close_agent` to free slot |
|
||||||
|
|
@ -23,3 +23,78 @@ multi_agent = true
|
||||||
```
|
```
|
||||||
|
|
||||||
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
|
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
|
||||||
|
|
@ -103,26 +103,33 @@ 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
|
## Remember
|
||||||
- Exact file paths always
|
- 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
|
- Exact commands with expected output
|
||||||
- Reference relevant skills with @ syntax
|
|
||||||
- DRY, YAGNI, TDD, frequent commits
|
- DRY, YAGNI, TDD, frequent commits
|
||||||
|
|
||||||
## Plan Review Loop
|
## Self-Review
|
||||||
|
|
||||||
After writing the complete plan:
|
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. Dispatch a single plan-document-reviewer subagent (see plan-document-reviewer-prompt.md) with precisely crafted review context — never your session history. This keeps the reviewer focused on the plan, not your thought process.
|
**1. Spec coverage:** Skim each section/requirement in the spec. Can you point to a task that implements it? List any gaps.
|
||||||
- Provide: path to the plan document, path to spec document
|
|
||||||
2. If ❌ Issues Found: fix the issues, re-dispatch reviewer for the whole plan
|
|
||||||
3. If ✅ Approved: proceed to execution handoff
|
|
||||||
|
|
||||||
**Review loop guidance:**
|
**2. Placeholder scan:** Search your plan for red flags — any of the patterns from the "No Placeholders" section above. Fix them.
|
||||||
- Same agent that wrote the plan fixes it (preserves context)
|
|
||||||
- If loop exceeds 3 iterations, surface to human for guidance
|
**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.
|
||||||
- Reviewers are advisory — explain disagreements if you believe feedback is incorrect
|
|
||||||
|
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
|
## Execution Handoff
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ skills/
|
||||||
## SKILL.md Structure
|
## SKILL.md Structure
|
||||||
|
|
||||||
**Frontmatter (YAML):**
|
**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
|
- Max 1024 characters total
|
||||||
- `name`: Use letters, numbers, and hyphens only (no parentheses, special chars)
|
- `name`: Use letters, numbers, and hyphens only (no parentheses, special chars)
|
||||||
- `description`: Third-person, describes ONLY when to use (NOT what it does)
|
- `description`: Third-person, describes ONLY when to use (NOT what it does)
|
||||||
|
|
@ -604,7 +604,7 @@ Deploying untested skills = deploying untested code. It's a violation of quality
|
||||||
|
|
||||||
**GREEN Phase - Write Minimal Skill:**
|
**GREEN Phase - Write Minimal Skill:**
|
||||||
- [ ] Name uses only letters, numbers, hyphens (no parentheses/special chars)
|
- [ ] 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 starts with "Use when..." and includes specific triggers/symptoms
|
||||||
- [ ] Description written in third person
|
- [ ] Description written in third person
|
||||||
- [ ] Keywords throughout for search (errors, symptoms, tools)
|
- [ ] 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
|
## Skill structure
|
||||||
|
|
||||||
<Note>
|
<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)
|
* `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)
|
* `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
|
### 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
|
### Token budgets
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue