Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ temp

# Humanize state directories (runtime-generated, project-local)
.humanize/
.claude-flow/
.swarm/

# Python cache
__pycache__/
Expand Down
23 changes: 23 additions & 0 deletions config/codex-hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"description": "Humanize Codex Hooks - Native Stop hooks for RLCR and PR loops",
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "{{HUMANIZE_RUNTIME_ROOT}}/hooks/loop-codex-stop-hook.sh",
"timeout": 7200,
"statusMessage": "humanize RLCR stop hook"
},
{
"type": "command",
"command": "{{HUMANIZE_RUNTIME_ROOT}}/hooks/pr-loop-stop-hook.sh",
"timeout": 7200,
"statusMessage": "humanize PR stop hook"
}
]
}
]
}
}
4 changes: 4 additions & 0 deletions docs/bitlesson.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Provider routing is automatic:

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

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

## Workflow

Each project keeps its BitLesson knowledge base at `.humanize/bitlesson.md`.
Expand Down
33 changes: 32 additions & 1 deletion docs/install-for-codex.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Install Humanize Skills for Codex

This guide explains how to install the Humanize skills for Codex skill runtime (`$CODEX_HOME/skills`).
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`).

## Quick Install (Recommended)

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

Requires Codex CLI `0.114.0` or newer for native hooks. Older Codex builds are not supported by the Codex install path.

## Verify

```bash
Expand Down Expand Up @@ -58,6 +64,21 @@ Installed files/directories:
- `${CODEX_HOME:-~/.codex}/skills/humanize/templates/`
- `${CODEX_HOME:-~/.codex}/skills/humanize/config/`
- `${CODEX_HOME:-~/.codex}/skills/humanize/agents/`
- `${CODEX_HOME:-~/.codex}/hooks.json`
- `${XDG_CONFIG_HOME:-~/.config}/humanize/config.json` (created or updated only when Humanize config keys are unset)

Verify native hooks:

```bash
codex features list | rg codex_hooks
sed -n '1,220p' "${CODEX_HOME:-$HOME/.codex}/hooks.json"
```

Expected:
- `codex_hooks` is `true`
- `hooks.json` contains `loop-codex-stop-hook.sh` and `pr-loop-stop-hook.sh`
- `${XDG_CONFIG_HOME:-~/.config}/humanize/config.json` contains `bitlesson_model` set to a Codex/OpenAI model such as `gpt-5.4`
- for `--target codex`, `${XDG_CONFIG_HOME:-~/.config}/humanize/config.json` also contains `provider_mode: "codex-only"`

## Optional: Install for Both Codex and Kimi

Expand All @@ -73,6 +94,9 @@ Installed files/directories:

# Custom Codex skills dir
./scripts/install-skills-codex.sh --codex-skills-dir /custom/codex/skills

# Reinstall only the native hooks/config
./scripts/install-codex-hooks.sh
```

## Troubleshooting
Expand All @@ -82,3 +106,10 @@ If scripts are not found from installed skills:
```bash
ls -la "${CODEX_HOME:-$HOME/.codex}/skills/humanize/scripts"
```

If native exit gating does not trigger:

```bash
codex features enable codex_hooks
sed -n '1,220p' "${CODEX_HOME:-$HOME/.codex}/hooks.json"
```
5 changes: 5 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ Current built-in keys:
| `codex_model` | `gpt-5.4` | Shared default model for Codex-backed review and analysis |
| `codex_effort` | `high` | Shared default reasoning effort (`xhigh`, `high`, `medium`, `low`) |
| `bitlesson_model` | `haiku` | Model used by the BitLesson selector agent |
| `provider_mode` | unset | Optional runtime mode hint such as `codex-only` |
| `agent_teams` | `false` | Project-level default for agent teams workflow |
| `alternative_plan_language` | `""` | Optional translated plan variant language; supported values include `Chinese`, `Korean`, `Japanese`, `Spanish`, `French`, `German`, `Portuguese`, `Russian`, `Arabic`, or ISO codes like `zh` |
| `gen_plan_mode` | `discussion` | Default plan-generation mode |
Expand All @@ -300,6 +301,10 @@ To override, add to `.humanize/config.json`:
}
```

On Codex installs, Humanize also seeds `${XDG_CONFIG_HOME:-~/.config}/humanize/config.json`
with a Codex/OpenAI `bitlesson_model` and `provider_mode: "codex-only"` when those keys
are unset, so BitLesson selection stays on the Codex/OpenAI path without probing Claude.

