@seanyao/roll 2026.514.3 → 2026.514.5

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## v2026.514.5
4
+
5
+ - **Fixed**: 上版 `claude/*` 临时分支清理意外失效 — 现已恢复 `[loop]`
6
+ - **Fixed**: loop session 结束后本地 worktree 不再积累,`git worktree list` 保持干净 `[loop]`
7
+ - **Fixed**: 发版脚本不再维护独立的 agent 检测逻辑,配置变更时两处不再悄悄漂移
8
+
3
9
  ## v2026.514.3
4
10
 
5
11
  ### 约定与导航
package/README.md CHANGED
@@ -62,6 +62,7 @@ roll loop on # optional: let the agent work unattended
62
62
  | Loop (autonomous executor) | [guide/en/loop.md](docs/guide/en/loop.md) | [guide/zh/loop.md](docs/guide/zh/loop.md) |
63
63
  | Dream (nightly health scan) | [guide/en/dream.md](docs/guide/en/dream.md) | [guide/zh/dream.md](docs/guide/zh/dream.md) |
64
64
  | Peer (cross-agent review) | [guide/en/peer.md](docs/guide/en/peer.md) | [guide/zh/peer.md](docs/guide/zh/peer.md) |
65
+ | Configuration (env vars) | [guide/en/configuration.md](docs/guide/en/configuration.md) | [guide/zh/configuration.md](docs/guide/zh/configuration.md) |
65
66
  | Skill selection guide | [guide/en/skills.md](docs/guide/en/skills.md) | [guide/zh/skills.md](docs/guide/zh/skills.md) |
66
67
  | Domain model (DDD) | [domain/context-map.md](docs/domain/context-map.md) | — |
67
68
  | Engineering common sense | [practices/engineering-common-sense.md](docs/practices/engineering-common-sense.md) | — |
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.514.3"
7
+ VERSION="2026.514.5"
8
8
  ROLL_HOME="${ROLL_HOME:-${HOME}/.roll}"
9
9
  ROLL_CONFIG="${ROLL_HOME}/config.yaml"
10
10
  ROLL_GLOBAL="${ROLL_HOME}/conventions/global"
