{"owner":"redis","repo":"node-redis","hasSkills":true,"totalSkillsCount":25,"totalTokensCount":30322,"categories":["anthropic-skill","root-instruction","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/bump-test-image/SKILL.md",".agents/skills/docs-sync/SKILL.md",".agents/skills/docs-sync/agents/openai.yaml",".agents/skills/docs-sync/references/doc-coverage-checklist.md",".agents/skills/implement-command/SKILL.md",".agents/skills/implement-command/agents/openai.yaml",".agents/skills/pr-draft-summary/SKILL.md",".agents/skills/pr-draft-summary/agents/openai.yaml",".agents/skills/runtime-behavior-probe/SKILL.md",".agents/skills/runtime-behavior-probe/agents/openai.yaml",".agents/skills/runtime-behavior-probe/references/error-cases.md",".agents/skills/runtime-behavior-probe/references/redis-runtime-patterns.md",".agents/skills/runtime-behavior-probe/references/repo-import-patterns.md",".agents/skills/runtime-behavior-probe/references/reporting-format.md",".agents/skills/runtime-behavior-probe/references/validation-matrix.md",".agents/skills/test-coverage-improver/SKILL.md",".agents/skills/test-coverage-improver/agents/openai.yaml","AGENTS.md","packages/bloom/README.md","packages/client/README.md","packages/entraid/README.md","packages/json/README.md","packages/redis/README.md","packages/search/README.md","packages/time-series/README.md"],"skills":{".agents/skills/bump-test-image/SKILL.md":"---\nname: bump-test-image\ndescription: Bump the default Redis docker test image (redislabs/client-libs-test) in the shared DEFAULT_DOCKER_CONFIG and the CI matrix, then force-push the bump-test-image branch and open a PR against upstream. User-invoked only.\ndisable-model-invocation: true\nargument-hint: <image-tag> [redis-version]\n---\n\n# Bump default docker test image\n\nUpdates the default `redislabs/client-libs-test` docker tag used by tests (a single shared config in `@redis/test-utils`), adjusts the CI matrix, commits on a freshly-reset `bump-test-image` branch, force-pushes it, and opens (or updates) a PR against `redis/node-redis` master.\n\n## Inputs\n\n- **tag** (required): the new docker tag, e.g. `8.8.0`, `8.8-rc1`, `custom-28772936538-debian`, `unstable-24425681442-debian`. If given as `redislabs/client-libs-test:<tag>`, strip the image name.\n- **version** (sometimes required): the Redis version the tag represents, e.g. `8.10`.\n  - Derivable when the tag is version-shaped: `8.8.0` → `8.8`, `8.8-rc1` → `8.8`, `8.8-m03` → `8.8`.\n  - NOT derivable for `custom-*` / `unstable-*` tags — if missing, ask the user which Redis version the tag maps to before doing anything else.\n\nIf no tag was given at all, ask for it and stop.\n\n## Step 1 — Sync master from upstream\n\nRemotes: `upstream` = `redis/node-redis` (canonical), `origin` = the user's fork.\n\n```bash\ngit fetch upstream\ngit rev-parse --abbrev-ref HEAD   # remember current branch\n```\n\nUpdate local master to upstream:\n\n- If currently on master: `git reset --hard upstream/master` (only if working tree is clean — if dirty, stop and tell the user).\n- Otherwise: `git fetch upstream master:master` (fails safely if master has local-only commits; if it fails, stop and report).\n\n## Step 2 — Reset the work branch\n\n```bash\ngit checkout -B bump-test-image master\n```\n\n`-B` creates or hard-resets the branch to latest master. Any previous bump commits on it are intentionally discarded (the branch is recycled for each bump; force-push later matches this).\n\n## Step 3 — Edit the shared default config\n\nThe default image config lives in exactly ONE place:\n\n`packages/test-utils/lib/index.ts` → `DEFAULT_DOCKER_CONFIG`:\n\n```typescript\nexport const DEFAULT_DOCKER_CONFIG: TestUtilsConfig = {\n  dockerImageName: 'redislabs/client-libs-test',\n  dockerImageTagArgument: 'redis-tag',\n  dockerImageVersionArgument: 'redis-version',\n  defaultDockerVersion: { tag: '<old-tag>', version: '<old-version>' }\n};\n```\n\nNote the old tag/version, then replace them with the new values on the `defaultDockerVersion` line. Nothing else in the file changes.\n\nIf `DEFAULT_DOCKER_CONFIG` does not exist in that file (e.g. master predates the dedupe refactor), STOP and tell the user — do not fall back to editing per-package test-utils files without confirmation.\n\n## Step 4 — Update the CI matrix\n\nFile: `.github/workflows/tests.yml`, `matrix` → `redis` entries of the form:\n\n```yaml\n          - tag: \"8.8.0\"\n            version: \"8.8\"\n```\n\nTwo cases, decided by comparing the new `version` with the old default version from Step 3:\n\n1. **New Redis version** (e.g. default moves 8.8 → 8.10): **add** a new `- tag/version` entry at the end of the matrix list, keeping the previous entry so CI retains coverage of the prior version.\n2. **Same Redis version** (rc → GA, or a newer custom build of the same version): **replace** the `tag` of the existing entry for that version; the `version` line stays.\n\n## Step 5 — Verify\n\n```bash\ngrep -rn \"defaultDockerVersion:\" packages --include=\"*.ts\" | grep -v dist | grep -v node_modules\n```\n\nExactly one config line (in `packages/test-utils/lib/index.ts`) must show the new tag/version; any other hits are the type definition / JSDoc examples, not real defaults. Also confirm `git diff --stat` touches exactly 2 files: `packages/test-utils/lib/index.ts` + `.github/workflows/tests.yml`.\n\n## Step 6 — Commit\n\nFill in this template exactly (Conventional Commits):\n\n```\nchore(tests): bump default docker test image to <X>\n\nBump the default Redis test image from <old-tag> to <new-tag> (Redis\n<version>) in DEFAULT_DOCKER_CONFIG, and <matrix-action> the CI matrix.\n```\n\n- `<X>`: the human-meaningful version when the tag is version-shaped (`8.10`, `8.8.0`, `8.8-rc1`, `8.8-m03`); the full tag for `unstable-*` builds; the version for `custom-*` tags (the actual tag already appears in the body as `<new-tag>`).\n- `<matrix-action>`: either `add an <version> entry to` (keeping prior-version coverage) or `update the <version> entry in`, matching what Step 4 did.\n\nExample:\n\n```\nchore(tests): bump default docker test image to 8.10\n\nBump the default Redis test image from 8.8.0 to custom-28772936538-debian\n(Redis 8.10) in DEFAULT_DOCKER_CONFIG, and add an 8.10 entry to the CI\nmatrix.\n```\n\nKeep any company-internal references (Jira/Confluence IDs, internal links) out of the commit and PR.\n\n## Step 7 — Force-push and PR\n\n```bash\ngit push -f origin bump-test-image\n```\n\nThen check for an existing open PR before creating one:\n\n```bash\ngh pr list --repo redis/node-redis --head <fork-owner>:bump-test-image --state open\n```\n\n(`<fork-owner>` = GitHub owner from the `origin` remote URL.)\n\n- If a PR exists, the force-push already updated it — just report the URL. Update its title/body via `gh pr edit` to match the new tag.\n- Otherwise create one:\n\n```bash\ngh pr create --repo redis/node-redis --base master --head <fork-owner>:bump-test-image \\\n  --title \"<commit subject>\" \\\n  --body \"<commit body>\"\n```\n\nFinish by reporting: old → new tag/version, matrix action taken (added vs replaced entry), and the PR URL.\n",".agents/skills/docs-sync/SKILL.md":"---\nname: docs-sync\ndescription: Analyze master branch implementation and configuration to find missing, incorrect, or outdated documentation in docs/, README.md, and per-package READMEs. Use when asked to audit doc coverage, sync docs with code, or propose doc updates/structure changes. Provide a report and ask for approval before editing docs.\n---\n\n# Docs Sync\n\n## Overview\n\nIdentify doc coverage gaps and inaccuracies by comparing master branch features and configuration options against the current docs, then propose targeted improvements.\n\n## Workflow\n\n1. Confirm scope and base branch\n   - Identify the current branch and default branch (`master`).\n   - Prefer analyzing the current branch to keep work aligned with in-flight changes.\n   - If the current branch is not `master`, analyze only the diff vs `master` to scope doc updates.\n   - Avoid switching branches if it would disrupt local changes; use `git show master:<path>` or `git worktree add` when needed.\n\n2. Build a feature inventory from the selected scope\n   - If on `master`: inventory the full surface area and review docs comprehensively.\n   - If not on `master`: inventory only changes vs `master` (feature additions/changes/removals).\n   - Focus on user-facing behavior: public exports, client/cluster/sentinel/pool config options, new or changed commands, connection-string options, default values, and documented runtime behaviors.\n   - Capture evidence for each item (file path + symbol/setting).\n   - Use targeted search to find option types and feature flags (for example: `rg \"Options\"`, `rg \"interface .*Options\"`, `rg \"export\"` under `packages/*/lib`).\n   - For Redis command behavior or server-side semantics, treat the source code (`parseCommand`/`transformReply` in `packages/*/lib/commands`) as the source of truth; consult [redis.io/commands](https://redis.io/commands) only to confirm server semantics when discrepancies appear.\n\n3. Doc-first pass: review existing pages\n   - Walk each relevant page under `docs/`, the top-level `README.md`, and each `packages/*/README.md`.\n   - Identify missing mentions of important, supported options (opt-in flags, config), customization points, or new features from `packages/`.\n   - Propose additions where users would reasonably expect to find them on that page.\n\n4. Code-first pass: map features to docs\n   - Review the current docs layout under `docs/` (e.g. `client-configuration.md`, `clustering.md`, `sentinel.md`, `pool.md`, `RESP.md`, `transactions.md`, `programmability.md`, `pub-sub.md`, `scan-iterators.md`, `command-options.md`).\n   - Determine the best page/section for each feature based on existing patterns and package boundaries.\n   - Identify features that lack any doc page or have a page but no corresponding content.\n   - Note when a structural adjustment would improve discoverability.\n\n5. Detect gaps and inaccuracies\n   - **Missing**: features/configs present in master but absent in docs.\n   - **Incorrect/outdated**: names, defaults, or behaviors that diverge from master.\n   - **Structural issues** (optional): pages overloaded, missing overviews, or mis-grouped topics.\n\n6. Produce a Docs Sync Report and ask for approval\n   - Provide a clear report with evidence, suggested doc locations, and proposed edits.\n   - Ask the user whether to proceed with doc updates.\n\n7. If approved, apply changes\n   - Edit docs under `docs/`, `README.md`, and the relevant `packages/*/README.md`.\n   - Keep changes aligned with the existing docs style and navigation.\n   - Place any runnable code snippets under `examples/` or `doctests/`, mirroring existing patterns.\n   - Verify any snippet you add still compiles with `npm run build` and fix issues before handoff.\n\n## Output format\n\nUse this template when reporting findings:\n\nDocs Sync Report\n\n- Doc-first findings\n  - Page + missing content → evidence + suggested insertion point\n- Code-first gaps\n  - Feature + evidence → suggested doc page/section (or missing page)\n- Incorrect or outdated docs\n  - Doc file + issue + correct info + evidence\n- Structural suggestions (optional)\n  - Proposed change + rationale\n- Proposed edits\n  - Doc file → concise change summary\n- Questions for the user\n\n## References\n\n- `references/doc-coverage-checklist.md`\n",".agents/skills/docs-sync/agents/openai.yaml":"interface:\n  display_name: \"Docs Sync\"\n  short_description: \"Audit docs coverage and propose targeted updates\"\n  default_prompt: \"Use $docs-sync to audit the current branch against docs/ and propose targeted documentation updates.\"\n",".agents/skills/docs-sync/references/doc-coverage-checklist.md":"# Doc Coverage Checklist\n\nUse this checklist to scan the selected scope (master = comprehensive, or current-branch diff) and validate documentation coverage.\n\n## Feature inventory targets\n\n- Public exports: clients (`RedisClient`, `RedisCluster`, `RedisSentinel`), pool, factory functions, types, and module entry points.\n- Configuration options: `*Options` types, default config objects (`defaults.ts`), and connection-string options.\n- New or changed commands under `packages/*/lib/commands` (names, args, reply shape, RESP2/RESP3 differences).\n- User-facing behaviors: reconnection, timeouts, pub/sub, pipelining/transactions, scan iterators, errors, caching, telemetry, and data handling.\n- Deprecations, removals, or renamed settings (track against migration guides `docs/v*-to-v*.md`).\n\n## Doc-first pass (page-by-page)\n\n- Review each relevant page under `docs/`, the top-level `README.md`, and each `packages/*/README.md`.\n- Look for missing opt-in flags or customization options that the page implies.\n- Add new features that belong on that page based on user intent and navigation.\n\n## Code-first pass (feature inventory)\n\n- Map features to the closest existing page based on package or feature area.\n- Prefer updating existing pages over creating new ones unless the topic is clearly new.\n- Use conceptual pages for cross-cutting concerns (auth, errors, RESP, clustering, pub/sub).\n- Keep quick-start flows minimal; move advanced details into deeper pages.\n\n## Evidence capture\n\n- Record the master-branch file path and symbol/setting name.\n- Note defaults or behavior-critical details for accuracy checks.\n- Avoid large code dumps; a short identifier is enough.\n\n## Red flags for outdated or incorrect docs\n\n- Option names/types no longer exist or differ from code.\n- Default values or allowed ranges do not match implementation.\n- Features removed in code but still documented.\n- New behaviors introduced without corresponding docs updates.\n\n## When to propose structural changes\n\n- A page mixes unrelated audiences (quick-start + deep reference) without clear separation.\n- Multiple pages duplicate the same concept without cross-links.\n- New feature areas have no obvious home in the docs structure.\n\n## Diff mode guidance (current branch vs master)\n\n- Focus only on changed behavior: new exports/options, modified defaults, removed features, or renamed settings.\n- Use `git diff master...HEAD` (or equivalent) to constrain analysis.\n- Document removals explicitly so docs can be pruned if needed.\n\n## Patch guidance\n\n- Keep edits scoped and aligned to existing tone and format.\n- Update cross-links when moving or renaming sections.\n- Place any runnable snippets under `examples/` or `doctests/`.\n",".agents/skills/implement-command/SKILL.md":"---\nname: implement-command\ndescription: Add a new Redis command (or command variant) to node-redis end-to-end — the `<NAME>.ts` Command file, its registration with JSDoc in the package `commands/index.ts`, and a co-located `<NAME>.spec.ts` with arg + behavior tests. Use when asked to implement, add, or wire up a Redis command in the client or a module package (json/search/bloom/time-series).\n---\n\n# Implement a node-redis Command\n\n## Overview\n\nA command in node-redis is a single `Command` object exported from\n`packages/<pkg>/lib/commands/<NAME>.ts`. It declares how to serialize arguments\nonto the wire (`parseCommand`) and how to map the RESP reply to a JS value\n(`transformReply`). It becomes callable on clients only after it is registered\nin the package's `commands/index.ts`. Both the raw name and a camelCase alias\nare exposed (`HSET` and `hSet`).\n\nThis skill is for `@redis/client` core commands and for module packages\n(`@redis/json`, `@redis/search`, `@redis/bloom`, `@redis/time-series`). It does\nnot cover RESP codec changes or new client transports.\n\nBefore writing, **read 2-3 existing commands with a similar shape** (simple\nkey read, key + options, variadic, RESP2/3-divergent reply) and mirror them.\nConsult [redis.io/commands](https://redis.io/commands) for argument order and\nreply type, but treat existing `parseCommand`/`transformReply` files as the\nsource of truth for repo conventions.\n\n## File layout\n\n| Package | Command dir | Import paths | Wire name prefix |\n| --- | --- | --- | --- |\n| `client` | `packages/client/lib/commands/` | `../client/parser`, `../RESP/types`, `./generic-transformers` | none (`GET`) |\n| `json` | `packages/json/lib/commands/` | `@redis/client/dist/lib/...` | `JSON.` (`JSON.ARRAPPEND`) |\n| `search` | `packages/search/lib/commands/` | `@redis/client/dist/lib/...` | `FT.` |\n| `bloom` | `packages/bloom/lib/commands/<family>/` | `@redis/client/dist/lib/...` | e.g. `BF.`, `CF.`, `TOPK.` |\n| `time-series` | `packages/time-series/lib/commands/` | `@redis/client/dist/lib/...` | `TS.` |\n\n**Naming**: file name = raw wire name with subcommand `_` separators\n(`ACL_CAT`, `CONFIG_GET`, `CLUSTER_FORGET`). A distinct reply variant gets its\nown file (`HRANDFIELD_COUNT_WITHVALUES`). Module commands drop the dotted\nprefix from the file name (`ARRAPPEND.ts` → wire `JSON.ARRAPPEND`).\n\n## Step 0 — Gather inputs (ask the user first)\n\nNew commands are often implemented **before they are publicly released**, so\nredis.io may not document them yet and a default `redis:latest` may not have\nthem. Before writing any code, ask the user for three things:\n\n1. **The command spec.** Ask for the redis/redis JSON spec file — one per\n   command under\n   [`src/commands/<name>.json`](https://github.com/redis/redis/tree/unstable/src/commands)\n   (subcommands use `-`, e.g. `client-info.json`, `acl-cat.json`). If the\n   command is unreleased, ask the user to paste the spec from their branch.\n   As a fallback on a live server: `redis-cli --json COMMAND DOCS <name>` and\n   `COMMAND INFO <name>`.\n2. **A running Redis instance that has the command.** This is the single most\n   useful input — ask for it explicitly and up front. Ask for connection\n   details (host/port, TLS, auth, module loaded). Use it to explore real\n   behavior and confirm the implementation matches the spec — **do not rely on\n   the spec alone**. Probe every argument branch and diff the real reply against\n   your `transformReply`. A quick `redis-cli` session or a throwaway probe\n   script (`packages/client` is already wired for `tsx`) is enough; never commit\n   the probe.\n3. **The first server version that ships the command.** Ask which Redis (or\n   module) version introduced it — the spec's `since` field is the answer when\n   present; otherwise ask the user directly. You need this to (a) write `@since`\n   in the JSDoc (Step 2) and (b) gate the behavior tests with\n   `minimumDockerVersion` (Step 3) so they don't run — and fail — on older\n   servers in CI.\n\nIf the user cannot provide a spec, derive arguments/reply from\n[redis.io/commands](https://redis.io/commands) but flag that it is unverified.\nIf they cannot provide a live instance, implement from the spec but state that\nruntime behavior was not confirmed. If the introducing version is unknown, say\nso and leave `@since`/`minimumDockerVersion` out rather than guessing.\n\n### Reading the spec JSON → mapping to a Command\n\nThe redis/redis spec drives every part of the `Command` object. Example\n(`getex.json`, trimmed):\n\n```json\n{\n  \"GETEX\": {\n    \"since\": \"6.2.0\", \"arity\": -2,\n    \"command_flags\": [\"WRITE\", \"FAST\"],\n    \"key_specs\": [{ \"begin_search\": { \"index\": { \"pos\": 1 } }, \"flags\": [\"RW\", \"UPDATE\"] }],\n    \"arguments\": [\n      { \"name\": \"key\", \"type\": \"key\", \"key_spec_index\": 0 },\n      { \"name\": \"expiration\", \"type\": \"oneof\", \"optional\": true, \"arguments\": [\n        { \"name\": \"seconds\", \"type\": \"integer\", \"token\": \"EX\" },\n        { \"name\": \"persist\", \"type\": \"pure-token\", \"token\": \"PERSIST\" }\n      ]}\n    ],\n    \"reply_schema\": { \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"null\" } ] }\n  }\n}\n```\n\n| Spec field | Drives |\n| --- | --- |\n| `command_flags` contains `READONLY` (and **not** `WRITE`) | `IS_READ_ONLY: true`. `WRITE` → omit it. Pure read with no side effects → also `CACHEABLE: true`. For keyed commands the generated metadata table already implies these defaults, so module packages omit the flags; keyless reads must set `IS_READ_ONLY: true` explicitly (Step 4). |\n| `key_specs` empty / no `key`-type args | `NOT_KEYED_COMMAND: true`. |\n| `arguments[].type: \"key\"` | `parser.pushKey(...)` (one per key, in spec order). |\n| `type: \"pure-token\"` + `token` | a literal flag pushed only when its option is set (`parser.push('PERSIST')`). |\n| `token` + value type (`integer`/`string`/...) | push the token then the stringified value (`parser.push('EX', seconds.toString())`). |\n| `type: \"oneof\"` | mutually exclusive branch → `if/else if` in `parseCommand`; model as a union/`options` field. |\n| `optional: true` | goes in the `options` object (exported `interface`); guard with `if (options?.x)`. |\n| `multiple: true` | variadic → `parser.pushVariadic*`. |\n| `arity` | sanity-check arg count in `parseArgs` tests. |\n| `reply_schema` (JSON Schema) | the `transformReply` return type. `oneOf [string, null]` → `BlobStringReply \\| NullReply`; `integer` → `NumberReply`; `array` → `ArrayReply<...>`; a map differing by RESP version → keyed `transformReply: { 2, 3 }`. |\n| `since` | the introducing server version → `@since` in the registry JSDoc (Step 2) **and** `minimumDockerVersion: [major, minor]` on the behavior tests (Step 3). |\n\nAfter implementing, run the command against the live instance and diff the real\nreply against `reply_schema` and your `transformReply` output.\n\n## Step 1 — Write `<NAME>.ts`\n\nMinimal pass-through command (`packages/client/lib/commands/GET.ts`):\n\n```typescript\nimport { CommandParser } from '../client/parser';\nimport { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types';\n\nexport default {\n  CACHEABLE: true,\n  IS_READ_ONLY: true,\n  parseCommand(parser: CommandParser, key: RedisArgument) {\n    parser.push('GET');\n    parser.pushKey(key);\n  },\n  transformReply: undefined as unknown as () => BlobStringReply | NullReply\n} as const satisfies Command;\n```\n\n`as const satisfies Command` is mandatory — it preserves the literal arg types\nfor the public API while type-checking the shape.\n\n### Command flags (all optional)\n\n- `IS_READ_ONLY: true` — read command; routable to replicas. Set for reads, omit/`false` for writes.\n- `CACHEABLE: true` — eligible for client-side caching. Only for pure reads with no side effects.\n- `NOT_KEYED_COMMAND: true` — command takes no key (server/connection level, e.g. `PING`, `CONFIG_GET`).\n- `IS_FORWARD_COMMAND` — internal; do not set on new commands.\n\n### `parseCommand` — serialize args via `CommandParser`\n\nFirst arg is always `parser`. Push the wire name first, then args in order.\nUse the parser helpers — **do not** hand-build arrays:\n\n- `push(...args)` — raw args (the command token, flags, stringified numbers).\n- `pushKey(key)` — a key. Registers it for cluster slot routing. Use for **every** key, never `push` a key.\n- `pushKeys(keys)` / `pushKeysLength(keys)` — multiple keys; the `Length` variant prefixes the count.\n- `pushVariadic(vals)` — a `RedisVariadicArgument` (one value or array) as flat args.\n- `pushVariadicWithLength(vals)` — same, prefixed with the count (e.g. `FIELDS <n> ...`).\n- `pushVariadicNumber(vals)` — number or array of numbers, stringified.\n\nNumbers are not auto-stringified by `push` — call `.toString()`. Optional\ntrailing args go in an `options` object; export its `interface` (see\n`SET.ts`'s `SetOptions`). Encode keyword flags conditionally:\n\n```typescript\nparseCommand(parser: CommandParser, key: RedisArgument, value: RedisArgument, options?: SetOptions) {\n  parser.push('SET');\n  parser.pushKey(key);\n  parser.push(value);\n  if (options?.condition) parser.push(options.condition); // 'NX' | 'XX'\n}\n```\n\n### `transformReply` — map RESP reply to JS\n\n- **Pass-through** (reply already the right shape): `transformReply: undefined as unknown as () => <ReplyType>`.\n- **Function**: `(reply: <RawType>) => <JsType>`. Use `UnwrapReply<...>` to read the raw RESP container.\n- **RESP-version keyed**: `{ 2: (reply) => ..., 3: (reply) => ... }` when RESP2 and RESP3 shapes differ (e.g. flat array vs map/tuple). See `HRANDFIELD_COUNT_WITHVALUES.ts`.\n\n#### Unify RESP2 onto the RESP3 shape\n\nWhen the server returns different shapes per protocol, the library exposes **one\nreturn type to callers**: the **RESP3 shape is the source of truth**, and the\nRESP2 reply is transformed to look like it. So the keyed form is almost always:\n\n- `3:` — **pass-through** (`undefined as unknown as () => <ReplyType>`), because\n  RESP3 already has the target shape (map, tuple, big-number, double, ...).\n- `2:` — a **function** that reshapes the flat/legacy RESP2 reply into that same\n  `<ReplyType>`. Type its input `UnwrapReply<Resp2Reply<ReplyType>>` so the raw\n  RESP2 container is visible while the output type still matches RESP3.\n\nCanonical example — `HELLO.ts` turns the RESP2 flat array (`[k, v, k, v, ...]`)\ninto the RESP3 map, while RESP3 passes through:\n\n```typescript\ntransformReply: {\n  2: (reply: UnwrapReply<Resp2Reply<HelloReply>>) => ({\n    server: reply[1], version: reply[3], proto: reply[5], /* ... */\n  }),\n  3: undefined as unknown as () => HelloReply\n}\n```\n\nReuse shared transformers where one exists (`HGETALL.ts` uses\n`transformTuplesReply` for `2:`, map pass-through for `3:`). Only when RESP3\nstill needs reshaping does `3:` get its own function too. Verify the actual\nper-protocol shapes against the live instance (Step 0) — connect once with\n`RESP: 2` and once with `RESP: 3` and diff.\n\nReply types live in `RESP/types`: `BlobStringReply`, `SimpleStringReply<'OK'>`,\n`NumberReply`, `DoubleReply`, `NullReply`, `BooleanReply`, `ArrayReply<T>`,\n`TuplesReply<[...]>`, `MapReply`, `UnwrapReply`.\n\n**RESP3 is the default.** No separate RESP3 test is needed for a new command;\nthe default test setup already exercises RESP3.\n\n### Type-mapping precision caveats\n\n- A `BLOB_STRING` reply cannot be remapped to `Number` via type mapping; only RESP3 `DOUBLE`/`BIG_NUMBER` are precision-risky.\n- If a `NumberReply` can exceed `Number.MAX_SAFE_INTEGER` (2^53-1), add a `@remarks` line to the JSDoc (Step 2) telling users to do `client.withTypeMapping({ [RESP_TYPES.NUMBER]: String })`. See the `ARGREP` entries in the client index for the exact wording.\n\n### Module package commands\n\nImport from the published client subpath, prefix the wire name, and reuse\nshared transformers (`packages/json/lib/commands/ARRAPPEND.ts`):\n\n```typescript\nimport { CommandParser } from '@redis/client/dist/lib/client/parser';\nimport { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';\n\nexport default {\n  IS_READ_ONLY: false,\n  parseCommand(parser, key, path, value) {\n    parser.push('JSON.ARRAPPEND');\n    parser.pushKey(key);\n    parser.push(path, /* transform */ value);\n  },\n  transformReply: undefined as unknown as () => NumberReply\n} as const satisfies Command;\n```\n\n## Step 2 — Register in `commands/index.ts`\n\n`import` the command, then add it to the default-export map **twice**: the raw\nname (shorthand) and a camelCase alias. **Every entry MUST have a JSDoc block\ndirectly above it** — `npm run check:command-jsdoc` fails on any registry entry\nwithout an attached JSDoc comment (no blank-line gap allowed).\n\n```typescript\nimport GET from './GET';\n// ...\nexport default {\n  /**\n   * Returns the value of a key, or null if the key does not exist\n   * @param key - Key to read\n   * @since 1.0.0\n   */\n  GET,\n  /**\n   * Returns the value of a key, or null if the key does not exist\n   * @param key - Key to read\n   * @since 1.0.0\n   */\n  get: GET,\n} satisfies RedisCommands;\n```\n\nKeep both JSDoc blocks (raw + alias) in sync. Document every `parseCommand`\nparam after `parser` with `@param`. Add `@since <version>` with the introducing\nserver version from Step 0 (the spec's `since`); omit it only if that version is\nunknown. Add `@remarks` for the precision caveat above when relevant. For module\npackages the registry files are\n`packages/<pkg>/lib/commands/index.ts` (bloom: per-family `.../<family>/index.ts`).\n\n## Step 3 — Write `<NAME>.spec.ts` (co-located)\n\nTwo layers: arg serialization (no server) + behavior (real server, server +\ncluster topologies). Mirror `GET.spec.ts`:\n\n```typescript\nimport { strict as assert } from 'node:assert';\nimport testUtils, { GLOBAL } from '../test-utils';\nimport { parseArgs } from './generic-transformers';\nimport GET from './GET';\n\ndescribe('GET', () => {\n  it('transformArguments', () => {\n    assert.deepEqual(parseArgs(GET, 'key'), ['GET', 'key']);\n  });\n\n  testUtils.testAll('get', async client => {\n    assert.equal(await client.get('key'), null);\n  }, {\n    client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 8] },\n    cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 8] }\n  });\n});\n```\n\n- `parseArgs(COMMAND, ...args)` asserts the exact wire array — cover each option/flag branch and variadic shapes. Arg tests need no server, so **never gate them** by version.\n- `testUtils.testAll(name, fn, { client, cluster })` runs the same body against a standalone server and a cluster. Use it so cluster key routing (`pushKey`) is exercised. Drop `cluster` only when the command is genuinely cluster-incompatible.\n- **Gate every behavior test by the introducing version** (Step 0). Spread `minimumDockerVersion: [major, minor]` into **both** the `client` and `cluster` options (as above). CI runs multiple server versions; without the gate the test runs on older servers that lack the command and fails. `[8, 8]` = \"8.8 and newer\". Apply the same to `testWithClient`/`testWithCluster` by spreading it into their single options object. Omit only if the version is genuinely unknown.\n- Pick the right `GLOBAL.SERVERS.*` / `GLOBAL.CLUSTERS.*` setup (see `test-utils.ts`); `OPEN` is the default.\n- **Docker is required** — test-utils starts real Redis containers.\n\n## Step 4 — Regenerate static command metadata\n\nCluster/sentinel routing (replica-safety, keyedness, CSC eligibility) is\nresolved from a generated table:\n`packages/client/lib/command-metadata/command-metadata-data.ts`. The file is\nauto-generated — **never edit it manually**. The table lives in\n`@redis/client`, but the `COMMAND` dump includes module commands (`ft`,\n`json`, `bf`, `ts`, ...), so a new command in **any** package needs a\nregenerate:\n\n```bash\nnpm run generate:metadata --workspace=packages/client -- redis://localhost:6379\n```\n\nThe script rebuilds the **entire** table from a single live server's `COMMAND`\nreply and overwrites the file — entries the server doesn't report are silently\ndropped. Run it only against a server with **all** bundled modules loaded and a\ncurrent core command set (e.g. the CI image `redislabs/client-libs-test` or a\nfull Redis 8.8+ build); reuse the Step 0 instance only if it meets that bar — a\nserver with just the new command's module would wipe every other module's\nmetadata. The script then applies the curation in\n`packages/client/scripts/command-metadata-overrides.ts`: hand-curated excludes\n(internal, deprecated and cluster-admin commands) plus per-command routing\noverrides. After regenerating, verify the new command has an entry with the\nexpected flags, **and** check `git diff` on `command-metadata-data.ts`: it must\ncontain only the intended additions/changes. Deletions of other modules' or\ncore entries mean the source server was incomplete — revert and rerun against\na full build.\n\nHow the table and the command object interact (override-first — see\n`lib/command-metadata/predicates.ts`):\n\n- The table sets **defaults only**, for both replica routing (cluster and\n  sentinel) and client-side caching eligibility. A keyed entry without the\n  `write` flag is already replica-safe, so a keyed read command with a correct\n  table entry needs no `IS_READ_ONLY` on the command object — bloom and json\n  omit it everywhere.\n- `IS_READ_ONLY`/`CACHEABLE` on the command object **win over the table**. Set\n  them as deliberate corrections, not to restate the table. The main case is\n  keyless reads, which default to master routing: `PING`/`INFO` in the client,\n  and module reads whose args are not keys — search and time-series set\n  `IS_READ_ONLY: true` on exactly their keyless commands (`FT.SEARCH` takes an\n  index name, `TS.MGET`/`TS.MRANGE` take filters, ...).\n- Table-shape fixes (wrong, missing or excluded entries, routing policies)\n  belong in `command-metadata-overrides.ts`; value intent (`IS_READ_ONLY`,\n  `CACHEABLE`) belongs in the command definition, never in the overrides file.\n\n## Step 5 — Build, verify, lint\n\n```bash\nnpm run build                                   # tsc --build (project references)\nnpm run check:command-jsdoc                     # registry JSDoc gate\nnpm run test-single -- packages/<pkg>/lib/commands/<NAME>.spec.ts\nnpm run lint                                     # changed files\n```\n\nIf the build fails on stale `dist/` from project references:\n\n```bash\nfind packages -type d -name \"dist\" -exec rm -rf {} + && npm run build\n```\n\nFor module packages, build the client first (or whole repo) — they import from\n`@redis/client/dist`.\n\n## Completion checklist\n\n- [ ] Asked the user for spec, a live instance with the command, and the introducing server version (Step 0); probed real behavior against the live instance.\n- [ ] `<NAME>.ts` created with `parseCommand` + `transformReply`, `as const satisfies Command`.\n- [ ] Flags set correctly (`IS_READ_ONLY` for reads, `CACHEABLE` only for side-effect-free reads, `NOT_KEYED_COMMAND` if no key; omit flags the metadata table already implies — Step 4).\n- [ ] Every key uses `pushKey`/`pushKeys`; numbers stringified; options behind an exported `interface`.\n- [ ] RESP2/3 divergence handled via keyed `transformReply`: RESP3 is the target shape (usually `3:` pass-through), RESP2 transformed to match it; both shapes verified against the live instance.\n- [ ] Registered in `commands/index.ts`: import + raw entry + camelCase alias, **each with JSDoc** (`@param` per arg; `@since` for the introducing version; `@remarks` for >2^53 precision).\n- [ ] Static command metadata regenerated (`npm run generate:metadata`) against a server with **all** bundled modules; the new command's entry verified and the diff contains no dropped entries (Step 4).\n- [ ] `<NAME>.spec.ts`: `parseArgs` covers all branches; `testUtils.testAll` covers server + cluster; behavior tests gated with `minimumDockerVersion` on both `client` and `cluster`.\n- [ ] `npm run build`, `npm run check:command-jsdoc`, the spec, and `npm run lint` all pass.\n- [ ] Commit message uses Conventional Commits; no company-internal refs.\n```\n",".agents/skills/implement-command/agents/openai.yaml":"interface:\n  display_name: \"Implement Command\"\n  short_description: \"Add a new Redis command to node-redis end-to-end\"\n  default_prompt: \"Use $implement-command to add a new Redis command to node-redis: write the <NAME>.ts Command file (parseCommand + transformReply), register it with JSDoc in the package commands/index.ts (raw name + camelCase alias), and add a co-located <NAME>.spec.ts with parseArgs and testUtils.testAll tests, then build, run check:command-jsdoc, the spec, and lint.\"\n",".agents/skills/pr-draft-summary/SKILL.md":"---\nname: pr-draft-summary\ndescription: Create the required PR-ready summary block, branch suggestion, title, and draft description for node-redis. Must be used before the final response whenever the actual task diff includes runtime code, tests, examples, build/test configuration, or docs with behavior impact, regardless of perceived change size. Skip only when no eligible files changed, every change is repo-meta or docs-only without behavior impact, the task is conversation-only, or the user explicitly opts out.\n---\n\n# PR Draft Summary\n\n## Purpose\n\nProduce a PR-ready summary after eligible work is complete: a concise change summary plus a PR-ready title and draft description for node-redis.\n\n## Close-out Gate\n\n1. Inspect the actual task diff before sending the final response.\n2. Run this skill when the diff includes runtime code, tests, examples, build/test configuration, or docs with behavior impact. Do not use perceived change size to skip an eligible change.\n3. Run it after any required verification and before sending the \"work complete\" response.\n4. Skip only when no eligible files changed, every change is repo-meta or docs-only without behavior impact, the task is conversation-only, or the user explicitly opts out.\n\n## Inputs to Collect Automatically (do not ask the user)\n\n- Current branch: `git rev-parse --abbrev-ref HEAD`.\n- Working tree: `git status -sb`.\n- Untracked files: `git ls-files --others --exclude-standard` (use with `git status -sb`; `--stat` omits them).\n- Changed files: `git diff --name-only` (unstaged) and `git diff --name-only --cached` (staged); sizes via `git diff --stat` and `git diff --stat --cached`.\n- Pull request base reference (use `origin/master`; never use a feature branch's upstream as the PR base):\n  - `BASE_REF=origin/master`; if it does not exist locally, use `master`.\n  - `BASE_COMMIT=$(git merge-base \"$BASE_REF\" HEAD)`.\n- Committed branch diff: `git diff --name-only \"${BASE_COMMIT}..HEAD\"` and `git diff --stat \"${BASE_COMMIT}..HEAD\"`.\n- Commits ahead of the base fork point: `git log --oneline --no-merges ${BASE_COMMIT}..HEAD`.\n- Category signals for this repo: runtime (`packages/*/lib`, `examples/`, `doctests/`, `benchmark/`, `scripts/`), tests (co-located `packages/**/*.spec.ts`), docs (`docs/`, `README.md`, `packages/*/README.md`, `AGENTS.md`, `.github/`), build/test config (`package.json`, `package-lock.json`, `tsconfig*.json`, `eslint.config.mjs`, `.mocharc*`, `mocha-multi-reporter-config.json`, `.nycrc*`).\n\n## Workflow\n\n1. Run the commands above without asking the user; compute `BASE_REF`/`BASE_COMMIT` first so later commands reuse them. Compare against `origin/master` or `master`, not the current branch's upstream.\n2. Combine the committed branch diff with staged, unstaged, and untracked changes. If the combined diff is empty, reply briefly that no code changes were detected and skip emitting the PR block.\n3. Infer change type from the touched paths listed under \"Category signals\"; classify as feature, fix, refactor, or docs-with-impact, and flag backward-compatibility risk only when the diff changes released public APIs, client/command behavior, connection config, or the RESP wire handling. Judge that risk against the latest release tag, not unreleased branch-only churn.\n4. Summarize changes in 1–3 short sentences using the top five paths and stats from the committed, staged, and unstaged diffs. Explicitly call out untracked files because `--stat` does not include them. Use commit messages as supporting context, not as a substitute for inspecting the committed diff.\n5. Choose the lead verb for the description: feature → `adds`, bug fix → `fixes`, refactor/perf → `improves` or `updates`, docs-only → `updates`.\n6. Suggest a branch name. If already off `master`, keep it; otherwise propose `feat/<slug>`, `fix/<slug>`, or `docs/<slug>` based on the primary area (for example `docs/pr-draft-summary-guidance`).\n7. If the current branch matches `issue-<number>` (digits only), keep that branch suggestion. When an issue number is present, reference `https://github.com/redis/node-redis/issues/<number>` and include an auto-closing line such as `This pull request resolves #<number>.` Do not block if the issue cannot be fetched.\n8. Draft the PR title and description using the template below. Use Conventional Commits prefixes for the title (`feat:`, `fix:`, `docs:`, `chore:`, etc.).\n9. Output only the block in \"Output Format\". Keep any surrounding status note minimal and in English. Do not include company-internal references (e.g. Jira/Confluence IDs) in the branch name, title, or description.\n\n## Output Format\n\nWhen closing out a task, add this concise Markdown block (English only) after any brief status note unless the task falls under the documented skip cases or the user says they do not want it.\n\n```\n# Pull Request Draft\n\n## Branch name suggestion\n\ngit checkout -b <kebab-case suggestion, e.g., feat/pr-draft-summary-skill>\n\n## Title\n\n<single-line imperative title, which can be a commit message; a Conventional Commits prefix such as feat:, fix:, or docs: is preferred>\n\n## Description\n\n<include what you changed plus a draft pull request title and description for your local changes; start the description with prose such as \"This pull request resolves/updates/adds ...\" using a verb that matches the change (you can use bullets later), explain the change background (for bugs, clearly describe the bug, symptoms, or repro; for features, what is needed and why), any behavior changes or considerations to be aware of, and you do not need to mention any tests you ran.>\n```\n\nKeep it tight—no redundant prose around the block, and avoid repeating details between `Changes` and the description. Tests do not need to be listed unless specifically requested.\n",".agents/skills/pr-draft-summary/agents/openai.yaml":"interface:\n  display_name: \"PR Draft Summary\"\n  short_description: \"Draft the repo-ready PR title and description\"\n  default_prompt: \"Use $pr-draft-summary to generate the PR-ready summary block, title, and draft description for the current changes.\"\n",".agents/skills/runtime-behavior-probe/SKILL.md":"---\nname: runtime-behavior-probe\ndescription: Plan and execute runtime-behavior investigations with temporary TypeScript probe scripts, validation matrices, state controls, and findings-first reports. Use only when the user explicitly invokes this skill to verify actual runtime behavior beyond normal code-level checks, especially to uncover edge cases, undocumented behavior, or common failure modes in local or live integrations. A baseline smoke check is fine as an entry point, but do not stop at happy-path confirmation.\n---\n\n# Runtime Behavior Probe\n\n## Overview\n\nUse this skill to investigate real runtime behavior, not to restate code or documentation. Start by planning the investigation, then execute a case matrix, record observed behavior, and report both the findings and the method used to obtain them.\n\n## Core Rules\n\n- Treat this skill as manual-only. Do not rely on implicit invocation.\n- For `node-redis`, treat this skill as a disposable-probe workflow, not a repository implementation workflow.\n- Unless the user explicitly asks for a reusable repository artifact, the allowed write scope is limited to:\n  - a temporary directory used for probe scripts or artifacts\n  - `.agents/skills/runtime-behavior-probe/**` when the user is editing this skill itself\n- For disposable probes in `node-redis`, do not modify `examples/**`, `packages/**`, any `package.json`, `README.md`, workspace config, or build config.\n- If your draft plan would touch a disallowed path, stop and rewrite the plan before editing anything.\n- A baseline success or smoke case is often the right entry point, but do not stop there when the real question involves edge cases, drift, or failure behavior.\n- Plan before running anything. Write the case matrix first, then fill it in with observed results. The matrix can live in a scratch note, a temporary file, or the probe script header.\n- Default to local or read-only probes. Consider a live service only when it is clearly relevant, then apply the lightweight gates below before you run it.\n- Size the probe to the decision. Start with the smallest matrix that can disqualify or validate the current hypothesis, then expand only when uncertainty remains.\n- Before a live probe, apply three lightweight gates:\n  - Destination gate. Use only a live destination that is clearly allowed for the task.\n  - Intent gate. Run the live probe only when the user explicitly wants runtime verification on that integration, or explicitly approves it after you propose the probe.\n  - Data gate. If the probe will read environment variables, mutate remote state, incur material cost, or exercise non-public or user data, name the exact variable names or data class and get explicit approval first.\n- Classify each case as read-only, mutating, or costly before execution. For mutating or costly cases, or for any live case that will read environment variables, define cleanup or rollback before running the probe.\n- Use temporary files or a temporary directory for one-off probe scripts.\n- In `node-redis`, use disposable probe files outside git-tracked paths by default. Do not add one-off probes, harnesses, benchmarks, or examples under `examples/`, `packages/`, or other repository directories unless the user explicitly asks for a checked-in artifact.\n- Keep temporary artifacts until the final response is drafted. Then delete them by default unless the user asked to keep them or they are needed for follow-up. Even when artifacts are deleted, keep a short run summary of the command shape, runtime context, and artifact status in the report.\n- Before executing a live probe that will read environment variables, tell the user the exact variable names you plan to use and why, then wait for explicit approval. Examples include `REDIS_URL`, `REDIS_PASSWORD`, and other expected default names for the system under test.\n- Never print secrets, even when they come from standard environment variables that this skill may use.\n- For Redis command or server semantics, treat the repository source as the truth and use [redis.io/commands](https://redis.io/commands) to confirm contract-sensitive details such as argument order, reply types, and version availability. Use runtime probing to validate or challenge the documented behavior, not to skip the documentation pass entirely. If you rely on the website rather than source, say so in the report.\n- For benchmark or comparison probes, make parity explicit before execution. Record what is held constant, what variable is under test, which reply-shape constraints keep the comparison fair, and any counters or timings that matter for interpreting latency or cost.\n- In `node-redis`, default to a light local loop for probe authoring: temporary `probe.ts` plus temporary `tsconfig.json`, `npx tsc --noEmit -p <tmp-tsconfig>`, then `npx tsx <tmp-probe>`. Escalate to `npm run build` only when the runtime question is specifically about `dist/`, emitted exports, or packaged output.\n- In `node-redis`, do not treat a request for runtime verification, benchmarking, or comparison as permission to add a reusable example, benchmark harness, package script, or checked-in sample. Those repository changes require explicit user intent.\n- For probes that need a running Redis, remove setup ambiguity before attributing a negative result to client behavior:\n  - Confirm the server is reachable and note its mode (standalone, cluster, or sentinel) and RESP version before interpreting reply shapes.\n  - Treat RESP2 and RESP3 as separate cases, not interchangeable setup details, when the question is about reply type mapping or push messages.\n  - Clear unsupported server-version or command options first so they do not invalidate the probe. The repo test harness `@redis/test-utils` can start Redis in Docker for local probes.\n\n## Workflow\n\n1. Restate the investigation target in operational terms. Name the runtime surface, the key uncertainty, and the highest-risk behaviors to test.\n2. For `node-redis`, declare the allowed write scope before you do any implementation work. For a normal disposable probe, that means a temporary directory only.\n3. Do a short preflight. Check the relevant code or docs first, decide whether the question needs local or live validation, and note any repo, baseline, or release boundary that matters.\n4. Create a validation matrix before executing probes. Cover both baseline behavior and the most relevant failure or drift cases. The matrix can live in a scratch note, a temporary file, or a structured header inside the probe script.\n5. For each case, choose an execution mode up front:\n   - `single-shot` for deterministic one-run checks.\n   - `repeat-N` for cache, retry, streaming, interruption, rate-limit, concurrency, or other run-to-run-sensitive behavior.\n   - `warm-up + repeat-N` when first-run cold-start effects could distort the result. Use these defaults unless the task clearly needs something else:\n   - Quick screen of a repeat-sensitive question: `repeat-3`.\n   - Decision-grade latency or release recommendation: `warm-up + repeat-10`.\n   - Costly live cases: start at `repeat-3`, then expand only if the answer remains unclear. If it is genuinely unclear whether extra runs are worth the time or cost, ask the user before expanding the probe.\n6. When the question is benchmark-like or comparative, run in phases. Start with a high-signal pilot matrix against a control, then expand only the surviving candidates or unresolved cases.\n7. If the question is about a suspected regression or behavior change, add at least one known-good control case such as `origin/master`, the latest release, or the same request without the suspected option.\n8. For comparative probes, define parity before execution. Record prompt or input shape, tool-choice setup, model-settings parity, state reuse rules, and any response-shape constraint that keeps the comparison fair. If materially different output length could bias the result, record usage or token notes too.\n9. If the question asks whether one option has the same intelligence or quality as another, decide whether the matrix supports only example-pattern parity or a broader quality claim. For broader claims, add at least one harder or more open-ended case. Otherwise say explicitly that the result is limited to the covered patterns.\n10. Plan state controls before execution when hidden state could affect the result. Record whether each case uses fresh or reused state, how cache reuse or cache busting is handled, what unique IDs isolate repeated runs, and how cleanup is verified.\n11. If any live case will read environment variables, list the exact variable names and purpose for each case, then ask the user for approval before execution. Keep the approval ask short and include destination, read-only versus mutating or costly risk, exact variable names, and cleanup or rollback if relevant.\n12. Build task-specific probe scripts in a temporary location. Keep the script small, observable, and easy to discard.\n13. If you are about to propose a checked-in script, example, benchmark, or workspace script for `node-redis`, stop and verify that the user explicitly asked for a reusable repository artifact. If not, keep the probe temporary.\n14. In `node-redis`, make the runtime context explicit:\n\n- Run TypeScript probes from the repository root with `npx tsx` when practical.\n- For probe authoring, create both `probe.ts` and a sibling temporary `tsconfig.json` under `mktemp -d`, then run `npx tsc --noEmit -p <tmp-tsconfig>` before the first live execution.\n- Record the current commit, working directory, Node executable, Node version, and the package or source path you imported.\n- Avoid accidental imports from a different checkout or from `/tmp/node_modules`. If the probe needs repository code, import it from a repository-relative `file://` URL rooted at `process.cwd()`.\n- Prefer current-branch `lib/` imports when the question is \"what does this branch do now?\" and prefer `dist/` imports only when the question is specifically about packaged output after a build.\n- Do not run a repository-wide build just to typecheck a disposable probe. Reserve `npm run build` for `dist/` probes or when emitted output is itself part of the question.\n\n15. Execute the matrix and capture evidence. Record request shape, setup, observation summary, unexpected or negative result, error details, timing, runtime context, approved environment-variable names, repeat counts, warm-up handling, variance when relevant, cleanup behavior, and for comparisons note what was held constant plus any response-shape or usage notes that affect interpretation.\n16. Update the matrix with actual outcomes, not guesses.\n17. Keep temporary artifacts until the final response is drafted. Then delete them unless the user asked to keep them or they are needed for follow-up. Benchmark and repeat-heavy probes often need follow-up, so keeping artifacts is normal when the result may be revisited. If deleted, retain and report a short run summary.\n18. Report findings first, with unexpected or negative findings first. Then summarize how the validation was performed and which cases were covered.\n19. If the probe isolates one clear defect, you may include a short implementation hypothesis or minimal repro direction. Do not expand into a larger next-step plan unless the user asked for it.\n\n## Validation Matrix\n\nUse a matrix that makes the news easy to scan. Start from the runtime question and the observation summary, not just from `expected` and `pass` or `fail`.\n\nUse a matrix with at least these columns:\n\n- `case_id`\n- `scenario`\n- `mode`\n- `question`\n- `setup`\n- `observation_summary`\n- `result_flag`\n- `evidence`\n\nAdd these columns when they materially improve the investigation:\n\n- `comparison_basis`\n- `variable_under_test`\n- `held_constant`\n- `output_constraint`\n- `status`\n- `confidence`\n- `state_setup`\n- `repeats`\n- `warm_up`\n- `variance`\n- `usage_note`\n- `risk_profile`\n- `env_vars`\n- `approval`\n- `control`\n\nTreat `result_flag` as a fast scan field such as `unexpected`, `negative`, `expected`, or `blocked`. Use `status` only when there is a credible comparison basis, baseline, or documented contract to compare against.\n\nAlways consider whether the matrix should include these categories:\n\n- Baseline success.\n- Control or baseline comparison when a regression is suspected.\n- Boundary input or parameter variation.\n- Invalid or unsupported input.\n- Missing or incorrect configuration.\n- Transient external failure such as timeout, network interruption, or rate limiting.\n- Retry, idempotence, or cleanup behavior.\n- Concurrency or overlapping operations when shared state or ordering may matter.\n- Open-ended quality or intelligence samples when the question is broader than pattern parity.\n\nOpen [validation-matrix.md](./references/validation-matrix.md) when you need a stronger prioritization model or a reusable case template.\n\n## Temporary Probe Scripts\n\nWrite one-off scripts in a temporary file or temporary directory such as one created by `mktemp -d`. Keep the script outside the repository by default, even when it imports code from the repository.\n\nFor `node-redis`, a disposable runtime probe should stay disposable. Do not add a package script, modify workspace config, or create a checked-in benchmark or example unless the user explicitly asks for a reusable repository artifact.\n\nFor `node-redis`, the default authoring loop should be:\n\n1. `tmpdir=$(mktemp -d)`\n2. Write `probe.ts` and `tsconfig.json` into `$tmpdir`\n3. From the repository root, run `npx tsc --noEmit -p \"$tmpdir/tsconfig.json\"`\n4. If the quick typecheck passes, run `npx tsx \"$tmpdir/probe.ts\"`\n5. Only run `npm run build` if the probe intentionally imports `dist/` or validates packaged output\n\nUse a temporary `tsconfig.json` that extends the repository base settings but includes only the disposable probe, for example:\n\n```json\n{\n  \"extends\": \"/absolute/path/to/node-redis/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"./probe.ts\"]\n}\n```\n\nIf the probe needs repository code:\n\n- Run it with the repository root as the working directory.\n- Use `npx tsx /tmp/probe.ts` from the repository root when practical.\n- Use `npx tsc --noEmit -p /tmp/probe-tsconfig.json` as the default quick typecheck step before executing the probe.\n- Import repository modules with a `file://` URL built from `process.cwd()` and a repo-relative path. Do not assume bare workspace imports such as `@redis/client` will resolve from `/tmp`.\n- Use `lib/` imports for current-branch behavior probes and `dist/` imports for packaged-output probes.\n\nDesign the probe to maximize observability:\n\n- Print or log the exact scenario being exercised.\n- Capture runtime context such as git SHA, working directory, Node executable and version, relevant package versions, model or deployment name, endpoint or base URL alias, and any retry or tool options that materially affect behavior.\n- For live probes, record only the names of environment variables that were approved for use. Never print their values.\n- Capture structured outputs when possible.\n- Preserve raw error type, message, and status code.\n- For repeat-sensitive cases, capture the attempt index, warm-up status, and any stable identifiers that help compare runs.\n- For repeated or benchmark-style probes, write both raw results and a compact summary artifact when practical.\n- Keep branching minimal so each script answers a narrow question.\n\nBefore deleting the temporary script or directory, keep a short run summary of the script path, command used, runtime context, and whether the evidence was kept or deleted.\n\nOpen [typescript_probe.ts](./templates/typescript_probe.ts) when you want a lightweight disposable TypeScript probe scaffold. Open [repo-import-patterns.md](./references/repo-import-patterns.md) when you need to load current-branch workspace code from a temporary script.\n\n## Reporting\n\nReport in this order:\n\n1. Findings. Put unexpected or negative findings first. If there was no real news, say that explicitly.\n2. Validation approach. Summarize the code used, the runtime surface exercised, the execution modes, and the case matrix coverage.\n3. Case results. Include the matrix or a condensed version of it when the case count is large.\n4. Artifact status and brief run summary. State whether temporary artifacts were deleted or kept, and provide kept paths or the retained summary.\n5. Optional implementation note. Include this only when one clear defect was isolated and a short implementation direction would help.\n\nFor comparative probes, the report should also say what was held constant, what variable was under test, and whether the result supports only pattern parity or a broader quality claim.\n\nOpen [reporting-format.md](./references/reporting-format.md) for the recommended response template.\n\n## Resources\n\n- Open [validation-matrix.md](./references/validation-matrix.md) to design and prioritize the case matrix.\n- Open [error-cases.md](./references/error-cases.md) to expand common failure scenarios.\n- Open [redis-runtime-patterns.md](./references/redis-runtime-patterns.md) for recurring node-redis runtime probe patterns.\n- Open [repo-import-patterns.md](./references/repo-import-patterns.md) for repository-relative TypeScript import guidance.\n- Open [reporting-format.md](./references/reporting-format.md) for the final report structure.\n- Open [typescript_probe.ts](./templates/typescript_probe.ts) for a minimal disposable TypeScript probe scaffold.\n",".agents/skills/runtime-behavior-probe/agents/openai.yaml":"interface:\n  display_name: \"Runtime Behavior Probe\"\n  short_description: \"Plan and run runtime behavior probes\"\n  default_prompt: \"Use $runtime-behavior-probe to investigate actual runtime behavior with a validation matrix, disposable TypeScript probes outside git-tracked paths, and a findings-first report. For node-redis, the default write scope is a temporary directory only. Do not modify examples/, packages/, package.json files, README.md, workspace config, or build config, and do not add checked-in examples, benchmarks, or package scripts unless the user explicitly asks for a reusable repository artifact.\"\npolicy:\n  allow_implicit_invocation: false\n",".agents/skills/runtime-behavior-probe/references/error-cases.md":"# Common Error Cases\n\nUse this reference to expand beyond the happy path. Favor error cases that a real user or operator is likely to hit.\n\n## Configuration Errors\n\nCheck whether the runtime behaves differently for:\n\n- Missing required environment variables.\n- Present but malformed secrets or identifiers.\n- Wrong endpoint or base URL.\n- Wrong model or deployment name.\n- Incompatible local dependency versions.\n\nLook for:\n\n- Error type and status code.\n- Whether the failure is immediate or delayed.\n- Whether the message is actionable.\n- Whether retrying without fixing configuration changes anything.\n\n## Input Errors\n\nProbe common bad-input patterns such as:\n\n- Missing required fields.\n- Wrong data type.\n- Unsupported enum or option value.\n- Empty but syntactically valid input.\n- Oversized input or too many items.\n- Mutually incompatible options.\n\nPrefer realistic invalid inputs over artificial nonsense. The point is to learn how the runtime fails in practice.\n\n## Transport and Availability Errors\n\nWhen networked services are involved, consider:\n\n- Connection failure.\n- Read timeout.\n- Server timeout or upstream gateway error.\n- Rate limit response.\n- Partial stream interruption.\n- Reusing a connection after a failure.\n\nCapture whether the client library retries automatically, whether it surfaces retry metadata, and whether the final exception preserves the original cause.\n\n## State and Repetition Errors\n\nMany surprising bugs appear only when an operation is repeated or interrupted:\n\n- Re-submit the same request.\n- Repeat after a timeout.\n- Retry after a partial tool call or partial stream.\n- Resume after local cleanup or process restart.\n- Repeat with slightly changed inputs while reusing shared state.\n\nObserve whether the operation is idempotent, duplicated, silently ignored, or left in a partial state.\n\n## Concurrency Errors\n\nWhen shared state, ordering, or isolation may matter, consider:\n\n- Two overlapping requests with the same logical input.\n- Parallel runs that reuse the same cache key, session, container, or temporary resource.\n- Concurrent retries, cancellation, or cleanup racing with active work.\n- Output or event streams from one run leaking into another.\n\nCapture whether the runtime serializes, rejects, duplicates, corrupts, or cross-contaminates the work.\n\n## Investigation Heuristics\n\nUse these heuristics to pick error cases quickly:\n\n- Ask which failure a real engineer would debug first in production.\n- Ask which failure is most expensive if it is misunderstood.\n- Ask which failure would be invisible from code review alone.\n- Ask which failure path is likely to differ across environments.\n\nIf the error behavior is already perfectly obvious from a local validator or type system, it is usually low priority for this skill.\n",".agents/skills/runtime-behavior-probe/references/redis-runtime-patterns.md":"# Redis Runtime Patterns\n\nUse this reference for recurring node-redis investigations so you do not have to rediscover the probe strategy each time. Treat the repository source as the truth for client behavior, and use [redis.io/commands](https://redis.io/commands) to confirm contract-sensitive details such as argument order, reply types, and version availability. If you rely on the website rather than source, say so in the report.\n\n## General Rules\n\n- Prefer small, focused probes over large harnesses. Keep one script focused on one uncertainty.\n- For comparative or benchmark-like questions, start with a pilot and expand only when the answer is still unclear.\n- Capture both the request shape (command and arguments as sent) and the raw reply, before any client-side type mapping.\n- Preserve raw error type, message, and any Redis error prefix such as `WRONGTYPE`, `MOVED`, `ASK`, `CROSSSLOT`, `NOSCRIPT`, or `LOADING`.\n- Record whether behavior differs between the first call and a repeated call, and across a reconnect.\n- When the question is about regression or contract drift, add a known-good control run before attributing the result to the change under investigation.\n- Keep comparison parity explicit. Record what was held constant, what variable changed, and whether reply-shape or encoding differences could bias the conclusion.\n- Record the runtime context that actually changes Redis client behavior: server mode (standalone, cluster, sentinel), RESP version (2 vs 3), server version, and whether a feature such as client-side caching or sharded pub/sub is in use.\n\n## Connection Environment Variables\n\nDo not read these variables automatically. Before a live probe uses any of them, tell the user the exact variable names you plan to read and why each one is needed, then wait for explicit approval. Never print their values:\n\n- `REDIS_URL`\n- `REDIS_PASSWORD`\n- `REDIS_TLS_CA`, `REDIS_TLS_CERT`, `REDIS_TLS_KEY` (and any other `REDIS_TLS_*` the probe needs)\n\nFor most local probes you do not need any of these. The repository test harness `@redis/test-utils` can start Redis in Docker, so a disposable probe can target a local containerized server (for example `redis://localhost:6379`) without reading credentials. Prefer that over a live remote service unless the question is specifically about the remote deployment.\n\nIf the task targets another integration, use that integration's expected default variable names under the same rule.\n\n## Environment False Signals\n\nBefore attributing a failure to the patch or client code under review, exclude source-selection and environment problems with a controlled comparison.\n\n- Confirm the commit, worktree, working directory, Node executable/version, and imported module path. A probe of stale `dist`, another checkout, `/tmp/node_modules`, or an installed `redis` package does not prove current `lib` behavior.\n- Rebuild when emitted declarations, package exports, generated metadata, or `dist` is the subject. Otherwise prefer current-source `lib/` imports so stale build output cannot create a false result.\n- Confirm a Redis server is actually running and reachable. A connection refused or timeout against a missing Docker/Redis container is an environment condition, not a client defect, until a controlled rerun against a known-good server ties it to the code.\n- Confirm the RESP version the client negotiated. A reply-shape surprise is often just RESP2 vs RESP3, not a bug.\n- Confirm server mode matches the probe assumptions. Running a cluster-only expectation against a standalone server, or the reverse, produces misleading routing and slot errors.\n- Run base and head controls with the same Node version, dependency graph, server mode, RESP version, environment-variable names, and command shape. Record intentional differences.\n- Treat sandbox denials, unavailable Docker infrastructure, expired remote sessions, authentication, quota, rate limits, service outages, and stale caches as environment conditions until a controlled rerun ties them to the patch.\n- Never print credentials, connection URLs with embedded passwords, or TLS key material. Change only the minimum in-scope environment or disposable state needed for the control and record variable names rather than values.\n\nIn the final report, distinguish code failures, unsupported configurations, environment blockers, and inconclusive probes. Do not collapse them into one failed-test count.\n\n## Node-Redis Probe Patterns\n\nStart from the uncertainty instead of from the full client surface. The patterns below cover the runtime questions that recur most often in node-redis.\n\n### RESP2 vs RESP3 reply shapes and type mapping\n\nUse when the uncertainty is what a command returns at runtime and how the client maps it.\n\n- Run the same command under RESP2 and RESP3 as separate cases. Maps, sets, doubles, big numbers, verbatim strings, and booleans differ between the two protocols.\n- Capture the raw reply and the post-mapping JavaScript value. Note whether the client returns plain objects, `Map`, arrays, `null`, or typed numbers.\n- Watch precision-sensitive types. RESP3 `DOUBLE` and `BIG_NUMBER` can lose precision when mapped to `Number`; record the exact value.\n- If the probe configures a custom `typeMapping`, treat each mapping choice as part of `held_constant`.\n\n### Single-node vs cluster vs sentinel\n\nUse when key routing, slot ownership, or topology matters.\n\n- For cluster, capture which node served the command and whether a `MOVED` or `ASK` redirect occurred. Probe multi-key commands across keys in different slots to observe `CROSSSLOT` behavior and any client-side slot checks.\n- Use hash tags (`{tag}`) to force keys into the same slot when the probe needs a multi-key command to succeed.\n- For sentinel, capture master discovery and what happens on a simulated failover.\n- Keep mode constant within a case. A standalone control alongside a cluster case is a useful comparison, not a substitute.\n\n### Pub/sub and sharded pub/sub\n\nUse when delivery semantics or resubscription matter.\n\n- Capture subscribe confirmation, message delivery order, and whether messages are delivered to pattern (`PSUBSCRIBE`) and channel subscribers as expected.\n- For sharded pub/sub (`SSUBSCRIBE` and `SPUBLISH`) in cluster, capture which shard delivered the message.\n- Force a reconnect and observe whether the client resubscribes to channels and patterns automatically. This is a common drift point.\n\n### Reconnection, retry, and the offline command queue\n\nUse when the question is about resilience.\n\n- Simulate a dropped connection (stop or restart the container, or close the socket) and capture: whether commands issued while offline are queued, the order they flush in on reconnect, and whether the reconnect strategy backoff behaves as configured.\n- Distinguish commands rejected immediately from commands queued and later resolved or rejected.\n- Capture whether in-flight commands at disconnect are retried, rejected, or hang.\n\n### Pipelining and MULTI/EXEC transactions\n\nUse when batching or atomicity is under test.\n\n- For pipelining, confirm replies map back to the right commands in order, including when one command in the batch errors.\n- For `MULTI`/`EXEC`, capture queueing errors versus execution errors, behavior on `DISCARD`, and `WATCH` optimistic-locking aborts (`EXEC` returning `null`).\n\n### Client-side caching (RESP3 push invalidation)\n\nUse when caching correctness matters.\n\n- Requires RESP3. Capture whether invalidation push messages arrive after a tracked key is modified by another client, and whether the local cache entry is dropped.\n- Probe the broadcast and default tracking modes separately.\n\n### Blocking commands and connection isolation\n\nUse for `BLPOP`, `BRPOP`, `XREAD BLOCK`, `WAIT`, and similar.\n\n- Capture whether the blocking command monopolizes a connection and whether the client isolates it (for example via a dedicated connection) so other commands are not stalled.\n- Probe timeout behavior and what resolves when the block is satisfied by another client versus when it times out.\n\n### Pool behavior\n\nUse when a pooled client is involved.\n\n- Capture how commands are distributed across pool connections, behavior under saturation, and what happens when a pooled connection drops.\n- For commands that require connection affinity (transactions, blocking, subscriptions), confirm the pool keeps them on a single connection.\n\n### SCAN iterators\n\nUse for `SCAN`, `HSCAN`, `SSCAN`, and `ZSCAN`.\n\n- Capture cursor progression, whether the async iterator terminates, and whether `COUNT` and `MATCH` behave as expected. Note that SCAN gives no count or completeness guarantees mid-iteration.\n\n### Large payloads and timeouts\n\nUse when size or latency is the concern.\n\n- Capture behavior with large values and large reply sets, and whether command-level or socket timeouts fire as configured.\n- For repeat-sensitive latency, use warm-up plus repeat-N and record medians, not a single sample.\n\n## What to Capture\n\nFor node-redis probes, try to record:\n\n- The command and arguments as sent, plus the raw reply before type mapping.\n- The mapped JavaScript value and its type, especially for RESP3 maps, sets, doubles, and big numbers.\n- Server mode (standalone, cluster, or sentinel), negotiated RESP version, and server version.\n- Whether fields are absent, `null`, empty, or transformed by the client.\n- Raw error type, message, and any Redis error prefix for failures.\n- Reconnect and retry behavior, offline-queue flush order, and resubscription on reconnect when relevant.\n- For cluster, which node served the command and any redirect.\n- Which environment-variable names were approved for the probe when a live or credentialed server was required.\n\nDo not spend time rediscovering static documentation unless the runtime result seems to contradict what you expected. The value of this skill is in the observed behavior.\n",".agents/skills/runtime-behavior-probe/references/repo-import-patterns.md":"# Repository Import Patterns\n\nUse this reference when a temporary probe script outside the repository needs to load code from the current branch.\n\n## Why This Exists\n\nWhen you run `npx tsx /tmp/probe.ts` from the repository root, `tsx` itself comes from this repository, but the probe file still lives under `/tmp`. Bare workspace imports such as `@redis/client` or `@redis/json` therefore resolve as if the script were its own package. That is often not what you want for \"what does the current branch do right now?\" investigations.\n\nThe safe default is to build a `file://` import URL from `process.cwd()`, which should be the repository root, and a repository-relative path.\n\n## Default Rule\n\n- Use `lib/` imports when the question is about current-branch behavior.\n- Use `dist/` imports when the question is about packaged output after a build.\n- Avoid bare workspace imports from `/tmp` unless the probe is explicitly testing published package resolution rather than branch-local source behavior.\n\n## Quick Typecheck Loop\n\nFor disposable probes in `node-redis`, the default loop should stay outside the repository and avoid a full workspace build:\n\n1. Create a temporary directory with `mktemp -d`.\n2. Write `probe.ts` there.\n3. Write a sibling `tsconfig.json` that extends the repository's `tsconfig.base.json`.\n4. From the repository root, run `npx tsc --noEmit -p /tmp/.../tsconfig.json`.\n5. If that passes, run `npx tsx /tmp/.../probe.ts`.\n\nExample temporary `tsconfig.json`:\n\n```json\n{\n  \"extends\": \"/absolute/path/to/node-redis/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"./probe.ts\"]\n}\n```\n\nThis keeps TypeScript module resolution and strictness close to the repository defaults while limiting the check surface to the disposable probe file.\n\nDo not convert this default loop into a checked-in benchmark, example, or package script unless the user explicitly asks for a reusable repository artifact.\n\n## Recommended Helper\n\nThe disposable TypeScript scaffold exposes:\n\n    const module = await importRepoModule(\"packages/client/lib/index.ts\");\n\nInternally, the helper should be equivalent to:\n\n    import { join } from \"node:path\";\n    import { pathToFileURL } from \"node:url\";\n\n    async function importRepoModule(repoRelativePath: string) {\n      const absolutePath = join(process.cwd(), repoRelativePath);\n      return import(pathToFileURL(absolutePath).href);\n    }\n\nThis keeps the import anchored to the current checkout instead of the temporary script location.\n\n## Choosing `lib/` Versus `dist/`\n\nUse `lib/` when:\n\n- You are validating a bug fix or regression on the current branch.\n- You want to know how the repository behaves before packaging.\n- The question is about implementation details or internal helper behavior.\n\nUse `dist/` when:\n\n- The question is specifically about what consumers load after `npm run build`.\n- You need to validate export maps or emitted `.js` and `.d.ts` output.\n- The bug only reproduces in generated output.\n\nIf the probe uses `dist/`, record whether you ran a fresh build first. A stale `dist/` directory can create false positives or false negatives.\n\nDo not run `npm run build` only to typecheck or execute a `lib/`-based disposable probe. The fast path is temp `tsconfig` plus `npx tsc --noEmit`, then `npx tsx`.\n\n## Simple Smoke Targets\n\nWhen you only need to prove the helper works, start with a simple target that has minimal side effects, for example:\n\n- `packages/client/lib/index.ts`\n- `packages/client/lib/client/index.ts`\n- `packages/json/lib/index.ts`\n- another file with obvious exported symbols and no environment requirement\n\nIf a chosen file pulls in more of the repo than expected, switch to a simpler target rather than weakening the import guidance.\n\n## What To Record\n\nWhen a probe imports repo code, report:\n\n- The repo-relative path you imported.\n- Whether it came from `lib/` or `dist/`.\n- The repository root used by `process.cwd()`.\n- Any prerequisite step such as `npm run build`.\n",".agents/skills/runtime-behavior-probe/references/reporting-format.md":"# Reporting Format\n\nLead with findings, not process. The user asked for investigation results, so the answer should start with the most important observed behaviors. Put the real news first.\n\n## Recommended Order\n\n1. Findings.\n2. Validation approach.\n3. Case matrix or condensed case summary.\n4. Artifact status and brief run summary.\n5. Optional implementation note.\n\n## Findings Section\n\nMake each finding answer one user-relevant question. Good findings usually include:\n\n- What was observed.\n- Why it matters.\n- The condition under which it happens.\n- What was held constant when the finding comes from a comparison probe.\n- `scope`: The boundary of the finding, such as commit, model, Node version, live versus local, or repeat mode.\n- `confidence`: `high`, `medium`, or `low`.\n\nAvoid burying the main result under setup details.\n\nPut `unexpected` or `negative` findings first. If there were no unexpected or negative findings in the executed cases, say that explicitly before the rest of the findings section.\n\nIf the probe was comparative, say whether the result supports:\n\n- Pattern parity only.\n- A broader quality claim.\n\nDo not imply a broader quality equivalence than the executed cases justify.\n\n## Validation Approach Section\n\nSummarize:\n\n- The runtime surface you exercised.\n- The shape of the probe code, in overview only.\n- Which categories of cases you covered.\n- Which execution modes you used, including repeat counts or warm-up handling when relevant.\n- Whether live credentials or external services were used.\n- Any important state controls such as fresh state, cache reuse, cache busting, unique IDs, or cleanup verification.\n- For comparison probes, what was held constant, what was varied, and whether output-shape or usage differences could still influence the conclusion.\n- Whether the usual docs path or an official-docs fallback was used for contract-sensitive checks.\n\nKeep this concise. The user needs enough detail to trust the result, not a line-by-line replay of the script.\n\n## Case Summary\n\nInclude either the full matrix or a condensed summary. At minimum, show:\n\n- Which scenarios were executed.\n- Whether the run was a quick pilot, an expanded matrix, or both.\n- Which ones produced `unexpected` or `negative` results.\n- Which ones passed or failed when a real comparison basis existed.\n- Which cases were blocked.\n- Where the supporting evidence lived, or that it was deleted.\n\nIf the matrix is large, show the highest-value cases in the main response and keep the rest as a compact appendix or note.\n\n## Artifact Status And Brief Run Summary\n\nState one of these explicitly:\n\n- Temporary artifacts were kept until the final response was drafted, then deleted after validation.\n- Temporary artifacts were kept at `<path>` because the user asked to keep them.\n- Temporary artifacts were kept at `<path>` because they are needed for follow-up analysis.\n\nEven if artifacts were deleted, retain a short run summary such as:\n\n- Probe command or runner shape.\n- Runtime context summary such as commit, Node executable, Node version, or model.\n- Artifact path and final status.\n\nFor benchmark or repeat-heavy probes, keeping artifacts for follow-up is often the right default even when the immediate report is done.\n\n## Optional Implementation Note\n\nInclude this only when one clear defect was isolated and a short implementation hypothesis or minimal repro direction would help. Keep it brief. Do not turn the report into a broader next-step plan unless the user asked for that.\n\n## Compact Template\n\nUse this outline when you need a fast structure:\n\n    Findings:\n    - <finding 1>\n      held constant: <prompt/tool/state settings kept the same, if comparative>\n      scope: <commit/model/node/live-local/repeat-mode>\n      confidence: <high|medium|low>\n    - <finding 2>\n      held constant: <prompt/tool/state settings kept the same, if comparative>\n      scope: <commit/model/node/live-local/repeat-mode>\n      confidence: <high|medium|low>\n\n    Validation approach:\n    - Surface: <what was exercised>\n    - Probe code: <brief overview>\n    - Coverage: <success, edge, error, repeat-sensitive, and quality categories>\n    - Execution modes: <single-shot|repeat-N|warm-up + repeat-N>\n    - Comparison parity: <what was held constant and what varied, if comparative>\n    - Docs source: <MCP or official-docs fallback, if relevant>\n\n    Case summary:\n    | case_id | scenario | result_flag | status | note |\n    | --- | --- | --- | --- | --- |\n    | S1 | ... | expected | pass | ... |\n    | E1 | ... | negative | fail | ... |\n\n    Artifact status and brief run summary:\n    - Temporary artifacts were kept until the final response was drafted, then deleted.\n    - Summary: <command/runtime-context/artifact-status summary>\n\n    Optional implementation note:\n    - <brief hypothesis or minimal repro direction>\n\nAdjust the format to the task, but preserve the ordering.\n",".agents/skills/runtime-behavior-probe/references/validation-matrix.md":"# Validation Matrix\n\nUse the matrix to decide what to probe before writing scripts. The goal is not exhaustive combinatorics; the goal is high-value coverage that is visible, explainable, and likely to reveal runtime surprises. The matrix should make the real news easy to scan.\n\n## Minimum Columns\n\nUse these columns unless the task clearly needs more:\n\n- `case_id`: Stable identifier such as `S1`, `E3`, or `R2`.\n- `scenario`: Short description of the behavior under test.\n- `mode`: `single-shot`, `repeat-N`, or `warm-up + repeat-N`.\n- `question`: The concrete runtime uncertainty this case is answering.\n- `setup`: Inputs, environment, or preconditions required for the case.\n- `observation_summary`: A compact summary of what actually happened.\n- `result_flag`: `unexpected`, `negative`, `expected`, or `blocked`.\n- `evidence`: Path, log reference, or `deleted`.\n\nAdd these columns when they materially improve the investigation:\n\n- `comparison_basis`: The baseline, docs, or prior behavior you are comparing against.\n- `variable_under_test`: The single factor that is intentionally changing in a comparison case.\n- `held_constant`: Prompt shape, tool setup, model settings, or state rules that were intentionally kept the same.\n- `output_constraint`: Any schema, length, or response-shape constraint used to keep the comparison fair.\n- `status`: Use `pass`, `fail`, `unexpected-pass`, `unexpected-fail`, or `blocked` only when there is a credible comparison basis or control.\n- `confidence`: `high`, `medium`, or `low`.\n- `state_setup`: Fresh or reused state, cache strategy, unique IDs, and cleanup checks.\n- `repeats`: Number of measured runs.\n- `warm_up`: Whether a warm-up run was used and why.\n- `variance`: Any useful spread or instability note across repeated runs.\n- `usage_note`: Token, usage, or output-length note when it materially affects interpretation.\n- `control`: Known-good comparison point for regression or behavior-change questions.\n- `risk_profile`: `read-only`, `mutating`, or `costly` for live probes.\n- `env_vars`: Exact environment-variable names the case plans to read.\n- `approval`: `not-needed`, `pending`, or `approved` for cases that need user permission before execution.\n\nUse `result_flag` as the fast scan field. It is what makes unexpected or negative findings jump out before the reader studies the full report.\n\nUse `status` only when you have a real comparison basis. If the case is exploratory and there is no trustworthy baseline, prefer a strong `observation_summary` plus `result_flag` and `confidence` instead of pretending the result is a clean pass or fail.\n\n## Choosing Execution Mode\n\nPick an execution mode before you run the case:\n\n- Use `single-shot` for deterministic, one-run checks.\n- Use `repeat-N` automatically when the question involves cache behavior, retries, streaming, interruptions, rate limiting, concurrency, or other run-to-run-sensitive behavior.\n- Use `warm-up + repeat-N` when the first run is likely to include cold-start effects such as container provisioning, import caches, or prompt-cache population.\n\nUse these defaults unless the task clearly needs something else:\n\n- `repeat-3` for a quick screen of a repeat-sensitive question.\n- `warm-up + repeat-10` for decision-grade latency comparisons or release-facing recommendations.\n- For costly live probes, start at `repeat-3`, then expand only if the answer is still unclear.\n\nIf it is genuinely unclear whether extra runs are worth the time or cost, ask the user before expanding the probe.\n\n## Phase The Matrix\n\nWhen the question is comparative or benchmark-like, do not jump straight to the largest matrix.\n\nStart with a pilot:\n\n- One control.\n- One or two highest-signal success cases.\n- The smallest repeat count that can disqualify a weak candidate quickly.\n\nExpand only when:\n\n- The candidate survives the pilot.\n- The results are close enough that more samples matter.\n- A major runtime surface is still uncovered.\n- The user explicitly wants decision-grade evidence.\n\n## Coverage Categories\n\nTry to cover at least one case from each relevant category:\n\n- `success`: Normal behavior that should work.\n- `control`: Known-good comparison such as `origin/master`, the latest release, or the same request without the suspected option.\n- `boundary`: Size, count, or parameter limits near a plausible edge.\n- `invalid`: Bad inputs or unsupported combinations.\n- `misconfig`: Missing key, wrong endpoint, bad permissions, or incompatible local setup.\n- `transient`: Timeout, temporary server issue, network breakage, or rate limiting.\n- `recovery`: Retry behavior, partial completion, duplicate submission, or cleanup.\n- `concurrency`: Overlapping operations when shared state, ordering, or isolation may matter.\n- `quality`: A harder or more open-ended sample when the user is asking about model intelligence, not just workflow parity.\n\nIf time is limited, prioritize categories in this order:\n\n1. Known-good control when the question implies regression or drift.\n2. Highest-risk success case.\n3. Most plausible user-facing failure.\n4. Most likely edge case with ambiguous behavior.\n5. Cleanup or retry semantics.\n6. Lower-probability extremes.\n\n## Matrix Template\n\nUse this compact template:\n\n    | case_id | scenario | mode | question | setup | state_setup | variable_under_test | held_constant | comparison_basis | observation_summary | result_flag | status | evidence |\n    | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n    | K1 | Known-good control | single-shot | Does the baseline still show the expected behavior? | Same probe against baseline target | Fresh state | none | current probe shape | `origin/master` or latest release | pending | pending | pending | pending |\n    | S1 | Baseline success | single-shot | What does the normal success path look like at runtime? | Valid config and representative input | Fresh state | none | representative input and setup | current docs or local expectation | pending | pending | pending | pending |\n    | R1 | Cache or retry behavior | warm-up + repeat-N | Does behavior change after the first run or across retries? | Same request repeated under controlled settings | Cache key or retry setup recorded | reuse versus fresh state | prompt shape and tool setup | same request without reuse, or docs if available | pending | pending | pending | pending |\n    | C1 | Model comparison pilot | warm-up + repeat-N | Does candidate B preserve the covered behavior while improving latency? | Same scenario across two models | Fresh state and stable IDs | model name | prompt shape, tool choice, and model settings parity | control model in the same probe | pending | pending | pending | pending |\n    | E1 | Invalid input | single-shot | How does the runtime reject a realistic bad input? | Missing required field | Fresh state | invalid field value | same request with valid field | same request with valid field | pending | pending | pending | pending |\n    | X1 | Concurrent overlap | repeat-N | Do overlapping runs interfere with each other? | Two or more overlapping operations | Unique IDs plus cleanup verification | overlap timing | same logical input | same request serialized, if available | pending | pending | pending | pending |\n\n## Recording Results\n\nKeep `question` unchanged after execution. Put the actual behavior in `observation_summary`, then mark the scan-friendly `result_flag`.\n\nUse these `result_flag` values consistently:\n\n- `unexpected`: The result diverged from the best current understanding in a surprising way.\n- `negative`: The result exposed a user-relevant failure, risk, or sharp edge.\n- `expected`: The result matched the current understanding and did not reveal new risk.\n- `blocked`: The case did not produce a trustworthy observation.\n\nOnly fill `status` when there is a credible comparison basis. Otherwise use `observation_summary`, `result_flag`, and `confidence` to communicate what was learned without over-claiming certainty.\n\nFor comparison cases, use `observation_summary` and the final report to say whether the evidence supports pattern parity only or a broader quality claim.\n\nIf a case reveals a new branch of behavior, add a follow-up case instead of overloading the original one.\n\n## Evidence Discipline\n\nTreat a case as incomplete when:\n\n- The observed output omits the key result you were testing.\n- The script mixed multiple questions and the result is ambiguous.\n- Hidden state, cache behavior, or previous runs may have influenced the result and were not controlled or documented.\n- The question is whether behavior changed, but the case has no credible control or baseline to compare against.\n- The case plans to read environment variables, but the exact variable names were not approved by the user before execution.\n- The case was repeat-sensitive, but it ran only once without a clear rationale.\n\nWhen this happens, narrow the probe and rerun. A smaller script with a cleaner result is better than a more complicated script that is hard to trust.\n",".agents/skills/test-coverage-improver/SKILL.md":"--\nname: test-coverage-improver\ndescription: 'Improve test coverage in the node-redis monorepo: run a package test suite under nyc, inspect coverage artifacts, identify low-coverage files and branches, propose high-impact tests, and confirm with the user before writing tests.'\n---\n\n# Test Coverage Improver\n\n## Overview\n\nUse this skill whenever coverage needs assessment or improvement (coverage regressions, failing thresholds, or user requests for stronger tests). It runs the coverage suite, analyzes results, highlights the biggest gaps, and prepares test additions while confirming with the user before changing code.\n\nCoverage is collected per package via `nyc` (each package's `test` script runs `nyc -r text-summary -r lcov mocha ...`). **Docker must be running** — the test harness starts real Redis containers via `@redis/test-utils`.\n\n## Quick Start\n\n1. Pick the target package (e.g. `@redis/client`) and run its tests under coverage from the repo root: `npm test -w @redis/client`. This regenerates that package's `coverage/`.\n2. Collect artifacts: the `text-summary` printed to stdout for totals, plus `packages/<pkg>/coverage/lcov.info` and `packages/<pkg>/coverage/lcov-report/index.html` for line/branch drill-downs. For machine-readable file totals, rerun with an added summary reporter: `npm test -w @redis/client -- ... ` is awkward, so prefer `npx nyc -r json-summary -r text-summary mocha ...` from inside the package when you need `coverage/coverage-summary.json`.\n3. Summarize coverage: total percentages, lowest files, branches under 80%, and uncovered lines/paths.\n4. Draft test ideas per file: scenario, behavior under test, expected outcome, and likely coverage gain.\n5. Ask the user for approval to implement the proposed tests; pause until they agree.\n6. After approval, write the tests next to the source in the relevant package, rerun coverage, then run `npm run build` and `npm run lint` before marking work complete.\n\n## Workflow Details\n\n- **Run coverage**: Execute the target package's test script (`npm test -w <pkg>`) from the repo root. Avoid watch flags. Keep prior coverage artifacts only if comparing trends. Confirm Docker is available first.\n- **Parse summaries efficiently**:\n  - Use the printed `text-summary` for quick totals.\n  - Use `coverage/lcov.info` or `coverage/lcov-report/index.html` to spot branch- and line-level holes.\n  - Add the `json-summary` nyc reporter when you need `coverage/coverage-summary.json` for file-level totals.\n- **Prioritize targets**:\n  - Core client/cluster/sentinel/pool/RESP code in `packages/*/lib` before examples or docs.\n  - Files with statements/branches below 80% or newly added code at 0%.\n  - Recent bug fixes or risky code paths (error handling, reconnection, timeouts, pub/sub, concurrency).\n- **Design impactful tests**:\n  - Hit uncovered branches: error cases, boundary inputs, optional flags, and cancellation/timeouts.\n  - Cover combinational logic rather than trivial happy paths.\n  - Co-locate specs next to source as `packages/<pkg>/lib/**/<NAME>.spec.ts` (mocha + `tsx`, `node:assert`). For command/topology tests use `testUtils.testAll(name, fn, { client, cluster })`. Avoid flaky async timing.\n- **Coordinate with the user**: Present a numbered, concise list of proposed test additions and expected coverage gains. Ask explicitly before editing code or fixtures.\n- **After implementation**: Rerun coverage, report the updated summary, and note any remaining low-coverage areas.\n\n## Notes\n\n- Keep any added comments or code in English.\n- Do not create `scripts/`, `references/`, or `assets/` unless needed later.\n- If coverage artifacts are missing or stale, rerun the package test suite instead of guessing.\n- `nyc` excludes `dist`, `**/*.spec.ts`, `lib/test-utils.ts`, and `examples/*` (see each package's `.nycrc`); do not target those for coverage.\n",".agents/skills/test-coverage-improver/agents/openai.yaml":"interface:\n  display_name: \"Test Coverage Improver\"\n  short_description: \"Analyze coverage gaps and propose high-impact tests\"\n  default_prompt: \"Use $test-coverage-improver to analyze coverage gaps, propose high-impact tests, and update coverage after approval.\"\n","AGENTS.md":"# AGENTS.md\n\nGuidance for agents working in this repo.\n\n## What this is\n\n`node-redis` — modern Redis client for Node.js. A **monorepo** (npm workspaces) of\npublishable packages. Requires Node `>= 20`. TypeScript throughout.\n\n## Packages (`packages/`)\n\n| Package | Purpose |\n| --- | --- |\n| `redis` | \"All-in-one\" meta-package re-exporting client + all modules |\n| `client` (`@redis/client`) | Core: `RedisClient`, `RedisCluster`, `RedisSentinel`, pool, RESP codec, command framework |\n| `bloom` (`@redis/bloom`) | Probabilistic commands (bloom, cuckoo, count-min, t-digest, top-k) |\n| `json` (`@redis/json`) | RedisJSON commands |\n| `search` (`@redis/search`) | RediSearch commands |\n| `time-series` (`@redis/time-series`) | Time-series commands |\n| `entraid` (`@redis/entraid`) | Microsoft Entra ID token auth |\n| `test-utils` (`@redis/test-utils`) | Shared test harness; spins up Redis via docker |\n\nModule packages depend on `@redis/client` and follow the same command structure.\n\n## Core layout — `packages/client/lib/`\n\n- `client/` — connection internals: `index.ts` (RedisClient), `socket.ts`, `commands-queue.ts`, `parser.ts`, `pool.ts`, `pub-sub.ts`, `cache.ts`\n- `cluster/`, `sentinel/` — cluster & sentinel clients\n- `commands/` — one file per Redis command (e.g. `GET.ts`) + `index.ts` registry\n- `RESP/` — RESP2/RESP3 protocol: `encoder.ts`, `decoder.ts`, `types.ts`\n- `authx/` — auth/credential providers\n\n## Command pattern\n\nEach command is `<NAME>.ts` exporting a `Command` object:\n\n```typescript\nexport default {\n  CACHEABLE: true,\n  IS_READ_ONLY: true,\n  parseCommand(parser: CommandParser, key: RedisArgument) {\n    parser.push('GET');\n    parser.pushKey(key);\n  },\n  transformReply: undefined as unknown as () => BlobStringReply | NullReply\n} as const satisfies Command;\n```\n\n- `parseCommand` builds wire args via `CommandParser` (`push`, `pushKey`, ...).\n- `transformReply` maps reply to JS type; `undefined` = pass-through. Can be keyed by RESP version `{ 2: ..., 3: ... }`.\n- Register new command in the package's `commands/index.ts` (import + map entry). RESP3 is default — no extra RESP3 test needed for new commands.\n- JSDoc on commands is checked: `npm run check:command-jsdoc`.\n\n## Tests\n\n- Co-located `<NAME>.spec.ts` next to source. Mocha + `tsx`, `node:assert`.\n- `testUtils.testAll(name, fn, { client, cluster })` runs same test across server + cluster topologies (see `test-utils.ts`, `GLOBAL`).\n- **Docker required** — test-utils starts real Redis containers.\n- Pure arg/reply tests use `parseArgs(COMMAND, ...args)`.\n\nCommands:\n- `npm test` — full suite, all workspaces (runs `cleanup` first).\n- `npm test -w @redis/client` — one package.\n- Single file from root: `npm run test-single -- <path-to-spec>`.\n- `npm run build` — `tsc --build` (project references; build before cross-package work).\n- Build can break on stale `dist/` from project references. Clean rebuild:\n  ```bash\n  find packages -type d -name \"dist\" -exec rm -rf {} + && npm run build\n  ```\n- `npm run lint` — lint changed files only; `npm run lint:all` for everything.\n\n## Conventions\n\n- TypeScript strict mode; `noUnusedLocals` on. Target ES2022 / NodeNext modules.\n- Raw command names (`HSET`) and camelCase aliases (`hSet`) both exposed.\n- Conventional Commits. Per-package releases via `release-it` (`npm run release`).\n- Keep company-internal refs (Jira/Confluence IDs, internal links) out of OSS commits, branches, PRs, code.\n\n## Docs\n\nDeep-dive guides in `docs/` (client-configuration, clustering, sentinel, pool, RESP, transactions, programmability, pub-sub, scan-iterators, migration guides). Runnable examples in `examples/`, `doctests/`.\n","packages/bloom/README.md":"# @redis/bloom\n\nThis package provides support for the [RedisBloom](https://redis.io/docs/data-types/probabilistic/) module, which adds additional probabilistic data structures to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisBloom module installed.\n\nRedisBloom provides the following probabilistic data structures:\n\n* Bloom Filter: for checking set membership with a high degree of certainty.\n* Cuckoo Filter: for checking set membership with a high degree of certainty.\n* T-Digest: for estimating the quantiles of a stream of data.\n* Top-K: Maintain a list of k most frequently seen items.\n* Count-Min Sketch: Determine the frequency of events in a stream.\n\nFor some examples, see [`bloom-filter.js`](https://github.com/redis/node-redis/tree/master/examples/bloom-filter.js), [`cuckoo-filter.js`](https://github.com/redis/node-redis/tree/master/examples/cuckoo-filter.js), [`count-min-sketch.js`](https://github.com/redis/node-redis/tree/master/examples/count-min-sketch.js) and [`topk.js`](https://github.com/redis/node-redis/tree/master/examples/topk.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n","packages/client/README.md":"# @redis/client\n\nThe source code and documentation for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.\n","packages/entraid/README.md":"# @redis/entraid\n\nSecure token-based authentication for Redis clients using Microsoft Entra ID (formerly Azure Active Directory).\n\n## Features\n\n- Token-based authentication using Microsoft Entra ID\n- Automatic token refresh before expiration\n- Automatic re-authentication of all connections after token refresh\n- Support for multiple authentication flows:\n  - Managed identities (system-assigned and user-assigned)\n  - Service principals (with or without certificates)\n  - Authorization Code with PKCE flow\n  - DefaultAzureCredential from @azure/identity\n- Built-in retry mechanisms for transient failures\n\n## Installation\n\n\n```bash\nnpm install \"@redis/client@5.0.0-next.7\"\nnpm install \"@redis/entraid@5.0.0-next.7\"\n```\n\n## Getting Started\n\nThe first step to using @redis/entraid is choosing the right credentials provider for your authentication needs. The `EntraIdCredentialsProviderFactory` class provides several factory methods to create the appropriate provider:\n\n- `createForSystemAssignedManagedIdentity`: Use when your application runs in Azure with a system-assigned managed identity\n- `createForUserAssignedManagedIdentity`: Use when your application runs in Azure with a user-assigned managed identity\n- `createForClientCredentials`: Use when authenticating with a service principal using client secret\n- `createForClientCredentialsWithCertificate`: Use when authenticating with a service principal using a certificate\n- `createForAuthorizationCodeWithPKCE`: Use for interactive authentication flows in user applications\n- `createForDefaultAzureCredential`: Use when you want to leverage Azure Identity's DefaultAzureCredential\n\n## Usage Examples\n\n### Service Principal Authentication\n\n```typescript\nimport { createClient } from '@redis/client';\nimport { EntraIdCredentialsProviderFactory } from '@redis/entraid';\n\nconst provider = EntraIdCredentialsProviderFactory.createForClientCredentials({\n  clientId: 'your-client-id',\n  clientSecret: 'your-client-secret',\n  authorityConfig: {\n    type: 'multi-tenant',\n    tenantId: 'your-tenant-id'\n  },\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8 // Refresh token after 80% of its lifetime\n  }\n});\n\nconst client = createClient({\n  url: 'redis://your-host',\n  credentialsProvider: provider\n});\n\nawait client.connect();\n```\n\n### System-Assigned Managed Identity\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForSystemAssignedManagedIdentity({\n  clientId: 'your-client-id',\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n```\n\n### User-Assigned Managed Identity\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForUserAssignedManagedIdentity({\n  clientId: 'your-client-id',\n  userAssignedClientId: 'your-user-assigned-client-id',\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n```\n\n### DefaultAzureCredential Authentication\n\ntip: see a real sample here: [samples/interactive-browser/index.ts](./samples/interactive-browser/index.ts)\n\nThe DefaultAzureCredential from @azure/identity provides a simplified authentication experience that automatically tries different authentication methods based on the environment. This is especially useful for applications that need to work in different environments (local development, CI/CD, and production).\n\n```typescript\nimport { createClient } from '@redis/client';\nimport { getDefaultAzureCredential } from '@azure/identity';\nimport { EntraIdCredentialsProviderFactory, REDIS_SCOPE_DEFAULT } from '@redis/entraid';\n\n// Create a DefaultAzureCredential instance\nconst credential = getDefaultAzureCredential();\n\n// Create a provider using DefaultAzureCredential\nconst provider = EntraIdCredentialsProviderFactory.createForDefaultAzureCredential({\n\n  credential,\n  scopes: REDIS_SCOPE_DEFAULT, // The Redis scope\n  // Optional additional parameters for getToken\n  options: {\n    // Any options you would normally pass to azure's default credential.getToken()\n  },\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n\nconst client = createClient({\n  url: 'redis://your-host',\n  credentialsProvider: provider\n});\n\nawait client.connect();\n```\n\n#### Important Notes on Using DefaultAzureCredential\n\nWhen using the `createForDefaultAzureCredential` method, you need to:\n\n1. Create your own instance of `DefaultAzureCredential`\n2. Pass the same parameters to the factory method that you would use with the `getToken()` method:\n   - `scopes`: The Redis scope (use the exported `REDIS_SCOPE_DEFAULT` constant)\n   - `options`: Any additional options for the getToken method\n\nThis factory method creates a wrapper around DefaultAzureCredential that adapts it to the Redis client's\nauthentication system, while maintaining all the flexibility of the original Azure Identity authentication.\n\n## Important Limitations\n\n### RESP2 PUB/SUB Limitations\n\nWhen using RESP2 (Redis Serialization Protocol 2), there are important limitations with PUB/SUB:\n\n- **No Re-Authentication in PUB/SUB Mode**: In RESP2, once a connection enters PUB/SUB mode, the socket is blocked and cannot process out-of-band commands like AUTH. This means that connections in PUB/SUB mode cannot be re-authenticated when tokens are refreshed.\n- **Connection Eviction**: As a result, PUB/SUB connections will be evicted by the Redis proxy when their tokens expire. The client will need to establish new connections with fresh tokens.\n\n### Transaction Safety\n\nWhen using token-based authentication, special care must be taken with Redis transactions. The token manager runs in the background and may attempt to re-authenticate connections at any time by sending AUTH commands. This can interfere with manually constructed transactions.\n\n#### ✅ Recommended: Use the Official Transaction API\n\nAlways use the official transaction API provided by the client:\n\n```typescript\n// Correct way to handle transactions\nconst multi = client.multi();\nmulti.set('key1', 'value1');\nmulti.set('key2', 'value2');\nawait multi.exec();\n```\n\n#### ❌ Avoid: Manual Transaction Construction\n\nDo not manually construct transactions by sending individual MULTI/EXEC commands:\n\n```typescript\n// Incorrect and potentially dangerous\nawait client.sendCommand(['MULTI']);\nawait client.sendCommand(['SET', 'key1', 'value1']);\nawait client.sendCommand(['SET', 'key2', 'value2']);\nawait client.sendCommand(['EXEC']); // Risk of AUTH command being injected before EXEC\n```\n\n## Error Handling\n\nThe provider includes built-in retry mechanisms for transient errors:\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForClientCredentials({\n  // ... other config ...\n  tokenManagerConfig: {\n    retry: {\n      maxAttempts: 3,\n      initialDelayMs: 100,\n      maxDelayMs: 1000,\n      backoffMultiplier: 2\n    }\n  }\n});\n```\n","packages/json/README.md":"# @redis/json\n\nThis package provides support for the [RedisJSON](https://redis.io/docs/latest/develop/data-types/json/) module, which adds JSON as a native data type to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisJSON module installed.\n\n## Usage\n\nFor a complete example, see [`managing-json.js`](https://github.com/redis/node-redis/blob/master/examples/managing-json.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n\n### Storing JSON Documents in Redis\n\nThe [`JSON.SET`](https://redis.io/commands/json.set/) command stores a JSON value at a given JSON Path in a Redis key.\n\nHere, we'll store a JSON document in the root of the Redis key \"`mydoc`\":\n\n```javascript\nawait client.json.set('noderedis:jsondata', '$', {\n  name: 'Roberta McDonald',\n  pets: [{\n    name: 'Rex',\n    species: 'dog',\n    age: 3,\n    isMammal: true\n  }, {\n    name: 'Goldie',\n    species: 'fish',\n    age: 2,\n    isMammal: false\n  }]\n});\n```\n\nFor more information about RedisJSON's path syntax, [check out the documentation](https://redis.io/docs/latest/develop/data-types/json/path).\n\n### Retrieving JSON Documents from Redis\n\nWith RedisJSON, we can retrieve all or part(s) of a JSON document using the [`JSON.GET`](https://redis.io/commands/json.get/) command and one or more JSON Paths. Let's get the name and age of one of the pets:\n\n```javascript\nconst results = await client.json.get('noderedis:jsondata', {\n  path: [\n    '.pets[1].name',\n    '.pets[1].age'\n  ]\n});\n```\n\n`results` will contain the following:\n\n```javascript\n { '.pets[1].name': 'Goldie', '.pets[1].age': 2 }\n```\n\n### Performing Atomic Updates on JSON Documents Stored in Redis\n\nRedisJSON includes commands that can atomically update values in a JSON document, in place in Redis without having to first retrieve the entire document.\n\nUsing the [`JSON.NUMINCRBY`](https://redis.io/commands/json.numincrby/) command, we can update the age of one of the pets like this:\n\n```javascript\nawait client.json.numIncrBy('noderedis:jsondata', '.pets[1].age', 1);\n```\n\nAnd we can add a new object to the pets array with the [`JSON.ARRAPPEND`](https://redis.io/commands/json.arrappend/) command:\n\n```javascript\nawait client.json.arrAppend('noderedis:jsondata', '.pets', {\n  name: 'Robin',\n  species: 'bird',\n  age: 1,\n  isMammal: false\n});\n```\n","packages/redis/README.md":"# Node-Redis\n\n[![Tests](https://img.shields.io/github/actions/workflow/status/redis/node-redis/tests.yml?branch=master)](https://github.com/redis/node-redis/actions/workflows/tests.yml)\n[![Coverage](https://codecov.io/gh/redis/node-redis/branch/master/graph/badge.svg?token=xcfqHhJC37)](https://codecov.io/gh/redis/node-redis)\n[![License](https://img.shields.io/github/license/redis/node-redis.svg)](https://github.com/redis/node-redis/blob/master/LICENSE)\n\n[![Discord](https://img.shields.io/discord/697882427875393627.svg?style=social&logo=discord)](https://discord.gg/redis)\n[![Twitch](https://img.shields.io/twitch/status/redisinc?style=social)](https://www.twitch.tv/redisinc)\n[![YouTube](https://img.shields.io/youtube/channel/views/UCD78lHSwYqMlyetR0_P4Vig?style=social)](https://www.youtube.com/redisinc)\n[![Twitter](https://img.shields.io/twitter/follow/redisinc?style=social)](https://twitter.com/redisinc)\n\nnode-redis is a modern, high performance [Redis](https://redis.io) client for Node.js.\n\n## How do I Redis?\n\n[Learn for free at Redis University](https://university.redis.com/)\n\n[Build faster with the Redis Launchpad](https://launchpad.redis.com/)\n\n[Try the Redis Cloud](https://redis.com/try-free/)\n\n[Dive in developer tutorials](https://developer.redis.com/)\n\n[Join the Redis community](https://redis.com/community/)\n\n[Work at Redis](https://redis.com/company/careers/jobs/)\n\n## Installation\n\nStart a redis via docker:\n\n```bash\ndocker run -p 6379:6379 -d redis:8.0-rc1\n```\n\nTo install node-redis, simply:\n\n```bash\nnpm install redis\n```\n> \"redis\" is the \"whole in one\" package that includes all the other packages. If you only need a subset of the commands,\n> you can install the individual packages. See the list below.\n\n## Packages\n\n| Name                                           | Description                                                                                 |\n| ---------------------------------------------- | ------------------------------------------------------------------------------------------- |\n| [`redis`](https://github.com/redis/node-redis/tree/master/packages/redis)                    | The client with all the [\"redis-stack\"](https://github.com/redis-stack/redis-stack) modules |\n| [`@redis/client`](https://github.com/redis/node-redis/tree/master/packages/client)           | The base clients (i.e `RedisClient`, `RedisCluster`, etc.)                                  |\n| [`@redis/bloom`](https://github.com/redis/node-redis/tree/master/packages/bloom)             | [Redis Bloom](https://redis.io/docs/data-types/probabilistic/) commands                     |\n| [`@redis/json`](https://github.com/redis/node-redis/tree/master/packages/json)               | [Redis JSON](https://redis.io/docs/data-types/json/) commands                               |\n| [`@redis/search`](https://github.com/redis/node-redis/tree/master/packages/search)           | [RediSearch](https://redis.io/docs/interact/search-and-query/) commands                     |\n| [`@redis/time-series`](https://github.com/redis/node-redis/tree/master/packages/time-series) | [Redis Time-Series](https://redis.io/docs/data-types/timeseries/) commands                  |\n| [`@redis/entraid`](https://github.com/redis/node-redis/tree/master/packages/entraid)         | Secure token-based authentication for Redis clients using Microsoft Entra ID                |\n\n> Looking for a high-level library to handle object mapping?\n> See [redis-om-node](https://github.com/redis/redis-om-node)!\n\n\n## Usage\n\n### Basic Example\n\n```typescript\nimport { createClient } from \"redis\";\n\nconst client = await createClient()\n  .on(\"error\", (err) => console.log(\"Redis Client Error\", err))\n  .connect();\n\nawait client.set(\"key\", \"value\");\nconst value = await client.get(\"key\");\nclient.destroy();\n```\n\nThe above code connects to localhost on port 6379. To connect to a different host or port, use a connection string in\nthe format `redis[s]://[[username][:password]@][host][:port][/db-number]`:\n\n```typescript\ncreateClient({\n  url: \"redis://alice:foobared@awesome.redis.server:6380\",\n});\n```\n\nYou can also use discrete parameters, UNIX sockets, and even TLS to connect. Details can be found in\nthe [client configuration guide](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md).\n\nTo check if the the client is connected and ready to send commands, use `client.isReady` which returns a boolean.\n`client.isOpen` is also available. This returns `true` when the client's underlying socket is open, and `false` when it\nisn't (for example when the client is still connecting or reconnecting after a network error).\n\n### Redis Commands\n\nThere is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed\nusing the raw Redis command names (`HSET`, `HGETALL`, etc.) and a friendlier camel-cased version (`hSet`, `hGetAll`,\netc.):\n\n```typescript\n// raw Redis commands\nawait client.HSET(\"key\", \"field\", \"value\");\nawait client.HGETALL(\"key\");\n\n// friendly JavaScript commands\nawait client.hSet(\"key\", \"field\", \"value\");\nawait client.hGetAll(\"key\");\n```\n\nModifiers to commands are specified using a JavaScript object:\n\n```typescript\nawait client.set(\"key\", \"value\", {\n  EX: 10,\n  NX: true,\n});\n```\n\nCommands that take several values of the same kind, such as `DEL`, `HDEL` or `LPUSH`, accept either a single value or\nan array:\n\n```typescript\nawait client.hDel(\"key\", \"field\");\nawait client.hDel(\"key\", [\"field1\", \"field2\"]);\n```\n\nDo not spread the values into separate arguments (`client.hDel(\"key\", ...fields)`). Only the first value is sent, and\nthe rest are silently ignored.\n\nReplies will be transformed into useful data structures:\n\n```typescript\nawait client.hGetAll(\"key\"); // { field1: 'value1', field2: 'value2' }\nawait client.hVals(\"key\"); // ['value1', 'value2']\n```\n\n`Buffer`s are supported as well:\n\n```typescript\nconst client = createClient().withTypeMapping({\n  [RESP_TYPES.BLOB_STRING]: Buffer\n});\n\nawait client.hSet(\"key\", \"field\", Buffer.from(\"value\")); // 'OK'\nawait client.hGet(\"key\", \"field\"); // { field: <Buffer 76 61 6c 75 65> }\n\n```\n\nFor commands that return serialized binary payloads, such as `DUMP`, map blob strings to `Buffer` before using the result with commands like `RESTORE`:\n\n```typescript\nconst binaryClient = createClient().withTypeMapping({\n  [RESP_TYPES.BLOB_STRING]: Buffer\n});\n\nconst dump = await binaryClient.dump(\"source\");\nawait binaryClient.restore(\"destination\", 0, dump);\n```\n\n### Unsupported Redis Commands\n\nIf you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use `.sendCommand()`:\n\n```typescript\nawait client.sendCommand([\"SET\", \"key\", \"value\", \"NX\"]); // 'OK'\n\nawait client.sendCommand([\"HGETALL\", \"key\"]); // ['key1', 'field1', 'key2', 'field2']\n```\n\n### Transactions (Multi/Exec)\n\nStart a [transaction](https://redis.io/topics/transactions) by calling `.multi()`, then chaining your commands. When\nyou're done, call `.exec()` and you'll get an array back with your results:\n\n```typescript\nawait client.set(\"another-key\", \"another-value\");\n\nconst [setKeyReply, otherKeyValue] = await client\n  .multi()\n  .set(\"key\", \"value\")\n  .get(\"another-key\")\n  .exec(); // ['OK', 'another-value']\n```\n\nYou can also [watch](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set) keys by calling\n`.watch()`. Your transaction will abort if any of the watched keys change.\n\n\n### Blocking Commands\n\nIn v4, `RedisClient` had the ability to create a pool of connections using an \"Isolation Pool\" on top of the \"main\"\nconnection. However, there was no way to use the pool without a \"main\" connection:\n\n```javascript\nconst client = await createClient()\n  .on(\"error\", (err) => console.error(err))\n  .connect();\n\nawait client.ping(client.commandOptions({ isolated: true }));\n```\n\nIn v5 we've extracted this pool logic into its own class—`RedisClientPool`:\n\n```javascript\nconst pool = await createClientPool()\n  .on(\"error\", (err) => console.error(err))\n  .connect();\n\nawait pool.ping();\n```\n\n\n### Pub/Sub\n\nSee the [Pub/Sub overview](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md).\n\n### Scan Iterator\n\n[`SCAN`](https://redis.io/commands/scan) results can be looped over\nusing [async iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator):\n\n```typescript\nfor await (const key of client.scanIterator()) {\n  // use the key!\n  await client.get(key);\n}\n```\n\nThis works with `HSCAN`, `SSCAN`, and `ZSCAN` too:\n\n```typescript\nfor await (const { field, value } of client.hScanIterator(\"hash\")) {\n}\nfor await (const member of client.sScanIterator(\"set\")) {\n}\nfor await (const { score, value } of client.zScanIterator(\"sorted-set\")) {\n}\n```\n\nYou can override the default options by providing a configuration object:\n\n```typescript\nclient.scanIterator({\n  TYPE: \"string\", // `SCAN` only\n  MATCH: \"patter*\",\n  COUNT: 100,\n});\n```\n\n### Disconnecting\n\nThe `QUIT` command has been deprecated in Redis 7.2 and should now also be considered deprecated in Node-Redis. Instead\nof sending a `QUIT` command to the server, the client can simply close the network connection.\n\n`client.QUIT/quit()` is replaced by `client.close()`. and, to avoid confusion, `client.disconnect()` has been renamed to\n`client.destroy()`.\n\n```typescript\nclient.destroy();\n```\n### Client Side Caching\n\n[Client Side Caching](https://redis.io/docs/manual/client-side-caching/) keeps command replies in a local cache. The Redis server notifies the client when cached replies are no longer valid. Requires RESP3.\n\n```typescript\nconst client = createClient({\n  clientSideCache: {\n    ttl: 0,                // Time-to-live (0 = no expiration)\n    maxEntries: 0,         // Maximum entries (0 = unlimited)\n    evictPolicy: \"LRU\",    // Eviction policy: \"LRU\" or \"FIFO\"\n    trackingMode: \"optin\"  // \"plain\" (default), \"optin\", or \"optout\"\n  }\n});\nawait client.connect();\n\nawait client.withCommandOptions({ cache: true }).get(\"user:42\"); // cached\nawait client.get(\"counter:hits\");                                // not cached in \"optin\" mode\n```\n\nSee [Client Side Caching](https://github.com/redis/node-redis/blob/master/docs/client-side-caching.md) for tracking modes, choosing what is cached, and more.\n\n### Auto-Pipelining\n\nNode Redis will automatically pipeline requests that are made during the same \"tick\".\n\n```typescript\nclient.set(\"Tm9kZSBSZWRpcw==\", \"users:1\");\nclient.sAdd(\"users:1:tokens\", \"Tm9kZSBSZWRpcw==\");\n```\n\nOf course, if you don't do something with your Promises you're certain to\nget [unhandled Promise exceptions](https://nodejs.org/api/process.html#process_event_unhandledrejection). To take\nadvantage of auto-pipelining and handle your Promises, use `Promise.all()`.\n\n```typescript\nawait Promise.all([\n  client.set(\"Tm9kZSBSZWRpcw==\", \"users:1\"),\n  client.sAdd(\"users:1:tokens\", \"Tm9kZSBSZWRpcw==\"),\n]);\n```\n\n### Programmability\n\nSee the [Programmability overview](https://github.com/redis/node-redis/blob/master/docs/programmability.md).\n\n### Clustering\n\nCheck out the [Clustering Guide](https://github.com/redis/node-redis/blob/master/docs/clustering.md) when using Node Redis to connect to a Redis Cluster.\n\n### Events\n\nThe Node Redis client class is an Nodejs EventEmitter and it emits an event each time the network status changes:\n\n| Name                    | When                                                                               | Listener arguments                                        |\n| ----------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------- |\n| `connect`               | Initiating a connection to the server                                              | _No arguments_                                            |\n| `ready`                 | Client is ready to use                                                             | _No arguments_                                            |\n| `end`                   | Connection has been closed (via `.disconnect()`)                                   | _No arguments_                                            |\n| `error`                 | An error has occurred—usually a network issue such as \"Socket closed unexpectedly\" | `(error: Error)`                                          |\n| `reconnecting`          | Client is trying to reconnect to the server                                        | _No arguments_                                            |\n| `terminated`             | Reconnection has stopped because `reconnectStrategy` returned `false` or an `Error`; emitted before the companion `error` | `(cause: Error)`                                           |\n| `sharded-channel-moved` | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event)                          | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event) |\n\n> :warning: You **MUST** listen to `error` events. If a client doesn't have at least one `error` listener registered and\n> an `error` occurs, that error will be thrown and the Node.js process will exit. See the [ > `EventEmitter` docs](https://nodejs.org/api/events.html#events_error_events) for more details.\n\n> The client will not emit [any other events](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md#all-the-removed-events) beyond those listed above.\n\n## Supported Redis versions\n\nNode Redis is supported with the following versions of Redis:\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 8.0.z   | :heavy_check_mark: |\n| 7.4.z   | :heavy_check_mark: |\n| 7.2.z   | :heavy_check_mark: |\n| < 7.2   | :x:                |\n\n> Node Redis should work with older versions of Redis, but it is not fully tested and we cannot offer support.\n\n## Migration\n\n- [From V3 to V4](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md)\n- [From V4 to V5](https://github.com/redis/node-redis/blob/master/docs/v4-to-v5.md)\n- [V5](https://github.com/redis/node-redis/blob/master/docs/v5.md)\n\n## Contributing\n\nIf you'd like to contribute, check out the [contributing guide](https://github.com/redis/node-redis/blob/master/CONTRIBUTING.md).\n\nThank you to all the people who already contributed to Node Redis!\n\n[![Contributors](https://contrib.rocks/image?repo=redis/node-redis)](https://github.com/redis/node-redis/graphs/contributors)\n\n## License\n\nThis repository is licensed under the \"MIT\" license. See [LICENSE](https://github.com/redis/node-redis/blob/master/LICENSE).\n","packages/search/README.md":"# @redis/search\n\nThis package provides support for the [RediSearch](https://redis.io/docs/interact/search-and-query/) module, which adds indexing and querying support for data stored in Redis Hashes or as JSON documents with the [RedisJSON](https://redis.io/docs/data-types/json/) module.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RediSearch module installed. To index and query JSON documents, you'll also need to add the RedisJSON module.\n\n## Usage\n\nFor complete examples, see [`search-hashes.js`](https://github.com/redis/node-redis/blob/master/examples/search-hashes.js) and [`search-json.js`](https://github.com/redis/node-redis/blob/master/examples/search-json.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n\n### Indexing and Querying Data in Redis Hashes\n\n#### Creating an Index\n\nBefore we can perform any searches, we need to tell RediSearch how to index our data, and which Redis keys to find that data in.  The [FT.CREATE](https://redis.io/commands/ft.create) command creates a RediSearch index. Here's how to use it to create an index we'll call `idx:animals` where we want to index hashes containing `name`, `species` and `age` fields, and whose key names in Redis begin with the prefix `noderedis:animals`:\n\n```javascript\nawait client.ft.create('idx:animals', {\n  name: {\n    type: SCHEMA_FIELD_TYPE.TEXT,\n    SORTABLE: true\n  },\n  species: SCHEMA_FIELD_TYPE.TAG,\n  age: SCHEMA_FIELD_TYPE.NUMERIC\n}, {\n  ON: 'HASH',\n  PREFIX: 'noderedis:animals'\n});\n```\n\nSee the [`FT.CREATE` documentation](https://redis.io/commands/ft.create/#description) for information about the different field types and additional options.\n\n#### Indexing a Field Multiple Times\n\nYou can index the same field multiple times with different types or aliases by using an array:\n\n```javascript\nawait client.ft.create('idx:products', {\n  sku: [\n    { type: SCHEMA_FIELD_TYPE.TEXT, AS: 'sku_text' },\n    { type: SCHEMA_FIELD_TYPE.TAG, AS: 'sku_tag', SORTABLE: true }\n  ]\n}, {\n  ON: 'HASH',\n  PREFIX: 'product:'\n});\n```\n\nThis allows querying the same field using different search strategies.\n\n#### Querying the Index\n\nOnce we've created an index, and added some data to Redis hashes whose keys begin with the prefix `noderedis:animals`, we can start writing some search queries.  RediSearch supports a rich query syntax for full-text search, faceted search, aggregation and more.  Check out the [`FT.SEARCH` documentation](https://redis.io/commands/ft.search) and the [query syntax reference](https://redis.io/docs/interact/search-and-query/query) for more information.\n\nLet's write a query to find all the animals where the `species` field has the value `dog`:\n\n```javascript\nconst results = await client.ft.search('idx:animals', '@species:{dog}');\n```\n\n`results` looks like this:\n\n```javascript\n{\n  total: 2,\n  documents: [\n    {\n      id: 'noderedis:animals:4',\n      value: {\n        name: 'Fido',\n        species: 'dog',\n        age: '7'\n      }\n    },\n    {\n      id: 'noderedis:animals:3',\n      value: {\n        name: 'Rover',\n        species: 'dog',\n        age: '9'\n      }\n    }\n  ]\n}\n```\n\n### Indexing and Querying Data with RedisJSON\n\nRediSearch can also index and query JSON documents stored in Redis using the RedisJSON module.  The approach is similar to that for indexing and searching data in hashes, but we can now use JSON Path like syntax and the data no longer has to be flat name/value pairs - it can contain nested objects and arrays.\n\n#### Creating an Index\n\nAs before, we create an index with the `FT.CREATE` command, this time specifying we want to index JSON documents that look like this:\n\n```javascript\n{\n  name: 'Alice',\n  age: 32,\n  coins: 100\n}\n```\n\nEach document represents a user in some system, and users have name, age and coins properties.\n\nOne way we might choose to index these documents is as follows:\n\n```javascript\nawait client.ft.create('idx:users', {\n  '$.name': {\n    type: SCHEMA_FIELD_TYPE.TEXT,\n    SORTABLE: 'UNF'\n  },\n  '$.age': {\n    type: SCHEMA_FIELD_TYPE.NUMERIC,\n    AS: 'age'\n  },\n  '$.coins': {\n    type: SCHEMA_FIELD_TYPE.NUMERIC,\n    AS: 'coins'\n  }\n}, {\n  ON: 'JSON',\n  PREFIX: 'noderedis:users'\n});\n```\n\nNote that we're using JSON Path to specify where the fields to index are in our JSON documents, and the `AS` clause to define a name/alias for each field.  We'll use these when writing queries.\n\n#### Querying the Index\n\nNow we have an index and some data stored as JSON documents in Redis (see the [JSON package documentation](https://github.com/redis/node-redis/tree/master/packages/json) for examples of how to store JSON), we can write some queries...\n\nWe'll use the [RediSearch query language](https://redis.io/docs/interact/search-and-query/query) and [`FT.SEARCH`](https://redis.io/commands/ft.search) command.  Here's a query to find users under the age of 30:\n\n```javascript\nawait client.ft.search('idx:users', '@age:[0 30]');\n```\n","packages/time-series/README.md":"# @redis/time-series\n\nThis package provides support for the [RedisTimeSeries](https://redis.io/docs/data-types/timeseries/) module, which adds a time series data structure to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisTimeSeries module installed.\n\n## Usage\n\nFor a complete example, see [`time-series.js`](https://github.com/redis/node-redis/blob/master/examples/time-series.js) in the Node Redis examples folder.\n\n### Creating Time Series data structure in Redis\n\nThe [`TS.CREATE`](https://oss.redis.com/redistimeseries/commands/#tscreate) command creates a new time series.\n\nHere, we'll create a new time series \"`temperature`\":\n\n```javascript\n\nimport { createClient } from 'redis';\nimport { TimeSeriesDuplicatePolicies, TimeSeriesEncoding, TIME_SERIES_AGGREGATION_TYPE } from '@redis/time-series';\n\n...\nconst created = await client.ts.create('temperature', {\n  RETENTION: 86400000, // 1 day in milliseconds\n  ENCODING: TimeSeriesEncoding.UNCOMPRESSED, // No compression - When not specified, the option is set to COMPRESSED\n  DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.BLOCK, // No duplicates - When not specified: set to the global DUPLICATE_POLICY configuration of the database (which by default, is BLOCK).\n});\n\nif (created === 'OK') {\n  console.log('Created timeseries.');\n} else {\n  console.log('Error creating timeseries :(');\n  process.exit(1);\n}\n```\n\n### Adding new value to a Time Series data structure in Redis\n\nWith RedisTimeSeries, we can add a single value to time series data structure using the [`TS.ADD`](https://redis.io/commands/ts.add/) command and if we would like to add multiple values we can use the [`TS.MADD`](https://redis.io/commands/ts.madd/) command.\n\n```javascript\n\nlet value = Math.floor(Math.random() * 1000) + 1; // Random data point value\nlet currentTimestamp = 1640995200000; // Jan 1 2022 00:00:00\nlet num = 0;\n\nwhile (num < 10000) {\n  // Add a new value to the timeseries, providing our own timestamp:\n  // https://redis.io/commands/ts.add/\n  await client.ts.add('temperature', currentTimestamp, value);\n  console.log(`Added timestamp ${currentTimestamp}, value ${value}.`);\n\n  num += 1;\n  value = Math.floor(Math.random() * 1000) + 1; // Get another random value\n  currentTimestamp += 1000; // Move on one second.\n}\n\n// Add multiple values to the timeseries in round trip to the server:\n// https://redis.io/commands/ts.madd/\nconst response = await client.ts.mAdd([{\n  key: 'temperature',\n  timestamp: currentTimestamp + 60000,\n  value: Math.floor(Math.random() * 1000) + 1\n}, {\n  key: 'temperature',\n  timestamp: currentTimestamp + 120000,\n  value: Math.floor(Math.random() * 1000) + 1\n}]);\n```\n\n### Retrieving Time Series data from Redis\n\nWith RedisTimeSeries, we can retrieve the time series data using the [`TS.RANGE`](https://redis.io/commands/ts.range/) command by passing the criteria as follows:\n\n```javascript\n// Query the timeseries with TS.RANGE:\n// https://redis.io/commands/ts.range/\nconst fromTimestamp = 1640995200000; // Jan 1 2022 00:00:00\nconst toTimestamp = 1640995260000; // Jan 1 2022 00:01:00\nconst rangeResponse = await client.ts.range('temperature', fromTimestamp, toTimestamp, {\n  // Group into 10 second averages.\n  AGGREGATION: {\n    type: TIME_SERIES_AGGREGATION_TYPE.AVG,\n    timeBucket: 10000\n  }\n});\n\nconsole.log('RANGE RESPONSE:');\n// rangeResponse looks like:\n// [\n//   { timestamp: 1640995200000, value: 356.8 },\n//   { timestamp: 1640995210000, value: 534.8 },\n//   { timestamp: 1640995220000, value: 481.3 },\n//   { timestamp: 1640995230000, value: 437 },\n//   { timestamp: 1640995240000, value: 507.3 },\n//   { timestamp: 1640995250000, value: 581.2 },\n//   { timestamp: 1640995260000, value: 600 }\n// ]\n```\n\nFor multiple aggregations in one command, use the dedicated `*MultiAggr` methods:\n\n```javascript\nconst multiRangeResponse = await client.ts.rangeMultiAggr('temperature', fromTimestamp, toTimestamp, {\n  AGGREGATION: {\n    types: [\n      TIME_SERIES_AGGREGATION_TYPE.MIN,\n      TIME_SERIES_AGGREGATION_TYPE.MAX,\n      TIME_SERIES_AGGREGATION_TYPE.AVG\n    ],\n    timeBucket: 10000\n  }\n});\n\n// multiRangeResponse looks like:\n// [\n//   { timestamp: 1640995200000, values: [120, 580, 356.8] },\n//   ...\n// ]\n```\n\nEquivalent multi-aggregation helpers are also available for reverse and multi-key variants:\n`revRangeMultiAggr`, `mRangeMultiAggr`, `mRevRangeMultiAggr`, `mRangeWithLabelsMultiAggr`,\n`mRevRangeWithLabelsMultiAggr`, `mRangeSelectedLabelsMultiAggr`, and `mRevRangeSelectedLabelsMultiAggr`.\n\n### Altering Time Series data Stored in Redis\n\nRedisTimeSeries includes commands that can update values in a time series data structure.\n\nUsing the [`TS.ALTER`](https://redis.io/commands/ts.alter/) command, we can update time series retention like this:\n\n```javascript\n// https://redis.io/commands/ts.alter/\nconst alterResponse = await client.ts.alter('temperature', {\n  RETENTION: 0 // Keep the entries forever\n});\n```\n\n### Retrieving Information about the timeseries Stored in Redis\n\nRedisTimeSeries also includes commands that can help to view the information on the state of a time series.\n\nUsing the [`TS.INFO`](https://redis.io/commands/ts.info/) command, we can view timeseries information like this:\n\n```javascript\n// Get some information about the state of the timeseries.\n// https://redis.io/commands/ts.info/\nconst tsInfo = await client.ts.info('temperature');\n\n// tsInfo looks like this:\n// {\n//   totalSamples: 1440,\n//   memoryUsage: 28904,\n//   firstTimestamp: 1641508920000,\n//   lastTimestamp: 1641595320000,\n//   retentionTime: 86400000,\n//   chunkCount: 7,\n//   chunkSize: 4096,\n//   chunkType: 'uncompressed',\n//   duplicatePolicy: 'block',\n//   labels: [],\n//   sourceKey: null,\n//   rules: []\n// }\n```\n"},"files":{".agents/skills/bump-test-image/SKILL.md":"---\nname: bump-test-image\ndescription: Bump the default Redis docker test image (redislabs/client-libs-test) in the shared DEFAULT_DOCKER_CONFIG and the CI matrix, then force-push the bump-test-image branch and open a PR against upstream. User-invoked only.\ndisable-model-invocation: true\nargument-hint: <image-tag> [redis-version]\n---\n\n# Bump default docker test image\n\nUpdates the default `redislabs/client-libs-test` docker tag used by tests (a single shared config in `@redis/test-utils`), adjusts the CI matrix, commits on a freshly-reset `bump-test-image` branch, force-pushes it, and opens (or updates) a PR against `redis/node-redis` master.\n\n## Inputs\n\n- **tag** (required): the new docker tag, e.g. `8.8.0`, `8.8-rc1`, `custom-28772936538-debian`, `unstable-24425681442-debian`. If given as `redislabs/client-libs-test:<tag>`, strip the image name.\n- **version** (sometimes required): the Redis version the tag represents, e.g. `8.10`.\n  - Derivable when the tag is version-shaped: `8.8.0` → `8.8`, `8.8-rc1` → `8.8`, `8.8-m03` → `8.8`.\n  - NOT derivable for `custom-*` / `unstable-*` tags — if missing, ask the user which Redis version the tag maps to before doing anything else.\n\nIf no tag was given at all, ask for it and stop.\n\n## Step 1 — Sync master from upstream\n\nRemotes: `upstream` = `redis/node-redis` (canonical), `origin` = the user's fork.\n\n```bash\ngit fetch upstream\ngit rev-parse --abbrev-ref HEAD   # remember current branch\n```\n\nUpdate local master to upstream:\n\n- If currently on master: `git reset --hard upstream/master` (only if working tree is clean — if dirty, stop and tell the user).\n- Otherwise: `git fetch upstream master:master` (fails safely if master has local-only commits; if it fails, stop and report).\n\n## Step 2 — Reset the work branch\n\n```bash\ngit checkout -B bump-test-image master\n```\n\n`-B` creates or hard-resets the branch to latest master. Any previous bump commits on it are intentionally discarded (the branch is recycled for each bump; force-push later matches this).\n\n## Step 3 — Edit the shared default config\n\nThe default image config lives in exactly ONE place:\n\n`packages/test-utils/lib/index.ts` → `DEFAULT_DOCKER_CONFIG`:\n\n```typescript\nexport const DEFAULT_DOCKER_CONFIG: TestUtilsConfig = {\n  dockerImageName: 'redislabs/client-libs-test',\n  dockerImageTagArgument: 'redis-tag',\n  dockerImageVersionArgument: 'redis-version',\n  defaultDockerVersion: { tag: '<old-tag>', version: '<old-version>' }\n};\n```\n\nNote the old tag/version, then replace them with the new values on the `defaultDockerVersion` line. Nothing else in the file changes.\n\nIf `DEFAULT_DOCKER_CONFIG` does not exist in that file (e.g. master predates the dedupe refactor), STOP and tell the user — do not fall back to editing per-package test-utils files without confirmation.\n\n## Step 4 — Update the CI matrix\n\nFile: `.github/workflows/tests.yml`, `matrix` → `redis` entries of the form:\n\n```yaml\n          - tag: \"8.8.0\"\n            version: \"8.8\"\n```\n\nTwo cases, decided by comparing the new `version` with the old default version from Step 3:\n\n1. **New Redis version** (e.g. default moves 8.8 → 8.10): **add** a new `- tag/version` entry at the end of the matrix list, keeping the previous entry so CI retains coverage of the prior version.\n2. **Same Redis version** (rc → GA, or a newer custom build of the same version): **replace** the `tag` of the existing entry for that version; the `version` line stays.\n\n## Step 5 — Verify\n\n```bash\ngrep -rn \"defaultDockerVersion:\" packages --include=\"*.ts\" | grep -v dist | grep -v node_modules\n```\n\nExactly one config line (in `packages/test-utils/lib/index.ts`) must show the new tag/version; any other hits are the type definition / JSDoc examples, not real defaults. Also confirm `git diff --stat` touches exactly 2 files: `packages/test-utils/lib/index.ts` + `.github/workflows/tests.yml`.\n\n## Step 6 — Commit\n\nFill in this template exactly (Conventional Commits):\n\n```\nchore(tests): bump default docker test image to <X>\n\nBump the default Redis test image from <old-tag> to <new-tag> (Redis\n<version>) in DEFAULT_DOCKER_CONFIG, and <matrix-action> the CI matrix.\n```\n\n- `<X>`: the human-meaningful version when the tag is version-shaped (`8.10`, `8.8.0`, `8.8-rc1`, `8.8-m03`); the full tag for `unstable-*` builds; the version for `custom-*` tags (the actual tag already appears in the body as `<new-tag>`).\n- `<matrix-action>`: either `add an <version> entry to` (keeping prior-version coverage) or `update the <version> entry in`, matching what Step 4 did.\n\nExample:\n\n```\nchore(tests): bump default docker test image to 8.10\n\nBump the default Redis test image from 8.8.0 to custom-28772936538-debian\n(Redis 8.10) in DEFAULT_DOCKER_CONFIG, and add an 8.10 entry to the CI\nmatrix.\n```\n\nKeep any company-internal references (Jira/Confluence IDs, internal links) out of the commit and PR.\n\n## Step 7 — Force-push and PR\n\n```bash\ngit push -f origin bump-test-image\n```\n\nThen check for an existing open PR before creating one:\n\n```bash\ngh pr list --repo redis/node-redis --head <fork-owner>:bump-test-image --state open\n```\n\n(`<fork-owner>` = GitHub owner from the `origin` remote URL.)\n\n- If a PR exists, the force-push already updated it — just report the URL. Update its title/body via `gh pr edit` to match the new tag.\n- Otherwise create one:\n\n```bash\ngh pr create --repo redis/node-redis --base master --head <fork-owner>:bump-test-image \\\n  --title \"<commit subject>\" \\\n  --body \"<commit body>\"\n```\n\nFinish by reporting: old → new tag/version, matrix action taken (added vs replaced entry), and the PR URL.\n",".agents/skills/docs-sync/SKILL.md":"---\nname: docs-sync\ndescription: Analyze master branch implementation and configuration to find missing, incorrect, or outdated documentation in docs/, README.md, and per-package READMEs. Use when asked to audit doc coverage, sync docs with code, or propose doc updates/structure changes. Provide a report and ask for approval before editing docs.\n---\n\n# Docs Sync\n\n## Overview\n\nIdentify doc coverage gaps and inaccuracies by comparing master branch features and configuration options against the current docs, then propose targeted improvements.\n\n## Workflow\n\n1. Confirm scope and base branch\n   - Identify the current branch and default branch (`master`).\n   - Prefer analyzing the current branch to keep work aligned with in-flight changes.\n   - If the current branch is not `master`, analyze only the diff vs `master` to scope doc updates.\n   - Avoid switching branches if it would disrupt local changes; use `git show master:<path>` or `git worktree add` when needed.\n\n2. Build a feature inventory from the selected scope\n   - If on `master`: inventory the full surface area and review docs comprehensively.\n   - If not on `master`: inventory only changes vs `master` (feature additions/changes/removals).\n   - Focus on user-facing behavior: public exports, client/cluster/sentinel/pool config options, new or changed commands, connection-string options, default values, and documented runtime behaviors.\n   - Capture evidence for each item (file path + symbol/setting).\n   - Use targeted search to find option types and feature flags (for example: `rg \"Options\"`, `rg \"interface .*Options\"`, `rg \"export\"` under `packages/*/lib`).\n   - For Redis command behavior or server-side semantics, treat the source code (`parseCommand`/`transformReply` in `packages/*/lib/commands`) as the source of truth; consult [redis.io/commands](https://redis.io/commands) only to confirm server semantics when discrepancies appear.\n\n3. Doc-first pass: review existing pages\n   - Walk each relevant page under `docs/`, the top-level `README.md`, and each `packages/*/README.md`.\n   - Identify missing mentions of important, supported options (opt-in flags, config), customization points, or new features from `packages/`.\n   - Propose additions where users would reasonably expect to find them on that page.\n\n4. Code-first pass: map features to docs\n   - Review the current docs layout under `docs/` (e.g. `client-configuration.md`, `clustering.md`, `sentinel.md`, `pool.md`, `RESP.md`, `transactions.md`, `programmability.md`, `pub-sub.md`, `scan-iterators.md`, `command-options.md`).\n   - Determine the best page/section for each feature based on existing patterns and package boundaries.\n   - Identify features that lack any doc page or have a page but no corresponding content.\n   - Note when a structural adjustment would improve discoverability.\n\n5. Detect gaps and inaccuracies\n   - **Missing**: features/configs present in master but absent in docs.\n   - **Incorrect/outdated**: names, defaults, or behaviors that diverge from master.\n   - **Structural issues** (optional): pages overloaded, missing overviews, or mis-grouped topics.\n\n6. Produce a Docs Sync Report and ask for approval\n   - Provide a clear report with evidence, suggested doc locations, and proposed edits.\n   - Ask the user whether to proceed with doc updates.\n\n7. If approved, apply changes\n   - Edit docs under `docs/`, `README.md`, and the relevant `packages/*/README.md`.\n   - Keep changes aligned with the existing docs style and navigation.\n   - Place any runnable code snippets under `examples/` or `doctests/`, mirroring existing patterns.\n   - Verify any snippet you add still compiles with `npm run build` and fix issues before handoff.\n\n## Output format\n\nUse this template when reporting findings:\n\nDocs Sync Report\n\n- Doc-first findings\n  - Page + missing content → evidence + suggested insertion point\n- Code-first gaps\n  - Feature + evidence → suggested doc page/section (or missing page)\n- Incorrect or outdated docs\n  - Doc file + issue + correct info + evidence\n- Structural suggestions (optional)\n  - Proposed change + rationale\n- Proposed edits\n  - Doc file → concise change summary\n- Questions for the user\n\n## References\n\n- `references/doc-coverage-checklist.md`\n",".agents/skills/docs-sync/agents/openai.yaml":"interface:\n  display_name: \"Docs Sync\"\n  short_description: \"Audit docs coverage and propose targeted updates\"\n  default_prompt: \"Use $docs-sync to audit the current branch against docs/ and propose targeted documentation updates.\"\n",".agents/skills/docs-sync/references/doc-coverage-checklist.md":"# Doc Coverage Checklist\n\nUse this checklist to scan the selected scope (master = comprehensive, or current-branch diff) and validate documentation coverage.\n\n## Feature inventory targets\n\n- Public exports: clients (`RedisClient`, `RedisCluster`, `RedisSentinel`), pool, factory functions, types, and module entry points.\n- Configuration options: `*Options` types, default config objects (`defaults.ts`), and connection-string options.\n- New or changed commands under `packages/*/lib/commands` (names, args, reply shape, RESP2/RESP3 differences).\n- User-facing behaviors: reconnection, timeouts, pub/sub, pipelining/transactions, scan iterators, errors, caching, telemetry, and data handling.\n- Deprecations, removals, or renamed settings (track against migration guides `docs/v*-to-v*.md`).\n\n## Doc-first pass (page-by-page)\n\n- Review each relevant page under `docs/`, the top-level `README.md`, and each `packages/*/README.md`.\n- Look for missing opt-in flags or customization options that the page implies.\n- Add new features that belong on that page based on user intent and navigation.\n\n## Code-first pass (feature inventory)\n\n- Map features to the closest existing page based on package or feature area.\n- Prefer updating existing pages over creating new ones unless the topic is clearly new.\n- Use conceptual pages for cross-cutting concerns (auth, errors, RESP, clustering, pub/sub).\n- Keep quick-start flows minimal; move advanced details into deeper pages.\n\n## Evidence capture\n\n- Record the master-branch file path and symbol/setting name.\n- Note defaults or behavior-critical details for accuracy checks.\n- Avoid large code dumps; a short identifier is enough.\n\n## Red flags for outdated or incorrect docs\n\n- Option names/types no longer exist or differ from code.\n- Default values or allowed ranges do not match implementation.\n- Features removed in code but still documented.\n- New behaviors introduced without corresponding docs updates.\n\n## When to propose structural changes\n\n- A page mixes unrelated audiences (quick-start + deep reference) without clear separation.\n- Multiple pages duplicate the same concept without cross-links.\n- New feature areas have no obvious home in the docs structure.\n\n## Diff mode guidance (current branch vs master)\n\n- Focus only on changed behavior: new exports/options, modified defaults, removed features, or renamed settings.\n- Use `git diff master...HEAD` (or equivalent) to constrain analysis.\n- Document removals explicitly so docs can be pruned if needed.\n\n## Patch guidance\n\n- Keep edits scoped and aligned to existing tone and format.\n- Update cross-links when moving or renaming sections.\n- Place any runnable snippets under `examples/` or `doctests/`.\n",".agents/skills/implement-command/SKILL.md":"---\nname: implement-command\ndescription: Add a new Redis command (or command variant) to node-redis end-to-end — the `<NAME>.ts` Command file, its registration with JSDoc in the package `commands/index.ts`, and a co-located `<NAME>.spec.ts` with arg + behavior tests. Use when asked to implement, add, or wire up a Redis command in the client or a module package (json/search/bloom/time-series).\n---\n\n# Implement a node-redis Command\n\n## Overview\n\nA command in node-redis is a single `Command` object exported from\n`packages/<pkg>/lib/commands/<NAME>.ts`. It declares how to serialize arguments\nonto the wire (`parseCommand`) and how to map the RESP reply to a JS value\n(`transformReply`). It becomes callable on clients only after it is registered\nin the package's `commands/index.ts`. Both the raw name and a camelCase alias\nare exposed (`HSET` and `hSet`).\n\nThis skill is for `@redis/client` core commands and for module packages\n(`@redis/json`, `@redis/search`, `@redis/bloom`, `@redis/time-series`). It does\nnot cover RESP codec changes or new client transports.\n\nBefore writing, **read 2-3 existing commands with a similar shape** (simple\nkey read, key + options, variadic, RESP2/3-divergent reply) and mirror them.\nConsult [redis.io/commands](https://redis.io/commands) for argument order and\nreply type, but treat existing `parseCommand`/`transformReply` files as the\nsource of truth for repo conventions.\n\n## File layout\n\n| Package | Command dir | Import paths | Wire name prefix |\n| --- | --- | --- | --- |\n| `client` | `packages/client/lib/commands/` | `../client/parser`, `../RESP/types`, `./generic-transformers` | none (`GET`) |\n| `json` | `packages/json/lib/commands/` | `@redis/client/dist/lib/...` | `JSON.` (`JSON.ARRAPPEND`) |\n| `search` | `packages/search/lib/commands/` | `@redis/client/dist/lib/...` | `FT.` |\n| `bloom` | `packages/bloom/lib/commands/<family>/` | `@redis/client/dist/lib/...` | e.g. `BF.`, `CF.`, `TOPK.` |\n| `time-series` | `packages/time-series/lib/commands/` | `@redis/client/dist/lib/...` | `TS.` |\n\n**Naming**: file name = raw wire name with subcommand `_` separators\n(`ACL_CAT`, `CONFIG_GET`, `CLUSTER_FORGET`). A distinct reply variant gets its\nown file (`HRANDFIELD_COUNT_WITHVALUES`). Module commands drop the dotted\nprefix from the file name (`ARRAPPEND.ts` → wire `JSON.ARRAPPEND`).\n\n## Step 0 — Gather inputs (ask the user first)\n\nNew commands are often implemented **before they are publicly released**, so\nredis.io may not document them yet and a default `redis:latest` may not have\nthem. Before writing any code, ask the user for three things:\n\n1. **The command spec.** Ask for the redis/redis JSON spec file — one per\n   command under\n   [`src/commands/<name>.json`](https://github.com/redis/redis/tree/unstable/src/commands)\n   (subcommands use `-`, e.g. `client-info.json`, `acl-cat.json`). If the\n   command is unreleased, ask the user to paste the spec from their branch.\n   As a fallback on a live server: `redis-cli --json COMMAND DOCS <name>` and\n   `COMMAND INFO <name>`.\n2. **A running Redis instance that has the command.** This is the single most\n   useful input — ask for it explicitly and up front. Ask for connection\n   details (host/port, TLS, auth, module loaded). Use it to explore real\n   behavior and confirm the implementation matches the spec — **do not rely on\n   the spec alone**. Probe every argument branch and diff the real reply against\n   your `transformReply`. A quick `redis-cli` session or a throwaway probe\n   script (`packages/client` is already wired for `tsx`) is enough; never commit\n   the probe.\n3. **The first server version that ships the command.** Ask which Redis (or\n   module) version introduced it — the spec's `since` field is the answer when\n   present; otherwise ask the user directly. You need this to (a) write `@since`\n   in the JSDoc (Step 2) and (b) gate the behavior tests with\n   `minimumDockerVersion` (Step 3) so they don't run — and fail — on older\n   servers in CI.\n\nIf the user cannot provide a spec, derive arguments/reply from\n[redis.io/commands](https://redis.io/commands) but flag that it is unverified.\nIf they cannot provide a live instance, implement from the spec but state that\nruntime behavior was not confirmed. If the introducing version is unknown, say\nso and leave `@since`/`minimumDockerVersion` out rather than guessing.\n\n### Reading the spec JSON → mapping to a Command\n\nThe redis/redis spec drives every part of the `Command` object. Example\n(`getex.json`, trimmed):\n\n```json\n{\n  \"GETEX\": {\n    \"since\": \"6.2.0\", \"arity\": -2,\n    \"command_flags\": [\"WRITE\", \"FAST\"],\n    \"key_specs\": [{ \"begin_search\": { \"index\": { \"pos\": 1 } }, \"flags\": [\"RW\", \"UPDATE\"] }],\n    \"arguments\": [\n      { \"name\": \"key\", \"type\": \"key\", \"key_spec_index\": 0 },\n      { \"name\": \"expiration\", \"type\": \"oneof\", \"optional\": true, \"arguments\": [\n        { \"name\": \"seconds\", \"type\": \"integer\", \"token\": \"EX\" },\n        { \"name\": \"persist\", \"type\": \"pure-token\", \"token\": \"PERSIST\" }\n      ]}\n    ],\n    \"reply_schema\": { \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"null\" } ] }\n  }\n}\n```\n\n| Spec field | Drives |\n| --- | --- |\n| `command_flags` contains `READONLY` (and **not** `WRITE`) | `IS_READ_ONLY: true`. `WRITE` → omit it. Pure read with no side effects → also `CACHEABLE: true`. For keyed commands the generated metadata table already implies these defaults, so module packages omit the flags; keyless reads must set `IS_READ_ONLY: true` explicitly (Step 4). |\n| `key_specs` empty / no `key`-type args | `NOT_KEYED_COMMAND: true`. |\n| `arguments[].type: \"key\"` | `parser.pushKey(...)` (one per key, in spec order). |\n| `type: \"pure-token\"` + `token` | a literal flag pushed only when its option is set (`parser.push('PERSIST')`). |\n| `token` + value type (`integer`/`string`/...) | push the token then the stringified value (`parser.push('EX', seconds.toString())`). |\n| `type: \"oneof\"` | mutually exclusive branch → `if/else if` in `parseCommand`; model as a union/`options` field. |\n| `optional: true` | goes in the `options` object (exported `interface`); guard with `if (options?.x)`. |\n| `multiple: true` | variadic → `parser.pushVariadic*`. |\n| `arity` | sanity-check arg count in `parseArgs` tests. |\n| `reply_schema` (JSON Schema) | the `transformReply` return type. `oneOf [string, null]` → `BlobStringReply \\| NullReply`; `integer` → `NumberReply`; `array` → `ArrayReply<...>`; a map differing by RESP version → keyed `transformReply: { 2, 3 }`. |\n| `since` | the introducing server version → `@since` in the registry JSDoc (Step 2) **and** `minimumDockerVersion: [major, minor]` on the behavior tests (Step 3). |\n\nAfter implementing, run the command against the live instance and diff the real\nreply against `reply_schema` and your `transformReply` output.\n\n## Step 1 — Write `<NAME>.ts`\n\nMinimal pass-through command (`packages/client/lib/commands/GET.ts`):\n\n```typescript\nimport { CommandParser } from '../client/parser';\nimport { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types';\n\nexport default {\n  CACHEABLE: true,\n  IS_READ_ONLY: true,\n  parseCommand(parser: CommandParser, key: RedisArgument) {\n    parser.push('GET');\n    parser.pushKey(key);\n  },\n  transformReply: undefined as unknown as () => BlobStringReply | NullReply\n} as const satisfies Command;\n```\n\n`as const satisfies Command` is mandatory — it preserves the literal arg types\nfor the public API while type-checking the shape.\n\n### Command flags (all optional)\n\n- `IS_READ_ONLY: true` — read command; routable to replicas. Set for reads, omit/`false` for writes.\n- `CACHEABLE: true` — eligible for client-side caching. Only for pure reads with no side effects.\n- `NOT_KEYED_COMMAND: true` — command takes no key (server/connection level, e.g. `PING`, `CONFIG_GET`).\n- `IS_FORWARD_COMMAND` — internal; do not set on new commands.\n\n### `parseCommand` — serialize args via `CommandParser`\n\nFirst arg is always `parser`. Push the wire name first, then args in order.\nUse the parser helpers — **do not** hand-build arrays:\n\n- `push(...args)` — raw args (the command token, flags, stringified numbers).\n- `pushKey(key)` — a key. Registers it for cluster slot routing. Use for **every** key, never `push` a key.\n- `pushKeys(keys)` / `pushKeysLength(keys)` — multiple keys; the `Length` variant prefixes the count.\n- `pushVariadic(vals)` — a `RedisVariadicArgument` (one value or array) as flat args.\n- `pushVariadicWithLength(vals)` — same, prefixed with the count (e.g. `FIELDS <n> ...`).\n- `pushVariadicNumber(vals)` — number or array of numbers, stringified.\n\nNumbers are not auto-stringified by `push` — call `.toString()`. Optional\ntrailing args go in an `options` object; export its `interface` (see\n`SET.ts`'s `SetOptions`). Encode keyword flags conditionally:\n\n```typescript\nparseCommand(parser: CommandParser, key: RedisArgument, value: RedisArgument, options?: SetOptions) {\n  parser.push('SET');\n  parser.pushKey(key);\n  parser.push(value);\n  if (options?.condition) parser.push(options.condition); // 'NX' | 'XX'\n}\n```\n\n### `transformReply` — map RESP reply to JS\n\n- **Pass-through** (reply already the right shape): `transformReply: undefined as unknown as () => <ReplyType>`.\n- **Function**: `(reply: <RawType>) => <JsType>`. Use `UnwrapReply<...>` to read the raw RESP container.\n- **RESP-version keyed**: `{ 2: (reply) => ..., 3: (reply) => ... }` when RESP2 and RESP3 shapes differ (e.g. flat array vs map/tuple). See `HRANDFIELD_COUNT_WITHVALUES.ts`.\n\n#### Unify RESP2 onto the RESP3 shape\n\nWhen the server returns different shapes per protocol, the library exposes **one\nreturn type to callers**: the **RESP3 shape is the source of truth**, and the\nRESP2 reply is transformed to look like it. So the keyed form is almost always:\n\n- `3:` — **pass-through** (`undefined as unknown as () => <ReplyType>`), because\n  RESP3 already has the target shape (map, tuple, big-number, double, ...).\n- `2:` — a **function** that reshapes the flat/legacy RESP2 reply into that same\n  `<ReplyType>`. Type its input `UnwrapReply<Resp2Reply<ReplyType>>` so the raw\n  RESP2 container is visible while the output type still matches RESP3.\n\nCanonical example — `HELLO.ts` turns the RESP2 flat array (`[k, v, k, v, ...]`)\ninto the RESP3 map, while RESP3 passes through:\n\n```typescript\ntransformReply: {\n  2: (reply: UnwrapReply<Resp2Reply<HelloReply>>) => ({\n    server: reply[1], version: reply[3], proto: reply[5], /* ... */\n  }),\n  3: undefined as unknown as () => HelloReply\n}\n```\n\nReuse shared transformers where one exists (`HGETALL.ts` uses\n`transformTuplesReply` for `2:`, map pass-through for `3:`). Only when RESP3\nstill needs reshaping does `3:` get its own function too. Verify the actual\nper-protocol shapes against the live instance (Step 0) — connect once with\n`RESP: 2` and once with `RESP: 3` and diff.\n\nReply types live in `RESP/types`: `BlobStringReply`, `SimpleStringReply<'OK'>`,\n`NumberReply`, `DoubleReply`, `NullReply`, `BooleanReply`, `ArrayReply<T>`,\n`TuplesReply<[...]>`, `MapReply`, `UnwrapReply`.\n\n**RESP3 is the default.** No separate RESP3 test is needed for a new command;\nthe default test setup already exercises RESP3.\n\n### Type-mapping precision caveats\n\n- A `BLOB_STRING` reply cannot be remapped to `Number` via type mapping; only RESP3 `DOUBLE`/`BIG_NUMBER` are precision-risky.\n- If a `NumberReply` can exceed `Number.MAX_SAFE_INTEGER` (2^53-1), add a `@remarks` line to the JSDoc (Step 2) telling users to do `client.withTypeMapping({ [RESP_TYPES.NUMBER]: String })`. See the `ARGREP` entries in the client index for the exact wording.\n\n### Module package commands\n\nImport from the published client subpath, prefix the wire name, and reuse\nshared transformers (`packages/json/lib/commands/ARRAPPEND.ts`):\n\n```typescript\nimport { CommandParser } from '@redis/client/dist/lib/client/parser';\nimport { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';\n\nexport default {\n  IS_READ_ONLY: false,\n  parseCommand(parser, key, path, value) {\n    parser.push('JSON.ARRAPPEND');\n    parser.pushKey(key);\n    parser.push(path, /* transform */ value);\n  },\n  transformReply: undefined as unknown as () => NumberReply\n} as const satisfies Command;\n```\n\n## Step 2 — Register in `commands/index.ts`\n\n`import` the command, then add it to the default-export map **twice**: the raw\nname (shorthand) and a camelCase alias. **Every entry MUST have a JSDoc block\ndirectly above it** — `npm run check:command-jsdoc` fails on any registry entry\nwithout an attached JSDoc comment (no blank-line gap allowed).\n\n```typescript\nimport GET from './GET';\n// ...\nexport default {\n  /**\n   * Returns the value of a key, or null if the key does not exist\n   * @param key - Key to read\n   * @since 1.0.0\n   */\n  GET,\n  /**\n   * Returns the value of a key, or null if the key does not exist\n   * @param key - Key to read\n   * @since 1.0.0\n   */\n  get: GET,\n} satisfies RedisCommands;\n```\n\nKeep both JSDoc blocks (raw + alias) in sync. Document every `parseCommand`\nparam after `parser` with `@param`. Add `@since <version>` with the introducing\nserver version from Step 0 (the spec's `since`); omit it only if that version is\nunknown. Add `@remarks` for the precision caveat above when relevant. For module\npackages the registry files are\n`packages/<pkg>/lib/commands/index.ts` (bloom: per-family `.../<family>/index.ts`).\n\n## Step 3 — Write `<NAME>.spec.ts` (co-located)\n\nTwo layers: arg serialization (no server) + behavior (real server, server +\ncluster topologies). Mirror `GET.spec.ts`:\n\n```typescript\nimport { strict as assert } from 'node:assert';\nimport testUtils, { GLOBAL } from '../test-utils';\nimport { parseArgs } from './generic-transformers';\nimport GET from './GET';\n\ndescribe('GET', () => {\n  it('transformArguments', () => {\n    assert.deepEqual(parseArgs(GET, 'key'), ['GET', 'key']);\n  });\n\n  testUtils.testAll('get', async client => {\n    assert.equal(await client.get('key'), null);\n  }, {\n    client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 8] },\n    cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 8] }\n  });\n});\n```\n\n- `parseArgs(COMMAND, ...args)` asserts the exact wire array — cover each option/flag branch and variadic shapes. Arg tests need no server, so **never gate them** by version.\n- `testUtils.testAll(name, fn, { client, cluster })` runs the same body against a standalone server and a cluster. Use it so cluster key routing (`pushKey`) is exercised. Drop `cluster` only when the command is genuinely cluster-incompatible.\n- **Gate every behavior test by the introducing version** (Step 0). Spread `minimumDockerVersion: [major, minor]` into **both** the `client` and `cluster` options (as above). CI runs multiple server versions; without the gate the test runs on older servers that lack the command and fails. `[8, 8]` = \"8.8 and newer\". Apply the same to `testWithClient`/`testWithCluster` by spreading it into their single options object. Omit only if the version is genuinely unknown.\n- Pick the right `GLOBAL.SERVERS.*` / `GLOBAL.CLUSTERS.*` setup (see `test-utils.ts`); `OPEN` is the default.\n- **Docker is required** — test-utils starts real Redis containers.\n\n## Step 4 — Regenerate static command metadata\n\nCluster/sentinel routing (replica-safety, keyedness, CSC eligibility) is\nresolved from a generated table:\n`packages/client/lib/command-metadata/command-metadata-data.ts`. The file is\nauto-generated — **never edit it manually**. The table lives in\n`@redis/client`, but the `COMMAND` dump includes module commands (`ft`,\n`json`, `bf`, `ts`, ...), so a new command in **any** package needs a\nregenerate:\n\n```bash\nnpm run generate:metadata --workspace=packages/client -- redis://localhost:6379\n```\n\nThe script rebuilds the **entire** table from a single live server's `COMMAND`\nreply and overwrites the file — entries the server doesn't report are silently\ndropped. Run it only against a server with **all** bundled modules loaded and a\ncurrent core command set (e.g. the CI image `redislabs/client-libs-test` or a\nfull Redis 8.8+ build); reuse the Step 0 instance only if it meets that bar — a\nserver with just the new command's module would wipe every other module's\nmetadata. The script then applies the curation in\n`packages/client/scripts/command-metadata-overrides.ts`: hand-curated excludes\n(internal, deprecated and cluster-admin commands) plus per-command routing\noverrides. After regenerating, verify the new command has an entry with the\nexpected flags, **and** check `git diff` on `command-metadata-data.ts`: it must\ncontain only the intended additions/changes. Deletions of other modules' or\ncore entries mean the source server was incomplete — revert and rerun against\na full build.\n\nHow the table and the command object interact (override-first — see\n`lib/command-metadata/predicates.ts`):\n\n- The table sets **defaults only**, for both replica routing (cluster and\n  sentinel) and client-side caching eligibility. A keyed entry without the\n  `write` flag is already replica-safe, so a keyed read command with a correct\n  table entry needs no `IS_READ_ONLY` on the command object — bloom and json\n  omit it everywhere.\n- `IS_READ_ONLY`/`CACHEABLE` on the command object **win over the table**. Set\n  them as deliberate corrections, not to restate the table. The main case is\n  keyless reads, which default to master routing: `PING`/`INFO` in the client,\n  and module reads whose args are not keys — search and time-series set\n  `IS_READ_ONLY: true` on exactly their keyless commands (`FT.SEARCH` takes an\n  index name, `TS.MGET`/`TS.MRANGE` take filters, ...).\n- Table-shape fixes (wrong, missing or excluded entries, routing policies)\n  belong in `command-metadata-overrides.ts`; value intent (`IS_READ_ONLY`,\n  `CACHEABLE`) belongs in the command definition, never in the overrides file.\n\n## Step 5 — Build, verify, lint\n\n```bash\nnpm run build                                   # tsc --build (project references)\nnpm run check:command-jsdoc                     # registry JSDoc gate\nnpm run test-single -- packages/<pkg>/lib/commands/<NAME>.spec.ts\nnpm run lint                                     # changed files\n```\n\nIf the build fails on stale `dist/` from project references:\n\n```bash\nfind packages -type d -name \"dist\" -exec rm -rf {} + && npm run build\n```\n\nFor module packages, build the client first (or whole repo) — they import from\n`@redis/client/dist`.\n\n## Completion checklist\n\n- [ ] Asked the user for spec, a live instance with the command, and the introducing server version (Step 0); probed real behavior against the live instance.\n- [ ] `<NAME>.ts` created with `parseCommand` + `transformReply`, `as const satisfies Command`.\n- [ ] Flags set correctly (`IS_READ_ONLY` for reads, `CACHEABLE` only for side-effect-free reads, `NOT_KEYED_COMMAND` if no key; omit flags the metadata table already implies — Step 4).\n- [ ] Every key uses `pushKey`/`pushKeys`; numbers stringified; options behind an exported `interface`.\n- [ ] RESP2/3 divergence handled via keyed `transformReply`: RESP3 is the target shape (usually `3:` pass-through), RESP2 transformed to match it; both shapes verified against the live instance.\n- [ ] Registered in `commands/index.ts`: import + raw entry + camelCase alias, **each with JSDoc** (`@param` per arg; `@since` for the introducing version; `@remarks` for >2^53 precision).\n- [ ] Static command metadata regenerated (`npm run generate:metadata`) against a server with **all** bundled modules; the new command's entry verified and the diff contains no dropped entries (Step 4).\n- [ ] `<NAME>.spec.ts`: `parseArgs` covers all branches; `testUtils.testAll` covers server + cluster; behavior tests gated with `minimumDockerVersion` on both `client` and `cluster`.\n- [ ] `npm run build`, `npm run check:command-jsdoc`, the spec, and `npm run lint` all pass.\n- [ ] Commit message uses Conventional Commits; no company-internal refs.\n```\n",".agents/skills/implement-command/agents/openai.yaml":"interface:\n  display_name: \"Implement Command\"\n  short_description: \"Add a new Redis command to node-redis end-to-end\"\n  default_prompt: \"Use $implement-command to add a new Redis command to node-redis: write the <NAME>.ts Command file (parseCommand + transformReply), register it with JSDoc in the package commands/index.ts (raw name + camelCase alias), and add a co-located <NAME>.spec.ts with parseArgs and testUtils.testAll tests, then build, run check:command-jsdoc, the spec, and lint.\"\n",".agents/skills/pr-draft-summary/SKILL.md":"---\nname: pr-draft-summary\ndescription: Create the required PR-ready summary block, branch suggestion, title, and draft description for node-redis. Must be used before the final response whenever the actual task diff includes runtime code, tests, examples, build/test configuration, or docs with behavior impact, regardless of perceived change size. Skip only when no eligible files changed, every change is repo-meta or docs-only without behavior impact, the task is conversation-only, or the user explicitly opts out.\n---\n\n# PR Draft Summary\n\n## Purpose\n\nProduce a PR-ready summary after eligible work is complete: a concise change summary plus a PR-ready title and draft description for node-redis.\n\n## Close-out Gate\n\n1. Inspect the actual task diff before sending the final response.\n2. Run this skill when the diff includes runtime code, tests, examples, build/test configuration, or docs with behavior impact. Do not use perceived change size to skip an eligible change.\n3. Run it after any required verification and before sending the \"work complete\" response.\n4. Skip only when no eligible files changed, every change is repo-meta or docs-only without behavior impact, the task is conversation-only, or the user explicitly opts out.\n\n## Inputs to Collect Automatically (do not ask the user)\n\n- Current branch: `git rev-parse --abbrev-ref HEAD`.\n- Working tree: `git status -sb`.\n- Untracked files: `git ls-files --others --exclude-standard` (use with `git status -sb`; `--stat` omits them).\n- Changed files: `git diff --name-only` (unstaged) and `git diff --name-only --cached` (staged); sizes via `git diff --stat` and `git diff --stat --cached`.\n- Pull request base reference (use `origin/master`; never use a feature branch's upstream as the PR base):\n  - `BASE_REF=origin/master`; if it does not exist locally, use `master`.\n  - `BASE_COMMIT=$(git merge-base \"$BASE_REF\" HEAD)`.\n- Committed branch diff: `git diff --name-only \"${BASE_COMMIT}..HEAD\"` and `git diff --stat \"${BASE_COMMIT}..HEAD\"`.\n- Commits ahead of the base fork point: `git log --oneline --no-merges ${BASE_COMMIT}..HEAD`.\n- Category signals for this repo: runtime (`packages/*/lib`, `examples/`, `doctests/`, `benchmark/`, `scripts/`), tests (co-located `packages/**/*.spec.ts`), docs (`docs/`, `README.md`, `packages/*/README.md`, `AGENTS.md`, `.github/`), build/test config (`package.json`, `package-lock.json`, `tsconfig*.json`, `eslint.config.mjs`, `.mocharc*`, `mocha-multi-reporter-config.json`, `.nycrc*`).\n\n## Workflow\n\n1. Run the commands above without asking the user; compute `BASE_REF`/`BASE_COMMIT` first so later commands reuse them. Compare against `origin/master` or `master`, not the current branch's upstream.\n2. Combine the committed branch diff with staged, unstaged, and untracked changes. If the combined diff is empty, reply briefly that no code changes were detected and skip emitting the PR block.\n3. Infer change type from the touched paths listed under \"Category signals\"; classify as feature, fix, refactor, or docs-with-impact, and flag backward-compatibility risk only when the diff changes released public APIs, client/command behavior, connection config, or the RESP wire handling. Judge that risk against the latest release tag, not unreleased branch-only churn.\n4. Summarize changes in 1–3 short sentences using the top five paths and stats from the committed, staged, and unstaged diffs. Explicitly call out untracked files because `--stat` does not include them. Use commit messages as supporting context, not as a substitute for inspecting the committed diff.\n5. Choose the lead verb for the description: feature → `adds`, bug fix → `fixes`, refactor/perf → `improves` or `updates`, docs-only → `updates`.\n6. Suggest a branch name. If already off `master`, keep it; otherwise propose `feat/<slug>`, `fix/<slug>`, or `docs/<slug>` based on the primary area (for example `docs/pr-draft-summary-guidance`).\n7. If the current branch matches `issue-<number>` (digits only), keep that branch suggestion. When an issue number is present, reference `https://github.com/redis/node-redis/issues/<number>` and include an auto-closing line such as `This pull request resolves #<number>.` Do not block if the issue cannot be fetched.\n8. Draft the PR title and description using the template below. Use Conventional Commits prefixes for the title (`feat:`, `fix:`, `docs:`, `chore:`, etc.).\n9. Output only the block in \"Output Format\". Keep any surrounding status note minimal and in English. Do not include company-internal references (e.g. Jira/Confluence IDs) in the branch name, title, or description.\n\n## Output Format\n\nWhen closing out a task, add this concise Markdown block (English only) after any brief status note unless the task falls under the documented skip cases or the user says they do not want it.\n\n```\n# Pull Request Draft\n\n## Branch name suggestion\n\ngit checkout -b <kebab-case suggestion, e.g., feat/pr-draft-summary-skill>\n\n## Title\n\n<single-line imperative title, which can be a commit message; a Conventional Commits prefix such as feat:, fix:, or docs: is preferred>\n\n## Description\n\n<include what you changed plus a draft pull request title and description for your local changes; start the description with prose such as \"This pull request resolves/updates/adds ...\" using a verb that matches the change (you can use bullets later), explain the change background (for bugs, clearly describe the bug, symptoms, or repro; for features, what is needed and why), any behavior changes or considerations to be aware of, and you do not need to mention any tests you ran.>\n```\n\nKeep it tight—no redundant prose around the block, and avoid repeating details between `Changes` and the description. Tests do not need to be listed unless specifically requested.\n",".agents/skills/pr-draft-summary/agents/openai.yaml":"interface:\n  display_name: \"PR Draft Summary\"\n  short_description: \"Draft the repo-ready PR title and description\"\n  default_prompt: \"Use $pr-draft-summary to generate the PR-ready summary block, title, and draft description for the current changes.\"\n",".agents/skills/runtime-behavior-probe/SKILL.md":"---\nname: runtime-behavior-probe\ndescription: Plan and execute runtime-behavior investigations with temporary TypeScript probe scripts, validation matrices, state controls, and findings-first reports. Use only when the user explicitly invokes this skill to verify actual runtime behavior beyond normal code-level checks, especially to uncover edge cases, undocumented behavior, or common failure modes in local or live integrations. A baseline smoke check is fine as an entry point, but do not stop at happy-path confirmation.\n---\n\n# Runtime Behavior Probe\n\n## Overview\n\nUse this skill to investigate real runtime behavior, not to restate code or documentation. Start by planning the investigation, then execute a case matrix, record observed behavior, and report both the findings and the method used to obtain them.\n\n## Core Rules\n\n- Treat this skill as manual-only. Do not rely on implicit invocation.\n- For `node-redis`, treat this skill as a disposable-probe workflow, not a repository implementation workflow.\n- Unless the user explicitly asks for a reusable repository artifact, the allowed write scope is limited to:\n  - a temporary directory used for probe scripts or artifacts\n  - `.agents/skills/runtime-behavior-probe/**` when the user is editing this skill itself\n- For disposable probes in `node-redis`, do not modify `examples/**`, `packages/**`, any `package.json`, `README.md`, workspace config, or build config.\n- If your draft plan would touch a disallowed path, stop and rewrite the plan before editing anything.\n- A baseline success or smoke case is often the right entry point, but do not stop there when the real question involves edge cases, drift, or failure behavior.\n- Plan before running anything. Write the case matrix first, then fill it in with observed results. The matrix can live in a scratch note, a temporary file, or the probe script header.\n- Default to local or read-only probes. Consider a live service only when it is clearly relevant, then apply the lightweight gates below before you run it.\n- Size the probe to the decision. Start with the smallest matrix that can disqualify or validate the current hypothesis, then expand only when uncertainty remains.\n- Before a live probe, apply three lightweight gates:\n  - Destination gate. Use only a live destination that is clearly allowed for the task.\n  - Intent gate. Run the live probe only when the user explicitly wants runtime verification on that integration, or explicitly approves it after you propose the probe.\n  - Data gate. If the probe will read environment variables, mutate remote state, incur material cost, or exercise non-public or user data, name the exact variable names or data class and get explicit approval first.\n- Classify each case as read-only, mutating, or costly before execution. For mutating or costly cases, or for any live case that will read environment variables, define cleanup or rollback before running the probe.\n- Use temporary files or a temporary directory for one-off probe scripts.\n- In `node-redis`, use disposable probe files outside git-tracked paths by default. Do not add one-off probes, harnesses, benchmarks, or examples under `examples/`, `packages/`, or other repository directories unless the user explicitly asks for a checked-in artifact.\n- Keep temporary artifacts until the final response is drafted. Then delete them by default unless the user asked to keep them or they are needed for follow-up. Even when artifacts are deleted, keep a short run summary of the command shape, runtime context, and artifact status in the report.\n- Before executing a live probe that will read environment variables, tell the user the exact variable names you plan to use and why, then wait for explicit approval. Examples include `REDIS_URL`, `REDIS_PASSWORD`, and other expected default names for the system under test.\n- Never print secrets, even when they come from standard environment variables that this skill may use.\n- For Redis command or server semantics, treat the repository source as the truth and use [redis.io/commands](https://redis.io/commands) to confirm contract-sensitive details such as argument order, reply types, and version availability. Use runtime probing to validate or challenge the documented behavior, not to skip the documentation pass entirely. If you rely on the website rather than source, say so in the report.\n- For benchmark or comparison probes, make parity explicit before execution. Record what is held constant, what variable is under test, which reply-shape constraints keep the comparison fair, and any counters or timings that matter for interpreting latency or cost.\n- In `node-redis`, default to a light local loop for probe authoring: temporary `probe.ts` plus temporary `tsconfig.json`, `npx tsc --noEmit -p <tmp-tsconfig>`, then `npx tsx <tmp-probe>`. Escalate to `npm run build` only when the runtime question is specifically about `dist/`, emitted exports, or packaged output.\n- In `node-redis`, do not treat a request for runtime verification, benchmarking, or comparison as permission to add a reusable example, benchmark harness, package script, or checked-in sample. Those repository changes require explicit user intent.\n- For probes that need a running Redis, remove setup ambiguity before attributing a negative result to client behavior:\n  - Confirm the server is reachable and note its mode (standalone, cluster, or sentinel) and RESP version before interpreting reply shapes.\n  - Treat RESP2 and RESP3 as separate cases, not interchangeable setup details, when the question is about reply type mapping or push messages.\n  - Clear unsupported server-version or command options first so they do not invalidate the probe. The repo test harness `@redis/test-utils` can start Redis in Docker for local probes.\n\n## Workflow\n\n1. Restate the investigation target in operational terms. Name the runtime surface, the key uncertainty, and the highest-risk behaviors to test.\n2. For `node-redis`, declare the allowed write scope before you do any implementation work. For a normal disposable probe, that means a temporary directory only.\n3. Do a short preflight. Check the relevant code or docs first, decide whether the question needs local or live validation, and note any repo, baseline, or release boundary that matters.\n4. Create a validation matrix before executing probes. Cover both baseline behavior and the most relevant failure or drift cases. The matrix can live in a scratch note, a temporary file, or a structured header inside the probe script.\n5. For each case, choose an execution mode up front:\n   - `single-shot` for deterministic one-run checks.\n   - `repeat-N` for cache, retry, streaming, interruption, rate-limit, concurrency, or other run-to-run-sensitive behavior.\n   - `warm-up + repeat-N` when first-run cold-start effects could distort the result. Use these defaults unless the task clearly needs something else:\n   - Quick screen of a repeat-sensitive question: `repeat-3`.\n   - Decision-grade latency or release recommendation: `warm-up + repeat-10`.\n   - Costly live cases: start at `repeat-3`, then expand only if the answer remains unclear. If it is genuinely unclear whether extra runs are worth the time or cost, ask the user before expanding the probe.\n6. When the question is benchmark-like or comparative, run in phases. Start with a high-signal pilot matrix against a control, then expand only the surviving candidates or unresolved cases.\n7. If the question is about a suspected regression or behavior change, add at least one known-good control case such as `origin/master`, the latest release, or the same request without the suspected option.\n8. For comparative probes, define parity before execution. Record prompt or input shape, tool-choice setup, model-settings parity, state reuse rules, and any response-shape constraint that keeps the comparison fair. If materially different output length could bias the result, record usage or token notes too.\n9. If the question asks whether one option has the same intelligence or quality as another, decide whether the matrix supports only example-pattern parity or a broader quality claim. For broader claims, add at least one harder or more open-ended case. Otherwise say explicitly that the result is limited to the covered patterns.\n10. Plan state controls before execution when hidden state could affect the result. Record whether each case uses fresh or reused state, how cache reuse or cache busting is handled, what unique IDs isolate repeated runs, and how cleanup is verified.\n11. If any live case will read environment variables, list the exact variable names and purpose for each case, then ask the user for approval before execution. Keep the approval ask short and include destination, read-only versus mutating or costly risk, exact variable names, and cleanup or rollback if relevant.\n12. Build task-specific probe scripts in a temporary location. Keep the script small, observable, and easy to discard.\n13. If you are about to propose a checked-in script, example, benchmark, or workspace script for `node-redis`, stop and verify that the user explicitly asked for a reusable repository artifact. If not, keep the probe temporary.\n14. In `node-redis`, make the runtime context explicit:\n\n- Run TypeScript probes from the repository root with `npx tsx` when practical.\n- For probe authoring, create both `probe.ts` and a sibling temporary `tsconfig.json` under `mktemp -d`, then run `npx tsc --noEmit -p <tmp-tsconfig>` before the first live execution.\n- Record the current commit, working directory, Node executable, Node version, and the package or source path you imported.\n- Avoid accidental imports from a different checkout or from `/tmp/node_modules`. If the probe needs repository code, import it from a repository-relative `file://` URL rooted at `process.cwd()`.\n- Prefer current-branch `lib/` imports when the question is \"what does this branch do now?\" and prefer `dist/` imports only when the question is specifically about packaged output after a build.\n- Do not run a repository-wide build just to typecheck a disposable probe. Reserve `npm run build` for `dist/` probes or when emitted output is itself part of the question.\n\n15. Execute the matrix and capture evidence. Record request shape, setup, observation summary, unexpected or negative result, error details, timing, runtime context, approved environment-variable names, repeat counts, warm-up handling, variance when relevant, cleanup behavior, and for comparisons note what was held constant plus any response-shape or usage notes that affect interpretation.\n16. Update the matrix with actual outcomes, not guesses.\n17. Keep temporary artifacts until the final response is drafted. Then delete them unless the user asked to keep them or they are needed for follow-up. Benchmark and repeat-heavy probes often need follow-up, so keeping artifacts is normal when the result may be revisited. If deleted, retain and report a short run summary.\n18. Report findings first, with unexpected or negative findings first. Then summarize how the validation was performed and which cases were covered.\n19. If the probe isolates one clear defect, you may include a short implementation hypothesis or minimal repro direction. Do not expand into a larger next-step plan unless the user asked for it.\n\n## Validation Matrix\n\nUse a matrix that makes the news easy to scan. Start from the runtime question and the observation summary, not just from `expected` and `pass` or `fail`.\n\nUse a matrix with at least these columns:\n\n- `case_id`\n- `scenario`\n- `mode`\n- `question`\n- `setup`\n- `observation_summary`\n- `result_flag`\n- `evidence`\n\nAdd these columns when they materially improve the investigation:\n\n- `comparison_basis`\n- `variable_under_test`\n- `held_constant`\n- `output_constraint`\n- `status`\n- `confidence`\n- `state_setup`\n- `repeats`\n- `warm_up`\n- `variance`\n- `usage_note`\n- `risk_profile`\n- `env_vars`\n- `approval`\n- `control`\n\nTreat `result_flag` as a fast scan field such as `unexpected`, `negative`, `expected`, or `blocked`. Use `status` only when there is a credible comparison basis, baseline, or documented contract to compare against.\n\nAlways consider whether the matrix should include these categories:\n\n- Baseline success.\n- Control or baseline comparison when a regression is suspected.\n- Boundary input or parameter variation.\n- Invalid or unsupported input.\n- Missing or incorrect configuration.\n- Transient external failure such as timeout, network interruption, or rate limiting.\n- Retry, idempotence, or cleanup behavior.\n- Concurrency or overlapping operations when shared state or ordering may matter.\n- Open-ended quality or intelligence samples when the question is broader than pattern parity.\n\nOpen [validation-matrix.md](./references/validation-matrix.md) when you need a stronger prioritization model or a reusable case template.\n\n## Temporary Probe Scripts\n\nWrite one-off scripts in a temporary file or temporary directory such as one created by `mktemp -d`. Keep the script outside the repository by default, even when it imports code from the repository.\n\nFor `node-redis`, a disposable runtime probe should stay disposable. Do not add a package script, modify workspace config, or create a checked-in benchmark or example unless the user explicitly asks for a reusable repository artifact.\n\nFor `node-redis`, the default authoring loop should be:\n\n1. `tmpdir=$(mktemp -d)`\n2. Write `probe.ts` and `tsconfig.json` into `$tmpdir`\n3. From the repository root, run `npx tsc --noEmit -p \"$tmpdir/tsconfig.json\"`\n4. If the quick typecheck passes, run `npx tsx \"$tmpdir/probe.ts\"`\n5. Only run `npm run build` if the probe intentionally imports `dist/` or validates packaged output\n\nUse a temporary `tsconfig.json` that extends the repository base settings but includes only the disposable probe, for example:\n\n```json\n{\n  \"extends\": \"/absolute/path/to/node-redis/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"./probe.ts\"]\n}\n```\n\nIf the probe needs repository code:\n\n- Run it with the repository root as the working directory.\n- Use `npx tsx /tmp/probe.ts` from the repository root when practical.\n- Use `npx tsc --noEmit -p /tmp/probe-tsconfig.json` as the default quick typecheck step before executing the probe.\n- Import repository modules with a `file://` URL built from `process.cwd()` and a repo-relative path. Do not assume bare workspace imports such as `@redis/client` will resolve from `/tmp`.\n- Use `lib/` imports for current-branch behavior probes and `dist/` imports for packaged-output probes.\n\nDesign the probe to maximize observability:\n\n- Print or log the exact scenario being exercised.\n- Capture runtime context such as git SHA, working directory, Node executable and version, relevant package versions, model or deployment name, endpoint or base URL alias, and any retry or tool options that materially affect behavior.\n- For live probes, record only the names of environment variables that were approved for use. Never print their values.\n- Capture structured outputs when possible.\n- Preserve raw error type, message, and status code.\n- For repeat-sensitive cases, capture the attempt index, warm-up status, and any stable identifiers that help compare runs.\n- For repeated or benchmark-style probes, write both raw results and a compact summary artifact when practical.\n- Keep branching minimal so each script answers a narrow question.\n\nBefore deleting the temporary script or directory, keep a short run summary of the script path, command used, runtime context, and whether the evidence was kept or deleted.\n\nOpen [typescript_probe.ts](./templates/typescript_probe.ts) when you want a lightweight disposable TypeScript probe scaffold. Open [repo-import-patterns.md](./references/repo-import-patterns.md) when you need to load current-branch workspace code from a temporary script.\n\n## Reporting\n\nReport in this order:\n\n1. Findings. Put unexpected or negative findings first. If there was no real news, say that explicitly.\n2. Validation approach. Summarize the code used, the runtime surface exercised, the execution modes, and the case matrix coverage.\n3. Case results. Include the matrix or a condensed version of it when the case count is large.\n4. Artifact status and brief run summary. State whether temporary artifacts were deleted or kept, and provide kept paths or the retained summary.\n5. Optional implementation note. Include this only when one clear defect was isolated and a short implementation direction would help.\n\nFor comparative probes, the report should also say what was held constant, what variable was under test, and whether the result supports only pattern parity or a broader quality claim.\n\nOpen [reporting-format.md](./references/reporting-format.md) for the recommended response template.\n\n## Resources\n\n- Open [validation-matrix.md](./references/validation-matrix.md) to design and prioritize the case matrix.\n- Open [error-cases.md](./references/error-cases.md) to expand common failure scenarios.\n- Open [redis-runtime-patterns.md](./references/redis-runtime-patterns.md) for recurring node-redis runtime probe patterns.\n- Open [repo-import-patterns.md](./references/repo-import-patterns.md) for repository-relative TypeScript import guidance.\n- Open [reporting-format.md](./references/reporting-format.md) for the final report structure.\n- Open [typescript_probe.ts](./templates/typescript_probe.ts) for a minimal disposable TypeScript probe scaffold.\n",".agents/skills/runtime-behavior-probe/agents/openai.yaml":"interface:\n  display_name: \"Runtime Behavior Probe\"\n  short_description: \"Plan and run runtime behavior probes\"\n  default_prompt: \"Use $runtime-behavior-probe to investigate actual runtime behavior with a validation matrix, disposable TypeScript probes outside git-tracked paths, and a findings-first report. For node-redis, the default write scope is a temporary directory only. Do not modify examples/, packages/, package.json files, README.md, workspace config, or build config, and do not add checked-in examples, benchmarks, or package scripts unless the user explicitly asks for a reusable repository artifact.\"\npolicy:\n  allow_implicit_invocation: false\n",".agents/skills/runtime-behavior-probe/references/error-cases.md":"# Common Error Cases\n\nUse this reference to expand beyond the happy path. Favor error cases that a real user or operator is likely to hit.\n\n## Configuration Errors\n\nCheck whether the runtime behaves differently for:\n\n- Missing required environment variables.\n- Present but malformed secrets or identifiers.\n- Wrong endpoint or base URL.\n- Wrong model or deployment name.\n- Incompatible local dependency versions.\n\nLook for:\n\n- Error type and status code.\n- Whether the failure is immediate or delayed.\n- Whether the message is actionable.\n- Whether retrying without fixing configuration changes anything.\n\n## Input Errors\n\nProbe common bad-input patterns such as:\n\n- Missing required fields.\n- Wrong data type.\n- Unsupported enum or option value.\n- Empty but syntactically valid input.\n- Oversized input or too many items.\n- Mutually incompatible options.\n\nPrefer realistic invalid inputs over artificial nonsense. The point is to learn how the runtime fails in practice.\n\n## Transport and Availability Errors\n\nWhen networked services are involved, consider:\n\n- Connection failure.\n- Read timeout.\n- Server timeout or upstream gateway error.\n- Rate limit response.\n- Partial stream interruption.\n- Reusing a connection after a failure.\n\nCapture whether the client library retries automatically, whether it surfaces retry metadata, and whether the final exception preserves the original cause.\n\n## State and Repetition Errors\n\nMany surprising bugs appear only when an operation is repeated or interrupted:\n\n- Re-submit the same request.\n- Repeat after a timeout.\n- Retry after a partial tool call or partial stream.\n- Resume after local cleanup or process restart.\n- Repeat with slightly changed inputs while reusing shared state.\n\nObserve whether the operation is idempotent, duplicated, silently ignored, or left in a partial state.\n\n## Concurrency Errors\n\nWhen shared state, ordering, or isolation may matter, consider:\n\n- Two overlapping requests with the same logical input.\n- Parallel runs that reuse the same cache key, session, container, or temporary resource.\n- Concurrent retries, cancellation, or cleanup racing with active work.\n- Output or event streams from one run leaking into another.\n\nCapture whether the runtime serializes, rejects, duplicates, corrupts, or cross-contaminates the work.\n\n## Investigation Heuristics\n\nUse these heuristics to pick error cases quickly:\n\n- Ask which failure a real engineer would debug first in production.\n- Ask which failure is most expensive if it is misunderstood.\n- Ask which failure would be invisible from code review alone.\n- Ask which failure path is likely to differ across environments.\n\nIf the error behavior is already perfectly obvious from a local validator or type system, it is usually low priority for this skill.\n",".agents/skills/runtime-behavior-probe/references/redis-runtime-patterns.md":"# Redis Runtime Patterns\n\nUse this reference for recurring node-redis investigations so you do not have to rediscover the probe strategy each time. Treat the repository source as the truth for client behavior, and use [redis.io/commands](https://redis.io/commands) to confirm contract-sensitive details such as argument order, reply types, and version availability. If you rely on the website rather than source, say so in the report.\n\n## General Rules\n\n- Prefer small, focused probes over large harnesses. Keep one script focused on one uncertainty.\n- For comparative or benchmark-like questions, start with a pilot and expand only when the answer is still unclear.\n- Capture both the request shape (command and arguments as sent) and the raw reply, before any client-side type mapping.\n- Preserve raw error type, message, and any Redis error prefix such as `WRONGTYPE`, `MOVED`, `ASK`, `CROSSSLOT`, `NOSCRIPT`, or `LOADING`.\n- Record whether behavior differs between the first call and a repeated call, and across a reconnect.\n- When the question is about regression or contract drift, add a known-good control run before attributing the result to the change under investigation.\n- Keep comparison parity explicit. Record what was held constant, what variable changed, and whether reply-shape or encoding differences could bias the conclusion.\n- Record the runtime context that actually changes Redis client behavior: server mode (standalone, cluster, sentinel), RESP version (2 vs 3), server version, and whether a feature such as client-side caching or sharded pub/sub is in use.\n\n## Connection Environment Variables\n\nDo not read these variables automatically. Before a live probe uses any of them, tell the user the exact variable names you plan to read and why each one is needed, then wait for explicit approval. Never print their values:\n\n- `REDIS_URL`\n- `REDIS_PASSWORD`\n- `REDIS_TLS_CA`, `REDIS_TLS_CERT`, `REDIS_TLS_KEY` (and any other `REDIS_TLS_*` the probe needs)\n\nFor most local probes you do not need any of these. The repository test harness `@redis/test-utils` can start Redis in Docker, so a disposable probe can target a local containerized server (for example `redis://localhost:6379`) without reading credentials. Prefer that over a live remote service unless the question is specifically about the remote deployment.\n\nIf the task targets another integration, use that integration's expected default variable names under the same rule.\n\n## Environment False Signals\n\nBefore attributing a failure to the patch or client code under review, exclude source-selection and environment problems with a controlled comparison.\n\n- Confirm the commit, worktree, working directory, Node executable/version, and imported module path. A probe of stale `dist`, another checkout, `/tmp/node_modules`, or an installed `redis` package does not prove current `lib` behavior.\n- Rebuild when emitted declarations, package exports, generated metadata, or `dist` is the subject. Otherwise prefer current-source `lib/` imports so stale build output cannot create a false result.\n- Confirm a Redis server is actually running and reachable. A connection refused or timeout against a missing Docker/Redis container is an environment condition, not a client defect, until a controlled rerun against a known-good server ties it to the code.\n- Confirm the RESP version the client negotiated. A reply-shape surprise is often just RESP2 vs RESP3, not a bug.\n- Confirm server mode matches the probe assumptions. Running a cluster-only expectation against a standalone server, or the reverse, produces misleading routing and slot errors.\n- Run base and head controls with the same Node version, dependency graph, server mode, RESP version, environment-variable names, and command shape. Record intentional differences.\n- Treat sandbox denials, unavailable Docker infrastructure, expired remote sessions, authentication, quota, rate limits, service outages, and stale caches as environment conditions until a controlled rerun ties them to the patch.\n- Never print credentials, connection URLs with embedded passwords, or TLS key material. Change only the minimum in-scope environment or disposable state needed for the control and record variable names rather than values.\n\nIn the final report, distinguish code failures, unsupported configurations, environment blockers, and inconclusive probes. Do not collapse them into one failed-test count.\n\n## Node-Redis Probe Patterns\n\nStart from the uncertainty instead of from the full client surface. The patterns below cover the runtime questions that recur most often in node-redis.\n\n### RESP2 vs RESP3 reply shapes and type mapping\n\nUse when the uncertainty is what a command returns at runtime and how the client maps it.\n\n- Run the same command under RESP2 and RESP3 as separate cases. Maps, sets, doubles, big numbers, verbatim strings, and booleans differ between the two protocols.\n- Capture the raw reply and the post-mapping JavaScript value. Note whether the client returns plain objects, `Map`, arrays, `null`, or typed numbers.\n- Watch precision-sensitive types. RESP3 `DOUBLE` and `BIG_NUMBER` can lose precision when mapped to `Number`; record the exact value.\n- If the probe configures a custom `typeMapping`, treat each mapping choice as part of `held_constant`.\n\n### Single-node vs cluster vs sentinel\n\nUse when key routing, slot ownership, or topology matters.\n\n- For cluster, capture which node served the command and whether a `MOVED` or `ASK` redirect occurred. Probe multi-key commands across keys in different slots to observe `CROSSSLOT` behavior and any client-side slot checks.\n- Use hash tags (`{tag}`) to force keys into the same slot when the probe needs a multi-key command to succeed.\n- For sentinel, capture master discovery and what happens on a simulated failover.\n- Keep mode constant within a case. A standalone control alongside a cluster case is a useful comparison, not a substitute.\n\n### Pub/sub and sharded pub/sub\n\nUse when delivery semantics or resubscription matter.\n\n- Capture subscribe confirmation, message delivery order, and whether messages are delivered to pattern (`PSUBSCRIBE`) and channel subscribers as expected.\n- For sharded pub/sub (`SSUBSCRIBE` and `SPUBLISH`) in cluster, capture which shard delivered the message.\n- Force a reconnect and observe whether the client resubscribes to channels and patterns automatically. This is a common drift point.\n\n### Reconnection, retry, and the offline command queue\n\nUse when the question is about resilience.\n\n- Simulate a dropped connection (stop or restart the container, or close the socket) and capture: whether commands issued while offline are queued, the order they flush in on reconnect, and whether the reconnect strategy backoff behaves as configured.\n- Distinguish commands rejected immediately from commands queued and later resolved or rejected.\n- Capture whether in-flight commands at disconnect are retried, rejected, or hang.\n\n### Pipelining and MULTI/EXEC transactions\n\nUse when batching or atomicity is under test.\n\n- For pipelining, confirm replies map back to the right commands in order, including when one command in the batch errors.\n- For `MULTI`/`EXEC`, capture queueing errors versus execution errors, behavior on `DISCARD`, and `WATCH` optimistic-locking aborts (`EXEC` returning `null`).\n\n### Client-side caching (RESP3 push invalidation)\n\nUse when caching correctness matters.\n\n- Requires RESP3. Capture whether invalidation push messages arrive after a tracked key is modified by another client, and whether the local cache entry is dropped.\n- Probe the broadcast and default tracking modes separately.\n\n### Blocking commands and connection isolation\n\nUse for `BLPOP`, `BRPOP`, `XREAD BLOCK`, `WAIT`, and similar.\n\n- Capture whether the blocking command monopolizes a connection and whether the client isolates it (for example via a dedicated connection) so other commands are not stalled.\n- Probe timeout behavior and what resolves when the block is satisfied by another client versus when it times out.\n\n### Pool behavior\n\nUse when a pooled client is involved.\n\n- Capture how commands are distributed across pool connections, behavior under saturation, and what happens when a pooled connection drops.\n- For commands that require connection affinity (transactions, blocking, subscriptions), confirm the pool keeps them on a single connection.\n\n### SCAN iterators\n\nUse for `SCAN`, `HSCAN`, `SSCAN`, and `ZSCAN`.\n\n- Capture cursor progression, whether the async iterator terminates, and whether `COUNT` and `MATCH` behave as expected. Note that SCAN gives no count or completeness guarantees mid-iteration.\n\n### Large payloads and timeouts\n\nUse when size or latency is the concern.\n\n- Capture behavior with large values and large reply sets, and whether command-level or socket timeouts fire as configured.\n- For repeat-sensitive latency, use warm-up plus repeat-N and record medians, not a single sample.\n\n## What to Capture\n\nFor node-redis probes, try to record:\n\n- The command and arguments as sent, plus the raw reply before type mapping.\n- The mapped JavaScript value and its type, especially for RESP3 maps, sets, doubles, and big numbers.\n- Server mode (standalone, cluster, or sentinel), negotiated RESP version, and server version.\n- Whether fields are absent, `null`, empty, or transformed by the client.\n- Raw error type, message, and any Redis error prefix for failures.\n- Reconnect and retry behavior, offline-queue flush order, and resubscription on reconnect when relevant.\n- For cluster, which node served the command and any redirect.\n- Which environment-variable names were approved for the probe when a live or credentialed server was required.\n\nDo not spend time rediscovering static documentation unless the runtime result seems to contradict what you expected. The value of this skill is in the observed behavior.\n",".agents/skills/runtime-behavior-probe/references/repo-import-patterns.md":"# Repository Import Patterns\n\nUse this reference when a temporary probe script outside the repository needs to load code from the current branch.\n\n## Why This Exists\n\nWhen you run `npx tsx /tmp/probe.ts` from the repository root, `tsx` itself comes from this repository, but the probe file still lives under `/tmp`. Bare workspace imports such as `@redis/client` or `@redis/json` therefore resolve as if the script were its own package. That is often not what you want for \"what does the current branch do right now?\" investigations.\n\nThe safe default is to build a `file://` import URL from `process.cwd()`, which should be the repository root, and a repository-relative path.\n\n## Default Rule\n\n- Use `lib/` imports when the question is about current-branch behavior.\n- Use `dist/` imports when the question is about packaged output after a build.\n- Avoid bare workspace imports from `/tmp` unless the probe is explicitly testing published package resolution rather than branch-local source behavior.\n\n## Quick Typecheck Loop\n\nFor disposable probes in `node-redis`, the default loop should stay outside the repository and avoid a full workspace build:\n\n1. Create a temporary directory with `mktemp -d`.\n2. Write `probe.ts` there.\n3. Write a sibling `tsconfig.json` that extends the repository's `tsconfig.base.json`.\n4. From the repository root, run `npx tsc --noEmit -p /tmp/.../tsconfig.json`.\n5. If that passes, run `npx tsx /tmp/.../probe.ts`.\n\nExample temporary `tsconfig.json`:\n\n```json\n{\n  \"extends\": \"/absolute/path/to/node-redis/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"./probe.ts\"]\n}\n```\n\nThis keeps TypeScript module resolution and strictness close to the repository defaults while limiting the check surface to the disposable probe file.\n\nDo not convert this default loop into a checked-in benchmark, example, or package script unless the user explicitly asks for a reusable repository artifact.\n\n## Recommended Helper\n\nThe disposable TypeScript scaffold exposes:\n\n    const module = await importRepoModule(\"packages/client/lib/index.ts\");\n\nInternally, the helper should be equivalent to:\n\n    import { join } from \"node:path\";\n    import { pathToFileURL } from \"node:url\";\n\n    async function importRepoModule(repoRelativePath: string) {\n      const absolutePath = join(process.cwd(), repoRelativePath);\n      return import(pathToFileURL(absolutePath).href);\n    }\n\nThis keeps the import anchored to the current checkout instead of the temporary script location.\n\n## Choosing `lib/` Versus `dist/`\n\nUse `lib/` when:\n\n- You are validating a bug fix or regression on the current branch.\n- You want to know how the repository behaves before packaging.\n- The question is about implementation details or internal helper behavior.\n\nUse `dist/` when:\n\n- The question is specifically about what consumers load after `npm run build`.\n- You need to validate export maps or emitted `.js` and `.d.ts` output.\n- The bug only reproduces in generated output.\n\nIf the probe uses `dist/`, record whether you ran a fresh build first. A stale `dist/` directory can create false positives or false negatives.\n\nDo not run `npm run build` only to typecheck or execute a `lib/`-based disposable probe. The fast path is temp `tsconfig` plus `npx tsc --noEmit`, then `npx tsx`.\n\n## Simple Smoke Targets\n\nWhen you only need to prove the helper works, start with a simple target that has minimal side effects, for example:\n\n- `packages/client/lib/index.ts`\n- `packages/client/lib/client/index.ts`\n- `packages/json/lib/index.ts`\n- another file with obvious exported symbols and no environment requirement\n\nIf a chosen file pulls in more of the repo than expected, switch to a simpler target rather than weakening the import guidance.\n\n## What To Record\n\nWhen a probe imports repo code, report:\n\n- The repo-relative path you imported.\n- Whether it came from `lib/` or `dist/`.\n- The repository root used by `process.cwd()`.\n- Any prerequisite step such as `npm run build`.\n",".agents/skills/runtime-behavior-probe/references/reporting-format.md":"# Reporting Format\n\nLead with findings, not process. The user asked for investigation results, so the answer should start with the most important observed behaviors. Put the real news first.\n\n## Recommended Order\n\n1. Findings.\n2. Validation approach.\n3. Case matrix or condensed case summary.\n4. Artifact status and brief run summary.\n5. Optional implementation note.\n\n## Findings Section\n\nMake each finding answer one user-relevant question. Good findings usually include:\n\n- What was observed.\n- Why it matters.\n- The condition under which it happens.\n- What was held constant when the finding comes from a comparison probe.\n- `scope`: The boundary of the finding, such as commit, model, Node version, live versus local, or repeat mode.\n- `confidence`: `high`, `medium`, or `low`.\n\nAvoid burying the main result under setup details.\n\nPut `unexpected` or `negative` findings first. If there were no unexpected or negative findings in the executed cases, say that explicitly before the rest of the findings section.\n\nIf the probe was comparative, say whether the result supports:\n\n- Pattern parity only.\n- A broader quality claim.\n\nDo not imply a broader quality equivalence than the executed cases justify.\n\n## Validation Approach Section\n\nSummarize:\n\n- The runtime surface you exercised.\n- The shape of the probe code, in overview only.\n- Which categories of cases you covered.\n- Which execution modes you used, including repeat counts or warm-up handling when relevant.\n- Whether live credentials or external services were used.\n- Any important state controls such as fresh state, cache reuse, cache busting, unique IDs, or cleanup verification.\n- For comparison probes, what was held constant, what was varied, and whether output-shape or usage differences could still influence the conclusion.\n- Whether the usual docs path or an official-docs fallback was used for contract-sensitive checks.\n\nKeep this concise. The user needs enough detail to trust the result, not a line-by-line replay of the script.\n\n## Case Summary\n\nInclude either the full matrix or a condensed summary. At minimum, show:\n\n- Which scenarios were executed.\n- Whether the run was a quick pilot, an expanded matrix, or both.\n- Which ones produced `unexpected` or `negative` results.\n- Which ones passed or failed when a real comparison basis existed.\n- Which cases were blocked.\n- Where the supporting evidence lived, or that it was deleted.\n\nIf the matrix is large, show the highest-value cases in the main response and keep the rest as a compact appendix or note.\n\n## Artifact Status And Brief Run Summary\n\nState one of these explicitly:\n\n- Temporary artifacts were kept until the final response was drafted, then deleted after validation.\n- Temporary artifacts were kept at `<path>` because the user asked to keep them.\n- Temporary artifacts were kept at `<path>` because they are needed for follow-up analysis.\n\nEven if artifacts were deleted, retain a short run summary such as:\n\n- Probe command or runner shape.\n- Runtime context summary such as commit, Node executable, Node version, or model.\n- Artifact path and final status.\n\nFor benchmark or repeat-heavy probes, keeping artifacts for follow-up is often the right default even when the immediate report is done.\n\n## Optional Implementation Note\n\nInclude this only when one clear defect was isolated and a short implementation hypothesis or minimal repro direction would help. Keep it brief. Do not turn the report into a broader next-step plan unless the user asked for that.\n\n## Compact Template\n\nUse this outline when you need a fast structure:\n\n    Findings:\n    - <finding 1>\n      held constant: <prompt/tool/state settings kept the same, if comparative>\n      scope: <commit/model/node/live-local/repeat-mode>\n      confidence: <high|medium|low>\n    - <finding 2>\n      held constant: <prompt/tool/state settings kept the same, if comparative>\n      scope: <commit/model/node/live-local/repeat-mode>\n      confidence: <high|medium|low>\n\n    Validation approach:\n    - Surface: <what was exercised>\n    - Probe code: <brief overview>\n    - Coverage: <success, edge, error, repeat-sensitive, and quality categories>\n    - Execution modes: <single-shot|repeat-N|warm-up + repeat-N>\n    - Comparison parity: <what was held constant and what varied, if comparative>\n    - Docs source: <MCP or official-docs fallback, if relevant>\n\n    Case summary:\n    | case_id | scenario | result_flag | status | note |\n    | --- | --- | --- | --- | --- |\n    | S1 | ... | expected | pass | ... |\n    | E1 | ... | negative | fail | ... |\n\n    Artifact status and brief run summary:\n    - Temporary artifacts were kept until the final response was drafted, then deleted.\n    - Summary: <command/runtime-context/artifact-status summary>\n\n    Optional implementation note:\n    - <brief hypothesis or minimal repro direction>\n\nAdjust the format to the task, but preserve the ordering.\n",".agents/skills/runtime-behavior-probe/references/validation-matrix.md":"# Validation Matrix\n\nUse the matrix to decide what to probe before writing scripts. The goal is not exhaustive combinatorics; the goal is high-value coverage that is visible, explainable, and likely to reveal runtime surprises. The matrix should make the real news easy to scan.\n\n## Minimum Columns\n\nUse these columns unless the task clearly needs more:\n\n- `case_id`: Stable identifier such as `S1`, `E3`, or `R2`.\n- `scenario`: Short description of the behavior under test.\n- `mode`: `single-shot`, `repeat-N`, or `warm-up + repeat-N`.\n- `question`: The concrete runtime uncertainty this case is answering.\n- `setup`: Inputs, environment, or preconditions required for the case.\n- `observation_summary`: A compact summary of what actually happened.\n- `result_flag`: `unexpected`, `negative`, `expected`, or `blocked`.\n- `evidence`: Path, log reference, or `deleted`.\n\nAdd these columns when they materially improve the investigation:\n\n- `comparison_basis`: The baseline, docs, or prior behavior you are comparing against.\n- `variable_under_test`: The single factor that is intentionally changing in a comparison case.\n- `held_constant`: Prompt shape, tool setup, model settings, or state rules that were intentionally kept the same.\n- `output_constraint`: Any schema, length, or response-shape constraint used to keep the comparison fair.\n- `status`: Use `pass`, `fail`, `unexpected-pass`, `unexpected-fail`, or `blocked` only when there is a credible comparison basis or control.\n- `confidence`: `high`, `medium`, or `low`.\n- `state_setup`: Fresh or reused state, cache strategy, unique IDs, and cleanup checks.\n- `repeats`: Number of measured runs.\n- `warm_up`: Whether a warm-up run was used and why.\n- `variance`: Any useful spread or instability note across repeated runs.\n- `usage_note`: Token, usage, or output-length note when it materially affects interpretation.\n- `control`: Known-good comparison point for regression or behavior-change questions.\n- `risk_profile`: `read-only`, `mutating`, or `costly` for live probes.\n- `env_vars`: Exact environment-variable names the case plans to read.\n- `approval`: `not-needed`, `pending`, or `approved` for cases that need user permission before execution.\n\nUse `result_flag` as the fast scan field. It is what makes unexpected or negative findings jump out before the reader studies the full report.\n\nUse `status` only when you have a real comparison basis. If the case is exploratory and there is no trustworthy baseline, prefer a strong `observation_summary` plus `result_flag` and `confidence` instead of pretending the result is a clean pass or fail.\n\n## Choosing Execution Mode\n\nPick an execution mode before you run the case:\n\n- Use `single-shot` for deterministic, one-run checks.\n- Use `repeat-N` automatically when the question involves cache behavior, retries, streaming, interruptions, rate limiting, concurrency, or other run-to-run-sensitive behavior.\n- Use `warm-up + repeat-N` when the first run is likely to include cold-start effects such as container provisioning, import caches, or prompt-cache population.\n\nUse these defaults unless the task clearly needs something else:\n\n- `repeat-3` for a quick screen of a repeat-sensitive question.\n- `warm-up + repeat-10` for decision-grade latency comparisons or release-facing recommendations.\n- For costly live probes, start at `repeat-3`, then expand only if the answer is still unclear.\n\nIf it is genuinely unclear whether extra runs are worth the time or cost, ask the user before expanding the probe.\n\n## Phase The Matrix\n\nWhen the question is comparative or benchmark-like, do not jump straight to the largest matrix.\n\nStart with a pilot:\n\n- One control.\n- One or two highest-signal success cases.\n- The smallest repeat count that can disqualify a weak candidate quickly.\n\nExpand only when:\n\n- The candidate survives the pilot.\n- The results are close enough that more samples matter.\n- A major runtime surface is still uncovered.\n- The user explicitly wants decision-grade evidence.\n\n## Coverage Categories\n\nTry to cover at least one case from each relevant category:\n\n- `success`: Normal behavior that should work.\n- `control`: Known-good comparison such as `origin/master`, the latest release, or the same request without the suspected option.\n- `boundary`: Size, count, or parameter limits near a plausible edge.\n- `invalid`: Bad inputs or unsupported combinations.\n- `misconfig`: Missing key, wrong endpoint, bad permissions, or incompatible local setup.\n- `transient`: Timeout, temporary server issue, network breakage, or rate limiting.\n- `recovery`: Retry behavior, partial completion, duplicate submission, or cleanup.\n- `concurrency`: Overlapping operations when shared state, ordering, or isolation may matter.\n- `quality`: A harder or more open-ended sample when the user is asking about model intelligence, not just workflow parity.\n\nIf time is limited, prioritize categories in this order:\n\n1. Known-good control when the question implies regression or drift.\n2. Highest-risk success case.\n3. Most plausible user-facing failure.\n4. Most likely edge case with ambiguous behavior.\n5. Cleanup or retry semantics.\n6. Lower-probability extremes.\n\n## Matrix Template\n\nUse this compact template:\n\n    | case_id | scenario | mode | question | setup | state_setup | variable_under_test | held_constant | comparison_basis | observation_summary | result_flag | status | evidence |\n    | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n    | K1 | Known-good control | single-shot | Does the baseline still show the expected behavior? | Same probe against baseline target | Fresh state | none | current probe shape | `origin/master` or latest release | pending | pending | pending | pending |\n    | S1 | Baseline success | single-shot | What does the normal success path look like at runtime? | Valid config and representative input | Fresh state | none | representative input and setup | current docs or local expectation | pending | pending | pending | pending |\n    | R1 | Cache or retry behavior | warm-up + repeat-N | Does behavior change after the first run or across retries? | Same request repeated under controlled settings | Cache key or retry setup recorded | reuse versus fresh state | prompt shape and tool setup | same request without reuse, or docs if available | pending | pending | pending | pending |\n    | C1 | Model comparison pilot | warm-up + repeat-N | Does candidate B preserve the covered behavior while improving latency? | Same scenario across two models | Fresh state and stable IDs | model name | prompt shape, tool choice, and model settings parity | control model in the same probe | pending | pending | pending | pending |\n    | E1 | Invalid input | single-shot | How does the runtime reject a realistic bad input? | Missing required field | Fresh state | invalid field value | same request with valid field | same request with valid field | pending | pending | pending | pending |\n    | X1 | Concurrent overlap | repeat-N | Do overlapping runs interfere with each other? | Two or more overlapping operations | Unique IDs plus cleanup verification | overlap timing | same logical input | same request serialized, if available | pending | pending | pending | pending |\n\n## Recording Results\n\nKeep `question` unchanged after execution. Put the actual behavior in `observation_summary`, then mark the scan-friendly `result_flag`.\n\nUse these `result_flag` values consistently:\n\n- `unexpected`: The result diverged from the best current understanding in a surprising way.\n- `negative`: The result exposed a user-relevant failure, risk, or sharp edge.\n- `expected`: The result matched the current understanding and did not reveal new risk.\n- `blocked`: The case did not produce a trustworthy observation.\n\nOnly fill `status` when there is a credible comparison basis. Otherwise use `observation_summary`, `result_flag`, and `confidence` to communicate what was learned without over-claiming certainty.\n\nFor comparison cases, use `observation_summary` and the final report to say whether the evidence supports pattern parity only or a broader quality claim.\n\nIf a case reveals a new branch of behavior, add a follow-up case instead of overloading the original one.\n\n## Evidence Discipline\n\nTreat a case as incomplete when:\n\n- The observed output omits the key result you were testing.\n- The script mixed multiple questions and the result is ambiguous.\n- Hidden state, cache behavior, or previous runs may have influenced the result and were not controlled or documented.\n- The question is whether behavior changed, but the case has no credible control or baseline to compare against.\n- The case plans to read environment variables, but the exact variable names were not approved by the user before execution.\n- The case was repeat-sensitive, but it ran only once without a clear rationale.\n\nWhen this happens, narrow the probe and rerun. A smaller script with a cleaner result is better than a more complicated script that is hard to trust.\n",".agents/skills/test-coverage-improver/SKILL.md":"--\nname: test-coverage-improver\ndescription: 'Improve test coverage in the node-redis monorepo: run a package test suite under nyc, inspect coverage artifacts, identify low-coverage files and branches, propose high-impact tests, and confirm with the user before writing tests.'\n---\n\n# Test Coverage Improver\n\n## Overview\n\nUse this skill whenever coverage needs assessment or improvement (coverage regressions, failing thresholds, or user requests for stronger tests). It runs the coverage suite, analyzes results, highlights the biggest gaps, and prepares test additions while confirming with the user before changing code.\n\nCoverage is collected per package via `nyc` (each package's `test` script runs `nyc -r text-summary -r lcov mocha ...`). **Docker must be running** — the test harness starts real Redis containers via `@redis/test-utils`.\n\n## Quick Start\n\n1. Pick the target package (e.g. `@redis/client`) and run its tests under coverage from the repo root: `npm test -w @redis/client`. This regenerates that package's `coverage/`.\n2. Collect artifacts: the `text-summary` printed to stdout for totals, plus `packages/<pkg>/coverage/lcov.info` and `packages/<pkg>/coverage/lcov-report/index.html` for line/branch drill-downs. For machine-readable file totals, rerun with an added summary reporter: `npm test -w @redis/client -- ... ` is awkward, so prefer `npx nyc -r json-summary -r text-summary mocha ...` from inside the package when you need `coverage/coverage-summary.json`.\n3. Summarize coverage: total percentages, lowest files, branches under 80%, and uncovered lines/paths.\n4. Draft test ideas per file: scenario, behavior under test, expected outcome, and likely coverage gain.\n5. Ask the user for approval to implement the proposed tests; pause until they agree.\n6. After approval, write the tests next to the source in the relevant package, rerun coverage, then run `npm run build` and `npm run lint` before marking work complete.\n\n## Workflow Details\n\n- **Run coverage**: Execute the target package's test script (`npm test -w <pkg>`) from the repo root. Avoid watch flags. Keep prior coverage artifacts only if comparing trends. Confirm Docker is available first.\n- **Parse summaries efficiently**:\n  - Use the printed `text-summary` for quick totals.\n  - Use `coverage/lcov.info` or `coverage/lcov-report/index.html` to spot branch- and line-level holes.\n  - Add the `json-summary` nyc reporter when you need `coverage/coverage-summary.json` for file-level totals.\n- **Prioritize targets**:\n  - Core client/cluster/sentinel/pool/RESP code in `packages/*/lib` before examples or docs.\n  - Files with statements/branches below 80% or newly added code at 0%.\n  - Recent bug fixes or risky code paths (error handling, reconnection, timeouts, pub/sub, concurrency).\n- **Design impactful tests**:\n  - Hit uncovered branches: error cases, boundary inputs, optional flags, and cancellation/timeouts.\n  - Cover combinational logic rather than trivial happy paths.\n  - Co-locate specs next to source as `packages/<pkg>/lib/**/<NAME>.spec.ts` (mocha + `tsx`, `node:assert`). For command/topology tests use `testUtils.testAll(name, fn, { client, cluster })`. Avoid flaky async timing.\n- **Coordinate with the user**: Present a numbered, concise list of proposed test additions and expected coverage gains. Ask explicitly before editing code or fixtures.\n- **After implementation**: Rerun coverage, report the updated summary, and note any remaining low-coverage areas.\n\n## Notes\n\n- Keep any added comments or code in English.\n- Do not create `scripts/`, `references/`, or `assets/` unless needed later.\n- If coverage artifacts are missing or stale, rerun the package test suite instead of guessing.\n- `nyc` excludes `dist`, `**/*.spec.ts`, `lib/test-utils.ts`, and `examples/*` (see each package's `.nycrc`); do not target those for coverage.\n",".agents/skills/test-coverage-improver/agents/openai.yaml":"interface:\n  display_name: \"Test Coverage Improver\"\n  short_description: \"Analyze coverage gaps and propose high-impact tests\"\n  default_prompt: \"Use $test-coverage-improver to analyze coverage gaps, propose high-impact tests, and update coverage after approval.\"\n","AGENTS.md":"# AGENTS.md\n\nGuidance for agents working in this repo.\n\n## What this is\n\n`node-redis` — modern Redis client for Node.js. A **monorepo** (npm workspaces) of\npublishable packages. Requires Node `>= 20`. TypeScript throughout.\n\n## Packages (`packages/`)\n\n| Package | Purpose |\n| --- | --- |\n| `redis` | \"All-in-one\" meta-package re-exporting client + all modules |\n| `client` (`@redis/client`) | Core: `RedisClient`, `RedisCluster`, `RedisSentinel`, pool, RESP codec, command framework |\n| `bloom` (`@redis/bloom`) | Probabilistic commands (bloom, cuckoo, count-min, t-digest, top-k) |\n| `json` (`@redis/json`) | RedisJSON commands |\n| `search` (`@redis/search`) | RediSearch commands |\n| `time-series` (`@redis/time-series`) | Time-series commands |\n| `entraid` (`@redis/entraid`) | Microsoft Entra ID token auth |\n| `test-utils` (`@redis/test-utils`) | Shared test harness; spins up Redis via docker |\n\nModule packages depend on `@redis/client` and follow the same command structure.\n\n## Core layout — `packages/client/lib/`\n\n- `client/` — connection internals: `index.ts` (RedisClient), `socket.ts`, `commands-queue.ts`, `parser.ts`, `pool.ts`, `pub-sub.ts`, `cache.ts`\n- `cluster/`, `sentinel/` — cluster & sentinel clients\n- `commands/` — one file per Redis command (e.g. `GET.ts`) + `index.ts` registry\n- `RESP/` — RESP2/RESP3 protocol: `encoder.ts`, `decoder.ts`, `types.ts`\n- `authx/` — auth/credential providers\n\n## Command pattern\n\nEach command is `<NAME>.ts` exporting a `Command` object:\n\n```typescript\nexport default {\n  CACHEABLE: true,\n  IS_READ_ONLY: true,\n  parseCommand(parser: CommandParser, key: RedisArgument) {\n    parser.push('GET');\n    parser.pushKey(key);\n  },\n  transformReply: undefined as unknown as () => BlobStringReply | NullReply\n} as const satisfies Command;\n```\n\n- `parseCommand` builds wire args via `CommandParser` (`push`, `pushKey`, ...).\n- `transformReply` maps reply to JS type; `undefined` = pass-through. Can be keyed by RESP version `{ 2: ..., 3: ... }`.\n- Register new command in the package's `commands/index.ts` (import + map entry). RESP3 is default — no extra RESP3 test needed for new commands.\n- JSDoc on commands is checked: `npm run check:command-jsdoc`.\n\n## Tests\n\n- Co-located `<NAME>.spec.ts` next to source. Mocha + `tsx`, `node:assert`.\n- `testUtils.testAll(name, fn, { client, cluster })` runs same test across server + cluster topologies (see `test-utils.ts`, `GLOBAL`).\n- **Docker required** — test-utils starts real Redis containers.\n- Pure arg/reply tests use `parseArgs(COMMAND, ...args)`.\n\nCommands:\n- `npm test` — full suite, all workspaces (runs `cleanup` first).\n- `npm test -w @redis/client` — one package.\n- Single file from root: `npm run test-single -- <path-to-spec>`.\n- `npm run build` — `tsc --build` (project references; build before cross-package work).\n- Build can break on stale `dist/` from project references. Clean rebuild:\n  ```bash\n  find packages -type d -name \"dist\" -exec rm -rf {} + && npm run build\n  ```\n- `npm run lint` — lint changed files only; `npm run lint:all` for everything.\n\n## Conventions\n\n- TypeScript strict mode; `noUnusedLocals` on. Target ES2022 / NodeNext modules.\n- Raw command names (`HSET`) and camelCase aliases (`hSet`) both exposed.\n- Conventional Commits. Per-package releases via `release-it` (`npm run release`).\n- Keep company-internal refs (Jira/Confluence IDs, internal links) out of OSS commits, branches, PRs, code.\n\n## Docs\n\nDeep-dive guides in `docs/` (client-configuration, clustering, sentinel, pool, RESP, transactions, programmability, pub-sub, scan-iterators, migration guides). Runnable examples in `examples/`, `doctests/`.\n","packages/bloom/README.md":"# @redis/bloom\n\nThis package provides support for the [RedisBloom](https://redis.io/docs/data-types/probabilistic/) module, which adds additional probabilistic data structures to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisBloom module installed.\n\nRedisBloom provides the following probabilistic data structures:\n\n* Bloom Filter: for checking set membership with a high degree of certainty.\n* Cuckoo Filter: for checking set membership with a high degree of certainty.\n* T-Digest: for estimating the quantiles of a stream of data.\n* Top-K: Maintain a list of k most frequently seen items.\n* Count-Min Sketch: Determine the frequency of events in a stream.\n\nFor some examples, see [`bloom-filter.js`](https://github.com/redis/node-redis/tree/master/examples/bloom-filter.js), [`cuckoo-filter.js`](https://github.com/redis/node-redis/tree/master/examples/cuckoo-filter.js), [`count-min-sketch.js`](https://github.com/redis/node-redis/tree/master/examples/count-min-sketch.js) and [`topk.js`](https://github.com/redis/node-redis/tree/master/examples/topk.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n","packages/client/README.md":"# @redis/client\n\nThe source code and documentation for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.\n","packages/entraid/README.md":"# @redis/entraid\n\nSecure token-based authentication for Redis clients using Microsoft Entra ID (formerly Azure Active Directory).\n\n## Features\n\n- Token-based authentication using Microsoft Entra ID\n- Automatic token refresh before expiration\n- Automatic re-authentication of all connections after token refresh\n- Support for multiple authentication flows:\n  - Managed identities (system-assigned and user-assigned)\n  - Service principals (with or without certificates)\n  - Authorization Code with PKCE flow\n  - DefaultAzureCredential from @azure/identity\n- Built-in retry mechanisms for transient failures\n\n## Installation\n\n\n```bash\nnpm install \"@redis/client@5.0.0-next.7\"\nnpm install \"@redis/entraid@5.0.0-next.7\"\n```\n\n## Getting Started\n\nThe first step to using @redis/entraid is choosing the right credentials provider for your authentication needs. The `EntraIdCredentialsProviderFactory` class provides several factory methods to create the appropriate provider:\n\n- `createForSystemAssignedManagedIdentity`: Use when your application runs in Azure with a system-assigned managed identity\n- `createForUserAssignedManagedIdentity`: Use when your application runs in Azure with a user-assigned managed identity\n- `createForClientCredentials`: Use when authenticating with a service principal using client secret\n- `createForClientCredentialsWithCertificate`: Use when authenticating with a service principal using a certificate\n- `createForAuthorizationCodeWithPKCE`: Use for interactive authentication flows in user applications\n- `createForDefaultAzureCredential`: Use when you want to leverage Azure Identity's DefaultAzureCredential\n\n## Usage Examples\n\n### Service Principal Authentication\n\n```typescript\nimport { createClient } from '@redis/client';\nimport { EntraIdCredentialsProviderFactory } from '@redis/entraid';\n\nconst provider = EntraIdCredentialsProviderFactory.createForClientCredentials({\n  clientId: 'your-client-id',\n  clientSecret: 'your-client-secret',\n  authorityConfig: {\n    type: 'multi-tenant',\n    tenantId: 'your-tenant-id'\n  },\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8 // Refresh token after 80% of its lifetime\n  }\n});\n\nconst client = createClient({\n  url: 'redis://your-host',\n  credentialsProvider: provider\n});\n\nawait client.connect();\n```\n\n### System-Assigned Managed Identity\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForSystemAssignedManagedIdentity({\n  clientId: 'your-client-id',\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n```\n\n### User-Assigned Managed Identity\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForUserAssignedManagedIdentity({\n  clientId: 'your-client-id',\n  userAssignedClientId: 'your-user-assigned-client-id',\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n```\n\n### DefaultAzureCredential Authentication\n\ntip: see a real sample here: [samples/interactive-browser/index.ts](./samples/interactive-browser/index.ts)\n\nThe DefaultAzureCredential from @azure/identity provides a simplified authentication experience that automatically tries different authentication methods based on the environment. This is especially useful for applications that need to work in different environments (local development, CI/CD, and production).\n\n```typescript\nimport { createClient } from '@redis/client';\nimport { getDefaultAzureCredential } from '@azure/identity';\nimport { EntraIdCredentialsProviderFactory, REDIS_SCOPE_DEFAULT } from '@redis/entraid';\n\n// Create a DefaultAzureCredential instance\nconst credential = getDefaultAzureCredential();\n\n// Create a provider using DefaultAzureCredential\nconst provider = EntraIdCredentialsProviderFactory.createForDefaultAzureCredential({\n\n  credential,\n  scopes: REDIS_SCOPE_DEFAULT, // The Redis scope\n  // Optional additional parameters for getToken\n  options: {\n    // Any options you would normally pass to azure's default credential.getToken()\n  },\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n\nconst client = createClient({\n  url: 'redis://your-host',\n  credentialsProvider: provider\n});\n\nawait client.connect();\n```\n\n#### Important Notes on Using DefaultAzureCredential\n\nWhen using the `createForDefaultAzureCredential` method, you need to:\n\n1. Create your own instance of `DefaultAzureCredential`\n2. Pass the same parameters to the factory method that you would use with the `getToken()` method:\n   - `scopes`: The Redis scope (use the exported `REDIS_SCOPE_DEFAULT` constant)\n   - `options`: Any additional options for the getToken method\n\nThis factory method creates a wrapper around DefaultAzureCredential that adapts it to the Redis client's\nauthentication system, while maintaining all the flexibility of the original Azure Identity authentication.\n\n## Important Limitations\n\n### RESP2 PUB/SUB Limitations\n\nWhen using RESP2 (Redis Serialization Protocol 2), there are important limitations with PUB/SUB:\n\n- **No Re-Authentication in PUB/SUB Mode**: In RESP2, once a connection enters PUB/SUB mode, the socket is blocked and cannot process out-of-band commands like AUTH. This means that connections in PUB/SUB mode cannot be re-authenticated when tokens are refreshed.\n- **Connection Eviction**: As a result, PUB/SUB connections will be evicted by the Redis proxy when their tokens expire. The client will need to establish new connections with fresh tokens.\n\n### Transaction Safety\n\nWhen using token-based authentication, special care must be taken with Redis transactions. The token manager runs in the background and may attempt to re-authenticate connections at any time by sending AUTH commands. This can interfere with manually constructed transactions.\n\n#### ✅ Recommended: Use the Official Transaction API\n\nAlways use the official transaction API provided by the client:\n\n```typescript\n// Correct way to handle transactions\nconst multi = client.multi();\nmulti.set('key1', 'value1');\nmulti.set('key2', 'value2');\nawait multi.exec();\n```\n\n#### ❌ Avoid: Manual Transaction Construction\n\nDo not manually construct transactions by sending individual MULTI/EXEC commands:\n\n```typescript\n// Incorrect and potentially dangerous\nawait client.sendCommand(['MULTI']);\nawait client.sendCommand(['SET', 'key1', 'value1']);\nawait client.sendCommand(['SET', 'key2', 'value2']);\nawait client.sendCommand(['EXEC']); // Risk of AUTH command being injected before EXEC\n```\n\n## Error Handling\n\nThe provider includes built-in retry mechanisms for transient errors:\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForClientCredentials({\n  // ... other config ...\n  tokenManagerConfig: {\n    retry: {\n      maxAttempts: 3,\n      initialDelayMs: 100,\n      maxDelayMs: 1000,\n      backoffMultiplier: 2\n    }\n  }\n});\n```\n","packages/json/README.md":"# @redis/json\n\nThis package provides support for the [RedisJSON](https://redis.io/docs/latest/develop/data-types/json/) module, which adds JSON as a native data type to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisJSON module installed.\n\n## Usage\n\nFor a complete example, see [`managing-json.js`](https://github.com/redis/node-redis/blob/master/examples/managing-json.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n\n### Storing JSON Documents in Redis\n\nThe [`JSON.SET`](https://redis.io/commands/json.set/) command stores a JSON value at a given JSON Path in a Redis key.\n\nHere, we'll store a JSON document in the root of the Redis key \"`mydoc`\":\n\n```javascript\nawait client.json.set('noderedis:jsondata', '$', {\n  name: 'Roberta McDonald',\n  pets: [{\n    name: 'Rex',\n    species: 'dog',\n    age: 3,\n    isMammal: true\n  }, {\n    name: 'Goldie',\n    species: 'fish',\n    age: 2,\n    isMammal: false\n  }]\n});\n```\n\nFor more information about RedisJSON's path syntax, [check out the documentation](https://redis.io/docs/latest/develop/data-types/json/path).\n\n### Retrieving JSON Documents from Redis\n\nWith RedisJSON, we can retrieve all or part(s) of a JSON document using the [`JSON.GET`](https://redis.io/commands/json.get/) command and one or more JSON Paths. Let's get the name and age of one of the pets:\n\n```javascript\nconst results = await client.json.get('noderedis:jsondata', {\n  path: [\n    '.pets[1].name',\n    '.pets[1].age'\n  ]\n});\n```\n\n`results` will contain the following:\n\n```javascript\n { '.pets[1].name': 'Goldie', '.pets[1].age': 2 }\n```\n\n### Performing Atomic Updates on JSON Documents Stored in Redis\n\nRedisJSON includes commands that can atomically update values in a JSON document, in place in Redis without having to first retrieve the entire document.\n\nUsing the [`JSON.NUMINCRBY`](https://redis.io/commands/json.numincrby/) command, we can update the age of one of the pets like this:\n\n```javascript\nawait client.json.numIncrBy('noderedis:jsondata', '.pets[1].age', 1);\n```\n\nAnd we can add a new object to the pets array with the [`JSON.ARRAPPEND`](https://redis.io/commands/json.arrappend/) command:\n\n```javascript\nawait client.json.arrAppend('noderedis:jsondata', '.pets', {\n  name: 'Robin',\n  species: 'bird',\n  age: 1,\n  isMammal: false\n});\n```\n","packages/redis/README.md":"# Node-Redis\n\n[![Tests](https://img.shields.io/github/actions/workflow/status/redis/node-redis/tests.yml?branch=master)](https://github.com/redis/node-redis/actions/workflows/tests.yml)\n[![Coverage](https://codecov.io/gh/redis/node-redis/branch/master/graph/badge.svg?token=xcfqHhJC37)](https://codecov.io/gh/redis/node-redis)\n[![License](https://img.shields.io/github/license/redis/node-redis.svg)](https://github.com/redis/node-redis/blob/master/LICENSE)\n\n[![Discord](https://img.shields.io/discord/697882427875393627.svg?style=social&logo=discord)](https://discord.gg/redis)\n[![Twitch](https://img.shields.io/twitch/status/redisinc?style=social)](https://www.twitch.tv/redisinc)\n[![YouTube](https://img.shields.io/youtube/channel/views/UCD78lHSwYqMlyetR0_P4Vig?style=social)](https://www.youtube.com/redisinc)\n[![Twitter](https://img.shields.io/twitter/follow/redisinc?style=social)](https://twitter.com/redisinc)\n\nnode-redis is a modern, high performance [Redis](https://redis.io) client for Node.js.\n\n## How do I Redis?\n\n[Learn for free at Redis University](https://university.redis.com/)\n\n[Build faster with the Redis Launchpad](https://launchpad.redis.com/)\n\n[Try the Redis Cloud](https://redis.com/try-free/)\n\n[Dive in developer tutorials](https://developer.redis.com/)\n\n[Join the Redis community](https://redis.com/community/)\n\n[Work at Redis](https://redis.com/company/careers/jobs/)\n\n## Installation\n\nStart a redis via docker:\n\n```bash\ndocker run -p 6379:6379 -d redis:8.0-rc1\n```\n\nTo install node-redis, simply:\n\n```bash\nnpm install redis\n```\n> \"redis\" is the \"whole in one\" package that includes all the other packages. If you only need a subset of the commands,\n> you can install the individual packages. See the list below.\n\n## Packages\n\n| Name                                           | Description                                                                                 |\n| ---------------------------------------------- | ------------------------------------------------------------------------------------------- |\n| [`redis`](https://github.com/redis/node-redis/tree/master/packages/redis)                    | The client with all the [\"redis-stack\"](https://github.com/redis-stack/redis-stack) modules |\n| [`@redis/client`](https://github.com/redis/node-redis/tree/master/packages/client)           | The base clients (i.e `RedisClient`, `RedisCluster`, etc.)                                  |\n| [`@redis/bloom`](https://github.com/redis/node-redis/tree/master/packages/bloom)             | [Redis Bloom](https://redis.io/docs/data-types/probabilistic/) commands                     |\n| [`@redis/json`](https://github.com/redis/node-redis/tree/master/packages/json)               | [Redis JSON](https://redis.io/docs/data-types/json/) commands                               |\n| [`@redis/search`](https://github.com/redis/node-redis/tree/master/packages/search)           | [RediSearch](https://redis.io/docs/interact/search-and-query/) commands                     |\n| [`@redis/time-series`](https://github.com/redis/node-redis/tree/master/packages/time-series) | [Redis Time-Series](https://redis.io/docs/data-types/timeseries/) commands                  |\n| [`@redis/entraid`](https://github.com/redis/node-redis/tree/master/packages/entraid)         | Secure token-based authentication for Redis clients using Microsoft Entra ID                |\n\n> Looking for a high-level library to handle object mapping?\n> See [redis-om-node](https://github.com/redis/redis-om-node)!\n\n\n## Usage\n\n### Basic Example\n\n```typescript\nimport { createClient } from \"redis\";\n\nconst client = await createClient()\n  .on(\"error\", (err) => console.log(\"Redis Client Error\", err))\n  .connect();\n\nawait client.set(\"key\", \"value\");\nconst value = await client.get(\"key\");\nclient.destroy();\n```\n\nThe above code connects to localhost on port 6379. To connect to a different host or port, use a connection string in\nthe format `redis[s]://[[username][:password]@][host][:port][/db-number]`:\n\n```typescript\ncreateClient({\n  url: \"redis://alice:foobared@awesome.redis.server:6380\",\n});\n```\n\nYou can also use discrete parameters, UNIX sockets, and even TLS to connect. Details can be found in\nthe [client configuration guide](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md).\n\nTo check if the the client is connected and ready to send commands, use `client.isReady` which returns a boolean.\n`client.isOpen` is also available. This returns `true` when the client's underlying socket is open, and `false` when it\nisn't (for example when the client is still connecting or reconnecting after a network error).\n\n### Redis Commands\n\nThere is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed\nusing the raw Redis command names (`HSET`, `HGETALL`, etc.) and a friendlier camel-cased version (`hSet`, `hGetAll`,\netc.):\n\n```typescript\n// raw Redis commands\nawait client.HSET(\"key\", \"field\", \"value\");\nawait client.HGETALL(\"key\");\n\n// friendly JavaScript commands\nawait client.hSet(\"key\", \"field\", \"value\");\nawait client.hGetAll(\"key\");\n```\n\nModifiers to commands are specified using a JavaScript object:\n\n```typescript\nawait client.set(\"key\", \"value\", {\n  EX: 10,\n  NX: true,\n});\n```\n\nCommands that take several values of the same kind, such as `DEL`, `HDEL` or `LPUSH`, accept either a single value or\nan array:\n\n```typescript\nawait client.hDel(\"key\", \"field\");\nawait client.hDel(\"key\", [\"field1\", \"field2\"]);\n```\n\nDo not spread the values into separate arguments (`client.hDel(\"key\", ...fields)`). Only the first value is sent, and\nthe rest are silently ignored.\n\nReplies will be transformed into useful data structures:\n\n```typescript\nawait client.hGetAll(\"key\"); // { field1: 'value1', field2: 'value2' }\nawait client.hVals(\"key\"); // ['value1', 'value2']\n```\n\n`Buffer`s are supported as well:\n\n```typescript\nconst client = createClient().withTypeMapping({\n  [RESP_TYPES.BLOB_STRING]: Buffer\n});\n\nawait client.hSet(\"key\", \"field\", Buffer.from(\"value\")); // 'OK'\nawait client.hGet(\"key\", \"field\"); // { field: <Buffer 76 61 6c 75 65> }\n\n```\n\nFor commands that return serialized binary payloads, such as `DUMP`, map blob strings to `Buffer` before using the result with commands like `RESTORE`:\n\n```typescript\nconst binaryClient = createClient().withTypeMapping({\n  [RESP_TYPES.BLOB_STRING]: Buffer\n});\n\nconst dump = await binaryClient.dump(\"source\");\nawait binaryClient.restore(\"destination\", 0, dump);\n```\n\n### Unsupported Redis Commands\n\nIf you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use `.sendCommand()`:\n\n```typescript\nawait client.sendCommand([\"SET\", \"key\", \"value\", \"NX\"]); // 'OK'\n\nawait client.sendCommand([\"HGETALL\", \"key\"]); // ['key1', 'field1', 'key2', 'field2']\n```\n\n### Transactions (Multi/Exec)\n\nStart a [transaction](https://redis.io/topics/transactions) by calling `.multi()`, then chaining your commands. When\nyou're done, call `.exec()` and you'll get an array back with your results:\n\n```typescript\nawait client.set(\"another-key\", \"another-value\");\n\nconst [setKeyReply, otherKeyValue] = await client\n  .multi()\n  .set(\"key\", \"value\")\n  .get(\"another-key\")\n  .exec(); // ['OK', 'another-value']\n```\n\nYou can also [watch](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set) keys by calling\n`.watch()`. Your transaction will abort if any of the watched keys change.\n\n\n### Blocking Commands\n\nIn v4, `RedisClient` had the ability to create a pool of connections using an \"Isolation Pool\" on top of the \"main\"\nconnection. However, there was no way to use the pool without a \"main\" connection:\n\n```javascript\nconst client = await createClient()\n  .on(\"error\", (err) => console.error(err))\n  .connect();\n\nawait client.ping(client.commandOptions({ isolated: true }));\n```\n\nIn v5 we've extracted this pool logic into its own class—`RedisClientPool`:\n\n```javascript\nconst pool = await createClientPool()\n  .on(\"error\", (err) => console.error(err))\n  .connect();\n\nawait pool.ping();\n```\n\n\n### Pub/Sub\n\nSee the [Pub/Sub overview](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md).\n\n### Scan Iterator\n\n[`SCAN`](https://redis.io/commands/scan) results can be looped over\nusing [async iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator):\n\n```typescript\nfor await (const key of client.scanIterator()) {\n  // use the key!\n  await client.get(key);\n}\n```\n\nThis works with `HSCAN`, `SSCAN`, and `ZSCAN` too:\n\n```typescript\nfor await (const { field, value } of client.hScanIterator(\"hash\")) {\n}\nfor await (const member of client.sScanIterator(\"set\")) {\n}\nfor await (const { score, value } of client.zScanIterator(\"sorted-set\")) {\n}\n```\n\nYou can override the default options by providing a configuration object:\n\n```typescript\nclient.scanIterator({\n  TYPE: \"string\", // `SCAN` only\n  MATCH: \"patter*\",\n  COUNT: 100,\n});\n```\n\n### Disconnecting\n\nThe `QUIT` command has been deprecated in Redis 7.2 and should now also be considered deprecated in Node-Redis. Instead\nof sending a `QUIT` command to the server, the client can simply close the network connection.\n\n`client.QUIT/quit()` is replaced by `client.close()`. and, to avoid confusion, `client.disconnect()` has been renamed to\n`client.destroy()`.\n\n```typescript\nclient.destroy();\n```\n### Client Side Caching\n\n[Client Side Caching](https://redis.io/docs/manual/client-side-caching/) keeps command replies in a local cache. The Redis server notifies the client when cached replies are no longer valid. Requires RESP3.\n\n```typescript\nconst client = createClient({\n  clientSideCache: {\n    ttl: 0,                // Time-to-live (0 = no expiration)\n    maxEntries: 0,         // Maximum entries (0 = unlimited)\n    evictPolicy: \"LRU\",    // Eviction policy: \"LRU\" or \"FIFO\"\n    trackingMode: \"optin\"  // \"plain\" (default), \"optin\", or \"optout\"\n  }\n});\nawait client.connect();\n\nawait client.withCommandOptions({ cache: true }).get(\"user:42\"); // cached\nawait client.get(\"counter:hits\");                                // not cached in \"optin\" mode\n```\n\nSee [Client Side Caching](https://github.com/redis/node-redis/blob/master/docs/client-side-caching.md) for tracking modes, choosing what is cached, and more.\n\n### Auto-Pipelining\n\nNode Redis will automatically pipeline requests that are made during the same \"tick\".\n\n```typescript\nclient.set(\"Tm9kZSBSZWRpcw==\", \"users:1\");\nclient.sAdd(\"users:1:tokens\", \"Tm9kZSBSZWRpcw==\");\n```\n\nOf course, if you don't do something with your Promises you're certain to\nget [unhandled Promise exceptions](https://nodejs.org/api/process.html#process_event_unhandledrejection). To take\nadvantage of auto-pipelining and handle your Promises, use `Promise.all()`.\n\n```typescript\nawait Promise.all([\n  client.set(\"Tm9kZSBSZWRpcw==\", \"users:1\"),\n  client.sAdd(\"users:1:tokens\", \"Tm9kZSBSZWRpcw==\"),\n]);\n```\n\n### Programmability\n\nSee the [Programmability overview](https://github.com/redis/node-redis/blob/master/docs/programmability.md).\n\n### Clustering\n\nCheck out the [Clustering Guide](https://github.com/redis/node-redis/blob/master/docs/clustering.md) when using Node Redis to connect to a Redis Cluster.\n\n### Events\n\nThe Node Redis client class is an Nodejs EventEmitter and it emits an event each time the network status changes:\n\n| Name                    | When                                                                               | Listener arguments                                        |\n| ----------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------- |\n| `connect`               | Initiating a connection to the server                                              | _No arguments_                                            |\n| `ready`                 | Client is ready to use                                                             | _No arguments_                                            |\n| `end`                   | Connection has been closed (via `.disconnect()`)                                   | _No arguments_                                            |\n| `error`                 | An error has occurred—usually a network issue such as \"Socket closed unexpectedly\" | `(error: Error)`                                          |\n| `reconnecting`          | Client is trying to reconnect to the server                                        | _No arguments_                                            |\n| `terminated`             | Reconnection has stopped because `reconnectStrategy` returned `false` or an `Error`; emitted before the companion `error` | `(cause: Error)`                                           |\n| `sharded-channel-moved` | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event)                          | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event) |\n\n> :warning: You **MUST** listen to `error` events. If a client doesn't have at least one `error` listener registered and\n> an `error` occurs, that error will be thrown and the Node.js process will exit. See the [ > `EventEmitter` docs](https://nodejs.org/api/events.html#events_error_events) for more details.\n\n> The client will not emit [any other events](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md#all-the-removed-events) beyond those listed above.\n\n## Supported Redis versions\n\nNode Redis is supported with the following versions of Redis:\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 8.0.z   | :heavy_check_mark: |\n| 7.4.z   | :heavy_check_mark: |\n| 7.2.z   | :heavy_check_mark: |\n| < 7.2   | :x:                |\n\n> Node Redis should work with older versions of Redis, but it is not fully tested and we cannot offer support.\n\n## Migration\n\n- [From V3 to V4](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md)\n- [From V4 to V5](https://github.com/redis/node-redis/blob/master/docs/v4-to-v5.md)\n- [V5](https://github.com/redis/node-redis/blob/master/docs/v5.md)\n\n## Contributing\n\nIf you'd like to contribute, check out the [contributing guide](https://github.com/redis/node-redis/blob/master/CONTRIBUTING.md).\n\nThank you to all the people who already contributed to Node Redis!\n\n[![Contributors](https://contrib.rocks/image?repo=redis/node-redis)](https://github.com/redis/node-redis/graphs/contributors)\n\n## License\n\nThis repository is licensed under the \"MIT\" license. See [LICENSE](https://github.com/redis/node-redis/blob/master/LICENSE).\n","packages/search/README.md":"# @redis/search\n\nThis package provides support for the [RediSearch](https://redis.io/docs/interact/search-and-query/) module, which adds indexing and querying support for data stored in Redis Hashes or as JSON documents with the [RedisJSON](https://redis.io/docs/data-types/json/) module.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RediSearch module installed. To index and query JSON documents, you'll also need to add the RedisJSON module.\n\n## Usage\n\nFor complete examples, see [`search-hashes.js`](https://github.com/redis/node-redis/blob/master/examples/search-hashes.js) and [`search-json.js`](https://github.com/redis/node-redis/blob/master/examples/search-json.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n\n### Indexing and Querying Data in Redis Hashes\n\n#### Creating an Index\n\nBefore we can perform any searches, we need to tell RediSearch how to index our data, and which Redis keys to find that data in.  The [FT.CREATE](https://redis.io/commands/ft.create) command creates a RediSearch index. Here's how to use it to create an index we'll call `idx:animals` where we want to index hashes containing `name`, `species` and `age` fields, and whose key names in Redis begin with the prefix `noderedis:animals`:\n\n```javascript\nawait client.ft.create('idx:animals', {\n  name: {\n    type: SCHEMA_FIELD_TYPE.TEXT,\n    SORTABLE: true\n  },\n  species: SCHEMA_FIELD_TYPE.TAG,\n  age: SCHEMA_FIELD_TYPE.NUMERIC\n}, {\n  ON: 'HASH',\n  PREFIX: 'noderedis:animals'\n});\n```\n\nSee the [`FT.CREATE` documentation](https://redis.io/commands/ft.create/#description) for information about the different field types and additional options.\n\n#### Indexing a Field Multiple Times\n\nYou can index the same field multiple times with different types or aliases by using an array:\n\n```javascript\nawait client.ft.create('idx:products', {\n  sku: [\n    { type: SCHEMA_FIELD_TYPE.TEXT, AS: 'sku_text' },\n    { type: SCHEMA_FIELD_TYPE.TAG, AS: 'sku_tag', SORTABLE: true }\n  ]\n}, {\n  ON: 'HASH',\n  PREFIX: 'product:'\n});\n```\n\nThis allows querying the same field using different search strategies.\n\n#### Querying the Index\n\nOnce we've created an index, and added some data to Redis hashes whose keys begin with the prefix `noderedis:animals`, we can start writing some search queries.  RediSearch supports a rich query syntax for full-text search, faceted search, aggregation and more.  Check out the [`FT.SEARCH` documentation](https://redis.io/commands/ft.search) and the [query syntax reference](https://redis.io/docs/interact/search-and-query/query) for more information.\n\nLet's write a query to find all the animals where the `species` field has the value `dog`:\n\n```javascript\nconst results = await client.ft.search('idx:animals', '@species:{dog}');\n```\n\n`results` looks like this:\n\n```javascript\n{\n  total: 2,\n  documents: [\n    {\n      id: 'noderedis:animals:4',\n      value: {\n        name: 'Fido',\n        species: 'dog',\n        age: '7'\n      }\n    },\n    {\n      id: 'noderedis:animals:3',\n      value: {\n        name: 'Rover',\n        species: 'dog',\n        age: '9'\n      }\n    }\n  ]\n}\n```\n\n### Indexing and Querying Data with RedisJSON\n\nRediSearch can also index and query JSON documents stored in Redis using the RedisJSON module.  The approach is similar to that for indexing and searching data in hashes, but we can now use JSON Path like syntax and the data no longer has to be flat name/value pairs - it can contain nested objects and arrays.\n\n#### Creating an Index\n\nAs before, we create an index with the `FT.CREATE` command, this time specifying we want to index JSON documents that look like this:\n\n```javascript\n{\n  name: 'Alice',\n  age: 32,\n  coins: 100\n}\n```\n\nEach document represents a user in some system, and users have name, age and coins properties.\n\nOne way we might choose to index these documents is as follows:\n\n```javascript\nawait client.ft.create('idx:users', {\n  '$.name': {\n    type: SCHEMA_FIELD_TYPE.TEXT,\n    SORTABLE: 'UNF'\n  },\n  '$.age': {\n    type: SCHEMA_FIELD_TYPE.NUMERIC,\n    AS: 'age'\n  },\n  '$.coins': {\n    type: SCHEMA_FIELD_TYPE.NUMERIC,\n    AS: 'coins'\n  }\n}, {\n  ON: 'JSON',\n  PREFIX: 'noderedis:users'\n});\n```\n\nNote that we're using JSON Path to specify where the fields to index are in our JSON documents, and the `AS` clause to define a name/alias for each field.  We'll use these when writing queries.\n\n#### Querying the Index\n\nNow we have an index and some data stored as JSON documents in Redis (see the [JSON package documentation](https://github.com/redis/node-redis/tree/master/packages/json) for examples of how to store JSON), we can write some queries...\n\nWe'll use the [RediSearch query language](https://redis.io/docs/interact/search-and-query/query) and [`FT.SEARCH`](https://redis.io/commands/ft.search) command.  Here's a query to find users under the age of 30:\n\n```javascript\nawait client.ft.search('idx:users', '@age:[0 30]');\n```\n","packages/time-series/README.md":"# @redis/time-series\n\nThis package provides support for the [RedisTimeSeries](https://redis.io/docs/data-types/timeseries/) module, which adds a time series data structure to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisTimeSeries module installed.\n\n## Usage\n\nFor a complete example, see [`time-series.js`](https://github.com/redis/node-redis/blob/master/examples/time-series.js) in the Node Redis examples folder.\n\n### Creating Time Series data structure in Redis\n\nThe [`TS.CREATE`](https://oss.redis.com/redistimeseries/commands/#tscreate) command creates a new time series.\n\nHere, we'll create a new time series \"`temperature`\":\n\n```javascript\n\nimport { createClient } from 'redis';\nimport { TimeSeriesDuplicatePolicies, TimeSeriesEncoding, TIME_SERIES_AGGREGATION_TYPE } from '@redis/time-series';\n\n...\nconst created = await client.ts.create('temperature', {\n  RETENTION: 86400000, // 1 day in milliseconds\n  ENCODING: TimeSeriesEncoding.UNCOMPRESSED, // No compression - When not specified, the option is set to COMPRESSED\n  DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.BLOCK, // No duplicates - When not specified: set to the global DUPLICATE_POLICY configuration of the database (which by default, is BLOCK).\n});\n\nif (created === 'OK') {\n  console.log('Created timeseries.');\n} else {\n  console.log('Error creating timeseries :(');\n  process.exit(1);\n}\n```\n\n### Adding new value to a Time Series data structure in Redis\n\nWith RedisTimeSeries, we can add a single value to time series data structure using the [`TS.ADD`](https://redis.io/commands/ts.add/) command and if we would like to add multiple values we can use the [`TS.MADD`](https://redis.io/commands/ts.madd/) command.\n\n```javascript\n\nlet value = Math.floor(Math.random() * 1000) + 1; // Random data point value\nlet currentTimestamp = 1640995200000; // Jan 1 2022 00:00:00\nlet num = 0;\n\nwhile (num < 10000) {\n  // Add a new value to the timeseries, providing our own timestamp:\n  // https://redis.io/commands/ts.add/\n  await client.ts.add('temperature', currentTimestamp, value);\n  console.log(`Added timestamp ${currentTimestamp}, value ${value}.`);\n\n  num += 1;\n  value = Math.floor(Math.random() * 1000) + 1; // Get another random value\n  currentTimestamp += 1000; // Move on one second.\n}\n\n// Add multiple values to the timeseries in round trip to the server:\n// https://redis.io/commands/ts.madd/\nconst response = await client.ts.mAdd([{\n  key: 'temperature',\n  timestamp: currentTimestamp + 60000,\n  value: Math.floor(Math.random() * 1000) + 1\n}, {\n  key: 'temperature',\n  timestamp: currentTimestamp + 120000,\n  value: Math.floor(Math.random() * 1000) + 1\n}]);\n```\n\n### Retrieving Time Series data from Redis\n\nWith RedisTimeSeries, we can retrieve the time series data using the [`TS.RANGE`](https://redis.io/commands/ts.range/) command by passing the criteria as follows:\n\n```javascript\n// Query the timeseries with TS.RANGE:\n// https://redis.io/commands/ts.range/\nconst fromTimestamp = 1640995200000; // Jan 1 2022 00:00:00\nconst toTimestamp = 1640995260000; // Jan 1 2022 00:01:00\nconst rangeResponse = await client.ts.range('temperature', fromTimestamp, toTimestamp, {\n  // Group into 10 second averages.\n  AGGREGATION: {\n    type: TIME_SERIES_AGGREGATION_TYPE.AVG,\n    timeBucket: 10000\n  }\n});\n\nconsole.log('RANGE RESPONSE:');\n// rangeResponse looks like:\n// [\n//   { timestamp: 1640995200000, value: 356.8 },\n//   { timestamp: 1640995210000, value: 534.8 },\n//   { timestamp: 1640995220000, value: 481.3 },\n//   { timestamp: 1640995230000, value: 437 },\n//   { timestamp: 1640995240000, value: 507.3 },\n//   { timestamp: 1640995250000, value: 581.2 },\n//   { timestamp: 1640995260000, value: 600 }\n// ]\n```\n\nFor multiple aggregations in one command, use the dedicated `*MultiAggr` methods:\n\n```javascript\nconst multiRangeResponse = await client.ts.rangeMultiAggr('temperature', fromTimestamp, toTimestamp, {\n  AGGREGATION: {\n    types: [\n      TIME_SERIES_AGGREGATION_TYPE.MIN,\n      TIME_SERIES_AGGREGATION_TYPE.MAX,\n      TIME_SERIES_AGGREGATION_TYPE.AVG\n    ],\n    timeBucket: 10000\n  }\n});\n\n// multiRangeResponse looks like:\n// [\n//   { timestamp: 1640995200000, values: [120, 580, 356.8] },\n//   ...\n// ]\n```\n\nEquivalent multi-aggregation helpers are also available for reverse and multi-key variants:\n`revRangeMultiAggr`, `mRangeMultiAggr`, `mRevRangeMultiAggr`, `mRangeWithLabelsMultiAggr`,\n`mRevRangeWithLabelsMultiAggr`, `mRangeSelectedLabelsMultiAggr`, and `mRevRangeSelectedLabelsMultiAggr`.\n\n### Altering Time Series data Stored in Redis\n\nRedisTimeSeries includes commands that can update values in a time series data structure.\n\nUsing the [`TS.ALTER`](https://redis.io/commands/ts.alter/) command, we can update time series retention like this:\n\n```javascript\n// https://redis.io/commands/ts.alter/\nconst alterResponse = await client.ts.alter('temperature', {\n  RETENTION: 0 // Keep the entries forever\n});\n```\n\n### Retrieving Information about the timeseries Stored in Redis\n\nRedisTimeSeries also includes commands that can help to view the information on the state of a time series.\n\nUsing the [`TS.INFO`](https://redis.io/commands/ts.info/) command, we can view timeseries information like this:\n\n```javascript\n// Get some information about the state of the timeseries.\n// https://redis.io/commands/ts.info/\nconst tsInfo = await client.ts.info('temperature');\n\n// tsInfo looks like this:\n// {\n//   totalSamples: 1440,\n//   memoryUsage: 28904,\n//   firstTimestamp: 1641508920000,\n//   lastTimestamp: 1641595320000,\n//   retentionTime: 86400000,\n//   chunkCount: 7,\n//   chunkSize: 4096,\n//   chunkType: 'uncompressed',\n//   duplicatePolicy: 'block',\n//   labels: [],\n//   sourceKey: null,\n//   rules: []\n// }\n```\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nGuidance for agents working in this repo.\n\n## What this is\n\n`node-redis` — modern Redis client for Node.js. A **monorepo** (npm workspaces) of\npublishable packages. Requires Node `>= 20`. TypeScript throughout.\n\n## Packages (`packages/`)\n\n| Package | Purpose |\n| --- | --- |\n| `redis` | \"All-in-one\" meta-package re-exporting client + all modules |\n| `client` (`@redis/client`) | Core: `RedisClient`, `RedisCluster`, `RedisSentinel`, pool, RESP codec, command framework |\n| `bloom` (`@redis/bloom`) | Probabilistic commands (bloom, cuckoo, count-min, t-digest, top-k) |\n| `json` (`@redis/json`) | RedisJSON commands |\n| `search` (`@redis/search`) | RediSearch commands |\n| `time-series` (`@redis/time-series`) | Time-series commands |\n| `entraid` (`@redis/entraid`) | Microsoft Entra ID token auth |\n| `test-utils` (`@redis/test-utils`) | Shared test harness; spins up Redis via docker |\n\nModule packages depend on `@redis/client` and follow the same command structure.\n\n## Core layout — `packages/client/lib/`\n\n- `client/` — connection internals: `index.ts` (RedisClient), `socket.ts`, `commands-queue.ts`, `parser.ts`, `pool.ts`, `pub-sub.ts`, `cache.ts`\n- `cluster/`, `sentinel/` — cluster & sentinel clients\n- `commands/` — one file per Redis command (e.g. `GET.ts`) + `index.ts` registry\n- `RESP/` — RESP2/RESP3 protocol: `encoder.ts`, `decoder.ts`, `types.ts`\n- `authx/` — auth/credential providers\n\n## Command pattern\n\nEach command is `<NAME>.ts` exporting a `Command` object:\n\n```typescript\nexport default {\n  CACHEABLE: true,\n  IS_READ_ONLY: true,\n  parseCommand(parser: CommandParser, key: RedisArgument) {\n    parser.push('GET');\n    parser.pushKey(key);\n  },\n  transformReply: undefined as unknown as () => BlobStringReply | NullReply\n} as const satisfies Command;\n```\n\n- `parseCommand` builds wire args via `CommandParser` (`push`, `pushKey`, ...).\n- `transformReply` maps reply to JS type; `undefined` = pass-through. Can be keyed by RESP version `{ 2: ..., 3: ... }`.\n- Register new command in the package's `commands/index.ts` (import + map entry). RESP3 is default — no extra RESP3 test needed for new commands.\n- JSDoc on commands is checked: `npm run check:command-jsdoc`.\n\n## Tests\n\n- Co-located `<NAME>.spec.ts` next to source. Mocha + `tsx`, `node:assert`.\n- `testUtils.testAll(name, fn, { client, cluster })` runs same test across server + cluster topologies (see `test-utils.ts`, `GLOBAL`).\n- **Docker required** — test-utils starts real Redis containers.\n- Pure arg/reply tests use `parseArgs(COMMAND, ...args)`.\n\nCommands:\n- `npm test` — full suite, all workspaces (runs `cleanup` first).\n- `npm test -w @redis/client` — one package.\n- Single file from root: `npm run test-single -- <path-to-spec>`.\n- `npm run build` — `tsc --build` (project references; build before cross-package work).\n- Build can break on stale `dist/` from project references. Clean rebuild:\n  ```bash\n  find packages -type d -name \"dist\" -exec rm -rf {} + && npm run build\n  ```\n- `npm run lint` — lint changed files only; `npm run lint:all` for everything.\n\n## Conventions\n\n- TypeScript strict mode; `noUnusedLocals` on. Target ES2022 / NodeNext modules.\n- Raw command names (`HSET`) and camelCase aliases (`hSet`) both exposed.\n- Conventional Commits. Per-package releases via `release-it` (`npm run release`).\n- Keep company-internal refs (Jira/Confluence IDs, internal links) out of OSS commits, branches, PRs, code.\n\n## Docs\n\nDeep-dive guides in `docs/` (client-configuration, clustering, sentinel, pool, RESP, transactions, programmability, pub-sub, scan-iterators, migration guides). Runnable examples in `examples/`, `doctests/`.\n","isInternal":false,"tokens":986,"sizeBytes":3676},{"name":"SKILL.md","path":".agents/skills/bump-test-image/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/bump-test-image/SKILL.md","title":"bump-test-image","category":"anthropic-skill","format":"markdown","content":"---\nname: bump-test-image\ndescription: Bump the default Redis docker test image (redislabs/client-libs-test) in the shared DEFAULT_DOCKER_CONFIG and the CI matrix, then force-push the bump-test-image branch and open a PR against upstream. User-invoked only.\ndisable-model-invocation: true\nargument-hint: <image-tag> [redis-version]\n---\n\n# Bump default docker test image\n\nUpdates the default `redislabs/client-libs-test` docker tag used by tests (a single shared config in `@redis/test-utils`), adjusts the CI matrix, commits on a freshly-reset `bump-test-image` branch, force-pushes it, and opens (or updates) a PR against `redis/node-redis` master.\n\n## Inputs\n\n- **tag** (required): the new docker tag, e.g. `8.8.0`, `8.8-rc1`, `custom-28772936538-debian`, `unstable-24425681442-debian`. If given as `redislabs/client-libs-test:<tag>`, strip the image name.\n- **version** (sometimes required): the Redis version the tag represents, e.g. `8.10`.\n  - Derivable when the tag is version-shaped: `8.8.0` → `8.8`, `8.8-rc1` → `8.8`, `8.8-m03` → `8.8`.\n  - NOT derivable for `custom-*` / `unstable-*` tags — if missing, ask the user which Redis version the tag maps to before doing anything else.\n\nIf no tag was given at all, ask for it and stop.\n\n## Step 1 — Sync master from upstream\n\nRemotes: `upstream` = `redis/node-redis` (canonical), `origin` = the user's fork.\n\n```bash\ngit fetch upstream\ngit rev-parse --abbrev-ref HEAD   # remember current branch\n```\n\nUpdate local master to upstream:\n\n- If currently on master: `git reset --hard upstream/master` (only if working tree is clean — if dirty, stop and tell the user).\n- Otherwise: `git fetch upstream master:master` (fails safely if master has local-only commits; if it fails, stop and report).\n\n## Step 2 — Reset the work branch\n\n```bash\ngit checkout -B bump-test-image master\n```\n\n`-B` creates or hard-resets the branch to latest master. Any previous bump commits on it are intentionally discarded (the branch is recycled for each bump; force-push later matches this).\n\n## Step 3 — Edit the shared default config\n\nThe default image config lives in exactly ONE place:\n\n`packages/test-utils/lib/index.ts` → `DEFAULT_DOCKER_CONFIG`:\n\n```typescript\nexport const DEFAULT_DOCKER_CONFIG: TestUtilsConfig = {\n  dockerImageName: 'redislabs/client-libs-test',\n  dockerImageTagArgument: 'redis-tag',\n  dockerImageVersionArgument: 'redis-version',\n  defaultDockerVersion: { tag: '<old-tag>', version: '<old-version>' }\n};\n```\n\nNote the old tag/version, then replace them with the new values on the `defaultDockerVersion` line. Nothing else in the file changes.\n\nIf `DEFAULT_DOCKER_CONFIG` does not exist in that file (e.g. master predates the dedupe refactor), STOP and tell the user — do not fall back to editing per-package test-utils files without confirmation.\n\n## Step 4 — Update the CI matrix\n\nFile: `.github/workflows/tests.yml`, `matrix` → `redis` entries of the form:\n\n```yaml\n          - tag: \"8.8.0\"\n            version: \"8.8\"\n```\n\nTwo cases, decided by comparing the new `version` with the old default version from Step 3:\n\n1. **New Redis version** (e.g. default moves 8.8 → 8.10): **add** a new `- tag/version` entry at the end of the matrix list, keeping the previous entry so CI retains coverage of the prior version.\n2. **Same Redis version** (rc → GA, or a newer custom build of the same version): **replace** the `tag` of the existing entry for that version; the `version` line stays.\n\n## Step 5 — Verify\n\n```bash\ngrep -rn \"defaultDockerVersion:\" packages --include=\"*.ts\" | grep -v dist | grep -v node_modules\n```\n\nExactly one config line (in `packages/test-utils/lib/index.ts`) must show the new tag/version; any other hits are the type definition / JSDoc examples, not real defaults. Also confirm `git diff --stat` touches exactly 2 files: `packages/test-utils/lib/index.ts` + `.github/workflows/tests.yml`.\n\n## Step 6 — Commit\n\nFill in this template exactly (Conventional Commits):\n\n```\nchore(tests): bump default docker test image to <X>\n\nBump the default Redis test image from <old-tag> to <new-tag> (Redis\n<version>) in DEFAULT_DOCKER_CONFIG, and <matrix-action> the CI matrix.\n```\n\n- `<X>`: the human-meaningful version when the tag is version-shaped (`8.10`, `8.8.0`, `8.8-rc1`, `8.8-m03`); the full tag for `unstable-*` builds; the version for `custom-*` tags (the actual tag already appears in the body as `<new-tag>`).\n- `<matrix-action>`: either `add an <version> entry to` (keeping prior-version coverage) or `update the <version> entry in`, matching what Step 4 did.\n\nExample:\n\n```\nchore(tests): bump default docker test image to 8.10\n\nBump the default Redis test image from 8.8.0 to custom-28772936538-debian\n(Redis 8.10) in DEFAULT_DOCKER_CONFIG, and add an 8.10 entry to the CI\nmatrix.\n```\n\nKeep any company-internal references (Jira/Confluence IDs, internal links) out of the commit and PR.\n\n## Step 7 — Force-push and PR\n\n```bash\ngit push -f origin bump-test-image\n```\n\nThen check for an existing open PR before creating one:\n\n```bash\ngh pr list --repo redis/node-redis --head <fork-owner>:bump-test-image --state open\n```\n\n(`<fork-owner>` = GitHub owner from the `origin` remote URL.)\n\n- If a PR exists, the force-push already updated it — just report the URL. Update its title/body via `gh pr edit` to match the new tag.\n- Otherwise create one:\n\n```bash\ngh pr create --repo redis/node-redis --base master --head <fork-owner>:bump-test-image \\\n  --title \"<commit subject>\" \\\n  --body \"<commit body>\"\n```\n\nFinish by reporting: old → new tag/version, matrix action taken (added vs replaced entry), and the PR URL.\n","frontmatter":{"name":"bump-test-image","description":"Bump the default Redis docker test image (redislabs/client-libs-test) in the shared DEFAULT_DOCKER_CONFIG and the CI matrix, then force-push the bump-test-image branch and open a PR against upstream. User-invoked only.","disable-model-invocation":true,"argument-hint":"<image-tag> [redis-version]","disableModelInvocation":true},"isInternal":false,"tokens":1481,"sizeBytes":5630},{"name":"openai.yaml","path":".agents/skills/docs-sync/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/docs-sync/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: \"Docs Sync\"\n  short_description: \"Audit docs coverage and propose targeted updates\"\n  default_prompt: \"Use $docs-sync to audit the current branch against docs/ and propose targeted documentation updates.\"\n","isInternal":false,"tokens":46,"sizeBytes":232},{"name":"doc-coverage-checklist.md","path":".agents/skills/docs-sync/references/doc-coverage-checklist.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/docs-sync/references/doc-coverage-checklist.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Doc Coverage Checklist\n\nUse this checklist to scan the selected scope (master = comprehensive, or current-branch diff) and validate documentation coverage.\n\n## Feature inventory targets\n\n- Public exports: clients (`RedisClient`, `RedisCluster`, `RedisSentinel`), pool, factory functions, types, and module entry points.\n- Configuration options: `*Options` types, default config objects (`defaults.ts`), and connection-string options.\n- New or changed commands under `packages/*/lib/commands` (names, args, reply shape, RESP2/RESP3 differences).\n- User-facing behaviors: reconnection, timeouts, pub/sub, pipelining/transactions, scan iterators, errors, caching, telemetry, and data handling.\n- Deprecations, removals, or renamed settings (track against migration guides `docs/v*-to-v*.md`).\n\n## Doc-first pass (page-by-page)\n\n- Review each relevant page under `docs/`, the top-level `README.md`, and each `packages/*/README.md`.\n- Look for missing opt-in flags or customization options that the page implies.\n- Add new features that belong on that page based on user intent and navigation.\n\n## Code-first pass (feature inventory)\n\n- Map features to the closest existing page based on package or feature area.\n- Prefer updating existing pages over creating new ones unless the topic is clearly new.\n- Use conceptual pages for cross-cutting concerns (auth, errors, RESP, clustering, pub/sub).\n- Keep quick-start flows minimal; move advanced details into deeper pages.\n\n## Evidence capture\n\n- Record the master-branch file path and symbol/setting name.\n- Note defaults or behavior-critical details for accuracy checks.\n- Avoid large code dumps; a short identifier is enough.\n\n## Red flags for outdated or incorrect docs\n\n- Option names/types no longer exist or differ from code.\n- Default values or allowed ranges do not match implementation.\n- Features removed in code but still documented.\n- New behaviors introduced without corresponding docs updates.\n\n## When to propose structural changes\n\n- A page mixes unrelated audiences (quick-start + deep reference) without clear separation.\n- Multiple pages duplicate the same concept without cross-links.\n- New feature areas have no obvious home in the docs structure.\n\n## Diff mode guidance (current branch vs master)\n\n- Focus only on changed behavior: new exports/options, modified defaults, removed features, or renamed settings.\n- Use `git diff master...HEAD` (or equivalent) to constrain analysis.\n- Document removals explicitly so docs can be pruned if needed.\n\n## Patch guidance\n\n- Keep edits scoped and aligned to existing tone and format.\n- Update cross-links when moving or renaming sections.\n- Place any runnable snippets under `examples/` or `doctests/`.\n","isInternal":false,"tokens":558,"sizeBytes":2712},{"name":"SKILL.md","path":".agents/skills/docs-sync/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/docs-sync/SKILL.md","title":"docs-sync","category":"anthropic-skill","format":"markdown","content":"---\nname: docs-sync\ndescription: Analyze master branch implementation and configuration to find missing, incorrect, or outdated documentation in docs/, README.md, and per-package READMEs. Use when asked to audit doc coverage, sync docs with code, or propose doc updates/structure changes. Provide a report and ask for approval before editing docs.\n---\n\n# Docs Sync\n\n## Overview\n\nIdentify doc coverage gaps and inaccuracies by comparing master branch features and configuration options against the current docs, then propose targeted improvements.\n\n## Workflow\n\n1. Confirm scope and base branch\n   - Identify the current branch and default branch (`master`).\n   - Prefer analyzing the current branch to keep work aligned with in-flight changes.\n   - If the current branch is not `master`, analyze only the diff vs `master` to scope doc updates.\n   - Avoid switching branches if it would disrupt local changes; use `git show master:<path>` or `git worktree add` when needed.\n\n2. Build a feature inventory from the selected scope\n   - If on `master`: inventory the full surface area and review docs comprehensively.\n   - If not on `master`: inventory only changes vs `master` (feature additions/changes/removals).\n   - Focus on user-facing behavior: public exports, client/cluster/sentinel/pool config options, new or changed commands, connection-string options, default values, and documented runtime behaviors.\n   - Capture evidence for each item (file path + symbol/setting).\n   - Use targeted search to find option types and feature flags (for example: `rg \"Options\"`, `rg \"interface .*Options\"`, `rg \"export\"` under `packages/*/lib`).\n   - For Redis command behavior or server-side semantics, treat the source code (`parseCommand`/`transformReply` in `packages/*/lib/commands`) as the source of truth; consult [redis.io/commands](https://redis.io/commands) only to confirm server semantics when discrepancies appear.\n\n3. Doc-first pass: review existing pages\n   - Walk each relevant page under `docs/`, the top-level `README.md`, and each `packages/*/README.md`.\n   - Identify missing mentions of important, supported options (opt-in flags, config), customization points, or new features from `packages/`.\n   - Propose additions where users would reasonably expect to find them on that page.\n\n4. Code-first pass: map features to docs\n   - Review the current docs layout under `docs/` (e.g. `client-configuration.md`, `clustering.md`, `sentinel.md`, `pool.md`, `RESP.md`, `transactions.md`, `programmability.md`, `pub-sub.md`, `scan-iterators.md`, `command-options.md`).\n   - Determine the best page/section for each feature based on existing patterns and package boundaries.\n   - Identify features that lack any doc page or have a page but no corresponding content.\n   - Note when a structural adjustment would improve discoverability.\n\n5. Detect gaps and inaccuracies\n   - **Missing**: features/configs present in master but absent in docs.\n   - **Incorrect/outdated**: names, defaults, or behaviors that diverge from master.\n   - **Structural issues** (optional): pages overloaded, missing overviews, or mis-grouped topics.\n\n6. Produce a Docs Sync Report and ask for approval\n   - Provide a clear report with evidence, suggested doc locations, and proposed edits.\n   - Ask the user whether to proceed with doc updates.\n\n7. If approved, apply changes\n   - Edit docs under `docs/`, `README.md`, and the relevant `packages/*/README.md`.\n   - Keep changes aligned with the existing docs style and navigation.\n   - Place any runnable code snippets under `examples/` or `doctests/`, mirroring existing patterns.\n   - Verify any snippet you add still compiles with `npm run build` and fix issues before handoff.\n\n## Output format\n\nUse this template when reporting findings:\n\nDocs Sync Report\n\n- Doc-first findings\n  - Page + missing content → evidence + suggested insertion point\n- Code-first gaps\n  - Feature + evidence → suggested doc page/section (or missing page)\n- Incorrect or outdated docs\n  - Doc file + issue + correct info + evidence\n- Structural suggestions (optional)\n  - Proposed change + rationale\n- Proposed edits\n  - Doc file → concise change summary\n- Questions for the user\n\n## References\n\n- `references/doc-coverage-checklist.md`\n","frontmatter":{"name":"docs-sync","description":"Analyze master branch implementation and configuration to find missing, incorrect, or outdated documentation in docs/, README.md, and per-package READMEs. Use when asked to audit doc coverage, sync docs with code, or propose doc updates/structure changes. Provide a report and ask for approval before editing docs."},"isInternal":false,"tokens":929,"sizeBytes":4252},{"name":"openai.yaml","path":".agents/skills/implement-command/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/implement-command/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: \"Implement Command\"\n  short_description: \"Add a new Redis command to node-redis end-to-end\"\n  default_prompt: \"Use $implement-command to add a new Redis command to node-redis: write the <NAME>.ts Command file (parseCommand + transformReply), register it with JSDoc in the package commands/index.ts (raw name + camelCase alias), and add a co-located <NAME>.spec.ts with parseArgs and testUtils.testAll tests, then build, run check:command-jsdoc, the spec, and lint.\"\n","isInternal":false,"tokens":120,"sizeBytes":493},{"name":"SKILL.md","path":".agents/skills/implement-command/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/implement-command/SKILL.md","title":"implement-command","category":"anthropic-skill","format":"markdown","content":"---\nname: implement-command\ndescription: Add a new Redis command (or command variant) to node-redis end-to-end — the `<NAME>.ts` Command file, its registration with JSDoc in the package `commands/index.ts`, and a co-located `<NAME>.spec.ts` with arg + behavior tests. Use when asked to implement, add, or wire up a Redis command in the client or a module package (json/search/bloom/time-series).\n---\n\n# Implement a node-redis Command\n\n## Overview\n\nA command in node-redis is a single `Command` object exported from\n`packages/<pkg>/lib/commands/<NAME>.ts`. It declares how to serialize arguments\nonto the wire (`parseCommand`) and how to map the RESP reply to a JS value\n(`transformReply`). It becomes callable on clients only after it is registered\nin the package's `commands/index.ts`. Both the raw name and a camelCase alias\nare exposed (`HSET` and `hSet`).\n\nThis skill is for `@redis/client` core commands and for module packages\n(`@redis/json`, `@redis/search`, `@redis/bloom`, `@redis/time-series`). It does\nnot cover RESP codec changes or new client transports.\n\nBefore writing, **read 2-3 existing commands with a similar shape** (simple\nkey read, key + options, variadic, RESP2/3-divergent reply) and mirror them.\nConsult [redis.io/commands](https://redis.io/commands) for argument order and\nreply type, but treat existing `parseCommand`/`transformReply` files as the\nsource of truth for repo conventions.\n\n## File layout\n\n| Package | Command dir | Import paths | Wire name prefix |\n| --- | --- | --- | --- |\n| `client` | `packages/client/lib/commands/` | `../client/parser`, `../RESP/types`, `./generic-transformers` | none (`GET`) |\n| `json` | `packages/json/lib/commands/` | `@redis/client/dist/lib/...` | `JSON.` (`JSON.ARRAPPEND`) |\n| `search` | `packages/search/lib/commands/` | `@redis/client/dist/lib/...` | `FT.` |\n| `bloom` | `packages/bloom/lib/commands/<family>/` | `@redis/client/dist/lib/...` | e.g. `BF.`, `CF.`, `TOPK.` |\n| `time-series` | `packages/time-series/lib/commands/` | `@redis/client/dist/lib/...` | `TS.` |\n\n**Naming**: file name = raw wire name with subcommand `_` separators\n(`ACL_CAT`, `CONFIG_GET`, `CLUSTER_FORGET`). A distinct reply variant gets its\nown file (`HRANDFIELD_COUNT_WITHVALUES`). Module commands drop the dotted\nprefix from the file name (`ARRAPPEND.ts` → wire `JSON.ARRAPPEND`).\n\n## Step 0 — Gather inputs (ask the user first)\n\nNew commands are often implemented **before they are publicly released**, so\nredis.io may not document them yet and a default `redis:latest` may not have\nthem. Before writing any code, ask the user for three things:\n\n1. **The command spec.** Ask for the redis/redis JSON spec file — one per\n   command under\n   [`src/commands/<name>.json`](https://github.com/redis/redis/tree/unstable/src/commands)\n   (subcommands use `-`, e.g. `client-info.json`, `acl-cat.json`). If the\n   command is unreleased, ask the user to paste the spec from their branch.\n   As a fallback on a live server: `redis-cli --json COMMAND DOCS <name>` and\n   `COMMAND INFO <name>`.\n2. **A running Redis instance that has the command.** This is the single most\n   useful input — ask for it explicitly and up front. Ask for connection\n   details (host/port, TLS, auth, module loaded). Use it to explore real\n   behavior and confirm the implementation matches the spec — **do not rely on\n   the spec alone**. Probe every argument branch and diff the real reply against\n   your `transformReply`. A quick `redis-cli` session or a throwaway probe\n   script (`packages/client` is already wired for `tsx`) is enough; never commit\n   the probe.\n3. **The first server version that ships the command.** Ask which Redis (or\n   module) version introduced it — the spec's `since` field is the answer when\n   present; otherwise ask the user directly. You need this to (a) write `@since`\n   in the JSDoc (Step 2) and (b) gate the behavior tests with\n   `minimumDockerVersion` (Step 3) so they don't run — and fail — on older\n   servers in CI.\n\nIf the user cannot provide a spec, derive arguments/reply from\n[redis.io/commands](https://redis.io/commands) but flag that it is unverified.\nIf they cannot provide a live instance, implement from the spec but state that\nruntime behavior was not confirmed. If the introducing version is unknown, say\nso and leave `@since`/`minimumDockerVersion` out rather than guessing.\n\n### Reading the spec JSON → mapping to a Command\n\nThe redis/redis spec drives every part of the `Command` object. Example\n(`getex.json`, trimmed):\n\n```json\n{\n  \"GETEX\": {\n    \"since\": \"6.2.0\", \"arity\": -2,\n    \"command_flags\": [\"WRITE\", \"FAST\"],\n    \"key_specs\": [{ \"begin_search\": { \"index\": { \"pos\": 1 } }, \"flags\": [\"RW\", \"UPDATE\"] }],\n    \"arguments\": [\n      { \"name\": \"key\", \"type\": \"key\", \"key_spec_index\": 0 },\n      { \"name\": \"expiration\", \"type\": \"oneof\", \"optional\": true, \"arguments\": [\n        { \"name\": \"seconds\", \"type\": \"integer\", \"token\": \"EX\" },\n        { \"name\": \"persist\", \"type\": \"pure-token\", \"token\": \"PERSIST\" }\n      ]}\n    ],\n    \"reply_schema\": { \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"null\" } ] }\n  }\n}\n```\n\n| Spec field | Drives |\n| --- | --- |\n| `command_flags` contains `READONLY` (and **not** `WRITE`) | `IS_READ_ONLY: true`. `WRITE` → omit it. Pure read with no side effects → also `CACHEABLE: true`. For keyed commands the generated metadata table already implies these defaults, so module packages omit the flags; keyless reads must set `IS_READ_ONLY: true` explicitly (Step 4). |\n| `key_specs` empty / no `key`-type args | `NOT_KEYED_COMMAND: true`. |\n| `arguments[].type: \"key\"` | `parser.pushKey(...)` (one per key, in spec order). |\n| `type: \"pure-token\"` + `token` | a literal flag pushed only when its option is set (`parser.push('PERSIST')`). |\n| `token` + value type (`integer`/`string`/...) | push the token then the stringified value (`parser.push('EX', seconds.toString())`). |\n| `type: \"oneof\"` | mutually exclusive branch → `if/else if` in `parseCommand`; model as a union/`options` field. |\n| `optional: true` | goes in the `options` object (exported `interface`); guard with `if (options?.x)`. |\n| `multiple: true` | variadic → `parser.pushVariadic*`. |\n| `arity` | sanity-check arg count in `parseArgs` tests. |\n| `reply_schema` (JSON Schema) | the `transformReply` return type. `oneOf [string, null]` → `BlobStringReply \\| NullReply`; `integer` → `NumberReply`; `array` → `ArrayReply<...>`; a map differing by RESP version → keyed `transformReply: { 2, 3 }`. |\n| `since` | the introducing server version → `@since` in the registry JSDoc (Step 2) **and** `minimumDockerVersion: [major, minor]` on the behavior tests (Step 3). |\n\nAfter implementing, run the command against the live instance and diff the real\nreply against `reply_schema` and your `transformReply` output.\n\n## Step 1 — Write `<NAME>.ts`\n\nMinimal pass-through command (`packages/client/lib/commands/GET.ts`):\n\n```typescript\nimport { CommandParser } from '../client/parser';\nimport { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types';\n\nexport default {\n  CACHEABLE: true,\n  IS_READ_ONLY: true,\n  parseCommand(parser: CommandParser, key: RedisArgument) {\n    parser.push('GET');\n    parser.pushKey(key);\n  },\n  transformReply: undefined as unknown as () => BlobStringReply | NullReply\n} as const satisfies Command;\n```\n\n`as const satisfies Command` is mandatory — it preserves the literal arg types\nfor the public API while type-checking the shape.\n\n### Command flags (all optional)\n\n- `IS_READ_ONLY: true` — read command; routable to replicas. Set for reads, omit/`false` for writes.\n- `CACHEABLE: true` — eligible for client-side caching. Only for pure reads with no side effects.\n- `NOT_KEYED_COMMAND: true` — command takes no key (server/connection level, e.g. `PING`, `CONFIG_GET`).\n- `IS_FORWARD_COMMAND` — internal; do not set on new commands.\n\n### `parseCommand` — serialize args via `CommandParser`\n\nFirst arg is always `parser`. Push the wire name first, then args in order.\nUse the parser helpers — **do not** hand-build arrays:\n\n- `push(...args)` — raw args (the command token, flags, stringified numbers).\n- `pushKey(key)` — a key. Registers it for cluster slot routing. Use for **every** key, never `push` a key.\n- `pushKeys(keys)` / `pushKeysLength(keys)` — multiple keys; the `Length` variant prefixes the count.\n- `pushVariadic(vals)` — a `RedisVariadicArgument` (one value or array) as flat args.\n- `pushVariadicWithLength(vals)` — same, prefixed with the count (e.g. `FIELDS <n> ...`).\n- `pushVariadicNumber(vals)` — number or array of numbers, stringified.\n\nNumbers are not auto-stringified by `push` — call `.toString()`. Optional\ntrailing args go in an `options` object; export its `interface` (see\n`SET.ts`'s `SetOptions`). Encode keyword flags conditionally:\n\n```typescript\nparseCommand(parser: CommandParser, key: RedisArgument, value: RedisArgument, options?: SetOptions) {\n  parser.push('SET');\n  parser.pushKey(key);\n  parser.push(value);\n  if (options?.condition) parser.push(options.condition); // 'NX' | 'XX'\n}\n```\n\n### `transformReply` — map RESP reply to JS\n\n- **Pass-through** (reply already the right shape): `transformReply: undefined as unknown as () => <ReplyType>`.\n- **Function**: `(reply: <RawType>) => <JsType>`. Use `UnwrapReply<...>` to read the raw RESP container.\n- **RESP-version keyed**: `{ 2: (reply) => ..., 3: (reply) => ... }` when RESP2 and RESP3 shapes differ (e.g. flat array vs map/tuple). See `HRANDFIELD_COUNT_WITHVALUES.ts`.\n\n#### Unify RESP2 onto the RESP3 shape\n\nWhen the server returns different shapes per protocol, the library exposes **one\nreturn type to callers**: the **RESP3 shape is the source of truth**, and the\nRESP2 reply is transformed to look like it. So the keyed form is almost always:\n\n- `3:` — **pass-through** (`undefined as unknown as () => <ReplyType>`), because\n  RESP3 already has the target shape (map, tuple, big-number, double, ...).\n- `2:` — a **function** that reshapes the flat/legacy RESP2 reply into that same\n  `<ReplyType>`. Type its input `UnwrapReply<Resp2Reply<ReplyType>>` so the raw\n  RESP2 container is visible while the output type still matches RESP3.\n\nCanonical example — `HELLO.ts` turns the RESP2 flat array (`[k, v, k, v, ...]`)\ninto the RESP3 map, while RESP3 passes through:\n\n```typescript\ntransformReply: {\n  2: (reply: UnwrapReply<Resp2Reply<HelloReply>>) => ({\n    server: reply[1], version: reply[3], proto: reply[5], /* ... */\n  }),\n  3: undefined as unknown as () => HelloReply\n}\n```\n\nReuse shared transformers where one exists (`HGETALL.ts` uses\n`transformTuplesReply` for `2:`, map pass-through for `3:`). Only when RESP3\nstill needs reshaping does `3:` get its own function too. Verify the actual\nper-protocol shapes against the live instance (Step 0) — connect once with\n`RESP: 2` and once with `RESP: 3` and diff.\n\nReply types live in `RESP/types`: `BlobStringReply`, `SimpleStringReply<'OK'>`,\n`NumberReply`, `DoubleReply`, `NullReply`, `BooleanReply`, `ArrayReply<T>`,\n`TuplesReply<[...]>`, `MapReply`, `UnwrapReply`.\n\n**RESP3 is the default.** No separate RESP3 test is needed for a new command;\nthe default test setup already exercises RESP3.\n\n### Type-mapping precision caveats\n\n- A `BLOB_STRING` reply cannot be remapped to `Number` via type mapping; only RESP3 `DOUBLE`/`BIG_NUMBER` are precision-risky.\n- If a `NumberReply` can exceed `Number.MAX_SAFE_INTEGER` (2^53-1), add a `@remarks` line to the JSDoc (Step 2) telling users to do `client.withTypeMapping({ [RESP_TYPES.NUMBER]: String })`. See the `ARGREP` entries in the client index for the exact wording.\n\n### Module package commands\n\nImport from the published client subpath, prefix the wire name, and reuse\nshared transformers (`packages/json/lib/commands/ARRAPPEND.ts`):\n\n```typescript\nimport { CommandParser } from '@redis/client/dist/lib/client/parser';\nimport { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';\n\nexport default {\n  IS_READ_ONLY: false,\n  parseCommand(parser, key, path, value) {\n    parser.push('JSON.ARRAPPEND');\n    parser.pushKey(key);\n    parser.push(path, /* transform */ value);\n  },\n  transformReply: undefined as unknown as () => NumberReply\n} as const satisfies Command;\n```\n\n## Step 2 — Register in `commands/index.ts`\n\n`import` the command, then add it to the default-export map **twice**: the raw\nname (shorthand) and a camelCase alias. **Every entry MUST have a JSDoc block\ndirectly above it** — `npm run check:command-jsdoc` fails on any registry entry\nwithout an attached JSDoc comment (no blank-line gap allowed).\n\n```typescript\nimport GET from './GET';\n// ...\nexport default {\n  /**\n   * Returns the value of a key, or null if the key does not exist\n   * @param key - Key to read\n   * @since 1.0.0\n   */\n  GET,\n  /**\n   * Returns the value of a key, or null if the key does not exist\n   * @param key - Key to read\n   * @since 1.0.0\n   */\n  get: GET,\n} satisfies RedisCommands;\n```\n\nKeep both JSDoc blocks (raw + alias) in sync. Document every `parseCommand`\nparam after `parser` with `@param`. Add `@since <version>` with the introducing\nserver version from Step 0 (the spec's `since`); omit it only if that version is\nunknown. Add `@remarks` for the precision caveat above when relevant. For module\npackages the registry files are\n`packages/<pkg>/lib/commands/index.ts` (bloom: per-family `.../<family>/index.ts`).\n\n## Step 3 — Write `<NAME>.spec.ts` (co-located)\n\nTwo layers: arg serialization (no server) + behavior (real server, server +\ncluster topologies). Mirror `GET.spec.ts`:\n\n```typescript\nimport { strict as assert } from 'node:assert';\nimport testUtils, { GLOBAL } from '../test-utils';\nimport { parseArgs } from './generic-transformers';\nimport GET from './GET';\n\ndescribe('GET', () => {\n  it('transformArguments', () => {\n    assert.deepEqual(parseArgs(GET, 'key'), ['GET', 'key']);\n  });\n\n  testUtils.testAll('get', async client => {\n    assert.equal(await client.get('key'), null);\n  }, {\n    client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 8] },\n    cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 8] }\n  });\n});\n```\n\n- `parseArgs(COMMAND, ...args)` asserts the exact wire array — cover each option/flag branch and variadic shapes. Arg tests need no server, so **never gate them** by version.\n- `testUtils.testAll(name, fn, { client, cluster })` runs the same body against a standalone server and a cluster. Use it so cluster key routing (`pushKey`) is exercised. Drop `cluster` only when the command is genuinely cluster-incompatible.\n- **Gate every behavior test by the introducing version** (Step 0). Spread `minimumDockerVersion: [major, minor]` into **both** the `client` and `cluster` options (as above). CI runs multiple server versions; without the gate the test runs on older servers that lack the command and fails. `[8, 8]` = \"8.8 and newer\". Apply the same to `testWithClient`/`testWithCluster` by spreading it into their single options object. Omit only if the version is genuinely unknown.\n- Pick the right `GLOBAL.SERVERS.*` / `GLOBAL.CLUSTERS.*` setup (see `test-utils.ts`); `OPEN` is the default.\n- **Docker is required** — test-utils starts real Redis containers.\n\n## Step 4 — Regenerate static command metadata\n\nCluster/sentinel routing (replica-safety, keyedness, CSC eligibility) is\nresolved from a generated table:\n`packages/client/lib/command-metadata/command-metadata-data.ts`. The file is\nauto-generated — **never edit it manually**. The table lives in\n`@redis/client`, but the `COMMAND` dump includes module commands (`ft`,\n`json`, `bf`, `ts`, ...), so a new command in **any** package needs a\nregenerate:\n\n```bash\nnpm run generate:metadata --workspace=packages/client -- redis://localhost:6379\n```\n\nThe script rebuilds the **entire** table from a single live server's `COMMAND`\nreply and overwrites the file — entries the server doesn't report are silently\ndropped. Run it only against a server with **all** bundled modules loaded and a\ncurrent core command set (e.g. the CI image `redislabs/client-libs-test` or a\nfull Redis 8.8+ build); reuse the Step 0 instance only if it meets that bar — a\nserver with just the new command's module would wipe every other module's\nmetadata. The script then applies the curation in\n`packages/client/scripts/command-metadata-overrides.ts`: hand-curated excludes\n(internal, deprecated and cluster-admin commands) plus per-command routing\noverrides. After regenerating, verify the new command has an entry with the\nexpected flags, **and** check `git diff` on `command-metadata-data.ts`: it must\ncontain only the intended additions/changes. Deletions of other modules' or\ncore entries mean the source server was incomplete — revert and rerun against\na full build.\n\nHow the table and the command object interact (override-first — see\n`lib/command-metadata/predicates.ts`):\n\n- The table sets **defaults only**, for both replica routing (cluster and\n  sentinel) and client-side caching eligibility. A keyed entry without the\n  `write` flag is already replica-safe, so a keyed read command with a correct\n  table entry needs no `IS_READ_ONLY` on the command object — bloom and json\n  omit it everywhere.\n- `IS_READ_ONLY`/`CACHEABLE` on the command object **win over the table**. Set\n  them as deliberate corrections, not to restate the table. The main case is\n  keyless reads, which default to master routing: `PING`/`INFO` in the client,\n  and module reads whose args are not keys — search and time-series set\n  `IS_READ_ONLY: true` on exactly their keyless commands (`FT.SEARCH` takes an\n  index name, `TS.MGET`/`TS.MRANGE` take filters, ...).\n- Table-shape fixes (wrong, missing or excluded entries, routing policies)\n  belong in `command-metadata-overrides.ts`; value intent (`IS_READ_ONLY`,\n  `CACHEABLE`) belongs in the command definition, never in the overrides file.\n\n## Step 5 — Build, verify, lint\n\n```bash\nnpm run build                                   # tsc --build (project references)\nnpm run check:command-jsdoc                     # registry JSDoc gate\nnpm run test-single -- packages/<pkg>/lib/commands/<NAME>.spec.ts\nnpm run lint                                     # changed files\n```\n\nIf the build fails on stale `dist/` from project references:\n\n```bash\nfind packages -type d -name \"dist\" -exec rm -rf {} + && npm run build\n```\n\nFor module packages, build the client first (or whole repo) — they import from\n`@redis/client/dist`.\n\n## Completion checklist\n\n- [ ] Asked the user for spec, a live instance with the command, and the introducing server version (Step 0); probed real behavior against the live instance.\n- [ ] `<NAME>.ts` created with `parseCommand` + `transformReply`, `as const satisfies Command`.\n- [ ] Flags set correctly (`IS_READ_ONLY` for reads, `CACHEABLE` only for side-effect-free reads, `NOT_KEYED_COMMAND` if no key; omit flags the metadata table already implies — Step 4).\n- [ ] Every key uses `pushKey`/`pushKeys`; numbers stringified; options behind an exported `interface`.\n- [ ] RESP2/3 divergence handled via keyed `transformReply`: RESP3 is the target shape (usually `3:` pass-through), RESP2 transformed to match it; both shapes verified against the live instance.\n- [ ] Registered in `commands/index.ts`: import + raw entry + camelCase alias, **each with JSDoc** (`@param` per arg; `@since` for the introducing version; `@remarks` for >2^53 precision).\n- [ ] Static command metadata regenerated (`npm run generate:metadata`) against a server with **all** bundled modules; the new command's entry verified and the diff contains no dropped entries (Step 4).\n- [ ] `<NAME>.spec.ts`: `parseArgs` covers all branches; `testUtils.testAll` covers server + cluster; behavior tests gated with `minimumDockerVersion` on both `client` and `cluster`.\n- [ ] `npm run build`, `npm run check:command-jsdoc`, the spec, and `npm run lint` all pass.\n- [ ] Commit message uses Conventional Commits; no company-internal refs.\n```\n","frontmatter":{"name":"implement-command","description":"Add a new Redis command (or command variant) to node-redis end-to-end — the `<NAME>.ts` Command file, its registration with JSDoc in the package `commands/index.ts`, and a co-located `<NAME>.spec.ts` with arg + behavior tests. Use when asked to implement, add, or wire up a Redis command in the client or a module package (json/search/bloom/time-series)."},"isInternal":false,"tokens":5159,"sizeBytes":20022},{"name":"openai.yaml","path":".agents/skills/pr-draft-summary/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/pr-draft-summary/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: \"PR Draft Summary\"\n  short_description: \"Draft the repo-ready PR title and description\"\n  default_prompt: \"Use $pr-draft-summary to generate the PR-ready summary block, title, and draft description for the current changes.\"\n","isInternal":false,"tokens":54,"sizeBytes":251},{"name":"SKILL.md","path":".agents/skills/pr-draft-summary/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/pr-draft-summary/SKILL.md","title":"pr-draft-summary","category":"anthropic-skill","format":"markdown","content":"---\nname: pr-draft-summary\ndescription: Create the required PR-ready summary block, branch suggestion, title, and draft description for node-redis. Must be used before the final response whenever the actual task diff includes runtime code, tests, examples, build/test configuration, or docs with behavior impact, regardless of perceived change size. Skip only when no eligible files changed, every change is repo-meta or docs-only without behavior impact, the task is conversation-only, or the user explicitly opts out.\n---\n\n# PR Draft Summary\n\n## Purpose\n\nProduce a PR-ready summary after eligible work is complete: a concise change summary plus a PR-ready title and draft description for node-redis.\n\n## Close-out Gate\n\n1. Inspect the actual task diff before sending the final response.\n2. Run this skill when the diff includes runtime code, tests, examples, build/test configuration, or docs with behavior impact. Do not use perceived change size to skip an eligible change.\n3. Run it after any required verification and before sending the \"work complete\" response.\n4. Skip only when no eligible files changed, every change is repo-meta or docs-only without behavior impact, the task is conversation-only, or the user explicitly opts out.\n\n## Inputs to Collect Automatically (do not ask the user)\n\n- Current branch: `git rev-parse --abbrev-ref HEAD`.\n- Working tree: `git status -sb`.\n- Untracked files: `git ls-files --others --exclude-standard` (use with `git status -sb`; `--stat` omits them).\n- Changed files: `git diff --name-only` (unstaged) and `git diff --name-only --cached` (staged); sizes via `git diff --stat` and `git diff --stat --cached`.\n- Pull request base reference (use `origin/master`; never use a feature branch's upstream as the PR base):\n  - `BASE_REF=origin/master`; if it does not exist locally, use `master`.\n  - `BASE_COMMIT=$(git merge-base \"$BASE_REF\" HEAD)`.\n- Committed branch diff: `git diff --name-only \"${BASE_COMMIT}..HEAD\"` and `git diff --stat \"${BASE_COMMIT}..HEAD\"`.\n- Commits ahead of the base fork point: `git log --oneline --no-merges ${BASE_COMMIT}..HEAD`.\n- Category signals for this repo: runtime (`packages/*/lib`, `examples/`, `doctests/`, `benchmark/`, `scripts/`), tests (co-located `packages/**/*.spec.ts`), docs (`docs/`, `README.md`, `packages/*/README.md`, `AGENTS.md`, `.github/`), build/test config (`package.json`, `package-lock.json`, `tsconfig*.json`, `eslint.config.mjs`, `.mocharc*`, `mocha-multi-reporter-config.json`, `.nycrc*`).\n\n## Workflow\n\n1. Run the commands above without asking the user; compute `BASE_REF`/`BASE_COMMIT` first so later commands reuse them. Compare against `origin/master` or `master`, not the current branch's upstream.\n2. Combine the committed branch diff with staged, unstaged, and untracked changes. If the combined diff is empty, reply briefly that no code changes were detected and skip emitting the PR block.\n3. Infer change type from the touched paths listed under \"Category signals\"; classify as feature, fix, refactor, or docs-with-impact, and flag backward-compatibility risk only when the diff changes released public APIs, client/command behavior, connection config, or the RESP wire handling. Judge that risk against the latest release tag, not unreleased branch-only churn.\n4. Summarize changes in 1–3 short sentences using the top five paths and stats from the committed, staged, and unstaged diffs. Explicitly call out untracked files because `--stat` does not include them. Use commit messages as supporting context, not as a substitute for inspecting the committed diff.\n5. Choose the lead verb for the description: feature → `adds`, bug fix → `fixes`, refactor/perf → `improves` or `updates`, docs-only → `updates`.\n6. Suggest a branch name. If already off `master`, keep it; otherwise propose `feat/<slug>`, `fix/<slug>`, or `docs/<slug>` based on the primary area (for example `docs/pr-draft-summary-guidance`).\n7. If the current branch matches `issue-<number>` (digits only), keep that branch suggestion. When an issue number is present, reference `https://github.com/redis/node-redis/issues/<number>` and include an auto-closing line such as `This pull request resolves #<number>.` Do not block if the issue cannot be fetched.\n8. Draft the PR title and description using the template below. Use Conventional Commits prefixes for the title (`feat:`, `fix:`, `docs:`, `chore:`, etc.).\n9. Output only the block in \"Output Format\". Keep any surrounding status note minimal and in English. Do not include company-internal references (e.g. Jira/Confluence IDs) in the branch name, title, or description.\n\n## Output Format\n\nWhen closing out a task, add this concise Markdown block (English only) after any brief status note unless the task falls under the documented skip cases or the user says they do not want it.\n\n```\n# Pull Request Draft\n\n## Branch name suggestion\n\ngit checkout -b <kebab-case suggestion, e.g., feat/pr-draft-summary-skill>\n\n## Title\n\n<single-line imperative title, which can be a commit message; a Conventional Commits prefix such as feat:, fix:, or docs: is preferred>\n\n## Description\n\n<include what you changed plus a draft pull request title and description for your local changes; start the description with prose such as \"This pull request resolves/updates/adds ...\" using a verb that matches the change (you can use bullets later), explain the change background (for bugs, clearly describe the bug, symptoms, or repro; for features, what is needed and why), any behavior changes or considerations to be aware of, and you do not need to mention any tests you ran.>\n```\n\nKeep it tight—no redundant prose around the block, and avoid repeating details between `Changes` and the description. Tests do not need to be listed unless specifically requested.\n","frontmatter":{"name":"pr-draft-summary","description":"Create the required PR-ready summary block, branch suggestion, title, and draft description for node-redis. Must be used before the final response whenever the actual task diff includes runtime code, tests, examples, build/test configuration, or docs with behavior impact, regardless of perceived change size. Skip only when no eligible files changed, every change is repo-meta or docs-only without behavior impact, the task is conversation-only, or the user explicitly opts out."},"isInternal":false,"tokens":1332,"sizeBytes":5794},{"name":"openai.yaml","path":".agents/skills/runtime-behavior-probe/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/runtime-behavior-probe/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: \"Runtime Behavior Probe\"\n  short_description: \"Plan and run runtime behavior probes\"\n  default_prompt: \"Use $runtime-behavior-probe to investigate actual runtime behavior with a validation matrix, disposable TypeScript probes outside git-tracked paths, and a findings-first report. For node-redis, the default write scope is a temporary directory only. Do not modify examples/, packages/, package.json files, README.md, workspace config, or build config, and do not add checked-in examples, benchmarks, or package scripts unless the user explicitly asks for a reusable repository artifact.\"\npolicy:\n  allow_implicit_invocation: false\n","isInternal":false,"tokens":132,"sizeBytes":661},{"name":"error-cases.md","path":".agents/skills/runtime-behavior-probe/references/error-cases.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/runtime-behavior-probe/references/error-cases.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Common Error Cases\n\nUse this reference to expand beyond the happy path. Favor error cases that a real user or operator is likely to hit.\n\n## Configuration Errors\n\nCheck whether the runtime behaves differently for:\n\n- Missing required environment variables.\n- Present but malformed secrets or identifiers.\n- Wrong endpoint or base URL.\n- Wrong model or deployment name.\n- Incompatible local dependency versions.\n\nLook for:\n\n- Error type and status code.\n- Whether the failure is immediate or delayed.\n- Whether the message is actionable.\n- Whether retrying without fixing configuration changes anything.\n\n## Input Errors\n\nProbe common bad-input patterns such as:\n\n- Missing required fields.\n- Wrong data type.\n- Unsupported enum or option value.\n- Empty but syntactically valid input.\n- Oversized input or too many items.\n- Mutually incompatible options.\n\nPrefer realistic invalid inputs over artificial nonsense. The point is to learn how the runtime fails in practice.\n\n## Transport and Availability Errors\n\nWhen networked services are involved, consider:\n\n- Connection failure.\n- Read timeout.\n- Server timeout or upstream gateway error.\n- Rate limit response.\n- Partial stream interruption.\n- Reusing a connection after a failure.\n\nCapture whether the client library retries automatically, whether it surfaces retry metadata, and whether the final exception preserves the original cause.\n\n## State and Repetition Errors\n\nMany surprising bugs appear only when an operation is repeated or interrupted:\n\n- Re-submit the same request.\n- Repeat after a timeout.\n- Retry after a partial tool call or partial stream.\n- Resume after local cleanup or process restart.\n- Repeat with slightly changed inputs while reusing shared state.\n\nObserve whether the operation is idempotent, duplicated, silently ignored, or left in a partial state.\n\n## Concurrency Errors\n\nWhen shared state, ordering, or isolation may matter, consider:\n\n- Two overlapping requests with the same logical input.\n- Parallel runs that reuse the same cache key, session, container, or temporary resource.\n- Concurrent retries, cancellation, or cleanup racing with active work.\n- Output or event streams from one run leaking into another.\n\nCapture whether the runtime serializes, rejects, duplicates, corrupts, or cross-contaminates the work.\n\n## Investigation Heuristics\n\nUse these heuristics to pick error cases quickly:\n\n- Ask which failure a real engineer would debug first in production.\n- Ask which failure is most expensive if it is misunderstood.\n- Ask which failure would be invisible from code review alone.\n- Ask which failure path is likely to differ across environments.\n\nIf the error behavior is already perfectly obvious from a local validator or type system, it is usually low priority for this skill.\n","isInternal":false,"tokens":532,"sizeBytes":2781},{"name":"redis-runtime-patterns.md","path":".agents/skills/runtime-behavior-probe/references/redis-runtime-patterns.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/runtime-behavior-probe/references/redis-runtime-patterns.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Redis Runtime Patterns\n\nUse this reference for recurring node-redis investigations so you do not have to rediscover the probe strategy each time. Treat the repository source as the truth for client behavior, and use [redis.io/commands](https://redis.io/commands) to confirm contract-sensitive details such as argument order, reply types, and version availability. If you rely on the website rather than source, say so in the report.\n\n## General Rules\n\n- Prefer small, focused probes over large harnesses. Keep one script focused on one uncertainty.\n- For comparative or benchmark-like questions, start with a pilot and expand only when the answer is still unclear.\n- Capture both the request shape (command and arguments as sent) and the raw reply, before any client-side type mapping.\n- Preserve raw error type, message, and any Redis error prefix such as `WRONGTYPE`, `MOVED`, `ASK`, `CROSSSLOT`, `NOSCRIPT`, or `LOADING`.\n- Record whether behavior differs between the first call and a repeated call, and across a reconnect.\n- When the question is about regression or contract drift, add a known-good control run before attributing the result to the change under investigation.\n- Keep comparison parity explicit. Record what was held constant, what variable changed, and whether reply-shape or encoding differences could bias the conclusion.\n- Record the runtime context that actually changes Redis client behavior: server mode (standalone, cluster, sentinel), RESP version (2 vs 3), server version, and whether a feature such as client-side caching or sharded pub/sub is in use.\n\n## Connection Environment Variables\n\nDo not read these variables automatically. Before a live probe uses any of them, tell the user the exact variable names you plan to read and why each one is needed, then wait for explicit approval. Never print their values:\n\n- `REDIS_URL`\n- `REDIS_PASSWORD`\n- `REDIS_TLS_CA`, `REDIS_TLS_CERT`, `REDIS_TLS_KEY` (and any other `REDIS_TLS_*` the probe needs)\n\nFor most local probes you do not need any of these. The repository test harness `@redis/test-utils` can start Redis in Docker, so a disposable probe can target a local containerized server (for example `redis://localhost:6379`) without reading credentials. Prefer that over a live remote service unless the question is specifically about the remote deployment.\n\nIf the task targets another integration, use that integration's expected default variable names under the same rule.\n\n## Environment False Signals\n\nBefore attributing a failure to the patch or client code under review, exclude source-selection and environment problems with a controlled comparison.\n\n- Confirm the commit, worktree, working directory, Node executable/version, and imported module path. A probe of stale `dist`, another checkout, `/tmp/node_modules`, or an installed `redis` package does not prove current `lib` behavior.\n- Rebuild when emitted declarations, package exports, generated metadata, or `dist` is the subject. Otherwise prefer current-source `lib/` imports so stale build output cannot create a false result.\n- Confirm a Redis server is actually running and reachable. A connection refused or timeout against a missing Docker/Redis container is an environment condition, not a client defect, until a controlled rerun against a known-good server ties it to the code.\n- Confirm the RESP version the client negotiated. A reply-shape surprise is often just RESP2 vs RESP3, not a bug.\n- Confirm server mode matches the probe assumptions. Running a cluster-only expectation against a standalone server, or the reverse, produces misleading routing and slot errors.\n- Run base and head controls with the same Node version, dependency graph, server mode, RESP version, environment-variable names, and command shape. Record intentional differences.\n- Treat sandbox denials, unavailable Docker infrastructure, expired remote sessions, authentication, quota, rate limits, service outages, and stale caches as environment conditions until a controlled rerun ties them to the patch.\n- Never print credentials, connection URLs with embedded passwords, or TLS key material. Change only the minimum in-scope environment or disposable state needed for the control and record variable names rather than values.\n\nIn the final report, distinguish code failures, unsupported configurations, environment blockers, and inconclusive probes. Do not collapse them into one failed-test count.\n\n## Node-Redis Probe Patterns\n\nStart from the uncertainty instead of from the full client surface. The patterns below cover the runtime questions that recur most often in node-redis.\n\n### RESP2 vs RESP3 reply shapes and type mapping\n\nUse when the uncertainty is what a command returns at runtime and how the client maps it.\n\n- Run the same command under RESP2 and RESP3 as separate cases. Maps, sets, doubles, big numbers, verbatim strings, and booleans differ between the two protocols.\n- Capture the raw reply and the post-mapping JavaScript value. Note whether the client returns plain objects, `Map`, arrays, `null`, or typed numbers.\n- Watch precision-sensitive types. RESP3 `DOUBLE` and `BIG_NUMBER` can lose precision when mapped to `Number`; record the exact value.\n- If the probe configures a custom `typeMapping`, treat each mapping choice as part of `held_constant`.\n\n### Single-node vs cluster vs sentinel\n\nUse when key routing, slot ownership, or topology matters.\n\n- For cluster, capture which node served the command and whether a `MOVED` or `ASK` redirect occurred. Probe multi-key commands across keys in different slots to observe `CROSSSLOT` behavior and any client-side slot checks.\n- Use hash tags (`{tag}`) to force keys into the same slot when the probe needs a multi-key command to succeed.\n- For sentinel, capture master discovery and what happens on a simulated failover.\n- Keep mode constant within a case. A standalone control alongside a cluster case is a useful comparison, not a substitute.\n\n### Pub/sub and sharded pub/sub\n\nUse when delivery semantics or resubscription matter.\n\n- Capture subscribe confirmation, message delivery order, and whether messages are delivered to pattern (`PSUBSCRIBE`) and channel subscribers as expected.\n- For sharded pub/sub (`SSUBSCRIBE` and `SPUBLISH`) in cluster, capture which shard delivered the message.\n- Force a reconnect and observe whether the client resubscribes to channels and patterns automatically. This is a common drift point.\n\n### Reconnection, retry, and the offline command queue\n\nUse when the question is about resilience.\n\n- Simulate a dropped connection (stop or restart the container, or close the socket) and capture: whether commands issued while offline are queued, the order they flush in on reconnect, and whether the reconnect strategy backoff behaves as configured.\n- Distinguish commands rejected immediately from commands queued and later resolved or rejected.\n- Capture whether in-flight commands at disconnect are retried, rejected, or hang.\n\n### Pipelining and MULTI/EXEC transactions\n\nUse when batching or atomicity is under test.\n\n- For pipelining, confirm replies map back to the right commands in order, including when one command in the batch errors.\n- For `MULTI`/`EXEC`, capture queueing errors versus execution errors, behavior on `DISCARD`, and `WATCH` optimistic-locking aborts (`EXEC` returning `null`).\n\n### Client-side caching (RESP3 push invalidation)\n\nUse when caching correctness matters.\n\n- Requires RESP3. Capture whether invalidation push messages arrive after a tracked key is modified by another client, and whether the local cache entry is dropped.\n- Probe the broadcast and default tracking modes separately.\n\n### Blocking commands and connection isolation\n\nUse for `BLPOP`, `BRPOP`, `XREAD BLOCK`, `WAIT`, and similar.\n\n- Capture whether the blocking command monopolizes a connection and whether the client isolates it (for example via a dedicated connection) so other commands are not stalled.\n- Probe timeout behavior and what resolves when the block is satisfied by another client versus when it times out.\n\n### Pool behavior\n\nUse when a pooled client is involved.\n\n- Capture how commands are distributed across pool connections, behavior under saturation, and what happens when a pooled connection drops.\n- For commands that require connection affinity (transactions, blocking, subscriptions), confirm the pool keeps them on a single connection.\n\n### SCAN iterators\n\nUse for `SCAN`, `HSCAN`, `SSCAN`, and `ZSCAN`.\n\n- Capture cursor progression, whether the async iterator terminates, and whether `COUNT` and `MATCH` behave as expected. Note that SCAN gives no count or completeness guarantees mid-iteration.\n\n### Large payloads and timeouts\n\nUse when size or latency is the concern.\n\n- Capture behavior with large values and large reply sets, and whether command-level or socket timeouts fire as configured.\n- For repeat-sensitive latency, use warm-up plus repeat-N and record medians, not a single sample.\n\n## What to Capture\n\nFor node-redis probes, try to record:\n\n- The command and arguments as sent, plus the raw reply before type mapping.\n- The mapped JavaScript value and its type, especially for RESP3 maps, sets, doubles, and big numbers.\n- Server mode (standalone, cluster, or sentinel), negotiated RESP version, and server version.\n- Whether fields are absent, `null`, empty, or transformed by the client.\n- Raw error type, message, and any Redis error prefix for failures.\n- Reconnect and retry behavior, offline-queue flush order, and resubscription on reconnect when relevant.\n- For cluster, which node served the command and any redirect.\n- Which environment-variable names were approved for the probe when a live or credentialed server was required.\n\nDo not spend time rediscovering static documentation unless the runtime result seems to contradict what you expected. The value of this skill is in the observed behavior.\n","isInternal":false,"tokens":1994,"sizeBytes":9898},{"name":"repo-import-patterns.md","path":".agents/skills/runtime-behavior-probe/references/repo-import-patterns.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/runtime-behavior-probe/references/repo-import-patterns.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Repository Import Patterns\n\nUse this reference when a temporary probe script outside the repository needs to load code from the current branch.\n\n## Why This Exists\n\nWhen you run `npx tsx /tmp/probe.ts` from the repository root, `tsx` itself comes from this repository, but the probe file still lives under `/tmp`. Bare workspace imports such as `@redis/client` or `@redis/json` therefore resolve as if the script were its own package. That is often not what you want for \"what does the current branch do right now?\" investigations.\n\nThe safe default is to build a `file://` import URL from `process.cwd()`, which should be the repository root, and a repository-relative path.\n\n## Default Rule\n\n- Use `lib/` imports when the question is about current-branch behavior.\n- Use `dist/` imports when the question is about packaged output after a build.\n- Avoid bare workspace imports from `/tmp` unless the probe is explicitly testing published package resolution rather than branch-local source behavior.\n\n## Quick Typecheck Loop\n\nFor disposable probes in `node-redis`, the default loop should stay outside the repository and avoid a full workspace build:\n\n1. Create a temporary directory with `mktemp -d`.\n2. Write `probe.ts` there.\n3. Write a sibling `tsconfig.json` that extends the repository's `tsconfig.base.json`.\n4. From the repository root, run `npx tsc --noEmit -p /tmp/.../tsconfig.json`.\n5. If that passes, run `npx tsx /tmp/.../probe.ts`.\n\nExample temporary `tsconfig.json`:\n\n```json\n{\n  \"extends\": \"/absolute/path/to/node-redis/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"./probe.ts\"]\n}\n```\n\nThis keeps TypeScript module resolution and strictness close to the repository defaults while limiting the check surface to the disposable probe file.\n\nDo not convert this default loop into a checked-in benchmark, example, or package script unless the user explicitly asks for a reusable repository artifact.\n\n## Recommended Helper\n\nThe disposable TypeScript scaffold exposes:\n\n    const module = await importRepoModule(\"packages/client/lib/index.ts\");\n\nInternally, the helper should be equivalent to:\n\n    import { join } from \"node:path\";\n    import { pathToFileURL } from \"node:url\";\n\n    async function importRepoModule(repoRelativePath: string) {\n      const absolutePath = join(process.cwd(), repoRelativePath);\n      return import(pathToFileURL(absolutePath).href);\n    }\n\nThis keeps the import anchored to the current checkout instead of the temporary script location.\n\n## Choosing `lib/` Versus `dist/`\n\nUse `lib/` when:\n\n- You are validating a bug fix or regression on the current branch.\n- You want to know how the repository behaves before packaging.\n- The question is about implementation details or internal helper behavior.\n\nUse `dist/` when:\n\n- The question is specifically about what consumers load after `npm run build`.\n- You need to validate export maps or emitted `.js` and `.d.ts` output.\n- The bug only reproduces in generated output.\n\nIf the probe uses `dist/`, record whether you ran a fresh build first. A stale `dist/` directory can create false positives or false negatives.\n\nDo not run `npm run build` only to typecheck or execute a `lib/`-based disposable probe. The fast path is temp `tsconfig` plus `npx tsc --noEmit`, then `npx tsx`.\n\n## Simple Smoke Targets\n\nWhen you only need to prove the helper works, start with a simple target that has minimal side effects, for example:\n\n- `packages/client/lib/index.ts`\n- `packages/client/lib/client/index.ts`\n- `packages/json/lib/index.ts`\n- another file with obvious exported symbols and no environment requirement\n\nIf a chosen file pulls in more of the repo than expected, switch to a simpler target rather than weakening the import guidance.\n\n## What To Record\n\nWhen a probe imports repo code, report:\n\n- The repo-relative path you imported.\n- Whether it came from `lib/` or `dist/`.\n- The repository root used by `process.cwd()`.\n- Any prerequisite step such as `npm run build`.\n","isInternal":false,"tokens":893,"sizeBytes":4000},{"name":"reporting-format.md","path":".agents/skills/runtime-behavior-probe/references/reporting-format.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/runtime-behavior-probe/references/reporting-format.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Reporting Format\n\nLead with findings, not process. The user asked for investigation results, so the answer should start with the most important observed behaviors. Put the real news first.\n\n## Recommended Order\n\n1. Findings.\n2. Validation approach.\n3. Case matrix or condensed case summary.\n4. Artifact status and brief run summary.\n5. Optional implementation note.\n\n## Findings Section\n\nMake each finding answer one user-relevant question. Good findings usually include:\n\n- What was observed.\n- Why it matters.\n- The condition under which it happens.\n- What was held constant when the finding comes from a comparison probe.\n- `scope`: The boundary of the finding, such as commit, model, Node version, live versus local, or repeat mode.\n- `confidence`: `high`, `medium`, or `low`.\n\nAvoid burying the main result under setup details.\n\nPut `unexpected` or `negative` findings first. If there were no unexpected or negative findings in the executed cases, say that explicitly before the rest of the findings section.\n\nIf the probe was comparative, say whether the result supports:\n\n- Pattern parity only.\n- A broader quality claim.\n\nDo not imply a broader quality equivalence than the executed cases justify.\n\n## Validation Approach Section\n\nSummarize:\n\n- The runtime surface you exercised.\n- The shape of the probe code, in overview only.\n- Which categories of cases you covered.\n- Which execution modes you used, including repeat counts or warm-up handling when relevant.\n- Whether live credentials or external services were used.\n- Any important state controls such as fresh state, cache reuse, cache busting, unique IDs, or cleanup verification.\n- For comparison probes, what was held constant, what was varied, and whether output-shape or usage differences could still influence the conclusion.\n- Whether the usual docs path or an official-docs fallback was used for contract-sensitive checks.\n\nKeep this concise. The user needs enough detail to trust the result, not a line-by-line replay of the script.\n\n## Case Summary\n\nInclude either the full matrix or a condensed summary. At minimum, show:\n\n- Which scenarios were executed.\n- Whether the run was a quick pilot, an expanded matrix, or both.\n- Which ones produced `unexpected` or `negative` results.\n- Which ones passed or failed when a real comparison basis existed.\n- Which cases were blocked.\n- Where the supporting evidence lived, or that it was deleted.\n\nIf the matrix is large, show the highest-value cases in the main response and keep the rest as a compact appendix or note.\n\n## Artifact Status And Brief Run Summary\n\nState one of these explicitly:\n\n- Temporary artifacts were kept until the final response was drafted, then deleted after validation.\n- Temporary artifacts were kept at `<path>` because the user asked to keep them.\n- Temporary artifacts were kept at `<path>` because they are needed for follow-up analysis.\n\nEven if artifacts were deleted, retain a short run summary such as:\n\n- Probe command or runner shape.\n- Runtime context summary such as commit, Node executable, Node version, or model.\n- Artifact path and final status.\n\nFor benchmark or repeat-heavy probes, keeping artifacts for follow-up is often the right default even when the immediate report is done.\n\n## Optional Implementation Note\n\nInclude this only when one clear defect was isolated and a short implementation hypothesis or minimal repro direction would help. Keep it brief. Do not turn the report into a broader next-step plan unless the user asked for that.\n\n## Compact Template\n\nUse this outline when you need a fast structure:\n\n    Findings:\n    - <finding 1>\n      held constant: <prompt/tool/state settings kept the same, if comparative>\n      scope: <commit/model/node/live-local/repeat-mode>\n      confidence: <high|medium|low>\n    - <finding 2>\n      held constant: <prompt/tool/state settings kept the same, if comparative>\n      scope: <commit/model/node/live-local/repeat-mode>\n      confidence: <high|medium|low>\n\n    Validation approach:\n    - Surface: <what was exercised>\n    - Probe code: <brief overview>\n    - Coverage: <success, edge, error, repeat-sensitive, and quality categories>\n    - Execution modes: <single-shot|repeat-N|warm-up + repeat-N>\n    - Comparison parity: <what was held constant and what varied, if comparative>\n    - Docs source: <MCP or official-docs fallback, if relevant>\n\n    Case summary:\n    | case_id | scenario | result_flag | status | note |\n    | --- | --- | --- | --- | --- |\n    | S1 | ... | expected | pass | ... |\n    | E1 | ... | negative | fail | ... |\n\n    Artifact status and brief run summary:\n    - Temporary artifacts were kept until the final response was drafted, then deleted.\n    - Summary: <command/runtime-context/artifact-status summary>\n\n    Optional implementation note:\n    - <brief hypothesis or minimal repro direction>\n\nAdjust the format to the task, but preserve the ordering.\n","isInternal":false,"tokens":1024,"sizeBytes":4904},{"name":"validation-matrix.md","path":".agents/skills/runtime-behavior-probe/references/validation-matrix.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/runtime-behavior-probe/references/validation-matrix.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Validation Matrix\n\nUse the matrix to decide what to probe before writing scripts. The goal is not exhaustive combinatorics; the goal is high-value coverage that is visible, explainable, and likely to reveal runtime surprises. The matrix should make the real news easy to scan.\n\n## Minimum Columns\n\nUse these columns unless the task clearly needs more:\n\n- `case_id`: Stable identifier such as `S1`, `E3`, or `R2`.\n- `scenario`: Short description of the behavior under test.\n- `mode`: `single-shot`, `repeat-N`, or `warm-up + repeat-N`.\n- `question`: The concrete runtime uncertainty this case is answering.\n- `setup`: Inputs, environment, or preconditions required for the case.\n- `observation_summary`: A compact summary of what actually happened.\n- `result_flag`: `unexpected`, `negative`, `expected`, or `blocked`.\n- `evidence`: Path, log reference, or `deleted`.\n\nAdd these columns when they materially improve the investigation:\n\n- `comparison_basis`: The baseline, docs, or prior behavior you are comparing against.\n- `variable_under_test`: The single factor that is intentionally changing in a comparison case.\n- `held_constant`: Prompt shape, tool setup, model settings, or state rules that were intentionally kept the same.\n- `output_constraint`: Any schema, length, or response-shape constraint used to keep the comparison fair.\n- `status`: Use `pass`, `fail`, `unexpected-pass`, `unexpected-fail`, or `blocked` only when there is a credible comparison basis or control.\n- `confidence`: `high`, `medium`, or `low`.\n- `state_setup`: Fresh or reused state, cache strategy, unique IDs, and cleanup checks.\n- `repeats`: Number of measured runs.\n- `warm_up`: Whether a warm-up run was used and why.\n- `variance`: Any useful spread or instability note across repeated runs.\n- `usage_note`: Token, usage, or output-length note when it materially affects interpretation.\n- `control`: Known-good comparison point for regression or behavior-change questions.\n- `risk_profile`: `read-only`, `mutating`, or `costly` for live probes.\n- `env_vars`: Exact environment-variable names the case plans to read.\n- `approval`: `not-needed`, `pending`, or `approved` for cases that need user permission before execution.\n\nUse `result_flag` as the fast scan field. It is what makes unexpected or negative findings jump out before the reader studies the full report.\n\nUse `status` only when you have a real comparison basis. If the case is exploratory and there is no trustworthy baseline, prefer a strong `observation_summary` plus `result_flag` and `confidence` instead of pretending the result is a clean pass or fail.\n\n## Choosing Execution Mode\n\nPick an execution mode before you run the case:\n\n- Use `single-shot` for deterministic, one-run checks.\n- Use `repeat-N` automatically when the question involves cache behavior, retries, streaming, interruptions, rate limiting, concurrency, or other run-to-run-sensitive behavior.\n- Use `warm-up + repeat-N` when the first run is likely to include cold-start effects such as container provisioning, import caches, or prompt-cache population.\n\nUse these defaults unless the task clearly needs something else:\n\n- `repeat-3` for a quick screen of a repeat-sensitive question.\n- `warm-up + repeat-10` for decision-grade latency comparisons or release-facing recommendations.\n- For costly live probes, start at `repeat-3`, then expand only if the answer is still unclear.\n\nIf it is genuinely unclear whether extra runs are worth the time or cost, ask the user before expanding the probe.\n\n## Phase The Matrix\n\nWhen the question is comparative or benchmark-like, do not jump straight to the largest matrix.\n\nStart with a pilot:\n\n- One control.\n- One or two highest-signal success cases.\n- The smallest repeat count that can disqualify a weak candidate quickly.\n\nExpand only when:\n\n- The candidate survives the pilot.\n- The results are close enough that more samples matter.\n- A major runtime surface is still uncovered.\n- The user explicitly wants decision-grade evidence.\n\n## Coverage Categories\n\nTry to cover at least one case from each relevant category:\n\n- `success`: Normal behavior that should work.\n- `control`: Known-good comparison such as `origin/master`, the latest release, or the same request without the suspected option.\n- `boundary`: Size, count, or parameter limits near a plausible edge.\n- `invalid`: Bad inputs or unsupported combinations.\n- `misconfig`: Missing key, wrong endpoint, bad permissions, or incompatible local setup.\n- `transient`: Timeout, temporary server issue, network breakage, or rate limiting.\n- `recovery`: Retry behavior, partial completion, duplicate submission, or cleanup.\n- `concurrency`: Overlapping operations when shared state, ordering, or isolation may matter.\n- `quality`: A harder or more open-ended sample when the user is asking about model intelligence, not just workflow parity.\n\nIf time is limited, prioritize categories in this order:\n\n1. Known-good control when the question implies regression or drift.\n2. Highest-risk success case.\n3. Most plausible user-facing failure.\n4. Most likely edge case with ambiguous behavior.\n5. Cleanup or retry semantics.\n6. Lower-probability extremes.\n\n## Matrix Template\n\nUse this compact template:\n\n    | case_id | scenario | mode | question | setup | state_setup | variable_under_test | held_constant | comparison_basis | observation_summary | result_flag | status | evidence |\n    | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n    | K1 | Known-good control | single-shot | Does the baseline still show the expected behavior? | Same probe against baseline target | Fresh state | none | current probe shape | `origin/master` or latest release | pending | pending | pending | pending |\n    | S1 | Baseline success | single-shot | What does the normal success path look like at runtime? | Valid config and representative input | Fresh state | none | representative input and setup | current docs or local expectation | pending | pending | pending | pending |\n    | R1 | Cache or retry behavior | warm-up + repeat-N | Does behavior change after the first run or across retries? | Same request repeated under controlled settings | Cache key or retry setup recorded | reuse versus fresh state | prompt shape and tool setup | same request without reuse, or docs if available | pending | pending | pending | pending |\n    | C1 | Model comparison pilot | warm-up + repeat-N | Does candidate B preserve the covered behavior while improving latency? | Same scenario across two models | Fresh state and stable IDs | model name | prompt shape, tool choice, and model settings parity | control model in the same probe | pending | pending | pending | pending |\n    | E1 | Invalid input | single-shot | How does the runtime reject a realistic bad input? | Missing required field | Fresh state | invalid field value | same request with valid field | same request with valid field | pending | pending | pending | pending |\n    | X1 | Concurrent overlap | repeat-N | Do overlapping runs interfere with each other? | Two or more overlapping operations | Unique IDs plus cleanup verification | overlap timing | same logical input | same request serialized, if available | pending | pending | pending | pending |\n\n## Recording Results\n\nKeep `question` unchanged after execution. Put the actual behavior in `observation_summary`, then mark the scan-friendly `result_flag`.\n\nUse these `result_flag` values consistently:\n\n- `unexpected`: The result diverged from the best current understanding in a surprising way.\n- `negative`: The result exposed a user-relevant failure, risk, or sharp edge.\n- `expected`: The result matched the current understanding and did not reveal new risk.\n- `blocked`: The case did not produce a trustworthy observation.\n\nOnly fill `status` when there is a credible comparison basis. Otherwise use `observation_summary`, `result_flag`, and `confidence` to communicate what was learned without over-claiming certainty.\n\nFor comparison cases, use `observation_summary` and the final report to say whether the evidence supports pattern parity only or a broader quality claim.\n\nIf a case reveals a new branch of behavior, add a follow-up case instead of overloading the original one.\n\n## Evidence Discipline\n\nTreat a case as incomplete when:\n\n- The observed output omits the key result you were testing.\n- The script mixed multiple questions and the result is ambiguous.\n- Hidden state, cache behavior, or previous runs may have influenced the result and were not controlled or documented.\n- The question is whether behavior changed, but the case has no credible control or baseline to compare against.\n- The case plans to read environment variables, but the exact variable names were not approved by the user before execution.\n- The case was repeat-sensitive, but it ran only once without a clear rationale.\n\nWhen this happens, narrow the probe and rerun. A smaller script with a cleaner result is better than a more complicated script that is hard to trust.\n","isInternal":false,"tokens":1874,"sizeBytes":9018},{"name":"SKILL.md","path":".agents/skills/runtime-behavior-probe/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/runtime-behavior-probe/SKILL.md","title":"runtime-behavior-probe","category":"anthropic-skill","format":"markdown","content":"---\nname: runtime-behavior-probe\ndescription: Plan and execute runtime-behavior investigations with temporary TypeScript probe scripts, validation matrices, state controls, and findings-first reports. Use only when the user explicitly invokes this skill to verify actual runtime behavior beyond normal code-level checks, especially to uncover edge cases, undocumented behavior, or common failure modes in local or live integrations. A baseline smoke check is fine as an entry point, but do not stop at happy-path confirmation.\n---\n\n# Runtime Behavior Probe\n\n## Overview\n\nUse this skill to investigate real runtime behavior, not to restate code or documentation. Start by planning the investigation, then execute a case matrix, record observed behavior, and report both the findings and the method used to obtain them.\n\n## Core Rules\n\n- Treat this skill as manual-only. Do not rely on implicit invocation.\n- For `node-redis`, treat this skill as a disposable-probe workflow, not a repository implementation workflow.\n- Unless the user explicitly asks for a reusable repository artifact, the allowed write scope is limited to:\n  - a temporary directory used for probe scripts or artifacts\n  - `.agents/skills/runtime-behavior-probe/**` when the user is editing this skill itself\n- For disposable probes in `node-redis`, do not modify `examples/**`, `packages/**`, any `package.json`, `README.md`, workspace config, or build config.\n- If your draft plan would touch a disallowed path, stop and rewrite the plan before editing anything.\n- A baseline success or smoke case is often the right entry point, but do not stop there when the real question involves edge cases, drift, or failure behavior.\n- Plan before running anything. Write the case matrix first, then fill it in with observed results. The matrix can live in a scratch note, a temporary file, or the probe script header.\n- Default to local or read-only probes. Consider a live service only when it is clearly relevant, then apply the lightweight gates below before you run it.\n- Size the probe to the decision. Start with the smallest matrix that can disqualify or validate the current hypothesis, then expand only when uncertainty remains.\n- Before a live probe, apply three lightweight gates:\n  - Destination gate. Use only a live destination that is clearly allowed for the task.\n  - Intent gate. Run the live probe only when the user explicitly wants runtime verification on that integration, or explicitly approves it after you propose the probe.\n  - Data gate. If the probe will read environment variables, mutate remote state, incur material cost, or exercise non-public or user data, name the exact variable names or data class and get explicit approval first.\n- Classify each case as read-only, mutating, or costly before execution. For mutating or costly cases, or for any live case that will read environment variables, define cleanup or rollback before running the probe.\n- Use temporary files or a temporary directory for one-off probe scripts.\n- In `node-redis`, use disposable probe files outside git-tracked paths by default. Do not add one-off probes, harnesses, benchmarks, or examples under `examples/`, `packages/`, or other repository directories unless the user explicitly asks for a checked-in artifact.\n- Keep temporary artifacts until the final response is drafted. Then delete them by default unless the user asked to keep them or they are needed for follow-up. Even when artifacts are deleted, keep a short run summary of the command shape, runtime context, and artifact status in the report.\n- Before executing a live probe that will read environment variables, tell the user the exact variable names you plan to use and why, then wait for explicit approval. Examples include `REDIS_URL`, `REDIS_PASSWORD`, and other expected default names for the system under test.\n- Never print secrets, even when they come from standard environment variables that this skill may use.\n- For Redis command or server semantics, treat the repository source as the truth and use [redis.io/commands](https://redis.io/commands) to confirm contract-sensitive details such as argument order, reply types, and version availability. Use runtime probing to validate or challenge the documented behavior, not to skip the documentation pass entirely. If you rely on the website rather than source, say so in the report.\n- For benchmark or comparison probes, make parity explicit before execution. Record what is held constant, what variable is under test, which reply-shape constraints keep the comparison fair, and any counters or timings that matter for interpreting latency or cost.\n- In `node-redis`, default to a light local loop for probe authoring: temporary `probe.ts` plus temporary `tsconfig.json`, `npx tsc --noEmit -p <tmp-tsconfig>`, then `npx tsx <tmp-probe>`. Escalate to `npm run build` only when the runtime question is specifically about `dist/`, emitted exports, or packaged output.\n- In `node-redis`, do not treat a request for runtime verification, benchmarking, or comparison as permission to add a reusable example, benchmark harness, package script, or checked-in sample. Those repository changes require explicit user intent.\n- For probes that need a running Redis, remove setup ambiguity before attributing a negative result to client behavior:\n  - Confirm the server is reachable and note its mode (standalone, cluster, or sentinel) and RESP version before interpreting reply shapes.\n  - Treat RESP2 and RESP3 as separate cases, not interchangeable setup details, when the question is about reply type mapping or push messages.\n  - Clear unsupported server-version or command options first so they do not invalidate the probe. The repo test harness `@redis/test-utils` can start Redis in Docker for local probes.\n\n## Workflow\n\n1. Restate the investigation target in operational terms. Name the runtime surface, the key uncertainty, and the highest-risk behaviors to test.\n2. For `node-redis`, declare the allowed write scope before you do any implementation work. For a normal disposable probe, that means a temporary directory only.\n3. Do a short preflight. Check the relevant code or docs first, decide whether the question needs local or live validation, and note any repo, baseline, or release boundary that matters.\n4. Create a validation matrix before executing probes. Cover both baseline behavior and the most relevant failure or drift cases. The matrix can live in a scratch note, a temporary file, or a structured header inside the probe script.\n5. For each case, choose an execution mode up front:\n   - `single-shot` for deterministic one-run checks.\n   - `repeat-N` for cache, retry, streaming, interruption, rate-limit, concurrency, or other run-to-run-sensitive behavior.\n   - `warm-up + repeat-N` when first-run cold-start effects could distort the result. Use these defaults unless the task clearly needs something else:\n   - Quick screen of a repeat-sensitive question: `repeat-3`.\n   - Decision-grade latency or release recommendation: `warm-up + repeat-10`.\n   - Costly live cases: start at `repeat-3`, then expand only if the answer remains unclear. If it is genuinely unclear whether extra runs are worth the time or cost, ask the user before expanding the probe.\n6. When the question is benchmark-like or comparative, run in phases. Start with a high-signal pilot matrix against a control, then expand only the surviving candidates or unresolved cases.\n7. If the question is about a suspected regression or behavior change, add at least one known-good control case such as `origin/master`, the latest release, or the same request without the suspected option.\n8. For comparative probes, define parity before execution. Record prompt or input shape, tool-choice setup, model-settings parity, state reuse rules, and any response-shape constraint that keeps the comparison fair. If materially different output length could bias the result, record usage or token notes too.\n9. If the question asks whether one option has the same intelligence or quality as another, decide whether the matrix supports only example-pattern parity or a broader quality claim. For broader claims, add at least one harder or more open-ended case. Otherwise say explicitly that the result is limited to the covered patterns.\n10. Plan state controls before execution when hidden state could affect the result. Record whether each case uses fresh or reused state, how cache reuse or cache busting is handled, what unique IDs isolate repeated runs, and how cleanup is verified.\n11. If any live case will read environment variables, list the exact variable names and purpose for each case, then ask the user for approval before execution. Keep the approval ask short and include destination, read-only versus mutating or costly risk, exact variable names, and cleanup or rollback if relevant.\n12. Build task-specific probe scripts in a temporary location. Keep the script small, observable, and easy to discard.\n13. If you are about to propose a checked-in script, example, benchmark, or workspace script for `node-redis`, stop and verify that the user explicitly asked for a reusable repository artifact. If not, keep the probe temporary.\n14. In `node-redis`, make the runtime context explicit:\n\n- Run TypeScript probes from the repository root with `npx tsx` when practical.\n- For probe authoring, create both `probe.ts` and a sibling temporary `tsconfig.json` under `mktemp -d`, then run `npx tsc --noEmit -p <tmp-tsconfig>` before the first live execution.\n- Record the current commit, working directory, Node executable, Node version, and the package or source path you imported.\n- Avoid accidental imports from a different checkout or from `/tmp/node_modules`. If the probe needs repository code, import it from a repository-relative `file://` URL rooted at `process.cwd()`.\n- Prefer current-branch `lib/` imports when the question is \"what does this branch do now?\" and prefer `dist/` imports only when the question is specifically about packaged output after a build.\n- Do not run a repository-wide build just to typecheck a disposable probe. Reserve `npm run build` for `dist/` probes or when emitted output is itself part of the question.\n\n15. Execute the matrix and capture evidence. Record request shape, setup, observation summary, unexpected or negative result, error details, timing, runtime context, approved environment-variable names, repeat counts, warm-up handling, variance when relevant, cleanup behavior, and for comparisons note what was held constant plus any response-shape or usage notes that affect interpretation.\n16. Update the matrix with actual outcomes, not guesses.\n17. Keep temporary artifacts until the final response is drafted. Then delete them unless the user asked to keep them or they are needed for follow-up. Benchmark and repeat-heavy probes often need follow-up, so keeping artifacts is normal when the result may be revisited. If deleted, retain and report a short run summary.\n18. Report findings first, with unexpected or negative findings first. Then summarize how the validation was performed and which cases were covered.\n19. If the probe isolates one clear defect, you may include a short implementation hypothesis or minimal repro direction. Do not expand into a larger next-step plan unless the user asked for it.\n\n## Validation Matrix\n\nUse a matrix that makes the news easy to scan. Start from the runtime question and the observation summary, not just from `expected` and `pass` or `fail`.\n\nUse a matrix with at least these columns:\n\n- `case_id`\n- `scenario`\n- `mode`\n- `question`\n- `setup`\n- `observation_summary`\n- `result_flag`\n- `evidence`\n\nAdd these columns when they materially improve the investigation:\n\n- `comparison_basis`\n- `variable_under_test`\n- `held_constant`\n- `output_constraint`\n- `status`\n- `confidence`\n- `state_setup`\n- `repeats`\n- `warm_up`\n- `variance`\n- `usage_note`\n- `risk_profile`\n- `env_vars`\n- `approval`\n- `control`\n\nTreat `result_flag` as a fast scan field such as `unexpected`, `negative`, `expected`, or `blocked`. Use `status` only when there is a credible comparison basis, baseline, or documented contract to compare against.\n\nAlways consider whether the matrix should include these categories:\n\n- Baseline success.\n- Control or baseline comparison when a regression is suspected.\n- Boundary input or parameter variation.\n- Invalid or unsupported input.\n- Missing or incorrect configuration.\n- Transient external failure such as timeout, network interruption, or rate limiting.\n- Retry, idempotence, or cleanup behavior.\n- Concurrency or overlapping operations when shared state or ordering may matter.\n- Open-ended quality or intelligence samples when the question is broader than pattern parity.\n\nOpen [validation-matrix.md](./references/validation-matrix.md) when you need a stronger prioritization model or a reusable case template.\n\n## Temporary Probe Scripts\n\nWrite one-off scripts in a temporary file or temporary directory such as one created by `mktemp -d`. Keep the script outside the repository by default, even when it imports code from the repository.\n\nFor `node-redis`, a disposable runtime probe should stay disposable. Do not add a package script, modify workspace config, or create a checked-in benchmark or example unless the user explicitly asks for a reusable repository artifact.\n\nFor `node-redis`, the default authoring loop should be:\n\n1. `tmpdir=$(mktemp -d)`\n2. Write `probe.ts` and `tsconfig.json` into `$tmpdir`\n3. From the repository root, run `npx tsc --noEmit -p \"$tmpdir/tsconfig.json\"`\n4. If the quick typecheck passes, run `npx tsx \"$tmpdir/probe.ts\"`\n5. Only run `npm run build` if the probe intentionally imports `dist/` or validates packaged output\n\nUse a temporary `tsconfig.json` that extends the repository base settings but includes only the disposable probe, for example:\n\n```json\n{\n  \"extends\": \"/absolute/path/to/node-redis/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"./probe.ts\"]\n}\n```\n\nIf the probe needs repository code:\n\n- Run it with the repository root as the working directory.\n- Use `npx tsx /tmp/probe.ts` from the repository root when practical.\n- Use `npx tsc --noEmit -p /tmp/probe-tsconfig.json` as the default quick typecheck step before executing the probe.\n- Import repository modules with a `file://` URL built from `process.cwd()` and a repo-relative path. Do not assume bare workspace imports such as `@redis/client` will resolve from `/tmp`.\n- Use `lib/` imports for current-branch behavior probes and `dist/` imports for packaged-output probes.\n\nDesign the probe to maximize observability:\n\n- Print or log the exact scenario being exercised.\n- Capture runtime context such as git SHA, working directory, Node executable and version, relevant package versions, model or deployment name, endpoint or base URL alias, and any retry or tool options that materially affect behavior.\n- For live probes, record only the names of environment variables that were approved for use. Never print their values.\n- Capture structured outputs when possible.\n- Preserve raw error type, message, and status code.\n- For repeat-sensitive cases, capture the attempt index, warm-up status, and any stable identifiers that help compare runs.\n- For repeated or benchmark-style probes, write both raw results and a compact summary artifact when practical.\n- Keep branching minimal so each script answers a narrow question.\n\nBefore deleting the temporary script or directory, keep a short run summary of the script path, command used, runtime context, and whether the evidence was kept or deleted.\n\nOpen [typescript_probe.ts](./templates/typescript_probe.ts) when you want a lightweight disposable TypeScript probe scaffold. Open [repo-import-patterns.md](./references/repo-import-patterns.md) when you need to load current-branch workspace code from a temporary script.\n\n## Reporting\n\nReport in this order:\n\n1. Findings. Put unexpected or negative findings first. If there was no real news, say that explicitly.\n2. Validation approach. Summarize the code used, the runtime surface exercised, the execution modes, and the case matrix coverage.\n3. Case results. Include the matrix or a condensed version of it when the case count is large.\n4. Artifact status and brief run summary. State whether temporary artifacts were deleted or kept, and provide kept paths or the retained summary.\n5. Optional implementation note. Include this only when one clear defect was isolated and a short implementation direction would help.\n\nFor comparative probes, the report should also say what was held constant, what variable was under test, and whether the result supports only pattern parity or a broader quality claim.\n\nOpen [reporting-format.md](./references/reporting-format.md) for the recommended response template.\n\n## Resources\n\n- Open [validation-matrix.md](./references/validation-matrix.md) to design and prioritize the case matrix.\n- Open [error-cases.md](./references/error-cases.md) to expand common failure scenarios.\n- Open [redis-runtime-patterns.md](./references/redis-runtime-patterns.md) for recurring node-redis runtime probe patterns.\n- Open [repo-import-patterns.md](./references/repo-import-patterns.md) for repository-relative TypeScript import guidance.\n- Open [reporting-format.md](./references/reporting-format.md) for the final report structure.\n- Open [typescript_probe.ts](./templates/typescript_probe.ts) for a minimal disposable TypeScript probe scaffold.\n","frontmatter":{"name":"runtime-behavior-probe","description":"Plan and execute runtime-behavior investigations with temporary TypeScript probe scripts, validation matrices, state controls, and findings-first reports. Use only when the user explicitly invokes this skill to verify actual runtime behavior beyond normal code-level checks, especially to uncover edge cases, undocumented behavior, or common failure modes in local or live integrations. A baseline smoke check is fine as an entry point, but do not stop at happy-path confirmation."},"isInternal":false,"tokens":3622,"sizeBytes":17520},{"name":"openai.yaml","path":".agents/skills/test-coverage-improver/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/test-coverage-improver/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"interface:\n  display_name: \"Test Coverage Improver\"\n  short_description: \"Analyze coverage gaps and propose high-impact tests\"\n  default_prompt: \"Use $test-coverage-improver to analyze coverage gaps, propose high-impact tests, and update coverage after approval.\"\n","isInternal":false,"tokens":58,"sizeBytes":264},{"name":"SKILL.md","path":".agents/skills/test-coverage-improver/SKILL.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/.agents/skills/test-coverage-improver/SKILL.md","title":"test-coverage-improver","category":"anthropic-skill","format":"markdown","content":"--\nname: test-coverage-improver\ndescription: 'Improve test coverage in the node-redis monorepo: run a package test suite under nyc, inspect coverage artifacts, identify low-coverage files and branches, propose high-impact tests, and confirm with the user before writing tests.'\n---\n\n# Test Coverage Improver\n\n## Overview\n\nUse this skill whenever coverage needs assessment or improvement (coverage regressions, failing thresholds, or user requests for stronger tests). It runs the coverage suite, analyzes results, highlights the biggest gaps, and prepares test additions while confirming with the user before changing code.\n\nCoverage is collected per package via `nyc` (each package's `test` script runs `nyc -r text-summary -r lcov mocha ...`). **Docker must be running** — the test harness starts real Redis containers via `@redis/test-utils`.\n\n## Quick Start\n\n1. Pick the target package (e.g. `@redis/client`) and run its tests under coverage from the repo root: `npm test -w @redis/client`. This regenerates that package's `coverage/`.\n2. Collect artifacts: the `text-summary` printed to stdout for totals, plus `packages/<pkg>/coverage/lcov.info` and `packages/<pkg>/coverage/lcov-report/index.html` for line/branch drill-downs. For machine-readable file totals, rerun with an added summary reporter: `npm test -w @redis/client -- ... ` is awkward, so prefer `npx nyc -r json-summary -r text-summary mocha ...` from inside the package when you need `coverage/coverage-summary.json`.\n3. Summarize coverage: total percentages, lowest files, branches under 80%, and uncovered lines/paths.\n4. Draft test ideas per file: scenario, behavior under test, expected outcome, and likely coverage gain.\n5. Ask the user for approval to implement the proposed tests; pause until they agree.\n6. After approval, write the tests next to the source in the relevant package, rerun coverage, then run `npm run build` and `npm run lint` before marking work complete.\n\n## Workflow Details\n\n- **Run coverage**: Execute the target package's test script (`npm test -w <pkg>`) from the repo root. Avoid watch flags. Keep prior coverage artifacts only if comparing trends. Confirm Docker is available first.\n- **Parse summaries efficiently**:\n  - Use the printed `text-summary` for quick totals.\n  - Use `coverage/lcov.info` or `coverage/lcov-report/index.html` to spot branch- and line-level holes.\n  - Add the `json-summary` nyc reporter when you need `coverage/coverage-summary.json` for file-level totals.\n- **Prioritize targets**:\n  - Core client/cluster/sentinel/pool/RESP code in `packages/*/lib` before examples or docs.\n  - Files with statements/branches below 80% or newly added code at 0%.\n  - Recent bug fixes or risky code paths (error handling, reconnection, timeouts, pub/sub, concurrency).\n- **Design impactful tests**:\n  - Hit uncovered branches: error cases, boundary inputs, optional flags, and cancellation/timeouts.\n  - Cover combinational logic rather than trivial happy paths.\n  - Co-locate specs next to source as `packages/<pkg>/lib/**/<NAME>.spec.ts` (mocha + `tsx`, `node:assert`). For command/topology tests use `testUtils.testAll(name, fn, { client, cluster })`. Avoid flaky async timing.\n- **Coordinate with the user**: Present a numbered, concise list of proposed test additions and expected coverage gains. Ask explicitly before editing code or fixtures.\n- **After implementation**: Rerun coverage, report the updated summary, and note any remaining low-coverage areas.\n\n## Notes\n\n- Keep any added comments or code in English.\n- Do not create `scripts/`, `references/`, or `assets/` unless needed later.\n- If coverage artifacts are missing or stale, rerun the package test suite instead of guessing.\n- `nyc` excludes `dist`, `**/*.spec.ts`, `lib/test-utils.ts`, and `examples/*` (see each package's `.nycrc`); do not target those for coverage.\n","isInternal":false,"tokens":890,"sizeBytes":3856},{"name":"README.md","path":"packages/bloom/README.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/packages/bloom/README.md","title":"bloom Documentation","category":"plugin-manifest","format":"markdown","content":"# @redis/bloom\n\nThis package provides support for the [RedisBloom](https://redis.io/docs/data-types/probabilistic/) module, which adds additional probabilistic data structures to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisBloom module installed.\n\nRedisBloom provides the following probabilistic data structures:\n\n* Bloom Filter: for checking set membership with a high degree of certainty.\n* Cuckoo Filter: for checking set membership with a high degree of certainty.\n* T-Digest: for estimating the quantiles of a stream of data.\n* Top-K: Maintain a list of k most frequently seen items.\n* Count-Min Sketch: Determine the frequency of events in a stream.\n\nFor some examples, see [`bloom-filter.js`](https://github.com/redis/node-redis/tree/master/examples/bloom-filter.js), [`cuckoo-filter.js`](https://github.com/redis/node-redis/tree/master/examples/cuckoo-filter.js), [`count-min-sketch.js`](https://github.com/redis/node-redis/tree/master/examples/count-min-sketch.js) and [`topk.js`](https://github.com/redis/node-redis/tree/master/examples/topk.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n","isInternal":false,"tokens":292,"sizeBytes":1265},{"name":"README.md","path":"packages/client/README.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/packages/client/README.md","title":"client Documentation","category":"plugin-manifest","format":"markdown","content":"# @redis/client\n\nThe source code and documentation for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.\n","isInternal":false,"tokens":34,"sizeBytes":140},{"name":"README.md","path":"packages/entraid/README.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/packages/entraid/README.md","title":"entraid Documentation","category":"plugin-manifest","format":"markdown","content":"# @redis/entraid\n\nSecure token-based authentication for Redis clients using Microsoft Entra ID (formerly Azure Active Directory).\n\n## Features\n\n- Token-based authentication using Microsoft Entra ID\n- Automatic token refresh before expiration\n- Automatic re-authentication of all connections after token refresh\n- Support for multiple authentication flows:\n  - Managed identities (system-assigned and user-assigned)\n  - Service principals (with or without certificates)\n  - Authorization Code with PKCE flow\n  - DefaultAzureCredential from @azure/identity\n- Built-in retry mechanisms for transient failures\n\n## Installation\n\n\n```bash\nnpm install \"@redis/client@5.0.0-next.7\"\nnpm install \"@redis/entraid@5.0.0-next.7\"\n```\n\n## Getting Started\n\nThe first step to using @redis/entraid is choosing the right credentials provider for your authentication needs. The `EntraIdCredentialsProviderFactory` class provides several factory methods to create the appropriate provider:\n\n- `createForSystemAssignedManagedIdentity`: Use when your application runs in Azure with a system-assigned managed identity\n- `createForUserAssignedManagedIdentity`: Use when your application runs in Azure with a user-assigned managed identity\n- `createForClientCredentials`: Use when authenticating with a service principal using client secret\n- `createForClientCredentialsWithCertificate`: Use when authenticating with a service principal using a certificate\n- `createForAuthorizationCodeWithPKCE`: Use for interactive authentication flows in user applications\n- `createForDefaultAzureCredential`: Use when you want to leverage Azure Identity's DefaultAzureCredential\n\n## Usage Examples\n\n### Service Principal Authentication\n\n```typescript\nimport { createClient } from '@redis/client';\nimport { EntraIdCredentialsProviderFactory } from '@redis/entraid';\n\nconst provider = EntraIdCredentialsProviderFactory.createForClientCredentials({\n  clientId: 'your-client-id',\n  clientSecret: 'your-client-secret',\n  authorityConfig: {\n    type: 'multi-tenant',\n    tenantId: 'your-tenant-id'\n  },\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8 // Refresh token after 80% of its lifetime\n  }\n});\n\nconst client = createClient({\n  url: 'redis://your-host',\n  credentialsProvider: provider\n});\n\nawait client.connect();\n```\n\n### System-Assigned Managed Identity\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForSystemAssignedManagedIdentity({\n  clientId: 'your-client-id',\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n```\n\n### User-Assigned Managed Identity\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForUserAssignedManagedIdentity({\n  clientId: 'your-client-id',\n  userAssignedClientId: 'your-user-assigned-client-id',\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n```\n\n### DefaultAzureCredential Authentication\n\ntip: see a real sample here: [samples/interactive-browser/index.ts](./samples/interactive-browser/index.ts)\n\nThe DefaultAzureCredential from @azure/identity provides a simplified authentication experience that automatically tries different authentication methods based on the environment. This is especially useful for applications that need to work in different environments (local development, CI/CD, and production).\n\n```typescript\nimport { createClient } from '@redis/client';\nimport { getDefaultAzureCredential } from '@azure/identity';\nimport { EntraIdCredentialsProviderFactory, REDIS_SCOPE_DEFAULT } from '@redis/entraid';\n\n// Create a DefaultAzureCredential instance\nconst credential = getDefaultAzureCredential();\n\n// Create a provider using DefaultAzureCredential\nconst provider = EntraIdCredentialsProviderFactory.createForDefaultAzureCredential({\n\n  credential,\n  scopes: REDIS_SCOPE_DEFAULT, // The Redis scope\n  // Optional additional parameters for getToken\n  options: {\n    // Any options you would normally pass to azure's default credential.getToken()\n  },\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n\nconst client = createClient({\n  url: 'redis://your-host',\n  credentialsProvider: provider\n});\n\nawait client.connect();\n```\n\n#### Important Notes on Using DefaultAzureCredential\n\nWhen using the `createForDefaultAzureCredential` method, you need to:\n\n1. Create your own instance of `DefaultAzureCredential`\n2. Pass the same parameters to the factory method that you would use with the `getToken()` method:\n   - `scopes`: The Redis scope (use the exported `REDIS_SCOPE_DEFAULT` constant)\n   - `options`: Any additional options for the getToken method\n\nThis factory method creates a wrapper around DefaultAzureCredential that adapts it to the Redis client's\nauthentication system, while maintaining all the flexibility of the original Azure Identity authentication.\n\n## Important Limitations\n\n### RESP2 PUB/SUB Limitations\n\nWhen using RESP2 (Redis Serialization Protocol 2), there are important limitations with PUB/SUB:\n\n- **No Re-Authentication in PUB/SUB Mode**: In RESP2, once a connection enters PUB/SUB mode, the socket is blocked and cannot process out-of-band commands like AUTH. This means that connections in PUB/SUB mode cannot be re-authenticated when tokens are refreshed.\n- **Connection Eviction**: As a result, PUB/SUB connections will be evicted by the Redis proxy when their tokens expire. The client will need to establish new connections with fresh tokens.\n\n### Transaction Safety\n\nWhen using token-based authentication, special care must be taken with Redis transactions. The token manager runs in the background and may attempt to re-authenticate connections at any time by sending AUTH commands. This can interfere with manually constructed transactions.\n\n#### ✅ Recommended: Use the Official Transaction API\n\nAlways use the official transaction API provided by the client:\n\n```typescript\n// Correct way to handle transactions\nconst multi = client.multi();\nmulti.set('key1', 'value1');\nmulti.set('key2', 'value2');\nawait multi.exec();\n```\n\n#### ❌ Avoid: Manual Transaction Construction\n\nDo not manually construct transactions by sending individual MULTI/EXEC commands:\n\n```typescript\n// Incorrect and potentially dangerous\nawait client.sendCommand(['MULTI']);\nawait client.sendCommand(['SET', 'key1', 'value1']);\nawait client.sendCommand(['SET', 'key2', 'value2']);\nawait client.sendCommand(['EXEC']); // Risk of AUTH command being injected before EXEC\n```\n\n## Error Handling\n\nThe provider includes built-in retry mechanisms for transient errors:\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForClientCredentials({\n  // ... other config ...\n  tokenManagerConfig: {\n    retry: {\n      maxAttempts: 3,\n      initialDelayMs: 100,\n      maxDelayMs: 1000,\n      backoffMultiplier: 2\n    }\n  }\n});\n```\n","isInternal":false,"tokens":1404,"sizeBytes":6757},{"name":"README.md","path":"packages/json/README.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/packages/json/README.md","title":"json Documentation","category":"plugin-manifest","format":"markdown","content":"# @redis/json\n\nThis package provides support for the [RedisJSON](https://redis.io/docs/latest/develop/data-types/json/) module, which adds JSON as a native data type to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisJSON module installed.\n\n## Usage\n\nFor a complete example, see [`managing-json.js`](https://github.com/redis/node-redis/blob/master/examples/managing-json.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n\n### Storing JSON Documents in Redis\n\nThe [`JSON.SET`](https://redis.io/commands/json.set/) command stores a JSON value at a given JSON Path in a Redis key.\n\nHere, we'll store a JSON document in the root of the Redis key \"`mydoc`\":\n\n```javascript\nawait client.json.set('noderedis:jsondata', '$', {\n  name: 'Roberta McDonald',\n  pets: [{\n    name: 'Rex',\n    species: 'dog',\n    age: 3,\n    isMammal: true\n  }, {\n    name: 'Goldie',\n    species: 'fish',\n    age: 2,\n    isMammal: false\n  }]\n});\n```\n\nFor more information about RedisJSON's path syntax, [check out the documentation](https://redis.io/docs/latest/develop/data-types/json/path).\n\n### Retrieving JSON Documents from Redis\n\nWith RedisJSON, we can retrieve all or part(s) of a JSON document using the [`JSON.GET`](https://redis.io/commands/json.get/) command and one or more JSON Paths. Let's get the name and age of one of the pets:\n\n```javascript\nconst results = await client.json.get('noderedis:jsondata', {\n  path: [\n    '.pets[1].name',\n    '.pets[1].age'\n  ]\n});\n```\n\n`results` will contain the following:\n\n```javascript\n { '.pets[1].name': 'Goldie', '.pets[1].age': 2 }\n```\n\n### Performing Atomic Updates on JSON Documents Stored in Redis\n\nRedisJSON includes commands that can atomically update values in a JSON document, in place in Redis without having to first retrieve the entire document.\n\nUsing the [`JSON.NUMINCRBY`](https://redis.io/commands/json.numincrby/) command, we can update the age of one of the pets like this:\n\n```javascript\nawait client.json.numIncrBy('noderedis:jsondata', '.pets[1].age', 1);\n```\n\nAnd we can add a new object to the pets array with the [`JSON.ARRAPPEND`](https://redis.io/commands/json.arrappend/) command:\n\n```javascript\nawait client.json.arrAppend('noderedis:jsondata', '.pets', {\n  name: 'Robin',\n  species: 'bird',\n  age: 1,\n  isMammal: false\n});\n```\n","isInternal":false,"tokens":635,"sizeBytes":2438},{"name":"README.md","path":"packages/redis/README.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/packages/redis/README.md","title":"redis Documentation","category":"plugin-manifest","format":"markdown","content":"# Node-Redis\n\n[![Tests](https://img.shields.io/github/actions/workflow/status/redis/node-redis/tests.yml?branch=master)](https://github.com/redis/node-redis/actions/workflows/tests.yml)\n[![Coverage](https://codecov.io/gh/redis/node-redis/branch/master/graph/badge.svg?token=xcfqHhJC37)](https://codecov.io/gh/redis/node-redis)\n[![License](https://img.shields.io/github/license/redis/node-redis.svg)](https://github.com/redis/node-redis/blob/master/LICENSE)\n\n[![Discord](https://img.shields.io/discord/697882427875393627.svg?style=social&logo=discord)](https://discord.gg/redis)\n[![Twitch](https://img.shields.io/twitch/status/redisinc?style=social)](https://www.twitch.tv/redisinc)\n[![YouTube](https://img.shields.io/youtube/channel/views/UCD78lHSwYqMlyetR0_P4Vig?style=social)](https://www.youtube.com/redisinc)\n[![Twitter](https://img.shields.io/twitter/follow/redisinc?style=social)](https://twitter.com/redisinc)\n\nnode-redis is a modern, high performance [Redis](https://redis.io) client for Node.js.\n\n## How do I Redis?\n\n[Learn for free at Redis University](https://university.redis.com/)\n\n[Build faster with the Redis Launchpad](https://launchpad.redis.com/)\n\n[Try the Redis Cloud](https://redis.com/try-free/)\n\n[Dive in developer tutorials](https://developer.redis.com/)\n\n[Join the Redis community](https://redis.com/community/)\n\n[Work at Redis](https://redis.com/company/careers/jobs/)\n\n## Installation\n\nStart a redis via docker:\n\n```bash\ndocker run -p 6379:6379 -d redis:8.0-rc1\n```\n\nTo install node-redis, simply:\n\n```bash\nnpm install redis\n```\n> \"redis\" is the \"whole in one\" package that includes all the other packages. If you only need a subset of the commands,\n> you can install the individual packages. See the list below.\n\n## Packages\n\n| Name                                           | Description                                                                                 |\n| ---------------------------------------------- | ------------------------------------------------------------------------------------------- |\n| [`redis`](https://github.com/redis/node-redis/tree/master/packages/redis)                    | The client with all the [\"redis-stack\"](https://github.com/redis-stack/redis-stack) modules |\n| [`@redis/client`](https://github.com/redis/node-redis/tree/master/packages/client)           | The base clients (i.e `RedisClient`, `RedisCluster`, etc.)                                  |\n| [`@redis/bloom`](https://github.com/redis/node-redis/tree/master/packages/bloom)             | [Redis Bloom](https://redis.io/docs/data-types/probabilistic/) commands                     |\n| [`@redis/json`](https://github.com/redis/node-redis/tree/master/packages/json)               | [Redis JSON](https://redis.io/docs/data-types/json/) commands                               |\n| [`@redis/search`](https://github.com/redis/node-redis/tree/master/packages/search)           | [RediSearch](https://redis.io/docs/interact/search-and-query/) commands                     |\n| [`@redis/time-series`](https://github.com/redis/node-redis/tree/master/packages/time-series) | [Redis Time-Series](https://redis.io/docs/data-types/timeseries/) commands                  |\n| [`@redis/entraid`](https://github.com/redis/node-redis/tree/master/packages/entraid)         | Secure token-based authentication for Redis clients using Microsoft Entra ID                |\n\n> Looking for a high-level library to handle object mapping?\n> See [redis-om-node](https://github.com/redis/redis-om-node)!\n\n\n## Usage\n\n### Basic Example\n\n```typescript\nimport { createClient } from \"redis\";\n\nconst client = await createClient()\n  .on(\"error\", (err) => console.log(\"Redis Client Error\", err))\n  .connect();\n\nawait client.set(\"key\", \"value\");\nconst value = await client.get(\"key\");\nclient.destroy();\n```\n\nThe above code connects to localhost on port 6379. To connect to a different host or port, use a connection string in\nthe format `redis[s]://[[username][:password]@][host][:port][/db-number]`:\n\n```typescript\ncreateClient({\n  url: \"redis://alice:foobared@awesome.redis.server:6380\",\n});\n```\n\nYou can also use discrete parameters, UNIX sockets, and even TLS to connect. Details can be found in\nthe [client configuration guide](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md).\n\nTo check if the the client is connected and ready to send commands, use `client.isReady` which returns a boolean.\n`client.isOpen` is also available. This returns `true` when the client's underlying socket is open, and `false` when it\nisn't (for example when the client is still connecting or reconnecting after a network error).\n\n### Redis Commands\n\nThere is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed\nusing the raw Redis command names (`HSET`, `HGETALL`, etc.) and a friendlier camel-cased version (`hSet`, `hGetAll`,\netc.):\n\n```typescript\n// raw Redis commands\nawait client.HSET(\"key\", \"field\", \"value\");\nawait client.HGETALL(\"key\");\n\n// friendly JavaScript commands\nawait client.hSet(\"key\", \"field\", \"value\");\nawait client.hGetAll(\"key\");\n```\n\nModifiers to commands are specified using a JavaScript object:\n\n```typescript\nawait client.set(\"key\", \"value\", {\n  EX: 10,\n  NX: true,\n});\n```\n\nCommands that take several values of the same kind, such as `DEL`, `HDEL` or `LPUSH`, accept either a single value or\nan array:\n\n```typescript\nawait client.hDel(\"key\", \"field\");\nawait client.hDel(\"key\", [\"field1\", \"field2\"]);\n```\n\nDo not spread the values into separate arguments (`client.hDel(\"key\", ...fields)`). Only the first value is sent, and\nthe rest are silently ignored.\n\nReplies will be transformed into useful data structures:\n\n```typescript\nawait client.hGetAll(\"key\"); // { field1: 'value1', field2: 'value2' }\nawait client.hVals(\"key\"); // ['value1', 'value2']\n```\n\n`Buffer`s are supported as well:\n\n```typescript\nconst client = createClient().withTypeMapping({\n  [RESP_TYPES.BLOB_STRING]: Buffer\n});\n\nawait client.hSet(\"key\", \"field\", Buffer.from(\"value\")); // 'OK'\nawait client.hGet(\"key\", \"field\"); // { field: <Buffer 76 61 6c 75 65> }\n\n```\n\nFor commands that return serialized binary payloads, such as `DUMP`, map blob strings to `Buffer` before using the result with commands like `RESTORE`:\n\n```typescript\nconst binaryClient = createClient().withTypeMapping({\n  [RESP_TYPES.BLOB_STRING]: Buffer\n});\n\nconst dump = await binaryClient.dump(\"source\");\nawait binaryClient.restore(\"destination\", 0, dump);\n```\n\n### Unsupported Redis Commands\n\nIf you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use `.sendCommand()`:\n\n```typescript\nawait client.sendCommand([\"SET\", \"key\", \"value\", \"NX\"]); // 'OK'\n\nawait client.sendCommand([\"HGETALL\", \"key\"]); // ['key1', 'field1', 'key2', 'field2']\n```\n\n### Transactions (Multi/Exec)\n\nStart a [transaction](https://redis.io/topics/transactions) by calling `.multi()`, then chaining your commands. When\nyou're done, call `.exec()` and you'll get an array back with your results:\n\n```typescript\nawait client.set(\"another-key\", \"another-value\");\n\nconst [setKeyReply, otherKeyValue] = await client\n  .multi()\n  .set(\"key\", \"value\")\n  .get(\"another-key\")\n  .exec(); // ['OK', 'another-value']\n```\n\nYou can also [watch](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set) keys by calling\n`.watch()`. Your transaction will abort if any of the watched keys change.\n\n\n### Blocking Commands\n\nIn v4, `RedisClient` had the ability to create a pool of connections using an \"Isolation Pool\" on top of the \"main\"\nconnection. However, there was no way to use the pool without a \"main\" connection:\n\n```javascript\nconst client = await createClient()\n  .on(\"error\", (err) => console.error(err))\n  .connect();\n\nawait client.ping(client.commandOptions({ isolated: true }));\n```\n\nIn v5 we've extracted this pool logic into its own class—`RedisClientPool`:\n\n```javascript\nconst pool = await createClientPool()\n  .on(\"error\", (err) => console.error(err))\n  .connect();\n\nawait pool.ping();\n```\n\n\n### Pub/Sub\n\nSee the [Pub/Sub overview](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md).\n\n### Scan Iterator\n\n[`SCAN`](https://redis.io/commands/scan) results can be looped over\nusing [async iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator):\n\n```typescript\nfor await (const key of client.scanIterator()) {\n  // use the key!\n  await client.get(key);\n}\n```\n\nThis works with `HSCAN`, `SSCAN`, and `ZSCAN` too:\n\n```typescript\nfor await (const { field, value } of client.hScanIterator(\"hash\")) {\n}\nfor await (const member of client.sScanIterator(\"set\")) {\n}\nfor await (const { score, value } of client.zScanIterator(\"sorted-set\")) {\n}\n```\n\nYou can override the default options by providing a configuration object:\n\n```typescript\nclient.scanIterator({\n  TYPE: \"string\", // `SCAN` only\n  MATCH: \"patter*\",\n  COUNT: 100,\n});\n```\n\n### Disconnecting\n\nThe `QUIT` command has been deprecated in Redis 7.2 and should now also be considered deprecated in Node-Redis. Instead\nof sending a `QUIT` command to the server, the client can simply close the network connection.\n\n`client.QUIT/quit()` is replaced by `client.close()`. and, to avoid confusion, `client.disconnect()` has been renamed to\n`client.destroy()`.\n\n```typescript\nclient.destroy();\n```\n### Client Side Caching\n\n[Client Side Caching](https://redis.io/docs/manual/client-side-caching/) keeps command replies in a local cache. The Redis server notifies the client when cached replies are no longer valid. Requires RESP3.\n\n```typescript\nconst client = createClient({\n  clientSideCache: {\n    ttl: 0,                // Time-to-live (0 = no expiration)\n    maxEntries: 0,         // Maximum entries (0 = unlimited)\n    evictPolicy: \"LRU\",    // Eviction policy: \"LRU\" or \"FIFO\"\n    trackingMode: \"optin\"  // \"plain\" (default), \"optin\", or \"optout\"\n  }\n});\nawait client.connect();\n\nawait client.withCommandOptions({ cache: true }).get(\"user:42\"); // cached\nawait client.get(\"counter:hits\");                                // not cached in \"optin\" mode\n```\n\nSee [Client Side Caching](https://github.com/redis/node-redis/blob/master/docs/client-side-caching.md) for tracking modes, choosing what is cached, and more.\n\n### Auto-Pipelining\n\nNode Redis will automatically pipeline requests that are made during the same \"tick\".\n\n```typescript\nclient.set(\"Tm9kZSBSZWRpcw==\", \"users:1\");\nclient.sAdd(\"users:1:tokens\", \"Tm9kZSBSZWRpcw==\");\n```\n\nOf course, if you don't do something with your Promises you're certain to\nget [unhandled Promise exceptions](https://nodejs.org/api/process.html#process_event_unhandledrejection). To take\nadvantage of auto-pipelining and handle your Promises, use `Promise.all()`.\n\n```typescript\nawait Promise.all([\n  client.set(\"Tm9kZSBSZWRpcw==\", \"users:1\"),\n  client.sAdd(\"users:1:tokens\", \"Tm9kZSBSZWRpcw==\"),\n]);\n```\n\n### Programmability\n\nSee the [Programmability overview](https://github.com/redis/node-redis/blob/master/docs/programmability.md).\n\n### Clustering\n\nCheck out the [Clustering Guide](https://github.com/redis/node-redis/blob/master/docs/clustering.md) when using Node Redis to connect to a Redis Cluster.\n\n### Events\n\nThe Node Redis client class is an Nodejs EventEmitter and it emits an event each time the network status changes:\n\n| Name                    | When                                                                               | Listener arguments                                        |\n| ----------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------- |\n| `connect`               | Initiating a connection to the server                                              | _No arguments_                                            |\n| `ready`                 | Client is ready to use                                                             | _No arguments_                                            |\n| `end`                   | Connection has been closed (via `.disconnect()`)                                   | _No arguments_                                            |\n| `error`                 | An error has occurred—usually a network issue such as \"Socket closed unexpectedly\" | `(error: Error)`                                          |\n| `reconnecting`          | Client is trying to reconnect to the server                                        | _No arguments_                                            |\n| `terminated`             | Reconnection has stopped because `reconnectStrategy` returned `false` or an `Error`; emitted before the companion `error` | `(cause: Error)`                                           |\n| `sharded-channel-moved` | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event)                          | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event) |\n\n> :warning: You **MUST** listen to `error` events. If a client doesn't have at least one `error` listener registered and\n> an `error` occurs, that error will be thrown and the Node.js process will exit. See the [ > `EventEmitter` docs](https://nodejs.org/api/events.html#events_error_events) for more details.\n\n> The client will not emit [any other events](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md#all-the-removed-events) beyond those listed above.\n\n## Supported Redis versions\n\nNode Redis is supported with the following versions of Redis:\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 8.0.z   | :heavy_check_mark: |\n| 7.4.z   | :heavy_check_mark: |\n| 7.2.z   | :heavy_check_mark: |\n| < 7.2   | :x:                |\n\n> Node Redis should work with older versions of Redis, but it is not fully tested and we cannot offer support.\n\n## Migration\n\n- [From V3 to V4](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md)\n- [From V4 to V5](https://github.com/redis/node-redis/blob/master/docs/v4-to-v5.md)\n- [V5](https://github.com/redis/node-redis/blob/master/docs/v5.md)\n\n## Contributing\n\nIf you'd like to contribute, check out the [contributing guide](https://github.com/redis/node-redis/blob/master/CONTRIBUTING.md).\n\nThank you to all the people who already contributed to Node Redis!\n\n[![Contributors](https://contrib.rocks/image?repo=redis/node-redis)](https://github.com/redis/node-redis/graphs/contributors)\n\n## License\n\nThis repository is licensed under the \"MIT\" license. See [LICENSE](https://github.com/redis/node-redis/blob/master/LICENSE).\n","isInternal":false,"tokens":3475,"sizeBytes":14643},{"name":"README.md","path":"packages/search/README.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/packages/search/README.md","title":"search Documentation","category":"plugin-manifest","format":"markdown","content":"# @redis/search\n\nThis package provides support for the [RediSearch](https://redis.io/docs/interact/search-and-query/) module, which adds indexing and querying support for data stored in Redis Hashes or as JSON documents with the [RedisJSON](https://redis.io/docs/data-types/json/) module.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RediSearch module installed. To index and query JSON documents, you'll also need to add the RedisJSON module.\n\n## Usage\n\nFor complete examples, see [`search-hashes.js`](https://github.com/redis/node-redis/blob/master/examples/search-hashes.js) and [`search-json.js`](https://github.com/redis/node-redis/blob/master/examples/search-json.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n\n### Indexing and Querying Data in Redis Hashes\n\n#### Creating an Index\n\nBefore we can perform any searches, we need to tell RediSearch how to index our data, and which Redis keys to find that data in.  The [FT.CREATE](https://redis.io/commands/ft.create) command creates a RediSearch index. Here's how to use it to create an index we'll call `idx:animals` where we want to index hashes containing `name`, `species` and `age` fields, and whose key names in Redis begin with the prefix `noderedis:animals`:\n\n```javascript\nawait client.ft.create('idx:animals', {\n  name: {\n    type: SCHEMA_FIELD_TYPE.TEXT,\n    SORTABLE: true\n  },\n  species: SCHEMA_FIELD_TYPE.TAG,\n  age: SCHEMA_FIELD_TYPE.NUMERIC\n}, {\n  ON: 'HASH',\n  PREFIX: 'noderedis:animals'\n});\n```\n\nSee the [`FT.CREATE` documentation](https://redis.io/commands/ft.create/#description) for information about the different field types and additional options.\n\n#### Indexing a Field Multiple Times\n\nYou can index the same field multiple times with different types or aliases by using an array:\n\n```javascript\nawait client.ft.create('idx:products', {\n  sku: [\n    { type: SCHEMA_FIELD_TYPE.TEXT, AS: 'sku_text' },\n    { type: SCHEMA_FIELD_TYPE.TAG, AS: 'sku_tag', SORTABLE: true }\n  ]\n}, {\n  ON: 'HASH',\n  PREFIX: 'product:'\n});\n```\n\nThis allows querying the same field using different search strategies.\n\n#### Querying the Index\n\nOnce we've created an index, and added some data to Redis hashes whose keys begin with the prefix `noderedis:animals`, we can start writing some search queries.  RediSearch supports a rich query syntax for full-text search, faceted search, aggregation and more.  Check out the [`FT.SEARCH` documentation](https://redis.io/commands/ft.search) and the [query syntax reference](https://redis.io/docs/interact/search-and-query/query) for more information.\n\nLet's write a query to find all the animals where the `species` field has the value `dog`:\n\n```javascript\nconst results = await client.ft.search('idx:animals', '@species:{dog}');\n```\n\n`results` looks like this:\n\n```javascript\n{\n  total: 2,\n  documents: [\n    {\n      id: 'noderedis:animals:4',\n      value: {\n        name: 'Fido',\n        species: 'dog',\n        age: '7'\n      }\n    },\n    {\n      id: 'noderedis:animals:3',\n      value: {\n        name: 'Rover',\n        species: 'dog',\n        age: '9'\n      }\n    }\n  ]\n}\n```\n\n### Indexing and Querying Data with RedisJSON\n\nRediSearch can also index and query JSON documents stored in Redis using the RedisJSON module.  The approach is similar to that for indexing and searching data in hashes, but we can now use JSON Path like syntax and the data no longer has to be flat name/value pairs - it can contain nested objects and arrays.\n\n#### Creating an Index\n\nAs before, we create an index with the `FT.CREATE` command, this time specifying we want to index JSON documents that look like this:\n\n```javascript\n{\n  name: 'Alice',\n  age: 32,\n  coins: 100\n}\n```\n\nEach document represents a user in some system, and users have name, age and coins properties.\n\nOne way we might choose to index these documents is as follows:\n\n```javascript\nawait client.ft.create('idx:users', {\n  '$.name': {\n    type: SCHEMA_FIELD_TYPE.TEXT,\n    SORTABLE: 'UNF'\n  },\n  '$.age': {\n    type: SCHEMA_FIELD_TYPE.NUMERIC,\n    AS: 'age'\n  },\n  '$.coins': {\n    type: SCHEMA_FIELD_TYPE.NUMERIC,\n    AS: 'coins'\n  }\n}, {\n  ON: 'JSON',\n  PREFIX: 'noderedis:users'\n});\n```\n\nNote that we're using JSON Path to specify where the fields to index are in our JSON documents, and the `AS` clause to define a name/alias for each field.  We'll use these when writing queries.\n\n#### Querying the Index\n\nNow we have an index and some data stored as JSON documents in Redis (see the [JSON package documentation](https://github.com/redis/node-redis/tree/master/packages/json) for examples of how to store JSON), we can write some queries...\n\nWe'll use the [RediSearch query language](https://redis.io/docs/interact/search-and-query/query) and [`FT.SEARCH`](https://redis.io/commands/ft.search) command.  Here's a query to find users under the age of 30:\n\n```javascript\nawait client.ft.search('idx:users', '@age:[0 30]');\n```\n","isInternal":false,"tokens":1253,"sizeBytes":5039},{"name":"README.md","path":"packages/time-series/README.md","rawUrl":"https://raw.githubusercontent.com/redis/node-redis/HEAD/packages/time-series/README.md","title":"time-series Documentation","category":"plugin-manifest","format":"markdown","content":"# @redis/time-series\n\nThis package provides support for the [RedisTimeSeries](https://redis.io/docs/data-types/timeseries/) module, which adds a time series data structure to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisTimeSeries module installed.\n\n## Usage\n\nFor a complete example, see [`time-series.js`](https://github.com/redis/node-redis/blob/master/examples/time-series.js) in the Node Redis examples folder.\n\n### Creating Time Series data structure in Redis\n\nThe [`TS.CREATE`](https://oss.redis.com/redistimeseries/commands/#tscreate) command creates a new time series.\n\nHere, we'll create a new time series \"`temperature`\":\n\n```javascript\n\nimport { createClient } from 'redis';\nimport { TimeSeriesDuplicatePolicies, TimeSeriesEncoding, TIME_SERIES_AGGREGATION_TYPE } from '@redis/time-series';\n\n...\nconst created = await client.ts.create('temperature', {\n  RETENTION: 86400000, // 1 day in milliseconds\n  ENCODING: TimeSeriesEncoding.UNCOMPRESSED, // No compression - When not specified, the option is set to COMPRESSED\n  DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.BLOCK, // No duplicates - When not specified: set to the global DUPLICATE_POLICY configuration of the database (which by default, is BLOCK).\n});\n\nif (created === 'OK') {\n  console.log('Created timeseries.');\n} else {\n  console.log('Error creating timeseries :(');\n  process.exit(1);\n}\n```\n\n### Adding new value to a Time Series data structure in Redis\n\nWith RedisTimeSeries, we can add a single value to time series data structure using the [`TS.ADD`](https://redis.io/commands/ts.add/) command and if we would like to add multiple values we can use the [`TS.MADD`](https://redis.io/commands/ts.madd/) command.\n\n```javascript\n\nlet value = Math.floor(Math.random() * 1000) + 1; // Random data point value\nlet currentTimestamp = 1640995200000; // Jan 1 2022 00:00:00\nlet num = 0;\n\nwhile (num < 10000) {\n  // Add a new value to the timeseries, providing our own timestamp:\n  // https://redis.io/commands/ts.add/\n  await client.ts.add('temperature', currentTimestamp, value);\n  console.log(`Added timestamp ${currentTimestamp}, value ${value}.`);\n\n  num += 1;\n  value = Math.floor(Math.random() * 1000) + 1; // Get another random value\n  currentTimestamp += 1000; // Move on one second.\n}\n\n// Add multiple values to the timeseries in round trip to the server:\n// https://redis.io/commands/ts.madd/\nconst response = await client.ts.mAdd([{\n  key: 'temperature',\n  timestamp: currentTimestamp + 60000,\n  value: Math.floor(Math.random() * 1000) + 1\n}, {\n  key: 'temperature',\n  timestamp: currentTimestamp + 120000,\n  value: Math.floor(Math.random() * 1000) + 1\n}]);\n```\n\n### Retrieving Time Series data from Redis\n\nWith RedisTimeSeries, we can retrieve the time series data using the [`TS.RANGE`](https://redis.io/commands/ts.range/) command by passing the criteria as follows:\n\n```javascript\n// Query the timeseries with TS.RANGE:\n// https://redis.io/commands/ts.range/\nconst fromTimestamp = 1640995200000; // Jan 1 2022 00:00:00\nconst toTimestamp = 1640995260000; // Jan 1 2022 00:01:00\nconst rangeResponse = await client.ts.range('temperature', fromTimestamp, toTimestamp, {\n  // Group into 10 second averages.\n  AGGREGATION: {\n    type: TIME_SERIES_AGGREGATION_TYPE.AVG,\n    timeBucket: 10000\n  }\n});\n\nconsole.log('RANGE RESPONSE:');\n// rangeResponse looks like:\n// [\n//   { timestamp: 1640995200000, value: 356.8 },\n//   { timestamp: 1640995210000, value: 534.8 },\n//   { timestamp: 1640995220000, value: 481.3 },\n//   { timestamp: 1640995230000, value: 437 },\n//   { timestamp: 1640995240000, value: 507.3 },\n//   { timestamp: 1640995250000, value: 581.2 },\n//   { timestamp: 1640995260000, value: 600 }\n// ]\n```\n\nFor multiple aggregations in one command, use the dedicated `*MultiAggr` methods:\n\n```javascript\nconst multiRangeResponse = await client.ts.rangeMultiAggr('temperature', fromTimestamp, toTimestamp, {\n  AGGREGATION: {\n    types: [\n      TIME_SERIES_AGGREGATION_TYPE.MIN,\n      TIME_SERIES_AGGREGATION_TYPE.MAX,\n      TIME_SERIES_AGGREGATION_TYPE.AVG\n    ],\n    timeBucket: 10000\n  }\n});\n\n// multiRangeResponse looks like:\n// [\n//   { timestamp: 1640995200000, values: [120, 580, 356.8] },\n//   ...\n// ]\n```\n\nEquivalent multi-aggregation helpers are also available for reverse and multi-key variants:\n`revRangeMultiAggr`, `mRangeMultiAggr`, `mRevRangeMultiAggr`, `mRangeWithLabelsMultiAggr`,\n`mRevRangeWithLabelsMultiAggr`, `mRangeSelectedLabelsMultiAggr`, and `mRevRangeSelectedLabelsMultiAggr`.\n\n### Altering Time Series data Stored in Redis\n\nRedisTimeSeries includes commands that can update values in a time series data structure.\n\nUsing the [`TS.ALTER`](https://redis.io/commands/ts.alter/) command, we can update time series retention like this:\n\n```javascript\n// https://redis.io/commands/ts.alter/\nconst alterResponse = await client.ts.alter('temperature', {\n  RETENTION: 0 // Keep the entries forever\n});\n```\n\n### Retrieving Information about the timeseries Stored in Redis\n\nRedisTimeSeries also includes commands that can help to view the information on the state of a time series.\n\nUsing the [`TS.INFO`](https://redis.io/commands/ts.info/) command, we can view timeseries information like this:\n\n```javascript\n// Get some information about the state of the timeseries.\n// https://redis.io/commands/ts.info/\nconst tsInfo = await client.ts.info('temperature');\n\n// tsInfo looks like this:\n// {\n//   totalSamples: 1440,\n//   memoryUsage: 28904,\n//   firstTimestamp: 1641508920000,\n//   lastTimestamp: 1641595320000,\n//   retentionTime: 86400000,\n//   chunkCount: 7,\n//   chunkSize: 4096,\n//   chunkType: 'uncompressed',\n//   duplicatePolicy: 'block',\n//   labels: [],\n//   sourceKey: null,\n//   rules: []\n// }\n```\n","isInternal":false,"tokens":1545,"sizeBytes":5834}],"systemPromptSnippet":"<yakaai_skills repo=\"redis/node-redis\">\n<!-- File: AGENTS.md (Tokens: ~986 | Category: root-instruction) -->\n# AGENTS.md\n\nGuidance for agents working in this repo.\n\n## What this is\n\n`node-redis` — modern Redis client for Node.js. A **monorepo** (npm workspaces) of\npublishable packages. Requires Node `>= 20`. TypeScript throughout.\n\n## Packages (`packages/`)\n\n| Package | Purpose |\n| --- | --- |\n| `redis` | \"All-in-one\" meta-package re-exporting client + all modules |\n| `client` (`@redis/client`) | Core: `RedisClient`, `RedisCluster`, `RedisSentinel`, pool, RESP codec, command framework |\n| `bloom` (`@redis/bloom`) | Probabilistic commands (bloom, cuckoo, count-min, t-digest, top-k) |\n| `json` (`@redis/json`) | RedisJSON commands |\n| `search` (`@redis/search`) | RediSearch commands |\n| `time-series` (`@redis/time-series`) | Time-series commands |\n| `entraid` (`@redis/entraid`) | Microsoft Entra ID token auth |\n| `test-utils` (`@redis/test-utils`) | Shared test harness; spins up Redis via docker |\n\nModule packages depend on `@redis/client` and follow the same command structure.\n\n## Core layout — `packages/client/lib/`\n\n- `client/` — connection internals: `index.ts` (RedisClient), `socket.ts`, `commands-queue.ts`, `parser.ts`, `pool.ts`, `pub-sub.ts`, `cache.ts`\n- `cluster/`, `sentinel/` — cluster & sentinel clients\n- `commands/` — one file per Redis command (e.g. `GET.ts`) + `index.ts` registry\n- `RESP/` — RESP2/RESP3 protocol: `encoder.ts`, `decoder.ts`, `types.ts`\n- `authx/` — auth/credential providers\n\n## Command pattern\n\nEach command is `<NAME>.ts` exporting a `Command` object:\n\n```typescript\nexport default {\n  CACHEABLE: true,\n  IS_READ_ONLY: true,\n  parseCommand(parser: CommandParser, key: RedisArgument) {\n    parser.push('GET');\n    parser.pushKey(key);\n  },\n  transformReply: undefined as unknown as () => BlobStringReply | NullReply\n} as const satisfies Command;\n```\n\n- `parseCommand` builds wire args via `CommandParser` (`push`, `pushKey`, ...).\n- `transformReply` maps reply to JS type; `undefined` = pass-through. Can be keyed by RESP version `{ 2: ..., 3: ... }`.\n- Register new command in the package's `commands/index.ts` (import + map entry). RESP3 is default — no extra RESP3 test needed for new commands.\n- JSDoc on commands is checked: `npm run check:command-jsdoc`.\n\n## Tests\n\n- Co-located `<NAME>.spec.ts` next to source. Mocha + `tsx`, `node:assert`.\n- `testUtils.testAll(name, fn, { client, cluster })` runs same test across server + cluster topologies (see `test-utils.ts`, `GLOBAL`).\n- **Docker required** — test-utils starts real Redis containers.\n- Pure arg/reply tests use `parseArgs(COMMAND, ...args)`.\n\nCommands:\n- `npm test` — full suite, all workspaces (runs `cleanup` first).\n- `npm test -w @redis/client` — one package.\n- Single file from root: `npm run test-single -- <path-to-spec>`.\n- `npm run build` — `tsc --build` (project references; build before cross-package work).\n- Build can break on stale `dist/` from project references. Clean rebuild:\n  ```bash\n  find packages -type d -name \"dist\" -exec rm -rf {} + && npm run build\n  ```\n- `npm run lint` — lint changed files only; `npm run lint:all` for everything.\n\n## Conventions\n\n- TypeScript strict mode; `noUnusedLocals` on. Target ES2022 / NodeNext modules.\n- Raw command names (`HSET`) and camelCase aliases (`hSet`) both exposed.\n- Conventional Commits. Per-package releases via `release-it` (`npm run release`).\n- Keep company-internal refs (Jira/Confluence IDs, internal links) out of OSS commits, branches, PRs, code.\n\n## Docs\n\nDeep-dive guides in `docs/` (client-configuration, clustering, sentinel, pool, RESP, transactions, programmability, pub-sub, scan-iterators, migration guides). Runnable examples in `examples/`, `doctests/`.\n\n<!-- File: .agents/skills/bump-test-image/SKILL.md (Tokens: ~1481 | Category: anthropic-skill) -->\n---\nname: bump-test-image\ndescription: Bump the default Redis docker test image (redislabs/client-libs-test) in the shared DEFAULT_DOCKER_CONFIG and the CI matrix, then force-push the bump-test-image branch and open a PR against upstream. User-invoked only.\ndisable-model-invocation: true\nargument-hint: <image-tag> [redis-version]\n---\n\n# Bump default docker test image\n\nUpdates the default `redislabs/client-libs-test` docker tag used by tests (a single shared config in `@redis/test-utils`), adjusts the CI matrix, commits on a freshly-reset `bump-test-image` branch, force-pushes it, and opens (or updates) a PR against `redis/node-redis` master.\n\n## Inputs\n\n- **tag** (required): the new docker tag, e.g. `8.8.0`, `8.8-rc1`, `custom-28772936538-debian`, `unstable-24425681442-debian`. If given as `redislabs/client-libs-test:<tag>`, strip the image name.\n- **version** (sometimes required): the Redis version the tag represents, e.g. `8.10`.\n  - Derivable when the tag is version-shaped: `8.8.0` → `8.8`, `8.8-rc1` → `8.8`, `8.8-m03` → `8.8`.\n  - NOT derivable for `custom-*` / `unstable-*` tags — if missing, ask the user which Redis version the tag maps to before doing anything else.\n\nIf no tag was given at all, ask for it and stop.\n\n## Step 1 — Sync master from upstream\n\nRemotes: `upstream` = `redis/node-redis` (canonical), `origin` = the user's fork.\n\n```bash\ngit fetch upstream\ngit rev-parse --abbrev-ref HEAD   # remember current branch\n```\n\nUpdate local master to upstream:\n\n- If currently on master: `git reset --hard upstream/master` (only if working tree is clean — if dirty, stop and tell the user).\n- Otherwise: `git fetch upstream master:master` (fails safely if master has local-only commits; if it fails, stop and report).\n\n## Step 2 — Reset the work branch\n\n```bash\ngit checkout -B bump-test-image master\n```\n\n`-B` creates or hard-resets the branch to latest master. Any previous bump commits on it are intentionally discarded (the branch is recycled for each bump; force-push later matches this).\n\n## Step 3 — Edit the shared default config\n\nThe default image config lives in exactly ONE place:\n\n`packages/test-utils/lib/index.ts` → `DEFAULT_DOCKER_CONFIG`:\n\n```typescript\nexport const DEFAULT_DOCKER_CONFIG: TestUtilsConfig = {\n  dockerImageName: 'redislabs/client-libs-test',\n  dockerImageTagArgument: 'redis-tag',\n  dockerImageVersionArgument: 'redis-version',\n  defaultDockerVersion: { tag: '<old-tag>', version: '<old-version>' }\n};\n```\n\nNote the old tag/version, then replace them with the new values on the `defaultDockerVersion` line. Nothing else in the file changes.\n\nIf `DEFAULT_DOCKER_CONFIG` does not exist in that file (e.g. master predates the dedupe refactor), STOP and tell the user — do not fall back to editing per-package test-utils files without confirmation.\n\n## Step 4 — Update the CI matrix\n\nFile: `.github/workflows/tests.yml`, `matrix` → `redis` entries of the form:\n\n```yaml\n          - tag: \"8.8.0\"\n            version: \"8.8\"\n```\n\nTwo cases, decided by comparing the new `version` with the old default version from Step 3:\n\n1. **New Redis version** (e.g. default moves 8.8 → 8.10): **add** a new `- tag/version` entry at the end of the matrix list, keeping the previous entry so CI retains coverage of the prior version.\n2. **Same Redis version** (rc → GA, or a newer custom build of the same version): **replace** the `tag` of the existing entry for that version; the `version` line stays.\n\n## Step 5 — Verify\n\n```bash\ngrep -rn \"defaultDockerVersion:\" packages --include=\"*.ts\" | grep -v dist | grep -v node_modules\n```\n\nExactly one config line (in `packages/test-utils/lib/index.ts`) must show the new tag/version; any other hits are the type definition / JSDoc examples, not real defaults. Also confirm `git diff --stat` touches exactly 2 files: `packages/test-utils/lib/index.ts` + `.github/workflows/tests.yml`.\n\n## Step 6 — Commit\n\nFill in this template exactly (Conventional Commits):\n\n```\nchore(tests): bump default docker test image to <X>\n\nBump the default Redis test image from <old-tag> to <new-tag> (Redis\n<version>) in DEFAULT_DOCKER_CONFIG, and <matrix-action> the CI matrix.\n```\n\n- `<X>`: the human-meaningful version when the tag is version-shaped (`8.10`, `8.8.0`, `8.8-rc1`, `8.8-m03`); the full tag for `unstable-*` builds; the version for `custom-*` tags (the actual tag already appears in the body as `<new-tag>`).\n- `<matrix-action>`: either `add an <version> entry to` (keeping prior-version coverage) or `update the <version> entry in`, matching what Step 4 did.\n\nExample:\n\n```\nchore(tests): bump default docker test image to 8.10\n\nBump the default Redis test image from 8.8.0 to custom-28772936538-debian\n(Redis 8.10) in DEFAULT_DOCKER_CONFIG, and add an 8.10 entry to the CI\nmatrix.\n```\n\nKeep any company-internal references (Jira/Confluence IDs, internal links) out of the commit and PR.\n\n## Step 7 — Force-push and PR\n\n```bash\ngit push -f origin bump-test-image\n```\n\nThen check for an existing open PR before creating one:\n\n```bash\ngh pr list --repo redis/node-redis --head <fork-owner>:bump-test-image --state open\n```\n\n(`<fork-owner>` = GitHub owner from the `origin` remote URL.)\n\n- If a PR exists, the force-push already updated it — just report the URL. Update its title/body via `gh pr edit` to match the new tag.\n- Otherwise create one:\n\n```bash\ngh pr create --repo redis/node-redis --base master --head <fork-owner>:bump-test-image \\\n  --title \"<commit subject>\" \\\n  --body \"<commit body>\"\n```\n\nFinish by reporting: old → new tag/version, matrix action taken (added vs replaced entry), and the PR URL.\n\n<!-- File: .agents/skills/docs-sync/agents/openai.yaml (Tokens: ~46 | Category: anthropic-skill) -->\ninterface:\n  display_name: \"Docs Sync\"\n  short_description: \"Audit docs coverage and propose targeted updates\"\n  default_prompt: \"Use $docs-sync to audit the current branch against docs/ and propose targeted documentation updates.\"\n\n<!-- File: .agents/skills/docs-sync/references/doc-coverage-checklist.md (Tokens: ~558 | Category: anthropic-skill) -->\n# Doc Coverage Checklist\n\nUse this checklist to scan the selected scope (master = comprehensive, or current-branch diff) and validate documentation coverage.\n\n## Feature inventory targets\n\n- Public exports: clients (`RedisClient`, `RedisCluster`, `RedisSentinel`), pool, factory functions, types, and module entry points.\n- Configuration options: `*Options` types, default config objects (`defaults.ts`), and connection-string options.\n- New or changed commands under `packages/*/lib/commands` (names, args, reply shape, RESP2/RESP3 differences).\n- User-facing behaviors: reconnection, timeouts, pub/sub, pipelining/transactions, scan iterators, errors, caching, telemetry, and data handling.\n- Deprecations, removals, or renamed settings (track against migration guides `docs/v*-to-v*.md`).\n\n## Doc-first pass (page-by-page)\n\n- Review each relevant page under `docs/`, the top-level `README.md`, and each `packages/*/README.md`.\n- Look for missing opt-in flags or customization options that the page implies.\n- Add new features that belong on that page based on user intent and navigation.\n\n## Code-first pass (feature inventory)\n\n- Map features to the closest existing page based on package or feature area.\n- Prefer updating existing pages over creating new ones unless the topic is clearly new.\n- Use conceptual pages for cross-cutting concerns (auth, errors, RESP, clustering, pub/sub).\n- Keep quick-start flows minimal; move advanced details into deeper pages.\n\n## Evidence capture\n\n- Record the master-branch file path and symbol/setting name.\n- Note defaults or behavior-critical details for accuracy checks.\n- Avoid large code dumps; a short identifier is enough.\n\n## Red flags for outdated or incorrect docs\n\n- Option names/types no longer exist or differ from code.\n- Default values or allowed ranges do not match implementation.\n- Features removed in code but still documented.\n- New behaviors introduced without corresponding docs updates.\n\n## When to propose structural changes\n\n- A page mixes unrelated audiences (quick-start + deep reference) without clear separation.\n- Multiple pages duplicate the same concept without cross-links.\n- New feature areas have no obvious home in the docs structure.\n\n## Diff mode guidance (current branch vs master)\n\n- Focus only on changed behavior: new exports/options, modified defaults, removed features, or renamed settings.\n- Use `git diff master...HEAD` (or equivalent) to constrain analysis.\n- Document removals explicitly so docs can be pruned if needed.\n\n## Patch guidance\n\n- Keep edits scoped and aligned to existing tone and format.\n- Update cross-links when moving or renaming sections.\n- Place any runnable snippets under `examples/` or `doctests/`.\n\n<!-- File: .agents/skills/docs-sync/SKILL.md (Tokens: ~929 | Category: anthropic-skill) -->\n---\nname: docs-sync\ndescription: Analyze master branch implementation and configuration to find missing, incorrect, or outdated documentation in docs/, README.md, and per-package READMEs. Use when asked to audit doc coverage, sync docs with code, or propose doc updates/structure changes. Provide a report and ask for approval before editing docs.\n---\n\n# Docs Sync\n\n## Overview\n\nIdentify doc coverage gaps and inaccuracies by comparing master branch features and configuration options against the current docs, then propose targeted improvements.\n\n## Workflow\n\n1. Confirm scope and base branch\n   - Identify the current branch and default branch (`master`).\n   - Prefer analyzing the current branch to keep work aligned with in-flight changes.\n   - If the current branch is not `master`, analyze only the diff vs `master` to scope doc updates.\n   - Avoid switching branches if it would disrupt local changes; use `git show master:<path>` or `git worktree add` when needed.\n\n2. Build a feature inventory from the selected scope\n   - If on `master`: inventory the full surface area and review docs comprehensively.\n   - If not on `master`: inventory only changes vs `master` (feature additions/changes/removals).\n   - Focus on user-facing behavior: public exports, client/cluster/sentinel/pool config options, new or changed commands, connection-string options, default values, and documented runtime behaviors.\n   - Capture evidence for each item (file path + symbol/setting).\n   - Use targeted search to find option types and feature flags (for example: `rg \"Options\"`, `rg \"interface .*Options\"`, `rg \"export\"` under `packages/*/lib`).\n   - For Redis command behavior or server-side semantics, treat the source code (`parseCommand`/`transformReply` in `packages/*/lib/commands`) as the source of truth; consult [redis.io/commands](https://redis.io/commands) only to confirm server semantics when discrepancies appear.\n\n3. Doc-first pass: review existing pages\n   - Walk each relevant page under `docs/`, the top-level `README.md`, and each `packages/*/README.md`.\n   - Identify missing mentions of important, supported options (opt-in flags, config), customization points, or new features from `packages/`.\n   - Propose additions where users would reasonably expect to find them on that page.\n\n4. Code-first pass: map features to docs\n   - Review the current docs layout under `docs/` (e.g. `client-configuration.md`, `clustering.md`, `sentinel.md`, `pool.md`, `RESP.md`, `transactions.md`, `programmability.md`, `pub-sub.md`, `scan-iterators.md`, `command-options.md`).\n   - Determine the best page/section for each feature based on existing patterns and package boundaries.\n   - Identify features that lack any doc page or have a page but no corresponding content.\n   - Note when a structural adjustment would improve discoverability.\n\n5. Detect gaps and inaccuracies\n   - **Missing**: features/configs present in master but absent in docs.\n   - **Incorrect/outdated**: names, defaults, or behaviors that diverge from master.\n   - **Structural issues** (optional): pages overloaded, missing overviews, or mis-grouped topics.\n\n6. Produce a Docs Sync Report and ask for approval\n   - Provide a clear report with evidence, suggested doc locations, and proposed edits.\n   - Ask the user whether to proceed with doc updates.\n\n7. If approved, apply changes\n   - Edit docs under `docs/`, `README.md`, and the relevant `packages/*/README.md`.\n   - Keep changes aligned with the existing docs style and navigation.\n   - Place any runnable code snippets under `examples/` or `doctests/`, mirroring existing patterns.\n   - Verify any snippet you add still compiles with `npm run build` and fix issues before handoff.\n\n## Output format\n\nUse this template when reporting findings:\n\nDocs Sync Report\n\n- Doc-first findings\n  - Page + missing content → evidence + suggested insertion point\n- Code-first gaps\n  - Feature + evidence → suggested doc page/section (or missing page)\n- Incorrect or outdated docs\n  - Doc file + issue + correct info + evidence\n- Structural suggestions (optional)\n  - Proposed change + rationale\n- Proposed edits\n  - Doc file → concise change summary\n- Questions for the user\n\n## References\n\n- `references/doc-coverage-checklist.md`\n\n<!-- File: .agents/skills/implement-command/agents/openai.yaml (Tokens: ~120 | Category: anthropic-skill) -->\ninterface:\n  display_name: \"Implement Command\"\n  short_description: \"Add a new Redis command to node-redis end-to-end\"\n  default_prompt: \"Use $implement-command to add a new Redis command to node-redis: write the <NAME>.ts Command file (parseCommand + transformReply), register it with JSDoc in the package commands/index.ts (raw name + camelCase alias), and add a co-located <NAME>.spec.ts with parseArgs and testUtils.testAll tests, then build, run check:command-jsdoc, the spec, and lint.\"\n\n<!-- File: .agents/skills/implement-command/SKILL.md (Tokens: ~5159 | Category: anthropic-skill) -->\n---\nname: implement-command\ndescription: Add a new Redis command (or command variant) to node-redis end-to-end — the `<NAME>.ts` Command file, its registration with JSDoc in the package `commands/index.ts`, and a co-located `<NAME>.spec.ts` with arg + behavior tests. Use when asked to implement, add, or wire up a Redis command in the client or a module package (json/search/bloom/time-series).\n---\n\n# Implement a node-redis Command\n\n## Overview\n\nA command in node-redis is a single `Command` object exported from\n`packages/<pkg>/lib/commands/<NAME>.ts`. It declares how to serialize arguments\nonto the wire (`parseCommand`) and how to map the RESP reply to a JS value\n(`transformReply`). It becomes callable on clients only after it is registered\nin the package's `commands/index.ts`. Both the raw name and a camelCase alias\nare exposed (`HSET` and `hSet`).\n\nThis skill is for `@redis/client` core commands and for module packages\n(`@redis/json`, `@redis/search`, `@redis/bloom`, `@redis/time-series`). It does\nnot cover RESP codec changes or new client transports.\n\nBefore writing, **read 2-3 existing commands with a similar shape** (simple\nkey read, key + options, variadic, RESP2/3-divergent reply) and mirror them.\nConsult [redis.io/commands](https://redis.io/commands) for argument order and\nreply type, but treat existing `parseCommand`/`transformReply` files as the\nsource of truth for repo conventions.\n\n## File layout\n\n| Package | Command dir | Import paths | Wire name prefix |\n| --- | --- | --- | --- |\n| `client` | `packages/client/lib/commands/` | `../client/parser`, `../RESP/types`, `./generic-transformers` | none (`GET`) |\n| `json` | `packages/json/lib/commands/` | `@redis/client/dist/lib/...` | `JSON.` (`JSON.ARRAPPEND`) |\n| `search` | `packages/search/lib/commands/` | `@redis/client/dist/lib/...` | `FT.` |\n| `bloom` | `packages/bloom/lib/commands/<family>/` | `@redis/client/dist/lib/...` | e.g. `BF.`, `CF.`, `TOPK.` |\n| `time-series` | `packages/time-series/lib/commands/` | `@redis/client/dist/lib/...` | `TS.` |\n\n**Naming**: file name = raw wire name with subcommand `_` separators\n(`ACL_CAT`, `CONFIG_GET`, `CLUSTER_FORGET`). A distinct reply variant gets its\nown file (`HRANDFIELD_COUNT_WITHVALUES`). Module commands drop the dotted\nprefix from the file name (`ARRAPPEND.ts` → wire `JSON.ARRAPPEND`).\n\n## Step 0 — Gather inputs (ask the user first)\n\nNew commands are often implemented **before they are publicly released**, so\nredis.io may not document them yet and a default `redis:latest` may not have\nthem. Before writing any code, ask the user for three things:\n\n1. **The command spec.** Ask for the redis/redis JSON spec file — one per\n   command under\n   [`src/commands/<name>.json`](https://github.com/redis/redis/tree/unstable/src/commands)\n   (subcommands use `-`, e.g. `client-info.json`, `acl-cat.json`). If the\n   command is unreleased, ask the user to paste the spec from their branch.\n   As a fallback on a live server: `redis-cli --json COMMAND DOCS <name>` and\n   `COMMAND INFO <name>`.\n2. **A running Redis instance that has the command.** This is the single most\n   useful input — ask for it explicitly and up front. Ask for connection\n   details (host/port, TLS, auth, module loaded). Use it to explore real\n   behavior and confirm the implementation matches the spec — **do not rely on\n   the spec alone**. Probe every argument branch and diff the real reply against\n   your `transformReply`. A quick `redis-cli` session or a throwaway probe\n   script (`packages/client` is already wired for `tsx`) is enough; never commit\n   the probe.\n3. **The first server version that ships the command.** Ask which Redis (or\n   module) version introduced it — the spec's `since` field is the answer when\n   present; otherwise ask the user directly. You need this to (a) write `@since`\n   in the JSDoc (Step 2) and (b) gate the behavior tests with\n   `minimumDockerVersion` (Step 3) so they don't run — and fail — on older\n   servers in CI.\n\nIf the user cannot provide a spec, derive arguments/reply from\n[redis.io/commands](https://redis.io/commands) but flag that it is unverified.\nIf they cannot provide a live instance, implement from the spec but state that\nruntime behavior was not confirmed. If the introducing version is unknown, say\nso and leave `@since`/`minimumDockerVersion` out rather than guessing.\n\n### Reading the spec JSON → mapping to a Command\n\nThe redis/redis spec drives every part of the `Command` object. Example\n(`getex.json`, trimmed):\n\n```json\n{\n  \"GETEX\": {\n    \"since\": \"6.2.0\", \"arity\": -2,\n    \"command_flags\": [\"WRITE\", \"FAST\"],\n    \"key_specs\": [{ \"begin_search\": { \"index\": { \"pos\": 1 } }, \"flags\": [\"RW\", \"UPDATE\"] }],\n    \"arguments\": [\n      { \"name\": \"key\", \"type\": \"key\", \"key_spec_index\": 0 },\n      { \"name\": \"expiration\", \"type\": \"oneof\", \"optional\": true, \"arguments\": [\n        { \"name\": \"seconds\", \"type\": \"integer\", \"token\": \"EX\" },\n        { \"name\": \"persist\", \"type\": \"pure-token\", \"token\": \"PERSIST\" }\n      ]}\n    ],\n    \"reply_schema\": { \"oneOf\": [ { \"type\": \"string\" }, { \"type\": \"null\" } ] }\n  }\n}\n```\n\n| Spec field | Drives |\n| --- | --- |\n| `command_flags` contains `READONLY` (and **not** `WRITE`) | `IS_READ_ONLY: true`. `WRITE` → omit it. Pure read with no side effects → also `CACHEABLE: true`. For keyed commands the generated metadata table already implies these defaults, so module packages omit the flags; keyless reads must set `IS_READ_ONLY: true` explicitly (Step 4). |\n| `key_specs` empty / no `key`-type args | `NOT_KEYED_COMMAND: true`. |\n| `arguments[].type: \"key\"` | `parser.pushKey(...)` (one per key, in spec order). |\n| `type: \"pure-token\"` + `token` | a literal flag pushed only when its option is set (`parser.push('PERSIST')`). |\n| `token` + value type (`integer`/`string`/...) | push the token then the stringified value (`parser.push('EX', seconds.toString())`). |\n| `type: \"oneof\"` | mutually exclusive branch → `if/else if` in `parseCommand`; model as a union/`options` field. |\n| `optional: true` | goes in the `options` object (exported `interface`); guard with `if (options?.x)`. |\n| `multiple: true` | variadic → `parser.pushVariadic*`. |\n| `arity` | sanity-check arg count in `parseArgs` tests. |\n| `reply_schema` (JSON Schema) | the `transformReply` return type. `oneOf [string, null]` → `BlobStringReply \\| NullReply`; `integer` → `NumberReply`; `array` → `ArrayReply<...>`; a map differing by RESP version → keyed `transformReply: { 2, 3 }`. |\n| `since` | the introducing server version → `@since` in the registry JSDoc (Step 2) **and** `minimumDockerVersion: [major, minor]` on the behavior tests (Step 3). |\n\nAfter implementing, run the command against the live instance and diff the real\nreply against `reply_schema` and your `transformReply` output.\n\n## Step 1 — Write `<NAME>.ts`\n\nMinimal pass-through command (`packages/client/lib/commands/GET.ts`):\n\n```typescript\nimport { CommandParser } from '../client/parser';\nimport { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types';\n\nexport default {\n  CACHEABLE: true,\n  IS_READ_ONLY: true,\n  parseCommand(parser: CommandParser, key: RedisArgument) {\n    parser.push('GET');\n    parser.pushKey(key);\n  },\n  transformReply: undefined as unknown as () => BlobStringReply | NullReply\n} as const satisfies Command;\n```\n\n`as const satisfies Command` is mandatory — it preserves the literal arg types\nfor the public API while type-checking the shape.\n\n### Command flags (all optional)\n\n- `IS_READ_ONLY: true` — read command; routable to replicas. Set for reads, omit/`false` for writes.\n- `CACHEABLE: true` — eligible for client-side caching. Only for pure reads with no side effects.\n- `NOT_KEYED_COMMAND: true` — command takes no key (server/connection level, e.g. `PING`, `CONFIG_GET`).\n- `IS_FORWARD_COMMAND` — internal; do not set on new commands.\n\n### `parseCommand` — serialize args via `CommandParser`\n\nFirst arg is always `parser`. Push the wire name first, then args in order.\nUse the parser helpers — **do not** hand-build arrays:\n\n- `push(...args)` — raw args (the command token, flags, stringified numbers).\n- `pushKey(key)` — a key. Registers it for cluster slot routing. Use for **every** key, never `push` a key.\n- `pushKeys(keys)` / `pushKeysLength(keys)` — multiple keys; the `Length` variant prefixes the count.\n- `pushVariadic(vals)` — a `RedisVariadicArgument` (one value or array) as flat args.\n- `pushVariadicWithLength(vals)` — same, prefixed with the count (e.g. `FIELDS <n> ...`).\n- `pushVariadicNumber(vals)` — number or array of numbers, stringified.\n\nNumbers are not auto-stringified by `push` — call `.toString()`. Optional\ntrailing args go in an `options` object; export its `interface` (see\n`SET.ts`'s `SetOptions`). Encode keyword flags conditionally:\n\n```typescript\nparseCommand(parser: CommandParser, key: RedisArgument, value: RedisArgument, options?: SetOptions) {\n  parser.push('SET');\n  parser.pushKey(key);\n  parser.push(value);\n  if (options?.condition) parser.push(options.condition); // 'NX' | 'XX'\n}\n```\n\n### `transformReply` — map RESP reply to JS\n\n- **Pass-through** (reply already the right shape): `transformReply: undefined as unknown as () => <ReplyType>`.\n- **Function**: `(reply: <RawType>) => <JsType>`. Use `UnwrapReply<...>` to read the raw RESP container.\n- **RESP-version keyed**: `{ 2: (reply) => ..., 3: (reply) => ... }` when RESP2 and RESP3 shapes differ (e.g. flat array vs map/tuple). See `HRANDFIELD_COUNT_WITHVALUES.ts`.\n\n#### Unify RESP2 onto the RESP3 shape\n\nWhen the server returns different shapes per protocol, the library exposes **one\nreturn type to callers**: the **RESP3 shape is the source of truth**, and the\nRESP2 reply is transformed to look like it. So the keyed form is almost always:\n\n- `3:` — **pass-through** (`undefined as unknown as () => <ReplyType>`), because\n  RESP3 already has the target shape (map, tuple, big-number, double, ...).\n- `2:` — a **function** that reshapes the flat/legacy RESP2 reply into that same\n  `<ReplyType>`. Type its input `UnwrapReply<Resp2Reply<ReplyType>>` so the raw\n  RESP2 container is visible while the output type still matches RESP3.\n\nCanonical example — `HELLO.ts` turns the RESP2 flat array (`[k, v, k, v, ...]`)\ninto the RESP3 map, while RESP3 passes through:\n\n```typescript\ntransformReply: {\n  2: (reply: UnwrapReply<Resp2Reply<HelloReply>>) => ({\n    server: reply[1], version: reply[3], proto: reply[5], /* ... */\n  }),\n  3: undefined as unknown as () => HelloReply\n}\n```\n\nReuse shared transformers where one exists (`HGETALL.ts` uses\n`transformTuplesReply` for `2:`, map pass-through for `3:`). Only when RESP3\nstill needs reshaping does `3:` get its own function too. Verify the actual\nper-protocol shapes against the live instance (Step 0) — connect once with\n`RESP: 2` and once with `RESP: 3` and diff.\n\nReply types live in `RESP/types`: `BlobStringReply`, `SimpleStringReply<'OK'>`,\n`NumberReply`, `DoubleReply`, `NullReply`, `BooleanReply`, `ArrayReply<T>`,\n`TuplesReply<[...]>`, `MapReply`, `UnwrapReply`.\n\n**RESP3 is the default.** No separate RESP3 test is needed for a new command;\nthe default test setup already exercises RESP3.\n\n### Type-mapping precision caveats\n\n- A `BLOB_STRING` reply cannot be remapped to `Number` via type mapping; only RESP3 `DOUBLE`/`BIG_NUMBER` are precision-risky.\n- If a `NumberReply` can exceed `Number.MAX_SAFE_INTEGER` (2^53-1), add a `@remarks` line to the JSDoc (Step 2) telling users to do `client.withTypeMapping({ [RESP_TYPES.NUMBER]: String })`. See the `ARGREP` entries in the client index for the exact wording.\n\n### Module package commands\n\nImport from the published client subpath, prefix the wire name, and reuse\nshared transformers (`packages/json/lib/commands/ARRAPPEND.ts`):\n\n```typescript\nimport { CommandParser } from '@redis/client/dist/lib/client/parser';\nimport { RedisArgument, NumberReply, Command } from '@redis/client/dist/lib/RESP/types';\n\nexport default {\n  IS_READ_ONLY: false,\n  parseCommand(parser, key, path, value) {\n    parser.push('JSON.ARRAPPEND');\n    parser.pushKey(key);\n    parser.push(path, /* transform */ value);\n  },\n  transformReply: undefined as unknown as () => NumberReply\n} as const satisfies Command;\n```\n\n## Step 2 — Register in `commands/index.ts`\n\n`import` the command, then add it to the default-export map **twice**: the raw\nname (shorthand) and a camelCase alias. **Every entry MUST have a JSDoc block\ndirectly above it** — `npm run check:command-jsdoc` fails on any registry entry\nwithout an attached JSDoc comment (no blank-line gap allowed).\n\n```typescript\nimport GET from './GET';\n// ...\nexport default {\n  /**\n   * Returns the value of a key, or null if the key does not exist\n   * @param key - Key to read\n   * @since 1.0.0\n   */\n  GET,\n  /**\n   * Returns the value of a key, or null if the key does not exist\n   * @param key - Key to read\n   * @since 1.0.0\n   */\n  get: GET,\n} satisfies RedisCommands;\n```\n\nKeep both JSDoc blocks (raw + alias) in sync. Document every `parseCommand`\nparam after `parser` with `@param`. Add `@since <version>` with the introducing\nserver version from Step 0 (the spec's `since`); omit it only if that version is\nunknown. Add `@remarks` for the precision caveat above when relevant. For module\npackages the registry files are\n`packages/<pkg>/lib/commands/index.ts` (bloom: per-family `.../<family>/index.ts`).\n\n## Step 3 — Write `<NAME>.spec.ts` (co-located)\n\nTwo layers: arg serialization (no server) + behavior (real server, server +\ncluster topologies). Mirror `GET.spec.ts`:\n\n```typescript\nimport { strict as assert } from 'node:assert';\nimport testUtils, { GLOBAL } from '../test-utils';\nimport { parseArgs } from './generic-transformers';\nimport GET from './GET';\n\ndescribe('GET', () => {\n  it('transformArguments', () => {\n    assert.deepEqual(parseArgs(GET, 'key'), ['GET', 'key']);\n  });\n\n  testUtils.testAll('get', async client => {\n    assert.equal(await client.get('key'), null);\n  }, {\n    client: { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [8, 8] },\n    cluster: { ...GLOBAL.CLUSTERS.OPEN, minimumDockerVersion: [8, 8] }\n  });\n});\n```\n\n- `parseArgs(COMMAND, ...args)` asserts the exact wire array — cover each option/flag branch and variadic shapes. Arg tests need no server, so **never gate them** by version.\n- `testUtils.testAll(name, fn, { client, cluster })` runs the same body against a standalone server and a cluster. Use it so cluster key routing (`pushKey`) is exercised. Drop `cluster` only when the command is genuinely cluster-incompatible.\n- **Gate every behavior test by the introducing version** (Step 0). Spread `minimumDockerVersion: [major, minor]` into **both** the `client` and `cluster` options (as above). CI runs multiple server versions; without the gate the test runs on older servers that lack the command and fails. `[8, 8]` = \"8.8 and newer\". Apply the same to `testWithClient`/`testWithCluster` by spreading it into their single options object. Omit only if the version is genuinely unknown.\n- Pick the right `GLOBAL.SERVERS.*` / `GLOBAL.CLUSTERS.*` setup (see `test-utils.ts`); `OPEN` is the default.\n- **Docker is required** — test-utils starts real Redis containers.\n\n## Step 4 — Regenerate static command metadata\n\nCluster/sentinel routing (replica-safety, keyedness, CSC eligibility) is\nresolved from a generated table:\n`packages/client/lib/command-metadata/command-metadata-data.ts`. The file is\nauto-generated — **never edit it manually**. The table lives in\n`@redis/client`, but the `COMMAND` dump includes module commands (`ft`,\n`json`, `bf`, `ts`, ...), so a new command in **any** package needs a\nregenerate:\n\n```bash\nnpm run generate:metadata --workspace=packages/client -- redis://localhost:6379\n```\n\nThe script rebuilds the **entire** table from a single live server's `COMMAND`\nreply and overwrites the file — entries the server doesn't report are silently\ndropped. Run it only against a server with **all** bundled modules loaded and a\ncurrent core command set (e.g. the CI image `redislabs/client-libs-test` or a\nfull Redis 8.8+ build); reuse the Step 0 instance only if it meets that bar — a\nserver with just the new command's module would wipe every other module's\nmetadata. The script then applies the curation in\n`packages/client/scripts/command-metadata-overrides.ts`: hand-curated excludes\n(internal, deprecated and cluster-admin commands) plus per-command routing\noverrides. After regenerating, verify the new command has an entry with the\nexpected flags, **and** check `git diff` on `command-metadata-data.ts`: it must\ncontain only the intended additions/changes. Deletions of other modules' or\ncore entries mean the source server was incomplete — revert and rerun against\na full build.\n\nHow the table and the command object interact (override-first — see\n`lib/command-metadata/predicates.ts`):\n\n- The table sets **defaults only**, for both replica routing (cluster and\n  sentinel) and client-side caching eligibility. A keyed entry without the\n  `write` flag is already replica-safe, so a keyed read command with a correct\n  table entry needs no `IS_READ_ONLY` on the command object — bloom and json\n  omit it everywhere.\n- `IS_READ_ONLY`/`CACHEABLE` on the command object **win over the table**. Set\n  them as deliberate corrections, not to restate the table. The main case is\n  keyless reads, which default to master routing: `PING`/`INFO` in the client,\n  and module reads whose args are not keys — search and time-series set\n  `IS_READ_ONLY: true` on exactly their keyless commands (`FT.SEARCH` takes an\n  index name, `TS.MGET`/`TS.MRANGE` take filters, ...).\n- Table-shape fixes (wrong, missing or excluded entries, routing policies)\n  belong in `command-metadata-overrides.ts`; value intent (`IS_READ_ONLY`,\n  `CACHEABLE`) belongs in the command definition, never in the overrides file.\n\n## Step 5 — Build, verify, lint\n\n```bash\nnpm run build                                   # tsc --build (project references)\nnpm run check:command-jsdoc                     # registry JSDoc gate\nnpm run test-single -- packages/<pkg>/lib/commands/<NAME>.spec.ts\nnpm run lint                                     # changed files\n```\n\nIf the build fails on stale `dist/` from project references:\n\n```bash\nfind packages -type d -name \"dist\" -exec rm -rf {} + && npm run build\n```\n\nFor module packages, build the client first (or whole repo) — they import from\n`@redis/client/dist`.\n\n## Completion checklist\n\n- [ ] Asked the user for spec, a live instance with the command, and the introducing server version (Step 0); probed real behavior against the live instance.\n- [ ] `<NAME>.ts` created with `parseCommand` + `transformReply`, `as const satisfies Command`.\n- [ ] Flags set correctly (`IS_READ_ONLY` for reads, `CACHEABLE` only for side-effect-free reads, `NOT_KEYED_COMMAND` if no key; omit flags the metadata table already implies — Step 4).\n- [ ] Every key uses `pushKey`/`pushKeys`; numbers stringified; options behind an exported `interface`.\n- [ ] RESP2/3 divergence handled via keyed `transformReply`: RESP3 is the target shape (usually `3:` pass-through), RESP2 transformed to match it; both shapes verified against the live instance.\n- [ ] Registered in `commands/index.ts`: import + raw entry + camelCase alias, **each with JSDoc** (`@param` per arg; `@since` for the introducing version; `@remarks` for >2^53 precision).\n- [ ] Static command metadata regenerated (`npm run generate:metadata`) against a server with **all** bundled modules; the new command's entry verified and the diff contains no dropped entries (Step 4).\n- [ ] `<NAME>.spec.ts`: `parseArgs` covers all branches; `testUtils.testAll` covers server + cluster; behavior tests gated with `minimumDockerVersion` on both `client` and `cluster`.\n- [ ] `npm run build`, `npm run check:command-jsdoc`, the spec, and `npm run lint` all pass.\n- [ ] Commit message uses Conventional Commits; no company-internal refs.\n```\n\n<!-- File: .agents/skills/pr-draft-summary/agents/openai.yaml (Tokens: ~54 | Category: anthropic-skill) -->\ninterface:\n  display_name: \"PR Draft Summary\"\n  short_description: \"Draft the repo-ready PR title and description\"\n  default_prompt: \"Use $pr-draft-summary to generate the PR-ready summary block, title, and draft description for the current changes.\"\n\n<!-- File: .agents/skills/pr-draft-summary/SKILL.md (Tokens: ~1332 | Category: anthropic-skill) -->\n---\nname: pr-draft-summary\ndescription: Create the required PR-ready summary block, branch suggestion, title, and draft description for node-redis. Must be used before the final response whenever the actual task diff includes runtime code, tests, examples, build/test configuration, or docs with behavior impact, regardless of perceived change size. Skip only when no eligible files changed, every change is repo-meta or docs-only without behavior impact, the task is conversation-only, or the user explicitly opts out.\n---\n\n# PR Draft Summary\n\n## Purpose\n\nProduce a PR-ready summary after eligible work is complete: a concise change summary plus a PR-ready title and draft description for node-redis.\n\n## Close-out Gate\n\n1. Inspect the actual task diff before sending the final response.\n2. Run this skill when the diff includes runtime code, tests, examples, build/test configuration, or docs with behavior impact. Do not use perceived change size to skip an eligible change.\n3. Run it after any required verification and before sending the \"work complete\" response.\n4. Skip only when no eligible files changed, every change is repo-meta or docs-only without behavior impact, the task is conversation-only, or the user explicitly opts out.\n\n## Inputs to Collect Automatically (do not ask the user)\n\n- Current branch: `git rev-parse --abbrev-ref HEAD`.\n- Working tree: `git status -sb`.\n- Untracked files: `git ls-files --others --exclude-standard` (use with `git status -sb`; `--stat` omits them).\n- Changed files: `git diff --name-only` (unstaged) and `git diff --name-only --cached` (staged); sizes via `git diff --stat` and `git diff --stat --cached`.\n- Pull request base reference (use `origin/master`; never use a feature branch's upstream as the PR base):\n  - `BASE_REF=origin/master`; if it does not exist locally, use `master`.\n  - `BASE_COMMIT=$(git merge-base \"$BASE_REF\" HEAD)`.\n- Committed branch diff: `git diff --name-only \"${BASE_COMMIT}..HEAD\"` and `git diff --stat \"${BASE_COMMIT}..HEAD\"`.\n- Commits ahead of the base fork point: `git log --oneline --no-merges ${BASE_COMMIT}..HEAD`.\n- Category signals for this repo: runtime (`packages/*/lib`, `examples/`, `doctests/`, `benchmark/`, `scripts/`), tests (co-located `packages/**/*.spec.ts`), docs (`docs/`, `README.md`, `packages/*/README.md`, `AGENTS.md`, `.github/`), build/test config (`package.json`, `package-lock.json`, `tsconfig*.json`, `eslint.config.mjs`, `.mocharc*`, `mocha-multi-reporter-config.json`, `.nycrc*`).\n\n## Workflow\n\n1. Run the commands above without asking the user; compute `BASE_REF`/`BASE_COMMIT` first so later commands reuse them. Compare against `origin/master` or `master`, not the current branch's upstream.\n2. Combine the committed branch diff with staged, unstaged, and untracked changes. If the combined diff is empty, reply briefly that no code changes were detected and skip emitting the PR block.\n3. Infer change type from the touched paths listed under \"Category signals\"; classify as feature, fix, refactor, or docs-with-impact, and flag backward-compatibility risk only when the diff changes released public APIs, client/command behavior, connection config, or the RESP wire handling. Judge that risk against the latest release tag, not unreleased branch-only churn.\n4. Summarize changes in 1–3 short sentences using the top five paths and stats from the committed, staged, and unstaged diffs. Explicitly call out untracked files because `--stat` does not include them. Use commit messages as supporting context, not as a substitute for inspecting the committed diff.\n5. Choose the lead verb for the description: feature → `adds`, bug fix → `fixes`, refactor/perf → `improves` or `updates`, docs-only → `updates`.\n6. Suggest a branch name. If already off `master`, keep it; otherwise propose `feat/<slug>`, `fix/<slug>`, or `docs/<slug>` based on the primary area (for example `docs/pr-draft-summary-guidance`).\n7. If the current branch matches `issue-<number>` (digits only), keep that branch suggestion. When an issue number is present, reference `https://github.com/redis/node-redis/issues/<number>` and include an auto-closing line such as `This pull request resolves #<number>.` Do not block if the issue cannot be fetched.\n8. Draft the PR title and description using the template below. Use Conventional Commits prefixes for the title (`feat:`, `fix:`, `docs:`, `chore:`, etc.).\n9. Output only the block in \"Output Format\". Keep any surrounding status note minimal and in English. Do not include company-internal references (e.g. Jira/Confluence IDs) in the branch name, title, or description.\n\n## Output Format\n\nWhen closing out a task, add this concise Markdown block (English only) after any brief status note unless the task falls under the documented skip cases or the user says they do not want it.\n\n```\n# Pull Request Draft\n\n## Branch name suggestion\n\ngit checkout -b <kebab-case suggestion, e.g., feat/pr-draft-summary-skill>\n\n## Title\n\n<single-line imperative title, which can be a commit message; a Conventional Commits prefix such as feat:, fix:, or docs: is preferred>\n\n## Description\n\n<include what you changed plus a draft pull request title and description for your local changes; start the description with prose such as \"This pull request resolves/updates/adds ...\" using a verb that matches the change (you can use bullets later), explain the change background (for bugs, clearly describe the bug, symptoms, or repro; for features, what is needed and why), any behavior changes or considerations to be aware of, and you do not need to mention any tests you ran.>\n```\n\nKeep it tight—no redundant prose around the block, and avoid repeating details between `Changes` and the description. Tests do not need to be listed unless specifically requested.\n\n<!-- File: .agents/skills/runtime-behavior-probe/agents/openai.yaml (Tokens: ~132 | Category: anthropic-skill) -->\ninterface:\n  display_name: \"Runtime Behavior Probe\"\n  short_description: \"Plan and run runtime behavior probes\"\n  default_prompt: \"Use $runtime-behavior-probe to investigate actual runtime behavior with a validation matrix, disposable TypeScript probes outside git-tracked paths, and a findings-first report. For node-redis, the default write scope is a temporary directory only. Do not modify examples/, packages/, package.json files, README.md, workspace config, or build config, and do not add checked-in examples, benchmarks, or package scripts unless the user explicitly asks for a reusable repository artifact.\"\npolicy:\n  allow_implicit_invocation: false\n\n<!-- File: .agents/skills/runtime-behavior-probe/references/error-cases.md (Tokens: ~532 | Category: anthropic-skill) -->\n# Common Error Cases\n\nUse this reference to expand beyond the happy path. Favor error cases that a real user or operator is likely to hit.\n\n## Configuration Errors\n\nCheck whether the runtime behaves differently for:\n\n- Missing required environment variables.\n- Present but malformed secrets or identifiers.\n- Wrong endpoint or base URL.\n- Wrong model or deployment name.\n- Incompatible local dependency versions.\n\nLook for:\n\n- Error type and status code.\n- Whether the failure is immediate or delayed.\n- Whether the message is actionable.\n- Whether retrying without fixing configuration changes anything.\n\n## Input Errors\n\nProbe common bad-input patterns such as:\n\n- Missing required fields.\n- Wrong data type.\n- Unsupported enum or option value.\n- Empty but syntactically valid input.\n- Oversized input or too many items.\n- Mutually incompatible options.\n\nPrefer realistic invalid inputs over artificial nonsense. The point is to learn how the runtime fails in practice.\n\n## Transport and Availability Errors\n\nWhen networked services are involved, consider:\n\n- Connection failure.\n- Read timeout.\n- Server timeout or upstream gateway error.\n- Rate limit response.\n- Partial stream interruption.\n- Reusing a connection after a failure.\n\nCapture whether the client library retries automatically, whether it surfaces retry metadata, and whether the final exception preserves the original cause.\n\n## State and Repetition Errors\n\nMany surprising bugs appear only when an operation is repeated or interrupted:\n\n- Re-submit the same request.\n- Repeat after a timeout.\n- Retry after a partial tool call or partial stream.\n- Resume after local cleanup or process restart.\n- Repeat with slightly changed inputs while reusing shared state.\n\nObserve whether the operation is idempotent, duplicated, silently ignored, or left in a partial state.\n\n## Concurrency Errors\n\nWhen shared state, ordering, or isolation may matter, consider:\n\n- Two overlapping requests with the same logical input.\n- Parallel runs that reuse the same cache key, session, container, or temporary resource.\n- Concurrent retries, cancellation, or cleanup racing with active work.\n- Output or event streams from one run leaking into another.\n\nCapture whether the runtime serializes, rejects, duplicates, corrupts, or cross-contaminates the work.\n\n## Investigation Heuristics\n\nUse these heuristics to pick error cases quickly:\n\n- Ask which failure a real engineer would debug first in production.\n- Ask which failure is most expensive if it is misunderstood.\n- Ask which failure would be invisible from code review alone.\n- Ask which failure path is likely to differ across environments.\n\nIf the error behavior is already perfectly obvious from a local validator or type system, it is usually low priority for this skill.\n\n<!-- File: .agents/skills/runtime-behavior-probe/references/redis-runtime-patterns.md (Tokens: ~1994 | Category: anthropic-skill) -->\n# Redis Runtime Patterns\n\nUse this reference for recurring node-redis investigations so you do not have to rediscover the probe strategy each time. Treat the repository source as the truth for client behavior, and use [redis.io/commands](https://redis.io/commands) to confirm contract-sensitive details such as argument order, reply types, and version availability. If you rely on the website rather than source, say so in the report.\n\n## General Rules\n\n- Prefer small, focused probes over large harnesses. Keep one script focused on one uncertainty.\n- For comparative or benchmark-like questions, start with a pilot and expand only when the answer is still unclear.\n- Capture both the request shape (command and arguments as sent) and the raw reply, before any client-side type mapping.\n- Preserve raw error type, message, and any Redis error prefix such as `WRONGTYPE`, `MOVED`, `ASK`, `CROSSSLOT`, `NOSCRIPT`, or `LOADING`.\n- Record whether behavior differs between the first call and a repeated call, and across a reconnect.\n- When the question is about regression or contract drift, add a known-good control run before attributing the result to the change under investigation.\n- Keep comparison parity explicit. Record what was held constant, what variable changed, and whether reply-shape or encoding differences could bias the conclusion.\n- Record the runtime context that actually changes Redis client behavior: server mode (standalone, cluster, sentinel), RESP version (2 vs 3), server version, and whether a feature such as client-side caching or sharded pub/sub is in use.\n\n## Connection Environment Variables\n\nDo not read these variables automatically. Before a live probe uses any of them, tell the user the exact variable names you plan to read and why each one is needed, then wait for explicit approval. Never print their values:\n\n- `REDIS_URL`\n- `REDIS_PASSWORD`\n- `REDIS_TLS_CA`, `REDIS_TLS_CERT`, `REDIS_TLS_KEY` (and any other `REDIS_TLS_*` the probe needs)\n\nFor most local probes you do not need any of these. The repository test harness `@redis/test-utils` can start Redis in Docker, so a disposable probe can target a local containerized server (for example `redis://localhost:6379`) without reading credentials. Prefer that over a live remote service unless the question is specifically about the remote deployment.\n\nIf the task targets another integration, use that integration's expected default variable names under the same rule.\n\n## Environment False Signals\n\nBefore attributing a failure to the patch or client code under review, exclude source-selection and environment problems with a controlled comparison.\n\n- Confirm the commit, worktree, working directory, Node executable/version, and imported module path. A probe of stale `dist`, another checkout, `/tmp/node_modules`, or an installed `redis` package does not prove current `lib` behavior.\n- Rebuild when emitted declarations, package exports, generated metadata, or `dist` is the subject. Otherwise prefer current-source `lib/` imports so stale build output cannot create a false result.\n- Confirm a Redis server is actually running and reachable. A connection refused or timeout against a missing Docker/Redis container is an environment condition, not a client defect, until a controlled rerun against a known-good server ties it to the code.\n- Confirm the RESP version the client negotiated. A reply-shape surprise is often just RESP2 vs RESP3, not a bug.\n- Confirm server mode matches the probe assumptions. Running a cluster-only expectation against a standalone server, or the reverse, produces misleading routing and slot errors.\n- Run base and head controls with the same Node version, dependency graph, server mode, RESP version, environment-variable names, and command shape. Record intentional differences.\n- Treat sandbox denials, unavailable Docker infrastructure, expired remote sessions, authentication, quota, rate limits, service outages, and stale caches as environment conditions until a controlled rerun ties them to the patch.\n- Never print credentials, connection URLs with embedded passwords, or TLS key material. Change only the minimum in-scope environment or disposable state needed for the control and record variable names rather than values.\n\nIn the final report, distinguish code failures, unsupported configurations, environment blockers, and inconclusive probes. Do not collapse them into one failed-test count.\n\n## Node-Redis Probe Patterns\n\nStart from the uncertainty instead of from the full client surface. The patterns below cover the runtime questions that recur most often in node-redis.\n\n### RESP2 vs RESP3 reply shapes and type mapping\n\nUse when the uncertainty is what a command returns at runtime and how the client maps it.\n\n- Run the same command under RESP2 and RESP3 as separate cases. Maps, sets, doubles, big numbers, verbatim strings, and booleans differ between the two protocols.\n- Capture the raw reply and the post-mapping JavaScript value. Note whether the client returns plain objects, `Map`, arrays, `null`, or typed numbers.\n- Watch precision-sensitive types. RESP3 `DOUBLE` and `BIG_NUMBER` can lose precision when mapped to `Number`; record the exact value.\n- If the probe configures a custom `typeMapping`, treat each mapping choice as part of `held_constant`.\n\n### Single-node vs cluster vs sentinel\n\nUse when key routing, slot ownership, or topology matters.\n\n- For cluster, capture which node served the command and whether a `MOVED` or `ASK` redirect occurred. Probe multi-key commands across keys in different slots to observe `CROSSSLOT` behavior and any client-side slot checks.\n- Use hash tags (`{tag}`) to force keys into the same slot when the probe needs a multi-key command to succeed.\n- For sentinel, capture master discovery and what happens on a simulated failover.\n- Keep mode constant within a case. A standalone control alongside a cluster case is a useful comparison, not a substitute.\n\n### Pub/sub and sharded pub/sub\n\nUse when delivery semantics or resubscription matter.\n\n- Capture subscribe confirmation, message delivery order, and whether messages are delivered to pattern (`PSUBSCRIBE`) and channel subscribers as expected.\n- For sharded pub/sub (`SSUBSCRIBE` and `SPUBLISH`) in cluster, capture which shard delivered the message.\n- Force a reconnect and observe whether the client resubscribes to channels and patterns automatically. This is a common drift point.\n\n### Reconnection, retry, and the offline command queue\n\nUse when the question is about resilience.\n\n- Simulate a dropped connection (stop or restart the container, or close the socket) and capture: whether commands issued while offline are queued, the order they flush in on reconnect, and whether the reconnect strategy backoff behaves as configured.\n- Distinguish commands rejected immediately from commands queued and later resolved or rejected.\n- Capture whether in-flight commands at disconnect are retried, rejected, or hang.\n\n### Pipelining and MULTI/EXEC transactions\n\nUse when batching or atomicity is under test.\n\n- For pipelining, confirm replies map back to the right commands in order, including when one command in the batch errors.\n- For `MULTI`/`EXEC`, capture queueing errors versus execution errors, behavior on `DISCARD`, and `WATCH` optimistic-locking aborts (`EXEC` returning `null`).\n\n### Client-side caching (RESP3 push invalidation)\n\nUse when caching correctness matters.\n\n- Requires RESP3. Capture whether invalidation push messages arrive after a tracked key is modified by another client, and whether the local cache entry is dropped.\n- Probe the broadcast and default tracking modes separately.\n\n### Blocking commands and connection isolation\n\nUse for `BLPOP`, `BRPOP`, `XREAD BLOCK`, `WAIT`, and similar.\n\n- Capture whether the blocking command monopolizes a connection and whether the client isolates it (for example via a dedicated connection) so other commands are not stalled.\n- Probe timeout behavior and what resolves when the block is satisfied by another client versus when it times out.\n\n### Pool behavior\n\nUse when a pooled client is involved.\n\n- Capture how commands are distributed across pool connections, behavior under saturation, and what happens when a pooled connection drops.\n- For commands that require connection affinity (transactions, blocking, subscriptions), confirm the pool keeps them on a single connection.\n\n### SCAN iterators\n\nUse for `SCAN`, `HSCAN`, `SSCAN`, and `ZSCAN`.\n\n- Capture cursor progression, whether the async iterator terminates, and whether `COUNT` and `MATCH` behave as expected. Note that SCAN gives no count or completeness guarantees mid-iteration.\n\n### Large payloads and timeouts\n\nUse when size or latency is the concern.\n\n- Capture behavior with large values and large reply sets, and whether command-level or socket timeouts fire as configured.\n- For repeat-sensitive latency, use warm-up plus repeat-N and record medians, not a single sample.\n\n## What to Capture\n\nFor node-redis probes, try to record:\n\n- The command and arguments as sent, plus the raw reply before type mapping.\n- The mapped JavaScript value and its type, especially for RESP3 maps, sets, doubles, and big numbers.\n- Server mode (standalone, cluster, or sentinel), negotiated RESP version, and server version.\n- Whether fields are absent, `null`, empty, or transformed by the client.\n- Raw error type, message, and any Redis error prefix for failures.\n- Reconnect and retry behavior, offline-queue flush order, and resubscription on reconnect when relevant.\n- For cluster, which node served the command and any redirect.\n- Which environment-variable names were approved for the probe when a live or credentialed server was required.\n\nDo not spend time rediscovering static documentation unless the runtime result seems to contradict what you expected. The value of this skill is in the observed behavior.\n\n<!-- File: .agents/skills/runtime-behavior-probe/references/repo-import-patterns.md (Tokens: ~893 | Category: anthropic-skill) -->\n# Repository Import Patterns\n\nUse this reference when a temporary probe script outside the repository needs to load code from the current branch.\n\n## Why This Exists\n\nWhen you run `npx tsx /tmp/probe.ts` from the repository root, `tsx` itself comes from this repository, but the probe file still lives under `/tmp`. Bare workspace imports such as `@redis/client` or `@redis/json` therefore resolve as if the script were its own package. That is often not what you want for \"what does the current branch do right now?\" investigations.\n\nThe safe default is to build a `file://` import URL from `process.cwd()`, which should be the repository root, and a repository-relative path.\n\n## Default Rule\n\n- Use `lib/` imports when the question is about current-branch behavior.\n- Use `dist/` imports when the question is about packaged output after a build.\n- Avoid bare workspace imports from `/tmp` unless the probe is explicitly testing published package resolution rather than branch-local source behavior.\n\n## Quick Typecheck Loop\n\nFor disposable probes in `node-redis`, the default loop should stay outside the repository and avoid a full workspace build:\n\n1. Create a temporary directory with `mktemp -d`.\n2. Write `probe.ts` there.\n3. Write a sibling `tsconfig.json` that extends the repository's `tsconfig.base.json`.\n4. From the repository root, run `npx tsc --noEmit -p /tmp/.../tsconfig.json`.\n5. If that passes, run `npx tsx /tmp/.../probe.ts`.\n\nExample temporary `tsconfig.json`:\n\n```json\n{\n  \"extends\": \"/absolute/path/to/node-redis/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"./probe.ts\"]\n}\n```\n\nThis keeps TypeScript module resolution and strictness close to the repository defaults while limiting the check surface to the disposable probe file.\n\nDo not convert this default loop into a checked-in benchmark, example, or package script unless the user explicitly asks for a reusable repository artifact.\n\n## Recommended Helper\n\nThe disposable TypeScript scaffold exposes:\n\n    const module = await importRepoModule(\"packages/client/lib/index.ts\");\n\nInternally, the helper should be equivalent to:\n\n    import { join } from \"node:path\";\n    import { pathToFileURL } from \"node:url\";\n\n    async function importRepoModule(repoRelativePath: string) {\n      const absolutePath = join(process.cwd(), repoRelativePath);\n      return import(pathToFileURL(absolutePath).href);\n    }\n\nThis keeps the import anchored to the current checkout instead of the temporary script location.\n\n## Choosing `lib/` Versus `dist/`\n\nUse `lib/` when:\n\n- You are validating a bug fix or regression on the current branch.\n- You want to know how the repository behaves before packaging.\n- The question is about implementation details or internal helper behavior.\n\nUse `dist/` when:\n\n- The question is specifically about what consumers load after `npm run build`.\n- You need to validate export maps or emitted `.js` and `.d.ts` output.\n- The bug only reproduces in generated output.\n\nIf the probe uses `dist/`, record whether you ran a fresh build first. A stale `dist/` directory can create false positives or false negatives.\n\nDo not run `npm run build` only to typecheck or execute a `lib/`-based disposable probe. The fast path is temp `tsconfig` plus `npx tsc --noEmit`, then `npx tsx`.\n\n## Simple Smoke Targets\n\nWhen you only need to prove the helper works, start with a simple target that has minimal side effects, for example:\n\n- `packages/client/lib/index.ts`\n- `packages/client/lib/client/index.ts`\n- `packages/json/lib/index.ts`\n- another file with obvious exported symbols and no environment requirement\n\nIf a chosen file pulls in more of the repo than expected, switch to a simpler target rather than weakening the import guidance.\n\n## What To Record\n\nWhen a probe imports repo code, report:\n\n- The repo-relative path you imported.\n- Whether it came from `lib/` or `dist/`.\n- The repository root used by `process.cwd()`.\n- Any prerequisite step such as `npm run build`.\n\n<!-- File: .agents/skills/runtime-behavior-probe/references/reporting-format.md (Tokens: ~1024 | Category: anthropic-skill) -->\n# Reporting Format\n\nLead with findings, not process. The user asked for investigation results, so the answer should start with the most important observed behaviors. Put the real news first.\n\n## Recommended Order\n\n1. Findings.\n2. Validation approach.\n3. Case matrix or condensed case summary.\n4. Artifact status and brief run summary.\n5. Optional implementation note.\n\n## Findings Section\n\nMake each finding answer one user-relevant question. Good findings usually include:\n\n- What was observed.\n- Why it matters.\n- The condition under which it happens.\n- What was held constant when the finding comes from a comparison probe.\n- `scope`: The boundary of the finding, such as commit, model, Node version, live versus local, or repeat mode.\n- `confidence`: `high`, `medium`, or `low`.\n\nAvoid burying the main result under setup details.\n\nPut `unexpected` or `negative` findings first. If there were no unexpected or negative findings in the executed cases, say that explicitly before the rest of the findings section.\n\nIf the probe was comparative, say whether the result supports:\n\n- Pattern parity only.\n- A broader quality claim.\n\nDo not imply a broader quality equivalence than the executed cases justify.\n\n## Validation Approach Section\n\nSummarize:\n\n- The runtime surface you exercised.\n- The shape of the probe code, in overview only.\n- Which categories of cases you covered.\n- Which execution modes you used, including repeat counts or warm-up handling when relevant.\n- Whether live credentials or external services were used.\n- Any important state controls such as fresh state, cache reuse, cache busting, unique IDs, or cleanup verification.\n- For comparison probes, what was held constant, what was varied, and whether output-shape or usage differences could still influence the conclusion.\n- Whether the usual docs path or an official-docs fallback was used for contract-sensitive checks.\n\nKeep this concise. The user needs enough detail to trust the result, not a line-by-line replay of the script.\n\n## Case Summary\n\nInclude either the full matrix or a condensed summary. At minimum, show:\n\n- Which scenarios were executed.\n- Whether the run was a quick pilot, an expanded matrix, or both.\n- Which ones produced `unexpected` or `negative` results.\n- Which ones passed or failed when a real comparison basis existed.\n- Which cases were blocked.\n- Where the supporting evidence lived, or that it was deleted.\n\nIf the matrix is large, show the highest-value cases in the main response and keep the rest as a compact appendix or note.\n\n## Artifact Status And Brief Run Summary\n\nState one of these explicitly:\n\n- Temporary artifacts were kept until the final response was drafted, then deleted after validation.\n- Temporary artifacts were kept at `<path>` because the user asked to keep them.\n- Temporary artifacts were kept at `<path>` because they are needed for follow-up analysis.\n\nEven if artifacts were deleted, retain a short run summary such as:\n\n- Probe command or runner shape.\n- Runtime context summary such as commit, Node executable, Node version, or model.\n- Artifact path and final status.\n\nFor benchmark or repeat-heavy probes, keeping artifacts for follow-up is often the right default even when the immediate report is done.\n\n## Optional Implementation Note\n\nInclude this only when one clear defect was isolated and a short implementation hypothesis or minimal repro direction would help. Keep it brief. Do not turn the report into a broader next-step plan unless the user asked for that.\n\n## Compact Template\n\nUse this outline when you need a fast structure:\n\n    Findings:\n    - <finding 1>\n      held constant: <prompt/tool/state settings kept the same, if comparative>\n      scope: <commit/model/node/live-local/repeat-mode>\n      confidence: <high|medium|low>\n    - <finding 2>\n      held constant: <prompt/tool/state settings kept the same, if comparative>\n      scope: <commit/model/node/live-local/repeat-mode>\n      confidence: <high|medium|low>\n\n    Validation approach:\n    - Surface: <what was exercised>\n    - Probe code: <brief overview>\n    - Coverage: <success, edge, error, repeat-sensitive, and quality categories>\n    - Execution modes: <single-shot|repeat-N|warm-up + repeat-N>\n    - Comparison parity: <what was held constant and what varied, if comparative>\n    - Docs source: <MCP or official-docs fallback, if relevant>\n\n    Case summary:\n    | case_id | scenario | result_flag | status | note |\n    | --- | --- | --- | --- | --- |\n    | S1 | ... | expected | pass | ... |\n    | E1 | ... | negative | fail | ... |\n\n    Artifact status and brief run summary:\n    - Temporary artifacts were kept until the final response was drafted, then deleted.\n    - Summary: <command/runtime-context/artifact-status summary>\n\n    Optional implementation note:\n    - <brief hypothesis or minimal repro direction>\n\nAdjust the format to the task, but preserve the ordering.\n\n<!-- File: .agents/skills/runtime-behavior-probe/references/validation-matrix.md (Tokens: ~1874 | Category: anthropic-skill) -->\n# Validation Matrix\n\nUse the matrix to decide what to probe before writing scripts. The goal is not exhaustive combinatorics; the goal is high-value coverage that is visible, explainable, and likely to reveal runtime surprises. The matrix should make the real news easy to scan.\n\n## Minimum Columns\n\nUse these columns unless the task clearly needs more:\n\n- `case_id`: Stable identifier such as `S1`, `E3`, or `R2`.\n- `scenario`: Short description of the behavior under test.\n- `mode`: `single-shot`, `repeat-N`, or `warm-up + repeat-N`.\n- `question`: The concrete runtime uncertainty this case is answering.\n- `setup`: Inputs, environment, or preconditions required for the case.\n- `observation_summary`: A compact summary of what actually happened.\n- `result_flag`: `unexpected`, `negative`, `expected`, or `blocked`.\n- `evidence`: Path, log reference, or `deleted`.\n\nAdd these columns when they materially improve the investigation:\n\n- `comparison_basis`: The baseline, docs, or prior behavior you are comparing against.\n- `variable_under_test`: The single factor that is intentionally changing in a comparison case.\n- `held_constant`: Prompt shape, tool setup, model settings, or state rules that were intentionally kept the same.\n- `output_constraint`: Any schema, length, or response-shape constraint used to keep the comparison fair.\n- `status`: Use `pass`, `fail`, `unexpected-pass`, `unexpected-fail`, or `blocked` only when there is a credible comparison basis or control.\n- `confidence`: `high`, `medium`, or `low`.\n- `state_setup`: Fresh or reused state, cache strategy, unique IDs, and cleanup checks.\n- `repeats`: Number of measured runs.\n- `warm_up`: Whether a warm-up run was used and why.\n- `variance`: Any useful spread or instability note across repeated runs.\n- `usage_note`: Token, usage, or output-length note when it materially affects interpretation.\n- `control`: Known-good comparison point for regression or behavior-change questions.\n- `risk_profile`: `read-only`, `mutating`, or `costly` for live probes.\n- `env_vars`: Exact environment-variable names the case plans to read.\n- `approval`: `not-needed`, `pending`, or `approved` for cases that need user permission before execution.\n\nUse `result_flag` as the fast scan field. It is what makes unexpected or negative findings jump out before the reader studies the full report.\n\nUse `status` only when you have a real comparison basis. If the case is exploratory and there is no trustworthy baseline, prefer a strong `observation_summary` plus `result_flag` and `confidence` instead of pretending the result is a clean pass or fail.\n\n## Choosing Execution Mode\n\nPick an execution mode before you run the case:\n\n- Use `single-shot` for deterministic, one-run checks.\n- Use `repeat-N` automatically when the question involves cache behavior, retries, streaming, interruptions, rate limiting, concurrency, or other run-to-run-sensitive behavior.\n- Use `warm-up + repeat-N` when the first run is likely to include cold-start effects such as container provisioning, import caches, or prompt-cache population.\n\nUse these defaults unless the task clearly needs something else:\n\n- `repeat-3` for a quick screen of a repeat-sensitive question.\n- `warm-up + repeat-10` for decision-grade latency comparisons or release-facing recommendations.\n- For costly live probes, start at `repeat-3`, then expand only if the answer is still unclear.\n\nIf it is genuinely unclear whether extra runs are worth the time or cost, ask the user before expanding the probe.\n\n## Phase The Matrix\n\nWhen the question is comparative or benchmark-like, do not jump straight to the largest matrix.\n\nStart with a pilot:\n\n- One control.\n- One or two highest-signal success cases.\n- The smallest repeat count that can disqualify a weak candidate quickly.\n\nExpand only when:\n\n- The candidate survives the pilot.\n- The results are close enough that more samples matter.\n- A major runtime surface is still uncovered.\n- The user explicitly wants decision-grade evidence.\n\n## Coverage Categories\n\nTry to cover at least one case from each relevant category:\n\n- `success`: Normal behavior that should work.\n- `control`: Known-good comparison such as `origin/master`, the latest release, or the same request without the suspected option.\n- `boundary`: Size, count, or parameter limits near a plausible edge.\n- `invalid`: Bad inputs or unsupported combinations.\n- `misconfig`: Missing key, wrong endpoint, bad permissions, or incompatible local setup.\n- `transient`: Timeout, temporary server issue, network breakage, or rate limiting.\n- `recovery`: Retry behavior, partial completion, duplicate submission, or cleanup.\n- `concurrency`: Overlapping operations when shared state, ordering, or isolation may matter.\n- `quality`: A harder or more open-ended sample when the user is asking about model intelligence, not just workflow parity.\n\nIf time is limited, prioritize categories in this order:\n\n1. Known-good control when the question implies regression or drift.\n2. Highest-risk success case.\n3. Most plausible user-facing failure.\n4. Most likely edge case with ambiguous behavior.\n5. Cleanup or retry semantics.\n6. Lower-probability extremes.\n\n## Matrix Template\n\nUse this compact template:\n\n    | case_id | scenario | mode | question | setup | state_setup | variable_under_test | held_constant | comparison_basis | observation_summary | result_flag | status | evidence |\n    | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n    | K1 | Known-good control | single-shot | Does the baseline still show the expected behavior? | Same probe against baseline target | Fresh state | none | current probe shape | `origin/master` or latest release | pending | pending | pending | pending |\n    | S1 | Baseline success | single-shot | What does the normal success path look like at runtime? | Valid config and representative input | Fresh state | none | representative input and setup | current docs or local expectation | pending | pending | pending | pending |\n    | R1 | Cache or retry behavior | warm-up + repeat-N | Does behavior change after the first run or across retries? | Same request repeated under controlled settings | Cache key or retry setup recorded | reuse versus fresh state | prompt shape and tool setup | same request without reuse, or docs if available | pending | pending | pending | pending |\n    | C1 | Model comparison pilot | warm-up + repeat-N | Does candidate B preserve the covered behavior while improving latency? | Same scenario across two models | Fresh state and stable IDs | model name | prompt shape, tool choice, and model settings parity | control model in the same probe | pending | pending | pending | pending |\n    | E1 | Invalid input | single-shot | How does the runtime reject a realistic bad input? | Missing required field | Fresh state | invalid field value | same request with valid field | same request with valid field | pending | pending | pending | pending |\n    | X1 | Concurrent overlap | repeat-N | Do overlapping runs interfere with each other? | Two or more overlapping operations | Unique IDs plus cleanup verification | overlap timing | same logical input | same request serialized, if available | pending | pending | pending | pending |\n\n## Recording Results\n\nKeep `question` unchanged after execution. Put the actual behavior in `observation_summary`, then mark the scan-friendly `result_flag`.\n\nUse these `result_flag` values consistently:\n\n- `unexpected`: The result diverged from the best current understanding in a surprising way.\n- `negative`: The result exposed a user-relevant failure, risk, or sharp edge.\n- `expected`: The result matched the current understanding and did not reveal new risk.\n- `blocked`: The case did not produce a trustworthy observation.\n\nOnly fill `status` when there is a credible comparison basis. Otherwise use `observation_summary`, `result_flag`, and `confidence` to communicate what was learned without over-claiming certainty.\n\nFor comparison cases, use `observation_summary` and the final report to say whether the evidence supports pattern parity only or a broader quality claim.\n\nIf a case reveals a new branch of behavior, add a follow-up case instead of overloading the original one.\n\n## Evidence Discipline\n\nTreat a case as incomplete when:\n\n- The observed output omits the key result you were testing.\n- The script mixed multiple questions and the result is ambiguous.\n- Hidden state, cache behavior, or previous runs may have influenced the result and were not controlled or documented.\n- The question is whether behavior changed, but the case has no credible control or baseline to compare against.\n- The case plans to read environment variables, but the exact variable names were not approved by the user before execution.\n- The case was repeat-sensitive, but it ran only once without a clear rationale.\n\nWhen this happens, narrow the probe and rerun. A smaller script with a cleaner result is better than a more complicated script that is hard to trust.\n\n<!-- File: .agents/skills/runtime-behavior-probe/SKILL.md (Tokens: ~3622 | Category: anthropic-skill) -->\n---\nname: runtime-behavior-probe\ndescription: Plan and execute runtime-behavior investigations with temporary TypeScript probe scripts, validation matrices, state controls, and findings-first reports. Use only when the user explicitly invokes this skill to verify actual runtime behavior beyond normal code-level checks, especially to uncover edge cases, undocumented behavior, or common failure modes in local or live integrations. A baseline smoke check is fine as an entry point, but do not stop at happy-path confirmation.\n---\n\n# Runtime Behavior Probe\n\n## Overview\n\nUse this skill to investigate real runtime behavior, not to restate code or documentation. Start by planning the investigation, then execute a case matrix, record observed behavior, and report both the findings and the method used to obtain them.\n\n## Core Rules\n\n- Treat this skill as manual-only. Do not rely on implicit invocation.\n- For `node-redis`, treat this skill as a disposable-probe workflow, not a repository implementation workflow.\n- Unless the user explicitly asks for a reusable repository artifact, the allowed write scope is limited to:\n  - a temporary directory used for probe scripts or artifacts\n  - `.agents/skills/runtime-behavior-probe/**` when the user is editing this skill itself\n- For disposable probes in `node-redis`, do not modify `examples/**`, `packages/**`, any `package.json`, `README.md`, workspace config, or build config.\n- If your draft plan would touch a disallowed path, stop and rewrite the plan before editing anything.\n- A baseline success or smoke case is often the right entry point, but do not stop there when the real question involves edge cases, drift, or failure behavior.\n- Plan before running anything. Write the case matrix first, then fill it in with observed results. The matrix can live in a scratch note, a temporary file, or the probe script header.\n- Default to local or read-only probes. Consider a live service only when it is clearly relevant, then apply the lightweight gates below before you run it.\n- Size the probe to the decision. Start with the smallest matrix that can disqualify or validate the current hypothesis, then expand only when uncertainty remains.\n- Before a live probe, apply three lightweight gates:\n  - Destination gate. Use only a live destination that is clearly allowed for the task.\n  - Intent gate. Run the live probe only when the user explicitly wants runtime verification on that integration, or explicitly approves it after you propose the probe.\n  - Data gate. If the probe will read environment variables, mutate remote state, incur material cost, or exercise non-public or user data, name the exact variable names or data class and get explicit approval first.\n- Classify each case as read-only, mutating, or costly before execution. For mutating or costly cases, or for any live case that will read environment variables, define cleanup or rollback before running the probe.\n- Use temporary files or a temporary directory for one-off probe scripts.\n- In `node-redis`, use disposable probe files outside git-tracked paths by default. Do not add one-off probes, harnesses, benchmarks, or examples under `examples/`, `packages/`, or other repository directories unless the user explicitly asks for a checked-in artifact.\n- Keep temporary artifacts until the final response is drafted. Then delete them by default unless the user asked to keep them or they are needed for follow-up. Even when artifacts are deleted, keep a short run summary of the command shape, runtime context, and artifact status in the report.\n- Before executing a live probe that will read environment variables, tell the user the exact variable names you plan to use and why, then wait for explicit approval. Examples include `REDIS_URL`, `REDIS_PASSWORD`, and other expected default names for the system under test.\n- Never print secrets, even when they come from standard environment variables that this skill may use.\n- For Redis command or server semantics, treat the repository source as the truth and use [redis.io/commands](https://redis.io/commands) to confirm contract-sensitive details such as argument order, reply types, and version availability. Use runtime probing to validate or challenge the documented behavior, not to skip the documentation pass entirely. If you rely on the website rather than source, say so in the report.\n- For benchmark or comparison probes, make parity explicit before execution. Record what is held constant, what variable is under test, which reply-shape constraints keep the comparison fair, and any counters or timings that matter for interpreting latency or cost.\n- In `node-redis`, default to a light local loop for probe authoring: temporary `probe.ts` plus temporary `tsconfig.json`, `npx tsc --noEmit -p <tmp-tsconfig>`, then `npx tsx <tmp-probe>`. Escalate to `npm run build` only when the runtime question is specifically about `dist/`, emitted exports, or packaged output.\n- In `node-redis`, do not treat a request for runtime verification, benchmarking, or comparison as permission to add a reusable example, benchmark harness, package script, or checked-in sample. Those repository changes require explicit user intent.\n- For probes that need a running Redis, remove setup ambiguity before attributing a negative result to client behavior:\n  - Confirm the server is reachable and note its mode (standalone, cluster, or sentinel) and RESP version before interpreting reply shapes.\n  - Treat RESP2 and RESP3 as separate cases, not interchangeable setup details, when the question is about reply type mapping or push messages.\n  - Clear unsupported server-version or command options first so they do not invalidate the probe. The repo test harness `@redis/test-utils` can start Redis in Docker for local probes.\n\n## Workflow\n\n1. Restate the investigation target in operational terms. Name the runtime surface, the key uncertainty, and the highest-risk behaviors to test.\n2. For `node-redis`, declare the allowed write scope before you do any implementation work. For a normal disposable probe, that means a temporary directory only.\n3. Do a short preflight. Check the relevant code or docs first, decide whether the question needs local or live validation, and note any repo, baseline, or release boundary that matters.\n4. Create a validation matrix before executing probes. Cover both baseline behavior and the most relevant failure or drift cases. The matrix can live in a scratch note, a temporary file, or a structured header inside the probe script.\n5. For each case, choose an execution mode up front:\n   - `single-shot` for deterministic one-run checks.\n   - `repeat-N` for cache, retry, streaming, interruption, rate-limit, concurrency, or other run-to-run-sensitive behavior.\n   - `warm-up + repeat-N` when first-run cold-start effects could distort the result. Use these defaults unless the task clearly needs something else:\n   - Quick screen of a repeat-sensitive question: `repeat-3`.\n   - Decision-grade latency or release recommendation: `warm-up + repeat-10`.\n   - Costly live cases: start at `repeat-3`, then expand only if the answer remains unclear. If it is genuinely unclear whether extra runs are worth the time or cost, ask the user before expanding the probe.\n6. When the question is benchmark-like or comparative, run in phases. Start with a high-signal pilot matrix against a control, then expand only the surviving candidates or unresolved cases.\n7. If the question is about a suspected regression or behavior change, add at least one known-good control case such as `origin/master`, the latest release, or the same request without the suspected option.\n8. For comparative probes, define parity before execution. Record prompt or input shape, tool-choice setup, model-settings parity, state reuse rules, and any response-shape constraint that keeps the comparison fair. If materially different output length could bias the result, record usage or token notes too.\n9. If the question asks whether one option has the same intelligence or quality as another, decide whether the matrix supports only example-pattern parity or a broader quality claim. For broader claims, add at least one harder or more open-ended case. Otherwise say explicitly that the result is limited to the covered patterns.\n10. Plan state controls before execution when hidden state could affect the result. Record whether each case uses fresh or reused state, how cache reuse or cache busting is handled, what unique IDs isolate repeated runs, and how cleanup is verified.\n11. If any live case will read environment variables, list the exact variable names and purpose for each case, then ask the user for approval before execution. Keep the approval ask short and include destination, read-only versus mutating or costly risk, exact variable names, and cleanup or rollback if relevant.\n12. Build task-specific probe scripts in a temporary location. Keep the script small, observable, and easy to discard.\n13. If you are about to propose a checked-in script, example, benchmark, or workspace script for `node-redis`, stop and verify that the user explicitly asked for a reusable repository artifact. If not, keep the probe temporary.\n14. In `node-redis`, make the runtime context explicit:\n\n- Run TypeScript probes from the repository root with `npx tsx` when practical.\n- For probe authoring, create both `probe.ts` and a sibling temporary `tsconfig.json` under `mktemp -d`, then run `npx tsc --noEmit -p <tmp-tsconfig>` before the first live execution.\n- Record the current commit, working directory, Node executable, Node version, and the package or source path you imported.\n- Avoid accidental imports from a different checkout or from `/tmp/node_modules`. If the probe needs repository code, import it from a repository-relative `file://` URL rooted at `process.cwd()`.\n- Prefer current-branch `lib/` imports when the question is \"what does this branch do now?\" and prefer `dist/` imports only when the question is specifically about packaged output after a build.\n- Do not run a repository-wide build just to typecheck a disposable probe. Reserve `npm run build` for `dist/` probes or when emitted output is itself part of the question.\n\n15. Execute the matrix and capture evidence. Record request shape, setup, observation summary, unexpected or negative result, error details, timing, runtime context, approved environment-variable names, repeat counts, warm-up handling, variance when relevant, cleanup behavior, and for comparisons note what was held constant plus any response-shape or usage notes that affect interpretation.\n16. Update the matrix with actual outcomes, not guesses.\n17. Keep temporary artifacts until the final response is drafted. Then delete them unless the user asked to keep them or they are needed for follow-up. Benchmark and repeat-heavy probes often need follow-up, so keeping artifacts is normal when the result may be revisited. If deleted, retain and report a short run summary.\n18. Report findings first, with unexpected or negative findings first. Then summarize how the validation was performed and which cases were covered.\n19. If the probe isolates one clear defect, you may include a short implementation hypothesis or minimal repro direction. Do not expand into a larger next-step plan unless the user asked for it.\n\n## Validation Matrix\n\nUse a matrix that makes the news easy to scan. Start from the runtime question and the observation summary, not just from `expected` and `pass` or `fail`.\n\nUse a matrix with at least these columns:\n\n- `case_id`\n- `scenario`\n- `mode`\n- `question`\n- `setup`\n- `observation_summary`\n- `result_flag`\n- `evidence`\n\nAdd these columns when they materially improve the investigation:\n\n- `comparison_basis`\n- `variable_under_test`\n- `held_constant`\n- `output_constraint`\n- `status`\n- `confidence`\n- `state_setup`\n- `repeats`\n- `warm_up`\n- `variance`\n- `usage_note`\n- `risk_profile`\n- `env_vars`\n- `approval`\n- `control`\n\nTreat `result_flag` as a fast scan field such as `unexpected`, `negative`, `expected`, or `blocked`. Use `status` only when there is a credible comparison basis, baseline, or documented contract to compare against.\n\nAlways consider whether the matrix should include these categories:\n\n- Baseline success.\n- Control or baseline comparison when a regression is suspected.\n- Boundary input or parameter variation.\n- Invalid or unsupported input.\n- Missing or incorrect configuration.\n- Transient external failure such as timeout, network interruption, or rate limiting.\n- Retry, idempotence, or cleanup behavior.\n- Concurrency or overlapping operations when shared state or ordering may matter.\n- Open-ended quality or intelligence samples when the question is broader than pattern parity.\n\nOpen [validation-matrix.md](./references/validation-matrix.md) when you need a stronger prioritization model or a reusable case template.\n\n## Temporary Probe Scripts\n\nWrite one-off scripts in a temporary file or temporary directory such as one created by `mktemp -d`. Keep the script outside the repository by default, even when it imports code from the repository.\n\nFor `node-redis`, a disposable runtime probe should stay disposable. Do not add a package script, modify workspace config, or create a checked-in benchmark or example unless the user explicitly asks for a reusable repository artifact.\n\nFor `node-redis`, the default authoring loop should be:\n\n1. `tmpdir=$(mktemp -d)`\n2. Write `probe.ts` and `tsconfig.json` into `$tmpdir`\n3. From the repository root, run `npx tsc --noEmit -p \"$tmpdir/tsconfig.json\"`\n4. If the quick typecheck passes, run `npx tsx \"$tmpdir/probe.ts\"`\n5. Only run `npm run build` if the probe intentionally imports `dist/` or validates packaged output\n\nUse a temporary `tsconfig.json` that extends the repository base settings but includes only the disposable probe, for example:\n\n```json\n{\n  \"extends\": \"/absolute/path/to/node-redis/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"include\": [\"./probe.ts\"]\n}\n```\n\nIf the probe needs repository code:\n\n- Run it with the repository root as the working directory.\n- Use `npx tsx /tmp/probe.ts` from the repository root when practical.\n- Use `npx tsc --noEmit -p /tmp/probe-tsconfig.json` as the default quick typecheck step before executing the probe.\n- Import repository modules with a `file://` URL built from `process.cwd()` and a repo-relative path. Do not assume bare workspace imports such as `@redis/client` will resolve from `/tmp`.\n- Use `lib/` imports for current-branch behavior probes and `dist/` imports for packaged-output probes.\n\nDesign the probe to maximize observability:\n\n- Print or log the exact scenario being exercised.\n- Capture runtime context such as git SHA, working directory, Node executable and version, relevant package versions, model or deployment name, endpoint or base URL alias, and any retry or tool options that materially affect behavior.\n- For live probes, record only the names of environment variables that were approved for use. Never print their values.\n- Capture structured outputs when possible.\n- Preserve raw error type, message, and status code.\n- For repeat-sensitive cases, capture the attempt index, warm-up status, and any stable identifiers that help compare runs.\n- For repeated or benchmark-style probes, write both raw results and a compact summary artifact when practical.\n- Keep branching minimal so each script answers a narrow question.\n\nBefore deleting the temporary script or directory, keep a short run summary of the script path, command used, runtime context, and whether the evidence was kept or deleted.\n\nOpen [typescript_probe.ts](./templates/typescript_probe.ts) when you want a lightweight disposable TypeScript probe scaffold. Open [repo-import-patterns.md](./references/repo-import-patterns.md) when you need to load current-branch workspace code from a temporary script.\n\n## Reporting\n\nReport in this order:\n\n1. Findings. Put unexpected or negative findings first. If there was no real news, say that explicitly.\n2. Validation approach. Summarize the code used, the runtime surface exercised, the execution modes, and the case matrix coverage.\n3. Case results. Include the matrix or a condensed version of it when the case count is large.\n4. Artifact status and brief run summary. State whether temporary artifacts were deleted or kept, and provide kept paths or the retained summary.\n5. Optional implementation note. Include this only when one clear defect was isolated and a short implementation direction would help.\n\nFor comparative probes, the report should also say what was held constant, what variable was under test, and whether the result supports only pattern parity or a broader quality claim.\n\nOpen [reporting-format.md](./references/reporting-format.md) for the recommended response template.\n\n## Resources\n\n- Open [validation-matrix.md](./references/validation-matrix.md) to design and prioritize the case matrix.\n- Open [error-cases.md](./references/error-cases.md) to expand common failure scenarios.\n- Open [redis-runtime-patterns.md](./references/redis-runtime-patterns.md) for recurring node-redis runtime probe patterns.\n- Open [repo-import-patterns.md](./references/repo-import-patterns.md) for repository-relative TypeScript import guidance.\n- Open [reporting-format.md](./references/reporting-format.md) for the final report structure.\n- Open [typescript_probe.ts](./templates/typescript_probe.ts) for a minimal disposable TypeScript probe scaffold.\n\n<!-- File: .agents/skills/test-coverage-improver/agents/openai.yaml (Tokens: ~58 | Category: anthropic-skill) -->\ninterface:\n  display_name: \"Test Coverage Improver\"\n  short_description: \"Analyze coverage gaps and propose high-impact tests\"\n  default_prompt: \"Use $test-coverage-improver to analyze coverage gaps, propose high-impact tests, and update coverage after approval.\"\n\n<!-- File: .agents/skills/test-coverage-improver/SKILL.md (Tokens: ~890 | Category: anthropic-skill) -->\n--\nname: test-coverage-improver\ndescription: 'Improve test coverage in the node-redis monorepo: run a package test suite under nyc, inspect coverage artifacts, identify low-coverage files and branches, propose high-impact tests, and confirm with the user before writing tests.'\n---\n\n# Test Coverage Improver\n\n## Overview\n\nUse this skill whenever coverage needs assessment or improvement (coverage regressions, failing thresholds, or user requests for stronger tests). It runs the coverage suite, analyzes results, highlights the biggest gaps, and prepares test additions while confirming with the user before changing code.\n\nCoverage is collected per package via `nyc` (each package's `test` script runs `nyc -r text-summary -r lcov mocha ...`). **Docker must be running** — the test harness starts real Redis containers via `@redis/test-utils`.\n\n## Quick Start\n\n1. Pick the target package (e.g. `@redis/client`) and run its tests under coverage from the repo root: `npm test -w @redis/client`. This regenerates that package's `coverage/`.\n2. Collect artifacts: the `text-summary` printed to stdout for totals, plus `packages/<pkg>/coverage/lcov.info` and `packages/<pkg>/coverage/lcov-report/index.html` for line/branch drill-downs. For machine-readable file totals, rerun with an added summary reporter: `npm test -w @redis/client -- ... ` is awkward, so prefer `npx nyc -r json-summary -r text-summary mocha ...` from inside the package when you need `coverage/coverage-summary.json`.\n3. Summarize coverage: total percentages, lowest files, branches under 80%, and uncovered lines/paths.\n4. Draft test ideas per file: scenario, behavior under test, expected outcome, and likely coverage gain.\n5. Ask the user for approval to implement the proposed tests; pause until they agree.\n6. After approval, write the tests next to the source in the relevant package, rerun coverage, then run `npm run build` and `npm run lint` before marking work complete.\n\n## Workflow Details\n\n- **Run coverage**: Execute the target package's test script (`npm test -w <pkg>`) from the repo root. Avoid watch flags. Keep prior coverage artifacts only if comparing trends. Confirm Docker is available first.\n- **Parse summaries efficiently**:\n  - Use the printed `text-summary` for quick totals.\n  - Use `coverage/lcov.info` or `coverage/lcov-report/index.html` to spot branch- and line-level holes.\n  - Add the `json-summary` nyc reporter when you need `coverage/coverage-summary.json` for file-level totals.\n- **Prioritize targets**:\n  - Core client/cluster/sentinel/pool/RESP code in `packages/*/lib` before examples or docs.\n  - Files with statements/branches below 80% or newly added code at 0%.\n  - Recent bug fixes or risky code paths (error handling, reconnection, timeouts, pub/sub, concurrency).\n- **Design impactful tests**:\n  - Hit uncovered branches: error cases, boundary inputs, optional flags, and cancellation/timeouts.\n  - Cover combinational logic rather than trivial happy paths.\n  - Co-locate specs next to source as `packages/<pkg>/lib/**/<NAME>.spec.ts` (mocha + `tsx`, `node:assert`). For command/topology tests use `testUtils.testAll(name, fn, { client, cluster })`. Avoid flaky async timing.\n- **Coordinate with the user**: Present a numbered, concise list of proposed test additions and expected coverage gains. Ask explicitly before editing code or fixtures.\n- **After implementation**: Rerun coverage, report the updated summary, and note any remaining low-coverage areas.\n\n## Notes\n\n- Keep any added comments or code in English.\n- Do not create `scripts/`, `references/`, or `assets/` unless needed later.\n- If coverage artifacts are missing or stale, rerun the package test suite instead of guessing.\n- `nyc` excludes `dist`, `**/*.spec.ts`, `lib/test-utils.ts`, and `examples/*` (see each package's `.nycrc`); do not target those for coverage.\n\n<!-- File: packages/bloom/README.md (Tokens: ~292 | Category: plugin-manifest) -->\n# @redis/bloom\n\nThis package provides support for the [RedisBloom](https://redis.io/docs/data-types/probabilistic/) module, which adds additional probabilistic data structures to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisBloom module installed.\n\nRedisBloom provides the following probabilistic data structures:\n\n* Bloom Filter: for checking set membership with a high degree of certainty.\n* Cuckoo Filter: for checking set membership with a high degree of certainty.\n* T-Digest: for estimating the quantiles of a stream of data.\n* Top-K: Maintain a list of k most frequently seen items.\n* Count-Min Sketch: Determine the frequency of events in a stream.\n\nFor some examples, see [`bloom-filter.js`](https://github.com/redis/node-redis/tree/master/examples/bloom-filter.js), [`cuckoo-filter.js`](https://github.com/redis/node-redis/tree/master/examples/cuckoo-filter.js), [`count-min-sketch.js`](https://github.com/redis/node-redis/tree/master/examples/count-min-sketch.js) and [`topk.js`](https://github.com/redis/node-redis/tree/master/examples/topk.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n\n<!-- File: packages/client/README.md (Tokens: ~34 | Category: plugin-manifest) -->\n# @redis/client\n\nThe source code and documentation for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.\n\n<!-- File: packages/entraid/README.md (Tokens: ~1404 | Category: plugin-manifest) -->\n# @redis/entraid\n\nSecure token-based authentication for Redis clients using Microsoft Entra ID (formerly Azure Active Directory).\n\n## Features\n\n- Token-based authentication using Microsoft Entra ID\n- Automatic token refresh before expiration\n- Automatic re-authentication of all connections after token refresh\n- Support for multiple authentication flows:\n  - Managed identities (system-assigned and user-assigned)\n  - Service principals (with or without certificates)\n  - Authorization Code with PKCE flow\n  - DefaultAzureCredential from @azure/identity\n- Built-in retry mechanisms for transient failures\n\n## Installation\n\n\n```bash\nnpm install \"@redis/client@5.0.0-next.7\"\nnpm install \"@redis/entraid@5.0.0-next.7\"\n```\n\n## Getting Started\n\nThe first step to using @redis/entraid is choosing the right credentials provider for your authentication needs. The `EntraIdCredentialsProviderFactory` class provides several factory methods to create the appropriate provider:\n\n- `createForSystemAssignedManagedIdentity`: Use when your application runs in Azure with a system-assigned managed identity\n- `createForUserAssignedManagedIdentity`: Use when your application runs in Azure with a user-assigned managed identity\n- `createForClientCredentials`: Use when authenticating with a service principal using client secret\n- `createForClientCredentialsWithCertificate`: Use when authenticating with a service principal using a certificate\n- `createForAuthorizationCodeWithPKCE`: Use for interactive authentication flows in user applications\n- `createForDefaultAzureCredential`: Use when you want to leverage Azure Identity's DefaultAzureCredential\n\n## Usage Examples\n\n### Service Principal Authentication\n\n```typescript\nimport { createClient } from '@redis/client';\nimport { EntraIdCredentialsProviderFactory } from '@redis/entraid';\n\nconst provider = EntraIdCredentialsProviderFactory.createForClientCredentials({\n  clientId: 'your-client-id',\n  clientSecret: 'your-client-secret',\n  authorityConfig: {\n    type: 'multi-tenant',\n    tenantId: 'your-tenant-id'\n  },\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8 // Refresh token after 80% of its lifetime\n  }\n});\n\nconst client = createClient({\n  url: 'redis://your-host',\n  credentialsProvider: provider\n});\n\nawait client.connect();\n```\n\n### System-Assigned Managed Identity\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForSystemAssignedManagedIdentity({\n  clientId: 'your-client-id',\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n```\n\n### User-Assigned Managed Identity\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForUserAssignedManagedIdentity({\n  clientId: 'your-client-id',\n  userAssignedClientId: 'your-user-assigned-client-id',\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n```\n\n### DefaultAzureCredential Authentication\n\ntip: see a real sample here: [samples/interactive-browser/index.ts](./samples/interactive-browser/index.ts)\n\nThe DefaultAzureCredential from @azure/identity provides a simplified authentication experience that automatically tries different authentication methods based on the environment. This is especially useful for applications that need to work in different environments (local development, CI/CD, and production).\n\n```typescript\nimport { createClient } from '@redis/client';\nimport { getDefaultAzureCredential } from '@azure/identity';\nimport { EntraIdCredentialsProviderFactory, REDIS_SCOPE_DEFAULT } from '@redis/entraid';\n\n// Create a DefaultAzureCredential instance\nconst credential = getDefaultAzureCredential();\n\n// Create a provider using DefaultAzureCredential\nconst provider = EntraIdCredentialsProviderFactory.createForDefaultAzureCredential({\n\n  credential,\n  scopes: REDIS_SCOPE_DEFAULT, // The Redis scope\n  // Optional additional parameters for getToken\n  options: {\n    // Any options you would normally pass to azure's default credential.getToken()\n  },\n  tokenManagerConfig: {\n    expirationRefreshRatio: 0.8\n  }\n});\n\nconst client = createClient({\n  url: 'redis://your-host',\n  credentialsProvider: provider\n});\n\nawait client.connect();\n```\n\n#### Important Notes on Using DefaultAzureCredential\n\nWhen using the `createForDefaultAzureCredential` method, you need to:\n\n1. Create your own instance of `DefaultAzureCredential`\n2. Pass the same parameters to the factory method that you would use with the `getToken()` method:\n   - `scopes`: The Redis scope (use the exported `REDIS_SCOPE_DEFAULT` constant)\n   - `options`: Any additional options for the getToken method\n\nThis factory method creates a wrapper around DefaultAzureCredential that adapts it to the Redis client's\nauthentication system, while maintaining all the flexibility of the original Azure Identity authentication.\n\n## Important Limitations\n\n### RESP2 PUB/SUB Limitations\n\nWhen using RESP2 (Redis Serialization Protocol 2), there are important limitations with PUB/SUB:\n\n- **No Re-Authentication in PUB/SUB Mode**: In RESP2, once a connection enters PUB/SUB mode, the socket is blocked and cannot process out-of-band commands like AUTH. This means that connections in PUB/SUB mode cannot be re-authenticated when tokens are refreshed.\n- **Connection Eviction**: As a result, PUB/SUB connections will be evicted by the Redis proxy when their tokens expire. The client will need to establish new connections with fresh tokens.\n\n### Transaction Safety\n\nWhen using token-based authentication, special care must be taken with Redis transactions. The token manager runs in the background and may attempt to re-authenticate connections at any time by sending AUTH commands. This can interfere with manually constructed transactions.\n\n#### ✅ Recommended: Use the Official Transaction API\n\nAlways use the official transaction API provided by the client:\n\n```typescript\n// Correct way to handle transactions\nconst multi = client.multi();\nmulti.set('key1', 'value1');\nmulti.set('key2', 'value2');\nawait multi.exec();\n```\n\n#### ❌ Avoid: Manual Transaction Construction\n\nDo not manually construct transactions by sending individual MULTI/EXEC commands:\n\n```typescript\n// Incorrect and potentially dangerous\nawait client.sendCommand(['MULTI']);\nawait client.sendCommand(['SET', 'key1', 'value1']);\nawait client.sendCommand(['SET', 'key2', 'value2']);\nawait client.sendCommand(['EXEC']); // Risk of AUTH command being injected before EXEC\n```\n\n## Error Handling\n\nThe provider includes built-in retry mechanisms for transient errors:\n\n```typescript\nconst provider = EntraIdCredentialsProviderFactory.createForClientCredentials({\n  // ... other config ...\n  tokenManagerConfig: {\n    retry: {\n      maxAttempts: 3,\n      initialDelayMs: 100,\n      maxDelayMs: 1000,\n      backoffMultiplier: 2\n    }\n  }\n});\n```\n\n<!-- File: packages/json/README.md (Tokens: ~635 | Category: plugin-manifest) -->\n# @redis/json\n\nThis package provides support for the [RedisJSON](https://redis.io/docs/latest/develop/data-types/json/) module, which adds JSON as a native data type to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisJSON module installed.\n\n## Usage\n\nFor a complete example, see [`managing-json.js`](https://github.com/redis/node-redis/blob/master/examples/managing-json.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n\n### Storing JSON Documents in Redis\n\nThe [`JSON.SET`](https://redis.io/commands/json.set/) command stores a JSON value at a given JSON Path in a Redis key.\n\nHere, we'll store a JSON document in the root of the Redis key \"`mydoc`\":\n\n```javascript\nawait client.json.set('noderedis:jsondata', '$', {\n  name: 'Roberta McDonald',\n  pets: [{\n    name: 'Rex',\n    species: 'dog',\n    age: 3,\n    isMammal: true\n  }, {\n    name: 'Goldie',\n    species: 'fish',\n    age: 2,\n    isMammal: false\n  }]\n});\n```\n\nFor more information about RedisJSON's path syntax, [check out the documentation](https://redis.io/docs/latest/develop/data-types/json/path).\n\n### Retrieving JSON Documents from Redis\n\nWith RedisJSON, we can retrieve all or part(s) of a JSON document using the [`JSON.GET`](https://redis.io/commands/json.get/) command and one or more JSON Paths. Let's get the name and age of one of the pets:\n\n```javascript\nconst results = await client.json.get('noderedis:jsondata', {\n  path: [\n    '.pets[1].name',\n    '.pets[1].age'\n  ]\n});\n```\n\n`results` will contain the following:\n\n```javascript\n { '.pets[1].name': 'Goldie', '.pets[1].age': 2 }\n```\n\n### Performing Atomic Updates on JSON Documents Stored in Redis\n\nRedisJSON includes commands that can atomically update values in a JSON document, in place in Redis without having to first retrieve the entire document.\n\nUsing the [`JSON.NUMINCRBY`](https://redis.io/commands/json.numincrby/) command, we can update the age of one of the pets like this:\n\n```javascript\nawait client.json.numIncrBy('noderedis:jsondata', '.pets[1].age', 1);\n```\n\nAnd we can add a new object to the pets array with the [`JSON.ARRAPPEND`](https://redis.io/commands/json.arrappend/) command:\n\n```javascript\nawait client.json.arrAppend('noderedis:jsondata', '.pets', {\n  name: 'Robin',\n  species: 'bird',\n  age: 1,\n  isMammal: false\n});\n```\n\n<!-- File: packages/redis/README.md (Tokens: ~3475 | Category: plugin-manifest) -->\n# Node-Redis\n\n[![Tests](https://img.shields.io/github/actions/workflow/status/redis/node-redis/tests.yml?branch=master)](https://github.com/redis/node-redis/actions/workflows/tests.yml)\n[![Coverage](https://codecov.io/gh/redis/node-redis/branch/master/graph/badge.svg?token=xcfqHhJC37)](https://codecov.io/gh/redis/node-redis)\n[![License](https://img.shields.io/github/license/redis/node-redis.svg)](https://github.com/redis/node-redis/blob/master/LICENSE)\n\n[![Discord](https://img.shields.io/discord/697882427875393627.svg?style=social&logo=discord)](https://discord.gg/redis)\n[![Twitch](https://img.shields.io/twitch/status/redisinc?style=social)](https://www.twitch.tv/redisinc)\n[![YouTube](https://img.shields.io/youtube/channel/views/UCD78lHSwYqMlyetR0_P4Vig?style=social)](https://www.youtube.com/redisinc)\n[![Twitter](https://img.shields.io/twitter/follow/redisinc?style=social)](https://twitter.com/redisinc)\n\nnode-redis is a modern, high performance [Redis](https://redis.io) client for Node.js.\n\n## How do I Redis?\n\n[Learn for free at Redis University](https://university.redis.com/)\n\n[Build faster with the Redis Launchpad](https://launchpad.redis.com/)\n\n[Try the Redis Cloud](https://redis.com/try-free/)\n\n[Dive in developer tutorials](https://developer.redis.com/)\n\n[Join the Redis community](https://redis.com/community/)\n\n[Work at Redis](https://redis.com/company/careers/jobs/)\n\n## Installation\n\nStart a redis via docker:\n\n```bash\ndocker run -p 6379:6379 -d redis:8.0-rc1\n```\n\nTo install node-redis, simply:\n\n```bash\nnpm install redis\n```\n> \"redis\" is the \"whole in one\" package that includes all the other packages. If you only need a subset of the commands,\n> you can install the individual packages. See the list below.\n\n## Packages\n\n| Name                                           | Description                                                                                 |\n| ---------------------------------------------- | ------------------------------------------------------------------------------------------- |\n| [`redis`](https://github.com/redis/node-redis/tree/master/packages/redis)                    | The client with all the [\"redis-stack\"](https://github.com/redis-stack/redis-stack) modules |\n| [`@redis/client`](https://github.com/redis/node-redis/tree/master/packages/client)           | The base clients (i.e `RedisClient`, `RedisCluster`, etc.)                                  |\n| [`@redis/bloom`](https://github.com/redis/node-redis/tree/master/packages/bloom)             | [Redis Bloom](https://redis.io/docs/data-types/probabilistic/) commands                     |\n| [`@redis/json`](https://github.com/redis/node-redis/tree/master/packages/json)               | [Redis JSON](https://redis.io/docs/data-types/json/) commands                               |\n| [`@redis/search`](https://github.com/redis/node-redis/tree/master/packages/search)           | [RediSearch](https://redis.io/docs/interact/search-and-query/) commands                     |\n| [`@redis/time-series`](https://github.com/redis/node-redis/tree/master/packages/time-series) | [Redis Time-Series](https://redis.io/docs/data-types/timeseries/) commands                  |\n| [`@redis/entraid`](https://github.com/redis/node-redis/tree/master/packages/entraid)         | Secure token-based authentication for Redis clients using Microsoft Entra ID                |\n\n> Looking for a high-level library to handle object mapping?\n> See [redis-om-node](https://github.com/redis/redis-om-node)!\n\n\n## Usage\n\n### Basic Example\n\n```typescript\nimport { createClient } from \"redis\";\n\nconst client = await createClient()\n  .on(\"error\", (err) => console.log(\"Redis Client Error\", err))\n  .connect();\n\nawait client.set(\"key\", \"value\");\nconst value = await client.get(\"key\");\nclient.destroy();\n```\n\nThe above code connects to localhost on port 6379. To connect to a different host or port, use a connection string in\nthe format `redis[s]://[[username][:password]@][host][:port][/db-number]`:\n\n```typescript\ncreateClient({\n  url: \"redis://alice:foobared@awesome.redis.server:6380\",\n});\n```\n\nYou can also use discrete parameters, UNIX sockets, and even TLS to connect. Details can be found in\nthe [client configuration guide](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md).\n\nTo check if the the client is connected and ready to send commands, use `client.isReady` which returns a boolean.\n`client.isOpen` is also available. This returns `true` when the client's underlying socket is open, and `false` when it\nisn't (for example when the client is still connecting or reconnecting after a network error).\n\n### Redis Commands\n\nThere is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed\nusing the raw Redis command names (`HSET`, `HGETALL`, etc.) and a friendlier camel-cased version (`hSet`, `hGetAll`,\netc.):\n\n```typescript\n// raw Redis commands\nawait client.HSET(\"key\", \"field\", \"value\");\nawait client.HGETALL(\"key\");\n\n// friendly JavaScript commands\nawait client.hSet(\"key\", \"field\", \"value\");\nawait client.hGetAll(\"key\");\n```\n\nModifiers to commands are specified using a JavaScript object:\n\n```typescript\nawait client.set(\"key\", \"value\", {\n  EX: 10,\n  NX: true,\n});\n```\n\nCommands that take several values of the same kind, such as `DEL`, `HDEL` or `LPUSH`, accept either a single value or\nan array:\n\n```typescript\nawait client.hDel(\"key\", \"field\");\nawait client.hDel(\"key\", [\"field1\", \"field2\"]);\n```\n\nDo not spread the values into separate arguments (`client.hDel(\"key\", ...fields)`). Only the first value is sent, and\nthe rest are silently ignored.\n\nReplies will be transformed into useful data structures:\n\n```typescript\nawait client.hGetAll(\"key\"); // { field1: 'value1', field2: 'value2' }\nawait client.hVals(\"key\"); // ['value1', 'value2']\n```\n\n`Buffer`s are supported as well:\n\n```typescript\nconst client = createClient().withTypeMapping({\n  [RESP_TYPES.BLOB_STRING]: Buffer\n});\n\nawait client.hSet(\"key\", \"field\", Buffer.from(\"value\")); // 'OK'\nawait client.hGet(\"key\", \"field\"); // { field: <Buffer 76 61 6c 75 65> }\n\n```\n\nFor commands that return serialized binary payloads, such as `DUMP`, map blob strings to `Buffer` before using the result with commands like `RESTORE`:\n\n```typescript\nconst binaryClient = createClient().withTypeMapping({\n  [RESP_TYPES.BLOB_STRING]: Buffer\n});\n\nconst dump = await binaryClient.dump(\"source\");\nawait binaryClient.restore(\"destination\", 0, dump);\n```\n\n### Unsupported Redis Commands\n\nIf you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use `.sendCommand()`:\n\n```typescript\nawait client.sendCommand([\"SET\", \"key\", \"value\", \"NX\"]); // 'OK'\n\nawait client.sendCommand([\"HGETALL\", \"key\"]); // ['key1', 'field1', 'key2', 'field2']\n```\n\n### Transactions (Multi/Exec)\n\nStart a [transaction](https://redis.io/topics/transactions) by calling `.multi()`, then chaining your commands. When\nyou're done, call `.exec()` and you'll get an array back with your results:\n\n```typescript\nawait client.set(\"another-key\", \"another-value\");\n\nconst [setKeyReply, otherKeyValue] = await client\n  .multi()\n  .set(\"key\", \"value\")\n  .get(\"another-key\")\n  .exec(); // ['OK', 'another-value']\n```\n\nYou can also [watch](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set) keys by calling\n`.watch()`. Your transaction will abort if any of the watched keys change.\n\n\n### Blocking Commands\n\nIn v4, `RedisClient` had the ability to create a pool of connections using an \"Isolation Pool\" on top of the \"main\"\nconnection. However, there was no way to use the pool without a \"main\" connection:\n\n```javascript\nconst client = await createClient()\n  .on(\"error\", (err) => console.error(err))\n  .connect();\n\nawait client.ping(client.commandOptions({ isolated: true }));\n```\n\nIn v5 we've extracted this pool logic into its own class—`RedisClientPool`:\n\n```javascript\nconst pool = await createClientPool()\n  .on(\"error\", (err) => console.error(err))\n  .connect();\n\nawait pool.ping();\n```\n\n\n### Pub/Sub\n\nSee the [Pub/Sub overview](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md).\n\n### Scan Iterator\n\n[`SCAN`](https://redis.io/commands/scan) results can be looped over\nusing [async iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator):\n\n```typescript\nfor await (const key of client.scanIterator()) {\n  // use the key!\n  await client.get(key);\n}\n```\n\nThis works with `HSCAN`, `SSCAN`, and `ZSCAN` too:\n\n```typescript\nfor await (const { field, value } of client.hScanIterator(\"hash\")) {\n}\nfor await (const member of client.sScanIterator(\"set\")) {\n}\nfor await (const { score, value } of client.zScanIterator(\"sorted-set\")) {\n}\n```\n\nYou can override the default options by providing a configuration object:\n\n```typescript\nclient.scanIterator({\n  TYPE: \"string\", // `SCAN` only\n  MATCH: \"patter*\",\n  COUNT: 100,\n});\n```\n\n### Disconnecting\n\nThe `QUIT` command has been deprecated in Redis 7.2 and should now also be considered deprecated in Node-Redis. Instead\nof sending a `QUIT` command to the server, the client can simply close the network connection.\n\n`client.QUIT/quit()` is replaced by `client.close()`. and, to avoid confusion, `client.disconnect()` has been renamed to\n`client.destroy()`.\n\n```typescript\nclient.destroy();\n```\n### Client Side Caching\n\n[Client Side Caching](https://redis.io/docs/manual/client-side-caching/) keeps command replies in a local cache. The Redis server notifies the client when cached replies are no longer valid. Requires RESP3.\n\n```typescript\nconst client = createClient({\n  clientSideCache: {\n    ttl: 0,                // Time-to-live (0 = no expiration)\n    maxEntries: 0,         // Maximum entries (0 = unlimited)\n    evictPolicy: \"LRU\",    // Eviction policy: \"LRU\" or \"FIFO\"\n    trackingMode: \"optin\"  // \"plain\" (default), \"optin\", or \"optout\"\n  }\n});\nawait client.connect();\n\nawait client.withCommandOptions({ cache: true }).get(\"user:42\"); // cached\nawait client.get(\"counter:hits\");                                // not cached in \"optin\" mode\n```\n\nSee [Client Side Caching](https://github.com/redis/node-redis/blob/master/docs/client-side-caching.md) for tracking modes, choosing what is cached, and more.\n\n### Auto-Pipelining\n\nNode Redis will automatically pipeline requests that are made during the same \"tick\".\n\n```typescript\nclient.set(\"Tm9kZSBSZWRpcw==\", \"users:1\");\nclient.sAdd(\"users:1:tokens\", \"Tm9kZSBSZWRpcw==\");\n```\n\nOf course, if you don't do something with your Promises you're certain to\nget [unhandled Promise exceptions](https://nodejs.org/api/process.html#process_event_unhandledrejection). To take\nadvantage of auto-pipelining and handle your Promises, use `Promise.all()`.\n\n```typescript\nawait Promise.all([\n  client.set(\"Tm9kZSBSZWRpcw==\", \"users:1\"),\n  client.sAdd(\"users:1:tokens\", \"Tm9kZSBSZWRpcw==\"),\n]);\n```\n\n### Programmability\n\nSee the [Programmability overview](https://github.com/redis/node-redis/blob/master/docs/programmability.md).\n\n### Clustering\n\nCheck out the [Clustering Guide](https://github.com/redis/node-redis/blob/master/docs/clustering.md) when using Node Redis to connect to a Redis Cluster.\n\n### Events\n\nThe Node Redis client class is an Nodejs EventEmitter and it emits an event each time the network status changes:\n\n| Name                    | When                                                                               | Listener arguments                                        |\n| ----------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------- |\n| `connect`               | Initiating a connection to the server                                              | _No arguments_                                            |\n| `ready`                 | Client is ready to use                                                             | _No arguments_                                            |\n| `end`                   | Connection has been closed (via `.disconnect()`)                                   | _No arguments_                                            |\n| `error`                 | An error has occurred—usually a network issue such as \"Socket closed unexpectedly\" | `(error: Error)`                                          |\n| `reconnecting`          | Client is trying to reconnect to the server                                        | _No arguments_                                            |\n| `terminated`             | Reconnection has stopped because `reconnectStrategy` returned `false` or an `Error`; emitted before the companion `error` | `(cause: Error)`                                           |\n| `sharded-channel-moved` | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event)                          | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event) |\n\n> :warning: You **MUST** listen to `error` events. If a client doesn't have at least one `error` listener registered and\n> an `error` occurs, that error will be thrown and the Node.js process will exit. See the [ > `EventEmitter` docs](https://nodejs.org/api/events.html#events_error_events) for more details.\n\n> The client will not emit [any other events](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md#all-the-removed-events) beyond those listed above.\n\n## Supported Redis versions\n\nNode Redis is supported with the following versions of Redis:\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 8.0.z   | :heavy_check_mark: |\n| 7.4.z   | :heavy_check_mark: |\n| 7.2.z   | :heavy_check_mark: |\n| < 7.2   | :x:                |\n\n> Node Redis should work with older versions of Redis, but it is not fully tested and we cannot offer support.\n\n## Migration\n\n- [From V3 to V4](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md)\n- [From V4 to V5](https://github.com/redis/node-redis/blob/master/docs/v4-to-v5.md)\n- [V5](https://github.com/redis/node-redis/blob/master/docs/v5.md)\n\n## Contributing\n\nIf you'd like to contribute, check out the [contributing guide](https://github.com/redis/node-redis/blob/master/CONTRIBUTING.md).\n\nThank you to all the people who already contributed to Node Redis!\n\n[![Contributors](https://contrib.rocks/image?repo=redis/node-redis)](https://github.com/redis/node-redis/graphs/contributors)\n\n## License\n\nThis repository is licensed under the \"MIT\" license. See [LICENSE](https://github.com/redis/node-redis/blob/master/LICENSE).\n\n<!-- File: packages/search/README.md (Tokens: ~1253 | Category: plugin-manifest) -->\n# @redis/search\n\nThis package provides support for the [RediSearch](https://redis.io/docs/interact/search-and-query/) module, which adds indexing and querying support for data stored in Redis Hashes or as JSON documents with the [RedisJSON](https://redis.io/docs/data-types/json/) module.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RediSearch module installed. To index and query JSON documents, you'll also need to add the RedisJSON module.\n\n## Usage\n\nFor complete examples, see [`search-hashes.js`](https://github.com/redis/node-redis/blob/master/examples/search-hashes.js) and [`search-json.js`](https://github.com/redis/node-redis/blob/master/examples/search-json.js) in the [examples folder](https://github.com/redis/node-redis/tree/master/examples).\n\n### Indexing and Querying Data in Redis Hashes\n\n#### Creating an Index\n\nBefore we can perform any searches, we need to tell RediSearch how to index our data, and which Redis keys to find that data in.  The [FT.CREATE](https://redis.io/commands/ft.create) command creates a RediSearch index. Here's how to use it to create an index we'll call `idx:animals` where we want to index hashes containing `name`, `species` and `age` fields, and whose key names in Redis begin with the prefix `noderedis:animals`:\n\n```javascript\nawait client.ft.create('idx:animals', {\n  name: {\n    type: SCHEMA_FIELD_TYPE.TEXT,\n    SORTABLE: true\n  },\n  species: SCHEMA_FIELD_TYPE.TAG,\n  age: SCHEMA_FIELD_TYPE.NUMERIC\n}, {\n  ON: 'HASH',\n  PREFIX: 'noderedis:animals'\n});\n```\n\nSee the [`FT.CREATE` documentation](https://redis.io/commands/ft.create/#description) for information about the different field types and additional options.\n\n#### Indexing a Field Multiple Times\n\nYou can index the same field multiple times with different types or aliases by using an array:\n\n```javascript\nawait client.ft.create('idx:products', {\n  sku: [\n    { type: SCHEMA_FIELD_TYPE.TEXT, AS: 'sku_text' },\n    { type: SCHEMA_FIELD_TYPE.TAG, AS: 'sku_tag', SORTABLE: true }\n  ]\n}, {\n  ON: 'HASH',\n  PREFIX: 'product:'\n});\n```\n\nThis allows querying the same field using different search strategies.\n\n#### Querying the Index\n\nOnce we've created an index, and added some data to Redis hashes whose keys begin with the prefix `noderedis:animals`, we can start writing some search queries.  RediSearch supports a rich query syntax for full-text search, faceted search, aggregation and more.  Check out the [`FT.SEARCH` documentation](https://redis.io/commands/ft.search) and the [query syntax reference](https://redis.io/docs/interact/search-and-query/query) for more information.\n\nLet's write a query to find all the animals where the `species` field has the value `dog`:\n\n```javascript\nconst results = await client.ft.search('idx:animals', '@species:{dog}');\n```\n\n`results` looks like this:\n\n```javascript\n{\n  total: 2,\n  documents: [\n    {\n      id: 'noderedis:animals:4',\n      value: {\n        name: 'Fido',\n        species: 'dog',\n        age: '7'\n      }\n    },\n    {\n      id: 'noderedis:animals:3',\n      value: {\n        name: 'Rover',\n        species: 'dog',\n        age: '9'\n      }\n    }\n  ]\n}\n```\n\n### Indexing and Querying Data with RedisJSON\n\nRediSearch can also index and query JSON documents stored in Redis using the RedisJSON module.  The approach is similar to that for indexing and searching data in hashes, but we can now use JSON Path like syntax and the data no longer has to be flat name/value pairs - it can contain nested objects and arrays.\n\n#### Creating an Index\n\nAs before, we create an index with the `FT.CREATE` command, this time specifying we want to index JSON documents that look like this:\n\n```javascript\n{\n  name: 'Alice',\n  age: 32,\n  coins: 100\n}\n```\n\nEach document represents a user in some system, and users have name, age and coins properties.\n\nOne way we might choose to index these documents is as follows:\n\n```javascript\nawait client.ft.create('idx:users', {\n  '$.name': {\n    type: SCHEMA_FIELD_TYPE.TEXT,\n    SORTABLE: 'UNF'\n  },\n  '$.age': {\n    type: SCHEMA_FIELD_TYPE.NUMERIC,\n    AS: 'age'\n  },\n  '$.coins': {\n    type: SCHEMA_FIELD_TYPE.NUMERIC,\n    AS: 'coins'\n  }\n}, {\n  ON: 'JSON',\n  PREFIX: 'noderedis:users'\n});\n```\n\nNote that we're using JSON Path to specify where the fields to index are in our JSON documents, and the `AS` clause to define a name/alias for each field.  We'll use these when writing queries.\n\n#### Querying the Index\n\nNow we have an index and some data stored as JSON documents in Redis (see the [JSON package documentation](https://github.com/redis/node-redis/tree/master/packages/json) for examples of how to store JSON), we can write some queries...\n\nWe'll use the [RediSearch query language](https://redis.io/docs/interact/search-and-query/query) and [`FT.SEARCH`](https://redis.io/commands/ft.search) command.  Here's a query to find users under the age of 30:\n\n```javascript\nawait client.ft.search('idx:users', '@age:[0 30]');\n```\n\n<!-- File: packages/time-series/README.md (Tokens: ~1545 | Category: plugin-manifest) -->\n# @redis/time-series\n\nThis package provides support for the [RedisTimeSeries](https://redis.io/docs/data-types/timeseries/) module, which adds a time series data structure to Redis.\n\nShould be used with [`redis`/`@redis/client`](https://github.com/redis/node-redis).\n\n:warning: To use these extra commands, your Redis server must have the RedisTimeSeries module installed.\n\n## Usage\n\nFor a complete example, see [`time-series.js`](https://github.com/redis/node-redis/blob/master/examples/time-series.js) in the Node Redis examples folder.\n\n### Creating Time Series data structure in Redis\n\nThe [`TS.CREATE`](https://oss.redis.com/redistimeseries/commands/#tscreate) command creates a new time series.\n\nHere, we'll create a new time series \"`temperature`\":\n\n```javascript\n\nimport { createClient } from 'redis';\nimport { TimeSeriesDuplicatePolicies, TimeSeriesEncoding, TIME_SERIES_AGGREGATION_TYPE } from '@redis/time-series';\n\n...\nconst created = await client.ts.create('temperature', {\n  RETENTION: 86400000, // 1 day in milliseconds\n  ENCODING: TimeSeriesEncoding.UNCOMPRESSED, // No compression - When not specified, the option is set to COMPRESSED\n  DUPLICATE_POLICY: TimeSeriesDuplicatePolicies.BLOCK, // No duplicates - When not specified: set to the global DUPLICATE_POLICY configuration of the database (which by default, is BLOCK).\n});\n\nif (created === 'OK') {\n  console.log('Created timeseries.');\n} else {\n  console.log('Error creating timeseries :(');\n  process.exit(1);\n}\n```\n\n### Adding new value to a Time Series data structure in Redis\n\nWith RedisTimeSeries, we can add a single value to time series data structure using the [`TS.ADD`](https://redis.io/commands/ts.add/) command and if we would like to add multiple values we can use the [`TS.MADD`](https://redis.io/commands/ts.madd/) command.\n\n```javascript\n\nlet value = Math.floor(Math.random() * 1000) + 1; // Random data point value\nlet currentTimestamp = 1640995200000; // Jan 1 2022 00:00:00\nlet num = 0;\n\nwhile (num < 10000) {\n  // Add a new value to the timeseries, providing our own timestamp:\n  // https://redis.io/commands/ts.add/\n  await client.ts.add('temperature', currentTimestamp, value);\n  console.log(`Added timestamp ${currentTimestamp}, value ${value}.`);\n\n  num += 1;\n  value = Math.floor(Math.random() * 1000) + 1; // Get another random value\n  currentTimestamp += 1000; // Move on one second.\n}\n\n// Add multiple values to the timeseries in round trip to the server:\n// https://redis.io/commands/ts.madd/\nconst response = await client.ts.mAdd([{\n  key: 'temperature',\n  timestamp: currentTimestamp + 60000,\n  value: Math.floor(Math.random() * 1000) + 1\n}, {\n  key: 'temperature',\n  timestamp: currentTimestamp + 120000,\n  value: Math.floor(Math.random() * 1000) + 1\n}]);\n```\n\n### Retrieving Time Series data from Redis\n\nWith RedisTimeSeries, we can retrieve the time series data using the [`TS.RANGE`](https://redis.io/commands/ts.range/) command by passing the criteria as follows:\n\n```javascript\n// Query the timeseries with TS.RANGE:\n// https://redis.io/commands/ts.range/\nconst fromTimestamp = 1640995200000; // Jan 1 2022 00:00:00\nconst toTimestamp = 1640995260000; // Jan 1 2022 00:01:00\nconst rangeResponse = await client.ts.range('temperature', fromTimestamp, toTimestamp, {\n  // Group into 10 second averages.\n  AGGREGATION: {\n    type: TIME_SERIES_AGGREGATION_TYPE.AVG,\n    timeBucket: 10000\n  }\n});\n\nconsole.log('RANGE RESPONSE:');\n// rangeResponse looks like:\n// [\n//   { timestamp: 1640995200000, value: 356.8 },\n//   { timestamp: 1640995210000, value: 534.8 },\n//   { timestamp: 1640995220000, value: 481.3 },\n//   { timestamp: 1640995230000, value: 437 },\n//   { timestamp: 1640995240000, value: 507.3 },\n//   { timestamp: 1640995250000, value: 581.2 },\n//   { timestamp: 1640995260000, value: 600 }\n// ]\n```\n\nFor multiple aggregations in one command, use the dedicated `*MultiAggr` methods:\n\n```javascript\nconst multiRangeResponse = await client.ts.rangeMultiAggr('temperature', fromTimestamp, toTimestamp, {\n  AGGREGATION: {\n    types: [\n      TIME_SERIES_AGGREGATION_TYPE.MIN,\n      TIME_SERIES_AGGREGATION_TYPE.MAX,\n      TIME_SERIES_AGGREGATION_TYPE.AVG\n    ],\n    timeBucket: 10000\n  }\n});\n\n// multiRangeResponse looks like:\n// [\n//   { timestamp: 1640995200000, values: [120, 580, 356.8] },\n//   ...\n// ]\n```\n\nEquivalent multi-aggregation helpers are also available for reverse and multi-key variants:\n`revRangeMultiAggr`, `mRangeMultiAggr`, `mRevRangeMultiAggr`, `mRangeWithLabelsMultiAggr`,\n`mRevRangeWithLabelsMultiAggr`, `mRangeSelectedLabelsMultiAggr`, and `mRevRangeSelectedLabelsMultiAggr`.\n\n### Altering Time Series data Stored in Redis\n\nRedisTimeSeries includes commands that can update values in a time series data structure.\n\nUsing the [`TS.ALTER`](https://redis.io/commands/ts.alter/) command, we can update time series retention like this:\n\n```javascript\n// https://redis.io/commands/ts.alter/\nconst alterResponse = await client.ts.alter('temperature', {\n  RETENTION: 0 // Keep the entries forever\n});\n```\n\n### Retrieving Information about the timeseries Stored in Redis\n\nRedisTimeSeries also includes commands that can help to view the information on the state of a time series.\n\nUsing the [`TS.INFO`](https://redis.io/commands/ts.info/) command, we can view timeseries information like this:\n\n```javascript\n// Get some information about the state of the timeseries.\n// https://redis.io/commands/ts.info/\nconst tsInfo = await client.ts.info('temperature');\n\n// tsInfo looks like this:\n// {\n//   totalSamples: 1440,\n//   memoryUsage: 28904,\n//   firstTimestamp: 1641508920000,\n//   lastTimestamp: 1641595320000,\n//   retentionTime: 86400000,\n//   chunkCount: 7,\n//   chunkSize: 4096,\n//   chunkType: 'uncompressed',\n//   duplicatePolicy: 'block',\n//   labels: [],\n//   sourceKey: null,\n//   rules: []\n// }\n```\n</yakaai_skills>"}