Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Rally

Step-by-step coordinator for multi-agent coding sessions.

## Command Wrappers

Rally now provides a wrapper-oriented command surface:

- `rally command install --target <codex|droid|pi|claude-code|factory|all>`
- `rally command run ...` (canonical wrapper entrypoint)
- `rally command exec ...` (alias for `run`)
- `rally command doctor --target ...`
- `rally command uninstall --target ...`

Install wrappers everywhere:

```bash
rally command install --target all
```

Installed `/rally` wrappers are thin adapters that delegate back to Rally core:

```bash
rally command run "$@"
```

## /rally Usage

Typical wrapper invocations:

- `/rally`
- `/rally build`
- `/rally build reviewer`
- `/rally <invite-code>`

`rally command run` resolves context in this order:

1. explicit args (`--session`, `--as`, positional selectors)
2. invite token input
3. saved local workspace context
4. interactive prompt fallback

## Troubleshooting

Use doctor to inspect path/status per target:

```bash
rally command doctor --target all
```

For details and target path mapping, see [docs/command-install-run.md](docs/command-install-run.md).
108 changes: 108 additions & 0 deletions docs/command-install-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Rally Command Install/Run

This document describes Rally's wrapper entrypoint for harness slash commands.

## Install Targets

Install wrapper artifacts for one harness:

```bash
rally command install --target codex
```

Install for all supported harnesses:

```bash
rally command install --target all
```

Supported targets:

- `codex` -> `~/.codex/commands/rally.md`
- `droid` -> `~/.droid/commands/rally.md`
- `pi` -> `~/.pi/commands/rally.md`
- `claude-code` -> `~/.claude/commands/rally.md`
- `factory` -> `~/.factory/commands/rally.md`

Each wrapper is Rally-managed content and delegates back to:

```bash
rally command run "$@"
```

## Wrapper Behavior

The generated `/rally` wrapper is intentionally thin:

- accepts wrapper arguments unchanged
- forwards arguments to `rally command run`
- keeps orchestration logic centralized in Rally

Examples:

- `/rally`
- `/rally build`
- `/rally build reviewer`
- `/rally <invite-code>`

## `rally command run` Patterns

`run` and `exec` are equivalent:

```bash
rally command run
rally command exec
```

Resolution precedence:

1. explicit args (`--session`, `--as`, positional selectors)
2. invite token (for example `rly:<session>:<agent>`, `rally://<session>/<agent>`)
3. saved workspace context (`~/.rally/command-context.json`)
4. interactive fallback prompt

## Troubleshooting with Doctor

Inspect current installation health:

```bash
rally command doctor --target all
```

Doctor reports per target:

- environment/path availability
- wrapper presence
- managed/unmanaged file state
- managed content drift

For preview mode:

```bash
rally command doctor --target all --dry-run
```

## Uninstall Behavior

Remove Rally-managed wrappers only:

```bash
rally command uninstall --target all
```

Unmanaged user files are never removed.

Preview removal decisions:

```bash
rally command uninstall --target all --dry-run
```

## Mapping to Rally Core Commands

Wrappers map to Rally core loop behavior through `rally command run`, which then joins/polls the existing session workflows:

- registration path: `rally join --session <name> --as <agent>` (as needed)
- instruction polling path: `rally next --session <name> --as <agent>`
- review flow remains canonical under `rally build checkpoint|review`
- plan actions remain canonical under `rally plan ...`
101 changes: 101 additions & 0 deletions docs/manual-qa-command-install-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Manual QA: Command Install/Run

Date: 2026-03-02

Environment:

- Rally binary: `/Users/justin/code/rally/target/debug/rally`
- Workspace: `/Users/justin/code/rally/worktrees/command-install-run`
- Temporary HOME used for isolated QA harness roots

## Scenario 1: Install wrappers for two targets

Commands:

```bash
HOME="$TMP_HOME" rally command install --target codex
HOME="$TMP_HOME" rally command install --target droid
```

Observed:

- `~/.codex/commands/rally.md` created
- `~/.droid/commands/rally.md` created

## Scenario 2: `/rally` routing behavior

Created implement session `qa-command-install-run-1772430801` with a single step.

Commands and observed results:

```bash
HOME="$TMP_HOME" rally command run --session "$SESSION" --as implementer --non-interactive
```

- Routed to implement instruction (`Implement step 1: Validate wrapper routing`)

```bash
HOME="$TMP_HOME" rally command run build --non-interactive
```

- Routed through saved context fallback and printed same instruction

```bash
HOME="$TMP_HOME" rally command run "rly:${SESSION}:implementer" --non-interactive
```

- Invite-token resolution worked and printed same instruction

## Scenario 3: `doctor`, uninstall, reinstall, and custom file safety

Doctor output validated per-target status/path reporting:

```bash
HOME="$TMP_HOME" rally command doctor --target all
```

Created unmanaged custom file:

```bash
echo "custom user wrapper" > "$TMP_HOME/.pi/commands/rally.md"
```

Confirmed non-destructive install behavior:

```bash
HOME="$TMP_HOME" rally command install --target pi
```

