Skip to content

refactor(buddy): align companion system with official CLI#82

Merged
claude-code-best merged 3 commits into
claude-code-best:mainfrom
amDosion:refactor/buddy-align-official
Apr 3, 2026
Merged

refactor(buddy): align companion system with official CLI#82
claude-code-best merged 3 commits into
claude-code-best:mainfrom
amDosion:refactor/buddy-align-official

Conversation

@amDosion
Copy link
Copy Markdown
Collaborator

@amDosion amDosion commented Apr 3, 2026

Summary

Reverse-engineered the official Claude Code CLI (v2.1.91) buddy/companion system and aligned our implementation to match.

Changes (7 files)

Added

  • src/buddy/CompanionCard.tsx — JSX bordered card matching official vc8: rarity header, colored sprite, name, personality, 10-bar stats, last reaction in nested border box. Press any key to dismiss.
  • src/buddy/companionReact.ts — Reaction system matching official ZUK+Dc8: 45s rate limiting, @-mention detection, transcript builder (12 msgs, 5000 chars), POST buddy_react API.

Modified

  • src/commands/buddy/index.tstype: locallocal-jsx, added description/argumentHint/immediate/isHidden.
  • src/commands/buddy/buddy.tsLocalCommandCallLocalJSXCommandCall signature. Removed mute/unmute/rehatch (official uses off/on only). /buddy show returns CompanionCard JSX. Pet auto-unmutes. companionMuted writes globalConfig (matches UI read source).
  • src/screens/REPL.tsx (line 2808) — globalThis.fireCompanionObserverimport { triggerCompanionReaction }.
  • src/state/AppStateStore.ts — Comment fix: observer.tscompanionReact.ts.
  • src/types/global.d.ts — Removed fireCompanionObserver declaration.

Data flow audit (verified consistent)

  • companionMuted: saveGlobalConfig()getGlobalConfig() (all 6 read sites)
  • companionReaction: setAppState()useAppState() (all 4 sites)
  • companionPetAt: setAppState()useAppState() (all 2 sites)

Test plan

  • /buddy off → companion muted, sprite hidden
  • /buddy on → companion unmuted, sprite visible
  • /buddy pet → heart animation, auto-unmute if muted
  • /buddy (no args) → bordered card with stats, last reaction
  • After query → companion speech bubble appears (~45s interval)
  • All 18 species render correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Companion card UI: sprite, rarity, stars, personality, stat bars, and a SHINY banner when applicable.
    • Displays the companion's most recent remark when available.
    • Press any key to dismiss the interactive companion view (when shown).
    • Companions now generate conversational reactions after turns and when directly addressed.
    • Updated buddy command: interactive hatch/view, immediate pet action (auto-unmutes), off/mute controls, and hidden when no companion exists.

## Summary

Reverse-engineered the official Claude Code CLI (v2.1.91) buddy/companion
system and aligned our implementation to match.

## Changes (7 files)

### Added
- `src/buddy/CompanionCard.tsx` (+109)
  JSX bordered card matching official vc8: rarity header, colored sprite,
  name, personality, 10-bar stats, last reaction in nested border.

- `src/buddy/companionReact.ts` (+156)
  Reaction system matching official ZUK+Dc8: 45s rate limiting, @-mention
  detection, transcript builder (12 msgs, 5000 chars), POST buddy_react API.

### Modified
- `src/commands/buddy/index.ts`
  type: local -> local-jsx, description/argumentHint/immediate/isHidden.

- `src/commands/buddy/buddy.ts`
  LocalCommandCall -> LocalJSXCommandCall signature (onDone, context, args).
  Removed mute/unmute/rehatch (official uses off/on only).
  /buddy show returns CompanionCard JSX instead of plain text.
  Pet auto-unmutes. companionMuted writes globalConfig (matches UI read source).

- `src/screens/REPL.tsx` (line 2808)
  globalThis.fireCompanionObserver -> import triggerCompanionReaction.

- `src/state/AppStateStore.ts` — comment fix.
- `src/types/global.d.ts` — removed fireCompanionObserver declaration.

## Data flow (verified consistent)
- companionMuted: saveGlobalConfig() <-> getGlobalConfig() (6 read sites)
- companionReaction: setAppState() <-> useAppState() (4 sites)
- companionPetAt: setAppState() <-> useAppState() (2 sites)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

Adds a JSX-based CompanionCard UI, relocates companion reaction logic into a new trigger function, converts the buddy command to a local-jsx handler that returns JSX and drives outputs via onDone, and updates REPL integration and types to call the new reaction trigger.

Changes

Cohort / File(s) Summary
Companion UI
src/buddy/CompanionCard.tsx
New exported React/Ink CompanionCard component: fixed-width bordered card, rarity/star header, sprite via renderSprite, optional "SHINY" banner and "last said", name/personality, stat bars (0–100 clamp), and useInput dismissal via onDone.
Companion reaction service
src/buddy/companionReact.ts
Added triggerCompanionReaction(messages, setReaction): verifies companion and mute state, detects explicit addressing, enforces rate limits, builds capped transcript (up to 12 messages, size limits), sends authenticated POST to buddy_react API (fire-and-forget), maintains recent reactions buffer (max 8), and invokes setReaction on success.
Buddy command handler & metadata
src/commands/buddy/buddy.ts, src/commands/buddy/index.ts
Reworked command from LocalCommandCall to local-jsx async call(onDone, context, args): Promise<React.ReactNode>; removed text stats rendering; default view returns CompanionCard; pet now unmutes, sets companionPetAt, triggers reaction and signals via onDone; command metadata updated (type: 'local-jsx', argumentHint, immediate, isHidden based on isBuddyLive).
REPL integration & types
src/screens/REPL.tsx, src/state/AppStateStore.ts, src/types/global.d.ts
REPL now calls triggerCompanionReaction(messagesRef.current, callback) when BUDDY feature enabled; companionReaction comment updated to reference new module; removed global fireCompanionObserver declaration.

