@seanyao/roll 2026.528.2 → 2026.529.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.
- package/CHANGELOG.md +21 -8
- package/README.md +1 -0
- package/bin/roll +239 -47
- package/lib/README.md +42 -0
- package/lib/i18n/README.md +54 -0
- package/lib/i18n/doctor.sh +13 -0
- package/lib/i18n/loop.sh +12 -12
- package/lib/prices/README.md +35 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,26 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## v2026.
|
|
3
|
+
## v2026.529.1
|
|
4
4
|
|
|
5
5
|
### Added
|
|
6
6
|
|
|
7
|
-
- **loop
|
|
8
|
-
- **CI 红了 loop 不再干等** — 先试着自己修,修不好再找人 `[loop]`
|
|
7
|
+
- **loop 运行时间可以按项目设** — 不用再全局迁就
|
|
9
8
|
|
|
10
9
|
### Fixed
|
|
11
10
|
|
|
12
|
-
- **`roll loop
|
|
13
|
-
- **
|
|
11
|
+
- **`roll loop on` 不再显示全 00:00** — 时间显示正常了
|
|
12
|
+
- **agent 检测不再误报** — 没装的工具不会被当成可用
|
|
14
13
|
|
|
15
|
-
## v2026.528.
|
|
14
|
+
## v2026.528.2
|
|
16
15
|
|
|
17
16
|
### Added
|
|
18
17
|
|
|
19
|
-
-
|
|
18
|
+
- **loop 换机器跑不会再拿过期 backlog** — 以前在 A 机器跑的 loop,搬到 B 机器继续时会用本地的旧 backlog,不知道有什么新待办;现在每轮开始前自动拉一次最新状态,多台机器始终看同一份 `[loop]`
|
|
19
|
+
|
|
20
|
+
- **CI 红了 loop 不再干等** — 主干测试挂掉时,loop 以前会停下等人去修;现在先自己分析失败原因、发一个修复,试满 3 次还没修好才发告警找人;自己开的 PR 被 CI 标红后也会自动续上去修,不会因为"本轮 loop 已经结束"就悄悄放弃 `[loop]`
|
|
21
|
+
|
|
22
|
+
- **`roll test` — 测试跑在独立环境里,不会再误伤本机** — 以前跑完整测试套件会触碰本机的 loop 调度服务,测试一过就把正在运行的 loop 打掉;现在测试在独立的 macOS VM 里跑,本机的 loop 完全不受影响 `[test]`
|
|
20
23
|
|
|
21
24
|
### Fixed
|
|
22
25
|
|
|
23
|
-
- **Kimi CLI
|
|
26
|
+
- **Kimi CLI 改名后全链路都能识别了** — Kimi 把工具从 kimi-cli 改名为 kimi-code、安装目录也换了;Roll 现在新旧名字都能认出来,kimi-code 的安装路径也加进了自动查找范围,调度环境里也能找到,已设好的旧配置不需要动 `[agent]`
|
|
27
|
+
|
|
28
|
+
- **`roll loop log` 现在真的能看了** — 每轮 cycle 的日志存档修好了;以前文件根本没生成,现在用 `roll loop log` 能查到每一轮跑了什么 `[loop]`
|
|
29
|
+
|
|
30
|
+
- **loop 跑完的终端窗口不再瞬间消失** — 以前 cycle 结束 Terminal 窗口立刻关掉,来不及看本轮干了什么;现在窗口留着直到你自己关 `[loop]`
|
|
31
|
+
|
|
32
|
+
### Improved
|
|
33
|
+
|
|
34
|
+
- **提交安全检查不会再被静默绕过** — Roll 的 TCR 要求每次提交前先过测试;以前在新终端或新机器上开工,这道检查会因为 git 配置没自动生效而悄悄失效,自动化环境尤其容易中招;现在每次打开 Claude Code 新会话、或跑 `roll setup` 都会自动配好,这个漏洞从源头堵死 `[infra]`
|
|
35
|
+
|
|
36
|
+
- **macOS 自带 bash 下中文命名的测试用例不再被静默跳过** — macOS 系统自带的旧版 bash 在处理中文或特殊字符测试名时会截断,导致这些测试根本没执行却也不报错,本地看起来全绿但实际上漏了一批用例;已修复,本地和 CI 结果现在一致 `[test]`
|
|
24
37
|
|
|
25
38
|
## v2026.527.1
|
|
26
39
|
|
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ roll loop on # let AI work through the backlog (optional)
|
|
|
50
50
|
| `roll status` | Show current state and drift |
|
|
51
51
|
| `roll agent [use <name>]` | Per-project agent selection |
|
|
52
52
|
| `roll ci [--wait]` | Show or wait for current commit's CI status |
|
|
53
|
+
| `roll test [--where] [--reset]` | Run the test suite (routes through isolation adapter; Tart VM on Apple Silicon) |
|
|
53
54
|
| `roll release` | Run the release script (human-only) |
|
|
54
55
|
| `roll review-pr <number>` | AI-powered code review for a PR |
|
|
55
56
|
| **Machine · global** | |
|
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.
|
|
7
|
+
VERSION="2026.529.1"
|
|
8
8
|
ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
|
|
9
9
|
ROLL_CONFIG="${ROLL_HOME}/config.yaml"
|
|
10
10
|
ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
|
|
@@ -83,31 +83,80 @@ lower_name() {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
# FIX-128: agent → binary-name(s) lookup. First binary found wins. Used by
|
|
87
|
+
# _agent_installed_by_name to enforce "CLI must exist on PATH" detection
|
|
88
|
+
# instead of the old "config dir exists" check (which Roll's own convention
|
|
89
|
+
# sync would fake — see FIX-128).
|
|
90
|
+
_agent_bin_names() {
|
|
91
|
+
case "$1" in
|
|
92
|
+
claude) echo "claude" ;;
|
|
93
|
+
codex|openai) echo "codex" ;; # openai is a Roll alias for codex
|
|
94
|
+
agy|gemini) echo "agy gemini" ;; # gemini reuses ~/.gemini for agy
|
|
95
|
+
kimi) echo "kimi-code kimi-cli kimi" ;; # FIX-126
|
|
96
|
+
deepseek) echo "deepseek" ;;
|
|
97
|
+
qwen) echo "qwen" ;;
|
|
98
|
+
pi) echo "pi" ;;
|
|
99
|
+
*) return 1 ;;
|
|
100
|
+
esac
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# FIX-128: detect whether an AI agent (by canonical name) is actually
|
|
104
|
+
# usable on this machine. For CLI-only agents this means "binary on PATH";
|
|
105
|
+
# GUI / bundled-binary agents keep their special-case paths. Falls back
|
|
106
|
+
# to dir-existence only for unknown agents the operator has registered
|
|
107
|
+
# manually (forward-compatible with future additions).
|
|
108
|
+
_agent_installed_by_name() {
|
|
109
|
+
local agent="$1"
|
|
110
|
+
local dir="${2:-}"
|
|
111
|
+
case "$agent" in
|
|
95
112
|
trae)
|
|
96
|
-
[[ -d "$HOME/Library/Application Support/Trae" ]] ||
|
|
97
|
-
[[ -d "$HOME/.config/Trae" ]]
|
|
113
|
+
[[ -d "$HOME/Library/Application Support/Trae" ]] || [[ -d "$HOME/.config/Trae" ]]
|
|
98
114
|
return
|
|
99
115
|
;;
|
|
100
116
|
opencode)
|
|
101
117
|
[[ -x "$HOME/.opencode/bin/opencode" ]]
|
|
102
118
|
return
|
|
103
119
|
;;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
cursor)
|
|
121
|
+
# cursor ships a GUI + an optional CLI; either path counts as "installed".
|
|
122
|
+
command -v cursor >/dev/null 2>&1 || [[ -d "$HOME/.cursor" ]]
|
|
123
|
+
return
|
|
124
|
+
;;
|
|
125
|
+
openclaw)
|
|
126
|
+
[[ -d "$HOME/.openclaw/workspace" ]]
|
|
127
|
+
return
|
|
108
128
|
;;
|
|
109
129
|
esac
|
|
110
|
-
|
|
130
|
+
local bins
|
|
131
|
+
if bins=$(_agent_bin_names "$agent" 2>/dev/null); then
|
|
132
|
+
local b
|
|
133
|
+
for b in $bins; do
|
|
134
|
+
command -v "$b" >/dev/null 2>&1 && return 0
|
|
135
|
+
done
|
|
136
|
+
return 1
|
|
137
|
+
fi
|
|
138
|
+
# Unknown agent — fall back to dir presence so user-added entries still work.
|
|
139
|
+
[[ -n "$dir" && -d "$dir" ]]
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Check if an AI tool is actually installed (back-compat shim around
|
|
143
|
+
# _agent_installed_by_name; preserves the dir-path-based signature used
|
|
144
|
+
# throughout bin/roll).
|
|
145
|
+
_is_ai_installed() {
|
|
146
|
+
local ai_dir="$1"
|
|
147
|
+
local bn
|
|
148
|
+
bn="$(basename "$ai_dir" | sed 's/^\.//')"
|
|
149
|
+
# Nested-dir layouts collapse to their parent agent name.
|
|
150
|
+
case "$bn" in
|
|
151
|
+
agent|workspace)
|
|
152
|
+
bn="$(basename "$(dirname "$ai_dir")" | sed 's/^\.//')"
|
|
153
|
+
;;
|
|
154
|
+
esac
|
|
155
|
+
# Mirror ai_tool_name's alias normalization so detection routes to the
|
|
156
|
+
# canonical agent record (e.g. ~/.gemini → agy, ~/.kimi-code → kimi).
|
|
157
|
+
[[ "$bn" == "gemini" ]] && bn="agy"
|
|
158
|
+
[[ "$bn" == "kimi-code" ]] && bn="kimi"
|
|
159
|
+
_agent_installed_by_name "$bn" "$ai_dir"
|
|
111
160
|
}
|
|
112
161
|
|
|
113
162
|
# ─── Spinner: TTY-aware status display for long-running steps (US-REL-003) ───
|
|
@@ -512,8 +561,7 @@ editor: ${EDITOR:-vim}
|
|
|
512
561
|
|
|
513
562
|
# Loop schedule (24h format, machine local timezone)
|
|
514
563
|
# Minute fields auto-derive from project path hash when omitted — avoids contention across projects.
|
|
515
|
-
|
|
516
|
-
loop_active_end: 18
|
|
564
|
+
# active_start/active_end moved to per-project .roll/local.yaml loop_schedule block (default 0/24).
|
|
517
565
|
# loop_minute: 5 # omit to auto-derive from project hash
|
|
518
566
|
loop_dream_hour: 3
|
|
519
567
|
# loop_dream_minute: 10 # omit to auto-derive
|
|
@@ -522,6 +570,19 @@ loop_brief_hour: 9
|
|
|
522
570
|
primary_agent: claude
|
|
523
571
|
YAML
|
|
524
572
|
ok "$(msg shared.created_roll_config_yaml)"
|
|
573
|
+
|
|
574
|
+
# FIX-128: the heredoc template hardcodes `primary_agent: claude` for
|
|
575
|
+
# the first-time case. Replace it with the first agent that actually
|
|
576
|
+
# has its CLI on PATH so users without Claude installed don't get a
|
|
577
|
+
# silently-broken default. If nothing detected, leave `claude` so the
|
|
578
|
+
# user still has a clear handle to fix manually.
|
|
579
|
+
local _detected_primary
|
|
580
|
+
_detected_primary="$(_first_installed_agent || true)"
|
|
581
|
+
if [[ -n "$_detected_primary" && "$_detected_primary" != "claude" ]]; then
|
|
582
|
+
_replace_primary_agent "$_detected_primary"
|
|
583
|
+
info "$(msg shared.primary_agent_auto_detected "$_detected_primary" 2>/dev/null \
|
|
584
|
+
|| echo "primary_agent → $_detected_primary (auto-detected from installed CLIs)")"
|
|
585
|
+
fi
|
|
525
586
|
fi
|
|
526
587
|
|
|
527
588
|
# Ensure all expected ai_* keys exist (handles upgrades where new tools were added)
|
|
@@ -529,6 +590,32 @@ YAML
|
|
|
529
590
|
|
|
530
591
|
}
|
|
531
592
|
|
|
593
|
+
# FIX-128: pick the first agent whose CLI is on PATH, scanning the same
|
|
594
|
+
# order the default config template lists them. Empty stdout when none
|
|
595
|
+
# detected; never errors.
|
|
596
|
+
_first_installed_agent() {
|
|
597
|
+
local agent
|
|
598
|
+
for agent in claude codex kimi deepseek qwen agy pi cursor opencode trae openclaw; do
|
|
599
|
+
if _agent_installed_by_name "$agent"; then
|
|
600
|
+
echo "$agent"
|
|
601
|
+
return 0
|
|
602
|
+
fi
|
|
603
|
+
done
|
|
604
|
+
return 1
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
# FIX-128: rewrite the `primary_agent:` line in $ROLL_CONFIG to the given
|
|
608
|
+
# value. Single-line in-place edit, preserves the rest of the file.
|
|
609
|
+
_replace_primary_agent() {
|
|
610
|
+
local new="$1"
|
|
611
|
+
[[ -f "$ROLL_CONFIG" && -n "$new" ]] || return 0
|
|
612
|
+
local tmp; tmp="$(mktemp)"
|
|
613
|
+
awk -v new="$new" '
|
|
614
|
+
/^primary_agent:/ { print "primary_agent: " new; next }
|
|
615
|
+
{ print }
|
|
616
|
+
' "$ROLL_CONFIG" > "$tmp" && mv "$tmp" "$ROLL_CONFIG"
|
|
617
|
+
}
|
|
618
|
+
|
|
532
619
|
# ─── Internal: create or repair per-skill symlinks (non-destructive) ─────────
|
|
533
620
|
_link_skills() {
|
|
534
621
|
local force="${1:-false}"
|
|
@@ -539,7 +626,18 @@ _link_skills() {
|
|
|
539
626
|
while IFS= read -r entry; do
|
|
540
627
|
local ai_dir
|
|
541
628
|
ai_dir="$(_ai_dir "$entry")"
|
|
542
|
-
|
|
629
|
+
# FIX-128: detection is now binary-on-PATH, but skill linking keeps
|
|
630
|
+
# the same Claude-always-syncs semantics as _apply_conventions and
|
|
631
|
+
# tolerates pre-existing config dirs (an agent the user is mid-
|
|
632
|
+
# upgrade or installed via nvm/asdf still has its convention dir;
|
|
633
|
+
# we don't want to silently stop linking skills there). Strict
|
|
634
|
+
# binary detection drives chooser logic (primary_agent /
|
|
635
|
+
# _onboard_discover_agents) — see FIX-128.
|
|
636
|
+
if [[ "$ai_dir" != "$HOME/.claude" ]] \
|
|
637
|
+
&& ! _is_ai_installed "$ai_dir" \
|
|
638
|
+
&& [[ ! -d "$ai_dir" ]]; then
|
|
639
|
+
continue
|
|
640
|
+
fi
|
|
543
641
|
mkdir -p "$ai_dir"
|
|
544
642
|
|
|
545
643
|
local ai_name ai_dir_real skills_dir
|
|
@@ -639,8 +737,13 @@ _sync_convention_for_tool() {
|
|
|
639
737
|
local dst_dir
|
|
640
738
|
dst_dir="$(dirname "$main_dst")"
|
|
641
739
|
|
|
642
|
-
# Only proceed if Claude (always)
|
|
643
|
-
|
|
740
|
+
# Only proceed if Claude (always), the tool is installed (binary-on-PATH
|
|
741
|
+
# per FIX-128), or the convention dir already exists (mid-upgrade /
|
|
742
|
+
# nvm-installed binaries that aren't on this shell's PATH still get
|
|
743
|
+
# their convention refresh).
|
|
744
|
+
if [[ "$dst_dir" != "$HOME/.claude" ]] \
|
|
745
|
+
&& ! _is_ai_installed "$dst_dir" \
|
|
746
|
+
&& [[ ! -d "$dst_dir" ]]; then
|
|
644
747
|
return
|
|
645
748
|
fi
|
|
646
749
|
mkdir -p "$dst_dir"
|
|
@@ -927,10 +1030,46 @@ HINT
|
|
|
927
1030
|
# prints install commands for the ones that aren't, so users who already opted
|
|
928
1031
|
# in (or opted out) don't get spammed each upgrade.
|
|
929
1032
|
cmd_doctor() {
|
|
1033
|
+
_doctor_agent_section
|
|
930
1034
|
_doctor_pr_section
|
|
931
1035
|
_doctor_launchd_stale_section
|
|
932
1036
|
}
|
|
933
1037
|
|
|
1038
|
+
# FIX-128: list every ai_* entry from config, tag each with binary-on-PATH
|
|
1039
|
+
# status and config-dir existence so the user can see at a glance which
|
|
1040
|
+
# agents are actually usable vs only have Roll-maintained dirs.
|
|
1041
|
+
_doctor_agent_section() {
|
|
1042
|
+
[[ -f "$ROLL_CONFIG" ]] || return 0
|
|
1043
|
+
echo ""
|
|
1044
|
+
echo "$(ROLL_LANG_RESOLVED=en msg doctor.agent_detection)"
|
|
1045
|
+
echo "$(ROLL_LANG_RESOLVED=zh msg doctor.agent_detection)"
|
|
1046
|
+
echo ""
|
|
1047
|
+
local _key _value _name _dir _installed _dir_exists _is_primary
|
|
1048
|
+
_is_primary=$(grep -E '^primary_agent:' "$ROLL_CONFIG" 2>/dev/null | sed 's/^primary_agent: *//')
|
|
1049
|
+
while IFS=: read -r _key _value; do
|
|
1050
|
+
[[ "$_key" =~ ^ai_ ]] || continue
|
|
1051
|
+
_name="${_key#ai_}"
|
|
1052
|
+
[[ "$_name" == "kimi_code" ]] && continue # dedupe
|
|
1053
|
+
_dir="${_value%%|*}"
|
|
1054
|
+
_dir="${_dir# }"
|
|
1055
|
+
_dir="${_dir/#\~/$HOME}"
|
|
1056
|
+
if _agent_installed_by_name "$_name" "$_dir"; then
|
|
1057
|
+
_installed="$(msg doctor.agent_installed)"
|
|
1058
|
+
else
|
|
1059
|
+
_installed="$(msg doctor.agent_missing)"
|
|
1060
|
+
fi
|
|
1061
|
+
if [[ -d "$_dir" ]]; then
|
|
1062
|
+
_dir_exists="$(msg doctor.agent_dir_exists)"
|
|
1063
|
+
else
|
|
1064
|
+
_dir_exists="$(msg doctor.agent_dir_missing)"
|
|
1065
|
+
fi
|
|
1066
|
+
local _tag=""
|
|
1067
|
+
[[ "$_name" == "$_is_primary" ]] && _tag=" ($(msg doctor.agent_primary_label))"
|
|
1068
|
+
printf " %-10s %-14s %s%s\n" "$_name" "$_installed" "$_dir_exists" "$_tag"
|
|
1069
|
+
done < "$ROLL_CONFIG"
|
|
1070
|
+
return 0
|
|
1071
|
+
}
|
|
1072
|
+
|
|
934
1073
|
# FIX-097: scan ${_LAUNCHD_DIR}/com.roll.*.plist for entries whose
|
|
935
1074
|
# WorkingDirectory no longer exists on disk. These are the ghost agents left
|
|
936
1075
|
# behind when a user manually reproduces a bug under /private/tmp/ or
|
|
@@ -1467,15 +1606,31 @@ _onboard_discover_agents() {
|
|
|
1467
1606
|
while IFS=: read -r _key _value; do
|
|
1468
1607
|
[[ "$_key" =~ ^ai_ ]] || continue
|
|
1469
1608
|
_name="${_key#ai_}"
|
|
1609
|
+
# ai_kimi_code → kimi (avoid listing the same agent twice).
|
|
1610
|
+
[[ "$_name" == "kimi_code" ]] && _name="kimi"
|
|
1470
1611
|
_dir="${_value%%|*}"
|
|
1471
1612
|
_dir="${_dir# }"
|
|
1472
1613
|
_dir="${_dir/#\~/$HOME}"
|
|
1473
|
-
|
|
1474
|
-
|
|
1614
|
+
# FIX-128: route via _agent_installed_by_name so "installed" means the
|
|
1615
|
+
# CLI is actually on PATH for known agents, not just the config dir
|
|
1616
|
+
# that Roll's own convention sync would have created.
|
|
1617
|
+
if _agent_installed_by_name "$_name" "$_dir"; then
|
|
1618
|
+
# Dedupe — kimi may appear under both ai_kimi and ai_kimi_code.
|
|
1619
|
+
# `${arr[@]+...}` keeps `set -u` happy when the array is still empty.
|
|
1620
|
+
local _already=0 _existing
|
|
1621
|
+
for _existing in ${_ONBOARD_INSTALLED[@]+"${_ONBOARD_INSTALLED[@]}"}; do
|
|
1622
|
+
if [[ "$_existing" == "$_name" ]]; then _already=1; break; fi
|
|
1623
|
+
done
|
|
1624
|
+
if [[ $_already -eq 0 ]]; then _ONBOARD_INSTALLED+=("$_name"); fi
|
|
1475
1625
|
else
|
|
1476
|
-
|
|
1626
|
+
local _already=0 _existing
|
|
1627
|
+
for _existing in ${_ONBOARD_MISSING[@]+"${_ONBOARD_MISSING[@]}"}; do
|
|
1628
|
+
if [[ "$_existing" == "$_name" ]]; then _already=1; break; fi
|
|
1629
|
+
done
|
|
1630
|
+
if [[ $_already -eq 0 ]]; then _ONBOARD_MISSING+=("$_name"); fi
|
|
1477
1631
|
fi
|
|
1478
1632
|
done < "$ROLL_CONFIG"
|
|
1633
|
+
return 0
|
|
1479
1634
|
}
|
|
1480
1635
|
|
|
1481
1636
|
# US-ONBOARD-018: pick an agent for the onboard flow.
|
|
@@ -4624,13 +4779,16 @@ _isolation_tart_check_binary() {
|
|
|
4624
4779
|
# returns 1 silently otherwise. Caller decides what to do.
|
|
4625
4780
|
_isolation_tart_vm_present() {
|
|
4626
4781
|
local name; name=$(_isolation_tart_vm_name)
|
|
4627
|
-
tart list 2>/dev/null | awk -v n="$name" '$
|
|
4782
|
+
tart list 2>/dev/null | awk -v n="$name" '$2 == n { found=1 } END { exit !found }'
|
|
4628
4783
|
}
|
|
4629
4784
|
|
|
4630
4785
|
# Returns the VM's IP on stdout when reachable; exit non-zero when the VM
|
|
4631
4786
|
# is stopped or `tart ip` fails for any other reason.
|
|
4632
4787
|
_isolation_tart_ip() {
|
|
4633
4788
|
local name; name=$(_isolation_tart_vm_name)
|
|
4789
|
+
# FIX: tart ip returns a stale DHCP-cached IP even for stopped VMs.
|
|
4790
|
+
# Gate on tart list State field before trusting the IP.
|
|
4791
|
+
tart list 2>/dev/null | awk -v n="$name" '$2 == n && $NF == "running" { found=1 } END { exit !found }' || return 1
|
|
4634
4792
|
local ip; ip=$(tart ip "$name" 2>/dev/null) || return 1
|
|
4635
4793
|
[[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
|
|
4636
4794
|
printf '%s\n' "$ip"
|
|
@@ -4680,7 +4838,7 @@ _isolation_tart_provision() {
|
|
|
4680
4838
|
local ip; ip=$(_isolation_tart_ip) || { err "tart provision: VM not running"; return 1; }
|
|
4681
4839
|
local user; user=$(_isolation_tart_ssh_user)
|
|
4682
4840
|
ssh -o BatchMode=yes -o StrictHostKeyChecking=no \
|
|
4683
|
-
"${user}@${ip}" "brew list bats >/dev/null 2>&1 || brew install bats-core; \
|
|
4841
|
+
"${user}@${ip}" "export PATH=/opt/homebrew/bin:/usr/local/bin:\$PATH; brew list bats >/dev/null 2>&1 || brew install bats-core; \
|
|
4684
4842
|
brew list node >/dev/null 2>&1 || brew install node; \
|
|
4685
4843
|
brew list bash >/dev/null 2>&1 || brew install bash"
|
|
4686
4844
|
}
|
|
@@ -4695,7 +4853,7 @@ _isolation_tart_exec() {
|
|
|
4695
4853
|
if ! ip=$(_isolation_tart_ip); then
|
|
4696
4854
|
# VM stopped — start it in the background with the repo mounted.
|
|
4697
4855
|
local repo_root; repo_root="$(pwd -P)"
|
|
4698
|
-
tart run --dir="roll:${repo_root}" "$name" >/dev/null 2>&1 &
|
|
4856
|
+
tart run --no-graphics --dir="roll:${repo_root}" "$name" >/dev/null 2>&1 &
|
|
4699
4857
|
# Wait up to ~30s for IP to come up.
|
|
4700
4858
|
local i=0
|
|
4701
4859
|
while (( i < 30 )); do
|
|
@@ -4706,7 +4864,9 @@ _isolation_tart_exec() {
|
|
|
4706
4864
|
[[ -n "${ip:-}" ]] || { err "tart exec: VM failed to start in 30s"; return 1; }
|
|
4707
4865
|
fi
|
|
4708
4866
|
local user; user=$(_isolation_tart_ssh_user)
|
|
4709
|
-
|
|
4867
|
+
local remote_cmd
|
|
4868
|
+
remote_cmd=$(printf '%q ' "$@")
|
|
4869
|
+
ssh -o BatchMode=yes -o StrictHostKeyChecking=no "${user}@${ip}" "export PATH=/opt/homebrew/bin:/usr/local/bin:\$PATH; cd '/Volumes/My Shared Files/roll' && $remote_cmd"
|
|
4710
4870
|
}
|
|
4711
4871
|
|
|
4712
4872
|
# reset: stop, delete, re-clone from base image, then re-provision.
|
|
@@ -4810,7 +4970,8 @@ Flags:
|
|
|
4810
4970
|
--help, -h Show this help.
|
|
4811
4971
|
|
|
4812
4972
|
Examples:
|
|
4813
|
-
roll test Run
|
|
4973
|
+
roll test Run affected tests (default: --affected HEAD~1).
|
|
4974
|
+
roll test -- tests/ Run the full suite explicitly.
|
|
4814
4975
|
roll test -- --tier=fast Forward arguments to npm test.
|
|
4815
4976
|
roll test --where Don't run; just report routing.
|
|
4816
4977
|
roll test --reset Rebuild the VM (or host no-op).
|
|
@@ -4855,7 +5016,16 @@ EOF
|
|
|
4855
5016
|
fi
|
|
4856
5017
|
|
|
4857
5018
|
# Pass remaining args through to npm test inside the configured adapter.
|
|
4858
|
-
|
|
5019
|
+
# Default to --affected (HEAD~1 base) when the caller passes no extra args —
|
|
5020
|
+
# mirrors the pre-commit hook's intent and keeps VM runs fast.
|
|
5021
|
+
# To run the full suite explicitly: roll test -- tests/
|
|
5022
|
+
local _npm_args=("$@")
|
|
5023
|
+
if [[ "${#_npm_args[@]}" -eq 0 ]]; then
|
|
5024
|
+
_npm_args=(--affected)
|
|
5025
|
+
fi
|
|
5026
|
+
# Always pass args via `--` so npm doesn't intercept flags like --affected
|
|
5027
|
+
# as npm config options (npm warns and silently drops them otherwise).
|
|
5028
|
+
_isolation_dispatch exec npm test -- "${_npm_args[@]}"
|
|
4859
5029
|
}
|
|
4860
5030
|
|
|
4861
5031
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -5431,6 +5601,28 @@ _loop_schedule_spec() {
|
|
|
5431
5601
|
echo "60 $offset"
|
|
5432
5602
|
}
|
|
5433
5603
|
|
|
5604
|
+
# Read loop active window from .roll/local.yaml loop_schedule block.
|
|
5605
|
+
# Resolution order:
|
|
5606
|
+
# 1. .roll/local.yaml loop_schedule.{active_start,active_end}
|
|
5607
|
+
# 2. default 0 / 24 (full day)
|
|
5608
|
+
# Validation: both values must be integers 0–24, active_start < active_end.
|
|
5609
|
+
# Output: "<start> <end>" on stdout.
|
|
5610
|
+
_loop_read_active_window() {
|
|
5611
|
+
local project_path="${1:-$(pwd -P)}"
|
|
5612
|
+
local local_file="${project_path}/.roll/local.yaml"
|
|
5613
|
+
if [[ -f "$local_file" ]]; then
|
|
5614
|
+
local val_start val_end
|
|
5615
|
+
val_start=$(awk '/^loop_schedule:/{found=1;next} found && /^[[:space:]]+active_start:/{print $2; exit}' "$local_file")
|
|
5616
|
+
val_end=$(awk '/^loop_schedule:/{found=1;next} found && /^[[:space:]]+active_end:/{print $2; exit}' "$local_file")
|
|
5617
|
+
if [[ "$val_start" =~ ^[0-9]+$ && "$val_end" =~ ^[0-9]+$ ]] \
|
|
5618
|
+
&& (( val_start < val_end && val_end <= 24 )); then
|
|
5619
|
+
echo "$val_start $val_end"
|
|
5620
|
+
return 0
|
|
5621
|
+
fi
|
|
5622
|
+
fi
|
|
5623
|
+
echo "0 24"
|
|
5624
|
+
}
|
|
5625
|
+
|
|
5434
5626
|
# US-LOOP-032: human-readable schedule description.
|
|
5435
5627
|
# Args: period offset [lang]
|
|
5436
5628
|
# lang: en (default) or zh
|
|
@@ -6625,8 +6817,8 @@ _install_launchd_plists() {
|
|
|
6625
6817
|
mkdir -p "${shared}/loop" "${shared}/dream" "${shared}/brief"
|
|
6626
6818
|
|
|
6627
6819
|
local active_start active_end dream_hour dream_minute brief_hour brief_minute loop_period loop_offset
|
|
6628
|
-
|
|
6629
|
-
|
|
6820
|
+
local _aw; _aw=$(_loop_read_active_window "$project_path")
|
|
6821
|
+
active_start="${_aw%% *}"; active_end="${_aw##* }"
|
|
6630
6822
|
# US-LOOP-012: use _loop_schedule_spec instead of raw loop_minute
|
|
6631
6823
|
local loop_spec; loop_spec=$(_loop_schedule_spec "$project_path")
|
|
6632
6824
|
loop_period="${loop_spec%% *}"
|
|
@@ -6802,8 +6994,8 @@ _loop_on() {
|
|
|
6802
6994
|
local agent; agent=$(_project_agent)
|
|
6803
6995
|
|
|
6804
6996
|
local active_start active_end loop_minute dream_hour dream_minute brief_hour brief_minute
|
|
6805
|
-
|
|
6806
|
-
|
|
6997
|
+
local _aw; _aw=$(_loop_read_active_window "$project_path")
|
|
6998
|
+
active_start="${_aw%% *}"; active_end="${_aw##* }"
|
|
6807
6999
|
# US-LOOP-011: read schedule spec from project or global config
|
|
6808
7000
|
local loop_spec loop_period loop_offset
|
|
6809
7001
|
loop_spec=$(_loop_schedule_spec "$project_path")
|
|
@@ -6849,10 +7041,10 @@ _loop_on() {
|
|
|
6849
7041
|
fi
|
|
6850
7042
|
|
|
6851
7043
|
ok "$(msg loop.loop_enabled)"
|
|
6852
|
-
|
|
7044
|
+
msg loop.roll_loop_s_active_02d_00 \
|
|
6853
7045
|
"$loop_sched_en" "$active_start" "$active_end" "$loop_sched_zh" "$active_start" "$active_end"
|
|
6854
|
-
|
|
6855
|
-
|
|
7046
|
+
msg loop.roll_dream_daily_at_02d_02d "$dream_hour" "$dream_minute" "$dream_hour" "$dream_minute"
|
|
7047
|
+
msg loop.roll_brief_daily_at_02d_02d "$brief_hour" "$brief_minute" "$brief_hour" "$brief_minute"
|
|
6856
7048
|
echo " • Agent: ${agent} (change: roll agent use <name>)"
|
|
6857
7049
|
return 0
|
|
6858
7050
|
fi
|
|
@@ -6880,10 +7072,10 @@ _loop_on() {
|
|
|
6880
7072
|
) | crontab -
|
|
6881
7073
|
|
|
6882
7074
|
ok "$(msg loop.loop_enabled_2)"
|
|
6883
|
-
|
|
7075
|
+
msg loop.roll_loop_s_active_02d_00_2 \
|
|
6884
7076
|
"$loop_sched_en" "$active_start" "$active_end" "$loop_sched_zh" "$active_start" "$active_end"
|
|
6885
|
-
|
|
6886
|
-
|
|
7077
|
+
msg loop.roll_dream_daily_at_02d_02d_2 "$dream_hour" "$dream_minute" "$dream_hour" "$dream_minute"
|
|
7078
|
+
msg loop.roll_brief_daily_at_02d_02d_2 "$brief_hour" "$brief_minute" "$brief_hour" "$brief_minute"
|
|
6887
7079
|
echo " • Agent: ${agent} (change: roll agent use <name>)"
|
|
6888
7080
|
}
|
|
6889
7081
|
|
|
@@ -7023,8 +7215,8 @@ _loop_test() {
|
|
|
7023
7215
|
|
|
7024
7216
|
# FIX-054: terminal preference removed — runner always uses Terminal.app.
|
|
7025
7217
|
local active_start active_end
|
|
7026
|
-
|
|
7027
|
-
|
|
7218
|
+
local _aw; _aw=$(_loop_read_active_window "$project_path")
|
|
7219
|
+
active_start="${_aw%% *}"; active_end="${_aw##* }"
|
|
7028
7220
|
|
|
7029
7221
|
info "$(msg loop.generating_test_runner_agent ${agent})"
|
|
7030
7222
|
_write_loop_runner_script "$test_runner" "$project_path" \
|
|
@@ -9307,8 +9499,8 @@ _loop_monitor() {
|
|
|
9307
9499
|
echo -e "$(msg loop.services ${BOLD} ${NC} ${CYAN} ${agent})"
|
|
9308
9500
|
if [[ "$(uname)" == "Darwin" ]]; then
|
|
9309
9501
|
local active_start active_end dream_hour dream_minute brief_hour brief_minute
|
|
9310
|
-
|
|
9311
|
-
|
|
9502
|
+
local _aw; _aw=$(_loop_read_active_window "$project_path")
|
|
9503
|
+
active_start="${_aw%% *}"; active_end="${_aw##* }"
|
|
9312
9504
|
# US-LOOP-013: use schedule spec for display
|
|
9313
9505
|
local loop_spec loop_period loop_offset
|
|
9314
9506
|
loop_spec=$(_loop_schedule_spec "$project_path")
|
|
@@ -10178,8 +10370,8 @@ _legacy_home() {
|
|
|
10178
10370
|
crontab -l 2>/dev/null | grep -q "${_LOOP_TAG}:${project_path}" && loop_state="enabled"
|
|
10179
10371
|
fi
|
|
10180
10372
|
local active_start active_end dream_hour dream_minute brief_hour brief_minute
|
|
10181
|
-
|
|
10182
|
-
|
|
10373
|
+
local _aw; _aw=$(_loop_read_active_window "$project_path")
|
|
10374
|
+
active_start="${_aw%% *}"; active_end="${_aw##* }"
|
|
10183
10375
|
# US-LOOP-013: use schedule spec for display
|
|
10184
10376
|
local loop_spec loop_period loop_offset
|
|
10185
10377
|
loop_spec=$(_loop_schedule_spec "$project_path")
|
package/lib/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
> **Draft** — auto-generated by roll-doc on 2026-05-28. Review before treating as authoritative.
|
|
2
|
+
|
|
3
|
+
# lib/ — Python helpers and i18n runtime
|
|
4
|
+
|
|
5
|
+
Python scripts and shell libraries that `bin/roll` delegates to for rendering-heavy or data-processing tasks.
|
|
6
|
+
|
|
7
|
+
## Key files
|
|
8
|
+
|
|
9
|
+
| File | Purpose |
|
|
10
|
+
|------|---------|
|
|
11
|
+
| `roll-loop-status.py` | Renders the `roll loop status` health dashboard — reads cycle event NDJSON, computes per-cycle rows, daily rollups, and phase-tracing breakdown |
|
|
12
|
+
| `roll-loop-story.py` | Per-story rollup: aggregates cycles, tokens, cost, and PR outcomes for `roll loop story <ID>` |
|
|
13
|
+
| `roll-status.py` | Renders the `roll status` one-screen sync health view |
|
|
14
|
+
| `roll-init.py` | Init-flow helpers called by `roll init` |
|
|
15
|
+
| `roll-setup.py` | Setup-flow helpers (convention sync, tool config write) |
|
|
16
|
+
| `roll-brief.py` | Brief generation: reads cycle records and produces the feature brief |
|
|
17
|
+
| `roll-backlog.py` | Backlog read/write helpers |
|
|
18
|
+
| `roll-peer.py` | Peer review coordination helpers |
|
|
19
|
+
| `roll-help.py` | Renders `roll --help` output |
|
|
20
|
+
| `roll-plan-validate.py` | Validates plan files before story execution |
|
|
21
|
+
| `model_prices.py` | List-price table for AI model API pricing (per MTok, native currency) |
|
|
22
|
+
| `prices_fetcher.py` | Fetches fresh price snapshots from vendor APIs |
|
|
23
|
+
| `roll_render.py` | Shared rendering utilities (tables, color, formatting) |
|
|
24
|
+
| `loop-fmt.py` | Loop log formatter (ANSI-strip, timestamp alignment) |
|
|
25
|
+
| `loop_unstick.py` | Diagnostic: detects and unsticks hung loop state |
|
|
26
|
+
| `backfill-pi-usage.py` | Backfills pi/deepseek token and cost data into existing cycle records |
|
|
27
|
+
| `changelog_audit.py` | Audits CHANGELOG.md against backlog entries |
|
|
28
|
+
| `i18n.sh` | Shell wrapper that delegates i18n string lookups to `lib/i18n/` |
|
|
29
|
+
| `slides-render.py` | Renders `.deck.md` → HTML slides |
|
|
30
|
+
| `slides-validate.py` | Validates deck file syntax and asset references |
|
|
31
|
+
|
|
32
|
+
## Sub-directories
|
|
33
|
+
|
|
34
|
+
- `agent_usage/` — token-usage capture and cost attribution per agent invocation
|
|
35
|
+
- `i18n/` — localized string tables for all CLI output (EN + ZH)
|
|
36
|
+
- `prices/` — price snapshot JSON files (per-vendor, dated)
|
|
37
|
+
- `slides/` — slide component library for `roll deck`
|
|
38
|
+
|
|
39
|
+
## Dependencies
|
|
40
|
+
|
|
41
|
+
Imported by `bin/roll` via subprocess calls (`python3 lib/<script>.py`).
|
|
42
|
+
No third-party pip dependencies — standard library only (json, sys, os, re, datetime).
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
> **Draft** — auto-generated by roll-doc on 2026-05-28. Review before treating as authoritative.
|
|
2
|
+
|
|
3
|
+
# lib/i18n/ — Localized string tables
|
|
4
|
+
|
|
5
|
+
All user-visible CLI output strings for both `en` and `zh` locales, organized by command domain.
|
|
6
|
+
|
|
7
|
+
## Structure
|
|
8
|
+
|
|
9
|
+
Each `.sh` file under `lib/i18n/` is a shell associative-array fragment exporting a `MSG_*` namespace:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
lib/i18n/
|
|
13
|
+
├── agent.sh # roll agent use / install messages
|
|
14
|
+
├── alert.sh # ALERT lifecycle messages
|
|
15
|
+
├── backlog.sh # backlog read/write output
|
|
16
|
+
├── brief.sh # roll-brief generation output
|
|
17
|
+
├── changelog.sh # changelog sync messages
|
|
18
|
+
├── ci.sh # CI self-heal messages
|
|
19
|
+
├── debug.sh # roll debug diagnostics
|
|
20
|
+
├── doctor.sh # roll-doctor check output
|
|
21
|
+
├── dream.sh # roll-.dream scan output
|
|
22
|
+
├── init.sh # roll init setup messages
|
|
23
|
+
├── lang.sh # locale detection + ROLL_LANG resolution
|
|
24
|
+
├── loop.sh # roll loop subcommand output (largest file)
|
|
25
|
+
├── migrate.sh # roll migrate messages
|
|
26
|
+
├── offboard.sh # roll offboard output
|
|
27
|
+
├── onboard.sh # roll onboard / legacy-onboard output
|
|
28
|
+
├── peer.sh # roll peer review messages
|
|
29
|
+
├── peer_help.sh # peer --help text
|
|
30
|
+
├── peer_reset.sh # peer reset confirmation messages
|
|
31
|
+
├── peer_status.sh # peer status output
|
|
32
|
+
├── prices_refresh.sh # prices refresh output
|
|
33
|
+
└── skills/ # per-skill i18n overrides
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Locale selection
|
|
37
|
+
|
|
38
|
+
`ROLL_LANG` env var controls which locale is active. Resolved by `lang.sh`:
|
|
39
|
+
|
|
40
|
+
1. `ROLL_LANG` explicit → use it
|
|
41
|
+
2. `LC_ALL` / `LANG` contains `zh` → `zh`
|
|
42
|
+
3. Default → `en`
|
|
43
|
+
|
|
44
|
+
Each `.sh` file branches on `ROLL_LANG` and exports the appropriate string set.
|
|
45
|
+
|
|
46
|
+
## skills/
|
|
47
|
+
|
|
48
|
+
Per-skill message overrides for `roll-build`, `roll-design`, `roll-fix`, `roll-loop`, `roll-onboard`. Same structure as top-level files — sourced after the base file to allow skill-specific overrides without editing shared strings.
|
|
49
|
+
|
|
50
|
+
## Adding a new string
|
|
51
|
+
|
|
52
|
+
1. Add the key to both `en` and `zh` branches in the appropriate domain file.
|
|
53
|
+
2. Reference via `msg <KEY>` in `bin/roll` or the relevant skill.
|
|
54
|
+
3. Never hardcode user-facing strings in `bin/roll` directly — always go through i18n.
|
package/lib/i18n/doctor.sh
CHANGED
|
@@ -29,3 +29,16 @@ _i18n_set en doctor.pr_event_without_zh "contributors get AI feedback on PR open
|
|
|
29
29
|
_i18n_set zh doctor.pr_event_without_zh "PR 一开即触发 AI 评审。"
|
|
30
30
|
_i18n_set en doctor.pr_event_secret "Then set the API key secret for your configured agent in GitHub repo settings."
|
|
31
31
|
_i18n_set zh doctor.pr_event_secret "然后在 GitHub 仓库设置中添加你配置的 agent 对应的 API key secret。"
|
|
32
|
+
|
|
33
|
+
_i18n_set en doctor.agent_detection "Agent detection"
|
|
34
|
+
_i18n_set zh doctor.agent_detection "Agent 检测"
|
|
35
|
+
_i18n_set en doctor.agent_installed "CLI on PATH"
|
|
36
|
+
_i18n_set zh doctor.agent_installed "CLI 可用"
|
|
37
|
+
_i18n_set en doctor.agent_missing "CLI not found"
|
|
38
|
+
_i18n_set zh doctor.agent_missing "CLI 未安装"
|
|
39
|
+
_i18n_set en doctor.agent_dir_exists "config dir exists"
|
|
40
|
+
_i18n_set zh doctor.agent_dir_exists "配置目录存在"
|
|
41
|
+
_i18n_set en doctor.agent_dir_missing "config dir missing"
|
|
42
|
+
_i18n_set zh doctor.agent_dir_missing "配置目录不存在"
|
|
43
|
+
_i18n_set en doctor.agent_primary_label "primary"
|
|
44
|
+
_i18n_set zh doctor.agent_primary_label "默认"
|
package/lib/i18n/loop.sh
CHANGED
|
@@ -3,22 +3,22 @@ _i18n_set en loop.loop_already_enabled_for_this_project "Loop already enabled fo
|
|
|
3
3
|
_i18n_set zh loop.loop_already_enabled_for_this_project "当前项目 loop 已启用"
|
|
4
4
|
_i18n_set en loop.loop_enabled "Loop enabled"
|
|
5
5
|
_i18n_set zh loop.loop_enabled "已启用"
|
|
6
|
-
_i18n_set en loop.roll_loop_s_active_02d_00 " • roll-loop %s active %02d:00–%02d:00 %s
|
|
7
|
-
_i18n_set zh loop.roll_loop_s_active_02d_00 "
|
|
8
|
-
_i18n_set en loop.roll_dream_daily_at_02d_02d " • roll-.dream daily at %02d:%02d"
|
|
9
|
-
_i18n_set zh loop.roll_dream_daily_at_02d_02d "每天 %02d:%02d
|
|
10
|
-
_i18n_set en loop.roll_brief_daily_at_02d_02d " • roll-brief daily at %02d:%02d"
|
|
11
|
-
_i18n_set zh loop.roll_brief_daily_at_02d_02d "每天 %02d:%02d
|
|
6
|
+
_i18n_set en loop.roll_loop_s_active_02d_00 " • roll-loop %s active %02d:00–%02d:00 %s(窗口 %02d:00–%02d:00)"
|
|
7
|
+
_i18n_set zh loop.roll_loop_s_active_02d_00 " • roll-loop %s 有效窗口 %02d:00–%02d:00 %s(active %02d:00–%02d:00)"
|
|
8
|
+
_i18n_set en loop.roll_dream_daily_at_02d_02d " • roll-.dream daily at %02d:%02d 每天 %02d:%02d"
|
|
9
|
+
_i18n_set zh loop.roll_dream_daily_at_02d_02d " • roll-.dream daily at %02d:%02d 每天 %02d:%02d"
|
|
10
|
+
_i18n_set en loop.roll_brief_daily_at_02d_02d " • roll-brief daily at %02d:%02d 每天 %02d:%02d"
|
|
11
|
+
_i18n_set zh loop.roll_brief_daily_at_02d_02d " • roll-brief daily at %02d:%02d 每天 %02d:%02d"
|
|
12
12
|
_i18n_set en loop.loop_already_enabled_for_this_project_2 "Loop already enabled for this project"
|
|
13
13
|
_i18n_set zh loop.loop_already_enabled_for_this_project_2 "当前项目 loop 已启用"
|
|
14
14
|
_i18n_set en loop.loop_enabled_2 "Loop enabled"
|
|
15
15
|
_i18n_set zh loop.loop_enabled_2 "已启用"
|
|
16
|
-
_i18n_set en loop.roll_loop_s_active_02d_00_2 " • roll-loop %s active %02d:00–%02d:00 %s
|
|
17
|
-
_i18n_set zh loop.roll_loop_s_active_02d_00_2 "
|
|
18
|
-
_i18n_set en loop.roll_dream_daily_at_02d_02d_2 " • roll-.dream daily at %02d:%02d"
|
|
19
|
-
_i18n_set zh loop.roll_dream_daily_at_02d_02d_2 "每天 %02d:%02d
|
|
20
|
-
_i18n_set en loop.roll_brief_daily_at_02d_02d_2 " • roll-brief daily at %02d:%02d"
|
|
21
|
-
_i18n_set zh loop.roll_brief_daily_at_02d_02d_2 "每天 %02d:%02d
|
|
16
|
+
_i18n_set en loop.roll_loop_s_active_02d_00_2 " • roll-loop %s active %02d:00–%02d:00 %s(窗口 %02d:00–%02d:00)"
|
|
17
|
+
_i18n_set zh loop.roll_loop_s_active_02d_00_2 " • roll-loop %s 有效窗口 %02d:00–%02d:00 %s(active %02d:00–%02d:00)"
|
|
18
|
+
_i18n_set en loop.roll_dream_daily_at_02d_02d_2 " • roll-.dream daily at %02d:%02d 每天 %02d:%02d"
|
|
19
|
+
_i18n_set zh loop.roll_dream_daily_at_02d_02d_2 " • roll-.dream daily at %02d:%02d 每天 %02d:%02d"
|
|
20
|
+
_i18n_set en loop.roll_brief_daily_at_02d_02d_2 " • roll-brief daily at %02d:%02d 每天 %02d:%02d"
|
|
21
|
+
_i18n_set zh loop.roll_brief_daily_at_02d_02d_2 " • roll-brief daily at %02d:%02d 每天 %02d:%02d"
|
|
22
22
|
_i18n_set en loop.loop_not_enabled_for_this_project "Loop not enabled for this project"
|
|
23
23
|
_i18n_set zh loop.loop_not_enabled_for_this_project "当前项目 loop 未启用"
|
|
24
24
|
_i18n_set en loop.loop_disabled "Loop disabled"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
> **Draft** — auto-generated by roll-doc on 2026-05-28. Review before treating as authoritative.
|
|
2
|
+
|
|
3
|
+
# lib/prices/ — Model price snapshots
|
|
4
|
+
|
|
5
|
+
Dated JSON snapshots of AI model list prices, used by `roll loop status` to compute per-cycle cost in both USD and native currency (CNY for pi/DeepSeek/Kimi).
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
| File | Contents |
|
|
10
|
+
|------|---------|
|
|
11
|
+
| `snapshot-2026-05-22.json` | Multi-vendor snapshot (Claude, GPT, Gemini, DeepSeek, Kimi, pi) |
|
|
12
|
+
| `snapshot-2026-05-23-deepseek.json` | DeepSeek-specific refresh |
|
|
13
|
+
| `snapshot-2026-05-23-kimi.json` | Kimi-specific refresh |
|
|
14
|
+
|
|
15
|
+
## Format
|
|
16
|
+
|
|
17
|
+
Each snapshot is a JSON object keyed by model ID:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"claude-opus-4-7": {
|
|
22
|
+
"input_per_mtok": 15.0,
|
|
23
|
+
"output_per_mtok": 75.0,
|
|
24
|
+
"cache_write_per_mtok": 18.75,
|
|
25
|
+
"cache_read_per_mtok": 1.5,
|
|
26
|
+
"currency": "USD"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
CNY-priced models (pi, DeepSeek, Kimi) use `"currency": "CNY"`.
|
|
32
|
+
|
|
33
|
+
## Refresh
|
|
34
|
+
|
|
35
|
+
`prices_fetcher.py` fetches fresh snapshots from vendor pricing APIs and writes a new dated file here. Run via `roll prices refresh`.
|