@revotools/cli 0.6.0 → 0.6.2
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/dist/revo +246 -66
- package/package.json +1 -1
package/dist/revo
CHANGED
|
@@ -8,7 +8,7 @@ set -euo pipefail
|
|
|
8
8
|
# Exit cleanly on SIGPIPE (e.g., revo clone | grep, revo status | head)
|
|
9
9
|
trap 'exit 0' PIPE
|
|
10
10
|
|
|
11
|
-
REVO_VERSION="0.6.
|
|
11
|
+
REVO_VERSION="0.6.2"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
# === lib/ui.sh ===
|
|
@@ -481,6 +481,7 @@ YAML_REPO_URLS=()
|
|
|
481
481
|
YAML_REPO_PATHS=()
|
|
482
482
|
YAML_REPO_TAGS=()
|
|
483
483
|
YAML_REPO_DEPS=()
|
|
484
|
+
YAML_REPO_BRANCHES=()
|
|
484
485
|
|
|
485
486
|
yaml_parse() {
|
|
486
487
|
local file="$1"
|
|
@@ -497,6 +498,7 @@ yaml_parse() {
|
|
|
497
498
|
YAML_REPO_PATHS=()
|
|
498
499
|
YAML_REPO_TAGS=()
|
|
499
500
|
YAML_REPO_DEPS=()
|
|
501
|
+
YAML_REPO_BRANCHES=()
|
|
500
502
|
|
|
501
503
|
if [[ ! -f "$file" ]]; then
|
|
502
504
|
return 1
|
|
@@ -550,6 +552,7 @@ yaml_parse() {
|
|
|
550
552
|
YAML_REPO_PATHS[$current_index]=$(yaml_path_from_url "$url")
|
|
551
553
|
YAML_REPO_TAGS[$current_index]=""
|
|
552
554
|
YAML_REPO_DEPS[$current_index]=""
|
|
555
|
+
YAML_REPO_BRANCHES[$current_index]=""
|
|
553
556
|
YAML_REPO_COUNT=$((YAML_REPO_COUNT + 1))
|
|
554
557
|
continue
|
|
555
558
|
fi
|
|
@@ -573,6 +576,8 @@ yaml_parse() {
|
|
|
573
576
|
deps_str="${deps_str//\"/}"
|
|
574
577
|
deps_str="${deps_str//\'/}"
|
|
575
578
|
YAML_REPO_DEPS[$current_index]="$deps_str"
|
|
579
|
+
elif [[ "$trimmed" =~ ^branch:[[:space:]]*(.+)$ ]]; then
|
|
580
|
+
YAML_REPO_BRANCHES[$current_index]="${BASH_REMATCH[1]}"
|
|
576
581
|
fi
|
|
577
582
|
fi
|
|
578
583
|
fi
|
|
@@ -648,6 +653,12 @@ yaml_get_deps() {
|
|
|
648
653
|
printf '%s' "${YAML_REPO_DEPS[$idx]:-}"
|
|
649
654
|
}
|
|
650
655
|
|
|
656
|
+
# Get repo default branch by index (empty means use workspace default)
|
|
657
|
+
yaml_get_branch() {
|
|
658
|
+
local idx="$1"
|
|
659
|
+
printf '%s' "${YAML_REPO_BRANCHES[$idx]:-}"
|
|
660
|
+
}
|
|
661
|
+
|
|
651
662
|
# Find repo index by name (path basename)
|
|
652
663
|
# Usage: idx=$(yaml_find_by_name "backend")
|
|
653
664
|
# Returns: index or -1 if not found
|
|
@@ -681,6 +692,7 @@ yaml_write() {
|
|
|
681
692
|
local path="${YAML_REPO_PATHS[$i]}"
|
|
682
693
|
local tags="${YAML_REPO_TAGS[$i]}"
|
|
683
694
|
local deps="${YAML_REPO_DEPS[$i]:-}"
|
|
695
|
+
local branch="${YAML_REPO_BRANCHES[$i]:-}"
|
|
684
696
|
|
|
685
697
|
printf ' - url: %s\n' "$url"
|
|
686
698
|
|
|
@@ -700,6 +712,11 @@ yaml_write() {
|
|
|
700
712
|
if [[ -n "$deps" ]]; then
|
|
701
713
|
printf ' depends_on: [%s]\n' "$deps"
|
|
702
714
|
fi
|
|
715
|
+
|
|
716
|
+
# Write branch if it differs from the workspace default
|
|
717
|
+
if [[ -n "$branch" ]] && [[ "$branch" != "$YAML_DEFAULTS_BRANCH" ]]; then
|
|
718
|
+
printf ' branch: %s\n' "$branch"
|
|
719
|
+
fi
|
|
703
720
|
done
|
|
704
721
|
|
|
705
722
|
printf '\ndefaults:\n'
|
|
@@ -708,12 +725,13 @@ yaml_write() {
|
|
|
708
725
|
}
|
|
709
726
|
|
|
710
727
|
# Add a repo to the config
|
|
711
|
-
# Usage: yaml_add_repo "url" "path" "tags" "deps"
|
|
728
|
+
# Usage: yaml_add_repo "url" "path" "tags" "deps" ["branch"]
|
|
712
729
|
yaml_add_repo() {
|
|
713
730
|
local url="$1"
|
|
714
731
|
local path="${2:-}"
|
|
715
732
|
local tags="${3:-}"
|
|
716
733
|
local deps="${4:-}"
|
|
734
|
+
local branch="${5:-}"
|
|
717
735
|
|
|
718
736
|
local idx=$YAML_REPO_COUNT
|
|
719
737
|
|
|
@@ -725,6 +743,7 @@ yaml_add_repo() {
|
|
|
725
743
|
YAML_REPO_PATHS[$idx]="$path"
|
|
726
744
|
YAML_REPO_TAGS[$idx]="$tags"
|
|
727
745
|
YAML_REPO_DEPS[$idx]="$deps"
|
|
746
|
+
YAML_REPO_BRANCHES[$idx]="$branch"
|
|
728
747
|
|
|
729
748
|
YAML_REPO_COUNT=$((YAML_REPO_COUNT + 1))
|
|
730
749
|
}
|
|
@@ -826,6 +845,7 @@ config_init() {
|
|
|
826
845
|
YAML_REPO_PATHS=()
|
|
827
846
|
YAML_REPO_TAGS=()
|
|
828
847
|
YAML_REPO_DEPS=()
|
|
848
|
+
YAML_REPO_BRANCHES=()
|
|
829
849
|
|
|
830
850
|
# Create directory structure
|
|
831
851
|
mkdir -p "$REVO_REPOS_DIR"
|
|
@@ -891,7 +911,9 @@ config_load() {
|
|
|
891
911
|
return 1
|
|
892
912
|
fi
|
|
893
913
|
|
|
894
|
-
yaml_parse "$REVO_CONFIG_FILE"
|
|
914
|
+
if ! yaml_parse "$REVO_CONFIG_FILE"; then
|
|
915
|
+
return 1
|
|
916
|
+
fi
|
|
895
917
|
}
|
|
896
918
|
|
|
897
919
|
# Save configuration
|
|
@@ -967,11 +989,25 @@ config_workspace_name() {
|
|
|
967
989
|
printf '%s' "$YAML_WORKSPACE_NAME"
|
|
968
990
|
}
|
|
969
991
|
|
|
970
|
-
# Get default branch
|
|
992
|
+
# Get workspace default branch
|
|
971
993
|
config_default_branch() {
|
|
972
994
|
printf '%s' "$YAML_DEFAULTS_BRANCH"
|
|
973
995
|
}
|
|
974
996
|
|
|
997
|
+
# Get effective default branch for a specific repo
|
|
998
|
+
# Falls back to workspace default if no per-repo branch is set
|
|
999
|
+
# Usage: branch=$(config_repo_default_branch "repo_index")
|
|
1000
|
+
config_repo_default_branch() {
|
|
1001
|
+
local idx="$1"
|
|
1002
|
+
local branch
|
|
1003
|
+
branch=$(yaml_get_branch "$idx")
|
|
1004
|
+
if [[ -n "$branch" ]]; then
|
|
1005
|
+
printf '%s' "$branch"
|
|
1006
|
+
else
|
|
1007
|
+
printf '%s' "$YAML_DEFAULTS_BRANCH"
|
|
1008
|
+
fi
|
|
1009
|
+
}
|
|
1010
|
+
|
|
975
1011
|
# === lib/git.sh ===
|
|
976
1012
|
# Revo CLI - Git Operations
|
|
977
1013
|
# Wrapper functions for git commands with consistent error handling
|
|
@@ -1180,6 +1216,34 @@ git_remote_url() {
|
|
|
1180
1216
|
git -C "$repo_dir" remote get-url origin 2>/dev/null
|
|
1181
1217
|
}
|
|
1182
1218
|
|
|
1219
|
+
# Detect the default branch for a cloned repo
|
|
1220
|
+
# Tries symbolic-ref first, then falls back to checking main/master
|
|
1221
|
+
# Usage: branch=$(git_default_branch "repo_dir")
|
|
1222
|
+
git_default_branch() {
|
|
1223
|
+
local repo_dir="$1"
|
|
1224
|
+
local ref
|
|
1225
|
+
|
|
1226
|
+
# Best source: what the remote says HEAD points to
|
|
1227
|
+
ref=$(git -C "$repo_dir" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null)
|
|
1228
|
+
if [[ -n "$ref" ]]; then
|
|
1229
|
+
printf '%s' "${ref##*/}"
|
|
1230
|
+
return 0
|
|
1231
|
+
fi
|
|
1232
|
+
|
|
1233
|
+
# Fallback: check which of main/master exists
|
|
1234
|
+
if git -C "$repo_dir" rev-parse --verify origin/main >/dev/null 2>&1; then
|
|
1235
|
+
printf '%s' "main"
|
|
1236
|
+
return 0
|
|
1237
|
+
fi
|
|
1238
|
+
if git -C "$repo_dir" rev-parse --verify origin/master >/dev/null 2>&1; then
|
|
1239
|
+
printf '%s' "master"
|
|
1240
|
+
return 0
|
|
1241
|
+
fi
|
|
1242
|
+
|
|
1243
|
+
# Last resort: whatever branch we're on
|
|
1244
|
+
git -C "$repo_dir" rev-parse --abbrev-ref HEAD 2>/dev/null
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1183
1247
|
# Check if branch exists (local or remote)
|
|
1184
1248
|
# Usage: if git_branch_exists "repo_dir" "branch_name"; then ...
|
|
1185
1249
|
git_branch_exists() {
|
|
@@ -1299,7 +1363,7 @@ _scan_json_string() {
|
|
|
1299
1363
|
_scan_pkg_has_dep() {
|
|
1300
1364
|
local file="$1"
|
|
1301
1365
|
local dep="$2"
|
|
1302
|
-
grep -
|
|
1366
|
+
grep -qF "\"$dep\"" "$file" 2>/dev/null
|
|
1303
1367
|
}
|
|
1304
1368
|
|
|
1305
1369
|
# Detect framework from package.json dependencies
|
|
@@ -1733,8 +1797,10 @@ cmd_init() {
|
|
|
1733
1797
|
fi
|
|
1734
1798
|
|
|
1735
1799
|
tags=$(_init_auto_tags "$dir" "$name")
|
|
1736
|
-
|
|
1737
|
-
|
|
1800
|
+
local branch
|
|
1801
|
+
branch=$(git_default_branch "$dir")
|
|
1802
|
+
yaml_add_repo "$remote" "$path" "$tags" "" "$branch"
|
|
1803
|
+
ui_step_done "Detected:" "$name → $remote (branch: $branch)"
|
|
1738
1804
|
detected_count=$((detected_count + 1))
|
|
1739
1805
|
done <<< "$_INIT_FOUND_DIRS"
|
|
1740
1806
|
|
|
@@ -1839,8 +1905,10 @@ cmd_detect() {
|
|
|
1839
1905
|
ln -s "../$name" "repos/$name"
|
|
1840
1906
|
fi
|
|
1841
1907
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1908
|
+
local branch
|
|
1909
|
+
branch=$(git_default_branch "$d")
|
|
1910
|
+
yaml_add_repo "$remote" "$name" "$tags" "" "$branch"
|
|
1911
|
+
ui_step_done "Found:" "$name ($remote, branch: $branch)"
|
|
1844
1912
|
found=$((found + 1))
|
|
1845
1913
|
done
|
|
1846
1914
|
|
|
@@ -1860,8 +1928,10 @@ cmd_detect() {
|
|
|
1860
1928
|
done
|
|
1861
1929
|
[[ $already -eq 1 ]] && continue
|
|
1862
1930
|
remote=$(cd "$d" && git remote get-url origin 2>/dev/null || echo "local://$d")
|
|
1863
|
-
|
|
1864
|
-
|
|
1931
|
+
local branch
|
|
1932
|
+
branch=$(git_default_branch "$d")
|
|
1933
|
+
yaml_add_repo "$remote" "$name" "$name" "" "$branch"
|
|
1934
|
+
ui_step_done "Found:" "$name ($remote, branch: $branch)"
|
|
1865
1935
|
found=$((found + 1))
|
|
1866
1936
|
done
|
|
1867
1937
|
fi
|
|
@@ -1894,6 +1964,7 @@ cmd_clone() {
|
|
|
1894
1964
|
while [[ $# -gt 0 ]]; do
|
|
1895
1965
|
case "$1" in
|
|
1896
1966
|
--tag)
|
|
1967
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
1897
1968
|
tag="$2"
|
|
1898
1969
|
shift 2
|
|
1899
1970
|
;;
|
|
@@ -1966,7 +2037,13 @@ cmd_clone() {
|
|
|
1966
2037
|
local clone_err
|
|
1967
2038
|
if clone_err=$(git clone --quiet "$url" "$full_path" 2>&1); then
|
|
1968
2039
|
ui_spinner_stop
|
|
1969
|
-
|
|
2040
|
+
# Detect and store the repo's default branch
|
|
2041
|
+
local detected_branch
|
|
2042
|
+
detected_branch=$(git_default_branch "$full_path")
|
|
2043
|
+
if [[ -n "$detected_branch" ]]; then
|
|
2044
|
+
YAML_REPO_BRANCHES[$repo]="$detected_branch"
|
|
2045
|
+
fi
|
|
2046
|
+
ui_step_done "Cloned:" "$path (branch: ${detected_branch:-$YAML_DEFAULTS_BRANCH})"
|
|
1970
2047
|
success_count=$((success_count + 1))
|
|
1971
2048
|
else
|
|
1972
2049
|
ui_spinner_error "Failed to clone: $path"
|
|
@@ -1977,9 +2054,12 @@ cmd_clone() {
|
|
|
1977
2054
|
fi
|
|
1978
2055
|
done <<< "$repos"
|
|
1979
2056
|
|
|
1980
|
-
#
|
|
1981
|
-
# newly cloned repos appear in the context immediately.
|
|
2057
|
+
# Persist any newly detected per-repo branches and regenerate CLAUDE.md
|
|
2058
|
+
# so newly cloned repos appear in the context immediately.
|
|
1982
2059
|
if [[ $fail_count -eq 0 ]] && { [[ $success_count -gt 0 ]] || [[ $skip_count -gt 0 ]]; }; then
|
|
2060
|
+
if [[ $success_count -gt 0 ]]; then
|
|
2061
|
+
config_save
|
|
2062
|
+
fi
|
|
1983
2063
|
context_regenerate_silent
|
|
1984
2064
|
fi
|
|
1985
2065
|
|
|
@@ -2011,6 +2091,7 @@ cmd_status() {
|
|
|
2011
2091
|
while [[ $# -gt 0 ]]; do
|
|
2012
2092
|
case "$1" in
|
|
2013
2093
|
--tag)
|
|
2094
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
2014
2095
|
tag="$2"
|
|
2015
2096
|
shift 2
|
|
2016
2097
|
;;
|
|
@@ -2105,6 +2186,7 @@ cmd_branch() {
|
|
|
2105
2186
|
while [[ $# -gt 0 ]]; do
|
|
2106
2187
|
case "$1" in
|
|
2107
2188
|
--tag)
|
|
2189
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
2108
2190
|
tag="$2"
|
|
2109
2191
|
shift 2
|
|
2110
2192
|
;;
|
|
@@ -2209,6 +2291,7 @@ cmd_checkout() {
|
|
|
2209
2291
|
while [[ $# -gt 0 ]]; do
|
|
2210
2292
|
case "$1" in
|
|
2211
2293
|
--tag)
|
|
2294
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
2212
2295
|
tag="$2"
|
|
2213
2296
|
shift 2
|
|
2214
2297
|
;;
|
|
@@ -2268,6 +2351,12 @@ cmd_checkout() {
|
|
|
2268
2351
|
continue
|
|
2269
2352
|
fi
|
|
2270
2353
|
|
|
2354
|
+
# Resolve "default" to each repo's own default branch
|
|
2355
|
+
local target="$branch_name"
|
|
2356
|
+
if [[ "$target" == "default" ]]; then
|
|
2357
|
+
target=$(config_repo_default_branch "$repo")
|
|
2358
|
+
fi
|
|
2359
|
+
|
|
2271
2360
|
# Check for uncommitted changes
|
|
2272
2361
|
if git_is_dirty "$full_path" && [[ $force -eq 0 ]]; then
|
|
2273
2362
|
ui_step_error "Uncommitted changes: $path"
|
|
@@ -2277,15 +2366,15 @@ cmd_checkout() {
|
|
|
2277
2366
|
fi
|
|
2278
2367
|
|
|
2279
2368
|
# Check if branch exists
|
|
2280
|
-
if ! git_branch_exists "$full_path" "$
|
|
2281
|
-
ui_step_error "Branch not found: $path"
|
|
2369
|
+
if ! git_branch_exists "$full_path" "$target"; then
|
|
2370
|
+
ui_step_error "Branch not found: $path ($target)"
|
|
2282
2371
|
fail_count=$((fail_count + 1))
|
|
2283
2372
|
continue
|
|
2284
2373
|
fi
|
|
2285
2374
|
|
|
2286
2375
|
# Checkout
|
|
2287
|
-
if git_checkout "$full_path" "$
|
|
2288
|
-
ui_step_done "Checked out:" "$path → $
|
|
2376
|
+
if git_checkout "$full_path" "$target"; then
|
|
2377
|
+
ui_step_done "Checked out:" "$path → $target"
|
|
2289
2378
|
success_count=$((success_count + 1))
|
|
2290
2379
|
else
|
|
2291
2380
|
ui_step_error "Failed: $path - $GIT_ERROR"
|
|
@@ -2323,6 +2412,7 @@ cmd_sync() {
|
|
|
2323
2412
|
while [[ $# -gt 0 ]]; do
|
|
2324
2413
|
case "$1" in
|
|
2325
2414
|
--tag)
|
|
2415
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
2326
2416
|
tag="$2"
|
|
2327
2417
|
shift 2
|
|
2328
2418
|
;;
|
|
@@ -2429,6 +2519,7 @@ cmd_exec() {
|
|
|
2429
2519
|
while [[ $# -gt 0 ]]; do
|
|
2430
2520
|
case "$1" in
|
|
2431
2521
|
--tag)
|
|
2522
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
2432
2523
|
tag="$2"
|
|
2433
2524
|
shift 2
|
|
2434
2525
|
;;
|
|
@@ -2499,7 +2590,7 @@ cmd_exec() {
|
|
|
2499
2590
|
local output
|
|
2500
2591
|
local exit_code
|
|
2501
2592
|
|
|
2502
|
-
if output=$(cd "$full_path" &&
|
|
2593
|
+
if output=$(cd "$full_path" && bash -c "$command" 2>&1); then
|
|
2503
2594
|
exit_code=0
|
|
2504
2595
|
else
|
|
2505
2596
|
exit_code=$?
|
|
@@ -2549,14 +2640,17 @@ cmd_add() {
|
|
|
2549
2640
|
while [[ $# -gt 0 ]]; do
|
|
2550
2641
|
case "$1" in
|
|
2551
2642
|
--tags)
|
|
2643
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tags requires a value"; return 1; }
|
|
2552
2644
|
tags="$2"
|
|
2553
2645
|
shift 2
|
|
2554
2646
|
;;
|
|
2555
2647
|
--path)
|
|
2648
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --path requires a value"; return 1; }
|
|
2556
2649
|
path="$2"
|
|
2557
2650
|
shift 2
|
|
2558
2651
|
;;
|
|
2559
2652
|
--depends-on)
|
|
2653
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --depends-on requires a value"; return 1; }
|
|
2560
2654
|
deps="$2"
|
|
2561
2655
|
shift 2
|
|
2562
2656
|
;;
|
|
@@ -2643,6 +2737,7 @@ cmd_list() {
|
|
|
2643
2737
|
while [[ $# -gt 0 ]]; do
|
|
2644
2738
|
case "$1" in
|
|
2645
2739
|
--tag)
|
|
2740
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
2646
2741
|
tag="$2"
|
|
2647
2742
|
shift 2
|
|
2648
2743
|
;;
|
|
@@ -2899,6 +2994,17 @@ _context_write_file() {
|
|
|
2899
2994
|
url=$(yaml_get_url "$i")
|
|
2900
2995
|
full_path="$REVO_REPOS_DIR/$path"
|
|
2901
2996
|
|
|
2997
|
+
local branch
|
|
2998
|
+
branch=$(yaml_get_branch "$i")
|
|
2999
|
+
|
|
3000
|
+
# Backfill: detect default branch for cloned repos that don't have one stored
|
|
3001
|
+
if [[ -z "$branch" ]] && [[ -d "$full_path" ]]; then
|
|
3002
|
+
branch=$(git_default_branch "$full_path")
|
|
3003
|
+
if [[ -n "$branch" ]]; then
|
|
3004
|
+
YAML_REPO_BRANCHES[$i]="$branch"
|
|
3005
|
+
fi
|
|
3006
|
+
fi
|
|
3007
|
+
|
|
2902
3008
|
{
|
|
2903
3009
|
printf '### %s\n' "$path"
|
|
2904
3010
|
|
|
@@ -2906,6 +3012,9 @@ _context_write_file() {
|
|
|
2906
3012
|
printf -- '- **Tags:** %s\n' "$tags"
|
|
2907
3013
|
fi
|
|
2908
3014
|
printf -- '- **Path:** repos/%s\n' "$path"
|
|
3015
|
+
if [[ -n "$branch" ]] && [[ "$branch" != "$YAML_DEFAULTS_BRANCH" ]]; then
|
|
3016
|
+
printf -- '- **Default branch:** %s\n' "$branch"
|
|
3017
|
+
fi
|
|
2909
3018
|
if [[ -n "$deps" ]]; then
|
|
2910
3019
|
printf -- '- **Depends on:** %s\n' "$deps"
|
|
2911
3020
|
fi
|
|
@@ -3031,38 +3140,87 @@ _context_write_file() {
|
|
|
3031
3140
|
done
|
|
3032
3141
|
fi
|
|
3033
3142
|
|
|
3034
|
-
# Agent instructions
|
|
3143
|
+
# Agent instructions — directive workflows for Claude Code
|
|
3035
3144
|
{
|
|
3036
3145
|
printf '\n'
|
|
3037
3146
|
printf '## Agent Instructions\n'
|
|
3038
3147
|
printf '\n'
|
|
3039
|
-
printf '
|
|
3148
|
+
printf 'You are working in a multi-repo workspace managed by **revo**. Do NOT explore\n'
|
|
3149
|
+
printf 'to figure out what revo is — it is a CLI tool already installed in the terminal. Use it.\n'
|
|
3040
3150
|
printf '\n'
|
|
3041
|
-
printf '
|
|
3042
|
-
printf '2. Check `.revo/features/` for active feature contexts\n'
|
|
3043
|
-
printf '3. Follow the dependency order above when making cross-repo changes\n'
|
|
3044
|
-
printf '4. Each repo may have its own CLAUDE.md with repo-specific instructions\n'
|
|
3045
|
-
printf '5. Use `revo status` to check state across all repos\n'
|
|
3046
|
-
printf '6. Use `revo commit "msg"` to commit across all repos at once\n'
|
|
3047
|
-
printf '7. Use `revo feature <name>` to start a coordinated feature workspace\n'
|
|
3048
|
-
printf '8. Use `revo pr "title"` to open coordinated pull requests\n'
|
|
3049
|
-
printf '9. Use `revo workspace <name>` to get a full-copy isolated workspace\n'
|
|
3050
|
-
printf ' under `.revo/workspaces/<name>/` (zero bootstrap, .env included)\n'
|
|
3151
|
+
printf '### Critical Rules\n'
|
|
3051
3152
|
printf '\n'
|
|
3052
|
-
printf '
|
|
3153
|
+
printf -- '- ALWAYS read `.revo/features/<name>.md` before starting work on any feature. It contains the plan.\n'
|
|
3154
|
+
printf -- '- NEVER ask "what is the scope?" for a feature that has a feature file. The file IS the scope.\n'
|
|
3155
|
+
printf -- '- When uncertain about revo commands, run `revo --help` or read `.revo/COMMANDS.md`.\n'
|
|
3156
|
+
printf -- '- Use `revo` commands for all cross-repo git operations. Do not manually cd into each repo and run git.\n'
|
|
3157
|
+
printf -- '- Each repo may have its own CLAUDE.md with repo-specific instructions — read it when working in that repo.\n'
|
|
3158
|
+
printf -- '- Follow the dependency order above when making cross-repo changes.\n'
|
|
3159
|
+
printf '\n'
|
|
3160
|
+
printf '### Workflows\n'
|
|
3161
|
+
printf '\n'
|
|
3162
|
+
printf '**"Work on feature X" or "Begin work on X":**\n'
|
|
3163
|
+
printf '1. Read `.revo/features/X.md` for the plan (if it does not exist, run `revo feature X` first)\n'
|
|
3164
|
+
printf '2. Run `revo status` to confirm you are on the right branches\n'
|
|
3165
|
+
printf '3. Start implementing according to the plan. Follow the dependency order in this file.\n'
|
|
3166
|
+
printf '\n'
|
|
3167
|
+
printf '**"Work on feature X use workspaces" or "use workspace":**\n'
|
|
3168
|
+
printf '1. Run `revo workspace X` to create an isolated copy at `.revo/workspaces/X/`\n'
|
|
3169
|
+
printf '2. cd into `.revo/workspaces/X/` — all subsequent revo commands operate on the workspace\n'
|
|
3170
|
+
printf '3. Read `.revo/features/X.md` (in the workspace root) for context\n'
|
|
3171
|
+
printf '4. Implement the feature. Commit with `revo commit "msg"` when ready.\n'
|
|
3172
|
+
printf '\n'
|
|
3173
|
+
printf '**"Create a feature for X" or "Plan feature X":**\n'
|
|
3174
|
+
printf '1. Run `revo feature X` to create branches and `.revo/features/X.md`\n'
|
|
3175
|
+
printf '2. Edit `.revo/features/X.md` with the plan (scope, affected repos, approach)\n'
|
|
3176
|
+
printf '\n'
|
|
3177
|
+
printf '**"Commit" or "Save progress":**\n'
|
|
3178
|
+
printf '1. Run `revo commit "descriptive message"` — stages and commits all dirty repos\n'
|
|
3179
|
+
printf '\n'
|
|
3180
|
+
printf '**"Push and PR" or "Open pull requests":**\n'
|
|
3181
|
+
printf '1. Run `revo push` to push all branches\n'
|
|
3182
|
+
printf '2. Run `revo pr "PR title"` to create coordinated, cross-referenced PRs\n'
|
|
3183
|
+
printf '\n'
|
|
3184
|
+
printf '**"Status" or "Sync":**\n'
|
|
3185
|
+
printf '1. `revo status` — branch and dirty state across all repos\n'
|
|
3186
|
+
printf '2. `revo sync` — pull latest across all repos\n'
|
|
3187
|
+
printf '\n'
|
|
3188
|
+
printf '### Quick Reference\n'
|
|
3189
|
+
printf '\n'
|
|
3190
|
+
printf 'Run `revo --help` for all commands. Key ones: `status`, `commit`, `push`,\n'
|
|
3191
|
+
printf '`pr`, `feature`, `workspace`, `sync`, `exec`, `checkout default`, `issue list`.\n'
|
|
3192
|
+
printf 'All commands accept `--tag TAG` to filter by repo tag.\n'
|
|
3193
|
+
printf 'See `.revo/COMMANDS.md` for the full command reference with examples.\n'
|
|
3194
|
+
printf '\n'
|
|
3195
|
+
printf '%s\n' "$CONTEXT_END_MARKER"
|
|
3196
|
+
} >> "$output"
|
|
3197
|
+
|
|
3198
|
+
# Splice the auto block into the target file, preserving any user content.
|
|
3199
|
+
_context_merge_into "$output" "$target"
|
|
3200
|
+
rm -f "$output"
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
# Write the .revo/COMMANDS.md reference file. Detailed command docs that
|
|
3204
|
+
# Claude Code can read on-demand, kept out of the main CLAUDE.md so the
|
|
3205
|
+
# agent sees directive workflows first, not a wall of syntax.
|
|
3206
|
+
_context_write_commands_file() {
|
|
3207
|
+
local target="$REVO_WORKSPACE_ROOT/.revo/COMMANDS.md"
|
|
3208
|
+
mkdir -p "$REVO_WORKSPACE_ROOT/.revo"
|
|
3209
|
+
|
|
3210
|
+
{
|
|
3211
|
+
printf '# revo Command Reference\n'
|
|
3053
3212
|
printf '\n'
|
|
3054
|
-
printf 'This workspace is managed by revo.\n'
|
|
3055
3213
|
printf 'Source: https://github.com/jippylong12/revo\n'
|
|
3056
3214
|
printf '\n'
|
|
3057
|
-
printf '
|
|
3215
|
+
printf '## Setup\n'
|
|
3058
3216
|
printf '\n'
|
|
3059
|
-
printf '**Setup:**\n'
|
|
3060
3217
|
printf -- '- `revo add <git-url> --tags <tags> [--depends-on <repo>]` — add a repo\n'
|
|
3061
3218
|
printf -- '- `revo clone` — clone all configured repos\n'
|
|
3062
|
-
printf -- '- `revo context` — regenerate
|
|
3219
|
+
printf -- '- `revo context` — regenerate workspace CLAUDE.md\n'
|
|
3063
3220
|
printf -- '- `revo detect` — bootstrap around existing repos in cwd\n'
|
|
3064
3221
|
printf '\n'
|
|
3065
|
-
printf '
|
|
3222
|
+
printf '## Daily Workflow\n'
|
|
3223
|
+
printf '\n'
|
|
3066
3224
|
printf -- '- `revo status` — branch and dirty state across all repos\n'
|
|
3067
3225
|
printf -- '- `revo sync` — pull latest across all repos\n'
|
|
3068
3226
|
printf -- '- `revo feature <name> [--tag t]` — create feature branch across repos\n'
|
|
@@ -3071,53 +3229,64 @@ _context_write_file() {
|
|
|
3071
3229
|
printf -- '- `revo pr "title" [--tag t]` — create coordinated PRs via gh CLI\n'
|
|
3072
3230
|
printf -- '- `revo exec "cmd" [--tag t]` — run command in filtered repos\n'
|
|
3073
3231
|
printf -- '- `revo checkout <branch> [--tag t]` — switch branch across repos\n'
|
|
3232
|
+
printf -- '- `revo checkout default` — switch each repo to its own default branch (handles mixed main/master)\n'
|
|
3233
|
+
printf '\n'
|
|
3234
|
+
printf '## Workspaces\n'
|
|
3074
3235
|
printf '\n'
|
|
3075
|
-
printf '**Workspaces (full-copy isolated workspaces):**\n'
|
|
3076
3236
|
printf -- '- `revo workspace <name> [--tag t]` — full copy of repos into `.revo/workspaces/<name>/` on `feature/<name>`\n'
|
|
3077
3237
|
printf -- '- `revo workspaces` — list active workspaces with branch and dirty state\n'
|
|
3078
3238
|
printf -- '- `revo workspace <name> --delete [--force]` — remove a workspace\n'
|
|
3079
3239
|
printf -- '- `revo workspace --clean` — remove workspaces whose branches are merged\n'
|
|
3080
3240
|
printf '\n'
|
|
3081
3241
|
printf 'Workspaces hardlink-copy everything (including `.env`, `node_modules`,\n'
|
|
3082
|
-
printf 'build artifacts) so
|
|
3242
|
+
printf 'build artifacts) so you can start work with zero bootstrap. Run\n'
|
|
3083
3243
|
printf '`revo` from inside `.revo/workspaces/<name>/` and it operates on the\n'
|
|
3084
3244
|
printf 'workspace copies, not the source tree.\n'
|
|
3085
3245
|
printf '\n'
|
|
3086
|
-
printf '
|
|
3246
|
+
printf '## Issues (cross-repo, via gh CLI)\n'
|
|
3247
|
+
printf '\n'
|
|
3087
3248
|
printf -- '- `revo issue list [--tag t] [--state open|closed|all] [--label L] [--json]` — list issues across repos\n'
|
|
3088
3249
|
printf -- '- `revo issue create --repo <name> "title" [--body b] [--label L] [--feature F]` — create in one repo\n'
|
|
3089
3250
|
printf -- '- `revo issue create --tag <t> "title" [--body b] [--feature F]` — create in every matching repo, cross-referenced\n'
|
|
3090
3251
|
printf '\n'
|
|
3091
3252
|
printf 'Use `--json` on `revo issue list` to get a flat JSON array (each entry has\n'
|
|
3092
|
-
printf 'a `repo` field) — easy to filter or pipe into jq
|
|
3093
|
-
printf 'cross-repo issue state.\n'
|
|
3253
|
+
printf 'a `repo` field) — easy to filter or pipe into jq.\n'
|
|
3094
3254
|
printf '\n'
|
|
3095
|
-
printf '
|
|
3255
|
+
printf '## Tag Filtering\n'
|
|
3096
3256
|
printf '\n'
|
|
3097
3257
|
printf 'All commands support `--tag <tag>` to target specific repos:\n'
|
|
3098
3258
|
printf '\n'
|
|
3099
3259
|
printf '```\n'
|
|
3100
3260
|
printf 'revo exec "npm test" --tag frontend\n'
|
|
3101
3261
|
printf 'revo sync --tag backend\n'
|
|
3102
|
-
printf 'revo
|
|
3262
|
+
printf 'revo commit "fix: typo" --tag api\n'
|
|
3103
3263
|
printf '```\n'
|
|
3104
3264
|
printf '\n'
|
|
3105
|
-
printf '
|
|
3265
|
+
printf '## Feature Workflow\n'
|
|
3106
3266
|
printf '\n'
|
|
3107
3267
|
printf '```\n'
|
|
3108
|
-
printf 'revo feature my-feature # branches all repos\n'
|
|
3109
|
-
printf '# edit .revo/features/my-feature.md
|
|
3268
|
+
printf 'revo feature my-feature # branches all repos, writes .revo/features/my-feature.md\n'
|
|
3269
|
+
printf '# edit .revo/features/my-feature.md with your plan\n'
|
|
3110
3270
|
printf '# work across repos\n'
|
|
3111
3271
|
printf 'revo commit "feat: my feature" # commits all dirty repos\n'
|
|
3272
|
+
printf 'revo push # push all branches\n'
|
|
3112
3273
|
printf 'revo pr "My feature" # coordinated PRs\n'
|
|
3113
3274
|
printf '```\n'
|
|
3114
3275
|
printf '\n'
|
|
3115
|
-
printf '
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3276
|
+
printf '## Workspace Workflow\n'
|
|
3277
|
+
printf '\n'
|
|
3278
|
+
printf '```\n'
|
|
3279
|
+
printf 'revo workspace my-feature # full copy to .revo/workspaces/my-feature/\n'
|
|
3280
|
+
printf 'cd .revo/workspaces/my-feature # enter the workspace\n'
|
|
3281
|
+
printf 'revo status # operates on workspace repos\n'
|
|
3282
|
+
printf '# work across repos\n'
|
|
3283
|
+
printf 'revo commit "feat: my feature" # commits workspace repos\n'
|
|
3284
|
+
printf 'revo push # push workspace branches\n'
|
|
3285
|
+
printf 'revo pr "My feature" # coordinated PRs from workspace\n'
|
|
3286
|
+
printf 'cd ../../.. # back to main workspace\n'
|
|
3287
|
+
printf 'revo workspace my-feature --delete # clean up\n'
|
|
3288
|
+
printf '```\n'
|
|
3289
|
+
} > "$target"
|
|
3121
3290
|
}
|
|
3122
3291
|
|
|
3123
3292
|
cmd_context() {
|
|
@@ -3151,9 +3320,13 @@ cmd_context() {
|
|
|
3151
3320
|
|
|
3152
3321
|
ui_spinner_start "Scanning $YAML_REPO_COUNT repositories..."
|
|
3153
3322
|
_context_write_file "$output"
|
|
3323
|
+
# Persist any newly detected per-repo branches back to revo.yaml
|
|
3324
|
+
config_save
|
|
3154
3325
|
ui_spinner_stop
|
|
3326
|
+
_context_write_commands_file
|
|
3155
3327
|
ui_step_done "Scanned:" "$YAML_REPO_COUNT repositories"
|
|
3156
3328
|
ui_step_done "Wrote:" "CLAUDE.md"
|
|
3329
|
+
ui_step_done "Wrote:" ".revo/COMMANDS.md"
|
|
3157
3330
|
|
|
3158
3331
|
if [[ $CONTEXT_CYCLE -eq 1 ]]; then
|
|
3159
3332
|
ui_step_error "Dependency cycle detected - see CLAUDE.md for order"
|
|
@@ -3172,6 +3345,7 @@ context_regenerate_silent() {
|
|
|
3172
3345
|
|
|
3173
3346
|
local output="$REVO_WORKSPACE_ROOT/CLAUDE.md"
|
|
3174
3347
|
_context_write_file "$output"
|
|
3348
|
+
_context_write_commands_file
|
|
3175
3349
|
ui_info "$(ui_dim "Regenerated CLAUDE.md for Claude Code")"
|
|
3176
3350
|
return 0
|
|
3177
3351
|
}
|
|
@@ -3187,6 +3361,7 @@ cmd_feature() {
|
|
|
3187
3361
|
while [[ $# -gt 0 ]]; do
|
|
3188
3362
|
case "$1" in
|
|
3189
3363
|
--tag)
|
|
3364
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
3190
3365
|
tag="$2"
|
|
3191
3366
|
shift 2
|
|
3192
3367
|
;;
|
|
@@ -3286,10 +3461,10 @@ cmd_feature() {
|
|
|
3286
3461
|
printf '# Feature: %s\n' "$name"
|
|
3287
3462
|
printf '\n'
|
|
3288
3463
|
printf '## Status\n'
|
|
3289
|
-
printf '- Created: %s\n' "$timestamp"
|
|
3290
|
-
printf '- Branch: %s\n' "$branch"
|
|
3464
|
+
printf -- '- Created: %s\n' "$timestamp"
|
|
3465
|
+
printf -- '- Branch: %s\n' "$branch"
|
|
3291
3466
|
if [[ -n "$tag" ]]; then
|
|
3292
|
-
printf '- Tag filter: %s\n' "$tag"
|
|
3467
|
+
printf -- '- Tag filter: %s\n' "$tag"
|
|
3293
3468
|
fi
|
|
3294
3469
|
printf '\n'
|
|
3295
3470
|
printf '## Repos\n'
|
|
@@ -3348,6 +3523,7 @@ cmd_commit() {
|
|
|
3348
3523
|
while [[ $# -gt 0 ]]; do
|
|
3349
3524
|
case "$1" in
|
|
3350
3525
|
--tag)
|
|
3526
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
3351
3527
|
tag="$2"
|
|
3352
3528
|
shift 2
|
|
3353
3529
|
;;
|
|
@@ -3455,6 +3631,7 @@ cmd_push() {
|
|
|
3455
3631
|
while [[ $# -gt 0 ]]; do
|
|
3456
3632
|
case "$1" in
|
|
3457
3633
|
--tag)
|
|
3634
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
3458
3635
|
tag="$2"
|
|
3459
3636
|
shift 2
|
|
3460
3637
|
;;
|
|
@@ -3573,10 +3750,12 @@ cmd_pr() {
|
|
|
3573
3750
|
while [[ $# -gt 0 ]]; do
|
|
3574
3751
|
case "$1" in
|
|
3575
3752
|
--tag)
|
|
3753
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
3576
3754
|
tag="$2"
|
|
3577
3755
|
shift 2
|
|
3578
3756
|
;;
|
|
3579
3757
|
--body)
|
|
3758
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --body requires a value"; return 1; }
|
|
3580
3759
|
body="$2"
|
|
3581
3760
|
shift 2
|
|
3582
3761
|
;;
|
|
@@ -3814,10 +3993,10 @@ _issue_list() {
|
|
|
3814
3993
|
|
|
3815
3994
|
while [[ $# -gt 0 ]]; do
|
|
3816
3995
|
case "$1" in
|
|
3817
|
-
--tag) tag="$2"; shift 2 ;;
|
|
3818
|
-
--state) state="$2"; shift 2 ;;
|
|
3819
|
-
--label) label="$2"; shift 2 ;;
|
|
3820
|
-
--limit) limit="$2"; shift 2 ;;
|
|
3996
|
+
--tag) [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }; tag="$2"; shift 2 ;;
|
|
3997
|
+
--state) [[ $# -lt 2 ]] && { ui_step_error "Option --state requires a value"; return 1; }; state="$2"; shift 2 ;;
|
|
3998
|
+
--label) [[ $# -lt 2 ]] && { ui_step_error "Option --label requires a value"; return 1; }; label="$2"; shift 2 ;;
|
|
3999
|
+
--limit) [[ $# -lt 2 ]] && { ui_step_error "Option --limit requires a value"; return 1; }; limit="$2"; shift 2 ;;
|
|
3821
4000
|
--json) as_json=1; shift ;;
|
|
3822
4001
|
--help|-h) _issue_help; return 0 ;;
|
|
3823
4002
|
*) ui_step_error "Unknown option: $1"; return 1 ;;
|
|
@@ -3981,12 +4160,12 @@ _issue_create() {
|
|
|
3981
4160
|
|
|
3982
4161
|
while [[ $# -gt 0 ]]; do
|
|
3983
4162
|
case "$1" in
|
|
3984
|
-
--repo) repo="$2"; shift 2 ;;
|
|
3985
|
-
--tag) tag="$2"; shift 2 ;;
|
|
3986
|
-
--body) body="$2"; shift 2 ;;
|
|
3987
|
-
--label) labels="$2"; shift 2 ;;
|
|
3988
|
-
--assignee) assignee="$2"; shift 2 ;;
|
|
3989
|
-
--feature) feature="$2"; shift 2 ;;
|
|
4163
|
+
--repo) [[ $# -lt 2 ]] && { ui_step_error "Option --repo requires a value"; return 1; }; repo="$2"; shift 2 ;;
|
|
4164
|
+
--tag) [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }; tag="$2"; shift 2 ;;
|
|
4165
|
+
--body) [[ $# -lt 2 ]] && { ui_step_error "Option --body requires a value"; return 1; }; body="$2"; shift 2 ;;
|
|
4166
|
+
--label) [[ $# -lt 2 ]] && { ui_step_error "Option --label requires a value"; return 1; }; labels="$2"; shift 2 ;;
|
|
4167
|
+
--assignee) [[ $# -lt 2 ]] && { ui_step_error "Option --assignee requires a value"; return 1; }; assignee="$2"; shift 2 ;;
|
|
4168
|
+
--feature) [[ $# -lt 2 ]] && { ui_step_error "Option --feature requires a value"; return 1; }; feature="$2"; shift 2 ;;
|
|
3990
4169
|
--help|-h) _issue_help; return 0 ;;
|
|
3991
4170
|
-*) ui_step_error "Unknown option: $1"; return 1 ;;
|
|
3992
4171
|
*)
|
|
@@ -4744,6 +4923,7 @@ cmd_workspace() {
|
|
|
4744
4923
|
while [[ $# -gt 0 ]]; do
|
|
4745
4924
|
case "$1" in
|
|
4746
4925
|
--tag)
|
|
4926
|
+
[[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
|
|
4747
4927
|
tag="$2"
|
|
4748
4928
|
shift 2
|
|
4749
4929
|
;;
|