10 KiB
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 |
|
|
true |
|
|
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[];
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
```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)
<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>