Sequence Diagram

sequenceDiagram
    participant User
    participant REPL
    participant CompanionReact as companionReact
    participant BuddyAPI as buddy_react API
    participant App as setReaction

    User->>REPL: Execute query
    REPL->>REPL: Process turn
    REPL->>CompanionReact: triggerCompanionReaction(messages, setReaction)
    CompanionReact->>CompanionReact: Check companion present & mute
    CompanionReact->>CompanionReact: Detect addressed vs turn, enforce rate limit
    CompanionReact->>CompanionReact: Build capped transcript & recent reactions
    alt eligible to request
        CompanionReact->>BuddyAPI: POST transcript + companion metadata (OAuth + org UUID)
        BuddyAPI-->>CompanionReact: 200 + reaction
        CompanionReact->>CompanionReact: Trim & cache reaction (max 8)
        CompanionReact->>App: setReaction(trimmed_reaction)
        App->>REPL: Update companionReaction state
    else not eligible or error
        CompanionReact-->>App: (no reaction) silent
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • 合并最新主分支 #36 — Overlaps buddy feature changes; edits to src/commands/buddy/buddy.ts and companion-related logic suggest a direct code-level relationship.

Poem

🐰 I drew a rounded card with stars aglow,
I taught my buddy when and how to show,
Sprites and "SHINY" flags, a stat-lined tune,
Reactions whisper back from a tiny rune,
Hop, code-pet — chatter bright as morning dew! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor(buddy): align companion system with official CLI' accurately summarizes the main change—a reverse-engineered refactor to align the buddy/companion system with the official Claude Code CLI implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@amDosion
Copy link
Copy Markdown
Collaborator Author

amDosion commented Apr 3, 2026

image image

与官方保持一致,支持45秒气泡,

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (6)
src/buddy/companionReact.ts (4)

8-13: Relative imports used instead of src/ path alias.

The coding guidelines specify using the src/ path alias via tsconfig mapping instead of relative paths. All imports in this file use relative paths.

♻️ Suggested refactor to use path alias
-import { getCompanion } from './companion.js'
-import { getGlobalConfig } from '../utils/config.js'
-import { getClaudeAIOAuthTokens } from '../utils/auth.js'
-import { getOauthConfig } from '../constants/oauth.js'
-import { getUserAgent } from '../utils/http.js'
-import type { Message } from '../types/message.js'
+import { getCompanion } from 'src/buddy/companion.js'
+import { getGlobalConfig } from 'src/utils/config.js'
+import { getClaudeAIOAuthTokens } from 'src/utils/auth.js'
+import { getOauthConfig } from 'src/constants/oauth.js'
+import { getUserAgent } from 'src/utils/http.js'
+import type { Message } from 'src/types/message.js'

As per coding guidelines: "Import src/ path alias via tsconfig mapping instead of relative paths in imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/buddy/companionReact.ts` around lines 8 - 13, Replace the relative
imports at the top of companionReact.ts with the project path-alias "src/"
imports: update import statements referencing getCompanion, getGlobalConfig,
getClaudeAIOAuthTokens, getOauthConfig, getUserAgent and the Message type to use
the tsconfig path alias (e.g. import { getCompanion } from 'src/buddy/companion'
etc.), ensuring module specifiers match existing exported module paths and
maintain file extensions/types as needed so the compiler resolves via the
tsconfig "src/*" mapping.

53-62: Rate-limit timestamp updated before API response is confirmed.

lastReactTime is set at line 53 before the API call completes. If the call fails, the companion won't react again until the interval passes. This may be intentional to prevent hammering the API on failures, but it means transient errors (network blips) will suppress reactions for 45s.

If failed attempts shouldn't consume the rate-limit window, move the timestamp update into the .then() block:

♻️ Alternative: update timestamp only on success
-  lastReactTime = now
-
   void callBuddyReactAPI(companion, transcript, addressed)
     .then(reaction => {
       if (!reaction) return
+      lastReactTime = Date.now()
       recentReactions.push(reaction)
       if (recentReactions.length > MAX_RECENT) recentReactions.shift()
       setReaction(reaction)
     })
     .catch(() => {})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/buddy/companionReact.ts` around lines 53 - 62, The rate-limit timestamp
lastReactTime is being set before callBuddyReactAPI completes; move the update
so failures don't consume the window by setting lastReactTime only after a
successful reaction is confirmed inside the .then() callback (e.g., when
reaction is truthy), leaving the existing logic that pushes to recentReactions,
enforces MAX_RECENT, and calls setReaction untouched; do not update
lastReactTime in the .catch() path so transient errors won't block subsequent
calls.

152-155: Unhandled JSON parse error on malformed response.

If the server returns 200 OK with invalid JSON (e.g., empty body or HTML error page), resp.json() will throw. Since callBuddyReactAPI is called with .catch(() => {}) upstream, this is caught silently, but wrapping the parse is cleaner:

♻️ Defensive JSON parsing
   if (!resp.ok) return null

-  const data = (await resp.json()) as { reaction?: string }
-  return data.reaction?.trim() || null
+  try {
+    const data = (await resp.json()) as { reaction?: string }
+    return data.reaction?.trim() || null
+  } catch {
+    return null
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/buddy/companionReact.ts` around lines 152 - 155, The response parsing in
callBuddyReactAPI can throw if resp.json() encounters invalid JSON; wrap the
await resp.json() call in a try/catch inside callBuddyReactAPI (or the function
containing resp) so malformed/empty bodies return null instead of propagating an
exception, e.g., catch the error from resp.json(), optionally log/debug it, and
return null; keep the existing check for resp.ok and preserve returning
data.reaction?.trim() || null when parsing succeeds.

