@revotools/cli 0.6.1 → 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 +129 -48
  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.1"
11
+ REVO_VERSION="0.6.2"
12
12
 
13
13
 
14
14
  # === lib/ui.sh ===
@@ -911,7 +911,9 @@ config_load() {
911
911
  return 1
912
912
  fi
913
913
 
914
- yaml_parse "$REVO_CONFIG_FILE"
914
+ if ! yaml_parse "$REVO_CONFIG_FILE"; then
915
+ return 1
916
+ fi
915
917
  }
916
918
 
917
919
  # Save configuration
@@ -1361,7 +1363,7 @@ _scan_json_string() {
1361
1363
  _scan_pkg_has_dep() {
1362
1364
  local file="$1"
1363
1365
  local dep="$2"
1364
- grep -q "\"$dep\"[[:space:]]*:" "$file" 2>/dev/null
1366
+ grep -qF "\"$dep\"" "$file" 2>/dev/null
1365
1367
  }
1366
1368
 
1367
1369
  # Detect framework from package.json dependencies
@@ -1962,6 +1964,7 @@ cmd_clone() {
1962
1964
  while [[ $# -gt 0 ]]; do
1963
1965
  case "$1" in
1964
1966
  --tag)
1967
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
1965
1968
  tag="$2"
1966
1969
  shift 2
1967
1970
  ;;
@@ -2088,6 +2091,7 @@ cmd_status() {
2088
2091
  while [[ $# -gt 0 ]]; do
2089
2092
  case "$1" in
2090
2093
  --tag)
2094
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
2091
2095
  tag="$2"
2092
2096
  shift 2
2093
2097
  ;;
@@ -2182,6 +2186,7 @@ cmd_branch() {
2182
2186
  while [[ $# -gt 0 ]]; do
2183
2187
  case "$1" in
2184
2188
  --tag)
2189
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
2185
2190
  tag="$2"
2186
2191
  shift 2
2187
2192
  ;;
@@ -2286,6 +2291,7 @@ cmd_checkout() {
2286
2291
  while [[ $# -gt 0 ]]; do
2287
2292
  case "$1" in
2288
2293
  --tag)
2294
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
2289
2295
  tag="$2"
2290
2296
  shift 2
2291
2297
  ;;
@@ -2406,6 +2412,7 @@ cmd_sync() {
2406
2412
  while [[ $# -gt 0 ]]; do
2407
2413
  case "$1" in
2408
2414
  --tag)
2415
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
2409
2416
  tag="$2"
2410
2417
  shift 2
2411
2418
  ;;
@@ -2512,6 +2519,7 @@ cmd_exec() {
2512
2519
  while [[ $# -gt 0 ]]; do
2513
2520
  case "$1" in
2514
2521
  --tag)
2522
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
2515
2523
  tag="$2"
2516
2524
  shift 2
2517
2525
  ;;
@@ -2582,7 +2590,7 @@ cmd_exec() {
2582
2590
  local output
2583
2591
  local exit_code
2584
2592
 
2585
- if output=$(cd "$full_path" && eval "$command" 2>&1); then
2593
+ if output=$(cd "$full_path" && bash -c "$command" 2>&1); then
2586
2594
  exit_code=0
2587
2595
  else
2588
2596
  exit_code=$?
@@ -2632,14 +2640,17 @@ cmd_add() {
2632
2640
  while [[ $# -gt 0 ]]; do
2633
2641
  case "$1" in
2634
2642
  --tags)
2643
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tags requires a value"; return 1; }
2635
2644
  tags="$2"
2636
2645
  shift 2
2637
2646
  ;;
2638
2647
  --path)
2648
+ [[ $# -lt 2 ]] && { ui_step_error "Option --path requires a value"; return 1; }
2639
2649
  path="$2"
2640
2650
  shift 2
2641
2651
  ;;
2642
2652
  --depends-on)
2653
+ [[ $# -lt 2 ]] && { ui_step_error "Option --depends-on requires a value"; return 1; }
2643
2654
  deps="$2"
2644
2655
  shift 2
2645
2656
  ;;
@@ -2726,6 +2737,7 @@ cmd_list() {
2726
2737
  while [[ $# -gt 0 ]]; do
2727
2738
  case "$1" in
2728
2739
  --tag)
2740
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
2729
2741
  tag="$2"
2730
2742
  shift 2
2731
2743
  ;;
@@ -3128,38 +3140,87 @@ _context_write_file() {
3128
3140
  done
3129
3141
  fi
3130
3142
 
3131
- # Agent instructions + revo tool docs
3143
+ # Agent instructions directive workflows for Claude Code
3132
3144
  {
3133
3145
  printf '\n'
3134
3146
  printf '## Agent Instructions\n'
3135
3147
  printf '\n'
3136
- 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'
3137
3150
  printf '\n'
3138
- printf '1. Read this file first to understand the repo structure\n'
3139
- printf '2. Check `.revo/features/` for active feature contexts\n'
3140
- printf '3. Follow the dependency order above when making cross-repo changes\n'
3141
- printf '4. Each repo may have its own CLAUDE.md with repo-specific instructions\n'
3142
- printf '5. Use `revo status` to check state across all repos\n'
3143
- printf '6. Use `revo commit "msg"` to commit across all repos at once\n'
3144
- printf '7. Use `revo feature <name>` to start a coordinated feature workspace\n'
3145
- printf '8. Use `revo pr "title"` to open coordinated pull requests\n'
3146
- printf '9. Use `revo workspace <name>` to get a full-copy isolated workspace\n'
3147
- printf ' under `.revo/workspaces/<name>/` (zero bootstrap, .env included)\n'
3151
+ printf '### Critical Rules\n'
3148
3152
  printf '\n'
3149
- 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'
3150
3212
  printf '\n'
3151
- printf 'This workspace is managed by revo.\n'
3152
3213
  printf 'Source: https://github.com/jippylong12/revo\n'
3153
3214
  printf '\n'
3154
- printf '### Available commands (run in terminal)\n'
3215
+ printf '## Setup\n'
3155
3216
  printf '\n'
3156
- printf '**Setup:**\n'
3157
3217
  printf -- '- `revo add <git-url> --tags <tags> [--depends-on <repo>]` — add a repo\n'
3158
3218
  printf -- '- `revo clone` — clone all configured repos\n'
3159
- printf -- '- `revo context` — regenerate this file\n'
3219
+ printf -- '- `revo context` — regenerate workspace CLAUDE.md\n'
3160
3220
  printf -- '- `revo detect` — bootstrap around existing repos in cwd\n'
3161
3221
  printf '\n'
3162
- printf '**Daily workflow:**\n'
3222
+ printf '## Daily Workflow\n'
3223
+ printf '\n'
3163
3224
  printf -- '- `revo status` — branch and dirty state across all repos\n'
3164
3225
  printf -- '- `revo sync` — pull latest across all repos\n'
3165
3226
  printf -- '- `revo feature <name> [--tag t]` — create feature branch across repos\n'
@@ -3168,53 +3229,64 @@ _context_write_file() {
3168
3229
  printf -- '- `revo pr "title" [--tag t]` — create coordinated PRs via gh CLI\n'
3169
3230
  printf -- '- `revo exec "cmd" [--tag t]` — run command in filtered repos\n'
3170
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'
3171
3235
  printf '\n'
3172
- printf '**Workspaces (full-copy isolated workspaces):**\n'
3173
3236
  printf -- '- `revo workspace <name> [--tag t]` — full copy of repos into `.revo/workspaces/<name>/` on `feature/<name>`\n'
3174
3237
  printf -- '- `revo workspaces` — list active workspaces with branch and dirty state\n'
3175
3238
  printf -- '- `revo workspace <name> --delete [--force]` — remove a workspace\n'
3176
3239
  printf -- '- `revo workspace --clean` — remove workspaces whose branches are merged\n'
3177
3240
  printf '\n'
3178
3241
  printf 'Workspaces hardlink-copy everything (including `.env`, `node_modules`,\n'
3179
- 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'
3180
3243
  printf '`revo` from inside `.revo/workspaces/<name>/` and it operates on the\n'
3181
3244
  printf 'workspace copies, not the source tree.\n'
3182
3245
  printf '\n'
3183
- printf '**Issues (cross-repo, via gh CLI):**\n'
3246
+ printf '## Issues (cross-repo, via gh CLI)\n'
3247
+ printf '\n'
3184
3248
  printf -- '- `revo issue list [--tag t] [--state open|closed|all] [--label L] [--json]` — list issues across repos\n'
3185
3249
  printf -- '- `revo issue create --repo <name> "title" [--body b] [--label L] [--feature F]` — create in one repo\n'
3186
3250
  printf -- '- `revo issue create --tag <t> "title" [--body b] [--feature F]` — create in every matching repo, cross-referenced\n'
3187
3251
  printf '\n'
3188
3252
  printf 'Use `--json` on `revo issue list` to get a flat JSON array (each entry has\n'
3189
- printf 'a `repo` field) — easy to filter or pipe into jq when reasoning about\n'
3190
- printf 'cross-repo issue state.\n'
3253
+ printf 'a `repo` field) — easy to filter or pipe into jq.\n'
3191
3254
  printf '\n'
3192
- printf '### Tag filtering\n'
3255
+ printf '## Tag Filtering\n'
3193
3256
  printf '\n'
3194
3257
  printf 'All commands support `--tag <tag>` to target specific repos:\n'
3195
3258
  printf '\n'
3196
3259
  printf '```\n'
3197
3260
  printf 'revo exec "npm test" --tag frontend\n'
3198
3261
  printf 'revo sync --tag backend\n'
3199
- printf 'revo branch hotfix --tag api\n'
3262
+ printf 'revo commit "fix: typo" --tag api\n'
3200
3263
  printf '```\n'
3201
3264
  printf '\n'
3202
- printf '### Feature workflow\n'
3265
+ printf '## Feature Workflow\n'
3203
3266
  printf '\n'
3204
3267
  printf '```\n'
3205
- printf 'revo feature my-feature # branches all repos\n'
3206
- 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'
3207
3270
  printf '# work across repos\n'
3208
3271
  printf 'revo commit "feat: my feature" # commits all dirty repos\n'
3272
+ printf 'revo push # push all branches\n'
3209
3273
  printf 'revo pr "My feature" # coordinated PRs\n'
3210
3274
  printf '```\n'
3211
3275
  printf '\n'
3212
- printf '%s\n' "$CONTEXT_END_MARKER"
3213
- } >> "$output"
3214
-
3215
- # Splice the auto block into the target file, preserving any user content.
3216
- _context_merge_into "$output" "$target"
3217
- 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"
3218
3290
  }
3219
3291
 
3220
3292
  cmd_context() {
@@ -3251,8 +3323,10 @@ cmd_context() {
3251
3323
  # Persist any newly detected per-repo branches back to revo.yaml
3252
3324
  config_save
3253
3325
  ui_spinner_stop
3326
+ _context_write_commands_file
3254
3327
  ui_step_done "Scanned:" "$YAML_REPO_COUNT repositories"
3255
3328
  ui_step_done "Wrote:" "CLAUDE.md"
3329
+ ui_step_done "Wrote:" ".revo/COMMANDS.md"
3256
3330
 
3257
3331
  if [[ $CONTEXT_CYCLE -eq 1 ]]; then
3258
3332
  ui_step_error "Dependency cycle detected - see CLAUDE.md for order"
@@ -3271,6 +3345,7 @@ context_regenerate_silent() {
3271
3345
 
3272
3346
  local output="$REVO_WORKSPACE_ROOT/CLAUDE.md"
3273
3347
  _context_write_file "$output"
3348
+ _context_write_commands_file
3274
3349
  ui_info "$(ui_dim "Regenerated CLAUDE.md for Claude Code")"
3275
3350
  return 0
3276
3351
  }
@@ -3286,6 +3361,7 @@ cmd_feature() {
3286
3361
  while [[ $# -gt 0 ]]; do
3287
3362
  case "$1" in
3288
3363
  --tag)
3364
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
3289
3365
  tag="$2"
3290
3366
  shift 2
3291
3367
  ;;
@@ -3447,6 +3523,7 @@ cmd_commit() {
3447
3523
  while [[ $# -gt 0 ]]; do
3448
3524
  case "$1" in
3449
3525
  --tag)
3526
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
3450
3527
  tag="$2"
3451
3528
  shift 2
3452
3529
  ;;
@@ -3554,6 +3631,7 @@ cmd_push() {
3554
3631
  while [[ $# -gt 0 ]]; do
3555
3632
  case "$1" in
3556
3633
  --tag)
3634
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
3557
3635
  tag="$2"
3558
3636
  shift 2
3559
3637
  ;;
@@ -3672,10 +3750,12 @@ cmd_pr() {
3672
3750
  while [[ $# -gt 0 ]]; do
3673
3751
  case "$1" in
3674
3752
  --tag)
3753
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
3675
3754
  tag="$2"
3676
3755
  shift 2
3677
3756
  ;;
3678
3757
  --body)
3758
+ [[ $# -lt 2 ]] && { ui_step_error "Option --body requires a value"; return 1; }
3679
3759
  body="$2"
3680
3760
  shift 2
3681
3761
  ;;
@@ -3913,10 +3993,10 @@ _issue_list() {
3913
3993
 
3914
3994
  while [[ $# -gt 0 ]]; do
3915
3995
  case "$1" in
3916
- --tag) tag="$2"; shift 2 ;;
3917
- --state) state="$2"; shift 2 ;;
3918
- --label) label="$2"; shift 2 ;;
3919
- --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 ;;
3920
4000
  --json) as_json=1; shift ;;
3921
4001
  --help|-h) _issue_help; return 0 ;;
3922
4002
  *) ui_step_error "Unknown option: $1"; return 1 ;;
@@ -4080,12 +4160,12 @@ _issue_create() {
4080
4160
 
4081
4161
  while [[ $# -gt 0 ]]; do
4082
4162
  case "$1" in
4083
- --repo) repo="$2"; shift 2 ;;
4084
- --tag) tag="$2"; shift 2 ;;
4085
- --body) body="$2"; shift 2 ;;
4086
- --label) labels="$2"; shift 2 ;;
4087
- --assignee) assignee="$2"; shift 2 ;;
4088
- --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 ;;
4089
4169
  --help|-h) _issue_help; return 0 ;;
4090
4170
  -*) ui_step_error "Unknown option: $1"; return 1 ;;
4091
4171
  *)
@@ -4843,6 +4923,7 @@ cmd_workspace() {
4843
4923
  while [[ $# -gt 0 ]]; do
4844
4924
  case "$1" in
4845
4925
  --tag)
4926
+ [[ $# -lt 2 ]] && { ui_step_error "Option --tag requires a value"; return 1; }
4846
4927
  tag="$2"
4847
4928
  shift 2
4848
4929
  ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revotools/cli",
3
- "version": "0.6.1",
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"