no-yolo-commits 1.0.0 → 1.0.3

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": "no-yolo-commits",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "AI pre-commit review (via the claude CLI) + a guard that auto-branches you off main/master instead of letting a direct commit land there.",
5
5
  "keywords": [
6
6
  "husky",
@@ -34,5 +34,11 @@
34
34
  },
35
35
  "publishConfig": {
36
36
  "access": "public"
37
+ },
38
+ "devDependencies": {
39
+ "husky": "^9.1.7"
40
+ },
41
+ "scripts": {
42
+ "prepare": "husky"
37
43
  }
38
44
  }
@@ -13,13 +13,18 @@ Do NOT flag stylistic preferences. Do NOT flag pre-existing patterns already use
13
13
 
14
14
  review_tmpfile="$(mktemp)"
15
15
 
16
- (claude -p "$review_prompt" \
16
+ # No subshell wrapper here on purpose: `(claude ...; exit 0) &` would make
17
+ # $! the wrapper's PID, not claude's — since the wrapper has more to do
18
+ # after claude exits, the shell can't tail-exec it, so killing $review_pid
19
+ # on timeout would kill the empty wrapper and leave claude itself running
20
+ # orphaned. Backgrounding claude directly makes $! the real PID.
21
+ claude -p "$review_prompt" \
17
22
  --output-format json \
18
23
  --json-schema '{"type":"object","properties":{"blocking_issues":{"type":"array","items":{"type":"string"}}},"required":["blocking_issues"]}' \
19
24
  --allowedTools "Bash(git diff*)" "Bash(git show*)" "Bash(git log*)" "Read" "Grep" "Glob" \
20
25
  --disallowedTools "Write" "Edit" "MultiEdit" "NotebookEdit" \
21
26
  --permission-mode bypassPermissions \
22
- >"$review_tmpfile" 2>/dev/null; exit 0) &
27
+ >"$review_tmpfile" 2>/dev/null &
23
28
  review_pid=$!
24
29
  (sleep 120; kill "$review_pid" >/dev/null 2>&1; exit 0) >/dev/null 2>&1 &
25
30
  review_watcher_pid=$!
@@ -33,7 +38,13 @@ Do NOT flag stylistic preferences. Do NOT flag pre-existing patterns already use
33
38
  const fs = require("fs")
34
39
  try {
35
40
  const data = JSON.parse(fs.readFileSync(process.argv[1], "utf-8"))
36
- const issues = (data.structured_output && data.structured_output.blocking_issues) || []
41
+ // An API/session error from claude itself is valid JSON with no
42
+ // structured_output — falling back to [] there made a failed review
43
+ // look identical to "no issues found" instead of "unavailable".
44
+ if (data.is_error || !data.structured_output) {
45
+ process.exit(2)
46
+ }
47
+ const issues = data.structured_output.blocking_issues || []
37
48
  if (issues.length > 0) {
38
49
  console.error("")
39
50
  console.error("⛔ AI review found blocking issues:")
@@ -46,8 +57,7 @@ Do NOT flag stylistic preferences. Do NOT flag pre-existing patterns already use
46
57
  } catch (e) {
47
58
  process.exit(2)
48
59
  }
49
- ' "$review_tmpfile"
50
- review_status=$?
60
+ ' "$review_tmpfile" && review_status=0 || review_status=$?
51
61
  rm -f "$review_tmpfile" 2>/dev/null || true
52
62
 
53
63
  if [ "$review_status" = "1" ]; then
@@ -83,7 +93,9 @@ if command -v claude >/dev/null 2>&1; then
83
93
  tmpfile="$(mktemp)"
84
94
  prompt="Summarize the following staged git diff as ONE short phrase in kebab-case (lowercase words separated by hyphens, no punctuation, no quotes), at most 8 words, describing what changed. Output ONLY the phrase, nothing else, no explanation."
85
95
 
86
- (printf '%s' "$diff" | claude -p "$prompt" >"$tmpfile" 2>/dev/null; exit 0) &
96
+ # Same reasoning as the review call above: background claude directly,
97
+ # no subshell wrapper, so $! is the real PID the timeout can kill.
98
+ printf '%s' "$diff" | claude -p "$prompt" >"$tmpfile" 2>/dev/null &
87
99
  claude_pid=$!
88
100
  (sleep 25; kill "$claude_pid" >/dev/null 2>&1; exit 0) >/dev/null 2>&1 &
89
101
  watcher_pid=$!
@@ -113,13 +125,14 @@ timestamp="$(date +%s)"
113
125
  new_branch="__PREFIX__-${timestamp}-${slug}"
114
126
  new_branch="$(printf '%s' "$new_branch" | cut -c1-100)"
115
127
 
116
- if ! git checkout -b "$new_branch" 2>/tmp/no-yolo-commits-error; then
128
+ branch_err_file="$(mktemp)"
129
+ if ! git checkout -b "$new_branch" 2>"$branch_err_file"; then
117
130
  echo "✗ Failed to create branch '$new_branch':"
118
- cat /tmp/no-yolo-commits-error 2>/dev/null || true
119
- rm -f /tmp/no-yolo-commits-error 2>/dev/null || true
131
+ cat "$branch_err_file" 2>/dev/null || true
132
+ rm -f "$branch_err_file" 2>/dev/null || true
120
133
  echo "Commit aborted — create/switch to a feature branch manually and retry."
121
134
  exit 1
122
135
  fi
123
- rm -f /tmp/no-yolo-commits-error 2>/dev/null || true
136
+ rm -f "$branch_err_file" 2>/dev/null || true
124
137
 
125
138
  echo "✓ Switched to '$new_branch' — continuing commit there."