This repository was archived by the owner on Oct 8, 2026. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathcodegen-commit
More file actions
executable file
·249 lines (217 loc) · 7.79 KB
/
Copy pathcodegen-commit
File metadata and controls
executable file
·249 lines (217 loc) · 7.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
#!/usr/bin/env bash
# codegen-commit — deterministic commit step. Replaces the paid `committer`
# subagent turn: no model call, no retry ladder, no read boundary to
# document — a script that stages, validates, and commits.
#
# Usage:
# codegen-commit --subject "<text>" [--cwd <dir>] [--no-gate] [--allow-revert]
# codegen-commit --check-subject "<text>"
#
# Subcommands (mutually exclusive):
# --subject "<text>" Stage all (`git add -A`), validate the subject,
# re-check gate verdict, run the backward-roll
# guard, `git commit -m "<text>"`.
# --check-subject "<text>" Validate only — no git state touched. Used by
# `/ready` and pitch-format-validator.sh to
# refuse a bad commit_subject: before spend.
#
# Options:
# --cwd <dir> Repo root (default: $PWD)
# --no-gate Skip the gate-verdict-clear requirement (explicit
# second signal — mirrors pre-commit-guard's ops
# two-signal unlock; never a silent default)
# --allow-revert Skip the backward-roll guard (equivalent to the legacy
# COMMITTER_ALLOW_REVERT=1 escape hatch)
#
# Env (test seam):
# CODEGEN_COMMIT_ALLOW_REVERT=1 same effect as --allow-revert
#
# Validation rules on the subject (mechanical subset only — imperative mood
# is NOT mechanised; see the pitch "committing is deterministic, not a
# model call" — that judgement lives in the shaper prompt, where a model
# already reads):
# - non-empty
# - single line (no embedded newline)
# - <= 72 bytes
# - first character uppercase
# - no trailing period
# - no Co-Authored-By / Signed-off-by trailer anywhere in the text
#
# Exit codes: 0 = success, 1 = refusal (bad subject, non-clear verdict,
# empty index, backward roll, commit failed), 2 = usage error.
set -euo pipefail
CWD="$PWD"
MODE=""
SUBJECT=""
NO_GATE=0
ALLOW_REVERT="${CODEGEN_COMMIT_ALLOW_REVERT:-0}"
usage() {
printf 'Usage:\n' >&2
printf ' codegen-commit --subject "<text>" [--cwd <dir>] [--no-gate] [--allow-revert]\n' >&2
printf ' codegen-commit --check-subject "<text>"\n' >&2
exit 2
}
while [ $# -gt 0 ]; do
case "$1" in
--subject)
[ $# -ge 2 ] || usage
MODE="commit"
SUBJECT="$2"
shift 2
;;
--check-subject)
[ $# -ge 2 ] || usage
MODE="check"
SUBJECT="$2"
shift 2
;;
--cwd)
[ $# -ge 2 ] || usage
CWD="$2"
shift 2
;;
--no-gate)
NO_GATE=1
shift
;;
--allow-revert)
ALLOW_REVERT=1
shift
;;
*)
printf 'codegen-commit: unrecognized argument: %s\n' "$1" >&2
usage
;;
esac
done
[ -n "$MODE" ] || usage
# validate_subject <text> — prints nothing on success; on failure prints the
# reason to stderr and returns 1. Never exits directly — callers decide.
validate_subject() {
local subject="$1"
if [ -z "$subject" ]; then
printf 'codegen-commit: subject is empty\n' >&2
return 1
fi
case "$subject" in
*$'\n'*)
printf 'codegen-commit: subject must be a single line\n' >&2
return 1
;;
esac
local byte_len
byte_len=$(printf '%s' "$subject" | wc -c | tr -d '[:space:]')
if [ "$byte_len" -gt 72 ]; then
printf 'codegen-commit: subject exceeds 72-byte hard cap (got %s): %s\n' "$byte_len" "$subject" >&2
return 1
fi
local first_char
first_char=$(printf '%s' "$subject" | cut -c1)
case "$first_char" in
[A-Z]) ;;
*)
printf 'codegen-commit: subject must start with an uppercase letter: %s\n' "$subject" >&2
return 1
;;
esac
case "$subject" in
*.)
printf 'codegen-commit: subject must not end with a trailing period: %s\n' "$subject" >&2
return 1
;;
esac
if printf '%s' "$subject" | grep -qiE 'co-authored-by|signed-off-by'; then
printf 'codegen-commit: subject must not carry a Co-Authored-By/Signed-off-by trailer: %s\n' "$subject" >&2
return 1
fi
return 0
}
if [ "$MODE" = "check" ]; then
if validate_subject "$SUBJECT"; then
exit 0
else
exit 1
fi
fi
# --- commit mode: full validate + stage + gate + revert-guard + commit ---
if ! validate_subject "$SUBJECT"; then
exit 1
fi
if [ ! -d "$CWD/.git" ] && ! git -C "$CWD" rev-parse --git-dir >/dev/null 2>&1; then
printf 'codegen-commit: %s is not a git work tree\n' "$CWD" >&2
exit 1
fi
# Gate verdict gate — refuse unless clear, or --no-gate explicitly given.
if [ "$NO_GATE" -ne 1 ]; then
result_file="$CWD/codegen/gate-pending/gate-result.json"
if [ ! -f "$result_file" ]; then
printf 'codegen-commit: %s is missing — gate verdict cannot be confirmed. Pass --no-gate to override.\n' "$result_file" >&2
exit 1
fi
verdict=$(jq -r '.verdict // ""' "$result_file" 2>/dev/null || printf '')
if [ "$verdict" != "clear" ]; then
printf 'codegen-commit: gate verdict is %s (need clear) — refusing to commit. Pass --no-gate to override.\n' "${verdict:-absent}" >&2
exit 1
fi
fi
git -C "$CWD" add -A
staged=$(git -C "$CWD" diff --cached --name-only)
if [ -z "$staged" ]; then
printf 'codegen-commit: nothing staged after git add -A — empty index, refusing to commit\n' >&2
exit 1
fi
# Backward-roll guard — ported from the deleted
# committer-no-revert-prior-commit.sh hook. Reads git state only.
if [ "$ALLOW_REVERT" != "1" ]; then
base_sha="${CODEGEN_CYCLE_BASE_SHA:-}"
if [ -n "$base_sha" ]; then
session_commits=$(git -C "$CWD" rev-list "$base_sha"..HEAD 2>/dev/null || printf '')
if [ -n "$session_commits" ]; then
TMP_STAGED=$(mktemp)
TMP_PARENT=$(mktemp)
trap 'rm -f "$TMP_STAGED" "$TMP_PARENT"' EXIT
backward_roll_files=""
while IFS= read -r staged_file; do
[ -z "$staged_file" ] && continue
if ! git -C "$CWD" show ":${staged_file}" >"$TMP_STAGED" 2>/dev/null; then
continue
fi
while IFS= read -r session_sha; do
[ -z "$session_sha" ] && continue
changed_in_commit=$(git -C "$CWD" diff --name-only "${session_sha}^" "${session_sha}" -- "${staged_file}" 2>/dev/null || printf '')
if [ -z "$changed_in_commit" ]; then
continue
fi
parent_sha="${session_sha}^"
if ! git -C "$CWD" show "${parent_sha}:${staged_file}" >"$TMP_PARENT" 2>/dev/null; then
continue
fi
if cmp -s "$TMP_STAGED" "$TMP_PARENT"; then
if [ -z "$backward_roll_files" ]; then
backward_roll_files="${staged_file} (reverts ${session_sha})"
else
backward_roll_files="${backward_roll_files}
${staged_file} (reverts ${session_sha})"
fi
break
fi
done <<EOF
$session_commits
EOF
done <<EOF
$staged
EOF
if [ -n "$backward_roll_files" ]; then
printf 'codegen-commit: staged content silently reverts work from a prior session commit.\nFiles that would roll back:\n %s\nIf intentional, pass --allow-revert (or set CODEGEN_COMMIT_ALLOW_REVERT=1) and retry.\n' "$backward_roll_files" >&2
exit 1
fi
fi
fi
fi
if ! git -C "$CWD" commit -m "$SUBJECT" >/dev/null; then
printf 'codegen-commit: git commit failed\n' >&2
exit 1
fi
sha=$(git -C "$CWD" rev-parse HEAD)
printf 'COMMITTED: %s %s\n' "$sha" "$SUBJECT"
exit 0