mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 11:45:20 +02:00
feat: GitLab MR creation in /ship + /document-release
Ship Step 1.5 now checks .gitlab-ci.yml for release workflows alongside GitHub Actions. Step 8 routes to glab mr create on GitLab repos with correct flag mapping (-b, -t, -d). Falls back to manual instructions when no CLI is available. Document-release now reads MR body via glab mr view -F json and updates via glab mr update on GitLab repos. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+46
-14
@@ -236,22 +236,42 @@ Then write a `## GSTACK REVIEW REPORT` section to the end of the plan file:
|
||||
file you are allowed to edit in plan mode. The plan file review report is part of the
|
||||
plan's living status.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
## Step 0: Detect platform and base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
First, detect the git hosting platform from the remote URL:
|
||||
|
||||
1. Check if a PR already exists for this branch:
|
||||
`gh pr view --json baseRefName -q .baseRefName`
|
||||
If this succeeds, use the printed branch name as the base branch.
|
||||
```bash
|
||||
git remote get-url origin 2>/dev/null
|
||||
```
|
||||
|
||||
2. If no PR exists (command fails), detect the repo's default branch:
|
||||
`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`
|
||||
- If the URL contains "github.com" → platform is **GitHub**
|
||||
- If the URL contains "gitlab" → platform is **GitLab**
|
||||
- Otherwise, check CLI availability:
|
||||
- `gh auth status 2>/dev/null` succeeds → platform is **GitHub** (covers GitHub Enterprise)
|
||||
- `glab auth status 2>/dev/null` succeeds → platform is **GitLab** (covers self-hosted)
|
||||
- Neither → **unknown** (use git-native commands only)
|
||||
|
||||
3. If both commands fail, fall back to `main`.
|
||||
Determine which branch this PR/MR targets, or the repo's default branch if no
|
||||
PR/MR exists. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
**If GitHub:**
|
||||
1. `gh pr view --json baseRefName -q .baseRefName` — if succeeds, use it
|
||||
2. `gh repo view --json defaultBranchRef -q .defaultBranchRef.name` — if succeeds, use it
|
||||
|
||||
**If GitLab:**
|
||||
1. `glab mr view -F json 2>/dev/null` and extract the `target_branch` field — if succeeds, use it
|
||||
2. `glab repo view -F json 2>/dev/null` and extract the `default_branch` field — if succeeds, use it
|
||||
|
||||
**Git-native fallback (if unknown platform, or CLI commands fail):**
|
||||
1. `git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'`
|
||||
2. If that fails: `git rev-parse --verify origin/main 2>/dev/null` → use `main`
|
||||
3. If that fails: `git rev-parse --verify origin/master 2>/dev/null` → use `master`
|
||||
|
||||
If all fail, fall back to `main`.
|
||||
|
||||
Print the detected base branch name. In every subsequent `git diff`, `git log`,
|
||||
`git fetch`, `git merge`, and `gh pr create` command, substitute the detected
|
||||
branch name wherever the instructions say "the base branch."
|
||||
`git fetch`, `git merge`, and PR/MR creation command, substitute the detected
|
||||
branch name wherever the instructions say "the base branch" or `<default>`.
|
||||
|
||||
---
|
||||
|
||||
@@ -524,14 +544,20 @@ EOF
|
||||
git push
|
||||
```
|
||||
|
||||
**PR body update (idempotent, race-safe):**
|
||||
**PR/MR body update (idempotent, race-safe):**
|
||||
|
||||
1. Read the existing PR body into a PID-unique tempfile:
|
||||
1. Read the existing PR/MR body into a PID-unique tempfile (use the platform detected in Step 0):
|
||||
|
||||
**If GitHub:**
|
||||
```bash
|
||||
gh pr view --json body -q .body > /tmp/gstack-pr-body-$$.md
|
||||
```
|
||||
|
||||
**If GitLab:**
|
||||
```bash
|
||||
glab mr view -F json 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('description',''))" > /tmp/gstack-pr-body-$$.md
|
||||
```
|
||||
|
||||
2. If the tempfile already contains a `## Documentation` section, replace that section with the
|
||||
updated content. If it does not contain one, append a `## Documentation` section at the end.
|
||||
|
||||
@@ -541,18 +567,24 @@ gh pr view --json body -q .body > /tmp/gstack-pr-body-$$.md
|
||||
|
||||
4. Write the updated body back:
|
||||
|
||||
**If GitHub:**
|
||||
```bash
|
||||
gh pr edit --body-file /tmp/gstack-pr-body-$$.md
|
||||
```
|
||||
|
||||
**If GitLab:**
|
||||
```bash
|
||||
glab mr update -d "$(cat /tmp/gstack-pr-body-$$.md)"
|
||||
```
|
||||
|
||||
5. Clean up the tempfile:
|
||||
|
||||
```bash
|
||||
rm -f /tmp/gstack-pr-body-$$.md
|
||||
```
|
||||
|
||||
6. If `gh pr view` fails (no PR exists): skip with message "No PR found — skipping body update."
|
||||
7. If `gh pr edit` fails: warn "Could not update PR body — documentation changes are in the
|
||||
6. If `gh pr view` / `glab mr view` fails (no PR/MR exists): skip with message "No PR/MR found — skipping body update."
|
||||
7. If `gh pr edit` / `glab mr update` fails: warn "Could not update PR/MR body — documentation changes are in the
|
||||
commit." and continue.
|
||||
|
||||
**Structured doc health summary (final output):**
|
||||
|
||||
@@ -291,14 +291,20 @@ EOF
|
||||
git push
|
||||
```
|
||||
|
||||
**PR body update (idempotent, race-safe):**
|
||||
**PR/MR body update (idempotent, race-safe):**
|
||||
|
||||
1. Read the existing PR body into a PID-unique tempfile:
|
||||
1. Read the existing PR/MR body into a PID-unique tempfile (use the platform detected in Step 0):
|
||||
|
||||
**If GitHub:**
|
||||
```bash
|
||||
gh pr view --json body -q .body > /tmp/gstack-pr-body-$$.md
|
||||
```
|
||||
|
||||
**If GitLab:**
|
||||
```bash
|
||||
glab mr view -F json 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('description',''))" > /tmp/gstack-pr-body-$$.md
|
||||
```
|
||||
|
||||
2. If the tempfile already contains a `## Documentation` section, replace that section with the
|
||||
updated content. If it does not contain one, append a `## Documentation` section at the end.
|
||||
|
||||
@@ -308,18 +314,24 @@ gh pr view --json body -q .body > /tmp/gstack-pr-body-$$.md
|
||||
|
||||
4. Write the updated body back:
|
||||
|
||||
**If GitHub:**
|
||||
```bash
|
||||
gh pr edit --body-file /tmp/gstack-pr-body-$$.md
|
||||
```
|
||||
|
||||
**If GitLab:**
|
||||
```bash
|
||||
glab mr update -d "$(cat /tmp/gstack-pr-body-$$.md)"
|
||||
```
|
||||
|
||||
5. Clean up the tempfile:
|
||||
|
||||
```bash
|
||||
rm -f /tmp/gstack-pr-body-$$.md
|
||||
```
|
||||
|
||||
6. If `gh pr view` fails (no PR exists): skip with message "No PR found — skipping body update."
|
||||
7. If `gh pr edit` fails: warn "Could not update PR body — documentation changes are in the
|
||||
6. If `gh pr view` / `glab mr view` fails (no PR/MR exists): skip with message "No PR/MR found — skipping body update."
|
||||
7. If `gh pr edit` / `glab mr update` fails: warn "Could not update PR/MR body — documentation changes are in the
|
||||
commit." and continue.
|
||||
|
||||
**Structured doc health summary (final output):**
|
||||
|
||||
+73
-24
@@ -253,22 +253,42 @@ Then write a `## GSTACK REVIEW REPORT` section to the end of the plan file:
|
||||
file you are allowed to edit in plan mode. The plan file review report is part of the
|
||||
plan's living status.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
## Step 0: Detect platform and base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
First, detect the git hosting platform from the remote URL:
|
||||
|
||||
1. Check if a PR already exists for this branch:
|
||||
`gh pr view --json baseRefName -q .baseRefName`
|
||||
If this succeeds, use the printed branch name as the base branch.
|
||||
```bash
|
||||
git remote get-url origin 2>/dev/null
|
||||
```
|
||||
|
||||
2. If no PR exists (command fails), detect the repo's default branch:
|
||||
`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`
|
||||
- If the URL contains "github.com" → platform is **GitHub**
|
||||
- If the URL contains "gitlab" → platform is **GitLab**
|
||||
- Otherwise, check CLI availability:
|
||||
- `gh auth status 2>/dev/null` succeeds → platform is **GitHub** (covers GitHub Enterprise)
|
||||
- `glab auth status 2>/dev/null` succeeds → platform is **GitLab** (covers self-hosted)
|
||||
- Neither → **unknown** (use git-native commands only)
|
||||
|
||||
3. If both commands fail, fall back to `main`.
|
||||
Determine which branch this PR/MR targets, or the repo's default branch if no
|
||||
PR/MR exists. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
**If GitHub:**
|
||||
1. `gh pr view --json baseRefName -q .baseRefName` — if succeeds, use it
|
||||
2. `gh repo view --json defaultBranchRef -q .defaultBranchRef.name` — if succeeds, use it
|
||||
|
||||
**If GitLab:**
|
||||
1. `glab mr view -F json 2>/dev/null` and extract the `target_branch` field — if succeeds, use it
|
||||
2. `glab repo view -F json 2>/dev/null` and extract the `default_branch` field — if succeeds, use it
|
||||
|
||||
**Git-native fallback (if unknown platform, or CLI commands fail):**
|
||||
1. `git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'`
|
||||
2. If that fails: `git rev-parse --verify origin/main 2>/dev/null` → use `main`
|
||||
3. If that fails: `git rev-parse --verify origin/master 2>/dev/null` → use `master`
|
||||
|
||||
If all fail, fall back to `main`.
|
||||
|
||||
Print the detected base branch name. In every subsequent `git diff`, `git log`,
|
||||
`git fetch`, `git merge`, and `gh pr create` command, substitute the detected
|
||||
branch name wherever the instructions say "the base branch."
|
||||
`git fetch`, `git merge`, and PR/MR creation command, substitute the detected
|
||||
branch name wherever the instructions say "the base branch" or `<default>`.
|
||||
|
||||
---
|
||||
|
||||
@@ -391,12 +411,13 @@ service with existing deployment — verify that a distribution pipeline exists.
|
||||
2. If new artifact detected, check for a release workflow:
|
||||
```bash
|
||||
ls .github/workflows/ 2>/dev/null | grep -iE 'release|publish|dist'
|
||||
grep -qE 'release|publish|deploy' .gitlab-ci.yml 2>/dev/null && echo "GITLAB_CI_RELEASE"
|
||||
```
|
||||
|
||||
3. **If no release pipeline exists and a new artifact was added:** Use AskUserQuestion:
|
||||
- "This PR adds a new binary/tool but there's no CI/CD pipeline to build and publish it.
|
||||
Users won't be able to download the artifact after merge."
|
||||
- A) Add a release workflow now (GitHub Actions cross-platform build + GitHub Releases)
|
||||
- A) Add a release workflow now (CI/CD release pipeline — GitHub Actions or GitLab CI depending on platform)
|
||||
- B) Defer — add to TODOS.md
|
||||
- C) Not needed — this is internal/web-only, existing deployment covers it
|
||||
|
||||
@@ -676,14 +697,22 @@ Use AskUserQuestion:
|
||||
git log --format="%an (%ae)" -1 -- <source-file-under-test>
|
||||
```
|
||||
If these are different people, prefer the production code author — they likely introduced the regression.
|
||||
- Create a GitHub issue assigned to that person:
|
||||
```bash
|
||||
gh issue create \
|
||||
--title "Pre-existing test failure: <test-name>" \
|
||||
--body "Found failing on branch <current-branch>. Failure is pre-existing.\n\n**Error:**\n```\n<first 10 lines>\n```\n\n**Last modified by:** <author>\n**Noticed by:** gstack /ship on <date>" \
|
||||
--assignee "<github-username>"
|
||||
```
|
||||
- If `gh` is not available or `--assignee` fails (user not in org, etc.), create the issue without assignee and note who should look at it in the body.
|
||||
- Create an issue assigned to that person (use the platform detected in Step 0):
|
||||
- **If GitHub:**
|
||||
```bash
|
||||
gh issue create \
|
||||
--title "Pre-existing test failure: <test-name>" \
|
||||
--body "Found failing on branch <current-branch>. Failure is pre-existing.\n\n**Error:**\n```\n<first 10 lines>\n```\n\n**Last modified by:** <author>\n**Noticed by:** gstack /ship on <date>" \
|
||||
--assignee "<github-username>"
|
||||
```
|
||||
- **If GitLab:**
|
||||
```bash
|
||||
glab issue create \
|
||||
-t "Pre-existing test failure: <test-name>" \
|
||||
-d "Found failing on branch <current-branch>. Failure is pre-existing.\n\n**Error:**\n```\n<first 10 lines>\n```\n\n**Last modified by:** <author>\n**Noticed by:** gstack /ship on <date>" \
|
||||
-a "<gitlab-username>"
|
||||
```
|
||||
- If neither CLI is available or `--assignee`/`-a` fails (user not in org, etc.), create the issue without assignee and note who should look at it in the body.
|
||||
- Continue with the workflow.
|
||||
|
||||
**If "Skip":**
|
||||
@@ -1420,12 +1449,13 @@ git push -u origin <branch-name>
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Create PR
|
||||
## Step 8: Create PR/MR
|
||||
|
||||
Create a pull request using `gh`:
|
||||
Create a pull request (GitHub) or merge request (GitLab) using the platform detected in Step 0.
|
||||
|
||||
```bash
|
||||
gh pr create --base <base> --title "<type>: <summary>" --body "$(cat <<'EOF'
|
||||
The PR/MR body should contain these sections:
|
||||
|
||||
```
|
||||
## Summary
|
||||
<bullet points from CHANGELOG>
|
||||
|
||||
@@ -1459,11 +1489,30 @@ gh pr create --base <base> --title "<type>: <summary>" --body "$(cat <<'EOF'
|
||||
- [x] All Vitest tests pass (N tests)
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
```
|
||||
|
||||
**If GitHub:**
|
||||
|
||||
```bash
|
||||
gh pr create --base <base> --title "<type>: <summary>" --body "$(cat <<'EOF'
|
||||
<PR body from above>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
**Output the PR URL** — then proceed to Step 8.5.
|
||||
**If GitLab:**
|
||||
|
||||
```bash
|
||||
glab mr create -b <base> -t "<type>: <summary>" -d "$(cat <<'EOF'
|
||||
<MR body from above>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
**If neither CLI is available:**
|
||||
Print the branch name, remote URL, and instruct the user to create the PR/MR manually via the web UI. Do not stop — the code is pushed and ready.
|
||||
|
||||
**Output the PR/MR URL** — then proceed to Step 8.5.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+27
-6
@@ -97,12 +97,13 @@ service with existing deployment — verify that a distribution pipeline exists.
|
||||
2. If new artifact detected, check for a release workflow:
|
||||
```bash
|
||||
ls .github/workflows/ 2>/dev/null | grep -iE 'release|publish|dist'
|
||||
grep -qE 'release|publish|deploy' .gitlab-ci.yml 2>/dev/null && echo "GITLAB_CI_RELEASE"
|
||||
```
|
||||
|
||||
3. **If no release pipeline exists and a new artifact was added:** Use AskUserQuestion:
|
||||
- "This PR adds a new binary/tool but there's no CI/CD pipeline to build and publish it.
|
||||
Users won't be able to download the artifact after merge."
|
||||
- A) Add a release workflow now (GitHub Actions cross-platform build + GitHub Releases)
|
||||
- A) Add a release workflow now (CI/CD release pipeline — GitHub Actions or GitLab CI depending on platform)
|
||||
- B) Defer — add to TODOS.md
|
||||
- C) Not needed — this is internal/web-only, existing deployment covers it
|
||||
|
||||
@@ -472,12 +473,13 @@ git push -u origin <branch-name>
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Create PR
|
||||
## Step 8: Create PR/MR
|
||||
|
||||
Create a pull request using `gh`:
|
||||
Create a pull request (GitHub) or merge request (GitLab) using the platform detected in Step 0.
|
||||
|
||||
```bash
|
||||
gh pr create --base <base> --title "<type>: <summary>" --body "$(cat <<'EOF'
|
||||
The PR/MR body should contain these sections:
|
||||
|
||||
```
|
||||
## Summary
|
||||
<bullet points from CHANGELOG>
|
||||
|
||||
@@ -511,11 +513,30 @@ gh pr create --base <base> --title "<type>: <summary>" --body "$(cat <<'EOF'
|
||||
- [x] All Vitest tests pass (N tests)
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
```
|
||||
|
||||
**If GitHub:**
|
||||
|
||||
```bash
|
||||
gh pr create --base <base> --title "<type>: <summary>" --body "$(cat <<'EOF'
|
||||
<PR body from above>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
**Output the PR URL** — then proceed to Step 8.5.
|
||||
**If GitLab:**
|
||||
|
||||
```bash
|
||||
glab mr create -b <base> -t "<type>: <summary>" -d "$(cat <<'EOF'
|
||||
<MR body from above>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
**If neither CLI is available:**
|
||||
Print the branch name, remote URL, and instruct the user to create the PR/MR manually via the web UI. Do not stop — the code is pushed and ready.
|
||||
|
||||
**Output the PR/MR URL** — then proceed to Step 8.5.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user