Skip to content

Commit 98c8f63

Browse files
authored
[fix] Make agent file installation opt-in on rimba init (#69)
* [fix] Replace non-existent `rimba cd` with `rimba open` in agent files The generated agent instruction files referenced `rimba cd`, which was never a real command. Replace all occurrences with `rimba open` in the template source (content.go) and regenerated outputs. Also update the `rimba init` docs to mention agent file installation. * [fix] Make agent file installation opt-in via --agent-files flag rimba init no longer installs AI agent instruction files by default. Users must pass --agent-files to opt in.
1 parent cd35049 commit 98c8f63

8 files changed

Lines changed: 98 additions & 20 deletions

File tree

.claude/skills/rimba/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ curl -sSfL https://raw.githubusercontent.com/lugassawan/rimba/main/scripts/insta
2121
| Start a new task | `rimba add <task>` |
2222
| See all worktrees | `rimba list` or `rimba list --json` |
2323
| Check worktree health | `rimba status` |
24-
| Navigate to a worktree | `cd $(rimba cd <task>)` |
24+
| Navigate to a worktree | `cd $(rimba open <task>)` |
2525
| Update from source branch | `rimba sync <task>` or `rimba sync` (all) |
2626
| Finish a feature | `rimba merge <task>` then `rimba remove <task>` |
2727
| Clean up merged work | `rimba clean --merged` |

.cursor/rules/rimba.mdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ See AGENTS.md at the repo root for full documentation.
1515
1. `rimba add <task>` — create worktree + branch
1616
2. `rimba list` — list all worktrees
1717
3. `rimba status` — health overview (dirty, stale, behind)
18-
4. `rimba cd <task>` — print path for cd
18+
4. `rimba open <task>` — print path or run shortcut (--ide, --agent)
1919
5. `rimba sync [task]` — rebase worktree(s) onto source
2020
6. `rimba merge <task>` — fast-forward merge into source
2121
7. `rimba remove <task>` — delete worktree + branch

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ curl -sSfL https://raw.githubusercontent.com/lugassawan/rimba/main/scripts/insta
2020

2121
| Concern | Commands |
2222
|---------|----------|
23-
| Create & navigate | `rimba add <task>`, `rimba open <task>`, `rimba cd <task>` |
23+
| Create & navigate | `rimba add <task>`, `rimba open <task>` |
2424
| Inspect | `rimba list`, `rimba status`, `rimba log [task]` |
2525
| Sync & merge | `rimba sync [task]`, `rimba merge <task>` |
2626
| Clean up | `rimba clean --merged`, `rimba archive <task>`, `rimba remove <task>` |
@@ -31,7 +31,7 @@ curl -sSfL https://raw.githubusercontent.com/lugassawan/rimba/main/scripts/insta
3131
**Create a worktree and start working:**
3232
```sh
3333
rimba add my-feature # creates worktree + branch
34-
rimba cd my-feature # prints cd command (use: cd $(rimba cd my-feature))
34+
rimba open my-feature # prints worktree path (use: cd $(rimba open my-feature))
3535
```
3636

3737
**Check health and clean up stale worktrees:**

cmd/init.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ import (
1212
"github.com/spf13/cobra"
1313
)
1414

15+
const flagAgentFiles = "agent-files"
16+
1517
func init() {
1618
rootCmd.AddCommand(initCmd)
19+
initCmd.Flags().Bool(flagAgentFiles, false, "Install AI agent instruction files (AGENTS.md, copilot, cursor, claude)")
1720
}
1821

1922
var initCmd = &cobra.Command{
2023
Use: "init",
2124
Short: "Initialize rimba in the current repository",
22-
Long: "Detects the repository root, creates a .rimba.toml config file, sets up the worktree directory, and installs agent instruction files.",
25+
Long: "Detects the repository root, creates a .rimba.toml config file, and sets up the worktree directory. Use --agent-files to also install AI agent instruction files.",
2326
Annotations: map[string]string{"skipConfig": "true"},
2427
RunE: func(cmd *cobra.Command, args []string) error {
2528
r := newRunner()
@@ -76,13 +79,16 @@ var initCmd = &cobra.Command{
7679
fmt.Fprintf(cmd.OutOrStdout(), "Config %s already exists, skipping config creation\n", configPath)
7780
}
7881

79-
// Install agent instruction files
80-
results, err := agentfile.Install(repoRoot)
81-
if err != nil {
82-
return fmt.Errorf("install agent files: %w", err)
83-
}
84-
for _, res := range results {
85-
fmt.Fprintf(cmd.OutOrStdout(), " Agent: %s (%s)\n", res.RelPath, res.Action)
82+
// Install agent instruction files if requested
83+
installAgentFiles, _ := cmd.Flags().GetBool(flagAgentFiles)
84+
if installAgentFiles {
85+
results, err := agentfile.Install(repoRoot)
86+
if err != nil {
87+
return fmt.Errorf("install agent files: %w", err)
88+
}
89+
for _, res := range results {
90+
fmt.Fprintf(cmd.OutOrStdout(), " Agent: %s (%s)\n", res.RelPath, res.Action)
91+
}
8692
}
8793

8894
return nil

cmd/init_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func TestInitCreatesAgentFiles(t *testing.T) {
7373
defer restore()
7474

7575
cmd, buf := newTestCmd()
76+
cmd.Flags().Bool(flagAgentFiles, true, "")
7677

7778
if err := initCmd.RunE(cmd, nil); err != nil {
7879
t.Fatalf("initCmd.RunE: %v", err)
@@ -124,6 +125,7 @@ func TestInitExistingConfigInstallsAgentFiles(t *testing.T) {
124125
defer restore()
125126

126127
cmd, buf := newTestCmd()
128+
cmd.Flags().Bool(flagAgentFiles, true, "")
127129

128130
// Should succeed (not error) when .rimba.toml already exists
129131
err := initCmd.RunE(cmd, nil)
@@ -162,12 +164,14 @@ func TestInitAgentFilesIdempotent(t *testing.T) {
162164

163165
// First init
164166
cmd1, _ := newTestCmd()
167+
cmd1.Flags().Bool(flagAgentFiles, true, "")
165168
if err := initCmd.RunE(cmd1, nil); err != nil {
166169
t.Fatalf("first initCmd.RunE: %v", err)
167170
}
168171

169172
// Second init (config exists now)
170173
cmd2, buf2 := newTestCmd()
174+
cmd2.Flags().Bool(flagAgentFiles, true, "")
171175
if err := initCmd.RunE(cmd2, nil); err != nil {
172176
t.Fatalf("second initCmd.RunE: %v", err)
173177
}
@@ -187,6 +191,40 @@ func TestInitAgentFilesIdempotent(t *testing.T) {
187191
}
188192
}
189193

194+
func TestInitWithoutFlagSkipsAgentFiles(t *testing.T) {
195+
repoDir := t.TempDir()
196+
197+
r := &mockRunner{
198+
run: func(args ...string) (string, error) {
199+
if len(args) >= 2 && args[1] == cmdShowToplevel {
200+
return repoDir, nil
201+
}
202+
if len(args) >= 1 && args[0] == cmdSymbolicRef {
203+
return refsRemotesOriginMain, nil
204+
}
205+
return "", nil
206+
},
207+
runInDir: noopRunInDir,
208+
}
209+
restore := overrideNewRunner(r)
210+
defer restore()
211+
212+
cmd, buf := newTestCmd()
213+
214+
if err := initCmd.RunE(cmd, nil); err != nil {
215+
t.Fatalf("initCmd.RunE: %v", err)
216+
}
217+
218+
out := buf.String()
219+
if strings.Contains(out, "Agent:") {
220+
t.Errorf("output should not mention agent files without --agent-files flag, got:\n%s", out)
221+
}
222+
223+
if _, err := os.Stat(filepath.Join(repoDir, "AGENTS.md")); err == nil {
224+
t.Error("AGENTS.md should not be created without --agent-files flag")
225+
}
226+
}
227+
190228
func TestInitRepoRootError(t *testing.T) {
191229
r := &mockRunner{
192230
run: func(args ...string) (string, error) {

docs/commands.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,19 @@ These flags are available on every command via the root `rimba` command:
1919

2020
### rimba init
2121

22-
Initialize rimba in the current repository. Detects the repo root, creates `.rimba.toml`, and sets up the worktree directory.
22+
Initialize rimba in the current repository. Detects the repo root, creates `.rimba.toml`, and sets up the worktree directory. Use `--agent-files` to also install AI agent instruction files (`AGENTS.md`, `.github/copilot-instructions.md`, `.cursor/rules/rimba.mdc`, `.claude/skills/rimba/SKILL.md`).
23+
24+
If `.rimba.toml` already exists, config creation is skipped but agent files are still installed or updated when `--agent-files` is passed.
2325

2426
```sh
25-
rimba init
27+
rimba init # Initialize config and worktree directory
28+
rimba init --agent-files # Also install AI agent instruction files
2629
```
2730

31+
| Flag | Description |
32+
|------|-------------|
33+
| `--agent-files` | Install AI agent instruction files (AGENTS.md, copilot, cursor, claude) |
34+
2835
---
2936

3037
## Worktree Lifecycle

internal/agentfile/content.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ curl -sSfL https://raw.githubusercontent.com/lugassawan/rimba/main/scripts/insta
2727
2828
| Concern | Commands |
2929
|---------|----------|
30-
| Create & navigate | ` + "`" + `rimba add <task>` + "`" + `, ` + "`" + `rimba open <task>` + "`" + `, ` + "`" + `rimba cd <task>` + "`" + ` |
30+
| Create & navigate | ` + "`" + `rimba add <task>` + "`" + `, ` + "`" + `rimba open <task>` + "`" + ` |
3131
| Inspect | ` + "`" + `rimba list` + "`" + `, ` + "`" + `rimba status` + "`" + `, ` + "`" + `rimba log [task]` + "`" + ` |
3232
| Sync & merge | ` + "`" + `rimba sync [task]` + "`" + `, ` + "`" + `rimba merge <task>` + "`" + ` |
3333
| Clean up | ` + "`" + `rimba clean --merged` + "`" + `, ` + "`" + `rimba archive <task>` + "`" + `, ` + "`" + `rimba remove <task>` + "`" + ` |
@@ -38,7 +38,7 @@ curl -sSfL https://raw.githubusercontent.com/lugassawan/rimba/main/scripts/insta
3838
**Create a worktree and start working:**
3939
` + "```" + `sh
4040
rimba add my-feature # creates worktree + branch
41-
rimba cd my-feature # prints cd command (use: cd $(rimba cd my-feature))
41+
rimba open my-feature # prints worktree path (use: cd $(rimba open my-feature))
4242
` + "```" + `
4343
4444
**Check health and clean up stale worktrees:**
@@ -123,7 +123,7 @@ See AGENTS.md at the repo root for full documentation.
123123
1. ` + "`" + `rimba add <task>` + "`" + ` — create worktree + branch
124124
2. ` + "`" + `rimba list` + "`" + ` — list all worktrees
125125
3. ` + "`" + `rimba status` + "`" + ` — health overview (dirty, stale, behind)
126-
4. ` + "`" + `rimba cd <task>` + "`" + ` — print path for cd
126+
4. ` + "`" + `rimba open <task>` + "`" + ` — print path or run shortcut (--ide, --agent)
127127
5. ` + "`" + `rimba sync [task]` + "`" + ` — rebase worktree(s) onto source
128128
6. ` + "`" + `rimba merge <task>` + "`" + ` — fast-forward merge into source
129129
7. ` + "`" + `rimba remove <task>` + "`" + ` — delete worktree + branch
@@ -175,7 +175,7 @@ curl -sSfL https://raw.githubusercontent.com/lugassawan/rimba/main/scripts/insta
175175
| Start a new task | ` + "`" + `rimba add <task>` + "`" + ` |
176176
| See all worktrees | ` + "`" + `rimba list` + "`" + ` or ` + "`" + `rimba list --json` + "`" + ` |
177177
| Check worktree health | ` + "`" + `rimba status` + "`" + ` |
178-
| Navigate to a worktree | ` + "`" + `cd $(rimba cd <task>)` + "`" + ` |
178+
| Navigate to a worktree | ` + "`" + `cd $(rimba open <task>)` + "`" + ` |
179179
| Update from source branch | ` + "`" + `rimba sync <task>` + "`" + ` or ` + "`" + `rimba sync` + "`" + ` (all) |
180180
| Finish a feature | ` + "`" + `rimba merge <task>` + "`" + ` then ` + "`" + `rimba remove <task>` + "`" + ` |
181181
| Clean up merged work | ` + "`" + `rimba clean --merged` + "`" + ` |

tests/e2e/init_test.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ func TestInitExistingConfigInstallsAgentFiles(t *testing.T) {
6060
}
6161

6262
repo := setupRepo(t)
63-
rimbaSuccess(t, repo, "init")
63+
rimbaSuccess(t, repo, "init", "--agent-files")
6464

6565
// Re-running init should succeed and update agent files
66-
r := rimbaSuccess(t, repo, "init")
66+
r := rimbaSuccess(t, repo, "init", "--agent-files")
6767
assertContains(t, r.Stdout, "already exists")
6868
assertContains(t, r.Stdout, "Agent:")
6969
}
@@ -126,6 +126,33 @@ func TestInitFailsOutsideGitRepo(t *testing.T) {
126126
rimbaFail(t, dir, "init")
127127
}
128128

129+
func TestInitWithAgentFilesFlag(t *testing.T) {
130+
if testing.Short() {
131+
t.Skip(skipE2E)
132+
}
133+
134+
repo := setupRepo(t)
135+
r := rimbaSuccess(t, repo, "init", "--agent-files")
136+
137+
assertContains(t, r.Stdout, "Agent:")
138+
assertFileExists(t, filepath.Join(repo, "AGENTS.md"))
139+
assertFileExists(t, filepath.Join(repo, ".github", "copilot-instructions.md"))
140+
assertFileExists(t, filepath.Join(repo, ".cursor", "rules", "rimba.mdc"))
141+
assertFileExists(t, filepath.Join(repo, ".claude", "skills", "rimba", "SKILL.md"))
142+
}
143+
144+
func TestInitSkipsAgentFilesWithoutFlag(t *testing.T) {
145+
if testing.Short() {
146+
t.Skip(skipE2E)
147+
}
148+
149+
repo := setupRepo(t)
150+
r := rimbaSuccess(t, repo, "init")
151+
152+
assertNotContains(t, r.Stdout, "Agent:")
153+
assertFileNotExists(t, filepath.Join(repo, "AGENTS.md"))
154+
}
155+
129156
// assertGitignoreContains verifies that .gitignore in the repo contains the given entry.
130157
func assertGitignoreContains(t *testing.T, repo, entry string) {
131158
t.Helper()

0 commit comments

Comments
 (0)