feat: auto-create PR on milestone completion (#687) (#1084)

* docs: add Node LTS pinning guide for macOS Homebrew users

New doc (docs/node-lts-macos.md) explains how to pin Node 24 LTS
via Homebrew to avoid running on odd-numbered development releases.
Covers brew install/link/pin, version managers as alternatives,
and verification steps.

Added notice banner in README linking to the guide.

* feat: auto-create PR on milestone completion (#687)

New git preferences:
- git.auto_pr (boolean, default false): create a PR when a
  milestone completes via gh CLI
- git.pr_target_branch (string, default main branch): target
  branch for auto-created PRs (e.g. develop, qa, staging)

Implementation:
- GitPreferences: added auto_pr and pr_target_branch fields
- preferences.ts: added validation for both fields
- auto-worktree.ts: after push, pushes milestone branch and
  creates PR via 'gh pr create' (non-fatal on failure)

Documentation:
- configuration.md: added fields to git config block, table,
  and new git.auto_pr section with requirements and flow
- git-strategy.md: added Automatic Pull Requests section with
  Gitflow example config
This commit is contained in:
Tom Boucher 2026-03-17 23:45:29 -04:00 committed by GitHub
parent 792b166ce6
commit c5739f1282
5 changed files with 93 additions and 2 deletions

View file

@ -238,6 +238,8 @@ git:
commit_docs: true # commit .gsd/ artifacts to git (set false to keep local)
manage_gitignore: true # set false to prevent GSD from modifying .gitignore
worktree_post_create: .gsd/hooks/post-worktree-create # script to run after worktree creation
auto_pr: false # create a PR on milestone completion (requires push_branches)
pr_target_branch: develop # target branch for auto-created PRs (default: main branch)
```
| Field | Type | Default | Description |
@ -254,6 +256,8 @@ git:
| `commit_docs` | boolean | `true` | Commit `.gsd/` planning artifacts to git. Set `false` to keep local-only |
| `manage_gitignore` | boolean | `true` | When `false`, GSD will not modify `.gitignore` at all — no baseline patterns, no self-healing. Use if you manage your own `.gitignore` |
| `worktree_post_create` | string | (none) | Script to run after worktree creation. Receives `SOURCE_DIR` and `WORKTREE_DIR` env vars |
| `auto_pr` | boolean | `false` | Automatically create a pull request when a milestone completes. Requires `auto_push: true` and `gh` CLI installed and authenticated |
| `pr_target_branch` | string | (main branch) | Target branch for auto-created PRs (e.g. `develop`, `qa`). Defaults to `main_branch` if not set |
#### `git.worktree_post_create`
@ -280,6 +284,29 @@ ln -sf "$SOURCE_DIR/assets" "$WORKTREE_DIR/assets"
The path can be absolute or relative to the project root. The script runs with a 30-second timeout. Failure is non-fatal — GSD logs a warning and continues.
#### `git.auto_pr`
Automatically create a pull request when a milestone completes. Designed for teams using Gitflow or branch-based workflows where work should go through PR review before merging to a target branch.
```yaml
git:
auto_push: true
auto_pr: true
pr_target_branch: develop # or qa, staging, etc.
```
**Requirements:**
- `auto_push: true` — the milestone branch must be pushed before a PR can be created
- [`gh` CLI](https://cli.github.com/) installed and authenticated (`gh auth login`)
**How it works:**
1. Milestone completes → GSD squash-merges the worktree to the main branch
2. Pushes the main branch to remote (if `auto_push: true`)
3. Pushes the milestone branch to remote
4. Creates a PR from the milestone branch to `pr_target_branch` via `gh pr create`
If `pr_target_branch` is not set, the PR targets the `main_branch` (or auto-detected main branch). PR creation failure is non-fatal — GSD logs and continues.
### `notifications`
Control what notifications GSD sends during auto mode:

View file

@ -147,6 +147,22 @@ git:
main_branch: main # primary branch name
commit_docs: true # commit .gsd/ to git
isolation: worktree # "worktree", "branch", or "none"
auto_pr: false # create PR on milestone completion
pr_target_branch: develop # PR target branch (default: main)
```
### Automatic Pull Requests
For teams using Gitflow or branch-based workflows, GSD can automatically create a pull request when a milestone completes:
```yaml
git:
auto_push: true
auto_pr: true
pr_target_branch: develop
```
This pushes the milestone branch and creates a PR targeting `develop` (or whichever branch you specify). Requires `gh` CLI installed and authenticated. See [git.auto_pr](./configuration.md#gitauto_pr) for details.
```
### `commit_docs: false`

View file

@ -487,7 +487,7 @@ export function mergeMilestoneToMain(
originalBasePath_: string,
milestoneId: string,
roadmapContent: string,
): { commitMessage: string; pushed: boolean } {
): { commitMessage: string; pushed: boolean; prCreated: boolean } {
const worktreeCwd = process.cwd();
const milestoneBranch = autoWorktreeBranch(milestoneId);
@ -602,6 +602,33 @@ export function mergeMilestoneToMain(
}
}
// 9b. Auto-create PR if enabled (requires push_branches + push succeeded)
let prCreated = false;
if (prefs.auto_pr === true && pushed) {
const remote = prefs.remote ?? "origin";
const prTarget = prefs.pr_target_branch ?? mainBranch;
try {
// Push the milestone branch to remote first
execSync(`git push ${remote} ${milestoneBranch}`, {
cwd: originalBasePath_,
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf-8",
});
// Create PR via gh CLI
execSync(
`gh pr create --base "${prTarget}" --head "${milestoneBranch}" --title "Milestone ${milestoneId} complete" --body "Auto-created by GSD on milestone completion."`,
{
cwd: originalBasePath_,
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf-8",
},
);
prCreated = true;
} catch {
// PR creation failure is non-fatal — gh may not be installed or authenticated
}
}
// 10. Remove worktree directory first (must happen before branch deletion)
try {
removeWorktree(originalBasePath_, milestoneId, { branch: null as unknown as string, deleteBranch: false });
@ -620,5 +647,5 @@ export function mergeMilestoneToMain(
originalBase = null;
nudgeGitBranchCache(previousCwd);
return { commitMessage, pushed };
return { commitMessage, pushed, prCreated };
}

View file

@ -66,6 +66,16 @@ export interface GitPreferences {
* Failure is non-fatal logged as a warning.
*/
worktree_post_create?: string;
/** When true, automatically create a pull request after milestone completion.
* The PR targets `pr_target_branch` (default: the main branch).
* Requires `push_branches: true` and a configured remote.
* Default: false.
*/
auto_pr?: boolean;
/** Target branch for auto-created PRs (e.g. "develop", "qa").
* Default: the main branch (from `main_branch` or auto-detected).
*/
pr_target_branch?: string;
}
export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;

View file

@ -1368,6 +1368,17 @@ export function validatePreferences(preferences: GSDPreferences): {
errors.push("git.worktree_post_create must be a non-empty string (path to script)");
}
}
if (g.auto_pr !== undefined) {
if (typeof g.auto_pr === "boolean") git.auto_pr = g.auto_pr;
else errors.push("git.auto_pr must be a boolean");
}
if (g.pr_target_branch !== undefined) {
if (typeof g.pr_target_branch === "string" && g.pr_target_branch.trim()) {
git.pr_target_branch = g.pr_target_branch.trim();
} else {
errors.push("git.pr_target_branch must be a non-empty string (branch name)");
}
}
// Deprecated: merge_to_main is ignored (branchless architecture).
if (g.merge_to_main !== undefined) {
warnings.push("git.merge_to_main is deprecated — milestone-level merge is now always used. Remove this setting.");