@reservine/dx 1.0.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/.claude-plugin/marketplace.json +22 -0
- package/README.md +303 -0
- package/bin/cli.ts +549 -0
- package/package.json +26 -0
- package/plugins/reservine-dx/.claude-plugin/plugin.json +8 -0
- package/plugins/reservine-dx/commands/cherry-pick-pr.md +221 -0
- package/plugins/reservine-dx/commands/cleanup.md +297 -0
- package/plugins/reservine-dx/commands/commit.md +118 -0
- package/plugins/reservine-dx/docker/worktree/docker-compose.isolated.template.yaml +144 -0
- package/plugins/reservine-dx/docker/worktree/seed-snapshot.sh +74 -0
- package/plugins/reservine-dx/scripts/_core.sh +330 -0
- package/plugins/reservine-dx/scripts/setup-worktree-be.sh +501 -0
- package/plugins/reservine-dx/scripts/setup-worktree-fe.sh +244 -0
- package/plugins/reservine-dx/scripts/setup-worktree.sh +59 -0
- package/plugins/reservine-dx/skills/cross-plan/SKILL.md +339 -0
- package/plugins/reservine-dx/skills/implement-plan/SKILL.md +512 -0
- package/plugins/reservine-dx/skills/implement-plan/references/plugin-contract.md +82 -0
- package/plugins/reservine-dx/skills/new-feature-planning/SKILL.md +544 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reservine-dx:cherry-pick-pr
|
|
3
|
+
description: Extract specific commits from the current branch into a clean PR against the base development branch
|
|
4
|
+
argument-hint: [commit hashes or description]
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Bash
|
|
8
|
+
- AskUserQuestion
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Cherry-Pick PR
|
|
12
|
+
|
|
13
|
+
Extract specific commits from the current branch into a clean PR against the base development branch using a temporary worktree.
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
Read project config before starting:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cat .claude/config 2>/dev/null
|
|
21
|
+
cat .claude/config.local 2>/dev/null
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Use `DEVELOPMENT_BRANCH` as `<base>` throughout this workflow.
|
|
25
|
+
Use `GITHUB_REPO` for PR URLs and `gh` commands.
|
|
26
|
+
|
|
27
|
+
If no config file exists, fall back to detection:
|
|
28
|
+
```bash
|
|
29
|
+
git rev-parse --verify origin/main &>/dev/null && echo "main" || echo "dev"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Workflow
|
|
33
|
+
|
|
34
|
+
### Step 1: Identify Commits
|
|
35
|
+
|
|
36
|
+
Ask the user which commits to cherry-pick, or infer from conversation context. Show candidates with:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git log --oneline <base>..HEAD
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Confirm the selected commits with the user before proceeding. Display them clearly:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Commits to cherry-pick:
|
|
46
|
+
abc1234 feat: add booking validation
|
|
47
|
+
def5678 fix: handle null employee
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Existing PR detection:** If the user references an existing PR (e.g., "into #281", "again into 281", "into PR 281"), store the PR number and look up its branch:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
gh pr view <pr-number> --json headRefName --jq '.headRefName'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
When an existing PR is detected:
|
|
57
|
+
- Use the returned branch name as `<branch-name>` throughout
|
|
58
|
+
- **Skip Step 2** (branch name already known)
|
|
59
|
+
- **Skip Step 7** (PR already exists)
|
|
60
|
+
|
|
61
|
+
### Step 2: Derive Branch Name
|
|
62
|
+
|
|
63
|
+
> **Skipped when targeting an existing PR** — the branch name comes from `gh pr view`.
|
|
64
|
+
|
|
65
|
+
Generate a branch name from the commit messages using `<type>/<short-description>` convention:
|
|
66
|
+
|
|
67
|
+
| Commit prefix | Branch type |
|
|
68
|
+
|---------------|-------------|
|
|
69
|
+
| `feat:` | `feat/` |
|
|
70
|
+
| `fix:` | `fix/` |
|
|
71
|
+
| `chore:` | `chore/` |
|
|
72
|
+
| `refactor:` | `refactor/` |
|
|
73
|
+
| `docs:` | `docs/` |
|
|
74
|
+
| Mixed types | Use the dominant type, or `chore/` as fallback |
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
- Single commit `feat: add SMS toggle` -> `feat/add-sms-toggle`
|
|
78
|
+
- Multiple commits about config -> `chore/config-system-updates`
|
|
79
|
+
|
|
80
|
+
Confirm the branch name with the user. They may want to adjust it.
|
|
81
|
+
|
|
82
|
+
### Step 3: Pre-flight Checks
|
|
83
|
+
|
|
84
|
+
**New PR flow:**
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Fetch latest base branch
|
|
88
|
+
git fetch origin <base>
|
|
89
|
+
|
|
90
|
+
# Check if branch already exists on remote
|
|
91
|
+
git ls-remote --heads origin <branch-name>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**If branch exists on remote:** Warn the user and ask how to proceed (rename branch, force-push, or abort).
|
|
95
|
+
|
|
96
|
+
**Existing PR flow:**
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Get branch name from existing PR (already done in Step 1)
|
|
100
|
+
gh pr view <pr-number> --json headRefName --jq '.headRefName'
|
|
101
|
+
|
|
102
|
+
# Fetch the PR branch
|
|
103
|
+
git fetch origin <branch-name>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Step 4: Create Worktree
|
|
107
|
+
|
|
108
|
+
**New PR flow:**
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
git worktree add .worktrees/<branch-name> origin/<base> -b <branch-name>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Existing PR flow** — check out the existing PR branch instead of creating from base:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git worktree add .worktrees/<branch-name> origin/<branch-name> -B <branch-name>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Step 5: Cherry-Pick
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cd .worktrees/<branch-name>
|
|
124
|
+
git cherry-pick <hash1> <hash2> ...
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**If conflicts occur:**
|
|
128
|
+
1. Stop immediately
|
|
129
|
+
2. Show the conflicted files: `git diff --name-only --diff-filter=U`
|
|
130
|
+
3. Show the conflict content for each file
|
|
131
|
+
4. Ask the user how to resolve
|
|
132
|
+
5. After resolution: `git add .` then `git cherry-pick --continue`
|
|
133
|
+
|
|
134
|
+
**If cherry-pick succeeds cleanly:** Continue to Step 6.
|
|
135
|
+
|
|
136
|
+
### Step 6: Push
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
cd .worktrees/<branch-name>
|
|
140
|
+
git push -u origin <branch-name>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Step 7: Create PR
|
|
144
|
+
|
|
145
|
+
> **Skipped when targeting an existing PR** — the PR already exists, no need to create one.
|
|
146
|
+
|
|
147
|
+
**New PR flow only:**
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
cd .worktrees/<branch-name>
|
|
151
|
+
gh pr create --base <base> --title "<title>" --body "$(cat <<'EOF'
|
|
152
|
+
## Summary
|
|
153
|
+
<bullet points summarizing the cherry-picked changes>
|
|
154
|
+
|
|
155
|
+
## Cherry-picked from
|
|
156
|
+
Branch: `<source-branch>`
|
|
157
|
+
Commits:
|
|
158
|
+
- `<hash1>` <message1>
|
|
159
|
+
- `<hash2>` <message2>
|
|
160
|
+
|
|
161
|
+
## Test plan
|
|
162
|
+
- [ ] <relevant test items based on the changes>
|
|
163
|
+
EOF
|
|
164
|
+
)"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**PR title:** Derive from commits. For single commit, use the commit message. For multiple, write a concise summary.
|
|
168
|
+
|
|
169
|
+
### Step 8: Cleanup
|
|
170
|
+
|
|
171
|
+
**Always run cleanup**, even if a previous step failed:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Return to the original repo root first
|
|
175
|
+
cd <original-repo-root>
|
|
176
|
+
git worktree remove .worktrees/<branch-name>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
If worktree removal fails (e.g., uncommitted changes from conflict resolution):
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
git worktree remove --force .worktrees/<branch-name>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Step 9: Report
|
|
186
|
+
|
|
187
|
+
Return the PR URL and a summary. Always include a full clickable URL.
|
|
188
|
+
|
|
189
|
+
**New PR** (`gh pr create` returns the URL):
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
PR created: <url>
|
|
193
|
+
Branch: <branch-name>
|
|
194
|
+
Commits: <count> cherry-picked from <source-branch>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Existing PR** (construct the URL from `GITHUB_REPO` and the PR number):
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
PR updated: https://github.com/<GITHUB_REPO>/pull/<pr-number>
|
|
201
|
+
Branch: <branch-name>
|
|
202
|
+
Commits: <count> cherry-picked from <source-branch>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Error Recovery
|
|
206
|
+
|
|
207
|
+
| Failure point | Recovery |
|
|
208
|
+
|---------------|----------|
|
|
209
|
+
| Worktree creation fails (branch exists locally) | `git branch -D <branch-name>` then retry |
|
|
210
|
+
| Cherry-pick conflicts | Show conflicts, ask user, resolve, `--continue` |
|
|
211
|
+
| Push fails (branch exists on remote) | Ask user: rename, force-push, or abort |
|
|
212
|
+
| PR creation fails | Show error, suggest manual creation with `gh pr create` |
|
|
213
|
+
| Any failure | Always clean up the worktree before stopping |
|
|
214
|
+
|
|
215
|
+
## Important Notes
|
|
216
|
+
|
|
217
|
+
- **Never force-push without explicit user approval**
|
|
218
|
+
- **Always return to the original repo root** before worktree cleanup
|
|
219
|
+
- The worktree lives in `.worktrees/` (gitignored)
|
|
220
|
+
- Cherry-pick preserves original commit messages and authorship
|
|
221
|
+
- If the user is on a long-running branch, remind them that the PR targets `<base>` (the main development branch)
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reservine-dx:cleanup
|
|
3
|
+
description: >
|
|
4
|
+
Clean up all artifacts for a finished feature: worktrees (FE + BE), running processes
|
|
5
|
+
(nx serve, Docker stacks), local and remote branches. Verifies PRs are merged before
|
|
6
|
+
deleting. Auto-discovers sibling worktrees across repos.
|
|
7
|
+
Triggers: "cleanup", "clean worktree", "remove worktree", "tear down", "feature done".
|
|
8
|
+
argument-hint: "[branch-name]"
|
|
9
|
+
allowed-tools:
|
|
10
|
+
- Bash
|
|
11
|
+
- Read
|
|
12
|
+
- AskUserQuestion
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Feature Cleanup
|
|
16
|
+
|
|
17
|
+
Clean up all artifacts for a finished feature branch across both FE and BE repos.
|
|
18
|
+
|
|
19
|
+
## Step 1: Detect Branch & Resolve Repos
|
|
20
|
+
|
|
21
|
+
1. Read `.claude/config` + `.claude/config.local`:
|
|
22
|
+
```bash
|
|
23
|
+
cat .claude/config 2>/dev/null
|
|
24
|
+
cat .claude/config.local 2>/dev/null
|
|
25
|
+
```
|
|
26
|
+
Extract: `GITHUB_REPO`, `DEVELOPMENT_BRANCH`, `BACKEND_DIR`/`FRONTEND_DIR`
|
|
27
|
+
|
|
28
|
+
2. Auto-detect current repo:
|
|
29
|
+
```bash
|
|
30
|
+
if [[ -f "angular.json" ]] || [[ -f "nx.json" ]]; then
|
|
31
|
+
CURRENT_REPO="FE"
|
|
32
|
+
else
|
|
33
|
+
CURRENT_REPO="BE"
|
|
34
|
+
fi
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. Resolve both repo paths (always need both for cross-repo cleanup):
|
|
38
|
+
- FE: from `FRONTEND_DIR` or `FRONTEND_DIR_ABSOLUTE` in config
|
|
39
|
+
- BE: from `BACKEND_DIR` or `BACKEND_DIR_ABSOLUTE` in config
|
|
40
|
+
- Resolve `FE_GITHUB_REPO` and `BE_GITHUB_REPO` from each repo's `.claude/config`
|
|
41
|
+
- Fallbacks: `LEFTEQ/reservine` (FE), `genesiscz/ReservineBack` (BE)
|
|
42
|
+
|
|
43
|
+
4. Determine the target branch:
|
|
44
|
+
- **If `$ARGUMENTS` provided** → use as branch name
|
|
45
|
+
- **Else if current dir is inside `.worktrees/`** → extract branch from path
|
|
46
|
+
- **Else if on a feature branch** (not main/dev/master/release) → use current branch
|
|
47
|
+
- **Else** → list existing worktrees and `AskUserQuestion`:
|
|
48
|
+
```bash
|
|
49
|
+
echo "=== FE worktrees ===" && git -C <FE_DIR> worktree list
|
|
50
|
+
echo "=== BE worktrees ===" && git -C <BE_DIR> worktree list
|
|
51
|
+
```
|
|
52
|
+
Present as options: the branch names from all worktrees (deduplicated).
|
|
53
|
+
|
|
54
|
+
## Step 2: Discover All Artifacts
|
|
55
|
+
|
|
56
|
+
Run all discovery in parallel (single bash block), collecting results:
|
|
57
|
+
|
|
58
|
+
### FE artifacts:
|
|
59
|
+
```bash
|
|
60
|
+
FE_DIR="<resolved FE path>"
|
|
61
|
+
|
|
62
|
+
# Worktree
|
|
63
|
+
FE_WORKTREE=$(git -C "$FE_DIR" worktree list | grep "<branch>" | awk '{print $1}')
|
|
64
|
+
|
|
65
|
+
# Processes (check ports 4200-4210, 1111, 1112, 1113)
|
|
66
|
+
FE_PIDS=""
|
|
67
|
+
if [[ -n "$FE_WORKTREE" ]]; then
|
|
68
|
+
for port in 4200 4201 4202 4203 4204 4205 1112; do
|
|
69
|
+
PID=$(lsof -t -i :$port 2>/dev/null)
|
|
70
|
+
if [[ -n "$PID" ]]; then
|
|
71
|
+
# Verify the process is actually serving from this worktree
|
|
72
|
+
CMD=$(ps -p $PID -o command= 2>/dev/null)
|
|
73
|
+
if echo "$CMD" | grep -q "$FE_WORKTREE\|port.*$port"; then
|
|
74
|
+
FE_PIDS="$FE_PIDS $PID:$port"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
# Branch (local + remote)
|
|
81
|
+
FE_LOCAL_BRANCH=$(git -C "$FE_DIR" branch --list "<branch>" 2>/dev/null)
|
|
82
|
+
FE_REMOTE_BRANCH=$(git -C "$FE_DIR" ls-remote --heads origin "<branch>" 2>/dev/null)
|
|
83
|
+
|
|
84
|
+
# PR
|
|
85
|
+
FE_PR=$(gh pr list --head "<branch>" --repo "$FE_GITHUB_REPO" --state all --json number,state,mergedAt,url --jq '.[0]' 2>/dev/null)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### BE artifacts:
|
|
89
|
+
```bash
|
|
90
|
+
BE_DIR="<resolved BE path>"
|
|
91
|
+
|
|
92
|
+
# Worktree
|
|
93
|
+
BE_WORKTREE=$(git -C "$BE_DIR" worktree list | grep "<branch>" | awk '{print $1}')
|
|
94
|
+
|
|
95
|
+
# Docker isolated stack
|
|
96
|
+
BRANCH_SLUG=$(echo "<branch>" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
|
97
|
+
BE_DOCKER=$(docker ps --filter "name=wt-${BRANCH_SLUG}" --format "{{.Names}}" 2>/dev/null)
|
|
98
|
+
BE_COMPOSE=""
|
|
99
|
+
if [[ -n "$BE_WORKTREE" ]] && [[ -f "$BE_WORKTREE/docker-compose.isolated.yaml" ]]; then
|
|
100
|
+
BE_COMPOSE="$BE_WORKTREE/docker-compose.isolated.yaml"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# Branch (local + remote)
|
|
104
|
+
BE_LOCAL_BRANCH=$(git -C "$BE_DIR" branch --list "<branch>" 2>/dev/null)
|
|
105
|
+
BE_REMOTE_BRANCH=$(git -C "$BE_DIR" ls-remote --heads origin "<branch>" 2>/dev/null)
|
|
106
|
+
|
|
107
|
+
# PR
|
|
108
|
+
BE_PR=$(gh pr list --head "<branch>" --repo "$BE_GITHUB_REPO" --state all --json number,state,mergedAt,url --jq '.[0]' 2>/dev/null)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Uncommitted changes check:
|
|
112
|
+
```bash
|
|
113
|
+
if [[ -n "$FE_WORKTREE" ]]; then
|
|
114
|
+
FE_DIRTY=$(git -C "$FE_WORKTREE" status --porcelain 2>/dev/null | grep -v '^??' | head -5)
|
|
115
|
+
fi
|
|
116
|
+
if [[ -n "$BE_WORKTREE" ]]; then
|
|
117
|
+
BE_DIRTY=$(git -C "$BE_WORKTREE" status --porcelain 2>/dev/null | grep -v '^??' | head -5)
|
|
118
|
+
fi
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Step 3: Check PR Status & Build Summary
|
|
122
|
+
|
|
123
|
+
Parse the PR JSON for each repo. Build a summary string:
|
|
124
|
+
|
|
125
|
+
For each repo:
|
|
126
|
+
- **MERGED**: `✓ PR #<num> — merged <relative-time>`
|
|
127
|
+
- **OPEN**: `⚠ PR #<num> — still OPEN`
|
|
128
|
+
- **CLOSED** (not merged): `⚠ PR #<num> — closed without merge`
|
|
129
|
+
- **No PR found**: `⚠ No PR found for this branch`
|
|
130
|
+
|
|
131
|
+
Determine safety level:
|
|
132
|
+
- All PRs merged → **safe** (recommend "Clean everything")
|
|
133
|
+
- Any PR open/missing → **unsafe** (recommend "Abort", require explicit override)
|
|
134
|
+
|
|
135
|
+
## Step 4: Present Summary & Confirm
|
|
136
|
+
|
|
137
|
+
Present the full discovery as a formatted message, then `AskUserQuestion`:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
Feature Cleanup: <branch>
|
|
141
|
+
═══════════════════════════
|
|
142
|
+
|
|
143
|
+
FE (<FE_GITHUB_REPO>):
|
|
144
|
+
<PR status line>
|
|
145
|
+
<worktree path or "No worktree found">
|
|
146
|
+
<processes or "No running processes">
|
|
147
|
+
<branches: local + remote status>
|
|
148
|
+
<dirty warning if uncommitted changes>
|
|
149
|
+
|
|
150
|
+
BE (<BE_GITHUB_REPO>):
|
|
151
|
+
<PR status line>
|
|
152
|
+
<worktree path or "No worktree found">
|
|
153
|
+
<Docker containers or "No isolated stack">
|
|
154
|
+
<branches: local + remote status>
|
|
155
|
+
<dirty warning if uncommitted changes>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Options (if all PRs merged and no dirty files):
|
|
159
|
+
- **Clean everything** (Recommended)
|
|
160
|
+
- **Skip FE** — only clean BE
|
|
161
|
+
- **Skip BE** — only clean FE
|
|
162
|
+
- **Abort**
|
|
163
|
+
|
|
164
|
+
Options (if any PR not merged OR dirty files):
|
|
165
|
+
- **Abort** (Recommended) — with warning about what's at risk
|
|
166
|
+
- **Force clean everything** — delete despite warnings
|
|
167
|
+
- **Skip FE** / **Skip BE**
|
|
168
|
+
|
|
169
|
+
## Step 5: Execute Cleanup
|
|
170
|
+
|
|
171
|
+
Execute in this exact order (processes first, then containers, then files, then git):
|
|
172
|
+
|
|
173
|
+
### 5a: Kill FE processes
|
|
174
|
+
```bash
|
|
175
|
+
for pid_port in $FE_PIDS; do
|
|
176
|
+
PID=${pid_port%:*}; PORT=${pid_port#*:}
|
|
177
|
+
kill $PID 2>/dev/null && echo "✓ Killed process PID $PID on :$PORT"
|
|
178
|
+
done
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 5b: Teardown BE Docker
|
|
182
|
+
|
|
183
|
+
**CRITICAL:** Docker teardown MUST complete fully before the worktree directory is touched.
|
|
184
|
+
Use a two-phase approach: try compose first, then force-remove by slug as mandatory cleanup.
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
SLUG=$(echo "<branch>" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
|
188
|
+
|
|
189
|
+
# Phase 1: Try graceful compose down (if compose file still exists)
|
|
190
|
+
if [[ -n "$BE_COMPOSE" ]] && [[ -f "$BE_COMPOSE" ]]; then
|
|
191
|
+
cd "$BE_WORKTREE"
|
|
192
|
+
docker compose -f docker-compose.isolated.yaml down -v 2>&1
|
|
193
|
+
echo "✓ Docker stack torn down via compose"
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# Phase 2: ALWAYS force-remove by slug (catches orphans, partially-stopped stacks, missing compose files)
|
|
197
|
+
REMAINING=$(docker ps -aq --filter "name=wt-${SLUG}" 2>/dev/null)
|
|
198
|
+
if [[ -n "$REMAINING" ]]; then
|
|
199
|
+
docker rm -f $REMAINING 2>/dev/null
|
|
200
|
+
echo "✓ Force-removed remaining Docker containers"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Phase 3: Remove Docker volumes by project name pattern
|
|
204
|
+
docker volume ls -q 2>/dev/null | grep "wt.*${SLUG}" | xargs docker volume rm 2>/dev/null
|
|
205
|
+
# Also try the compose project name format
|
|
206
|
+
docker volume ls -q 2>/dev/null | grep "wt-${SLUG}" | xargs docker volume rm 2>/dev/null
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 5c: Remove FE worktree
|
|
210
|
+
```bash
|
|
211
|
+
if [[ -n "$FE_WORKTREE" ]]; then
|
|
212
|
+
cd "$FE_DIR"
|
|
213
|
+
git worktree remove --force "$FE_WORKTREE" 2>&1
|
|
214
|
+
git worktree prune
|
|
215
|
+
echo "✓ Removed FE worktree"
|
|
216
|
+
fi
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 5d: Remove BE worktree
|
|
220
|
+
```bash
|
|
221
|
+
if [[ -n "$BE_WORKTREE" ]]; then
|
|
222
|
+
cd "$BE_DIR"
|
|
223
|
+
# Docker may have created files owned by root — force remove the directory first
|
|
224
|
+
rm -rf "$BE_WORKTREE" 2>/dev/null
|
|
225
|
+
git worktree prune
|
|
226
|
+
echo "✓ Removed BE worktree"
|
|
227
|
+
fi
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 5e: Delete local branches
|
|
231
|
+
```bash
|
|
232
|
+
if [[ -n "$FE_LOCAL_BRANCH" ]]; then
|
|
233
|
+
git -C "$FE_DIR" branch -D "<branch>" 2>&1
|
|
234
|
+
echo "✓ Deleted FE local branch"
|
|
235
|
+
fi
|
|
236
|
+
if [[ -n "$BE_LOCAL_BRANCH" ]]; then
|
|
237
|
+
git -C "$BE_DIR" branch -D "<branch>" 2>&1
|
|
238
|
+
echo "✓ Deleted BE local branch"
|
|
239
|
+
fi
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 5f: Delete remote branches (only if PR was merged)
|
|
243
|
+
```bash
|
|
244
|
+
# FE
|
|
245
|
+
if [[ "$FE_PR_STATE" == "MERGED" ]] && [[ -n "$FE_REMOTE_BRANCH" ]]; then
|
|
246
|
+
git -C "$FE_DIR" push origin --delete "<branch>" 2>/dev/null \
|
|
247
|
+
&& echo "✓ Deleted FE remote branch" \
|
|
248
|
+
|| echo "ℹ FE remote branch already deleted"
|
|
249
|
+
fi
|
|
250
|
+
# BE
|
|
251
|
+
if [[ "$BE_PR_STATE" == "MERGED" ]] && [[ -n "$BE_REMOTE_BRANCH" ]]; then
|
|
252
|
+
git -C "$BE_DIR" push origin --delete "<branch>" 2>/dev/null \
|
|
253
|
+
&& echo "✓ Deleted BE remote branch" \
|
|
254
|
+
|| echo "ℹ BE remote branch already deleted"
|
|
255
|
+
fi
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 5g: Prune remote refs
|
|
259
|
+
```bash
|
|
260
|
+
git -C "$FE_DIR" remote prune origin 2>/dev/null
|
|
261
|
+
git -C "$BE_DIR" remote prune origin 2>/dev/null
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Step 6: Report
|
|
265
|
+
|
|
266
|
+
Print the final cleanup report:
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
Cleanup Complete
|
|
270
|
+
═════════════════
|
|
271
|
+
|
|
272
|
+
FE (<FE_GITHUB_REPO>):
|
|
273
|
+
<✓/ℹ for each action taken or skipped>
|
|
274
|
+
|
|
275
|
+
BE (<BE_GITHUB_REPO>):
|
|
276
|
+
<✓/ℹ for each action taken or skipped>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Edge Case Handling
|
|
280
|
+
|
|
281
|
+
| Scenario | Action |
|
|
282
|
+
|----------|--------|
|
|
283
|
+
| Worktree has uncommitted changes | Show dirty files in summary, default to Abort |
|
|
284
|
+
| Docker teardown fails (permission) | Log error, suggest manual `rm -rf` + `docker compose down -v` |
|
|
285
|
+
| Branch doesn't exist locally | Skip with `ℹ` note |
|
|
286
|
+
| Remote branch already deleted | Skip with `ℹ` note (don't error) |
|
|
287
|
+
| No worktree found for branch | Skip with `ℹ` note, still clean branches |
|
|
288
|
+
| Only one repo has artifacts | Clean what exists, report the other as "nothing to clean" |
|
|
289
|
+
| Running from inside the worktree being deleted | `cd` to repo root first before removing |
|
|
290
|
+
| Branch name has `#` characters | Sanitize for Docker slug, use raw name for git ops |
|
|
291
|
+
|
|
292
|
+
## Notes
|
|
293
|
+
|
|
294
|
+
- Always `cd` to repo root before `git worktree remove` (can't remove the worktree you're standing in)
|
|
295
|
+
- BE worktrees may have Docker-owned files — use `rm -rf` before `git worktree prune` rather than `git worktree remove`
|
|
296
|
+
- Remote branch deletion is idempotent — GitHub's auto-delete may have already removed it
|
|
297
|
+
- The `lsof` port scan only checks common worktree ports (4200-4205, 1112) — if the user served on a custom port, it won't be detected (acceptable tradeoff)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Claude Command: Commit
|
|
2
|
+
|
|
3
|
+
When this command is invoked, you MUST spawn a sub-agent to handle all commit work. Do NOT run any git commands yourself — delegate everything.
|
|
4
|
+
|
|
5
|
+
## Instructions
|
|
6
|
+
|
|
7
|
+
1. Spawn a single `general-purpose` sub-agent with `mode: "auto"` using the prompt below
|
|
8
|
+
2. Pass through any user arguments (e.g. `$ARGUMENTS`) into the agent prompt where indicated
|
|
9
|
+
3. When the agent returns, relay its summary to the user verbatim — do NOT add your own commentary
|
|
10
|
+
|
|
11
|
+
## Agent Prompt
|
|
12
|
+
|
|
13
|
+
Use this as the agent's prompt (replace `{{ARGUMENTS}}` with `$ARGUMENTS`):
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
You are a commit assistant. Your job is to create well-formatted git commits.
|
|
18
|
+
|
|
19
|
+
**User arguments:** `{{ARGUMENTS}}`
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
|
|
23
|
+
### 1. Detect stack & pre-commit checks
|
|
24
|
+
|
|
25
|
+
**First, detect the stack** by checking files in the working directory:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
if [[ -f "angular.json" ]] || [[ -f "nx.json" ]]; then
|
|
29
|
+
STACK="angular"
|
|
30
|
+
elif [[ -f "artisan" ]] || [[ -f "composer.json" ]]; then
|
|
31
|
+
STACK="laravel"
|
|
32
|
+
fi
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Then, unless the user passed `--no-verify`, run stack-specific checks:**
|
|
36
|
+
|
|
37
|
+
**Angular (FE):**
|
|
38
|
+
- `nx lint reservine` — lint check
|
|
39
|
+
- `bun run build:reservine:prod` — production build
|
|
40
|
+
|
|
41
|
+
**Laravel (BE):**
|
|
42
|
+
- `vendor/bin/pint --test` — code style check
|
|
43
|
+
- `vendor/bin/phpstan analyze <changed-php-files> --memory-limit=512M` — static analysis on changed files only
|
|
44
|
+
|
|
45
|
+
To get the changed PHP files for PHPStan:
|
|
46
|
+
```bash
|
|
47
|
+
git diff --cached --name-only --diff-filter=ACMR -- '*.php' | tr '\n' ' '
|
|
48
|
+
```
|
|
49
|
+
If no PHP files are staged, skip PHPStan.
|
|
50
|
+
|
|
51
|
+
**Unknown stack:** Skip pre-commit checks with a warning.
|
|
52
|
+
|
|
53
|
+
If any check fails, report the failure and stop. Do NOT proceed to commit.
|
|
54
|
+
|
|
55
|
+
If the user passed `--no-verify`, skip all checks entirely.
|
|
56
|
+
|
|
57
|
+
### 2. Gather context
|
|
58
|
+
|
|
59
|
+
Run these in parallel:
|
|
60
|
+
- `git status` (to see staged/unstaged/untracked files — never use `-uall`)
|
|
61
|
+
- `git diff --cached` and `git diff` (to see all changes)
|
|
62
|
+
- `git log --oneline -10` (to see recent commit style and detect task IDs)
|
|
63
|
+
|
|
64
|
+
### 3. Stage files
|
|
65
|
+
|
|
66
|
+
- If files are already staged, use only those
|
|
67
|
+
- If nothing is staged, run `git add` for all modified and new files (prefer specific filenames over `git add -A`)
|
|
68
|
+
- NEVER stage files that look like secrets (`.env`, credentials, tokens)
|
|
69
|
+
|
|
70
|
+
### 4. Analyze changes
|
|
71
|
+
|
|
72
|
+
Review the staged diff. Determine:
|
|
73
|
+
- What type of change this is (feat, fix, docs, style, refactor, perf, test, chore, ci, revert, wip)
|
|
74
|
+
- Whether a task ID can be inferred from the branch name or recent commits
|
|
75
|
+
- Whether the changes should be split into multiple commits (different concerns, different types, different logical groups)
|
|
76
|
+
|
|
77
|
+
### 5. Commit
|
|
78
|
+
|
|
79
|
+
For each logical commit:
|
|
80
|
+
1. Stage only the relevant files for that commit
|
|
81
|
+
2. Create the commit with the format below
|
|
82
|
+
3. Use a HEREDOC for the message to preserve formatting
|
|
83
|
+
|
|
84
|
+
**Commit message format:**
|
|
85
|
+
|
|
86
|
+
With task ID: `[TASK_ID] - type(scope): description`
|
|
87
|
+
Without task ID: `type(scope): description`
|
|
88
|
+
|
|
89
|
+
Scope is optional — use it when changes are clearly scoped to a module/area.
|
|
90
|
+
|
|
91
|
+
Task ID patterns to detect:
|
|
92
|
+
- Branch name like `feat/something-123` or `fix/PROJ-456` → extract the ID
|
|
93
|
+
- Recent commits with `[#123]` or `[PROJ-123]` → reuse the pattern
|
|
94
|
+
- If no ID is apparent, omit it
|
|
95
|
+
|
|
96
|
+
Rules:
|
|
97
|
+
- Present tense, imperative mood ("add" not "added")
|
|
98
|
+
- First line under 72 characters (excluding task ID)
|
|
99
|
+
- Focus on "why" not "what"
|
|
100
|
+
- Each commit should be atomic — one logical change
|
|
101
|
+
|
|
102
|
+
### 6. Verify
|
|
103
|
+
|
|
104
|
+
Run `git status` after committing to confirm success.
|
|
105
|
+
|
|
106
|
+
### 7. Return summary
|
|
107
|
+
|
|
108
|
+
Return a concise summary in this format:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
Committed N change(s):
|
|
112
|
+
- <commit hash short> <commit message>
|
|
113
|
+
- <commit hash short> <commit message>
|
|
114
|
+
|
|
115
|
+
Files: <number> changed, <insertions> insertions(+), <deletions> deletions(-)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
If the commit was split into multiple commits, list each one. Keep it brief.
|