@seanyao/roll 3.621.1 → 3.624.1

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.
Files changed (67) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/roll.mjs +2807 -2405
  3. package/package.json +1 -1
  4. package/lib/__pycache__/changelog_audit.cpython-314.pyc +0 -0
  5. package/lib/__pycache__/github_sync.cpython-314.pyc +0 -0
  6. package/lib/__pycache__/loop-fmt.cpython-314.pyc +0 -0
  7. package/lib/__pycache__/loop_result_eval.cpython-314.pyc +0 -0
  8. package/lib/__pycache__/loop_unstick.cpython-314.pyc +0 -0
  9. package/lib/__pycache__/model_prices.cpython-314.pyc +0 -0
  10. package/lib/__pycache__/prices_fetcher.cpython-314.pyc +0 -0
  11. package/lib/__pycache__/roll-home.cpython-314.pyc +0 -0
  12. package/lib/__pycache__/roll-loop-status.cpython-314.pyc +0 -0
  13. package/lib/__pycache__/roll_git.cpython-314.pyc +0 -0
  14. package/lib/__pycache__/roll_render.cpython-314.pyc +0 -0
  15. package/lib/__pycache__/slides-render.cpython-314.pyc +0 -0
  16. package/lib/agent_usage/__pycache__/__init__.cpython-314.pyc +0 -0
  17. package/lib/agent_usage/__pycache__/gemini.cpython-314.pyc +0 -0
  18. package/lib/agent_usage/__pycache__/kimi.cpython-314.pyc +0 -0
  19. package/lib/agent_usage/__pycache__/openai.cpython-314.pyc +0 -0
  20. package/lib/agent_usage/__pycache__/pi.cpython-314.pyc +0 -0
  21. package/lib/agent_usage/__pycache__/pi_emit.cpython-314.pyc +0 -0
  22. package/lib/agent_usage/__pycache__/qwen.cpython-314.pyc +0 -0
  23. package/skills/README.md +0 -64
  24. package/skills/docs/skill-authoring.md +0 -74
  25. package/skills/reports/skill-audit-summary.md +0 -53
  26. package/skills/roll-.changelog/SKILL.md +0 -47
  27. package/skills/roll-.changelog/references/full-contract.md +0 -462
  28. package/skills/roll-.clarify/SKILL.md +0 -64
  29. package/skills/roll-.dream/SKILL.md +0 -47
  30. package/skills/roll-.dream/references/full-contract.md +0 -365
  31. package/skills/roll-.echo/SKILL.md +0 -118
  32. package/skills/roll-.qa/SKILL.md +0 -47
  33. package/skills/roll-.qa/references/full-contract.md +0 -256
  34. package/skills/roll-.review/SKILL.md +0 -148
  35. package/skills/roll-build/SKILL.md +0 -49
  36. package/skills/roll-build/references/full-contract.md +0 -968
  37. package/skills/roll-debug/SKILL.md +0 -48
  38. package/skills/roll-debug/assets/injectable-bb.js +0 -263
  39. package/skills/roll-debug/references/full-contract.md +0 -607
  40. package/skills/roll-design/SKILL.md +0 -52
  41. package/skills/roll-design/references/engineering-checklist.md +0 -298
  42. package/skills/roll-design/references/full-contract.md +0 -940
  43. package/skills/roll-doc-audit/SKILL.md +0 -51
  44. package/skills/roll-doc-audit/references/full-contract.md +0 -796
  45. package/skills/roll-doctor/SKILL.md +0 -211
  46. package/skills/roll-fix/SKILL.md +0 -49
  47. package/skills/roll-fix/references/full-contract.md +0 -672
  48. package/skills/roll-idea/SKILL.md +0 -62
  49. package/skills/roll-loop/SKILL.md +0 -50
  50. package/skills/roll-loop/references/full-contract.md +0 -534
  51. package/skills/roll-notes/SKILL.md +0 -107
  52. package/skills/roll-onboard/SKILL.md +0 -238
  53. package/skills/roll-peer/SKILL.md +0 -47
  54. package/skills/roll-peer/references/full-contract.md +0 -323
  55. package/skills/roll-propose/SKILL.md +0 -155
  56. package/skills/roll-review-pr/SKILL.md +0 -62
  57. package/skills/roll-spar/SKILL.md +0 -47
  58. package/skills/roll-spar/references/full-contract.md +0 -288
  59. package/skills/route-cases/skills.json +0 -216
  60. package/skills/scripts/audit-skills.mjs +0 -272
  61. package/skills/scripts/test-audit-skills.mjs +0 -39
  62. package/skills/tests/fixtures/skill-audit/block-skill/SKILL.md +0 -12
  63. package/skills/tests/fixtures/skill-audit/minimal-skill/SKILL.md +0 -8
  64. package/skills/tests/fixtures/skill-audit/quoted-skill/SKILL.md +0 -10
  65. package/skills/tests/fixtures/skill-audit/route-cases.json +0 -21
  66. package/skills/tests/fixtures/skill-audit/spoke-skill/SKILL.md +0 -12
  67. package/skills/tests/fixtures/skill-audit/spoke-skill/references/runbook.md +0 -3
