# Agent instructions

## Repository layout

This repo is a Claude Code plugin (a "marketplace" with a single plugin).

- `skills/` — one directory per skill (`developer`, `fix-pr`, `implement-issue`,
  `review-pr`, `setup-developer-skills`), each with its own `SKILL.md`.
- `agents/` — worker agent definitions as top-level `.md` files
  (`code-author.md`, `diff-reviewer.md`).
- `hooks/` — `hooks.json` plus the hook scripts it registers. Claude Code
  auto-loads `hooks/hooks.json` from this standard path, so **do not** also
  name it in `plugin.json`'s `hooks` key: that registers the same file twice
  and the whole plugin fails to load. The manifest key is only for hook files
  kept somewhere else.
- `tests/` — regression tests for the bundled scripts (plain bash, no
  framework; run directly, e.g. `bash tests/cleanup-worktrees.test.sh`), plus
  `node tests/developer-workflow.test.mjs` for the `/developer` workflow's
  scheduling (fake agents, no network).
- `.claude-plugin/plugin.json` — the plugin manifest and **canonical version**.
- `.claude-plugin/marketplace.json` — marketplace entry; points at `./`. The
  schema *allows* a per-plugin `version`, but we deliberately omit it so
  `plugin.json` stays the single source of truth — **do not add one here**.
- `CHANGELOG.md` — user-facing history (see release process below).
- `BACKLOG.md` — unscheduled ideas, with what blocks each one. Not a roadmap;
  entries leave when they ship or stop being good ideas.

## Conventions

- **Commits** follow [Conventional Commits](https://www.conventionalcommits.org)
  (`feat:`, `fix:`, `docs:`, `chore:`, `refactor:`…).
- Agent definitions live **only** in `agents/`, at the plugin root, where
  Claude Code auto-loads them. There is no second copy to keep in sync.
- **Never** add a `Co-Authored-By: Claude` / Anthropic trailer to commits.
- The version lives in **one place** (`.claude-plugin/plugin.json`); nothing
  else should hard-code it.

## Releasing a new version

Any change that puts a **bare** `X.Y.Z` in `.claude-plugin/plugin.json` **must**
update the changelog and publish the release — never set one without completing
every step below. Setting a `-next` pre-release (step 9) is the one exception:
it publishes nothing.

Versioning follows [Semantic Versioning](https://semver.org). While the project
is `0.x`, every GitHub release is a **pre-release** (there is no stable/`Latest`
release until `1.0.0`).

Development happens on the **`next` branch**, whose `version` is the release it
is working **toward**, marked as a semver pre-release: `0.16.0-next` is the
unreleased run-up to `0.16.0`, and sorts after `0.15.0` but before `0.16.0`.
The suffix exists so `claude plugin list` and the `/plugin` UI say out loud
that the loaded build is not the published one. On top of that, a pre-commit hook
(`.githooks/pre-commit`) stamps every commit's version with its UTC commit
time — `0.16.0-next.20260918143210` — so each commit is a distinct version:
`claude plugin update` and project-scoped installs pick it up, and
`scripts/plugin-mode.sh status` shows which commit each install came from.
Enable the hook once per clone with `git config core.hooksPath .githooks`;
it leaves a bare `X.Y.Z` alone. Test it with `bash tests/version-stamp.test.sh`. Only the release steps below
take it off. The target number is a forecast, not a promise — if the cycle's
scope turns out to be a patch rather than a minor, correct the string when you
release (step 1). Try it out with `scripts/plugin-mode.sh dev` (this checkout,
uncommitted edits included — this machine only) or `scripts/plugin-mode.sh
next` (the pushed `next` branch on GitHub — works the same on any machine).
`scripts/plugin-mode.sh prod` switches back to the published release.
Changelog entries land under the `## [Unreleased]` heading as you go; a release
promotes that section to the new version.

### Steps

1. **Set the version** in `.claude-plugin/plugin.json` to the release `X.Y.Z` —
   a bare number, dropping the `-next.<stamp>` suffix `next` carries between releases.
   Usually that is just `0.16.0-next.<stamp>` → `0.16.0`, but if the cycle's actual
   scope no longer matches what the branch was forecasting, set the number the
   changes earn and ignore the forecast.
2. **Update `CHANGELOG.md`** ([Keep a Changelog](https://keepachangelog.com)
   format):
   - Rename the `## [Unreleased]` heading to `## [X.Y.Z] - YYYY-MM-DD`, then add
     a fresh empty `## [Unreleased]` section above it for the next cycle.
   - Group entries under `Added` / `Changed` / `Fixed` / `Documentation`.
   - Update the link references at the bottom of the file: repoint
     `[Unreleased]` to `.../compare/vX.Y.Z...next` and add the matching
     `[X.Y.Z]: https://github.com/sgomez/developer-skills/releases/tag/vX.Y.Z`
3. **Commit** the version bump and changelog together on `next`, e.g.
   `git commit -m "feat: <summary> (X.Y.Z)"`.
4. **Merge `next` into `main`** — publishing always goes through `main`:
   ```sh
   git switch main && git merge --ff-only next
   ```
   Keep it a fast-forward so `main` lands the exact bump commit (rebase `next`
   onto `main` first if it has diverged).
5. **Tag** the version-bump commit on `main` with an **annotated** tag:
   `git tag -a vX.Y.Z -m vX.Y.Z`. It must be annotated — `--follow-tags` in
   the next step pushes annotated tags only, and silently ignores lightweight
   ones.
6. **Push** `main` and the tag: `git push origin main --follow-tags`. Confirm
   the tag actually landed: `git ls-remote --tags origin vX.Y.Z`.
7. **Publish the GitHub release**, using that version's changelog section as the
   body and marking it as a pre-release while on `0.x`:
   ```sh
   gh release create vX.Y.Z --title vX.Y.Z --notes-file <section.md> \
     --prerelease --latest=false --verify-tag
   ```
8. **Continue development on `next`**: `git switch next && git merge --ff-only main`
   so both branches share the release commit before the next cycle.
9. **Reopen the cycle on `next`**: set `version` to the *next* anticipated
    release with the suffix back on — after `0.15.0` ships, that's
    `0.16.0-next` — and commit it alone: `git commit -m "chore: reopen next
    (0.16.0-next)"`. `next` now sits one commit ahead of `main`; the next
    release's step 4 fast-forwards both this commit and the new bump onto
    `main`, which is why step 1 sets the version outright rather than
    incrementing whatever string it finds.

The git tag `vX.Y.Z` must point at the exact commit that set `version` to the
bare `X.Y.Z` in `.claude-plugin/plugin.json` — not at the `X.Y.Z-next` reopen
commit that follows it, and not at the pre-release run-up.
