Files
Zauberwald/.planning/phases/02-gemini-integration-asset-pipeline/02-04-PLAN.md
T

10 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
02-gemini-integration-asset-pipeline 04 execute 2
02-01
02-02
src/companion/companion.ts
src/companion/companion.test.ts
true
COMP-01
COMP-02
COMP-03
COMP-04
truths artifacts key_links
getGreeting tries Gemini API first, returns fallback text if API unavailable
getLetterIntro tries Gemini API first, returns fallback text if API unavailable
getForestComment tries Gemini API first, returns fallback text if API unavailable
All returned texts are max 25 words, simple German, no performance praise
path provides exports
src/companion/companion.ts Companion text module with API-first + fallback strategy
getGreeting
getLetterIntro
getForestComment
path provides
src/companion/companion.test.ts Tests for companion text module
from to via pattern
src/companion/companion.ts src/api/gemini.ts calls generateText for AI-generated companion dialogue generateText
from to via pattern
src/companion/companion.ts src/companion/fallbacks.ts imports fallback functions when API returns null getRandomFallbackGreeting|getFallbackLetterIntro|getRandomFallbackForestComment
from to via pattern
src/companion/companion.ts src/companion/characters.ts looks up companion name and personality by type companions
Create the companion text module that provides AI-generated or fallback text for greetings, letter introductions, and forest comments.

Purpose: The companion is the emotional core of Zauberwald. This module makes her speak — via AI when possible, via curated fallbacks otherwise. Output: src/companion/companion.ts with 3 exported functions, full test coverage.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-gemini-integration-asset-pipeline/02-01-SUMMARY.md

@src/companion/characters.ts @src/companion/fallbacks.ts @src/api/gemini.ts @src/api/config.ts

```typescript export async function generateText(prompt: string, systemPrompt: string, config: GeminiConfig): Promise; export async function checkRateLimit(db: IDBDatabase, type: 'text' | 'image'): Promise; export async function incrementApiCall(db: IDBDatabase, type: 'text' | 'image'): Promise; ```
export interface GeminiConfig {
  geminiApiKey: string;
  geminiModel: string;
  imageModel: string;
}
export async function loadConfig(): Promise<GeminiConfig | null>;
export type TimeOfDay = 'morgen' | 'mittag' | 'abend';
export function getRandomFallbackGreeting(timeOfDay: TimeOfDay): string;
export function getRandomFallbackForestComment(): string;
export function getFallbackLetterIntro(letter: string): LetterIntro | undefined;
export interface CompanionDefinition {
  type: CompanionType;
  name: string;
  emoji: string;
  personality: string;
}
export const companions: CompanionDefinition[];
Task 1: Companion text module with API-first fallback strategy src/companion/companion.ts, src/companion/companion.test.ts - src/companion/characters.ts (companion definitions with name, personality) - src/companion/fallbacks.ts (fallback functions from Plan 02) - src/api/gemini.ts (generateText signature, checkRateLimit, incrementApiCall) - src/api/config.ts (loadConfig, GeminiConfig) - SPEC.md section 9.2 (exact prompt templates for greeting, letter intro, forest comment) getGreeting: - Test: returns AI text when API succeeds and rate limit not exceeded - Test: returns fallback text when loadConfig returns null (no API key) - Test: returns fallback text when generateText returns null (API error) - Test: returns fallback text when rate limit exceeded - Test: passes correct system prompt with companion personality and time of day - Test: calls incrementApiCall after successful API call
getLetterIntro:
- Test: returns AI text when API succeeds
- Test: returns fallback LetterIntro text when API unavailable
- Test: system prompt includes letter, finger description, and companion personality
- Test: returned text includes the letter being introduced

getForestComment:
- Test: returns AI text when API succeeds
- Test: returns fallback text when API unavailable
- Test: system prompt includes element description and companion personality
Create `src/companion/companion.ts` (per D-13):
```typescript
import { companions } from "./characters";
import type { CompanionType } from "../types";
import { loadConfig } from "../api/config";
import { generateText, checkRateLimit, incrementApiCall } from "../api/gemini";
import {
  type TimeOfDay,
  getRandomFallbackGreeting,
  getFallbackLetterIntro,
  getRandomFallbackForestComment,
} from "./fallbacks";

function getTimeOfDay(): TimeOfDay {
  const hour = new Date().getHours();
  if (hour < 12) return "morgen";
  if (hour < 17) return "mittag";
  return "abend";
}

function getCompanion(type: CompanionType) {
  return companions.find(c => c.type === type)!;
}
```

