# Social Content Generator — Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Symfony command `app:generate-social-content` that generates 7 social media posts per week (3 variants each) from mission data.

**Architecture:** Command delegates to `SocialContentService` (mission selection, text generation, history) and `SocialImageGenerator` (1080x1080 PNG via GD). Output goes to `var/social-content/week-YYYY-WW/`.

**Tech Stack:** Symfony 8, PHP 8.4, GD (imagettftext), Inter TTF font

---

## File Structure

| Action | File | Responsibility |
|--------|------|----------------|
| Create | `src/Service/SocialContentService.php` | Mission selection, text generation, hashtags, history, manifest |
| Create | `src/Service/SocialImageGenerator.php` | 1080x1080 PNG generation via GD |
| Create | `src/Command/GenerateSocialContentCommand.php` | CLI entry point with options |
| Create | `var/fonts/Inter-Bold.ttf` | Font file for image generation |
| Modify | `.gitignore` | Add `var/social-content/` |

---

## Chunk 1: Foundation

### Task 1: Font + gitignore setup

**Files:**
- Create: `var/fonts/Inter-Bold.ttf`
- Modify: `.gitignore`

- [ ] **Step 1: Download Inter Bold TTF**

```bash
mkdir -p var/fonts
curl -L "https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip" -o /tmp/inter.zip
unzip -o /tmp/inter.zip -d /tmp/inter
cp /tmp/inter/Inter.ttc var/fonts/ || cp /tmp/inter/InterVariable.ttf var/fonts/Inter-Bold.ttf
```

Note: Inter v4 ships as variable font. If `.ttc` is available, use it. Otherwise use the variable `.ttf`. GD's `imagettftext()` supports both.

- [ ] **Step 2: Update .gitignore**

Add to `.gitignore`:
```
/var/social-content/
/var/social-history.json
```

Keep `var/fonts/` tracked (small, needed for image generation).

- [ ] **Step 3: Commit**

```bash
git add var/fonts/ .gitignore
git commit -m "chore: add Inter font for social content images, update gitignore"
```

---

### Task 2: SocialContentService — mission selection + history

**Files:**
- Create: `src/Service/SocialContentService.php`

- [ ] **Step 1: Create service with mission selection logic**

