@seanyao/roll 2026.512.1 → 2026.512.2

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 +5 -0
  2. package/bin/roll +167 -28
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+ - **Added**: BACKLOG 支持 block / defer / unblock 状态管理 — 标记卡住的任务不再占队列
5
+ - **Fixed**: 自动弹窗现在能识别 Ghostty 和 iTerm2,不再强制弹出 Terminal.app
6
+ - **Fixed**: loop 检测到上一轮还在跑时自动跳过,不重复启动
7
+
3
8
  ## v2026.512.1
4
9
  - **Added**: `roll loop pause` / `roll loop resume` — 想自己上手时一键暂停 loop,做完再恢复
5
10
  - **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.2"
8
8
  ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
9
9
  ROLL_CONFIG="${ROLL_HOME}/config.yaml"
10
10
  ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
@@ -1994,6 +1994,13 @@ if [ -f "\$LOCK" ]; then
1994
1994
  fi
1995
1995
  rm -f "\$LOCK"
1996
1996
  fi
1997
+ # Guard against stale-LOCK case: if the tmux session is already alive,
1998
+ # a previous runner's LOCK was removed (e.g. parent terminal closed) but
1999
+ # the work is still in progress — don't kill it.
2000
+ if command -v tmux >/dev/null 2>&1 && tmux has-session -t "\$SESSION" 2>/dev/null; then
2001
+ echo "[\$(date '+%Y-%m-%dT%H:%M:%S%z')] tmux session \$SESSION still active, skipping" >> "\$LOG"
2002
+ exit 0
2003
+ fi
1997
2004
  echo "\$\$" > "\$LOCK"
1998
2005
  trap 'rm -f "\$LOCK"' EXIT
1999
2006
  if command -v tmux >/dev/null 2>&1; then
@@ -2004,29 +2011,29 @@ if command -v tmux >/dev/null 2>&1; then
2004
2011
  # tmux session so the user can watch the loop work in real time. Best-effort
2005
2012
  # focus retention: capture the current frontmost app and re-activate after.
2006
2013
  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
2014
+ # Runtime terminal detection: try preferred first, fallback through installed apps.
2015
+ # open -na returns non-zero when app not found, so || chain works as fallback.
2016
+ _PREF="${terminal_pref}"
2017
+ _launched=0
2018
+ if [ "\$_PREF" = "ghostty" ] || [ "\$_PREF" = "Ghostty" ]; then
2019
+ open -na Ghostty.app --args -e tmux attach -t \$SESSION >/dev/null 2>&1 && _launched=1 || true
2020
+ fi
2021
+ if [ "\$_launched" -eq 0 ] && { [ "\$_PREF" = "iTerm2" ] || [ "\$_PREF" = "iTerm" ] || [ -d "/Applications/iTerm.app" ]; }; then
2022
+ osascript \\
2023
+ -e 'tell application "System Events" to set _prev to name of first application process whose frontmost is true' \\
2024
+ -e "tell application \"iTerm2\" to create window with default profile command \"tmux attach -t \$SESSION\"" \\
2025
+ -e 'delay 0.3' -e 'try' -e 'tell application _prev to activate' -e 'end try' >/dev/null 2>&1 \\
2026
+ && _launched=1 || true
2027
+ fi
2028
+ if [ "\$_launched" -eq 0 ] && [ -d "/Applications/Ghostty.app" ]; then
2029
+ open -na Ghostty.app --args -e tmux attach -t \$SESSION >/dev/null 2>&1 && _launched=1 || true
2030
+ fi
2031
+ if [ "\$_launched" -eq 0 ] && command -v osascript >/dev/null 2>&1; then
2032
+ osascript \\
2033
+ -e 'tell application "System Events" to set _prev to name of first application process whose frontmost is true' \\
2034
+ -e "tell application \"Terminal\" to do script \"tmux attach -t \$SESSION\"" \\
2035
+ -e 'delay 0.3' -e 'try' -e 'tell application _prev to activate' -e 'end try' >/dev/null 2>&1 || true
2036
+ fi
2030
2037
  fi
