Compare commits

..

9 Commits

Author SHA1 Message Date
Jesse Vincent
367f0e81a6 Remove openai codex hooks - the UX is bad and codex is good about triggering 2026-06-24 16:05:05 -07:00
Jesse Vincent
b18647194e Gemini CLI has been EOLed by Google as of 6/18/26 2026-06-24 11:19:20 -07:00
Jesse Vincent
e24f65cf01 Continued pruning of tokens we shouldn't need for modern agents 2026-06-24 11:15:14 -07:00
Jesse Vincent
f8997c7aa8 Rremove a ton of agentic boilerplate around per-harness tools 2026-06-24 11:06:52 -07:00
Jesse Vincent
b3b4535dcd Some manual tweaking of the bootstrap 2026-06-24 11:02:40 -07:00
Jesse Vincent
274cf1617b WIP snapshot: compress using-superpowers bootstrap (~1698 → ~993 tokens)
Condense the injected bootstrap without dropping behavior-shaping content:
replace the graphviz flowchart with prose (1% rule, plan-mode->brainstorm gate,
announce + checklist->todos), fold Instruction-Priority into User Instructions,
and drop the per-platform skill-loading section. Keep the full Red Flags
rationalization table (all 12), the platform tool-mapping pointer (now a
skill-relative path list), skill priority framed as a category (process before
implementation, not just the two named skills), and user-instruction precedence
plus the WHAT/HOW guard.

Snapshot on a local branch for review — NOT final, NOT for dev. Open items:
the dropped "Skill Types" (rigid/flexible) section, and evals for user
preferences overriding skills. Clean Docker evals show the more-aggressive
g-minimal preserves load-bearing triggering across opus/sonnet/haiku/codex/
gemini/kimi; this variant keeps more content.

Claude-Session: https://claude.ai/code/session_01Hz32pBE7kiqr78DY6PWmZN
2026-06-24 10:54:25 -07:00
Ada Sen
321c8cd24c fix(codex): stop bootstrap re-firing on resume (match Claude startup|clear|compact)
Bug: the SessionStart hook matcher in hooks-codex.json included "resume",
causing the superpowers bootstrap to re-fire on every Codex session resume.

Fix: align with Claude's hooks/hooks.json matcher "startup|clear|compact":
- drop "resume" (the bug: resume should not trigger re-bootstrap)
- add "compact" (so bootstrap re-injects after context compaction, like Claude)

Before: "matcher": "startup|resume|clear"
After:  "matcher": "startup|clear|compact"
2026-06-23 16:15:56 -07:00
Jesse Vincent
bfa3e4137a Keep Codex hooks manifest in plugin metadata
Prompt: Jesse questioned whether the PR should remove the hooks config from the Codex plugin manifest.

Runtime investigation showed Codex accepts a committed plugin manifest with hooks and installs the plugin successfully. Removing the field changes behavior: Codex falls back to the default hooks/hooks.json, which uses the non-Codex session-start hook and CLAUDE_PLUGIN_ROOT path, instead of hooks/hooks-codex.json and the session-start-codex script.

Changes: restore .codex-plugin/plugin.json hooks to ./hooks/hooks-codex.json and update the Codex marketplace manifest test to require that Codex-specific hook pointer instead of rejecting hooks.

Validation: bash tests/codex/test-marketplace-manifest.sh; scripts/lint-shell.sh tests/codex/test-marketplace-manifest.sh; bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh; bash tests/kimi/test-plugin-manifest.sh; bash tests/shell-lint/test-lint-shell.sh.
2026-06-22 11:51:28 -07:00
Jesse Vincent
a17aaaef3a Add Codex marketplace manifest
Prompt: Jesse asked for a new worktree off the local superpowers dev branch to add the Codex manifest after diagnosing why github.com/obra/superpowers did not show installable Codex plugins.

Root cause: Codex marketplace sources expect a .agents/plugins/marketplace.json at the marketplace root. The superpowers repo only had the Claude marketplace file and the Codex plugin manifest, so Codex could configure the marketplace name but found no installable plugin entries.

Changes: add a repo-local Codex marketplace manifest for superpowers-dev that points at this same repository root via the same-root source pattern Codex already accepts; add a focused marketplace manifest test; remove the unsupported hooks field from .codex-plugin/plugin.json so the plugin validator accepts the manifest.