**getGreeting(characterType: CompanionType, db: IDBDatabase): Promise<string>**
1. Call `loadConfig()` — if null, return `getRandomFallbackGreeting(getTimeOfDay())`
2. Call `checkRateLimit(db, 'text')` — if false, return fallback
3. Build system prompt from SPEC.md 9.2 template:
   ```
   Du bist {name}, ein(e) {personality} im Zauberwald.
   Du sprichst mit einem 7-jaehrigen Kind, das Tippen lernt.

   Regeln:
   - Max. 2 kurze Saetze (insgesamt max. 25 Woerter)
   - Einfache Sprache, kurze Woerter
   - Freundlich und warm, nie belehrend
   - Keine Bewertung von Leistung
   - Kein Englisch
   - Beziehe dich auf die Tageszeit: {timeOfDay}
   ```
4. User prompt: `"Schreib eine Begruessung."`
5. Call `generateText(userPrompt, systemPrompt, config)`
6. If result is null, return fallback
7. Call `incrementApiCall(db, 'text')`
8. Return result

**getLetterIntro(characterType: CompanionType, letter: string, fingerDescription: string, db: IDBDatabase): Promise<string>**
1. Same config/rate-limit pattern
2. System prompt from SPEC.md 9.2:
   ```
   Du bist {name}, ein(e) {personality} im Zauberwald.
   Du stellst einem 7-jaehrigen Kind den Buchstaben {letter} vor.

   Regeln:
   - Max. 2 Saetze (max. 25 Woerter)
   - Erklaere, welcher Finger den Buchstaben drueckt: {fingerDescription}
   - Verwende eine bildhafte Eselsbruecke, die zum Wald/Natur/Tiere-Thema passt
   - Einfache Sprache
   - Kein Englisch
   ```
3. User prompt: `"Stelle den Buchstaben vor."`
4. On failure: return `getFallbackLetterIntro(letter)?.text ?? "Druecke die leuchtende Taste!"`

**getForestComment(characterType: CompanionType, elementDescription: string, db: IDBDatabase): Promise<string>**
1. Same config/rate-limit pattern
2. System prompt from SPEC.md 9.2:
   ```
   Du bist {name}, ein(e) {personality} im Zauberwald.
   Ein Kind hat gerade eine Tippuebung abgeschlossen. Im Wald ist ein neues Wesen erschienen: {elementDescription}.

   Regeln:
   - Max. 2 kurze Saetze (insgesamt max. 25 Woerter)
   - Druecke Staunen und Freude aus
   - Einfache Sprache fuer 7-Jaehrige
   - Kein Lob fuer Leistung, sondern Begeisterung ueber das neue Waldelement
   - Kein Englisch
   ```
3. User prompt: `"Kommentiere das neue Element."`
4. On failure: return `getRandomFallbackForestComment()`

**Tests (src/companion/companion.test.ts):**
- Mock `fetch` (via vi.spyOn) for generateText calls
- Mock `loadConfig` to return config or null
- Use `fake-indexeddb` for rate-limit DB operations
- Test each function with API success, API failure, no config, rate-limited
- At least 9 test cases total (3 per function)
cd /home/dev/workspace/zauberwald && npx vitest run src/companion/companion.test.ts - `grep -c "export async function getGreeting" src/companion/companion.ts` returns 1 - `grep -c "export async function getLetterIntro" src/companion/companion.ts` returns 1 - `grep -c "export async function getForestComment" src/companion/companion.ts` returns 1 - `grep "loadConfig" src/companion/companion.ts` shows config loader is used - `grep "checkRateLimit" src/companion/companion.ts` shows rate-limiting is checked - `grep "getRandomFallbackGreeting\|getFallbackLetterIntro\|getRandomFallbackForestComment" src/companion/companion.ts` shows fallbacks imported - `npx vitest run src/companion/companion.test.ts` passes with 0 failures and >= 9 tests Companion module exports getGreeting, getLetterIntro, getForestComment. Each tries Gemini API first (with rate-limit check), falls back to static texts. All tests pass. - `npx vitest run src/companion/` — all tests pass - Each function handles: API success, API failure, no config, rate-limited

<success_criteria>

  • getGreeting returns AI greeting or fallback, respecting rate limits
  • getLetterIntro returns AI letter introduction or fallback
  • getForestComment returns AI forest comment or fallback
  • All texts conform to COMP-04: max 25 words, simple German, no performance praise
  • Tests cover all paths (API success, failure, no config, rate-limited) </success_criteria>
After completion, create `.planning/phases/02-gemini-integration-asset-pipeline/02-04-SUMMARY.md`