@seanyao/roll 2026.512.6 → 2026.512.7

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v2026.512.7
4
+ - **Added**: `roll alert` — 查看、确认、清除 loop 告警,不用再去翻 loop status
5
+ - **Added**: macOS 系统通知 — story 完成或告警写入时自动弹通知,静音模式下不打扰
6
+ - **Added**: `roll ci [--wait]` — 查看当前提交的 CI 状态,或等待 CI 跑完再继续
7
+ - **Fixed**: loop 现在会等 CI 通过后才标记故事完成,CI 失败则保持进行中并发出提醒
8
+ - **Fixed**: changelog 更新不再产生独立 commit,并入故事完成提交,git log 更干净
9
+ - **Added**: `docs/domain/` — Roll 架构的 DDD 领域模型文档(5 个 Bounded Context + 自治操作 Aggregate 设计)
10
+ - **Fixed**: `roll loop runs` 不再报"当前项目尚无运行记录",历史记录正常显示
11
+ - **Added**: 文档目录重组 — methodology、skill 选择指南、loop 验证记录迁移至 `docs/guide/` 和 `docs/practices/`,根目录不再有散落文件
12
+ - **Added**: README 大幅精简并新增文档导航索引 — 首页更清晰,所有指南一表可查
13
+ - **Added**: dream 每晚自动检测文档缺口,brief 新增文档覆盖率数字
14
+
3
15
  ## v2026.512.6
4
16
  - **Added**: peer review 现在也会自动弹出终端窗口,实时观察跨 AI 协商过程(mute 关闭同一开关)
5
17
  - **Added**: `docs/guide/en/` — loop/dream/peer 英文用户指南上线,覆盖所有子命令和使用场景
package/README.md CHANGED
@@ -19,262 +19,75 @@
19
19
 
20
20
  ## What is Roll?
21
21
 
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.
22
+ Roll is an instruction and workflow framework for AI agents it encodes proven engineering practices (TDD, TCR, SRE, INVEST) as skills any agent can follow, distributes unified conventions to every AI client on your machine, and optionally lets the agent work unattended via an autonomous evolution layer.
23
23
 
24
- Roll fixes this through three mechanisms:
25
-
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.
29
-
30
- The result: any agent, any client, same constraints — and optionally, continuous autonomous delivery.
31
-
32
- ---
33
-
34
- ## Start Here
35
-
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.
37
-
38
- **[English](docs/methodology-en.md)** · **[中文](docs/methodology.md)**
24
+ **Three core values:**
25
+ 1. **Any agent, same constraints** — Claude, Cursor, Kimi, DeepSeek, Codex all receive identical engineering guardrails
26
+ 2. **Skill system** — 20 skills encode research design build check fix as reliable, repeatable workflows
27
+ 3. **Autonomous evolution** — `roll loop on` runs BACKLOG items hourly; humans retain sole release authority
39
28
 
40
29
  ---
41
30
 
42
- ## Installation
31
+ ## Quick Start (30 seconds)
43
32
 
44
33
  ```bash
45
34
  npm install -g @seanyao/roll
46
- roll setup
35
+ roll setup # distribute conventions to all AI clients
36
+ cd my-project
37
+ roll init # create AGENTS.md + BACKLOG.md + docs/features/
38
+ roll loop on # optional: let the agent work unattended
47
39
  ```
48
40
 
49
41
  **Requirements:** bash 4+, Node.js 16+
50
42
 
51
- To update:
52
-
53
- ```bash
54
- roll update
55
- ```
56
-
57
- > **For contributors** (working on roll itself): `git clone https://github.com/seanyao/roll.git && cd roll && ./install.sh`
58
-
59
43
  ---
60
44
 
61
- ## Convention Management
45
+ ## Documentation Index
62
46
 
63
- Unified behavioral conventions for Claude Code / Gemini CLI / Cursor / Kimi / Codex / DeepSeek / Pi / OpenCode / Trae — all from one source.
47
+ | Topic | English | 中文 |
48
+ |-------|---------|------|
49
+ | Overview & architecture | [guide/en/overview.md](docs/guide/en/overview.md) | [guide/zh/overview.md](docs/guide/zh/overview.md) |
50
+ | Engineering methodology | [guide/en/methodology.md](docs/guide/en/methodology.md) | [guide/zh/methodology.md](docs/guide/zh/methodology.md) |
51
+ | Loop (autonomous executor) | [guide/en/loop.md](docs/guide/en/loop.md) | [guide/zh/loop.md](docs/guide/zh/loop.md) |
52
+ | Dream (nightly health scan) | [guide/en/dream.md](docs/guide/en/dream.md) | [guide/zh/dream.md](docs/guide/zh/dream.md) |
53
+ | Peer (cross-agent review) | [guide/en/peer.md](docs/guide/en/peer.md) | [guide/zh/peer.md](docs/guide/zh/peer.md) |
54
+ | Skill selection guide | [guide/en/skills.md](docs/guide/en/skills.md) | [guide/zh/skills.md](docs/guide/zh/skills.md) |
55
+ | Domain model (DDD) | [domain/context-map.md](docs/domain/context-map.md) | — |
56
+ | Engineering common sense | [practices/engineering-common-sense.md](docs/practices/engineering-common-sense.md) | — |
64
57
 
65
- ### Commands
58
+ ---
66
59
 
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.
60
+ ## Commands
68
61
 
69
62
  | Command | Description |
