@seanyao/roll 2026.510.9 → 2026.511.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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## v2026.511.1
4
+ - **Changed**: roll loop 调度器切换到 launchd(macOS)— `roll setup` 自动安装 loop/dream/brief 三个 launchd 服务(默认关闭),`roll loop on/off/status` 统一走 launchctl 管理,幂等安装,Linux 保留 crontab 回退路径
5
+ - **Added**: roll-loop TCR 硬校验 — 故事完成后自动统计 `tcr:` 微提交数量,为 0 时将故事状态回退为 📋 Todo 并写 ALERT,防止 agent 跳过 TCR 节奏
6
+ - **Fixed**: CI 测试环境兼容 — 移除依赖本地 state.yaml 的 hello_world.bats,修复 GitHub Actions 持续失败
7
+
8
+ ## v2026.510.10
9
+ - **Fixed**: release.sh changelog 同步时序修复 — 修正条件逻辑和执行顺序,确保每次发版时 changelog 正确更新
10
+ - **Added**: roll-loop 22:00 自动执行验证 — 新增 hello_world.bats 作为 loop 定时执行的端到端存档,可回放确认调度器正常工作
11
+
12
+ ## v2026.510.9
13
+ - **Fixed**: CHANGELOG 改版本号分组 — 每个 release 独立 section,GitHub Release 增量内容提取正确
14
+ - **Fixed**: release.yml 加 fetch-depth: 0,确保历史 tag 在 workflow 中可见
15
+
3
16
  ## v2026.510.8
4
17
  - **Fixed**: release.sh 自洽 — 内联 agent 检测和 changelog 同步,发版流程不依赖外部调用
5
18
  - **Fixed**: roll release 命令重构 — 改为独立调用 roll-release skill,与 scripts/release.sh 解耦
package/README.md CHANGED
@@ -134,7 +134,7 @@ Research → Design → Build → Check → Fix → (loop)
134
134
  | Execute a planned Story | `$roll-build US-001` |
135
135
  | Free-form feature request | `$roll-build "add search to admin"` |
136
136
  | Bug fix | `$roll-fix FIX-001` |
137
- | Fast backlog capture (bug / idea) | `$roll-jot "description"` |
137
+ | Fast backlog capture (bug / idea) | `$roll-idea "description"` |
138
138
  | High-risk logic (payments, auth, state machines) | `$roll-spar "feature"` |
139
139
  | Deep research (product / company / tech) | `$roll-research "subject"` |
140
140
  | Patrol production for regressions | `$roll-sentinel patrol` |
@@ -148,7 +148,7 @@ Research → Design → Build → Check → Fix → (loop)
148
148
  | `$roll-build` | DESIGN+BUILD | Universal entry: Story ID, fix ID, or free-text fly mode |
149
149
  | `$roll-design` | DESIGN | Multi-turn discuss → [peer review] → DDD model → solution design → [peer review] → write Stories to BACKLOG |
150
150
  | `$roll-fix` | FIX | Bug fix / hotfix from BACKLOG |
151
- | `$roll-jot` | Support | Fast capture a bug or idea into BACKLOG.md |
151
+ | `$roll-idea` | Support | Fast capture a bug or idea into BACKLOG.md |
152
152
  | `$roll-spar` | BUILD | Adversarial TDD — Attacker writes tests, Defender writes code |
153
153
  | `$roll-sentinel` | CHECK | Scheduled production patrol |
