@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.
- package/CHANGELOG.md +5 -0
- package/bin/roll +167 -28
- 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.
|
|
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
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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"
|