Codex model is resolved with this precedence:
1. CLI `--codex-model` flag (highest priority)
2. Feature-specific defaults (e.g., PR loop defaults to `medium` effort)
Expand Down
11 changes: 7 additions & 4 deletions hooks/loop-codex-stop-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,9 @@ mkdir -p "$CACHE_DIR"

# portable-timeout.sh already sourced above

# Disable native hooks for nested Codex reviewer calls to prevent Stop-hook recursion.
CODEX_DISABLE_HOOKS_ARGS=(--disable codex_hooks)

# Build command arguments for summary review (codex exec)
CODEX_EXEC_ARGS=("-m" "$CODEX_EXEC_MODEL")
if [[ -n "$CODEX_EXEC_EFFORT" ]]; then
Expand Down Expand Up @@ -1056,14 +1059,14 @@ Provider: codex
echo "# Review base ($review_base_type): $review_base"
echo "# Timeout: $CODEX_TIMEOUT seconds"
echo ""
echo "codex review --base $review_base ${CODEX_REVIEW_ARGS[*]}"
echo "codex ${CODEX_DISABLE_HOOKS_ARGS[*]} review --base $review_base ${CODEX_REVIEW_ARGS[*]}"
} > "$CODEX_REVIEW_CMD_FILE"

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

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

echo "Code review exit code: $CODEX_REVIEW_EXIT_CODE" >&2
Expand Down Expand Up @@ -1387,7 +1390,7 @@ CODEX_PROMPT_CONTENT=$(cat "$REVIEW_PROMPT_FILE")
echo "# Working directory: $PROJECT_ROOT"
echo "# Timeout: $CODEX_TIMEOUT seconds"
echo ""
echo "codex exec ${CODEX_EXEC_ARGS[*]} \"<prompt>\""
echo "codex ${CODEX_DISABLE_HOOKS_ARGS[*]} exec ${CODEX_EXEC_ARGS[*]} \"<prompt>\""
echo ""
echo "# Prompt content:"
echo "$CODEX_PROMPT_CONTENT"
Expand All @@ -1397,7 +1400,7 @@ echo "Codex command saved to: $CODEX_CMD_FILE" >&2
echo "Running summary review with timeout ${CODEX_TIMEOUT}s..." >&2

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

