{"owner":"redis","repo":"lettuce","hasSkills":true,"totalSkillsCount":8,"totalTokensCount":12294,"categories":["anthropic-skill","claude-rule","root-instruction"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/writing-javadoc/SKILL.md",".agents/skills/draft-pr-description/SKILL.md",".claude/rules/rules.md","CLAUDE.md","AGENTS.md",".agents/skills/extend-commands-api/SKILL.md",".agents/rules/java-rules.md",".agents/rules/rules.md"],"skills":{".agents/skills/writing-javadoc/SKILL.md":"---\nname: writing-javadoc\ndescription: Use when writing or editing Javadoc for Lettuce public API — new methods, classes, deprecations, or when a reviewer asks to fix or improve doc comments. Points to the full house ruleset in .agents/docs/javadoc.md (imperative method summaries, fixed tag order, @param/@return/@throws/@since/@deprecated house forms) and the rule that command-interface Javadoc is written on the sync interface first and mirrored to the other flavors. Trigger on \"write javadoc for\", \"document this method\", \"add a deprecation notice\", \"fix the doc comment\".\n---\n\n# Writing Javadoc for Lettuce\n\nThe complete, authoritative ruleset is **[.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md)** —\nread it before writing. This skill is the operating checklist; the doc wins on any\ndetail.\n\n## Before you write\n\n- **Is it a command interface?** The command interfaces are hand-edited, with the\n  **sync** interface's Javadoc as the reference text. Write or fix the comment on\n  the sync method first, then mirror it to **every** flavor that declares the\n  method, with the flavor-appropriate `@return` phrasing (`RedisFuture`,\n  `Mono`/`Flux`, …):\n  - `src/main/java/io/lettuce/core/api/{sync,async,reactive}/`\n  - the Kotlin coroutine interfaces (`src/main/kotlin/…/api/coroutines/`)\n  - the cluster node-selection interfaces\n    (`src/main/java/io/lettuce/core/cluster/api/{sync,async}/NodeSelection…`)\n  - the Sentinel interfaces (`src/main/java/io/lettuce/core/sentinel/api/…` and\n    their Kotlin flavor)\n\n  Javadoc parity is **not** test-enforced — keep the flavors in sync by hand. See\n  [api-consistency.md](../../../.agents/docs/api-consistency.md).\n\n## The rules most often gotten wrong\n\n1. **Golden rule:** document the caller-facing contract (inputs, outputs, effects,\n   errors, nullability) — never implementation details or refactor rationale.\n2. **First sentence:** imperative verb for methods (\"Append…\", \"Get…\", \"Remove…\"),\n   noun phrase for types. Not \"This method…\". Ends in a clean period.\n3. **Tag order:** `@param` → `@return` → `@throws` → `@author` → `@since` → `@see`\n   → `@deprecated`.\n4. **`@param`** for every parameter (type params `<K>`/`<V>` first); state\n   nullability with the house phrases `must not be {@code null}.` / `can be {@code null}.`\n5. **`@return`** for non-void; describe the value and its meaningful states, not the\n   type.\n6. **`@since`** is a bare version — `@since 7.7` (see .agents/docs/javadoc.md for deriving\n   the version from the build).\n7. **`@deprecated`** — keep the `@Deprecated` annotation and the tag in sync; house\n   form: `@deprecated since <version>, use {@link Replacement} instead; scheduled for\n   removal in a future major release.`\n8. **`@author`** on types only — the *maintainer's* name (an agent must not invent\n   one); don't reorder existing authors.\n9. Use `{@code null}` for literals; `{@link}` only to types resolvable on the\n   compile classpath (else `{@code TypeName}`).\n\n## Verify, don't guess\n\nMatch the surrounding file, and consult `.agents/docs/javadoc.md` for any subtle case\nrather than inventing a rule. Javadoc lint is off in the build (`<doclint>none</doclint>`),\nso review is the only check — get it right by hand.\n",".agents/skills/draft-pr-description/SKILL.md":"---\nname: draft-pr-description\ndescription: Generate a clear, concise GitHub PR title and description from the diff between two local git branches, and save it to prDescription.md in the repo root. Use this whenever the user asks to write, generate, draft, or update a PR description or PR title from local branch changes — including phrasing like \"summarize this diff into a PR description,\" \"write a PR description for my current branch,\" \"create a PR title and description,\" or any request to compare a base and target branch for PR purposes. Trigger even if the user doesn't name specific branches; this skill knows how to default them.\nargument-hint: [base-branch] [target-branch]\narguments: [base, target]\nallowed-tools: Bash(git diff *) Bash(git log *) Bash(git branch *)\n---\n\n# PR Description Generator\n\nWrites a GitHub PR title and description from the diff between two local git branches, focused on what reviewers need: purpose, key decisions/assumptions, behavioral or conceptual changes, and a brief testing note if relevant. Uses only standard git commands, so any agent can follow it.\n\n## Arguments\n\nTwo optional positional arguments — base branch, then target branch:\n\n- Base: `$base`\n- Target: `$target`\n\nAn empty value means \"not provided\" — default it per the resolution below (a missing argument is defaulted, not guessed). A target cannot be given without a base.\n\n## 1. Resolve branches\n\n- Both named → use as given.\n- Only one named → it's the **base**; target defaults to current branch.\n- None named → base defaults to `main`, then `master` if `main` doesn't exist (`git branch --list`). **Only check these two names — no scanning other branches, no guessing.** If neither exists, stop and ask. Target defaults to current branch (`git branch --show-current`).\n\nState the resolved base/target before proceeding.\n\n## 2. Diff\n\n- Confirm both branches exist (`git branch --list <name>`); stop if not.\n- `git diff <base>...<target>` and `git log <base>..<target> --oneline`\n- Empty diff → stop and say so, don't fabricate a description.\n\n## 3. Analyze\n\nFrom the diff/log, identify: purpose of the change; key decisions and trade-offs; assumptions the code relies on; behavioral changes (concrete: \"X now rejects Y\" not \"changed validation\"); conceptual/contract changes; breaking changes, new config/env vars, migrations; test changes (what new tests cover conceptually, why existing ones were adapted — not test-by-test).\n\nSkip routine stuff (formatting, trivial renames). No file/line enumeration — write like explaining to a teammate, not a changelog.\n\n## 4. Output structure\n\nWrite to `prDescription.md` (repo root, overwrite if exists):\n\n```markdown\n# <Title — imperative, <70 chars>\n\n## Summary\n<2-3 sentences>\n\n## Key Decisions & Assumptions\n<bullets — omit if none>\n\n## Behavioral / Conceptual Changes\n<bullets, concrete — omit if none>\n\n## Testing\n<2-3 sentences, only if diff touches tests — omit otherwise, don't write \"no tests\">\n\n## Notes\n<breaking changes, follow-ups — omit if none>\n```\n\nKeep it brief (readable in under a minute, ~150-200 words total unless the change is genuinely large). Omit empty sections rather than padding them. Never fabricate testing details, tickets, or names not evidenced in the diff — flag uncertainty in Notes instead.\n\n## 5. Confirm\n\nAfter writing, give a short confirmation (don't reprint the file) and offer to run `gh pr create --title \"...\" --body-file prDescription.md` if `gh` is available. Include the provided/resolved base and target branch names into the suggested command as part of the suggestion. At the end, ask if the user wants to open the PR now and wait for confirmation before proceeding. If the user declines, stop and say the PR description is ready in `prDescription.md`.\n",".claude/rules/rules.md":"# Rules\n\nThis project's coding rules are maintained **tool-neutrally** in `.agents/rules/` so\nevery agent shares one source. This file is the Claude Code entry point into them.\n\nRead [`.agents/rules/rules.md`](../../.agents/rules/rules.md) for the index, and the\nrelevant rule set before editing — e.g. [`.agents/rules/java-rules.md`](../../.agents/rules/java-rules.md)\nwhen editing Java source (`src/**/*.java`).\n","CLAUDE.md":"@AGENTS.md\n\n<!--\nThe shared operating manual lives in AGENTS.md (imported above). Reusable task\nprocedures live in .agents/skills/ and are referenced from AGENTS.md.\n-->\n","AGENTS.md":"# Lettuce Coding Conventions\n\nEssential rules for working on the Lettuce codebase — a scalable, thread-safe\nRedis client for the JVM built on netty and Project Reactor. Detailed guidance\nfor specific tasks lives in `.agents/skills/`; consult the relevant skill when\nperforming that type of work.\n\n## How this guidance is organized\n\nAgent guidance is **self-contained under agent-owned locations** and never mixed into\nthe published documentation site:\n\n- **Agent-facing** (this material) lives at the repo root (`AGENTS.md`, `CLAUDE.md`),\n  under `.agents/` (tool-neutral: `.agents/docs/`, `.agents/rules/`, `.agents/skills/`),\n  and `.claude/` (Claude-specific tooling that bridges to `.agents/`). None of it is\n  published by MkDocs.\n- **User-facing** contributor/user documentation lives under `docs/` and is published\n  to the docs site. Agents read the agent-owned copies, not `docs/`.\n\nWithin the agent material it's a small graph: **each topic has one owner file**, and\neverything else **links** to it rather than restating it. When a topic changes, edit\nthe owner only.\n\n| Topic | Owner |\n|-------|-------|\n| Always-on facts, guardrails, index (this file) | `AGENTS.md` |\n| Command flow & code generation | `.agents/docs/architecture.md` |\n| API consistency (flavors, mapping rules, tests) | `.agents/docs/api-consistency.md` |\n| Testing — environment, running, naming, layout, CI | `.agents/docs/integration-testing.md` |\n| Javadoc conventions (incl. `@since` derivation) | `.agents/docs/javadoc.md` |\n| Coding rules (Java style, …) | `.agents/rules/*` |\n| Task procedures (add a command, write Javadoc, PR description) | `.agents/skills/*` |\n\nRule: state a fact in its owner and link from elsewhere; don't copy it. This file\nkeeps only a minimal always-on quick-reference. Skills own the *sequence* of a task\nand link out for the mechanics.\n\n## General Principles\n\n- **Update documentation.** When changes affect user-facing behavior, config, or\n  public API, update the relevant pages under `docs/` (MkDocs).\n- **Add or update tests.** Bug fixes need a reproducer test; new features need\n  tests. See `.agents/docs/integration-testing.md` for unit vs. integration conventions.\n- **You are responsible for what you submit.** Validate all changes. Do not submit\n  AI-generated code without human oversight.\n\n## Guardrails (agents)\n\nThese are hard rules for automated agents. A human maintainer performs these\nactions, not the agent.\n\n- **Never `git commit` or `git push`.** Stage and describe changes; leave\n  committing and pushing to the maintainer.\n- **Never create or merge pull requests, and never reply to GitHub issues or PRs**\n  (no `gh pr create`/`merge`/`comment`/`review`, no `gh issue create`/`comment`).\n  Draft text for the maintainer to post if asked — for a PR description, use the\n  `draft-pr-description` skill, which writes to `prDescription.md`.\n\n## Requirements & Toolchain\n\n- Builds and runs on the JDK baseline declared in `pom.xml`\n  (`maven.compiler.source`/`target`); targets Redis 2.6+.\n- **The test build pins a specific JDK to match CI** — check `.github/workflows/`\n  (or `.agents/docs/integration-testing.md`) and use that JDK to build and reproduce CI.\n- Build tool is **Maven** (`mvn`). Integration tests require **Docker**.\n- **Prefer semantic code tooling over text search for Java, when available.** If a\n  Java language server or IDE integration is present (e.g. an `jdtls`-based LSP, or\n  an IDE MCP), use it for definitions, references, symbol/type search, call\n  hierarchy, and diagnostics instead of grepping. Fall back to text search only when\n  no such tool is available.\n\n## Project Structure\n\n```\nsrc/main/java/io/lettuce/core/                Core client, connections, commands\nsrc/main/java/io/lettuce/core/cluster/        Redis Cluster support\nsrc/main/java/io/lettuce/core/masterreplica/  Master/Replica & Sentinel\nsrc/main/java/io/lettuce/core/pubsub/         Pub/Sub\nsrc/main/java/io/lettuce/core/support/        Connection pooling (ConnectionPoolSupport, BoundedAsyncPool)\nsrc/main/java/io/lettuce/core/api/            Sync/async/reactive command interfaces\nsrc/test/java/io/lettuce/                      Tests (see .agents/docs/integration-testing.md §5)\nformatting.xml                                 Eclipse formatter rules\nMakefile                                       Docker-backed test lifecycle\n```\n\nWhere to look: connect → `core/`; pooling → `core/support/`; sharding →\n`core/cluster/`; HA/Sentinel → `core/masterreplica/`.\n\n**How a command flows** through the API interfaces, command builder, codec, and\nthe netty/RESP wire layer is documented in `.agents/docs/architecture.md`. Read it\nbefore adding commands or touching the protocol layer — note the\nsync/async/reactive/Kotlin command interfaces are **hand-edited** and kept in\nlockstep by the API consistency test suite (see `.agents/docs/api-consistency.md`).\n\n## Build & Test Commands\n\n**Unit tests** (pure logic, no server — named `*UnitTests`, run by Surefire):\n\n```bash\nmvn clean test                       # build + run unit tests, no Redis needed\nmvn test -Dtest=FooUnitTests         # a single unit test\n```\n\n**Integration tests** (need a running Redis — named `*IntegrationTests`, run by\nFailsafe; **off by default**, `make test` enables them via `-DskipITs=false`):\n\n```bash\nmake start                           # boot the Dockerized Redis topology (add version=<v> to pick one)\nmake test                            # full build incl. integration tests\nmake stop                            # tear the environment down\n```\n\nRun one integration test fast (env already started):\n\n```bash\nTEST_WORK_FOLDER=./work/docker mvn -DskipITs=false -DskipUnitTests=true \\\n  -Dit.test=FooIntegrationTests verify -Pci\n```\n\nOne caveat worth remembering: `-Dit.test=` filters Failsafe (a single integration\ntest); `-DskipUnitTests=true` runs integration only. The full flag reference,\nRedis-version matrix, topologies, TLS/mTLS and CI mapping live in\n[`.agents/docs/integration-testing.md`](.agents/docs/integration-testing.md).\n\n**Apply formatting** (required before committing):\n\n```bash\nmvn formatter:format                 # applies formatting.xml; do NOT submit formatting-only diffs\n```\n\n## Testing Rules\n\n- **The file-name suffix picks the runner:** `*UnitTests` (Surefire, no server),\n  `*IntegrationTests` (Failsafe, needs Redis). Prefer those suffixes. A singular\n  `*Test` suffix is excluded by Surefire and picked up by Failsafe instead, so such a\n  class never runs in `mvn test`; it only runs in the integration phase (`make test`,\n  which is what CI runs, Docker required) or when named explicitly via `-Dtest=`. Name\n  pure unit tests `*UnitTests`.\n- Integration tests read host/port/TLS from `TestSettings` / `TlsSettings` — never\n  hard-code them.\n- Everything else about testing (topologies, TLS/mTLS, layout, CI) lives in\n  [`.agents/docs/integration-testing.md`](.agents/docs/integration-testing.md).\n\n## Coding Style Essentials\n\n- 4-space indentation, enforced by `formatter-maven-plugin` — run\n  `mvn formatter:format`. Don't submit formatting-only changes (they create noise\n  and get PRs rejected).\n- **Add `@since <version>` to all new public API** — see\n  [`.agents/docs/javadoc.md`](.agents/docs/javadoc.md) for the version-derivation recipe.\n- **`@author` is the human contributor's.** Maintainers add their own `@author` to\n  files they touch (per CONTRIBUTING). An automated agent must **not** invent an\n  `@author` for itself — leave the tag to the maintainer unless told which name to add.\n- **Java style** — imports over fully-qualified names, braces on every control\n  statement (no braceless one-liners), no wildcard imports. Full list in\n  `.agents/rules/java-rules.md` (see `.agents/rules/` before editing Java).\n- Javadoc for public API follows house conventions — see `.agents/docs/javadoc.md`, or use\n  the `writing-javadoc` skill.\n\n## Commit & PR\n\n- Open a feature-request issue first; reference its number in the PR\n  (`<text> #<ticket>`).\n- Include unit and/or integration tests for every change.\n- Split feature/bugfix commits from formatting/polishing commits.\n- Follow `.github/PULL_REQUEST_TEMPLATE.md`.\n\n## Key References\n\n| Document | What it covers |\n|----------|----------------|\n| `.github/CONTRIBUTING.md` | How to build, test, and contribute |\n| `.agents/docs/architecture.md` | How a command flows: API shapes, code generation, command model, codec, RESP/netty wire layer |\n| `.agents/docs/api-consistency.md` | The API flavors, their return-type mapping rules, and the consistency test suite that enforces them |\n| `.agents/docs/integration-testing.md` | Test environment, topologies, unit vs. integration, CI |\n| `.agents/docs/javadoc.md` | Javadoc house conventions for public API |\n| `.github/PULL_REQUEST_TEMPLATE.md` | PR checklist |\n| `formatting.xml` | Eclipse code-formatting rules |\n| [Javadoc](https://redis.github.io/lettuce/overview/) | Public API reference |\n\n## On-Demand Skills\n\nDetailed task guidance lives in `.agents/skills/`. Consult the relevant skill\nbefore doing that type of work.\n\n| Skill | When to use |\n|-------|-------------|\n| `extend-commands-api` | Add or extend a Redis command (or overload) end-to-end, evidence-first: HLD + server PR + live redis-cli verification, plan-mode approval, then all API interface flavors, builder, async/reactive/Kotlin impls, and tests |\n| `create-implementation-plan-for-redis-api-change` | Turn a shared client HLD into Lettuce's reviewed implementation plan (API, files per flavor, ordered steps, test matrix) without touching sources; read-only, writes one plan file; conventions come from `extend-commands-api` |\n| `writing-javadoc` | Write or fix Javadoc for public API following Lettuce house conventions |\n| `draft-pr-description` | Generate a GitHub PR title and description from the diff between two local branches |\n",".agents/skills/extend-commands-api/SKILL.md":"---\nname: extend-commands-api\ndescription: Add or extend Redis commands in the Lettuce client API end-to-end — a new core command, a family of new commands, an extension to an existing command's options, or a module/area command (Search/JSON/Bloom/VectorSet). Gathers evidence first (HLD document, the server-side PR in the repo owning the command family, live verification against the Dockerized test environment with redis-cli), plans the full implementation matrix in plan mode, then implements across all API flavors with unit and integration tests. Trigger on \"add support for the <X> command\", \"implement <REDIS COMMAND> in Lettuce\", \"extend <command> with <option>\", or adding a new argument/overload to an existing command.\nallowed-tools: Bash(mvn *), Bash(make *), Bash(redis-cli *), Bash(gh *)\n---\n\n# Extend the Lettuce Commands API\n\nImplement a new Redis command (or extend an existing one) in Lettuce, following the\nconventions maintainers enforce in review. The workflow is evidence-first: prove the\ncommand's behavior on a real server before designing the Java API.\n\nRead [.agents/docs/architecture.md](../../../.agents/docs/architecture.md) first for\nthe model behind this flow — especially that the sync/async/reactive/Kotlin command\n**interfaces are hand-edited source files kept in lockstep by the API consistency\ntest suite** (see [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md)).\n\n> **Historical caveat when reading old PRs**: PRs merged before the generator removal\n> also edit `src/test/java/io/lettuce/core/api/<Group>Commands.java` — the old\n> template source files. Those files **no longer exist**; do not recreate them. The\n> flavor interfaces are now edited directly.\n\n## Phase 0 — Gather evidence BEFORE planning\n\nDo all of the following before writing any plan or code:\n\n1. **Ask the user for the HLD.** Interactively ask for the path to a markdown file\n   containing the High-Level Design for the command(s) (or confirm none exists). If\n   a path is given, read it fully — it is the primary source for syntax, semantics,\n   reply shape per RESP2/RESP3, and edge cases.\n\n2. **Find the server-side PR in the repo that owns the command.** Route the\n   search by command family — module command families are developed in their\n   owning repositories, not in `redis/redis`:\n\n   | Command family | Repository | Syntax source |\n   |----------------|------------|---------------|\n   | Core commands, vector sets (`VADD`, …) | `redis/redis` | `src/commands/*.json` |\n   | Search (`FT.*`) | `RediSearch/RediSearch` | PR diff + command docs (no `src/commands/*.json`) |\n   | JSON (`JSON.*`) | `RedisJSON/RedisJSON` | PR diff + command docs |\n   | Probabilistic (`BF.*`, `CF.*`, `CMS.*`, `TOPK.*`, `TDIGEST.*`) | `RedisBloom/RedisBloom` | PR diff + command docs |\n   | Time series (`TS.*`) | `RedisTimeSeries/RedisTimeSeries` | PR diff + command docs |\n\n   If the search comes up empty in the routed repo, fall back to `redis/redis`\n   (and vice versa) before concluding there is no server PR.\n\n   First verify that `gh` works in the current (sandboxed) environment:\n   ```bash\n   gh auth status\n   ```\n   Sandboxes often block access to credential files, so `gh` may report\n   unauthenticated here even though it works on the user's machine. If so, ask the\n   user for permission to run these specific read-only `gh` commands outside the\n   sandbox; only fall back to the unauthenticated GitHub REST API if they decline.\n\n   Then search for the PR that adds/extends the command on the server:\n   ```bash\n   gh search prs --repo <owning-repo> \"<COMMAND NAME>\" --limit 10\n   gh pr view <num> --repo <owning-repo>\n   gh pr diff <num> --repo <owning-repo>  # in redis/redis: src/commands/*.json has exact syntax\n   ```\n   Extract: exact wire syntax (argument order and optionality), reply type per\n   **RESP2 and RESP3** (they can differ — this determines the `CommandOutput` and\n   the reactive `Mono`/`Flux` mapping), error conditions, and the **first server\n   version carrying the feature** (drives `@EnabledOnCommand` gating and the test\n   env version). Note whether the server marks the feature *experimental/preview*.\n\n3. **Verify the command exists on the \"next\" Redis OSS version.** Start the\n   integration environment using the **highest** version the Makefile supports\n   (check `SUPPORTED_TEST_ENV_VERSIONS` in the `Makefile`, or the\n   `src/test/resources/docker-env/.env.vX.XX` files — pick numerically, `8.10` >\n   `8.8`):\n   ```bash\n   grep SUPPORTED_TEST_ENV_VERSIONS Makefile\n   make start version=8.10\n   ```\n   Then probe the running servers. Test connection defaults live in\n   `src/test/java/io/lettuce/test/settings/TestSettings.java` — the main standalone\n   node is `localhost:6479` (no auth); module commands (`FT.*`, `JSON.*`, `BF.*`,\n   `VADD`, …) run on the stack node at `localhost:16379`:\n   ```bash\n   redis-cli -p 6479 INFO server | grep redis_version\n   redis-cli -p 6479 COMMAND INFO <COMMAND>   # non-empty → command exists\n   redis-cli -p 6479 COMMAND DOCS <COMMAND>   # arity/args — compare with the server PR\n   ```\n   For a NEW command, `COMMAND INFO` must return a non-empty reply. For an EXTENDED\n   command, additionally invoke it with the new syntax against a scratch key and\n   confirm the server accepts it (no `ERR syntax error` / `ERR unknown argument`).\n\n   **If the command/option is missing on the latest env version**, the image tag\n   pinned in the `.env.vX.XX` file is too old. Ask the user for a\n   `redislabs/client-libs-test` image tag that contains it (e.g. an RC/edge build —\n   tags are listed at https://hub.docker.com/r/redislabs/client-libs-test/tags; you\n   may check and suggest candidates), then:\n   ```bash\n   make stop\n   CLIENT_LIBS_TEST_IMAGE_TAG=<tag> make start\n   ```\n   If the PR should also move CI to that build, update the newest `.env.vX.XX`\n   pin as part of the change — command PRs do this when they need a fresh server\n   (cf. the hotkeys PR bumping `.env.v8.6`). If no tag carries the feature, report\n   it and proceed: implementation continues, integration tests stay gated until an\n   image ships the change. Keep the environment running for later test runs.\n\n4. **Create redis-cli showcase test cases.** Once the command is available, derive\n   a small set of redis-cli scenarios from the HLD and the server PR and run them.\n   These serve two purposes:\n   - **Smoke test**: prove the documented behavior holds — happy path, each new\n     option/keyword, reply shape (run with `redis-cli -3` too when RESP3 replies\n     differ), edge cases and error conditions (missing key, out-of-range args,\n     conflicting options) — so the Java implementation is built against observed\n     replies, not assumptions.\n   - **Showcase**: each scenario should read as a mini use-case explaining WHY the\n     command/option exists. Prefer realistic data over `foo`/`bar`.\n\n   Save the scenarios as a commented script in a scratch file (one block per\n   use-case: a \"what this demonstrates\" comment, the commands, the observed reply\n   pasted back as comments). Carry this forward: it feeds the Phase 1 plan, the\n   test assertions, and the PR description. If the command could not be made\n   available on any image, still write the scenarios as *expected* transcripts and\n   mark them unverified.\n\n5. **Read the testing and consistency docs**:\n   [.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n   (environment, `*UnitTests` vs `*IntegrationTests` naming, base/overload test\n   structure, running one test) and\n   [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md) (the\n   flavors and their return-type mapping rules).\n\n6. **Trace one analogous existing command** end-to-end (same command group, similar\n   reply shape) so the plan mirrors real code, not guesswork. Good reference PRs,\n   validated against git history:\n\n   | Commit | What it exemplifies |\n   |--------|---------------------|\n   | `7ca3e9cc0` (CLIENT NO-TOUCH #3776) | Minimal new command: all 6 flavors + builder + 2 dispatch layers + Kotlin impl + builder unit test + integration test |\n   | `312ecc7b4` (INCREX #3746) | New command with args: self-typed abstract args base, new value type, new `CommandOutput`s, per-type args/output unit tests, **creating a missing RESP2 overload class** |\n   | `b009df37b` (XNACK #3728) | New command with an enum-valued argument |\n   | `9a0899875` (BITOP extensions #3334) | New operations/overloads on an existing command |\n   | `c603c8120` (HSCAN NOVALUES #2816) | New overload rippling into helpers (`ScanIterator`, `ScanStream`, `ScanFlow`) |\n   | `aa7b4b0be` (stream idempotency #3637) | Option added to an existing `*Args` class flows through with **no interface change** (the `XAddArgs` part) |\n   | `a209fba70` (CAS/CAD #3512) | Read-only command registered in `ReadOnlyCommands` + count test update |\n   | `838a4d39a` (HOTKEYS #3638) | Map-shaped reply via `ComplexOutput` + a `*ReplyParser`, cluster interface additions, full integration overload set, `.env` image bump |\n   | `6567f2d5e` (RediSearch #3375) | Whole new command area: own `Redis<Area>CommandBuilder`, `arguments`/reply-parser packages |\n\n   **Conventions beat precedent.** Traced reference commands can predate the\n   current conventions; when the reference code conflicts with a written rule in\n   this skill or the linked docs, the rule wins. Example: `sintercard(K...)`\n   ships without a single-key overload because it predates the\n   one-overload-per-varargs rule — a new command mirroring it must still add the\n   single-argument overload.\n\n7. **Determine the `@since` version** — recipe owned by\n   [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md):\n   ```bash\n   mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n   ```\n   Drop `-SNAPSHOT` and the patch digit (`7.7.0-SNAPSHOT` → `@since 7.7`).\n\n**The evidence is your working context, not a deliverable.** Hold it in mind to\ndrive the work — do not paste it back to the maintainer or pause for \"spec\napproval.\" The only stop for the maintainer is the Phase 1 plan approval; surface\ngenuinely ambiguous design choices *there*, with the proposed sync signatures,\nrather than interrupting earlier.\n\n## Phase 1 — Plan mode, then explicit approval\n\nEnter **plan mode**. Using the evidence, classify the change with the decision tree\nbelow and enumerate the exact file-by-file touch list, the test matrix, and the\ngating annotations. The plan must contain:\n\n- A short \"what this feature enables\" section built from the showcase scenarios\n  (Phase 0 step 4), including one or two representative command/reply transcripts.\n- **The proposed sync interface signature(s) with their Javadoc.** The sync\n  interface is the human-authored **API contract**; every other flavor is derived\n  from it and it is costly to change once mirrored. Plan approval is the\n  maintainer's sign-off on that contract — if the signatures must deviate later\n  during implementation, stop and re-confirm before mirroring.\n- **The complete overload set, enumerated.** For every varargs parameter, list\n  the matching single-argument overload; for a multi-key command with an `*Args`\n  object, use the `List<K>` shape (no varargs) and list the fixed-arity\n  convenience overloads instead — both house rules live in \"Types & args\n  conventions\". Justify any overload you omit so the maintainer signs off on the\n  exception. An overload discovered missing in review means reworking all six\n  flavors.\n\nPresent the plan and **explicitly ask permission to execute** before implementing.\nDo not start editing files until the user approves.\n\nOnce approved, copy this checklist into your working notes and tick items off:\n\n```\nExtend-commands progress:\n- [ ] 0. Evidence: HLD, server PR, live probe + showcase, RESP2/RESP3 replies, @since\n- [ ] 1. Plan approved — incl. the sync signature(s), the API contract\n- [ ] 2. Types: argument & response types (they must exist before any interface edit)\n- [ ] 3. Sync interface: full overload set (varargs → single-arg too) + Javadoc\n       (@param constraints + @throws for builder-validated preconditions)\n- [ ] 4. Mirror: async, reactive, Kotlin, NodeSelection×2 — consistency tests pass\n- [ ] 5. Implementations: CommandType/Keyword, builder, async, reactive, Kotlin impl\n- [ ] 6. Tests: args/builder/output unit tests + integration base/overloads\n- [ ] 7. Docs: entry in the current-release section of docs/new-features.md\n- [ ] 8. Verify: mvn clean test + a single integration test run; then make stop\n```\n\n## Decision tree — what kind of change is this?\n\n**A. Extension of an existing command that fits an existing `*Args` class**\n(new option token / new field):\n- Touch ONLY the `*Args` class (+ `CommandKeyword` for new tokens) + tests. Do NOT\n  touch the command interfaces, builder signatures, or dispatch layers — the\n  existing `args.build(commandArgs)` delegation carries the new option through\n  automatically (cf. the `XAddArgs` part of the stream-idempotency PR).\n- Add a fluent setter returning `this`; register new tokens in\n  `src/main/java/io/lettuce/core/protocol/CommandKeyword.java`.\n- If the reply shape grows, extend the response model/output backward-compatibly.\n\n**B. New command(s) in an existing group — core or module area** — the FULL\nmatrix, in this order (types first — every flavor references them, so they must\nexist to compile). For a command joining an **existing module area** (a new\n`FT.*` method in the Search group, a new `JSON.*` method, …) the same matrix\napplies with the area substitutions: the group is the area's flavor interfaces,\nthe builder is the area's `Redis<Area>CommandBuilder` (+ its\n`Redis<Area>CommandBuilderUnitTests`), argument/reply types go in the area\npackage, and gating/tests follow the module rules in D (capability probe, stack\nnode). The dispatch layers are the same `AbstractRedisAsyncCommands` /\n`AbstractRedisReactiveCommands` and Kotlin `*Impl.kt` as for core commands.\n1. **Argument/response types** — see \"Types & args conventions\" below.\n2. **Sync interface** `src/main/java/io/lettuce/core/api/sync/<Group>Commands.java`\n   — pick the group by command family (STRING → `RedisStringCommands`, HASH →\n   `RedisHashCommands`, generic-key → `RedisKeyCommands`, …). This is the reference\n   signature + Javadoc all flavors mirror (see the\n   [writing-javadoc](../writing-javadoc/SKILL.md) skill; `@since` mandatory):\n   ```java\n   /**\n    * Returns the length of the string value stored at {@code key}.\n    *\n    * @param key the key, must not be {@code null}.\n    * @return the length of the string at {@code key}, or {@code 0} when {@code key} does\n    *         not exist.\n    * @since 7.7\n    */\n   Long strlen(K key);\n   ```\n   Three contract rules to apply while designing the signatures and their Javadoc:\n   - **Multi-key commands that also take an `*Args` object take the keys as\n     `List<K>`, not varargs.** A varargs parameter must come last, so a trailing\n     options object can only follow it via an awkward leading placement (cf. the\n     older `sintercard(long limit, K... keys)`). For new commands, take the keys\n     as `List<K>` so the `*Args` argument comes last —\n     `sunioncard(List<K> keys, SUnionCardArgs args)` — and add fixed-arity\n     convenience overloads (`(K key1, K key2)` and their `*Args` variants)\n     instead of a varargs form. Since there is no varargs parameter here, the\n     single-argument-overload rule below does not apply; add only the overloads\n     that are meaningful — e.g. **no single-key `sunioncard`/`sdiffcard`**, whose\n     one-key union/difference is just `SCARD`.\n   - **Every varargs parameter gets a single-argument overload** —\n     `foo(K key, V value)` alongside `foo(K key, V... values)`. This is a hard\n     convention for new commands and overrides any traced precedent that lacks\n     it (older commands predate the rule). Does not apply to the `List<K>`-shaped\n     multi-key-with-`*Args` commands above, which take no varargs.\n   - **Validated preconditions are contract.** Any constraint the builder will\n     enforce (null checks, non-empty varargs) must be stated in the `@param`\n     text with the house phrases (`must not be {@code null}.`,\n     `must not be empty.`) *and* documented with a matching\n     `@throws IllegalArgumentException if …` tag — forms owned by\n     [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md). Both are then\n     mirrored to every flavor.\n3. **Mirror to every flavor**: async, reactive, Kotlin coroutines, and the two\n   cluster node-selection interfaces (`NodeSelection<Group>Commands` /\n   `NodeSelection<Group>AsyncCommands`), applying the per-flavor return-type\n   mapping rules from\n   [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md).\n4. **Protocol enums** — command name in\n   `src/main/java/io/lettuce/core/protocol/CommandType.java` (wire bytes derive\n   from the enum name); subcommand tokens in `CommandKeyword`. **Never add a\n   `CommandKeyword` that duplicates a name already in `CommandType`** (e.g. `SET`,\n   `DISCARD`) — the builder static-imports both enums and the bare name becomes\n   ambiguous; reference `CommandType.<NAME>` directly instead.\n5. **Builder** — `RedisCommandBuilder.java` (or the area builder, see D): null\n   checks via `LettuceAssert`/`notNullKey`, `CommandArgs` in **wire order**, the\n   `CommandOutput` chosen from the observed RESP2/RESP3 replies:\n   ```java\n   public Command<K, V, Long> strlen(K key) {\n       notNullKey(key);\n       return createCommand(STRLEN, new IntegerOutput<>(codec), key);\n   }\n\n   public Command<K, V, Boolean> copy(K source, K destination, CopyArgs copyArgs) {\n       LettuceAssert.notNull(source, \"Source \" + MUST_NOT_BE_NULL);\n       LettuceAssert.notNull(destination, \"Destination \" + MUST_NOT_BE_NULL);\n       CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(source).addKey(destination);\n       copyArgs.build(args);\n       return createCommand(COPY, new BooleanOutput<>(codec), args);\n   }\n   ```\n   Every `LettuceAssert` precondition written here is public contract: if the\n   interface Javadoc (step 2) does not already state the constraint and its\n   `@throws IllegalArgumentException`, go back and add it — on every flavor.\n6. **Dispatch layers** — one-liners in *both* `AbstractRedisAsyncCommands` *and*\n   `AbstractRedisReactiveCommands` (`createMono` for scalars,\n   `createDissolvingFlux` for `List`/`Set` replies, matching the interface's\n   `Mono`/`Flux`):\n   ```java\n   // AbstractRedisAsyncCommands\n   public RedisFuture<Long> strlen(K key) { return dispatch(commandBuilder.strlen(key)); }\n   // AbstractRedisReactiveCommands\n   public Mono<Long> strlen(K key) { return createMono(() -> commandBuilder.strlen(key)); }\n   ```\n7. **Kotlin impl** —\n   `src/main/kotlin/io/lettuce/core/api/coroutines/<Group>CoroutinesCommandsImpl.kt`:\n   ```kotlin\n   override suspend fun strlen(key: K): Long = ops.strlen(key).awaitSingle()\n   ```\n8. **Cluster** — pick the routing shape deliberately; there are three cases:\n   - A **single-key** command flows through automatically (routed by slot).\n   - A **broadcast/all-shards** command (its answer is the aggregate over the\n     whole cluster — cf. `dbsize`, `flushall`) needs hand-coded fan-out\n     overrides in `RedisAdvancedClusterAsyncCommandsImpl` *and* its reactive\n     sibling (`executeOnUpstream` + a `MultiNodeExecution` aggregator), and may\n     need methods on the cluster aggregate interfaces.\n   - A **node-specific** command (keyless, but its result is only meaningful per\n     node — cf. HOTKEYS) must **not** be fanned out: add `default` overrides on\n     the cluster aggregate interfaces that throw `UnsupportedOperationException`\n     and direct callers to the node-selection API or `getConnection(nodeId)`\n     (cf. `RedisClusterCommands.hotkeysReset()`); only the node-selection\n     flavors execute it.\n\n   See \"Cluster routing\" in\n   [.agents/docs/architecture.md](../../../.agents/docs/architecture.md).\n9. **Read-only command?** Register it in\n   `src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java` (`CommandName`\n   enum) so replica-read routing knows, and bump the size assertion in\n   `ClusterReadOnlyCommandsUnitTests`.\n\n**C. Extension needing new overloads / a new `*Args` class** (hybrid): the new\nmethods go through the full matrix of B; the option plumbing follows A. Check\nwhether command helpers must follow (`ScanIterator`/`ScanStream`/`ScanFlow` for\nscan-family commands).\n\n**D. New command group / module area** (Search/JSON/Bloom/VectorSet-style):\n- **Unlike Jedis, Lettuce module areas are full citizens**: every group gets all\n  six flavors — sync, async, reactive, Kotlin coroutines, and both node-selection\n  interfaces — plus Kotlin impls.\n- Create the flavor interfaces by mirroring an existing area end-to-end, register\n  the group in the `CommandInterfaces` enum\n  (`src/test/java/io/lettuce/core/api/consistency/`), and wire the group into the\n  hand-written aggregate interfaces (`RedisCommands`, `RedisAsyncCommands`,\n  `RedisReactiveCommands`, and the cluster variants) so they `extend` it — the\n  consistency tests enforce the aggregate wiring.\n- Areas get their **own builder** (`Redis<Area>CommandBuilder`, cf.\n  `RediSearchCommandBuilder`) with a matching\n  `Redis<Area>CommandBuilderUnitTests`, and keep their argument types and reply\n  parsers in an area package (e.g. `core/search/arguments/`).\n- Module commands still gate on server capability, not version:\n  `@EnabledOnCommand(\"FT.CREATE\")`-style probes; integration tests target the\n  stack node.\n\n## Types & args conventions\n\n- **Argument types**: an options object is a `*Args implements CompositeArgument`\n  class (e.g. `io.lettuce.core.CopyArgs`) whose `build(CommandArgs)` appends its\n  tokens. Fluent setters return `this`. If two overloads share options but differ\n  in a typed field (long vs double), use a self-typed abstract base\n  (`BaseFooArgs<T extends BaseFooArgs<T>>`) with concrete subclasses — cf.\n  `BaseIncrexArgs`/`IncrexArgs`/`IncrexFloatArgs`.\n- **`@since` goes on every new public element, not just the class.** A\n  class-level `@since` is **not inherited**: the nested `Builder` type, each of\n  its static factory methods, and each public fluent setter needs its own\n  `@since` tag, or the generated API docs lose the release provenance for those\n  members.\n- Token-valued argument enums are plain enums whose values the builder/args class\n  appends (cf. `XNackMode`).\n- **Response types**: reuse existing models where possible — `Value`, `KeyValue`,\n  `ScoredValue`, `GeoCoordinates`, `GeoWithin`, `StreamMessage`, `KeyScanCursor`\n  (all in `io.lettuce.core`) — and add a new one only when the reply genuinely\n  doesn't map. For a map-shaped/structured reply, pair a model class with a\n  `ComplexDataParser` consumed via `ComplexOutput` (cf. `HotkeysReply` +\n  `HotkeysReplyParser`).\n- **Return-type idioms** (established conventions): `1/0` integer reply →\n  `Boolean` (cf. `copy`, `expire`, `hsetnx`); count → `Long`; status → `String`;\n  bulk value → `V`. And the overload rules from step B.2: every varargs\n  parameter also gets a single-argument overload, while a multi-key command with\n  an `*Args` object takes `List<K>` (not varargs) plus fixed-arity overloads.\n- The `CommandOutput` (the reply *parser*) is chosen at the builder step from the\n  **observed** RESP2/RESP3 replies of Phase 0 — if none fits, add one under\n  `io.lettuce.core.output` with a unit test (cf. `IncrexLongOutput`).\n\n## The consistency suite is the safety net\n\nAfter mirroring, run:\n\n```bash\nmvn -Dtest='*ConsistencyUnitTests,CommandBuilderCoverageUnitTests' \\\n    -Dsurefire.failIfNoSpecifiedTests=false test\n```\n\nIt names exactly the flavor/signature you missed. For a genuinely unusual return\ntype (e.g. `Flux<Value<Long>>`, or `Mono<List<Double>>` because Redis returns\nnulls), register it in the registry that owns the flavor —\n`src/test/java/io/lettuce/core/api/consistency/KnownApiDeviations.java` for the\nJava flavors, `src/test/kotlin/io/lettuce/core/api/consistency/KnownKotlinApiDeviations.kt`\nfor the coroutine flavor — **with a comment justifying it**. Never use a\ndeviation entry to paper over a sync/async signature mismatch — that breaks the\nsync-over-async runtime proxy.\n\n**Format before you build**: run `mvn formatter:format` after hand-editing — the\nbuild's `formatter:validate` step fails the compile on unformatted code. Do not\nsubmit formatting-only diffs.\n\n## Test matrix — what to write\n\nNaming, placement, and the base/overload structure are owned by\n[.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n— follow it. The established per-command layers (write all that apply):\n\n1. **Args unit tests** (`*Args` classes): assert the exact encoded tokens and\n   wire order, setter validation, and overload equivalence — e.g.\n   `IncrexArgsUnitTests`, `XAddArgsUnitTests`. No server needed.\n2. **Builder unit test**: assert the constructed command and encoded args —\n   including the RESP2/RESP3 output shape observed in Phase 0. Core commands go\n   in `src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java`; area\n   commands in their `Redis<Area>CommandBuilderUnitTests`.\n3. **Output unit tests** when a new `CommandOutput` was added (cf.\n   `IncrexOutputUnitTests`).\n4. **Integration tests**: add methods to the sync base class\n   (`<Group>CommandIntegrationTests`), gated per-test with\n   `@EnabledOnCommand(\"<NAME>\")`. Use assertions derived from the redis-cli\n   showcase transcripts — real semantics, not just \"no error\", covering the whole\n   family the option touches (with/without optional args, error cases):\n   ```java\n   @Test\n   @EnabledOnCommand(\"COPY\")\n   void copy() {\n       redis.set(key, value);\n       assertThat(redis.copy(key, key + \"2\")).isTrue();\n   }\n   ```\n5. **Overloads**: the base's `@Test` methods re-run automatically under the\n   group's existing RESP2/cluster/reactive/Tx overload classes — but **only the\n   ones that exist**. Check the target group against peer groups and **create a\n   missing overload class** when it matters for the command (INCREX created\n   `StringCommandResp2IntegrationTests` because its reply differs by protocol).\n   Provide the base test at minimum; add overloads that carry real\n   risk (RESP2 when replies differ, cluster when routing matters).\n\n## Running the tests\n\nThe build pins a specific JDK to match CI — check `.github/workflows/` and the\nlocal-gotchas section of\n[.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n(pin `JAVA_HOME`, worktree `git-commit-id-plugin` skip, `TEST_WORK_FOLDER`).\n\n- Unit tests (Surefire, no server): `mvn clean test`, or `mvn -Dtest=FooUnitTests test`.\n- Integration tests (Failsafe) need the Docker env from Phase 0 and the `verify`\n  lifecycle — **`-Dit.test=` filters Failsafe, `-Dtest=` does not**:\n  ```bash\n  TEST_WORK_FOLDER=./work/docker mvn -DskipITs=false -DskipUnitTests=true \\\n    -Dit.test=FooIntegrationTests verify -Pci\n  ```\n  If the command isn't in any published `redislabs/client-libs-test` tag yet, say\n  so: the integration tests will be skipped by `@EnabledOnCommand` (expected and\n  acceptable), but they must still be written and compile.\n\n**Tear the environment down when you are done.** The Docker topology started in\nPhase 0 keeps running (and holds the test ports) until stopped. After the final\nverification run — and equally when the task is aborted or fails partway — run:\n\n```bash\nmake stop\n```\n\n## PR hygiene checklist (verify before finishing)\n\n- [ ] Every layer of the chosen matrix updated consistently; the consistency suite\n      and `CommandBuilderCoverageUnitTests` pass.\n- [ ] Every varargs parameter has its single-argument overload, and every\n      multi-key command with an `*Args` object uses `List<K>` (not varargs) with\n      fixed-arity overloads — mirrored across all flavors (or a\n      maintainer-approved justification from the plan).\n- [ ] `@since` on **every** new public element — including the nested `Builder`,\n      static factories, and fluent setters of new `*Args` classes; class-level\n      tags are not inherited (see\n      [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md)). Javadoc written on\n      the sync interface and mirrored with flavor-appropriate `@return` phrasing.\n- [ ] Builder-validated preconditions documented on all flavors: `@param`\n      constraint phrases + `@throws IllegalArgumentException if …`.\n- [ ] No dead `CommandKeyword` constants; no keyword duplicating a `CommandType`.\n- [ ] Read-only commands registered in `ReadOnlyCommands` (+ count test bumped).\n- [ ] `mvn formatter:format` run; no formatting-only noise in the diff.\n- [ ] Tests at every applicable layer, gated with `@EnabledOnCommand`; missing\n      integration overload classes created where the command needs them.\n- [ ] `docs/` (MkDocs) updated — a new command or option **is** user-facing: add\n      a one-line entry to the current-release section of `docs/new-features.md`\n      (follow its existing \"Support for [`X`](redis.io link) …\" pattern), plus\n      any feature page the change affects.\n- [ ] `.env.vX.XX` image pin bumped if the feature needed a newer server build.\n- [ ] PR description states: server PR link, HLD link, gating choice and why,\n      behavior against older servers, and includes a showcase transcript. (Draft\n      with the [draft-pr-description](../draft-pr-description/SKILL.md) skill;\n      remember the guardrail — the agent never creates the PR itself.)\n- [ ] Docker test environment stopped (`make stop`) after the final verification\n      run.\n\n## Top pitfalls\n\n1. **Skipping the live verification / not checking RESP2 vs RESP3.** The reply\n   shape can differ between protocols; it determines the `CommandOutput` and the\n   reactive mapping. Confirm against a running server, don't assume.\n2. **Adding Args/response types after the interface edits** — every flavor\n   references them; the project won't compile. Types come first.\n3. **Editing only some flavors, or silencing the consistency suite** with a\n   deviations-registry entry instead of fixing the signature.\n4. **Forgetting a dispatch layer** — both `AbstractRedisAsyncCommands` *and*\n   `AbstractRedisReactiveCommands`, plus the Kotlin `*Impl.kt`.\n5. **Recreating the removed generator source files** under\n   `src/test/java/io/lettuce/core/api/` because an old reference PR touched them.\n6. **Wrong `CommandArgs` order or `CommandOutput`**, missing `@since`, missing\n   `@EnabledOnCommand` gating, or missing the read-only registry entry.\n7. **Letting a traced precedent override a written convention** — e.g. skipping\n   the single-argument overload because `sintercard(K...)` doesn't have one, or\n   stopping at a class-level `@since` because an old `*Args` class did. Older\n   code predates the rules; the conventions win.\n",".agents/rules/java-rules.md":"# Java rules\n\nConventions for Java source that agents most often get wrong. The build does **not**\nenforce these — the Eclipse formatter (`formatting.xml`) reformats whitespace but does\nnot add missing braces or convert fully-qualified names to imports, and there is no\nCheckstyle — so apply them by hand.\n\n- **Use imports, not fully-qualified names.** Reference a type by its simple name and\n  add an `import`. Do not inline a package-qualified name in code\n  (e.g. write `RedisCommands`, not `io.lettuce.core.api.sync.RedisCommands`). The only\n  exception is disambiguating a genuine clash between two same-named types in one file.\n- **No wildcard imports** (`import foo.bar.*;`). Import each type explicitly.\n- **Always use braces on control statements.** `if`, `else`, `for`, `while`, and `do`\n  take a block even for a single-statement body — never a braceless one-liner:\n\n  ```java\n  // NO\n  if (key == null)\n      return;\n\n  // YES\n  if (key == null) {\n      return;\n  }\n  ```\n\n- **One statement per line.**\n\nRun `mvn formatter:format` before finishing — it fixes layout, but not the rules above.\n",".agents/rules/rules.md":"# Coding rules\n\nTool-neutral coding rules for this repo. Each file covers one area; read the\nrelevant rule set before editing that kind of file.\n\n| Rule set | Read before editing |\n|----------|---------------------|\n| [java-rules.md](java-rules.md) | Java source (`src/**/*.java`) |\n\nAdd a new file here as new rule areas come up, and list it in the table above.\n"},"files":{".agents/skills/writing-javadoc/SKILL.md":"---\nname: writing-javadoc\ndescription: Use when writing or editing Javadoc for Lettuce public API — new methods, classes, deprecations, or when a reviewer asks to fix or improve doc comments. Points to the full house ruleset in .agents/docs/javadoc.md (imperative method summaries, fixed tag order, @param/@return/@throws/@since/@deprecated house forms) and the rule that command-interface Javadoc is written on the sync interface first and mirrored to the other flavors. Trigger on \"write javadoc for\", \"document this method\", \"add a deprecation notice\", \"fix the doc comment\".\n---\n\n# Writing Javadoc for Lettuce\n\nThe complete, authoritative ruleset is **[.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md)** —\nread it before writing. This skill is the operating checklist; the doc wins on any\ndetail.\n\n## Before you write\n\n- **Is it a command interface?** The command interfaces are hand-edited, with the\n  **sync** interface's Javadoc as the reference text. Write or fix the comment on\n  the sync method first, then mirror it to **every** flavor that declares the\n  method, with the flavor-appropriate `@return` phrasing (`RedisFuture`,\n  `Mono`/`Flux`, …):\n  - `src/main/java/io/lettuce/core/api/{sync,async,reactive}/`\n  - the Kotlin coroutine interfaces (`src/main/kotlin/…/api/coroutines/`)\n  - the cluster node-selection interfaces\n    (`src/main/java/io/lettuce/core/cluster/api/{sync,async}/NodeSelection…`)\n  - the Sentinel interfaces (`src/main/java/io/lettuce/core/sentinel/api/…` and\n    their Kotlin flavor)\n\n  Javadoc parity is **not** test-enforced — keep the flavors in sync by hand. See\n  [api-consistency.md](../../../.agents/docs/api-consistency.md).\n\n## The rules most often gotten wrong\n\n1. **Golden rule:** document the caller-facing contract (inputs, outputs, effects,\n   errors, nullability) — never implementation details or refactor rationale.\n2. **First sentence:** imperative verb for methods (\"Append…\", \"Get…\", \"Remove…\"),\n   noun phrase for types. Not \"This method…\". Ends in a clean period.\n3. **Tag order:** `@param` → `@return` → `@throws` → `@author` → `@since` → `@see`\n   → `@deprecated`.\n4. **`@param`** for every parameter (type params `<K>`/`<V>` first); state\n   nullability with the house phrases `must not be {@code null}.` / `can be {@code null}.`\n5. **`@return`** for non-void; describe the value and its meaningful states, not the\n   type.\n6. **`@since`** is a bare version — `@since 7.7` (see .agents/docs/javadoc.md for deriving\n   the version from the build).\n7. **`@deprecated`** — keep the `@Deprecated` annotation and the tag in sync; house\n   form: `@deprecated since <version>, use {@link Replacement} instead; scheduled for\n   removal in a future major release.`\n8. **`@author`** on types only — the *maintainer's* name (an agent must not invent\n   one); don't reorder existing authors.\n9. Use `{@code null}` for literals; `{@link}` only to types resolvable on the\n   compile classpath (else `{@code TypeName}`).\n\n## Verify, don't guess\n\nMatch the surrounding file, and consult `.agents/docs/javadoc.md` for any subtle case\nrather than inventing a rule. Javadoc lint is off in the build (`<doclint>none</doclint>`),\nso review is the only check — get it right by hand.\n",".agents/skills/draft-pr-description/SKILL.md":"---\nname: draft-pr-description\ndescription: Generate a clear, concise GitHub PR title and description from the diff between two local git branches, and save it to prDescription.md in the repo root. Use this whenever the user asks to write, generate, draft, or update a PR description or PR title from local branch changes — including phrasing like \"summarize this diff into a PR description,\" \"write a PR description for my current branch,\" \"create a PR title and description,\" or any request to compare a base and target branch for PR purposes. Trigger even if the user doesn't name specific branches; this skill knows how to default them.\nargument-hint: [base-branch] [target-branch]\narguments: [base, target]\nallowed-tools: Bash(git diff *) Bash(git log *) Bash(git branch *)\n---\n\n# PR Description Generator\n\nWrites a GitHub PR title and description from the diff between two local git branches, focused on what reviewers need: purpose, key decisions/assumptions, behavioral or conceptual changes, and a brief testing note if relevant. Uses only standard git commands, so any agent can follow it.\n\n## Arguments\n\nTwo optional positional arguments — base branch, then target branch:\n\n- Base: `$base`\n- Target: `$target`\n\nAn empty value means \"not provided\" — default it per the resolution below (a missing argument is defaulted, not guessed). A target cannot be given without a base.\n\n## 1. Resolve branches\n\n- Both named → use as given.\n- Only one named → it's the **base**; target defaults to current branch.\n- None named → base defaults to `main`, then `master` if `main` doesn't exist (`git branch --list`). **Only check these two names — no scanning other branches, no guessing.** If neither exists, stop and ask. Target defaults to current branch (`git branch --show-current`).\n\nState the resolved base/target before proceeding.\n\n## 2. Diff\n\n- Confirm both branches exist (`git branch --list <name>`); stop if not.\n- `git diff <base>...<target>` and `git log <base>..<target> --oneline`\n- Empty diff → stop and say so, don't fabricate a description.\n\n## 3. Analyze\n\nFrom the diff/log, identify: purpose of the change; key decisions and trade-offs; assumptions the code relies on; behavioral changes (concrete: \"X now rejects Y\" not \"changed validation\"); conceptual/contract changes; breaking changes, new config/env vars, migrations; test changes (what new tests cover conceptually, why existing ones were adapted — not test-by-test).\n\nSkip routine stuff (formatting, trivial renames). No file/line enumeration — write like explaining to a teammate, not a changelog.\n\n## 4. Output structure\n\nWrite to `prDescription.md` (repo root, overwrite if exists):\n\n```markdown\n# <Title — imperative, <70 chars>\n\n## Summary\n<2-3 sentences>\n\n## Key Decisions & Assumptions\n<bullets — omit if none>\n\n## Behavioral / Conceptual Changes\n<bullets, concrete — omit if none>\n\n## Testing\n<2-3 sentences, only if diff touches tests — omit otherwise, don't write \"no tests\">\n\n## Notes\n<breaking changes, follow-ups — omit if none>\n```\n\nKeep it brief (readable in under a minute, ~150-200 words total unless the change is genuinely large). Omit empty sections rather than padding them. Never fabricate testing details, tickets, or names not evidenced in the diff — flag uncertainty in Notes instead.\n\n## 5. Confirm\n\nAfter writing, give a short confirmation (don't reprint the file) and offer to run `gh pr create --title \"...\" --body-file prDescription.md` if `gh` is available. Include the provided/resolved base and target branch names into the suggested command as part of the suggestion. At the end, ask if the user wants to open the PR now and wait for confirmation before proceeding. If the user declines, stop and say the PR description is ready in `prDescription.md`.\n",".claude/rules/rules.md":"# Rules\n\nThis project's coding rules are maintained **tool-neutrally** in `.agents/rules/` so\nevery agent shares one source. This file is the Claude Code entry point into them.\n\nRead [`.agents/rules/rules.md`](../../.agents/rules/rules.md) for the index, and the\nrelevant rule set before editing — e.g. [`.agents/rules/java-rules.md`](../../.agents/rules/java-rules.md)\nwhen editing Java source (`src/**/*.java`).\n","CLAUDE.md":"@AGENTS.md\n\n<!--\nThe shared operating manual lives in AGENTS.md (imported above). Reusable task\nprocedures live in .agents/skills/ and are referenced from AGENTS.md.\n-->\n","AGENTS.md":"# Lettuce Coding Conventions\n\nEssential rules for working on the Lettuce codebase — a scalable, thread-safe\nRedis client for the JVM built on netty and Project Reactor. Detailed guidance\nfor specific tasks lives in `.agents/skills/`; consult the relevant skill when\nperforming that type of work.\n\n## How this guidance is organized\n\nAgent guidance is **self-contained under agent-owned locations** and never mixed into\nthe published documentation site:\n\n- **Agent-facing** (this material) lives at the repo root (`AGENTS.md`, `CLAUDE.md`),\n  under `.agents/` (tool-neutral: `.agents/docs/`, `.agents/rules/`, `.agents/skills/`),\n  and `.claude/` (Claude-specific tooling that bridges to `.agents/`). None of it is\n  published by MkDocs.\n- **User-facing** contributor/user documentation lives under `docs/` and is published\n  to the docs site. Agents read the agent-owned copies, not `docs/`.\n\nWithin the agent material it's a small graph: **each topic has one owner file**, and\neverything else **links** to it rather than restating it. When a topic changes, edit\nthe owner only.\n\n| Topic | Owner |\n|-------|-------|\n| Always-on facts, guardrails, index (this file) | `AGENTS.md` |\n| Command flow & code generation | `.agents/docs/architecture.md` |\n| API consistency (flavors, mapping rules, tests) | `.agents/docs/api-consistency.md` |\n| Testing — environment, running, naming, layout, CI | `.agents/docs/integration-testing.md` |\n| Javadoc conventions (incl. `@since` derivation) | `.agents/docs/javadoc.md` |\n| Coding rules (Java style, …) | `.agents/rules/*` |\n| Task procedures (add a command, write Javadoc, PR description) | `.agents/skills/*` |\n\nRule: state a fact in its owner and link from elsewhere; don't copy it. This file\nkeeps only a minimal always-on quick-reference. Skills own the *sequence* of a task\nand link out for the mechanics.\n\n## General Principles\n\n- **Update documentation.** When changes affect user-facing behavior, config, or\n  public API, update the relevant pages under `docs/` (MkDocs).\n- **Add or update tests.** Bug fixes need a reproducer test; new features need\n  tests. See `.agents/docs/integration-testing.md` for unit vs. integration conventions.\n- **You are responsible for what you submit.** Validate all changes. Do not submit\n  AI-generated code without human oversight.\n\n## Guardrails (agents)\n\nThese are hard rules for automated agents. A human maintainer performs these\nactions, not the agent.\n\n- **Never `git commit` or `git push`.** Stage and describe changes; leave\n  committing and pushing to the maintainer.\n- **Never create or merge pull requests, and never reply to GitHub issues or PRs**\n  (no `gh pr create`/`merge`/`comment`/`review`, no `gh issue create`/`comment`).\n  Draft text for the maintainer to post if asked — for a PR description, use the\n  `draft-pr-description` skill, which writes to `prDescription.md`.\n\n## Requirements & Toolchain\n\n- Builds and runs on the JDK baseline declared in `pom.xml`\n  (`maven.compiler.source`/`target`); targets Redis 2.6+.\n- **The test build pins a specific JDK to match CI** — check `.github/workflows/`\n  (or `.agents/docs/integration-testing.md`) and use that JDK to build and reproduce CI.\n- Build tool is **Maven** (`mvn`). Integration tests require **Docker**.\n- **Prefer semantic code tooling over text search for Java, when available.** If a\n  Java language server or IDE integration is present (e.g. an `jdtls`-based LSP, or\n  an IDE MCP), use it for definitions, references, symbol/type search, call\n  hierarchy, and diagnostics instead of grepping. Fall back to text search only when\n  no such tool is available.\n\n## Project Structure\n\n```\nsrc/main/java/io/lettuce/core/                Core client, connections, commands\nsrc/main/java/io/lettuce/core/cluster/        Redis Cluster support\nsrc/main/java/io/lettuce/core/masterreplica/  Master/Replica & Sentinel\nsrc/main/java/io/lettuce/core/pubsub/         Pub/Sub\nsrc/main/java/io/lettuce/core/support/        Connection pooling (ConnectionPoolSupport, BoundedAsyncPool)\nsrc/main/java/io/lettuce/core/api/            Sync/async/reactive command interfaces\nsrc/test/java/io/lettuce/                      Tests (see .agents/docs/integration-testing.md §5)\nformatting.xml                                 Eclipse formatter rules\nMakefile                                       Docker-backed test lifecycle\n```\n\nWhere to look: connect → `core/`; pooling → `core/support/`; sharding →\n`core/cluster/`; HA/Sentinel → `core/masterreplica/`.\n\n**How a command flows** through the API interfaces, command builder, codec, and\nthe netty/RESP wire layer is documented in `.agents/docs/architecture.md`. Read it\nbefore adding commands or touching the protocol layer — note the\nsync/async/reactive/Kotlin command interfaces are **hand-edited** and kept in\nlockstep by the API consistency test suite (see `.agents/docs/api-consistency.md`).\n\n## Build & Test Commands\n\n**Unit tests** (pure logic, no server — named `*UnitTests`, run by Surefire):\n\n```bash\nmvn clean test                       # build + run unit tests, no Redis needed\nmvn test -Dtest=FooUnitTests         # a single unit test\n```\n\n**Integration tests** (need a running Redis — named `*IntegrationTests`, run by\nFailsafe; **off by default**, `make test` enables them via `-DskipITs=false`):\n\n```bash\nmake start                           # boot the Dockerized Redis topology (add version=<v> to pick one)\nmake test                            # full build incl. integration tests\nmake stop                            # tear the environment down\n```\n\nRun one integration test fast (env already started):\n\n```bash\nTEST_WORK_FOLDER=./work/docker mvn -DskipITs=false -DskipUnitTests=true \\\n  -Dit.test=FooIntegrationTests verify -Pci\n```\n\nOne caveat worth remembering: `-Dit.test=` filters Failsafe (a single integration\ntest); `-DskipUnitTests=true` runs integration only. The full flag reference,\nRedis-version matrix, topologies, TLS/mTLS and CI mapping live in\n[`.agents/docs/integration-testing.md`](.agents/docs/integration-testing.md).\n\n**Apply formatting** (required before committing):\n\n```bash\nmvn formatter:format                 # applies formatting.xml; do NOT submit formatting-only diffs\n```\n\n## Testing Rules\n\n- **The file-name suffix picks the runner:** `*UnitTests` (Surefire, no server),\n  `*IntegrationTests` (Failsafe, needs Redis). Prefer those suffixes. A singular\n  `*Test` suffix is excluded by Surefire and picked up by Failsafe instead, so such a\n  class never runs in `mvn test`; it only runs in the integration phase (`make test`,\n  which is what CI runs, Docker required) or when named explicitly via `-Dtest=`. Name\n  pure unit tests `*UnitTests`.\n- Integration tests read host/port/TLS from `TestSettings` / `TlsSettings` — never\n  hard-code them.\n- Everything else about testing (topologies, TLS/mTLS, layout, CI) lives in\n  [`.agents/docs/integration-testing.md`](.agents/docs/integration-testing.md).\n\n## Coding Style Essentials\n\n- 4-space indentation, enforced by `formatter-maven-plugin` — run\n  `mvn formatter:format`. Don't submit formatting-only changes (they create noise\n  and get PRs rejected).\n- **Add `@since <version>` to all new public API** — see\n  [`.agents/docs/javadoc.md`](.agents/docs/javadoc.md) for the version-derivation recipe.\n- **`@author` is the human contributor's.** Maintainers add their own `@author` to\n  files they touch (per CONTRIBUTING). An automated agent must **not** invent an\n  `@author` for itself — leave the tag to the maintainer unless told which name to add.\n- **Java style** — imports over fully-qualified names, braces on every control\n  statement (no braceless one-liners), no wildcard imports. Full list in\n  `.agents/rules/java-rules.md` (see `.agents/rules/` before editing Java).\n- Javadoc for public API follows house conventions — see `.agents/docs/javadoc.md`, or use\n  the `writing-javadoc` skill.\n\n## Commit & PR\n\n- Open a feature-request issue first; reference its number in the PR\n  (`<text> #<ticket>`).\n- Include unit and/or integration tests for every change.\n- Split feature/bugfix commits from formatting/polishing commits.\n- Follow `.github/PULL_REQUEST_TEMPLATE.md`.\n\n## Key References\n\n| Document | What it covers |\n|----------|----------------|\n| `.github/CONTRIBUTING.md` | How to build, test, and contribute |\n| `.agents/docs/architecture.md` | How a command flows: API shapes, code generation, command model, codec, RESP/netty wire layer |\n| `.agents/docs/api-consistency.md` | The API flavors, their return-type mapping rules, and the consistency test suite that enforces them |\n| `.agents/docs/integration-testing.md` | Test environment, topologies, unit vs. integration, CI |\n| `.agents/docs/javadoc.md` | Javadoc house conventions for public API |\n| `.github/PULL_REQUEST_TEMPLATE.md` | PR checklist |\n| `formatting.xml` | Eclipse code-formatting rules |\n| [Javadoc](https://redis.github.io/lettuce/overview/) | Public API reference |\n\n## On-Demand Skills\n\nDetailed task guidance lives in `.agents/skills/`. Consult the relevant skill\nbefore doing that type of work.\n\n| Skill | When to use |\n|-------|-------------|\n| `extend-commands-api` | Add or extend a Redis command (or overload) end-to-end, evidence-first: HLD + server PR + live redis-cli verification, plan-mode approval, then all API interface flavors, builder, async/reactive/Kotlin impls, and tests |\n| `create-implementation-plan-for-redis-api-change` | Turn a shared client HLD into Lettuce's reviewed implementation plan (API, files per flavor, ordered steps, test matrix) without touching sources; read-only, writes one plan file; conventions come from `extend-commands-api` |\n| `writing-javadoc` | Write or fix Javadoc for public API following Lettuce house conventions |\n| `draft-pr-description` | Generate a GitHub PR title and description from the diff between two local branches |\n",".agents/skills/extend-commands-api/SKILL.md":"---\nname: extend-commands-api\ndescription: Add or extend Redis commands in the Lettuce client API end-to-end — a new core command, a family of new commands, an extension to an existing command's options, or a module/area command (Search/JSON/Bloom/VectorSet). Gathers evidence first (HLD document, the server-side PR in the repo owning the command family, live verification against the Dockerized test environment with redis-cli), plans the full implementation matrix in plan mode, then implements across all API flavors with unit and integration tests. Trigger on \"add support for the <X> command\", \"implement <REDIS COMMAND> in Lettuce\", \"extend <command> with <option>\", or adding a new argument/overload to an existing command.\nallowed-tools: Bash(mvn *), Bash(make *), Bash(redis-cli *), Bash(gh *)\n---\n\n# Extend the Lettuce Commands API\n\nImplement a new Redis command (or extend an existing one) in Lettuce, following the\nconventions maintainers enforce in review. The workflow is evidence-first: prove the\ncommand's behavior on a real server before designing the Java API.\n\nRead [.agents/docs/architecture.md](../../../.agents/docs/architecture.md) first for\nthe model behind this flow — especially that the sync/async/reactive/Kotlin command\n**interfaces are hand-edited source files kept in lockstep by the API consistency\ntest suite** (see [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md)).\n\n> **Historical caveat when reading old PRs**: PRs merged before the generator removal\n> also edit `src/test/java/io/lettuce/core/api/<Group>Commands.java` — the old\n> template source files. Those files **no longer exist**; do not recreate them. The\n> flavor interfaces are now edited directly.\n\n## Phase 0 — Gather evidence BEFORE planning\n\nDo all of the following before writing any plan or code:\n\n1. **Ask the user for the HLD.** Interactively ask for the path to a markdown file\n   containing the High-Level Design for the command(s) (or confirm none exists). If\n   a path is given, read it fully — it is the primary source for syntax, semantics,\n   reply shape per RESP2/RESP3, and edge cases.\n\n2. **Find the server-side PR in the repo that owns the command.** Route the\n   search by command family — module command families are developed in their\n   owning repositories, not in `redis/redis`:\n\n   | Command family | Repository | Syntax source |\n   |----------------|------------|---------------|\n   | Core commands, vector sets (`VADD`, …) | `redis/redis` | `src/commands/*.json` |\n   | Search (`FT.*`) | `RediSearch/RediSearch` | PR diff + command docs (no `src/commands/*.json`) |\n   | JSON (`JSON.*`) | `RedisJSON/RedisJSON` | PR diff + command docs |\n   | Probabilistic (`BF.*`, `CF.*`, `CMS.*`, `TOPK.*`, `TDIGEST.*`) | `RedisBloom/RedisBloom` | PR diff + command docs |\n   | Time series (`TS.*`) | `RedisTimeSeries/RedisTimeSeries` | PR diff + command docs |\n\n   If the search comes up empty in the routed repo, fall back to `redis/redis`\n   (and vice versa) before concluding there is no server PR.\n\n   First verify that `gh` works in the current (sandboxed) environment:\n   ```bash\n   gh auth status\n   ```\n   Sandboxes often block access to credential files, so `gh` may report\n   unauthenticated here even though it works on the user's machine. If so, ask the\n   user for permission to run these specific read-only `gh` commands outside the\n   sandbox; only fall back to the unauthenticated GitHub REST API if they decline.\n\n   Then search for the PR that adds/extends the command on the server:\n   ```bash\n   gh search prs --repo <owning-repo> \"<COMMAND NAME>\" --limit 10\n   gh pr view <num> --repo <owning-repo>\n   gh pr diff <num> --repo <owning-repo>  # in redis/redis: src/commands/*.json has exact syntax\n   ```\n   Extract: exact wire syntax (argument order and optionality), reply type per\n   **RESP2 and RESP3** (they can differ — this determines the `CommandOutput` and\n   the reactive `Mono`/`Flux` mapping), error conditions, and the **first server\n   version carrying the feature** (drives `@EnabledOnCommand` gating and the test\n   env version). Note whether the server marks the feature *experimental/preview*.\n\n3. **Verify the command exists on the \"next\" Redis OSS version.** Start the\n   integration environment using the **highest** version the Makefile supports\n   (check `SUPPORTED_TEST_ENV_VERSIONS` in the `Makefile`, or the\n   `src/test/resources/docker-env/.env.vX.XX` files — pick numerically, `8.10` >\n   `8.8`):\n   ```bash\n   grep SUPPORTED_TEST_ENV_VERSIONS Makefile\n   make start version=8.10\n   ```\n   Then probe the running servers. Test connection defaults live in\n   `src/test/java/io/lettuce/test/settings/TestSettings.java` — the main standalone\n   node is `localhost:6479` (no auth); module commands (`FT.*`, `JSON.*`, `BF.*`,\n   `VADD`, …) run on the stack node at `localhost:16379`:\n   ```bash\n   redis-cli -p 6479 INFO server | grep redis_version\n   redis-cli -p 6479 COMMAND INFO <COMMAND>   # non-empty → command exists\n   redis-cli -p 6479 COMMAND DOCS <COMMAND>   # arity/args — compare with the server PR\n   ```\n   For a NEW command, `COMMAND INFO` must return a non-empty reply. For an EXTENDED\n   command, additionally invoke it with the new syntax against a scratch key and\n   confirm the server accepts it (no `ERR syntax error` / `ERR unknown argument`).\n\n   **If the command/option is missing on the latest env version**, the image tag\n   pinned in the `.env.vX.XX` file is too old. Ask the user for a\n   `redislabs/client-libs-test` image tag that contains it (e.g. an RC/edge build —\n   tags are listed at https://hub.docker.com/r/redislabs/client-libs-test/tags; you\n   may check and suggest candidates), then:\n   ```bash\n   make stop\n   CLIENT_LIBS_TEST_IMAGE_TAG=<tag> make start\n   ```\n   If the PR should also move CI to that build, update the newest `.env.vX.XX`\n   pin as part of the change — command PRs do this when they need a fresh server\n   (cf. the hotkeys PR bumping `.env.v8.6`). If no tag carries the feature, report\n   it and proceed: implementation continues, integration tests stay gated until an\n   image ships the change. Keep the environment running for later test runs.\n\n4. **Create redis-cli showcase test cases.** Once the command is available, derive\n   a small set of redis-cli scenarios from the HLD and the server PR and run them.\n   These serve two purposes:\n   - **Smoke test**: prove the documented behavior holds — happy path, each new\n     option/keyword, reply shape (run with `redis-cli -3` too when RESP3 replies\n     differ), edge cases and error conditions (missing key, out-of-range args,\n     conflicting options) — so the Java implementation is built against observed\n     replies, not assumptions.\n   - **Showcase**: each scenario should read as a mini use-case explaining WHY the\n     command/option exists. Prefer realistic data over `foo`/`bar`.\n\n   Save the scenarios as a commented script in a scratch file (one block per\n   use-case: a \"what this demonstrates\" comment, the commands, the observed reply\n   pasted back as comments). Carry this forward: it feeds the Phase 1 plan, the\n   test assertions, and the PR description. If the command could not be made\n   available on any image, still write the scenarios as *expected* transcripts and\n   mark them unverified.\n\n5. **Read the testing and consistency docs**:\n   [.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n   (environment, `*UnitTests` vs `*IntegrationTests` naming, base/overload test\n   structure, running one test) and\n   [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md) (the\n   flavors and their return-type mapping rules).\n\n6. **Trace one analogous existing command** end-to-end (same command group, similar\n   reply shape) so the plan mirrors real code, not guesswork. Good reference PRs,\n   validated against git history:\n\n   | Commit | What it exemplifies |\n   |--------|---------------------|\n   | `7ca3e9cc0` (CLIENT NO-TOUCH #3776) | Minimal new command: all 6 flavors + builder + 2 dispatch layers + Kotlin impl + builder unit test + integration test |\n   | `312ecc7b4` (INCREX #3746) | New command with args: self-typed abstract args base, new value type, new `CommandOutput`s, per-type args/output unit tests, **creating a missing RESP2 overload class** |\n   | `b009df37b` (XNACK #3728) | New command with an enum-valued argument |\n   | `9a0899875` (BITOP extensions #3334) | New operations/overloads on an existing command |\n   | `c603c8120` (HSCAN NOVALUES #2816) | New overload rippling into helpers (`ScanIterator`, `ScanStream`, `ScanFlow`) |\n   | `aa7b4b0be` (stream idempotency #3637) | Option added to an existing `*Args` class flows through with **no interface change** (the `XAddArgs` part) |\n   | `a209fba70` (CAS/CAD #3512) | Read-only command registered in `ReadOnlyCommands` + count test update |\n   | `838a4d39a` (HOTKEYS #3638) | Map-shaped reply via `ComplexOutput` + a `*ReplyParser`, cluster interface additions, full integration overload set, `.env` image bump |\n   | `6567f2d5e` (RediSearch #3375) | Whole new command area: own `Redis<Area>CommandBuilder`, `arguments`/reply-parser packages |\n\n   **Conventions beat precedent.** Traced reference commands can predate the\n   current conventions; when the reference code conflicts with a written rule in\n   this skill or the linked docs, the rule wins. Example: `sintercard(K...)`\n   ships without a single-key overload because it predates the\n   one-overload-per-varargs rule — a new command mirroring it must still add the\n   single-argument overload.\n\n7. **Determine the `@since` version** — recipe owned by\n   [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md):\n   ```bash\n   mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n   ```\n   Drop `-SNAPSHOT` and the patch digit (`7.7.0-SNAPSHOT` → `@since 7.7`).\n\n**The evidence is your working context, not a deliverable.** Hold it in mind to\ndrive the work — do not paste it back to the maintainer or pause for \"spec\napproval.\" The only stop for the maintainer is the Phase 1 plan approval; surface\ngenuinely ambiguous design choices *there*, with the proposed sync signatures,\nrather than interrupting earlier.\n\n## Phase 1 — Plan mode, then explicit approval\n\nEnter **plan mode**. Using the evidence, classify the change with the decision tree\nbelow and enumerate the exact file-by-file touch list, the test matrix, and the\ngating annotations. The plan must contain:\n\n- A short \"what this feature enables\" section built from the showcase scenarios\n  (Phase 0 step 4), including one or two representative command/reply transcripts.\n- **The proposed sync interface signature(s) with their Javadoc.** The sync\n  interface is the human-authored **API contract**; every other flavor is derived\n  from it and it is costly to change once mirrored. Plan approval is the\n  maintainer's sign-off on that contract — if the signatures must deviate later\n  during implementation, stop and re-confirm before mirroring.\n- **The complete overload set, enumerated.** For every varargs parameter, list\n  the matching single-argument overload; for a multi-key command with an `*Args`\n  object, use the `List<K>` shape (no varargs) and list the fixed-arity\n  convenience overloads instead — both house rules live in \"Types & args\n  conventions\". Justify any overload you omit so the maintainer signs off on the\n  exception. An overload discovered missing in review means reworking all six\n  flavors.\n\nPresent the plan and **explicitly ask permission to execute** before implementing.\nDo not start editing files until the user approves.\n\nOnce approved, copy this checklist into your working notes and tick items off:\n\n```\nExtend-commands progress:\n- [ ] 0. Evidence: HLD, server PR, live probe + showcase, RESP2/RESP3 replies, @since\n- [ ] 1. Plan approved — incl. the sync signature(s), the API contract\n- [ ] 2. Types: argument & response types (they must exist before any interface edit)\n- [ ] 3. Sync interface: full overload set (varargs → single-arg too) + Javadoc\n       (@param constraints + @throws for builder-validated preconditions)\n- [ ] 4. Mirror: async, reactive, Kotlin, NodeSelection×2 — consistency tests pass\n- [ ] 5. Implementations: CommandType/Keyword, builder, async, reactive, Kotlin impl\n- [ ] 6. Tests: args/builder/output unit tests + integration base/overloads\n- [ ] 7. Docs: entry in the current-release section of docs/new-features.md\n- [ ] 8. Verify: mvn clean test + a single integration test run; then make stop\n```\n\n## Decision tree — what kind of change is this?\n\n**A. Extension of an existing command that fits an existing `*Args` class**\n(new option token / new field):\n- Touch ONLY the `*Args` class (+ `CommandKeyword` for new tokens) + tests. Do NOT\n  touch the command interfaces, builder signatures, or dispatch layers — the\n  existing `args.build(commandArgs)` delegation carries the new option through\n  automatically (cf. the `XAddArgs` part of the stream-idempotency PR).\n- Add a fluent setter returning `this`; register new tokens in\n  `src/main/java/io/lettuce/core/protocol/CommandKeyword.java`.\n- If the reply shape grows, extend the response model/output backward-compatibly.\n\n**B. New command(s) in an existing group — core or module area** — the FULL\nmatrix, in this order (types first — every flavor references them, so they must\nexist to compile). For a command joining an **existing module area** (a new\n`FT.*` method in the Search group, a new `JSON.*` method, …) the same matrix\napplies with the area substitutions: the group is the area's flavor interfaces,\nthe builder is the area's `Redis<Area>CommandBuilder` (+ its\n`Redis<Area>CommandBuilderUnitTests`), argument/reply types go in the area\npackage, and gating/tests follow the module rules in D (capability probe, stack\nnode). The dispatch layers are the same `AbstractRedisAsyncCommands` /\n`AbstractRedisReactiveCommands` and Kotlin `*Impl.kt` as for core commands.\n1. **Argument/response types** — see \"Types & args conventions\" below.\n2. **Sync interface** `src/main/java/io/lettuce/core/api/sync/<Group>Commands.java`\n   — pick the group by command family (STRING → `RedisStringCommands`, HASH →\n   `RedisHashCommands`, generic-key → `RedisKeyCommands`, …). This is the reference\n   signature + Javadoc all flavors mirror (see the\n   [writing-javadoc](../writing-javadoc/SKILL.md) skill; `@since` mandatory):\n   ```java\n   /**\n    * Returns the length of the string value stored at {@code key}.\n    *\n    * @param key the key, must not be {@code null}.\n    * @return the length of the string at {@code key}, or {@code 0} when {@code key} does\n    *         not exist.\n    * @since 7.7\n    */\n   Long strlen(K key);\n   ```\n   Three contract rules to apply while designing the signatures and their Javadoc:\n   - **Multi-key commands that also take an `*Args` object take the keys as\n     `List<K>`, not varargs.** A varargs parameter must come last, so a trailing\n     options object can only follow it via an awkward leading placement (cf. the\n     older `sintercard(long limit, K... keys)`). For new commands, take the keys\n     as `List<K>` so the `*Args` argument comes last —\n     `sunioncard(List<K> keys, SUnionCardArgs args)` — and add fixed-arity\n     convenience overloads (`(K key1, K key2)` and their `*Args` variants)\n     instead of a varargs form. Since there is no varargs parameter here, the\n     single-argument-overload rule below does not apply; add only the overloads\n     that are meaningful — e.g. **no single-key `sunioncard`/`sdiffcard`**, whose\n     one-key union/difference is just `SCARD`.\n   - **Every varargs parameter gets a single-argument overload** —\n     `foo(K key, V value)` alongside `foo(K key, V... values)`. This is a hard\n     convention for new commands and overrides any traced precedent that lacks\n     it (older commands predate the rule). Does not apply to the `List<K>`-shaped\n     multi-key-with-`*Args` commands above, which take no varargs.\n   - **Validated preconditions are contract.** Any constraint the builder will\n     enforce (null checks, non-empty varargs) must be stated in the `@param`\n     text with the house phrases (`must not be {@code null}.`,\n     `must not be empty.`) *and* documented with a matching\n     `@throws IllegalArgumentException if …` tag — forms owned by\n     [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md). Both are then\n     mirrored to every flavor.\n3. **Mirror to every flavor**: async, reactive, Kotlin coroutines, and the two\n   cluster node-selection interfaces (`NodeSelection<Group>Commands` /\n   `NodeSelection<Group>AsyncCommands`), applying the per-flavor return-type\n   mapping rules from\n   [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md).\n4. **Protocol enums** — command name in\n   `src/main/java/io/lettuce/core/protocol/CommandType.java` (wire bytes derive\n   from the enum name); subcommand tokens in `CommandKeyword`. **Never add a\n   `CommandKeyword` that duplicates a name already in `CommandType`** (e.g. `SET`,\n   `DISCARD`) — the builder static-imports both enums and the bare name becomes\n   ambiguous; reference `CommandType.<NAME>` directly instead.\n5. **Builder** — `RedisCommandBuilder.java` (or the area builder, see D): null\n   checks via `LettuceAssert`/`notNullKey`, `CommandArgs` in **wire order**, the\n   `CommandOutput` chosen from the observed RESP2/RESP3 replies:\n   ```java\n   public Command<K, V, Long> strlen(K key) {\n       notNullKey(key);\n       return createCommand(STRLEN, new IntegerOutput<>(codec), key);\n   }\n\n   public Command<K, V, Boolean> copy(K source, K destination, CopyArgs copyArgs) {\n       LettuceAssert.notNull(source, \"Source \" + MUST_NOT_BE_NULL);\n       LettuceAssert.notNull(destination, \"Destination \" + MUST_NOT_BE_NULL);\n       CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(source).addKey(destination);\n       copyArgs.build(args);\n       return createCommand(COPY, new BooleanOutput<>(codec), args);\n   }\n   ```\n   Every `LettuceAssert` precondition written here is public contract: if the\n   interface Javadoc (step 2) does not already state the constraint and its\n   `@throws IllegalArgumentException`, go back and add it — on every flavor.\n6. **Dispatch layers** — one-liners in *both* `AbstractRedisAsyncCommands` *and*\n   `AbstractRedisReactiveCommands` (`createMono` for scalars,\n   `createDissolvingFlux` for `List`/`Set` replies, matching the interface's\n   `Mono`/`Flux`):\n   ```java\n   // AbstractRedisAsyncCommands\n   public RedisFuture<Long> strlen(K key) { return dispatch(commandBuilder.strlen(key)); }\n   // AbstractRedisReactiveCommands\n   public Mono<Long> strlen(K key) { return createMono(() -> commandBuilder.strlen(key)); }\n   ```\n7. **Kotlin impl** —\n   `src/main/kotlin/io/lettuce/core/api/coroutines/<Group>CoroutinesCommandsImpl.kt`:\n   ```kotlin\n   override suspend fun strlen(key: K): Long = ops.strlen(key).awaitSingle()\n   ```\n8. **Cluster** — pick the routing shape deliberately; there are three cases:\n   - A **single-key** command flows through automatically (routed by slot).\n   - A **broadcast/all-shards** command (its answer is the aggregate over the\n     whole cluster — cf. `dbsize`, `flushall`) needs hand-coded fan-out\n     overrides in `RedisAdvancedClusterAsyncCommandsImpl` *and* its reactive\n     sibling (`executeOnUpstream` + a `MultiNodeExecution` aggregator), and may\n     need methods on the cluster aggregate interfaces.\n   - A **node-specific** command (keyless, but its result is only meaningful per\n     node — cf. HOTKEYS) must **not** be fanned out: add `default` overrides on\n     the cluster aggregate interfaces that throw `UnsupportedOperationException`\n     and direct callers to the node-selection API or `getConnection(nodeId)`\n     (cf. `RedisClusterCommands.hotkeysReset()`); only the node-selection\n     flavors execute it.\n\n   See \"Cluster routing\" in\n   [.agents/docs/architecture.md](../../../.agents/docs/architecture.md).\n9. **Read-only command?** Register it in\n   `src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java` (`CommandName`\n   enum) so replica-read routing knows, and bump the size assertion in\n   `ClusterReadOnlyCommandsUnitTests`.\n\n**C. Extension needing new overloads / a new `*Args` class** (hybrid): the new\nmethods go through the full matrix of B; the option plumbing follows A. Check\nwhether command helpers must follow (`ScanIterator`/`ScanStream`/`ScanFlow` for\nscan-family commands).\n\n**D. New command group / module area** (Search/JSON/Bloom/VectorSet-style):\n- **Unlike Jedis, Lettuce module areas are full citizens**: every group gets all\n  six flavors — sync, async, reactive, Kotlin coroutines, and both node-selection\n  interfaces — plus Kotlin impls.\n- Create the flavor interfaces by mirroring an existing area end-to-end, register\n  the group in the `CommandInterfaces` enum\n  (`src/test/java/io/lettuce/core/api/consistency/`), and wire the group into the\n  hand-written aggregate interfaces (`RedisCommands`, `RedisAsyncCommands`,\n  `RedisReactiveCommands`, and the cluster variants) so they `extend` it — the\n  consistency tests enforce the aggregate wiring.\n- Areas get their **own builder** (`Redis<Area>CommandBuilder`, cf.\n  `RediSearchCommandBuilder`) with a matching\n  `Redis<Area>CommandBuilderUnitTests`, and keep their argument types and reply\n  parsers in an area package (e.g. `core/search/arguments/`).\n- Module commands still gate on server capability, not version:\n  `@EnabledOnCommand(\"FT.CREATE\")`-style probes; integration tests target the\n  stack node.\n\n## Types & args conventions\n\n- **Argument types**: an options object is a `*Args implements CompositeArgument`\n  class (e.g. `io.lettuce.core.CopyArgs`) whose `build(CommandArgs)` appends its\n  tokens. Fluent setters return `this`. If two overloads share options but differ\n  in a typed field (long vs double), use a self-typed abstract base\n  (`BaseFooArgs<T extends BaseFooArgs<T>>`) with concrete subclasses — cf.\n  `BaseIncrexArgs`/`IncrexArgs`/`IncrexFloatArgs`.\n- **`@since` goes on every new public element, not just the class.** A\n  class-level `@since` is **not inherited**: the nested `Builder` type, each of\n  its static factory methods, and each public fluent setter needs its own\n  `@since` tag, or the generated API docs lose the release provenance for those\n  members.\n- Token-valued argument enums are plain enums whose values the builder/args class\n  appends (cf. `XNackMode`).\n- **Response types**: reuse existing models where possible — `Value`, `KeyValue`,\n  `ScoredValue`, `GeoCoordinates`, `GeoWithin`, `StreamMessage`, `KeyScanCursor`\n  (all in `io.lettuce.core`) — and add a new one only when the reply genuinely\n  doesn't map. For a map-shaped/structured reply, pair a model class with a\n  `ComplexDataParser` consumed via `ComplexOutput` (cf. `HotkeysReply` +\n  `HotkeysReplyParser`).\n- **Return-type idioms** (established conventions): `1/0` integer reply →\n  `Boolean` (cf. `copy`, `expire`, `hsetnx`); count → `Long`; status → `String`;\n  bulk value → `V`. And the overload rules from step B.2: every varargs\n  parameter also gets a single-argument overload, while a multi-key command with\n  an `*Args` object takes `List<K>` (not varargs) plus fixed-arity overloads.\n- The `CommandOutput` (the reply *parser*) is chosen at the builder step from the\n  **observed** RESP2/RESP3 replies of Phase 0 — if none fits, add one under\n  `io.lettuce.core.output` with a unit test (cf. `IncrexLongOutput`).\n\n## The consistency suite is the safety net\n\nAfter mirroring, run:\n\n```bash\nmvn -Dtest='*ConsistencyUnitTests,CommandBuilderCoverageUnitTests' \\\n    -Dsurefire.failIfNoSpecifiedTests=false test\n```\n\nIt names exactly the flavor/signature you missed. For a genuinely unusual return\ntype (e.g. `Flux<Value<Long>>`, or `Mono<List<Double>>` because Redis returns\nnulls), register it in the registry that owns the flavor —\n`src/test/java/io/lettuce/core/api/consistency/KnownApiDeviations.java` for the\nJava flavors, `src/test/kotlin/io/lettuce/core/api/consistency/KnownKotlinApiDeviations.kt`\nfor the coroutine flavor — **with a comment justifying it**. Never use a\ndeviation entry to paper over a sync/async signature mismatch — that breaks the\nsync-over-async runtime proxy.\n\n**Format before you build**: run `mvn formatter:format` after hand-editing — the\nbuild's `formatter:validate` step fails the compile on unformatted code. Do not\nsubmit formatting-only diffs.\n\n## Test matrix — what to write\n\nNaming, placement, and the base/overload structure are owned by\n[.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n— follow it. The established per-command layers (write all that apply):\n\n1. **Args unit tests** (`*Args` classes): assert the exact encoded tokens and\n   wire order, setter validation, and overload equivalence — e.g.\n   `IncrexArgsUnitTests`, `XAddArgsUnitTests`. No server needed.\n2. **Builder unit test**: assert the constructed command and encoded args —\n   including the RESP2/RESP3 output shape observed in Phase 0. Core commands go\n   in `src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java`; area\n   commands in their `Redis<Area>CommandBuilderUnitTests`.\n3. **Output unit tests** when a new `CommandOutput` was added (cf.\n   `IncrexOutputUnitTests`).\n4. **Integration tests**: add methods to the sync base class\n   (`<Group>CommandIntegrationTests`), gated per-test with\n   `@EnabledOnCommand(\"<NAME>\")`. Use assertions derived from the redis-cli\n   showcase transcripts — real semantics, not just \"no error\", covering the whole\n   family the option touches (with/without optional args, error cases):\n   ```java\n   @Test\n   @EnabledOnCommand(\"COPY\")\n   void copy() {\n       redis.set(key, value);\n       assertThat(redis.copy(key, key + \"2\")).isTrue();\n   }\n   ```\n5. **Overloads**: the base's `@Test` methods re-run automatically under the\n   group's existing RESP2/cluster/reactive/Tx overload classes — but **only the\n   ones that exist**. Check the target group against peer groups and **create a\n   missing overload class** when it matters for the command (INCREX created\n   `StringCommandResp2IntegrationTests` because its reply differs by protocol).\n   Provide the base test at minimum; add overloads that carry real\n   risk (RESP2 when replies differ, cluster when routing matters).\n\n## Running the tests\n\nThe build pins a specific JDK to match CI — check `.github/workflows/` and the\nlocal-gotchas section of\n[.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n(pin `JAVA_HOME`, worktree `git-commit-id-plugin` skip, `TEST_WORK_FOLDER`).\n\n- Unit tests (Surefire, no server): `mvn clean test`, or `mvn -Dtest=FooUnitTests test`.\n- Integration tests (Failsafe) need the Docker env from Phase 0 and the `verify`\n  lifecycle — **`-Dit.test=` filters Failsafe, `-Dtest=` does not**:\n  ```bash\n  TEST_WORK_FOLDER=./work/docker mvn -DskipITs=false -DskipUnitTests=true \\\n    -Dit.test=FooIntegrationTests verify -Pci\n  ```\n  If the command isn't in any published `redislabs/client-libs-test` tag yet, say\n  so: the integration tests will be skipped by `@EnabledOnCommand` (expected and\n  acceptable), but they must still be written and compile.\n\n**Tear the environment down when you are done.** The Docker topology started in\nPhase 0 keeps running (and holds the test ports) until stopped. After the final\nverification run — and equally when the task is aborted or fails partway — run:\n\n```bash\nmake stop\n```\n\n## PR hygiene checklist (verify before finishing)\n\n- [ ] Every layer of the chosen matrix updated consistently; the consistency suite\n      and `CommandBuilderCoverageUnitTests` pass.\n- [ ] Every varargs parameter has its single-argument overload, and every\n      multi-key command with an `*Args` object uses `List<K>` (not varargs) with\n      fixed-arity overloads — mirrored across all flavors (or a\n      maintainer-approved justification from the plan).\n- [ ] `@since` on **every** new public element — including the nested `Builder`,\n      static factories, and fluent setters of new `*Args` classes; class-level\n      tags are not inherited (see\n      [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md)). Javadoc written on\n      the sync interface and mirrored with flavor-appropriate `@return` phrasing.\n- [ ] Builder-validated preconditions documented on all flavors: `@param`\n      constraint phrases + `@throws IllegalArgumentException if …`.\n- [ ] No dead `CommandKeyword` constants; no keyword duplicating a `CommandType`.\n- [ ] Read-only commands registered in `ReadOnlyCommands` (+ count test bumped).\n- [ ] `mvn formatter:format` run; no formatting-only noise in the diff.\n- [ ] Tests at every applicable layer, gated with `@EnabledOnCommand`; missing\n      integration overload classes created where the command needs them.\n- [ ] `docs/` (MkDocs) updated — a new command or option **is** user-facing: add\n      a one-line entry to the current-release section of `docs/new-features.md`\n      (follow its existing \"Support for [`X`](redis.io link) …\" pattern), plus\n      any feature page the change affects.\n- [ ] `.env.vX.XX` image pin bumped if the feature needed a newer server build.\n- [ ] PR description states: server PR link, HLD link, gating choice and why,\n      behavior against older servers, and includes a showcase transcript. (Draft\n      with the [draft-pr-description](../draft-pr-description/SKILL.md) skill;\n      remember the guardrail — the agent never creates the PR itself.)\n- [ ] Docker test environment stopped (`make stop`) after the final verification\n      run.\n\n## Top pitfalls\n\n1. **Skipping the live verification / not checking RESP2 vs RESP3.** The reply\n   shape can differ between protocols; it determines the `CommandOutput` and the\n   reactive mapping. Confirm against a running server, don't assume.\n2. **Adding Args/response types after the interface edits** — every flavor\n   references them; the project won't compile. Types come first.\n3. **Editing only some flavors, or silencing the consistency suite** with a\n   deviations-registry entry instead of fixing the signature.\n4. **Forgetting a dispatch layer** — both `AbstractRedisAsyncCommands` *and*\n   `AbstractRedisReactiveCommands`, plus the Kotlin `*Impl.kt`.\n5. **Recreating the removed generator source files** under\n   `src/test/java/io/lettuce/core/api/` because an old reference PR touched them.\n6. **Wrong `CommandArgs` order or `CommandOutput`**, missing `@since`, missing\n   `@EnabledOnCommand` gating, or missing the read-only registry entry.\n7. **Letting a traced precedent override a written convention** — e.g. skipping\n   the single-argument overload because `sintercard(K...)` doesn't have one, or\n   stopping at a class-level `@since` because an old `*Args` class did. Older\n   code predates the rules; the conventions win.\n",".agents/rules/java-rules.md":"# Java rules\n\nConventions for Java source that agents most often get wrong. The build does **not**\nenforce these — the Eclipse formatter (`formatting.xml`) reformats whitespace but does\nnot add missing braces or convert fully-qualified names to imports, and there is no\nCheckstyle — so apply them by hand.\n\n- **Use imports, not fully-qualified names.** Reference a type by its simple name and\n  add an `import`. Do not inline a package-qualified name in code\n  (e.g. write `RedisCommands`, not `io.lettuce.core.api.sync.RedisCommands`). The only\n  exception is disambiguating a genuine clash between two same-named types in one file.\n- **No wildcard imports** (`import foo.bar.*;`). Import each type explicitly.\n- **Always use braces on control statements.** `if`, `else`, `for`, `while`, and `do`\n  take a block even for a single-statement body — never a braceless one-liner:\n\n  ```java\n  // NO\n  if (key == null)\n      return;\n\n  // YES\n  if (key == null) {\n      return;\n  }\n  ```\n\n- **One statement per line.**\n\nRun `mvn formatter:format` before finishing — it fixes layout, but not the rules above.\n",".agents/rules/rules.md":"# Coding rules\n\nTool-neutral coding rules for this repo. Each file covers one area; read the\nrelevant rule set before editing that kind of file.\n\n| Rule set | Read before editing |\n|----------|---------------------|\n| [java-rules.md](java-rules.md) | Java source (`src/**/*.java`) |\n\nAdd a new file here as new rule areas come up, and list it in the table above.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/redis/lettuce/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Lettuce Coding Conventions\n\nEssential rules for working on the Lettuce codebase — a scalable, thread-safe\nRedis client for the JVM built on netty and Project Reactor. Detailed guidance\nfor specific tasks lives in `.agents/skills/`; consult the relevant skill when\nperforming that type of work.\n\n## How this guidance is organized\n\nAgent guidance is **self-contained under agent-owned locations** and never mixed into\nthe published documentation site:\n\n- **Agent-facing** (this material) lives at the repo root (`AGENTS.md`, `CLAUDE.md`),\n  under `.agents/` (tool-neutral: `.agents/docs/`, `.agents/rules/`, `.agents/skills/`),\n  and `.claude/` (Claude-specific tooling that bridges to `.agents/`). None of it is\n  published by MkDocs.\n- **User-facing** contributor/user documentation lives under `docs/` and is published\n  to the docs site. Agents read the agent-owned copies, not `docs/`.\n\nWithin the agent material it's a small graph: **each topic has one owner file**, and\neverything else **links** to it rather than restating it. When a topic changes, edit\nthe owner only.\n\n| Topic | Owner |\n|-------|-------|\n| Always-on facts, guardrails, index (this file) | `AGENTS.md` |\n| Command flow & code generation | `.agents/docs/architecture.md` |\n| API consistency (flavors, mapping rules, tests) | `.agents/docs/api-consistency.md` |\n| Testing — environment, running, naming, layout, CI | `.agents/docs/integration-testing.md` |\n| Javadoc conventions (incl. `@since` derivation) | `.agents/docs/javadoc.md` |\n| Coding rules (Java style, …) | `.agents/rules/*` |\n| Task procedures (add a command, write Javadoc, PR description) | `.agents/skills/*` |\n\nRule: state a fact in its owner and link from elsewhere; don't copy it. This file\nkeeps only a minimal always-on quick-reference. Skills own the *sequence* of a task\nand link out for the mechanics.\n\n## General Principles\n\n- **Update documentation.** When changes affect user-facing behavior, config, or\n  public API, update the relevant pages under `docs/` (MkDocs).\n- **Add or update tests.** Bug fixes need a reproducer test; new features need\n  tests. See `.agents/docs/integration-testing.md` for unit vs. integration conventions.\n- **You are responsible for what you submit.** Validate all changes. Do not submit\n  AI-generated code without human oversight.\n\n## Guardrails (agents)\n\nThese are hard rules for automated agents. A human maintainer performs these\nactions, not the agent.\n\n- **Never `git commit` or `git push`.** Stage and describe changes; leave\n  committing and pushing to the maintainer.\n- **Never create or merge pull requests, and never reply to GitHub issues or PRs**\n  (no `gh pr create`/`merge`/`comment`/`review`, no `gh issue create`/`comment`).\n  Draft text for the maintainer to post if asked — for a PR description, use the\n  `draft-pr-description` skill, which writes to `prDescription.md`.\n\n## Requirements & Toolchain\n\n- Builds and runs on the JDK baseline declared in `pom.xml`\n  (`maven.compiler.source`/`target`); targets Redis 2.6+.\n- **The test build pins a specific JDK to match CI** — check `.github/workflows/`\n  (or `.agents/docs/integration-testing.md`) and use that JDK to build and reproduce CI.\n- Build tool is **Maven** (`mvn`). Integration tests require **Docker**.\n- **Prefer semantic code tooling over text search for Java, when available.** If a\n  Java language server or IDE integration is present (e.g. an `jdtls`-based LSP, or\n  an IDE MCP), use it for definitions, references, symbol/type search, call\n  hierarchy, and diagnostics instead of grepping. Fall back to text search only when\n  no such tool is available.\n\n## Project Structure\n\n```\nsrc/main/java/io/lettuce/core/                Core client, connections, commands\nsrc/main/java/io/lettuce/core/cluster/        Redis Cluster support\nsrc/main/java/io/lettuce/core/masterreplica/  Master/Replica & Sentinel\nsrc/main/java/io/lettuce/core/pubsub/         Pub/Sub\nsrc/main/java/io/lettuce/core/support/        Connection pooling (ConnectionPoolSupport, BoundedAsyncPool)\nsrc/main/java/io/lettuce/core/api/            Sync/async/reactive command interfaces\nsrc/test/java/io/lettuce/                      Tests (see .agents/docs/integration-testing.md §5)\nformatting.xml                                 Eclipse formatter rules\nMakefile                                       Docker-backed test lifecycle\n```\n\nWhere to look: connect → `core/`; pooling → `core/support/`; sharding →\n`core/cluster/`; HA/Sentinel → `core/masterreplica/`.\n\n**How a command flows** through the API interfaces, command builder, codec, and\nthe netty/RESP wire layer is documented in `.agents/docs/architecture.md`. Read it\nbefore adding commands or touching the protocol layer — note the\nsync/async/reactive/Kotlin command interfaces are **hand-edited** and kept in\nlockstep by the API consistency test suite (see `.agents/docs/api-consistency.md`).\n\n## Build & Test Commands\n\n**Unit tests** (pure logic, no server — named `*UnitTests`, run by Surefire):\n\n```bash\nmvn clean test                       # build + run unit tests, no Redis needed\nmvn test -Dtest=FooUnitTests         # a single unit test\n```\n\n**Integration tests** (need a running Redis — named `*IntegrationTests`, run by\nFailsafe; **off by default**, `make test` enables them via `-DskipITs=false`):\n\n```bash\nmake start                           # boot the Dockerized Redis topology (add version=<v> to pick one)\nmake test                            # full build incl. integration tests\nmake stop                            # tear the environment down\n```\n\nRun one integration test fast (env already started):\n\n```bash\nTEST_WORK_FOLDER=./work/docker mvn -DskipITs=false -DskipUnitTests=true \\\n  -Dit.test=FooIntegrationTests verify -Pci\n```\n\nOne caveat worth remembering: `-Dit.test=` filters Failsafe (a single integration\ntest); `-DskipUnitTests=true` runs integration only. The full flag reference,\nRedis-version matrix, topologies, TLS/mTLS and CI mapping live in\n[`.agents/docs/integration-testing.md`](.agents/docs/integration-testing.md).\n\n**Apply formatting** (required before committing):\n\n```bash\nmvn formatter:format                 # applies formatting.xml; do NOT submit formatting-only diffs\n```\n\n## Testing Rules\n\n- **The file-name suffix picks the runner:** `*UnitTests` (Surefire, no server),\n  `*IntegrationTests` (Failsafe, needs Redis). Prefer those suffixes. A singular\n  `*Test` suffix is excluded by Surefire and picked up by Failsafe instead, so such a\n  class never runs in `mvn test`; it only runs in the integration phase (`make test`,\n  which is what CI runs, Docker required) or when named explicitly via `-Dtest=`. Name\n  pure unit tests `*UnitTests`.\n- Integration tests read host/port/TLS from `TestSettings` / `TlsSettings` — never\n  hard-code them.\n- Everything else about testing (topologies, TLS/mTLS, layout, CI) lives in\n  [`.agents/docs/integration-testing.md`](.agents/docs/integration-testing.md).\n\n## Coding Style Essentials\n\n- 4-space indentation, enforced by `formatter-maven-plugin` — run\n  `mvn formatter:format`. Don't submit formatting-only changes (they create noise\n  and get PRs rejected).\n- **Add `@since <version>` to all new public API** — see\n  [`.agents/docs/javadoc.md`](.agents/docs/javadoc.md) for the version-derivation recipe.\n- **`@author` is the human contributor's.** Maintainers add their own `@author` to\n  files they touch (per CONTRIBUTING). An automated agent must **not** invent an\n  `@author` for itself — leave the tag to the maintainer unless told which name to add.\n- **Java style** — imports over fully-qualified names, braces on every control\n  statement (no braceless one-liners), no wildcard imports. Full list in\n  `.agents/rules/java-rules.md` (see `.agents/rules/` before editing Java).\n- Javadoc for public API follows house conventions — see `.agents/docs/javadoc.md`, or use\n  the `writing-javadoc` skill.\n\n## Commit & PR\n\n- Open a feature-request issue first; reference its number in the PR\n  (`<text> #<ticket>`).\n- Include unit and/or integration tests for every change.\n- Split feature/bugfix commits from formatting/polishing commits.\n- Follow `.github/PULL_REQUEST_TEMPLATE.md`.\n\n## Key References\n\n| Document | What it covers |\n|----------|----------------|\n| `.github/CONTRIBUTING.md` | How to build, test, and contribute |\n| `.agents/docs/architecture.md` | How a command flows: API shapes, code generation, command model, codec, RESP/netty wire layer |\n| `.agents/docs/api-consistency.md` | The API flavors, their return-type mapping rules, and the consistency test suite that enforces them |\n| `.agents/docs/integration-testing.md` | Test environment, topologies, unit vs. integration, CI |\n| `.agents/docs/javadoc.md` | Javadoc house conventions for public API |\n| `.github/PULL_REQUEST_TEMPLATE.md` | PR checklist |\n| `formatting.xml` | Eclipse code-formatting rules |\n| [Javadoc](https://redis.github.io/lettuce/overview/) | Public API reference |\n\n## On-Demand Skills\n\nDetailed task guidance lives in `.agents/skills/`. Consult the relevant skill\nbefore doing that type of work.\n\n| Skill | When to use |\n|-------|-------------|\n| `extend-commands-api` | Add or extend a Redis command (or overload) end-to-end, evidence-first: HLD + server PR + live redis-cli verification, plan-mode approval, then all API interface flavors, builder, async/reactive/Kotlin impls, and tests |\n| `create-implementation-plan-for-redis-api-change` | Turn a shared client HLD into Lettuce's reviewed implementation plan (API, files per flavor, ordered steps, test matrix) without touching sources; read-only, writes one plan file; conventions come from `extend-commands-api` |\n| `writing-javadoc` | Write or fix Javadoc for public API following Lettuce house conventions |\n| `draft-pr-description` | Generate a GitHub PR title and description from the diff between two local branches |\n","isInternal":false,"tokens":2369,"sizeBytes":9900},{"name":"java-rules.md","path":".agents/rules/java-rules.md","rawUrl":"https://raw.githubusercontent.com/redis/lettuce/HEAD/.agents/rules/java-rules.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Java rules\n\nConventions for Java source that agents most often get wrong. The build does **not**\nenforce these — the Eclipse formatter (`formatting.xml`) reformats whitespace but does\nnot add missing braces or convert fully-qualified names to imports, and there is no\nCheckstyle — so apply them by hand.\n\n- **Use imports, not fully-qualified names.** Reference a type by its simple name and\n  add an `import`. Do not inline a package-qualified name in code\n  (e.g. write `RedisCommands`, not `io.lettuce.core.api.sync.RedisCommands`). The only\n  exception is disambiguating a genuine clash between two same-named types in one file.\n- **No wildcard imports** (`import foo.bar.*;`). Import each type explicitly.\n- **Always use braces on control statements.** `if`, `else`, `for`, `while`, and `do`\n  take a block even for a single-statement body — never a braceless one-liner:\n\n  ```java\n  // NO\n  if (key == null)\n      return;\n\n  // YES\n  if (key == null) {\n      return;\n  }\n  ```\n\n- **One statement per line.**\n\nRun `mvn formatter:format` before finishing — it fixes layout, but not the rules above.\n","isInternal":false,"tokens":281,"sizeBytes":1112},{"name":"rules.md","path":".agents/rules/rules.md","rawUrl":"https://raw.githubusercontent.com/redis/lettuce/HEAD/.agents/rules/rules.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Coding rules\n\nTool-neutral coding rules for this repo. Each file covers one area; read the\nrelevant rule set before editing that kind of file.\n\n| Rule set | Read before editing |\n|----------|---------------------|\n| [java-rules.md](java-rules.md) | Java source (`src/**/*.java`) |\n\nAdd a new file here as new rule areas come up, and list it in the table above.\n","isInternal":false,"tokens":86,"sizeBytes":363},{"name":"SKILL.md","path":".agents/skills/draft-pr-description/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/lettuce/HEAD/.agents/skills/draft-pr-description/SKILL.md","title":"draft-pr-description","category":"anthropic-skill","format":"markdown","content":"---\nname: draft-pr-description\ndescription: Generate a clear, concise GitHub PR title and description from the diff between two local git branches, and save it to prDescription.md in the repo root. Use this whenever the user asks to write, generate, draft, or update a PR description or PR title from local branch changes — including phrasing like \"summarize this diff into a PR description,\" \"write a PR description for my current branch,\" \"create a PR title and description,\" or any request to compare a base and target branch for PR purposes. Trigger even if the user doesn't name specific branches; this skill knows how to default them.\nargument-hint: [base-branch] [target-branch]\narguments: [base, target]\nallowed-tools: Bash(git diff *) Bash(git log *) Bash(git branch *)\n---\n\n# PR Description Generator\n\nWrites a GitHub PR title and description from the diff between two local git branches, focused on what reviewers need: purpose, key decisions/assumptions, behavioral or conceptual changes, and a brief testing note if relevant. Uses only standard git commands, so any agent can follow it.\n\n## Arguments\n\nTwo optional positional arguments — base branch, then target branch:\n\n- Base: `$base`\n- Target: `$target`\n\nAn empty value means \"not provided\" — default it per the resolution below (a missing argument is defaulted, not guessed). A target cannot be given without a base.\n\n## 1. Resolve branches\n\n- Both named → use as given.\n- Only one named → it's the **base**; target defaults to current branch.\n- None named → base defaults to `main`, then `master` if `main` doesn't exist (`git branch --list`). **Only check these two names — no scanning other branches, no guessing.** If neither exists, stop and ask. Target defaults to current branch (`git branch --show-current`).\n\nState the resolved base/target before proceeding.\n\n## 2. Diff\n\n- Confirm both branches exist (`git branch --list <name>`); stop if not.\n- `git diff <base>...<target>` and `git log <base>..<target> --oneline`\n- Empty diff → stop and say so, don't fabricate a description.\n\n## 3. Analyze\n\nFrom the diff/log, identify: purpose of the change; key decisions and trade-offs; assumptions the code relies on; behavioral changes (concrete: \"X now rejects Y\" not \"changed validation\"); conceptual/contract changes; breaking changes, new config/env vars, migrations; test changes (what new tests cover conceptually, why existing ones were adapted — not test-by-test).\n\nSkip routine stuff (formatting, trivial renames). No file/line enumeration — write like explaining to a teammate, not a changelog.\n\n## 4. Output structure\n\nWrite to `prDescription.md` (repo root, overwrite if exists):\n\n```markdown\n# <Title — imperative, <70 chars>\n\n## Summary\n<2-3 sentences>\n\n## Key Decisions & Assumptions\n<bullets — omit if none>\n\n## Behavioral / Conceptual Changes\n<bullets, concrete — omit if none>\n\n## Testing\n<2-3 sentences, only if diff touches tests — omit otherwise, don't write \"no tests\">\n\n## Notes\n<breaking changes, follow-ups — omit if none>\n```\n\nKeep it brief (readable in under a minute, ~150-200 words total unless the change is genuinely large). Omit empty sections rather than padding them. Never fabricate testing details, tickets, or names not evidenced in the diff — flag uncertainty in Notes instead.\n\n## 5. Confirm\n\nAfter writing, give a short confirmation (don't reprint the file) and offer to run `gh pr create --title \"...\" --body-file prDescription.md` if `gh` is available. Include the provided/resolved base and target branch names into the suggested command as part of the suggestion. At the end, ask if the user wants to open the PR now and wait for confirmation before proceeding. If the user declines, stop and say the PR description is ready in `prDescription.md`.\n","isInternal":false,"tokens":858,"sizeBytes":3794},{"name":"SKILL.md","path":".agents/skills/extend-commands-api/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/lettuce/HEAD/.agents/skills/extend-commands-api/SKILL.md","title":"extend-commands-api","category":"anthropic-skill","format":"markdown","content":"---\nname: extend-commands-api\ndescription: Add or extend Redis commands in the Lettuce client API end-to-end — a new core command, a family of new commands, an extension to an existing command's options, or a module/area command (Search/JSON/Bloom/VectorSet). Gathers evidence first (HLD document, the server-side PR in the repo owning the command family, live verification against the Dockerized test environment with redis-cli), plans the full implementation matrix in plan mode, then implements across all API flavors with unit and integration tests. Trigger on \"add support for the <X> command\", \"implement <REDIS COMMAND> in Lettuce\", \"extend <command> with <option>\", or adding a new argument/overload to an existing command.\nallowed-tools: Bash(mvn *), Bash(make *), Bash(redis-cli *), Bash(gh *)\n---\n\n# Extend the Lettuce Commands API\n\nImplement a new Redis command (or extend an existing one) in Lettuce, following the\nconventions maintainers enforce in review. The workflow is evidence-first: prove the\ncommand's behavior on a real server before designing the Java API.\n\nRead [.agents/docs/architecture.md](../../../.agents/docs/architecture.md) first for\nthe model behind this flow — especially that the sync/async/reactive/Kotlin command\n**interfaces are hand-edited source files kept in lockstep by the API consistency\ntest suite** (see [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md)).\n\n> **Historical caveat when reading old PRs**: PRs merged before the generator removal\n> also edit `src/test/java/io/lettuce/core/api/<Group>Commands.java` — the old\n> template source files. Those files **no longer exist**; do not recreate them. The\n> flavor interfaces are now edited directly.\n\n## Phase 0 — Gather evidence BEFORE planning\n\nDo all of the following before writing any plan or code:\n\n1. **Ask the user for the HLD.** Interactively ask for the path to a markdown file\n   containing the High-Level Design for the command(s) (or confirm none exists). If\n   a path is given, read it fully — it is the primary source for syntax, semantics,\n   reply shape per RESP2/RESP3, and edge cases.\n\n2. **Find the server-side PR in the repo that owns the command.** Route the\n   search by command family — module command families are developed in their\n   owning repositories, not in `redis/redis`:\n\n   | Command family | Repository | Syntax source |\n   |----------------|------------|---------------|\n   | Core commands, vector sets (`VADD`, …) | `redis/redis` | `src/commands/*.json` |\n   | Search (`FT.*`) | `RediSearch/RediSearch` | PR diff + command docs (no `src/commands/*.json`) |\n   | JSON (`JSON.*`) | `RedisJSON/RedisJSON` | PR diff + command docs |\n   | Probabilistic (`BF.*`, `CF.*`, `CMS.*`, `TOPK.*`, `TDIGEST.*`) | `RedisBloom/RedisBloom` | PR diff + command docs |\n   | Time series (`TS.*`) | `RedisTimeSeries/RedisTimeSeries` | PR diff + command docs |\n\n   If the search comes up empty in the routed repo, fall back to `redis/redis`\n   (and vice versa) before concluding there is no server PR.\n\n   First verify that `gh` works in the current (sandboxed) environment:\n   ```bash\n   gh auth status\n   ```\n   Sandboxes often block access to credential files, so `gh` may report\n   unauthenticated here even though it works on the user's machine. If so, ask the\n   user for permission to run these specific read-only `gh` commands outside the\n   sandbox; only fall back to the unauthenticated GitHub REST API if they decline.\n\n   Then search for the PR that adds/extends the command on the server:\n   ```bash\n   gh search prs --repo <owning-repo> \"<COMMAND NAME>\" --limit 10\n   gh pr view <num> --repo <owning-repo>\n   gh pr diff <num> --repo <owning-repo>  # in redis/redis: src/commands/*.json has exact syntax\n   ```\n   Extract: exact wire syntax (argument order and optionality), reply type per\n   **RESP2 and RESP3** (they can differ — this determines the `CommandOutput` and\n   the reactive `Mono`/`Flux` mapping), error conditions, and the **first server\n   version carrying the feature** (drives `@EnabledOnCommand` gating and the test\n   env version). Note whether the server marks the feature *experimental/preview*.\n\n3. **Verify the command exists on the \"next\" Redis OSS version.** Start the\n   integration environment using the **highest** version the Makefile supports\n   (check `SUPPORTED_TEST_ENV_VERSIONS` in the `Makefile`, or the\n   `src/test/resources/docker-env/.env.vX.XX` files — pick numerically, `8.10` >\n   `8.8`):\n   ```bash\n   grep SUPPORTED_TEST_ENV_VERSIONS Makefile\n   make start version=8.10\n   ```\n   Then probe the running servers. Test connection defaults live in\n   `src/test/java/io/lettuce/test/settings/TestSettings.java` — the main standalone\n   node is `localhost:6479` (no auth); module commands (`FT.*`, `JSON.*`, `BF.*`,\n   `VADD`, …) run on the stack node at `localhost:16379`:\n   ```bash\n   redis-cli -p 6479 INFO server | grep redis_version\n   redis-cli -p 6479 COMMAND INFO <COMMAND>   # non-empty → command exists\n   redis-cli -p 6479 COMMAND DOCS <COMMAND>   # arity/args — compare with the server PR\n   ```\n   For a NEW command, `COMMAND INFO` must return a non-empty reply. For an EXTENDED\n   command, additionally invoke it with the new syntax against a scratch key and\n   confirm the server accepts it (no `ERR syntax error` / `ERR unknown argument`).\n\n   **If the command/option is missing on the latest env version**, the image tag\n   pinned in the `.env.vX.XX` file is too old. Ask the user for a\n   `redislabs/client-libs-test` image tag that contains it (e.g. an RC/edge build —\n   tags are listed at https://hub.docker.com/r/redislabs/client-libs-test/tags; you\n   may check and suggest candidates), then:\n   ```bash\n   make stop\n   CLIENT_LIBS_TEST_IMAGE_TAG=<tag> make start\n   ```\n   If the PR should also move CI to that build, update the newest `.env.vX.XX`\n   pin as part of the change — command PRs do this when they need a fresh server\n   (cf. the hotkeys PR bumping `.env.v8.6`). If no tag carries the feature, report\n   it and proceed: implementation continues, integration tests stay gated until an\n   image ships the change. Keep the environment running for later test runs.\n\n4. **Create redis-cli showcase test cases.** Once the command is available, derive\n   a small set of redis-cli scenarios from the HLD and the server PR and run them.\n   These serve two purposes:\n   - **Smoke test**: prove the documented behavior holds — happy path, each new\n     option/keyword, reply shape (run with `redis-cli -3` too when RESP3 replies\n     differ), edge cases and error conditions (missing key, out-of-range args,\n     conflicting options) — so the Java implementation is built against observed\n     replies, not assumptions.\n   - **Showcase**: each scenario should read as a mini use-case explaining WHY the\n     command/option exists. Prefer realistic data over `foo`/`bar`.\n\n   Save the scenarios as a commented script in a scratch file (one block per\n   use-case: a \"what this demonstrates\" comment, the commands, the observed reply\n   pasted back as comments). Carry this forward: it feeds the Phase 1 plan, the\n   test assertions, and the PR description. If the command could not be made\n   available on any image, still write the scenarios as *expected* transcripts and\n   mark them unverified.\n\n5. **Read the testing and consistency docs**:\n   [.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n   (environment, `*UnitTests` vs `*IntegrationTests` naming, base/overload test\n   structure, running one test) and\n   [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md) (the\n   flavors and their return-type mapping rules).\n\n6. **Trace one analogous existing command** end-to-end (same command group, similar\n   reply shape) so the plan mirrors real code, not guesswork. Good reference PRs,\n   validated against git history:\n\n   | Commit | What it exemplifies |\n   |--------|---------------------|\n   | `7ca3e9cc0` (CLIENT NO-TOUCH #3776) | Minimal new command: all 6 flavors + builder + 2 dispatch layers + Kotlin impl + builder unit test + integration test |\n   | `312ecc7b4` (INCREX #3746) | New command with args: self-typed abstract args base, new value type, new `CommandOutput`s, per-type args/output unit tests, **creating a missing RESP2 overload class** |\n   | `b009df37b` (XNACK #3728) | New command with an enum-valued argument |\n   | `9a0899875` (BITOP extensions #3334) | New operations/overloads on an existing command |\n   | `c603c8120` (HSCAN NOVALUES #2816) | New overload rippling into helpers (`ScanIterator`, `ScanStream`, `ScanFlow`) |\n   | `aa7b4b0be` (stream idempotency #3637) | Option added to an existing `*Args` class flows through with **no interface change** (the `XAddArgs` part) |\n   | `a209fba70` (CAS/CAD #3512) | Read-only command registered in `ReadOnlyCommands` + count test update |\n   | `838a4d39a` (HOTKEYS #3638) | Map-shaped reply via `ComplexOutput` + a `*ReplyParser`, cluster interface additions, full integration overload set, `.env` image bump |\n   | `6567f2d5e` (RediSearch #3375) | Whole new command area: own `Redis<Area>CommandBuilder`, `arguments`/reply-parser packages |\n\n   **Conventions beat precedent.** Traced reference commands can predate the\n   current conventions; when the reference code conflicts with a written rule in\n   this skill or the linked docs, the rule wins. Example: `sintercard(K...)`\n   ships without a single-key overload because it predates the\n   one-overload-per-varargs rule — a new command mirroring it must still add the\n   single-argument overload.\n\n7. **Determine the `@since` version** — recipe owned by\n   [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md):\n   ```bash\n   mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n   ```\n   Drop `-SNAPSHOT` and the patch digit (`7.7.0-SNAPSHOT` → `@since 7.7`).\n\n**The evidence is your working context, not a deliverable.** Hold it in mind to\ndrive the work — do not paste it back to the maintainer or pause for \"spec\napproval.\" The only stop for the maintainer is the Phase 1 plan approval; surface\ngenuinely ambiguous design choices *there*, with the proposed sync signatures,\nrather than interrupting earlier.\n\n## Phase 1 — Plan mode, then explicit approval\n\nEnter **plan mode**. Using the evidence, classify the change with the decision tree\nbelow and enumerate the exact file-by-file touch list, the test matrix, and the\ngating annotations. The plan must contain:\n\n- A short \"what this feature enables\" section built from the showcase scenarios\n  (Phase 0 step 4), including one or two representative command/reply transcripts.\n- **The proposed sync interface signature(s) with their Javadoc.** The sync\n  interface is the human-authored **API contract**; every other flavor is derived\n  from it and it is costly to change once mirrored. Plan approval is the\n  maintainer's sign-off on that contract — if the signatures must deviate later\n  during implementation, stop and re-confirm before mirroring.\n- **The complete overload set, enumerated.** For every varargs parameter, list\n  the matching single-argument overload; for a multi-key command with an `*Args`\n  object, use the `List<K>` shape (no varargs) and list the fixed-arity\n  convenience overloads instead — both house rules live in \"Types & args\n  conventions\". Justify any overload you omit so the maintainer signs off on the\n  exception. An overload discovered missing in review means reworking all six\n  flavors.\n\nPresent the plan and **explicitly ask permission to execute** before implementing.\nDo not start editing files until the user approves.\n\nOnce approved, copy this checklist into your working notes and tick items off:\n\n```\nExtend-commands progress:\n- [ ] 0. Evidence: HLD, server PR, live probe + showcase, RESP2/RESP3 replies, @since\n- [ ] 1. Plan approved — incl. the sync signature(s), the API contract\n- [ ] 2. Types: argument & response types (they must exist before any interface edit)\n- [ ] 3. Sync interface: full overload set (varargs → single-arg too) + Javadoc\n       (@param constraints + @throws for builder-validated preconditions)\n- [ ] 4. Mirror: async, reactive, Kotlin, NodeSelection×2 — consistency tests pass\n- [ ] 5. Implementations: CommandType/Keyword, builder, async, reactive, Kotlin impl\n- [ ] 6. Tests: args/builder/output unit tests + integration base/overloads\n- [ ] 7. Docs: entry in the current-release section of docs/new-features.md\n- [ ] 8. Verify: mvn clean test + a single integration test run; then make stop\n```\n\n## Decision tree — what kind of change is this?\n\n**A. Extension of an existing command that fits an existing `*Args` class**\n(new option token / new field):\n- Touch ONLY the `*Args` class (+ `CommandKeyword` for new tokens) + tests. Do NOT\n  touch the command interfaces, builder signatures, or dispatch layers — the\n  existing `args.build(commandArgs)` delegation carries the new option through\n  automatically (cf. the `XAddArgs` part of the stream-idempotency PR).\n- Add a fluent setter returning `this`; register new tokens in\n  `src/main/java/io/lettuce/core/protocol/CommandKeyword.java`.\n- If the reply shape grows, extend the response model/output backward-compatibly.\n\n**B. New command(s) in an existing group — core or module area** — the FULL\nmatrix, in this order (types first — every flavor references them, so they must\nexist to compile). For a command joining an **existing module area** (a new\n`FT.*` method in the Search group, a new `JSON.*` method, …) the same matrix\napplies with the area substitutions: the group is the area's flavor interfaces,\nthe builder is the area's `Redis<Area>CommandBuilder` (+ its\n`Redis<Area>CommandBuilderUnitTests`), argument/reply types go in the area\npackage, and gating/tests follow the module rules in D (capability probe, stack\nnode). The dispatch layers are the same `AbstractRedisAsyncCommands` /\n`AbstractRedisReactiveCommands` and Kotlin `*Impl.kt` as for core commands.\n1. **Argument/response types** — see \"Types & args conventions\" below.\n2. **Sync interface** `src/main/java/io/lettuce/core/api/sync/<Group>Commands.java`\n   — pick the group by command family (STRING → `RedisStringCommands`, HASH →\n   `RedisHashCommands`, generic-key → `RedisKeyCommands`, …). This is the reference\n   signature + Javadoc all flavors mirror (see the\n   [writing-javadoc](../writing-javadoc/SKILL.md) skill; `@since` mandatory):\n   ```java\n   /**\n    * Returns the length of the string value stored at {@code key}.\n    *\n    * @param key the key, must not be {@code null}.\n    * @return the length of the string at {@code key}, or {@code 0} when {@code key} does\n    *         not exist.\n    * @since 7.7\n    */\n   Long strlen(K key);\n   ```\n   Three contract rules to apply while designing the signatures and their Javadoc:\n   - **Multi-key commands that also take an `*Args` object take the keys as\n     `List<K>`, not varargs.** A varargs parameter must come last, so a trailing\n     options object can only follow it via an awkward leading placement (cf. the\n     older `sintercard(long limit, K... keys)`). For new commands, take the keys\n     as `List<K>` so the `*Args` argument comes last —\n     `sunioncard(List<K> keys, SUnionCardArgs args)` — and add fixed-arity\n     convenience overloads (`(K key1, K key2)` and their `*Args` variants)\n     instead of a varargs form. Since there is no varargs parameter here, the\n     single-argument-overload rule below does not apply; add only the overloads\n     that are meaningful — e.g. **no single-key `sunioncard`/`sdiffcard`**, whose\n     one-key union/difference is just `SCARD`.\n   - **Every varargs parameter gets a single-argument overload** —\n     `foo(K key, V value)` alongside `foo(K key, V... values)`. This is a hard\n     convention for new commands and overrides any traced precedent that lacks\n     it (older commands predate the rule). Does not apply to the `List<K>`-shaped\n     multi-key-with-`*Args` commands above, which take no varargs.\n   - **Validated preconditions are contract.** Any constraint the builder will\n     enforce (null checks, non-empty varargs) must be stated in the `@param`\n     text with the house phrases (`must not be {@code null}.`,\n     `must not be empty.`) *and* documented with a matching\n     `@throws IllegalArgumentException if …` tag — forms owned by\n     [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md). Both are then\n     mirrored to every flavor.\n3. **Mirror to every flavor**: async, reactive, Kotlin coroutines, and the two\n   cluster node-selection interfaces (`NodeSelection<Group>Commands` /\n   `NodeSelection<Group>AsyncCommands`), applying the per-flavor return-type\n   mapping rules from\n   [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md).\n4. **Protocol enums** — command name in\n   `src/main/java/io/lettuce/core/protocol/CommandType.java` (wire bytes derive\n   from the enum name); subcommand tokens in `CommandKeyword`. **Never add a\n   `CommandKeyword` that duplicates a name already in `CommandType`** (e.g. `SET`,\n   `DISCARD`) — the builder static-imports both enums and the bare name becomes\n   ambiguous; reference `CommandType.<NAME>` directly instead.\n5. **Builder** — `RedisCommandBuilder.java` (or the area builder, see D): null\n   checks via `LettuceAssert`/`notNullKey`, `CommandArgs` in **wire order**, the\n   `CommandOutput` chosen from the observed RESP2/RESP3 replies:\n   ```java\n   public Command<K, V, Long> strlen(K key) {\n       notNullKey(key);\n       return createCommand(STRLEN, new IntegerOutput<>(codec), key);\n   }\n\n   public Command<K, V, Boolean> copy(K source, K destination, CopyArgs copyArgs) {\n       LettuceAssert.notNull(source, \"Source \" + MUST_NOT_BE_NULL);\n       LettuceAssert.notNull(destination, \"Destination \" + MUST_NOT_BE_NULL);\n       CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(source).addKey(destination);\n       copyArgs.build(args);\n       return createCommand(COPY, new BooleanOutput<>(codec), args);\n   }\n   ```\n   Every `LettuceAssert` precondition written here is public contract: if the\n   interface Javadoc (step 2) does not already state the constraint and its\n   `@throws IllegalArgumentException`, go back and add it — on every flavor.\n6. **Dispatch layers** — one-liners in *both* `AbstractRedisAsyncCommands` *and*\n   `AbstractRedisReactiveCommands` (`createMono` for scalars,\n   `createDissolvingFlux` for `List`/`Set` replies, matching the interface's\n   `Mono`/`Flux`):\n   ```java\n   // AbstractRedisAsyncCommands\n   public RedisFuture<Long> strlen(K key) { return dispatch(commandBuilder.strlen(key)); }\n   // AbstractRedisReactiveCommands\n   public Mono<Long> strlen(K key) { return createMono(() -> commandBuilder.strlen(key)); }\n   ```\n7. **Kotlin impl** —\n   `src/main/kotlin/io/lettuce/core/api/coroutines/<Group>CoroutinesCommandsImpl.kt`:\n   ```kotlin\n   override suspend fun strlen(key: K): Long = ops.strlen(key).awaitSingle()\n   ```\n8. **Cluster** — pick the routing shape deliberately; there are three cases:\n   - A **single-key** command flows through automatically (routed by slot).\n   - A **broadcast/all-shards** command (its answer is the aggregate over the\n     whole cluster — cf. `dbsize`, `flushall`) needs hand-coded fan-out\n     overrides in `RedisAdvancedClusterAsyncCommandsImpl` *and* its reactive\n     sibling (`executeOnUpstream` + a `MultiNodeExecution` aggregator), and may\n     need methods on the cluster aggregate interfaces.\n   - A **node-specific** command (keyless, but its result is only meaningful per\n     node — cf. HOTKEYS) must **not** be fanned out: add `default` overrides on\n     the cluster aggregate interfaces that throw `UnsupportedOperationException`\n     and direct callers to the node-selection API or `getConnection(nodeId)`\n     (cf. `RedisClusterCommands.hotkeysReset()`); only the node-selection\n     flavors execute it.\n\n   See \"Cluster routing\" in\n   [.agents/docs/architecture.md](../../../.agents/docs/architecture.md).\n9. **Read-only command?** Register it in\n   `src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java` (`CommandName`\n   enum) so replica-read routing knows, and bump the size assertion in\n   `ClusterReadOnlyCommandsUnitTests`.\n\n**C. Extension needing new overloads / a new `*Args` class** (hybrid): the new\nmethods go through the full matrix of B; the option plumbing follows A. Check\nwhether command helpers must follow (`ScanIterator`/`ScanStream`/`ScanFlow` for\nscan-family commands).\n\n**D. New command group / module area** (Search/JSON/Bloom/VectorSet-style):\n- **Unlike Jedis, Lettuce module areas are full citizens**: every group gets all\n  six flavors — sync, async, reactive, Kotlin coroutines, and both node-selection\n  interfaces — plus Kotlin impls.\n- Create the flavor interfaces by mirroring an existing area end-to-end, register\n  the group in the `CommandInterfaces` enum\n  (`src/test/java/io/lettuce/core/api/consistency/`), and wire the group into the\n  hand-written aggregate interfaces (`RedisCommands`, `RedisAsyncCommands`,\n  `RedisReactiveCommands`, and the cluster variants) so they `extend` it — the\n  consistency tests enforce the aggregate wiring.\n- Areas get their **own builder** (`Redis<Area>CommandBuilder`, cf.\n  `RediSearchCommandBuilder`) with a matching\n  `Redis<Area>CommandBuilderUnitTests`, and keep their argument types and reply\n  parsers in an area package (e.g. `core/search/arguments/`).\n- Module commands still gate on server capability, not version:\n  `@EnabledOnCommand(\"FT.CREATE\")`-style probes; integration tests target the\n  stack node.\n\n## Types & args conventions\n\n- **Argument types**: an options object is a `*Args implements CompositeArgument`\n  class (e.g. `io.lettuce.core.CopyArgs`) whose `build(CommandArgs)` appends its\n  tokens. Fluent setters return `this`. If two overloads share options but differ\n  in a typed field (long vs double), use a self-typed abstract base\n  (`BaseFooArgs<T extends BaseFooArgs<T>>`) with concrete subclasses — cf.\n  `BaseIncrexArgs`/`IncrexArgs`/`IncrexFloatArgs`.\n- **`@since` goes on every new public element, not just the class.** A\n  class-level `@since` is **not inherited**: the nested `Builder` type, each of\n  its static factory methods, and each public fluent setter needs its own\n  `@since` tag, or the generated API docs lose the release provenance for those\n  members.\n- Token-valued argument enums are plain enums whose values the builder/args class\n  appends (cf. `XNackMode`).\n- **Response types**: reuse existing models where possible — `Value`, `KeyValue`,\n  `ScoredValue`, `GeoCoordinates`, `GeoWithin`, `StreamMessage`, `KeyScanCursor`\n  (all in `io.lettuce.core`) — and add a new one only when the reply genuinely\n  doesn't map. For a map-shaped/structured reply, pair a model class with a\n  `ComplexDataParser` consumed via `ComplexOutput` (cf. `HotkeysReply` +\n  `HotkeysReplyParser`).\n- **Return-type idioms** (established conventions): `1/0` integer reply →\n  `Boolean` (cf. `copy`, `expire`, `hsetnx`); count → `Long`; status → `String`;\n  bulk value → `V`. And the overload rules from step B.2: every varargs\n  parameter also gets a single-argument overload, while a multi-key command with\n  an `*Args` object takes `List<K>` (not varargs) plus fixed-arity overloads.\n- The `CommandOutput` (the reply *parser*) is chosen at the builder step from the\n  **observed** RESP2/RESP3 replies of Phase 0 — if none fits, add one under\n  `io.lettuce.core.output` with a unit test (cf. `IncrexLongOutput`).\n\n## The consistency suite is the safety net\n\nAfter mirroring, run:\n\n```bash\nmvn -Dtest='*ConsistencyUnitTests,CommandBuilderCoverageUnitTests' \\\n    -Dsurefire.failIfNoSpecifiedTests=false test\n```\n\nIt names exactly the flavor/signature you missed. For a genuinely unusual return\ntype (e.g. `Flux<Value<Long>>`, or `Mono<List<Double>>` because Redis returns\nnulls), register it in the registry that owns the flavor —\n`src/test/java/io/lettuce/core/api/consistency/KnownApiDeviations.java` for the\nJava flavors, `src/test/kotlin/io/lettuce/core/api/consistency/KnownKotlinApiDeviations.kt`\nfor the coroutine flavor — **with a comment justifying it**. Never use a\ndeviation entry to paper over a sync/async signature mismatch — that breaks the\nsync-over-async runtime proxy.\n\n**Format before you build**: run `mvn formatter:format` after hand-editing — the\nbuild's `formatter:validate` step fails the compile on unformatted code. Do not\nsubmit formatting-only diffs.\n\n## Test matrix — what to write\n\nNaming, placement, and the base/overload structure are owned by\n[.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n— follow it. The established per-command layers (write all that apply):\n\n1. **Args unit tests** (`*Args` classes): assert the exact encoded tokens and\n   wire order, setter validation, and overload equivalence — e.g.\n   `IncrexArgsUnitTests`, `XAddArgsUnitTests`. No server needed.\n2. **Builder unit test**: assert the constructed command and encoded args —\n   including the RESP2/RESP3 output shape observed in Phase 0. Core commands go\n   in `src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java`; area\n   commands in their `Redis<Area>CommandBuilderUnitTests`.\n3. **Output unit tests** when a new `CommandOutput` was added (cf.\n   `IncrexOutputUnitTests`).\n4. **Integration tests**: add methods to the sync base class\n   (`<Group>CommandIntegrationTests`), gated per-test with\n   `@EnabledOnCommand(\"<NAME>\")`. Use assertions derived from the redis-cli\n   showcase transcripts — real semantics, not just \"no error\", covering the whole\n   family the option touches (with/without optional args, error cases):\n   ```java\n   @Test\n   @EnabledOnCommand(\"COPY\")\n   void copy() {\n       redis.set(key, value);\n       assertThat(redis.copy(key, key + \"2\")).isTrue();\n   }\n   ```\n5. **Overloads**: the base's `@Test` methods re-run automatically under the\n   group's existing RESP2/cluster/reactive/Tx overload classes — but **only the\n   ones that exist**. Check the target group against peer groups and **create a\n   missing overload class** when it matters for the command (INCREX created\n   `StringCommandResp2IntegrationTests` because its reply differs by protocol).\n   Provide the base test at minimum; add overloads that carry real\n   risk (RESP2 when replies differ, cluster when routing matters).\n\n## Running the tests\n\nThe build pins a specific JDK to match CI — check `.github/workflows/` and the\nlocal-gotchas section of\n[.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n(pin `JAVA_HOME`, worktree `git-commit-id-plugin` skip, `TEST_WORK_FOLDER`).\n\n- Unit tests (Surefire, no server): `mvn clean test`, or `mvn -Dtest=FooUnitTests test`.\n- Integration tests (Failsafe) need the Docker env from Phase 0 and the `verify`\n  lifecycle — **`-Dit.test=` filters Failsafe, `-Dtest=` does not**:\n  ```bash\n  TEST_WORK_FOLDER=./work/docker mvn -DskipITs=false -DskipUnitTests=true \\\n    -Dit.test=FooIntegrationTests verify -Pci\n  ```\n  If the command isn't in any published `redislabs/client-libs-test` tag yet, say\n  so: the integration tests will be skipped by `@EnabledOnCommand` (expected and\n  acceptable), but they must still be written and compile.\n\n**Tear the environment down when you are done.** The Docker topology started in\nPhase 0 keeps running (and holds the test ports) until stopped. After the final\nverification run — and equally when the task is aborted or fails partway — run:\n\n```bash\nmake stop\n```\n\n## PR hygiene checklist (verify before finishing)\n\n- [ ] Every layer of the chosen matrix updated consistently; the consistency suite\n      and `CommandBuilderCoverageUnitTests` pass.\n- [ ] Every varargs parameter has its single-argument overload, and every\n      multi-key command with an `*Args` object uses `List<K>` (not varargs) with\n      fixed-arity overloads — mirrored across all flavors (or a\n      maintainer-approved justification from the plan).\n- [ ] `@since` on **every** new public element — including the nested `Builder`,\n      static factories, and fluent setters of new `*Args` classes; class-level\n      tags are not inherited (see\n      [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md)). Javadoc written on\n      the sync interface and mirrored with flavor-appropriate `@return` phrasing.\n- [ ] Builder-validated preconditions documented on all flavors: `@param`\n      constraint phrases + `@throws IllegalArgumentException if …`.\n- [ ] No dead `CommandKeyword` constants; no keyword duplicating a `CommandType`.\n- [ ] Read-only commands registered in `ReadOnlyCommands` (+ count test bumped).\n- [ ] `mvn formatter:format` run; no formatting-only noise in the diff.\n- [ ] Tests at every applicable layer, gated with `@EnabledOnCommand`; missing\n      integration overload classes created where the command needs them.\n- [ ] `docs/` (MkDocs) updated — a new command or option **is** user-facing: add\n      a one-line entry to the current-release section of `docs/new-features.md`\n      (follow its existing \"Support for [`X`](redis.io link) …\" pattern), plus\n      any feature page the change affects.\n- [ ] `.env.vX.XX` image pin bumped if the feature needed a newer server build.\n- [ ] PR description states: server PR link, HLD link, gating choice and why,\n      behavior against older servers, and includes a showcase transcript. (Draft\n      with the [draft-pr-description](../draft-pr-description/SKILL.md) skill;\n      remember the guardrail — the agent never creates the PR itself.)\n- [ ] Docker test environment stopped (`make stop`) after the final verification\n      run.\n\n## Top pitfalls\n\n1. **Skipping the live verification / not checking RESP2 vs RESP3.** The reply\n   shape can differ between protocols; it determines the `CommandOutput` and the\n   reactive mapping. Confirm against a running server, don't assume.\n2. **Adding Args/response types after the interface edits** — every flavor\n   references them; the project won't compile. Types come first.\n3. **Editing only some flavors, or silencing the consistency suite** with a\n   deviations-registry entry instead of fixing the signature.\n4. **Forgetting a dispatch layer** — both `AbstractRedisAsyncCommands` *and*\n   `AbstractRedisReactiveCommands`, plus the Kotlin `*Impl.kt`.\n5. **Recreating the removed generator source files** under\n   `src/test/java/io/lettuce/core/api/` because an old reference PR touched them.\n6. **Wrong `CommandArgs` order or `CommandOutput`**, missing `@since`, missing\n   `@EnabledOnCommand` gating, or missing the read-only registry entry.\n7. **Letting a traced precedent override a written convention** — e.g. skipping\n   the single-argument overload because `sintercard(K...)` doesn't have one, or\n   stopping at a class-level `@since` because an old `*Args` class did. Older\n   code predates the rules; the conventions win.\n","frontmatter":{"name":"extend-commands-api","description":"Add or extend Redis commands in the Lettuce client API end-to-end — a new core command, a family of new commands, an extension to an existing command's options, or a module/area command (Search/JSON/Bloom/VectorSet). Gathers evidence first (HLD document, the server-side PR in the repo owning the command family, live verification against the Dockerized test environment with redis-cli), plans the full implementation matrix in plan mode, then implements across all API flavors with unit and integration tests. Trigger on \"add support for the <X> command\", \"implement <REDIS COMMAND> in Lettuce\", \"extend <command> with <option>\", or adding a new argument/overload to an existing command.","allowed-tools":"Bash(mvn *), Bash(make *), Bash(redis-cli *), Bash(gh *)","allowedTools":["Bash(mvn","*),","Bash(make","*),","Bash(redis-cli","*),","Bash(gh","*)"]},"allowedTools":["Bash(mvn","*),","Bash(make","*),","Bash(redis-cli","*),","Bash(gh","*)"],"isInternal":false,"tokens":7716,"sizeBytes":31080},{"name":"SKILL.md","path":".agents/skills/writing-javadoc/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/lettuce/HEAD/.agents/skills/writing-javadoc/SKILL.md","title":"writing-javadoc","category":"anthropic-skill","format":"markdown","content":"---\nname: writing-javadoc\ndescription: Use when writing or editing Javadoc for Lettuce public API — new methods, classes, deprecations, or when a reviewer asks to fix or improve doc comments. Points to the full house ruleset in .agents/docs/javadoc.md (imperative method summaries, fixed tag order, @param/@return/@throws/@since/@deprecated house forms) and the rule that command-interface Javadoc is written on the sync interface first and mirrored to the other flavors. Trigger on \"write javadoc for\", \"document this method\", \"add a deprecation notice\", \"fix the doc comment\".\n---\n\n# Writing Javadoc for Lettuce\n\nThe complete, authoritative ruleset is **[.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md)** —\nread it before writing. This skill is the operating checklist; the doc wins on any\ndetail.\n\n## Before you write\n\n- **Is it a command interface?** The command interfaces are hand-edited, with the\n  **sync** interface's Javadoc as the reference text. Write or fix the comment on\n  the sync method first, then mirror it to **every** flavor that declares the\n  method, with the flavor-appropriate `@return` phrasing (`RedisFuture`,\n  `Mono`/`Flux`, …):\n  - `src/main/java/io/lettuce/core/api/{sync,async,reactive}/`\n  - the Kotlin coroutine interfaces (`src/main/kotlin/…/api/coroutines/`)\n  - the cluster node-selection interfaces\n    (`src/main/java/io/lettuce/core/cluster/api/{sync,async}/NodeSelection…`)\n  - the Sentinel interfaces (`src/main/java/io/lettuce/core/sentinel/api/…` and\n    their Kotlin flavor)\n\n  Javadoc parity is **not** test-enforced — keep the flavors in sync by hand. See\n  [api-consistency.md](../../../.agents/docs/api-consistency.md).\n\n## The rules most often gotten wrong\n\n1. **Golden rule:** document the caller-facing contract (inputs, outputs, effects,\n   errors, nullability) — never implementation details or refactor rationale.\n2. **First sentence:** imperative verb for methods (\"Append…\", \"Get…\", \"Remove…\"),\n   noun phrase for types. Not \"This method…\". Ends in a clean period.\n3. **Tag order:** `@param` → `@return` → `@throws` → `@author` → `@since` → `@see`\n   → `@deprecated`.\n4. **`@param`** for every parameter (type params `<K>`/`<V>` first); state\n   nullability with the house phrases `must not be {@code null}.` / `can be {@code null}.`\n5. **`@return`** for non-void; describe the value and its meaningful states, not the\n   type.\n6. **`@since`** is a bare version — `@since 7.7` (see .agents/docs/javadoc.md for deriving\n   the version from the build).\n7. **`@deprecated`** — keep the `@Deprecated` annotation and the tag in sync; house\n   form: `@deprecated since <version>, use {@link Replacement} instead; scheduled for\n   removal in a future major release.`\n8. **`@author`** on types only — the *maintainer's* name (an agent must not invent\n   one); don't reorder existing authors.\n9. Use `{@code null}` for literals; `{@link}` only to types resolvable on the\n   compile classpath (else `{@code TypeName}`).\n\n## Verify, don't guess\n\nMatch the surrounding file, and consult `.agents/docs/javadoc.md` for any subtle case\nrather than inventing a rule. Javadoc lint is off in the build (`<doclint>none</doclint>`),\nso review is the only check — get it right by hand.\n","frontmatter":{"name":"writing-javadoc","description":"Use when writing or editing Javadoc for Lettuce public API — new methods, classes, deprecations, or when a reviewer asks to fix or improve doc comments. Points to the full house ruleset in .agents/docs/javadoc.md (imperative method summaries, fixed tag order, @param/@return/@throws/@since/@deprecated house forms) and the rule that command-interface Javadoc is written on the sync interface first and mirrored to the other flavors. Trigger on \"write javadoc for\", \"document this method\", \"add a deprecation notice\", \"fix the doc comment\"."},"isInternal":false,"tokens":842,"sizeBytes":3277},{"name":"rules.md","path":".claude/rules/rules.md","rawUrl":"https://raw.githubusercontent.com/redis/lettuce/HEAD/.claude/rules/rules.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules\n\nThis project's coding rules are maintained **tool-neutrally** in `.agents/rules/` so\nevery agent shares one source. This file is the Claude Code entry point into them.\n\nRead [`.agents/rules/rules.md`](../../.agents/rules/rules.md) for the index, and the\nrelevant rule set before editing — e.g. [`.agents/rules/java-rules.md`](../../.agents/rules/java-rules.md)\nwhen editing Java source (`src/**/*.java`).\n","isInternal":false,"tokens":100,"sizeBytes":416},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/redis/lettuce/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"@AGENTS.md\n\n<!--\nThe shared operating manual lives in AGENTS.md (imported above). Reusable task\nprocedures live in .agents/skills/ and are referenced from AGENTS.md.\n-->\n","isInternal":false,"tokens":42,"sizeBytes":170}],"systemPromptSnippet":"<yakaai_skills repo=\"redis/lettuce\">\n<!-- File: AGENTS.md (Tokens: ~2369 | Category: root-instruction) -->\n# Lettuce Coding Conventions\n\nEssential rules for working on the Lettuce codebase — a scalable, thread-safe\nRedis client for the JVM built on netty and Project Reactor. Detailed guidance\nfor specific tasks lives in `.agents/skills/`; consult the relevant skill when\nperforming that type of work.\n\n## How this guidance is organized\n\nAgent guidance is **self-contained under agent-owned locations** and never mixed into\nthe published documentation site:\n\n- **Agent-facing** (this material) lives at the repo root (`AGENTS.md`, `CLAUDE.md`),\n  under `.agents/` (tool-neutral: `.agents/docs/`, `.agents/rules/`, `.agents/skills/`),\n  and `.claude/` (Claude-specific tooling that bridges to `.agents/`). None of it is\n  published by MkDocs.\n- **User-facing** contributor/user documentation lives under `docs/` and is published\n  to the docs site. Agents read the agent-owned copies, not `docs/`.\n\nWithin the agent material it's a small graph: **each topic has one owner file**, and\neverything else **links** to it rather than restating it. When a topic changes, edit\nthe owner only.\n\n| Topic | Owner |\n|-------|-------|\n| Always-on facts, guardrails, index (this file) | `AGENTS.md` |\n| Command flow & code generation | `.agents/docs/architecture.md` |\n| API consistency (flavors, mapping rules, tests) | `.agents/docs/api-consistency.md` |\n| Testing — environment, running, naming, layout, CI | `.agents/docs/integration-testing.md` |\n| Javadoc conventions (incl. `@since` derivation) | `.agents/docs/javadoc.md` |\n| Coding rules (Java style, …) | `.agents/rules/*` |\n| Task procedures (add a command, write Javadoc, PR description) | `.agents/skills/*` |\n\nRule: state a fact in its owner and link from elsewhere; don't copy it. This file\nkeeps only a minimal always-on quick-reference. Skills own the *sequence* of a task\nand link out for the mechanics.\n\n## General Principles\n\n- **Update documentation.** When changes affect user-facing behavior, config, or\n  public API, update the relevant pages under `docs/` (MkDocs).\n- **Add or update tests.** Bug fixes need a reproducer test; new features need\n  tests. See `.agents/docs/integration-testing.md` for unit vs. integration conventions.\n- **You are responsible for what you submit.** Validate all changes. Do not submit\n  AI-generated code without human oversight.\n\n## Guardrails (agents)\n\nThese are hard rules for automated agents. A human maintainer performs these\nactions, not the agent.\n\n- **Never `git commit` or `git push`.** Stage and describe changes; leave\n  committing and pushing to the maintainer.\n- **Never create or merge pull requests, and never reply to GitHub issues or PRs**\n  (no `gh pr create`/`merge`/`comment`/`review`, no `gh issue create`/`comment`).\n  Draft text for the maintainer to post if asked — for a PR description, use the\n  `draft-pr-description` skill, which writes to `prDescription.md`.\n\n## Requirements & Toolchain\n\n- Builds and runs on the JDK baseline declared in `pom.xml`\n  (`maven.compiler.source`/`target`); targets Redis 2.6+.\n- **The test build pins a specific JDK to match CI** — check `.github/workflows/`\n  (or `.agents/docs/integration-testing.md`) and use that JDK to build and reproduce CI.\n- Build tool is **Maven** (`mvn`). Integration tests require **Docker**.\n- **Prefer semantic code tooling over text search for Java, when available.** If a\n  Java language server or IDE integration is present (e.g. an `jdtls`-based LSP, or\n  an IDE MCP), use it for definitions, references, symbol/type search, call\n  hierarchy, and diagnostics instead of grepping. Fall back to text search only when\n  no such tool is available.\n\n## Project Structure\n\n```\nsrc/main/java/io/lettuce/core/                Core client, connections, commands\nsrc/main/java/io/lettuce/core/cluster/        Redis Cluster support\nsrc/main/java/io/lettuce/core/masterreplica/  Master/Replica & Sentinel\nsrc/main/java/io/lettuce/core/pubsub/         Pub/Sub\nsrc/main/java/io/lettuce/core/support/        Connection pooling (ConnectionPoolSupport, BoundedAsyncPool)\nsrc/main/java/io/lettuce/core/api/            Sync/async/reactive command interfaces\nsrc/test/java/io/lettuce/                      Tests (see .agents/docs/integration-testing.md §5)\nformatting.xml                                 Eclipse formatter rules\nMakefile                                       Docker-backed test lifecycle\n```\n\nWhere to look: connect → `core/`; pooling → `core/support/`; sharding →\n`core/cluster/`; HA/Sentinel → `core/masterreplica/`.\n\n**How a command flows** through the API interfaces, command builder, codec, and\nthe netty/RESP wire layer is documented in `.agents/docs/architecture.md`. Read it\nbefore adding commands or touching the protocol layer — note the\nsync/async/reactive/Kotlin command interfaces are **hand-edited** and kept in\nlockstep by the API consistency test suite (see `.agents/docs/api-consistency.md`).\n\n## Build & Test Commands\n\n**Unit tests** (pure logic, no server — named `*UnitTests`, run by Surefire):\n\n```bash\nmvn clean test                       # build + run unit tests, no Redis needed\nmvn test -Dtest=FooUnitTests         # a single unit test\n```\n\n**Integration tests** (need a running Redis — named `*IntegrationTests`, run by\nFailsafe; **off by default**, `make test` enables them via `-DskipITs=false`):\n\n```bash\nmake start                           # boot the Dockerized Redis topology (add version=<v> to pick one)\nmake test                            # full build incl. integration tests\nmake stop                            # tear the environment down\n```\n\nRun one integration test fast (env already started):\n\n```bash\nTEST_WORK_FOLDER=./work/docker mvn -DskipITs=false -DskipUnitTests=true \\\n  -Dit.test=FooIntegrationTests verify -Pci\n```\n\nOne caveat worth remembering: `-Dit.test=` filters Failsafe (a single integration\ntest); `-DskipUnitTests=true` runs integration only. The full flag reference,\nRedis-version matrix, topologies, TLS/mTLS and CI mapping live in\n[`.agents/docs/integration-testing.md`](.agents/docs/integration-testing.md).\n\n**Apply formatting** (required before committing):\n\n```bash\nmvn formatter:format                 # applies formatting.xml; do NOT submit formatting-only diffs\n```\n\n## Testing Rules\n\n- **The file-name suffix picks the runner:** `*UnitTests` (Surefire, no server),\n  `*IntegrationTests` (Failsafe, needs Redis). Prefer those suffixes. A singular\n  `*Test` suffix is excluded by Surefire and picked up by Failsafe instead, so such a\n  class never runs in `mvn test`; it only runs in the integration phase (`make test`,\n  which is what CI runs, Docker required) or when named explicitly via `-Dtest=`. Name\n  pure unit tests `*UnitTests`.\n- Integration tests read host/port/TLS from `TestSettings` / `TlsSettings` — never\n  hard-code them.\n- Everything else about testing (topologies, TLS/mTLS, layout, CI) lives in\n  [`.agents/docs/integration-testing.md`](.agents/docs/integration-testing.md).\n\n## Coding Style Essentials\n\n- 4-space indentation, enforced by `formatter-maven-plugin` — run\n  `mvn formatter:format`. Don't submit formatting-only changes (they create noise\n  and get PRs rejected).\n- **Add `@since <version>` to all new public API** — see\n  [`.agents/docs/javadoc.md`](.agents/docs/javadoc.md) for the version-derivation recipe.\n- **`@author` is the human contributor's.** Maintainers add their own `@author` to\n  files they touch (per CONTRIBUTING). An automated agent must **not** invent an\n  `@author` for itself — leave the tag to the maintainer unless told which name to add.\n- **Java style** — imports over fully-qualified names, braces on every control\n  statement (no braceless one-liners), no wildcard imports. Full list in\n  `.agents/rules/java-rules.md` (see `.agents/rules/` before editing Java).\n- Javadoc for public API follows house conventions — see `.agents/docs/javadoc.md`, or use\n  the `writing-javadoc` skill.\n\n## Commit & PR\n\n- Open a feature-request issue first; reference its number in the PR\n  (`<text> #<ticket>`).\n- Include unit and/or integration tests for every change.\n- Split feature/bugfix commits from formatting/polishing commits.\n- Follow `.github/PULL_REQUEST_TEMPLATE.md`.\n\n## Key References\n\n| Document | What it covers |\n|----------|----------------|\n| `.github/CONTRIBUTING.md` | How to build, test, and contribute |\n| `.agents/docs/architecture.md` | How a command flows: API shapes, code generation, command model, codec, RESP/netty wire layer |\n| `.agents/docs/api-consistency.md` | The API flavors, their return-type mapping rules, and the consistency test suite that enforces them |\n| `.agents/docs/integration-testing.md` | Test environment, topologies, unit vs. integration, CI |\n| `.agents/docs/javadoc.md` | Javadoc house conventions for public API |\n| `.github/PULL_REQUEST_TEMPLATE.md` | PR checklist |\n| `formatting.xml` | Eclipse code-formatting rules |\n| [Javadoc](https://redis.github.io/lettuce/overview/) | Public API reference |\n\n## On-Demand Skills\n\nDetailed task guidance lives in `.agents/skills/`. Consult the relevant skill\nbefore doing that type of work.\n\n| Skill | When to use |\n|-------|-------------|\n| `extend-commands-api` | Add or extend a Redis command (or overload) end-to-end, evidence-first: HLD + server PR + live redis-cli verification, plan-mode approval, then all API interface flavors, builder, async/reactive/Kotlin impls, and tests |\n| `create-implementation-plan-for-redis-api-change` | Turn a shared client HLD into Lettuce's reviewed implementation plan (API, files per flavor, ordered steps, test matrix) without touching sources; read-only, writes one plan file; conventions come from `extend-commands-api` |\n| `writing-javadoc` | Write or fix Javadoc for public API following Lettuce house conventions |\n| `draft-pr-description` | Generate a GitHub PR title and description from the diff between two local branches |\n\n<!-- File: .agents/rules/java-rules.md (Tokens: ~281 | Category: anthropic-skill) -->\n# Java rules\n\nConventions for Java source that agents most often get wrong. The build does **not**\nenforce these — the Eclipse formatter (`formatting.xml`) reformats whitespace but does\nnot add missing braces or convert fully-qualified names to imports, and there is no\nCheckstyle — so apply them by hand.\n\n- **Use imports, not fully-qualified names.** Reference a type by its simple name and\n  add an `import`. Do not inline a package-qualified name in code\n  (e.g. write `RedisCommands`, not `io.lettuce.core.api.sync.RedisCommands`). The only\n  exception is disambiguating a genuine clash between two same-named types in one file.\n- **No wildcard imports** (`import foo.bar.*;`). Import each type explicitly.\n- **Always use braces on control statements.** `if`, `else`, `for`, `while`, and `do`\n  take a block even for a single-statement body — never a braceless one-liner:\n\n  ```java\n  // NO\n  if (key == null)\n      return;\n\n  // YES\n  if (key == null) {\n      return;\n  }\n  ```\n\n- **One statement per line.**\n\nRun `mvn formatter:format` before finishing — it fixes layout, but not the rules above.\n\n<!-- File: .agents/rules/rules.md (Tokens: ~86 | Category: anthropic-skill) -->\n# Coding rules\n\nTool-neutral coding rules for this repo. Each file covers one area; read the\nrelevant rule set before editing that kind of file.\n\n| Rule set | Read before editing |\n|----------|---------------------|\n| [java-rules.md](java-rules.md) | Java source (`src/**/*.java`) |\n\nAdd a new file here as new rule areas come up, and list it in the table above.\n\n<!-- File: .agents/skills/draft-pr-description/SKILL.md (Tokens: ~858 | Category: anthropic-skill) -->\n---\nname: draft-pr-description\ndescription: Generate a clear, concise GitHub PR title and description from the diff between two local git branches, and save it to prDescription.md in the repo root. Use this whenever the user asks to write, generate, draft, or update a PR description or PR title from local branch changes — including phrasing like \"summarize this diff into a PR description,\" \"write a PR description for my current branch,\" \"create a PR title and description,\" or any request to compare a base and target branch for PR purposes. Trigger even if the user doesn't name specific branches; this skill knows how to default them.\nargument-hint: [base-branch] [target-branch]\narguments: [base, target]\nallowed-tools: Bash(git diff *) Bash(git log *) Bash(git branch *)\n---\n\n# PR Description Generator\n\nWrites a GitHub PR title and description from the diff between two local git branches, focused on what reviewers need: purpose, key decisions/assumptions, behavioral or conceptual changes, and a brief testing note if relevant. Uses only standard git commands, so any agent can follow it.\n\n## Arguments\n\nTwo optional positional arguments — base branch, then target branch:\n\n- Base: `$base`\n- Target: `$target`\n\nAn empty value means \"not provided\" — default it per the resolution below (a missing argument is defaulted, not guessed). A target cannot be given without a base.\n\n## 1. Resolve branches\n\n- Both named → use as given.\n- Only one named → it's the **base**; target defaults to current branch.\n- None named → base defaults to `main`, then `master` if `main` doesn't exist (`git branch --list`). **Only check these two names — no scanning other branches, no guessing.** If neither exists, stop and ask. Target defaults to current branch (`git branch --show-current`).\n\nState the resolved base/target before proceeding.\n\n## 2. Diff\n\n- Confirm both branches exist (`git branch --list <name>`); stop if not.\n- `git diff <base>...<target>` and `git log <base>..<target> --oneline`\n- Empty diff → stop and say so, don't fabricate a description.\n\n## 3. Analyze\n\nFrom the diff/log, identify: purpose of the change; key decisions and trade-offs; assumptions the code relies on; behavioral changes (concrete: \"X now rejects Y\" not \"changed validation\"); conceptual/contract changes; breaking changes, new config/env vars, migrations; test changes (what new tests cover conceptually, why existing ones were adapted — not test-by-test).\n\nSkip routine stuff (formatting, trivial renames). No file/line enumeration — write like explaining to a teammate, not a changelog.\n\n## 4. Output structure\n\nWrite to `prDescription.md` (repo root, overwrite if exists):\n\n```markdown\n# <Title — imperative, <70 chars>\n\n## Summary\n<2-3 sentences>\n\n## Key Decisions & Assumptions\n<bullets — omit if none>\n\n## Behavioral / Conceptual Changes\n<bullets, concrete — omit if none>\n\n## Testing\n<2-3 sentences, only if diff touches tests — omit otherwise, don't write \"no tests\">\n\n## Notes\n<breaking changes, follow-ups — omit if none>\n```\n\nKeep it brief (readable in under a minute, ~150-200 words total unless the change is genuinely large). Omit empty sections rather than padding them. Never fabricate testing details, tickets, or names not evidenced in the diff — flag uncertainty in Notes instead.\n\n## 5. Confirm\n\nAfter writing, give a short confirmation (don't reprint the file) and offer to run `gh pr create --title \"...\" --body-file prDescription.md` if `gh` is available. Include the provided/resolved base and target branch names into the suggested command as part of the suggestion. At the end, ask if the user wants to open the PR now and wait for confirmation before proceeding. If the user declines, stop and say the PR description is ready in `prDescription.md`.\n\n<!-- File: .agents/skills/extend-commands-api/SKILL.md (Tokens: ~7716 | Category: anthropic-skill) -->\n---\nname: extend-commands-api\ndescription: Add or extend Redis commands in the Lettuce client API end-to-end — a new core command, a family of new commands, an extension to an existing command's options, or a module/area command (Search/JSON/Bloom/VectorSet). Gathers evidence first (HLD document, the server-side PR in the repo owning the command family, live verification against the Dockerized test environment with redis-cli), plans the full implementation matrix in plan mode, then implements across all API flavors with unit and integration tests. Trigger on \"add support for the <X> command\", \"implement <REDIS COMMAND> in Lettuce\", \"extend <command> with <option>\", or adding a new argument/overload to an existing command.\nallowed-tools: Bash(mvn *), Bash(make *), Bash(redis-cli *), Bash(gh *)\n---\n\n# Extend the Lettuce Commands API\n\nImplement a new Redis command (or extend an existing one) in Lettuce, following the\nconventions maintainers enforce in review. The workflow is evidence-first: prove the\ncommand's behavior on a real server before designing the Java API.\n\nRead [.agents/docs/architecture.md](../../../.agents/docs/architecture.md) first for\nthe model behind this flow — especially that the sync/async/reactive/Kotlin command\n**interfaces are hand-edited source files kept in lockstep by the API consistency\ntest suite** (see [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md)).\n\n> **Historical caveat when reading old PRs**: PRs merged before the generator removal\n> also edit `src/test/java/io/lettuce/core/api/<Group>Commands.java` — the old\n> template source files. Those files **no longer exist**; do not recreate them. The\n> flavor interfaces are now edited directly.\n\n## Phase 0 — Gather evidence BEFORE planning\n\nDo all of the following before writing any plan or code:\n\n1. **Ask the user for the HLD.** Interactively ask for the path to a markdown file\n   containing the High-Level Design for the command(s) (or confirm none exists). If\n   a path is given, read it fully — it is the primary source for syntax, semantics,\n   reply shape per RESP2/RESP3, and edge cases.\n\n2. **Find the server-side PR in the repo that owns the command.** Route the\n   search by command family — module command families are developed in their\n   owning repositories, not in `redis/redis`:\n\n   | Command family | Repository | Syntax source |\n   |----------------|------------|---------------|\n   | Core commands, vector sets (`VADD`, …) | `redis/redis` | `src/commands/*.json` |\n   | Search (`FT.*`) | `RediSearch/RediSearch` | PR diff + command docs (no `src/commands/*.json`) |\n   | JSON (`JSON.*`) | `RedisJSON/RedisJSON` | PR diff + command docs |\n   | Probabilistic (`BF.*`, `CF.*`, `CMS.*`, `TOPK.*`, `TDIGEST.*`) | `RedisBloom/RedisBloom` | PR diff + command docs |\n   | Time series (`TS.*`) | `RedisTimeSeries/RedisTimeSeries` | PR diff + command docs |\n\n   If the search comes up empty in the routed repo, fall back to `redis/redis`\n   (and vice versa) before concluding there is no server PR.\n\n   First verify that `gh` works in the current (sandboxed) environment:\n   ```bash\n   gh auth status\n   ```\n   Sandboxes often block access to credential files, so `gh` may report\n   unauthenticated here even though it works on the user's machine. If so, ask the\n   user for permission to run these specific read-only `gh` commands outside the\n   sandbox; only fall back to the unauthenticated GitHub REST API if they decline.\n\n   Then search for the PR that adds/extends the command on the server:\n   ```bash\n   gh search prs --repo <owning-repo> \"<COMMAND NAME>\" --limit 10\n   gh pr view <num> --repo <owning-repo>\n   gh pr diff <num> --repo <owning-repo>  # in redis/redis: src/commands/*.json has exact syntax\n   ```\n   Extract: exact wire syntax (argument order and optionality), reply type per\n   **RESP2 and RESP3** (they can differ — this determines the `CommandOutput` and\n   the reactive `Mono`/`Flux` mapping), error conditions, and the **first server\n   version carrying the feature** (drives `@EnabledOnCommand` gating and the test\n   env version). Note whether the server marks the feature *experimental/preview*.\n\n3. **Verify the command exists on the \"next\" Redis OSS version.** Start the\n   integration environment using the **highest** version the Makefile supports\n   (check `SUPPORTED_TEST_ENV_VERSIONS` in the `Makefile`, or the\n   `src/test/resources/docker-env/.env.vX.XX` files — pick numerically, `8.10` >\n   `8.8`):\n   ```bash\n   grep SUPPORTED_TEST_ENV_VERSIONS Makefile\n   make start version=8.10\n   ```\n   Then probe the running servers. Test connection defaults live in\n   `src/test/java/io/lettuce/test/settings/TestSettings.java` — the main standalone\n   node is `localhost:6479` (no auth); module commands (`FT.*`, `JSON.*`, `BF.*`,\n   `VADD`, …) run on the stack node at `localhost:16379`:\n   ```bash\n   redis-cli -p 6479 INFO server | grep redis_version\n   redis-cli -p 6479 COMMAND INFO <COMMAND>   # non-empty → command exists\n   redis-cli -p 6479 COMMAND DOCS <COMMAND>   # arity/args — compare with the server PR\n   ```\n   For a NEW command, `COMMAND INFO` must return a non-empty reply. For an EXTENDED\n   command, additionally invoke it with the new syntax against a scratch key and\n   confirm the server accepts it (no `ERR syntax error` / `ERR unknown argument`).\n\n   **If the command/option is missing on the latest env version**, the image tag\n   pinned in the `.env.vX.XX` file is too old. Ask the user for a\n   `redislabs/client-libs-test` image tag that contains it (e.g. an RC/edge build —\n   tags are listed at https://hub.docker.com/r/redislabs/client-libs-test/tags; you\n   may check and suggest candidates), then:\n   ```bash\n   make stop\n   CLIENT_LIBS_TEST_IMAGE_TAG=<tag> make start\n   ```\n   If the PR should also move CI to that build, update the newest `.env.vX.XX`\n   pin as part of the change — command PRs do this when they need a fresh server\n   (cf. the hotkeys PR bumping `.env.v8.6`). If no tag carries the feature, report\n   it and proceed: implementation continues, integration tests stay gated until an\n   image ships the change. Keep the environment running for later test runs.\n\n4. **Create redis-cli showcase test cases.** Once the command is available, derive\n   a small set of redis-cli scenarios from the HLD and the server PR and run them.\n   These serve two purposes:\n   - **Smoke test**: prove the documented behavior holds — happy path, each new\n     option/keyword, reply shape (run with `redis-cli -3` too when RESP3 replies\n     differ), edge cases and error conditions (missing key, out-of-range args,\n     conflicting options) — so the Java implementation is built against observed\n     replies, not assumptions.\n   - **Showcase**: each scenario should read as a mini use-case explaining WHY the\n     command/option exists. Prefer realistic data over `foo`/`bar`.\n\n   Save the scenarios as a commented script in a scratch file (one block per\n   use-case: a \"what this demonstrates\" comment, the commands, the observed reply\n   pasted back as comments). Carry this forward: it feeds the Phase 1 plan, the\n   test assertions, and the PR description. If the command could not be made\n   available on any image, still write the scenarios as *expected* transcripts and\n   mark them unverified.\n\n5. **Read the testing and consistency docs**:\n   [.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n   (environment, `*UnitTests` vs `*IntegrationTests` naming, base/overload test\n   structure, running one test) and\n   [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md) (the\n   flavors and their return-type mapping rules).\n\n6. **Trace one analogous existing command** end-to-end (same command group, similar\n   reply shape) so the plan mirrors real code, not guesswork. Good reference PRs,\n   validated against git history:\n\n   | Commit | What it exemplifies |\n   |--------|---------------------|\n   | `7ca3e9cc0` (CLIENT NO-TOUCH #3776) | Minimal new command: all 6 flavors + builder + 2 dispatch layers + Kotlin impl + builder unit test + integration test |\n   | `312ecc7b4` (INCREX #3746) | New command with args: self-typed abstract args base, new value type, new `CommandOutput`s, per-type args/output unit tests, **creating a missing RESP2 overload class** |\n   | `b009df37b` (XNACK #3728) | New command with an enum-valued argument |\n   | `9a0899875` (BITOP extensions #3334) | New operations/overloads on an existing command |\n   | `c603c8120` (HSCAN NOVALUES #2816) | New overload rippling into helpers (`ScanIterator`, `ScanStream`, `ScanFlow`) |\n   | `aa7b4b0be` (stream idempotency #3637) | Option added to an existing `*Args` class flows through with **no interface change** (the `XAddArgs` part) |\n   | `a209fba70` (CAS/CAD #3512) | Read-only command registered in `ReadOnlyCommands` + count test update |\n   | `838a4d39a` (HOTKEYS #3638) | Map-shaped reply via `ComplexOutput` + a `*ReplyParser`, cluster interface additions, full integration overload set, `.env` image bump |\n   | `6567f2d5e` (RediSearch #3375) | Whole new command area: own `Redis<Area>CommandBuilder`, `arguments`/reply-parser packages |\n\n   **Conventions beat precedent.** Traced reference commands can predate the\n   current conventions; when the reference code conflicts with a written rule in\n   this skill or the linked docs, the rule wins. Example: `sintercard(K...)`\n   ships without a single-key overload because it predates the\n   one-overload-per-varargs rule — a new command mirroring it must still add the\n   single-argument overload.\n\n7. **Determine the `@since` version** — recipe owned by\n   [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md):\n   ```bash\n   mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n   ```\n   Drop `-SNAPSHOT` and the patch digit (`7.7.0-SNAPSHOT` → `@since 7.7`).\n\n**The evidence is your working context, not a deliverable.** Hold it in mind to\ndrive the work — do not paste it back to the maintainer or pause for \"spec\napproval.\" The only stop for the maintainer is the Phase 1 plan approval; surface\ngenuinely ambiguous design choices *there*, with the proposed sync signatures,\nrather than interrupting earlier.\n\n## Phase 1 — Plan mode, then explicit approval\n\nEnter **plan mode**. Using the evidence, classify the change with the decision tree\nbelow and enumerate the exact file-by-file touch list, the test matrix, and the\ngating annotations. The plan must contain:\n\n- A short \"what this feature enables\" section built from the showcase scenarios\n  (Phase 0 step 4), including one or two representative command/reply transcripts.\n- **The proposed sync interface signature(s) with their Javadoc.** The sync\n  interface is the human-authored **API contract**; every other flavor is derived\n  from it and it is costly to change once mirrored. Plan approval is the\n  maintainer's sign-off on that contract — if the signatures must deviate later\n  during implementation, stop and re-confirm before mirroring.\n- **The complete overload set, enumerated.** For every varargs parameter, list\n  the matching single-argument overload; for a multi-key command with an `*Args`\n  object, use the `List<K>` shape (no varargs) and list the fixed-arity\n  convenience overloads instead — both house rules live in \"Types & args\n  conventions\". Justify any overload you omit so the maintainer signs off on the\n  exception. An overload discovered missing in review means reworking all six\n  flavors.\n\nPresent the plan and **explicitly ask permission to execute** before implementing.\nDo not start editing files until the user approves.\n\nOnce approved, copy this checklist into your working notes and tick items off:\n\n```\nExtend-commands progress:\n- [ ] 0. Evidence: HLD, server PR, live probe + showcase, RESP2/RESP3 replies, @since\n- [ ] 1. Plan approved — incl. the sync signature(s), the API contract\n- [ ] 2. Types: argument & response types (they must exist before any interface edit)\n- [ ] 3. Sync interface: full overload set (varargs → single-arg too) + Javadoc\n       (@param constraints + @throws for builder-validated preconditions)\n- [ ] 4. Mirror: async, reactive, Kotlin, NodeSelection×2 — consistency tests pass\n- [ ] 5. Implementations: CommandType/Keyword, builder, async, reactive, Kotlin impl\n- [ ] 6. Tests: args/builder/output unit tests + integration base/overloads\n- [ ] 7. Docs: entry in the current-release section of docs/new-features.md\n- [ ] 8. Verify: mvn clean test + a single integration test run; then make stop\n```\n\n## Decision tree — what kind of change is this?\n\n**A. Extension of an existing command that fits an existing `*Args` class**\n(new option token / new field):\n- Touch ONLY the `*Args` class (+ `CommandKeyword` for new tokens) + tests. Do NOT\n  touch the command interfaces, builder signatures, or dispatch layers — the\n  existing `args.build(commandArgs)` delegation carries the new option through\n  automatically (cf. the `XAddArgs` part of the stream-idempotency PR).\n- Add a fluent setter returning `this`; register new tokens in\n  `src/main/java/io/lettuce/core/protocol/CommandKeyword.java`.\n- If the reply shape grows, extend the response model/output backward-compatibly.\n\n**B. New command(s) in an existing group — core or module area** — the FULL\nmatrix, in this order (types first — every flavor references them, so they must\nexist to compile). For a command joining an **existing module area** (a new\n`FT.*` method in the Search group, a new `JSON.*` method, …) the same matrix\napplies with the area substitutions: the group is the area's flavor interfaces,\nthe builder is the area's `Redis<Area>CommandBuilder` (+ its\n`Redis<Area>CommandBuilderUnitTests`), argument/reply types go in the area\npackage, and gating/tests follow the module rules in D (capability probe, stack\nnode). The dispatch layers are the same `AbstractRedisAsyncCommands` /\n`AbstractRedisReactiveCommands` and Kotlin `*Impl.kt` as for core commands.\n1. **Argument/response types** — see \"Types & args conventions\" below.\n2. **Sync interface** `src/main/java/io/lettuce/core/api/sync/<Group>Commands.java`\n   — pick the group by command family (STRING → `RedisStringCommands`, HASH →\n   `RedisHashCommands`, generic-key → `RedisKeyCommands`, …). This is the reference\n   signature + Javadoc all flavors mirror (see the\n   [writing-javadoc](../writing-javadoc/SKILL.md) skill; `@since` mandatory):\n   ```java\n   /**\n    * Returns the length of the string value stored at {@code key}.\n    *\n    * @param key the key, must not be {@code null}.\n    * @return the length of the string at {@code key}, or {@code 0} when {@code key} does\n    *         not exist.\n    * @since 7.7\n    */\n   Long strlen(K key);\n   ```\n   Three contract rules to apply while designing the signatures and their Javadoc:\n   - **Multi-key commands that also take an `*Args` object take the keys as\n     `List<K>`, not varargs.** A varargs parameter must come last, so a trailing\n     options object can only follow it via an awkward leading placement (cf. the\n     older `sintercard(long limit, K... keys)`). For new commands, take the keys\n     as `List<K>` so the `*Args` argument comes last —\n     `sunioncard(List<K> keys, SUnionCardArgs args)` — and add fixed-arity\n     convenience overloads (`(K key1, K key2)` and their `*Args` variants)\n     instead of a varargs form. Since there is no varargs parameter here, the\n     single-argument-overload rule below does not apply; add only the overloads\n     that are meaningful — e.g. **no single-key `sunioncard`/`sdiffcard`**, whose\n     one-key union/difference is just `SCARD`.\n   - **Every varargs parameter gets a single-argument overload** —\n     `foo(K key, V value)` alongside `foo(K key, V... values)`. This is a hard\n     convention for new commands and overrides any traced precedent that lacks\n     it (older commands predate the rule). Does not apply to the `List<K>`-shaped\n     multi-key-with-`*Args` commands above, which take no varargs.\n   - **Validated preconditions are contract.** Any constraint the builder will\n     enforce (null checks, non-empty varargs) must be stated in the `@param`\n     text with the house phrases (`must not be {@code null}.`,\n     `must not be empty.`) *and* documented with a matching\n     `@throws IllegalArgumentException if …` tag — forms owned by\n     [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md). Both are then\n     mirrored to every flavor.\n3. **Mirror to every flavor**: async, reactive, Kotlin coroutines, and the two\n   cluster node-selection interfaces (`NodeSelection<Group>Commands` /\n   `NodeSelection<Group>AsyncCommands`), applying the per-flavor return-type\n   mapping rules from\n   [.agents/docs/api-consistency.md](../../../.agents/docs/api-consistency.md).\n4. **Protocol enums** — command name in\n   `src/main/java/io/lettuce/core/protocol/CommandType.java` (wire bytes derive\n   from the enum name); subcommand tokens in `CommandKeyword`. **Never add a\n   `CommandKeyword` that duplicates a name already in `CommandType`** (e.g. `SET`,\n   `DISCARD`) — the builder static-imports both enums and the bare name becomes\n   ambiguous; reference `CommandType.<NAME>` directly instead.\n5. **Builder** — `RedisCommandBuilder.java` (or the area builder, see D): null\n   checks via `LettuceAssert`/`notNullKey`, `CommandArgs` in **wire order**, the\n   `CommandOutput` chosen from the observed RESP2/RESP3 replies:\n   ```java\n   public Command<K, V, Long> strlen(K key) {\n       notNullKey(key);\n       return createCommand(STRLEN, new IntegerOutput<>(codec), key);\n   }\n\n   public Command<K, V, Boolean> copy(K source, K destination, CopyArgs copyArgs) {\n       LettuceAssert.notNull(source, \"Source \" + MUST_NOT_BE_NULL);\n       LettuceAssert.notNull(destination, \"Destination \" + MUST_NOT_BE_NULL);\n       CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(source).addKey(destination);\n       copyArgs.build(args);\n       return createCommand(COPY, new BooleanOutput<>(codec), args);\n   }\n   ```\n   Every `LettuceAssert` precondition written here is public contract: if the\n   interface Javadoc (step 2) does not already state the constraint and its\n   `@throws IllegalArgumentException`, go back and add it — on every flavor.\n6. **Dispatch layers** — one-liners in *both* `AbstractRedisAsyncCommands` *and*\n   `AbstractRedisReactiveCommands` (`createMono` for scalars,\n   `createDissolvingFlux` for `List`/`Set` replies, matching the interface's\n   `Mono`/`Flux`):\n   ```java\n   // AbstractRedisAsyncCommands\n   public RedisFuture<Long> strlen(K key) { return dispatch(commandBuilder.strlen(key)); }\n   // AbstractRedisReactiveCommands\n   public Mono<Long> strlen(K key) { return createMono(() -> commandBuilder.strlen(key)); }\n   ```\n7. **Kotlin impl** —\n   `src/main/kotlin/io/lettuce/core/api/coroutines/<Group>CoroutinesCommandsImpl.kt`:\n   ```kotlin\n   override suspend fun strlen(key: K): Long = ops.strlen(key).awaitSingle()\n   ```\n8. **Cluster** — pick the routing shape deliberately; there are three cases:\n   - A **single-key** command flows through automatically (routed by slot).\n   - A **broadcast/all-shards** command (its answer is the aggregate over the\n     whole cluster — cf. `dbsize`, `flushall`) needs hand-coded fan-out\n     overrides in `RedisAdvancedClusterAsyncCommandsImpl` *and* its reactive\n     sibling (`executeOnUpstream` + a `MultiNodeExecution` aggregator), and may\n     need methods on the cluster aggregate interfaces.\n   - A **node-specific** command (keyless, but its result is only meaningful per\n     node — cf. HOTKEYS) must **not** be fanned out: add `default` overrides on\n     the cluster aggregate interfaces that throw `UnsupportedOperationException`\n     and direct callers to the node-selection API or `getConnection(nodeId)`\n     (cf. `RedisClusterCommands.hotkeysReset()`); only the node-selection\n     flavors execute it.\n\n   See \"Cluster routing\" in\n   [.agents/docs/architecture.md](../../../.agents/docs/architecture.md).\n9. **Read-only command?** Register it in\n   `src/main/java/io/lettuce/core/protocol/ReadOnlyCommands.java` (`CommandName`\n   enum) so replica-read routing knows, and bump the size assertion in\n   `ClusterReadOnlyCommandsUnitTests`.\n\n**C. Extension needing new overloads / a new `*Args` class** (hybrid): the new\nmethods go through the full matrix of B; the option plumbing follows A. Check\nwhether command helpers must follow (`ScanIterator`/`ScanStream`/`ScanFlow` for\nscan-family commands).\n\n**D. New command group / module area** (Search/JSON/Bloom/VectorSet-style):\n- **Unlike Jedis, Lettuce module areas are full citizens**: every group gets all\n  six flavors — sync, async, reactive, Kotlin coroutines, and both node-selection\n  interfaces — plus Kotlin impls.\n- Create the flavor interfaces by mirroring an existing area end-to-end, register\n  the group in the `CommandInterfaces` enum\n  (`src/test/java/io/lettuce/core/api/consistency/`), and wire the group into the\n  hand-written aggregate interfaces (`RedisCommands`, `RedisAsyncCommands`,\n  `RedisReactiveCommands`, and the cluster variants) so they `extend` it — the\n  consistency tests enforce the aggregate wiring.\n- Areas get their **own builder** (`Redis<Area>CommandBuilder`, cf.\n  `RediSearchCommandBuilder`) with a matching\n  `Redis<Area>CommandBuilderUnitTests`, and keep their argument types and reply\n  parsers in an area package (e.g. `core/search/arguments/`).\n- Module commands still gate on server capability, not version:\n  `@EnabledOnCommand(\"FT.CREATE\")`-style probes; integration tests target the\n  stack node.\n\n## Types & args conventions\n\n- **Argument types**: an options object is a `*Args implements CompositeArgument`\n  class (e.g. `io.lettuce.core.CopyArgs`) whose `build(CommandArgs)` appends its\n  tokens. Fluent setters return `this`. If two overloads share options but differ\n  in a typed field (long vs double), use a self-typed abstract base\n  (`BaseFooArgs<T extends BaseFooArgs<T>>`) with concrete subclasses — cf.\n  `BaseIncrexArgs`/`IncrexArgs`/`IncrexFloatArgs`.\n- **`@since` goes on every new public element, not just the class.** A\n  class-level `@since` is **not inherited**: the nested `Builder` type, each of\n  its static factory methods, and each public fluent setter needs its own\n  `@since` tag, or the generated API docs lose the release provenance for those\n  members.\n- Token-valued argument enums are plain enums whose values the builder/args class\n  appends (cf. `XNackMode`).\n- **Response types**: reuse existing models where possible — `Value`, `KeyValue`,\n  `ScoredValue`, `GeoCoordinates`, `GeoWithin`, `StreamMessage`, `KeyScanCursor`\n  (all in `io.lettuce.core`) — and add a new one only when the reply genuinely\n  doesn't map. For a map-shaped/structured reply, pair a model class with a\n  `ComplexDataParser` consumed via `ComplexOutput` (cf. `HotkeysReply` +\n  `HotkeysReplyParser`).\n- **Return-type idioms** (established conventions): `1/0` integer reply →\n  `Boolean` (cf. `copy`, `expire`, `hsetnx`); count → `Long`; status → `String`;\n  bulk value → `V`. And the overload rules from step B.2: every varargs\n  parameter also gets a single-argument overload, while a multi-key command with\n  an `*Args` object takes `List<K>` (not varargs) plus fixed-arity overloads.\n- The `CommandOutput` (the reply *parser*) is chosen at the builder step from the\n  **observed** RESP2/RESP3 replies of Phase 0 — if none fits, add one under\n  `io.lettuce.core.output` with a unit test (cf. `IncrexLongOutput`).\n\n## The consistency suite is the safety net\n\nAfter mirroring, run:\n\n```bash\nmvn -Dtest='*ConsistencyUnitTests,CommandBuilderCoverageUnitTests' \\\n    -Dsurefire.failIfNoSpecifiedTests=false test\n```\n\nIt names exactly the flavor/signature you missed. For a genuinely unusual return\ntype (e.g. `Flux<Value<Long>>`, or `Mono<List<Double>>` because Redis returns\nnulls), register it in the registry that owns the flavor —\n`src/test/java/io/lettuce/core/api/consistency/KnownApiDeviations.java` for the\nJava flavors, `src/test/kotlin/io/lettuce/core/api/consistency/KnownKotlinApiDeviations.kt`\nfor the coroutine flavor — **with a comment justifying it**. Never use a\ndeviation entry to paper over a sync/async signature mismatch — that breaks the\nsync-over-async runtime proxy.\n\n**Format before you build**: run `mvn formatter:format` after hand-editing — the\nbuild's `formatter:validate` step fails the compile on unformatted code. Do not\nsubmit formatting-only diffs.\n\n## Test matrix — what to write\n\nNaming, placement, and the base/overload structure are owned by\n[.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n— follow it. The established per-command layers (write all that apply):\n\n1. **Args unit tests** (`*Args` classes): assert the exact encoded tokens and\n   wire order, setter validation, and overload equivalence — e.g.\n   `IncrexArgsUnitTests`, `XAddArgsUnitTests`. No server needed.\n2. **Builder unit test**: assert the constructed command and encoded args —\n   including the RESP2/RESP3 output shape observed in Phase 0. Core commands go\n   in `src/test/java/io/lettuce/core/RedisCommandBuilderUnitTests.java`; area\n   commands in their `Redis<Area>CommandBuilderUnitTests`.\n3. **Output unit tests** when a new `CommandOutput` was added (cf.\n   `IncrexOutputUnitTests`).\n4. **Integration tests**: add methods to the sync base class\n   (`<Group>CommandIntegrationTests`), gated per-test with\n   `@EnabledOnCommand(\"<NAME>\")`. Use assertions derived from the redis-cli\n   showcase transcripts — real semantics, not just \"no error\", covering the whole\n   family the option touches (with/without optional args, error cases):\n   ```java\n   @Test\n   @EnabledOnCommand(\"COPY\")\n   void copy() {\n       redis.set(key, value);\n       assertThat(redis.copy(key, key + \"2\")).isTrue();\n   }\n   ```\n5. **Overloads**: the base's `@Test` methods re-run automatically under the\n   group's existing RESP2/cluster/reactive/Tx overload classes — but **only the\n   ones that exist**. Check the target group against peer groups and **create a\n   missing overload class** when it matters for the command (INCREX created\n   `StringCommandResp2IntegrationTests` because its reply differs by protocol).\n   Provide the base test at minimum; add overloads that carry real\n   risk (RESP2 when replies differ, cluster when routing matters).\n\n## Running the tests\n\nThe build pins a specific JDK to match CI — check `.github/workflows/` and the\nlocal-gotchas section of\n[.agents/docs/integration-testing.md](../../../.agents/docs/integration-testing.md)\n(pin `JAVA_HOME`, worktree `git-commit-id-plugin` skip, `TEST_WORK_FOLDER`).\n\n- Unit tests (Surefire, no server): `mvn clean test`, or `mvn -Dtest=FooUnitTests test`.\n- Integration tests (Failsafe) need the Docker env from Phase 0 and the `verify`\n  lifecycle — **`-Dit.test=` filters Failsafe, `-Dtest=` does not**:\n  ```bash\n  TEST_WORK_FOLDER=./work/docker mvn -DskipITs=false -DskipUnitTests=true \\\n    -Dit.test=FooIntegrationTests verify -Pci\n  ```\n  If the command isn't in any published `redislabs/client-libs-test` tag yet, say\n  so: the integration tests will be skipped by `@EnabledOnCommand` (expected and\n  acceptable), but they must still be written and compile.\n\n**Tear the environment down when you are done.** The Docker topology started in\nPhase 0 keeps running (and holds the test ports) until stopped. After the final\nverification run — and equally when the task is aborted or fails partway — run:\n\n```bash\nmake stop\n```\n\n## PR hygiene checklist (verify before finishing)\n\n- [ ] Every layer of the chosen matrix updated consistently; the consistency suite\n      and `CommandBuilderCoverageUnitTests` pass.\n- [ ] Every varargs parameter has its single-argument overload, and every\n      multi-key command with an `*Args` object uses `List<K>` (not varargs) with\n      fixed-arity overloads — mirrored across all flavors (or a\n      maintainer-approved justification from the plan).\n- [ ] `@since` on **every** new public element — including the nested `Builder`,\n      static factories, and fluent setters of new `*Args` classes; class-level\n      tags are not inherited (see\n      [.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md)). Javadoc written on\n      the sync interface and mirrored with flavor-appropriate `@return` phrasing.\n- [ ] Builder-validated preconditions documented on all flavors: `@param`\n      constraint phrases + `@throws IllegalArgumentException if …`.\n- [ ] No dead `CommandKeyword` constants; no keyword duplicating a `CommandType`.\n- [ ] Read-only commands registered in `ReadOnlyCommands` (+ count test bumped).\n- [ ] `mvn formatter:format` run; no formatting-only noise in the diff.\n- [ ] Tests at every applicable layer, gated with `@EnabledOnCommand`; missing\n      integration overload classes created where the command needs them.\n- [ ] `docs/` (MkDocs) updated — a new command or option **is** user-facing: add\n      a one-line entry to the current-release section of `docs/new-features.md`\n      (follow its existing \"Support for [`X`](redis.io link) …\" pattern), plus\n      any feature page the change affects.\n- [ ] `.env.vX.XX` image pin bumped if the feature needed a newer server build.\n- [ ] PR description states: server PR link, HLD link, gating choice and why,\n      behavior against older servers, and includes a showcase transcript. (Draft\n      with the [draft-pr-description](../draft-pr-description/SKILL.md) skill;\n      remember the guardrail — the agent never creates the PR itself.)\n- [ ] Docker test environment stopped (`make stop`) after the final verification\n      run.\n\n## Top pitfalls\n\n1. **Skipping the live verification / not checking RESP2 vs RESP3.** The reply\n   shape can differ between protocols; it determines the `CommandOutput` and the\n   reactive mapping. Confirm against a running server, don't assume.\n2. **Adding Args/response types after the interface edits** — every flavor\n   references them; the project won't compile. Types come first.\n3. **Editing only some flavors, or silencing the consistency suite** with a\n   deviations-registry entry instead of fixing the signature.\n4. **Forgetting a dispatch layer** — both `AbstractRedisAsyncCommands` *and*\n   `AbstractRedisReactiveCommands`, plus the Kotlin `*Impl.kt`.\n5. **Recreating the removed generator source files** under\n   `src/test/java/io/lettuce/core/api/` because an old reference PR touched them.\n6. **Wrong `CommandArgs` order or `CommandOutput`**, missing `@since`, missing\n   `@EnabledOnCommand` gating, or missing the read-only registry entry.\n7. **Letting a traced precedent override a written convention** — e.g. skipping\n   the single-argument overload because `sintercard(K...)` doesn't have one, or\n   stopping at a class-level `@since` because an old `*Args` class did. Older\n   code predates the rules; the conventions win.\n\n<!-- File: .agents/skills/writing-javadoc/SKILL.md (Tokens: ~842 | Category: anthropic-skill) -->\n---\nname: writing-javadoc\ndescription: Use when writing or editing Javadoc for Lettuce public API — new methods, classes, deprecations, or when a reviewer asks to fix or improve doc comments. Points to the full house ruleset in .agents/docs/javadoc.md (imperative method summaries, fixed tag order, @param/@return/@throws/@since/@deprecated house forms) and the rule that command-interface Javadoc is written on the sync interface first and mirrored to the other flavors. Trigger on \"write javadoc for\", \"document this method\", \"add a deprecation notice\", \"fix the doc comment\".\n---\n\n# Writing Javadoc for Lettuce\n\nThe complete, authoritative ruleset is **[.agents/docs/javadoc.md](../../../.agents/docs/javadoc.md)** —\nread it before writing. This skill is the operating checklist; the doc wins on any\ndetail.\n\n## Before you write\n\n- **Is it a command interface?** The command interfaces are hand-edited, with the\n  **sync** interface's Javadoc as the reference text. Write or fix the comment on\n  the sync method first, then mirror it to **every** flavor that declares the\n  method, with the flavor-appropriate `@return` phrasing (`RedisFuture`,\n  `Mono`/`Flux`, …):\n  - `src/main/java/io/lettuce/core/api/{sync,async,reactive}/`\n  - the Kotlin coroutine interfaces (`src/main/kotlin/…/api/coroutines/`)\n  - the cluster node-selection interfaces\n    (`src/main/java/io/lettuce/core/cluster/api/{sync,async}/NodeSelection…`)\n  - the Sentinel interfaces (`src/main/java/io/lettuce/core/sentinel/api/…` and\n    their Kotlin flavor)\n\n  Javadoc parity is **not** test-enforced — keep the flavors in sync by hand. See\n  [api-consistency.md](../../../.agents/docs/api-consistency.md).\n\n## The rules most often gotten wrong\n\n1. **Golden rule:** document the caller-facing contract (inputs, outputs, effects,\n   errors, nullability) — never implementation details or refactor rationale.\n2. **First sentence:** imperative verb for methods (\"Append…\", \"Get…\", \"Remove…\"),\n   noun phrase for types. Not \"This method…\". Ends in a clean period.\n3. **Tag order:** `@param` → `@return` → `@throws` → `@author` → `@since` → `@see`\n   → `@deprecated`.\n4. **`@param`** for every parameter (type params `<K>`/`<V>` first); state\n   nullability with the house phrases `must not be {@code null}.` / `can be {@code null}.`\n5. **`@return`** for non-void; describe the value and its meaningful states, not the\n   type.\n6. **`@since`** is a bare version — `@since 7.7` (see .agents/docs/javadoc.md for deriving\n   the version from the build).\n7. **`@deprecated`** — keep the `@Deprecated` annotation and the tag in sync; house\n   form: `@deprecated since <version>, use {@link Replacement} instead; scheduled for\n   removal in a future major release.`\n8. **`@author`** on types only — the *maintainer's* name (an agent must not invent\n   one); don't reorder existing authors.\n9. Use `{@code null}` for literals; `{@link}` only to types resolvable on the\n   compile classpath (else `{@code TypeName}`).\n\n## Verify, don't guess\n\nMatch the surrounding file, and consult `.agents/docs/javadoc.md` for any subtle case\nrather than inventing a rule. Javadoc lint is off in the build (`<doclint>none</doclint>`),\nso review is the only check — get it right by hand.\n\n<!-- File: .claude/rules/rules.md (Tokens: ~100 | Category: anthropic-skill) -->\n# Rules\n\nThis project's coding rules are maintained **tool-neutrally** in `.agents/rules/` so\nevery agent shares one source. This file is the Claude Code entry point into them.\n\nRead [`.agents/rules/rules.md`](../../.agents/rules/rules.md) for the index, and the\nrelevant rule set before editing — e.g. [`.agents/rules/java-rules.md`](../../.agents/rules/java-rules.md)\nwhen editing Java source (`src/**/*.java`).\n\n<!-- File: CLAUDE.md (Tokens: ~42 | Category: claude-rule) -->\n@AGENTS.md\n\n<!--\nThe shared operating manual lives in AGENTS.md (imported above). Reusable task\nprocedures live in .agents/skills/ and are referenced from AGENTS.md.\n-->\n</yakaai_skills>"}