@seanyao/roll 2026.512.1 → 2026.512.5

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/roll +181 -33
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## v2026.512.5
4
+ - **Fixed**: loop 遇到 API 错误时自动重试,不再直接中断
5
+
6
+ ## v2026.512.3
7
+ - **Added**: BACKLOG 支持 block / defer / unblock 状态管理 — 标记卡住的任务不再占队列
8
+ - **Fixed**: 自动弹窗现在能识别 Ghostty 和 iTerm2,不再强制弹出 Terminal.app
9
+ - **Fixed**: loop 检测到上一轮还在跑时自动跳过,不重复启动
10
+
3
11
  ## v2026.512.1
4
12
  - **Added**: `roll loop pause` / `roll loop resume` — 想自己上手时一键暂停 loop,做完再恢复
5
13
  - **Added**: `roll status` 新增所有项目的 loop 状态一览 — 调度时间、待办数、是否在跑
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.512.1"
7
+ VERSION="2026.512.5"
8
8
  ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
9
9
  ROLL_CONFIG="${ROLL_HOME}/config.yaml"
10
10
  ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
@@ -1961,13 +1961,22 @@ _write_loop_runner_script() {
1961
1961
  local cmd_verbose="${cmd/claude -p/claude -p --verbose --output-format stream-json}"
1962
1962
  cat > "$inner_path" << INNER
1963
1963
  #!/bin/bash -l
1964
+ set -o pipefail
1964
1965
  export PATH="/opt/homebrew/bin:\$PATH"
1965
1966
  FMT="${fmt_script}"
1966
- if [ -f "\$FMT" ]; then
1967
- ( cd "${project_path}" && ${cmd_verbose} ) | python3 "\$FMT"
1968
- else
1969
- cd "${project_path}" && ${cmd_verbose}
1970
- fi
1967
+ for _attempt in 1 2 3; do
1968
+ if [ -f "\$FMT" ]; then
1969
+ ( cd "${project_path}" && ${cmd_verbose} ) | python3 "\$FMT"
1970
+ else
1971
+ ( cd "${project_path}" && ${cmd_verbose} )
1972
+ fi
1973
+ _exit=\$?
1974
+ [ "\$_exit" -eq 0 ] && break
1975
+ if [ "\$_attempt" -lt 3 ]; then
1976
+ echo "[loop] claude exited \$_exit (attempt \$_attempt/3) — retrying in 30s..."
1977
+ sleep 30
1978
+ fi
1979
+ done
1971
1980
  INNER
1972
1981
  chmod +x "$inner_path"
1973
1982
 
@@ -1994,6 +2003,13 @@ if [ -f "\$LOCK" ]; then
1994
2003
  fi
1995
2004
  rm -f "\$LOCK"
1996
2005
  fi
2006
+ # Guard against stale-LOCK case: if the tmux session is already alive,
2007
+ # a previous runner's LOCK was removed (e.g. parent terminal closed) but
2008
+ # the work is still in progress — don't kill it.
2009
+ if command -v tmux >/dev/null 2>&1 && tmux has-session -t "\$SESSION" 2>/dev/null; then
2010
+ echo "[\$(date '+%Y-%m-%dT%H:%M:%S%z')] tmux session \$SESSION still active, skipping" >> "\$LOG"
2011
+ exit 0
2012
+ fi
1997
2013
  echo "\$\$" > "\$LOCK"
1998
2014
  trap 'rm -f "\$LOCK"' EXIT
1999
2015
  if command -v tmux >/dev/null 2>&1; then
@@ -2004,29 +2020,29 @@ if command -v tmux >/dev/null 2>&1; then
2004
2020
  # tmux session so the user can watch the loop work in real time. Best-effort
2005
2021
  # focus retention: capture the current frontmost app and re-activate after.
2006
2022
  if [ ! -f "\$HOME/.shared/roll/mute" ] && [ "\$(uname)" = "Darwin" ]; then
2007
- case "${terminal_pref}" in
2008
- ghostty|Ghostty)
2009
- (open -na Ghostty.app --args -e tmux attach -t \$SESSION >/dev/null 2>&1) &
2010
- ;;
2011
- iTerm2|iTerm)
2012
- (osascript \\
2013
- -e 'tell application "System Events" to set _prev to name of first application process whose frontmost is true' \\
2014
- -e "tell application \"iTerm2\" to create window with default profile command \"tmux attach -t \$SESSION\"" \\
2015
- -e 'delay 0.3' \\
2016
- -e 'try' \\
2017
- -e 'tell application _prev to activate' \\
2018
- -e 'end try' >/dev/null 2>&1) &
2019
- ;;
2020
- *)
2021
- command -v osascript >/dev/null 2>&1 && (osascript \\
2022
- -e 'tell application "System Events" to set _prev to name of first application process whose frontmost is true' \\
2023
- -e "tell application \"Terminal\" to do script \"tmux attach -t \$SESSION\"" \\
2024
- -e 'delay 0.3' \\
2025
- -e 'try' \\
2026
- -e 'tell application _prev to activate' \\
2027
- -e 'end try' >/dev/null 2>&1) &
2028
- ;;
2029
- esac
2023
+ # Runtime terminal detection: try preferred first, fallback through installed apps.
2024
+ # open -na returns non-zero when app not found, so || chain works as fallback.
2025
+ _PREF="${terminal_pref}"
2026
+ _launched=0
2027
+ if [ "\$_PREF" = "ghostty" ] || [ "\$_PREF" = "Ghostty" ]; then
2028
+ open -na Ghostty.app --args -e tmux attach -t \$SESSION >/dev/null 2>&1 && _launched=1 || true
2029
+ fi
2030
+ if [ "\$_launched" -eq 0 ] && { [ "\$_PREF" = "iTerm2" ] || [ "\$_PREF" = "iTerm" ] || [ -d "/Applications/iTerm.app" ]; }; then
2031
+ osascript \\
2032
+ -e 'tell application "System Events" to set _prev to name of first application process whose frontmost is true' \\
2033
+ -e "tell application \"iTerm2\" to create window with default profile command \"tmux attach -t \$SESSION\"" \\
2034
+ -e 'delay 0.3' -e 'try' -e 'tell application _prev to activate' -e 'end try' >/dev/null 2>&1 \\
2035
+ && _launched=1 || true
2036
+ fi
2037
+ if [ "\$_launched" -eq 0 ] && [ -d "/Applications/Ghostty.app" ]; then
2038
+ open -na Ghostty.app --args -e tmux attach -t \$SESSION >/dev/null 2>&1 && _launched=1 || true
2039
+ fi
2040
+ if [ "\$_launched" -eq 0 ] && command -v osascript >/dev/null 2>&1; then
2041
+ osascript \\
2042
+ -e 'tell application "System Events" to set _prev to name of first application process whose frontmost is true' \\
2043
+ -e "tell application \"Terminal\" to do script \"tmux attach -t \$SESSION\"" \\
2044
+ -e 'delay 0.3' -e 'try' -e 'tell application _prev to activate' -e 'end try' >/dev/null 2>&1 || true
2045
+ fi
2030
2046
  fi
