gitstow 0.4.0__tar.gz → 0.5.0__tar.gz

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.
Files changed (121) hide show
  1. {gitstow-0.4.0 → gitstow-0.5.0}/AGENTS.md +48 -43
  2. {gitstow-0.4.0 → gitstow-0.5.0}/CHANGELOG.md +7 -0
  3. {gitstow-0.4.0 → gitstow-0.5.0}/CLAUDE.md +4 -0
  4. {gitstow-0.4.0 → gitstow-0.5.0}/PKG-INFO +1 -1
  5. gitstow-0.5.0/docs/building/plans/2026-07-16-move-repo-between-workspaces.md +95 -0
  6. {gitstow-0.4.0 → gitstow-0.5.0}/pyproject.toml +1 -1
  7. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/__init__.py +1 -1
  8. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/manage.py +31 -0
  9. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/git.py +11 -0
  10. gitstow-0.5.0/src/gitstow/core/operations.py +349 -0
  11. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/routes/pages.py +9 -0
  12. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/routes/repos.py +22 -0
  13. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/_repo_drawer.html +22 -0
  14. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_cli.py +53 -2
  15. gitstow-0.5.0/tests/test_move.py +600 -0
  16. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_serve.py +61 -0
  17. gitstow-0.4.0/src/gitstow/core/operations.py +0 -98
  18. {gitstow-0.4.0 → gitstow-0.5.0}/.claude/settings.json +0 -0
  19. {gitstow-0.4.0 → gitstow-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  20. {gitstow-0.4.0 → gitstow-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  21. {gitstow-0.4.0 → gitstow-0.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  22. {gitstow-0.4.0 → gitstow-0.5.0}/.github/workflows/ci.yml +0 -0
  23. {gitstow-0.4.0 → gitstow-0.5.0}/.github/workflows/publish.yml +0 -0
  24. {gitstow-0.4.0 → gitstow-0.5.0}/.gitignore +0 -0
  25. {gitstow-0.4.0 → gitstow-0.5.0}/BACKLOG.md +0 -0
  26. {gitstow-0.4.0 → gitstow-0.5.0}/CODE_OF_CONDUCT.md +0 -0
  27. {gitstow-0.4.0 → gitstow-0.5.0}/CONTRIBUTING.md +0 -0
  28. {gitstow-0.4.0 → gitstow-0.5.0}/LICENSE +0 -0
  29. {gitstow-0.4.0 → gitstow-0.5.0}/README.md +0 -0
  30. {gitstow-0.4.0 → gitstow-0.5.0}/SECURITY.md +0 -0
  31. {gitstow-0.4.0 → gitstow-0.5.0}/demo.gif +0 -0
  32. {gitstow-0.4.0 → gitstow-0.5.0}/demo.tape +0 -0
  33. {gitstow-0.4.0 → gitstow-0.5.0}/docs/building/audit-2026-07-06.md +0 -0
  34. {gitstow-0.4.0 → gitstow-0.5.0}/docs/building/implementation-plan.md +0 -0
  35. {gitstow-0.4.0 → gitstow-0.5.0}/docs/building/plans/2026-07-06-wave-1-correctness-safety.md +0 -0
  36. {gitstow-0.4.0 → gitstow-0.5.0}/docs/building/plans/2026-07-06-wave-2-status-model.md +0 -0
  37. {gitstow-0.4.0 → gitstow-0.5.0}/docs/building/plans/2026-07-06-wave-3-structure-polish.md +0 -0
  38. {gitstow-0.4.0 → gitstow-0.5.0}/docs/building/plans/2026-07-12-ui-wave-a-make-it-real.md +0 -0
  39. {gitstow-0.4.0 → gitstow-0.5.0}/docs/building/plans/2026-07-12-ui-wave-b-polish.md +0 -0
  40. {gitstow-0.4.0 → gitstow-0.5.0}/docs/building/ui-audit-2026-07-12.md +0 -0
  41. {gitstow-0.4.0 → gitstow-0.5.0}/docs/user/commands.md +0 -0
  42. {gitstow-0.4.0 → gitstow-0.5.0}/docs/user/concepts.md +0 -0
  43. {gitstow-0.4.0 → gitstow-0.5.0}/docs/user/configuration.md +0 -0
  44. {gitstow-0.4.0 → gitstow-0.5.0}/docs/user/getting-started.md +0 -0
  45. {gitstow-0.4.0 → gitstow-0.5.0}/scripts/release.sh +0 -0
  46. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/__main__.py +0 -0
  47. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/__init__.py +0 -0
  48. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/add.py +0 -0
  49. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/config_cmd.py +0 -0
  50. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/doctor.py +0 -0
  51. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/exec_cmd.py +0 -0
  52. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/export_cmd.py +0 -0
  53. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/fetch.py +0 -0
  54. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/helpers.py +0 -0
  55. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/list_cmd.py +0 -0
  56. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/main.py +0 -0
  57. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/migrate.py +0 -0
  58. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/onboard.py +0 -0
  59. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/open_cmd.py +0 -0
  60. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/pull.py +0 -0
  61. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/remove.py +0 -0
  62. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/search.py +0 -0
  63. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/serve.py +0 -0
  64. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/setup_ai.py +0 -0
  65. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/shell.py +0 -0
  66. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/skill_cmd.py +0 -0
  67. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/stats.py +0 -0
  68. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/status.py +0 -0
  69. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/update.py +0 -0
  70. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/cli/workspace_cmd.py +0 -0
  71. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/__init__.py +0 -0
  72. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/collection_io.py +0 -0
  73. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/config.py +0 -0
  74. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/discovery.py +0 -0
  75. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/locking.py +0 -0
  76. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/parallel.py +0 -0
  77. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/paths.py +0 -0
  78. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/repo.py +0 -0
  79. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/status_model.py +0 -0
  80. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/core/url_parser.py +0 -0
  81. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/mcp/__init__.py +0 -0
  82. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/mcp/server.py +0 -0
  83. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/skill/SKILL.md +0 -0
  84. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/__init__.py +0 -0
  85. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/routes/__init__.py +0 -0
  86. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/routes/collection.py +0 -0
  87. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/routes/dashboard.py +0 -0
  88. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/routes/system.py +0 -0
  89. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/routes/workspaces.py +0 -0
  90. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/server.py +0 -0
  91. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/static/app.css +0 -0
  92. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/static/dashboard.js +0 -0
  93. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/static/fonts/bricolage-grotesque-latin-ext-var.woff2 +0 -0
  94. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/static/fonts/bricolage-grotesque-latin-var.woff2 +0 -0
  95. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/static/fonts/fonts.css +0 -0
  96. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/static/fonts/jetbrains-mono-latin-ext-var.woff2 +0 -0
  97. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/static/fonts/jetbrains-mono-latin-var.woff2 +0 -0
  98. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/static/vendor/VENDORED.md +0 -0
  99. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/static/vendor/htmx.min.js +0 -0
  100. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/add_repo.html +0 -0
  101. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/base.html +0 -0
  102. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/dashboard.html +0 -0
  103. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/partials/dashboard_rows.html +0 -0
  104. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/partials/fetch_summary.html +0 -0
  105. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/partials/pull_summary.html +0 -0
  106. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/partials/repo_row.html +0 -0
  107. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/settings.html +0 -0
  108. {gitstow-0.4.0 → gitstow-0.5.0}/src/gitstow/web/templates/workspaces.html +0 -0
  109. {gitstow-0.4.0 → gitstow-0.5.0}/tests/__init__.py +0 -0
  110. {gitstow-0.4.0 → gitstow-0.5.0}/tests/conftest.py +0 -0
  111. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_collection_io.py +0 -0
  112. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_config.py +0 -0
  113. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_git.py +0 -0
  114. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_locking.py +0 -0
  115. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_mcp.py +0 -0
  116. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_onboard.py +0 -0
  117. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_operations.py +0 -0
  118. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_repo.py +0 -0
  119. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_setup_ai.py +0 -0
  120. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_status_model.py +0 -0
  121. {gitstow-0.4.0 → gitstow-0.5.0}/tests/test_url_parser.py +0 -0
@@ -1,8 +1,8 @@
1
- # gitstow - AI Developer Guide
1
+ # gitstow — AI Developer Guide
2
2
 
3
3
  ## What is this?
4
4
 
5
- A CLI tool that manages collections of git repositories across multiple workspaces. Think "package manager for repos" - whether they're open-source projects you learn from or active projects you work on.
5
+ A CLI tool that manages collections of git repositories across multiple workspaces. Think "package manager for repos" — whether they're open-source projects you learn from or active projects you work on.
6
6
 
7
7
  ## Operating Contract
8
8
 
@@ -14,14 +14,15 @@ A CLI tool that manages collections of git repositories across multiple workspac
14
14
 
15
15
  ## Product Standards
16
16
 
17
- - Keep CLI, TUI, web dashboard, JSON output, docs, and tests semantically aligned when changing user-facing status behavior.
17
+ - Keep CLI, web dashboard, JSON output, docs, and tests semantically aligned when changing user-facing status behavior.
18
18
  - For repo state presentation, avoid using "dirty" as a broad user-facing bucket for every local change. Present it as local/uncommitted changes with the composition visible: modified, staged, and untracked counts.
19
19
  - Keep local working-tree state separate from remote relationship state. For example: local changes, clean, ahead, behind, diverged, frozen, missing.
20
20
  - When improving the web dashboard, implement the actual missing dashboard feature and shared classification/model behavior instead of copying a CLI-only assumption into the template.
21
+ - For `web/` changes, HTTP-level tests (TestClient, curl) are necessary but not sufficient — they pass on bugs below the HTTP layer. Before calling a `web/` change done, verify form structure against the parsed DOM (browsers silently drop nested `<form>` tags) and verify layout/geometry in a real browser (`overflow-x: auto` clips absolutely-positioned children even without horizontal overflow).
21
22
 
22
23
  ## Key Concept: Workspaces
23
24
 
24
- Repos are organized into **workspaces** - directories with a label and layout mode:
25
+ Repos are organized into **workspaces** — directories with a label and layout mode:
25
26
  - **structured** layout: `workspace/owner/repo/` (open-source collections)
26
27
  - **flat** layout: `workspace/repo/` (active projects, no owner subdirectory)
27
28
 
@@ -40,60 +41,62 @@ src/gitstow/
40
41
  │ ├── exec_cmd.py, search.py, open_cmd.py, stats.py # Power commands
41
42
  │ ├── export_cmd.py # export/import collection
42
43
  │ ├── shell.py # Shell integration (fzf, aliases)
43
- │ ├── tui.py # TUI launcher
44
44
  │ ├── serve.py # Web dashboard launcher
45
45
  │ ├── migrate.py, config_cmd.py, onboard.py, doctor.py, skill_cmd.py
46
46
  │ └── __init__.py
47
47
  ├── core/ # Business logic (git ops, URL parsing, state)
48
- │ ├── paths.py # Path constants, central repos.yaml resolution
49
- │ ├── config.py # Workspace + Settings dataclasses, load/save
50
- │ ├── repo.py # Repo dataclass + RepoStore (nested YAML CRUD)
51
- │ ├── url_parser.py # URL → (host, owner, repo) extraction
52
- │ ├── git.py # All git subprocess calls
53
- │ ├── discovery.py # Walk directory tree (structured + flat), reconcile
54
- │ ├── parallel.py # Async execution with semaphore
55
- │ └── __init__.py
56
- ├── tui/ # Textual interactive dashboard
57
- │ ├── app.py # Main TUI application
48
+ │ ├── paths.py # Path constants, central repos.yaml resolution
49
+ │ ├── config.py # Workspace + Settings dataclasses, load/save
50
+ │ ├── repo.py # Repo dataclass + RepoStore (nested YAML CRUD)
51
+ │ ├── url_parser.py # URL → (host, owner, repo) extraction
52
+ │ ├── git.py # All git subprocess calls
53
+ │ ├── discovery.py # Walk directory tree (structured + flat), reconcile
54
+ │ ├── parallel.py # Async execution with semaphore
55
+ │ ├── status_model.py # Shared repo-state classifier — local composition vs remote relationship
56
+ │ ├── operations.py # Shared filter + bulk-runner layer (pull/fetch/MCP)
57
+ │ ├── locking.py # Cross-process file lock guarding repos.yaml writes
58
58
  │ └── __init__.py
59
59
  ├── web/ # FastAPI browser dashboard (gitstow ui)
60
60
  │ ├── server.py # FastAPI app, uvicorn runner, app.state.server stash
61
61
  │ ├── static/app.css # Dark theme, Bricolage Grotesque + JetBrains Mono
62
62
  │ ├── templates/ # Jinja2 — base.html + page templates + partials/
63
63
  │ └── routes/ # dashboard.py, repos.py, workspaces.py, collection.py, pages.py, system.py
64
- └── skill/ # Codex skill (SKILL.md)
64
+ └── skill/ # Claude Code skill (SKILL.md)
65
65
  └── SKILL.md
66
66
  ```
67
67
 
68
68
  **Key rules:**
69
- 1. CLI never touches git directly - it calls `core/git.py`
69
+ 1. CLI never touches git directly — it calls `core/git.py`
70
70
  2. `core/repo.py` (RepoStore) is the only module that reads/writes `repos.yaml`
71
- 3. `core/config.py` defines Workspace dataclass - all workspace logic flows from here
71
+ 3. `core/config.py` defines Workspace dataclass — all workspace logic flows from here
72
72
  4. `cli/helpers.py` provides `resolve_workspaces()`, `resolve_repo()`, `iter_repos_with_workspace()`
73
73
 
74
74
  ## Key Files
75
75
 
76
- - `core/config.py` - Workspace + Settings dataclasses. `get_workspaces()` with legacy migration shim.
77
- - `core/repo.py` - Repo with workspace field, RepoStore with nested YAML format, legacy auto-migration.
78
- - `core/discovery.py` - `discover_repos(root, layout)` supports structured and flat layouts.
79
- - `core/url_parser.py` - URL parsing (the hardest part). Test changes here thoroughly.
80
- - `core/git.py` - All git subprocess calls. Uses `git status --porcelain=v2 --branch` for single-call efficiency.
81
- - `core/parallel.py` - Async execution with semaphore (max 6 concurrent).
82
- - `cli/helpers.py` - Shared workspace resolution used by all CLI commands.
83
- - `cli/workspace_cmd.py` - workspace list/add/remove/scan subcommands.
84
- - `cli/main.py` - Typer app, global `-w/--workspace` option, command registration.
85
- - `tui/app.py` - Textual dashboard with DataTable, filter, pull, freeze toggle.
76
+ - `core/config.py` — Workspace + Settings dataclasses. `get_workspaces()` with legacy migration shim.
77
+ - `core/repo.py` — Repo with workspace field, RepoStore with nested YAML format, legacy auto-migration.
78
+ - `core/discovery.py` — `discover_repos(root, layout)` supports structured and flat layouts.
79
+ - `core/url_parser.py` — URL parsing (the hardest part). Test changes here thoroughly.
80
+ - `core/git.py` — All git subprocess calls. Uses `git status --porcelain=v2 --branch` for single-call efficiency.
81
+ - `core/parallel.py` — Async execution with semaphore (max 6 concurrent).
82
+ - `core/status_model.py` — `RepoState` — the single source of truth for local (modified/staged/untracked) vs remote (in-sync/ahead/behind/diverged) classification, consumed by CLI, web, and JSON.
83
+ - `core/operations.py` — Shared `filter_repo_pairs()` + bulk-runner used by `pull`, `fetch`, and the MCP server so surfaces can't drift.
84
+ - `core/locking.py` — `file_lock()` cross-process advisory lock guarding `repos.yaml` against concurrent CLI/web writes.
85
+ - `cli/helpers.py` — Shared workspace resolution used by all CLI commands.
86
+ - `cli/workspace_cmd.py` — workspace list/add/remove/scan subcommands.
87
+ - `cli/main.py` — Typer app, global `-w/--workspace` option, command registration.
86
88
 
87
89
  ## Data Files
88
90
 
89
- - `~/.gitstow/config.yaml` - Settings (workspaces list, default host, SSH pref).
90
- - `~/.gitstow/repos.yaml` - Repo metadata nested by workspace label. Central location.
91
+ - `~/.gitstow/config.yaml` — Settings (workspaces list, default host, SSH pref).
92
+ - `~/.gitstow/repos.yaml` — Repo metadata nested by workspace label. Central location.
93
+ - `~/.gitstow/repos.lock` — Cross-process advisory lock file (`core/locking.py`) held during `repos.yaml` writes. Not user-facing data; safe to ignore/delete if orphaned.
91
94
 
92
95
  ### repos.yaml format
93
96
  ```yaml
94
97
  oss:
95
- anthropic/Codex:
96
- remote_url: https://github.com/anthropic/Codex.git
98
+ anthropic/claude-code:
99
+ remote_url: https://github.com/anthropic/claude-code.git
97
100
  tags: [ai]
98
101
  active:
99
102
  gitstow:
@@ -101,14 +104,14 @@ active:
101
104
  tags: [active]
102
105
  ```
103
106
 
104
- ## All Commands (32)
107
+ ## All Commands (37)
105
108
 
106
109
  **Core:** `add`, `pull`, `fetch`, `list`, `status`, `remove`, `migrate`
107
110
  **Workspace:** `workspace list`, `workspace add`, `workspace remove`, `workspace scan`
108
111
  **Repo management:** `repo freeze`, `repo unfreeze`, `repo tag`, `repo untag`, `repo tags`, `repo info`
109
112
  **Power:** `exec`, `search`, `open`, `stats`
110
113
  **Sharing:** `collection export`, `collection import`
111
- **Shell:** `shell pick`, `shell init`, `shell completions`, `shell setup`, `tui`, `ui`
114
+ **Shell:** `shell pick`, `shell init`, `shell completions`, `shell setup`, `ui`
112
115
  **Setup:** `onboard`, `config show/set/path/migrate-root`, `doctor`, `install-skill`, `setup-ai`, `update`
113
116
 
114
117
  ## Development
@@ -116,11 +119,13 @@ active:
116
119
  ```bash
117
120
  cd ~/labs/projects/gitstow
118
121
  pip install -e ".[dev]"
119
- pytest # 44 tests
122
+ pytest # full suite — keep green
120
123
  ruff check src/
121
- pip install -e ".[tui]" # For TUI development
122
124
  ```
123
125
 
126
+ - **Worktree gotcha:** tests run from a git worktree silently exercise the main checkout (the editable install points at the primary clone). From a worktree, run `PYTHONPATH=<worktree>/src .venv/bin/python -m pytest -q`.
127
+ - **Releasing publishes to PyPI.** `scripts/release.sh X.Y.Z` tags and pushes; the tag triggers a public PyPI publish. Require the user's explicit in-session instruction to release — plan approval or a "let's go" on implementation does not cover it.
128
+
124
129
  ## Patterns
125
130
 
126
131
  - `--json -j` and `--quiet -q` on all main commands
@@ -132,19 +137,19 @@ pip install -e ".[tui]" # For TUI development
132
137
  - asyncio with semaphore for parallel git ops
133
138
  - `git status --porcelain=v2 --branch` for single-call status (vs gita's 4-5 calls)
134
139
  - Repo.global_key (`workspace:key`) for unique identification across workspaces
135
- - Legacy format auto-migration (flat repos.yaml -> nested, root_path -> workspaces)
140
+ - Legacy format auto-migration (flat repos.yaml → nested, root_path → workspaces)
136
141
 
137
142
  ## AI Integration
138
143
 
139
- **Primary: Codex skill** (`src/gitstow/skill/SKILL.md`)
140
- - Installed to `~/.Codex/skills/gitstow/` via `gitstow install-skill` or `gitstow onboard`
144
+ **Primary: Claude Code skill** (`src/gitstow/skill/SKILL.md`)
145
+ - Installed to `~/.claude/skills/gitstow/` via `gitstow install-skill` or `gitstow onboard`
141
146
  - Auto-updates on version bumps (checks `.version` marker on every CLI invocation)
142
- - Zero context cost when inactive - only loaded when task matches the skill description
143
- - Codex runs gitstow CLI commands via Bash - full access to all commands
147
+ - Zero context cost when inactive — only loaded when task matches the skill description
148
+ - Claude runs gitstow CLI commands via Bash — full access to all commands
144
149
 
145
150
  **Optional: MCP server** (`src/gitstow/mcp/server.py`)
146
- - For non-Codex AI tools (Codex Desktop, Cursor, Windsurf)
151
+ - For non-Claude-Code AI tools (Claude Desktop, Cursor, Windsurf)
147
152
  - Install: `pip install gitstow[mcp]`, entry point: `gitstow-mcp`
148
- - 13 tools + 3 resources, wraps same `core/` modules as CLI
153
+ - 14 tools + 3 resources, wraps same `core/` modules as CLI
149
154
  - **Tradeoff:** MCP tools are always loaded into context (costs tokens even when idle).
150
155
  The skill has no such cost. Only use MCP for dedicated repo-management setups.
@@ -4,6 +4,13 @@ All notable changes to gitstow will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.5.0] - 2026-07-16
8
+
9
+ ### Added
10
+
11
+ - **Move repos between workspaces.** From the dashboard's repo detail page (new Workspace section: pick a target, confirm, done) or the new `gitstow repo move <repo> <workspace>` command. The folder physically relocates on disk following the target workspace's layout (structured `owner/repo` vs flat), the catalog updates in the same locked operation, existing tags are kept and the target's auto-tags merge in, and repos that own linked git worktrees get `git worktree repair` run automatically after the move.
12
+ - **Move safety net.** Collisions are refused before anything is touched (catalog, disk, even dangling symlinks); sources that can't move safely are refused with actionable errors (symlinked repos, linked-worktree gitfiles, relative `.git` symlinks, non-repo directories squatting on a tracked path); catalog keys can never escape their workspace on disk; and if anything fails mid-move, the rollback restores the folder without ever deleting data it can't prove is its own.
13
+
7
14
  ## [0.4.0] - 2026-07-12
8
15
 
9
16
  ### Added
@@ -107,6 +107,9 @@ pytest # full suite — keep green
107
107
  ruff check src/
108
108
  ```
109
109
 
110
+ - **Worktree gotcha:** tests run from a git worktree silently exercise the main checkout (the editable install points at the primary clone). From a worktree, run `PYTHONPATH=<worktree>/src .venv/bin/python -m pytest -q`.
111
+ - **Releasing publishes to PyPI.** `scripts/release.sh X.Y.Z` tags and pushes; the tag triggers a public PyPI publish. Require the user's explicit in-session instruction to release — plan approval or a "let's go" on implementation does not cover it.
112
+
110
113
  ## Patterns
111
114
 
112
115
  - `--json -j` and `--quiet -q` on all main commands
@@ -128,6 +131,7 @@ ruff check src/
128
131
  - For repo state presentation, avoid using "dirty" as a broad user-facing bucket for every local change. Present it as local/uncommitted changes with the composition visible: modified, staged, and untracked counts.
129
132
  - Keep local working-tree state separate from remote relationship state. For example: local changes, clean, ahead, behind, diverged, frozen, missing.
130
133
  - When improving the web dashboard, implement the actual missing dashboard feature and shared classification/model behavior instead of copying a CLI-only assumption into the template.
134
+ - For `web/` changes, HTTP-level tests (pytest TestClient, curl) are necessary but not sufficient — they pass on bugs that live below the HTTP layer. Before calling a UI change done, verify form structure against the parsed DOM (browsers silently drop nested `<form>` tags) and verify layout/geometry in a real browser (e.g. `overflow-x: auto` clips absolutely-positioned children even without horizontal overflow).
131
135
 
132
136
  ## AI Integration
133
137
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitstow
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: A git repository library manager — clone, organize, and maintain collections of repos you learn from
5
5
  Project-URL: Homepage, https://github.com/rishmadaan/gitstow
6
6
  Project-URL: Repository, https://github.com/rishmadaan/gitstow
@@ -0,0 +1,95 @@
1
+ # Move repos between workspaces — spec
2
+
3
+ **Date:** 2026-07-16
4
+ **Status:** approved, pending implementation (Codex, new branch)
5
+
6
+ ## What
7
+
8
+ A repo can be reassigned from one workspace to another. The folder physically
9
+ moves on disk to the target workspace's directory, and the catalog
10
+ (`repos.yaml`) updates to match. Surfaced in the web dashboard's repo detail
11
+ page and as a thin CLI command. No such capability exists today in any surface.
12
+
13
+ ## Core logic — `move_repo()` in `src/gitstow/core/operations.py`
14
+
15
+ ```python
16
+ def move_repo(store: RepoStore, settings: Settings, key: str, from_ws: str, to_ws: str) -> Repo
17
+ ```
18
+
19
+ Raises `ValueError` with a human-readable message on any rule violation; on
20
+ success returns the new `Repo` and has updated disk + catalog.
21
+
22
+ Rules, in order:
23
+
24
+ 1. **Resolve:** repo must exist in catalog at `from_ws:key`; target workspace
25
+ label must exist in settings and differ from `from_ws`. Else error.
26
+ 2. **Re-key by target layout:**
27
+ - target `flat` → new `owner = ""`, new key = `name`.
28
+ - target `structured` → keep current owner; if owner is empty, parse it
29
+ from `remote_url` via `core/url_parser.py`; if still no owner, refuse:
30
+ a structured workspace files repos under `owner/repo` and discovery
31
+ would never find a bare folder.
32
+ 3. **Collision checks (before touching anything):**
33
+ - catalog: no existing repo at `to_ws:new_key`.
34
+ - disk: destination path (per target layout) must not exist.
35
+ 4. **Disk move:** if the source folder exists, `shutil.move` it to the
36
+ destination (create the owner parent dir first when structured). If the
37
+ source is missing on disk, skip the move — catalog-only reassignment of
38
+ an already-missing repo is allowed. After a structured-source move, remove
39
+ the now-empty owner directory if empty (ignore errors).
40
+ 5. **Catalog update (single locked mutation):** remove old entry, add new
41
+ `Repo` with updated `workspace`/`owner`; preserve `remote_url`, `frozen`,
42
+ `added`, `last_pulled`, `last_fetched`; tags = existing tags plus the
43
+ target workspace's `auto_tags` (deduped, order preserved).
44
+
45
+ Ordering note: validate everything first, then disk move, then catalog write.
46
+
47
+ ## Web UI — repo detail page
48
+
49
+ - New section "Workspace" on `_repo_drawer.html` (between Tags and Freeze):
50
+ a `<select name="target">` listing all *other* workspace labels, plus a
51
+ Move button. `data-confirm` message states the folder will move on disk.
52
+ If there is only one workspace, render the section disabled with a hint
53
+ instead of an empty select.
54
+ - New route `POST /repos/{workspace}/{key:path}/move` in
55
+ `src/gitstow/web/routes/repos.py`, matching the existing route patterns
56
+ there (path param style, error handling, redirect style). On success,
57
+ 303-redirect to the repo's new detail URL (`/repos/{to_ws}/{new_key}`).
58
+ On `ValueError`, re-render the drawer with the error shown the same way
59
+ other drawer errors are shown.
60
+ - No nested `<form>` tags (browsers silently drop them — verify against
61
+ parsed DOM, per CLAUDE.md).
62
+
63
+ ## CLI — `gitstow repo move`
64
+
65
+ - `gitstow repo move <key> <target-workspace>` in `src/gitstow/cli/manage.py`,
66
+ mirroring the `repo tag`/`repo freeze` conventions (helpers for repo
67
+ resolution, Rich output, same error style). Wraps the same `move_repo()`.
68
+
69
+ ## Tests
70
+
71
+ - `tests/` — core: structured→flat, flat→structured (owner parsed from URL),
72
+ flat→structured with no derivable owner (refused), disk+catalog collision
73
+ refusals, missing-on-disk catalog-only move, tag merge with auto_tags,
74
+ metadata preserved. Use tmp_path fixtures; no network.
75
+ - Web: TestClient test for the move route (success redirect + error render).
76
+ - CLI: one exercise of `repo move` via the existing CLI test harness pattern.
77
+ - Full suite stays green (`pytest` from the worktree with
78
+ `PYTHONPATH=<worktree>/src`).
79
+
80
+ ## Out of scope
81
+
82
+ Bulk/multi-select move, drag-and-drop, moving across hosts, MCP tool parity
83
+ (can wrap `move_repo()` later).
84
+
85
+ Nested-group owners (`group/subgroup/repo`) in structured workspaces land at
86
+ `root/group/subgroup/repo` — the same place `gitstow add` clones them today —
87
+ which two-level structured discovery cannot see (pre-existing limitation
88
+ across add/scan/doctor, flagged in Codex review pass 6; fix is a separate
89
+ discovery feature).
90
+
91
+ ## Verification (post-implementation, by the session lead — not Codex)
92
+
93
+ Review the full diff; run the suite; then verify in a real browser per
94
+ CLAUDE.md web standards (form structure in parsed DOM, actual move of a
95
+ scratch repo in `/private/tmp/gitstow-demo`).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gitstow"
7
- version = "0.4.0"
7
+ version = "0.5.0"
8
8
  description = "A git repository library manager — clone, organize, and maintain collections of repos you learn from"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """gitstow — a git repository library manager."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.5.0"
@@ -17,6 +17,7 @@ from gitstow.core.git import (
17
17
  format_size,
18
18
  is_git_repo,
19
19
  )
20
+ from gitstow.core.operations import move_repo
20
21
  from gitstow.core.repo import RepoStore
21
22
  from gitstow.core.status_model import classify
22
23
  from gitstow.cli.helpers import resolve_repo
@@ -176,6 +177,36 @@ def remove_tags(
176
177
  console.print(f" [dim]None of those tags were on {repo.key}.[/dim]")
177
178
 
178
179
 
180
+ @manage_app.command("move")
181
+ def move(
182
+ ctx: typer.Context,
183
+ repo_key: str = typer.Argument(help="Repo to move (owner/repo or name)."),
184
+ target: str = typer.Argument(help="Target workspace label."),
185
+ ) -> None:
186
+ """[bold]Move[/bold] a repo to another workspace — relocates its folder on disk.
187
+
188
+ \b
189
+ Examples:
190
+ gitstow repo move anthropic/claude-code active
191
+ gitstow -w oss repo move dupe active
192
+ """
193
+ settings = load_config()
194
+ store = RepoStore()
195
+ ws_label = (ctx.obj or {}).get("workspace")
196
+ repo, _ = resolve_repo(store, settings, repo_key, ws_label)
197
+
198
+ try:
199
+ moved = move_repo(store, settings, repo.key, repo.workspace, target)
200
+ except ValueError as exc:
201
+ err_console.print(f"[red]Error:[/red] {exc}")
202
+ raise typer.Exit(code=1)
203
+
204
+ console.print(
205
+ f" [green]✓[/green] Moved {repo.key} → workspace [bold]{target}[/bold] "
206
+ f"(now [cyan]{moved.key}[/cyan])."
207
+ )
208
+
209
+
179
210
  @manage_app.command("tags")
180
211
  def list_tags(
181
212
  quiet: bool = typer.Option(False, "--quiet", "-q", help="One tag per line."),
@@ -280,6 +280,17 @@ def get_last_commit(repo_path: Path) -> CommitInfo:
280
280
  )
281
281
 
282
282
 
283
+ def repair_worktrees(repo_path: Path) -> bool:
284
+ """Run `git worktree repair` from a repo that was just relocated.
285
+
286
+ Linked worktrees keep absolute back-pointers into the main repo's
287
+ .git/worktrees/; after the main repo moves, repair rewrites them.
288
+ Returns True on success.
289
+ """
290
+ result = _run_git(["worktree", "repair"], cwd=repo_path)
291
+ return result.returncode == 0
292
+
293
+
283
294
  def get_branch(repo_path: Path) -> str:
284
295
  """Get the current branch name."""
285
296
  result = _run_git(["branch", "--show-current"], cwd=repo_path)