@seanyao/roll 2026.521.1 → 2026.521.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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v2026.521.2
4
+
5
+ ### Fixed
6
+
7
+ - **`roll setup` 不再在 macOS 后台活动列表里留下 ghost「bash」条目** — 改由 `roll init` 和 `roll loop on` 按需安装 `[loop]`
8
+ - **`roll setup` 现在能正确显示哪些步骤真的装了** — 之前装好的 skills 和 peer 状态目录会被误标成"跳过" `[loop]`
9
+ - **`roll loop status --days 7` 不再吞掉天数参数** — 之前永远只显示默认 3 天,dashboard 底部那条 "more" 提示也改成能直接复制跑通 `[loop]`
10
+ - **loop 每轮起手不再刷一堆"找不到文件"报错** — 修了内部脚本里的旧路径,并让 agent 在 zsh 下不再被未匹配的通配符卡住 `[loop]`
11
+
3
12
  ## v2026.521.1
4
13
 
5
14
  ### Added
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.521.1"
7
+ VERSION="2026.521.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"
@@ -577,6 +577,10 @@ _ensure_tmux() {
577
577
  # a re-copy with identical content is recognised as a no-op even when the inner
578
578
  # helper rewrites the file. Watch is a colon-separated list of directories;
579
579
  # missing dirs are skipped silently.
580
+ # FIX-079: also track symlinks (`_link_skills` only creates symlinks) and
581
+ # directories (`_peer_ensure_state_dir` only creates dirs). Without these, a
582
+ # step that did real work but produced no regular file would falsely render
583
+ # as ↷ on a brand-new install.
580
584
  _setup_snapshot() {
581
585
  local watch="$1"
582
586
  local -a dirs
@@ -586,6 +590,10 @@ _setup_snapshot() {
586
590
  for d in "${dirs[@]}"; do
587
591
  [[ -d "$d" ]] || continue
588
592
  find "$d" -type f -print0 2>/dev/null | xargs -0 cksum 2>/dev/null
593
+ while IFS= read -r l; do
594
+ printf 'L %s -> %s\n' "$l" "$(readlink "$l")"
595
+ done < <(find "$d" -type l 2>/dev/null)
596
+ find "$d" -type d -print 2>/dev/null
589
597
  done
590
598
  } | sort
591
599
  }
@@ -660,10 +668,9 @@ cmd_setup() {
660
668
  fi
661
669
  fi
662
670
 
663
- if [[ "$(uname)" == "Darwin" ]]; then
664
- _run_setup_step "$HOME/Library/LaunchAgents" _install_launchd_plists "$(pwd -P)"
665
- _record "$(_state_to_marker "$_ROLL_SETUP_STATE")" "Install launchd plists (macOS)"
666
- fi
671
+ # FIX-078: launchd plist 安装从 setup 里拿掉——plist 是 per-project 资源,
672
+ # setup 是全局安装阶段,不应该给 cwd 留 disabled 的占位。需要时 cmd_init /
673
+ # _loop_on 各自会调 _install_launchd_plists。
667
674
 
668
675
  _emit_setup_v2_ui "${steps_buf[@]}"
669
676
  }
@@ -3933,7 +3940,7 @@ cmd_loop() {
3933
3940
  off) _loop_off ;;
3934
3941
  now) _loop_now ;;
3935
3942
  test) _loop_test ;;
3936
- status) _loop_status ;;
3943
+ status) _loop_status "$@" ;;
3937
3944
  monitor) _loop_monitor "${1:-3}" ;;
3938
3945
  runs) _loop_runs "$@" ;;
3939
3946
  events) _loop_event_log "${1:-20}" ;;
@@ -706,7 +706,7 @@ def render(events, cron, state, backlog, *, days=3, lang="both", now=None,
706
706
  c("muted", " ") +
707
707
  c("dim", "watch ") + c("blue", "roll loop --watch") +
708
708
  c("muted", " ") +
709
- c("dim", "more ") + c("blue", "roll loop --days 7"))
709
+ c("dim", "more ") + c("blue", "roll loop status --days 7"))
710
710
 
