@revotools/cli 0.2.2 → 0.3.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.
- package/README.md +41 -5
- package/dist/revo +464 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,8 +8,13 @@ Revo is a fork of [Mars](https://github.com/dean0x/mars). It keeps everything
|
|
|
8
8
|
Mars gives you — tag-based filtering, coordinated branches, zero dependencies —
|
|
9
9
|
and adds features designed for working with coding agents:
|
|
10
10
|
|
|
11
|
+
- **`revo init`** auto-detects existing git repos in the current directory and
|
|
12
|
+
bootstraps a workspace around them, no manual `revo add` required.
|
|
13
|
+
- **`revo detect`** does the same on demand for an already-populated folder
|
|
14
|
+
full of clones.
|
|
11
15
|
- **`revo context`** scans your repos and writes a root-level `CLAUDE.md` so
|
|
12
|
-
Claude Code can understand the whole workspace at a glance
|
|
16
|
+
Claude Code can understand the whole workspace at a glance — including
|
|
17
|
+
active feature briefs and a built-in revo command reference.
|
|
13
18
|
- **`revo feature <name>`** creates a coordinated branch and shared context
|
|
14
19
|
file across matching repos.
|
|
15
20
|
- **`revo commit`**, **`revo push`**, **`revo pr`** let you commit, push, and
|
|
@@ -22,6 +27,8 @@ Pure bash 3.2+, works on macOS out of the box, no runtime dependencies beyond
|
|
|
22
27
|
|
|
23
28
|
## Quick Start
|
|
24
29
|
|
|
30
|
+
### From an empty directory
|
|
31
|
+
|
|
25
32
|
```bash
|
|
26
33
|
mkdir my-project && cd my-project
|
|
27
34
|
revo init
|
|
@@ -30,10 +37,23 @@ revo add git@github.com:org/shared-types.git --tags shared
|
|
|
30
37
|
revo add git@github.com:org/backend.git --tags backend,api --depends-on shared-types
|
|
31
38
|
revo add git@github.com:org/frontend.git --tags frontend,web --depends-on backend
|
|
32
39
|
|
|
33
|
-
revo clone # CLAUDE.md is
|
|
40
|
+
revo clone # CLAUDE.md is regenerated after each clone batch
|
|
34
41
|
revo context # regenerate it any time repos change
|
|
35
42
|
```
|
|
36
43
|
|
|
44
|
+
### From a folder you already populated with clones
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd ~/code/my-project # already has frontend/, backend/, shared/ as git repos
|
|
48
|
+
revo init # auto-detects existing repos, links them, writes CLAUDE.md
|
|
49
|
+
# — or —
|
|
50
|
+
revo detect # same idea, runs on its own without prompting
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`init` and `detect` both link root-level repos into `repos/` via relative
|
|
54
|
+
symlinks so the rest of revo's data model keeps working without moving any
|
|
55
|
+
files around.
|
|
56
|
+
|
|
37
57
|
Then point Claude Code at the workspace directory and it will read `CLAUDE.md`
|
|
38
58
|
on its own.
|
|
39
59
|
|
|
@@ -61,13 +81,14 @@ revo pr "Clock endpoint for students" ── coordinated PRs via gh
|
|
|
61
81
|
|
|
62
82
|
## Commands
|
|
63
83
|
|
|
64
|
-
### Workspace
|
|
84
|
+
### Workspace
|
|
65
85
|
|
|
66
86
|
| Command | Description |
|
|
67
87
|
|---------|-------------|
|
|
68
|
-
| `revo init` | Initialize a new workspace |
|
|
88
|
+
| `revo init` | Initialize a new workspace; auto-detects existing git repos in cwd |
|
|
89
|
+
| `revo detect` | Bootstrap revo around git repos already present in cwd |
|
|
69
90
|
| `revo add <url> [--tags t1,t2] [--depends-on r1,r2]` | Add a repository to config |
|
|
70
|
-
| `revo clone [--tag TAG]` | Clone configured repositories |
|
|
91
|
+
| `revo clone [--tag TAG]` | Clone configured repositories (regenerates `CLAUDE.md` after) |
|
|
71
92
|
| `revo list [--tag TAG]` | List configured repositories |
|
|
72
93
|
| `revo status [--tag TAG]` | Show status of all repositories |
|
|
73
94
|
| `revo sync [--tag TAG] [--rebase]` | Pull latest changes |
|
|
@@ -85,6 +106,21 @@ revo pr "Clock endpoint for students" ── coordinated PRs via gh
|
|
|
85
106
|
| `revo push [--tag TAG]` | Push branches across repos |
|
|
86
107
|
| `revo pr <title> [--tag TAG] [--body BODY]` | Create coordinated PRs via `gh` |
|
|
87
108
|
|
|
109
|
+
### What `revo context` detects per repo
|
|
110
|
+
|
|
111
|
+
- **Node.js** — Next.js, Nuxt, Remix, SvelteKit, Astro, Vite, NestJS, Fastify,
|
|
112
|
+
Express, Hono, Angular, React, Vue, Svelte, React Native, Expo
|
|
113
|
+
- **Python** — Django, FastAPI, Flask, Starlette
|
|
114
|
+
- **Go** (module name), **Rust** (Cargo), **Java/Kotlin** (Gradle), **Java**
|
|
115
|
+
(Maven), **Ruby** (Rails, Sinatra), **Swift** (Package.swift)
|
|
116
|
+
- API route directories (`src/routes/`, `src/api/`, `app/api/`, `routes/`,
|
|
117
|
+
`pages/api/`, etc.)
|
|
118
|
+
- Whether the repo ships its own `CLAUDE.md`, `Dockerfile`, or
|
|
119
|
+
`docker-compose.yml`
|
|
120
|
+
- The first non-trivial line of `README.md` as a one-line description
|
|
121
|
+
- Any `.revo/features/*.md` briefs at the workspace root, listed as
|
|
122
|
+
`## Active Features`
|
|
123
|
+
|
|
88
124
|
## Tag Filtering
|
|
89
125
|
|
|
90
126
|
Target subsets of repos using `--tag`:
|
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.
|
|
11
|
+
REVO_VERSION="0.3.0"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
# === lib/ui.sh ===
|
|
@@ -1232,6 +1232,10 @@ _scan_node_framework() {
|
|
|
1232
1232
|
printf 'SvelteKit'
|
|
1233
1233
|
elif _scan_pkg_has_dep "$file" "astro"; then
|
|
1234
1234
|
printf 'Astro'
|
|
1235
|
+
elif _scan_pkg_has_dep "$file" "expo"; then
|
|
1236
|
+
printf 'Expo (React Native)'
|
|
1237
|
+
elif _scan_pkg_has_dep "$file" "react-native"; then
|
|
1238
|
+
printf 'React Native'
|
|
1235
1239
|
elif _scan_pkg_has_dep "$file" "vite"; then
|
|
1236
1240
|
printf 'Vite'
|
|
1237
1241
|
elif _scan_pkg_has_dep "$file" "nestjs" || _scan_pkg_has_dep "$file" "@nestjs/core"; then
|
|
@@ -1242,6 +1246,8 @@ _scan_node_framework() {
|
|
|
1242
1246
|
printf 'Express'
|
|
1243
1247
|
elif _scan_pkg_has_dep "$file" "hono"; then
|
|
1244
1248
|
printf 'Hono'
|
|
1249
|
+
elif _scan_pkg_has_dep "$file" "@angular/core"; then
|
|
1250
|
+
printf 'Angular'
|
|
1245
1251
|
elif _scan_pkg_has_dep "$file" "react"; then
|
|
1246
1252
|
printf 'React'
|
|
1247
1253
|
elif _scan_pkg_has_dep "$file" "vue"; then
|
|
@@ -1251,6 +1257,16 @@ _scan_node_framework() {
|
|
|
1251
1257
|
fi
|
|
1252
1258
|
}
|
|
1253
1259
|
|
|
1260
|
+
# Detect framework from a Gemfile
|
|
1261
|
+
_scan_ruby_framework() {
|
|
1262
|
+
local file="$1"
|
|
1263
|
+
if grep -q "rails" "$file" 2>/dev/null; then
|
|
1264
|
+
printf 'Rails'
|
|
1265
|
+
elif grep -q "sinatra" "$file" 2>/dev/null; then
|
|
1266
|
+
printf 'Sinatra'
|
|
1267
|
+
fi
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1254
1270
|
# Detect framework from Python dependency files
|
|
1255
1271
|
_scan_python_framework() {
|
|
1256
1272
|
local repo_dir="$1"
|
|
@@ -1387,6 +1403,27 @@ scan_repo() {
|
|
|
1387
1403
|
SCAN_NAME=$(grep -m1 -E '^[[:space:]]*name[[:space:]]*=' "$repo_dir/Cargo.toml" 2>/dev/null | sed -E 's/.*=[[:space:]]*"([^"]*)".*/\1/' || true)
|
|
1388
1404
|
fi
|
|
1389
1405
|
|
|
1406
|
+
# Java/Kotlin (Gradle)
|
|
1407
|
+
if [[ -z "$SCAN_LANG" ]] && { [[ -f "$repo_dir/build.gradle" ]] || [[ -f "$repo_dir/build.gradle.kts" ]]; }; then
|
|
1408
|
+
SCAN_LANG="Java/Kotlin (Gradle)"
|
|
1409
|
+
fi
|
|
1410
|
+
|
|
1411
|
+
# Java (Maven)
|
|
1412
|
+
if [[ -z "$SCAN_LANG" ]] && [[ -f "$repo_dir/pom.xml" ]]; then
|
|
1413
|
+
SCAN_LANG="Java (Maven)"
|
|
1414
|
+
fi
|
|
1415
|
+
|
|
1416
|
+
# Ruby
|
|
1417
|
+
if [[ -z "$SCAN_LANG" ]] && [[ -f "$repo_dir/Gemfile" ]]; then
|
|
1418
|
+
SCAN_LANG="Ruby"
|
|
1419
|
+
SCAN_FRAMEWORK=$(_scan_ruby_framework "$repo_dir/Gemfile")
|
|
1420
|
+
fi
|
|
1421
|
+
|
|
1422
|
+
# Swift
|
|
1423
|
+
if [[ -z "$SCAN_LANG" ]] && [[ -f "$repo_dir/Package.swift" ]]; then
|
|
1424
|
+
SCAN_LANG="Swift"
|
|
1425
|
+
fi
|
|
1426
|
+
|
|
1390
1427
|
# Routes
|
|
1391
1428
|
SCAN_ROUTES=$(_scan_list_routes "$repo_dir")
|
|
1392
1429
|
|
|
@@ -1412,12 +1449,181 @@ scan_repo() {
|
|
|
1412
1449
|
|
|
1413
1450
|
# === lib/commands/init.sh ===
|
|
1414
1451
|
# Revo CLI - init command
|
|
1415
|
-
#
|
|
1452
|
+
# Initializes a workspace and auto-detects existing git repos in the current
|
|
1453
|
+
# directory (and in repos/), so that running `revo init` in a folder you
|
|
1454
|
+
# already populated with clones bootstraps a usable workspace immediately.
|
|
1455
|
+
|
|
1456
|
+
# Suggest tags for an auto-detected repo based on its package contents.
|
|
1457
|
+
# Always returns the repo name as a tag, plus an optional category tag
|
|
1458
|
+
# (frontend/backend) when one can be inferred. Skips the category when it
|
|
1459
|
+
# would be a duplicate of the name.
|
|
1460
|
+
_init_auto_tags() {
|
|
1461
|
+
local dir="$1"
|
|
1462
|
+
local name="$2"
|
|
1463
|
+
local category=""
|
|
1464
|
+
|
|
1465
|
+
if [[ -f "$dir/package.json" ]]; then
|
|
1466
|
+
if grep -qE '"(next|nuxt|react|vue|svelte|@angular/core|astro|@remix-run/react|@sveltejs/kit|expo|react-native)"' "$dir/package.json" 2>/dev/null; then
|
|
1467
|
+
category="frontend"
|
|
1468
|
+
elif grep -qE '"(express|fastify|hono|@nestjs/core|nestjs|koa)"' "$dir/package.json" 2>/dev/null; then
|
|
1469
|
+
category="backend"
|
|
1470
|
+
fi
|
|
1471
|
+
elif [[ -f "$dir/go.mod" ]] || [[ -f "$dir/Cargo.toml" ]] || [[ -f "$dir/pom.xml" ]] || [[ -f "$dir/build.gradle" ]] || [[ -f "$dir/build.gradle.kts" ]]; then
|
|
1472
|
+
category="backend"
|
|
1473
|
+
elif [[ -f "$dir/pyproject.toml" ]] || [[ -f "$dir/requirements.txt" ]]; then
|
|
1474
|
+
category="backend"
|
|
1475
|
+
fi
|
|
1476
|
+
|
|
1477
|
+
if [[ -n "$category" ]] && [[ "$category" != "$name" ]]; then
|
|
1478
|
+
printf '%s, %s' "$name" "$category"
|
|
1479
|
+
else
|
|
1480
|
+
printf '%s' "$name"
|
|
1481
|
+
fi
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
# Scan for git repos in the current directory and in repos/.
|
|
1485
|
+
# Sets _INIT_FOUND_DIRS to a newline-separated list of repo paths.
|
|
1486
|
+
_INIT_FOUND_DIRS=""
|
|
1487
|
+
_init_scan_existing() {
|
|
1488
|
+
_INIT_FOUND_DIRS=""
|
|
1489
|
+
|
|
1490
|
+
local d
|
|
1491
|
+
for d in */; do
|
|
1492
|
+
d="${d%/}"
|
|
1493
|
+
[[ "$d" == "repos" ]] && continue
|
|
1494
|
+
[[ "$d" == ".revo" ]] && continue
|
|
1495
|
+
if [[ -d "$d/.git" ]]; then
|
|
1496
|
+
if [[ -z "$_INIT_FOUND_DIRS" ]]; then
|
|
1497
|
+
_INIT_FOUND_DIRS="$d"
|
|
1498
|
+
else
|
|
1499
|
+
_INIT_FOUND_DIRS="$_INIT_FOUND_DIRS"$'\n'"$d"
|
|
1500
|
+
fi
|
|
1501
|
+
fi
|
|
1502
|
+
done
|
|
1503
|
+
|
|
1504
|
+
if [[ -d "repos" ]]; then
|
|
1505
|
+
for d in repos/*/; do
|
|
1506
|
+
d="${d%/}"
|
|
1507
|
+
if [[ -d "$d/.git" ]]; then
|
|
1508
|
+
if [[ -z "$_INIT_FOUND_DIRS" ]]; then
|
|
1509
|
+
_INIT_FOUND_DIRS="$d"
|
|
1510
|
+
else
|
|
1511
|
+
_INIT_FOUND_DIRS="$_INIT_FOUND_DIRS"$'\n'"$d"
|
|
1512
|
+
fi
|
|
1513
|
+
fi
|
|
1514
|
+
done
|
|
1515
|
+
fi
|
|
1516
|
+
}
|
|
1517
|
+
|
|
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.
|
|
1520
|
+
_init_write_claude_md() {
|
|
1521
|
+
local out="$REVO_WORKSPACE_ROOT/CLAUDE.md"
|
|
1522
|
+
|
|
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"
|
|
1557
|
+
return 0
|
|
1558
|
+
fi
|
|
1559
|
+
|
|
1560
|
+
cat > "$out" << 'EOF'
|
|
1561
|
+
# Workspace managed by revo
|
|
1562
|
+
|
|
1563
|
+
This is a multi-repo workspace managed by revo.
|
|
1564
|
+
revo is installed and available in the terminal.
|
|
1565
|
+
|
|
1566
|
+
## Quick reference
|
|
1567
|
+
|
|
1568
|
+
### If repos are not yet added
|
|
1569
|
+
The user may give you repo URLs or descriptions. Use these commands to set up:
|
|
1570
|
+
|
|
1571
|
+
```bash
|
|
1572
|
+
revo add <git-url> --tags <tag1,tag2> [--depends-on <repo-name>]
|
|
1573
|
+
revo clone
|
|
1574
|
+
revo context # regenerates this file with full repo details
|
|
1575
|
+
```
|
|
1576
|
+
|
|
1577
|
+
Example:
|
|
1578
|
+
```bash
|
|
1579
|
+
revo add git@github.com:org/shared.git --tags shared,types
|
|
1580
|
+
revo add git@github.com:org/backend.git --tags backend,api --depends-on shared
|
|
1581
|
+
revo add git@github.com:org/frontend.git --tags frontend,web --depends-on backend
|
|
1582
|
+
revo clone
|
|
1583
|
+
```
|
|
1584
|
+
|
|
1585
|
+
### If repos are already set up
|
|
1586
|
+
Use `revo status` to see all repos, branches, and dirty state.
|
|
1587
|
+
|
|
1588
|
+
## Available commands
|
|
1589
|
+
- `revo status` — branch and dirty state across all repos
|
|
1590
|
+
- `revo sync` — pull latest across all repos
|
|
1591
|
+
- `revo feature <name>` — create feature branch across all repos
|
|
1592
|
+
- `revo commit "msg"` — commit all dirty repos with same message
|
|
1593
|
+
- `revo push` — push all repos
|
|
1594
|
+
- `revo pr "title"` — create coordinated PRs via gh CLI
|
|
1595
|
+
- `revo exec "cmd" --tag <tag>` — run command in filtered repos
|
|
1596
|
+
- `revo context` — regenerate this file after repos change
|
|
1597
|
+
- `revo add <url> --tags <t> --depends-on <d>` — add a repo
|
|
1598
|
+
|
|
1599
|
+
### Tag filtering
|
|
1600
|
+
All commands support `--tag <tag>` to target specific repos:
|
|
1601
|
+
|
|
1602
|
+
```bash
|
|
1603
|
+
revo exec "npm test" --tag frontend
|
|
1604
|
+
revo sync --tag backend
|
|
1605
|
+
revo branch hotfix --tag api
|
|
1606
|
+
```
|
|
1607
|
+
|
|
1608
|
+
## Working in this workspace
|
|
1609
|
+
- Repos are in the repos/ subdirectory (or as configured in revo.yaml)
|
|
1610
|
+
- Edit files across repos/ directly — you have full access
|
|
1611
|
+
- When making cross-repo changes, follow the dependency order below
|
|
1612
|
+
- Check .revo/features/ for active feature briefs
|
|
1613
|
+
- Use revo commands instead of manually running git in each repo
|
|
1614
|
+
|
|
1615
|
+
## Dependency order
|
|
1616
|
+
<!-- revo context will fill this in once repos are cloned -->
|
|
1617
|
+
Run `revo context` after cloning to populate repo details and dependency order.
|
|
1618
|
+
EOF
|
|
1619
|
+
|
|
1620
|
+
ui_step_done "Created CLAUDE.md (Claude reads this automatically)"
|
|
1621
|
+
}
|
|
1416
1622
|
|
|
1417
1623
|
cmd_init() {
|
|
1418
1624
|
local workspace_name=""
|
|
1419
1625
|
|
|
1420
|
-
#
|
|
1626
|
+
# Already initialized? Bail out.
|
|
1421
1627
|
if [[ -f "revo.yaml" ]] || [[ -f "mars.yaml" ]]; then
|
|
1422
1628
|
ui_step_error "Workspace already initialized in this directory"
|
|
1423
1629
|
return 1
|
|
@@ -1425,20 +1631,21 @@ cmd_init() {
|
|
|
1425
1631
|
|
|
1426
1632
|
ui_intro "Revo - Claude-first Multi-Repo Workspace"
|
|
1427
1633
|
|
|
1428
|
-
#
|
|
1634
|
+
# Default workspace name to current directory basename so init can run
|
|
1635
|
+
# non-interactively. The user can still override by typing a value.
|
|
1636
|
+
local default_name
|
|
1637
|
+
default_name=$(basename "$PWD")
|
|
1429
1638
|
ui_step "Workspace name?"
|
|
1430
1639
|
printf '%s ' "$(ui_bar)"
|
|
1431
1640
|
read -r workspace_name
|
|
1432
|
-
|
|
1433
1641
|
if [[ -z "$workspace_name" ]]; then
|
|
1434
|
-
|
|
1435
|
-
return 1
|
|
1642
|
+
workspace_name="$default_name"
|
|
1436
1643
|
fi
|
|
1437
1644
|
|
|
1438
1645
|
ui_step_done "Workspace:" "$workspace_name"
|
|
1439
1646
|
ui_bar_line
|
|
1440
1647
|
|
|
1441
|
-
# Initialize
|
|
1648
|
+
# Initialize the on-disk config.
|
|
1442
1649
|
if ! config_init "$workspace_name"; then
|
|
1443
1650
|
ui_step_error "Failed to initialize workspace"
|
|
1444
1651
|
return 1
|
|
@@ -1448,8 +1655,177 @@ cmd_init() {
|
|
|
1448
1655
|
ui_step_done "Created .gitignore"
|
|
1449
1656
|
ui_step_done "Created repos/ directory"
|
|
1450
1657
|
|
|
1658
|
+
# Auto-detect existing git repos in the current directory.
|
|
1659
|
+
_init_scan_existing
|
|
1660
|
+
local detected_count=0
|
|
1661
|
+
if [[ -n "$_INIT_FOUND_DIRS" ]]; then
|
|
1662
|
+
local dir name remote tags path
|
|
1663
|
+
while IFS= read -r dir; do
|
|
1664
|
+
[[ -z "$dir" ]] && continue
|
|
1665
|
+
name=$(basename "$dir")
|
|
1666
|
+
remote=$(cd "$dir" && git remote get-url origin 2>/dev/null || true)
|
|
1667
|
+
if [[ -z "$remote" ]]; then
|
|
1668
|
+
ui_step_error "Skipping $name (no git remote)"
|
|
1669
|
+
continue
|
|
1670
|
+
fi
|
|
1671
|
+
|
|
1672
|
+
path="$name"
|
|
1673
|
+
|
|
1674
|
+
# If the repo lives at the workspace root (not under repos/),
|
|
1675
|
+
# link it into repos/ so the rest of revo's data model — which
|
|
1676
|
+
# always resolves $REVO_REPOS_DIR/$path — works without copying
|
|
1677
|
+
# or moving files.
|
|
1678
|
+
if [[ "$dir" != repos/* ]]; then
|
|
1679
|
+
if [[ ! -e "repos/$name" ]]; then
|
|
1680
|
+
ln -s "../$name" "repos/$name"
|
|
1681
|
+
fi
|
|
1682
|
+
fi
|
|
1683
|
+
|
|
1684
|
+
tags=$(_init_auto_tags "$dir" "$name")
|
|
1685
|
+
yaml_add_repo "$remote" "$path" "$tags" ""
|
|
1686
|
+
ui_step_done "Detected:" "$name → $remote"
|
|
1687
|
+
detected_count=$((detected_count + 1))
|
|
1688
|
+
done <<< "$_INIT_FOUND_DIRS"
|
|
1689
|
+
|
|
1690
|
+
if [[ $detected_count -gt 0 ]]; then
|
|
1691
|
+
config_save
|
|
1692
|
+
ui_info "$(ui_dim "Edit revo.yaml to adjust tags or add depends_on relationships")"
|
|
1693
|
+
fi
|
|
1694
|
+
fi
|
|
1695
|
+
|
|
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.
|
|
1700
|
+
if [[ $detected_count -gt 0 ]]; then
|
|
1701
|
+
ui_bar_line
|
|
1702
|
+
cmd_context
|
|
1703
|
+
return 0
|
|
1704
|
+
fi
|
|
1705
|
+
|
|
1451
1706
|
ui_outro "Workspace initialized! Run 'revo add <url>' to add repositories."
|
|
1707
|
+
return 0
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
# === lib/commands/detect.sh ===
|
|
1711
|
+
# Revo CLI - detect command
|
|
1712
|
+
# Bootstraps a workspace around git repos that already exist in the current
|
|
1713
|
+
# directory. Use this when you have a parent folder full of clones and want
|
|
1714
|
+
# revo to wrap them without re-cloning.
|
|
1452
1715
|
|
|
1716
|
+
cmd_detect() {
|
|
1717
|
+
while [[ $# -gt 0 ]]; do
|
|
1718
|
+
case "$1" in
|
|
1719
|
+
--help|-h)
|
|
1720
|
+
printf 'Usage: revo detect\n\n'
|
|
1721
|
+
printf 'Auto-detect git repositories in the current directory and\n'
|
|
1722
|
+
printf 'bootstrap a revo workspace around them. Generates revo.yaml\n'
|
|
1723
|
+
printf 'and CLAUDE.md from what it finds.\n'
|
|
1724
|
+
return 0
|
|
1725
|
+
;;
|
|
1726
|
+
*)
|
|
1727
|
+
ui_step_error "Unknown option: $1"
|
|
1728
|
+
return 1
|
|
1729
|
+
;;
|
|
1730
|
+
esac
|
|
1731
|
+
done
|
|
1732
|
+
|
|
1733
|
+
if [[ -f "revo.yaml" ]] || [[ -f "mars.yaml" ]]; then
|
|
1734
|
+
ui_step_error "Workspace already initialized — run 'revo context' to regenerate CLAUDE.md"
|
|
1735
|
+
return 1
|
|
1736
|
+
fi
|
|
1737
|
+
|
|
1738
|
+
ui_intro "Revo - Detect Existing Repositories"
|
|
1739
|
+
ui_step "Scanning for git repos..."
|
|
1740
|
+
|
|
1741
|
+
# Initialize the workspace using the cwd basename. This sets
|
|
1742
|
+
# REVO_WORKSPACE_ROOT/REVO_CONFIG_FILE/REVO_REPOS_DIR and writes an empty
|
|
1743
|
+
# revo.yaml + .gitignore.
|
|
1744
|
+
local default_name
|
|
1745
|
+
default_name=$(basename "$PWD")
|
|
1746
|
+
if ! config_init "$default_name"; then
|
|
1747
|
+
ui_step_error "Failed to initialize workspace"
|
|
1748
|
+
return 1
|
|
1749
|
+
fi
|
|
1750
|
+
|
|
1751
|
+
local found=0
|
|
1752
|
+
local d name remote category tags
|
|
1753
|
+
|
|
1754
|
+
for d in */; do
|
|
1755
|
+
d="${d%/}"
|
|
1756
|
+
[[ "$d" == "repos" ]] && continue
|
|
1757
|
+
[[ "$d" == ".revo" ]] && continue
|
|
1758
|
+
[[ -d "$d/.git" ]] || continue
|
|
1759
|
+
|
|
1760
|
+
name="$d"
|
|
1761
|
+
remote=$(cd "$d" && git remote get-url origin 2>/dev/null || echo "local://$d")
|
|
1762
|
+
|
|
1763
|
+
# Auto-categorize from package contents.
|
|
1764
|
+
category=""
|
|
1765
|
+
if [[ -f "$d/package.json" ]]; then
|
|
1766
|
+
if grep -qE '"(next|nuxt|react|vue|svelte|@angular/core|astro|@remix-run/react|@sveltejs/kit|expo|react-native)"' "$d/package.json" 2>/dev/null; then
|
|
1767
|
+
category="frontend"
|
|
1768
|
+
elif grep -qE '"(express|fastify|hono|@nestjs/core|nestjs|koa)"' "$d/package.json" 2>/dev/null; then
|
|
1769
|
+
category="backend"
|
|
1770
|
+
fi
|
|
1771
|
+
elif [[ -f "$d/go.mod" ]] || [[ -f "$d/Cargo.toml" ]] || [[ -f "$d/pom.xml" ]] || [[ -f "$d/build.gradle" ]] || [[ -f "$d/build.gradle.kts" ]]; then
|
|
1772
|
+
category="backend"
|
|
1773
|
+
elif [[ -f "$d/pyproject.toml" ]] || [[ -f "$d/requirements.txt" ]]; then
|
|
1774
|
+
category="backend"
|
|
1775
|
+
fi
|
|
1776
|
+
|
|
1777
|
+
if [[ -n "$category" ]] && [[ "$category" != "$name" ]]; then
|
|
1778
|
+
tags="$name, $category"
|
|
1779
|
+
else
|
|
1780
|
+
tags="$name"
|
|
1781
|
+
fi
|
|
1782
|
+
|
|
1783
|
+
# Link the root-level repo into repos/ so the rest of revo's data
|
|
1784
|
+
# model (which always resolves $REVO_REPOS_DIR/$path) works.
|
|
1785
|
+
if [[ ! -e "repos/$name" ]]; then
|
|
1786
|
+
ln -s "../$name" "repos/$name"
|
|
1787
|
+
fi
|
|
1788
|
+
|
|
1789
|
+
yaml_add_repo "$remote" "$name" "$tags" ""
|
|
1790
|
+
ui_step_done "Found:" "$name ($remote)"
|
|
1791
|
+
found=$((found + 1))
|
|
1792
|
+
done
|
|
1793
|
+
|
|
1794
|
+
if [[ -d "repos" ]]; then
|
|
1795
|
+
for d in repos/*/; do
|
|
1796
|
+
d="${d%/}"
|
|
1797
|
+
[[ -d "$d/.git" ]] || continue
|
|
1798
|
+
name=$(basename "$d")
|
|
1799
|
+
# Skip repos already added via symlink in the loop above.
|
|
1800
|
+
local already=0
|
|
1801
|
+
local i
|
|
1802
|
+
for ((i = 0; i < YAML_REPO_COUNT; i++)); do
|
|
1803
|
+
if [[ "$(yaml_get_path "$i")" == "$name" ]]; then
|
|
1804
|
+
already=1
|
|
1805
|
+
break
|
|
1806
|
+
fi
|
|
1807
|
+
done
|
|
1808
|
+
[[ $already -eq 1 ]] && continue
|
|
1809
|
+
remote=$(cd "$d" && git remote get-url origin 2>/dev/null || echo "local://$d")
|
|
1810
|
+
yaml_add_repo "$remote" "$name" "$name" ""
|
|
1811
|
+
ui_step_done "Found:" "$name ($remote)"
|
|
1812
|
+
found=$((found + 1))
|
|
1813
|
+
done
|
|
1814
|
+
fi
|
|
1815
|
+
|
|
1816
|
+
if [[ $found -eq 0 ]]; then
|
|
1817
|
+
rm -f "$REVO_CONFIG_FILE"
|
|
1818
|
+
ui_step_error "No git repos found in current directory. Use 'revo init' instead."
|
|
1819
|
+
ui_outro_cancel "Nothing to detect"
|
|
1820
|
+
return 1
|
|
1821
|
+
fi
|
|
1822
|
+
|
|
1823
|
+
config_save
|
|
1824
|
+
ui_step_done "Detected $found repository(ies)"
|
|
1825
|
+
ui_info "$(ui_dim "Edit revo.yaml to adjust tags or add depends_on relationships")"
|
|
1826
|
+
ui_bar_line
|
|
1827
|
+
|
|
1828
|
+
cmd_context
|
|
1453
1829
|
return 0
|
|
1454
1830
|
}
|
|
1455
1831
|
|
|
@@ -1548,9 +1924,10 @@ cmd_clone() {
|
|
|
1548
1924
|
fi
|
|
1549
1925
|
done <<< "$repos"
|
|
1550
1926
|
|
|
1551
|
-
#
|
|
1552
|
-
|
|
1553
|
-
|
|
1927
|
+
# Always regenerate the workspace CLAUDE.md after a clone batch so that
|
|
1928
|
+
# newly cloned repos appear in the context immediately.
|
|
1929
|
+
if [[ $fail_count -eq 0 ]] && { [[ $success_count -gt 0 ]] || [[ $skip_count -gt 0 ]]; }; then
|
|
1930
|
+
context_regenerate_silent
|
|
1554
1931
|
fi
|
|
1555
1932
|
|
|
1556
1933
|
# Summary
|
|
@@ -2489,7 +2866,29 @@ _context_write_file() {
|
|
|
2489
2866
|
printf '\n> Warning: a dependency cycle was detected. Listed in best-effort order.\n' >> "$output"
|
|
2490
2867
|
fi
|
|
2491
2868
|
|
|
2492
|
-
#
|
|
2869
|
+
# Active features (any .revo/features/*.md files)
|
|
2870
|
+
local features_dir="$REVO_WORKSPACE_ROOT/.revo/features"
|
|
2871
|
+
if [[ -d "$features_dir" ]]; then
|
|
2872
|
+
local has_features=0
|
|
2873
|
+
local f
|
|
2874
|
+
for f in "$features_dir"/*.md; do
|
|
2875
|
+
[[ -f "$f" ]] || continue
|
|
2876
|
+
if [[ $has_features -eq 0 ]]; then
|
|
2877
|
+
{
|
|
2878
|
+
printf '\n'
|
|
2879
|
+
printf '## Active Features\n'
|
|
2880
|
+
printf '\n'
|
|
2881
|
+
} >> "$output"
|
|
2882
|
+
has_features=1
|
|
2883
|
+
fi
|
|
2884
|
+
local fname rel
|
|
2885
|
+
fname=$(basename "$f" .md)
|
|
2886
|
+
rel=".revo/features/$(basename "$f")"
|
|
2887
|
+
printf -- '- **%s** — see [%s](%s)\n' "$fname" "$rel" "$rel" >> "$output"
|
|
2888
|
+
done
|
|
2889
|
+
fi
|
|
2890
|
+
|
|
2891
|
+
# Agent instructions + revo tool docs
|
|
2493
2892
|
{
|
|
2494
2893
|
printf '\n'
|
|
2495
2894
|
printf '## Agent Instructions\n'
|
|
@@ -2504,6 +2903,49 @@ _context_write_file() {
|
|
|
2504
2903
|
printf '6. Use `revo commit "msg"` to commit across all repos at once\n'
|
|
2505
2904
|
printf '7. Use `revo feature <name>` to start a coordinated feature workspace\n'
|
|
2506
2905
|
printf '8. Use `revo pr "title"` to open coordinated pull requests\n'
|
|
2906
|
+
printf '\n'
|
|
2907
|
+
printf '## Workspace Tool: revo\n'
|
|
2908
|
+
printf '\n'
|
|
2909
|
+
printf 'This workspace is managed by revo.\n'
|
|
2910
|
+
printf 'Source: https://github.com/jippylong12/revo\n'
|
|
2911
|
+
printf '\n'
|
|
2912
|
+
printf '### Available commands (run in terminal)\n'
|
|
2913
|
+
printf '\n'
|
|
2914
|
+
printf '**Setup:**\n'
|
|
2915
|
+
printf -- '- `revo add <git-url> --tags <tags> [--depends-on <repo>]` — add a repo\n'
|
|
2916
|
+
printf -- '- `revo clone` — clone all configured repos\n'
|
|
2917
|
+
printf -- '- `revo context` — regenerate this file\n'
|
|
2918
|
+
printf -- '- `revo detect` — bootstrap around existing repos in cwd\n'
|
|
2919
|
+
printf '\n'
|
|
2920
|
+
printf '**Daily workflow:**\n'
|
|
2921
|
+
printf -- '- `revo status` — branch and dirty state across all repos\n'
|
|
2922
|
+
printf -- '- `revo sync` — pull latest across all repos\n'
|
|
2923
|
+
printf -- '- `revo feature <name> [--tag t]` — create feature branch across repos\n'
|
|
2924
|
+
printf -- '- `revo commit "msg" [--tag t]` — commit all dirty repos\n'
|
|
2925
|
+
printf -- '- `revo push [--tag t]` — push all repos\n'
|
|
2926
|
+
printf -- '- `revo pr "title" [--tag t]` — create coordinated PRs via gh CLI\n'
|
|
2927
|
+
printf -- '- `revo exec "cmd" [--tag t]` — run command in filtered repos\n'
|
|
2928
|
+
printf -- '- `revo checkout <branch> [--tag t]` — switch branch across repos\n'
|
|
2929
|
+
printf '\n'
|
|
2930
|
+
printf '### Tag filtering\n'
|
|
2931
|
+
printf '\n'
|
|
2932
|
+
printf 'All commands support `--tag <tag>` to target specific repos:\n'
|
|
2933
|
+
printf '\n'
|
|
2934
|
+
printf '```\n'
|
|
2935
|
+
printf 'revo exec "npm test" --tag frontend\n'
|
|
2936
|
+
printf 'revo sync --tag backend\n'
|
|
2937
|
+
printf 'revo branch hotfix --tag api\n'
|
|
2938
|
+
printf '```\n'
|
|
2939
|
+
printf '\n'
|
|
2940
|
+
printf '### Feature workflow\n'
|
|
2941
|
+
printf '\n'
|
|
2942
|
+
printf '```\n'
|
|
2943
|
+
printf 'revo feature my-feature # branches all repos\n'
|
|
2944
|
+
printf '# edit .revo/features/my-feature.md to describe scope\n'
|
|
2945
|
+
printf '# work across repos\n'
|
|
2946
|
+
printf 'revo commit "feat: my feature" # commits all dirty repos\n'
|
|
2947
|
+
printf 'revo pr "My feature" # coordinated PRs\n'
|
|
2948
|
+
printf '```\n'
|
|
2507
2949
|
} >> "$output"
|
|
2508
2950
|
}
|
|
2509
2951
|
|
|
@@ -2551,15 +2993,15 @@ cmd_context() {
|
|
|
2551
2993
|
return 0
|
|
2552
2994
|
}
|
|
2553
2995
|
|
|
2554
|
-
# Called automatically from cmd_clone
|
|
2555
|
-
|
|
2996
|
+
# Called automatically from cmd_clone after a successful clone batch.
|
|
2997
|
+
# Always rewrites the workspace CLAUDE.md so newly cloned repos show up.
|
|
2998
|
+
context_regenerate_silent() {
|
|
2556
2999
|
[[ -z "$REVO_WORKSPACE_ROOT" ]] && return 0
|
|
2557
|
-
local output="$REVO_WORKSPACE_ROOT/CLAUDE.md"
|
|
2558
|
-
[[ -f "$output" ]] && return 0
|
|
2559
3000
|
[[ $YAML_REPO_COUNT -eq 0 ]] && return 0
|
|
2560
3001
|
|
|
3002
|
+
local output="$REVO_WORKSPACE_ROOT/CLAUDE.md"
|
|
2561
3003
|
_context_write_file "$output"
|
|
2562
|
-
ui_info "$(ui_dim "
|
|
3004
|
+
ui_info "$(ui_dim "Regenerated CLAUDE.md for Claude Code")"
|
|
2563
3005
|
return 0
|
|
2564
3006
|
}
|
|
2565
3007
|
|
|
@@ -3144,7 +3586,8 @@ Revo - Claude-first multi-repo workspace manager v$REVO_VERSION
|
|
|
3144
3586
|
Usage: revo <command> [options]
|
|
3145
3587
|
|
|
3146
3588
|
Workspace commands:
|
|
3147
|
-
init Initialize a new workspace
|
|
3589
|
+
init Initialize a new workspace (auto-detects existing repos)
|
|
3590
|
+
detect Bootstrap revo around existing git repos in cwd
|
|
3148
3591
|
add URL [--tags TAGS] Add a repository to the workspace
|
|
3149
3592
|
clone [--tag TAG] Clone configured repositories
|
|
3150
3593
|
list [--tag TAG] List configured repositories
|
|
@@ -3207,6 +3650,9 @@ main() {
|
|
|
3207
3650
|
init)
|
|
3208
3651
|
cmd_init "$@"
|
|
3209
3652
|
;;
|
|
3653
|
+
detect)
|
|
3654
|
+
cmd_detect "$@"
|
|
3655
|
+
;;
|
|
3210
3656
|
clone)
|
|
3211
3657
|
cmd_clone "$@"
|
|
3212
3658
|
;;
|