154
154
  | `$roll-debug` | CHECK | Deep page diagnostics + root cause analysis |
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.510.9"
7
+ VERSION="2026.511.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"
@@ -544,6 +544,12 @@ cmd_setup() {
544
544
  _peer_ensure_state_dir
545
545
  ok "Peer state directory ready. Peer 状态目录已就绪。"
546
546
 
547
+ if [[ "$(uname)" == "Darwin" ]]; then
548
+ echo ""
549
+ info "Installing launchd plists... 正在安装 LaunchAgents..."
550
+ _install_launchd_plists "$(pwd -P)"
551
+ fi
552
+
547
553
  echo ""
548
554
  ok "Setup complete. 初始化完成。"
549
555
 
@@ -1729,8 +1735,128 @@ cmd_agent() {
1729
1735
  # ═══════════════════════════════════════════════════════════════════════════════
1730
1736
 
1731
1737
  _LOOP_TAG="# roll-loop"
1738
+ _SHARED_ROOT="${HOME}/.shared/roll"
1732
1739
  _LOOP_STATE="${HOME}/.shared/roll/loop/state.yaml"
1733
1740
  _LOOP_ALERT="${HOME}/.shared/roll/loop/ALERT.md"
1741
+ _LAUNCHD_DIR="${HOME}/Library/LaunchAgents"
1742
+
1743
+ # Returns a filesystem-safe slug combining the project basename and a 6-char
1744
+ # hash of the full path, ensuring uniqueness across sibling dirs with same name.
1745
+ _project_slug() {
1746
+ local path="$1"
1747
+ local base; base=$(basename "$path")
1748
+ local hash
1749
+ if command -v md5 &>/dev/null; then
1750
+ hash=$(printf '%s' "$path" | md5 | cut -c1-6)
1751
+ else
1752
+ hash=$(printf '%s' "$path" | md5sum | cut -c1-6)
1753
+ fi
1754
+ base=$(printf '%s' "$base" | tr -cs '[:alnum:]' '-' | sed 's/-*$//')
1755
+ printf '%s' "${base}-${hash}"
1756
+ }
1757
+
1758
+ _launchd_label() {
1759
+ local service="$1" project_path="$2"
1760
+ printf 'com.roll.%s.%s' "$service" "$(_project_slug "$project_path")"
1761
+ }
1762
+
1763
+ _launchd_plist_path() {
1764
+ local service="$1" project_path="$2"
1765
+ printf '%s/%s.plist' "$_LAUNCHD_DIR" "$(_launchd_label "$service" "$project_path")"
1766
+ }
1767
+
1768
+ _write_launchd_plist() {
1769
+ local plist_path="$1" label="$2" project_path="$3"
1770
+ local minute="$4" hour="$5" runner_script="$6"
1771
+
1772
+ local hour_xml=""
1773
+ [[ -n "$hour" ]] && hour_xml=" <key>Hour</key>
1774
+ <integer>${hour}</integer>
1775
+ "
1776
+
1777
+ local content
1778
+ content="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
1779
+ <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
1780
+ <plist version=\"1.0\">
1781
+ <dict>
1782
+ <key>Label</key>
1783
+ <string>${label}</string>
1784
+ <key>ProgramArguments</key>
1785
+ <array>
1786
+ <string>/bin/bash</string>
1787
+ <string>-l</string>
1788
+ <string>${runner_script}</string>
1789
+ </array>
1790
+ <key>StartCalendarInterval</key>
1791
+ <dict>
1792
+ <key>Minute</key>
1793
+ <integer>${minute}</integer>
1794
+ ${hour_xml} </dict>
1795
+ <key>WorkingDirectory</key>
1796
+ <string>${project_path}</string>
1797
+ </dict>
1798
+ </plist>"
1799
+
1800
+ if [[ -f "$plist_path" ]] && [[ "$(cat "$plist_path")" == "$content" ]]; then
1801
+ return 0
1802
+ fi
1803
+ printf '%s\n' "$content" > "$plist_path"
1804
+ }
1805
+
1806
+ _write_runner_script() {
1807
+ local script_path="$1" project_path="$2" cmd="$3" log_path="$4"
1808
+ mkdir -p "$(dirname "$script_path")"
1809
+ printf '#!/bin/bash -l\ncd "%s" && %s >> "%s" 2>&1\n' "$project_path" "$cmd" "$log_path" > "$script_path"
1810
+ chmod +x "$script_path"
1811
+ }
1812
+
1813
+ _launchd_is_loaded() {
1814
+ launchctl list "$1" &>/dev/null 2>&1
1815
+ }
1816
+
1817
+ # Install launchd plist files (disabled by default) and runner scripts for
1818
+ # a given project path. Idempotent — skips unchanged files. Does NOT load.
1819
+ _install_launchd_plists() {
1820
+ local project_path="$1"
1821
+ local sd="${ROLL_HOME}/skills"
1822
+ local shared="${_SHARED_ROOT}"
1823
+
1824
+ mkdir -p "$_LAUNCHD_DIR"
1825
+ mkdir -p "${shared}/loop" "${shared}/dream" "${shared}/brief"
1826
+
1827
+ local services=("loop" "dream" "brief")
1828
+ local skill_names=("roll-loop" "roll-.dream" "roll-brief")
1829
+ local minutes=("0" "0" "0")
1830
+ local hours=("" "1" "8")
1831
+
1832
+ local updated=0
1833
+ for i in "${!services[@]}"; do
1834
+ local svc="${services[$i]}"
1835
+ local skill="${skill_names[$i]}"
1836
+ local minute="${minutes[$i]}"
1837
+ local hour="${hours[$i]}"
1838
+ local label; label=$(_launchd_label "$svc" "$project_path")
1839
+ local plist; plist=$(_launchd_plist_path "$svc" "$project_path")
1840
+ local runner="${shared}/${svc}/run.sh"
1841
+ local log="${shared}/${svc}/cron.log"
1842
+ local cmd; cmd=$(_agent_skill_cmd "${sd}/${skill}/SKILL.md" 2>/dev/null || echo "roll loop now")
1843
+
1844
+ _write_runner_script "$runner" "$project_path" "cd \"${project_path}\" && ${cmd}" "$log"
1845
+
1846
+ local before=""
1847
+ [[ -f "$plist" ]] && before=$(cat "$plist")
1848
+ _write_launchd_plist "$plist" "$label" "$project_path" "$minute" "$hour" "$runner"
1849
+ local after; after=$(cat "$plist")
1850
+ [[ "$before" != "$after" ]] && updated=$((updated + 1))
1851
+ done
1852
+
1853
+ if [[ $updated -gt 0 ]]; then
1854
+ ok "Launchd plists installed (${updated} updated) LaunchAgents 已安装"
1855
+ echo " Run: roll loop on to activate 运行 roll loop on 激活"
1856
+ else
1857
+ ok "Launchd plists up to date LaunchAgents 无需更新"
1858
+ fi
1859
+ }
1734
1860
 
1735
1861
  _agent_skill_cmd() {
1736
1862
  local skill_path="$1"
@@ -1738,7 +1864,7 @@ _agent_skill_cmd() {
1738
1864
  # Strip YAML frontmatter inline for cron commands
1739
1865
  local strip="awk 'NR==1 && /^---$/{skip=1;next} skip && /^---$/{skip=0;next} !skip{print}' '${skill_path}'"
1740
1866
  case "$agent" in
1741
- claude) echo "claude -p \"\$(${strip})\"" ;;
1867
+ claude) local _bin; _bin=$(command -v claude 2>/dev/null || echo "claude"); echo "${_bin} -p \"\$(${strip})\"" ;;
1742
1868
  kimi) echo "kimi --quiet -p \"\$(${strip})\"" ;;
1743
1869
  deepseek) echo "deepseek \"\$(${strip})\"" ;;
1744
1870
  pi) echo "pi -p \"\$(${strip})\"" ;;
