@seanyao/roll 2026.505.2 → 2026.506.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/bin/roll +120 -11
- package/conventions/templates/cli/CLAUDE.md +3 -5
- package/package.json +1 -1
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.506.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"
|
|
@@ -64,6 +64,29 @@ lower_name() {
|
|
|
64
64
|
echo "$1" | tr '[:upper:]' '[:lower:]'
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
# Detect AI client by walking the process tree (innermost match wins).
|
|
68
|
+
# Returns empty string if no known AI client is found.
|
|
69
|
+
_detect_ai_client() {
|
|
70
|
+
local pid=$$ depth=0 cmd
|
|
71
|
+
while [[ "$pid" -gt 1 ]] && [[ "$depth" -lt 15 ]]; do
|
|
72
|
+
cmd=$(ps -p "$pid" -o comm= 2>/dev/null | tr '[:upper:]' '[:lower:]' || true)
|
|
73
|
+
case "$cmd" in
|
|
74
|
+
*opencode*) echo "opencode"; return ;;
|
|
75
|
+
*claude*) echo "claude code"; return ;;
|
|
76
|
+
*kimi*) echo "kimi cli"; return ;;
|
|
77
|
+
*gemini*) echo "gemini cli"; return ;;
|
|
78
|
+
*codex*) echo "codex"; return ;;
|
|
79
|
+
*cursor*) echo "cursor"; return ;;
|
|
80
|
+
esac
|
|
81
|
+
local ppid
|
|
82
|
+
ppid=$(ps -p "$pid" -o ppid= 2>/dev/null | tr -d ' ' || true)
|
|
83
|
+
[[ "$ppid" == "$pid" ]] && break
|
|
84
|
+
pid=$ppid
|
|
85
|
+
depth=$((depth + 1))
|
|
86
|
+
done
|
|
87
|
+
echo ""
|
|
88
|
+
}
|
|
89
|
+
|
|
67
90
|
# Check if an AI tool is actually installed.
|
|
68
91
|
# Most tools create their own config dir; Trae on macOS uses Library/Application Support
|
|
69
92
|
# and expects roll to manage ~/.trae/ — so we detect via the app directory instead.
|
|
@@ -78,6 +101,10 @@ _is_ai_installed() {
|
|
|
78
101
|
[[ -d "$HOME/.config/Trae" ]]
|
|
79
102
|
return
|
|
80
103
|
;;
|
|
104
|
+
opencode)
|
|
105
|
+
[[ -x "$HOME/.opencode/bin/opencode" ]]
|
|
106
|
+
return
|
|
107
|
+
;;
|
|
81
108
|
esac
|
|
82
109
|
return 1
|
|
83
110
|
}
|
|
@@ -117,6 +144,7 @@ _ensure_config_entries() {
|
|
|
117
144
|
"ai_codex:~/.codex|AGENTS.md|AGENTS.md"
|
|
118
145
|
"ai_cursor:~/.cursor|.cursor-rules|.cursor-rules"
|
|
119
146
|
"ai_trae:~/.trae|user_rules.md|project_rules.md"
|
|
147
|
+
"ai_opencode:~/.config/opencode|AGENTS.md|AGENTS.md"
|
|
120
148
|
"ai_openclaw:~/.openclaw/workspace|AGENTS.md|AGENTS.md"
|
|
121
149
|
)
|
|
122
150
|
|
|
@@ -328,6 +356,7 @@ ai_kimi: ~/.kimi|AGENTS.md|AGENTS.md
|
|
|
328
356
|
ai_codex: ~/.codex|AGENTS.md|AGENTS.md
|
|
329
357
|
ai_cursor: ~/.cursor|.cursor-rules|.cursor-rules
|
|
330
358
|
ai_trae: ~/.trae|user_rules.md|project_rules.md
|
|
359
|
+
ai_opencode: ~/.config/opencode|AGENTS.md|AGENTS.md
|
|
331
360
|
ai_openclaw: ~/.openclaw/workspace|AGENTS.md|AGENTS.md
|
|
332
361
|
|
|
333
362
|
# User preferences
|
|
@@ -572,25 +601,49 @@ _merge_global_to_project() {
|
|
|
572
601
|
|
|
573
602
|
[[ -f "$src" ]] || { warn "Global AGENTS.md not found at ${src/#$HOME/~}"; return; }
|
|
574
603
|
|
|
604
|
+
# Detect project type — controls which sections are included
|
|
605
|
+
local project_type skip_frontend=false
|
|
606
|
+
project_type="$(scan_project_type_from_files "$project_dir")"
|
|
607
|
+
case "$project_type" in
|
|
608
|
+
cli|backend-service|unknown) skip_frontend=true ;;
|
|
609
|
+
esac
|
|
610
|
+
|
|
575
611
|
if [[ ! -f "$dst" ]]; then
|
|
576
|
-
|
|
612
|
+
# Fresh create: write sections filtered by project type
|
|
613
|
+
local fc_h="" fc_b="" fc_pre=true fc_want=true
|
|
614
|
+
while IFS= read -r fc_line; do
|
|
615
|
+
if [[ "$fc_line" =~ ^##\ ]]; then
|
|
616
|
+
if [[ -n "$fc_h" && "$fc_want" == "true" ]]; then
|
|
617
|
+
printf '%s\n%s' "$fc_h" "$fc_b" >> "$dst"
|
|
618
|
+
fi
|
|
619
|
+
fc_h="$fc_line"; fc_b=""; fc_pre=false
|
|
620
|
+
fc_want=true
|
|
621
|
+
[[ "$skip_frontend" == "true" && "$fc_h" == "## 7. Frontend Default Stack" ]] && fc_want=false
|
|
622
|
+
elif [[ "$fc_pre" == "true" ]]; then
|
|
623
|
+
printf '%s\n' "$fc_line" >> "$dst"
|
|
624
|
+
else
|
|
625
|
+
fc_b+="$fc_line"$'\n'
|
|
626
|
+
fi
|
|
627
|
+
done < "$src"
|
|
628
|
+
if [[ -n "$fc_h" && "$fc_want" == "true" ]]; then
|
|
629
|
+
printf '%s\n%s' "$fc_h" "$fc_b" >> "$dst"
|
|
630
|
+
fi
|
|
577
631
|
ok "Created: AGENTS.md"
|
|
578
632
|
_ROLL_MERGE_SUMMARY+=("created|AGENTS.md")
|
|
579
633
|
return
|
|
580
634
|
fi
|
|
581
635
|
|
|
582
|
-
if diff -q "$src" "$dst" &>/dev/null; then
|
|
583
|
-
_ROLL_MERGE_SUMMARY+=("unchanged|AGENTS.md")
|
|
584
|
-
return
|
|
585
|
-
fi
|
|
586
|
-
|
|
587
636
|
# Section-merge: append any ## sections from global missing in project
|
|
588
637
|
local added=0 cur_h="" cur_b=""
|
|
589
638
|
while IFS= read -r line; do
|
|
590
639
|
if [[ "$line" =~ ^##\ ]]; then
|
|
591
640
|
if [[ -n "$cur_h" ]] && ! grep -qF "$cur_h" "$dst" 2>/dev/null; then
|
|
592
|
-
|
|
593
|
-
|
|
641
|
+
local skip_sec=false
|
|
642
|
+
[[ "$skip_frontend" == "true" && "$cur_h" == "## 7. Frontend Default Stack" ]] && skip_sec=true
|
|
643
|
+
if [[ "$skip_sec" == "false" ]]; then
|
|
644
|
+
printf '\n%s\n%s' "$cur_h" "$cur_b" >> "$dst"
|
|
645
|
+
added=$((added + 1))
|
|
646
|
+
fi
|
|
594
647
|
fi
|
|
595
648
|
cur_h="$line"; cur_b=""
|
|
596
649
|
elif [[ -n "$cur_h" ]]; then
|
|
@@ -598,8 +651,12 @@ _merge_global_to_project() {
|
|
|
598
651
|
fi
|
|
599
652
|
done < "$src"
|
|
600
653
|
if [[ -n "$cur_h" ]] && ! grep -qF "$cur_h" "$dst" 2>/dev/null; then
|
|
601
|
-
|
|
602
|
-
|
|
654
|
+
local skip_sec=false
|
|
655
|
+
[[ "$skip_frontend" == "true" && "$cur_h" == "## 7. Frontend Default Stack" ]] && skip_sec=true
|
|
656
|
+
if [[ "$skip_sec" == "false" ]]; then
|
|
657
|
+
printf '\n%s\n%s' "$cur_h" "$cur_b" >> "$dst"
|
|
658
|
+
added=$((added + 1))
|
|
659
|
+
fi
|
|
603
660
|
fi
|
|
604
661
|
|
|
605
662
|
if [[ $added -gt 0 ]]; then
|
|
@@ -610,6 +667,52 @@ _merge_global_to_project() {
|
|
|
610
667
|
fi
|
|
611
668
|
}
|
|
612
669
|
|
|
670
|
+
_merge_claude_to_project() {
|
|
671
|
+
local project_dir="$1"
|
|
672
|
+
local project_type
|
|
673
|
+
project_type="$(scan_project_type_from_files "$project_dir")"
|
|
674
|
+
|
|
675
|
+
local tpl_file="$ROLL_TEMPLATES/$project_type/CLAUDE.md"
|
|
676
|
+
[[ -f "$tpl_file" ]] || return 0 # No template for this project type
|
|
677
|
+
|
|
678
|
+
local claude_dir="$project_dir/.claude"
|
|
679
|
+
local out_file="$claude_dir/CLAUDE.md"
|
|
680
|
+
|
|
681
|
+
mkdir -p "$claude_dir"
|
|
682
|
+
|
|
683
|
+
if [[ ! -f "$out_file" ]]; then
|
|
684
|
+
cp "$tpl_file" "$out_file"
|
|
685
|
+
ok "Created: .claude/CLAUDE.md"
|
|
686
|
+
_ROLL_MERGE_SUMMARY+=("created|.claude/CLAUDE.md")
|
|
687
|
+
return
|
|
688
|
+
fi
|
|
689
|
+
|
|
690
|
+
# Append any ## sections from template missing in project file
|
|
691
|
+
local added=0 cur_h="" cur_b=""
|
|
692
|
+
while IFS= read -r line; do
|
|
693
|
+
if [[ "$line" =~ ^##\ ]]; then
|
|
694
|
+
if [[ -n "$cur_h" ]] && ! grep -qF "$cur_h" "$out_file" 2>/dev/null; then
|
|
695
|
+
printf '\n%s\n%s' "$cur_h" "$cur_b" >> "$out_file"
|
|
696
|
+
added=$((added + 1))
|
|
697
|
+
fi
|
|
698
|
+
cur_h="$line"; cur_b=""
|
|
699
|
+
elif [[ -n "$cur_h" ]]; then
|
|
700
|
+
cur_b+="$line"$'\n'
|
|
701
|
+
fi
|
|
702
|
+
done < "$tpl_file"
|
|
703
|
+
if [[ -n "$cur_h" ]] && ! grep -qF "$cur_h" "$out_file" 2>/dev/null; then
|
|
704
|
+
printf '\n%s\n%s' "$cur_h" "$cur_b" >> "$out_file"
|
|
705
|
+
added=$((added + 1))
|
|
706
|
+
fi
|
|
707
|
+
|
|
708
|
+
if [[ $added -gt 0 ]]; then
|
|
709
|
+
ok "Merged: .claude/CLAUDE.md ($added new sections)"
|
|
710
|
+
_ROLL_MERGE_SUMMARY+=("merged|.claude/CLAUDE.md")
|
|
711
|
+
else
|
|
712
|
+
_ROLL_MERGE_SUMMARY+=("unchanged|.claude/CLAUDE.md")
|
|
713
|
+
fi
|
|
714
|
+
}
|
|
715
|
+
|
|
613
716
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
614
717
|
# COMMAND: init
|
|
615
718
|
# Initialize or re-merge a project. Always operates on the current directory.
|
|
@@ -633,9 +736,14 @@ cmd_init() {
|
|
|
633
736
|
fi
|
|
634
737
|
|
|
635
738
|
_merge_global_to_project "$project_dir"
|
|
739
|
+
_merge_claude_to_project "$project_dir"
|
|
636
740
|
_write_backlog "$project_dir/BACKLOG.md"
|
|
637
741
|
_ensure_features_dir "$project_dir/docs/features"
|
|
638
742
|
print_merge_summary
|
|
743
|
+
|
|
744
|
+
echo ""
|
|
745
|
+
info "Syncing conventions to AI tools... 正在同步约定到 AI 工具..."
|
|
746
|
+
_sync_conventions
|
|
639
747
|
echo ""
|
|
640
748
|
|
|
641
749
|
if [[ "$has_agents" == "true" ]]; then
|
|
@@ -645,6 +753,7 @@ cmd_init() {
|
|
|
645
753
|
fi
|
|
646
754
|
}
|
|
647
755
|
|
|
756
|
+
|
|
648
757
|
# ─── Helper: merge global preamble + template into output ────────────────────
|
|
649
758
|
merge_convention() {
|
|
650
759
|
local filename="$1"
|
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
## Stack
|
|
6
6
|
|
|
7
|
-
- Node.js / TypeScript
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
- Distribution: npm package with bin entry
|
|
7
|
+
- Runtime / Language: {e.g. Node.js / TypeScript, Go, Python, Bash}
|
|
8
|
+
- Test framework: {e.g. Vitest, pytest, bats}
|
|
9
|
+
- Distribution: {e.g. npm, Homebrew, binary release}
|
|
11
10
|
|
|
12
11
|
## Claude Code Notes
|
|
13
12
|
|
|
@@ -15,4 +14,3 @@
|
|
|
15
14
|
- Test commands by running them in Bash, not just unit tests.
|
|
16
15
|
- Use `$roll-design` to plan command structure and options before implementation.
|
|
17
16
|
- Verify `--help` output is clear and complete for each command.
|
|
18
|
-
- Run `npm run build && node dist/index.js --help` before pushing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanyao/roll",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.506.1",
|
|
4
4
|
"description": "Roll — Roll out features with AI agents",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "find tests/unit tests/integration -name '*.bats' | sort | xargs ./tests/helpers/bats-core/bin/bats"
|