xtrm-tools 0.5.29 → 0.5.30
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +19 -3
- package/README.md +13 -37
- package/cli/dist/index.cjs +311 -161
- package/cli/dist/index.cjs.map +1 -1
- package/cli/package.json +1 -1
- package/config/instructions/agents-top.md +1 -1
- package/config/instructions/claude-top.md +1 -1
- package/config/pi/extensions/beads/index.ts +68 -7
- package/config/pi/extensions/core/guard-rules.ts +0 -2
- package/config/pi/extensions/custom-footer/index.ts +5 -6
- package/hooks/beads-claim-sync.mjs +18 -6
- package/hooks/beads-gate-messages.mjs +5 -2
- package/hooks/beads-memory-gate.mjs +20 -7
- package/hooks/statusline.mjs +44 -8
- package/package.json +3 -2
- package/plugins/xtrm-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/xtrm-tools/hooks/beads-claim-sync.mjs +18 -6
- package/plugins/xtrm-tools/hooks/beads-gate-messages.mjs +5 -2
- package/plugins/xtrm-tools/hooks/beads-memory-gate.mjs +20 -7
- package/plugins/xtrm-tools/hooks/statusline.mjs +44 -8
- package/plugins/xtrm-tools/skills/sync-docs/SKILL.md +57 -2
- package/plugins/xtrm-tools/skills/sync-docs/scripts/drift_detector.py +1 -1
- package/plugins/xtrm-tools/skills/sync-docs/scripts/validate_metadata.py +1 -1
- package/plugins/xtrm-tools/skills/xt-end/SKILL.md +4 -4
- package/plugins/xtrm-tools/skills/xt-merge/SKILL.md +190 -0
- package/skills/sync-docs/SKILL.md +57 -2
- package/skills/sync-docs/scripts/drift_detector.py +1 -1
- package/skills/sync-docs/scripts/validate_metadata.py +1 -1
- package/skills/xt-end/SKILL.md +4 -4
- package/skills/xt-merge/SKILL.md +190 -0
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
docs-only drift detection on README.md, CHANGELOG.md, and docs/ — creating
|
|
9
9
|
missing focused files instead of a monolithic README.
|
|
10
10
|
gemini-command: sync-docs
|
|
11
|
-
version: 1.
|
|
11
|
+
version: 1.2.0
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# sync-docs
|
|
@@ -33,7 +33,7 @@ Phase 5: Validate — schema-check all docs/
|
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
# Global install
|
|
36
|
-
python3 "$HOME/.
|
|
36
|
+
python3 "$HOME/.agents/skills/sync-docs/scripts/context_gatherer.py" [--since=30]
|
|
37
37
|
|
|
38
38
|
# From repository
|
|
39
39
|
python3 "skills/sync-docs/scripts/context_gatherer.py" [--since=30]
|
|
@@ -149,7 +149,62 @@ python3 "skills/sync-docs/scripts/drift_detector.py" scan --since 30
|
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
152
|
+
## Frontmatter Schema
|
|
153
|
+
|
|
154
|
+
All `docs/*.md` files require valid YAML frontmatter. Scripts only validate `docs/*.md` (not subdirectories).
|
|
155
|
+
|
|
156
|
+
### Required Fields
|
|
157
|
+
|
|
158
|
+
| Field | Format | Example |
|
|
159
|
+
|-------|--------|---------|
|
|
160
|
+
| `title` | string (quote if contains colon) | `"Session-Flow: Pi Parity"` |
|
|
161
|
+
| `scope` | string | `hooks` |
|
|
162
|
+
| `category` | enum (see below) | `reference` |
|
|
163
|
+
| `version` | semver | `1.0.0` |
|
|
164
|
+
| `updated` | date | `2026-03-22` |
|
|
165
|
+
|
|
166
|
+
### Valid Categories
|
|
167
|
+
|
|
168
|
+
Only these values pass validation:
|
|
169
|
+
|
|
170
|
+
| Category | Use for |
|
|
171
|
+
|----------|---------|
|
|
172
|
+
| `api` | API documentation |
|
|
173
|
+
| `architecture` | System design, architecture decisions |
|
|
174
|
+
| `guide` | How-to guides, tutorials |
|
|
175
|
+
| `overview` | High-level introductions |
|
|
176
|
+
| `plan` | Planning documents, roadmaps |
|
|
177
|
+
| `reference` | Reference documentation |
|
|
178
|
+
|
|
179
|
+
**Invalid**: `roadmap`, `deprecated`, `complete` — will fail validation.
|
|
180
|
+
|
|
181
|
+
### YAML Quoting
|
|
182
|
+
|
|
183
|
+
Titles with special characters (colons, quotes) must be quoted:
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
# ✅ Correct
|
|
187
|
+
title: "Session-Flow: Pi Parity"
|
|
188
|
+
title: "What's New in v2.0"
|
|
189
|
+
|
|
190
|
+
# ❌ Incorrect — YAML parse error
|
|
191
|
+
title: Session-Flow: Pi Parity
|
|
192
|
+
title: What's New in v2.0
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Optional Fields
|
|
196
|
+
|
|
197
|
+
| Field | Format | Use |
|
|
198
|
+
|-------|--------|-----|
|
|
199
|
+
| `description` | string (quoted) | Brief summary |
|
|
200
|
+
| `source_of_truth_for` | list of globs | Link to code areas |
|
|
201
|
+
| `synced_at` | git hash | Drift checkpoint |
|
|
202
|
+
| `domain` | list of tags | Categorization |
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
152
206
|
## docs/ as SSOT
|
|
153
207
|
|
|
154
208
|
`docs/` is the only source of truth for project documentation in this workflow.
|
|
209
|
+
Scripts validate `docs/*.md` only — subdirectories (`docs/plans/`, `docs/reference/`) are ignored.
|
|
155
210
|
Use frontmatter (`source_of_truth_for`) to link docs pages to code areas and detect drift.
|
|
@@ -42,7 +42,7 @@ def get_docs_files(project_root: Path) -> list[Path]:
|
|
|
42
42
|
docs_dir = project_root / "docs"
|
|
43
43
|
if not docs_dir.exists():
|
|
44
44
|
return []
|
|
45
|
-
return sorted(docs_dir.
|
|
45
|
+
return sorted(docs_dir.glob("*.md"))
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def extract_frontmatter(content: str) -> dict[str, Any]:
|
|
@@ -60,7 +60,7 @@ xt end
|
|
|
60
60
|
|
|
61
61
|
### If it succeeds
|
|
62
62
|
You'll see:
|
|
63
|
-
- ✓ Rebased onto origin
|
|
63
|
+
- ✓ Rebased onto origin/<default-branch>
|
|
64
64
|
- ✓ Pushed branch
|
|
65
65
|
- ✓ PR created: <url>
|
|
66
66
|
- ✓ Linked PR to N issue(s)
|
|
@@ -84,7 +84,7 @@ Then re-run `xt end`. If the conflicts are complex, explain what each file confl
|
|
|
84
84
|
|
|
85
85
|
Usually a stale remote ref. Try:
|
|
86
86
|
```bash
|
|
87
|
-
git fetch origin
|
|
87
|
+
git fetch origin
|
|
88
88
|
xt end
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -117,9 +117,9 @@ If the worktree was removed: confirm that too.
|
|
|
117
117
|
|
|
118
118
|
## Edge cases
|
|
119
119
|
|
|
120
|
-
**Already on main branch**: `xt end` will error — you're not in an xt session. Don't run it from
|
|
120
|
+
**Already on main/master branch**: `xt end` will error — you're not in an xt session. Don't run it from the default branch.
|
|
121
121
|
|
|
122
|
-
**No commits yet on branch**: The PR will have no changes. This usually means something went wrong earlier. Verify with `git log origin
|
|
122
|
+
**No commits yet on branch**: The PR will have no changes. This usually means something went wrong earlier. Verify with `git log origin/<default-branch>..HEAD` (where default-branch is main or master).
|
|
123
123
|
|
|
124
124
|
**`gh` CLI not authenticated**: `gh pr create` will fail. Fix: `gh auth login`, then re-run `xt end`.
|
|
125
125
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xt-merge
|
|
3
|
+
description: |
|
|
4
|
+
Merges queued PRs from xt worktree sessions in the correct order (FIFO), maintaining linear
|
|
5
|
+
history by rebasing remaining PRs after each merge. Use this skill whenever the user has
|
|
6
|
+
multiple open PRs from xt worktrees, asks to "merge my PRs", "process the PR queue",
|
|
7
|
+
"drain the queue", "merge worktree branches", or says "what PRs do I have open".
|
|
8
|
+
Also activate after any xt-end completion when other PRs are already open, or when the
|
|
9
|
+
user asks "can I merge yet" or "is CI green". Handles the full sequence: list → sort →
|
|
10
|
+
CI check → merge oldest → rebase cascade → repeat until queue is empty.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# merge-prs — Worktree PR Merge Workflow
|
|
14
|
+
|
|
15
|
+
You are draining a queue of PRs created by `xt end` from multiple worktree sessions.
|
|
16
|
+
The key constraint is **ordering**: merge in FIFO order and rebase the remaining PRs
|
|
17
|
+
after each merge. Work through the stages below in sequence.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Why FIFO and why the rebase cascade matters
|
|
22
|
+
|
|
23
|
+
When `xt end` runs, it rebases the worktree branch onto `origin/main` at that moment
|
|
24
|
+
and pushes. If you ran three sessions in sequence:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Session A finishes at t=1 → xt/feature-a rebased onto main@sha1
|
|
28
|
+
Session B finishes at t=2 → xt/feature-b rebased onto main@sha2 (sha2 >= sha1)
|
|
29
|
+
Session C finishes at t=3 → xt/feature-c rebased onto main@sha3 (sha3 >= sha2)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
After merging A, main advances to sha4. Branch B is now based on sha2 — it still
|
|
33
|
+
compiles and CI passes, but it doesn't include A's changes. You must rebase B onto
|
|
34
|
+
sha4 before merging, so the history stays linear and B's CI reflects the real state
|
|
35
|
+
of main + B.
|
|
36
|
+
|
|
37
|
+
**FIFO = merge the oldest-created PR first.** The older the PR, the smaller the
|
|
38
|
+
rebase cascade it triggers in subsequent branches. Merging out of order means
|
|
39
|
+
you're rebasing more than necessary and risk conflicts that wouldn't have existed.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Stage 1 — Build the queue
|
|
44
|
+
|
|
45
|
+
List all open PRs from xt worktree branches:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
gh pr list --state open --json number,title,headRefName,createdAt,isDraft \
|
|
49
|
+
--jq '.[] | select(.headRefName | startswith("xt/")) | [.number, .createdAt, .headRefName, .title] | @tsv' \
|
|
50
|
+
| sort -k2
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This sorts by creation time. The top row is the **head of the queue** — merge it first.
|
|
54
|
+
|
|
55
|
+
If there are draft PRs in the list, skip them. Drafts are not ready to merge.
|
|
56
|
+
|
|
57
|
+
Present the sorted queue to the user before proceeding:
|
|
58
|
+
```
|
|
59
|
+
Queue (oldest → newest):
|
|
60
|
+
#42 xt/fix-auth-gate "Fix beads edit gate claim check" 2026-03-21 10:14
|
|
61
|
+
#45 xt/add-release-script "Add release script for npm publish" 2026-03-21 14:32
|
|
62
|
+
#47 xt/default-branch "Detect default branch in xt end" 2026-03-22 09:11
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Stage 2 — Check CI on the head PR
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
gh pr checks <number>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Wait for all checks to pass. If CI is still running, tell the user and pause — don't
|
|
74
|
+
merge a PR with pending or failing checks.
|
|
75
|
+
|
|
76
|
+
If CI is failing:
|
|
77
|
+
- Show the failing check names and link to the run
|
|
78
|
+
- Do NOT proceed with the merge
|
|
79
|
+
- Let the user decide: fix the issue in the worktree (may already be deleted), push a
|
|
80
|
+
fixup commit directly to the branch, or close the PR
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Stage 3 — Merge the head PR
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
gh pr merge <number> --rebase --delete-branch
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use `--rebase` (not `--squash` or `--merge`) to keep linear history and preserve
|
|
91
|
+
individual commits from the session. Use `--delete-branch` to clean up the remote branch.
|
|
92
|
+
|
|
93
|
+
After merge, confirm main advanced:
|
|
94
|
+
```bash
|
|
95
|
+
git fetch origin
|
|
96
|
+
git log origin/main --oneline -3
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Stage 4 — Rebase cascade (all remaining PRs)
|
|
102
|
+
|
|
103
|
+
For every remaining PR in the queue, rebase its branch onto the new main:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
git fetch origin main
|
|
107
|
+
git checkout xt/<branch>
|
|
108
|
+
git rebase origin/main
|
|
109
|
+
git push origin xt/<branch> --force-with-lease
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Repeat for each remaining branch. Do them in queue order (oldest next).
|
|
113
|
+
|
|
114
|
+
After pushing, GitHub will re-trigger CI on each rebased PR. You don't need to wait
|
|
115
|
+
for CI here — the rebase just gets the branches current. CI will run in parallel.
|
|
116
|
+
|
|
117
|
+
### If rebase conflicts occur
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git status # shows conflicted files
|
|
121
|
+
# edit each file to resolve <<<< ==== >>>> markers
|
|
122
|
+
git add <resolved-files>
|
|
123
|
+
git rebase --continue
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Conflicts mean two sessions touched the same file. Resolve carefully:
|
|
127
|
+
- Keep both changes if they're in different parts of the file
|
|
128
|
+
- If they overlap, understand what each session was doing and merge the intent
|
|
129
|
+
- When unsure, call the user in to review before continuing
|
|
130
|
+
|
|
131
|
+
After resolving, push with `--force-with-lease` and move to the next branch.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Stage 5 — Repeat
|
|
136
|
+
|
|
137
|
+
Go back to Stage 2 with the new head of the queue. Check CI, merge, rebase cascade,
|
|
138
|
+
repeat until the queue is empty.
|
|
139
|
+
|
|
140
|
+
The full loop:
|
|
141
|
+
```
|
|
142
|
+
while queue not empty:
|
|
143
|
+
wait for CI green on head PR
|
|
144
|
+
merge head PR (--rebase --delete-branch)
|
|
145
|
+
rebase all remaining PRs onto new main
|
|
146
|
+
push each (--force-with-lease)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Stage 6 — Done
|
|
152
|
+
|
|
153
|
+
When the queue is empty:
|
|
154
|
+
```bash
|
|
155
|
+
gh pr list --state open
|
|
156
|
+
git log origin/main --oneline -5
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Confirm no open xt/ PRs remain and show the user the final state of main.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Edge cases
|
|
164
|
+
|
|
165
|
+
**PR was already merged**: `gh pr merge` will error. Skip it and continue.
|
|
166
|
+
|
|
167
|
+
**Branch was deleted** (worktree cleaned up by `xt end --keep`): The remote branch
|
|
168
|
+
still exists (pushed by `xt end`). The local branch may not. Check out from remote:
|
|
169
|
+
```bash
|
|
170
|
+
git fetch origin
|
|
171
|
+
git checkout -b xt/<branch> origin/xt/<branch>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**CI never triggers after rebase push**: GitHub sometimes needs a nudge. Close and
|
|
175
|
+
re-open the PR, or push an empty commit:
|
|
176
|
+
```bash
|
|
177
|
+
git commit --allow-empty -m "trigger CI"
|
|
178
|
+
git push origin xt/<branch>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Dependent sessions** (B was intentionally built on A's work): If session B was
|
|
182
|
+
started from inside session A's worktree rather than from main, B's branch already
|
|
183
|
+
contains A's commits. In this case B will rebase cleanly onto main after A merges —
|
|
184
|
+
its commits are a superset. No special handling needed; the rebase just eliminates
|
|
185
|
+
the duplicate commits.
|
|
186
|
+
|
|
187
|
+
**Multiple conflicts across many PRs**: If the cascade produces conflicts in several
|
|
188
|
+
branches, tackle them one at a time in queue order. Don't try to resolve all of them
|
|
189
|
+
before pushing any — push each one as you resolve it so CI starts running in parallel
|
|
190
|
+
while you work on the next.
|
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
docs-only drift detection on README.md, CHANGELOG.md, and docs/ — creating
|
|
9
9
|
missing focused files instead of a monolithic README.
|
|
10
10
|
gemini-command: sync-docs
|
|
11
|
-
version: 1.
|
|
11
|
+
version: 1.2.0
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# sync-docs
|
|
@@ -33,7 +33,7 @@ Phase 5: Validate — schema-check all docs/
|
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
# Global install
|
|
36
|
-
python3 "$HOME/.
|
|
36
|
+
python3 "$HOME/.agents/skills/sync-docs/scripts/context_gatherer.py" [--since=30]
|
|
37
37
|
|
|
38
38
|
# From repository
|
|
39
39
|
python3 "skills/sync-docs/scripts/context_gatherer.py" [--since=30]
|
|
@@ -149,7 +149,62 @@ python3 "skills/sync-docs/scripts/drift_detector.py" scan --since 30
|
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
152
|
+
## Frontmatter Schema
|
|
153
|
+
|
|
154
|
+
All `docs/*.md` files require valid YAML frontmatter. Scripts only validate `docs/*.md` (not subdirectories).
|
|
155
|
+
|
|
156
|
+
### Required Fields
|
|
157
|
+
|
|
158
|
+
| Field | Format | Example |
|
|
159
|
+
|-------|--------|---------|
|
|
160
|
+
| `title` | string (quote if contains colon) | `"Session-Flow: Pi Parity"` |
|
|
161
|
+
| `scope` | string | `hooks` |
|
|
162
|
+
| `category` | enum (see below) | `reference` |
|
|
163
|
+
| `version` | semver | `1.0.0` |
|
|
164
|
+
| `updated` | date | `2026-03-22` |
|
|
165
|
+
|
|
166
|
+
### Valid Categories
|
|
167
|
+
|
|
168
|
+
Only these values pass validation:
|
|
169
|
+
|
|
170
|
+
| Category | Use for |
|
|
171
|
+
|----------|---------|
|
|
172
|
+
| `api` | API documentation |
|
|
173
|
+
| `architecture` | System design, architecture decisions |
|
|
174
|
+
| `guide` | How-to guides, tutorials |
|
|
175
|
+
| `overview` | High-level introductions |
|
|
176
|
+
| `plan` | Planning documents, roadmaps |
|
|
177
|
+
| `reference` | Reference documentation |
|
|
178
|
+
|
|
179
|
+
**Invalid**: `roadmap`, `deprecated`, `complete` — will fail validation.
|
|
180
|
+
|
|
181
|
+
### YAML Quoting
|
|
182
|
+
|
|
183
|
+
Titles with special characters (colons, quotes) must be quoted:
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
# ✅ Correct
|
|
187
|
+
title: "Session-Flow: Pi Parity"
|
|
188
|
+
title: "What's New in v2.0"
|
|
189
|
+
|
|
190
|
+
# ❌ Incorrect — YAML parse error
|
|
191
|
+
title: Session-Flow: Pi Parity
|
|
192
|
+
title: What's New in v2.0
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Optional Fields
|
|
196
|
+
|
|
197
|
+
| Field | Format | Use |
|
|
198
|
+
|-------|--------|-----|
|
|
199
|
+
| `description` | string (quoted) | Brief summary |
|
|
200
|
+
| `source_of_truth_for` | list of globs | Link to code areas |
|
|
201
|
+
| `synced_at` | git hash | Drift checkpoint |
|
|
202
|
+
| `domain` | list of tags | Categorization |
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
152
206
|
## docs/ as SSOT
|
|
153
207
|
|
|
154
208
|
`docs/` is the only source of truth for project documentation in this workflow.
|
|
209
|
+
Scripts validate `docs/*.md` only — subdirectories (`docs/plans/`, `docs/reference/`) are ignored.
|
|
155
210
|
Use frontmatter (`source_of_truth_for`) to link docs pages to code areas and detect drift.
|
|
@@ -42,7 +42,7 @@ def get_docs_files(project_root: Path) -> list[Path]:
|
|
|
42
42
|
docs_dir = project_root / "docs"
|
|
43
43
|
if not docs_dir.exists():
|
|
44
44
|
return []
|
|
45
|
-
return sorted(docs_dir.
|
|
45
|
+
return sorted(docs_dir.glob("*.md"))
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def extract_frontmatter(content: str) -> dict[str, Any]:
|
package/skills/xt-end/SKILL.md
CHANGED
|
@@ -60,7 +60,7 @@ xt end
|
|
|
60
60
|
|
|
61
61
|
### If it succeeds
|
|
62
62
|
You'll see:
|
|
63
|
-
- ✓ Rebased onto origin
|
|
63
|
+
- ✓ Rebased onto origin/<default-branch>
|
|
64
64
|
- ✓ Pushed branch
|
|
65
65
|
- ✓ PR created: <url>
|
|
66
66
|
- ✓ Linked PR to N issue(s)
|
|
@@ -84,7 +84,7 @@ Then re-run `xt end`. If the conflicts are complex, explain what each file confl
|
|
|
84
84
|
|
|
85
85
|
Usually a stale remote ref. Try:
|
|
86
86
|
```bash
|
|
87
|
-
git fetch origin
|
|
87
|
+
git fetch origin
|
|
88
88
|
xt end
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -117,9 +117,9 @@ If the worktree was removed: confirm that too.
|
|
|
117
117
|
|
|
118
118
|
## Edge cases
|
|
119
119
|
|
|
120
|
-
**Already on main branch**: `xt end` will error — you're not in an xt session. Don't run it from
|
|
120
|
+
**Already on main/master branch**: `xt end` will error — you're not in an xt session. Don't run it from the default branch.
|
|
121
121
|
|
|
122
|
-
**No commits yet on branch**: The PR will have no changes. This usually means something went wrong earlier. Verify with `git log origin
|
|
122
|
+
**No commits yet on branch**: The PR will have no changes. This usually means something went wrong earlier. Verify with `git log origin/<default-branch>..HEAD` (where default-branch is main or master).
|
|
123
123
|
|
|
124
124
|
**`gh` CLI not authenticated**: `gh pr create` will fail. Fix: `gh auth login`, then re-run `xt end`.
|
|
125
125
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: xt-merge
|
|
3
|
+
description: |
|
|
4
|
+
Merges queued PRs from xt worktree sessions in the correct order (FIFO), maintaining linear
|
|
5
|
+
history by rebasing remaining PRs after each merge. Use this skill whenever the user has
|
|
6
|
+
multiple open PRs from xt worktrees, asks to "merge my PRs", "process the PR queue",
|
|
7
|
+
"drain the queue", "merge worktree branches", or says "what PRs do I have open".
|
|
8
|
+
Also activate after any xt-end completion when other PRs are already open, or when the
|
|
9
|
+
user asks "can I merge yet" or "is CI green". Handles the full sequence: list → sort →
|
|
10
|
+
CI check → merge oldest → rebase cascade → repeat until queue is empty.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# merge-prs — Worktree PR Merge Workflow
|
|
14
|
+
|
|
15
|
+
You are draining a queue of PRs created by `xt end` from multiple worktree sessions.
|
|
16
|
+
The key constraint is **ordering**: merge in FIFO order and rebase the remaining PRs
|
|
17
|
+
after each merge. Work through the stages below in sequence.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Why FIFO and why the rebase cascade matters
|
|
22
|
+
|
|
23
|
+
When `xt end` runs, it rebases the worktree branch onto `origin/main` at that moment
|
|
24
|
+
and pushes. If you ran three sessions in sequence:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Session A finishes at t=1 → xt/feature-a rebased onto main@sha1
|
|
28
|
+
Session B finishes at t=2 → xt/feature-b rebased onto main@sha2 (sha2 >= sha1)
|
|
29
|
+
Session C finishes at t=3 → xt/feature-c rebased onto main@sha3 (sha3 >= sha2)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
After merging A, main advances to sha4. Branch B is now based on sha2 — it still
|
|
33
|
+
compiles and CI passes, but it doesn't include A's changes. You must rebase B onto
|
|
34
|
+
sha4 before merging, so the history stays linear and B's CI reflects the real state
|
|
35
|
+
of main + B.
|
|
36
|
+
|
|
37
|
+
**FIFO = merge the oldest-created PR first.** The older the PR, the smaller the
|
|
38
|
+
rebase cascade it triggers in subsequent branches. Merging out of order means
|
|
39
|
+
you're rebasing more than necessary and risk conflicts that wouldn't have existed.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Stage 1 — Build the queue
|
|
44
|
+
|
|
45
|
+
List all open PRs from xt worktree branches:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
gh pr list --state open --json number,title,headRefName,createdAt,isDraft \
|
|
49
|
+
--jq '.[] | select(.headRefName | startswith("xt/")) | [.number, .createdAt, .headRefName, .title] | @tsv' \
|
|
50
|
+
| sort -k2
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This sorts by creation time. The top row is the **head of the queue** — merge it first.
|
|
54
|
+
|
|
55
|
+
If there are draft PRs in the list, skip them. Drafts are not ready to merge.
|
|
56
|
+
|
|
57
|
+
Present the sorted queue to the user before proceeding:
|
|
58
|
+
```
|
|
59
|
+
Queue (oldest → newest):
|
|
60
|
+
#42 xt/fix-auth-gate "Fix beads edit gate claim check" 2026-03-21 10:14
|
|
61
|
+
#45 xt/add-release-script "Add release script for npm publish" 2026-03-21 14:32
|
|
62
|
+
#47 xt/default-branch "Detect default branch in xt end" 2026-03-22 09:11
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Stage 2 — Check CI on the head PR
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
gh pr checks <number>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Wait for all checks to pass. If CI is still running, tell the user and pause — don't
|
|
74
|
+
merge a PR with pending or failing checks.
|
|
75
|
+
|
|
76
|
+
If CI is failing:
|
|
77
|
+
- Show the failing check names and link to the run
|
|
78
|
+
- Do NOT proceed with the merge
|
|
79
|
+
- Let the user decide: fix the issue in the worktree (may already be deleted), push a
|
|
80
|
+
fixup commit directly to the branch, or close the PR
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Stage 3 — Merge the head PR
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
gh pr merge <number> --rebase --delete-branch
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use `--rebase` (not `--squash` or `--merge`) to keep linear history and preserve
|
|
91
|
+
individual commits from the session. Use `--delete-branch` to clean up the remote branch.
|
|
92
|
+
|
|
93
|
+
After merge, confirm main advanced:
|
|
94
|
+
```bash
|
|
95
|
+
git fetch origin
|
|
96
|
+
git log origin/main --oneline -3
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Stage 4 — Rebase cascade (all remaining PRs)
|
|
102
|
+
|
|
103
|
+
For every remaining PR in the queue, rebase its branch onto the new main:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
git fetch origin main
|
|
107
|
+
git checkout xt/<branch>
|
|
108
|
+
git rebase origin/main
|
|
109
|
+
git push origin xt/<branch> --force-with-lease
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Repeat for each remaining branch. Do them in queue order (oldest next).
|
|
113
|
+
|
|
114
|
+
After pushing, GitHub will re-trigger CI on each rebased PR. You don't need to wait
|
|
115
|
+
for CI here — the rebase just gets the branches current. CI will run in parallel.
|
|
116
|
+
|
|
117
|
+
### If rebase conflicts occur
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git status # shows conflicted files
|
|
121
|
+
# edit each file to resolve <<<< ==== >>>> markers
|
|
122
|
+
git add <resolved-files>
|
|
123
|
+
git rebase --continue
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Conflicts mean two sessions touched the same file. Resolve carefully:
|
|
127
|
+
- Keep both changes if they're in different parts of the file
|
|
128
|
+
- If they overlap, understand what each session was doing and merge the intent
|
|
129
|
+
- When unsure, call the user in to review before continuing
|
|
130
|
+
|
|
131
|
+
After resolving, push with `--force-with-lease` and move to the next branch.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Stage 5 — Repeat
|
|
136
|
+
|
|
137
|
+
Go back to Stage 2 with the new head of the queue. Check CI, merge, rebase cascade,
|
|
138
|
+
repeat until the queue is empty.
|
|
139
|
+
|
|
140
|
+
The full loop:
|
|
141
|
+
```
|
|
142
|
+
while queue not empty:
|
|
143
|
+
wait for CI green on head PR
|
|
144
|
+
merge head PR (--rebase --delete-branch)
|
|
145
|
+
rebase all remaining PRs onto new main
|
|
146
|
+
push each (--force-with-lease)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Stage 6 — Done
|
|
152
|
+
|
|
153
|
+
When the queue is empty:
|
|
154
|
+
```bash
|
|
155
|
+
gh pr list --state open
|
|
156
|
+
git log origin/main --oneline -5
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Confirm no open xt/ PRs remain and show the user the final state of main.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Edge cases
|
|
164
|
+
|
|
165
|
+
**PR was already merged**: `gh pr merge` will error. Skip it and continue.
|
|
166
|
+
|
|
167
|
+
**Branch was deleted** (worktree cleaned up by `xt end --keep`): The remote branch
|
|
168
|
+
still exists (pushed by `xt end`). The local branch may not. Check out from remote:
|
|
169
|
+
```bash
|
|
170
|
+
git fetch origin
|
|
171
|
+
git checkout -b xt/<branch> origin/xt/<branch>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**CI never triggers after rebase push**: GitHub sometimes needs a nudge. Close and
|
|
175
|
+
re-open the PR, or push an empty commit:
|
|
176
|
+
```bash
|
|
177
|
+
git commit --allow-empty -m "trigger CI"
|
|
178
|
+
git push origin xt/<branch>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Dependent sessions** (B was intentionally built on A's work): If session B was
|
|
182
|
+
started from inside session A's worktree rather than from main, B's branch already
|
|
183
|
+
contains A's commits. In this case B will rebase cleanly onto main after A merges —
|
|
184
|
+
its commits are a superset. No special handling needed; the rebase just eliminates
|
|
185
|
+
the duplicate commits.
|
|
186
|
+
|
|
187
|
+
**Multiple conflicts across many PRs**: If the cascade produces conflicts in several
|
|
188
|
+
branches, tackle them one at a time in queue order. Don't try to resolve all of them
|
|
189
|
+
before pushing any — push each one as you resolve it so CI starts running in parallel
|
|
190
|
+
while you work on the next.
|