@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.
Files changed (2) hide show
  1. package/dist/revo +246 -66
  2. 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.0"
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 -q "\"$dep\"[[:space:]]*:" "$file" 2>/dev/null
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
- yaml_add_repo "$remote" "$path" "$tags" ""
1737
- ui_step_done "Detected:" "$name → $remote"
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
- yaml_add_repo "$remote" "$name" "$tags" ""
1843
- ui_step_done "Found:" "$name ($remote)"
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
- yaml_add_repo "$remote" "$name" "$name" ""
1864
- ui_step_done "Found:" "$name ($remote)"
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
- ui_step_done "Cloned:" "$path"
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
- # Always regenerate the workspace CLAUDE.md after a clone batch so that
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" "$branch_name"; then
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" "$branch_name"; then
2288
- ui_step_done "Checked out:" "$path → $branch_name"
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" && eval "$command" 2>&1); then
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 + revo tool docs
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 'When working in this workspace:\n'
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 '1. Read this file first to understand the repo structure\n'
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 '## Workspace Tool: revo\n'
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 '### Available commands (run in terminal)\n'
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 this file\n'
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 '**Daily workflow:**\n'
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 Claude can start work with zero bootstrap. Run\n'
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 '**Issues (cross-repo, via gh CLI):**\n'
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 when reasoning about\n'
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 '### Tag filtering\n'
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 branch hotfix --tag api\n'
3262
+ printf 'revo commit "fix: typo" --tag api\n'
3103
3263
  printf '```\n'
3104
3264
  printf '\n'
3105
- printf '### Feature workflow\n'
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 to describe scope\n'
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 '%s\n' "$CONTEXT_END_MARKER"
3116
- } >> "$output"
3117
-
3118
- # Splice the auto block into the target file, preserving any user content.
3119
- _context_merge_into "$output" "$target"
3120
- rm -f "$output"
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
  ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revotools/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Claude-first multi-repo workspace manager (fork of Mars)",
5
5
  "bin": {
6
6
  "revo": "./dist/revo"