```php
<?php

namespace App\Service;

use App\Repository\MissionRepository;
use App\Entity\Mission;

class SocialContentService
{
    private const CHARACTER_ROTATION = ['scout', 'blitz', 'nova', 'echo'];
    private const DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];

    private const CHARACTER_HASHTAGS = [
        'scout' => '#ScoutTropiciel',
        'blitz' => '#BlitzChallenge',
        'nova' => '#NovaOdkrywa',
        'echo' => '#EchoNatura',
    ];

    private const LOCATION_HASHTAGS = [
        'home' => '#zabawywdomu',
        'park' => '#zabawywparku',
    ];

    private const BASE_HASHTAGS = ['#TROPO', '#TROPOapp', '#edukacjaprzezzabawę', '#aplikacjadladzieci'];
    private const GENERIC_HASHTAGS = ['#zagadkidladzieci', '#ciekawostkidladzieci', '#mamahack'];

    private const QUOTES = [
        'scout' => [
            'Hej, mam dla Ciebie zagadkę! Podążaj za tropem! 🐕',
            'Każdy trop prowadzi do przygody! Gotowy? 🐕',
            'Widzisz te ślady? To nasza kolejna misja! 🐕',
            'Nos mi podpowiada, że czeka nas coś super! 🐕',
            'Tropiciel nigdy się nie poddaje — Ty też nie! 🐕',
            'Znalazłem coś ciekawego — chodź, pokażę! 🐕',
            'Najlepsze przygody zaczynają się od jednego kroku! 🐕',
            'Mój nos nigdy nie kłamie — tu jest coś fajnego! 🐕',
            'Razem możemy odkryć każdą tajemnicę! 🐕',
            'Trop znaleziony! Misja czas start! 🐕',
        ],
        'blitz' => [
            'Kto pierwszy rozwiąże? Na start! 🐇',
            'Szybciej, szybciej! Przygoda nie czeka! 🐇',
            'Trzy, dwa, jeden — AKCJA! 🐇',
            'Energia na maksa! Czas na wyzwanie! 🐇',
            'Ruszamy! Kto da radę nadążyć? 🐇',
            'Hop, hop! Kolejna misja czeka! 🐇',
            'Szybki jak błyskawica — to moje motto! 🐇',
            'Nie ma czasu do stracenia — zaczynamy! 🐇',
            'Challenge accepted! A Ty? 🐇',
            'Ruch to zdrowie, a zabawa to życie! 🐇',
        ],
        'nova' => [
            'Czy wiesz, że...? Mam coś fascynującego! 🦝',
            'Eksperyment numer 47 — zaczynamy! 🦝',
            'Ciekawość to moja supermoc! A Twoja? 🦝',
            'Mam teorię... sprawdźmy ją razem! 🦝',
            'Każdy wielki wynalazek zaczął się od pytania! 🦝',
            'Laboratorium otwarte — wchodzimy! 🦝',
            'Nauka jest super, kiedy robisz ją sam! 🦝',
            'Odkrycie dnia — gotowi? 🦝',
            'To nie magia — to nauka! A może jedno i drugie? 🦝',
            'Hmmm, interesujące... muszę to zbadać! 🦝',
        ],
        'echo' => [
            'Posłuchaj, co mówi natura... 🐢',
            'Cierpliwość prowadzi do mądrości. Gotowy? 🐢',
            'Każde drzewo ma swoją historię... 🐢',
            'Wolniej. Oddychaj. Teraz obserwuj! 🐢',
            'Mądrość przychodzi do tych, którzy słuchają 🐢',
            'Natura jest najlepszym nauczycielem 🐢',
            'Krok po kroku — tak się zdobywa góry 🐢',
            'Zamknij oczy i posłuchaj... słyszysz? 🐢',
            'Spokojnie, mamy czas. Najlepsze rzeczy nie spieszą się 🐢',
            'Widziałem już wiele, ale każdy dzień przynosi coś nowego 🐢',
        ],
    ];

    public function __construct(
        private MissionRepository $missionRepo,
        private CharacterService $characterService,
        private string $projectDir,
    ) {}

    /**
     * Select 7 missions for a given week, respecting rotation and history.
     * @return Mission[]
     */
    public function selectMissions(string $weekId, bool $force = false): array
    {
        $allMissions = $this->missionRepo->findAll();
        $usedIds = $force ? [] : $this->getUsedMissionIds();

        $available = array_filter($allMissions, fn(Mission $m) => !in_array($m->getId(), $usedIds));
        if (count($available) < 7) {
            $available = $allMissions; // reset if too few available
        }

        // Group by character
        $byCharacter = [];
        foreach ($available as $mission) {
            $byCharacter[$mission->getCharacter()][] = $mission;
        }

        $selected = [];
        $usedLocations = [];

        for ($i = 0; $i < 7; $i++) {
            $char = self::CHARACTER_ROTATION[$i % count(self::CHARACTER_ROTATION)];
            $pool = $byCharacter[$char] ?? [];

            if (empty($pool)) {
                // Fallback: any character
                $pool = $available;
            }

            // Prefer alternating locations
            $lastLocation = end($usedLocations) ?: 'park';
            $preferredLocation = $lastLocation === 'home' ? 'park' : 'home';

            $preferred = array_filter($pool, fn(Mission $m) => $m->getLocation() === $preferredLocation);
            $pick = !empty($preferred) ? $preferred[array_rand($preferred)] : $pool[array_rand($pool)];

            $selected[] = $pick;
            $usedLocations[] = $pick->getLocation();

            // Remove from pools
            foreach ($byCharacter as $c => &$missions) {
                $missions = array_filter($missions, fn(Mission $m) => $m->getId() !== $pick->getId());
            }
            $available = array_filter($available, fn(Mission $m) => $m->getId() !== $pick->getId());
        }

        return $selected;
    }

    /**
     * Generate 3 text variants for a mission.
     * @return array{zagadka: array, ciekawostka: array, cytat: array}
     */
    public function generateVariants(Mission $mission): array
    {
        $character = $mission->getCharacter();
        $location = $mission->getLocation();
        $steps = $mission->getSteps();

        // Pick a random step for zagadka and ciekawostka
        $step = $steps[array_rand($steps)];

        $hashtags = $this->buildHashtags($character, $location);

        return [
            'zagadka' => [
                'type' => 'zagadka',
                'text' => $this->formatPost($step['riddle_text'] ?? '', $character, $hashtags),
                'raw_text' => $step['riddle_text'] ?? '',
            ],
            'ciekawostka' => [
                'type' => 'ciekawostka',
                'text' => $this->formatPost($step['fun_fact'] ?? '', $character, $hashtags),
                'raw_text' => $step['fun_fact'] ?? '',
            ],
            'cytat' => [
                'type' => 'cytat',
                'text' => $this->formatPost($this->getRandomQuote($character), $character, $hashtags),
                'raw_text' => $this->getRandomQuote($character),
            ],
        ];
    }

    public function buildHashtags(string $character, string $location): string
    {
        $tags = array_merge(
            self::BASE_HASHTAGS,
            [self::CHARACTER_HASHTAGS[$character] ?? ''],
            [self::LOCATION_HASHTAGS[$location] ?? ''],
            self::GENERIC_HASHTAGS,
        );

        return implode(' ', array_filter($tags));
    }

    public function getRandomQuote(string $character): string
    {
        $quotes = self::QUOTES[$character] ?? self::QUOTES['scout'];
        return $quotes[array_rand($quotes)];
    }

    /**
     * Save week history to var/social-history.json
     * @param Mission[] $missions
     */
    public function saveHistory(string $weekId, array $missions): void
    {
        $path = $this->projectDir . '/var/social-history.json';
        $history = file_exists($path) ? json_decode(file_get_contents($path), true) : ['weeks' => []];
        $history['weeks'][$weekId] = array_map(fn(Mission $m) => $m->getId(), $missions);
        file_put_contents($path, json_encode($history, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
    }

    /**
     * Generate manifest.json content
     * @param Mission[] $missions
     */
    public function buildManifest(string $weekId, array $missions): array
    {
        $posts = [];
        foreach ($missions as $i => $mission) {
            $num = str_pad($i + 1, 2, '0', STR_PAD_LEFT);
            $posts[] = [
                'day' => self::DAYS[$i],
                'directory' => "{$num}-{$mission->getCharacter()}",
                'character' => $mission->getCharacter(),
                'variants' => ['zagadka', 'ciekawostka', 'cytat'],
                'mission_id' => $mission->getId(),
            ];
        }

        return [
            'week' => $weekId,
            'generated_at' => (new \DateTimeImmutable())->format('c'),
            'posts' => $posts,
        ];
    }

    public function getOutputDir(string $weekId): string
    {
        return $this->projectDir . '/var/social-content/week-' . $weekId;
    }

    private function formatPost(string $content, string $character, string $hashtags): string
    {
        $chars = $this->characterService->getBySlug($character);
        $emoji = $chars['emoji'] ?? '';

        // Trim content to ~200 chars
        if (mb_strlen($content) > 180) {
            $content = mb_substr($content, 0, 177) . '...';
        }

        return "{$emoji} {$content}\n\n{$hashtags}";
    }

    private function getUsedMissionIds(): array
    {
        $path = $this->projectDir . '/var/social-history.json';
        if (!file_exists($path)) {
            return [];
        }
        $history = json_decode(file_get_contents($path), true);
        $ids = [];
        foreach (($history['weeks'] ?? []) as $weekIds) {
            $ids = array_merge($ids, $weekIds);
        }
        return array_unique($ids);
    }
}
```

