Compare commits

..

7 Commits

Author SHA1 Message Date
Drew Ritter
458269ac94 docs(codex-tools): add named agent dispatch mapping for Codex (#647) 2026-03-23 17:35:52 -07:00
Drew Ritter
a6f6c57753 docs: add implementation plan for Codex App compatibility (PRI-823)
8 tasks covering: environment detection in using-git-worktrees,
Step 1.5 + cleanup guard in finishing-a-development-branch,
Integration line updates, codex-tools.md docs, automated tests,
and final verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 13:01:13 -07:00
Drew Ritter
cb8d0a4efa docs: add cleanup guard test (#5) and sandbox fallback test (#10) to spec
Both tests address real risk scenarios:
- #5: cleanup guard bug would delete Codex App's own worktree (data loss)
- #10: Local thread sandbox fallback needs manual Codex App validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:46:39 -07:00
Drew Ritter
f81d5b45bb docs: clarify executing-plans in What Does NOT Change section
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:39:19 -07:00
Drew Ritter
bbe64d4a3b docs: address team review feedback for PRI-823 spec
- Add commit SHA + data loss warning to handoff payload (HIGH)
- Add explicit commit step before handoff (HIGH)
- Remove misleading "mark as externally managed" from Path B
- Add executing-plans 1-line edit (was missing)
- Add branch name derivation rules
- Add conditional UI language for non-App environments
- Add sandbox fallback for permission errors
- Add STOP directive after Step 0 reporting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:38:25 -07:00
Drew Ritter
37ecd67afa docs: address spec review feedback for PRI-823
Fix three Important issues from spec review:
- Clarify Step 1.5 placement relative to existing Steps 2/3
- Re-derive environment state at cleanup time instead of relying on
  earlier skill output
- Acknowledge pre-existing Step 5 cleanup inconsistency

Also: precise step references, exact codex-tools.md content, clearer
Integration section update instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:33:15 -07:00
Drew Ritter
f014bb0775 docs: add Codex App compatibility design spec (PRI-823)
Design for making using-git-worktrees, finishing-a-development-branch,
and subagent-driven-development skills work in the Codex App's sandboxed
worktree environment. Read-only environment detection via git-dir vs
git-common-dir comparison, ~48 lines across 4 files, zero breaking changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 12:30:35 -07:00
35 changed files with 182 additions and 1381 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.7",
"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.7",
"version": "5.0.5",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com"
@@ -9,12 +9,5 @@
"homepage": "https://github.com/obra/superpowers",
"repository": "https://github.com/obra/superpowers",
"license": "MIT",
"keywords": [
"skills",
"tdd",
"debugging",
"collaboration",
"best-practices",
"workflows"
]
"keywords": ["skills", "tdd", "debugging", "collaboration", "best-practices", "workflows"]
}

View File

@@ -2,7 +2,7 @@
"name": "superpowers",
"displayName": "Superpowers",
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
"version": "5.0.7",
"version": "5.0.2",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com"
@@ -10,14 +10,7 @@
"homepage": "https://github.com/obra/superpowers",
"repository": "https://github.com/obra/superpowers",
"license": "MIT",
"keywords": [
"skills",
"tdd",
"debugging",
"collaboration",
"best-practices",
"workflows"
],
"keywords": ["skills", "tdd", "debugging", "collaboration", "best-practices", "workflows"],
"skills": "./skills/",
"agents": "./agents/",
"commands": "./commands/",

View File

@@ -1,52 +0,0 @@
---
name: Bug Report
about: Something isn't working as expected
labels: bug
---
<!--
BEFORE FILING: Search open AND closed issues. The Windows SessionStart
hook alone has been reported 29 times. If your issue already exists,
add a comment or reaction to the existing one instead.
-->
- [ ] I searched existing issues and this is not a duplicate
## Environment
| Field | Value |
|-------|-------|
| Superpowers version | |
| Harness (Claude Code, Cursor, etc.) | |
| Harness version | |
| Model | |
| OS + shell | |
## Is this a Superpowers issue or a platform issue?
<!-- Superpowers is a plugin. Some reported "bugs" are actually issues
in the underlying platform or model. If you're not sure, try
reproducing without Superpowers installed.
If the problem persists without Superpowers, file the issue with
your platform instead. -->
- [ ] I confirmed this issue does not occur without Superpowers installed
## What happened?
<!-- Be specific. "It doesn't work" is not a bug report. -->
## Steps to reproduce
1.
2.
3.
## Expected behavior
<!-- What should have happened? -->
## Actual behavior
<!-- What happened instead? -->
## Debug log or conversation transcript
<!-- A debug log or conversation transcript showing the issue is the
single most helpful thing you can include. Without one, we're
guessing. Screenshots of error output are also useful. -->

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Questions & Help
url: https://discord.gg/35wsABTejz
about: For usage questions, troubleshooting help, and general discussion, please visit our Discord instead of opening an issue.

View File

@@ -1,34 +0,0 @@
---
name: Feature Request
about: Propose a change or addition to Superpowers
labels: enhancement
---
<!--
BEFORE FILING: Search open AND closed issues. Many features have been
requested before — some were implemented differently, some are in
progress, and some were intentionally declined.
-->
- [ ] I searched existing issues and this has not been proposed before
## What problem does this solve?
<!-- Describe the problem from your own experience. What were you doing,
what went wrong or was missing, and why did it matter?
"It would be cool if..." is not a problem statement. -->
## Proposed solution
<!-- What specifically do you want to happen? Be concrete. -->
## What alternatives did you consider?
<!-- What other approaches could solve the same problem? Why is your
proposal better? -->
## Is this appropriate for core Superpowers?
<!-- Would this benefit someone working on a completely different kind
of project? If this is specific to your domain, workflow, or a
third-party tool, it may belong as its own plugin instead. -->
## Context
<!-- Optional: version info, harness, model, workflow where you hit this. -->

View File

@@ -1,23 +0,0 @@
---
name: IDE / Platform Support Request
about: Request support for a new IDE, editor, or AI coding tool
labels: platform-support
---
<!--
BEFORE FILING: Search existing issues — your IDE may already be
requested or discussed.
-->
- [ ] I searched existing issues for this IDE/platform
## Which IDE or platform?
<!-- Name and link -->
## Does this tool have a plugin or extension system?
<!-- If yes, link to the docs. If no, explain how third-party
integrations typically work with this tool. -->
## Have you tried manual installation?
<!-- Many tools work with Superpowers through manual setup even without
official support. Did you try? What happened? -->

View File

@@ -1,87 +0,0 @@
<!--
BEFORE SUBMITTING: Read every word of this template. PRs that leave
sections blank, contain multiple unrelated changes, or show no evidence
of human involvement will be closed without review.
-->
## What problem are you trying to solve?
<!-- Describe the specific problem you encountered. If this was a session
issue, include: what you were doing, what went wrong, the model's
exact failure mode, and ideally a transcript or session log.
"Improving" something is not a problem statement. What broke? What
failed? What was the user experience that motivated this? -->
## What does this PR change?
<!-- 1-3 sentences. What, not why — the "why" belongs above. -->
## Is this change appropriate for the core library?
<!-- Superpowers core contains general-purpose skills and infrastructure
that benefit all users. Ask yourself:
- Would this be useful to someone working on a completely different
kind of project than yours?
- Is this project-specific, team-specific, or tool-specific?
- Does this integrate or promote a third-party service?
If your change is a new skill for a specific domain, workflow tool,
or third-party integration, it belongs in its own plugin — not here.
See the plugin development docs for how to publish it separately. -->
## What alternatives did you consider?
<!-- What other approaches did you try or evaluate before landing on this
one? Why were they worse? If you didn't consider alternatives, say so
— but know that's a red flag. -->
## Does this PR contain multiple unrelated changes?
<!-- If yes: stop. Split it into separate PRs. Bundled PRs will be closed.
If you believe the changes are related, explain the dependency. -->
## Existing PRs
- [ ] I have reviewed all open AND closed PRs for duplicates or prior art
- Related PRs: <!-- #number, #number, or "none found" -->
<!-- If a related closed PR exists, explain what's different about your
approach and why it should succeed where the other didn't. -->
## Environment tested
| Harness (e.g. Claude Code, Cursor) | Harness version | Model | Model version/ID |
|-------------------------------------|-----------------|-------|------------------|
| | | | |
## Evaluation
- What was the initial prompt you (or your human partner) used to start
the session that led to this change?
- How many eval sessions did you run AFTER making the change?
- How did outcomes change compared to before the change?
<!-- "It works" is not evaluation. Describe the before/after difference
you observed across multiple sessions. -->
## Rigor
- [ ] If this is a skills change: I used `superpowers:writing-skills` and
completed adversarial pressure testing (paste results below)
- [ ] This change was tested adversarially, not just on the happy path
- [ ] I did not modify carefully-tuned content (Red Flags table,
rationalizations, "human partner" language) without extensive evals
showing the change is an improvement
<!-- If you changed wording in skills that shape agent behavior, show your
eval methodology and results. These are not prose — they are code. -->
## Human review
- [ ] A human has reviewed the COMPLETE proposed diff before submission
<!--
STOP. If the checkbox above is not checked, do not submit this PR.
PRs will be closed without review if they:
- Show no evidence of human involvement
- Contain multiple unrelated changes
- Promote or integrate third-party services or tools
- Submit project-specific or personal configuration as core changes
- Leave required sections blank or use placeholder text
- Modify behavior-shaping content without eval evidence
-->

View File

@@ -68,6 +68,8 @@ When skills reference tools you don't have, substitute OpenCode equivalents:
- \`Skill\` tool → OpenCode's native \`skill\` tool
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
**Skills location:**
Superpowers skills are in \`${configDir}/skills/superpowers/\`
Use OpenCode's native \`skill\` tool to list and load skills.`;
return `<EXTREMELY_IMPORTANT>
@@ -94,19 +96,12 @@ ${toolMapping}
}
},
// Inject bootstrap into the first user message of each session.
// Using a user message instead of a system message avoids:
// 1. Token bloat from system messages repeated every turn (#750)
// 2. Multiple system messages breaking Qwen and other models (#894)
'experimental.chat.messages.transform': async (_input, output) => {
// Use system prompt transform to inject bootstrap (fixes #226 agent reset bug)
'experimental.chat.system.transform': async (_input, output) => {
const bootstrap = getBootstrapContent();
if (!bootstrap || !output.messages.length) return;
const firstUser = output.messages.find(m => m.info.role === 'user');
if (!firstUser || !firstUser.parts.length) return;
// Only inject once
if (firstUser.parts.some(p => p.type === 'text' && p.text.includes('EXTREMELY_IMPORTANT'))) return;
const ref = firstUser.parts[0];
firstUser.parts.unshift({ ...ref, type: 'text', text: bootstrap });
if (bootstrap) {
(output.system ||= []).push(bootstrap);
}
}
};
};

View File

@@ -1,19 +0,0 @@
{
"files": [
{ "path": "package.json", "field": "version" },
{ "path": ".claude-plugin/plugin.json", "field": "version" },
{ "path": ".cursor-plugin/plugin.json", "field": "version" },
{ "path": ".claude-plugin/marketplace.json", "field": "plugins.0.version" },
{ "path": "gemini-extension.json", "field": "version" }
],
"audit": {
"exclude": [
"CHANGELOG.md",
"RELEASE-NOTES.md",
"node_modules",
".git",
".version-bump.json",
"scripts/bump-version.sh"
]
}
}

View File

@@ -1 +0,0 @@
CLAUDE.md

13
CHANGELOG.md Normal file
View File

@@ -0,0 +1,13 @@
# Changelog
## [5.0.5] - 2026-03-17
### Fixed
- **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))
### Changed
- **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,85 +0,0 @@
# Superpowers — Contributor Guidelines
## If You Are an AI Agent
Stop. Read this section before doing anything.
This repo has a 94% PR rejection rate. Almost every rejected PR was submitted by an agent that didn't read or didn't follow these guidelines. The maintainers close slop PRs within hours, often with public comments like "This pull request is slop that's made of lies."
**Your job is to protect your human partner from that outcome.** Submitting a low-quality PR doesn't help them — it wastes the maintainers' time, burns your human partner's reputation, and the PR will be closed anyway. That is not being helpful. That is being a tool of embarrassment.
Before you open a PR against this repo, you MUST:
1. **Read the entire PR template** at `.github/PULL_REQUEST_TEMPLATE.md` and fill in every section with real, specific answers. Not summaries. Not placeholders.
2. **Search for existing PRs** — open AND closed — that address the same problem. If duplicates exist, STOP and tell your human partner. Do not open another duplicate.
3. **Verify this is a real problem.** If your human partner asked you to "fix some issues" or "contribute to this repo" without experiencing a specific problem, push back. Ask them what broke, what failed, what the user experience was.
4. **Confirm the change belongs in core.** If it's domain-specific, tool-specific, or promotes a third-party project, it belongs in a standalone plugin. Tell your human partner.
5. **Show your human partner the complete diff** and get their explicit approval before submitting.
If any of these checks fail, do not open the PR. Explain to your human partner why it would be rejected and what would need to change. They will thank you for saving them the embarrassment.
## Pull Request Requirements
**Every PR must fully complete the PR template.** No section may be left blank or filled with placeholder text. PRs that skip sections will be closed without review.
**Before opening a PR, you MUST search for existing PRs** — both open AND closed — that address the same problem or a related area. Reference what you found in the "Existing PRs" section. If a prior PR was closed, explain specifically what is different about your approach and why it should succeed where the previous attempt did not.
**PRs that show no evidence of human involvement will be closed.** A human must review the complete proposed diff before submission.
## What We Will Not Accept
### Third-party dependencies
PRs that add optional or required dependencies on third-party projects will not be accepted unless they are adding support for a new harness (e.g., a new IDE or CLI tool). Superpowers is a zero-dependency plugin by design. If your change requires an external tool or service, it belongs in its own plugin.
### "Compliance" changes to skills
Our internal skill philosophy differs from Anthropic's published guidance on writing skills. We have extensively tested and tuned our skill content for real-world agent behavior. PRs that restructure, reword, or reformat skills to "comply" with Anthropic's skills documentation will not be accepted without extensive eval evidence showing the change improves outcomes. The bar for modifying behavior-shaping content is very high.
### Project-specific or personal configuration
Skills, hooks, or configuration that only benefit a specific project, team, domain, or workflow do not belong in core. Publish these as a separate plugin.
### Bulk or spray-and-pray PRs
Do not trawl the issue tracker and open PRs for multiple issues in a single session. Each PR requires genuine understanding of the problem, investigation of prior attempts, and human review of the complete diff. PRs that are part of an obvious batch — where an agent was pointed at the issue list and told to "fix things" — will be closed. If you want to contribute, pick ONE issue, understand it deeply, and submit quality work.
### Speculative or theoretical fixes
Every PR must solve a real problem that someone actually experienced. "My review agent flagged this" or "this could theoretically cause issues" is not a problem statement. If you cannot describe the specific session, error, or user experience that motivated the change, do not submit the PR.
### Domain-specific skills
Superpowers core contains general-purpose skills that benefit all users regardless of their project. Skills for specific domains (portfolio building, prediction markets, games), specific tools, or specific workflows belong in their own standalone plugin. Ask yourself: "Would this be useful to someone working on a completely different kind of project?" If not, publish it separately.
### Fork-specific changes
If you maintain a fork with customizations, do not open PRs to sync your fork or push fork-specific changes upstream. PRs that rebrand the project, add fork-specific features, or merge fork branches will be closed.
### Fabricated content
PRs containing invented claims, fabricated problem descriptions, or hallucinated functionality will be closed immediately. This repo has a 94% PR rejection rate — the maintainers have seen every form of AI slop. They will notice.
### Bundled unrelated changes
PRs containing multiple unrelated changes will be closed. Split them into separate PRs.
## Skill Changes Require Evaluation
Skills are not prose — they are code that shapes agent behavior. If you modify skill content:
- Use `superpowers:writing-skills` to develop and test changes
- Run adversarial pressure testing across multiple sessions
- Show before/after eval results in your PR
- Do not modify carefully-tuned content (Red Flags tables, rationalization lists, "human partner" language) without evidence the change is an improvement
## Understand the Project Before Contributing
Before proposing changes to skill design, workflow philosophy, or architecture, read existing skills and understand the project's design decisions. Superpowers has its own tested philosophy about skill design, agent behavior shaping, and terminology (e.g., "your human partner" is deliberate, not interchangeable with "the user"). Changes that rewrite the project's voice or restructure its approach without understanding why it exists will be rejected.
## General
- Read `.github/PULL_REQUEST_TEMPLATE.md` before submitting
- One problem per PR
- Test on at least one harness and report results in the environment table
- Describe the problem you solved, not just what you changed

View File

@@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
jesse@primeradiant.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -82,13 +82,6 @@ Fetch and follow instructions from https://raw.githubusercontent.com/obra/superp
**Detailed docs:** [docs/README.opencode.md](docs/README.opencode.md)
### GitHub Copilot CLI
```bash
copilot plugin marketplace add obra/superpowers-marketplace
copilot plugin install superpowers@superpowers-marketplace
```
### Gemini CLI
```bash
@@ -181,10 +174,7 @@ Skills update automatically when you update the plugin:
MIT License - see LICENSE file for details
## Community
## Support
Superpowers is built by [Jesse Vincent](https://blog.fsck.com) and the rest of the folks at [Prime Radiant](https://primeradiant.com).
- **Discord**: [Join us](https://discord.gg/35wsABTejz) for community support, questions, and sharing what you're building with Superpowers
- **Issues**: https://github.com/obra/superpowers/issues
- **Release announcements**: [Sign up](https://primeradiant.com/superpowers/) to get notified about new versions
- **Marketplace**: https://github.com/obra/superpowers-marketplace

View File

@@ -1,44 +1,5 @@
# Superpowers Release Notes
## v5.0.7 (2026-03-31)
### GitHub Copilot CLI Support
- **SessionStart context injection** — Copilot CLI v1.0.11 added support for `additionalContext` in sessionStart hook output. The session-start hook now detects the `COPILOT_CLI` environment variable and emits the SDK-standard `{ "additionalContext": "..." }` format, giving Copilot CLI users the full superpowers bootstrap at session start. (Original fix by @culinablaz in PR #910)
- **Tool mapping** — added `references/copilot-tools.md` with the full Claude Code to Copilot CLI tool equivalence table
- **Skill and README updates** — added Copilot CLI to the `using-superpowers` skill's platform instructions and README installation section
### OpenCode Fixes
- **Skills path consistency** — the bootstrap text no longer advertises a misleading `configDir/skills/superpowers/` path that didn't match the runtime path. The agent should use the native `skill` tool, not navigate to files by path. Tests now use consistent paths derived from a single source of truth. (#847, #916)
- **Bootstrap as user message** — moved bootstrap injection from `experimental.chat.system.transform` to `experimental.chat.messages.transform`, prepending to the first user message instead of adding a system message. Avoids token bloat from system messages repeated every turn (#750) and fixes compatibility with Qwen and other models that break on multiple system messages (#894).
## v5.0.6 (2026-03-24)
### Inline Self-Review Replaces Subagent Review Loops
The subagent review loop (dispatching a fresh agent to review plans/specs) doubled execution time (~25 min overhead) without measurably improving plan quality. Regression testing across 5 versions with 5 trials each showed identical quality scores regardless of whether the review loop ran.
- **brainstorming** — replaced Spec Review Loop (subagent dispatch + 3-iteration cap) with inline Spec Self-Review checklist: placeholder scan, internal consistency, scope check, ambiguity check
- **writing-plans** — replaced Plan Review Loop (subagent dispatch + 3-iteration cap) with inline Self-Review checklist: spec coverage, placeholder scan, type consistency
- **writing-plans** — added explicit "No Placeholders" section defining plan failures (TBD, vague descriptions, undefined references, "similar to Task N")
- Self-review catches 3-5 real bugs per run in ~30s instead of ~25 min, with comparable defect rates to the subagent approach
### Brainstorm Server
- **Session directory restructured** — the brainstorm server session directory now contains two peer subdirectories: `content/` (HTML files served to the browser) and `state/` (events, server-info, pid, log). Previously, server state and user interaction data were stored alongside served content, making them accessible over HTTP. The `screen_dir` and `state_dir` paths are both included in the server-started JSON. (Reported by 吉田仁)
### Bug Fixes
- **Owner-PID lifecycle fixes** — the brainstorm server's owner-PID monitoring had two bugs causing false shutdowns within 60 seconds: (1) EPERM from cross-user PIDs (Tailscale SSH, etc.) was treated as "process dead", and (2) on WSL the grandparent PID resolves to a short-lived subprocess that exits before the first lifecycle check. Fixed by treating EPERM as "alive" and validating the owner PID at startup — if it's already dead, monitoring is disabled and the server relies on the 30-minute idle timeout. This also removes the Windows/MSYS2-specific carve-out from `start-server.sh` since the server now handles it generically. (#879)
- **writing-skills** — corrected false claim that SKILL.md frontmatter supports "only two fields"; now says "two required fields" and links to the agentskills.io specification for all supported fields (PR #882 by @arittr)
### Codex App Compatibility
- **codex-tools** — added named agent dispatch mapping documenting how to translate Claude Code's named agent types to Codex's `spawn_agent` with worker roles (PR #647 by @arittr)
- **codex-tools** — added environment detection and Codex App finishing sections for worktree-aware skills (by @arittr)
- **Design spec** — added Codex App compatibility design spec (PRI-823) covering read-only environment detection, worktree-safe skill behavior, and sandbox fallback patterns (by @arittr)
## v5.0.5 (2026-03-17)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "superpowers",
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
"version": "5.0.7",
"version": "5.0.0",
"contextFileName": "GEMINI.md"
}

View File

@@ -35,23 +35,23 @@ warning_escaped=$(escape_for_json "$warning_message")
session_context="<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>"
# Output context injection as JSON.
# Cursor hooks expect additional_context (snake_case).
# Claude Code hooks expect hookSpecificOutput.additionalContext (nested).
# Copilot CLI (v1.0.11+) and others expect additionalContext (top-level, SDK standard).
# Claude Code reads BOTH additional_context and hookSpecificOutput without
# deduplication, so we must emit only the field the current platform consumes.
# Cursor hooks expect additional_context.
# Claude Code hooks expect hookSpecificOutput.additionalContext.
# Claude Code reads BOTH fields without deduplication, so we must only
# emit the field consumed by the current platform to avoid double injection.
#
# Uses printf instead of heredoc to work around bash 5.3+ heredoc hang.
# Uses printf instead of heredoc (cat <<EOF) to work around a bash 5.3+
# bug where heredoc variable expansion hangs when content exceeds ~512 bytes.
# 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)
# Cursor sets CURSOR_PLUGIN_ROOT (may also set CLAUDE_PLUGIN_ROOT) — emit additional_context
printf '{\n "additional_context": "%s"\n}\n' "$session_context"
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -z "${COPILOT_CLI:-}" ]; then
# Claude Code sets CLAUDE_PLUGIN_ROOT without COPILOT_CLI
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
# Claude Code sets CLAUDE_PLUGIN_ROOT — emit only hookSpecificOutput
printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$session_context"
else
# Copilot CLI (sets COPILOT_CLI=1) or unknown platform — SDK standard format
printf '{\n "additionalContext": "%s"\n}\n' "$session_context"
# Other platformsemit additional_context as fallback
printf '{\n "additional_context": "%s"\n}\n' "$session_context"
fi
exit 0

View File

@@ -1,6 +1,6 @@
{
"name": "superpowers",
"version": "5.0.7",
"version": "5.0.4",
"type": "module",
"main": ".opencode/plugins/superpowers.js"
}

View File

@@ -1,220 +0,0 @@
#!/usr/bin/env bash
#
# bump-version.sh — bump version numbers across all declared files,
# with drift detection and repo-wide audit for missed files.
#
# Usage:
# bump-version.sh <new-version> Bump all declared files to new version
# bump-version.sh --check Report current versions (detect drift)
# bump-version.sh --audit Check + grep repo for old version strings
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG="$REPO_ROOT/.version-bump.json"
if [[ ! -f "$CONFIG" ]]; then
echo "error: .version-bump.json not found at $CONFIG" >&2
exit 1
fi
# --- helpers ---
# Read a dotted field path from a JSON file.
# Handles both simple ("version") and nested ("plugins.0.version") paths.
read_json_field() {
local file="$1" field="$2"
# Convert dot-path to jq path: "plugins.0.version" -> .plugins[0].version
local jq_path
jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g')
jq -r "$jq_path" "$file"
}
# Write a dotted field path in a JSON file, preserving formatting.
write_json_field() {
local file="$1" field="$2" value="$3"
local jq_path
jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g')
local tmp="${file}.tmp"
jq "$jq_path = \"$value\"" "$file" > "$tmp" && mv "$tmp" "$file"
}
# Read the list of declared files from config.
# Outputs lines of "path<TAB>field"
declared_files() {
jq -r '.files[] | "\(.path)\t\(.field)"' "$CONFIG"
}
# Read the audit exclude patterns from config.
audit_excludes() {
jq -r '.audit.exclude[]' "$CONFIG" 2>/dev/null
}
# --- commands ---
cmd_check() {
local has_drift=0
local versions=()
echo "Version check:"
echo ""
while IFS=$'\t' read -r path field; do
local fullpath="$REPO_ROOT/$path"
if [[ ! -f "$fullpath" ]]; then
printf " %-45s MISSING\n" "$path ($field)"
has_drift=1
continue
fi
local ver
ver=$(read_json_field "$fullpath" "$field")
printf " %-45s %s\n" "$path ($field)" "$ver"
versions+=("$ver")
done < <(declared_files)
echo ""
# Check if all versions match
local unique
unique=$(printf '%s\n' "${versions[@]}" | sort -u | wc -l | tr -d ' ')
if [[ "$unique" -gt 1 ]]; then
echo "DRIFT DETECTED — versions are not in sync:"
printf '%s\n' "${versions[@]}" | sort | uniq -c | sort -rn | while read -r count ver; do
echo " $ver ($count files)"
done
has_drift=1
else
echo "All declared files are in sync at ${versions[0]}"
fi
return $has_drift
}
cmd_audit() {
# First run check
cmd_check || true
echo ""
# Determine the current version (most common across declared files)
local current_version
current_version=$(
while IFS=$'\t' read -r path field; do
local fullpath="$REPO_ROOT/$path"
[[ -f "$fullpath" ]] && read_json_field "$fullpath" "$field"
done < <(declared_files) | sort | uniq -c | sort -rn | head -1 | awk '{print $2}'
)
if [[ -z "$current_version" ]]; then
echo "error: could not determine current version" >&2
return 1
fi
echo "Audit: scanning repo for version string '$current_version'..."
echo ""
# Build grep exclude args
local -a exclude_args=()
while IFS= read -r pattern; do
exclude_args+=("--exclude=$pattern" "--exclude-dir=$pattern")
done < <(audit_excludes)
# Also always exclude binary files and .git
exclude_args+=("--exclude-dir=.git" "--exclude-dir=node_modules" "--binary-files=without-match")
# Get list of declared paths for comparison
local -a declared_paths=()
while IFS=$'\t' read -r path _field; do
declared_paths+=("$path")
done < <(declared_files)
# Grep for the version string
local found_undeclared=0
while IFS= read -r match; do
local match_file
match_file=$(echo "$match" | cut -d: -f1)
# Make path relative to repo root
local rel_path="${match_file#$REPO_ROOT/}"
# Check if this file is in the declared list
local is_declared=0
for dp in "${declared_paths[@]}"; do
if [[ "$rel_path" == "$dp" ]]; then
is_declared=1
break
fi
done
if [[ "$is_declared" -eq 0 ]]; then
if [[ "$found_undeclared" -eq 0 ]]; then
echo "UNDECLARED files containing '$current_version':"
found_undeclared=1
fi
echo " $match"
fi
done < <(grep -rn "${exclude_args[@]}" -F "$current_version" "$REPO_ROOT" 2>/dev/null || true)
if [[ "$found_undeclared" -eq 0 ]]; then
echo "No undeclared files contain the version string. All clear."
else
echo ""
echo "Review the above files — if they should be bumped, add them to .version-bump.json"
echo "If they should be skipped, add them to the audit.exclude list."
fi
}
cmd_bump() {
local new_version="$1"
# Validate semver-ish format
if ! echo "$new_version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
echo "error: '$new_version' doesn't look like a version (expected X.Y.Z)" >&2
exit 1
fi
echo "Bumping all declared files to $new_version..."
echo ""
while IFS=$'\t' read -r path field; do
local fullpath="$REPO_ROOT/$path"
if [[ ! -f "$fullpath" ]]; then
echo " SKIP (missing): $path"
continue
fi
local old_ver
old_ver=$(read_json_field "$fullpath" "$field")
write_json_field "$fullpath" "$field" "$new_version"
printf " %-45s %s -> %s\n" "$path ($field)" "$old_ver" "$new_version"
done < <(declared_files)
echo ""
echo "Done. Running audit to check for missed files..."
echo ""
cmd_audit
}
# --- main ---
case "${1:-}" in
--check)
cmd_check
;;
--audit)
cmd_audit
;;
--help|-h|"")
echo "Usage: bump-version.sh <new-version> | --check | --audit"
echo ""
echo " <new-version> Bump all declared files to the given version"
echo " --check Show current versions, detect drift"
echo " --audit Check + scan repo for undeclared version references"
exit 0
;;
--*)
echo "error: unknown flag '$1'" >&2
exit 1
;;
*)
cmd_bump "$1"
;;
esac

View File

@@ -1,388 +0,0 @@
#!/usr/bin/env bash
#
# sync-to-codex-plugin.sh
#
# Sync this superpowers checkout → prime-radiant-inc/openai-codex-plugins.
# Clones the fork fresh into a temp dir, rsyncs upstream content, regenerates
# the Codex overlay file (.codex-plugin/plugin.json) inline, commits, pushes a
# sync branch, and opens a PR.
# Path/user agnostic — auto-detects upstream from script location.
#
# Deterministic: running twice against the same upstream SHA produces PRs with
# identical diffs, so two back-to-back runs can verify the tool itself.
#
# Usage:
# ./scripts/sync-to-codex-plugin.sh # full run
# ./scripts/sync-to-codex-plugin.sh -n # dry run
# ./scripts/sync-to-codex-plugin.sh -y # skip confirm
# ./scripts/sync-to-codex-plugin.sh --local PATH # existing checkout
# ./scripts/sync-to-codex-plugin.sh --base BRANCH # default: main
# ./scripts/sync-to-codex-plugin.sh --bootstrap --assets-src DIR # create initial plugin
#
# Bootstrap mode: skips the "plugin must exist on base" check and seeds
# plugins/superpowers/assets/ from --assets-src <dir> which must contain
# PrimeRadiant_Favicon.svg and PrimeRadiant_Favicon.png. Run once by one
# team member to create the initial PR; every subsequent run is a normal
# (non-bootstrap) sync.
#
# Requires: bash, rsync, git, gh (authenticated), python3.
set -euo pipefail
# =============================================================================
# Config — edit as upstream or canonical plugin shape evolves
# =============================================================================
FORK="prime-radiant-inc/openai-codex-plugins"
DEFAULT_BASE="main"
DEST_REL="plugins/superpowers"
# Paths in upstream that should NOT land in the embedded plugin.
# The Codex-only paths are here too — they're managed by generate/bootstrap
# steps, not by rsync.
#
# All patterns use a leading "/" to anchor them to the source root.
# Unanchored patterns like "scripts/" would match any directory named
# "scripts" at any depth — including legitimate nested dirs like
# skills/brainstorming/scripts/. Anchoring prevents that.
# (.DS_Store is intentionally unanchored — Finder creates them everywhere.)
EXCLUDES=(
# Dotfiles and infra — top-level only
"/.claude/"
"/.claude-plugin/"
"/.codex/"
"/.cursor-plugin/"
"/.git/"
"/.gitattributes"
"/.github/"
"/.gitignore"
"/.opencode/"
"/.version-bump.json"
"/.worktrees/"
".DS_Store"
# Root ceremony files
"/AGENTS.md"
"/CHANGELOG.md"
"/CLAUDE.md"
"/GEMINI.md"
"/RELEASE-NOTES.md"
"/gemini-extension.json"
"/package.json"
# Directories not shipped by canonical Codex plugins
"/commands/"
"/docs/"
"/hooks/"
"/lib/"
"/scripts/"
"/tests/"
"/tmp/"
# Codex-only paths — managed outside rsync
"/.codex-plugin/"
"/assets/"
)
# =============================================================================
# Generated overlay file
# =============================================================================
# Writes the Codex plugin manifest to "$1" with the given upstream version.
# Args: dest_path, version
generate_plugin_json() {
local dest="$1"
local version="$2"
mkdir -p "$(dirname "$dest")"
cat > "$dest" <<EOF
{
"name": "superpowers",
"version": "$version",
"description": "An agentic skills framework & software development methodology that works: planning, TDD, debugging, and collaboration workflows.",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com",
"url": "https://github.com/obra"
},
"homepage": "https://github.com/obra/superpowers",
"repository": "https://github.com/obra/superpowers",
"license": "MIT",
"keywords": [
"brainstorming",
"subagent-driven-development",
"skills",
"planning",
"tdd",
"debugging",
"code-review",
"workflow"
],
"skills": "./skills/",
"interface": {
"displayName": "Superpowers",
"shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents",
"longDescription": "Use Superpowers to guide agent work through brainstorming, implementation planning, test-driven development, systematic debugging, parallel execution, code review, and finish-the-branch workflows.",
"developerName": "Jesse Vincent",
"category": "Coding",
"capabilities": [
"Interactive",
"Read",
"Write"
],
"defaultPrompt": [
"I've got an idea for something I'd like to build.",
"Let's add a feature to this project."
],
"brandColor": "#F59E0B",
"composerIcon": "./assets/superpowers-small.svg",
"logo": "./assets/app-icon.png",
"screenshots": []
}
}
EOF
}
# =============================================================================
# Args
# =============================================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
UPSTREAM="$(cd "$SCRIPT_DIR/.." && pwd)"
BASE="$DEFAULT_BASE"
DRY_RUN=0
YES=0
LOCAL_CHECKOUT=""
BOOTSTRAP=0
ASSETS_SRC=""
usage() {
sed -n 's/^# \{0,1\}//;2,27p' "$0"
exit "${1:-0}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--dry-run) DRY_RUN=1; shift ;;
-y|--yes) YES=1; shift ;;
--local) LOCAL_CHECKOUT="$2"; shift 2 ;;
--base) BASE="$2"; shift 2 ;;
--bootstrap) BOOTSTRAP=1; shift ;;
--assets-src) ASSETS_SRC="$2"; shift 2 ;;
-h|--help) usage 0 ;;
*) echo "Unknown arg: $1" >&2; usage 2 ;;
esac
done
# =============================================================================
# Preflight
# =============================================================================
die() { echo "ERROR: $*" >&2; exit 1; }
command -v rsync >/dev/null || die "rsync not found in PATH"
command -v git >/dev/null || die "git not found in PATH"
command -v gh >/dev/null || die "gh not found — install GitHub CLI"
command -v python3 >/dev/null || die "python3 not found in PATH"
gh auth status >/dev/null 2>&1 || die "gh not authenticated — run 'gh auth login'"
[[ -d "$UPSTREAM/.git" ]] || die "upstream '$UPSTREAM' is not a git checkout"
[[ -f "$UPSTREAM/package.json" ]] || die "upstream has no package.json — cannot read version"
# Bootstrap-mode validation
if [[ $BOOTSTRAP -eq 1 ]]; then
[[ -n "$ASSETS_SRC" ]] || die "--bootstrap requires --assets-src <path>"
ASSETS_SRC="$(cd "$ASSETS_SRC" 2>/dev/null && pwd)" || die "assets source '$ASSETS_SRC' is not a directory"
[[ -f "$ASSETS_SRC/PrimeRadiant_Favicon.svg" ]] || die "assets source missing PrimeRadiant_Favicon.svg"
[[ -f "$ASSETS_SRC/PrimeRadiant_Favicon.png" ]] || die "assets source missing PrimeRadiant_Favicon.png"
fi
# Read the upstream version from package.json
UPSTREAM_VERSION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "$UPSTREAM/package.json")"
[[ -n "$UPSTREAM_VERSION" ]] || die "could not read 'version' from upstream package.json"
UPSTREAM_BRANCH="$(cd "$UPSTREAM" && git branch --show-current)"
UPSTREAM_SHA="$(cd "$UPSTREAM" && git rev-parse HEAD)"
UPSTREAM_SHORT="$(cd "$UPSTREAM" && git rev-parse --short HEAD)"
confirm() {
[[ $YES -eq 1 ]] && return 0
read -rp "$1 [y/N] " ans
[[ "$ans" == "y" || "$ans" == "Y" ]]
}
if [[ "$UPSTREAM_BRANCH" != "main" ]]; then
echo "WARNING: upstream is on '$UPSTREAM_BRANCH', not 'main'"
confirm "Sync from '$UPSTREAM_BRANCH' anyway?" || exit 1
fi
UPSTREAM_STATUS="$(cd "$UPSTREAM" && git status --porcelain)"
if [[ -n "$UPSTREAM_STATUS" ]]; then
echo "WARNING: upstream has uncommitted changes:"
echo "$UPSTREAM_STATUS" | sed 's/^/ /'
echo "Sync will use working-tree state, not HEAD ($UPSTREAM_SHORT)."
confirm "Continue anyway?" || exit 1
fi
# =============================================================================
# Prepare destination (clone fork fresh, or use --local)
# =============================================================================
CLEANUP_DIR=""
cleanup() {
[[ -n "$CLEANUP_DIR" ]] && rm -rf "$CLEANUP_DIR"
}
trap cleanup EXIT
if [[ -n "$LOCAL_CHECKOUT" ]]; then
DEST_REPO="$(cd "$LOCAL_CHECKOUT" && pwd)"
[[ -d "$DEST_REPO/.git" ]] || die "--local path '$DEST_REPO' is not a git checkout"
else
echo "Cloning $FORK..."
CLEANUP_DIR="$(mktemp -d)"
DEST_REPO="$CLEANUP_DIR/openai-codex-plugins"
gh repo clone "$FORK" "$DEST_REPO" >/dev/null
fi
DEST="$DEST_REPO/$DEST_REL"
# Checkout base branch
cd "$DEST_REPO"
git checkout -q "$BASE" 2>/dev/null || die "base branch '$BASE' doesn't exist in $FORK"
# Plugin-existence check depends on mode
if [[ $BOOTSTRAP -eq 1 ]]; then
[[ ! -d "$DEST" ]] || die "--bootstrap but base branch '$BASE' already has '$DEST_REL/' — use normal sync instead"
mkdir -p "$DEST"
else
[[ -d "$DEST" ]] || die "base branch '$BASE' has no '$DEST_REL/' — use --bootstrap + --assets-src, or pass --base <branch>"
fi
# =============================================================================
# Create sync branch
# =============================================================================
TIMESTAMP="$(date -u +%Y%m%d-%H%M%S)"
if [[ $BOOTSTRAP -eq 1 ]]; then
SYNC_BRANCH="bootstrap/superpowers-${UPSTREAM_SHORT}-${TIMESTAMP}"
else
SYNC_BRANCH="sync/superpowers-${UPSTREAM_SHORT}-${TIMESTAMP}"
fi
git checkout -q -b "$SYNC_BRANCH"
# =============================================================================
# Build rsync args
# =============================================================================
RSYNC_ARGS=(-av --delete)
for pat in "${EXCLUDES[@]}"; do RSYNC_ARGS+=(--exclude="$pat"); done
# =============================================================================
# Dry run preview (always shown)
# =============================================================================
echo ""
echo "Upstream: $UPSTREAM ($UPSTREAM_BRANCH @ $UPSTREAM_SHORT)"
echo "Version: $UPSTREAM_VERSION"
echo "Fork: $FORK"
echo "Base: $BASE"
echo "Branch: $SYNC_BRANCH"
if [[ $BOOTSTRAP -eq 1 ]]; then
echo "Mode: BOOTSTRAP (creating initial plugin from scratch)"
echo "Assets: $ASSETS_SRC"
fi
echo ""
echo "=== Preview (rsync --dry-run) ==="
rsync "${RSYNC_ARGS[@]}" --dry-run --itemize-changes "$UPSTREAM/" "$DEST/"
echo "=== End preview ==="
echo ""
echo "Overlay file (.codex-plugin/plugin.json) will be regenerated with"
echo "version $UPSTREAM_VERSION regardless of rsync output."
if [[ $BOOTSTRAP -eq 1 ]]; then
echo "Assets (superpowers-small.svg, app-icon.png) will be seeded from:"
echo " $ASSETS_SRC"
fi
if [[ $DRY_RUN -eq 1 ]]; then
echo ""
echo "Dry run only. Nothing was changed or pushed."
exit 0
fi
# =============================================================================
# Apply
# =============================================================================
echo ""
confirm "Apply changes, push branch, and open PR?" || { echo "Aborted."; exit 1; }
echo ""
echo "Syncing upstream content..."
rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$DEST/"
if [[ $BOOTSTRAP -eq 1 ]]; then
echo "Seeding brand assets..."
mkdir -p "$DEST/assets"
cp "$ASSETS_SRC/PrimeRadiant_Favicon.svg" "$DEST/assets/superpowers-small.svg"
cp "$ASSETS_SRC/PrimeRadiant_Favicon.png" "$DEST/assets/app-icon.png"
fi
echo "Regenerating overlay file..."
generate_plugin_json "$DEST/.codex-plugin/plugin.json" "$UPSTREAM_VERSION"
# Bail early if nothing actually changed
cd "$DEST_REPO"
if [[ -z "$(git status --porcelain "$DEST_REL")" ]]; then
echo "No changes — embedded plugin was already in sync with upstream $UPSTREAM_SHORT (v$UPSTREAM_VERSION)."
exit 0
fi
# =============================================================================
# Commit, push, open PR
# =============================================================================
git add "$DEST_REL"
if [[ $BOOTSTRAP -eq 1 ]]; then
COMMIT_TITLE="bootstrap superpowers v$UPSTREAM_VERSION from upstream main @ $UPSTREAM_SHORT"
PR_BODY="Initial bootstrap of the superpowers plugin from upstream \`main\` @ \`$UPSTREAM_SHORT\` (v$UPSTREAM_VERSION).
Creates \`plugins/superpowers/\` from scratch: upstream content via rsync, \`.codex-plugin/plugin.json\` regenerated inline, brand assets seeded from a local Brand Assets directory.
Run via: \`scripts/sync-to-codex-plugin.sh --bootstrap --assets-src <path>\`
Upstream commit: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
This is a one-time bootstrap. Subsequent syncs will be normal (non-bootstrap) runs and will not touch the \`assets/\` directory."
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).
Run via: \`scripts/sync-to-codex-plugin.sh\`
Upstream commit: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
Running the sync tool again against the same upstream SHA should produce a PR with an identical diff — use that to verify the tool is behaving."
fi
git commit --quiet -m "$COMMIT_TITLE
Automated sync via scripts/sync-to-codex-plugin.sh
Upstream: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
Branch: $SYNC_BRANCH"
echo "Pushing $SYNC_BRANCH to $FORK..."
git push -u origin "$SYNC_BRANCH" --quiet
echo "Opening PR..."
PR_URL="$(gh pr create \
--repo "$FORK" \
--base "$BASE" \
--head "$SYNC_BRANCH" \
--title "$COMMIT_TITLE" \
--body "$PR_BODY")"
PR_NUM="${PR_URL##*/}"
DIFF_URL="https://github.com/$FORK/pull/$PR_NUM/files"
echo ""
echo "PR opened: $PR_URL"
echo "Diff view: $DIFF_URL"

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 self-review**quick inline check for placeholders, contradictions, ambiguity, scope (see below)
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)
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,7 +43,8 @@ digraph brainstorming {
"Present design sections" [shape=box];
"User approves design?" [shape=diamond];
"Write design doc" [shape=box];
"Spec self-review\n(fix inline)" [shape=box];
"Spec review loop" [shape=box];
"Spec review passed?" [shape=diamond];
"User reviews spec?" [shape=diamond];
"Invoke writing-plans skill" [shape=doublecircle];
@@ -56,8 +57,10 @@ 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 self-review\n(fix inline)";
"Spec self-review\n(fix inline)" -> "User reviews spec?";
"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"];
"User reviews spec?" -> "Write design doc" [label="changes requested"];
"User reviews spec?" -> "Invoke writing-plans skill" [label="approved"];
}
@@ -113,15 +116,12 @@ digraph brainstorming {
- Use elements-of-style:writing-clearly-and-concisely skill if available
- Commit the design document to git
**Spec Self-Review:**
After writing the spec document, look at it with fresh eyes:
**Spec Review Loop:**
After writing the spec document:
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.
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
**User Review Gate:**
After the spec review loop passes, ask the user to review the written spec before proceeding:

View File

@@ -76,10 +76,8 @@ function decodeFrame(buffer) {
const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383));
const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1';
const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST);
const SESSION_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
const CONTENT_DIR = path.join(SESSION_DIR, 'content');
const STATE_DIR = path.join(SESSION_DIR, 'state');
let ownerPid = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null;
const SCREEN_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
const OWNER_PID = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null;
const MIME_TYPES = {
'.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
@@ -114,10 +112,10 @@ function wrapInFrame(content) {
}
function getNewestScreen() {
const files = fs.readdirSync(CONTENT_DIR)
const files = fs.readdirSync(SCREEN_DIR)
.filter(f => f.endsWith('.html'))
.map(f => {
const fp = path.join(CONTENT_DIR, f);
const fp = path.join(SCREEN_DIR, f);
return { path: fp, mtime: fs.statSync(fp).mtime.getTime() };
})
.sort((a, b) => b.mtime - a.mtime);
@@ -144,7 +142,7 @@ function handleRequest(req, res) {
res.end(html);
} else if (req.method === 'GET' && req.url.startsWith('/files/')) {
const fileName = req.url.slice(7);
const filePath = path.join(CONTENT_DIR, path.basename(fileName));
const filePath = path.join(SCREEN_DIR, path.basename(fileName));
if (!fs.existsSync(filePath)) {
res.writeHead(404);
res.end('Not found');
@@ -232,7 +230,7 @@ function handleMessage(text) {
touchActivity();
console.log(JSON.stringify({ source: 'user-event', ...event }));
if (event.choice) {
const eventsFile = path.join(STATE_DIR, 'events');
const eventsFile = path.join(SCREEN_DIR, '.events');
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
}
}
@@ -260,33 +258,32 @@ const debounceTimers = new Map();
// ========== Server Startup ==========
function startServer() {
if (!fs.existsSync(CONTENT_DIR)) fs.mkdirSync(CONTENT_DIR, { recursive: true });
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
if (!fs.existsSync(SCREEN_DIR)) fs.mkdirSync(SCREEN_DIR, { recursive: true });
// Track known files to distinguish new screens from updates.
// macOS fs.watch reports 'rename' for both new files and overwrites,
// so we can't rely on eventType alone.
const knownFiles = new Set(
fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html'))
fs.readdirSync(SCREEN_DIR).filter(f => f.endsWith('.html'))
);
const server = http.createServer(handleRequest);
server.on('upgrade', handleUpgrade);
const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => {
const watcher = fs.watch(SCREEN_DIR, (eventType, filename) => {
if (!filename || !filename.endsWith('.html')) return;
if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename));
debounceTimers.set(filename, setTimeout(() => {
debounceTimers.delete(filename);
const filePath = path.join(CONTENT_DIR, filename);
const filePath = path.join(SCREEN_DIR, filename);
if (!fs.existsSync(filePath)) return; // file was deleted
touchActivity();
if (!knownFiles.has(filename)) {
knownFiles.add(filename);
const eventsFile = path.join(STATE_DIR, 'events');
const eventsFile = path.join(SCREEN_DIR, '.events');
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
} else {
@@ -300,10 +297,10 @@ function startServer() {
function shutdown(reason) {
console.log(JSON.stringify({ type: 'server-stopped', reason }));
const infoFile = path.join(STATE_DIR, 'server-info');
const infoFile = path.join(SCREEN_DIR, '.server-info');
if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile);
fs.writeFileSync(
path.join(STATE_DIR, 'server-stopped'),
path.join(SCREEN_DIR, '.server-stopped'),
JSON.stringify({ reason, timestamp: Date.now() }) + '\n'
);
watcher.close();
@@ -312,8 +309,8 @@ function startServer() {
}
function ownerAlive() {
if (!ownerPid) return true;
try { process.kill(ownerPid, 0); return true; } catch (e) { return e.code === 'EPERM'; }
if (!OWNER_PID) return true;
try { process.kill(OWNER_PID, 0); return true; } catch (e) { return false; }
}
// Check every 60s: exit if owner process died or idle for 30 minutes
@@ -323,27 +320,14 @@ function startServer() {
}, 60 * 1000);
lifecycleCheck.unref();
// Validate owner PID at startup. If it's already dead, the PID resolution
// was wrong (common on WSL, Tailscale SSH, and cross-user scenarios).
// Disable monitoring and rely on the idle timeout instead.
if (ownerPid) {
try { process.kill(ownerPid, 0); }
catch (e) {
if (e.code !== 'EPERM') {
console.log(JSON.stringify({ type: 'owner-pid-invalid', pid: ownerPid, reason: 'dead at startup' }));
ownerPid = null;
}
}
}
server.listen(PORT, HOST, () => {
const info = JSON.stringify({
type: 'server-started', port: Number(PORT), host: HOST,
url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT,
screen_dir: CONTENT_DIR, state_dir: STATE_DIR
screen_dir: SCREEN_DIR
});
console.log(info);
fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info + '\n');
fs.writeFileSync(path.join(SCREEN_DIR, '.server-info'), info + '\n');
});
}

View File

@@ -78,17 +78,16 @@ fi
SESSION_ID="$$-$(date +%s)"
if [[ -n "$PROJECT_DIR" ]]; then
SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
SCREEN_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
else
SESSION_DIR="/tmp/brainstorm-${SESSION_ID}"
SCREEN_DIR="/tmp/brainstorm-${SESSION_ID}"
fi
STATE_DIR="${SESSION_DIR}/state"
PID_FILE="${STATE_DIR}/server.pid"
LOG_FILE="${STATE_DIR}/server.log"
PID_FILE="${SCREEN_DIR}/.server.pid"
LOG_FILE="${SCREEN_DIR}/.server.log"
# Create fresh session directory with content and state peers
mkdir -p "${SESSION_DIR}/content" "$STATE_DIR"
# Create fresh session directory
mkdir -p "$SCREEN_DIR"
# Kill any existing server
if [[ -f "$PID_FILE" ]]; then
@@ -107,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="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs
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="$SESSION_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="$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

@@ -1,20 +1,19 @@
#!/usr/bin/env bash
# Stop the brainstorm server and clean up
# Usage: stop-server.sh <session_dir>
# Usage: stop-server.sh <screen_dir>
#
# Kills the server process. Only deletes session directory if it's
# under /tmp (ephemeral). Persistent directories (.superpowers/) are
# kept so mockups can be reviewed later.
SESSION_DIR="$1"
SCREEN_DIR="$1"
if [[ -z "$SESSION_DIR" ]]; then
echo '{"error": "Usage: stop-server.sh <session_dir>"}'
if [[ -z "$SCREEN_DIR" ]]; then
echo '{"error": "Usage: stop-server.sh <screen_dir>"}'
exit 1
fi
STATE_DIR="${SESSION_DIR}/state"
PID_FILE="${STATE_DIR}/server.pid"
PID_FILE="${SCREEN_DIR}/.server.pid"
if [[ -f "$PID_FILE" ]]; then
pid=$(cat "$PID_FILE")
@@ -43,11 +42,11 @@ if [[ -f "$PID_FILE" ]]; then
exit 1
fi
rm -f "$PID_FILE" "${STATE_DIR}/server.log"
rm -f "$PID_FILE" "${SCREEN_DIR}/.server.log"
# Only delete ephemeral /tmp directories
if [[ "$SESSION_DIR" == /tmp/* ]]; then
rm -rf "$SESSION_DIR"
if [[ "$SCREEN_DIR" == /tmp/* ]]; then
rm -rf "$SCREEN_DIR"
fi
echo '{"status": "stopped"}'

View File

@@ -26,7 +26,7 @@ A question *about* a UI topic is not automatically a visual question. "What kind
## How It Works
The server watches a directory for HTML files and serves the newest one to the browser. You write HTML content to `screen_dir`, the user sees it in their browser and can click to select options. Selections are recorded to `state_dir/events` that you read on your next turn.
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.
**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,13 +37,12 @@ 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
# Returns: {"type":"server-started","port":52341,"url":"http://localhost:52341",
# "screen_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000/content",
# "state_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000/state"}
# "screen_dir":"/path/to/project/.superpowers/brainstorm/12345-1706000000"}
```
Save `screen_dir` and `state_dir` from the response. Tell user to open the URL.
Save `screen_dir` from the response. Tell user to open the URL.
**Finding connection info:** The server writes its startup JSON to `$STATE_DIR/server-info`. If you launched the server in the background and didn't capture stdout, read that file to get the URL and port. When using `--project-dir`, check `<project>/.superpowers/brainstorm/` for the session directory.
**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.
**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.
@@ -62,7 +61,7 @@ scripts/start-server.sh --project-dir /path/to/project
# across conversation turns.
scripts/start-server.sh --project-dir /path/to/project
```
When calling this via the Bash tool, set `run_in_background: true`. Then read `$STATE_DIR/server-info` on the next turn to get the URL and port.
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
@@ -94,7 +93,7 @@ Use `--url-host` to control what hostname is printed in the returned URL JSON.
## The Loop
1. **Check server is alive**, then **write HTML** to a new file in `screen_dir`:
- Before each write, check that `$STATE_DIR/server-info` exists. If it doesn't (or `$STATE_DIR/server-stopped` exists), the server has shut down — restart it with `start-server.sh` before continuing. The server auto-exits after 30 minutes of inactivity.
- 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.
- Use semantic filenames: `platform.html`, `visual-style.html`, `layout.html`
- **Never reuse filenames** — each screen gets a fresh file
- Use Write tool — **never use cat/heredoc** (dumps noise into terminal)
@@ -106,9 +105,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."
3. **On your next turn** — after the user responds in the terminal:
- Read `$STATE_DIR/events` if it exists — this contains the user's browser interactions (clicks, selections) as JSON lines
- Read `$SCREEN_DIR/.events` if it exists — this contains the user's browser interactions (clicks, selections) as JSON lines
- Merge with the user's terminal text to get the full picture
- The terminal message is the primary feedback; `state_dir/events` provides structured interaction data
- The terminal message is the primary feedback; `.events` provides structured interaction data
4. **Iterate or advance** — if feedback changes current screen, write a new file (e.g., `layout-v2.html`). Only move to the next question when the current step is validated.
@@ -245,7 +244,7 @@ The frame template provides these CSS classes for your content:
## Browser Events Format
When the user clicks options in the browser, their interactions are recorded to `$STATE_DIR/events` (one JSON object per line). The file is cleared automatically when you push a new screen.
When the user clicks options in the browser, their interactions are recorded to `$SCREEN_DIR/.events` (one JSON object per line). The file is cleared automatically when you push a new screen.
```jsonl
{"type":"click","choice":"a","text":"Option A - Simple Layout","timestamp":1706000101}
@@ -255,7 +254,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.
If `$STATE_DIR/events` doesn't exist, the user didn't interact with the browser — use only their terminal text.
If `.events` doesn't exist, the user didn't interact with the browser — use only their terminal text.
## Design Tips
@@ -276,7 +275,7 @@ If `$STATE_DIR/events` doesn't exist, the user didn't interact with the browser
## Cleaning Up
```bash
scripts/stop-server.sh $SESSION_DIR
scripts/stop-server.sh $SCREEN_DIR
```
If the session used `--project-dir`, mockup files persist in `.superpowers/brainstorm/` for later reference. Only `/tmp` sessions get deleted on stop.

