Skip to content

Commit c35cd75

Browse files
committed
Add native Codex hook support and harden install/test flows
- add native Codex hook config and installer, wire RLCR/PR stop hooks, and document Codex setup and usage - fix BitLesson selector routing and update related skills/docs - simplify install-skill target handling and harden RLCR test mock argument parsing
1 parent f330ede commit c35cd75

20 files changed

Lines changed: 1298 additions & 99 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ temp
66

77
# Humanize state directories (runtime-generated, project-local)
88
.humanize/
9+
.claude-flow/
10+
.swarm/
911

1012
# Python cache
1113
__pycache__/

config/codex-hooks.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"description": "Humanize Codex Hooks - Native Stop hooks for RLCR and PR loops",
3+
"hooks": {
4+
"Stop": [
5+
{
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "{{HUMANIZE_RUNTIME_ROOT}}/hooks/loop-codex-stop-hook.sh",
10+
"timeout": 7200,
11+
"statusMessage": "humanize RLCR stop hook"
12+
},
13+
{
14+
"type": "command",
15+
"command": "{{HUMANIZE_RUNTIME_ROOT}}/hooks/pr-loop-stop-hook.sh",
16+
"timeout": 7200,
17+
"statusMessage": "humanize PR stop hook"
18+
}
19+
]
20+
}
21+
]
22+
}
23+
}

docs/bitlesson.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Provider routing is automatic:
1818

1919
If the configured provider binary is missing, the selector falls back to the default Codex model so the loop can still proceed.
2020

21+
On Codex-only installs, Humanize writes `provider_mode: "codex-only"` into the user config.
22+
When that mode is present, the selector forces BitLesson selection onto the Codex/OpenAI path
23+
before provider resolution, even if an older default such as `haiku` would otherwise route to Claude.
24+
2125
## Workflow
2226

2327
Each project keeps its BitLesson knowledge base at `.humanize/bitlesson.md`.

docs/install-for-codex.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Install Humanize Skills for Codex
22

3-
This guide explains how to install the Humanize skills for Codex skill runtime (`$CODEX_HOME/skills`).
3+
This guide explains how to install Humanize for Codex CLI, including the skill runtime (`$CODEX_HOME/skills`) and the native Codex `Stop` hook (`$CODEX_HOME/hooks.json`).
44

55
## Quick Install (Recommended)
66

@@ -25,8 +25,14 @@ Or use the unified installer directly:
2525
This will:
2626
- Sync `humanize`, `humanize-gen-plan`, and `humanize-rlcr` into `${CODEX_HOME:-~/.codex}/skills`
2727
- Copy runtime dependencies into `${CODEX_HOME:-~/.codex}/skills/humanize`
28+
- Install/update native Humanize Stop hooks in `${CODEX_HOME:-~/.codex}/hooks.json`
29+
- Enable the experimental `codex_hooks` feature in `${CODEX_HOME:-~/.codex}/config.toml` when `codex` is available
30+
- Seed `~/.config/humanize/config.json` with a Codex/OpenAI `bitlesson_model` when that key is not already set
31+
- Mark the install as `provider_mode: "codex-only"` when using `--target codex`
2832
- Use RLCR defaults: `codex exec` with `gpt-5.4:high`, `codex review` with `gpt-5.4:high`
2933

34+
Requires Codex CLI `0.114.0` or newer for native hooks. Older Codex builds are not supported by the Codex install path.
35+
3036
## Verify
3137

