Skip to content

Conversation

@tegnike
Copy link
Owner

@tegnike tegnike commented Dec 24, 2025

Summary by CodeRabbit

  • 新機能

    • メモリ、プレゼンス、キオスク、アイドル、デモモードの設定と挙動を強化
    • ボイスエージェント(Realtime/音声)の導入ガイドとクイックスタートを追加
    • 顔検出モデルとプリセットを追加し、プレゼンス体験を向上
  • ドキュメント

    • 仕様テンプレート、設計原則、タスク生成・レビュー・検証ワークフロー、テスト実行/報告手順など多数追加
    • 日英の翻訳テキストを大幅拡張
  • テスト

    • コンポーネント/フック/統合の網羅的なテスト群を追加
  • その他

    • デモモード用設定と環境変数の更新

✏️ Tip: You can customize this high-level summary in your review settings.

- Introduced the `openai-voice-agents.skill` file for the new skill.
- Added comprehensive documentation in Markdown format, including `SKILL.md`, `build.md`, `index.md`, `quickstart.md`, and `transport.md`.
- Implemented a search tool script (`search_docs.py`) for full-text search across documentation files.
- Included usage examples and detailed instructions for setting up and using the voice agents.
…cognition hooks

- Added unit tests for `useAudioProcessing`, `useBrowserSpeechRecognition`, `useVoiceRecognition`, `useWhisperRecognition`, and other related hooks.
- Established error handling patterns and ensured consistent toast notifications for various error scenarios.
- Included tests for audio context initialization, media recorder behavior, and speech detection functionalities.
- Enhanced test coverage for edge cases and user interactions, ensuring robust functionality across different components.
- Introduced a new module for loading preset files from the `/public/presets/` directory.
- Implemented a custom hook `usePresetLoader` to manage preset loading during application initialization.
- Added unit tests for the preset loading logic, ensuring correct handling of file reading, error scenarios, and fallback mechanisms.
- Created integration tests to validate the interaction between the preset loader and the settings store.
- Added example preset files for testing purposes.
- Replaced specific model identifiers with simplified names for better clarity and maintainability.
- Updated the list to reflect the latest model naming conventions.
- Added unit tests for `useVoiceRecognition`, `useBrowserSpeechRecognition`, and `useWhisperRecognition` to ensure proper functionality and prevent infinite rendering loops.
- Introduced integration tests to validate the stability of the voice recognition hooks under various scenarios, including keyboard shortcuts and continuous listening modes.
- Enhanced memoization in the hooks to maintain stable references and improve performance.
- Verified that the hooks correctly handle user input and state changes without causing unnecessary re-renders.
- Introduced a new MemorySettings component for managing memory features, including enabling/disabling memory, setting similarity thresholds, and managing memory limits.
- Implemented memory storage using IndexedDB for persistent message storage and retrieval.
- Added API endpoints for embedding generation and memory file management, including restoration from local files.
- Enhanced chat handling to save user and assistant messages to memory, integrating memory context into chat prompts.
- Developed comprehensive tests for memory-related functionalities, ensuring reliability and performance across various scenarios.
- Updated `handleApiError` function to accept an optional error detail parameter, allowing for more informative error messages.
- Modified error handling in `getDifyChatResponseStream` and `getVercelAIChatResponseStream` to include detailed error information from API responses.
- Improved error response formatting in custom API handlers to return specific error messages instead of generic ones.
- Updated `MessageInput` component to toggle microphone icon based on listening mode.
- Enhanced `handleSendMessage` function to trim user input and stop listening before sending messages.
- Improved `useVoiceRecognition` hook to restart listening in continuous mode after stopping.
- Added additional checks for disabling the microphone button during processing states.
- Implemented PresenceDebugPreview component for camera feed and bounding box display.
- Created PresenceIndicator component to visually represent current detection state.
- Developed PresenceManager to manage detection lifecycle and integrate with existing components.
- Added PresenceSettings component for configuring detection options.
- Introduced presence types and error handling in presenceTypes.ts.
- Enhanced home store to include presence state and error management.
- Updated settings store to manage presence detection settings.
- Implemented usePresenceDetection hook for handling face detection logic.
- Integrated presence detection into the main application layout.
…components

- Add IdleManager component to manage idle mode and automatic speech.
- Create IdleSettings component for configuring idle mode settings.
- Introduce useIdleMode hook to handle idle state logic and speech triggering.
- Define idle types and constants in idleTypes.ts for better type management.
- Integrate idle mode settings into the global settings store.
- Update main settings index to include idle settings tab.
- Add integration tests for idle mode functionality.
- Refactor presence detection tests to improve readability.
- Adjust presenceDebugPreview component to ensure proper video reference typing.
- Added `useKioskMode` hook for managing kiosk mode state and input validation.
- Introduced `KioskOverlay` component for fullscreen and passcode dialog handling.
- Created `PasscodeDialog` for user authentication in kiosk mode.
- Developed `KioskSettings` component for configuring kiosk mode options.
- Integrated NG word filtering and input length restrictions in kiosk mode.
- Updated `Menu`, `MessageInput`, and `Settings` components to support kiosk mode features.
- Added guidance messages for kiosk mode users.
- Enhanced settings store to include kiosk mode configurations.
- Implemented hooks for handling fullscreen and long press events for the Escape key.
- Updated Tailwind CSS configuration for new animations.
- Implemented demo mode checks across various API endpoints to restrict functionality when enabled.
- Created utility functions for demo mode detection and error response generation.
- Added a custom hook to manage demo mode state in components.
- Introduced a DemoModeNotice component to inform users when features are disabled in demo mode.
- Updated existing components and settings to reflect demo mode status, including disabling buttons and adjusting UI elements.
- Added unit tests for demo mode functionality in relevant API and utility files.
@vercel
Copy link

vercel bot commented Dec 24, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
aituber-kit Error Error Jan 9, 2026 2:53pm

@claude
Copy link
Contributor

claude bot commented Dec 24, 2025

Claude finished @tegnike's task —— View job


PR Review: Feature/Fix Realtime API

Reviewed this large PR (100 files, 26K+ additions) focusing on code quality, bugs, performance, security, and test coverage.

Review Tasks

  • Analyze PR scope and key changes
  • Review new Kiro spec-driven development framework
  • Examine core feature implementations (memory, presence, kiosk, idle modes)
  • Check OpenAI voice agents integration
  • Review test coverage and quality
  • Assess security implications
  • Evaluate performance considerations
  • Provide constructive feedback summary

Review Summary

Positive Aspects

1. Comprehensive Test Coverage

  • Excellent: 30+ new test files with thorough coverage
  • Well-structured test suites using Jest & React Testing Library
  • Good use of mocking for external dependencies (face-api.js, stores)
  • Tests follow TDD approach with proper requirement traceability

2. Code Quality & Architecture

  • Strong TypeScript usage with explicit type definitions
  • Well-organized modular structure following feature-driven architecture
  • Good separation of concerns (services, stores, components, hooks)
  • Comprehensive error handling with typed error responses

3. Security Implementation

  • API key validation in embedding endpoint (embedding.ts:53-62)
  • Method validation for API endpoints
  • Demo mode restrictions for sensitive operations
  • Input sanitization and validation

⚠️ Areas Requiring Attention

1. Potential Security Concerns

  • 🚨 File System Access: memory-files.ts:34-55 - Direct file system operations without path traversal protection

    // Consider adding path validation to prevent directory traversal
    const safePath = path.normalize(filename).replace(/^(\.\.[\/\\])+/, '');
  • API Key Exposure: Multiple endpoints accept API keys in request body - consider security implications

  • Missing Rate Limiting: High-usage endpoints like /api/embedding lack rate limiting

2. Performance Considerations

  • Memory Usage: IndexedDB operations in memoryService.ts could be optimized for large datasets
  • Embedding Processing: Synchronous cosine similarity calculations may block UI for large vectors
  • Face Detection: usePresenceDetection.ts runs continuous detection - needs CPU usage optimization

3. Code Quality Issues

  • Missing Null Checks: In memoryService.ts:74-98, potential null reference in embedding operations
  • Error Handling: Some API endpoints (memory-files.ts:81-84) silently skip errors without proper logging
  • Type Safety: Usage of any in error handling (embedding.ts:82)

📋 Specific Recommendations

Security Improvements:

// In memory-files.ts, add path validation:
const safePath = path.resolve(logsDir, filename);
if (!safePath.startsWith(path.resolve(logsDir))) {
  return res.status(403).json({ message: 'Invalid file path' });
}

Performance Optimization:

// In memoryTypes.ts, optimize cosine similarity for large vectors:
export function cosineSimilarity(vectorA: number[], vectorB: number[]): number {
  // Add early exit for zero vectors
  // Consider using Web Workers for large calculations
}

Error Handling:

  • Replace any types with specific error interfaces
  • Add structured logging for debugging production issues
  • Implement circuit breaker pattern for external API calls

🔍 Framework Assessment: Kiro System

Strengths:

  • Comprehensive spec-driven development approach
  • Well-structured template system
  • Clear separation of steering vs. specifications
  • Good documentation standards

Concerns:

  • Large framework addition (1000+ lines) without gradual introduction
  • Missing migration documentation for existing codebase
  • Potential complexity overhead for smaller features

🎯 Final Assessment

Overall Quality: B+ (Good with improvements needed)

This PR demonstrates strong engineering practices with comprehensive testing and well-architected features. The memory/RAG system is particularly well-implemented with proper TypeScript usage and modular design.

Before Merging:

  1. ✅ Add path traversal protection to file operations
  2. ⚠️ Consider rate limiting for high-usage endpoints
  3. 🔄 Replace any types with specific interfaces
  4. 📊 Add performance monitoring for face detection
  5. 📝 Document Kiro framework migration strategy

The codebase shows professional development practices with good test coverage. Security concerns are moderate and addressable. Performance optimizations would benefit long-term scalability.


@coderabbitai
Copy link

coderabbitai bot commented Dec 24, 2025

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (15)
  • src/components/idleManager.tsx
  • src/components/presenceIndicator.tsx
  • src/components/settings/memorySettings.tsx
  • src/features/presets/usePresetLoader.ts
  • src/hooks/useAudioProcessing.ts
  • src/hooks/useBrowserSpeechRecognition.ts
  • src/hooks/useEscLongPress.ts
  • src/hooks/useFullscreen.ts
  • src/hooks/useIdleMode.ts
  • src/hooks/useKioskMode.ts
  • src/hooks/usePresenceDetection.ts
  • src/hooks/useRealtimeVoiceAPI.ts
  • src/hooks/useSilenceDetection.ts
  • src/hooks/useVoiceRecognition.ts
  • src/hooks/useWhisperRecognition.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

KiRo仕様駆動ワークフロー、設計ルール/テンプレート、Playwrightテストレポーター仕様、OpenAI Voice Agentsドキュメント、face-api プレゼンス検出アセット、環境設定変更、依存追加、および50+のユニット/統合テスト群を一括で追加しました。

Changes

