@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.
- package/bin/multiagent-safety.js +21 -5
- package/package.json +3 -1
- package/templates/AGENTS.multiagent-safety.md +1 -0
- package/templates/githooks/post-checkout +68 -0
- package/templates/githooks/post-merge +3 -39
- package/templates/githooks/pre-commit +193 -27
- package/templates/githooks/pre-push +0 -0
- package/templates/scripts/agent-branch-finish.sh +702 -70
- package/templates/scripts/agent-branch-start.sh +877 -76
- package/templates/scripts/agent-worktree-prune.sh +353 -65
- package/templates/scripts/codex-agent.sh +238 -626
- package/templates/scripts/install-agent-git-hooks.sh +27 -4
- package/templates/scripts/openspec/init-change-workspace.sh +50 -4
- package/templates/scripts/openspec/init-plan-workspace.sh +495 -48
- package/templates/scripts/review-bot-watch.sh +11 -11
package/bin/multiagent-safety.js
CHANGED
|
@@ -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
|
|
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
|
|
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 '${
|
|
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.
|
|
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
|
-
|
|
5
|
-
|
|
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 [[ "$
|
|
127
|
-
if [[ "$
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|