@seanyao/roll 2026.514.5 → 2026.515.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## v2026.515.1
4
+
5
+ - **New**: `roll brief` / `roll dream` 生成文档后自动提交推送 — 每次晨报和夜检不再需要手动 commit `[loop]`
6
+ - **New**: 双语 FAQ 指南 — 10 个自治交付常见场景(loop 卡住、PR 冲突、agent 切换、权限问题等),每条含原因和原理,EN + ZH 对照 `[docs]`
7
+ - **New**: 可选的事件驱动 PR 评审模板 — `cp templates/workflows/pr-review-event.yml .github/workflows/`,PR 开即触发 AI 评审,不装也行(loop 每轮兜底) `[pr]`
8
+ - **New**: loop PR inbox 从"分类但空转"升级到"分类+执行" — eligible PR 自动调 AI 评审,stale PR 自动 rebase,fork 和冲突写 ALERT;bot 已评审的 PR 自动让步 `[loop]`
9
+ - **New**: `roll review-pr <number>` — agent-agnostic AI 代码评审,任意 agent(Claude/Kimi/DeepSeek 等)均可评审任意 git 平台的 PR;PR body 加 `[skip-ai-review]` 可跳过 `[pr]`
10
+ - **Fixed**: `roll peer` 终态后 tmux session 不再残留 — AGREE/ESCALATE/UNKNOWN/round≥3 自动 kill,round<3 保留复用 `[peer]`
11
+ - **New**: `loop/cycle-*` 远程僵尸分支兜底 GC — 每轮 cycle 结尾扫描已合入 main 的 `loop/cycle-*` 分支并删除,弥补 PR auto-merge 失败时的清理盲区 `[loop]`
12
+
3
13
  ## v2026.514.5
4
14
 
5
15
  - **Fixed**: 上版 `claude/*` 临时分支清理意外失效 — 现已恢复 `[loop]`
package/README.md CHANGED
@@ -64,6 +64,7 @@ roll loop on # optional: let the agent work unattended
64
64
  | Peer (cross-agent review) | [guide/en/peer.md](docs/guide/en/peer.md) | [guide/zh/peer.md](docs/guide/zh/peer.md) |
65
65
  | Configuration (env vars) | [guide/en/configuration.md](docs/guide/en/configuration.md) | [guide/zh/configuration.md](docs/guide/zh/configuration.md) |
66
66
  | Skill selection guide | [guide/en/skills.md](docs/guide/en/skills.md) | [guide/zh/skills.md](docs/guide/zh/skills.md) |
67
+ | FAQ (troubleshooting) | [guide/en/faq.md](docs/guide/en/faq.md) | [guide/zh/faq.md](docs/guide/zh/faq.md) |
67
68
  | Domain model (DDD) | [domain/context-map.md](docs/domain/context-map.md) | — |
68
69
  | Engineering common sense | [practices/engineering-common-sense.md](docs/practices/engineering-common-sense.md) | — |
69
70
 
package/bin/roll CHANGED
@@ -4,7 +4,7 @@ set -euo pipefail
4
4
  # Roll — AI Agent Convention Manager
5
5
  # Single source of truth for how all AI coding agents behave.
6
6
 
7
- VERSION="2026.514.5"
7
+ VERSION="2026.515.1"
8
8
  ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
9
9
  ROLL_CONFIG="${ROLL_HOME}/config.yaml"
10
10
  ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
@@ -614,6 +614,7 @@ cmd_setup() {
614
614
 
615
615
  echo ""
616
616
  _print_pr_pipeline_hint
617
+ _print_pr_event_hint
617
618
  }
618
619
 
619
620
  # ─── PR pipeline hint ────────────────────────────────────────────────────────
@@ -643,6 +644,24 @@ _print_pr_pipeline_hint() {
643
644
  HINT
644
645
  }
645
646
 
