@seanyao/roll 2026.519.2 → 2026.519.3
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 +20 -0
- package/README.md +31 -12
- package/bin/roll +95 -19
- package/lib/__pycache__/roll-loop-status.cpython-314.pyc +0 -0
- package/lib/roll-backlog.py +1 -1
- package/lib/roll-help.py +1 -1
- package/package.json +1 -1
- package/skills/roll-build/SKILL.md +2 -2
- package/skills/roll-loop/SKILL.md +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2026.519.3
|
|
4
|
+
|
|
5
|
+
### Major
|
|
6
|
+
|
|
7
|
+
- **`.roll/` 拆为独立私有仓库** — 过程文件(backlog / brief / features / dream / domain / briefs)整体迁入嵌套私有 repo `roll-meta`,主仓只留产品文档。AGENTS.md 新增 §9 说明双 repo 协作约定 `[meta]`
|
|
8
|
+
|
|
9
|
+
### Improved
|
|
10
|
+
|
|
11
|
+
- **`.roll/features/` 按 Epic 分组** — features 目录由扁平改为 Epic 子目录,附带 loop 架构经验文档 `[docs]`
|
|
12
|
+
- **站点登陆页对齐 v2.0 架构** — `docs/site/` 文案与 2.0 实际产物对齐,去掉过期描述 `[docs]`
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **loop 异常退出** — dashboard 不再卡在"运行中",崩溃 / 被 kill / 超时退出都会写下结束标记 `[loop]`
|
|
17
|
+
- **`FIX-065` loop 共享状态隔离** — 测试运行的 loop 不再污染生产 backlog/brief,sandbox 化共享路径 `[loop]`
|
|
18
|
+
|
|
3
19
|
## v2026.519.2
|
|
4
20
|
|
|
5
21
|
### Improved
|
|
6
22
|
|
|
7
23
|
- **`roll init`** — 初始化流程现在显示 6 步编号进度,新建文件用绿色 `+`、合并已有用琥珀 `~`,结尾给三步上手指南 `[loop]`
|
|
8
24
|
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **`roll --help` 中 `init` 描述** — 文案从 `+ docs/` 改为 `+ .roll/features/`,对齐 2.0 实际产物和 README;v2 (`lib/roll-help.py`) 与 legacy (`bin/roll`) 两份 help 同步修正 `[FIX-064]`
|
|
28
|
+
|
|
9
29
|
## v2026.519.1
|
|
10
30
|
|
|
11
31
|
### Major(大版本重构)
|
package/README.md
CHANGED
|
@@ -37,9 +37,6 @@ Roll 2.0 introduces a **process/product split** to keep open-source projects cle
|
|
|
37
37
|
- **`$roll-onboard`** — interactive skill for adopting Roll on legacy codebases without rewrites. AI reads your code, asks 9 questions in ≤ 3 minutes, produces a plan; `roll init --apply` executes it.
|
|
38
38
|
- **Three adoption patterns** — `seed` (new project), `graft` (legacy, non-invasive), `replant` (legacy, clean rebuild). See [guide/en/patterns/](guide/en/patterns/).
|
|
39
39
|
|
|
40
|
-
📖 Upgrading from 1.x? Read [guide/en/migration-2.0.md](guide/en/migration-2.0.md).
|
|
41
|
-
📖 New legacy project? Read [guide/en/legacy-onboarding.md](guide/en/legacy-onboarding.md).
|
|
42
|
-
|
|
43
40
|
## Evolution
|
|
44
41
|
|
|
45
42
|
Roll didn't start as a framework. It started as a question: *what if the AI didn't just write code, but actually shipped it?*
|
|
@@ -66,6 +63,20 @@ roll loop on # optional: let the agent work unattended
|
|
|
66
63
|
|
|
67
64
|
---
|
|
68
65
|
|
|
66
|
+
## Adoption Paths
|
|
67
|
+
|
|
68
|
+
Three ways to bring Roll into a project. Not sure which fits? Just run `roll init` — it detects legacy code and routes you to `$roll-onboard` automatically.
|
|
69
|
+
|
|
70
|
+
| Path | When to use | How to start |
|
|
71
|
+
|------|-------------|--------------|
|
|
72
|
+
| **Seed** | Brand-new project, starting from zero | `roll init` (the Quick Start path above) |
|
|
73
|
+
| **Graft** | Existing codebase, keep current workflow intact | `roll init` → AI prompts `$roll-onboard` → 9 questions in ≤ 3 min → `roll init --apply` |
|
|
74
|
+
| **Replant** | Existing codebase, ready to realign on Roll conventions | Same as Graft, choose "clean rebuild" during onboarding |
|
|
75
|
+
|
|
76
|
+
Details: [seed](guide/en/patterns/seed-pattern.md) · [graft](guide/en/patterns/graft-pattern.md) · [replant](guide/en/patterns/replant-pattern.md)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
69
80
|
## Documentation Index
|
|
70
81
|
|
|
71
82
|
| Topic | English | 中文 |
|
|
@@ -78,7 +89,7 @@ roll loop on # optional: let the agent work unattended
|
|
|
78
89
|
| Configuration (env vars) | [guide/en/configuration.md](guide/en/configuration.md) | [guide/zh/configuration.md](guide/zh/configuration.md) |
|
|
79
90
|
| Skill selection guide | [guide/en/skills.md](guide/en/skills.md) | [guide/zh/skills.md](guide/zh/skills.md) |
|
|
80
91
|
| FAQ (troubleshooting) | [guide/en/faq.md](guide/en/faq.md) | [guide/zh/faq.md](guide/zh/faq.md) |
|
|
81
|
-
|
|
|
92
|
+
| Adoption patterns | [guide/en/patterns/](guide/en/patterns/) | [guide/zh/patterns/](guide/zh/patterns/) |
|
|
82
93
|
| Engineering common sense | [practices/engineering-common-sense.md](guide/en/practices/engineering-common-sense.md) | — |
|
|
83
94
|
|
|
84
95
|
---
|
|
@@ -87,15 +98,23 @@ roll loop on # optional: let the agent work unattended
|
|
|
87
98
|
|
|
88
99
|
| Command | Description |
|
|
89
100
|
|---------|-------------|
|
|
101
|
+
| **Autonomy · daily use** | |
|
|
102
|
+
| `roll loop <on\|off\|now\|status\|monitor>` | 🤖 Manage the autonomous BACKLOG executor |
|
|
103
|
+
| `roll brief` | 🤖 Show latest owner brief |
|
|
104
|
+
| `roll backlog [block\|defer\|…]` | View and manage pending tasks |
|
|
105
|
+
| `roll peer` | 🤖 Cross-agent negotiation & review |
|
|
106
|
+
| `roll alert` | View / clear loop alerts |
|
|
107
|
+
| **Project · per repo** | |
|
|
108
|
+
| `roll init` | Create AGENTS.md + .roll/backlog.md + .roll/features/ |
|
|
109
|
+
| `roll status` | Show current state and drift |
|
|
110
|
+
| `roll agent [use <name>]` | Per-project agent selection (Claude / Cursor / Codex / Kimi / …) |
|
|
111
|
+
| `roll ci [--wait]` | Show or wait for current commit's CI status |
|
|
112
|
+
| `roll release` | 🤖 Run the release script (human-only) |
|
|
113
|
+
| `roll review-pr <number>` | 🤖 AI-powered code review for a PR |
|
|
114
|
+
| **Machine · global** | |
|
|
90
115
|
| `roll setup [-f]` | First-time install or re-sync conventions to all AI clients |
|
|
91
|
-
| `roll update` | Upgrade to latest
|
|
92
|
-
| `roll
|
|
93
|
-
| `roll status` | Show sync state, skill links, detected AI tools |
|
|
94
|
-
| `roll backlog` | Show pending tasks from .roll/backlog.md |
|
|
95
|
-
| `roll loop <on\|off\|now\|status\|monitor>` | 🤖 Manage autonomous executor |
|
|
96
|
-
| `roll brief` | 🤖 Show latest owner digest |
|
|
97
|
-
| `roll peer` | 🤖 Cross-agent code review |
|
|
98
|
-
| `roll release` | 🤖 Version + tag + npm publish + GitHub Release |
|
|
116
|
+
| `roll update` | Upgrade to latest + re-sync |
|
|
117
|
+
| `roll version` | Print installed roll version |
|
|
99
118
|
|
|
100
119
|
---
|
|
101
120
|
|
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.519.
|
|
7
|
+
VERSION="2026.519.3"
|
|
8
8
|
ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
|
|
9
9
|
ROLL_CONFIG="${ROLL_HOME}/config.yaml"
|
|
10
10
|
ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
|
|
@@ -2343,7 +2343,13 @@ cmd_review_pr() {
|
|
|
2343
2343
|
local output
|
|
2344
2344
|
info "Reviewing PR #${pr_number} with ${agent}..."
|
|
2345
2345
|
_agent_argv "$agent" text "$prompt" || { err "Unknown agent '${agent}'"; return 1; }
|
|
2346
|
-
|
|
2346
|
+
local _stderr_log; _stderr_log=$(mktemp)
|
|
2347
|
+
output=$("${_AGENT_ARGV[@]}" 2>"$_stderr_log")
|
|
2348
|
+
if [[ -z "$output" && -s "$_stderr_log" ]]; then
|
|
2349
|
+
err "agent ${agent} produced no output. stderr (first 5 lines):"
|
|
2350
|
+
head -5 "$_stderr_log" | sed 's/^/ /' >&2
|
|
2351
|
+
fi
|
|
2352
|
+
rm -f "$_stderr_log"
|
|
2347
2353
|
|
|
2348
2354
|
echo "$output"
|
|
2349
2355
|
|
|
@@ -2545,6 +2551,35 @@ PYEOF
|
|
|
2545
2551
|
}
|
|
2546
2552
|
|
|
2547
2553
|
_LOOP_TAG="# roll-loop"
|
|
2554
|
+
# FIX-065: when sourced in a test context with no explicit override, route
|
|
2555
|
+
# shared state into a per-process /tmp path instead of falling back to
|
|
2556
|
+
# production ~/.shared/roll/. Without this safety net, tests that source
|
|
2557
|
+
# bin/roll (directly or via a generated inner runner under /var/folders/)
|
|
2558
|
+
# would write ALERT / state / events / runs.jsonl into the live loop
|
|
2559
|
+
# daemon's monitored directory and trigger false aborts.
|
|
2560
|
+
#
|
|
2561
|
+
# Test context is detected via three signals (any one is enough):
|
|
2562
|
+
# 1. BATS_TEST_FILENAME is set (works for direct test invocations)
|
|
2563
|
+
# 2. The caller's file path lives under /tmp or /var/folders (catches the
|
|
2564
|
+
# generated runner-inner.sh path that bats subprocesses spawn —
|
|
2565
|
+
# BATS_* env can be lost across `bash -l` + nested forks)
|
|
2566
|
+
# 3. PWD is under /tmp or /var/folders (catches helpers that cd'd in)
|
|
2567
|
+
if [ -z "${_SHARED_ROOT:-}" ]; then
|
|
2568
|
+
_roll_in_test_ctx=0
|
|
2569
|
+
if [ -n "${BATS_TEST_FILENAME:-}" ]; then
|
|
2570
|
+
_roll_in_test_ctx=1
|
|
2571
|
+
else
|
|
2572
|
+
_roll_caller="${BASH_SOURCE[1]:-}"
|
|
2573
|
+
case "$_roll_caller" in /tmp/*|/private/tmp/*|/var/folders/*) _roll_in_test_ctx=1 ;; esac
|
|
2574
|
+
case "$PWD" in /tmp/*|/private/tmp/*|/var/folders/*) _roll_in_test_ctx=1 ;; esac
|
|
2575
|
+
fi
|
|
2576
|
+
if [ "$_roll_in_test_ctx" = 1 ]; then
|
|
2577
|
+
_SHARED_ROOT="${TMPDIR:-/tmp}/roll-test-shared.$$"
|
|
2578
|
+
mkdir -p "${_SHARED_ROOT}/loop"
|
|
2579
|
+
export _SHARED_ROOT
|
|
2580
|
+
fi
|
|
2581
|
+
unset _roll_in_test_ctx _roll_caller
|
|
2582
|
+
fi
|
|
2548
2583
|
: "${_SHARED_ROOT:=${HOME}/.shared/roll}"
|
|
2549
2584
|
# FIX-052: per-project loop state — ALERT/state/mute were globally shared,
|
|
2550
2585
|
# causing one project's alerts to surface in another project's session and
|
|
@@ -2553,7 +2588,9 @@ _LOOP_TAG="# roll-loop"
|
|
|
2553
2588
|
: "${_LOOP_PROJ_SLUG:=$(_project_slug 2>/dev/null || echo default)}"
|
|
2554
2589
|
: "${_LOOP_STATE:=${_SHARED_ROOT}/loop/state-${_LOOP_PROJ_SLUG}.yaml}"
|
|
2555
2590
|
: "${_LOOP_ALERT:=${_SHARED_ROOT}/loop/ALERT-${_LOOP_PROJ_SLUG}.md}"
|
|
2556
|
-
|
|
2591
|
+
# FIX-065: was hardcoded to ${HOME}/.shared/roll/loop/runs.jsonl which ignored
|
|
2592
|
+
# _SHARED_ROOT overrides and silently leaked test runs.jsonl writes into prod.
|
|
2593
|
+
_LOOP_RUNS="${_SHARED_ROOT}/loop/runs.jsonl"
|
|
2557
2594
|
: "${_LOOP_MUTE_FILE:=${_SHARED_ROOT}/loop/mute-${_LOOP_PROJ_SLUG}}"
|
|
2558
2595
|
_LAUNCHD_DIR="${HOME}/Library/LaunchAgents"
|
|
2559
2596
|
|
|
@@ -2564,6 +2601,13 @@ _config_read_int() {
|
|
|
2564
2601
|
if [[ "$val" =~ ^[0-9]+$ ]]; then echo "$val"; else echo "$default"; fi
|
|
2565
2602
|
}
|
|
2566
2603
|
|
|
2604
|
+
# REFACTOR-031: cross-platform file mtime in epoch seconds.
|
|
2605
|
+
# Replaces the `stat -c %Y ... || stat -f %m ... || echo 0` pattern that was
|
|
2606
|
+
# copy-pasted in four places (dashboard age widgets, briefs, dream, peer).
|
|
2607
|
+
_file_mtime() {
|
|
2608
|
+
stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null || echo 0
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2567
2611
|
# Derive a minute in [1,55] from project path hash + offset so different projects
|
|
2568
2612
|
# and different services within a project don't fire at the same time.
|
|
2569
2613
|
# Offsets used: loop=0, dream=2, brief=4 → always three distinct values (2<55).
|
|
@@ -2589,6 +2633,24 @@ _loop_event() {
|
|
|
2589
2633
|
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2590
2634
|
slug=$(_project_slug 2>/dev/null || basename "$PWD")
|
|
2591
2635
|
evfile="${_SHARED_ROOT:-$HOME/.shared/roll}/loop/events-${slug}.ndjson"
|
|
2636
|
+
# FIX-065 tripwire: in a test context (BATS or temp cwd), refuse to write
|
|
2637
|
+
# into production ~/.shared/roll/. Catching this in code is the last line
|
|
2638
|
+
# of defense if some unusual path bypassed the auto-sandbox at source-time.
|
|
2639
|
+
# Skipped when HOME itself has been redirected to a sandbox dir — then
|
|
2640
|
+
# $HOME/.shared/roll IS the sandbox, not prod.
|
|
2641
|
+
case "${HOME:-}" in
|
|
2642
|
+
/tmp/*|/private/tmp/*|*/var/folders/*|*/tmp.*) ;;
|
|
2643
|
+
*)
|
|
2644
|
+
if [ -n "${HOME:-}" ] && [ "${evfile#${HOME}/.shared/roll/}" != "$evfile" ]; then
|
|
2645
|
+
case "${BATS_TEST_FILENAME:-}${PWD}" in
|
|
2646
|
+
*/tmp.*|*/var/folders/*|/tmp/*|/private/tmp/*|*.bats)
|
|
2647
|
+
echo "[FIX-065] refusing prod _loop_event write: $evfile (test context)" >&2
|
|
2648
|
+
return 1
|
|
2649
|
+
;;
|
|
2650
|
+
esac
|
|
2651
|
+
fi
|
|
2652
|
+
;;
|
|
2653
|
+
esac
|
|
2592
2654
|
mkdir -p "$(dirname "$evfile")"
|
|
2593
2655
|
|
|
2594
2656
|
# stdout: tab-separated for tmux display
|
|
@@ -2779,7 +2841,7 @@ fi
|
|
|
2779
2841
|
printf '%s:%s\n' "\$\$" "\$(date -u +%s)" > "\$INNER_LOCK"
|
|
2780
2842
|
# FIX-038: background heartbeat writer — outer script uses this as primary liveness signal
|
|
2781
2843
|
# to detect stale execution without relying on PID reuse heuristics.
|
|
2782
|
-
HEARTBEAT_FILE="
|
|
2844
|
+
HEARTBEAT_FILE="\${_SHARED_ROOT:-\${HOME}/.shared/roll}/loop/.heartbeat-${slug}"
|
|
2783
2845
|
_heartbeat_writer() {
|
|
2784
2846
|
while true; do echo "\$(date -u +%s)" > "\$HEARTBEAT_FILE"; sleep 60; done
|
|
2785
2847
|
}
|
|
@@ -2791,6 +2853,11 @@ _HEARTBEAT_PID=\$!
|
|
|
2791
2853
|
# next cron tick can proceed. Overridable via env for tests.
|
|
2792
2854
|
LOOP_CYCLE_TIMEOUT_SEC="\${ROLL_LOOP_CYCLE_TIMEOUT_SEC:-2700}"
|
|
2793
2855
|
_CYCLE_TIMED_OUT=0
|
|
2856
|
+
# IDEA-028 / FIX-066: track whether cycle_end has been emitted via any of the
|
|
2857
|
+
# explicit completion paths (publish/merge_back/orphan-push/claude-failed).
|
|
2858
|
+
# When zero, the EXIT trap emits a fallback so cycle_start never orphans the
|
|
2859
|
+
# dashboard into a phantom "still running" row.
|
|
2860
|
+
_CYCLE_END_WRITTEN=0
|
|
2794
2861
|
_on_sigterm() { _CYCLE_TIMED_OUT=1; }
|
|
2795
2862
|
trap '_on_sigterm' TERM
|
|
2796
2863
|
# US-LOOP-005: idempotent runs.jsonl writer shared by normal exit, timeout
|
|
@@ -2798,7 +2865,7 @@ trap '_on_sigterm' TERM
|
|
|
2798
2865
|
# multiple callers in the same cycle are safe.
|
|
2799
2866
|
_runs_append() {
|
|
2800
2867
|
local _status="\$1"; local _tcr="\${2:-0}"; local _built="\${3:-[]}"
|
|
2801
|
-
local _runs_dst="
|
|
2868
|
+
local _runs_dst="\${_SHARED_ROOT:-\${HOME}/.shared/roll}/loop/runs.jsonl"
|
|
2802
2869
|
command -v jq >/dev/null 2>&1 || return 0
|
|
2803
2870
|
local _cid="\${CYCLE_ID:-pre-cycle-\$\$}"
|
|
2804
2871
|
local _rid="loop-\${_cid%-*}"
|
|
@@ -2834,12 +2901,21 @@ _inner_cleanup() {
|
|
|
2834
2901
|
for _pid in \$(jobs -p); do kill "\$_pid" 2>/dev/null; done
|
|
2835
2902
|
if [ "\${_CYCLE_TIMED_OUT:-0}" -eq 1 ]; then
|
|
2836
2903
|
_loop_event cycle_end "\${CYCLE_ID:-unknown}" "\${BRANCH:-}" "blocked" 2>/dev/null || true
|
|
2904
|
+
_CYCLE_END_WRITTEN=1
|
|
2837
2905
|
# US-LOOP-005 T9: timeout path must also write runs.jsonl row so dashboard
|
|
2838
2906
|
# has a terminal record (cycle_end alone is insufficient — runs.jsonl is
|
|
2839
2907
|
# the canonical history feed for 'roll loop runs').
|
|
2840
2908
|
_runs_append "failed" 0 "[]" 2>/dev/null || true
|
|
2841
2909
|
_worktree_alert "cycle \${CYCLE_ID:-unknown}: \${LOOP_CYCLE_TIMEOUT_SEC}s timeout — claude/python killed; in-progress story marked Blocked" 2>/dev/null || true
|
|
2842
2910
|
fi
|
|
2911
|
+
# IDEA-028 / FIX-066: catch every other abort (SIGKILL, set -e fire, ALERT
|
|
2912
|
+
# poisoning that bypasses the retry budget, etc.). Without this, cycle_start
|
|
2913
|
+
# is emitted but cycle_end never is, and dashboard renders the cycle as
|
|
2914
|
+
# "still running" until the next successful cycle rolls past it.
|
|
2915
|
+
if [ "\${_CYCLE_END_WRITTEN:-0}" -eq 0 ] && [ -n "\${CYCLE_ID:-}" ]; then
|
|
2916
|
+
_loop_event cycle_end "\${CYCLE_ID}" "\${BRANCH:-}" "aborted" 2>/dev/null || true
|
|
2917
|
+
_runs_append "aborted" 0 "[]" 2>/dev/null || true
|
|
2918
|
+
fi
|
|
2843
2919
|
rm -f "\$INNER_LOCK" "\$HEARTBEAT_FILE"
|
|
2844
2920
|
exit "\$_rc"
|
|
2845
2921
|
}
|
|
@@ -3023,13 +3099,13 @@ if [ "\$_USE_WORKTREE" = "1" ]; then
|
|
|
3023
3099
|
fi
|
|
3024
3100
|
fi
|
|
3025
3101
|
_worktree_cleanup "\$WT" "\$BRANCH"
|
|
3026
|
-
_loop_event cycle_end "\${CYCLE_ID}" "" "done" || true
|
|
3102
|
+
_loop_event cycle_end "\${CYCLE_ID}" "" "done" || true; _CYCLE_END_WRITTEN=1
|
|
3027
3103
|
echo "[loop] cycle \${CYCLE_ID}: published; worktree cleaned"
|
|
3028
3104
|
elif [ "\$_publish_status" -eq 2 ]; then
|
|
3029
3105
|
if ( cd "${project_path}" && _worktree_merge_back "\$BRANCH" ); then
|
|
3030
3106
|
_worktree_cleanup "\$WT" "\$BRANCH"
|
|
3031
3107
|
# US-LOOP-005 T3: gh unavailable + ff merge_back OK → cycle_end done
|
|
3032
|
-
_loop_event cycle_end "\${CYCLE_ID}" "" "done" || true
|
|
3108
|
+
_loop_event cycle_end "\${CYCLE_ID}" "" "done" || true; _CYCLE_END_WRITTEN=1
|
|
3033
3109
|
echo "[loop] cycle \${CYCLE_ID}: gh unavailable; merged via ff and cleaned up"
|
|
3034
3110
|
else
|
|
3035
3111
|
# FIX-039: gh unavailable + merge_back failed — push orphan branch+tag to origin
|
|
@@ -3040,12 +3116,12 @@ if [ "\$_USE_WORKTREE" = "1" ]; then
|
|
|
3040
3116
|
&& git push origin "\$_orphan_tag" 2>/dev/null ); then
|
|
3041
3117
|
_worktree_cleanup "\$WT" "\$BRANCH"
|
|
3042
3118
|
# US-LOOP-005 T4: gh unavailable + orphan push OK → cycle_end orphan
|
|
3043
|
-
_loop_event cycle_end "\${CYCLE_ID}" "" "orphan" || true
|
|
3119
|
+
_loop_event cycle_end "\${CYCLE_ID}" "" "orphan" || true; _CYCLE_END_WRITTEN=1
|
|
3044
3120
|
_worktree_alert "cycle \${CYCLE_ID}: gh+merge_back failed; FIX-039 pushed orphan+tag \${_orphan_tag}; worktree cleaned"
|
|
3045
3121
|
echo "[loop] cycle \${CYCLE_ID}: FIX-039: orphan branch+tag \${_orphan_tag} pushed; worktree cleaned"
|
|
3046
3122
|
else
|
|
3047
3123
|
# US-LOOP-005 T5: gh unavailable + all failed → cycle_end failed
|
|
3048
|
-
_loop_event cycle_end "\${CYCLE_ID}" "" "failed" || true
|
|
3124
|
+
_loop_event cycle_end "\${CYCLE_ID}" "" "failed" || true; _CYCLE_END_WRITTEN=1
|
|
3049
3125
|
_worktree_alert "cycle \${CYCLE_ID}: gh+merge_back+push all failed; worktree preserved at \$WT"
|
|
3050
3126
|
echo "[loop] cycle \${CYCLE_ID}: all publish paths failed; worktree preserved at \$WT"
|
|
3051
3127
|
fi
|
|
@@ -3059,12 +3135,12 @@ if [ "\$_USE_WORKTREE" = "1" ]; then
|
|
|
3059
3135
|
&& git push origin "\$_orphan_tag" 2>/dev/null ); then
|
|
3060
3136
|
_worktree_cleanup "\$WT" "\$BRANCH"
|
|
3061
3137
|
# US-LOOP-005 T6: PR publish failed + orphan push OK → cycle_end orphan
|
|
3062
|
-
_loop_event cycle_end "\${CYCLE_ID}" "" "orphan" || true
|
|
3138
|
+
_loop_event cycle_end "\${CYCLE_ID}" "" "orphan" || true; _CYCLE_END_WRITTEN=1
|
|
3063
3139
|
_worktree_alert "cycle \${CYCLE_ID}: PR publish failed; FIX-039 pushed orphan+tag \${_orphan_tag}; worktree cleaned"
|
|
3064
3140
|
echo "[loop] cycle \${CYCLE_ID}: FIX-039: orphan branch+tag \${_orphan_tag} pushed; worktree cleaned"
|
|
3065
3141
|
else
|
|
3066
3142
|
# US-LOOP-005 T7: PR publish failed + orphan push failed → cycle_end failed
|
|
3067
|
-
_loop_event cycle_end "\${CYCLE_ID}" "" "failed" || true
|
|
3143
|
+
_loop_event cycle_end "\${CYCLE_ID}" "" "failed" || true; _CYCLE_END_WRITTEN=1
|
|
3068
3144
|
_worktree_alert "cycle \${CYCLE_ID}: PR publish failed; worktree preserved at \$WT (branch \$BRANCH)"
|
|
3069
3145
|
echo "[loop] cycle \${CYCLE_ID}: PR publish failed; worktree preserved at \$WT"
|
|
3070
3146
|
fi
|
|
@@ -3072,7 +3148,7 @@ if [ "\$_USE_WORKTREE" = "1" ]; then
|
|
|
3072
3148
|
fi
|
|
3073
3149
|
else
|
|
3074
3150
|
# US-LOOP-005 T8: claude session failed after retry budget → cycle_end failed
|
|
3075
|
-
_loop_event cycle_end "\${CYCLE_ID}" "" "failed" || true
|
|
3151
|
+
_loop_event cycle_end "\${CYCLE_ID}" "" "failed" || true; _CYCLE_END_WRITTEN=1
|
|
3076
3152
|
_worktree_alert "cycle \${CYCLE_ID}: claude exited \$_exit; worktree preserved at \$WT (branch \$BRANCH)"
|
|
3077
3153
|
echo "[loop] cycle \${CYCLE_ID}: claude failed (exit \$_exit); worktree preserved at \$WT"
|
|
3078
3154
|
fi
|
|
@@ -3117,13 +3193,13 @@ if [ -z "\$ROLL_LOOP_FORCE" ] && [ -f "\$PAUSE" ]; then exit 0; fi
|
|
|
3117
3193
|
HEARTBEAT_TIMEOUT="\${ROLL_HEARTBEAT_TIMEOUT:-1800}"
|
|
3118
3194
|
# FIX-052: per-project STATE_FILE (was global state.yaml — caused two projects
|
|
3119
3195
|
# to clobber each other's cycle state).
|
|
3120
|
-
STATE_FILE="
|
|
3196
|
+
STATE_FILE="\${_SHARED_ROOT:-\${HOME}/.shared/roll}/loop/state-${slug}.yaml"
|
|
3121
3197
|
if [ -f "\$STATE_FILE" ]; then
|
|
3122
3198
|
_state=\$(grep '^status:' "\$STATE_FILE" | awk '{print \$2}' 2>/dev/null || echo "")
|
|
3123
3199
|
if [ "\$_state" = "running" ]; then
|
|
3124
3200
|
_still_active=false
|
|
3125
3201
|
# FIX-038: heartbeat is primary signal
|
|
3126
|
-
_heartbeat_file="
|
|
3202
|
+
_heartbeat_file="\${_SHARED_ROOT:-\${HOME}/.shared/roll}/loop/.heartbeat-${slug}"
|
|
3127
3203
|
if [ -f "\$_heartbeat_file" ]; then
|
|
3128
3204
|
_hb_ts=\$(cat "\$_heartbeat_file" 2>/dev/null || echo "0")
|
|
3129
3205
|
_now=\$(date -u +%s)
|
|
@@ -5289,7 +5365,7 @@ cmd_brief() {
|
|
|
5289
5365
|
latest=$(ls "${briefs_dir}"/*.md 2>/dev/null | sort | tail -1 || true)
|
|
5290
5366
|
else
|
|
5291
5367
|
local mod_time now age
|
|
5292
|
-
mod_time=$(
|
|
5368
|
+
mod_time=$(_file_mtime "$latest")
|
|
5293
5369
|
now=$(date +%s); age=$(( now - mod_time ))
|
|
5294
5370
|
if (( age > 86400 )); then
|
|
5295
5371
|
info "Brief is $(( age / 3600 ))h old — regenerating... 简报已 $(( age / 3600 )) 小时未更新,重新生成..."
|
|
@@ -5666,7 +5742,7 @@ _dash_last_dream_hours() {
|
|
|
5666
5742
|
local dream_log="${HOME}/.shared/roll/dream/log.md"
|
|
5667
5743
|
[[ -f "$dream_log" ]] || return 0
|
|
5668
5744
|
local mod_time now
|
|
5669
|
-
mod_time=$(
|
|
5745
|
+
mod_time=$(_file_mtime "$dream_log")
|
|
5670
5746
|
now=$(date +%s)
|
|
5671
5747
|
echo $(( (now - mod_time) / 3600 ))
|
|
5672
5748
|
}
|
|
@@ -5686,7 +5762,7 @@ _dash_last_peer() {
|
|
|
5686
5762
|
local result
|
|
5687
5763
|
result=$(grep -oE '(AGREE|REFINE|OBJECT|ESCALATE)' "$latest" 2>/dev/null | tail -1 || true)
|
|
5688
5764
|
local mod_time now days
|
|
5689
|
-
mod_time=$(
|
|
5765
|
+
mod_time=$(_file_mtime "$latest")
|
|
5690
5766
|
now=$(date +%s)
|
|
5691
5767
|
days=$(( (now - mod_time) / 86400 ))
|
|
5692
5768
|
printf '%s|%s' "${result:-—}" "${days}"
|
|
@@ -5979,7 +6055,7 @@ _legacy_home() {
|
|
|
5979
6055
|
local latest_brief; latest_brief=$(ls .roll/briefs/*.md 2>/dev/null | sort | tail -1 || true)
|
|
5980
6056
|
if [[ -n "$latest_brief" ]]; then
|
|
5981
6057
|
local mod_time now age summary
|
|
5982
|
-
mod_time=$(
|
|
6058
|
+
mod_time=$(_file_mtime "$latest_brief")
|
|
5983
6059
|
now=$(date +%s); age=$(( (now - mod_time) / 3600 ))
|
|
5984
6060
|
summary=$(_dash_brief_summary "$latest_brief")
|
|
5985
6061
|
printf " Brief ${CYAN}%sh${NC} ago — %s\n" "$age" "${summary:-—}"
|
|
@@ -6016,7 +6092,7 @@ _legacy_help() {
|
|
|
6016
6092
|
echo "Commands:"
|
|
6017
6093
|
echo " setup [-f] [Machine] First-time install or re-sync 首次安装或重新同步"
|
|
6018
6094
|
echo " update [Upgrade] npm install latest + re-sync 一键升级到最新版"
|
|
6019
|
-
echo " init [Project] Create AGENTS.md + .roll/backlog.md +
|
|
6095
|
+
echo " init [Project] Create AGENTS.md + .roll/backlog.md + .roll/features/ 初始化项目工作流文件"
|
|
6020
6096
|
echo " status [Diagnostic] Show current state 显示当前状态"
|
|
6021
6097
|
echo " peer [Peer Review] Cross-agent negotiation 跨 Agent 协商对审"
|
|
6022
6098
|
echo " loop <on|off|now|status|monitor|resume|reset> [Autonomous] Manage scheduled BACKLOG executor 管理自主执行循环"
|
|
Binary file
|
package/lib/roll-backlog.py
CHANGED
|
@@ -244,7 +244,7 @@ def _write_demo(path: str) -> None:
|
|
|
244
244
|
### Feature: autonomous-evolution
|
|
245
245
|
| Story | Description | Status |
|
|
246
246
|
|-------|-------------|--------|
|
|
247
|
-
| [US-AUTO-042](.roll/features/autonomous-evolution.md) | Loop cost telemetry — write model and token data per cycle | 📋 Todo |
|
|
247
|
+
| [US-AUTO-042](.roll/features/autonomous-evolution/autonomous-evolution.md) | Loop cost telemetry — write model and token data per cycle | 📋 Todo |
|
|
248
248
|
|
|
249
249
|
## ♻️ Refactor
|
|
250
250
|
| ID | Description | Status |
|
package/lib/roll-help.py
CHANGED
|
@@ -45,7 +45,7 @@ AUTONOMY = [
|
|
|
45
45
|
]
|
|
46
46
|
|
|
47
47
|
PROJECT = [
|
|
48
|
-
("init", "", "create AGENTS.md + .roll/backlog.md +
|
|
48
|
+
("init", "", "create AGENTS.md + .roll/backlog.md + .roll/features/", "初始化项目工作流文件", False),
|
|
49
49
|
("status", "", "show current state and drift", "显示当前状态和漂移项", False),
|
|
50
50
|
("agent", "[use <name>]", "per-project agent selection", "切换项目 agent", False),
|
|
51
51
|
("ci", "[--wait]", "show or wait for current commit's CI status", "查看 / 等待 CI 状态", False),
|
package/package.json
CHANGED
|
@@ -278,7 +278,7 @@ When any signal appears, **do not stop — flag it**:
|
|
|
278
278
|
# 1. Append to .roll/backlog.md under ## ♻️ Refactor
|
|
279
279
|
# REFACTOR-XXX | <one-line description> | 📋 Todo
|
|
280
280
|
|
|
281
|
-
# 2. Append a brief entry to .roll/features/refactor-log.md
|
|
281
|
+
# 2. Append a brief entry to .roll/features/autonomous-evolution/refactor-log.md
|
|
282
282
|
```
|
|
283
283
|
|
|
284
284
|
**REFACTOR entry format in .roll/backlog.md:**
|
|
@@ -287,7 +287,7 @@ When any signal appears, **do not stop — flag it**:
|
|
|
287
287
|
| REFACTOR-001 | {one-line plain-language description} | 📋 Todo |
|
|
288
288
|
```
|
|
289
289
|
|
|
290
|
-
描述写法:参见 AGENTS.md "Backlog descriptions" 规则。说清楚"什么需要改"以及"不改会怎样",技术细节写在 `.roll/features/refactor-log.md`。
|
|
290
|
+
描述写法:参见 AGENTS.md "Backlog descriptions" 规则。说清楚"什么需要改"以及"不改会怎样",技术细节写在 `.roll/features/autonomous-evolution/refactor-log.md`。
|
|
291
291
|
|
|
292
292
|
**refactor-log.md entry format:**
|
|
293
293
|
|
|
@@ -208,11 +208,14 @@ For each item, **before invoking the executor skill**, mark the story 🔨 In Pr
|
|
|
208
208
|
|
|
209
209
|
This commit is what makes the work visible — without it, tcr micro-commits during execution are invisible to `roll-brief`.
|
|
210
210
|
|
|
211
|
-
选定故事后,调用 `_loop_event` 发出
|
|
211
|
+
选定故事后,调用 `_loop_event` 发出 pick_todo 事件,让 dashboard / monitor / attach 都能把"这个 cycle 选了哪个 story"正确归类:
|
|
212
212
|
|
|
213
213
|
```bash
|
|
214
214
|
# 选定故事后立即 emit(在调用 executor skill 之前)
|
|
215
|
-
|
|
215
|
+
# label 必须是 cycle_id(来自 bin/roll 注入的 LOOP_CYCLE_ID 环境变量),
|
|
216
|
+
# 不是 US_ID — dashboard 按 label 聚类,US_ID 当 label 会让事件分到错的桶
|
|
217
|
+
# 里,cycle 看起来"有 token 没 ID"。
|
|
218
|
+
_loop_event pick_todo "$LOOP_CYCLE_ID" "$US_ID" ""
|
|
216
219
|
```
|
|
217
220
|
|
|
218
221
|
Then invoke the executor:
|