@imdeadpool/guardex 7.0.0 → 7.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,6 +54,7 @@ const TEMPLATE_FILES = [
54
54
  'githooks/pre-commit',
55
55
  'githooks/pre-push',
56
56
  'githooks/post-merge',
57
+ 'githooks/post-checkout',
57
58
  'codex/skills/guardex/SKILL.md',
58
59
  'codex/skills/guardex-merge-skills-to-dev/SKILL.md',
59
60
  'claude/commands/guardex.md',
@@ -97,6 +98,7 @@ const EXECUTABLE_RELATIVE_PATHS = new Set([
97
98
  '.githooks/pre-commit',
98
99
  '.githooks/pre-push',
99
100
  '.githooks/post-merge',
101
+ '.githooks/post-checkout',
100
102
  ]);
101
103
 
102
104
  const CRITICAL_GUARDRAIL_PATHS = new Set([
@@ -104,6 +106,7 @@ const CRITICAL_GUARDRAIL_PATHS = new Set([
104
106
  '.githooks/pre-commit',
105
107
  '.githooks/pre-push',
106
108
  '.githooks/post-merge',
109
+ '.githooks/post-checkout',
107
110
  'scripts/agent-branch-start.sh',
108
111
  'scripts/agent-branch-finish.sh',
109
112
  'scripts/agent-worktree-prune.sh',
@@ -131,6 +134,7 @@ const MANAGED_GITIGNORE_PATHS = [
131
134
  '.githooks/pre-commit',
132
135
  '.githooks/pre-push',
133
136
  '.githooks/post-merge',
137
+ '.githooks/post-checkout',
134
138
  'oh-my-codex/',
135
139
  '.codex/skills/guardex/SKILL.md',
136
140
  '.codex/skills/guardex-merge-skills-to-dev/SKILL.md',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "7.0.0",
3
+ "version": "7.0.2",
4
4
  "description": "GuardeX: the Guardian T-Rex for your repo, with hardened multi-agent git guardrails.",
5
5
  "license": "MIT",
6
6
  "preferGlobal": true,
@@ -1,7 +1,7 @@
1
1
  <!-- multiagent-safety:START -->
2
2
  ## Multi-Agent Safety Contract
3
3
 
4
- **Isolation.** Every task runs on a dedicated `agent/*` branch + worktree. Start with `scripts/agent-branch-start.sh "<task>" "<agent-name>"`. Treat the base branch (`main`/`dev`) as read-only while an agent branch is active.
4
+ **Isolation.** Every task runs on a dedicated `agent/*` branch + worktree. Start with `scripts/agent-branch-start.sh "<task>" "<agent-name>"`. Treat the base branch (`main`/`dev`) as read-only while an agent branch is active. Never `git checkout <branch>` on a primary working tree (including nested repos); use `git worktree add` instead. The `.githooks/post-checkout` hook auto-reverts primary-branch switches during agent sessions — bypass only with `GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1`.
5
5
 
6
6
  **Ownership.** Before editing, claim files: `scripts/agent-file-locks.py claim --branch "<agent-branch>" <file...>`. Before deleting, confirm the path is in your claim. Don't edit outside your scope unless reassigned.
7
7
 
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # post-checkout <prev_head> <new_head> <branch_checkout_flag>
5
+ branch_checkout="${3:-0}"
6
+ [[ "$branch_checkout" == "1" ]] || exit 0
7
+
8
+ if [[ "${GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH:-0}" == "1" ]]; then
9
+ exit 0
10
+ fi
11
+
12
+ # Skip in secondary worktrees — only the primary checkout is guarded.
13
+ git_dir_abs="$(cd "$(git rev-parse --git-dir)" && pwd -P)"
14
+ common_dir_abs="$(cd "$(git rev-parse --git-common-dir)" && pwd -P)"
15
+ if [[ "$git_dir_abs" != "$common_dir_abs" ]]; then
16
+ exit 0
17
+ fi
18
+
19
+ new_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
20
+ # Parse the latest reflog entry; post-checkout writes "checkout: moving from <prev> to <new>".
21
+ prev_branch="$(git reflog -1 HEAD 2>/dev/null | sed -n 's/.*checkout: moving from \([^ ]*\) to .*/\1/p' || true)"
22
+
23
+ [[ -n "$prev_branch" && -n "$new_branch" && "$prev_branch" != "$new_branch" ]] || exit 0
24
+
25
+ protected_raw="${GUARDEX_PROTECTED_BRANCHES:-$(git config --get multiagent.protectedBranches || true)}"
26
+ [[ -n "$protected_raw" ]] || protected_raw="dev main master"
27
+ protected_raw="${protected_raw//,/ }"
28
+
29
+ is_protected() {
30
+ local branch="$1"
31
+ for p in $protected_raw; do
32
+ [[ "$branch" == "$p" ]] && return 0
33
+ done
34
+ return 1
35
+ }
36
+
37
+ # Only guard when moving AWAY from a protected primary branch.
38
+ is_protected "$prev_branch" || exit 0
39
+
40
+ is_agent=0
41
+ if [[ -n "${CLAUDECODE:-}" \
42
+ || -n "${CLAUDE_CODE_SESSION_ID:-}" \
43
+ || -n "${CODEX_THREAD_ID:-}" \
44
+ || -n "${OMX_SESSION_ID:-}" \
45
+ || "${CODEX_CI:-0}" == "1" ]]; then
46
+ is_agent=1
47
+ fi
48
+
49
+ echo "" >&2
50
+ echo "[agent-primary-branch-guard] Primary checkout switched branches." >&2
51
+ echo "[agent-primary-branch-guard] from: $prev_branch (protected)" >&2
52
+ echo "[agent-primary-branch-guard] to: $new_branch" >&2
53
+ echo "[agent-primary-branch-guard] The primary working tree must stay on its base/protected branch." >&2
54
+ echo "[agent-primary-branch-guard] Use 'git worktree add' (or scripts/agent-branch-start.sh) for feature work." >&2
55
+
56
+ if [[ "$is_agent" == "1" ]]; then
57
+ echo "[agent-primary-branch-guard] Agent session detected — reverting to '$prev_branch'." >&2
58
+ echo "[agent-primary-branch-guard] Bypass with GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1 if truly intentional." >&2
59
+ if git diff --quiet && git diff --cached --quiet; then
60
+ GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1 git checkout "$prev_branch" >/dev/null 2>&1 || true
61
+ echo "[agent-primary-branch-guard] Reverted to '$prev_branch'." >&2
62
+ else
63
+ echo "[agent-primary-branch-guard] Working tree dirty — auto-revert skipped." >&2
64
+ echo "[agent-primary-branch-guard] Fix manually: git stash && git checkout $prev_branch" >&2
65
+ fi
66
+ else
67
+ echo "[agent-primary-branch-guard] Bypass with GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1 if intentional." >&2
68
+ fi
@@ -194,6 +194,20 @@ is_clean_worktree() {
194
194
  source_worktree="$(get_worktree_for_branch "$SOURCE_BRANCH")"
195
195
  created_source_probe=0
196
196
  source_probe_path=""
197
+ integration_worktree=""
198
+
199
+ cleanup() {
200
+ if [[ -n "$integration_worktree" && -d "$integration_worktree" ]]; then
201
+ git -C "$repo_root" worktree remove "$integration_worktree" --force >/dev/null 2>&1 || true
202
+ fi
203
+ if [[ "$created_source_probe" -eq 1 && -n "$source_probe_path" && -d "$source_probe_path" ]]; then
204
+ # Abort any in-progress git op so `worktree remove --force` succeeds on conflict-stuck probes.
205
+ git -C "$source_probe_path" rebase --abort >/dev/null 2>&1 || true
206
+ git -C "$source_probe_path" merge --abort >/dev/null 2>&1 || true
207
+ git -C "$repo_root" worktree remove "$source_probe_path" --force >/dev/null 2>&1 || true
208
+ fi
209
+ }
210
+ trap cleanup EXIT
197
211
 
198
212
  if [[ -z "$source_worktree" ]]; then
199
213
  source_probe_path="${agent_worktree_root}/__source-probe-${SOURCE_BRANCH//\//__}-$(date +%Y%m%d-%H%M%S)"
@@ -261,16 +275,6 @@ mkdir -p "$(dirname "$integration_worktree")"
261
275
  git -C "$repo_root" worktree add "$integration_worktree" "$start_ref" >/dev/null
262
276
  git -C "$integration_worktree" checkout -b "$integration_branch" >/dev/null
263
277
 
264
- cleanup() {
265
- if [[ -d "$integration_worktree" ]]; then
266
- git -C "$repo_root" worktree remove "$integration_worktree" --force >/dev/null 2>&1 || true
267
- fi
268
- if [[ "$created_source_probe" -eq 1 && -n "$source_probe_path" && -d "$source_probe_path" ]]; then
269
- git -C "$repo_root" worktree remove "$source_probe_path" --force >/dev/null 2>&1 || true
270
- fi
271
- }
272
- trap cleanup EXIT
273
-
274
278
  if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRANCH}"; then
275
279
  git -C "$source_worktree" fetch origin "$BASE_BRANCH" --quiet
276
280