Cohort / File(s) Change Summary
KiRo コマンド仕様
.claude/commands/kiro/spec-init.md, .claude/commands/kiro/spec-design.md, .claude/commands/kiro/spec-requirements.md, .claude/commands/kiro/spec-tasks.md, .claude/commands/kiro/spec-impl.md, .claude/commands/kiro/spec-status.md, .claude/commands/kiro/validate-*.md
仕様の初期化、要件生成、設計生成/検証、タスク生成、実装実行、ステータス報告などKiRo向け自動ワークフロー仕様を多数追加。出力形式・安全策・フォールバックを定義。
KiRo ステアリング & ルール・テンプレート
.kiro/settings/rules/*, .kiro/settings/templates/**
設計発見(full/light)、設計原則、設計レビュー、EARS、ギャップ分析、タスク生成ルール等のルール群と、specs/steering用テンプレートを追加。
Agents / テストレポーター / テスト実行ワークフロー
.claude/agents/playwright-reporter.md, .claude/agents/test-runner.md, .claude/commands/run-tests.md
Playwrightベースのレポーター仕様、テストランナーエージェント定義、テスト実行/再実行ワークフローを追加。
OpenAI Voice Agents スキル & ドキュメント
.claude/skills/openai-voice-agents/SKILL.md, .claude/skills/openai-voice-agents/docs/*, .claude/skills/openai-voice-agents/scripts/*
Voice Agents向けの詳細ドキュメント群(build/quickstart/transport等)とドキュメント検索スクリプトを追加。
設定・環境・プラグイン
.claude/settings.json, .env.example, .gitignore
Playwrightプラグイン有効化設定、NEXT_PUBLIC_DEMO_MODE 環境変数追加、.kiro/specs/*/reports を .gitignore に追加。
翻訳ファイル
locales/en/translation.json, locales/ja/translation.json
メモリ、プレゼンス、アイドル、キオスク、Live2D 等に関する多数の翻訳キーを追加(en/ja)。
依存関係・アセット
package.json, public/models/tiny_face_detector_model-weights_manifest.json, public/presets/*
依存に face-api.js, idb, fake-indexeddb を追加。Tiny Face Detector モデルマニフェストとプリセットを追加。
プロジェクト文書更新
CLAUDE.md
README を「AITuber Kit」向けに大幅更新(技術スタック、KiRoワークフロー等)。
テスト追加 — コンポーネント / 機能
src/__tests__/components/*, src/__tests__/features/*
多数のコンポーネント/UI設定/機能テストを追加(Memory, Kiosk, Idle, Presence 等)。
テスト追加 — フック / 統合
src/__tests__/hooks/*, src/__tests__/integration/*
フック単体テスト(useAudioProcessing 等)、統合テスト(無限ループ防止、キオスク統合、プレゼンス検出、音声認識機能など)を大量に追加。
API テスト更新
src/__tests__/pages/api/convertSlide.test.ts, src/__tests__/pages/api/delete-image.test.ts
APIハンドラのデモモードパスやエラーパスを検証するテストを追加/更新。
テスト期待値の微修正
src/__tests__/features/chat/*.test.ts
dify/vercel チャット関連テストの期待エラーメッセージに詳細文字列を追加(ストリーム/APIエラー表現の拡張)。

Sequence Diagram(s)

(ドキュメント・テンプレート追加と大規模テスト追加が主体で、複数コンポーネント間の新しい制御フロー追加は含まれないため図は省略します。)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning PRのタイトル「Feature/fix realtime api」は、変更セット全体の主な目的を明確に反映していません。実際の変更は、Kiro仕様駆動開発システム、Playwright報告書エージェント、OpenAI音声エージェントスキル、メモリ機能、プレゼンス検出、アイドルモード、キオスクモードなど、複数の大規模な機能追加が含まれており、Realtime APIはこれらの一部に過ぎません。 タイトルを変更セットの主要な内容をより正確に反映したものに修正してください。例えば「Add Kiro spec-driven system, AI agents, and feature suite (memory, presence, idle, kiosk)」のようにより包括的なタイトルが適切です。
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/fix-realtime-api

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

Copy link

@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: 9

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (16)
src/__tests__/hooks/useRealtimeVoiceAPI.test.ts-6-45 (1)

6-45: セットアップされたモックが未使用です

ストアと i18n のモックがセットアップされていますが、現在のテストでは使用されていません。これらは useRealtimeVoiceAPI フックをテストするためのものと思われますが、フック自体がテストされていないため未使用となっています。

将来のフックテスト実装時に使用される予定であれば問題ありませんが、現時点では不要なコードとなっています。

src/__tests__/hooks/useRealtimeVoiceAPI.test.ts-47-74 (1)

47-74: SpeechRecognition モックが未使用です

SpeechRecognition の包括的なモックがセットアップされていますが、現在のテストでは使用されていません。Line 79 で mockSpeechRecognition.lang がリセットされていますが、実際のテストではこのモックを検証していません。

将来のフックテスト実装時に使用される予定であれば問題ありませんが、現時点では準備だけで終わっています。

src/__tests__/hooks/useRealtimeVoiceAPI.test.ts-1-5 (1)

1-5: グローバルオブジェクトの復元処理が必要です

テストでwindow.SpeechRecognitionwindow.webkitSpeechRecognitionをモック化していますが、afterAll()フック内で元の値に戻す必要があります。これにより、テスト間の副作用を防ぐことができます。

 const MockSpeechRecognitionClass = jest.fn().mockImplementation(() => {
   return { ...mockSpeechRecognition }
 })

+const originalSpeechRecognition = window.SpeechRecognition
+const originalWebkitSpeechRecognition = window.webkitSpeechRecognition
+
 // Setup global SpeechRecognition
 Object.defineProperty(window, 'SpeechRecognition', {
   writable: true,
   value: MockSpeechRecognitionClass,
 })

 Object.defineProperty(window, 'webkitSpeechRecognition', {
   writable: true,
   value: MockSpeechRecognitionClass,
 })

 describe('useRealtimeVoiceAPI - 言語設定の動的反映', () => {
   beforeEach(() => {
     jest.clearAllMocks()
     mockSpeechRecognition.lang = ''
   })
+
+  afterAll(() => {
+    Object.defineProperty(window, 'SpeechRecognition', {
+      writable: true,
+      value: originalSpeechRecognition,
+    })
+    Object.defineProperty(window, 'webkitSpeechRecognition', {
+      writable: true,
+      value: originalWebkitSpeechRecognition,
+    })
+  })

Committable suggestion skipped: line range outside the PR's diff.

src/__tests__/hooks/errorHandling.test.ts-115-218 (1)

115-218: テストが実装フックの検証を行っていません。

Lines 115-218のテストは定数値とシミュレートされたパターン検証のみで、実装ファイル(useBrowserSpeechRecognition.tsuseSilenceDetection.tsuseWhisperRecognition.tsなど)が実際にこれらのエラーハンドリングパターンに従っているかを検証していません。

実装ファイルは正しいパターンに従っていますが(例:useBrowserSpeechRecognition.ts Line 166で'Microphone permission error:'useWhisperRecognition.ts Line 94で'Whisper transcription error:'を使用)、errorHandling.test.tsがこれを直接テストしていないため、今後の変更時に回帰を防ぐことができません。

推奨: renderHookを使用して、実装フックのエラー処理を直接テストするテストケースを追加してください。例えば、マイク権限エラー時に実際に'Microphone permission error:'がログされ、正しいタグを持つtoastが表示されることを検証してください。

.claude/skills/openai-voice-agents/docs/build.md-402-402 (1)

402-402: 誤字を修正してください。

Line 402で「playack」となっていますが、正しくは「playback」です。

🔎 修正案
-If you are using WebSocket, you will need to handle this yourself by stopping audio playack of whatever has been queued up to be played.
+If you are using WebSocket, you will need to handle this yourself by stopping audio playback of whatever has been queued up to be played.
src/__tests__/components/demoModeNotice.test.tsx-35-41 (1)

35-41: テストでfeatureKeyプロップの使用を検証してください。

このテスト(35-41行目)はfeatureKey="upload"を渡していますが、実際にこのプロップが使用されているかを検証していません。コンポーネントのコード(demoModeNotice.tsx)を確認すると、featureKeyは受け取られていますが、レンダリングで使用されていません。このテストは27-33行目のテストと同じ期待値を持つため、本質的に重複しています。

以下のいずれかを検討してください:

  1. コンポーネントがfeatureKeyを使用するように実装を修正する場合、テストでその使用を検証する
  2. featureKeyが使用されない場合、このテストケースと未使用のfeatureKeyプロップを削除する
src/__tests__/hooks/usePresetLoader.test.ts-19-20 (1)

19-20: global.fetchの復元が必要です。

テストファイルでグローバルオブジェクト(global.fetch)を上書きする場合は、afterAll()フック内で元の値に戻して、テスト間の副作用を防ぐべきです。

Based on learnings, テスト間の副作用を防ぐため、グローバルオブジェクトの復元が推奨されています。

🔎 提案する修正
 // fetch APIのモック
+const originalFetch = global.fetch
 const mockFetch = jest.fn()
 global.fetch = mockFetch
+
+afterAll(() => {
+  global.fetch = originalFetch
+})
src/__tests__/features/memory/memoryStoreSync.test.ts-29-30 (1)

29-30: global.fetchの復元が必要です。

global.fetchを上書きしていますが、テスト終了後に元の値に戻していません。テスト間の副作用を防ぐため、afterAll()フック内で元の値に戻すべきです。

Based on learnings, テストファイルでグローバルオブジェクトを上書きする場合は、元の値に戻すことが推奨されています。

🔎 提案する修正
 // Mock fetch for Embedding API
+const originalFetch = global.fetch
 const mockFetch = jest.fn()
 global.fetch = mockFetch

 describe('MemoryStoreSync', () => {
   const originalState = settingsStore.getState()
 
   beforeEach(async () => {
     // ... existing code ...
   })
 
   afterEach(async () => {
     // ... existing code ...
   })
+
+  afterAll(() => {
+    global.fetch = originalFetch
+  })
 
   // ... tests ...
 })
src/__tests__/hooks/useAudioProcessing.test.ts-24-74 (1)

24-74: グローバルオブジェクトの復元が必要です。

window.AudioContextwindow.webkitAudioContextwindow.MediaRecordernavigator.mediaDevicesなどのグローバルオブジェクトを上書きしていますが、テスト終了後に元の値に戻していません。テスト間の副作用を防ぐため、afterAll()フック内で元の値に戻すべきです。

Based on learnings, テストファイルでグローバルオブジェクトを上書きする場合は、元の値に戻すことが推奨されています。

🔎 提案する修正
+// Save original values
+const originalAudioContext = window.AudioContext
+const originalWebkitAudioContext = (window as any).webkitAudioContext
+const originalMediaRecorder = window.MediaRecorder
+const originalMediaDevices = navigator.mediaDevices
+
 // Mock AudioContext
 const mockAudioContextClose = jest.fn().mockResolvedValue(undefined)
 // ... rest of mocks ...

 describe('useAudioProcessing', () => {
   beforeEach(() => {
     jest.clearAllMocks()
     MockAudioContext.mockClear()
   })
+
+  afterAll(() => {
+    // Restore original values
+    Object.defineProperty(window, 'AudioContext', {
+      writable: true,
+      value: originalAudioContext,
+    })
+    Object.defineProperty(window, 'webkitAudioContext', {
+      writable: true,
+      value: originalWebkitAudioContext,
+    })
+    Object.defineProperty(window, 'MediaRecorder', {
+      writable: true,
+      value: originalMediaRecorder,
+    })
+    Object.defineProperty(navigator, 'mediaDevices', {
+      writable: true,
+      value: originalMediaDevices,
+    })
+  })

   // ... tests ...
 })

Committable suggestion skipped: line range outside the PR's diff.

src/__tests__/components/settings/kioskSettings.test.tsx-41-43 (1)

41-43: 型定義の不整合を確認

mockSettingsStoreMockedFunction<typeof settingsStore> として型付けされていますが、settingsStore は関数ではなく、getState/setState メソッドを持つオブジェクトとしてモックされています。この型定義は実際のモック構造と一致していない可能性があります。

🔎 型定義の修正案
-const mockSettingsStore = settingsStore as jest.MockedFunction<
-  typeof settingsStore
->
+const mockSettingsStore = settingsStore as jest.Mocked<typeof settingsStore>
.claude/commands/kiro/spec-requirements.md-107-109 (1)

107-109: ファイル末尾が不完全です。

spec-status.mdと同様に、このファイルもthinkという単語で終わっています。これがKiRoワークフローテンプレートの意図的なパターンであれば問題ありませんが、そうでなければ修正が必要です。

src/__tests__/features/memory/memoryService.test.ts-483-486 (1)

483-486: MemorySearchResultインターフェースを削除し、インポートに追加してください。

MemorySearchResultはすでに@/features/memory/memoryServiceからエクスポートされているため、ファイル末尾のローカル定義は不要です。既存のインポートにMemorySearchResultを追加してください。

.claude/commands/kiro/spec-status.md-95-97 (1)

95-97: ファイル末尾が不完全です。

ファイルがthinkという単語で終わっており、コードブロックが正しく閉じられていないか、内容が不完全なようです。これは意図的なプレースホルダーでしょうか?

🔎 修正案
 - Run with no argument or use wildcard
 - Shows all specs in `.kiro/specs/` with their status
-
-think

または、thinkがテンプレートの一部である場合は、その目的を明確にするコメントを追加してください。

CLAUDE.md-17-30 (1)

17-30: コードブロックに言語指定を追加

マークダウンのコードブロックには言語指定子を追加することで、適切なシンタックスハイライトが適用されます。

🔎 提案される修正
-```
+```plaintext
 src/
 ├── components/     # UIコンポーネント
 ├── features/       # 機能別モジュール
src/__tests__/hooks/useVoiceRecognition.test.ts-1039-1104 (1)

1039-1104: stopListening の呼び出し順序検証が機能していません。

Line 1086-1090でstopListeningSpyを作成していますが、このスパイはresult.current.stopListeningに割り当てられていません。そのため、イベントハンドラー内で実際に呼ばれるstopListeningはスパイではなく元の関数であり、callOrder配列には'stopListening'が記録されません。

実装(useVoiceRecognition.ts の handleKeyUp)は設計通りにawait stopListening()の後にonChatProcessStart()を呼び出していますが、このテストでは呼び出し順序を実際には検証できていません。Line 1103の最終アサーションも、stopListeningが定義されているかのみを確認しており、呼び出し順序を検証していません。

.claude/commands/kiro/spec-tasks.md-151-153 (1)

151-153: ファイル末尾の不完全なコードブロック。

ファイルが think と開始バッククォートで終了していますが、これは不完全なコードブロックまたはフォーマットエラーのように見えます。

🔎 修正の提案
 **Note**: The implementation phase will guide you through executing tasks with appropriate context and validation.
-
-think
-```

または意図的なブロックであれば閉じる必要があります:

 **Note**: The implementation phase will guide you through executing tasks with appropriate context and validation.
-
-think
-```
+```
🧹 Nitpick comments (48)
src/__tests__/hooks/useFullscreen.test.ts (2)

115-142: エラーハンドリングのテストカバレッジを追加することを推奨します。

基本的な動作は適切にテストされていますが、実装にはエラーハンドリング(try-catchとconsole.warn)が含まれています。requestFullscreenが失敗するケースのテストを追加することで、カバレッジがより包括的になります。

🔎 エラーケースのテスト例
+    it('should handle requestFullscreen errors gracefully', async () => {
+      const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation()
+      mockRequestFullscreen.mockRejectedValueOnce(new Error('User gesture required'))
+
+      const { result } = renderHook(() => useFullscreen())
+
+      await act(async () => {
+        await result.current.requestFullscreen()
+      })
+
+      expect(consoleWarnSpy).toHaveBeenCalledWith(
+        'Fullscreen request failed:',
+        expect.any(Error)
+      )
+      consoleWarnSpy.mockRestore()
+    })

144-165: exitFullscreenについてもエラーハンドリングのテストを追加することを推奨します。

基本動作は正しくテストされていますが、実装にはエラーハンドリングが含まれているため、exitFullscreenが失敗するケースもテストすることで完全性が向上します。

🔎 エラーケースのテスト例
+    it('should handle exitFullscreen errors gracefully', async () => {
+      const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation()
+      mockFullscreenElement = document.documentElement
+      mockExitFullscreen.mockRejectedValueOnce(new Error('Exit failed'))
+
+      const { result } = renderHook(() => useFullscreen())
+
+      await act(async () => {
+        await result.current.exitFullscreen()
+      })
+
+      expect(consoleWarnSpy).toHaveBeenCalledWith(
+        'Exit fullscreen failed:',
+        expect.any(Error)
+      )
+      consoleWarnSpy.mockRestore()
+    })
src/__tests__/components/settings/slideConvert.test.tsx (5)

38-52: セレクターパターンの型安全性を向上できます。

selector ? selector(state) : state のパターンは、セレクターが false0 などの falsy 値を返す場合に誤動作する可能性があります。

🔎 より安全な実装の提案
   default: jest.fn((selector) => {
     const state = {
       selectAIService: 'openai',
       selectLanguage: 'ja',
       selectAIModel: 'gpt-4o',
       enableMultiModal: true,
       multiModalMode: 'auto',
       customModel: '',
     }
-    return selector ? selector(state) : state
+    return typeof selector === 'function' ? selector(state) : state
   }),

85-97: ボタン要素のクエリ方法をより堅牢にできます。

getByTextclosest('button') の組み合わせは、DOM 構造の変更に脆弱です。getByRole を使用することで、セマンティックかつ保守性の高いテストになります。

🔎 推奨される実装
     it('should render file upload button enabled in normal mode', () => {
       render(<SlideConvert onFolderUpdate={jest.fn()} />)
-      const uploadButton = screen.getByText('ファイルを選択')
+      const uploadButton = screen.getByRole('button', { name: 'ファイルを選択' })
       expect(uploadButton).toBeInTheDocument()
-      expect(uploadButton.closest('button')).not.toBeDisabled()
+      expect(uploadButton).not.toBeDisabled()
     })

     it('should render convert button enabled in normal mode', () => {
       render(<SlideConvert onFolderUpdate={jest.fn()} />)
-      const convertButton = screen.getByText('変換')
+      const convertButton = screen.getByRole('button', { name: '変換' })
       expect(convertButton).toBeInTheDocument()
-      expect(convertButton.closest('button')).not.toBeDisabled()
+      expect(convertButton).not.toBeDisabled()
     })

112-122: ボタン要素のクエリ方法を統一して堅牢にしてください。

通常モードのテストと同様に、getByRole を使用することで、DOM 構造の変更に強いテストになります。

🔎 推奨される実装
     it('should render file upload button disabled in demo mode', () => {
       render(<SlideConvert onFolderUpdate={jest.fn()} />)
-      const uploadButton = screen.getByText('ファイルを選択')
-      expect(uploadButton.closest('button')).toBeDisabled()
+      const uploadButton = screen.getByRole('button', { name: 'ファイルを選択' })
+      expect(uploadButton).toBeDisabled()
     })

     it('should render convert button disabled in demo mode', () => {
       render(<SlideConvert onFolderUpdate={jest.fn()} />)
-      const convertButton = screen.getByText('変換')
-      expect(convertButton.closest('button')).toBeDisabled()
+      const convertButton = screen.getByRole('button', { name: '変換' })
+      expect(convertButton).toBeDisabled()
     })

131-135: スタイルのテスト方法を実装詳細に依存しないようにできます。

CSS クラス名 opacity-50 を直接テストすると、スタイリング実装の変更に脆弱になります。data 属性や計算されたスタイルをテストする方が保守性が高くなります。

🔎 推奨される実装案

オプション 1: data 属性を使用(コンポーネント側の変更が必要)

コンポーネントに data-demo-disabled などの属性を追加し、それをテスト:

     it('should apply grayed out style to form in demo mode', () => {
       const { container } = render(<SlideConvert onFolderUpdate={jest.fn()} />)
       const form = container.querySelector('form')
-      expect(form?.parentElement).toHaveClass('opacity-50')
+      expect(form?.parentElement).toHaveAttribute('data-demo-disabled', 'true')
     })

オプション 2: 計算されたスタイルをテスト(より統合的)

     it('should apply grayed out style to form in demo mode', () => {
       const { container } = render(<SlideConvert onFolderUpdate={jest.fn()} />)
       const form = container.querySelector('form')
-      expect(form?.parentElement).toHaveClass('opacity-50')
+      const wrapper = form?.parentElement
+      expect(wrapper).toHaveStyle({ opacity: expect.stringMatching(/0\.5|0\.50/) })
     })

73-137: テストカバレッジを拡張できます。

現在のテストは基本的な UI 状態を適切にカバーしていますが、以下のシナリオを追加することでより堅牢になります:

  • ファイルアップロードのインタラクション
  • 変換ボタンクリック時の動作
  • モデル選択の変更
  • ローディング状態の表示
  • エラーハンドリング
  • アクセシビリティ(ARIA 属性、キーボードナビゲーション)

これらのテストシナリオの実装をお手伝いしましょうか?新しい issue を作成することもできます。

src/__tests__/hooks/useRealtimeVoiceAPI.test.ts (1)

82-102: テストカバレッジの拡張を推奨

getVoiceLanguageCode 関数は 16 種類の言語をサポートしていますが(src/utils/voiceLanguage.ts を参照)、現在のテストは 4 言語とフォールバックのみをカバーしています。

以下の言語のテストが不足しています:vi, fr, es, pt, de, ru, it, ar, hi, pl, th

テストカバレッジを向上させるため、これらの言語マッピングのテストケースを追加することを検討してください。

🔎 提案: 不足している言語のテストケースを追加
     it('不明な言語はja-JPにフォールバックする', () => {
       expect(getVoiceLanguageCode('unknown')).toBe('ja-JP')
     })
+
+    it('viを渡すとvi-VNを返す', () => {
+      expect(getVoiceLanguageCode('vi')).toBe('vi-VN')
+    })
+
+    it('frを渡すとfr-FRを返す', () => {
+      expect(getVoiceLanguageCode('fr')).toBe('fr-FR')
+    })
+
+    it('esを渡すとes-ESを返す', () => {
+      expect(getVoiceLanguageCode('es')).toBe('es-ES')
+    })
+
+    // 他の言語も同様に追加
   })
src/__tests__/hooks/errorHandling.test.ts (1)

87-113: 定数を実装コードと共有することを検討してください。

これらの定数はテスト専用に定義されていますが、実装コードでも同じ値を使用する必要があります。定数を共通モジュールに移動し、テストと実装の両方で使用することで、一貫性を保証できます。

💡 共通定数モジュールの例

新しいファイル src/features/constants/errorHandling.ts を作成:

export const ERROR_TOAST_TAGS = {
  MICROPHONE_PERMISSION_ERROR: 'microphone-permission-error',
  MICROPHONE_PERMISSION_ERROR_FIREFOX: 'microphone-permission-error-firefox',
  SPEECH_RECOGNITION_NOT_SUPPORTED: 'speech-recognition-not-supported',
  SPEECH_RECOGNITION_ERROR: 'speech-recognition-error',
  NO_SPEECH_DETECTED: 'no-speech-detected',
  NO_SPEECH_DETECTED_LONG_SILENCE: 'no-speech-detected-long-silence',
  WHISPER_ERROR: 'whisper-error',
} as const

export const ERROR_TOAST_MESSAGE_KEYS = {
  MICROPHONE_PERMISSION_DENIED: 'Toasts.MicrophonePermissionDenied',
  FIREFOX_NOT_SUPPORTED: 'Toasts.FirefoxNotSupported',
  SPEECH_RECOGNITION_NOT_SUPPORTED: 'Toasts.SpeechRecognitionNotSupported',
  SPEECH_RECOGNITION_ERROR: 'Toasts.SpeechRecognitionError',
  NO_SPEECH_DETECTED: 'Toasts.NoSpeechDetected',
  WHISPER_ERROR: 'Toasts.WhisperError',
} as const

このファイルをテストと実装の両方でインポートして使用します。

.claude/skills/openai-voice-agents/docs/index.md (1)

17-17: 見出しレベルの段階的な増加を確認してください

見出しがh3###)として定義されていますが、その前にh2レベルの見出しが存在しません。Markdownの構造上、見出しレベルは1段階ずつ増加させる必要があります。

🔎 推奨される修正
-### Key features
+## Key features
.kiro/settings/templates/steering/tech.md (1)

51-51: Markdownの書式を改善できます

イタリック体(強調)が使用されていますが、これは注釈として意図されているようです。Markdownリンターがこれを見出しの誤用として検出しています。

🔎 推奨される修正

より明確な注釈形式に変更することをお勧めします:

----
-
-_Document standards and patterns, not every dependency_
+---
+> **注記:** 標準とパターンを文書化し、すべての依存関係を列挙しないでください。

または、プレーンテキストとして:

----
-
-_Document standards and patterns, not every dependency_
+---
+注: 標準とパターンを文書化し、すべての依存関係を列挙しないでください。
package.json (1)

49-49: face-api.js@0.22.2 のセキュリティ確認済み

face-api.js@0.22.2 は最新バージョンであり、公開されているセキュリティアドバイザリ(Snyk、Socket等)でも既知の脆弱性は報告されていません。ただし、このパッケージは約5年間更新されておらず事実上メンテナンスが停止している状態です。将来の互換性について懸念がある場合は、積極的にメンテナンスされている @vladmandic/face-api フォークの使用を検討してください。

.claude/skills/openai-voice-agents/docs/quickstart.md (1)

15-155: コードブロックに言語指定子を追加することを検討してください。

ドキュメント全体で複数のコードブロックに言語指定子が欠けています(lines 15, 23, 35, 63, 79, 95, 103, 153)。シンタックスハイライトのために言語を指定することで、可読性が向上します。

例:

```bash
npm install @openai/agents zod@3
```

ただし、これはOpenAIの公式ドキュメントから取得されたコンテンツであるため、アップストリームで修正される可能性があります。

.claude/agents/playwright-reporter.md (1)

73-73: マークダウンのエスケープ記法に問題があります

mcp**chrome-devtools**\* の記法が意図した表記と異なっている可能性があります。ヘッダーの tools フィールド(行4)では mcp__chrome-devtools__* となっていますが、ここではアスタリスクのエスケープにより太字として解釈されています。

🔎 修正案
-- **mcp**chrome-devtools**\***: ブラウザ操作(スクリーンショット、ページ操作など)
+- **mcp__chrome-devtools__\***: ブラウザ操作(スクリーンショット、ページ操作など)
src/__tests__/components/settings/voice.test.tsx (1)

228-236: クラウドTTSオプションの存在確認が不十分な可能性

if (option) の条件により、オプションが存在しない場合にテストがサイレントにパスしてしまいます。これにより、期待するオプションが実際には存在しないケースを検出できません。

🔎 修正案
      cloudOptions.forEach((optionValue) => {
        const option = Array.from(options).find(
          (opt) => opt.value === optionValue
        )
-        if (option) {
-          expect(option).not.toBeDisabled()
-        }
+        // オプションが存在することを確認し、無効化されていないことを検証
+        expect(option).toBeDefined()
+        expect(option).not.toBeDisabled()
      })
src/__tests__/features/stores/settingsRealtimeApi.test.ts (2)

68-79: テスト内容とテスト名が一致していません

テスト名は「setState後も」と記載されていますが、実際には初期状態のみを検証しており、setState を呼び出した後の動作は検証されていません。コメントでUI保護について言及していますが、テストとして不完全です。

🔎 修正案(テスト名をより正確に)
-    it('should keep realtimeAPIMode false in demo mode even after setState', () => {
-      // Note: In demo mode, UI buttons are disabled, so setState shouldn't be called
-      // This test verifies the initial state is correctly set to false
+    it('should initialize realtimeAPIMode to false in demo mode', () => {
       const settingsStore = require('@/features/stores/settings').default

       // Initial state should be false in demo mode
       expect(settingsStore.getState().realtimeAPIMode).toBe(false)
-
-      // Even if we try to set it (which shouldn't happen in practice due to disabled UI),
-      // the UI level protection prevents this. But the store itself doesn't block setState.
-      // The protection is at the initialization and rehydration level.
     })

81-86: 同様にテスト名と内容の不一致があります

このテストも「setState後も」と記載されていますが、初期状態の検証のみです。

🔎 修正案
-    it('should keep audioMode false in demo mode even after setState', () => {
+    it('should initialize audioMode to false in demo mode', () => {
       const settingsStore = require('@/features/stores/settings').default

       // Initial state should be false in demo mode
       expect(settingsStore.getState().audioMode).toBe(false)
     })
.kiro/settings/templates/steering-custom/authentication.md (1)

20-25: コードブロックに言語指定がありません

静的解析ツール(markdownlint)により、フェンスコードブロックに言語指定がないことが報告されています。ドキュメントの一貫性のため、言語を指定するか text を使用することを推奨します。

🔎 修正案
-```
+```text
 1) User proves identity (credentials or provider)
 2) Server verifies and issues token/session
 3) Client sends token per request
 4) Server verifies token and proceeds
</details>

</blockquote></details>
<details>
<summary>.kiro/settings/templates/steering-custom/deployment.md (1)</summary><blockquote>

`19-21`: **フェンスドコードブロックに言語指定の追加を検討してください。**

静的解析ツールがこのコードブロックに言語指定がないことを指摘しています。フロー図として `text` または `plaintext` を指定することで、リンターの警告を解消できます。


<details>
<summary>🔎 修正案</summary>

```diff
-```
+```text
 Code → Test → Build → Scan → Deploy (staged) → Verify
-```
+```
src/__tests__/components/settings/modelProvider/OpenAIConfig.test.tsx (1)

152-161: セレクターの脆弱性: インデックスベースのボタン選択は壊れやすいです。

buttons[0] でRealtime APIトグルを取得していますが、UIの変更でボタンの順序が変わると失敗する可能性があります。より堅牢なセレクターの使用を検討してください。

🔎 改善案
     it('should allow toggling Realtime API mode in normal mode', () => {
       render(<OpenAIConfig {...defaultProps} />)
-      const buttons = screen.getAllByRole('button')
-      const realtimeToggle = buttons[0] // First OFF button is Realtime API toggle
+      const realtimeSection = screen
+        .getByText('Realtime APIモード')
+        .closest('div')?.parentElement
+      const realtimeToggle = realtimeSection?.querySelector('button')

-      fireEvent.click(realtimeToggle)
+      fireEvent.click(realtimeToggle!)
       // Check that setState was called (mock would be called)
       const settingsStore = require('@/features/stores/settings').default
       expect(settingsStore.setState).toHaveBeenCalled()
     })
src/__tests__/features/memory/memoryStore.test.ts (1)

8-13: structuredClone ポリフィルの制限についてコメントを追加することを検討してください。

JSON.parse(JSON.stringify(obj)) による実装は、Dateオブジェクト、undefined、関数、循環参照などを正しく処理できません。MemoryRecord はシリアライズ可能なデータのみを含むため、このテストでは問題ありませんが、制限事項をコメントで明記すると将来の保守性が向上します。

🔎 改善案
 // Polyfill structuredClone for fake-indexeddb in Jest environment
+// Note: This simplified polyfill only handles JSON-serializable data.
+// It doesn't support Date objects, undefined, functions, or circular references.
 if (typeof globalThis.structuredClone === 'undefined') {
   globalThis.structuredClone = <T>(obj: T): T => {
     return JSON.parse(JSON.stringify(obj))
   }
 }
.kiro/settings/rules/tasks-parallel-analysis.md (1)

1-39: LGTM!並列タスク分析ルールが包括的に文書化されています。

並列実行可能なタスクを識別するための基準、マーキング規則、品質チェックリストが明確に定義されています。

オプションの改善提案:9〜14行目の基準リストで、4つの項目すべてが「**No」で始まっています。これは技術文書としては許容範囲ですが、可読性をさらに向上させたい場合は、一部を言い換えることを検討してください(例:「Lacks data dependency」、「Touches no conflicting files」など)。

src/__tests__/features/kiosk/guidanceMessage.test.tsx (1)

79-93: スタイリングテストが機能していますが、より具体的な検証を検討してください。

テストは正しく動作していますが、87〜92行目のフォントサイズテストで正規表現/text-(2xl|3xl|4xl)/を使用しています。コンポーネントコードを見ると、text-3xlが具体的に使用されています。

より厳密な検証を行いたい場合は、以下を検討してください:

🔎 より具体的なアサーション案
-    it('applies large font size', () => {
+    it('applies text-3xl font size', () => {
       render(<GuidanceMessage message="話しかけてね!" visible={true} />)
 
       const element = screen.getByTestId('guidance-message')
-      expect(element.className).toMatch(/text-(2xl|3xl|4xl)/)
+      expect(element).toHaveClass('text-3xl')
     })

現在の実装でも機能しますが、より具体的なアサーションはリグレッションをより確実に検出できます。

src/__tests__/hooks/useDemoMode.test.ts (1)

40-47: メモ化の検証方法を明確化することを推奨します。

現在のテストは返されたオブジェクト全体の参照を比較していますが、useDemoModeuseMemoを使用してisDemoModeをメモ化しています(オブジェクト自体ではありません)。

テストの意図をより明確にするため、isDemoModeの値の安定性を検証することを検討してください。

🔎 テストの意図を明確化する改善案
  it('should memoize the result', () => {
    process.env.NEXT_PUBLIC_DEMO_MODE = 'true'
    const { result, rerender } = renderHook(() => useDemoMode())
-   const firstResult = result.current
+   const firstIsDemoMode = result.current.isDemoMode

    rerender()
-   expect(result.current).toEqual(firstResult)
+   expect(result.current.isDemoMode).toBe(firstIsDemoMode)
+   expect(result.current.isDemoMode).toBe(true)
  })
.claude/skills/openai-voice-agents/SKILL.md (1)

34-39: コードブロックに言語指定を追加してください。

Markdownのコードブロックに言語を指定することで、構文ハイライトとツールの互換性が向上します。

🔎 修正案
-```
+```markdown
 [Answer based on documentation]

 **Source:** [source_url]
 **Fetched:** [fetched_at]
-```
+```
src/__tests__/hooks/useWhisperRecognition.test.ts (1)

79-129: テストの重複を統合することを推奨します。

Lines 79-129の3つのテストは、いずれも同じパターン(再レンダリング後のハンドラ参照の安定性)を検証しています。

テストの保守性を向上させるため、これらを1つの統合テストにまとめることを検討してください。

🔎 統合案
- describe('Requirement 7.1: processWhisperRecognition関数がuseCallbackでラップされている', () => {
-   it('processWhisperRecognitionがstopListeningの依存配列に含まれること', () => {
+ describe('Requirement 7.1: ハンドラ関数の参照安定性', () => {
+   it('すべてのハンドラ関数が再レンダリング後も同じ参照を保持すること', () => {
      const { result, rerender } = renderHook(() =>
        useWhisperRecognition(mockOnChatProcessStart)
      )

      const stopListeningFirst = result.current.stopListening
+     const startListeningFirst = result.current.startListening
+     const toggleListeningFirst = result.current.toggleListening

      rerender()

      const stopListeningSecond = result.current.stopListening
+     const startListeningSecond = result.current.startListening
+     const toggleListeningSecond = result.current.toggleListening

      expect(stopListeningFirst).toBe(stopListeningSecond)
+     expect(startListeningFirst).toBe(startListeningSecond)
+     expect(toggleListeningFirst).toBe(toggleListeningSecond)
    })
-
-   it('startListeningの参照が安定していること', () => { ... })
-   it('toggleListeningの参照が安定していること', () => { ... })
  })
.kiro/settings/rules/design-discovery-full.md (1)

1-113: 設計ディスカバリープロセスのドキュメントが包括的です。

設計調査の手順が論理的に構成されており、要件分析から技術調査、リスク評価までカバーしています。

静的解析ツールが指摘しているように、11行目などでボールドテキスト(**Map Requirements to Technical Needs**)が見出しの代わりに使用されています。一貫性のため、以下のようにMarkdownの見出し記法(###)を使用することを検討してください:

🔎 オプショナルな改善提案
-**Map Requirements to Technical Needs**
+### Map Requirements to Technical Needs

同様のパターンが20行目、30行目などにも存在します。

.kiro/settings/templates/specs/tasks.md (1)

1-23: タスクフォーマットテンプレートが明確です。

実装タスクの構造化されたフォーマット(メジャータスクのみ、メジャー+サブタスク構造)が提供されており、並列実行マーカーやテストカバレッジの表記方法も説明されています。

静的解析ツールが指摘しているように、21行目のコードスパン内にスペースが含まれています:

🔎 オプショナルな改善提案
-> **Parallel marker**: Append ` (P)` only to tasks that can be executed in parallel. Omit the marker when running in `--sequential` mode.
+> **Parallel marker**: Append `(P)` only to tasks that can be executed in parallel. Omit the marker when running in `--sequential` mode.
src/__tests__/features/memory/memoryContextBuilder.test.ts (2)

23-33: ヘルパー関数の重複を解消してください。

createMemoryRecord が2箇所で定義されています(Lines 23-33と184-194)。テストファイル全体で共有できるようにトップレベルで1つ定義し、各 describe ブロック内で再利用することを推奨します。

🔎 提案: ヘルパー関数をトップレベルに移動
+// 全テストで共有するヘルパー関数
+const createMemoryRecord = (
+  overrides: Partial<MemoryRecord> = {}
+): MemoryRecord => ({
+  id: 'test-id',
+  role: 'user',
+  content: 'テストメッセージ',
+  embedding: [0.1, 0.2, 0.3],
+  timestamp: '2025-01-15T14:30:00Z',
+  sessionId: 'session-1',
+  ...overrides,
+})
+
 describe('MemoryContextBuilder', () => {
   let builder: MemoryContextBuilder

   beforeEach(() => {
     builder = new MemoryContextBuilder()
   })

   describe('buildContext', () => {
-    const createMemoryRecord = (
-      overrides: Partial<MemoryRecord> = {}
-    ): MemoryRecord => ({
-      id: 'test-id',
-      role: 'user',
-      content: 'テストメッセージ',
-      embedding: [0.1, 0.2, 0.3],
-      timestamp: '2025-01-15T14:30:00Z',
-      sessionId: 'session-1',
-      ...overrides,
-    })

     it('should return empty string for empty memories array (Req 4.4)', () => {
       // ...
     })
   })

   describe('token limit enforcement (Req 4.3, 4.5)', () => {
-    const createMemoryRecord = (
-      overrides: Partial<MemoryRecord> = {}
-    ): MemoryRecord => ({
-      id: 'test-id',
-      role: 'user',
-      content: 'テストメッセージ',
-      embedding: [0.1, 0.2, 0.3],
-      timestamp: '2025-01-15T14:30:00Z',
-      sessionId: 'session-1',
-      ...overrides,
-    })

     it('should truncate memories when exceeding maxTokens', () => {
       // ...
     })
   })
 })

Also applies to: 184-194


161-167: トークン推定のテストをより具体的にすることを検討してください。

現在のテストは結果が0より大きいか、範囲内かのみをチェックしています。トークン推定ロジックが正しく動作していることをより明確に検証するため、少なくとも1つのテストケースで期待されるトークン数の具体的な値をアサートすることを推奨します。

例: 英語の場合、約4文字で1トークンなので、25文字の場合は約6-7トークンになることを検証。

src/__tests__/components/presenceSettings.test.tsx (2)

119-125: ボタンの特定方法を改善してください。

getAllByText('StatusOff')[0] を使用してトグルボタンを特定していますが、これは配列のインデックスに依存しており、UI構造の変更に対して脆弱です。

より堅牢なアプローチとして、data-testid 属性を使用するか、より具体的なセレクタ(例: getByRole('button', { name: /presence detection/i }))を使用することを推奨します。


196-204: ボタンの特定方法の一貫性を保ってください。

Line 119と同様に、getAllByText('StatusOff')[1] を使用してデバッグモードトグルを特定しています。UI要素の順序に依存せず、より明示的な方法で要素を特定することを推奨します。

src/__tests__/hooks/usePresetLoader.test.ts (1)

215-260: 不要な動的インポートを削除してください。

getPresetWithFallbackは既にファイル上部(Line 13)でインポートされているため、各テストケース内での動的インポート(await import(...))は不要です。これによりテストの実行速度が低下し、コードの可読性も下がります。

🔎 提案する修正
+import { getPresetWithFallback } from '@/features/presets/presetLoader'
+
 describe('getPresetWithFallback', () => {
   it('コンテンツが存在する場合はそれを返す', async () => {
-    const { getPresetWithFallback } = await import(
-      '@/features/presets/presetLoader'
-    )
-
     const result = getPresetWithFallback('カスタムプロンプト', undefined)
 
     expect(result).toBe('カスタムプロンプト')
   })
 
   it('コンテンツがnullの場合は環境変数を使用', async () => {
-    const { getPresetWithFallback } = await import(
-      '@/features/presets/presetLoader'
-    )
-
     const result = getPresetWithFallback(null, '環境変数のプロンプト')
 
     expect(result).toBe('環境変数のプロンプト')
   })
 
   // 他のテストケースも同様に修正
 })
.claude/commands/kiro/steering.md (1)

96-105: コードブロックに言語識別子を追加してください。

出力例のコードブロックに言語識別子が指定されていません。Markdown linterの警告を解消し、シンタックスハイライトを有効にするため、言語識別子(例:textまたはmarkdown)を追加することを推奨します。

🔎 提案する修正
 ### Bootstrap:
 
-```
+```text
 ✅ Steering Created
 
 ## Generated:
 - product.md: [Brief description]
 - tech.md: [Key stack]
 - structure.md: [Organization]
 
 Review and approve as Source of Truth.
 ```
 
 ### Sync:
 
-```
+```text
 ✅ Steering Updated
 
 ## Changes:
 - tech.md: React 18 → 19
 - structure.md: Added API pattern
 
 ## Code Drift:
 - Components not following import conventions
 
 ## Recommendations:
 - Consider api-standards.md
 ```

Also applies to: 109-121

src/__tests__/hooks/useSilenceDetection.test.ts (1)

107-153: resolveStopListeningの初期化パターンについて

テスト3.2でresolveStopListeningletで宣言されていますが、Promiseが作成される前にアクセスされる可能性があります。現在の実装では問題ありませんが、より安全なパターンとして明示的な型アサーションまたは初期化を検討してください。

🔎 より安全な初期化パターンの提案
-    it('3.2: stopListeningFnが実行中でも追加の呼び出しがブロックされる', async () => {
-      let resolveStopListening: () => void
+    it('3.2: stopListeningFnが実行中でも追加の呼び出しがブロックされる', async () => {
+      let resolveStopListening: (() => void) | undefined
       const mockStopListening = jest.fn().mockImplementation(() => {
         return new Promise<void>((resolve) => {
           resolveStopListening = resolve
         })
       })
.claude/commands/kiro/steering-custom.md (1)

87-105: コードブロックに言語指定を追加してください。

markdownlintが指摘しているように、フェンスドコードブロックには言語を指定することが推奨されます。出力例のブロックにはtextまたはmarkdownを指定してください。

🔎 修正案
-```
+```text
 ✅ Custom Steering Created
 
 ## Created:
src/__tests__/hooks/usePresenceDetection.test.ts (2)

673-740: スキップされたテストについて

クールダウン中の再検知抑制テストがスキップされており、TODOコメントでsetIntervalのコールバック更新タイミングの問題が説明されています。実際の動作ではuseEffectでインターバルが再作成されるため正常に機能するとのことです。

このスキップされたテストを追跡するために、Issueを作成することをお勧めします。

このテストの問題を追跡するためのIssueを作成しますか?


262-275: videoRefの設定パターンをヘルパー関数に抽出することを検討

videoRef.current = mockVideoElementの設定パターンがテスト全体で繰り返されています。可読性とメンテナンス性を向上させるために、ヘルパー関数への抽出を検討してください。

🔎 ヘルパー関数の提案
// テストファイルの先頭に追加
const setVideoRef = (
  result: { current: { videoRef: React.RefObject<HTMLVideoElement | null> } }
) => {
  ;(result.current.videoRef as React.MutableRefObject<HTMLVideoElement | null>).current = mockVideoElement
}

// 使用例
setVideoRef(result)
src/__tests__/features/memory/memoryService.test.ts (1)

454-479: パフォーマンステストのしきい値について

要件3.5では100ms以内の検索完了を求めていますが、テストでは500msのバッファを許容しています。APIコールはモックされているため、実際の処理時間のみを測定しています。

テスト環境のばらつきを考慮しつつも、より厳密なしきい値(例:200ms)を設定することで、パフォーマンス回帰をより早期に検出できる可能性があります。

.claude/skills/openai-voice-agents/scripts/search_docs.py (3)

16-16: 未使用のインポート: datetime

datetimeモジュールがインポートされていますが、コード内で使用されていません。削除してください。

🔎 修正案
 from pathlib import Path
 from typing import List, Dict, Tuple, Optional
-from datetime import datetime

159-160: 例外処理をより具体的にすることを検討

Ruffが指摘しているように、広範なExceptionのキャッチは具体性に欠けます。ファイル読み込み操作で発生する可能性のある具体的な例外をキャッチすることを検討してください。

🔎 修正案
-        except Exception as e:
+        except (OSError, IOError, UnicodeDecodeError) as e:
             print(f"Error reading {file_path}: {e}", file=sys.stderr)

50-54: シンプルなYAMLパーサーの制限事項について

現在の実装はkey: value形式の単純なYAMLのみを処理します。以下のケースは正しく処理されません:

  • 複数行の値
  • コロンを含む引用符付き文字列(例: title: "Hello: World"
  • 配列やネストされた構造

これが意図的な制限であれば、docstringに注記を追加することをお勧めします。より堅牢な処理が必要な場合は、pyyamlライブラリの使用を検討してください。

.claude/commands/kiro/validate-design.md (1)

94-94: 箇条書きの書式を修正

箇条書きの項目として正しく認識されるよう、ハイフンの前に改行を追加してください。

🔎 提案される修正
-- Review feedback and apply changes if needed - Run `/kiro:spec-tasks $1` to generate implementation tasks
+- Review feedback and apply changes if needed
+- Run `/kiro:spec-tasks $1` to generate implementation tasks
.claude/commands/kiro/spec-design.md (1)

117-121: 文章の可読性を改善

3つの連続した文が "For" で始まっているため、文章構造を変えることで可読性が向上します。

🔎 提案される修正
-- **Type Safety**:
-  - Enforce strong typing aligned with the project's technology stack.
-  - For statically typed languages, define explicit types/interfaces and avoid unsafe casts.
-  - For TypeScript, never use `any`; prefer precise types and generics.
-  - For dynamically typed languages, provide type hints/annotations where available (e.g., Python type hints) and validate inputs at boundaries.
-  - Document public interfaces and contracts clearly to ensure cross-component type safety.
+- **Type Safety**:
+  - Enforce strong typing aligned with the project's technology stack.
+  - In statically typed languages, define explicit types/interfaces and avoid unsafe casts.
+  - TypeScript code must never use `any`; prefer precise types and generics.
+  - Dynamically typed languages should provide type hints/annotations where available (e.g., Python type hints) and validate inputs at boundaries.
+  - Document public interfaces and contracts clearly to ensure cross-component type safety.
.claude/commands/kiro/validate-gap.md (1)

16-16: 文法を修正

"needs identified" は非標準的な表現です。"needs to be identified" に修正することで、より標準的な英語表現になります。

🔎 提案される修正
-  - Technical research needs identified for design phase
+  - Technical research needs to be identified for design phase
src/__tests__/hooks/useIdleMode.test.ts (1)

162-174: タイマークリーンアップのテストが間接的です。

このテストはエラーが発生しないことを確認していますが、タイマーが実際にクリアされたことを直接検証していません。現状でも機能しますが、より強固な検証が望ましい場合があります。

🔎 より明示的な検証の提案
   describe('useEffect cleanupでタイマークリア', () => {
     it('should cleanup timer on unmount', () => {
-      const { unmount } = renderHook(() => useIdleMode({}))
+      const { result, unmount } = renderHook(() => useIdleMode({}))
+      const initialSeconds = result.current.secondsUntilNextSpeech
       unmount()
 
-      // Timer should be cleared (no error on advancing timers after unmount)
-      expect(() => {
-        act(() => {
-          jest.advanceTimersByTime(1000)
-        })
-      }).not.toThrow()
+      // アンマウント後もタイマーが進まないことを確認
+      act(() => {
+        jest.advanceTimersByTime(5000)
+      })
+      // アンマウント後は状態が更新されないことを検証
+      expect(true).toBe(true) // No React warnings about updating unmounted component
     })
   })
src/__tests__/integration/presenceDetectionIntegration.test.tsx (1)

218-242: コールバックテストが実際の呼び出しを検証していません。

このテストはコールバックプロパティを受け取ってフックが初期化されることを確認していますが、顔検出やグリーティング時にコールバックが実際に呼び出されることを検証していません。これは Task 5.1 の統合テストとしては基本動作の確認で十分かもしれませんが、将来的にはコールバック実行のテストも検討してください。

src/__tests__/hooks/useVoiceRecognition.test.ts (1)

646-648: 弱いアサーション expect(true).toBe(true) が使用されています。

Lines 647 と 708 で expect(true).toBe(true) が使用されていますが、これは「エラーが発生しないこと」を確認するだけで、実際の動作を検証していません。将来的にはより具体的なアサーションへの改善を検討してください。

🔎 改善案
       // エラーなくモード変更が完了すること
-      expect(true).toBe(true)
+      // モード変更後もフックが正常に動作することを確認
+      expect(result.current.isListening).toBeDefined()
       // 少なくともエラーなく動作することを確認
-      expect(true).toBe(true)
+      // continuousMicListeningModeがfalseの場合、自動でリスニングが開始されないことを確認
+      expect(result.current.isListening).toBe(false)

Also applies to: 706-709

Comment on lines 39 to 49
it('should not render when debug mode is disabled', () => {
mockSettingsStore.mockImplementation((selector) => {
const state = { presenceDebugMode: false }
return selector(state as any)
})

const { container } = render(
<PresenceDebugPreview videoRef={mockVideoRef} detectionResult={null} />
)
expect(container.firstChild).toBeNull()
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# PresenceDebugPreviewコンポーネントの実装を探す
find . -name "*.tsx" -o -name "*.ts" | grep -v __tests__ | xargs grep -l "PresenceDebugPreview" | head -5

Repository: tegnike/aituber-kit

Length of output: 141


🏁 Script executed:

# PresenceDebugPreviewコンポーネントの条件付きレンダリング実装を確認
rg "PresenceDebugPreview" --type tsx --type ts -g "!**/__tests__/**" -A 30

Repository: tegnike/aituber-kit

Length of output: 90


🏁 Script executed:

# テストファイルの完全な内容を確認
cat -n src/__tests__/components/presenceDebugPreview.test.tsx | head -60

Repository: tegnike/aituber-kit

Length of output: 2160


🏁 Script executed:

cat -n src/components/presenceDebugPreview.tsx | head -80

Repository: tegnike/aituber-kit

Length of output: 2886


🏁 Script executed:

# Check the full component implementation
wc -l src/components/presenceDebugPreview.tsx

Repository: tegnike/aituber-kit

Length of output: 106


🏁 Script executed:

cat -n src/components/presenceDebugPreview.tsx | tail -30

Repository: tegnike/aituber-kit

Length of output: 1149


🏁 Script executed:

cat -n src/__tests__/components/presenceDebugPreview.test.tsx | sed -n '130,160p'

Repository: tegnike/aituber-kit

Length of output: 1109


クリティカル:コンポーネント実装に条件付きレンダリング不足。

PresenceDebugPreviewコンポーネントは、presenceDebugModefalseのとき、コンポーネント全体を返さないようにする必要があります。現在の実装では、デバッグモードが無効な場合でも<div>ラッパーとビデオ要素が常にレンダリングされており、テスト期待値(container.firstChildnull)に反しています。

コンポーネントの早期リターン処理を追加してください:

if (!presenceDebugMode) {
  return null
}

また、行142のバウンディングボックステストも確認が必要です。実装ではミラー計算(videoWidth - box.x - box.width)とスケール係数が適用されているため、テスト期待値の計算ロジックと一致していません。

🧰 Tools
🪛 GitHub Actions: Run Tests

[error] 48-48: PresenceDebugPreview should render null when debug mode is disabled, but a video element was found.

🤖 Prompt for AI Agents
In src/__tests__/components/presenceDebugPreview.test.tsx around lines 39 to 49:
the test expects the component to render nothing when presenceDebugMode is false
but the component currently always renders its wrapper; update the
PresenceDebugPreview component to early-return null when presenceDebugMode is
falsy (i.e. add if (!presenceDebugMode) return null at the top of the render),
and also verify/fix the bounding-box logic to match the implementation’s mirror
and scale math (ensure tests compute mirrored X as videoWidth - box.x -
box.width and apply the same scale factor the component uses, or adjust the
component to match the test’s expected coordinate math).

Comment on lines +216 to +242
it('should remove older memories first when truncating (Req 4.3)', () => {
const memories: MemoryRecord[] = [
createMemoryRecord({
id: 'old-1',
content: '古いメッセージ1',
timestamp: '2025-01-15T08:00:00Z',
}),
createMemoryRecord({
id: 'old-2',
content: '古いメッセージ2',
timestamp: '2025-01-15T09:00:00Z',
}),
createMemoryRecord({
id: 'new-1',
content: '新しいメッセージ',
timestamp: '2025-01-15T14:00:00Z',
}),
]

const options: ContextOptions = { maxTokens: 50 }
const result = builder.buildContext(memories, options)

// 新しいメッセージが優先される(古いものから削除される)
// トークン上限が厳しい場合、古いメッセージが削除される
const tokens = builder.estimateTokens(result)
expect(tokens).toBeLessThanOrEqual(50)
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

古いメモリの削除順序をより厳密に検証してください。

このテストはトークン上限以下であることのみを確認していますが、「古いメッセージから優先的に削除される」という要件(Req 4.3)を直接検証していません。

result の内容を解析して、新しいメッセージ('新しいメッセージ')が含まれ、古いメッセージ('古いメッセージ1' など)が削除されていることを明示的に確認することを推奨します。

🔎 提案: 削除順序の検証を追加
     const options: ContextOptions = { maxTokens: 50 }
     const result = builder.buildContext(memories, options)

+    // 新しいメッセージが含まれることを確認
+    expect(result).toContain('新しいメッセージ')
+    
     // 新しいメッセージが優先される(古いものから削除される)
     // トークン上限が厳しい場合、古いメッセージが削除される
     const tokens = builder.estimateTokens(result)
     expect(tokens).toBeLessThanOrEqual(50)
🤖 Prompt for AI Agents
In src/__tests__/features/memory/memoryContextBuilder.test.ts around lines 216
to 242, the test only asserts the resulting token count is <= 50 but does not
verify that older memories were removed first as required by Req 4.3; update the
test to parse the builder.buildContext(memories, options) result and assert that
the new message ('新しいメッセージ' / id 'new-1') is present and at least one or both
older messages ('古いメッセージ1' id 'old-1', '古いメッセージ2' id 'old-2') are absent (or
that only the newest entries remain), ensuring the removal order is explicitly
validated in addition to the token count.

Comment on lines +1 to +370
/**
* @jest-environment jsdom
*/