View File

@@ -29,15 +29,13 @@ If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "alw
**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
**In Copilot CLI:** Use the `skill` tool. Skills are auto-discovered from installed plugins. The `skill` tool works the same as Claude Code's `Skill` tool.
**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
**In other environments:** Check your platform's documentation for how skills are loaded.
## Platform Adaptation
Skills use Claude Code tool names. Non-CC platforms: see `references/copilot-tools.md` (Copilot CLI), `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
# Using Skills

View File

@@ -1,52 +0,0 @@
# Copilot CLI Tool Mapping
Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
| Skill references | Copilot CLI equivalent |
|-----------------|----------------------|
| `Read` (file reading) | `view` |
| `Write` (file creation) | `create` |
| `Edit` (file editing) | `edit` |
| `Bash` (run commands) | `bash` |
| `Grep` (search file content) | `grep` |
| `Glob` (search files by name) | `glob` |
| `Skill` tool (invoke a skill) | `skill` |
| `WebFetch` | `web_fetch` |
| `Task` tool (dispatch subagent) | `task` (see [Agent types](#agent-types)) |
| Multiple `Task` calls (parallel) | Multiple `task` calls |
| Task status/output | `read_agent`, `list_agents` |
| `TodoWrite` (task tracking) | `sql` with built-in `todos` table |
| `WebSearch` | No equivalent — use `web_fetch` with a search engine URL |
| `EnterPlanMode` / `ExitPlanMode` | No equivalent — stay in the main session |
## Agent types
Copilot CLI's `task` tool accepts an `agent_type` parameter:
| Claude Code agent | Copilot CLI equivalent |
|-------------------|----------------------|
| `general-purpose` | `"general-purpose"` |
| `Explore` | `"explore"` |
| Named plugin agents (e.g. `superpowers:code-reviewer`) | Discovered automatically from installed plugins |
## Async shell sessions
Copilot CLI supports persistent async shell sessions, which have no direct Claude Code equivalent:
| Tool | Purpose |
|------|---------|
| `bash` with `async: true` | Start a long-running command in the background |
| `write_bash` | Send input to a running async session |
| `read_bash` | Read output from an async session |
| `stop_bash` | Terminate an async session |
| `list_bash` | List all active shell sessions |
## Additional Copilot CLI tools
| Tool | Purpose |
|------|---------|
| `store_memory` | Persist facts about the codebase for future sessions |
| `report_intent` | Update the UI status line with current intent |
| `sql` | Query the session's SQLite database (todos, metadata) |
| `fetch_copilot_cli_documentation` | Look up Copilot CLI documentation |
| GitHub MCP tools (`github-mcp-server-*`) | Native GitHub API access (issues, PRs, code search) |

View File

@@ -103,33 +103,26 @@ 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 every step — if a step changes code, show the code
- Complete code in plan (not "add validation")
- Exact commands with expected output
- Reference relevant skills with @ syntax
- DRY, YAGNI, TDD, frequent commits
## Self-Review
## Plan Review Loop
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.
After writing the complete plan:
**1. Spec coverage:** Skim each section/requirement in the spec. Can you point to a task that implements it? List any gaps.
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
**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.
**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
## Execution Handoff

View File

@@ -93,7 +93,7 @@ skills/
## SKILL.md Structure
**Frontmatter (YAML):**
- Two required fields: `name` and `description` (see [agentskills.io/specification](https://agentskills.io/specification) for all supported fields)
- Only two fields supported: `name` and `description`
- Max 1024 characters total
- `name`: Use letters, numbers, and hyphens only (no parentheses, special chars)
- `description`: Third-person, describes ONLY when to use (NOT what it does)
@@ -604,7 +604,7 @@ Deploying untested skills = deploying untested code. It's a violation of quality
**GREEN Phase - Write Minimal Skill:**
- [ ] Name uses only letters, numbers, hyphens (no parentheses/special chars)
- [ ] YAML frontmatter with required `name` and `description` fields (max 1024 chars; see [spec](https://agentskills.io/specification))
- [ ] YAML frontmatter with only name and description (max 1024 chars)
- [ ] Description starts with "Use when..." and includes specific triggers/symptoms
- [ ] Description written in third person
- [ ] Keywords throughout for search (errors, symptoms, tools)

View File

@@ -144,7 +144,7 @@ What works perfectly for Opus might need more detail for Haiku. If you plan to u
## Skill structure
<Note>
**YAML Frontmatter**: The SKILL.md frontmatter requires two fields:
**YAML Frontmatter**: The SKILL.md frontmatter supports two fields:
* `name` - Human-readable name of the Skill (64 characters maximum)
* `description` - One-line description of what the Skill does and when to use it (1024 characters maximum)
@@ -1092,7 +1092,7 @@ reader = PdfReader("file.pdf")
### YAML frontmatter requirements
The SKILL.md frontmatter 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.
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.
### Token budgets

View File

@@ -18,8 +18,6 @@ const assert = require('assert');
const SERVER_PATH = path.join(__dirname, '../../skills/brainstorming/scripts/server.cjs');
const TEST_PORT = 3334;
const TEST_DIR = '/tmp/brainstorm-test';
const CONTENT_DIR = path.join(TEST_DIR, 'content');
const STATE_DIR = path.join(TEST_DIR, 'state');
function cleanup() {
if (fs.existsSync(TEST_DIR)) {
@@ -71,6 +69,7 @@ async function waitForServer(server) {
async function runTests() {
cleanup();
fs.mkdirSync(TEST_DIR, { recursive: true });
const server = startServer();
let stdoutAccum = '';
@@ -104,14 +103,12 @@ async function runTests() {
return Promise.resolve();
});
await test('writes server-info to state/', () => {
const infoPath = path.join(STATE_DIR, 'server-info');
assert(fs.existsSync(infoPath), 'state/server-info should exist');
await test('writes .server-info file', () => {
const infoPath = path.join(TEST_DIR, '.server-info');
assert(fs.existsSync(infoPath), '.server-info should exist');
const info = JSON.parse(fs.readFileSync(infoPath, 'utf-8').trim());
assert.strictEqual(info.type, 'server-started');
assert.strictEqual(info.port, TEST_PORT);
assert.strictEqual(info.screen_dir, CONTENT_DIR, 'screen_dir should point to content/');
assert.strictEqual(info.state_dir, STATE_DIR, 'state_dir should point to state/');
return Promise.resolve();
});
@@ -121,7 +118,7 @@ async function runTests() {
await test('serves waiting page when no screens exist', async () => {
const res = await fetch(`http://localhost:${TEST_PORT}/`);
assert.strictEqual(res.status, 200);
assert(res.body.includes('Waiting for the agent'), 'Should show waiting message');
assert(res.body.includes('Waiting for Claude'), 'Should show waiting message');
});
await test('injects helper.js into waiting page', async () => {
@@ -138,7 +135,7 @@ async function runTests() {
await test('serves full HTML documents as-is (not wrapped)', async () => {
const fullDoc = '<!DOCTYPE html>\n<html><head><title>Custom</title></head><body><h1>Custom Page</h1></body></html>';
fs.writeFileSync(path.join(CONTENT_DIR, 'full-doc.html'), fullDoc);
fs.writeFileSync(path.join(TEST_DIR, 'full-doc.html'), fullDoc);
await sleep(300);
const res = await fetch(`http://localhost:${TEST_PORT}/`);
@@ -149,7 +146,7 @@ async function runTests() {
await test('wraps content fragments in frame template', async () => {
const fragment = '<h2>Pick a layout</h2>\n<div class="options"><div class="option" data-choice="a"><div class="letter">A</div></div></div>';
fs.writeFileSync(path.join(CONTENT_DIR, 'fragment.html'), fragment);
fs.writeFileSync(path.join(TEST_DIR, 'fragment.html'), fragment);
await sleep(300);
const res = await fetch(`http://localhost:${TEST_PORT}/`);
@@ -160,9 +157,9 @@ async function runTests() {
});
await test('serves newest file by mtime', async () => {
fs.writeFileSync(path.join(CONTENT_DIR, 'older.html'), '<h2>Older</h2>');
fs.writeFileSync(path.join(TEST_DIR, 'older.html'), '<h2>Older</h2>');
await sleep(100);
fs.writeFileSync(path.join(CONTENT_DIR, 'newer.html'), '<h2>Newer</h2>');
fs.writeFileSync(path.join(TEST_DIR, 'newer.html'), '<h2>Newer</h2>');
await sleep(300);
const res = await fetch(`http://localhost:${TEST_PORT}/`);
@@ -171,7 +168,7 @@ async function runTests() {
await test('ignores non-html files for serving', async () => {
// Write a newer non-HTML file — should still serve newest .html
fs.writeFileSync(path.join(CONTENT_DIR, 'data.json'), '{"not": "html"}');
fs.writeFileSync(path.join(TEST_DIR, 'data.json'), '{"not": "html"}');
await sleep(300);
const res = await fetch(`http://localhost:${TEST_PORT}/`);
@@ -209,9 +206,9 @@ async function runTests() {
ws.close();
});
await test('writes choice events to state/events', async () => {
await test('writes choice events to .events file', async () => {
// Clean up events from prior tests
const eventsFile = path.join(STATE_DIR, 'events');
const eventsFile = path.join(TEST_DIR, '.events');
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
@@ -228,8 +225,8 @@ async function runTests() {
ws.close();
});
await test('does NOT write non-choice events to state/events', async () => {
const eventsFile = path.join(STATE_DIR, 'events');
await test('does NOT write non-choice events to .events file', async () => {
const eventsFile = path.join(TEST_DIR, '.events');
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
@@ -260,7 +257,7 @@ async function runTests() {
if (JSON.parse(data.toString()).type === 'reload') ws2Reload = true;
});
fs.writeFileSync(path.join(CONTENT_DIR, 'multi-client.html'), '<h2>Multi</h2>');
fs.writeFileSync(path.join(TEST_DIR, 'multi-client.html'), '<h2>Multi</h2>');
await sleep(500);
assert(ws1Reload, 'Client 1 should receive reload');
@@ -276,7 +273,7 @@ async function runTests() {
await sleep(100);
// This should not throw even though ws1 is closed
fs.writeFileSync(path.join(CONTENT_DIR, 'after-close.html'), '<h2>After</h2>');
fs.writeFileSync(path.join(TEST_DIR, 'after-close.html'), '<h2>After</h2>');
await sleep(300);
// If we got here without error, the test passes
});
@@ -307,7 +304,7 @@ async function runTests() {
if (JSON.parse(data.toString()).type === 'reload') gotReload = true;
});
fs.writeFileSync(path.join(CONTENT_DIR, 'watch-new.html'), '<h2>New</h2>');
fs.writeFileSync(path.join(TEST_DIR, 'watch-new.html'), '<h2>New</h2>');
await sleep(500);
assert(gotReload, 'Should send reload on new file');
@@ -315,7 +312,7 @@ async function runTests() {
});
await test('sends reload on .html file change', async () => {
const filePath = path.join(CONTENT_DIR, 'watch-change.html');
const filePath = path.join(TEST_DIR, 'watch-change.html');
fs.writeFileSync(filePath, '<h2>Original</h2>');
await sleep(500);
@@ -343,35 +340,35 @@ async function runTests() {
if (JSON.parse(data.toString()).type === 'reload') gotReload = true;
});
fs.writeFileSync(path.join(CONTENT_DIR, 'data.txt'), 'not html');
fs.writeFileSync(path.join(TEST_DIR, 'data.txt'), 'not html');
await sleep(500);
assert(!gotReload, 'Should NOT reload for non-HTML files');
ws.close();
});
await test('clears state/events on new screen', async () => {
// Create an events file
const eventsFile = path.join(STATE_DIR, 'events');
await test('clears .events on new screen', async () => {
// Create an .events file
const eventsFile = path.join(TEST_DIR, '.events');
fs.writeFileSync(eventsFile, '{"choice":"a"}\n');
assert(fs.existsSync(eventsFile));
fs.writeFileSync(path.join(CONTENT_DIR, 'clear-events.html'), '<h2>New screen</h2>');
fs.writeFileSync(path.join(TEST_DIR, 'clear-events.html'), '<h2>New screen</h2>');
await sleep(500);
assert(!fs.existsSync(eventsFile), 'state/events should be cleared on new screen');
assert(!fs.existsSync(eventsFile), '.events should be cleared on new screen');
});
await test('logs screen-added on new file', async () => {
stdoutAccum = '';
fs.writeFileSync(path.join(CONTENT_DIR, 'log-test.html'), '<h2>Log</h2>');
fs.writeFileSync(path.join(TEST_DIR, 'log-test.html'), '<h2>Log</h2>');
await sleep(500);
assert(stdoutAccum.includes('screen-added'), 'Should log screen-added');
});
await test('logs screen-updated on file change', async () => {
const filePath = path.join(CONTENT_DIR, 'log-update.html');
const filePath = path.join(TEST_DIR, 'log-update.html');
fs.writeFileSync(filePath, '<h2>V1</h2>');
await sleep(500);

View File

@@ -7,39 +7,30 @@ set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
# Create temp home directory for isolation
export TEST_HOME
TEST_HOME=$(mktemp -d)
export TEST_HOME=$(mktemp -d)
export HOME="$TEST_HOME"
export XDG_CONFIG_HOME="$TEST_HOME/.config"
export OPENCODE_CONFIG_DIR="$TEST_HOME/.config/opencode"
# Standard install layout:
# $OPENCODE_CONFIG_DIR/superpowers/ ← package root
# $OPENCODE_CONFIG_DIR/superpowers/skills/ ← skills dir (../../skills from plugin)
# $OPENCODE_CONFIG_DIR/superpowers/.opencode/plugins/superpowers.js ← plugin file
# $OPENCODE_CONFIG_DIR/plugins/superpowers.js ← symlink OpenCode reads
# Install plugin to test location
mkdir -p "$HOME/.config/opencode/superpowers"
cp -r "$REPO_ROOT/lib" "$HOME/.config/opencode/superpowers/"
cp -r "$REPO_ROOT/skills" "$HOME/.config/opencode/superpowers/"
SUPERPOWERS_DIR="$OPENCODE_CONFIG_DIR/superpowers"
SUPERPOWERS_SKILLS_DIR="$SUPERPOWERS_DIR/skills"
SUPERPOWERS_PLUGIN_FILE="$SUPERPOWERS_DIR/.opencode/plugins/superpowers.js"
# Copy plugin directory
mkdir -p "$HOME/.config/opencode/superpowers/.opencode/plugins"
cp "$REPO_ROOT/.opencode/plugins/superpowers.js" "$HOME/.config/opencode/superpowers/.opencode/plugins/"
# Install skills
mkdir -p "$SUPERPOWERS_DIR"
cp -r "$REPO_ROOT/skills" "$SUPERPOWERS_DIR/"
# Install plugin
mkdir -p "$(dirname "$SUPERPOWERS_PLUGIN_FILE")"
cp "$REPO_ROOT/.opencode/plugins/superpowers.js" "$SUPERPOWERS_PLUGIN_FILE"
# Register plugin via symlink (what OpenCode actually reads)
mkdir -p "$OPENCODE_CONFIG_DIR/plugins"
ln -sf "$SUPERPOWERS_PLUGIN_FILE" "$OPENCODE_CONFIG_DIR/plugins/superpowers.js"
# Register plugin via symlink
mkdir -p "$HOME/.config/opencode/plugins"
ln -sf "$HOME/.config/opencode/superpowers/.opencode/plugins/superpowers.js" \
"$HOME/.config/opencode/plugins/superpowers.js"
# Create test skills in different locations for testing
# Personal test skill
mkdir -p "$OPENCODE_CONFIG_DIR/skills/personal-test"
cat > "$OPENCODE_CONFIG_DIR/skills/personal-test/SKILL.md" <<'EOF'
mkdir -p "$HOME/.config/opencode/skills/personal-test"
cat > "$HOME/.config/opencode/skills/personal-test/SKILL.md" <<'EOF'
---
name: personal-test
description: Test personal skill for verification
@@ -66,12 +57,9 @@ PROJECT_SKILL_MARKER_67890
EOF
echo "Setup complete: $TEST_HOME"
echo "OPENCODE_CONFIG_DIR: $OPENCODE_CONFIG_DIR"
echo "Superpowers dir: $SUPERPOWERS_DIR"
echo "Skills dir: $SUPERPOWERS_SKILLS_DIR"
echo "Plugin file: $SUPERPOWERS_PLUGIN_FILE"
echo "Plugin registered at: $OPENCODE_CONFIG_DIR/plugins/superpowers.js"
echo "Test project at: $TEST_HOME/test-project"
echo "Plugin installed to: $HOME/.config/opencode/superpowers/.opencode/plugins/superpowers.js"
echo "Plugin registered at: $HOME/.config/opencode/plugins/superpowers.js"
echo "Test project at: $TEST_HOME/test-project"
# Helper function for cleanup (call from tests or trap)
cleanup_test_env() {
@@ -83,6 +71,3 @@ cleanup_test_env() {
# Export for use in tests
export -f cleanup_test_env
export REPO_ROOT
export SUPERPOWERS_DIR
export SUPERPOWERS_SKILLS_DIR
export SUPERPOWERS_PLUGIN_FILE

View File

@@ -13,19 +13,17 @@ source "$SCRIPT_DIR/setup.sh"
# Trap to cleanup on exit
trap cleanup_test_env EXIT
plugin_link="$OPENCODE_CONFIG_DIR/plugins/superpowers.js"
# Test 1: Verify plugin file exists and is registered
echo "Test 1: Checking plugin registration..."
if [ -L "$plugin_link" ]; then
if [ -L "$HOME/.config/opencode/plugins/superpowers.js" ]; then
echo " [PASS] Plugin symlink exists"
else
echo " [FAIL] Plugin symlink not found at $plugin_link"
echo " [FAIL] Plugin symlink not found at $HOME/.config/opencode/plugins/superpowers.js"
exit 1
fi
# Verify symlink target exists
if [ -f "$(readlink -f "$plugin_link")" ]; then
if [ -f "$(readlink -f "$HOME/.config/opencode/plugins/superpowers.js")" ]; then
echo " [PASS] Plugin symlink target exists"
else
echo " [FAIL] Plugin symlink target does not exist"
@@ -34,44 +32,36 @@ fi
# Test 2: Verify skills directory is populated
echo "Test 2: Checking skills directory..."
skill_count=$(find "$SUPERPOWERS_SKILLS_DIR" -name "SKILL.md" | wc -l)
skill_count=$(find "$HOME/.config/opencode/superpowers/skills" -name "SKILL.md" | wc -l)
if [ "$skill_count" -gt 0 ]; then
echo " [PASS] Found $skill_count skills"
echo " [PASS] Found $skill_count skills installed"
else
echo " [FAIL] No skills found in $SUPERPOWERS_SKILLS_DIR"
echo " [FAIL] No skills found in installed location"
exit 1
fi
# Test 3: Check using-superpowers skill exists (critical for bootstrap)
echo "Test 3: Checking using-superpowers skill (required for bootstrap)..."
if [ -f "$SUPERPOWERS_SKILLS_DIR/using-superpowers/SKILL.md" ]; then
# Test 4: Check using-superpowers skill exists (critical for bootstrap)
echo "Test 4: Checking using-superpowers skill (required for bootstrap)..."
if [ -f "$HOME/.config/opencode/superpowers/skills/using-superpowers/SKILL.md" ]; then
echo " [PASS] using-superpowers skill exists"
else
echo " [FAIL] using-superpowers skill not found (required for bootstrap)"
exit 1
fi
# Test 4: Verify plugin JavaScript syntax (basic check)
echo "Test 4: Checking plugin JavaScript syntax..."
if node --check "$SUPERPOWERS_PLUGIN_FILE" 2>/dev/null; then
# Test 5: Verify plugin JavaScript syntax (basic check)
echo "Test 5: Checking plugin JavaScript syntax..."
plugin_file="$HOME/.config/opencode/superpowers/.opencode/plugins/superpowers.js"
if node --check "$plugin_file" 2>/dev/null; then
echo " [PASS] Plugin JavaScript syntax is valid"
else
echo " [FAIL] Plugin has JavaScript syntax errors"
exit 1
fi
# Test 5: Verify bootstrap text does not reference a hardcoded skills path
echo "Test 5: Checking bootstrap does not advertise a wrong skills path..."
if grep -q 'configDir}/skills/superpowers/' "$SUPERPOWERS_PLUGIN_FILE"; then
echo " [FAIL] Plugin still references old configDir skills path"
exit 1
else
echo " [PASS] Plugin does not advertise a misleading skills path"
fi
# Test 6: Verify personal test skill was created
echo "Test 6: Checking test fixtures..."
if [ -f "$OPENCODE_CONFIG_DIR/skills/personal-test/SKILL.md" ]; then
if [ -f "$HOME/.config/opencode/skills/personal-test/SKILL.md" ]; then
echo " [PASS] Personal test skill fixture created"
else
echo " [FAIL] Personal test skill fixture not found"

View File

@@ -18,8 +18,8 @@ trap cleanup_test_env EXIT
echo "Setting up priority test fixtures..."
# 1. Create in superpowers location (lowest priority)
mkdir -p "$SUPERPOWERS_SKILLS_DIR/priority-test"
cat > "$SUPERPOWERS_SKILLS_DIR/priority-test/SKILL.md" <<'EOF'
mkdir -p "$HOME/.config/opencode/superpowers/skills/priority-test"
cat > "$HOME/.config/opencode/superpowers/skills/priority-test/SKILL.md" <<'EOF'
---
name: priority-test
description: Superpowers version of priority test skill
@@ -32,8 +32,8 @@ PRIORITY_MARKER_SUPERPOWERS_VERSION
EOF
# 2. Create in personal location (medium priority)
mkdir -p "$OPENCODE_CONFIG_DIR/skills/priority-test"
cat > "$OPENCODE_CONFIG_DIR/skills/priority-test/SKILL.md" <<'EOF'
mkdir -p "$HOME/.config/opencode/skills/priority-test"
cat > "$HOME/.config/opencode/skills/priority-test/SKILL.md" <<'EOF'
---
name: priority-test
description: Personal version of priority test skill
@@ -65,14 +65,14 @@ echo " Created priority-test skill in all three locations"
echo ""
echo "Test 1: Verifying test fixtures..."
if [ -f "$SUPERPOWERS_SKILLS_DIR/priority-test/SKILL.md" ]; then
if [ -f "$HOME/.config/opencode/superpowers/skills/priority-test/SKILL.md" ]; then
echo " [PASS] Superpowers version exists"
else
echo " [FAIL] Superpowers version missing"
exit 1
fi
if [ -f "$OPENCODE_CONFIG_DIR/skills/priority-test/SKILL.md" ]; then
if [ -f "$HOME/.config/opencode/skills/priority-test/SKILL.md" ]; then
echo " [PASS] Personal version exists"
else
echo " [FAIL] Personal version missing"