This skill reviews the entire Ably CLI codebase against the conventions defined in the ably-new-command skill. It uses a combination of LSP for structural analysis (type hierarchies, call chains, symbol definitions) and grep/read for text-pattern checks, choosing the right tool for each check.
Tool selection guide
Use LSP when you need type-system awareness:
goToDefinition— resolve what base class a command actually extends (not just the import text)findReferences— trace all callers offail(),logJsonResult,logJsonEventthrough inheritancehover— reveal resolved types, inherited members, full type hierarchydocumentSymbol— list all methods/properties in a class structurally
Use grep/read when a text match is sufficient:
this.error()calls in command files- Component string casing in
this.fail()andlogCliEvent()calls new Error(...)wrapping inthis.fail()calls (unnecessary — accepts strings)shouldOutputJsonguards aroundthis.log()formatSuccess()calls ending with.formatProgress()calls with manual...chalk.cyan()whereformatResource()should be used- Quoted resource names instead of
formatResource(name) --json/--pretty-jsoninstatic examplesarrays- Flag spreads like
...productApiFlags,...durationFlag,...clientIdFlag waitUntilInterruptedOrTimeoutusage- Missing test files (glob for them)
--durationflag in subscribe test files- Mock helper imports (
getMockAblyRealtime,getMockAblyRest,getMockConfigManager)
Step 0: Verify LSP prerequisites
Before doing any analysis, check that the LSP tooling is available:
-
Check for the LSP tool — verify the
LSPtool is available in your tool list. If it is not, tell the user:The LSP plugin is not installed. Install it by running:
claude plugin add anthropic/claude-code-lspThen restart Claude Code and re-run this skill. Stop here — do not proceed without the LSP tool.
-
Check for
typescript-language-server— runwhich typescript-language-serverornpx --yes typescript-language-server --version. If the binary is not found, install it globally for the user:npm install -g typescript-language-server typescriptConfirm installation succeeded before proceeding.
Step 1: Read the reference skill
Read these files to understand the canonical patterns:
.claude/skills/ably-new-command/SKILL.md— the checklist and conventions.claude/skills/ably-new-command/references/patterns.md— implementation templates.claude/skills/ably-new-command/references/testing.md— test scaffolds
Step 2: Spawn sweep agents
Launch these agents in parallel. Each agent gets a focused mandate and uses the appropriate tool (LSP or grep/read) per check.
Agent 1: Base Class & Inheritance Sweep
Goal: Verify every command extends the correct base class per the skill's rules.
Method (LSP — needs type resolution):
- Use
LSP documentSymbolon each command file insrc/commands/to find the class export - Use
LSP goToDefinitionon the base class reference (e.g.,AblyBaseCommand,ControlBaseCommand,ChatBaseCommand,SpacesBaseCommand,StatsBaseCommand) to confirm it resolves to the correct file - Cross-reference: rooms/* commands should extend
ChatBaseCommand, spaces/* should extendSpacesBaseCommand, stats/* should extendStatsBaseCommand, control API commands should extendControlBaseCommand - Use
LSP hoveron the class declaration to see the full type hierarchy
What to flag: Commands extending the wrong base class, or commands that should use a specialized base class but use AblyBaseCommand directly (exception: REST-only commands in rooms/spaces that don't need realtime lifecycle). Also verify that topic index files (src/commands/*/index.ts, src/commands/*/*/index.ts) extend BaseTopicCommand with correct topicName and commandGroup properties.
Agent 2: Error Handling Sweep
Goal: Verify all commands use fail() exclusively for errors, not this.error() or chalk.red().
Method (mixed):
- LSP
findReferencesonfail(from base-command.ts) to get every call site — confirms all commands funnel throughfail() - Grep for
this\.error\(insrc/commands/to find direct error calls — simple text match is sufficient here - For any ambiguous matches, use LSP
hoverto confirm they're the oclifthis.error()and not something else - Grep for
this\.fail\(and check component strings are camelCase — single-word lowercase ("room","auth"), multi-word camelCase ("channelPublish","roomPresenceSubscribe"). Flag PascalCase like"ChannelPublish"or kebab-case like"web-cli". - Grep for
this\.fail\(\s*new Error\(—this.fail()accepts plain strings, sonew Error(...)wrapping is unnecessary. Flag as a simplification opportunity. - Check that catch blocks calling
this.fail()do NOT include manual oclif error re-throw guards (if (error instanceof Error && "oclif" in error) throw error). The base classfail()method handles this automatically — manual guards are unnecessary boilerplate. - Verify all error codes in
src/utils/errors.ts: fetch https://ably.com/docs/platform/errors/codes using WebFetch to get the official description for each code, then verify every hint matches. Do NOT rely on memory or assumptions about what an error code means — always fetch the doc. Hints must only contain actionable CLI advice (not restated upstream messages).
Reasoning guidance:
- Base class files (
*-base-command.ts) usingthis.error()are deviations — they should usethis.fail()instead src/commands/interactive.tsis exempt (REPL mode)src/commands/help.tsextendingCommanddirectly is exempt (nofailavailable)chalk.red("✗")used as visual indicators (not error handling) is exempt- Component strings must be camelCase for consistency in verbose logs and JSON envelopes
Agent 3: Output Formatting Sweep
Goal: Verify all human output uses the correct format helpers and correct output method (stderr for status, stdout for data).
Method (grep/read — text patterns):
- Grep for
chalk\.cyan\(in command files — should useformatResource()instead - Grep for
formatProgress(and check matches for manual...appended - Grep for
formatSuccess(and read the lines to check they end with. - Grep for
this\.log(formatProgress\|this\.log(formatSuccess\|this\.log(formatListening\|this\.log(formatWarningandthis\.logToStderr(formatProgress\|this\.logToStderr(formatSuccess\|this\.logToStderr(formatListening\|this\.logToStderr(formatWarning— these must use the base command helpers instead:this.logProgress(msg, flags),this.logSuccessMessage(msg, flags),this.logListening(msg, flags),this.logHolding(msg, flags),this.logWarning(msg, flags). In non-JSON mode all helpers emit to stderr. In JSON mode:logProgressandlogSuccessMessageare silent (no-ops), whilelogListening(status: "listening"),logHolding(status: "holding"), andlogWarning(status: "warning") emit structured JSON on stdout.logSuccessMessageshould be inside theelseblock afterlogJsonResult. Also check these helpers are NOT insideshouldOutputJsonguards — they don't need them. - Grep for
shouldOutputJsonto find all JSON-aware commands and verify data output is properly branched - Grep for quoted resource names patterns like
"${or'${nearchannel,name,appvariables — should useformatResource()
Method (grep — structured output format):
7. Grep for box-drawing characters (┌, ┬, ├, └, │) in command files — non-JSON output must use multi-line labeled blocks, not ASCII tables or grids
8. Grep for subscribe commands that call getAll() or equivalent before subscribing — subscribe commands must NOT fetch initial state (they should only listen for new events)
9. For data-outputting commands, read both the JSON and non-JSON output paths and compare fields — non-JSON should expose the same fields as JSON mode (omitting only null/empty values)
10. Grep for local interface definitions in src/commands/ that duplicate SDK types (e.g., interface CursorPosition, interface CursorData, interface PresenceMessage) — these should import from ably, @ably/spaces, or @ably/chat instead. Display/output interfaces in src/utils/ are intentional and fine.
Method (LSP — for completeness mapping):
11. Use LSP findReferences on shouldOutputJson to get the definitive list of all commands that check for JSON mode — cross-reference against the full command list to find commands missing JSON guards
Reasoning guidance:
- List commands don't use
formatSuccess()(no action to confirm) — this is correct, not a deviation chalk.red("✗")/chalk.green("✓")as visual indicators in test/bench output is acceptablechalk.yellow("Warning: ...")should useformatWarning()instead —formatWarningadds the⚠symbol automatically and "Warning:" prefix is unnecessary- ASCII tables/grids in non-JSON output are deviations — use multi-line labeled blocks with
formatLabel()instead - Subscribe commands fetching initial state (via
getAll(),getSelf(), etc.) before subscribing are deviations — subscribe should only listen for new events - Non-JSON output that hides fields available in JSON mode is a deviation — both modes should expose the same data
- Local
interfacedefinitions in command files that duplicate SDK types are deviations — import from the SDK package instead. Display/output interfaces insrc/utils/(e.g.,MemberOutput,MessageDisplayFields) are intentional transformations, not duplicates.
Agent 4: Flag Architecture Sweep
Goal: Verify every command uses the correct flag sets per the skill rules.
Method (grep — text patterns):
- Grep for
productApiFlags,clientIdFlag,durationFlag,rewindFlag,timeRangeFlags,ControlBaseCommand.globalFlagsin command files to map which commands use which flag sets - Cross-reference against the rules:
- Product API commands must use
productApiFlags - Subscribe/stream commands must have
durationFlag - Subscribe commands with replay must have
rewindFlag - History/stats commands must have
timeRangeFlags - Commands that perform writes (subscribe, publish, enter, set, acquire, update, delete, append) must have
clientIdFlag; read-only queries (get, get-all, history, occupancy get) must NOT haveclientIdFlag - Control API commands must use
ControlBaseCommand.globalFlags
- Product API commands must use
Method (LSP — for ambiguous cases):
3. Use LSP goToDefinition on flag spread references to confirm they resolve to src/flags.ts (not a local redefinition)
Reasoning guidance:
- A write command (subscribe, publish, enter, set, acquire, update, delete, append) without
clientIdFlagis a deviation; a read-only query (get, get-all, history, occupancy get) WITHclientIdFlagis also a deviation - A non-subscribe command having
durationFlagis suspicious but might be valid (e.g., presence enter) - Control API commands should NOT have
productApiFlags
Agent 5: JSON Output & Envelope Sweep
Goal: Verify all commands produce correct JSON output when --json is used.
Method (mixed):
- LSP
findReferencesonlogJsonResultandlogJsonEventto get the definitive list of all call sites — every leaf command should use one of these - Grep for
formatJsonRecordin command files (not base-command.ts) — direct usage should be flagged as needing migration tologJsonResult/logJsonEvent - Grep for
shouldOutputJsonin command files to find all JSON-aware commands - Cross-reference: every leaf command should appear in both the
logJsonResult/logJsonEventlist and theshouldOutputJsonlist - Read streaming commands to verify they use
logJsonEvent, one-shot commands uselogJsonResult - Read each
logJsonResult/logJsonEventcall and verify data is nested under a domain key — singular for events/single items (e.g.,{message: ...},{cursor: ...}), plural for collections (e.g.,{cursors: [...]},{rules: [...]}). Top-level envelope fields aretype,command,successonly. Metadata liketotal,timestamp,appIdmay sit alongside the domain key. - Check hold commands (set, enter, acquire) emit
this.logHolding(...)afterlogJsonResult— this emitsstatus: "holding"in JSON mode, signaling to consumers that the command is alive and waiting for Ctrl+C /--duration. For passive subscribe commands, check forthis.logListening(...)instead (emitsstatus: "listening")
Reasoning guidance:
- Commands that ONLY have human output (no JSON path) are deviations
- Direct
formatJsonRecordusage in command files should uselogJsonResult/logJsonEventinstead - Topic index commands (showing help) don't need JSON output
- Data spread at the top level without a domain key is a deviation — nest under a singular or plural domain noun
- Metadata fields (
total,timestamp,hasMore,appId) alongside the domain key are acceptable — they describe the result, not the domain objects - Hold commands missing
logHoldingafterlogJsonResultare deviations — JSON consumers need the hold signal
Agent 6: Test Pattern Sweep
Goal: Verify test files follow the skill's testing conventions.
Method (grep/glob — text patterns and file matching):
- Glob for each command in
src/commands/and check if a corresponding test file exists attest/unit/commands/ - Grep for
describe(in test files to check for the 5 required describe blocks with EXACT standard names:describe("help"— required in every test filedescribe("argument validation"— required (test required args OR unknown flag rejection)describe("functionality"— required (core happy-path tests)describe("flags"— required (verify flags exist and work)describe("error handling"— required (API errors, network failures) Flag non-standard variants:"command arguments and flags","command flags","flag options","parameter validation". Exception:interactive.test.ts,interactive-sigint.test.ts, andbench/*.test.tsare exempt.
- Grep for
--durationin unit testrunCommand()args — should NOT be present (env var handles it). Exceptions:test:waittests,interactive-siginttests, help output checks. - Grep for
getMockAblyRealtime,getMockAblyRest,getMockConfigManagerin test files to verify correct mock usage per command type - Grep for
--api-key,--token,--access-tokenin unit test files — these should not use CLI auth flags - Check for use of shared test helpers where applicable:
- Control API tests should use
nockControl(),getControlApiContext(),controlApiCleanup()fromtest/helpers/control-api-test-helpers.tsinstead of manual nock setup - Control API tests should use mock factories (
mockApp(),mockKey(),mockRule(),mockQueue(),mockNamespace(),mockStats()) fromtest/fixtures/control-api.tsinstead of duplicating inline response objects - Tests with boilerplate help/arg-validation/flags blocks should use
standardHelpTests(),standardArgValidationTests(),standardFlagTests()fromtest/helpers/standard-tests.ts - Control API error handling blocks should use
standardControlApiErrorTests()fromtest/helpers/standard-tests.tsfor 401/500/network error tests
- Control API tests should use
Reasoning guidance:
- Missing test files are deviations but may be documented as known gaps
- Missing describe blocks or non-standard block names are deviations that should be flagged
- Subscribe tests that auto-exit via mocked callbacks (without
--duration) may be acceptable
Agent 7: Lifecycle & Convention Sweep
Goal: Verify commands use the correct lifecycle helpers and include required metadata.
Method (grep/read — text patterns):
- Grep for
waitUntilInterruptedOrTimeoutin command files — should usethis.waitAndTrackCleanup()instead - Grep for
setupChannelStateLoggingin subscribe commands (rooms/, spaces/) — flag those that don't call it - Grep for
room.attach()orspace.enter()in all rooms/* and spaces/* commands — verify it's only called for commands that need a realtime connection. In the Chat SDK, methods usingthis._chatApi.*are REST (no attach needed), while methods usingthis._channel.publish()orthis._channel.presence.*need realtime attachment. REST-only: messages send/update/delete/history, occupancy get. Needs attach: presence enter/get/subscribe, typing keystroke/stop, reactions send/subscribe, occupancy subscribe, messages subscribe. Unnecessary attachment adds latency and creates an unneeded realtime connection. - Read command files and check
static examplesarrays for--jsonor--pretty-jsonexamples — flag leaf commands that have examples but no JSON variant - Compare skill templates (
patterns.md,SKILL.md,testing.md— already read in Step 1) against actual codebase method names/imports — flag any outdated patterns
Method (LSP — for base class verification):
5. If a subscribe command doesn't call setupChannelStateLogging directly, use LSP goToDefinition on the base class to check if it's handled there
Reasoning guidance:
waitUntilInterruptedOrTimeoutin bench commands may be acceptable (different lifecycle)- Missing
setupChannelStateLoggingin rooms/spaces may be handled byChatBaseCommand/SpacesBaseCommand— check the base class room.attach()in REST-based commands is a deviation. Chat SDK methods usingthis._chatApi.*(messages send/update/delete/history, occupancy get) are pure REST calls. Methods usingthis._channel.publish()orthis._channel.presence.*(reactions send, typing keystroke, presence enter/get/subscribe, occupancy subscribe, messages subscribe) DO need attachment.- Topic index commands and
help.tsdon't need--jsonexamples - Skill template accuracy issues are low-effort, high-value fixes
Step 3: Collect and reason about findings
After all agents complete, compile and output findings directly to the user. For each deviation:
- State the rule — which skill checklist item or pattern is violated
- State the evidence — which file, which line, what was found (and which tool was used)
- Reason about it — is this a true deviation, an acceptable exception, or a false positive?
- Suggest the fix — what would the ideal change be to align with the skill
True deviation criteria
A finding is a true deviation if:
- The skill explicitly states a rule and the code violates it
- There is no documented exception for this case
- The fix would not break functionality or require architectural changes beyond the scope
A finding is NOT a true deviation if:
- The code has a legitimate reason to differ (e.g., REPL mode, help command without base class access)
- The skill doesn't actually cover this case
- The "violation" is in a base class where the pattern appropriately differs from command-level code
Output format
Present findings directly to the user in this structure:
Summary table
| Category | Deviations Found | True Deviations | Exceptions |
|---|---|---|---|
| Base Class | N | N | N |
| Error Handling | N | N | N |
| Output Formatting | N | N | N |
| Flag Architecture | N | N | N |
| JSON Output | N | N | N |
| Lifecycle & Conventions | N | N | N |
| Test Patterns | N | N | N |
Findings by category
For each finding:
- Rule: [The skill rule being checked]
- Evidence: [File:line, tool used (LSP/grep/read), what was found]
- Reasoning: [Why this is/isn't a true deviation]
- Suggested Fix: [What to change]
- Verdict: TRUE DEVIATION | ACCEPTABLE EXCEPTION | FALSE POSITIVE