3238
```bash
@@ -50,6 +56,21 @@ Installed files/directories:
5056
- `${CODEX_HOME:-~/.codex}/skills/humanize/scripts/`
5157
- `${CODEX_HOME:-~/.codex}/skills/humanize/hooks/`
5258
- `${CODEX_HOME:-~/.codex}/skills/humanize/prompt-template/`
59+
- `${CODEX_HOME:-~/.codex}/hooks.json`
60+
- `${XDG_CONFIG_HOME:-~/.config}/humanize/config.json` (created or updated only when Humanize config keys are unset)
61+
62+
Verify native hooks:
63+
64+
```bash
65+
codex features list | rg codex_hooks
66+
sed -n '1,220p' "${CODEX_HOME:-$HOME/.codex}/hooks.json"
67+
```
68+
69+
Expected:
70+
- `codex_hooks` is `true`
71+
- `hooks.json` contains `loop-codex-stop-hook.sh` and `pr-loop-stop-hook.sh`
72+
- `${XDG_CONFIG_HOME:-~/.config}/humanize/config.json` contains `bitlesson_model` set to a Codex/OpenAI model such as `gpt-5.4`
73+
- for `--target codex`, `${XDG_CONFIG_HOME:-~/.config}/humanize/config.json` also contains `provider_mode: "codex-only"`
5374

5475
## Optional: Install for Both Codex and Kimi
5576

@@ -65,6 +86,9 @@ Installed files/directories:
6586

6687
# Custom Codex skills dir
6788
./scripts/install-skills-codex.sh --codex-skills-dir /custom/codex/skills
89+
90+
# Reinstall only the native hooks/config
91+
./scripts/install-codex-hooks.sh
6892
```
6993

7094
## Troubleshooting
@@ -74,3 +98,10 @@ If scripts are not found from installed skills:
7498
```bash
7599
ls -la "${CODEX_HOME:-$HOME/.codex}/skills/humanize/scripts"
76100
```
101+
102+
If native exit gating does not trigger:
103+
104+
```bash
105+
codex features enable codex_hooks
106+
sed -n '1,220p' "${CODEX_HOME:-$HOME/.codex}/hooks.json"
107+
```