/**
* エラーハンドリング統一テスト (Requirement 8)
*
* 各フックのエラーハンドリングパターンが以下の基準で統一されていることを検証:
* 1. console.error出力形式の統一
* 2. toastStoreを使用したユーザー通知パターンの統一
* 3. 同じエラーカテゴリに対して同じトーストタグを使用
*/

import toastStore from '@/features/stores/toast'

// Mock stores
jest.mock('@/features/stores/settings', () => ({
__esModule: true,
default: Object.assign(
jest.fn((selector) => {
const state = {
selectLanguage: 'ja',
realtimeAPIMode: false,
initialSpeechTimeout: 10,
noSpeechTimeout: 3,
continuousMicListeningMode: false,
openaiKey: 'test-key',
whisperTranscriptionModel: 'whisper-1',
}
return selector(state)
}),
{
getState: jest.fn(() => ({
selectLanguage: 'ja',
realtimeAPIMode: false,
initialSpeechTimeout: 10,
noSpeechTimeout: 3,
continuousMicListeningMode: false,
openaiKey: 'test-key',
whisperTranscriptionModel: 'whisper-1',
})),
setState: jest.fn(),
}
),
}))

const mockAddToast = jest.fn()
jest.mock('@/features/stores/toast', () => ({
__esModule: true,
default: {
getState: jest.fn(() => ({ addToast: mockAddToast })),
},
}))

