From 360292ee708d5fe2258e74fca76a342ce778b7bf Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 2 Sep 2026 11:55:57 +0200 Subject: [PATCH 1/2] Add agentic skills for commits, PRs, and issues Port the commit, pr, and issue skills from elastic/docs-builder. The skills encode plain-language writing rules, full-URL linking, and a PR lifecycle check that refreshes stale descriptions after follow-up commits. Co-Authored-By: Claude --- .claude/skills/commit/SKILL.md | 95 +++++++++++++++++++ .claude/skills/issue/SKILL.md | 118 +++++++++++++++++++++++ .claude/skills/pr/SKILL.md | 160 ++++++++++++++++++++++++++++++++ .claude/skills/writing-style.md | 134 ++++++++++++++++++++++++++ 4 files changed, 507 insertions(+) create mode 100644 .claude/skills/commit/SKILL.md create mode 100644 .claude/skills/issue/SKILL.md create mode 100644 .claude/skills/pr/SKILL.md create mode 100644 .claude/skills/writing-style.md diff --git a/.claude/skills/commit/SKILL.md b/.claude/skills/commit/SKILL.md new file mode 100644 index 0000000..6b9a0d6 --- /dev/null +++ b/.claude/skills/commit/SKILL.md @@ -0,0 +1,95 @@ +--- +name: commit +description: Stage relevant files and create a well-formed git commit. Use this when the user asks to commit changes, save work, or create a commit. +--- + +# Commit Skill + +Read [`.claude/skills/writing-style.md`](../writing-style.md) before writing the commit message. + +Creates a clean, well-formed commit following this project's conventions. + +## Steps + +### 1. Check for project hooks + +If the repo has a hook runner, ensure it is installed before committing. Common patterns: + +```bash +# Husky.Net (dotnet) +if [ -f .husky/task-runner.json ] && [ ! -f .husky/_/husky.sh ]; then + dotnet tool restore && dotnet husky install +fi + +# Husky (Node) +# hooks install automatically via npm ci / npm install + +# lefthook +if [ -f lefthook.yml ] || [ -f .lefthook.yml ]; then + lefthook install +fi +``` + +Do not use `--no-verify`. + +### 2. Understand what changed + +```bash +git status +git diff +git diff --staged +git log --oneline -5 +``` + +### 3. Stage files + +Stage specific files by name — never `git add -A` or `git add .` blindly. Exclude: +- `.env` files or anything with secrets/credentials +- Large binaries not already tracked +- Unrelated changes to the task at hand + +### 4. Write the commit message + +- **First line**: Imperative mood, ≤72 chars, no trailing period. Front-load the outcome — a reader scanning `git log` sees this line only. +- **Body** (optional): One short paragraph explaining *why*, not what. Skip if the title is self-explanatory. Follow the sentence mechanics in `writing-style.md`. +- **Trailer**: Add a `Co-Authored-By:` line that identifies the model that helped write this commit. Use whatever attribution feels accurate — the model name you know yourself to be running as, or simply `Claude` if you are uncertain. The address is always `noreply@anthropic.com`. The point is honest attribution, not a precise version string. + +Always pass the message via HEREDOC to avoid shell escaping issues: + +```bash +git commit -m "$(cat <<'EOF' +Title here + +Optional body explaining why. + +Co-Authored-By: Claude +EOF +)" +``` + +### 5. Handle hook failures + +If a git hook fails: +1. Read the error output carefully +2. Fix the underlying issue (formatting, linting, type errors — whatever the hook checks) +3. Re-stage the affected files +4. Create a **new commit** — never `git commit --amend` for a failed commit, and never use `--no-verify` + +### 6. Verify success + +```bash +git status +``` + +Confirm a clean working tree. + +### 7. Refresh the PR description if one exists + +```bash +gh pr view --json number,url,isDraft,baseRefName --jq '{number,url,isDraft,baseRefName}' 2>/dev/null +``` + +- **No PR** → done. Say nothing. +- **PR exists** → compare the current body against the current diff versus the PR's base branch. A PR description always describes the current diff against the base branch. It is never a log of the commits on the branch and never records the direction the work took. If any section (`## What`, `## Verify`, or the lead paragraph) no longer describes that diff, the description is stale. +- **Stale description** → read and follow [pr](../pr/SKILL.md)'s update path (step 7). Do not hand-edit the body inline from the commit skill. State plainly what was refreshed. +- **Still accurate** → state that the description is still accurate. No edit needed. diff --git a/.claude/skills/issue/SKILL.md b/.claude/skills/issue/SKILL.md new file mode 100644 index 0000000..363a49f --- /dev/null +++ b/.claude/skills/issue/SKILL.md @@ -0,0 +1,118 @@ +--- +name: issue +description: File a well-formed bug report or feature request. Use when the user asks to open an issue, report a bug, or request a feature. +--- + +# Issue Skill + +Read [`.claude/skills/writing-style.md`](../writing-style.md) before writing anything. + +Files a GitHub issue that matches the repo's templates, applies correct labels, and checks for duplicates first. + +## Steps + +### 1. Check for duplicates + +Search for near-duplicates before opening anything. Link any you find in the issue body rather than filing a second. Use the full URL form for all links — see `## Linking to issues and pull requests` in [`writing-style.md`](../writing-style.md). + +```bash +gh issue list --search "" --limit 10 +``` + +### 2. Determine the issue type + +- **Bug** — something that used to work stopped, or produces wrong output. Use `bug-report` structure. +- **Feature / enhancement** — something that does not exist yet, or needs to be better. Use `enhancement` structure. + +### 3. Write the title + +- ≤70 characters, no trailing period +- States the observable problem or the wanted capability — not the internal cause or the implementation + +### 4. Write the body + +**Bug report:** + +``` + + +### What happened + + + +### How to reproduce + + + +### Version or commit + + +``` + +**Feature request:** + +``` + + +### What is getting in your way + + + +### What would you like instead + + + +### Anything else + + +``` + +Formatting rules: +- Same plain-language rules as PR bodies — active voice, short sentences, no mechanical noun clusters. +- Commands and error messages in fenced blocks. +- Backticks on all identifiers: flags, config keys, file paths, method names. +- Skip any section that has nothing to say — a blank section adds noise, not structure. + +### 5. Choose labels + +Apply the labels this repo defines. Common defaults: + +1. **Type** (required): `bug` or `enhancement` +2. **Area** (one, if the repo defines area labels): pick the label that matches the affected subsystem +3. **`needs triage`** (if the repo uses it) + +Do not invent new labels. Check `.github/` or the repo's CONTRIBUTING guide for the label set. + +### 6. Create the issue + +One call — title, labels, and body together: + +```bash +gh issue create \ + --title "" \ + --label "bug,needs triage" \ + --body "$(cat <<'EOF' +<lead sentence> + +### What happened + +... + +### Version or commit + +... +EOF +)" +``` + +Replace `bug` with `enhancement` for feature requests. Omit area or triage labels if the repo does not use them. + +### 7. Return the issue URL + +Always print the URL so the user can open it directly. diff --git a/.claude/skills/pr/SKILL.md b/.claude/skills/pr/SKILL.md new file mode 100644 index 0000000..7e9f2bf --- /dev/null +++ b/.claude/skills/pr/SKILL.md @@ -0,0 +1,160 @@ +--- +name: pr +description: Create a GitHub pull request with a focused why/what body. Use when the user asks to open a PR, create a pull request, or ship a branch. +--- + +# PR Skill + +Read [`.claude/skills/writing-style.md`](../writing-style.md) before writing anything. + +Creates a GitHub PR body that a newcomer can orient from in under a minute: front-loaded outcome, grounded Why, behaviour-led What, verifiable by the reviewer. + +## Steps + +### 1. Understand the branch + +```bash +git status +git log main..HEAD --oneline +git diff main...HEAD --stat +``` + +### 2. Commit uncommitted work if needed + +If the working tree has changes that belong in this PR, read and follow [commit](../commit/SKILL.md) first. Do not commit inline. Do not skip hooks. + +### 3. Push (if needed) + +```bash +git push -u origin HEAD +``` + +### 4. Write the PR title + +- ≤70 characters, imperative mood, no trailing period +- States what changed at a human level — not a file list, not a symbol name +- No `[bug]` / `[feature]` / `[chore]` prefixes — the label carries the type + +### 5. Write the PR body + +Required structure: + +``` +<One or two sentences, no heading. What this changes and the effect. + A newcomer reads only this and knows whether the PR concerns them.> + +**Prompt summary:** <One paragraph, two to three sentences. What the author was asked + to do in their own framing — the goal behind the branch, not the diff. Present tense, + active voice. This is the ask; ## Why is the problem in the code. They are not + interchangeable and neither restates the other. Omit only when the branch has no + originating ask.> + +## Why +<Two to four sentences. The concrete failure or gap. Active voice, present tense + for current behaviour. Do not open with the history of a prior PR.> + +## What + +#### <Conceptual label — not a filename> +<Prose paragraph. Group by what changed conceptually, not by which files moved. + Lead with behaviour. Name a symbol only when the reviewer needs it to find the code. + Two to four sentences. Three to five sections total.> + +## Verify +<How a reviewer confirms this locally. Use real commands they would run. + If there is no clear local verification step, omit this section entirely. + Do not list CI checks or scripts an agent would run to prove their own work — + those are not reviewer steps.> +``` + +Conditional add-ons — each is one or two sentences with a bold lead-in, no heading: + +- **Breaking** — what a consumer must change and when it bites them. +- **Out of scope** — a gap this PR deliberately leaves, so a reviewer does not raise it as a finding. +- **Risk** — shared or production state this touches. Required when the change reaches infrastructure, credentials, or shared data stores. +- **Stack** — position and links when this is one of several stacked PRs: `3 of 5, on top of [#42](https://github.com/org/repo/pull/42)`. A bare `Stack: 3/5` with no links is not enough. + +**Do not** include bullet lists of changed files. Do not summarize what the diff already states plainly. + +### 6. Apply labels + +Apply the labels this repo uses. Common defaults: + +| Label | Use when | +|---|---| +| `bug` | A defect in existing behaviour is fixed | +| `enhancement` or `feature` | A capability is added or improved | +| `chore` | Cleanup, refactor, internal restructure — no user-visible change | +| `documentation` | Docs-only change | +| `dependencies` | Dependency version bumps | + +Check `.github/workflows/` or the repo's CONTRIBUTING guide for any enforced label policy before creating the PR. + +### 7. Check whether a PR already exists + +```bash +gh pr view --json number,url,baseRefName --jq '{number,url,baseRefName}' 2>/dev/null +``` + +**If a PR exists — update it.** + +Rebuild the body from the cumulative diff against the PR's own base branch (not a hardcoded `main`): + +```bash +git diff origin/<baseRefName>...HEAD --stat +git diff origin/<baseRefName>...HEAD +``` + +Write the description of **the current diff against the base** — never a log of the commits on the branch and never an "update" or "addendum" appended to the old body. Any section of the old body that no longer matches the diff is wrong, not history. Replace it. + +- Preserve the original `**Prompt summary:**` verbatim unless the ask itself changed; extend it rather than replace it when scope was added. +- Reassess the label — added commits can shift a `chore` to a `bug`. +- Apply in one call: + +```bash +gh pr edit --title "<title>" --body "$(cat <<'EOF' +<new body> +EOF +)" +``` + +Add or remove the label only if it changed: + +```bash +gh pr edit --add-label "<new-label>" --remove-label "<old-label>" +``` + +**If no PR exists — create it.** Proceed to step 8. + +### 8. Create the PR + +One call — title, label, and body together. No follow-up `gh pr edit`: + +```bash +gh pr create --title "<title>" --label "<label>" --body "$(cat <<'EOF' +<lead sentence(s)> + +**Prompt summary:** ... + +## Why + +... + +## What + +#### ... + +... + +## Verify + +```bash +<command> +``` +EOF +)" +``` + +### 9. Return the PR URL + +Always print the URL so the user can open it directly. diff --git a/.claude/skills/writing-style.md b/.claude/skills/writing-style.md new file mode 100644 index 0000000..1f873e8 --- /dev/null +++ b/.claude/skills/writing-style.md @@ -0,0 +1,134 @@ +# Writing style for commits, PRs, and issues + +Every commit message, PR body, and issue filed in this repo follows these rules. +Skills that write those artifacts read this file first. + +--- + +## Governing principles (ISO 24495-1) + +- **Relevant** — write for a reviewer who has not seen the branch. Cut whatever the diff already states plainly. +- **Findable** — the first sentence states the outcome. A newcomer reads it and knows whether the change concerns them. +- **Understandable** — plain words, short sentences, no assumed context. +- **Usable** — after reading, the reviewer can evaluate, revert, or reproduce. + +--- + +## Sentence mechanics (ASD-STE100, adapted) + +*The ASD-STE100 sentence rules apply here. Its ~900-word approved vocabulary does not — it rejects `assembler`, `idempotent`, and `reconciliation` and its clipped imperative register produces mechanical prose. Take the mechanics, drop the dictionary.* + +- Active voice. Name the actor. "A retry clears the lock", not "The lock is cleared on retry". +- One idea per sentence. Around 25 words maximum. +- Six sentences maximum per paragraph. +- Present tense for how the code behaves now; past tense only for what it used to do. +- No noun cluster longer than three words. "shallow clone lock collision guard" → "a guard against lock collisions in shallow clones". +- One term per concept, every time. Do not alternate *job* / *step* / *task* for the same thing. +- Keep articles: "The assembler runs", not "Assembler runs". +- One subordinate clause per sentence. No em-dash pile-ups. + +--- + +## Fenced blocks for anything runnable + +A command, a config snippet, a YAML fragment, an error message, or a stack trace goes in a fenced code block with a language tag — not inline, however short. + +Inline backticks *name* a thing. A fenced block holds something the reader runs, pastes, or reads as output. + +- `--no-delete` inline (naming the flag) +- Command in a block: + ```bash + my-tool deploy --no-delete preview + ``` + +Test names go in `#` comments inside the block next to the command that runs them, not in prose beside it: + +```bash +dotnet test tests/MyProject.Tests/ +# MyMethod_Scenario_Expected — the case being verified +``` + +--- + +## Backticks, used liberally + +Every identifier gets backticks, every time, including repeat mentions. This covers: + +- Types, methods, properties, fields: `MyService.Fetch`, `Cache.ClearStale` +- CLI commands and flags: `my-tool`, `--no-delete`, `--verbose` +- Env vars: `MY_APP_API_KEY` +- File and directory paths: `ci.yml`, `src/MyProject/` +- YAML keys (with trailing colon): `output:`, `items:` +- Config values, labels, package names, branch names, exit codes + +Prose that names a symbol bare is wrong even when it reads fine: + +| Wrong | Right | +|---|---| +| MyService.Fetch returned void | `MyService.Fetch` returned `void` | +| pass --no-delete | pass `--no-delete` | +| the synthetics job in ci.yml | the `synthetics` job in `ci.yml` | + +Do **not** backtick prose nouns that merely share a name with code — the assembler, the scrubber, a profile — unless you mean the literal identifier. + +--- + +## Linking to issues and pull requests + +Link liberally. Any issue, PR, or discussion named in prose gets a link — first mention and every repeat. + +**Always the full URL.** Never a bare `#3855`, and never a short form like `elastic/repo#3855`. GitHub resolves `#num` against whatever repo the current page lives in — the same body pasted into a different repo silently points at the wrong thing. + +Markdown form: `[#3855](https://github.com/elastic/docs-builder/pull/3855)`. Keep the `#num` as link text so it stays scannable in a git log or review tool that strips HTML. + +Cross-repo mentions include the org and repo in the link text: + +``` +[elastic/docs-actions#412](https://github.com/elastic/docs-actions/pull/412) +``` + +| Wrong | Right | +|---|---| +| See #42 for context | See [#42](https://github.com/org/repo/pull/42) for context | +| other-org/other-repo#7 fixed this | [other-org/other-repo#7](https://github.com/other-org/other-repo/pull/7) fixed this | +| Refs #99 | Refs [#99](https://github.com/org/repo/issues/99) | + +--- + +## Anti-mechanical rules for "What" sections + +`## What` uses `####` subheadings, not bullet points. Each subheading names the thing that changed (a file, a command, a concept). The prose under it states what changed and why it matters — two to four sentences, same plain-language rules as everywhere else. + +Lead with the behaviour change; name the symbol second when the reviewer needs it to find the code. + +| Wrong | Right | +|---|---| +| `` `Cache.ClearStale` sweeps `*.lock` files before each retry `` | A retry clears stale `*.lock` files before it runs | +| `` **`BuildService.Build`**: captures the `GenerateAll` result `` | The build result is captured and written to the output directory | + +**Banned openers** for the prose paragraph: +- "Added", "Updated", "Changed", "Refactored", "Modified" +- Any sentence that starts with a file path or a symbol name +- Any paragraph that only restates the subheading + +**Three to five `####` sections in a "What".** More than five is a signal the change should be split. + +--- + +## Before/after examples + +These are the rules in action. If a new rule does not survive this test, the rule is wrong. + +**`## What`, first bullet:** + +> ❌ `Cache.ClearStale(IFileSystem, ...)` sweeps `*.lock` files under `.git/` before each retry. Called only from the retry path — never before attempt 1, where a lock could belong to a concurrent process. + +> ✅ A retry clears stale `*.lock` files before it runs. The first attempt is never affected — a lock there can belong to a live process. + +**Opening of `## Why`:** + +> ❌ [#42](https://github.com/org/repo/pull/42) tried to fix stale pool listings by bringing back `registry.json`. That approach was rejected… + +> ✅ Scrubbing strips `prs:` from the public copies of private-repo entries. `--prs` joined only on that YAML field, so those entries dropped out with no error. + +The history of a rejected PR is not the problem this PR solves. Lead with the problem. From 6c078ad4ed50d329a80bdb04791b087e70b055b3 Mon Sep 17 00:00:00 2001 From: Martijn Laarman <Mpdreamz@gmail.com> Date: Wed, 2 Sep 2026 12:05:44 +0200 Subject: [PATCH 2/2] Gitignore .claude/settings.local.json settings.local.json holds personal Claude Code overrides and should not be shared. The rest of .claude/ (skills, settings.json, hooks) is team-shared and stays committed. Co-Authored-By: Claude <noreply@anthropic.com> --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 7270b4e..8a61ed2 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,6 @@ docs-temp .artifacts/ .cursor/ + +# Claude Code personal settings +.claude/settings.local.json