@@ -1,62 +0,0 @@
1
- ---
2
- name: roll-idea
3
- license: MIT
4
- allowed-tools: "Read, Edit"
5
- description: "Load when the user gives a short idea or bug note that should be quickly classified, assigned an ID, and appended to backlog."
6
- ---
7
- # roll-idea
8
-
9
- ## Gotchas
10
-
11
- - Capture is intentionally shallow; do not expand into full DDD stories unless roll-design is invoked.
12
- - Do not overwrite existing backlog numbering or statuses while appending the quick capture.
13
-
14
- > One-liner in, backlog entry out. No questions asked.
15
-
16
- ## Trigger
17
-
18
- User explicitly invokes `roll-idea` with a free-text description.
19
-
20
- ```
21
- $roll-idea 评价页面的星星也要纳入到 draft 的 scope 里
22
- $roll-idea 手机端星星指标一行一个从上往下排
23
- $roll-idea 给 HOD 加一个批量导出 PDF 的功能
24
- ```
25
-
26
- ## When Not to Use
27
-
28
- - Requirement needs discussion or splitting into Stories (use `$roll-design`)
29
- - A US-XXX / FIX-XXX is ready to execute (use `$roll-build` / `$roll-fix`)
30
- - Recording a development moment or feeling (use `$roll-notes`)
31
-
32
- ## Behavior
33
-
34
- 1. **Read** `.roll/backlog.md` from the project root.
35
- 2. **Classify** the input:
36
- - If it describes a defect, regression, broken behavior, or "也要/没/不/bug" → **bug**
37
- - Otherwise → **idea**
38
- 3. **Assign ID**:
39
- - Bug → next `FIX-NNN`
40
- - Idea → next `IDEA-NNN`
41
- 4. **Append** a new row to the appropriate table in `.roll/backlog.md`:
42
- - Bug → `## 🐛 Bug Fixes` table
43
- - Idea → `## 💡 Ideas` table (create the section if it doesn't exist)
44
- 5. **Update stats** line if present (e.g. `Bug Fixes: N`, `Ideas: N`).
45
- 6. **Report** the assigned ID and where it was recorded.
46
-
47
- ## Output format
48
-
49
- ```
50
- 📝 Recorded as {ID}
51
-
52
- Type: {bug|idea}
53
- Table: {Bug Fixes|Ideas}
54
- Text: {description}
55
- ```
56
-
57
- ## Rules
58
-
59
- - Do **not** ask the user for clarification.
60
- - If the description is vague, record it verbatim and append `(细节待确认)`.
61
- - Never modify existing entries — only append new rows.
62
- - If `.roll/backlog.md` does not exist, report an error and stop.
@@ -1,50 +0,0 @@
1
- ---
2
- name: roll-loop
3
- license: MIT
4
- allowed-tools: "Read, Glob, Grep, Write, Edit, Bash(git:*), Bash(cat:*), Skill"
5
- description: "Load when configuring, explaining, or operating Roll autonomous backlog execution loop that scans Todo work and dispatches US/FIX/REFACTOR items."
6
- ---
7
- # Roll Loop
8
-
9
- This hub keeps the routing boundary, hard gates, and execution skeleton in the initial context. Load the heavier runbook only when the task actually needs the detailed contract.
10
-
11
- ## Load
12
-
13
- Load when configuring, explaining, or operating Roll autonomous backlog execution loop that scans Todo work and dispatches US/FIX/REFACTOR items.
14
-
15
- ## When Not to Use
16
-
17
- - One-shot story execution by a human agent; load roll-build or roll-fix.
18
- - Nightly architecture scan; load roll-.dream.
19
-
20
- ## Read On Demand
21
-
22
- - Read [the full contract](references/full-contract.md) before executing the workflow end to end, recovering from failures, or checking exact output templates.
23
- - Keep this hub in context for trigger boundaries and hard gates.
24
-
25
- ## Workflow Skeleton
26
-
27
- 1. Scan BACKLOG for Todo items.
28
- 2. Route US/FIX/REFACTOR to the right skill.
29
- 3. Run bounded cycles with fresh context.
30
- 4. Persist events, runs, alerts, and status.
31
- 5. Pause on repeated failure.
32
-
33
- ## Hard Gates
34
-
35
- - Loop never cuts a release autonomously.
36
- - Fail-loud and PAUSE beat silent fallback.
37
-
38
- - NEVER run `git push` or `gh pr create` yourself inside a loop cycle: the RUNNER owns publish — it pushes the branch, opens the PR, and runs the attest/peer gates first. A self-published PR bypasses every gate (FIX-245: the runner adopts it and logs a discipline breach, but the gates have already been jumped). Finish your TCR commits and stop; publishing is not your step.
39
- 循环内严禁自己 push/开 PR——发布由 runner 负责(先过闸再出门);自开 PR = 跳闸违纪。
40
-
41
- ## Gotchas
42
-
43
- - Loop dispatches backlog items; it must not merge releases or bypass human-on-the-loop decisions.
44
- - Fail-loud pause behavior is preferable to silent fallback when repeated execution breaks.
45
-
46
- ## Maintenance
47
-
48
- - Description changes require updates in `route-cases/skills.json`.
49
- - New observed failures should add a gotcha and the matching positive or negative route case.
50
- - Heavy examples, templates, recovery paths, and deterministic snippets belong in `references/`, `assets/`, or `scripts/`, not in this hub.
@@ -1,534 +0,0 @@
1
- # Full Contract Reference
2
-
3
- This file preserves the detailed contract extracted from SKILL.md. Read it when the hub points here for exact workflow steps, templates, rubrics, or recovery branches.
4
-
5
- ---
6
-
7
- # Roll Loop (Autonomous BACKLOG Executor)
8
-
9
- > Follows the Architecture Constraints, Development Discipline, and Engineering
10
- > Common Sense defined in the project AGENTS.md.
11
-
12
- Runs on a schedule. Picks up pending BACKLOG items and executes them without
13
- human intervention. The human stays informed via `roll-brief` and retains
14
- sole authority over releases.
15
-
16
- ## Execution Boundary
17
-
18
- **What roll-loop executes autonomously:**
19
- - US-XXX (User Stories) → `$roll-build`
20
- - FIX-XXX (Bug fixes) → `$roll-fix`
21
- - REFACTOR-XXX (Refactors) → `$roll-build`
22
-
23
- **What roll-loop never executes:**
24
- - Releases — production deployment is always a human decision (requires 2FA in real terminal)
25
- - Any Story marked 🚫 Hold or flagged for human review
26
- - Destructive operations outside normal skill scope
27
-
28
- **Human bypass path** — roll-loop 是默认调度器,不垄断执行权。任何时刻人可直接
29
- `$roll-build US-XXX` 或 `$roll-fix FIX-XXX` 绕过 loop 立即执行(紧急 bug、中断插入、
30
- 故事评审等场景)。loop 通过 LOCK 和 `🔨 In Progress` 状态识别并跳过人正在做的故事,
31
- 人机并行不会撞车(见 Concurrency Safety)。
32
-
33
- ## Environment Constraints (autonomous loop)
34
-
35
- You are running inside an autonomous cycle. No human is watching this turn.
36
- Adapt commands to the constraints below — otherwise you will burn turns on
37
- denied operations and the cycle will idle-exit.
38
-
39
- - **No `AskUserQuestion`**: no human can answer. If you genuinely cannot
40
- proceed without a decision, write an entry to `${HOME}/.shared/roll/loop/ALERT-<slug>.md`
41
- describing what's needed and exit cleanly.
42
- - **Avoid compound bash**: each `Bash` call must run a single command.
43
- No `cmd1 && cmd2`, no `cmd1 ; cmd2`, no pipes (`|`), no `$(...)` /
44
- backtick subshells, no `bash -c '...'` with nested quoting. These are
45
- rejected by static analysis before they run. Chain operations as
46
- separate Bash calls and read intermediate output yourself.
47
- - **Prefer Read/Edit over cat/sed**: use the `Read` tool for any file
48
- lookup, `Edit` for modifications. They cross sandbox boundaries that
49
- `cat` / `ls` / `sed` cannot.
50
- - **CWD-relative paths first**: the cycle's CWD is the per-cycle worktree.
51
- Files inside it (.roll/backlog.md, bin/roll, tests/, docs/) are always
52
- accessible. Files at `~/.shared/roll/...` are reachable via the `Read`
53
- tool but not via shell commands.
54
- - **Quote every glob**: the `Bash` tool runs commands through the user's
55
- login shell, which on macOS is typically `zsh`. zsh's default `nomatch`
56
- aborts unquoted globs that find no match with `(eval):1: no matches
57
- found: <pattern>` and exit 1, burning a turn on a meaningless error.
58
- Quote literal globs (`ls 'tests/integration/helpers.*'`) or — better —
59
- use the `Glob` tool, which is shell-agnostic and never aborts on empty
60
- matches.
61
- - **Skill invocation is the work**: route US/REFACTOR via `$roll-build`,
62
- FIX via `$roll-fix`. Do not try to re-implement those flows inline.
63
-
64
- ## Configuration
65
-
66
- ```yaml
67
- # ~/.roll/config.yaml
68
- loop:
69
- primary_agent: claude # claude | deepseek | kimi | pi | ...
70
- max_items_per_run: 1 # one story per cycle — atomic delivery, predictable cycle time
71
- brief_on_feature_complete: true
72
- retry_backoff: [2, 4, 8, 16] # seconds, exponential
73
- ```
74
-
75
- ## Workflow
76
-
77
- > **One story per cycle (强约束)**: 每个 cycle 只 pick 一个 Todo、跑完 Step 4
78
- > 立刻退出,不再回 Step 2 找下一个。理由:
79
- > - cycle 时间可预测(不会因贪心一连串 PR 撞 45 分钟 hard timeout)
80
- > - PR / events / dashboard 每行一个故事,归因清晰
81
- > - 一个故事一个 PR 一次 review,blast radius 最小
82
- >
83
- > 唯一例外是依赖修复(CI self-heal 等)已经内嵌在当前故事的 Step 4 里——
84
- > 那部分不算"新挑故事"。
85
- >
86
- > 实现层:max_items_per_run 默认 1,executor skill 跑完 Step 5 必须 exit。
87
- > 不要在同一个 cycle 内多次 emit `pick_todo` 事件。
88
-
89
- ### Step 1 — Orphan 🔨 Recovery
90
-
91
- Process-level crash recovery (LOCK, heartbeat, retry budget) is handled by
92
- the v3 runner (`packages/cli/src/runner/run-cycle.ts`) — the per-project LOCK
93
- guarantees only one cycle for this slug is alive when you start. So at
94
- this point, any `🔨 In Progress` row in `.roll/backlog.md` belongs to a
95
- previous cycle that crashed before flipping it back; reclaim it before
96
- scanning.
97
-
98
- 1. Scan .roll/backlog.md for all rows whose Status column contains `🔨 In Progress`.
99
- 2. For each such row: revert the status back to
100
- `📋 Todo`, commit `chore: revert orphan 🔨 US-XXX to 📋`, and append
101
- a line to `~/.shared/roll/loop/ALERT-<slug>.md` recording the orphan
102
- id and time so the next brief surfaces it.
103
- 3. After orphan sweep, proceed to Step 1.5 (Pre-run CI health check) before scanning.
104
-
105
- ### Step 1.5 — Pre-run CI Health Check
106
-
107
- Call `roll loop precheck-ci` before scanning BACKLOG. This is a **defensive gate**
108
- against building on a broken base. Check the **exit code** and route accordingly:
109
-
110
- | Exit code | Meaning | Action |
111
- |-----------|---------|--------|
112
- | `0` | CI green / pending / unknown | Proceed to Step 1.6 (PR Inbox) and Step 2 (BACKLOG scan) |
113
- | `1` | CI red AND heal exhausted or `ROLL_LOOP_NO_HEAL=1` | ALERT already written; exit cleanly this cycle |
114
- | `2` | CI red AND heal attempt allowed (US-LOOP-046) | **Hot-fix path** — skip BACKLOG, fix CI instead (see below) |
115
-
116
- `gh` missing or repo unparseable → `precheck-ci` returns `0`; graceful skip.
117
-
118
- **Hot-fix path (exit code 2) — US-LOOP-046:**
119
-
120
- Do NOT pick any BACKLOG stories this cycle. Instead:
121
-
122
- 1. Capture context: `roll loop hotfix-head-context` → prints path to context log
123
- 2. Invoke `Skill("roll-fix")` with brief:
124
- `"CI red on HEAD. Failing run logs at <context-log-path>. Diagnose root cause, fix via TCR, commit, push. Do NOT change BACKLOG status."`
125
- 3. After `roll-fix` completes, re-run `roll ci --wait` to verify the fix
126
- 4. If CI is still red: run `roll loop precheck-ci` again; if it returns `1` (heal exhausted),
127
- exit cleanly — ALERT was already written by the precheck
128
-
129
- ### Step 1.6 — PR Inbox (US-AUTO-034)
130
-
131
- Before scanning BACKLOG, process open PRs first. PRs are also units of work:
132
- external contributors and human teammates expect their PRs to be reviewed and
133
- moved forward, not starved while loop opens new fronts.
134
-
135
- Call `roll loop pr-inbox` after the pre-run CI check passes. It walks
136
- `gh pr list --state open` and routes each PR by classification:
137
-
138
- | Classification | Action |
139
- |---|---|
140
- | `loop_self` (head ref starts with `loop/` **or** `claude/`, CI not red) | squash-merge directly when CI green + clean; if the PR is BEHIND/CONFLICTING with main, rebase it first (circuit-gated) so it merges on a later tick. Never AI-review your own commit. (Agent-authored `claude/*` PRs are loop-owned the same way; a CI-red `claude/*` PR is **not** auto-healed — it falls through for a human to decide.) |
141
- | `loop_self_ci_red` (loop/* PR whose CI went red) | **US-LOOP-062a**: background-heal via `roll loop pr-heal-run` (per-PR lock + heal budget `ROLL_LOOP_HEAL_MAX`, default 2, via the configured agent); on `ROLL_LOOP_NO_HEAL=1` / budget exhausted → deduped `[TYPE:loop-pr-ci-red]` ALERT (never silently dropped) |
142
- | `blocked_human_request_changes` | Skip — last human review requested changes; wait for the author to push fixes |
143
- | `blocked_human_approved` | **US-LOOP-062b**: merge directly (`gh pr merge --squash`) when CI green + mergeable, instead of relying on repo auto-merge (which may be off); merge failure is non-fatal (retried next tick) |
144
- | `stale` (CI failed or branch behind/conflicting) | Rebase onto `origin/main` after the circuit breaker allows it |
145
- | `eligible` (clean external PR, no blocking review) | Review via `roll review-pr` (or the equivalent project agent skill) — the actual decision is provided by US-AUTO-035's GitHub Action |
146
-
147
- **Rebase circuit breaker** — the runner records each rebase attempt under
148
- `pr_state.<PR>.attempts_at` in the per-slug state file
149
- (`~/.shared/roll/loop/state-<slug>.yaml`, FIX-052), pruning entries older
150
- than 24 h. Once ≥3 attempts land within 24 h, further rebases are blocked and an
151
- ALERT is written (typical cause: a broken workflow file makes CI never run,
152
- which would otherwise drive infinite rebase loops).
153
-
154
- **Lenient on infrastructure** — `gh` missing, repo unparseable, or any
155
- `gh` API failure → `roll loop pr-inbox` returns 0 and the loop falls through to
156
- Step 2 (BACKLOG scan). Same posture as the pre-run CI check.
157
-
158
- ### Step 2 — Scan BACKLOG
159
-
160
- Read `.roll/backlog.md`. Collect all rows where Status = `📋 Todo`, in order:
161
-
162
- Priority: FIX-XXX first (bugs block progress), then US-XXX, then REFACTOR-XXX.
163
-
164
- **Skip rows with Status = `🔨 In Progress`**. These are currently being executed by:
165
- - Another concurrent executor (human via `$roll-build`, peer agent)
166
- - An earlier loop iteration that hasn't finished yet (rare; should be guarded by LOCK)
167
- - A previous interrupted run (the resume logic in Step 1 will pick these up)
168
-
169
- **In-flight PR gate** (FIX-048). Before picking, also exclude stories already
170
- claimed by an **open `loop/*` PR**. Each cycle's worktree is branched from
171
- `origin/main`, so a story another cycle has marked 🔨 In Progress is invisible
172
- locally until that cycle's PR merges. Without this gate, two cycles started
173
- back-to-back will both pick the same Todo row and produce duplicate PRs.
174
-
175
- Use `roll loop pr-inbox` to discover story IDs already claimed by open
176
- `loop/*` PRs on the remote. Skip any candidate whose ID appears. The runner is
177
- lenient: `gh` missing / API error → empty claim list.
178
-
179
- **Dependency gate** (FIX-032). For each `📋 Todo` candidate, before picking,
180
- check that every `depends-on:<ID>` referenced in the story's backlog row is
181
- already ✅ Done. The v3 runner evaluates dependencies natively; do not call the
182
- retired bash helper `_loop_check_depends_on`. If a dependency is unsatisfied,
183
- skip the story and log to `runs.jsonl` `skipped` with reason
184
- `"depends-on: <unsatisfied>"`.
185
-
186
- Move to the next candidate when skipping. The gate is a pure function
187
- over `.roll/backlog.md` text — no side effects, no LOCK interaction.
188
-
189
- Cap at `max_items_per_run` to limit blast radius per cycle.
190
-
191
- ### Concurrency Safety
192
-
193
- Loop has two layers of concurrency protection:
194
-
195
- 1. **Per-project LOCK** (enforced by the v3 runner, see `packages/cli/src/runner/run-cycle.ts`):
196
- - LOCK file path: `~/.shared/roll/loop/.LOCK-<project-slug>`
197
- - On launch: if LOCK exists and the PID inside is alive → exit 0 (previous loop still running)
198
- - On launch: if LOCK exists but PID is dead → clean up stale LOCK and continue
199
- - On exit (normal or via trap): LOCK is removed
200
- - One LOCK per project — different projects' loops run independently
201
-
202
- 2. **🔨 In Progress story status** (enforced here):
203
- - Before picking a story, check its status is `📋 Todo`
204
- - Skip any `🔨 In Progress` row (someone else is on it)
205
- - Mark each story `🔨 In Progress` BEFORE invoking the executor skill (see Step 3)
206
- - On completion: update to `✅ Done`; on TCR failure: revert to `📋 Todo`
207
-
208
- 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).
209
-
210
- ### Step 3 — Route and Execute
211
-
212
- > **US-AGENT-006 — Per-story agent routing (pre-cycle)**
213
- >
214
- > Before this skill even starts, the v3 runner has already:
215
- > 1. Picked the next eligible Todo (priority FIX > US > REFACTOR, depends-on gate respected).
216
- > 2. Read its Agent profile (`est_min` / `risk_zone`) and routed an agent (hard rules from `.roll/agent-routes.yaml` + soft preference from `runs.jsonl`).
217
- > 3. Exported `ROLL_LOOP_ROUTED_STORY` / `ROLL_LOOP_ROUTED_AGENT` / `ROLL_LOOP_ROUTED_RULE` and printed `[loop] story <id> routed to <agent> via <rule_kind>` to cron.log.
218
- >
219
- > When `ROLL_LOOP_ROUTED_STORY` is set, prefer it as `US_ID` for this cycle. The story has already been chosen by hard+soft routing rules — and, per FIX-146, the runner re-validates it against the authoritative backlog right before handing it to you (re-picking the next eligible Todo if it went ✅ Done / In Progress / ineligible between pick and handoff, emitting a `story_stale` event). So treat `ROLL_LOOP_ROUTED_STORY` as already-eligible and just work it. Only if you still find at cycle start that it is no longer 📋 Todo in BACKLOG (a residual concurrent flip), signal `story_stale` and let the runner pick the next eligible Todo rather than idling the whole cycle.
220
- >
221
- > Old single-agent fallback (`primary_agent` from `~/.roll/config.yaml`) still applies when:
222
- > - no story is pickable (empty Todo / all blocked by depends-on)
223
- > - the matching agent-routes.yaml has no agent that fits the story profile (then `cold_start_default` is used)
224
-
225
- For each item, **before invoking the executor skill**, mark the story 🔨 In Progress in the **main repo's** `.roll/backlog.md` so brief and peer agents can see it being worked on. The cycle worktree is gitignored at `.roll/`, so editing the worktree's own copy + committing carries no change back to main — write directly via the backlog store instead. The v3 runner updates `${ROLL_MAIN_PROJECT}/.roll/backlog.md` in place; do not call the retired bash helpers `_loop_mark_in_progress` / `_loop_mark_todo`.
226
-
227
- If the executor fails (TCR aborts, CI red, etc.), revert the marker so the next cycle can re-pick the story.
228
-
229
- Status flips happen in main directly — no per-cycle commit needed. `roll-brief` reads main's backlog, so the 🔨 marker is visible the moment the update returns.
230
-
231
- 选定故事后,发出 `pick_todo` 事件,让 dashboard / monitor / attach 都能把"这个 cycle 选了哪个 story"正确归类。 The v3 runner emits events natively; do not call the retired bash helper `_loop_event`.
232
-
233
- - Emit immediately after picking and before invoking the executor skill.
234
- - `label` must be the `cycle_id` (from the `LOOP_CYCLE_ID` environment variable),
235
- not `US_ID` — the dashboard groups by label, and using `US_ID` as the label
236
- would put events in the wrong bucket, making the cycle appear to have tokens
237
- but no ID.
238
-
239
- Then invoke the executor:
240
-
241
- ```
242
- Item type → Skill invoked
243
- ─────────────────────────────────
244
- US-XXX → Skill("roll-build", "US-XXX")
245
- FIX-XXX → Skill("roll-fix", "FIX-XXX")
246
- REFACTOR-XXX → Skill("roll-build", "REFACTOR-XXX")
247
- ```
248
-
249
- The executor will update the row to `✅ Done` on success (it transitions from `🔨 In Progress` → `✅ Done`, same Edit logic as from `📋 Todo`).
250
-
251
- Before invoking, also write current item to the per-slug state file
252
- (`~/.shared/roll/loop/state-<slug>.yaml`, FIX-052):
253
-
254
- ```yaml
255
- status: running
256
- current_item: US-AUTO-004
257
- started_at: "2026-05-10T02:00:00+08:00"
258
- agent: claude
259
- run_id: loop-20260510-0200
260
- ```
261
-
262
- ### Step 4 — Post-Item Cleanup
263
-
264
- After each item completes:
265
-
266
- 1. **TCR 硬校验** — call `roll loop enforce-tcr <story_id> <started_at>`:
267
- - Count `tcr:` prefix commits since `started_at` via `git log --oneline --since=<started_at>`
268
- - 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`)
269
- - Count > 0 → continue normally
270
- 2. **CI Gate** — **MUST** invoke `roll ci --wait`. **Do NOT call `gh` directly**
271
- (no `gh run list`, no `gh run watch`,
272
- no ad-hoc shell checks): `roll ci --wait` is the only sanctioned entry —
273
- it derives `owner/repo` from the git remote and uses `gh -R <slug>`, which
274
- is required to work through `~/.ssh/config` host rewrites that break gh's
275
- auto-detection.
276
- - CI passes → clear any `heal_count:` entry in `~/.shared/roll/loop/state-<slug>.yaml` (idempotent — drop the line if present, no-op otherwise) and continue normally
277
- - CI fails / times out / `gh` call fails → enter **CI self-heal** (US-AUTO-041)
278
- - `gh` binary not installed (`command -v gh` fails) → skip gracefully
279
- (return 0). Any other `gh` error is **not** "gh unavailable" — it is a
280
- hard failure and must block the gate.
281
-
282
- **CI self-heal (US-AUTO-041)** — bounded auto-fix before ALERT.
283
-
284
- 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.
285
-
286
- **Path A — attempt allowed (counter incremented in `state-<slug>.yaml`):**
287
-
288
- 1. Capture failure summary:
289
- ```
290
- gh run view --log-failed --repo <slug> \
291
- $(gh run list --commit HEAD --json databaseId,conclusion -L 5 \
292
- | jq -r '.[] | select(.conclusion=="failure") | .databaseId' | head -1) \
293
- 2>/dev/null | head -200 > /tmp/roll-heal-<story_id>.log
294
- ```
295
- 2. Invoke `Skill("roll-fix")` with brief:
296
- `"CI red after <story_id>. Failing run logs at /tmp/roll-heal-<story_id>.log.
297
- Diagnose root cause, fix via TCR, commit, push. Do NOT change <story_id>'s
298
- BACKLOG status — it stays ✅ Done. The fix is a follow-up."`
299
- 3. After `roll-fix` completes, return to step 2 (CI Gate) — re-run `roll ci --wait`.
300
- The counter in `state-<slug>.yaml` prevents infinite loops.
301
-
302
- **Path B — heal exhausted (≥`ROLL_LOOP_HEAL_MAX`, default 2) or disabled (`ROLL_LOOP_NO_HEAL=1`) (exit 1):**
303
-
304
- 1. Do NOT force ✅ Done here. CI red means the PR will not merge. Under
305
- **US-AUTO-044** the main loop no longer waits for merge — it publishes the
306
- PR and exits; the dedicated PR Loop (`com.roll.pr.<slug>`, every 5 min)
307
- merges / rebases / closes it asynchronously. There is no false-Done risk:
308
- with worktree isolation the ✅ Done lives only in the unmerged PR, never on
309
- the loop's main checkout, and the story is not re-picked meanwhile via the
310
- open-PR eligibility gate (FIX-146). The story's
311
- ✅ Done lands on main only when the PR Loop actually merges the PR.
312
- 2. Write ALERT to `~/.shared/roll/loop/ALERT-<slug>.md` with:
313
- - story ID, time, commit SHA
314
- - heal attempts made (read `heal_count:` from `state-<slug>.yaml`)
315
- - last failure summary (head of `/tmp/roll-heal-<story_id>.log`)
316
- - suggested actions: `$roll-fix` manually / inspect CI / `roll loop reset`
317
- 3. Skip to next story.
318
-
319
- **Bypass for debugging / cost control:** set `ROLL_LOOP_NO_HEAL=1` to restore
320
- pre-US-AUTO-041 fail-fast behaviour.
321
- 3. Update state file: `status: idle`
322
- 4. Check if a Feature is now fully complete (all its Stories ✅)
323
- 5. If yes and `brief_on_feature_complete: true` → invoke `Skill("roll-brief")`
324
- 6. **EXIT the cycle.** 不要回 Step 2 找下一个故事,不要再 emit `pick_todo`。
325
- 一个 cycle 只交付一个故事;剩下的 Todo 等下一个 launchd tick 起新 cycle 处理。
326
-
327
- ### Step 5 — Write Run Summary
328
-
329
- > **FIX-044**: The v3 runner (`packages/cli/src/runner/run-cycle.ts`) appends
330
- > this record deterministically at cycle end. The agent should still emit a run
331
- > summary in the cycle's final report for `cron.log` visibility.
332
-
333
- After all items in this cycle:
334
-
335
- ```yaml
336
- # ~/.shared/roll/loop/state-<slug>.yaml (FIX-052)
337
- status: idle
338
- last_run: "2026-05-10T02:15:00+08:00"
339
- last_run_items: [US-AUTH-003, FIX-007]
340
- last_run_outcome: success
341
- ```
342
-
343
- Then append a JSONL record to `~/.shared/roll/loop/runs.jsonl` for per-iteration
344
- visibility (one line per cycle, append-only — never delete or rewrite earlier lines).
345
-
346
- **⚠️ Strict schema contract — do NOT deviate.** Every field has exactly one
347
- canonical form. Synonyms like `"success"`, `"noop"`, `"completed"` are forbidden
348
- for `status`. Numbers and arrays cannot be interchanged. UTC `Z` suffix only,
349
- no timezone offsets. **No extra fields** — emit only the keys listed below (plus
350
- optional `reason` when `status="failed"`); do not add `note`, `comment`,
351
- `details`, `info`, etc. If you feel the urge to annotate, put it in the cycle's
352
- final report in `cron.log` instead.
353
-
354
- **Canonical record (copy this exact shape, fill in real values):**
355
-
356
- ```json
357
- {"ts":"2026-05-11T11:46:43Z","project":"roll-d9dfa0","run_id":"loop-20260511-1911","status":"built","built":["US-AUTO-024","US-AUTO-025"],"skipped":[],"alerts":[],"tcr_count":5,"duration_sec":2080}
358
- ```
359
-
360
- **Field contract — types are enforced**:
361
-
362
- | Field | Type | Format / Enum |
363
- |---|---|---|
364
- | `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. |
365
- | `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}"` |
366
- | `run_id` | string | Matches `state-<slug>.yaml` `run_id` exactly. Format: `loop-YYYYMMDD-HHMM`. |
367
- | `status` | enum | Exactly one of: `built` (≥1 story shipped), `idle` (no Todo items found), `failed` (paused/error). **No synonyms.** |
368
- | `built` | array&lt;string&gt; | Story ids completed this cycle. `[]` when none. **Always array, never null/number.** |
369
- | `skipped` | array&lt;string&gt; | Story ids skipped because they were `🔨 In Progress`. `[]` when none. **Always array.** |
370
- | `alerts` | array&lt;string&gt; | Newly raised ALERT identifiers/tags this cycle. `[]` when none. **Always array, never number.** |
371
- | `tcr_count` | integer | Total `tcr:` prefix commits made this cycle. `0` when none. |
372
- | `duration_sec` | integer | Seconds from cycle start to completion. Integer only, no decimals. |
373
-
374
- Optional field, only when `status == "failed"`:
375
- - `reason` (string): short human-readable explanation.
376
-
377
- **Write recipe:**
378
-
379
- ```bash
380
- ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
381
- # Compute project slug — inlined equivalent of bin/roll's _project_slug
382
- # (Claude sessions can't call roll's internal functions, so we inline).
383
- # Must produce identical output to _project_slug to match `roll loop runs` filter.
384
- _p=$(pwd -P)
385
- _base=$(basename "$_p" | tr -cs '[:alnum:]' '-' | sed 's/-*$//')
386
- _hash=$(printf '%s' "$_p" | md5 | cut -c1-6 2>/dev/null || printf '%s' "$_p" | md5sum | cut -c1-6)
387
- project="${_base}-${_hash}" # e.g. roll-d9dfa0 — must match roll loop runs filter
388
- # duration_sec = cycle_end_epoch - cycle_start_epoch (track at Step 1)
389
- # tcr_count = git log --oneline --since="<cycle_start>" | grep -c '^[a-f0-9]* tcr:'
390
-
391
- jq -nc \
392
- --arg ts "$ts" \
393
- --arg project "$project" \
394
- --arg run_id "$run_id" \
395
- --arg status "built" \
396
- --argjson built '["US-AUTO-024"]' \
397
- --argjson skipped '[]' \
398
- --argjson alerts '[]' \
399
- --argjson tcr_count 14 \
400
- --argjson duration_sec 1680 \
401
- '{ts:$ts, project:$project, run_id:$run_id, status:$status,
402
- built:$built, skipped:$skipped, alerts:$alerts,
403
- tcr_count:$tcr_count, duration_sec:$duration_sec}' \
404
- >> ~/.shared/roll/loop/runs.jsonl
405
- ```
406
-
407
- The companion read-side is `roll loop runs [N] [--all]` — shows the most recent
408
- N records (default 10) for the current project, or across all projects with `--all`.
409
-
410
- ## Failure Handling
411
-
412
- ### Network Error (transient)
413
-
414
- ```
415
- Attempt 1 fails
416
- → wait 2s → Attempt 2
417
- → wait 4s → Attempt 3
418
- → wait 8s → Attempt 4
419
- → wait 16s → Attempt 5
420
- → still failing → escalate to token/agent failure path
421
- ```
422
-
423
- ### Token Exhausted / Agent Unavailable
424
-
425
- ```
426
- Primary agent fails (non-network error)
427
- → 3 attempts at the agent_invoke phase (with 30s back-off between)
428
- → still failing → PAUSE
429
- ```
430
-
431
- ### Pause + Alert
432
-
433
- When the primary agent exhausts its retry budget:
434
-
435
- 1. Write state:
436
- ```yaml
437
- status: paused
438
- paused_at: "2026-05-10T02:07:00+08:00"
439
- paused_on: US-AUTH-003
440
- reason: "primary agent (claude) unavailable after 3 attempts"
441
- ```
442
-
443
- 2. Write alert:
444
- ```markdown
445
- # ALERT — roll-loop paused
446
-
447
- **Time**: 2026-05-10 02:07
448
- **Paused on**: US-AUTH-003
449
- **Reason**: claude exited non-zero on 3 consecutive attempts
450
-
451
- **Action required** (choose one):
452
- - Top up credits and run: `roll loop resume`
453
- - Switch agent: edit `~/.roll/config.yaml` → `primary_agent`
454
- - Take over manually: `$roll-build US-AUTH-003`
455
- ```
456
-
457
- 3. Write alert file to `~/.shared/roll/loop/ALERT-<slug>.md`
458
-
459
- ## Resuming After Pause
460
-
461
- ```bash
462
- roll loop resume # picks up from state-<slug>.yaml current_item
463
- roll loop status # show current state without running
464
- roll loop reset # clear state and start fresh next scheduled run
465
- ```
466
-
467
- ## Scheduler Configuration
468
-
469
- roll-loop runs **locally** — it needs access to the local codebase, local
470
- test runner, and local agent CLI. GitHub Actions runs on remote servers and
471
- cannot fulfill these requirements.
472
-
473
- ### Local cron (default)
474
-
475
- Install once with `roll loop on` — agent selection happens per cycle from the
476
- `.roll/agents.yaml` complexity slots (easy/default/hard/fallback), so the cron
477
- entry is agent-agnostic. No agent-specific command needed.
478
-
479
- ```bash
480
- roll loop on # install cron for loop + dream + brief
481
- roll loop off # remove cron entries
482
- roll loop status # show current state
483
- ```
484
-
485
- ### Manual run (for testing)
486
-
487
- ```bash
488
- roll loop now # execute one cycle immediately
489
- ```
490
-
491
- ### Live attach (transparency)
492
-
493
- Each loop iteration runs inside a detached tmux session named
494
- `roll-loop-<slug>` (tmux is a required dependency — `roll setup` auto-installs
495
- it via Homebrew on macOS, or prints the install command elsewhere).
496
-
497
- **Default — auto-attach popup**: when the loop fires, a background Terminal
498
- window pops up running `tmux attach -t roll-loop-<slug>`. You can watch the
499
- agent work in real time without typing anything. The popup is best-effort
500
- focus-retaining (it captures the previously-active app and restores focus
501
- after the window appears) and the tmux session keeps running even if you
502
- close the window.
503
-
504
- **Manual attach** (any time):
505
-
506
- ```bash
507
- roll loop attach # exec tmux attach -t roll-loop-<slug>
508
- ```
509
-
510
- Press `Ctrl-B D` to detach — the loop continues running uninterrupted.
511
-
512
- **Mute / unmute the popup**:
513
-
514
- ```bash
515
- roll loop mute # 🔇 — suppress auto-attach popup (loop still runs in tmux)
516
- roll loop unmute # 🔔 — re-enable the popup
517
- ```
518
-
519
- Mute state is a single marker file at `~/.shared/roll/mute` and is shared
520
- across all projects on this machine. Check the current state with
521
- `roll loop status` — it shows an `Auto-attach: live | muted` line.
522
-
523
- ## Integration Map
524
-
525
- ```
526
- roll-loop
527
- ├── reads .roll/backlog.md
528
- ├── invokes $roll-build (US-XXX, REFACTOR-XXX)
529
- ├── invokes $roll-fix (FIX-XXX)
530
- ├── invokes $roll-brief (on Feature completion)
531
- ├── reads ~/.roll/config.yaml (agent routing)
532
- ├── writes ~/.shared/roll/loop/state-<slug>.yaml
533
- └── writes ~/.shared/roll/loop/ALERT-<slug>.md (on failure)
534
- ```