From c68304463b52b281b7389d731bc4279cfb718ddb Mon Sep 17 00:00:00 2001 From: Bjoern Kottner <12345+bjoern@users.noreply.github.com> Date: Fri, 29 May 2026 14:16:56 +0000 Subject: [PATCH 1/3] docs: openspec propose add-branch-command --- .../changes/add-branch-command/.openspec.yaml | 2 + openspec/changes/add-branch-command/design.md | 45 +++++++++++++++++++ .../changes/add-branch-command/proposal.md | 27 +++++++++++ .../specs/branch-command/spec.md | 34 ++++++++++++++ .../specs/command-execution/spec.md | 17 +++++++ openspec/changes/add-branch-command/tasks.md | 24 ++++++++++ 6 files changed, 149 insertions(+) create mode 100644 openspec/changes/add-branch-command/.openspec.yaml create mode 100644 openspec/changes/add-branch-command/design.md create mode 100644 openspec/changes/add-branch-command/proposal.md create mode 100644 openspec/changes/add-branch-command/specs/branch-command/spec.md create mode 100644 openspec/changes/add-branch-command/specs/command-execution/spec.md create mode 100644 openspec/changes/add-branch-command/tasks.md diff --git a/openspec/changes/add-branch-command/.openspec.yaml b/openspec/changes/add-branch-command/.openspec.yaml new file mode 100644 index 0000000..1194417 --- /dev/null +++ b/openspec/changes/add-branch-command/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-29 diff --git a/openspec/changes/add-branch-command/design.md b/openspec/changes/add-branch-command/design.md new file mode 100644 index 0000000..723566e --- /dev/null +++ b/openspec/changes/add-branch-command/design.md @@ -0,0 +1,45 @@ +## Context + +`gitctl` provides uniform fan-out of git commands across multiple repositories via a shared worker pool in `gitrepo/gitrepos.go`. Adding a new command follows a well-established three-step pattern: add a constant + switch case in `gitrepo/gitrepo.go`, create a cobra command file in `app/cmd/`, and register it in `root.go`. + +`git branch --show-current` prints the name of the currently checked-out branch and exits 0. In detached HEAD state it prints an empty string and still exits 0. + +## Goals / Non-Goals + +**Goals:** +- Add `gitctl branch` that runs `git branch --show-current` across all discovered repositories. +- Show the current branch name per repo using the standard output format. +- Keep implementation structurally identical to `gitfetch.go` / `gitstash.go`. + +**Non-Goals:** +- Listing all branches (`git branch --all`) — that is a distinct use case for a future change. +- Switching branches across repos — out of scope. +- Filtering or grouping repos by branch name — deferred to a future change. +- Special-casing detached HEAD with a human-friendly label — acceptable edge case; empty output is unambiguous to git users. + +## Decisions + +### Decision 1: Use `git branch --show-current` (not `git rev-parse --abbrev-ref HEAD`) + +**Chosen**: `exec.Command("git", "branch", "--show-current")`. + +`--show-current` was introduced in git 2.22 (2019) and is the canonical modern form. It is clean, predictable, and unambiguous. `git rev-parse --abbrev-ref HEAD` is a workaround for older gits and returns the literal string `"HEAD"` in detached state rather than empty — less clean. + +**Trade-off**: Requires git ≥ 2.22. gitctl has no explicit minimum git version today; this is acceptable given git 2.22 is over 5 years old. + +### Decision 2: Pass `--show-current` as a second argument in the switch case + +**Chosen**: The `runRaw` switch handles `GitBranch` with `exec.Command(gitCommand, branchCommand, "--show-current")`. The `branchCommand` constant is `"branch"` (consistent with other single-word constants). The flag is passed inline in the case — no additional constant needed. + +**Why not a separate constant?** The flag is not reused elsewhere; inlining it keeps the code readable without adding noisy constants. + +### Decision 3: Detached HEAD emits empty output — no special handling + +**Chosen**: `git branch --show-current` exits 0 with empty stdout in detached HEAD state. The existing output formatter will display an empty body for that repo. No special-casing is added. + +**Why not add a "(detached HEAD)" label?** Would require touching the `color` package global state or adding logic to `FormatOutput`, adding complexity for a rare edge case. A future `output-enhancements` change can address this. + +## Risks / Trade-offs + +- **git < 2.22** — `--show-current` is not recognised; git exits non-zero and reports an error. → Documented in command description; acceptable given the age of git 2.22. +- **Empty output for detached HEAD** — may confuse users who expect to see "HEAD" or similar. → Acceptable for v1; document in short description or a future follow-on. diff --git a/openspec/changes/add-branch-command/proposal.md b/openspec/changes/add-branch-command/proposal.md new file mode 100644 index 0000000..36bd2fa --- /dev/null +++ b/openspec/changes/add-branch-command/proposal.md @@ -0,0 +1,27 @@ +## Why + +When working across many feature branches simultaneously, it's essential to know which branch each repository is on at a glance. `git status` provides this but includes working-tree noise; there's no clean `gitctl` command that shows just the current branch per repo. + +## What Changes + +- Add a `branch` subcommand that runs `git branch --show-current` on all discovered repositories and reports the current branch name for each. +- Register `branch` alongside `status`, `pull`, `fetch`, and `stash` in the root command. +- Extend the `runRaw` command switch to handle `branch`. +- Add `branch` to the supported commands table in the command-execution spec. + +## Capabilities + +### New Capabilities + +- `branch-command`: The `gitctl branch` subcommand — runs `git branch --show-current` concurrently across all discovered repositories, collects results in discovery order, and reports the current branch name per repo using the standard output format. + +### Modified Capabilities + +- `command-execution`: A new supported command (`branch`) is added to the command table. No behavioral changes to the execution model itself. + +## Impact + +- `app/cmd/gitbranch.go`: New file defining the `branchCmd` cobra command. +- `app/cmd/root.go`: Register `branchCmd`. +- `gitrepo/gitrepo.go`: Add `GitBranch` constant and `branchCommand` const (`--show-current` flag passed); handle in the `runRaw` switch. +- `openspec/specs/command-execution/spec.md`: Add `branch` to the supported commands table. diff --git a/openspec/changes/add-branch-command/specs/branch-command/spec.md b/openspec/changes/add-branch-command/specs/branch-command/spec.md new file mode 100644 index 0000000..72cacb6 --- /dev/null +++ b/openspec/changes/add-branch-command/specs/branch-command/spec.md @@ -0,0 +1,34 @@ +## ADDED Requirements + +### Requirement: gitctl branch subcommand exists +The CLI SHALL expose a `branch` subcommand that runs `git branch --show-current` concurrently across all repositories discovered from the configured base directories. + +#### Scenario: Branch shows current branch name +- **WHEN** a repository is on a named branch +- **THEN** `git branch --show-current` outputs the branch name +- **THEN** the result is displayed in the standard per-repository output format + +#### Scenario: Branch on detached HEAD exits 0 +- **WHEN** a repository is in detached HEAD state +- **THEN** `git branch --show-current` exits 0 with empty stdout +- **THEN** this is treated as success, not an error + +#### Scenario: Branch runs concurrently across all repositories +- **WHEN** multiple repositories are discovered +- **THEN** `git branch --show-current` is dispatched to all repositories via the existing worker pool +- **THEN** results are collected and printed in discovery order + +### Requirement: git branch command form +The implementation SHALL execute `exec.Command("git", "branch", "--show-current")`. + +#### Scenario: Correct command form used +- **WHEN** the branch subcommand is invoked +- **THEN** the process spawned is `git branch --show-current` with no additional arguments + +### Requirement: Branch honours dry-run mode +When `--dry-run` is set, the branch subcommand SHALL not spawn any git processes, following the existing dry-run contract. + +#### Scenario: Dry-run with branch +- **WHEN** `gitctl branch --dry-run` is invoked +- **THEN** no `git branch` processes are spawned +- **THEN** a message is printed for each repository indicating what would have run diff --git a/openspec/changes/add-branch-command/specs/command-execution/spec.md b/openspec/changes/add-branch-command/specs/command-execution/spec.md new file mode 100644 index 0000000..3945bad --- /dev/null +++ b/openspec/changes/add-branch-command/specs/command-execution/spec.md @@ -0,0 +1,17 @@ +## MODIFIED Requirements + +### Requirement: Supported commands +`gitctl` SHALL support the following subcommands, each mapping to a single `git` invocation per repository. + +| Subcommand | Git command executed | +|------------|-------------------------------| +| `status` | `git status` | +| `pull` | `git pull` | +| `fetch` | `git fetch` | +| `stash` | `git stash` | +| `branch` | `git branch --show-current` | + +#### Scenario: branch subcommand dispatches git branch --show-current +- **WHEN** the user runs `gitctl branch` +- **THEN** `git branch --show-current` is executed in every discovered repository +- **THEN** results are printed in discovery order using the standard output format diff --git a/openspec/changes/add-branch-command/tasks.md b/openspec/changes/add-branch-command/tasks.md new file mode 100644 index 0000000..b67d12e --- /dev/null +++ b/openspec/changes/add-branch-command/tasks.md @@ -0,0 +1,24 @@ +## 1. Core Implementation + +- [ ] 1.1 Add `GitBranch = "branch"` constant and `branchCommand = "branch"` in `gitrepo/gitrepo.go` +- [ ] 1.2 Add `case GitBranch` to the `runRaw` command switch in `gitrepo/gitrepo.go`, executing `exec.Command(gitCommand, branchCommand, "--show-current")` +- [ ] 1.3 Create `app/cmd/gitbranch.go` defining `branchCmd` following the same pattern as `gitfetch.go`; set short description to "Show current branch for multiple git repositories." +- [ ] 1.4 Register `branchCmd` with `rootCmd.AddCommand(branchCmd)` in `app/cmd/root.go` + +## 2. Tests + +- [ ] 2.1 Add `TestBranchCommandExecutesGitBranchOnLocalRepo` in `app/cmd/gitbranch_test.go` (dry-run mode, mirrors `gitfetch_test.go`) +- [ ] 2.2 Add `TestGitRepoRunGitBranch` in `gitrepo/gitrepo_test.go` verifying `GitBranch` executes against a valid repo path +- [ ] 2.3 Add `TestRunGitBranchCommand` in `gitrepo/gitrepos_test.go` verifying `RunGitCommand(GitBranch, baseDirs)` succeeds in dry-run + +## 3. Spec Promotion + +- [ ] 3.1 Add `branch` to the supported commands table in `openspec/specs/command-execution/spec.md` +- [ ] 3.2 Create `openspec/specs/branch-command/spec.md` (promote from change specs) + +## 4. Verification + +- [ ] 4.1 Run `go build ./...` — no compile errors +- [ ] 4.2 Run `go test -race ./...` — all tests pass, no races +- [ ] 4.3 Run `go fmt ./...` on changed files +- [ ] 4.4 Verify `gitctl branch --help` shows the correct usage description From e4dbd6c13d4deec7ff1ca08e6562833766ddcf91 Mon Sep 17 00:00:00 2001 From: Bjoern Kottner <12345+bjoern@users.noreply.github.com> Date: Fri, 29 May 2026 14:34:25 +0000 Subject: [PATCH 2/3] docs: openspec apply add-branch-command --- app/cmd/gitbranch.go | 24 ++++++++++++++ app/cmd/gitbranch_test.go | 28 ++++++++++++++++ app/cmd/root.go | 1 + gitrepo/gitrepo.go | 7 ++++ gitrepo/gitrepo_test.go | 11 +++++++ gitrepo/gitrepos_test.go | 13 ++++++++ openspec/changes/add-branch-command/tasks.md | 26 +++++++-------- openspec/specs/branch-command/spec.md | 34 ++++++++++++++++++++ openspec/specs/command-execution/spec.md | 15 +++++---- 9 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 app/cmd/gitbranch.go create mode 100644 app/cmd/gitbranch_test.go create mode 100644 openspec/specs/branch-command/spec.md diff --git a/app/cmd/gitbranch.go b/app/cmd/gitbranch.go new file mode 100644 index 0000000..7f76c3f --- /dev/null +++ b/app/cmd/gitbranch.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/bjoernkarma/gitctl/config" + "github.com/bjoernkarma/gitctl/gitrepo" +) + +var branchCmd = &cobra.Command{ + Use: "branch", + Short: "Show current branch for multiple git repositories.", + RunE: func(cmd *cobra.Command, args []string) error { + baseDirs, err := config.GetBaseDirs() + if err != nil { + return err + } + if err := gitrepo.RunGitCommand(gitrepo.GitBranch, baseDirs); err != nil { + // Errors have already been displayed via the color package. + return ErrSilent + } + return nil + }, +} diff --git a/app/cmd/gitbranch_test.go b/app/cmd/gitbranch_test.go new file mode 100644 index 0000000..8190cb7 --- /dev/null +++ b/app/cmd/gitbranch_test.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "bytes" + "log" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBranchCommandExecutesGitBranchOnLocalRepo(t *testing.T) { + var buf bytes.Buffer + t.Setenv("GITCTL_VERBOSITY_DEBUG", "true") + originalLogWriter := log.Writer() + log.SetOutput(&buf) + rootCmd.SetOut(&buf) + rootCmd.SetErr(&buf) + defer func() { + log.SetOutput(originalLogWriter) + }() + + rootCmd.SetArgs([]string{"branch", "--local", "--debug", "--verbose"}) + err := rootCmd.Execute() + + expected := "Configuration settings:" + assert.Contains(t, buf.String(), expected, "expected %v to be contained in %v", expected, buf.String()) + assert.NoError(t, err) +} diff --git a/app/cmd/root.go b/app/cmd/root.go index 34bb919..67621bb 100644 --- a/app/cmd/root.go +++ b/app/cmd/root.go @@ -95,6 +95,7 @@ func init() { rootCmd.AddCommand(pullCmd) rootCmd.AddCommand(fetchCmd) rootCmd.AddCommand(stashCmd) + rootCmd.AddCommand(branchCmd) } func InitConfig() error { diff --git a/gitrepo/gitrepo.go b/gitrepo/gitrepo.go index 3397376..5620669 100644 --- a/gitrepo/gitrepo.go +++ b/gitrepo/gitrepo.go @@ -14,8 +14,10 @@ import ( const ( gitDirToSearch = ".git" gitCommand = "git" + branchCommand = "branch" fetchCommand = "fetch" pullCommand = "pull" + stashCommand = "stash" statusCommand = "status" ) @@ -26,6 +28,7 @@ type GitRepo struct { } const ( + GitBranch = "branch" GitFetch = "fetch" GitPull = "pull" GitStash = "stash" @@ -83,10 +86,14 @@ func (gitRepo *GitRepo) runRaw(command string) ([]byte, error) { var gitCmd *exec.Cmd switch command { + case GitBranch: + gitCmd = exec.Command(gitCommand, branchCommand, "--show-current") case GitFetch: gitCmd = exec.Command(gitCommand, fetchCommand) case GitPull: gitCmd = exec.Command(gitCommand, pullCommand) + case GitStash: + gitCmd = exec.Command(gitCommand, stashCommand) case GitStatus: gitCmd = exec.Command(gitCommand, statusCommand) default: diff --git a/gitrepo/gitrepo_test.go b/gitrepo/gitrepo_test.go index 87c2a92..72298cd 100644 --- a/gitrepo/gitrepo_test.go +++ b/gitrepo/gitrepo_test.go @@ -102,6 +102,17 @@ func TestGitRepoRunGitFetch(t *testing.T) { _ = err } +func TestGitRepoRunGitBranch(t *testing.T) { + testDir, _ := filepath.Abs(microserviceDirPath) + gitRepo := GitRepo{path: testDir} + + output, err := gitRepo.RunGitCommand(GitBranch) + + // branch --show-current on a bare fixture repo may return empty output; just verify no crash + assert.NotNil(t, output) + _ = err +} + func TestGitRepoEmptyRunGitStatus(t *testing.T) { // Call the function under test gitRepo := GitRepo{path: ""} diff --git a/gitrepo/gitrepos_test.go b/gitrepo/gitrepos_test.go index adfc792..95e1fa1 100644 --- a/gitrepo/gitrepos_test.go +++ b/gitrepo/gitrepos_test.go @@ -162,6 +162,19 @@ func TestRunWithWorkerPoolPreservesDiscoveryOrder(t *testing.T) { assert.NoError(t, results[2].err) } +func TestRunGitBranchCommand(t *testing.T) { + viper.Reset() + t.Cleanup(viper.Reset) + viper.Set(config.GitCtlDryRun, true) + + command := GitBranch + testDir, _ := filepath.Abs(testDirPath) + baseDirs := []string{testDir} + + err := RunGitCommand(command, baseDirs) + assert.NoError(t, err) +} + func TestRunGitFetchCommand(t *testing.T) { viper.Reset() t.Cleanup(viper.Reset) diff --git a/openspec/changes/add-branch-command/tasks.md b/openspec/changes/add-branch-command/tasks.md index b67d12e..d764b3a 100644 --- a/openspec/changes/add-branch-command/tasks.md +++ b/openspec/changes/add-branch-command/tasks.md @@ -1,24 +1,24 @@ ## 1. Core Implementation -- [ ] 1.1 Add `GitBranch = "branch"` constant and `branchCommand = "branch"` in `gitrepo/gitrepo.go` -- [ ] 1.2 Add `case GitBranch` to the `runRaw` command switch in `gitrepo/gitrepo.go`, executing `exec.Command(gitCommand, branchCommand, "--show-current")` -- [ ] 1.3 Create `app/cmd/gitbranch.go` defining `branchCmd` following the same pattern as `gitfetch.go`; set short description to "Show current branch for multiple git repositories." -- [ ] 1.4 Register `branchCmd` with `rootCmd.AddCommand(branchCmd)` in `app/cmd/root.go` +- [x] 1.1 Add `GitBranch = "branch"` constant and `branchCommand = "branch"` in `gitrepo/gitrepo.go` +- [x] 1.2 Add `case GitBranch` to the `runRaw` command switch in `gitrepo/gitrepo.go`, executing `exec.Command(gitCommand, branchCommand, "--show-current")` +- [x] 1.3 Create `app/cmd/gitbranch.go` defining `branchCmd` following the same pattern as `gitfetch.go`; set short description to "Show current branch for multiple git repositories." +- [x] 1.4 Register `branchCmd` with `rootCmd.AddCommand(branchCmd)` in `app/cmd/root.go` ## 2. Tests -- [ ] 2.1 Add `TestBranchCommandExecutesGitBranchOnLocalRepo` in `app/cmd/gitbranch_test.go` (dry-run mode, mirrors `gitfetch_test.go`) -- [ ] 2.2 Add `TestGitRepoRunGitBranch` in `gitrepo/gitrepo_test.go` verifying `GitBranch` executes against a valid repo path -- [ ] 2.3 Add `TestRunGitBranchCommand` in `gitrepo/gitrepos_test.go` verifying `RunGitCommand(GitBranch, baseDirs)` succeeds in dry-run +- [x] 2.1 Add `TestBranchCommandExecutesGitBranchOnLocalRepo` in `app/cmd/gitbranch_test.go` (dry-run mode, mirrors `gitfetch_test.go`) +- [x] 2.2 Add `TestGitRepoRunGitBranch` in `gitrepo/gitrepo_test.go` verifying `GitBranch` executes against a valid repo path +- [x] 2.3 Add `TestRunGitBranchCommand` in `gitrepo/gitrepos_test.go` verifying `RunGitCommand(GitBranch, baseDirs)` succeeds in dry-run ## 3. Spec Promotion -- [ ] 3.1 Add `branch` to the supported commands table in `openspec/specs/command-execution/spec.md` -- [ ] 3.2 Create `openspec/specs/branch-command/spec.md` (promote from change specs) +- [x] 3.1 Add `branch` to the supported commands table in `openspec/specs/command-execution/spec.md` +- [x] 3.2 Create `openspec/specs/branch-command/spec.md` (promote from change specs) ## 4. Verification -- [ ] 4.1 Run `go build ./...` — no compile errors -- [ ] 4.2 Run `go test -race ./...` — all tests pass, no races -- [ ] 4.3 Run `go fmt ./...` on changed files -- [ ] 4.4 Verify `gitctl branch --help` shows the correct usage description +- [x] 4.1 Run `go build ./...` — no compile errors +- [x] 4.2 Run `go test -race ./...` — all tests pass, no races +- [x] 4.3 Run `go fmt ./...` on changed files +- [x] 4.4 Verify `gitctl branch --help` shows the correct usage description diff --git a/openspec/specs/branch-command/spec.md b/openspec/specs/branch-command/spec.md new file mode 100644 index 0000000..72cacb6 --- /dev/null +++ b/openspec/specs/branch-command/spec.md @@ -0,0 +1,34 @@ +## ADDED Requirements + +### Requirement: gitctl branch subcommand exists +The CLI SHALL expose a `branch` subcommand that runs `git branch --show-current` concurrently across all repositories discovered from the configured base directories. + +#### Scenario: Branch shows current branch name +- **WHEN** a repository is on a named branch +- **THEN** `git branch --show-current` outputs the branch name +- **THEN** the result is displayed in the standard per-repository output format + +#### Scenario: Branch on detached HEAD exits 0 +- **WHEN** a repository is in detached HEAD state +- **THEN** `git branch --show-current` exits 0 with empty stdout +- **THEN** this is treated as success, not an error + +#### Scenario: Branch runs concurrently across all repositories +- **WHEN** multiple repositories are discovered +- **THEN** `git branch --show-current` is dispatched to all repositories via the existing worker pool +- **THEN** results are collected and printed in discovery order + +### Requirement: git branch command form +The implementation SHALL execute `exec.Command("git", "branch", "--show-current")`. + +#### Scenario: Correct command form used +- **WHEN** the branch subcommand is invoked +- **THEN** the process spawned is `git branch --show-current` with no additional arguments + +### Requirement: Branch honours dry-run mode +When `--dry-run` is set, the branch subcommand SHALL not spawn any git processes, following the existing dry-run contract. + +#### Scenario: Dry-run with branch +- **WHEN** `gitctl branch --dry-run` is invoked +- **THEN** no `git branch` processes are spawned +- **THEN** a message is printed for each repository indicating what would have run diff --git a/openspec/specs/command-execution/spec.md b/openspec/specs/command-execution/spec.md index 198e082..f295143 100644 --- a/openspec/specs/command-execution/spec.md +++ b/openspec/specs/command-execution/spec.md @@ -2,16 +2,17 @@ ## Overview -After discovering repositories, `gitctl` runs a Git command against each one using a bounded worker pool. Currently supported commands are `status`, `pull`, `fetch`, and `stash`. +After discovering repositories, `gitctl` runs a Git command against each one using a bounded worker pool. Currently supported commands are `status`, `pull`, `fetch`, `stash`, and `branch`. ## Supported Commands -| Subcommand | Git command executed | -|------------|----------------------| -| `status` | `git status` | -| `pull` | `git pull` | -| `fetch` | `git fetch` | -| `stash` | `git stash` | +| Subcommand | Git command executed | +|------------|-------------------------------| +| `status` | `git status` | +| `pull` | `git pull` | +| `fetch` | `git fetch` | +| `stash` | `git stash` | +| `branch` | `git branch --show-current` | ## Execution Model From 3792f7115b78d2eec8a508aa3fcd8e4279ee3c05 Mon Sep 17 00:00:00 2001 From: Bjoern Kottner <12345+bjoern@users.noreply.github.com> Date: Fri, 29 May 2026 14:38:29 +0000 Subject: [PATCH 3/3] docs: openspec archive add-branch-command --- .../2026-05-29-add-branch-command}/.openspec.yaml | 0 .../2026-05-29-add-branch-command}/design.md | 0 .../2026-05-29-add-branch-command}/proposal.md | 0 .../2026-05-29-add-branch-command}/specs/branch-command/spec.md | 0 .../specs/command-execution/spec.md | 0 .../2026-05-29-add-branch-command}/tasks.md | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename openspec/changes/{add-branch-command => archive/2026-05-29-add-branch-command}/.openspec.yaml (100%) rename openspec/changes/{add-branch-command => archive/2026-05-29-add-branch-command}/design.md (100%) rename openspec/changes/{add-branch-command => archive/2026-05-29-add-branch-command}/proposal.md (100%) rename openspec/changes/{add-branch-command => archive/2026-05-29-add-branch-command}/specs/branch-command/spec.md (100%) rename openspec/changes/{add-branch-command => archive/2026-05-29-add-branch-command}/specs/command-execution/spec.md (100%) rename openspec/changes/{add-branch-command => archive/2026-05-29-add-branch-command}/tasks.md (100%) diff --git a/openspec/changes/add-branch-command/.openspec.yaml b/openspec/changes/archive/2026-05-29-add-branch-command/.openspec.yaml similarity index 100% rename from openspec/changes/add-branch-command/.openspec.yaml rename to openspec/changes/archive/2026-05-29-add-branch-command/.openspec.yaml diff --git a/openspec/changes/add-branch-command/design.md b/openspec/changes/archive/2026-05-29-add-branch-command/design.md similarity index 100% rename from openspec/changes/add-branch-command/design.md rename to openspec/changes/archive/2026-05-29-add-branch-command/design.md diff --git a/openspec/changes/add-branch-command/proposal.md b/openspec/changes/archive/2026-05-29-add-branch-command/proposal.md similarity index 100% rename from openspec/changes/add-branch-command/proposal.md rename to openspec/changes/archive/2026-05-29-add-branch-command/proposal.md diff --git a/openspec/changes/add-branch-command/specs/branch-command/spec.md b/openspec/changes/archive/2026-05-29-add-branch-command/specs/branch-command/spec.md similarity index 100% rename from openspec/changes/add-branch-command/specs/branch-command/spec.md rename to openspec/changes/archive/2026-05-29-add-branch-command/specs/branch-command/spec.md diff --git a/openspec/changes/add-branch-command/specs/command-execution/spec.md b/openspec/changes/archive/2026-05-29-add-branch-command/specs/command-execution/spec.md similarity index 100% rename from openspec/changes/add-branch-command/specs/command-execution/spec.md rename to openspec/changes/archive/2026-05-29-add-branch-command/specs/command-execution/spec.md diff --git a/openspec/changes/add-branch-command/tasks.md b/openspec/changes/archive/2026-05-29-add-branch-command/tasks.md similarity index 100% rename from openspec/changes/add-branch-command/tasks.md rename to openspec/changes/archive/2026-05-29-add-branch-command/tasks.md