Validation: bash tests/codex/test-marketplace-manifest.sh; uv run --with PyYAML python /Users/jesse/.codex/skills/.system/plugin-creator/scripts/validate_plugin.py /Users/jesse/git/superpowers/superpowers/.worktrees/codex-marketplace-manifest; throwaway HOME codex plugin marketplace add/list/add; bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh; bash tests/kimi/test-plugin-manifest.sh; bash tests/shell-lint/test-lint-shell.sh; scripts/lint-shell.sh tests/codex/test-marketplace-manifest.sh.
2026-06-22 11:51:28 -07:00
27 changed files with 323 additions and 730 deletions

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "superpowers",
"version": "6.1.1",
"version": "6.0.3",
"description": "An agentic skills framework & software development methodology that works: planning, TDD, debugging, and collaboration workflows.",
"author": {
"name": "Jesse Vincent",
@@ -21,13 +21,12 @@
"workflow"
],
"skills": "./skills/",
"hooks": {},
"interface": {
"displayName": "Superpowers",
"shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents",
"longDescription": "Use Superpowers to guide agent work through brainstorming, implementation planning, test-driven development, systematic debugging, parallel execution, code review, and finish-the-branch workflows.",
"developerName": "Jesse Vincent",
"category": "Developer Tools",
"category": "Coding",
"capabilities": [
"Interactive",
"Read",

View File

@@ -2,7 +2,7 @@
"name": "superpowers",
"displayName": "Superpowers",
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
"version": "6.1.1",
"version": "6.0.3",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com"

View File

@@ -1,6 +1,6 @@
{
"name": "superpowers",
"version": "6.1.1",
"version": "6.0.3",
"description": "An agentic skills framework and software development methodology.",
"author": {
"name": "Jesse Vincent",

View File

@@ -1,34 +1,5 @@
# Superpowers Release Notes
## v6.1.1 (2026-07-02)
### Codex
- **Codex no longer re-registers the Claude SessionStart hook.** v6.1.0 removed the Codex hook config and its manifest `hooks` pointer, meaning to stop Codex from installing a SessionStart hook — but with no `hooks` field, Codex fell back to auto-discovering `hooks/hooks.json`, the Claude Code SessionStart hook that the marketplace ships from the repo root, and re-registered it along with its install-time trust prompt. The Codex manifest now declares an explicit empty hooks object (`hooks: {}`), which Codex reads as "no hooks" instead of reaching the auto-discovery fallback. An absent field, `[]`, and an empty inline list all collapse back to the fallback, so the value has to be exactly `{}`.
- **Removed orphaned Codex session-start dead code.** `hooks/session-start-codex` had no caller once the Codex hook config was deleted, so it and its redundant test cases are gone. The worked shell-hook example in `docs/porting-to-a-new-harness.md` moves from Codex — now native skill discovery with no session-start hook — to Cursor, a live shell-hook harness, and the stale `hooks-codex.json` pointer in `docs/windows/polyglot-hooks.md` is corrected. The Codex plugin category is also fixed to "Developer Tools".
### Packaging
- **New `package-codex-plugin.sh` for building the Codex portal package.** A maintainer script produces a deterministic Codex "portal" archive — `.zip` by default, `tar.gz` on request — that normalizes entry timestamps, preserves executable modes, verifies every packaged skill ships its OpenAI metadata, includes the app and composer icons, and refuses to run against a dirty worktree. The packaged manifest keeps the source `hooks: {}` object so a portal-installed plugin avoids the same SessionStart auto-discovery, and the script can rebuild a byte-identical archive from a saved metadata source. Covered by a new test suite.
## v6.1.0 (2026-06-30)
### Lower Per-Session Token Cost
The `using-superpowers` bootstrap is injected into every session, so its size is paid for constantly. This release trims it and the per-harness references it points to, without dropping behavior-shaping content.
- **Compressed the `using-superpowers` bootstrap.** Replaced the graphviz skill-flow diagram with the prose it encoded, folded the standalone Instruction-Priority section into User Instructions, dropped the per-platform "How to Access Skills" walkthrough, and trimmed the Platform Adaptation pointer to the harnesses that still ship a reference file. The full Red Flags rationalization table and the user-instruction precedence rules are unchanged.
- **Pruned the per-harness tool-mapping references.** The verbose action-to-tool tables restated guidance modern agents already follow. Each reference file is trimmed to the harness-specific notes that still carry weight — subagent dispatch, task tracking, instructions-file paths — and `claude-code-tools.md` and `copilot-tools.md`, which had nothing harness-specific left, are deleted.
### Codex
- **Codex can install from the marketplace.** Codex marketplace sources expect a `.agents/plugins/marketplace.json` at the marketplace root; the repo only shipped the Claude marketplace file, so Codex could name the marketplace but found no installable plugin entries. A repo-local Codex marketplace manifest now points at the same repository root, so the plugin is installable from Codex.
- **Codex no longer ships a SessionStart hook.** Codex reliably triggers skills on its own, and the bootstrap hook made the UX worse rather than better. The Codex hook config (`hooks-codex.json`) and its manifest registration are removed.
### Harness Support
- **Gemini CLI support removed.** Google EOLed the Gemini CLI on 2026-06-18; the extension can no longer be installed or updated. Gemini is gone from the install docs, the subagent-capable platform lists, and the eval-harness description, and its tool-mapping reference is deleted.
## v6.0.3 (2026-06-18)
### Subagent-Driven Development

View File

@@ -90,7 +90,7 @@ every session, with no per-session opt-in by your human partner.** This is the
one non-negotiable capability. It can take any form:
- a **hook/event system** that runs a shell command at session start and reads
its stdout (Claude Code, Cursor, Copilot CLI), or
its stdout (Claude Code, Codex, Cursor, Copilot CLI), or
- an **in-process plugin/extension** with a session-start or message lifecycle
callback that can mutate the message array (OpenCode, pi), or
- an **instructions-file** convention where the harness loads a context file that
@@ -227,20 +227,18 @@ you may **not** do is bridge a gap by editing the user's global config.
The harness has a hook system that runs a shell command at session start and
reads JSON from its stdout. The configured command runs `run-hook.cmd`, a
polyglot wrapper that just locates bash and dispatches the named script; the
script (`hooks/session-start`, or a harness-specific variant) is what reads
`using-superpowers/SKILL.md` and prints a JSON object whose **field name and
nesting differ per harness**.
script (`hooks/session-start`, or a harness-specific variant like
`hooks/session-start-codex`) is what reads `using-superpowers/SKILL.md` and
prints a JSON object whose **field name and nesting differ per harness**.
- Reference: `hooks/session-start`, `hooks/run-hook.cmd`, and the per-harness
hook config `hooks/hooks.json` (Claude Code) and `hooks/hooks-cursor.json`
- Reference: `hooks/session-start` (and `hooks/session-start-codex`),
`hooks/run-hook.cmd`, and the per-harness hook config `hooks/hooks.json`
(Claude Code), `hooks/hooks-codex.json` (Codex), `hooks/hooks-cursor.json`
(Cursor).
- Manifests: `.cursor-plugin/plugin.json` is the Shape A manifest example that
points the harness at `./skills/` and the right `hooks-*.json`. Claude Code's
- Manifests: `.codex-plugin/plugin.json`, `.cursor-plugin/plugin.json` point the
harness at `./skills/` and the right `hooks-*.json`. (Claude Code's
`.claude-plugin/plugin.json` sets neither field — it auto-discovers `skills/`
and `hooks/hooks.json` by convention. Do **not** copy Codex's
`.codex-plugin/plugin.json` for Shape A: it declares an empty `hooks` object
specifically to suppress Codex's `hooks/hooks.json` auto-discovery, because
Codex surfaces skills natively and runs no session-start hook.
and `hooks/hooks.json` by convention.)
> **A hook *system* is not a session-start *event*.** A harness can have a
> `hooks.json` mechanism — and even contain the literal string `SessionStart` in
@@ -289,7 +287,7 @@ part of the installed extension** — never substitute "edit the user's global
| If the harness… | Use shape | Copy from |
|---|---|---|
| runs a shell command at session start and reads its stdout | A (shell-hook) | Cursor (`hooks/session-start` + `hooks/hooks-cursor.json` + `.cursor-plugin/`) |
| runs a shell command at session start and reads its stdout | A (shell-hook) | Codex (`hooks/session-start-codex` + `hooks/hooks-codex.json` + `.codex-plugin/`) |
| is a JS/TS plugin host with session/message lifecycle callbacks | B (in-process) | OpenCode (`.opencode/`) — or pi (`.pi/`) if it has no native skill tool |
| ships an extension-declared context file it always loads | C (instructions-file) | Gemini (`gemini-extension.json` + `GEMINI.md` + `references/gemini-tools.md`) |
| has a plugin install command and a manifest `contextFileName` (or equivalent) the installer keeps | C via the plugin installer | Antigravity (`.antigravity-plugin/``agy plugin install` ships a generated context file; verify the installer preserves it — Part 6) |
@@ -311,7 +309,7 @@ patterns below are summaries; the code is the spec.
Create whatever the harness uses to recognize the plugin. Match the existing
ones in spirit:
- **Shape A:** a `*-plugin/plugin.json` (see `.cursor-plugin/plugin.json`) with
- **Shape A:** a `*-plugin/plugin.json` (see `.codex-plugin/plugin.json`) with
`name`, `version`, `description`, author/license/keywords, `"skills":
"./skills/"`, and `"hooks": "./hooks/hooks-<harness>.json"`. Plus the
`hooks-<harness>.json` itself, registering a session-start hook whose command
@@ -377,24 +375,25 @@ both double-injects). Find the
exact field, nesting, and event-matcher values your harness expects. Then
decide: add a fourth branch to `hooks/session-start`, or — if the harness needs
a different bootstrap message or env contract — add a dedicated
`hooks/session-start-<harness>` script. If you add a branch
`hooks/session-start-<harness>` script, the way Codex did. If you add a branch
and your harness *also* sets an env var an earlier branch keys on (some harnesses
set `CLAUDE_PLUGIN_ROOT` too), order your branch before the one that would
otherwise shadow it. Match the harness's
own event-matcher strings (Claude Code uses `startup|clear|compact`, Cursor
`sessionStart`); wrong matchers mean the hook silently never fires.
own event-matcher strings (Claude Code uses `startup|clear|compact`, Codex
`startup|resume|clear`, Cursor `sessionStart`); wrong matchers mean the hook
silently never fires.
The **hook-config schema itself varies per harness** — don't assume the
Claude Code shape is universal. Compare `hooks/hooks.json` and
`hooks/hooks-cursor.json`: Cursor's uses
Claude/Codex shape is universal. Compare `hooks/hooks.json`,
`hooks/hooks-codex.json`, and `hooks/hooks-cursor.json`: Cursor's uses
`"version": 1`, a lowercase `sessionStart` key, a relative
`./hooks/run-hook.cmd` command, and omits the `matcher`/`type`/`async` fields
Claude Code uses. Match your `hooks-<harness>.json` to whichever existing file is
`./hooks/run-hook.cmd` command, and omits the `matcher`/`type`/`async` fields the
others use. Match your `hooks-<harness>.json` to whichever existing file is
closest, not to a single canonical template.
The hook **command string references a harness-provided plugin-root variable**,
and its name differs per harness: `hooks.json` uses `${CLAUDE_PLUGIN_ROOT}`,
`hooks-cursor.json` uses a relative path. Use
`hooks-codex.json` uses `${PLUGIN_ROOT}`, Cursor uses a relative path. Use
whatever your harness exports. (The `session-start` script re-derives the root
itself via `dirname`, so the script body doesn't depend on this — but the
command in the manifest does.)
@@ -785,7 +784,7 @@ Use this as the live index; when in doubt, read the files, not this table.
| Harness | Entry point | Bootstrap mechanism | Tool mapping | Tests | Distribution |
|---|---|---|---|---|---|
| Claude Code | `.claude-plugin/plugin.json` + `hooks/hooks.json` | shell hook → `hooks/session-start` (`hookSpecificOutput.additionalContext`) | native `Skill` tool; `references/claude-code-tools.md` | `tests/hooks/` | marketplace |
| Codex | `.codex-plugin/plugin.json` (declares empty `hooks`) | native skill discovery (no session-start hook) | `references/codex-tools.md` | `tests/codex/`, `tests/codex-plugin-sync/` | fork sync (`scripts/sync-to-codex-plugin.sh`) |
| Codex | `.codex-plugin/plugin.json` + `hooks/hooks-codex.json` | shell hook → `hooks/session-start-codex` | `references/codex-tools.md` | `tests/codex-plugin-sync/`, `tests/hooks/` | fork sync (`scripts/sync-to-codex-plugin.sh`) |
| Cursor | `.cursor-plugin/plugin.json` + `hooks/hooks-cursor.json` | shell hook → `hooks/session-start` (`additional_context`) | `references/claude-code-tools.md` | `tests/hooks/` | hand-authored |
| Copilot CLI | (shares Claude Code hook path; `COPILOT_CLI` env) | shell hook → `hooks/session-start` (`additionalContext`) | `references/copilot-tools.md` | `tests/hooks/` | — |
| Gemini CLI | `gemini-extension.json` + `GEMINI.md` | instructions file `@`-includes bootstrap + mapping | `references/gemini-tools.md` | — | `gemini extensions install` |
@@ -800,10 +799,10 @@ Use this as the live index; when in doubt, read the files, not this table.
- **Wrong JSON field → silent failure or double injection.** Shape A only.
Confirm the exact field/nesting; Claude Code reads two fields without dedup.
- **Hook-config schema varies per harness.** Shape A. Cursor's `hooks-cursor.json`
looks nothing like the Claude Code one (`version`, lowercase `sessionStart`,
looks nothing like the Claude/Codex one (`version`, lowercase `sessionStart`,
relative command, no `matcher`/`type`/`async`). Match the closest existing file.
- **Plugin-root env var differs per harness.** Shape A. The hook command uses
`${CLAUDE_PLUGIN_ROOT}` (Claude) or a relative path
`${CLAUDE_PLUGIN_ROOT}` (Claude), `${PLUGIN_ROOT}` (Codex), or a relative path
(Cursor). Use what your harness exports; the script re-derives the root itself.
- **System-message injection.** Shape B injects a *user* message on purpose
(#750, #894). Don't "fix" it to a system message.

View File

@@ -140,7 +140,7 @@ Check that the script filename is **extensionless** in `hooks.json`. A command l
### Hook doesn't fire at all
Verify the `matcher` in `hooks.json` matches the event type your harness emits. Claude Code uses `startup|clear|compact`; Cursor uses `sessionStart`. Check `hooks-cursor.json` for the Cursor variant.
Verify the `matcher` in `hooks.json` matches the event type your harness emits. Claude Code uses `startup|clear|compact`; Codex uses `startup|resume|clear`. Check `hooks-codex.json` for the Codex variant.
## Related Issues

View File

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

26
hooks/session-start-codex Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Codex SessionStart hook for superpowers plugin
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill")
escape_for_json() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\r'/\\r}"
s="${s//$'\t'/\\t}"
printf '%s' "$s"
}
using_superpowers_escaped=$(escape_for_json "$using_superpowers_content")
session_context="<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, follow the Codex skill-loading instructions in that skill:**\n\n${using_superpowers_escaped}\n</EXTREMELY_IMPORTANT>"
printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$session_context" | cat
exit 0

View File

@@ -1,6 +1,6 @@
{
"name": "superpowers",
"version": "6.1.1",
"version": "6.0.3",
"description": "Superpowers skills and runtime bootstrap for coding agents",
"type": "module",
"main": ".opencode/plugins/superpowers.js",

View File

@@ -1,342 +0,0 @@
#!/usr/bin/env bash
#
# Package the Superpowers Codex plugin as a rootless archive for portal upload.
#
# The Codex portal artifact differs from the old openai/plugins sync flow:
# it is a standalone archive, but it still needs the OpenAI-owned
# skills/*/agents/openai.yaml metadata that used to be preserved from the
# destination plugin repo. Seed that metadata from a prior official package.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
REF="HEAD"
OUTPUT=""
FORMAT=""
METADATA_SOURCE=""
ALLOW_DIRTY=0
KEEP_STAGE=0
usage() {
cat <<'EOF'
Usage:
scripts/package-codex-plugin.sh [options]
Options:
--output PATH Write archive to PATH.
Default: ../_tmp/sup-codex-packaging/superpowers-VERSION.zip
--format FORMAT Archive format: zip or tar.gz. Default: zip.
If --output ends in .zip, .tar.gz, or .tgz, that
extension is used when --format is omitted.
--metadata-source PATH Prior official package directory, .zip, or .tar.gz used to
seed skills/*/agents/openai.yaml.
Default: ../_tmp/sup-codex-packaging/superpowers,
falling back to superpowers.zip, then superpowers.tar.gz
--ref REF Git ref to package. Default: HEAD.
--allow-dirty Permit a dirty working tree. The archive still uses --ref.
--keep-stage Print and keep the temporary staging directory.
-h, --help Show this help.
The archive is rootless: .codex-plugin/, assets/, skills/, README.md, LICENSE,
and CODE_OF_CONDUCT.md sit at the archive root. Source-only repo files, hooks, tests,
docs, and other harness manifests are intentionally not shipped.
EOF
}
die() {
echo "ERROR: $*" >&2
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
--output)
[[ $# -ge 2 ]] || die "--output requires a path"
OUTPUT="$2"
shift 2
;;
--format)
[[ $# -ge 2 ]] || die "--format requires a value"
case "$2" in
zip)
FORMAT="zip"
;;
tar.gz|tgz)
FORMAT="tar.gz"
;;
*)
die "--format must be zip or tar.gz"
;;
esac
shift 2
;;
--metadata-source)
[[ $# -ge 2 ]] || die "--metadata-source requires a path"
METADATA_SOURCE="$2"
shift 2
;;
--ref)
[[ $# -ge 2 ]] || die "--ref requires a value"
REF="$2"
shift 2
;;
--allow-dirty)
ALLOW_DIRTY=1
shift
;;
--keep-stage)
KEEP_STAGE=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown arg: $1" >&2
usage >&2
exit 2
;;
esac
done
infer_format_from_output() {
local output_path="$1"
case "$output_path" in
*.tar.gz|*.tgz)
printf '%s\n' "tar.gz"
;;
*.zip)
printf '%s\n' "zip"
;;
*)
return 1
;;
esac
}
if [[ -z "$FORMAT" ]]; then
FORMAT="$(infer_format_from_output "$OUTPUT" || true)"
if [[ -z "$FORMAT" ]]; then
FORMAT="zip"
fi
else
output_format="$(infer_format_from_output "$OUTPUT" || true)"
if [[ -n "$output_format" && "$output_format" != "$FORMAT" ]]; then
die "--output extension does not match --format $FORMAT: $OUTPUT"
fi
fi
command -v git >/dev/null || die "git not found in PATH"
command -v jq >/dev/null || die "jq not found in PATH"
command -v tar >/dev/null || die "tar not found in PATH"
command -v gzip >/dev/null || die "gzip not found in PATH"
command -v shasum >/dev/null || die "shasum not found in PATH"
if [[ "$FORMAT" == "zip" ]]; then
command -v zip >/dev/null || die "zip not found in PATH"
command -v unzip >/dev/null || die "unzip not found in PATH"
fi
[[ -d "$REPO_ROOT/.git" ]] || die "repo root is not a git checkout: $REPO_ROOT"
git -C "$REPO_ROOT" rev-parse --verify "$REF^{commit}" >/dev/null ||
die "git ref does not resolve to a commit: $REF"
if [[ "$ALLOW_DIRTY" -ne 1 ]]; then
dirty_status="$(git -C "$REPO_ROOT" status --porcelain --untracked-files=all)"
if [[ -n "$dirty_status" ]]; then
echo "Working tree has uncommitted changes:" >&2
printf '%s\n' "$dirty_status" | sed 's/^/ /' >&2
die "commit or stash changes first, or pass --allow-dirty to package $REF anyway"
fi
fi
if [[ -z "$METADATA_SOURCE" ]]; then
if [[ -d "$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers" ]]; then
METADATA_SOURCE="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers"
elif [[ -f "$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers.zip" ]]; then
METADATA_SOURCE="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers.zip"
elif [[ -f "$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers.tar.gz" ]]; then
METADATA_SOURCE="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers.tar.gz"
else
die "no metadata source found; pass --metadata-source <prior package dir, zip, or tar.gz>"
fi
fi
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/superpowers-codex-package.XXXXXX")"
STAGE="$WORK_DIR/payload"
METADATA_WORK="$WORK_DIR/metadata"
ARCHIVE_LIST="$WORK_DIR/archive-list"
cleanup() {
if [[ "$KEEP_STAGE" -eq 1 ]]; then
echo "Keeping staging directory: $WORK_DIR" >&2
else
rm -rf "$WORK_DIR"
fi
}
trap cleanup EXIT
mkdir -p "$STAGE" "$METADATA_WORK"
metadata_root_from_dir() {
local candidate="$1"
local nested
if [[ -d "$candidate/skills" ]]; then
printf '%s\n' "$candidate"
return 0
fi
nested="$(find "$candidate" -mindepth 2 -maxdepth 2 -type d -name skills -print -quit)"
if [[ -n "$nested" ]]; then
dirname "$nested"
return 0
fi
return 1
}
prepare_metadata_root() {
local source="$1"
local root
if [[ -d "$source" ]]; then
root="$(cd "$source" && pwd)"
elif [[ -f "$source" ]]; then
case "$source" in
*.tar.gz|*.tgz)
tar -xzf "$source" -C "$METADATA_WORK"
root="$METADATA_WORK"
;;
*.zip)
command -v unzip >/dev/null || die "unzip not found in PATH"
unzip -q "$source" -d "$METADATA_WORK"
root="$METADATA_WORK"
;;
*)
die "metadata source must be a directory, .zip, or .tar.gz: $source"
;;
esac
else
die "metadata source does not exist: $source"
fi
metadata_root_from_dir "$root" ||
die "metadata source does not contain a skills/ directory: $source"
}
METADATA_ROOT="$(prepare_metadata_root "$METADATA_SOURCE")"
git -C "$REPO_ROOT" archive --format=tar "$REF" -- \
.codex-plugin \
CODE_OF_CONDUCT.md \
LICENSE \
README.md \
assets \
skills \
| tar -xf - -C "$STAGE"
VERSION="$(jq -r '.version // empty' "$STAGE/.codex-plugin/plugin.json")"
[[ -n "$VERSION" ]] || die "could not read version from .codex-plugin/plugin.json"
if [[ -z "$OUTPUT" ]]; then
case "$FORMAT" in
zip)
OUTPUT="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers-$VERSION.zip"
;;
tar.gz)
OUTPUT="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers-$VERSION.tar.gz"
;;
esac
fi
mkdir -p "$(dirname "$OUTPUT")"
OUTPUT="$(cd "$(dirname "$OUTPUT")" && pwd)/$(basename "$OUTPUT")"
missing_metadata=0
while IFS= read -r skill_dir; do
skill_name="${skill_dir##*/}"
metadata_file="$METADATA_ROOT/skills/$skill_name/agents/openai.yaml"
if [[ ! -f "$metadata_file" ]]; then
echo "Missing OpenAI agent metadata for skill: $skill_name" >&2
missing_metadata=1
continue
fi
mkdir -p "$skill_dir/agents"
cp "$metadata_file" "$skill_dir/agents/openai.yaml"
done < <(find "$STAGE/skills" -mindepth 1 -maxdepth 1 -type d -print | sort)
if [[ "$missing_metadata" -ne 0 ]]; then
die "metadata source is incomplete"
fi
skill_count="$(find "$STAGE/skills" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')"
metadata_count="$(find "$STAGE/skills" -path '*/agents/openai.yaml' -type f | wc -l | tr -d ' ')"
[[ "$skill_count" == "$metadata_count" ]] ||
die "metadata count mismatch: $metadata_count metadata files for $skill_count skills"
(
cd "$STAGE"
{
find . -mindepth 1 -type d | sed 's#^\./##' | LC_ALL=C sort
find . -mindepth 1 -type f | sed 's#^\./##' | LC_ALL=C sort
} >"$ARCHIVE_LIST"
)
case "$FORMAT" in
zip)
# ZIP cannot represent dates earlier than 1980.
TZ=UTC find "$STAGE" -exec touch -t 198001010000 {} +
(
cd "$STAGE"
rm -f "$OUTPUT"
COPYFILE_DISABLE=1 zip -X -q - -@ <"$ARCHIVE_LIST" >"$OUTPUT"
)
;;
tar.gz)
# Match the prior official archive's deterministic tar entry metadata.
TZ=UTC find "$STAGE" -exec touch -t 197001010000 {} +
(
cd "$STAGE"
rm -f "$OUTPUT"
COPYFILE_DISABLE=1 tar -cf - --no-recursion --format ustar --uid 0 --gid 0 --uname '' --gname '' -T "$ARCHIVE_LIST" |
gzip -9n >"$OUTPUT"
)
;;
esac
if command -v xattr >/dev/null 2>&1; then
xattr -c "$OUTPUT" 2>/dev/null || true
fi
case "$FORMAT" in
zip)
archive_paths="$(unzip -Z1 "$OUTPUT" | sed 's#/$##')"
;;
tar.gz)
archive_paths="$(tar -tzf "$OUTPUT")"
;;
esac
unexpected_paths="$(
printf '%s\n' "$archive_paths" |
grep -E '(^superpowers/|^\.agents/|^hooks/|package\.json$|^\.git|^\.pytest_cache|^\.ruff_cache|^scripts/|^tests/|^docs/|^evals/|^lib/|^\.claude|^\.cursor|^\.kimi|^\.opencode|^\.pi|^AGENTS\.md$|^CLAUDE\.md$|^GEMINI\.md$|^RELEASE-NOTES\.md$|^CHANGELOG\.md$)' || true
)"
if [[ -n "$unexpected_paths" ]]; then
printf '%s\n' "$unexpected_paths" | sed 's/^/ /' >&2
die "archive contains source-only paths"
fi
entry_count="$(printf '%s\n' "$archive_paths" | wc -l | tr -d ' ')"
checksum="$(shasum -a 256 "$OUTPUT" | awk '{print $1}')"
echo "Archive: $OUTPUT"
echo "Format: $FORMAT"
echo "Version: $VERSION"
echo "Entries: $entry_count"
echo "Skills: $skill_count"
echo "SHA-256: $checksum"

