dubstack 0.5.0 → 0.6.0

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.
package/README.md CHANGED
@@ -1,362 +1,513 @@
1
1
  # DubStack
2
2
 
3
- A local-first CLI for managing **stacked diffs** — chains of dependent git branches that build on each other. Stop juggling complex rebase chains by hand.
3
+ DubStack (`dub`) is a local-first CLI for stacked branch workflows.
4
4
 
5
- ## Why Stacked Diffs?
5
+ It is designed for the Graphite mental model: small, dependent PRs that are easy to review, update, and rebase.
6
6
 
7
- Stacked diffs let you break large features into small, reviewable PRs that depend on each other. Instead of one 2,000-line monster PR, you get a clean chain:
7
+ ## Why DubStack
8
8
 
9
- ```
9
+ Large PRs are hard to review and painful to keep up to date.
10
+
11
+ Stacked branches let you split work into focused layers:
12
+
13
+ ```text
10
14
  (main)
11
- └─ feat/api-models
12
- └─ feat/api-endpoint
13
- └─ feat/ui-component
15
+ └─ feat/auth-types
16
+ └─ feat/auth-login
17
+ └─ feat/auth-tests
14
18
  ```
15
19
 
16
- When `main` updates or you amend an earlier branch, `dub restack` cascades rebases through the entire chain for you.
20
+ When a lower branch changes, `dub restack` propagates it upstack.
17
21
 
18
22
  ## Install
19
23
 
20
- **Homebrew (Recommended)**
24
+ ### Homebrew (recommended)
21
25
 
22
26
  ```bash
23
27
  brew tap wiseiodev/dubstack
24
28
  brew install dubstack
25
29
  ```
26
30
 
27
- To update to the latest version:
31
+ Update:
28
32
 
29
33
  ```bash
30
34
  brew upgrade dubstack
31
35
  ```
32
36
 
33
- **npm**
37
+ ### npm
34
38
 
35
39
  ```bash
36
40
  npm install -g dubstack
37
41
  ```
38
42
 
39
- **From Source** (for contributors)
40
-
41
- > Requires **Node ≥ 22** and **pnpm**.
43
+ ### From source
42
44
 
43
45
  ```bash
44
- # Clone and install
45
- git clone https://github.com/wiseiodev/dubstack.git && cd dubstack
46
+ git clone https://github.com/wiseiodev/dubstack.git
47
+ cd dubstack
46
48
  pnpm install
47
-
48
- # Link globally so `dub` is available everywhere
49
49
  pnpm build
50
50
  pnpm link --global
51
51
  ```
52
52
 
53
+ ## Graphite Mental Model
54
+
55
+ If you have `gt` muscle memory, use this as a fast map:
56
+
57
+ | Graphite (`gt`) | DubStack (`dub`) |
58
+ |---|---|
59
+ | `gt create` | `dub create` |
60
+ | `gt modify` | `dub modify` or `dub m` |
61
+ | `gt submit` / `gt ss` | `dub submit` / `dub ss` |
62
+ | `gt sync` | `dub sync` |
63
+ | `gt checkout` / `gt co` | `dub checkout` / `dub co` |
64
+ | `gt log` / `gt ls` | `dub log` / `dub ls` |
65
+ | `gt up` / `gt down` | `dub up` / `dub down` |
66
+ | `gt top` / `gt bottom` | `dub top` / `dub bottom` |
67
+ | `gt info` | `dub info` |
68
+ | `gt pr` | `dub pr` |
69
+ | `gt restack` | `dub restack` |
70
+ | `gt continue` | `dub continue` |
71
+ | `gt abort` | `dub abort` |
72
+ | `gt track --parent` | `dub track --parent` |
73
+ | `gt untrack` | `dub untrack` |
74
+ | `gt delete` | `dub delete` |
75
+ | `gt parent` | `dub parent` |
76
+ | `gt children` | `dub children` |
77
+ | `gt trunk` | `dub trunk` |
78
+ | `gt undo` | `dub undo` |
79
+
53
80
  ## Quick Start
54
81
 
55
82
  ```bash
56
- # 1. Initialize in any git repo
57
- cd my-project
58
- dub init
59
-
60
- # 2. Start stacking branches
83
+ # 1) Start from trunk
61
84
  git checkout main
