Skip to content

Commit 2455684

Browse files
author
zenus
committed
fix: enforce strict codex-worker delegation
1 parent 4b770d3 commit 2455684

11 files changed

Lines changed: 264 additions & 10 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"name": "humanize",
99
"source": "./",
1010
"description": "Humanize - An iterative development plugin that uses a Codex CLI worker (gpt-5.4) for implementation and a separate Codex CLI analyzer/reviewer (gpt-5.4, non-codex) for independent review (cross-vendor style).",
11-
"version": "1.11.3"
11+
"version": "1.11.4"
1212
}
1313
]
1414
}

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "humanize",
33
"description": "Humanize - An iterative development plugin that uses a Codex CLI worker (gpt-5.4) for implementation and a separate Codex CLI analyzer/reviewer (gpt-5.4, non-codex) for independent review (cross-vendor style).",
4-
"version": "1.11.3",
4+
"version": "1.11.4",
55
"author": {
66
"name": "humania-org"
77
},

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Humanize
22

3-
**Current Version: 1.11.3**
3+
**Current Version: 1.11.4**
44

55
> Derived from the [GAAC (GitHub-as-a-Context)](https://github.com/SihaoLiu/gaac) project.
66

hooks/lib/loop-common.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,58 @@ is_in_any_loop_dir() {
941941
is_in_humanize_loop_dir "$path" || is_in_pr_loop_dir "$path"
942942
}
943943

944+
# Returns 0 when the active RLCR loop is in strict delegation mode for Claude.
945+
# This applies only to implementation rounds in agent-teams mode.
946+
strict_delegation_mode_active() {
947+
[[ "${STATE_AGENT_TEAMS:-false}" == "true" ]] || return 1
948+
[[ "${STATE_DELEGATION_ENFORCEMENT:-warn}" == "strict" ]] || return 1
949+
[[ "${STATE_REVIEW_STARTED:-}" == "false" ]] || return 1
950+
return 0
951+
}
952+
953+
# Allow Claude to keep coordination artifacts up to date even in strict mode.
954+
# Source-code changes must go through /humanize:codex-worker.
955+
is_delegation_coordinator_path() {
956+
local path="$1"
957+
local path_lower
958+
local basename_lower
959+
960+
path_lower=$(to_lower "$path")
961+
basename_lower=$(basename "$path_lower")
962+
963+
if echo "$path_lower" | grep -q '\.humanize/'; then
964+
return 0
965+
fi
966+
967+
[[ "$basename_lower" == "bitlesson.md" ]]
968+
}
969+
970+
# Standard message for blocking direct Claude implementation in strict mode.
971+
# Usage: strict_delegation_blocked_message "edit" "`path`" "$current_round"
972+
strict_delegation_blocked_message() {
973+
local action="$1"
974+
local target="$2"
975+
local current_round="$3"
976+
local fallback="# Strict Delegation Required
977+
978+
This RLCR loop is running in strict delegation mode for round {{CURRENT_ROUND}}.
979+
980+
Claude must not {{ACTION}} {{TARGET}} directly. In strict mode, source-code changes
981+
must go through \`/humanize:codex-worker\`.
982+
983+
Allowed coordinator actions:
984+
- update \`.humanize/\` coordination files
985+
- update \`bitlesson.md\` when required by the workflow
986+
- inspect files, diffs, and run non-mutating commands
987+
988+
Next step: invoke \`/humanize:codex-worker\` for the coding task, then continue coordinating from Claude."
989+
990+
load_and_render_safe "$TEMPLATE_DIR" "block/strict-delegation-required.md" "$fallback" \
991+
"ACTION=$action" \
992+
"TARGET=$target" \
993+
"CURRENT_ROUND=$current_round"
994+
}
995+
944996
# Find the most recent active PR loop directory with state.md
945997
# Similar to find_active_loop but for PR loops
946998
# Outputs the directory path to stdout, or empty string if none found

hooks/loop-bash-validator.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,38 @@ set -euo pipefail
1717
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
1818
source "$SCRIPT_DIR/lib/loop-common.sh"
1919

20+
bash_command_looks_mutating() {
21+
local command_lower="$1"
22+
local patterns=(
23+
'(^|[[:space:]])(mkdir|touch|install|ln|chmod|chown|chgrp|rm|mv|cp|truncate|patch)([[:space:]]|$)'
24+
'(^|[[:space:]])git[[:space:]]+([^[:space:]]+[[:space:]]+)*(add|commit|apply|am|cherry-pick|merge|rebase|restore)([[:space:]]|$)'
25+
'(^|[[:space:]])sed[[:space:]]+-i([[:space:]]|$)'
26+
'(^|[[:space:]])awk[[:space:]]+-i[[:space:]]+inplace([[:space:]]|$)'
27+
'(^|[[:space:]])perl[[:space:]]+-[^[:space:]]*i'
28+
'(^|[[:space:]])tee([[:space:]]|$)'
29+
'(^|[[:space:]])dd[[:space:]].*of='
30+
)
31+
32+
for pattern in "${patterns[@]}"; do
33+
if echo "$command_lower" | grep -qE "$pattern"; then
34+
return 0
35+
fi
36+
done
37+
38+
if echo "$command_lower" | grep -qE '(^|[;&|])[[:space:]]*(cat|echo|printf)[^|;]*>>?[[:space:]]*[^[:space:]]+'; then
39+
if ! echo "$command_lower" | grep -qE '>>?[[:space:]]*/dev/null([[:space:];|&]|$)'; then
40+
return 0
41+
fi
42+
fi
43+
44+
return 1
45+
}
46+
47+
bash_command_targets_coordination_artifact() {
48+
local command_lower="$1"
49+
echo "$command_lower" | grep -qE '(^|[^[:alnum:]_])bitlesson\.md($|[^[:alnum:]_])|\.humanize/'
50+
}
51+
2052
# ========================================
2153
# Parse Hook Input
2254
# ========================================
@@ -85,6 +117,11 @@ if [[ -n "$ACTIVE_LOOP_DIR" ]]; then
85117
fi
86118
CURRENT_ROUND="$STATE_CURRENT_ROUND"
87119

120+
if strict_delegation_mode_active && bash_command_looks_mutating "$COMMAND_LOWER" && ! bash_command_targets_coordination_artifact "$COMMAND_LOWER"; then
121+
strict_delegation_blocked_message "run code-modifying Bash commands against" "the repository" "$CURRENT_ROUND" >&2
122+
exit 2
123+
fi
124+
88125
# ========================================
89126
# Block Git Push When push_every_round is false
90127
# ========================================

hooks/loop-edit-validator.sh

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,23 @@ FILE_PATH_LOWER=$(to_lower "$FILE_PATH")
3434
# Extract session_id from hook input for session-aware loop filtering
3535
HOOK_SESSION_ID=$(extract_session_id "$HOOK_INPUT")
3636

37+
# Precompute active RLCR state for delegation enforcement and round validation.
38+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
39+
LOOP_BASE_DIR="$PROJECT_ROOT/.humanize/rlcr"
40+
ACTIVE_LOOP_DIR=$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID")
41+
CURRENT_ROUND=""
42+
STRICT_DELEGATION_ACTIVE="false"
43+
44+
if [[ -n "$ACTIVE_LOOP_DIR" ]]; then
45+
STATE_FILE_TO_PARSE=$(resolve_active_state_file "$ACTIVE_LOOP_DIR")
46+
if [[ -n "$STATE_FILE_TO_PARSE" ]] && parse_state_file "$STATE_FILE_TO_PARSE" 2>/dev/null; then
47+
CURRENT_ROUND="${STATE_CURRENT_ROUND:-0}"
48+
if strict_delegation_mode_active; then
49+
STRICT_DELEGATION_ACTIVE="true"
50+
fi
51+
fi
52+
fi
53+
3754
# ========================================
3855
# Block Todos and Prompt Files
3956
# ========================================
@@ -84,6 +101,10 @@ fi
84101
# ========================================
85102

86103
if ! is_in_humanize_loop_dir "$FILE_PATH"; then
104+
if [[ "$STRICT_DELEGATION_ACTIVE" == "true" ]] && ! is_delegation_coordinator_path "$FILE_PATH"; then
105+
strict_delegation_blocked_message "edit" "\`$FILE_PATH\`" "${CURRENT_ROUND:-0}" >&2
106+
exit 2
107+
fi
87108
exit 0
88109
fi
89110

@@ -93,7 +114,7 @@ fi
93114

94115
PROJECT_ROOT="${PROJECT_ROOT:-${CLAUDE_PROJECT_DIR:-$(pwd)}}"
95116
LOOP_BASE_DIR="${LOOP_BASE_DIR:-$PROJECT_ROOT/.humanize/rlcr}"
96-
ACTIVE_LOOP_DIR="${LOOP_DIR:-$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID")}"
117+
ACTIVE_LOOP_DIR="${ACTIVE_LOOP_DIR:-$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID")}"
97118

98119
if [[ -z "$ACTIVE_LOOP_DIR" ]]; then
99120
exit 0

hooks/loop-write-validator.sh

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,23 @@ FILE_PATH_LOWER=$(to_lower "$FILE_PATH")
5151
# Extract session_id from hook input for session-aware loop filtering
5252
HOOK_SESSION_ID=$(extract_session_id "$HOOK_INPUT")
5353

54+
# Precompute active RLCR state for delegation enforcement and round validation.
55+
PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
56+
LOOP_BASE_DIR="$PROJECT_ROOT/.humanize/rlcr"
57+
ACTIVE_LOOP_DIR=$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID")
58+
CURRENT_ROUND=""
59+
STRICT_DELEGATION_ACTIVE="false"
60+
61+
if [[ -n "$ACTIVE_LOOP_DIR" ]]; then
62+
STATE_FILE_TO_PARSE=$(resolve_active_state_file "$ACTIVE_LOOP_DIR")
63+
if [[ -n "$STATE_FILE_TO_PARSE" ]] && parse_state_file "$STATE_FILE_TO_PARSE" 2>/dev/null; then
64+
CURRENT_ROUND="${STATE_CURRENT_ROUND:-0}"
65+
if strict_delegation_mode_active; then
66+
STRICT_DELEGATION_ACTIVE="true"
67+
fi
68+
fi
69+
fi
70+
5471
# ========================================
5572
# Block Todos and Prompt Files
5673
# ========================================
@@ -106,6 +123,10 @@ IN_HUMANIZE_LOOP_DIR=$(is_in_humanize_loop_dir "$FILE_PATH" && echo "true" || ec
106123

107124
# If not a summary file, not a finalize summary, and not in .humanize/rlcr, allow normally
108125
if [[ "$IS_SUMMARY_FILE" == "false" ]] && [[ "$IS_FINALIZE_SUMMARY" == "false" ]] && [[ "$IN_HUMANIZE_LOOP_DIR" == "false" ]]; then
126+
if [[ "$STRICT_DELEGATION_ACTIVE" == "true" ]] && ! is_delegation_coordinator_path "$FILE_PATH"; then
127+
strict_delegation_blocked_message "write to" "\`$FILE_PATH\`" "${CURRENT_ROUND:-0}" >&2
128+
exit 2
129+
fi
109130
exit 0
110131
fi
111132

@@ -126,7 +147,7 @@ fi
126147
# Re-initialize if not set by earlier todos check
127148
PROJECT_ROOT="${PROJECT_ROOT:-${CLAUDE_PROJECT_DIR:-$(pwd)}}"
128149
LOOP_BASE_DIR="${LOOP_BASE_DIR:-$PROJECT_ROOT/.humanize/rlcr}"
129-
ACTIVE_LOOP_DIR="${LOOP_DIR:-$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID")}"
150+
ACTIVE_LOOP_DIR="${ACTIVE_LOOP_DIR:-$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID")}"
130151

131152
if [[ -z "$ACTIVE_LOOP_DIR" ]]; then
132153
exit 0
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Strict Delegation Required
2+
3+
This RLCR loop is running in strict delegation mode for round {{CURRENT_ROUND}}.
4+
5+
Claude must not {{ACTION}} {{TARGET}} directly. In strict mode, source-code changes
6+
must go through `/humanize:codex-worker`.
7+
8+
Allowed coordinator actions:
9+
- update `.humanize/` coordination files
10+
- update `bitlesson.md` when required by the workflow
11+
- inspect files, diffs, and run non-mutating commands
12+
13+
Next step: invoke `/humanize:codex-worker` for the coding task, then continue coordinating from Claude.

scripts/setup-rlcr-loop.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ AGENT_TEAMS_EXPLICIT="false"
5656
WORKTREE_TEAMS_EXPLICIT="false"
5757
WORKTREE_ROOT=""
5858
BITLESSON_ALLOW_EMPTY_NONE="true"
59-
DELEGATION_ENFORCEMENT="${HUMANIZE_CODEX_DELEGATION_ENFORCEMENT:-warn}"
59+
DELEGATION_ENFORCEMENT="${HUMANIZE_CODEX_DELEGATION_ENFORCEMENT:-strict}"
6060

6161
show_help() {
6262
cat <<HELP_EOF
@@ -111,7 +111,7 @@ OPTIONS:
111111
one concrete lesson entry in `bitlesson.md`
112112
HUMANIZE_CODEX_DELEGATION_ENFORCEMENT
113113
Delegation enforcement level for agent-team prompting.
114-
Allowed values: warn, strict (default: warn)
114+
Allowed values: warn, strict (default: strict)
115115
-h, --help Show this help message
116116
117117
DESCRIPTION:

tests/robustness/test-hook-system-robustness.sh

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,38 @@ else
154154
fail "Path traversal to state.md" "exit 2 (blocked)" "exit $EXIT_CODE, result: $RESULT"
155155
fi
156156

157+
# Test 5c: Strict delegation blocks direct source edits outside .humanize
158+
echo ""
159+
echo "Test 5c: Strict delegation blocks direct source edits"
160+
mkdir -p "$TEST_DIR/strict-edit/.humanize/rlcr/2026-01-19_13-00-00"
161+
cat > "$TEST_DIR/strict-edit/.humanize/rlcr/2026-01-19_13-00-00/state.md" << 'EOF'
162+
---
163+
current_round: 2
164+
max_iterations: 42
165+
plan_file: plan.md
166+
start_branch: main
167+
base_branch: main
168+
push_every_round: false
169+
codex_model: o3-mini
170+
codex_effort: medium
171+
codex_timeout: 1200
172+
review_started: false
173+
plan_tracked: false
174+
agent_teams: true
175+
delegation_enforcement: strict
176+
---
177+
EOF
178+
JSON='{"tool_name":"Edit","tool_input":{"file_path":"'"$TEST_DIR"'/strict-edit/src/app.py","old_string":"old","new_string":"new"}}'
179+
set +e
180+
RESULT=$(echo "$JSON" | CLAUDE_PROJECT_DIR="$TEST_DIR/strict-edit" bash "$PROJECT_ROOT/hooks/loop-edit-validator.sh" 2>&1)
181+
EXIT_CODE=$?
182+
set -e
183+
if [[ $EXIT_CODE -eq 2 ]] && echo "$RESULT" | grep -qi "strict delegation required"; then
184+
pass "Strict delegation blocks direct source edits (exit 2)"
185+
else
186+
fail "Strict delegation source edit" "exit 2 with strict delegation message" "exit $EXIT_CODE, result: $RESULT"
187+
fi
188+
157189
# ========================================
158190
# Plan File Validator Tests
159191
# ========================================
@@ -381,6 +413,38 @@ else
381413
fail "Unrelated command" "allowed through" "exit $EXIT_CODE, result: $RESULT"
382414
fi
383415

416+
# Test 12d: Strict delegation blocks mutating Bash before direct implementation
417+
echo ""
418+
echo "Test 12d: Strict delegation blocks mutating Bash commands"
419+
mkdir -p "$TEST_DIR/strict-bash/.humanize/rlcr/2026-01-19_13-30-00"
420+
cat > "$TEST_DIR/strict-bash/.humanize/rlcr/2026-01-19_13-30-00/state.md" << 'EOF'
421+
---
422+
current_round: 2
423+
max_iterations: 42
424+
plan_file: plan.md
425+
start_branch: main
426+
base_branch: main
427+
push_every_round: false
428+
codex_model: o3-mini
429+
codex_effort: medium
430+
codex_timeout: 1200
431+
review_started: false
432+
plan_tracked: false
433+
agent_teams: true
434+
delegation_enforcement: strict
435+
---
436+
EOF
437+
JSON='{"tool_name":"Bash","tool_input":{"command":"touch src/new_file.py"}}'
438+
set +e
439+
RESULT=$(echo "$JSON" | CLAUDE_PROJECT_DIR="$TEST_DIR/strict-bash" bash "$PROJECT_ROOT/hooks/loop-bash-validator.sh" 2>&1)
440+
EXIT_CODE=$?
441+
set -e
442+
if [[ $EXIT_CODE -eq 2 ]] && echo "$RESULT" | grep -qi "strict delegation required"; then
443+
pass "Strict delegation blocks mutating Bash commands (exit 2)"
444+
else
445+
fail "Strict delegation Bash mutation" "exit 2 with strict delegation message" "exit $EXIT_CODE, result: $RESULT"
446+
fi
447+
384448
# Test 13: Edit validator handles newlines in strings
385449
echo ""
386450
echo "Test 13: Edit validator handles newlines in strings"
@@ -410,6 +474,52 @@ else
410474
fail "Binary content handling" "exit < 128" "exit $EXIT_CODE"
411475
fi
412476

477+
# Test 14b: Strict delegation blocks direct source writes outside .humanize
478+
echo ""
479+
echo "Test 14b: Strict delegation blocks direct source writes"
480+
mkdir -p "$TEST_DIR/strict-write/.humanize/rlcr/2026-01-19_14-00-00"
481+
cat > "$TEST_DIR/strict-write/.humanize/rlcr/2026-01-19_14-00-00/state.md" << 'EOF'
482+
---
483+
current_round: 2
484+
max_iterations: 42
485+
plan_file: plan.md
486+
start_branch: main
487+
base_branch: main
488+
push_every_round: false
489+
codex_model: o3-mini
490+
codex_effort: medium
491+
codex_timeout: 1200
492+
review_started: false
493+
plan_tracked: false
494+
agent_teams: true
495+
delegation_enforcement: strict
496+
---
497+
EOF
498+
JSON='{"tool_name":"Write","tool_input":{"file_path":"'"$TEST_DIR"'/strict-write/src/new_file.py","content":"print(1)"}}'
499+
set +e
500+
RESULT=$(echo "$JSON" | CLAUDE_PROJECT_DIR="$TEST_DIR/strict-write" bash "$PROJECT_ROOT/hooks/loop-write-validator.sh" 2>&1)
501+
EXIT_CODE=$?
502+
set -e
503+
if [[ $EXIT_CODE -eq 2 ]] && echo "$RESULT" | grep -qi "strict delegation required"; then
504+
pass "Strict delegation blocks direct source writes (exit 2)"
505+
else
506+
fail "Strict delegation source write" "exit 2 with strict delegation message" "exit $EXIT_CODE, result: $RESULT"
507+
fi
508+
509+
# Test 14c: Strict delegation still allows bitlesson.md updates
510+
echo ""
511+
echo "Test 14c: Strict delegation allows bitlesson.md writes"
512+
JSON='{"tool_name":"Write","tool_input":{"file_path":"'"$TEST_DIR"'/strict-write/bitlesson.md","content":"# BitLesson\n"}}'
513+
set +e
514+
RESULT=$(echo "$JSON" | CLAUDE_PROJECT_DIR="$TEST_DIR/strict-write" bash "$PROJECT_ROOT/hooks/loop-write-validator.sh" 2>&1)
515+
EXIT_CODE=$?
516+
set -e
517+
if [[ $EXIT_CODE -eq 0 ]] && ! echo "$RESULT" | grep -q '"decision".*:.*"block"'; then
518+
pass "Strict delegation allows bitlesson.md writes"
519+
else
520+
fail "Strict delegation bitlesson allow" "exit 0 without block" "exit $EXIT_CODE, result: $RESULT"
521+
fi
522+
413523
# ========================================
414524
# Concurrent Access Tests
415525
# ========================================

0 commit comments

Comments
 (0)