From 14528eb35eb3796330078ae1680a91d2e83ee633 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Fri, 3 Jul 2026 05:32:38 +0700 Subject: [PATCH] feat: add comprehensive design documentation for dynamic GitHub profile README --- ...026-07-03-profile-readme-implementation.md | 622 ++++++++++++++++++ .../specs/2026-07-03-profile-readme-design.md | 217 ++++++ 2 files changed, 839 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-03-profile-readme-implementation.md create mode 100644 docs/superpowers/specs/2026-07-03-profile-readme-design.md diff --git a/docs/superpowers/plans/2026-07-03-profile-readme-implementation.md b/docs/superpowers/plans/2026-07-03-profile-readme-implementation.md new file mode 100644 index 0000000..7ac5d53 --- /dev/null +++ b/docs/superpowers/plans/2026-07-03-profile-readme-implementation.md @@ -0,0 +1,622 @@ +# Ultimate Profile README Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Transform the GitHub profile README into a dynamic, visually stunning page with AI-generated activity log, contribution snake animation, and comprehensive GitHub metrics. + +**Architecture:** Static HTML-in-markdown layout with dynamic `github-readme-stats` SVG cards loaded at view time, plus two GitHub Actions workflows: one for contribution snake (daily cron), one for AI activity log (every 6h cron via Node.js script). + +**Tech Stack:** Markdown + HTML (README), YAML (GitHub Actions), Node.js 20 (AI script), OpenAI-compatible LLM API + +## Global Constraints + +- GitHub username: `asepharyana` +- All auto-commits MUST use `[skip ci]` in commit message to prevent infinite action loops +- Auto-commit author MUST be `github-actions[bot]` +- AI API key, base URL, and model name stored as GitHub Actions secrets (not in code) +- Snake SVGs committed to git and referenced from README via relative path +- Section markers `` / `` delimit AI-generated content in README + +--- + +### Task 1: Static README layout + .gitignore + assets scaffolding + +**Files:** +- Create: `README.md` +- Create: `.gitignore` +- Create: `assets/generated/.gitkeep` + +**Interfaces:** +- Consumes: nothing +- Produces: The full README visual surface; Section markers at `..` for Task 3; `` snake path `assets/generated/snake.svg` for Task 2 + +- [ ] **Step 1: Create .gitignore** + +```gitignore +# Node.js +node_modules/ +npm-debug.log* +package-lock.json + +# OS files +.DS_Store +Thumbs.db + +# Environment (secrets template — actual secrets go to GitHub Actions) +.env +``` + +- [ ] **Step 2: Create assets/generated/.gitkeep** + +```bash +mkdir -p assets/generated && touch assets/generated/.gitkeep +``` + +- [ ] **Step 3: Generate README header (banner, animated typing, visitor badge)** + +Write to `README.md`: + +```markdown +
+ + + + +
+ +

+ Typing SVG +

+ +

+ Visitor count +

+``` + +- [ ] **Step 4: Add Tech Stack badges section** + +Append to `README.md`: + +```markdown +## 🛠️ Tech Stack + +

+ + + + + + + + + + + + +

+``` + +- [ ] **Step 5: Add GitHub Stats dashboard (3-column layout + trophies)** + +Append to `README.md`: + +```markdown +## 📊 GitHub Analytics + +

+ + + + + +

+ +

+ + + +

+``` + +- [ ] **Step 6: Add AI Activity Log section (with dynamic markers)** + +Append to `README.md`: + +```markdown +## 🤖 AI Activity Log + +> *Aktivitas GitHub terbaru, dirangkum oleh AI* + + +

+ ▸ Menunggu aktivitas pertama... 🚀 +

+ +``` + +- [ ] **Step 7: Add Contribution Snake section** + +Append to `README.md`: + +```markdown +## 🐍 Contribution Graph + + + + + Contribution snake animation + +``` + +- [ ] **Step 8: Add Connect / Footer section** + +Append to `README.md`: + +```markdown +## 📫 Let's Connect + +

+ + + + + + + + + +

+ +--- + +

+ +

+ +

+ Made with ❤️ and lots of ☕ +

