@revotools/cli 0.3.0 → 0.4.0

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.3.0"
11
+ REVO_VERSION="0.4.0"
12
12
 
13
13
 
14
14
  # === lib/ui.sh ===
@@ -794,8 +794,52 @@ config_init() {
794
794
  # Write config
795
795
  yaml_write "$REVO_CONFIG_FILE"
796
796
 
797
- # Create .gitignore
798
- printf 'repos/\n.revo/\n' > "$dir/.gitignore"
797
+ # Merge revo's required entries into .gitignore without clobbering
798
+ # any existing user entries.
799
+ config_ensure_gitignore "$dir/.gitignore"
800
+
801
+ return 0
802
+ }
803
+
804
+ # Ensure the given .gitignore contains the entries revo needs
805
+ # (`repos/` and `.revo/`). Existing content is preserved; only missing
806
+ # entries are appended. Creates the file if it doesn't exist.
807
+ # Usage: config_ensure_gitignore "/path/to/.gitignore"
808
+ config_ensure_gitignore() {
809
+ local gitignore="$1"
810
+ local needed
811
+ needed=$(printf 'repos/\n.revo/\n')
812
+
813
+ if [[ ! -f "$gitignore" ]]; then
814
+ printf '%s\n' "$needed" > "$gitignore"
815
+ return 0
816
+ fi
817
+
818
+ # Append only the entries that aren't already present (exact line match,
819
+ # ignoring leading/trailing whitespace and comments).
820
+ local entry has_entry needs_newline=0
821
+ # If the file is non-empty and doesn't end in a newline, we need to add
822
+ # one before appending so we don't merge into an existing line.
823
+ if [[ -s "$gitignore" ]] && [[ -n "$(tail -c1 "$gitignore" 2>/dev/null)" ]]; then
824
+ needs_newline=1
825
+ fi
826
+
827
+ local appended=0
828
+ while IFS= read -r entry; do
829
+ [[ -z "$entry" ]] && continue
830
+ has_entry=$(awk -v e="$entry" '
831
+ { sub(/^[[:space:]]+/, ""); sub(/[[:space:]]+$/, "") }
832
+ $0 == e { found = 1; exit }
833
+ END { exit !found }
834
+ ' "$gitignore" && printf 'yes' || printf 'no')
835
+ if [[ "$has_entry" == "no" ]]; then
836
+ if [[ $appended -eq 0 ]] && [[ $needs_newline -eq 1 ]]; then
837
+ printf '\n' >> "$gitignore"
838
+ fi
839
+ printf '%s\n' "$entry" >> "$gitignore"
840
+ appended=1
841
+ fi
842
+ done <<< "$needed"
799
843
 
800
844
  return 0
801
845
  }
@@ -1515,45 +1559,13 @@ _init_scan_existing() {
1515
1559
  fi
1516
1560
  }
1517
1561
 
1518
- # Write a Claude-first onboarding CLAUDE.md, or append a revo section if the
1519
- # user already has their own CLAUDE.md in the workspace root.
1562
+ # Write a Claude-first onboarding CLAUDE.md if and only if the workspace
1563
+ # root has no CLAUDE.md yet. Existing files are left alone — `revo context`
1564
+ # will append its marker-wrapped auto block once repos are added.
1520
1565
  _init_write_claude_md() {
1521
1566
  local out="$REVO_WORKSPACE_ROOT/CLAUDE.md"
1522
1567
 
1523
- if [[ -f "$out" ]] && ! grep -q "managed by revo" "$out" 2>/dev/null; then
1524
- if grep -q "Workspace Tool: revo" "$out" 2>/dev/null; then
1525
- return 0
1526
- fi
1527
- cat >> "$out" << 'EOF'
1528
-
1529
- ---
1530
-
1531
- ## Workspace Tool: revo
1532
-
1533
- This workspace uses revo to manage multiple repos.
1534
- Source: https://github.com/jippylong12/revo
1535
-
1536
- ### Setup commands
1537
- - `revo add <git-url> --tags <tags> [--depends-on <repo>]` — add a repo to workspace
1538
- - `revo clone` — clone all configured repos
1539
- - `revo context` — scan repos and regenerate the workspace context section
1540
-
1541
- ### Daily commands
1542
- - `revo status` — branch and dirty state across all repos
1543
- - `revo sync` — pull latest across all repos
1544
- - `revo feature <name>` — create feature branch across all repos
1545
- - `revo commit "msg"` — commit all dirty repos with same message
1546
- - `revo push` — push all repos
1547
- - `revo pr "title"` — create coordinated PRs via gh CLI
1548
- - `revo exec "cmd" --tag <tag>` — run command in filtered repos
1549
-
1550
- ### Working in this workspace
1551
- - Repos live in the repos/ subdirectory (or wherever configured in revo.yaml)
1552
- - Edit files across repos directly
1553
- - Check .revo/features/ for active feature briefs
1554
- - Follow dependency order when making cross-repo changes
1555
- EOF
1556
- ui_step_done "Appended revo section to existing CLAUDE.md"
1568
+ if [[ -f "$out" ]]; then
1557
1569
  return 0
1558
1570
  fi
1559
1571
 
@@ -1693,16 +1705,18 @@ cmd_init() {
1693
1705
  fi
1694
1706
  fi
1695
1707
 
1696
- # Write the Claude-first onboarding CLAUDE.md (or append to user's own).
1697
- _init_write_claude_md
1698
-
1699
- # If we detected repos, immediately generate the full workspace context.
1708
+ # If we detected repos, hand off to cmd_context — it now wraps its output
1709
+ # in BEGIN/END markers and preserves any user content in CLAUDE.md, so we
1710
+ # don't need the onboarding placeholder. When no repos were detected,
1711
+ # write the placeholder so Claude has something to read.
1700
1712
  if [[ $detected_count -gt 0 ]]; then
1701
1713
  ui_bar_line
1702
1714
  cmd_context
1703
1715
  return 0
1704
1716
  fi
1705
1717
 
1718
+ _init_write_claude_md
1719
+
1706
1720
  ui_outro "Workspace initialized! Run 'revo add <url>' to add repositories."
1707
1721
  return 0
1708
1722
  }
@@ -2752,15 +2766,75 @@ _context_topo_sort() {
2752
2766
  return 0
2753
2767
  }
2754
2768
 
2755
- # Write the workspace CLAUDE.md to the given path.
2769
+ # Markers used to delimit revo's auto-generated block inside the workspace
2770
+ # CLAUDE.md. Anything between these markers is owned by `revo context` and
2771
+ # replaced on every regeneration. Anything outside is preserved untouched.
2772
+ CONTEXT_BEGIN_MARKER='<!-- BEGIN revo:auto - regenerated by `revo context`, do not edit between markers -->'
2773
+ CONTEXT_END_MARKER='<!-- END revo:auto -->'
2774
+
2775
+ # Splice an auto-generated block into the target CLAUDE.md without
2776
+ # clobbering user content.
2777
+ # - If target doesn't exist: copy auto file to target.
2778
+ # - If target has BEGIN+END markers: replace the marker block in place.
2779
+ # - Otherwise: append the auto block (with a separator) at the end.
2780
+ # Usage: _context_merge_into "/tmp/auto" "/path/to/CLAUDE.md"
2781
+ _context_merge_into() {
2782
+ local auto_file="$1"
2783
+ local target="$2"
2784
+
2785
+ if [[ ! -f "$target" ]]; then
2786
+ cp "$auto_file" "$target"
2787
+ return 0
2788
+ fi
2789
+
2790
+ if grep -qF "$CONTEXT_BEGIN_MARKER" "$target" 2>/dev/null \
2791
+ && grep -qF "$CONTEXT_END_MARKER" "$target" 2>/dev/null; then
2792
+ local merged
2793
+ merged=$(mktemp -t revo-merge.XXXXXX)
2794
+ awk -v auto="$auto_file" -v begin="$CONTEXT_BEGIN_MARKER" -v end="$CONTEXT_END_MARKER" '
2795
+ BEGIN { in_block = 0 }
2796
+ index($0, begin) == 1 {
2797
+ in_block = 1
2798
+ while ((getline line < auto) > 0) print line
2799
+ close(auto)
2800
+ next
2801
+ }
2802
+ index($0, end) == 1 && in_block {
2803
+ in_block = 0
2804
+ next
2805
+ }
2806
+ !in_block { print }
2807
+ ' "$target" > "$merged"
2808
+ mv "$merged" "$target"
2809
+ return 0
2810
+ fi
2811
+
2812
+ # No markers — append at the end with a blank-line separator. If the
2813
+ # existing file doesn't end in a newline, add one first so we don't merge
2814
+ # into the user's last line.
2815
+ if [[ -s "$target" ]] && [[ -n "$(tail -c1 "$target" 2>/dev/null)" ]]; then
2816
+ printf '\n' >> "$target"
2817
+ fi
2818
+ printf '\n' >> "$target"
2819
+ cat "$auto_file" >> "$target"
2820
+ return 0
2821
+ }
2822
+
2823
+ # Write the workspace CLAUDE.md to the given path. The auto-generated content
2824
+ # is wrapped in BEGIN/END markers and spliced into any existing CLAUDE.md so
2825
+ # user content above and below the markers is preserved across regenerations.
2756
2826
  # Usage: _context_write_file "/path/to/CLAUDE.md"
2757
2827
  _context_write_file() {
2758
- local output="$1"
2828
+ local target="$1"
2759
2829
  local workspace
2760
2830
  workspace=$(config_workspace_name)
2761
2831
 
2762
- # Start file
2832
+ local output
2833
+ output=$(mktemp -t revo-auto.XXXXXX)
2834
+
2835
+ # Start auto block (markers + header)
2763
2836
  {
2837
+ printf '%s\n' "$CONTEXT_BEGIN_MARKER"
2764
2838
  printf '# Workspace Context (auto-generated by revo)\n'
2765
2839
  printf '\n'
2766
2840
  printf 'This workspace contains multiple repositories managed by revo.\n'
@@ -2768,8 +2842,9 @@ _context_write_file() {
2768
2842
  printf 'Workspace: **%s**\n' "$workspace"
2769
2843
  fi
2770
2844
  printf '\n'
2771
- printf '> This file is regenerated by `revo context`. Manual edits below the\n'
2772
- printf '> `## Agent Instructions` section will be preserved across regeneration.\n'
2845
+ printf '> This block is auto-generated by `revo context`. Edits between the\n'
2846
+ printf '> BEGIN/END markers will be lost on regeneration. Add your own content\n'
2847
+ printf '> above or below the markers.\n'
2773
2848
  printf '\n'
2774
2849
  printf '## Repos\n'
2775
2850
  printf '\n'
@@ -2946,7 +3021,13 @@ _context_write_file() {
2946
3021
  printf 'revo commit "feat: my feature" # commits all dirty repos\n'
2947
3022
  printf 'revo pr "My feature" # coordinated PRs\n'
2948
3023
  printf '```\n'
3024
+ printf '\n'
3025
+ printf '%s\n' "$CONTEXT_END_MARKER"
2949
3026
  } >> "$output"
3027
+
3028
+ # Splice the auto block into the target file, preserving any user content.
3029
+ _context_merge_into "$output" "$target"
3030
+ rm -f "$output"
2950
3031
  }
2951
3032
 
2952
3033
  cmd_context() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revotools/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Claude-first multi-repo workspace manager (fork of Mars)",
5
5
  "bin": {
6
6
  "revo": "./dist/revo"