echo "Codex exit code: $CODEX_EXIT_CODE" >&2
Expand Down
5 changes: 4 additions & 1 deletion hooks/pr-loop-stop-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1334,12 +1334,15 @@ if [[ "${HUMANIZE_CODEX_BYPASS_SANDBOX:-}" == "true" ]] || [[ "${HUMANIZE_CODEX_
CODEX_AUTO_FLAG="--dangerously-bypass-approvals-and-sandbox"
fi

# Disable native hooks for nested Codex reviewer calls to prevent Stop-hook recursion.
CODEX_DISABLE_HOOKS_ARGS=(--disable codex_hooks)

CODEX_ARGS+=("$CODEX_AUTO_FLAG" "-C" "$PROJECT_ROOT")

CODEX_PROMPT_CONTENT=$(cat "$CODEX_PROMPT_FILE")
CODEX_EXIT_CODE=0

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

if [[ $CODEX_EXIT_CODE -ne 0 ]]; then
Expand Down
77 changes: 54 additions & 23 deletions scripts/bitlesson-select.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null
MERGED_CONFIG="$(load_merged_config "$PLUGIN_ROOT" "$PROJECT_ROOT")"
BITLESSON_MODEL="$(get_config_value "$MERGED_CONFIG" "bitlesson_model")"
BITLESSON_MODEL="${BITLESSON_MODEL:-haiku}"
CODEX_FALLBACK_MODEL="$(get_config_value "$MERGED_CONFIG" "codex_model")"
CODEX_FALLBACK_MODEL="${CODEX_FALLBACK_MODEL:-$DEFAULT_CODEX_MODEL}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Initialize Codex fallback model before using nounset fallback

With set -u, this line dereferences DEFAULT_CODEX_MODEL before loop-common.sh is sourced, so a valid-but-empty codex_model in merged config ("codex_model": "") causes bitlesson-select.sh to abort with unbound variable before any routing logic runs. This blocks BitLesson selection (and therefore RLCR progress) for users with that config shape instead of falling back to the default model.

Useful? React with 👍 / 👎.

PROVIDER_MODE="$(get_config_value "$MERGED_CONFIG" "provider_mode")"
PROVIDER_MODE="${PROVIDER_MODE:-auto}"

# Source portable timeout wrapper
source "$SCRIPT_DIR/portable-timeout.sh"
Expand Down Expand Up @@ -82,12 +86,34 @@ if [[ -z "$BITLESSON_FILE" ]]; then
exit 1
fi

if [[ ! -f "$BITLESSON_FILE" ]]; then
echo "Error: BitLesson file not found: $BITLESSON_FILE" >&2
exit 1
fi

BITLESSON_CONTENT="$(cat "$BITLESSON_FILE")"
if [[ -z "$(printf '%s' "$BITLESSON_CONTENT" | tr -d ' \t\n\r')" ]]; then
echo "Error: BitLesson file is empty (whitespace only): $BITLESSON_FILE" >&2
exit 1
fi

if ! printf '%s\n' "$BITLESSON_CONTENT" | grep -Eq '^[[:space:]]*##[[:space:]]+Lesson:'; then
printf 'LESSON_IDS: NONE\n'
printf 'RATIONALE: The BitLesson file has no recorded lessons yet.\n'
exit 0
fi

# ========================================
# Determine Provider from BITLESSON_MODEL
# ========================================

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

if [[ "$PROVIDER_MODE" == "codex-only" ]] && [[ "$BITLESSON_PROVIDER" == "claude" ]]; then
BITLESSON_MODEL="$CODEX_FALLBACK_MODEL"
BITLESSON_PROVIDER="codex"
fi

# ========================================
# Conditional Dependency Check (with fallback)
# ========================================
Expand All @@ -99,17 +125,6 @@ if ! check_provider_dependency "$BITLESSON_PROVIDER" 2>/dev/null; then
check_provider_dependency "$BITLESSON_PROVIDER"
fi

if [[ ! -f "$BITLESSON_FILE" ]]; then
echo "Error: BitLesson file not found: $BITLESSON_FILE" >&2
exit 1
fi

BITLESSON_CONTENT="$(cat "$BITLESSON_FILE")"
if [[ -z "$(printf '%s' "$BITLESSON_CONTENT" | tr -d ' \t\n\r')" ]]; then
echo "Error: BitLesson file is empty (whitespace only): $BITLESSON_FILE" >&2
exit 1
fi

# ========================================
# Detect Project Root (for -C)
# ========================================
Expand Down Expand Up @@ -148,6 +163,7 @@ $BITLESSON_CONTENT
1. Match only lessons that are directly relevant to the sub-task scope and failure mode.
2. Prefer precision over recall: do not include weakly related lessons.
3. If nothing is relevant, return \`NONE\`.
4. Use only the information in this prompt. Do not use tools, shell commands, browser access, MCP servers, or repository inspection.

## Output Format (Stable)

Expand All @@ -164,21 +180,35 @@ EOF

SELECTOR_TIMEOUT=120

CODEX_EXIT_CODE=0
if [[ "$BITLESSON_PROVIDER" == "codex" ]]; then
CODEX_EXEC_ARGS=("-m" "$BITLESSON_MODEL" "-c" "model_reasoning_effort=high")
run_selector() {
local provider="$1"
local model="$2"

if [[ "$provider" == "codex" ]]; then
local codex_exec_args=(
"--disable" "codex_hooks"
"--skip-git-repo-check"
"--ephemeral"
"-s" "read-only"
"-m" "$model"
"-c" "model_reasoning_effort=low"
"-C" "$CODEX_PROJECT_ROOT"
)
printf '%s' "$PROMPT" | run_with_timeout "$SELECTOR_TIMEOUT" codex exec "${codex_exec_args[@]}" -
return $?
fi

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

RAW_OUTPUT="$(printf '%s' "$PROMPT" | run_with_timeout "$SELECTOR_TIMEOUT" codex exec "${CODEX_EXEC_ARGS[@]}" -)" || CODEX_EXIT_CODE=$?
elif [[ "$BITLESSON_PROVIDER" == "claude" ]]; then
RAW_OUTPUT="$(printf '%s' "$PROMPT" | run_with_timeout "$SELECTOR_TIMEOUT" claude --print --model "$BITLESSON_MODEL" -)" || CODEX_EXIT_CODE=$?
fi
echo "Error: Unsupported BitLesson provider '$provider'" >&2
return 1
}

CODEX_EXIT_CODE=0
RAW_OUTPUT="$(run_selector "$BITLESSON_PROVIDER" "$BITLESSON_MODEL" 2>&1)" || CODEX_EXIT_CODE=$?

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

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

Expand Down
Loading
Loading