mirror of
https://github.com/obra/superpowers.git
synced 2026-07-25 19:54:02 +08:00
feat(codex): ship the compaction hook as a plugin-provided hook
The compaction re-injection hook previously required users to hand-merge an example into user-level ~/.codex/hooks.json — which renders in the Codex hooks UI as an anonymous, unattributed "Hook 1" and puts the install burden on every user. Restore the plugin-provided delivery this repo used before "Remove Codex hooks" (640ce6c0): the Codex manifest points hooks at hooks/hooks-codex.json, which runs session-start-codex via ${PLUGIN_ROOT}/hooks/run-hook.cmd with matcher "compact". Unlike the removed hook, this one never fires at session start — Codex surfaces skills natively there, which is why the old startup-injecting hook was removed. The matcher plus the script's own source gate restrict it to post-compaction re-starts. The explicit manifest pointer also keeps suppressing Codex's hooks/hooks.json auto-discovery fallback, which the previous empty-object declaration existed for (7d8d3d4b). The Codex portal archive now ships hooks/hooks-codex.json, hooks/run-hook.cmd, and hooks/session-start-codex; other-harness hook files stay excluded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@
|
|||||||
"workflow"
|
"workflow"
|
||||||
],
|
],
|
||||||
"skills": "./skills/",
|
"skills": "./skills/",
|
||||||
"hooks": {},
|
"hooks": "./hooks/hooks-codex.json",
|
||||||
"interface": {
|
"interface": {
|
||||||
"displayName": "Superpowers",
|
"displayName": "Superpowers",
|
||||||
"shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents",
|
"shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents",
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -98,37 +98,18 @@ Superpowers is available via the [official Codex plugin marketplace](https://git
|
|||||||
|
|
||||||
- Select `Install Plugin`.
|
- Select `Install Plugin`.
|
||||||
|
|
||||||
#### Codex: compaction re-injection hook (recommended)
|
#### Codex: compaction re-injection hook
|
||||||
|
|
||||||
Codex compacts long sessions, replacing the transcript with a summary that
|
Codex compacts long sessions, replacing the transcript with a summary that
|
||||||
drops Superpowers' skill instructions mid-run — long autonomous workflows
|
drops Superpowers' skill instructions mid-run — long autonomous workflows
|
||||||
(like subagent-driven-development) then drift back to harness defaults.
|
(like subagent-driven-development) then drift back to harness defaults.
|
||||||
Claude Code re-injects the bootstrap after every compaction; this hook
|
Claude Code re-injects the bootstrap after every compaction; the plugin ships
|
||||||
restores the same behavior on Codex (0.145+).
|
a SessionStart hook (`hooks/hooks-codex.json`) that restores the same
|
||||||
|
behavior on Codex (0.145+). It fires only on post-compaction re-starts
|
||||||
|
(`source: "compact"`) and is silent at normal session start.
|
||||||
|
|
||||||
Merge `hooks/hooks-codex.json.example` from your Superpowers install into
|
The hook installs with the plugin — no configuration needed. Codex asks you
|
||||||
`~/.codex/hooks.json`, replacing the placeholder with the absolute path to
|
to review and trust it once, the first time it loads after install or update.
|
||||||
your install:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"hooks": {
|
|
||||||
"SessionStart": [
|
|
||||||
{
|
|
||||||
"hooks": [
|
|
||||||
{
|
|
||||||
"type": "command",
|
|
||||||
"command": "bash \"/path/to/superpowers/hooks/session-start-codex\"",
|
|
||||||
"timeout": 30
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Codex asks you to trust the hook once, the first time it runs in the app.
|
|
||||||
Headless automation (CI, eval harnesses) must pass
|
Headless automation (CI, eval harnesses) must pass
|
||||||
`--dangerously-bypass-hook-trust` instead, because untrusted hooks are
|
`--dangerously-bypass-hook-trust` instead, because untrusted hooks are
|
||||||
skipped silently.
|
skipped silently.
|
||||||
|
|||||||
@@ -237,10 +237,12 @@ nesting differ per harness**.
|
|||||||
- Manifests: `.cursor-plugin/plugin.json` is the Shape A manifest example that
|
- 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
|
points the harness at `./skills/` and the right `hooks-*.json`. Claude Code's
|
||||||
`.claude-plugin/plugin.json` sets neither field — it auto-discovers `skills/`
|
`.claude-plugin/plugin.json` sets neither field — it auto-discovers `skills/`
|
||||||
and `hooks/hooks.json` by convention. Do **not** copy Codex's
|
and `hooks/hooks.json` by convention. Codex's `.codex-plugin/plugin.json`
|
||||||
`.codex-plugin/plugin.json` for Shape A: it declares an empty `hooks` object
|
points `hooks` at `./hooks/hooks-codex.json` — a compaction-only hook, not a
|
||||||
specifically to suppress Codex's `hooks/hooks.json` auto-discovery, because
|
bootstrap injector: Codex surfaces skills natively at session start, so its
|
||||||
Codex surfaces skills natively and runs no session-start hook.
|
hook fires only on post-compaction re-starts. The explicit pointer also
|
||||||
|
suppresses Codex's `hooks/hooks.json` auto-discovery fallback, which would
|
||||||
|
otherwise run the Claude Code hook.
|
||||||
|
|
||||||
> **A hook *system* is not a session-start *event*.** A harness can have a
|
> **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
|
> `hooks.json` mechanism — and even contain the literal string `SessionStart` in
|
||||||
@@ -785,7 +787,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 |
|
| 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; no adapter file needed | `tests/hooks/` | marketplace |
|
| Claude Code | `.claude-plugin/plugin.json` + `hooks/hooks.json` | shell hook → `hooks/session-start` (`hookSpecificOutput.additionalContext`) | native `Skill` tool; no adapter file needed | `tests/hooks/` | marketplace |
|
||||||
| Codex | `.codex-plugin/plugin.json` (declares empty `hooks`) | native skill discovery (no session-start hook) | `references/codex-tools.md` | `tests/codex/`, `tests/codex-plugin-sync/` | fork sync (`scripts/sync-to-codex-plugin.sh`) |
|
| Codex | `.codex-plugin/plugin.json` + `hooks/hooks-codex.json` | native skill discovery at startup; shell hook → `hooks/session-start-codex` re-injects after compaction only | `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`) | none needed (Claude Code–compatible tool surface) | `tests/hooks/` | hand-authored |
|
| Cursor | `.cursor-plugin/plugin.json` + `hooks/hooks-cursor.json` | shell hook → `hooks/session-start` (`additional_context`) | none needed (Claude Code–compatible tool surface) | `tests/hooks/` | hand-authored |
|
||||||
| Copilot CLI | (shares Claude Code hook path; `COPILOT_CLI` env) | shell hook → `hooks/session-start` (`additionalContext`) | none needed (Claude Code–compatible tool surface) | `tests/hooks/` | — |
|
| Copilot CLI | (shares Claude Code hook path; `COPILOT_CLI` env) | shell hook → `hooks/session-start` (`additionalContext`) | none needed (Claude Code–compatible tool surface) | `tests/hooks/` | — |
|
||||||
| Gemini CLI | `gemini-extension.json` + `GEMINI.md` | instructions file `@`-includes bootstrap + mapping | `references/gemini-tools.md` | — | `gemini extensions install` |
|
| Gemini CLI | `gemini-extension.json` + `GEMINI.md` | instructions file `@`-includes bootstrap + mapping | `references/gemini-tools.md` | — | `gemini extensions install` |
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"SessionStart": [
|
"SessionStart": [
|
||||||
{
|
{
|
||||||
|
"matcher": "compact",
|
||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "bash \"/ABSOLUTE/PATH/TO/superpowers/hooks/session-start-codex\"",
|
"command": "\"${PLUGIN_ROOT}/hooks/run-hook.cmd\" session-start-codex",
|
||||||
|
"async": false,
|
||||||
"timeout": 30
|
"timeout": 30
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -40,8 +40,9 @@ Options:
|
|||||||
-h, --help Show this help.
|
-h, --help Show this help.
|
||||||
|
|
||||||
The archive is rootless: .codex-plugin/, assets/, skills/, README.md, LICENSE,
|
The archive is rootless: .codex-plugin/, assets/, skills/, README.md, LICENSE,
|
||||||
and CODE_OF_CONDUCT.md sit at the archive root. Source-only repo files, hooks, tests,
|
CODE_OF_CONDUCT.md, and the Codex SessionStart hook (hooks/hooks-codex.json plus
|
||||||
docs, and other harness manifests are intentionally not shipped.
|
its two scripts) sit at the archive root. Source-only repo files, other-harness
|
||||||
|
hooks, tests, docs, and other harness manifests are intentionally not shipped.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +239,9 @@ git -C "$REPO_ROOT" -c tar.umask=0022 archive --format=tar "$REF" -- \
|
|||||||
LICENSE \
|
LICENSE \
|
||||||
README.md \
|
README.md \
|
||||||
assets \
|
assets \
|
||||||
|
hooks/hooks-codex.json \
|
||||||
|
hooks/run-hook.cmd \
|
||||||
|
hooks/session-start-codex \
|
||||||
skills \
|
skills \
|
||||||
| tar -xpf - -C "$STAGE"
|
| tar -xpf - -C "$STAGE"
|
||||||
|
|
||||||
@@ -333,7 +337,7 @@ esac
|
|||||||
|
|
||||||
unexpected_paths="$(
|
unexpected_paths="$(
|
||||||
printf '%s\n' "$archive_paths" |
|
printf '%s\n' "$archive_paths" |
|
||||||
grep -E '(^superpowers/|^\.agents/|^hooks/|package\.json$|^\.git|^\.pytest_cache|^\.ruff_cache|^scripts/|^tests/|^docs/|^evals/|^lib/|^\.claude|^\.cursor|^\.kimi|^\.opencode|^\.pi|^AGENTS\.md$|^CLAUDE\.md$|^GEMINI\.md$|^RELEASE-NOTES\.md$|^CHANGELOG\.md$)' || true
|
grep -E '(^superpowers/|^\.agents/|^hooks/hooks\.json$|^hooks/hooks-cursor\.json$|^hooks/session-start$|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
|
if [[ -n "$unexpected_paths" ]]; then
|
||||||
printf '%s\n' "$unexpected_paths" | sed 's/^/ /' >&2
|
printf '%s\n' "$unexpected_paths" | sed 's/^/ /' >&2
|
||||||
|
|||||||
@@ -42,12 +42,14 @@ Context compaction replaces your transcript with a summary that keeps
|
|||||||
your progress but not your working instructions — the first
|
your progress but not your working instructions — the first
|
||||||
post-compaction dispatch is where routing drift starts, and once one
|
post-compaction dispatch is where routing drift starts, and once one
|
||||||
bare spawn lands, the broken pattern becomes its own precedent. The
|
bare spawn lands, the broken pattern becomes its own precedent. The
|
||||||
compaction re-injection hook (README, "Codex: compaction re-injection
|
plugin ships a compaction re-injection hook (`hooks/hooks-codex.json`,
|
||||||
hook") restores the bootstrap after every compaction; recommend it to
|
Codex 0.145+) that restores the bootstrap after every compaction; it
|
||||||
your human partner if it is not installed. Without it, the printed
|
needs one-time trust approval, so if you never see a
|
||||||
`dispatch:` hints are your only re-grounding — treat every one you see
|
`<CONTEXT_RESTORED>` block after a compaction, tell your human partner
|
||||||
as authoritative, especially right after a summary appears in your
|
the hook may be untrusted or unsupported on this version. Without it,
|
||||||
context.
|
the printed `dispatch:` hints are your only re-grounding — treat every
|
||||||
|
one you see as authoritative, especially right after a summary appears
|
||||||
|
in your context.
|
||||||
|
|
||||||
## Environment Detection
|
## Environment Detection
|
||||||
|
|
||||||
|
|||||||
@@ -52,25 +52,37 @@ if not plugin_manifest.exists():
|
|||||||
manifest = json.loads(plugin_manifest.read_text(encoding="utf-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("name"), plugin.get("name"), "plugin manifest name")
|
||||||
|
|
||||||
# Codex auto-discovers a plugin's hooks/hooks.json whenever the Codex manifest
|
# The Codex manifest must declare its hooks explicitly. An absent field makes
|
||||||
# has no `hooks` field: load_plugin_hooks falls back to a hardcoded
|
# load_plugin_hooks fall back to a hardcoded DEFAULT_HOOKS_CONFIG_FILE =
|
||||||
# DEFAULT_HOOKS_CONFIG_FILE = "hooks/hooks.json" and registers it. That file is
|
# "hooks/hooks.json" — the Claude Code SessionStart hook, which injects the
|
||||||
# the Claude Code SessionStart hook, it is tracked in this repo, and this
|
# bootstrap at startup and must not run on Codex. The explicit pointer both
|
||||||
# marketplace installs the whole repo root (source url "./"), so on Codex the
|
# registers the Codex compaction re-injection hook and overrides that fallback.
|
||||||
# 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"
|
hooks_config = repo_root / "hooks" / "hooks.json"
|
||||||
if not hooks_config.exists():
|
if not hooks_config.exists():
|
||||||
raise AssertionError("hooks/hooks.json must exist (Claude Code SessionStart hook)")
|
raise AssertionError("hooks/hooks.json must exist (Claude Code SessionStart hook)")
|
||||||
|
|
||||||
assert_equal(
|
assert_equal(
|
||||||
manifest.get("hooks"),
|
manifest.get("hooks"),
|
||||||
{},
|
"./hooks/hooks-codex.json",
|
||||||
"Codex manifest must declare empty hooks {} to suppress hooks/hooks.json auto-discovery",
|
"Codex manifest must point hooks at the Codex hook config (an absent field "
|
||||||
|
"falls back to auto-discovering the Claude Code hooks/hooks.json)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
codex_hooks_path = repo_root / "hooks" / "hooks-codex.json"
|
||||||
|
if not codex_hooks_path.exists():
|
||||||
|
raise AssertionError("hooks/hooks-codex.json must exist (Codex manifest points at it)")
|
||||||
|
|
||||||
|
codex_hooks = json.loads(codex_hooks_path.read_text(encoding="utf-8"))
|
||||||
|
session_start = codex_hooks["hooks"]["SessionStart"]
|
||||||
|
assert_equal(len(session_start), 1, "Codex SessionStart hook group count")
|
||||||
|
assert_equal(session_start[0].get("matcher"), "compact", "Codex hook matcher")
|
||||||
|
entry = session_start[0]["hooks"][0]
|
||||||
|
assert_equal(entry.get("type"), "command", "Codex hook type")
|
||||||
|
command = entry.get("command", "")
|
||||||
|
if "${PLUGIN_ROOT}" not in command or not command.endswith("session-start-codex"):
|
||||||
|
raise AssertionError(
|
||||||
|
f"Codex hook command must run session-start-codex via ${{PLUGIN_ROOT}}: {command!r}"
|
||||||
|
)
|
||||||
|
|
||||||
print("Codex marketplace manifest looks good")
|
print("Codex marketplace manifest looks good")
|
||||||
PY
|
PY
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ tar_extracted="$TEST_ROOT/tar-extracted"
|
|||||||
write_metadata_fixture "$metadata_source"
|
write_metadata_fixture "$metadata_source"
|
||||||
|
|
||||||
source_hooks="$(python3 -c 'import json; print(json.load(open("'"$REPO_ROOT"'/.codex-plugin/plugin.json")).get("hooks"))')"
|
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"
|
assert_equals "$source_hooks" "./hooks/hooks-codex.json" "source Codex manifest declares the Codex hook config"
|
||||||
|
|
||||||
if output="$("$SCRIPT_UNDER_TEST" --allow-dirty --metadata-source "$metadata_source" --output "$archive" 2>&1)"; then
|
if output="$("$SCRIPT_UNDER_TEST" --allow-dirty --metadata-source "$metadata_source" --output "$archive" 2>&1)"; then
|
||||||
pass "package script exits successfully"
|
pass "package script exits successfully"
|
||||||
@@ -163,10 +163,13 @@ assert_contains "$output" "SHA-256:" "reports archive checksum"
|
|||||||
extract_archive "$archive" "$extracted"
|
extract_archive "$archive" "$extracted"
|
||||||
|
|
||||||
archive_paths="$(list_archive "$archive" | normalize_archive_paths)"
|
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$)'
|
unexpected_pattern='(^superpowers/|^\.agents/|^hooks/hooks\.json$|^hooks/hooks-cursor\.json$|^hooks/session-start$|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_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" ".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/SKILL.md" "archive includes skills"
|
||||||
|
assert_contains "$archive_paths" "hooks/hooks-codex.json" "archive includes Codex hook config"
|
||||||
|
assert_contains "$archive_paths" "hooks/session-start-codex" "archive includes Codex hook script"
|
||||||
|
assert_contains "$archive_paths" "hooks/run-hook.cmd" "archive includes hook runner"
|
||||||
assert_contains "$archive_paths" "skills/brainstorming/agents/openai.yaml" "archive includes OpenAI skill metadata"
|
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/app-icon.png" "archive includes app icon"
|
||||||
assert_contains "$archive_paths" "assets/superpowers-small.svg" "archive includes composer icon"
|
assert_contains "$archive_paths" "assets/superpowers-small.svg" "archive includes composer icon"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && 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"
|
||||||
EXAMPLE_UNDER_TEST="$REPO_ROOT/hooks/hooks-codex.json.example"
|
CONFIG_UNDER_TEST="$REPO_ROOT/hooks/hooks-codex.json"
|
||||||
|
|
||||||
FAILURES=0
|
FAILURES=0
|
||||||
|
|
||||||
@@ -86,20 +86,25 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if node -e '
|
if node -e '
|
||||||
const example = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
const config = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
||||||
const entry = example.hooks.SessionStart[0].hooks[0];
|
const group = config.hooks.SessionStart[0];
|
||||||
|
if (group.matcher !== "compact") {
|
||||||
|
console.error(`hook matcher is ${JSON.stringify(group.matcher)}, expected "compact"`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const entry = group.hooks[0];
|
||||||
if (entry.type !== "command") {
|
if (entry.type !== "command") {
|
||||||
console.error(`example hook type is ${JSON.stringify(entry.type)}, expected "command"`);
|
console.error(`hook type is ${JSON.stringify(entry.type)}, expected "command"`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
if (!/session-start-codex"$/.test(entry.command)) {
|
if (!entry.command.includes("${PLUGIN_ROOT}") || !/run-hook\.cmd" session-start-codex$/.test(entry.command)) {
|
||||||
console.error(`unexpected example command shape: ${entry.command}`);
|
console.error(`unexpected command shape: ${entry.command}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
' "$EXAMPLE_UNDER_TEST"; then
|
' "$CONFIG_UNDER_TEST"; then
|
||||||
pass "hooks-codex.json.example parses and invokes session-start-codex"
|
pass "hooks-codex.json runs session-start-codex via \${PLUGIN_ROOT} on compact"
|
||||||
else
|
else
|
||||||
fail "hooks-codex.json.example parses and invokes session-start-codex"
|
fail "hooks-codex.json runs session-start-codex via \${PLUGIN_ROOT} on compact"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$FAILURES" -gt 0 ]]; then
|
if [[ "$FAILURES" -gt 0 ]]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user