70
63
  |---------|-------------|
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 |
73
- | `roll init` | New project: create `AGENTS.md` + `BACKLOG.md` + `docs/features/` |
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 |
82
-
83
- ### Typical Flow
84
-
85
- ```bash
86
- # 1. Install on this machine
87
- npm install -g @seanyao/roll
88
- roll setup
89
-
90
- # 2. Initialize a project (run from project root)
91
- cd my-app
92
- roll init
93
-
94
- # 3. Upgrade to a new release
95
- roll update
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
101
-
102
- # 5. Switch agent per project
103
- roll agent use kimi # all 🤖 commands now use kimi for this project
104
- ```
105
-
106
- ### How Convention Layering Works
107
-
108
- ```
109
- Global ~/.claude/CLAUDE.md ← user-owned; roll setup appends @roll.md
110
- ~/.claude/roll.md ← Roll conventions (written by roll setup/sync)
111
- ↓ auto-stacked
112
- Project <project>/AGENTS.md ← generated by roll init
113
- <project>/.claude/CLAUDE.md
114
- ```
115
-
116
- Global conventions are additive and never overwrite existing files. Project conventions are injected per project via `roll init`.
117
-
118
- ### Directory Layout
119
-
120
- ```
121
- ~/.roll/
122
- ├── config.yaml # sync path configuration
123
- └── conventions/
124
- ├── global/ # single source of truth
125
- │ ├── AGENTS.md
126
- │ ├── CLAUDE.md / GEMINI.md / .cursor-rules
127
- └── templates/ # project type templates
128
- ├── fullstack/
129
- ├── frontend-only/
130
- ├── backend-service/
131
- └── cli/
132
- ```
133
-
134
- ---
135
-
136
- ## Skill System
137
-
138
- Skills are instructions that encode proven engineering practices into a form AI agents can reliably follow. They live in `~/.roll/skills/` and are symlinked into each AI client's skill directory on `roll setup`.
139
-
140
- ### Workflow
141
-
142
- ```
143
- Research → Design → Build → Check → Fix → (loop)
144
- ```
145
-
146
- ### Quick Reference
147
-
148
- | Scenario | Skill |
149
- |----------|-------|
150
- | Uncertain approach, need to think it through | `$roll-design "topic"` |
151
- | Execute a planned Story | `$roll-build US-001` |
152
- | Free-form feature request | `$roll-build "add search to admin"` |
153
- | Bug fix | `$roll-fix FIX-001` |
154
- | Fast backlog capture (bug / idea) | `$roll-idea "description"` |
155
- | High-risk logic (payments, auth, state machines) | `$roll-spar "feature"` |
156
- | Deep research (product / company / tech) | `$roll-research "subject"` |
157
- | Cross-agent code review | `$roll-peer` |
158
- | Patrol production for regressions | `$roll-sentinel patrol` |
159
- | Debug a broken page | `$roll-debug <URL>` |
160
- | Record a dev moment / diary entry | `$roll-notes "just fixed that nasty bug"` |
161
- | Let the agent work overnight | `roll loop on` |
162
-
163
- ### Full Skill List
164
-
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).
241
-
242
- ---
243
-
244
- ## Project Structure (`roll init`)
245
-
246
- ```
247
- my-project/
248
- ├── AGENTS.md # Project constraints & skill routing
249
- ├── BACKLOG.md # Story and bug index
250
- ├── docs/features/ # Story details and specs
251
- └── ... your project files
252
- ```
64
+ | `roll setup [-f]` | First-time install or re-sync conventions to all AI clients |
65
+ | `roll update` | Upgrade to latest version |
66
+ | `roll init` | Initialize project: AGENTS.md + BACKLOG.md + docs/features/ |
67
+ | `roll status` | Show sync state, skill links, detected AI tools |
68
+ | `roll backlog` | Show pending tasks from BACKLOG.md |
69
+ | `roll loop <on\|off\|now\|status\|monitor>` | 🤖 Manage autonomous executor |
70
+ | `roll brief` | 🤖 Show latest owner digest |
71
+ | `roll peer` | 🤖 Cross-agent code review |
72
+ | `roll release` | 🤖 Version + tag + npm publish + GitHub Release |
253
73
 
254
74
  ---
255
75
 
256
76
  ## Contributing
257
77
 
258
- Contributions are welcome. Roll is a small focused tool keep PRs focused on one thing.
259
-
260
- 1. Fork the repo and create a branch from `main`
261
- 2. Make your changes with tests where applicable (`tests/`)
262
- 3. Ensure `./bin/roll --help` output is correct
263
- 4. Open a pull request with a clear description
78
+ PRs welcome. Keep them focused on one thing. For larger changes, open an issue first.
264
79
 
265
- For larger changes, open an issue first to discuss the approach.
80
+ 1. `git clone https://github.com/seanyao/roll.git && cd roll && ./install.sh`
81
+ 2. Make changes with bats tests (`tests/`)
82
+ 3. Run `npm test` before pushing
266
83
 
267
84
  ---
268
85
 
269
86
  ## Acknowledgments
270
87
 