+``` + +- [ ] **Step 9: Commit all static files** + +```bash +git add .gitignore assets/generated/.gitkeep README.md +git commit -m "feat: initial profile README with static layout" +``` + +--- + +### Task 2: Contribution snake GitHub Action + +**Files:** +- Create: `.github/workflows/snake.yml` + +**Interfaces:** +- Consumes: `assets/generated/snake-dark.svg`, `assets/generated/snake-light.svg` (creates them) +- Produces: Snake SVGs committed to `assets/generated/` + +- [ ] **Step 1: Create workflows directory** + +```bash +mkdir -p .github/workflows +``` + +- [ ] **Step 2: Create snake.yml workflow** + +Write to `.github/workflows/snake.yml`: + +```yaml +name: Generate Snake + +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate Snake (Dark) + uses: Platane/snk/svg-only@v3 + with: + github_user_name: asepharyana + outputs: | + assets/generated/snake-dark.svg?palette=github-dark + + - name: Generate Snake (Light) + uses: Platane/snk/svg-only@v3 + with: + github_user_name: asepharyana + outputs: | + assets/generated/snake-light.svg?palette=github-light + + - name: Push to repo + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: update contribution snake [skip ci]" + file_pattern: assets/generated/snake-*.svg +``` + +- [ ] **Step 3: Commit snake workflow** + +```bash +git add .github/workflows/snake.yml +git commit -m "feat: add contribution snake GitHub Action" +``` + +--- + +### Task 3: AI Activity log script + workflow + +**Files:** +- Create: `scripts/generate-ai-blog.js` +- Create: `.github/workflows/ai-activity.yml` + +**Interfaces:** +- Consumes: README markers `` / `` from Task 1 +- Produces: Updated AI content between markers in README.md +- Uses GH Actions secrets: `AI_LLM_API_KEY`, `AI_LLM_BASE_URL`, `AI_LLM_MODEL` +- Uses implicit `GITHUB_TOKEN` for GitHub API access + +- [ ] **Step 1: Create scripts directory** + +```bash +mkdir -p scripts +``` + +- [ ] **Step 2: Write generate-ai-blog.js** + +Write to `scripts/generate-ai-blog.js`: + +```javascript +// scripts/generate-ai-blog.js +// Fetches recent GitHub activity → summarizes via LLM → updates README.md markers +const https = require("https"); +const fs = require("fs"); +const path = require("path"); + +// ── Configuration ─────────────────────────────────────────────── +const GITHUB_USER = "asepharyana"; +const GITHUB_TOKEN = process.env.GITHUB_TOKEN || ""; +const AI_API_KEY = process.env.AI_LLM_API_KEY || ""; +const AI_BASE_URL = (process.env.AI_LLM_BASE_URL || "").replace(/\/+$/, ""); +const AI_MODEL = process.env.AI_LLM_MODEL || "text"; +const README_PATH = path.join(__dirname, "..", "README.md"); +const MARKER_START = ""; +const MARKER_END = ""; +const EVENTS_DAYS_BACK = 7; +const MAX_EVENTS = 100; + +// ── Helpers ───────────────────────────────────────────────────── + +/** Minimal HTTPS fetch returning body string */ +function fetch(url, options = {}) { + return new Promise((resolve, reject) => { + const req = https.get(url, { headers: options.headers || {} }, (res) => { + let data = ""; + res.on("data", (chunk) => (data += chunk)); + res.on("end", () => resolve({ status: res.statusCode, data })); + }); + req.on("error", reject); + req.setTimeout(15000, () => { req.destroy(); reject(new Error("Request timeout")); }); + }); +} + +/** Post JSON to URL and return parsed response */ +function postJSON(url, body, headers = {}) { + return new Promise((resolve, reject) => { + const urlObj = new URL(url); + const data = JSON.stringify(body); + const opts = { + hostname: urlObj.hostname, + port: urlObj.port || 443, + path: urlObj.pathname + urlObj.search, + method: "POST", + headers: { "Content-Type": "application/json", ...headers }, + }; + const req = https.request(opts, (res) => { + let raw = ""; + res.on("data", (chunk) => (raw += chunk)); + res.on("end", () => resolve({ status: res.statusCode, data: raw })); + }); + req.on("error", reject); + req.setTimeout(30000, () => { req.destroy(); reject(new Error("LLM request timeout")); }); + req.write(data); + req.end(); + }); +} + +/** Check if event happened within the last N days */ +function isRecent(createdAt, days) { + const then = new Date(createdAt).getTime(); + const cutoff = Date.now() - days * 24 * 60 * 60 * 1000; + return then >= cutoff; +} + +/** Format a single event into readable text */ +function formatEvent(event) { + const repo = event.repo?.name || "unknown"; + switch (event.type) { + case "PushEvent": { + const branch = (event.payload?.ref || "").replace("refs/heads/", ""); + const commits = event.payload?.commits || []; + const msgs = commits.slice(0, 3).map((c) => c.message?.split("\n")[0] || "no message").filter(Boolean); + return `• Push ${commits.length} commit${commits.length > 1 ? "s" : ""} ke \`${repo}\` (${branch}): ${msgs.join("; ")}`; + } + case "CreateEvent": { + const refType = event.payload?.ref_type || ""; + const ref = event.payload?.ref || refType; + return `• Membuat ${refType} baru: \`${ref}\` di \`${repo}\``; + } + case "PullRequestEvent": { + const action = event.payload?.action || ""; + const title = event.payload?.pull_request?.title || ""; + return `• ${action === "opened" ? "Open" : action === "closed" && event.payload?.pull_request?.merged ? "Merge" : action} PR: "${title}" di \`${repo}\``; + } + case "IssuesEvent": { + const action = event.payload?.action || ""; + const title = event.payload?.issue?.title || ""; + return `• ${action.charAt(0).toUpperCase() + action.slice(1)} issue: "${title}" di \`${repo}\``; + } + case "WatchEvent": + return `• ⭐ Star \`${repo}\``; + case "ForkEvent": + return `• 🍴 Fork \`${repo}\``; + default: + return `• ${event.type} di \`${repo}\``; + } +} + +/** Deduplicate events: group same repo + type, limit to 8 entries */ +function deduplicate(events) { + const seen = new Set(); + const result = []; + for (const ev of events) { + const key = `${ev.type}:${ev.repo?.name}`; + if (!seen.has(key)) { + seen.add(key); + result.push(ev); + if (result.length >= 8) break; + } + } + return result; +} + +// ── Main ──────────────────────────────────────────────────────── + +async function main() { + console.log("[AI Blog] Starting..."); + + // 1. Fetch GitHub events + if (!GITHUB_TOKEN) { + console.log("[AI Blog] No GITHUB_TOKEN, skipping activity fetch"); + writeFallback(); + return; + } + + const eventsUrl = `https://api.github.com/users/${GITHUB_USER}/events?per_page=${MAX_EVENTS}`; + const { status, data } = await fetch(eventsUrl, { + headers: { + "User-Agent": "generate-ai-blog", + Authorization: `Bearer ${GITHUB_TOKEN}`, + Accept: "application/vnd.github.v3+json", + }, + }); + + if (status !== 200) { + console.log(`[AI Blog] GitHub API returned ${status}, keeping existing content`); + return; + } + + let events; + try { + events = JSON.parse(data); + } catch { + console.log("[AI Blog] Failed to parse GitHub API response"); + writeFallback(); + return; + } + + if (!Array.isArray(events) || events.length === 0) { + console.log("[AI Blog] No events found"); + writeFallback(); + return; + } + + // 2. Filter to recent + relevant event types + const recent = events.filter( + (e) => + isRecent(e.created_at, EVENTS_DAYS_BACK) && + ["PushEvent", "CreateEvent", "PullRequestEvent", "IssuesEvent", "WatchEvent", "ForkEvent"].includes(e.type) + ); + + if (recent.length === 0) { + console.log("[AI Blog] No recent activity found"); + writeFallback(); + return; + } + + // 3. Format and deduplicate + const deduped = deduplicate(recent); + const formatted = deduped.map(formatEvent).join("\n"); + console.log(`[AI Blog] Found ${deduped.length} unique events:\n${formatted}`); + + // 4. Build LLM prompt + const prompt = `Buat summary aktivitas GitHub dalam bahasa Indonesia kasual, maksimal 3 kalimat, pakai emoji. Jangan pake format list. Langsung kontennya aja, tanpa intro:\n\n${formatted}`; + + // 5. Call LLM + if (!AI_API_KEY) { + console.log("[AI Blog] No AI_API_KEY, using raw activity text"); + writeContent(formatted, formatted); + return; + } + + const chatUrl = `${AI_BASE_URL}/chat/completions`; + const payload = { + model: AI_MODEL, + messages: [{ role: "user", content: prompt }], + max_tokens: 300, + temperature: 0.7, + }; + + console.log(`[AI Blog] Calling LLM at ${AI_BASE_URL}...`); + let llmResult; + try { + const llmResp = await postJSON(chatUrl, payload, { + Authorization: `Bearer ${AI_API_KEY}`, + }); + console.log(`[AI Blog] LLM response status: ${llmResp.status}`); + + if (llmResp.status === 200) { + const parsed = JSON.parse(llmResp.data); + llmResult = parsed.choices?.[0]?.message?.content?.trim(); + } + } catch (err) { + console.log(`[AI Blog] LLM call failed: ${err.message}`); + } + + const summary = llmResult || formatted.slice(0, 300); + writeContent(summary, formatted); +} + +/** Write content between README markers. Falls back to raw events if LLM fails. */ +function writeContent(summary, rawFallback) { + if (!fs.existsSync(README_PATH)) { + console.error(`[AI Blog] README.md not found at ${README_PATH}`); + process.exit(0); + } + + let readme = fs.readFileSync(README_PATH, "utf-8"); + const startIdx = readme.indexOf(MARKER_START); + const endIdx = readme.indexOf(MARKER_END); + + if (startIdx === -1 || endIdx === -1) { + console.error("[AI Blog] Section markers not found in README.md"); + process.exit(0); + } + + const before = readme.slice(0, startIdx + MARKER_START.length); + const after = readme.slice(endIdx); + const middle = `\n\n

