mirror of
https://github.com/obra/superpowers.git
synced 2026-04-19 13:32:40 +00:00
Compare commits
1 Commits
sync-to-co
...
f/faster-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a78fa0f4d8 |
@@ -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",
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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/",
|
||||
|
||||
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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. -->
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -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.
|
||||
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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. -->
|
||||
23
.github/ISSUE_TEMPLATE/platform_support.md
vendored
23
.github/ISSUE_TEMPLATE/platform_support.md
vendored
@@ -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? -->
|
||||
87
.github/PULL_REQUEST_TEMPLATE.md
vendored
87
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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
|
||||
-->
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
13
CHANGELOG.md
Normal file
13
CHANGELOG.md
Normal 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`)
|
||||
85
CLAUDE.md
85
CLAUDE.md
@@ -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
|
||||
@@ -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.
|
||||
14
README.md
14
README.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,564 +0,0 @@
|
||||
# Codex App Compatibility Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Make `using-git-worktrees`, `finishing-a-development-branch`, and related skills work in the Codex App's sandboxed worktree environment without breaking existing behavior.
|
||||
|
||||
**Architecture:** Read-only environment detection (`git-dir` vs `git-common-dir`) at the start of two skills. If already in a linked worktree, skip creation. If on detached HEAD, emit a handoff payload instead of the 4-option menu. Sandbox fallback catches permission errors during worktree creation.
|
||||
|
||||
**Tech Stack:** Git, Markdown (skill files are instruction documents, not executable code)
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-03-23-codex-app-compatibility-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Responsibility | Action |
|
||||
|---|---|---|
|
||||
| `skills/using-git-worktrees/SKILL.md` | Worktree creation + isolation | Add Step 0 detection + sandbox fallback |
|
||||
| `skills/finishing-a-development-branch/SKILL.md` | Branch finishing workflow | Add Step 1.5 detection + cleanup guard |
|
||||
| `skills/subagent-driven-development/SKILL.md` | Plan execution with subagents | Update Integration description |
|
||||
| `skills/executing-plans/SKILL.md` | Plan execution inline | Update Integration description |
|
||||
| `skills/using-superpowers/references/codex-tools.md` | Codex platform reference | Add detection + finishing docs |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Step 0 to `using-git-worktrees`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/using-git-worktrees/SKILL.md:14-15` (insert after Overview, before Directory Selection Process)
|
||||
|
||||
- [ ] **Step 1: Read the current skill file**
|
||||
|
||||
Read `skills/using-git-worktrees/SKILL.md` in full. Identify the exact insertion point: after the "Announce at start" line (line 14) and before "## Directory Selection Process" (line 16).
|
||||
|
||||
- [ ] **Step 2: Insert Step 0 section**
|
||||
|
||||
Insert the following between the Overview section and "## Directory Selection Process":
|
||||
|
||||
```markdown
|
||||
## Step 0: Check if Already in an Isolated Workspace
|
||||
|
||||
Before creating a worktree, check if one already exists:
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
**If `GIT_DIR` differs from `GIT_COMMON`:** You are already inside a linked worktree (created by the Codex App, Claude Code's Agent tool, a previous skill run, or the user). Do NOT create another worktree. Instead:
|
||||
|
||||
1. Run project setup (auto-detect package manager as in "Run Project Setup" below)
|
||||
2. Verify clean baseline (run tests as in "Verify Clean Baseline" below)
|
||||
3. Report with branch state:
|
||||
- On a branch: "Already in an isolated workspace at `<path>` on branch `<name>`. Tests passing. Ready to implement."
|
||||
- Detached HEAD: "Already in an isolated workspace at `<path>` (detached HEAD, externally managed). Tests passing. Note: branch creation needed at finish time. Ready to implement."
|
||||
|
||||
After reporting, STOP. Do not continue to Directory Selection or Creation Steps.
|
||||
|
||||
**If `GIT_DIR` equals `GIT_COMMON`:** Proceed with the full worktree creation flow below.
|
||||
|
||||
**Sandbox fallback:** If you proceed to Creation Steps but `git worktree add -b` fails with a permission error (e.g., "Operation not permitted"), treat this as a late-detected restricted environment. Fall back to the behavior above — run setup and baseline tests in the current directory, report accordingly, and STOP.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the insertion**
|
||||
|
||||
Read the file again. Confirm:
|
||||
- Step 0 appears between Overview and Directory Selection Process
|
||||
- The rest of the file (Directory Selection, Safety Verification, Creation Steps, etc.) is unchanged
|
||||
- No duplicate sections or broken markdown
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/using-git-worktrees/SKILL.md
|
||||
git commit -m "feat(using-git-worktrees): add Step 0 environment detection (PRI-823)
|
||||
|
||||
Skip worktree creation when already in a linked worktree. Includes
|
||||
sandbox fallback for permission errors on git worktree add."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Update `using-git-worktrees` Integration section
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/using-git-worktrees/SKILL.md:211-215` (Integration > Called by)
|
||||
|
||||
- [ ] **Step 1: Update the three "Called by" entries**
|
||||
|
||||
Change lines 212-214 from:
|
||||
|
||||
```markdown
|
||||
- **brainstorming** (Phase 4) - REQUIRED when design is approved and implementation follows
|
||||
- **subagent-driven-development** - REQUIRED before executing any tasks
|
||||
- **executing-plans** - REQUIRED before executing any tasks
|
||||
```
|
||||
|
||||
To:
|
||||
|
||||
```markdown
|
||||
- **brainstorming** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
- **subagent-driven-development** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
- **executing-plans** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the Integration section**
|
||||
|
||||
Read the Integration section. Confirm all three entries are updated, "Pairs with" is unchanged.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/using-git-worktrees/SKILL.md
|
||||
git commit -m "docs(using-git-worktrees): update Integration descriptions (PRI-823)
|
||||
|
||||
Clarify that skill ensures a workspace exists, not that it always creates one."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Add Step 1.5 to `finishing-a-development-branch`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/finishing-a-development-branch/SKILL.md:38` (insert after Step 1, before Step 2)
|
||||
|
||||
- [ ] **Step 1: Read the current skill file**
|
||||
|
||||
Read `skills/finishing-a-development-branch/SKILL.md` in full. Identify the insertion point: after "**If tests pass:** Continue to Step 2." (line 38) and before "### Step 2: Determine Base Branch" (line 40).
|
||||
|
||||
- [ ] **Step 2: Insert Step 1.5 section**
|
||||
|
||||
Insert the following between Step 1 and Step 2:
|
||||
|
||||
```markdown
|
||||
### Step 1.5: Detect Environment
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
**Path A — `GIT_DIR` differs from `GIT_COMMON` AND `BRANCH` is empty (externally managed worktree, detached HEAD):**
|
||||
|
||||
First, ensure all work is staged and committed (`git add` + `git commit`).
|
||||
|
||||
Then present this to the user (do NOT present the 4-option menu):
|
||||
|
||||
```
|
||||
Implementation complete. All tests passing.
|
||||
Current HEAD: <full-commit-sha>
|
||||
|
||||
This workspace is externally managed (detached HEAD).
|
||||
I cannot create branches, push, or open PRs from here.
|
||||
|
||||
⚠ These commits are on a detached HEAD. If you do not create a branch,
|
||||
they may be lost when this workspace is cleaned up.
|
||||
|
||||
If your host application provides these controls:
|
||||
- "Create branch" — to name a branch, then commit/push/PR
|
||||
- "Hand off to local" — to move changes to your local checkout
|
||||
|
||||
Suggested branch name: <ticket-id/short-description>
|
||||
Suggested commit message: <summary-of-work>
|
||||
```
|
||||
|
||||
Branch name: use ticket ID if available (e.g., `pri-823/codex-compat`), otherwise slugify the first 5 words of the plan title, otherwise omit. Avoid sensitive content in branch names.
|
||||
|
||||
Skip to Step 5 (cleanup is a no-op — see guard below).
|
||||
|
||||
**Path B — `GIT_DIR` differs from `GIT_COMMON` AND `BRANCH` exists (externally managed worktree, named branch):**
|
||||
|
||||
Proceed to Step 2 and present the 4-option menu as normal.
|
||||
|
||||
**Path C — `GIT_DIR` equals `GIT_COMMON` (normal environment):**
|
||||
|
||||
Proceed to Step 2 and present the 4-option menu as normal.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the insertion**
|
||||
|
||||
Read the file again. Confirm:
|
||||
- Step 1.5 appears between Step 1 and Step 2
|
||||
- Steps 2-5 are unchanged
|
||||
- Path A handoff includes commit SHA and data loss warning
|
||||
- Paths B and C proceed to Step 2 normally
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/finishing-a-development-branch/SKILL.md
|
||||
git commit -m "feat(finishing-a-development-branch): add Step 1.5 environment detection (PRI-823)
|
||||
|
||||
Detect externally managed worktrees with detached HEAD and emit handoff
|
||||
payload instead of 4-option menu. Includes commit SHA and data loss warning."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Add Step 5 cleanup guard to `finishing-a-development-branch`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/finishing-a-development-branch/SKILL.md` (Step 5: Cleanup Worktree — find by section heading, line numbers will have shifted after Task 3)
|
||||
|
||||
- [ ] **Step 1: Read the current Step 5 section**
|
||||
|
||||
Find the "### Step 5: Cleanup Worktree" section in `skills/finishing-a-development-branch/SKILL.md` (line numbers will have shifted after Task 3's insertion). The current Step 5 is:
|
||||
|
||||
```markdown
|
||||
### Step 5: Cleanup Worktree
|
||||
|
||||
**For Options 1, 2, 4:**
|
||||
|
||||
Check if in worktree:
|
||||
```bash
|
||||
git worktree list | grep $(git branch --show-current)
|
||||
```
|
||||
|
||||
If yes:
|
||||
```bash
|
||||
git worktree remove <worktree-path>
|
||||
```
|
||||
|
||||
**For Option 3:** Keep worktree.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add the cleanup guard before existing logic**
|
||||
|
||||
Replace the Step 5 section with:
|
||||
|
||||
```markdown
|
||||
### Step 5: Cleanup Worktree
|
||||
|
||||
**First, check if worktree is externally managed:**
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
```
|
||||
|
||||
If `GIT_DIR` differs from `GIT_COMMON`: skip worktree removal — the host environment owns this workspace.
|
||||
|
||||
**Otherwise, for Options 1 and 4:**
|
||||
|
||||
Check if in worktree:
|
||||
```bash
|
||||
git worktree list | grep $(git branch --show-current)
|
||||
```
|
||||
|
||||
If yes:
|
||||
```bash
|
||||
git worktree remove <worktree-path>
|
||||
```
|
||||
|
||||
**For Option 3:** Keep worktree.
|
||||
```
|
||||
|
||||
Note: the original text said "For Options 1, 2, 4" but the Quick Reference table and Common Mistakes section say "Options 1 & 4 only." This edit aligns Step 5 with those sections.
|
||||
|
||||
- [ ] **Step 3: Verify the replacement**
|
||||
|
||||
Read Step 5. Confirm:
|
||||
- Cleanup guard (re-detection) appears first
|
||||
- Existing removal logic preserved for non-externally-managed worktrees
|
||||
- "Options 1 and 4" (not "1, 2, 4") matches Quick Reference and Common Mistakes
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/finishing-a-development-branch/SKILL.md
|
||||
git commit -m "feat(finishing-a-development-branch): add Step 5 cleanup guard (PRI-823)
|
||||
|
||||
Re-detect externally managed worktree at cleanup time and skip removal.
|
||||
Also fixes pre-existing inconsistency: cleanup now correctly says
|
||||
Options 1 and 4 only, matching Quick Reference and Common Mistakes."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Update Integration lines in `subagent-driven-development` and `executing-plans`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/subagent-driven-development/SKILL.md:268`
|
||||
- Modify: `skills/executing-plans/SKILL.md:68`
|
||||
|
||||
- [ ] **Step 1: Update `subagent-driven-development`**
|
||||
|
||||
Change line 268 from:
|
||||
```
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting
|
||||
```
|
||||
To:
|
||||
```
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update `executing-plans`**
|
||||
|
||||
Change line 68 from:
|
||||
```
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting
|
||||
```
|
||||
To:
|
||||
```
|
||||
- **superpowers:using-git-worktrees** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify both files**
|
||||
|
||||
Read line 268 of `skills/subagent-driven-development/SKILL.md` and line 68 of `skills/executing-plans/SKILL.md`. Confirm both say "Ensures isolated workspace (creates one or verifies existing)".
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/subagent-driven-development/SKILL.md skills/executing-plans/SKILL.md
|
||||
git commit -m "docs(sdd, executing-plans): update worktree Integration descriptions (PRI-823)
|
||||
|
||||
Clarify that using-git-worktrees ensures a workspace exists rather than
|
||||
always creating one."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Add environment detection docs to `codex-tools.md`
|
||||
|
||||
**Files:**
|
||||
- Modify: `skills/using-superpowers/references/codex-tools.md:25` (append at end)
|
||||
|
||||
- [ ] **Step 1: Read the current file**
|
||||
|
||||
Read `skills/using-superpowers/references/codex-tools.md` in full. Confirm it ends at line 25-26 after the multi_agent section.
|
||||
|
||||
- [ ] **Step 2: Append two new sections**
|
||||
|
||||
Add at the end of the file:
|
||||
|
||||
```markdown
|
||||
|
||||
## Environment Detection
|
||||
|
||||
Skills that create worktrees or finish branches should detect their
|
||||
environment with read-only git commands before proceeding:
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
|
||||
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
|
||||
|
||||
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
|
||||
Step 1.5 for how each skill uses these signals.
|
||||
|
||||
## Codex App Finishing
|
||||
|
||||
When the sandbox blocks branch/push operations (detached HEAD in an
|
||||
externally managed worktree), the agent commits all work and informs
|
||||
the user to use the App's native controls:
|
||||
|
||||
- **"Create branch"** — names the branch, then commit/push/PR via App UI
|
||||
- **"Hand off to local"** — transfers work to the user's local checkout
|
||||
|
||||
The agent can still run tests, stage files, and output suggested branch
|
||||
names, commit messages, and PR descriptions for the user to copy.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the additions**
|
||||
|
||||
Read the full file. Confirm:
|
||||
- Two new sections appear after the existing content
|
||||
- Bash code block renders correctly (not escaped)
|
||||
- Cross-references to Step 0 and Step 1.5 are present
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add skills/using-superpowers/references/codex-tools.md
|
||||
git commit -m "docs(codex-tools): add environment detection and App finishing docs (PRI-823)
|
||||
|
||||
Document the git-dir vs git-common-dir detection pattern and the Codex
|
||||
App's native finishing flow for skills that need to adapt."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Automated test — environment detection
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/codex-app-compat/test-environment-detection.sh`
|
||||
|
||||
- [ ] **Step 1: Create test directory**
|
||||
|
||||
```bash
|
||||
mkdir -p tests/codex-app-compat
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write the detection test script**
|
||||
|
||||
Create `tests/codex-app-compat/test-environment-detection.sh`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Test environment detection logic from PRI-823
|
||||
# Tests the git-dir vs git-common-dir comparison used by
|
||||
# using-git-worktrees Step 0 and finishing-a-development-branch Step 1.5
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TEMP_DIR" EXIT
|
||||
|
||||
log_pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
|
||||
log_fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
|
||||
|
||||
# Helper: run detection and return "linked" or "normal"
|
||||
detect_worktree() {
|
||||
local git_dir git_common
|
||||
git_dir=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
git_common=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
if [ "$git_dir" != "$git_common" ]; then
|
||||
echo "linked"
|
||||
else
|
||||
echo "normal"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=== Test 1: Normal repo detection ==="
|
||||
cd "$TEMP_DIR"
|
||||
git init test-repo > /dev/null 2>&1
|
||||
cd test-repo
|
||||
git commit --allow-empty -m "init" > /dev/null 2>&1
|
||||
result=$(detect_worktree)
|
||||
if [ "$result" = "normal" ]; then
|
||||
log_pass "Normal repo detected as normal"
|
||||
else
|
||||
log_fail "Normal repo detected as '$result' (expected 'normal')"
|
||||
fi
|
||||
|
||||
echo "=== Test 2: Linked worktree detection ==="
|
||||
git worktree add "$TEMP_DIR/test-wt" -b test-branch > /dev/null 2>&1
|
||||
cd "$TEMP_DIR/test-wt"
|
||||
result=$(detect_worktree)
|
||||
if [ "$result" = "linked" ]; then
|
||||
log_pass "Linked worktree detected as linked"
|
||||
else
|
||||
log_fail "Linked worktree detected as '$result' (expected 'linked')"
|
||||
fi
|
||||
|
||||
echo "=== Test 3: Detached HEAD detection ==="
|
||||
git checkout --detach HEAD > /dev/null 2>&1
|
||||
branch=$(git branch --show-current)
|
||||
if [ -z "$branch" ]; then
|
||||
log_pass "Detached HEAD: branch is empty"
|
||||
else
|
||||
log_fail "Detached HEAD: branch is '$branch' (expected empty)"
|
||||
fi
|
||||
|
||||
echo "=== Test 4: Linked worktree + detached HEAD (Codex App simulation) ==="
|
||||
result=$(detect_worktree)
|
||||
branch=$(git branch --show-current)
|
||||
if [ "$result" = "linked" ] && [ -z "$branch" ]; then
|
||||
log_pass "Codex App simulation: linked + detached HEAD"
|
||||
else
|
||||
log_fail "Codex App simulation: result='$result', branch='$branch'"
|
||||
fi
|
||||
|
||||
echo "=== Test 5: Cleanup guard — linked worktree should NOT remove ==="
|
||||
cd "$TEMP_DIR/test-wt"
|
||||
result=$(detect_worktree)
|
||||
if [ "$result" = "linked" ]; then
|
||||
log_pass "Cleanup guard: linked worktree correctly detected (would skip removal)"
|
||||
else
|
||||
log_fail "Cleanup guard: expected 'linked', got '$result'"
|
||||
fi
|
||||
|
||||
echo "=== Test 6: Cleanup guard — main repo SHOULD remove ==="
|
||||
cd "$TEMP_DIR/test-repo"
|
||||
result=$(detect_worktree)
|
||||
if [ "$result" = "normal" ]; then
|
||||
log_pass "Cleanup guard: main repo correctly detected (would proceed with removal)"
|
||||
else
|
||||
log_fail "Cleanup guard: expected 'normal', got '$result'"
|
||||
fi
|
||||
|
||||
# Cleanup worktree before temp dir removal
|
||||
cd "$TEMP_DIR/test-repo"
|
||||
git worktree remove "$TEMP_DIR/test-wt" > /dev/null 2>&1 || true
|
||||
|
||||
echo ""
|
||||
echo "=== Results: $PASS passed, $FAIL failed ==="
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Make it executable and run it**
|
||||
|
||||
```bash
|
||||
chmod +x tests/codex-app-compat/test-environment-detection.sh
|
||||
./tests/codex-app-compat/test-environment-detection.sh
|
||||
```
|
||||
|
||||
Expected output: 6 passed, 0 failed.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/codex-app-compat/test-environment-detection.sh
|
||||
git commit -m "test: add environment detection tests for Codex App compat (PRI-823)
|
||||
|
||||
Tests git-dir vs git-common-dir comparison in normal repo, linked
|
||||
worktree, detached HEAD, and cleanup guard scenarios."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Final verification
|
||||
|
||||
**Files:**
|
||||
- Read: all 5 modified skill files
|
||||
|
||||
- [ ] **Step 1: Run the automated detection tests**
|
||||
|
||||
```bash
|
||||
./tests/codex-app-compat/test-environment-detection.sh
|
||||
```
|
||||
|
||||
Expected: 6 passed, 0 failed.
|
||||
|
||||
- [ ] **Step 2: Read each modified file and verify changes**
|
||||
|
||||
Read each file end-to-end:
|
||||
- `skills/using-git-worktrees/SKILL.md` — Step 0 present, rest unchanged
|
||||
- `skills/finishing-a-development-branch/SKILL.md` — Step 1.5 present, cleanup guard present, rest unchanged
|
||||
- `skills/subagent-driven-development/SKILL.md` — line 268 updated
|
||||
- `skills/executing-plans/SKILL.md` — line 68 updated
|
||||
- `skills/using-superpowers/references/codex-tools.md` — two new sections at end
|
||||
|
||||
- [ ] **Step 3: Verify no unintended changes**
|
||||
|
||||
```bash
|
||||
git diff --stat HEAD~7
|
||||
```
|
||||
|
||||
Should show exactly 6 files changed (5 skill files + 1 test file). No other files modified.
|
||||
|
||||
- [ ] **Step 4: Run existing test suite**
|
||||
|
||||
If test runner exists:
|
||||
```bash
|
||||
# Run skill-triggering tests
|
||||
./tests/skill-triggering/run-all.sh 2>/dev/null || echo "Skill triggering tests not available in this environment"
|
||||
|
||||
# Run SDD integration test
|
||||
./tests/claude-code/test-subagent-driven-development-integration.sh 2>/dev/null || echo "SDD integration test not available in this environment"
|
||||
```
|
||||
|
||||
Note: these tests require Claude Code with `--dangerously-skip-permissions`. If not available, document that regression tests should be run manually.
|
||||
@@ -1,244 +0,0 @@
|
||||
# Codex App Compatibility: Worktree and Finishing Skill Adaptation
|
||||
|
||||
Make superpowers skills work in the Codex App's sandboxed worktree environment without breaking existing Claude Code or Codex CLI behavior.
|
||||
|
||||
**Ticket:** PRI-823
|
||||
|
||||
## Motivation
|
||||
|
||||
The Codex App runs agents inside git worktrees it manages — detached HEAD, located under `$CODEX_HOME/worktrees/`, with a Seatbelt sandbox that blocks `git checkout -b`, `git push`, and network access. Three superpowers skills assume unrestricted git access: `using-git-worktrees` creates manual worktrees with named branches, `finishing-a-development-branch` merges/pushes/PRs by branch name, and `subagent-driven-development` requires both.
|
||||
|
||||
The Codex CLI (open source terminal tool) does NOT have this conflict — it has no built-in worktree management. Our manual worktree approach fills an isolation gap there. The problem is specifically with the Codex App.
|
||||
|
||||
## Empirical Findings
|
||||
|
||||
Tested in the Codex App on 2026-03-23:
|
||||
|
||||
| Operation | workspace-write sandbox | Full access sandbox |
|
||||
|---|---|---|
|
||||
| `git add` | Works | Works |
|
||||
| `git commit` | Works | Works |
|
||||
| `git checkout -b` | **Blocked** (can't write `.git/refs/heads/`) | Works |
|
||||
| `git push` | **Blocked** (network + `.git/refs/remotes/`) | Works |
|
||||
| `gh pr create` | **Blocked** (network) | Works |
|
||||
| `git status/diff/log` | Works | Works |
|
||||
|
||||
Additional findings:
|
||||
- `spawn_agent` subagents **share** the parent thread's filesystem (confirmed via marker file test)
|
||||
- "Create branch" button appears in the App header regardless of which branch the worktree was started from
|
||||
- The App's native finishing flow: Create branch → Commit modal → Commit and push / Commit and create PR
|
||||
- `network_access = true` config is silently broken on macOS (issue #10390)
|
||||
|
||||
## Design: Read-Only Environment Detection
|
||||
|
||||
Three read-only git commands detect the environment without side effects:
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
Two signals derived:
|
||||
|
||||
- **IN_LINKED_WORKTREE:** `GIT_DIR != GIT_COMMON` — the agent is in a worktree created by something else (Codex App, Claude Code Agent tool, previous skill run, or the user)
|
||||
- **ON_DETACHED_HEAD:** `BRANCH` is empty — no named branch exists
|
||||
|
||||
Why `git-dir != git-common-dir` instead of checking `show-toplevel`:
|
||||
- In a normal repo, both resolve to the same `.git` directory
|
||||
- In a linked worktree, `git-dir` is `.git/worktrees/<name>` while `git-common-dir` is `.git`
|
||||
- In a submodule, both are equal — avoiding a false positive that `show-toplevel` would produce
|
||||
- Resolving via `cd && pwd -P` handles the relative-path problem (`git-common-dir` returns `.git` relative in normal repos but absolute in worktrees) and symlinks (macOS `/tmp` → `/private/tmp`)
|
||||
|
||||
### Decision Matrix
|
||||
|
||||
| Linked Worktree? | Detached HEAD? | Environment | Action |
|
||||
|---|---|---|---|
|
||||
| No | No | Claude Code / Codex CLI / normal git | Full skill behavior (unchanged) |
|
||||
| Yes | Yes | Codex App worktree (workspace-write) | Skip worktree creation; handoff payload at finish |
|
||||
| Yes | No | Codex App (Full access) or manual worktree | Skip worktree creation; full finishing flow |
|
||||
| No | Yes | Unusual (manual detached HEAD) | Create worktree normally; warn at finish |
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. `using-git-worktrees/SKILL.md` — Add Step 0 (~12 lines)
|
||||
|
||||
New section between "Overview" and "Directory Selection Process":
|
||||
|
||||
**Step 0: Check if Already in an Isolated Workspace**
|
||||
|
||||
Run the detection commands. If `GIT_DIR != GIT_COMMON`, skip worktree creation entirely. Instead:
|
||||
1. Skip to "Run Project Setup" subsection under Creation Steps — `npm install` etc. is idempotent, worth running for safety
|
||||
2. Then "Verify Clean Baseline" — run tests
|
||||
3. Report with branch state:
|
||||
- On a branch: "Already in an isolated workspace at `<path>` on branch `<name>`. Tests passing. Ready to implement."
|
||||
- Detached HEAD: "Already in an isolated workspace at `<path>` (detached HEAD, externally managed). Tests passing. Note: branch creation needed at finish time. Ready to implement."
|
||||
|
||||
If `GIT_DIR == GIT_COMMON`, proceed with the full worktree creation flow (unchanged).
|
||||
|
||||
Safety verification (.gitignore check) is skipped when Step 0 fires — irrelevant for externally-created worktrees.
|
||||
|
||||
Update the Integration section's "Called by" entries. Change the description on each from context-specific text to: "Ensures isolated workspace (creates one or verifies existing)". For example, the `subagent-driven-development` entry changes from "REQUIRED: Set up isolated workspace before starting" to "REQUIRED: Ensures isolated workspace (creates one or verifies existing)".
|
||||
|
||||
**Sandbox fallback:** If `GIT_DIR == GIT_COMMON` and the skill proceeds to Creation Steps, but `git worktree add -b` fails with a permission error (e.g., Seatbelt sandbox denial), treat this as a late-detected restricted environment. Fall back to the Step 0 "already in workspace" behavior — skip creation, run setup and baseline tests in the current directory, report accordingly.
|
||||
|
||||
After reporting in Step 0, STOP. Do not continue to Directory Selection or Creation Steps.
|
||||
|
||||
**Everything else unchanged:** Directory Selection, Safety Verification, Creation Steps, Project Setup, Baseline Tests, Quick Reference, Common Mistakes, Red Flags.
|
||||
|
||||
### 2. `finishing-a-development-branch/SKILL.md` — Add Step 1.5 + cleanup guard (~20 lines)
|
||||
|
||||
**Step 1.5: Detect Environment** (after Step 1 "Verify Tests", before Step 2 "Determine Base Branch")
|
||||
|
||||
Run the detection commands. Three paths:
|
||||
|
||||
- **Path A** skips Steps 2 and 3 entirely (no base branch or options needed).
|
||||
- **Paths B and C** proceed through Step 2 (Determine Base Branch) and Step 3 (Present Options) as normal.
|
||||
|
||||
**Path A — Externally managed worktree + detached HEAD** (`GIT_DIR != GIT_COMMON` AND `BRANCH` empty):
|
||||
|
||||
First, ensure all work is staged and committed (`git add` + `git commit`). The Codex App's finishing controls operate on committed work.
|
||||
|
||||
Then present this to the user (do NOT present the 4-option menu):
|
||||
|
||||
```
|
||||
Implementation complete. All tests passing.
|
||||
Current HEAD: <full-commit-sha>
|
||||
|
||||
This workspace is externally managed (detached HEAD).
|
||||
I cannot create branches, push, or open PRs from here.
|
||||
|
||||
⚠ These commits are on a detached HEAD. If you do not create a branch,
|
||||
they may be lost when this workspace is cleaned up.
|
||||
|
||||
If your host application provides these controls:
|
||||
- "Create branch" — to name a branch, then commit/push/PR
|
||||
- "Hand off to local" — to move changes to your local checkout
|
||||
|
||||
Suggested branch name: <ticket-id/short-description>
|
||||
Suggested commit message: <summary-of-work>
|
||||
```
|
||||
|
||||
Branch name derivation: use the ticket ID if available (e.g., `pri-823/codex-compat`), otherwise slugify the first 5 words of the plan title, otherwise omit the suggestion. Avoid including sensitive content (vulnerability descriptions, customer names) in branch names.
|
||||
|
||||
Skip to Step 5 (cleanup is a no-op for externally managed worktrees).
|
||||
|
||||
**Path B — Externally managed worktree + named branch** (`GIT_DIR != GIT_COMMON` AND `BRANCH` exists):
|
||||
|
||||
Present the 4-option menu as normal. (The Step 5 cleanup guard will re-detect the externally managed state independently.)
|
||||
|
||||
**Path C — Normal environment** (`GIT_DIR == GIT_COMMON`):
|
||||
|
||||
Present the 4-option menu as today (unchanged).
|
||||
|
||||
**Step 5 cleanup guard:**
|
||||
|
||||
Re-run the `GIT_DIR` vs `GIT_COMMON` detection at cleanup time (do not rely on earlier skill output — the finishing skill may run in a different session). If `GIT_DIR != GIT_COMMON`, skip `git worktree remove` — the host environment owns this workspace.
|
||||
|
||||
Otherwise, check and remove as today. Note: the existing Step 5 text says "For Options 1, 2, 4" but the Quick Reference table and Common Mistakes section say "Options 1 & 4 only." The new guard is added before this existing logic and does not change which options trigger cleanup.
|
||||
|
||||
**Everything else unchanged:** Options 1-4 logic, Quick Reference, Common Mistakes, Red Flags.
|
||||
|
||||
### 3. `subagent-driven-development/SKILL.md` and `executing-plans/SKILL.md` — 1 line edit each
|
||||
|
||||
Both skills have an identical Integration section line. Change from:
|
||||
```
|
||||
- superpowers:using-git-worktrees - REQUIRED: Set up isolated workspace before starting
|
||||
```
|
||||
To:
|
||||
```
|
||||
- superpowers:using-git-worktrees - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
|
||||
```
|
||||
|
||||
**Everything else unchanged:** Dispatch/review loop, prompt templates, model selection, status handling, red flags.
|
||||
|
||||
### 4. `codex-tools.md` — Add environment detection docs (~15 lines)
|
||||
|
||||
Two new sections at the end:
|
||||
|
||||
**Environment Detection:**
|
||||
|
||||
```markdown
|
||||
## Environment Detection
|
||||
|
||||
Skills that create worktrees or finish branches should detect their
|
||||
environment with read-only git commands before proceeding:
|
||||
|
||||
\```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
\```
|
||||
|
||||
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
|
||||
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
|
||||
|
||||
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
|
||||
Step 1.5 for how each skill uses these signals.
|
||||
```
|
||||
|
||||
**Codex App Finishing:**
|
||||
|
||||
```markdown
|
||||
## Codex App Finishing
|
||||
|
||||
When the sandbox blocks branch/push operations (detached HEAD in an
|
||||
externally managed worktree), the agent commits all work and informs
|
||||
the user to use the App's native controls:
|
||||
|
||||
- **"Create branch"** — names the branch, then commit/push/PR via App UI
|
||||
- **"Hand off to local"** — transfers work to the user's local checkout
|
||||
|
||||
The agent can still run tests, stage files, and output suggested branch
|
||||
names, commit messages, and PR descriptions for the user to copy.
|
||||
```
|
||||
|
||||
## What Does NOT Change
|
||||
|
||||
- `implementer-prompt.md`, `spec-reviewer-prompt.md`, `code-quality-reviewer-prompt.md` — subagent prompts untouched
|
||||
- `executing-plans/SKILL.md` — only the 1-line Integration description changes (same as `subagent-driven-development`); all runtime behavior is unchanged
|
||||
- `dispatching-parallel-agents/SKILL.md` — no worktree or finishing operations
|
||||
- `.codex/INSTALL.md` — installation process unchanged
|
||||
- The 4-option finishing menu — preserved exactly for Claude Code and Codex CLI
|
||||
- The full worktree creation flow — preserved exactly for non-worktree environments
|
||||
- Subagent dispatch/review/iterate loop — unchanged (filesystem sharing confirmed)
|
||||
|
||||
## Scope Summary
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `skills/using-git-worktrees/SKILL.md` | +12 lines (Step 0) |
|
||||
| `skills/finishing-a-development-branch/SKILL.md` | +20 lines (Step 1.5 + cleanup guard) |
|
||||
| `skills/subagent-driven-development/SKILL.md` | 1 line edit |
|
||||
| `skills/executing-plans/SKILL.md` | 1 line edit |
|
||||
| `skills/using-superpowers/references/codex-tools.md` | +15 lines |
|
||||
|
||||
~50 lines added/changed across 5 files. Zero new files. Zero breaking changes.
|
||||
|
||||
## Future Considerations
|
||||
|
||||
If a third skill needs the same detection pattern, extract it into a shared `references/environment-detection.md` file (Approach B). Not needed now — only 2 skills use it.
|
||||
|
||||
## Test Plan
|
||||
|
||||
### Automated (run in Claude Code after implementation)
|
||||
|
||||
1. Normal repo detection — assert IN_LINKED_WORKTREE=false
|
||||
2. Linked worktree detection — `git worktree add` test worktree, assert IN_LINKED_WORKTREE=true
|
||||
3. Detached HEAD detection — `git checkout --detach`, assert ON_DETACHED_HEAD=true
|
||||
4. Finishing skill handoff output — verify handoff message (not 4-option menu) in restricted environment
|
||||
5. **Step 5 cleanup guard** — create a linked worktree (`git worktree add /tmp/test-cleanup -b test-cleanup`), `cd` into it, run the Step 5 cleanup detection (`GIT_DIR` vs `GIT_COMMON`), assert it would NOT call `git worktree remove`. Then `cd` back to main repo, run the same detection, assert it WOULD call `git worktree remove`. Clean up test worktree afterward.
|
||||
|
||||
### Manual Codex App Tests (5 tests)
|
||||
|
||||
1. Detection in Worktree thread (workspace-write) — verify GIT_DIR != GIT_COMMON, empty branch
|
||||
2. Detection in Worktree thread (Full access) — same detection, different sandbox behavior
|
||||
3. Finishing skill handoff format — verify agent emits handoff payload, not 4-option menu
|
||||
4. Full lifecycle — detection → commit → finishing detection → correct behavior → cleanup
|
||||
5. **Sandbox fallback in Local thread** — Start a Codex App **Local thread** (workspace-write sandbox). Prompt: "Use the superpowers skill `using-git-worktrees` to set up an isolated workspace for implementing a small change." Pre-check: `git checkout -b test-sandbox-check` should fail with `Operation not permitted`. Expected: the skill detects `GIT_DIR == GIT_COMMON` (normal repo), attempts `git worktree add -b`, hits Seatbelt denial, falls back to Step 0 "already in workspace" behavior — runs setup, baseline tests, reports ready from current directory. Pass: agent recovers gracefully without cryptic error messages. Fail: agent prints raw Seatbelt error, retries, or gives up with confusing output.
|
||||
|
||||
### Regression
|
||||
|
||||
- Existing Claude Code skill-triggering tests still pass
|
||||
- Existing subagent-driven-development integration tests still pass
|
||||
- Normal Claude Code session: full worktree creation + 4-option finishing still works
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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 platforms — emit additional_context as fallback
|
||||
printf '{\n "additional_context": "%s"\n}\n' "$session_context"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "5.0.7",
|
||||
"version": "5.0.4",
|
||||
"type": "module",
|
||||
"main": ".opencode/plugins/superpowers.js"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -1,386 +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": "Core skills library for Codex: 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": [
|
||||
"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 adapted for Codex.",
|
||||
"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"
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"}'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Skills use Claude Code tool names. When you encounter these in a skill, use your
|
||||
|
||||
| Skill references | Codex equivalent |
|
||||
|-----------------|------------------|
|
||||
| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
|
||||
| `Task` tool (dispatch subagent) | `spawn_agent` |
|
||||
| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
|
||||
| Task returns result | `wait` |
|
||||
| Task completes automatically | `close_agent` to free slot |
|
||||
@@ -23,78 +23,3 @@ multi_agent = true
|
||||
```
|
||||
|
||||
This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
|
||||
|
||||
## Named agent dispatch
|
||||
|
||||
Claude Code skills reference named agent types like `superpowers:code-reviewer`.
|
||||
Codex does not have a named agent registry — `spawn_agent` creates generic agents
|
||||
from built-in roles (`default`, `explorer`, `worker`).
|
||||
|
||||
When a skill says to dispatch a named agent type:
|
||||
|
||||
1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
|
||||
local prompt template like `code-quality-reviewer-prompt.md`)
|
||||
2. Read the prompt content
|
||||
3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
|
||||
4. Spawn a `worker` agent with the filled content as the `message`
|
||||
|
||||
| Skill instruction | Codex equivalent |
|
||||
|-------------------|------------------|
|
||||
| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
|
||||
| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
|
||||
|
||||
### Message framing
|
||||
|
||||
The `message` parameter is user-level input, not a system prompt. Structure it
|
||||
for maximum instruction adherence:
|
||||
|
||||
```
|
||||
Your task is to perform the following. Follow the instructions below exactly.
|
||||
|
||||
<agent-instructions>
|
||||
[filled prompt content from the agent's .md file]
|
||||
</agent-instructions>
|
||||
|
||||
Execute this now. Output ONLY the structured response following the format
|
||||
specified in the instructions above.
|
||||
```
|
||||
|
||||
- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
|
||||
- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
|
||||
- End with an explicit execution directive to prevent summarization of the instructions
|
||||
|
||||
### When this workaround can be removed
|
||||
|
||||
This approach compensates for Codex's plugin system not yet supporting an `agents`
|
||||
field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
|
||||
plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
|
||||
skills can dispatch named agent types directly.
|
||||
|
||||
## Environment Detection
|
||||
|
||||
Skills that create worktrees or finish branches should detect their
|
||||
environment with read-only git commands before proceeding:
|
||||
|
||||
```bash
|
||||
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
|
||||
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
|
||||
BRANCH=$(git branch --show-current)
|
||||
```
|
||||
|
||||
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
|
||||
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
|
||||
|
||||
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
|
||||
Step 1 for how each skill uses these signals.
|
||||
|
||||
## Codex App Finishing
|
||||
|
||||
When the sandbox blocks branch/push operations (detached HEAD in an
|
||||
externally managed worktree), the agent commits all work and informs
|
||||
the user to use the App's native controls:
|
||||
|
||||
- **"Create branch"** — names the branch, then commit/push/PR via App UI
|
||||
- **"Hand off to local"** — transfers work to the user's local checkout
|
||||
|
||||
The agent can still run tests, stage files, and output suggested branch
|
||||
names, commit messages, and PR descriptions for the user to copy.
|
||||
|
||||
@@ -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) |
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user