@imdeadpool/guardex 6.0.0 → 6.1.0

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',
@@ -203,6 +207,15 @@ const CLI_COMMAND_DESCRIPTIONS = [
203
207
  ['help', 'Show this help output'],
204
208
  ['version', 'Print GuardeX version'],
205
209
  ];
210
+ const CORE_COMMAND_NAMES = new Set([
211
+ 'setup',
212
+ 'doctor',
213
+ 'status',
214
+ 'finish',
215
+ 'cleanup',
216
+ 'sync',
217
+ 'scan',
218
+ ]);
206
219
  const AGENT_BOT_DESCRIPTIONS = [
207
220
  ['review', 'Start PR monitor + codex-agent review flow (default interval: 30s)'],
208
221
  ['agents', 'Start/stop both review and cleanup bots for this repo'],
@@ -351,12 +364,15 @@ function statusDot(status) {
351
364
  return colorize('●', '33'); // yellow for degraded/unknown
352
365
  }
353
366
 
354
- function commandCatalogLines(indent = ' ') {
355
- const maxCommandLength = CLI_COMMAND_DESCRIPTIONS.reduce(
367
+ function commandCatalogLines(indent = ' ', { coreOnly = false } = {}) {
368
+ const entries = coreOnly
369
+ ? CLI_COMMAND_DESCRIPTIONS.filter(([name]) => CORE_COMMAND_NAMES.has(name))
370
+ : CLI_COMMAND_DESCRIPTIONS;
371
+ const maxCommandLength = entries.reduce(
356
372
  (max, [command]) => Math.max(max, command.length),
357
373
  0,
358
374
  );
359
- return CLI_COMMAND_DESCRIPTIONS.map(
375
+ return entries.map(
360
376
  ([command, description]) => `${indent}${command.padEnd(maxCommandLength + 2)}${description}`,
361
377
  );
362
378
  }
@@ -373,7 +389,7 @@ function agentBotCatalogLines(indent = ' ') {
373
389
 
374
390
  function printToolLogsSummary() {
375
391
  const usageLine = ` $ ${SHORT_TOOL_NAME} <command> [options]`;
376
- const commandDetails = commandCatalogLines(' ');
392
+ const commandDetails = commandCatalogLines(' ', { coreOnly: true });
377
393
  const agentBotDetails = agentBotCatalogLines(' ');
378
394
 
379
395
  if (!supportsAnsiColors()) {
@@ -418,7 +434,7 @@ function printToolLogsSummary() {
418
434
  }
419
435
  console.log(` ${pipe}${line.slice(2)}`);
420
436
  }
421
- console.log(` ${corner}─ ${colorize(`Try '${TOOL_NAME} doctor' for one-step repair + verification.`, '2')}`);
437
+ console.log(` ${corner}─ ${colorize(`Try '${SHORT_TOOL_NAME} doctor' to repair drift, or '${SHORT_TOOL_NAME} help' for the full command list.`, '2')}`);
422
438
  }
423
439
 
424
440
  function usage(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "6.0.0",
3
+ "version": "6.1.0",
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,
@@ -10,6 +10,8 @@
10
10
  "multiagent-safety": "bin/multiagent-safety.js"
11
11
  },
12
12
  "scripts": {
13
+ "prepack": "node pack-helpers/dereference-templates.mjs",
14
+ "postpack": "git checkout -- templates",
13
15
  "test": "node --test test/*.test.js",
14
16
  "agent:codex": "bash ./scripts/codex-agent.sh",
15
17
  "agent:branch:start": "bash ./scripts/agent-branch-start.sh",
@@ -11,6 +11,7 @@
11
11
  - If ownership is unclear or overlaps, stop that edit, post a blocker comment, and let the leader/integrator reassign scope.
12
12
  - For git isolation, each agent must start on a dedicated branch via `scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"`.
13
13
  - In-place branch mode is disallowed: never switch the active local/base checkout to an agent branch.
14
+ - Primary-checkout immutability: agents MUST NOT run `git checkout <branch>` on any repo's primary working tree, including nested repos inside the parent workspace (e.g. tool repos nested under the product repo). Keep each repo's primary checkout on its base/protected branch; use `git worktree add` for feature work. The `.githooks/post-checkout` hook auto-reverts primary-checkout branch switches when an agent session is detected; bypass only with `GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1` when truly intentional.
14
15
  - Treat the base branch (`main` or the user's current local base branch) as read-only while the agent branch is active.
15
16
  - Agent completion defaults to `scripts/codex-agent.sh`, which auto-finishes the branch (auto-commit changed files, push/create PR, attempt merge, and pull the local base branch after merge).
16
17
  - OMX completion policy: when a task is done, the agent must commit the task changes, push the agent branch, and create/update a PR for those changes (via `codex-agent` or `agent-branch-finish`).
@@ -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
@@ -1,43 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- if [[ "${GUARDEX_DISABLE_POST_MERGE_CLEANUP:-0}" == "1" ]]; then
5
- exit 0
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
6
7
  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
@@ -9,6 +9,12 @@ if [[ -z "$branch" ]]; then
9
9
  exit 0
10
10
  fi
11
11
 
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
+
12
18
  if [[ "${ALLOW_COMMIT_ON_PROTECTED_BRANCH:-0}" == "1" ]]; then
13
19
  exit 0
14
20
  fi
@@ -24,7 +30,7 @@ if [[ -n "${CODEX_THREAD_ID:-}" || -n "${OMX_SESSION_ID:-}" || "${CODEX_CI:-0}"
24
30
  fi
25
31
 
26
32
  is_vscode_git_context=0
27
- if [[ -n "${VSCODE_GIT_IPC_HANDLE:-}" || -n "${VSCODE_GIT_ASKPASS_NODE:-}" || -n "${VSCODE_IPC_HOOK_CLI:-}" ]]; then
33
+ if [[ -n "${VSCODE_GIT_IPC_HANDLE:-}" || -n "${VSCODE_GIT_ASKPASS_NODE:-}" || -n "${VSCODE_IPC_HOOK_CLI:-}" || "${TERM_PROGRAM:-}" == "vscode" ]]; then
28
34
  is_vscode_git_context=1
29
35
  fi
30
36
 
@@ -68,6 +74,163 @@ case "$codex_require_agent_branch" in
68
74
  *) should_require_codex_agent_branch=1 ;;
69
75
  esac
70
76
 
77
+ sanitize_slug() {
78
+ local raw="$1"
79
+ local fallback="${2:-task}"
80
+ local slug
81
+ slug="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g')"
82
+ if [[ -z "$slug" ]]; then
83
+ slug="$fallback"
84
+ fi
85
+ printf '%s' "$slug"
86
+ }
87
+
88
+ resolve_agent_branch_base() {
89
+ local branch_name="$1"
90
+ git config --get "branch.${branch_name}.guardexBase" || true
91
+ }
92
+
93
+ is_helper_agent_branch() {
94
+ local branch_name="$1"
95
+ local base_branch=""
96
+ base_branch="$(resolve_agent_branch_base "$branch_name")"
97
+ [[ "$base_branch" == agent/* ]]
98
+ }
99
+
100
+ ensure_agent_branch_openspec_workspace() {
101
+ local branch_name="$1"
102
+ local change_slug change_dir specs_dir capability_slug branch_base
103
+ local missing_workspace=0
104
+ local openspec_script="scripts/openspec/init-change-workspace.sh"
105
+
106
+ branch_base="$(git config --get "branch.${branch_name}.guardexBase" || true)"
107
+ if [[ "$branch_base" == agent/* ]]; then
108
+ echo "[agent-openspec-guard] Skipping OpenSpec change workspace bootstrap for helper branch '${branch_name}' (base '${branch_base}')."
109
+ return 0
110
+ fi
111
+
112
+ change_slug="$(sanitize_slug "${branch_name//\//-}" "change")"
113
+ change_dir="openspec/changes/${change_slug}"
114
+ specs_dir="${change_dir}/specs"
115
+
116
+ if [[ ! -f "${change_dir}/.openspec.yaml" || ! -f "${change_dir}/proposal.md" || ! -f "${change_dir}/tasks.md" ]]; then
117
+ missing_workspace=1
118
+ elif [[ ! -d "$specs_dir" ]] || ! find "$specs_dir" -mindepth 2 -maxdepth 2 -type f -name spec.md | grep -q .; then
119
+ missing_workspace=1
120
+ fi
121
+
122
+ if [[ "$missing_workspace" -ne 1 ]]; then
123
+ return 0
124
+ fi
125
+
126
+ if [[ ! -f "$openspec_script" ]]; then
127
+ cat >&2 <<MSG
128
+ [agent-openspec-guard] Missing OpenSpec change workspace for '${branch_name}'.
129
+ Expected path:
130
+ ${change_dir}
131
+ Cannot auto-initialize because '${openspec_script}' is missing.
132
+ Run:
133
+ gx setup --target "$(git rev-parse --show-toplevel)"
134
+ bash scripts/openspec/init-change-workspace.sh "${change_slug}" "<capability-slug>"
135
+ MSG
136
+ exit 1
137
+ fi
138
+
139
+ if [[ ! -x "$openspec_script" ]]; then
140
+ chmod +x "$openspec_script" 2>/dev/null || true
141
+ fi
142
+
143
+ capability_slug="$(sanitize_slug "${branch_name##*/}" "general-behavior")"
144
+ init_output=""
145
+ if ! init_output="$(bash "$openspec_script" "$change_slug" "$capability_slug" 2>&1)"; then
146
+ printf '%s\n' "$init_output" >&2
147
+ cat >&2 <<MSG
148
+ [agent-openspec-guard] OpenSpec auto-init failed for '${branch_name}'.
149
+ Run manually:
150
+ bash scripts/openspec/init-change-workspace.sh "${change_slug}" "${capability_slug}"
151
+ MSG
152
+ exit 1
153
+ fi
154
+
155
+ if [[ -n "$init_output" ]]; then
156
+ printf '%s\n' "$init_output"
157
+ fi
158
+
159
+ git add "$change_dir"
160
+
161
+ if [[ -x scripts/agent-file-locks.py ]]; then
162
+ staged_openspec="$(git diff --cached --name-only -- "$change_dir" | sed '/^$/d' || true)"
163
+ if [[ -n "$staged_openspec" ]]; then
164
+ mapfile -t openspec_files < <(printf '%s\n' "$staged_openspec")
165
+ python3 scripts/agent-file-locks.py claim --branch "$branch_name" "${openspec_files[@]}" >/dev/null 2>&1 || true
166
+ fi
167
+ fi
168
+
169
+ echo "[agent-openspec-guard] Bootstrapped OpenSpec change workspace: ${change_dir}"
170
+ }
171
+
172
+ should_auto_reroute_protected_branch() {
173
+ local raw="${GUARDEX_AUTO_REROUTE_PROTECTED_BRANCH:-$(git config --get multiagent.autoRerouteProtectedBranch || true)}"
174
+ local lowered=""
175
+ if [[ -z "$raw" ]]; then
176
+ raw="true"
177
+ fi
178
+ lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
179
+ case "$lowered" in
180
+ 1|true|yes|on) return 0 ;;
181
+ *) return 1 ;;
182
+ esac
183
+ }
184
+
185
+ auto_reroute_protected_branch_commit() {
186
+ local branch_name="$1"
187
+ local starter_script="scripts/agent-branch-start.sh"
188
+ local task_name="${GUARDEX_AUTO_REROUTE_TASK_NAME:-protected-branch-commit-reroute}"
189
+ local agent_name="${GUARDEX_AUTO_REROUTE_AGENT_NAME:-auto-reroute}"
190
+ local changed_paths=""
191
+ local start_output=""
192
+ local start_status=0
193
+ local new_branch=""
194
+ local worktree_path=""
195
+
196
+ changed_paths="$({
197
+ git diff --name-only
198
+ git diff --cached --name-only
199
+ git ls-files --others --exclude-standard
200
+ } | sed '/^$/d' | sort -u)"
201
+
202
+ if [[ -z "$changed_paths" ]]; then
203
+ return 1
204
+ fi
205
+
206
+ if [[ ! -x "$starter_script" ]]; then
207
+ return 1
208
+ fi
209
+
210
+ set +e
211
+ start_output="$(bash "$starter_script" "$task_name" "$agent_name" "$branch_name" 2>&1)"
212
+ start_status=$?
213
+ set -e
214
+
215
+ if [[ "$start_status" -ne 0 ]]; then
216
+ printf '%s\n' "$start_output" >&2
217
+ return 1
218
+ fi
219
+
220
+ new_branch="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Created branch: //p' | tail -n 1)"
221
+ worktree_path="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | tail -n 1)"
222
+
223
+ printf '%s\n' "$start_output" >&2
224
+ cat >&2 <<MSG
225
+ [agent-branch-guard] Protected-branch commit rerouted automatically.
226
+ Changes from '${branch_name}' were moved to:
227
+ branch: ${new_branch:-<see output>}
228
+ worktree: ${worktree_path:-<see output>}
229
+ Continue work and commit from that agent worktree.
230
+ MSG
231
+ return 0
232
+ }
233
+
71
234
  is_codex_managed_only_commit_on_protected=0
72
235
  if [[ "$is_codex_session" == "1" && "$is_protected_branch" == "1" ]]; then
73
236
  deleted_paths="$(git diff --cached --name-only --diff-filter=D)"
@@ -123,17 +286,32 @@ MSG
123
286
  fi
124
287
  fi
125
288
 
126
- if [[ "$is_protected_branch" == "1" ]]; then
127
- if [[ "$is_codex_session" != "1" && "$is_vscode_git_context" == "1" ]]; then
128
- if [[ "$allow_vscode_protected_branch_writes" == "1" ]]; then
129
- exit 0
130
- fi
289
+ if [[ "$is_codex_session" == "1" && "$branch" == agent/* ]]; then
290
+ if [[ "$is_linked_worktree" != "1" && "${GUARDEX_ALLOW_CODEX_ON_PRIMARY_WORKTREE:-0}" != "1" ]]; then
291
+ cat >&2 <<'MSG'
292
+ [codex-worktree-guard] Codex agent commits are blocked from the primary checkout.
293
+ Use a linked agent worktree for agent/* branches:
294
+ bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
295
+ Then commit from the printed worktree path.
296
+
297
+ Temporary bypass (not recommended):
298
+ GUARDEX_ALLOW_CODEX_ON_PRIMARY_WORKTREE=1 git commit ...
299
+ MSG
300
+ exit 1
131
301
  fi
302
+ fi
132
303
 
133
- if [[ "$is_unborn_branch" == "1" && "$is_codex_session" != "1" ]]; then
304
+ if [[ "$is_protected_branch" == "1" ]]; then
305
+ if [[ "$is_codex_session" != "1" && "$is_vscode_git_context" == "1" && "$allow_vscode_protected_branch_writes" == "1" ]]; then
134
306
  exit 0
135
307
  fi
136
308
 
309
+ if should_auto_reroute_protected_branch; then
310
+ if auto_reroute_protected_branch_commit "$branch"; then
311
+ exit 1
312
+ fi
313
+ fi
314
+
137
315
  git_dir="$(git rev-parse --git-dir)"
138
316
  if [[ -f "$git_dir/MERGE_HEAD" ]]; then
139
317
  exit 0
@@ -145,8 +323,10 @@ Use an agent branch first:
145
323
  bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
146
324
  After finishing work:
147
325
  bash scripts/agent-branch-finish.sh
326
+ Auto-reroute can be disabled (not recommended):
327
+ GUARDEX_AUTO_REROUTE_PROTECTED_BRANCH=0 git commit ...
148
328
 
149
- Optional repo opt-in for VS Code protected-branch commits:
329
+ Optional repo override for manual VS Code protected-branch commits:
150
330
  git config multiagent.allowVscodeProtectedBranchWrites true
151
331
 
152
332
  Temporary bypass (not recommended):
@@ -155,26 +335,12 @@ MSG
155
335
  exit 1
156
336
  fi
157
337
 
158
- if [[ "$is_agent_context" == "1" && "$branch" != agent/* ]]; then
159
- cat >&2 <<'MSG'
160
- [agent-branch-guard] Agent commits must run on dedicated agent/* branches.
161
- Start an agent branch first:
162
- bash scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"
163
- Then commit on that branch.
164
-
165
- Temporary bypass (not recommended):
166
- ALLOW_COMMIT_ON_PROTECTED_BRANCH=1 git commit ...
167
- MSG
168
- exit 1
169
- fi
170
-
171
338
  if [[ "$branch" == agent/* ]]; then
172
- if [[ "${GUARDEX_AUTOCLAIM_STAGED_LOCKS:-1}" == "1" ]]; then
173
- while IFS= read -r staged_file; do
174
- [[ -z "$staged_file" ]] && continue
175
- [[ "$staged_file" == ".omx/state/agent-file-locks.json" ]] && continue
176
- python3 scripts/agent-file-locks.py claim --branch "$branch" "$staged_file" >/dev/null 2>&1 || true
177
- done < <(git diff --cached --name-only --diff-filter=ACMRDTUXB)
339
+ if is_helper_agent_branch "$branch"; then
340
+ helper_base="$(resolve_agent_branch_base "$branch")"
341
+ echo "[agent-openspec-guard] Skipping OpenSpec change workspace bootstrap for helper branch '${branch}' (base '${helper_base}')."
342
+ else
343
+ ensure_agent_branch_openspec_workspace "$branch"
178
344
  fi
179
345
 
180
346
  if ! python3 scripts/agent-file-locks.py validate --branch "$branch" --staged; then
File without changes