mirror of
https://github.com/obra/superpowers.git
synced 2026-06-25 20:19:04 +08:00
Compare commits
15 Commits
codex/pri-
...
compress-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20707a3336 | ||
|
|
321c8cd24c | ||
|
|
bfa3e4137a | ||
|
|
a17aaaef3a | ||
|
|
896224c4b1 | ||
|
|
549dee6f64 | ||
|
|
4f9bd3131e | ||
|
|
caf14aac66 | ||
|
|
667b2c4a2e | ||
|
|
93b8444b51 | ||
|
|
207a12b203 | ||
|
|
b62616fc12 | ||
|
|
a21956e48c | ||
|
|
29c0b1b7db | ||
|
|
cf32920d3a |
20
.agents/plugins/marketplace.json
Normal file
20
.agents/plugins/marketplace.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "superpowers-dev",
|
||||
"interface": {
|
||||
"displayName": "Superpowers Dev"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "superpowers",
|
||||
"source": {
|
||||
"source": "url",
|
||||
"url": "./"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Developer Tools"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "6.0.0",
|
||||
"version": "6.0.3",
|
||||
"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": "6.0.0",
|
||||
"version": "6.0.3",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "6.0.0",
|
||||
"version": "6.0.3",
|
||||
"description": "An agentic skills framework & software development methodology that works: planning, TDD, debugging, and collaboration workflows.",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "superpowers",
|
||||
"displayName": "Superpowers",
|
||||
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "6.0.0",
|
||||
"version": "6.0.3",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -7,8 +7,7 @@ node_modules/
|
||||
inspo
|
||||
triage/
|
||||
|
||||
# Eval harness — drill ships its own gitignore at evals/.gitignore;
|
||||
# these are belt-and-suspenders entries for tools that don't recurse.
|
||||
evals/results/
|
||||
evals/.venv/
|
||||
evals/.env
|
||||
# Eval harness lives in its own repository, cloned into evals/ for local
|
||||
# development (see CLAUDE.md / README.md). It is not part of the published
|
||||
# plugin, so the whole directory is ignored here.
|
||||
evals/
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "evals"]
|
||||
path = evals
|
||||
url = git@github.com:prime-radiant-inc/superpowers-evals.git
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "6.0.0",
|
||||
"version": "6.0.3",
|
||||
"description": "An agentic skills framework and software development methodology.",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
|
||||
@@ -101,7 +101,7 @@ Skills are not prose — they are code that shapes agent behavior. If you modify
|
||||
|
||||
## Eval harness
|
||||
|
||||
Skill-behavior evals live in the `evals/` submodule — after cloning, run `git submodule update --init evals`, then see `evals/README.md`. Drill (the harness) drives real tmux sessions of Claude Code / Codex / Gemini CLI and judges skill compliance with an LLM verifier. Plugin-infrastructure tests still live at `tests/`.
|
||||
Skill-behavior evals live in [superpowers-evals](https://github.com/prime-radiant-inc/superpowers-evals/), cloned into `evals/` — see `evals/README.md` for setup. Drill (the harness) drives real tmux sessions of Claude Code / Codex / Gemini CLI and judges skill compliance with an LLM verifier. Plugin-infrastructure tests still live at `tests/`.
|
||||
|
||||
## Understand the Project Before Contributing
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ The general contribution process for Superpowers is below. Keep in mind that we
|
||||
4. Follow the `writing-skills` skill for creating and testing new and modified skills
|
||||
5. Submit a PR, being sure to fill in the pull request template.
|
||||
|
||||
Skill-behavior tests use the eval harness submodule at `evals/`. After cloning this repo, run `git submodule update --init evals`, then see `evals/README.md` for setup. Plugin-infrastructure tests live at `tests/` and run via the relevant `run-*.sh` or `npm test`.
|
||||
Skill-behavior tests use the drill eval harness from [superpowers-evals](https://github.com/prime-radiant-inc/superpowers-evals/), cloned into `evals/` — see `evals/README.md` for setup. Plugin-infrastructure tests live at `tests/` and run via the relevant `run-*.sh` or `npm test`.
|
||||
|
||||
See `skills/writing-skills/SKILL.md` for the complete guide.
|
||||
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# Superpowers Release Notes
|
||||
|
||||
## v6.0.3 (2026-06-18)
|
||||
|
||||
### Subagent-Driven Development
|
||||
|
||||
- **SDD scratch files moved out of `.git/`.** Claude Code treats `.git/` as a protected path and denies agent writes there, so an implementer subagent writing its report into `.git/sdd/` got blocked mid-run. Task briefs, implementer reports, review diffs, and the progress ledger now live in a self-ignoring `.superpowers/sdd/` directory in the working tree — kept out of `git status` and out of commits, and resolved per worktree by a shared `sdd-workspace` helper. One caveat: because the workspace is git-ignored working-tree scratch, `git clean -fdx` will delete the progress ledger; recover from `git log` if that happens. (#1780)
|
||||
|
||||
## v6.0.2 (2026-06-16)
|
||||
|
||||
### Install Fixes
|
||||
|
||||
- **We no longer ship the `evals` submodule.** It broke plugin installs for some users, so the eval harness now lives in its own repo, separate from the published plugin. (#1778, #1774)
|
||||
|
||||
## v6.0.1 (2026-06-16)
|
||||
|
||||
### Codex Fixes
|
||||
|
||||
- **Version display in the brainstorm companion** — packaged Codex plugins ship without a root `package.json`, so the visual companion reported its version as "unknown". `readSuperpowersVersion()` now falls back to `.codex-plugin/plugin.json` when `package.json` is absent.
|
||||
- **Cleaner Codex plugin sync** — the sync-to-codex script now excludes `.gitmodules` and `.pre-commit-config.yaml`, keeping repo metadata out of the packaged Codex plugin.
|
||||
|
||||
## v6.0.0 (2026-06-16)
|
||||
|
||||
Superpowers 6.0 is a big release. The headline is a rewrite of how `subagent-driven-development` reviews each task — cheaper, stricter, and harder to game.
|
||||
|
||||
1
evals
1
evals
Submodule evals deleted from 70a245c36c
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "6.0.0",
|
||||
"version": "6.0.3",
|
||||
"contextFileName": "GEMINI.md"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"matcher": "startup|resume|clear",
|
||||
"matcher": "startup|clear|compact",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"version": "6.0.0",
|
||||
"version": "6.0.3",
|
||||
"description": "Superpowers skills and runtime bootstrap for coding agents",
|
||||
"type": "module",
|
||||
"main": ".opencode/plugins/superpowers.js",
|
||||
|
||||
@@ -52,9 +52,11 @@ EXCLUDES=(
|
||||
"/.gitattributes"
|
||||
"/.github/"
|
||||
"/.gitignore"
|
||||
"/.gitmodules"
|
||||
"/.kimi-plugin/"
|
||||
"/.opencode/"
|
||||
"/.pi/"
|
||||
"/.pre-commit-config.yaml"
|
||||
"/.version-bump.json"
|
||||
"/.worktrees/"
|
||||
".DS_Store"
|
||||
|
||||
@@ -206,14 +206,22 @@ const helperInjection = '<script>\n' + helperScript + '\n</script>';
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
function readSuperpowersVersion() {
|
||||
try {
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, '../../..', 'package.json'), 'utf-8')
|
||||
);
|
||||
return String(packageJson.version || 'unknown');
|
||||
} catch (e) {
|
||||
return 'unknown';
|
||||
const root = path.join(__dirname, '../../..');
|
||||
const manifests = [
|
||||
path.join(root, 'package.json'),
|
||||
path.join(root, '.codex-plugin/plugin.json')
|
||||
];
|
||||
|
||||
for (const manifest of manifests) {
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(manifest, 'utf-8'));
|
||||
if (data.version) return String(data.version);
|
||||
} catch (e) {
|
||||
// Packaged Codex plugins omit package.json; try the next manifest.
|
||||
}
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function isTruthyEnv(value) {
|
||||
|
||||
@@ -251,7 +251,7 @@ sequences — the single most expensive failure observed. Track progress in
|
||||
a ledger file, not only in todos.
|
||||
|
||||
- At skill start, check for a ledger:
|
||||
`cat "$(git rev-parse --git-path sdd)/progress.md"`. Tasks listed there
|
||||
`cat "$(git rev-parse --show-toplevel)/.superpowers/sdd/progress.md"`. Tasks listed there
|
||||
as complete are DONE — do not re-dispatch them; resume at the first task
|
||||
not marked complete.
|
||||
- When a task's review comes back clean, append one line to the ledger in
|
||||
@@ -260,6 +260,8 @@ a ledger file, not only in todos.
|
||||
- The ledger is your recovery map: the commits it names exist in git even
|
||||
when your context no longer remembers creating them. After compaction,
|
||||
trust the ledger and `git log` over your own recollection.
|
||||
- `git clean -fdx` will destroy the ledger (it's git-ignored scratch); if
|
||||
that happens, recover from `git log`.
|
||||
|
||||
## Prompt Templates
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
# tasks intact.
|
||||
#
|
||||
# Usage: review-package BASE HEAD [OUTFILE]
|
||||
# Default OUTFILE: <git-dir>/sdd/review-<base7>..<head7>.diff — unique per
|
||||
# repo instance and per range, so concurrent sessions cannot collide and a
|
||||
# re-review after fixes always gets a distinctly named fresh file.
|
||||
# Default OUTFILE: <repo-root>/.superpowers/sdd/review-<base7>..<head7>.diff
|
||||
# (named per range, so a re-review after fixes gets a distinct fresh file).
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
||||
@@ -24,9 +23,7 @@ git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&
|
||||
if [ $# -eq 3 ]; then
|
||||
out=$3
|
||||
else
|
||||
dir=$(git rev-parse --git-path sdd)
|
||||
mkdir -p "$dir"
|
||||
dir=$(cd "$dir" && pwd)
|
||||
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
|
||||
out="$dir/review-$(git rev-parse --short "$base")..$(git rev-parse --short "$head").diff"
|
||||
fi
|
||||
|
||||
|
||||
22
skills/subagent-driven-development/scripts/sdd-workspace
Executable file
22
skills/subagent-driven-development/scripts/sdd-workspace
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# Resolve and ensure the working-tree directory SDD uses for its short-lived
|
||||
# artifacts: task briefs, implementer reports, review packages, and the
|
||||
# progress ledger. Print the directory's absolute path.
|
||||
#
|
||||
# The workspace lives in the working tree (not under .git/) because Claude Code
|
||||
# treats .git/ as a protected path and denies agent writes there — which blocks
|
||||
# an implementer subagent from writing its report file. A self-ignoring
|
||||
# .gitignore keeps the workspace out of `git status` and out of accidental
|
||||
# commits without modifying any tracked file.
|
||||
#
|
||||
# Single source of truth for the workspace location, so task-brief and
|
||||
# review-package cannot drift to different directories.
|
||||
#
|
||||
# Usage: sdd-workspace
|
||||
set -euo pipefail
|
||||
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
dir="$root/.superpowers/sdd"
|
||||
mkdir -p "$dir"
|
||||
printf '*\n' > "$dir/.gitignore"
|
||||
cd "$dir" && pwd
|
||||
@@ -4,8 +4,8 @@
|
||||
# through the controller's context.
|
||||
#
|
||||
# Usage: task-brief PLAN_FILE TASK_NUMBER [OUTFILE]
|
||||
# Default OUTFILE: <git-dir>/sdd/task-<N>-brief.md — unique per repo
|
||||
# instance, so concurrent sessions cannot collide.
|
||||
# Default OUTFILE: <repo-root>/.superpowers/sdd/task-<N>-brief.md
|
||||
# (per worktree; concurrent runs in the same working tree share it).
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
|
||||
@@ -20,9 +20,7 @@ n=$2
|
||||
if [ $# -eq 3 ]; then
|
||||
out=$3
|
||||
else
|
||||
dir=$(git rev-parse --git-path sdd)
|
||||
mkdir -p "$dir"
|
||||
dir=$(cd "$dir" && pwd)
|
||||
dir=$("$(cd "$(dirname "$0")" && pwd)/sdd-workspace")
|
||||
out="$dir/task-${n}-brief.md"
|
||||
fi
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ description: Use when starting any conversation - establishes how to find and us
|
||||
---
|
||||
|
||||
<SUBAGENT-STOP>
|
||||
If you were dispatched as a subagent to execute a specific task, skip this skill.
|
||||
If you were dispatched as a subagent to execute a specific task, ignore this skill.
|
||||
</SUBAGENT-STOP>
|
||||
|
||||
<EXTREMELY-IMPORTANT>
|
||||
@@ -12,72 +12,23 @@ If you think there is even a 1% chance a skill might apply to what you are doing
|
||||
|
||||
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
|
||||
|
||||
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
|
||||
This is not negotiable. You cannot rationalize your way out of this.
|
||||
</EXTREMELY-IMPORTANT>
|
||||
|
||||
## Instruction Priority
|
||||
|
||||
Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
|
||||
|
||||
1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
|
||||
2. **Superpowers skills** — override default system behavior where they conflict
|
||||
3. **Default system prompt** — lowest priority
|
||||
|
||||
If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
|
||||
|
||||
## How to Access Skills
|
||||
|
||||
**Never read skill files manually with file tools** — always use your platform's skill-loading mechanism so the skill is properly activated.
|
||||
|
||||
**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you — follow it directly.
|
||||
|
||||
**In Codex:** Skills load natively. Follow the instructions presented when a skill activates.
|
||||
|
||||
**In Copilot CLI:** Use the `skill` tool. Skills are auto-discovered from installed plugins.
|
||||
|
||||
**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 speak in actions ("dispatch a subagent", "create a todo", "read a file") rather than naming any one runtime's tools. For per-platform tool equivalents and instructions-file conventions, see [claude-code-tools.md](references/claude-code-tools.md), [codex-tools.md](references/codex-tools.md), [copilot-tools.md](references/copilot-tools.md), [gemini-tools.md](references/gemini-tools.md), [pi-tools.md](references/pi-tools.md), and [antigravity-tools.md](references/antigravity-tools.md). Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
|
||||
|
||||
# Using Skills
|
||||
|
||||
## The Rule
|
||||
|
||||
**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
|
||||
**Invoke relevant or requested skills BEFORE any response or action** — including clarifying questions, exploring the codebase, or checking files. If it turns out wrong for the situation, you don't have to use it.
|
||||
|
||||
```dot
|
||||
digraph skill_flow {
|
||||
"User message received" [shape=doublecircle];
|
||||
"About to enter plan mode?" [shape=doublecircle];
|
||||
"Already brainstormed?" [shape=diamond];
|
||||
"Invoke brainstorming skill" [shape=box];
|
||||
"Might any skill apply?" [shape=diamond];
|
||||
"Invoke the skill" [shape=box];
|
||||
"Announce: 'Using [skill] to [purpose]'" [shape=box];
|
||||
"Has checklist?" [shape=diamond];
|
||||
"Create a todo per item" [shape=box];
|
||||
"Follow skill exactly" [shape=box];
|
||||
"Respond (including clarifications)" [shape=doublecircle];
|
||||
**Before entering plan mode:** if you haven't already brainstormed, invoke the brainstorming skill first.
|
||||
|
||||
"About to enter plan mode?" -> "Already brainstormed?";
|
||||
"Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
|
||||
"Already brainstormed?" -> "Might any skill apply?" [label="yes"];
|
||||
"Invoke brainstorming skill" -> "Might any skill apply?";
|
||||
Then announce "Using [skill] to [purpose]" and follow the skill exactly. If it has a checklist, create a todo per item.
|
||||
|
||||
"User message received" -> "Might any skill apply?";
|
||||
"Might any skill apply?" -> "Invoke the skill" [label="yes, even 1%"];
|
||||
"Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
|
||||
"Invoke the skill" -> "Announce: 'Using [skill] to [purpose]'";
|
||||
"Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
|
||||
"Has checklist?" -> "Create a todo per item" [label="yes"];
|
||||
"Has checklist?" -> "Follow skill exactly" [label="no"];
|
||||
"Create a todo per item" -> "Follow skill exactly";
|
||||
}
|
||||
```
|
||||
## Skill Priority
|
||||
|
||||
When multiple skills apply, process skills come first — they set the approach, then implementation skills (frontend-design, etc.) carry it out. Brainstorming and systematic-debugging are Superpowers' most common process skills, but the rule holds for any of them.
|
||||
|
||||
- "Let's build X" → superpowers:brainstorming first, then implementation skills.
|
||||
- "Fix this bug" → superpowers:systematic-debugging first, then domain skills.
|
||||
|
||||
## Red Flags
|
||||
|
||||
@@ -98,24 +49,14 @@ These thoughts mean STOP—you're rationalizing:
|
||||
| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
|
||||
| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
|
||||
|
||||
## Skill Priority
|
||||
## Platform Adaptation
|
||||
|
||||
When multiple skills could apply, use this order:
|
||||
If your harness appears here, read its reference file for special instructions:
|
||||
|
||||
1. **Process skills first** (brainstorming, systematic-debugging) - these determine HOW to approach the task
|
||||
2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
|
||||
|
||||
"Let's build X" → brainstorming first, then implementation skills.
|
||||
"Fix this bug" → systematic-debugging first, then domain-specific skills.
|
||||
|
||||
## Skill Types
|
||||
|
||||
**Rigid** (TDD, systematic-debugging): Follow exactly. Don't adapt away discipline.
|
||||
|
||||
**Flexible** (patterns): Adapt principles to context.
|
||||
|
||||
The skill itself tells you which.
|
||||
- Codex: `references/codex-tools.md`
|
||||
- Pi: `references/pi-tools.md`
|
||||
- Antigravity: `references/antigravity-tools.md`
|
||||
|
||||
## User Instructions
|
||||
|
||||
Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.
|
||||
User instructions (CLAUDE.md, AGENTS.md, GEMINI.md, etc, direct requests) take precedence over skills, which in turn override default behavior. Only skip skill workflows or instructions when your human partner has explicitly told you to.
|
||||
|
||||
@@ -26,9 +26,9 @@ function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function startServer({ port, dir, env = {} }) {
|
||||
function startServer({ port, dir, env = {}, serverPath = SERVER_PATH }) {
|
||||
cleanup(dir);
|
||||
return spawn('node', [SERVER_PATH], {
|
||||
return spawn('node', [serverPath], {
|
||||
env: {
|
||||
...process.env,
|
||||
BRAINSTORM_PORT: String(port),
|
||||
@@ -74,6 +74,21 @@ function writeFragment(dir) {
|
||||
fs.writeFileSync(path.join(contentDir, 'screen.html'), '<h2>Pick a layout</h2>');
|
||||
}
|
||||
|
||||
function createPackagedServerFixture(version) {
|
||||
const root = fs.mkdtempSync(path.join('/tmp', 'superpowers-packaged-server-'));
|
||||
const scriptDir = path.join(root, 'skills/brainstorming/scripts');
|
||||
fs.cpSync(path.join(REPO_ROOT, 'skills/brainstorming/scripts'), scriptDir, { recursive: true });
|
||||
fs.mkdirSync(path.join(root, '.codex-plugin'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(root, '.codex-plugin/plugin.json'),
|
||||
JSON.stringify({ name: 'superpowers', version }, null, 2)
|
||||
);
|
||||
return {
|
||||
root,
|
||||
serverPath: path.join(scriptDir, 'server.cjs')
|
||||
};
|
||||
}
|
||||
|
||||
async function withServer(options, fn) {
|
||||
const server = startServer(options);
|
||||
try {
|
||||
@@ -104,13 +119,13 @@ async function test(name, fn) {
|
||||
}
|
||||
}
|
||||
|
||||
function assertBrandedWithLogo(html) {
|
||||
function assertBrandedWithLogo(html, version = PACKAGE_VERSION) {
|
||||
assert(
|
||||
html.includes(`Superpowers v${PACKAGE_VERSION}`),
|
||||
html.includes(`Superpowers v${version}`),
|
||||
'branding text should include dynamic package version'
|
||||
);
|
||||
assert(
|
||||
!html.includes(`Superpowers v${PACKAGE_VERSION} by`),
|
||||
!html.includes(`Superpowers v${version} by`),
|
||||
'branding text should not include "by" when the logo is visible'
|
||||
);
|
||||
assert(
|
||||
@@ -139,15 +154,15 @@ function assertBrandedWithLogo(html) {
|
||||
);
|
||||
}
|
||||
|
||||
function assertBrandedFallbackText(html) {
|
||||
function assertBrandedFallbackText(html, version = PACKAGE_VERSION) {
|
||||
assert(
|
||||
html.includes(`Prime Radiant Superpowers v${PACKAGE_VERSION}`),
|
||||
html.includes(`Prime Radiant Superpowers v${version}`),
|
||||
'disabled telemetry should keep plain text Prime Radiant/Superpowers branding'
|
||||
);
|
||||
}
|
||||
|
||||
function assertTelemetryImage(html) {
|
||||
const expectedUrl = `${ASSET_URL}?v=${encodeURIComponent(PACKAGE_VERSION)}`;
|
||||
function assertTelemetryImage(html, version = PACKAGE_VERSION) {
|
||||
const expectedUrl = `${ASSET_URL}?v=${encodeURIComponent(version)}`;
|
||||
assert(html.includes(`src="${expectedUrl}"`), 'remote image should use the dedicated main-domain asset with only v=');
|
||||
assert(!html.includes('event='), 'remote image URL must not include event=');
|
||||
assert(!html.includes('surface='), 'remote image URL must not include surface=');
|
||||
@@ -255,6 +270,26 @@ async function main() {
|
||||
});
|
||||
});
|
||||
|
||||
await test('packaged Codex plugin reads version from .codex-plugin manifest', async () => {
|
||||
const port = 3457;
|
||||
const dir = '/tmp/brainstorm-branding-packaged-codex';
|
||||
const packagedVersion = '7.8.9';
|
||||
const fixture = createPackagedServerFixture(packagedVersion);
|
||||
|
||||
try {
|
||||
await withServer({ port, dir, serverPath: fixture.serverPath }, async () => {
|
||||
writeFragment(dir);
|
||||
await sleep(300);
|
||||
const html = await fetchHtml(port);
|
||||
assertBrandedWithLogo(html, packagedVersion);
|
||||
assertTelemetryImage(html, packagedVersion);
|
||||
assert(!html.includes('Superpowers vunknown'), 'packaged plugin should not fall back to unknown version');
|
||||
});
|
||||
} finally {
|
||||
cleanup(fixture.root);
|
||||
}
|
||||
});
|
||||
|
||||
await test('SUPERPOWERS_DISABLE_TELEMETRY=true omits remote image but keeps local branding', async () => {
|
||||
const port = 3453;
|
||||
const dir = '/tmp/brainstorm-branding-disabled';
|
||||
|
||||
8
tests/brainstorm-server/package-lock.json
generated
8
tests/brainstorm-server/package-lock.json
generated
@@ -8,13 +8,13 @@
|
||||
"name": "brainstorm-server-tests",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.19.0"
|
||||
"ws": "^8.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"version": "8.21.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"test": "node ws-protocol.test.js && node helper.test.js && node browser-launcher.test.js && node auth.test.js && node branding.test.js && node server.test.js && node lifecycle.test.js && bash start-server.test.sh && bash stop-server.test.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^8.19.0"
|
||||
"ws": "^8.21.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ done
|
||||
# List of skill tests to run (fast unit tests)
|
||||
tests=(
|
||||
"test-worktree-path-policy.sh"
|
||||
"test-sdd-workspace.sh"
|
||||
"test-subagent-driven-development.sh"
|
||||
)
|
||||
|
||||
|
||||
142
tests/claude-code/test-sdd-workspace.sh
Executable file
142
tests/claude-code/test-sdd-workspace.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env bash
|
||||
# Tests for the SDD workspace: scripts/sdd-workspace resolves a self-ignoring
|
||||
# working-tree directory for SDD artifacts, and the SDD scripts write into it.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SDD_SCRIPTS="$REPO_ROOT/skills/subagent-driven-development/scripts"
|
||||
|
||||
FAILURES=0
|
||||
TEST_ROOT=""
|
||||
|
||||
pass() { echo " [PASS] $1"; }
|
||||
fail() {
|
||||
echo " [FAIL] $1"
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "$TEST_ROOT" && -d "$TEST_ROOT" ]]; then
|
||||
rm -rf "$TEST_ROOT"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "=== Test: sdd-workspace ==="
|
||||
|
||||
TEST_ROOT="$(mktemp -d)"
|
||||
trap cleanup EXIT
|
||||
|
||||
# Resolve repo to its physical path so string comparisons match the
|
||||
# helper's output (git rev-parse --show-toplevel resolves symlinks; on
|
||||
# macOS mktemp lives under /var -> /private/var).
|
||||
git init -q -b main "$TEST_ROOT/repo"
|
||||
local repo
|
||||
repo="$(cd "$TEST_ROOT/repo" && git rev-parse --show-toplevel)"
|
||||
|
||||
local dir
|
||||
dir="$(cd "$repo" && "$SDD_SCRIPTS/sdd-workspace")"
|
||||
|
||||
if [[ "$dir" == "$repo/.superpowers/sdd" ]]; then
|
||||
pass "prints <repo-root>/.superpowers/sdd"
|
||||
else
|
||||
fail "prints <repo-root>/.superpowers/sdd"
|
||||
echo " got: $dir"
|
||||
fi
|
||||
|
||||
if [[ -f "$repo/.superpowers/sdd/.gitignore" && "$(cat "$repo/.superpowers/sdd/.gitignore")" == "*" ]]; then
|
||||
pass "self-ignoring .gitignore created with '*'"
|
||||
else
|
||||
fail "self-ignoring .gitignore created with '*'"
|
||||
fi
|
||||
|
||||
printf 'x\n' > "$repo/.superpowers/sdd/artifact.md"
|
||||
local status
|
||||
status="$(cd "$repo" && git status --porcelain)"
|
||||
if [[ -z "$status" ]]; then
|
||||
pass "workspace invisible to git status"
|
||||
else
|
||||
fail "workspace invisible to git status"
|
||||
echo " status: $status"
|
||||
fi
|
||||
|
||||
( cd "$repo" && git add -A )
|
||||
local staged
|
||||
staged="$(cd "$repo" && git diff --cached --name-only)"
|
||||
if [[ -z "$staged" ]]; then
|
||||
pass "git add -A does not stage the workspace"
|
||||
else
|
||||
fail "git add -A does not stage the workspace"
|
||||
echo " staged: $staged"
|
||||
fi
|
||||
|
||||
cat > "$repo/plan.md" <<'PLAN'
|
||||
# Plan
|
||||
|
||||
## Task 1: First thing
|
||||
|
||||
Do the first thing.
|
||||
PLAN
|
||||
|
||||
local brief_out brief_path
|
||||
brief_out="$(cd "$repo" && "$SDD_SCRIPTS/task-brief" plan.md 1)"
|
||||
brief_path="$(printf '%s\n' "$brief_out" | sed -n 's/^wrote \(.*\): [0-9][0-9]* lines$/\1/p')"
|
||||
case "$brief_path" in
|
||||
"$repo/.superpowers/sdd/"*) pass "task-brief writes its brief under the workspace" ;;
|
||||
*)
|
||||
fail "task-brief writes its brief under the workspace"
|
||||
echo " got: $brief_path"
|
||||
;;
|
||||
esac
|
||||
|
||||
local git_id=(-c user.email=t@example.com -c user.name=t -c commit.gpgsign=false)
|
||||
( cd "$repo" \
|
||||
&& git add plan.md \
|
||||
&& git "${git_id[@]}" commit -qm c1 \
|
||||
&& printf 'y\n' > f && git add f \
|
||||
&& git "${git_id[@]}" commit -qm c2 )
|
||||
local rp_out rp_path
|
||||
rp_out="$(cd "$repo" && "$SDD_SCRIPTS/review-package" HEAD~1 HEAD)"
|
||||
rp_path="$(printf '%s\n' "$rp_out" | sed -n 's/^wrote \(.*\): [0-9].*$/\1/p')"
|
||||
case "$rp_path" in
|
||||
"$repo/.superpowers/sdd/"*) pass "review-package writes its diff under the workspace" ;;
|
||||
*)
|
||||
fail "review-package writes its diff under the workspace"
|
||||
echo " got: $rp_path"
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Worktree isolation: a linked worktree resolves its own workspace ---
|
||||
local wt="$TEST_ROOT/wt"
|
||||
( cd "$repo" && git worktree add -q "$wt" -b wt-feature )
|
||||
local wt_root wt_dir
|
||||
wt_root="$(cd "$wt" && git rev-parse --show-toplevel)"
|
||||
wt_dir="$(cd "$wt" && "$SDD_SCRIPTS/sdd-workspace")"
|
||||
if [[ "$wt_dir" == "$wt_root/.superpowers/sdd" && "$wt_dir" != "$dir" ]]; then
|
||||
pass "linked worktree resolves its own distinct workspace"
|
||||
else
|
||||
fail "linked worktree resolves its own distinct workspace"
|
||||
echo " main: $dir"
|
||||
echo " wt: $wt_dir"
|
||||
fi
|
||||
|
||||
printf 'y\n' > "$wt/.superpowers/sdd/artifact.md"
|
||||
local wt_status
|
||||
wt_status="$(cd "$wt" && git status --porcelain)"
|
||||
if [[ -z "$wt_status" ]]; then
|
||||
pass "worktree workspace invisible to git status"
|
||||
else
|
||||
fail "worktree workspace invisible to git status"
|
||||
echo " status: $wt_status"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [[ "$FAILURES" -ne 0 ]]; then
|
||||
echo "FAILED: $FAILURES assertion(s)."
|
||||
exit 1
|
||||
fi
|
||||
echo "PASS"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -200,6 +200,23 @@ EOF
|
||||
.private-journal/
|
||||
EOF
|
||||
|
||||
cat > "$repo/.gitmodules" <<'EOF'
|
||||
[submodule "evals"]
|
||||
path = evals
|
||||
url = git@example.com:example/evals.git
|
||||
EOF
|
||||
|
||||
cat > "$repo/.pre-commit-config.yaml" <<'EOF'
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: evals-check
|
||||
name: evals check
|
||||
entry: echo evals
|
||||
language: system
|
||||
files: ^evals/
|
||||
EOF
|
||||
|
||||
if [[ "$with_pure_ignored" == "1" ]]; then
|
||||
cat >> "$repo/.gitignore" <<'EOF'
|
||||
ignored-cache/
|
||||
@@ -277,6 +294,8 @@ EOF
|
||||
.codex-plugin/plugin.json \
|
||||
.kimi-plugin/plugin.json \
|
||||
.gitignore \
|
||||
.gitmodules \
|
||||
.pre-commit-config.yaml \
|
||||
assets/app-icon.png \
|
||||
assets/superpowers-small.svg \
|
||||
evals/drill/README.md \
|
||||
@@ -643,6 +662,8 @@ main() {
|
||||
assert_not_contains "$preview_section" ".private-journal/leak.txt" "Preview excludes ignored untracked file"
|
||||
assert_not_contains "$preview_section" "ignored-cache/" "Preview excludes pure ignored directories"
|
||||
assert_not_contains "$preview_section" "evals/" "Preview excludes eval harness"
|
||||
assert_not_contains "$preview_section" ".gitmodules" "Preview excludes repo submodule metadata"
|
||||
assert_not_contains "$preview_section" ".pre-commit-config.yaml" "Preview excludes repo pre-commit config"
|
||||
assert_not_contains "$preview_output" "Overlay file (.codex-plugin/plugin.json) will be regenerated" "Preview omits overlay regeneration note"
|
||||
assert_not_contains "$preview_output" "Assets (superpowers-small.svg, app-icon.png) will be seeded from" "Preview omits assets seeding note"
|
||||
assert_contains "$preview_section" "skills/example/SKILL.md" "Preview reflects dirty tracked destination file"
|
||||
|
||||
61
tests/codex/test-marketplace-manifest.sh
Executable file
61
tests/codex/test-marketplace-manifest.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
MARKETPLACE="$REPO_ROOT/.agents/plugins/marketplace.json"
|
||||
|
||||
python3 - "$MARKETPLACE" "$REPO_ROOT" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
marketplace_path = Path(sys.argv[1])
|
||||
repo_root = Path(sys.argv[2])
|
||||
|
||||
if not marketplace_path.exists():
|
||||
raise AssertionError(".agents/plugins/marketplace.json must exist")
|
||||
|
||||
marketplace = json.loads(marketplace_path.read_text(encoding="utf-8"))
|
||||
|
||||
def assert_equal(actual, expected, label):
|
||||
if actual != expected:
|
||||
raise AssertionError(f"{label}: expected {expected!r}, got {actual!r}")
|
||||
|
||||
assert_equal(marketplace.get("name"), "superpowers-dev", "marketplace name")
|
||||
assert_equal(
|
||||
marketplace.get("interface", {}).get("displayName"),
|
||||
"Superpowers Dev",
|
||||
"marketplace display name",
|
||||
)
|
||||
|
||||
plugins = marketplace.get("plugins")
|
||||
if not isinstance(plugins, list):
|
||||
raise AssertionError("plugins must be a list")
|
||||
|
||||
matching_plugins = [plugin for plugin in plugins if plugin.get("name") == "superpowers"]
|
||||
assert_equal(len(matching_plugins), 1, "superpowers plugin entry count")
|
||||
|
||||
plugin = matching_plugins[0]
|
||||
assert_equal(plugin.get("source"), {"source": "url", "url": "./"}, "plugin source")
|
||||
assert_equal(
|
||||
plugin.get("policy"),
|
||||
{"installation": "AVAILABLE", "authentication": "ON_INSTALL"},
|
||||
"plugin policy",
|
||||
)
|
||||
assert_equal(plugin.get("category"), "Developer Tools", "plugin category")
|
||||
|
||||
plugin_manifest = repo_root / ".codex-plugin" / "plugin.json"
|
||||
if not plugin_manifest.exists():
|
||||
raise AssertionError(".codex-plugin/plugin.json must exist")
|
||||
|
||||
manifest = json.loads(plugin_manifest.read_text(encoding="utf-8"))
|
||||
assert_equal(manifest.get("name"), plugin.get("name"), "plugin manifest name")
|
||||
assert_equal(
|
||||
manifest.get("hooks"),
|
||||
"./hooks/hooks-codex.json",
|
||||
"Codex hooks manifest",
|
||||
)
|
||||
|
||||
print("Codex marketplace manifest looks good")
|
||||
PY
|
||||
Reference in New Issue
Block a user