Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ jobs:
test-pmctl-safe.sh
test-pmctl-validate.sh
test-pmctl-decision.sh
test-pmctl-worktree.sh

test-doctor:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -419,6 +420,13 @@ jobs:
- name: pmctl validate brief regression suite
run: bash scripts/test-pmctl-validate.sh

test-pmctl-worktree:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: pmctl worktree regression suite
run: bash scripts/test-pmctl-worktree.sh

lint-backlog:
runs-on: ubuntu-latest
steps:
Expand Down
17 changes: 11 additions & 6 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ CC-001/CC-002 were consumed by PR #24 fix bundle inline, with no standalone entr
| CC-004 | 🟢 someday | test-pr-gate.sh docstring 格式統一 | ops | 2026-05-12 | pr:#38 | P3 | — |
| CC-011 | 🟢 someday | sync-memory.sh + install 選項:symlink memory 到雲端資料夾實現跨裝置共用 | ux/memory | 2026-05-14 | — | — | — |
| CC-012 | 🟢 someday | SessionStart hook:session 啟動時 pull 最新 memory(git/rsync)確保跨裝置同步 | ux/memory | 2026-05-14 | — | — | — |
| CC-014 | 🔵 active | `using-git-worktrees` skill:parallel PR gate 隔離開發環境。v0.8.0 Phase 4 | arch | 2026-05-14 | | — | — |
| CC-014 | ✅ closed 2026-07-02 | repo 通用 worktree 平行開發工具:建立/清理 worktree + using-git-worktrees skill。v0.8.0 Phase 4 | arch | 2026-05-14 | pr:#358 | — | — |
| CC-015 | ⏸ deferred | `systematic-debugging` skill:結構化偵錯工作流 | ux | 2026-05-14 | — | — | — |
| CC-018 | 🟢 someday | Codex quota 自動追蹤 + rate-limit 路徑統一(吸收 CC-269):寫到 `~/.local/share/pm-dispatch/state/rate-limits.json`;解析 API response headers;token-usage.sh 加 Codex pool 顯示 | ux/token | 2026-05-14 | — | P3 | — |
| CC-023 | ⏸ deferred | `coupling-reviewer`:PR gate 加入語言感知耦合分析(dependency-cruiser/gocyclo/coca) | ops/gate | 2026-05-14 | — | — | — |
Expand Down Expand Up @@ -216,12 +216,17 @@ _Terminal_ (CC-378: swept OUT to `BACKLOG-ARCHIVE.md` by `scripts/archive-closed
**Note**: 依賴 CC-011;建議與 CC-011 合入同一 PR(Phase 1 + Phase 2 同步落地,CC-012 無獨立實作意義)。
**Status note (CC-050 audit 2026-05-18)**: Downgraded from ⏸ deferred to 🟢 someday — depends on CC-011; no active plan. Re-evaluate together with CC-011.

## CC-014 — `using-git-worktrees` skill
## CC-014 — repo 通用 worktree 平行開發工具 ✅ 2026-07-02

**Status note (v0.8.0 planning 2026-07-01)**: Re-activated (was downgraded to ⏸ deferred by the CC-050 audit 2026-05-18 for lacking an open branch) — assigned to v0.8.0 Phase 4.
**Problem**: `--parallel` PR gate 各 reviewer 在同一 working tree 執行,reviewer 寫入可能互相干擾。
**Why**: git worktree 讓每個 subagent 在獨立環境工作,避免狀態污染,也直接補強 CC-003 的解法方向。
**Requirement**: `commands/using-git-worktrees.md` skill,指導平行開發中使用 git worktree;評估 `--parallel` gate 是否可為每個 reviewer 建立獨立 worktree。
**Status note (v0.8.0 planning 2026-07-02)**: Re-activated (was downgraded to ⏸ deferred by the CC-050 audit 2026-05-18 for lacking an open branch) — assigned to v0.8.0 Phase 4. Scope broadened 2026-07-02: 不再侷限於 pr-gate reviewer 隔離,改為 repo 層級通用 worktree 工具,讓任一 ticket/分支都能快速建立、切換、清理獨立 worktree 以支援多票並行開發。
**Problem**: 目前開發者(與 `--parallel` PR gate 各 reviewer)都在同一 working tree 上工作,跨票並行開發時彼此的未 commit 變更、build 產物會互相干擾;沒有標準化的方式建立/清理獨立 worktree。
**Why**: git worktree 讓每個工作串流(人或 subagent)在獨立環境工作,避免狀態污染;同時直接補強 CC-003 的解法方向,也可延伸解掉 `--parallel` gate 的 reviewer 隔離問題。
**Requirement**:
1. `scripts/worktree-*.sh`(或等效 `pmctl` 子指令):為指定 ticket/分支建立、列出、清理 worktree,統一命名慣例與清理時機(避免孤兒 worktree 殘留)。
2. `commands/using-git-worktrees.md` skill:指導開發者(人或 dispatch executor)如何用這些工具做功能分支平行開發。
3. 評估 `--parallel` PR gate 是否可改用同一套工具為每個 reviewer 建立獨立 worktree(原票聚焦點,現列為本票子項而非全部範圍)。
**Outcome**: `pmctl worktree create/list/remove/gc` 落地,manifest 存於 state store(`sw_project_worktree_dir`,跨主 repo/linked worktree 同一 partition);`commands/using-git-worktrees.md` skill 文件;36 個 focused test。`--parallel` gate reviewer 隔離(原需求 3)留待未來 follow-up ticket,未併入本次範圍。
**See**: pr:#358

## CC-015 — `systematic-debugging` skill

Expand Down
6 changes: 3 additions & 3 deletions MILESTONES.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@
|----|------|------|
| CC-381 | install host-PM-aware — 縮小為 read-only host-profile-detection / doctor 擴充切片:讓 `doctor.sh`/`pmctl doctor` 能回答「目前 host 是 claude/codex/opencode?哪些能力有 wiring?哪些只能透過 pmctl 手動使用?」不動 installer write path。前置票 CC-372/374/375/380 已全數 done,本 Phase 目標是把 CC-381 從設計陳述推進為有明確 Requirement 的實作票 | 🔵 active |

### Phase 4 — CC-014 `using-git-worktrees` skill(P3;低風險並行;與 Phase 1-3 檔案面不重疊)
### Phase 4 — CC-014 repo 通用 worktree 平行開發工具(P3;低風險並行;與 Phase 1-3 檔案面不重疊)

| 票 | 摘要 | 狀態 |
|----|------|------|
| CC-014 | `using-git-worktrees` skill:為平行開發(`--parallel` PR gate reviewer 目前共用同一 working tree,寫入可能互相干擾)補一份指導 skill;評估 `--parallel` gate 是否可為每個 reviewer 建立獨立 git worktree | 🔵 active |
| CC-014 | repo 通用 worktree 建立/列出/清理工具 + `using-git-worktrees` skill,支援多票並行開發;`--parallel` PR gate reviewer 隔離整合留待未來 follow-up ticket,未併入本次範圍 | ✅ done pr:#358 |

> 由 CC-050 稽核降級的 ⏸ deferred(無開放分支)重新啟用;規劃時尚未有實作分支,範圍與時程由後續 `/pre-impl` 或直接 dispatch 時再收斂。
> 由 CC-050 稽核降級的 ⏸ deferred(無開放分支)重新啟用;2026-07-02 範圍由「pr-gate reviewer 隔離」擴大為「repo 通用 worktree 工具」;規劃時尚未有實作分支,範圍與時程由後續 `/pre-impl` 或直接 dispatch 時再收斂。

### 待後續 / 明確排除

Expand Down
26 changes: 25 additions & 1 deletion cli/pmctl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ done
REPO_ROOT="$(cd "$(dirname "$_pmctl_self")/.." && pwd)"
unset _pmctl_self _pmctl_dir

for _lib in detached-launch pmctl-policy pmctl-fs pmctl-adapter pmctl-backlog pmctl-guard executor-router pmctl-dispatch pmctl-trace pmctl-task pmctl-decision gate-result-verify pmctl-gate pmctl-safe pmctl-validate pmctl-context pmctl-memory pmctl-artifacts pmctl-pre-release; do
for _lib in detached-launch pmctl-policy pmctl-fs pmctl-adapter pmctl-backlog pmctl-guard executor-router pmctl-dispatch pmctl-trace pmctl-task pmctl-decision gate-result-verify pmctl-gate pmctl-safe pmctl-validate pmctl-context pmctl-memory pmctl-artifacts pmctl-pre-release pmctl-worktree; do
# shellcheck source=/dev/null
[[ -r "$REPO_ROOT/scripts/lib/$_lib.sh" ]] && . "$REPO_ROOT/scripts/lib/$_lib.sh"
done
Expand Down Expand Up @@ -99,6 +99,30 @@ case "$cmd/$sub" in
fi
pmctl_artifacts_migrate "$REPO_ROOT" "" "$@"
;;
worktree/create)
if ! declare -F pmctl_worktree_create >/dev/null; then
pmctl_die "worktree create unavailable"
fi
pmctl_worktree_create "$REPO_ROOT" "" "$@"
;;
worktree/list)
if ! declare -F pmctl_worktree_list >/dev/null; then
pmctl_die "worktree list unavailable"
fi
pmctl_worktree_list "$REPO_ROOT" "" "$@"
;;
worktree/remove)
if ! declare -F pmctl_worktree_remove >/dev/null; then
pmctl_die "worktree remove unavailable"
fi
pmctl_worktree_remove "$REPO_ROOT" "" "$@"
;;
worktree/gc)
if ! declare -F pmctl_worktree_gc >/dev/null; then
pmctl_die "worktree gc unavailable"
fi
pmctl_worktree_gc "$REPO_ROOT" "" "$@"
;;
trace/tail)
if ! declare -F pmctl_trace_tail >/dev/null; then
pmctl_die "trace tail unavailable"
Expand Down
60 changes: 60 additions & 0 deletions commands/using-git-worktrees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
description: Use a dedicated git worktree per ticket/branch for parallel development — isolates uncommitted changes and build artifacts so multiple branches can be worked on at once without stepping on each other.
argument-hint: "[branch-name]"
---

Set up an isolated `git worktree` for parallel development using `pmctl worktree`, instead of switching branches in place or cloning the repo again.

## What

`pmctl worktree` wraps `git worktree` with a small out-of-repo registry (manifest) so linked worktrees are easy to list and clean up later, without hand-tracking which directory belongs to which branch.

**Prerequisite: this requires git.** `pmctl worktree` is a thin wrapper over `git worktree add/remove/prune` — there is no non-git fallback. The target directory must already be a git repository; `pmctl worktree create` fails immediately (git itself rejects the operation) if it is not.

Worktrees are stored out-of-repo, under the state store (`~/.local/share/pm-dispatch/state/projects/<project>/worktrees/checkouts/<slug>`), not inside the repo — so they never show up in `git status`, don't need a `.gitignore` entry, and survive `git clean`. The registry (manifest) resolves to the **same partition** whether `pmctl worktree` is invoked from the primary checkout or from inside one of the linked worktrees it created, so `pmctl worktree list` always shows the full picture regardless of which checkout you run it from. Manifest writes (`create` appending, `remove`/`gc` removing entries) are serialized under a single lock and always commit against the manifest's current on-disk state, not a snapshot taken earlier — running `pmctl worktree create` concurrently with `remove`/`gc` from another shell or process is safe.

## When to use

- You're mid-way through one ticket and need to start a second one without stashing/committing WIP just to switch branches.
- You want to run a long build/test in one branch's worktree while continuing to edit another branch.
- You need a disposable checkout of a branch (e.g. to inspect an old PR) without disturbing your primary working tree.

## Example

```sh
pmctl worktree create feat/my-feature
pmctl worktree list
pmctl worktree remove feat/my-feature
```

## Usage

```
pmctl worktree create <branch> [--from <base-branch>] [--name <slug>] [--cd <work_dir>]
pmctl worktree list [--cd <work_dir>] [--json]
pmctl worktree remove <name|branch> [--force] [--cd <work_dir>]
pmctl worktree gc [--dry-run] [--merged] [--max-age-days D] [--force] [--cd <work_dir>]
```

- `create <branch>` — attaches an existing local branch, or creates a new one off the current `HEAD` if it doesn't exist yet. Pass `--from <base-branch>` to create the new branch off a specific base instead of `HEAD`. Prints the new worktree's absolute path on success — capture it if you need to `cd` into it.
- `create --name <slug>` — override the manifest slug (defaults to the branch name with `/` replaced by `-`). Two worktrees cannot share a slug; `create` fails rather than silently overwriting an existing one.
- `list` — table of registered worktrees (`SLUG`, `BRANCH`, `PATH`). Add `--json` for a machine-readable array (each entry: `slug`, `branch`, `path`, `created_ts`).
- `remove <name|branch>` — matches by slug or by branch name. Fails if the worktree has uncommitted changes; pass `--force` to discard them and remove anyway. **`--force` is destructive** — it discards uncommitted work in that worktree with no recovery path, so confirm you don't need those changes before passing it.
- `gc` — reconciles the manifest against actual git/filesystem state: drops entries whose directory was removed manually (e.g. `rm -rf`) or that git no longer tracks (these are never destructive — the worktree is already gone or already untracked, so `gc` only cleans up the leftover manifest entry). Add `--merged` to also remove worktrees whose branch is fully merged, or `--max-age-days N` to remove entries older than N days — by default `gc` will *not* remove a merged/aged worktree that still has uncommitted changes (same dirty-worktree protection as plain `remove` without `--force`); it prints a `skipping ... has uncommitted changes` line and keeps the manifest entry instead. Pass `gc --force` to override that protection and force-remove merged/aged worktrees even when dirty — treat it with the same caution as `remove --force`. `--merged` is evaluated against the primary checkout's branch, not whichever worktree you happen to run `gc` from, so running `gc --merged` from inside a linked worktree never mistakes "merged into itself" for "safe to delete". Always run with `--dry-run` first to see what would be removed before running for real.

All four subcommands accept `--cd <work_dir>` to target a repo other than the current directory (same convention as `pmctl artifacts`/`pmctl dispatch`).

## Typical workflow

1. `pmctl worktree create feat/my-feature` — creates the worktree and prints its path.
2. `cd <printed path>` and work there like a normal checkout: `git add`, `git commit`, push, open a PR — all git operations behave exactly as in a regular checkout, because a linked worktree shares the same object store and refs as the primary checkout.
3. When the ticket's PR is merged (or abandoned), `pmctl worktree remove feat/my-feature` from either the primary checkout or from inside the worktree itself.
4. Periodically run `pmctl worktree gc --dry-run --merged` to spot worktrees left behind for already-merged branches, then `pmctl worktree gc --merged` to clean them up.

## Cleanup and orphan recovery

If a worktree directory is deleted directly (`rm -rf` instead of `pmctl worktree remove`), git and the manifest both still reference it. Run `pmctl worktree gc` — it detects the missing path, removes the stale manifest entry, and runs `git worktree prune` so `git worktree list` stays in sync too. By default `gc` never discards a directory that still exists and has uncommitted changes: for `--merged`/`--max-age-days` matches it attempts a plain (non-forced) removal and skips + reports any that turn out dirty, exactly like `remove` without `--force`. Only entries that are already gone or already untracked by git are removed unconditionally, since there is nothing live left to lose. Pass `gc --force` if you want merged/aged dirty worktrees discarded anyway.

## Out of scope

This tool does not touch the `--parallel` PR gate's reviewer-isolation logic (`scripts/pr-gate.sh`) — that is a separate integration tracked independently. `pmctl worktree` is a general-purpose utility for any parallel branch work, not specific to the gate.
Loading