2031
2038
  while tmux has-session -t "\$SESSION" 2>/dev/null; do sleep 5; done
2032
2039
  else
@@ -2799,9 +2806,45 @@ cmd_release() {
2799
2806
  }
2800
2807
 
2801
2808
  # ═══════════════════════════════════════════════════════════════════════════════
2802
- # BACKLOG — show pending tasks
2809
+ # BACKLOG — show pending tasks / manage status
2803
2810
  # ═══════════════════════════════════════════════════════════════════════════════
2804
2811
 
2812
+ # Update status of all BACKLOG rows whose ID field contains <pattern> (case-insensitive).
2813
+ # Uses Python for reliable emoji/Unicode handling.
2814
+ _backlog_set_status() {
2815
+ local pattern="$1"
2816
+ local new_status="$2"
2817
+ local backlog="BACKLOG.md"
2818
+ python3 -c "
2819
+ import sys, re
2820
+ pattern, new_status, filename = sys.argv[1], sys.argv[2], sys.argv[3]
2821
+ lines = open(filename, encoding='utf-8').readlines()
2822
+ count = 0
2823
+ out = []
2824
+ for line in lines:
2825
+ if line.startswith('|') and line.count('|') >= 4:
2826
+ parts = line.split('|')
2827
+ if len(parts) >= 5:
2828
+ id_field = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', parts[1]).strip()
2829
+ if pattern.upper() in id_field.upper():
2830
+ parts[-2] = ' ' + new_status + ' '
2831
+ line = '|'.join(parts)
2832
+ count += 1
2833
+ out.append(line)
2834
+ open(filename, 'w', encoding='utf-8').writelines(out)
2835
+ print(count)
2836
+ " "$pattern" "$new_status" "$backlog"
2837
+ }
2838
+
2839
+ _backlog_extract_id() {
2840
+ local line="$1"
2841
+ if echo "$line" | grep -q '\[US-'; then
2842
+ echo "$line" | sed 's/.*\[\(US-[^]]*\)\].*/\1/'
2843
+ else
2844
+ echo "$line" | awk -F'|' '{print $2}' | tr -d ' '
2845
+ fi
2846
+ }
2847
+
2805
2848
  cmd_backlog() {
2806
2849
  local backlog="BACKLOG.md"
2807
2850
  if [[ ! -f "$backlog" ]]; then
@@ -2809,8 +2852,38 @@ cmd_backlog() {
2809
2852
  return 1
2810
2853
  fi
2811
2854
 
2812
- local us_items fix_items refactor_items idea_items total=0
2855
+ local subcmd="${1:-}"
2856
+
2857
+ # ── Status management subcommands ─────────────────────────────────────────
2858
+ case "$subcmd" in
2859
+ block|defer|unblock|promote)
2860
+ local pattern="${2:-}"
2861
+ local reason="${3:-}"
2862
+ if [[ -z "$pattern" ]]; then
2863
+ err "Usage: roll backlog $subcmd <pattern> [reason] 用法: roll backlog $subcmd <匹配模式> [原因]"
2864
+ return 1
2865
+ fi
2866
+ local new_status
2867
+ case "$subcmd" in
2868
+ block) new_status="🔒 Blocked${reason:+ [${reason}]}" ;;
2869
+ defer) new_status="⏸ Deferred${reason:+ [${reason}]}" ;;
2870
+ unblock|promote) new_status="📋 Todo" ;;
2871
+ esac
2872
+ local count
2873
+ count=$(_backlog_set_status "$pattern" "$new_status")
2874
+ if [[ "$count" -eq 0 ]]; then
2875
+ echo " No items matched: $pattern 未找到匹配项: $pattern"
2876
+ else
2877
+ echo " Updated ${count} item(s) → ${new_status} 已更新 ${count} 条目"
2878
+ fi
2879
+ return
2880
+ ;;
2881
+ esac
2882
+
2883
+ # ── Display mode ──────────────────────────────────────────────────────────
2884
+ local DIM='\033[2m'
2813
2885
 
