Skip to content

Commit da91d80

Browse files
henrikjeclaude
andcommitted
feat(diff): remove arb diff command
The command does not provide enough value over `arb exec git diff` to justify its maintenance cost and CLI surface area. Its only meaningful coordination value is merge-base resolution, which is a single well-known git command. See DR-0096. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 83a7cea commit da91d80

20 files changed

Lines changed: 70 additions & 1225 deletions

GUIDELINES.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Beyond showing honest state, Arborist actively watches for conditions that signa
2424

2525
### Coordination and overview, not authoring
2626

27-
Arborist coordinates multi-repo operations (push, rebase, merge) and provides workspace-level overview (status, log, diff). It does not replace Git for authoring operations. Committing, staging, interactive rebase, and PR creation belong to direct interaction with each repository. `arb exec` bridges the gap for anything Arborist doesn't cover.
27+
Arborist coordinates multi-repo operations (push, rebase, merge) and provides workspace-level overview (status, log). It does not replace Git for authoring operations. Committing, staging, interactive rebase, and PR creation belong to direct interaction with each repository. `arb exec` bridges the gap for anything Arborist doesn't cover.
2828

2929
### Do one thing and do it well
3030

@@ -95,7 +95,7 @@ Tell the user *what happened*, not just *that it happened*. Use descriptive per-
9595

9696
**State-changing commands** (`push`, `pull`, `rebase`, `merge`, `reset`, `retarget`): accept optional `[repos...]` to narrow scope; default to all repos. Follow the five-phase workflow: assess → plan → confirm → execute → summarize. Each defines a typed assessment interface classifying repos into will-operate / up-to-date / skip-with-reason.
9797

98-
**Overview commands** (`status`, `log`, `diff`) are read-only. They scope to the feature branch via base branch resolution, skip detached/drifted repos with explanation, and support `[repos...]` filtering, `--json`, and `--verbose`.
98+
**Overview commands** (`status`, `log`) are read-only. They scope to the feature branch via base branch resolution, skip detached/drifted repos with explanation, and support `[repos...]` filtering, `--json`, and `--verbose`.
9999

100100
### Expected flags per command category
101101