jest.mock('@/features/stores/home', () => ({
__esModule: true,
default: {
setState: jest.fn(),
getState: jest.fn(() => ({
chatProcessing: false,
isSpeaking: false,
})),
},
}))

jest.mock('@/features/stores/websocketStore', () => ({
__esModule: true,
default: {
getState: () => ({ wsManager: null }),
},
}))

jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))

jest.mock('@/features/messages/speakQueue', () => ({
SpeakQueue: {
stopAll: jest.fn(),
onSpeakCompletion: jest.fn(),
removeSpeakCompletionCallback: jest.fn(),
},
}))

// エラーハンドリングで使用されるべきトーストタグの定義
const EXPECTED_TOAST_TAGS = {
// マイク権限関連
MICROPHONE_PERMISSION_ERROR: 'microphone-permission-error',
MICROPHONE_PERMISSION_ERROR_FIREFOX: 'microphone-permission-error-firefox',

// 音声認識関連
SPEECH_RECOGNITION_NOT_SUPPORTED: 'speech-recognition-not-supported',
SPEECH_RECOGNITION_ERROR: 'speech-recognition-error',

// 音声検出関連
NO_SPEECH_DETECTED: 'no-speech-detected',
NO_SPEECH_DETECTED_LONG_SILENCE: 'no-speech-detected-long-silence',

// Whisper API関連
WHISPER_ERROR: 'whisper-error',
}