- [ ] **Step 2: Verify service autoloading**

```bash
php bin/console debug:container App\Service\SocialContentService
```

Expected: service is registered.

- [ ] **Step 3: Commit**

```bash
git add src/Service/SocialContentService.php
git commit -m "feat: add SocialContentService for social media content generation"
```

---

### Task 3: SocialImageGenerator — PNG via GD

**Files:**
- Create: `src/Service/SocialImageGenerator.php`

- [ ] **Step 1: Create image generator service**

```php
<?php

namespace App\Service;

class SocialImageGenerator
{
    private const WIDTH = 1080;
    private const HEIGHT = 1080;
    private const PADDING = 80;
    private const MAX_TEXT_WIDTH = 920; // WIDTH - 2*PADDING

    private const CHARACTER_COLORS = [
        'scout' => ['bg' => [214, 231, 250], 'accent' => [74, 144, 217]],
        'blitz' => ['bg' => [254, 226, 214], 'accent' => [255, 107, 53]],
        'nova'  => ['bg' => [232, 214, 250], 'accent' => [123, 45, 142]],
        'echo'  => ['bg' => [214, 245, 227], 'accent' => [45, 142, 94]],
    ];

    private const CHARACTER_EMOJI = [
        'scout' => "\xF0\x9F\x90\x95", // 🐕
        'blitz' => "\xF0\x9F\x90\x87", // 🐇
        'nova'  => "\xF0\x9F\xA6\x9D", // 🦝
        'echo'  => "\xF0\x9F\x90\xA2", // 🐢
    ];

    public function __construct(
        private string $projectDir,
    ) {}

    /**
     * Generate a 1080x1080 PNG social media image.
     */
    public function generate(string $text, string $character, string $type, string $outputPath): void
    {
        $fontPath = $this->projectDir . '/var/fonts/Inter-Bold.ttf';
        if (!file_exists($fontPath)) {
            // Fallback: try variable font
            $fontPath = $this->projectDir . '/var/fonts/InterVariable.ttf';
        }
        if (!file_exists($fontPath)) {
            throw new \RuntimeException('Font file not found in var/fonts/. Run: curl to download Inter font.');
        }

        $colors = self::CHARACTER_COLORS[$character] ?? self::CHARACTER_COLORS['scout'];

        $img = imagecreatetruecolor(self::WIDTH, self::HEIGHT);

        // Background
        $bgColor = imagecolorallocate($img, $colors['bg'][0], $colors['bg'][1], $colors['bg'][2]);
        imagefill($img, 0, 0, $bgColor);

        // Accent bar at top
        $accentColor = imagecolorallocate($img, $colors['accent'][0], $colors['accent'][1], $colors['accent'][2]);
        imagefilledrectangle($img, 0, 0, self::WIDTH, 12, $accentColor);

        // Type label
        $labelColor = imagecolorallocate($img, $colors['accent'][0], $colors['accent'][1], $colors['accent'][2]);
        $typeLabels = ['zagadka' => 'ZAGADKA', 'ciekawostka' => 'CZY WIESZ, ŻE...', 'cytat' => 'CYTAT DNIA'];
        $label = $typeLabels[$type] ?? mb_strtoupper($type);
        $labelFontSize = 28;
        $labelBox = imagettfbbox($labelFontSize, 0, $fontPath, $label);
        $labelX = (self::WIDTH - ($labelBox[2] - $labelBox[0])) / 2;
        imagettftext($img, $labelFontSize, 0, (int)$labelX, 100, $labelColor, $fontPath, $label);

        // Main text (wrapped)
        $textColor = imagecolorallocate($img, 26, 29, 31); // --color-dark
        $fontSize = $this->calculateFontSize($text, $fontPath);
        $lines = $this->wrapText($text, $fontPath, $fontSize);
        $lineHeight = $fontSize * 1.5;
        $totalTextHeight = count($lines) * $lineHeight;
        $startY = (self::HEIGHT - $totalTextHeight) / 2 + $fontSize;

        foreach ($lines as $i => $line) {
            $box = imagettfbbox($fontSize, 0, $fontPath, $line);
            $lineWidth = $box[2] - $box[0];
            $x = (self::WIDTH - $lineWidth) / 2;
            $y = $startY + ($i * $lineHeight);
            imagettftext($img, $fontSize, 0, (int)$x, (int)$y, $textColor, $fontPath, $line);
        }

        // Logo "TROPO" at bottom
        $logoFontSize = 32;
        $logoColor = imagecolorallocate($img, $colors['accent'][0], $colors['accent'][1], $colors['accent'][2]);
        $logoBox = imagettfbbox($logoFontSize, 0, $fontPath, 'TROPO');
        $logoX = (self::WIDTH - ($logoBox[2] - $logoBox[0])) / 2;
        imagettftext($img, $logoFontSize, 0, (int)$logoX, self::HEIGHT - 60, $logoColor, $fontPath, 'TROPO');

        // Bottom accent bar
        imagefilledrectangle($img, 0, self::HEIGHT - 12, self::WIDTH, self::HEIGHT, $accentColor);

        // Save
        $dir = dirname($outputPath);
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
        imagepng($img, $outputPath);
        imagedestroy($img);
    }

    private function calculateFontSize(string $text, string $fontPath): float
    {
        $len = mb_strlen($text);
        if ($len < 50) return 48.0;
        if ($len < 100) return 40.0;
        if ($len < 150) return 34.0;
        return 28.0;
    }

    private function wrapText(string $text, string $fontPath, float $fontSize): array
    {
        $words = explode(' ', $text);
        $lines = [];
        $currentLine = '';

        foreach ($words as $word) {
            $testLine = $currentLine === '' ? $word : $currentLine . ' ' . $word;
            $box = imagettfbbox($fontSize, 0, $fontPath, $testLine);
            $lineWidth = $box[2] - $box[0];

            if ($lineWidth > self::MAX_TEXT_WIDTH && $currentLine !== '') {
                $lines[] = $currentLine;
                $currentLine = $word;
            } else {
                $currentLine = $testLine;
            }
        }
        if ($currentLine !== '') {
            $lines[] = $currentLine;
        }

        return $lines;
    }
}
```

