From a17aaaef3a81d9fb920aee2c693ab919c076e3c8 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Mon, 22 Jun 2026 10:32:59 -0700 Subject: [PATCH 01/20] 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. --- .agents/plugins/marketplace.json | 20 +++++++ .codex-plugin/plugin.json | 1 - tests/codex/test-marketplace-manifest.sh | 66 ++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 .agents/plugins/marketplace.json create mode 100755 tests/codex/test-marketplace-manifest.sh diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000000..4e3b4350d5 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -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" + } + ] +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 7f8ae7b641..c98b525f6e 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -21,7 +21,6 @@ "workflow" ], "skills": "./skills/", - "hooks": "./hooks/hooks-codex.json", "interface": { "displayName": "Superpowers", "shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents", diff --git a/tests/codex/test-marketplace-manifest.sh b/tests/codex/test-marketplace-manifest.sh new file mode 100755 index 0000000000..486cb30130 --- /dev/null +++ b/tests/codex/test-marketplace-manifest.sh @@ -0,0 +1,66 @@ +#!/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") + +unsupported_manifest_fields = ["hooks"] +present_unsupported = sorted( + field for field in unsupported_manifest_fields if field in manifest +) +if present_unsupported: + raise AssertionError( + "unsupported Codex manifest fields present: " + + ", ".join(present_unsupported) + ) + +print("Codex marketplace manifest looks good") +PY From bfa3e4137a287602ff7c928e5d4ed99ab1d5e9dd Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Mon, 22 Jun 2026 11:14:09 -0700 Subject: [PATCH 02/20] 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. --- .codex-plugin/plugin.json | 1 + tests/codex/test-marketplace-manifest.sh | 13 ++++--------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index c98b525f6e..7f8ae7b641 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -21,6 +21,7 @@ "workflow" ], "skills": "./skills/", + "hooks": "./hooks/hooks-codex.json", "interface": { "displayName": "Superpowers", "shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents", diff --git a/tests/codex/test-marketplace-manifest.sh b/tests/codex/test-marketplace-manifest.sh index 486cb30130..c7093f6b1f 100755 --- a/tests/codex/test-marketplace-manifest.sh +++ b/tests/codex/test-marketplace-manifest.sh @@ -51,16 +51,11 @@ 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") - -unsupported_manifest_fields = ["hooks"] -present_unsupported = sorted( - field for field in unsupported_manifest_fields if field in manifest +assert_equal( + manifest.get("hooks"), + "./hooks/hooks-codex.json", + "Codex hooks manifest", ) -if present_unsupported: - raise AssertionError( - "unsupported Codex manifest fields present: " - + ", ".join(present_unsupported) - ) print("Codex marketplace manifest looks good") PY From 321c8cd24ce558fbaa26004af3b68bddfb343b63 Mon Sep 17 00:00:00 2001 From: Ada Sen Date: Tue, 23 Jun 2026 22:57:31 +0000 Subject: [PATCH 03/20] 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" --- hooks/hooks-codex.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/hooks-codex.json b/hooks/hooks-codex.json index 5c357fccf6..0719e5340f 100644 --- a/hooks/hooks-codex.json +++ b/hooks/hooks-codex.json @@ -2,7 +2,7 @@ "hooks": { "SessionStart": [ { - "matcher": "startup|resume|clear", + "matcher": "startup|clear|compact", "hooks": [ { "type": "command", From 1f0c76e0b04e26039fc3bb6f309cc213482cadb2 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Wed, 24 Jun 2026 19:23:09 -0700 Subject: [PATCH 04/20] Remove Codex hooks Codex reliably triggers skills on its own, and the SessionStart hook made the UX worse rather than better. Drop the Codex hook config and its registration in the plugin manifest. --- .codex-plugin/plugin.json | 1 - hooks/hooks-codex.json | 16 ---------------- 2 files changed, 17 deletions(-) delete mode 100644 hooks/hooks-codex.json diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 7f8ae7b641..c98b525f6e 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -21,7 +21,6 @@ "workflow" ], "skills": "./skills/", - "hooks": "./hooks/hooks-codex.json", "interface": { "displayName": "Superpowers", "shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents", diff --git a/hooks/hooks-codex.json b/hooks/hooks-codex.json deleted file mode 100644 index 0719e5340f..0000000000 --- a/hooks/hooks-codex.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "hooks": { - "SessionStart": [ - { - "matcher": "startup|clear|compact", - "hooks": [ - { - "type": "command", - "command": "\"${PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start-codex", - "async": false - } - ] - } - ] - } -} From 6be431b772237efbde26dbb628c698675d6e2e32 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Wed, 24 Jun 2026 19:23:23 -0700 Subject: [PATCH 05/20] Remove Gemini CLI support Google EOLed the Gemini CLI on 2026-06-18; the extension can no longer be installed or updated. Remove Gemini from the install docs, the subagent-capable platform lists, and the eval-harness description, and delete its tool-mapping reference. --- CLAUDE.md | 2 +- README.md | 16 +---- skills/brainstorming/visual-companion.md | 7 --- skills/executing-plans/SKILL.md | 2 +- .../references/gemini-tools.md | 63 ------------------- skills/writing-skills/SKILL.md | 2 +- 6 files changed, 4 insertions(+), 88 deletions(-) delete mode 100644 skills/using-superpowers/references/gemini-tools.md diff --git a/CLAUDE.md b/CLAUDE.md index 5f3d7410f8..f8e45e9db4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 [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/`. +Skill-behavior evals live in [superpowers-evals](https://github.com/prime-radiant-inc/superpowers-evals/), cloned into `evals/` — see `evals/README.md` for setup. The harness drives real tmux sessions of Claude Code / Codex and judges skill compliance with an LLM verifier. Plugin-infrastructure tests still live at `tests/`. ## Understand the Project Before Contributing diff --git a/README.md b/README.md index bb398c6b68..48e3f1985d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ If this sounds like someone you know, definitely send them our way. ## Quickstart -Give your agent Superpowers: [Claude Code](#claude-code), [Antigravity](#antigravity), [Codex App](#codex-app), [Codex CLI](#codex-cli), [Cursor](#cursor), [Factory Droid](#factory-droid), [Gemini CLI](#gemini-cli), [GitHub Copilot CLI](#github-copilot-cli), [Kimi Code](#kimi-code), [OpenCode](#opencode), [Pi](#pi). +Give your agent Superpowers: [Claude Code](#claude-code), [Antigravity](#antigravity), [Codex App](#codex-app), [Codex CLI](#codex-cli), [Cursor](#cursor), [Factory Droid](#factory-droid), [GitHub Copilot CLI](#github-copilot-cli), [Kimi Code](#kimi-code), [OpenCode](#opencode), [Pi](#pi). ## How it works @@ -122,20 +122,6 @@ Superpowers is available via the [official Codex plugin marketplace](https://git droid plugin install superpowers@superpowers ``` -### Gemini CLI - -- Install the extension: - - ```bash - gemini extensions install https://github.com/obra/superpowers - ``` - -- Update later: - - ```bash - gemini extensions update superpowers - ``` - ### GitHub Copilot CLI - Register the marketplace: diff --git a/skills/brainstorming/visual-companion.md b/skills/brainstorming/visual-companion.md index 906c9ac87b..7b89f6b257 100644 --- a/skills/brainstorming/visual-companion.md +++ b/skills/brainstorming/visual-companion.md @@ -74,13 +74,6 @@ On Windows, the script auto-detects and switches to foreground mode (which block scripts/start-server.sh --project-dir /path/to/project --open ``` -**Gemini CLI:** -```bash -# Use --foreground and set is_background: true on your shell tool call -# so the process survives across turns -scripts/start-server.sh --project-dir /path/to/project --open --foreground -``` - **Copilot CLI:** ```bash # Use --foreground and start the server via the bash tool with mode: "async" diff --git a/skills/executing-plans/SKILL.md b/skills/executing-plans/SKILL.md index 78d8854066..075a1038f1 100644 --- a/skills/executing-plans/SKILL.md +++ b/skills/executing-plans/SKILL.md @@ -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. The quality of its work will be significantly higher if run on a platform with subagent support (Claude Code, Codex CLI, Codex App, Copilot CLI, and Gemini 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 diff --git a/skills/using-superpowers/references/gemini-tools.md b/skills/using-superpowers/references/gemini-tools.md deleted file mode 100644 index b01b65238a..0000000000 --- a/skills/using-superpowers/references/gemini-tools.md +++ /dev/null @@ -1,63 +0,0 @@ -# Gemini CLI Tool Mapping - -Skills speak in actions ("dispatch a subagent", "create a todo", "read a file"). On Gemini CLI these resolve to the tools below. - -| Action skills request | Gemini CLI equivalent | -|----------------------|----------------------| -| Read a file | `read_file` | -| Read multiple files at once | `read_many_files` | -| Create a new file | `write_file` | -| Edit a file | `replace` | -| Run a shell command | `run_shell_command` | -| Search file contents | `grep_search` | -| Find files by name | `glob` | -| List files and subdirectories | `list_directory` | -| Fetch a URL | `web_fetch` | -| Search the web | `google_web_search` | -| Invoke a skill | `activate_skill` | -| Dispatch a subagent (`Subagent (general-purpose):` template) | `invoke_agent` with `agent_name: "generalist"` (invocable via `@generalist` chat syntax — see [Subagent support](#subagent-support)) | -| Multiple parallel dispatches | Multiple `invoke_agent` calls in the same response | -| Task tracking ("create a todo", "mark complete") | `write_todos` (statuses: pending, in_progress, completed, cancelled, blocked) | - -## Instructions file - -When a skill mentions "your instructions file", on Gemini CLI this is **`GEMINI.md`**. Gemini CLI loads `GEMINI.md` hierarchically: global at `~/.gemini/GEMINI.md`, project-level files in workspace directories and their ancestors, and sub-directory `GEMINI.md` files when a tool accesses files in those directories. - -## Personal skills directory - -User-level skills live at **`~/.gemini/skills/`**, with **`~/.agents/skills/`** as a cross-runtime alias (shared with Codex and Copilot CLI). When both directories exist at the same scope, `.agents/skills/` takes precedence. Each skill is a subdirectory containing a `SKILL.md` (with `name` and `description` frontmatter). - -## Subagent support - -Gemini CLI dispatches subagents through the `invoke_agent` tool, which takes `agent_name` and `prompt` parameters. The same dispatch is also surfaced as a chat-syntax shortcut: typing `@generalist ` is equivalent to calling `invoke_agent` with `agent_name: "generalist"`. Built-in agent names include `generalist`, `cli_help`, `codebase_investigator`, and (with browser tooling enabled) `browser_agent`. - -Skills dispatch with `Subagent (general-purpose):` and either reference a prompt-template file (e.g., `superpowers:subagent-driven-development`'s `./implementer-prompt.md`) or supply an inline prompt. On Gemini CLI: - -| Skill dispatch form | Gemini CLI equivalent | -|---------------------|----------------------| -| References a `*-prompt.md` template (implementer, task-reviewer, code-reviewer, etc.) | Fill the template, then `invoke_agent` with `agent_name: "generalist"` and the filled prompt | -| References `superpowers:requesting-code-review`'s `./code-reviewer.md` | `invoke_agent` with `agent_name: "generalist"` and the filled review template | -| Inline prompt (no template referenced) | `invoke_agent` with `agent_name: "generalist"` and your inline prompt | - -### Prompt filling - -Skills provide prompt templates with placeholders like `{WHAT_WAS_IMPLEMENTED}` or `[FULL TEXT of task]`. Fill all placeholders before passing the complete prompt to `invoke_agent`. The prompt template itself contains the agent's role, review criteria, and expected output format — the subagent will follow it. - -### Parallel dispatch - -Gemini CLI supports parallel subagent dispatch. Issue multiple `invoke_agent` calls in the same response (or multiple `@generalist` invocations in one prompt) to run independent subagent work in parallel. Keep dependent tasks sequential, but do not serialize independent subagent tasks just to preserve a simpler history. - -## Additional Gemini CLI tools - -These tools are unique to Gemini CLI: - -| Tool | Purpose | -|------|---------| -| `save_memory` (legacy) | Persist facts across sessions when `experimental.memoryV2 = false` | -| `get_internal_docs` | Look up Gemini CLI's bundled documentation | -| `ask_user` | Pose structured questions to the user (text / single-select / multi-select) | -| `enter_plan_mode` / `exit_plan_mode` | Switch into and out of read-only plan mode | -| `update_topic` | Update the current conversation's topic / strategic-intent metadata | -| `complete_task` | Signal that a Gemini subagent has completed and return its result to the parent agent | -| `tracker_create_task`, `tracker_update_task`, `tracker_get_task`, `tracker_list_tasks`, `tracker_add_dependency`, `tracker_visualize` | Rich task tracker with dependency and visualization support | -| `read_mcp_resource`, `list_mcp_resources` | MCP resource access | diff --git a/skills/writing-skills/SKILL.md b/skills/writing-skills/SKILL.md index 8928d449f5..6d3ded6e62 100644 --- a/skills/writing-skills/SKILL.md +++ b/skills/writing-skills/SKILL.md @@ -9,7 +9,7 @@ description: Use when creating new skills, editing existing skills, or verifying **Writing skills IS Test-Driven Development applied to process documentation.** -**Personal skills live in your runtime's skills directory** — see [claude-code-tools.md](../using-superpowers/references/claude-code-tools.md), [codex-tools.md](../using-superpowers/references/codex-tools.md), [copilot-tools.md](../using-superpowers/references/copilot-tools.md), or [gemini-tools.md](../using-superpowers/references/gemini-tools.md) for the path on your runtime. Codex, Copilot CLI, and Gemini CLI all also recognize `~/.agents/skills/` as a cross-runtime alias. +**Personal skills live in your runtime's skills directory** You write test cases (pressure scenarios with subagents), watch them fail (baseline behavior), write the skill (documentation), watch tests pass (agents comply), and refactor (close loopholes). From 4000288dac23ba905cc45a22936275d88c43c888 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Wed, 24 Jun 2026 19:23:35 -0700 Subject: [PATCH 06/20] Prune per-harness tool-mapping boilerplate The verbose action-to-tool tables and skill-loading explainers in the per-harness reference files restated guidance modern agents already follow. Trim each file to the harness-specific notes that still carry weight (subagent dispatch, task tracking, instructions-file paths), and delete claude-code-tools.md and copilot-tools.md, which had nothing left that wasn't generic. --- .../references/antigravity-tools.md | 75 +------------------ .../references/claude-code-tools.md | 50 ------------- .../references/codex-tools.md | 35 +-------- .../references/copilot-tools.md | 49 ------------ .../using-superpowers/references/pi-tools.md | 12 --- 5 files changed, 2 insertions(+), 219 deletions(-) delete mode 100644 skills/using-superpowers/references/claude-code-tools.md delete mode 100644 skills/using-superpowers/references/copilot-tools.md diff --git a/skills/using-superpowers/references/antigravity-tools.md b/skills/using-superpowers/references/antigravity-tools.md index b0d4fa1fe5..71155fde81 100644 --- a/skills/using-superpowers/references/antigravity-tools.md +++ b/skills/using-superpowers/references/antigravity-tools.md @@ -4,85 +4,12 @@ Skills speak in actions ("dispatch a subagent", "create a todo", "read a file"). | Action skills request | Antigravity CLI equivalent | |----------------------|----------------------| -| Read a file | `view_file` | -| Create a new file | `write_to_file` | -| Edit a file | `replace_file_content` | -| Edit a file in several places at once | `multi_replace_file_content` | -| Run a shell command | `run_command` | -| Search file contents | `grep_search` | -| Find files by name / list a directory | `list_dir` (no dedicated glob tool — combine `list_dir` with `grep_search`) | -| Fetch a URL | `read_url_content` | -| Search the web | `search_web` | -| Pose a structured question to your human partner | `ask_question` | | Dispatch a subagent (`Subagent (general-purpose):` template) | `invoke_subagent` with a built-in `TypeName` — `self` for full-capability work, `research` for read-only (see [Subagent support](#subagent-support)) | -| Multiple parallel dispatches | Multiple entries in one `invoke_subagent` call's `Subagents` array | | Task tracking ("create a todo", "mark complete") | a **task artifact** — `write_to_file` with `IsArtifact: true` and `ArtifactType: "task"` (see [Task tracking](#task-tracking)). **Not** `manage_task`, which manages background processes. | -## Invoking a skill — read its `SKILL.md` - -Antigravity surfaces every installed skill's `name` + `description` to you at the -start of each session, but it has **no `Skill`/`activate_skill` tool**. To load a -skill, **read its `SKILL.md` with `view_file`, setting `IsSkillFile: true`** when -the skill applies — e.g. `view_file` on -`.../plugins/superpowers/skills//SKILL.md` with `IsSkillFile: true`. -(`IsSkillFile` is agy's own signal that you're reading a file to *execute its -instructions*, not to edit or preview it — set it whenever you load a skill.) - -This is the blessed skill-loading mechanism on this harness. The general rule -"never read skill files manually" means "don't bypass your platform's -skill-loading mechanism" — and on Antigravity, reading `SKILL.md` *is* that -mechanism. Reading it honors the rule rather than breaking it. - -You already know which skills exist and what they're for: their names and -descriptions are in front of you at session start. When a description matches -what you're about to do, read that skill's `SKILL.md` before acting. - -## Subagent support - -Antigravity dispatches subagents with `invoke_subagent`, passing each one a -`TypeName` in the `Subagents` array. Two `TypeName`s are **built in** — use them -directly, no `define_subagent` needed: - -- **`self`** — a full clone of you, with every tool you have (including - `write_to_file`/`replace_file_content`/`run_command`). The safe default for - general-purpose work: implementing, fixing, anything that edits files or runs - commands. -- **`research`** — read-only (file reading, `grep_search`, web/URL fetch; no write - or command access). Use it when you specifically want a subagent that can't make - changes — investigation and read-only review. - -Call `define_subagent` only for a custom system prompt or capability mix: set -`enable_write_tools: true` to grant file edits **and** `run_command`, -`enable_subagent_tools` for nested dispatch, `enable_mcp_tools` for MCP. Then -invoke it by the name you gave it. (`manage_subagents` lists/kills running -subagents.) - -Skills dispatch with `Subagent (general-purpose):` and either reference a -prompt-template file (e.g. `superpowers:subagent-driven-development`'s -`./implementer-prompt.md`) or supply an inline prompt. On Antigravity: - -| Skill dispatch form | Antigravity equivalent | -|---------------------|----------------------| -| An implementer-style `*-prompt.md` template (writes code, runs tests) | Fill the template, then `invoke_subagent` with `TypeName: "self"` and the filled prompt | -| A read-only reviewer template (`task-reviewer`, `code-reviewer`, `requesting-code-review`'s `./code-reviewer.md`) | `invoke_subagent` with `TypeName: "research"` and the filled review template | -| Inline prompt (no template referenced) | `invoke_subagent` with `TypeName: "self"` (or `"research"` if the task only reads) and your inline prompt | - -### Prompt filling - -Skills provide prompt templates with placeholders like `{WHAT_WAS_IMPLEMENTED}` or -`[FULL TEXT of task]`. Fill all placeholders before passing the complete prompt to -`invoke_subagent`. The prompt template itself contains the agent's role, review -criteria, and expected output format — the subagent will follow it. - -### Parallel dispatch - -Put multiple entries in a single `invoke_subagent` call's `Subagents` array to run -independent subagent work in parallel. Keep dependent tasks sequential, but do not -serialize independent subagent tasks just to preserve a simpler history. - ## Task tracking -Antigravity has **no todo / `TodoWrite` tool** (`manage_task` manages background +Antigravity has **no todo tool** (`manage_task` manages background processes — `list`/`kill`/`status`/`send_input` — it is *not* a checklist). When a skill says to create a todo list or track tasks, maintain a **task artifact**: a markdown checklist saved with `write_to_file` (`IsArtifact: true`, diff --git a/skills/using-superpowers/references/claude-code-tools.md b/skills/using-superpowers/references/claude-code-tools.md deleted file mode 100644 index 7ddd549aa7..0000000000 --- a/skills/using-superpowers/references/claude-code-tools.md +++ /dev/null @@ -1,50 +0,0 @@ -# Claude Code Tool Mapping - -Skills speak in actions ("dispatch a subagent", "create a todo", "read a file"). On Claude Code these resolve to the tools below. - -## Tools - -| Action skills request | Claude Code tool | -|----------------------|------------------| -| Read a file | `Read` | -| Create a new file | `Write` | -| Edit a file | `Edit` | -| Run a shell command | `Bash` | -| Search file contents | `Grep` | -| Find files by name | `Glob` | -| Fetch a URL | `WebFetch` | -| Search the web | `WebSearch` | -| Invoke a skill | `Skill` | -| Dispatch a subagent (`Subagent (general-purpose):` template) | `Agent` (older releases named this `Task`) | -| Multiple parallel dispatches | Multiple `Agent` calls in one response | -| Task tracking ("create a todo", "mark complete") | `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet`; `TodoWrite` in `claude -p` / Agent SDK unless `CLAUDE_CODE_ENABLE_TASKS=1` is set | -| Background-process / subagent lifecycle (read output, cancel) | `TaskOutput`, `TaskStop` — these are distinct from the todo tools above and apply to running shells, agents, and remote sessions | - -## Instructions file - -When a skill mentions "your instructions file", on Claude Code this is **`CLAUDE.md`**. Claude Code walks up the directory tree from the current working directory and concatenates every `CLAUDE.md` and `CLAUDE.local.md` it finds along the way. Standard locations: - -| Scope | Location | -|-------|----------| -| Project (team-shared) | `./CLAUDE.md` or `./.claude/CLAUDE.md` | -| User global | `~/.claude/CLAUDE.md` | -| Local-private (gitignored) | `./CLAUDE.local.md` | -| Managed policy (org-wide) | `/Library/Application Support/ClaudeCode/CLAUDE.md` (macOS), `/etc/claude-code/CLAUDE.md` (Linux/WSL), `C:\Program Files\ClaudeCode\CLAUDE.md` (Windows) | - -CLAUDE.md files can pull in additional content with `@path/to/file` imports (relative or absolute, max five hops deep). Subdirectory `CLAUDE.md` files are also discovered automatically and loaded on-demand when Claude Code reads files in those subdirectories. - -Claude Code does **not** read `AGENTS.md` directly. If a project already maintains `AGENTS.md` for other agents, import it from `CLAUDE.md` so both runtimes share the same instructions: - -```markdown -@AGENTS.md - -## Claude Code - -(Claude-Code-specific instructions go here.) -``` - -For path-scoped rules and larger-project organization, see `.claude/rules/` (rules can be scoped to specific files via `paths` frontmatter and load on demand). - -## Personal skills directory - -User-level skills live at **`~/.claude/skills/`**. Each skill is a subdirectory containing a `SKILL.md` (with `name` and `description` frontmatter) plus any supporting files. Claude Code does not currently recognize the cross-runtime `~/.agents/skills/` path that Codex, Copilot CLI, and Gemini CLI read; if you're relying on cross-runtime support in the future, verify against the [official skills docs](https://code.claude.com/docs/en/skills). diff --git a/skills/using-superpowers/references/codex-tools.md b/skills/using-superpowers/references/codex-tools.md index 1ab253fd91..1897cc3bb1 100644 --- a/skills/using-superpowers/references/codex-tools.md +++ b/skills/using-superpowers/references/codex-tools.md @@ -1,31 +1,3 @@ -# Codex Tool Mapping - -Skills speak in actions ("dispatch a subagent", "create a todo", "read a file"). On Codex these resolve to the tools below. - -| Action skills request | Codex equivalent | -|----------------------|------------------| -| Read a file | `shell` (e.g., `cat`, `head`, `tail`) — Codex reads files via shell | -| Create / edit / delete a file | `apply_patch` (structured diff for create, update, delete) | -| Run a shell command | `shell` | -| Search file contents | `shell` (e.g., `grep`, `rg`) | -| Find files by name | `shell` (e.g., `find`, `ls`) | -| Fetch a URL | `shell` with `curl` / `wget` — Codex has no native fetch tool | -| Search the web | `web_search` (enabled by default; configurable in `config.toml` via the top-level `web_search` setting — `live`, `cached`, or `disabled`) | -| Invoke a skill | Skills load natively — just follow the instructions | -| Dispatch a subagent (`Subagent (general-purpose):` template) | `spawn_agent` (see [Subagent dispatch requires multi-agent support](#subagent-dispatch-requires-multi-agent-support)) | -| Multiple parallel dispatches | Multiple `spawn_agent` calls in one response | -| Wait for subagent result | `wait_agent` | -| Free up subagent slot when done | `close_agent` | -| Task tracking ("create a todo", "mark complete") | `update_plan` | - -## Instructions file - -When a skill mentions "your instructions file", on Codex this is **`AGENTS.md`** at the project root. Codex also reads `~/.codex/AGENTS.md` for global context, and an `AGENTS.override.md` (in the project tree or `~/.codex/`) takes precedence when present. Codex walks from the project root down to the current working directory, concatenating `AGENTS.md` files it finds along the way, up to `project_doc_max_bytes` (32 KiB by default). - -## Personal skills directory - -User-level skills live at **`$CODEX_HOME/skills/`** (default `~/.codex/skills/`). Codex also reads the cross-runtime path **`~/.agents/skills/`** (shared with Copilot CLI and Gemini CLI). When both directories exist at the same scope, Codex loads them both as separate skill catalogs — Codex's docs don't currently document a precedence between them. Each skill is a subdirectory containing a `SKILL.md` (with `name` and `description` frontmatter). - ## Subagent dispatch requires multi-agent support Add to your Codex config (`~/.codex/config.toml`): @@ -35,12 +7,7 @@ Add to your Codex config (`~/.codex/config.toml`): multi_agent = true ``` -This enables `spawn_agent`, `wait_agent`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`. - -Legacy note: Codex builds before `rust-v0.115.0` exposed spawned-agent -waiting as `wait`. Current Codex uses `wait_agent` for spawned agents. The -`wait` name now belongs to code-mode `exec/wait`, which resumes a yielded exec -cell by `cell_id`; it is not the spawned-agent result tool. +This enables `spawn_agent`, `wait_agent`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`. When using subagent-driven-development, you should always close implementer and reviewer subagents when they have finished all their work. ## Environment Detection diff --git a/skills/using-superpowers/references/copilot-tools.md b/skills/using-superpowers/references/copilot-tools.md deleted file mode 100644 index 2cf54a0d98..0000000000 --- a/skills/using-superpowers/references/copilot-tools.md +++ /dev/null @@ -1,49 +0,0 @@ -# Copilot CLI Tool Mapping - -Skills speak in actions ("dispatch a subagent", "create a todo", "read a file"). On Copilot CLI these resolve to the tools below. - -| Action skills request | Copilot CLI equivalent | -|----------------------|----------------------| -| Read a file | `view` | -| Create / edit / delete a file | `apply_patch` (Copilot CLI has no separate create/edit/write tools) | -| Run a shell command | `bash` | -| Search file contents | `rg` (ripgrep; Copilot CLI does not expose a `grep` tool) | -| Find files by name | `glob` | -| Fetch a URL | `web_fetch` | -| Search the web | `web_search` | -| Invoke a skill | `skill` | -| Dispatch a subagent (`Subagent (general-purpose):` template) | `task` with `agent_type: "general-purpose"` (other accepted types: `explore`, `task`, `code-review`, `research`, `configure-copilot`) | -| Multiple parallel dispatches | Multiple `task` calls in one response | -| Subagent status/output/control | `read_agent`, `list_agents`, `write_agent` | -| Task tracking ("create a todo", "mark complete") | `update_todo` | -| Enter / exit plan mode | No equivalent — stay in the main session | - -## Instructions file - -When a skill mentions "your instructions file", on Copilot CLI this is **`AGENTS.md`** at the repository root. If both `AGENTS.md` and `.github/copilot-instructions.md` are present, Copilot reads both. - -## Personal skills directory - -User-level skills live at **`~/.copilot/skills/`**. Copilot CLI also recognizes the cross-runtime alias **`~/.agents/skills/`**, which is shared with Codex and Gemini CLI. Each skill is a subdirectory containing a `SKILL.md` (with `name` and `description` frontmatter). - -## Async shell sessions - -Copilot CLI supports persistent async shell sessions: - -| Tool | Purpose | -|------|---------| -| `bash` with `mode: "async"` (and optionally `detach: true`) | Start a long-running command in the background; returns a `shellId` | -| `write_bash` | Send input to a running async session | -| `read_bash` | Read output from an async session | -| `stop_bash` | Terminate an async session | -| `list_bash` | List all active shell sessions | - -## Additional Copilot CLI tools - -| Tool | Purpose | -|------|---------| -| `store_memory` | Persist facts about the codebase for future sessions | -| `report_intent` | Update the UI status line with current intent | -| `sql` | Query the session's SQLite database (todos, metadata) | -| `fetch_copilot_cli_documentation` | Look up Copilot CLI documentation | -| GitHub MCP tools (`github-mcp-server-*`) | Native GitHub API access (issues, PRs, code search) | diff --git a/skills/using-superpowers/references/pi-tools.md b/skills/using-superpowers/references/pi-tools.md index 04889cbaef..0c1f21713a 100644 --- a/skills/using-superpowers/references/pi-tools.md +++ b/skills/using-superpowers/references/pi-tools.md @@ -4,21 +4,9 @@ Skills speak in actions ("dispatch a subagent", "create a todo", "read a file"). | Action skills request | Pi equivalent | | --- | --- | -| Invoke a skill | Pi native skills: load the relevant `SKILL.md` with `read`, or let the human use `/skill:name` | -| Read a file | `read` | -| Create a file | `write` | -| Edit a file | `edit` | -| Run a shell command | `bash` | -| Search file contents | `grep` when active; otherwise `bash` with `rg`/`grep` | -| Find files by name | `find` or `bash` with shell globs | -| List files and subdirectories | `ls` when active; otherwise `bash` with `ls` | | Dispatch a subagent (`Subagent (general-purpose):` template) | Use an installed subagent tool such as `subagent` from `pi-subagents` if available | | Task tracking ("create a todo", "mark complete") | Use an installed todo/task tool if available, otherwise track tasks in the plan or `TODO.md` | -## Skills - -Pi discovers skills from configured skill directories and installed Pi packages. A Superpowers Pi package should expose `skills/` through its `pi.skills` manifest entry. Pi does not expose Claude Code's `Skill` tool, but the agent should still follow the Superpowers rule: when a skill applies, load and follow it before responding. - ## Subagents Pi core does not ship a standard subagent tool. The `pi-subagents` package is a strong optional companion and provides a `subagent` tool with single-agent, chain, parallel, async, forked-context, and resume/status workflows. If no subagent tool is available, do not fabricate `Task` calls; execute sequentially in the current session or explain that the optional subagent capability is not installed. From 98b080041d126436fdec0b09521fa3b2a3721e98 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Wed, 24 Jun 2026 19:23:47 -0700 Subject: [PATCH 07/20] Compress the using-superpowers bootstrap The bootstrap is injected into every session, so its token cost is paid constantly. Condense it without dropping behavior-shaping content: - Replace the graphviz skill-flow diagram with the prose it encoded (the 1% rule, the plan-mode to brainstorm gate, announce + checklist to todos). - Fold the standalone Instruction-Priority section into User Instructions. - Drop the per-platform 'How to Access Skills' walkthrough. - Trim the Platform Adaptation pointer to the harnesses that still have a reference file (Codex, Pi, Antigravity). Keeps the full Red Flags rationalization table, skill priority framed as process-before-implementation, and user-instruction precedence. --- skills/using-superpowers/SKILL.md | 91 ++++++------------------------- 1 file changed, 16 insertions(+), 75 deletions(-) diff --git a/skills/using-superpowers/SKILL.md b/skills/using-superpowers/SKILL.md index 5371221718..8a08873ba0 100644 --- a/skills/using-superpowers/SKILL.md +++ b/skills/using-superpowers/SKILL.md @@ -4,7 +4,7 @@ description: Use when starting any conversation - establishes how to find and us --- -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. @@ -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. -## 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. +## The Rule -**In other environments:** Check your platform's documentation for how skills are loaded. +**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. -## Platform Adaptation +**Before entering plan mode:** if you haven't already brainstormed, invoke the brainstorming skill first. -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. +Then announce "Using [skill] to [purpose]" and follow the skill exactly. If it has a checklist, create a todo per item. -# Using Skills +## Skill Priority -## The Rule +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. -**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. - -```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]; - - "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?"; - - "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"; -} -``` +- "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 - -When multiple skills could apply, use this order: - -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. +## Platform Adaptation -**Flexible** (patterns): Adapt principles to context. +If your harness appears here, read its reference file for special instructions: -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. From 9c9b9bd7c8f305aeb1876a97ee2a17869de0106a Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Tue, 30 Jun 2026 10:28:53 -0700 Subject: [PATCH 08/20] test(codex): assert Codex manifest ships no hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 1f0c76e removed the Codex SessionStart hook — dropping the hooks field from .codex-plugin/plugin.json and deleting hooks-codex.json — but left test-marketplace-manifest.sh asserting the old hooks pointer, so the test has failed on dev since. Assert the field is absent instead, locking in the no-Codex-hooks decision. --- tests/codex/test-marketplace-manifest.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/codex/test-marketplace-manifest.sh b/tests/codex/test-marketplace-manifest.sh index c7093f6b1f..3045cde67b 100755 --- a/tests/codex/test-marketplace-manifest.sh +++ b/tests/codex/test-marketplace-manifest.sh @@ -53,8 +53,8 @@ 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", + None, + "Codex manifest ships no hooks", ) print("Codex marketplace manifest looks good") From 8554b7215c7015b5be717cb953b972679d1f6dd7 Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Tue, 30 Jun 2026 10:29:02 -0700 Subject: [PATCH 09/20] Release v6.1.0: leaner per-session bootstrap, Codex marketplace install, Gemini removed Bump all manifests to 6.1.0 and add RELEASE-NOTES for v6.1.0: - Compress the using-superpowers bootstrap and prune per-harness tool-mapping references (lower per-session token cost). - Add a Codex marketplace manifest so the plugin installs from Codex; drop the Codex SessionStart hook. - Remove Gemini CLI support (Google EOLed the Gemini CLI 2026-06-18). --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- .cursor-plugin/plugin.json | 2 +- .kimi-plugin/plugin.json | 2 +- RELEASE-NOTES.md | 18 ++++++++++++++++++ gemini-extension.json | 2 +- package.json | 2 +- 8 files changed, 25 insertions(+), 7 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index fe85296296..f8343e4bf3 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ { "name": "superpowers", "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", - "version": "6.0.3", + "version": "6.1.0", "source": "./", "author": { "name": "Jesse Vincent", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 8bec5fe086..83ebf0713c 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "superpowers", "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", - "version": "6.0.3", + "version": "6.1.0", "author": { "name": "Jesse Vincent", "email": "jesse@fsck.com" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index c98b525f6e..a491777839 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "superpowers", - "version": "6.0.3", + "version": "6.1.0", "description": "An agentic skills framework & software development methodology that works: planning, TDD, debugging, and collaboration workflows.", "author": { "name": "Jesse Vincent", diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index ff2a77623e..d94de6f015 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "superpowers", "displayName": "Superpowers", "description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques", - "version": "6.0.3", + "version": "6.1.0", "author": { "name": "Jesse Vincent", "email": "jesse@fsck.com" diff --git a/.kimi-plugin/plugin.json b/.kimi-plugin/plugin.json index 6f9d485419..32c3ea580f 100644 --- a/.kimi-plugin/plugin.json +++ b/.kimi-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "superpowers", - "version": "6.0.3", + "version": "6.1.0", "description": "An agentic skills framework and software development methodology.", "author": { "name": "Jesse Vincent", diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9fbb323be6..1d9c505353 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,5 +1,23 @@ # Superpowers Release Notes +## 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 diff --git a/gemini-extension.json b/gemini-extension.json index d8208794ff..0fac6898a5 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "superpowers", "description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques", - "version": "6.0.3", + "version": "6.1.0", "contextFileName": "GEMINI.md" } diff --git a/package.json b/package.json index ee6b22f0e5..387b763c3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "superpowers", - "version": "6.0.3", + "version": "6.1.0", "description": "Superpowers skills and runtime bootstrap for coding agents", "type": "module", "main": ".opencode/plugins/superpowers.js", From b15ef6ebbe2de701c32ba1b235596580e060f7e2 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 30 Jun 2026 15:38:20 -0700 Subject: [PATCH 10/20] fix(codex): suppress SessionStart hook auto-discovery with empty hooks object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. hooks/hooks.json is the Claude Code SessionStart hook, it is tracked in this repo, and the Codex marketplace installs the whole repo root (source url "./"), so the fallback re-registered the SessionStart hook and its install-time trust prompt on Codex. Removing the Codex hook file and the manifest `hooks` pointer (commit "Remove Codex hooks") did not disable the hook on Codex — it removed the explicit declaration that was overriding the fallback, so the fallback took over and found the Claude hooks/hooks.json. Declare an empty inline hooks object ({}) in .codex-plugin/plugin.json. It parses as an empty inline hook set and stops Codex reaching the auto-discovery fallback. An absent field, an empty array ([]), and an empty inline list all collapse back to the fallback, so the value must be exactly {}. Update the test to assert the manifest declares hooks: {} (and that hooks/hooks.json exists, which is what makes the declaration necessary), replacing the prior assertion that the field was absent — which passed while the hook was still being auto-discovered. Co-Authored-By: Claude Opus 4.8 (1M context) --- .codex-plugin/plugin.json | 1 + tests/codex/test-marketplace-manifest.sh | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index a491777839..812e72c709 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -21,6 +21,7 @@ "workflow" ], "skills": "./skills/", + "hooks": {}, "interface": { "displayName": "Superpowers", "shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents", diff --git a/tests/codex/test-marketplace-manifest.sh b/tests/codex/test-marketplace-manifest.sh index 3045cde67b..4301a06eec 100755 --- a/tests/codex/test-marketplace-manifest.sh +++ b/tests/codex/test-marketplace-manifest.sh @@ -51,10 +51,25 @@ 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"), - None, - "Codex manifest ships no hooks", + {}, + "Codex manifest must declare empty hooks {} to suppress hooks/hooks.json auto-discovery", ) print("Codex marketplace manifest looks good") From 3a1d8fe8d7c665bde850c9e3481273779a453243 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 30 Jun 2026 13:41:12 -0700 Subject: [PATCH 11/20] Add Codex portal package script --- scripts/package-codex-plugin.sh | 256 +++++++++++++++++++++++ tests/codex/test-package-codex-plugin.sh | 133 ++++++++++++ 2 files changed, 389 insertions(+) create mode 100755 scripts/package-codex-plugin.sh create mode 100755 tests/codex/test-package-codex-plugin.sh diff --git a/scripts/package-codex-plugin.sh b/scripts/package-codex-plugin.sh new file mode 100755 index 0000000000..667b7d28ee --- /dev/null +++ b/scripts/package-codex-plugin.sh @@ -0,0 +1,256 @@ +#!/usr/bin/env bash +# +# Package the Superpowers Codex plugin as a rootless .tar.gz 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="" +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.tar.gz + --metadata-source PATH Prior official package directory or .tar.gz used to + seed skills/*/agents/openai.yaml. + Default: ../_tmp/sup-codex-packaging/superpowers, + falling back to ../_tmp/sup-codex-packaging/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 tar 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 + ;; + --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 + +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" + +[[ -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.tar.gz" ]]; then + METADATA_SOURCE="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers.tar.gz" + else + die "no metadata source found; pass --metadata-source " + fi +fi + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/superpowers-codex-package.XXXXXX")" +STAGE="$WORK_DIR/payload" +METADATA_WORK="$WORK_DIR/metadata" +TAR_LIST="$WORK_DIR/tar-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 | head -n 1)" + 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" + ;; + *) + die "metadata source must be a directory 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 jq -e 'has("hooks")' "$STAGE/.codex-plugin/plugin.json" >/dev/null; then + die "Codex manifest must not declare hooks for the portal package" +fi + +if [[ -z "$OUTPUT" ]]; then + OUTPUT="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers-$VERSION.tar.gz" +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" + +# Match the prior official archive's deterministic tar entry metadata. +TZ=UTC find "$STAGE" -exec touch -t 197001010000 {} + + +( + cd "$STAGE" + { + find . -mindepth 1 -type d | sed 's#^\./##' | LC_ALL=C sort + find . -mindepth 1 -type f | sed 's#^\./##' | LC_ALL=C sort + } >"$TAR_LIST" + + rm -f "$OUTPUT" + COPYFILE_DISABLE=1 tar -cnf - --format ustar --uid 0 --gid 0 --uname '' --gname '' -T "$TAR_LIST" | + gzip -9n >"$OUTPUT" +) + +if command -v xattr >/dev/null 2>&1; then + xattr -c "$OUTPUT" 2>/dev/null || true +fi + +unexpected_paths="$( + tar -tzf "$OUTPUT" | + 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="$(tar -tzf "$OUTPUT" | wc -l | tr -d ' ')" +checksum="$(shasum -a 256 "$OUTPUT" | awk '{print $1}')" + +echo "Archive: $OUTPUT" +echo "Version: $VERSION" +echo "Entries: $entry_count" +echo "Skills: $skill_count" +echo "SHA-256: $checksum" diff --git a/tests/codex/test-package-codex-plugin.sh b/tests/codex/test-package-codex-plugin.sh new file mode 100755 index 0000000000..804568d1ce --- /dev/null +++ b/tests/codex/test-package-codex-plugin.sh @@ -0,0 +1,133 @@ +#!/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 +} + +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" <&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" "SHA-256:" "reports archive checksum" + +mkdir -p "$extracted" +tar -xzf "$archive" -C "$extracted" + +archive_paths="$(tar -tzf "$archive" | sort)" +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="$(tar -xOf "$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/ None" "archive manifest is current and hook-free" + +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" + +task_brief_mode="$(tar -tzvf "$archive" skills/subagent-driven-development/scripts/task-brief | awk '{print $1}')" +assert_equals "$task_brief_mode" "-rwxr-xr-x" "archive preserves executable script mode" + +metadata_times="$(tar -tzvf "$archive" | awk '{print $6, $7, $8}' | sort -u)" +assert_equals "$metadata_times" "Dec 31 1969" "archive normalizes entry timestamps" + +if [[ "$FAILURES" -eq 0 ]]; then + echo "All Codex package archive tests passed" +else + echo "$FAILURES Codex package archive test(s) failed" + exit 1 +fi From 6770bfbcc5924812f1857e2672871eabacea1dc7 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 30 Jun 2026 13:45:54 -0700 Subject: [PATCH 12/20] Harden Codex package script checks --- scripts/package-codex-plugin.sh | 4 +- tests/codex/test-package-codex-plugin.sh | 55 ++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/scripts/package-codex-plugin.sh b/scripts/package-codex-plugin.sh index 667b7d28ee..60be05d51c 100755 --- a/scripts/package-codex-plugin.sh +++ b/scripts/package-codex-plugin.sh @@ -137,7 +137,7 @@ metadata_root_from_dir() { return 0 fi - nested="$(find "$candidate" -mindepth 2 -maxdepth 2 -type d -name skills -print | head -n 1)" + nested="$(find "$candidate" -mindepth 2 -maxdepth 2 -type d -name skills -print -quit)" if [[ -n "$nested" ]]; then dirname "$nested" return 0 @@ -229,7 +229,7 @@ TZ=UTC find "$STAGE" -exec touch -t 197001010000 {} + } >"$TAR_LIST" rm -f "$OUTPUT" - COPYFILE_DISABLE=1 tar -cnf - --format ustar --uid 0 --gid 0 --uname '' --gname '' -T "$TAR_LIST" | + COPYFILE_DISABLE=1 tar -cf - --no-recursion --format ustar --uid 0 --gid 0 --uname '' --gname '' -T "$TAR_LIST" | gzip -9n >"$OUTPUT" ) diff --git a/tests/codex/test-package-codex-plugin.sh b/tests/codex/test-package-codex-plugin.sh index 804568d1ce..50be9ba7ff 100755 --- a/tests/codex/test-package-codex-plugin.sh +++ b/tests/codex/test-package-codex-plugin.sh @@ -125,6 +125,61 @@ assert_equals "$task_brief_mode" "-rwxr-xr-x" "archive preserves executable scri metadata_times="$(tar -tzvf "$archive" | awk '{print $6, $7, $8}' | sort -u)" assert_equals "$metadata_times" "Dec 31 1969" "archive normalizes entry timestamps" +metadata_archive="$TEST_ROOT/metadata-source.tar.gz" +archive_from_tar_source="$TEST_ROOT/superpowers-from-tar-source.tar.gz" +( + cd "$metadata_source" + tar -czf "$metadata_archive" . +) + +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 + +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.tar.gz" 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 From 8e19a0c3e6729356fcbfc943d6ab1d3936bfefd6 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 30 Jun 2026 14:08:40 -0700 Subject: [PATCH 13/20] Default Codex portal package to zip --- scripts/package-codex-plugin.sh | 128 +++++++++++++++++++---- tests/codex/test-package-codex-plugin.sh | 123 ++++++++++++++++++++-- 2 files changed, 221 insertions(+), 30 deletions(-) diff --git a/scripts/package-codex-plugin.sh b/scripts/package-codex-plugin.sh index 60be05d51c..008644edf9 100755 --- a/scripts/package-codex-plugin.sh +++ b/scripts/package-codex-plugin.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Package the Superpowers Codex plugin as a rootless .tar.gz for portal upload. +# 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 @@ -14,6 +14,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" REF="HEAD" OUTPUT="" +FORMAT="" METADATA_SOURCE="" ALLOW_DIRTY=0 KEEP_STAGE=0 @@ -25,18 +26,21 @@ Usage: Options: --output PATH Write archive to PATH. - Default: ../_tmp/sup-codex-packaging/superpowers-VERSION.tar.gz - --metadata-source PATH Prior official package directory or .tar.gz used to + 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 ../_tmp/sup-codex-packaging/superpowers.tar.gz + 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 tar root. Source-only repo files, hooks, tests, +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 } @@ -53,6 +57,21 @@ while [[ $# -gt 0 ]]; do 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" @@ -83,11 +102,43 @@ while [[ $# -gt 0 ]]; do 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 || @@ -105,17 +156,19 @@ 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 " + die "no metadata source found; pass --metadata-source " fi fi WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/superpowers-codex-package.XXXXXX")" STAGE="$WORK_DIR/payload" METADATA_WORK="$WORK_DIR/metadata" -TAR_LIST="$WORK_DIR/tar-list" +ARCHIVE_LIST="$WORK_DIR/archive-list" cleanup() { if [[ "$KEEP_STAGE" -eq 1 ]]; then @@ -158,8 +211,13 @@ prepare_metadata_root() { 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 or .tar.gz: $source" + die "metadata source must be a directory, .zip, or .tar.gz: $source" ;; esac else @@ -189,7 +247,14 @@ if jq -e 'has("hooks")' "$STAGE/.codex-plugin/plugin.json" >/dev/null; then fi if [[ -z "$OUTPUT" ]]; then - OUTPUT="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers-$VERSION.tar.gz" + 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")" @@ -218,27 +283,51 @@ metadata_count="$(find "$STAGE/skills" -path '*/agents/openai.yaml' -type f | wc [[ "$skill_count" == "$metadata_count" ]] || die "metadata count mismatch: $metadata_count metadata files for $skill_count skills" -# Match the prior official archive's deterministic tar entry metadata. -TZ=UTC find "$STAGE" -exec touch -t 197001010000 {} + - ( cd "$STAGE" { find . -mindepth 1 -type d | sed 's#^\./##' | LC_ALL=C sort find . -mindepth 1 -type f | sed 's#^\./##' | LC_ALL=C sort - } >"$TAR_LIST" - - rm -f "$OUTPUT" - COPYFILE_DISABLE=1 tar -cf - --no-recursion --format ustar --uid 0 --gid 0 --uname '' --gname '' -T "$TAR_LIST" | - gzip -9n >"$OUTPUT" + } >"$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="$( - tar -tzf "$OUTPUT" | + 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 @@ -246,10 +335,11 @@ if [[ -n "$unexpected_paths" ]]; then die "archive contains source-only paths" fi -entry_count="$(tar -tzf "$OUTPUT" | wc -l | tr -d ' ')" +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" diff --git a/tests/codex/test-package-codex-plugin.sh b/tests/codex/test-package-codex-plugin.sh index 50be9ba7ff..d608674cab 100755 --- a/tests/codex/test-package-codex-plugin.sh +++ b/tests/codex/test-package-codex-plugin.sh @@ -62,6 +62,61 @@ assert_not_matches() { 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 @@ -79,8 +134,10 @@ EOF echo "Codex package archive tests" metadata_source="$TEST_ROOT/metadata-source" -archive="$TEST_ROOT/superpowers.tar.gz" +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" if output="$("$SCRIPT_UNDER_TEST" --allow-dirty --metadata-source "$metadata_source" --output "$archive" 2>&1)"; then @@ -97,12 +154,12 @@ else 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" -mkdir -p "$extracted" -tar -xzf "$archive" -C "$extracted" +extract_archive "$archive" "$extracted" -archive_paths="$(tar -tzf "$archive" | sort)" +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" @@ -111,7 +168,7 @@ assert_contains "$archive_paths" "skills/brainstorming/agents/openai.yaml" "arch 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="$(tar -xOf "$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"))]))')" +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/ None" "archive manifest is current and hook-free" @@ -119,17 +176,48 @@ skill_count="$(find "$extracted/skills" -mindepth 1 -maxdepth 1 -type d | wc -l 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" -task_brief_mode="$(tar -tzvf "$archive" skills/subagent-driven-development/scripts/task-brief | awk '{print $1}')" -assert_equals "$task_brief_mode" "-rwxr-xr-x" "archive preserves executable script mode" +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 -metadata_times="$(tar -tzvf "$archive" | awk '{print $6, $7, $8}' | sort -u)" -assert_equals "$metadata_times" "Dec 31 1969" "archive normalizes entry timestamps" +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" -archive_from_tar_source="$TEST_ROOT/superpowers-from-tar-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 @@ -145,6 +233,19 @@ 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" \ @@ -169,7 +270,7 @@ dirty_output="$( cd "$dirty_repo" scripts/package-codex-plugin.sh \ --metadata-source "$metadata_source" \ - --output "$TEST_ROOT/dirty.tar.gz" 2>&1 + --output "$TEST_ROOT/dirty.zip" 2>&1 )" dirty_status=$? set -e From 43c10985cf9a3bd21341271d213531b7c7ff6a3f Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 30 Jun 2026 14:16:14 -0700 Subject: [PATCH 14/20] Fix Codex plugin category --- .codex-plugin/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 812e72c709..6ecfff4fb7 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -27,7 +27,7 @@ "shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents", "longDescription": "Use Superpowers to guide agent work through brainstorming, implementation planning, test-driven development, systematic debugging, parallel execution, code review, and finish-the-branch workflows.", "developerName": "Jesse Vincent", - "category": "Coding", + "category": "Developer Tools", "capabilities": [ "Interactive", "Read", From af6104527b773e2addc1ddab8ff9674098bfeea1 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 30 Jun 2026 15:57:52 -0700 Subject: [PATCH 15/20] chore(codex): remove orphaned session-start-codex hook + refresh hook docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hooks/session-start-codex has had no caller since "Remove Codex hooks" (#1845) deleted hooks-codex.json and its manifest registration; the Codex manifest now declares an empty hooks object so Codex registers no session-start hook at all. The script is Codex-specific dead code — nothing executes it on Codex or any other harness. - Delete hooks/session-start-codex. - tests/hooks/test-session-start.sh: drop the two Codex cases that are redundant with the generic session-start tests (nested-format and the legacy-warning omission are already covered by the Claude Code cases). Re-point the "wrapper dispatches" case to the live `session-start` script so run-hook.cmd dispatch coverage — used by Claude Code and Cursor in production — is preserved rather than lost. - docs/porting-to-a-new-harness.md: Codex is no longer a Shape A (shell-hook) harness, so re-anchor that worked example to Cursor (a live shell-hook harness that demonstrates the same per-harness field, schema, and matcher variance) and mark Codex as native skill discovery with no session-start hook. Clears the references to the deleted hooks-codex.json. - docs/windows/polyglot-hooks.md: the "check hooks-codex.json" pointer referenced a file deleted in #1845; re-point to hooks-cursor.json. RELEASE-NOTES.md keeps its historical mention of hooks-codex.json (it accurately records what that release did). The tests/codex-plugin-sync fixtures build their own synthetic session-start-codex and test the sync mechanism generically, so they are intentionally left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/porting-to-a-new-harness.md | 47 ++++++++++++++++--------------- docs/windows/polyglot-hooks.md | 2 +- hooks/session-start-codex | 26 ----------------- tests/hooks/test-session-start.sh | 44 +++-------------------------- 4 files changed, 29 insertions(+), 90 deletions(-) delete mode 100755 hooks/session-start-codex diff --git a/docs/porting-to-a-new-harness.md b/docs/porting-to-a-new-harness.md index d74b1c64ff..986cf56139 100644 --- a/docs/porting-to-a-new-harness.md +++ b/docs/porting-to-a-new-harness.md @@ -227,18 +227,20 @@ 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 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**. +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**. -- 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` +- Reference: `hooks/session-start`, `hooks/run-hook.cmd`, and the per-harness + hook config `hooks/hooks.json` (Claude Code) and `hooks/hooks-cursor.json` (Cursor). -- 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.) +- Manifests: `.cursor-plugin/plugin.json` points 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. Codex's `.codex-plugin/plugin.json` ships skills but declares an + empty `hooks` object: Codex auto-discovers `hooks/hooks.json` when the field + is absent, so the empty object suppresses that — Codex surfaces skills + natively and runs no session-start hook.) > **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 @@ -287,7 +289,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) | Codex (`hooks/session-start-codex` + `hooks/hooks-codex.json` + `.codex-plugin/`) | +| runs a shell command at session start and reads its stdout | A (shell-hook) | Cursor (`hooks/session-start` + `hooks/hooks-cursor.json` + `.cursor-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) | @@ -375,25 +377,24 @@ 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-` script, the way Codex did. If you add a branch +`hooks/session-start-` script. 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`, Codex -`startup|resume|clear`, Cursor `sessionStart`); wrong matchers mean the hook -silently never fires. +own event-matcher strings (Claude Code uses `startup|clear|compact`, Cursor +`sessionStart`); wrong matchers mean the hook silently never fires. The **hook-config schema itself varies per harness** — don't assume the -Claude/Codex shape is universal. Compare `hooks/hooks.json`, -`hooks/hooks-codex.json`, and `hooks/hooks-cursor.json`: Cursor's uses +Claude Code shape is universal. Compare `hooks/hooks.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 the -others use. Match your `hooks-.json` to whichever existing file is +`./hooks/run-hook.cmd` command, and omits the `matcher`/`type`/`async` fields +Claude Code uses. Match your `hooks-.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-codex.json` uses `${PLUGIN_ROOT}`, Cursor uses a relative path. Use +`hooks-cursor.json` 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.) @@ -784,7 +785,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` + `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`) | +| 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`) | | 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` | @@ -799,10 +800,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/Codex one (`version`, lowercase `sessionStart`, + looks nothing like the Claude Code 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), `${PLUGIN_ROOT}` (Codex), or a relative path + `${CLAUDE_PLUGIN_ROOT}` (Claude) 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. diff --git a/docs/windows/polyglot-hooks.md b/docs/windows/polyglot-hooks.md index ca597c3abc..8b84f27176 100644 --- a/docs/windows/polyglot-hooks.md +++ b/docs/windows/polyglot-hooks.md @@ -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`; Codex uses `startup|resume|clear`. Check `hooks-codex.json` for the Codex variant. +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. ## Related Issues diff --git a/hooks/session-start-codex b/hooks/session-start-codex deleted file mode 100755 index f25ea0846e..0000000000 --- a/hooks/session-start-codex +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -# Codex SessionStart hook for superpowers plugin - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" - -using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill") - -escape_for_json() { - local s="$1" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//$'\n'/\\n}" - s="${s//$'\r'/\\r}" - s="${s//$'\t'/\\t}" - printf '%s' "$s" -} - -using_superpowers_escaped=$(escape_for_json "$using_superpowers_content") -session_context="\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, follow the Codex skill-loading instructions in that skill:**\n\n${using_superpowers_escaped}\n" - -printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$session_context" | cat - -exit 0 diff --git a/tests/hooks/test-session-start.sh b/tests/hooks/test-session-start.sh index 989d72c659..b027f3c650 100755 --- a/tests/hooks/test-session-start.sh +++ b/tests/hooks/test-session-start.sh @@ -4,7 +4,6 @@ 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 @@ -154,35 +153,15 @@ assert_command_output \ CLAUDE_PLUGIN_ROOT="$REPO_ROOT" \ bash "$HOOK_UNDER_TEST" -codex_home="$(make_home codex-plugin-hooks)" -codex_data="$TEST_ROOT/codex-plugin-hooks/data" -mkdir -p "$codex_data" +wrapper_home="$(make_home run-hook-wrapper)" assert_command_output \ - "Codex plugin hooks use dedicated script and emit nested SessionStart additionalContext" \ + "run-hook.cmd wrapper dispatches to the named session-start script" \ "nested" \ "" \ "" \ - "$codex_home" \ - PLUGIN_DATA="$codex_data" \ - CLAUDE_PLUGIN_DATA="$codex_data" \ - PLUGIN_ROOT="$REPO_ROOT" \ + "$wrapper_home" \ CLAUDE_PLUGIN_ROOT="$REPO_ROOT" \ - 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 + bash "$WRAPPER_UNDER_TEST" session-start cursor_home="$(make_home cursor)" assert_command_output \ @@ -217,21 +196,6 @@ 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 From 4575372ed3ef72f6b8926efa54113e81e567feaf Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 30 Jun 2026 17:10:01 -0700 Subject: [PATCH 16/20] docs: re-anchor Shape A examples away from Codex --- docs/porting-to-a-new-harness.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/porting-to-a-new-harness.md b/docs/porting-to-a-new-harness.md index 986cf56139..d288c6b078 100644 --- a/docs/porting-to-a-new-harness.md +++ b/docs/porting-to-a-new-harness.md @@ -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, Codex, Cursor, Copilot CLI), or + its stdout (Claude Code, 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 @@ -234,13 +234,13 @@ 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` (Cursor). -- Manifests: `.cursor-plugin/plugin.json` points 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. Codex's `.codex-plugin/plugin.json` ships skills but declares an - empty `hooks` object: Codex auto-discovers `hooks/hooks.json` when the field - is absent, so the empty object suppresses that — Codex surfaces skills - natively and runs no session-start hook.) +- 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 + `.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. > **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 @@ -311,7 +311,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 `.codex-plugin/plugin.json`) with +- **Shape A:** a `*-plugin/plugin.json` (see `.cursor-plugin/plugin.json`) with `name`, `version`, `description`, author/license/keywords, `"skills": "./skills/"`, and `"hooks": "./hooks/hooks-.json"`. Plus the `hooks-.json` itself, registering a session-start hook whose command From 6561afc87d1116165d46e36050be9972fd371106 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 30 Jun 2026 17:32:44 -0700 Subject: [PATCH 17/20] Strip hooks from Codex portal package --- scripts/package-codex-plugin.sh | 4 +++- tests/codex/test-package-codex-plugin.sh | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/package-codex-plugin.sh b/scripts/package-codex-plugin.sh index 008644edf9..91458b5163 100755 --- a/scripts/package-codex-plugin.sh +++ b/scripts/package-codex-plugin.sh @@ -243,7 +243,9 @@ VERSION="$(jq -r '.version // empty' "$STAGE/.codex-plugin/plugin.json")" [[ -n "$VERSION" ]] || die "could not read version from .codex-plugin/plugin.json" if jq -e 'has("hooks")' "$STAGE/.codex-plugin/plugin.json" >/dev/null; then - die "Codex manifest must not declare hooks for the portal package" + manifest_tmp="$WORK_DIR/plugin-manifest.json" + jq 'del(.hooks)' "$STAGE/.codex-plugin/plugin.json" >"$manifest_tmp" + mv "$manifest_tmp" "$STAGE/.codex-plugin/plugin.json" fi if [[ -z "$OUTPUT" ]]; then diff --git a/tests/codex/test-package-codex-plugin.sh b/tests/codex/test-package-codex-plugin.sh index d608674cab..3a3d715de3 100755 --- a/tests/codex/test-package-codex-plugin.sh +++ b/tests/codex/test-package-codex-plugin.sh @@ -140,6 +140,9 @@ 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 From 592dd0215a2d241220b2c438d61f123137c69230 Mon Sep 17 00:00:00 2001 From: Drew Ritter Date: Tue, 30 Jun 2026 17:45:41 -0700 Subject: [PATCH 18/20] Preserve hooks in Codex package manifest --- scripts/package-codex-plugin.sh | 6 ------ tests/codex/test-package-codex-plugin.sh | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/scripts/package-codex-plugin.sh b/scripts/package-codex-plugin.sh index 91458b5163..00399f061b 100755 --- a/scripts/package-codex-plugin.sh +++ b/scripts/package-codex-plugin.sh @@ -242,12 +242,6 @@ git -C "$REPO_ROOT" archive --format=tar "$REF" -- \ VERSION="$(jq -r '.version // empty' "$STAGE/.codex-plugin/plugin.json")" [[ -n "$VERSION" ]] || die "could not read version from .codex-plugin/plugin.json" -if jq -e 'has("hooks")' "$STAGE/.codex-plugin/plugin.json" >/dev/null; then - manifest_tmp="$WORK_DIR/plugin-manifest.json" - jq 'del(.hooks)' "$STAGE/.codex-plugin/plugin.json" >"$manifest_tmp" - mv "$manifest_tmp" "$STAGE/.codex-plugin/plugin.json" -fi - if [[ -z "$OUTPUT" ]]; then case "$FORMAT" in zip) diff --git a/tests/codex/test-package-codex-plugin.sh b/tests/codex/test-package-codex-plugin.sh index 3a3d715de3..62c73f1cc1 100755 --- a/tests/codex/test-package-codex-plugin.sh +++ b/tests/codex/test-package-codex-plugin.sh @@ -173,7 +173,7 @@ assert_contains "$archive_paths" "assets/superpowers-small.svg" "archive include 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/ None" "archive manifest is current and hook-free" +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 ' ')" From 4346e2c529629e58d473846e2796d322689b3aed Mon Sep 17 00:00:00 2001 From: gercotermaat Date: Thu, 2 Jul 2026 16:11:36 +0200 Subject: [PATCH 19/20] feat(using-superpowers): add Claude Code to Platform Adaptation section --- skills/using-superpowers/SKILL.md | 1 + 1 file changed, 1 insertion(+) diff --git a/skills/using-superpowers/SKILL.md b/skills/using-superpowers/SKILL.md index 8a08873ba0..8c8d64fb3a 100644 --- a/skills/using-superpowers/SKILL.md +++ b/skills/using-superpowers/SKILL.md @@ -56,6 +56,7 @@ If your harness appears here, read its reference file for special instructions: - Codex: `references/codex-tools.md` - Pi: `references/pi-tools.md` - Antigravity: `references/antigravity-tools.md` +- Claude Code: `references/claude-tools.md` ## User Instructions From 4a0db1b371febc9d8e5bece15f3c96fe12fbe240 Mon Sep 17 00:00:00 2001 From: gercotermaat Date: Thu, 2 Jul 2026 16:14:33 +0200 Subject: [PATCH 20/20] feat(using-superpowers): add claude-tools.md for Claude Code tool mapping --- .../references/claude-tools.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 skills/using-superpowers/references/claude-tools.md diff --git a/skills/using-superpowers/references/claude-tools.md b/skills/using-superpowers/references/claude-tools.md new file mode 100644 index 0000000000..d258e71fff --- /dev/null +++ b/skills/using-superpowers/references/claude-tools.md @@ -0,0 +1,18 @@ +# Claude Code Tool Mapping + +Skills speak in actions ("dispatch a subagent", "create a todo", "read a file"). On Claude Code these resolve to the tools below. + +| Action skills request | Claude Code equivalent | +|----------------------|------------------------| +| Invoke a skill | `Skill` | +| Dispatch a subagent (`Subagent (general-purpose):` template) | `Agent` (older releases named this `Task`) | +| Multiple parallel dispatches | Multiple `Agent` calls in one response | +| Task tracking ("create a todo", "mark complete") | `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet`; `TodoWrite` in `claude -p` / Agent SDK unless `CLAUDE_CODE_ENABLE_TASKS=1` is set | + +## Instructions file + +When a skill mentions "your instructions file", on Claude Code this is **`CLAUDE.md`** at the project root. Claude Code also reads `~/.claude/CLAUDE.md` for global context. Claude Code walks the directory tree upward from the working directory, loading `CLAUDE.md` files it finds along the way. + +## Personal skills directory + +User-level skills live at **`~/.claude/skills/`**.