Integrated repository, worktree, git stacker, and project manager
0

Configure Feed

Select the types of activity you want to include in your feed.

rename pm project wt create/delete to add/remove

Clean rename, no back-compat aliases. Updates every integration:
zsh completion (regenerated _pm; also fixes the stale --repo
substitution that had lost _pm_repos completion), the pm-workflow
Claude Code skill (plugin bumped to 0.1.1), and the Emacs package
(pm-wt-add/pm-wt-remove function chain, prompts, and transient).

Jordan Isaacs (Jun 9, 2026, 9:55 PM UTC) ab8517d9 79476c7a

+211 -185
+2 -2
README.md
··· 41 41 # project — named working sets 42 42 pm project create <name> --repos r1,r2 # create a project, claim slots, link 43 43 pm project ls | status <name> 44 - pm project wt create <name> --repos … # add worktrees to an existing project 44 + pm project wt add <name> --repos … # add worktrees to an existing project 45 45 pm project wt attach <name> {--repos … | --all} 46 46 pm project wt detach <name> {--repos … | --all} 47 - pm project wt delete <name> [--repos …] 47 + pm project wt remove <name> [--repos …] 48 48 pm project delete <name> [--repos …] # per-repo or whole project 49 49 50 50 # pool — worktree slots
+88 -62
integrations/_pm
··· 19 19 'project:manage projects' 20 20 'repo:manage canonical repos under ~/.repos' 21 21 'stacker:branch-stack tracking against pm'\''s pool' 22 - 'cd:Print the path of a project, or one of its worktrees.' 22 + 'cd:Change directory to a project, or one of its worktrees.' 23 23 'check:Verify invariants across pool and projects.' 24 24 ) 25 25 _describe -t commands 'command' commands ··· 49 49 case $words[1] in 50 50 ls) 51 51 _arguments \ 52 - '1:limit to one project' \ 52 + '1:project name (defaults to current project)' \ 53 53 '2:every project' \ 54 - '-p[limit to one project]:p' \ 55 - '--project[limit to one project]:project' \ 54 + '-p[project name (defaults to current project)]:p:_pm_projects' \ 55 + '--project[project name (defaults to current project)]:project:_pm_projects' \ 56 56 '--all[every project]' \ 57 57 '-n[-n]:n' \ 58 58 '--limit[-n]:limit' \ ··· 67 67 claude) 68 68 _arguments \ 69 69 '*:ARGS' \ 70 - '-p[-p]:p' \ 71 - '--project[-p]:project' \ 70 + '-p[project name (defaults to current project)]:p:_pm_projects' \ 71 + '--project[project name (defaults to current project)]:project:_pm_projects' \ 72 72 '--help[Display this message and exit.]' \ 73 73 '-h[Display this message and exit.]' \ 74 74 '--version[Display application version.]' ··· 77 77 codex) 78 78 _arguments \ 79 79 '*:ARGS' \ 80 - '-p[-p]:p' \ 81 - '--project[-p]:project' \ 80 + '-p[project name (defaults to current project)]:p:_pm_projects' \ 81 + '--project[project name (defaults to current project)]:project:_pm_projects' \ 82 82 '--help[Display this message and exit.]' \ 83 83 '-h[Display this message and exit.]' \ 84 84 '--version[Display application version.]' ··· 87 87 cursor) 88 88 _arguments \ 89 89 '*:ARGS' \ 90 - '-p[-p]:p' \ 91 - '--project[-p]:project' \ 90 + '-p[project name (defaults to current project)]:p:_pm_projects' \ 91 + '--project[project name (defaults to current project)]:project:_pm_projects' \ 92 92 '--help[Display this message and exit.]' \ 93 93 '-h[Display this message and exit.]' \ 94 94 '--version[Display application version.]' ··· 129 129 ;; 130 130 ls) 131 131 _arguments \ 132 - '1:repo:_pm_repos' \ 133 - '--repo[--repo]:repo:_pm_repos' \ 132 + '1:limit to one repo (default\: all repos)' \ 133 + '-r[limit to one repo (default\: all repos)]:r' \ 134 + '--repo[limit to one repo (default\: all repos)]:repo' \ 134 135 '--json[--json]' \ 135 136 '--help[Display this message and exit.]' \ 136 137 '-h[Display this message and exit.]' \ ··· 196 197 ;; 197 198 status) 198 199 _arguments \ 199 - '1:project:_pm_projects' \ 200 - '--project[--project]:project:_pm_projects' \ 200 + '1:project name (defaults to current project)' \ 201 + '-p[project name (defaults to current project)]:p:_pm_projects' \ 202 + '--project[project name (defaults to current project)]:project:_pm_projects' \ 201 203 '--json[--json]' \ 202 204 '-s[comma-separated subset\: worktrees,prs,stacker,sessions]:s' \ 203 205 '--section[comma-separated subset\: worktrees,prs,stacker,sessions]:section' \ ··· 218 220 cmds) 219 221 local -a commands 220 222 commands=( 223 + 'add:Add worktree(s) to an existing project.' 221 224 'attach:Re-attach worktrees (best-effort slot reclaim).' 222 - 'create:Add worktree(s) to an existing project.' 223 - 'delete:Delete worktree row(s) from a project.' 224 225 'detach:Detach worktrees\: unlink forward + release pool row.' 226 + 'remove:Remove worktree row(s) from a project.' 225 227 ) 226 228 _describe -t commands 'command' commands 227 229 ;; 228 230 args) 229 231 case $words[1] in 232 + add) 233 + _arguments \ 234 + '1:repo:_pm_repos' \ 235 + '2:project name (defaults to current project)' \ 236 + '--spec[--spec]:spec' \ 237 + '-p[project name (defaults to current project)]:p:_pm_projects' \ 238 + '--project[project name (defaults to current project)]:project:_pm_projects' \ 239 + '--json[--json]' \ 240 + '--help[Display this message and exit.]' \ 241 + '-h[Display this message and exit.]' \ 242 + '--version[Display application version.]' 243 + 244 + ;; 230 245 attach) 231 246 _arguments \ 232 247 '1:project name (defaults to current project)' \ ··· 243 258 '--version[Display application version.]' 244 259 245 260 ;; 246 - create) 247 - _arguments \ 248 - '1:repo:_pm_repos' \ 249 - '2:project name (defaults to current project)' \ 250 - '--spec[--spec]:spec' \ 251 - '-p[project name (defaults to current project)]:p:_pm_projects' \ 252 - '--project[project name (defaults to current project)]:project:_pm_projects' \ 253 - '--json[--json]' \ 254 - '--help[Display this message and exit.]' \ 255 - '-h[Display this message and exit.]' \ 256 - '--version[Display application version.]' 257 - 258 - ;; 259 - delete) 261 + detach) 260 262 _arguments \ 261 263 '1:project name (defaults to current project)' \ 262 264 '2:comma-separated worktree names' \ 263 265 '3:every worktree in the project' \ 264 266 '-p[project name (defaults to current project)]:p:_pm_projects' \ 265 267 '--project[project name (defaults to current project)]:project:_pm_projects' \ 266 - '--wt[comma-separated worktree names]:wt:_pm_wt_all' \ 268 + '--wt[comma-separated worktree names]:wt:_pm_wt_detachable' \ 267 269 '--all[every worktree in the project]' \ 268 270 '--dry-run[--dry-run]' \ 269 271 '--no-dry-run[--dry-run]' \ ··· 273 275 '--version[Display application version.]' 274 276 275 277 ;; 276 - detach) 278 + remove) 277 279 _arguments \ 278 280 '1:project name (defaults to current project)' \ 279 281 '2:comma-separated worktree names' \ 280 282 '3:every worktree in the project' \ 281 283 '-p[project name (defaults to current project)]:p:_pm_projects' \ 282 284 '--project[project name (defaults to current project)]:project:_pm_projects' \ 283 - '--wt[comma-separated worktree names]:wt:_pm_wt_detachable' \ 285 + '--wt[comma-separated worktree names]:wt:_pm_wt_all' \ 284 286 '--all[every worktree in the project]' \ 285 287 '--dry-run[--dry-run]' \ 286 288 '--no-dry-run[--dry-run]' \ ··· 329 331 ;; 330 332 maintenance) 331 333 _arguments \ 332 - '--repo[--repo]:repo:_pm_repos' \ 334 + '-r[comma-separated repo list (defaults to every repo)]:r' \ 335 + '--repo[comma-separated repo list (defaults to every repo)]:repo' \ 333 336 '--json[--json]' \ 334 337 '--help[Display this message and exit.]' \ 335 338 '-h[Display this message and exit.]' \ ··· 338 341 ;; 339 342 pull) 340 343 _arguments \ 341 - '--repo[--repo]:repo:_pm_repos' \ 344 + '-r[comma-separated repo list (defaults to every repo)]:r' \ 345 + '--repo[comma-separated repo list (defaults to every repo)]:repo' \ 342 346 '--json[--json]' \ 343 347 '--help[Display this message and exit.]' \ 344 348 '-h[Display this message and exit.]' \ ··· 384 388 case $words[1] in 385 389 abort) 386 390 _arguments \ 387 - '1:defaults to the cwd'\''s pm slot' \ 388 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 391 + '1:repo name (defaults to the cwd'\''s pm slot)' \ 392 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 393 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 389 394 '--help[Display this message and exit.]' \ 390 395 '-h[Display this message and exit.]' \ 391 396 '--version[Display application version.]' ··· 394 399 absorb) 395 400 _arguments \ 396 401 '1:--branch' \ 397 - '2:defaults to the cwd'\''s pm slot' \ 402 + '2:repo name (defaults to the cwd'\''s pm slot)' \ 398 403 '--branch[--branch]:branch' \ 399 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 404 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 405 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 400 406 '--continue[resume a paused absorb]' \ 401 407 '--abort[abort the paused op]' \ 402 408 '--help[Display this message and exit.]' \ ··· 410 416 '2:--value' \ 411 417 '--key[--key]:key' \ 412 418 '--value[--value]:value' \ 413 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 419 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 420 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 414 421 '--list[list all set keys]' \ 415 422 '--unset[remove a key]' \ 416 423 '--json[--json]' \ ··· 421 428 ;; 422 429 continue) 423 430 _arguments \ 424 - '1:defaults to the cwd'\''s pm slot' \ 425 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 431 + '1:repo name (defaults to the cwd'\''s pm slot)' \ 432 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 433 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 426 434 '--help[Display this message and exit.]' \ 427 435 '-h[Display this message and exit.]' \ 428 436 '--version[Display application version.]' ··· 432 440 _arguments \ 433 441 '1:--branch' \ 434 442 '--branch[--branch]:branch' \ 435 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 443 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 444 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 436 445 '--on[--on]:on' \ 437 446 '--copy[--copy]:copy' \ 438 447 '--replace[--replace]' \ ··· 474 483 log) 475 484 _arguments \ 476 485 '1:--branch' \ 477 - '2:defaults to the cwd'\''s pm slot' \ 486 + '2:repo name (defaults to the cwd'\''s pm slot)' \ 478 487 '--branch[--branch]:branch' \ 479 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 488 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 489 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 480 490 '--help[Display this message and exit.]' \ 481 491 '-h[Display this message and exit.]' \ 482 492 '--version[Display application version.]' ··· 485 495 ls) 486 496 _arguments \ 487 497 '1:--branch' \ 488 - '2:defaults to the cwd'\''s pm slot' \ 498 + '2:repo name (defaults to the cwd'\''s pm slot)' \ 489 499 '3:every tracked branch in the repo (default\: current lineage)' \ 490 500 '4:exclude ancestors from the walk' \ 491 501 '5:exclude descendants from the walk' \ 492 502 '6:start the walk from this branch' \ 493 503 '--branch[--branch]:branch' \ 494 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 504 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 505 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 495 506 '-a[every tracked branch in the repo (default\: current lineage)]' \ 496 507 '--all[every tracked branch in the repo (default\: current lineage)]' \ 497 508 '--skip-ancestors[exclude ancestors from the walk]' \ ··· 535 546 refresh) 536 547 _arguments \ 537 548 '1:--branch' \ 538 - '2:defaults to the cwd'\''s pm slot' \ 549 + '2:repo name (defaults to the cwd'\''s pm slot)' \ 539 550 '--branch[--branch]:branch' \ 540 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 551 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 552 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 541 553 '--help[Display this message and exit.]' \ 542 554 '-h[Display this message and exit.]' \ 543 555 '--version[Display application version.]' ··· 546 558 unlink) 547 559 _arguments \ 548 560 '1:--branch' \ 549 - '2:defaults to the cwd'\''s pm slot' \ 561 + '2:repo name (defaults to the cwd'\''s pm slot)' \ 550 562 '--branch[--branch]:branch' \ 551 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 563 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 564 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 552 565 '--all[clear PR links for every branch]' \ 553 566 '--help[Display this message and exit.]' \ 554 567 '-h[Display this message and exit.]' \ ··· 562 575 push) 563 576 _arguments \ 564 577 '1:--branch' \ 565 - '2:defaults to the cwd'\''s pm slot' \ 578 + '2:repo name (defaults to the cwd'\''s pm slot)' \ 566 579 '3:every tracked branch in the repo (default\: current lineage)' \ 567 580 '4:exclude ancestors from the walk' \ 568 581 '5:exclude descendants from the walk' \ 569 582 '6:start the walk from this branch' \ 570 583 '--branch[--branch]:branch' \ 571 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 584 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 585 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 572 586 '-a[every tracked branch in the repo (default\: current lineage)]' \ 573 587 '--all[every tracked branch in the repo (default\: current lineage)]' \ 574 588 '--skip-ancestors[exclude ancestors from the walk]' \ ··· 587 601 remove) 588 602 _arguments \ 589 603 '1:--branch' \ 590 - '2:defaults to the cwd'\''s pm slot' \ 604 + '2:repo name (defaults to the cwd'\''s pm slot)' \ 591 605 '--branch[--branch]:branch' \ 592 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 606 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 607 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 593 608 '--force[--force]' \ 594 609 '--parent[--parent]' \ 595 610 '--keep-branch[--keep-branch]' \ ··· 603 618 '1:--new-name' \ 604 619 '--new-name[--new-name]:new-name' \ 605 620 '--branch[--branch]:branch' \ 606 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 621 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 622 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 607 623 '--help[Display this message and exit.]' \ 608 624 '-h[Display this message and exit.]' \ 609 625 '--version[Display application version.]' ··· 614 630 '1:--base-ref' \ 615 631 '--base-ref[--base-ref]:base-ref' \ 616 632 '--branch[--branch]:branch' \ 617 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 633 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 634 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 618 635 '--help[Display this message and exit.]' \ 619 636 '-h[Display this message and exit.]' \ 620 637 '--version[Display application version.]' ··· 625 642 '1:--new-parent' \ 626 643 '--new-parent[--new-parent]:new-parent' \ 627 644 '--branch[--branch]:branch' \ 628 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 645 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 646 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 629 647 '--continue[resume a paused cherry-pick]' \ 630 648 '--abort[abort the paused op]' \ 649 + '--allow-drop-parent-modifications[drop out-of-band history changes below the stack range and replay only working …]' \ 650 + '--allow-drop-merge[when a descendant'\''s PR is merged but its squash is not on its parent, drop the …]' \ 651 + '--offline[skip the PR-state refresh; use cached pr_state for the merged-PR collapse.]' \ 631 652 '--help[Display this message and exit.]' \ 632 653 '-h[Display this message and exit.]' \ 633 654 '--version[Display application version.]' ··· 640 661 '--new-name[--new-name]:new-name' \ 641 662 '--commit[--commit]:commit' \ 642 663 '--branch[--branch]:branch' \ 643 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 664 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 665 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 644 666 '--stay[--stay]' \ 645 667 '--help[Display this message and exit.]' \ 646 668 '-h[Display this message and exit.]' \ ··· 650 672 sync) 651 673 _arguments \ 652 674 '1:--branch' \ 653 - '2:defaults to the cwd'\''s pm slot' \ 675 + '2:repo name (defaults to the cwd'\''s pm slot)' \ 654 676 '3:every tracked branch in the repo (default\: current lineage)' \ 655 677 '4:exclude ancestors from the walk' \ 656 678 '5:exclude descendants from the walk' \ 657 679 '6:start the walk from this branch' \ 658 680 '--branch[--branch]:branch' \ 659 - '--repo[defaults to the cwd'\''s pm slot]:repo:_pm_repos' \ 681 + '-r[repo name (defaults to the cwd'\''s pm slot)]:r' \ 682 + '--repo[repo name (defaults to the cwd'\''s pm slot)]:repo:_pm_repos' \ 660 683 '-a[every tracked branch in the repo (default\: current lineage)]' \ 661 684 '--all[every tracked branch in the repo (default\: current lineage)]' \ 662 685 '--skip-ancestors[exclude ancestors from the walk]' \ ··· 664 687 '--from[start the walk from this branch]:from' \ 665 688 '--continue[resume a paused cherry-pick]' \ 666 689 '--abort[abort the paused op]' \ 690 + '--allow-drop-parent-modifications[drop out-of-band history changes below the stack range and replay only the bran…]' \ 691 + '--allow-drop-merge[when the branch'\''s PR is merged but the squash isn'\''t on the parent, drop the bra…]' \ 692 + '--offline[skip the PR-state refresh; use cached pr_state for the merged-PR collapse decis…]' \ 667 693 '--help[Display this message and exit.]' \ 668 694 '-h[Display this message and exit.]' \ 669 695 '--version[Display application version.]' ··· 679 705 '2:wt:_pm_wt_for_cd' \ 680 706 '--project[--project]:project:_pm_projects' \ 681 707 '--wt[--wt]:wt:_pm_wt_for_cd' \ 682 - '--print[print path instead of cd]' \ 708 + '--print[print the path instead of cd'\''ing (opts out of the shell wrapper)]' \ 683 709 '--help[Display this message and exit.]' \ 684 710 '-h[Display this message and exit.]' \ 685 711 '--version[Display application version.]'
+6 -6
integrations/emacs/pm-commands.el
··· 54 54 55 55 ;;;; project wt 56 56 57 - (defun pm--wt-create (name spec cb) 57 + (defun pm--wt-add (name spec cb) 58 58 (pm--run-async 59 - (pm--strs "project" "wt" "create" spec "--project" name "--json") 60 - cb :tag 'wt-create)) 59 + (pm--strs "project" "wt" "add" spec "--project" name "--json") 60 + cb :tag 'wt-add)) 61 61 62 62 (defun pm--wt-attach (name wts all no-branch cb) 63 63 (pm--run-async ··· 77 77 "--json") 78 78 cb :tag 'wt-detach)) 79 79 80 - (defun pm--wt-delete (name wts all cb) 80 + (defun pm--wt-remove (name wts all cb) 81 81 (pm--run-async 82 - (pm--strs "project" "wt" "delete" name 82 + (pm--strs "project" "wt" "remove" name 83 83 (cond (all '("--all")) 84 84 (wts (list "--wt" wts))) 85 85 "--json") 86 - cb :tag 'wt-delete)) 86 + cb :tag 'wt-remove)) 87 87 88 88 ;;;; pool 89 89
+1 -1
integrations/emacs/pm-tests.el
··· 769 769 the transient scope. In step 3 it sees alpha (the original 770 770 buffer's container) and silently lists alpha's sessions. 771 771 772 - Every other project-dispatch suffix (status, wt-create, wt-attach, 772 + Every other project-dispatch suffix (status, wt-add, wt-attach, 773 773 agent-launch, …) already reads `(pm--ds-container-name)' / 774 774 `(transient-scope)' for exactly this reason — see the comment 775 775 \"The bound project travels through the transient via `:scope'…\"
+11 -11
integrations/emacs/pm-transient.el
··· 101 101 (interactive) 102 102 (pm-project-status (pm--ds-container-name))) 103 103 104 - (transient-define-suffix pm-project-dispatch--wt-create () 104 + (transient-define-suffix pm-project-dispatch--wt-add () 105 105 "Add a worktree to the bound project." 106 106 :description "new" 107 107 (interactive) 108 - (pm-wt-create (pm--ds-container-name))) 108 + (pm-wt-add (pm--ds-container-name))) 109 109 110 110 (transient-define-suffix pm-project-dispatch--wt-attach () 111 111 "Attach worktrees in the bound project, honoring infix args." ··· 159 159 (message "pm: %s" 160 160 (if dry-run "detach (dry-run)" "detached"))))))))) 161 161 162 - (transient-define-suffix pm-project-dispatch--wt-delete () 163 - "Delete worktrees from the bound project, honoring infix args." 162 + (transient-define-suffix pm-project-dispatch--wt-remove () 163 + "Remove worktrees from the bound project, honoring infix args." 164 164 :description 165 165 (lambda () 166 166 (let ((args (pm--dispatch-args))) 167 - (pm--decorate-desc "delete" (and (member "--all" args) "all")))) 167 + (pm--decorate-desc "remove" (and (member "--all" args) "all")))) 168 168 (interactive) 169 169 (let* ((args (pm--dispatch-args)) 170 170 (all (and (member "--all" args) t)) ··· 172 172 (wts (unless all 173 173 (mapconcat #'identity 174 174 (pm--read-worktrees-multi 175 - "Delete which: " name) 175 + "Remove which: " name) 176 176 ",")))) 177 177 (when (pm--maybe-confirm 178 - (format "Permanently delete %s in %s? " 178 + (format "Permanently remove %s in %s? " 179 179 (if all "all worktrees" wts) name)) 180 - (pm--wt-delete name wts all 180 + (pm--wt-remove name wts all 181 181 (lambda (_) 182 182 (pm-refresh 183 - (lambda (_) (message "pm: deleted")))))))) 183 + (lambda (_) (message "pm: removed")))))))) 184 184 185 185 (transient-define-suffix pm-project-dispatch--delete () 186 186 "Delete the bound project." ··· 270 270 [["Inspect" 271 271 ("s" pm-project-dispatch--status)] 272 272 ["Worktrees" 273 - ("n" pm-project-dispatch--wt-create) 273 + ("n" pm-project-dispatch--wt-add) 274 274 ("a" pm-project-dispatch--wt-attach) 275 275 ("D" pm-project-dispatch--wt-detach) 276 - ("X" pm-project-dispatch--wt-delete)] 276 + ("X" pm-project-dispatch--wt-remove)] 277 277 ["Navigate" 278 278 :if pm--ds-inside-worktree-p 279 279 ("p" pm-project-dispatch--jump-parent)
+10 -10
integrations/emacs/pm-ui.el
··· 145 145 ;;;; Worktree commands 146 146 147 147 ;;;###autoload 148 - (defun pm-wt-create (name) 148 + (defun pm-wt-add (name) 149 149 "Add a worktree to project NAME." 150 150 (interactive (list (pm--read-project "Add worktree to: "))) 151 151 (pm--repo-ls ··· 156 156 "Worktrees (repos, comma-sep): " repo-names nil t))) 157 157 (when (null chosen) (user-error "Aborted")) 158 158 (let ((spec (pm--build-spec chosen))) 159 - (pm--wt-create 159 + (pm--wt-add 160 160 name spec 161 161 (lambda (_) 162 162 (pm-refresh 163 163 (lambda (_) (message "pm: added wt(s) to %s" name)))))))))) 164 164 165 165 (defun pm--wt-action (action verb name) 166 - "Common skeleton for attach/detach/delete on worktrees in NAME. 166 + "Common skeleton for attach/detach/remove on worktrees in NAME. 167 167 ACTION is the wrapper symbol; VERB is the user-visible name." 168 168 (let* ((all (y-or-n-p (format "%s ALL worktrees in %s? " verb name))) 169 169 (wts (unless all ··· 171 171 (pm--read-worktrees-multi 172 172 (format "%s which worktrees: " verb) name) 173 173 ",")))) 174 - (when (and (member verb '("Detach" "Delete")) 174 + (when (and (member verb '("Detach" "Remove")) 175 175 (not (pm--maybe-confirm 176 176 (format "%s %s in %s? " 177 177 verb (if all "all worktrees" wts) name)))) ··· 185 185 (lambda (_) 186 186 (pm-refresh 187 187 (lambda (_) (message "pm: detached")))))) 188 - ((eq action 'delete) (pm--wt-delete name wts all 188 + ((eq action 'remove) (pm--wt-remove name wts all 189 189 (lambda (_) 190 190 (pm-refresh 191 - (lambda (_) (message "pm: deleted")))))) 192 - ((eq action 'create) (error "Use pm-wt-create directly"))))) 191 + (lambda (_) (message "pm: removed")))))) 192 + ((eq action 'add) (error "Use pm-wt-add directly"))))) 193 193 194 194 ;;;###autoload 195 195 (defun pm-wt-attach (name) ··· 202 202 (pm--wt-action 'detach "Detach" name)) 203 203 204 204 ;;;###autoload 205 - (defun pm-wt-delete (name) 206 - (interactive (list (pm--read-project "Delete worktrees in: "))) 207 - (pm--wt-action 'delete "Delete" name)) 205 + (defun pm-wt-remove (name) 206 + (interactive (list (pm--read-project "Remove worktrees in: "))) 207 + (pm--wt-action 'remove "Remove" name)) 208 208 209 209 ;;;; Pool / repo verbs (the listing buffers live in `pm-pool', `pm-repo') 210 210
+3 -3
integrations/emacs/pm.el
··· 50 50 ;;;###autoload 51 51 (defcustom pm-confirm-destructive t 52 52 "When non-nil, confirm destructive pm operations. 53 - Applies to `project delete', `wt detach --all', `wt delete', etc." 53 + Applies to `project delete', `wt detach --all', `wt remove', etc." 54 54 :type 'boolean 55 55 :group 'pm) 56 56 ··· 98 98 (autoload 'pm-project-create "pm-ui" nil t) 99 99 (autoload 'pm-project-delete "pm-ui" nil t) 100 100 (autoload 'pm-project-switch "pm-ui" nil t) 101 - (autoload 'pm-wt-create "pm-ui" nil t) 101 + (autoload 'pm-wt-add "pm-ui" nil t) 102 102 (autoload 'pm-wt-attach "pm-ui" nil t) 103 103 (autoload 'pm-wt-detach "pm-ui" nil t) 104 - (autoload 'pm-wt-delete "pm-ui" nil t) 104 + (autoload 'pm-wt-remove "pm-ui" nil t) 105 105 (autoload 'pm-pool-add "pm-ui" nil t) 106 106 (autoload 'pm-repo-pull "pm-ui" nil t) 107 107 (autoload 'pm-project-status "pm-status" nil t)
+1 -1
integrations/claude-code/.claude-plugin/plugin.json
··· 1 1 { 2 2 "name": "pm", 3 - "version": "0.1.0", 3 + "version": "0.1.1", 4 4 "description": "pm CLI workflow — projects, pooled worktree slots, and cherry-pick-based stacked PRs", 5 5 "author": { 6 6 "name": "Jordan Isaacs",
+7 -7
src/project_manager/cli/_complete.py
··· 176 176 # --repo (pool add/ls + every stacker command with `--repo`) 177 177 ("'--repo[--repo]:repo'", "'--repo[--repo]:repo:_pm_repos'"), 178 178 ( 179 - "'--repo[defaults to the cwd'\\''s pm slot]:repo'", 180 - "'--repo[defaults to the cwd'\\''s pm slot]:repo:_pm_repos'", 179 + "'--repo[repo name (defaults to the cwd'\\''s pm slot)]:repo'", 180 + "'--repo[repo name (defaults to the cwd'\\''s pm slot)]:repo:_pm_repos'", 181 181 ), 182 182 # --project / -p flag forms (every wt subcommand uses the descriptive 183 183 # form; project delete/status use the terse form — both get bound). ··· 191 191 ), 192 192 # `pm pool {ls,add}` positional → repos. 193 193 ("'1:--repo'", "'1:repo:_pm_repos'"), 194 - # `pm project wt create` positional spec defaults to `<repo>` (or 194 + # `pm project wt add` positional spec defaults to `<repo>` (or 195 195 # `<wt>:<repo>`). Completing repo names covers the common case. 196 196 ("'1:--spec'", "'1:repo:_pm_repos'"), 197 197 # `pm cd <project> <wt>` — `'2:--wt'` only appears here, and the ··· 209 209 # - `pm project create` has `'1:--project'` and `'--project[--project]:project'` 210 210 # — both represent a NEW project name; must NOT complete. 211 211 # - `pm project {delete,status}` use the same strings — DO complete. 212 - # - `pm project wt {attach,detach,delete}` all use 212 + # - `pm project wt {attach,detach,remove}` all use 213 213 # `'--wt[comma-separated worktree names]:wt'` but need different helpers 214 214 # (attachable / detachable / all). 215 215 # ··· 217 217 # per-subcommand rewrites for the `--wt` slot inside the wt section. 218 218 219 219 # Matches the project-level `create)` _arguments block (from the `create)` 220 - # label through the next `;;`). `'1:--spec'` only appears in wt-create, so 220 + # label through the next `;;`). `'1:--spec'` only appears in wt-add, so 221 221 # this pattern anchors on the `'1:--project'` line which is unique to the 222 222 # project-level create. 223 223 _PROJECT_CREATE_BLOCK = re.compile( ··· 228 228 _WT_FLAG_BY_SUB: dict[str, str] = { 229 229 "attach": "_pm_wt_attachable", 230 230 "detach": "_pm_wt_detachable", 231 - "delete": "_pm_wt_all", 231 + "remove": "_pm_wt_all", 232 232 } 233 233 234 234 _WT_NEEDLE = "'--wt[comma-separated worktree names]:wt'" 235 235 236 236 237 237 def _rewrite_wt_flag_per_sub(script: str, *, warn: list[str]) -> str: 238 - """Bind --wt to a different helper per wt subcommand (attach/detach/delete). 238 + """Bind --wt to a different helper per wt subcommand (attach/detach/remove). 239 239 240 240 Walks each `<sub>) _arguments ... ;;` block in turn and substitutes the 241 241 `--wt` slot inside. Blocks that don't contain the slot (e.g. `delete)`
+10 -10
integrations/claude-code/skills/pm-workflow/SKILL.md
··· 24 24 ## Critical Rules 25 25 26 26 1. **Always pass `--json` when reading state.** `pm project ls`, `pm project status`, `pm pool ls`, `pm repo ls`, `pm agent ls`, `pm check`, and the `pm project wt` plan-output commands all support `--json`. Use it for any inspection — the human-readable output uses tree/table rendering meant for terminals. 27 - 2. **Don't edit symlinks under `~/.projects/<name>/` by hand.** Always go through `pm project wt create | attach | detach | delete`. Hand-edits desync the project's `.pm.db` and the pool. 27 + 2. **Don't edit symlinks under `~/.projects/<name>/` by hand.** Always go through `pm project wt add | attach | detach | remove`. Hand-edits desync the project's `.pm.db` and the pool. 28 28 3. **Don't `git init` or commit inside a project directory.** Projects are not git repos; they're folders of symlinks. Run git commands inside the worktree (e.g. `~/.projects/foo/frontend/`), not the project root. 29 29 4. **`pm cd` only works if the zsh wrapper is sourced.** It needs `source <pm install>/integrations/pm-cd.zsh` in the user's zshrc. Without the wrapper, `pm cd` prints help. Use `pm cd --print <proj> [<wt>]` if you just need the path for scripting. 30 30 5. **Use `pm check --fix` to repair drift**, never manual surgery on symlinks or the pool db. 31 - 6. **Don't create projects or worktrees** unless the user asked. `pm project create` and `pm project wt create` are durable, user-visible state. 31 + 6. **Don't create projects or worktrees** unless the user asked. `pm project create` and `pm project wt add` are durable, user-visible state. 32 32 33 33 ## Command Reference 34 34 ··· 45 45 46 46 | Command | Description | `--json` | 47 47 |---|---|---| 48 - | `pm project wt create <repo> [-p <proj>] [--spec <spec>]` | Add worktrees to a project | ✓ | 48 + | `pm project wt add <repo> [-p <proj>] [--spec <spec>]` | Add worktrees to a project | ✓ | 49 49 | `pm project wt attach [-p <proj>] [--wt a,b \| --all]` | Re-attach worktrees, reclaim slots from the pool | ✓ | 50 50 | `pm project wt detach [-p <proj>] [--wt a,b \| --all] [--dry-run]` | Unlink the symlinks but keep the slots warm in the pool | ✓ | 51 - | `pm project wt delete [-p <proj>] [--wt a,b \| --all] [--dry-run]` | Remove worktrees from the project entirely | ✓ | 51 + | `pm project wt remove [-p <proj>] [--wt a,b \| --all] [--dry-run]` | Remove worktrees from the project entirely | ✓ | 52 52 53 53 `-p/--project` is optional — if omitted, `pm` infers the project from the cwd. 54 54 ··· 109 109 ### Adding a worktree to an existing project 110 110 111 111 ```bash 112 - pm project wt create frontend -p my-feature 112 + pm project wt add frontend -p my-feature 113 113 # Inferred project from cwd: 114 - cd ~/.projects/my-feature && pm project wt create backend 114 + cd ~/.projects/my-feature && pm project wt add backend 115 115 ``` 116 116 117 117 ### Detach / re-attach (parking a slot without losing the branch) ··· 127 127 ### Cleaning up 128 128 129 129 ```bash 130 - pm project wt delete -p my-feature --wt scratch --dry-run # preview 131 - pm project wt delete -p my-feature --wt scratch 130 + pm project wt remove -p my-feature --wt scratch --dry-run # preview 131 + pm project wt remove -p my-feature --wt scratch 132 132 # Or wipe the whole project: 133 133 pm project delete my-feature --dry-run 134 134 pm project delete my-feature ··· 172 172 173 173 | Footgun | What happens | Right move | 174 174 |---|---|---| 175 - | Editing symlinks under `~/.projects/<n>/` by hand | Project `.pm.db` and pool drift | `pm project wt {attach,detach,delete}`, then `pm check --fix` | 175 + | Editing symlinks under `~/.projects/<n>/` by hand | Project `.pm.db` and pool drift | `pm project wt {attach,detach,remove}`, then `pm check --fix` | 176 176 | `git init` or committing inside a project root | The project becomes a broken pseudo-repo | Run git inside the worktree (e.g. `~/.projects/p/frontend/`), never the project root | 177 177 | Detach refuses with "uncommitted changes" or "in-progress operation" | Slot has dirty state or a paused stacker op | Resolve in the worktree first (commit/stash, or `pm stacker continue`/`abort`) | 178 178 | `pm cd` prints help instead of cd'ing | The zsh wrapper isn't sourced | Source `<install>/integrations/pm-cd.zsh` in zshrc; or use `pm cd --print` for the path | 179 179 | `PoolExhaustedError` on create/attach | No free slots for that repo and the pool can't grow on demand | `pm pool add <repo>` to mint a slot, or `pm project wt detach` an unused worktree to free one | 180 - | "slot busy" on detach/delete | Slot has a paused `pm stacker` operation holding it | `pm stacker continue` or `pm stacker abort` in the affected worktree first | 180 + | "slot busy" on detach/remove | Slot has a paused `pm stacker` operation holding it | `pm stacker continue` or `pm stacker abort` in the affected worktree first | 181 181 182 182 ## Output Samples (for parsing) 183 183
+1 -1
src/project_manager/project/cli/_plan_printers.py
··· 1 - """Shared dry-run plan printers for project/wt delete and detach.""" 1 + """Shared dry-run plan printers for project delete, wt remove, and detach.""" 2 2 3 3 import sys 4 4
+2 -2
src/project_manager/project/cli/wt/__init__.py
··· 8 8 App(name="wt", help="manage worktrees within a project"), 9 9 ) 10 10 11 + from . import add as _add # noqa: F401,E402 11 12 from . import attach as _attach # noqa: F401,E402 12 - from . import create as _create # noqa: F401,E402 13 - from . import delete as _delete # noqa: F401,E402 14 13 from . import detach as _detach # noqa: F401,E402 14 + from . import remove as _remove # noqa: F401,E402
+33
src/project_manager/project/cli/wt/add.py
··· 1 + """`pm project wt add`.""" 2 + 3 + from typing import Annotated 4 + 5 + from cyclopts import Parameter 6 + 7 + from project_manager import config, render 8 + from project_manager.cli._shared import ProjectFlag 9 + from project_manager.project import create as create_mod 10 + from project_manager.project import current 11 + from project_manager.project.spec import parse_wt_spec 12 + 13 + from . import wt_app 14 + 15 + 16 + @wt_app.command 17 + def add( 18 + spec: str, 19 + flag: ProjectFlag = ProjectFlag(), 20 + *, 21 + json: Annotated[bool, Parameter(negative="")] = False, 22 + ) -> int: 23 + """Add worktree(s) to an existing project. 24 + 25 + <spec> is the same format as `pm project create --wt`: 26 + comma-separated `<repo>` or `<name>:<repo>`. 27 + """ 28 + paths = config.load() 29 + project = current.resolve_project(paths, flag.project) 30 + items = parse_wt_spec(spec) 31 + claimed = create_mod.create(paths, project, items) 32 + render.emit_rows(claimed, create_mod.CREATED_COLUMNS, as_json=json) 33 + return 0
-33
src/project_manager/project/cli/wt/create.py
··· 1 - """`pm project wt create`.""" 2 - 3 - from typing import Annotated 4 - 5 - from cyclopts import Parameter 6 - 7 - from project_manager import config, render 8 - from project_manager.cli._shared import ProjectFlag 9 - from project_manager.project import create as create_mod 10 - from project_manager.project import current 11 - from project_manager.project.spec import parse_wt_spec 12 - 13 - from . import wt_app 14 - 15 - 16 - @wt_app.command 17 - def create( 18 - spec: str, 19 - flag: ProjectFlag = ProjectFlag(), 20 - *, 21 - json: Annotated[bool, Parameter(negative="")] = False, 22 - ) -> int: 23 - """Add worktree(s) to an existing project. 24 - 25 - <spec> is the same format as `pm project create --wt`: 26 - comma-separated `<repo>` or `<name>:<repo>`. 27 - """ 28 - paths = config.load() 29 - project = current.resolve_project(paths, flag.project) 30 - items = parse_wt_spec(spec) 31 - claimed = create_mod.create(paths, project, items) 32 - render.emit_rows(claimed, create_mod.CREATED_COLUMNS, as_json=json) 33 - return 0
-36
src/project_manager/project/cli/wt/delete.py
··· 1 - """`pm project wt delete`.""" 2 - 3 - from typing import Annotated 4 - 5 - from cyclopts import Parameter 6 - 7 - from project_manager import config 8 - from project_manager.cli._shared import ProjectFlag, WtSelection, selected_wts 9 - from project_manager.project import current 10 - from project_manager.project import delete as delete_mod 11 - from project_manager.project.cli._plan_printers import emit_delete_plan 12 - 13 - from . import wt_app 14 - 15 - 16 - @wt_app.command 17 - def delete( 18 - flag: ProjectFlag = ProjectFlag(), 19 - sel: WtSelection = WtSelection(), 20 - *, 21 - dry_run: bool = False, 22 - json: Annotated[bool, Parameter(negative="")] = False, 23 - ) -> int: 24 - """Delete worktree row(s) from a project. 25 - 26 - --dry-run prints the plan without mutating state; exits 1 if any blocker. 27 - """ 28 - paths = config.load() 29 - project = current.resolve_project(paths, flag.project) 30 - selected = selected_wts(sel) 31 - if dry_run: 32 - plan = delete_mod.plan_delete(paths, project, selected) 33 - emit_delete_plan(plan, paths, as_json=json) 34 - return 1 if plan.has_blocker else 0 35 - delete_mod.delete(paths, project, selected) 36 - return 0
+36
src/project_manager/project/cli/wt/remove.py
··· 1 + """`pm project wt remove`.""" 2 + 3 + from typing import Annotated 4 + 5 + from cyclopts import Parameter 6 + 7 + from project_manager import config 8 + from project_manager.cli._shared import ProjectFlag, WtSelection, selected_wts 9 + from project_manager.project import current 10 + from project_manager.project import delete as delete_mod 11 + from project_manager.project.cli._plan_printers import emit_delete_plan 12 + 13 + from . import wt_app 14 + 15 + 16 + @wt_app.command 17 + def remove( 18 + flag: ProjectFlag = ProjectFlag(), 19 + sel: WtSelection = WtSelection(), 20 + *, 21 + dry_run: bool = False, 22 + json: Annotated[bool, Parameter(negative="")] = False, 23 + ) -> int: 24 + """Remove worktree row(s) from a project. 25 + 26 + --dry-run prints the plan without mutating state; exits 1 if any blocker. 27 + """ 28 + paths = config.load() 29 + project = current.resolve_project(paths, flag.project) 30 + selected = selected_wts(sel) 31 + if dry_run: 32 + plan = delete_mod.plan_delete(paths, project, selected) 33 + emit_delete_plan(plan, paths, as_json=json) 34 + return 1 if plan.has_blocker else 0 35 + delete_mod.delete(paths, project, selected) 36 + return 0