271
- Roll builds on ideas and inspiration from the open-source community:
272
-
273
- - **[khazix-skills](https://github.com/KKKKhazix/khazix-skills)** by Digital Life Khazix — The HV Analysis (Horizontal-Vertical Analysis) deep research framework and schema used by `$roll-research` are derived from this project, used under the MIT License. Copyright (c) 2026 数字生命卡兹克.
274
- - **[superpowers](https://github.com/obra/superpowers)** by Jesse Vincent — A composable skills library for AI coding agents that informed several workflow patterns in Roll.
88
+ - **[khazix-skills](https://github.com/KKKKhazix/khazix-skills)** by Digital Life Khazix HV Analysis framework used by `$roll-research`, MIT License.
89
+ - **[superpowers](https://github.com/obra/superpowers)** by Jesse Vincent — composable skills library that inspired several Roll workflow patterns.
275
90
 
276
91
  ---
277
92
 
278
- ## License
279
-
280
- MIT
93
+ MIT License
package/bin/roll CHANGED
@@ -4,7 +4,7 @@ set -euo pipefail
4
4
  # Roll — AI Agent Convention Manager
5
5
  # Single source of truth for how all AI coding agents behave.
6
6
 
7
- VERSION="2026.512.6"
7
+ VERSION="2026.512.7"
8
8
  ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
9
9
  ROLL_CONFIG="${ROLL_HOME}/config.yaml"
10
10
  ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
@@ -117,6 +117,18 @@ _get_ai_tools() {
117
117
  done
118
118
  }
119
119
 
120
+ # Iterate all configured AI tools, calling: callback entry ai_dir ai_config ai_src [extra_args...]
121
+ _for_each_ai_tool() {
122
+ local _feach_cb="$1"; shift
123
+ while IFS= read -r _feach_entry; do
124
+ "$_feach_cb" "$_feach_entry" \
125
+ "$(_ai_dir "$_feach_entry")" \
126
+ "$(_ai_config "$_feach_entry")" \
127
+ "$(_ai_src "$_feach_entry")" \
128
+ "$@"
129
+ done < <(_get_ai_tools)
130
+ }
131
+
120
132
  # Add any ai_* keys from the default set that are missing from the user's config.
121
133
  # Non-destructive: never removes or modifies existing entries.
122
134
  _ensure_config_entries() {
@@ -505,16 +517,14 @@ _sync_convention_for_tool() {
505
517
  fi
506
518
  }
507
519
 
520
+ _sync_one_tool() {
521
+ local _entry="$1" _ai_dir="$2" _cfg="$3" _src="$4" force="$5"
522
+ _sync_convention_for_tool "$ROLL_GLOBAL/$_src" "$_ai_dir/$_cfg" "$force"
523
+ }
524
+
508
525
  _sync_conventions() {
509
526
  local force="${1:-false}"
510
-
511
- while IFS= read -r entry; do
512
- local ai_dir config_file src_file
513
- ai_dir="$(_ai_dir "$entry")"
514
- config_file="$(_ai_config "$entry")"
515
- src_file="$(_ai_src "$entry")"
516
- _sync_convention_for_tool "$ROLL_GLOBAL/$src_file" "$ai_dir/$config_file" "$force"
517
- done < <(_get_ai_tools)
527
+ _for_each_ai_tool _sync_one_tool "$force"
518
528
  }
519
529
 
520
530
  # ─── Internal: sync skills (pull + link) ──────────────────────────────────────
@@ -1038,22 +1048,10 @@ scan_project_type_from_files() {
1038
1048
  fi
1039
1049
  }
1040
1050
 
1041
- # ─── Helper: true when cwd has no existing source code ───────────────────────
1042
- is_fresh_project() {
1043
- local dir="${1:-.}"
1044
- ! ( [[ -f "$dir/package.json" ]] || [[ -f "$dir/go.mod" ]] || \
1045
- [[ -f "$dir/Cargo.toml" ]] || [[ -f "$dir/requirements.txt" ]] || \
1046
- [[ -f "$dir/pyproject.toml" ]] || \
1047
- [[ -d "$dir/src" ]] || [[ -d "$dir/api" ]] || [[ -d "$dir/app" ]] )
1048
- }
1049
-
1050
- # ─── Helper: make a scaffold dir + .gitkeep ───────────────────────────────────
1051
- _mkscaffold() { mkdir -p "$1"; touch "$1/.gitkeep"; }
1052
-
1053
1051
  # ─── Helper: write starter BACKLOG.md (no-op if exists) ──────────────────────
1054
1052
  _write_backlog() {
1055
1053
  if [[ -f "$1" ]]; then
1056
- _WK_MERGE_SUMMARY+=("unchanged|BACKLOG.md")
1054
+ _ROLL_MERGE_SUMMARY+=("unchanged|BACKLOG.md")
1057
1055
  return
1058
1056
  fi
1059
1057
  cat > "$1" << 'EOF'
@@ -1068,18 +1066,18 @@ _write_backlog() {
1068
1066
  |----|---------|--------|
1069
1067
  EOF
1070
1068
  ok "Created: BACKLOG.md"
1071
- _WK_MERGE_SUMMARY+=("created|BACKLOG.md")
1069
+ _ROLL_MERGE_SUMMARY+=("created|BACKLOG.md")
1072
1070
  }
1073
1071
 
1074
1072
  _ensure_features_dir() {
1075
1073
  if [[ -d "$1" ]]; then
1076
- _WK_MERGE_SUMMARY+=("unchanged|docs/features/")
1074
+ _ROLL_MERGE_SUMMARY+=("unchanged|docs/features/")
1077
1075
  return
1078
1076
  fi
1079
1077
 
1080
1078
  mkdir -p "$1"
1081
1079
  ok "Created: docs/features/"
1082
- _WK_MERGE_SUMMARY+=("created|docs/features/")
1080
+ _ROLL_MERGE_SUMMARY+=("created|docs/features/")
1083
1081
  }
1084
1082
 
1085
1083
  # ─── Helper: write starter .gitignore (no-op if exists) ──────────────────────
@@ -2266,7 +2264,8 @@ cmd_loop() {
2266
2264
  pause) _loop_pause ;;
2267
2265
  resume) _loop_resume ;;
2268
2266
  reset) _loop_reset ;;
2269
- *) err "Usage: roll loop <on|off|now|test|status|monitor|runs|attach|mute|unmute|pause|resume|reset>"; exit 1 ;;
2267
+ notify) _notify "${1:-roll}" "${2:-}" ;;
2268
+ *) err "Usage: roll loop <on|off|now|test|status|monitor|runs|attach|mute|unmute|pause|resume|reset|notify>"; exit 1 ;;
2270
2269
  esac
2271
2270
  }