62
- dub create feat/api-models
63
- # hack hack hack, commit...
64
-
65
- dub create feat/api-endpoint
66
- # hack hack hack, commit...
85
+ git pull
67
86
 
68
- dub create feat/ui-component
69
- # hack hack hack, commit...
87
+ # 2) Create stacked branches
88
+ # Create + stage all + commit
89
+ dub create feat/auth-types -am "feat: add auth types"
90
+ dub create feat/auth-login -am "feat: add login flow"
91
+ dub create feat/auth-tests -am "test: add auth tests"
70
92
 
71
- # 3. See your stack
93
+ # 3) View stack
72
94
  dub log
73
95
 
74
- # 4. Rebase the whole chain after main updates
75
- git checkout main && git pull
76
- dub restack
77
-
78
- # 4b. Sync local stack state with remote branches
79
- dub sync
96
+ # 4) Submit stack PRs
97
+ dub ss
80
98
 
81
- # 5. Made a mistake? Undo it
82
- # 5. Made a mistake? Undo it
83
- dub undo
99
+ # 5) Open PR for current branch
100
+ dub pr
84
101
  ```
85
102
 
86
- ## Agent Skills
103
+ For a more detailed walkthrough, see [`QUICKSTART.md`](./QUICKSTART.md).
87
104
 
88
- DubStack provides skills for AI coding agents (like Cursor, Windsurf, etc.) to use the CLI effectively.
105
+ ## Command Reference
89
106
 
90
- You can install them easily using the `dub skills` command:
107
+ ### `dub init`
91
108
 
92
- ```bash
93
- # Install all skills
94
- dub skills add
109
+ Initialize DubStack state in the current git repository.
95
110
 
96
- # Install specific skill
97
- dub skills add dub-flow
111
+ ```bash
112
+ dub init
98
113
  ```
99
114
 
100
- Alternatively, you can install them manually via `npx`:
115
+ Notes:
116
+ - `dub create` auto-initializes state if needed.
117
+ - Running `dub init` manually is still useful for explicit setup.
118
+
119
+ ### `dub create <branch>`
120
+
121
+ Create a branch stacked on top of the current branch.
101
122
 
102
123
  ```bash
103
- # Core CLI reference & workflows
104
- npx skills add wiseiodev/dubstack/skills/dubstack
124
+ # branch only
125
+ dub create feat/my-change
105
126
 
106
- # Specialized PR creation workflow
107
- npx skills add wiseiodev/dubstack/skills/dub-flow
127
+ # create + commit staged changes
128
+ dub create feat/my-change -m "feat: ..."
129
+
130
+ # stage all + create + commit
131
+ dub create feat/my-change -am "feat: ..."
132
+
133
+ # stage tracked-file updates + create + commit
134
+ dub create feat/my-change -um "feat: ..."
135
+
136
+ # interactive hunk staging + create + commit
137
+ dub create feat/my-change -pm "feat: ..."
108
138
  ```
109
139
 
110
- ## Commands
140
+ Flags:
141
+ - `-m, --message <message>`: commit message
142
+ - `-a, --all`: stage all changes before commit (requires `-m`)
143
+ - `-u, --update`: stage tracked-file updates before commit (requires `-m`)
144
+ - `-p, --patch`: select hunks interactively before commit (requires `-m`)
111
145
 
112
- ### `dub init`
146
+ ### `dub modify` / `dub m`
113
147
 
114
- Initializes DubStack in the current git repository.
148
+ Amend or create commits on the current branch, then restack descendants.
115
149
 
116
150
  ```bash
117
- dub init
118
- ```
151
+ # amend current commit
152
+ dub modify
119
153
 
120
- - Creates `.git/dubstack/state.json` with an empty state
121
- - Adds `.git/dubstack` to `.gitignore`
122
- - **Idempotent** — safe to run multiple times
154
+ # create a new commit
155
+ dub modify -c -m "fix: ..."
123
156
 
124
- ```
125
- DubStack initialized # first run
126
- ⚠ DubStack already initialized # subsequent runs
157
+ # interactive staging
158
+ dub modify -p
159
+
160
+ # stage all tracked updates
161
+ dub modify -u
162
+
163
+ # show staged diff before modify
164
+ dub modify -v
165
+
166
+ # show staged + unstaged diff before modify
167
+ dub modify -vv
168
+
169
+ # interactive rebase of this branch's commits
170
+ dub modify --interactive-rebase
127
171
  ```
