Skip to content

Commit 89f87b5

Browse files
author
igor
committed
Merge branch 'fizzy-forging-stardust': safe-agent-action git-gate rewrite + upstream-pr-prep helper
2 parents e4a81b1 + 77532cf commit 89f87b5

2 files changed

Lines changed: 618 additions & 16 deletions

File tree

agent-helpers/safe-agent-action.sh

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,38 +182,108 @@ case "$ACTION" in
182182
# ====================================================================
183183

184184
git)
185-
cd "$REPO_ROOT"
185+
# Worktree-aware cwd: resolve the repo toplevel from the caller's
186+
# cwd instead of the fixed REPO_ROOT. This lets wrapper git calls
187+
# issued from inside a git worktree (e.g. /tmp/linuxcnc-pr-...)
188+
# operate on that worktree's HEAD rather than the main fork
189+
# working tree. Other wrapper cases still cd to REPO_ROOT; only
190+
# the git case is worktree-aware.
191+
local_TOPLEVEL=$(git rev-parse --show-toplevel 2>/dev/null || true)
192+
[ -z "$local_TOPLEVEL" ] && die "git: not in a git repo"
193+
cd "$local_TOPLEVEL"
186194
local_SUB="${1:-}"
187195
case "$local_SUB" in
188-
# Read-only
189-
status|log|diff|branch|show|rev-parse|describe|tag|ls-files|blame|shortlog|reflog|config|stash|grep|ls-tree|cat-file)
196+
# Read-only (always safe)
197+
status|log|diff|show|rev-parse|describe|tag|ls-files|blame|shortlog|reflog|config|grep|ls-tree|cat-file)
198+
exec git "$@"
199+
;;
200+
# Branch: allow listing/creation, block -D/-M/-d/-m/--delete/--move
201+
# when the target is master or main (closes the pre-existing gap).
202+
branch)
203+
local_HAS_DEL=0
204+
local_HAS_MAIN=0
205+
for arg in "$@"; do
206+
case "$arg" in
207+
-D|-M|-d|-m|--delete|--move|--delete-force|--move-force)
208+
local_HAS_DEL=1
209+
;;
210+
master|main)
211+
local_HAS_MAIN=1
212+
;;
213+
esac
214+
done
215+
if [ "$local_HAS_DEL" = 1 ] && [ "$local_HAS_MAIN" = 1 ]; then
216+
die "git branch: refusing to delete/move master/main. Protected branches."
217+
fi
190218
exec git "$@"
191219
;;
192-
# Index / commit (non-destructive)
193-
add|rm|mv|restore)
220+
# Workspace / remote-tracking ops (non-destructive to remotes)
221+
add|rm|mv|restore|switch|stash|fetch)
194222
exec git "$@"
195223
;;
196-
switch)
224+
# Local state mutations (recoverable from reflog/origin; permitted
225+
# under "allow all feature-branch work")
226+
checkout|reset|clean|gc|rebase|cherry-pick|worktree)
197227
exec git "$@"
198228
;;
229+
# Commit: always allow. --amend is normal pre-push; post-push
230+
# amend is gated by the push rule below. --no-edit is allowed
231+
# (useful for amend flows the operator explicitly chose).
199232
commit)
233+
exec git "$@"
234+
;;
235+
# Merge: refuse when current branch is master/main. Operator
236+
# uses session-merge flow or bare git with explicit approval.
237+
merge)
238+
local_CUR=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
239+
if [ "$local_CUR" = "master" ] || [ "$local_CUR" = "main" ]; then
240+
die "git merge: refusing to merge into '$local_CUR'. Use 'session-merge <branch>' for the merge-to-main approval flow, or switch off master/main first."
241+
fi
242+
exec git "$@"
243+
;;
244+
# Push: refuse if (a) current branch is master/main, (b) any
245+
# argv token targets master/main via any refspec form, or
246+
# (c) the remote argument is 'upstream'. Force-push flags are
247+
# allowed on feature branches by design.
248+
push)
249+
local_CUR=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
250+
if [ "$local_CUR" = "master" ] || [ "$local_CUR" = "main" ]; then
251+
die "git push: refusing to push while on '$local_CUR' (implicit target). Switch to a feature branch first, or use session-merge for the merge-to-main approval flow."
252+
fi
253+
local_SAW_REMOTE=0
254+
local_REMOTE=""
255+
for arg in "$@"; do
256+
case "$arg" in
257+
push|-*) ;; # subcommand or flag
258+
*)
259+
if [ "$local_SAW_REMOTE" = 0 ]; then
260+
local_REMOTE="$arg"
261+
local_SAW_REMOTE=1
262+
fi
263+
;;
264+
esac
265+
done
266+
if [ "$local_REMOTE" = "upstream" ]; then
267+
die "git push upstream ...: refusing to push to the upstream remote. This script never pushes upstream. Use origin for your own fork."
268+
fi
200269
for arg in "$@"; do
201270
case "$arg" in
202-
--amend|--no-edit)
203-
die "git commit $arg: not allowed (would rewrite a published commit). Create a new commit instead."
271+
master|main|*:master|*:main|+master|+main|HEAD:master|HEAD:main|refs/heads/master|refs/heads/main|:master|:main)
272+
die "git push: refusing refspec '$arg' (targets master/main). Feature branches only. Use session-merge to merge a feature branch to main."
204273
;;
205274
esac
206275
done
207276
exec git "$@"
208277
;;
209-
push|"push-force"|reset|rebase|clean|gc|filter-branch|update-ref|checkout|merge)
210-
die "git $local_SUB: blocked in wrapper (destructive or rewriting history). Get explicit operator approval via bare git permission."
278+
# Still blocked: niche history/ref manipulation
279+
filter-branch|update-ref)
280+
die "git $local_SUB: blocked in wrapper (niche history/ref manipulation, rarely correct). Get explicit operator approval if truly needed."
211281
;;
212282
"")
213283
die "Usage: $0 git <subcommand> [args]"
214284
;;
215285
*)
216-
die "git $local_SUB: not in wrapper allowlist. Read-only: status, log, diff, branch, show, etc. Write: add, rm, mv, commit, restore, stash. Anything else needs explicit operator approval."
286+
die "git $local_SUB: not in wrapper allowlist. Add it to safe-agent-action.sh if it's safe for feature-branch work."
217287
;;
218288
esac
219289
;;
@@ -471,10 +541,16 @@ Search / inspect (read-only):
471541
tail <file> [n] Show last N lines of a file (default 50)
472542
du [path] Disk usage (defaults to repo root)
473543
474-
Git (restricted):
475-
git <subcmd> [args] Read-only: status, log, diff, branch, show, etc.
476-
Index/commit: add, rm, mv, commit (no --amend),
477-
restore, stash. Destructive ops blocked.
544+
Git (feature-branch work allowed; master/main protection):
545+
git <subcmd> [args] Feature-branch work allowed freely. Push/merge to
546+
master/main is blocked by gates on the subcommand
547+
(refuses refspecs targeting master/main and refuses
548+
the upstream remote). Force-push to feature branches
549+
is allowed. commit --amend is allowed pre-push.
550+
branch -D/-M on master/main is blocked.
551+
filter-branch and update-ref are still blocked.
552+
The git case is worktree-aware: calls from inside a
553+
git worktree operate on that worktree's HEAD.
478554
479555
Branch-per-session workflow:
480556
session-start <name> Create feature branch from main + push to origin
@@ -501,7 +577,9 @@ LinuxCNC tools (each documents its PURPOSE in the source):
501577
502578
Disallowed:
503579
exec, sh, bash, eval, sudo, su
504-
git push / reset / rebase / merge (use session-push for feature branches)
580+
git filter-branch / update-ref (niche history rewriting)
581+
git push/merge targeting master/main or the upstream remote
582+
(use session-merge flow for merging a feature branch to master/main)
505583
506584
Permission setup:
507585
Add to .claude/settings.json:

0 commit comments

Comments
 (0)