647
+ _print_pr_event_hint() {
648
+ cat <<'HINT'
649
+
650
+ Optional — enable event-driven PR review (seconds-fast, GitHub only).
651
+ 可选 —— 启用事件驱动 PR 评审(秒级响应,仅限 GitHub)。
652
+
653
+ Without this, Roll reviews PRs each loop cycle (~1h). With it,
654
+ contributors get AI feedback on PR open/update immediately.
655
+ 不安装也行 — loop 每轮会兜底评审。安装后 PR 一开即触发 AI 评审。
656
+
657
+ cp templates/workflows/pr-review-event.yml .github/workflows/
658
+
659
+ Then set the API key secret for your configured agent in GitHub repo settings.
660
+ 然后在 GitHub 仓库设置中添加你配置的 agent 对应的 API key secret。
661
+
662
+ HINT
663
+ }
664
+
646
665
  # ═══════════════════════════════════════════════════════════════════════════════
647
666
  # COMMAND: update
648
667
  # Thin wrapper: upgrade the npm-installed package, then re-sync via setup.
@@ -1777,6 +1796,16 @@ cmd_peer() {
1777
1796
  echo ""
1778
1797
  info "Log: $log_file"
1779
1798
 
1799
+ local _should_kill=true
1800
+ case "$resolution" in
1801
+ REFINE|OBJECT) [[ "$round" -lt 3 ]] && _should_kill=false ;;
1802
+ esac
1803
+ if [[ "$_should_kill" == "true" ]] && [[ -n "$peer_session" ]] \
1804
+ && command -v tmux >/dev/null 2>&1 \
1805
+ && tmux has-session -t "$peer_session" 2>/dev/null; then
1806
+ tmux kill-session -t "$peer_session" 2>/dev/null || true
1807
+ fi
1808
+
1780
1809
  case "$resolution" in
1781
1810
  AGREE) exit 0 ;;
1782
1811
  REFINE|OBJECT) exit 2 ;;
@@ -1898,6 +1927,17 @@ _skill_content() {
1898
1927
  awk 'NR==1 && /^---$/{skip=1;next} skip && /^---$/{skip=0;next} !skip{print}' "$1"
1899
1928
  }
1900
1929
 
