@seanyao/roll 2026.511.3 → 2026.511.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.
- package/CHANGELOG.md +7 -0
- package/bin/roll +169 -37
- package/package.json +1 -1
- package/skills/roll-design/SKILL.md +3 -3
- package/skills/roll-propose/SKILL.md +153 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2026.511.5
|
|
4
|
+
- **Fixed**: launchd plist 自动 reload — plist 内容变更且服务已加载时自动 unload + reload,升级 roll 后 loop 服务立即生效,无需手动重启
|
|
5
|
+
- **Improved**: roll loop status/monitor 三态展示 — 区分 ● 运行中 / ⚠ 已安装未加载 / ○ 未安装,并给出对应的自愈操作提示
|
|
6
|
+
|
|
7
|
+
## v2026.511.4
|
|
8
|
+
- **Fixed**: roll init 自动重建 launchd runner scripts — 升级 roll 后直接跑 `roll init` 即可迁移到独立 runner,无需手动执行 roll setup 或 roll loop on
|
|
9
|
+
|
|
3
10
|
## v2026.511.3
|
|
4
11
|
- **Fixed**: loop/dream/brief 多项目运行隔离 — 共享 run.sh 导致所有项目的 loop 在同一目录执行,改为每个项目独立 runner 脚本(run-{slug}.sh),彻底隔离多项目并发执行环境
|
|
5
12
|
- **Fixed**: roll release 自发版拦截 — 在 roll 自身项目执行 `roll release` 时自动拦截并提示改用 scripts/release.sh,防止误操作绕过 2FA
|
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.511.
|
|
7
|
+
VERSION="2026.511.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"
|
|
@@ -352,6 +352,17 @@ ai_deepseek: ~/.deepseek|AGENTS.md|AGENTS.md
|
|
|
352
352
|
default_language: zh
|
|
353
353
|
default_project_type: fullstack
|
|
354
354
|
editor: ${EDITOR:-vim}
|
|
355
|
+
|
|
356
|
+
# Loop schedule (24h format, machine local timezone)
|
|
357
|
+
# Minute fields auto-derive from project path hash when omitted — avoids contention across projects.
|
|
358
|
+
loop_active_start: 10 # loop only fires inside this window (after human reviews brief)
|
|
359
|
+
loop_active_end: 18
|
|
360
|
+
# loop_minute: 5 # omit to auto-derive from project hash
|
|
361
|
+
loop_dream_hour: 3
|
|
362
|
+
# loop_dream_minute: 10 # omit to auto-derive
|
|
363
|
+
loop_brief_hour: 9
|
|
364
|
+
# loop_brief_minute: 15 # omit to auto-derive
|
|
365
|
+
primary_agent: claude
|
|
355
366
|
YAML
|
|
356
367
|
ok "Created: ~/.roll/config.yaml 已创建: ~/.roll/config.yaml"
|
|
357
368
|
fi
|
|
@@ -758,6 +769,8 @@ cmd_init() {
|
|
|
758
769
|
_sync_conventions
|
|
759
770
|
echo ""
|
|
760
771
|
|
|
772
|
+
_install_launchd_plists "$project_dir"
|
|
773
|
+
|
|
761
774
|
if [[ "$has_agents" == "true" ]]; then
|
|
762
775
|
ok "Done. 完成。"
|
|
763
776
|
else
|
|
@@ -1755,6 +1768,28 @@ _project_slug() {
|
|
|
1755
1768
|
printf '%s' "${base}-${hash}"
|
|
1756
1769
|
}
|
|
1757
1770
|
|
|
1771
|
+
_config_read_int() {
|
|
1772
|
+
local key="$1" default="$2"
|
|
1773
|
+
local val
|
|
1774
|
+
val=$(grep "^${key}:" "$ROLL_CONFIG" 2>/dev/null | awk '{print $2}' | tr -d '"' | head -1)
|
|
1775
|
+
if [[ "$val" =~ ^[0-9]+$ ]]; then echo "$val"; else echo "$default"; fi
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
# Derive a minute in [1,55] from project path hash + offset so different projects
|
|
1779
|
+
# and different services within a project don't fire at the same time.
|
|
1780
|
+
# Offsets used: loop=0, dream=2, brief=4 → always three distinct values (2<55).
|
|
1781
|
+
_loop_derive_minute() {
|
|
1782
|
+
local project_path="$1" offset="${2:-0}"
|
|
1783
|
+
local hash_hex
|
|
1784
|
+
if command -v md5 &>/dev/null; then
|
|
1785
|
+
hash_hex=$(printf '%s' "$project_path" | md5 | cut -c1-6)
|
|
1786
|
+
else
|
|
1787
|
+
hash_hex=$(printf '%s' "$project_path" | md5sum | cut -c1-6)
|
|
1788
|
+
fi
|
|
1789
|
+
local hash_dec; hash_dec=$(printf '%d' "0x${hash_hex}")
|
|
1790
|
+
echo $(( (hash_dec + offset) % 55 + 1 ))
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1758
1793
|
_launchd_label() {
|
|
1759
1794
|
local service="$1" project_path="$2"
|
|
1760
1795
|
printf 'com.roll.%s.%s' "$service" "$(_project_slug "$project_path")"
|
|
@@ -1810,12 +1845,42 @@ _write_runner_script() {
|
|
|
1810
1845
|
chmod +x "$script_path"
|
|
1811
1846
|
}
|
|
1812
1847
|
|
|
1848
|
+
# Like _write_runner_script but prepends an active window guard.
|
|
1849
|
+
# Silently exits when current hour is outside [active_start, active_end).
|
|
1850
|
+
_write_loop_runner_script() {
|
|
1851
|
+
local script_path="$1" project_path="$2" cmd="$3" log_path="$4"
|
|
1852
|
+
local active_start="${5:-10}" active_end="${6:-18}"
|
|
1853
|
+
mkdir -p "$(dirname "$script_path")"
|
|
1854
|
+
cat > "$script_path" << SCRIPT
|
|
1855
|
+
#!/bin/bash -l
|
|
1856
|
+
h=\$(printf '%d' "\$(date +%H)")
|
|
1857
|
+
if [ "\$h" -lt ${active_start} ] || [ "\$h" -ge ${active_end} ]; then exit 0; fi
|
|
1858
|
+
cd "${project_path}" && ${cmd} >> "${log_path}" 2>&1
|
|
1859
|
+
SCRIPT
|
|
1860
|
+
chmod +x "$script_path"
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1813
1863
|
_launchd_is_loaded() {
|
|
1814
|
-
launchctl
|
|
1864
|
+
launchctl print-disabled "gui/$(id -u)" 2>/dev/null | grep -qF "\"$1\" => enabled"
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
_launchd_svc_state() {
|
|
1868
|
+
local svc="$1" project_path="$2"
|
|
1869
|
+
local label; label=$(_launchd_label "$svc" "$project_path")
|
|
1870
|
+
local plist; plist=$(_launchd_plist_path "$svc" "$project_path")
|
|
1871
|
+
if _launchd_is_loaded "$label"; then
|
|
1872
|
+
echo "enabled"
|
|
1873
|
+
elif [[ -f "$plist" ]]; then
|
|
1874
|
+
echo "installed-off"
|
|
1875
|
+
else
|
|
1876
|
+
echo "not-installed"
|
|
1877
|
+
fi
|
|
1815
1878
|
}
|
|
1816
1879
|
|
|
1817
1880
|
# Install launchd plist files (disabled by default) and runner scripts for
|
|
1818
1881
|
# a given project path. Idempotent — skips unchanged files. Does NOT load.
|
|
1882
|
+
# Schedule times are read from ~/.roll/config.yaml; missing fields are
|
|
1883
|
+
# auto-derived from the project path hash so different projects don't contend.
|
|
1819
1884
|
_install_launchd_plists() {
|
|
1820
1885
|
local project_path="$1"
|
|
1821
1886
|
local sd="${ROLL_HOME}/skills"
|
|
@@ -1824,12 +1889,22 @@ _install_launchd_plists() {
|
|
|
1824
1889
|
mkdir -p "$_LAUNCHD_DIR"
|
|
1825
1890
|
mkdir -p "${shared}/loop" "${shared}/dream" "${shared}/brief"
|
|
1826
1891
|
|
|
1892
|
+
local active_start active_end loop_minute dream_hour dream_minute brief_hour brief_minute
|
|
1893
|
+
active_start=$(_config_read_int "loop_active_start" "10")
|
|
1894
|
+
active_end=$(_config_read_int "loop_active_end" "18")
|
|
1895
|
+
loop_minute=$(_config_read_int "loop_minute" "$(_loop_derive_minute "$project_path" 0)")
|
|
1896
|
+
dream_hour=$(_config_read_int "loop_dream_hour" "3")
|
|
1897
|
+
dream_minute=$(_config_read_int "loop_dream_minute" "$(_loop_derive_minute "$project_path" 2)")
|
|
1898
|
+
brief_hour=$(_config_read_int "loop_brief_hour" "9")
|
|
1899
|
+
brief_minute=$(_config_read_int "loop_brief_minute" "$(_loop_derive_minute "$project_path" 4)")
|
|
1900
|
+
|
|
1827
1901
|
local services=("loop" "dream" "brief")
|
|
1828
1902
|
local skill_names=("roll-loop" "roll-.dream" "roll-brief")
|
|
1829
|
-
local minutes=("
|
|
1830
|
-
local hours=("" "
|
|
1903
|
+
local minutes=("$loop_minute" "$dream_minute" "$brief_minute")
|
|
1904
|
+
local hours=("" "$dream_hour" "$brief_hour")
|
|
1831
1905
|
|
|
1832
1906
|
local updated=0
|
|
1907
|
+
local slug; slug=$(_project_slug "$project_path")
|
|
1833
1908
|
for i in "${!services[@]}"; do
|
|
1834
1909
|
local svc="${services[$i]}"
|
|
1835
1910
|
local skill="${skill_names[$i]}"
|
|
@@ -1837,18 +1912,27 @@ _install_launchd_plists() {
|
|
|
1837
1912
|
local hour="${hours[$i]}"
|
|
1838
1913
|
local label; label=$(_launchd_label "$svc" "$project_path")
|
|
1839
1914
|
local plist; plist=$(_launchd_plist_path "$svc" "$project_path")
|
|
1840
|
-
local slug; slug=$(_project_slug "$project_path")
|
|
1841
1915
|
local runner="${shared}/${svc}/run-${slug}.sh"
|
|
1842
1916
|
local log="${shared}/${svc}/cron.log"
|
|
1843
1917
|
local cmd; cmd=$(_agent_skill_cmd "${sd}/${skill}/SKILL.md" 2>/dev/null || echo "roll loop now")
|
|
1844
1918
|
|
|
1845
|
-
|
|
1919
|
+
if [[ "$svc" == "loop" ]]; then
|
|
1920
|
+
_write_loop_runner_script "$runner" "$project_path" "cd \"${project_path}\" && ${cmd}" "$log" "$active_start" "$active_end"
|
|
1921
|
+
else
|
|
1922
|
+
_write_runner_script "$runner" "$project_path" "cd \"${project_path}\" && ${cmd}" "$log"
|
|
1923
|
+
fi
|
|
1846
1924
|
|
|
1847
1925
|
local before=""
|
|
1848
1926
|
[[ -f "$plist" ]] && before=$(cat "$plist")
|
|
1849
1927
|
_write_launchd_plist "$plist" "$label" "$project_path" "$minute" "$hour" "$runner"
|
|
1850
1928
|
local after; after=$(cat "$plist")
|
|
1851
|
-
[[ "$before" != "$after" ]]
|
|
1929
|
+
if [[ "$before" != "$after" ]]; then
|
|
1930
|
+
updated=$((updated + 1))
|
|
1931
|
+
if _launchd_is_loaded "$label"; then
|
|
1932
|
+
launchctl unload "$plist" 2>/dev/null || true
|
|
1933
|
+
launchctl load "$plist" 2>/dev/null || true
|
|
1934
|
+
fi
|
|
1935
|
+
fi
|
|
1852
1936
|
done
|
|
1853
1937
|
|
|
1854
1938
|
if [[ $updated -gt 0 ]]; then
|
|
@@ -1893,6 +1977,15 @@ _loop_on() {
|
|
|
1893
1977
|
local project_path; project_path=$(pwd -P)
|
|
1894
1978
|
local agent; agent=$(_project_agent)
|
|
1895
1979
|
|
|
1980
|
+
local active_start active_end loop_minute dream_hour dream_minute brief_hour brief_minute
|
|
1981
|
+
active_start=$(_config_read_int "loop_active_start" "10")
|
|
1982
|
+
active_end=$(_config_read_int "loop_active_end" "18")
|
|
1983
|
+
loop_minute=$(_config_read_int "loop_minute" "$(_loop_derive_minute "$project_path" 0)")
|
|
1984
|
+
dream_hour=$(_config_read_int "loop_dream_hour" "3")
|
|
1985
|
+
dream_minute=$(_config_read_int "loop_dream_minute" "$(_loop_derive_minute "$project_path" 2)")
|
|
1986
|
+
brief_hour=$(_config_read_int "loop_brief_hour" "9")
|
|
1987
|
+
brief_minute=$(_config_read_int "loop_brief_minute" "$(_loop_derive_minute "$project_path" 4)")
|
|
1988
|
+
|
|
1896
1989
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
1897
1990
|
_install_launchd_plists "$project_path" >/dev/null
|
|
1898
1991
|
|
|
@@ -1901,7 +1994,7 @@ _loop_on() {
|
|
|
1901
1994
|
local label; label=$(_launchd_label "$svc" "$project_path")
|
|
1902
1995
|
if ! _launchd_is_loaded "$label"; then
|
|
1903
1996
|
all_loaded=false
|
|
1904
|
-
launchctl load "$(_launchd_plist_path "$svc" "$project_path")" 2>/dev/null || true
|
|
1997
|
+
launchctl load -w "$(_launchd_plist_path "$svc" "$project_path")" 2>/dev/null || true
|
|
1905
1998
|
fi
|
|
1906
1999
|
done
|
|
1907
2000
|
|
|
@@ -1910,9 +2003,10 @@ _loop_on() {
|
|
|
1910
2003
|
fi
|
|
1911
2004
|
|
|
1912
2005
|
ok "Loop enabled 已启用"
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2006
|
+
printf " • roll-loop every hour :%02d active %02d:00–%02d:00 每小时 :%02d(窗口 %02d:00–%02d:00)\n" \
|
|
2007
|
+
"$loop_minute" "$active_start" "$active_end" "$loop_minute" "$active_start" "$active_end"
|
|
2008
|
+
printf " • roll-.dream daily at %02d:%02d 每天 %02d:%02d\n" "$dream_hour" "$dream_minute" "$dream_hour" "$dream_minute"
|
|
2009
|
+
printf " • roll-brief daily at %02d:%02d 每天 %02d:%02d\n" "$brief_hour" "$brief_minute" "$brief_hour" "$brief_minute"
|
|
1916
2010
|
echo " • Agent: ${agent} (change: roll agent use <name>)"
|
|
1917
2011
|
return 0
|
|
1918
2012
|
fi
|
|
@@ -1932,15 +2026,16 @@ _loop_on() {
|
|
|
1932
2026
|
|
|
1933
2027
|
(
|
|
1934
2028
|
crontab -l 2>/dev/null
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
2029
|
+
printf "%d * * * * %s %s:%s\n" "$loop_minute" "$loop_cmd" "$_LOOP_TAG" "$project_path"
|
|
2030
|
+
printf "%d %d * * * %s %s:%s\n" "$dream_minute" "$dream_hour" "$dream_cmd" "$_LOOP_TAG" "$project_path"
|
|
2031
|
+
printf "%d %d * * * %s %s:%s\n" "$brief_minute" "$brief_hour" "$brief_cmd" "$_LOOP_TAG" "$project_path"
|
|
1938
2032
|
) | crontab -
|
|
1939
2033
|
|
|
1940
2034
|
ok "Loop enabled 已启用"
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
2035
|
+
printf " • roll-loop every hour :%02d active %02d:00–%02d:00 每小时 :%02d(窗口 %02d:00–%02d:00)\n" \
|
|
2036
|
+
"$loop_minute" "$active_start" "$active_end" "$loop_minute" "$active_start" "$active_end"
|
|
2037
|
+
printf " • roll-.dream daily at %02d:%02d 每天 %02d:%02d\n" "$dream_hour" "$dream_minute" "$dream_hour" "$dream_minute"
|
|
2038
|
+
printf " • roll-brief daily at %02d:%02d 每天 %02d:%02d\n" "$brief_hour" "$brief_minute" "$brief_hour" "$brief_minute"
|
|
1944
2039
|
echo " • Agent: ${agent} (change: roll agent use <name>)"
|
|
1945
2040
|
}
|
|
1946
2041
|
|
|
@@ -1953,7 +2048,7 @@ _loop_off() {
|
|
|
1953
2048
|
local label; label=$(_launchd_label "$svc" "$project_path")
|
|
1954
2049
|
if _launchd_is_loaded "$label"; then
|
|
1955
2050
|
any_loaded=true
|
|
1956
|
-
launchctl unload "$(_launchd_plist_path "$svc" "$project_path")" 2>/dev/null || true
|
|
2051
|
+
launchctl unload -w "$(_launchd_plist_path "$svc" "$project_path")" 2>/dev/null || true
|
|
1957
2052
|
fi
|
|
1958
2053
|
done
|
|
1959
2054
|
if ! $any_loaded; then
|
|
@@ -1993,16 +2088,24 @@ _loop_status() {
|
|
|
1993
2088
|
local project_path; project_path=$(pwd -P)
|
|
1994
2089
|
local agent; agent=$(_project_agent)
|
|
1995
2090
|
echo ""
|
|
1996
|
-
local loop_enabled=false
|
|
1997
2091
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
1998
|
-
|
|
2092
|
+
echo -e " Services Agent: ${CYAN}${agent}${NC}"
|
|
2093
|
+
for svc in loop dream brief; do
|
|
2094
|
+
local state; state=$(_launchd_svc_state "$svc" "$project_path")
|
|
2095
|
+
case "$state" in
|
|
2096
|
+
enabled) echo -e " ${GREEN}${svc} ● enabled${NC}" ;;
|
|
2097
|
+
installed-off) echo -e " ${YELLOW}${svc} ⚠ installed/off${NC} run: roll loop on" ;;
|
|
2098
|
+
not-installed) echo -e " ${RED}${svc} ○ not installed${NC} run: roll setup" ;;
|
|
2099
|
+
esac
|
|
2100
|
+
done
|
|
1999
2101
|
else
|
|
2102
|
+
local loop_enabled=false
|
|
2000
2103
|
crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}" && loop_enabled=true
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2104
|
+
if $loop_enabled; then
|
|
2105
|
+
echo -e " Scheduler ${GREEN}● enabled${NC} Agent: ${CYAN}${agent}${NC}"
|
|
2106
|
+
else
|
|
2107
|
+
echo -e " Scheduler ${YELLOW}○ disabled${NC} run: roll loop on"
|
|
2108
|
+
fi
|
|
2006
2109
|
fi
|
|
2007
2110
|
[[ -f "$_LOOP_ALERT" ]] && { echo ""; echo -e " ${RED}⚠ ALERT:${NC}"; sed 's/^/ /' "$_LOOP_ALERT"; }
|
|
2008
2111
|
[[ -f "$_LOOP_STATE" ]] && { echo ""; echo " State:"; sed 's/^/ /' "$_LOOP_STATE"; }
|
|
@@ -2093,15 +2196,30 @@ _loop_monitor() {
|
|
|
2093
2196
|
# Services status (three services on macOS, single on Linux)
|
|
2094
2197
|
echo -e " ${BOLD}Services 服务状态${NC} Agent: ${CYAN}${agent}${NC}"
|
|
2095
2198
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
2096
|
-
local
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2199
|
+
local active_start active_end loop_minute dream_hour dream_minute brief_hour brief_minute
|
|
2200
|
+
active_start=$(_config_read_int "loop_active_start" "10")
|
|
2201
|
+
active_end=$(_config_read_int "loop_active_end" "18")
|
|
2202
|
+
loop_minute=$(_config_read_int "loop_minute" "$(_loop_derive_minute "$project_path" 0)")
|
|
2203
|
+
dream_hour=$(_config_read_int "loop_dream_hour" "3")
|
|
2204
|
+
dream_minute=$(_config_read_int "loop_dream_minute" "$(_loop_derive_minute "$project_path" 2)")
|
|
2205
|
+
brief_hour=$(_config_read_int "loop_brief_hour" "9")
|
|
2206
|
+
brief_minute=$(_config_read_int "loop_brief_minute" "$(_loop_derive_minute "$project_path" 4)")
|
|
2207
|
+
|
|
2208
|
+
local loop_sched dream_sched brief_sched
|
|
2209
|
+
loop_sched=$(printf "every hour :%02d active %02d:00–%02d:00" "$loop_minute" "$active_start" "$active_end")
|
|
2210
|
+
dream_sched=$(printf "%02d:%02d" "$dream_hour" "$dream_minute")
|
|
2211
|
+
brief_sched=$(printf "%02d:%02d" "$brief_hour" "$brief_minute")
|
|
2212
|
+
|
|
2213
|
+
local svcs=("loop" "dream" "brief")
|
|
2214
|
+
local scheds=("$loop_sched" "$dream_sched" "$brief_sched")
|
|
2215
|
+
for i in "${!svcs[@]}"; do
|
|
2216
|
+
local svc="${svcs[$i]}" schedule="${scheds[$i]}"
|
|
2217
|
+
local state; state=$(_launchd_svc_state "$svc" "$project_path")
|
|
2218
|
+
case "$state" in
|
|
2219
|
+
enabled) printf " ${GREEN}%-8s ● enabled${NC} (%s)\n" "$svc" "$schedule" ;;
|
|
2220
|
+
installed-off) printf " ${YELLOW}%-8s ⚠ installed/off${NC} (%s) run: roll loop on\n" "$svc" "$schedule" ;;
|
|
2221
|
+
not-installed) printf " ${RED}%-8s ○ not installed${NC} (%s) run: roll setup\n" "$svc" "$schedule" ;;
|
|
2222
|
+
esac
|
|
2105
2223
|
done
|
|
2106
2224
|
else
|
|
2107
2225
|
if crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}"; then
|
|
@@ -2246,20 +2364,23 @@ cmd_backlog() {
|
|
|
2246
2364
|
return 1
|
|
2247
2365
|
fi
|
|
2248
2366
|
|
|
2249
|
-
local us_items fix_items refactor_items total=0
|
|
2367
|
+
local us_items fix_items refactor_items idea_items total=0
|
|
2250
2368
|
|
|
2251
2369
|
us_items=$(grep -E '^\| \[US-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2252
2370
|
fix_items=$(grep -E '^\| FIX-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2253
2371
|
refactor_items=$(grep -E '^\| REFACTOR-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2372
|
+
idea_items=$(grep -E '^\| IDEA-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2254
2373
|
|
|
2255
|
-
local us_count fix_count refactor_count
|
|
2374
|
+
local us_count fix_count refactor_count idea_count
|
|
2256
2375
|
us_count=$(echo "$us_items" | grep -c . || true)
|
|
2257
2376
|
fix_count=$(echo "$fix_items" | grep -c . || true)
|
|
2258
2377
|
refactor_count=$(echo "$refactor_items" | grep -c . || true)
|
|
2378
|
+
idea_count=$(echo "$idea_items" | grep -c . || true)
|
|
2259
2379
|
[[ -z "$us_items" ]] && us_count=0
|
|
2260
2380
|
[[ -z "$fix_items" ]] && fix_count=0
|
|
2261
2381
|
[[ -z "$refactor_items" ]] && refactor_count=0
|
|
2262
|
-
|
|
2382
|
+
[[ -z "$idea_items" ]] && idea_count=0
|
|
2383
|
+
total=$(( us_count + fix_count + refactor_count + idea_count ))
|
|
2263
2384
|
|
|
2264
2385
|
echo ""
|
|
2265
2386
|
echo -e " ${BOLD}Pending Backlog 待处理任务${NC} (${total} items)"
|
|
@@ -2298,6 +2419,17 @@ cmd_backlog() {
|
|
|
2298
2419
|
echo ""
|
|
2299
2420
|
fi
|
|
2300
2421
|
|
|
2422
|
+
if [[ $idea_count -gt 0 ]]; then
|
|
2423
|
+
echo -e " ${NC}Ideas 创意 (${idea_count})"
|
|
2424
|
+
while IFS= read -r line; do
|
|
2425
|
+
local id desc
|
|
2426
|
+
id=$(echo "$line" | awk -F'|' '{print $2}' | tr -d ' ')
|
|
2427
|
+
desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//')
|
|
2428
|
+
printf " %-14s %s\n" "$id" "$desc"
|
|
2429
|
+
done <<< "$idea_items"
|
|
2430
|
+
echo ""
|
|
2431
|
+
fi
|
|
2432
|
+
|
|
2301
2433
|
if [[ $total -eq 0 ]]; then
|
|
2302
2434
|
echo -e " ${GREEN}✓ Nothing pending — backlog is clear 暂无待处理任务${NC}"
|
|
2303
2435
|
echo ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanyao/roll",
|
|
3
|
-
"version": "2026.511.
|
|
3
|
+
"version": "2026.511.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"
|
|
@@ -48,8 +48,8 @@ $roll-design "user system design"
|
|
|
48
48
|
# Split Stories from an existing Plan
|
|
49
49
|
$roll-design --from-plan docs/features/auth-plan.md
|
|
50
50
|
|
|
51
|
-
# Directly create a Story
|
|
52
|
-
$roll-design
|
|
51
|
+
# Directly create a Story (auto-detected as User Story → Slice DDD)
|
|
52
|
+
$roll-design "user login feature"
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
## DDD Depth Scale
|
|
@@ -622,7 +622,7 @@ $roll-build US-AUTH-001 → TCR → CI/CD → Deploy
|
|
|
622
622
|
|
|
623
623
|
```
|
|
624
624
|
$roll-debug discovers issue → Suggest creating FIX
|
|
625
|
-
$roll-design
|
|
625
|
+
$roll-design "fix login API 404" → Create FIX-AUTH-001 ← auto-detected as Bug Fix
|
|
626
626
|
$roll-fix FIX-AUTH-001 → Quick fix
|
|
627
627
|
```
|
|
628
628
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roll-propose
|
|
3
|
+
license: MIT
|
|
4
|
+
allowed-tools: "Read, Glob, Grep, Write, Bash(git:*)"
|
|
5
|
+
description: |
|
|
6
|
+
Human-triggered product proposal generator. Reads project context (BACKLOG,
|
|
7
|
+
recent commits, existing skills) and generates 1–3 structured US drafts from
|
|
8
|
+
a user-facing perspective. Writes to PROPOSALS.md for human review — never
|
|
9
|
+
directly to BACKLOG. Distinct from roll-.dream (which surfaces technical debt
|
|
10
|
+
from execution experience); roll-propose thinks in user scenarios and feature
|
|
11
|
+
gaps.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# roll-propose
|
|
15
|
+
|
|
16
|
+
> Follows the Architecture Constraints, Development Discipline, and Engineering
|
|
17
|
+
> Common Sense defined in the project AGENTS.md.
|
|
18
|
+
|
|
19
|
+
Human-triggered skill for product-level feature ideation. Generates structured
|
|
20
|
+
User Story drafts from a product/user perspective and queues them in
|
|
21
|
+
PROPOSALS.md for human approval before entering BACKLOG.
|
|
22
|
+
|
|
23
|
+
## Distinct from roll-.dream
|
|
24
|
+
|
|
25
|
+
| | roll-propose | roll-.dream |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Triggered by | Human explicitly | Nightly schedule |
|
|
28
|
+
| Perspective | User-facing / product scenarios | Code health / technical debt |
|
|
29
|
+
| Output | PROPOSALS.md (pending approval) | BACKLOG (REFACTOR-XXX) |
|
|
30
|
+
| Thinking style | "What would users want next?" | "What is the code telling us?" |
|
|
31
|
+
|
|
32
|
+
## When to Use
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
$roll-propose # generate proposals from full context
|
|
36
|
+
$roll-propose 用户反馈里提到了XX # provide a focus hint
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## When Not to Use
|
|
40
|
+
|
|
41
|
+
- Describing a known defect or broken behavior (use `$roll-idea`)
|
|
42
|
+
- A story is already well-defined and ready to build (use `$roll-build`)
|
|
43
|
+
- Exploring technical architecture or design (use `$roll-design`)
|
|
44
|
+
- Surfacing code-level technical debt (use `$roll-.dream`)
|
|
45
|
+
|
|
46
|
+
## Behavior
|
|
47
|
+
|
|
48
|
+
### Step 1 — Gather Context
|
|
49
|
+
|
|
50
|
+
Read in parallel:
|
|
51
|
+
|
|
52
|
+
1. `BACKLOG.md` — all existing US-XXX, FIX-XXX, REFACTOR-XXX, IDEA-XXX entries (both Todo and Done) to avoid proposing duplicates
|
|
53
|
+
2. `PROPOSALS.md` (if exists) — already-proposed items (avoid re-proposing rejected or pending ones)
|
|
54
|
+
3. Recent 20 commits via `git log --oneline -20` — what has recently shipped
|
|
55
|
+
4. `skills/` directory listing — what capabilities roll already has
|
|
56
|
+
5. Optional: any focus hint passed by the user
|
|
57
|
+
|
|
58
|
+
### Step 2 — Think from User Perspective
|
|
59
|
+
|
|
60
|
+
Frame proposals from the **product engineer / end user** point of view:
|
|
61
|
+
|
|
62
|
+
- What recurring friction do users of roll face that no current skill addresses?
|
|
63
|
+
- What workflow is partially covered but has visible gaps?
|
|
64
|
+
- What would make the autonomous loop more legible, controllable, or trustworthy to its human owner?
|
|
65
|
+
|
|
66
|
+
Avoid technical-debt reasoning (that is roll-.dream's domain). Focus on:
|
|
67
|
+
- New user-visible commands or behaviors
|
|
68
|
+
- Improvements to existing UX (output clarity, discoverability, onboarding)
|
|
69
|
+
- Integrations that extend reach (new AI tools, editor support, CI patterns)
|
|
70
|
+
|
|
71
|
+
### Step 3 — Draft 1–3 Proposals
|
|
72
|
+
|
|
73
|
+
Generate between 1 and 3 proposals. For each:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
## PROPOSAL: {Short title}
|
|
77
|
+
|
|
78
|
+
**Motivation (why):**
|
|
79
|
+
One to two sentences from the user's perspective explaining the pain or opportunity.
|
|
80
|
+
|
|
81
|
+
**Target scenario:**
|
|
82
|
+
Concrete usage example — what the user does, what they see, what they gain.
|
|
83
|
+
|
|
84
|
+
**Acceptance Criteria (draft):**
|
|
85
|
+
- [ ] AC 1
|
|
86
|
+
- [ ] AC 2
|
|
87
|
+
- [ ] AC 3
|
|
88
|
+
|
|
89
|
+
**Suggested ID:** US-{EPIC}-{NNN} (best-guess prefix; human assigns final ID)
|
|
90
|
+
**Suggested Epic / Feature:** {name}
|
|
91
|
+
**Estimated complexity:** {S | M | L}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Complexity guide: S = one skill file or small bin/roll change, M = skill + bin/roll + tests, L = multi-file + new infrastructure.
|
|
95
|
+
|
|
96
|
+
### Step 4 — Write to PROPOSALS.md
|
|
97
|
+
|
|
98
|
+
Append to `PROPOSALS.md` in the project root (create if absent):
|
|
99
|
+
|
|
100
|
+
```markdown
|
|
101
|
+
---
|
|
102
|
+
proposed: {YYYY-MM-DD HH:MM}
|
|
103
|
+
status: pending
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
{proposals from Step 3}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use `---` as separator between proposal batches. Never overwrite existing content.
|
|
110
|
+
|
|
111
|
+
### Step 5 — Report
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
✅ roll-propose: {N} proposal(s) written to PROPOSALS.md
|
|
115
|
+
|
|
116
|
+
To approve: move the entry to BACKLOG.md and assign a US-XXX ID.
|
|
117
|
+
To reject: annotate with "Rejected: {reason}" to suppress future re-proposals.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Output Rules
|
|
121
|
+
|
|
122
|
+
- Write proposals in the same language as the project's primary documentation (Chinese for this project).
|
|
123
|
+
- Never write directly to BACKLOG.md — PROPOSALS.md is the staging area.
|
|
124
|
+
- If a similar proposal already exists in PROPOSALS.md (pending or rejected), note similarity and skip or merge rather than creating a duplicate.
|
|
125
|
+
- Aim for 2 proposals by default; generate 1 if context is thin, 3 if focus hint suggests a rich area.
|
|
126
|
+
|
|
127
|
+
## PROPOSALS.md Format
|
|
128
|
+
|
|
129
|
+
```markdown
|
|
130
|
+
# Roll Proposals
|
|
131
|
+
|
|
132
|
+
> 待审批提案。批准后手工移入 BACKLOG.md 并分配 US-XXX 编号。
|
|
133
|
+
> 拒绝时在条目末尾注明拒绝原因,防止 Agent 重复提出相似提案。
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
proposed: 2026-05-11 11:30
|
|
137
|
+
status: pending
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## PROPOSAL: ...
|
|
141
|
+
|
|
142
|
+
...
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
proposed: 2026-05-08 09:00
|
|
146
|
+
status: rejected
|
|
147
|
+
rejected_reason: 与现有 roll-design 功能重叠,不需要单独技能
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## PROPOSAL: ...
|
|
151
|
+
|
|
152
|
+
...
|
|
153
|
+
```
|