\n ${summary}\n

\n\n`; + const newReadme = before + middle + after; + + fs.writeFileSync(README_PATH, newReadme, "utf-8"); + console.log(`[AI Blog] README.md updated successfully`); +} + +/** Write fallback text when no activity is found */ +function writeFallback() { + const fallback = "▸ Belum ada aktivitas GitHub dalam 7 hari terakhir. Pantau terus! 🚀"; + writeContent(fallback, fallback); +} + +main().catch((err) => { + console.error(`[AI Blog] Error: ${err.message}`); + process.exit(0); +}); +``` + +- [ ] **Step 3: Create ai-activity.yml workflow** + +Write to `.github/workflows/ai-activity.yml`: + +```yaml +name: AI Activity Log + +on: + schedule: + - cron: "0 */6 * * *" + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: write + +jobs: + generate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install --save-dev prettier + + - name: Generate AI activity log + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AI_LLM_API_KEY: ${{ secrets.AI_LLM_API_KEY }} + AI_LLM_BASE_URL: ${{ secrets.AI_LLM_BASE_URL }} + AI_LLM_MODEL: ${{ secrets.AI_LLM_MODEL }} + run: node scripts/generate-ai-blog.js + + - name: Format README + run: npx prettier --write README.md || true + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: update AI activity log [skip ci]" + file_pattern: README.md +``` + +- [ ] **Step 4: Create package.json (for prettier dependency)** + +```json +{ + "name": "profile-readme", + "private": true, + "scripts": { + "generate-ai": "node scripts/generate-ai-blog.js" + } +} +``` + +- [ ] **Step 5: Commit all AI activity files** + +```bash +git add scripts/generate-ai-blog.js .github/workflows/ai-activity.yml package.json +git commit -m "feat: add AI-powered activity log" +``` + +--- + +### Task 4: Final push & setup instructions + +**Files:** None (documentation + user action) + +- [ ] **Step 1: Push everything to GitHub** + +```bash +git push origin main +``` + +- [ ] **Step 2: Add secrets to GitHub repository** + +User needs to add these secrets via GitHub.com → Settings → Secrets and variables → Actions: + +| Secret | Value | +|---|---| +| `AI_LLM_API_KEY` | `sk-5dd268d88adb496b-818beb-6bc7498e` | +| `AI_LLM_BASE_URL` | `https://omniroute.imrnes.team/v1` | +| `AI_LLM_MODEL` | `text` | + +- [ ] **Step 3: Trigger initial runs** + +After secrets are set, manually trigger both workflows from GitHub Actions tab: +1. `Generate Snake` → verify snake SVGs appear in `assets/generated/` +2. `AI Activity Log` → verify README updates with AI summary diff --git a/docs/superpowers/specs/2026-07-03-profile-readme-design.md b/docs/superpowers/specs/2026-07-03-profile-readme-design.md new file mode 100644 index 0000000..b6b22bd --- /dev/null +++ b/docs/superpowers/specs/2026-07-03-profile-readme-design.md @@ -0,0 +1,217 @@ +# GitHub Profile README — Ultimate Dynamic Design + +## Overview + +Create a highly dynamic and visually stunning GitHub profile README for `asepharyana/asepharyana`. The README will auto-update via GitHub Actions, featuring AI-generated activity summaries, contribution visualizations, and comprehensive GitHub metrics. + +## Architecture + +### Repository Structure + +``` +asepharyana/ +├── .github/ +│ └── workflows/ +│ ├── ai-activity.yml # LLM-powered activity log (cron: every 6h) +│ └── snake.yml # Contribution snake SVG (cron: daily) +├── scripts/ +│ └── generate-ai-blog.js # Fetch activity → call LLM → update README +├── assets/ +│ └── generated/ # Auto-generated SVGs (checked into git — referenced by README; seed with .gitkeep) +├── README.md # The main profile README +└── docs/ + └── superpowers/specs/ + └── 2026-07-03-profile-readme-design.md +``` + +### Component Breakdown + +| Component | File | Responsibility | +|---|---|---| +| README Shell | `README.md` | Static layout with marker comments for dynamic content injection | +| AI Activity | `scripts/generate-ai-blog.js` | Fetch 7-day GitHub activity → format → send to LLM → write result | +| Snake Gen | `.github/workflows/snake.yml` | Uses `Platane/snk` action to render contribution grid as snake | +| Stats Cards | Inline in README | SVG cards via `github-readme-stats` API (queried at view time) | + +## User Configuration + +- **GitHub username:** `asepharyana` +- **LinkedIn:** `https://www.linkedin.com/in/asep-haryana-saputra-2014a5294/` +- **Website:** `https://asepharyana.my.id/` +- **AI LLM API:** `sk-5dd268d88adb496b-818beb-6bc7498e` @ `https://omniroute.imrnes.team/v1` model `text` +- **Role:** Mahasiswa | AI Enthusiast + +## README Layout & Section Design + +### 1. Header — Hero Section +- Custom banner (generated SVG or GitHub banner) +- Animated typing effect via markdown-compatible approach (JavaScript/CSS not available — use GIF from readme-typing-svg or similar SVG service) +- Name, tagline, role badges +- Visitor counter badge + +### 2. Tech Stack Badges +- Grouped by category: Languages, AI/ML, Tools +- Shields.io badges with flat design +- Dark mode compatible + +### 3. GitHub Stats Dashboard +- **3-column layout** using HTML tables/grid in markdown: + - GitHub Stats card (`github-readme-stats`) + - GitHub Streak card (`github-readme-streak-stats`) + - Top Languages card (`github-readme-stats`) +- GitHub Trophies row below stats (`github-profile-trophy`) + +### 4. AI Activity Log (Dynamic via Actions) +- **Section markers:** `` / `` +- Replaced every 6 hours by `generate-ai-blog.js` +- Content: 2-3 sentences in casual Indonesian with emojis +- Describes: recent commits, new repos, merged PRs, opened issues +- **Fallback:** "Belum ada aktivitas GitHub dalam 7 hari terakhir. Pantau terus! 🚀" + +### 5. Contribution Snake +- Animated SVG showing contribution grid as a snake eating squares +- Dark mode variant +- Generated daily via `snake.yml` + +### 6. Connect & Footer +- Social links row: LinkedIn, Website, Email (if public) +- "Visitors" count badge +- Fun fact section + +## AI Activity Generator — Detailed Spec + +### Workflow: `.github/workflows/ai-activity.yml` + +```yaml +name: AI Activity Log +on: + schedule: + - cron: "0 */6 * * *" # every 6 hours at :00 + push: + branches: [main] + workflow_dispatch: # manual trigger + +jobs: + generate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Generate AI activity log + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + AI_LLM_API_KEY: ${{ secrets.AI_LLM_API_KEY }} + AI_LLM_BASE_URL: ${{ secrets.AI_LLM_BASE_URL }} + AI_LLM_MODEL: ${{ secrets.AI_LLM_MODEL }} + run: node scripts/generate-ai-blog.js + + - name: Commit README changes + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add README.md + git diff --staged --quiet || git commit -m "chore: update AI activity log [skip ci]" + git push +``` + +### Script: `scripts/generate-ai-blog.js` + +**Input:** +1. GitHub Events API — `/users/{username}/events?per_page=100` +2. Filter: only events from last 7 days +3. Normalize to structured format: + - `PushEvent` → {type: "commit", repo, branch, message_preview} + - `CreateEvent` → {type: "create", repo, ref_type: "repo"|"branch"} + - `PullRequestEvent` → {type: "pr", repo, action: "opened"|"merged"|"closed"} + - `IssuesEvent` → {type: "issue", repo, action, title} + - `WatchEvent` → {type: "star", repo} + - `ForkEvent` → {type: "fork", repo} +4. Deduplicate: group same repo + same type + +**LLM Call:** +``` +POST {base_url}/chat/completions +Authorization: Bearer {api_key} + +{ + "model": "{model}", + "messages": [ + { + "role": "user", + "content": "Buat summary aktivitas GitHub dalam bahasa Indonesia kasual, maksimal 3 kalimat, pakai emoji. Jangan pake format list. Langsung kontennya aja:\n\n{formatted_activity}" + } + ], + "max_tokens": 300, + "temperature": 0.7 +} +``` + +**Output:** +Replace content between `` and `` in README.md. + +**Error Handling:** +- If GitHub API fails (no token, rate limit): skip block, keep previous content +- If LLM API fails (timeout, bad response, auth error): use fallback text +- If no activity in 7 days: fallback text +- If README markers not found: log error, exit 0 (don't fail action) + +## GitHub Actions — All Workflows + +### Snake Contribution +```yaml +name: Generate Snake +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Platane/snk@v3 + with: + github_user_name: asepharyana + outputs: | + assets/generated/snake.svg?palette=github-dark + assets/generated/snake-light.svg?palette=github-light + - uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: update contribution snake [skip ci]" +``` + +> **Note:** GitHub stats cards (`github-readme-stats`, `github-readme-streak-stats`, `github-profile-trophy`) load dynamically at view time via `` URLs — no Action needed. They're not committed files. + +## Error Handling & Resilience + +| Failure Mode | Detection | Recovery | +|---|---|---| +| LLM API down | HTTP error / timeout | Keep previous AI log content, log warning | +| GitHub API rate limited | 403/429 response | Wait & retry once, then skip | +| Snake generation fails | Action exit code | Previous snake SVG stays cached | +| README edit conflict | git push rejected | Retry on next cron trigger | +| No events in 7 days | Empty filtered data | Write fallback "No recent activity" message | +| Missing section markers | Script can't find delimiters | Append section, mark as degraded | +| API key missing from secrets | Empty env var | Graceful skip with Action annotation | + +## Commit Strategy + +- All auto-commits use `[skip ci]` suffix to prevent infinite Action loops +- Author: `github-actions[bot]` +- Human edits to README only via direct push — next Action run won't overwrite marker-delimited sections that have changed since last fetch (we always re-fetch and re-write between markers) +- The entire `...` block is owned by the script — any human edits inside it will be overwritten on next run + +## Success Criteria + +- [ ] README renders with all sections visible on profile +- [ ] AI Activity section updates autonomously every 6 hours +- [ ] Snake animation appears and animates on profile +- [ ] Stats cards show accurate GitHub data +- [ ] All social links point to correct URLs +- [ ] Dark mode renders correctly (no invisible text/images) +- [ ] All error states produce graceful fallbacks visible to viewers +- [ ] No actions fail silently — failures are logged and non-fatal