2031
2047
  while tmux has-session -t "\$SESSION" 2>/dev/null; do sleep 5; done
2032
2048
  else
@@ -2799,9 +2815,45 @@ cmd_release() {
2799
2815
  }
2800
2816
 
2801
2817
  # ═══════════════════════════════════════════════════════════════════════════════
2802
- # BACKLOG — show pending tasks
2818
+ # BACKLOG — show pending tasks / manage status
2803
2819
  # ═══════════════════════════════════════════════════════════════════════════════
2804
2820
 
2821
+ # Update status of all BACKLOG rows whose ID field contains <pattern> (case-insensitive).
2822
+ # Uses Python for reliable emoji/Unicode handling.
2823
+ _backlog_set_status() {
2824
+ local pattern="$1"
2825
+ local new_status="$2"
2826
+ local backlog="BACKLOG.md"
2827
+ python3 -c "
2828
+ import sys, re
2829
+ pattern, new_status, filename = sys.argv[1], sys.argv[2], sys.argv[3]
2830
+ lines = open(filename, encoding='utf-8').readlines()
2831
+ count = 0
2832
+ out = []
2833
+ for line in lines:
2834
+ if line.startswith('|') and line.count('|') >= 4:
2835
+ parts = line.split('|')
2836
+ if len(parts) >= 5:
2837
+ id_field = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', parts[1]).strip()
2838
+ if pattern.upper() in id_field.upper():
2839
+ parts[-2] = ' ' + new_status + ' '
2840
+ line = '|'.join(parts)
2841
+ count += 1
2842
+ out.append(line)
2843
+ open(filename, 'w', encoding='utf-8').writelines(out)
2844
+ print(count)
2845
+ " "$pattern" "$new_status" "$backlog"
2846
+ }
2847
+
2848
+ _backlog_extract_id() {
2849
+ local line="$1"
2850
+ if echo "$line" | grep -q '\[US-'; then
2851
+ echo "$line" | sed 's/.*\[\(US-[^]]*\)\].*/\1/'
2852
+ else
2853
+ echo "$line" | awk -F'|' '{print $2}' | tr -d ' '
2854
+ fi
2855
+ }
2856
+
2805
2857
  cmd_backlog() {
2806
2858
  local backlog="BACKLOG.md"
2807
2859
  if [[ ! -f "$backlog" ]]; then
@@ -2809,8 +2861,38 @@ cmd_backlog() {
2809
2861
  return 1
2810
2862
  fi
2811
2863
 
2812
- local us_items fix_items refactor_items idea_items total=0
2864
+ local subcmd="${1:-}"
2865
+
2866
+ # ── Status management subcommands ─────────────────────────────────────────
2867
+ case "$subcmd" in
2868
+ block|defer|unblock|promote)
2869
+ local pattern="${2:-}"
2870
+ local reason="${3:-}"
2871
+ if [[ -z "$pattern" ]]; then
2872
+ err "Usage: roll backlog $subcmd <pattern> [reason] 用法: roll backlog $subcmd <匹配模式> [原因]"
2873
+ return 1
2874
+ fi
2875
+ local new_status
2876
+ case "$subcmd" in
2877
+ block) new_status="🔒 Blocked${reason:+ [${reason}]}" ;;
2878
+ defer) new_status="⏸ Deferred${reason:+ [${reason}]}" ;;
2879
+ unblock|promote) new_status="📋 Todo" ;;
2880
+ esac
2881
+ local count
2882
+ count=$(_backlog_set_status "$pattern" "$new_status")
2883
+ if [[ "$count" -eq 0 ]]; then
2884
+ echo " No items matched: $pattern 未找到匹配项: $pattern"
2885
+ else
2886
+ echo " Updated ${count} item(s) → ${new_status} 已更新 ${count} 条目"
2887
+ fi
2888
+ return
2889
+ ;;
2890
+ esac
2891
+
2892
+ # ── Display mode ──────────────────────────────────────────────────────────
2893
+ local DIM='\033[2m'
2813
2894
 