711
711
  def _read_plist_loop_minute() -> int:
712
712
  """FIX-063: read actual loop Minute from launchd plist (truth source).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanyao/roll",
3
- "version": "2026.521.1",
3
+ "version": "2026.521.2",
4
4
  "description": "Roll — Roll out features with AI agents",
5
5
  "scripts": {
6
6
  "test": "bash tests/run.sh"
@@ -44,7 +44,7 @@ Adapt commands to the constraints below — otherwise you will burn turns on
44
44
  denied operations and the cycle will idle-exit.
45
45
 
46
46
  - **No `AskUserQuestion`**: no human can answer. If you genuinely cannot
47
- proceed without a decision, write an entry to `${HOME}/.shared/roll/loop/ALERT.md`
47
+ proceed without a decision, write an entry to `${HOME}/.shared/roll/loop/ALERT-<slug>.md`
48
48
  describing what's needed and exit cleanly.
49
49
  - **Avoid compound bash**: each `Bash` call must run a single command.
50
50
  No `cmd1 && cmd2`, no `cmd1 ; cmd2`, no pipes (`|`), no `$(...)` /
@@ -58,6 +58,13 @@ denied operations and the cycle will idle-exit.
58
58
  Files inside it (.roll/backlog.md, bin/roll, tests/, docs/) are always
59
59
  accessible. Files at `~/.shared/roll/...` are reachable via the `Read`
60
60
  tool but not via shell commands.
61
+ - **Quote every glob**: the `Bash` tool runs commands through the user's
62
+ login shell, which on macOS is typically `zsh`. zsh's default `nomatch`
63
+ aborts unquoted globs that find no match with `(eval):1: no matches
64
+ found: <pattern>` and exit 1, burning a turn on a meaningless error.
65
+ Quote literal globs (`ls 'tests/integration/helpers.*'`) or — better —
66
+ use the `Glob` tool, which is shell-agnostic and never aborts on empty
67
+ matches.
61
68
  - **Skill invocation is the work**: route US/REFACTOR via `$roll-build`,
62
69
  FIX via `$roll-fix`. Do not try to re-implement those flows inline.
63
70
 
@@ -75,23 +82,20 @@ loop:
75
82
 
76
83
  ## Workflow
77
84
 
78
- ### Step 1 — Read State
85
+ ### Step 1 — Orphan 🔨 Recovery
79
86
 
80
- ```bash
81
- STATE_FILE=~/.shared/roll/loop/state.yaml
82
-
83
- # If a previous run was interrupted, resume from state
84
- if [ -f "$STATE_FILE" ] && grep -q "status: interrupted" "$STATE_FILE"; then
85
- # Resume the interrupted item first
86
- fi
87
- ```
88
-
89
- **Orphan 🔨 recovery** — clean up stories left in `🔨 In Progress` by a crashed previous run:
87
+ Process-level crash recovery (LOCK, heartbeat, retry budget) is handled by
88
+ the runner in `bin/roll:_write_loop_runner_script` — the per-project LOCK
89
+ guarantees only one cycle for this slug is alive when you start. So at
90
+ this point, any `🔨 In Progress` row in `.roll/backlog.md` belongs to a
91
+ previous cycle that crashed before flipping it back; reclaim it before
92
+ scanning.
90
93
 
91
94
  1. Scan .roll/backlog.md for all rows whose Status column contains `🔨 In Progress`.
92
- 2. For each such story, check `state.yaml`:
93
- - If `current_item` matches the story id AND `status: running` this is the resume case (handled above), leave it.
94
- - Otherwise → this is an **orphan 🔨** (the loop that marked it crashed before finishing). Revert the status back to `📋 Todo`, commit `chore: revert orphan 🔨 US-XXX to 📋`, and append a line to `~/.shared/roll/loop/ALERT.md` recording the orphan id and time so the next brief surfaces it.
95
+ 2. For each row found: revert the status back to `📋 Todo`, commit
96
+ `chore: revert orphan 🔨 US-XXX to 📋`, and append a line to
97
+ `~/.shared/roll/loop/ALERT-<slug>.md` recording the orphan id and time
98
+ so the next brief surfaces it.
95
99
  3. After orphan sweep, proceed to Step 1.5 (Pre-run CI health check) before scanning.
96
100
 
97
101
  ### Step 1.5 — Pre-run CI Health Check
@@ -126,7 +130,8 @@ Call `_loop_pr_inbox` after the pre-run CI check passes. It walks
126
130
  | `eligible` (clean external PR, no blocking review) | Invoke `_loop_pr_review_external` — the actual decision is provided by US-AUTO-035's GitHub Action |
127
131
 
128
132
  **Rebase circuit breaker** — `_loop_pr_rebase_circuit <pr>` records each rebase
129
- attempt under `pr_state.<PR>.attempts_at` in `state.yaml`, pruning entries older
133
+ attempt under `pr_state.<PR>.attempts_at` in the per-slug state file
134
+ (`~/.shared/roll/loop/state-<slug>.yaml`, FIX-052), pruning entries older
130
135
  than 24 h. Once ≥3 attempts land within 24 h, further rebases are blocked and an
131
136
  ALERT is written (typical cause: a broken workflow file makes CI never run,
132
137
  which would otherwise drive infinite rebase loops).
@@ -239,10 +244,10 @@ REFACTOR-XXX → Skill("roll-build", "REFACTOR-XXX")
239
244
 
240
245
  The executor will update the row to `✅ Done` on success (it transitions from `🔨 In Progress` → `✅ Done`, same Edit logic as from `📋 Todo`).
241
246
 
242
- Before invoking, also write current item to state file:
247
+ Before invoking, also write current item to the per-slug state file
248
+ (`~/.shared/roll/loop/state-<slug>.yaml`, FIX-052):
243
249
 
244
250
  ```yaml
245
- # ~/.shared/roll/loop/state.yaml
246
251
  status: running
247
252
  current_item: US-AUTO-004
248
253
  started_at: "2026-05-10T02:00:00+08:00"
@@ -256,7 +261,7 @@ After each item completes:
256
261
 
257
262
  1. **TCR 硬校验** — call `roll loop enforce-tcr <story_id> <started_at>`:
258
263
  - Count `tcr:` prefix commits since `started_at` via `git log --oneline --since=<started_at>`
259
- - Count == 0 → revert story status in .roll/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`)
264
+ - Count == 0 → revert story status in .roll/backlog.md from ✅ Done → 📋 Todo; write ALERT to `~/.shared/roll/loop/ALERT-<slug>.md` with story ID, time, reason "zero tcr: commits since story start", and suggested actions (`roll loop now` / `$roll-build <id>` / `roll loop reset`)
260
265
  - Count > 0 → continue normally
261
266
  2. **CI Gate** — **MUST** invoke `roll ci --wait` (the `_loop_enforce_ci`
262
267
  wrapper). **Do NOT call `gh` directly** (no `gh run list`, no `gh run watch`,
@@ -274,7 +279,7 @@ After each item completes:
274
279
 
275
280
  Read `heal_count:` from `~/.shared/roll/loop/state-<slug>.yaml`; treat a missing line as `0`. If the count is below `ROLL_LOOP_HEAL_MAX` (default 2) and `ROLL_LOOP_NO_HEAL` is not set, increment it and take Path A. Otherwise take Path B.
276
281
 
277
- **Path A — attempt allowed (counter incremented in `state.yaml`):**
282
+ **Path A — attempt allowed (counter incremented in `state-<slug>.yaml`):**
278
283
 
279
284
  1. Capture failure summary:
280
285
  ```
@@ -288,15 +293,15 @@ After each item completes:
288
293
  Diagnose root cause, fix via TCR, commit, push. Do NOT change <story_id>'s
289
294
  BACKLOG status — it stays ✅ Done. The fix is a follow-up."`
290
295
  3. After `roll-fix` completes, return to step 2 (CI Gate) — re-run `roll ci --wait`.
291
- The counter in `state.yaml` prevents infinite loops.
296
+ The counter in `state-<slug>.yaml` prevents infinite loops.
292
297
 
293
298
  **Path B — heal exhausted (≥`ROLL_LOOP_HEAL_MAX`, default 2) or disabled (`ROLL_LOOP_NO_HEAL=1`) (exit 1):**
294
299
 
295
300
  1. Keep story as ✅ Done — commits are already on main; CI red is a follow-up
296
301
  problem, not a story failure.
297
- 2. Write ALERT to `~/.shared/roll/loop/ALERT.md` with:
302
+ 2. Write ALERT to `~/.shared/roll/loop/ALERT-<slug>.md` with:
298
303
  - story ID, time, commit SHA
299
- - heal attempts made (read `heal_count:` from `state.yaml`)
304
+ - heal attempts made (read `heal_count:` from `state-<slug>.yaml`)
300
305
  - last failure summary (head of `/tmp/roll-heal-<story_id>.log`)
301
306
  - suggested actions: `$roll-fix` manually / inspect CI / `roll loop reset`
302
307
  3. Skip to next story.
@@ -317,7 +322,7 @@ After each item completes:
317
322
  After all items in this cycle:
318
323
 
319
324
  ```yaml
320
- # ~/.shared/roll/loop/state.yaml
325
+ # ~/.shared/roll/loop/state-<slug>.yaml (FIX-052)
321
326
  status: idle
322
327
  last_run: "2026-05-10T02:15:00+08:00"
323
328
  last_run_items: [US-AUTH-003, FIX-007]
@@ -347,7 +352,7 @@ final report in `cron.log` instead.
347
352
  |---|---|---|
348
353
  | `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. |
349
354
  | `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}"` |
350
- | `run_id` | string | Matches `state.yaml` `run_id` exactly. Format: `loop-YYYYMMDD-HHMM`. |
355
+ | `run_id` | string | Matches `state-<slug>.yaml` `run_id` exactly. Format: `loop-YYYYMMDD-HHMM`. |
351
356
  | `status` | enum | Exactly one of: `built` (≥1 story shipped), `idle` (no Todo items found), `failed` (paused/error). **No synonyms.** |
352
357
  | `built` | array&lt;string&gt; | Story ids completed this cycle. `[]` when none. **Always array, never null/number.** |
353
358
  | `skipped` | array&lt;string&gt; | Story ids skipped because they were `🔨 In Progress`. `[]` when none. **Always array.** |
@@ -439,12 +444,12 @@ reason: "both primary (claude) and fallback (deepseek) unavailable"
439
444
  - Take over manually: `$roll-build US-AUTH-003`
440
445
  ```
441
446
 
442
- 3. Write alert file to `~/.shared/roll/loop/ALERT.md`
447
+ 3. Write alert file to `~/.shared/roll/loop/ALERT-<slug>.md`
443
448
 
444
449
  ## Resuming After Pause
445
450
 
446
451
  ```bash
447
- roll loop resume # picks up from state.yaml current_item
452
+ roll loop resume # picks up from state-<slug>.yaml current_item
448
453
  roll loop status # show current state without running
449
454
  roll loop reset # clear state and start fresh next scheduled run
450
455
  ```
@@ -514,6 +519,6 @@ roll-loop
514
519
  ├── invokes $roll-fix (FIX-XXX)
515
520
  ├── invokes $roll-brief (on Feature completion)
516
521
  ├── reads ~/.roll/config.yaml (agent routing)
517
- ├── writes ~/.shared/roll/loop/state.yaml
518
- └── writes ~/.shared/roll/loop/ALERT.md (on failure)
522
+ ├── writes ~/.shared/roll/loop/state-<slug>.yaml
523
+ └── writes ~/.shared/roll/loop/ALERT-<slug>.md (on failure)
519
524
  ```