claude-plugin-viban 1.0.33 → 1.0.35
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/bin/viban +169 -92
- package/commands/assign.md +10 -0
- package/commands/release.md +4 -1
- package/package.json +1 -1
- package/scripts/tui_coprocess.py +72 -0
- package/skills/assign/SKILL.md +10 -0
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/release.md
CHANGED
|
@@ -67,8 +67,11 @@ If tests fail: **stop and inform user**. Do not release with failing tests.
|
|
|
67
67
|
# Update package.json version
|
|
68
68
|
# (use jq or sed to update the version field)
|
|
69
69
|
|
|
70
|
+
# IMPORTANT: Also update .claude-plugin/plugin.json version to stay in sync
|
|
71
|
+
# (marketplace uses plugin.json version for caching)
|
|
72
|
+
|
|
70
73
|
# Commit
|
|
71
|
-
git add package.json
|
|
74
|
+
git add package.json .claude-plugin/plugin.json
|
|
72
75
|
git commit -m "{new_version}"
|
|
73
76
|
|
|
74
77
|
# Tag
|
package/package.json
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 |
|