1930
+ _parse_review_verdict() {
1931
+ local output="$1"
1932
+ local line
1933
+ line=$(echo "$output" | grep -o '<!--VERDICT:[^>]*-->' | tail -1)
1934
+ [ -n "$line" ] || return 0
1935
+ local type reason
1936
+ type=$(echo "$line" | sed -E 's/<!--VERDICT:([A-Z_]+)(:.*)?-->/\1/')
1937
+ reason=$(echo "$line" | sed -E 's/<!--VERDICT:[A-Z_]+:?//; s/-->//' | sed 's/^ *//')
1938
+ echo "${type}${reason:+:${reason}}"
1939
+ }
1940
+
1901
1941
  _agent_run_skill() {
1902
1942
  local skill="$1"
1903
1943
  local agent; agent=$(_project_agent)
@@ -1915,6 +1955,102 @@ _agent_run_skill() {
1915
1955
  esac
1916
1956
  }
1917
1957
 
1958
+ cmd_review_pr() {
1959
+ local pr_number="${1:-}"
1960
+ [ -n "$pr_number" ] || { err "Usage: roll review-pr <number>"; return 1; }
1961
+
1962
+ local slug; slug=$(_gh_repo_slug) || { err "Not a GitHub repo — review-pr requires GitHub remote"; return 1; }
1963
+
1964
+ local pr_json
1965
+ pr_json=$(gh -R "$slug" pr view "$pr_number" --json title,body,diff 2>&1) \
1966
+ || { err "gh pr view failed: ${pr_json}"; return 1; }
1967
+
1968
+ local title body diff
1969
+ title=$(echo "$pr_json" | jq -r '.title // ""')
1970
+ body=$(echo "$pr_json" | jq -r '.body // ""')
1971
+ diff=$(echo "$pr_json" | jq -r '.diff // ""')
1972
+
1973
+ if echo "$body" | grep -qF '[skip-ai-review]'; then
1974
+ gh -R "$slug" pr review "$pr_number" --approve -b "Auto-approved: [skip-ai-review] detected" 2>/dev/null || true
1975
+ info "PR #${pr_number}: [skip-ai-review] — auto-approved"
1976
+ return 0
1977
+ fi
1978
+
1979
+ local template="${ROLL_PKG_DIR}/skills/roll-review-pr/SKILL.md"
1980
+ [ -f "$template" ] || { err "Skill template not found: ${template}"; return 1; }
1981
+
1982
+ local content; content=$(_skill_content "$template")
1983
+
1984
+ local tmp; tmp=$(mktemp)
1985
+ # shellcheck disable=SC2064
1986
+ trap "rm -f '$tmp'" EXIT
1987
+
1988
+ echo "$content" > "$tmp"
1989
+ sed -i '' "s|{{PR_TITLE}}|${title}|g" "$tmp" 2>/dev/null \
1990
+ || sed -i "s|{{PR_TITLE}}|${title}|g" "$tmp"
1991
+
1992
+ local body_escaped; body_escaped=$(printf '%s' "$body" | sed 's/[&/\]/\\&/g; s/$/\\/' | sed '$ s/\\$//')
1993
+ sed -i '' "s|{{PR_BODY}}|${body_escaped}|g" "$tmp" 2>/dev/null \
1994
+ || sed -i "s|{{PR_BODY}}|${body_escaped}|g" "$tmp"
1995
+
1996
+ local diff_truncated
1997
+ diff_truncated=$(echo "$diff" | head -500)
1998
+ local diff_file; diff_file=$(mktemp)
1999
+ echo "$diff_truncated" > "$diff_file"
2000
+ awk -v f="$diff_file" '{
2001
+ if (index($0, "{{PR_DIFF}}")) {
2002
+ while ((getline line < f) > 0) print line
2003
+ close(f)
2004
+ } else print
2005
+ }' "$tmp" > "${tmp}.out" && mv "${tmp}.out" "$tmp"
2006
+ rm -f "$diff_file"
2007
+
2008
+ local prompt; prompt=$(cat "$tmp")
2009
+ rm -f "$tmp"
2010
+ trap - EXIT
2011
+
2012
+ local agent; agent=$(_project_agent)
2013
+ local output
2014
+ info "Reviewing PR #${pr_number} with ${agent}..."
2015
+ case "$agent" in
2016
+ claude) output=$(claude -p --output-format text "$prompt" 2>/dev/null) ;;
2017
+ kimi) output=$(kimi --quiet -p "$prompt" 2>/dev/null) ;;
2018
+ deepseek) output=$(deepseek "$prompt" 2>/dev/null) ;;
2019
+ pi) output=$(pi -p "$prompt" 2>/dev/null) ;;
2020
+ codex) output=$(codex exec "$prompt" 2>/dev/null) ;;
2021
+ opencode) output=$(opencode run "$prompt" 2>/dev/null) ;;
2022
+ *) err "Unknown agent '${agent}'"; return 1 ;;
2023
+ esac
2024
+
2025
+ echo "$output"
2026
+
2027
+ local verdict; verdict=$(_parse_review_verdict "$output")
2028
+ local vtype; vtype="${verdict%%:*}"
2029
+ local vreason; vreason="${verdict#*:}"
2030
+ [ "$vreason" = "$vtype" ] && vreason=""
2031
+
2032
+ case "$vtype" in
2033
+ APPROVE)
2034
+ gh -R "$slug" pr review "$pr_number" --approve -b "AI review: approved" 2>/dev/null || true
2035
+ info "PR #${pr_number}: APPROVED"
2036
+ ;;
2037
+ REQUEST_CHANGES)
2038
+ gh -R "$slug" pr review "$pr_number" --request-changes -b "${vreason:-AI review requested changes}" 2>/dev/null || true
2039
+ info "PR #${pr_number}: REQUEST_CHANGES — ${vreason}"
2040
+ ;;
2041
+ UNCERTAIN)
2042
+ warn "PR #${pr_number}: UNCERTAIN — ${vreason}"
2043
+ local alert_file="${ROLL_LOOP_DIR:-${HOME}/.shared/roll/loop}/ALERT.md"
2044
+ mkdir -p "$(dirname "$alert_file")"
2045
+ printf '[%s] PR #%s: AI review UNCERTAIN — %s\n' \
2046
+ "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$pr_number" "$vreason" >> "$alert_file"
2047
+ ;;
2048
+ *)
2049
+ warn "PR #${pr_number}: no verdict parsed from agent output"
2050
+ ;;
2051
+ esac
2052
+ }
2053
+
1918
2054
  cmd_agent() {
1919
2055
  local subcmd="${1:-}"; shift || true
1920
2056
  case "$subcmd" in
@@ -1929,8 +2065,12 @@ cmd_agent() {
1929
2065
  fi
1930
2066
  ok "Agent set to ${name} for this project 当前项目 agent 已设为 ${name}"
1931
2067
  local project_path; project_path=$(pwd -P)
1932
- crontab -l 2>/dev/null | grep -q "roll-loop:${project_path}" && \
1933
- info "Takes effect on next scheduled run — or: roll loop now"
2068
+ local slug; slug=$(_project_slug "$project_path")
2069
+ local runner="${_SHARED_ROOT}/loop/run-${slug}.sh"
2070
+ if [[ -f "$runner" ]]; then
2071
+ _install_launchd_plists "$project_path" >/dev/null
2072
+ ok "Loop runner scripts regenerated for new agent 已为新 agent 重新生成 loop 脚本"
2073
+ fi
1934
2074
  ;;
1935
2075
  list)
1936
2076
  echo ""; echo " Available agents 可用 agent:"; echo ""
@@ -2192,6 +2332,9 @@ if [ "\$_USE_WORKTREE" = "1" ]; then
2192
2332
  echo "[loop] cycle \${CYCLE_ID}: claude failed (exit \$_exit); worktree preserved at \$WT"
2193
2333
  fi
2194
2334
  fi
2335
+
2336
+ # US-AUTO-040: fallback GC — delete remote loop/cycle-* branches already merged to main.
2337
+ _loop_cleanup_stale_cycle_branches "${project_path}" || true
2195
2338
  INNER
2196
2339
  chmod +x "$inner_path"
2197
2340
 
@@ -2227,7 +2370,9 @@ fi
2227
2370
  echo "\$\$" > "\$LOCK"
2228
2371
  trap 'rm -f "\$LOCK"' EXIT
2229
2372
  if command -v tmux >/dev/null 2>&1; then
2230
- tmux kill-session -t "\$SESSION" 2>/dev/null || true
2373
+ tmux list-sessions -F "#{session_name}" 2>/dev/null | grep "^roll-loop-" | while read _s; do
2374
+ tmux kill-session -t "\$_s" 2>/dev/null || true
2375
+ done
2231
2376
  tmux new-session -d -s "\$SESSION" -x 200 -y 50 "bash \"\$INNER_SCRIPT\""
2232
2377
  tmux pipe-pane -t "\$SESSION" "cat >> \"\$LOG\""
2233
2378
  # Auto-attach popup: when not muted, spawn a Terminal window attached to the
@@ -3319,6 +3464,55 @@ _loop_pr_state_write() {
3319
3464
  ' "$state" > "$tmp" && mv "$tmp" "$state"
3320
3465
  }
3321
3466
 
3467
+ # _loop_pr_review_external <pr_number>
3468
+ # Calls cmd_review_pr (US-PR-001) to run AI review on an eligible external PR.
3469
+ # Lenient: errors are logged but do not fail the loop.
3470
+ _loop_pr_review_external() {
3471
+ local pr="$1"
3472
+ [ -n "$pr" ] || return 0
3473
+ cmd_review_pr "$pr" 2>&1 || {
3474
+ warn "review-pr failed for PR #${pr} (non-fatal)"
3475
+ return 0
3476
+ }
3477
+ }
3478
+
3479
+ # _loop_pr_rebase_stale <pr_number> <head_ref>
3480
+ # Attempts to rebase a stale PR onto origin/main and push.
3481
+ # Fork PRs are skipped (no write access). Conflicts write ALERT.
3482
+ _loop_pr_rebase_stale() {
3483
+ local pr="$1" head_ref="$2"
3484
+ [ -n "$pr" ] && [ -n "$head_ref" ] || return 0
3485
+
3486
+ local slug; slug=$(_gh_repo_slug 2>/dev/null) || return 0
3487
+
3488
+ local pr_json
3489
+ pr_json=$(gh -R "$slug" pr view "$pr" --json headRepository,headRepositoryOwner,isCrossRepository 2>/dev/null) || return 0
3490
+ local is_fork
3491
+ is_fork=$(echo "$pr_json" | jq -r '.isCrossRepository // false' 2>/dev/null)
3492
+ if [ "$is_fork" = "true" ]; then
3493
+ local alert="${_LOOP_ALERT:-${HOME}/.shared/roll/loop/ALERT.md}"
3494
+ mkdir -p "$(dirname "$alert")" 2>/dev/null || true
3495
+ printf '[%s] PR #%s: fork PR — cannot rebase (no write access)\n' \
3496
+ "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$pr" >> "$alert"
3497
+ return 0
3498
+ fi
3499
+
3500
+ git fetch origin "$head_ref" 2>/dev/null || return 0
3501
+ if git checkout "$head_ref" 2>/dev/null \
3502
+ && git rebase origin/main 2>/dev/null \
3503
+ && git push origin "$head_ref" 2>/dev/null; then
3504
+ info "PR #${pr}: rebased ${head_ref} onto origin/main"
3505
+ else
3506
+ git rebase --abort 2>/dev/null || true
3507
+ git checkout - 2>/dev/null || true
3508
+ local alert="${_LOOP_ALERT:-${HOME}/.shared/roll/loop/ALERT.md}"
3509
+ mkdir -p "$(dirname "$alert")" 2>/dev/null || true
3510
+ printf '[%s] PR #%s: rebase conflict on %s — please rebase manually\n' \
3511
+ "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$pr" "$head_ref" >> "$alert"
3512
+ fi
3513
+ return 0
3514
+ }
3515
+
3322
3516
  # _loop_pr_inbox
3323
3517
  # Walks open PRs and routes each by classification.
3324
3518
  # Lenient on gh unavailability — returns 0 so the loop continues to BACKLOG.
@@ -3348,10 +3542,13 @@ _loop_pr_inbox() {
3348
3542
  --json reviews,mergeStateStatus,statusCheckRollup \
3349
3543
  2>/dev/null) || { i=$((i + 1)); continue; }
3350
3544
 
3351
- local human_review ci_state mergeable
3545
+ local human_review ci_state mergeable bot_review
3352
3546
  human_review=$(echo "$view_json" | jq -r '
3353
3547
  [.reviews[]? | select(.authorAssociation != "BOT" and .authorAssociation != "APP")]
3354
3548
  | last // {} | .state // ""' 2>/dev/null)
3549
+ bot_review=$(echo "$view_json" | jq -r '
3550
+ [.reviews[]? | select(.authorAssociation == "BOT" or .authorAssociation == "APP")]
3551
+ | last // {} | .state // ""' 2>/dev/null)
3355
3552
  mergeable=$(echo "$view_json" | jq -r '.mergeStateStatus // ""' 2>/dev/null)
3356
3553
  ci_state=$(echo "$view_json" | jq -r '
3357
3554
  if (.statusCheckRollup | length) == 0 then ""
@@ -3359,6 +3556,17 @@ _loop_pr_inbox() {
3359
3556
  elif all(.statusCheckRollup[]?; .conclusion == "SUCCESS" or .conclusion == "SKIPPED") then "success"
3360
3557
  else "pending" end' 2>/dev/null)
3361
3558
 
3559
+ # Bot review gate: if a GHA workflow already handled this PR, defer to it.
3560
+ if [ "$bot_review" = "APPROVED" ]; then
3561
+ i=$((i + 1)); continue
3562
+ elif [ "$bot_review" = "CHANGES_REQUESTED" ]; then
3563
+ local alert="${_LOOP_ALERT:-${HOME}/.shared/roll/loop/ALERT.md}"
3564
+ mkdir -p "$(dirname "$alert")" 2>/dev/null || true
3565
+ printf '[%s] PR #%s: bot review CHANGES_REQUESTED — loop PR rejected by GHA reviewer\n' \
3566
+ "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$num" >> "$alert"
3567
+ i=$((i + 1)); continue
3568
+ fi
3569
+
3362
3570
  local verdict
3363
3571
  verdict=$(_loop_pr_classify "$head_ref" "$human_review" "$ci_state" "$mergeable")
3364
3572
 
@@ -3368,15 +3576,10 @@ _loop_pr_inbox() {
3368
3576
  ;;
3369
3577
  stale)
3370
3578
  _loop_pr_rebase_circuit "$num" || true
3371
- # Actual rebase work delegated to _loop_pr_rebase_stale when present
3372
- # (kept out of this cycle to avoid touching git remote in tests).
3373
- command -v _loop_pr_rebase_stale >/dev/null 2>&1 \
3374
- && _loop_pr_rebase_stale "$num" "$head_ref" || true
3579
+ _loop_pr_rebase_stale "$num" "$head_ref" || true
3375
3580
  ;;
3376
3581
  eligible)
3377
- # Hand off to review (US-AUTO-035 supplies the actual decision).
3378
- command -v _loop_pr_review_external >/dev/null 2>&1 \
3379
- && _loop_pr_review_external "$num" || true
3582
+ _loop_pr_review_external "$num" || true
3380
3583
  ;;
3381
3584
  esac
3382
3585
 
@@ -3715,6 +3918,28 @@ _claude_cleanup_stale_worktrees() {
3715
3918
  return 0
3716
3919
  }
3717
3920
 
3921
+ _loop_cleanup_stale_cycle_branches() {
3922
+ local project_path="${1:-.}"
3923
+ local url; url=$(git -C "$project_path" remote get-url origin 2>/dev/null) || return 0
3924
+ [[ "$url" == *github.com* ]] || return 0
3925
+
3926
+ local branches
3927
+ branches=$(git -C "$project_path" ls-remote --heads origin 'refs/heads/loop/cycle-*' 2>/dev/null \
3928
+ | awk '{print $2}' | sed 's|^refs/heads/||')
3929
+ [ -z "$branches" ] && return 0
3930
+
3931
+ while IFS= read -r branch; do
3932
+ [ -z "$branch" ] && continue
3933
+ if ! git -C "$project_path" merge-base --is-ancestor "$branch" origin/main 2>/dev/null; then
3934
+ continue
3935
+ fi
3936
+ if git -C "$project_path" push origin --delete "$branch" 2>/dev/null; then
3937
+ echo "[loop] deleted stale cycle branch: $branch"
3938
+ fi
3939
+ done <<< "$branches"
3940
+ return 0
3941
+ }
3942
+
3718
3943
  # US-AUTO-033: publish a loop cycle branch as a GitHub PR with auto-merge.
3719
3944
  #
3720
3945
  # _loop_publish_pr <branch> [title]
@@ -4719,6 +4944,7 @@ usage() {
4719
4944
  echo " agent [use <name>|list] [Config] Per-project agent selection 切换项目 agent"
4720
4945
  echo " release [Publish] Sync changelog + version bump + npm publish 同步日志并发版"
4721
4946
  echo " ci [--wait] [CI] Show or wait for current commit's CI status 查看/等待 CI 状态"
4947
+ echo " review-pr <number> [PR Review] AI-powered code review for a PR AI 代码评审"
4722
4948
  echo ""
4723
4949
  echo "Examples / 示例:"
4724
4950
  echo " roll setup # New machine: first-time install 新机器首次安装"
@@ -4750,6 +4976,7 @@ main() {
4750
4976
  agent) cmd_agent "$@" ;;
4751
4977
  release) cmd_release "$@" ;;
4752
4978
  ci) cmd_ci "$@" ;;
4979
+ review-pr) cmd_review_pr "$@" ;;
4753
4980
  version|--version|-v) echo "roll v${VERSION}" ;;
4754
4981
  help|--help|-h) usage ;;
