Переглянути джерело

docs: plan Codex native hooks implementation

Drew Ritter 4 місяців тому
батько
коміт
a57b6c5dc1
1 змінених файлів з 369 додано та 0 видалено
  1. 369 0
      docs/superpowers/plans/2026-05-12-codex-native-hooks.md

+ 369 - 0
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.