mirror of
https://github.com/obra/superpowers.git
synced 2026-05-15 13:39:05 +08:00
docs: plan Codex native hooks implementation
This commit is contained in:
369
docs/superpowers/plans/2026-05-12-codex-native-hooks.md
Normal file
369
docs/superpowers/plans/2026-05-12-codex-native-hooks.md
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user