@@ -122,7 +122,7 @@ Individual sync commands add domain-specific flags (`--force`, `--autostash`, `-
122122
| `--all-repos` (`-a`) | Select all repos (scripting) |
123123
| Interactive picker | When no args and TTY |
124124

125-
**Overview commands** (`status`, `log`, `diff`):
125+
**Overview commands** (`status`, `log`):
126126

127127
| Flag | Purpose |
128128
|------|---------|
@@ -142,7 +142,7 @@ When multiple operations manage the same `.arb/` subsystem (repos, templates), g
142142

143143
**`pull`** always fetches — it inherently needs fresh remote state to assess what to pull. It does not offer `--no-fetch`.
144144

145-
**Content commands** (`log`, `diff`) do not fetch by default — stale content is less confusing. `--fetch` opts in.
145+
**`log`** does not fetch by default — stale content is less confusing. `--fetch` opts in.
146146

147147
The `ARB_NO_FETCH` environment variable globally suppresses automatic fetching — equivalent to passing `-N` to every command. Explicit `--fetch` overrides it. `pull` is unaffected (it always fetches). See `decisions/0045-universal-fetch-flags.md` and `decisions/0087-arb-no-fetch-env-var.md`.
148148

@@ -174,9 +174,9 @@ When repos are the command's primary target, they are positional arguments (`arb
174174

175175
### Status-based filtering: `--where` and `--dirty`
176176

177-
`--where` (`-w`) filters repos by `RepoFlags`. Supported on every command that gathers workspace status: `status`, `diff`, `log`, `exec`, `open`, `list`, `delete`, `push`, `pull`, `rebase`, `merge`, `reset`. Commands that don't gather status (e.g. `attach`, `create`, `branch rename`) do not get `--where`.
177+
`--where` (`-w`) filters repos by `RepoFlags`. Supported on every command that gathers workspace status: `status`, `log`, `exec`, `open`, `list`, `delete`, `push`, `pull`, `rebase`, `merge`, `reset`. Commands that don't gather status (e.g. `attach`, `create`, `branch rename`) do not get `--where`.
178178

179-
`--dirty` (`-d`) is a shorthand for `--where dirty`, mutually exclusive with `--where`. Only offered where "dirty" is a natural filter: `status`, `diff`, `log`, `exec`, `open`, `list`. Omitted from sync commands and `delete`.
179+
`--dirty` (`-d`) is a shorthand for `--where dirty`, mutually exclusive with `--where`. Only offered where "dirty" is a natural filter: `status`, `log`, `exec`, `open`, `list`. Omitted from sync commands and `delete`.
180180

181181
Filter terms are organized by orthogonal dimension (see ARCHITECTURE.md). Positional terms use the `<position>-<axis>` pattern (`ahead-share`, `behind-base`) so axis relationships are obvious. Filter names describe state, not suggested action — whether a repo "needs rebase" depends on the user's intent; the filter just says `behind-base`. Named positive terms exist only where they provide non-trivial composition (`pushed` = `^ahead-share+^no-share`) or are natural vocabulary (`clean`, `safe`); trivially-derivable positives use `^` negation instead (`^behind-base` rather than a named `synced-base`).
182182

QA.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Check that similar commands follow the same patterns as defined in GUIDELINES.md
5858

5959
- State-changing commands (`push`, `pull`, `rebase`, `merge`, `reset`, `retarget`) all follow the five-phase mutation flow.
6060
- Membership-changing commands (`attach`, `detach`, `create`) handle `[repos...]`, interactive picker, `--all-repos`, and non-TTY errors consistently.
61-
- Overview commands (`status`, `log`, `diff`) handle `--fetch`/`--no-fetch`, `--json`, `[repos...]` consistently. They must not fetch by default.
61+
- Overview commands (`status`, `log`) handle `--fetch`/`--no-fetch`, `--json`, `[repos...]` consistently. They must not fetch by default.
6262
- Mutation commands (`push`, `rebase`, `merge`) must fetch by default. `exec` and `open` must not have fetch flags.
6363
- `--where` filtering works the same everywhere it appears. Filter terms must be anchored to `FILTER_TERMS` in `status.ts` — no ad-hoc string comparisons.
6464
- `--force` has per-command semantics (plan modifier in `push`, safety bypass in `delete`). Verify that help text and behavior match each command's specific contract.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ arb exec npm install # run a command in every repo
352352
arb status -w dirty,unpushed # filter repos by status flags
353353
```
354354

355-
All state-changing commands support `--dry-run` to preview the plan and `--yes` to skip confirmation prompts. `status`, `branch`, `list`, `log`, `diff`, and `repo list` support `--json` for structured output and `--quiet` for one name per line — useful for feeding into other commands. `arb exec` runs any command in each repo; `--where` (`-w`) filters repos by status flags like `dirty`, `behind-base`, or `ahead-share` and works across most commands. Exit codes are meaningful: 0 for success, 1 for issues, 130 for user abort. Human-facing output goes to stderr, machine-parsable data to stdout — so piping works naturally.
355+
All state-changing commands support `--dry-run` to preview the plan and `--yes` to skip confirmation prompts. `status`, `branch`, `list`, `log`, and `repo list` support `--json` for structured output and `--quiet` for one name per line — useful for feeding into other commands. `arb exec` runs any command in each repo; `--where` (`-w`) filters repos by status flags like `dirty`, `behind-base`, or `ahead-share` and works across most commands. Exit codes are meaningful: 0 for success, 1 for issues, 130 for user abort. Human-facing output goes to stderr, machine-parsable data to stdout — so piping works naturally.
356356

357357
## Alternatives
358358

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Remove the diff command
2+
3+
Date: 2026-03-26
4+
5+
## Context
6+
7+
DR-0023 introduced `arb diff` as the third overview command alongside `status` and `log`, forming a coherent triad. The command shows the cumulative diff of the feature branch since diverging from the base branch across all repos, with TTY, pipe, and JSON output modes.
8+
9+
After using the command in practice, the value proposition turned out to be thin. In a workspace with multiple repos, the full diff across all repos is overwhelming and rarely useful — developers diff interactively in their editor or one repo at a time. The `--stat` summary mode was the most useful output, but it remains a thin wrapper around `git diff --stat` with merge-base resolution.
10+
11+
## Options
12+
13+
### A: Remove `arb diff` entirely
14+
15+
Users who need the diff use `arb exec -- git diff --stat $(git merge-base origin/main HEAD)` or work in individual repos.
16+
17+
- **Pros:** Maximum surface area reduction (~500 lines of command code, ~587 lines of integration tests). Simplifies the overview layer. One fewer command to learn, document, test, and maintain.
18+
- **Cons:** Breaks the "overview triad" from DR-0023. Loses cross-repo summary stats and JSON output.
19+
20+
### B: Fold diff into `arb log --diff`
21+
22+
Add a `--diff` flag to `arb log` that shows per-commit patches or cumulative diffstat.
23+
24+
- **Pros:** Retains some diff capability.
25+
- **Cons:** Cumulative changeset and per-commit patches are fundamentally different operations. Expands log's scope beyond its core question, violating "do one thing well." `--verbose` already shows per-commit changed files.
26+
27+
### C: Keep `arb diff` (status quo)
28+
29+
No changes.
30+
31+
- **Pros:** The triad remains complete.
32+
- **Cons:** Maintaining a command with thin value.
33+
34+
## Decision
35+
36+
Option A: remove `arb diff` entirely.
37+
38+
## Reasoning
39+
40+
The "Evaluating new operations" framework in GUIDELINES.md asks: "Would `arb exec` leave users meaningfully worse off?" For diff, the answer is no. The command does not involve conflict prediction, divergence analysis, plan/confirm flows, or state machines — the hallmarks of commands that earn their place per the "Minimal, semantic CLI" principle. Its only meaningful coordination value is merge-base resolution, which is a single well-known git command.
41+
42+
Option B was rejected because grafting cumulative-diff semantics onto `arb log` creates conceptual confusion. If the command does not earn its place as a standalone, attaching it to another command does not change that.
43+
44+
## Consequences
45+
46+
- The overview command layer is now `status` and `log`. GUIDELINES.md references are updated accordingly.
47+
- DR-0023 remains unchanged as a historical record. Its reasoning about the authoring boundary (`arb commit`, `arb pr`) still holds; only the diff inclusion is reversed.
48+
- DR-0031 (diff merge-base against working tree) is now historical context with no active code.
49+
- Users who relied on `arb diff --json` for scripting need to adapt. This is acceptable during pre-release per "Prefer correctness over backwards compatibility."

docs/scripting-automation.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ arb delete --all-safe --where gone # batch-remove workspaces whose bran
4646

4747
For workspace-level commands (`list`, `delete`), AND applies per-repo: a workspace matches `dirty+ahead-share` only if a _single_ repo is both dirty and ahead-share, not if one repo is dirty and a different repo is ahead-share.
4848

49-
`--where` is supported on `status`, `exec`, `open`, `diff`, `log`, `list`, `delete`, `push`, `pull`, `rebase`, `merge`, and `reset`. On `status`, `exec`, `open`, `diff`, `log`, and `list`, the shorthand `--dirty` (`-d`) is equivalent to `--where dirty`.
49+
`--where` is supported on `status`, `exec`, `open`, `log`, `list`, `delete`, `push`, `pull`, `rebase`, `merge`, and `reset`. On `status`, `exec`, `open`, `log`, and `list`, the shorthand `--dirty` (`-d`) is equivalent to `--where dirty`.
5050

5151
The full list of filter terms:
5252

@@ -120,7 +120,7 @@ Commands that accept `[repos...]` or `[names...]` also read names from stdin whe
120120

121121
```bash
122122
arb status -q --where dirty | arb exec git stash # stash only dirty repos (exec doesn't read stdin)
123-
arb status -q --where ahead-share | arb diff # diff only ahead-share repos
123+
arb status -q --where ahead-share | arb log # log only ahead-share repos
124124
arb list -q --where gone | arb delete -y # delete gone workspaces
125125
arb status -q | grep -v legacy | arb rebase -y # rebase all except "legacy"
126126
```
@@ -132,7 +132,7 @@ arb push --where ^behind-base -y # only push repos that are already rebased
132132
arb rebase --where ^diverged -y # skip diverged repos, rebase the easy ones
133133
```
134134

135-
Stdin-accepting commands: `create`, `attach`, `detach`, `status`, `diff`, `log`, `push`, `pull`, `rebase`, `merge`, `reset`, `delete`.
135+
Stdin-accepting commands: `create`, `attach`, `detach`, `status`, `log`, `push`, `pull`, `rebase`, `merge`, `reset`, `delete`.
136136

137137
`exec` and `open` are excluded because they inherit stdin for child processes. Use xargs instead:
138138

@@ -142,14 +142,13 @@ arb status -q --where dirty | xargs -I{} arb exec --repo {} make test
142142

143143
## Machine-readable output
144144

145-
Six commands support `--json` for structured output to stdout:
145+
Five commands support `--json` for structured output to stdout:
146146

147147
| Command | Output shape |
148148
|---------|-------------|
149149
| `arb status --json` | Object with `workspace`, `branch`, `base`, `repos[]`, aggregates |
150150
| `arb list --json` | Array of workspace objects with status counts and labels |
151151
| `arb log --json` | Object with `workspace`, `branch`, `base`, `repos[]`, `totalCommits` |
152-
| `arb diff --json` | Object with `workspace`, `branch`, `base`, `repos[]`, file/line totals |
153152
| `arb branch --json` | Object with `branch`, `base`, per-repo branches |
154153
| `arb repo list --json` | Array of repo entries with remote role detail |
155154

docs/sync.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,6 @@ All commands show a plan before proceeding. Add `--verbose` (`-v`) to see the ac
113113
| Sync commands (`push`, `rebase`, `merge`) | Yes | `--no-fetch` (`-N`) |
114114
| `pull` | Always | (no opt-out) |
115115
| Dashboard commands (`status`, `list`) | Yes | `--no-fetch` (`-N`) |
116-
| Content commands (`log`, `diff`) | No | `--fetch` |
116+
| `log` | No | `--fetch` |
117117

118118
Set `ARB_NO_FETCH` to disable automatic fetching globally — equivalent to passing `-N` to every command. Explicit `--fetch` overrides the env var. `pull` always fetches regardless, since it inherently needs fresh remote state.

shell/arb.bash

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -469,20 +469,6 @@ __arb_complete_log() {
469469
COMPREPLY=($(compgen -W "$(__arb_workspace_repo_names "$base_dir")" -- "$cur"))
470470
}
471471

472-
__arb_complete_diff() {
473-
local base_dir="$1" cur="$2"
474-
local prev="${COMP_WORDS[COMP_CWORD-1]}"
475-
if [[ "$prev" == "-w" || "$prev" == "--where" ]]; then
476-
__arb_complete_where_value "$cur"
477-
return
478-
fi
479-
if [[ "$cur" == -* ]]; then
480-
COMPREPLY=($(compgen -W "--fetch -N --no-fetch --stat --json --schema -d --dirty -w --where" -- "$cur"))
481-
return
482-
fi
483-
COMPREPLY=($(compgen -W "$(__arb_workspace_repo_names "$base_dir")" -- "$cur"))
484-
}
485-
486472
__arb_complete_pull() {
487473
local base_dir="$1" cur="$2"
488474
local prev="${COMP_WORDS[COMP_CWORD-1]}"
@@ -657,7 +643,7 @@ __arb_complete_template() {
657643
__arb_complete_help() {
658644
local base_dir="$1" cur="$2"
659645
local topics="filtering remotes stacked templates scripting"
660-
local commands="init repo create delete rename list path cd attach detach status watch branch pull push rebase retarget merge reset undo log diff exec open template"
646+
local commands="init repo create delete rename list path cd attach detach status watch branch pull push rebase retarget merge reset undo log exec open template"
661647
COMPREPLY=($(compgen -W "$topics $commands" -- "$cur"))
662648
}
663649

@@ -683,7 +669,7 @@ _arb() {
683669

684670
# Completing the subcommand itself
685671
if ((COMP_CWORD <= cmd_pos)); then
686-
local commands="init repo create delete rename list path cd attach detach status watch branch pull push rebase retarget merge reset log diff exec open template help"
672+
local commands="init repo create delete rename list path cd attach detach status watch branch pull push rebase retarget merge reset log exec open template help"
687673
# Also complete global flags
688674
if [[ "$cur" == -* ]]; then
689675
COMPREPLY=($(compgen -W "-C -h --help --version --debug" -- "$cur"))
@@ -717,7 +703,6 @@ _arb() {
717703
reset) __arb_complete_reset "$base_dir" "$cur" ;;
718704
undo) COMPREPLY=($(compgen -W "-y --yes -n --dry-run -f --force" -- "$cur")) ;;
719705
log) __arb_complete_log "$base_dir" "$cur" ;;
720-
diff) __arb_complete_diff "$base_dir" "$cur" ;;
721706
exec) __arb_complete_exec "$base_dir" "$cur" ;;
722707
open) __arb_complete_open "$base_dir" "$cur" ;;
723708
template) __arb_complete_template "$base_dir" "$cur" ;;