2895
+ local us_items fix_items refactor_items idea_items total=0
2814
2896
  us_items=$(grep -E '^\| \[US-' "$backlog" | grep -F '| 📋 Todo |' || true)
2815
2897
  fix_items=$(grep -E '^\| FIX-' "$backlog" | grep -F '| 📋 Todo |' || true)
2816
2898
  refactor_items=$(grep -E '^\| REFACTOR-' "$backlog" | grep -F '| 📋 Todo |' || true)
@@ -2827,6 +2909,20 @@ cmd_backlog() {
2827
2909
  [[ -z "$idea_items" ]] && idea_count=0
2828
2910
  total=$(( us_count + fix_count + refactor_count + idea_count ))
2829
2911
 
2912
+ local blocked_items deferred_items unknown_items
2913
+ blocked_items=$(grep -E '^\|' "$backlog" | grep '🔒 Blocked' || true)
2914
+ deferred_items=$(grep -E '^\|' "$backlog" | grep '⏸ Deferred' || true)
2915
+ unknown_items=$( { grep -E '^\| \[US-' "$backlog"; grep -E '^\| FIX-' "$backlog"; grep -E '^\| REFACTOR-' "$backlog"; grep -E '^\| IDEA-' "$backlog"; } \
2916
+ | grep -v '📋 Todo\|🔨 In Progress\|✅ Done\|🔒 Blocked\|⏸ Deferred' || true)
2917
+
2918
+ local blocked_count deferred_count unknown_count
2919
+ blocked_count=$(echo "$blocked_items" | grep -c . || true)
2920
+ deferred_count=$(echo "$deferred_items" | grep -c . || true)
2921
+ unknown_count=$(echo "$unknown_items" | grep -c . || true)
2922
+ [[ -z "$blocked_items" ]] && blocked_count=0
2923
+ [[ -z "$deferred_items" ]] && deferred_count=0
2924
+ [[ -z "$unknown_items" ]] && unknown_count=0
2925
+
2830
2926
  echo ""
2831
2927
  echo -e " ${BOLD}Pending Backlog 待处理任务${NC} (${total} items)"
2832
2928
  echo ""
@@ -2879,6 +2975,53 @@ cmd_backlog() {
2879
2975
  echo -e " ${GREEN}✓ Nothing pending — backlog is clear 暂无待处理任务${NC}"
2880
2976
  echo ""
2881
2977
  fi
2978
+
2979
+ # ── Blocked ───────────────────────────────────────────────────────────────
2980
+ if [[ $blocked_count -gt 0 ]]; then
2981
+ echo -e " ${DIM}Blocked 已阻塞 (${blocked_count})${NC}"
2982
+ while IFS= read -r line; do
2983
+ [[ -z "$line" ]] && continue
2984
+ local id desc reason
2985
+ id=$(_backlog_extract_id "$line")
2986
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-52)
2987
+ reason=$(echo "$line" | awk -F'|' '{print $4}' | grep -oE '\[.*\]' | tr -d '[]' || true)
2988
+ printf " ${DIM}🔒 %-14s %s${NC}" "$id" "$desc"
2989
+ [[ -n "$reason" ]] && printf "${DIM} (%s)${NC}" "$reason"
2990
+ printf "\n"
2991
+ done <<< "$blocked_items"
2992
+ echo ""
2993
+ fi
2994
+
2995
+ # ── Deferred ──────────────────────────────────────────────────────────────
2996
+ if [[ $deferred_count -gt 0 ]]; then
2997
+ echo -e " ${DIM}Deferred 已推迟 (${deferred_count})${NC}"
2998
+ while IFS= read -r line; do
2999
+ [[ -z "$line" ]] && continue
3000
+ local id desc reason
3001
+ id=$(_backlog_extract_id "$line")
3002
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-52)
3003
+ reason=$(echo "$line" | awk -F'|' '{print $4}' | grep -oE '\[.*\]' | tr -d '[]' || true)
3004
+ printf " ${DIM}⏸ %-14s %s${NC}" "$id" "$desc"
3005
+ [[ -n "$reason" ]] && printf "${DIM} (%s)${NC}" "$reason"
3006
+ printf "\n"
3007
+ done <<< "$deferred_items"
3008
+ echo ""
3009
+ fi
3010
+
3011
+ # ── Unknown status (show for human/AI triage) ─────────────────────────────
3012
+ if [[ $unknown_count -gt 0 ]]; then
3013
+ echo -e " ${YELLOW}? Unknown Status 未知状态 (${unknown_count})${NC}"
3014
+ echo -e " ${YELLOW} Fix: roll backlog block/defer/unblock <pattern> 运行命令修正状态${NC}"
3015
+ while IFS= read -r line; do
3016
+ [[ -z "$line" ]] && continue
3017
+ local id desc status_raw
3018
+ id=$(_backlog_extract_id "$line")
3019
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-52)
3020
+ status_raw=$(echo "$line" | awk -F'|' '{print $4}' | sed 's/^ *//;s/ *$//')
3021
+ printf " ${YELLOW}? %-14s %s [%s]${NC}\n" "$id" "$desc" "$status_raw"
3022
+ done <<< "$unknown_items"
3023
+ echo ""
3024
+ fi
2882
3025
  }