4755
4982
  "") [[ -f "BACKLOG.md" ]] && _dashboard || { usage; _show_changelog; } ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanyao/roll",
3
- "version": "2026.514.5",
3
+ "version": "2026.515.1",
4
4
  "description": "Roll — Roll out features with AI agents",
5
5
  "scripts": {
6
6
  "test": "bash tests/run.sh"
@@ -275,6 +275,7 @@ git add BACKLOG.md docs/dream/YYYY-MM-DD.md
275
275
  git commit -m "chore: dream scan YYYY-MM-DD — {N} REFACTOR entries"
276
276
  # 无发现时:
277
277
  git commit -m "chore: dream scan YYYY-MM-DD — no findings"
278
+ git push origin main
278
279
  ```
279
280
 
280
281
  - BACKLOG.md 和 dream 日志必须在**同一个 commit** 里入库,避免出现"REFACTOR 已加但日志找不到"或反过来的撕裂状态
@@ -167,6 +167,7 @@ A simple heuristic — not a gate, just a signal for the human:
167
167
  ```bash
168
168
  git add docs/briefs/YYYY-MM-DD-NN.md
169
169
  git commit -m "docs: roll-brief YYYY-MM-DD-NN — {触发原因}"
170
+ git push origin main
170
171
  ```
171
172
 
172
173
  - 触发原因来自调用上下文(Feature 完成 / 每日 / 手动 / `--feature` / `--since`),用一句话填入
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: roll-review-pr
3
+ license: MIT
4
+ allowed-tools: "Read"
5
+ description: "Agent-agnostic PR review skill. Reviews a pull request diff and emits a structured 3-state verdict (APPROVE / REQUEST_CHANGES / UNCERTAIN). Used by `roll review-pr` and `_loop_pr_review_external`."
6
+ ---
7
+
8
+ # PR Review
9
+
10
+ > Follows the Architecture Constraints, Development Discipline, and Engineering
11
+ > Common Sense defined in the project AGENTS.md.
12
+
13
+ You are reviewing a pull request. Your job is to assess code quality,
14
+ correctness, and adherence to project conventions.
15
+
16
+ ## Context
17
+
18
+ **PR Title:** {{PR_TITLE}}
19
+
20
+ **PR Body:**
21
+ {{PR_BODY}}
22
+
23
+ ## Diff
24
+
25
+ ```diff
26
+ {{PR_DIFF}}
27
+ ```
28
+
29
+ ## Review Instructions
30
+
31
+ 1. Read the diff carefully. Focus on:
32
+ - Correctness: logic errors, off-by-one, unhandled edge cases
33
+ - Security: injection, secrets exposure, unsafe operations
34
+ - Conventions: naming, structure, test coverage (as described in AGENTS.md)
35
+ - Scope: changes should match what the PR title/body claims
36
+
37
+ 2. Write your analysis in free text (2-10 sentences). Be specific — cite file
38
+ names and line numbers when pointing out issues.
39
+
40
+ 3. End your response with exactly ONE verdict footer on its own line:
41
+
42
+ - If the code is acceptable:
43
+ `<!--VERDICT:APPROVE-->`
44
+
45
+ - If changes are needed (cite the most important issue):
46
+ `<!--VERDICT:REQUEST_CHANGES:one-line reason-->`
47
+
48
+ - If you cannot confidently judge (e.g., missing context, domain-specific logic):
49
+ `<!--VERDICT:UNCERTAIN:one-line reason-->`
50
+
51
+ ## Rules
52
+
53
+ - The verdict footer MUST appear on the last non-empty line of your response.
54
+ - Choose exactly one verdict. Do not combine them.
55
+ - REQUEST_CHANGES is for real issues — not style nitpicks or personal preferences.
56
+ - When in doubt between APPROVE and UNCERTAIN, prefer UNCERTAIN.
57
+ - If the PR body contains `[skip-ai-review]`, immediately output
58
+ `<!--VERDICT:APPROVE-->` with no analysis.