- [ ] **Step 2: Verify service autoloading**

```bash
php bin/console debug:container App\Service\SocialImageGenerator
```

- [ ] **Step 3: Commit**

```bash
git add src/Service/SocialImageGenerator.php
git commit -m "feat: add SocialImageGenerator for 1080x1080 PNG social cards via GD"
```

---

## Chunk 2: Command + Integration

### Task 4: GenerateSocialContentCommand

**Files:**
- Create: `src/Command/GenerateSocialContentCommand.php`

- [ ] **Step 1: Create the command**

```php
<?php

namespace App\Command;

use App\Service\SocialContentService;
use App\Service\SocialImageGenerator;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

#[AsCommand(
    name: 'app:generate-social-content',
    description: 'Generate social media posts (Instagram/TikTok) from mission data',
)]
class GenerateSocialContentCommand extends Command
{
    public function __construct(
        private SocialContentService $contentService,
        private SocialImageGenerator $imageGenerator,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('week', null, InputOption::VALUE_REQUIRED, 'Week ID (e.g. 2026-12). Default: current week')
            ->addOption('text-only', null, InputOption::VALUE_NONE, 'Skip image generation')
            ->addOption('force', null, InputOption::VALUE_NONE, 'Ignore mission history (allow repeats)')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);

        $weekId = $input->getOption('week') ?? (new \DateTimeImmutable())->format('Y-W');
        $textOnly = $input->getOption('text-only');
        $force = $input->getOption('force');

        $io->title("Generating social content for week {$weekId}");

        // 1. Select missions
        $missions = $this->contentService->selectMissions($weekId, $force);
        $io->info(sprintf('Selected %d missions', count($missions)));

        // 2. Prepare output directory
        $outputDir = $this->contentService->getOutputDir($weekId);
        if (is_dir($outputDir)) {
            $io->warning("Output directory already exists: {$outputDir}");
            // Clean it
            $this->removeDir($outputDir);
        }
        mkdir($outputDir, 0755, true);

        // 3. Generate content for each mission
        foreach ($missions as $i => $mission) {
            $num = str_pad($i + 1, 2, '0', STR_PAD_LEFT);
            $dirName = "{$num}-{$mission->getCharacter()}";
            $missionDir = "{$outputDir}/{$dirName}";

            $io->section("#{$num}: {$mission->getCharacter()} — {$mission->getTitle()}");

            $variants = $this->contentService->generateVariants($mission);

            foreach ($variants as $variantName => $variant) {
                $variantDir = "{$missionDir}/{$variantName}";
                mkdir($variantDir, 0755, true);

                // post.txt
                file_put_contents("{$variantDir}/post.txt", $variant['text']);

                // meta.json
                $meta = [
                    'mission_id' => $mission->getId(),
                    'character' => $mission->getCharacter(),
                    'location' => $mission->getLocation(),
                    'difficulty' => $mission->getDifficulty(),
                    'type' => $variant['type'],
                    'generated_at' => (new \DateTimeImmutable())->format('c'),
                ];
                file_put_contents("{$variantDir}/meta.json", json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));

                // image.png
                if (!$textOnly) {
                    $this->imageGenerator->generate(
                        $variant['raw_text'],
                        $mission->getCharacter(),
                        $variantName,
                        "{$variantDir}/image.png",
                    );
                    $io->text("  ✓ {$variantName}: image + text");
                } else {
                    $io->text("  ✓ {$variantName}: text only");
                }
            }
        }

        // 4. Write manifest
        $manifest = $this->contentService->buildManifest($weekId, $missions);
        file_put_contents(
            "{$outputDir}/manifest.json",
            json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
        );

        // 5. Save history
        $this->contentService->saveHistory($weekId, $missions);

        $io->success("Generated content in: {$outputDir}");
        $io->table(
            ['Day', 'Character', 'Mission'],
            array_map(fn($p) => [$p['day'], $p['character'], $p['mission_id']], $manifest['posts']),
        );

        return Command::SUCCESS;
    }

    private function removeDir(string $dir): void
    {
        $files = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::CHILD_FIRST,
        );
        foreach ($files as $file) {
            $file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath());
        }
        rmdir($dir);
    }
}
```

