You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .claude/skills/ably-codebase-review/SKILL.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -94,14 +94,14 @@ Launch these agents **in parallel**. Each agent gets a focused mandate and uses
94
94
95
95
### Agent 3: Output Formatting Sweep
96
96
97
-
**Goal:** Verify all human output uses the correct format helpers and is JSON-guarded.
97
+
**Goal:** Verify all human output uses the correct format helpers and correct output method (stderr for status, stdout for data).
98
98
99
99
**Method (grep/read — text patterns):**
100
100
1. **Grep** for `chalk\.cyan\(` in command files — should use `formatResource()` instead
101
101
2. **Grep** for `formatProgress(` and check matches for manual `...` appended
102
102
3. **Grep** for `formatSuccess(` and read the lines to check they end with `.`
103
-
4. **Grep** for `shouldOutputJson` to find all JSON-aware commands
104
-
5. **Read** command files and look for unguarded `this.log()` calls (not inside `if (!this.shouldOutputJson(flags))`)
103
+
4. **Grep** for `this\.log(formatProgress\|this\.log(formatSuccess\|this\.log(formatListening\|this\.log(formatWarning` and `this\.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: `logProgress` and `logSuccessMessage` are **silent** (no-ops), while `logListening` (status: "listening"), `logHolding` (status: "holding"), and `logWarning` (status: "warning") emit structured JSON on stdout. `logSuccessMessage` should be inside the `else` block after `logJsonResult`. Also check these helpers are NOT inside `shouldOutputJson` guards — they don't need them.
104
+
5. **Grep** for `shouldOutputJson` to find all JSON-aware commands and verify data output is properly branched
105
105
6. **Grep** for quoted resource names patterns like `"${` or `'${` near `channel`, `name`, `app` variables — should use `formatResource()`
106
106
107
107
**Method (grep — structured output format):**
@@ -155,15 +155,15 @@ Launch these agents **in parallel**. Each agent gets a focused mandate and uses
155
155
4. Cross-reference: every leaf command should appear in both the `logJsonResult`/`logJsonEvent` list and the `shouldOutputJson` list
156
156
5. **Read** streaming commands to verify they use `logJsonEvent`, one-shot commands use `logJsonResult`
157
157
6. **Read** each `logJsonResult`/`logJsonEvent` call 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 are `type`, `command`, `success` only. Metadata like `total`, `timestamp`, `appId` may sit alongside the domain key.
158
-
7. **Check** hold commands (set, enter, acquire) emit `logJsonStatus("holding", ...)` after `logJsonResult` — this signals to JSON consumers that the command is alive and waiting for Ctrl+C / `--duration`
158
+
7. **Check** hold commands (set, enter, acquire) emit `this.logHolding(...)` after `logJsonResult` — this emits `status: "holding"` in JSON mode, signaling to consumers that the command is alive and waiting for Ctrl+C / `--duration`. For passive subscribe commands, check for `this.logListening(...)` instead (emits `status: "listening"`)
159
159
160
160
**Reasoning guidance:**
161
161
- Commands that ONLY have human output (no JSON path) are deviations
162
162
- Direct `formatJsonRecord` usage in command files should use `logJsonResult`/`logJsonEvent` instead
163
163
- Topic index commands (showing help) don't need JSON output
164
164
- Data spread at the top level without a domain key is a deviation — nest under a singular or plural domain noun
165
165
- Metadata fields (`total`, `timestamp`, `hasMore`, `appId`) alongside the domain key are acceptable — they describe the result, not the domain objects
166
-
- Hold commands missing `logJsonStatus` after `logJsonResult` are deviations — JSON consumers need the hold signal
166
+
- Hold commands missing `logHolding` after `logJsonResult` are deviations — JSON consumers need the hold signal
The CLI has specific output helpers in `src/utils/output.ts`. All human-readable output must be wrapped in a JSON guard:
213
+
The base command provides five logging helpers: `this.logProgress(msg, flags)`, `this.logSuccessMessage(msg, flags)`, `this.logListening(msg, flags)`, `this.logHolding(msg, flags)`, `this.logWarning(msg, flags)`. These do NOT need `shouldOutputJson` guards. In non-JSON mode they all emit formatted text on stderr. In JSON mode: `logProgress` and `logSuccessMessage` are **silent** (no-ops — the JSON result/event records already convey progress and success), while `logListening` (status: "listening"), `logHolding` (status: "holding"), and `logWarning` (status: "warning") emit structured JSON on stdout. `logSuccessMessage` should be placed inside the `else` block after `logJsonResult` for readability. Only human-readable **data output** (field labels, headings, record blocks) needs the `if/else` guard:
214
214
215
215
```typescript
216
-
// JSON guard — all human output goes through this
217
-
if (!this.shouldOutputJson(flags)) {
218
-
this.log(formatProgress("Attaching to channel: "+formatResource(channelName)));
219
-
}
216
+
// Progress/success/listening — no guard needed, helpers handle both modes
217
+
this.logProgress("Attaching to channel: "+formatResource(channelName), flags);
220
218
221
219
// After success:
222
-
if (!this.shouldOutputJson(flags)) {
223
-
this.log(formatSuccess("Subscribed to channel: "+formatResource(channelName) +"."));
224
-
this.log(formatListening("Listening for messages."));
225
-
}
220
+
this.logSuccessMessage("Subscribed to channel: "+formatResource(channelName) +".", flags);
221
+
this.logListening("Listening for messages.", flags);
226
222
227
223
// JSON output — nest data under a domain key, not spread at top level.
@@ -669,7 +663,7 @@ Commands must behave strictly according to their documented purpose — no unint
669
663
670
664
**Set / enter / acquire commands** — hold state until Ctrl+C / `--duration`:
671
665
- Enter space (manual: `enterSpace: false` + `space.enter()` + `markAsEntered()`), perform operation, output confirmation, then hold with `waitAndTrackCleanup`
672
-
- Emit `formatListening("Holding <thing>.")` (human) and `logJsonStatus("holding", ...)` (JSON)
666
+
- Emit `this.logHolding("Holding <thing>. Press Ctrl+C to exit.", flags)` — in non-JSON mode this shows dim text on stderr; in JSON mode it emits `status: "holding"`
673
667
- **NOT subscribe** to other events — that is what subscribe commands are for
674
668
675
669
**Side-effect rules:**
@@ -706,8 +700,7 @@ await this.space!.enter();
706
700
this.markAsEntered();
707
701
await this.space!.locations.set(location);
708
702
// output result...
709
-
this.log(formatListening("Holding location."));
710
-
this.logJsonStatus("holding", "Holding location. Press Ctrl+C to exit.", flags);
703
+
this.logHolding("Holding location. Press Ctrl+C to exit.", flags);
Metadata fields (`total`, `timestamp`, `hasMore`, `appId`) may sit alongside the collection key since they describe the result, not the domain objects.
754
747
755
-
### Hold status for long-running commands (logJsonStatus)
748
+
### Hold status for long-running commands (logHolding)
756
749
757
-
Long-running commands that hold state (e.g. `spaces members enter`, `spaces locations set`, `spaces locks acquire`, `spaces cursors set`) must emit a status line after the result so JSON consumers know the command is alive and waiting:
750
+
Long-running commands that hold state (e.g. `spaces members enter`, `spaces locations set`, `spaces locks acquire`, `spaces cursors set`) must call `this.logHolding(...)` after the result so JSON consumers know the command is alive and waiting. For passive subscribe/stream commands, use `this.logListening(...)` instead.
0 commit comments