67-80: Static analysis ReDoS warning is a false positive, but edge case exists for non-word-character names.

The escapeRegex helper properly escapes metacharacters, so the ReDoS concern is mitigated. However, if companion.name consists entirely of non-word characters (e.g., emoji like "🦆"), the pattern \b\b matches zero-width positions everywhere, causing false positives.

Consider adding a guard or adjusting the pattern:

♻️ Handle edge case for non-word-character names
 function isAddressed(messages: Message[], name: string): boolean {
+  // If name has no word characters, use simple includes check
+  if (!/\w/.test(name)) {
+    for (let i = messages.length - 1; i >= Math.max(0, messages.length - 3); i--) {
+      const m = messages[i]
+      if (m?.type !== 'user') continue
+      const content = (m as any).message?.content
+      if (typeof content === 'string' && content.toLowerCase().includes(name.toLowerCase())) return true
+    }
+    return false
+  }
   const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'i')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/buddy/companionReact.ts` around lines 67 - 80, The isAddressed function
can false-positive when name is all non-word characters (e.g., emoji) because
using \b around it makes the regex match everywhere; update isAddressed to
detect that case by checking the provided name (from companion.name) for any
word characters (e.g., /\w/ or Unicode-aware equivalent) and, if none exist,
avoid surrounding with \b and instead perform a direct substring or indexOf
check (or construct a regex without \b) against message content; keep using
escapeRegex for escaping and only apply the \b-delimited regex when the name
contains at least one word character so you preserve current behavior and avoid
false positives.
src/commands/buddy/buddy.ts (2)

120-123: Auto-unmute on /buddy view may surprise users who intentionally muted.

When a user runs /buddy to view their companion, it automatically unmutes if previously muted. This could be confusing for users who explicitly ran /buddy off and just want to check their companion's stats without re-enabling reactions.

Consider either:

  1. Keeping this behavior but documenting it clearly, or
  2. Only viewing without changing mute state
💡 Alternative: view without auto-unmute
-  // Auto-unmute when viewing
-  if (companion && getGlobalConfig().companionMuted) {
-    saveGlobalConfig(cfg => ({ ...cfg, companionMuted: false }))
-  }
+  // Note: viewing companion does not change mute state
+  // Use /buddy on to unmute
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/buddy/buddy.ts` around lines 120 - 123, The current handler
unconditionally flips companionMuted to false when a companion exists (see
companion, getGlobalConfig(), saveGlobalConfig, companionMuted) which surprises
users; change the behavior to only read and display the companion state without
mutating companionMuted (remove or guard the saveGlobalConfig call), or
alternatively require an explicit unmute action/flag (e.g. `/buddy on`) before
calling saveGlobalConfig so viewing `/buddy` does not change the mute setting.
Ensure you update the logic around companion and the saveGlobalConfig(...)
invocation accordingly so viewing is non-mutating unless an explicit unmute
command is provided.

1-16: Relative imports used instead of src/ path alias.

Similar to companionReact.ts, this file uses relative paths for imports rather than the src/ path alias specified in the coding guidelines.

♻️ Suggested refactor to use path alias
 import React from 'react'
 import {
   getCompanion,
   rollWithSeed,
   generateSeed,
-} from '../../buddy/companion.js'
-import { type StoredCompanion, RARITY_STARS } from '../../buddy/types.js'
-import { renderSprite } from '../../buddy/sprites.js'
-import { CompanionCard } from '../../buddy/CompanionCard.js'
-import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
-import { triggerCompanionReaction } from '../../buddy/companionReact.js'
-import type { ToolUseContext } from '../../Tool.js'
+} from 'src/buddy/companion.js'
+import { type StoredCompanion, RARITY_STARS } from 'src/buddy/types.js'
+import { renderSprite } from 'src/buddy/sprites.js'
+import { CompanionCard } from 'src/buddy/CompanionCard.js'
+import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js'
+import { triggerCompanionReaction } from 'src/buddy/companionReact.js'
+import type { ToolUseContext } from 'src/Tool.js'
 import type {
   LocalJSXCommandContext,
   LocalJSXCommandOnDone,
-} from '../../types/command.js'
+} from 'src/types/command.js'

