Compare commits

...

6 Commits

Author SHA1 Message Date
Jesse Vincent
a78fa0f4d8 Replace subagent review loops with lightweight inline self-review
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 (v3.6.0 through v5.0.4) with
5 trials each showed identical plan sizes, task counts, and quality scores
regardless of whether the review loop ran.

Changes:
- writing-plans: Replace subagent Plan Review Loop with inline Self-Review
  checklist (spec coverage, placeholder scan, type consistency)
- writing-plans: Add explicit "No Placeholders" section listing plan failures
  (TBD, vague descriptions, undefined references, "similar to Task N")
- brainstorming: Replace subagent Spec Review Loop with inline Spec Self-Review
  (placeholder scan, internal consistency, scope check, ambiguity check)
- Both skills now use "look at it with fresh eyes" framing

Testing: 5 trials with the new skill show self-review catches 3-5 real bugs
per run (spawn positions, API mismatches, seed bugs, grid indexing) in ~30s
instead of ~25 min. Remaining defects are comparable to the subagent approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 13:28:56 -07:00
Jesse Vincent
8a0a5ca6a3 Release v5.0.5: brainstorm server ESM fix, Windows PID fix, stop-server reliability 2026-03-17 15:01:57 -07:00
Jesse Vincent
2d46da1b37 Credit @lucasyhzhu-debug for Windows brainstorm docs (PR #768) 2026-03-17 14:51:02 -07:00
Jesse Vincent
0002948041 Update RELEASE-NOTES.md with brainstorm server ESM fix 2026-03-17 14:35:03 -07:00
sarbojitrana
3128a2c3cd fix : resolve ESM/CommonJS module confict in brainstorming server 2026-03-17 14:34:16 -07:00
jesse
f34ee479b7 fix: Windows brainstorm server lifecycle, restore execution choice
- Skip OWNER_PID monitoring on Windows/MSYS2 where the PID namespace is
  invisible to Node.js, preventing server self-termination after 60s (#770)
- Document run_in_background: true for Claude Code on Windows (#767)
- Restore user choice between subagent-driven and inline execution after
  plan writing; subagent-driven is recommended but no longer mandatory
- Add Windows lifecycle test script verified on Windows 11 VM
- Note #723 (stop-server.sh reliability) as already fixed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 04:09:36 +00:00
12 changed files with 434 additions and 61 deletions

View File

@@ -9,7 +9,7 @@
{
"name": "superpowers",
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
"version": "5.0.4",
"version": "5.0.5",
"source": "./",
"author": {
"name": "Jesse Vincent",

View File

@@ -1,7 +1,7 @@
{
"name": "superpowers",
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
"version": "5.0.4",
"version": "5.0.5",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com"

View File

@@ -1,20 +1,13 @@
# Changelog
## Unreleased
## [5.0.5] - 2026-03-17
### Fixed
- **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. Applies to all Windows shells (CMD, PowerShell, Git Bash) since they all route through Git Bash. ([#737](https://github.com/obra/superpowers/issues/737), based on [#740](https://github.com/obra/superpowers/pull/740))
- **Portable shebangs**: Replace `#!/bin/bash` with `#!/usr/bin/env bash` in all 13 shell scripts. Fixes execution on NixOS, FreeBSD, and macOS with Homebrew bash where `/bin/bash` is outdated or missing. ([#700](https://github.com/obra/superpowers/pull/700), dupes: [#747](https://github.com/obra/superpowers/pull/747))
- **POSIX-safe hook script**: Replace `${BASH_SOURCE[0]:-$0}` with `$0` in `hooks/session-start` and polyglot-hooks docs. Fixes 'Bad substitution' error on Ubuntu/Debian where `/bin/sh` is dash. ([#553](https://github.com/obra/superpowers/pull/553))
- **Bash 5.3+ hook hang**: Replace 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](https://github.com/obra/superpowers/pull/572), [#571](https://github.com/obra/superpowers/issues/571))
- **Cursor hooks support**: Add `hooks/hooks-cursor.json` with Cursor's camelCase format (`sessionStart`, `version: 1`) and update `.cursor-plugin/plugin.json` to reference it. Fix platform detection in `session-start` to check `CURSOR_PLUGIN_ROOT` first (Cursor may also set `CLAUDE_PLUGIN_ROOT`). (Based on [#709](https://github.com/obra/superpowers/pull/709))
- **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](https://github.com/obra/superpowers/pull/784) by @sarbojitrana, fixes [#774](https://github.com/obra/superpowers/issues/774), [#780](https://github.com/obra/superpowers/issues/780), [#783](https://github.com/obra/superpowers/issues/783))
- **Brainstorm owner-PID on Windows**: Skip `BRAINSTORM_OWNER_PID` lifecycle monitoring on Windows/MSYS2 where the PID namespace is invisible to Node.js. Prevents the server from self-terminating after 60 seconds. The 30-minute idle timeout remains as the safety net. ([#770](https://github.com/obra/superpowers/issues/770), docs from [PR #768](https://github.com/obra/superpowers/pull/768) by @lucasyhzhu-debug)
- **stop-server.sh reliability**: Verify the server process actually died before reporting success. Waits up to 2 seconds for graceful shutdown, escalates to `SIGKILL`, and reports failure if the process survives. ([#723](https://github.com/obra/superpowers/issues/723))
### Already fixed on dev (closed PRs)
### Changed
- **Windows hook quoting** ([#630](https://github.com/obra/superpowers/pull/630), [#529](https://github.com/obra/superpowers/issues/529)): `hooks.json` already uses escaped double quotes on dev.
- **Windows symlink path** ([#539](https://github.com/obra/superpowers/pull/539)): Closed — the PR introduced a bug (literal `~` in path alongside `$env:USERPROFILE`). Current docs are correct.
### Known Issues
- **`BRAINSTORM_OWNER_PID` on Windows (main branch only)**: The main branch's `server.js` uses `process.kill(OWNER_PID, 0)` for lifecycle checks, but receives MSYS2 PIDs which are invisible to Node.js (different PID namespace). This causes the server to self-terminate after 60 seconds. Fix: resolve `OWNER_PID` via `/proc/$PPID/winpid` to get the Windows-native PID. The dev branch's `index.js` does not have this issue since it has no OWNER_PID lifecycle check.
- **Execution handoff**: Restore user choice between subagent-driven-development and executing-plans after plan writing. Subagent-driven is recommended but no longer mandatory. (Reverts `5e51c3e`)

View File

@@ -1,5 +1,17 @@
# Superpowers Release Notes
## 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

View File

@@ -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
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 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
9. **Transition to implementation** — invoke writing-plans skill to create implementation plan
@@ -43,8 +43,7 @@ digraph brainstorming {
"Present design sections" [shape=box];
"User approves design?" [shape=diamond];
"Write design doc" [shape=box];
"Spec review loop" [shape=box];
"Spec review passed?" [shape=diamond];
"Spec self-review\n(fix inline)" [shape=box];
"User reviews spec?" [shape=diamond];
"Invoke writing-plans skill" [shape=doublecircle];
@@ -57,10 +56,8 @@ digraph brainstorming {
"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 review loop";
"Spec review loop" -> "Spec review passed?";
"Spec review passed?" -> "Spec review loop" [label="issues found,\nfix and re-dispatch"];
"Spec review passed?" -> "User reviews spec?" [label="approved"];
"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"];
}
@@ -116,12 +113,15 @@ digraph brainstorming {
- Use elements-of-style:writing-clearly-and-concisely skill if available
- Commit the design document to git
**Spec Review Loop:**
After writing the spec document:
**Spec Self-Review:**
After writing the spec document, look at it with fresh eyes:
1. Dispatch spec-document-reviewer subagent (see spec-document-reviewer-prompt.md)
2. If Issues Found: fix, re-dispatch, repeat until Approved
3. If loop exceeds 3 iterations, surface to human for guidance
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:

View File

@@ -106,16 +106,22 @@ if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then
OWNER_PID="$PPID"
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.
if [[ "$FOREGROUND" == "true" ]]; then
echo "$$" > "$PID_FILE"
env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.js
env BRAINSTORM_DIR="$SCREEN_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="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.js > "$LOG_FILE" 2>&1 &
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 &
SERVER_PID=$!
disown "$SERVER_PID" 2>/dev/null
echo "$SERVER_PID" > "$PID_FILE"

View File

@@ -48,12 +48,21 @@ Save `screen_dir` from the response. Tell user to open the URL.
**Launching the server by platform:**
**Claude Code:**
**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 `$SCREEN_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
@@ -61,14 +70,6 @@ scripts/start-server.sh --project-dir /path/to/project
scripts/start-server.sh --project-dir /path/to/project
```
**Windows (Git Bash / CMD / PowerShell):**
```bash
# Windows/Git Bash reaps nohup background processes. The script auto-detects
# this via OSTYPE/MSYSTEM and switches to foreground mode automatically.
# No extra flags needed — all Windows shells route through Git Bash.
scripts/start-server.sh --project-dir /path/to/project
```
**Gemini CLI:**
```bash
# Use --foreground and set is_background: true on your shell tool call

View File

@@ -49,7 +49,7 @@ This structure informs the task decomposition. Each task should produce self-con
```markdown
# [Feature Name] 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.
> **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]
@@ -103,40 +103,50 @@ 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
## 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.
- 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
**1. Spec coverage:** Skim each section/requirement in the spec. Can you point to a task that implements it? List any gaps.
**Review loop guidance:**
- Same agent that wrote the plan fixes it (preserves context)
- If loop exceeds 3 iterations, surface to human for guidance
- Reviewers are advisory — explain disagreements if you believe feedback is incorrect
**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:
After saving the plan, offer execution choice:
**"Plan complete and saved to `docs/superpowers/plans/<filename>.md`. Ready to execute?"**
**"Plan complete and saved to `docs/superpowers/plans/<filename>.md`. Two execution options:**
**Execution path depends on harness capabilities:**
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
**If harness has subagents (Claude Code, etc.):**
- **REQUIRED:** Use superpowers:subagent-driven-development
- Do NOT offer a choice - subagent-driven is the standard approach
**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
- Fresh subagent per task + two-stage review
**If harness does NOT have subagents:**
- Execute plan in current session using superpowers:executing-plans
**If Inline Execution chosen:**
- **REQUIRED SUB-SKILL:** Use superpowers:executing-plans
- Batch execution with checkpoints for review

View File

@@ -15,7 +15,7 @@ const fs = require('fs');
const path = require('path');
const assert = require('assert');
const SERVER_PATH = path.join(__dirname, '../../skills/brainstorming/scripts/server.js');
const SERVER_PATH = path.join(__dirname, '../../skills/brainstorming/scripts/server.cjs');
const TEST_PORT = 3334;
const TEST_DIR = '/tmp/brainstorm-test';

View 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

View File

@@ -16,7 +16,7 @@ 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.js');
const SERVER_PATH = path.join(__dirname, '../../skills/brainstorming/scripts/server.cjs');
let ws;
try {