| name | create-pr |
| description | Opens a well-formed pull request for the Arcane repository following project conventions. Use when asked to create, open, or submit a pull request, or to push a branch and request a review. Enforces sentence-form PR titles (the symbol-based `type[scope]: Subject` format stays on commits — labels carry type and scope on the PR), branch naming conventions, structured PR body, and Assisted-by transparency trailer.
|
| compatibility | Requires git and GitHub CLI (gh) |
Arcane Pull Request Skill
Pre-flight checks
Before pushing anything:
-
Gather context in one pass (branch, issue number, push status, commits since base, files changed, untracked files):
bash .agents/skills/create-pr/scripts/gather-context.sh main
Use this output for the whole draft below instead of re-running git log / git diff / git status piecemeal.
-
Read .agents/skills/git-commit/SKILL.md for commit format and scope conventions.
-
Pick the matching PR template in .github/PULL_REQUEST_TEMPLATE/:
| Change type | Template | Distinguishing sections |
|---|
| New app / new infra component | feature.md | Infrastructure Components, Security Implementation, External Access Points, Integration Points, Future Enhancements |
| Restructure / consolidation / migration | refactoring.md | Rationale, Architecture Simplification, Security Boundary Preservation, Behavioural Changes, Migration Path, Next Steps |
| Bug fix / regression | bugfix.md | Root Cause, Behavioural Changes, Regression Risk, Observability |
| Anything else / unsure | pull_request_template.md (root, same as feature.md) | — |
The root pull_request_template.md is a copy of feature.md and applies by default. To use a non-default template
explicitly, append ?template=<name>.md to the PR URL or use gh pr create --web and pick from the GitHub picker.
-
List the last 3 merged PRs that are not dependency bumps for style reference:
gh pr list --limit 30 --state merged --json number,title,author \
| python3 -c "
import json, sys
prs = json.load(sys.stdin)
human = [p for p in prs if not p['author']['is_bot']][:3]
for p in human: print(p['number'], p['title'])
"
-
Run the commit validator and review its output:
bash .agents/skills/create-pr/scripts/validate_commits.sh main
The script prints OK, FAIL, or WARN for each check:
FAIL — must be fixed before opening the PR (symbol format, GPG signature, Assisted-by trailer).
WARN — Signed-off-by is missing. This is a user responsibility: the Signed-off-by line is a DCO legal
attestation that only the human committer can make. Surface the warning to the user and let them decide. Do not add
-s to any commit command yourself.
-
Run trunk check on the changed files and fix all issues before pushing:
trunk check --filter=-conftest
ISSUES reported — fix every lint/format error before creating the PR. Do not suppress trunk warnings with
trunk-ignore unless the warning is a known false positive; explain why in a comment when you do.
No issues — proceed to push.
Run trunk check locally rather than relying on the CI to catch it: a failed CI run after the PR is open costs a
round-trip and blocks review.
Branch naming
| Type | Pattern | Example |
|---|
| Feature | feat/<desc> | feat/forgejo-lungmen |
| Bug fix | fix/<desc> | fix/cert-manager-renewal |
| Documentation | docs/<desc> | docs/bootstrap-guide |
| Tooling | chore/<desc> | chore/renovate-config |
| Refactor | refactor/<desc> | refactor/network-policies |
| Issue-based | issue-<number>/<desc> | issue-661/consolidate-db |
Workflow
Opening a new PR
- Create a branch following the naming conventions above.
- Run the commit validator (step 5 above) and fix any
FAIL items; surface WARN items to the user.
- Run
trunk check --filter=-conftest (step 6 above) and fix all issues.
- Push:
git push -u origin <branch-name>
- Draft the PR body following the selected template — see
.github/PULL_REQUEST_TEMPLATE/<type>.md and
references/pr-examples.md.
- Create the PR with a sentence-form title that says what changes (no symbol prefix, no bracketed scope — the
commit symbol format is for git log, not for PR titles).
- Apply the scope label(s) matching the changed paths (
project, catalog, agents:skills, agents:sessions,
agents:knowledge, gh, deps) — see .github/labels.yaml. There is no type::* label for PRs; type is a
GitHub-native Issue Type field on issues only. Add size::* when you have signal.
Pushing follow-up commits to an existing PR
When adding a commit to a branch that already has an open PR:
-
Run the commit validator and trunk check as above.
-
Push the commit.
-
Ask the user whether the PR body needs updating to reflect the new changes:
A follow-up commit was just pushed to PR #N. Do you want me to review the PR body and update it to reflect the new
changes?
If the user says yes, fetch the current body with gh pr view --json body, diff it against the new commits, and
propose targeted edits to the Summary, Changes Made, and Testing Validation sections. Never silently rewrite the PR
body — always show the diff to the user before applying.
PR title format
Sentence form, no symbol prefix, no bracketed scope. Scope lives in the scope label (project, catalog,
agents:skills, …) — there's no type::* label for PRs, type is a GitHub-native Issue Type field on issues only. The
commit symbol format stays where it belongs — on commits — and is validated by commitlint there.
Examples:
| Bad (commit format applied to a PR) | Good (sentence + labels) |
|---|
+[project:lungmen.akn]: Add Forgejo Git hosting service | Add Forgejo as a self-hosted Git forge on lungmen |
![project:lungmen.akn]: WAL volume full | Fix CNPG apps-secured WAL retention on lungmen |
^[deps]: cert-manager to v1.16 | Bump cert-manager to v1.16 |
Rules:
- Sentence-case, no trailing period.
- ≤ 70 characters when possible (GitHub truncates around there in lists).
- Start with a verb —
Add …, Fix …, Replace …, Bump …, Document ….
- If the change is the implementation of a single issue, the PR title can be the same sentence as the issue title —
that's a feature, not a duplicate.
PR body structure
The templates in .github/PULL_REQUEST_TEMPLATE/ are the authoritative structure. Fill them in honestly and concisely.
The three templates share the same skeleton — only the Technical Impact sub-sections (and a few specific sections like
Rationale or Root Cause) differ.
Shared skeleton (all templates)
## Summary
2–4 sentences: what changed, why now, strategic context (phase in a plan, motivating issue). Always include "**Related
Issue:** #<n>".
## Changes Made
### <Subsystem or component name>
Optional 1-line intro describing the subsystem, then file list:
- **[`path/to/file.yaml`](path/to/file.yaml)** — what it does and why it exists
- Removed: `legacy-file.yaml` — replaced by …
## Technical Impact
(See template-specific sub-sections below.)
## Testing Validation
- [ ] Pods reach `Running` status in the target namespace
- [ ] ExternalSecrets sync successfully
- [ ] <service-specific check>
## Related Issues
Closes #<number> <!-- or "Addresses #X (Phase N)" for multi-phase work -->
---
<sub>AI-assisted with <provider>:<model-id> under human supervision</sub>
Template-specific Technical Impact sub-sections
feature.md — keep sections that apply, remove the rest:
### Infrastructure Components
New services, charts, operators introduced and their role.
### Security Implementation
Network policies (Cilium), OpenBao paths, ExternalSecrets, OIDC, attack surface.
### External Access Points
HTTPRoute / TCPRoute hostnames, LoadBalancer services, OIDC integration.
### Integration Points
How this connects to existing systems (databases, registry, gateways).
Plus an optional ## Future Enhancements section after Technical Impact for follow-up work intentionally left out of
scope.
refactoring.md — adds ## Rationale (right after Summary, before Changes Made) explaining why this refactor now.
Technical Impact sub-sections:
### Architecture Simplification
Before/after comparison — use a table when the gain is striking.
| Before | After |
| ------ | ----- |
| | |
### Security Boundary Preservation
Confirm isolation, secrets, network policies preserved or improved.
### Behavioural Changes
None (pure refactor) / observable differences for users or operators.
### Migration Path
Steps required to roll out without downtime, if any.
### Next Steps
Subsequent phases — keep when this PR is a milestone within a larger plan.
The ### Removed sub-section inside Changes Made is required for refactors.
bugfix.md — adds ## Root Cause (right after Summary) with logs/metrics that confirm the diagnosis, then ## Fix
instead of ## Changes Made. Technical Impact sub-sections:
### Behavioural Changes
What changes for users / operators after the fix.
### Regression Risk
Low / Medium / High — justify the rating, list affected areas and mitigation.
### Observability
Logs / metrics / probes that will confirm the fix in production.
Creating the PR
Always write the PR body to a temp file and use --body-file. Never use --body "$(cat <<'EOF' ... EOF)" — shell
quoting in that pattern causes agents to escape backticks as ```, which GitHub renders literally and corrupts the
body.
git push -u origin <branch-name>
cat > /tmp/pr_body.md << 'PREOF'
<body following the selected template>
PREOF
gh pr create \
--title "Sentence describing the change" \
--body-file /tmp/pr_body.md \
--base main \
--label "project"
rm /tmp/pr_body.md
Rules
- PR title: sentence-case English, verb-first, no symbol prefix, no bracketed scope, no trailing period. The commit
symbol format stays on commits.
- PR labels: the scope label(s) matching the changed paths are mandatory (see
.github/labels.yaml: project,
catalog, agents:skills, agents:sessions, agents:knowledge, gh, deps). Add size::* when you have signal;
priority::* is issues-only.
- Commits: All commits must have the symbol-based
type[scope]: Subject format, GPG signature (-S), and
Assisted-by: trailer. Signed-off-by is the user's responsibility — never add -s yourself.
- PR body line length: No hard limit — do NOT wrap PR body text at 80 characters. GitHub renders Markdown, so
natural prose flow is preferred over artificial line breaks. The 80-char rule applies only to git commit bodies, not
PR descriptions.
- File paths: Link files in Changes Made using
[path](path) markdown syntax
- Technical Impact: Always present with named sub-sections — never just a bare Before/After table or a flat
bullet list. Drop sub-sections that don't apply rather than leaving them empty.
- Refactor PRs:
## Rationale is mandatory; explains why this refactor now.
- Bugfix PRs:
## Root Cause must include evidence (logs/metrics), not just a hypothesis.
- Attribution: Always end the body with
<sub>AI-assisted with <provider>:<model-id> under human supervision</sub>
- Issue reference: Always include
Closes #number or Addresses #number (Phase N) when applicable
- No broken state: Never create a PR with known broken manifests or missing secrets
Examples
See references/pr-examples.md for real merged PRs from this repository, each illustrating one of the three templates.
Good — feature PR with named Technical Impact sub-sections:
git push -u origin feat/forgejo-lungmen
cat > /tmp/pr_body.md << 'PREOF'
Adds Forgejo as a self-hosted Git forge to the lungmen.akn cluster. Forgejo is
a lightweight software forge providing GitHub-compatible Git hosting
capabilities for personal infrastructure development.
**Related Issue:**
- **[`forgejo.deployment.yaml`](projects/lungmen.akn/src/apps/forgejo/forgejo.deployment.yaml)** — Deploys Forgejo 10.0 from Codeberg official image
- **[`forgejo.httproute.yaml`](projects/lungmen.akn/src/apps/forgejo/forgejo.httproute.yaml)** — External access via Envoy Gateway on forgejo.chezmoi.sh
- **[`forgejo.database.externalsecret.yaml`](projects/lungmen.akn/src/apps/forgejo/forgejo.database.externalsecret.yaml)** — PostgreSQL credentials from OpenBao
- **[`security/network-policy.default-hardened.yaml`](projects/lungmen.akn/src/apps/forgejo/security/network-policy.default-hardened.yaml)** — Default-deny baseline for the forgejo namespace
Forgejo 10.0 deployed from the Codeberg official image, backed by a shared
CloudNative-PG cluster in the `databases` namespace. Persistent storage uses
the SMB CSI driver against the NAS for repository data.
Zero-trust namespace with a default-deny Cilium policy and explicit allowlists
for Envoy Gateway ingress, PostgreSQL egress, and SSH L7 inspection. All
credentials sourced from OpenBao via ExternalSecrets Operator.
- HTTP/HTTPS: `forgejo.chezmoi.sh` via Envoy Gateway HTTPRoute
- SSH: dedicated LoadBalancer service for Git clone/push operations
- OIDC client credentials provisioned for future Pocket-Id SSO integration
- [ ] Forgejo pods reach `Running` status in `forgejo` namespace
- [ ] ExternalSecrets sync for database, admin, and OIDC credentials
- [ ] HTTPRoute `forgejo-websecure` reports `Accepted` status
- [ ] Database connection succeeds (check pod logs for PostgreSQL errors)
- [ ] Cilium network policies enforce expected traffic isolation
- Configure OIDC provider settings in Forgejo for Pocket-Id SSO
- Enable Forgejo Actions for CI/CD capabilities
Closes
---
<sub>AI-assisted with Z.ai:GLM-4.7 under human supervision</sub>
PREOF
gh pr create \
--title "Add Forgejo as a self-hosted Git forge on lungmen" \
--body-file /tmp/pr_body.md \
--base main \
--label "project"
rm /tmp/pr_body.md
Bad — empty body, no labels, title doesn't say what changes:
gh pr create \
--title "Forgejo"
--body "Added Forgejo as described in issue #973"
Review checklist
References
- Commit rules:
.agents/skills/git-commit/SKILL.md
- PR templates:
.github/PULL_REQUEST_TEMPLATE/ (feature.md, refactoring.md, bugfix.md)
- Real PR examples:
.agents/skills/create-pr/references/pr-examples.md
- Project overview:
AGENTS.md
- Commit config:
.commitlintrc.js