@seanyao/roll 2026.510.10 → 2026.511.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 +13 -0
- package/README.md +106 -28
- package/bin/roll +476 -28
- package/conventions/global/AGENTS.md +1 -1
- package/package.json +1 -1
- package/skills/roll-.clarify/SKILL.md +1 -1
- package/skills/roll-.qa/SKILL.md +2 -2
- package/skills/roll-brief/SKILL.md +45 -25
- package/skills/roll-build/SKILL.md +2 -2
- package/skills/roll-design/SKILL.md +1 -1
- package/skills/{roll-jot → roll-idea}/SKILL.md +6 -6
- package/skills/roll-loop/SKILL.md +7 -3
- package/skills/roll-notes/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2026.511.2
|
|
4
|
+
- **Added**: roll loop monitor 三服务状态 — 监控台新增 loop/dream/brief 三个 launchd 服务的运行状态、调度时间和实时 log tail,一屏掌握全局执行情况
|
|
5
|
+
- **Fixed**: dashboard 多处展示问题 — 修复 pending_count 算术错误、brief 内联显示 release readiness、移除底部冗余命令列表
|
|
6
|
+
- **Fixed**: loop 异常退出后 state 未重置 — 防止 queue 卡住导致后续任务无法执行
|
|
7
|
+
- **Fixed**: CI 稳定性 — 修复 _notify_update 裸返回和时间断言,消除环境差异引起的随机失败
|
|
8
|
+
- **Improved**: roll-brief 输出格式 — 序号命名、省略空 section、元信息格式精简,减少无效噪音
|
|
9
|
+
|
|
10
|
+
## v2026.511.1
|
|
11
|
+
- **Changed**: roll loop 调度器切换到 launchd(macOS)— `roll setup` 自动安装 loop/dream/brief 三个 launchd 服务(默认关闭),`roll loop on/off/status` 统一走 launchctl 管理,幂等安装,Linux 保留 crontab 回退路径
|
|
12
|
+
- **Added**: roll-loop TCR 硬校验 — 故事完成后自动统计 `tcr:` 微提交数量,为 0 时将故事状态回退为 📋 Todo 并写 ALERT,防止 agent 跳过 TCR 节奏
|
|
13
|
+
- **Fixed**: CI 测试环境兼容 — 移除依赖本地 state.yaml 的 hello_world.bats,修复 GitHub Actions 持续失败
|
|
14
|
+
|
|
3
15
|
## v2026.510.10
|
|
4
16
|
- **Fixed**: release.sh changelog 同步时序修复 — 修正条件逻辑和执行顺序,确保每次发版时 changelog 正确更新
|
|
17
|
+
- **Added**: roll-loop 22:00 自动执行验证 — 新增 hello_world.bats 作为 loop 定时执行的端到端存档,可回放确认调度器正常工作
|
|
5
18
|
|
|
6
19
|
## v2026.510.9
|
|
7
20
|
- **Fixed**: CHANGELOG 改版本号分组 — 每个 release 独立 section,GitHub Release 增量内容提取正确
|
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
> Roll out features with AI agents — _Move fast, no sprints._
|
|
11
11
|
|
|
12
|
+
**[中文版 README](README_CN.md)**
|
|
13
|
+
|
|
12
14
|
[](LICENSE)
|
|
13
15
|
[](https://www.npmjs.com/package/@seanyao/roll)
|
|
14
16
|
[](https://github.com/seanyao/roll/actions/workflows/ci.yml)
|
|
@@ -17,20 +19,21 @@
|
|
|
17
19
|
|
|
18
20
|
## What is Roll?
|
|
19
21
|
|
|
20
|
-
Roll solves a specific problem: when developers on the same team use different AI clients (Claude Code, Cursor, Gemini CLI, Codex), each agent operates under different engineering constraints. One developer's Claude runs tests another's Cursor never even considers.
|
|
22
|
+
Roll solves a specific problem: when developers on the same team use different AI clients (Claude Code, Cursor, Gemini CLI, Kimi, Codex, DeepSeek, Pi, OpenCode, Trae), each agent operates under different engineering constraints. One developer's Claude runs tests another's Cursor never even considers.
|
|
21
23
|
|
|
22
|
-
Roll fixes this through
|
|
24
|
+
Roll fixes this through three mechanisms:
|
|
23
25
|
|
|
24
|
-
1. **Convention CLI** — a single source of engineering conventions distributed to every AI client on the machine (`~/.claude/`, `~/.cursor/`, `~/.gemini/`, etc.)
|
|
25
|
-
2. **Skill System** — proven engineering practices (TDD, TCR, SRE, INVEST) encoded as instructions that any AI agent can follow
|
|
26
|
+
1. **Convention CLI** — a single source of engineering conventions distributed to every AI client on the machine (`~/.claude/`, `~/.cursor/`, `~/.gemini/`, `~/.kimi/`, `~/.codex/`, `~/.deepseek/`, etc.)
|
|
27
|
+
2. **Skill System** — 20 proven engineering practices (TDD, TCR, SRE, INVEST, DDD) encoded as instructions that any AI agent can follow
|
|
28
|
+
3. **Autonomous Evolution** — an optional layer that lets the agent work unattended: `roll-loop` executes BACKLOG items hourly, `roll-.dream` scans code health nightly, and `roll-brief` briefs the human each morning. The human retains sole authority over releases.
|
|
26
29
|
|
|
27
|
-
The result: any agent, any client, same constraints.
|
|
30
|
+
The result: any agent, any client, same constraints — and optionally, continuous autonomous delivery.
|
|
28
31
|
|
|
29
32
|
---
|
|
30
33
|
|
|
31
34
|
## Start Here
|
|
32
35
|
|
|
33
|
-
Before commands and skills, read the Engineering Methodology — it explains the three-loop architecture (Research → Build → Observe), why each skill exists, and how they connect into a complete delivery system.
|
|
36
|
+
Before commands and skills, read the Engineering Methodology — it explains the three-loop architecture (Research → Build → Observe), the autonomous evolution layer, why each skill exists, and how they connect into a complete delivery system.
|
|
34
37
|
|
|
35
38
|
**[English](docs/methodology-en.md)** · **[中文](docs/methodology.md)**
|
|
36
39
|
|
|
@@ -57,17 +60,25 @@ roll update
|
|
|
57
60
|
|
|
58
61
|
## Convention Management
|
|
59
62
|
|
|
60
|
-
Unified behavioral conventions for Claude Code / Gemini CLI / Cursor / Codex — all from one source.
|
|
63
|
+
Unified behavioral conventions for Claude Code / Gemini CLI / Cursor / Kimi / Codex / DeepSeek / Pi / OpenCode / Trae — all from one source.
|
|
61
64
|
|
|
62
65
|
### Commands
|
|
63
66
|
|
|
67
|
+
Commands fall into two categories: **bash commands** run pure shell logic; **agent commands** (marked with 🤖) launch a full AI agent session to execute a SKILL.md.
|
|
68
|
+
|
|
64
69
|
| Command | Description |
|
|
65
70
|
|---------|-------------|
|
|
66
|
-
| `roll setup` | First-time install on this machine, or re-sync
|
|
67
|
-
| `roll update` | One-step upgrade: `npm install -g @seanyao/roll@latest` + re-sync via
|
|
71
|
+
| `roll setup [-f]` | First-time install on this machine, or re-sync (use `--force` to overwrite local cache) |
|
|
72
|
+
| `roll update` | One-step upgrade: `npm install -g @seanyao/roll@latest` + re-sync via setup |
|
|
68
73
|
| `roll init` | New project: create `AGENTS.md` + `BACKLOG.md` + `docs/features/` |
|
|
69
|
-
|
|
70
74
|
| `roll status` | Show sync state, skill links, and detected AI tools |
|
|
75
|
+
| `roll backlog` | Show all pending tasks from BACKLOG.md |
|
|
76
|
+
| `roll agent [use <name>\|list]` | Per-project agent selection — affects all 🤖 commands |
|
|
77
|
+
| `roll loop <on\|off\|now\|status\|monitor\|resume\|reset>` | 🤖 Manage the autonomous BACKLOG executor (three-service: loop/dream/brief) |
|
|
78
|
+
| `roll brief` | 🤖 Show latest owner brief (regenerate if stale >24h) |
|
|
79
|
+
| `roll peer` | 🤖 Cross-agent code review and negotiation |
|
|
80
|
+
| `roll release` | 🤖 Sync changelog + version bump + tag + npm publish + GitHub Release |
|
|
81
|
+
| `roll` _(no args, in project dir)_ | Dashboard: loop status, pending count, latest brief summary |
|
|
71
82
|
|
|
72
83
|
### Typical Flow
|
|
73
84
|
|
|
@@ -83,7 +94,13 @@ roll init
|
|
|
83
94
|
# 3. Upgrade to a new release
|
|
84
95
|
roll update
|
|
85
96
|
|
|
97
|
+
# 4. Enable autonomous evolution (optional)
|
|
98
|
+
roll loop on # three services: loop (hourly) + dream (nightly) + brief (daily)
|
|
99
|
+
roll loop monitor # real-time top-like dashboard
|
|
100
|
+
roll brief # read the latest digest
|
|
86
101
|
|
|
102
|
+
# 5. Switch agent per project
|
|
103
|
+
roll agent use kimi # all 🤖 commands now use kimi for this project
|
|
87
104
|
```
|
|
88
105
|
|
|
89
106
|
### How Convention Layering Works
|
|
@@ -134,32 +151,93 @@ Research → Design → Build → Check → Fix → (loop)
|
|
|
134
151
|
| Execute a planned Story | `$roll-build US-001` |
|
|
135
152
|
| Free-form feature request | `$roll-build "add search to admin"` |
|
|
136
153
|
| Bug fix | `$roll-fix FIX-001` |
|
|
137
|
-
| Fast backlog capture (bug / idea) | `$roll-
|
|
154
|
+
| Fast backlog capture (bug / idea) | `$roll-idea "description"` |
|
|
138
155
|
| High-risk logic (payments, auth, state machines) | `$roll-spar "feature"` |
|
|
139
156
|
| Deep research (product / company / tech) | `$roll-research "subject"` |
|
|
157
|
+
| Cross-agent code review | `$roll-peer` |
|
|
140
158
|
| Patrol production for regressions | `$roll-sentinel patrol` |
|
|
141
159
|
| Debug a broken page | `$roll-debug <URL>` |
|
|
142
160
|
| Record a dev moment / diary entry | `$roll-notes "just fixed that nasty bug"` |
|
|
161
|
+
| Let the agent work overnight | `roll loop on` |
|
|
143
162
|
|
|
144
163
|
### Full Skill List
|
|
145
164
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
|
149
|
-
|
|
150
|
-
| `$roll-
|
|
151
|
-
| `$roll-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
|
156
|
-
|
|
157
|
-
| `$roll
|
|
158
|
-
| `$roll
|
|
159
|
-
| `$roll
|
|
160
|
-
| `$roll
|
|
161
|
-
| `$roll
|
|
162
|
-
|
|
165
|
+
**Loop A — Research & Design**
|
|
166
|
+
|
|
167
|
+
| Skill | Description |
|
|
168
|
+
|-------|-------------|
|
|
169
|
+
| `$roll-research` | HV Analysis (Horizontal-Vertical) deep research framework, outputs PDF report |
|
|
170
|
+
| `$roll-design` | Multi-turn discuss → [peer review] → DDD model → solution design → [peer review] → write Stories to BACKLOG |
|
|
171
|
+
|
|
172
|
+
**Loop B — Implementation & Iteration**
|
|
173
|
+
|
|
174
|
+
| Skill | Description |
|
|
175
|
+
|-------|-------------|
|
|
176
|
+
| `$roll-build` | Universal entry: Story ID, fix ID, or free-text fly mode. TCR-driven micro-commits |
|
|
177
|
+
| `$roll-spar` | Adversarial TDD — Attacker writes tests, Defender writes code |
|
|
178
|
+
| `$roll-fix` | Bug fix / hotfix from BACKLOG |
|
|
179
|
+
| `$roll-release` | One-command publish: auto version (YYYY.MMDD.N) → tag → npm publish → GitHub Release |
|
|
180
|
+
| `$roll-peer` | Cross-agent code review — Claude / Kimi / DeepSeek / Codex bilateral negotiation |
|
|
181
|
+
|
|
182
|
+
**Loop C — Observability & Maintenance**
|
|
183
|
+
|
|
184
|
+
| Skill | Description |
|
|
185
|
+
|-------|-------------|
|
|
186
|
+
| `$roll-sentinel` | Randomized production patrol with adaptive hot-spot weighting |
|
|
187
|
+
| `$roll-debug` | Deep page diagnostics + root cause analysis (Black Box probe) |
|
|
188
|
+
|
|
189
|
+
**Autonomous Evolution (optional, via `roll loop on`)**
|
|
190
|
+
|
|
191
|
+
| Skill | Description |
|
|
192
|
+
|-------|-------------|
|
|
193
|
+
| `$roll-loop` | Hourly BACKLOG executor — routes US/FIX/REFACTOR to the right skill, enforces TCR |
|
|
194
|
+
| `$roll-.dream` | Nightly code health scan — dead code, architectural drift → REFACTOR entries |
|
|
195
|
+
| `$roll-brief` | Owner-facing digest — what's done, what's pending, release readiness verdict |
|
|
196
|
+
|
|
197
|
+
**Passive Support**
|
|
198
|
+
|
|
199
|
+
| Skill | Description |
|
|
200
|
+
|-------|-------------|
|
|
201
|
+
| `$roll-.review` | Pre-commit multi-dimensional self code review |
|
|
202
|
+
| `$roll-.qa` | Test pyramid and coverage standards reference |
|
|
203
|
+
| `$roll-.changelog` | Auto-generate CHANGELOG from BACKLOG |
|
|
204
|
+
| `$roll-.echo` | Passive intent clarification |
|
|
205
|
+
| `$roll-.clarify` | Passive scope clarification for vague build requests |
|
|
206
|
+
| `$roll-idea` | Fast capture a bug or idea into BACKLOG.md |
|
|
207
|
+
| `$roll-notes` | Project diary — append dev moments to `notes/YYYY-MM-DD.md` |
|
|
208
|
+
| `$roll-doctor` | One-command dev toolchain health check |
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Autonomous Evolution
|
|
213
|
+
|
|
214
|
+
Off by default. Enable with `roll loop on` to let the agent work unattended on your project.
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
┌─────────────────────────────────────────────────────────┐
|
|
218
|
+
│ Base layer (always active) │
|
|
219
|
+
│ $roll-design → $roll-build → $roll-fix → $roll-spar │
|
|
220
|
+
│ Human drives every action │
|
|
221
|
+
├─────────────────────────────────────────────────────────┤
|
|
222
|
+
│ Autonomous layer (opt-in: roll loop on) │
|
|
223
|
+
│ roll-loop — hourly BACKLOG executor │
|
|
224
|
+
│ roll-.dream — nightly code health scan │
|
|
225
|
+
│ roll-brief — daily morning digest + release readiness │
|
|
226
|
+
│ Human reviews briefs; retains sole release authority │
|
|
227
|
+
└─────────────────────────────────────────────────────────┘
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Three services are scheduled via macOS launchd (Linux: crontab) and managed as a unit:
|
|
231
|
+
|
|
232
|
+
- **roll-loop** runs hourly, picks up `📋 Todo` items from BACKLOG, routes them (`US-XXX → $roll-build`, `FIX-XXX → $roll-fix`, `REFACTOR-XXX → $roll-build`), and enforces TCR discipline — if a story completes with zero `tcr:` micro-commits, it reverts to Todo with an ALERT.
|
|
233
|
+
- **roll-.dream** runs nightly, scanning for dead code, architectural drift, and refactoring opportunities. Outputs `REFACTOR-XXX` entries to BACKLOG.
|
|
234
|
+
- **roll-brief** runs each morning (or on-demand via `roll brief`), producing an owner-facing digest with a release-readiness verdict.
|
|
235
|
+
|
|
236
|
+
The autonomous layer **never** invokes `roll-release`. Production deployment is always a human decision.
|
|
237
|
+
|
|
238
|
+
Use `roll loop monitor` for a real-time `top`-like view of all three services: launchd status, current execution state, pending queue, alerts, and live log tail.
|
|
239
|
+
|
|
240
|
+
Per-project agent configuration: `roll agent use <name>` writes `.roll.yaml` so each project can use a different AI client (claude / kimi / deepseek / pi / codex / opencode). Lookup order: `.roll.yaml` → `~/.roll/config.yaml` → `claude` (default).
|
|
163
241
|
|
|
164
242
|
---
|
|
165
243
|
|
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.
|
|
7
|
+
VERSION="2026.511.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"
|
|
@@ -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=("" "3" "9")
|
|
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)
|
|
1755
|
-
off)
|
|
1756
|
-
now)
|
|
1757
|
-
status)
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
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 03:00 每晚 03:00"
|
|
1914
|
+
echo " • roll-brief daily at 09:00 每天 09: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
|
|
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") >>
|
|
1777
|
-
dream_cmd="cd \"${project_path}\" && $(_agent_skill_cmd "${sd}/roll-.dream/SKILL.md") >>
|
|
1778
|
-
brief_cmd="cd \"${project_path}\" && $(_agent_skill_cmd "${sd}/roll-brief/SKILL.md") >>
|
|
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
|
|
@@ -1786,13 +1938,31 @@ _loop_on() {
|
|
|
1786
1938
|
|
|
1787
1939
|
ok "Loop enabled 已启用"
|
|
1788
1940
|
echo " • roll-loop every hour :00 每小时整点"
|
|
1789
|
-
echo " • roll-.dream nightly at
|
|
1790
|
-
echo " • roll-brief daily at
|
|
1941
|
+
echo " • roll-.dream nightly at 03:00 每晚 03:00"
|
|
1942
|
+
echo " • roll-brief daily at 09:00 每天 09:00"
|
|
1791
1943
|
echo " • Agent: ${agent} (change: roll agent use <name>)"
|
|
1792
1944
|
}
|
|
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,26 @@ _loop_now() {
|
|
|
1805
1975
|
warn "Loop already running loop 正在运行中"; return 0
|
|
1806
1976
|
fi
|
|
1807
1977
|
info "Triggering loop cycle... 正在触发一个周期..."
|
|
1808
|
-
|
|
1978
|
+
local log_file="${_SHARED_ROOT}/loop/launchd.log"
|
|
1979
|
+
mkdir -p "$(dirname "$log_file")"
|
|
1980
|
+
_agent_run_skill "roll-loop" 2>&1 | tee -a "$log_file" || true
|
|
1981
|
+
# Reset stale running state if skill exited without cleanup (e.g. API error, SIGKILL)
|
|
1982
|
+
if [[ -f "$_LOOP_STATE" ]] && grep -q "^status: running" "$_LOOP_STATE" 2>/dev/null; then
|
|
1983
|
+
printf "status: idle\n" > "$_LOOP_STATE"
|
|
1984
|
+
fi
|
|
1809
1985
|
}
|
|
1810
1986
|
|
|
1811
1987
|
_loop_status() {
|
|
1812
1988
|
local project_path; project_path=$(pwd -P)
|
|
1813
1989
|
local agent; agent=$(_project_agent)
|
|
1814
1990
|
echo ""
|
|
1815
|
-
|
|
1991
|
+
local loop_enabled=false
|
|
1992
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
1993
|
+
_launchd_is_loaded "$(_launchd_label "loop" "$project_path")" && loop_enabled=true
|
|
1994
|
+
else
|
|
1995
|
+
crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}" && loop_enabled=true
|
|
1996
|
+
fi
|
|
1997
|
+
if $loop_enabled; then
|
|
1816
1998
|
echo -e " Scheduler ${GREEN}● enabled${NC} Agent: ${CYAN}${agent}${NC}"
|
|
1817
1999
|
else
|
|
1818
2000
|
echo -e " Scheduler ${YELLOW}○ disabled${NC} run: roll loop on"
|
|
@@ -1842,6 +2024,176 @@ _loop_reset() {
|
|
|
1842
2024
|
fi
|
|
1843
2025
|
}
|
|
1844
2026
|
|
|
2027
|
+
# Count `tcr:` prefixed commits in the current git repo since started_at timestamp.
|
|
2028
|
+
_loop_tcr_count() {
|
|
2029
|
+
local started_at="$1"
|
|
2030
|
+
git log --oneline --since="${started_at}" 2>/dev/null \
|
|
2031
|
+
| awk '/^[a-f0-9]+ tcr:/{n++} END{print n+0}'
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
# Verify TCR rhythm after a story completes. Returns 0 if ok, 1 if no TCR commits.
|
|
2035
|
+
# On failure: reverts story in BACKLOG.md to 📋 Todo and writes ALERT.
|
|
2036
|
+
_loop_enforce_tcr() {
|
|
2037
|
+
local story_id="$1"
|
|
2038
|
+
local started_at="${2:-}"
|
|
2039
|
+
|
|
2040
|
+
[[ -z "$started_at" ]] && return 0
|
|
2041
|
+
|
|
2042
|
+
local count; count=$(_loop_tcr_count "$started_at")
|
|
2043
|
+
|
|
2044
|
+
if [[ "$count" -eq 0 ]]; then
|
|
2045
|
+
# Revert story status
|
|
2046
|
+
if [[ -f "BACKLOG.md" ]]; then
|
|
2047
|
+
local tmp; tmp=$(mktemp)
|
|
2048
|
+
sed "/\[${story_id}\]/s/ | ✅ Done |/ | 📋 Todo |/" BACKLOG.md > "$tmp" \
|
|
2049
|
+
&& mv "$tmp" BACKLOG.md
|
|
2050
|
+
fi
|
|
2051
|
+
|
|
2052
|
+
# Write ALERT
|
|
2053
|
+
mkdir -p "$(dirname "$_LOOP_ALERT")"
|
|
2054
|
+
cat > "$_LOOP_ALERT" << EOF
|
|
2055
|
+
# ALERT — TCR check failed
|
|
2056
|
+
|
|
2057
|
+
**Time**: $(date '+%Y-%m-%d %H:%M')
|
|
2058
|
+
**Story**: ${story_id}
|
|
2059
|
+
**Reason**: zero tcr: commits since story start (${started_at})
|
|
2060
|
+
|
|
2061
|
+
**Action required** (choose one):
|
|
2062
|
+
- Add TCR commits and re-run: \`roll loop now\`
|
|
2063
|
+
- Take over manually: \`\$roll-build ${story_id}\`
|
|
2064
|
+
- Reset and retry: \`roll loop reset\` then \`roll loop now\`
|
|
2065
|
+
EOF
|
|
2066
|
+
return 1
|
|
2067
|
+
fi
|
|
2068
|
+
|
|
2069
|
+
return 0
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
_loop_monitor() {
|
|
2073
|
+
local interval="${1:-3}"
|
|
2074
|
+
local project_path; project_path=$(pwd -P)
|
|
2075
|
+
local project_name; project_name=$(basename "$project_path")
|
|
2076
|
+
|
|
2077
|
+
# Determine terminal clear capability
|
|
2078
|
+
local clear_cmd="clear"
|
|
2079
|
+
command -v clear &>/dev/null || clear_cmd="echo ''"
|
|
2080
|
+
|
|
2081
|
+
while true; do
|
|
2082
|
+
$clear_cmd
|
|
2083
|
+
local agent; agent=$(_project_agent)
|
|
2084
|
+
local now; now=$(date '+%Y-%m-%d %H:%M:%S')
|
|
2085
|
+
|
|
2086
|
+
echo -e "\n ${BOLD}${CYAN}roll loop monitor${NC} ${YELLOW}${project_name}${NC} ${now} (Ctrl-C to exit)\n"
|
|
2087
|
+
|
|
2088
|
+
# Services status (three services on macOS, single on Linux)
|
|
2089
|
+
echo -e " ${BOLD}Services 服务状态${NC} Agent: ${CYAN}${agent}${NC}"
|
|
2090
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
2091
|
+
local svc_info svc schedule
|
|
2092
|
+
for svc_info in "loop:hourly" "dream:03:00" "brief:09:00"; do
|
|
2093
|
+
svc="${svc_info%%:*}"
|
|
2094
|
+
schedule="${svc_info#*:}"
|
|
2095
|
+
if _launchd_is_loaded "$(_launchd_label "$svc" "$project_path")"; then
|
|
2096
|
+
printf " ${GREEN}%-8s ● enabled${NC} (%s)\n" "$svc" "$schedule"
|
|
2097
|
+
else
|
|
2098
|
+
printf " ${YELLOW}%-8s ○ disabled${NC} (%s)\n" "$svc" "$schedule"
|
|
2099
|
+
fi
|
|
2100
|
+
done
|
|
2101
|
+
else
|
|
2102
|
+
if crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}"; then
|
|
2103
|
+
echo -e " ${GREEN}loop ● enabled${NC}"
|
|
2104
|
+
else
|
|
2105
|
+
echo -e " ${YELLOW}loop ○ disabled${NC} run: roll loop on"
|
|
2106
|
+
fi
|
|
2107
|
+
fi
|
|
2108
|
+
|
|
2109
|
+
# Current state
|
|
2110
|
+
if [[ -f "$_LOOP_STATE" ]]; then
|
|
2111
|
+
local status current_item started_at run_id
|
|
2112
|
+
status=$(grep '^status:' "$_LOOP_STATE" | awk '{print $2}')
|
|
2113
|
+
current_item=$(grep '^current_item:' "$_LOOP_STATE" | awk '{print $2}')
|
|
2114
|
+
started_at=$(grep '^started_at:' "$_LOOP_STATE" | cut -d' ' -f2- | tr -d '"')
|
|
2115
|
+
run_id=$(grep '^run_id:' "$_LOOP_STATE" | awk '{print $2}')
|
|
2116
|
+
echo ""
|
|
2117
|
+
case "$status" in
|
|
2118
|
+
running) echo -e " State ${GREEN}▶ running${NC} ${CYAN}${current_item}${NC} started: ${started_at} run: ${run_id}" ;;
|
|
2119
|
+
paused) echo -e " State ${RED}‖ paused${NC} on: ${current_item}" ;;
|
|
2120
|
+
idle) echo -e " State ${YELLOW}○ idle${NC}" ;;
|
|
2121
|
+
*) echo -e " State ${status}" ;;
|
|
2122
|
+
esac
|
|
2123
|
+
else
|
|
2124
|
+
echo ""
|
|
2125
|
+
echo -e " State ${YELLOW}○ no state file${NC}"
|
|
2126
|
+
fi
|
|
2127
|
+
|
|
2128
|
+
# Alert
|
|
2129
|
+
if [[ -f "$_LOOP_ALERT" ]]; then
|
|
2130
|
+
echo ""
|
|
2131
|
+
echo -e " ${RED}⚠ ALERT:${NC}"
|
|
2132
|
+
sed 's/^/ /' "$_LOOP_ALERT"
|
|
2133
|
+
fi
|
|
2134
|
+
|
|
2135
|
+
# Queue: pending items
|
|
2136
|
+
echo ""
|
|
2137
|
+
echo -e " ${BOLD}Queue 待处理队列${NC}"
|
|
2138
|
+
local backlog="BACKLOG.md"
|
|
2139
|
+
if [[ -f "$backlog" ]]; then
|
|
2140
|
+
local queue_count=0
|
|
2141
|
+
local fix_pending us_pending refactor_pending
|
|
2142
|
+
fix_pending=$(grep -E '^\| FIX-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2143
|
+
us_pending=$(grep -E '^\| \[US-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2144
|
+
refactor_pending=$(grep -E '^\| REFACTOR-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2145
|
+
|
|
2146
|
+
# FIX first (priority)
|
|
2147
|
+
while IFS= read -r line; do
|
|
2148
|
+
[[ -z "$line" ]] && continue
|
|
2149
|
+
local id desc
|
|
2150
|
+
id=$(echo "$line" | awk -F'|' '{print $2}' | tr -d ' ')
|
|
2151
|
+
desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-60)
|
|
2152
|
+
printf " ${RED}%-14s${NC} %s\n" "$id" "$desc"
|
|
2153
|
+
(( queue_count++ )) || true
|
|
2154
|
+
done <<< "$fix_pending"
|
|
2155
|
+
|
|
2156
|
+
# US stories
|
|
2157
|
+
while IFS= read -r line; do
|
|
2158
|
+
[[ -z "$line" ]] && continue
|
|
2159
|
+
local id desc
|
|
2160
|
+
id=$(echo "$line" | sed 's/.*\[\(US-[^]]*\)\].*/\1/')
|
|
2161
|
+
desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-60)
|
|
2162
|
+
printf " ${CYAN}%-14s${NC} %s\n" "$id" "$desc"
|
|
2163
|
+
(( queue_count++ )) || true
|
|
2164
|
+
done <<< "$us_pending"
|
|
2165
|
+
|
|
2166
|
+
# Refactors
|
|
2167
|
+
while IFS= read -r line; do
|
|
2168
|
+
[[ -z "$line" ]] && continue
|
|
2169
|
+
local id desc
|
|
2170
|
+
id=$(echo "$line" | awk -F'|' '{print $2}' | tr -d ' ')
|
|
2171
|
+
desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//' | cut -c1-60)
|
|
2172
|
+
printf " ${YELLOW}%-14s${NC} %s\n" "$id" "$desc"
|
|
2173
|
+
(( queue_count++ )) || true
|
|
2174
|
+
done <<< "$refactor_pending"
|
|
2175
|
+
|
|
2176
|
+
[[ $queue_count -eq 0 ]] && echo -e " ${GREEN}✓ empty${NC}"
|
|
2177
|
+
else
|
|
2178
|
+
echo " BACKLOG.md not found"
|
|
2179
|
+
fi
|
|
2180
|
+
|
|
2181
|
+
# Log tail (launchd.log)
|
|
2182
|
+
local log_file="${_SHARED_ROOT}/loop/launchd.log"
|
|
2183
|
+
echo ""
|
|
2184
|
+
echo -e " ─────────────────────────────────────────────────────"
|
|
2185
|
+
echo -e " ${BOLD}Log Tail 实时日志${NC} (~/.shared/roll/loop/launchd.log, last 10 lines)"
|
|
2186
|
+
if [[ -f "$log_file" && -s "$log_file" ]]; then
|
|
2187
|
+
tail -10 "$log_file" | sed 's/^/ /'
|
|
2188
|
+
else
|
|
2189
|
+
echo -e " ${YELLOW}(no log yet)${NC}"
|
|
2190
|
+
fi
|
|
2191
|
+
|
|
2192
|
+
echo ""
|
|
2193
|
+
sleep "$interval"
|
|
2194
|
+
done
|
|
2195
|
+
}
|
|
2196
|
+
|
|
1845
2197
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
1846
2198
|
# BRIEF — owner-facing project digest
|
|
1847
2199
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1872,6 +2224,75 @@ cmd_release() {
|
|
|
1872
2224
|
_agent_run_skill "roll-release"
|
|
1873
2225
|
}
|
|
1874
2226
|
|
|
2227
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
2228
|
+
# BACKLOG — show pending tasks
|
|
2229
|
+
# ═══════════════════════════════════════════════════════════════════════════════
|
|
2230
|
+
|
|
2231
|
+
cmd_backlog() {
|
|
2232
|
+
local backlog="BACKLOG.md"
|
|
2233
|
+
if [[ ! -f "$backlog" ]]; then
|
|
2234
|
+
err "BACKLOG.md not found — run 'roll init' first 未找到 BACKLOG.md,请先运行 roll init"
|
|
2235
|
+
return 1
|
|
2236
|
+
fi
|
|
2237
|
+
|
|
2238
|
+
local us_items fix_items refactor_items total=0
|
|
2239
|
+
|
|
2240
|
+
us_items=$(grep -E '^\| \[US-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2241
|
+
fix_items=$(grep -E '^\| FIX-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2242
|
+
refactor_items=$(grep -E '^\| REFACTOR-' "$backlog" | grep -F '| 📋 Todo |' || true)
|
|
2243
|
+
|
|
2244
|
+
local us_count fix_count refactor_count
|
|
2245
|
+
us_count=$(echo "$us_items" | grep -c . || true)
|
|
2246
|
+
fix_count=$(echo "$fix_items" | grep -c . || true)
|
|
2247
|
+
refactor_count=$(echo "$refactor_items" | grep -c . || true)
|
|
2248
|
+
[[ -z "$us_items" ]] && us_count=0
|
|
2249
|
+
[[ -z "$fix_items" ]] && fix_count=0
|
|
2250
|
+
[[ -z "$refactor_items" ]] && refactor_count=0
|
|
2251
|
+
total=$(( us_count + fix_count + refactor_count ))
|
|
2252
|
+
|
|
2253
|
+
echo ""
|
|
2254
|
+
echo -e " ${BOLD}Pending Backlog 待处理任务${NC} (${total} items)"
|
|
2255
|
+
echo ""
|
|
2256
|
+
|
|
2257
|
+
if [[ $fix_count -gt 0 ]]; then
|
|
2258
|
+
echo -e " ${RED}Bug Fixes 缺陷修复 (${fix_count})${NC}"
|
|
2259
|
+
while IFS= read -r line; do
|
|
2260
|
+
local id desc
|
|
2261
|
+
id=$(echo "$line" | awk -F'|' '{print $2}' | tr -d ' ')
|
|
2262
|
+
desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//')
|
|
2263
|
+
printf " %-12s %s\n" "$id" "$desc"
|
|
2264
|
+
done <<< "$fix_items"
|
|
2265
|
+
echo ""
|
|
2266
|
+
fi
|
|
2267
|
+
|
|
2268
|
+
if [[ $us_count -gt 0 ]]; then
|
|
2269
|
+
echo -e " ${CYAN}User Stories 用户故事 (${us_count})${NC}"
|
|
2270
|
+
while IFS= read -r line; do
|
|
2271
|
+
local id desc
|
|
2272
|
+
id=$(echo "$line" | sed 's/.*\[\(US-[^]]*\)\].*/\1/')
|
|
2273
|
+
desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//')
|
|
2274
|
+
printf " %-14s %s\n" "$id" "$desc"
|
|
2275
|
+
done <<< "$us_items"
|
|
2276
|
+
echo ""
|
|
2277
|
+
fi
|
|
2278
|
+
|
|
2279
|
+
if [[ $refactor_count -gt 0 ]]; then
|
|
2280
|
+
echo -e " ${YELLOW}Refactors 重构 (${refactor_count})${NC}"
|
|
2281
|
+
while IFS= read -r line; do
|
|
2282
|
+
local id desc
|
|
2283
|
+
id=$(echo "$line" | awk -F'|' '{print $2}' | tr -d ' ')
|
|
2284
|
+
desc=$(echo "$line" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//')
|
|
2285
|
+
printf " %-16s %s\n" "$id" "$desc"
|
|
2286
|
+
done <<< "$refactor_items"
|
|
2287
|
+
echo ""
|
|
2288
|
+
fi
|
|
2289
|
+
|
|
2290
|
+
if [[ $total -eq 0 ]]; then
|
|
2291
|
+
echo -e " ${GREEN}✓ Nothing pending — backlog is clear 暂无待处理任务${NC}"
|
|
2292
|
+
echo ""
|
|
2293
|
+
fi
|
|
2294
|
+
}
|
|
2295
|
+
|
|
1875
2296
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
1876
2297
|
|
|
1877
2298
|
_dashboard() {
|
|
@@ -1881,31 +2302,55 @@ _dashboard() {
|
|
|
1881
2302
|
|
|
1882
2303
|
echo -e "\n ${BOLD}${CYAN}${project_name}${NC} ${YELLOW}v${VERSION}${NC}\n"
|
|
1883
2304
|
|
|
1884
|
-
|
|
2305
|
+
local _dash_loop_enabled=false
|
|
2306
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
2307
|
+
_launchd_is_loaded "$(_launchd_label "loop" "$project_path")" && _dash_loop_enabled=true
|
|
2308
|
+
else
|
|
2309
|
+
crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}" && _dash_loop_enabled=true
|
|
2310
|
+
fi
|
|
2311
|
+
if $_dash_loop_enabled; then
|
|
1885
2312
|
echo -e " Loop ${GREEN}● on${NC} Agent: ${CYAN}${agent}${NC}"
|
|
2313
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
2314
|
+
for svc_info in "loop:hourly" "dream:03:00" "brief:09:00"; do
|
|
2315
|
+
local svc="${svc_info%%:*}" schedule="${svc_info#*:}"
|
|
2316
|
+
if _launchd_is_loaded "$(_launchd_label "$svc" "$project_path")"; then
|
|
2317
|
+
printf " ${GREEN}%-8s ● enabled${NC} (%s)\n" "$svc" "$schedule"
|
|
2318
|
+
else
|
|
2319
|
+
printf " ${YELLOW}%-8s ○ disabled${NC} (%s)\n" "$svc" "$schedule"
|
|
2320
|
+
fi
|
|
2321
|
+
done
|
|
2322
|
+
fi
|
|
1886
2323
|
else
|
|
1887
2324
|
echo -e " Loop ${YELLOW}○ off${NC} run: roll loop on"
|
|
1888
2325
|
fi
|
|
1889
2326
|
[[ -f "$_LOOP_ALERT" ]] && echo -e " ${RED}⚠ ALERT — run: roll loop status${NC}"
|
|
1890
2327
|
|
|
2328
|
+
# Backlog summary
|
|
2329
|
+
if [[ -f "BACKLOG.md" ]]; then
|
|
2330
|
+
local pending_count
|
|
2331
|
+
pending_count=$(grep -cF '| 📋 Todo |' "BACKLOG.md" 2>/dev/null) || pending_count=0
|
|
2332
|
+
if (( pending_count > 0 )); then
|
|
2333
|
+
echo -e " Backlog ${YELLOW}${pending_count} pending${NC} run: roll backlog"
|
|
2334
|
+
else
|
|
2335
|
+
echo -e " Backlog ${GREEN}✓ clear${NC}"
|
|
2336
|
+
fi
|
|
2337
|
+
fi
|
|
2338
|
+
|
|
1891
2339
|
local briefs_dir="docs/briefs"
|
|
1892
2340
|
local latest; latest=$(ls "${briefs_dir}"/*.md 2>/dev/null | sort | tail -1 || true)
|
|
1893
2341
|
if [[ -n "$latest" ]]; then
|
|
1894
2342
|
local mod_time now age
|
|
1895
2343
|
mod_time=$(stat -c %Y "$latest" 2>/dev/null || stat -f %m "$latest" 2>/dev/null || echo 0)
|
|
1896
2344
|
now=$(date +%s); age=$(( (now - mod_time) / 3600 ))
|
|
1897
|
-
|
|
1898
|
-
grep -
|
|
1899
|
-
echo ""
|
|
1900
|
-
echo "
|
|
2345
|
+
local readiness; readiness=$(awk '/^## Release Readiness/{found=1;next} found && /[^[:space:]]/{gsub(/\*\*/,""); print; exit}' "$latest" 2>/dev/null || true)
|
|
2346
|
+
local done_count; done_count=$(grep -c '| Story\|| Fix\|| Refactor' "$latest" 2>/dev/null) || done_count=0
|
|
2347
|
+
echo -e "\n Brief (${age}h ago) ${readiness:+${CYAN}${readiness}${NC}}"
|
|
2348
|
+
[[ "$done_count" -gt 0 ]] && echo -e " This cycle: ${done_count} items completed"
|
|
1901
2349
|
else
|
|
1902
2350
|
echo -e "\n No brief yet — run: roll brief"
|
|
1903
2351
|
fi
|
|
1904
2352
|
|
|
1905
2353
|
echo ""
|
|
1906
|
-
echo " loop <on|off|now|status|resume|reset> · brief · agent use <name> · release"
|
|
1907
|
-
echo " setup · update · init · status · peer · help"
|
|
1908
|
-
echo ""
|
|
1909
2354
|
}
|
|
1910
2355
|
|
|
1911
2356
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1930,8 +2375,9 @@ usage() {
|
|
|
1930
2375
|
echo " init [Project] Create AGENTS.md + BACKLOG.md + docs/ 初始化项目工作流文件"
|
|
1931
2376
|
echo " status [Diagnostic] Show current state 显示当前状态"
|
|
1932
2377
|
echo " peer [Peer Review] Cross-agent negotiation 跨 Agent 协商对审"
|
|
1933
|
-
echo " loop <on|off|now|status|resume|reset> [Autonomous] Manage scheduled BACKLOG executor 管理自主执行循环"
|
|
2378
|
+
echo " loop <on|off|now|status|monitor|resume|reset> [Autonomous] Manage scheduled BACKLOG executor 管理自主执行循环"
|
|
1934
2379
|
echo " brief [Digest] Show latest owner brief (regenerate if stale) 展示最新简报"
|
|
2380
|
+
echo " backlog [View] Show all pending tasks from BACKLOG.md 显示待处理任务清单"
|
|
1935
2381
|
echo " agent [use <name>|list] [Config] Per-project agent selection 切换项目 agent"
|
|
1936
2382
|
echo " release [Publish] Sync changelog + version bump + npm publish 同步日志并发版"
|
|
1937
2383
|
echo ""
|
|
@@ -1941,6 +2387,7 @@ usage() {
|
|
|
1941
2387
|
echo " roll init # New or re-merge project (run in project) 新建或重新合并(项目目录)"
|
|
1942
2388
|
echo " roll loop on # Enable autonomous loop (cron) 启用自主执行循环"
|
|
1943
2389
|
echo " roll brief # Show latest brief 查看最新简报"
|
|
2390
|
+
echo " roll backlog # Show pending BACKLOG items 查看待处理任务"
|
|
1944
2391
|
echo " roll agent use kimi # Switch this project to kimi 切换当前项目到 kimi"
|
|
1945
2392
|
|
|
1946
2393
|
}
|
|
@@ -1957,6 +2404,7 @@ main() {
|
|
|
1957
2404
|
peer) cmd_peer "$@" ;;
|
|
1958
2405
|
loop) cmd_loop "$@" ;;
|
|
1959
2406
|
brief) cmd_brief "$@" ;;
|
|
2407
|
+
backlog) cmd_backlog "$@" ;;
|
|
1960
2408
|
agent) cmd_agent "$@" ;;
|
|
1961
2409
|
release) cmd_release "$@" ;;
|
|
1962
2410
|
version|--version|-v) echo "roll v${VERSION}" ;;
|
|
@@ -2012,7 +2460,7 @@ _check_update_async() {
|
|
|
2012
2460
|
|
|
2013
2461
|
_notify_update() {
|
|
2014
2462
|
local cache="${ROLL_HOME}/.update-check"
|
|
2015
|
-
[[ -f "$cache" ]] || return
|
|
2463
|
+
[[ -f "$cache" ]] || return 0
|
|
2016
2464
|
local latest; latest=$(awk '{print $2}' "$cache" 2>/dev/null || true)
|
|
2017
2465
|
[[ -z "$latest" || "$latest" == "$VERSION" ]] && return
|
|
2018
2466
|
local newer; newer=$(printf '%s\n%s\n' "$VERSION" "$latest" | sort -V | tail -1)
|
|
@@ -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-
|
|
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.
|
|
3
|
+
"version": "2026.511.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"
|
|
@@ -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-
|
|
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`)
|
package/skills/roll-.qa/SKILL.md
CHANGED
|
@@ -233,10 +233,10 @@ CI failure
|
|
|
233
233
|
|
|
234
234
|
```bash
|
|
235
235
|
# For fixable bugs — create FIX entry
|
|
236
|
-
$roll-
|
|
236
|
+
$roll-idea fix "CI: {step} fails — {root cause summary}"
|
|
237
237
|
|
|
238
238
|
# For flaky/environmental issues — create IDEA entry
|
|
239
|
-
$roll-
|
|
239
|
+
$roll-idea idea "CI: investigate flaky {test name}"
|
|
240
240
|
```
|
|
241
241
|
|
|
242
242
|
### Step 4: Execute Fix
|
|
@@ -95,52 +95,72 @@ A simple heuristic — not a gate, just a signal for the human:
|
|
|
95
95
|
|
|
96
96
|
### Step 4 — Write Brief
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
文件命名:`docs/briefs/YYYY-MM-DD-{NN}.md`,NN 为当日序号(01 起,零填充两位)。
|
|
99
|
+
计算方式:`ls docs/briefs/YYYY-MM-DD-*.md 2>/dev/null | wc -l`,+1 即为本次序号。
|
|
100
|
+
|
|
101
|
+
全部用中文输出。**省略空 section**(无内容时连标题一起不输出):
|
|
99
102
|
|
|
100
103
|
```markdown
|
|
101
|
-
#
|
|
104
|
+
# 简报 {YYYY-MM-DD HH:mm}
|
|
105
|
+
|
|
106
|
+
> 触发:{触发原因} | 覆盖:{起止时间范围}
|
|
102
107
|
|
|
103
|
-
##
|
|
104
|
-
|
|
|
108
|
+
## 已完成({N} 项)
|
|
109
|
+
| 编号 | 描述 | 类型 |
|
|
105
110
|
|----|-------------|------|
|
|
106
|
-
| US-XXX | {
|
|
107
|
-
| FIX-XXX | {
|
|
108
|
-
| REFACTOR-XXX | {
|
|
111
|
+
| US-XXX | {标题} | 用户故事 |
|
|
112
|
+
| FIX-XXX | {标题} | 缺陷修复 |
|
|
113
|
+
| REFACTOR-XXX | {标题} | 重构 |
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
<!-- 仅当有 🔨 条目时输出 -->
|
|
116
|
+
## 进行中
|
|
117
|
+
| 编号 | 描述 |
|
|
112
118
|
|----|-------------|
|
|
113
|
-
| US-XXX | {
|
|
119
|
+
| US-XXX | {标题} — 开始于 {date} |
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
<!-- 仅当有 📋 条目时输出 -->
|
|
122
|
+
## 待处理队列({N} 项)
|
|
123
|
+
| 编号 | 描述 | 优先级 |
|
|
117
124
|
|----|-------------|----------|
|
|
118
|
-
| US-XXX | {
|
|
125
|
+
| US-XXX | {标题} | 高 |
|
|
126
|
+
|
|
127
|
+
<!-- 仅当 roll-.dream 有新发现时输出 -->
|
|
128
|
+
## 悟见
|
|
129
|
+
{来自 docs/dream/ 的摘要}
|
|
130
|
+
|
|
131
|
+
<!-- 仅当有 ESCALATE 告警时输出 -->
|
|
132
|
+
## 需人工介入
|
|
133
|
+
{告警内容}
|
|
119
134
|
|
|
120
|
-
##
|
|
121
|
-
{
|
|
135
|
+
## 发版就绪
|
|
136
|
+
{✅ 可发版 / ⚠️ 暂缓 — 原因}
|
|
122
137
|
|
|
123
|
-
|
|
124
|
-
{any alerts, or "None — agent operating normally."}
|
|
138
|
+
**下一版本:** `v{YYYY}.{MMDD}.{序号}`
|
|
125
139
|
|
|
126
|
-
|
|
127
|
-
{
|
|
140
|
+
- {本轮新增 1}
|
|
141
|
+
- {本轮新增 2}
|
|
128
142
|
|
|
129
143
|
---
|
|
130
|
-
|
|
144
|
+
*状态:进行中 {N} · 待处理 {N} · 告警 {N} | 下次简报:{datetime}*
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**首份简报或传入 `--full-history`**:在"已完成"表格后追加历史汇总区块:
|
|
148
|
+
|
|
149
|
+
```markdown
|
|
150
|
+
### 历史汇总
|
|
151
|
+
**Epic: {Name}** — {done}/{total} ✅
|
|
131
152
|
```
|
|
132
153
|
|
|
133
154
|
### Step 5 — Notify
|
|
134
155
|
|
|
135
|
-
|
|
136
|
-
or CI log:
|
|
156
|
+
写完文件后在终端或 CI 日志中打印简报路径:
|
|
137
157
|
|
|
138
158
|
```
|
|
139
|
-
📋
|
|
140
|
-
|
|
159
|
+
📋 简报已生成:docs/briefs/2026-05-10-01.md
|
|
160
|
+
发布就绪:✅ 可发版
|
|
141
161
|
```
|
|
142
162
|
|
|
143
|
-
|
|
163
|
+
有升级事项时须显著打印,不得遗漏。
|
|
144
164
|
|
|
145
165
|
## Scheduler Configuration
|
|
146
166
|
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
14
|
+
User explicitly invokes `roll-idea` with a free-text description.
|
|
15
15
|
|
|
16
16
|
```
|
|
17
|
-
$roll-
|
|
18
|
-
$roll-
|
|
19
|
-
$roll-
|
|
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.
|
|
96
|
-
|
|
97
|
-
|
|
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-
|
|
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
|
|