View File

@@ -77,7 +77,6 @@ digraph brainstorming {
- Propose 2-3 different approaches with trade-offs
- Present options conversationally with your recommendation and reasoning
- Lead with your recommended option and explain why
- YAGNI ruthlessly - remove unnecessary features from every approach and design
**Presenting the design:**
@@ -131,6 +130,15 @@ Wait for the user's response. If they request changes, make them and re-run the
- Invoke the writing-plans skill to create a detailed implementation plan
- Do NOT invoke any other skill. writing-plans is the next step.
## Key Principles
- **One question at a time** - Don't overwhelm with multiple questions
- **Multiple choice preferred** - Easier to answer than open-ended when possible
- **YAGNI ruthlessly** - Remove unnecessary features from all designs
- **Explore alternatives** - Always propose 2-3 approaches before settling
- **Incremental validation** - Present design, get approval before moving on
- **Be flexible** - Go back and clarify when something doesn't make sense
## Visual Companion
A browser-based companion for showing mockups, diagrams, and visual options during brainstorming. Available as a tool — not a mode. Accepting the companion means it's available for questions that benefit from visual treatment; it does NOT mean every question goes through the browser.

View File

@@ -158,6 +158,15 @@ Agent 3 → Fix tool-approval-race-conditions.test.ts
**Integration:** All fixes independent, no conflicts, full suite green
**Time saved:** 3 problems solved in parallel vs sequentially
## Key Benefits
1. **Parallelization** - Multiple investigations happen simultaneously
2. **Focus** - Each agent has narrow scope, less context to track
3. **Independence** - Agents don't interfere with each other
4. **Speed** - 3 problems solved in time of 1
## Verification
After agents return:
@@ -165,3 +174,12 @@ After agents return:
2. **Check for conflicts** - Did agents edit same code?
3. **Run full suite** - Verify all fixes work together
4. **Spot check** - Agents can make systematic errors
## Real-World Impact
From debugging session (2025-10-03):
- 6 failures across 3 files
- 3 agents dispatched in parallel
- All investigations completed concurrently
- All fixes integrated successfully
- Zero conflicts between agent changes

View File

@@ -11,7 +11,7 @@ Load plan, review critically, execute all tasks, report when complete.
**Announce at start:** "I'm using the executing-plans skill to implement this plan."
**Note:** Tell your human partner that Superpowers works much better with access to subagents (Claude Code, Codex CLI, Codex App, and Copilot CLI all qualify; see the per-platform tool refs in `../using-superpowers/references/`). If subagents are available, use superpowers:subagent-driven-development instead of this skill.
**Note:** Tell your human partner that Superpowers works much better with access to subagents. The quality of its work will be significantly higher if run on a platform with subagent support (Claude Code, Codex CLI, Codex App, and Copilot CLI all qualify; see the per-platform tool refs in `../using-superpowers/references/`). If subagents are available, use superpowers:subagent-driven-development instead of this skill.
## The Process

View File

@@ -203,3 +203,11 @@ You understand 1,2,3,6. Unclear on 4,5.
## GitHub Thread Replies
When replying to inline review comments on GitHub, reply in the comment thread (`gh api repos/{owner}/{repo}/pulls/{pr}/comments/{id}/replies`), not as a top-level PR comment.
## The Bottom Line
**External feedback = suggestions to evaluate, not orders to follow.**
Verify. Question. Then implement.
No performative agreement. Technical rigor always.

View File

@@ -5,7 +5,7 @@ description: Use when completing tasks, implementing major features, or before m
# Requesting Code Review
Dispatch a code reviewer subagent to catch issues before they cascade. The reviewer gets precisely crafted context for evaluation — never your session's history.
Dispatch a code reviewer subagent to catch issues before they cascade. The reviewer gets precisely crafted context for evaluation — never your session's history. This keeps the reviewer focused on the work product, not your thought process, and preserves your own context for continued work.
**Core principle:** Review early, review often.
@@ -72,6 +72,21 @@ You: [Fix progress indicators]
[Continue to Task 3]
```
## Integration with Workflows
**Subagent-Driven Development:**
- Review after EACH task
- Catch issues before they compound
- Fix before moving to next task
**Executing Plans:**
- Review after each task or at natural checkpoints
- Get feedback, apply, continue
**Ad-Hoc Development:**
- Review before merge
- Review when stuck
## Red Flags
**Never:**

View File

@@ -332,6 +332,38 @@ Final reviewer: All requirements met, ready to merge
Done!
```
## Advantages
**vs. Manual execution:**
- Subagents follow TDD naturally
- Fresh context per task (no confusion)
- Parallel-safe (subagents don't interfere)
- Subagent can ask questions (before AND during work)
**vs. Executing Plans:**
- Same session (no handoff)
- Continuous progress (no waiting)
- Review checkpoints automatic
**Efficiency gains:**
- Controller curates exactly what context is needed; bulk artifacts move
as files, not pasted text
- Subagent gets complete information upfront
- Questions surfaced before work begins (not after)
**Quality gates:**
- Self-review catches issues before handoff
- Task review carries two verdicts: spec compliance and code quality
- Review loops ensure fixes actually work
- Spec compliance prevents over/under-building
- Code quality ensures implementation is well-built
**Cost:**
- More subagent invocations (implementer + reviewer per task)
- Controller does more prep work (extracting all tasks upfront)
- Review loops add iterations
- But catches issues early (cheaper than debugging later)
## Red Flags
**Never:**

View File

@@ -7,6 +7,8 @@ description: Use when encountering any bug, test failure, or unexpected behavior
## Overview
Random fixes waste time and create new bugs. Quick patches mask underlying issues.
**Core principle:** ALWAYS find root cause before attempting fixes. Symptom fixes are failure.
**Violating the letter of this process is violating the spirit of debugging.**
@@ -284,3 +286,11 @@ These techniques are part of systematic debugging and available in this director
**Related skills:**
- **superpowers:test-driven-development** - For creating failing test case (Phase 4, Step 1)
- **superpowers:verification-before-completion** - Verify fix worked before claiming success
## Real-World Impact
From debugging sessions:
- Systematic approach: 15-30 minutes to fix
- Random fixes approach: 2-3 hours of thrashing
- First-time fix rate: 95% vs 40%
- New bugs introduced: Near zero vs common

View File

@@ -203,6 +203,56 @@ Next failing test for next feature.
| **Clear** | Name describes behavior | `test('test1')` |
| **Shows intent** | Demonstrates desired API | Obscures what code should do |
## Why Order Matters
**"I'll write tests after to verify it works"**
Tests written after code pass immediately. Passing immediately proves nothing:
- Might test wrong thing
- Might test implementation, not behavior
- Might miss edge cases you forgot
- You never saw it catch the bug
Test-first forces you to see the test fail, proving it actually tests something.
**"I already manually tested all the edge cases"**
Manual testing is ad-hoc. You think you tested everything but:
- No record of what you tested
- Can't re-run when code changes
- Easy to forget cases under pressure
- "It worked when I tried it" ≠ comprehensive
Automated tests are systematic. They run the same way every time.
**"Deleting X hours of work is wasteful"**
Sunk cost fallacy. The time is already gone. Your choice now:
- Delete and rewrite with TDD (X more hours, high confidence)
- Keep it and add tests after (30 min, low confidence, likely bugs)
The "waste" is keeping code you can't trust. Working code without real tests is technical debt.
**"TDD is dogmatic, being pragmatic means adapting"**
TDD IS pragmatic:
- Finds bugs before commit (faster than debugging after)
- Prevents regressions (tests catch breaks immediately)
- Documents behavior (tests show how to use code)
- Enables refactoring (change freely, tests catch breaks)
"Pragmatic" shortcuts = debugging in production = slower.
**"Tests after achieve the same goals - it's spirit not ritual"**
No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"
Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones.
Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't).
30 minutes of tests after ≠ TDD. You get coverage, lose proof tests work.
## Common Rationalizations
| Excuse | Reality |

View File

@@ -156,12 +156,47 @@ Ready to implement <feature-name>
| Tests fail during baseline | Report failures + ask |
| No package.json/Cargo.toml | Skip dependency install |
## Common Rationalizations
## Common Mistakes
| Excuse | Reality |
|--------|---------|
| "I'm obviously not in a worktree — no need to check" | Run Step 0. Harness-created isolation and submodules both fool eyeballing; the detection commands settle it. |
| "`git worktree add` is quicker than hunting for a native tool" | A native tool (e.g. `EnterWorktree`) owns placement, branching, and cleanup. Bypassing it is the #1 mistake — it creates phantom state your harness can't see or manage. |
| "The worktree directory is surely ignored already" | Run `git check-ignore`. An unignored worktree directory commits the whole tree into the repo. |
| "Any directory name works" | Explicit instructions beat an existing project-local directory, which beats the `.worktrees/` default. |
| "The workspace is fresh — baseline tests can wait" | A dirty baseline makes every later failure ambiguous. Run the tests now; proceeding past failures is your human partner's call. |
### Fighting the harness
- **Problem:** Using `git worktree add` when the platform already provides isolation
- **Fix:** Step 0 detects existing isolation. Step 1a defers to native tools.
### Skipping detection
- **Problem:** Creating a nested worktree inside an existing one
- **Fix:** Always run Step 0 before creating anything
### Skipping ignore verification
- **Problem:** Worktree contents get tracked, pollute git status
- **Fix:** Always use `git check-ignore` before creating project-local worktree
### Assuming directory location
- **Problem:** Creates inconsistency, violates project conventions
- **Fix:** Follow priority: explicit instructions > existing project-local directory > default
### Proceeding with failing tests
- **Problem:** Can't distinguish new bugs from pre-existing issues
- **Fix:** Report failures, get explicit permission to proceed
## Red Flags
**Never:**
- Create a worktree when Step 0 detects existing isolation
- Use `git worktree add` when you have a native worktree tool (e.g., `EnterWorktree`). This is the #1 mistake — if you have it, use it.
- Skip Step 1a by jumping straight to Step 1b's git commands
- Create worktree without verifying it's ignored (project-local)
- Skip baseline test verification
- Proceed with failing tests without asking
**Always:**
- Run Step 0 detection first
- Prefer native tools over git fallback
- Follow directory priority: explicit instructions > existing project-local directory > default
- Verify directory is ignored for project-local
- Auto-detect and run project setup
- Verify clean test baseline

View File

@@ -7,6 +7,8 @@ description: Use when about to claim work is complete, fixed, or passing, before
## Overview
Claiming work is complete without verification is dishonesty, not efficiency.
**Core principle:** Evidence before claims, always.
**Violating the letter of this rule is violating the spirit of this rule.**
@@ -103,6 +105,15 @@ Skip any step = lying, not verifying
❌ Trust agent report
```
## Why This Matters
From 24 failure memories:
- your human partner said "I don't believe you" - trust broken
- Undefined functions shipped - would crash
- Missing requirements shipped - incomplete features
- Time wasted on false completion → redirect → rework
- Violates: "Honesty is a core value. If you lie, you'll be replaced."
## When To Apply
**ALWAYS before:**
@@ -118,3 +129,11 @@ Skip any step = lying, not verifying
- Paraphrases and synonyms
- Implications of success
- ANY communication suggesting completion/correctness
## The Bottom Line
**No shortcuts for verification.**
Run the command. Read the output. THEN claim the result.
This is non-negotiable.

View File

@@ -135,6 +135,12 @@ Every step must contain the actual content an engineer needs. These are **plan f
- Steps that describe what to do without showing how (code blocks required for code steps)
- References to types, functions, or methods not defined in any task
## Remember
- Exact file paths always
- Complete code in every step — if a step changes code, show the code
- Exact commands with expected output
- DRY, YAGNI, TDD, frequent commits
## Self-Review
After writing the complete plan, look at the spec with fresh eyes and check the plan against it. This is a checklist you run yourself — not a subagent dispatch.

View File

@@ -677,3 +677,13 @@ How future agents find your skill:
6. **Loads example** (only when implementing)
**Optimize for this flow** - put searchable terms early and often.
## The Bottom Line
**Creating skills IS TDD for process documentation.**
Same Iron Law: No skill without failing test first.
Same cycle: RED (baseline) → GREEN (write skill) → REFACTOR (close loopholes).
Same benefits: Better quality, fewer surprises, bulletproof results.
If you follow TDD for code, follow it for skills. It's the same discipline applied to documentation.

View File

@@ -51,25 +51,10 @@ if not plugin_manifest.exists():
manifest = json.loads(plugin_manifest.read_text(encoding="utf-8"))
assert_equal(manifest.get("name"), plugin.get("name"), "plugin manifest name")
# Codex auto-discovers a plugin's hooks/hooks.json whenever the Codex manifest
# has no `hooks` field: load_plugin_hooks falls back to a hardcoded
# DEFAULT_HOOKS_CONFIG_FILE = "hooks/hooks.json" and registers it. That file is
# the Claude Code SessionStart hook, it is tracked in this repo, and this
# marketplace installs the whole repo root (source url "./"), so on Codex the
# fallback re-registers the SessionStart hook and its install-time trust prompt.
# Declaring an empty inline hooks object ({}) parses as an empty inline hook set
# and suppresses the auto-discovery. An absent field, an empty array ([]), and
# an empty inline list all collapse back to the fallback, so the value must be
# exactly an empty object.
hooks_config = repo_root / "hooks" / "hooks.json"
if not hooks_config.exists():
raise AssertionError("hooks/hooks.json must exist (Claude Code SessionStart hook)")
assert_equal(
manifest.get("hooks"),
{},
"Codex manifest must declare empty hooks {} to suppress hooks/hooks.json auto-discovery",
"./hooks/hooks-codex.json",
"Codex hooks manifest",
)
print("Codex marketplace manifest looks good")

View File

@@ -1,292 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SCRIPT_UNDER_TEST="$REPO_ROOT/scripts/package-codex-plugin.sh"
FAILURES=0
TEST_ROOT="$(mktemp -d)"
cleanup() {
rm -rf "$TEST_ROOT"
}
trap cleanup EXIT
pass() {
echo " [PASS] $1"
}
fail() {
echo " [FAIL] $1"
FAILURES=$((FAILURES + 1))
}
assert_equals() {
local actual="$1"
local expected="$2"
local description="$3"
if [[ "$actual" == "$expected" ]]; then
pass "$description"
else
fail "$description"
echo " expected: $expected"
echo " actual: $actual"
fi
}
assert_contains() {
local haystack="$1"
local needle="$2"
local description="$3"
if printf '%s' "$haystack" | grep -Fq -- "$needle"; then
pass "$description"
else
fail "$description"
echo " expected to find: $needle"
fi
}
assert_not_matches() {
local haystack="$1"
local pattern="$2"
local description="$3"
if printf '%s' "$haystack" | grep -Eq -- "$pattern"; then
fail "$description"
echo " did not expect to match: $pattern"
else
pass "$description"
fi
}
list_archive() {
local archive_path="$1"
case "$archive_path" in
*.tar.gz|*.tgz)
tar -tzf "$archive_path"
;;
*.zip)
unzip -Z1 "$archive_path"
;;
*)
unzip -Z1 "$archive_path"
;;
esac
}
normalize_archive_paths() {
sed 's#/$##' | LC_ALL=C sort
}
extract_archive() {
local archive_path="$1"
local destination="$2"
mkdir -p "$destination"
case "$archive_path" in
*.tar.gz|*.tgz)
tar -xzf "$archive_path" -C "$destination"
;;
*.zip)
unzip -q "$archive_path" -d "$destination"
;;
*)
unzip -q "$archive_path" -d "$destination"
;;
esac
}
read_archive_file() {
local archive_path="$1"
local file_path="$2"
case "$archive_path" in
*.tar.gz|*.tgz)
tar -xOf "$archive_path" "$file_path"
;;
*.zip)
unzip -p "$archive_path" "$file_path"
;;
*)
unzip -p "$archive_path" "$file_path"
;;
esac
}
write_metadata_fixture() {
local destination="$1"
local skill
while IFS= read -r skill; do
mkdir -p "$destination/skills/$skill/agents"
cat >"$destination/skills/$skill/agents/openai.yaml" <<EOF
interface:
display_name: "$skill"
short_description: "Fixture metadata for $skill"
EOF
done < <(find "$REPO_ROOT/skills" -mindepth 1 -maxdepth 1 -type d -print | sed 's#.*/##' | sort)
}
echo "Codex package archive tests"
metadata_source="$TEST_ROOT/metadata-source"
archive="$TEST_ROOT/superpowers"
tar_archive="$TEST_ROOT/superpowers.tar.gz"
extracted="$TEST_ROOT/extracted"
tar_extracted="$TEST_ROOT/tar-extracted"
write_metadata_fixture "$metadata_source"
source_hooks="$(python3 -c 'import json; print(json.load(open("'"$REPO_ROOT"'/.codex-plugin/plugin.json")).get("hooks"))')"
assert_equals "$source_hooks" "{}" "source Codex manifest suppresses local hook auto-discovery"
if output="$("$SCRIPT_UNDER_TEST" --allow-dirty --metadata-source "$metadata_source" --output "$archive" 2>&1)"; then
pass "package script exits successfully"
else
fail "package script exits successfully"
printf '%s\n' "$output" | sed 's/^/ /'
fi
if [[ -f "$archive" ]]; then
pass "package script writes archive"
else
fail "package script writes archive"
fi
assert_contains "$output" "Archive:" "reports archive path"
assert_contains "$output" "Format: zip" "reports default zip format"
assert_contains "$output" "SHA-256:" "reports archive checksum"
extract_archive "$archive" "$extracted"
archive_paths="$(list_archive "$archive" | normalize_archive_paths)"
unexpected_pattern='(^superpowers/|^\.agents/|^hooks/|package\.json$|^\.git|^\.pytest_cache|^\.ruff_cache|^scripts/|^tests/|^docs/|^evals/|^lib/|^\.claude|^\.cursor|^\.kimi|^\.opencode|^\.pi|^AGENTS\.md$|^CLAUDE\.md$|^GEMINI\.md$|^RELEASE-NOTES\.md$|^CHANGELOG\.md$)'
assert_not_matches "$archive_paths" "$unexpected_pattern" "archive excludes source-only paths"
assert_contains "$archive_paths" ".codex-plugin/plugin.json" "archive includes Codex manifest"
assert_contains "$archive_paths" "skills/brainstorming/SKILL.md" "archive includes skills"
assert_contains "$archive_paths" "skills/brainstorming/agents/openai.yaml" "archive includes OpenAI skill metadata"
assert_contains "$archive_paths" "assets/app-icon.png" "archive includes app icon"
assert_contains "$archive_paths" "assets/superpowers-small.svg" "archive includes composer icon"
manifest_summary="$(read_archive_file "$archive" .codex-plugin/plugin.json | python3 -c 'import json,sys; data=json.load(sys.stdin); print("\t".join([data["name"], data["version"], data["skills"], str(data.get("hooks"))]))')"
expected_version="$(python3 -c 'import json; print(json.load(open("'"$REPO_ROOT"'/.codex-plugin/plugin.json"))["version"])')"
assert_equals "$manifest_summary" "superpowers $expected_version ./skills/ $source_hooks" "archive manifest preserves source hooks"
skill_count="$(find "$extracted/skills" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')"
metadata_count="$(find "$extracted/skills" -path '*/agents/openai.yaml' -type f | wc -l | tr -d ' ')"
assert_equals "$metadata_count" "$skill_count" "every packaged skill has OpenAI metadata"
if [[ -x "$extracted/skills/subagent-driven-development/scripts/task-brief" ]]; then
pass "archive preserves executable script mode"
else
fail "archive preserves executable script mode"
fi
zip_times="$(python3 - "$archive" <<'PY'
import sys
import zipfile
with zipfile.ZipFile(sys.argv[1]) as archive:
print("\n".join(sorted({str(info.date_time) for info in archive.infolist()})))
PY
)"
assert_equals "$zip_times" "(1980, 1, 1, 0, 0, 0)" "zip archive normalizes entry timestamps"
if tar_output="$("$SCRIPT_UNDER_TEST" --allow-dirty --metadata-source "$metadata_source" --format tar.gz --output "$tar_archive" 2>&1)"; then
pass "package script writes explicit tar.gz archive"
else
fail "package script writes explicit tar.gz archive"
printf '%s\n' "$tar_output" | sed 's/^/ /'
fi
assert_contains "$tar_output" "Format: tar.gz" "reports explicit tar.gz format"
extract_archive "$tar_archive" "$tar_extracted"
tar_archive_paths="$(list_archive "$tar_archive" | normalize_archive_paths)"
assert_equals "$tar_archive_paths" "$archive_paths" "zip and tar.gz archives contain the same paths"
tar_task_brief_mode="$(tar -tzvf "$tar_archive" skills/subagent-driven-development/scripts/task-brief | awk '{print $1}')"
assert_equals "$tar_task_brief_mode" "-rwxr-xr-x" "tar.gz archive preserves executable script mode"
tar_metadata_times="$(tar -tzvf "$tar_archive" | awk '{print $6, $7, $8}' | sort -u)"
assert_equals "$tar_metadata_times" "Dec 31 1969" "tar.gz archive normalizes entry timestamps"
metadata_archive="$TEST_ROOT/metadata-source.tar.gz"
metadata_zip="$TEST_ROOT/metadata-source.zip"
archive_from_tar_source="$TEST_ROOT/superpowers-from-tar-source.zip"
archive_from_zip_source="$TEST_ROOT/superpowers-from-zip-source.zip"
(
cd "$metadata_source"
tar -czf "$metadata_archive" .
zip -X -q -r "$metadata_zip" .
)
if output="$("$SCRIPT_UNDER_TEST" --allow-dirty --metadata-source "$metadata_archive" --output "$archive_from_tar_source" 2>&1)"; then
pass "package script accepts tarball metadata source"
else
fail "package script accepts tarball metadata source"
printf '%s\n' "$output" | sed 's/^/ /'
fi
if cmp -s "$archive" "$archive_from_tar_source"; then
pass "tarball metadata source produces identical archive"
else
fail "tarball metadata source produces identical archive"
fi
if output="$("$SCRIPT_UNDER_TEST" --allow-dirty --metadata-source "$metadata_zip" --output "$archive_from_zip_source" 2>&1)"; then
pass "package script accepts zip metadata source"
else
fail "package script accepts zip metadata source"
printf '%s\n' "$output" | sed 's/^/ /'
fi
if cmp -s "$archive" "$archive_from_zip_source"; then
pass "zip metadata source produces identical archive"
else
fail "zip metadata source produces identical archive"
fi
incomplete_metadata="$TEST_ROOT/incomplete-metadata"
mkdir -p "$incomplete_metadata/skills/brainstorming/agents"
cp "$metadata_source/skills/brainstorming/agents/openai.yaml" \
"$incomplete_metadata/skills/brainstorming/agents/openai.yaml"
set +e
missing_output="$("$SCRIPT_UNDER_TEST" --allow-dirty --metadata-source "$incomplete_metadata" --output "$TEST_ROOT/missing.tar.gz" 2>&1)"
missing_status=$?
set -e
if [[ "$missing_status" -ne 0 ]]; then
pass "package script rejects incomplete metadata source"
else
fail "package script rejects incomplete metadata source"
fi
assert_contains "$missing_output" "ERROR: metadata source is incomplete" "incomplete metadata reports clear error"
dirty_repo="$TEST_ROOT/dirty-repo"
git clone -q --no-local "$REPO_ROOT" "$dirty_repo"
printf '\n# dirty fixture\n' >>"$dirty_repo/README.md"
set +e
dirty_output="$(
cd "$dirty_repo"
scripts/package-codex-plugin.sh \
--metadata-source "$metadata_source" \
--output "$TEST_ROOT/dirty.zip" 2>&1
)"
dirty_status=$?
set -e
if [[ "$dirty_status" -ne 0 ]]; then
pass "package script rejects dirty worktree by default"
else
fail "package script rejects dirty worktree by default"
fi
assert_contains "$dirty_output" "Working tree has uncommitted changes:" "dirty worktree reports changed files"
if [[ "$FAILURES" -eq 0 ]]; then
echo "All Codex package archive tests passed"
else
echo "$FAILURES Codex package archive test(s) failed"
exit 1
fi

View File

@@ -4,6 +4,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
HOOK_UNDER_TEST="$REPO_ROOT/hooks/session-start"
CODEX_HOOK_UNDER_TEST="$REPO_ROOT/hooks/session-start-codex"
WRAPPER_UNDER_TEST="$REPO_ROOT/hooks/run-hook.cmd"
FAILURES=0
@@ -153,15 +154,35 @@ assert_command_output \
CLAUDE_PLUGIN_ROOT="$REPO_ROOT" \
bash "$HOOK_UNDER_TEST"
wrapper_home="$(make_home run-hook-wrapper)"
codex_home="$(make_home codex-plugin-hooks)"
codex_data="$TEST_ROOT/codex-plugin-hooks/data"
mkdir -p "$codex_data"
assert_command_output \
"run-hook.cmd wrapper dispatches to the named session-start script" \
"Codex plugin hooks use dedicated script and emit nested SessionStart additionalContext" \
"nested" \
"" \
"" \
"$wrapper_home" \
"$codex_home" \
PLUGIN_DATA="$codex_data" \
CLAUDE_PLUGIN_DATA="$codex_data" \
PLUGIN_ROOT="$REPO_ROOT" \
CLAUDE_PLUGIN_ROOT="$REPO_ROOT" \
bash "$WRAPPER_UNDER_TEST" session-start
bash "$CODEX_HOOK_UNDER_TEST"
codex_wrapper_home="$(make_home codex-wrapper)"
codex_wrapper_data="$TEST_ROOT/codex-wrapper/data"
mkdir -p "$codex_wrapper_data"
assert_command_output \
"Codex wrapper path dispatches to dedicated script" \
"nested" \
"" \
"" \
"$codex_wrapper_home" \
PLUGIN_DATA="$codex_wrapper_data" \
CLAUDE_PLUGIN_DATA="$codex_wrapper_data" \
PLUGIN_ROOT="$REPO_ROOT" \
CLAUDE_PLUGIN_ROOT="$REPO_ROOT" \
bash "$WRAPPER_UNDER_TEST" session-start-codex
cursor_home="$(make_home cursor)"
assert_command_output \
@@ -196,6 +217,21 @@ assert_command_output \
CLAUDE_PLUGIN_ROOT="$REPO_ROOT" \
bash "$HOOK_UNDER_TEST"
codex_legacy_home="$(make_home codex-legacy-warning-removed)"
codex_legacy_data="$TEST_ROOT/codex-legacy-warning-removed/data"
mkdir -p "$codex_legacy_home/.config/superpowers/skills" "$codex_legacy_data"
assert_command_output \
"Codex SessionStart omits obsolete legacy custom-skill warning" \
"nested" \
"" \
"Superpowers now uses"$'\037'"~/.config/superpowers/skills"$'\037'"~/.claude/skills"$'\037'"legacy" \
"$codex_legacy_home" \
PLUGIN_DATA="$codex_legacy_data" \
CLAUDE_PLUGIN_DATA="$codex_legacy_data" \
PLUGIN_ROOT="$REPO_ROOT" \
CLAUDE_PLUGIN_ROOT="$REPO_ROOT" \
bash "$CODEX_HOOK_UNDER_TEST"
if [[ "$FAILURES" -gt 0 ]]; then
echo "STATUS: FAILED ($FAILURES failure(s))"
exit 1