This skill reviews only the changes on the current branch (compared to main) against the conventions defined in the ably-new-command skill. It uses a combination of LSP for structural analysis (type hierarchies, call chains) and grep/read for text-pattern checks, choosing the right tool for each check.
Unlike ably-codebase-review which sweeps the entire codebase, this skill is scoped to what has changed on the current branch, making it fast and focused for pre-PR review.
Tool selection guide
Use LSP when you need type-system awareness:
goToDefinition— resolve what base class a command actually extendsfindReferences— trace all callers of a method through 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: Identify changed files
Run git diff main...HEAD --name-only to get the list of files changed on this branch. Filter to only files relevant for review:
src/commands/**/*.ts— command filessrc/**/*-base-command.ts— base classessrc/flags.ts— flag definitionssrc/utils/output.ts— output helperstest/unit/commands/**/*.ts— test filestest/helpers/**/*.ts— test helpers
Also run git log main..HEAD --oneline to understand the scope of changes.
If there are no changes compared to main (i.e., the branch IS main), tell the user and stop.
Step 2: 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 3: Review changed files
For each changed command file, run the relevant checks. Spawn agents for parallel review when multiple command files are changed. For 1–2 files, review sequentially.
For changed command files (src/commands/**/*.ts)
Base class check (LSP — needs type resolution):
- Use
LSP goToDefinitionon the base class to confirm it resolves correctly - Use
LSP hoverto verify the full type hierarchy - Verify the command extends the right base class per the skill rules (rooms/* →
ChatBaseCommand, spaces/* →SpacesBaseCommand, etc.) - For index files (
index.ts), verify they extendBaseTopicCommandwith correcttopicNameandcommandGroupproperties
Error handling check (grep, with LSP for ambiguous cases):
- Grep for
this\.error\(in the file — should usethis.fail()instead - If found, use LSP
hoveron the call to confirm it's 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. - Check any new or changed 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 the 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).
Output formatting check (grep/read — text patterns):
- Grep for
chalk\.cyan\(— should useformatResource()instead - Grep for
chalk\.yellow\(— should useformatWarning()instead - Grep for
formatProgress(and check for manual...appended - Grep for
formatSuccess(and check lines 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. - Look for quoted resource names instead of
formatResource(name) - Grep for box-drawing characters (
┌,┬,├,└,─.*─,│) — non-JSON output must use multi-line labeled blocks, not ASCII tables or grids - Read the file and check that non-JSON data output uses
formatLabel()for field labels in multi-line blocks, not inline or single-line formatting - Check subscribe commands do NOT fetch initial state (no
getAll()or equivalent before subscribing) — subscribe should only listen for new events
Field completeness check (read — for data-outputting commands):
- Read the JSON output path and compare fields emitted vs the non-JSON output path — non-JSON should expose the same fields as JSON mode (omitting only null/empty values)
- Check that available SDK fields (e.g.,
connectionId,clientId,isConnected,profileData,lastEvent) are shown in non-JSON output, not just in JSON mode - Grep for local
interfacedefinitions in command files that duplicate SDK types (e.g.,interface CursorPosition,interface CursorData) — these should import fromably,@ably/spaces, or@ably/chatinstead. Display/output interfaces insrc/utils/are fine.
Flag architecture check (grep, with LSP for ambiguous cases):
- Grep for flag spreads (
productApiFlags,clientIdFlag,durationFlag,rewindFlag,timeRangeFlags,ControlBaseCommand.globalFlags) - Verify correct flag sets per the skill rules
- Check subscribe commands have
durationFlag,rewindFlag,clientIdFlagas appropriate; write commands (publish, enter, set, acquire, update, delete, append) should also haveclientIdFlag; read-only queries (get, get-all, history, occupancy get) must NOT haveclientIdFlag - For ambiguous cases, use LSP
goToDefinitionto confirm flag imports resolve tosrc/flags.ts
JSON output check (grep/read):
- Grep for
logJsonResultorlogJsonEvent— every leaf command should use one - Grep for
formatJsonRecord— direct usage should be flagged as needing migration - Grep for
shouldOutputJson— verify human output is guarded - Read the file to verify streaming commands use
logJsonEventand one-shot commands uselogJsonResult - Read
logJsonResult/logJsonEventcall sites and check data is nested under a domain key (singular for events/single items, plural for collections) — not spread at top level. 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")
Control API helper check (grep — for Control API commands only):
- Grep for
resolveAppId— should userequireAppIdinstead (encapsulates null check andfail()) - Grep for
createControlApiwith manual try-catch — considerrunControlCommandfor single API calls
Lifecycle check (grep/read):
- Grep for
waitUntilInterruptedOrTimeout— should usethis.waitAndTrackCleanup()instead - Grep for
room.attach()orspace.enter()— 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
static examplesand check for--jsonor--pretty-jsonvariant - Read the command description — verify imperative mood, sentence case, no trailing period
For changed test files (test/unit/commands/**/*.ts)
- Grep for
describe(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 any non-standard variants:"command arguments and flags","command flags","flag options","parameter validation". These must use the exact standard names above. Exception:interactive.test.ts,interactive-sigint.test.ts, andbench/*.test.tsare exempt (REPL/benchmark tests, not command tests).
- Grep for
getMockAblyRealtime,getMockAblyRest,getMockConfigManagerto verify correct mock usage - Grep for
--durationin unit testrunCommand()args — should NOT be present (env var handles it). Exceptions:test:waittests,interactive-siginttests, help output checks. - Grep for
--api-key,--token,--access-token— unit tests should not use CLI auth flags - Check for use of shared test helpers where applicable:
- Control API tests should consider using
nockControl(),getControlApiContext(),controlApiCleanup()fromtest/helpers/control-api-test-helpers.tsinstead of manual nock setup - Control API tests should consider using mock factories (
mockApp(),mockKey(),mockRule(),mockQueue(),mockNamespace(),mockStats()) fromtest/fixtures/control-api.tsinstead of inline response objects - Tests with boilerplate help/arg-validation/flags blocks should consider using
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 - JSON envelope tests should use
captureJsonLogs()fromtest/helpers/ndjson.tsinstead of manual console.log spying
- Control API tests should consider using
For new command files (added, not modified)
Apply the full checklist from the ably-new-command skill. These deserve the most scrutiny since they're entirely new code. Use both LSP (base class, type hierarchy) and grep/read (all text-pattern checks).
For changed base classes or utilities
- Read the changes and verify they don't break patterns relied on by commands
- Grep for new helper functions and check naming conventions (
format*prefix for output helpers) - Grep for
this\.error\(— should only be used insidefail(), not directly
Step 4: Check for missing test coverage
Glob for each new or modified command file and check if a corresponding test file exists at test/unit/commands/. If a command was added but no test was added, flag it.
Step 5: Output findings
Present findings directly to the user, organized by file. Lead with a summary.
Summary
| File | Status | Issues |
|---|---|---|
src/commands/foo/bar.ts |
N issues | Brief list |
test/unit/commands/foo/bar.test.ts |
OK | — |
Findings by file
For each file with issues:
src/commands/foo/bar.ts
- [Category] — description of the issue
- Rule: [The skill rule being checked]
- Evidence: [Line number, tool used (LSP/grep/read), what was found]
- Suggested Fix: [What to change]
- Verdict: TRUE DEVIATION | ACCEPTABLE EXCEPTION
Overall assessment
A brief summary: does this branch follow the conventions well? Are the issues minor or major? Is it ready for PR?