docs(02): create phase plan for Gemini integration + asset pipeline
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user