As per coding guidelines: "Import src/ path alias via tsconfig mapping instead of relative paths in imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/buddy/buddy.ts` around lines 1 - 16, Replace the relative
imports in buddy.ts with the project path alias (src/...) so imports like
getCompanion, rollWithSeed, generateSeed (from ../../buddy/companion.js),
StoredCompanion and RARITY_STARS (from ../../buddy/types.js), renderSprite
(../../buddy/sprites.js), CompanionCard (../../buddy/CompanionCard.js),
triggerCompanionReaction (../../buddy/companionReact.js) and
getGlobalConfig/saveGlobalConfig (../../utils/config.js) use the tsconfig path
mapping (e.g. import from "src/buddy/companion", "src/buddy/types", etc.);
update each import statement accordingly and ensure extensions match project
conventions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/buddy/CompanionCard.tsx`:
- Around line 15-16: Clamp the incoming stat value before computing the bar to
avoid negative repeat counts: in CompanionCard (where `value` is used to compute
`filled` and `bar`), replace the direct use of `value` with a clamped value
(e.g. v = Math.max(0, Math.min(100, value))) and compute `filled` = Math.round(v
/ 10) and then build `bar` from that clamped `filled`; ensure `filled` and `bar`
are the only places using the clamped variable so rendering is safe for values
outside 0..100.
- Around line 6-9: Replace the relative imports in CompanionCard.tsx with
tsconfig path-alias imports: import Box, Text and useInput from the aliased
module for ink (instead of '../ink.js'), import renderSprite from the aliased
sprites module (instead of './sprites.js'), and import RARITY_COLORS,
RARITY_STARS, STAT_NAMES and the Companion type from the aliased types module
(instead of './types.js'); update the import targets to use the project's
"src/..." path mappings so the symbols Box, Text, useInput, renderSprite,
RARITY_COLORS, RARITY_STARS, STAT_NAMES and Companion are resolved via the
tsconfig alias.

In `@src/commands/buddy/buddy.ts`:
- Around line 70-74: The function signature for call currently returns
Promise<null> but actually returns React elements; update the return type of the
call function to Promise<React.ReactNode> to match the LocalJSXCommandCall
interface and the actual return values (e.g., the
React.createElement(CompanionCard, ...) return path). Leave early returns that
return null as-is since null is a valid ReactNode; ensure any TypeScript
import/namespace references allow using React.ReactNode in the signature if not
already imported.

In `@src/commands/buddy/index.ts`:
- Around line 7-8: Update the command metadata to include the "on" argument:
change the description and argumentHint fields (description and argumentHint in
the buddy command) so they advertise the supported args including "on" (e.g.,
include "on" alongside "pet" and "off") to match the implemented `/buddy on`
flow.
- Line 2: Replace the relative import of isBuddyLive with the repo path alias:
change the import statement that currently references
'../../buddy/useBuddyNotification.js' to use the tsconfig alias starting with
'src/' (i.e., import isBuddyLive from 'src/buddy/useBuddyNotification.js' or
equivalent), so the symbol isBuddyLive is imported via the src/ alias instead of
a relative path.

In `@src/screens/REPL.tsx`:
- Line 278: The import in REPL.tsx uses a relative path for
triggerCompanionReaction; update the import to use the configured tsconfig path
alias (src/) instead of '../buddy/companionReact.js' so the module resolves via
the project's src/ mapping; locate the import statement referencing
triggerCompanionReaction in REPL.tsx and replace the relative path with the
aliased path (e.g., import { triggerCompanionReaction } from
'src/buddy/companionReact') ensuring the file extension and casing match project
conventions.

---

Nitpick comments:
In `@src/buddy/companionReact.ts`:
- Around line 8-13: Replace the relative imports at the top of companionReact.ts
with the project path-alias "src/" imports: update import statements referencing
getCompanion, getGlobalConfig, getClaudeAIOAuthTokens, getOauthConfig,
getUserAgent and the Message type to use the tsconfig path alias (e.g. import {
getCompanion } from 'src/buddy/companion' etc.), ensuring module specifiers
match existing exported module paths and maintain file extensions/types as
needed so the compiler resolves via the tsconfig "src/*" mapping.
- Around line 53-62: The rate-limit timestamp lastReactTime is being set before
callBuddyReactAPI completes; move the update so failures don't consume the
window by setting lastReactTime only after a successful reaction is confirmed
inside the .then() callback (e.g., when reaction is truthy), leaving the
existing logic that pushes to recentReactions, enforces MAX_RECENT, and calls
setReaction untouched; do not update lastReactTime in the .catch() path so
transient errors won't block subsequent calls.
- Around line 152-155: The response parsing in callBuddyReactAPI can throw if
resp.json() encounters invalid JSON; wrap the await resp.json() call in a
try/catch inside callBuddyReactAPI (or the function containing resp) so
malformed/empty bodies return null instead of propagating an exception, e.g.,
catch the error from resp.json(), optionally log/debug it, and return null; keep
the existing check for resp.ok and preserve returning data.reaction?.trim() ||
null when parsing succeeds.
- Around line 67-80: The isAddressed function can false-positive when name is
all non-word characters (e.g., emoji) because using \b around it makes the regex
match everywhere; update isAddressed to detect that case by checking the
provided name (from companion.name) for any word characters (e.g., /\w/ or
Unicode-aware equivalent) and, if none exist, avoid surrounding with \b and
instead perform a direct substring or indexOf check (or construct a regex
without \b) against message content; keep using escapeRegex for escaping and
only apply the \b-delimited regex when the name contains at least one word
character so you preserve current behavior and avoid false positives.

In `@src/commands/buddy/buddy.ts`:
- Around line 120-123: The current handler unconditionally flips companionMuted
to false when a companion exists (see companion, getGlobalConfig(),
saveGlobalConfig, companionMuted) which surprises users; change the behavior to
only read and display the companion state without mutating companionMuted
(remove or guard the saveGlobalConfig call), or alternatively require an
explicit unmute action/flag (e.g. `/buddy on`) before calling saveGlobalConfig
so viewing `/buddy` does not change the mute setting. Ensure you update the
logic around companion and the saveGlobalConfig(...) invocation accordingly so
viewing is non-mutating unless an explicit unmute command is provided.
- Around line 1-16: Replace the relative imports in buddy.ts with the project
path alias (src/...) so imports like getCompanion, rollWithSeed, generateSeed
(from ../../buddy/companion.js), StoredCompanion and RARITY_STARS (from
../../buddy/types.js), renderSprite (../../buddy/sprites.js), CompanionCard
(../../buddy/CompanionCard.js), triggerCompanionReaction
(../../buddy/companionReact.js) and getGlobalConfig/saveGlobalConfig
(../../utils/config.js) use the tsconfig path mapping (e.g. import from
"src/buddy/companion", "src/buddy/types", etc.); update each import statement
accordingly and ensure extensions match project conventions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a37b4afd-aa71-472e-8bd0-f06450ab251f

📥 Commits

Reviewing files that changed from the base of the PR and between 7935bfb and 9911194.

📒 Files selected for processing (7)
  • src/buddy/CompanionCard.tsx
  • src/buddy/companionReact.ts
  • src/commands/buddy/buddy.ts
  • src/commands/buddy/index.ts
  • src/screens/REPL.tsx
  • src/state/AppStateStore.ts
  • src/types/global.d.ts

Comment on lines +6 to +9
import { Box, Text } from '../ink.js';
import { useInput } from '../ink.js';
import { renderSprite } from './sprites.js';
import { RARITY_COLORS, RARITY_STARS, STAT_NAMES, type Companion } from './types.js';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use src/ path aliases for new TSX imports.

Lines 6-9 introduce new relative imports; these should use src/... aliases.

Suggested fix
-import { Box, Text } from '../ink.js';
-import { useInput } from '../ink.js';
-import { renderSprite } from './sprites.js';
-import { RARITY_COLORS, RARITY_STARS, STAT_NAMES, type Companion } from './types.js';
+import { Box, Text } from 'src/ink.js';
+import { useInput } from 'src/ink.js';
+import { renderSprite } from 'src/buddy/sprites.js';
+import { RARITY_COLORS, RARITY_STARS, STAT_NAMES, type Companion } from 'src/buddy/types.js';

As per coding guidelines Import src/ path alias via tsconfig mapping instead of relative paths in imports.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Box, Text } from '../ink.js';
import { useInput } from '../ink.js';
import { renderSprite } from './sprites.js';
import { RARITY_COLORS, RARITY_STARS, STAT_NAMES, type Companion } from './types.js';
import { Box, Text } from 'src/ink.js';
import { useInput } from 'src/ink.js';
import { renderSprite } from 'src/buddy/sprites.js';
import { RARITY_COLORS, RARITY_STARS, STAT_NAMES, type Companion } from 'src/buddy/types.js';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/buddy/CompanionCard.tsx` around lines 6 - 9, Replace the relative imports
in CompanionCard.tsx with tsconfig path-alias imports: import Box, Text and
useInput from the aliased module for ink (instead of '../ink.js'), import
renderSprite from the aliased sprites module (instead of './sprites.js'), and
import RARITY_COLORS, RARITY_STARS, STAT_NAMES and the Companion type from the
aliased types module (instead of './types.js'); update the import targets to use
the project's "src/..." path mappings so the symbols Box, Text, useInput,
renderSprite, RARITY_COLORS, RARITY_STARS, STAT_NAMES and Companion are resolved
via the tsconfig alias.