- [ ] **Step 2: Verify command is registered**

```bash
php bin/console list app
```

Expected: `app:generate-social-content` appears in list.

- [ ] **Step 3: Commit**

```bash
git add src/Command/GenerateSocialContentCommand.php
git commit -m "feat: add app:generate-social-content command"
```

---

### Task 5: Test run + fixes

- [ ] **Step 1: Run text-only generation**

```bash
php bin/console app:generate-social-content --text-only
```

Expected: Creates `var/social-content/week-YYYY-WW/` with 7 subdirectories, each containing 3 variant folders with `post.txt` + `meta.json`.

- [ ] **Step 2: Verify output structure**

```bash
find var/social-content/ -type f | head -30
```

Expected structure:
```
var/social-content/week-2026-12/manifest.json
var/social-content/week-2026-12/01-scout/zagadka/post.txt
var/social-content/week-2026-12/01-scout/zagadka/meta.json
...
```

- [ ] **Step 3: Verify post.txt content**

```bash
cat var/social-content/week-*/01-*/zagadka/post.txt
```

Expected: Polish text + hashtags, max ~200 chars content + hashtag block.

- [ ] **Step 4: Run with images**

```bash
php bin/console app:generate-social-content --force
```

Expected: Same structure but each variant also has `image.png` (1080x1080).