@@ -465,7 +465,9 @@ _link_skills() {
465
465
  local current_target
466
466
  current_target="$(readlink "$skill_link")"
467
467
  if [[ "$current_target" != "$skill_dir" ]]; then
468
- ln -sf "$skill_dir" "$skill_link"
468
+ # macOS ln -sf follows symlinks-to-dirs and creates inside instead of
469
+ # replacing — explicitly remove first to guarantee replacement.
470
+ rm -f "$skill_link" && ln -s "$skill_dir" "$skill_link"
469
471
  repaired=$((repaired + 1))
470
472
  fi
471
473
  # correct symlink: skip silently
@@ -2123,6 +2125,9 @@ WT="\$(_worktree_path "${slug}" "cycle-\${CYCLE_ID}")"
2123
2125
  BRANCH="loop/cycle-\${CYCLE_ID}"
2124
2126
  _USE_WORKTREE=0
2125
2127
  cd "${project_path}" 2>/dev/null || true
2128
+ # US-AUTO-038: snapshot orphan claude/* branches before claude runs so the
2129
+ # post-claude cleanup can diff and delete only this session's additions.
2130
+ CLAUDE_BRANCH_SNAPSHOT="\$(_claude_remote_snapshot "${project_path}")"
2126
2131
  if _worktree_fetch_origin main \\
2127
2132
  && _worktree_create "\$WT" "\$BRANCH" "origin/main"; then
2128
2133
  _USE_WORKTREE=1
@@ -2148,6 +2153,14 @@ for _attempt in 1 2 3; do
2148
2153
  fi
2149
2154
  done
2150
2155
 
2156
+ # US-AUTO-038: diff snapshot vs current and delete any claude/* branches this
2157
+ # session pushed to origin. Runs regardless of claude's exit code (cleanup is
2158
+ # orthogonal to success/failure) and is silent on non-GitHub / unreachable.
2159
+ _claude_cleanup_new_branches "\$CLAUDE_BRANCH_SNAPSHOT" "${project_path}" || true
2160
+ # REFACTOR-011: also prune local .claude/worktrees/ entries whose branch has
2161
+ # been merged to main (remote-branch cleanup above doesn't touch local worktrees).
2162
+ _claude_cleanup_stale_worktrees "${project_path}" || true
2163
+
2151
2164
  # Post-claude: publish cycle branch. Doc-only changes (BACKLOG/docs) merge
2152
2165
  # immediately via --admin; code changes use auto-merge (CI gate required).
2153
2166
  # When \`gh\` is unavailable, fall back to the legacy ff-merge path.
@@ -3633,6 +3646,75 @@ _worktree_merge_back() {
3633
3646
  return 0
3634
3647
  }
3635
3648
 
3649
+ # _claude_remote_snapshot [repo]
3650
+ # Echo the current set of remote `claude/*` branch names (sans
3651
+ # refs/heads/), one per line, sorted. Silent on remote unreachable / no
3652
+ # remote / no matches — empty stdout, exit 0.
3653
+ _claude_remote_snapshot() {
3654
+ local repo="${1:-.}"
3655
+ git -C "$repo" ls-remote --heads origin 'refs/heads/claude/*' 2>/dev/null \
3656
+ | awk '{print $2}' \
3657
+ | sed 's|^refs/heads/||' \
3658
+ | sort
3659
+ }
3660
+
3661
+ # _claude_cleanup_new_branches <prior> [repo]
3662
+ # Delete remote `claude/*` branches present now but absent from <prior>
3663
+ # (newline-separated list, as emitted by _claude_remote_snapshot). Skips
3664
+ # silently when origin is not a GitHub remote. Each successful delete logs
3665
+ # one INFO line; failures are silently ignored so the loop's main flow is
3666
+ # never derailed.
3667
+ _claude_cleanup_new_branches() {
3668
+ local prior="$1"
3669
+ local repo="${2:-.}"
3670
+ local url; url=$(git -C "$repo" remote get-url origin 2>/dev/null)
3671
+ [[ "$url" == *github.com* ]] || return 0
3672
+ local current; current=$(_claude_remote_snapshot "$repo")
3673
+ [ -z "$current" ] && return 0
3674
+ local prior_sorted; prior_sorted=$(printf '%s\n' "$prior" | sort -u)
3675
+ local new_branches
3676
+ new_branches=$(comm -13 <(printf '%s\n' "$prior_sorted") <(printf '%s\n' "$current"))
3677
+ [ -z "$new_branches" ] && return 0
3678
+ while IFS= read -r branch; do
3679
+ [ -z "$branch" ] && continue
3680
+ if git -C "$repo" push origin --delete "$branch" 2>/dev/null; then
3681
+ echo "[loop] deleted stale claude branch: $branch"
3682
+ fi
3683
+ done <<< "$new_branches"
3684
+ return 0
3685
+ }
3686
+
3687
+ # _claude_cleanup_stale_worktrees [project_path]
3688
+ # Remove local worktrees under <project_path>/.claude/worktrees/ whose
3689
+ # branch has been fully merged into main (merge-base --is-ancestor). Active
3690
+ # worktrees (branch ahead of main) are preserved. Runs `git worktree prune`
3691
+ # afterwards to clear stale metadata. Silent on missing directory or any
3692
+ # individual failure so the loop's main flow is never derailed.
3693
+ _claude_cleanup_stale_worktrees() {
3694
+ local project_path="${1:-.}"
3695
+ local wt_dir="${project_path}/.claude/worktrees"
3696
+ [ -d "$wt_dir" ] || return 0
3697
+ local entry branch
3698
+ for entry in "$wt_dir"/*/; do
3699
+ [ -d "$entry" ] || continue
3700
+ branch=$(git -C "$project_path" worktree list --porcelain 2>/dev/null \
3701
+ | awk -v p="${entry%/}" '
3702
+ /^worktree / { cur=$2; flag=(cur==p) }
3703
+ /^branch / && flag { sub(/^refs\/heads\//, "", $2); print $2; flag=0 }
3704
+ ')
3705
+ [ -z "$branch" ] && branch=$(git -C "$entry" symbolic-ref --short HEAD 2>/dev/null)
3706
+ [ -z "$branch" ] && continue
3707
+ if git -C "$project_path" merge-base --is-ancestor "$branch" main 2>/dev/null; then
3708
+ git -C "$project_path" worktree remove --force "$entry" 2>/dev/null || true
3709
+ rm -rf "$entry" 2>/dev/null || true
3710
+ git -C "$project_path" branch -D "$branch" 2>/dev/null || true
3711
+ echo "[loop] removed stale worktree: $branch"
3712
+ fi
3713
+ done
3714
+ git -C "$project_path" worktree prune 2>/dev/null || true
3715
+ return 0
3716
+ }
3717
+
3636
3718
  # US-AUTO-033: publish a loop cycle branch as a GitHub PR with auto-merge.
3637
3719
  #
3638
3720
  # _loop_publish_pr <branch> [title]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanyao/roll",
3
- "version": "2026.514.3",
3
+ "version": "2026.514.5",
4
4
  "description": "Roll — Roll out features with AI agents",
5
5
  "scripts": {
6
6
  "test": "bash tests/run.sh"