diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md new file mode 100644 index 0000000..e9835e7 --- /dev/null +++ b/.claude/commands/commit.md @@ -0,0 +1,45 @@ +--- +description: Commit recent changes using a conventional commit message, after syncing with the remote. +allowed-tools: Bash(git status:*), Bash(git pull:*), Bash(git merge:*), Bash(git diff:*), Bash(git add:*), Bash(git commit:*), Bash(git push:*), AskUserQuestion, Edit, Write +model: haiku +--- +Commit recent changes using a conventional commit message, after syncing with the remote. + +## Steps + +1. **Check git status** — run `git status` to see staged, unstaged, and untracked files. If there is nothing staged, stage recent changes. If there are unstaged or changed files from previous sessions group them (if possible) and use `AskUserQuestion` to ask the user if these changes shall be committed as well. + +2. **Sync** — run `git pull --rebase` to incorporate upstream changes before committing. If it fails (e.g. conflicts), stop and report the error; do not proceed. + +3. **Analyze the diff** — run `git diff --cached` to understand what is staged. + +4. **Draft a conventional commit message** following this format: + ``` + (): + + + ``` + - **type**: `feat` | `fix` | `docs` | `style` | `refactor` | `test` | `chore` | `perf` | `ci` | `build` | `revert` + - **scope**: affected module/component (omit if unclear or cross-cutting) + - **short description**: imperative mood, lowercase, no trailing period, ≤ 72 chars + - **body**: wrap at 72 chars; omit if the subject line is self-explanatory + - Add `BREAKING CHANGE: ` in the footer if applicable + +5. **Update CHANGELOG.md** — prepend an entry for this commit under the `[Unreleased]` section (create the file and section if absent), but **only if the change is user-facing** — a new feature, fix, or behaviour change someone using the Coil app would notice. Skip this step for CI/CD, docs-only, or internal tooling changes (see AGENTS.md's "Project conventions"). Format: + ``` + ## [Unreleased] + + ### + - + ``` + Categories: `Added` | `Changed` | `Fixed` | `Removed` | `Security` | `Performance`. Use one category block per entry; add multiple bullets if needed. Stage `CHANGELOG.md` with `git add CHANGELOG.md` before committing. + +6. **Confirm** — use `AskUserQuestion` with two questions in a single call: + 1. Show the drafted commit message and CHANGELOG entry (or note that none was needed); ask the user to approve or request changes. + 2. Ask whether to push after committing. + +7. **Commit** — once confirmed, run `git commit -m ""`. + +8. **Merge** — run `git merge --no-edit origin/main` to pull in any commits that landed on `main` (e.g. PR merge commits) so this branch stays up-to-date with the base branch; skip this if already on `main`. If it fails, stop and report the error. + +9. **Push** — if the user chose to push, run `git push`. Note: `main` is branch-protected (PR required, enforced for admins) — a direct `git push` to `main` will be rejected. If not already on a feature branch, push there and open a PR instead. diff --git a/.claude/commands/release.md b/.claude/commands/release.md new file mode 100644 index 0000000..9abe680 --- /dev/null +++ b/.claude/commands/release.md @@ -0,0 +1,105 @@ +--- +description: Bump the app version (major/minor/patch or explicit semver), update CHANGELOG.md and What's New files, and commit the release. +allowed-tools: Bash(git status:*), Bash(git pull:*), Bash(git diff:*), Bash(git add:*), Bash(git commit:*), Bash(git push:*), Read, Edit, Bash, AskUserQuestion, TodoWrite, TodoRead +model: haiku +--- + +You are preparing a new release for the Coil Android app. Your job is to determine the new version number, update `app/build.gradle.kts`, restructure `CHANGELOG.md`, generate What's New files, and commit the result. + +## Step 0 — Check readiness + +Coil is pre-implementation until Phase 1 (module skeleton) lands — see `AGENTS.md`. If `app/build.gradle.kts` does not exist yet, stop and tell the user this command cuts an actual app release and needs the `:app` module first; there is nothing to version yet. + +## Input + +The user may have provided an argument: `$ARGUMENTS` + +Interpret `$ARGUMENTS` as follows: +- `major`, `minor`, or `patch` (case-insensitive) → bump that semver component +- A full semver string like `1.0.4` or `2.0.0` → use it exactly +- Empty / blank → analyse the changelog and suggest a bump type (see below) +- Anything else → it is unusual; flag it and ask for confirmation or correction + +## Step 1 — Read current version + +Read `app/build.gradle.kts` and extract: +- `versionName` (e.g. `"0.1.0"`) +- `versionCode` (integer) + +## Step 2 — Determine target version + +**If argument is `major` / `minor` / `patch`:** +Compute the new semver by incrementing the appropriate component (reset lower components to 0). + +**If argument is a valid semver (`X.Y.Z` with all three numeric parts):** +Use it as-is. Warn (but don't block) if the new version is less than or equal to the current one. + +**If argument is empty:** +Read the `## [Unreleased]` section of `CHANGELOG.md`. Based on the entries: +- Any `### Added` → at minimum a `minor` bump +- Only `### Fixed` or `### Changed` (no new features) → `patch` +- Breaking change indicated (rare) → `major` + +Formulate a suggestion with a one-sentence rationale. + +**If argument is unusual** (e.g. only two parts like `1.2`, has leading zeros, non-numeric, etc.): +Do not proceed. Ask the user to confirm or correct. + +## Step 3 — Confirm with user + +Before making any changes, use **AskUserQuestion** to confirm. Show: +- Current version +- Proposed new version +- New versionCode (current + 1) + +Single question, two options: "Proceed" and "Cancel / change". If the user cancels or provides a correction, re-evaluate from Step 2 with their input, then confirm again. + +## Step 4 — Apply changes + +**4a. Create What's New files** + +Create `docs/whatsnew/X.Y.Z-{LOCALE}` files for Coil's launch locales: + +- `en-US` (source language — write this one first, the others are translations of it) +- `de-DE` +- `fr-FR` +- `es-ES` +- `nl-NL` + +Each file should contain a short user-facing summary of the release (**max 300 characters** to leave margin for translations, which can expand the text). Base the content on the `## [Unreleased]` section of `CHANGELOG.md`, but write it in plain language for end users — not a technical log. Do not copy changelog bullet points verbatim. + +Only include changes that are visible or relevant to the user of the app itself. Exclude anything related to CI/CD workflows, GitHub Actions, GitHub Pages, the website, internal tooling, or other infrastructure — users don't see these. + +**Translation quality:** per `AGENTS.md` and `README.md`, unreviewed machine translation is not acceptable for anything a user reads. Draft each locale's text yourself, but flag clearly to the user that the `de-DE`/`fr-FR`/`es-ES`/`nl-NL` drafts need a fluent-speaker review pass before the release actually ships — same standard as `res/values-/strings.xml`. + +**Translation note:** Keep English concise (max 300 chars) as a starting point, but the 300/500 numbers are guidance for the *English draft* only, not a guarantee for the rest. Translation length varies a lot by language (Romance/Germanic languages tend to expand, CJK languages tend to compress) — a translation that started from an in-limit English draft can still end up over the limit. + +**Mandatory per-locale check — do this for every locale, not just English:** after writing (or translating) each `docs/whatsnew/X.Y.Z-{LOCALE}` file, check that specific file's character count (e.g. `wc -m`, not `wc -c`, since `wc -c` undercounts multi-byte UTF-8 content). No locale file may exceed 500 characters — Google Play's hard "what's new" limit is per-locale, not per-release. If any file is over, re-trim that locale's text (summarize/combine bullets) and re-check — do not assume it's fine because the English source was short. + +**4b. Update `app/build.gradle.kts`** + +Replace the `versionCode` and `versionName` lines with the new values. Use Edit — do not rewrite the whole file. + +**4c. Update `CHANGELOG.md`** + +Get today's date via: `date +%Y-%m-%d` + +Replace the line `## [Unreleased]` (at the top of the Unreleased section) with: + +``` +## [Unreleased] + +## [X.Y.Z] - YYYY-MM-DD +``` + +This preserves an empty Unreleased section for future work and stamps the release with today's date. + +## Step 5 — Commit + +Run: +``` +git add app/build.gradle.kts CHANGELOG.md docs/whatsnew/ +git commit -m "chore(release): bump version to X.Y.Z" +``` + +Use `AskUserQuestion` to ask if he wishes to push. If so run `git push`. Note: `main` is branch-protected (PR required, enforced for admins) — push to a branch and open a PR rather than pushing `main` directly. diff --git a/AGENTS.md b/AGENTS.md index 7e578fb..9930eab 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,6 +49,22 @@ replacement. and the `coilforphoniebox.app` DNS must point at GitHub Pages, for the `CNAME` file in `docs/pages/` to actually resolve. +`main` is branch-protected: PRs required (enforced for admins too, 0 required approvals), plus +`Build`/`Unit Tests`/`Lint`/`Analyze (Kotlin)` as required status checks (these currently pass +by skipping, per the gate above). `ci.yml`/`codeql.yml` also trigger on PRs into `develop`. + +## Slash commands + +`.claude/commands/` — repo-specific Claude Code commands: + +- `/commit` — stages/syncs, drafts a conventional commit message, updates `CHANGELOG.md`'s + `Unreleased` section when the change is user-facing, and offers to push (to a branch — `main` + is protected). +- `/release` — bumps `versionName`/`versionCode` in `app/build.gradle.kts`, stamps the + `CHANGELOG.md` `Unreleased` section with the new version, and generates + `docs/whatsnew/X.Y.Z-{locale}` files for `en-US`/`de-DE`/`fr-FR`/`es-ES`/`nl-NL` (the format + `google-play.yml` expects). Gated on `app/build.gradle.kts` existing — a no-op until Phase 1. + ## Commands There is no root build yet, so the README's `./gradlew assembleDebug` is aspirational until