Comment thread src/buddy/CompanionCard.tsx Outdated
Comment thread src/commands/buddy/buddy.ts Outdated
@@ -1,10 +1,15 @@
import type { Command } from '../../commands.js'
import { isBuddyLive } from '../../buddy/useBuddyNotification.js'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Switch this new import to the src/ alias.

Line 2 adds a relative import in a TS file; use the repo alias to stay consistent.

Suggested fix
-import { isBuddyLive } from '../../buddy/useBuddyNotification.js'
+import { isBuddyLive } from 'src/buddy/useBuddyNotification.js'

As per coding guidelines Import src/ path alias via tsconfig mapping instead of relative paths in imports.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { isBuddyLive } from '../../buddy/useBuddyNotification.js'
import { isBuddyLive } from 'src/buddy/useBuddyNotification.js'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/buddy/index.ts` at line 2, Replace the relative import of
isBuddyLive with the repo path alias: change the import statement that currently
references '../../buddy/useBuddyNotification.js' to use the tsconfig alias
starting with 'src/' (i.e., import isBuddyLive from
'src/buddy/useBuddyNotification.js' or equivalent), so the symbol isBuddyLive is
imported via the src/ alias instead of a relative path.

Comment on lines +7 to +8
description: 'Hatch a coding companion · pet, off',
argumentHint: '[pet|off]',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Include on in command hint/description to match supported args.

Line 8 currently advertises [pet|off], but /buddy on is part of the command flow in this PR. The hint/description should include it.

Suggested fix
-  description: 'Hatch a coding companion · pet, off',
-  argumentHint: '[pet|off]',
+  description: 'Hatch a coding companion · pet, on, off',
+  argumentHint: '[pet|on|off]',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description: 'Hatch a coding companion · pet, off',
argumentHint: '[pet|off]',
description: 'Hatch a coding companion · pet, on, off',
argumentHint: '[pet|on|off]',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/buddy/index.ts` around lines 7 - 8, Update the command metadata
to include the "on" argument: change the description and argumentHint fields
(description and argumentHint in the buddy command) so they advertise the
supported args including "on" (e.g., include "on" alongside "pet" and "off") to
match the implemented `/buddy on` flow.

Comment thread src/screens/REPL.tsx
import { IssueFlagBanner } from '../components/PromptInput/IssueFlagBanner.js';
import { useIssueFlagBanner } from '../hooks/useIssueFlagBanner.js';
import { CompanionSprite, CompanionFloatingBubble, MIN_COLS_FOR_FULL_SPRITE } from '../buddy/CompanionSprite.js';
import { triggerCompanionReaction } from '../buddy/companionReact.js';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use src/ alias for the new companion import.

Line 278 introduces a new relative import in a TSX file; this should use the configured src/ path alias.

Suggested fix
-import { triggerCompanionReaction } from '../buddy/companionReact.js';
+import { triggerCompanionReaction } from 'src/buddy/companionReact.js';

