diff --git a/docs/superpowers/plans/2026-05-12-codex-native-hooks.md b/docs/superpowers/plans/2026-05-12-codex-native-hooks.md new file mode 100644 index 00000000..92da5682 --- /dev/null +++ b/docs/superpowers/plans/2026-05-12-codex-native-hooks.md @@ -0,0 +1,369 @@ +# Codex Native Hooks Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Give Codex CLI users native Superpowers startup behavior through trusted Codex plugin hooks, keep the current Codex plugin/skills path as a fallback while `plugin_hooks` is gated, and prepare Codex App user-facing parity behind a visible `/hooks` UI smoke test. + +**Architecture:** Add a Codex-specific hook manifest that points Codex at the existing shared `hooks/session-start` implementation through the existing cross-platform `hooks/run-hook.cmd` wrapper. Package the hook files into the official Codex plugin sync output. Keep Claude Code's existing `hooks/hooks.json`, Cursor's `hooks/hooks-cursor.json`, and Copilot/unknown-platform output behavior unchanged. + +**Tech Stack:** Bash hook scripts, JSON plugin manifests, shell regression tests, Node.js only for JSON parsing in tests, Codex CLI/app-server smoke verification. No new runtime dependencies. + +--- + +## Source Map + +- Spec: `docs/superpowers/specs/2026-05-12-codex-native-hooks-design.md` +- Codex plugin manifest: `.codex-plugin/plugin.json` +- Claude hook manifest: `hooks/hooks.json` +- Cursor hook manifest: `hooks/hooks-cursor.json` +- New Codex hook manifest: `hooks/hooks-codex.json` +- Shared hook wrapper: `hooks/run-hook.cmd` +- Shared hook implementation: `hooks/session-start` +- Codex plugin sync script: `scripts/sync-to-codex-plugin.sh` +- Codex sync regression test: `tests/codex-plugin-sync/test-sync-to-codex-plugin.sh` +- New hook output regression test: `tests/hooks/test-session-start.sh` +- User docs: `README.md` +- Windows hook docs: `docs/windows/polyglot-hooks.md` + +## Locked Decisions + +- Use `hooks/hooks-codex.json` for Codex instead of changing the shared Claude `hooks/hooks.json`. +- Do not add `resume` to the Claude matcher until Claude Code is explicitly verified to accept it. +- Use Codex's verified `${PLUGIN_ROOT}` placeholder in the Codex manifest command. +- Do not use or document `CODEX_PLUGIN_ROOT`; Codex plugin hooks provide `PLUGIN_ROOT`, `CLAUDE_PLUGIN_ROOT`, `PLUGIN_DATA`, and `CLAUDE_PLUGIN_DATA`. +- Do not auto-trust hooks from Superpowers. Users trust executable hooks through Codex's `/hooks` UI. +- Keep fallback behavior: Codex users without enabled/trusted plugin hooks still get installed skills, but not automatic startup bootstrap. +- Treat App runtime support as verified through the bundled app-server, but only claim App user-facing parity after a visible App `/hooks` UI smoke test. + +## Task 1: Baseline And Contract Check + +- [ ] Run: + + ```bash + git status --short --branch + codex --version + codex features list + /Applications/Codex.app/Contents/Resources/codex --version + ``` + +- [ ] Expected observations: + - Worktree status is understood before edits. + - Local Codex is at least `0.130.0`. + - `plugin_hooks` exists and is still under development/default-off, or any drift is recorded in the implementation notes. + - App-bundled Codex can be invoked, or the App smoke task is marked blocked with the exact command error. + +- [ ] Read the files in the Source Map before editing. + +## Task 2: Add SessionStart Output Regression Tests + +- [ ] Create `tests/hooks/test-session-start.sh`. + +- [ ] The test file must: + - Run from any current working directory. + - Use a temporary `HOME` for each scenario. + - Invoke the real `hooks/session-start`. + - Parse JSON with Node.js from stdin; do not add npm dependencies. + - Fail on invalid JSON, empty injected context, or platform output shape drift. + +- [ ] Cover these scenarios: + - Claude Code: `CLAUDE_PLUGIN_ROOT="$REPO_ROOT"` emits `hookSpecificOutput.hookEventName = "SessionStart"` and a non-empty `hookSpecificOutput.additionalContext`. + - Codex plugin hooks: `PLUGIN_DATA="$tmp/data" CLAUDE_PLUGIN_DATA="$tmp/data" PLUGIN_ROOT="$REPO_ROOT" CLAUDE_PLUGIN_ROOT="$REPO_ROOT"` emits the same nested `hookSpecificOutput.additionalContext` shape. + - Cursor: `CURSOR_PLUGIN_ROOT="$REPO_ROOT" CLAUDE_PLUGIN_ROOT="$REPO_ROOT"` emits top-level `additional_context` and does not also emit `hookSpecificOutput`. + - Copilot CLI: `COPILOT_CLI=1 CLAUDE_PLUGIN_ROOT="$REPO_ROOT"` emits top-level `additionalContext` and does not also emit `hookSpecificOutput`. + - Claude legacy warning: with `$HOME/.config/superpowers/skills` present and `CLAUDE_PLUGIN_ROOT="$REPO_ROOT"`, the injected context still contains the existing Claude migration guidance to `~/.claude/skills`. + - Codex legacy warning: with `$HOME/.config/superpowers/skills` present and the Codex plugin-hook environment, the injected context does not mention `~/.claude/skills` or "Claude Code's skills system" and instead uses harness-neutral custom-skill wording. + +- [ ] Run the test before changing `hooks/session-start`: + + ```bash + bash tests/hooks/test-session-start.sh + ``` + +- [ ] Expected result before implementation: the Codex legacy warning scenario fails because the current shared hook sees `CLAUDE_PLUGIN_ROOT` and emits Claude-specific migration text. + +## Task 3: Make Legacy Warning Text Harness-Aware + +- [ ] Edit `hooks/session-start`. + +- [ ] Preserve these existing behaviors: + - The script derives the plugin root from its own location. + - The script reads `skills/using-superpowers/SKILL.md`. + - Cursor receives only top-level `additional_context`. + - Claude receives only nested `hookSpecificOutput.additionalContext`. + - Codex receives the same nested shape as Claude, because the verified Codex parser accepts it and Codex sets `CLAUDE_PLUGIN_ROOT`. + - Copilot and unknown platforms receive only top-level `additionalContext`. + +- [ ] Add Codex detection before building the legacy warning. Use Codex data env vars, not the locally derived `PLUGIN_ROOT` variable: + + ```bash + is_codex_hook=0 + if [ -n "${PLUGIN_DATA:-}" ] || [ -n "${CLAUDE_PLUGIN_DATA:-}" ]; then + is_codex_hook=1 + fi + ``` + +- [ ] Keep the current Claude legacy warning text for non-Codex Claude. + +- [ ] For Codex hook runs, use this neutral warning content when `$HOME/.config/superpowers/skills` exists: + + ```text + WARNING: Superpowers now uses your coding agent's native skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move custom skills to a skills location supported by your coding agent. To make this message go away, remove ~/.config/superpowers/skills + ``` + +- [ ] Re-run: + + ```bash + bash tests/hooks/test-session-start.sh + ``` + +- [ ] Expected result after implementation: all session-start scenarios pass. + +## Task 4: Add Codex Hook Manifest + +- [ ] Add `hooks/hooks-codex.json`: + + ```json + { + "hooks": { + "SessionStart": [ + { + "matcher": "startup|resume|clear", + "hooks": [ + { + "type": "command", + "command": "\"${PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start", + "async": false + } + ] + } + ] + } + } + ``` + +- [ ] Edit `.codex-plugin/plugin.json` and add: + + ```json + "hooks": "./hooks/hooks-codex.json" + ``` + +- [ ] Keep `.claude-plugin/plugin.json`, `.cursor-plugin/plugin.json`, `hooks/hooks.json`, and `hooks/hooks-cursor.json` unchanged unless a test proves they must change. + +- [ ] Validate JSON: + + ```bash + node -e 'for (const f of [".codex-plugin/plugin.json","hooks/hooks-codex.json","hooks/hooks.json","hooks/hooks-cursor.json"]) JSON.parse(require("fs").readFileSync(f,"utf8"));' + ``` + +## Task 5: Package Hooks In Codex Plugin Sync + +- [ ] Edit `scripts/sync-to-codex-plugin.sh`. + +- [ ] Remove the `/hooks/` entry from `EXCLUDES`. + +- [ ] Do not broaden the sync script beyond this packaging change. + +- [ ] Edit `tests/codex-plugin-sync/test-sync-to-codex-plugin.sh` fixtures: + - In `write_upstream_fixture`, create: + - `hooks/hooks-codex.json` + - `hooks/session-start` + - `hooks/run-hook.cmd` + - Add those files to the upstream fixture commit. + - In `write_synced_destination_fixture`, create and commit the same hook files under `plugins/superpowers/hooks/` so the clean no-op apply scenario stays clean. + +- [ ] Add preview assertions: + - Preview includes `hooks/hooks-codex.json`. + - Preview includes `hooks/session-start`. + - Preview includes `hooks/run-hook.cmd`. + +- [ ] Add clean no-op assertion: + - Clean no-op local apply reports no changes when hook files are already synced. + +- [ ] Run: + + ```bash + bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh + ``` + +- [ ] Expected result: all existing sync assertions still pass, and the new hook packaging assertions pass. + +## Task 6: Update README Codex Docs + +- [ ] Edit `README.md`. + +- [ ] In `### Codex CLI`, keep the existing marketplace install steps and add a short optional preview subsection after install: + + ````markdown + #### Automatic startup bootstrap + + Codex plugin hooks are still gated behind Codex's `plugin_hooks` feature. To opt in: + + ```bash + codex features enable plugin_hooks + ``` + + Restart Codex, open `/hooks`, review the Superpowers `SessionStart` hook, and trust it. Codex will ask you to re-review the hook after Superpowers updates if the hook definition changes. + + If `plugin_hooks` is disabled, unavailable, or untrusted, Superpowers still installs as a normal Codex plugin and the skills remain available. The automatic startup bootstrap is the part that waits for the trusted hook. + ```` + +- [ ] In `### Codex App`, keep the existing install steps. + +- [ ] If the visible App `/hooks` UI smoke in Task 9 passes during implementation, add the same automatic startup bootstrap subsection to `### Codex App`, with the enablement step described as the persisted Codex config path: + + ```bash + codex features enable plugin_hooks + ``` + +- [ ] If the visible App `/hooks` UI smoke is blocked or fails, do not claim automatic App startup bootstrap in the README. Record the App smoke status in the implementation summary instead. + +- [ ] README wording must not imply automatic startup for Codex users who have not enabled and trusted plugin hooks. + +## Task 7: Update Windows Hook Docs + +- [ ] Edit `docs/windows/polyglot-hooks.md`. + +- [ ] Update stale references: + - Replace `session-start.cmd` with `run-hook.cmd`. + - Replace `session-start.sh` with extensionless `session-start`. + - Show `hooks/hooks-codex.json` as the Codex-specific manifest. + - Keep `hooks/hooks.json` as the Claude Code manifest. + - Mention Codex uses `${PLUGIN_ROOT}` and `startup|resume|clear`. + +- [ ] The docs must describe the current file structure: + + ```text + hooks/ + |-- hooks.json + |-- hooks-codex.json + |-- hooks-cursor.json + |-- run-hook.cmd + `-- session-start + ``` + +- [ ] Update the Windows simulation command to: + + ```powershell + $env:CLAUDE_PLUGIN_ROOT = "C:\path\to\plugin" + cmd /c "C:\path\to\plugin\hooks\run-hook.cmd session-start" + ``` + +- [ ] Do not add Windows-specific runtime dependencies. + +## Task 8: Run Codex CLI Native Hook Smoke + +- [ ] Build a temporary isolated Codex home and staged plugin from the working tree: + + ```bash + SMOKE_HOME="$(mktemp -d)" + SMOKE_PLUGIN="$SMOKE_HOME/plugins/cache/debug/superpowers/local" + mkdir -p "$SMOKE_PLUGIN" + rsync -a --exclude .git ./ "$SMOKE_PLUGIN/" + ``` + +- [ ] Use persisted feature config, not undocumented root CLI feature flags: + + ```bash + cat > "$SMOKE_HOME/config.toml" <<'EOF' + [features] + plugins = true + hooks = true + plugin_hooks = true + + [plugins."superpowers@debug"] + enabled = true + EOF + ``` + +- [ ] Run Codex `app-server --listen stdio://` against the temp home and call `hooks/list`: + + ```bash + CODEX_HOME="$SMOKE_HOME" codex app-server --listen stdio:// + ``` + +- [ ] Expected `hooks/list` result: + - One Superpowers `SessionStart` plugin hook appears from `hooks/hooks-codex.json`. + - The hook matcher is `startup|resume|clear`. + - The expanded command points at `hooks/run-hook.cmd session-start`. + - Initial `trustStatus` is `untrusted` unless the temp config already contains a trusted hash. + +- [ ] Trust the hook only inside the temp home using `hooks/list` `currentHash` plus `config/batchWrite`. + +- [ ] Start a clean Codex thread and trigger a turn. + +- [ ] Expected event/result: + - `hook/started` and `hook/completed` events are emitted for the Superpowers `SessionStart` hook. + - The resulting context includes the Superpowers startup text from `using-superpowers`. + +- [ ] Keep this as a development smoke only. Do not document headless trust as normal user setup. + +## Task 9: Run Codex App Visible UI Smoke + +- [ ] Confirm the App-bundled Codex runtime version: + + ```bash + /Applications/Codex.app/Contents/Resources/codex --version + ``` + +- [ ] With `plugin_hooks` enabled in persisted Codex config, launch the Codex App. + +- [ ] Install or use the staged Superpowers plugin in the App. + +- [ ] Open a clean App thread and observe the startup warning, if any. + +- [ ] Open `/hooks`. + +- [ ] Expected UI result: + - The Superpowers `SessionStart` hook is listed. + - The UI shows the hook command/source for review. + - Trusting the hook is possible from the UI. + - Toggling enabled after trust makes the hook runnable. + +- [ ] Run the acceptance prompt in a clean App thread: + + ```text + Let's make a react todo list + ``` + +- [ ] Passing App result: + - The agent sees the Superpowers startup context automatically. + - `superpowers:brainstorming` triggers before code is written. + +- [ ] If the App UI smoke passes, update README App docs in Task 6 and record the transcript or concise evidence in the PR body. + +- [ ] If the App UI smoke is blocked or fails, leave README App docs limited to plugin installation and report the exact blocker. + +## Task 10: Final Verification + +- [ ] Run: + + ```bash + bash tests/hooks/test-session-start.sh + bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh + node -e 'for (const f of [".codex-plugin/plugin.json","hooks/hooks-codex.json","hooks/hooks.json","hooks/hooks-cursor.json"]) JSON.parse(require("fs").readFileSync(f,"utf8"));' + git diff --check + ``` + +- [ ] Run the Codex CLI smoke from Task 8. + +- [ ] Run the App UI smoke from Task 9, or record why it could not be completed. + +- [ ] Review the diff: + + ```bash + git diff -- .codex-plugin/plugin.json hooks scripts tests README.md docs/windows/polyglot-hooks.md + ``` + +- [ ] Confirm no unrelated files changed. + +- [ ] If opening a PR, follow `.github/PULL_REQUEST_TEMPLATE.md`, search open and closed PRs for overlapping Codex hook work, include the relevant smoke evidence, and call out whether App UI smoke passed. + +## Completion Criteria + +- `hooks/hooks-codex.json` exists and is referenced by `.codex-plugin/plugin.json`. +- Codex plugin sync includes the `hooks/` directory and tests prevent future accidental exclusion. +- `hooks/session-start` still emits valid JSON for Claude, Codex, Cursor, Copilot, and unknown platform paths. +- Codex docs explain enablement, `/hooks` trust, re-review after updates, and fallback behavior without overclaiming default automatic startup. +- App docs claim automatic startup only if the visible App `/hooks` UI smoke passes. +- No new dependencies are introduced. +- Claude Code, Cursor, and Copilot behavior is not changed except for any neutral warning text that tests explicitly cover.