@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 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.505.2"
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
- cp "$src" "$dst"
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
- printf '\n%s\n%s' "$cur_h" "$cur_b" >> "$dst"
593
- added=$((added + 1))
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
- printf '\n%s\n%s' "$cur_h" "$cur_b" >> "$dst"
602
- added=$((added + 1))
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
- - CLI framework: commander or citty
9
- - Testing: Vitest + execa (CLI integration tests)
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.505.2",
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"