claude-plugin-viban 1.3.9 → 1.3.12
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/assets/viban.png +0 -0
- package/bin/viban +250 -20
- package/package.json +1 -1
- package/skills/assign/SKILL.md +15 -39
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/claude-plugin-viban)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
-

|
|
10
10
|
|
|
11
11
|
## Why viban?
|
|
12
12
|
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
## Recommended Workflow
|
|
19
19
|
|
|
20
|
+

|
|
21
|
+
|
|
20
22
|
The most effective way to use viban is with **multiple terminal sessions**:
|
|
21
23
|
|
|
22
24
|
```
|
package/assets/viban.png
ADDED
|
Binary file
|
package/bin/viban
CHANGED
|
@@ -601,9 +601,10 @@ build_column_lines() {
|
|
|
601
601
|
# Sort: review by updated_at desc, others by effective order (ordered cards first, then priority)
|
|
602
602
|
local sort_expr='sort_by(if .order != null then [0, .order] else [1, ({"P0":0,"P1":1,"P2":2,"P3":3}[.priority // "P3"] // 3), .id] end)'
|
|
603
603
|
[[ "$st" == "review" ]] && sort_expr='sort_by(.updated_at) | reverse'
|
|
604
|
-
local
|
|
604
|
+
local done_ids_tui=$(printf '%s' "$json_data" | jq '[.issues[]|select(.status=="done")|.id]')
|
|
605
|
+
local issues_data=$(printf '%s' "$json_data" | jq -r --arg s "$st" --argjson done "$done_ids_tui" "
|
|
605
606
|
.issues | map(select(.status==\$s)) | $sort_expr |
|
|
606
|
-
.[] | \"\\(.id)\t\\(.title)\t\\((.description // \"\") | gsub(\"[\\n\\t\\r]\"; \" \"))\t\\(.priority // \"P3\")\t\\(.type // \"\")\t\\(.external_id // \"\")\"")
|
|
607
|
+
.[] | \"\\(.id)\t\\(.title)\t\\((.description // \"\") | gsub(\"[\\n\\t\\r]\"; \" \"))\t\\(.priority // \"P3\")\t\\(.type // \"\")\t\\(.external_id // \"\")\t\\(if ((.blocked_by // []) | length > 0 and any(. as \$b | \$done | index(\$b) == null)) then \"blocked\" else \"\" end)\"")
|
|
607
608
|
local count=0
|
|
608
609
|
# Count total issues (not capped) for overflow indicator
|
|
609
610
|
if [[ -n "$issues_data" ]]; then
|
|
@@ -639,7 +640,8 @@ build_column_lines() {
|
|
|
639
640
|
[[ "$st" == "in_progress" ]] && _spinner_w=2
|
|
640
641
|
local _cc _bc _pfx _did
|
|
641
642
|
|
|
642
|
-
|
|
643
|
+
local -a _blocked_flags=()
|
|
644
|
+
while IFS=$'\t' read -r _id _title _desc _priority _type _ext_id _blocked; do
|
|
643
645
|
[[ -z "$_id" ]] && continue
|
|
644
646
|
(( ${#_ids} >= CACHED_MAX_TASKS )) && break
|
|
645
647
|
[[ -z "$_priority" || "$_priority" == "null" ]] && _priority="P3"
|
|
@@ -648,6 +650,7 @@ build_column_lines() {
|
|
|
648
650
|
|
|
649
651
|
_ids+=("$_id"); _titles+=("$_title"); _descs+=("$_desc")
|
|
650
652
|
_priorities+=("$_priority"); _types+=("$_type"); _ext_ids+=("$_ext_id")
|
|
653
|
+
_blocked_flags+=("$_blocked")
|
|
651
654
|
|
|
652
655
|
# Per-card title width limit (use display ID length)
|
|
653
656
|
_did=$(display_id "$_id" "$_ext_id")
|
|
@@ -742,10 +745,11 @@ build_column_lines() {
|
|
|
742
745
|
desc_pad=$((card_inner - ${_desc_cws[$_i]}))
|
|
743
746
|
(( desc_pad < 0 )) && desc_pad=0
|
|
744
747
|
|
|
745
|
-
# Priority and
|
|
748
|
+
# Priority, type, and blocked tags
|
|
746
749
|
priority_tag="[$priority]"
|
|
747
750
|
priority_color="${PRIORITY_COLOR[$priority]:-$A_DIM}"
|
|
748
751
|
type_tag="" type_color="" tags_w=0
|
|
752
|
+
local blocked_tag="" blocked_color=""
|
|
749
753
|
if [[ -n "$issue_type" ]]; then
|
|
750
754
|
type_tag="[${TYPE_LABEL[$issue_type]:-$issue_type}]"
|
|
751
755
|
type_color="${TYPE_COLOR[$issue_type]:-$A_DIM}"
|
|
@@ -753,6 +757,11 @@ build_column_lines() {
|
|
|
753
757
|
else
|
|
754
758
|
tags_w=${#priority_tag}
|
|
755
759
|
fi
|
|
760
|
+
if [[ "${_blocked_flags[$_i]}" == "blocked" ]]; then
|
|
761
|
+
blocked_tag=" BLOCKED"
|
|
762
|
+
blocked_color="\033[38;2;255;69;58m"
|
|
763
|
+
tags_w=$((tags_w + 8))
|
|
764
|
+
fi
|
|
756
765
|
tags_pad=$((card_inner - tags_w - 2))
|
|
757
766
|
|
|
758
767
|
border_color="$A_DIM"
|
|
@@ -769,9 +778,9 @@ build_column_lines() {
|
|
|
769
778
|
printf " ${border_color}│${A_RESET}${text_color}%s${A_RESET}%${title_pad}s${border_color}│${A_RESET} \n" "$title_content" ""
|
|
770
779
|
printf " ${border_color}│${A_RESET}${desc_color}%s${A_RESET}%${desc_pad}s${border_color}│${A_RESET} \n" "$desc_content" ""
|
|
771
780
|
if [[ -n "$type_tag" ]]; then
|
|
772
|
-
printf " ${border_color}│${A_RESET} ${priority_color}%s${A_RESET} ${type_color}%s${A_RESET}%${tags_pad}s${border_color}│${A_RESET} \n" "$priority_tag" "$type_tag" ""
|
|
781
|
+
printf " ${border_color}│${A_RESET} ${priority_color}%s${A_RESET} ${type_color}%s${A_RESET}${blocked_color}%s${A_RESET}%${tags_pad}s${border_color}│${A_RESET} \n" "$priority_tag" "$type_tag" "$blocked_tag" ""
|
|
773
782
|
else
|
|
774
|
-
printf " ${border_color}│${A_RESET} ${priority_color}%s${A_RESET}%${tags_pad}s${border_color}│${A_RESET} \n" "$priority_tag" ""
|
|
783
|
+
printf " ${border_color}│${A_RESET} ${priority_color}%s${A_RESET}${blocked_color}%s${A_RESET}%${tags_pad}s${border_color}│${A_RESET} \n" "$priority_tag" "$blocked_tag" ""
|
|
775
784
|
fi
|
|
776
785
|
printf " ${border_color}╰%s╯${A_RESET} \n" "$border"
|
|
777
786
|
|
|
@@ -1375,12 +1384,32 @@ move_card_status() {
|
|
|
1375
1384
|
# CLI commands
|
|
1376
1385
|
cmd_list() {
|
|
1377
1386
|
init_json
|
|
1387
|
+
local _done_ids=$(jq '[.issues[]|select(.status=="done")|.id]' "$VIBAN_JSON")
|
|
1388
|
+
local filter_status=""
|
|
1389
|
+
[[ "$1" == "--status" && -n "$2" ]] && filter_status="$2"
|
|
1390
|
+
|
|
1378
1391
|
echo ""
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1392
|
+
if [[ -n "$filter_status" ]]; then
|
|
1393
|
+
local count=$(jq -r --arg s "$filter_status" '[.issues[]|select(.status==$s)]|length' "$VIBAN_JSON")
|
|
1394
|
+
echo "● $filter_status ($count)"
|
|
1395
|
+
jq -r --arg s "$filter_status" --argjson done "$_done_ids" '.issues|map(select(.status==$s))|sort_by(.updated_at)|reverse|.[]|" \(if .external_id then .external_id else "#\(.id)" end) [\(.priority // "P3")]\(if .type then " [\(.type | ascii_upcase)]" else "" end)\(if ((.blocked_by // []) | length > 0 and any(. as $b | $done | index($b) == null)) then " [BLOCKED]" else "" end) \(.title)"' "$VIBAN_JSON"
|
|
1382
1396
|
echo ""
|
|
1383
|
-
|
|
1397
|
+
else
|
|
1398
|
+
for st in $VIBAN_STATUSES; do
|
|
1399
|
+
gum style --foreground "${STATUS_COLOR[$st]}" --bold "● ${STATUS_LABEL[$st]} ($(count_issues_by_status "$st"))"
|
|
1400
|
+
get_issues_by_status "$st" | jq -r '.[]|" \(if .external_id then .external_id else "#\(.id)" end) [\(.priority // "P3")]\(if .type then " [\(.type | ascii_upcase)]" else "" end) \(.title)"'
|
|
1401
|
+
echo ""
|
|
1402
|
+
done
|
|
1403
|
+
fi
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
cmd_history() {
|
|
1407
|
+
init_json
|
|
1408
|
+
local count=$(jq '[.issues[]|select(.status=="done")]|length' "$VIBAN_JSON")
|
|
1409
|
+
echo ""
|
|
1410
|
+
echo "● Done ($count)"
|
|
1411
|
+
jq -r '.issues|map(select(.status=="done"))|sort_by(.updated_at)|reverse|.[]|" \(if .external_id then .external_id else "#\(.id)" end) [\(.priority // "P3")]\(if .type then " [\(.type | ascii_upcase)]" else "" end) \(.title) (\(.updated_at | split("T")[0]))"' "$VIBAN_JSON"
|
|
1412
|
+
echo ""
|
|
1384
1413
|
}
|
|
1385
1414
|
|
|
1386
1415
|
cmd_priority() {
|
|
@@ -1412,7 +1441,7 @@ cmd_add() {
|
|
|
1412
1441
|
[[ -z "$1" ]] && { echo "Usage: viban add \"title\" [\"description\"] [priority] [type] [attachments...]"; exit 1; }
|
|
1413
1442
|
|
|
1414
1443
|
# Support both positional and named args (--title, --description, --priority, --type, --ext-id)
|
|
1415
|
-
local title="" desc="" priority="P3" issue_type="" ext_id=""
|
|
1444
|
+
local title="" desc="" priority="P3" issue_type="" ext_id="" parent_id=""
|
|
1416
1445
|
local -a attachments=()
|
|
1417
1446
|
local positional=()
|
|
1418
1447
|
|
|
@@ -1424,6 +1453,7 @@ cmd_add() {
|
|
|
1424
1453
|
--priority) priority="$2"; shift 2 ;;
|
|
1425
1454
|
--type) issue_type="$2"; shift 2 ;;
|
|
1426
1455
|
--ext-id|--external-id) ext_id="$2"; shift 2 ;;
|
|
1456
|
+
--parent) parent_id="$2"; shift 2 ;;
|
|
1427
1457
|
--attach|--attachments) shift; while [[ $# -gt 0 && "$1" != --* ]]; do attachments+=("$1"); shift; done ;;
|
|
1428
1458
|
--*) shift 2 2>/dev/null || shift ;; # skip unknown flags
|
|
1429
1459
|
*) positional+=("$1"); shift ;;
|
|
@@ -1441,6 +1471,32 @@ cmd_add() {
|
|
|
1441
1471
|
|
|
1442
1472
|
[[ -z "$title" ]] && { echo "Usage: viban add \"title\" [\"description\"] [priority] [type]"; exit 1; }
|
|
1443
1473
|
|
|
1474
|
+
# Duplicate detection: warn on similar titles (word-level Jaccard >= 0.5)
|
|
1475
|
+
local duplicates=$(jq -r --arg title "$title" '
|
|
1476
|
+
def words: ascii_downcase | gsub("[^a-z0-9가-힣\\s]"; " ") | split(" ") | map(select(length > 1)) | unique;
|
|
1477
|
+
($title | words) as $new_words |
|
|
1478
|
+
if ($new_words | length) == 0 then empty else
|
|
1479
|
+
.issues[] | select(.status != "done") |
|
|
1480
|
+
(.title | words) as $existing_words |
|
|
1481
|
+
([$new_words[] | select(. as $w | $existing_words | index($w) != null)] | length) as $overlap |
|
|
1482
|
+
([$new_words[], $existing_words[]] | unique | length) as $union |
|
|
1483
|
+
select($union > 0 and ($overlap / $union) >= 0.5) |
|
|
1484
|
+
"\(.id)\t\(.title)"
|
|
1485
|
+
end
|
|
1486
|
+
' "$VIBAN_JSON")
|
|
1487
|
+
if [[ -n "$duplicates" ]]; then
|
|
1488
|
+
echo "⚠ Potential duplicate(s):"
|
|
1489
|
+
while IFS=$'\t' read -r dup_id dup_title; do
|
|
1490
|
+
echo " #$dup_id $dup_title"
|
|
1491
|
+
done <<< "$duplicates"
|
|
1492
|
+
fi
|
|
1493
|
+
|
|
1494
|
+
# Validate parent exists if specified
|
|
1495
|
+
if [[ -n "$parent_id" ]]; then
|
|
1496
|
+
local parent_exists=$(jq -r --argjson id "$parent_id" '.issues[]|select((.id|tonumber)==$id)|.id//empty' "$VIBAN_JSON")
|
|
1497
|
+
[[ -z "$parent_exists" ]] && { echo "Error: Parent issue #$parent_id not found"; exit 1; }
|
|
1498
|
+
fi
|
|
1499
|
+
|
|
1444
1500
|
local id=$(get_next_id) now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1445
1501
|
# Validate priority
|
|
1446
1502
|
[[ ! "$priority" =~ ^P[0-3]$ ]] && priority="P3"
|
|
@@ -1455,7 +1511,7 @@ cmd_add() {
|
|
|
1455
1511
|
# Order is only assigned when manually moved
|
|
1456
1512
|
local tmpjson=$(mktemp)
|
|
1457
1513
|
printf '%s' "$desc" > "$tmpjson"
|
|
1458
|
-
jq --arg id "$id" --arg title "$title" --rawfile desc "$tmpjson" --arg priority "$priority" --arg issue_type "$issue_type" --arg ext_id "$ext_id" --argjson attachments "$attachments_json" --arg now "$now" '
|
|
1514
|
+
jq --arg id "$id" --arg title "$title" --rawfile desc "$tmpjson" --arg priority "$priority" --arg issue_type "$issue_type" --arg ext_id "$ext_id" --arg parent "$parent_id" --argjson attachments "$attachments_json" --arg now "$now" '
|
|
1459
1515
|
.next_id = ((.next_id // 0) + 1) |
|
|
1460
1516
|
.issues += [{
|
|
1461
1517
|
id:($id|tonumber),
|
|
@@ -1465,6 +1521,7 @@ cmd_add() {
|
|
|
1465
1521
|
priority:$priority,
|
|
1466
1522
|
type:(if $issue_type == "" then null else $issue_type end),
|
|
1467
1523
|
external_id:(if $ext_id == "" then null else $ext_id end),
|
|
1524
|
+
parent_id:(if $parent == "" then null else ($parent|tonumber) end),
|
|
1468
1525
|
attachments:$attachments,
|
|
1469
1526
|
assigned_to:null,
|
|
1470
1527
|
created_at:$now,
|
|
@@ -1489,13 +1546,19 @@ cmd_add() {
|
|
|
1489
1546
|
[[ -n "$issue_type" ]] && type_info=" [$issue_type]"
|
|
1490
1547
|
local attach_info=""
|
|
1491
1548
|
[[ ${#attachments[@]} -gt 0 ]] && attach_info=" +${#attachments[@]} files"
|
|
1492
|
-
|
|
1549
|
+
local parent_info=""
|
|
1550
|
+
[[ -n "$parent_id" ]] && parent_info=" (child of #$parent_id)"
|
|
1551
|
+
echo "✓ $(display_id "$id" "$ext_id") added ($priority)$type_info$attach_info$parent_info"
|
|
1493
1552
|
}
|
|
1494
1553
|
|
|
1495
1554
|
cmd_assign() {
|
|
1496
1555
|
init_json
|
|
1497
1556
|
local session="${1:-$(echo $RANDOM | md5 | head -c 8)}"
|
|
1498
|
-
local
|
|
1557
|
+
local done_ids=$(jq '[.issues[]|select(.status=="done")|.id]' "$VIBAN_JSON")
|
|
1558
|
+
local issue=$(jq -r --argjson done "$done_ids" '
|
|
1559
|
+
.issues|map(select(.status=="backlog"))|map(select(
|
|
1560
|
+
(.blocked_by // []) | length == 0 or all(. as $b | $done | index($b) != null)
|
|
1561
|
+
))|sort_by(if .order != null then [0, .order] else [1, ({"P0":0,"P1":1,"P2":2,"P3":3}[.priority // "P3"] // 3), .id] end)|first' "$VIBAN_JSON")
|
|
1499
1562
|
[[ "$issue" == "null" || -z "$issue" ]] && { echo "No backlog"; exit 1; }
|
|
1500
1563
|
local id=$(printf '%s' "$issue" | jq -r '.id') now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1501
1564
|
local ext_id=$(printf '%s' "$issue" | jq -r '.external_id // ""')
|
|
@@ -1530,10 +1593,10 @@ cmd_review() {
|
|
|
1530
1593
|
|
|
1531
1594
|
cmd_done() {
|
|
1532
1595
|
init_json
|
|
1533
|
-
[[ -z "$1" ]] && { echo "Usage: viban done <id> [--
|
|
1596
|
+
[[ -z "$1" ]] && { echo "Usage: viban done <id> [--purge]"; exit 1; }
|
|
1534
1597
|
local id="$1"
|
|
1535
1598
|
local remove=false
|
|
1536
|
-
[[ "$2" == "--remove" ]] && remove=true
|
|
1599
|
+
[[ "$2" == "--remove" || "$2" == "--purge" ]] && remove=true
|
|
1537
1600
|
|
|
1538
1601
|
# Cleanup worktree if exists
|
|
1539
1602
|
local repo_root=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
@@ -1571,7 +1634,70 @@ cmd_done() {
|
|
|
1571
1634
|
fi
|
|
1572
1635
|
}
|
|
1573
1636
|
|
|
1574
|
-
|
|
1637
|
+
cmd_move() {
|
|
1638
|
+
init_json
|
|
1639
|
+
[[ -z "$1" || -z "$2" ]] && { echo "Usage: viban move <id> <status>"; exit 1; }
|
|
1640
|
+
local id="$1"
|
|
1641
|
+
local new_status="$2"
|
|
1642
|
+
|
|
1643
|
+
# Validate status
|
|
1644
|
+
local valid_statuses="backlog in_progress review done"
|
|
1645
|
+
if [[ ! " $valid_statuses " == *" $new_status "* ]]; then
|
|
1646
|
+
echo "Error: Invalid status '$new_status'. Valid: backlog, in_progress, review, done"
|
|
1647
|
+
exit 1
|
|
1648
|
+
fi
|
|
1649
|
+
|
|
1650
|
+
# Verify issue exists
|
|
1651
|
+
local cur_status=$(jq -r --argjson id "$id" '.issues[]|select((.id|tonumber)==$id)|.status//empty' "$VIBAN_JSON")
|
|
1652
|
+
[[ -z "$cur_status" ]] && { echo "Error: Issue #$id not found"; exit 1; }
|
|
1653
|
+
|
|
1654
|
+
if [[ "$cur_status" == "$new_status" ]]; then
|
|
1655
|
+
echo "Issue #$id is already in $new_status"
|
|
1656
|
+
return 0
|
|
1657
|
+
fi
|
|
1658
|
+
|
|
1659
|
+
local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1660
|
+
jq --argjson id "$id" --arg s "$new_status" --arg now "$now" \
|
|
1661
|
+
'(.issues[]|select((.id|tonumber)==$id)) |= . + {status:$s,updated_at:$now}' \
|
|
1662
|
+
"$VIBAN_JSON" > "$VIBAN_JSON.tmp" && mv "$VIBAN_JSON.tmp" "$VIBAN_JSON"
|
|
1663
|
+
|
|
1664
|
+
echo "✓ $(display_id "$id" "$(get_ext_id "$id")") → $new_status"
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
cmd_comment() {
|
|
1668
|
+
init_json
|
|
1669
|
+
[[ -z "$1" || -z "$2" ]] && { echo "Usage: viban comment <id> \"message\""; exit 1; }
|
|
1670
|
+
local id="$1"
|
|
1671
|
+
shift
|
|
1672
|
+
local message="$*"
|
|
1673
|
+
|
|
1674
|
+
# Verify issue exists
|
|
1675
|
+
local exists=$(jq -r --argjson id "$id" '.issues[]|select((.id|tonumber)==$id)|.id//empty' "$VIBAN_JSON")
|
|
1676
|
+
[[ -z "$exists" ]] && { echo "Error: Issue #$id not found"; exit 1; }
|
|
1677
|
+
|
|
1678
|
+
local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1679
|
+
jq --argjson id "$id" --arg msg "$message" --arg now "$now" \
|
|
1680
|
+
'(.issues[]|select((.id|tonumber)==$id)) |= . + {comments:((.comments // []) + [{text:$msg,created_at:$now}]),updated_at:$now}' \
|
|
1681
|
+
"$VIBAN_JSON" > "$VIBAN_JSON.tmp" && mv "$VIBAN_JSON.tmp" "$VIBAN_JSON"
|
|
1682
|
+
|
|
1683
|
+
local count=$(jq -r --argjson id "$id" '.issues[]|select((.id|tonumber)==$id)|.comments|length' "$VIBAN_JSON")
|
|
1684
|
+
echo "✓ comment #$count added to $(display_id "$id" "$(get_ext_id "$id")")"
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
cmd_get() {
|
|
1688
|
+
init_json
|
|
1689
|
+
local id="$1"
|
|
1690
|
+
# Output issue JSON
|
|
1691
|
+
jq --argjson id "$id" '.issues[]|select((.id|tonumber)==$id)' "$VIBAN_JSON"
|
|
1692
|
+
# Show sub-tasks if any
|
|
1693
|
+
local subtasks=$(jq -r --argjson id "$id" '[.issues[]|select(.parent_id==$id)]|length' "$VIBAN_JSON")
|
|
1694
|
+
if [[ "$subtasks" -gt 0 ]]; then
|
|
1695
|
+
local done_count=$(jq -r --argjson id "$id" '[.issues[]|select(.parent_id==$id and .status=="done")]|length' "$VIBAN_JSON")
|
|
1696
|
+
echo ""
|
|
1697
|
+
echo "Sub-tasks: $done_count/$subtasks done ($((done_count * 100 / subtasks))%)"
|
|
1698
|
+
jq -r --argjson id "$id" '.issues[]|select(.parent_id==$id)|" #\(.id) [\(.status)] \(.title)"' "$VIBAN_JSON"
|
|
1699
|
+
fi
|
|
1700
|
+
}
|
|
1575
1701
|
|
|
1576
1702
|
cmd_attach() {
|
|
1577
1703
|
init_json
|
|
@@ -1599,6 +1725,97 @@ cmd_attach() {
|
|
|
1599
1725
|
echo "✓ $(display_id "$id" "$(get_ext_id "$id")"): ${#files[@]} file(s) attached"
|
|
1600
1726
|
}
|
|
1601
1727
|
|
|
1728
|
+
cmd_link() {
|
|
1729
|
+
init_json
|
|
1730
|
+
[[ -z "$1" || "$2" != "blocks" || -z "$3" ]] && { echo "Usage: viban link <id> blocks <id>"; exit 1; }
|
|
1731
|
+
local blocker_id="$1" blocked_id="$3"
|
|
1732
|
+
|
|
1733
|
+
# Verify both issues exist
|
|
1734
|
+
local b1=$(jq -r --argjson id "$blocker_id" '.issues[]|select((.id|tonumber)==$id)|.id//empty' "$VIBAN_JSON")
|
|
1735
|
+
local b2=$(jq -r --argjson id "$blocked_id" '.issues[]|select((.id|tonumber)==$id)|.id//empty' "$VIBAN_JSON")
|
|
1736
|
+
[[ -z "$b1" ]] && { echo "Error: Issue #$blocker_id not found"; exit 1; }
|
|
1737
|
+
[[ -z "$b2" ]] && { echo "Error: Issue #$blocked_id not found"; exit 1; }
|
|
1738
|
+
[[ "$blocker_id" == "$blocked_id" ]] && { echo "Error: Cannot block self"; exit 1; }
|
|
1739
|
+
|
|
1740
|
+
local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1741
|
+
jq --argjson bid "$blocked_id" --argjson rid "$blocker_id" --arg now "$now" \
|
|
1742
|
+
'(.issues[]|select((.id|tonumber)==$bid)) |= . + {blocked_by:((.blocked_by // []) | if index($rid) then . else . + [$rid] end),updated_at:$now}' \
|
|
1743
|
+
"$VIBAN_JSON" > "$VIBAN_JSON.tmp" && mv "$VIBAN_JSON.tmp" "$VIBAN_JSON"
|
|
1744
|
+
|
|
1745
|
+
echo "✓ #$blocker_id blocks #$blocked_id"
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
cmd_unlink() {
|
|
1749
|
+
init_json
|
|
1750
|
+
[[ -z "$1" || "$2" != "blocks" || -z "$3" ]] && { echo "Usage: viban unlink <id> blocks <id>"; exit 1; }
|
|
1751
|
+
local blocker_id="$1" blocked_id="$3"
|
|
1752
|
+
|
|
1753
|
+
local now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1754
|
+
jq --argjson bid "$blocked_id" --argjson rid "$blocker_id" --arg now "$now" \
|
|
1755
|
+
'(.issues[]|select((.id|tonumber)==$bid)) |= . + {blocked_by:((.blocked_by // []) - [$rid]),updated_at:$now}' \
|
|
1756
|
+
"$VIBAN_JSON" > "$VIBAN_JSON.tmp" && mv "$VIBAN_JSON.tmp" "$VIBAN_JSON"
|
|
1757
|
+
|
|
1758
|
+
echo "✓ #$blocker_id no longer blocks #$blocked_id"
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
cmd_stats() {
|
|
1762
|
+
init_json
|
|
1763
|
+
local now_epoch=$(date +%s)
|
|
1764
|
+
local week_ago_epoch=$((now_epoch - 604800))
|
|
1765
|
+
|
|
1766
|
+
# Total by status
|
|
1767
|
+
echo ""
|
|
1768
|
+
echo "Board Summary"
|
|
1769
|
+
echo "─────────────"
|
|
1770
|
+
local backlog_n=$(jq '[.issues[]|select(.status=="backlog")]|length' "$VIBAN_JSON")
|
|
1771
|
+
local wip_n=$(jq '[.issues[]|select(.status=="in_progress")]|length' "$VIBAN_JSON")
|
|
1772
|
+
local review_n=$(jq '[.issues[]|select(.status=="review")]|length' "$VIBAN_JSON")
|
|
1773
|
+
local done_n=$(jq '[.issues[]|select(.status=="done")]|length' "$VIBAN_JSON")
|
|
1774
|
+
local total_n=$(jq '.issues|length' "$VIBAN_JSON")
|
|
1775
|
+
echo " Backlog: $backlog_n In Progress: $wip_n Review: $review_n Done: $done_n Total: $total_n"
|
|
1776
|
+
|
|
1777
|
+
# P0/P1 open count
|
|
1778
|
+
local p0_n=$(jq '[.issues[]|select(.status!="done" and .priority=="P0")]|length' "$VIBAN_JSON")
|
|
1779
|
+
local p1_n=$(jq '[.issues[]|select(.status!="done" and .priority=="P1")]|length' "$VIBAN_JSON")
|
|
1780
|
+
echo " Open P0: $p0_n Open P1: $p1_n"
|
|
1781
|
+
|
|
1782
|
+
# Issues added/closed this week
|
|
1783
|
+
echo ""
|
|
1784
|
+
echo "This Week (last 7 days)"
|
|
1785
|
+
echo "───────────────────────"
|
|
1786
|
+
local week_ago_iso=$(date -u -r $week_ago_epoch +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -d "@$week_ago_epoch" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)
|
|
1787
|
+
local added_week=$(jq -r --arg since "$week_ago_iso" '[.issues[]|select(.created_at >= $since)]|length' "$VIBAN_JSON")
|
|
1788
|
+
local closed_week=$(jq -r --arg since "$week_ago_iso" '[.issues[]|select(.status=="done" and .updated_at >= $since)]|length' "$VIBAN_JSON")
|
|
1789
|
+
echo " Added: $added_week Completed: $closed_week"
|
|
1790
|
+
|
|
1791
|
+
# Average cycle time (created_at → updated_at for done issues)
|
|
1792
|
+
echo ""
|
|
1793
|
+
echo "Cycle Time"
|
|
1794
|
+
echo "──────────"
|
|
1795
|
+
local avg_hours=$(jq -r '
|
|
1796
|
+
[.issues[]|select(.status=="done")|
|
|
1797
|
+
((.updated_at|split("T")[0]|split("-")|map(tonumber)) as [$y2,$m2,$d2] |
|
|
1798
|
+
(.created_at|split("T")[0]|split("-")|map(tonumber)) as [$y1,$m1,$d1] |
|
|
1799
|
+
(($y2-$y1)*365 + ($m2-$m1)*30 + ($d2-$d1)) * 24)
|
|
1800
|
+
] | if length == 0 then null else (add / length | floor) end
|
|
1801
|
+
' "$VIBAN_JSON")
|
|
1802
|
+
if [[ "$avg_hours" == "null" || -z "$avg_hours" ]]; then
|
|
1803
|
+
echo " Average: no completed issues"
|
|
1804
|
+
elif [[ "$avg_hours" -lt 24 ]]; then
|
|
1805
|
+
echo " Average: <1 day"
|
|
1806
|
+
else
|
|
1807
|
+
echo " Average: $((avg_hours / 24)) days"
|
|
1808
|
+
fi
|
|
1809
|
+
|
|
1810
|
+
# Oldest open issue
|
|
1811
|
+
echo ""
|
|
1812
|
+
echo "Oldest Open Issue"
|
|
1813
|
+
echo "─────────────────"
|
|
1814
|
+
local oldest=$(jq -r '[.issues[]|select(.status!="done")]|sort_by(.created_at)|first|if . then "#\(.id) [\(.priority)] \(.title) (created \(.created_at|split("T")[0]))" else "none" end' "$VIBAN_JSON")
|
|
1815
|
+
echo " $oldest"
|
|
1816
|
+
echo ""
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1602
1819
|
cmd_migrate() {
|
|
1603
1820
|
init_json
|
|
1604
1821
|
echo "Migrating issues..."
|
|
@@ -1684,15 +1901,21 @@ main() {
|
|
|
1684
1901
|
check_deps
|
|
1685
1902
|
init_json
|
|
1686
1903
|
case "${1:-}" in
|
|
1687
|
-
list) cmd_list;;
|
|
1904
|
+
list) shift; cmd_list "$@";;
|
|
1905
|
+
history) cmd_history;;
|
|
1688
1906
|
add) shift; cmd_add "$@";;
|
|
1689
1907
|
attach) shift; cmd_attach "$@";;
|
|
1690
1908
|
assign) cmd_assign "$2";;
|
|
1691
1909
|
review) cmd_review "$2";;
|
|
1692
1910
|
done) cmd_done "$2" "$3";;
|
|
1911
|
+
move) cmd_move "$2" "$3";;
|
|
1693
1912
|
get) cmd_get "$2";;
|
|
1913
|
+
comment) shift; cmd_comment "$@";;
|
|
1914
|
+
link) cmd_link "$2" "$3" "$4";;
|
|
1915
|
+
unlink) cmd_unlink "$2" "$3" "$4";;
|
|
1694
1916
|
edit) [[ -z "$2" ]] && { echo "Usage: viban edit <id>"; exit 1; }; edit_issue "$2";;
|
|
1695
1917
|
priority) cmd_priority "$2" "$3";;
|
|
1918
|
+
stats) cmd_stats;;
|
|
1696
1919
|
migrate) cmd_migrate;;
|
|
1697
1920
|
sync) shift; cmd_sync "$@";;
|
|
1698
1921
|
--version|-v)
|
|
@@ -1717,14 +1940,21 @@ main() {
|
|
|
1717
1940
|
echo ""
|
|
1718
1941
|
echo " viban TUI"
|
|
1719
1942
|
echo " viban list Show board"
|
|
1720
|
-
echo " viban
|
|
1943
|
+
echo " viban list --status <s> Filter by status"
|
|
1944
|
+
echo " viban history Show completed issues"
|
|
1945
|
+
echo " viban add \"title\" [\"desc\"] [P0-P3] [type] [--parent <id>] Add task"
|
|
1721
1946
|
echo " viban attach <id> <file1> [file2...] Attach files to task"
|
|
1722
1947
|
echo " viban priority <id> <P0-P3> Set priority"
|
|
1723
1948
|
echo " viban assign Assign first backlog (by priority)"
|
|
1724
1949
|
echo " viban review → Human Review"
|
|
1725
|
-
echo " viban
|
|
1950
|
+
echo " viban move <id> <status> Move to status (backlog,in_progress,review,done)"
|
|
1951
|
+
echo " viban done <id> [--purge] Complete (--purge to permanently delete)"
|
|
1952
|
+
echo " viban comment <id> \"msg\" Add comment to task"
|
|
1953
|
+
echo " viban link <id> blocks <id> Add dependency"
|
|
1954
|
+
echo " viban unlink <id> blocks <id> Remove dependency"
|
|
1726
1955
|
echo " viban edit <id> Edit task in editor"
|
|
1727
1956
|
echo " viban get <id> Get task details (JSON)"
|
|
1957
|
+
echo " viban stats Show throughput metrics and statistics"
|
|
1728
1958
|
echo " viban migrate Migrate: extract type from title"
|
|
1729
1959
|
echo " viban sync Sync with external issue tracker (GitHub, etc.)"
|
|
1730
1960
|
echo " viban update Update to latest version (if available)"
|
package/package.json
CHANGED
package/skills/assign/SKILL.md
CHANGED
|
@@ -45,33 +45,12 @@ viban get $ISSUE_ID
|
|
|
45
45
|
|
|
46
46
|
Display the issue title, description, priority, and type to the user.
|
|
47
47
|
|
|
48
|
-
## Step 3: Evaluate Clarity
|
|
48
|
+
## Step 3: Evaluate Clarity → Proceed Immediately
|
|
49
49
|
|
|
50
|
-
Assess whether the issue description provides enough context to start working
|
|
50
|
+
Assess whether the issue description provides enough context to start working.
|
|
51
51
|
|
|
52
|
-
- **Clear
|
|
53
|
-
- **Unclear
|
|
54
|
-
|
|
55
|
-
### If Unclear
|
|
56
|
-
|
|
57
|
-
Interview the user with AskUserQuestion to gather missing context. Ask about:
|
|
58
|
-
- What specifically is the problem? (symptom)
|
|
59
|
-
- Where does it happen? (location/trigger)
|
|
60
|
-
- What is the expected behavior?
|
|
61
|
-
- Any additional constraints or context?
|
|
62
|
-
|
|
63
|
-
After gathering answers, update the issue description:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
cat > /tmp/viban-desc-update.md <<'VIBAN_EOF'
|
|
67
|
-
{original description}
|
|
68
|
-
|
|
69
|
-
## Clarification
|
|
70
|
-
{gathered context from interview}
|
|
71
|
-
VIBAN_EOF
|
|
72
|
-
|
|
73
|
-
# Re-add the issue with enriched description (edit via TUI or recreate)
|
|
74
|
-
```
|
|
52
|
+
- **Clear** → **proceed directly to Step 4. Do NOT ask the user for confirmation. Do NOT ask "should I start?". Just start.**
|
|
53
|
+
- **Unclear** → interview the user with AskUserQuestion to gather missing context, then proceed to Step 4 immediately.
|
|
75
54
|
|
|
76
55
|
## Step 4: Execute Workflow
|
|
77
56
|
|
|
@@ -90,9 +69,7 @@ Follow the workflow from Step 0. If no workflow was found, use this default pipe
|
|
|
90
69
|
- Run build and tests to confirm the fix works
|
|
91
70
|
- Verify no regressions
|
|
92
71
|
|
|
93
|
-
### 4.4 Ship (MANDATORY)
|
|
94
|
-
|
|
95
|
-
Every step below is **required** unless the workflow explicitly says to stop earlier.
|
|
72
|
+
### 4.4 Ship (MANDATORY — execute ALL 4 commands in sequence)
|
|
96
73
|
|
|
97
74
|
```bash
|
|
98
75
|
# 1. Commit
|
|
@@ -102,7 +79,7 @@ git commit -m "fix: {description} (#$ISSUE_ID)"
|
|
|
102
79
|
# 2. Push
|
|
103
80
|
git push -u origin issue-$ISSUE_ID
|
|
104
81
|
|
|
105
|
-
# 3. Create PR (REQUIRED — do NOT skip
|
|
82
|
+
# 3. Create PR (REQUIRED — do NOT skip)
|
|
106
83
|
gh pr create --title "fix: {description}" --body "Resolves #$ISSUE_ID
|
|
107
84
|
|
|
108
85
|
## Summary
|
|
@@ -110,19 +87,18 @@ gh pr create --title "fix: {description}" --body "Resolves #$ISSUE_ID
|
|
|
110
87
|
|
|
111
88
|
## Test plan
|
|
112
89
|
{how it was verified}"
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
**If `gh pr create` fails**: fix the error and retry. Do NOT skip PR creation.
|
|
116
|
-
|
|
117
|
-
## Step 5: Move to Review
|
|
118
90
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
```bash
|
|
91
|
+
# 4. Move to review (REQUIRED — do NOT skip)
|
|
122
92
|
viban review $ISSUE_ID
|
|
123
93
|
```
|
|
124
94
|
|
|
125
|
-
|
|
95
|
+
**If any command fails**: fix the error and retry. Do NOT skip any step.
|
|
96
|
+
|
|
97
|
+
### Completion Checklist (ALL must be true before you stop)
|
|
98
|
+
|
|
99
|
+
- [ ] PR created (you have a PR URL)
|
|
100
|
+
- [ ] `viban review` called (issue status is "review")
|
|
101
|
+
- [ ] Completion message includes PR URL
|
|
126
102
|
|
|
127
103
|
```
|
|
128
104
|
Issue #{id} resolved → review
|
|
@@ -130,7 +106,7 @@ Issue #{id} resolved → review
|
|
|
130
106
|
PR: {pr_url}
|
|
131
107
|
```
|
|
132
108
|
|
|
133
|
-
**
|
|
109
|
+
**If you don't have a PR URL or haven't called `viban review`, you are NOT done. Go back and do it.**
|
|
134
110
|
|
|
135
111
|
---
|
|
136
112
|
|