@@ -1751,31 +1877,57 @@ _agent_skill_cmd() {
1751
1877
  cmd_loop() {
1752
1878
  local subcmd="${1:-status}"; shift || true
1753
1879
  case "$subcmd" in
1754
- on) _loop_on ;;
1755
- off) _loop_off ;;
1756
- now) _loop_now ;;
1757
- status) _loop_status ;;
1758
- resume) _loop_resume ;;
1759
- reset) _loop_reset ;;
1760
- *) err "Usage: roll loop <on|off|now|status|resume|reset>"; exit 1 ;;
1880
+ on) _loop_on ;;
1881
+ off) _loop_off ;;
1882
+ now) _loop_now ;;
1883
+ status) _loop_status ;;
1884
+ monitor) _loop_monitor "${1:-3}" ;;
1885
+ resume) _loop_resume ;;
1886
+ reset) _loop_reset ;;
1887
+ *) err "Usage: roll loop <on|off|now|status|monitor|resume|reset>"; exit 1 ;;
1761
1888
  esac
1762
1889
  }
1763
1890
 
1764
1891
  _loop_on() {
1765
1892
  local project_path; project_path=$(pwd -P)
1766
1893
  local agent; agent=$(_project_agent)
1767
- local sd="${ROLL_HOME}/skills"
1768
1894
 
1895
+ if [[ "$(uname)" == "Darwin" ]]; then
1896
+ _install_launchd_plists "$project_path" >/dev/null
1897
+
1898
+ local all_loaded=true
1899
+ for svc in loop dream brief; do
1900
+ local label; label=$(_launchd_label "$svc" "$project_path")
1901
+ if ! _launchd_is_loaded "$label"; then
1902
+ all_loaded=false
1903
+ launchctl load "$(_launchd_plist_path "$svc" "$project_path")" 2>/dev/null || true
1904
+ fi
1905
+ done
1906
+
1907
+ if $all_loaded; then
1908
+ warn "Loop already enabled for this project 当前项目 loop 已启用"; return 0
1909
+ fi
1910
+
1911
+ ok "Loop enabled 已启用"
1912
+ echo " • roll-loop every hour :00 每小时整点"
1913
+ echo " • roll-.dream nightly at 01:00 每晚 01:00"
1914
+ echo " • roll-brief daily at 08:00 每天 08:00"
1915
+ echo " • Agent: ${agent} (change: roll agent use <name>)"
1916
+ return 0
1917
+ fi
1918
+
1919
+ # Linux: crontab
1920
+ local sd="${ROLL_HOME}/skills"
1769
1921
  if crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}"; then
1770
1922
  warn "Loop already enabled for this project 当前项目 loop 已启用"; return 0
1771
1923
  fi
1772
1924
 
1773
- mkdir -p ~/.shared/roll/{loop,dream,brief}
1925
+ mkdir -p "${_SHARED_ROOT}/loop" "${_SHARED_ROOT}/dream" "${_SHARED_ROOT}/brief"
1774
1926
 
1775
1927
  local loop_cmd dream_cmd brief_cmd
1776
- loop_cmd="cd \"${project_path}\" && $(_agent_skill_cmd "${sd}/roll-loop/SKILL.md") >> ~/.shared/roll/loop/cron.log 2>&1"
1777
- dream_cmd="cd \"${project_path}\" && $(_agent_skill_cmd "${sd}/roll-.dream/SKILL.md") >> ~/.shared/roll/dream/cron.log 2>&1"
1778
- brief_cmd="cd \"${project_path}\" && $(_agent_skill_cmd "${sd}/roll-brief/SKILL.md") >> ~/.shared/roll/brief/cron.log 2>&1"
1928
+ loop_cmd="cd \"${project_path}\" && $(_agent_skill_cmd "${sd}/roll-loop/SKILL.md") >> ${_SHARED_ROOT}/loop/cron.log 2>&1"
1929
+ dream_cmd="cd \"${project_path}\" && $(_agent_skill_cmd "${sd}/roll-.dream/SKILL.md") >> ${_SHARED_ROOT}/dream/cron.log 2>&1"
1930
+ brief_cmd="cd \"${project_path}\" && $(_agent_skill_cmd "${sd}/roll-brief/SKILL.md") >> ${_SHARED_ROOT}/brief/cron.log 2>&1"
1779
1931
 
