{"owner":"redis","repo":"jedis","hasSkills":true,"totalSkillsCount":4,"totalTokensCount":9515,"categories":["anthropic-skill","root-instruction","command-prompt"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/extend-commands-api/SKILL.md","AGENTS.md",".claude/commands/createPRDescription.md",".agents/skills/creating-description-for-gh-pr/SKILL.md"],"skills":{".agents/skills/extend-commands-api/SKILL.md":"---\nname: extend-commands-api\ndescription: Add or extend Redis commands in the Jedis client API — a new core command, a family of new commands, an extension to an existing command's options, or a module command (Search/TimeSeries/JSON/Bloom). Gathers evidence (Redis server PR, HLD document), plans the full implementation matrix, then implements with unit and integration tests following Jedis maintainer conventions. Runs supervised (plan mode, user approval) by default, or unattended for automation such as the RedisClientsBot parity pipeline.\nmetadata:\n  modes: supervised, unattended\n---\n\n# Extend the Jedis Commands API\n\nImplement a new Redis command (or extend an existing one) in Jedis, following the\nconventions Jedis maintainers enforce in review.\n\n## Modes — read this first\n\nThis skill runs in one of two modes. The engineering rules in every section below\nare identical in both; only who answers questions and approves the plan differs.\n\n- **Supervised** (default): a human is in the session. Ask the questions, present\n  the plan in plan mode and wait for approval, as written below.\n- **Unattended**: no human will answer or approve anything, so never stop to wait.\n  Use it ONLY when the invoking prompt says `Mode: unattended` or the environment\n  has `CLIENT_SKILL_MODE=unattended`. Never switch to it on your own.\n\nIn unattended mode the invoking prompt supplies the inputs a human would give:\nthe HLD path (normally `./HLD.md`), the server PR reference, the test command, and\ntwo running servers from the `redislabs/client-libs-test` image it names, password-\nprotected like the local Docker env and without TLS: a standalone (`$REDIS_URL`,\npassword `$REDIS_STANDALONE_PASSWORD`, i.e. `foobared`) and a 6-node cluster with 3\nmasters and 3 replicas (`$REDIS_CLUSTER_URLS`, password `$REDIS_CLUSTER_PASSWORD`,\ni.e. `cluster`). `$REDIS_ENDPOINTS_CONFIG_PATH` already points at an `endpoints.json`\nthat describes them, passwords included, as `standalone0`, `modules-docker` (the\nstandalone; the image ships the modules) and `cluster-stable`. Jedis tests read that\nvariable (`TestEnvUtil.getEndpointsConfigPath`) and authenticate with the passwords in\nit, so the integration tests run against both servers unchanged. Wherever a step below has an **Unattended:**\nnote, follow the note:\n\n| Step | Supervised | Unattended |\n|---|---|---|\n| Phase 0.1 HLD | ask the user for the path | read the given HLD fully |\n| Phase 0.2 server PR | `gh`, asking to run it outside the sandbox if needed | no `gh` at all: the given PR reference, the HLD's facts, else the unauthenticated GitHub REST API |\n| Phase 0.3 environment / image tag | `make start`; ask for an image tag if the command is missing | no Docker: probe the given standalone and cluster; if the command is missing there, continue with gated tests |\n| Phase 0.4 redis-cli scenarios | run against `standalone0` | run against the given Redis URL; if `redis-cli` is unavailable, write them as unverified transcripts |\n| Phase 1 plan + approval | plan mode, ask before editing | write the plan into the final report, then implement it in the same session |\n| Running tests | `make start`, `mvn -B verify`, `make stop` | `mvn -B test` (unit), then the changed family's integration tests on standalone **and** cluster through `$REDIS_ENDPOINTS_CONFIG_PATH` (see Running the tests) |\n| An open design question | ask the user | take the HLD's choice (else the most consistent existing Jedis convention) and list it as an open question in the report |\n\nUnattended runs also follow these rules:\n- **Change files.** A run that ends without changes has failed. Stop without\n  editing only when implementing is impossible, and say exactly why in the report.\n- **Don't commit, push, or open a PR.** The automation that invoked you does that.\n  Don't edit `.github/` workflows, release/version files or `pom.xml` versions.\n  The formatter includes in `pom.xml` are the exception (see B.7).\n- **Finish with a report** covering, in plain text with no placeholders:\n  - what was implemented\n  - the decision-tree class (A–D) and the plan\n  - design decisions, and which layers intentionally did NOT change and why\n  - unit results; integration results with the test classes run on standalone and on\n    cluster; integration tests left gated, skipped, or out of scope\n  - steps skipped because they need a human or Docker\n  - open questions for maintainers\n\n  This feeds the PR description (see the PR hygiene checklist).\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 the user for the path to a\n   markdown file containing the High-Level Design for the command(s) (or confirm\n   that no HLD exists). If a path is given, read it fully — it is the primary\n   source for syntax, semantics, reply shape, and edge cases.\n   **Unattended:** don't ask; read the HLD path the invoking prompt gives.\n2. **Find the server-side PR in the `redis/redis` GitHub repo.**\n   **Unattended:** skip every `gh` command in this step: `gh auth status`, and the\n   `gh search` / `gh pr view` / `gh pr diff` block below. Don't ask for permission.\n   Use the server PR reference the invoking prompt gives. The HLD usually quotes\n   the syntax, the reply shapes and the first version. Fill any gaps from the\n   unauthenticated GitHub REST API (e.g.\n   `https://api.github.com/repos/redis/redis/pulls/<num>/files`, then the raw\n   `src/commands/*.json` file), and go on to \"From the server PR, extract\".\n\n   **Supervised:** first verify that `gh` works in the current (sandboxed)\n   environment:\n   ```bash\n   gh auth status\n   ```\n   Sandboxes often block access to credential files (`~/.netrc`, gh config), so\n   `gh` may report unauthenticated here even though it works on the user's\n   machine. If `gh auth status` fails or reports no authentication, **ask the\n   user for permission to run the `gh` commands outside the sandbox** (e.g. with\n   the sandbox override for these specific read-only commands), explaining that\n   `gh` cannot reach its credentials from inside the sandbox. Only if the user\n   declines (or `gh` is genuinely not logged in anywhere) fall back to the\n   unauthenticated GitHub REST API or fetching\n   `https://github.com/redis/redis/pulls?q=<command>`.\n\n   Then search for the PR that adds/extends the command on the server:\n   ```bash\n   gh search prs --repo redis/redis \"<COMMAND NAME>\" --limit 10\n   gh pr view <num> --repo redis/redis\n   gh pr diff <num> --repo redis/redis   # look at src/commands/*.json for exact syntax\n   ```\n   From the server PR,\n   extract: exact wire syntax (argument order and optionality), reply type per\n   RESP2/RESP3, error conditions, time complexity, and — critically — the **first\n   server version carrying the feature** (RC builds like `8.7.225` = Redis 8.8 RC\n   are used for test gating). Note whether the server marks the feature\n   *experimental / in preview*.\n3. **Verify the command exists on the \"next\" Redis OSS version.** Start the\n   integration environment using the **latest** `.env.vX.XX` file under\n   `src/test/resources/env/` (pick the highest version numerically, not\n   lexicographically — e.g. `8.10` > `8.8`):\n   ```bash\n   ls src/test/resources/env/.env.v* | sort -V | tail -1   # → e.g. .env.v8.10\n   make start version=8.10\n   ```\n   Then probe the `standalone0` endpoint. Read its connection details from\n   `src/test/resources/endpoints.json` (currently `redis://localhost:6379`,\n   password `foobared`) and check the target command via redis-cli:\n   ```bash\n   redis-cli -u redis://localhost:6379 -a foobared INFO server | grep redis_version\n   redis-cli -u redis://localhost:6379 -a foobared COMMAND INFO <COMMAND>   # non-empty → command exists\n   redis-cli -u redis://localhost:6379 -a foobared 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\n   EXTENDED command, additionally invoke it with the new syntax against a\n   scratch key and confirm the server accepts it (no `ERR syntax error` /\n   `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 for the target change. Interactively\n   **ask the user for a `redislabs/client-libs-test` image tag** that contains it\n   (e.g. an RC/edge/milestone build — tags are listed at\n   https://hub.docker.com/r/redislabs/client-libs-test/tags; you may check them\n   yourself and suggest candidates). Then restart the environment with that tag\n   and re-run the probes:\n   ```bash\n   make stop\n   make start CLIENT_LIBS_TEST_IMAGE_TAG=<tag>\n   ```\n   If the user has no tag to offer (or the probe still fails), report it and\n   proceed anyway: the implementation can continue, but integration tests will\n   be gated/skipped until a `redislabs/client-libs-test` image ships the change.\n   Keep the environment running for later test runs, or `make stop` if you\n   won't need it soon.\n   **Unattended:** no Docker, no `make start`/`make stop`, no image-tag question.\n   The servers already run the target image. Probe the standalone with the same\n   `INFO server` / `COMMAND INFO` / `COMMAND DOCS` checks (e.g.\n   `redis-cli -u \"$REDIS_URL\" COMMAND INFO <COMMAND>`; the URL carries the\n   credentials), and the cluster with `redis-cli -c -h \"$REDIS_CLUSTER_HOST\"\n   -p \"$REDIS_CLUSTER_START_PORT\" -a \"$REDIS_CLUSTER_PASSWORD\" --no-auth-warning CLUSTER INFO`\n   (expect `cluster_state:ok`). If the command is missing, continue anyway with\n   gated integration tests, and say so in the report.\n4. **Create redis-cli showcase test cases.** Once the command is available on\n   the running environment, derive a small set of redis-cli scenarios from the\n   HLD and the server PR and run them against `standalone0`. These serve two\n   purposes at once:\n   - **Smoke test**: prove the documented behavior actually holds on the target\n     server — happy path, each new option/keyword, reply shape (run with\n     `redis-cli -3` too if RESP3 replies differ), edge cases and error\n     conditions called out in the HLD (empty/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 that explains\n     WHY the command/option exists — what a user could not do (or could only do\n     awkwardly) before, and how the new API covers it. Prefer realistic data\n     over `foo`/`bar` (e.g. rate-limit counters for a bounded INCR, sensor\n     readings for a time-series aggregator).\n\n   Save the scenarios as a commented script in a scratch file (one block per\n   use-case: a one-line \"what this demonstrates\" comment, the redis-cli\n   commands, and the observed reply pasted back as comments). Carry this\n   material forward: it feeds the Phase 1 plan (as the explanation of the\n   feature and the source of expected values for tests), the Java test\n   assertions, and the PR description. If the command could not be made\n   available on any image (see step 3), still write the scenarios from the\n   HLD/PR as *expected* transcripts and mark them unverified.\n   **Unattended:** run the scenarios against `$REDIS_URL` instead of\n   `standalone0`. For a keyed command, also run one through the cluster. Always\n   pass the Redis command as arguments: a bare `redis-cli` waits on stdin and\n   hangs a headless run.\n   ```bash\n   redis-cli -u \"$REDIS_URL\" <COMMAND> <args...>\n   redis-cli -c -h \"$REDIS_CLUSTER_HOST\" -p \"$REDIS_CLUSTER_START_PORT\" \\\n     -a \"$REDIS_CLUSTER_PASSWORD\" --no-auth-warning <COMMAND> <args...>\n   ```\n   If `redis-cli` isn't installed, write them as unverified expected transcripts. Keep the scratch file out of the change; carry the\n   scenarios into the report instead.\n5. **Read `docs/integration-testing.md`** in the Jedis repo — it defines the test\n   environment, endpoint discovery, `*Test` vs `*IT` naming, and how to run tests.\n6. **Trace one analogous existing command** end-to-end in the codebase (same\n   command group, similar reply shape) so the plan mirrors real code, not\n   guesswork.\n7. Determine the `@since` version:\n   ```bash\n   mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n   ```\n   Strip `-SNAPSHOT` (e.g. `8.0.0-SNAPSHOT` → `@since 8.0`).\n\n## Phase 1 — Plan, then approval (supervised) or straight to implementation (unattended)\n\nUsing the evidence, classify the change with the decision tree below, enumerate\nthe exact file-by-file touch list, the test matrix, and the gating annotations.\nOpen the plan with a short \"what this feature enables\" section built from the\nredis-cli showcase scenarios (Phase 0 step 4), including one or two\nrepresentative command/reply transcripts, so the reader sees the use-case before\nthe file list.\n\n- **Supervised:** enter **plan mode**, present the plan to the user and\n  **explicitly ask permission to execute in auto-accept mode** before\n  implementing. Do not start editing files until the user approves.\n- **Unattended:** don't enter plan mode and don't wait. The automation's\n  approval was the merged HLD. Keep the plan for the final report, then\n  implement it right away, following the whole matrix exactly as a supervised\n  run would.\n\n## Decision tree — what kind of change is this?\n\n**A. Extension of an existing command that fits an existing params class**\n(e.g. new enum value / new option token):\n- Touch ONLY the params/model classes + tests. Do NOT touch command interfaces,\n  CommandObjects, UnifiedJedis, PipeliningBase, or Jedis — the existing\n  `IParams.addParams(CommandArguments)` delegation carries the new option through\n  automatically, for both String and binary surfaces.\n- If the option is a bare token: add an enum constant implementing `Rawable`\n  (bytes come from `SafeEncoder.encode(name())` — self-wiring).\n- If it's a new field: add a fluent setter returning `this`, extend\n  `addParams()`, and **update `equals`/`hashCode`** in sync.\n- If the reply shape grows (e.g. more array elements): extend the response model\n  class backward-compatibly — keep old constructors/getters working, add new\n  accessors, update the builder in the relevant `*BuilderFactory` to branch on\n  reply size. Jedis does not defensively copy response collections (stated\n  maintainer convention).\n\n**B. New core command(s)**  — the FULL matrix, every layer:\n1. `Protocol.java` — new `Command` enum constant(s); new `Keyword` constants for\n   sub-tokens, grouped under a `// <FEATURE> keywords` comment, alphabetical.\n   **Never add a Keyword that duplicates a token already carried by a dedicated\n   `Rawable` enum** (dead keywords get flagged in review).\n2. Command interfaces in `commands/`: `<Group>Commands`, `<Group>BinaryCommands`,\n   `<Group>PipelineCommands`, `<Group>PipelineBinaryCommands`. For a whole new\n   command *family*, create four new `<Family>*Commands` interfaces and add one\n   `extends` entry to `JedisCommands`, `JedisBinaryCommands`, `PipelineCommands`,\n   `PipelineBinaryCommands`.\n3. `CommandObjects` — String and byte[] factory methods side by side under a\n   `// <Feature> commands` section. `ClusterCommandObjects` needs overrides ONLY\n   for multi-key commands requiring slot checks; single-key commands need nothing.\n4. `UnifiedJedis` — one-line `@Override` delegating to\n   `executeCommand(commandObjects.xxx(...))`.\n5. `PipeliningBase` — `appendCommand(commandObjects.xxx(...))` returning\n   `Response<T>`.\n6. `Jedis` (legacy) — `checkIsInMultiOrPipeline(); connection.executeCommand(commandObjects.xxx(...))`.\n7. `pom.xml` — add every new file to the **formatter-maven-plugin includes** list\n   (this repo format-enforces an allowlist; new files must be registered).\n\n**C. Extension needing new overloads / a new params class** (hybrid): new methods\ngo through the full matrix of B; the option plumbing follows the params\nconventions below.\n\n**D. Module command (search / timeseries / json / bloom)**:\n- Module interfaces are **String-only** — there are NO `*BinaryCommands` variants\n  for modules; never create them.\n- Module commands/keywords live in the module protocol class\n  (`TimeSeriesProtocol.TimeSeriesCommand/TimeSeriesKeyword`,\n  `SearchProtocol.SearchCommand/SearchKeyword`, …), each an enum implementing\n  `ProtocolCommand`/`Rawable` with `SafeEncoder.encode()`-cached bytes.\n- Strongly prefer extending the module's params/builder classes over adding\n  interface methods — PRs #4504 and #4534 shipped whole features with ZERO\n  changes to interfaces, CommandObjects, UnifiedJedis, or PipeliningBase.\n- Search aggregation builders (`Reducer` subclasses) build a `List<Object>` via\n  `getOwnArgs()`; the parent computes `narg` automatically. Canonicalize clause\n  order at serialization time regardless of builder call order.\n- Module response parsing lives in the module's builder factory\n  (`TimeSeriesBuilderFactory`, `SearchBuilderFactory`); search aggregation results\n  are loosely typed and often absorb new reply content with no parsing changes.\n\n## Binary (byte[]) variant policy\n\n- **Core commands: full String/byte[] parity is mandatory** — all four\n  interfaces, paired CommandObjects methods, and all three clients. Reviewers\n  explicitly request binary integration tests too.\n- Only **keys and textual values** get `byte[]` overloads. Numeric/structural\n  args (`long index`, ranges, booleans, enums) stay identical.\n- **Params classes are shared, never duplicated**: one params class serves both\n  surfaces; if a param carries text, give the params class both `foo(String)` and\n  `foo(byte[])` setters (see `ArgrepParams`).\n- Return types mirror the key type (`String`→`byte[]`,\n  `List<String>`→`List<byte[]>`); count/index/model results stay shared.\n- **Modules: no binary variants at all.**\n\n## Params class conventions (`params/`)\n\n- Implement `IParams` with `addParams(CommandArguments args)`. Fluent setters\n  return `this`; provide a static factory named after the class\n  (`increxParams()`, `rangeParams()`).\n- Do NOT extend another command's params hierarchy. If two overloads share\n  options but differ in a typed field (long vs double), create a self-typed\n  abstract base: `BaseFooParams<T extends BaseFooParams<T>>` with a private\n  `self()` cast, plus two concrete subclasses — compile-time type safety over a\n  polymorphic single class.\n- Implement **`equals`/`hashCode`** (needed for Mockito matching in mocked tests)\n  and keep them in sync with every new field. `toString` is not required on\n  params classes.\n- Validate eagerly in setters with descriptive `IllegalArgumentException` /\n  `IllegalStateException` messages, consistent wording across sibling classes\n  (e.g. \"Aggregators must be non-null and non-empty\"). Required-config checks may\n  run at serialization time. No client-side server-version checks — an old server\n  returning an error is acceptable and should be noted in the PR description.\n- Expiration-style option groups are single-slot, last-wins (mirror `SetParams`).\n- Document the wire-emission order in javadoc and lock it down in unit tests.\n\n## Response mapping (`BuilderFactory` / `resps/`)\n\n- **Reuse existing generic builders whenever the reply maps to a standard shape**\n  (`LONG_LIST`, `DOUBLE_LIST`, `STRING`, `ENCODED_OBJECT_MAP`, …). Maintainers\n  actively reject bespoke response classes for 2-element arrays and the like.\n- Reuse `redis.clients.jedis.util.KeyValue<K,V>` for pair replies.\n- When a model class is genuinely needed (map-shaped INFO replies), put it in\n  `resps/`: public `String` constants for reply field names, a\n  `Map<String,Object>`-taking constructor, typed getters, plus a raw-map accessor\n  for forward compatibility; subclass for FULL/extended variants.\n- Nullable numeric replies are boxed `Long` — **never `Optional`/`OptionalLong`**\n  in the command API; consistency with existing signatures (e.g. `zrank`) wins.\n- Multi-mode commands (one wire command, several reply types by mode) are split\n  into distinctly-named typed Java methods (`aropAggregate`/`aropBitwise`/`aropCount`)\n  rather than one polymorphic method. But mode selected by argument type uses\n  **overloads of one name** (`increx(key, long, …)` / `increx(key, double, …)`),\n  with the mode keyword (BYINT/BYFLOAT) implied by the overload.\n\n## Encoding & enum rules\n\n- String ↔ bytes: `SafeEncoder.encode()` — never `String.getBytes()`.\n- Numeric → bytes: `Protocol.toByteArray()`.\n- Token-valued argument enums live in `args/` and implement `Rawable` with\n  `raw = SafeEncoder.encode(name())` cached in the constructor.\n- Compose multi-token byte values (e.g. comma-joined lists) at the byte level.\n\n## Javadoc, `@since`, `@Experimental`\n\n- **Interface methods only** carry javadoc; implementations (`UnifiedJedis`,\n  `Jedis`, `PipeliningBase`, `CommandObjects`) carry none.\n- String-interface javadoc: `<b><a href=\"https://redis.io/commands/xxx\">XXX\n  Command</a></b>`, prose semantics, `Time complexity: O(...)`, `@param`,\n  `@return`, `@since <version>`. Binary variants get short javadoc with\n  `@see` to the String variant. Pipeline variants: one line — \"Pipeline variant\n  of {@link ...}\" + `@since`.\n- `@since` on every new public method and class, computed from `pom.xml`.\n- If the server feature is **preview/experimental**, annotate ALL new public API\n  (interfaces, methods, params, args, resps) with\n  `redis.clients.jedis.annots.Experimental` and label the PR `experimental`.\n  Do NOT use `@Experimental` for stable GA features.\n\n## Test matrix — what to write\n\nFollow `docs/integration-testing.md` for environment, layout, and naming. The\nestablished per-command test layers (write all that apply):\n\n1. **Params unit test** (`params/FooParamsTest`, plain `*Test`, no Redis):\n   `@Nested` groups (`ValidationTests`, `AddParamsTests`, overload-equivalence),\n   asserting exact wire args and order with\n   `redis.clients.jedis.util.CommandArgumentsMatchers`\n   (`hasArgumentCount`, `hasArguments`) and `RawableFactory.from(...)`; also test\n   the `equals`/`hashCode` contract.\n2. **Mocked delegation tests** (unit, Mockito): add `@Mock CommandObject<T>`\n   fields to `MockedCommandObjectsTestBase` (name them by TYPE, e.g.\n   `listLongCommandObject` — reuse existing ones when the type matches), then\n   `when/verify` tests in `mocked/unified/UnifiedJedis<Group>CommandsTest` and\n   `mocked/pipeline/PipeliningBase<Group>CommandsTest`.\n3. **Unified integration tests**: add methods to the existing abstract\n   `commands/unified/<Group>CommandsTestBase` when one exists; for a new family,\n   create `<Family>CommandsTestBase extends UnifiedJedisCommandsTestBase` plus\n   thin per-topology runners — standalone\n   (`RedisClientCommandsTestHelper.getClient(protocol)`) and cluster\n   (`ClusterCommandsTestHelper.getCleanCluster(protocol)`). **New concrete\n   integration classes MUST be named `*IT`** — never `*IntegrationTest`, never\n   `@Tag(\"integration\")` on new classes.\n4. **Cluster handling**: multi-key commands need cluster test overrides using\n   hash-tagged keys (`s1{:}`) so keys share a slot; tests semantically\n   incompatible with cluster get `@Test @Override` + `@Disabled(\"<reason>\")`\n   empty bodies.\n5. **Legacy `Jedis` coverage**: extend `commands/jedis/<Group>*CommandsTest`\n   (which extends `JedisCommandsTestBase`) — can be minimal (smoke/missing-key)\n   when the unified base covers behavior fully. Binary variants get their own\n   integration tests (reviewers ask for these explicitly).\n\nCross-cutting test conventions:\n- **RESP2/RESP3** comes free: test bases are `@ParameterizedClass` +\n  `@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")`\n  (or `#jedisRespVersions` for legacy) — no per-test work; just make sure new\n  classes inherit the right base.\n- **Gating**: command already released → `@SinceRedisVersion(\"<RC build>\")`\n  (e.g. `\"8.7.225\"` for 8.8) at the shared base-class level ONCE — do not repeat\n  it on subclasses (maintainers remove redundant ones). Command not yet in any\n  GA server → `@EnabledOnCommand(\"<COMMAND>\")` (capability probe via COMMAND\n  INFO). Module presence as precondition → `assumeTrue(hasCommand(...))` probes.\n  Environment exclusions → `@ConditionalOnEnv(value = TestEnvUtil.ENV_...,\n  enabled = false)` (e.g. skip Redis Enterprise for brand-new features).\n- Cover the **whole family the option touches** (store + non-store variants,\n  with/without optional args, standalone + cluster), asserting real semantics,\n  not just \"no error\". Avoid brittle assertions (no hard-coded `hashCode()`\n  literals).\n\n## Running the tests\n\nJedis pins the **CI JDK (Java 8)** for the test build — check\n`.github/workflows/` for the exact version and use a matching local JDK. Verify\nwith `java -version` before running anything; a newer JDK may fail the build or\nsilently produce the wrong bytecode target.\n\n- Unit tests (Surefire, no Redis): `mvn -B test`, or `mvn -Dtest=FooParamsTest test`.\n- Integration tests (Failsafe) need the Docker env and the `verify` lifecycle:\n  ```bash\n  make start version=8.8        # pick the version that carries the new command\n  mvn -B verify                 # or: mvn -Dtest=FooIT verify\n  make stop\n  ```\n  Never run integration coverage via `mvn test`/`surefire:test` — Failsafe owns\n  `*IT` classes. If a brand-new command isn't in any published\n  `redislabs/client-libs-test` tag yet, say so: the integration tests will be\n  skipped/gated (that is expected and acceptable — `@EnabledOnCommand` /\n  `@SinceRedisVersion` handle it), but they must still be written and compile.\n- **Unattended:** no `make start`/`make stop`. The servers are already running\n  and `$REDIS_ENDPOINTS_CONFIG_PATH` points the tests at them. Run:\n  1. Unit tests: `mvn -B test`, or the test command the invoking prompt gives.\n  2. The changed family's integration tests on **both** topologies, through\n     Failsafe only. Two kinds of runner exist:\n     - existing core families: `@Tag(\"integration\")` `*Test` classes (Failsafe's\n       `it-tagged` execution), namely the unified standalone runner\n       `RedisClient<Family>CommandsTest`, the cluster runner\n       `Cluster<Family>CommandsTest` and the legacy `commands/jedis/<Family>CommandsTest`\n     - module and new families: `*IT` classes (the `it-suffix` execution), a\n       standalone runner in `commands/unified/client/<module>/` (e.g.\n       `BloomRedisClientCommandsIT`, on `modules-docker`) and a cluster runner in\n       `commands/unified/cluster/<module>/` (e.g. `BloomClusterCommandsIT`, on\n       `cluster-stable`; the cluster image loads the modules too)\n     \n     Select them with one wildcard that covers both kinds and every topology, plus\n     an explicit name for any new runner that doesn't match it:\n     ```bash\n     mvn -B -DskipUnitTests=true -Dit.failIfNoSpecifiedTests=false \\\n       -Dit.test='*<Family>*Commands*' verify\n     ```\n     `-Dit.failIfNoSpecifiedTests=false` is needed because each Failsafe execution\n     sees the same filter and one of them usually matches nothing. That also means\n     `verify` can pass having run **no** test, so don't trust the exit code alone.\n  3. Prove it ran: read `target/failsafe-reports/*.txt` (one file per class, named\n     by its fully qualified class name) and list every class that ran with its counts.\n     A class in package `redis.clients.jedis.commands.unified.cluster` (any\n     sub-package) is a cluster run; anything else is standalone. Don't go by the\n     name prefix: module runners are `<Family>ClusterCommandsIT`, and one is\n     `FTHybridCommandsClusterIT`. At least one standalone class and one cluster class\n     must report `Tests run:` > 0 for the changed commands. This applies to module\n     families too. If either topology ran nothing, fix the filter and rerun. Don't\n     report success.\n  \n  Tests that need endpoints the file doesn't have (sentinel, TLS, ACL users,\n  `cluster-unbound`) are out of scope: list them as not run. Report passed /\n  failed / skipped per topology, and the classes run.\n\n## PR hygiene checklist (verify before finishing)\n\n- [ ] Every layer of the chosen matrix updated consistently (a return-type change\n      must propagate across all 4 interfaces × 3 implementations × all test layers).\n- [ ] New files added to the `pom.xml` formatter-plugin includes.\n- [ ] `@since` (and `@Experimental` if preview) on all new public API.\n- [ ] No unused imports, no wildcard imports, no dead `Protocol.Keyword` constants.\n- [ ] `equals`/`hashCode` on params updated and unit-tested.\n- [ ] `docs/` updated if user-facing behavior/configuration changed; migration\n      guide entry if anything breaks.\n- [ ] PR description states: server PR link, version gate choice and why, which\n      layers intentionally did NOT change and why, and behavior against older\n      servers. (**Unattended:** put these in the final report; the automation\n      builds the PR description from it.)\n","AGENTS.md":"# Jedis Coding Conventions\nJedis is a synchronous Java client for Redis, shipped as a library on Maven Central as `redis.clients:jedis`.\nIt covers standalone, cluster, sentinel, and multi-db setups — with pipelining, transactions, pub/sub, and Redis Stack modules — and targets Java 8 for source/binary compatibility.\n\n## General Principles\n- **Maintain JDK 8 compatibility.** All production and test code must compile and run on JDK 8.\n- **Keep documentation up to date.** If a change affects user-facing behavior, configuration, or the public API, update the relevant pages under [`docs/`](docs) (MkDocs).\n- **Add or update tests.** Every bug fix should include a regression test, and every new feature should include appropriate tests. Follow the conventions in [`docs/integration-testing.md`](docs/integration-testing.md) for choosing between unit and integration tests.\n- **Document breaking changes.** Any breaking change must be documented in the appropriate migration guide under [`docs/migration-guides/`](docs/migration-guides).\n- **Record behavior changes in the release notes.** If a change alters user-visible behavior without breaking the API, add an entry to the running notes for the next release under [`docs/release-notes/`](docs/release-notes) in the same PR, following the entry format in that file.\n- **Follow the contribution guidelines.** Ensure all changes comply with the project rules in [`.github/CONTRIBUTING.md`](.github/CONTRIBUTING.md).\n- **Request approval before adding dependencies.** Do not introduce new runtime, test, or build dependencies without explicit confirmation.\n- **Review-friendly changes.** Keep commits and pull requests focused and logically grouped. Separate feature or bug-fix changes from refactoring, formatting, and other mechanical changes whenever practical.\n\n## Development Environment\n- **Use the CI JDK version.** Build and test using the same JDK version configured in the GitHub Actions workflows. Check `.github/workflows/` (or `docs/integration-testing.md`) to determine the required version before reproducing CI failures.\n\n## Build & Test Commands\n- `make start version=8.6` + `mvn clean verify` + `make stop` — full run against\n  the Docker Redis env (`make test` does all three).\n- `mvn test` — unit tests (no Redis); `mvn -Dtest=ClassName test` for one class.\n\n## Implementation Guidelines\n### Code Style\n- Keep comments short and informative. Comment **why**, not **what**. Avoid comments that simply restate the code.\n- Remove unused imports. Do not use wildcard imports. Avoid fully qualified class names unless necessary to resolve naming conflicts.\n- **Annotate all new public API with `@since`.** Determine the version from the current build by running:\n  ```sh\n  mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n  ```\n  (or by reading the top-level `<version>` in `pom.xml`), then remove the `-SNAPSHOT` suffix. For example, `8.0.0-SNAPSHOT` becomes `@since 8.0`.\n\n### Test Conventions\n- Name new unit test classes using the `*Test` convention.\n- Name new integration test classes using the `*IT` convention. Do not use the `integration` tag for new tests.\n\n## Architecture\n\nSee [`docs/redis-client-components-overview.md`](docs/redis-client-components-overview.md)\nfor a high-level walkthrough (executors, providers, builders, and command execution flows).\n\n`UnifiedJedis` is the core client. It implements the command interfaces and\ndelegates to three collaborators:\n\n- `ConnectionProvider` (`providers/`) — obtains connections: pooled, cluster,\n  sentinel, or multi-db.\n- `CommandExecutor` (`executors/`) — runs a command: simple, retry, cluster\n  routing, or failover.\n- `CommandObjects` — factory building a typed `CommandObject<T>` per command.\n\n**Modern clients** extend `UnifiedJedis` and are built through\n`AbstractClientBuilder` subclasses (`builders/`): `RedisClient`,\n`RedisClusterClient`, `RedisSentinelClient`, `MultiDbClient`. A new client\noverrides `createDefaultConnectionProvider()`, `createDefaultCommandExecutor()`,\n`createClient()`, and `validateSpecificConfiguration()` (run before `build()`).\n\n**Legacy client:** `Jedis` is the single-connection client (pooling via\n`JedisPool`), still supported. `JedisPool`, `JedisCluster`, and\n`JedisSentinelPool` are `@Deprecated` in favor of the builder-based clients.\n\n**Feature modules** expose dedicated command interfaces under\n`redis.clients.jedis.<module>`: `search`, `json`, `bloom`, `timeseries`, plus\n`csc` (client-side caching) and `mcf` (multi-db / failover).\n\n## Conventions\n\n**Adding or changing a command** — trace an existing command first, then update\nthe full matrix so all surfaces stay in sync:\n\n1. **Command interfaces** (`commands/`): `<Group>Commands` and\n   `<Group>BinaryCommands` (String vs `byte[]`), each with its `<Group>Pipeline…`\n   variant.\n2. **`CommandObjects`** — build the args and a response `Builder<T>`, e.g.\n   `new CommandObject<>(commandArguments(GET).key(key), BuilderFactory.STRING)`.\n   `ClusterCommandObjects` overrides these to enforce cluster constraints.\n3. **`UnifiedJedis`** and **`PipeliningBase`** — the execution entry points.\n4. **`Jedis`** — add it here too, for backward compatibility.\n\n**Encoding:**\n- String ↔ bytes: `SafeEncoder.encode()` — never `String.getBytes()` (breaks GBK).\n- Numeric → bytes: `Protocol.toByteArray()`.\n\n## Agent Skills\n\nTask procedures live in [`.agents/skills/`](.agents/skills) (also reachable as\n`.claude/skills/`). Read the matching `SKILL.md` before that kind of work:\n\n- `extend-commands-api` — add or extend a Redis command end-to-end (evidence, decision tree, full\n  String/binary/pipeline matrix, tests), supervised or unattended.\n- `create-implementation-plan-for-redis-api-change` — turn a shared client HLD into Jedis's reviewed\n  implementation plan (API, files, ordered steps, test matrix) without touching sources; read-only,\n  writes one plan file; conventions come from `extend-commands-api`.\n- `creating-description-for-gh-pr` — draft a PR title and description from the diff between two\n  local branches.\n",".claude/commands/createPRDescription.md":"---\ndescription: Generate a PR title and description from the diff between two local branches, using the creating-description-for-gh-pr skill, and save to prDescription.md\nargument-hint: [base-branch] [target-branch]\n---\n\nUse the \"creating-description-for-gh-pr\" skill to generate a PR title and description.\n\nArguments provided to this command: base = `$1`, target = `$2` (either may be empty).\n\nPass this context into the skill's branch resolution logic (Step 1). Since these are positional arguments, only three states are possible:\n- Both `$1` and `$2` given → `$1` is the explicit base branch, `$2` is the explicit target branch.\n- Only `$1` given (`$2` empty) → `$1` is the explicit base branch; let the skill default the target branch.\n- Neither given (both empty) → let the skill default both base and target branches.\n\n`$1` cannot be empty while `$2` is non-empty — do not handle that case.\n\nDo not duplicate or reinterpret the skill's instructions; follow them as written, including the output structure, the writing-style constraints, and the save/confirm steps.\n",".agents/skills/creating-description-for-gh-pr/SKILL.md":"---\nname: creating-description-for-gh-pr\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.\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## 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"},"files":{".agents/skills/extend-commands-api/SKILL.md":"---\nname: extend-commands-api\ndescription: Add or extend Redis commands in the Jedis client API — a new core command, a family of new commands, an extension to an existing command's options, or a module command (Search/TimeSeries/JSON/Bloom). Gathers evidence (Redis server PR, HLD document), plans the full implementation matrix, then implements with unit and integration tests following Jedis maintainer conventions. Runs supervised (plan mode, user approval) by default, or unattended for automation such as the RedisClientsBot parity pipeline.\nmetadata:\n  modes: supervised, unattended\n---\n\n# Extend the Jedis Commands API\n\nImplement a new Redis command (or extend an existing one) in Jedis, following the\nconventions Jedis maintainers enforce in review.\n\n## Modes — read this first\n\nThis skill runs in one of two modes. The engineering rules in every section below\nare identical in both; only who answers questions and approves the plan differs.\n\n- **Supervised** (default): a human is in the session. Ask the questions, present\n  the plan in plan mode and wait for approval, as written below.\n- **Unattended**: no human will answer or approve anything, so never stop to wait.\n  Use it ONLY when the invoking prompt says `Mode: unattended` or the environment\n  has `CLIENT_SKILL_MODE=unattended`. Never switch to it on your own.\n\nIn unattended mode the invoking prompt supplies the inputs a human would give:\nthe HLD path (normally `./HLD.md`), the server PR reference, the test command, and\ntwo running servers from the `redislabs/client-libs-test` image it names, password-\nprotected like the local Docker env and without TLS: a standalone (`$REDIS_URL`,\npassword `$REDIS_STANDALONE_PASSWORD`, i.e. `foobared`) and a 6-node cluster with 3\nmasters and 3 replicas (`$REDIS_CLUSTER_URLS`, password `$REDIS_CLUSTER_PASSWORD`,\ni.e. `cluster`). `$REDIS_ENDPOINTS_CONFIG_PATH` already points at an `endpoints.json`\nthat describes them, passwords included, as `standalone0`, `modules-docker` (the\nstandalone; the image ships the modules) and `cluster-stable`. Jedis tests read that\nvariable (`TestEnvUtil.getEndpointsConfigPath`) and authenticate with the passwords in\nit, so the integration tests run against both servers unchanged. Wherever a step below has an **Unattended:**\nnote, follow the note:\n\n| Step | Supervised | Unattended |\n|---|---|---|\n| Phase 0.1 HLD | ask the user for the path | read the given HLD fully |\n| Phase 0.2 server PR | `gh`, asking to run it outside the sandbox if needed | no `gh` at all: the given PR reference, the HLD's facts, else the unauthenticated GitHub REST API |\n| Phase 0.3 environment / image tag | `make start`; ask for an image tag if the command is missing | no Docker: probe the given standalone and cluster; if the command is missing there, continue with gated tests |\n| Phase 0.4 redis-cli scenarios | run against `standalone0` | run against the given Redis URL; if `redis-cli` is unavailable, write them as unverified transcripts |\n| Phase 1 plan + approval | plan mode, ask before editing | write the plan into the final report, then implement it in the same session |\n| Running tests | `make start`, `mvn -B verify`, `make stop` | `mvn -B test` (unit), then the changed family's integration tests on standalone **and** cluster through `$REDIS_ENDPOINTS_CONFIG_PATH` (see Running the tests) |\n| An open design question | ask the user | take the HLD's choice (else the most consistent existing Jedis convention) and list it as an open question in the report |\n\nUnattended runs also follow these rules:\n- **Change files.** A run that ends without changes has failed. Stop without\n  editing only when implementing is impossible, and say exactly why in the report.\n- **Don't commit, push, or open a PR.** The automation that invoked you does that.\n  Don't edit `.github/` workflows, release/version files or `pom.xml` versions.\n  The formatter includes in `pom.xml` are the exception (see B.7).\n- **Finish with a report** covering, in plain text with no placeholders:\n  - what was implemented\n  - the decision-tree class (A–D) and the plan\n  - design decisions, and which layers intentionally did NOT change and why\n  - unit results; integration results with the test classes run on standalone and on\n    cluster; integration tests left gated, skipped, or out of scope\n  - steps skipped because they need a human or Docker\n  - open questions for maintainers\n\n  This feeds the PR description (see the PR hygiene checklist).\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 the user for the path to a\n   markdown file containing the High-Level Design for the command(s) (or confirm\n   that no HLD exists). If a path is given, read it fully — it is the primary\n   source for syntax, semantics, reply shape, and edge cases.\n   **Unattended:** don't ask; read the HLD path the invoking prompt gives.\n2. **Find the server-side PR in the `redis/redis` GitHub repo.**\n   **Unattended:** skip every `gh` command in this step: `gh auth status`, and the\n   `gh search` / `gh pr view` / `gh pr diff` block below. Don't ask for permission.\n   Use the server PR reference the invoking prompt gives. The HLD usually quotes\n   the syntax, the reply shapes and the first version. Fill any gaps from the\n   unauthenticated GitHub REST API (e.g.\n   `https://api.github.com/repos/redis/redis/pulls/<num>/files`, then the raw\n   `src/commands/*.json` file), and go on to \"From the server PR, extract\".\n\n   **Supervised:** first verify that `gh` works in the current (sandboxed)\n   environment:\n   ```bash\n   gh auth status\n   ```\n   Sandboxes often block access to credential files (`~/.netrc`, gh config), so\n   `gh` may report unauthenticated here even though it works on the user's\n   machine. If `gh auth status` fails or reports no authentication, **ask the\n   user for permission to run the `gh` commands outside the sandbox** (e.g. with\n   the sandbox override for these specific read-only commands), explaining that\n   `gh` cannot reach its credentials from inside the sandbox. Only if the user\n   declines (or `gh` is genuinely not logged in anywhere) fall back to the\n   unauthenticated GitHub REST API or fetching\n   `https://github.com/redis/redis/pulls?q=<command>`.\n\n   Then search for the PR that adds/extends the command on the server:\n   ```bash\n   gh search prs --repo redis/redis \"<COMMAND NAME>\" --limit 10\n   gh pr view <num> --repo redis/redis\n   gh pr diff <num> --repo redis/redis   # look at src/commands/*.json for exact syntax\n   ```\n   From the server PR,\n   extract: exact wire syntax (argument order and optionality), reply type per\n   RESP2/RESP3, error conditions, time complexity, and — critically — the **first\n   server version carrying the feature** (RC builds like `8.7.225` = Redis 8.8 RC\n   are used for test gating). Note whether the server marks the feature\n   *experimental / in preview*.\n3. **Verify the command exists on the \"next\" Redis OSS version.** Start the\n   integration environment using the **latest** `.env.vX.XX` file under\n   `src/test/resources/env/` (pick the highest version numerically, not\n   lexicographically — e.g. `8.10` > `8.8`):\n   ```bash\n   ls src/test/resources/env/.env.v* | sort -V | tail -1   # → e.g. .env.v8.10\n   make start version=8.10\n   ```\n   Then probe the `standalone0` endpoint. Read its connection details from\n   `src/test/resources/endpoints.json` (currently `redis://localhost:6379`,\n   password `foobared`) and check the target command via redis-cli:\n   ```bash\n   redis-cli -u redis://localhost:6379 -a foobared INFO server | grep redis_version\n   redis-cli -u redis://localhost:6379 -a foobared COMMAND INFO <COMMAND>   # non-empty → command exists\n   redis-cli -u redis://localhost:6379 -a foobared 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\n   EXTENDED command, additionally invoke it with the new syntax against a\n   scratch key and confirm the server accepts it (no `ERR syntax error` /\n   `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 for the target change. Interactively\n   **ask the user for a `redislabs/client-libs-test` image tag** that contains it\n   (e.g. an RC/edge/milestone build — tags are listed at\n   https://hub.docker.com/r/redislabs/client-libs-test/tags; you may check them\n   yourself and suggest candidates). Then restart the environment with that tag\n   and re-run the probes:\n   ```bash\n   make stop\n   make start CLIENT_LIBS_TEST_IMAGE_TAG=<tag>\n   ```\n   If the user has no tag to offer (or the probe still fails), report it and\n   proceed anyway: the implementation can continue, but integration tests will\n   be gated/skipped until a `redislabs/client-libs-test` image ships the change.\n   Keep the environment running for later test runs, or `make stop` if you\n   won't need it soon.\n   **Unattended:** no Docker, no `make start`/`make stop`, no image-tag question.\n   The servers already run the target image. Probe the standalone with the same\n   `INFO server` / `COMMAND INFO` / `COMMAND DOCS` checks (e.g.\n   `redis-cli -u \"$REDIS_URL\" COMMAND INFO <COMMAND>`; the URL carries the\n   credentials), and the cluster with `redis-cli -c -h \"$REDIS_CLUSTER_HOST\"\n   -p \"$REDIS_CLUSTER_START_PORT\" -a \"$REDIS_CLUSTER_PASSWORD\" --no-auth-warning CLUSTER INFO`\n   (expect `cluster_state:ok`). If the command is missing, continue anyway with\n   gated integration tests, and say so in the report.\n4. **Create redis-cli showcase test cases.** Once the command is available on\n   the running environment, derive a small set of redis-cli scenarios from the\n   HLD and the server PR and run them against `standalone0`. These serve two\n   purposes at once:\n   - **Smoke test**: prove the documented behavior actually holds on the target\n     server — happy path, each new option/keyword, reply shape (run with\n     `redis-cli -3` too if RESP3 replies differ), edge cases and error\n     conditions called out in the HLD (empty/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 that explains\n     WHY the command/option exists — what a user could not do (or could only do\n     awkwardly) before, and how the new API covers it. Prefer realistic data\n     over `foo`/`bar` (e.g. rate-limit counters for a bounded INCR, sensor\n     readings for a time-series aggregator).\n\n   Save the scenarios as a commented script in a scratch file (one block per\n   use-case: a one-line \"what this demonstrates\" comment, the redis-cli\n   commands, and the observed reply pasted back as comments). Carry this\n   material forward: it feeds the Phase 1 plan (as the explanation of the\n   feature and the source of expected values for tests), the Java test\n   assertions, and the PR description. If the command could not be made\n   available on any image (see step 3), still write the scenarios from the\n   HLD/PR as *expected* transcripts and mark them unverified.\n   **Unattended:** run the scenarios against `$REDIS_URL` instead of\n   `standalone0`. For a keyed command, also run one through the cluster. Always\n   pass the Redis command as arguments: a bare `redis-cli` waits on stdin and\n   hangs a headless run.\n   ```bash\n   redis-cli -u \"$REDIS_URL\" <COMMAND> <args...>\n   redis-cli -c -h \"$REDIS_CLUSTER_HOST\" -p \"$REDIS_CLUSTER_START_PORT\" \\\n     -a \"$REDIS_CLUSTER_PASSWORD\" --no-auth-warning <COMMAND> <args...>\n   ```\n   If `redis-cli` isn't installed, write them as unverified expected transcripts. Keep the scratch file out of the change; carry the\n   scenarios into the report instead.\n5. **Read `docs/integration-testing.md`** in the Jedis repo — it defines the test\n   environment, endpoint discovery, `*Test` vs `*IT` naming, and how to run tests.\n6. **Trace one analogous existing command** end-to-end in the codebase (same\n   command group, similar reply shape) so the plan mirrors real code, not\n   guesswork.\n7. Determine the `@since` version:\n   ```bash\n   mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n   ```\n   Strip `-SNAPSHOT` (e.g. `8.0.0-SNAPSHOT` → `@since 8.0`).\n\n## Phase 1 — Plan, then approval (supervised) or straight to implementation (unattended)\n\nUsing the evidence, classify the change with the decision tree below, enumerate\nthe exact file-by-file touch list, the test matrix, and the gating annotations.\nOpen the plan with a short \"what this feature enables\" section built from the\nredis-cli showcase scenarios (Phase 0 step 4), including one or two\nrepresentative command/reply transcripts, so the reader sees the use-case before\nthe file list.\n\n- **Supervised:** enter **plan mode**, present the plan to the user and\n  **explicitly ask permission to execute in auto-accept mode** before\n  implementing. Do not start editing files until the user approves.\n- **Unattended:** don't enter plan mode and don't wait. The automation's\n  approval was the merged HLD. Keep the plan for the final report, then\n  implement it right away, following the whole matrix exactly as a supervised\n  run would.\n\n## Decision tree — what kind of change is this?\n\n**A. Extension of an existing command that fits an existing params class**\n(e.g. new enum value / new option token):\n- Touch ONLY the params/model classes + tests. Do NOT touch command interfaces,\n  CommandObjects, UnifiedJedis, PipeliningBase, or Jedis — the existing\n  `IParams.addParams(CommandArguments)` delegation carries the new option through\n  automatically, for both String and binary surfaces.\n- If the option is a bare token: add an enum constant implementing `Rawable`\n  (bytes come from `SafeEncoder.encode(name())` — self-wiring).\n- If it's a new field: add a fluent setter returning `this`, extend\n  `addParams()`, and **update `equals`/`hashCode`** in sync.\n- If the reply shape grows (e.g. more array elements): extend the response model\n  class backward-compatibly — keep old constructors/getters working, add new\n  accessors, update the builder in the relevant `*BuilderFactory` to branch on\n  reply size. Jedis does not defensively copy response collections (stated\n  maintainer convention).\n\n**B. New core command(s)**  — the FULL matrix, every layer:\n1. `Protocol.java` — new `Command` enum constant(s); new `Keyword` constants for\n   sub-tokens, grouped under a `// <FEATURE> keywords` comment, alphabetical.\n   **Never add a Keyword that duplicates a token already carried by a dedicated\n   `Rawable` enum** (dead keywords get flagged in review).\n2. Command interfaces in `commands/`: `<Group>Commands`, `<Group>BinaryCommands`,\n   `<Group>PipelineCommands`, `<Group>PipelineBinaryCommands`. For a whole new\n   command *family*, create four new `<Family>*Commands` interfaces and add one\n   `extends` entry to `JedisCommands`, `JedisBinaryCommands`, `PipelineCommands`,\n   `PipelineBinaryCommands`.\n3. `CommandObjects` — String and byte[] factory methods side by side under a\n   `// <Feature> commands` section. `ClusterCommandObjects` needs overrides ONLY\n   for multi-key commands requiring slot checks; single-key commands need nothing.\n4. `UnifiedJedis` — one-line `@Override` delegating to\n   `executeCommand(commandObjects.xxx(...))`.\n5. `PipeliningBase` — `appendCommand(commandObjects.xxx(...))` returning\n   `Response<T>`.\n6. `Jedis` (legacy) — `checkIsInMultiOrPipeline(); connection.executeCommand(commandObjects.xxx(...))`.\n7. `pom.xml` — add every new file to the **formatter-maven-plugin includes** list\n   (this repo format-enforces an allowlist; new files must be registered).\n\n**C. Extension needing new overloads / a new params class** (hybrid): new methods\ngo through the full matrix of B; the option plumbing follows the params\nconventions below.\n\n**D. Module command (search / timeseries / json / bloom)**:\n- Module interfaces are **String-only** — there are NO `*BinaryCommands` variants\n  for modules; never create them.\n- Module commands/keywords live in the module protocol class\n  (`TimeSeriesProtocol.TimeSeriesCommand/TimeSeriesKeyword`,\n  `SearchProtocol.SearchCommand/SearchKeyword`, …), each an enum implementing\n  `ProtocolCommand`/`Rawable` with `SafeEncoder.encode()`-cached bytes.\n- Strongly prefer extending the module's params/builder classes over adding\n  interface methods — PRs #4504 and #4534 shipped whole features with ZERO\n  changes to interfaces, CommandObjects, UnifiedJedis, or PipeliningBase.\n- Search aggregation builders (`Reducer` subclasses) build a `List<Object>` via\n  `getOwnArgs()`; the parent computes `narg` automatically. Canonicalize clause\n  order at serialization time regardless of builder call order.\n- Module response parsing lives in the module's builder factory\n  (`TimeSeriesBuilderFactory`, `SearchBuilderFactory`); search aggregation results\n  are loosely typed and often absorb new reply content with no parsing changes.\n\n## Binary (byte[]) variant policy\n\n- **Core commands: full String/byte[] parity is mandatory** — all four\n  interfaces, paired CommandObjects methods, and all three clients. Reviewers\n  explicitly request binary integration tests too.\n- Only **keys and textual values** get `byte[]` overloads. Numeric/structural\n  args (`long index`, ranges, booleans, enums) stay identical.\n- **Params classes are shared, never duplicated**: one params class serves both\n  surfaces; if a param carries text, give the params class both `foo(String)` and\n  `foo(byte[])` setters (see `ArgrepParams`).\n- Return types mirror the key type (`String`→`byte[]`,\n  `List<String>`→`List<byte[]>`); count/index/model results stay shared.\n- **Modules: no binary variants at all.**\n\n## Params class conventions (`params/`)\n\n- Implement `IParams` with `addParams(CommandArguments args)`. Fluent setters\n  return `this`; provide a static factory named after the class\n  (`increxParams()`, `rangeParams()`).\n- Do NOT extend another command's params hierarchy. If two overloads share\n  options but differ in a typed field (long vs double), create a self-typed\n  abstract base: `BaseFooParams<T extends BaseFooParams<T>>` with a private\n  `self()` cast, plus two concrete subclasses — compile-time type safety over a\n  polymorphic single class.\n- Implement **`equals`/`hashCode`** (needed for Mockito matching in mocked tests)\n  and keep them in sync with every new field. `toString` is not required on\n  params classes.\n- Validate eagerly in setters with descriptive `IllegalArgumentException` /\n  `IllegalStateException` messages, consistent wording across sibling classes\n  (e.g. \"Aggregators must be non-null and non-empty\"). Required-config checks may\n  run at serialization time. No client-side server-version checks — an old server\n  returning an error is acceptable and should be noted in the PR description.\n- Expiration-style option groups are single-slot, last-wins (mirror `SetParams`).\n- Document the wire-emission order in javadoc and lock it down in unit tests.\n\n## Response mapping (`BuilderFactory` / `resps/`)\n\n- **Reuse existing generic builders whenever the reply maps to a standard shape**\n  (`LONG_LIST`, `DOUBLE_LIST`, `STRING`, `ENCODED_OBJECT_MAP`, …). Maintainers\n  actively reject bespoke response classes for 2-element arrays and the like.\n- Reuse `redis.clients.jedis.util.KeyValue<K,V>` for pair replies.\n- When a model class is genuinely needed (map-shaped INFO replies), put it in\n  `resps/`: public `String` constants for reply field names, a\n  `Map<String,Object>`-taking constructor, typed getters, plus a raw-map accessor\n  for forward compatibility; subclass for FULL/extended variants.\n- Nullable numeric replies are boxed `Long` — **never `Optional`/`OptionalLong`**\n  in the command API; consistency with existing signatures (e.g. `zrank`) wins.\n- Multi-mode commands (one wire command, several reply types by mode) are split\n  into distinctly-named typed Java methods (`aropAggregate`/`aropBitwise`/`aropCount`)\n  rather than one polymorphic method. But mode selected by argument type uses\n  **overloads of one name** (`increx(key, long, …)` / `increx(key, double, …)`),\n  with the mode keyword (BYINT/BYFLOAT) implied by the overload.\n\n## Encoding & enum rules\n\n- String ↔ bytes: `SafeEncoder.encode()` — never `String.getBytes()`.\n- Numeric → bytes: `Protocol.toByteArray()`.\n- Token-valued argument enums live in `args/` and implement `Rawable` with\n  `raw = SafeEncoder.encode(name())` cached in the constructor.\n- Compose multi-token byte values (e.g. comma-joined lists) at the byte level.\n\n## Javadoc, `@since`, `@Experimental`\n\n- **Interface methods only** carry javadoc; implementations (`UnifiedJedis`,\n  `Jedis`, `PipeliningBase`, `CommandObjects`) carry none.\n- String-interface javadoc: `<b><a href=\"https://redis.io/commands/xxx\">XXX\n  Command</a></b>`, prose semantics, `Time complexity: O(...)`, `@param`,\n  `@return`, `@since <version>`. Binary variants get short javadoc with\n  `@see` to the String variant. Pipeline variants: one line — \"Pipeline variant\n  of {@link ...}\" + `@since`.\n- `@since` on every new public method and class, computed from `pom.xml`.\n- If the server feature is **preview/experimental**, annotate ALL new public API\n  (interfaces, methods, params, args, resps) with\n  `redis.clients.jedis.annots.Experimental` and label the PR `experimental`.\n  Do NOT use `@Experimental` for stable GA features.\n\n## Test matrix — what to write\n\nFollow `docs/integration-testing.md` for environment, layout, and naming. The\nestablished per-command test layers (write all that apply):\n\n1. **Params unit test** (`params/FooParamsTest`, plain `*Test`, no Redis):\n   `@Nested` groups (`ValidationTests`, `AddParamsTests`, overload-equivalence),\n   asserting exact wire args and order with\n   `redis.clients.jedis.util.CommandArgumentsMatchers`\n   (`hasArgumentCount`, `hasArguments`) and `RawableFactory.from(...)`; also test\n   the `equals`/`hashCode` contract.\n2. **Mocked delegation tests** (unit, Mockito): add `@Mock CommandObject<T>`\n   fields to `MockedCommandObjectsTestBase` (name them by TYPE, e.g.\n   `listLongCommandObject` — reuse existing ones when the type matches), then\n   `when/verify` tests in `mocked/unified/UnifiedJedis<Group>CommandsTest` and\n   `mocked/pipeline/PipeliningBase<Group>CommandsTest`.\n3. **Unified integration tests**: add methods to the existing abstract\n   `commands/unified/<Group>CommandsTestBase` when one exists; for a new family,\n   create `<Family>CommandsTestBase extends UnifiedJedisCommandsTestBase` plus\n   thin per-topology runners — standalone\n   (`RedisClientCommandsTestHelper.getClient(protocol)`) and cluster\n   (`ClusterCommandsTestHelper.getCleanCluster(protocol)`). **New concrete\n   integration classes MUST be named `*IT`** — never `*IntegrationTest`, never\n   `@Tag(\"integration\")` on new classes.\n4. **Cluster handling**: multi-key commands need cluster test overrides using\n   hash-tagged keys (`s1{:}`) so keys share a slot; tests semantically\n   incompatible with cluster get `@Test @Override` + `@Disabled(\"<reason>\")`\n   empty bodies.\n5. **Legacy `Jedis` coverage**: extend `commands/jedis/<Group>*CommandsTest`\n   (which extends `JedisCommandsTestBase`) — can be minimal (smoke/missing-key)\n   when the unified base covers behavior fully. Binary variants get their own\n   integration tests (reviewers ask for these explicitly).\n\nCross-cutting test conventions:\n- **RESP2/RESP3** comes free: test bases are `@ParameterizedClass` +\n  `@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")`\n  (or `#jedisRespVersions` for legacy) — no per-test work; just make sure new\n  classes inherit the right base.\n- **Gating**: command already released → `@SinceRedisVersion(\"<RC build>\")`\n  (e.g. `\"8.7.225\"` for 8.8) at the shared base-class level ONCE — do not repeat\n  it on subclasses (maintainers remove redundant ones). Command not yet in any\n  GA server → `@EnabledOnCommand(\"<COMMAND>\")` (capability probe via COMMAND\n  INFO). Module presence as precondition → `assumeTrue(hasCommand(...))` probes.\n  Environment exclusions → `@ConditionalOnEnv(value = TestEnvUtil.ENV_...,\n  enabled = false)` (e.g. skip Redis Enterprise for brand-new features).\n- Cover the **whole family the option touches** (store + non-store variants,\n  with/without optional args, standalone + cluster), asserting real semantics,\n  not just \"no error\". Avoid brittle assertions (no hard-coded `hashCode()`\n  literals).\n\n## Running the tests\n\nJedis pins the **CI JDK (Java 8)** for the test build — check\n`.github/workflows/` for the exact version and use a matching local JDK. Verify\nwith `java -version` before running anything; a newer JDK may fail the build or\nsilently produce the wrong bytecode target.\n\n- Unit tests (Surefire, no Redis): `mvn -B test`, or `mvn -Dtest=FooParamsTest test`.\n- Integration tests (Failsafe) need the Docker env and the `verify` lifecycle:\n  ```bash\n  make start version=8.8        # pick the version that carries the new command\n  mvn -B verify                 # or: mvn -Dtest=FooIT verify\n  make stop\n  ```\n  Never run integration coverage via `mvn test`/`surefire:test` — Failsafe owns\n  `*IT` classes. If a brand-new command isn't in any published\n  `redislabs/client-libs-test` tag yet, say so: the integration tests will be\n  skipped/gated (that is expected and acceptable — `@EnabledOnCommand` /\n  `@SinceRedisVersion` handle it), but they must still be written and compile.\n- **Unattended:** no `make start`/`make stop`. The servers are already running\n  and `$REDIS_ENDPOINTS_CONFIG_PATH` points the tests at them. Run:\n  1. Unit tests: `mvn -B test`, or the test command the invoking prompt gives.\n  2. The changed family's integration tests on **both** topologies, through\n     Failsafe only. Two kinds of runner exist:\n     - existing core families: `@Tag(\"integration\")` `*Test` classes (Failsafe's\n       `it-tagged` execution), namely the unified standalone runner\n       `RedisClient<Family>CommandsTest`, the cluster runner\n       `Cluster<Family>CommandsTest` and the legacy `commands/jedis/<Family>CommandsTest`\n     - module and new families: `*IT` classes (the `it-suffix` execution), a\n       standalone runner in `commands/unified/client/<module>/` (e.g.\n       `BloomRedisClientCommandsIT`, on `modules-docker`) and a cluster runner in\n       `commands/unified/cluster/<module>/` (e.g. `BloomClusterCommandsIT`, on\n       `cluster-stable`; the cluster image loads the modules too)\n     \n     Select them with one wildcard that covers both kinds and every topology, plus\n     an explicit name for any new runner that doesn't match it:\n     ```bash\n     mvn -B -DskipUnitTests=true -Dit.failIfNoSpecifiedTests=false \\\n       -Dit.test='*<Family>*Commands*' verify\n     ```\n     `-Dit.failIfNoSpecifiedTests=false` is needed because each Failsafe execution\n     sees the same filter and one of them usually matches nothing. That also means\n     `verify` can pass having run **no** test, so don't trust the exit code alone.\n  3. Prove it ran: read `target/failsafe-reports/*.txt` (one file per class, named\n     by its fully qualified class name) and list every class that ran with its counts.\n     A class in package `redis.clients.jedis.commands.unified.cluster` (any\n     sub-package) is a cluster run; anything else is standalone. Don't go by the\n     name prefix: module runners are `<Family>ClusterCommandsIT`, and one is\n     `FTHybridCommandsClusterIT`. At least one standalone class and one cluster class\n     must report `Tests run:` > 0 for the changed commands. This applies to module\n     families too. If either topology ran nothing, fix the filter and rerun. Don't\n     report success.\n  \n  Tests that need endpoints the file doesn't have (sentinel, TLS, ACL users,\n  `cluster-unbound`) are out of scope: list them as not run. Report passed /\n  failed / skipped per topology, and the classes run.\n\n## PR hygiene checklist (verify before finishing)\n\n- [ ] Every layer of the chosen matrix updated consistently (a return-type change\n      must propagate across all 4 interfaces × 3 implementations × all test layers).\n- [ ] New files added to the `pom.xml` formatter-plugin includes.\n- [ ] `@since` (and `@Experimental` if preview) on all new public API.\n- [ ] No unused imports, no wildcard imports, no dead `Protocol.Keyword` constants.\n- [ ] `equals`/`hashCode` on params updated and unit-tested.\n- [ ] `docs/` updated if user-facing behavior/configuration changed; migration\n      guide entry if anything breaks.\n- [ ] PR description states: server PR link, version gate choice and why, which\n      layers intentionally did NOT change and why, and behavior against older\n      servers. (**Unattended:** put these in the final report; the automation\n      builds the PR description from it.)\n","AGENTS.md":"# Jedis Coding Conventions\nJedis is a synchronous Java client for Redis, shipped as a library on Maven Central as `redis.clients:jedis`.\nIt covers standalone, cluster, sentinel, and multi-db setups — with pipelining, transactions, pub/sub, and Redis Stack modules — and targets Java 8 for source/binary compatibility.\n\n## General Principles\n- **Maintain JDK 8 compatibility.** All production and test code must compile and run on JDK 8.\n- **Keep documentation up to date.** If a change affects user-facing behavior, configuration, or the public API, update the relevant pages under [`docs/`](docs) (MkDocs).\n- **Add or update tests.** Every bug fix should include a regression test, and every new feature should include appropriate tests. Follow the conventions in [`docs/integration-testing.md`](docs/integration-testing.md) for choosing between unit and integration tests.\n- **Document breaking changes.** Any breaking change must be documented in the appropriate migration guide under [`docs/migration-guides/`](docs/migration-guides).\n- **Record behavior changes in the release notes.** If a change alters user-visible behavior without breaking the API, add an entry to the running notes for the next release under [`docs/release-notes/`](docs/release-notes) in the same PR, following the entry format in that file.\n- **Follow the contribution guidelines.** Ensure all changes comply with the project rules in [`.github/CONTRIBUTING.md`](.github/CONTRIBUTING.md).\n- **Request approval before adding dependencies.** Do not introduce new runtime, test, or build dependencies without explicit confirmation.\n- **Review-friendly changes.** Keep commits and pull requests focused and logically grouped. Separate feature or bug-fix changes from refactoring, formatting, and other mechanical changes whenever practical.\n\n## Development Environment\n- **Use the CI JDK version.** Build and test using the same JDK version configured in the GitHub Actions workflows. Check `.github/workflows/` (or `docs/integration-testing.md`) to determine the required version before reproducing CI failures.\n\n## Build & Test Commands\n- `make start version=8.6` + `mvn clean verify` + `make stop` — full run against\n  the Docker Redis env (`make test` does all three).\n- `mvn test` — unit tests (no Redis); `mvn -Dtest=ClassName test` for one class.\n\n## Implementation Guidelines\n### Code Style\n- Keep comments short and informative. Comment **why**, not **what**. Avoid comments that simply restate the code.\n- Remove unused imports. Do not use wildcard imports. Avoid fully qualified class names unless necessary to resolve naming conflicts.\n- **Annotate all new public API with `@since`.** Determine the version from the current build by running:\n  ```sh\n  mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n  ```\n  (or by reading the top-level `<version>` in `pom.xml`), then remove the `-SNAPSHOT` suffix. For example, `8.0.0-SNAPSHOT` becomes `@since 8.0`.\n\n### Test Conventions\n- Name new unit test classes using the `*Test` convention.\n- Name new integration test classes using the `*IT` convention. Do not use the `integration` tag for new tests.\n\n## Architecture\n\nSee [`docs/redis-client-components-overview.md`](docs/redis-client-components-overview.md)\nfor a high-level walkthrough (executors, providers, builders, and command execution flows).\n\n`UnifiedJedis` is the core client. It implements the command interfaces and\ndelegates to three collaborators:\n\n- `ConnectionProvider` (`providers/`) — obtains connections: pooled, cluster,\n  sentinel, or multi-db.\n- `CommandExecutor` (`executors/`) — runs a command: simple, retry, cluster\n  routing, or failover.\n- `CommandObjects` — factory building a typed `CommandObject<T>` per command.\n\n**Modern clients** extend `UnifiedJedis` and are built through\n`AbstractClientBuilder` subclasses (`builders/`): `RedisClient`,\n`RedisClusterClient`, `RedisSentinelClient`, `MultiDbClient`. A new client\noverrides `createDefaultConnectionProvider()`, `createDefaultCommandExecutor()`,\n`createClient()`, and `validateSpecificConfiguration()` (run before `build()`).\n\n**Legacy client:** `Jedis` is the single-connection client (pooling via\n`JedisPool`), still supported. `JedisPool`, `JedisCluster`, and\n`JedisSentinelPool` are `@Deprecated` in favor of the builder-based clients.\n\n**Feature modules** expose dedicated command interfaces under\n`redis.clients.jedis.<module>`: `search`, `json`, `bloom`, `timeseries`, plus\n`csc` (client-side caching) and `mcf` (multi-db / failover).\n\n## Conventions\n\n**Adding or changing a command** — trace an existing command first, then update\nthe full matrix so all surfaces stay in sync:\n\n1. **Command interfaces** (`commands/`): `<Group>Commands` and\n   `<Group>BinaryCommands` (String vs `byte[]`), each with its `<Group>Pipeline…`\n   variant.\n2. **`CommandObjects`** — build the args and a response `Builder<T>`, e.g.\n   `new CommandObject<>(commandArguments(GET).key(key), BuilderFactory.STRING)`.\n   `ClusterCommandObjects` overrides these to enforce cluster constraints.\n3. **`UnifiedJedis`** and **`PipeliningBase`** — the execution entry points.\n4. **`Jedis`** — add it here too, for backward compatibility.\n\n**Encoding:**\n- String ↔ bytes: `SafeEncoder.encode()` — never `String.getBytes()` (breaks GBK).\n- Numeric → bytes: `Protocol.toByteArray()`.\n\n## Agent Skills\n\nTask procedures live in [`.agents/skills/`](.agents/skills) (also reachable as\n`.claude/skills/`). Read the matching `SKILL.md` before that kind of work:\n\n- `extend-commands-api` — add or extend a Redis command end-to-end (evidence, decision tree, full\n  String/binary/pipeline matrix, tests), supervised or unattended.\n- `create-implementation-plan-for-redis-api-change` — turn a shared client HLD into Jedis's reviewed\n  implementation plan (API, files, ordered steps, test matrix) without touching sources; read-only,\n  writes one plan file; conventions come from `extend-commands-api`.\n- `creating-description-for-gh-pr` — draft a PR title and description from the diff between two\n  local branches.\n",".claude/commands/createPRDescription.md":"---\ndescription: Generate a PR title and description from the diff between two local branches, using the creating-description-for-gh-pr skill, and save to prDescription.md\nargument-hint: [base-branch] [target-branch]\n---\n\nUse the \"creating-description-for-gh-pr\" skill to generate a PR title and description.\n\nArguments provided to this command: base = `$1`, target = `$2` (either may be empty).\n\nPass this context into the skill's branch resolution logic (Step 1). Since these are positional arguments, only three states are possible:\n- Both `$1` and `$2` given → `$1` is the explicit base branch, `$2` is the explicit target branch.\n- Only `$1` given (`$2` empty) → `$1` is the explicit base branch; let the skill default the target branch.\n- Neither given (both empty) → let the skill default both base and target branches.\n\n`$1` cannot be empty while `$2` is non-empty — do not handle that case.\n\nDo not duplicate or reinterpret the skill's instructions; follow them as written, including the output structure, the writing-style constraints, and the save/confirm steps.\n",".agents/skills/creating-description-for-gh-pr/SKILL.md":"---\nname: creating-description-for-gh-pr\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.\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## 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"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/redis/jedis/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Jedis Coding Conventions\nJedis is a synchronous Java client for Redis, shipped as a library on Maven Central as `redis.clients:jedis`.\nIt covers standalone, cluster, sentinel, and multi-db setups — with pipelining, transactions, pub/sub, and Redis Stack modules — and targets Java 8 for source/binary compatibility.\n\n## General Principles\n- **Maintain JDK 8 compatibility.** All production and test code must compile and run on JDK 8.\n- **Keep documentation up to date.** If a change affects user-facing behavior, configuration, or the public API, update the relevant pages under [`docs/`](docs) (MkDocs).\n- **Add or update tests.** Every bug fix should include a regression test, and every new feature should include appropriate tests. Follow the conventions in [`docs/integration-testing.md`](docs/integration-testing.md) for choosing between unit and integration tests.\n- **Document breaking changes.** Any breaking change must be documented in the appropriate migration guide under [`docs/migration-guides/`](docs/migration-guides).\n- **Record behavior changes in the release notes.** If a change alters user-visible behavior without breaking the API, add an entry to the running notes for the next release under [`docs/release-notes/`](docs/release-notes) in the same PR, following the entry format in that file.\n- **Follow the contribution guidelines.** Ensure all changes comply with the project rules in [`.github/CONTRIBUTING.md`](.github/CONTRIBUTING.md).\n- **Request approval before adding dependencies.** Do not introduce new runtime, test, or build dependencies without explicit confirmation.\n- **Review-friendly changes.** Keep commits and pull requests focused and logically grouped. Separate feature or bug-fix changes from refactoring, formatting, and other mechanical changes whenever practical.\n\n## Development Environment\n- **Use the CI JDK version.** Build and test using the same JDK version configured in the GitHub Actions workflows. Check `.github/workflows/` (or `docs/integration-testing.md`) to determine the required version before reproducing CI failures.\n\n## Build & Test Commands\n- `make start version=8.6` + `mvn clean verify` + `make stop` — full run against\n  the Docker Redis env (`make test` does all three).\n- `mvn test` — unit tests (no Redis); `mvn -Dtest=ClassName test` for one class.\n\n## Implementation Guidelines\n### Code Style\n- Keep comments short and informative. Comment **why**, not **what**. Avoid comments that simply restate the code.\n- Remove unused imports. Do not use wildcard imports. Avoid fully qualified class names unless necessary to resolve naming conflicts.\n- **Annotate all new public API with `@since`.** Determine the version from the current build by running:\n  ```sh\n  mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n  ```\n  (or by reading the top-level `<version>` in `pom.xml`), then remove the `-SNAPSHOT` suffix. For example, `8.0.0-SNAPSHOT` becomes `@since 8.0`.\n\n### Test Conventions\n- Name new unit test classes using the `*Test` convention.\n- Name new integration test classes using the `*IT` convention. Do not use the `integration` tag for new tests.\n\n## Architecture\n\nSee [`docs/redis-client-components-overview.md`](docs/redis-client-components-overview.md)\nfor a high-level walkthrough (executors, providers, builders, and command execution flows).\n\n`UnifiedJedis` is the core client. It implements the command interfaces and\ndelegates to three collaborators:\n\n- `ConnectionProvider` (`providers/`) — obtains connections: pooled, cluster,\n  sentinel, or multi-db.\n- `CommandExecutor` (`executors/`) — runs a command: simple, retry, cluster\n  routing, or failover.\n- `CommandObjects` — factory building a typed `CommandObject<T>` per command.\n\n**Modern clients** extend `UnifiedJedis` and are built through\n`AbstractClientBuilder` subclasses (`builders/`): `RedisClient`,\n`RedisClusterClient`, `RedisSentinelClient`, `MultiDbClient`. A new client\noverrides `createDefaultConnectionProvider()`, `createDefaultCommandExecutor()`,\n`createClient()`, and `validateSpecificConfiguration()` (run before `build()`).\n\n**Legacy client:** `Jedis` is the single-connection client (pooling via\n`JedisPool`), still supported. `JedisPool`, `JedisCluster`, and\n`JedisSentinelPool` are `@Deprecated` in favor of the builder-based clients.\n\n**Feature modules** expose dedicated command interfaces under\n`redis.clients.jedis.<module>`: `search`, `json`, `bloom`, `timeseries`, plus\n`csc` (client-side caching) and `mcf` (multi-db / failover).\n\n## Conventions\n\n**Adding or changing a command** — trace an existing command first, then update\nthe full matrix so all surfaces stay in sync:\n\n1. **Command interfaces** (`commands/`): `<Group>Commands` and\n   `<Group>BinaryCommands` (String vs `byte[]`), each with its `<Group>Pipeline…`\n   variant.\n2. **`CommandObjects`** — build the args and a response `Builder<T>`, e.g.\n   `new CommandObject<>(commandArguments(GET).key(key), BuilderFactory.STRING)`.\n   `ClusterCommandObjects` overrides these to enforce cluster constraints.\n3. **`UnifiedJedis`** and **`PipeliningBase`** — the execution entry points.\n4. **`Jedis`** — add it here too, for backward compatibility.\n\n**Encoding:**\n- String ↔ bytes: `SafeEncoder.encode()` — never `String.getBytes()` (breaks GBK).\n- Numeric → bytes: `Protocol.toByteArray()`.\n\n## Agent Skills\n\nTask procedures live in [`.agents/skills/`](.agents/skills) (also reachable as\n`.claude/skills/`). Read the matching `SKILL.md` before that kind of work:\n\n- `extend-commands-api` — add or extend a Redis command end-to-end (evidence, decision tree, full\n  String/binary/pipeline matrix, tests), supervised or unattended.\n- `create-implementation-plan-for-redis-api-change` — turn a shared client HLD into Jedis's reviewed\n  implementation plan (API, files, ordered steps, test matrix) without touching sources; read-only,\n  writes one plan file; conventions come from `extend-commands-api`.\n- `creating-description-for-gh-pr` — draft a PR title and description from the diff between two\n  local branches.\n","isInternal":false,"tokens":1420,"sizeBytes":6095},{"name":"SKILL.md","path":".agents/skills/creating-description-for-gh-pr/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/jedis/HEAD/.agents/skills/creating-description-for-gh-pr/SKILL.md","title":"creating-description-for-gh-pr","category":"anthropic-skill","format":"markdown","content":"---\nname: creating-description-for-gh-pr\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.\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## 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","frontmatter":{"name":"creating-description-for-gh-pr","description":"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."},"isInternal":false,"tokens":761,"sizeBytes":3377},{"name":"SKILL.md","path":".agents/skills/extend-commands-api/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/jedis/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 Jedis client API — a new core command, a family of new commands, an extension to an existing command's options, or a module command (Search/TimeSeries/JSON/Bloom). Gathers evidence (Redis server PR, HLD document), plans the full implementation matrix, then implements with unit and integration tests following Jedis maintainer conventions. Runs supervised (plan mode, user approval) by default, or unattended for automation such as the RedisClientsBot parity pipeline.\nmetadata:\n  modes: supervised, unattended\n---\n\n# Extend the Jedis Commands API\n\nImplement a new Redis command (or extend an existing one) in Jedis, following the\nconventions Jedis maintainers enforce in review.\n\n## Modes — read this first\n\nThis skill runs in one of two modes. The engineering rules in every section below\nare identical in both; only who answers questions and approves the plan differs.\n\n- **Supervised** (default): a human is in the session. Ask the questions, present\n  the plan in plan mode and wait for approval, as written below.\n- **Unattended**: no human will answer or approve anything, so never stop to wait.\n  Use it ONLY when the invoking prompt says `Mode: unattended` or the environment\n  has `CLIENT_SKILL_MODE=unattended`. Never switch to it on your own.\n\nIn unattended mode the invoking prompt supplies the inputs a human would give:\nthe HLD path (normally `./HLD.md`), the server PR reference, the test command, and\ntwo running servers from the `redislabs/client-libs-test` image it names, password-\nprotected like the local Docker env and without TLS: a standalone (`$REDIS_URL`,\npassword `$REDIS_STANDALONE_PASSWORD`, i.e. `foobared`) and a 6-node cluster with 3\nmasters and 3 replicas (`$REDIS_CLUSTER_URLS`, password `$REDIS_CLUSTER_PASSWORD`,\ni.e. `cluster`). `$REDIS_ENDPOINTS_CONFIG_PATH` already points at an `endpoints.json`\nthat describes them, passwords included, as `standalone0`, `modules-docker` (the\nstandalone; the image ships the modules) and `cluster-stable`. Jedis tests read that\nvariable (`TestEnvUtil.getEndpointsConfigPath`) and authenticate with the passwords in\nit, so the integration tests run against both servers unchanged. Wherever a step below has an **Unattended:**\nnote, follow the note:\n\n| Step | Supervised | Unattended |\n|---|---|---|\n| Phase 0.1 HLD | ask the user for the path | read the given HLD fully |\n| Phase 0.2 server PR | `gh`, asking to run it outside the sandbox if needed | no `gh` at all: the given PR reference, the HLD's facts, else the unauthenticated GitHub REST API |\n| Phase 0.3 environment / image tag | `make start`; ask for an image tag if the command is missing | no Docker: probe the given standalone and cluster; if the command is missing there, continue with gated tests |\n| Phase 0.4 redis-cli scenarios | run against `standalone0` | run against the given Redis URL; if `redis-cli` is unavailable, write them as unverified transcripts |\n| Phase 1 plan + approval | plan mode, ask before editing | write the plan into the final report, then implement it in the same session |\n| Running tests | `make start`, `mvn -B verify`, `make stop` | `mvn -B test` (unit), then the changed family's integration tests on standalone **and** cluster through `$REDIS_ENDPOINTS_CONFIG_PATH` (see Running the tests) |\n| An open design question | ask the user | take the HLD's choice (else the most consistent existing Jedis convention) and list it as an open question in the report |\n\nUnattended runs also follow these rules:\n- **Change files.** A run that ends without changes has failed. Stop without\n  editing only when implementing is impossible, and say exactly why in the report.\n- **Don't commit, push, or open a PR.** The automation that invoked you does that.\n  Don't edit `.github/` workflows, release/version files or `pom.xml` versions.\n  The formatter includes in `pom.xml` are the exception (see B.7).\n- **Finish with a report** covering, in plain text with no placeholders:\n  - what was implemented\n  - the decision-tree class (A–D) and the plan\n  - design decisions, and which layers intentionally did NOT change and why\n  - unit results; integration results with the test classes run on standalone and on\n    cluster; integration tests left gated, skipped, or out of scope\n  - steps skipped because they need a human or Docker\n  - open questions for maintainers\n\n  This feeds the PR description (see the PR hygiene checklist).\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 the user for the path to a\n   markdown file containing the High-Level Design for the command(s) (or confirm\n   that no HLD exists). If a path is given, read it fully — it is the primary\n   source for syntax, semantics, reply shape, and edge cases.\n   **Unattended:** don't ask; read the HLD path the invoking prompt gives.\n2. **Find the server-side PR in the `redis/redis` GitHub repo.**\n   **Unattended:** skip every `gh` command in this step: `gh auth status`, and the\n   `gh search` / `gh pr view` / `gh pr diff` block below. Don't ask for permission.\n   Use the server PR reference the invoking prompt gives. The HLD usually quotes\n   the syntax, the reply shapes and the first version. Fill any gaps from the\n   unauthenticated GitHub REST API (e.g.\n   `https://api.github.com/repos/redis/redis/pulls/<num>/files`, then the raw\n   `src/commands/*.json` file), and go on to \"From the server PR, extract\".\n\n   **Supervised:** first verify that `gh` works in the current (sandboxed)\n   environment:\n   ```bash\n   gh auth status\n   ```\n   Sandboxes often block access to credential files (`~/.netrc`, gh config), so\n   `gh` may report unauthenticated here even though it works on the user's\n   machine. If `gh auth status` fails or reports no authentication, **ask the\n   user for permission to run the `gh` commands outside the sandbox** (e.g. with\n   the sandbox override for these specific read-only commands), explaining that\n   `gh` cannot reach its credentials from inside the sandbox. Only if the user\n   declines (or `gh` is genuinely not logged in anywhere) fall back to the\n   unauthenticated GitHub REST API or fetching\n   `https://github.com/redis/redis/pulls?q=<command>`.\n\n   Then search for the PR that adds/extends the command on the server:\n   ```bash\n   gh search prs --repo redis/redis \"<COMMAND NAME>\" --limit 10\n   gh pr view <num> --repo redis/redis\n   gh pr diff <num> --repo redis/redis   # look at src/commands/*.json for exact syntax\n   ```\n   From the server PR,\n   extract: exact wire syntax (argument order and optionality), reply type per\n   RESP2/RESP3, error conditions, time complexity, and — critically — the **first\n   server version carrying the feature** (RC builds like `8.7.225` = Redis 8.8 RC\n   are used for test gating). Note whether the server marks the feature\n   *experimental / in preview*.\n3. **Verify the command exists on the \"next\" Redis OSS version.** Start the\n   integration environment using the **latest** `.env.vX.XX` file under\n   `src/test/resources/env/` (pick the highest version numerically, not\n   lexicographically — e.g. `8.10` > `8.8`):\n   ```bash\n   ls src/test/resources/env/.env.v* | sort -V | tail -1   # → e.g. .env.v8.10\n   make start version=8.10\n   ```\n   Then probe the `standalone0` endpoint. Read its connection details from\n   `src/test/resources/endpoints.json` (currently `redis://localhost:6379`,\n   password `foobared`) and check the target command via redis-cli:\n   ```bash\n   redis-cli -u redis://localhost:6379 -a foobared INFO server | grep redis_version\n   redis-cli -u redis://localhost:6379 -a foobared COMMAND INFO <COMMAND>   # non-empty → command exists\n   redis-cli -u redis://localhost:6379 -a foobared 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\n   EXTENDED command, additionally invoke it with the new syntax against a\n   scratch key and confirm the server accepts it (no `ERR syntax error` /\n   `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 for the target change. Interactively\n   **ask the user for a `redislabs/client-libs-test` image tag** that contains it\n   (e.g. an RC/edge/milestone build — tags are listed at\n   https://hub.docker.com/r/redislabs/client-libs-test/tags; you may check them\n   yourself and suggest candidates). Then restart the environment with that tag\n   and re-run the probes:\n   ```bash\n   make stop\n   make start CLIENT_LIBS_TEST_IMAGE_TAG=<tag>\n   ```\n   If the user has no tag to offer (or the probe still fails), report it and\n   proceed anyway: the implementation can continue, but integration tests will\n   be gated/skipped until a `redislabs/client-libs-test` image ships the change.\n   Keep the environment running for later test runs, or `make stop` if you\n   won't need it soon.\n   **Unattended:** no Docker, no `make start`/`make stop`, no image-tag question.\n   The servers already run the target image. Probe the standalone with the same\n   `INFO server` / `COMMAND INFO` / `COMMAND DOCS` checks (e.g.\n   `redis-cli -u \"$REDIS_URL\" COMMAND INFO <COMMAND>`; the URL carries the\n   credentials), and the cluster with `redis-cli -c -h \"$REDIS_CLUSTER_HOST\"\n   -p \"$REDIS_CLUSTER_START_PORT\" -a \"$REDIS_CLUSTER_PASSWORD\" --no-auth-warning CLUSTER INFO`\n   (expect `cluster_state:ok`). If the command is missing, continue anyway with\n   gated integration tests, and say so in the report.\n4. **Create redis-cli showcase test cases.** Once the command is available on\n   the running environment, derive a small set of redis-cli scenarios from the\n   HLD and the server PR and run them against `standalone0`. These serve two\n   purposes at once:\n   - **Smoke test**: prove the documented behavior actually holds on the target\n     server — happy path, each new option/keyword, reply shape (run with\n     `redis-cli -3` too if RESP3 replies differ), edge cases and error\n     conditions called out in the HLD (empty/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 that explains\n     WHY the command/option exists — what a user could not do (or could only do\n     awkwardly) before, and how the new API covers it. Prefer realistic data\n     over `foo`/`bar` (e.g. rate-limit counters for a bounded INCR, sensor\n     readings for a time-series aggregator).\n\n   Save the scenarios as a commented script in a scratch file (one block per\n   use-case: a one-line \"what this demonstrates\" comment, the redis-cli\n   commands, and the observed reply pasted back as comments). Carry this\n   material forward: it feeds the Phase 1 plan (as the explanation of the\n   feature and the source of expected values for tests), the Java test\n   assertions, and the PR description. If the command could not be made\n   available on any image (see step 3), still write the scenarios from the\n   HLD/PR as *expected* transcripts and mark them unverified.\n   **Unattended:** run the scenarios against `$REDIS_URL` instead of\n   `standalone0`. For a keyed command, also run one through the cluster. Always\n   pass the Redis command as arguments: a bare `redis-cli` waits on stdin and\n   hangs a headless run.\n   ```bash\n   redis-cli -u \"$REDIS_URL\" <COMMAND> <args...>\n   redis-cli -c -h \"$REDIS_CLUSTER_HOST\" -p \"$REDIS_CLUSTER_START_PORT\" \\\n     -a \"$REDIS_CLUSTER_PASSWORD\" --no-auth-warning <COMMAND> <args...>\n   ```\n   If `redis-cli` isn't installed, write them as unverified expected transcripts. Keep the scratch file out of the change; carry the\n   scenarios into the report instead.\n5. **Read `docs/integration-testing.md`** in the Jedis repo — it defines the test\n   environment, endpoint discovery, `*Test` vs `*IT` naming, and how to run tests.\n6. **Trace one analogous existing command** end-to-end in the codebase (same\n   command group, similar reply shape) so the plan mirrors real code, not\n   guesswork.\n7. Determine the `@since` version:\n   ```bash\n   mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n   ```\n   Strip `-SNAPSHOT` (e.g. `8.0.0-SNAPSHOT` → `@since 8.0`).\n\n## Phase 1 — Plan, then approval (supervised) or straight to implementation (unattended)\n\nUsing the evidence, classify the change with the decision tree below, enumerate\nthe exact file-by-file touch list, the test matrix, and the gating annotations.\nOpen the plan with a short \"what this feature enables\" section built from the\nredis-cli showcase scenarios (Phase 0 step 4), including one or two\nrepresentative command/reply transcripts, so the reader sees the use-case before\nthe file list.\n\n- **Supervised:** enter **plan mode**, present the plan to the user and\n  **explicitly ask permission to execute in auto-accept mode** before\n  implementing. Do not start editing files until the user approves.\n- **Unattended:** don't enter plan mode and don't wait. The automation's\n  approval was the merged HLD. Keep the plan for the final report, then\n  implement it right away, following the whole matrix exactly as a supervised\n  run would.\n\n## Decision tree — what kind of change is this?\n\n**A. Extension of an existing command that fits an existing params class**\n(e.g. new enum value / new option token):\n- Touch ONLY the params/model classes + tests. Do NOT touch command interfaces,\n  CommandObjects, UnifiedJedis, PipeliningBase, or Jedis — the existing\n  `IParams.addParams(CommandArguments)` delegation carries the new option through\n  automatically, for both String and binary surfaces.\n- If the option is a bare token: add an enum constant implementing `Rawable`\n  (bytes come from `SafeEncoder.encode(name())` — self-wiring).\n- If it's a new field: add a fluent setter returning `this`, extend\n  `addParams()`, and **update `equals`/`hashCode`** in sync.\n- If the reply shape grows (e.g. more array elements): extend the response model\n  class backward-compatibly — keep old constructors/getters working, add new\n  accessors, update the builder in the relevant `*BuilderFactory` to branch on\n  reply size. Jedis does not defensively copy response collections (stated\n  maintainer convention).\n\n**B. New core command(s)**  — the FULL matrix, every layer:\n1. `Protocol.java` — new `Command` enum constant(s); new `Keyword` constants for\n   sub-tokens, grouped under a `// <FEATURE> keywords` comment, alphabetical.\n   **Never add a Keyword that duplicates a token already carried by a dedicated\n   `Rawable` enum** (dead keywords get flagged in review).\n2. Command interfaces in `commands/`: `<Group>Commands`, `<Group>BinaryCommands`,\n   `<Group>PipelineCommands`, `<Group>PipelineBinaryCommands`. For a whole new\n   command *family*, create four new `<Family>*Commands` interfaces and add one\n   `extends` entry to `JedisCommands`, `JedisBinaryCommands`, `PipelineCommands`,\n   `PipelineBinaryCommands`.\n3. `CommandObjects` — String and byte[] factory methods side by side under a\n   `// <Feature> commands` section. `ClusterCommandObjects` needs overrides ONLY\n   for multi-key commands requiring slot checks; single-key commands need nothing.\n4. `UnifiedJedis` — one-line `@Override` delegating to\n   `executeCommand(commandObjects.xxx(...))`.\n5. `PipeliningBase` — `appendCommand(commandObjects.xxx(...))` returning\n   `Response<T>`.\n6. `Jedis` (legacy) — `checkIsInMultiOrPipeline(); connection.executeCommand(commandObjects.xxx(...))`.\n7. `pom.xml` — add every new file to the **formatter-maven-plugin includes** list\n   (this repo format-enforces an allowlist; new files must be registered).\n\n**C. Extension needing new overloads / a new params class** (hybrid): new methods\ngo through the full matrix of B; the option plumbing follows the params\nconventions below.\n\n**D. Module command (search / timeseries / json / bloom)**:\n- Module interfaces are **String-only** — there are NO `*BinaryCommands` variants\n  for modules; never create them.\n- Module commands/keywords live in the module protocol class\n  (`TimeSeriesProtocol.TimeSeriesCommand/TimeSeriesKeyword`,\n  `SearchProtocol.SearchCommand/SearchKeyword`, …), each an enum implementing\n  `ProtocolCommand`/`Rawable` with `SafeEncoder.encode()`-cached bytes.\n- Strongly prefer extending the module's params/builder classes over adding\n  interface methods — PRs #4504 and #4534 shipped whole features with ZERO\n  changes to interfaces, CommandObjects, UnifiedJedis, or PipeliningBase.\n- Search aggregation builders (`Reducer` subclasses) build a `List<Object>` via\n  `getOwnArgs()`; the parent computes `narg` automatically. Canonicalize clause\n  order at serialization time regardless of builder call order.\n- Module response parsing lives in the module's builder factory\n  (`TimeSeriesBuilderFactory`, `SearchBuilderFactory`); search aggregation results\n  are loosely typed and often absorb new reply content with no parsing changes.\n\n## Binary (byte[]) variant policy\n\n- **Core commands: full String/byte[] parity is mandatory** — all four\n  interfaces, paired CommandObjects methods, and all three clients. Reviewers\n  explicitly request binary integration tests too.\n- Only **keys and textual values** get `byte[]` overloads. Numeric/structural\n  args (`long index`, ranges, booleans, enums) stay identical.\n- **Params classes are shared, never duplicated**: one params class serves both\n  surfaces; if a param carries text, give the params class both `foo(String)` and\n  `foo(byte[])` setters (see `ArgrepParams`).\n- Return types mirror the key type (`String`→`byte[]`,\n  `List<String>`→`List<byte[]>`); count/index/model results stay shared.\n- **Modules: no binary variants at all.**\n\n## Params class conventions (`params/`)\n\n- Implement `IParams` with `addParams(CommandArguments args)`. Fluent setters\n  return `this`; provide a static factory named after the class\n  (`increxParams()`, `rangeParams()`).\n- Do NOT extend another command's params hierarchy. If two overloads share\n  options but differ in a typed field (long vs double), create a self-typed\n  abstract base: `BaseFooParams<T extends BaseFooParams<T>>` with a private\n  `self()` cast, plus two concrete subclasses — compile-time type safety over a\n  polymorphic single class.\n- Implement **`equals`/`hashCode`** (needed for Mockito matching in mocked tests)\n  and keep them in sync with every new field. `toString` is not required on\n  params classes.\n- Validate eagerly in setters with descriptive `IllegalArgumentException` /\n  `IllegalStateException` messages, consistent wording across sibling classes\n  (e.g. \"Aggregators must be non-null and non-empty\"). Required-config checks may\n  run at serialization time. No client-side server-version checks — an old server\n  returning an error is acceptable and should be noted in the PR description.\n- Expiration-style option groups are single-slot, last-wins (mirror `SetParams`).\n- Document the wire-emission order in javadoc and lock it down in unit tests.\n\n## Response mapping (`BuilderFactory` / `resps/`)\n\n- **Reuse existing generic builders whenever the reply maps to a standard shape**\n  (`LONG_LIST`, `DOUBLE_LIST`, `STRING`, `ENCODED_OBJECT_MAP`, …). Maintainers\n  actively reject bespoke response classes for 2-element arrays and the like.\n- Reuse `redis.clients.jedis.util.KeyValue<K,V>` for pair replies.\n- When a model class is genuinely needed (map-shaped INFO replies), put it in\n  `resps/`: public `String` constants for reply field names, a\n  `Map<String,Object>`-taking constructor, typed getters, plus a raw-map accessor\n  for forward compatibility; subclass for FULL/extended variants.\n- Nullable numeric replies are boxed `Long` — **never `Optional`/`OptionalLong`**\n  in the command API; consistency with existing signatures (e.g. `zrank`) wins.\n- Multi-mode commands (one wire command, several reply types by mode) are split\n  into distinctly-named typed Java methods (`aropAggregate`/`aropBitwise`/`aropCount`)\n  rather than one polymorphic method. But mode selected by argument type uses\n  **overloads of one name** (`increx(key, long, …)` / `increx(key, double, …)`),\n  with the mode keyword (BYINT/BYFLOAT) implied by the overload.\n\n## Encoding & enum rules\n\n- String ↔ bytes: `SafeEncoder.encode()` — never `String.getBytes()`.\n- Numeric → bytes: `Protocol.toByteArray()`.\n- Token-valued argument enums live in `args/` and implement `Rawable` with\n  `raw = SafeEncoder.encode(name())` cached in the constructor.\n- Compose multi-token byte values (e.g. comma-joined lists) at the byte level.\n\n## Javadoc, `@since`, `@Experimental`\n\n- **Interface methods only** carry javadoc; implementations (`UnifiedJedis`,\n  `Jedis`, `PipeliningBase`, `CommandObjects`) carry none.\n- String-interface javadoc: `<b><a href=\"https://redis.io/commands/xxx\">XXX\n  Command</a></b>`, prose semantics, `Time complexity: O(...)`, `@param`,\n  `@return`, `@since <version>`. Binary variants get short javadoc with\n  `@see` to the String variant. Pipeline variants: one line — \"Pipeline variant\n  of {@link ...}\" + `@since`.\n- `@since` on every new public method and class, computed from `pom.xml`.\n- If the server feature is **preview/experimental**, annotate ALL new public API\n  (interfaces, methods, params, args, resps) with\n  `redis.clients.jedis.annots.Experimental` and label the PR `experimental`.\n  Do NOT use `@Experimental` for stable GA features.\n\n## Test matrix — what to write\n\nFollow `docs/integration-testing.md` for environment, layout, and naming. The\nestablished per-command test layers (write all that apply):\n\n1. **Params unit test** (`params/FooParamsTest`, plain `*Test`, no Redis):\n   `@Nested` groups (`ValidationTests`, `AddParamsTests`, overload-equivalence),\n   asserting exact wire args and order with\n   `redis.clients.jedis.util.CommandArgumentsMatchers`\n   (`hasArgumentCount`, `hasArguments`) and `RawableFactory.from(...)`; also test\n   the `equals`/`hashCode` contract.\n2. **Mocked delegation tests** (unit, Mockito): add `@Mock CommandObject<T>`\n   fields to `MockedCommandObjectsTestBase` (name them by TYPE, e.g.\n   `listLongCommandObject` — reuse existing ones when the type matches), then\n   `when/verify` tests in `mocked/unified/UnifiedJedis<Group>CommandsTest` and\n   `mocked/pipeline/PipeliningBase<Group>CommandsTest`.\n3. **Unified integration tests**: add methods to the existing abstract\n   `commands/unified/<Group>CommandsTestBase` when one exists; for a new family,\n   create `<Family>CommandsTestBase extends UnifiedJedisCommandsTestBase` plus\n   thin per-topology runners — standalone\n   (`RedisClientCommandsTestHelper.getClient(protocol)`) and cluster\n   (`ClusterCommandsTestHelper.getCleanCluster(protocol)`). **New concrete\n   integration classes MUST be named `*IT`** — never `*IntegrationTest`, never\n   `@Tag(\"integration\")` on new classes.\n4. **Cluster handling**: multi-key commands need cluster test overrides using\n   hash-tagged keys (`s1{:}`) so keys share a slot; tests semantically\n   incompatible with cluster get `@Test @Override` + `@Disabled(\"<reason>\")`\n   empty bodies.\n5. **Legacy `Jedis` coverage**: extend `commands/jedis/<Group>*CommandsTest`\n   (which extends `JedisCommandsTestBase`) — can be minimal (smoke/missing-key)\n   when the unified base covers behavior fully. Binary variants get their own\n   integration tests (reviewers ask for these explicitly).\n\nCross-cutting test conventions:\n- **RESP2/RESP3** comes free: test bases are `@ParameterizedClass` +\n  `@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")`\n  (or `#jedisRespVersions` for legacy) — no per-test work; just make sure new\n  classes inherit the right base.\n- **Gating**: command already released → `@SinceRedisVersion(\"<RC build>\")`\n  (e.g. `\"8.7.225\"` for 8.8) at the shared base-class level ONCE — do not repeat\n  it on subclasses (maintainers remove redundant ones). Command not yet in any\n  GA server → `@EnabledOnCommand(\"<COMMAND>\")` (capability probe via COMMAND\n  INFO). Module presence as precondition → `assumeTrue(hasCommand(...))` probes.\n  Environment exclusions → `@ConditionalOnEnv(value = TestEnvUtil.ENV_...,\n  enabled = false)` (e.g. skip Redis Enterprise for brand-new features).\n- Cover the **whole family the option touches** (store + non-store variants,\n  with/without optional args, standalone + cluster), asserting real semantics,\n  not just \"no error\". Avoid brittle assertions (no hard-coded `hashCode()`\n  literals).\n\n## Running the tests\n\nJedis pins the **CI JDK (Java 8)** for the test build — check\n`.github/workflows/` for the exact version and use a matching local JDK. Verify\nwith `java -version` before running anything; a newer JDK may fail the build or\nsilently produce the wrong bytecode target.\n\n- Unit tests (Surefire, no Redis): `mvn -B test`, or `mvn -Dtest=FooParamsTest test`.\n- Integration tests (Failsafe) need the Docker env and the `verify` lifecycle:\n  ```bash\n  make start version=8.8        # pick the version that carries the new command\n  mvn -B verify                 # or: mvn -Dtest=FooIT verify\n  make stop\n  ```\n  Never run integration coverage via `mvn test`/`surefire:test` — Failsafe owns\n  `*IT` classes. If a brand-new command isn't in any published\n  `redislabs/client-libs-test` tag yet, say so: the integration tests will be\n  skipped/gated (that is expected and acceptable — `@EnabledOnCommand` /\n  `@SinceRedisVersion` handle it), but they must still be written and compile.\n- **Unattended:** no `make start`/`make stop`. The servers are already running\n  and `$REDIS_ENDPOINTS_CONFIG_PATH` points the tests at them. Run:\n  1. Unit tests: `mvn -B test`, or the test command the invoking prompt gives.\n  2. The changed family's integration tests on **both** topologies, through\n     Failsafe only. Two kinds of runner exist:\n     - existing core families: `@Tag(\"integration\")` `*Test` classes (Failsafe's\n       `it-tagged` execution), namely the unified standalone runner\n       `RedisClient<Family>CommandsTest`, the cluster runner\n       `Cluster<Family>CommandsTest` and the legacy `commands/jedis/<Family>CommandsTest`\n     - module and new families: `*IT` classes (the `it-suffix` execution), a\n       standalone runner in `commands/unified/client/<module>/` (e.g.\n       `BloomRedisClientCommandsIT`, on `modules-docker`) and a cluster runner in\n       `commands/unified/cluster/<module>/` (e.g. `BloomClusterCommandsIT`, on\n       `cluster-stable`; the cluster image loads the modules too)\n     \n     Select them with one wildcard that covers both kinds and every topology, plus\n     an explicit name for any new runner that doesn't match it:\n     ```bash\n     mvn -B -DskipUnitTests=true -Dit.failIfNoSpecifiedTests=false \\\n       -Dit.test='*<Family>*Commands*' verify\n     ```\n     `-Dit.failIfNoSpecifiedTests=false` is needed because each Failsafe execution\n     sees the same filter and one of them usually matches nothing. That also means\n     `verify` can pass having run **no** test, so don't trust the exit code alone.\n  3. Prove it ran: read `target/failsafe-reports/*.txt` (one file per class, named\n     by its fully qualified class name) and list every class that ran with its counts.\n     A class in package `redis.clients.jedis.commands.unified.cluster` (any\n     sub-package) is a cluster run; anything else is standalone. Don't go by the\n     name prefix: module runners are `<Family>ClusterCommandsIT`, and one is\n     `FTHybridCommandsClusterIT`. At least one standalone class and one cluster class\n     must report `Tests run:` > 0 for the changed commands. This applies to module\n     families too. If either topology ran nothing, fix the filter and rerun. Don't\n     report success.\n  \n  Tests that need endpoints the file doesn't have (sentinel, TLS, ACL users,\n  `cluster-unbound`) are out of scope: list them as not run. Report passed /\n  failed / skipped per topology, and the classes run.\n\n## PR hygiene checklist (verify before finishing)\n\n- [ ] Every layer of the chosen matrix updated consistently (a return-type change\n      must propagate across all 4 interfaces × 3 implementations × all test layers).\n- [ ] New files added to the `pom.xml` formatter-plugin includes.\n- [ ] `@since` (and `@Experimental` if preview) on all new public API.\n- [ ] No unused imports, no wildcard imports, no dead `Protocol.Keyword` constants.\n- [ ] `equals`/`hashCode` on params updated and unit-tested.\n- [ ] `docs/` updated if user-facing behavior/configuration changed; migration\n      guide entry if anything breaks.\n- [ ] PR description states: server PR link, version gate choice and why, which\n      layers intentionally did NOT change and why, and behavior against older\n      servers. (**Unattended:** put these in the final report; the automation\n      builds the PR description from it.)\n","frontmatter":{"name":"extend-commands-api","description":"Add or extend Redis commands in the Jedis client API — a new core command, a family of new commands, an extension to an existing command's options, or a module command (Search/TimeSeries/JSON/Bloom). Gathers evidence (Redis server PR, HLD document), plans the full implementation matrix, then implements with unit and integration tests following Jedis maintainer conventions. Runs supervised (plan mode, user approval) by default, or unattended for automation such as the RedisClientsBot parity pipeline.","metadata":{"modes":"supervised, unattended"}},"isInternal":false,"tokens":7087,"sizeBytes":29087},{"name":"createPRDescription.md","path":".claude/commands/createPRDescription.md","rawUrl":"https://raw.githubusercontent.com/redis/jedis/HEAD/.claude/commands/createPRDescription.md","title":"Command: /createprdescription","category":"command-prompt","format":"markdown","content":"---\ndescription: Generate a PR title and description from the diff between two local branches, using the creating-description-for-gh-pr skill, and save to prDescription.md\nargument-hint: [base-branch] [target-branch]\n---\n\nUse the \"creating-description-for-gh-pr\" skill to generate a PR title and description.\n\nArguments provided to this command: base = `$1`, target = `$2` (either may be empty).\n\nPass this context into the skill's branch resolution logic (Step 1). Since these are positional arguments, only three states are possible:\n- Both `$1` and `$2` given → `$1` is the explicit base branch, `$2` is the explicit target branch.\n- Only `$1` given (`$2` empty) → `$1` is the explicit base branch; let the skill default the target branch.\n- Neither given (both empty) → let the skill default both base and target branches.\n\n`$1` cannot be empty while `$2` is non-empty — do not handle that case.\n\nDo not duplicate or reinterpret the skill's instructions; follow them as written, including the output structure, the writing-style constraints, and the save/confirm steps.\n","isInternal":false,"tokens":247,"sizeBytes":1082}],"systemPromptSnippet":"<yakaai_skills repo=\"redis/jedis\">\n<!-- File: AGENTS.md (Tokens: ~1420 | Category: root-instruction) -->\n# Jedis Coding Conventions\nJedis is a synchronous Java client for Redis, shipped as a library on Maven Central as `redis.clients:jedis`.\nIt covers standalone, cluster, sentinel, and multi-db setups — with pipelining, transactions, pub/sub, and Redis Stack modules — and targets Java 8 for source/binary compatibility.\n\n## General Principles\n- **Maintain JDK 8 compatibility.** All production and test code must compile and run on JDK 8.\n- **Keep documentation up to date.** If a change affects user-facing behavior, configuration, or the public API, update the relevant pages under [`docs/`](docs) (MkDocs).\n- **Add or update tests.** Every bug fix should include a regression test, and every new feature should include appropriate tests. Follow the conventions in [`docs/integration-testing.md`](docs/integration-testing.md) for choosing between unit and integration tests.\n- **Document breaking changes.** Any breaking change must be documented in the appropriate migration guide under [`docs/migration-guides/`](docs/migration-guides).\n- **Record behavior changes in the release notes.** If a change alters user-visible behavior without breaking the API, add an entry to the running notes for the next release under [`docs/release-notes/`](docs/release-notes) in the same PR, following the entry format in that file.\n- **Follow the contribution guidelines.** Ensure all changes comply with the project rules in [`.github/CONTRIBUTING.md`](.github/CONTRIBUTING.md).\n- **Request approval before adding dependencies.** Do not introduce new runtime, test, or build dependencies without explicit confirmation.\n- **Review-friendly changes.** Keep commits and pull requests focused and logically grouped. Separate feature or bug-fix changes from refactoring, formatting, and other mechanical changes whenever practical.\n\n## Development Environment\n- **Use the CI JDK version.** Build and test using the same JDK version configured in the GitHub Actions workflows. Check `.github/workflows/` (or `docs/integration-testing.md`) to determine the required version before reproducing CI failures.\n\n## Build & Test Commands\n- `make start version=8.6` + `mvn clean verify` + `make stop` — full run against\n  the Docker Redis env (`make test` does all three).\n- `mvn test` — unit tests (no Redis); `mvn -Dtest=ClassName test` for one class.\n\n## Implementation Guidelines\n### Code Style\n- Keep comments short and informative. Comment **why**, not **what**. Avoid comments that simply restate the code.\n- Remove unused imports. Do not use wildcard imports. Avoid fully qualified class names unless necessary to resolve naming conflicts.\n- **Annotate all new public API with `@since`.** Determine the version from the current build by running:\n  ```sh\n  mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n  ```\n  (or by reading the top-level `<version>` in `pom.xml`), then remove the `-SNAPSHOT` suffix. For example, `8.0.0-SNAPSHOT` becomes `@since 8.0`.\n\n### Test Conventions\n- Name new unit test classes using the `*Test` convention.\n- Name new integration test classes using the `*IT` convention. Do not use the `integration` tag for new tests.\n\n## Architecture\n\nSee [`docs/redis-client-components-overview.md`](docs/redis-client-components-overview.md)\nfor a high-level walkthrough (executors, providers, builders, and command execution flows).\n\n`UnifiedJedis` is the core client. It implements the command interfaces and\ndelegates to three collaborators:\n\n- `ConnectionProvider` (`providers/`) — obtains connections: pooled, cluster,\n  sentinel, or multi-db.\n- `CommandExecutor` (`executors/`) — runs a command: simple, retry, cluster\n  routing, or failover.\n- `CommandObjects` — factory building a typed `CommandObject<T>` per command.\n\n**Modern clients** extend `UnifiedJedis` and are built through\n`AbstractClientBuilder` subclasses (`builders/`): `RedisClient`,\n`RedisClusterClient`, `RedisSentinelClient`, `MultiDbClient`. A new client\noverrides `createDefaultConnectionProvider()`, `createDefaultCommandExecutor()`,\n`createClient()`, and `validateSpecificConfiguration()` (run before `build()`).\n\n**Legacy client:** `Jedis` is the single-connection client (pooling via\n`JedisPool`), still supported. `JedisPool`, `JedisCluster`, and\n`JedisSentinelPool` are `@Deprecated` in favor of the builder-based clients.\n\n**Feature modules** expose dedicated command interfaces under\n`redis.clients.jedis.<module>`: `search`, `json`, `bloom`, `timeseries`, plus\n`csc` (client-side caching) and `mcf` (multi-db / failover).\n\n## Conventions\n\n**Adding or changing a command** — trace an existing command first, then update\nthe full matrix so all surfaces stay in sync:\n\n1. **Command interfaces** (`commands/`): `<Group>Commands` and\n   `<Group>BinaryCommands` (String vs `byte[]`), each with its `<Group>Pipeline…`\n   variant.\n2. **`CommandObjects`** — build the args and a response `Builder<T>`, e.g.\n   `new CommandObject<>(commandArguments(GET).key(key), BuilderFactory.STRING)`.\n   `ClusterCommandObjects` overrides these to enforce cluster constraints.\n3. **`UnifiedJedis`** and **`PipeliningBase`** — the execution entry points.\n4. **`Jedis`** — add it here too, for backward compatibility.\n\n**Encoding:**\n- String ↔ bytes: `SafeEncoder.encode()` — never `String.getBytes()` (breaks GBK).\n- Numeric → bytes: `Protocol.toByteArray()`.\n\n## Agent Skills\n\nTask procedures live in [`.agents/skills/`](.agents/skills) (also reachable as\n`.claude/skills/`). Read the matching `SKILL.md` before that kind of work:\n\n- `extend-commands-api` — add or extend a Redis command end-to-end (evidence, decision tree, full\n  String/binary/pipeline matrix, tests), supervised or unattended.\n- `create-implementation-plan-for-redis-api-change` — turn a shared client HLD into Jedis's reviewed\n  implementation plan (API, files, ordered steps, test matrix) without touching sources; read-only,\n  writes one plan file; conventions come from `extend-commands-api`.\n- `creating-description-for-gh-pr` — draft a PR title and description from the diff between two\n  local branches.\n\n<!-- File: .agents/skills/creating-description-for-gh-pr/SKILL.md (Tokens: ~761 | Category: anthropic-skill) -->\n---\nname: creating-description-for-gh-pr\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.\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## 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: ~7087 | Category: anthropic-skill) -->\n---\nname: extend-commands-api\ndescription: Add or extend Redis commands in the Jedis client API — a new core command, a family of new commands, an extension to an existing command's options, or a module command (Search/TimeSeries/JSON/Bloom). Gathers evidence (Redis server PR, HLD document), plans the full implementation matrix, then implements with unit and integration tests following Jedis maintainer conventions. Runs supervised (plan mode, user approval) by default, or unattended for automation such as the RedisClientsBot parity pipeline.\nmetadata:\n  modes: supervised, unattended\n---\n\n# Extend the Jedis Commands API\n\nImplement a new Redis command (or extend an existing one) in Jedis, following the\nconventions Jedis maintainers enforce in review.\n\n## Modes — read this first\n\nThis skill runs in one of two modes. The engineering rules in every section below\nare identical in both; only who answers questions and approves the plan differs.\n\n- **Supervised** (default): a human is in the session. Ask the questions, present\n  the plan in plan mode and wait for approval, as written below.\n- **Unattended**: no human will answer or approve anything, so never stop to wait.\n  Use it ONLY when the invoking prompt says `Mode: unattended` or the environment\n  has `CLIENT_SKILL_MODE=unattended`. Never switch to it on your own.\n\nIn unattended mode the invoking prompt supplies the inputs a human would give:\nthe HLD path (normally `./HLD.md`), the server PR reference, the test command, and\ntwo running servers from the `redislabs/client-libs-test` image it names, password-\nprotected like the local Docker env and without TLS: a standalone (`$REDIS_URL`,\npassword `$REDIS_STANDALONE_PASSWORD`, i.e. `foobared`) and a 6-node cluster with 3\nmasters and 3 replicas (`$REDIS_CLUSTER_URLS`, password `$REDIS_CLUSTER_PASSWORD`,\ni.e. `cluster`). `$REDIS_ENDPOINTS_CONFIG_PATH` already points at an `endpoints.json`\nthat describes them, passwords included, as `standalone0`, `modules-docker` (the\nstandalone; the image ships the modules) and `cluster-stable`. Jedis tests read that\nvariable (`TestEnvUtil.getEndpointsConfigPath`) and authenticate with the passwords in\nit, so the integration tests run against both servers unchanged. Wherever a step below has an **Unattended:**\nnote, follow the note:\n\n| Step | Supervised | Unattended |\n|---|---|---|\n| Phase 0.1 HLD | ask the user for the path | read the given HLD fully |\n| Phase 0.2 server PR | `gh`, asking to run it outside the sandbox if needed | no `gh` at all: the given PR reference, the HLD's facts, else the unauthenticated GitHub REST API |\n| Phase 0.3 environment / image tag | `make start`; ask for an image tag if the command is missing | no Docker: probe the given standalone and cluster; if the command is missing there, continue with gated tests |\n| Phase 0.4 redis-cli scenarios | run against `standalone0` | run against the given Redis URL; if `redis-cli` is unavailable, write them as unverified transcripts |\n| Phase 1 plan + approval | plan mode, ask before editing | write the plan into the final report, then implement it in the same session |\n| Running tests | `make start`, `mvn -B verify`, `make stop` | `mvn -B test` (unit), then the changed family's integration tests on standalone **and** cluster through `$REDIS_ENDPOINTS_CONFIG_PATH` (see Running the tests) |\n| An open design question | ask the user | take the HLD's choice (else the most consistent existing Jedis convention) and list it as an open question in the report |\n\nUnattended runs also follow these rules:\n- **Change files.** A run that ends without changes has failed. Stop without\n  editing only when implementing is impossible, and say exactly why in the report.\n- **Don't commit, push, or open a PR.** The automation that invoked you does that.\n  Don't edit `.github/` workflows, release/version files or `pom.xml` versions.\n  The formatter includes in `pom.xml` are the exception (see B.7).\n- **Finish with a report** covering, in plain text with no placeholders:\n  - what was implemented\n  - the decision-tree class (A–D) and the plan\n  - design decisions, and which layers intentionally did NOT change and why\n  - unit results; integration results with the test classes run on standalone and on\n    cluster; integration tests left gated, skipped, or out of scope\n  - steps skipped because they need a human or Docker\n  - open questions for maintainers\n\n  This feeds the PR description (see the PR hygiene checklist).\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 the user for the path to a\n   markdown file containing the High-Level Design for the command(s) (or confirm\n   that no HLD exists). If a path is given, read it fully — it is the primary\n   source for syntax, semantics, reply shape, and edge cases.\n   **Unattended:** don't ask; read the HLD path the invoking prompt gives.\n2. **Find the server-side PR in the `redis/redis` GitHub repo.**\n   **Unattended:** skip every `gh` command in this step: `gh auth status`, and the\n   `gh search` / `gh pr view` / `gh pr diff` block below. Don't ask for permission.\n   Use the server PR reference the invoking prompt gives. The HLD usually quotes\n   the syntax, the reply shapes and the first version. Fill any gaps from the\n   unauthenticated GitHub REST API (e.g.\n   `https://api.github.com/repos/redis/redis/pulls/<num>/files`, then the raw\n   `src/commands/*.json` file), and go on to \"From the server PR, extract\".\n\n   **Supervised:** first verify that `gh` works in the current (sandboxed)\n   environment:\n   ```bash\n   gh auth status\n   ```\n   Sandboxes often block access to credential files (`~/.netrc`, gh config), so\n   `gh` may report unauthenticated here even though it works on the user's\n   machine. If `gh auth status` fails or reports no authentication, **ask the\n   user for permission to run the `gh` commands outside the sandbox** (e.g. with\n   the sandbox override for these specific read-only commands), explaining that\n   `gh` cannot reach its credentials from inside the sandbox. Only if the user\n   declines (or `gh` is genuinely not logged in anywhere) fall back to the\n   unauthenticated GitHub REST API or fetching\n   `https://github.com/redis/redis/pulls?q=<command>`.\n\n   Then search for the PR that adds/extends the command on the server:\n   ```bash\n   gh search prs --repo redis/redis \"<COMMAND NAME>\" --limit 10\n   gh pr view <num> --repo redis/redis\n   gh pr diff <num> --repo redis/redis   # look at src/commands/*.json for exact syntax\n   ```\n   From the server PR,\n   extract: exact wire syntax (argument order and optionality), reply type per\n   RESP2/RESP3, error conditions, time complexity, and — critically — the **first\n   server version carrying the feature** (RC builds like `8.7.225` = Redis 8.8 RC\n   are used for test gating). Note whether the server marks the feature\n   *experimental / in preview*.\n3. **Verify the command exists on the \"next\" Redis OSS version.** Start the\n   integration environment using the **latest** `.env.vX.XX` file under\n   `src/test/resources/env/` (pick the highest version numerically, not\n   lexicographically — e.g. `8.10` > `8.8`):\n   ```bash\n   ls src/test/resources/env/.env.v* | sort -V | tail -1   # → e.g. .env.v8.10\n   make start version=8.10\n   ```\n   Then probe the `standalone0` endpoint. Read its connection details from\n   `src/test/resources/endpoints.json` (currently `redis://localhost:6379`,\n   password `foobared`) and check the target command via redis-cli:\n   ```bash\n   redis-cli -u redis://localhost:6379 -a foobared INFO server | grep redis_version\n   redis-cli -u redis://localhost:6379 -a foobared COMMAND INFO <COMMAND>   # non-empty → command exists\n   redis-cli -u redis://localhost:6379 -a foobared 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\n   EXTENDED command, additionally invoke it with the new syntax against a\n   scratch key and confirm the server accepts it (no `ERR syntax error` /\n   `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 for the target change. Interactively\n   **ask the user for a `redislabs/client-libs-test` image tag** that contains it\n   (e.g. an RC/edge/milestone build — tags are listed at\n   https://hub.docker.com/r/redislabs/client-libs-test/tags; you may check them\n   yourself and suggest candidates). Then restart the environment with that tag\n   and re-run the probes:\n   ```bash\n   make stop\n   make start CLIENT_LIBS_TEST_IMAGE_TAG=<tag>\n   ```\n   If the user has no tag to offer (or the probe still fails), report it and\n   proceed anyway: the implementation can continue, but integration tests will\n   be gated/skipped until a `redislabs/client-libs-test` image ships the change.\n   Keep the environment running for later test runs, or `make stop` if you\n   won't need it soon.\n   **Unattended:** no Docker, no `make start`/`make stop`, no image-tag question.\n   The servers already run the target image. Probe the standalone with the same\n   `INFO server` / `COMMAND INFO` / `COMMAND DOCS` checks (e.g.\n   `redis-cli -u \"$REDIS_URL\" COMMAND INFO <COMMAND>`; the URL carries the\n   credentials), and the cluster with `redis-cli -c -h \"$REDIS_CLUSTER_HOST\"\n   -p \"$REDIS_CLUSTER_START_PORT\" -a \"$REDIS_CLUSTER_PASSWORD\" --no-auth-warning CLUSTER INFO`\n   (expect `cluster_state:ok`). If the command is missing, continue anyway with\n   gated integration tests, and say so in the report.\n4. **Create redis-cli showcase test cases.** Once the command is available on\n   the running environment, derive a small set of redis-cli scenarios from the\n   HLD and the server PR and run them against `standalone0`. These serve two\n   purposes at once:\n   - **Smoke test**: prove the documented behavior actually holds on the target\n     server — happy path, each new option/keyword, reply shape (run with\n     `redis-cli -3` too if RESP3 replies differ), edge cases and error\n     conditions called out in the HLD (empty/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 that explains\n     WHY the command/option exists — what a user could not do (or could only do\n     awkwardly) before, and how the new API covers it. Prefer realistic data\n     over `foo`/`bar` (e.g. rate-limit counters for a bounded INCR, sensor\n     readings for a time-series aggregator).\n\n   Save the scenarios as a commented script in a scratch file (one block per\n   use-case: a one-line \"what this demonstrates\" comment, the redis-cli\n   commands, and the observed reply pasted back as comments). Carry this\n   material forward: it feeds the Phase 1 plan (as the explanation of the\n   feature and the source of expected values for tests), the Java test\n   assertions, and the PR description. If the command could not be made\n   available on any image (see step 3), still write the scenarios from the\n   HLD/PR as *expected* transcripts and mark them unverified.\n   **Unattended:** run the scenarios against `$REDIS_URL` instead of\n   `standalone0`. For a keyed command, also run one through the cluster. Always\n   pass the Redis command as arguments: a bare `redis-cli` waits on stdin and\n   hangs a headless run.\n   ```bash\n   redis-cli -u \"$REDIS_URL\" <COMMAND> <args...>\n   redis-cli -c -h \"$REDIS_CLUSTER_HOST\" -p \"$REDIS_CLUSTER_START_PORT\" \\\n     -a \"$REDIS_CLUSTER_PASSWORD\" --no-auth-warning <COMMAND> <args...>\n   ```\n   If `redis-cli` isn't installed, write them as unverified expected transcripts. Keep the scratch file out of the change; carry the\n   scenarios into the report instead.\n5. **Read `docs/integration-testing.md`** in the Jedis repo — it defines the test\n   environment, endpoint discovery, `*Test` vs `*IT` naming, and how to run tests.\n6. **Trace one analogous existing command** end-to-end in the codebase (same\n   command group, similar reply shape) so the plan mirrors real code, not\n   guesswork.\n7. Determine the `@since` version:\n   ```bash\n   mvn help:evaluate -Dexpression=project.version -q -DforceStdout\n   ```\n   Strip `-SNAPSHOT` (e.g. `8.0.0-SNAPSHOT` → `@since 8.0`).\n\n## Phase 1 — Plan, then approval (supervised) or straight to implementation (unattended)\n\nUsing the evidence, classify the change with the decision tree below, enumerate\nthe exact file-by-file touch list, the test matrix, and the gating annotations.\nOpen the plan with a short \"what this feature enables\" section built from the\nredis-cli showcase scenarios (Phase 0 step 4), including one or two\nrepresentative command/reply transcripts, so the reader sees the use-case before\nthe file list.\n\n- **Supervised:** enter **plan mode**, present the plan to the user and\n  **explicitly ask permission to execute in auto-accept mode** before\n  implementing. Do not start editing files until the user approves.\n- **Unattended:** don't enter plan mode and don't wait. The automation's\n  approval was the merged HLD. Keep the plan for the final report, then\n  implement it right away, following the whole matrix exactly as a supervised\n  run would.\n\n## Decision tree — what kind of change is this?\n\n**A. Extension of an existing command that fits an existing params class**\n(e.g. new enum value / new option token):\n- Touch ONLY the params/model classes + tests. Do NOT touch command interfaces,\n  CommandObjects, UnifiedJedis, PipeliningBase, or Jedis — the existing\n  `IParams.addParams(CommandArguments)` delegation carries the new option through\n  automatically, for both String and binary surfaces.\n- If the option is a bare token: add an enum constant implementing `Rawable`\n  (bytes come from `SafeEncoder.encode(name())` — self-wiring).\n- If it's a new field: add a fluent setter returning `this`, extend\n  `addParams()`, and **update `equals`/`hashCode`** in sync.\n- If the reply shape grows (e.g. more array elements): extend the response model\n  class backward-compatibly — keep old constructors/getters working, add new\n  accessors, update the builder in the relevant `*BuilderFactory` to branch on\n  reply size. Jedis does not defensively copy response collections (stated\n  maintainer convention).\n\n**B. New core command(s)**  — the FULL matrix, every layer:\n1. `Protocol.java` — new `Command` enum constant(s); new `Keyword` constants for\n   sub-tokens, grouped under a `// <FEATURE> keywords` comment, alphabetical.\n   **Never add a Keyword that duplicates a token already carried by a dedicated\n   `Rawable` enum** (dead keywords get flagged in review).\n2. Command interfaces in `commands/`: `<Group>Commands`, `<Group>BinaryCommands`,\n   `<Group>PipelineCommands`, `<Group>PipelineBinaryCommands`. For a whole new\n   command *family*, create four new `<Family>*Commands` interfaces and add one\n   `extends` entry to `JedisCommands`, `JedisBinaryCommands`, `PipelineCommands`,\n   `PipelineBinaryCommands`.\n3. `CommandObjects` — String and byte[] factory methods side by side under a\n   `// <Feature> commands` section. `ClusterCommandObjects` needs overrides ONLY\n   for multi-key commands requiring slot checks; single-key commands need nothing.\n4. `UnifiedJedis` — one-line `@Override` delegating to\n   `executeCommand(commandObjects.xxx(...))`.\n5. `PipeliningBase` — `appendCommand(commandObjects.xxx(...))` returning\n   `Response<T>`.\n6. `Jedis` (legacy) — `checkIsInMultiOrPipeline(); connection.executeCommand(commandObjects.xxx(...))`.\n7. `pom.xml` — add every new file to the **formatter-maven-plugin includes** list\n   (this repo format-enforces an allowlist; new files must be registered).\n\n**C. Extension needing new overloads / a new params class** (hybrid): new methods\ngo through the full matrix of B; the option plumbing follows the params\nconventions below.\n\n**D. Module command (search / timeseries / json / bloom)**:\n- Module interfaces are **String-only** — there are NO `*BinaryCommands` variants\n  for modules; never create them.\n- Module commands/keywords live in the module protocol class\n  (`TimeSeriesProtocol.TimeSeriesCommand/TimeSeriesKeyword`,\n  `SearchProtocol.SearchCommand/SearchKeyword`, …), each an enum implementing\n  `ProtocolCommand`/`Rawable` with `SafeEncoder.encode()`-cached bytes.\n- Strongly prefer extending the module's params/builder classes over adding\n  interface methods — PRs #4504 and #4534 shipped whole features with ZERO\n  changes to interfaces, CommandObjects, UnifiedJedis, or PipeliningBase.\n- Search aggregation builders (`Reducer` subclasses) build a `List<Object>` via\n  `getOwnArgs()`; the parent computes `narg` automatically. Canonicalize clause\n  order at serialization time regardless of builder call order.\n- Module response parsing lives in the module's builder factory\n  (`TimeSeriesBuilderFactory`, `SearchBuilderFactory`); search aggregation results\n  are loosely typed and often absorb new reply content with no parsing changes.\n\n## Binary (byte[]) variant policy\n\n- **Core commands: full String/byte[] parity is mandatory** — all four\n  interfaces, paired CommandObjects methods, and all three clients. Reviewers\n  explicitly request binary integration tests too.\n- Only **keys and textual values** get `byte[]` overloads. Numeric/structural\n  args (`long index`, ranges, booleans, enums) stay identical.\n- **Params classes are shared, never duplicated**: one params class serves both\n  surfaces; if a param carries text, give the params class both `foo(String)` and\n  `foo(byte[])` setters (see `ArgrepParams`).\n- Return types mirror the key type (`String`→`byte[]`,\n  `List<String>`→`List<byte[]>`); count/index/model results stay shared.\n- **Modules: no binary variants at all.**\n\n## Params class conventions (`params/`)\n\n- Implement `IParams` with `addParams(CommandArguments args)`. Fluent setters\n  return `this`; provide a static factory named after the class\n  (`increxParams()`, `rangeParams()`).\n- Do NOT extend another command's params hierarchy. If two overloads share\n  options but differ in a typed field (long vs double), create a self-typed\n  abstract base: `BaseFooParams<T extends BaseFooParams<T>>` with a private\n  `self()` cast, plus two concrete subclasses — compile-time type safety over a\n  polymorphic single class.\n- Implement **`equals`/`hashCode`** (needed for Mockito matching in mocked tests)\n  and keep them in sync with every new field. `toString` is not required on\n  params classes.\n- Validate eagerly in setters with descriptive `IllegalArgumentException` /\n  `IllegalStateException` messages, consistent wording across sibling classes\n  (e.g. \"Aggregators must be non-null and non-empty\"). Required-config checks may\n  run at serialization time. No client-side server-version checks — an old server\n  returning an error is acceptable and should be noted in the PR description.\n- Expiration-style option groups are single-slot, last-wins (mirror `SetParams`).\n- Document the wire-emission order in javadoc and lock it down in unit tests.\n\n## Response mapping (`BuilderFactory` / `resps/`)\n\n- **Reuse existing generic builders whenever the reply maps to a standard shape**\n  (`LONG_LIST`, `DOUBLE_LIST`, `STRING`, `ENCODED_OBJECT_MAP`, …). Maintainers\n  actively reject bespoke response classes for 2-element arrays and the like.\n- Reuse `redis.clients.jedis.util.KeyValue<K,V>` for pair replies.\n- When a model class is genuinely needed (map-shaped INFO replies), put it in\n  `resps/`: public `String` constants for reply field names, a\n  `Map<String,Object>`-taking constructor, typed getters, plus a raw-map accessor\n  for forward compatibility; subclass for FULL/extended variants.\n- Nullable numeric replies are boxed `Long` — **never `Optional`/`OptionalLong`**\n  in the command API; consistency with existing signatures (e.g. `zrank`) wins.\n- Multi-mode commands (one wire command, several reply types by mode) are split\n  into distinctly-named typed Java methods (`aropAggregate`/`aropBitwise`/`aropCount`)\n  rather than one polymorphic method. But mode selected by argument type uses\n  **overloads of one name** (`increx(key, long, …)` / `increx(key, double, …)`),\n  with the mode keyword (BYINT/BYFLOAT) implied by the overload.\n\n## Encoding & enum rules\n\n- String ↔ bytes: `SafeEncoder.encode()` — never `String.getBytes()`.\n- Numeric → bytes: `Protocol.toByteArray()`.\n- Token-valued argument enums live in `args/` and implement `Rawable` with\n  `raw = SafeEncoder.encode(name())` cached in the constructor.\n- Compose multi-token byte values (e.g. comma-joined lists) at the byte level.\n\n## Javadoc, `@since`, `@Experimental`\n\n- **Interface methods only** carry javadoc; implementations (`UnifiedJedis`,\n  `Jedis`, `PipeliningBase`, `CommandObjects`) carry none.\n- String-interface javadoc: `<b><a href=\"https://redis.io/commands/xxx\">XXX\n  Command</a></b>`, prose semantics, `Time complexity: O(...)`, `@param`,\n  `@return`, `@since <version>`. Binary variants get short javadoc with\n  `@see` to the String variant. Pipeline variants: one line — \"Pipeline variant\n  of {@link ...}\" + `@since`.\n- `@since` on every new public method and class, computed from `pom.xml`.\n- If the server feature is **preview/experimental**, annotate ALL new public API\n  (interfaces, methods, params, args, resps) with\n  `redis.clients.jedis.annots.Experimental` and label the PR `experimental`.\n  Do NOT use `@Experimental` for stable GA features.\n\n## Test matrix — what to write\n\nFollow `docs/integration-testing.md` for environment, layout, and naming. The\nestablished per-command test layers (write all that apply):\n\n1. **Params unit test** (`params/FooParamsTest`, plain `*Test`, no Redis):\n   `@Nested` groups (`ValidationTests`, `AddParamsTests`, overload-equivalence),\n   asserting exact wire args and order with\n   `redis.clients.jedis.util.CommandArgumentsMatchers`\n   (`hasArgumentCount`, `hasArguments`) and `RawableFactory.from(...)`; also test\n   the `equals`/`hashCode` contract.\n2. **Mocked delegation tests** (unit, Mockito): add `@Mock CommandObject<T>`\n   fields to `MockedCommandObjectsTestBase` (name them by TYPE, e.g.\n   `listLongCommandObject` — reuse existing ones when the type matches), then\n   `when/verify` tests in `mocked/unified/UnifiedJedis<Group>CommandsTest` and\n   `mocked/pipeline/PipeliningBase<Group>CommandsTest`.\n3. **Unified integration tests**: add methods to the existing abstract\n   `commands/unified/<Group>CommandsTestBase` when one exists; for a new family,\n   create `<Family>CommandsTestBase extends UnifiedJedisCommandsTestBase` plus\n   thin per-topology runners — standalone\n   (`RedisClientCommandsTestHelper.getClient(protocol)`) and cluster\n   (`ClusterCommandsTestHelper.getCleanCluster(protocol)`). **New concrete\n   integration classes MUST be named `*IT`** — never `*IntegrationTest`, never\n   `@Tag(\"integration\")` on new classes.\n4. **Cluster handling**: multi-key commands need cluster test overrides using\n   hash-tagged keys (`s1{:}`) so keys share a slot; tests semantically\n   incompatible with cluster get `@Test @Override` + `@Disabled(\"<reason>\")`\n   empty bodies.\n5. **Legacy `Jedis` coverage**: extend `commands/jedis/<Group>*CommandsTest`\n   (which extends `JedisCommandsTestBase`) — can be minimal (smoke/missing-key)\n   when the unified base covers behavior fully. Binary variants get their own\n   integration tests (reviewers ask for these explicitly).\n\nCross-cutting test conventions:\n- **RESP2/RESP3** comes free: test bases are `@ParameterizedClass` +\n  `@MethodSource(\"redis.clients.jedis.commands.CommandsTestsParameters#respVersions\")`\n  (or `#jedisRespVersions` for legacy) — no per-test work; just make sure new\n  classes inherit the right base.\n- **Gating**: command already released → `@SinceRedisVersion(\"<RC build>\")`\n  (e.g. `\"8.7.225\"` for 8.8) at the shared base-class level ONCE — do not repeat\n  it on subclasses (maintainers remove redundant ones). Command not yet in any\n  GA server → `@EnabledOnCommand(\"<COMMAND>\")` (capability probe via COMMAND\n  INFO). Module presence as precondition → `assumeTrue(hasCommand(...))` probes.\n  Environment exclusions → `@ConditionalOnEnv(value = TestEnvUtil.ENV_...,\n  enabled = false)` (e.g. skip Redis Enterprise for brand-new features).\n- Cover the **whole family the option touches** (store + non-store variants,\n  with/without optional args, standalone + cluster), asserting real semantics,\n  not just \"no error\". Avoid brittle assertions (no hard-coded `hashCode()`\n  literals).\n\n## Running the tests\n\nJedis pins the **CI JDK (Java 8)** for the test build — check\n`.github/workflows/` for the exact version and use a matching local JDK. Verify\nwith `java -version` before running anything; a newer JDK may fail the build or\nsilently produce the wrong bytecode target.\n\n- Unit tests (Surefire, no Redis): `mvn -B test`, or `mvn -Dtest=FooParamsTest test`.\n- Integration tests (Failsafe) need the Docker env and the `verify` lifecycle:\n  ```bash\n  make start version=8.8        # pick the version that carries the new command\n  mvn -B verify                 # or: mvn -Dtest=FooIT verify\n  make stop\n  ```\n  Never run integration coverage via `mvn test`/`surefire:test` — Failsafe owns\n  `*IT` classes. If a brand-new command isn't in any published\n  `redislabs/client-libs-test` tag yet, say so: the integration tests will be\n  skipped/gated (that is expected and acceptable — `@EnabledOnCommand` /\n  `@SinceRedisVersion` handle it), but they must still be written and compile.\n- **Unattended:** no `make start`/`make stop`. The servers are already running\n  and `$REDIS_ENDPOINTS_CONFIG_PATH` points the tests at them. Run:\n  1. Unit tests: `mvn -B test`, or the test command the invoking prompt gives.\n  2. The changed family's integration tests on **both** topologies, through\n     Failsafe only. Two kinds of runner exist:\n     - existing core families: `@Tag(\"integration\")` `*Test` classes (Failsafe's\n       `it-tagged` execution), namely the unified standalone runner\n       `RedisClient<Family>CommandsTest`, the cluster runner\n       `Cluster<Family>CommandsTest` and the legacy `commands/jedis/<Family>CommandsTest`\n     - module and new families: `*IT` classes (the `it-suffix` execution), a\n       standalone runner in `commands/unified/client/<module>/` (e.g.\n       `BloomRedisClientCommandsIT`, on `modules-docker`) and a cluster runner in\n       `commands/unified/cluster/<module>/` (e.g. `BloomClusterCommandsIT`, on\n       `cluster-stable`; the cluster image loads the modules too)\n     \n     Select them with one wildcard that covers both kinds and every topology, plus\n     an explicit name for any new runner that doesn't match it:\n     ```bash\n     mvn -B -DskipUnitTests=true -Dit.failIfNoSpecifiedTests=false \\\n       -Dit.test='*<Family>*Commands*' verify\n     ```\n     `-Dit.failIfNoSpecifiedTests=false` is needed because each Failsafe execution\n     sees the same filter and one of them usually matches nothing. That also means\n     `verify` can pass having run **no** test, so don't trust the exit code alone.\n  3. Prove it ran: read `target/failsafe-reports/*.txt` (one file per class, named\n     by its fully qualified class name) and list every class that ran with its counts.\n     A class in package `redis.clients.jedis.commands.unified.cluster` (any\n     sub-package) is a cluster run; anything else is standalone. Don't go by the\n     name prefix: module runners are `<Family>ClusterCommandsIT`, and one is\n     `FTHybridCommandsClusterIT`. At least one standalone class and one cluster class\n     must report `Tests run:` > 0 for the changed commands. This applies to module\n     families too. If either topology ran nothing, fix the filter and rerun. Don't\n     report success.\n  \n  Tests that need endpoints the file doesn't have (sentinel, TLS, ACL users,\n  `cluster-unbound`) are out of scope: list them as not run. Report passed /\n  failed / skipped per topology, and the classes run.\n\n## PR hygiene checklist (verify before finishing)\n\n- [ ] Every layer of the chosen matrix updated consistently (a return-type change\n      must propagate across all 4 interfaces × 3 implementations × all test layers).\n- [ ] New files added to the `pom.xml` formatter-plugin includes.\n- [ ] `@since` (and `@Experimental` if preview) on all new public API.\n- [ ] No unused imports, no wildcard imports, no dead `Protocol.Keyword` constants.\n- [ ] `equals`/`hashCode` on params updated and unit-tested.\n- [ ] `docs/` updated if user-facing behavior/configuration changed; migration\n      guide entry if anything breaks.\n- [ ] PR description states: server PR link, version gate choice and why, which\n      layers intentionally did NOT change and why, and behavior against older\n      servers. (**Unattended:** put these in the final report; the automation\n      builds the PR description from it.)\n\n<!-- File: .claude/commands/createPRDescription.md (Tokens: ~247 | Category: command-prompt) -->\n---\ndescription: Generate a PR title and description from the diff between two local branches, using the creating-description-for-gh-pr skill, and save to prDescription.md\nargument-hint: [base-branch] [target-branch]\n---\n\nUse the \"creating-description-for-gh-pr\" skill to generate a PR title and description.\n\nArguments provided to this command: base = `$1`, target = `$2` (either may be empty).\n\nPass this context into the skill's branch resolution logic (Step 1). Since these are positional arguments, only three states are possible:\n- Both `$1` and `$2` given → `$1` is the explicit base branch, `$2` is the explicit target branch.\n- Only `$1` given (`$2` empty) → `$1` is the explicit base branch; let the skill default the target branch.\n- Neither given (both empty) → let the skill default both base and target branches.\n\n`$1` cannot be empty while `$2` is non-empty — do not handle that case.\n\nDo not duplicate or reinterpret the skill's instructions; follow them as written, including the output structure, the writing-style constraints, and the save/confirm steps.\n</yakaai_skills>"}