@seanyao/roll 4.712.1 → 4.713.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 +28 -0
- package/dist/roll.mjs +12366 -12762
- package/docs/architecture.md +63 -7
- package/guide/en/configuration.md +1 -1
- package/guide/en/faq.md +7 -6
- package/guide/en/loop-driven-architecture.md +15 -16
- package/guide/en/loop.md +34 -15
- package/guide/zh/configuration.md +1 -1
- package/guide/zh/faq.md +5 -4
- package/guide/zh/loop-driven-architecture.md +15 -16
- package/guide/zh/loop.md +21 -3
- package/package.json +1 -1
- package/skills/roll-build/SKILL.md +1 -0
- package/skills/roll-build/references/full-contract.md +17 -12
- package/skills/roll-loop/SKILL.md +8 -0
- package/skills/roll-loop/references/full-contract.md +10 -6
package/docs/architecture.md
CHANGED
|
@@ -77,10 +77,10 @@ web 站点与静态展示(当前不是活体 Supervisor 控制台)
|
|
|
77
77
|
|
|
78
78
|
这是系统的引擎。一个 Loop 是一个自治进程,按定时器唤醒,执行一个 Cycle。
|
|
79
79
|
|
|
80
|
-
**Cycle
|
|
80
|
+
**Cycle 生命周期**:选故事(先查租约 `deliveryLease`)→ 路由 agent → 创建隔离工作区 → agent 执行(TCR 循环)→ attest 硬闸 → 送交 PR → AWAITING_MERGE 挂起并释放 loop → pick 下一张卡。交付推进由 Delivery Reconciler 在任意 `roll` 调用时机会性对账完成——不依赖独立 daemon。
|
|
81
81
|
|
|
82
82
|
**关键约束**:
|
|
83
|
-
- 一 Cycle 只做一个 Story
|
|
83
|
+
- 一 Cycle 只做一个 Story。AWAITING_MERGE 后释放 loop,拿下一个;交付由 Delivery Reconciler 对账推进,不 block loop。
|
|
84
84
|
- 进程可能被 SIGKILL。下次唤醒时,通过锁龄、心跳、PID 判断孤儿态,安全接管或重做。
|
|
85
85
|
- 心跳每 60 秒写入一次。超时无心跳 → 判定死亡并落终态。
|
|
86
86
|
- 退出时无条件写入终态。这是硬约束——trap 兜底。
|
|
@@ -89,8 +89,7 @@ web 站点与静态展示(当前不是活体 Supervisor 控制台)
|
|
|
89
89
|
**Loop 类型**:
|
|
90
90
|
| 类型 | 职责 |
|
|
91
91
|
|------|------|
|
|
92
|
-
| main | 消费待办,执行完整的 pick→TCR→PR→对账
|
|
93
|
-
| pr | 监控 open loop PR:CI 挂了自愈,绿了合并 |
|
|
92
|
+
| main | 消费待办,执行完整的 pick→TCR→PR→AWAITING_MERGE→对账 周期(含 Delivery Reconciler 机会性对账) |
|
|
94
93
|
| ci | 监控 CI 状态 |
|
|
95
94
|
| alert | 消费 ALERT 文件,推送到用户 |
|
|
96
95
|
|
|
@@ -139,13 +138,70 @@ cycle 写 `loop:pending`,只做恢复探测,不启动 Builder、不把卡记
|
|
|
139
138
|
**反规则**:不因历史表现自动改写角色绑定。不做失败后的静默跨 agent 重试。指标可以*建议*
|
|
140
139
|
策略变更,但绝不绕过 human-on-the-loop。
|
|
141
140
|
|
|
142
|
-
### BC4 ·
|
|
141
|
+
### BC4 · 交付(Delivery Reconciler)
|
|
143
142
|
|
|
144
143
|
每次交付是一个 Pull Request。一个 Story 至多同时有一个 open PR。
|
|
145
144
|
|
|
146
|
-
|
|
145
|
+
**最后一公里 = 一个 reconcile 闭环,无独立守护进程。**
|
|
147
146
|
|
|
148
|
-
|
|
147
|
+
#### 交付生命周期
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
building ──attest earned──► publishable ──push+PR──► awaiting_merge ──┐
|
|
151
|
+
│ │
|
|
152
|
+
└─attest MISSING──► blocked_no_evidence (fail-loud,不推分支) │
|
|
153
|
+
▼
|
|
154
|
+
awaiting_merge ──L1/L2 强信号命中──► delivered / delivered_external
|
|
155
|
+
awaiting_merge ──CI 绿未合──► 自驱合并 (gh pr merge --squash) ──► 下轮判 delivered
|
|
156
|
+
awaiting_merge ──CI 红──► ci_failed ──► fix-forward cycle
|
|
157
|
+
awaiting_merge ──CI 长红(≥24h)──► degraded(ci_stuck),带 reason+dwell
|
|
158
|
+
awaiting_merge ──draft / 合并冲突 / 缺权限──► degraded(draft|merge_conflict|no_permission)
|
|
159
|
+
awaiting_merge ──PR 被关(未合)──► terminal(pr_closed_unmerged)
|
|
160
|
+
awaiting_merge ──mergeable UNKNOWN / gh 错──► wait(瞬时态,不判)
|
|
161
|
+
awaiting_merge ──同卡他 cycle 已 delivered──► superseded(带原因)
|
|
162
|
+
awaiting_merge ──证据不足──► 留 awaiting_merge(绝不误判)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**交付状态**:
|
|
166
|
+
|
|
167
|
+
| 状态 | 含义 |
|
|
168
|
+
|------|------|
|
|
169
|
+
| `building` | TCR 进行中 |
|
|
170
|
+
| `blocked_no_evidence` | 过了测试但缺 attest/ac-map → fail-loud,未推分支 |
|
|
171
|
+
| `awaiting_merge` | 分支+PR 已开;挂起,loop 释放并继续下一张卡 |
|
|
172
|
+
| `ci_failed` | PR CI 红 → 需 fix-forward |
|
|
173
|
+
| `delivered` | 合进 main:runner 自驱合并 |
|
|
174
|
+
| `delivered_external` | 合进 main:外部(supervisor / 人手动合 / 其他 cycle)——patch-id / PR-state 反查确认,一等公民 |
|
|
175
|
+
| `superseded` | 同卡另一 cycle 已 delivered |
|
|
176
|
+
| `abandoned` | lease 释放 / 卡撤销 |
|
|
177
|
+
|
|
178
|
+
#### 分层真相判定(强→弱,任一强信号即 delivered)
|
|
179
|
+
|
|
180
|
+
| 层 | 信号 | 可靠性 | 何时可用 |
|
|
181
|
+
|---|---|---|---|
|
|
182
|
+
| L1 | **PR 状态**:`gh pr view` → `MERGED`;gh 沉默时离线同源——main 上的 `(#N)` merge commit(无 PR 号的旧 cycle 回退到 subject 含 story-id) | 最强(权威) | gh 可用且 PR 可解析时;离线也可从 main 的 git log 读 |
|
|
183
|
+
| L2 | **patch-id 等价**:`git patch-id(diff origin/main...branch)` ∈ main 候选 merge commit 的 patch-id 集 | 强(squash/rebase 安全) | 离线也行;不依赖 gh |
|
|
184
|
+
| L3 | **backlog Done + attest 报告存在** | 弱(仅佐证,单独不足) | 兜底交叉验证 |
|
|
185
|
+
|
|
186
|
+
**判定规则**:`delivered` 需 ≥1 个强信号(L1 或 L2)。L3 单独不足以判 delivered(agent 可能预写 Done)。L1 与 L2 冲突时以 L1 为准并告警。全不命中 → 留 `awaiting_merge`,**绝不误判**。
|
|
187
|
+
|
|
188
|
+
**边界态判据(US-DELIV-010)**:卡死/终态 PR 不再笼统挂起——`reconcileDelivery` 派生确定性判定:PR 被关(未合)→ `terminal(pr_closed_unmerged)`;draft / 合并冲突 / CI 长红(滞留 ≥ `CI_STUCK_DWELL_MS`=24h,锚定 `delivery:published`)/ gh 缺权限(auth)→ `degraded`,均带 **reason + dwell**(`roll loop reconcile --json` 可读,供呈现与人工分流);mergeable UNKNOWN、gh 瞬时错误(offline/provider_error/not_found)→ `wait`;分支删除 / force-push 改了 patch-id → L2 自然失效,靠 L1 或 wait;squash 改写标题不影响 patch-id(按 diff 内容计算)。铁律不变:**degraded/terminal 绝不等于 delivered**,draft/冲突/UNKNOWN 也绝不触发自驱合并。
|
|
189
|
+
|
|
190
|
+
**单一真相引擎(US-DELIV-008)**:`roll loop reconcile` 命令与 `roll loop cycles` 读路径共用同一个纯函数 `reconcileDelivery` + 同一份事实采集(`packages/cli/src/lib/delivery-facts.ts`)。旧 subject-match 探针已退役为 L1 的离线输入信号,不再是并行的第二判据——读路径与命令对同一 cycle 的 delivered 判定永不分歧。
|
|
191
|
+
|
|
192
|
+
#### Delivery Reconciler
|
|
193
|
+
|
|
194
|
+
纯判定 + 薄 IO,在任意 `roll` 调用 / cycle 边界 / `roll loop reconcile` 时机会性运行:
|
|
195
|
+
|
|
196
|
+
- **触发点**:(a) 每次 `roll loop` cycle 边界;(b) 任意 `roll` 命令的前置机会性 reconcile;(c) 显式 `roll loop reconcile [--json]`;(d) CI 里可选一步
|
|
197
|
+
- **自驱合并**:CI 绿且未合 → `gh pr merge --squash`,不依赖仓库 auto-merge 开关、不依赖 launchd
|
|
198
|
+
- **外部合并反查**:supervisor / 人手动合并被 patch-id / PR-state 自动回填为 `delivered_external`——手动合并是一等支持路径,不是泄漏
|
|
199
|
+
- **幂等 & 崩溃可续**:reconcile 反复跑永远安全,向真相收敛
|
|
200
|
+
- **退役守护进程**:原 `com.roll.pr.<slug>` launchd PR-loop 守护已退役,合并逻辑整体搬进 Delivery Reconciler,不再需要常驻进程
|
|
201
|
+
|
|
202
|
+
#### 交付判定
|
|
203
|
+
|
|
204
|
+
合并入 main 才算交付。PR 已开、CI 已绿、agent 声称完成都不算——事后对账,只认 main 上真实的 merge commit。main 是唯一交付真相;reconcile 只把真相投影回 cycle 行。
|
|
149
205
|
|
|
150
206
|
### BC5 · 演化
|
|
151
207
|
|
|
@@ -16,7 +16,7 @@ shared conventions.
|
|
|
16
16
|
| `ROLL_LOOP_FORCE` | unset | When set to any non-empty value, `roll loop` bypasses the active-window check and the pause file. `roll loop now` and `roll loop test` set this internally; export it manually only when you want a cron-scheduled run to ignore quiet hours. |
|
|
17
17
|
| `ROLL_LOOP_NO_HEAL` | `0` | Set to `1` to disable post-build CI self-heal and restore fail-fast behaviour. Useful for debugging or when you want to cap autonomous spend per cycle. |
|
|
18
18
|
| `ROLL_LOOP_HEAL_MAX` | `2` | Maximum number of CI self-heal attempts per story after the build commits land. Raise it for noisy CI environments; lower it to fail faster. |
|
|
19
|
-
| `ROLL_PR_MERGE_TIMEOUT` | `600` (seconds) | **Deprecated (US-AUTO-044).** The main loop no longer waits for merge
|
|
19
|
+
| `ROLL_PR_MERGE_TIMEOUT` | `600` (seconds) | **Deprecated (US-AUTO-044).** The main loop no longer waits for merge; the Delivery Reconciler advances eligible PRs opportunistically. |
|
|
20
20
|
| `ROLL_LOOP_NO_POPUP` | unset | When set to any non-empty value, the runner does **not** auto-spawn a macOS Terminal.app window running `tmux attach`. For tests and headless batch runs — the popup outlives the killed tmux session and clutters the desktop. |
|
|
21
21
|
| `ROLL_LOOP_GC_RETENTION_DAYS` | `30` | Override the GC retention period for `roll loop gc`. Takes precedence over `loop_gc.retention_days` in `.roll/local.yaml`. |
|
|
22
22
|
| `ROLL_FEED_BUDGET_BYTES` | `16384` | Byte budget for the context feed handed to the inner agent each cycle. Set it to a positive integer to dial the feed to the inner agent's capacity; non-numeric or non-positive values fall back to the default. |
|
package/guide/en/faq.md
CHANGED
|
@@ -539,9 +539,10 @@ to know which step ate the time before deciding whether to tune anything.
|
|
|
539
539
|
|
|
540
540
|
**Why this matters:** Every cycle is sliced into six named phases
|
|
541
541
|
(`startup` / `preflight` / `worktree_setup` / `agent_invoke` / `publish_push` /
|
|
542
|
-
`cleanup`). The main loop no longer waits for merge (US-AUTO-044)
|
|
543
|
-
|
|
544
|
-
|
|
542
|
+
`cleanup`). The main loop no longer waits for merge (US-AUTO-044): it records
|
|
543
|
+
`awaiting_merge`, then the Delivery Reconciler advances the PR on cycle
|
|
544
|
+
boundaries, read paths, or an explicit `roll loop reconcile`. The top-line
|
|
545
|
+
duration is therefore dominated by `agent_invoke` in almost every cycle.
|
|
545
546
|
|
|
546
547
|
**What to do:**
|
|
547
548
|
|
|
@@ -551,9 +552,9 @@ now dominated by `agent_invoke` in almost every cycle.
|
|
|
551
552
|
3. Common patterns:
|
|
552
553
|
- `agent_invoke` dominating → expected for a multi-file story; nothing
|
|
553
554
|
to tune unless you can split the story.
|
|
554
|
-
- PR sitting open / not merging →
|
|
555
|
-
|
|
556
|
-
|
|
555
|
+
- PR sitting open / not merging → run `roll loop reconcile --json`, then
|
|
556
|
+
inspect its CI, draft/review, conflict, or permission reason. This is no
|
|
557
|
+
longer a main-loop phase.
|
|
557
558
|
- `worktree_setup` > 30 s → likely a slow `git fetch origin`; transient
|
|
558
559
|
network issue.
|
|
559
560
|
- `preflight` > 30 s → previous cycles left orphan worktrees; loop is
|
|
@@ -35,29 +35,28 @@ These problems don't matter much for one-shot tasks ("summarize this document",
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
## Roll's Approach: Independent Loops
|
|
38
|
+
## Roll's Approach: Independent Loops + Reconciliation
|
|
39
39
|
|
|
40
|
-
Roll coordinates AI agents differently. Instead of a central planner and a shared execution graph, Roll uses **independent loops**
|
|
40
|
+
Roll coordinates AI agents differently. Instead of a central planner and a shared execution graph, Roll uses **independent loops** with one job each, plus an event-backed Delivery Reconciler that advances published work opportunistically.
|
|
41
41
|
|
|
42
42
|
```
|
|
43
43
|
BACKLOG ←──────── shared state ────────→ git / PRs / alerts
|
|
44
44
|
│
|
|
45
|
-
|
|
46
|
-
▼ ▼
|
|
47
|
-
main loop
|
|
48
|
-
|
|
49
|
-
deliver
|
|
50
|
-
stories
|
|
51
|
-
merge
|
|
45
|
+
┌───────┼──────────────┬──────────┐
|
|
46
|
+
▼ ▼ ▼ ▼
|
|
47
|
+
main loop reconciler dream brief
|
|
48
|
+
cycle boundary/read daily daily
|
|
49
|
+
deliver merge + credit scan digest
|
|
50
|
+
stories from main code
|
|
52
51
|
```
|
|
53
52
|
|
|
54
|
-
Each loop:
|
|
53
|
+
Each scheduled loop:
|
|
55
54
|
1. **Polls** a specific artifact (BACKLOG, open PRs, alert file)
|
|
56
55
|
2. **Acts** on what it finds (write code, heal CI, merge)
|
|
57
56
|
3. **Writes back** to shared artifacts (commits, PR comments, BACKLOG updates)
|
|
58
57
|
4. **Sleeps** until next interval
|
|
59
58
|
|
|
60
|
-
Loops never call each other directly. They coordinate
|
|
59
|
+
Loops never call each other directly. They coordinate through artifacts. Reconciliation is not a daemon: cycle boundaries, read paths, and `roll loop reconcile` all run the same idempotent truth engine.
|
|
61
60
|
|
|
62
61
|
---
|
|
63
62
|
|
|
@@ -102,10 +101,10 @@ main loop fires:
|
|
|
102
101
|
→ writes code in TCR micro-steps (each step: test → commit or revert)
|
|
103
102
|
→ opens PR
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
→ sees PR is open, CI still running →
|
|
104
|
+
publish-boundary reconcile tick:
|
|
105
|
+
→ sees PR is open, CI still running → keeps awaiting_merge
|
|
107
106
|
|
|
108
|
-
|
|
107
|
+
next cycle/read/explicit reconcile tick:
|
|
109
108
|
→ CI green, mergeable → merges PR → done
|
|
110
109
|
```
|
|
111
110
|
|
|
@@ -125,7 +124,7 @@ Software delivery is not a one-shot task. It is an ongoing process:
|
|
|
125
124
|
|
|
126
125
|
A DAG is designed to execute once and terminate. A loop is designed to run forever, doing useful work whenever conditions are right. For continuous delivery, you want loops.
|
|
127
126
|
|
|
128
|
-
**Resilience**: Loops are isolated.
|
|
127
|
+
**Resilience**: Loops are isolated. A failed cycle does not stop other projects, and any later Roll invocation can resume reconciliation from the event ledger and main.
|
|
129
128
|
|
|
130
129
|
**Observability**: Every action a loop takes produces a git commit, a PR comment, or a BACKLOG update. The history of the system is the git log — human-readable, diffable, revertable.
|
|
131
130
|
|
|
@@ -142,7 +141,7 @@ As the system matures, loops become more specialized:
|
|
|
142
141
|
| Loop | Cadence | Does |
|
|
143
142
|
|------|---------|------|
|
|
144
143
|
| **main loop** | 30 min | Reads BACKLOG → writes code → opens PR |
|
|
145
|
-
| **
|
|
144
|
+
| **Delivery Reconciler** | cycle boundaries, reads, explicit command | Merges eligible green PRs and credits delivery from main evidence |
|
|
146
145
|
| **CI loop** | 5 min | Detects flaky tests, collects timing data, reruns failures |
|
|
147
146
|
| **alert loop** | 1 min | Aggregates `_LOOP_ALERT` entries, sends notifications |
|
|
148
147
|
| **bug loop** | 1 hour | Scans logs and error patterns, opens FIX stories |
|
package/guide/en/loop.md
CHANGED
|
@@ -98,6 +98,13 @@ Loop is scheduled via **launchd** (macOS). By default, every hour at a
|
|
|
98
98
|
project-derived minute (different projects get different offsets to avoid
|
|
99
99
|
collisions).
|
|
100
100
|
|
|
101
|
+
`roll loop on` is safe to run again when the scheduler is already installed.
|
|
102
|
+
It rewrites the current runner and plist, applies them with
|
|
103
|
+
`launchctl bootout` followed by `launchctl bootstrap`, and verifies the live
|
|
104
|
+
job before reporting success. If launchd still rejects the job, the command
|
|
105
|
+
exits non-zero and prints the exact domain, label, plist path, and diagnostic
|
|
106
|
+
commands; it never leaves a failed bootstrap looking enabled.
|
|
107
|
+
|
|
101
108
|
```
|
|
102
109
|
Active window: 0–24 (always on — the shipped default)
|
|
103
110
|
```
|
|
@@ -271,6 +278,23 @@ three ranking suggestions and reasons. The ranking is advisory only; Hold, Cut,
|
|
|
271
278
|
unsatisfied dependencies, skip-list, open PR, merged-delivery, and pending-publish
|
|
272
279
|
gates still decide eligibility.
|
|
273
280
|
|
|
281
|
+
### One Card, One Lease
|
|
282
|
+
|
|
283
|
+
By default a card has exactly one delivery lease. Before picking, the picker
|
|
284
|
+
consults the delivery leases derived from the event stream: a card already
|
|
285
|
+
`in_flight`, `awaiting_merge`, `ci_red`, or `delivered` is skipped with a
|
|
286
|
+
`card held: <state> (<cycle>)` reason (event `pick:skipped`, idle reason
|
|
287
|
+
`all_leased`). Same-card parallel builds are off — quality redundancy comes
|
|
288
|
+
from the adversarial pair inside one cycle, not from duplicate builds that only
|
|
289
|
+
one merge can land. A cycle that ended without delivering releases its lease,
|
|
290
|
+
so a legal fix-forward retry of the same card stays pickable.
|
|
291
|
+
|
|
292
|
+
`roll loop run-once --race` is the explicit opt-in for same-card parallel
|
|
293
|
+
racing (carried to child cycles as `ROLL_LOOP_RACE=1`). On the FIRST merge,
|
|
294
|
+
`roll loop reconcile` atomically supersedes the remaining sibling cycles
|
|
295
|
+
(`delivery:reconciled{superseded}`), so racing costs at most one merge plus
|
|
296
|
+
discarded sibling work — never duplicate deliveries.
|
|
297
|
+
|
|
274
298
|
### Goal Mode vs Scheduled Mode
|
|
275
299
|
|
|
276
300
|
`roll loop go` is a manual goal session, not a launchd scheduler tick. While it
|
|
@@ -1016,15 +1040,11 @@ immediately writes an ALERT and stops. Instead it tries to fix CI autonomously.
|
|
|
1016
1040
|
- If still red after `ROLL_LOOP_HEAL_MAX` attempts (default 2): writes ALERT and stops.
|
|
1017
1041
|
4. If CI is red AND heal is exhausted or `ROLL_LOOP_NO_HEAL=1`: writes ALERT (existing behavior).
|
|
1018
1042
|
|
|
1019
|
-
**Loop
|
|
1020
|
-
|
|
1021
|
-
`
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
default 2) and a per-PR lock that prevents duplicate concurrent heals. The heal
|
|
1025
|
-
runs in the background so the PR tick continues. When heal is disabled
|
|
1026
|
-
(`ROLL_LOOP_NO_HEAL=1`) or the budget is exhausted, it writes a deduped
|
|
1027
|
-
`[TYPE:loop-pr-ci-red]` ALERT instead of silently skipping.
|
|
1043
|
+
**Loop-owned PRs (`loop/*`) that turn red after a cycle exits** remain
|
|
1044
|
+
`awaiting_merge`. The Delivery Reconciler reports a long-red PR as
|
|
1045
|
+
`degraded(ci_stuck)` after the dwell threshold; it never invents delivery and
|
|
1046
|
+
never runs a separate background healer. Fix the branch, then run
|
|
1047
|
+
`roll loop reconcile` (or let the next cycle/read path tick retry).
|
|
1028
1048
|
|
|
1029
1049
|
**Environment variables:**
|
|
1030
1050
|
|
|
@@ -1067,11 +1087,10 @@ Fork PRs are skipped (no write access) with an ALERT.
|
|
|
1067
1087
|
- Bot `APPROVED` → skip, let auto-merge proceed
|
|
1068
1088
|
- Bot `CHANGES_REQUESTED` → write ALERT (loop PR rejected by GHA reviewer)
|
|
1069
1089
|
|
|
1070
|
-
**Active merge
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
retried on the next tick.
|
|
1090
|
+
**Active merge:** when a PR is eligible, CI-green, and mergeable, the Delivery
|
|
1091
|
+
Reconciler runs `gh pr merge --squash` rather than depending on repo-level
|
|
1092
|
+
auto-merge. Merge failure is non-fatal: the PR stays `awaiting_merge` and is
|
|
1093
|
+
retried on a later reconcile tick.
|
|
1075
1094
|
|
|
1076
1095
|
### Optional: Event-driven PR review (GHA)
|
|
1077
1096
|
|
|
@@ -1172,7 +1191,7 @@ so the tmux viewer never looks frozen.
|
|
|
1172
1191
|
| 5 | `publish_push` | push branch + open PR (or doc-only merge) | 5 – 30 s |
|
|
1173
1192
|
| 6 | `cleanup` | env cleanup + emit PR final state + worktree teardown | < 1 s |
|
|
1174
1193
|
|
|
1175
|
-
> **US-AUTO-044**: the main loop exits after opening the PR and **no longer waits for merge**.
|
|
1194
|
+
> **US-AUTO-044**: the main loop exits after opening the PR and **no longer waits for merge**. The event-backed Delivery Reconciler advances it at cycle boundaries, read paths, or via `roll loop reconcile`; there is no dedicated merge daemon. A story with an open PR is skipped by the eligibility gate, so it is neither re-opened nor falsely marked Done.
|
|
1176
1195
|
|
|
1177
1196
|
Idle / failed / aborted cycles only emit the phases they actually entered.
|
|
1178
1197
|
At cycle exit, the inner runner prints a phase breakdown panel sorted by
|
|
@@ -15,7 +15,7 @@ Roll 在启动时解析三个环境变量。在运行 `roll` 之前覆盖任意
|
|
|
15
15
|
| `ROLL_LOOP_FORCE` | 未设置 | 设为任意非空值时,`roll loop` 会跳过活跃窗口和 pause 文件检查。`roll loop now` 和 `roll loop test` 内部已自动设置;只有当你希望 cron 定时调度也忽略静默时段时,才需要手动 export。 |
|
|
16
16
|
| `ROLL_LOOP_NO_HEAL` | `0` | 设为 `1` 关闭构建完成后的 CI 自愈,恢复 fail-fast。调试或想给自主循环按周期省钱时使用。 |
|
|
17
17
|
| `ROLL_LOOP_HEAL_MAX` | `2` | 故事提交落地后,CI 自愈的最大尝试次数。CI 抖动较多时可调大;想更快失败则调小。 |
|
|
18
|
-
| `ROLL_PR_MERGE_TIMEOUT` | `600`(秒) | **已弃用(US-AUTO-044)。** 主 loop
|
|
18
|
+
| `ROLL_PR_MERGE_TIMEOUT` | `600`(秒) | **已弃用(US-AUTO-044)。** 主 loop 不再等合并;符合条件的 PR 由 Delivery Reconciler 按机会推进。 |
|
|
19
19
|
| `ROLL_LOOP_NO_POPUP` | 未设置 | 设为任意非空值时,runner 在 macOS 下不再自动弹出 Terminal.app 窗口运行 `tmux attach`。供测试和后台跑批使用——窗口在 tmux session 结束后会留下空 attach 提示,污染桌面。 |
|
|
20
20
|
| `ROLL_LOOP_GC_RETENTION_DAYS` | `30` | 覆盖 `roll loop gc` 的保留天数。优先级高于 `.roll/local.yaml` 中的 `loop_gc.retention_days`。 |
|
|
21
21
|
| `ROLL_FEED_BUDGET_BYTES` | `16384` | 每个周期交给内层 agent 的上下文 feed 字节预算。设为正整数即可调节容量;非数字或非正数回落默认值。 |
|
package/guide/zh/faq.md
CHANGED
|
@@ -483,8 +483,9 @@ tmux 观测 pane 使用同一个 watch 入口。
|
|
|
483
483
|
|
|
484
484
|
**原因:** 每轮 cycle 在内部被切成 6 个命名阶段
|
|
485
485
|
(`startup` / `preflight` / `worktree_setup` / `agent_invoke` /
|
|
486
|
-
`publish_push` / `cleanup`)。主 loop 不再等合并(US-AUTO-044
|
|
487
|
-
|
|
486
|
+
`publish_push` / `cleanup`)。主 loop 不再等合并(US-AUTO-044):它记录
|
|
487
|
+
`awaiting_merge`,随后由 Delivery Reconciler 在 cycle 边界、读路径或显式
|
|
488
|
+
`roll loop reconcile` 时推进。因此现在几乎每轮都是 `agent_invoke` 占大头。
|
|
488
489
|
|
|
489
490
|
**怎么办:**
|
|
490
491
|
|
|
@@ -494,8 +495,8 @@ PR Loop 异步处理),所以现在几乎每轮都是 `agent_invoke` 占大
|
|
|
494
495
|
3. 常见模式:
|
|
495
496
|
- `agent_invoke` 占绝大头 → 多文件故事的正常表现;除非能拆故事
|
|
496
497
|
否则没什么可调的。
|
|
497
|
-
- PR 一直开着没合 →
|
|
498
|
-
|
|
498
|
+
- PR 一直开着没合 → 运行 `roll loop reconcile --json`,再查 CI、draft/
|
|
499
|
+
评审、冲突或权限原因。这已不再是主 loop 的阶段。
|
|
499
500
|
- `worktree_setup` > 30 秒 → `git fetch origin` 慢;通常是临时网络
|
|
500
501
|
抖动。
|
|
501
502
|
- `preflight` > 30 秒 → 上轮留下了孤儿 worktree,loop 正在回收;
|
|
@@ -35,29 +35,28 @@
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
## Roll 的方案:独立 Loop
|
|
38
|
+
## Roll 的方案:独立 Loop + 交付对账
|
|
39
39
|
|
|
40
|
-
Roll
|
|
40
|
+
Roll 采用不同的协调方式。没有中心规划器,没有共享执行图,而是职责单一的**独立 Loop**,再加一个按机会推进已发布交付的事件型 Delivery Reconciler:
|
|
41
41
|
|
|
42
42
|
```
|
|
43
43
|
BACKLOG ←──────── 共享状态 ────────→ git / PR / alert
|
|
44
44
|
│
|
|
45
|
-
|
|
46
|
-
▼ ▼
|
|
47
|
-
主 loop
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
merge
|
|
45
|
+
┌───────┼──────────────┬──────────┐
|
|
46
|
+
▼ ▼ ▼ ▼
|
|
47
|
+
主 loop 交付对账器 dream brief
|
|
48
|
+
cycle 边界/读路径 daily daily
|
|
49
|
+
交付 合并+main 记账 扫描 摘要
|
|
50
|
+
story
|
|
52
51
|
```
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
每个定时 Loop:
|
|
55
54
|
1. **轮询**特定 artifact(BACKLOG、open PR、alert 文件)
|
|
56
55
|
2. **行动**(写代码、heal CI、合 PR)
|
|
57
56
|
3. **写回**共享 artifact(提交、PR 评论、BACKLOG 更新)
|
|
58
57
|
4. **休眠**直到下一个周期
|
|
59
58
|
|
|
60
|
-
Loop
|
|
59
|
+
Loop 之间从不直接调用对方,完全通过 artifact 协调。交付对账器不是 daemon:cycle 边界、读路径和 `roll loop reconcile` 都运行同一个幂等真相引擎。
|
|
61
60
|
|
|
62
61
|
---
|
|
63
62
|
|
|
@@ -102,10 +101,10 @@ Loop 之间从不直接调用对方。完全通过 artifact 协调。
|
|
|
102
101
|
→ TCR 微步骤写代码(每步:测试 → 提交 或 回滚)
|
|
103
102
|
→ 开 PR
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
→ 发现 PR 是 open,CI 还在跑 →
|
|
104
|
+
发布边界触发 reconcile tick:
|
|
105
|
+
→ 发现 PR 是 open,CI 还在跑 → 保持 awaiting_merge
|
|
107
106
|
|
|
108
|
-
|
|
107
|
+
下个 cycle / 读路径 / 显式 reconcile tick:
|
|
109
108
|
→ CI 绿,可合并 → 合 PR → 完成
|
|
110
109
|
```
|
|
111
110
|
|
|
@@ -125,7 +124,7 @@ PR loop 再次触发(5 分钟后):
|
|
|
125
124
|
|
|
126
125
|
DAG 被设计为执行一次后终止。Loop 被设计为永远运行,在条件合适时做有价值的工作。持续交付需要 Loop。
|
|
127
126
|
|
|
128
|
-
**韧性**:Loop
|
|
127
|
+
**韧性**:Loop 相互隔离。一轮 cycle 失败不会拖垮其他项目;后续任意 Roll 调用都能从事件账本与 main 继续对账。
|
|
129
128
|
|
|
130
129
|
**可观测性**:Loop 的每一个动作都会产生一个 git commit、一条 PR 评论或一次 BACKLOG 更新。系统的历史就是 git log——人类可读、可 diff、可回滚。
|
|
131
130
|
|
|
@@ -142,7 +141,7 @@ DAG 被设计为执行一次后终止。Loop 被设计为永远运行,在条
|
|
|
142
141
|
| Loop | 节奏 | 职责 |
|
|
143
142
|
|------|------|------|
|
|
144
143
|
| **主 loop** | 30 分钟 | 读 BACKLOG → 写代码 → 开 PR |
|
|
145
|
-
|
|
|
144
|
+
| **交付对账器** | cycle 边界、读路径、显式命令 | 合并符合条件的绿色 PR,并从 main 证据记账 |
|
|
146
145
|
| **CI loop** | 5 分钟 | 检测 flaky 测试,收集耗时数据,重跑失败 |
|
|
147
146
|
| **alert loop** | 1 分钟 | 聚合 `_LOOP_ALERT`,发送通知 |
|
|
148
147
|
| **bug loop** | 1 小时 | 扫描日志和错误模式,开 FIX story |
|
package/guide/zh/loop.md
CHANGED
|
@@ -92,6 +92,11 @@ Loop 通过 **launchd**(macOS)调度。默认每小时在一个根据项目
|
|
|
92
92
|
|
|
93
93
|
活跃窗口之外,loop 静默退出,不执行任何操作。默认 `0-24` 意味着任何时刻触发都会执行;只有显式收窄窗口后,窗口外的触发才会静默退出。
|
|
94
94
|
|
|
95
|
+
调度器已经安装时也可以安全地再次运行 `roll loop on`。命令会重写当前 runner
|
|
96
|
+
和 plist,依次执行 `launchctl bootout`、`launchctl bootstrap`,并在报告成功前
|
|
97
|
+
验证任务确实存活。如果 launchd 仍拒绝任务,命令会以非零码退出,并打印准确的
|
|
98
|
+
域、标签、plist 路径和可直接执行的诊断命令;bootstrap 失败不会再伪装成已启用。
|
|
99
|
+
|
|
95
100
|
## 配置调度(Configuring the schedule)
|
|
96
101
|
|
|
97
102
|
不用再手工编辑 `~/.roll/config.yaml` 和 `.roll/local.yaml` 再祈祷 launchd plist 能 reload 上。改用 `roll config` 命令族:每次写入都落到正确的 yaml 文件,**并且**自动重生 runner、重 bootstrap launchd plist、立刻在 `roll loop status` 反映——没有手工 reload 这一步。
|
|
@@ -240,6 +245,19 @@ roll agent list # 查看本机已装的 agent
|
|
|
240
245
|
Hold、Cut、未满足依赖、skip-list、open PR、已合并交付和 pending-publish gate 仍决定
|
|
241
246
|
卡片是否可选。
|
|
242
247
|
|
|
248
|
+
### 一卡一租约
|
|
249
|
+
|
|
250
|
+
默认一张卡只有一条交付租约。选卡前 picker 会先咨询由事件流投影出的交付租约:
|
|
251
|
+
卡已处 `in_flight`、`awaiting_merge`、`ci_red` 或 `delivered` 一律 skip,理由形如
|
|
252
|
+
`card held: <state> (<cycle>)`(事件 `pick:skipped`,idle 理由 `all_leased`)。
|
|
253
|
+
同卡并行建造默认关闭——质量冗余由单 cycle 内的攻防结对提供,不靠"只有一份能合入"
|
|
254
|
+
的重复建造。已结束但未交付的 cycle 会释放租约,所以同卡的合法 fix-forward 重试仍可被选。
|
|
255
|
+
|
|
256
|
+
`roll loop run-once --race` 是同卡并行竞速的显式开关(经 `ROLL_LOOP_RACE=1` 传给
|
|
257
|
+
子 cycle)。首个 merge 发生时,`roll loop reconcile` 原子取消其余 sibling cycle
|
|
258
|
+
(`delivery:reconciled{superseded}`),竞速的代价至多是一份合入加 sibling 废活,
|
|
259
|
+
绝不产生重复交付。
|
|
260
|
+
|
|
243
261
|
### Goal Mode 与定时模式
|
|
244
262
|
|
|
245
263
|
`roll loop go` 是手动 goal session,不是 launchd 定时 tick。运行期间 Roll 会持有
|
|
@@ -904,9 +922,9 @@ ALERT 条目会在 `roll loop status`、`roll loop alert` 和 cycle/story 证据
|
|
|
904
922
|
3. CI 红且允许热修:通过 `roll loop hotfix-head-context` 抓 CI 失败日志和最近 commit diff,调 `roll-fix` 修复,等 CI 变绿。超过 `ROLL_LOOP_HEAL_MAX`(默认 2)次还没修好则写 ALERT 停工。
|
|
905
923
|
4. CI 红且已用完热修次数或 `ROLL_LOOP_NO_HEAL=1`:写 ALERT(保留原有行为)。
|
|
906
924
|
|
|
907
|
-
自家 PR(`loop/*` 分支)在 cycle
|
|
925
|
+
自家 PR(`loop/*` 分支)在 cycle 结束后转红时会保持 `awaiting_merge`。Delivery Reconciler 在超过滞留阈值后报告 `degraded(ci_stuck)`;它不会伪造 delivered,也不运行独立后台 healer。修复分支后运行 `roll loop reconcile`(或等待下一次 cycle/读路径 tick)重试。
|
|
908
926
|
|
|
909
|
-
|
|
927
|
+
符合资格、CI 绿、可合并的 PR 会被 Delivery Reconciler **主动合并**:直接 `gh pr merge --squash`,不依赖仓库级 auto-merge;合并失败非致命,PR 保持 `awaiting_merge`,后续 tick 重试。
|
|
910
928
|
|
|
911
929
|
**环境变量:**
|
|
912
930
|
|
|
@@ -1036,7 +1054,7 @@ PR 等合并)每 30–60s 还会 emit 一次 `phase_tick` 心跳,tmux 不再
|
|
|
1036
1054
|
| 5 | `publish_push` | push 分支 + 建 PR(doc-only 直接合) | 5 – 30 秒 |
|
|
1037
1055
|
| 6 | `cleanup` | 环境清理 + 落 PR 终态 + 拆 worktree | < 1 秒 |
|
|
1038
1056
|
|
|
1039
|
-
> **US-AUTO-044**:主 loop 开完 PR
|
|
1057
|
+
> **US-AUTO-044**:主 loop 开完 PR 即退,**不再等合并**。事件型 Delivery Reconciler 在 cycle 边界、读路径或显式 `roll loop reconcile` 时推进;没有独立合并 daemon。有 open PR 的 story 由资格闸跳过,不会重复开,也不会假 Done。
|
|
1040
1058
|
|
|
1041
1059
|
Idle / failed / aborted cycle 只 emit 实际进入过的阶段。
|
|
1042
1060
|
cycle 收尾时 inner runner 在 stdout 打一份按耗时降序的面板:
|
package/package.json
CHANGED
|
@@ -43,6 +43,7 @@ same TCR, evidence, Evaluator, and release gates apply in both modes.
|
|
|
43
43
|
- In the test-gated loop the tests are the weakest link: an isolated Test Adequacy reviewer (Phase 6 Agent 4) audits the tests against the AC, seeing only AC + test files — never the implementation diff or builder reasoning. Self-review of one's own tests (Phase 2) is necessary but not isolation.
|
|
44
44
|
- Self-review, attest, and E2E evidence remain required. The Review Score is produced by the runner's fresh-session peer Reviewer — the agent does NOT self-score.
|
|
45
45
|
- Docs/code/product alignment is a DoD gate: user-visible behavior, command, output-copy, site, or delivery-view changes update the touched README/docs/guide/site/help in the same delivery.
|
|
46
|
+
- Done ≡ merged to `main`. After publish the cycle is `awaiting_merge`; the Delivery Reconciler (no daemon) self-drives the merge and reconciles truth from `main` — manual merges are first-class (`delivered_external`). Never pre-flip a card to Done on an open PR.
|
|
46
47
|
|
|
47
48
|
## Gotchas
|
|
48
49
|
|
|
@@ -750,17 +750,21 @@ story (loop or manual Phase 10.6) and earn the report at delivery time.
|
|
|
750
750
|
|
|
751
751
|
**Done ≡ merged (FIX-322/323).** A card is `✅ Done` only once its delivery is
|
|
752
752
|
**merged to `main`** — not when the branch is pushed and not while the PR is
|
|
753
|
-
merely open. `
|
|
754
|
-
|
|
753
|
+
merely open. `awaiting_merge` (pushed / PR open, awaiting merge) is **not**
|
|
754
|
+
delivered. Consequences you can rely on:
|
|
755
755
|
|
|
756
|
-
- The picker **skips** any card that already has a merged delivery
|
|
757
|
-
reset to 📋 Todo after merge is not re-picked
|
|
758
|
-
-
|
|
759
|
-
|
|
756
|
+
- The picker **skips** any card that already has a merged delivery (one card,
|
|
757
|
+
one `deliveryLease`), so a card reset to 📋 Todo after merge is not re-picked
|
|
758
|
+
and re-built.
|
|
759
|
+
- Truth reconciles from `main`: the **Delivery Reconciler** (US-DELIV-001..007,
|
|
760
|
+
no daemon — the `com.roll.pr.<slug>` PR Loop is retired) confirms merges via
|
|
761
|
+
PR-state (L1) / patch-id (L2) and flips the cycle to `delivered` — manual /
|
|
762
|
+
external merges included (`delivered_external` is first-class).
|
|
760
763
|
|
|
761
764
|
So on the manual path, flip the row to Done **after** the PR merges; under
|
|
762
|
-
`roll-loop` the
|
|
763
|
-
merge
|
|
765
|
+
`roll-loop` the cycle ends at publish (`awaiting_merge`) and the reconciler
|
|
766
|
+
self-drives the merge (`gh pr merge --squash` on green CI) and flips the row
|
|
767
|
+
once the merge lands on `main` — do not pre-flip on a still-open PR.
|
|
764
768
|
|
|
765
769
|
Both locations must be updated — neither can be skipped:
|
|
766
770
|
|
|
@@ -863,10 +867,11 @@ Before creating any file or directory:
|
|
|
863
867
|
Work is not complete until it reaches:
|
|
864
868
|
commit → push → CI signal → **PR merged to main** → deploy → online
|
|
865
869
|
verification → backlog update.
|
|
866
|
-
A pushed branch or an open PR is `
|
|
867
|
-
the row flips to `✅ Done` only after the merge. Truth reconciles from
|
|
868
|
-
|
|
869
|
-
|
|
870
|
+
A pushed branch or an open PR is `awaiting_merge`, NOT delivered;
|
|
871
|
+
the row flips to `✅ Done` only after the merge. Truth reconciles from
|
|
872
|
+
`main`: the Delivery Reconciler confirms merges via PR-state / patch-id
|
|
873
|
+
(manual merges included → `delivered_external`) and the picker skips
|
|
874
|
+
already-merged cards.
|
|
870
875
|
|
|
871
876
|
2. **TCR for every micro-step**
|
|
872
877
|
- Each behavior change: Test → Green=Commit / Red=Revert
|
|
@@ -38,6 +38,14 @@ bypasses pause, budget, route, evidence, Evaluator, or release gates.
|
|
|
38
38
|
4. Persist events, runs, alerts, and status.
|
|
39
39
|
5. Pause on repeated failure.
|
|
40
40
|
|
|
41
|
+
## Delivery Lifecycle (US-DELIV-001..007)
|
|
42
|
+
|
|
43
|
+
最后一公里 = 一个 reconcile 闭环,无独立守护进程(`com.roll.pr.<slug>` PR Loop 已退役)。
|
|
44
|
+
|
|
45
|
+
- A cycle ends at publish: branch pushed + PR opened → `awaiting_merge`; the loop is released to pick the next card. Nothing blocks on merge.
|
|
46
|
+
- The **Delivery Reconciler** advances delivery opportunistically — cycle boundaries, read paths, and explicit `roll loop reconcile`: CI-green PRs are merged self-drive (`gh pr merge --squash`); merged PRs reconcile from `main` (L1 PR-state / L2 patch-id) to `delivered`; external / manual merges reconcile to `delivered_external` (first-class); insufficient evidence stays `awaiting_merge` — never misjudge.
|
|
47
|
+
- Reconcile is idempotent and crash-safe: any single `roll` invocation can advance the truth.
|
|
48
|
+
|
|
41
49
|
## Hard Gates
|
|
42
50
|
|
|
43
51
|
- Loop never cuts a release autonomously.
|
|
@@ -301,14 +301,18 @@ After each item completes:
|
|
|
301
301
|
|
|
302
302
|
**Path B — heal exhausted (≥`ROLL_LOOP_HEAL_MAX`, default 2) or disabled (`ROLL_LOOP_NO_HEAL=1`) (exit 1):**
|
|
303
303
|
|
|
304
|
-
1. Do NOT force ✅ Done here. CI red means the PR will not merge.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
304
|
+
1. Do NOT force ✅ Done here. CI red means the PR will not merge. The main
|
|
305
|
+
loop never waits for merge — it publishes the PR, records
|
|
306
|
+
`awaiting_merge`, and exits; the **Delivery Reconciler**
|
|
307
|
+
(US-DELIV-001..007, no daemon — the `com.roll.pr.<slug>` PR Loop is
|
|
308
|
+
retired) advances it opportunistically on cycle boundaries and on
|
|
309
|
+
`roll loop reconcile`: self-drive merge (`gh pr merge --squash`) once CI
|
|
310
|
+
turns green, then reconcile to `delivered` / `delivered_external` from
|
|
311
|
+
`main` (L1 PR-state / L2 patch-id). There is no false-Done risk:
|
|
308
312
|
with worktree isolation the ✅ Done lives only in the unmerged PR, never on
|
|
309
313
|
the loop's main checkout, and the story is not re-picked meanwhile via the
|
|
310
|
-
open-PR eligibility gate (FIX-146)
|
|
311
|
-
✅ Done lands on main only when the
|
|
314
|
+
open-PR eligibility gate (FIX-146) / `deliveryLease`. The story's
|
|
315
|
+
✅ Done lands on main only when the reconciler confirms the merge.
|
|
312
316
|
2. Write ALERT to `~/.shared/roll/loop/ALERT-<slug>.md` with:
|
|
313
317
|
- story ID, time, commit SHA
|
|
314
318
|
- heal attempts made (read `heal_count:` from `state-<slug>.yaml`)
|