1780
1932
  (
1781
1933
  crontab -l 2>/dev/null
@@ -1793,6 +1945,24 @@ _loop_on() {
1793
1945
 
1794
1946
  _loop_off() {
1795
1947
  local project_path; project_path=$(pwd -P)
1948
+
1949
+ if [[ "$(uname)" == "Darwin" ]]; then
1950
+ local any_loaded=false
1951
+ for svc in loop dream brief; do
1952
+ local label; label=$(_launchd_label "$svc" "$project_path")
1953
+ if _launchd_is_loaded "$label"; then
1954
+ any_loaded=true
1955
+ launchctl unload "$(_launchd_plist_path "$svc" "$project_path")" 2>/dev/null || true
1956
+ fi
1957
+ done
1958
+ if ! $any_loaded; then
1959
+ warn "Loop not enabled for this project 当前项目 loop 未启用"; return 0
1960
+ fi
1961
+ ok "Loop disabled 已停用"
1962
+ return 0
1963
+ fi
1964
+
1965
+ # Linux: crontab
1796
1966
  if ! crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}"; then
1797
1967
  warn "Loop not enabled for this project 当前项目 loop 未启用"; return 0
1798
1968
  fi
@@ -1805,14 +1975,22 @@ _loop_now() {
1805
1975
  warn "Loop already running loop 正在运行中"; return 0
1806
1976
  fi
1807
1977
  info "Triggering loop cycle... 正在触发一个周期..."
1808
- _agent_run_skill "roll-loop"
1978
+ local log_file="${_SHARED_ROOT}/loop/launchd.log"
1979
+ mkdir -p "$(dirname "$log_file")"
1980
+ _agent_run_skill "roll-loop" | tee -a "$log_file"
1809
1981
  }
1810
1982
 
1811
1983
  _loop_status() {
1812
1984
  local project_path; project_path=$(pwd -P)
1813
1985
  local agent; agent=$(_project_agent)
1814
1986
  echo ""
1815
- if crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}"; then
1987
+ local loop_enabled=false
1988
+ if [[ "$(uname)" == "Darwin" ]]; then
1989
+ _launchd_is_loaded "$(_launchd_label "loop" "$project_path")" && loop_enabled=true
1990
+ else
1991
+ crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}" && loop_enabled=true
1992
+ fi
1993
+ if $loop_enabled; then
1816
1994
  echo -e " Scheduler ${GREEN}● enabled${NC} Agent: ${CYAN}${agent}${NC}"
1817
1995
  else
1818
1996
  echo -e " Scheduler ${YELLOW}○ disabled${NC} run: roll loop on"
@@ -1842,6 +2020,176 @@ _loop_reset() {
1842
2020
  fi
1843
2021
  }
1844
2022
 
2023
+ # Count `tcr:` prefixed commits in the current git repo since started_at timestamp.
2024
+ _loop_tcr_count() {
2025
+ local started_at="$1"
2026
+ git log --oneline --since="${started_at}" 2>/dev/null \
2027
+ | awk '/^[a-f0-9]+ tcr:/{n++} END{print n+0}'
2028
+ }
2029
+
2030
+ # Verify TCR rhythm after a story completes. Returns 0 if ok, 1 if no TCR commits.
2031
+ # On failure: reverts story in BACKLOG.md to 📋 Todo and writes ALERT.
2032
+ _loop_enforce_tcr() {
2033
+ local story_id="$1"
2034
+ local started_at="${2:-}"
2035
+
2036
+ [[ -z "$started_at" ]] && return 0
2037
+
2038
+ local count; count=$(_loop_tcr_count "$started_at")
2039
+
2040
+ if [[ "$count" -eq 0 ]]; then
2041
+ # Revert story status
2042
+ if [[ -f "BACKLOG.md" ]]; then
2043
+ local tmp; tmp=$(mktemp)
2044
+ sed "/\[${story_id}\]/s/ | ✅ Done |/ | 📋 Todo |/" BACKLOG.md > "$tmp" \
2045
+ && mv "$tmp" BACKLOG.md
2046
+ fi
2047
+
2048
+ # Write ALERT
2049
+ mkdir -p "$(dirname "$_LOOP_ALERT")"
2050
+ cat > "$_LOOP_ALERT" << EOF
2051
+ # ALERT — TCR check failed
2052
+
2053
+ **Time**: $(date '+%Y-%m-%d %H:%M')
2054
+ **Story**: ${story_id}
2055
+ **Reason**: zero tcr: commits since story start (${started_at})
2056
+
2057
+ **Action required** (choose one):
2058
+ - Add TCR commits and re-run: \`roll loop now\`
2059
+ - Take over manually: \`\$roll-build ${story_id}\`
2060
+ - Reset and retry: \`roll loop reset\` then \`roll loop now\`
2061
+ EOF
2062
+ return 1
2063
+ fi
2064
+
2065
+ return 0
2066
+ }
2067
+
2068
+ _loop_monitor() {
2069
+ local interval="${1:-3}"
2070
+ local project_path; project_path=$(pwd -P)
2071
+ local project_name; project_name=$(basename "$project_path")
2072
+
2073
+ # Determine terminal clear capability
2074
+ local clear_cmd="clear"
2075
+ command -v clear &>/dev/null || clear_cmd="echo ''"
2076
+
2077
+ while true; do
2078
+ $clear_cmd
2079
+ local agent; agent=$(_project_agent)
2080
+ local now; now=$(date '+%Y-%m-%d %H:%M:%S')
2081
+
2082
+ echo -e "\n ${BOLD}${CYAN}roll loop monitor${NC} ${YELLOW}${project_name}${NC} ${now} (Ctrl-C to exit)\n"
2083
+
2084
+ # Services status (three services on macOS, single on Linux)
2085
+ echo -e " ${BOLD}Services 服务状态${NC} Agent: ${CYAN}${agent}${NC}"
2086
+ if [[ "$(uname)" == "Darwin" ]]; then
2087
+ local svc_info svc schedule
2088
+ for svc_info in "loop:hourly" "dream:01:00" "brief:08:00"; do
2089
+ svc="${svc_info%%:*}"
2090
+ schedule="${svc_info#*:}"
2091
+ if _launchd_is_loaded "$(_launchd_label "$svc" "$project_path")"; then
2092
+ printf " ${GREEN}%-8s ● enabled${NC} (%s)\n" "$svc" "$schedule"
2093
+ else
2094
+ printf " ${YELLOW}%-8s ○ disabled${NC} (%s)\n" "$svc" "$schedule"
2095
+ fi
2096
+ done
2097
+ else
2098
+ if crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}"; then
2099
+ echo -e " ${GREEN}loop ● enabled${NC}"
2100
+ else
2101
+ echo -e " ${YELLOW}loop ○ disabled${NC} run: roll loop on"
2102
+ fi
2103
+ fi
2104
+
2105
+ # Current state
2106
+ if [[ -f "$_LOOP_STATE" ]]; then
2107
+ local status current_item started_at run_id
2108
+ status=$(grep '^status:' "$_LOOP_STATE" | awk '{print $2}')
2109
+ current_item=$(grep '^current_item:' "$_LOOP_STATE" | awk '{print $2}')
2110
+ started_at=$(grep '^started_at:' "$_LOOP_STATE" | cut -d' ' -f2- | tr -d '"')
2111
+ run_id=$(grep '^run_id:' "$_LOOP_STATE" | awk '{print $2}')
2112
+ echo ""
2113
+ case "$status" in
2114
+ running) echo -e " State ${GREEN}▶ running${NC} ${CYAN}${current_item}${NC} started: ${started_at} run: ${run_id}" ;;
2115
+ paused) echo -e " State ${RED}‖ paused${NC} on: ${current_item}" ;;
2116
+ idle) echo -e " State ${YELLOW}○ idle${NC}" ;;
2117
+ *) echo -e " State ${status}" ;;
2118
+ esac
2119
+ else
2120
+ echo ""
2121
+ echo -e " State ${YELLOW}○ no state file${NC}"
2122
+ fi
2123
+
2124
+ # Alert
2125
+ if [[ -f "$_LOOP_ALERT" ]]; then
2126
+ echo ""
2127
+ echo -e " ${RED}⚠ ALERT:${NC}"
2128
+ sed 's/^/ /' "$_LOOP_ALERT"
2129
+ fi
2130
+
2131
+ # Queue: pending items
2132
+ echo ""
2133
+ echo -e " ${BOLD}Queue 待处理队列${NC}"
2134
+ local backlog="BACKLOG.md"
2135
+ if [[ -f "$backlog" ]]; then
2136
+ local queue_count=0
2137
+ local fix_pending us_pending refactor_pending
2138
+ fix_pending=$(grep -E '^\| FIX-' "$backlog" | grep '📋 Todo' || true)
2139
+ us_pending=$(grep -E '^\| \[US-' "$backlog" | grep '📋 Todo' || true)
2140
+ refactor_pending=$(grep -E '^\| REFACTOR-' "$backlog" | grep '📋 Todo' || true)
2141
+
2142
+ # FIX first (priority)
2143
+ while IFS= read -r line; do
2144
+ [[ -z "$line" ]] && continue
2145
+ local id desc
2146
+ id=$(echo "$line" | awk -F'|' '{print $2}' | tr -d ' ')
2147
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-60)
2148
+ printf " ${RED}%-14s${NC} %s\n" "$id" "$desc"
2149
+ (( queue_count++ )) || true
2150
+ done <<< "$fix_pending"
2151
+
2152
+ # US stories
2153
+ while IFS= read -r line; do
2154
+ [[ -z "$line" ]] && continue
2155
+ local id desc
2156
+ id=$(echo "$line" | sed 's/.*\[\(US-[^]]*\)\].*/\1/')
2157
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-60)
2158
+ printf " ${CYAN}%-14s${NC} %s\n" "$id" "$desc"
2159
+ (( queue_count++ )) || true
2160
+ done <<< "$us_pending"
2161
+
2162
+ # Refactors
2163
+ while IFS= read -r line; do
2164
+ [[ -z "$line" ]] && continue
2165
+ local id desc
2166
+ id=$(echo "$line" | awk -F'|' '{print $2}' | tr -d ' ')
2167
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-60)
2168
+ printf " ${YELLOW}%-14s${NC} %s\n" "$id" "$desc"
2169
+ (( queue_count++ )) || true
2170
+ done <<< "$refactor_pending"
2171
+
2172
+ [[ $queue_count -eq 0 ]] && echo -e " ${GREEN}✓ empty${NC}"
2173
+ else
2174
+ echo " BACKLOG.md not found"
2175
+ fi
2176
+
2177
+ # Log tail (launchd.log)
2178
+ local log_file="${_SHARED_ROOT}/loop/launchd.log"
2179
+ echo ""
2180
+ echo -e " ─────────────────────────────────────────────────────"
2181
+ echo -e " ${BOLD}Log Tail 实时日志${NC} (~/.shared/roll/loop/launchd.log, last 10 lines)"
2182
+ if [[ -f "$log_file" && -s "$log_file" ]]; then
2183
+ tail -10 "$log_file" | sed 's/^/ /'
2184
+ else
2185
+ echo -e " ${YELLOW}(no log yet)${NC}"
2186
+ fi
2187
+
2188
+ echo ""
2189
+ sleep "$interval"
2190
+ done
2191
+ }
2192
+
1845
2193
  # ═══════════════════════════════════════════════════════════════════════════════
1846
2194
  # BRIEF — owner-facing project digest
1847
2195
  # ═══════════════════════════════════════════════════════════════════════════════
@@ -1872,6 +2220,75 @@ cmd_release() {
1872
2220
  _agent_run_skill "roll-release"
1873
2221
  }
1874
2222
 
2223
+ # ═══════════════════════════════════════════════════════════════════════════════
2224
+ # BACKLOG — show pending tasks
2225
+ # ═══════════════════════════════════════════════════════════════════════════════
2226
+
2227
+ cmd_backlog() {
2228
+ local backlog="BACKLOG.md"
2229
+ if [[ ! -f "$backlog" ]]; then
2230
+ err "BACKLOG.md not found — run 'roll init' first 未找到 BACKLOG.md,请先运行 roll init"
2231
+ return 1
2232
+ fi
2233
+
2234
+ local us_items fix_items refactor_items total=0
2235
+
2236
+ us_items=$(grep -E '^\| \[US-' "$backlog" | grep '📋 Todo' || true)
2237
+ fix_items=$(grep -E '^\| FIX-' "$backlog" | grep '📋 Todo' || true)
2238
+ refactor_items=$(grep -E '^\| REFACTOR-' "$backlog" | grep '📋 Todo' || true)
2239
+
2240
+ local us_count fix_count refactor_count
2241
+ us_count=$(echo "$us_items" | grep -c . || true)
2242
+ fix_count=$(echo "$fix_items" | grep -c . || true)
2243
+ refactor_count=$(echo "$refactor_items" | grep -c . || true)
2244
+ [[ -z "$us_items" ]] && us_count=0
2245
+ [[ -z "$fix_items" ]] && fix_count=0
2246
+ [[ -z "$refactor_items" ]] && refactor_count=0
2247
+ total=$(( us_count + fix_count + refactor_count ))
2248
+
2249
+ echo ""
2250
+ echo -e " ${BOLD}Pending Backlog 待处理任务${NC} (${total} items)"
2251
+ echo ""
2252
+
2253
+ if [[ $fix_count -gt 0 ]]; then
2254
+ echo -e " ${RED}Bug Fixes 缺陷修复 (${fix_count})${NC}"
2255
+ while IFS= read -r line; do
2256
+ local id desc
2257
+ id=$(echo "$line" | awk -F'|' '{print $2}' | tr -d ' ')
2258
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//')
2259
+ printf " %-12s %s\n" "$id" "$desc"
2260
+ done <<< "$fix_items"
2261
+ echo ""
2262
+ fi
2263
+
2264
+ if [[ $us_count -gt 0 ]]; then
2265
+ echo -e " ${CYAN}User Stories 用户故事 (${us_count})${NC}"
2266
+ while IFS= read -r line; do
2267
+ local id desc
2268
+ id=$(echo "$line" | sed 's/.*\[\(US-[^]]*\)\].*/\1/')
2269
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//')
2270
+ printf " %-14s %s\n" "$id" "$desc"
2271
+ done <<< "$us_items"
2272
+ echo ""
2273
+ fi
2274
+
2275
+ if [[ $refactor_count -gt 0 ]]; then
2276
+ echo -e " ${YELLOW}Refactors 重构 (${refactor_count})${NC}"
2277
+ while IFS= read -r line; do
2278
+ local id desc
2279
+ id=$(echo "$line" | awk -F'|' '{print $2}' | tr -d ' ')
2280
+ desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//')
2281
+ printf " %-16s %s\n" "$id" "$desc"
2282
+ done <<< "$refactor_items"
2283
+ echo ""
2284
+ fi
2285
+
2286
+ if [[ $total -eq 0 ]]; then
2287
+ echo -e " ${GREEN}✓ Nothing pending — backlog is clear 暂无待处理任务${NC}"
2288
+ echo ""
2289
+ fi
2290
+ }
2291
+
1875
2292
  # ─────────────────────────────────────────────────────────────────────────────
1876
2293
 
1877
2294
  _dashboard() {
@@ -1888,6 +2305,17 @@ _dashboard() {
1888
2305
  fi
1889
2306
  [[ -f "$_LOOP_ALERT" ]] && echo -e " ${RED}⚠ ALERT — run: roll loop status${NC}"
1890
2307
 
2308
+ # Backlog summary
2309
+ if [[ -f "BACKLOG.md" ]]; then
2310
+ local pending_count
2311
+ pending_count=$(grep -c '📋 Todo' "BACKLOG.md" 2>/dev/null || echo 0)
2312
+ if (( pending_count > 0 )); then
2313
+ echo -e " Backlog ${YELLOW}${pending_count} pending${NC} run: roll backlog"
2314
+ else
2315
+ echo -e " Backlog ${GREEN}✓ clear${NC}"
2316
+ fi
2317
+ fi
2318
+
1891
2319
  local briefs_dir="docs/briefs"
1892
2320
  local latest; latest=$(ls "${briefs_dir}"/*.md 2>/dev/null | sort | tail -1 || true)
1893
2321
  if [[ -n "$latest" ]]; then
@@ -1903,7 +2331,7 @@ _dashboard() {
1903
2331
  fi
1904
2332
 
1905
2333
  echo ""
1906
- echo " loop <on|off|now|status|resume|reset> · brief · agent use <name> · release"
2334
+ echo " loop <on|off|now|status|monitor|resume|reset> · brief · backlog · agent use <name> · release"
1907
2335
  echo " setup · update · init · status · peer · help"
1908
2336
  echo ""
1909
2337
  }
@@ -1930,8 +2358,9 @@ usage() {
1930
2358
  echo " init [Project] Create AGENTS.md + BACKLOG.md + docs/ 初始化项目工作流文件"
1931
2359
  echo " status [Diagnostic] Show current state 显示当前状态"
1932
2360
  echo " peer [Peer Review] Cross-agent negotiation 跨 Agent 协商对审"
1933
- echo " loop <on|off|now|status|resume|reset> [Autonomous] Manage scheduled BACKLOG executor 管理自主执行循环"
2361
+ echo " loop <on|off|now|status|monitor|resume|reset> [Autonomous] Manage scheduled BACKLOG executor 管理自主执行循环"
1934
2362
  echo " brief [Digest] Show latest owner brief (regenerate if stale) 展示最新简报"
2363
+ echo " backlog [View] Show all pending tasks from BACKLOG.md 显示待处理任务清单"
1935
2364
  echo " agent [use <name>|list] [Config] Per-project agent selection 切换项目 agent"
1936
2365
  echo " release [Publish] Sync changelog + version bump + npm publish 同步日志并发版"
1937
2366
  echo ""
@@ -1941,6 +2370,7 @@ usage() {
1941
2370
  echo " roll init # New or re-merge project (run in project) 新建或重新合并(项目目录)"
1942
2371
  echo " roll loop on # Enable autonomous loop (cron) 启用自主执行循环"
1943
2372
  echo " roll brief # Show latest brief 查看最新简报"
2373
+ echo " roll backlog # Show pending BACKLOG items 查看待处理任务"
1944
2374
  echo " roll agent use kimi # Switch this project to kimi 切换当前项目到 kimi"
1945
2375
 
1946
2376
  }
@@ -1957,6 +2387,7 @@ main() {
1957
2387
  peer) cmd_peer "$@" ;;
1958
2388
  loop) cmd_loop "$@" ;;
1959
2389
  brief) cmd_brief "$@" ;;
2390
+ backlog) cmd_backlog "$@" ;;
1960
2391
  agent) cmd_agent "$@" ;;
1961
2392
  release) cmd_release "$@" ;;
1962
2393
  version|--version|-v) echo "roll v${VERSION}" ;;
@@ -31,7 +31,7 @@
31
31
 
32
32
  ## 4. Workflow
33
33
  - **Scope Gate**: Only implement what is explicitly listed in the AC. Nothing more.
34
- - Requests made in conversation are NOT AC — capture with `roll-jot` first.
34
+ - Requests made in conversation are NOT AC — capture with `roll-idea` first.
35
35
  - Any new Story/Fix requires design doc + user confirmation before TCR starts.
36
36
  - Do not commit without user approval unless explicitly told to auto-commit.
37
37
  - **TCR**: Test -> Green = Commit / Red = Revert. No WIP commits.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanyao/roll",
3
- "version": "2026.510.9",
3
+ "version": "2026.511.1",
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"
@@ -76,12 +76,23 @@ Bad:
76
76
 
77
77
  ### 4. Version Number Format
78
78
 
79
+ Determine the current version being released:
80
+
81
+ ```bash
82
+ VERSION=$(node -e "process.stdout.write(require('./package.json').version)" 2>/dev/null \
83
+ || git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//')
79
84
  ```
80
- YYYY.MM.DD
81
- YYYY.MM.DD-1 (multiple releases on the same day)
82
- YYYY.MM.DD-2
85
+
86
+ `package.json` is the authoritative source — it is updated before this skill runs. Fall back to git tags only if `package.json` is unavailable.
87
+
88
+ The CHANGELOG section header uses the full version string with a `v` prefix:
89
+
90
+ ```
91
+ ## v${VERSION}
83
92
  ```
84
93
 
94
+ For the human-readable date display within entries, use `YYYY.MM.DD` format.
95
+
85
96
  ### 5. Generate CHANGELOG.md
86
97
 
87
98
  **Create mode** (first time):
@@ -22,7 +22,7 @@ Auto-invoked by `roll-build` (Fly mode) when the user input is:
22
22
  ## When Not to Use
23
23
 
24
24
  - Intent is already clear and actionable
25
- - User gives a specific command with a skill trigger (e.g. `$roll-jot ...`)
25
+ - User gives a specific command with a skill trigger (e.g. `$roll-idea ...`)
26
26
  - User is answering a clarification question you just asked
27
27
  - The task is simple enough that misinterpretation risk is negligible
28
28
  - User messy thoughts need restatement rather than questioning (use `$roll-.echo`)
@@ -233,10 +233,10 @@ CI failure
233
233
 
234
234
  ```bash
235
235
  # For fixable bugs — create FIX entry
236
- $roll-jot fix "CI: {step} fails — {root cause summary}"
236
+ $roll-idea fix "CI: {step} fails — {root cause summary}"
237
237
 
238
238
  # For flaky/environmental issues — create IDEA entry
239
- $roll-jot idea "CI: investigate flaky {test name}"
239
+ $roll-idea idea "CI: investigate flaky {test name}"
240
240
  ```
241
241
 
242
242
  ### Step 4: Execute Fix
@@ -95,52 +95,51 @@ A simple heuristic — not a gate, just a signal for the human:
95
95
 
96
96
  ### Step 4 — Write Brief
97
97
 
98
- Output to `docs/briefs/YYYY-MM-DD-HH.md`:
98
+ Output to `docs/briefs/YYYY-MM-DD-HH.md`,全部用中文输出:
99
99
 
100
100
  ```markdown
101
- # Brief {YYYY-MM-DD HH:mm}
101
+ # 简报 {YYYY-MM-DD HH:mm}
102
102
 
103
- ## What's Done
104
- | ID | Description | Type |
103
+ ## 已完成
104
+ | 编号 | 描述 | 类型 |
105
105
  |----|-------------|------|
106
- | US-XXX | {title} | Story |
107
- | FIX-XXX | {title} | Fix |
108
- | REFACTOR-XXX | {title} | Refactor |
106
+ | US-XXX | {标题} | 用户故事 |
107
+ | FIX-XXX | {标题} | 缺陷修复 |
108
+ | REFACTOR-XXX | {标题} | 重构 |
109
109
 
110
- ## In Progress
111
- | ID | Description |
110
+ ## 进行中
111
+ | 编号 | 描述 |
112
112
  |----|-------------|
113
- | US-XXX | {title} — started {date} |
113
+ | US-XXX | {标题} — 开始于 {date} |
114
114
 
115
- ## Queue ({N} items)
116
- | ID | Description | Priority |
115
+ ## 待处理队列({N} 项)
116
+ | 编号 | 描述 | 优先级 |
117
117
  |----|-------------|----------|
118
- | US-XXX | {title} | high |
118
+ | US-XXX | {标题} | |
119
119
 
120
- ## Overnight Dream Findings
121
- {summary from docs/dream/ since last brief, or "No new findings."}
120
+ ## 夜间巡检发现
121
+ {来自 docs/dream/ 的摘要,无则写"暂无新发现。"}
122
122
 
123
- ## Escalations Requiring Human Input
124
- {any alerts, or "None — agent operating normally."}
123
+ ## 需要人工介入的升级事项
124
+ {任何告警,无则写" — agent 运行正常。"}
125
125
 
126
- ## Release Readiness
127
- {✅ Release candidate / ⚠️ Holdreason}
126
+ ## 发布就绪状态
127
+ {✅ 可发布 / ⚠️ 暂缓原因}
128
128
 
129
129
  ---
130
- *Next scheduled brief: {datetime}*
130
+ *下次定时简报:{datetime}*
131
131
  ```
132
132
 
133
133
  ### Step 5 — Notify
134
134
 
135
- After writing the file, print the brief path so it's visible in the terminal
136
- or CI log:
135
+ 写完文件后在终端或 CI 日志中打印简报路径:
137
136
 
138
137
  ```
139
- 📋 Brief written: docs/briefs/2026-05-10-08.md
140
- Release readiness: ✅ Release candidate
138
+ 📋 简报已生成:docs/briefs/2026-05-10-08.md
139
+ 发布就绪:✅ 可发布
141
140
  ```
142
141
 
143
- If there are escalations, print them prominently so they're impossible to miss.
142
+ 有升级事项时须显著打印,不得遗漏。
144
143
 
145
144
  ## Scheduler Configuration
146
145
 
@@ -18,7 +18,7 @@ One entry point. Any input. Full delivery.
18
18
  Input received
19
19
  ├── matches "US-[A-Z]+-[0-9]+" → Story mode: read BACKLOG → TCR workflow
20
20
  ├── matches "FIX-[A-Z]+-[0-9]+" → redirect to $roll-fix
21
- ├── matches "IDEA-[0-9]+" → redirect to $roll-jot (lookup and expand)
21
+ ├── matches "IDEA-[0-9]+" → redirect to $roll-idea (lookup and expand)
22
22
  └── anything else → Fly mode: clarify → design → execute
23
23
  ```
24
24
 
@@ -654,7 +654,7 @@ When complex state management is error-prone → consider full reset + re-initia
654
654
  roll-build → ship anything (new idea, US-ID, free-text request)
655
655
  roll-fix → fix a specific known bug (FIX-XXX / BUG-XXX)
656
656
  roll-design → plan and design before building (no code output)
657
- roll-jot → fast capture a bug or idea into BACKLOG.md
657
+ roll-idea → fast capture a bug or idea into BACKLOG.md
658
658
  roll-.clarify → passive scope clarification for vague build requests
659
659
  ```
660
660
 
@@ -29,7 +29,7 @@ Discuss approaches, design architecture, plan requirements, and write to `BACKLO
29
29
 
30
30
  ## When Not to Use
31
31
 
32
- - One-liner capture of ideas or bugs (use `$roll-jot`)
32
+ - One-liner capture of ideas or bugs (use `$roll-idea`)
33
33
  - Executing an already-split US-XXX (use `$roll-build`)
34
34
  - Fixing a well-defined FIX-XXX (use `$roll-fix`)
35
35
 
@@ -1,22 +1,22 @@
1
1
  ---
2
- name: roll-jot
2
+ name: roll-idea
3
3
  license: MIT
4
4
  allowed-tools: "Read, Edit"
5
5
  description: "Fast backlog capture. Analyzes a short description, classifies it as bug or idea, and appends it to BACKLOG.md with an auto-incremented ID."
6
6
  ---
7
7
 
8
- # roll-jot
8
+ # roll-idea
9
9
 
10
10
  > One-liner in, backlog entry out. No questions asked.
11
11
 
12
12
  ## Trigger
13
13
 
14
- User explicitly invokes `roll-jot` with a free-text description.
14
+ User explicitly invokes `roll-idea` with a free-text description.
15
15
 
16
16
  ```
17
- $roll-jot 评价页面的星星也要纳入到 draft 的 scope 里
18
- $roll-jot 手机端星星指标一行一个从上往下排
19
- $roll-jot 给 HOD 加一个批量导出 PDF 的功能
17
+ $roll-idea 评价页面的星星也要纳入到 draft 的 scope 里
18
+ $roll-idea 手机端星星指标一行一个从上往下排
19
+ $roll-idea 给 HOD 加一个批量导出 PDF 的功能
20
20
  ```
21
21
 
22
22
  ## When Not to Use
@@ -92,9 +92,13 @@ run_id: loop-20260510-0200
92
92
 
93
93
  After each item completes:
94
94
 
95
- 1. Update state file: `status: idle`
96
- 2. Check if a Feature is now fully complete (all its Stories ✅)
97
- 3. If yes and `brief_on_feature_complete: true` invoke `Skill("roll-brief")`
95
+ 1. **TCR 硬校验** call `_loop_enforce_tcr <story_id> <started_at>`:
96
+ - Count `tcr:` prefix commits since `started_at` via `git log --oneline --since=<started_at>`
97
+ - Count == 0 → revert story status in BACKLOG.md from Done → 📋 Todo; write ALERT to `~/.shared/roll/loop/ALERT.md` with story ID, time, reason "zero tcr: commits since story start", and suggested actions (`roll loop now` / `$roll-build <id>` / `roll loop reset`)
98
+ - Count > 0 → continue normally
99
+ 2. Update state file: `status: idle`
100
+ 3. Check if a Feature is now fully complete (all its Stories ✅)
101
+ 4. If yes and `brief_on_feature_complete: true` → invoke `Skill("roll-brief")`
98
102
 
99
103
  ### Step 5 — Write Run Summary
100
104
 
@@ -23,7 +23,7 @@ $roll-notes 今天的 code review 给了很好的反馈
23
23
 
24
24
  ## When Not to Use
25
25
 
26
- - Capturing a bug or feature idea into BACKLOG (use `$roll-jot`)
26
+ - Capturing a bug or feature idea into BACKLOG (use `$roll-idea`)
27
27
  - Generating user-facing release notes (use `$roll-.changelog`)
28
28
  - Writing design documents or AC (use `$roll-design`)
29
29