Skip to content

Commit 48bce3f

Browse files
author
NagyVikt
committed
Auto-finish: gx doctor repairs
1 parent 362e731 commit 48bce3f

13 files changed

Lines changed: 933 additions & 1779 deletions

File tree

.githooks/post-merge

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,43 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
# Auto-sync agent worktrees when the base branch is updated in this worktree.
5-
if [[ -x "scripts/agent-sync-on-base-update.sh" ]]; then
6-
bash scripts/agent-sync-on-base-update.sh --quiet || true
4+
if [[ "${GUARDEX_DISABLE_POST_MERGE_CLEANUP:-0}" == "1" ]]; then
5+
exit 0
76
fi
7+
8+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
9+
if [[ -z "$repo_root" ]]; then
10+
exit 0
11+
fi
12+
13+
branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
14+
if [[ -z "$branch" || "$branch" == "HEAD" ]]; then
15+
exit 0
16+
fi
17+
18+
base_branch="${GUARDEX_BASE_BRANCH:-$(git -C "$repo_root" config --get multiagent.baseBranch || true)}"
19+
if [[ -z "$base_branch" ]]; then
20+
base_branch="dev"
21+
fi
22+
23+
if [[ "$branch" != "$base_branch" ]]; then
24+
exit 0
25+
fi
26+
27+
cli_path="$repo_root/bin/multiagent-safety.js"
28+
if [[ ! -f "$cli_path" ]]; then
29+
exit 0
30+
fi
31+
32+
node_bin="${GUARDEX_NODE_BIN:-node}"
33+
if ! command -v "$node_bin" >/dev/null 2>&1; then
34+
exit 0
35+
fi
36+
37+
"$node_bin" "$cli_path" cleanup \
38+
--target "$repo_root" \
39+
--base "$base_branch" \
40+
--include-pr-merged \
41+
--keep-clean-worktrees >/dev/null 2>&1 || true
42+
43+
exit 0

.githooks/pre-commit

Lines changed: 22 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@ if [[ -z "$branch" ]]; then
99
exit 0
1010
fi
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-
1812
if [[ "${ALLOW_COMMIT_ON_PROTECTED_BRANCH:-0}" == "1" ]]; then
1913
exit 0
2014
fi
@@ -38,7 +32,7 @@ if [[ -n "${CLAUDECODE:-}" || -n "${CLAUDE_CODE_SESSION_ID:-}" ]]; then
3832
fi
3933

4034
is_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
4337
fi
4438

@@ -82,163 +76,6 @@ case "$codex_require_agent_branch" in
8276
*) should_require_codex_agent_branch=1 ;;
8377
esac
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-
24279
is_codex_managed_only_commit_on_protected=0
24380
if [[ "$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
295132
fi
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-
312134
if [[ "$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>"
334154
After 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
342170
Temporary bypass (not recommended):
343171
ALLOW_COMMIT_ON_PROTECTED_BRANCH=1 git commit ...
@@ -346,11 +174,12 @@ MSG
346174
fi
347175

348176
if [[ "$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

.github/pull.yml.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: "1"
2+
rules:
3+
- base: main
4+
upstream: upstream-owner:main
5+
mergeMethod: hardreset
6+
mergeUnstable: true

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ openspec/plan/*
7777

7878
# multiagent-safety:START
7979
.omx/
80+
.omc/
8081
scripts/agent-branch-start.sh
8182
scripts/agent-branch-finish.sh
8283
scripts/codex-agent.sh
@@ -88,6 +89,8 @@ scripts/openspec/init-plan-workspace.sh
8889
scripts/openspec/init-change-workspace.sh
8990
.githooks/pre-commit
9091
.githooks/pre-push
92+
.githooks/post-merge
93+
.githooks/post-checkout
9194
oh-my-codex/
9295
.codex/skills/guardex/SKILL.md
9396
.codex/skills/guardex-merge-skills-to-dev/SKILL.md
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-04-20
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## Why
2+
3+
- TODO: describe the user/problem outcome this change addresses.
4+
5+
## What Changes
6+
7+
- TODO: summarize the intended behavior and scope.
8+
9+
## Impact
10+
11+
- TODO: call out risks, rollout notes, and affected surfaces.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## ADDED Requirements
2+
3+
### Requirement: gx-doctor behavior
4+
The system SHALL enforce gx-doctor behavior as defined by this change.
5+
6+
#### Scenario: Baseline acceptance
7+
- **WHEN** gx-doctor behavior is exercised
8+
- **THEN** the expected outcome is produced
9+
- **AND** regressions are covered by tests.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## 1. Specification
2+
3+
- [ ] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-gx-doctor-2026-04-20-12-13`.
4+
- [ ] 1.2 Define normative requirements in `specs/gx-doctor/spec.md`.
5+
6+
## 2. Implementation
7+
8+
- [ ] 2.1 Implement scoped behavior changes.
9+
- [ ] 2.2 Add/update focused regression coverage.
10+
11+
## 3. Verification
12+
13+
- [ ] 3.1 Run targeted project verification commands.
14+
- [ ] 3.2 Run `openspec validate agent-codex-gx-doctor-2026-04-20-12-13 --type change --strict`.
15+
- [ ] 3.3 Run `openspec validate --specs`.

0 commit comments

Comments
 (0)