Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "claudius",
"version": "4.3.0",
"version": "4.4.0",
"description": "Collection of specialized development agents and skills for Claude Code",
"author": {
"name": "lklimek",
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). This project use

## [Unreleased]

## [4.4.0] - 2026-05-29

### Changed

- PR bodies now lead with a "Why this PR exists" rationale section (problem, reproduction/threat scenario, blocking relationship) before What/Testing/Breaking/Checklist. The skeleton lives in `skills/git-and-github/SKILL.md` (§Creating a PR); `skills/push/SKILL.md` delegates to it. Pinned by `tests/test_pr_body_template.py`.

## [4.3.0] - 2026-05-29

### Added
Expand Down
17 changes: 16 additions & 1 deletion skills/git-and-github/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,22 @@ If a push fails with 403 or "Resource not accessible" and `ghsudo` is installed,

Check for a PR template first. If a template exists, read and fill it in. When applicable, include an informal user story (what the user can achieve, no technical details -- start with "Imagine you are...").

Always create PRs as drafts.
The PR body **must lead with a `## Why this PR exists` section** — reviewers read motivation before mechanics. Use this skeleton (drop empty sections):

```markdown
## Why this PR exists
- **Problem**: 1-2 plain-language sentences on what's broken or missing.
- **What breaks without it**: a concrete reproduction or threat scenario — numbered steps or a short narrative showing the actual failure/misbehaviour, not an abstract claim.
- **Blocking relationship**: prerequisite for / depends on / stacked atop PR #N, if any.

## What was done
## Testing
## Breaking changes
## Checklist
## Attribution
```

`Why this PR exists` comes first; the remaining sections follow in that order. Always create PRs as drafts.

### Reviewing a PR

Expand Down
1 change: 1 addition & 0 deletions skills/push/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Load `claudius:git-and-github` skill first — all commit, push, PR, and attribu
4. **Push** to remote

5. **PR**
- PR body MUST lead with a "Why this PR exists" section per `git-and-github` §Creating a PR
- If PR exists for this branch: update its title and description to reflect current changes
- If no PR: create a draft PR with summary + test plan per `git-and-github`

Expand Down
58 changes: 58 additions & 0 deletions tests/test_pr_body_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Regression guard: the canonical PR-body template must lead with "Why this PR exists".

PR descriptions are owned by ONE skill (`git-and-github`); `push` delegates to it.
This test pins the contract so a refactor can't silently demote the rationale section
below the mechanics: `git-and-github/SKILL.md` must define a "Why this PR exists"
heading positioned AHEAD of the "What was done"/"Testing" sections, and `push/SKILL.md`
must reference it rather than inlining a duplicate template.
"""

from __future__ import annotations

from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent
GIT_GITHUB = REPO_ROOT / "skills" / "git-and-github" / "SKILL.md"
PUSH = REPO_ROOT / "skills" / "push" / "SKILL.md"

WHY = "Why this PR exists"


def test_git_github_has_why_section() -> None:
text = GIT_GITHUB.read_text(encoding="utf-8")
assert (
f"## {WHY}" in text
), f"{GIT_GITHUB}: missing '## {WHY}' heading in PR-body template"


def test_why_section_leads_what_and_testing() -> None:
text = GIT_GITHUB.read_text(encoding="utf-8")
why = text.index(f"## {WHY}")
what = text.index("## What was done")
testing = text.index("## Testing")
assert why < what, f"{GIT_GITHUB}: '{WHY}' must precede 'What was done'"
assert why < testing, f"{GIT_GITHUB}: '{WHY}' must precede 'Testing'"

Comment thread
lklimek marked this conversation as resolved.

def test_why_section_demands_reproduction_and_blocking() -> None:
"""The skeleton must prompt for a concrete repro/threat scenario and blocking relationship."""
text = GIT_GITHUB.read_text(encoding="utf-8")
lowered = text.lower()
assert (
"reproduction" in lowered or "threat scenario" in lowered
), f"{GIT_GITHUB}: '{WHY}' skeleton must ask for a reproduction or threat scenario"
assert (
"blocking" in lowered
), f"{GIT_GITHUB}: '{WHY}' skeleton must ask for the blocking relationship"
Comment thread
lklimek marked this conversation as resolved.


def test_push_references_why_without_inlining_template() -> None:
text = PUSH.read_text(encoding="utf-8")
assert WHY in text, f"{PUSH}: must reference the '{WHY}' section"
assert (
"git-and-github" in text
), f"{PUSH}: must delegate to git-and-github for the template"
# No duplicated skeleton: push references the section, it doesn't redefine the heading.
assert (
f"## {WHY}" not in text
), f"{PUSH}: must not inline a duplicate '## {WHY}' template — delegate to git-and-github"
Loading