128
172
 
129
- ---
173
+ Flags:
174
+ - `-a, --all`
175
+ - `-u, --update`
176
+ - `-p, --patch`
177
+ - `-c, --commit`
178
+ - `-e, --edit`
179
+ - `-m, --message <message>` (repeatable)
180
+ - `-v, --verbose` (repeatable)
181
+ - `--interactive-rebase`
130
182
 
131
- ### `dub create <branch-name>`
183
+ ### `dub checkout` / `dub co`
132
184
 
133
- Creates a new branch stacked on top of the current branch.
185
+ Checkout a branch directly or use interactive search.
134
186
 
135
187
  ```bash
136
- # On main
137
- dub create feat/api-models
188
+ # checkout explicit branch
189
+ dub checkout feat/auth-login
138
190
 
139
- # On feat/api-models
140
- dub create feat/api-endpoint
141
- ```
191
+ # interactive picker
192
+ dub checkout
142
193
 
143
- - Checks out the new branch at the current HEAD
144
- - Records the parent → child relationship in state
145
- - Auto-creates a new stack if the parent isn't already tracked
146
- - Saves an undo snapshot before any mutation
194
+ # checkout trunk for current tracked stack
195
+ dub checkout --trunk
147
196
 
148
- **Errors:**
149
- | Condition | Message |
150
- |---|---|
151
- | Not initialized | `DubStack is not initialized. Run 'dub init' first.` |
152
- | Branch already exists | `Branch '<name>' already exists.` |
153
- | Detached HEAD | `HEAD is detached. Check out a branch first.` |
197
+ # interactive picker including non-tracked local branches
198
+ dub checkout --show-untracked
154
199
 
155
- ---
200
+ # interactive picker scoped to current stack
201
+ dub checkout --stack
202
+ ```
156
203
 
157
- ### `dub log`
204
+ ### `dub log` / `dub ls` / `dub l`
158
205
 
159
- Displays an ASCII tree of all tracked stacks.
206
+ Render tracked stacks as an ASCII tree.
160
207
 
161
208
  ```bash
162
209
  dub log
210
+ dub ls
211
+ dub l
212
+
213
+ # show only current stack
214
+ dub log --stack
215
+
216
+ # show all stacks explicitly
217
+ dub log --all
218
+
219
+ # reverse branch ordering for quick top-down scan
220
+ dub log --reverse
163
221
  ```
164
222
 
165
- Example output:
223
+ ### Navigation: `dub up`, `dub down`, `dub top`, `dub bottom`
224
+
225
+ ```bash
226
+ # move one branch upstack
227
+ dub up
228
+
229
+ # move multiple levels upstack
230
+ dub up 2
231
+ # or: dub up --steps 2
232
+
233
+ # move downstack
234
+ dub down
235
+ dub down 2
236
+
237
+ # jump to tip branch in current path
238
+ dub top
166
239
 
240
+ # jump to first branch above root
241
+ dub bottom
167
242
  ```
168
- (main)
169
- ├─ feat/api-models
170
- │ └─ feat/api-endpoint (Current)
171
- └─ feat/auth
172
- └─ feat/auth-ui ⚠ (missing)
243
+
244
+ ### `dub info` and `dub branch info`
245
+
246
+ Show tracked metadata for a branch.
247
+
248
+ ```bash
249
+ # current branch
250
+ dub info
251
+
252
+ # explicit branch
253
+ dub info feat/auth-login
254
+
255
+ # equivalent legacy style
256
+ dub branch info
173
257
  ```
174
258
 
175
- - **Current branch** is highlighted and marked `(Current)`
176
- - **Root branches** are shown in parentheses, e.g. `(main)`
177
- - **Deleted branches** still tracked in state show `⚠ (missing)`
178
- - Multiple stacks are separated by blank lines
259
+ ### Orientation: `dub parent`, `dub children`, `dub trunk`
179
260
 
180
- ---
261
+ Quickly inspect where the current branch sits in its tracked stack.
181
262
 
182
- ### `dub restack`
263
+ ```bash
264
+ dub parent # direct parent of current branch
265
+ dub children # direct children
266
+ dub trunk # stack root/trunk branch
267
+ ```
183
268
 
184
- Rebases all branches in the current stack onto their updated parents.
269
+ All three commands accept an optional branch argument:
185
270
 
186
271
  ```bash