2272
2271
 
@@ -2484,7 +2483,7 @@ _loop_status() {
2484
2483
  else
2485
2484
  echo -e " Auto-attach ${GREEN}live${NC} run: roll loop mute"
2486
2485
  fi
2487
- [[ -f "$_LOOP_ALERT" ]] && { echo ""; echo -e " ${RED}⚠ ALERT:${NC}"; sed 's/^/ /' "$_LOOP_ALERT"; }
2486
+ [[ -f "$_LOOP_ALERT" ]] && { echo ""; echo -e " ${RED}⚠ ALERT${NC} (${CYAN}roll alert${NC} to manage)"; sed 's/^/ /' "$_LOOP_ALERT"; }
2488
2487
  [[ -f "$_LOOP_STATE" ]] && { echo ""; echo " State:"; sed 's/^/ /' "$_LOOP_STATE"; }
2489
2488
  echo ""
2490
2489
  }
@@ -2683,6 +2682,16 @@ _loop_runs() {
2683
2682
  done <<<"$recent"
2684
2683
  }
2685
2684
 
2685
+ # Send a macOS system notification. No-op when muted, non-macOS, or osascript unavailable.
2686
+ _notify() {
2687
+ local title="${1:-roll}"
2688
+ local body="${2:-}"
2689
+ [ "$(uname)" = "Darwin" ] || return 0
2690
+ [ -f "$_LOOP_MUTE_FILE" ] && return 0
2691
+ command -v osascript >/dev/null 2>&1 || return 0
2692
+ osascript -e "display notification \"${body}\" with title \"${title}\"" >/dev/null 2>&1 || true
2693
+ }
2694
+
2686
2695
  # Count `tcr:` prefixed commits in the current git repo since started_at timestamp.