2886
+ local us_items fix_items refactor_items idea_items total=0
2814
2887
  us_items=$(grep -E '^\| \[US-' "$backlog" | grep -F '| 📋 Todo |' || true)
2815
2888
  fix_items=$(grep -E '^\| FIX-' "$backlog" | grep -F '| 📋 Todo |' || true)
2816
2889
  refactor_items=$(grep -E '^\| REFACTOR-' "$backlog" | grep -F '| 📋 Todo |' || true)
@@ -2827,6 +2900,20 @@ cmd_backlog() {
2827
2900
  [[ -z "$idea_items" ]] && idea_count=0
2828
2901
  total=$(( us_count + fix_count + refactor_count + idea_count ))
2829
2902
 
2903
+ local blocked_items deferred_items unknown_items
2904
+ blocked_items=$(grep -E '^\|' "$backlog" | grep '🔒 Blocked' || true)
2905
+ deferred_items=$(grep -E '^\|' "$backlog" | grep '⏸ Deferred' || true)
2906
+ unknown_items=$( { grep -E '^\| \[US-' "$backlog"; grep -E '^\| FIX-' "$backlog"; grep -E '^\| REFACTOR-' "$backlog"; grep -E '^\| IDEA-' "$backlog"; } \
2907
+ | grep -v '📋 Todo\|🔨 In Progress\|✅ Done\|🔒 Blocked\|⏸ Deferred' || true)
2908
+
2909
+ local blocked_count deferred_count unknown_count
2910
+ blocked_count=$(echo "$blocked_items" | grep -c . || true)
2911
+ deferred_count=$(echo "$deferred_items" | grep -c . || true)
2912
+ unknown_count=$(echo "$unknown_items" | grep -c . || true)
2913
+ [[ -z "$blocked_items" ]] && blocked_count=0
2914
+ [[ -z "$deferred_items" ]] && deferred_count=0
2915
+ [[ -z "$unknown_items" ]] && unknown_count=0
2916
+
2830
2917
  echo ""
2831
2918
  echo -e " ${BOLD}Pending Backlog 待处理任务${NC} (${total} items)"
2832
2919
  echo ""
@@ -2879,6 +2966,53 @@ cmd_backlog() {
2879
2966
  echo -e " ${GREEN}✓ Nothing pending — backlog is clear 暂无待处理任务${NC}"
2880
2967
  echo ""
2881
2968
  fi
2969
+
2970
+ # ── Blocked ───────────────────────────────────────────────────────────────
2971
+ if [[ $blocked_count -gt 0 ]]; then
2972
+ echo -e " ${DIM}Blocked 已阻塞 (${blocked_count})${NC}"
2973
+ while IFS= read -r line; do
2974
+ [[ -z "$line" ]] && continue
2975
+ local id desc reason
2976
+ id=$(_backlog_extract_id "$line")
2977
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-52)
2978
+ reason=$(echo "$line" | awk -F'|' '{print $4}' | grep -oE '\[.*\]' | tr -d '[]' || true)
2979
+ printf " ${DIM}🔒 %-14s %s${NC}" "$id" "$desc"
2980
+ [[ -n "$reason" ]] && printf "${DIM} (%s)${NC}" "$reason"
2981
+ printf "\n"
2982
+ done <<< "$blocked_items"
2983
+ echo ""
2984
+ fi
2985
+
2986
+ # ── Deferred ──────────────────────────────────────────────────────────────
2987
+ if [[ $deferred_count -gt 0 ]]; then
2988
+ echo -e " ${DIM}Deferred 已推迟 (${deferred_count})${NC}"
2989
+ while IFS= read -r line; do
2990
+ [[ -z "$line" ]] && continue
2991
+ local id desc reason
2992
+ id=$(_backlog_extract_id "$line")
2993
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-52)
2994
+ reason=$(echo "$line" | awk -F'|' '{print $4}' | grep -oE '\[.*\]' | tr -d '[]' || true)
2995
+ printf " ${DIM}⏸ %-14s %s${NC}" "$id" "$desc"
2996
+ [[ -n "$reason" ]] && printf "${DIM} (%s)${NC}" "$reason"
2997
+ printf "\n"
2998
+ done <<< "$deferred_items"
2999
+ echo ""
3000
+ fi
3001
+
3002
+ # ── Unknown status (show for human/AI triage) ─────────────────────────────
3003
+ if [[ $unknown_count -gt 0 ]]; then
3004
+ echo -e " ${YELLOW}? Unknown Status 未知状态 (${unknown_count})${NC}"
3005
+ echo -e " ${YELLOW} Fix: roll backlog block/defer/unblock <pattern> 运行命令修正状态${NC}"
3006
+ while IFS= read -r line; do
3007
+ [[ -z "$line" ]] && continue
3008
+ local id desc status_raw
3009
+ id=$(_backlog_extract_id "$line")
3010
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-52)
3011
+ status_raw=$(echo "$line" | awk -F'|' '{print $4}' | sed 's/^ *//;s/ *$//')
3012
+ printf " ${YELLOW}? %-14s %s [%s]${NC}\n" "$id" "$desc" "$status_raw"
3013
+ done <<< "$unknown_items"
3014
+ echo ""
3015
+ fi
2882
3016
  }