187
- dub restack
272
+ dub parent feat/auth-login
273
+ dub children feat/auth-types
274
+ dub trunk feat/auth-tests
188
275
  ```
189
276
 
190
- **How it works:**
277
+ If branch metadata is missing, these commands print a remediation path using `dub track`.
191
278
 
192
- 1. Snapshots every branch tip _before_ starting
193
- 2. Walks the tree in topological order (parents first)
194
- 3. For each child branch, runs `git rebase --onto <parent_new_tip> <parent_old_tip> <child>`
195
- 4. Skips branches whose parent hasn't moved
196
- 5. Returns you to the branch you started on
279
+ ### `dub track [branch] [--parent <branch>]`
197
280
 
198
- **Conflict handling:**
281
+ Track an existing local branch or re-parent a tracked branch.
199
282
 
200
- If a rebase hits a conflict, DubStack pauses and tells you:
283
+ ```bash
284
+ # track current branch
285
+ dub track
201
286
 
287
+ # track explicit branch
288
+ dub track feat/auth-login --parent feat/auth-types
289
+
290
+ # repair parent metadata
291
+ dub track feat/auth-login --parent main
202
292
  ```
203
- ⚠ Conflict while restacking 'feat/api-endpoint'
204
- Resolve conflicts, stage changes, then run: dub restack --continue
205
- ```
206
293
 
207
- After resolving:
294
+ Notes:
295
+ - If `--parent` is omitted, DubStack tries to infer a safe default.
296
+ - In interactive shells, DubStack prompts when parent choice is ambiguous.
297
+ - Re-parenting can require follow-up rebasing via `dub restack`.
298
+
299
+ ### `dub untrack [branch] [--downstack]`
300
+
301
+ Remove branch metadata from DubStack without deleting local git branches.
208
302
 
209
303
  ```bash
210
- # Fix the conflicting files
211
- git add .
212
- dub restack --continue
304
+ # untrack current branch only
305
+ dub untrack
306
+
307
+ # untrack explicit branch and descendants
308
+ dub untrack feat/auth-login --downstack
213
309
  ```
214
310
 
215
- Progress is saved to `.git/dubstack/restack-progress.json`, so the resume picks up exactly where it left off.
311
+ Use this when branch exists locally but should no longer participate in stack operations.
312
+
313
+ ### `dub delete [branch] [--upstack|--downstack] [--force] [--quiet]`
314
+
315
+ Delete local branches with stack-aware expansion and metadata repair.
316
+
317
+ ```bash
318
+ # delete one branch (with confirmation)
319
+ dub delete feat/auth-login
320
+
321
+ # delete branch and descendants
322
+ dub delete feat/auth-login --upstack
216
323
 
217
- **Output examples:**
324
+ # delete branch and ancestors toward trunk
325
+ dub delete feat/auth-login --downstack
218
326
 
327
+ # fully non-interactive destructive delete
328
+ dub delete feat/auth-login --upstack --force --quiet
219
329
  ```
220
- ✔ Stack is already up to date
221
330
 
222
- ✔ Restacked 2 branch(es)
223
- feat/api-models
224
- feat/api-endpoint
331
+ Flags:
332
+ - `--upstack`: include descendants
333
+ - `--downstack`: include ancestors (excluding root)
334
+ - `-f, --force`: force delete unmerged branches
335
+ - `-q, --quiet`: skip confirmation prompt
336
+
337
+ ### `dub continue` / `dub abort`
338
+
339
+ Unified recovery pair for interrupted restacks and rebases.
340
+
341
+ ```bash
342
+ # continue active restack/rebase
343
+ dub continue
344
+
345
+ # abort active restack/rebase
346
+ dub abort
225
347
  ```
226
348
 
227
- **Errors:**
228
- | Condition | Message |
229
- |---|---|
230
- | Uncommitted changes | `Working tree has uncommitted changes. Commit or stash them before restacking.` |
231
- | Branch not in a stack | `Branch '<name>' is not part of any stack.` |
232
- | Tracked branch deleted | `Branch '<name>' is tracked in state but no longer exists in git.` |
349
+ Use these when the CLI reports conflicts or an in-progress operation.
233
350
 
