@@ -9,12 +9,6 @@ if [[ -z "$branch" ]]; then
99 exit 0
1010fi
1111
12- git_dir=" $( git rev-parse --git-dir 2> /dev/null || true) "
13- is_linked_worktree=0
14- if [[ -n " $git_dir " && " $git_dir " == * " /worktrees/" * ]]; then
15- is_linked_worktree=1
16- fi
17-
1812if [[ " ${ALLOW_COMMIT_ON_PROTECTED_BRANCH:- 0} " == " 1" ]]; then
1913 exit 0
2014fi
@@ -38,7 +32,7 @@ if [[ -n "${CLAUDECODE:-}" || -n "${CLAUDE_CODE_SESSION_ID:-}" ]]; then
3832fi
3933
4034is_vscode_git_context=0
41- if [[ -n " ${VSCODE_GIT_IPC_HANDLE:- } " || -n " ${VSCODE_GIT_ASKPASS_NODE:- } " || -n " ${VSCODE_IPC_HOOK_CLI:- } " || " ${TERM_PROGRAM :- } " == " vscode " ]]; then
35+ if [[ -n " ${VSCODE_GIT_IPC_HANDLE:- } " || -n " ${VSCODE_GIT_ASKPASS_NODE:- } " || -n " ${VSCODE_IPC_HOOK_CLI:- } " ]]; then
4236 is_vscode_git_context=1
4337fi
4438
@@ -82,163 +76,6 @@ case "$codex_require_agent_branch" in
8276 * ) should_require_codex_agent_branch=1 ;;
8377esac
8478
85- sanitize_slug () {
86- local raw=" $1 "
87- local fallback=" ${2:- task} "
88- local slug
89- slug=" $( printf ' %s' " $raw " | tr ' [:upper:]' ' [:lower:]' | sed -E ' s/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g' ) "
90- if [[ -z " $slug " ]]; then
91- slug=" $fallback "
92- fi
93- printf ' %s' " $slug "
94- }
95-
96- resolve_agent_branch_base () {
97- local branch_name=" $1 "
98- git config --get " branch.${branch_name} .guardexBase" || true
99- }
100-
101- is_helper_agent_branch () {
102- local branch_name=" $1 "
103- local base_branch=" "
104- base_branch=" $( resolve_agent_branch_base " $branch_name " ) "
105- [[ " $base_branch " == agent/* ]]
106- }
107-
108- ensure_agent_branch_openspec_workspace () {
109- local branch_name=" $1 "
110- local change_slug change_dir specs_dir capability_slug branch_base
111- local missing_workspace=0
112- local openspec_script=" scripts/openspec/init-change-workspace.sh"
113-
114- branch_base=" $( git config --get " branch.${branch_name} .guardexBase" || true) "
115- if [[ " $branch_base " == agent/* ]]; then
116- echo " [agent-openspec-guard] Skipping OpenSpec change workspace bootstrap for helper branch '${branch_name} ' (base '${branch_base} ')."
117- return 0
118- fi
119-
120- change_slug=" $( sanitize_slug " ${branch_name// \/ / -} " " change" ) "
121- change_dir=" openspec/changes/${change_slug} "
122- specs_dir=" ${change_dir} /specs"
123-
124- if [[ ! -f " ${change_dir} /.openspec.yaml" || ! -f " ${change_dir} /proposal.md" || ! -f " ${change_dir} /tasks.md" ]]; then
125- missing_workspace=1
126- elif [[ ! -d " $specs_dir " ]] || ! find " $specs_dir " -mindepth 2 -maxdepth 2 -type f -name spec.md | grep -q . ; then
127- missing_workspace=1
128- fi
129-
130- if [[ " $missing_workspace " -ne 1 ]]; then
131- return 0
132- fi
133-
134- if [[ ! -f " $openspec_script " ]]; then
135- cat >&2 << MSG
136- [agent-openspec-guard] Missing OpenSpec change workspace for '${branch_name} '.
137- Expected path:
138- ${change_dir}
139- Cannot auto-initialize because '${openspec_script} ' is missing.
140- Run:
141- gx setup --target "$( git rev-parse --show-toplevel) "
142- bash scripts/openspec/init-change-workspace.sh "${change_slug} " "<capability-slug>"
143- MSG
144- exit 1
145- fi
146-
147- if [[ ! -x " $openspec_script " ]]; then
148- chmod +x " $openspec_script " 2> /dev/null || true
149- fi
150-
151- capability_slug=" $( sanitize_slug " ${branch_name##*/ } " " general-behavior" ) "
152- init_output=" "
153- if ! init_output=" $( bash " $openspec_script " " $change_slug " " $capability_slug " 2>&1 ) " ; then
154- printf ' %s\n' " $init_output " >&2
155- cat >&2 << MSG
156- [agent-openspec-guard] OpenSpec auto-init failed for '${branch_name} '.
157- Run manually:
158- bash scripts/openspec/init-change-workspace.sh "${change_slug} " "${capability_slug} "
159- MSG
160- exit 1
161- fi
162-
163- if [[ -n " $init_output " ]]; then
164- printf ' %s\n' " $init_output "
165- fi
166-
167- git add " $change_dir "
168-
169- if [[ -x scripts/agent-file-locks.py ]]; then
170- staged_openspec=" $( git diff --cached --name-only -- " $change_dir " | sed ' /^$/d' || true) "
171- if [[ -n " $staged_openspec " ]]; then
172- mapfile -t openspec_files < <( printf ' %s\n' " $staged_openspec " )
173- python3 scripts/agent-file-locks.py claim --branch " $branch_name " " ${openspec_files[@]} " > /dev/null 2>&1 || true
174- fi
175- fi
176-
177- echo " [agent-openspec-guard] Bootstrapped OpenSpec change workspace: ${change_dir} "
178- }
179-
180- should_auto_reroute_protected_branch () {
181- local raw=" ${GUARDEX_AUTO_REROUTE_PROTECTED_BRANCH:- $(git config --get multiagent.autoRerouteProtectedBranch || true)} "
182- local lowered=" "
183- if [[ -z " $raw " ]]; then
184- raw=" true"
185- fi
186- lowered=" $( printf ' %s' " $raw " | tr ' [:upper:]' ' [:lower:]' ) "
187- case " $lowered " in
188- 1|true|yes|on) return 0 ;;
189- * ) return 1 ;;
190- esac
191- }
192-
193- auto_reroute_protected_branch_commit () {
194- local branch_name=" $1 "
195- local starter_script=" scripts/agent-branch-start.sh"
196- local task_name=" ${GUARDEX_AUTO_REROUTE_TASK_NAME:- protected-branch-commit-reroute} "
197- local agent_name=" ${GUARDEX_AUTO_REROUTE_AGENT_NAME:- auto-reroute} "
198- local changed_paths=" "
199- local start_output=" "
200- local start_status=0
201- local new_branch=" "
202- local worktree_path=" "
203-
204- changed_paths=" $( {
205- git diff --name-only
206- git diff --cached --name-only
207- git ls-files --others --exclude-standard
208- } | sed ' /^$/d' | sort -u) "
209-
210- if [[ -z " $changed_paths " ]]; then
211- return 1
212- fi
213-
214- if [[ ! -x " $starter_script " ]]; then
215- return 1
216- fi
217-
218- set +e
219- start_output=" $( bash " $starter_script " " $task_name " " $agent_name " " $branch_name " 2>&1 ) "
220- start_status=$?
221- set -e
222-
223- if [[ " $start_status " -ne 0 ]]; then
224- printf ' %s\n' " $start_output " >&2
225- return 1
226- fi
227-
228- new_branch=" $( printf ' %s\n' " $start_output " | sed -n ' s/^\[agent-branch-start\] Created branch: //p' | tail -n 1) "
229- worktree_path=" $( printf ' %s\n' " $start_output " | sed -n ' s/^\[agent-branch-start\] Worktree: //p' | tail -n 1) "
230-
231- printf ' %s\n' " $start_output " >&2
232- cat >&2 << MSG
233- [agent-branch-guard] Protected-branch commit rerouted automatically.
234- Changes from '${branch_name} ' were moved to:
235- branch: ${new_branch:- <see output>}
236- worktree: ${worktree_path:- <see output>}
237- Continue work and commit from that agent worktree.
238- MSG
239- return 0
240- }
241-
24279is_codex_managed_only_commit_on_protected=0
24380if [[ " $is_codex_session " == " 1" && " $is_protected_branch " == " 1" ]]; then
24481 deleted_paths=" $( git diff --cached --name-only --diff-filter=D) "
@@ -294,32 +131,15 @@ MSG
294131 fi
295132fi
296133
297- if [[ " $is_codex_session " == " 1" && " $branch " == agent/* ]]; then
298- if [[ " $is_linked_worktree " != " 1" && " ${GUARDEX_ALLOW_CODEX_ON_PRIMARY_WORKTREE:- 0} " != " 1" ]]; then
299- cat >&2 << 'MSG '
300- [codex-worktree-guard] Codex agent commits are blocked from the primary checkout.
301- Use a linked agent worktree for agent/* branches:
302- bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
303- Then commit from the printed worktree path.
304-
305- Temporary bypass (not recommended):
306- GUARDEX_ALLOW_CODEX_ON_PRIMARY_WORKTREE=1 git commit ...
307- MSG
308- exit 1
309- fi
310- fi
311-
312134if [[ " $is_protected_branch " == " 1" ]]; then
313135 # Humans may commit directly on protected branches; only agent sessions
314- # (Codex / Claude Code / OMX) are funneled through the block/reroute path .
136+ # (Codex / Claude Code / OMX) are blocked .
315137 if [[ " $is_agent_session " != " 1" ]]; then
316138 exit 0
317139 fi
318140
319- if should_auto_reroute_protected_branch; then
320- if auto_reroute_protected_branch_commit " $branch " ; then
321- exit 1
322- fi
141+ if [[ " $is_unborn_branch " == " 1" && " $is_codex_session " != " 1" ]]; then
142+ exit 0
323143 fi
324144
325145 git_dir=" $( git rev-parse --git-dir) "
@@ -333,11 +153,19 @@ Use an agent branch first:
333153 bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
334154After finishing work:
335155 bash scripts/agent-branch-finish.sh
336- Auto-reroute can be disabled (not recommended):
337- GUARDEX_AUTO_REROUTE_PROTECTED_BRANCH=0 git commit ...
338156
339- Optional repo override for manual VS Code protected-branch commits:
340- git config multiagent.allowVscodeProtectedBranchWrites true
157+ Temporary bypass (not recommended):
158+ ALLOW_COMMIT_ON_PROTECTED_BRANCH=1 git commit ...
159+ MSG
160+ exit 1
161+ fi
162+
163+ if [[ " $is_agent_session " == " 1" && " $branch " != agent/* ]]; then
164+ cat >&2 << 'MSG '
165+ [agent-branch-guard] Agent commits must run on dedicated agent/* branches.
166+ Start an agent branch first:
167+ bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
168+ Then commit on that branch.
341169
342170Temporary bypass (not recommended):
343171 ALLOW_COMMIT_ON_PROTECTED_BRANCH=1 git commit ...
@@ -346,11 +174,12 @@ MSG
346174fi
347175
348176if [[ " $branch " == agent/* ]]; then
349- if is_helper_agent_branch " $branch " ; then
350- helper_base=" $( resolve_agent_branch_base " $branch " ) "
351- echo " [agent-openspec-guard] Skipping OpenSpec change workspace bootstrap for helper branch '${branch} ' (base '${helper_base} ')."
352- else
353- ensure_agent_branch_openspec_workspace " $branch "
177+ if [[ " ${GUARDEX_AUTOCLAIM_STAGED_LOCKS:- 1} " == " 1" ]]; then
178+ while IFS= read -r staged_file; do
179+ [[ -z " $staged_file " ]] && continue
180+ [[ " $staged_file " == " .omx/state/agent-file-locks.json" ]] && continue
181+ python3 scripts/agent-file-locks.py claim --branch " $branch " " $staged_file " > /dev/null 2>&1 || true
182+ done < <( git diff --cached --name-only --diff-filter=ACMRDTUXB)
354183 fi
355184
356185 if ! python3 scripts/agent-file-locks.py validate --branch " $branch " --staged; then
0 commit comments