2883
3026
 
2884
3027
  # ─────────────────────────────────────────────────────────────────────────────
@@ -2985,7 +3128,10 @@ usage() {
2985
3128
  echo " peer [Peer Review] Cross-agent negotiation 跨 Agent 协商对审"
2986
3129
  echo " loop <on|off|now|status|monitor|resume|reset> [Autonomous] Manage scheduled BACKLOG executor 管理自主执行循环"
2987
3130
  echo " brief [Digest] Show latest owner brief (regenerate if stale) 展示最新简报"
2988
- echo " backlog [View] Show all pending tasks from BACKLOG.md 显示待处理任务清单"
3131
+ echo " backlog [View] Show pending tasks (Todo/Blocked/Deferred/Unknown) 显示任务清单"
3132
+ echo " backlog block <pat> [reason] Mark matching items as 🔒 Blocked 标记为已阻塞"
3133
+ echo " backlog defer <pat> [reason] Mark matching items as ⏸ Deferred 标记为已推迟"
3134
+ echo " backlog unblock <pat> Restore matching items to 📋 Todo 恢复为待处理"
2989
3135
  echo " agent [use <name>|list] [Config] Per-project agent selection 切换项目 agent"
2990
3136
  echo " release [Publish] Sync changelog + version bump + npm publish 同步日志并发版"
2991
3137
  echo ""
@@ -2995,7 +3141,9 @@ usage() {
2995
3141
  echo " roll init # New or re-merge project (run in project) 新建或重新合并(项目目录)"
2996
3142
  echo " roll loop on # Enable autonomous loop (cron) 启用自主执行循环"
2997
3143
  echo " roll brief # Show latest brief 查看最新简报"
2998
- echo " roll backlog # Show pending BACKLOG items 查看待处理任务"
3144
+ echo " roll backlog # Show pending/blocked/deferred items 查看待处理任务"
3145
+ echo " roll backlog defer US-DOC '过早引入' # Defer all US-DOC-* items 推迟一类任务"
3146
+ echo " roll backlog block US-HW-001 '硬件未到货' # Block a specific item 标记阻塞"
2999
3147
  echo " roll agent use kimi # Switch this project to kimi 切换当前项目到 kimi"
3000
3148
 
3001
3149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanyao/roll",
3
- "version": "2026.512.1",
3
+ "version": "2026.512.5",
4
4
  "description": "Roll — Roll out features with AI agents",
5
5
  "scripts": {
6
6
  "test": "find tests/unit tests/integration -name '*.bats' | sort | xargs ./tests/helpers/bats-core/bin/bats"