@imdeadpool/guardex 6.0.0 → 6.0.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "6.0.0",
3
+ "version": "6.0.1",
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",
@@ -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