shell/arb.zsh

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ _arb() {
200200
'reset:Reset all repos to the share branch (or base if not pushed)'
201201
'undo:Undo the last workspace operation'
202202
'log:Show feature branch commits across repos'
203-
'diff:Show feature branch diff across repos'
204203
'exec:Run a command in each repo'
205204
'open:Open repos in an application'
206205
'template:Manage workspace templates'
@@ -561,17 +560,6 @@ _arb() {
561560
'(-d --dirty -w --where)'{-w,--where}'[Filter repos by status flags]:filter:_arb_where_filter' \
562561
'*:repo:($ws_repo_names)'
563562
;;
564-
diff)
565-
_arguments \
566-
'(-N --fetch --no-fetch)--fetch[Fetch before showing diff]' \
567-
'(-N --fetch --no-fetch)'{-N,--no-fetch}'[Skip fetching (default)]' \
568-
'--stat[Show diffstat summary instead of full diff]' \
569-
'(--json --schema)--json[Output structured JSON]' \
570-
'(--schema --json)--schema[Print JSON Schema for --json output]' \
571-
'(-d --dirty -w --where)'{-d,--dirty}'[Only diff dirty repos]' \
572-
'(-d --dirty -w --where)'{-w,--where}'[Filter repos by status flags]:filter:_arb_where_filter' \
573-
'*:repo:($ws_repo_names)'
574-
;;
575563
help)
576564
local -a help_completions=(
577565
'filtering:Filter syntax for --where and --dirty'
@@ -598,7 +586,6 @@ _arb() {
598586
'merge:Merge the base branch into feature branches'
599587
'reset:Reset all repos to the share branch (or base if not pushed)'
600588
'log:Show feature branch commits across repos'
601-
'diff:Show feature branch diff across repos'
602589
'exec:Run a command in each repo'
603590
'open:Open repos in an application'
604591
'template:Manage workspace templates'

0 commit comments

Comments
 (0)