2883
3017
 
2884
3018
  # ─────────────────────────────────────────────────────────────────────────────
@@ -2985,7 +3119,10 @@ usage() {
2985
3119
  echo " peer [Peer Review] Cross-agent negotiation 跨 Agent 协商对审"
2986
3120
  echo " loop <on|off|now|status|monitor|resume|reset> [Autonomous] Manage scheduled BACKLOG executor 管理自主执行循环"
2987
3121
  echo " brief [Digest] Show latest owner brief (regenerate if stale) 展示最新简报"
2988
- echo " backlog [View] Show all pending tasks from BACKLOG.md 显示待处理任务清单"
3122
+ echo " backlog [View] Show pending tasks (Todo/Blocked/Deferred/Unknown) 显示任务清单"
3123
+ echo " backlog block <pat> [reason] Mark matching items as 🔒 Blocked 标记为已阻塞"
3124
+ echo " backlog defer <pat> [reason] Mark matching items as ⏸ Deferred 标记为已推迟"
3125
+ echo " backlog unblock <pat> Restore matching items to 📋 Todo 恢复为待处理"
2989
3126
  echo " agent [use <name>|list] [Config] Per-project agent selection 切换项目 agent"
2990
3127
  echo " release [Publish] Sync changelog + version bump + npm publish 同步日志并发版"
2991
3128
  echo ""
@@ -2995,7 +3132,9 @@ usage() {
2995
3132
  echo " roll init # New or re-merge project (run in project) 新建或重新合并(项目目录)"
2996
3133
  echo " roll loop on # Enable autonomous loop (cron) 启用自主执行循环"
2997
3134
  echo " roll brief # Show latest brief 查看最新简报"
2998
- echo " roll backlog # Show pending BACKLOG items 查看待处理任务"
3135
+ echo " roll backlog # Show pending/blocked/deferred items 查看待处理任务"
3136
+ echo " roll backlog defer US-DOC '过早引入' # Defer all US-DOC-* items 推迟一类任务"
3137
+ echo " roll backlog block US-HW-001 '硬件未到货' # Block a specific item 标记阻塞"
2999
3138
  echo " roll agent use kimi # Switch this project to kimi 切换当前项目到 kimi"
3000
3139
 
3001
3140
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanyao/roll",
3
- "version": "2026.512.1",
3
+ "version": "2026.512.2",
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"