2687
2696
  _loop_tcr_count() {
2688
2697
  local started_at="$1"
@@ -2690,6 +2699,87 @@ _loop_tcr_count() {
2690
2699
  | awk '/^[a-f0-9]+ tcr:/{n++} END{print n+0}'
2691
2700
  }
2692
2701
 
2702
+ # Poll gh run list until current commit's CI completes.
2703
+ # Returns 0 on success or when gh is unavailable (graceful skip).
2704
+ # Returns 1 on CI failure or timeout.
2705
+ _ci_wait() {
2706
+ local timeout="${1:-300}"
2707
+ local interval=15
2708
+ local elapsed=0
2709
+
2710
+ command -v gh &>/dev/null || { warn "gh not installed — skipping CI gate gh 未安装,跳过 CI 检查"; return 0; }
2711
+
2712
+ local commit; commit=$(git rev-parse HEAD 2>/dev/null) || { err "Not a git repo 非 git 仓库"; return 1; }
2713
+ local short; short=$(git rev-parse --short HEAD 2>/dev/null)
2714
+
2715
+ ok "Waiting for CI on ${short} 等待 CI: ${short}"
2716
+
2717
+ while (( elapsed < timeout )); do
2718
+ local runs
2719
+ runs=$(gh run list --commit "$commit" --json status,conclusion 2>/dev/null) || {
2720
+ warn "gh run list failed — skipping CI gate"
2721
+ return 0
2722
+ }
2723
+
2724
+ if [[ -z "$runs" || "$runs" == "[]" ]]; then
2725
+ (( elapsed == 0 )) && echo " No CI runs found yet, waiting... 尚无 CI 记录,等待触发..."
2726
+ sleep "$interval"
2727
+ elapsed=$(( elapsed + interval ))
2728
+ continue
2729
+ fi
2730
+
2731
+ local pending
2732
+ pending=$(echo "$runs" | jq -r '[.[] | select(.status != "completed")] | length' 2>/dev/null || echo "0")
2733
+
2734
+ if [[ "$pending" -gt 0 ]]; then
2735
+ printf " ⏳ CI running (%ds)... CI 运行中(%ds)...\n" "$elapsed" "$elapsed"
2736
+ sleep "$interval"
2737
+ elapsed=$(( elapsed + interval ))
2738
+ continue
2739
+ fi
2740
+
2741
+ local failed
2742
+ failed=$(echo "$runs" | jq -r '[.[] | select(.conclusion != "success" and .conclusion != "skipped" and .conclusion != null)] | length' 2>/dev/null || echo "0")
2743
+
2744
+ if [[ "$failed" -gt 0 ]]; then
2745
+ err "CI failed for ${short} CI 失败: ${short}"
2746
+ return 1
2747
+ fi
2748
+
2749
+ ok "CI passed CI 通过"
2750
+ return 0
2751
+ done
2752
+
2753
+ warn "CI timed out after ${timeout}s CI 等待超时(${timeout}s)"
2754
+ return 1
2755
+ }
2756
+
2757
+ # CI gate before marking a story Done.
2758
+ # On CI failure: writes ALERT, returns 1 (caller keeps story 🔨 In Progress).
2759
+ # When gh unavailable: returns 0 (graceful skip).
2760
+ _loop_enforce_ci() {
2761
+ local story_id="$1"
2762
+
2763
+ _ci_wait 300 && return 0
2764
+
2765
+ mkdir -p "$(dirname "$_LOOP_ALERT")"
2766
+ cat > "$_LOOP_ALERT" << EOF
2767
+ # ALERT — CI gate failed
2768
+
2769
+ **Time**: $(date '+%Y-%m-%d %H:%M')
2770
+ **Story**: ${story_id}
2771
+ **Commit**: $(git rev-parse --short HEAD 2>/dev/null || echo unknown)
2772
+ **Reason**: CI did not pass — story kept as 🔨 In Progress CI 未通过,故事保持进行中
2773
+
2774
+ **Action required** (choose one):
2775
+ - Fix CI and re-run: \`roll loop now\`
2776
+ - Take over manually: \`\$roll-build ${story_id}\`
2777
+ - Reset and retry: \`roll loop reset\` then \`roll loop now\`
2778
+ EOF
2779
+ _notify "roll ⚠ CI Failed" "${story_id}: CI did not pass"
2780
+ return 1
2781
+ }
2782
+
2693
2783
  # Verify TCR rhythm after a story completes. Returns 0 if ok, 1 if no TCR commits.
2694
2784
  # On failure: reverts story in BACKLOG.md to 📋 Todo and writes ALERT.
2695
2785
  _loop_enforce_tcr() {
@@ -2722,6 +2812,7 @@ _loop_enforce_tcr() {
2722
2812
  - Take over manually: \`\$roll-build ${story_id}\`
2723
2813
  - Reset and retry: \`roll loop reset\` then \`roll loop now\`
2724
2814
  EOF
2815
+ _notify "roll ⚠ TCR Failed" "${story_id}: no tcr: commits found"
2725
2816
  return 1
2726
2817
  fi
2727
2818
 
@@ -2802,7 +2893,7 @@ _loop_monitor() {
2802
2893
  # Alert
2803
2894
  if [[ -f "$_LOOP_ALERT" ]]; then
2804
2895
  echo ""
2805
- echo -e " ${RED}⚠ ALERT:${NC}"
2896
+ echo -e " ${RED}⚠ ALERT${NC} (${CYAN}roll alert${NC} to manage)"
2806
2897
  sed 's/^/ /' "$_LOOP_ALERT"
2807
2898
  fi
2808
2899
 
@@ -2973,6 +3064,88 @@ _backlog_extract_id() {
2973
3064
  fi
2974
3065
  }
2975
3066
 
3067
+ # ═══════════════════════════════════════════════════════════════════════════════
3068
+ # CI — check or wait for current commit's CI status
3069
+ # ═══════════════════════════════════════════════════════════════════════════════
3070
+ # ALERT — view / ack / resolve loop alert lifecycle
3071
+ # ═══════════════════════════════════════════════════════════════════════════════
3072
+
3073
+ cmd_alert() {
3074
+ local subcmd="${1:-list}"
3075
+ shift || true
3076
+
3077
+ case "$subcmd" in
3078
+ list|"")
3079
+ if [[ ! -f "$_LOOP_ALERT" ]]; then
3080
+ ok "No active alerts 暂无告警"
3081
+ return 0
3082
+ fi
3083
+ echo -e "${BOLD}Active Alert 当前告警${NC}"
3084
+ echo ""
3085
+ cat "$_LOOP_ALERT"
3086
+ echo ""
3087
+ echo -e " Run '${CYAN}roll alert ack${NC}' to acknowledge, '${CYAN}roll alert resolve${NC}' to clear."
3088
+ echo -e " 运行 'roll alert ack' 确认告警,'roll alert resolve' 清除告警。"
3089
+ ;;
3090
+ ack)
3091
+ if [[ ! -f "$_LOOP_ALERT" ]]; then
3092
+ warn "No active alerts to acknowledge 暂无待确认告警"
3093
+ return 0
3094
+ fi
3095
+ local ts; ts=$(date '+%Y-%m-%d %H:%M:%S')
3096
+ {
3097
+ echo ""
3098
+ echo "**Acknowledged**: ${ts}"
3099
+ } >> "$_LOOP_ALERT"
3100
+ ok "Alert acknowledged at ${ts} 告警已确认"
3101
+ ;;
3102
+ resolve|clear)
3103
+ if [[ ! -f "$_LOOP_ALERT" ]]; then
3104
+ ok "No active alerts 暂无告警"
3105
+ return 0
3106
+ fi
3107
+ rm -f "$_LOOP_ALERT"
3108
+ ok "Alert resolved and cleared 告警已解决并清除"
3109
+ ;;
3110
+ *)
3111
+ err "Unknown subcommand: $subcmd 未知子命令: $subcmd"
3112
+ echo " Usage: roll alert [list|ack|resolve]"
3113
+ echo " 用法: roll alert [list|ack|resolve]"
3114
+ return 1
3115
+ ;;
3116
+ esac
3117
+ }
3118
+
3119
+ # ═══════════════════════════════════════════════════════════════════════════════
3120
+
3121
+ cmd_ci() {
3122
+ local wait_mode=false
3123
+ local timeout=300
3124
+
3125
+ while [[ $# -gt 0 ]]; do
3126
+ case "$1" in
3127
+ --wait) wait_mode=true; shift ;;
3128
+ --timeout=*) timeout="${1#*=}"; shift ;;
3129
+ *) err "Usage: roll ci [--wait] [--timeout=N] 用法: roll ci [--wait] [--timeout=N]"; exit 1 ;;
3130
+ esac
3131
+ done
3132
+
3133
+ if $wait_mode; then
3134
+ _ci_wait "$timeout"
3135
+ return
3136
+ fi
3137
+
3138
+ command -v gh &>/dev/null || { warn "gh not installed gh 未安装"; return 0; }
3139
+ local commit; commit=$(git rev-parse HEAD 2>/dev/null) || { err "Not a git repo 非 git 仓库"; return 1; }
3140
+ local runs
3141
+ runs=$(gh run list --commit "$commit" --json status,conclusion,name 2>/dev/null) || { warn "gh run list failed"; return 0; }
3142
+ if [[ -z "$runs" || "$runs" == "[]" ]]; then
3143
+ echo "No CI runs for $(git rev-parse --short HEAD) 当前提交无 CI 记录"
3144
+ return 0
3145
+ fi
3146
+ echo "$runs" | jq -r '.[] | "\(.name): \(.status)/\(.conclusion)"'
3147
+ }
3148
+
2976
3149
  cmd_backlog() {
2977
3150
  local backlog="BACKLOG.md"
2978
3151
  if [[ ! -f "$backlog" ]]; then
@@ -3193,7 +3366,7 @@ _dashboard() {
3193
3366
  else
3194
3367
  echo -e " Loop ${YELLOW}○ off${NC} run: roll loop on"
3195
3368
  fi
3196
- [[ -f "$_LOOP_ALERT" ]] && echo -e " ${RED}⚠ ALERT — run: roll loop status${NC}"
3369
+ [[ -f "$_LOOP_ALERT" ]] && echo -e " ${RED}⚠ ALERT — run: roll alert${NC}"
3197
3370
 
3198
3371
  # Backlog summary
3199
3372
  if [[ -f "BACKLOG.md" ]]; then
@@ -3253,6 +3426,7 @@ usage() {
3253
3426
  echo " backlog unblock <pat> Restore matching items to 📋 Todo 恢复为待处理"
3254
3427
  echo " agent [use <name>|list] [Config] Per-project agent selection 切换项目 agent"
3255
3428
  echo " release [Publish] Sync changelog + version bump + npm publish 同步日志并发版"
3429
+ echo " ci [--wait] [CI] Show or wait for current commit's CI status 查看/等待 CI 状态"
3256
3430
  echo ""
3257
3431
  echo "Examples / 示例:"
3258
3432
  echo " roll setup # New machine: first-time install 新机器首次安装"
@@ -3280,8 +3454,10 @@ main() {
3280
3454
  loop) cmd_loop "$@" ;;
3281
3455
  brief) cmd_brief "$@" ;;
3282
3456
  backlog) cmd_backlog "$@" ;;
3457
+ alert) cmd_alert "$@" ;;
3283
3458
  agent) cmd_agent "$@" ;;
3284
3459
  release) cmd_release "$@" ;;
3460
+ ci) cmd_ci "$@" ;;
3285
3461
  version|--version|-v) echo "roll v${VERSION}" ;;
3286
3462
  help|--help|-h) usage ;;
3287
3463
  "") [[ -f "BACKLOG.md" ]] && _dashboard || { usage; _show_changelog; } ;;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@seanyao/roll",
3
- "version": "2026.512.6",
3
+ "version": "2026.512.7",
4
4
  "description": "Roll — Roll out features with AI agents",
5
5
  "scripts": {
6
- "test": "find tests/unit tests/integration -name '*.bats' | sort | xargs ./tests/helpers/bats-core/bin/bats"
6
+ "test": "bash tests/run.sh"
7
7
  },
8
8
  "keywords": [
9
9
  "ai",
@@ -101,6 +101,19 @@ CHANGELOG 是给**使用者**看的,不是给维护者看的。一句话讲清
101
101
  - **Fixed**: 多个 loop 实例不会再互相打架(重复触发自动跳过)
102
102
  ```
103
103
 
104
+ ❌ 说机制不说现象(Fix 类最常犯):
105
+ ```
106
+ - **Fixed**: `roll loop runs` 过滤条件从完整路径改为 slug,历史记录不再因路径不匹配而消失
107
+ - **Fixed**: `roll-loop` skill 写入 `runs.jsonl` 时 project slug 计算方式明确,避免写成 bare basename
108
+ ```
109
+
110
+ ✅ 直接说用户看到了什么:
111
+ ```
112
+ - **Fixed**: `roll loop runs` 不再报"当前项目尚无运行记录",历史记录正常显示
113
+ ```
114
+
115
+ Fix 类句式参考:`<命令/功能> 不再 <之前的坏现象>`,或 `<命令/功能> 现在 <正常表现>`。内部有几个 bug 导致这一个现象,合并成一条。
116
+
104
117
  ### 4. Section Header — Always `## Unreleased`
105
118
 
106
119
  **⚠️ do NOT guess version numbers.** Only `scripts/release.sh` assigns concrete
@@ -152,11 +165,20 @@ version numbers like `v2026.511.8`. Just write to `## Unreleased`.
152
165
 
153
166
  **Ordering**: Unreleased always at top. Below it, released versions in reverse chronological order.
154
167
 
155
- ### 6. Commit Update
168
+ ### 6. Stage Update
169
+
170
+ **Normal path (called from `$roll-build` or `$roll-fix`)**: stage only — the
171
+ caller's completion commit will pick up CHANGELOG.md.
172
+
173
+ ```bash
174
+ git add CHANGELOG.md
175
+ ```
176
+
177
+ **Standalone / manual path** (called outside a roll-build session): stage and commit.
156
178
 
157
179
  ```bash
158
180
  git add CHANGELOG.md
159
- git commit -m "docs: update changelog for release $(date +%Y.%m.%d)"
181
+ git commit -m "chore: sync changelog"
160
182
  git push
161
183
  ```
162
184
 
@@ -97,6 +97,46 @@ Find repeated structures that warrant extraction:
97
97
  Flag: any pattern appearing 3+ times that could be extracted into a shared
98
98
  utility or convention.
99
99
 
100
+ ### Scan 5 — Doc Coverage Check
101
+
102
+ Check documentation structure against the conventions in `AGENTS.md § Documentation Conventions`.
103
+
104
+ **Check A — BACKLOG Done stories missing guide/en/ docs:**
105
+
106
+ Scan BACKLOG.md for features with multiple ✅ Done stories. For each feature epic, check whether a corresponding `docs/guide/en/<topic>.md` exists. If a feature has ≥3 Done stories and no guide doc, flag it.
107
+
108
+ **Check B — guide/en/ files missing guide/zh/ translations:**
109
+
110
+ ```bash
111
+ for f in docs/guide/en/*.md; do
112
+ base=$(basename "$f")
113
+ [ ! -f "docs/guide/zh/$base" ] && echo "missing ZH: $base"
114
+ done
115
+ ```
116
+
117
+ Flag any `docs/guide/en/<topic>.md` that has no matching `docs/guide/zh/<topic>.md`, provided the EN file has existed since before the most recent git tag (i.e., at least one release cycle old).
118
+
119
+ **Check C — stray files in docs/ root (根目录散落文件):**
120
+
121
+ ```bash
122
+ find docs/ -maxdepth 1 -name '*.md' 2>/dev/null
123
+ ```
124
+
125
+ Flag any `.md` file directly in `docs/` root (allowed subdirs: `guide/`, `domain/`, `features/`, `practices/`, `briefs/`, `dream/`).
126
+
127
+ **REFACTOR entry format for doc findings:**
128
+
129
+ ```markdown
130
+ | REFACTOR-XXX | docs: {具体缺口描述} — flagged by dream {YYYY-MM-DD} | 📋 Todo |
131
+ ```
132
+
133
+ **Dream log section** — add after existing sections:
134
+
135
+ ```markdown
136
+ ## 文档覆盖度
137
+ {发现内容 或 "文档结构符合规范,无缺口。"}
138
+ ```
139
+
100
140
  ## Output
101
141
 
102
142
  ### REFACTOR Entry (BACKLOG.md)
@@ -121,7 +161,7 @@ without context switching:
121
161
  # Dream Log {YYYY-MM-DD}
122
162
 
123
163
  ## 概要
124
- - 扫描项:死代码 / 架构漂移 / 裁剪候选 / 新兴模式
164
+ - 扫描项:死代码 / 架构漂移 / 裁剪候选 / 新兴模式 / 文档覆盖度
125
165
  - 发现:{N} 项标记,{M} 个 REFACTOR 条目已创建
126
166
 
127
167
  ## 死代码
@@ -78,6 +78,9 @@ From BACKLOG.md and git log, classify all items since last brief:
78
78
  - **Queue**: items still 📋 Todo (ordered by priority)
79
79
  - **Dream findings**: any REFACTOR entries added by roll-.dream since last brief
80
80
  - **Escalations**: any ALERT files in `~/.shared/roll/loop/` or `~/.shared/roll/dream/`
81
+ - **Doc coverage**: compute from `docs/guide/en/` and `docs/guide/zh/`:
82
+ - EN coverage = number of files in `docs/guide/en/`
83
+ - ZH translation rate = files in `docs/guide/zh/` ÷ files in `docs/guide/en/` × 100%
81
84
 
82
85
  ### Step 3 — Assess Release Readiness
83
86
 
@@ -132,6 +135,12 @@ A simple heuristic — not a gate, just a signal for the human:
132
135
  ## 需人工介入
133
136
  {告警内容}
134
137
 
138
+ <!-- 始终输出 doc coverage 数字;若无缺口写"覆盖完整" -->
139
+ ## 文档覆盖度
140
+ - guide/en: {N} 个文档
141
+ - ZH 翻译率:{M}/{N}({%})
142
+ - {缺口列表 或 "覆盖完整,无缺口。"}
143
+
135
144
  ## 发版就绪
136
145
  {✅ 可发版 / ⚠️ 暂缓 — 原因}
137
146
 
@@ -509,17 +509,21 @@ For Fly mode: first append an index row under the appropriate Epic > Feature gro
509
509
 
510
510
  If the US section does not yet exist, create the full section (AC / Files / Dependencies).
511
511
 
512
+ **Before committing, run `$roll-.changelog`** to stage CHANGELOG.md — then include
513
+ it in the completion commit so no separate changelog commit is created.
514
+
512
515
  ```bash
513
- git add BACKLOG.md docs/features/
516
+ # 1. Stage changelog (roll-.changelog stages CHANGELOG.md only, does not commit)
517
+ $roll-.changelog
518
+
519
+ # 2. Commit BACKLOG + feature doc + CHANGELOG.md together
520
+ git add BACKLOG.md docs/features/ CHANGELOG.md
514
521
  git commit -m "docs: mark {US-ID} as completed"
515
522
  git push
516
523
  ```
517
524
 
518
525
  ### Phase 12: Report & Celebrate
519
526
 
520
- **Before reporting, run `$roll-.changelog`** to sync completed Story to CHANGELOG.md.
521
- This is mandatory — release notes depend on it.
522
-
523
527
  ```
524
528
  ✅ Pushed to GitHub: origin/main
525
529
  🚀 Deployed: <url>
@@ -530,7 +534,7 @@ This is mandatory — release notes depend on it.
530
534
  📊 TCR Stats: <success rate, revert count if any>
531
535
  📋 Review Gate: <self-review findings summary>
532
536
  📝 BACKLOG: <US-ID> marked ✅ Done
533
- 📄 CHANGELOG: $roll-.changelog updated
537
+ 📄 CHANGELOG: bundled into completion commit (Phase 11)
534
538
 
535
539
  🎉 Shipped.
536
540
 
@@ -604,7 +608,7 @@ Before creating any file or directory:
604
608
  - [ ] **Verification Gate passed** (fresh evidence for tests, build, deploy, no regression)
605
609
  - [ ] **BACKLOG.md index status updated** (📋 → ✅, REQUIRED)
606
610
  - [ ] **`docs/features/<feature>.md` US section updated** (Completed date + [x] ACs, REQUIRED)
607
- - [ ] **CHANGELOG.md updated** via `$roll-.changelog` (REQUIRED)
611
+ - [ ] **CHANGELOG.md staged and bundled** into completion commit via `$roll-.changelog` in Phase 11 (REQUIRED)
608
612
  - [ ] Summary reported to user
609
613
 
610
614
  ---
@@ -142,9 +142,14 @@ After each item completes:
142
142
  - Count `tcr:` prefix commits since `started_at` via `git log --oneline --since=<started_at>`
143
143
  - 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`)
144
144
  - Count > 0 → continue normally
145
- 2. Update state file: `status: idle`
146
- 3. Check if a Feature is now fully complete (all its Stories ✅)
147
- 4. If yes and `brief_on_feature_complete: true` invoke `Skill("roll-brief")`
145
+ 2. **CI Gate** call `roll ci --wait` (or `_loop_enforce_ci <story_id>`):
146
+ - Polls `gh run list --commit <HEAD>` until all CI runs complete
147
+ - CI passescontinue normally
148
+ - CI fails or times out → keep story as `🔨 In Progress` (do NOT mark ✅ Done); write ALERT; skip to next story
149
+ - `gh` not installed → skip gracefully (return 0)
150
+ 3. Update state file: `status: idle`
151
+ 4. Check if a Feature is now fully complete (all its Stories ✅)
152
+ 5. If yes and `brief_on_feature_complete: true` → invoke `Skill("roll-brief")`
148
153
 
149
154
  ### Step 5 — Write Run Summary
150
155
 
@@ -180,7 +185,7 @@ final report in `cron.log` instead.
180
185
  | Field | Type | Format / Enum |
181
186
  |---|---|---|
182
187
  | `ts` | string | ISO 8601 **UTC** with `Z` suffix. Get via `date -u +%Y-%m-%dT%H:%M:%SZ`. Never use `+08:00` or other offsets. |
183
- | `project` | string | Project **slug** only (e.g. `roll-d9dfa0`), NOT the absolute path. Derive from `basename` of plist label or `_project_slug` output. |
188
+ | `project` | string | Project **slug** only (e.g. `roll-d9dfa0`), NOT the absolute path and NOT plain `basename`. Compute via: `p=$(pwd -P); base=$(basename "$p" | tr -cs '[:alnum:]' '-' | sed 's/-*$//'); hash=$(printf '%s' "$p" | md5 | cut -c1-6 2>/dev/null || printf '%s' "$p" | md5sum | cut -c1-6); echo "${base}-${hash}"` |
184
189
  | `run_id` | string | Matches `state.yaml` `run_id` exactly. Format: `loop-YYYYMMDD-HHMM`. |
185
190
  | `status` | enum | Exactly one of: `built` (≥1 story shipped), `idle` (no Todo items found), `failed` (paused/error). **No synonyms.** |
186
191
  | `built` | array&lt;string&gt; | Story ids completed this cycle. `[]` when none. **Always array, never null/number.** |
@@ -196,7 +201,13 @@ Optional field, only when `status == "failed"`:
196
201
 
197
202
  ```bash
198
203
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
199
- project=$(_project_slug "$(pwd -P)") # must match roll loop runs filter
204
+ # Compute project slug inlined equivalent of bin/roll's _project_slug
205
+ # (Claude sessions can't call roll's internal functions, so we inline).
206
+ # Must produce identical output to _project_slug to match `roll loop runs` filter.
207
+ _p=$(pwd -P)
208
+ _base=$(basename "$_p" | tr -cs '[:alnum:]' '-' | sed 's/-*$//')
209
+ _hash=$(printf '%s' "$_p" | md5 | cut -c1-6 2>/dev/null || printf '%s' "$_p" | md5sum | cut -c1-6)
210
+ project="${_base}-${_hash}" # e.g. roll-d9dfa0 — must match roll loop runs filter
200
211
  # duration_sec = cycle_end_epoch - cycle_start_epoch (track at Step 1)
201
212
  # tcr_count = git log --oneline --since="<cycle_start>" | grep -c '^[a-f0-9]* tcr:'
202
213