@seanyao/roll 2026.511.5 → 2026.511.6
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/bin/roll +34 -8
- package/package.json +1 -1
- package/skills/roll-build/SKILL.md +1 -1
- package/skills/roll-fix/SKILL.md +1 -1
- package/skills/roll-loop/SKILL.md +43 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2026.511.8
|
|
4
|
+
- **Fixed**: 集成测试 launchd ghost 泄漏 — `integration_teardown` 在删除 TEST_TMP 之前,先 `launchctl bootout` 该沙箱里被 `roll loop on` 注册到 user gui domain 的所有 `com.roll.*` 服务,避免删 plist 后 launchd 仍保留指向不存在路径的 ghost 注册。
|
|
5
|
+
|
|
6
|
+
## v2026.511.7
|
|
7
|
+
- **Added**: loop 执行 story 前显式标记 🔨 In Progress — roll-loop SKILL 在调用 executor 之前先把 BACKLOG 中的故事状态从 📋 Todo 改为 🔨 In Progress 并提交 `chore: mark US-XXX in progress`,brief 简报和 peer agent 都能即时感知正在进行的工作,tcr 微提交不再"对 brief 不可见"。
|
|
8
|
+
- **Added**: loop 启动时孤儿 🔨 自愈 — 扫描 BACKLOG 中无对应 state.yaml running item 的 🔨 条目,视为上次崩溃残留,自动 revert 回 📋 Todo 并写 ALERT,避免被"卡"在错误的中间状态里。
|
|
9
|
+
- **Improved**: roll-build / roll-fix SKILL 状态转换段更新 — 显式接受 📋 Todo 或 🔨 In Progress 作为 ✅ Done 前置状态,loop 触发链路状态过渡更稳健。
|
|
10
|
+
|
|
11
|
+
## v2026.511.6
|
|
12
|
+
- **Added**: Loop 并发安全 — runner script 启动时写入 per-project LOCK 文件并检测重入;活跃 PID 已存在则跳过本次,残留死 LOCK 自动清理;正常/异常退出均通过 trap 清掉 LOCK。彻底防止两个 loop 实例同时启动造成的 BACKLOG/git 冲突。
|
|
13
|
+
- **Added**: roll-loop SKILL 显式声明 skip-🔨 In Progress 语义 — claude 扫 BACKLOG 时跳过已被人工或 peer agent 标记的执行中条目,为人机协同和多 agent 协作奠定基础。
|
|
14
|
+
- **Fixed**: 5 个 pre-existing 测试失败 — `run_roll` helper 切换到 TEST_TMP 作为 cwd 避免 slug 冲突;loop status 测试匹配三态显示新文案;dashboard 测试匹配 `_launchd_svc_state` + array 派生 schedule 的新结构。
|
|
15
|
+
|
|
3
16
|
## v2026.511.5
|
|
4
17
|
- **Fixed**: launchd plist 自动 reload — plist 内容变更且服务已加载时自动 unload + reload,升级 roll 后 loop 服务立即生效,无需手动重启
|
|
5
18
|
- **Improved**: roll loop status/monitor 三态展示 — 区分 ● 运行中 / ⚠ 已安装未加载 / ○ 未安装,并给出对应的自愈操作提示
|
package/bin/roll
CHANGED
|
@@ -4,7 +4,7 @@ set -euo pipefail
|
|
|
4
4
|
# Roll — AI Agent Convention Manager
|
|
5
5
|
# Single source of truth for how all AI coding agents behave.
|
|
6
6
|
|
|
7
|
-
VERSION="2026.511.
|
|
7
|
+
VERSION="2026.511.6"
|
|
8
8
|
ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
|
|
9
9
|
ROLL_CONFIG="${ROLL_HOME}/config.yaml"
|
|
10
10
|
ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
|
|
@@ -1855,6 +1855,17 @@ _write_loop_runner_script() {
|
|
|
1855
1855
|
#!/bin/bash -l
|
|
1856
1856
|
h=\$(printf '%d' "\$(date +%H)")
|
|
1857
1857
|
if [ "\$h" -lt ${active_start} ] || [ "\$h" -ge ${active_end} ]; then exit 0; fi
|
|
1858
|
+
LOCK="\$(dirname "\$0")/.LOCK-\$(basename "\$0" .sh | sed 's/^run-//')"
|
|
1859
|
+
if [ -f "\$LOCK" ]; then
|
|
1860
|
+
prev_pid=\$(head -1 "\$LOCK" 2>/dev/null || echo "")
|
|
1861
|
+
if [ -n "\$prev_pid" ] && kill -0 "\$prev_pid" 2>/dev/null; then
|
|
1862
|
+
echo "[\$(date '+%Y-%m-%dT%H:%M:%S%z')] loop already running (PID \$prev_pid), skipping" >> "${log_path}"
|
|
1863
|
+
exit 0
|
|
1864
|
+
fi
|
|
1865
|
+
rm -f "\$LOCK"
|
|
1866
|
+
fi
|
|
1867
|
+
echo "\$\$" > "\$LOCK"
|
|
1868
|
+
trap 'rm -f "\$LOCK"' EXIT
|
|
1858
1869
|
cd "${project_path}" && ${cmd} >> "${log_path}" 2>&1
|
|
1859
1870
|
SCRIPT
|
|
1860
1871
|
chmod +x "$script_path"
|
|
@@ -2454,13 +2465,28 @@ _dashboard() {
|
|
|
2454
2465
|
if $_dash_loop_enabled; then
|
|
2455
2466
|
echo -e " Loop ${GREEN}● on${NC} Agent: ${CYAN}${agent}${NC}"
|
|
2456
2467
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2468
|
+
local active_start active_end loop_minute dream_hour dream_minute brief_hour brief_minute
|
|
2469
|
+
active_start=$(_config_read_int "loop_active_start" "10")
|
|
2470
|
+
active_end=$(_config_read_int "loop_active_end" "18")
|
|
2471
|
+
loop_minute=$(_config_read_int "loop_minute" "$(_loop_derive_minute "$project_path" 0)")
|
|
2472
|
+
dream_hour=$(_config_read_int "loop_dream_hour" "3")
|
|
2473
|
+
dream_minute=$(_config_read_int "loop_dream_minute" "$(_loop_derive_minute "$project_path" 2)")
|
|
2474
|
+
brief_hour=$(_config_read_int "loop_brief_hour" "9")
|
|
2475
|
+
brief_minute=$(_config_read_int "loop_brief_minute" "$(_loop_derive_minute "$project_path" 4)")
|
|
2476
|
+
local loop_sched dream_sched brief_sched
|
|
2477
|
+
loop_sched=$(printf "every hour :%02d active %02d:00–%02d:00" "$loop_minute" "$active_start" "$active_end")
|
|
2478
|
+
dream_sched=$(printf "%02d:%02d" "$dream_hour" "$dream_minute")
|
|
2479
|
+
brief_sched=$(printf "%02d:%02d" "$brief_hour" "$brief_minute")
|
|
2480
|
+
local svcs=("loop" "dream" "brief")
|
|
2481
|
+
local scheds=("$loop_sched" "$dream_sched" "$brief_sched")
|
|
2482
|
+
for i in "${!svcs[@]}"; do
|
|
2483
|
+
local svc="${svcs[$i]}" schedule="${scheds[$i]}"
|
|
2484
|
+
local state; state=$(_launchd_svc_state "$svc" "$project_path")
|
|
2485
|
+
case "$state" in
|
|
2486
|
+
enabled) printf " ${GREEN}%-8s ● enabled${NC} (%s)\n" "$svc" "$schedule" ;;
|
|
2487
|
+
installed-off) printf " ${YELLOW}%-8s ⚠ off${NC} (%s) run: roll loop on\n" "$svc" "$schedule" ;;
|
|
2488
|
+
not-installed) printf " ${RED}%-8s ○ missing${NC} (%s) run: roll setup\n" "$svc" "$schedule" ;;
|
|
2489
|
+
esac
|
|
2464
2490
|
done
|
|
2465
2491
|
fi
|
|
2466
2492
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanyao/roll",
|
|
3
|
-
"version": "2026.511.
|
|
3
|
+
"version": "2026.511.6",
|
|
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"
|
|
@@ -483,7 +483,7 @@ Both locations must be updated — neither can be skipped:
|
|
|
483
483
|
| [US-{ID}](docs/features/<feature>.md#us-{id}) | {Title} | ✅ Done |
|
|
484
484
|
```
|
|
485
485
|
|
|
486
|
-
Change the Status from `📋 Todo` to `✅ Done`.
|
|
486
|
+
Change the Status from `📋 Todo` or `🔨 In Progress` (whichever the row currently shows) to `✅ Done`. When invoked by `roll-loop`, the row will already be `🔨 In Progress` — that is the expected starting state, and the transition is the same Edit operation.
|
|
487
487
|
For Fly mode: first append an index row under the appropriate Epic > Feature group, then mark it done.
|
|
488
488
|
|
|
489
489
|
**② Update `docs/features/<feature>.md` US section:**
|
package/skills/roll-fix/SKILL.md
CHANGED
|
@@ -296,7 +296,7 @@ Both locations must be updated — neither can be skipped:
|
|
|
296
296
|
| [FIX-{ID}](docs/features/<feature>.md#fix-{id}) | {Title} | ✅ Done |
|
|
297
297
|
```
|
|
298
298
|
|
|
299
|
-
Change the Status of the corresponding row from `📋 Todo` to `✅ Done`.
|
|
299
|
+
Change the Status of the corresponding row from `📋 Todo` or `🔨 In Progress` (whichever the row currently shows) to `✅ Done`. When invoked by `roll-loop`, the row will already be `🔨 In Progress` — that is the expected starting state.
|
|
300
300
|
|
|
301
301
|
**② Update `docs/features/<feature>.md` FIX section:**
|
|
302
302
|
|
|
@@ -57,17 +57,56 @@ if [ -f "$STATE_FILE" ] && grep -q "status: interrupted" "$STATE_FILE"; then
|
|
|
57
57
|
fi
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
**Orphan 🔨 recovery** — clean up stories left in `🔨 In Progress` by a crashed previous run:
|
|
61
|
+
|
|
62
|
+
1. Scan BACKLOG.md for all rows whose Status column contains `🔨 In Progress`.
|
|
63
|
+
2. For each such story, check `state.yaml`:
|
|
64
|
+
- If `current_item` matches the story id AND `status: running` → this is the resume case (handled above), leave it.
|
|
65
|
+
- 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.
|
|
66
|
+
3. After orphan sweep, proceed to Step 2 (Scan BACKLOG) — the reverted stories will be picked up normally if they're top of the queue.
|
|
67
|
+
|
|
60
68
|
### Step 2 — Scan BACKLOG
|
|
61
69
|
|
|
62
70
|
Read `BACKLOG.md`. Collect all rows where Status = `📋 Todo`, in order:
|
|
63
71
|
|
|
64
72
|
Priority: FIX-XXX first (bugs block progress), then US-XXX, then REFACTOR-XXX.
|
|
65
73
|
|
|
74
|
+
**Skip rows with Status = `🔨 In Progress`**. These are currently being executed by:
|
|
75
|
+
- Another concurrent executor (human via `$roll-build`, peer agent)
|
|
76
|
+
- An earlier loop iteration that hasn't finished yet (rare; should be guarded by LOCK)
|
|
77
|
+
- A previous interrupted run (the resume logic in Step 1 will pick these up)
|
|
78
|
+
|
|
66
79
|
Cap at `max_items_per_run` to limit blast radius per cycle.
|
|
67
80
|
|
|
81
|
+
### Concurrency Safety
|
|
82
|
+
|
|
83
|
+
Loop has two layers of concurrency protection:
|
|
84
|
+
|
|
85
|
+
1. **Per-project LOCK** (enforced by runner script, see `bin/roll:_write_loop_runner_script`):
|
|
86
|
+
- LOCK file path: `~/.shared/roll/loop/.LOCK-<project-slug>`
|
|
87
|
+
- On launch: if LOCK exists and the PID inside is alive → exit 0 (previous loop still running)
|
|
88
|
+
- On launch: if LOCK exists but PID is dead → clean up stale LOCK and continue
|
|
89
|
+
- On exit (normal or via trap): LOCK is removed
|
|
90
|
+
- One LOCK per project — different projects' loops run independently
|
|
91
|
+
|
|
92
|
+
2. **🔨 In Progress story status** (enforced here):
|
|
93
|
+
- Before picking a story, check its status is `📋 Todo`
|
|
94
|
+
- Skip any `🔨 In Progress` row (someone else is on it)
|
|
95
|
+
- Mark each story `🔨 In Progress` BEFORE invoking the executor skill (see Step 3)
|
|
96
|
+
- On completion: update to `✅ Done`; on TCR failure: revert to `📋 Todo`
|
|
97
|
+
|
|
98
|
+
Together these mean: only one loop runs at a time per project (LOCK), and within a loop, stories already claimed by humans or peer agents are skipped (status check).
|
|
99
|
+
|
|
68
100
|
### Step 3 — Route and Execute
|
|
69
101
|
|
|
70
|
-
For each item:
|
|
102
|
+
For each item, **before invoking the executor skill**, mark the story 🔨 In Progress in BACKLOG.md so brief and peer agents can see it's being worked on:
|
|
103
|
+
|
|
104
|
+
1. Edit BACKLOG.md: change the row's Status column from `📋 Todo` to `🔨 In Progress`.
|
|
105
|
+
2. Commit: `git commit -am "chore: mark US-XXX in progress"` (use the actual story id).
|
|
106
|
+
|
|
107
|
+
This commit is what makes the work visible — without it, tcr micro-commits during execution are invisible to `roll-brief`.
|
|
108
|
+
|
|
109
|
+
Then invoke the executor:
|
|
71
110
|
|
|
72
111
|
```
|
|
73
112
|
Item type → Skill invoked
|
|
@@ -77,7 +116,9 @@ FIX-XXX → Skill("roll-fix", "FIX-XXX")
|
|
|
77
116
|
REFACTOR-XXX → Skill("roll-build", "REFACTOR-XXX")
|
|
78
117
|
```
|
|
79
118
|
|
|
80
|
-
|
|
119
|
+
The executor will update the row to `✅ Done` on success (it transitions from `🔨 In Progress` → `✅ Done`, same Edit logic as from `📋 Todo`).
|
|
120
|
+
|
|
121
|
+
Before invoking, also write current item to state file:
|
|
81
122
|
|
|
82
123
|
```yaml
|
|
83
124
|
# ~/.shared/roll/loop/state.yaml
|