234
- ---
351
+ ### `dub submit` / `dub ss`
235
352
 
236
- ### `dub sync`
353
+ Push stack branches and create or update PRs.
237
354
 
238
- Synchronizes tracked stack branches with remote refs and optionally restacks.
355
+ ```bash
356
+ dub submit
357
+ dub ss
358
+
359
+ # preview only
360
+ dub submit --dry-run
361
+ ```
362
+
363
+ ### `dub pr [branch-or-number]`
364
+
365
+ Open a PR in browser via `gh`.
239
366
 
240
367
  ```bash
241
- dub sync
368
+ # current branch PR
369
+ dub pr
370
+
371
+ # explicit branch / PR target
372
+ dub pr feat/auth-login
373
+ dub pr 123
242
374
  ```
243
375
 
244
- Useful flags:
376
+ ### `dub sync`
377
+
378
+ Synchronize tracked branches with remote refs, then optionally restack.
245
379
 
246
380
  ```bash
247
- # Sync all stacks across trunks
381
+ # sync current stack
382
+ dub sync
383
+
384
+ # sync all tracked stacks
248
385
  dub sync --all
249
386
 
250
- # Non-interactive safe mode (skip destructive operations)
387
+ # non-interactive mode
251
388
  dub sync --no-interactive
252
389
 
253
- # Force overwrite/delete decisions during sync
390
+ # force destructive sync decisions
254
391
  dub sync --force
255
392
 
256
- # Skip post-sync restack
393
+ # skip post-sync restack
257
394
  dub sync --no-restack
258
395
  ```
259
396
 
260
- Current behavior:
261
- - Fetches tracked roots and branches from `origin`
262
- - Attempts fast-forward trunk updates (or overwrite with `--force`)
263
- - Cleans local branches whose PRs are `MERGED`/`CLOSED` and whose commits are
264
- confirmed in trunk (interactive/force), while keeping non-cleanable stacks
265
- excluded from destructive sync actions
266
- - Reconciles each tracked branch:
267
- - restores missing local branches from remote
268
- - fast-forwards branches that are safely behind remote
269
- - keeps local-ahead branches
270
- - prompts/skips/forces on diverged branches
271
- - Runs post-sync restack by default
272
-
273
- ---
397
+ Current sync behavior includes:
398
+ - fetch tracked refs from `origin`
399
+ - attempt trunk fast-forward (or overwrite with `--force`)
400
+ - cleanup for merged/closed PR branches whose commits are confirmed in trunk
401
+ - reconcile local/remote divergence states per branch
402
+ - restack by default after sync
403
+
404
+ ### `dub restack`
405
+
406
+ Rebase stack branches onto updated parents.
407
+
408
+ ```bash
409
+ dub restack
410
+
411
+ # continue after resolving conflicts
412
+ dub restack --continue
413
+ ```
274
414
 
275
415
  ### `dub undo`
276
416
 
277
- Rolls back the last `dub create` or `dub restack` operation.
417
+ Undo last `dub create` or `dub restack` operation.
278
418
 
279
419
  ```bash
280
420
  dub undo
281
421
  ```
282
422
 
283
- **Undo strategies:**
423
+ ### `dub skills`
284
424
 
285
- - **After `create`:** Deletes the created branch, restores state, checks out the previous branch
286
- - **After `restack`:** Force-resets every rebased branch to its pre-rebase commit, restores state
425
+ Install or remove packaged agent skills.
287
426
 
288
- Only **one level** of undo is supported. After undo, the undo entry is cleared.
427
+ ```bash
428
+ # install all bundled skills
429
+ dub skills add
289
430
 
290
- ```
291
- Undid 'create': Deleted branch 'feat/api-endpoint'
292
- ✔ Undid 'restack': Reset 3 branches to pre-restack state
293
- ```
431
+ # install one skill
432
+ dub skills add dubstack
294
433
 
295
- **Errors:**
296
- | Condition | Message |
297
- |---|---|
298
- | Nothing to undo | `Nothing to undo.` |
299
- | Uncommitted changes | `Working tree has uncommitted changes. Commit or stash them before undoing.` |
434
+ # remove one skill
435
+ dub skills remove dub-flow
436
+
437
+ # preview without changing anything
438
+ dub skills add --dry-run
439
+ dub skills remove --dry-run
440
+ ```
300
441
 
