Repository navigation
Add pr-visual-writeup skill to .agents/skills - #1354
Conversation
Shared agent skill under the cross-client `.agents/skills/` convention so Claude Code, Codex, and other compliant agents can discover it.
Claude Code only scans .claude/skills/ (not the cross-client .agents/skills/ convention), so point it at the shared directory via a symlink. Any skill added under .agents/skills/ is picked up by Claude Code, Codex, and other compliant agents.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughIntroduces a new PR visual writeup skill comprising documentation, workflow patterns, and helper scripts for capturing UI screenshots across theme and viewport variations, converting scroll animations to GIFs, uploading assets to GitHub gists, and composing structured PR bodies with embedded visuals. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
.agents/skills/pr-visual-writeup/scripts/detect_dev_server.sh (1)
26-29: Exit code is ambiguous when no servers are found.The stderr message says "no listening node servers" but the script still exits
0, so callers (or the SKILL.md workflow that uses this to pick a port) can't distinguish "nothing running" from "one found". Considerexit 1so scripted consumers can branch on it.Proposed change
if [ -z "$ports" ]; then echo "no listening node servers in $MIN-$MAX" >&2 - exit 0 + exit 1 fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/pr-visual-writeup/scripts/detect_dev_server.sh around lines 26 - 29, The script currently prints "no listening node servers in $MIN-$MAX" to stderr but returns exit code 0, which is ambiguous for callers; change the exit behavior so that when the variable "ports" is empty (the if [ -z "$ports" ] branch) the script exits with a non-zero status (e.g., exit 1) so consumers can detect "no servers found"; update the echo/exit in that branch accordingly and ensure any tests or callers expecting success are adjusted to treat the non-zero exit as "no servers"..agents/skills/pr-visual-writeup/scripts/upload_gist.sh (1)
69-73:$OLDPWDis fragile as the gist-id.txt destination.
$OLDPWDis whatever directory the caller was in before this script ran, which may not be where they expect the id file (e.g. if they invoked via an absolute path from a different cwd, or if the calling script itselfcd'd). SKILL.md says "Stashes the gist id in./gist-id.txt", so writing to$PWDcaptured before the internalcd "$WORK/gist"would be more predictable.Proposed fix
+ORIG_PWD=$PWD @@ cd "$WORK/gist" @@ -echo "$GIST_ID" > "$OLDPWD/gist-id.txt" +echo "$GIST_ID" > "$ORIG_PWD/gist-id.txt"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/pr-visual-writeup/scripts/upload_gist.sh around lines 69 - 73, The script currently writes the GIST_ID to "$OLDPWD/gist-id.txt", which is fragile; capture the current working directory into a stable variable (e.g. ORIGINAL_PWD="${PWD}") before the internal cd "$WORK/gist" and then write the id to "$ORIGINAL_PWD/gist-id.txt" instead of "$OLDPWD"; update the echo that outputs the raw URLs if it should reference the same directory variable or leave it relative to the script's working directory, but replace any use of "$OLDPWD" with the saved ORIGINAL_PWD variable and ensure GIST_ID is written to ./gist-id.txt as promised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.agents/skills/pr-visual-writeup/references/gist-upload.md:
- Around line 48-50: The fenced code block containing the URL template should
include a language tag to satisfy markdownlint MD040; update the block that
currently contains
"https://gist.githubusercontent.com/<user>/<gist-id>/raw/<filename>" so the
opening fence is ```text (i.e., add the "text" language identifier) to the
existing fenced code block.
In @.agents/skills/pr-visual-writeup/scripts/upload_gist.sh:
- Around line 42-56: The script currently copies files into "$WORK/gist/" and
lets later cp overwrite files with the same basename; update the loop that
iterates over input dirs (the for d in "$@"; do / for f in "$d"/*; do block) to
detect basename collisions before copying: compute b=$(basename "$f"), check a
tracking map (e.g. an associative array or temporary file keyed by "$b") to see
if b was already seen, and if so print a clear error or warning to stderr
(including both source paths) and exit non‑zero (or continue if you choose to
only warn); otherwise record the basename -> source and proceed to copy and
increment count; ensure messages use >&2 and preserve the existing staged count
behavior.
- Around line 64-66: The current git invocation in upload_gist.sh embeds the PAT
in the command argv via -c credential.helper="!f() { echo username=$USER; echo
password=$TOKEN; }; f", exposing the token to other same-UID processes; change
the flow to avoid passing $TOKEN on the command line by using GIT_ASKPASS (or an
askpass helper read from stdin) instead of the inline credential.helper: create
a small askpass helper that prints the password to stdout when invoked and set
GIT_ASKPASS to its path (or export a process-safe askpass via a here-doc) before
invoking git push, and remove the inline -c credential.helper snippet so the
token is never present in the git argv.
In @.agents/skills/pr-visual-writeup/SKILL.md:
- Around line 129-137: Markdownlint flagged MD040 for the directory tree fenced
code block in .agents/skills/pr-visual-writeup/SKILL.md (the block starting with
"/tmp/pr-<N>-visuals/"); fix it by adding a language identifier to the opening
fence (e.g., change the opening "```" to "```text") so the block becomes a
labeled code block, ensure the closing fence remains present, and apply the same
fix to any other unlabeled fenced blocks in that file.
---
Nitpick comments:
In @.agents/skills/pr-visual-writeup/scripts/detect_dev_server.sh:
- Around line 26-29: The script currently prints "no listening node servers in
$MIN-$MAX" to stderr but returns exit code 0, which is ambiguous for callers;
change the exit behavior so that when the variable "ports" is empty (the if [ -z
"$ports" ] branch) the script exits with a non-zero status (e.g., exit 1) so
consumers can detect "no servers found"; update the echo/exit in that branch
accordingly and ensure any tests or callers expecting success are adjusted to
treat the non-zero exit as "no servers".
In @.agents/skills/pr-visual-writeup/scripts/upload_gist.sh:
- Around line 69-73: The script currently writes the GIST_ID to
"$OLDPWD/gist-id.txt", which is fragile; capture the current working directory
into a stable variable (e.g. ORIGINAL_PWD="${PWD}") before the internal cd
"$WORK/gist" and then write the id to "$ORIGINAL_PWD/gist-id.txt" instead of
"$OLDPWD"; update the echo that outputs the raw URLs if it should reference the
same directory variable or leave it relative to the script's working directory,
but replace any use of "$OLDPWD" with the saved ORIGINAL_PWD variable and ensure
GIST_ID is written to ./gist-id.txt as promised.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a7950e21-8b05-4aaa-bf34-f1ee4a2725fc
📒 Files selected for processing (8)
.agents/skills/pr-visual-writeup/SKILL.md.agents/skills/pr-visual-writeup/references/capture-patterns.md.agents/skills/pr-visual-writeup/references/gist-upload.md.agents/skills/pr-visual-writeup/references/pr-body-template.md.agents/skills/pr-visual-writeup/scripts/convert_clips.sh.agents/skills/pr-visual-writeup/scripts/detect_dev_server.sh.agents/skills/pr-visual-writeup/scripts/upload_gist.sh.claude/skills
| # 3. Stage every file from every input dir. Don't recurse — gists are flat. | ||
| count=0 | ||
| for d in "$@"; do | ||
| for f in "$d"/*; do | ||
| [ -f "$f" ] || continue | ||
| cp "$f" "$WORK/gist/" | ||
| count=$((count+1)) | ||
| done | ||
| done | ||
|
|
||
| if [ $count -eq 0 ]; then | ||
| echo "no files found in input directories" >&2 | ||
| exit 1 | ||
| fi | ||
| echo "staged $count files" >&2 |
There was a problem hiding this comment.
Silent filename collisions across input dirs.
With upload_gist.sh "desc" shots/ clips/, if any basename appears in both dirs the second cp overwrites the first and the collision is invisible in the uploaded gist. gist-upload.md already documents "a later cp wins", but since the whole point of the script is a reproducible asset push, at minimum warn (or fail) on collision rather than silently dropping a screenshot.
Proposed fix
for d in "$@"; do
for f in "$d"/*; do
[ -f "$f" ] || continue
+ base=$(basename "$f")
+ if [ -e "$WORK/gist/$base" ]; then
+ echo "collision: $base already staged (from a prior dir); refusing to overwrite" >&2
+ exit 1
+ fi
cp "$f" "$WORK/gist/"
count=$((count+1))
done
done🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/skills/pr-visual-writeup/scripts/upload_gist.sh around lines 42 -
56, The script currently copies files into "$WORK/gist/" and lets later cp
overwrite files with the same basename; update the loop that iterates over input
dirs (the for d in "$@"; do / for f in "$d"/*; do block) to detect basename
collisions before copying: compute b=$(basename "$f"), check a tracking map
(e.g. an associative array or temporary file keyed by "$b") to see if b was
already seen, and if so print a clear error or warning to stderr (including both
source paths) and exit non‑zero (or continue if you choose to only warn);
otherwise record the basename -> source and proceed to copy and increment count;
ensure messages use >&2 and preserve the existing staged count behavior.
| git -c credential.helper= \ | ||
| -c credential.helper="!f() { echo username=$USER; echo password=$TOKEN; }; f" \ | ||
| push --quiet |
There was a problem hiding this comment.
PAT is briefly visible to other processes on the box.
Passing the token via -c credential.helper="!f() { echo username=$USER; echo password=$TOKEN; }; f" embeds it in the git argv, so any same-UID process that reads /proc/<pid>/cmdline during the push can see it. GIT_ASKPASS (or piping the helper via stdin) avoids putting the secret on the command line. Low risk on a dev workstation; worth noting for CI contexts.
Alternative using GIT_ASKPASS
-git -c credential.helper= \
- -c credential.helper="!f() { echo username=$USER; echo password=$TOKEN; }; f" \
- push --quiet
+ASKPASS=$(mktemp)
+trap 'rm -f "$ASKPASS"' RETURN
+cat > "$ASKPASS" <<EOF
+#!/usr/bin/env bash
+case "\$1" in
+ Username*) echo "$USER" ;;
+ Password*) echo "$TOKEN" ;;
+esac
+EOF
+chmod +x "$ASKPASS"
+GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 git -c credential.helper= push --quiet📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| git -c credential.helper= \ | |
| -c credential.helper="!f() { echo username=$USER; echo password=$TOKEN; }; f" \ | |
| push --quiet | |
| ASKPASS=$(mktemp) | |
| trap 'rm -f "$ASKPASS"' RETURN | |
| cat > "$ASKPASS" <<EOF | |
| #!/usr/bin/env bash | |
| case "\$1" in | |
| Username*) echo "$USER" ;; | |
| Password*) echo "$TOKEN" ;; | |
| esac | |
| EOF | |
| chmod +x "$ASKPASS" | |
| GIT_ASKPASS="$ASKPASS" GIT_TERMINAL_PROMPT=0 git -c credential.helper= push --quiet |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/skills/pr-visual-writeup/scripts/upload_gist.sh around lines 64 -
66, The current git invocation in upload_gist.sh embeds the PAT in the command
argv via -c credential.helper="!f() { echo username=$USER; echo password=$TOKEN;
}; f", exposing the token to other same-UID processes; change the flow to avoid
passing $TOKEN on the command line by using GIT_ASKPASS (or an askpass helper
read from stdin) instead of the inline credential.helper: create a small askpass
helper that prints the password to stdout when invoked and set GIT_ASKPASS to
its path (or export a process-safe askpass via a here-doc) before invoking git
push, and remove the inline -c credential.helper snippet so the token is never
present in the git argv.
| ``` | ||
| /tmp/pr-<N>-visuals/ | ||
| ├── scope.md # phase 1 output | ||
| ├── shots/ # captured PNGs | ||
| ├── clips/ # webm + gif scroll animations | ||
| ├── body.md # composed PR description | ||
| ├── gist-id.txt # for re-pushing if you add more shots later | ||
| └── urls.txt # raw URL per file, for copy-paste | ||
| ``` |
There was a problem hiding this comment.
MD040: add a language to the tree diagram fenced block.
markdownlint flagged this. text renders fine for the directory layout.
-```
+```text
/tmp/pr-<N>-visuals/
├── scope.md # phase 1 output🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 129-129: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.agents/skills/pr-visual-writeup/SKILL.md around lines 129 - 137,
Markdownlint flagged MD040 for the directory tree fenced code block in
.agents/skills/pr-visual-writeup/SKILL.md (the block starting with
"/tmp/pr-<N>-visuals/"); fix it by adding a language identifier to the opening
fence (e.g., change the opening "```" to "```text") so the block becomes a
labeled code block, ensure the closing fence remains present, and apply the same
fix to any other unlabeled fenced blocks in that file.
Greptile SummaryThis PR adds a Confidence Score: 5/5Safe to merge; all findings are P2 style/reliability suggestions in helper scripts that don't affect core skill correctness. Both flagged issues (wait exit-status masking in convert_clips.sh and missing lsof guard in detect_dev_server.sh) are P2 quality improvements. No correctness, data-integrity, or security issues were found. The credential-helper pattern in upload_gist.sh is intentional and documented. scripts/convert_clips.sh and scripts/detect_dev_server.sh have minor robustness gaps worth addressing in a follow-up. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A([User invokes pr-visual-writeup skill]) --> B[Phase 1: Scope\ngh pr diff · detect_dev_server.sh\nwrite /tmp/pr-N-visuals/scope.md]
B --> C[Phase 2: Parallel Capture\nSpawn N subagents in ONE message\none per theme × viewport]
C --> D1[light-standard\n1920×1200]
C --> D2[dark-standard\n1920×1200]
C --> D3[light-wide\n2560×1440]
C --> D4[dark-wide\n2560×1440]
D1 & D2 & D3 & D4 --> E[Phase 3: Process\nconvert_clips.sh\nWebM → GIF in parallel]
E --> F[Phase 4: Upload\nupload_gist.sh\nPAT git-push → raw URLs]
F --> G[Phase 5: Compose + Set\npr-body-template.md\ngh pr edit --body-file]
Prompt To Fix All With AIThis is a comment left during a code review.
Path: .agents/skills/pr-visual-writeup/scripts/convert_clips.sh
Line: 34-35
Comment:
**`wait` silently drops intermediate ffmpeg failures**
In bash, `wait` (no arguments) returns the exit status of the *last* background process, not a logical OR of all of them. If, say, the second-to-last conversion fails due to a corrupt source file but the last one succeeds, `wait` returns 0, the `set -e` guard never fires, and the script prints `done.` despite missing GIFs. Consider collecting exit statuses explicitly:
```suggestion
wait
# Collect per-job exit codes
status=0
for pid in "${pids[@]}"; do
wait "$pid" || status=$?
done
[ $status -ne 0 ] && echo "one or more conversions failed" >&2
echo "done."
```
This requires accumulating PIDs into a `pids=()` array inside the loop (`pids+=($!)`), but gives callers a reliable non-zero exit on partial failure.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: .agents/skills/pr-visual-writeup/scripts/detect_dev_server.sh
Line: 19-27
Comment:
**Missing `lsof` dependency check gives a misleading exit message**
Unlike `convert_clips.sh`'s explicit `ffmpeg` guard, this script pipes `lsof 2>/dev/null` without verifying `lsof` is installed. If `lsof` is absent, bash emits "command not found" to stderr (silenced), the pipeline yields empty output, and the user sees `no listening node servers in 3000-9999` — which looks like a runtime result rather than a missing-tool error.
```suggestion
if ! command -v lsof >/dev/null 2>&1; then
echo "lsof not found. Install with: brew install lsof (or your distro's package manager)" >&2
exit 1
fi
# Collect every node-listened TCP port in the range.
ports=$(lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null \
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "Symlink .claude/skills -> .agents/skills" | Re-trigger Greptile |
| wait | ||
| echo "done." |
There was a problem hiding this comment.
wait silently drops intermediate ffmpeg failures
In bash, wait (no arguments) returns the exit status of the last background process, not a logical OR of all of them. If, say, the second-to-last conversion fails due to a corrupt source file but the last one succeeds, wait returns 0, the set -e guard never fires, and the script prints done. despite missing GIFs. Consider collecting exit statuses explicitly:
| wait | |
| echo "done." | |
| wait | |
| # Collect per-job exit codes | |
| status=0 | |
| for pid in "${pids[@]}"; do | |
| wait "$pid" || status=$? | |
| done | |
| [ $status -ne 0 ] && echo "one or more conversions failed" >&2 | |
| echo "done." |
This requires accumulating PIDs into a pids=() array inside the loop (pids+=($!)), but gives callers a reliable non-zero exit on partial failure.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .agents/skills/pr-visual-writeup/scripts/convert_clips.sh
Line: 34-35
Comment:
**`wait` silently drops intermediate ffmpeg failures**
In bash, `wait` (no arguments) returns the exit status of the *last* background process, not a logical OR of all of them. If, say, the second-to-last conversion fails due to a corrupt source file but the last one succeeds, `wait` returns 0, the `set -e` guard never fires, and the script prints `done.` despite missing GIFs. Consider collecting exit statuses explicitly:
```suggestion
wait
# Collect per-job exit codes
status=0
for pid in "${pids[@]}"; do
wait "$pid" || status=$?
done
[ $status -ne 0 ] && echo "one or more conversions failed" >&2
echo "done."
```
This requires accumulating PIDs into a `pids=()` array inside the loop (`pids+=($!)`), but gives callers a reliable non-zero exit on partial failure.
How can I resolve this? If you propose a fix, please make it concise.| ports=$(lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null \ | ||
| | awk '/^node/ {print $9}' \ | ||
| | grep -oE ':[0-9]+$' \ | ||
| | tr -d ':' \ | ||
| | sort -u \ | ||
| | awk -v min="$MIN" -v max="$MAX" '$1 >= min && $1 <= max') | ||
|
|
||
| if [ -z "$ports" ]; then | ||
| echo "no listening node servers in $MIN-$MAX" >&2 |
There was a problem hiding this comment.
Missing
lsof dependency check gives a misleading exit message
Unlike convert_clips.sh's explicit ffmpeg guard, this script pipes lsof 2>/dev/null without verifying lsof is installed. If lsof is absent, bash emits "command not found" to stderr (silenced), the pipeline yields empty output, and the user sees no listening node servers in 3000-9999 — which looks like a runtime result rather than a missing-tool error.
| ports=$(lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null \ | |
| | awk '/^node/ {print $9}' \ | |
| | grep -oE ':[0-9]+$' \ | |
| | tr -d ':' \ | |
| | sort -u \ | |
| | awk -v min="$MIN" -v max="$MAX" '$1 >= min && $1 <= max') | |
| if [ -z "$ports" ]; then | |
| echo "no listening node servers in $MIN-$MAX" >&2 | |
| if ! command -v lsof >/dev/null 2>&1; then | |
| echo "lsof not found. Install with: brew install lsof (or your distro's package manager)" >&2 | |
| exit 1 | |
| fi | |
| # Collect every node-listened TCP port in the range. | |
| ports=$(lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null \ |
Prompt To Fix With AI
This is a comment left during a code review.
Path: .agents/skills/pr-visual-writeup/scripts/detect_dev_server.sh
Line: 19-27
Comment:
**Missing `lsof` dependency check gives a misleading exit message**
Unlike `convert_clips.sh`'s explicit `ffmpeg` guard, this script pipes `lsof 2>/dev/null` without verifying `lsof` is installed. If `lsof` is absent, bash emits "command not found" to stderr (silenced), the pipeline yields empty output, and the user sees `no listening node servers in 3000-9999` — which looks like a runtime result rather than a missing-tool error.
```suggestion
if ! command -v lsof >/dev/null 2>&1; then
echo "lsof not found. Install with: brew install lsof (or your distro's package manager)" >&2
exit 1
fi
# Collect every node-listened TCP port in the range.
ports=$(lsof -iTCP -sTCP:LISTEN -P -n 2>/dev/null \
```
How can I resolve this? If you propose a fix, please make it concise.
Shared agent skill under the cross-client
.agents/skills/convention so Claude Code, Codex, and other compliant agents can discover it.Summary by CodeRabbit
New Features
Documentation
Chores