- Failed as expected without `--force` and did not overwrite custom file

Verified uninstall behavior:

```bash
HOME="$TMP_HOME" rally command uninstall --target all --dry-run
HOME="$TMP_HOME" rally command uninstall --target all
```

- Managed codex/droid wrappers removed
- Unmanaged `~/.pi/commands/rally.md` preserved

Reinstall check:

```bash
HOME="$TMP_HOME" rally command install --target codex
HOME="$TMP_HOME" rally command install --target droid
HOME="$TMP_HOME" rally command doctor --target all --dry-run
```

- Managed wrappers recreated cleanly
- Doctor reported managed status for codex/droid and unmanaged status for pi custom file

## Result

Manual QA passed for:

- two-target wrapper installation
- `/rally`, `/rally build`, and invite-token routing
- doctor diagnostics
- uninstall cleanup
- reinstall behavior
- preservation of custom non-Rally wrapper files
91 changes: 91 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ COMMAND GROUPS:
Core loop: create, join, next, done, status, sessions
Canonical built-ins: plan ..., build ...
Generic workflows: workflow list, workflow action
Wrapper surface: command install|run|exec|doctor|uninstall
Compatibility aliases (deprecated): file-issue, challenge, agree, checkpoint, review
Handoff: chain

Expand Down Expand Up @@ -79,6 +80,8 @@ pub enum Command {
Build(BuildCommandArgs),
#[command(about = "Generic workflow commands")]
Workflow(WorkflowCommandArgs),
#[command(about = "Install and run short `/rally ...` wrapper commands")]
Command(CommandCommandArgs),
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
Expand Down Expand Up @@ -286,3 +289,91 @@ pub struct WorkflowActionArgs {
#[arg(long, help = "Path to a JSON file containing action arguments")]
pub args_file: Option<PathBuf>,
}

#[derive(Args, Debug)]
pub struct CommandCommandArgs {
#[command(subcommand)]
pub command: CommandSubcommand,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
pub enum CommandTargetArg {
Codex,
Droid,
Pi,
ClaudeCode,
Factory,
All,
}

#[derive(Subcommand, Debug)]
pub enum CommandSubcommand {
#[command(about = "Install Rally-managed wrapper artifacts")]
Install(CommandInstallArgs),
#[command(
about = "Wrapper execution entrypoint",
long_about = "Wrapper execution entrypoint.\n\nArgument contract:\n rally command run\n rally command run <workflow-or-invite>\n rally command run <workflow> <role>\n\nResolution precedence (implemented by this command family):\n 1) explicit args\n 2) invite token\n 3) saved context\n 4) interactive fallback"
)]
Run(CommandRunArgs),
#[command(
about = "Alias for `run` (identical behavior and argument contract)",
long_about = "Alias for `run` with identical behavior.\n\nArgument contract:\n rally command exec\n rally command exec <workflow-or-invite>\n rally command exec <workflow> <role>\n\nResolution precedence (implemented by this command family):\n 1) explicit args\n 2) invite token\n 3) saved context\n 4) interactive fallback"
)]
Exec(CommandRunArgs),
#[command(about = "Validate wrapper installation and target paths")]
Doctor(CommandDoctorArgs),
#[command(about = "Uninstall Rally-managed wrapper artifacts")]
Uninstall(CommandUninstallArgs),
}

#[derive(Args, Debug)]
pub struct CommandInstallArgs {
#[arg(long, value_enum, help = "Install target harness")]
pub target: CommandTargetArg,
#[arg(long, help = "Overwrite existing non-managed files")]
pub force: bool,
#[arg(long, help = "Preview writes without touching files")]
pub dry_run: bool,
}

#[derive(Args, Debug, Clone)]
pub struct CommandRunArgs {
#[arg(
value_name = "FIRST",
help = "Optional workflow shorthand (for example `build`) or invite token"
)]
pub first: Option<String>,
#[arg(value_name = "SECOND", help = "Optional role/agent selector")]
pub second: Option<String>,
#[arg(
value_name = "EXTRA",
trailing_var_arg = true,
allow_hyphen_values = true,
help = "Additional wrapper tokens passed to run-resolution logic"
)]
pub extra: Vec<String>,
#[arg(long, help = "Explicit session override")]
pub session: Option<String>,
#[arg(long = "as", help = "Explicit agent/role override")]
pub agent: Option<String>,
#[arg(long, help = "Explicit invite token override")]
pub invite: Option<String>,
#[arg(long, help = "Disable interactive fallback when unresolved")]
pub non_interactive: bool,
}

#[derive(Args, Debug)]
pub struct CommandDoctorArgs {
#[arg(long, value_enum, default_value = "all", help = "Target harness to inspect")]
pub target: CommandTargetArg,
#[arg(long, help = "Print checks without changing filesystem")]
pub dry_run: bool,
}

#[derive(Args, Debug)]
pub struct CommandUninstallArgs {
#[arg(long, value_enum, help = "Uninstall target harness")]
pub target: CommandTargetArg,
#[arg(long, help = "Preview removals without touching files")]
pub dry_run: bool,
}
Loading