As per coding guidelines Import src/ path alias via tsconfig mapping instead of relative paths in imports.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { triggerCompanionReaction } from '../buddy/companionReact.js';
import { triggerCompanionReaction } from 'src/buddy/companionReact.js';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/screens/REPL.tsx` at line 278, The import in REPL.tsx uses a relative
path for triggerCompanionReaction; update the import to use the configured
tsconfig path alias (src/) instead of '../buddy/companionReact.js' so the module
resolves via the project's src/ mapping; locate the import statement referencing
triggerCompanionReaction in REPL.tsx and replace the relative path with the
aliased path (e.g., import { triggerCompanionReaction } from
'src/buddy/companionReact') ensuring the file extension and casing match project
conventions.

- buddy.ts: return type Promise<null> → Promise<React.ReactNode>
  to match LocalJSXCommandCall interface (CompanionCard path returns
  ReactElement, not null).
- CompanionCard.tsx: clamp stat value to 0..100 before .repeat()
  to prevent negative count runtime error on out-of-range values.

Import path alias suggestions (src/ vs ../) dismissed — project
convention uses relative paths (verified against color.ts, help.ts).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/commands/buddy/buddy.ts (1)

1-16: Relative imports violate path alias convention.

The coding guidelines require using the src/ path alias via tsconfig mapping instead of relative paths. All imports here use relative paths (e.g., ../../buddy/companion.js).

♻️ Suggested refactor to use path aliases
 import React from 'react'
 import {
   getCompanion,
   rollWithSeed,
   generateSeed,
-} from '../../buddy/companion.js'
-import { type StoredCompanion, RARITY_STARS } from '../../buddy/types.js'
-import { renderSprite } from '../../buddy/sprites.js'
-import { CompanionCard } from '../../buddy/CompanionCard.js'
-import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
-import { triggerCompanionReaction } from '../../buddy/companionReact.js'
-import type { ToolUseContext } from '../../Tool.js'
+} from 'src/buddy/companion.js'
+import { type StoredCompanion, RARITY_STARS } from 'src/buddy/types.js'
+import { renderSprite } from 'src/buddy/sprites.js'
+import { CompanionCard } from 'src/buddy/CompanionCard.js'
+import { getGlobalConfig, saveGlobalConfig } from 'src/utils/config.js'
+import { triggerCompanionReaction } from 'src/buddy/companionReact.js'
+import type { ToolUseContext } from 'src/Tool.js'
 import type {
   LocalJSXCommandContext,
   LocalJSXCommandOnDone,
-} from '../../types/command.js'
+} from 'src/types/command.js'

As per coding guidelines: "Import src/ path alias via tsconfig mapping instead of relative paths in imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/buddy/buddy.ts` around lines 1 - 16, Replace all relative
imports in this file with the project's tsconfig path alias (src/). Specifically
update the imports that bring in getCompanion, rollWithSeed, generateSeed from
../../buddy/companion.js; StoredCompanion and RARITY_STARS from
../../buddy/types.js; renderSprite from ../../buddy/sprites.js; CompanionCard
from ../../buddy/CompanionCard.js; getGlobalConfig and saveGlobalConfig from
../../utils/config.js; and triggerCompanionReaction from
../../buddy/companionReact.js to use the src/ alias (e.g., import { getCompanion
} from 'src/buddy/companion') so they conform to the codebase import convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/buddy/buddy.ts`:
- Around line 104-111: The code is using an unsafe cast (context as
any).messages when calling triggerCompanionReaction in buddy.ts; fix by adding
messages: Message[] to the ToolUseContext (or LocalJSXCommandContext) type
declaration in src/types/command.ts (or to the Tool.ts context shape) so the
runtime messages property is represented in types, then remove the (context as
any) cast in buddy.ts and any other places (e.g., feedback.tsx, copy.tsx) so
they use context.messages typed as Message[]; alternatively, if messages is
intentionally runtime-only, add a clear comment in the type file documenting
that and keep a narrow explicit accessor function to read it rather than casting
to any.

---

Nitpick comments:
In `@src/commands/buddy/buddy.ts`:
- Around line 1-16: Replace all relative imports in this file with the project's
tsconfig path alias (src/). Specifically update the imports that bring in
getCompanion, rollWithSeed, generateSeed from ../../buddy/companion.js;
StoredCompanion and RARITY_STARS from ../../buddy/types.js; renderSprite from
../../buddy/sprites.js; CompanionCard from ../../buddy/CompanionCard.js;
getGlobalConfig and saveGlobalConfig from ../../utils/config.js; and
triggerCompanionReaction from ../../buddy/companionReact.js to use the src/
alias (e.g., import { getCompanion } from 'src/buddy/companion') so they conform
to the codebase import convention.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c51d61fb-803b-4a76-8e73-d5e6ed24e600

📥 Commits

Reviewing files that changed from the base of the PR and between 9911194 and 7d4adce.

📒 Files selected for processing (2)
  • src/buddy/CompanionCard.tsx
  • src/commands/buddy/buddy.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/buddy/CompanionCard.tsx

Comment on lines +104 to +111
// Trigger a post-pet reaction
triggerCompanionReaction((context as any).messages ?? [], reaction =>
setState?.(prev =>
prev.companionReaction === reaction
? prev
: { ...prev, companionReaction: reaction },
),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if messages is passed to local-jsx command contexts at runtime
rg -n "messages" src/commands --type ts -C3 | head -60

echo "---"
# Check how local-jsx commands receive their context
ast-grep --pattern 'call($_, $_, $_) {
  $$$
}'

Repository: claude-code-best/claude-code

Length of output: 4392


🏁 Script executed:

# First, let's examine the type definitions for LocalJSXCommandContext and ToolUseContext
cat -n src/types/command.ts | head -100

Repository: claude-code-best/claude-code

Length of output: 4150


🏁 Script executed:

# Check what properties are actually available on the context types
rg "interface LocalJSXCommandContext|interface ToolUseContext" -A 20 src/types/command.ts

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Look at buddy.ts to understand what context parameter is passed to the call function
cat -n src/commands/buddy/buddy.ts | sed -n '1,110p'

Repository: claude-code-best/claude-code

Length of output: 4862


🏁 Script executed:

# Check how buddy command is invoked to understand what context type is passed
rg "buddy.*call|call.*buddy" -B 5 -A 5 src/commands

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Check the ToolUseContext type definition
rg "interface ToolUseContext|type ToolUseContext|export.*ToolUseContext" -A 30 src/Tool.ts | head -60

Repository: claude-code-best/claude-code

Length of output: 1346


🏁 Script executed:

# Check if other commands use similar patterns to access messages
rg "context.*messages" src/commands --type ts -B 2 -A 2

Repository: claude-code-best/claude-code

Length of output: 4137


🏁 Script executed:

# Search for any type augmentation or re-declaration of ToolUseContext or LocalJSXCommandContext
rg "ToolUseContext|LocalJSXCommandContext" src -g "*.d.ts" -A 5

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Check the exact signatures of commands using context.messages to see their context type
rg "export.*function call|export const call" src/commands/feedback/feedback.tsx -A 1
rg "export.*function call|export const call" src/commands/copy/copy.tsx -A 1
rg "export.*function call|export const call" src/commands/export/export.tsx -A 1

Repository: claude-code-best/claude-code

Length of output: 547


🏁 Script executed:

# Check if there's a global declaration or type file that adds messages to context
fd ".*\.d\.ts$" src | xargs grep -l "messages" 2>/dev/null

Repository: claude-code-best/claude-code

Length of output: 56


context.messages is untyped and accessed via unsafe any cast.

The messages property is not declared on ToolUseContext or LocalJSXCommandContext (per src/types/command.ts lines 80–98 and src/Tool.ts), yet the code accesses it via (context as any).messages ?? [] at line 105. While the fallback to [] prevents crashes, the companion reaction silently loses conversation context without it.

This pattern exists inconsistently across the codebase—other commands like feedback.tsx and copy.tsx access context.messages without any cast, suggesting the types are out of sync with the runtime implementation.

Add messages: Message[] to ToolUseContext or document why this is intentionally a runtime-only property.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/buddy/buddy.ts` around lines 104 - 111, The code is using an
unsafe cast (context as any).messages when calling triggerCompanionReaction in
buddy.ts; fix by adding messages: Message[] to the ToolUseContext (or
LocalJSXCommandContext) type declaration in src/types/command.ts (or to the
Tool.ts context shape) so the runtime messages property is represented in types,
then remove the (context as any) cast in buddy.ts and any other places (e.g.,
feedback.tsx, copy.tsx) so they use context.messages typed as Message[];
alternatively, if messages is intentionally runtime-only, add a clear comment in
the type file documenting that and keep a narrow explicit accessor function to
read it rather than casting to any.

