';
+ fs.writeFileSync(path.join(CONTENT_DIR, 'alpine-fragment.html'), fragment);
+ await sleep(300);
+
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
+ assert(res.body.includes('x-data="{ open: false }"'), 'Should preserve x-data');
+ assert(res.body.includes('@click="open = !open"'), 'Should preserve @click');
+ assert(res.body.includes('x-show="open"'), 'Should preserve x-show');
+ assert(res.body.includes('/vendor/alpine.js'), 'Should include Alpine script');
+ });
+```
+
+- [ ] **Step 2: Run the failing tests**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+node tests/brainstorm-server/server.test.js
+```
+
+Expected: FAIL because `/vendor/alpine.js` returns 404 and the frame does not include Alpine yet.
+
+- [ ] **Step 3: Implement exact vendor serving**
+
+In `skills/brainstorming/scripts/server.cjs`, add these constants after `helperInjection`:
+
+```js
+const ALPINE_VENDOR_PATH = path.join(__dirname, 'vendor', 'alpine.js');
+
+function loadVendorFile(filePath, name) {
+ try {
+ return fs.readFileSync(filePath);
+ } catch (error) {
+ throw new Error(
+ `Failed to load vendored ${name} at ${filePath}; ` +
+ 'run the refresh command in skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md. ' +
+ error.message
+ );
+ }
+}
+
+const VENDOR_FILES = new Map([
+ ['/vendor/alpine.js', {
+ content: loadVendorFile(ALPINE_VENDOR_PATH, 'Alpine'),
+ contentType: 'application/javascript; charset=utf-8'
+ }]
+]);
+```
+
+Add these helpers after `getNewestScreen()`:
+
+```js
+function parseRequestUrl(req) {
+ return new URL(req.url, 'http://localhost');
+}
+
+function serveVendorFile(requestUrl, res) {
+ const vendorFile = VENDOR_FILES.get(requestUrl.pathname);
+ if (!vendorFile) {
+ res.writeHead(404);
+ res.end('Not found');
+ return;
+ }
+
+ res.writeHead(200, { 'Content-Type': vendorFile.contentType });
+ res.end(vendorFile.content);
+}
+```
+
+Change the start of `handleRequest(req, res)` to parse once and use `pathname`:
+
+```js
+function handleRequest(req, res) {
+ touchActivity();
+ const requestUrl = parseRequestUrl(req);
+
+ if (req.method === 'GET' && requestUrl.pathname === '/') {
+```
+
+Add the vendor branch before `/files/`:
+
+```js
+ } else if (req.method === 'GET' && requestUrl.pathname.startsWith('/vendor/')) {
+ serveVendorFile(requestUrl, res);
+ } else if (req.method === 'GET' && requestUrl.pathname.startsWith('/files/')) {
+ const fileName = requestUrl.pathname.slice(7);
+```
+
+Keep the rest of the `/files/` branch unchanged except that it now uses `fileName` from `requestUrl.pathname`.
+
+- [ ] **Step 4: Inject Alpine from the frame template**
+
+In `skills/brainstorming/scripts/frame-template.html`, add this script tag immediately before ``:
+
+```html
+
+```
+
+Change the indicator copy to:
+
+```html
+ Interact with the mockup, then return to the terminal
+```
+
+- [ ] **Step 5: Run the server tests**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+node tests/brainstorm-server/server.test.js
+```
+
+Expected: `PASS` and `0 failed`.
+
+- [ ] **Step 6: Commit Tasks 1 and 2**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+git add \
+ skills/brainstorming/scripts/server.cjs \
+ skills/brainstorming/scripts/frame-template.html \
+ skills/brainstorming/scripts/vendor/alpine.js \
+ skills/brainstorming/scripts/vendor/alpine.provenance.json \
+ skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md \
+ tests/brainstorm-server/server.test.js
+git commit -m "feat: add Alpine to visual companion runtime"
+```
+
+## Task 3: Preserve Alpine Through Codex Plugin Sync
+
+**Files:**
+- Modify: `scripts/sync-to-codex-plugin.sh`
+- Modify: `tests/codex-plugin-sync/test-sync-to-codex-plugin.sh`
+
+- [ ] **Step 1: Add failing sync fixture coverage**
+
+In `write_upstream_fixture()`, extend the `mkdir -p` block with:
+
+```bash
+ "$repo/skills/brainstorming/scripts/vendor" \
+```
+
+After the example skill fixture, add:
+
+```bash
+ cat > "$repo/skills/brainstorming/scripts/server.cjs" <<'EOF'
+console.log('fixture server')
+EOF
+
+ cat > "$repo/skills/brainstorming/scripts/helper.js" <<'EOF'
+window.fixtureHelper = true
+EOF
+
+ cat > "$repo/skills/brainstorming/scripts/frame-template.html" <<'EOF'
+
+EOF
+
+ printf 'fixture alpine\n' > "$repo/skills/brainstorming/scripts/vendor/alpine.js"
+
+ cat > "$repo/skills/brainstorming/scripts/vendor/alpine.provenance.json" <<'EOF'
+{"name":"alpinejs","version":"3.15.12","localPath":"skills/brainstorming/scripts/vendor/alpine.js","sha256":"fixture","approvalArtifact":"SUP-215"}
+EOF
+
+ cat > "$repo/skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md" <<'EOF'
+# Third-Party Notices
+
+Alpine.js fixture notice.
+EOF
+```
+
+Add these paths to the `git -C "$repo" add` list:
+
+```bash
+ skills/brainstorming/scripts/server.cjs \
+ skills/brainstorming/scripts/helper.js \
+ skills/brainstorming/scripts/frame-template.html \
+ skills/brainstorming/scripts/vendor/alpine.js \
+ skills/brainstorming/scripts/vendor/alpine.provenance.json \
+ skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md \
+```
+
+In `write_synced_destination_fixture()`, extend the `mkdir -p` block with:
+
+```bash
+ "$repo/plugins/superpowers/skills/brainstorming/scripts/vendor" \
+```
+
+Add the same fixture files under `plugins/superpowers/skills/brainstorming/scripts/`, then add those paths to the destination `git add` list.
+
+Add these preview assertions after `Preview reflects dirty tracked destination file`:
+
+```bash
+ assert_contains "$preview_section" "skills/brainstorming/scripts/server.cjs" "Preview includes skill-local server runtime"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/helper.js" "Preview includes skill-local helper runtime"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/frame-template.html" "Preview includes skill-local frame template"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/vendor/alpine.js" "Preview includes vendored Alpine"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/vendor/alpine.provenance.json" "Preview includes Alpine provenance"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md" "Preview includes Alpine notice"
+```
+
+Add these no-op fixture path variables near `noop_openai_metadata_path`:
+
+```bash
+ local noop_alpine_path
+ local noop_alpine_provenance_path
+ local noop_alpine_notice_path
+```
+
+Assign them after `noop_openai_metadata_path=...`:
+
+```bash
+ noop_alpine_path="$noop_apply_dest/plugins/superpowers/skills/brainstorming/scripts/vendor/alpine.js"
+ noop_alpine_provenance_path="$noop_apply_dest/plugins/superpowers/skills/brainstorming/scripts/vendor/alpine.provenance.json"
+ noop_alpine_notice_path="$noop_apply_dest/plugins/superpowers/skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md"
+```
+
+Add these no-op assertions after the OpenAI metadata assertion:
+
+```bash
+ assert_file_equals "$noop_alpine_path" "fixture alpine" "Clean no-op local apply preserves vendored Alpine"
+ assert_file_equals "$noop_alpine_provenance_path" "{\"name\":\"alpinejs\",\"version\":\"3.15.12\",\"localPath\":\"skills/brainstorming/scripts/vendor/alpine.js\",\"sha256\":\"fixture\",\"approvalArtifact\":\"SUP-215\"}" "Clean no-op local apply preserves Alpine provenance"
+ assert_contains "$(cat "$noop_alpine_notice_path")" "Alpine.js fixture notice." "Clean no-op local apply preserves Alpine notice"
+```
+
+Add this source assertion near the existing source assertions:
+
+```bash
+ assert_contains "$script_source" "Vendored third-party code included in this sync" "Source calls out vendored third-party code in sync PR body"
+```
+
+- [ ] **Step 2: Run the failing sync test**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh
+```
+
+Expected: FAIL on the source assertion because the sync PR body does not mention vendored third-party code yet.
+
+- [ ] **Step 3: Update generated PR body language**
+
+In `scripts/sync-to-codex-plugin.sh`, add this helper before
+`if [[ $BOOTSTRAP -eq 1 ]]; then` in the commit/PR section. Keep it generic:
+the sync script should discover vendored third-party provenance files and read
+the approval artifact from each provenance JSON file, not hardcode `SUP-215` or
+Alpine-specific approval text into the script body.
+
+```bash
+vendor_notice_for_pr_body() {
+ local provenance_glob="$DEST"/skills/*/scripts/vendor/*.provenance.json
+
+ if ! compgen -G "$provenance_glob" > /dev/null; then
+ return 0
+ fi
+
+ python3 - "$DEST" <<'PY'
+import glob
+import json
+import os
+import sys
+
+dest = sys.argv[1]
+provenance_files = sorted(glob.glob(os.path.join(dest, "skills", "*", "scripts", "vendor", "*.provenance.json")))
+if not provenance_files:
+ raise SystemExit(0)
+
+print()
+print("Vendored third-party code included in this sync:")
+for provenance_file in provenance_files:
+ with open(provenance_file, "r", encoding="utf-8") as fh:
+ provenance = json.load(fh)
+
+ rel_provenance = os.path.relpath(provenance_file, dest)
+ rel_vendor_dir = os.path.dirname(rel_provenance)
+ basename = os.path.basename(provenance_file).removesuffix(".provenance.json")
+ local_path = provenance.get("localPath") or os.path.join(rel_vendor_dir, f"{basename}.js")
+ notice_path = os.path.join(rel_vendor_dir, "THIRD_PARTY_NOTICES.md")
+ name = provenance.get("name", "unknown")
+ version = provenance.get("version", "unknown")
+ approval = provenance.get("approvalArtifact", "not recorded")
+ sha256 = provenance.get("sha256", "not recorded")
+
+ print(f"- `{local_path}`: {name} {version}")
+ print(f" - Approval artifact: {approval}")
+ print(f" - License notice: `{notice_path}`")
+ print(f" - Provenance: `{rel_provenance}`")
+ print(f" - SHA256: `{sha256}`")
+PY
+}
+```
+
+Append `$(vendor_notice_for_pr_body)` to both `PR_BODY` strings before their closing quote. For the normal sync body, the final paragraph should become:
+
+```bash
+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.$(vendor_notice_for_pr_body)"
+```
+
+For the bootstrap body, the final paragraph should become:
+
+```bash
+This is a one-time bootstrap. Subsequent syncs will be normal (non-bootstrap) runs using the same tracked upstream plugin files.$(vendor_notice_for_pr_body)"
+```
+
+- [ ] **Step 4: Run the sync test**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh
+```
+
+Expected: `PASS`.
+
+- [ ] **Step 5: Commit Task 3**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+git add scripts/sync-to-codex-plugin.sh tests/codex-plugin-sync/test-sync-to-codex-plugin.sh
+git commit -m "test: cover Alpine in Codex plugin sync"
+```
+
+## Task 4: Update Visual Companion Guidance
+
+**Files:**
+- Modify: `skills/brainstorming/visual-companion.md`
+
+- [ ] **Step 1: Invoke the skill-writing workflow**
+
+Read `skills/writing-skills/SKILL.md` before editing `visual-companion.md`.
+
+- [ ] **Step 2: Update the selection-first copy**
+
+Change the `How It Works` paragraph to:
+
+```markdown
+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 tries the mockup in their browser, and they respond in the terminal. Use `[data-choice]` only when you are deliberately asking the user to pick among named A/B/C visual options.
+```
+
+Change Loop step 2 to:
+
+```markdown
+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 an interactive meal-planning mockup with tabs and an editable grocery list")
+ - Ask them to respond in the terminal: "Take a look, try the mockup, and tell me what feels right or wrong."
+ - If the screen is a deliberate A/B/C choice, also say: "Click an option if you'd like; your terminal feedback is still the source of truth."
+```
+
+- [ ] **Step 3: Add compact Alpine guidance before the current minimal example**
+
+Insert this section before `**Minimal example:**`:
+
+````markdown
+## Interactive Mockups With Alpine
+
+Frame-wrapped fragments automatically load Alpine.js. Use Alpine when visible interaction is central to the design question: tabs, toggles, accordions, modal open/close, wizard next/back, lightweight form validation, or simple add/remove list behavior.
+
+Keep it illustrative. Do not build a fake application just because realistic chrome includes many controls. If an interaction is not part of the question, render that area as passive content.
+
+```html
+
+
+
+
+
+
+
+
Week plan
+
Three realistic meals are enough for the mockup.
+
+
+
+
Grocery list
+
+
+
+
+
+
+
+
+
+```
+
+Rules:
+
+- Write content fragments by default; do not add an Alpine `` to
+ `frame-template.html`.
+- Keep the existing helper server-injected from `server.cjs` into every served
+ page, including waiting pages and full HTML documents.
+- Do not automatically inject Alpine into waiting pages or full HTML documents.
+ Full documents may include their own scripts, including `/vendor/alpine.js`,
+ when they need complete control.
+- Update the frame's default indicator copy from a selection-specific prompt to
+ neutral language such as "Interact with the mockup, then return to the
+ terminal." Preserve the helper's selected-choice update behavior when a
+ deliberate `[data-choice]` is clicked.
+
+Required runtime invariant:
+
+- By the time `DOMContentLoaded` fires for a served frame-wrapped fragment,
+ every `x-data` block in that fragment has been evaluated and `x-show` /
+ `@click` directives are bound.
+- The existing helper must still connect to the WebSocket server, reload on
+ screen changes, and capture deliberate `[data-choice]` clicks.
+- The helper must not depend on Alpine.
+
+Expected served fragment order:
+
+1. Page/frame HTML
+2. Alpine script with `defer`
+3. Existing helper injection
+
+Because `defer` changes execution order, the implementation should test the
+runtime behavior rather than only checking byte order in the served HTML.
+
+V1 guarantees automatic Alpine support only for normal frame-wrapped fragments.
+The common agent path should remain fragments; do not require robust
+full-document Alpine injection in SUP-215.
+
+### Codex Plugin Sync
+
+The root sync script already uses anchored root-level excludes, so `/scripts/`
+does not match nested skill-local paths like
+`skills/brainstorming/scripts/vendor/alpine.js`. SUP-215 should preserve that
+behavior rather than changing the exclusion model.
+
+The sync script does need one user-visible change: generated Codex plugin PR
+bodies should surface the vendored third-party code when the synced diff
+includes `skills/brainstorming/scripts/vendor/alpine.js`. The PR body should
+call out the approval artifact, license notice, and SHA256 provenance instead
+of presenting the sync as an opaque tracked-file copy.
+
+### Mockup Authoring Guidance
+
+Update `visual-companion.md` so agents treat Alpine as available by default.
+
+The key instruction:
+
+> If a visual mockup includes something that looks clickable, editable, or
+> selectable to a user, make it work only when that interaction is part of the
+> current design question. Otherwise, render it visibly as passive non-control
+> content or keep the behavior minimal and illustrative.
+
+The guide should lead with an Alpine-backed interactive mockup example before
+the existing selection-card examples. Existing `data-choice` examples should be
+kept but clearly labeled as deliberate A/B choice affordances, not normal UI
+controls.
+
+Keep the guide compact. It should include one concise Alpine example and a
+terse do/don't checklist, not a cookbook of separate snippets for every UI
+pattern.
+
+Common Alpine patterns the example or checklist may reference:
+
+- tabs and sidebar navigation
+- modal/dialog open and close
+- accordion expand/collapse
+- form input and lightweight validation
+- multi-step wizard navigation
+- toggle/switch state
+- simple list add/remove/edit behavior
+- toast or inline success feedback
+
+Controls that should work when they are central to the current visual question:
+
+- tabs and sidebar/nav items
+- buttons that imply state changes
+- toggles and switches
+- form fields and submit buttons
+- modal/dialog triggers
+- accordion headers
+- wizard next/back controls
+- add/edit/delete list actions
+
+Boundaries:
+
+These are authoring rules enforced by agent discipline, skill guidance, human
+review, and eval evidence. They are not enforced by the server, frame template,
+or vendored Alpine in V1. If runtime enforcement becomes necessary, that should
+be a follow-up hardening task, likely involving CSP and a revisit of the Alpine
+CSP build.
+
+- No fake backend calls.
+- No network requests.
+- No localStorage/sessionStorage persistence.
+- No complex application logic beyond what the mockup needs to communicate.
+- No interactivity that is not visually implied by the mockup.
+- Do not build full add/edit/delete/search/wizard behavior merely because those
+ controls appear in a realistic product screen. If the question is about visual
+ hierarchy, surrounding app chrome can be passive.
+- No script tags for Alpine; the frame provides it.
+- Do not put exploratory Alpine controls inside `[data-choice]` containers
+ unless the click is intended to select that choice. Use a separate choice
+ affordance or `@click.stop` where appropriate.
+- Replace existing network-positive guidance such as loading live Unsplash
+ images. If real images matter, use project-provided local assets through the
+ existing `/files/` route or choose a simple local placeholder.
+
+### Sample Data Policy
+
+Do not ship canned sample fixtures.
+
+When a mockup represents data, the agent should create 2-5 compact, realistic,
+domain-specific records. The records should match the product being discussed.
+A family meal-planning tool should not show generic SaaS users; a workshop
+scheduling app should show realistic sessions, facilitators, rooms, or dates.
+
+Put records in Alpine `x-data` only when interaction needs state, such as
+filtering, editing, adding, selecting, or stepping through records. If the data
+is only presentational, render it directly as HTML.
+
+This keeps mockups grounded in the user's idea and avoids every screen
+collapsing into the same dashboard template.
+
+### Feedback and Events
+
+V1 keeps the current feedback model unchanged.
+
+- The terminal remains the primary feedback channel.
+- Existing `[data-choice]` click capture remains supported.
+- Alpine interactions are for user understanding, not automatic telemetry.
+- Default guide and frame language should say "try/interact with the mockup,
+ then respond in the terminal," not "click an option" unless the screen is
+ explicitly asking for an A/B/C choice.
+- Use `data-choice` only when asking the user to choose among named options the
+ agent should read on the next turn.
+- Do not instrument ordinary tabs, forms, toggles, modals, or list interactions
+ as choice events.
+- Do not add broad interaction streaming in V1.
+- Do not ask agents to wire new `brainstorm.feedback(...)` calls in V1.
+
+This avoids expanding context with noisy interaction logs. The user can freely
+poke at a mockup, then tell the agent what worked or did not work.
+
+## V2 Follow-Up
+
+After dogfooding Alpine-backed mockups, revisit the old selection-oriented
+event model.
+
+Possible V2 direction:
+
+- Remove or de-emphasize the selection-specific helper code.
+- Replace it with a general ephemeral interaction stream file.
+- Keep that stream out of default context; agents should read it only when it is
+ useful.
+- Clear the stream when a new screen is pushed and/or when the server stops.
+
+Do not implement this in SUP-215. The point of V1 is to learn whether Alpine
+improves visual brainstorming before changing the feedback model.
+
+## Security and Trust Boundary
+
+Superpowers visual companion is not Brainstorm.
+
+Brainstorm renders user-generated artifacts inside a multi-user web
+application, so CSP and iframe sandboxing are product security boundaries.
+Superpowers runs a local helper server inside the user's coding harness. The
+server binds to `127.0.0.1` by default, and the user has already authorized the
+agent to write local files and run local commands.
+
+The relevant V1 guardrails are:
+
+- keep the default bind host as localhost-only
+- vendor Alpine instead of fetching it from a CDN at runtime
+- serve only known vendored files
+- prohibit network requests in generated mockups
+- prohibit storage-based persistence in generated mockups
+
+CSP and iframe sandboxing can be revisited if local usage reveals a concrete
+need.
+
+## Testing
+
+Extend the existing brainstorm server tests.
+
+Required coverage:
+
+- `/vendor/alpine.js` returns the vendored Alpine script with a JavaScript
+ content type.
+- `/vendor/alpine.js?v=` returns the same vendored script.
+- Unknown, nested, and traversal-ish vendor paths return 404, including encoded
+ traversal attempts.
+- Frame-wrapped fragments include the Alpine script automatically.
+- Existing helper injection still occurs.
+- Waiting pages and full HTML documents continue to receive helper injection
+ and do not receive automatic Alpine injection.
+- Existing `[data-choice]` click capture still writes `state/events`.
+- A fragment containing Alpine attributes is served without stripping or
+ escaping those attributes.
+- Vendored Alpine provenance verification recomputes the SHA256 and checks the
+ required metadata and notice files.
+
+Do not pretend the existing `tests/brainstorm-server/server.test.js` harness can
+prove Alpine runtime behavior. It is an HTTP/WebSocket test harness and does not
+execute browser DOM events or Alpine directives. Runtime behaviors such as
+`x-show`, `@click`, and `@click.stop` must be covered by a real browser test if
+one is added, or by manual dogfood evidence in the PR.
+
+Codex plugin sync coverage:
+
+- Update `tests/codex-plugin-sync/test-sync-to-codex-plugin.sh` so the fixture
+ includes the visual companion runtime files:
+ `skills/brainstorming/scripts/server.cjs`,
+ `skills/brainstorming/scripts/helper.js`,
+ `skills/brainstorming/scripts/frame-template.html`,
+ `skills/brainstorming/scripts/vendor/alpine.js`,
+ `skills/brainstorming/scripts/vendor/alpine.provenance.json`, and
+ `skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md`.
+- Assert that dry-run preview includes those nested skill-local runtime files.
+- Assert that the no-op synced destination fixture contains those files, so the
+ test proves root `/scripts/` exclusion does not remove
+ `skills/brainstorming/scripts/`.
+- If a positive changed-apply fixture is added, assert that the applied
+ destination contains the vendored Alpine file and provenance files.
+- Update `scripts/sync-to-codex-plugin.sh` PR body generation so any downstream
+ Codex plugin PR carrying `skills/brainstorming/scripts/vendor/alpine.js`
+ explicitly calls out the vendored third-party code, approval artifact,
+ license notice, and SHA256 provenance.
+
+Skill behavior coverage:
+
+- Use `superpowers:writing-skills` for the `visual-companion.md` behavior
+ change.
+- Include adversarial pressure-test evidence in the implementation PR: initial
+ prompt, environment, eval count, observed output, and whether the output met
+ expectations.
+- Cover at least this matrix:
+ - Interactive mockup without `data-choice`: uses Alpine directives, omits an
+ Alpine script tag, includes compact domain-specific sample data when useful,
+ avoids backend/storage/network behavior, and asks the user to respond in the
+ terminal.
+ - Deliberate A/B choice: preserves `data-choice` for named options and keeps
+ the choice semantics clear.
+ - Static visual: uses no Alpine when interactivity is not useful.
+ - Busy dashboard or app shell: limits interactivity to the design question and
+ does not build a fake mini-application.
+ - Image-heavy mockup that previously might have used a live Unsplash URL: now
+ uses a `/files/` local asset or a local placeholder, with
+ before/after evidence for the guidance change.
+
+Manual dogfood check:
+
+1. Start the visual companion with `scripts/start-server.sh --project-dir`.
+2. Write a normal fragment that uses `x-data`, `@click`, and `x-show`.
+3. Open the local URL.
+4. Confirm Alpine initializes with no console errors.
+5. Confirm `@click` changes state and `x-show` toggles visibility.
+6. Confirm the interaction works without the agent adding an Alpine script tag.
+7. Confirm a nested Alpine control using `@click.stop` near a `[data-choice]`
+ surface does not produce an unintended extra choice event.
+8. Confirm the terminal remains the feedback path.
+
+If adding an automated browser dependency is too heavy for SUP-215, this
+browser proof can be manual PR evidence rather than a new test dependency.
+
+## Rollout
+
+V1 is an experiment, but it should still ship cleanly:
+
+- Keep changes contained to the brainstorming skill runtime, guide, and tests.
+- Do not change the visual companion startup flow.
+- Do not create a new mode in the user-facing language.
+- Describe the behavior as "interactive mockups" or "Alpine-backed mockups,"
+ not as a separate artifact/prototype system.
+- Include the maintainer-approved dependency exception and third-party
+ provenance in the PR.
+- Include real browser dogfood evidence that Alpine initializes and runs.
+- Include skill-behavior evidence that the updated guidance changes agent
+ output, not just server bytes.
+- Include the PR base in the review notes. The SUP-215 PR should show a focused
+ diff against its chosen base.
+- After dogfooding, decide whether SUP-215 should be followed by a V2 ticket
+ for event-stream cleanup.
diff --git a/docs/testing.md b/docs/testing.md
index c283e78e0a..7bde5a0869 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -1,303 +1,34 @@
-# Testing Superpowers Skills
+# Testing Superpowers
-This document describes how to test Superpowers skills, particularly the integration tests for complex skills like `subagent-driven-development`.
+Superpowers has two distinct kinds of tests, each in its own directory:
-## Overview
+- **`tests/`** — does the plugin's non-LLM code work? Bash + node + python integration tests for brainstorm-server JS, OpenCode plugin loading, codex-plugin sync, and analysis utilities.
+- **`evals/`** — do agents behave correctly on real LLM sessions? Python harness driving real tmux sessions of Claude Code / Codex / Gemini CLI, with an LLM actor and verifier judging skill compliance.
-Testing skills that involve subagents, workflows, and complex interactions requires running actual Claude Code sessions in headless mode and verifying their behavior through session transcripts.
+## Plugin tests
-## Test Structure
+Live in `tests/`. Currently:
-```
-tests/
-├── claude-code/
-│ ├── test-helpers.sh # Shared test utilities
-│ ├── test-subagent-driven-development-integration.sh
-│ ├── analyze-token-usage.py # Token analysis tool
-│ └── run-skill-tests.sh # Test runner (if exists)
-```
-
-## Running Tests
-
-### Integration Tests
-
-Integration tests execute real Claude Code sessions with actual skills:
-
-```bash
-# Run the subagent-driven-development integration test
-cd tests/claude-code
-./test-subagent-driven-development-integration.sh
-```
-
-**Note:** Integration tests can take 10-30 minutes as they execute real implementation plans with multiple subagents.
-
-### Requirements
-
-- Must run from the **superpowers plugin directory** (not from temp directories)
-- Claude Code must be installed and available as `claude` command
-- Local dev marketplace must be enabled: `"superpowers@superpowers-dev": true` in `~/.claude/settings.json`
-
-## Integration Test: subagent-driven-development
-
-### What It Tests
-
-The integration test verifies the `subagent-driven-development` skill correctly:
-
-1. **Plan Loading**: Reads the plan once at the beginning
-2. **Full Task Text**: Provides complete task descriptions to subagents (doesn't make them read files)
-3. **Self-Review**: Ensures subagents perform self-review before reporting
-4. **Review Order**: Runs spec compliance review before code quality review
-5. **Review Loops**: Uses review loops when issues are found
-6. **Independent Verification**: Spec reviewer reads code independently, doesn't trust implementer reports
-
-### How It Works
-
-1. **Setup**: Creates a temporary Node.js project with a minimal implementation plan
-2. **Execution**: Runs Claude Code in headless mode with the skill
-3. **Verification**: Parses the session transcript (`.jsonl` file) to verify:
- - Skill tool was invoked
- - Subagents were dispatched (Task tool)
- - TodoWrite was used for tracking
- - Implementation files were created
- - Tests pass
- - Git commits show proper workflow
-4. **Token Analysis**: Shows token usage breakdown by subagent
-
-### Test Output
-
-```
-========================================
- Integration Test: subagent-driven-development
-========================================
-
-Test project: /tmp/tmp.xyz123
+- `tests/brainstorm-server/` — node test suite for the brainstorm server JS code.
+- `tests/opencode/` — bash tests for OpenCode plugin loading, bootstrap caching, and tool registration.
+- `tests/codex-plugin-sync/` — bash sync verification.
+- `tests/claude-code/test-helpers.sh`, `analyze-token-usage.py` — utilities used by remaining bash tests.
+- `tests/claude-code/test-subagent-driven-development.sh` — agent-can-describe-SDD test (no drill counterpart; tests description-recall, not behavior).
+- `tests/claude-code/test-subagent-driven-development-integration.sh` — extended SDD integration with token analysis (drill covers the YAGNI subset; bash adds commit-count, Claude Code task-tracking, and token telemetry assertions).
+- `tests/claude-code/test-worktree-native-preference.sh` — RED-GREEN-REFACTOR validation for worktree skill (drill covers the PRESSURE phase; bash also covers RED/GREEN baselines).
+- `tests/explicit-skill-requests/` — Haiku-specific, multi-turn, and skill-name-prompted tests not covered by drill.
-=== Verification Tests ===
-
-Test 1: Skill tool invoked...
- [PASS] subagent-driven-development skill was invoked
-
-Test 2: Subagents dispatched...
- [PASS] 7 subagents dispatched
-
-Test 3: Task tracking...
- [PASS] TodoWrite used 5 time(s)
-
-Test 6: Implementation verification...
- [PASS] src/math.js created
- [PASS] add function exists
- [PASS] multiply function exists
- [PASS] test/math.test.js created
- [PASS] Tests pass
-
-Test 7: Git commit history...
- [PASS] Multiple commits created (3 total)
-
-Test 8: No extra features added...
- [PASS] No extra features added
-
-=========================================
- Token Usage Analysis
-=========================================
-
-Usage Breakdown:
-----------------------------------------------------------------------------------------------------
-Agent Description Msgs Input Output Cache Cost
-----------------------------------------------------------------------------------------------------
-main Main session (coordinator) 34 27 3,996 1,213,703 $ 4.09
-3380c209 implementing Task 1: Create Add Function 1 2 787 24,989 $ 0.09
-34b00fde implementing Task 2: Create Multiply Function 1 4 644 25,114 $ 0.09
-3801a732 reviewing whether an implementation matches... 1 5 703 25,742 $ 0.09
-4c142934 doing a final code review... 1 6 854 25,319 $ 0.09
-5f017a42 a code reviewer. Review Task 2... 1 6 504 22,949 $ 0.08
-a6b7fbe4 a code reviewer. Review Task 1... 1 6 515 22,534 $ 0.08
-f15837c0 reviewing whether an implementation matches... 1 6 416 22,485 $ 0.07
-----------------------------------------------------------------------------------------------------
-
-TOTALS:
- Total messages: 41
- Input tokens: 62
- Output tokens: 8,419
- Cache creation tokens: 132,742
- Cache read tokens: 1,382,835
-
- Total input (incl cache): 1,515,639
- Total tokens: 1,524,058
-
- Estimated cost: $4.67
- (at $3/$15 per M tokens for input/output)
-
-========================================
- Test Summary
-========================================
-
-STATUS: PASSED
-```
-
-## Token Analysis Tool
-
-### Usage
-
-Analyze token usage from any Claude Code session:
-
-```bash
-python3 tests/claude-code/analyze-token-usage.py ~/.claude/projects//.jsonl
-```
+Run plugin tests via the relevant directory's `run-*.sh` or `npm test`.
-### Finding Session Files
+## Skill behavior evals
-Session transcripts are stored in `~/.claude/projects/` with the working directory path encoded:
+Live in `evals/`. Drill is the harness; scenarios live at `evals/scenarios/*.yaml`. See `evals/README.md` for setup. Quick start:
```bash
-# 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
-```
-
-### What It Shows
-
-- **Main session usage**: Token usage by the coordinator (you or main Claude instance)
-- **Per-subagent breakdown**: Each Task invocation with:
- - Agent ID
- - Description (extracted from prompt)
- - Message count
- - Input/output tokens
- - Cache usage
- - Estimated cost
-- **Totals**: Overall token usage and cost estimate
-
-### Understanding the Output
-
-- **High cache reads**: Good - means prompt caching is working
-- **High input tokens on main**: Expected - coordinator has full context
-- **Similar costs per subagent**: Expected - each gets similar task complexity
-- **Cost per task**: Typical range is $0.05-$0.15 per subagent depending on task
-
-## Troubleshooting
-
-### Skills Not Loading
-
-**Problem**: Skill not found when running headless tests
-
-**Solutions**:
-1. Ensure you're running FROM the superpowers directory: `cd /path/to/superpowers && tests/...`
-2. Check `~/.claude/settings.json` has `"superpowers@superpowers-dev": true` in `enabledPlugins`
-3. Verify skill exists in `skills/` directory
-
-### Permission Errors
-
-**Problem**: Claude blocked from writing files or accessing directories
-
-**Solutions**:
-1. Use `--permission-mode bypassPermissions` flag
-2. Use `--add-dir /path/to/temp/dir` to grant access to test directories
-3. Check file permissions on test directories
-
-### Test Timeouts
-
-**Problem**: Test takes too long and times out
-
-**Solutions**:
-1. Increase timeout: `timeout 1800 claude ...` (30 minutes)
-2. Check for infinite loops in skill logic
-3. Review subagent task complexity
-
-### Session File Not Found
-
-**Problem**: Can't find session transcript after test run
-
-**Solutions**:
-1. Check the correct project directory in `~/.claude/projects/`
-2. Use `find ~/.claude/projects -name "*.jsonl" -mmin -60` to find recent sessions
-3. Verify test actually ran (check for errors in test output)
-
-## Writing New Integration Tests
-
-### Template
-
-```bash
-#!/usr/bin/env bash
-set -euo pipefail
-
-SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
-source "$SCRIPT_DIR/test-helpers.sh"
-
-# Create test project
-TEST_PROJECT=$(create_test_project)
-trap "cleanup_test_project $TEST_PROJECT" EXIT
-
-# Set up test files...
-cd "$TEST_PROJECT"
-
-# Run Claude with skill
-PROMPT="Your test prompt here"
-cd "$SCRIPT_DIR/../.." && timeout 1800 claude -p "$PROMPT" \
- --allowed-tools=all \
- --add-dir "$TEST_PROJECT" \
- --permission-mode bypassPermissions \
- 2>&1 | tee output.txt
-
-# Find and analyze session
-WORKING_DIR_ESCAPED=$(echo "$SCRIPT_DIR/../.." | sed 's/\\//-/g' | sed 's/^-//')
-SESSION_DIR="$HOME/.claude/projects/$WORKING_DIR_ESCAPED"
-SESSION_FILE=$(find "$SESSION_DIR" -name "*.jsonl" -type f -mmin -60 | sort -r | head -1)
-
-# Verify behavior by parsing session transcript
-if grep -q '"name":"Skill".*"skill":"your-skill-name"' "$SESSION_FILE"; then
- echo "[PASS] Skill was invoked"
-fi
-
-# Show token analysis
-python3 "$SCRIPT_DIR/analyze-token-usage.py" "$SESSION_FILE"
-```
-
-### Best Practices
-
-1. **Always cleanup**: Use trap to cleanup temp directories
-2. **Parse transcripts**: Don't grep user-facing output - parse the `.jsonl` session file
-3. **Grant permissions**: Use `--permission-mode bypassPermissions` and `--add-dir`
-4. **Run from plugin dir**: Skills only load when running from the superpowers directory
-5. **Show token usage**: Always include token analysis for cost visibility
-6. **Test real behavior**: Verify actual files created, tests passing, commits made
-
-## Session Transcript Format
-
-Session transcripts are JSONL (JSON Lines) files where each line is a JSON object representing a message or tool result.
-
-### Key Fields
-
-```json
-{
- "type": "assistant",
- "message": {
- "content": [...],
- "usage": {
- "input_tokens": 27,
- "output_tokens": 3996,
- "cache_read_input_tokens": 1213703
- }
- }
-}
-```
-
-### Tool Results
-
-```json
-{
- "type": "user",
- "toolUseResult": {
- "agentId": "3380c209",
- "usage": {
- "input_tokens": 2,
- "output_tokens": 787,
- "cache_read_input_tokens": 24989
- },
- "prompt": "You are implementing Task 1...",
- "content": [{"type": "text", "text": "..."}]
- }
-}
+cd evals
+uv sync --extra dev
+export ANTHROPIC_API_KEY=sk-...
+uv run drill run triggering-test-driven-development -b claude
```
-The `agentId` field links to subagent sessions, and the `usage` field contains token usage for that specific subagent invocation.
+Drill scenarios are slow (3-30+ minutes each) and run real LLM sessions. They are not part of CI today; the natural follow-up is a tiered model (fast subset on PR, full sweep nightly + on-demand).
diff --git a/evals b/evals
new file mode 160000
index 0000000000..e2b37138c8
--- /dev/null
+++ b/evals
@@ -0,0 +1 @@
+Subproject commit e2b37138c8c636561febf4b18a1f9e401874ca69
diff --git a/hooks/hooks-codex.json b/hooks/hooks-codex.json
new file mode 100644
index 0000000000..5c357fccf6
--- /dev/null
+++ b/hooks/hooks-codex.json
@@ -0,0 +1,16 @@
+{
+ "hooks": {
+ "SessionStart": [
+ {
+ "matcher": "startup|resume|clear",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "\"${PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start-codex",
+ "async": false
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/hooks/session-start b/hooks/session-start
index 2460429503..93a6bc2c6b 100755
--- a/hooks/session-start
+++ b/hooks/session-start
@@ -7,13 +7,6 @@ set -euo pipefail
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\nIN 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"
-fi
-
# Read using-superpowers content
using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill")
@@ -31,8 +24,7 @@ escape_for_json() {
}
using_superpowers_escaped=$(escape_for_json "$using_superpowers_content")
-warning_escaped=$(escape_for_json "$warning_message")
-session_context="\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"
+session_context="\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"
# Output context injection as JSON.
# Cursor hooks expect additional_context (snake_case).
@@ -45,13 +37,13 @@ session_context="\nYou have superpowers.\n\n**Below is the
# 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"
+ printf '{\n "additional_context": "%s"\n}\n' "$session_context" | cat
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"
+ printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$session_context" | cat
else
# Copilot CLI (sets COPILOT_CLI=1) or unknown platform — SDK standard format
- printf '{\n "additionalContext": "%s"\n}\n' "$session_context"
+ printf '{\n "additionalContext": "%s"\n}\n' "$session_context" | cat
fi
exit 0
diff --git a/hooks/session-start-codex b/hooks/session-start-codex
new file mode 100755
index 0000000000..f25ea0846e
--- /dev/null
+++ b/hooks/session-start-codex
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+# Codex SessionStart hook for superpowers plugin
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
+
+using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill")
+
+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")
+session_context="\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, follow the Codex skill-loading instructions in that skill:**\n\n${using_superpowers_escaped}\n"
+
+printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$session_context" | cat
+
+exit 0
diff --git a/package.json b/package.json
index 2b8146635e..54c7bdfa8a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,23 @@
{
"name": "superpowers",
"version": "5.1.0",
+ "description": "Superpowers skills and runtime bootstrap for coding agents",
"type": "module",
- "main": ".opencode/plugins/superpowers.js"
+ "main": ".opencode/plugins/superpowers.js",
+ "keywords": [
+ "pi-package",
+ "skills",
+ "tdd",
+ "debugging",
+ "collaboration",
+ "workflow"
+ ],
+ "pi": {
+ "extensions": [
+ "./.pi/extensions/superpowers.ts"
+ ],
+ "skills": [
+ "./skills"
+ ]
+ }
}
diff --git a/scripts/sync-to-codex-plugin.sh b/scripts/sync-to-codex-plugin.sh
index fc0a8e85d0..f490836582 100755
--- a/scripts/sync-to-codex-plugin.sh
+++ b/scripts/sync-to-codex-plugin.sh
@@ -69,7 +69,7 @@ EXCLUDES=(
# Directories not shipped by canonical Codex plugins
"/commands/"
"/docs/"
- "/hooks/"
+ "/evals/"
"/lib/"
"/scripts/"
"/tests/"
@@ -415,26 +415,73 @@ fi
git add "$DEST_REL"
+vendor_notice_for_pr_body() {
+ local provenance_glob="$DEST"/skills/*/scripts/vendor/*.provenance.json
+
+ if ! compgen -G "$provenance_glob" > /dev/null; then
+ return 0
+ fi
+
+ command -v python3 >/dev/null || die "python3 not found in PATH"
+ python3 - "$DEST" <<'PY'
+import glob
+import json
+import os
+import sys
+
+dest = sys.argv[1]
+provenance_files = sorted(glob.glob(os.path.join(dest, "skills", "*", "scripts", "vendor", "*.provenance.json")))
+if not provenance_files:
+ raise SystemExit(0)
+
+print()
+print()
+print("Vendored third-party code included in this sync:")
+for provenance_file in provenance_files:
+ with open(provenance_file, "r", encoding="utf-8") as fh:
+ provenance = json.load(fh)
+
+ rel_provenance = os.path.relpath(provenance_file, dest)
+ rel_vendor_dir = os.path.dirname(rel_provenance)
+ basename = os.path.basename(provenance_file)
+ suffix = ".provenance.json"
+ if basename.endswith(suffix):
+ basename = basename[:-len(suffix)]
+ local_path = provenance.get("localPath") or os.path.join(rel_vendor_dir, f"{basename}.js")
+ notice_path = os.path.join(rel_vendor_dir, "THIRD_PARTY_NOTICES.md")
+ name = provenance.get("name", "unknown")
+ version = provenance.get("version", "unknown")
+ approval = provenance.get("approvalArtifact", "not recorded")
+ sha256 = provenance.get("sha256", "not recorded")
+
+ print(f"- `{local_path}`: {name} {version}")
+ print(f" - Approval artifact: {approval}")
+ print(f" - License notice: `{notice_path}`")
+ print(f" - Provenance: `{rel_provenance}`")
+ print(f" - SHA256: `{sha256}`")
+PY
+}
+
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/\`.
+Creates \`plugins/superpowers/\` by copying the tracked plugin files from upstream, including \`.codex-plugin/plugin.json\`, \`assets/\`, and \`hooks/\`.
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."
+This is a one-time bootstrap. Subsequent syncs will be normal (non-bootstrap) runs using the same tracked upstream plugin files.$(vendor_notice_for_pr_body)"
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.
+Copies the tracked plugin files from upstream, including the committed Codex manifest, assets, and hooks.
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."
+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.$(vendor_notice_for_pr_body)"
fi
git commit --quiet -m "$COMMIT_TITLE
diff --git a/skills/brainstorming/scripts/frame-template.html b/skills/brainstorming/scripts/frame-template.html
index dcfe01817e..7a3c8ec81c 100644
--- a/skills/brainstorming/scripts/frame-template.html
+++ b/skills/brainstorming/scripts/frame-template.html
@@ -13,7 +13,7 @@
* - Scrollable main content area
* - CSS helpers for common UI patterns
*
- * Content is injected via placeholder comment in #claude-content.
+ * Content is injected via placeholder comment in #frame-content.
*/
* { box-sizing: border-box; margin: 0; padding: 0; }
@@ -77,7 +77,7 @@
.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%; }
+ #frame-content { padding: 2rem; min-height: 100%; }
.indicator-bar {
background: var(--bg-secondary);
@@ -193,6 +193,7 @@
.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%; }
+