From redis-companion
Generate a least-privilege Redis ACL rule for the codebase at the given path. Use when invoked via `/redis-companion:rule <path>` or when the user explicitly asks to scope or generate a Redis ACL rule for a service with a path argument. Orchestrates the `redis-companion:acl-generator` agent across two phases (discovery, synthesis) and gathers user input between them via `AskUserQuestion`.
How this skill is triggered — by the user, by Claude, or both
Slash command
/redis-companion:ruleThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
The user requested a rule for the path: `$ARGUMENTS`
The user requested a rule for the path: $ARGUMENTS
$ARGUMENTS is empty or missingRespond exactly with:
The
/redis-companion:rulecommand needs a path argument.Usage:
/redis-companion:rule <path>Example:
/redis-companion:rule ./my-serviceIf you want a more conversational entry point, just say something like "scope a Redis ACL for ./my-service" and Claude will route you to the
redis-companion:acl-generatoragent.
Then stop. Do not proceed without a path.
$ARGUMENTS contains a path — the three-phase orchestrationFirst, emit a brief greeting to the user (exactly one short line — no headers, no bullets):
👋 Hi! I'm redis-companion. Scanning
$ARGUMENTSfor Redis usage — I'll ask you a few questions, then emit a least-privilege ACL rule with per-term annotations.
Then proceed to the three phases below — discovery (sub-agent), batched ask (AskUserQuestion), and synthesis (sub-agent). Do not deviate from the order. Do not skip phases. Do not attempt the analysis yourself outside these phase boundaries.
Why this structure exists: Claude Code sub-agents run single-shot — they can't pause mid-response to ask the user a question. So the interactive step lives in the skill (here, in the main conversation), via AskUserQuestion, between two stateless sub-agent dispatches. Read docs/EXPECTED_BEHAVIOR.md if it's available to you for the full test oracle (the behavioral spec the plugin is scored against).
Spawn the redis-companion:acl-generator sub-agent via the Task tool — pass redis-companion:acl-generator as the subagent_type (plugin agents require the fully-qualified namespaced name; the unqualified acl-generator will not resolve). Use this prompt:
Mode: DISCOVERY ONLY.
Analyze the codebase at
$ARGUMENTSfor Redis client usage. Run steps D1–D9 from your DISCOVERY mode. Then return a structured discovery summary and stop. Do not ask any questions. Do not synthesize a rule.Read only source files and package manifests. Do NOT read service-internal docs (
README.md,CHANGELOG.md,LICENSE,CONTRIBUTING.md,docs/*, etc.) — they describe what the service does for end users, not how it talks to Redis. They don't inform the ACL rule.Lazy-load the
acl-referenceskill — only invoke it if you encounter a non-standard client library or an ambiguous method call (scripting helpers, locks, transactional pipelines, subcommand-named methods, Sentinel/Cluster client mode, sharded pub/sub). For the well-known clients (redis-py,ioredis,go-redis) with standard methods, your training data is sufficient and the skill invocation is wasted tool calls.Your structured summary must include:
- Client library (name + how detected, e.g.,
redis-pyfromfrom redis import Redisin service.py:10)- Commands used (table: call site → method → Redis command)
- Key patterns (table: pattern → constant or literal source → call sites using it)
- Channel patterns (same shape as keys, but
&prefix)- Stream keys (same shape as keys, but note these are stream-typed)
- Speculation candidates (TODO/FIXME comments near Redis calls that imply future commands — e.g., a
# TODO: add subscribenearr.publish(...)). For each: file path, line number, comment text, and what command(s) it implies.- Server version from
INFO SERVERif MCP is connected (else: note "MCP not connected")- Mapping notes (any ambiguous client-library method→command mappings you flagged via step 2g)
Permitted tools:
Read,Grep,Glob,Skill(for loadingacl-referenceonce),mcp__plugin_redis-companion_redis__info(INFO SERVER, once),WebFetch(one-shot, for ambiguous mappings). Forbidden:Bash, any data-reading MCP tool (scan_keys,get,hgetall, etc.).
Wait for the agent's return. Read the discovery summary carefully — you will use it to format Phase 2 questions and pass it back to the agent in Phase 3.
Use the AskUserQuestion tool to ask the user both baseline questions at once, plus Q3 if Phase 1 surfaced any speculation candidates. This is the only Claude Code primitive that actually pauses the conversation for structured user input — natural-language "wait for the user" instructions don't enforce a pause.
Construct the question set as follows.
Q1 — Target Redis edition:
ACL SETUSER command runnable via redis-cli."Rule for option ordering: the recommended / safest / most-common option ALWAYS goes first. The Claude Code UI cursor defaults to the first option, so the default choice should be the one most users want.
Q2 — Target Redis version:
If Phase 1's discovery summary included a version from INFO SERVER (e.g., 8.6.3), present this as a CONFIRM-OR-OVERRIDE:
INFO SERVER. Use this version, or generate the rule for a different version?"Since: <= <X.Y.Z>."If the user picks "Override", treat this as if MCP did not provide a version: fire the major-version question, then the minor-version follow-up. Continue with Q3 (speculation) if applicable.
If MCP did NOT provide a version, present as an open question. Order: newest version FIRST (Redis 8 = most-likely target for greenfield, most-features):
%R~/%W~ available; pub/sub default-deny."If MCP didn't provide a version AND the user picked a major, fire a SECOND AskUserQuestion for the minor version. Order: latest minor of that major FIRST (recommended; matches what the upstream-derived map covers most precisely):
If user picked Redis 8:
HSETEX was added in 8.0, MSETEX in 8.4.)"If user picked Redis 7:
If user picked Redis 6:
Two design choices that simplify the v1 question set:
+@category regardless of usage ratios. Strict is the safest default; removing the choice removes a confusing decision point. The agent's S2 step still has the balanced/brevity logic in its prompt for future re-enablement, but the skill never passes anything other than "strict" today.-@admin -@dangerous) are omitted from the rule body. They only matter functionally when the rule uses +@category grants — +@write would pull in FLUSHDB, etc., and the deny clauses prevent that. With strict individual grants and the nocommands baseline (which is -@all, denying everything by default), -@admin -@dangerous are no-ops — they remove categories that were never granted. Including them would just be visual noise. The user's command grants are already limited to what the service uses.Q3 (conditional — only if Phase 1 surfaced speculation candidates):
For each speculation candidate, add a question. Leave out first — it's the safer default. For example, a TODO at service.py:40 suggesting SUBSCRIBE is planned:
# TODO: add a subscribe handler at service.py:40, suggesting SUBSCRIBE may be added later. Include the planned grants now or leave them out?"+SUBSCRIBE. Rule covers the planned addition without re-running."Calling AskUserQuestion: the platform limit is 4 questions per call. With only two baseline questions, this fits comfortably. Plan your calls:
Do not narrate before/after the calls. AskUserQuestion renders its own UI in Claude Code (the question text + option chips appear as a structured prompt, separate from your regular text output), so wrapping it with "I'll now ask you a few questions" or "Thanks for those answers" just adds visual noise — the structured prompt is self-contained.
Compute the effective target version before dispatching. This is how minor-version filtering works:
redis_version from INFO SERVER (e.g., 8.6.3): use that exact version. The agent will filter the category map by Since: <= 8.6.3, so commands like HEXPIRE (Since 7.4.0) are included only if 7.4.0 ≤ 8.6.3 — yes, included.7.4. Filter by Since: <= 7.4. No assumption needed — the user told us.Spawn the redis-companion:acl-generator agent (same dispatch rule as Phase 1 — fully-qualified subagent_type) with this prompt:
Mode: SYNTHESIS.
Here is the discovery summary from Phase 1:
<paste the full Phase 1 return verbatim>The user answered:
- Edition:
- Version (major):
- Speculation candidates: <answer(s) to Q3, one per candidate>
Hardcoded for v1: granularity is "strict least-privilege" (never collapse to
+@category), and the rule body does NOT include-@admin -@dangerousdefense-in-depth denies (they're functional no-ops when the rule usesnocommandsbaseline + individual command grants — the user can't run @admin/@dangerous commands they weren't explicitly granted).Effective target version for filtering:
<exact_version>(either fromINFO SERVERdirectly, or the assumed latest minor of the user's major-version pick — state which one explicitly here). Filter thecommand-category-map.mdsuch that only commands withSince: <= <exact_version>are eligible. Surface this version assumption in the Detected Context output.Run S1 (map commands → categories with version filtering), S3 (compose rule body — skip S2's collapse decision since granularity is hardcoded to strict; skip emitting
-@admin -@dangerousfrom S3 step 9 since denies are hardcoded off), and emit the OSS or Enterprise output per the user's edition answer. Use the username derived from the analyzed directory's basename (<basename of $ARGUMENTS>) — if it has invalid Redis username chars, fall back tomy-service-user.Required output sections, in order:
- The
ACL SETUSER <user> on ><changeme> ...command (OSS) or rule body only (Enterprise) — no-@admin -@dangeroustrailing denies- A clearly-flagged callout block before apply instructions explaining how to substitute the password:
><strong_password>for non-local deployments,nopass(replacing the whole><changeme>token) for local-dev Redis without auth- Per-term annotation table with source line citations from the discovery summary
- "Detected context" block (client library, edition, version + how known, MCP status, mapping notes)
- How to apply —
redis-cliexamples for both password andnopasscases- Do NOT offer "type apply" — MCP can't run
ACL SETUSERForbidden tools: any tool that reads or writes data; this phase is text-only synthesis from the inputs above.
Long ACL rule lines get mangled by terminal copy-paste — word-wrap inserts hard line breaks, heredoc indentation breaks the EOF terminator, and shell-special characters (~, *, &, >) require careful quoting. The reliable solution is to write the full output to a markdown file and have the user apply it with a short extraction command. The user copies short commands from the prompt; the rule itself stays in the .md.
Step 1 — Write ./acl-rule-<username>.md to the current working directory.
Save the agent's full synthesis output (verbatim) to ./acl-rule-<username>.md in cwd. This file contains everything: rule, per-term annotations, detected context, apply instructions, verify steps. It's the comprehensive deliverable — designed for review, audit trail, version control, future reference.
Overwrite-safe procedure (mandatory order):
Glob (pattern: acl-rule-<username>.md) or Read directly on the path to check whether the file already exists from a prior run.Read on the file first (this satisfies the Write tool's same-session read-before-overwrite guard — without it, Write errors out with "Error writing file" and the user sees a noisy retry on screen).Write with the agent's verbatim synthesis output.Do not skip the existence check, even on a "first run" — the user may have prior rule files in cwd from earlier /redis-companion:rule invocations. The check is cheap and removes the only on-camera error visible during repeated demos.
Critical formatting requirement for grep-extraction to work later: the rule must appear on its own line starting with ACL SETUSER (or the rule body for Enterprise). The agent's standard output template already puts the rule inside a fenced code block on its own line — preserve that exactly when writing the file.
If Write still fails or the user denies the permission prompt, fall back: tell the user the file write didn't happen and surface the agent's full output inline as a fallback.
Step 2 — Emit a CONDENSED user-facing message.
Do NOT re-emit the agent's full output to the user. The detailed view lives in the .md file. Your Claude Code message should be tight:
✅ Rule generated for `<username>` (Redis <edition> <version>, <N> commands, strict least-privilege)
**`ACL SETUSER` command** (creates the user and grants the rule body in one shot):
\`\`\`
ACL SETUSER <username> on ><changeme> <rest of rule>
\`\`\`
**Full details:** `./acl-rule-<username>.md` — open this file for per-term annotations, detected context, and apply patterns.
**Apply** (substitute the password inline and pipe straight to `redis-cli` — for local nopass Redis use `nopass`, otherwise replace with `>YOUR_STRONG_PASSWORD`; for a remote target, append `-h <host> -p <port> --user <admin> --askpass`):
\`\`\`
! sed 's/><changeme>/nopass/' ./acl-rule-<username>.md | grep -m1 '^ACL SETUSER' | redis-cli
\`\`\`
**Verify the rule landed:**
\`\`\`
! redis-cli ACL GETUSER <username>
\`\`\`
**Smoke-test the service end-to-end** (this is the demo's strongest beat — apply the rule and prove the service still works under it):
\`\`\`
! REDIS_URL='redis://<username>:@127.0.0.1:6379' python3 $ARGUMENTS/service.py
\`\`\`
(Note the trailing colon — `redis://user:@host` is the unambiguous nopass URL form. Plain `redis://user@host` confuses `redis-cli`/clients into using `user` as a password for `default`.) Expect `6/6 operations succeeded` if applicable. For a passworded apply, use `redis://<username>:${REDIS_PASS}@127.0.0.1:6379`.
**Negative test** (confirm denies actually deny):
\`\`\`
! redis-cli -u 'redis://<username>:@127.0.0.1:6379' FLUSHDB
\`\`\`
Expected: `(error) NOPERM User <username> has no permissions to run the 'flushdb' command`.
The user copies the short paste-safe one-liners from the prompt — each starts with ! so Claude Code runs it directly as a shell command on paste, no manual prefix needed. The rule itself is extracted by grep from the .md file, never copy-pasted from the prompt.
Important: the smoke-test path assumes the analyzed directory contains a runnable service.py (true for the bundled examples/sample-service/). If the path doesn't have a service.py at its root (e.g., a multi-module service), substitute the actual entrypoint or omit the smoke-test block. The agent's discovery output tells you the actual entry-point file path — use that.
If the user wants to see the per-term annotations or the full apply pattern alternatives, they cat the file or open it in an editor. The condensed prompt message tells them where to look.
AskUserQuestion. The user's answer is authoritative; your inference is not.redis-cli.npx claudepluginhub mjtrapani/redis-companion --plugin redis-companionCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.