301
- ---
442
+ ## Typical Workflows
302
443
 
303
- ## Typical Workflow
444
+ ### Add review feedback to a middle branch
304
445
 
305
446
  ```bash
306
- # Start a feature stack off main
307
- git checkout main
308
- dub create feat/data-layer
309
- # write code, commit
447
+ # jump to branch needing edits
448
+ dub co feat/auth-login
310
449
 
311
- dub create feat/api-routes
312
- # write code, commit
450
+ # edit + amend + restack descendants
451
+ dub m -a -m "fix: address feedback"
313
452
 
314
- dub create feat/frontend
315
- # write code, commit
453
+ # resubmit stack
454
+ dub ss
455
+ ```
316
456
 
317
- # View the stack
318
- dub log
319
- # (main)
320
- # └─ feat/data-layer
321
- # └─ feat/api-routes
322
- # └─ feat/frontend (Current)
323
-
324
- # Later: main gets updated, or you amend feat/data-layer
325
- git checkout feat/data-layer
326
- # amend your commits...
327
- dub restack
328
- # ✔ Restacked 2 branch(es)
329
- # ↳ feat/api-routes
330
- # ↳ feat/frontend
457
+ ### Sync after trunk moves
331
458
 
332
- # Oops, that restack went wrong
333
- dub undo
334
- # ✔ Undid 'restack': Reset 3 branches to pre-restack state
459
+ ```bash
460
+ git checkout main
461
+ git pull
462
+ dub sync
335
463
  ```
336
464
 
337
- ## How State Works
338
-
339
- DubStack stores all state locally inside your git repo:
465
+ ### Recover from restack conflict
340
466
 
467
+ ```bash
468
+ dub restack
469
+ # resolve conflicts
470
+ git add <resolved-files>
471
+ dub restack --continue
341
472
  ```
473
+
474
+ ## Troubleshooting
475
+
476
+ | Problem | What to do |
477
+ |---|---|
478
+ | `gh CLI not found` | Install GitHub CLI: https://cli.github.com |
479
+ | `Not authenticated with GitHub` | Run `gh auth login` |
480
+ | Branch not part of stack | Create via `dub create` or run from tracked branch |
481
+ | Restack conflict | Resolve files, `git add`, `dub restack --continue` |
482
+ | Rebase/restack interrupted | Use `dub continue` to resume, `dub abort` to cancel |
483
+ | Branch not tracked | Run `dub track <branch> --parent <parent>` |
484
+ | Need metadata-only removal | Use `dub untrack` (or `--downstack`) |
485
+ | Need stack-aware branch deletion | Use `dub delete` with `--upstack` / `--downstack` |
486
+ | Sync skipped branch | Re-run with `--interactive` or `--force` as appropriate |
487
+ | Wrong operation during create/restack | Use `dub undo` (single-level) |
488
+
489
+ ## State Files
490
+
491
+ DubStack stores local state in your repo:
492
+
493
+ ```text
342
494
  .git/dubstack/
343
- ├── state.json # branch relationships and stack metadata
344
- ├── undo.json # snapshot for single-level undo
345
- └── restack-progress.json # in-flight restack state (temporary)
495
+ ├── state.json
496
+ ├── undo.json
497
+ └── restack-progress.json
346
498
  ```
347
499
 
348
- Nothing is pushed to your remote. State is per-repo and git-ignored.
500
+ Nothing is pushed to your remote from these files.
349
501
 
350
502
  ## Development
351
503
 
352
504
  ```bash
353
- pnpm install # install deps
354
- pnpm dev # run via tsx (no build step)
355
- pnpm build # compile TypeScript to dist/
356
- pnpm test # run tests (vitest)
357
- pnpm typecheck # type-check without emitting
358
- pnpm checks # lint + format check (biome)
359
- pnpm checks:fix # auto-fix lint + format issues
505
+ pnpm install
506
+ pnpm test
507
+ pnpm typecheck
508
+ pnpm checks
509
+ pnpm checks:fix
510
+ pnpm build
360
511
  ```
361
512
 
362
513
  ## License