docs/usage.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ Current built-in keys:
179179
| `codex_model` | `gpt-5.4` | Shared default model for Codex-backed review and analysis |
180180
| `codex_effort` | `high` | Shared default reasoning effort (`xhigh`, `high`, `medium`, `low`) |
181181
| `bitlesson_model` | `haiku` | Model used by the BitLesson selector agent |
182+
| `provider_mode` | unset | Optional runtime mode hint such as `codex-only` |
182183
| `agent_teams` | `false` | Project-level default for agent teams workflow |
183184
| `chinese_plan` | `false` | Project preference for Chinese plan generation |
184185
| `gen_plan_mode` | `discussion` | Default plan-generation mode |
@@ -202,6 +203,10 @@ To override, add to `.humanize/config.json`:
202203
}
203204
```
204205

206+
On Codex installs, Humanize also seeds `${XDG_CONFIG_HOME:-~/.config}/humanize/config.json`
207+
with a Codex/OpenAI `bitlesson_model` and `provider_mode: "codex-only"` when those keys
208+
are unset, so BitLesson selection stays on the Codex/OpenAI path without probing Claude.
209+
205210
Codex model is resolved with this precedence:
206211
1. CLI `--codex-model` flag (highest priority)
207212
2. Feature-specific defaults (e.g., PR loop defaults to `medium` effort)

hooks/loop-codex-stop-hook.sh

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,9 @@ mkdir -p "$CACHE_DIR"
980980

981981
# portable-timeout.sh already sourced above
982982

983+
# Disable native hooks for nested Codex reviewer calls to prevent Stop-hook recursion.
984+
CODEX_DISABLE_HOOKS_ARGS=(--disable codex_hooks)
985+
983986
# Build command arguments for summary review (codex exec)
984987
CODEX_EXEC_ARGS=("-m" "$CODEX_EXEC_MODEL")
985988
if [[ -n "$CODEX_EXEC_EFFORT" ]]; then
@@ -1056,14 +1059,14 @@ Provider: codex
10561059
echo "# Review base ($review_base_type): $review_base"
10571060
echo "# Timeout: $CODEX_TIMEOUT seconds"
10581061
echo ""
1059-
echo "codex review --base $review_base ${CODEX_REVIEW_ARGS[*]}"
1062+
echo "codex ${CODEX_DISABLE_HOOKS_ARGS[*]} review --base $review_base ${CODEX_REVIEW_ARGS[*]}"
10601063
} > "$CODEX_REVIEW_CMD_FILE"
10611064

10621065
echo "Code review command saved to: $CODEX_REVIEW_CMD_FILE" >&2
10631066
echo "Running codex review with timeout ${CODEX_TIMEOUT}s in $PROJECT_ROOT (base: $review_base)..." >&2
10641067

10651068
CODEX_REVIEW_EXIT_CODE=0
1066-
(cd "$PROJECT_ROOT" && run_with_timeout "$CODEX_TIMEOUT" codex review --base "$review_base" "${CODEX_REVIEW_ARGS[@]}") \
1069+
(cd "$PROJECT_ROOT" && run_with_timeout "$CODEX_TIMEOUT" codex "${CODEX_DISABLE_HOOKS_ARGS[@]}" review --base "$review_base" "${CODEX_REVIEW_ARGS[@]}") \
10671070
> "$CODEX_REVIEW_LOG_FILE" 2>&1 || CODEX_REVIEW_EXIT_CODE=$?
10681071

10691072
echo "Code review exit code: $CODEX_REVIEW_EXIT_CODE" >&2
@@ -1404,7 +1407,7 @@ CODEX_PROMPT_CONTENT=$(cat "$REVIEW_PROMPT_FILE")
14041407
echo "# Working directory: $PROJECT_ROOT"
14051408
echo "# Timeout: $CODEX_TIMEOUT seconds"
14061409
echo ""
1407-
echo "codex exec ${CODEX_EXEC_ARGS[*]} \"<prompt>\""
1410+
echo "codex ${CODEX_DISABLE_HOOKS_ARGS[*]} exec ${CODEX_EXEC_ARGS[*]} \"<prompt>\""
14081411
echo ""
14091412
echo "# Prompt content:"
14101413
echo "$CODEX_PROMPT_CONTENT"
@@ -1414,7 +1417,7 @@ echo "Codex command saved to: $CODEX_CMD_FILE" >&2
14141417
echo "Running summary review with timeout ${CODEX_TIMEOUT}s..." >&2
14151418

14161419
CODEX_EXIT_CODE=0
1417-
printf '%s' "$CODEX_PROMPT_CONTENT" | run_with_timeout "$CODEX_TIMEOUT" codex exec "${CODEX_EXEC_ARGS[@]}" - \
1420+
printf '%s' "$CODEX_PROMPT_CONTENT" | run_with_timeout "$CODEX_TIMEOUT" codex "${CODEX_DISABLE_HOOKS_ARGS[@]}" exec "${CODEX_EXEC_ARGS[@]}" - \
14181421
> "$CODEX_STDOUT_FILE" 2> "$CODEX_STDERR_FILE" || CODEX_EXIT_CODE=$?
14191422

14201423
echo "Codex exit code: $CODEX_EXIT_CODE" >&2

hooks/pr-loop-stop-hook.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1334,12 +1334,15 @@ if [[ "${HUMANIZE_CODEX_BYPASS_SANDBOX:-}" == "true" ]] || [[ "${HUMANIZE_CODEX_
13341334
CODEX_AUTO_FLAG="--dangerously-bypass-approvals-and-sandbox"
13351335
fi
13361336

1337+
# Disable native hooks for nested Codex reviewer calls to prevent Stop-hook recursion.
1338+
CODEX_DISABLE_HOOKS_ARGS=(--disable codex_hooks)
1339+
13371340
CODEX_ARGS+=("$CODEX_AUTO_FLAG" "-C" "$PROJECT_ROOT")
13381341

13391342
CODEX_PROMPT_CONTENT=$(cat "$CODEX_PROMPT_FILE")
13401343
CODEX_EXIT_CODE=0
13411344

1342-
printf '%s' "$CODEX_PROMPT_CONTENT" | run_with_timeout "$PR_CODEX_TIMEOUT" codex exec "${CODEX_ARGS[@]}" - \
1345+
printf '%s' "$CODEX_PROMPT_CONTENT" | run_with_timeout "$PR_CODEX_TIMEOUT" codex "${CODEX_DISABLE_HOOKS_ARGS[@]}" exec "${CODEX_ARGS[@]}" - \
13431346
> "$CHECK_FILE" 2>/dev/null || CODEX_EXIT_CODE=$?
13441347

13451348
if [[ $CODEX_EXIT_CODE -ne 0 ]]; then

scripts/bitlesson-select.sh

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null
1515
MERGED_CONFIG="$(load_merged_config "$PLUGIN_ROOT" "$PROJECT_ROOT")"
1616
BITLESSON_MODEL="$(get_config_value "$MERGED_CONFIG" "bitlesson_model")"
1717
BITLESSON_MODEL="${BITLESSON_MODEL:-haiku}"
18+
CODEX_FALLBACK_MODEL="$(get_config_value "$MERGED_CONFIG" "codex_model")"
19+
CODEX_FALLBACK_MODEL="${CODEX_FALLBACK_MODEL:-$DEFAULT_CODEX_MODEL}"
20+
PROVIDER_MODE="$(get_config_value "$MERGED_CONFIG" "provider_mode")"
21+
PROVIDER_MODE="${PROVIDER_MODE:-auto}"
1822

1923
# Source portable timeout wrapper
2024
source "$SCRIPT_DIR/portable-timeout.sh"
@@ -82,12 +86,34 @@ if [[ -z "$BITLESSON_FILE" ]]; then
8286
exit 1
8387
fi
8488

89+
if [[ ! -f "$BITLESSON_FILE" ]]; then
90+
echo "Error: BitLesson file not found: $BITLESSON_FILE" >&2
91+
exit 1
92+
fi
93+
94+
BITLESSON_CONTENT="$(cat "$BITLESSON_FILE")"
95+
if [[ -z "$(printf '%s' "$BITLESSON_CONTENT" | tr -d ' \t\n\r')" ]]; then
96+
echo "Error: BitLesson file is empty (whitespace only): $BITLESSON_FILE" >&2
97+
exit 1
98+
fi
99+
100+
if ! printf '%s\n' "$BITLESSON_CONTENT" | grep -Eq '^[[:space:]]*##[[:space:]]+Lesson:'; then
101+
printf 'LESSON_IDS: NONE\n'
102+
printf 'RATIONALE: The BitLesson file has no recorded lessons yet.\n'
103+
exit 0
104+
fi
105+
85106
# ========================================
86107
# Determine Provider from BITLESSON_MODEL
87108
# ========================================
88109

89110
BITLESSON_PROVIDER="$(detect_provider "$BITLESSON_MODEL")"
90111

112+
if [[ "$PROVIDER_MODE" == "codex-only" ]] && [[ "$BITLESSON_PROVIDER" == "claude" ]]; then
113+
BITLESSON_MODEL="$CODEX_FALLBACK_MODEL"
114+
BITLESSON_PROVIDER="codex"
115+
fi
116+
91117
# ========================================
92118
# Conditional Dependency Check (with fallback)
93119
# ========================================
@@ -99,17 +125,6 @@ if ! check_provider_dependency "$BITLESSON_PROVIDER" 2>/dev/null; then
99125
check_provider_dependency "$BITLESSON_PROVIDER"
100126
fi
101127

102-
if [[ ! -f "$BITLESSON_FILE" ]]; then
103-
echo "Error: BitLesson file not found: $BITLESSON_FILE" >&2
104-
exit 1
105-
fi
106-
107-
BITLESSON_CONTENT="$(cat "$BITLESSON_FILE")"
108-
if [[ -z "$(printf '%s' "$BITLESSON_CONTENT" | tr -d ' \t\n\r')" ]]; then
109-
echo "Error: BitLesson file is empty (whitespace only): $BITLESSON_FILE" >&2
110-
exit 1
111-
fi
112-
113128
# ========================================
114129
# Detect Project Root (for -C)
115130
# ========================================
@@ -148,6 +163,7 @@ $BITLESSON_CONTENT
148163
1. Match only lessons that are directly relevant to the sub-task scope and failure mode.
149164
2. Prefer precision over recall: do not include weakly related lessons.
150165
3. If nothing is relevant, return \`NONE\`.
166+
4. Use only the information in this prompt. Do not use tools, shell commands, browser access, MCP servers, or repository inspection.
151167
152168
## Output Format (Stable)
153169
@@ -164,21 +180,35 @@ EOF
164180

165181
SELECTOR_TIMEOUT=120
166182

167-
CODEX_EXIT_CODE=0
168-
if [[ "$BITLESSON_PROVIDER" == "codex" ]]; then
169-
CODEX_EXEC_ARGS=("-m" "$BITLESSON_MODEL" "-c" "model_reasoning_effort=high")
183+
run_selector() {
184+
local provider="$1"
185+
local model="$2"
186+
187+
if [[ "$provider" == "codex" ]]; then
188+
local codex_exec_args=(
189+
"--disable" "codex_hooks"
190+
"--skip-git-repo-check"
191+
"--ephemeral"
192+
"-s" "read-only"
193+
"-m" "$model"
194+
"-c" "model_reasoning_effort=low"
195+
"-C" "$CODEX_PROJECT_ROOT"
196+
)
197+
printf '%s' "$PROMPT" | run_with_timeout "$SELECTOR_TIMEOUT" codex exec "${codex_exec_args[@]}" -
198+
return $?
199+
fi
170200

171-
# Determine automation flag based on environment variable (same as ask-codex.sh)
172-
CODEX_AUTO_FLAG="--full-auto"
173-
if [[ "${HUMANIZE_CODEX_BYPASS_SANDBOX:-}" == "true" ]] || [[ "${HUMANIZE_CODEX_BYPASS_SANDBOX:-}" == "1" ]]; then
174-
CODEX_AUTO_FLAG="--dangerously-bypass-approvals-and-sandbox"
201+
if [[ "$provider" == "claude" ]]; then
202+
printf '%s' "$PROMPT" | run_with_timeout "$SELECTOR_TIMEOUT" claude --print --model "$model" -
203+
return $?
175204
fi
176-
CODEX_EXEC_ARGS+=("$CODEX_AUTO_FLAG" "-C" "$CODEX_PROJECT_ROOT")
177205

178-
RAW_OUTPUT="$(printf '%s' "$PROMPT" | run_with_timeout "$SELECTOR_TIMEOUT" codex exec "${CODEX_EXEC_ARGS[@]}" -)" || CODEX_EXIT_CODE=$?
179-
elif [[ "$BITLESSON_PROVIDER" == "claude" ]]; then
180-
RAW_OUTPUT="$(printf '%s' "$PROMPT" | run_with_timeout "$SELECTOR_TIMEOUT" claude --print --model "$BITLESSON_MODEL" -)" || CODEX_EXIT_CODE=$?
181-
fi
206+
echo "Error: Unsupported BitLesson provider '$provider'" >&2
207+
return 1
208+
}
209+
210+
CODEX_EXIT_CODE=0
211+
RAW_OUTPUT="$(run_selector "$BITLESSON_PROVIDER" "$BITLESSON_MODEL" 2>&1)" || CODEX_EXIT_CODE=$?
182212

183213
if [[ $CODEX_EXIT_CODE -eq 124 ]]; then
184214
echo "Error: BitLesson selector timed out after ${SELECTOR_TIMEOUT} seconds" >&2
@@ -187,6 +217,7 @@ fi
187217

188218
if [[ $CODEX_EXIT_CODE -ne 0 ]]; then
189219
echo "Error: BitLesson selector failed (exit code $CODEX_EXIT_CODE)" >&2
220+
printf '%s\n' "$RAW_OUTPUT" >&2
190221
exit "$CODEX_EXIT_CODE"
191222
fi
192223

0 commit comments

Comments
 (0)