// エラーハンドリングで使用されるべきトーストメッセージキーの定義
const EXPECTED_TOAST_MESSAGE_KEYS = {
MICROPHONE_PERMISSION_DENIED: 'Toasts.MicrophonePermissionDenied',
FIREFOX_NOT_SUPPORTED: 'Toasts.FirefoxNotSupported',
SPEECH_RECOGNITION_NOT_SUPPORTED: 'Toasts.SpeechRecognitionNotSupported',
SPEECH_RECOGNITION_ERROR: 'Toasts.SpeechRecognitionError',
NO_SPEECH_DETECTED: 'Toasts.NoSpeechDetected',
WHISPER_ERROR: 'Toasts.WhisperError',
}

describe('エラーハンドリングの統一 (Requirement 8)', () => {
beforeEach(() => {
jest.clearAllMocks()
})

describe('8.1 エラーカテゴリとトーストタグの一致', () => {
it('マイク権限エラーには統一されたタグが使用される', () => {
// 期待される動作: マイク権限エラー時に 'microphone-permission-error' タグを使用
expect(EXPECTED_TOAST_TAGS.MICROPHONE_PERMISSION_ERROR).toBe(
'microphone-permission-error'
)
})

it('音声認識未サポートエラーには統一されたタグが使用される', () => {
// 期待される動作: SpeechRecognition APIがない場合に統一タグを使用
expect(EXPECTED_TOAST_TAGS.SPEECH_RECOGNITION_NOT_SUPPORTED).toBe(
'speech-recognition-not-supported'
)
})

it('音声未検出には統一されたタグが使用される', () => {
// 期待される動作: 音声が検出されなかった場合に統一タグを使用
expect(EXPECTED_TOAST_TAGS.NO_SPEECH_DETECTED).toBe('no-speech-detected')
})

it('Whisperエラーには統一されたタグが使用される', () => {
// 期待される動作: Whisper APIエラー時に統一タグを使用
expect(EXPECTED_TOAST_TAGS.WHISPER_ERROR).toBe('whisper-error')
})
})

describe('8.2 console.error出力形式の統一', () => {
it('エラーメッセージは一貫したプレフィックスを使用すべき', () => {
// エラーログの形式が統一されているかを確認するためのパターン
// 例: 'Error starting recognition:', error
// 例: 'Whisper transcription error:', error
// 統一形式: '[コンテキスト] エラー説明:', error

const consoleSpy = jest.spyOn(console, 'error').mockImplementation()

// シミュレートされたエラーログを確認
console.error('Error starting recognition:', new Error('test'))
console.error('Microphone permission error:', new Error('test'))
console.error('Speech recognition error:', new Error('test'))

// 各呼び出しで第1引数が文字列であることを確認
consoleSpy.mock.calls.forEach((call) => {
expect(typeof call[0]).toBe('string')
// エラーメッセージが':'で終わる形式であることを確認
expect(call[0]).toMatch(/:\s*$/)
})

consoleSpy.mockRestore()
})
})

describe('8.3 toastStoreを使用したユーザー通知パターンの統一', () => {
it('toastには必須フィールド(message, type, tag)が含まれる', () => {
// 期待されるtoastの構造
const expectedToastStructure = {
message: expect.any(String),
type: expect.stringMatching(/^(info|error|warning|success)$/),
tag: expect.any(String),
}

// 各エラーカテゴリで使用されるtoastパターンを検証
const sampleToasts = [
{
message: 'Toasts.MicrophonePermissionDenied',
type: 'error',
tag: 'microphone-permission-error',
},
{
message: 'Toasts.NoSpeechDetected',
type: 'info',
tag: 'no-speech-detected',
},
{
message: 'Toasts.WhisperError',
type: 'error',
tag: 'whisper-error',
},
]

sampleToasts.forEach((toast) => {
expect(toast).toMatchObject(expectedToastStructure)
})
})

it('エラータイプにはerror、情報にはinfoが使用される', () => {
// エラーカテゴリとtype の対応関係を確認
const errorTypeMap = {
'microphone-permission-error': 'error',
'speech-recognition-not-supported': 'error',
'speech-recognition-error': 'error',
'whisper-error': 'error',
'no-speech-detected': 'info', // 音声未検出は情報レベル
}

Object.entries(errorTypeMap).forEach(([tag, expectedType]) => {
expect(expectedType).toMatch(/^(error|info)$/)
})
})
})
})

describe('統一されたエラーハンドリングパターンの定義', () => {
/**
* このテストは、各フックで使用すべき統一されたエラーハンドリングパターンを定義します。
* 実装時には、以下のパターンに従ってエラーハンドリングを行います。
*/

it('マイク権限エラー時の統一パターン', () => {
const handleMicrophonePermissionError = (
error: Error,
t: (key: string) => string
) => {
console.error('Microphone permission error:', error)
toastStore.getState().addToast({
message: t(EXPECTED_TOAST_MESSAGE_KEYS.MICROPHONE_PERMISSION_DENIED),
type: 'error',
tag: EXPECTED_TOAST_TAGS.MICROPHONE_PERMISSION_ERROR,
})
}

const testError = new Error('Permission denied')
const mockT = (key: string) => key

const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
handleMicrophonePermissionError(testError, mockT)

expect(consoleSpy).toHaveBeenCalledWith(
'Microphone permission error:',
testError
)
expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.MICROPHONE_PERMISSION_DENIED,
type: 'error',
tag: EXPECTED_TOAST_TAGS.MICROPHONE_PERMISSION_ERROR,
})

consoleSpy.mockRestore()
})

it('音声認識未サポートエラー時の統一パターン', () => {
const handleSpeechRecognitionNotSupported = (
t: (key: string) => string
) => {
console.error('Speech Recognition API is not supported in this browser')
toastStore.getState().addToast({
message: t(
EXPECTED_TOAST_MESSAGE_KEYS.SPEECH_RECOGNITION_NOT_SUPPORTED
),
type: 'error',
tag: EXPECTED_TOAST_TAGS.SPEECH_RECOGNITION_NOT_SUPPORTED,
})
}

const mockT = (key: string) => key

const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
handleSpeechRecognitionNotSupported(mockT)

expect(consoleSpy).toHaveBeenCalledWith(
'Speech Recognition API is not supported in this browser'
)
expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.SPEECH_RECOGNITION_NOT_SUPPORTED,
type: 'error',
tag: EXPECTED_TOAST_TAGS.SPEECH_RECOGNITION_NOT_SUPPORTED,
})

consoleSpy.mockRestore()
})

it('音声未検出時の統一パターン', () => {
const handleNoSpeechDetected = (t: (key: string) => string) => {
toastStore.getState().addToast({
message: t(EXPECTED_TOAST_MESSAGE_KEYS.NO_SPEECH_DETECTED),
type: 'info',
tag: EXPECTED_TOAST_TAGS.NO_SPEECH_DETECTED,
})
}

const mockT = (key: string) => key

handleNoSpeechDetected(mockT)

expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.NO_SPEECH_DETECTED,
type: 'info',
tag: EXPECTED_TOAST_TAGS.NO_SPEECH_DETECTED,
})
})

it('Whisper APIエラー時の統一パターン', () => {
const handleWhisperError = (error: Error, t: (key: string) => string) => {
console.error('Whisper transcription error:', error)
toastStore.getState().addToast({
message: t(EXPECTED_TOAST_MESSAGE_KEYS.WHISPER_ERROR),
type: 'error',
tag: EXPECTED_TOAST_TAGS.WHISPER_ERROR,
})
}

const testError = new Error('Whisper API failed')
const mockT = (key: string) => key

const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
handleWhisperError(testError, mockT)

expect(consoleSpy).toHaveBeenCalledWith(
'Whisper transcription error:',
testError
)
expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.WHISPER_ERROR,
type: 'error',
tag: EXPECTED_TOAST_TAGS.WHISPER_ERROR,
})

consoleSpy.mockRestore()
})

it('音声認識開始エラー時の統一パターン', () => {
const handleSpeechRecognitionStartError = (
error: Error,
t: (key: string) => string
) => {
console.error('Error starting recognition:', error)
toastStore.getState().addToast({
message: t(EXPECTED_TOAST_MESSAGE_KEYS.SPEECH_RECOGNITION_ERROR),
type: 'error',
tag: EXPECTED_TOAST_TAGS.SPEECH_RECOGNITION_ERROR,
})
}

const testError = new Error('Failed to start')
const mockT = (key: string) => key

const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
handleSpeechRecognitionStartError(testError, mockT)

expect(consoleSpy).toHaveBeenCalledWith(
'Error starting recognition:',
testError
)
expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.SPEECH_RECOGNITION_ERROR,
type: 'error',
tag: EXPECTED_TOAST_TAGS.SPEECH_RECOGNITION_ERROR,
})

