diff --git a/scripts/agent-git b/scripts/agent-git new file mode 100755 index 00000000..7ef84b2a --- /dev/null +++ b/scripts/agent-git @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Run git with the commit AUTHOR named after the agent: `Claude ($BEADS_ACTOR)`. +# Why GIT_AUTHOR_NAME and not `-c user.name`: .agents/skills/wave/agent-rules.md. +set -euo pipefail + +if [ -z "${BEADS_ACTOR:-}" ]; then + echo 'agent-git: BEADS_ACTOR is unset; refusing to author as the owner' >&2 + echo 'usage: BEADS_ACTOR= scripts/agent-git commit|merge|cherry-pick ...' >&2 + exit 2 +fi + +GIT_AUTHOR_NAME="Claude ($BEADS_ACTOR)" exec git "$@" diff --git a/scripts/agent-git-test.sh b/scripts/agent-git-test.sh new file mode 100644 index 00000000..3e2ee6f7 --- /dev/null +++ b/scripts/agent-git-test.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# check_the_checker for scripts/agent-git (improvise-1xfb.3). A throwaway repo +# whose only identity is its own config; no global or system git config. +set -uo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUN="$here/agent-git" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +export GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1 +unset GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL + +pass=0; fail=0 +ok() { printf ' ok %s\n' "$1"; pass=$((pass+1)); } +bad() { printf ' FAIL %s (%s)\n' "$1" "$2"; fail=$((fail+1)); } + +# $1 name, $2 wanted, $3 got +same() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "got '$3', wanted '$2'"; fi; } + +repo="$work/repo" +git init -q "$repo" +git -C "$repo" config user.name Owner +git -C "$repo" config user.email owner@example.com +git -C "$repo" commit -q --allow-empty -m base +cd "$repo" || exit 1 +base="$(git rev-parse HEAD)" + +# The refusals: nothing may be committed, since the commit would be the owner's. +(unset BEADS_ACTOR; "$RUN" commit -q --allow-empty -m unset) > "$work/out" 2>&1 +same "an unset BEADS_ACTOR is refused" 2 "$?" +grep -q 'BEADS_ACTOR is unset' "$work/out" && ok "the refusal names BEADS_ACTOR" \ + || bad "the refusal names BEADS_ACTOR" "$(head -c 200 "$work/out")" +BEADS_ACTOR='' "$RUN" commit -q --allow-empty -m empty > /dev/null 2>&1 +same "an empty BEADS_ACTOR is refused" 2 "$?" +same "a refusal commits nothing" "$base" "$(git rev-parse HEAD)" + +# A commit: the author is the agent, and nothing else about the identity moves. +BEADS_ACTOR=agent-a1 "$RUN" commit -q --allow-empty -m one +same "the author is Claude ()" 'Claude (agent-a1)' "$(git log -1 --format=%an)" +same "the author email is the configured one" owner@example.com "$(git log -1 --format=%ae)" +same "the committer is untouched" 'Owner ' "$(git log -1 --format='%cn <%ce>')" +same "shared config is untouched" Owner "$(git config user.name)" + +# A merge commit is authored too; a plain amend keeps the agent as author. +git checkout -q -b side "$base" +BEADS_ACTOR=agent-b2 "$RUN" commit -q --allow-empty -m side +git checkout -q - +BEADS_ACTOR=agent-a1 "$RUN" merge -q --no-edit side +same "a merge commit is authored by the agent" 'Claude (agent-a1)' "$(git log -1 --format=%an)" +same "the merge has two parents" 2 "$(git log -1 --format=%P | wc -w | tr -d ' ')" + +# git's own failure is passed through, not swallowed. +BEADS_ACTOR=agent-a1 "$RUN" commit -q -m nothing-staged > /dev/null 2>&1 +rc=$? +[ "$rc" != 0 ] && ok "git's own failure is passed through" \ + || bad "git's own failure is passed through" "exit 0" + +printf '\nagent-git-test: %d passed, %d failed\n' "$pass" "$fail" +[ "$fail" = 0 ] diff --git a/scripts/checkers.sh b/scripts/checkers.sh index bf837508..183678fc 100755 --- a/scripts/checkers.sh +++ b/scripts/checkers.sh @@ -102,7 +102,7 @@ ci_suites() { build) echo assemble-stamp ;; js) echo node-test ;; # check-harness-lock must never read "could not resolve" as "stale" (improvise-zs2r). - kb) echo kb checkers pr-watch release-preflight pr-reviews fmt-workspaces bd-audit-ids check-harness-lock ;; + kb) echo kb checkers pr-watch release-preflight pr-reviews fmt-workspaces bd-audit-ids check-harness-lock agent-git ;; *) return 1 ;; esac }