- buddy.ts:105: remove unsafe (context as any).messages cast.
  ToolUseContext already declares messages: Message[] at Tool.ts:250,
  so context.messages is properly typed. Other commands (feedback,
  copy, export) access it the same way without cast.

- companionReact.ts:154: wrap resp.json() in try/catch for defensive
  JSON parsing. Malformed 200 responses now return null instead of
  propagating to the outer catch.

Rate-limit timing (set before API call) kept as-is — matches official
ZUK pattern: prevents retry-storm on transient failures.

src/ path alias suggestions dismissed — project uses relative paths.
Auto-unmute on /buddy view kept — matches official behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/buddy/companionReact.ts`:
- Around line 8-13: The imports in companionReact.ts use relative paths; replace
them with the tsconfig path-alias imports that start with "src/" to follow repo
conventions—for example update imports referencing getCompanion,
getGlobalConfig, getClaudeAIOAuthTokens, getOauthConfig, getUserAgent and the
Message type to use "src/..." alias paths (e.g., import { getCompanion } from
'src/buddy/companion') so the module resolver and linting conform to the
project's tsconfig mapping.
- Around line 68-77: The current detection regex (pattern built with
escapeRegex(name)) matches bare names like "Bob" instead of explicit `@-mentions`;
update the pattern construction in companionReact.ts to require an '@' before
the name (for example use a regex like `(^|\\s)@${escapeRegex(name)}\\b` with
case-insensitive flag) so the loop over messages (messages, pattern,
escapeRegex, name) only returns true when the user was explicitly mentioned with
'@'.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6607521e-05aa-42f6-ba27-fc103381631b

📥 Commits

Reviewing files that changed from the base of the PR and between 7d4adce and e74d1f0.

📒 Files selected for processing (2)
  • src/buddy/companionReact.ts
  • src/commands/buddy/buddy.ts
✅ Files skipped from review due to trivial changes (1)
  • src/commands/buddy/buddy.ts

Comment on lines +8 to +13
import { getCompanion } from './companion.js'
import { getGlobalConfig } from '../utils/config.js'
import { getClaudeAIOAuthTokens } from '../utils/auth.js'
import { getOauthConfig } from '../constants/oauth.js'
import { getUserAgent } from '../utils/http.js'
import type { Message } from '../types/message.js'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use src/ path alias imports instead of relative imports

These imports currently violate the repository import-path convention.

Proposed fix
-import { getCompanion } from './companion.js'
-import { getGlobalConfig } from '../utils/config.js'
-import { getClaudeAIOAuthTokens } from '../utils/auth.js'
-import { getOauthConfig } from '../constants/oauth.js'
-import { getUserAgent } from '../utils/http.js'
-import type { Message } from '../types/message.js'
+import { getCompanion } from 'src/buddy/companion.js'
+import { getGlobalConfig } from 'src/utils/config.js'
+import { getClaudeAIOAuthTokens } from 'src/utils/auth.js'
+import { getOauthConfig } from 'src/constants/oauth.js'
+import { getUserAgent } from 'src/utils/http.js'
+import type { Message } from 'src/types/message.js'

As per coding guidelines, Import src/ path alias via tsconfig mapping instead of relative paths in imports.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { getCompanion } from './companion.js'
import { getGlobalConfig } from '../utils/config.js'
import { getClaudeAIOAuthTokens } from '../utils/auth.js'
import { getOauthConfig } from '../constants/oauth.js'
import { getUserAgent } from '../utils/http.js'
import type { Message } from '../types/message.js'
import { getCompanion } from 'src/buddy/companion.js'
import { getGlobalConfig } from 'src/utils/config.js'
import { getClaudeAIOAuthTokens } from 'src/utils/auth.js'
import { getOauthConfig } from 'src/constants/oauth.js'
import { getUserAgent } from 'src/utils/http.js'
import type { Message } from 'src/types/message.js'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/buddy/companionReact.ts` around lines 8 - 13, The imports in
companionReact.ts use relative paths; replace them with the tsconfig path-alias
imports that start with "src/" to follow repo conventions—for example update
imports referencing getCompanion, getGlobalConfig, getClaudeAIOAuthTokens,
getOauthConfig, getUserAgent and the Message type to use "src/..." alias paths
(e.g., import { getCompanion } from 'src/buddy/companion') so the module
resolver and linting conform to the project's tsconfig mapping.

