feat: restore buddy companion system (Phase 1)#49
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
移除 shims/、xiaohongshu/、vendor/、旧版 docs、多余 src 组件等 107 个文件 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…st display Adds a default built-in status line that shows model name, context usage with token counts, 5h session and 7d weekly rate limit progress bars with countdowns, and cumulative cost. No configuration needed — displays automatically when no external statusLine command is configured. Refactors StatusLine.tsx into routing component with ExternalStatusLine (existing) and BuiltinStatusLineWrapper (new). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove external command status line path, keep built-in only - Fix token data source: use currentUsage (same as contextUsedPct) - Mock Date.now() in tests for deterministic results - Switch imports to src/ aliases, remove dead KAIROS gate - Add status line screenshot Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
备份 buddy 系统反编译还原文档及相关改动,为后续 buddy 功能恢复做准备。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Supplement missing action layer for the buddy companion system: - Command dispatcher (buddy.ts): /buddy hatch, /buddy pet, /buddy off/on - Local soul generation: deterministic name + personality from species/stats - Card formatting: ASCII sprite, stats bars, rarity display - Observer (observer.ts): local template-based reactions for name mention, test failures, and errors; self-registers on globalThis for REPL compat - Enable BUDDY feature flag via --feature=BUDDY in dev.ts - Replace empty stub index.ts with proper Command metadata 29 tests, 75 assertions, 0 failures. No existing files modified except dev.ts (1 line) and the empty stub index.ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a local "Claude Code Buddy" feature: deterministic companion generation, persistence, UI/sprite assets, Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as /buddy Command
participant Companion as Companion Service
participant Config as Global Config
participant Observer
User->>CLI: "/buddy" (first call)
CLI->>Companion: rollWithSeed / build soul
Companion-->>CLI: bones + soul
CLI->>Config: save companion (hatchedAt, seed)
Config-->>CLI: persisted
CLI-->>User: rendered companion card
Note over Observer,Config: Observer registered on import
User->>Observer: fireCompanionObserver(messages, callback)
Observer->>Config: getCompanion(), getGlobalConfig()
Observer->>Observer: detectReactionReason(messages, name)
alt no companion or muted
Observer-->>User: undefined
else trigger matched
Observer->>Observer: buildLocalReaction(reason, seed)
Observer-->>User: reaction string
end
sequenceDiagram
participant User
participant CLI as /buddy Command
participant AppState
participant Config as Global Config
User->>CLI: "/buddy pet"
alt no companion
CLI-->>User: "no companion yet"
else companion exists
CLI->>AppState: setAppState({ companionPetAt: now })
AppState-->>CLI: updated
CLI-->>User: pet response (heart)
end
User->>CLI: "/buddy off"
CLI->>Config: set companionMuted = true
CLI-->>User: "Companion muted"
User->>CLI: "/buddy on"
CLI->>Config: set companionMuted = false
CLI-->>User: "Companion unmuted"
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (9)
src/components/StatusLine.tsx (1)
23-24: UnusedvimModeprop.The
vimModeprop is declared inPropsbut never used inStatusLineInner. If this is intentionally kept for API compatibility with existing call sites, consider adding a brief comment. Otherwise, it can be removed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/StatusLine.tsx` around lines 23 - 24, The Props type declares a vimMode field that is never used in the component; either remove vimMode from the Props type and any component signature/consumers (search for Props and StatusLineInner) or explicitly document why it is preserved for API compatibility by adding a short comment above the vimMode declaration and inside StatusLineInner noting it is intentionally unused; update the Props interface and StatusLineInner parameter list accordingly so the code and documentation remain consistent.docs/claude-buddy-system/src/buddy/buddyCommand.ts (2)
186-188:console.logdebug statement should be removed or converted to debug logging.This log statement will output to the terminal in any context where this code runs, potentially confusing users.
- const prompt = buildSoulPrompt(bones, inspirationSeed); - console.log('[buddy] Would call AI with prompt:', prompt); + const prompt = buildSoulPrompt(bones, inspirationSeed); + // TODO: Implement actual AI call to Haiku model🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/claude-buddy-system/src/buddy/buddyCommand.ts` around lines 186 - 188, Remove the stray console.log in the buddy command where the prompt is built (the lines using buildSoulPrompt and the local variable prompt); either delete the console.log('[buddy] Would call AI with prompt:', prompt) entirely or replace it with a proper debug logger call (e.g., use the existing module logger or a debug-level function) so that prompt content is not printed to stdout in normal runs.
197-203: Config stubs are no-ops — command won't persist any state.
_getConfig()returns an empty object and_updateConfig()does nothing. Commands like/buddy off,/buddy on, and/buddy petwill appear to succeed but won't persist any state changes. This should either throw to prevent accidental use or include a TODO comment marking these as requiring implementation.// Config 存根 (需要对接实际 config 系统) function _getConfig(): any { - return {}; + // TODO: Replace with getGlobalConfig() from src/utils/config.ts + throw new Error('_getConfig stub not implemented') } function _updateConfig(patch: any): void { - // 源码: S8((prev) => ({ ...prev, ...patch })) + // TODO: Replace with saveGlobalConfig() from src/utils/config.ts + throw new Error('_updateConfig stub not implemented') }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/claude-buddy-system/src/buddy/buddyCommand.ts` around lines 197 - 203, The config stubs _getConfig() and _updateConfig(patch) are no-ops which means commands like /buddy on/off/pet won’t persist state; update these functions so they either throw a clear runtime error to prevent accidental use or include a prominent TODO and warning log indicating they must be implemented against the real config system. Specifically, change _getConfig to throw (or return a documented sentinel) and change _updateConfig to throw (or log and throw) until a real config integration is provided; ensure both functions and their error messages reference the function names _getConfig and _updateConfig so callers can identify the missing implementation.docs/claude-buddy-system/src/buddy/buddyReaction.ts (1)
20-27: Unused constants may be premature abstractions.
REACTION_TIMEOUT,MAX_RETRIES, andMAX_REACTION_LENGTHare defined but not used in this file. If these are intended for future API implementation, consider adding a comment explaining their purpose, or removing them until needed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/claude-buddy-system/src/buddy/buddyReaction.ts` around lines 20 - 27, The constants REACTION_TIMEOUT, MAX_RETRIES, and MAX_REACTION_LENGTH are declared but unused; either remove them to avoid premature abstraction or document/prepare them for future use: if you intend to keep them, add a brief comment explaining their planned purpose and usage and/or export them (e.g., export const REACTION_TIMEOUT) so callers can use them; otherwise delete the declarations to clean up dead code. Ensure you update any tests or imports if you choose to export rather than remove.src/buddy/observer.ts (1)
138-161:asyncfunction performs no async operations — consider making synchronous.
fireCompanionObserveris declaredasyncbut contains noawaitstatements. The function is entirely synchronous. While this doesn't cause bugs (the returned Promise resolves immediately), it's misleading and adds unnecessary overhead.However, if Phase 2 plans to add remote API calls here, keeping it async is forward-compatible.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/buddy/observer.ts` around lines 138 - 161, The function fireCompanionObserver is declared async but contains no awaits; remove the async keyword and change its signature from "export async function fireCompanionObserver(...): Promise<void>" to a synchronous "export function fireCompanionObserver(...): void", then update any call sites that currently await or treat it as a Promise to call it synchronously (remove awaits or .then handlers); alternatively, if you prefer forward compatibility, keep the async but add a clear comment explaining it's intentionally async for future remote calls—ensure the function name (fireCompanionObserver), its parameters (messages, callback), and any callers are updated accordingly.src/buddy/__tests__/observer.test.ts (1)
10-17: Consider usingmock.module()for config isolation instead of direct mutation.While the current approach works because
NODE_ENV=testuses an in-memory config object, usingmock.module()would make tests more isolated and explicit about mocking behavior. This is a minor suggestion given the current approach works correctly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/buddy/__tests__/observer.test.ts` around lines 10 - 17, The test helper resetConfig directly mutates global config via saveGlobalConfig/getGlobalConfig (in resetConfig), which relies on in-memory behavior; instead make the config isolation explicit by using mock.module() to provide a mocked config module or factory for companion/companionMuted for the tests; replace the saveGlobalConfig(...) call in resetConfig with a mock.module() setup that returns the desired config shape (including companion and companionMuted undefined) or mock the module that exposes getGlobalConfig/saveGlobalConfig so each test gets an isolated fake config instance.docs/claude-buddy-system/src/buddy/companion.ts (1)
329-345: Silent failure in config read hides errors and returns stale data.The empty
catch {}block at line 342 swallows all errors including permission issues, JSON parse errors for corrupted files, or encoding problems. Consider at minimum logging the error for debugging purposes in this reference implementation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/claude-buddy-system/src/buddy/companion.ts` around lines 329 - 345, The catch-all in _readCompanionFromConfig silently swallows all exceptions (file IO, permissions, JSON parse, etc.); update the catch block to log the caught error (including the error object and the computed configPath) via a logger or console.error before returning undefined so failures are visible for debugging; ensure you reference _readCompanionFromConfig and the configPath/companion variables when adding the diagnostic message and keep the function's behavior of returning undefined on error.docs/claude-buddy-system/src/buddy/CompanionWidget.ts (1)
214-221: Hatch animation has only 3 frames — may appear abrupt.The
HATCH_EGG_FRAMESarray contains only 3 frames: intact egg, cracked egg, and star burst. The comment on lines 206-212 describes a 5-step sequence. Consider adding intermediate frames for smoother animation if this is used in production.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/claude-buddy-system/src/buddy/CompanionWidget.ts` around lines 214 - 221, HATCH_EGG_FRAMES currently has only 3 frames (intact, cracked, starburst) but comments expect a 5-step sequence; add two intermediate ASCII frames to smooth the hatch transition (e.g., a subtle hairline crack frame and a wider-crack/pre-starlight frame) between the existing intact and cracked and between cracked and starburst frames, updating each frame object in HATCH_EGG_FRAMES (offset and lines arrays) so the sequence visually progresses over five frames and preserves consistent art width and alignment.src/commands/buddy/buddy.ts (1)
6-20: Switch this file tosrc/alias imports.The new command is using deep relative traversals throughout, which violates the repo import convention and will get brittle the next time this folder moves. Please rewrite these to
src/...aliases instead.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 6 - 20, Replace the deep relative imports at the top of the file with the project's tsconfig alias imports (src/...) so the module resolver uses the src path mapping; update imports that currently bring in ToolUseContext, getCompanion, companionUserId, roll, renderSprite, RARITY_STARS, STAT_NAMES, Companion, CompanionBones, CompanionSoul, LocalJSXCommandContext, LocalJSXCommandOnDone, getGlobalConfig and saveGlobalConfig to import from their corresponding src/... paths instead of '../../...' style paths, ensuring the same exported names are used and that the tsconfig path mapping resolves correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/settings.local.json:
- Around line 4-51: This local settings file contains over-privileged commands
(e.g., "Bash(git push:*)", "Bash(ssh:*)", broad "Bash(curl:*)", "Bash(git
reset:*)", "Bash(git cherry-pick:*)") and machine-specific absolute paths;
remove or replace these entries with a minimal, least-privilege template (keep
only safe readonly and dev-help entries like "Read(...)", basic "Bash(git
status:*)", "Bash(git add:*)", "Bash(git commit:*)") and strip absolute paths
such as /mnt/user/appdata/...; also add .claude/settings.local.json to
.gitignore and commit a template file (e.g., .claude/settings.template.json)
with placeholder, non-destructive commands and comments instructing users to
keep the real file local.
In @.claude/skills/anthropic-skills:
- Line 1: The submodule update for .claude/skills/anthropic-skills references a
non-existent commit (98669c11ca63e9c81c11501e1437e5c47b556621) and conflicts
with the actual .agents/skills/anthropic-skills commit
(b9db0fb4e1bd2ad57078dd3557ff6e159387f8c7); either remove the
.claude/skills/anthropic-skills submodule change from this PR if it was included
accidentally, or update the submodule entry to point to a valid upstream commit
and update the PR description to justify why this submodule change is part of
the buddy system restoration; ensure you update the gitlink in the commit for
.claude/skills/anthropic-skills to a real commit hash and run git submodule
sync/update before amending the PR commit.
In `@AGENTS.md`:
- Around line 50-53: Update the docs text in AGENTS.md that states "feature()
always returns false" to note the exception for the BUDDY flag: mention that
while most runtime feature() flags are disabled by default, the BUDDY feature is
explicitly enabled in src/entrypoints/cli.tsx (reference the feature() behavior
and the BUDDY flag) so contributors are not misled; adjust wording to either
list the exception or rephrase to "most flags disabled except BUDDY" and include
a short pointer to src/entrypoints/cli.tsx for where this is implemented.
In `@docs/claude-buddy-system/README.md`:
- Around line 91-123: Update the README to clearly distinguish upstream/design
intents from the current Phase 1 local-only implementation by adding an explicit
note inside the "AI 灵魂生成", "宠物评论 (Reaction)", "系统提示注入", and "编译门控" sections that
this PR implements only a local/dev-phase behaviour (no production model-soul
generation, the POST /api/... endpoint is not wired/served here, and GrowthBook
flag is not enabled) and that upstream/phase-2 components remain design intents;
reference the gating symbols feature('BUDDY'), isBuddyLive(), and
tengu_amber_flint when stating which checks are simulated vs actually enforced
in Phase 1 so readers won’t assume runtime/remote behavior.
- Around line 13-107: The README contains multiple fenced code blocks without
language tags (e.g., the src/buddy tree, the deterministic generation snippet
`userId + "friend-2026-401" → FNV-1a...`, the hats ASCII block, and the API POST
snippet), triggering markdownlint MD040; add appropriate language identifiers to
each fence (for example `text` for the file tree and ASCII hats, `text` or
`bash` for the generation snippet, and `http`/`text` for the POST API example)
so all triple-backtick blocks are annotated; update the fences around the
symbols shown (the src/buddy tree block, the deterministic generation block, the
hats block, and the POST /api/organizations/{orgId}/claude_code/buddy_react
block).
In `@docs/claude-buddy-system/src/buddy/buddyReaction.ts`:
- Line 46: The ReactionReason union in buddyReaction.ts includes 'periodic' but
observer.ts lacks support; update observer.ts to match by adding 'periodic' to
its ReactionReason type and implement handling in detectReactionReason and
buildLocalReaction: modify detectReactionReason (function name) to return
'periodic' under the intended periodic-detection condition (e.g., when the
trigger is a scheduled/heartbeat check) and add a corresponding branch in
buildLocalReaction to construct the local reaction payload for 'periodic' cases
(ensuring any required fields are set). Ensure the symbol names ReactionReason,
detectReactionReason, and buildLocalReaction are updated consistently so types
and runtime behavior align.
In `@docs/claude-buddy-system/src/buddy/companion.ts`:
- Around line 348-367: _replace the direct fs read/write in
_saveCompanionToConfig with the shared config helpers: stop using fs/path and
instead call saveGlobalConfig (or saveConfigWithLock) to persist the updated
companion object so file locking, atomic writes, and auth-safety checks are
honored; ensure you run the wouldLoseAuthState guard (from src/utils/config.ts)
before applying changes to avoid wiping tokens, and update only the companion
field on the existing config object via the helper so concurrent processes and
auth state protections are preserved (use the same semantics as
saveGlobalConfig/saveConfigWithLock).
- Around line 211-217: companionUserId currently reads only
process.env.CLAUDE_USER_ID, causing unauthenticated users to all resolve to
'anon'; restore the original logic by reading the config fallback path instead:
call getGlobalConfig() and return config.oauthAccount?.accountUuid ??
config.userID ?? 'anon' (or equivalent accessors used elsewhere in this repo) so
companionUserId() uses oauthAccount.accountUuid first, then config.userID, then
the 'anon' default; update the function companionUserId and import or reference
getGlobalConfig/config as needed to match the original implementation.
In `@docs/claude-buddy-system/src/buddy/useBuddyNotification.ts`:
- Around line 13-25: The comments for isBuddyLive mention first-party auth and
headless checks but the function only enforces the date gate; update isBuddyLive
to either (a) enforce the missing checks by calling getAuthType() and returning
false when it !== 'firstParty', and calling isHeadless() and returning false
when true before applying the existing date logic, or (b) if those runtime
checks are not desired, remove the misleading comment lines so the doc matches
the implemented date-only behavior; locate and modify the isBuddyLive function
to implement one of these two options.
In `@src/buddy/observer.ts`:
- Around line 138-141: Update the declaration in global.d.ts to match the
implementation: change fireCompanionObserver's callback parameter type from
(reaction: unknown) => void to (reaction: string | undefined) => void and change
its return type from void (or Promise) to void; then remove the unnecessary
async modifier from the implementation function fireCompanionObserver in
src/buddy/observer.ts so the function is a plain synchronous function with
signature fireCompanionObserver(messages: unknown[], callback: (reaction: string
| undefined) => void): void.
In `@src/commands/buddy/buddy.ts`:
- Around line 116-140: formatCompanionCard currently always renders the "active"
footer and doesn't accept mute state; change its signature to accept a muted
boolean (e.g., formatCompanionCard(companion: Companion, muted = false)) and use
that flag to swap the two footer lines to muted-friendly copy when muted
(replace the active-presence lines ` ${companion.name} is here · it'll chime in
as you code` and ` say its name to get its take · /buddy pet · /buddy off` with
a muted variant). Update callers (where /buddy builds the card) to pass the
companion's mute flag, and apply the same change to the other companion
formatter referenced in the comment (the formatter around lines 188-195) so both
renderers consult the muted boolean; keep existing usages of STAT_NAMES,
renderSprite, companion.shiny and other symbols intact.
---
Nitpick comments:
In `@docs/claude-buddy-system/src/buddy/buddyCommand.ts`:
- Around line 186-188: Remove the stray console.log in the buddy command where
the prompt is built (the lines using buildSoulPrompt and the local variable
prompt); either delete the console.log('[buddy] Would call AI with prompt:',
prompt) entirely or replace it with a proper debug logger call (e.g., use the
existing module logger or a debug-level function) so that prompt content is not
printed to stdout in normal runs.
- Around line 197-203: The config stubs _getConfig() and _updateConfig(patch)
are no-ops which means commands like /buddy on/off/pet won’t persist state;
update these functions so they either throw a clear runtime error to prevent
accidental use or include a prominent TODO and warning log indicating they must
be implemented against the real config system. Specifically, change _getConfig
to throw (or return a documented sentinel) and change _updateConfig to throw (or
log and throw) until a real config integration is provided; ensure both
functions and their error messages reference the function names _getConfig and
_updateConfig so callers can identify the missing implementation.
In `@docs/claude-buddy-system/src/buddy/buddyReaction.ts`:
- Around line 20-27: The constants REACTION_TIMEOUT, MAX_RETRIES, and
MAX_REACTION_LENGTH are declared but unused; either remove them to avoid
premature abstraction or document/prepare them for future use: if you intend to
keep them, add a brief comment explaining their planned purpose and usage and/or
export them (e.g., export const REACTION_TIMEOUT) so callers can use them;
otherwise delete the declarations to clean up dead code. Ensure you update any
tests or imports if you choose to export rather than remove.
In `@docs/claude-buddy-system/src/buddy/companion.ts`:
- Around line 329-345: The catch-all in _readCompanionFromConfig silently
swallows all exceptions (file IO, permissions, JSON parse, etc.); update the
catch block to log the caught error (including the error object and the computed
configPath) via a logger or console.error before returning undefined so failures
are visible for debugging; ensure you reference _readCompanionFromConfig and the
configPath/companion variables when adding the diagnostic message and keep the
function's behavior of returning undefined on error.
In `@docs/claude-buddy-system/src/buddy/CompanionWidget.ts`:
- Around line 214-221: HATCH_EGG_FRAMES currently has only 3 frames (intact,
cracked, starburst) but comments expect a 5-step sequence; add two intermediate
ASCII frames to smooth the hatch transition (e.g., a subtle hairline crack frame
and a wider-crack/pre-starlight frame) between the existing intact and cracked
and between cracked and starburst frames, updating each frame object in
HATCH_EGG_FRAMES (offset and lines arrays) so the sequence visually progresses
over five frames and preserves consistent art width and alignment.
In `@src/buddy/__tests__/observer.test.ts`:
- Around line 10-17: The test helper resetConfig directly mutates global config
via saveGlobalConfig/getGlobalConfig (in resetConfig), which relies on in-memory
behavior; instead make the config isolation explicit by using mock.module() to
provide a mocked config module or factory for companion/companionMuted for the
tests; replace the saveGlobalConfig(...) call in resetConfig with a
mock.module() setup that returns the desired config shape (including companion
and companionMuted undefined) or mock the module that exposes
getGlobalConfig/saveGlobalConfig so each test gets an isolated fake config
instance.
In `@src/buddy/observer.ts`:
- Around line 138-161: The function fireCompanionObserver is declared async but
contains no awaits; remove the async keyword and change its signature from
"export async function fireCompanionObserver(...): Promise<void>" to a
synchronous "export function fireCompanionObserver(...): void", then update any
call sites that currently await or treat it as a Promise to call it
synchronously (remove awaits or .then handlers); alternatively, if you prefer
forward compatibility, keep the async but add a clear comment explaining it's
intentionally async for future remote calls—ensure the function name
(fireCompanionObserver), its parameters (messages, callback), and any callers
are updated accordingly.
In `@src/commands/buddy/buddy.ts`:
- Around line 6-20: Replace the deep relative imports at the top of the file
with the project's tsconfig alias imports (src/...) so the module resolver uses
the src path mapping; update imports that currently bring in ToolUseContext,
getCompanion, companionUserId, roll, renderSprite, RARITY_STARS, STAT_NAMES,
Companion, CompanionBones, CompanionSoul, LocalJSXCommandContext,
LocalJSXCommandOnDone, getGlobalConfig and saveGlobalConfig to import from their
corresponding src/... paths instead of '../../...' style paths, ensuring the
same exported names are used and that the tsconfig path mapping resolves
correctly.
In `@src/components/StatusLine.tsx`:
- Around line 23-24: The Props type declares a vimMode field that is never used
in the component; either remove vimMode from the Props type and any component
signature/consumers (search for Props and StatusLineInner) or explicitly
document why it is preserved for API compatibility by adding a short comment
above the vimMode declaration and inside StatusLineInner noting it is
intentionally unused; update the Props interface and StatusLineInner parameter
list accordingly so the code and documentation remain consistent.
🪄 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: 28461c38-0df6-47a0-923d-05bad01f4618
⛔ Files ignored due to path filters (1)
QQ20260402-192932.pngis excluded by!**/*.png
📒 Files selected for processing (23)
.agents/skills/anthropic-skills.claude/settings.local.json.claude/skills/anthropic-skills.githooks/pre-commitAGENTS.mddocs/claude-buddy-system/IMPLEMENTATION-PLAN.mddocs/claude-buddy-system/README.mddocs/claude-buddy-system/src/buddy/CompanionWidget.tsdocs/claude-buddy-system/src/buddy/buddyCommand.tsdocs/claude-buddy-system/src/buddy/buddyReaction.tsdocs/claude-buddy-system/src/buddy/companion.tsdocs/claude-buddy-system/src/buddy/sprites.tsdocs/claude-buddy-system/src/buddy/types.tsdocs/claude-buddy-system/src/buddy/useBuddyNotification.tsscripts/dev.tssrc/buddy/__tests__/observer.test.tssrc/buddy/observer.tssrc/commands/buddy/__tests__/buddy.test.tssrc/commands/buddy/buddy.tssrc/commands/buddy/index.tssrc/components/BuiltinStatusLine.tsxsrc/components/StatusLine.tsxsrc/components/__tests__/BuiltinStatusLine.test.ts
| "Bash(git rm:*)", | ||
| "Bash(git commit:*)", | ||
| "Bash(grep -ri buddy /mnt/user/appdata/claude-code/src/ --include=*.ts --include=*.tsx --include=*.js -l)", | ||
| "Bash(grep -ri buddy /mnt/user/appdata/claude-code/ --include=*.md --include=*.json -l)", | ||
| "Bash(git remote:*)", | ||
| "Bash(git fetch:*)", | ||
| "Bash(git merge:*)", | ||
| "Bash(grep -rn \"getSettings_DEPRECATED\\\\|getSettingsForSource\\\\|useSettings\\\\b\" /mnt/user/appdata/claude-code/src/hooks/useSettings.ts /mnt/user/appdata/claude-code/src/utils/settings/ --include=*.ts --include=*.tsx)", | ||
| "Bash(grep -rn extractQuotaStatusFromHeaders src/ --include=*.ts --include=*.tsx)", | ||
| "Bash(find src/components -name Usage* -o -name usage*)", | ||
| "Bash(find src:*)", | ||
| "Bash(grep -rn \"extractQuotaStatusFromHeaders\\\\|extractQuotaStatusFromError\" src/services/api/ --include=*.ts --include=*.tsx)", | ||
| "Bash(grep -rn \"getRawUtilization\\\\|checkQuotaStatus\" /mnt/user/appdata/claude-code/src/ --include=*.ts --include=*.tsx)", | ||
| "Bash(grep -n TerminalSize /mnt/user/appdata/claude-code/src/ink/components/TerminalSizeContext.ts*)", | ||
| "Bash(ls /mnt/user/appdata/claude-code/src/components/BuiltinStatusLine*)", | ||
| "Bash(ls /mnt/user/appdata/claude-code/src/components/Builtin*)", | ||
| "Bash(grep -r export.*formatCountdown src/components/)", | ||
| "Bash(bun test:*)", | ||
| "Bash(bun run:*)", | ||
| "Bash(npx biome:*)", | ||
| "Bash(git status:*)", | ||
| "Bash(git add:*)", | ||
| "Bash(gh --version)", | ||
| "Bash(gh auth:*)", | ||
| "Bash(curl -sL https://github.com/cli/cli/releases/latest/download/gh_2.74.1_linux_amd64.tar.gz -o /tmp/gh.tar.gz)", | ||
| "Bash(tar -xzf /tmp/gh.tar.gz -C /tmp)", | ||
| "Read(//tmp/**)", | ||
| "Bash(curl -sI https://github.com/cli/cli/releases/latest)", | ||
| "Bash(curl -sL \"https://github.com/cli/cli/releases/download/v2.89.0/gh_2.89.0_linux_amd64.tar.gz\" -o /tmp/gh.tar.gz)", | ||
| "Bash(cp /tmp/gh_2.89.0_linux_amd64/bin/gh /usr/local/bin/gh)", | ||
| "Bash(chmod +x /usr/local/bin/gh)", | ||
| "Bash(git checkout:*)", | ||
| "Bash(git cherry-pick:*)", | ||
| "Bash(git push:*)", | ||
| "Bash(gh pr:*)", | ||
| "Bash(gh api:*)", | ||
| "Bash(curl:*)", | ||
| "Bash(ssh:*)", | ||
| "Bash(git reset:*)", | ||
| "WebSearch", | ||
| "Bash(mkdir -p ~/.claude/skills)", | ||
| "Read(//root/.claude/**)", | ||
| "Bash(mkdir -p /mnt/user/appdata/claude-code/.claude/skills)", | ||
| "Bash(git clone:*)", | ||
| "Bash(python3 -m json.tool)", | ||
| "Bash(python3 -c \"import sys,json; data=json.load\\(sys.stdin\\); [print\\(f'''' {d[\"\"name\"\"]}/ \\({d[\"\"type\"\"]}\\)''''\\) for d in data]\")", | ||
| "Bash(python3 -c \"import sys,json; data=json.load\\(sys.stdin\\); [print\\(f'''' {d[\"\"name\"\"]} \\({d[\"\"type\"\"]}\\)''''\\) for d in data]\")", | ||
| "Bash(git pull:*)" |
There was a problem hiding this comment.
Do not commit an over-privileged local agent policy.
This checked-in local config grants destructive/network-capable permissions (git push, ssh, broad curl, reset/cherry-pick) and hardcodes machine-specific absolute paths. That is a security and portability risk if reused outside one workstation. Please keep this file local-only (gitignored) and commit a minimal least-privilege template instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/settings.local.json around lines 4 - 51, This local settings file
contains over-privileged commands (e.g., "Bash(git push:*)", "Bash(ssh:*)",
broad "Bash(curl:*)", "Bash(git reset:*)", "Bash(git cherry-pick:*)") and
machine-specific absolute paths; remove or replace these entries with a minimal,
least-privilege template (keep only safe readonly and dev-help entries like
"Read(...)", basic "Bash(git status:*)", "Bash(git add:*)", "Bash(git
commit:*)") and strip absolute paths such as /mnt/user/appdata/...; also add
.claude/settings.local.json to .gitignore and commit a template file (e.g.,
.claude/settings.template.json) with placeholder, non-destructive commands and
comments instructing users to keep the real file local.
| @@ -0,0 +1 @@ | |||
| Subproject commit 98669c11ca63e9c81c11501e1437e5c47b556621 | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify the submodule commit and check if it relates to buddy system
# Check if the submodule path exists and fetch the commit
if [ -d ".claude/skills/anthropic-skills" ]; then
cd ".claude/skills/anthropic-skills" || exit 1
# Verify commit exists
if git cat-file -e 98669c11ca63e9c81c11501e1437e5c47b556621 2>/dev/null; then
echo "✓ Commit exists"
# Show commit details
echo -e "\n=== Commit details ==="
git show --no-patch --format=fuller 98669c11ca63e9c81c11501e1437e5c47b556621
# Check for buddy-related changes in recent history
echo -e "\n=== Checking for buddy-related changes ==="
git log --oneline --grep="buddy" -i --all | head -10
# Show files changed in this commit
echo -e "\n=== Files changed in this commit ==="
git show --name-status --format="" 98669c11ca63e9c81c11501e1437e5c47b556621
else
echo "✗ Commit 98669c11ca63e9c81c11501e1437e5c47b556621 not found"
fi
else
echo "✗ Submodule directory not found"
fiRepository: claude-code-best/claude-code
Length of output: 131
🏁 Script executed:
#!/bin/bash
# Check if .agents/skills/anthropic-skills points to the same commit
if [ -d ".agents/skills/anthropic-skills" ]; then
cd ".agents/skills/anthropic-skills" || exit 1
current_commit=$(git rev-parse HEAD)
echo "Current commit in .agents/skills/anthropic-skills: $current_commit"
if [ "$current_commit" = "98669c11ca63e9c81c11501e1437e5c47b556621" ]; then
echo "✓ Commits match"
else
echo "✗ Commits differ"
fi
else
echo "Note: .agents/skills/anthropic-skills not found for comparison"
fiRepository: claude-code-best/claude-code
Length of output: 180
This submodule update points to a non-existent commit and does not align with the claimed upstream.
The commit 98669c11ca63e9c81c11501e1437e5c47b556621 does not exist in the .claude/skills/anthropic-skills submodule. Additionally, the .agents/skills/anthropic-skills submodule is at b9db0fb4e1bd2ad57078dd3557ff6e159387f8c7, contradicting the claim that the update matches the upstream.
This submodule change appears unrelated to the PR's stated objective (buddy system restoration) and should be either:
- Removed from this PR if it was accidentally included, or
- Corrected to point to a valid commit and justified in the PR description.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.claude/skills/anthropic-skills at line 1, The submodule update for
.claude/skills/anthropic-skills references a non-existent commit
(98669c11ca63e9c81c11501e1437e5c47b556621) and conflicts with the actual
.agents/skills/anthropic-skills commit
(b9db0fb4e1bd2ad57078dd3557ff6e159387f8c7); either remove the
.claude/skills/anthropic-skills submodule change from this PR if it was included
accidentally, or update the submodule entry to point to a valid upstream commit
and update the PR description to justify why this submodule change is part of
the buddy system restoration; ensure you update the gitlink in the commit for
.claude/skills/anthropic-skills to a real commit hash and run git submodule
sync/update before amending the PR commit.
| 1. **`src/entrypoints/cli.tsx`** — True entrypoint. Injects runtime polyfills at the top: | ||
| - `feature()` always returns `false` (all feature flags disabled, skipping unimplemented branches). | ||
| - `globalThis.MACRO` — simulates build-time macro injection (VERSION, BUILD_TIME, etc.). | ||
| - `BUILD_TARGET`, `BUILD_ENV`, `INTERFACE_TYPE` globals. |
There was a problem hiding this comment.
Update feature-flag docs to reflect current Buddy behavior.
Lines 50-53 say all feature() flags are disabled, but dev now explicitly enables BUDDY. Please clarify this exception (or update the wording) to avoid misleading contributors.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@AGENTS.md` around lines 50 - 53, Update the docs text in AGENTS.md that
states "feature() always returns false" to note the exception for the BUDDY
flag: mention that while most runtime feature() flags are disabled by default,
the BUDDY feature is explicitly enabled in src/entrypoints/cli.tsx (reference
the feature() behavior and the BUDDY flag) so contributors are not misled;
adjust wording to either list the exception or rephrase to "most flags disabled
except BUDDY" and include a short pointer to src/entrypoints/cli.tsx for where
this is implemented.
| ``` | ||
| src/buddy/ | ||
| ├── types.ts # 类型定义 & 常量 (物种/稀有度/属性/外观) | ||
| ├── companion.ts # 核心模块 (PRNG/哈希/骨架生成/Config读写) | ||
| ├── sprites.ts # ASCII 精灵动画 (18物种×3帧 + 帽子) | ||
| ├── useBuddyNotification.ts # 可用性检查 & 预告通知 | ||
| ├── buddyCommand.ts # /buddy 斜杠命令入口 | ||
| ├── buddyReaction.ts # 宠物自动评论 (API调用 + 触发逻辑) | ||
| └── CompanionWidget.ts # 终端渲染组件 (React + Ink) | ||
| ``` | ||
|
|
||
| ## 核心机制 | ||
|
|
||
| ### 1. 确定性宠物生成 | ||
|
|
||
| 每个用户的宠物是**确定性的** — 改配置也没用: | ||
|
|
||
| ``` | ||
| userId + "friend-2026-401" → FNV-1a hash → Mulberry32 PRNG → 逐项 roll | ||
| ``` | ||
|
|
||
| ### 2. 18 种物种 | ||
|
|
||
| | 物种 | 英文 | 特征 | | ||
| |------|------|------| | ||
| | 🦆 鸭子 | duck | `<(· )___` | | ||
| | 🪿 鹅 | goose | `(·>` | | ||
| | 🫧 果冻 | blob | 会膨胀缩小 | | ||
| | 🐱 猫 | cat | `=·ω·=` | | ||
| | 🐉 龙 | dragon | 会喷烟 | | ||
| | 🐙 章鱼 | octopus | 触手摆动 | | ||
| | 🦉 猫头鹰 | owl | 会眨眼 | | ||
| | 🐧 企鹅 | penguin | 会滑行 | | ||
| | 🐢 乌龟 | turtle | 龟壳变化 | | ||
| | 🐌 蜗牛 | snail | 留痕迹 | | ||
| | 👻 幽灵 | ghost | 飘浮波纹 | | ||
| | 🦎 六角恐龙 | axolotl | 鳃摆动 | | ||
| | 🦫 水豚 | capybara | 最大头 | | ||
| | 🌵 仙人掌 | cactus | 手臂变换 | | ||
| | 🤖 机器人 | robot | 天线闪烁 | | ||
| | 🐰 兔子 | rabbit | 耳朵抖动 | | ||
| | 🍄 蘑菇 | mushroom | 帽子变大 | | ||
| | 😺 胖猫 | chonk | 尾巴摇 | | ||
|
|
||
| ### 3. 稀有度系统 | ||
|
|
||
| | 稀有度 | 概率 | 星级 | 基础属性 | 帽子 | | ||
| |--------|------|------|----------|------| | ||
| | Common | 60% | ★ | 5 | 无 | | ||
| | Uncommon | 25% | ★★ | 15 | 有 | | ||
| | Rare | 10% | ★★★ | 25 | 有 | | ||
| | Epic | 4% | ★★★★ | 35 | 有 | | ||
| | Legendary | 1% | ★★★★★ | 50 | 有 | | ||
|
|
||
| **闪光 (Shiny)**: 任何稀有度都有 1% 概率 | ||
|
|
||
| ### 4. 外观系统 | ||
|
|
||
| **眼睛** (6种): `·` `✦` `×` `◉` `@` `°` | ||
|
|
||
| **帽子** (8种): | ||
| ``` | ||
| crown: \^^^/ 皇冠 | ||
| tophat: [___] 高帽 | ||
| propeller: -+- 螺旋桨帽 | ||
| halo: ( ) 光环 | ||
| wizard: /^\ 巫师帽 | ||
| beanie: (___) 毛线帽 | ||
| tinyduck: ,> 小鸭子 | ||
| ``` | ||
|
|
||
| ### 5. 属性系统 | ||
|
|
||
| 五项属性: **DEBUGGING** / **PATIENCE** / **CHAOS** / **WISDOM** / **SNARK** | ||
|
|
||
| - 随机选 2 个为突出属性 (主属性大幅加成,副属性略低) | ||
| - 其余为基础 + 随机 | ||
|
|
||
| ### 6. AI 灵魂生成 | ||
|
|
||
| 孵化时调用 Haiku 模型生成: | ||
| - **名字**: 一个词,≤12字符,略带荒诞 (如 Pith, Dusker, Crumb) | ||
| - **性格**: 一句话,影响它评论代码的方式 | ||
|
|
||
| ### 7. 宠物评论 (Reaction) | ||
|
|
||
| 宠物会在以下情况冒出气泡评论: | ||
| - **测试失败** — 检测到 "X failed" / "FAIL" 等 | ||
| - **代码错误** — 检测到 "error:" / "exception" / "traceback" | ||
| - **被叫名字** — 用户在消息中提到宠物名字 | ||
| - **周期性** — 每隔一段时间 | ||
|
|
||
| 评论通过 API 生成: | ||
| ``` | ||
| POST /api/organizations/{orgId}/claude_code/buddy_react |
There was a problem hiding this comment.
Add language tags to fenced code blocks (MD040).
Please annotate these fences (e.g., text, bash) so markdownlint warnings are resolved.
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 13-13: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 30-30: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 74-74: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 106-106: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/claude-buddy-system/README.md` around lines 13 - 107, The README
contains multiple fenced code blocks without language tags (e.g., the src/buddy
tree, the deterministic generation snippet `userId + "friend-2026-401" →
FNV-1a...`, the hats ASCII block, and the API POST snippet), triggering
markdownlint MD040; add appropriate language identifiers to each fence (for
example `text` for the file tree and ASCII hats, `text` or `bash` for the
generation snippet, and `http`/`text` for the POST API example) so all
triple-backtick blocks are annotated; update the fences around the symbols shown
(the src/buddy tree block, the deterministic generation block, the hats block,
and the POST /api/organizations/{orgId}/claude_code/buddy_react block).
| ### 6. AI 灵魂生成 | ||
|
|
||
| 孵化时调用 Haiku 模型生成: | ||
| - **名字**: 一个词,≤12字符,略带荒诞 (如 Pith, Dusker, Crumb) | ||
| - **性格**: 一句话,影响它评论代码的方式 | ||
|
|
||
| ### 7. 宠物评论 (Reaction) | ||
|
|
||
| 宠物会在以下情况冒出气泡评论: | ||
| - **测试失败** — 检测到 "X failed" / "FAIL" 等 | ||
| - **代码错误** — 检测到 "error:" / "exception" / "traceback" | ||
| - **被叫名字** — 用户在消息中提到宠物名字 | ||
| - **周期性** — 每隔一段时间 | ||
|
|
||
| 评论通过 API 生成: | ||
| ``` | ||
| POST /api/organizations/{orgId}/claude_code/buddy_react | ||
| ``` | ||
|
|
||
| ### 8. 系统提示注入 | ||
|
|
||
| 宠物激活后,会在 Claude 的系统提示中注入一段 `<system-reminder>`, | ||
| 告诉 Claude "有一个叫 {name} 的 {species} 坐在旁边, | ||
| 用户叫它名字时你要让开"。 | ||
|
|
||
| ## 编译门控 | ||
|
|
||
| Buddy 系统受三层门控保护: | ||
|
|
||
| 1. **编译开关**: `feature('BUDDY')` — 构建时决定代码是否包含 | ||
| 2. **运行时检查**: `isBuddyLive()` — firstParty + 日期 ≥ 2026-04-01 | ||
| 3. **远程标志**: `tengu_amber_flint` — GrowthBook A/B 测试 | ||
|
|
There was a problem hiding this comment.
Clarify “upstream design” vs “current Phase 1 behavior.”
These sections currently read as active runtime behavior (model soul generation, remote reaction API, GrowthBook gating), but this PR is local-only Phase 1. Add an explicit note to prevent expectation drift.
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 106-106: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/claude-buddy-system/README.md` around lines 91 - 123, Update the README
to clearly distinguish upstream/design intents from the current Phase 1
local-only implementation by adding an explicit note inside the "AI 灵魂生成", "宠物评论
(Reaction)", "系统提示注入", and "编译门控" sections that this PR implements only a
local/dev-phase behaviour (no production model-soul generation, the POST
/api/... endpoint is not wired/served here, and GrowthBook flag is not enabled)
and that upstream/phase-2 components remain design intents; reference the gating
symbols feature('BUDDY'), isBuddyLive(), and tengu_amber_flint when stating
which checks are simulated vs actually enforced in Phase 1 so readers won’t
assume runtime/remote behavior.
| function _saveCompanionToConfig(soul: CompanionSoul): void { | ||
| // 源码: saveGlobalConfig({ ...getGlobalConfig(), companion: soul }) | ||
| try { | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const configPath = path.join( | ||
| process.env.HOME || process.env.USERPROFILE || '', | ||
| '.claude', | ||
| '.claude.json', | ||
| ); | ||
| let config: any = {}; | ||
| try { | ||
| config = JSON.parse(fs.readFileSync(configPath, 'utf8')); | ||
| } catch {} | ||
| config.companion = soul; | ||
| fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); | ||
| } catch { | ||
| // silent fail | ||
| } | ||
| } |
There was a problem hiding this comment.
Raw fs.writeFileSync bypasses config locking and safety guards — risks data loss and auth token corruption.
This implementation:
- Has no file locking — concurrent writes from multiple processes can corrupt the file
- Bypasses
wouldLoseAuthStateguard (seesrc/utils/config.ts:1217-1223) that prevents wiping authentication tokens - Performs read-modify-write without atomicity — another process could write between read and write
The production code uses saveGlobalConfig() with saveConfigWithLock() which handles all these concerns.
function _saveCompanionToConfig(soul: CompanionSoul): void {
- // 源码: saveGlobalConfig({ ...getGlobalConfig(), companion: soul })
- try {
- const fs = require('fs');
- const path = require('path');
- const configPath = path.join(
- process.env.HOME || process.env.USERPROFILE || '',
- '.claude',
- '.claude.json',
- );
- let config: any = {};
- try {
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
- } catch {}
- config.companion = soul;
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
- } catch {
- // silent fail
- }
+ // Use the proper config API from src/utils/config.ts:
+ // saveGlobalConfig(current => ({ ...current, companion: soul }))
+ throw new Error('_saveCompanionToConfig: use saveGlobalConfig from src/utils/config.ts')
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/claude-buddy-system/src/buddy/companion.ts` around lines 348 - 367,
_replace the direct fs read/write in _saveCompanionToConfig with the shared
config helpers: stop using fs/path and instead call saveGlobalConfig (or
saveConfigWithLock) to persist the updated companion object so file locking,
atomic writes, and auth-safety checks are honored; ensure you run the
wouldLoseAuthState guard (from src/utils/config.ts) before applying changes to
avoid wiping tokens, and update only the companion field on the existing config
object via the helper so concurrent processes and auth state protections are
preserved (use the same semantics as saveGlobalConfig/saveConfigWithLock).
| * 条件: | ||
| * 1. 必须是 firstParty (OAuth 登录的 claude.ai 用户) | ||
| * 2. 不能是 headless 模式 | ||
| * 3. 日期限制: 2026 年 4 月 1 日之后 (April Fools 彩蛋,但保留至今) | ||
| * 源码: q.getFullYear() > 2026 || (q.getFullYear() === 2026 && q.getMonth() >= 3) | ||
| * 注意: getMonth() 是 0-based,3 = April | ||
| */ | ||
| export function isBuddyLive(): boolean { | ||
| // 源码: if (getAuthType() !== 'firstParty') return false; | ||
| // 源码: if (isHeadless()) return false; | ||
| const now = new Date(); | ||
| return now.getFullYear() > 2026 || (now.getFullYear() === 2026 && now.getMonth() >= 3); | ||
| } |
There was a problem hiding this comment.
Align isBuddyLive() comments with actual logic.
The docs say availability also depends on first-party auth/headless checks, but the function currently only enforces date gating. Either implement the missing checks or trim the comments so behavior is unambiguous.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/claude-buddy-system/src/buddy/useBuddyNotification.ts` around lines 13 -
25, The comments for isBuddyLive mention first-party auth and headless checks
but the function only enforces the date gate; update isBuddyLive to either (a)
enforce the missing checks by calling getAuthType() and returning false when it
!== 'firstParty', and calling isHeadless() and returning false when true before
applying the existing date logic, or (b) if those runtime checks are not
desired, remove the misleading comment lines so the doc matches the implemented
date-only behavior; locate and modify the isBuddyLive function to implement one
of these two options.
| export async function fireCompanionObserver( | ||
| messages: unknown[], | ||
| callback: (reaction: string | undefined) => void, | ||
| ): Promise<void> { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the global.d.ts declaration for fireCompanionObserver
rg -n 'fireCompanionObserver' --type=ts -A5 -B2Repository: claude-code-best/claude-code
Length of output: 7221
🏁 Script executed:
sed -n '138,162p' src/buddy/observer.tsRepository: claude-code-best/claude-code
Length of output: 645
Update global.d.ts to match implementation signature and remove unnecessary async modifier.
The implementation in src/buddy/observer.ts (lines 138-141) has type mismatches with its declaration in src/types/global.d.ts (lines 32-35):
- Callback parameter: implementation specifies
(reaction: string | undefined) => voidbut declaration uses(reaction: unknown) => void - Return type: implementation returns
Promise<void>but declaration specifiesvoid
Additionally, fireCompanionObserver is declared as async but contains no await statements—it's entirely synchronous and could be a regular function.
Update global.d.ts to:
declare function fireCompanionObserver(
messages: unknown[],
callback: (reaction: string | undefined) => void,
): voidAnd remove the async modifier from the implementation in observer.ts line 138.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/buddy/observer.ts` around lines 138 - 141, Update the declaration in
global.d.ts to match the implementation: change fireCompanionObserver's callback
parameter type from (reaction: unknown) => void to (reaction: string |
undefined) => void and change its return type from void (or Promise) to void;
then remove the unnecessary async modifier from the implementation function
fireCompanionObserver in src/buddy/observer.ts so the function is a plain
synchronous function with signature fireCompanionObserver(messages: unknown[],
callback: (reaction: string | undefined) => void): void.
| export function formatCompanionCard(companion: Companion): string { | ||
| const stars = RARITY_STARS[companion.rarity] | ||
| const sprite = renderSprite(companion, 0) | ||
| const shinyTag = companion.shiny ? ' ✨ SHINY' : '' | ||
|
|
||
| const lines = [ | ||
| '', | ||
| ...sprite, | ||
| '', | ||
| ` ${companion.name}`, | ||
| ` ${companion.species} · ${companion.rarity.toUpperCase()} ${stars}${shinyTag}`, | ||
| '', | ||
| ` ${companion.personality}`, | ||
| '', | ||
| ...STAT_NAMES.map(stat => { | ||
| const val = companion.stats[stat] | ||
| const filled = Math.floor(val / 10) | ||
| const bar = '█'.repeat(filled) + '░'.repeat(10 - filled) | ||
| return ` ${stat.padEnd(12)} ${bar} ${val}` | ||
| }), | ||
| '', | ||
| ` ${companion.name} is here · it'll chime in as you code`, | ||
| ` say its name to get its take · /buddy pet · /buddy off`, | ||
| ] | ||
| return lines.join('\n') |
There was a problem hiding this comment.
Muted companions still read as active in /buddy.
After /buddy off, the default branch still formats the existing card with no mute context, and the formatter always prints the active-presence footer. /buddy will therefore describe a muted companion as if it can still react. Pass the mute flag into the formatter and swap the footer/status copy when muted.
🛠️ Suggested fix
-export function formatCompanionCard(companion: Companion): string {
+export function formatCompanionCard(companion: Companion, muted = false): string {
const stars = RARITY_STARS[companion.rarity]
const sprite = renderSprite(companion, 0)
const shinyTag = companion.shiny ? ' ✨ SHINY' : ''
@@
- ` ${companion.name} is here · it'll chime in as you code`,
- ` say its name to get its take · /buddy pet · /buddy off`,
+ muted
+ ? ` ${companion.name} is muted · it won't chime in right now`
+ : ` ${companion.name} is here · it'll chime in as you code`,
+ muted
+ ? ' /buddy on to wake it back up · /buddy pet also unmutes'
+ : ` say its name to get its take · /buddy pet · /buddy off`,
]
return lines.join('\n')
}
@@
const existing = getCompanion()
if (existing) {
- onDone(formatCompanionCard(existing), { display: 'system' })
+ onDone(
+ formatCompanionCard(existing, Boolean(getGlobalConfig().companionMuted)),
+ { display: 'system' },
+ )
} else {Also applies to: 188-195
🤖 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 116 - 140, formatCompanionCard
currently always renders the "active" footer and doesn't accept mute state;
change its signature to accept a muted boolean (e.g.,
formatCompanionCard(companion: Companion, muted = false)) and use that flag to
swap the two footer lines to muted-friendly copy when muted (replace the
active-presence lines ` ${companion.name} is here · it'll chime in as you code`
and ` say its name to get its take · /buddy pet · /buddy off` with a muted
variant). Update callers (where /buddy builds the card) to pass the companion's
mute flag, and apply the same change to the other companion formatter referenced
in the comment (the formatter around lines 188-195) so both renderers consult
the muted boolean; keep existing usages of STAT_NAMES, renderSprite,
companion.shiny and other symbols intact.
- Adopt upstream dev.ts feature flag system (BUDDY + TRANSCRIPT_CLASSIFIER defaults, FEATURE_* env var support) - Adopt upstream buddy.ts (type: 'local', LocalCommandCall, generateSeed, rollWithSeed, hatch/rehatch/pet/mute/unmute) - Add /buddy off and /buddy on as aliases for mute/unmute (matches original decompiled source) - Keep observer.ts (local template-based reactions) — upstream has none - Update .gitignore: exclude .claude/settings.local.json, .claude/skills/, .agents/skills/, QQ*.png - Rewrite tests to match upstream LocalCommandCall signature 29 tests, 57 assertions, 0 failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The yolo-classifier-prompts txt files don't exist in this repo yet, so enabling TRANSCRIPT_CLASSIFIER at dev startup causes a module resolution error. Keep only BUDDY in defaults; TRANSCRIPT_CLASSIFIER can be enabled via FEATURE_TRANSCRIPT_CLASSIFIER=1 when the prompts are added. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@amDosion 请清理一下代码哈, 清理完成 @我合入 |
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
src/commands/buddy/buddy.ts (1)
90-102:⚠️ Potential issue | 🟡 MinorMuted companions are still rendered as active in
/buddycard output.The card footer remains “active” even when muted, so the status messaging is misleading.
🤖 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 90 - 102, The card footer always shows active commands; update the construction of the lines array so it reflects a muted companion by checking companion.muted (or equivalent flag) and altering the footer string accordingly. Locate the lines array creation in buddy.ts and change the last element (currently the 'Commands: /buddy ...' entry) to conditionally show a muted status (for example "Status: Muted" or "Muted — Commands disabled") when companion.muted is true, otherwise keep the existing commands text; ensure you reference companion.muted and preserve other entries like sprite, speciesLabel, shiny, stars, and renderStats.
🧹 Nitpick comments (1)
src/commands/buddy/__tests__/buddy.test.ts (1)
102-108: Add an assertion that/buddy petauto-unmutes.This test only checks hearts text; it should also verify
companionMutedflips tofalseafter petting.✅ Suggested test extension
test('/buddy pet with companion shows hearts', async () => { await call('hatch', {} as any) + saveGlobalConfig(c => ({ ...c, companionMuted: true })) const name = getGlobalConfig().companion!.name const result = await call('pet', {} as any) expect(result.value).toContain(name) expect(result.value).toContain('♥') + expect(getGlobalConfig().companionMuted).toBe(false) })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/buddy/__tests__/buddy.test.ts` around lines 102 - 108, The test for '/buddy pet with companion shows hearts' currently checks the pet text but not that petting auto-unmutes the companion; after calling call('pet', {} as any) add an assertion that getGlobalConfig().companionMuted is false (e.g. expect(getGlobalConfig().companionMuted).toBe(false)) so the test verifies the side‑effect of unmuting the companion alongside the existing checks for name and '♥'.
🤖 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/__tests__/buddy.test.ts`:
- Around line 2-3: Replace relative import paths in the test with the project's
tsconfig alias: change imports of getGlobalConfig and saveGlobalConfig from
'../../../utils/config.js' to 'src/utils/config' and change getCompanion from
'../../../buddy/companion.js' to 'src/buddy/companion'; update the import
specifiers in src/commands/buddy/__tests__/buddy.test.ts so the test uses the
src/... path aliases for getGlobalConfig, saveGlobalConfig and getCompanion.
In `@src/commands/buddy/buddy.ts`:
- Around line 1-13: Update the import statements at the top of this module to
use the tsconfig path alias (src/...) instead of relative paths; specifically
replace the current relative imports that bring in getCompanion, rollWithSeed,
generateSeed (from '../../buddy/companion.js'), StoredCompanion, RARITY_STARS,
STAT_NAMES (from '../../buddy/types.js'), renderSprite (from
'../../buddy/sprites.js'), getGlobalConfig, saveGlobalConfig (from
'../../utils/config.js') and LocalCommandCall (from '../../types/command.js')
with their corresponding src/... alias imports so the module consistently uses
the project's src path mapping.
- Around line 149-173: The pet branch updates companionPetAt but doesn't unmute
the buddy, so modify the AppState update in the /buddy pet handler (the
setAppState call inside the try block) to also clear companionMuted (set
companionMuted: false) when petting; keep the existing try/catch behavior for
non-interactive mode and ensure the same state key names (companionPetAt,
companionMuted) are used so getCompanion/renderSprite still work as expected.
- Around line 109-112: Update the hatch-tip text returned by the buddy command
so it references the correct reroll command; change the message string in the
returned object (the 'value' field in the response inside buddy.ts where it
returns type: 'text') to mention "/buddy rehatch" instead of "/buddy hatch
again" so users get the accurate instruction.
---
Duplicate comments:
In `@src/commands/buddy/buddy.ts`:
- Around line 90-102: The card footer always shows active commands; update the
construction of the lines array so it reflects a muted companion by checking
companion.muted (or equivalent flag) and altering the footer string accordingly.
Locate the lines array creation in buddy.ts and change the last element
(currently the 'Commands: /buddy ...' entry) to conditionally show a muted
status (for example "Status: Muted" or "Muted — Commands disabled") when
companion.muted is true, otherwise keep the existing commands text; ensure you
reference companion.muted and preserve other entries like sprite, speciesLabel,
shiny, stars, and renderStats.
---
Nitpick comments:
In `@src/commands/buddy/__tests__/buddy.test.ts`:
- Around line 102-108: The test for '/buddy pet with companion shows hearts'
currently checks the pet text but not that petting auto-unmutes the companion;
after calling call('pet', {} as any) add an assertion that
getGlobalConfig().companionMuted is false (e.g.
expect(getGlobalConfig().companionMuted).toBe(false)) so the test verifies the
side‑effect of unmuting the companion alongside the existing checks for name and
'♥'.
🪄 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: b32c5fd7-4cc7-4abf-bb38-6748c35a059c
📒 Files selected for processing (7)
.gitignorescripts/dev.tssrc/buddy/companion.tssrc/buddy/types.tssrc/commands/buddy/__tests__/buddy.test.tssrc/commands/buddy/buddy.tssrc/commands/buddy/index.ts
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
- src/commands/buddy/index.ts
- scripts/dev.ts
| import { getGlobalConfig, saveGlobalConfig } from '../../../utils/config.js' | ||
| import { getCompanion } from '../../../buddy/companion.js' |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Switch test imports to src/ aliases.
These relative imports should use the configured src/... alias to match project conventions.
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/__tests__/buddy.test.ts` around lines 2 - 3, Replace
relative import paths in the test with the project's tsconfig alias: change
imports of getGlobalConfig and saveGlobalConfig from '../../../utils/config.js'
to 'src/utils/config' and change getCompanion from '../../../buddy/companion.js'
to 'src/buddy/companion'; update the import specifiers in
src/commands/buddy/__tests__/buddy.test.ts so the test uses the src/... path
aliases for getGlobalConfig, saveGlobalConfig and getCompanion.
| import { | ||
| getCompanion, | ||
| rollWithSeed, | ||
| generateSeed, | ||
| } from '../../buddy/companion.js' | ||
| import { | ||
| type StoredCompanion, | ||
| RARITY_STARS, | ||
| STAT_NAMES, | ||
| } from '../../buddy/types.js' | ||
| import { renderSprite } from '../../buddy/sprites.js' | ||
| import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' | ||
| import type { LocalCommandCall } from '../../types/command.js' |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Use src/ alias imports in this command module.
The new file uses relative import paths; switch these to src/... alias imports for consistency with repo rules.
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 - 13, Update the import
statements at the top of this module to use the tsconfig path alias (src/...)
instead of relative paths; specifically replace the current relative imports
that bring in getCompanion, rollWithSeed, generateSeed (from
'../../buddy/companion.js'), StoredCompanion, RARITY_STARS, STAT_NAMES (from
'../../buddy/types.js'), renderSprite (from '../../buddy/sprites.js'),
getGlobalConfig, saveGlobalConfig (from '../../utils/config.js') and
LocalCommandCall (from '../../types/command.js') with their corresponding
src/... alias imports so the module consistently uses the project's src path
mapping.
| return { | ||
| type: 'text', | ||
| value: `You already have a companion! Use /buddy to see it.\n(Tip: /buddy hatch again will re-roll a new one.)`, | ||
| } |
There was a problem hiding this comment.
Hatch tip text is inaccurate.
The response says “/buddy hatch again will re-roll,” but rerolling is handled by /buddy rehatch.
✏️ Proposed text fix
- value: `You already have a companion! Use /buddy to see it.\n(Tip: /buddy hatch again will re-roll a new one.)`,
+ value: `You already have a companion! Use /buddy to see it.\n(Tip: use /buddy rehatch to roll a new one.)`,📝 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.
| return { | |
| type: 'text', | |
| value: `You already have a companion! Use /buddy to see it.\n(Tip: /buddy hatch again will re-roll a new one.)`, | |
| } | |
| return { | |
| type: 'text', | |
| value: `You already have a companion! Use /buddy to see it.\n(Tip: use /buddy rehatch to roll a new one.)`, | |
| } |
🤖 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 109 - 112, Update the hatch-tip
text returned by the buddy command so it references the correct reroll command;
change the message string in the returned object (the 'value' field in the
response inside buddy.ts where it returns type: 'text') to mention "/buddy
rehatch" instead of "/buddy hatch again" so users get the accurate instruction.
| if (sub === 'pet') { | ||
| const companion = getCompanion() | ||
| if (!companion) { | ||
| return { | ||
| type: 'text', | ||
| value: | ||
| "You don't have a companion yet! Use /buddy hatch to get one.", | ||
| } | ||
| } | ||
|
|
||
| try { | ||
| const { setAppState } = await import('../../state/AppStateStore.js') | ||
| setAppState(prev => ({ | ||
| ...prev, | ||
| companionPetAt: Date.now(), | ||
| })) | ||
| } catch { | ||
| // non-interactive mode — AppState not available | ||
| } | ||
|
|
||
| return { | ||
| type: 'text', | ||
| value: ` ${renderSprite(companion, 0).join('\n')}\n\n ${companion.name} purrs happily! ♥`, | ||
| } | ||
| } |
There was a problem hiding this comment.
/buddy pet does not auto-unmute the companion.
This branch updates companionPetAt but never clears companionMuted, so muted behavior remains active after petting.
🐛 Proposed fix
if (sub === 'pet') {
const companion = getCompanion()
if (!companion) {
@@
try {
const { setAppState } = await import('../../state/AppStateStore.js')
setAppState(prev => ({
...prev,
companionPetAt: Date.now(),
}))
} catch {
// non-interactive mode — AppState not available
}
+
+ // Petting should wake the companion up.
+ saveGlobalConfig(cfg => ({ ...cfg, companionMuted: false }))
return {
type: 'text',
value: ` ${renderSprite(companion, 0).join('\n')}\n\n ${companion.name} purrs happily! ♥`,
}
}📝 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.
| if (sub === 'pet') { | |
| const companion = getCompanion() | |
| if (!companion) { | |
| return { | |
| type: 'text', | |
| value: | |
| "You don't have a companion yet! Use /buddy hatch to get one.", | |
| } | |
| } | |
| try { | |
| const { setAppState } = await import('../../state/AppStateStore.js') | |
| setAppState(prev => ({ | |
| ...prev, | |
| companionPetAt: Date.now(), | |
| })) | |
| } catch { | |
| // non-interactive mode — AppState not available | |
| } | |
| return { | |
| type: 'text', | |
| value: ` ${renderSprite(companion, 0).join('\n')}\n\n ${companion.name} purrs happily! ♥`, | |
| } | |
| } | |
| if (sub === 'pet') { | |
| const companion = getCompanion() | |
| if (!companion) { | |
| return { | |
| type: 'text', | |
| value: | |
| "You don't have a companion yet! Use /buddy hatch to get one.", | |
| } | |
| } | |
| try { | |
| const { setAppState } = await import('../../state/AppStateStore.js') | |
| setAppState(prev => ({ | |
| ...prev, | |
| companionPetAt: Date.now(), | |
| })) | |
| } catch { | |
| // non-interactive mode — AppState not available | |
| } | |
| // Petting should wake the companion up. | |
| saveGlobalConfig(cfg => ({ ...cfg, companionMuted: false })) | |
| return { | |
| type: 'text', | |
| value: ` ${renderSprite(companion, 0).join('\n')}\n\n ${companion.name} purrs happily! ♥`, | |
| } | |
| } |
🤖 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 149 - 173, The pet branch updates
companionPetAt but doesn't unmute the buddy, so modify the AppState update in
the /buddy pet handler (the setAppState call inside the try block) to also clear
companionMuted (set companionMuted: false) when petting; keep the existing
try/catch behavior for non-interactive mode and ensure the same state key names
(companionPetAt, companionMuted) are used so getCompanion/renderSprite still
work as expected.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/dev.ts`:
- Around line 20-24: The envFeatures extraction only checks keys and ignores
values, so FEATURE_PROACTIVE=0 will still enable features; update the logic
around envFeatures (which reads process.env) to also check the environment
variable value (e.g., only accept "1", "true", or "yes" — case-insensitive)
before mapping the key to the feature name; modify the filter to use both key
and value (e.g., .filter(([k, v]) => k.startsWith("FEATURE_") && typeof v ===
"string" && ["1","true","yes"].includes(v.toLowerCase())) ) so only intended
feature flags are enabled.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| // Any env var matching FEATURE_<NAME>=1 will also enable that feature. | ||
| // e.g. FEATURE_PROACTIVE=1 bun run dev | ||
| const envFeatures = Object.entries(process.env) | ||
| .filter(([k]) => k.startsWith("FEATURE_")) | ||
| .map(([k]) => k.replace("FEATURE_", "")); |
There was a problem hiding this comment.
Environment variable value is not checked.
The comment states FEATURE_<NAME>=1 enables the feature, but the filter only checks the key prefix and ignores the value. Setting FEATURE_PROACTIVE=0 or FEATURE_PROACTIVE=false would still enable the feature.
🛠️ Proposed fix to respect the env var value
const envFeatures = Object.entries(process.env)
- .filter(([k]) => k.startsWith("FEATURE_"))
+ .filter(([k, v]) => k.startsWith("FEATURE_") && v === "1")
.map(([k]) => k.replace("FEATURE_", ""));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/dev.ts` around lines 20 - 24, The envFeatures extraction only checks
keys and ignores values, so FEATURE_PROACTIVE=0 will still enable features;
update the logic around envFeatures (which reads process.env) to also check the
environment variable value (e.g., only accept "1", "true", or "yes" —
case-insensitive) before mapping the key to the feature name; modify the filter
to use both key and value (e.g., .filter(([k, v]) => k.startsWith("FEATURE_") &&
typeof v === "string" && ["1","true","yes"].includes(v.toLowerCase())) ) so only
intended feature flags are enabled.
你直接拒绝吧,我看你那边的有一点优势,我们这里都是研究的官方cli的反向编译出来的,最大化保持官方一致, 你们有这个了,我就不弄了 |
|
@amDosion thanks |
Summary
feature('BUDDY')via--feature=BUDDYin dev.ts — single compile switch, zero scattered changessrc/commands/buddy/index.tswith properCommandmetadatabuddy.ts(command impl),observer.ts(reaction observer), 2 test filesWhat works after this PR
/buddy/buddy pet/buddy off/buddy onObserver triggers reactions on: user mentioning companion name, test failures, error output.
Design decisions
dev.ts(+1 line) and the empty stubindex.ts--feature=BUDDYis Bun's native feature flag mechanism, same as Anthropic's internal build systemglobalThisvia side-effect import, so REPL.tsx's barefireCompanionObserver()call works without import changesTest plan
/buddyhatch produces deterministic companion with sprite, stats, personality/buddy off→companionMuted: true,/buddy on→false/buddy petsetscompanionPetAt, auto-unmutes if mutedundefinedwhen muted or no companionbun run dev→/buddy→ sprite visible, reactions trigger🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
Chores