consoleSpy.mockRestore()
})
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

テストファイル全体の設計について

このテストファイルは「統一されたエラーハンドリングパターン」を定義する仕様書としては価値がありますが、実装の品質を保証するテストとしては不十分です。

現在の問題:

  • テストが実装を検証していない
  • 回帰を検出できない
  • 実装がパターンから逸脱しても検出されない

推奨されるアプローチ:

  1. このファイルを2つに分割:

    • errorHandling.constants.ts: 定数定義(実装でも使用)
    • errorHandling.test.ts: 実際の実装を検証するテスト
  2. または、実際のフックをテストに含め、統一パターンに従っているかを検証

このアプローチにより、テストの価値が大幅に向上します。

この改善案の実装サンプルコードを生成しましょうか?新しいissueとして追跡することも可能です。

Comment on lines +221 to +369
describe('統一されたエラーハンドリングパターンの定義', () => {
/**
* このテストは、各フックで使用すべき統一されたエラーハンドリングパターンを定義します。
* 実装時には、以下のパターンに従ってエラーハンドリングを行います。
*/

it('マイク権限エラー時の統一パターン', () => {
const handleMicrophonePermissionError = (
error: Error,
t: (key: string) => string
) => {
console.error('Microphone permission error:', error)
toastStore.getState().addToast({
message: t(EXPECTED_TOAST_MESSAGE_KEYS.MICROPHONE_PERMISSION_DENIED),
type: 'error',
tag: EXPECTED_TOAST_TAGS.MICROPHONE_PERMISSION_ERROR,
})
}

const testError = new Error('Permission denied')
const mockT = (key: string) => key

const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
handleMicrophonePermissionError(testError, mockT)

expect(consoleSpy).toHaveBeenCalledWith(
'Microphone permission error:',
testError
)
expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.MICROPHONE_PERMISSION_DENIED,
type: 'error',
tag: EXPECTED_TOAST_TAGS.MICROPHONE_PERMISSION_ERROR,
})

consoleSpy.mockRestore()
})

it('音声認識未サポートエラー時の統一パターン', () => {
const handleSpeechRecognitionNotSupported = (
t: (key: string) => string
) => {
console.error('Speech Recognition API is not supported in this browser')
toastStore.getState().addToast({
message: t(
EXPECTED_TOAST_MESSAGE_KEYS.SPEECH_RECOGNITION_NOT_SUPPORTED
),
type: 'error',
tag: EXPECTED_TOAST_TAGS.SPEECH_RECOGNITION_NOT_SUPPORTED,
})
}

const mockT = (key: string) => key

const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
handleSpeechRecognitionNotSupported(mockT)

expect(consoleSpy).toHaveBeenCalledWith(
'Speech Recognition API is not supported in this browser'
)
expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.SPEECH_RECOGNITION_NOT_SUPPORTED,
type: 'error',
tag: EXPECTED_TOAST_TAGS.SPEECH_RECOGNITION_NOT_SUPPORTED,
})

consoleSpy.mockRestore()
})

it('音声未検出時の統一パターン', () => {
const handleNoSpeechDetected = (t: (key: string) => string) => {
toastStore.getState().addToast({
message: t(EXPECTED_TOAST_MESSAGE_KEYS.NO_SPEECH_DETECTED),
type: 'info',
tag: EXPECTED_TOAST_TAGS.NO_SPEECH_DETECTED,
})
}

const mockT = (key: string) => key

handleNoSpeechDetected(mockT)

expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.NO_SPEECH_DETECTED,
type: 'info',
tag: EXPECTED_TOAST_TAGS.NO_SPEECH_DETECTED,
})
})

it('Whisper APIエラー時の統一パターン', () => {
const handleWhisperError = (error: Error, t: (key: string) => string) => {
console.error('Whisper transcription error:', error)
toastStore.getState().addToast({
message: t(EXPECTED_TOAST_MESSAGE_KEYS.WHISPER_ERROR),
type: 'error',
tag: EXPECTED_TOAST_TAGS.WHISPER_ERROR,
})
}

const testError = new Error('Whisper API failed')
const mockT = (key: string) => key

const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
handleWhisperError(testError, mockT)

expect(consoleSpy).toHaveBeenCalledWith(
'Whisper transcription error:',
testError
)
expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.WHISPER_ERROR,
type: 'error',
tag: EXPECTED_TOAST_TAGS.WHISPER_ERROR,
})

consoleSpy.mockRestore()
})

