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
56 changes: 56 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Agent instructions

Facts specific to this repo. General working preferences live in your global config,
not here.

## Ruby and the dev environment

This project uses [devenv](https://devenv.sh) (Nix-based) to manage Ruby and the
development environment. Do **not** suggest or use rbenv, rvm, asdf, or Homebrew Ruby.

```bash
devenv shell # interactive
devenv shell -- <command> # one-off
```

The Ruby version is set in `devenv.nix`. It currently targets Ruby 4.0. The gem
itself supports Ruby 3.2 and newer (`required_ruby_version` in the gemspec), and CI
tests that whole range.

## Releasing a new version

**Read [`docs/updating-version.md`](docs/updating-version.md) before changing the
version.** Do not improvise a release.

The three things that trip agents up:

1. Only `lib/snippet_cli/version.rb` and `CHANGELOG.md` change. The gemspec, the
`version` command, and its spec all read `SnippetCli::VERSION`.
2. `nix/gemset.nix` and `nix/Gemfile.lock` pin the **published** gem. They stay on
the old version during a release. CI repins them after publish.
3. `gem-release-ready` must be in the **pull request title**, not a commit message.
`master` requires a PR, so the squash commit message comes from the PR title, and
that is what triggers `publish`.

## Nix packaging

`nix/default.nix` is a `bundlerApp` derivation that installs the published gem from
RubyGems. It does not build from the working tree, so source changes do not appear in
the Nix package until the gem is released and the pins are regenerated.

The `gum` gem ships its binary in a platform-specific directory. The derivation
patches around this by symlinking `pkgs.gum` into the expected path. If `gum` changes
its layout, that patch in `nix/default.nix` is what breaks.

Verify packaging changes with `nix flake check --no-build`. Do not run a full build
just to prove the package installs.

## Tests

```bash
devenv shell -- bundle exec rake spec
```

Some `spec/integration/cli_spec.rb` examples spawn a subprocess through aruba. They
fail in a polluted local Bundler environment and pass in CI. Always compare the
failure count against `master` before assuming a change broke them.
91 changes: 28 additions & 63 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,21 @@
# Claude Code Instructions

## Ruby Version Management
Claude Code reads this file, not `AGENTS.md`. The repo instructions live in
`AGENTS.md` so every coding agent reads the same source; the import below pulls them
in at session start. Add repo guidance there, not here.

This project uses [devenv](https://devenv.sh) (Nix-based) to manage the Ruby version and development environment. Do **not** suggest or use rbenv, rvm, asdf, or Homebrew Ruby for this project.

To enter the development environment:

```bash
devenv shell
```

To run a single command inside the devenv environment (without an interactive shell):

```bash
devenv shell -- <command>
```

For example: `devenv shell -- ruby --version`

The Ruby version is specified in `devenv.nix`. Currently targeting Ruby 4.0.

<!-- BACKLOG.MD MCP GUIDELINES START -->

<CRITICAL_INSTRUCTION>

## BACKLOG WORKFLOW INSTRUCTIONS

This project uses Backlog.md MCP for all task and project management activities.

### Critical guidance

- If your client supports MCP resources, read `backlog://workflow/overview` to understand when and how to use Backlog for this project.
- If your client only supports tools or the above request fails, call `backlog.get_backlog_instructions()` to load the tool-oriented overview. Use the `instruction` selector when you need `task-creation`, `task-execution`, or `task-finalization`.

- **First time working here?** Read the overview resource IMMEDIATELY to learn the workflow
- **Already familiar?** You should have the overview cached ("## Backlog.md Overview (MCP)")
- **When to read it**: BEFORE creating tasks, or when you're unsure whether to track work

These guides cover:

- Decision framework for when to create tasks
- Search-first workflow to avoid duplicates
- Links to detailed guides for task creation, execution, and finalization
- MCP tools reference

You MUST read the overview resource to understand the complete workflow. The information is NOT summarized here.

### Completing tasks

`mcp__backlog__task_complete` requires status to be "Done" first. Always call `mcp__backlog__task_edit` with `status: "Done"` before calling `mcp__backlog__task_complete`, or it will error.

</CRITICAL_INSTRUCTION>

<!-- BACKLOG.MD MCP GUIDELINES END -->
@AGENTS.md

<!-- rtk-instructions v2 -->

# RTK (Rust Token Killer) - Token-Optimized Commands

## Golden Rule

**Always prefix commands with `rtk`**. If RTK has a dedicated filter, it uses it. If not, it passes through unchanged. This means RTK is always safe to use.

**Important**: Even in command chains with `&&`, use `rtk`:

```bash
# ❌ Wrong
git add . && git commit -m "msg" && git push
Expand All @@ -73,6 +27,7 @@ rtk git add . && rtk git commit -m "msg" && rtk git push
## RTK Commands by Workflow

### Build & Compile (80-90% savings)

```bash
rtk cargo build # Cargo build output
rtk cargo check # Cargo check output
Expand All @@ -84,6 +39,7 @@ rtk next build # Next.js build with route metrics (87%)
```

### Test (90-99% savings)

```bash
rtk cargo test # Cargo test failures only (90%)
rtk vitest run # Vitest failures only (99.5%)
Expand All @@ -92,6 +48,7 @@ rtk test <cmd> # Generic test wrapper - failures only
```

### Git (59-80% savings)

```bash
rtk git status # Compact status
rtk git log # Compact log (works with all git flags)
Expand All @@ -110,6 +67,7 @@ rtk git worktree # Compact worktree
Note: Git passthrough works for ALL subcommands, even those not explicitly listed.

### GitHub (26-87% savings)

```bash
rtk gh pr view <num> # Compact PR view (87%)
rtk gh pr checks # Compact PR checks (79%)
Expand All @@ -119,6 +77,7 @@ rtk gh api # Compact API responses (26%)
```

### JavaScript/TypeScript Tooling (70-90% savings)

```bash
rtk pnpm list # Compact dependency tree (70%)
rtk pnpm outdated # Compact outdated packages (80%)
Expand All @@ -129,6 +88,7 @@ rtk prisma # Prisma without ASCII art (88%)
```

### Files & Search (60-75% savings)

```bash
rtk ls <path> # Tree format, compact (65%)
rtk read <file> # Code reading with filtering (60%)
Expand All @@ -137,6 +97,7 @@ rtk find <pattern> # Find grouped by directory (70%)
```

### Analysis & Debug (70-90% savings)

```bash
rtk err <cmd> # Filter errors only from any command
rtk log <file> # Deduplicated logs with counts
Expand All @@ -148,6 +109,7 @@ rtk diff # Ultra-compact diffs
```

### Infrastructure (85% savings)

```bash
rtk docker ps # Compact container list
rtk docker images # Compact image list
Expand All @@ -157,12 +119,14 @@ rtk kubectl logs # Deduplicated pod logs
```

### Network (65-70% savings)

```bash
rtk curl <url> # Compact HTTP responses (70%)
rtk wget <url> # Compact download output (65%)
```

### Meta Commands

```bash
rtk gain # View token savings statistics
rtk gain --history # View command history with savings
Expand All @@ -174,16 +138,17 @@ rtk init --global # Add RTK to ~/.claude/CLAUDE.md

## Token Savings Overview

| Category | Commands | Typical Savings |
|----------|----------|-----------------|
| Tests | vitest, playwright, cargo test | 90-99% |
| Build | next, tsc, lint, prettier | 70-87% |
| Git | status, log, diff, add, commit | 59-80% |
| GitHub | gh pr, gh run, gh issue | 26-87% |
| Package Managers | pnpm, npm, npx | 70-90% |
| Files | ls, read, grep, find | 60-75% |
| Infrastructure | docker, kubectl | 85% |
| Network | curl, wget | 65-70% |
| Category | Commands | Typical Savings |
| ---------------- | ------------------------------ | --------------- |
| Tests | vitest, playwright, cargo test | 90-99% |
| Build | next, tsc, lint, prettier | 70-87% |
| Git | status, log, diff, add, commit | 59-80% |
| GitHub | gh pr, gh run, gh issue | 26-87% |
| Package Managers | pnpm, npm, npx | 70-90% |
| Files | ls, read, grep, find | 60-75% |
| Infrastructure | docker, kubectl | 85% |
| Network | curl, wget | 65-70% |

Overall average: **60-90% token reduction** on common development operations.
<!-- /rtk-instructions -->

<!-- /rtk-instructions -->
137 changes: 137 additions & 0 deletions docs/updating-version.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Updating the version

How to release a new version of snippet_cli. Follow these steps in order.

## Change exactly two files

| File | Change |
| ---------------------------- | ------------------------------------------------------------------ |
| `lib/snippet_cli/version.rb` | Set `VERSION` to the new number. This is the only source of truth. |
| `CHANGELOG.md` | Add a section for the new version. |

That is the whole edit. Do not change the version anywhere else.

## Never hand-edit these

These already derive the version. Editing them creates two sources of truth.

- `snippet_cli.gemspec` — reads `SnippetCli::VERSION`
- `lib/snippet_cli/commands/version.rb` — interpolates the constant
- `spec/commands/version_spec.rb` — asserts against the constant

These two are different. They pin the **published** gem, not the source:

- `nix/gemset.nix`
- `nix/Gemfile.lock`

They must stay on the **old** version while you release. The new gem does not exist
on RubyGems yet, so bundix cannot resolve it. CI repins them after publish. If you
bump them by hand, the build breaks.

## Choose the number

Follow [semantic versioning](https://semver.org). One case is easy to get wrong:

**Raising `required_ruby_version` is a minor bump, not a patch.** It stops users on
the dropped Ruby from installing at all. Treat any narrowing of supported Ruby,
Espanso schema, or platform the same way.

## Steps

1. Branch off `master`. Name it `release/<version>`.
2. Set `VERSION` in `lib/snippet_cli/version.rb`.
3. Add a `CHANGELOG.md` section. Write it for users, not for the git log. Read the
commits since the last tag to find what actually changed:
```bash
git log --no-merges --pretty="%s" v<previous>..master
```
4. Run the suite:
```bash
devenv shell -- bundle exec rake spec
```
Compare the failure count against `master` before you assume you broke something.
Some `spec/integration/cli_spec.rb` examples fail in a polluted local Bundler
environment and pass in CI.
5. Commit and push the branch.
6. Open a PR against `master`. **Put `gem-release-ready` in the PR title.**
7. Wait for the required checks.
8. Squash-merge. Keep `gem-release-ready` in the squash commit message.

## Why the marker goes in the title

The `publish` job runs only when this is true:

```yaml
github.event_name == 'push' && github.ref == 'refs/heads/master'
&& contains(github.event.head_commit.message, 'gem-release-ready')
```

The `master` ruleset requires a pull request, so the push to `master` is the squash
commit. GitHub builds that commit message from the PR title. A marker in a branch
commit does not survive the squash. **No marker means no release.**

## What CI does

| Job | When | Purpose |
| --------------- | ------------------ | -------------------------------------------------------------- |
| `test` | every push and PR | rspec across Ruby 3.2–4.0 on Linux and macOS |
| `package` | every push and PR | installs the built gem and runs it outside the checkout |
| `gemset-check` | every push and PR | compares the Nix pins to `version.rb` |
| `publish` | marker on `master` | builds, pushes to RubyGems and GitHub Packages, tags, releases |
| `update-gemset` | after `publish` | repins the Nix files and opens a PR |

## Expect `gemset-check` to pass with a notice

On a release PR, `version.rb` is ahead of `nix/gemset.nix`. That is correct, not a
failure. The job asks RubyGems whether the new version is published. It is not yet,
so the job treats it as a release in flight and passes:

```
::notice::version.rb is <version>, which RubyGems has not indexed yet.
Release in flight -- update-gemset repins after publish.
```

If it **fails** instead, the pins lag a version that is already published. That is
real drift. Regenerate them (see below).

## After the merge

`publish` pushes the gem, then `update-gemset` runs. It waits for RubyGems to index
the release, regenerates the Nix pins, and opens a `chore/repin-gemset-v<version>`
PR with auto-merge enabled. Confirm that PR lands.

Then verify:

```bash
gem list -r snippet_cli # new version is on RubyGems
nix run github:ajmarkow/snippet_cli -- version
```

## Failure modes

**`update-gemset` times out.** It polls RubyGems for 10 minutes. If indexing is
slower, the job fails but the gem is already published. Rerun that job alone from
the Actions tab. Nothing is broken.

**Bundler resolves the wrong version.** `nix/Gemfile` pins `~> 0.5`, which stops
before 1.0. A major release falls outside it. The job detects this and fails rather
than repinning the wrong version. Widen the constraint in `nix/Gemfile`.

**`update-gemset` fails at checkout.** The `GEMSET_BOT_TOKEN` secret is missing or
expired. It must be a fine-grained PAT with Contents and Pull requests read/write.
`GITHUB_TOKEN` does not work here: GitHub does not start check runs for pull
requests it opens, so the required checks never report and auto-merge waits forever.

## Regenerating the Nix pins by hand

Only do this when `gemset-check` reports real drift, or when `update-gemset` failed
and you are cleaning up. The gem must already be on RubyGems.

```bash
nix develop --command bash -c "cd nix && bundle lock --update snippet_cli && bundix"
nix fmt nix/gemset.nix
```

Both lines matter. `--update snippet_cli` keeps unrelated transitive gems pinned.
`nix fmt` matches the checked-in formatting, because bundix emits compact lists.
Run together, they produce no drift when the pins are already correct.