docs(02): create phase plan for Gemini integration + asset pipeline

This commit is contained in:
2026-03-29 11:50:06 +02:00
parent 1465ac45dc
commit 02a189b9f2
6 changed files with 1402 additions and 2 deletions
@@ -0,0 +1,256 @@
---
phase: 02-gemini-integration-asset-pipeline
plan: 04
type: execute
wave: 2
depends_on: ["02-01", "02-02"]
files_modified:
- src/companion/companion.ts
- src/companion/companion.test.ts
autonomous: true
requirements: [COMP-01, COMP-02, COMP-03, COMP-04]
must_haves:
truths:
- "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"
artifacts:
- path: "src/companion/companion.ts"
provides: "Companion text module with API-first + fallback strategy"
exports: ["getGreeting", "getLetterIntro", "getForestComment"]
- path: "src/companion/companion.test.ts"
provides: "Tests for companion text module"
key_links:
- from: "src/companion/companion.ts"
to: "src/api/gemini.ts"
via: "calls generateText for AI-generated companion dialogue"
pattern: "generateText"
- from: "src/companion/companion.ts"
to: "src/companion/fallbacks.ts"
via: "imports fallback functions when API returns null"
pattern: "getRandomFallbackGreeting|getFallbackLetterIntro|getRandomFallbackForestComment"
- from: "src/companion/companion.ts"
to: "src/companion/characters.ts"
via: "looks up companion name and personality by type"
pattern: "companions"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<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
<interfaces>
<!-- From src/api/gemini.ts (Plan 01) -->
```typescript
export async function generateText(prompt: string, systemPrompt: string, config: GeminiConfig): Promise<string | null>;
export async function checkRateLimit(db: IDBDatabase, type: 'text' | 'image'): Promise<boolean>;
export async function incrementApiCall(db: IDBDatabase, type: 'text' | 'image'): Promise<void>;
```
<!-- From src/api/config.ts (Plan 01) -->
```typescript
export interface GeminiConfig {
geminiApiKey: string;
geminiModel: string;
imageModel: string;
}
export async function loadConfig(): Promise<GeminiConfig | null>;
```
<!-- From src/companion/fallbacks.ts (Plan 02) -->
```typescript
export type TimeOfDay = 'morgen' | 'mittag' | 'abend';
export function getRandomFallbackGreeting(timeOfDay: TimeOfDay): string;
export function getRandomFallbackForestComment(): string;
export function getFallbackLetterIntro(letter: string): LetterIntro | undefined;
```
<!-- From src/companion/characters.ts -->
```typescript
export interface CompanionDefinition {
type: CompanionType;
name: string;
emoji: string;
personality: string;
}
export const companions: CompanionDefinition[];
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Companion text module with API-first fallback strategy</name>
<files>src/companion/companion.ts, src/companion/companion.test.ts</files>
<read_first>
- 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)
</read_first>
<behavior>
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
</behavior>
<action>
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)
</action>
<verify>
<automated>cd /home/dev/workspace/zauberwald && npx vitest run src/companion/companion.test.ts</automated>
</verify>
<acceptance_criteria>
- `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
</acceptance_criteria>
<done>Companion module exports getGreeting, getLetterIntro, getForestComment. Each tries Gemini API first (with rate-limit check), falls back to static texts. All tests pass.</done>
</task>
</tasks>
<verification>
- `npx vitest run src/companion/` — all tests pass
- Each function handles: API success, API failure, no config, rate-limited
</verification>
<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>
<output>
After completion, create `.planning/phases/02-gemini-integration-asset-pipeline/02-04-SUMMARY.md`
</output>