it('音声認識開始エラー時の統一パターン', () => {
const handleSpeechRecognitionStartError = (
error: Error,
t: (key: string) => string
) => {
console.error('Error starting recognition:', error)
toastStore.getState().addToast({
message: t(EXPECTED_TOAST_MESSAGE_KEYS.SPEECH_RECOGNITION_ERROR),
type: 'error',
tag: EXPECTED_TOAST_TAGS.SPEECH_RECOGNITION_ERROR,
})
}

const testError = new Error('Failed to start')
const mockT = (key: string) => key

const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
handleSpeechRecognitionStartError(testError, mockT)

expect(consoleSpy).toHaveBeenCalledWith(
'Error starting recognition:',
testError
)
expect(mockAddToast).toHaveBeenCalledWith({
message: EXPECTED_TOAST_MESSAGE_KEYS.SPEECH_RECOGNITION_ERROR,
type: 'error',
tag: EXPECTED_TOAST_TAGS.SPEECH_RECOGNITION_ERROR,
})

consoleSpy.mockRestore()
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

実際のフック実装をテストするように変更すべきです。

このテストスイートは、テスト内で定義されたサンプルハンドラー関数を検証していますが、実際のフック実装を検証していません。これでは実装が統一されたパターンに従っているかを保証できません。

推奨される改善:

  1. 実際のフック(例:useBrowserSpeechRecognition)をインポート
  2. React Testing LibraryのrenderHookを使用してフックをテスト
  3. エラー条件を発生させる
  4. 実際に呼び出されるconsole.errortoastStore.addToastを検証
💡 実装テストの例
import { renderHook } from '@testing-library/react'
import { useBrowserSpeechRecognition } from '@/features/hooks/useBrowserSpeechRecognition'

describe('useBrowserSpeechRecognition エラーハンドリング', () => {
  it('マイク権限エラー時に統一パターンを使用', async () => {
    // マイク権限拒否をシミュレート
    const mockGetUserMedia = jest.fn().mockRejectedValue(
      new DOMException('Permission denied', 'NotAllowedError')
    )
    Object.defineProperty(global.navigator, 'mediaDevices', {
      value: { getUserMedia: mockGetUserMedia },
      writable: true,
    })

    const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
    const { result } = renderHook(() => useBrowserSpeechRecognition())

    // フックのメソッドを呼び出してエラーを発生させる
    await act(async () => {
      await result.current.startListening()
    })

    // 統一パターンに従っているかを検証
    expect(consoleSpy).toHaveBeenCalledWith(
      'Microphone permission error:',
      expect.any(Error)
    )
    expect(mockAddToast).toHaveBeenCalledWith({
      message: expect.stringContaining('MicrophonePermissionDenied'),
      type: 'error',
      tag: 'microphone-permission-error',
    })

    consoleSpy.mockRestore()
  })
})

このアプローチにより、実装が実際に統一パターンに従っているかを検証できます。

🤖 Prompt for AI Agents
src/__tests__/hooks/errorHandling.test.ts lines 221-369: the test currently
asserts on inline sample handler functions rather than the real hook
implementation; update the tests to import the actual hook (e.g.,
useBrowserSpeechRecognition), use renderHook from React Testing Library,
simulate error conditions (mock navigator.mediaDevices.getUserMedia,
SpeechRecognition, or Whisper API failures) to trigger the hook's error paths,
spy on console.error and mockAddToast, call the hook methods (via
result.current) inside act to produce the error, and assert that console.error
and toastStore.addToast were called with the unified messages/tags.

Comment on lines +14 to +25
beforeEach(() => {
settingsStore.setState({
kioskModeEnabled: DEFAULT_KIOSK_CONFIG.kioskModeEnabled,
kioskPasscode: DEFAULT_KIOSK_CONFIG.kioskPasscode,
kioskGuidanceMessage: DEFAULT_KIOSK_CONFIG.kioskGuidanceMessage,
kioskGuidanceTimeout: DEFAULT_KIOSK_CONFIG.kioskGuidanceTimeout,
kioskMaxInputLength: DEFAULT_KIOSK_CONFIG.kioskMaxInputLength,
kioskNgWords: DEFAULT_KIOSK_CONFIG.kioskNgWords,
kioskNgWordEnabled: DEFAULT_KIOSK_CONFIG.kioskNgWordEnabled,
kioskTemporaryUnlock: DEFAULT_KIOSK_CONFIG.kioskTemporaryUnlock,
})
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -A 30 'DEFAULT_KIOSK_CONFIG' --type=ts

Repository: tegnike/aituber-kit

Length of output: 31557


DEFAULT_KIOSK_CONFIGkioskGuidanceMessagekioskGuidanceTimeoutプロパティが存在しません。

beforeEachでこれらのプロパティを設定しようとしていますが、実際のDEFAULT_KIOSK_CONFIG定義には存在しません。結果として、これらの値はundefinedとして店舗状態に設定されることになります。DEFAULT_KIOSK_CONFIGにこれらのプロパティを追加するか、beforeEachから削除してください。

🤖 Prompt for AI Agents
In src/__tests__/hooks/useKioskMode.test.ts around lines 14 to 25, the test's
beforeEach references kioskGuidanceMessage and kioskGuidanceTimeout which are
not present on DEFAULT_KIOSK_CONFIG; either add these two properties with
appropriate default values and types to the DEFAULT_KIOSK_CONFIG definition (in
the module where it is declared) or remove those keys from the beforeEach setup
so you only set properties that actually exist; ensure whichever fix you choose
keeps types consistent with the store and tests.

Comment on lines +104 to +117
describe('SpeechRecognition初期化時の言語設定', () => {
it('ハードコードされたja-JPではなく、getVoiceLanguageCodeを使用すべき', () => {
// このテストは現在の実装が期待に沿っていないことを確認する(REDフェーズ)
// 現在のコード: newRecognition.lang = 'ja-JP' (ハードコード)
// 期待するコード: newRecognition.lang = getVoiceLanguageCode(selectLanguage)

// selectLanguage='en'の場合、期待値は'en-US'
const expectedLang = getVoiceLanguageCode('en')
expect(expectedLang).toBe('en-US')

// 注: このテストは実装修正後に、実際のフックをテストするように拡張する
// 現状はgetVoiceLanguageCode関数自体の動作を確認
})
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

テストが期待する動作を検証していません

このテストは「RED フェーズ」を意図したものですが、実際には期待される動作の失敗を検証していません。現在のテストは getVoiceLanguageCode 関数が正しく動作することを確認しているだけで、フックが getVoiceLanguageCode を使用しているかどうかを検証していません。

TDD の RED フェーズを実装する場合、実際のフックの動作を検証し、現在の実装では失敗するテストを書く必要があります。

🔎 提案: 実際のフック動作を検証する失敗するテストに変更

フックをインポートして実際の動作をテストする例:

it('SpeechRecognitionの初期化時にselectLanguageに基づいた言語コードを設定する', () => {
  // settings store の selectLanguage を 'en' に設定
  const { result } = renderHook(() => useRealtimeVoiceAPI())
  
  // フックが初期化したSpeechRecognitionインスタンスを取得
  // (フックの実装によって方法は異なる)
  
  // 期待: recognition.lang が 'en-US' になっている
  expect(mockSpeechRecognition.lang).toBe('en-US')
  
  // 現在の実装では 'ja-JP' がハードコードされているため、このテストは失敗する
})

または、現時点では実装修正前のため、このテストブロックを削除し、実装修正後に適切なテストを追加することを検討してください。

🤖 Prompt for AI Agents
In src/__tests__/hooks/useRealtimeVoiceAPI.test.ts around lines 104 to 117, the
test only asserts getVoiceLanguageCode('en') returns 'en-US' and does not verify
that the hook actually uses getVoiceLanguageCode (so it won't fail while the
hook is still hardcoding 'ja-JP'); replace or update this test to exercise the
real hook: render the hook (e.g., with renderHook), set or mock the settings
store selectLanguage to 'en', trigger the hook initialization, then assert that
the created SpeechRecognition instance's lang equals getVoiceLanguageCode('en')
(expect 'en-US') so the test will fail until the hook is changed to use
getVoiceLanguageCode; alternatively remove this placeholder test and add the
proper hook-level test after implementation.

Comment on lines +15 to +26
beforeEach(() => {
settingsStore.setState({
kioskModeEnabled: DEFAULT_KIOSK_CONFIG.kioskModeEnabled,
kioskPasscode: DEFAULT_KIOSK_CONFIG.kioskPasscode,
kioskGuidanceMessage: DEFAULT_KIOSK_CONFIG.kioskGuidanceMessage,
kioskGuidanceTimeout: DEFAULT_KIOSK_CONFIG.kioskGuidanceTimeout,
kioskMaxInputLength: DEFAULT_KIOSK_CONFIG.kioskMaxInputLength,
kioskNgWords: DEFAULT_KIOSK_CONFIG.kioskNgWords,
kioskNgWordEnabled: DEFAULT_KIOSK_CONFIG.kioskNgWordEnabled,
kioskTemporaryUnlock: DEFAULT_KIOSK_CONFIG.kioskTemporaryUnlock,
})
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the complete definition of DEFAULT_KIOSK_CONFIG
rg -A 15 "export const DEFAULT_KIOSK_CONFIG" --type=ts

Repository: tegnike/aituber-kit

Length of output: 1080


DEFAULT_KIOSK_CONFIG に存在しないプロパティへのアクセス

beforeEachkioskGuidanceMessagekioskGuidanceTimeoutDEFAULT_KIOSK_CONFIG から参照していますが、これらのプロパティは DEFAULT_KIOSK_CONFIG に存在しません。実際の定義には、kioskModeEnabledkioskPasscodekioskMaxInputLengthkioskNgWordskioskNgWordEnabledkioskTemporaryUnlock のみ含まれています。このコードは実行時に undefined にアクセスするため、修正が必要です。

🤖 Prompt for AI Agents
In src/__tests__/integration/kioskModeIntegration.test.ts around lines 15–26 the
beforeEach references kioskGuidanceMessage and kioskGuidanceTimeout which do not
exist on DEFAULT_KIOSK_CONFIG; remove those two keys from the setState call (or
alternatively add those properties with appropriate default values to
DEFAULT_KIOSK_CONFIG where it’s defined) so the test no longer reads undefined
properties at runtime and only sets properties that actually exist.

Comment on lines +16 to +17
// fetchをモック
global.fetch = jest.fn()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

グローバルオブジェクトの復元処理を追加してください。

global.fetch を上書きしていますが、テスト終了後に元の値に戻す処理がありません。これは他のテストファイルに副作用を与える可能性があります。

afterAll() フック内で元の値に復元する処理を追加してください。

🔎 提案: グローバルオブジェクトの復元
+// fetchの元の実装を保存
+const originalFetch = global.fetch
+
 // fetchをモック
 global.fetch = jest.fn()

 describe('usePresetLoader統合テスト', () => {
+  afterAll(() => {
+    // fetchを元の実装に戻す
+    global.fetch = originalFetch
+  })
+
   beforeEach(() => {
     jest.clearAllMocks()

Based on learnings, グローバルオブジェクトを上書きする場合は、afterAll()フック内で元の値に戻して、テスト間の副作用を防ぐべきです。

📝 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
// fetchをモック
global.fetch = jest.fn()
// fetchの元の実装を保存
const originalFetch = global.fetch
// fetchをモック
global.fetch = jest.fn()
describe('usePresetLoader統合テスト', () => {
afterAll(() => {
// fetchを元の実装に戻す
global.fetch = originalFetch
})
beforeEach(() => {
jest.clearAllMocks()
})
🤖 Prompt for AI Agents
In src/__tests__/integration/usePresetLoaderIntegration.test.ts around lines
16-17, the test overwrites global.fetch without restoring it, causing potential
cross-test side effects; save the original fetch before mocking (e.g. const
originalFetch = global.fetch), set global.fetch = jest.fn() for the test, and
add an afterAll() hook that restores the original with global.fetch =
originalFetch so other tests aren’t affected.

@claude
Copy link
Contributor

claude bot commented Dec 25, 2025

Claude finished @tegnike's task —— View job


PR Review: Feature/Fix Realtime API

Reviewed this large PR (100 files, 26K+ additions) focusing on code quality, bugs, performance, security, and test coverage.

Review Tasks

  • Analyze PR scope and key changes
  • Review new Kiro spec-driven development framework
  • Examine core feature implementations (memory, presence, kiosk, idle modes)
  • Check OpenAI voice agents integration
  • Review test coverage and quality
  • Assess security implications
  • Evaluate performance considerations
  • Provide constructive feedback summary

Review Summary

Positive Aspects

1. Comprehensive Test Coverage

  • Excellent: 30+ new test files with thorough coverage
  • Well-structured test suites using Jest & React Testing Library
  • Good use of mocking for external dependencies (face-api.js, stores)
  • Tests follow TDD approach with proper requirement traceability

2. Code Quality & Architecture

  • Strong TypeScript usage with explicit type definitions
  • Well-organized modular structure following feature-driven architecture
  • Good separation of concerns (services, stores, components, hooks)
  • Comprehensive error handling with typed error responses

3. Security Implementation

  • API key validation in embedding endpoint (embedding.ts:53-62)
  • Method validation for API endpoints
  • Demo mode restrictions for sensitive operations
  • Input sanitization and validation

⚠️ Areas Requiring Attention

1. Potential Security Concerns

  • 🚨 File System Access: memory-files.ts:34-55 - Direct file system operations without path traversal protection

    // Consider adding path validation to prevent directory traversal
    const safePath = path.normalize(filename).replace(/^(\.\.[\/\\])+/, '');
  • API Key Exposure: Multiple endpoints accept API keys in request body - consider security implications

  • Missing Rate Limiting: High-usage endpoints like /api/embedding lack rate limiting

2. Performance Considerations

  • Memory Usage: IndexedDB operations in memoryService.ts could be optimized for large datasets
  • Embedding Processing: Synchronous cosine similarity calculations may block UI for large vectors
  • Face Detection: usePresenceDetection.ts runs continuous detection - needs CPU usage optimization

3. Code Quality Issues

  • Missing Null Checks: In memoryService.ts:74-98, potential null reference in embedding operations
  • Error Handling: Some API endpoints (memory-files.ts:81-84) silently skip errors without proper logging
  • Type Safety: Usage of any in error handling (embedding.ts:82)

📋 Specific Recommendations

Security Improvements:

// In memory-files.ts, add path validation:
const safePath = path.resolve(logsDir, filename);
if (!safePath.startsWith(path.resolve(logsDir))) {
  return res.status(403).json({ message: 'Invalid file path' });
}

Performance Optimization:

// In memoryTypes.ts, optimize cosine similarity for large vectors:
export function cosineSimilarity(vectorA: number[], vectorB: number[]): number {
  // Add early exit for zero vectors
  // Consider using Web Workers for large calculations
}

Error Handling:

  • Replace any types with specific error interfaces
  • Add structured logging for debugging production issues
  • Implement circuit breaker pattern for external API calls

🔍 Framework Assessment: Kiro System

Strengths:

  • Comprehensive spec-driven development approach
  • Well-structured template system
  • Clear separation of steering vs. specifications
  • Good documentation standards

Concerns:

  • Large framework addition (1000+ lines) without gradual introduction
  • Missing migration documentation for existing codebase
  • Potential complexity overhead for smaller features

🎯 Final Assessment

Overall Quality: B+ (Good with improvements needed)

This PR demonstrates strong engineering practices with comprehensive testing and well-architected features. The memory/RAG system is particularly well-implemented with proper TypeScript usage and modular design.

Before Merging:

  1. ✅ Add path traversal protection to file operations
  2. ⚠️ Consider rate limiting for high-usage endpoints
  3. 🔄 Replace any types with specific interfaces
  4. 📊 Add performance monitoring for face detection
  5. 📝 Document Kiro framework migration strategy

The codebase shows professional development practices with good test coverage. Security concerns are moderate and addressable. Performance optimizations would benefit long-term scalability.


Copy link

@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

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87b7135 and d862e1d.

📒 Files selected for processing (7)
  • .claude/agents/test-runner.md
  • .claude/commands/run-tests.md
  • src/__tests__/components/presenceDebugPreview.test.tsx
  • src/__tests__/features/chat/difyChat.test.ts
  • src/__tests__/features/chat/vercelAIChat.test.ts
  • src/__tests__/features/kiosk/passcodeDialog.test.tsx
  • src/__tests__/integration/voiceRecognitionFunctionality.test.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/__tests__/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

テストは__tests__ディレクトリに配置

Files:

  • src/__tests__/features/chat/difyChat.test.ts
  • src/__tests__/features/kiosk/passcodeDialog.test.tsx
  • src/__tests__/features/chat/vercelAIChat.test.ts
  • src/__tests__/components/presenceDebugPreview.test.tsx
🧠 Learnings (2)
📚 Learning: 2025-08-05T17:30:02.776Z
Learnt from: CR
Repo: tegnike/aituber-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-05T17:30:02.776Z
Learning: Jestのパターンマッチで特定テストを実行可能

Applied to files:

  • .claude/commands/run-tests.md
📚 Learning: 2025-05-21T14:11:19.604Z
Learnt from: RimgO
Repo: tegnike/aituber-kit PR: 395
File: src/components/messageInputContainer.tsx:46-60
Timestamp: 2025-05-21T14:11:19.604Z
Learning: In `src/components/messageInputContainer.tsx`, the `if (true) { //isNewUser` condition in the `onUserDetected` handler is intentionally left as is because this section is planned to be changed later.

Applied to files:

  • src/__tests__/components/presenceDebugPreview.test.tsx
🧬 Code graph analysis (2)
src/__tests__/features/kiosk/passcodeDialog.test.tsx (1)
src/features/kiosk/passcodeDialog.tsx (2)
  • PasscodeDialogProps (11-16)
  • PasscodeDialog (21-195)
src/__tests__/components/presenceDebugPreview.test.tsx (1)
src/features/presence/presenceTypes.ts (1)
  • DetectionResult (43-47)
🪛 GitHub Actions: Lint and Format
src/__tests__/components/presenceDebugPreview.test.tsx

[error] 63-63: Replace container.querySelector('[data-testid="bounding-box"]') with ⏎········container.querySelector('[data-testid="bounding-box"]')⏎······ prettier/prettier

🪛 GitHub Actions: Run Tests
src/__tests__/components/presenceDebugPreview.test.tsx

[error] 63-63: Prettier formatting failed in test file. Replace the indicated formatting to satisfy prettier/prettier. Run 'prettier --write' to fix.

🪛 markdownlint-cli2 (0.18.1)
.claude/agents/test-runner.md

30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


59-59: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


95-95: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


102-102: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-review
🔇 Additional comments (16)
.claude/agents/test-runner.md (1)

1-122: ドキュメント構造は良好です

テスト実行エージェントの仕様が明確に定義されており、責務・実行フロー・報告形式が体系的にまとめられています。レポート出力ルールの詳細さと、メインエージェントへの報告形式の二層構造は実装時に有用です。上記のコードブロック言語指定を修正すれば、ドキュメントとして完成度が高いです。

.claude/commands/run-tests.md (1)

1-41: テスト実行ワークフローの実装が適切です

ワークフロー設計が明確で、以下の点で優れています:

  1. サブエージェント連携: test-runner サブエージェントへの委譲が適切に定義されている
  2. 条件分岐: 成功時と失敗時の処理が明確に分離されている
  3. 安全ガードレール: 3回までの修正試行、マスター相談への段階的エスカレーション
  4. 柔軟性: $ARGUMENTS プレースホルダーにより、テスト対象の指定が可能

ワークフロー全体として、テスト駆動の自動修正プロセスが適切に実装されています。

src/__tests__/components/presenceDebugPreview.test.tsx (5)

1-48: セットアップとモックの実装が適切です。

テストのセットアップが正しく実装されています。ビデオ要素のモック化、ストアとi18nのモック、beforeEachでのクリーンアップが適切に行われています。


51-64: 過去のレビューコメントとテスト期待値の不整合を確認してください。

このテストは、デバッグモードが無効な場合でもビデオ要素がレンダリングされることを期待していますが、過去のレビューコメントでは、presenceDebugModefalseのときにコンポーネントがnullを返すべきだと指摘されています。

行60のコメント「Video element is always rendered for camera preview」は、ビデオ要素を常にレンダリングする設計意図を示していますが、これは過去のレビューコメントと矛盾しています。

コンポーネントの実装意図を明確にし、以下のいずれかを選択してください:

  1. デバッグモード無効時にnullを返す(過去のレビューに従う)
  2. ビデオは常にレンダリングし、オーバーレイのみを条件付きで表示する(このテストの期待値に従う)

79-94: LGTM!

ビデオ要素のレンダリングテストが適切に実装されています。


96-164: バウンディングボックステストのロジックが正確です。

バウンディングボックスのテストが包括的に実装されています:

  • 顔が検出されない場合のテスト
  • 顔が検出された場合のテスト
  • ミラー計算を含む位置とサイズの検証(行157-163)

ミラー計算の式 videoWidth(640) - x(10) - width(100) = 530 が正しく適用されています。


167-183: LGTM!

カスタムclassNameプロップのテストが適切に実装されています。

src/__tests__/features/chat/difyChat.test.ts (2)

285-321: APIエラーハンドリングのテスト期待値が正しく更新されています

Line 317の期待値が実装の変更を正しく反映しています。翻訳キーとAPIエラー詳細を連結する形式になっており、他のテストケースとも一貫性があります。


224-263: テスト期待値と実装が一致していることを確認しました

実装側(src/features/chat/difyChat.ts)のエラーハンドリングを確認した結果、テストの期待値は実装と正確に一致しています。

  • 行95-104: ストリームリード時のエラーを適切にキャッチ
  • 行98-99: エラーメッセージを handleApiError('AIAPIError', errorDetail) で構築
  • handleApiError 関数(行6-16)が ${translatedKey}: ${errorDetail} 形式でメッセージを生成

テストは正しく実装の動作を検証しており、コーディングガイドライン(__tests__ディレクトリ配置)にも準拠しています。ただし、エラー詳細("Stream error")が英語のままで翻訳されない点は、将来的な多言語対応強化時に考慮する価値があります。

src/__tests__/features/chat/vercelAIChat.test.ts (3)

107-123: 非ストリーミングエラーレスポンスのテスト期待値を確認しました

Line 122の更新により、非ストリーミングAPIのエラーレスポンスでも詳細なエラーメッセージが返されることを検証しています。ストリーミングケースと同じフォーマット(翻訳キー: エラー詳細)を使用しており、一貫性が保たれています。


198-236: ストリーミングエラーハンドリングのテスト更新を確認しました

Line 232の期待値が、difyChat.test.tsと同じパターンでエラー詳細を含むようになっています。両ファイル間で一貫したエラーメッセージフォーマットが維持されており、適切です。


258-283: APIエラーレスポンスのテスト期待値が適切に更新されています

Line 279の更新により、difyChat.test.tsと同じエラーメッセージフォーマットを使用していることが確認できます。両チャット実装で統一されたエラーハンドリングが行われており、コードベース全体で一貫性が保たれています。

src/__tests__/features/kiosk/passcodeDialog.test.tsx (4)

1-19: テストファイルの構造とセットアップが適切です

ファイルヘッダー、インポート、およびヘルパー関数が適切に設定されています。要件番号が明示されており、保守性が高いです。


38-294: 包括的なテストカバレッジと優れた構造

テストスイートは要件に明確にマッピングされており、以下を網羅しています:

  • 要件3.1: UI要素のレンダリング
  • 要件3.2: パスコード検証ロジック
  • 要件3.3: ロックアウトメカニクス

フェイクタイマーの使用が適切で、30秒のロックアウト期間やカウントダウンの動作を正確にテストしています。エッジケース(入力クリア、試行回数リセット、ボタン無効化など)も適切にカバーされています。


306-318: Escapeキーテストが正しく修正されました

過去のレビューコメントで指摘された500msの遅延が適切に考慮されています。jest.advanceTimersByTime(500)を使用してタイマーを進めてからキーイベントを発火させることで、コンポーネントの実装と一致した正確なテストになっています。


296-337: アクセシビリティとUXテストが充実しています

以下の重要なアクセシビリティ要件が適切にテストされています:

  • フォーカス管理(非同期動作を考慮したwaitFor使用)
  • キーボードインタラクション(Escape、Enter)
  • セキュリティ配慮(パスワード入力のマスキング)

テストの実装方法も適切で、非同期動作やキーボードイベントのベストプラクティスに従っています。

Comment on lines +30 to +34
```
reports/
└── tests/
└── YYYY-MM-DD_HH-mm-ss_test-report.md
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

フォルダ構造コードブロックに言語指定を追加してください

コードブロックの言語指定がありません。フォルダ構造を示すため text または plaintext を指定してください。

🔎 推奨される修正
-```
+```text
 reports/
 └── tests/
     └── YYYY-MM-DD_HH-mm-ss_test-report.md
-```
+```
📝 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
```
reports/
└── tests/
└── YYYY-MM-DD_HH-mm-ss_test-report.md
```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
.claude/agents/test-runner.md around lines 30 to 34: the fenced code block
showing the folder structure lacks a language specifier; update the opening
fence to include a language such as "text" or "plaintext" (e.g., change ``` to
```text) so the block is explicitly marked as plain text.

Comment on lines +56 to +59
**エラー内容**:
```
[エラーメッセージ]
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

エラーメッセージを表示するコードブロックに言語指定を追加してください

マークダウンリントの規則に従い、コードブロックには言語を指定する必要があります。ここは通常のテキスト出力として text を指定してください。

🔎 推奨される修正
 **エラー内容**:
-```
+```text
 [エラーメッセージ]
-```
+```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

59-59: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In .claude/agents/test-runner.md around lines 56 to 59, the markdown code block
showing the error message lacks a language tag; add the language specifier
"text" to the opening fence so the block becomes ```text [エラーメッセージ] ``` (i.e.,
change the opening ``` to ```text and keep the closing ```), ensuring the code
block complies with the markdown lint rule.

Comment on lines +95 to +98
```
テスト完了: 全 [X] テストが成功しました。
レポート: reports/tests/[ファイル名].md
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

全テスト成功時の報告形式コードブロックに言語指定を追加してください

コードブロックに言語指定がありません。通常のテキスト形式として text を指定してください。

🔎 推奨される修正
 ### 全テスト成功の場合
 
-```
+```text
 テスト完了: 全 [X] テストが成功しました。
 レポート: reports/tests/[ファイル名].md
-```
+```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

95-95: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
.claude/agents/test-runner.md around lines 95 to 98: the fenced code block
showing the test completion message lacks a language specifier; add "text" after
the opening triple backticks so it becomes ```text, leaving the block contents
unchanged and keeping the closing triple backticks as-is.

Comment on lines +102 to +115
```
テスト完了: [X] 件の失敗があります。

失敗したテスト:
1. [ファイル名] - [テスト名]
原因: [簡潔な原因説明]
修正対象: [ファイルパス:行番号]

詳細レポート: reports/tests/[ファイル名].md

修正が必要なファイル:
- [ファイルパス1]
- [ファイルパス2]
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

失敗時の報告形式コードブロックに言語指定を追加してください

こちらのコードブロックも言語指定がありません。テキスト形式として text を指定してください。

🔎 推奨される修正
 ### 失敗がある場合
 
-```
+```text
 テスト完了: [X] 件の失敗があります。
 
 失敗したテスト:
@@ -106,7 +106,7 @@
 修正が必要なファイル:
 - [ファイルパス1]
 - [ファイルパス2]
-```
+```
📝 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
```
テスト完了: [X] 件の失敗があります。
失敗したテスト:
1. [ファイル名] - [テスト名]
原因: [簡潔な原因説明]
修正対象: [ファイルパス:行番号]
詳細レポート: reports/tests/[ファイル名].md
修正が必要なファイル:
- [ファイルパス1]
- [ファイルパス2]
```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

102-102: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In .claude/agents/test-runner.md around lines 102–115 the failure-report code
block is missing a language specifier; change the opening fence from ``` to
```text so the block is explicitly marked as plain text (leave the content and
closing fence unchanged).

// Video element is always rendered for camera preview
expect(container.querySelector('video')).toBeInTheDocument()
// But debug overlay should not be rendered
expect(container.querySelector('[data-testid="bounding-box"]')).not.toBeInTheDocument()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Prettierフォーマットエラーを修正してください。

パイプラインでPrettierフォーマットエラーが発生しています。

🔎 修正案
-      expect(container.querySelector('[data-testid="bounding-box"]')).not.toBeInTheDocument()
+      expect(
+        container.querySelector('[data-testid="bounding-box"]')
+      ).not.toBeInTheDocument()
📝 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
expect(container.querySelector('[data-testid="bounding-box"]')).not.toBeInTheDocument()
expect(
container.querySelector('[data-testid="bounding-box"]')
).not.toBeInTheDocument()
🧰 Tools
🪛 GitHub Actions: Lint and Format

[error] 63-63: Replace container.querySelector('[data-testid="bounding-box"]') with ⏎········container.querySelector('[data-testid="bounding-box"]')⏎······ prettier/prettier

🪛 GitHub Actions: Run Tests

[error] 63-63: Prettier formatting failed in test file. Replace the indicated formatting to satisfy prettier/prettier. Run 'prettier --write' to fix.

🤖 Prompt for AI Agents
In src/__tests__/components/presenceDebugPreview.test.tsx around line 63,
Prettier is flagging the expect line as misformatted; fix it by reformatting the
file with the project's Prettier settings (e.g., run npm run format or npx
prettier --write src/__tests__/components/presenceDebugPreview.test.tsx) or
manually adjust the line to match style (ensure correct spacing, no trailing
whitespace, proper indentation and punctuation such as adding/removing a
trailing semicolon per project config).

Comment on lines +22 to +36
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
const translations: Record<string, string> = {
'Kiosk.PasscodeTitle': 'パスコードを入力',
'Kiosk.PasscodeIncorrect': 'パスコードが違います',
'Kiosk.PasscodeLocked': 'ロック中',
'Kiosk.PasscodeRemainingAttempts': '残り{{count}}回',
'Kiosk.Cancel': 'キャンセル',
'Kiosk.Unlock': '解除',
}
return translations[key] || key
},
}),
}))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

i18nモックが補間パラメータを処理していません

モックは翻訳キーのみを処理し、補間パラメータ(例:{ count: 2 })を無視しています。'Kiosk.PasscodeRemainingAttempts''残り{{count}}回'を返しますが、166行目と182行目のテストは/残り2回//残り1回/を期待しています。これによりテストが失敗する可能性があります。

🔎 補間を処理するモックの修正案
 jest.mock('react-i18next', () => ({
   useTranslation: () => ({
-    t: (key: string) => {
+    t: (key: string, params?: Record<string, any>) => {
       const translations: Record<string, string> = {
         'Kiosk.PasscodeTitle': 'パスコードを入力',
         'Kiosk.PasscodeIncorrect': 'パスコードが違います',
         'Kiosk.PasscodeLocked': 'ロック中',
         'Kiosk.PasscodeRemainingAttempts': '残り{{count}}回',
         'Kiosk.Cancel': 'キャンセル',
         'Kiosk.Unlock': '解除',
       }
-      return translations[key] || key
+      let result = translations[key] || key
+      
+      // Handle interpolation
+      if (params) {
+        Object.keys(params).forEach((param) => {
+          result = result.replace(`{{${param}}}`, String(params[param]))
+        })
+      }
+      
+      return result
     },
   }),
 }))
📝 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
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
const translations: Record<string, string> = {
'Kiosk.PasscodeTitle': 'パスコードを入力',
'Kiosk.PasscodeIncorrect': 'パスコードが違います',
'Kiosk.PasscodeLocked': 'ロック中',
'Kiosk.PasscodeRemainingAttempts': '残り{{count}}回',
'Kiosk.Cancel': 'キャンセル',
'Kiosk.Unlock': '解除',
}
return translations[key] || key
},
}),
}))
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, params?: Record<string, any>) => {
const translations: Record<string, string> = {
'Kiosk.PasscodeTitle': 'パスコードを入力',
'Kiosk.PasscodeIncorrect': 'パスコードが違います',
'Kiosk.PasscodeLocked': 'ロック中',
'Kiosk.PasscodeRemainingAttempts': '残り{{count}}回',
'Kiosk.Cancel': 'キャンセル',
'Kiosk.Unlock': '解除',
}
let result = translations[key] || key
// Handle interpolation
if (params) {
Object.keys(params).forEach((param) => {
result = result.replace(`{{${param}}}`, String(params[param]))
})
}
return result
},
}),
}))
🤖 Prompt for AI Agents
In src/__tests__/features/kiosk/passcodeDialog.test.tsx around lines 22 to 36,
the current i18n mock ignores interpolation params so keys like
'Kiosk.PasscodeRemainingAttempts' return '残り{{count}}回' even when tests expect
'残り2回' or '残り1回'; update the mock's useTranslation.t to accept an optional
options/params argument (e.g., second arg or object) and replace occurrences of
{{count}} (and any simple {{key}}) with the provided value(s) (handle numeric
count and string keys at minimum) so tests that assert against interpolated text
pass.