Comment on lines +68 to +77
const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'i')
for (
let i = messages.length - 1;
i >= Math.max(0, messages.length - 3);
i--
) {
const m = messages[i]
if (m?.type !== 'user') continue
const content = (m as any).message?.content
if (typeof content === 'string' && pattern.test(content)) return true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

@-mention detection is not actually enforced

This regex matches bare names (Bob) rather than explicit mentions (@Bob), so addressed can be true when the user did not mention with @.

Proposed fix
 function isAddressed(messages: Message[], name: string): boolean {
-  const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'i')
+  const pattern = new RegExp(`(?:^|\\s)@${escapeRegex(name)}(?=\\b|\\s|$)`, 'i')
   for (
     let i = messages.length - 1;
     i >= Math.max(0, messages.length - 3);
     i--
   ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'i')
for (
let i = messages.length - 1;
i >= Math.max(0, messages.length - 3);
i--
) {
const m = messages[i]
if (m?.type !== 'user') continue
const content = (m as any).message?.content
if (typeof content === 'string' && pattern.test(content)) return true
const pattern = new RegExp(`(?:^|\\s)@${escapeRegex(name)}(?=\\b|\\s|$)`, 'i')
for (
let i = messages.length - 1;
i >= Math.max(0, messages.length - 3);
i--
) {
const m = messages[i]
if (m?.type !== 'user') continue
const content = (m as any).message?.content
if (typeof content === 'string' && pattern.test(content)) return true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/buddy/companionReact.ts` around lines 68 - 77, The current detection
regex (pattern built with escapeRegex(name)) matches bare names like "Bob"
instead of explicit `@-mentions`; update the pattern construction in
companionReact.ts to require an '@' before the name (for example use a regex
like `(^|\\s)@${escapeRegex(name)}\\b` with case-insensitive flag) so the loop
over messages (messages, pattern, escapeRegex, name) only returns true when the
user was explicitly mentioned with '@'.

@claude-code-best claude-code-best merged commit 5a7d06f into claude-code-best:main Apr 3, 2026
2 checks passed
@amDosion amDosion deleted the refactor/buddy-align-official branch April 3, 2026 11:34
dfsfdfse pushed a commit to dfsfdfse/claude-code that referenced this pull request Apr 25, 2026
…e-best#82)

* refactor(buddy): align companion system with official CLI

## Summary

Reverse-engineered the official Claude Code CLI (v2.1.91) buddy/companion
system and aligned our implementation to match.

## Changes (7 files)

### Added
- `src/buddy/CompanionCard.tsx` (+109)
  JSX bordered card matching official vc8: rarity header, colored sprite,
  name, personality, 10-bar stats, last reaction in nested border.

- `src/buddy/companionReact.ts` (+156)
  Reaction system matching official ZUK+Dc8: 45s rate limiting, @-mention
  detection, transcript builder (12 msgs, 5000 chars), POST buddy_react API.

### Modified
- `src/commands/buddy/index.ts`
  type: local -> local-jsx, description/argumentHint/immediate/isHidden.

- `src/commands/buddy/buddy.ts`
  LocalCommandCall -> LocalJSXCommandCall signature (onDone, context, args).
  Removed mute/unmute/rehatch (official uses off/on only).
  /buddy show returns CompanionCard JSX instead of plain text.
  Pet auto-unmutes. companionMuted writes globalConfig (matches UI read source).

- `src/screens/REPL.tsx` (line 2808)
  globalThis.fireCompanionObserver -> import triggerCompanionReaction.

- `src/state/AppStateStore.ts` — comment fix.
- `src/types/global.d.ts` — removed fireCompanionObserver declaration.

## Data flow (verified consistent)
- companionMuted: saveGlobalConfig() <-> getGlobalConfig() (6 read sites)
- companionReaction: setAppState() <-> useAppState() (4 sites)
- companionPetAt: setAppState() <-> useAppState() (2 sites)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(buddy): address CodeRabbit review findings

- buddy.ts: return type Promise<null> → Promise<React.ReactNode>
  to match LocalJSXCommandCall interface (CompanionCard path returns
  ReactElement, not null).
- CompanionCard.tsx: clamp stat value to 0..100 before .repeat()
  to prevent negative count runtime error on out-of-range values.

Import path alias suggestions (src/ vs ../) dismissed — project
convention uses relative paths (verified against color.ts, help.ts).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(buddy): address second round CodeRabbit findings

- buddy.ts:105: remove unsafe (context as any).messages cast.
  ToolUseContext already declares messages: Message[] at Tool.ts:250,
  so context.messages is properly typed. Other commands (feedback,
  copy, export) access it the same way without cast.

- companionReact.ts:154: wrap resp.json() in try/catch for defensive
  JSON parsing. Malformed 200 responses now return null instead of
  propagating to the outer catch.

Rate-limit timing (set before API call) kept as-is — matches official
ZUK pattern: prevents retry-storm on transient failures.

src/ path alias suggestions dismissed — project uses relative paths.
Auto-unmute on /buddy view kept — matches official behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: unraid <local@unraid.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants