claude-plugin-viban 1.0.34 → 1.0.36

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viban",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "Terminal Kanban TUI for AI-human collaborative issue tracking",
5
5
  "author": {
6
6
  "name": "happy-nut"
package/README.md CHANGED
@@ -50,6 +50,7 @@ This separation keeps your workflow clean and prevents context switching.
50
50
  ## Requirements
51
51
 
52
52
  - zsh
53
+ - python3 (macOS/Linux built-in)
53
54
  - [gum](https://github.com/charmbracelet/gum)
54
55
  - [jq](https://jqlang.github.io/jq/)
55
56
 
@@ -289,7 +290,8 @@ claude-plugin-viban/
289
290
  ├── docs/
290
291
  │ └── CLAUDE.md # Claude Code integration guide
291
292
  ├── scripts/
292
- └── check-deps.sh # Dependency checker
293
+ ├── check-deps.sh # Dependency checker
294
+ │ └── tui_coprocess.py # Persistent Python coprocess for TUI rendering
293
295
  ├── skills/
294
296
  │ ├── assign/SKILL.md # /viban:assign skill
295
297
  │ ├── setup/SKILL.md # /viban:setup skill
package/bin/viban CHANGED
@@ -119,6 +119,9 @@ esac
119
119
 
120
120
  IN_TUI=false
121
121
  cleanup() {
122
+ # Skip cleanup in subshells — EXIT trap fires in $() command substitutions
123
+ [[ ${ZSH_SUBSHELL:-0} -gt 0 ]] && return
124
+ _stop_coproc
122
125
  printf '\033[?25h\033[0m'
123
126
  stty echo 2>/dev/null
124
127
  $IN_TUI && clear
@@ -126,6 +129,60 @@ cleanup() {
126
129
  }
127
130
  trap cleanup INT TERM EXIT
128
131
 
132
+ # Python coprocess for TUI rendering (eliminates per-frame spawn overhead)
133
+ # Uses explicit file descriptors (fd 7/8) to avoid interfering with read -sk1
134
+ _COPROC_PID=""
135
+ _COPROC_RESULT=""
136
+
137
+ _start_coproc() {
138
+ local _in_fifo _out_fifo
139
+ _in_fifo=$(mktemp -u /tmp/viban_cp_in.XXXXXX)
140
+ _out_fifo=$(mktemp -u /tmp/viban_cp_out.XXXXXX)
141
+ mkfifo "$_in_fifo" "$_out_fifo"
142
+ python3 "$VIBAN_SCRIPT_DIR/scripts/tui_coprocess.py" < "$_in_fifo" > "$_out_fifo" &
143
+ _COPROC_PID=$!
144
+ exec 7>"$_in_fifo" 8<"$_out_fifo"
145
+ rm -f "$_in_fifo" "$_out_fifo"
146
+ }
147
+
148
+ _stop_coproc() {
149
+ if [[ -n "$_COPROC_PID" ]] && kill -0 "$_COPROC_PID" 2>/dev/null; then
150
+ echo "QUIT" >&7 2>/dev/null
151
+ exec 7>&- 2>/dev/null
152
+ wait "$_COPROC_PID" 2>/dev/null
153
+ else
154
+ exec 7>&- 2>/dev/null
155
+ fi
156
+ exec 8<&- 2>/dev/null
157
+ _COPROC_PID=""
158
+ }
159
+
160
+ _coproc_batch_trunc() {
161
+ echo "BATCH_TRUNC" >&7
162
+ echo "$1" >&7
163
+ echo "END" >&7
164
+ _COPROC_RESULT=""
165
+ local line
166
+ while read -r line <&8; do
167
+ [[ "$line" == "END" ]] && break
168
+ [[ -n "$_COPROC_RESULT" ]] && _COPROC_RESULT+=$'\n'
169
+ _COPROC_RESULT+="$line"
170
+ done
171
+ }
172
+
173
+ _coproc_batch_width() {
174
+ echo "BATCH_WIDTH" >&7
175
+ echo "$1" >&7
176
+ echo "END" >&7
177
+ _COPROC_RESULT=""
178
+ local line
179
+ while read -r line <&8; do
180
+ [[ "$line" == "END" ]] && break
181
+ [[ -n "$_COPROC_RESULT" ]] && _COPROC_RESULT+=$'\n'
182
+ _COPROC_RESULT+="$line"
183
+ done
184
+ }
185
+
129
186
  # Prevent gum from querying terminal colors
130
187
  export CLICOLOR_FORCE=1
131
188
  export COLORTERM=truecolor
@@ -449,48 +506,6 @@ get_term_height() {
449
506
  fi
450
507
  }
451
508
 
452
- # Get display width of string using Unicode East Asian Width property
453
- # F(Fullwidth) and W(Wide) = 2 columns, others = 1 column
454
- str_width() {
455
- local str="$1"
456
- local char_count=${#str}
457
- local byte_count
458
- LC_ALL=C byte_count=${#str}
459
-
460
- # If all ASCII, simple calculation (fast path)
461
- [[ $byte_count -eq $char_count ]] && { echo $char_count; return; }
462
-
463
- # Use Python for accurate Unicode width calculation
464
- # Note: <<< adds trailing newline, so we strip it with rstrip()
465
- python3 -c "
466
- import unicodedata, sys
467
- s = sys.stdin.read().rstrip('\n')
468
- print(sum(2 if unicodedata.east_asian_width(c) in 'FW' else 1 for c in s))
469
- " <<< "$str"
470
- }
471
-
472
- # Truncate string to max display width (optimized)
473
- # Uses str_width for width calculation to ensure correct byte counting
474
- truncate_str() {
475
- local str="$1" max=$2
476
- local len=${#str}
477
- local w=$(str_width "$str")
478
- # If already fits, return as-is
479
- (( w <= max )) && { echo "$str"; return; }
480
- # Binary search for truncation point
481
- local lo=0 hi=$len mid sub_str
482
- while (( lo < hi )); do
483
- mid=$(( (lo + hi + 1) / 2 ))
484
- sub_str="${str:0:$mid}"
485
- w=$(str_width "$sub_str")
486
- if (( w <= max )); then
487
- lo=$mid
488
- else
489
- hi=$((mid - 1))
490
- fi
491
- done
492
- echo "${str:0:$lo}"
493
- }
494
509
 
495
510
  # ANSI color codes - Orange Theme
496
511
  A_RESET="\033[0m"
@@ -607,64 +622,120 @@ build_column_lines() {
607
622
  local card_inner=$((col_w - 4))
608
623
  local border=$(gen_border $card_inner)
609
624
 
610
- # Task cards (5 lines: top border, title, desc/priority, empty, bottom border)
611
- local shown=0
612
- while IFS=$'\t' read -r id title desc priority issue_type; do
613
- [[ -z "$id" ]] && continue
614
- (( shown >= CACHED_MAX_TASKS )) && {
615
- local more_text=" +$((count - shown)) more..."
616
- local more_w=${#more_text}
617
- printf "${A_DIM}%s${A_RESET}%$((col_w - more_w))s\n" "$more_text" ""
618
- ((lines_used++))
619
- break
620
- }
625
+ # --- Pass 1: Collect card data into arrays ---
626
+ local -a _ids _titles _descs _priorities _types _title_max_ws _title_pfxs
627
+ local _has_nonascii=0
628
+ local _desc_max_w=$((card_inner - 4))
629
+ local _spinner_w=0
630
+ [[ "$st" == "in_progress" ]] && _spinner_w=2
631
+ local _cc _bc _pfx
632
+
633
+ while IFS=$'\t' read -r _id _title _desc _priority _type; do
634
+ [[ -z "$_id" ]] && continue
635
+ (( ${#_ids} >= CACHED_MAX_TASKS )) && break
636
+ [[ -z "$_priority" || "$_priority" == "null" ]] && _priority="P3"
637
+ [[ -z "$_type" || "$_type" == "null" ]] && _type=""
638
+ [[ "$_desc" == "null" ]] && _desc=""
639
+
640
+ _ids+=("$_id"); _titles+=("$_title"); _descs+=("$_desc")
641
+ _priorities+=("$_priority"); _types+=("$_type")
642
+
643
+ # Per-card title width limit
644
+ _title_max_ws+=($((card_inner - 4 - ${#_id} - _spinner_w - 1)))
645
+ # Prefix for width calc (X as spinner placeholder - same width 1 as braille chars)
646
+ _pfx=" "
647
+ (( _spinner_w )) && _pfx=" X "
648
+ _title_pfxs+=("${_pfx}#${_id} ")
649
+
650
+ # Check for non-ASCII
651
+ if (( ! _has_nonascii )); then
652
+ _cc=${#_title}
653
+ LC_ALL=C _bc=${#_title}; unset LC_ALL
654
+ (( _bc != _cc )) && _has_nonascii=1
655
+ if (( ! _has_nonascii && ${#_desc} > 0 )); then
656
+ _cc=${#_desc}; LC_ALL=C _bc=${#_desc}; unset LC_ALL
657
+ (( _bc != _cc )) && _has_nonascii=1
658
+ fi
659
+ fi
660
+ done <<< "$issues_data"
621
661
 
622
- # Default priority and type if not set
623
- [[ -z "$priority" || "$priority" == "null" ]] && priority="P3"
624
- [[ -z "$issue_type" || "$issue_type" == "null" ]] && issue_type=""
662
+ local _n=${#_ids}
625
663
 
626
- # Title line (with spinner for in_progress)
627
- local spinner_prefix=""
628
- local spinner_w=0
629
- if [[ "$st" == "in_progress" ]]; then
630
- spinner_prefix="${SPINNER_FRAMES[$((SPINNER_IDX % ${#SPINNER_FRAMES[@]} + 1))]} "
631
- spinner_w=2 # char(1) + space(1)
664
+ # --- Pass 2: Batch compute truncation + widths (single Python call) ---
665
+ local -a _short_titles _title_cws _short_descs _desc_cws
666
+
667
+ if (( _n > 0 )); then
668
+ if (( _has_nonascii )); then
669
+ # Build batch input: 2 lines per card (title, desc)
670
+ # Format: max_w<TAB>prefix<TAB>string
671
+ local _batch_input=""
672
+ for (( _i=1; _i<=_n; _i++ )); do
673
+ _batch_input+="${_title_max_ws[$_i]}"$'\t'"${_title_pfxs[$_i]}"$'\t'"${_titles[$_i]}"$'\n'
674
+ _batch_input+="${_desc_max_w}"$'\t'" "$'\t'"${_descs[$_i]}"$'\n'
675
+ done
676
+
677
+ # Single Python call: truncate each string and compute content width
678
+ local _batch_output
679
+ _coproc_batch_trunc "$_batch_input"
680
+ _batch_output="$_COPROC_RESULT"
681
+
682
+ local _li=0
683
+ while IFS=$'\t' read -r _tr _cw; do
684
+ ((_li++))
685
+ if (( _li % 2 == 1 )); then
686
+ _short_titles+=("$_tr"); _title_cws+=($_cw)
687
+ else
688
+ _short_descs+=("$_tr"); _desc_cws+=($_cw)
689
+ fi
690
+ done <<< "$_batch_output"
691
+ else
692
+ # All-ASCII fast path - no Python needed
693
+ for (( _i=1; _i<=_n; _i++ )); do
694
+ local _t="${_titles[$_i]}" _mw=${_title_max_ws[$_i]}
695
+ (( ${#_t} > _mw )) && _t="${_t:0:$_mw}"
696
+ _short_titles+=("$_t")
697
+ local _fc="${_title_pfxs[$_i]}${_t}"
698
+ _title_cws+=(${#_fc})
699
+
700
+ local _d="${_descs[$_i]}"
701
+ (( ${#_d} > _desc_max_w )) && _d="${_d:0:$_desc_max_w}"
702
+ _short_descs+=("$_d")
703
+ _desc_cws+=($((2 + ${#_d})))
704
+ done
632
705
  fi
633
- local prefix_w=$((4 + ${#id} + spinner_w)) # " #" + id_digits + " " + spinner
634
- local title_w=$((card_inner - prefix_w - 1)) # -1 right margin for width safety
635
- local short=$(truncate_str "$title" $title_w)
636
- local title_content=" ${spinner_prefix}#$id $short"
637
- local title_content_w=$(str_width "$title_content")
638
- local title_pad=$((card_inner - title_content_w))
706
+ fi
707
+
708
+ # --- Pass 3: Render cards ---
709
+ local shown=0
710
+ for (( _i=1; _i<=_n; _i++ )); do
711
+ local id="${_ids[$_i]}"
712
+ local priority="${_priorities[$_i]}"
713
+ local issue_type="${_types[$_i]}"
714
+
715
+ # Title line
716
+ local spinner_prefix=""
717
+ [[ "$st" == "in_progress" ]] && spinner_prefix="${SPINNER_FRAMES[$((SPINNER_IDX % ${#SPINNER_FRAMES[@]} + 1))]} "
718
+ local title_content=" ${spinner_prefix}#${id} ${_short_titles[$_i]}"
719
+ local title_pad=$((card_inner - ${_title_cws[$_i]}))
639
720
  (( title_pad < 0 )) && title_pad=0
640
721
 
641
- # Description line (dimmed, truncated)
642
- local desc_w=$((card_inner - 4))
643
- local desc_short=""
644
- if [[ -n "$desc" && "$desc" != "null" ]]; then
645
- desc_short=$(truncate_str "$desc" $desc_w)
646
- fi
647
- local desc_content=" $desc_short"
648
- local desc_content_w=$(str_width "$desc_content")
649
- local desc_pad=$((card_inner - desc_content_w))
722
+ # Description line
723
+ local desc_content=" ${_short_descs[$_i]}"
724
+ local desc_pad=$((card_inner - ${_desc_cws[$_i]}))
650
725
  (( desc_pad < 0 )) && desc_pad=0
651
726
 
652
- # Priority and type tags on same line (e.g., [P0] [BUG])
727
+ # Priority and type tags
653
728
  local priority_tag="[$priority]"
654
729
  local priority_color="${PRIORITY_COLOR[$priority]:-$A_DIM}"
655
- local type_tag=""
656
- local type_color=""
657
- local tags_content=""
658
- local tags_w=0
730
+ local type_tag="" type_color="" tags_w=0
659
731
  if [[ -n "$issue_type" ]]; then
660
732
  type_tag="[${TYPE_LABEL[$issue_type]:-$issue_type}]"
661
733
  type_color="${TYPE_COLOR[$issue_type]:-$A_DIM}"
662
- # Calculate total width: " [P1] [BUG]"
663
734
  tags_w=$((${#priority_tag} + 1 + ${#type_tag}))
664
735
  else
665
736
  tags_w=${#priority_tag}
666
737
  fi
667
- local tags_pad=$((card_inner - tags_w - 2)) # -2 for leading spaces
738
+ local tags_pad=$((card_inner - tags_w - 2))
668
739
 
669
740
  local border_color="$A_DIM"
670
741
  local text_color="$A_FG"
@@ -688,7 +759,14 @@ build_column_lines() {
688
759
 
689
760
  ((shown++))
690
761
  lines_used=$((lines_used + 5))
691
- done <<< "$issues_data"
762
+ done
763
+
764
+ # Overflow indicator
765
+ if (( count > _n )); then
766
+ local more_text=" +$((count - _n)) more..."
767
+ printf "${A_DIM}%s${A_RESET}%$((col_w - ${#more_text}))s\n" "$more_text" ""
768
+ ((lines_used++))
769
+ fi
692
770
 
693
771
  if (( count == 0 )); then
694
772
  local no_text=" No tasks"
@@ -785,11 +863,8 @@ draw_board() {
785
863
  fi
786
864
  done
787
865
  local -a _py_results
788
- _py_results=("${(@f)$(python3 -c "
789
- import unicodedata,sys
790
- for line in sys.stdin.read().rstrip('\n').split('\n'):
791
- print(sum(2 if unicodedata.east_asian_width(c) in 'FW' else 1 for c in line))
792
- " <<< "$_input")}")
866
+ _coproc_batch_width "$_input"
867
+ _py_results=("${(@f)_COPROC_RESULT}")
793
868
  # Map Python results back to width array
794
869
  local _pi=1
795
870
  for ((_idx=1; _idx<=${#all_widths[@]}; _idx++)); do
@@ -821,9 +896,10 @@ draw_footer() {
821
896
  }
822
897
 
823
898
  read_key() {
899
+ local _rk_timeout="${1:-0.5}"
824
900
  local key result=""
825
- # Timeout for spinner animation refresh (0.5s to prevent key drops)
826
- read -sk1 -t 0.5 key 2>/dev/null || { echo "timeout"; return; }
901
+ # Timeout for spinner animation refresh (default 0.5s)
902
+ read -sk1 -t "$_rk_timeout" key 2>/dev/null || { echo "timeout"; return; }
827
903
 
828
904
  if [[ "$key" == $'\e' ]]; then
829
905
  read -sk1 -t 0.1 c2 2>/dev/null
@@ -967,6 +1043,7 @@ delete_issue() {
967
1043
 
968
1044
  level1_columns() {
969
1045
  IN_TUI=true
1046
+ _start_coproc
970
1047
  local col=0 card=0
971
1048
 
972
1049
  # Hide cursor and disable input echo
@@ -204,6 +204,16 @@ After approval: Delete issue from viban TUI
204
204
 
205
205
  ---
206
206
 
207
+ ## CRITICAL: Status Transition Rule
208
+
209
+ > **NEVER end this command with the issue still in `in_progress`.**
210
+ >
211
+ > Before exiting — whether you completed all phases or stopped early due to errors:
212
+ > ```bash
213
+ > viban review $ISSUE_ID
214
+ > ```
215
+ > This is MANDATORY. If you skip this, the board becomes stale and misleading.
216
+
207
217
  ## CLI Reference
208
218
 
209
219
  | Command | Description |
package/commands/setup.md CHANGED
@@ -35,9 +35,10 @@ Check which dependencies are already installed:
35
35
 
36
36
  ```bash
37
37
  command -v zsh &> /dev/null && echo "✓ zsh" || echo "✗ zsh"
38
+ command -v python3 &> /dev/null && echo "✓ python3" || echo "✗ python3"
38
39
  command -v gum &> /dev/null && echo "✓ gum" || echo "✗ gum"
39
40
  command -v jq &> /dev/null && echo "✓ jq" || echo "✗ jq"
40
- command -v viban &> /dev/null && echo "✓ viban" || echo "✗ viban"
41
+ command -v viban &> /dev/null && echo "✓ viban ($(viban --version 2>/dev/null || echo 'not installed'))" || echo "✗ viban"
41
42
  ```
42
43
 
43
44
  ### Step 3: Install Missing Dependencies
@@ -92,12 +93,14 @@ sudo dnf install -y gum
92
93
  sudo pacman -S --noconfirm zsh jq gum
93
94
  ```
94
95
 
95
- ### Step 4: Install viban CLI
96
+ ### Step 4: Install or Update viban CLI
96
97
 
97
98
  ```bash
98
- npm install -g claude-plugin-viban
99
+ npm install -g claude-plugin-viban@latest
99
100
  ```
100
101
 
102
+ This installs viban if not present, or updates to the latest version if already installed.
103
+
101
104
  ### Step 5: Verify Installation
102
105
 
103
106
  ```bash
@@ -113,6 +116,7 @@ If successful, show:
113
116
 
114
117
  All dependencies installed:
115
118
  ✓ zsh
119
+ ✓ python3
116
120
  ✓ gum
117
121
  ✓ jq
118
122
  ✓ viban
@@ -121,8 +125,8 @@ You can now use:
121
125
  viban Open TUI board
122
126
  viban add "task" Add a task
123
127
  viban list List all tasks
124
- /assign Auto-resolve next issue
125
- /task Create structured issue
128
+ /viban:assign Auto-resolve next issue
129
+ /viban:add Create structured issue
126
130
  ```
127
131
 
128
132
  ### Step 6: Workflow Setup Introduction
package/docs/CLAUDE.md CHANGED
@@ -29,6 +29,16 @@ EOF
29
29
  - 스피너 있을 때/없을 때 둘 다 확인
30
30
  - 한글 포함 제목 truncation
31
31
 
32
+ ## Dependency Management
33
+
34
+ **새 런타임 의존성 추가 시 4곳 모두 업데이트 필수:**
35
+ - `README.md` (Requirements 섹션)
36
+ - `scripts/check-deps.sh` (check_dep 호출)
37
+ - `install.sh` (install_pkg 호출)
38
+ - `skills/setup/SKILL.md` (체크 목록 + 완료 메시지)
39
+
40
+ 릴리스 전 누락 여부 확인할 것.
41
+
32
42
  ## Release Rules
33
43
 
34
44
  **릴리즈는 사용자가 명시적으로 요청할 때만 수행한다.**
@@ -106,6 +116,64 @@ local title=$(printf '%s' "$issue" | jq -r '.title')
106
116
 
107
117
  Violating this rule causes jq parse errors on issues with newlines/tabs in description.
108
118
 
119
+ ### Coprocess Pattern (FIFO-based)
120
+
121
+ **zsh `coproc` 사용 금지** - `read -sk1` 키 입력과 충돌. 대신 FIFO + fd 사용.
122
+
123
+ ```bash
124
+ # BAD: coproc interferes with read -sk1
125
+ coproc python3 script.py
126
+ echo "cmd" >&p
127
+ read -r result <&p
128
+
129
+ # GOOD: explicit FIFOs with file descriptors
130
+ mkfifo "$_in_fifo" "$_out_fifo"
131
+ python3 script.py < "$_in_fifo" > "$_out_fifo" &
132
+ exec 7>"$_in_fifo" 8<"$_out_fifo"
133
+ echo "cmd" >&7
134
+ read -r result <&8
135
+ ```
136
+
137
+ ### Variable Declaration in Loops
138
+
139
+ **`local` 을 루프 안에서 재선언하면 현재 값이 stdout으로 출력됨**
140
+
141
+ ```bash
142
+ # BAD: 2회차부터 _bc의 값이 stdout에 찍힘
143
+ while ...; do
144
+ local _cc=${#_title} _bc
145
+ done
146
+
147
+ # GOOD: 루프 밖에서 선언, 안에서 할당만
148
+ local _cc _bc
149
+ while ...; do
150
+ _cc=${#_title}
151
+ done
152
+ ```
153
+
154
+ ### EXIT Trap in Subshells
155
+
156
+ **`$(...)` 서브셸에서 EXIT trap 이 발동됨** - cleanup 함수에 가드 필요
157
+
158
+ ```bash
159
+ cleanup() {
160
+ [[ ${ZSH_SUBSHELL:-0} -gt 0 ]] && return
161
+ # ... actual cleanup
162
+ }
163
+ ```
164
+
165
+ ### Parameter Flag Syntax
166
+
167
+ **`${(@f)var}` 에서 `$` 접두사 불필요**
168
+
169
+ ```bash
170
+ # BAD: bad substitution error
171
+ ${(@f)$_COPROC_RESULT}
172
+
173
+ # GOOD
174
+ ${(@f)_COPROC_RESULT}
175
+ ```
176
+
109
177
  ### Locale Handling (zsh-specific)
110
178
 
111
179
  **`LC_ALL=C var=val` persists in zsh (unlike bash)**
package/install.sh CHANGED
@@ -124,6 +124,9 @@ echo ""
124
124
  # Install zsh (required for viban script)
125
125
  install_pkg "zsh"
126
126
 
127
+ # Install python3 (required for TUI coprocess)
128
+ install_pkg "python3"
129
+
127
130
  # Install jq
128
131
  install_pkg "jq"
129
132
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin-viban",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "Terminal Kanban TUI for AI-human collaborative issue tracking",
5
5
  "main": "bin/viban",
6
6
  "bin": {
@@ -33,6 +33,7 @@ check_dep() {
33
33
  fi
34
34
  }
35
35
 
36
+ check_dep "python3" "brew install python3" "apt install python3"
36
37
  check_dep "gum" "brew install gum" "See https://github.com/charmbracelet/gum#installation"
37
38
  check_dep "jq" "brew install jq" "apt install jq"
38
39
 
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ """Persistent Python coprocess for TUI rendering.
3
+
4
+ Stays resident and handles Unicode display width calculations
5
+ via stdin/stdout protocol, eliminating per-frame Python spawn overhead.
6
+
7
+ Protocol:
8
+ BATCH_TRUNC - truncate strings + compute display widths
9
+ BATCH_WIDTH - compute display widths only
10
+ QUIT - shutdown
11
+ """
12
+
13
+ import sys
14
+ import unicodedata
15
+
16
+
17
+ def display_width(s):
18
+ return sum(2 if unicodedata.east_asian_width(c) in 'FW' else 1 for c in s)
19
+
20
+
21
+ def truncate(s, max_w):
22
+ w = 0
23
+ for i, c in enumerate(s):
24
+ cw = 2 if unicodedata.east_asian_width(c) in 'FW' else 1
25
+ if w + cw > max_w:
26
+ return s[:i]
27
+ w += cw
28
+ return s
29
+
30
+
31
+ def handle_batch_trunc():
32
+ results = []
33
+ for line in sys.stdin:
34
+ line = line.rstrip('\n')
35
+ if line == 'END':
36
+ break
37
+ if not line:
38
+ continue
39
+ parts = line.split('\t', 2)
40
+ max_w = int(parts[0])
41
+ pfx = parts[1]
42
+ txt = parts[2] if len(parts) > 2 else ''
43
+ t = truncate(txt, max_w)
44
+ results.append(f'{t}\t{display_width(pfx + t)}')
45
+ sys.stdout.write('\n'.join(results) + '\nEND\n')
46
+ sys.stdout.flush()
47
+
48
+
49
+ def handle_batch_width():
50
+ results = []
51
+ for line in sys.stdin:
52
+ line = line.rstrip('\n')
53
+ if line == 'END':
54
+ break
55
+ results.append(str(display_width(line)))
56
+ sys.stdout.write('\n'.join(results) + '\nEND\n')
57
+ sys.stdout.flush()
58
+
59
+
60
+ def main():
61
+ for line in sys.stdin:
62
+ cmd = line.strip()
63
+ if cmd == 'QUIT':
64
+ break
65
+ elif cmd == 'BATCH_TRUNC':
66
+ handle_batch_trunc()
67
+ elif cmd == 'BATCH_WIDTH':
68
+ handle_batch_width()
69
+
70
+
71
+ if __name__ == '__main__':
72
+ main()
@@ -205,6 +205,16 @@ After approval: Delete issue from viban TUI
205
205
 
206
206
  ---
207
207
 
208
+ ## CRITICAL: Status Transition Rule
209
+
210
+ > **NEVER end this skill with the issue still in `in_progress`.**
211
+ >
212
+ > Before exiting — whether you completed all phases or stopped early due to errors:
213
+ > ```bash
214
+ > viban review $ISSUE_ID
215
+ > ```
216
+ > This is MANDATORY. If you skip this, the board becomes stale and misleading.
217
+
208
218
  ## CLI Reference
209
219
 
210
220
  | Command | Description |
@@ -36,9 +36,10 @@ Check which dependencies are already installed:
36
36
 
37
37
  ```bash
38
38
  command -v zsh &> /dev/null && echo "✓ zsh" || echo "✗ zsh"
39
+ command -v python3 &> /dev/null && echo "✓ python3" || echo "✗ python3"
39
40
  command -v gum &> /dev/null && echo "✓ gum" || echo "✗ gum"
40
41
  command -v jq &> /dev/null && echo "✓ jq" || echo "✗ jq"
41
- command -v viban &> /dev/null && echo "✓ viban" || echo "✗ viban"
42
+ command -v viban &> /dev/null && echo "✓ viban ($(viban --version 2>/dev/null || echo 'not installed'))" || echo "✗ viban"
42
43
  ```
43
44
 
44
45
  ### Step 3: Install Missing Dependencies
@@ -93,12 +94,14 @@ sudo dnf install -y gum
93
94
  sudo pacman -S --noconfirm zsh jq gum
94
95
  ```
95
96
 
96
- ### Step 4: Install viban CLI
97
+ ### Step 4: Install or Update viban CLI
97
98
 
98
99
  ```bash
99
- npm install -g claude-plugin-viban
100
+ npm install -g claude-plugin-viban@latest
100
101
  ```
101
102
 
103
+ This installs viban if not present, or updates to the latest version if already installed.
104
+
102
105
  ### Step 5: Verify Installation
103
106
 
104
107
  ```bash
@@ -114,6 +117,7 @@ If successful, show:
114
117
 
115
118
  All dependencies installed:
116
119
  ✓ zsh
120
+ ✓ python3
117
121
  ✓ gum
118
122
  ✓ jq
119
123
  ✓ viban
@@ -122,8 +126,8 @@ You can now use:
122
126
  viban Open TUI board
123
127
  viban add "task" Add a task
124
128
  viban list List all tasks
125
- /assign Auto-resolve next issue
126
- /task Create structured issue
129
+ /viban:assign Auto-resolve next issue
130
+ /viban:add Create structured issue
127
131
  ```
128
132
 
129
133
  ### Step 6: Workflow Setup Introduction
@@ -1,100 +0,0 @@
1
- ---
2
- name: release
3
- description: "Bump version, commit, tag, and push to trigger npm publish"
4
- ---
5
-
6
- # /release - Release a new version
7
-
8
- Bump the package version, commit, tag, and push to trigger the automated npm publish workflow.
9
-
10
- ## Execution Steps
11
-
12
- ### Step 1: Pre-flight checks
13
-
14
- ```bash
15
- # Ensure on main branch
16
- git branch --show-current # must be "main"
17
-
18
- # Ensure working tree is clean (no uncommitted changes)
19
- git status --porcelain
20
-
21
- # Ensure up to date with remote
22
- git fetch origin main
23
- git diff origin/main --stat
24
- ```
25
-
26
- If not on main, working tree is dirty, or behind remote: **stop and inform user**.
27
-
28
- ### Step 2: Determine version bump
29
-
30
- Read current version from `package.json`:
31
-
32
- ```bash
33
- grep '"version"' package.json
34
- ```
35
-
36
- Ask user with AskUserQuestion:
37
- - header: "Version"
38
- - question: "Current version is {current}. What kind of bump?"
39
- - options:
40
- - "patch" (x.y.Z) - bug fixes, small changes
41
- - "minor" (x.Y.0) - new features, backward compatible
42
- - "major" (X.0.0) - breaking changes
43
- - multiSelect: false
44
-
45
- Calculate the new version based on selection.
46
-
47
- ### Step 3: Show changelog preview
48
-
49
- Show commits since last tag:
50
-
51
- ```bash
52
- git log $(git describe --tags --abbrev=0)..HEAD --oneline
53
- ```
54
-
55
- Display to user for confirmation before proceeding.
56
-
57
- ### Step 4: Run tests
58
-
59
- ```bash
60
- zsh tests/run_all.zsh
61
- ```
62
-
63
- If tests fail: **stop and inform user**. Do not release with failing tests.
64
-
65
- ### Step 5: Bump version and release
66
-
67
- ```bash
68
- # Update package.json version
69
- # (use jq or sed to update the version field)
70
-
71
- # Commit
72
- git add package.json
73
- git commit -m "{new_version}"
74
-
75
- # Tag
76
- git tag v{new_version}
77
-
78
- # Push with tags
79
- git push origin main --tags
80
- ```
81
-
82
- ### Step 6: Confirm
83
-
84
- ```
85
- ╭──────────────────────────────────────╮
86
- │ Released v{new_version}! │
87
- ╰──────────────────────────────────────╯
88
-
89
- - npm publish: triggered via GitHub Actions
90
- - GitHub Release: auto-generated with release notes
91
-
92
- Check: https://github.com/happy-nut/claude-plugin-viban/actions
93
- ```
94
-
95
- ## Error Handling
96
-
97
- - **Not on main**: "Switch to main branch first"
98
- - **Dirty working tree**: "Commit or stash changes first"
99
- - **Tests failing**: "Fix failing tests before release"
100
- - **Push failed**: "Check remote access and try again"
File without changes