- [ ] **Step 5: Verify image**

```bash
file var/social-content/week-*/01-*/zagadka/image.png
identify var/social-content/week-*/01-*/zagadka/image.png 2>/dev/null || php -r "print_r(getimagesize('$(ls var/social-content/week-*/01-*/zagadka/image.png | head -1)'));"
```

Expected: PNG, 1080x1080.

- [ ] **Step 6: Verify history**

```bash
cat var/social-history.json
```

Expected: JSON with current week's mission IDs.

- [ ] **Step 7: Verify manifest**

```bash
cat var/social-content/week-*/manifest.json
```

Expected: 7 entries with day, directory, character, mission_id.

- [ ] **Step 8: Fix any issues found during testing**

Address any runtime errors, font issues, text wrapping problems.

- [ ] **Step 9: Final commit**

```bash
git add src/
git commit -m "feat: social content generator — working command with text + image output"
```

---

### Task 6: Update CLAUDE.md

**Files:**
- Modify: `CLAUDE.md`

- [ ] **Step 1: Add command to CLAUDE.md**

Add to the Komendy section:
```bash
php bin/console app:generate-social-content [--week=YYYY-WW] [--text-only] [--force]
```

- [ ] **Step 2: Commit**

```bash
git add CLAUDE.md
git commit -m "docs: add generate-social-content command to CLAUDE.md"
```
