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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -1
- package/bin/viban +169 -92
- package/commands/assign.md +10 -0
- package/commands/setup.md +9 -5
- package/docs/CLAUDE.md +68 -0
- package/install.sh +3 -0
- package/package.json +1 -1
- package/scripts/check-deps.sh +1 -0
- package/scripts/tui_coprocess.py +72 -0
- package/skills/assign/SKILL.md +10 -0
- package/skills/setup/SKILL.md +9 -5
- package/skills/release/SKILL.md +0 -100
- /package/{commands → docs}/release.md +0 -0
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
|
-
│
|
|
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
|
-
#
|
|
611
|
-
local
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
623
|
-
[[ -z "$priority" || "$priority" == "null" ]] && priority="P3"
|
|
624
|
-
[[ -z "$issue_type" || "$issue_type" == "null" ]] && issue_type=""
|
|
662
|
+
local _n=${#_ids}
|
|
625
663
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
local
|
|
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
|
|
642
|
-
local
|
|
643
|
-
local
|
|
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
|
|
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))
|
|
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
|
|
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
|
-
|
|
789
|
-
|
|
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
|
|
826
|
-
read -sk1 -t
|
|
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
|
package/commands/assign.md
CHANGED
|
@@ -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
|
|
125
|
-
/
|
|
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
package/package.json
CHANGED
package/scripts/check-deps.sh
CHANGED
|
@@ -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()
|
package/skills/assign/SKILL.md
CHANGED
|
@@ -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 |
|
package/skills/setup/SKILL.md
CHANGED
|
@@ -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
|
|
126
|
-
/
|
|
129
|
+
/viban:assign Auto-resolve next issue
|
|
130
|
+
/viban:add Create structured issue
|
|
127
131
|
```
|
|
128
132
|
|
|
129
133
|
### Step 6: Workflow Setup Introduction
|
package/skills/release/SKILL.md
DELETED
|
@@ -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
|