-
Notifications
You must be signed in to change notification settings - Fork 4
chore: sync SDLC files with bootnode-sdlc starter-kit #474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| --- | ||
| name: sdlc:create-pr | ||
| description: Use when creating a pull request -- reads the PR template, auto-fills from git context and linked issue, confirms with the user, then creates via gh CLI. | ||
| --- | ||
|
|
||
| # /sdlc:create-pr | ||
|
|
||
| Create a well-structured GitHub pull request by reading the repo's PR template and filling it from context. | ||
|
|
||
| **Core principle:** Templates own the format. Context owns the content. User owns the final word. | ||
|
|
||
| ## Template Location | ||
|
|
||
| Read `.github/PULL_REQUEST_TEMPLATE.md` relative to the project root on every invocation. This path is fixed -- do not search for it. | ||
|
|
||
| ## Core Pattern | ||
|
|
||
| 1. **Gather** -- Collect all inputs silently. Ask only when auto-derivation fails. | ||
| 2. **Draft** -- Fill every template section from the gathered context. | ||
| 3. **Confirm** -- Show the full draft. Wait for explicit approval. Iterate. | ||
| 4. **Create** -- Run `gh pr create` with `--body-file`. Report the URL. | ||
|
|
||
| ## Step 1: Gather | ||
|
|
||
| **Auto-derive (no user interaction):** | ||
|
|
||
| - Read `.github/PULL_REQUEST_TEMPLATE.md` | ||
| - Run `git diff <base>...HEAD` -- the branch diff | ||
| - Run `git log <base>..HEAD --oneline` -- the commit history | ||
| - Extract issue number from branch name (pattern: `type/NNN-description`, e.g., `feat/17-add-skill` → `#17`) | ||
| - If issue number found, run `gh issue view NNN --json title,body,labels` and extract acceptance criteria from the body | ||
|
|
||
| **Ask when needed (use multiple-choice where possible):** | ||
|
|
||
| | Question | When | Options | | ||
| |----------|------|---------| | ||
| | "Which issue does this PR close?" | Branch name has no issue number | List of recent open issues (via `gh issue list --state open --limit 5 --json number,title`) + "None" + Other | | ||
| | "PR type?" | Always; pre-select "Ready for review" | "Ready for review" / "Draft" | | ||
| | "Base branch?" | Always; **must be asked even when auto-detected** -- the auto-detected value is the pre-selected default, not a reason to skip | Branch we branched from (auto-detected via `git merge-base` against known remote branches; pre-selected as default) / `develop` (if present in remote) / `main` / Other | | ||
| | "Who should review this PR?" | Always; multi-select | All reviewers returned by script (see below), in order, plus "Other" as the last option | | ||
| | "Who should this PR be assigned to?" | Always; pre-select "Me" | "Me" (resolved via `gh api user --jq '.login'`) / "Nobody" (default if "Me" feels presumptuous) / Other | | ||
| | "Which checklist items have you completed?" | Always; multi-select; zero selections is valid (means none completed yet) | "Self-reviewed my own diff" / "Tests added or updated" / "Docs updated (if applicable)" / "No unrelated changes bundled in" | | ||
| | "Will you add screenshots to this PR?" | Always | "Yes, I'll add them after creation" / "No" (default) | | ||
|
|
||
| ### Helper scripts | ||
|
|
||
| **IMPORTANT:** Do NOT run `bash .claude/skills/create-pr/*.sh` directly -- that path only works for project-local installs. Always use the commands below, which resolve the script location first. | ||
|
|
||
| Auto-detect base branch: | ||
|
|
||
| ```bash | ||
| if [[ -f .claude/skills/create-pr/get-base-branch.sh ]]; then bash .claude/skills/create-pr/get-base-branch.sh; elif [[ -f "$HOME/.claude/skills/create-pr/get-base-branch.sh" ]]; then bash "$HOME/.claude/skills/create-pr/get-base-branch.sh"; fi | ||
| ``` | ||
|
|
||
| It outputs the branch name (e.g., `main`, `develop`) whose merge-base with HEAD is most recent -- i.e., the branch we most likely forked from. Present it as the pre-selected default in the base branch question. | ||
|
|
||
| Fetch recent reviewers: | ||
|
|
||
| ```bash | ||
| if [[ -f .claude/skills/create-pr/get-reviewers.sh ]]; then bash .claude/skills/create-pr/get-reviewers.sh; elif [[ -f "$HOME/.claude/skills/create-pr/get-reviewers.sh" ]]; then bash "$HOME/.claude/skills/create-pr/get-reviewers.sh"; fi | ||
| ``` | ||
|
|
||
| It outputs up to 4 reviewer logins, one per line, ordered most-recent-first (excludes the current user; falls back to alphabetical collaborators for new repos). Show every login the script returns as an option, in the exact order returned. Add "Other" as the last option. Do not add a "Skip" or "None" option -- if the user wants no reviewers, they select only "Other" and leave it empty. | ||
|
|
||
| Do not add labels to the PR. Labels are managed separately. | ||
|
|
||
| ## Step 2: Draft | ||
|
|
||
| ### PR Title | ||
|
|
||
| Conventional commit format: `type(scope): subject` or `type: subject`. | ||
|
|
||
| Allowed types: `feat`, `fix`, `docs`, `test`, `ci`, `refactor`, `perf`, `chore`, `revert`, `wip`, `build`, `style`, `release`. | ||
|
|
||
| <!-- Standard Conventional Commits prefixes only, matching the types documented in CLAUDE.md. Projects adopting this starter kit can extend this list to suit their conventions. --> | ||
|
|
||
| - Derive from branch name and commit history | ||
| - Scope is optional | ||
| - Subject: lowercase, imperative mood, no trailing period | ||
|
|
||
| ### PR Body | ||
|
|
||
| Fill every section in template order. Strip all HTML comments (`<!-- ... -->`). | ||
|
|
||
| #### Summary | ||
|
|
||
| First line: `Closes #` | ||
|
|
||
| If no linked issue, first line: `No related issue. <one sentence motivation>` | ||
|
|
||
| Followed by 1-3 sentences synthesizing commits and issue description, focused on *why* not *what*. If no issue is linked, derive from commits and branch name only. | ||
|
|
||
| #### Changes | ||
|
|
||
| Bullet list. Each bullet = one discrete change from the diff or commit messages. | ||
|
|
||
| #### Acceptance criteria | ||
|
|
||
| - **Issue has AC:** Mirror as checkboxes. Check off items the diff demonstrates are fulfilled. | ||
| - **Issue has no AC, or no issue:** Suggest AC based on the changes made. Present as unchecked checkboxes. | ||
| - **AC diverged from issue:** Note it explicitly. Example: "Note: criterion X was moved to #M" or "Added: Y discovered during implementation." | ||
|
|
||
| #### Test plan | ||
|
|
||
| Two subsections, always present: | ||
|
|
||
| ##### Automated tests | ||
| List test files added/modified in the diff and the command to run them. If none: `No automated tests added.` | ||
|
|
||
| ##### Manual verification | ||
| If the change has user-facing or integration behavior, list manual steps. If purely internal: `No manual steps required.` | ||
|
|
||
| #### Breaking changes | ||
|
|
||
| If breaking changes detected (API changes, removed exports, schema changes): describe what breaks and migration steps. | ||
|
|
||
| If none: `None.` | ||
|
|
||
| #### Checklist | ||
|
|
||
| Render all four items based on the user's selections from the Gather step: | ||
|
|
||
| - Items selected by the user → `- [x] <item>` | ||
| - Items not selected → `- [ ] <item>` | ||
|
|
||
| The four items, in order: | ||
|
|
||
| 1. Self-reviewed my own diff | ||
| 2. Tests added or updated | ||
| 3. Docs updated (if applicable) | ||
| 4. No unrelated changes bundled in | ||
|
|
||
| #### Screenshots | ||
|
|
||
| Based on the Step 1 answer: | ||
|
|
||
| - "Yes": `To be added after PR creation.` (remind user to attach via GitHub UI) | ||
| - "No" (default): `None.` | ||
|
|
||
| **The section is always present.** | ||
|
|
||
| ## Step 3: Confirm | ||
|
|
||
| Show to the user: | ||
| - PR title | ||
| - Complete body (all sections, no HTML comments) | ||
| - Target base branch | ||
| - The exact `gh pr create` command that will run | ||
|
|
||
| Wait for explicit approval. Accept edits to any section. Loop until approved. | ||
|
|
||
| ## Step 4: Create | ||
|
|
||
| ```bash | ||
| BODY_FILE=$(mktemp /tmp/gh_pr_body_XXXXXX) | ||
|
|
||
| # Replace the placeholder below with the actual drafted PR body: | ||
| cat > "$BODY_FILE" << 'EOF' | ||
| {{PR_BODY}} | ||
| EOF | ||
|
|
||
| gh pr create \ | ||
| --title "<title>" \ | ||
| --base "<base-branch>" \ | ||
| --body-file "$BODY_FILE" \ | ||
| [--reviewer <handle> ...] \ | ||
| [--assignee <handle>] | ||
| ``` | ||
|
|
||
| Add `--draft` if user selected "Draft" in Step 1. Add one `--reviewer <handle>` flag per reviewer selected in Step 1; if "Other" was selected, use the handle the user provided. Add `--assignee <handle>` using the resolved login if "Me" or "Other" was selected; omit if "Nobody". | ||
|
|
||
| After reporting the PR URL: if the user selected "Yes" for screenshots in Step 1, remind them to attach screenshots via the GitHub UI. | ||
|
|
||
| ## Edge Cases | ||
|
|
||
| ### Branch is behind base | ||
| Present options: | ||
| 1. **Continue as-is** -- create the PR and note it's behind | ||
| 2. **Rebase onto base** -- run `git rebase <base>`; if conflicts, help resolve | ||
| 3. **Merge base in** -- run `git merge <base>`; if conflicts, help resolve | ||
| 4. **Abort** -- stop; do not create the PR | ||
|
|
||
| ### No commits ahead of base | ||
| Stop. "No commits ahead of `<base>`. Nothing to create a PR from." | ||
|
|
||
| ## Common Mistakes | ||
|
|
||
| - **Reconstructing the template from memory** -- read `.github/PULL_REQUEST_TEMPLATE.md` every time. | ||
| - **Generating an ad-hoc format** -- every section from the template must appear, in template order. | ||
| - **Creating before confirmation** -- never run `gh pr create` without explicit user approval. | ||
| - **Leaving HTML comments** -- strip all `<!-- ... -->` from the output. | ||
| - **Silently omitting `Closes #`** -- if no issue, say so explicitly on the first line. | ||
| - **Deleting empty sections** -- Breaking changes and Screenshots are always present; use `None.` | ||
| - **Ignoring AC divergence** -- note explicitly when PR criteria differ from the issue's. | ||
| - **Skipping the base-branch question** -- always present it. Auto-detection provides the default, not the answer. | ||
|
|
||
| ## Installation | ||
|
|
||
| This skill includes helper scripts alongside `SKILL.md`. When installing or updating, copy (or symlink) the **entire `create-pr/` directory** -- not just `SKILL.md`. All files in this directory are required: | ||
|
|
||
| - `SKILL.md` -- skill definition | ||
| - `get-base-branch.sh` -- auto-detects the base branch | ||
| - `get-reviewers.sh` -- fetches recent reviewer logins | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| # Find the base branch: the most likely target for a pull request. | ||
| # | ||
| # Strategy: pick the remote branch whose merge-base with HEAD is most recent | ||
| # (i.e., the branch we most likely forked from). This works even when the | ||
| # base branch has advanced past the fork point. | ||
| # | ||
| # 1. Check well-known stable branches first (main, master, develop, staging). | ||
| # Among those that exist, pick the one with the closest merge-base to HEAD. | ||
| # 2. If none match, fall back to any remote branch with the closest merge-base. | ||
|
|
||
| current=$(git rev-parse --abbrev-ref HEAD) | ||
|
|
||
| # Priority 1: well-known base branches -- pick closest merge-base | ||
| best_branch="" | ||
| best_ts=0 | ||
|
|
||
| for candidate in main master develop staging; do | ||
| ref="origin/$candidate" | ||
| git rev-parse --verify "$ref" >/dev/null 2>&1 || continue | ||
| [[ "$candidate" == "$current" ]] && continue | ||
| mb=$(git merge-base HEAD "$ref" 2>/dev/null) || continue | ||
| ts=$(git log -1 --format=%ct "$mb" 2>/dev/null) || continue | ||
| [[ -n "$ts" ]] || continue | ||
| if (( ts > best_ts )); then | ||
| best_ts=$ts | ||
| best_branch=$candidate | ||
| fi | ||
| done | ||
|
|
||
| if [[ -n "$best_branch" ]]; then | ||
| echo "$best_branch" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Priority 2: any other remote branch, pick closest merge-base | ||
| best_branch="" | ||
| best_ts=0 | ||
|
|
||
| while IFS= read -r ref; do | ||
| branch="${ref#origin/}" | ||
| [[ "$branch" == "HEAD" ]] && continue | ||
| [[ "$branch" == "$current" ]] && continue | ||
| mb=$(git merge-base HEAD "$ref" 2>/dev/null) || continue | ||
| ts=$(git log -1 --format=%ct "$mb" 2>/dev/null) || continue | ||
| [[ -n "$ts" ]] || continue | ||
| if (( ts > best_ts )); then | ||
| best_ts=$ts | ||
| best_branch=$branch | ||
| fi | ||
| done < <(git for-each-ref --format='%(refname:short)' refs/remotes/origin/) | ||
|
|
||
| if [[ -z "$best_branch" ]]; then | ||
| echo "No base branch found" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "$best_branch" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| me=$(gh api user --jq '.login' 2>/dev/null) || true | ||
|
|
||
| # Attempt: recent PR reviewers sorted by most-recent-first | ||
| reviewers=$( | ||
| gh pr list --state all --limit 20 --json reviews \ | ||
| --jq " | ||
| [ .[].reviews[] | ||
| | { login: .author.login, ts: .submittedAt } | ||
| ] | ||
| | sort_by(.ts) | reverse | ||
| | map(.login) | ||
| | map(select(. != \"$me\")) | ||
| | reduce .[] as \$x ( | ||
| { seen: {}, out: [] }; | ||
| if .seen[\$x] then . else { seen: (.seen | .[\$x] = true), out: (.out + [\$x]) } end | ||
| ) | ||
| | .out[:4] | ||
| | .[] | ||
| " 2>/dev/null || true | ||
| ) | ||
|
|
||
| if [[ -n "$reviewers" ]]; then | ||
| echo "$reviewers" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Fallback: collaborators (alphabetical, excluding self) | ||
| repo=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null) || exit 0 | ||
| gh api "/repos/$repo/collaborators" \ | ||
| --jq "[ .[].login | select(. != \"$me\") ] | sort | .[:4] | .[]" \ | ||
| 2>/dev/null || true |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,9 @@ | ||||||||
| --- | ||||||||
| name: issue | ||||||||
| name: sdlc:issue | ||||||||
| description: Use when creating a GitHub issue from a brief -- bug, feature, epic, or spike -- against the repo's GitHub issue templates via gh CLI. | ||||||||
| --- | ||||||||
|
|
||||||||
| # /issue | ||||||||
| # /sdlc:issue | ||||||||
|
|
||||||||
| Create a well-structured GitHub issue using the repo's own templates and `gh` CLI. | ||||||||
|
|
||||||||
|
|
@@ -18,28 +18,50 @@ Create a well-structured GitHub issue using the repo's own templates and `gh` CL | |||||||
| 5. **Confirm** -- Show full draft including labels. Wait for explicit approval. Iterate until approved. | ||||||||
| 6. **Create** -- Write body to temp file, run `gh issue create` with all labels, report issue URL. | ||||||||
|
|
||||||||
| ## Title Format | ||||||||
|
|
||||||||
| Issue titles must be **natural language, sentence case** (code terms and command names retain their canonical casing) -- no conventional commit prefixes, no scope tags. | ||||||||
|
|
||||||||
| Conventional commit format (`type(scope): subject`) is for **commits and PR titles only**. It is not appropriate for issue titles, which appear in GitHub's issue list and must be scannable at a glance. | ||||||||
|
|
||||||||
| **Good:** | ||||||||
| - `Issue skill defaults to conventional commit format for titles` | ||||||||
| - `mktemp fails with .md suffix` | ||||||||
| - `Add natural language title guidance to issue skill` | ||||||||
|
|
||||||||
| **Bad:** | ||||||||
| - `fix(skills): mktemp fails with .md suffix` | ||||||||
| - `fix: issue skill defaults to conventional commit format` | ||||||||
| - `feat(issue): add title guidance` | ||||||||
|
|
||||||||
| Rule: if a reader has to mentally strip a prefix to understand the title, the title is wrong. | ||||||||
|
|
||||||||
| ## Template Map | ||||||||
|
|
||||||||
| | Type | File | Type label | Additional labels | | ||||||||
| |---------|-----------------|---------------|---------------------------| | ||||||||
| | Bug | `1-bug.yml` | `bug` | `severity: <level>` | | ||||||||
| | Bug | `1-bug.yml` | `bug` | `priority: <level>` | | ||||||||
| | Feature | `2-feature.yml` | `enhancement` | `priority: <level>` | | ||||||||
| | Epic | `3-epic.yml` | `epic` | `priority: <level>` | | ||||||||
| | Spike | `4-spike.yml` | `spike` | -- | | ||||||||
|
|
||||||||
| ## Labels | ||||||||
|
|
||||||||
| Severity and priority are applied as labels, not form dropdowns. See the Label Conventions section in `CLAUDE.md` for the full table and descriptions. | ||||||||
| Priority is applied as a label, not a form dropdown. See the Label Conventions section in `CLAUDE.md` for the full table and descriptions. | ||||||||
|
|
||||||||
| - Bugs, features, and epics each get a `priority: <level>` label. | ||||||||
| - Spikes don't carry priority. | ||||||||
| - If the brief doesn't specify a level, ask once using a numbered list -- never default silently: | ||||||||
|
|
||||||||
| - Bugs get a `severity: <level>` label (critical / high / medium / low). | ||||||||
| - Features and epics get a `priority: <level>` label (high / medium / low). | ||||||||
| - Spikes don't carry severity or priority. | ||||||||
| - If the brief doesn't specify a level, ask once. Never default silently. | ||||||||
| 1. Critical | ||||||||
| 2. High | ||||||||
| 3. Medium | ||||||||
| 4. Low | ||||||||
|
|
||||||||
| ## gh Command | ||||||||
|
|
||||||||
| ```bash | ||||||||
| BODY_FILE=$(mktemp /tmp/gh_issue_body.XXXXXX.md) | ||||||||
| BODY_FILE=$(mktemp /tmp/gh_issue_body_XXXXXX) | ||||||||
|
|
||||||||
| cat > "$BODY_FILE" << 'EOF' | ||||||||
| <body> | ||||||||
|
|
@@ -48,11 +70,11 @@ EOF | |||||||
| gh issue create \ | ||||||||
| --title "<title>" \ | ||||||||
| --label "<type-label>" \ | ||||||||
| --label "<severity-or-priority-label>" \ # omit for spikes | ||||||||
| --label "<priority-label>" \ # omit for spikes | ||||||||
|
||||||||
| --label "<priority-label>" \ # omit for spikes | |
| # omit the next label for spikes | |
| --label "<priority-label>" \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in d1c6ed6. Moved the inline # omit for spikes comment to its own line so the backslash is the last character and correctly escapes the newline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
gh pr createsnippet is in abashcode block but includes bracketed placeholders like[--reviewer <handle> ...], which makes the command invalid if copied verbatim. Consider either (1) showing a valid minimal command and listing optional flags below, or (2) converting the bracketed lines to comments so the snippet remains runnable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a skill instruction document consumed by an AI agent, not a shell script meant to be executed verbatim. The
[--reviewer ...]bracket notation follows standard CLI synopsis conventions and is intentionally abstract -- the agent is expected to construct the actual flags from the prose instruction that follows. Changing brackets to shell comments would obscure the intent and reduce discoverability for the agent.