@claude
Copy link
Contributor

claude bot commented Jan 5, 2026

Claude finished @tegnike's task —— View job


PR Review: Feature/Fix Realtime API

Reviewed this large PR (100 files, 26K+ additions) focusing on code quality, bugs, performance, security, and test coverage.

Review Tasks

  • Analyze PR scope and key changes
  • Review new Kiro spec-driven development framework
  • Examine core feature implementations (memory, presence, kiosk, idle modes)
  • Check OpenAI voice agents integration
  • Review test coverage and quality
  • Assess security implications
  • Evaluate performance considerations
  • Provide constructive feedback summary

Review Summary

Positive Aspects

1. Comprehensive Test Coverage

  • Excellent: 30+ new test files with thorough coverage
  • Well-structured test suites using Jest & React Testing Library
  • Good use of mocking for external dependencies (face-api.js, stores)
  • Tests follow TDD approach with proper requirement traceability

2. Code Quality & Architecture

  • Strong TypeScript usage with explicit type definitions
  • Well-organized modular structure following feature-driven architecture
  • Good separation of concerns (services, stores, components, hooks)
  • Comprehensive error handling with typed error responses

3. Security Implementation

  • API key validation in embedding endpoint (embedding.ts:53-62)
  • Method validation for API endpoints
  • Demo mode restrictions for sensitive operations
  • Input sanitization and validation

⚠️ Areas Requiring Attention

1. Potential Security Concerns

  • 🚨 File System Access: memory-files.ts:34-55 - Direct file system operations without path traversal protection

    // Consider adding path validation to prevent directory traversal
    const safePath = path.normalize(filename).replace(/^(\.\.[\/\\])+/, '');
  • API Key Exposure: Multiple endpoints accept API keys in request body - consider security implications

  • Missing Rate Limiting: High-usage endpoints like /api/embedding lack rate limiting

2. Performance Considerations

  • Memory Usage: IndexedDB operations in memoryService.ts could be optimized for large datasets
  • Embedding Processing: Synchronous cosine similarity calculations may block UI for large vectors
  • Face Detection: usePresenceDetection.ts runs continuous detection - needs CPU usage optimization

3. Code Quality Issues

  • Missing Null Checks: In memoryService.ts:74-98, potential null reference in embedding operations
  • Error Handling: Some API endpoints (memory-files.ts:81-84) silently skip errors without proper logging
  • Type Safety: Usage of any in error handling (embedding.ts:82)

📋 Specific Recommendations

Security Improvements:

// In memory-files.ts, add path validation:
const safePath = path.resolve(logsDir, filename);
if (!safePath.startsWith(path.resolve(logsDir))) {
  return res.status(403).json({ message: 'Invalid file path' });
}

Performance Optimization:

// In memoryTypes.ts, optimize cosine similarity for large vectors:
export function cosineSimilarity(vectorA: number[], vectorB: number[]): number {
  // Add early exit for zero vectors
  // Consider using Web Workers for large calculations
}

Error Handling:

  • Replace any types with specific error interfaces
  • Add structured logging for debugging production issues
  • Implement circuit breaker pattern for external API calls

🔍 Framework Assessment: Kiro System

Strengths:

  • Comprehensive spec-driven development approach
  • Well-structured template system
  • Clear separation of steering vs. specifications
  • Good documentation standards

Concerns:

  • Large framework addition (1000+ lines) without gradual introduction
  • Missing migration documentation for existing codebase
  • Potential complexity overhead for smaller features

🎯 Final Assessment

Overall Quality: B+ (Good with improvements needed)

This PR demonstrates strong engineering practices with comprehensive testing and well-architected features. The memory/RAG system is particularly well-implemented with proper TypeScript usage and modular design.

Before Merging:

  1. ✅ Add path traversal protection to file operations
  2. ⚠️ Consider rate limiting for high-usage endpoints
  3. 🔄 Replace any types with specific interfaces
  4. 📊 Add performance monitoring for face detection
  5. 📝 Document Kiro framework migration strategy

The codebase shows professional development practices with good test coverage. Security concerns are moderate and addressable. Performance optimizations would benefit long-term scalability.


Copy link

@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: 0

🧹 Nitpick comments (2)
src/__tests__/components/settings/kioskSettings.test.tsx (2)

17-32: ストアのモック実装が機能しているが、より明確にできます

Object.assign(jest.fn(), { setState, getState }) パターンは動作しますが、やや複雑です。以下のような標準的なモックパターンの方が読みやすくなります。

🔎 より明確なモック実装の提案
-jest.mock('@/features/stores/settings', () => {
-  return {
-    __esModule: true,
-    default: Object.assign(jest.fn(), {
-      setState: (arg: any) => mockSetState(arg),
-      getState: () => ({
-        kioskModeEnabled: false,
-        kioskPasscode: '0000',
-        kioskMaxInputLength: 200,
-        kioskNgWords: [],
-        kioskNgWordEnabled: false,
-        kioskTemporaryUnlock: false,
-      }),
-    }),
-  }
-})
+jest.mock('@/features/stores/settings', () => ({
+  __esModule: true,
+  default: jest.fn((selector) => {
+    const state = {
+      kioskModeEnabled: false,
+      kioskPasscode: '0000',
+      kioskMaxInputLength: 200,
+      kioskNgWords: [],
+      kioskNgWordEnabled: false,
+      kioskTemporaryUnlock: false,
+    }
+    return selector ? selector(state) : state
+  }),
+  setState: mockSetState,
+}))

85-90: 配列インデックスに依存したセレクタはUI変更に脆弱です

toggleButtons[0] を使用していますが、UI上のボタン順序が変わるとテストが壊れる可能性があります。より具体的なセレクタ(例: getByRole with name option)を使用すると、テストの堅牢性が向上します。

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d862e1d and 9c23fd8.

⛔ Files ignored due to path filters (4)
  • public/images/setting-icons/idle-settings.svg is excluded by !**/*.svg
  • public/images/setting-icons/kiosk-settings.svg is excluded by !**/*.svg
  • public/images/setting-icons/memory-settings.svg is excluded by !**/*.svg
  • public/images/setting-icons/presence-settings.svg is excluded by !**/*.svg
📒 Files selected for processing (5)
  • locales/ja/translation.json
  • src/__tests__/components/settings/kioskSettings.test.tsx
  • src/components/menu.tsx
  • src/components/settings/index.tsx
  • src/components/settings/kioskSettings.tsx
🧰 Additional context used
📓 Path-based instructions (2)
locales/ja/**/*.json

📄 CodeRabbit inference engine (CLAUDE.md)

言語ファイルの更新は日本語(/locales/ja/)のみ行う

Files:

  • locales/ja/translation.json
**/__tests__/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

テストは__tests__ディレクトリに配置

Files:

  • src/__tests__/components/settings/kioskSettings.test.tsx
🧠 Learnings (2)
📚 Learning: 2025-08-05T17:30:02.776Z
Learnt from: CR
Repo: tegnike/aituber-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-05T17:30:02.776Z
Learning: Applies to locales/!(ja)/**/*.json : 他の言語ファイル(en、ko、zh等)は手動で更新しない

Applied to files:

  • locales/ja/translation.json
📚 Learning: 2025-08-05T17:30:02.776Z
Learnt from: CR
Repo: tegnike/aituber-kit PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-05T17:30:02.776Z
Learning: Applies to locales/ja/**/*.json : 言語ファイルの更新は日本語(`/locales/ja/`)のみ行う

Applied to files:

  • locales/ja/translation.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-review
🔇 Additional comments (4)
locales/ja/translation.json (1)

442-553: 日本語ロケールファイルへの大規模な追加を確認しました

メモリ、人感検知、アイドルモード、デモ端末モードなど、新機能のUI文字列を包括的にカバーする翻訳キーが追加されています。コーディングガイドラインに従い、日本語ロケールファイルのみが更新されている点が適切です。

追加されたキーの構造とネストも既存のパターンと一致しており、問題ありません。

Based on coding guidelines, 言語ファイルの更新は日本語(/locales/ja/)のみ行う というルールに準拠しています。

src/__tests__/components/settings/kioskSettings.test.tsx (3)

113-153: 入力文字数制限のバリデーションテストが適切に実装されています

最小値(50文字)と最大値(500文字)のクランプ処理を、blur イベントを通じて正しくテストしています。境界値テストが含まれており、バリデーションロジックの品質を保証しています。


155-216: NGワードフィルターのテストカバレッジが充実しています

トグル、条件付きレンダリング、入力処理、既存ワードの表示など、NGワードフィルター機能の主要な動作を網羅的にテストしています。

lines 198-201の find() を使った検証は動作しますが、より直接的なアサーション方法も検討できます。


1-224: テストファイル全体の構造が適切です

__tests__ ディレクトリへの配置はコーディングガイドラインに従っています。テストは適切に分離され(beforeEach)、要件ごとに整理されており、保守性が高い構造になっています。

Based on coding guidelines, テストは__tests__ディレクトリに配置 というルールに準拠しています。

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.

2 participants