# skill_records

skill_id: typescript-compile-check-resilient__v0_a699d9e7
name: typescript-compile-check-resilient
path: example/skills/typescript-compile-check-resilient/SKILL.md
category: workflow
visibility: private
lineage_origin: captured
lineage_source_task_id: evolution-v2-task-05
lineage_created_at: 2026-03-10T18:41:54.434183
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T18:41:54.434204
last_updated: 2026-03-10T18:41:54.434205
description:
TypeScript compile-checking in projects where npx and node_modules/.bin symlinks fail, with a filtering technique to isolate errors from only newly-created or task-specific files — ignoring pre-existing legacy errors.

lineage_change_summary:
Capture a workflow skill for TypeScript compile-checking when npx/symlinks fail, including a grep-based technique to isolate errors from newly-created files only.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -0,0 +1,146 @@
+---
+name: typescript-compile-check-resilient
+description: TypeScript compile-checking in projects where npx and node_modules/.bin symlinks fail, with a filtering technique to isolate errors from only newly-created or task-specific files — ignoring pre-existing legacy errors.
+---
+
+# TypeScript Compile-Check (Resilient) with File-Scoped Error Filtering
+
+Use this workflow when you need to validate TypeScript changes in a project but the standard `npx tsc` or `node_modules/.bin/tsc` invocations fail due to broken symlinks, missing PATH entries, or environment issues.
+
+---
+
+## When to Use This Skill
+
+- `npx tsc` fails with "command not found" or permission errors
+- `node_modules/.bin/tsc` symlink is broken or unresolvable
+- You need to validate only *newly-created* or *task-specific* files without being blocked by pre-existing compile errors in the broader codebase
+
+---
+
+## Step 1 — Invoke tsc Directly via node_modules
+
+Bypass `npx` and symlinks entirely by calling the TypeScript binary directly:
+
+```bash
+node_modules/typescript/bin/tsc --noEmit 2>&1; echo EXIT:$?
+```
+
+**Why this works:** The `tsc` CLI entry point is a plain Node.js script at `node_modules/typescript/bin/tsc`. It does not depend on symlinks or PATH resolution. The `--noEmit` flag checks types without writing output files. Capturing `EXIT:$?` lets you programmatically detect success (`EXIT:0`) vs failure (`EXIT:1` or higher).
+
+If the project uses a custom `tsconfig`, specify it explicitly:
+
+```bash
+node_modules/typescript/bin/tsc --noEmit --project tsconfig.json 2>&1; echo EXIT:$?
+```
+
+---
+
+## Step 2 — Filter Output to Task-Specific Files Only
+
+When a legacy codebase has pre-existing errors, the full `tsc` output will be noisy and may cause false negatives. Pipe through `grep` to isolate errors from only the files you created or modified:
+
+```bash
+node_modules/typescript/bin/tsc --noEmit 2>&1 \
+  | grep -E "(file-pattern)" ; echo EXIT:${PIPESTATUS[0]}
+```
+
+### Examples
+
+**Single new file:**
+```bash
+node_modules/typescript/bin/tsc --noEmit 2>&1 \
+  | grep -E "settings-store\.ts"
+```
+
+**Multiple new files (pipe-delimited pattern):**
+```bash
+node_modules/typescript/bin/tsc --noEmit 2>&1 \
+  | grep -E "(settings-keys|settings-store|preferences|SettingsModal)"
+```
+
+**All files in a specific directory:**
+```bash
+node_modules/typescript/bin/tsc --noEmit 2>&1 \
+  | grep -E "src/features/my-feature/"
+```
+
+**Check: no output = no errors in your files (success).**
+
+---
+
+## Step 3 — Capture Exit Code Correctly Through a Pipe
+
+When grepping, the shell exit code reflects `grep`, not `tsc`. Use `${PIPESTATUS[0]}` (bash) to recover the original `tsc` exit code:
+
+```bash
+node_modules/typescript/bin/tsc --noEmit 2>&1 \
+  | grep -E "(my-file-pattern)"; echo "TSC_EXIT:${PIPESTATUS[0]}"
+```
+
+Or store errors explicitly:
+
+```bash
+TSC_OUTPUT=$(node_modules/typescript/bin/tsc --noEmit 2>&1)
+TSC_EXIT=$?
+echo "$TSC_OUTPUT" | grep -E "(settings-keys|settings-store|preferences|SettingsModal)"
+echo "TSC_EXIT:$TSC_EXIT"
+```
+
+The second approach (storing output in a variable) is the most reliable since `$?` is captured before the pipe.
+
+---
+
+## Step 4 — Interpret Results
+
+| Condition | Meaning |
+|-----------|---------|
+| `TSC_EXIT:0`, no grep output | ✅ Full project clean, your files are fine |
+| `TSC_EXIT:1`, no grep output | ✅ Your files are fine; pre-existing errors elsewhere (acceptable) |
+| `TSC_EXIT:1`, grep output present | ❌ Your files have errors — fix them |
+| `TSC_EXIT:2` | ❌ tsc configuration error (bad tsconfig, missing typescript package) |
+
+---
+
+## Fallback: Verify TypeScript Is Installed
+
+If `node_modules/typescript/bin/tsc` does not exist:
+
+```bash
+# Check if typescript is installed
+ls node_modules/typescript/bin/tsc 2>/dev/null || echo "TypeScript not installed"
+
+# Install if needed
+npm install --save-dev typescript
+# or
+yarn add --dev typescript
+```
+
+---
+
+## Full Example
+
+```bash
+# Store full tsc output and exit code
+TSC_OUTPUT=$(node_modules/typescript/bin/tsc --noEmit 2>&1)
+TSC_EXIT=$?
+
+# Show only errors in newly-created files
+NEW_FILE_ERRORS=$(echo "$TSC_OUTPUT" | grep -E "(settings-keys|settings-store|preferences|SettingsModal)")
+
+if [ -z "$NEW_FILE_ERRORS" ]; then
+  echo "✅ No TypeScript errors in new files (TSC_EXIT:$TSC_EXIT)"
+else
+  echo "❌ TypeScript errors in new files:"
+  echo "$NEW_FILE_ERRORS"
+  exit 1
+fi
+```
+
+---
+
+## Notes
+
+- This technique is safe for CI and local environments alike.
+- The direct `node_modules/typescript/bin/tsc` path works on macOS, Linux, and Windows (via `node node_modules/typescript/bin/tsc` on Windows CMD).
+- On Windows, replace the bash script with: `node node_modules/typescript/bin/tsc --noEmit`
+- Always prefer `--noEmit` for validation-only checks to avoid side effects.

lineage_content_snapshot:
{"SKILL.md": "---\nname: typescript-compile-check-resilient\ndescription: TypeScript compile-checking in projects where npx and node_modules/.bin symlinks fail, with a filtering technique to isolate errors from only newly-created or task-specific files — ignoring pre-existing legacy errors.\n---\n\n# TypeScript Compile-Check (Resilient) with File-Scoped Error Filtering\n\nUse this workflow when you need to validate TypeScript changes in a project but the standard `npx tsc` or `node_modules/.bin/tsc` invocations fail due to broken symlinks, missing PATH entries, or environment issues.\n\n---\n\n## When to Use This Skill\n\n- `npx tsc` fails with \"command not found\" or permission errors\n- `node_modules/.bin/tsc` symlink is broken or unresolvable\n- You need to validate only *newly-created* or *task-specific* files without being blocked by pre-existing compile errors in the broader codebase\n\n---\n\n## Step 1 — Invoke tsc Directly via node_modules\n\nBypass `npx` and symlinks entirely by calling the TypeScript binary directly:\n\n```bash\nnode_modules/typescript/bin/tsc --noEmit 2>&1; echo EXIT:$?\n```\n\n**Why this works:** The `tsc` CLI entry point is a plain Node.js script at `node_modules/typescript/bin/tsc`. It does not depend on symlinks or PATH resolution. The `--noEmit` flag checks types without writing output files. Capturing `EXIT:$?` lets you programmatically detect success (`EXIT:0`) vs failure (`EXIT:1` or higher).\n\nIf the project uses a custom `tsconfig`, specify it explicitly:\n\n```bash\nnode_modules/typescript/bin/tsc --noEmit --project tsconfig.json 2>&1; echo EXIT:$?\n```\n\n---\n\n## Step 2 — Filter Output to Task-Specific Files Only\n\nWhen a legacy codebase has pre-existing errors, the full `tsc` output will be noisy and may cause false negatives. Pipe through `grep` to isolate errors from only the files you created or modified:\n\n```bash\nnode_modules/typescript/bin/tsc --noEmit 2>&1 \\\n  | grep -E \"(file-pattern)\" ; echo EXIT:${PIPESTATUS[0]}\n```\n\n### Examples\n\n**Single new file:**\n```bash\nnode_modules/typescript/bin/tsc --noEmit 2>&1 \\\n  | grep -E \"settings-store\\.ts\"\n```\n\n**Multiple new files (pipe-delimited pattern):**\n```bash\nnode_modules/typescript/bin/tsc --noEmit 2>&1 \\\n  | grep -E \"(settings-keys|settings-store|preferences|SettingsModal)\"\n```\n\n**All files in a specific directory:**\n```bash\nnode_modules/typescript/bin/tsc --noEmit 2>&1 \\\n  | grep -E \"src/features/my-feature/\"\n```\n\n**Check: no output = no errors in your files (success).**\n\n---\n\n## Step 3 — Capture Exit Code Correctly Through a Pipe\n\nWhen grepping, the shell exit code reflects `grep`, not `tsc`. Use `${PIPESTATUS[0]}` (bash) to recover the original `tsc` exit code:\n\n```bash\nnode_modules/typescript/bin/tsc --noEmit 2>&1 \\\n  | grep -E \"(my-file-pattern)\"; echo \"TSC_EXIT:${PIPESTATUS[0]}\"\n```\n\nOr store errors explicitly:\n\n```bash\nTSC_OUTPUT=$(node_modules/typescript/bin/tsc --noEmit 2>&1)\nTSC_EXIT=$?\necho \"$TSC_OUTPUT\" | grep -E \"(settings-keys|settings-store|preferences|SettingsModal)\"\necho \"TSC_EXIT:$TSC_EXIT\"\n```\n\nThe second approach (storing output in a variable) is the most reliable since `$?` is captured before the pipe.\n\n---\n\n## Step 4 — Interpret Results\n\n| Condition | Meaning |\n|-----------|---------|\n| `TSC_EXIT:0`, no grep output | ✅ Full project clean, your files are fine |\n| `TSC_EXIT:1`, no grep output | ✅ Your files are fine; pre-existing errors elsewhere (acceptable) |\n| `TSC_EXIT:1`, grep output present | ❌ Your files have errors — fix them |\n| `TSC_EXIT:2` | ❌ tsc configuration error (bad tsconfig, missing typescript package) |\n\n---\n\n## Fallback: Verify TypeScript Is Installed\n\nIf `node_modules/typescript/bin/tsc` does not exist:\n\n```bash\n# Check if typescript is installed\nls node_modules/typescript/bin/tsc 2>/dev/null || echo \"TypeScript not installed\"\n\n# Install if needed\nnpm install --save-dev typescript\n# or\nyarn add --dev typescript\n```\n\n---\n\n## Full Example\n\n```bash\n# Store full tsc output and exit code\nTSC_OUTPUT=$(node_modules/typescript/bin/tsc --noEmit 2>&1)\nTSC_EXIT=$?\n\n# Show only errors in newly-created files\nNEW_FILE_ERRORS=$(echo \"$TSC_OUTPUT\" | grep -E \"(settings-keys|settings-store|preferences|SettingsModal)\")\n\nif [ -z \"$NEW_FILE_ERRORS\" ]; then\n  echo \"✅ No TypeScript errors in new files (TSC_EXIT:$TSC_EXIT)\"\nelse\n  echo \"❌ TypeScript errors in new files:\"\n  echo \"$NEW_FILE_ERRORS\"\n  exit 1\nfi\n```\n\n---\n\n## Notes\n\n- This technique is safe for CI and local environments alike.\n- The direct `node_modules/typescript/bin/tsc` path works on macOS, Linux, and Windows (via `node node_modules/typescript/bin/tsc` on Windows CMD).\n- On Windows, replace the bash script with: `node node_modules/typescript/bin/tsc --noEmit`\n- Always prefer `--noEmit` for validation-only checks to avoid side effects."}


skill_id: write-file-special-chars-workaround-enhanced__v0_774d4e20
name: write-special-chars-ts-check
path: example/skills/write-special-chars-ts-check/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: evolution-v2-task-05
lineage_created_at: 2026-03-10T18:42:09.078915
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T18:42:09.078931
last_updated: 2026-03-10T18:42:09.078933
description:
Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) by using a Python heredoc script executed via shell.

lineage_change_summary:
Enhanced the skill to add TypeScript verification via `node_modules/typescript/bin/tsc --noEmit` with grep filtering for newly-written files, replacing unreliable npx/symlink approaches.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,9 +1,9 @@
 ---
-name: write-file-special-chars-workaround
-description: Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) by using a Python heredoc script executed via shell.
+name: write-file-special-chars-with-ts-verify
+description: Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) using a Python heredoc script, plus a reliable TypeScript type-checking step that filters output to only the newly-written files to avoid being misled by pre-existing errors.
 ---
 
-# Write File Special Characters Workaround
+# Write File Special Characters Workaround (with TypeScript Verification)
 
 ## Problem
 
@@ -18,19 +18,26 @@
 This is especially common when writing TypeScript, JavaScript, shell scripts,
 or any source file with string interpolation syntax.
 
-## Solution: Python Heredoc Workaround
+Additionally, after writing TypeScript files, you need to verify they are
+type-correct — but common methods like `npx tsc` or `./node_modules/.bin/tsc`
+can fail with `[ERROR] unknown error` due to timeout or symlink resolution
+issues. The reliable method is to call the TypeScript binary directly.
+
+## Solution: Python Heredoc Workaround + Direct TSC Verification
+
+### Phase 1: Write Files via Python Heredoc
 
 Write a small Python script to `/tmp/` that uses triple-quoted strings to
 embed the file content, then execute it with `python3`.
 
-### Step-by-Step
+#### Step-by-Step
 
 1. **Compose a Python writer script** using triple-quoted strings (`"""..."""`).
 2. **Write the Python script itself to `/tmp/`** — its content is plain enough
    that `write_file` will succeed (no problematic characters in the wrapper).
 3. **Execute** the script with `run_shell`: `python3 /tmp/write_<name>.py`
 
-### Escaping Rules Inside the Python Script
+#### Escaping Rules Inside the Python Script
 
 | Character | How to handle |
 |-----------|--------------|
@@ -38,7 +45,7 @@
 | Triple double-quote `"""` | Escape as `\"\"\"` or use `'''` strings instead |
 | Everything else (backticks, `$`, `'`, `{}`) | No escaping needed |
 
-### Template
+#### Template
 
 ```python
 #!/usr/bin/env python3
@@ -52,7 +59,7 @@
 print("File written successfully.")
 ```
 
-### Concrete Example
+#### Concrete Example
 
 Suppose you need to write a TypeScript file with template literals and
 apostrophes that causes `write_file` to fail:
@@ -83,10 +90,142 @@
 python3 /tmp/write_greeting.py
 ```
 
-**Step 3 — Verify:**
+**Step 3 — Verify the file was created:**
 
 ```bash
 cat /app/src/greeting.ts
+```
+
+### Phase 2: TypeScript Type-Checking (Reliable Method)
+
+After writing TypeScript files, always verify they introduce zero new type
+errors. Use the direct binary path — **not** `npx` or `.bin` symlinks, which
+are prone to timeout and path-resolution failures.
+
+#### The Reliable TSC Command
+
+```bash
+node node_modules/typescript/bin/tsc --noEmit 2>&1 | grep "src/greeting.ts"
+```
+
+Key points:
+- `node node_modules/typescript/bin/tsc` — invokes TypeScript directly via
+  `node`, bypassing npx overhead and symlink resolution entirely.
+- `--noEmit` — type-checks only; does not write output files.
+- `2>&1` — merges stderr into stdout so grep can filter everything.
+- `| grep "<filename>"` — filters output to only lines mentioning your
+  newly-written file(s), so pre-existing errors in other files do not
+  create false alarms.
+
+#### Why Not npx or .bin Symlinks?
+
+| Method | Risk |
+|--------|------|
+| `npx tsc --noEmit` | Frequently returns `[ERROR] unknown error` (timeout, network, PATH issues) |
+| `./node_modules/.bin/tsc --noEmit` | Symlink resolution can fail in some environments |
+| `node node_modules/typescript/bin/tsc --noEmit` | **Reliable** — direct JS execution, no symlinks, no network |
+
+#### Filtering for Multiple New Files
+
+When you've written several files, pipe through a more specific grep pattern:
+
+```bash
+node node_modules/typescript/bin/tsc --noEmit 2>&1 | grep -E "src/(greeting|utils|component)\.ts"
+```
+
+Or use a directory prefix to catch all new files in one folder:
+
+```bash
+node node_modules/typescript/bin/tsc --noEmit 2>&1 | grep "src/myfeature/"
+```
+
+#### Interpreting the Output
+
+- **No output / empty result** — grep found no errors in your new files. ✅
+  The files are type-correct (pre-existing errors elsewhere are unrelated).
+- **Lines printed** — type errors exist in your new files. ❌ Fix and re-check.
+
+Example clean output (no new errors):
+```
+(no output)
+```
+
+Example with errors:
+```
+src/greeting.ts(3,14): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
+```
+
+## Full End-to-End Example
+
+### Writing multiple TypeScript files and verifying them
+
+**Step 1 — Write the Python helper to `/tmp/`:**
+
+Use `write_file` with path `/tmp/write_settings_files.py`:
+
+```python
+#!/usr/bin/env python3
+import os
+
+files = {
+    "/app/src/settings/settings-keys.ts": """
+export const SETTINGS_KEYS = {
+  theme: 'app.theme',
+  language: 'app.language',
+} as const;
+
+export type SettingKey = keyof typeof SETTINGS_KEYS;
+""".lstrip("\n"),
+
+    "/app/src/settings/settings-store.ts": """
+import { SETTINGS_KEYS, SettingKey } from './settings-keys';
+
+export class SettingsStore {
+  private data: Record<string, string> = {};
+
+  get(key: SettingKey): string | undefined {
+    return this.data[SETTINGS_KEYS[key]];
+  }
+
+  set(key: SettingKey, value: string): void {
+    this.data[SETTINGS_KEYS[key]] = value;
+  }
+}
+""".lstrip("\n"),
+}
+
+for path, content in files.items():
+    os.makedirs(os.path.dirname(path), exist_ok=True)
+    with open(path, "w") as f:
+        f.write(content)
+    print(f"Written: {path}")
+```
+
+**Step 2 — Execute the helper:**
+
+```bash
+python3 /tmp/write_settings_files.py
+```
+
+**Step 3 — Verify file contents:**
+
+```bash
+cat /app/src/settings/settings-keys.ts
+cat /app/src/settings/settings-store.ts
+```
+
+**Step 4 — Type-check only the new files:**
+
+```bash
+cd /app && node node_modules/typescript/bin/tsc --noEmit 2>&1 | grep "src/settings/"
+```
+
+Expected output if all is well: *(empty — no errors in the new files)*
+
+**Step 5 — Optional cleanup:**
+
+```bash
+rm /tmp/write_settings_files.py
 ```
 
 ## When to Use This Pattern
@@ -95,6 +234,7 @@
 - The file contains TypeScript/JavaScript template literals.
 - The file contains mixed quote styles or shell-like special characters.
 - Any time direct file writing fails and content cannot be easily sanitized.
+- After writing TypeScript files and needing reliable type-checking.
 
 ## Multiple Files
 
@@ -136,4 +276,7 @@
 - **Handles all TypeScript syntax** — template literals, generics, JSX, etc.
 - **Atomic** — the target file is only created if the Python script succeeds.
 - **Debuggable** — the intermediate `.py` file can be inspected if needed.
-- **Cleanup (optional):** `rm /tmp/write_*.py` after all files are written.+- **Reliable type-checking** — direct `node` invocation avoids npx/symlink failures.
+- **Targeted verification** — grep filtering isolates errors in new files only,
+  preventing pre-existing project errors from masking or confusing results.
+- **Cleanup (optional):** `rm /tmp/write_*.py` after all files are written.

lineage_content_snapshot:
{"SKILL.md": "---\nname: write-file-special-chars-with-ts-verify\ndescription: Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) using a Python heredoc script, plus a reliable TypeScript type-checking step that filters output to only the newly-written files to avoid being misled by pre-existing errors.\n---\n\n# Write File Special Characters Workaround (with TypeScript Verification)\n\n## Problem\n\nThe `write_file` tool may fail with `[ERROR] unknown error` when file content\ncontains special characters such as:\n\n- Apostrophes / single quotes (`'`)\n- Backticks (`` ` ``)\n- Template literals (`` `${variable}` ``) common in TypeScript/JavaScript\n- Complex combinations of quotes and escape sequences\n\nThis is especially common when writing TypeScript, JavaScript, shell scripts,\nor any source file with string interpolation syntax.\n\nAdditionally, after writing TypeScript files, you need to verify they are\ntype-correct — but common methods like `npx tsc` or `./node_modules/.bin/tsc`\ncan fail with `[ERROR] unknown error` due to timeout or symlink resolution\nissues. The reliable method is to call the TypeScript binary directly.\n\n## Solution: Python Heredoc Workaround + Direct TSC Verification\n\n### Phase 1: Write Files via Python Heredoc\n\nWrite a small Python script to `/tmp/` that uses triple-quoted strings to\nembed the file content, then execute it with `python3`.\n\n#### Step-by-Step\n\n1. **Compose a Python writer script** using triple-quoted strings (`\"\"\"...\"\"\"`).\n2. **Write the Python script itself to `/tmp/`** — its content is plain enough\n   that `write_file` will succeed (no problematic characters in the wrapper).\n3. **Execute** the script with `run_shell`: `python3 /tmp/write_<name>.py`\n\n#### Escaping Rules Inside the Python Script\n\n| Character | How to handle |\n|-----------|--------------|\n| Backslash `\\` | Escape as `\\\\` |\n| Triple double-quote `\"\"\"` | Escape as `\\\"\\\"\\\"` or use `'''` strings instead |\n| Everything else (backticks, `$`, `'`, `{}`) | No escaping needed |\n\n#### Template\n\n```python\n#!/usr/bin/env python3\ncontent = \"\"\"\n<YOUR FILE CONTENT HERE>\n\"\"\".lstrip(\"\\n\")\n\nwith open(\"/path/to/target/file.ts\", \"w\") as f:\n    f.write(content)\n\nprint(\"File written successfully.\")\n```\n\n#### Concrete Example\n\nSuppose you need to write a TypeScript file with template literals and\napostrophes that causes `write_file` to fail:\n\n**Step 1 — Write the Python helper to `/tmp/`:**\n\nUse `write_file` with path `/tmp/write_greeting.py` and content:\n\n```python\n#!/usr/bin/env python3\ncontent = \"\"\"\nexport function greet(name: string): string {\n  const msg = `Hello, ${name}! It's a great day.`;\n  console.log(`Greeting: ${msg}`);\n  return msg;\n}\n\"\"\".lstrip(\"\\n\")\n\nwith open(\"/app/src/greeting.ts\", \"w\") as f:\n    f.write(content)\n\nprint(\"Written: /app/src/greeting.ts\")\n```\n\n**Step 2 — Execute the helper:**\n\n```bash\npython3 /tmp/write_greeting.py\n```\n\n**Step 3 — Verify the file was created:**\n\n```bash\ncat /app/src/greeting.ts\n```\n\n### Phase 2: TypeScript Type-Checking (Reliable Method)\n\nAfter writing TypeScript files, always verify they introduce zero new type\nerrors. Use the direct binary path — **not** `npx` or `.bin` symlinks, which\nare prone to timeout and path-resolution failures.\n\n#### The Reliable TSC Command\n\n```bash\nnode node_modules/typescript/bin/tsc --noEmit 2>&1 | grep \"src/greeting.ts\"\n```\n\nKey points:\n- `node node_modules/typescript/bin/tsc` — invokes TypeScript directly via\n  `node`, bypassing npx overhead and symlink resolution entirely.\n- `--noEmit` — type-checks only; does not write output files.\n- `2>&1` — merges stderr into stdout so grep can filter everything.\n- `| grep \"<filename>\"` — filters output to only lines mentioning your\n  newly-written file(s), so pre-existing errors in other files do not\n  create false alarms.\n\n#### Why Not npx or .bin Symlinks?\n\n| Method | Risk |\n|--------|------|\n| `npx tsc --noEmit` | Frequently returns `[ERROR] unknown error` (timeout, network, PATH issues) |\n| `./node_modules/.bin/tsc --noEmit` | Symlink resolution can fail in some environments |\n| `node node_modules/typescript/bin/tsc --noEmit` | **Reliable** — direct JS execution, no symlinks, no network |\n\n#### Filtering for Multiple New Files\n\nWhen you've written several files, pipe through a more specific grep pattern:\n\n```bash\nnode node_modules/typescript/bin/tsc --noEmit 2>&1 | grep -E \"src/(greeting|utils|component)\\.ts\"\n```\n\nOr use a directory prefix to catch all new files in one folder:\n\n```bash\nnode node_modules/typescript/bin/tsc --noEmit 2>&1 | grep \"src/myfeature/\"\n```\n\n#### Interpreting the Output\n\n- **No output / empty result** — grep found no errors in your new files. ✅\n  The files are type-correct (pre-existing errors elsewhere are unrelated).\n- **Lines printed** — type errors exist in your new files. ❌ Fix and re-check.\n\nExample clean output (no new errors):\n```\n(no output)\n```\n\nExample with errors:\n```\nsrc/greeting.ts(3,14): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.\n```\n\n## Full End-to-End Example\n\n### Writing multiple TypeScript files and verifying them\n\n**Step 1 — Write the Python helper to `/tmp/`:**\n\nUse `write_file` with path `/tmp/write_settings_files.py`:\n\n```python\n#!/usr/bin/env python3\nimport os\n\nfiles = {\n    \"/app/src/settings/settings-keys.ts\": \"\"\"\nexport const SETTINGS_KEYS = {\n  theme: 'app.theme',\n  language: 'app.language',\n} as const;\n\nexport type SettingKey = keyof typeof SETTINGS_KEYS;\n\"\"\".lstrip(\"\\n\"),\n\n    \"/app/src/settings/settings-store.ts\": \"\"\"\nimport { SETTINGS_KEYS, SettingKey } from './settings-keys';\n\nexport class SettingsStore {\n  private data: Record<string, string> = {};\n\n  get(key: SettingKey): string | undefined {\n    return this.data[SETTINGS_KEYS[key]];\n  }\n\n  set(key: SettingKey, value: string): void {\n    this.data[SETTINGS_KEYS[key]] = value;\n  }\n}\n\"\"\".lstrip(\"\\n\"),\n}\n\nfor path, content in files.items():\n    os.makedirs(os.path.dirname(path), exist_ok=True)\n    with open(path, \"w\") as f:\n        f.write(content)\n    print(f\"Written: {path}\")\n```\n\n**Step 2 — Execute the helper:**\n\n```bash\npython3 /tmp/write_settings_files.py\n```\n\n**Step 3 — Verify file contents:**\n\n```bash\ncat /app/src/settings/settings-keys.ts\ncat /app/src/settings/settings-store.ts\n```\n\n**Step 4 — Type-check only the new files:**\n\n```bash\ncd /app && node node_modules/typescript/bin/tsc --noEmit 2>&1 | grep \"src/settings/\"\n```\n\nExpected output if all is well: *(empty — no errors in the new files)*\n\n**Step 5 — Optional cleanup:**\n\n```bash\nrm /tmp/write_settings_files.py\n```\n\n## When to Use This Pattern\n\n- `write_file` returns `[ERROR] unknown error` for a specific file.\n- The file contains TypeScript/JavaScript template literals.\n- The file contains mixed quote styles or shell-like special characters.\n- Any time direct file writing fails and content cannot be easily sanitized.\n- After writing TypeScript files and needing reliable type-checking.\n\n## Multiple Files\n\nFor writing several problematic files in one pass, consolidate them into a\nsingle Python script:\n\n```python\n#!/usr/bin/env python3\nimport os\n\nfiles = {\n    \"/app/src/component.tsx\": \"\"\"\nimport React from 'react';\n\nconst App = () => (\n  <div className={`container`}>\n    <h1>It's working!</h1>\n  </div>\n);\n\nexport default App;\n\"\"\".lstrip(\"\\n\"),\n\n    \"/app/src/utils.ts\": \"\"\"\nexport const format = (val: number) => `Value: ${val.toFixed(2)}`;\n\"\"\".lstrip(\"\\n\"),\n}\n\nfor path, content in files.items():\n    os.makedirs(os.path.dirname(path), exist_ok=True)\n    with open(path, \"w\") as f:\n        f.write(content)\n    print(f\"Written: {path}\")\n```\n\n## Key Advantages\n\n- **No shell escaping required** — Python triple-quoted strings are literal.\n- **Handles all TypeScript syntax** — template literals, generics, JSX, etc.\n- **Atomic** — the target file is only created if the Python script succeeds.\n- **Debuggable** — the intermediate `.py` file can be inspected if needed.\n- **Reliable type-checking** — direct `node` invocation avoids npx/symlink failures.\n- **Targeted verification** — grep filtering isolates errors in new files only,\n  preventing pre-existing project errors from masking or confusing results.\n- **Cleanup (optional):** `rm /tmp/write_*.py` after all files are written.\n"}


skill_id: typescript-compile-check-resilient-fallback__v0_e1825d6c
name: typescript-compile-check-resilient-fallback
path: example/skills/typescript-compile-check-resilient-fallback/SKILL.md
category: workflow
visibility: private
lineage_origin: captured
lineage_source_task_id: evolution-v2-task-06
lineage_created_at: 2026-03-10T18:46:18.129029
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T18:46:18.129055
last_updated: 2026-03-10T18:46:18.129056
description:
A resilient workflow for running TypeScript compile checks (tsc --noEmit) that falls back gracefully when npx or direct binary invocations fail, and distinguishes pre-existing errors from newly introduced ones.

lineage_change_summary:
Capture the TypeScript compile-check resilient fallback pattern using shell_agent as a fallback when npx/direct tsc invocations fail, plus filtering pre-existing errors to detect only newly introduced regressions.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -0,0 +1,130 @@
+---
+name: typescript-compile-check-resilient-fallback
+description: A resilient workflow for running TypeScript compile checks (tsc --noEmit) that falls back gracefully when npx or direct binary invocations fail, and distinguishes pre-existing errors from newly introduced ones.
+---
+
+# TypeScript Compile-Check Resilient Fallback
+
+Use this workflow whenever you need to verify that TypeScript files compile
+cleanly after creating or modifying them, especially in environments where
+`npx` or direct `./node_modules/.bin/tsc` invocations may return unexpected
+errors.
+
+---
+
+## Step 1 — Attempt Primary Invocation
+
+Run the compile check with `run_shell` using the standard approach:
+
+```bash
+npx tsc --noEmit
+```
+
+or, if you already know the project root:
+
+```bash
+./node_modules/.bin/tsc --noEmit
+```
+
+**Evaluate the result:**
+- If it exits cleanly (exit code 0, no unexpected output) → done.
+- If it exits with **TypeScript diagnostic errors** → proceed to Step 3.
+- If it exits with an **unknown / tool-level error** (e.g., `npx` not found,
+  permission denied, spawn error, unclear non-zero exit unrelated to TS
+  diagnostics) → proceed to Step 2.
+
+---
+
+## Step 2 — Fallback via `shell_agent`
+
+When the primary invocation fails with an unknown error, delegate to
+`shell_agent` so it can resolve environment issues automatically:
+
+> Task: "From the project directory `<PROJECT_DIR>`, run
+> `node node_modules/typescript/bin/tsc --noEmit` and report all
+> TypeScript diagnostic errors."
+
+Example `shell_agent` task string:
+
+```
+From /path/to/project, run:
+  node node_modules/typescript/bin/tsc --noEmit
+Capture the full stdout/stderr output and return it.
+```
+
+`shell_agent` will handle PATH issues, working-directory changes, and
+retry on transient failures automatically.
+
+---
+
+## Step 3 — Filter Pre-Existing Errors
+
+TypeScript projects frequently have pre-existing errors in files you did
+**not** touch. To avoid false regressions:
+
+1. **Identify the baseline error count** for the project (e.g., determined
+   at task start or documented in task context). A common baseline is
+   **11 pre-existing errors** in unrelated files.
+2. **Collect the full diagnostic list** from tsc output.
+3. **Filter** to keep only errors whose file path matches a file you
+   created or modified in this task.
+4. **Evaluate:**
+   - Zero errors in your new/modified files → ✅ no regression introduced.
+   - One or more errors in your new/modified files → ❌ fix them before
+     proceeding.
+
+### Filtering heuristic (pseudocode)
+
+```
+all_errors   = parse_tsc_output(tsc_stdout)
+my_files     = [list of files created/modified in this task]
+new_errors   = [e for e in all_errors if e.file in my_files]
+
+if len(new_errors) == 0:
+    print("✅ No new TypeScript errors introduced.")
+else:
+    for err in new_errors:
+        print(f"❌ {err.file}:{err.line} — {err.message}")
+    raise Exception("Fix TypeScript errors before continuing.")
+```
+
+---
+
+## Step 4 — Fix and Re-Run
+
+If new errors are found:
+1. Fix the errors in your file(s).
+2. Repeat from **Step 1**.
+
+---
+
+## Decision Tree Summary
+
+```
+run_shell: npx tsc --noEmit
+        │
+        ├─ clean exit ──────────────────────────────► ✅ Done
+        │
+        ├─ TS diagnostic errors ────────────────────► Step 3 (filter)
+        │
+        └─ unknown/tool error
+                │
+                └─ shell_agent: node .../tsc --noEmit
+                        │
+                        ├─ TS diagnostic errors ────► Step 3 (filter)
+                        └─ clean exit ──────────────► ✅ Done
+```
+
+---
+
+## Notes
+
+- Always run tsc from the **project root** (where `tsconfig.json` lives).
+- The `node node_modules/typescript/bin/tsc` invocation is the most portable
+  fallback because it bypasses `npx`, shell PATH lookups, and binary
+  permission issues entirely.
+- When the baseline error count is unknown, run tsc **before** making any
+  changes and record the count; that becomes your baseline.
+- Only report/act on errors in files **you own**; never attempt to fix
+  pre-existing errors in third-party or unrelated project files unless
+  explicitly asked.

lineage_content_snapshot:
{"SKILL.md": "---\nname: typescript-compile-check-resilient-fallback\ndescription: A resilient workflow for running TypeScript compile checks (tsc --noEmit) that falls back gracefully when npx or direct binary invocations fail, and distinguishes pre-existing errors from newly introduced ones.\n---\n\n# TypeScript Compile-Check Resilient Fallback\n\nUse this workflow whenever you need to verify that TypeScript files compile\ncleanly after creating or modifying them, especially in environments where\n`npx` or direct `./node_modules/.bin/tsc` invocations may return unexpected\nerrors.\n\n---\n\n## Step 1 — Attempt Primary Invocation\n\nRun the compile check with `run_shell` using the standard approach:\n\n```bash\nnpx tsc --noEmit\n```\n\nor, if you already know the project root:\n\n```bash\n./node_modules/.bin/tsc --noEmit\n```\n\n**Evaluate the result:**\n- If it exits cleanly (exit code 0, no unexpected output) → done.\n- If it exits with **TypeScript diagnostic errors** → proceed to Step 3.\n- If it exits with an **unknown / tool-level error** (e.g., `npx` not found,\n  permission denied, spawn error, unclear non-zero exit unrelated to TS\n  diagnostics) → proceed to Step 2.\n\n---\n\n## Step 2 — Fallback via `shell_agent`\n\nWhen the primary invocation fails with an unknown error, delegate to\n`shell_agent` so it can resolve environment issues automatically:\n\n> Task: \"From the project directory `<PROJECT_DIR>`, run\n> `node node_modules/typescript/bin/tsc --noEmit` and report all\n> TypeScript diagnostic errors.\"\n\nExample `shell_agent` task string:\n\n```\nFrom /path/to/project, run:\n  node node_modules/typescript/bin/tsc --noEmit\nCapture the full stdout/stderr output and return it.\n```\n\n`shell_agent` will handle PATH issues, working-directory changes, and\nretry on transient failures automatically.\n\n---\n\n## Step 3 — Filter Pre-Existing Errors\n\nTypeScript projects frequently have pre-existing errors in files you did\n**not** touch. To avoid false regressions:\n\n1. **Identify the baseline error count** for the project (e.g., determined\n   at task start or documented in task context). A common baseline is\n   **11 pre-existing errors** in unrelated files.\n2. **Collect the full diagnostic list** from tsc output.\n3. **Filter** to keep only errors whose file path matches a file you\n   created or modified in this task.\n4. **Evaluate:**\n   - Zero errors in your new/modified files → ✅ no regression introduced.\n   - One or more errors in your new/modified files → ❌ fix them before\n     proceeding.\n\n### Filtering heuristic (pseudocode)\n\n```\nall_errors   = parse_tsc_output(tsc_stdout)\nmy_files     = [list of files created/modified in this task]\nnew_errors   = [e for e in all_errors if e.file in my_files]\n\nif len(new_errors) == 0:\n    print(\"✅ No new TypeScript errors introduced.\")\nelse:\n    for err in new_errors:\n        print(f\"❌ {err.file}:{err.line} — {err.message}\")\n    raise Exception(\"Fix TypeScript errors before continuing.\")\n```\n\n---\n\n## Step 4 — Fix and Re-Run\n\nIf new errors are found:\n1. Fix the errors in your file(s).\n2. Repeat from **Step 1**.\n\n---\n\n## Decision Tree Summary\n\n```\nrun_shell: npx tsc --noEmit\n        │\n        ├─ clean exit ──────────────────────────────► ✅ Done\n        │\n        ├─ TS diagnostic errors ────────────────────► Step 3 (filter)\n        │\n        └─ unknown/tool error\n                │\n                └─ shell_agent: node .../tsc --noEmit\n                        │\n                        ├─ TS diagnostic errors ────► Step 3 (filter)\n                        └─ clean exit ──────────────► ✅ Done\n```\n\n---\n\n## Notes\n\n- Always run tsc from the **project root** (where `tsconfig.json` lives).\n- The `node node_modules/typescript/bin/tsc` invocation is the most portable\n  fallback because it bypasses `npx`, shell PATH lookups, and binary\n  permission issues entirely.\n- When the baseline error count is unknown, run tsc **before** making any\n  changes and record the count; that becomes your baseline.\n- Only report/act on errors in files **you own**; never attempt to fix\n  pre-existing errors in third-party or unrelated project files unless\n  explicitly asked."}


skill_id: write-file-special-chars-workaround-enhanced_8cb2f2__v0_eaf02afb
name: write-special-chars-readback
path: example/skills/write-special-chars-readback/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: evolution-v2-task-06
lineage_created_at: 2026-03-10T18:46:37.163475
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T18:46:37.163492
last_updated: 2026-03-10T18:46:37.163493
description:
Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) by using a Python heredoc script executed via shell.

lineage_change_summary:
Enhanced the skill to prescribe explicit cleanup of /tmp helper scripts and a mandatory read_file verification step as standard workflow phases after writing files via Python heredoc.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,9 +1,9 @@
 ---
-name: write-file-special-chars-workaround
-description: Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) by using a Python heredoc script executed via shell.
+name: write-file-special-chars-workaround-verified
+description: Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) using a Python heredoc script, with mandatory post-write verification via read_file and cleanup of /tmp helper scripts.
 ---
 
-# Write File Special Characters Workaround
+# Write File Special Characters Workaround (Verified)
 
 ## Problem
 
@@ -29,6 +29,8 @@
 2. **Write the Python script itself to `/tmp/`** — its content is plain enough
    that `write_file` will succeed (no problematic characters in the wrapper).
 3. **Execute** the script with `run_shell`: `python3 /tmp/write_<name>.py`
+4. **Verify** the written file with `read_file` to confirm contents are correct.
+5. **Clean up** the `/tmp` helper script: `rm /tmp/write_<name>.py`
 
 ### Escaping Rules Inside the Python Script
 
@@ -83,11 +85,33 @@
 python3 /tmp/write_greeting.py
 ```
 
-**Step 3 — Verify:**
+**Step 3 — Verify with `read_file`:**
+
+Use `read_file` with path `/app/src/greeting.ts` to confirm the file content
+is exactly what was intended. This catches silent truncation or encoding
+issues that shell `cat` output might obscure.
+
+**Step 4 — Clean up the helper:**
 
 ```bash
-cat /app/src/greeting.ts
+rm /tmp/write_greeting.py
 ```
+
+This keeps `/tmp` tidy and prevents stale helper scripts from being confused
+with current ones on subsequent runs.
+
+## Standard Post-Write Checklist
+
+After every use of this pattern, always complete **all three** of these steps:
+
+| Step | Action | Why |
+|------|---------|-----|
+| ✅ Execute | `python3 /tmp/write_<name>.py` | Writes the target file |
+| ✅ Verify | `read_file` on the target path | Confirms content integrity |
+| ✅ Clean up | `rm /tmp/write_<name>.py` | Prevents stale helper accumulation |
+
+Skipping verification risks silently shipping a truncated or malformed file.
+Skipping cleanup risks confusion when the same helper name is reused later.
 
 ## When to Use This Pattern
 
@@ -130,10 +154,22 @@
     print(f"Written: {path}")
 ```
 
+After running the multi-file script:
+
+1. Use `read_file` on **each** target path to verify all files were written
+   correctly.
+2. Then clean up: `rm /tmp/write_<name>.py`
+
 ## Key Advantages
 
 - **No shell escaping required** — Python triple-quoted strings are literal.
 - **Handles all TypeScript syntax** — template literals, generics, JSX, etc.
 - **Atomic** — the target file is only created if the Python script succeeds.
 - **Debuggable** — the intermediate `.py` file can be inspected if needed.
-- **Cleanup (optional):** `rm /tmp/write_*.py` after all files are written.+- **Cleanup (required):** `rm /tmp/write_*.py` after all files are written —
+  this is a mandatory step, not optional.
+- **Verified** — always follow up with `read_file` on the target file to
+  confirm content integrity before considering the task complete.
+*** End Files
+
+<EVOLUTION_COMPLETE>

lineage_content_snapshot:
{"SKILL.md": "---\nname: write-file-special-chars-workaround-verified\ndescription: Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) using a Python heredoc script, with mandatory post-write verification via read_file and cleanup of /tmp helper scripts.\n---\n\n# Write File Special Characters Workaround (Verified)\n\n## Problem\n\nThe `write_file` tool may fail with `[ERROR] unknown error` when file content\ncontains special characters such as:\n\n- Apostrophes / single quotes (`'`)\n- Backticks (`` ` ``)\n- Template literals (`` `${variable}` ``) common in TypeScript/JavaScript\n- Complex combinations of quotes and escape sequences\n\nThis is especially common when writing TypeScript, JavaScript, shell scripts,\nor any source file with string interpolation syntax.\n\n## Solution: Python Heredoc Workaround\n\nWrite a small Python script to `/tmp/` that uses triple-quoted strings to\nembed the file content, then execute it with `python3`.\n\n### Step-by-Step\n\n1. **Compose a Python writer script** using triple-quoted strings (`\"\"\"...\"\"\"`).\n2. **Write the Python script itself to `/tmp/`** — its content is plain enough\n   that `write_file` will succeed (no problematic characters in the wrapper).\n3. **Execute** the script with `run_shell`: `python3 /tmp/write_<name>.py`\n4. **Verify** the written file with `read_file` to confirm contents are correct.\n5. **Clean up** the `/tmp` helper script: `rm /tmp/write_<name>.py`\n\n### Escaping Rules Inside the Python Script\n\n| Character | How to handle |\n|-----------|--------------|\n| Backslash `\\` | Escape as `\\\\` |\n| Triple double-quote `\"\"\"` | Escape as `\\\"\\\"\\\"` or use `'''` strings instead |\n| Everything else (backticks, `$`, `'`, `{}`) | No escaping needed |\n\n### Template\n\n```python\n#!/usr/bin/env python3\ncontent = \"\"\"\n<YOUR FILE CONTENT HERE>\n\"\"\".lstrip(\"\\n\")\n\nwith open(\"/path/to/target/file.ts\", \"w\") as f:\n    f.write(content)\n\nprint(\"File written successfully.\")\n```\n\n### Concrete Example\n\nSuppose you need to write a TypeScript file with template literals and\napostrophes that causes `write_file` to fail:\n\n**Step 1 — Write the Python helper to `/tmp/`:**\n\nUse `write_file` with path `/tmp/write_greeting.py` and content:\n\n```python\n#!/usr/bin/env python3\ncontent = \"\"\"\nexport function greet(name: string): string {\n  const msg = `Hello, ${name}! It's a great day.`;\n  console.log(`Greeting: ${msg}`);\n  return msg;\n}\n\"\"\".lstrip(\"\\n\")\n\nwith open(\"/app/src/greeting.ts\", \"w\") as f:\n    f.write(content)\n\nprint(\"Written: /app/src/greeting.ts\")\n```\n\n**Step 2 — Execute the helper:**\n\n```bash\npython3 /tmp/write_greeting.py\n```\n\n**Step 3 — Verify with `read_file`:**\n\nUse `read_file` with path `/app/src/greeting.ts` to confirm the file content\nis exactly what was intended. This catches silent truncation or encoding\nissues that shell `cat` output might obscure.\n\n**Step 4 — Clean up the helper:**\n\n```bash\nrm /tmp/write_greeting.py\n```\n\nThis keeps `/tmp` tidy and prevents stale helper scripts from being confused\nwith current ones on subsequent runs.\n\n## Standard Post-Write Checklist\n\nAfter every use of this pattern, always complete **all three** of these steps:\n\n| Step | Action | Why |\n|------|---------|-----|\n| ✅ Execute | `python3 /tmp/write_<name>.py` | Writes the target file |\n| ✅ Verify | `read_file` on the target path | Confirms content integrity |\n| ✅ Clean up | `rm /tmp/write_<name>.py` | Prevents stale helper accumulation |\n\nSkipping verification risks silently shipping a truncated or malformed file.\nSkipping cleanup risks confusion when the same helper name is reused later.\n\n## When to Use This Pattern\n\n- `write_file` returns `[ERROR] unknown error` for a specific file.\n- The file contains TypeScript/JavaScript template literals.\n- The file contains mixed quote styles or shell-like special characters.\n- Any time direct file writing fails and content cannot be easily sanitized.\n\n## Multiple Files\n\nFor writing several problematic files in one pass, consolidate them into a\nsingle Python script:\n\n```python\n#!/usr/bin/env python3\nimport os\n\nfiles = {\n    \"/app/src/component.tsx\": \"\"\"\nimport React from 'react';\n\nconst App = () => (\n  <div className={`container`}>\n    <h1>It's working!</h1>\n  </div>\n);\n\nexport default App;\n\"\"\".lstrip(\"\\n\"),\n\n    \"/app/src/utils.ts\": \"\"\"\nexport const format = (val: number) => `Value: ${val.toFixed(2)}`;\n\"\"\".lstrip(\"\\n\"),\n}\n\nfor path, content in files.items():\n    os.makedirs(os.path.dirname(path), exist_ok=True)\n    with open(path, \"w\") as f:\n        f.write(content)\n    print(f\"Written: {path}\")\n```\n\nAfter running the multi-file script:\n\n1. Use `read_file` on **each** target path to verify all files were written\n   correctly.\n2. Then clean up: `rm /tmp/write_<name>.py`\n\n## Key Advantages\n\n- **No shell escaping required** — Python triple-quoted strings are literal.\n- **Handles all TypeScript syntax** — template literals, generics, JSX, etc.\n- **Atomic** — the target file is only created if the Python script succeeds.\n- **Debuggable** — the intermediate `.py` file can be inspected if needed.\n- **Cleanup (required):** `rm /tmp/write_*.py` after all files are written —\n  this is a mandatory step, not optional.\n- **Verified** — always follow up with `read_file` on the target file to\n  confirm content integrity before considering the task complete.\n*** End Files\n\n<EVOLUTION_COMPLETE>\n"}


skill_id: large-file-write-fallback__v0_627a7d13
name: large-file-write-heredoc
path: example/skills/large-file-write-heredoc/SKILL.md
category: workflow
visibility: private
lineage_origin: captured
lineage_source_task_id: evolution-v2-task-07
lineage_created_at: 2026-03-10T18:59:02.222206
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T18:59:02.222229
last_updated: 2026-03-10T22:07:55.113341
description:
Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints.

lineage_change_summary:
When write_file and shell_agent fail on large content payloads, use run_shell with a Python heredoc to reliably write file contents by piping stdin into open(path, 'w').

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -0,0 +1,105 @@
+---
+name: large-file-write-fallback
+description: Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints.
+---
+
+# Large File Write Fallback
+
+## Problem
+
+When writing large files (typically several KB or more), two common tools may
+fail with `unknown error` due to internal payload size limits:
+
+- `write_file` — has a maximum content size it can handle in a single call.
+- `shell_agent` — may also hit payload limits when the task description
+  includes large inline content.
+
+## Solution
+
+Use `run_shell` with a **Python heredoc** pattern. This streams the file
+content through stdin directly into Python's `open()`, bypassing the payload
+constraints of the other tools.
+
+## Template
+
+```
+python3 - << 'EOF'
+content = """<FILE CONTENTS HERE>"""
+with open("<TARGET PATH>", "w") as f:
+    f.write(content)
+EOF
+```
+
+Pass this as the `command` parameter to `run_shell`.
+
+## Step-by-Step Instructions
+
+1. **Attempt the normal write** using `write_file` first. If it succeeds, you
+   are done.
+
+2. **If `write_file` fails** (especially with `unknown error` or a timeout on
+   large content), do NOT retry with `shell_agent` using inline content —
+   it will likely fail for the same reason.
+
+3. **Use the `run_shell` heredoc fallback:**
+
+   - Embed the full file content inside a Python triple-quoted string.
+   - Specify the target path inside the `open()` call.
+   - Pass the entire block as the `command` to `run_shell`.
+
+4. **Escape carefully** inside the heredoc:
+   - Backslashes that should be literal in the file must be doubled (`\\`).
+   - Triple-quotes inside the content must be escaped (`\"\"\"`).
+   - The heredoc delimiter `EOF` must not appear on a line by itself inside
+     the content (rename it to `PYEOF` or `FILEEOF` if needed).
+
+5. **Verify the write** by following up with a `run_shell` call such as:
+   ```bash
+   wc -l <TARGET PATH> && head -5 <TARGET PATH>
+   ```
+
+## Full Example
+
+Suppose you need to write a large TypeScript file to
+`src/components/Dashboard.ts`:
+
+```
+python3 - << 'PYEOF'
+content = """import { foo } from './foo';
+
+export interface DashboardData {
+  title: string;
+  items: string[];
+}
+
+export function createDashboard(data: DashboardData): string {
+  return `<div>${data.title}</div>`;
+}
+"""
+with open("src/components/Dashboard.ts", "w") as f:
+    f.write(content)
+PYEOF
+```
+
+Pass the above (without the surrounding code fence) as the `command`
+argument to `run_shell`.
+
+## When to Use This Pattern
+
+| Situation | Recommended tool |
+|---|---|
+| Small file (< ~2 KB) | `write_file` |
+| Medium file, no errors yet | `write_file` (try first) |
+| Large file or `write_file` failed | `run_shell` + Python heredoc |
+| `shell_agent` also fails on large inline content | `run_shell` + Python heredoc |
+
+## Notes
+
+- This pattern works for **any text-based file** (TypeScript, Python, JSON,
+  YAML, Markdown, etc.).
+- For binary files, adapt the approach to use `base64` decoding inside the
+  Python script.
+- The heredoc delimiter (`EOF`, `PYEOF`, `FILEEOF`) can be any string not
+  present as a standalone line in your content — choose accordingly.
+- This technique is also useful when content contains characters that would
+  need heavy shell escaping in a plain `echo` or `printf` approach.

lineage_content_snapshot:
{"SKILL.md": "---\nname: large-file-write-fallback\ndescription: Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints.\n---\n\n# Large File Write Fallback\n\n## Problem\n\nWhen writing large files (typically several KB or more), two common tools may\nfail with `unknown error` due to internal payload size limits:\n\n- `write_file` — has a maximum content size it can handle in a single call.\n- `shell_agent` — may also hit payload limits when the task description\n  includes large inline content.\n\n## Solution\n\nUse `run_shell` with a **Python heredoc** pattern. This streams the file\ncontent through stdin directly into Python's `open()`, bypassing the payload\nconstraints of the other tools.\n\n## Template\n\n```\npython3 - << 'EOF'\ncontent = \"\"\"<FILE CONTENTS HERE>\"\"\"\nwith open(\"<TARGET PATH>\", \"w\") as f:\n    f.write(content)\nEOF\n```\n\nPass this as the `command` parameter to `run_shell`.\n\n## Step-by-Step Instructions\n\n1. **Attempt the normal write** using `write_file` first. If it succeeds, you\n   are done.\n\n2. **If `write_file` fails** (especially with `unknown error` or a timeout on\n   large content), do NOT retry with `shell_agent` using inline content —\n   it will likely fail for the same reason.\n\n3. **Use the `run_shell` heredoc fallback:**\n\n   - Embed the full file content inside a Python triple-quoted string.\n   - Specify the target path inside the `open()` call.\n   - Pass the entire block as the `command` to `run_shell`.\n\n4. **Escape carefully** inside the heredoc:\n   - Backslashes that should be literal in the file must be doubled (`\\\\`).\n   - Triple-quotes inside the content must be escaped (`\\\"\\\"\\\"`).\n   - The heredoc delimiter `EOF` must not appear on a line by itself inside\n     the content (rename it to `PYEOF` or `FILEEOF` if needed).\n\n5. **Verify the write** by following up with a `run_shell` call such as:\n   ```bash\n   wc -l <TARGET PATH> && head -5 <TARGET PATH>\n   ```\n\n## Full Example\n\nSuppose you need to write a large TypeScript file to\n`src/components/Dashboard.ts`:\n\n```\npython3 - << 'PYEOF'\ncontent = \"\"\"import { foo } from './foo';\n\nexport interface DashboardData {\n  title: string;\n  items: string[];\n}\n\nexport function createDashboard(data: DashboardData): string {\n  return `<div>${data.title}</div>`;\n}\n\"\"\"\nwith open(\"src/components/Dashboard.ts\", \"w\") as f:\n    f.write(content)\nPYEOF\n```\n\nPass the above (without the surrounding code fence) as the `command`\nargument to `run_shell`.\n\n## When to Use This Pattern\n\n| Situation | Recommended tool |\n|---|---|\n| Small file (< ~2 KB) | `write_file` |\n| Medium file, no errors yet | `write_file` (try first) |\n| Large file or `write_file` failed | `run_shell` + Python heredoc |\n| `shell_agent` also fails on large inline content | `run_shell` + Python heredoc |\n\n## Notes\n\n- This pattern works for **any text-based file** (TypeScript, Python, JSON,\n  YAML, Markdown, etc.).\n- For binary files, adapt the approach to use `base64` decoding inside the\n  Python script.\n- The heredoc delimiter (`EOF`, `PYEOF`, `FILEEOF`) can be any string not\n  present as a standalone line in your content — choose accordingly.\n- This technique is also useful when content contains characters that would\n  need heavy shell escaping in a plain `echo` or `printf` approach."}


skill_id: push-based-widget-module-pattern__v0_5c3bee33
name: push-based-widget-module-pattern
path: example/skills/push-based-widget-module-pattern/SKILL.md
category: workflow
visibility: private
lineage_origin: captured
lineage_source_task_id: evolution-v2-task-07
lineage_created_at: 2026-03-10T18:59:15.370968
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T18:59:15.370982
last_updated: 2026-03-10T18:59:15.370983
description:
How to implement sidebar/widget UI components that receive pushed data updates from a caller, using a singleton module pattern with exported factory and update functions plus module-level DOM refs for efficient partial re-renders.

lineage_change_summary:
Implement push-based sidebar/widget components as modules with singleton state and exported factory + update functions, using module-level DOM refs for efficient partial re-renders.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -0,0 +1,210 @@
+---
+name: push-based-widget-module-pattern
+description: How to implement sidebar/widget UI components that receive pushed data updates from a caller, using a singleton module pattern with exported factory and update functions plus module-level DOM refs for efficient partial re-renders.
+---
+
+# Push-Based Widget Module Pattern
+
+Use this pattern when a UI sidebar or widget component needs to be updated by its **caller** (push-based) rather than fetching data itself. Instead of a class or framework component, implement the widget as a plain module with singleton state, a factory function to create the DOM, and an update function to efficiently patch it.
+
+---
+
+## When to Use
+
+- A sidebar, panel, or widget is rendered once but updated frequently with new data.
+- The data source lives outside the component (e.g., a parent controller, event bus, or store).
+- You want lightweight partial re-renders without rebuilding the entire DOM tree.
+- You prefer a simple module interface over a class-based or framework-based component.
+
+---
+
+## Core Structure
+
+```
+widgets/
+  MyWidget.ts          # The module (singleton state + exports)
+  MyWidget.types.ts    # (Optional) Exported data interface
+```
+
+---
+
+## Step-by-Step Instructions
+
+### 1. Define the Data Interface
+
+Export a typed interface so callers know exactly what data to push.
+
+```typescript
+// MyWidget.types.ts  (or inline at top of MyWidget.ts)
+export interface MyWidgetData {
+  title: string;
+  items: string[];
+  status: "idle" | "active" | "error";
+  // ... all fields the widget needs to render
+}
+```
+
+### 2. Declare Module-Level Singleton Refs
+
+At module scope, keep references to the **container** and any **frequently updated child nodes**. This avoids querying the DOM on every update.
+
+```typescript
+// MyWidget.ts
+import type { MyWidgetData } from "./MyWidget.types";
+
+// Singleton DOM refs
+let containerEl: HTMLElement | null = null;
+let titleEl: HTMLElement | null = null;
+let itemListEl: HTMLElement | null = null;
+let statusEl: HTMLElement | null = null;
+
+// Current state snapshot (optional — useful for diffing)
+let currentData: MyWidgetData | null = null;
+```
+
+### 3. Export a Factory Function (`createMyWidget`)
+
+The factory builds the full DOM tree **once**, stores refs to mutable nodes, and returns the root element to be mounted by the caller.
+
+```typescript
+export function createMyWidget(): HTMLElement {
+  // Build the container
+  containerEl = document.createElement("aside");
+  containerEl.className = "my-widget";
+
+  // Build static structure; keep refs to dynamic parts
+  titleEl = document.createElement("h2");
+  titleEl.className = "my-widget__title";
+
+  itemListEl = document.createElement("ul");
+  itemListEl.className = "my-widget__list";
+
+  statusEl = document.createElement("span");
+  statusEl.className = "my-widget__status";
+
+  // Compose the tree
+  const header = document.createElement("header");
+  header.appendChild(titleEl);
+  header.appendChild(statusEl);
+
+  containerEl.appendChild(header);
+  containerEl.appendChild(itemListEl);
+
+  return containerEl;
+}
+```
+
+> **Rule:** `createMyWidget` should be callable only once per page. If called again, consider returning the existing `containerEl` (idempotent factory).
+
+### 4. Export an Update Function (`updateMyWidget`)
+
+The update function receives the new data object and performs **surgical DOM mutations** on the stored refs. It does NOT rebuild the whole tree.
+
+```typescript
+export function updateMyWidget(data: MyWidgetData): void {
+  if (!containerEl) {
+    console.warn("updateMyWidget called before createMyWidget");
+    return;
+  }
+
+  currentData = data;
+
+  // Update only what changed
+  if (titleEl) titleEl.textContent = data.title;
+  if (statusEl) {
+    statusEl.textContent = data.status;
+    statusEl.dataset.status = data.status; // for CSS hooks
+  }
+
+  // Rebuild only dynamic list items
+  if (itemListEl) {
+    itemListEl.innerHTML = ""; // clear previous items
+    data.items.forEach((item) => {
+      const li = document.createElement("li");
+      li.textContent = item;
+      itemListEl!.appendChild(li);
+    });
+  }
+}
+```
+
+> **Optimization tip:** For large lists, compare `currentData` to `data` before clearing and rebuilding, or use a keyed reconciliation approach.
+
+### 5. Caller Usage Pattern
+
+The caller mounts the widget once and pushes updates whenever its data changes.
+
+```typescript
+import { createMyWidget, updateMyWidget } from "./widgets/MyWidget";
+import type { MyWidgetData } from "./widgets/MyWidget.types";
+
+// Mount once
+const sidebar = document.getElementById("sidebar")!;
+sidebar.appendChild(createMyWidget());
+
+// Push updates (e.g., from a store subscription, WebSocket, timer)
+function onDataChange(newData: MyWidgetData) {
+  updateMyWidget(newData);
+}
+
+// Example: push an initial state immediately
+onDataChange({
+  title: "Today's Focus",
+  items: ["Task A", "Task B"],
+  status: "active",
+});
+```
+
+---
+
+## Full Module Template
+
+```typescript
+// widgets/MyWidget.ts
+
+export interface MyWidgetData {
+  // Define all fields here
+}
+
+// --- Singleton state ---
+let containerEl: HTMLElement | null = null;
+// Add more refs as needed per section of the widget
+
+// --- Factory ---
+export function createMyWidget(): HTMLElement {
+  if (containerEl) return containerEl; // idempotent
+
+  containerEl = document.createElement("aside");
+  // ... build DOM, store refs ...
+  return containerEl;
+}
+
+// --- Update ---
+export function updateMyWidget(data: MyWidgetData): void {
+  if (!containerEl) return;
+  // ... patch DOM refs with new data ...
+}
+```
+
+---
+
+## Guidelines & Best Practices
+
+| Concern | Recommendation |
+|---|---|
+| **Idempotent factory** | Return existing `containerEl` if called twice to avoid duplicate mounts. |
+| **Null guards** | Always check refs before mutating — the update fn may be called before mount in some flows. |
+| **Granular refs** | Store refs for every section that updates independently. Avoid `querySelector` in the update path. |
+| **Interface exports** | Always export the data interface so callers can type their push calls. |
+| **No internal fetching** | The module must never fetch or subscribe to external data — that is the caller's responsibility. |
+| **CSS hooks** | Use `data-*` attributes or CSS classes on refs to let stylesheets react to state changes. |
+| **Cleanup** | Optionally export a `destroyMyWidget()` that nulls all refs for SPA teardown scenarios. |
+
+---
+
+## Anti-Patterns to Avoid
+
+- ❌ **Class with constructor** — adds instantiation overhead and prevents the clean factory/update split.
+- ❌ **Querying the DOM in the update function** (`document.querySelector(...)`) — slow and fragile.
+- ❌ **Rebuilding the entire tree on every update** — defeats the purpose of the module-level refs.
+- ❌ **Mixing data-fetching into the widget module** — breaks the push-based contract.

lineage_content_snapshot:
{"SKILL.md": "---\nname: push-based-widget-module-pattern\ndescription: How to implement sidebar/widget UI components that receive pushed data updates from a caller, using a singleton module pattern with exported factory and update functions plus module-level DOM refs for efficient partial re-renders.\n---\n\n# Push-Based Widget Module Pattern\n\nUse this pattern when a UI sidebar or widget component needs to be updated by its **caller** (push-based) rather than fetching data itself. Instead of a class or framework component, implement the widget as a plain module with singleton state, a factory function to create the DOM, and an update function to efficiently patch it.\n\n---\n\n## When to Use\n\n- A sidebar, panel, or widget is rendered once but updated frequently with new data.\n- The data source lives outside the component (e.g., a parent controller, event bus, or store).\n- You want lightweight partial re-renders without rebuilding the entire DOM tree.\n- You prefer a simple module interface over a class-based or framework-based component.\n\n---\n\n## Core Structure\n\n```\nwidgets/\n  MyWidget.ts          # The module (singleton state + exports)\n  MyWidget.types.ts    # (Optional) Exported data interface\n```\n\n---\n\n## Step-by-Step Instructions\n\n### 1. Define the Data Interface\n\nExport a typed interface so callers know exactly what data to push.\n\n```typescript\n// MyWidget.types.ts  (or inline at top of MyWidget.ts)\nexport interface MyWidgetData {\n  title: string;\n  items: string[];\n  status: \"idle\" | \"active\" | \"error\";\n  // ... all fields the widget needs to render\n}\n```\n\n### 2. Declare Module-Level Singleton Refs\n\nAt module scope, keep references to the **container** and any **frequently updated child nodes**. This avoids querying the DOM on every update.\n\n```typescript\n// MyWidget.ts\nimport type { MyWidgetData } from \"./MyWidget.types\";\n\n// Singleton DOM refs\nlet containerEl: HTMLElement | null = null;\nlet titleEl: HTMLElement | null = null;\nlet itemListEl: HTMLElement | null = null;\nlet statusEl: HTMLElement | null = null;\n\n// Current state snapshot (optional — useful for diffing)\nlet currentData: MyWidgetData | null = null;\n```\n\n### 3. Export a Factory Function (`createMyWidget`)\n\nThe factory builds the full DOM tree **once**, stores refs to mutable nodes, and returns the root element to be mounted by the caller.\n\n```typescript\nexport function createMyWidget(): HTMLElement {\n  // Build the container\n  containerEl = document.createElement(\"aside\");\n  containerEl.className = \"my-widget\";\n\n  // Build static structure; keep refs to dynamic parts\n  titleEl = document.createElement(\"h2\");\n  titleEl.className = \"my-widget__title\";\n\n  itemListEl = document.createElement(\"ul\");\n  itemListEl.className = \"my-widget__list\";\n\n  statusEl = document.createElement(\"span\");\n  statusEl.className = \"my-widget__status\";\n\n  // Compose the tree\n  const header = document.createElement(\"header\");\n  header.appendChild(titleEl);\n  header.appendChild(statusEl);\n\n  containerEl.appendChild(header);\n  containerEl.appendChild(itemListEl);\n\n  return containerEl;\n}\n```\n\n> **Rule:** `createMyWidget` should be callable only once per page. If called again, consider returning the existing `containerEl` (idempotent factory).\n\n### 4. Export an Update Function (`updateMyWidget`)\n\nThe update function receives the new data object and performs **surgical DOM mutations** on the stored refs. It does NOT rebuild the whole tree.\n\n```typescript\nexport function updateMyWidget(data: MyWidgetData): void {\n  if (!containerEl) {\n    console.warn(\"updateMyWidget called before createMyWidget\");\n    return;\n  }\n\n  currentData = data;\n\n  // Update only what changed\n  if (titleEl) titleEl.textContent = data.title;\n  if (statusEl) {\n    statusEl.textContent = data.status;\n    statusEl.dataset.status = data.status; // for CSS hooks\n  }\n\n  // Rebuild only dynamic list items\n  if (itemListEl) {\n    itemListEl.innerHTML = \"\"; // clear previous items\n    data.items.forEach((item) => {\n      const li = document.createElement(\"li\");\n      li.textContent = item;\n      itemListEl!.appendChild(li);\n    });\n  }\n}\n```\n\n> **Optimization tip:** For large lists, compare `currentData` to `data` before clearing and rebuilding, or use a keyed reconciliation approach.\n\n### 5. Caller Usage Pattern\n\nThe caller mounts the widget once and pushes updates whenever its data changes.\n\n```typescript\nimport { createMyWidget, updateMyWidget } from \"./widgets/MyWidget\";\nimport type { MyWidgetData } from \"./widgets/MyWidget.types\";\n\n// Mount once\nconst sidebar = document.getElementById(\"sidebar\")!;\nsidebar.appendChild(createMyWidget());\n\n// Push updates (e.g., from a store subscription, WebSocket, timer)\nfunction onDataChange(newData: MyWidgetData) {\n  updateMyWidget(newData);\n}\n\n// Example: push an initial state immediately\nonDataChange({\n  title: \"Today's Focus\",\n  items: [\"Task A\", \"Task B\"],\n  status: \"active\",\n});\n```\n\n---\n\n## Full Module Template\n\n```typescript\n// widgets/MyWidget.ts\n\nexport interface MyWidgetData {\n  // Define all fields here\n}\n\n// --- Singleton state ---\nlet containerEl: HTMLElement | null = null;\n// Add more refs as needed per section of the widget\n\n// --- Factory ---\nexport function createMyWidget(): HTMLElement {\n  if (containerEl) return containerEl; // idempotent\n\n  containerEl = document.createElement(\"aside\");\n  // ... build DOM, store refs ...\n  return containerEl;\n}\n\n// --- Update ---\nexport function updateMyWidget(data: MyWidgetData): void {\n  if (!containerEl) return;\n  // ... patch DOM refs with new data ...\n}\n```\n\n---\n\n## Guidelines & Best Practices\n\n| Concern | Recommendation |\n|---|---|\n| **Idempotent factory** | Return existing `containerEl` if called twice to avoid duplicate mounts. |\n| **Null guards** | Always check refs before mutating — the update fn may be called before mount in some flows. |\n| **Granular refs** | Store refs for every section that updates independently. Avoid `querySelector` in the update path. |\n| **Interface exports** | Always export the data interface so callers can type their push calls. |\n| **No internal fetching** | The module must never fetch or subscribe to external data — that is the caller's responsibility. |\n| **CSS hooks** | Use `data-*` attributes or CSS classes on refs to let stylesheets react to state changes. |\n| **Cleanup** | Optionally export a `destroyMyWidget()` that nulls all refs for SPA teardown scenarios. |\n\n---\n\n## Anti-Patterns to Avoid\n\n- ❌ **Class with constructor** — adds instantiation overhead and prevents the clean factory/update split.\n- ❌ **Querying the DOM in the update function** (`document.querySelector(...)`) — slow and fragile.\n- ❌ **Rebuilding the entire tree on every update** — defeats the purpose of the module-level refs.\n- ❌ **Mixing data-fetching into the widget module** — breaks the push-based contract."}


skill_id: panel-component-merged_44c39a__v0_23e1e444
name: panel-component-with-push-sidebar
path: example/skills/panel-component-with-push-sidebar/SKILL.md
category: reference
visibility: private
lineage_origin: derived
lineage_source_task_id: evolution-v2-task-07
lineage_created_at: 2026-03-10T19:02:04.836407
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T19:02:04.836441
last_updated: 2026-03-10T19:02:04.836442
lineage_change_summary:
Merged both panel skills and added a comprehensive 'push-update sidebar' pattern covering singleton elements, SectionRefs, createX()/updateX() exports, and guarded CSS injection for externally-driven components.

lineage_content_snapshot:
{"SKILL.md": "---\nname: panel-component-with-push-sidebar\ndescription: Dashboard panel components (class-based, self-fetching) and push-update sidebar modules (functional, externally-driven) using vanilla TypeScript DOM API, with retry logic, localStorage persistence, and guarded CSS injection.\n---\n\n# Panel & Push-Update Sidebar Patterns\n\nTwo complementary patterns for building dashboard UI in **vanilla TypeScript** (no framework, no JSX):\n\n| Pattern | Use when… |\n|---|---|\n| **Panel class** (extends `Panel`) | The component fetches its own data on a timer |\n| **Push-update sidebar module** | Data arrives from outside (caller pushes it in) |\n\n---\n\n## Part 1 — Panel Class Pattern\n\n### Architecture Overview\n\n```\nPanel (base class)\n├── element: HTMLElement (outer container, .panel)\n│   ├── header: HTMLElement (.panel-header)\n│   │   ├── headerLeft (.panel-header-left)\n│   │   │   ├── title (.panel-title)\n│   │   │   └── newBadge (.panel-new-badge) [optional]\n│   │   ├── statusBadge (.panel-data-badge) [optional]\n│   │   └── countEl (.panel-count) [optional]\n│   ├── content: HTMLElement (.panel-content)\n│   └── resizeHandle (.panel-resize-handle)\n```\n\n### Base Panel Class\n\nCreate `src/components/Panel.ts`:\n\n```typescript\nexport interface PanelOptions {\n  id: string;\n  title: string;\n  showCount?: boolean;\n  className?: string;\n}\n\nexport class Panel {\n  protected element: HTMLElement;\n  protected content: HTMLElement;\n  protected header: HTMLElement;\n  protected countEl: HTMLElement | null = null;\n  protected panelId: string;\n  private _fetching = false;\n\n  // --- Retry state (reset before each logical fetch sequence) ---\n  private retryAttempts = 0;\n  private maxRetries = 3;\n  private retryDelay = 1000; // ms; doubles on each attempt\n\n  constructor(options: PanelOptions) {\n    this.panelId = options.id;\n    this.element = document.createElement('div');\n    this.element.className = `panel ${options.className || ''}`;\n    this.element.dataset.panel = options.id;\n\n    // Header\n    this.header = document.createElement('div');\n    this.header.className = 'panel-header';\n\n    const headerLeft = document.createElement('div');\n    headerLeft.className = 'panel-header-left';\n\n    const title = document.createElement('span');\n    title.className = 'panel-title';\n    title.textContent = options.title;\n    headerLeft.appendChild(title);\n    this.header.appendChild(headerLeft);\n\n    // Count badge (optional)\n    if (options.showCount) {\n      this.countEl = document.createElement('span');\n      this.countEl.className = 'panel-count';\n      this.countEl.textContent = '0';\n      this.header.appendChild(this.countEl);\n    }\n\n    // Content area\n    this.content = document.createElement('div');\n    this.content.className = 'panel-content';\n    this.content.id = `${options.id}Content`;\n\n    this.element.appendChild(this.header);\n    this.element.appendChild(this.content);\n    this.showLoading();\n  }\n\n  // ----------------------------------------------------------------\n  // Public API\n  // ----------------------------------------------------------------\n\n  public getElement(): HTMLElement { return this.element; }\n\n  public showLoading(message = 'Loading...'): void {\n    this.content.innerHTML = `\n      <div class=\"panel-loading\">\n        <div class=\"panel-loading-spinner\"></div>\n        <div class=\"panel-loading-text\">${message}</div>\n      </div>`;\n  }\n\n  public showError(message = 'Failed to load', onRetry?: () => void): void {\n    this.content.innerHTML = `\n      <div class=\"panel-error-state\">\n        <div class=\"panel-error-msg\">${message}</div>\n        ${onRetry ? '<button class=\"panel-retry-btn\" data-panel-retry>Retry</button>' : ''}\n      </div>`;\n    if (onRetry) {\n      this.content.querySelector('[data-panel-retry]')\n        ?.addEventListener('click', onRetry);\n    }\n  }\n\n  public setContent(html: string): void { this.content.innerHTML = html; }\n\n  public setCount(count: number): void {\n    if (this.countEl) this.countEl.textContent = count.toString();\n  }\n\n  public show(): void { this.element.classList.remove('hidden'); }\n  public hide(): void { this.element.classList.add('hidden'); }\n\n  public destroy(): void { this.element.remove(); }\n\n  // ----------------------------------------------------------------\n  // Protected API — available in subclasses\n  // ----------------------------------------------------------------\n\n  protected setFetching(v: boolean): void { this._fetching = v; }\n  protected get isFetching(): boolean { return this._fetching; }\n\n  /**\n   * Fetch a URL with exponential-backoff retry.\n   * Call resetRetry() before each new fetch sequence.\n   */\n  protected async fetchWithRetry(url: string): Promise<unknown> {\n    while (this.retryAttempts < this.maxRetries) {\n      try {\n        const res = await fetch(url);\n        if (!res.ok) throw new Error(`HTTP ${res.status}`);\n        return await res.json();\n      } catch (err: unknown) {\n        this.retryAttempts++;\n        if (this.retryAttempts >= this.maxRetries) {\n          const msg = err instanceof Error ? err.message : String(err);\n          throw new Error(`Failed after ${this.maxRetries} attempts: ${msg}`);\n        }\n        await new Promise(r => setTimeout(r, this.retryDelay));\n        this.retryDelay *= 2;\n      }\n    }\n  }\n\n  /** Reset retry counters before a fresh fetch sequence. */\n  protected resetRetry(): void {\n    this.retryAttempts = 0;\n    this.retryDelay = 1000;\n  }\n\n  // ----------------------------------------------------------------\n  // State persistence (localStorage)\n  // ----------------------------------------------------------------\n\n  public saveState(): void {\n    localStorage.setItem(`panelState_${this.panelId}`, JSON.stringify({\n      isExpanded: !this.element.classList.contains('collapsed'),\n      width: this.element.style.width,\n      height: this.element.style.height,\n    }));\n  }\n\n  public loadState(): void {\n    const raw = localStorage.getItem(`panelState_${this.panelId}`);\n    if (!raw) return;\n    const { isExpanded, width, height } = JSON.parse(raw) as {\n      isExpanded: boolean; width: string; height: string;\n    };\n    if (!isExpanded) this.element.classList.add('collapsed');\n    if (width)  this.element.style.width  = width;\n    if (height) this.element.style.height = height;\n  }\n}\n```\n\n### Protected API Reference\n\n| Member / Method | Type | Description |\n|---|---|---|\n| `element` | `HTMLElement` | Outer container div (`.panel`) |\n| `header` | `HTMLElement` | Header bar — append extra controls here |\n| `content` | `HTMLElement` | Scrollable content area |\n| `countEl` | `HTMLElement \\| null` | Count badge, or `null` if `showCount` not set |\n| `panelId` | `string` | The `id` from `PanelOptions` |\n| `isFetching` | `boolean` getter | `true` while an async fetch is in progress |\n| `setFetching(v)` | `void` | Set/clear the fetching guard |\n| `showLoading(msg?)` | `void` | Replace content with a spinner |\n| `showError(msg?, onRetry?)` | `void` | Replace content with error + optional retry button |\n| `setContent(html)` | `void` | Set raw HTML into the content area |\n| `setCount(n)` | `void` | Update count badge (no-op if `countEl` is null) |\n| `fetchWithRetry(url)` | `Promise<unknown>` | Fetch with exponential-backoff retry (3 attempts) |\n| `resetRetry()` | `void` | Reset retry counters before a new fetch sequence |\n| `saveState()` | `void` | Persist expanded/size state to localStorage |\n| `loadState()` | `void` | Restore state from localStorage |\n\n### Creating a Concrete Panel (Example: StockPanel)\n\n```typescript\nimport { Panel } from './Panel';\n\ninterface StockQuote {\n  symbol: string;\n  name: string;\n  price: number | null;\n  change: number | null;\n  sparkline?: number[];\n}\n\nexport class StockPanel extends Panel {\n  private refreshTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor() {\n    super({ id: 'stocks', title: 'Stock Market', showCount: true });\n    this.loadState();          // restore saved size/collapsed state\n    this.fetchData();\n    this.refreshTimer = setInterval(() => this.fetchData(), 60_000);\n  }\n\n  private async fetchData(): Promise<void> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    this.resetRetry();         // start a fresh retry sequence\n    try {\n      const quotes = await this.fetchWithRetry('/api/stocks') as StockQuote[];\n      this.render(quotes);\n      this.setCount(quotes.length);\n      this.saveState();\n    } catch (err: unknown) {\n      const msg = err instanceof Error ? err.message : 'Unknown error';\n      this.showError(`Failed to load stock data: ${msg}`, () => this.fetchData());\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private render(quotes: StockQuote[]): void {\n    const rows = quotes.map(q => `\n      <div class=\"stock-row\">\n        <span class=\"stock-symbol\">${q.symbol}</span>\n        <span class=\"stock-name\">${q.name}</span>\n        <span class=\"stock-price\">${q.price != null ? '$' + q.price.toFixed(2) : '—'}</span>\n        <span class=\"stock-change ${(q.change ?? 0) >= 0 ? 'positive' : 'negative'}\">\n          ${q.change != null ? (q.change >= 0 ? '+' : '') + q.change.toFixed(2) + '%' : '—'}\n        </span>\n        ${miniSparkline(q.sparkline, q.change)}\n      </div>`).join('');\n    this.setContent(`<div class=\"stock-list\">${rows}</div>`);\n  }\n\n  public override destroy(): void {\n    if (this.refreshTimer) clearInterval(this.refreshTimer);\n    super.destroy();\n  }\n}\n```\n\n### Key Patterns for Self-Fetching Panels\n\n1. **Constructor** → `super()` → `loadState()` → initial `fetchData()` → start refresh timer\n2. **fetchData()** → `isFetching` guard → `resetRetry()` → `fetchWithRetry()` → `render()` + `saveState()`\n3. **render()** builds HTML strings → `this.setContent(html)`\n4. **destroy()** clears timers, calls `super.destroy()`\n5. Use `showLoading()` during initial load (auto-called in constructor)\n6. Use `showError(msg, retryFn)` on failure; retryFn must call `fetchData()` (which calls `resetRetry()`)\n\n---\n\n## Part 2 — Push-Update Sidebar Pattern\n\nUse this pattern when the component **does not fetch data itself** — instead it receives data pushed by an external caller (e.g. a WebSocket handler, a store subscription, or a parent orchestrator).\n\n### When to use this pattern vs. Panel class\n\n```\nSelf-fetching?  → Panel class (Part 1)\nData pushed in? → Push-update sidebar module (Part 2)\n```\n\n### Module Structure\n\nA push-update sidebar is a **plain TypeScript module** (not a class) with:\n\n| Export | Purpose |\n|---|---|\n| `createXSidebar(): HTMLElement` | Build and return the root element (idempotent singleton) |\n| `updateX(data: XData): void` | Re-render only the sections that changed |\n| `interface XData` | The data shape the caller must provide |\n\nInternally the module uses:\n- A **module-level singleton** `let sidebarEl: HTMLElement | null = null`\n- A **`SectionRefs` interface** caching live DOM node references to avoid repeated `querySelector` calls\n- A **guarded `injectStyles()`** function that inserts a `<style>` tag exactly once\n\n### Skeleton\n\n```typescript\n// src/components/FooSidebar.ts\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface FooData {\n  title: string;\n  items: { id: string; label: string; value: number }[];\n  lastUpdated: Date;\n}\n\n// ── DOM node cache ────────────────────────────────────────────────────────────\n\ninterface SectionRefs {\n  root:      HTMLElement;\n  titleEl:   HTMLElement;\n  listEl:    HTMLElement;\n  footerEl:  HTMLElement;\n}\n\n// ── Singleton state ───────────────────────────────────────────────────────────\n\nlet sidebarEl: HTMLElement | null = null;\nlet refs: SectionRefs | null = null;\nlet stylesInjected = false;\n\n// ── CSS injection ─────────────────────────────────────────────────────────────\n\nfunction injectStyles(): void {\n  if (stylesInjected) return;            // guard: run exactly once\n  stylesInjected = true;\n  const style = document.createElement('style');\n  style.dataset.owner = 'foo-sidebar';   // easy to find in DevTools\n  style.textContent = `\n    .foo-sidebar { /* … */ }\n    .foo-sidebar__title { font-weight: 600; }\n    .foo-sidebar__list  { list-style: none; padding: 0; }\n    .foo-sidebar__footer { font-size: 0.75rem; color: var(--muted); }\n  `;\n  document.head.appendChild(style);\n}\n\n// ── Builder ───────────────────────────────────────────────────────────────────\n\n/**\n * Create (or return the existing) sidebar element.\n * Idempotent — safe to call multiple times; always returns the same node.\n */\nexport function createFooSidebar(): HTMLElement {\n  if (sidebarEl) return sidebarEl;       // singleton guard\n\n  injectStyles();\n\n  const root = document.createElement('aside');\n  root.className = 'foo-sidebar';\n\n  const titleEl = document.createElement('h2');\n  titleEl.className = 'foo-sidebar__title';\n  root.appendChild(titleEl);\n\n  const listEl = document.createElement('ul');\n  listEl.className = 'foo-sidebar__list';\n  root.appendChild(listEl);\n\n  const footerEl = document.createElement('div');\n  footerEl.className = 'foo-sidebar__footer';\n  root.appendChild(footerEl);\n\n  // Cache references — avoids querySelector on every update\n  refs = { root, titleEl, listEl, footerEl };\n  sidebarEl = root;\n  return root;\n}\n\n// ── Updater ───────────────────────────────────────────────────────────────────\n\n/**\n * Push new data into the sidebar.\n * Calls createFooSidebar() defensively if refs is not yet initialised.\n */\nexport function updateFoo(data: FooData): void {\n  if (!refs) createFooSidebar();\n  const r = refs!;\n\n  r.titleEl.textContent = data.title;\n\n  r.listEl.innerHTML = data.items\n    .map(item => `\n      <li class=\"foo-sidebar__item\" data-id=\"${item.id}\">\n        <span class=\"foo-item-label\">${item.label}</span>\n        <span class=\"foo-item-value\">${item.value}</span>\n      </li>`)\n    .join('');\n\n  r.footerEl.textContent =\n    `Updated ${data.lastUpdated.toLocaleTimeString()}`;\n}\n```\n\n### Real-World Example: TodayFocusSidebar\n\nA sidebar that summarises the user's day — greeting, meeting countdown, inbox counts, stock alerts, CI failures, and an AI briefing with truncate/expand toggle. Data is pushed in from an external orchestrator.\n\n```typescript\n// src/components/TodayFocusSidebar.ts\n\nexport interface FocusData {\n  userName: string;\n  nextMeeting: { title: string; startsInMinutes: number } | null;\n  inboxCounts: { email: number; slack: number; github: number };\n  stockAlerts: { symbol: string; changePercent: number }[]; // pre-filtered >= 2%\n  ciFailures: { repo: string; branch: string }[];\n  aiBriefing: string; // may be long — sidebar truncates with toggle\n}\n\ninterface SectionRefs {\n  root:       HTMLElement;\n  greetingEl: HTMLElement;\n  meetingEl:  HTMLElement;\n  inboxEl:    HTMLElement;\n  stocksEl:   HTMLElement;\n  ciEl:       HTMLElement;\n  briefingEl: HTMLElement;\n}\n\nlet sidebarEl: HTMLElement | null = null;\nlet refs: SectionRefs | null = null;\nlet stylesInjected = false;\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction greeting(name: string): string {\n  const h = new Date().getHours();\n  const salutation = h < 12 ? 'Good morning' : h < 17 ? 'Good afternoon' : 'Good evening';\n  return `${salutation}, ${name}`;\n}\n\nfunction formatCountdown(minutes: number): string {\n  if (minutes <= 0) return 'Now';\n  if (minutes < 60) return `in ${minutes}m`;\n  return `in ${Math.floor(minutes / 60)}h ${minutes % 60}m`;\n}\n\n// ── CSS ───────────────────────────────────────────────────────────────────────\n\nfunction injectStyles(): void {\n  if (stylesInjected) return;\n  stylesInjected = true;\n  const s = document.createElement('style');\n  s.dataset.owner = 'today-focus-sidebar';\n  s.textContent = `\n    .tfs { display:flex; flex-direction:column; gap:12px; padding:16px;\n           background:var(--bg-surface, #1e1e2e); color:var(--fg, #cdd6f4);\n           border-radius:8px; font-family:inherit; }\n    .tfs__greeting  { font-size:1.1rem; font-weight:600; }\n    .tfs__meeting   { font-size:0.85rem; color:var(--yellow, #f9e2af); }\n    .tfs__section   { font-size:0.85rem; }\n    .tfs__label     { font-size:0.7rem; text-transform:uppercase;\n                      letter-spacing:.06em; color:var(--muted, #6c7086);\n                      margin-bottom:4px; }\n    .tfs__briefing  { font-size:0.82rem; line-height:1.5; }\n    .tfs__toggle    { background:none; border:none; color:var(--blue, #89b4fa);\n                      cursor:pointer; font-size:0.8rem; padding:2px 0; }\n    .tfs__alert-pos { color:var(--green, #a6e3a1); }\n    .tfs__alert-neg { color:var(--red,   #f38ba8); }\n    .tfs__ci-fail   { color:var(--red,   #f38ba8); }\n  `;\n  document.head.appendChild(s);\n}\n\n// ── Builder ───────────────────────────────────────────────────────────────────\n\nexport function createTodayFocusSidebar(): HTMLElement {\n  if (sidebarEl) return sidebarEl;\n  injectStyles();\n\n  const make = (tag: string, cls: string): HTMLElement => {\n    const el = document.createElement(tag);\n    el.className = cls;\n    return el as HTMLElement;\n  };\n\n  const root       = make('aside', 'tfs');\n  const greetingEl = make('div',   'tfs__greeting');\n  const meetingEl  = make('div',   'tfs__meeting');\n  const inboxEl    = make('div',   'tfs__section');\n  const stocksEl   = make('div',   'tfs__section');\n  const ciEl       = make('div',   'tfs__section');\n  const briefingEl = make('div',   'tfs__section');\n\n  root.append(greetingEl, meetingEl, inboxEl, stocksEl, ciEl, briefingEl);\n  refs = { root, greetingEl, meetingEl, inboxEl, stocksEl, ciEl, briefingEl };\n  sidebarEl = root;\n  return root;\n}\n\n// ── Updater ───────────────────────────────────────────────────────────────────\n\nconst BRIEFING_LIMIT = 200;\n\nexport function updateTodayFocus(data: FocusData): void {\n  if (!refs) createTodayFocusSidebar();\n  const r = refs!;\n\n  // Greeting\n  r.greetingEl.textContent = greeting(data.userName);\n\n  // Next meeting\n  if (data.nextMeeting) {\n    r.meetingEl.textContent =\n      `Next: ${data.nextMeeting.title} — ${formatCountdown(data.nextMeeting.startsInMinutes)}`;\n    r.meetingEl.hidden = false;\n  } else {\n    r.meetingEl.hidden = true;\n  }\n\n  // Inbox counts\n  const { email, slack, github } = data.inboxCounts;\n  r.inboxEl.innerHTML = `\n    <div class=\"tfs__label\">Inbox</div>\n    Email: ${email} &nbsp; Slack: ${slack} &nbsp; GitHub: ${github}`;\n\n  // Stock alerts (caller should pre-filter >= 2%, but we guard here too)\n  const alerts = data.stockAlerts.filter(s => Math.abs(s.changePercent) >= 2);\n  r.stocksEl.innerHTML = alerts.length === 0\n    ? ''\n    : `<div class=\"tfs__label\">Stock Alerts</div>` +\n      alerts.map(s => {\n        const cls  = s.changePercent >= 0 ? 'tfs__alert-pos' : 'tfs__alert-neg';\n        const sign = s.changePercent >= 0 ? '+' : '';\n        return `<span class=\"${cls}\">${s.symbol} ${sign}${s.changePercent.toFixed(1)}%</span>`;\n      }).join(' &nbsp; ');\n\n  // CI failures\n  r.ciEl.innerHTML = data.ciFailures.length === 0\n    ? ''\n    : `<div class=\"tfs__label\">CI Failures</div>` +\n      data.ciFailures\n        .map(f => `<div class=\"tfs__ci-fail\">${f.repo} / ${f.branch}</div>`)\n        .join('');\n\n  // AI briefing with truncate/expand toggle\n  const text = data.aiBriefing;\n  if (text.length <= BRIEFING_LIMIT) {\n    r.briefingEl.innerHTML =\n      `<div class=\"tfs__label\">Briefing</div><div class=\"tfs__briefing\">${text}</div>`;\n  } else {\n    const short = text.slice(0, BRIEFING_LIMIT) + '…';\n    r.briefingEl.innerHTML = `\n      <div class=\"tfs__label\">Briefing</div>\n      <div class=\"tfs__briefing\"\n           data-full=\"${encodeURIComponent(text)}\"\n           data-short=\"${encodeURIComponent(short)}\"\n           data-expanded=\"false\">${short}</div>\n      <button class=\"tfs__toggle\" data-briefing-toggle>Show more</button>`;\n    r.briefingEl.querySelector('[data-briefing-toggle]')\n      ?.addEventListener('click', function (this: HTMLButtonElement) {\n        const div = r.briefingEl.querySelector<HTMLElement>('[data-full]')!;\n        const expanded = div.dataset.expanded === 'true';\n        div.textContent = decodeURIComponent(\n          expanded ? div.dataset.short! : div.dataset.full!);\n        div.dataset.expanded = String(!expanded);\n        this.textContent = expanded ? 'Show more' : 'Show less';\n      });\n  }\n}\n```\n\n### Push-Update Checklist\n\n- [ ] Define `interface XData` (exported — callers need it)\n- [ ] Define `interface SectionRefs` (internal — not exported)\n- [ ] Declare module-level `let sidebarEl`, `let refs`, `let stylesInjected`\n- [ ] `injectStyles()` inserts one `<style>` tag, guarded by `stylesInjected`; set `style.dataset.owner` for DevTools visibility\n- [ ] `createXSidebar()` is idempotent: returns existing `sidebarEl` if already built\n- [ ] `createXSidebar()` populates `refs` with live node references\n- [ ] `updateX(data)` calls `createXSidebar()` defensively if `refs` is null\n- [ ] `updateX(data)` mutates only nodes in `refs` — never calls `document.querySelector`\n- [ ] Export `createXSidebar`, `updateX`, and `XData` interface; keep `SectionRefs` module-private\n\n### Barrel File Wiring\n\nAfter creating a push-update sidebar, register it in both barrel files:\n\n```typescript\n// src/components/index.ts\nexport { createTodayFocusSidebar, updateTodayFocus } from './TodayFocusSidebar';\nexport type { FocusData } from './TodayFocusSidebar';\n\n// src/index.ts\nexport * from './components';\n```\n\n---\n\n## Part 3 — Sparkline Utility\n\nUsed by both patterns for inline SVG sparklines:\n\n```typescript\n// src/utils/sparkline.ts\nexport function miniSparkline(\n  data: number[] | undefined,\n  change: number | null,\n  w = 50,\n  h = 16,\n): string {\n  if (!data || data.length < 2) return '';\n  const min = Math.min(...data);\n  const max = Math.max(...data);\n  const range = max - min || 1;\n  const color = change != null && change >= 0 ? 'var(--green)' : 'var(--red)';\n  const points = data.map((v, i) => {\n    const x = (i / (data.length - 1)) * w;\n    const y = h - ((v - min) / range) * (h - 2) - 1;\n    return `${x.toFixed(1)},${y.toFixed(1)}`;\n  }).join(' ');\n  return `<svg width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\">` +\n    `<polyline points=\"${points}\" fill=\"none\" stroke=\"${color}\" ` +\n    `stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`;\n}\n```\n\n---\n\n## Quick-Reference Decision Tree\n\n```\nNeed a new dashboard component?\n│\n├─ Will it fetch its own data (polling / one-shot)?\n│   └─ YES → extend Panel (Part 1)\n│             • constructor → super() → loadState() → fetchData() → setInterval\n│             • fetchData() → resetRetry() → fetchWithRetry() → render() → saveState()\n│             • destroy() → clearInterval → super.destroy()\n│\n└─ Will data be pushed from outside?\n    └─ YES → functional push-update module (Part 2)\n              • createXSidebar() — idempotent, builds DOM, fills SectionRefs\n              • updateX(data)    — mutates only SectionRefs nodes\n              • injectStyles()   — guarded, runs once\n              • export createX, updateX, XData; keep SectionRefs private\n```\n", "examples/TodayFocusSidebar.example.ts": "/**\n * Usage example — wiring the push-update sidebar into a page orchestrator.\n *\n * The sidebar does NOT fetch data itself. The orchestrator owns the data\n * lifecycle and pushes updates via updateTodayFocus().\n */\nimport {\n  createTodayFocusSidebar,\n  updateTodayFocus,\n  FocusData,\n} from '../src/components/TodayFocusSidebar';\n\n// 1. Create the element once and mount it\nconst sidebar = createTodayFocusSidebar();\ndocument.getElementById('sidebar-slot')!.appendChild(sidebar);\n\n// 2. Push initial data (e.g. from a REST response)\nasync function loadInitialData(): Promise<void> {\n  const focus = await fetch('/api/today-focus').then(r => r.json()) as FocusData;\n  updateTodayFocus(focus);\n}\n\nloadInitialData();\n\n// 3. Push live updates from a WebSocket\nconst ws = new WebSocket('/ws/focus');\nws.addEventListener('message', (evt) => {\n  const data = JSON.parse(evt.data) as FocusData;\n  updateTodayFocus(data);\n});\n\n// 4. Periodic polling fallback\nsetInterval(loadInitialData, 60_000);\n*** End Files\n\n<EVOLUTION_COMPLETE>\n", "examples/localStorageUtils.ts": "/**\n * localStorageUtils.ts\n *\n * Thin helpers used by Panel.saveState() / Panel.loadState().\n * Useful standalone for testing or custom subclasses.\n */\n\nexport function readJSON<T>(key: string): T | null {\n  try {\n    const raw = localStorage.getItem(key);\n    return raw ? (JSON.parse(raw) as T) : null;\n  } catch {\n    return null;\n  }\n}\n\nexport function writeJSON<T>(key: string, value: T): void {\n  try {\n    localStorage.setItem(key, JSON.stringify(value));\n  } catch {\n    // Quota exceeded or private-browsing restriction — fail silently\n  }\n}\n\nexport function removeKey(key: string): void {\n  localStorage.removeItem(key);\n}\n"}


skill_id: panel-component-enhanced__v3_56fc2b2e
name: panel-retry-localstorage
path: example/skills/panel-retry-localstorage/SKILL.md
category: workflow
visibility: private
lineage_origin: fixed
lineage_source_task_id: evolution-v2-task-08
lineage_created_at: 2026-03-10T19:15:54.402141
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T19:15:54.402153
last_updated: 2026-03-10T19:15:54.402154
lineage_change_summary:
Added explicit note and emoji example in the DEFAULT_LINKS definition to clarify that icon values must be direct Unicode emoji characters, not text abbreviations.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -295,6 +295,32 @@
 6. Use `showError(msg, retryFn)` on failure
 7. Sparkline SVGs use inline `<svg>` with `<polyline>` — see sparkline utility
 
+## Icon / Emoji Values
+
+> **Important:** When a panel or component accepts an `icon` field (e.g. in a `QuickLink` definition), the value **must be a literal Unicode emoji character** — not a text abbreviation or shortcode.
+>
+> ✅ Correct — use the actual emoji character directly:
+> ```typescript
+> const DEFAULT_LINKS = [
+>   { label: 'GitHub',    icon: '🐙', url: 'https://github.com' },
+>   { label: 'Gmail',     icon: '📧', url: 'https://mail.google.com' },
+>   { label: 'Calendar',  icon: '📅', url: 'https://calendar.google.com' },
+>   { label: 'Docs',      icon: '📝', url: 'https://docs.google.com' },
+>   { label: 'Slack',     icon: '💬', url: 'https://slack.com' },
+> ];
+> ```
+>
+> ❌ Wrong — do NOT use abbreviation strings as icon values:
+> ```typescript
+> // BAD: 'GH', 'GM', 'CAL', 'DOC', 'SL' are not emoji; they will render as plain text
+> const DEFAULT_LINKS = [
+>   { label: 'GitHub',   icon: 'GH',  url: 'https://github.com' },
+>   { label: 'Gmail',    icon: 'GM',  url: 'https://mail.google.com' },
+> ];
+> ```
+>
+> Emoji are standard Unicode code points. Copy-paste them directly into the source string — no escaping, no library, no conversion step required. For example `'🐙'` is the literal octopus emoji and will render correctly in any modern browser. Do **not** attempt to "fix" an abbreviation into an emoji via a shell one-liner or `node -e` script; write the file with the correct emoji character in the string literal from the start.
+
 ## Sparkline Utility
 
 ```typescript

lineage_content_snapshot:
{"SKILL.md": "---\nname: panel-component-enhanced\ndescription: Enhanced dashboard panel component with robust error handling, retry logic, and localStorage persistence for state management.\n---\n\n# Panel Component Pattern\n\nCreate dashboard panel components using **vanilla TypeScript** (no framework, no JSX). Each panel is a class extending a `Panel` base class.\n\n## Architecture Overview\n\n### Enhanced Features\n- **Retry Logic**: Automatically retries failed data fetches with exponential backoff.\n- **State Persistence**: Saves panel state (expanded/collapsed, size) to localStorage.\n- **Detailed Error Handling**: Provides more granular error messages and recovery options.\n\n```\nPanel (base class)\n├── element: HTMLElement (outer container, .panel)\n│   ├── header: HTMLElement (.panel-header)\n│   │   ├── headerLeft (.panel-header-left)\n│   │   │   ├── title (.panel-title)\n│   │   │   └── newBadge (.panel-new-badge) [optional]\n│   │   ├── statusBadge (.panel-data-badge) [optional]\n│   │   └── countEl (.panel-count) [optional]\n│   ├── content: HTMLElement (.panel-content)\n│   └── resizeHandle (.panel-resize-handle)\n```\n\n## Base Panel Class (Simplified for this project)\n\n```typescript\nexport class Panel {\n  // ... existing code ...\n\n  private retryAttempts = 0;\n  private maxRetries = 3;\n  private retryDelay = 1000; // starts at 1s, doubles each retry\n\n  protected async fetchWithRetry(url: string): Promise<any> {\n    while (this.retryAttempts < this.maxRetries) {\n      try {\n        const response = await fetch(url);\n        if (!response.ok) throw new Error(`HTTP ${response.status}`);\n        return await response.json();\n      } catch (error) {\n        this.retryAttempts++;\n        if (this.retryAttempts >= this.maxRetries) {\n          throw new Error(`Failed after ${this.maxRetries} attempts: ${error.message}`);\n        }\n        await new Promise(resolve => setTimeout(resolve, this.retryDelay));\n        this.retryDelay *= 2;\n      }\n    }\n  }\n\n  public saveState(): void {\n    localStorage.setItem(`panelState_${this.panelId}`, JSON.stringify({\n      isExpanded: !this.element.classList.contains('collapsed'),\n      width: this.element.style.width,\n      height: this.element.style.height\n    }));\n  }\n\n  public loadState(): void {\n    const savedState = localStorage.getItem(`panelState_${this.panelId}`);\n    if (savedState) {\n      const { isExpanded, width, height } = JSON.parse(savedState);\n      if (!isExpanded) this.element.classList.add('collapsed');\n      if (width) this.element.style.width = width;\n      if (height) this.element.style.height = height;\n    }\n  }\n}\n```\n\nCreate `src/components/Panel.ts`:\n\n```typescript\nexport interface PanelOptions {\n  id: string;\n  title: string;\n  showCount?: boolean;\n  className?: string;\n}\n\nexport class Panel {\n  protected element: HTMLElement;\n  protected content: HTMLElement;\n  protected header: HTMLElement;\n  protected countEl: HTMLElement | null = null;\n  protected panelId: string;\n  private _fetching = false;\n\n  constructor(options: PanelOptions) {\n    this.panelId = options.id;\n    this.element = document.createElement('div');\n    this.element.className = `panel ${options.className || ''}`;\n    this.element.dataset.panel = options.id;\n\n    // Header\n    this.header = document.createElement('div');\n    this.header.className = 'panel-header';\n\n    const headerLeft = document.createElement('div');\n    headerLeft.className = 'panel-header-left';\n\n    const title = document.createElement('span');\n    title.className = 'panel-title';\n    title.textContent = options.title;\n    headerLeft.appendChild(title);\n    this.header.appendChild(headerLeft);\n\n    // Count badge (optional)\n    if (options.showCount) {\n      this.countEl = document.createElement('span');\n      this.countEl.className = 'panel-count';\n      this.countEl.textContent = '0';\n      this.header.appendChild(this.countEl);\n    }\n\n    // Content area\n    this.content = document.createElement('div');\n    this.content.className = 'panel-content';\n    this.content.id = `${options.id}Content`;\n\n    this.element.appendChild(this.header);\n    this.element.appendChild(this.content);\n    this.showLoading();\n  }\n\n  public getElement(): HTMLElement { return this.element; }\n\n  public showLoading(message = 'Loading...'): void {\n    this.content.innerHTML = `\n      <div class=\"panel-loading\">\n        <div class=\"panel-loading-spinner\"></div>\n        <div class=\"panel-loading-text\">${message}</div>\n      </div>`;\n  }\n\n  public showError(message = 'Failed to load', onRetry?: () => void): void {\n    this.content.innerHTML = `\n      <div class=\"panel-error-state\">\n        <div class=\"panel-error-msg\">${message}</div>\n        ${onRetry ? '<button class=\"panel-retry-btn\" data-panel-retry>Retry</button>' : ''}\n      </div>`;\n    if (onRetry) {\n      this.content.querySelector('[data-panel-retry]')?.addEventListener('click', onRetry);\n    }\n  }\n\n  public setContent(html: string): void {\n    this.content.innerHTML = html;\n  }\n\n  public setCount(count: number): void {\n    if (this.countEl) this.countEl.textContent = count.toString();\n  }\n\n  public show(): void { this.element.classList.remove('hidden'); }\n  public hide(): void { this.element.classList.add('hidden'); }\n\n  protected setFetching(v: boolean): void { this._fetching = v; }\n  protected get isFetching(): boolean { return this._fetching; }\n\n  public destroy(): void {\n    this.element.remove();\n  }\n}\n```\n\n## Creating a Concrete Panel (Example: StockPanel)\n\n```typescript\nexport class StockPanel extends Panel {\n  // ... existing code ...\n\n  private async fetchData(): Promise<void> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    try {\n      const quotes = await this.fetchWithRetry('/api/stocks');\n      this.render(quotes);\n      this.setCount(quotes.length);\n      this.saveState();\n    } catch (err) {\n      this.showError(`Failed to load stock data: ${err.message}`, () => {\n        this.retryAttempts = 0;\n        this.retryDelay = 1000;\n        this.fetchData();\n      });\n    } finally {\n      this.setFetching(false);\n    }\n  }\n}\n```\n\nEach panel extends `Panel` and manages its own data fetching + rendering:\n\n```typescript\nimport { Panel } from './Panel';\n\ninterface StockQuote {\n  symbol: string;\n  name: string;\n  price: number | null;\n  change: number | null;\n  sparkline?: number[];\n}\n\nexport class StockPanel extends Panel {\n  private refreshTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor() {\n    super({ id: 'stocks', title: 'Stock Market', showCount: true });\n    this.fetchData();\n    this.refreshTimer = setInterval(() => this.fetchData(), 60_000);\n  }\n\n  private async fetchData(): Promise<void> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    try {\n      const quotes = await fetchStockQuotes(); // from data-service\n      this.render(quotes);\n      this.setCount(quotes.length);\n    } catch (err) {\n      this.showError('Failed to load stock data', () => this.fetchData());\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private render(quotes: StockQuote[]): void {\n    const rows = quotes.map(q => `\n      <div class=\"stock-row\">\n        <span class=\"stock-symbol\">${q.symbol}</span>\n        <span class=\"stock-name\">${q.name}</span>\n        <span class=\"stock-price\">${q.price != null ? '$' + q.price.toFixed(2) : '—'}</span>\n        <span class=\"stock-change ${(q.change ?? 0) >= 0 ? 'positive' : 'negative'}\">\n          ${q.change != null ? (q.change >= 0 ? '+' : '') + q.change.toFixed(2) + '%' : '—'}\n        </span>\n      </div>\n    `).join('');\n\n    this.setContent(`<div class=\"stock-list\">${rows}</div>`);\n  }\n\n  public override destroy(): void {\n    if (this.refreshTimer) clearInterval(this.refreshTimer);\n    super.destroy();\n  }\n}\n```\n\n## Protected API Reference\n\nAll members and methods listed below are accessible from subclasses without importing or inspecting `Panel.ts` directly:\n\n| Member / Method | Type | Description |\n|---|---|---|\n| `element` | `HTMLElement` | Outer container div (`.panel`) |\n| `header` | `HTMLElement` | Header bar div (`.panel-header`) — append extra controls here |\n| `content` | `HTMLElement` | Content area div (`.panel-content`) |\n| `countEl` | `HTMLElement \\| null` | Count badge span, or `null` if `showCount` was not set |\n| `panelId` | `string` | The `id` passed to `PanelOptions` |\n| `isFetching` | `boolean` (getter) | `true` while an async fetch is in progress |\n| `setFetching(v: boolean)` | `void` | Set the fetching guard flag |\n| `showLoading(message?)` | `void` | Replace content with a loading spinner |\n| `showError(message?, onRetry?)` | `void` | Replace content with an error state and optional retry button |\n| `setContent(html: string)` | `void` | Set raw HTML into the content area |\n| `setCount(count: number)` | `void` | Update the count badge text (no-op if `countEl` is null) |\n\n> **Example — appending a button to the header in a subclass:**\n> ```typescript\n> constructor() {\n>   super({ id: 'insights', title: 'Insights', className: 'panel-wide' });\n>   const btn = document.createElement('button');\n>   btn.className = 'panel-refresh-btn';\n>   btn.textContent = '↻';\n>   btn.addEventListener('click', () => this.generate());\n>   this.header.appendChild(btn);   // 'header' is protected — safe to use here\n> }\n> ```\n\n## Key Patterns\n\n1. **Constructor** calls `super()` with panel config, then triggers initial data fetch\n2. **fetchData()** is async, uses `isFetching` guard, shows error on failure with retry\n3. **render()** builds HTML strings and calls `this.setContent(html)`\n4. **destroy()** cleans up timers and event listeners\n5. Use `showLoading()` during initial load (auto-called in constructor)\n6. Use `showError(msg, retryFn)` on failure\n7. Sparkline SVGs use inline `<svg>` with `<polyline>` — see sparkline utility\n\n## Icon / Emoji Values\n\n> **Important:** When a panel or component accepts an `icon` field (e.g. in a `QuickLink` definition), the value **must be a literal Unicode emoji character** — not a text abbreviation or shortcode.\n>\n> ✅ Correct — use the actual emoji character directly:\n> ```typescript\n> const DEFAULT_LINKS = [\n>   { label: 'GitHub',    icon: '🐙', url: 'https://github.com' },\n>   { label: 'Gmail',     icon: '📧', url: 'https://mail.google.com' },\n>   { label: 'Calendar',  icon: '📅', url: 'https://calendar.google.com' },\n>   { label: 'Docs',      icon: '📝', url: 'https://docs.google.com' },\n>   { label: 'Slack',     icon: '💬', url: 'https://slack.com' },\n> ];\n> ```\n>\n> ❌ Wrong — do NOT use abbreviation strings as icon values:\n> ```typescript\n> // BAD: 'GH', 'GM', 'CAL', 'DOC', 'SL' are not emoji; they will render as plain text\n> const DEFAULT_LINKS = [\n>   { label: 'GitHub',   icon: 'GH',  url: 'https://github.com' },\n>   { label: 'Gmail',    icon: 'GM',  url: 'https://mail.google.com' },\n> ];\n> ```\n>\n> Emoji are standard Unicode code points. Copy-paste them directly into the source string — no escaping, no library, no conversion step required. For example `'🐙'` is the literal octopus emoji and will render correctly in any modern browser. Do **not** attempt to \"fix\" an abbreviation into an emoji via a shell one-liner or `node -e` script; write the file with the correct emoji character in the string literal from the start.\n\n## Sparkline Utility\n\n```typescript\nexport function miniSparkline(data: number[] | undefined, change: number | null, w = 50, h = 16): string {\n  if (!data || data.length < 2) return '';\n  const min = Math.min(...data);\n  const max = Math.max(...data);\n  const range = max - min || 1;\n  const color = change != null && change >= 0 ? 'var(--green)' : 'var(--red)';\n  const points = data.map((v, i) => {\n    const x = (i / (data.length - 1)) * w;\n    const y = h - ((v - min) / range) * (h - 2) - 1;\n    return `${x.toFixed(1)},${y.toFixed(1)}`;\n  }).join(' ');\n  return `<svg width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\"><polyline points=\"${points}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`;\n}\n```\n", "examples/localStorageUtils.ts": ""}


skill_id: large-file-write-via-temp-script__v0_21e79414
name: large-file-write-via-temp-script
path: example/skills/large-file-write-via-temp-script/SKILL.md
category: tool_guide
visibility: private
lineage_origin: captured
lineage_source_task_id: evolution-v2-task-08
lineage_created_at: 2026-03-10T19:16:07.128816
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T19:16:07.128833
last_updated: 2026-03-10T19:16:07.128834
description:
Reliable fallback technique for writing large or special-character-heavy file content when inline write_file or shell_agent calls fail with unknown errors — uses a Python helper script written to a temp file to avoid escaping and truncation pitfalls.

lineage_change_summary:
When writing large TypeScript (or any) files with special characters fails inline, use a Python script written to a temp file via heredoc or base64 to perform the write reliably.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -0,0 +1,148 @@
+---
+name: large-file-write-via-temp-script
+description: Reliable fallback technique for writing large or special-character-heavy file content when inline write_file or shell_agent calls fail with unknown errors — uses a Python helper script written to a temp file to avoid escaping and truncation pitfalls.
+---
+
+# Large File Write via Temp Script
+
+## Problem
+
+Writing large files — especially TypeScript, TSX, or any content containing
+Unicode emoji, backticks, `${}` template literals, or multi-line heredoc
+conflicts — can silently fail or truncate content when passed inline.
+
+**Failure modes observed:**
+- `write_file` returns `[ERROR] unknown error` on large payloads
+- `node -e '...'` with backtick template literals silently empties the target file
+- `python3 -c '...'` breaks on unescaped quotes or special characters
+- `shell_agent` may time out or fail when the inline content is too large
+
+---
+
+## Solution: Write a Python Script to a Temp File First
+
+Instead of passing file content inline, write a small Python helper script to
+`/tmp/write_task.py` (or similar), then execute it. The helper script holds
+the target file content as a raw Python string or via base64 decoding.
+
+---
+
+## Approach A — Python Triple-Quoted String (preferred for readability)
+
+Use `write_file` or a heredoc to create the helper, then run it:
+
+```bash
+# Step 1: Write the helper script to /tmp
+cat > /tmp/write_task.py << 'PYEOF'
+import pathlib
+
+content = r"""
+// Your TypeScript / file content goes here
+// Backticks `like this`, ${template} literals, and emoji 🎉 are all safe
+export const MyComponent = () => {
+  const msg = `Hello, world!`;
+  return <div>{msg}</div>;
+};
+"""
+
+pathlib.Path("/path/to/target/file.ts").write_text(content.lstrip("\n"), encoding="utf-8")
+print("Written successfully.")
+PYEOF
+
+# Step 2: Execute the helper
+python3 /tmp/write_task.py
+```
+
+> **Why `r"""`?** A raw triple-quoted string avoids all backslash escaping
+> issues. The only content that cannot appear verbatim is `"""` itself — if
+> needed, split the string or escape that sequence only.
+
+---
+
+## Approach B — Base64-Encoded Content (most robust for binary/complex content)
+
+When the content contains `"""` or other Python string edge cases:
+
+```bash
+# Step 1: Base64-encode your content (done once, outside the target system)
+# python3 -c "import base64; print(base64.b64encode(open('file.ts').read().encode()).decode())"
+
+# Step 2: Embed the base64 blob in the helper script
+cat > /tmp/write_task.py << 'PYEOF'
+import base64, pathlib
+
+b64 = (
+    "aW1wb3J0IFJlYWN0IGZyb20gJ3JlYWN0JzsKCmV4cG9ydCBjb25zdCBNeUNvbXBvbmVudCA9"
+    "ICgpID0+IHsKICByZXR1cm4gPGRpdj5IZWxsbzwvZGl2PjsKfTsK"
+    # ... more lines if needed
+)
+
+content = base64.b64decode(b64).decode("utf-8")
+pathlib.Path("/path/to/target/file.ts").write_text(content, encoding="utf-8")
+print(f"Written {len(content)} bytes successfully.")
+PYEOF
+
+python3 /tmp/write_task.py
+```
+
+---
+
+## Approach C — shell_agent Delegation (simplest for agents)
+
+Ask `shell_agent` to perform the write using the temp-script pattern:
+
+> "Write the following TypeScript content to `/src/components/MyPanel.ts`.
+> Do NOT use `node -e` or inline Python. Instead write a Python script to
+> `/tmp/write_panel.py` using a triple-quoted raw string, then execute it."
+
+---
+
+## Decision Guide
+
+| Situation | Recommended Approach |
+|---|---|
+| Content has backticks / `${}` template literals | Approach A (raw `r"""`) |
+| Content has `"""` sequences | Approach B (base64) |
+| Content is binary or non-UTF-8 | Approach B (base64) |
+| Delegating to shell_agent | Approach C with explicit instructions |
+| Quick small file | Direct `write_file` (no workaround needed) |
+
+---
+
+## Anti-Patterns to Avoid
+
+```bash
+# ❌ DANGEROUS — backtick template literals will break node -e
+node -e "const fs = require('fs'); fs.writeFileSync('out.ts', \`${content}\`);"
+
+# ❌ FRAGILE — breaks on unescaped quotes and special characters
+python3 -c "open('out.ts','w').write('${content}')"
+
+# ❌ UNRELIABLE for large payloads — may silently truncate
+echo "${large_content}" > out.ts
+```
+
+---
+
+## Verification Step
+
+Always verify the write succeeded:
+
+```bash
+# Check file exists and has non-zero size
+python3 -c "
+import pathlib
+p = pathlib.Path('/path/to/target/file.ts')
+assert p.exists() and p.stat().st_size > 0, f'Write failed: {p}'
+print(f'OK — {p.stat().st_size} bytes written to {p}')
+"
+```
+
+---
+
+## Notes
+
+- The temp script approach works for **any file type**: `.ts`, `.tsx`, `.js`, `.json`, `.md`, `.py`, etc.
+- Always `lstrip("\n")` the triple-quoted string to avoid a leading blank line.
+- For very large files (>50 KB), prefer base64 to avoid any risk of heredoc conflicts.
+- Clean up temp scripts after use: `rm /tmp/write_task.py`

lineage_content_snapshot:
{"SKILL.md": "---\nname: large-file-write-via-temp-script\ndescription: Reliable fallback technique for writing large or special-character-heavy file content when inline write_file or shell_agent calls fail with unknown errors — uses a Python helper script written to a temp file to avoid escaping and truncation pitfalls.\n---\n\n# Large File Write via Temp Script\n\n## Problem\n\nWriting large files — especially TypeScript, TSX, or any content containing\nUnicode emoji, backticks, `${}` template literals, or multi-line heredoc\nconflicts — can silently fail or truncate content when passed inline.\n\n**Failure modes observed:**\n- `write_file` returns `[ERROR] unknown error` on large payloads\n- `node -e '...'` with backtick template literals silently empties the target file\n- `python3 -c '...'` breaks on unescaped quotes or special characters\n- `shell_agent` may time out or fail when the inline content is too large\n\n---\n\n## Solution: Write a Python Script to a Temp File First\n\nInstead of passing file content inline, write a small Python helper script to\n`/tmp/write_task.py` (or similar), then execute it. The helper script holds\nthe target file content as a raw Python string or via base64 decoding.\n\n---\n\n## Approach A — Python Triple-Quoted String (preferred for readability)\n\nUse `write_file` or a heredoc to create the helper, then run it:\n\n```bash\n# Step 1: Write the helper script to /tmp\ncat > /tmp/write_task.py << 'PYEOF'\nimport pathlib\n\ncontent = r\"\"\"\n// Your TypeScript / file content goes here\n// Backticks `like this`, ${template} literals, and emoji 🎉 are all safe\nexport const MyComponent = () => {\n  const msg = `Hello, world!`;\n  return <div>{msg}</div>;\n};\n\"\"\"\n\npathlib.Path(\"/path/to/target/file.ts\").write_text(content.lstrip(\"\\n\"), encoding=\"utf-8\")\nprint(\"Written successfully.\")\nPYEOF\n\n# Step 2: Execute the helper\npython3 /tmp/write_task.py\n```\n\n> **Why `r\"\"\"`?** A raw triple-quoted string avoids all backslash escaping\n> issues. The only content that cannot appear verbatim is `\"\"\"` itself — if\n> needed, split the string or escape that sequence only.\n\n---\n\n## Approach B — Base64-Encoded Content (most robust for binary/complex content)\n\nWhen the content contains `\"\"\"` or other Python string edge cases:\n\n```bash\n# Step 1: Base64-encode your content (done once, outside the target system)\n# python3 -c \"import base64; print(base64.b64encode(open('file.ts').read().encode()).decode())\"\n\n# Step 2: Embed the base64 blob in the helper script\ncat > /tmp/write_task.py << 'PYEOF'\nimport base64, pathlib\n\nb64 = (\n    \"aW1wb3J0IFJlYWN0IGZyb20gJ3JlYWN0JzsKCmV4cG9ydCBjb25zdCBNeUNvbXBvbmVudCA9\"\n    \"ICgpID0+IHsKICByZXR1cm4gPGRpdj5IZWxsbzwvZGl2PjsKfTsK\"\n    # ... more lines if needed\n)\n\ncontent = base64.b64decode(b64).decode(\"utf-8\")\npathlib.Path(\"/path/to/target/file.ts\").write_text(content, encoding=\"utf-8\")\nprint(f\"Written {len(content)} bytes successfully.\")\nPYEOF\n\npython3 /tmp/write_task.py\n```\n\n---\n\n## Approach C — shell_agent Delegation (simplest for agents)\n\nAsk `shell_agent` to perform the write using the temp-script pattern:\n\n> \"Write the following TypeScript content to `/src/components/MyPanel.ts`.\n> Do NOT use `node -e` or inline Python. Instead write a Python script to\n> `/tmp/write_panel.py` using a triple-quoted raw string, then execute it.\"\n\n---\n\n## Decision Guide\n\n| Situation | Recommended Approach |\n|---|---|\n| Content has backticks / `${}` template literals | Approach A (raw `r\"\"\"`) |\n| Content has `\"\"\"` sequences | Approach B (base64) |\n| Content is binary or non-UTF-8 | Approach B (base64) |\n| Delegating to shell_agent | Approach C with explicit instructions |\n| Quick small file | Direct `write_file` (no workaround needed) |\n\n---\n\n## Anti-Patterns to Avoid\n\n```bash\n# ❌ DANGEROUS — backtick template literals will break node -e\nnode -e \"const fs = require('fs'); fs.writeFileSync('out.ts', \\`${content}\\`);\"\n\n# ❌ FRAGILE — breaks on unescaped quotes and special characters\npython3 -c \"open('out.ts','w').write('${content}')\"\n\n# ❌ UNRELIABLE for large payloads — may silently truncate\necho \"${large_content}\" > out.ts\n```\n\n---\n\n## Verification Step\n\nAlways verify the write succeeded:\n\n```bash\n# Check file exists and has non-zero size\npython3 -c \"\nimport pathlib\np = pathlib.Path('/path/to/target/file.ts')\nassert p.exists() and p.stat().st_size > 0, f'Write failed: {p}'\nprint(f'OK — {p.stat().st_size} bytes written to {p}')\n\"\n```\n\n---\n\n## Notes\n\n- The temp script approach works for **any file type**: `.ts`, `.tsx`, `.js`, `.json`, `.md`, `.py`, etc.\n- Always `lstrip(\"\\n\")` the triple-quoted string to avoid a leading blank line.\n- For very large files (>50 KB), prefer base64 to avoid any risk of heredoc conflicts.\n- Clean up temp scripts after use: `rm /tmp/write_task.py`"}


skill_id: panel-component-enhanced-merged_465bbc__v0_6b912c96
name: panel-breaker-backoff
path: example/skills/panel-breaker-backoff/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: evolution-v2-task-08
lineage_created_at: 2026-03-10T19:18:23.841722
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T19:18:23.841758
last_updated: 2026-03-10T22:09:49.109991
lineage_change_summary:
Merged panel-component-enhanced and data-service-merged into a single unified skill that shows how to wrap existing service calls with CircuitBreaker.execute() as a one-liner, clarifies when to use circuit breaker vs. direct service calls, and preserves all Panel architecture, retry logic, and localStorage patterns in one self-contained reference.

lineage_content_snapshot:
{"SKILL.md": "---\nname: resilient-panel-unified\ndescription: Unified guide for authoring dashboard panel components in vanilla TypeScript — covers the Panel base class architecture, localStorage state persistence, exponential-backoff retry, and CircuitBreaker integration. Shows how to wrap any existing fetchX() service call with CircuitBreaker.execute() as a one-liner and clarifies when each resilience layer is appropriate.\n---\n\n# Resilient Panel — Unified Pattern\n\nCreate dashboard panel components using **vanilla TypeScript** (no framework, no JSX).\nEach panel is a class extending a `Panel` base class and optionally wraps its data source with a `CircuitBreaker`.\n\n---\n\n## Architecture Overview\n\n```\nPanel (base class)\n├── element: HTMLElement (.panel)\n│   ├── header: HTMLElement (.panel-header)\n│   │   ├── headerLeft (.panel-header-left)\n│   │   │   ├── title (.panel-title)\n│   │   │   └── newBadge (.panel-new-badge) [optional]\n│   │   ├── statusBadge (.panel-data-badge) [optional]\n│   │   └── countEl (.panel-count) [optional]\n│   ├── content: HTMLElement (.panel-content)\n│   └── resizeHandle (.panel-resize-handle)\n```\n\n### Resilience layers (choose what you need)\n\n| Layer | File | When to use |\n|---|---|---|\n| **isFetching guard** | `Panel` base class | Always — prevents concurrent duplicate requests |\n| **Exponential-backoff retry** | `Panel.fetchWithRetry()` | When you own the fetch call and want per-request retry before surfacing an error |\n| **CircuitBreaker** | `src/utils/circuit-breaker.ts` | When a service may fail repeatedly; stops hammering the API and serves stale cache instead |\n| **localStorage persistence** | `Panel.saveState()` / `loadState()` | When you want collapsed/size state to survive page reloads |\n\n> **Wrapping an existing service call with CircuitBreaker is a one-liner:**\n> ```typescript\n> // Before\n> const data = await fetchStockQuotes();\n> // After — adds caching + cooldown with zero refactor\n> const data = await breaker.execute(() => fetchStockQuotes(), []);\n> ```\n\n---\n\n## File 1 — `src/components/Panel.ts`\n\n```typescript\nexport interface PanelOptions {\n  id: string;\n  title: string;\n  showCount?: boolean;\n  className?: string;\n}\n\nexport class Panel {\n  protected element: HTMLElement;\n  protected content: HTMLElement;\n  protected header: HTMLElement;\n  protected countEl: HTMLElement | null = null;\n  protected panelId: string;\n  private _fetching = false;\n\n  // Retry state (used by fetchWithRetry)\n  protected retryAttempts = 0;\n  protected retryDelay = 1000; // ms; doubles each attempt\n  private maxRetries = 3;\n\n  constructor(options: PanelOptions) {\n    this.panelId = options.id;\n    this.element = document.createElement('div');\n    this.element.className = `panel ${options.className ?? ''}`;\n    this.element.dataset.panel = options.id;\n\n    // Header\n    this.header = document.createElement('div');\n    this.header.className = 'panel-header';\n\n    const headerLeft = document.createElement('div');\n    headerLeft.className = 'panel-header-left';\n\n    const title = document.createElement('span');\n    title.className = 'panel-title';\n    title.textContent = options.title;\n    headerLeft.appendChild(title);\n    this.header.appendChild(headerLeft);\n\n    // Count badge (optional)\n    if (options.showCount) {\n      this.countEl = document.createElement('span');\n      this.countEl.className = 'panel-count';\n      this.countEl.textContent = '0';\n      this.header.appendChild(this.countEl);\n    }\n\n    // Content area\n    this.content = document.createElement('div');\n    this.content.className = 'panel-content';\n    this.content.id = `${options.id}Content`;\n\n    this.element.appendChild(this.header);\n    this.element.appendChild(this.content);\n    this.showLoading();\n  }\n\n  // ── Public API ───────────────────────────────────────────────────────\n\n  public getElement(): HTMLElement { return this.element; }\n\n  public showLoading(message = 'Loading...'): void {\n    this.content.innerHTML = `\n      <div class=\"panel-loading\">\n        <div class=\"panel-loading-spinner\"></div>\n        <div class=\"panel-loading-text\">${message}</div>\n      </div>`;\n  }\n\n  public showError(message = 'Failed to load', onRetry?: () => void): void {\n    this.content.innerHTML = `\n      <div class=\"panel-error-state\">\n        <div class=\"panel-error-msg\">${message}</div>\n        ${onRetry ? '<button class=\"panel-retry-btn\" data-panel-retry>Retry</button>' : ''}\n      </div>`;\n    if (onRetry) {\n      this.content.querySelector('[data-panel-retry]')\n        ?.addEventListener('click', onRetry);\n    }\n  }\n\n  public setContent(html: string): void { this.content.innerHTML = html; }\n\n  public setCount(count: number): void {\n    if (this.countEl) this.countEl.textContent = count.toString();\n  }\n\n  public show(): void { this.element.classList.remove('hidden'); }\n  public hide(): void { this.element.classList.add('hidden'); }\n\n  public destroy(): void { this.element.remove(); }\n\n  // ── Protected helpers ────────────────────────────────────────────────\n\n  protected setFetching(v: boolean): void { this._fetching = v; }\n  protected get isFetching(): boolean { return this._fetching; }\n\n  /**\n   * Fetch `url` with exponential-backoff retry (up to maxRetries attempts).\n   * Reset `this.retryAttempts` and `this.retryDelay` before calling if you\n   * want a fresh retry sequence (e.g. from a user-initiated Retry button).\n   */\n  protected async fetchWithRetry(url: string): Promise<unknown> {\n    while (this.retryAttempts < this.maxRetries) {\n      try {\n        const response = await fetch(url);\n        if (!response.ok) throw new Error(`HTTP ${response.status}`);\n        return await response.json();\n      } catch (error) {\n        this.retryAttempts++;\n        if (this.retryAttempts >= this.maxRetries) {\n          throw new Error(\n            `Failed after ${this.maxRetries} attempts: ${(error as Error).message}`\n          );\n        }\n        await new Promise(resolve => setTimeout(resolve, this.retryDelay));\n        this.retryDelay *= 2;\n      }\n    }\n  }\n\n  /** Persist expanded/collapsed state and dimensions to localStorage. */\n  public saveState(): void {\n    localStorage.setItem(`panelState_${this.panelId}`, JSON.stringify({\n      isExpanded: !this.element.classList.contains('collapsed'),\n      width: this.element.style.width,\n      height: this.element.style.height,\n    }));\n  }\n\n  /** Restore state saved by saveState(). */\n  public loadState(): void {\n    const raw = localStorage.getItem(`panelState_${this.panelId}`);\n    if (!raw) return;\n    const { isExpanded, width, height } = JSON.parse(raw) as {\n      isExpanded: boolean; width: string; height: string;\n    };\n    if (!isExpanded) this.element.classList.add('collapsed');\n    if (width)  this.element.style.width  = width;\n    if (height) this.element.style.height = height;\n  }\n}\n```\n\n---\n\n## File 2 — `src/utils/circuit-breaker.ts`\n\n```typescript\ninterface CircuitState {\n  failures: number;\n  cooldownUntil: number;\n}\n\ninterface CacheEntry<T> {\n  data: T;\n  timestamp: number;\n}\n\nexport interface CircuitBreakerOptions {\n  name: string;\n  maxFailures?: number;   // default 2\n  cooldownMs?: number;    // default 5 min\n  cacheTtlMs?: number;    // default 10 min\n}\n\nexport class CircuitBreaker<T> {\n  private state: CircuitState = { failures: 0, cooldownUntil: 0 };\n  private cache: CacheEntry<T> | null = null;\n  private readonly name: string;\n  private readonly maxFailures: number;\n  private readonly cooldownMs: number;\n  private readonly cacheTtlMs: number;\n\n  constructor(options: CircuitBreakerOptions) {\n    this.name        = options.name;\n    this.maxFailures = options.maxFailures ?? 2;\n    this.cooldownMs  = options.cooldownMs  ?? 5 * 60 * 1000;\n    this.cacheTtlMs  = options.cacheTtlMs  ?? 10 * 60 * 1000;\n  }\n\n  isOnCooldown(): boolean {\n    if (Date.now() < this.state.cooldownUntil) return true;\n    if (this.state.cooldownUntil > 0) {\n      this.state = { failures: 0, cooldownUntil: 0 };\n    }\n    return false;\n  }\n\n  getCached(): T | null {\n    if (this.cache && Date.now() - this.cache.timestamp < this.cacheTtlMs) {\n      return this.cache.data;\n    }\n    return null;\n  }\n\n  recordSuccess(data: T): void {\n    this.state = { failures: 0, cooldownUntil: 0 };\n    this.cache = { data, timestamp: Date.now() };\n  }\n\n  recordFailure(): void {\n    this.state.failures++;\n    if (this.state.failures >= this.maxFailures) {\n      this.state.cooldownUntil = Date.now() + this.cooldownMs;\n      console.warn(`[${this.name}] Circuit open — cooldown ${this.cooldownMs / 1000}s`);\n    }\n  }\n\n  /**\n   * Execute fn. On success, caches result. On failure, records failure and\n   * returns defaultValue. While on cooldown or cache is fresh, skips fn\n   * entirely and returns cached/default value.\n   */\n  async execute<R extends T>(fn: () => Promise<R>, defaultValue: R): Promise<R> {\n    if (this.isOnCooldown()) {\n      const cached = this.getCached();\n      return (cached as R) ?? defaultValue;\n    }\n    const cached = this.getCached();\n    if (cached !== null) return cached as R;\n    try {\n      const result = await fn();\n      this.recordSuccess(result);\n      return result;\n    } catch (e) {\n      console.error(`[${this.name}] Request failed:`, e);\n      this.recordFailure();\n      return defaultValue;\n    }\n  }\n}\n\nexport function createCircuitBreaker<T>(\n  options: CircuitBreakerOptions\n): CircuitBreaker<T> {\n  return new CircuitBreaker<T>(options);\n}\n```\n\n---\n\n## File 3 — `src/utils/sparkline.ts`\n\n```typescript\n/**\n * Returns an inline SVG sparkline string, or '' if data is too short.\n * Embed the return value directly in HTML template strings.\n */\nexport function miniSparkline(\n  data: number[] | undefined,\n  change: number | null,\n  w = 50,\n  h = 16\n): string {\n  if (!data || data.length < 2) return '';\n  const min   = Math.min(...data);\n  const max   = Math.max(...data);\n  const range = max - min || 1;\n  const color = change != null && change >= 0 ? 'var(--green)' : 'var(--red)';\n  const points = data.map((v, i) => {\n    const x = (i / (data.length - 1)) * w;\n    const y = h - ((v - min) / range) * (h - 2) - 1;\n    return `${x.toFixed(1)},${y.toFixed(1)}`;\n  }).join(' ');\n  return `<svg width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\">` +\n    `<polyline points=\"${points}\" fill=\"none\" stroke=\"${color}\" ` +\n    `stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`;\n}\n```\n\n---\n\n## Creating a Concrete Panel\n\n### Option A — Wrapping an existing project service with CircuitBreaker (preferred)\n\nUse this when the project already has a `fetchStockQuotes()` (or similar) function.\nThe circuit breaker is a **one-liner wrapper** — no refactoring of the service needed.\n\n```typescript\nimport { Panel } from './Panel';\nimport { createCircuitBreaker } from '../utils/circuit-breaker';\nimport { fetchStockQuotes } from '../services/stocks'; // existing project service\n\ninterface StockQuote {\n  symbol: string;\n  name: string;\n  price: number | null;\n  change: number | null;\n  sparkline?: number[];\n}\n\n// Module-level breaker — shared across re-renders, persists cache between fetches\nconst breaker = createCircuitBreaker<StockQuote[]>({\n  name: 'StockMarket',\n  cacheTtlMs: 60_000,\n  cooldownMs: 5 * 60_000,\n});\n\nexport class StockPanel extends Panel {\n  private refreshTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor() {\n    super({ id: 'stocks', title: 'Stock Market', showCount: true });\n    this.loadState();       // restore collapsed/size from localStorage\n    this.fetchData();\n    this.refreshTimer = setInterval(() => this.fetchData(), 60_000);\n\n    // Optional: manual refresh button in header\n    const btn = document.createElement('button');\n    btn.className = 'panel-refresh-btn';\n    btn.textContent = 'Refresh';\n    btn.addEventListener('click', () => this.fetchData());\n    this.header.appendChild(btn);\n  }\n\n  private async fetchData(): Promise<void> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    try {\n      // One-liner: wrap existing service call with circuit breaker\n      const quotes = await breaker.execute(() => fetchStockQuotes(), []);\n      this.render(quotes);\n      this.setCount(quotes.length);\n      this.saveState();\n    } catch (err) {\n      this.showError(\n        `Failed to load stock data: ${(err as Error).message}`,\n        () => this.fetchData()\n      );\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private render(quotes: StockQuote[]): void {\n    const rows = quotes.map(q => `\n      <div class=\"stock-row\">\n        <span class=\"stock-symbol\">${q.symbol}</span>\n        <span class=\"stock-name\">${q.name}</span>\n        <span class=\"stock-price\">${q.price != null ? '$' + q.price.toFixed(2) : '--'}</span>\n        <span class=\"stock-change ${(q.change ?? 0) >= 0 ? 'positive' : 'negative'}\">\n          ${q.change != null ? (q.change >= 0 ? '+' : '') + q.change.toFixed(2) + '%' : '--'}\n        </span>\n      </div>`).join('');\n    this.setContent(`<div class=\"stock-list\">${rows}</div>`);\n  }\n\n  public override destroy(): void {\n    if (this.refreshTimer) clearInterval(this.refreshTimer);\n    super.destroy();\n  }\n}\n```\n\n### Option B — Direct fetch with `fetchWithRetry` (no existing service layer)\n\nUse this when there is no project service and you want per-request exponential backoff:\n\n```typescript\nimport { Panel } from './Panel';\n\ninterface NewsArticle {\n  id: string;\n  title: string;\n  url: string;\n}\n\nexport class NewsPanel extends Panel {\n  private refreshTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor() {\n    super({ id: 'news', title: 'Live News', showCount: true });\n    this.fetchData();\n    this.refreshTimer = setInterval(() => this.fetchData(), 120_000);\n  }\n\n  private async fetchData(): Promise<void> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    try {\n      // fetchWithRetry handles up to 3 attempts with exponential backoff\n      const articles = await this.fetchWithRetry('/api/news') as NewsArticle[];\n      this.render(articles);\n      this.setCount(articles.length);\n    } catch (err) {\n      this.showError(\n        `News unavailable: ${(err as Error).message}`,\n        () => {\n          // Reset retry counters before user-initiated retry\n          this.retryAttempts = 0;\n          this.retryDelay = 1000;\n          this.fetchData();\n        }\n      );\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private render(articles: NewsArticle[]): void {\n    const items = articles.map(a =>\n      `<div class=\"news-item\"><a href=\"${a.url}\">${a.title}</a></div>`\n    ).join('');\n    this.setContent(`<div class=\"news-list\">${items}</div>`);\n  }\n\n  public override destroy(): void {\n    if (this.refreshTimer) clearInterval(this.refreshTimer);\n    super.destroy();\n  }\n}\n```\n\n### Option C — Plain fetch, no resilience layer\n\nFor non-critical panels or mock/dev data where retries add no value:\n\n```typescript\nprivate async fetchData(): Promise<void> {\n  if (this.isFetching) return;\n  this.setFetching(true);\n  try {\n    const resp = await fetch('/api/config');\n    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\n    const data = await resp.json();\n    this.render(data);\n  } catch (err) {\n    this.showError('Could not load config', () => this.fetchData());\n  } finally {\n    this.setFetching(false);\n  }\n}\n```\n\n---\n\n## Decision Guide — Which resilience layer?\n\n```\nDoes the project have an existing fetchX() service function?\n  YES -> wrap it: breaker.execute(() => fetchX(), defaultValue)     <- Option A\n  NO  -> does the endpoint fail intermittently under normal load?\n           YES -> use fetchWithRetry() for per-request retry         <- Option B\n           NO  -> plain fetch() with showError/retry is enough       <- Option C\n\nIs the endpoint unreliable or rate-limited for extended periods?\n  YES -> add CircuitBreaker (prevents hammering during outages)\n  NO  -> fetchWithRetry or plain fetch is sufficient\n\nDo you need panel state (size, collapsed) to survive page reloads?\n  YES -> call this.loadState() in constructor (after super())\n         call this.saveState() after a successful render\n```\n\n---\n\n## Protected API Reference\n\nAll members below are accessible from subclasses without importing or inspecting `Panel.ts` directly:\n\n| Member / Method | Type | Description |\n|---|---|---|\n| `element` | `HTMLElement` | Outer container div (`.panel`) |\n| `header` | `HTMLElement` | Header bar div — append extra controls here |\n| `content` | `HTMLElement` | Content area div (`.panel-content`) |\n| `countEl` | `HTMLElement \\| null` | Count badge, or `null` if `showCount` not set |\n| `panelId` | `string` | The `id` from `PanelOptions` |\n| `retryAttempts` | `number` | Current retry count for `fetchWithRetry` |\n| `retryDelay` | `number` | Current delay (ms) for `fetchWithRetry`; reset to 1000 before retry |\n| `isFetching` | `boolean` (getter) | `true` while an async fetch is in progress |\n| `setFetching(v)` | `void` | Set the fetching guard flag |\n| `showLoading(msg?)` | `void` | Replace content with a loading spinner |\n| `showError(msg?, onRetry?)` | `void` | Replace content with an error state and optional retry button |\n| `setContent(html)` | `void` | Set raw HTML into the content area |\n| `setCount(n)` | `void` | Update the count badge (no-op if `countEl` is null) |\n| `fetchWithRetry(url)` | `Promise<unknown>` | Fetch with exponential-backoff retry (3 attempts) |\n| `saveState()` | `void` | Persist expanded/size state to localStorage |\n| `loadState()` | `void` | Restore state from localStorage |\n\n> **Example — appending a button to the header in a subclass:**\n> ```typescript\n> constructor() {\n>   super({ id: 'insights', title: 'Insights', className: 'panel-wide' });\n>   const btn = document.createElement('button');\n>   btn.className = 'panel-refresh-btn';\n>   btn.textContent = 'Refresh';\n>   btn.addEventListener('click', () => this.generate());\n>   this.header.appendChild(btn); // 'header' is protected — safe to use here\n> }\n> ```\n\n---\n\n## Key Patterns (checklist)\n\n1. **Constructor**: call `super()` with `PanelOptions`, optionally call `loadState()`, then trigger initial data fetch.\n2. **fetchData()**: async, use `isFetching` guard, wrap service calls with `breaker.execute()` when available.\n3. **render()**: build HTML strings, call `this.setContent(html)`, then `this.saveState()`.\n4. **Error recovery**: `showError(message, () => this.fetchData())` — retry button is wired automatically.\n5. **destroy()**: clear all `setInterval` / `setTimeout` handles, then call `super.destroy()`.\n6. **Sparklines**: import `miniSparkline` from `src/utils/sparkline.ts`, embed return value directly in HTML template strings.\n7. **Header controls**: `this.header.appendChild(el)` — `header` is `protected`, safe to use in subclasses.\n\n---\n\n## localStorage Utilities (optional helpers)\n\nSee `examples/localStorageUtils.ts` for typed wrappers when you need to persist additional per-panel data beyond the built-in size/collapsed state (e.g. user filter selections, last-viewed item).\n", "examples/localStorageUtils.ts": "/**\n * Typed helpers for persisting arbitrary panel data in localStorage.\n * Use these when you need to save more than the built-in size/collapsed state\n * (e.g. user preferences, last-viewed item, filter selections).\n */\n\n/**\n * Save any serialisable value under a namespaced key.\n *\n * @example\n * savePanelData('stocks', 'filters', { sector: 'tech', minChange: -5 });\n */\nexport function savePanelData<T>(panelId: string, key: string, value: T): void {\n  try {\n    localStorage.setItem(`panel_${panelId}_${key}`, JSON.stringify(value));\n  } catch (e) {\n    console.warn(`[localStorage] Failed to save ${panelId}/${key}:`, e);\n  }\n}\n\n/**\n * Load a previously saved value, returning defaultValue if absent or unparseable.\n *\n * @example\n * const filters = loadPanelData('stocks', 'filters', { sector: 'all', minChange: -100 });\n */\nexport function loadPanelData<T>(panelId: string, key: string, defaultValue: T): T {\n  try {\n    const raw = localStorage.getItem(`panel_${panelId}_${key}`);\n    if (raw === null) return defaultValue;\n    return JSON.parse(raw) as T;\n  } catch (e) {\n    console.warn(`[localStorage] Failed to load ${panelId}/${key}:`, e);\n    return defaultValue;\n  }\n}\n\n/**\n * Remove all localStorage entries for a given panel (call from destroy()).\n *\n * @example\n * clearPanelData('stocks');\n */\nexport function clearPanelData(panelId: string): void {\n  const prefix = `panel_${panelId}_`;\n  Object.keys(localStorage)\n    .filter(k => k.startsWith(prefix))\n    .forEach(k => localStorage.removeItem(k));\n}\n*** End Files\n\n<EVOLUTION_COMPLETE>\n"}


skill_id: delegate-task__imp_3476b422
name: delegate-task
path: Downloads/open-cowork/nanobot/nanobot/skills/delegate-task/SKILL.md
category: workflow
visibility: private
lineage_origin: imported
lineage_content_snapshot: {}
lineage_created_at: 2026-03-10T20:00:19.063979
first_seen: 2026-03-10T20:00:19.063981
last_updated: 2026-03-10T20:00:19.063982
description:
Delegate tasks to ClawSkills for execution, fix broken skills, and upload skills to the cloud community. ClawSkills is a universal tool-use layer with strong multi-step execution capabilities, a skill evolution engine that auto-improves skills over time, and a cloud community where agents share battle-tested reusable skills.


skill_id: github__imp_ffc86e7e
name: github
path: Downloads/open-cowork/nanobot/nanobot/skills/github/SKILL.md
category: workflow
visibility: private
lineage_origin: imported
lineage_content_snapshot: {}
lineage_created_at: 2026-03-10T20:00:19.064012
first_seen: 2026-03-10T20:00:19.064016
last_updated: 2026-03-10T20:00:19.064016
description:
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.


skill_id: skill-creator__imp_5c4993ce
name: skill-creator
path: Downloads/open-cowork/nanobot/nanobot/skills/skill-creator/SKILL.md
category: workflow
visibility: private
lineage_origin: imported
lineage_content_snapshot: {}
lineage_created_at: 2026-03-10T20:00:19.064221
first_seen: 2026-03-10T20:00:19.064223
last_updated: 2026-03-10T20:05:37.551351
description:
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.


skill_id: skill-discovery__imp_d6e829f3
name: skill-discovery
path: Downloads/open-cowork/nanobot/nanobot/skills/skill-discovery/SKILL.md
category: workflow
visibility: private
lineage_origin: imported
lineage_content_snapshot: {}
lineage_created_at: 2026-03-10T20:00:19.064438
first_seen: 2026-03-10T20:00:19.064441
last_updated: 2026-03-10T20:00:19.064441
description:
Search for reusable skills across ClawSkills local registry and cloud community. ClawSkills is a universal tool-use layer with a cloud skill community where agents share and evolve reusable skills. Use this to discover existing skills before building from scratch.


skill_id: summarize__imp_abbc7d98
name: summarize
path: Downloads/open-cowork/nanobot/nanobot/skills/summarize/SKILL.md
category: workflow
visibility: private
lineage_origin: imported
lineage_content_snapshot: {}
lineage_created_at: 2026-03-10T20:00:19.064468
first_seen: 2026-03-10T20:00:19.064470
last_updated: 2026-03-10T20:00:19.064471
description:
Summarize or extract text/transcripts from URLs, podcasts, and local files (great fallback for “transcribe this YouTube/video”).


skill_id: skill-creator__v1_bae4107e
name: skill-creator
path: Downloads/open-cowork/nanobot/nanobot/skills/skill-creator/SKILL.md
category: workflow
visibility: private
lineage_origin: fixed
lineage_source_task_id: task_d0bd72539f7c
lineage_created_at: 2026-03-10T20:05:37.550391
lineage_created_by: openrouter/anthropic/claude-opus-4-5
first_seen: 2026-03-10T20:05:37.550417
last_updated: 2026-03-10T20:05:37.550418
description:
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.

lineage_change_summary:
Removed references to non-existent init_skill.py and package_skill.py scripts, clarifying that skill creation is done manually by creating the directory structure and files directly.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -200,14 +200,13 @@
 
 ## Skill Creation Process
 
-Skill creation involves these steps:
+Skill creation involves these steps. All steps are performed manually by creating the directory structure and files directly—there are no helper scripts.
 
 1. Understand the skill with concrete examples
 2. Plan reusable skill contents (scripts, references, assets)
-3. Initialize the skill (run init_skill.py)
-4. Edit the skill (implement resources and write SKILL.md)
-5. Package the skill (run package_skill.py)
-6. Iterate based on real usage
+3. Create the skill directory and SKILL.md file
+4. Implement resources (scripts, references, assets as needed)
+5. Iterate based on real usage
 
 Follow these steps in order, skipping only if there is a clear reason why they are not applicable.
 
@@ -238,10 +237,60 @@
 
 ### Step 2: Planning the Reusable Skill Contents
 
-To turn concrete examples into an effective skill, analyze each example by:
+Before creating the skill, analyze each example by:
 
 1. Considering how to execute on the example from scratch
 2. Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly
+
+Example: When building a `pdf-editor` skill to handle queries like "Help me rotate this PDF," the analysis shows:
+
+1. Rotating a PDF requires re-writing the same code each time
+2. A `scripts/rotate_pdf.py` script would be helpful to store in the skill
+
+### Step 3: Creating the Skill Directory Structure
+
+Create the skill manually by setting up the directory structure:
+
+```bash
+# Create the skill directory
+mkdir -p skill-name
+
+# Create optional subdirectories as needed
+mkdir -p skill-name/scripts      # for executable code
+mkdir -p skill-name/references   # for documentation to load into context
+mkdir -p skill-name/assets       # for files used in output
+```
+
+Then create the SKILL.md file with the required YAML frontmatter:
+
+```markdown
+---
+name: skill-name
+description: Brief description of what the skill does and when to use it.
+---
+
+# Skill Title
+
+(Instructions and guidance for using the skill)
+```
+
+### Step 4: Implementing Resources
+
+Add any scripts, references, or assets identified in Step 2:
+
+- **Scripts**: Write executable code in `scripts/` directory
+- **References**: Add documentation files in `references/` directory  
+- **Assets**: Add templates, images, or other output files in `assets/` directory
+
+Ensure SKILL.md references these files and describes when to use them.
+
+### Step 5: Iterate Based on Real Usage
+
+Test the skill with real examples and refine based on:
+
+- Whether the skill triggers appropriately (adjust description)
+- Whether instructions are clear and complete
+- Whether bundled resources work correctly
 
 Example: When building a `pdf-editor` skill to handle queries like "Help me rotate this PDF," the analysis shows:
 

lineage_content_snapshot:
{"SKILL.md": "---\nname: skill-creator\ndescription: Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.\n---\n\n# Skill Creator\n\nThis skill provides guidance for creating effective skills.\n\n## About Skills\n\nSkills are modular, self-contained packages that extend the agent's capabilities by providing\nspecialized knowledge, workflows, and tools. Think of them as \"onboarding guides\" for specific\ndomains or tasks—they transform the agent from a general-purpose agent into a specialized agent\nequipped with procedural knowledge that no model can fully possess.\n\n### What Skills Provide\n\n1. Specialized workflows - Multi-step procedures for specific domains\n2. Tool integrations - Instructions for working with specific file formats or APIs\n3. Domain expertise - Company-specific knowledge, schemas, business logic\n4. Bundled resources - Scripts, references, and assets for complex and repetitive tasks\n\n## Core Principles\n\n### Concise is Key\n\nThe context window is a public good. Skills share the context window with everything else the agent needs: system prompt, conversation history, other Skills' metadata, and the actual user request.\n\n**Default assumption: the agent is already very smart.** Only add context the agent doesn't already have. Challenge each piece of information: \"Does the agent really need this explanation?\" and \"Does this paragraph justify its token cost?\"\n\nPrefer concise examples over verbose explanations.\n\n### Set Appropriate Degrees of Freedom\n\nMatch the level of specificity to the task's fragility and variability:\n\n**High freedom (text-based instructions)**: Use when multiple approaches are valid, decisions depend on context, or heuristics guide the approach.\n\n**Medium freedom (pseudocode or scripts with parameters)**: Use when a preferred pattern exists, some variation is acceptable, or configuration affects behavior.\n\n**Low freedom (specific scripts, few parameters)**: Use when operations are fragile and error-prone, consistency is critical, or a specific sequence must be followed.\n\nThink of the agent as exploring a path: a narrow bridge with cliffs needs specific guardrails (low freedom), while an open field allows many routes (high freedom).\n\n### Anatomy of a Skill\n\nEvery skill consists of a required SKILL.md file and optional bundled resources:\n\n```\nskill-name/\n├── SKILL.md (required)\n│   ├── YAML frontmatter metadata (required)\n│   │   ├── name: (required)\n│   │   └── description: (required)\n│   └── Markdown instructions (required)\n└── Bundled Resources (optional)\n    ├── scripts/          - Executable code (Python/Bash/etc.)\n    ├── references/       - Documentation intended to be loaded into context as needed\n    └── assets/           - Files used in output (templates, icons, fonts, etc.)\n```\n\n#### SKILL.md (required)\n\nEvery SKILL.md consists of:\n\n- **Frontmatter** (YAML): Contains `name` and `description` fields. These are the only fields that the agent reads to determine when the skill gets used, thus it is very important to be clear and comprehensive in describing what the skill is, and when it should be used.\n- **Body** (Markdown): Instructions and guidance for using the skill. Only loaded AFTER the skill triggers (if at all).\n\n#### Bundled Resources (optional)\n\n##### Scripts (`scripts/`)\n\nExecutable code (Python/Bash/etc.) for tasks that require deterministic reliability or are repeatedly rewritten.\n\n- **When to include**: When the same code is being rewritten repeatedly or deterministic reliability is needed\n- **Example**: `scripts/rotate_pdf.py` for PDF rotation tasks\n- **Benefits**: Token efficient, deterministic, may be executed without loading into context\n- **Note**: Scripts may still need to be read by the agent for patching or environment-specific adjustments\n\n##### References (`references/`)\n\nDocumentation and reference material intended to be loaded as needed into context to inform the agent's process and thinking.\n\n- **When to include**: For documentation that the agent should reference while working\n- **Examples**: `references/finance.md` for financial schemas, `references/mnda.md` for company NDA template, `references/policies.md` for company policies, `references/api_docs.md` for API specifications\n- **Use cases**: Database schemas, API documentation, domain knowledge, company policies, detailed workflow guides\n- **Benefits**: Keeps SKILL.md lean, loaded only when the agent determines it's needed\n- **Best practice**: If files are large (>10k words), include grep search patterns in SKILL.md\n- **Avoid duplication**: Information should live in either SKILL.md or references files, not both. Prefer references files for detailed information unless it's truly core to the skill—this keeps SKILL.md lean while making information discoverable without hogging the context window. Keep only essential procedural instructions and workflow guidance in SKILL.md; move detailed reference material, schemas, and examples to references files.\n\n##### Assets (`assets/`)\n\nFiles not intended to be loaded into context, but rather used within the output the agent produces.\n\n- **When to include**: When the skill needs files that will be used in the final output\n- **Examples**: `assets/logo.png` for brand assets, `assets/slides.pptx` for PowerPoint templates, `assets/frontend-template/` for HTML/React boilerplate, `assets/font.ttf` for typography\n- **Use cases**: Templates, images, icons, boilerplate code, fonts, sample documents that get copied or modified\n- **Benefits**: Separates output resources from documentation, enables the agent to use files without loading them into context\n\n#### What to Not Include in a Skill\n\nA skill should only contain essential files that directly support its functionality. Do NOT create extraneous documentation or auxiliary files, including:\n\n- README.md\n- INSTALLATION_GUIDE.md\n- QUICK_REFERENCE.md\n- CHANGELOG.md\n- etc.\n\nThe skill should only contain the information needed for an AI agent to do the job at hand. It should not contain auxiliary context about the process that went into creating it, setup and testing procedures, user-facing documentation, etc. Creating additional documentation files just adds clutter and confusion.\n\n### Progressive Disclosure Design Principle\n\nSkills use a three-level loading system to manage context efficiently:\n\n1. **Metadata (name + description)** - Always in context (~100 words)\n2. **SKILL.md body** - When skill triggers (<5k words)\n3. **Bundled resources** - As needed by the agent (Unlimited because scripts can be executed without reading into context window)\n\n#### Progressive Disclosure Patterns\n\nKeep SKILL.md body to the essentials and under 500 lines to minimize context bloat. Split content into separate files when approaching this limit. When splitting out content into other files, it is very important to reference them from SKILL.md and describe clearly when to read them, to ensure the reader of the skill knows they exist and when to use them.\n\n**Key principle:** When a skill supports multiple variations, frameworks, or options, keep only the core workflow and selection guidance in SKILL.md. Move variant-specific details (patterns, examples, configuration) into separate reference files.\n\n**Pattern 1: High-level guide with references**\n\n```markdown\n# PDF Processing\n\n## Quick start\n\nExtract text with pdfplumber:\n[code example]\n\n## Advanced features\n\n- **Form filling**: See [FORMS.md](FORMS.md) for complete guide\n- **API reference**: See [REFERENCE.md](REFERENCE.md) for all methods\n- **Examples**: See [EXAMPLES.md](EXAMPLES.md) for common patterns\n```\n\nthe agent loads FORMS.md, REFERENCE.md, or EXAMPLES.md only when needed.\n\n**Pattern 2: Domain-specific organization**\n\nFor Skills with multiple domains, organize content by domain to avoid loading irrelevant context:\n\n```\nbigquery-skill/\n├── SKILL.md (overview and navigation)\n└── reference/\n    ├── finance.md (revenue, billing metrics)\n    ├── sales.md (opportunities, pipeline)\n    ├── product.md (API usage, features)\n    └── marketing.md (campaigns, attribution)\n```\n\nWhen a user asks about sales metrics, the agent only reads sales.md.\n\nSimilarly, for skills supporting multiple frameworks or variants, organize by variant:\n\n```\ncloud-deploy/\n├── SKILL.md (workflow + provider selection)\n└── references/\n    ├── aws.md (AWS deployment patterns)\n    ├── gcp.md (GCP deployment patterns)\n    └── azure.md (Azure deployment patterns)\n```\n\nWhen the user chooses AWS, the agent only reads aws.md.\n\n**Pattern 3: Conditional details**\n\nShow basic content, link to advanced content:\n\n```markdown\n# DOCX Processing\n\n## Creating documents\n\nUse docx-js for new documents. See [DOCX-JS.md](DOCX-JS.md).\n\n## Editing documents\n\nFor simple edits, modify the XML directly.\n\n**For tracked changes**: See [REDLINING.md](REDLINING.md)\n**For OOXML details**: See [OOXML.md](OOXML.md)\n```\n\nthe agent reads REDLINING.md or OOXML.md only when the user needs those features.\n\n**Important guidelines:**\n\n- **Avoid deeply nested references** - Keep references one level deep from SKILL.md. All reference files should link directly from SKILL.md.\n- **Structure longer reference files** - For files longer than 100 lines, include a table of contents at the top so the agent can see the full scope when previewing.\n\n## Skill Creation Process\n\nSkill creation involves these steps. All steps are performed manually by creating the directory structure and files directly—there are no helper scripts.\n\n1. Understand the skill with concrete examples\n2. Plan reusable skill contents (scripts, references, assets)\n3. Create the skill directory and SKILL.md file\n4. Implement resources (scripts, references, assets as needed)\n5. Iterate based on real usage\n\nFollow these steps in order, skipping only if there is a clear reason why they are not applicable.\n\n### Skill Naming\n\n- Use lowercase letters, digits, and hyphens only; normalize user-provided titles to hyphen-case (e.g., \"Plan Mode\" -> `plan-mode`).\n- When generating names, generate a name under 64 characters (letters, digits, hyphens).\n- Prefer short, verb-led phrases that describe the action.\n- Namespace by tool when it improves clarity or triggering (e.g., `gh-address-comments`, `linear-address-issue`).\n- Name the skill folder exactly after the skill name.\n\n### Step 1: Understanding the Skill with Concrete Examples\n\nSkip this step only when the skill's usage patterns are already clearly understood. It remains valuable even when working with an existing skill.\n\nTo create an effective skill, clearly understand concrete examples of how the skill will be used. This understanding can come from either direct user examples or generated examples that are validated with user feedback.\n\nFor example, when building an image-editor skill, relevant questions include:\n\n- \"What functionality should the image-editor skill support? Editing, rotating, anything else?\"\n- \"Can you give some examples of how this skill would be used?\"\n- \"I can imagine users asking for things like 'Remove the red-eye from this image' or 'Rotate this image'. Are there other ways you imagine this skill being used?\"\n- \"What would a user say that should trigger this skill?\"\n\nTo avoid overwhelming users, avoid asking too many questions in a single message. Start with the most important questions and follow up as needed for better effectiveness.\n\nConclude this step when there is a clear sense of the functionality the skill should support.\n\n### Step 2: Planning the Reusable Skill Contents\n\nBefore creating the skill, analyze each example by:\n\n1. Considering how to execute on the example from scratch\n2. Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly\n\nExample: When building a `pdf-editor` skill to handle queries like \"Help me rotate this PDF,\" the analysis shows:\n\n1. Rotating a PDF requires re-writing the same code each time\n2. A `scripts/rotate_pdf.py` script would be helpful to store in the skill\n\n### Step 3: Creating the Skill Directory Structure\n\nCreate the skill manually by setting up the directory structure:\n\n```bash\n# Create the skill directory\nmkdir -p skill-name\n\n# Create optional subdirectories as needed\nmkdir -p skill-name/scripts      # for executable code\nmkdir -p skill-name/references   # for documentation to load into context\nmkdir -p skill-name/assets       # for files used in output\n```\n\nThen create the SKILL.md file with the required YAML frontmatter:\n\n```markdown\n---\nname: skill-name\ndescription: Brief description of what the skill does and when to use it.\n---\n\n# Skill Title\n\n(Instructions and guidance for using the skill)\n```\n\n### Step 4: Implementing Resources\n\nAdd any scripts, references, or assets identified in Step 2:\n\n- **Scripts**: Write executable code in `scripts/` directory\n- **References**: Add documentation files in `references/` directory  \n- **Assets**: Add templates, images, or other output files in `assets/` directory\n\nEnsure SKILL.md references these files and describes when to use them.\n\n### Step 5: Iterate Based on Real Usage\n\nTest the skill with real examples and refine based on:\n\n- Whether the skill triggers appropriately (adjust description)\n- Whether instructions are clear and complete\n- Whether bundled resources work correctly\n\nExample: When building a `pdf-editor` skill to handle queries like \"Help me rotate this PDF,\" the analysis shows:\n\n1. Rotating a PDF requires re-writing the same code each time\n2. A `scripts/rotate_pdf.py` script would be helpful to store in the skill\n\nExample: When designing a `frontend-webapp-builder` skill for queries like \"Build me a todo app\" or \"Build me a dashboard to track my steps,\" the analysis shows:\n\n1. Writing a frontend webapp requires the same boilerplate HTML/React each time\n2. An `assets/hello-world/` template containing the boilerplate HTML/React project files would be helpful to store in the skill\n\nExample: When building a `big-query` skill to handle queries like \"How many users have logged in today?\" the analysis shows:\n\n1. Querying BigQuery requires re-discovering the table schemas and relationships each time\n2. A `references/schema.md` file documenting the table schemas would be helpful to store in the skill\n\nTo establish the skill's contents, analyze each concrete example to create a list of the reusable resources to include: scripts, references, and assets.\n\n### Step 3: Initializing the Skill\n\nAt this point, it is time to actually create the skill.\n\nSkip this step only if the skill being developed already exists, and iteration or packaging is needed. In this case, continue to the next step.\n\nWhen creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable.\n\nUsage:\n\n```bash\nscripts/init_skill.py <skill-name> --path <output-directory> [--resources scripts,references,assets] [--examples]\n```\n\nExamples:\n\n```bash\nscripts/init_skill.py my-skill --path skills/public\nscripts/init_skill.py my-skill --path skills/public --resources scripts,references\nscripts/init_skill.py my-skill --path skills/public --resources scripts --examples\n```\n\nThe script:\n\n- Creates the skill directory at the specified path\n- Generates a SKILL.md template with proper frontmatter and TODO placeholders\n- Optionally creates resource directories based on `--resources`\n- Optionally adds example files when `--examples` is set\n\nAfter initialization, customize the SKILL.md and add resources as needed. If you used `--examples`, replace or delete placeholder files.\n\n### Step 4: Edit the Skill\n\nWhen editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of the agent to use. Include information that would be beneficial and non-obvious to the agent. Consider what procedural knowledge, domain-specific details, or reusable assets would help another the agent instance execute these tasks more effectively.\n\n#### Learn Proven Design Patterns\n\nConsult these helpful guides based on your skill's needs:\n\n- **Multi-step processes**: See references/workflows.md for sequential workflows and conditional logic\n- **Specific output formats or quality standards**: See references/output-patterns.md for template and example patterns\n\nThese files contain established best practices for effective skill design.\n\n#### Start with Reusable Skill Contents\n\nTo begin implementation, start with the reusable resources identified above: `scripts/`, `references/`, and `assets/` files. Note that this step may require user input. For example, when implementing a `brand-guidelines` skill, the user may need to provide brand assets or templates to store in `assets/`, or documentation to store in `references/`.\n\nAdded scripts must be tested by actually running them to ensure there are no bugs and that the output matches what is expected. If there are many similar scripts, only a representative sample needs to be tested to ensure confidence that they all work while balancing time to completion.\n\nIf you used `--examples`, delete any placeholder files that are not needed for the skill. Only create resource directories that are actually required.\n\n#### Update SKILL.md\n\n**Writing Guidelines:** Always use imperative/infinitive form.\n\n##### Frontmatter\n\nWrite the YAML frontmatter with `name` and `description`:\n\n- `name`: The skill name\n- `description`: This is the primary triggering mechanism for your skill, and helps the agent understand when to use the skill.\n  - Include both what the Skill does and specific triggers/contexts for when to use it.\n  - Include all \"when to use\" information here - Not in the body. The body is only loaded after triggering, so \"When to Use This Skill\" sections in the body are not helpful to the agent.\n  - Example description for a `docx` skill: \"Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when the agent needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks\"\n\nDo not include any other fields in YAML frontmatter.\n\n##### Body\n\nWrite instructions for using the skill and its bundled resources.\n\n### Step 5: Packaging a Skill\n\nOnce development of the skill is complete, it must be packaged into a distributable .skill file that gets shared with the user. The packaging process automatically validates the skill first to ensure it meets all requirements:\n\n```bash\nscripts/package_skill.py <path/to/skill-folder>\n```\n\nOptional output directory specification:\n\n```bash\nscripts/package_skill.py <path/to/skill-folder> ./dist\n```\n\nThe packaging script will:\n\n1. **Validate** the skill automatically, checking:\n\n   - YAML frontmatter format and required fields\n   - Skill naming conventions and directory structure\n   - Description completeness and quality\n   - File organization and resource references\n\n2. **Package** the skill if validation passes, creating a .skill file named after the skill (e.g., `my-skill.skill`) that includes all files and maintains the proper directory structure for distribution. The .skill file is a zip file with a .skill extension.\n\nIf validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again.\n\n### Step 6: Iterate\n\nAfter testing the skill, users may request improvements. Often this happens right after using the skill, with fresh context of how the skill performed.\n\n**Iteration workflow:**\n\n1. Use the skill on real tasks\n2. Notice struggles or inefficiencies\n3. Identify how SKILL.md or bundled resources should be updated\n4. Implement changes and test again\n"}


skill_id: create-full-stack-panel-feature-enhanced__v3_3beb370f
name: create-full-stack-panel-feature-with-server-wiring
path: example/skills/create-full-stack-panel-feature-with-server-wiring/SKILL.md
category: workflow
visibility: private
lineage_origin: fixed
lineage_source_task_id: v2b-task-09
lineage_created_at: 2026-03-10T20:53:34.176577
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T20:53:34.176590
last_updated: 2026-03-10T20:53:34.176591
lineage_change_summary:
Added a Unicode/multi-byte character caveat to the file-creation section warning that write_file may fail silently on emoji or box-drawing characters, with ASCII-safe fallbacks and a run_shell heredoc recovery path.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -149,6 +149,27 @@
 ### 4. Create Files in Order
 
 Generate files in dependency order (backend → frontend → registration):
+
+> ⚠️ **Unicode / multi-byte character warning**
+> `write_file` may fail with `'unknown error'` (or silently produce a truncated file) when
+> the content contains multi-byte Unicode characters such as emoji (e.g. document/chart icons)
+> or box-drawing characters (e.g. ─ │ ╔).
+> **Prevention:** prefer ASCII-safe equivalents in generated source code — e.g. `[OK]`
+> instead of a checkmark emoji, `->` instead of an arrow, plain hyphens/pipes instead of
+> box-drawing chars.
+> **Recovery:** if a `write_file` call returns an error or the resulting file is empty or
+> truncated, fall back to `run_shell` with a heredoc:
+>
+> ```bash
+> # Recovery path -- write file content via shell heredoc (avoids write_file Unicode bug)
+> cat > path/to/file.ts << 'HEREDOC'
+> // file content here -- ensure no raw emoji or box-drawing chars are present
+> HEREDOC
+> ```
+>
+> Verify the file was written correctly with `wc -l path/to/file.ts` or
+> `head -5 path/to/file.ts` after every write that previously failed.
+
 
 #### A. Server Route Handler
 

lineage_content_snapshot:
{"SKILL.md": "---\nname: create-full-stack-panel-feature-with-server-wiring\ndescription: Multi-layer workflow for creating complete panel features by analyzing existing patterns and generating coordinated server routes, services, UI components, registration, styling files, AND explicitly wiring new handlers into the server's dispatch table (supports both Express middleware and manual http.createServer switch/case patterns).\n---\n\n# Create Full-Stack Panel Feature (with Server Wiring)\n\nThis skill guides you through creating a complete, pattern-consistent panel feature across all application layers: backend API routes, service logic, frontend components, registration, styling — **and crucially, wiring the new route handler into the server's dispatch table**, whether that uses Express or a manual `http.createServer` pattern.\n\n## When to Use\n\n- Adding a new panel type to a dashboard or admin interface\n- Creating a feature that spans multiple architectural layers\n- Ensuring consistency with existing codebase patterns across the stack\n- Projects that use a manual `http.createServer` dispatch loop instead of Express router mounting\n\n## Workflow Steps\n\n### 1. Identify Pattern Files\n\nLocate existing examples for each layer you need to implement:\n\n```bash\n# Find existing server routes\nfind . -name \"*route*.js\" -o -name \"*routes*.js\" -o -name \"*route*.ts\" -o -name \"*routes*.ts\" | grep -E \"(panel|dashboard|server)\"\n\n# Find service files\nfind . -name \"*service*.js\" -o -name \"*service*.ts\" | grep -E \"(panel|dashboard|server)\"\n\n# Find UI components\nfind . -name \"*.jsx\" -o -name \"*.tsx\" -o -name \"*.ts\" | grep -i panel\n\n# Find the server entry point (critical for wiring step)\nfind . -name \"index.ts\" -o -name \"index.js\" -o -name \"server.ts\" -o -name \"server.js\" | grep -v node_modules | head -10\n\n# Find registration/config files\nfind . -name \"*config*.js\" -o -name \"*registry*.js\" -o -name \"*config*.ts\" -o -name \"*registry*.ts\"\n\n# Find styling files\nfind . -name \"*.css\" -o -name \"*.scss\" | grep -i panel\n```\n\n### 2. Read and Analyze Patterns\n\nRead 1-2 representative examples from **each layer** to understand:\n\n- Naming conventions (e.g., `MetricsPanel`, `metrics-service.ts`)\n- Code structure and organization\n- Import/export patterns\n- Registration mechanisms\n- API endpoint patterns\n- Error handling approaches\n- Styling conventions\n\n**Also read the server entry point** (`server/index.ts`, `src/server.ts`, etc.) to determine which dispatch pattern is used:\n\n```bash\n# Read the server entry point to identify routing mechanism\ncat server/index.ts   # or server/index.js, src/server.ts, etc.\n```\n\n**Example analysis checklist:**\n\n```\nServer Route Pattern:\n- ✓ Endpoint naming: /api/panels/{type}\n- ✓ Authentication middleware\n- ✓ Response format: { success, data, error }\n\nService Pattern:\n- ✓ Export structure: class vs functions\n- ✓ Data transformation logic\n- ✓ Error propagation\n\nComponent Pattern:\n- ✓ Props interface\n- ✓ State management (hooks/class)\n- ✓ Data fetching approach\n- ✓ Loading/error states\n\nRegistration Pattern:\n- ✓ Registry file location\n- ✓ Registration format (array/object)\n- ✓ Required metadata fields\n\nServer Dispatch Pattern:\n- ✓ Express app.use() / router.use()  → Express routing\n- ✓ switch/case on url/pathname       → Manual dispatch table\n- ✓ if/else if chain on url           → Manual dispatch chain\n- ✓ handler map object { '/path': fn }→ Handler map pattern\n```\n\n### 3. Plan Your Feature Files\n\nBased on patterns, list the files you need to create **and** the existing files you need to update:\n\n**Typical full-stack panel structure:**\n1. **Server route handler** (e.g., `server/routes/system-health-route.ts`)\n2. **Service layer** (e.g., `server/services/system-health-service.ts`)\n3. **UI component** (e.g., `client/panels/SystemHealthPanel.ts`)\n4. **Panel registration** (e.g., update `client/config/panel-registry.ts`)\n5. **Styling** (e.g., `client/styles/system-health-panel.css`)\n6. **Type definitions** (if TypeScript, e.g., `types/system-health.ts`)\n7. **⚠️ Server wiring** (update `server/index.ts` to import and dispatch to the new handler)\n\n### 3b. Detect Project Framework\n\n**Before selecting templates in Step 4, determine the frontend framework in use:**\n\n```bash\n# Check for React\ngrep -s \"react\" package.json | head -5\n\n# Check for TypeScript without React (vanilla TS)\nls client/components/*.ts 2>/dev/null | head -3\nls client/panels/*.ts 2>/dev/null | head -3\n\n# Check for a Panel base class (common in vanilla TS dashboards)\ngrep -r \"class.*Panel\" client/ --include=\"*.ts\" -l | head -3\ngrep -r \"extends Panel\" client/ --include=\"*.ts\" -l | head -3\n```\n\n**Decision rule:**\n- If `react` or `react-dom` is present in `package.json` → use the **React/JSX template** (Step 4C-React).\n- If components are `.ts` files extending a `Panel` base class → use the **Vanilla TS template** (Step 4C-Vanilla).\n- When in doubt, read one or two existing component files to confirm before proceeding.\n\n### 3c. Detect Server Dispatch Pattern\n\n**Read the server entry point and classify it:**\n\n```bash\n# Look for Express usage\ngrep -n \"express\\|app\\.use\\|router\\.\" server/index.ts 2>/dev/null || \\\ngrep -n \"express\\|app\\.use\\|router\\.\" server/index.js 2>/dev/null || \\\ngrep -n \"express\\|app\\.use\\|router\\.\" src/server.ts 2>/dev/null\n\n# Look for manual http.createServer dispatch\ngrep -n \"createServer\\|switch.*url\\|switch.*pathname\\|req\\.url\\|req\\.pathname\" server/index.ts 2>/dev/null || \\\ngrep -n \"createServer\\|switch.*url\\|switch.*pathname\\|req\\.url\\|req\\.pathname\" server/index.js 2>/dev/null\n```\n\n**Decision rule:**\n- `app.use('/api/feature', featureRouter)` style → **Express pattern** (Step 5A)\n- `switch(pathname) { case '/api/feature': ... }` style → **Manual switch/case pattern** (Step 5B)\n- `if (url.startsWith('/api/feature'))` style → **Manual if/else chain** (Step 5C)\n- `{ '/api/feature': handlerFn }` map style → **Handler map pattern** (Step 5D)\n\n### 4. Create Files in Order\n\nGenerate files in dependency order (backend → frontend → registration):\n\n> ⚠️ **Unicode / multi-byte character warning**\n> `write_file` may fail with `'unknown error'` (or silently produce a truncated file) when\n> the content contains multi-byte Unicode characters such as emoji (e.g. document/chart icons)\n> or box-drawing characters (e.g. ─ │ ╔).\n> **Prevention:** prefer ASCII-safe equivalents in generated source code — e.g. `[OK]`\n> instead of a checkmark emoji, `->` instead of an arrow, plain hyphens/pipes instead of\n> box-drawing chars.\n> **Recovery:** if a `write_file` call returns an error or the resulting file is empty or\n> truncated, fall back to `run_shell` with a heredoc:\n>\n> ```bash\n> # Recovery path -- write file content via shell heredoc (avoids write_file Unicode bug)\n> cat > path/to/file.ts << 'HEREDOC'\n> // file content here -- ensure no raw emoji or box-drawing chars are present\n> HEREDOC\n> ```\n>\n> Verify the file was written correctly with `wc -l path/to/file.ts` or\n> `head -5 path/to/file.ts` after every write that previously failed.\n\n\n#### A. Server Route Handler\n\n**Express style:**\n```javascript\n// server/routes/{feature}-route.js\nconst express = require('express');\nconst router = express.Router();\nconst featureService = require('../services/{feature}-service');\n\nrouter.get('/api/panels/{feature}', async (req, res) => {\n  try {\n    const data = await featureService.getData();\n    res.json({ success: true, data });\n  } catch (error) {\n    res.status(500).json({ success: false, error: error.message });\n  }\n});\n\nmodule.exports = router;\n```\n\n**Manual dispatch style (TypeScript):**\n```typescript\n// server/routes/{feature}-route.ts\nimport { IncomingMessage, ServerResponse } from 'http';\nimport { featureService } from '../services/{feature}-service';\n\nexport async function handle{Feature}Request(\n  req: IncomingMessage,\n  res: ServerResponse,\n  action?: string\n): Promise<void> {\n  try {\n    const data = await featureService.getData(action);\n    res.writeHead(200, { 'Content-Type': 'application/json' });\n    res.end(JSON.stringify({ success: true, data }));\n  } catch (err) {\n    res.writeHead(500, { 'Content-Type': 'application/json' });\n    res.end(JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }));\n  }\n}\n```\n\n#### B. Service Layer\n\n```typescript\n// server/services/{feature}-service.ts\nexport interface {Feature}Data {\n  // Define your data shape\n}\n\nexport async function getData(action?: string): Promise<{Feature}Data> {\n  // Implementation following existing service patterns\n  // - Data fetching\n  // - Business logic\n  // - Data transformation\n  return {} as {Feature}Data;\n}\n```\n\n#### C-React. UI Component (React/JSX projects)\n\n*Use this template only when React is confirmed in Step 3b.*\n\n```javascript\n// client/components/{Feature}Panel.jsx\nimport React, { useState, useEffect } from 'react';\nimport './styles/{feature}-panel.css';\n\nconst FeaturePanel = () => {\n  const [data, setData] = useState(null);\n  const [loading, setLoading] = useState(true);\n  const [error, setError] = useState(null);\n\n  useEffect(() => {\n    fetch('/api/panels/{feature}')\n      .then(res => res.json())\n      .then(result => {\n        setData(result.data);\n        setLoading(false);\n      })\n      .catch(err => {\n        setError(err.message);\n        setLoading(false);\n      });\n  }, []);\n\n  if (loading) return <div>Loading...</div>;\n  if (error) return <div>Error: {error}</div>;\n\n  return (\n    <div className=\"{feature}-panel\">\n      {/* Render data following UI patterns */}\n    </div>\n  );\n};\n\nexport default FeaturePanel;\n```\n\n#### C-Vanilla. UI Component (Vanilla TypeScript / Panel base-class projects)\n\n*Use this template when components extend a Panel base class (no React).*\n\n```typescript\n// client/panels/{Feature}Panel.ts\nimport { Panel } from '../core/Panel';\n\nexport class FeaturePanel extends Panel {\n  private intervalId: number | null = null;\n\n  constructor(id: string) {\n    super(id);\n    this.setTitle('Feature Display Name');\n  }\n\n  async onLoad(): Promise<void> {\n    if (this.isFetching) return;\n    this.isFetching = true;\n    try {\n      const res = await fetch('/api/panels/{feature}', {\n        headers: { Authorization: `Bearer ${localStorage.getItem('token') ?? ''}` }\n      });\n      if (!res.ok) throw new Error(`HTTP ${res.status}`);\n      const { data } = await res.json();\n      this.render(data);\n    } catch (err) {\n      this.showError(err instanceof Error ? err.message : String(err), () => this.onLoad());\n    } finally {\n      this.isFetching = false;\n    }\n  }\n\n  private render(data: unknown): void {\n    const container = document.createElement('div');\n    container.className = '{feature}-panel';\n    // Build DOM nodes from data instead of JSX:\n    // const item = document.createElement('p');\n    // item.textContent = String(data);\n    // container.appendChild(item);\n    this.setContent(container);\n  }\n\n  // Call this to start polling (optional)\n  startPolling(intervalMs = 60_000): void {\n    this.onLoad();\n    this.intervalId = window.setInterval(() => this.onLoad(), intervalMs);\n  }\n\n  destroy(): void {\n    if (this.intervalId !== null) clearInterval(this.intervalId);\n    super.destroy();\n  }\n}\n```\n\n**Key differences from React template:**\n- Extends `Panel` base class; uses `setContent()` / `showError()` / `setTitle()` / `setCount()` instead of React state.\n- DOM construction via `document.createElement` / `innerHTML` instead of JSX.\n- No `useState` / `useEffect`; lifecycle is `onLoad()` + `destroy()`.\n- File extension is `.ts`, not `.jsx`/`.tsx`.\n- Registration passes a factory function or class constructor, not a JSX component.\n\n#### D. Panel Registration\n\n**React projects** — import the component directly:\n\n```javascript\n// client/config/panel-registry.js (update existing)\nimport FeaturePanel from '../components/FeaturePanel';\n\nexport const panels = [\n  // ... existing panels\n  {\n    id: '{feature}',\n    name: 'Feature Display Name',\n    component: FeaturePanel,\n    icon: 'icon-name',\n    category: 'appropriate-category'\n  }\n];\n```\n\n**Vanilla TS projects** — register via factory:\n\n```typescript\n// client/config/panel-registry.ts (update existing)\nimport { FeaturePanel } from '../panels/FeaturePanel';\n\nexport const panels = [\n  // ... existing panels\n  {\n    id: '{feature}',\n    name: 'Feature Display Name',\n    factory: (id: string) => new FeaturePanel(id),\n    icon: 'icon-name',\n    category: 'appropriate-category'\n  }\n];\n```\n\n#### E. Styling\n\n```css\n/* client/styles/{feature}-panel.css */\n.{feature}-panel {\n  /* Follow existing panel styling conventions */\n  padding: 1rem;\n  border-radius: 4px;\n}\n\n.{feature}-panel__header {\n  /* Consistent header styling */\n}\n```\n\n### 5. Wire the Route Handler into the Server\n\n**This step is frequently missed and causes features to silently return 404.** After creating the route handler, you must register it with the server's request dispatcher.\n\nFirst, re-read the server entry point to confirm the exact dispatch pattern:\n\n```bash\ncat server/index.ts    # adjust path as needed\n```\n\n---\n\n#### 5A. Express Pattern\n\nAdd a `require`/`import` and an `app.use()` call alongside the existing routes:\n\n```typescript\n// server/index.ts  — Express style\nimport featureRouter from './routes/{feature}-route';\n\n// Place with other app.use() route registrations\napp.use('/api/panels/{feature}', featureRouter);\n```\n\n**Checklist:**\n- [ ] Import is at the top of the file with other route imports\n- [ ] `app.use()` call is placed before any catch-all `404` handler\n- [ ] Path prefix matches what the client fetches\n\n---\n\n#### 5B. Manual switch/case Pattern\n\nThis is the pattern used when the server is built with `http.createServer` and routes are dispatched via a `switch` on the URL pathname. Read the existing cases to match the exact style, then add a new `case`:\n\n```typescript\n// server/index.ts — manual switch/case dispatch\nimport { handle{Feature}Request } from './routes/{feature}-route';\n\n// Inside the createServer callback, find the switch block:\nconst url = new URL(req.url ?? '/', `http://${req.headers.host}`);\nconst pathname = url.pathname;\nconst action = url.searchParams.get('action') ?? undefined;\n\nswitch (pathname) {\n  // ... existing cases ...\n\n  case '/api/panels/{feature}':\n    await handle{Feature}Request(req, res, action);\n    break;\n\n  // ... keep the default/404 case last ...\n  default:\n    res.writeHead(404, { 'Content-Type': 'application/json' });\n    res.end(JSON.stringify({ error: 'Not found' }));\n}\n```\n\n**Checklist:**\n- [ ] Import is at the top of the file with other handler imports\n- [ ] New `case` is placed **before** the `default` case\n- [ ] `break` (or `return`) statement is present after the handler call\n- [ ] The path string exactly matches the client's `fetch('/api/panels/{feature}')` call\n- [ ] `action` / query-param forwarding matches the handler's signature\n\n---\n\n#### 5C. Manual if/else Chain Pattern\n\nIf the server uses an `if/else if` chain instead of `switch`:\n\n```typescript\n// server/index.ts — if/else chain\nimport { handle{Feature}Request } from './routes/{feature}-route';\n\n// Inside the createServer callback:\nif (pathname === '/api/panels/existing-a') {\n  await handleExistingA(req, res);\n} else if (pathname === '/api/panels/existing-b') {\n  await handleExistingB(req, res);\n// ↓ Add your new branch before the final else/404 block\n} else if (pathname === '/api/panels/{feature}') {\n  await handle{Feature}Request(req, res, action);\n} else {\n  res.writeHead(404, { 'Content-Type': 'application/json' });\n  res.end(JSON.stringify({ error: 'Not found' }));\n}\n```\n\n**Checklist:**\n- [ ] New `else if` branch is before the final `else` (404) block\n- [ ] Import is at the top of the file\n\n---\n\n#### 5D. Handler Map Pattern\n\nIf the server uses a map object to look up handlers:\n\n```typescript\n// server/index.ts — handler map\nimport { handle{Feature}Request } from './routes/{feature}-route';\n\nconst handlers: Record<string, RequestHandler> = {\n  '/api/panels/existing-a': handleExistingA,\n  '/api/panels/existing-b': handleExistingB,\n  // ↓ Add your new entry\n  '/api/panels/{feature}': handle{Feature}Request,\n};\n```\n\n**Checklist:**\n- [ ] New entry key matches client fetch URL exactly\n- [ ] Import is at the top of the file\n\n---\n\n#### 5E. Verify Wiring\n\nAfter updating the server entry point, confirm the wiring is complete:\n\n```bash\n# Confirm the import exists\ngrep -n \"handle{Feature}Request\\|{feature}-route\" server/index.ts\n\n# Confirm the dispatch entry exists\ngrep -n \"{feature}\" server/index.ts\n\n# Confirm no syntax errors (TypeScript projects)\nnpx tsc --noEmit 2>&1 | head -20\n\n# Quick smoke test: start the server and curl the endpoint\n# curl http://localhost:PORT/api/panels/{feature}\n```\n\n### 6. Verify Pattern Consistency\n\nAfter creating all files and wiring the server, check:\n\n- [ ] Naming follows project conventions across all layers\n- [ ] Import/export statements are consistent\n- [ ] Error handling matches existing patterns\n- [ ] API response format is uniform\n- [ ] Component structure mirrors other panels\n- [ ] Registration metadata is complete\n- [ ] CSS class naming follows BEM or project convention\n- [ ] Server entry point updated (new route is reachable)\n\n### 7. Test Integration Points\n\nVerify that all pieces connect:\n\n```bash\n# Server route handler file exists\nls server/routes/{feature}-route.*\n\n# Server entry point imports and dispatches the new handler\ngrep -n \"{feature}\" server/index.ts   # or server/index.js\n\n# Component is imported in registry\ngrep -r \"import.*{Feature}Panel\" client/config/\n\n# Styles are imported\ngrep -r \"import.*{feature}-panel.css\" client/\n```\n\n## Key Principles\n\n1. **Pattern before implementation**: Always read existing code first — especially the server entry point\n2. **Maintain consistency**: Match naming, structure, and style exactly\n3. **Complete the stack**: Don't leave layers incomplete — the server wiring step is as important as creating the handler file\n4. **Follow the chain**: Backend service → Route handler → **Server wiring** → Frontend component → Registration → Styling\n5. **Verify integration**: Ensure all pieces connect properly; a missing `case` in a switch block causes silent 404s\n\n## Common Pitfalls\n\n- ❌ Creating a route handler file but forgetting to add it to the server's dispatch table → **silent 404 on every request**\n- ❌ Creating component before verifying API endpoint works\n- ❌ Inconsistent naming across layers (camelCase vs kebab-case)\n- ❌ Forgetting to register the panel in the frontend config\n- ❌ Missing error handling in any layer\n- ❌ Skipping styling, leading to broken UI\n- ❌ Placing a new `case` after the `default` case in a switch (unreachable code)\n- ❌ Path string mismatch between server case and client fetch URL\n\n## Variations\n\n**For simple features**: May omit service layer if route logic is trivial\n\n**For complex features**: May need additional files:\n- Database migrations/models\n- Redux actions/reducers (if using Redux)\n- Test files for each layer\n- Documentation files\n\n**For TypeScript projects**: Add `.d.ts` or `.ts` type definition files\n\n**For Express projects**: Step 5A covers the wiring; it is usually straightforward (`app.use()`).\n\n**For manual http.createServer projects**: Steps 5B–5D are critical. Always read the server entry point before assuming the pattern — projects vary between switch/case, if/else chains, and handler maps.\n"}


skill_id: unicode-safe-file-writing__v0_2505aeb1
name: unicode-safe-file-writing
path: example/skills/unicode-safe-file-writing/SKILL.md
category: tool_guide
visibility: private
lineage_origin: captured
lineage_source_task_id: v2b-task-09
lineage_created_at: 2026-03-10T20:53:40.650147
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T20:53:40.650154
last_updated: 2026-03-10T20:53:40.650155
description:
How to reliably write files containing multi-byte Unicode (emoji, special symbols) when write_file fails with 'unknown error' by falling back to run_shell with a heredoc.

lineage_change_summary:
When write_file fails with 'unknown error' on multi-byte Unicode content, recover by using run_shell with a shell heredoc to write the file, stripping or replacing problematic characters as needed.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -0,0 +1,148 @@
+---
+name: unicode-safe-file-writing
+description: How to reliably write files containing multi-byte Unicode (emoji, special symbols) when write_file fails with 'unknown error' by falling back to run_shell with a heredoc.
+---
+
+# Unicode-Safe File Writing
+
+## Problem
+
+The `write_file` tool may fail with `'unknown error'` when the file content
+contains multi-byte Unicode characters such as:
+
+- Emoji (e.g. `🚀`, `✅`, `❌`)
+- Special typographic symbols (e.g. `→`, `•`, `—`, `©`)
+- Non-ASCII characters outside the Latin-1 range
+
+This is a known encoding limitation of `write_file`.
+
+## Solution
+
+Fall back to `run_shell` and write the file using a **shell heredoc**
+(`cat > file << 'EOF' ... EOF`). The single-quoted delimiter `'EOF'`
+prevents the shell from interpreting any special characters inside the
+block.
+
+---
+
+## Step-by-Step Recovery
+
+### Step 1 — Detect the failure
+
+If `write_file` returns an error such as:
+
+```
+Error: unknown error
+```
+
+and the content contains non-ASCII characters, assume a Unicode encoding
+issue and proceed to the heredoc fallback.
+
+### Step 2 — Strip or replace problematic characters (if needed)
+
+Before embedding content in the heredoc, decide whether to:
+
+- **Keep** the characters as-is (works if the shell/terminal is UTF-8 aware)
+- **Replace** emoji/symbols with ASCII equivalents (safest, most portable)
+- **Remove** them entirely
+
+Common replacements:
+
+| Original | Replacement |
+|----------|-------------|
+| `✅`     | `[OK]`      |
+| `❌`     | `[ERROR]`   |
+| `🚀`     | `[START]`   |
+| `→`      | `->`        |
+| `•`      | `-`         |
+| `—`      | `--`        |
+
+### Step 3 — Write the file via heredoc
+
+Use `run_shell` with the following pattern:
+
+```bash
+cat > path/to/file.ext << 'EOF'
+...file content here (with Unicode stripped/replaced if needed)...
+EOF
+```
+
+**Key details:**
+- Use `'EOF'` (single-quoted) as the heredoc delimiter — this disables all
+  shell variable expansion and special character interpretation inside the block.
+- Do **not** indent the closing `EOF` (it must start at column 0).
+- For deeply nested content, choose a delimiter that cannot appear in the
+  content itself (e.g. `'HEREDOC_END'`, `'FILEEND'`).
+
+---
+
+## Example
+
+### Failing call
+
+```python
+write_file(
+    path="src/components/StatusPanel.ts",
+    content="// Status icons\nconst icons = { ok: '✅', err: '❌', run: '🚀' };\n"
+)
+# → Error: unknown error
+```
+
+### Recovery via heredoc
+
+```bash
+cat > src/components/StatusPanel.ts << 'EOF'
+// Status icons
+const icons = { ok: '[OK]', err: '[ERROR]', run: '[START]' };
+EOF
+```
+
+Or, if UTF-8 output is acceptable in the environment:
+
+```bash
+cat > src/components/StatusPanel.ts << 'EOF'
+// Status icons
+const icons = { ok: '✅', err: '❌', run: '🚀' };
+EOF
+```
+
+---
+
+## Multi-File Scenario
+
+When writing several files and one fails, write the others with `write_file`
+as normal and apply the heredoc fallback only to the failing file(s):
+
+```bash
+# Write multiple files that contain Unicode
+cat > src/utils/icons.ts << 'EOF'
+export const ICONS = {
+  success: '[OK]',
+  failure: '[FAIL]',
+  pending: '[...]',
+};
+EOF
+
+cat > src/utils/labels.ts << 'EOF'
+export const LABELS = {
+  title: 'Dashboard',
+  subtitle: 'Real-time status',
+};
+EOF
+```
+
+---
+
+## Important Notes
+
+1. **Always prefer `write_file` first** — only fall back to the heredoc
+   approach when `write_file` fails.
+2. **Verify after writing** — use `run_shell` with `cat path/to/file` or
+   `head -5 path/to/file` to confirm the content was written correctly.
+3. **Directory creation** — `write_file` auto-creates parent directories;
+   the heredoc approach does not. Pre-create directories with
+   `mkdir -p path/to/dir` if needed.
+4. **Large files** — heredocs work well for files of any practical size;
+   there is no meaningful shell limit.
+5. **Escaping backslashes** — inside a single-quoted heredoc, backslashes
+   are treated literally (no escaping needed), which is usually desirable.

lineage_content_snapshot:
{"SKILL.md": "---\nname: unicode-safe-file-writing\ndescription: How to reliably write files containing multi-byte Unicode (emoji, special symbols) when write_file fails with 'unknown error' by falling back to run_shell with a heredoc.\n---\n\n# Unicode-Safe File Writing\n\n## Problem\n\nThe `write_file` tool may fail with `'unknown error'` when the file content\ncontains multi-byte Unicode characters such as:\n\n- Emoji (e.g. `🚀`, `✅`, `❌`)\n- Special typographic symbols (e.g. `→`, `•`, `—`, `©`)\n- Non-ASCII characters outside the Latin-1 range\n\nThis is a known encoding limitation of `write_file`.\n\n## Solution\n\nFall back to `run_shell` and write the file using a **shell heredoc**\n(`cat > file << 'EOF' ... EOF`). The single-quoted delimiter `'EOF'`\nprevents the shell from interpreting any special characters inside the\nblock.\n\n---\n\n## Step-by-Step Recovery\n\n### Step 1 — Detect the failure\n\nIf `write_file` returns an error such as:\n\n```\nError: unknown error\n```\n\nand the content contains non-ASCII characters, assume a Unicode encoding\nissue and proceed to the heredoc fallback.\n\n### Step 2 — Strip or replace problematic characters (if needed)\n\nBefore embedding content in the heredoc, decide whether to:\n\n- **Keep** the characters as-is (works if the shell/terminal is UTF-8 aware)\n- **Replace** emoji/symbols with ASCII equivalents (safest, most portable)\n- **Remove** them entirely\n\nCommon replacements:\n\n| Original | Replacement |\n|----------|-------------|\n| `✅`     | `[OK]`      |\n| `❌`     | `[ERROR]`   |\n| `🚀`     | `[START]`   |\n| `→`      | `->`        |\n| `•`      | `-`         |\n| `—`      | `--`        |\n\n### Step 3 — Write the file via heredoc\n\nUse `run_shell` with the following pattern:\n\n```bash\ncat > path/to/file.ext << 'EOF'\n...file content here (with Unicode stripped/replaced if needed)...\nEOF\n```\n\n**Key details:**\n- Use `'EOF'` (single-quoted) as the heredoc delimiter — this disables all\n  shell variable expansion and special character interpretation inside the block.\n- Do **not** indent the closing `EOF` (it must start at column 0).\n- For deeply nested content, choose a delimiter that cannot appear in the\n  content itself (e.g. `'HEREDOC_END'`, `'FILEEND'`).\n\n---\n\n## Example\n\n### Failing call\n\n```python\nwrite_file(\n    path=\"src/components/StatusPanel.ts\",\n    content=\"// Status icons\\nconst icons = { ok: '✅', err: '❌', run: '🚀' };\\n\"\n)\n# → Error: unknown error\n```\n\n### Recovery via heredoc\n\n```bash\ncat > src/components/StatusPanel.ts << 'EOF'\n// Status icons\nconst icons = { ok: '[OK]', err: '[ERROR]', run: '[START]' };\nEOF\n```\n\nOr, if UTF-8 output is acceptable in the environment:\n\n```bash\ncat > src/components/StatusPanel.ts << 'EOF'\n// Status icons\nconst icons = { ok: '✅', err: '❌', run: '🚀' };\nEOF\n```\n\n---\n\n## Multi-File Scenario\n\nWhen writing several files and one fails, write the others with `write_file`\nas normal and apply the heredoc fallback only to the failing file(s):\n\n```bash\n# Write multiple files that contain Unicode\ncat > src/utils/icons.ts << 'EOF'\nexport const ICONS = {\n  success: '[OK]',\n  failure: '[FAIL]',\n  pending: '[...]',\n};\nEOF\n\ncat > src/utils/labels.ts << 'EOF'\nexport const LABELS = {\n  title: 'Dashboard',\n  subtitle: 'Real-time status',\n};\nEOF\n```\n\n---\n\n## Important Notes\n\n1. **Always prefer `write_file` first** — only fall back to the heredoc\n   approach when `write_file` fails.\n2. **Verify after writing** — use `run_shell` with `cat path/to/file` or\n   `head -5 path/to/file` to confirm the content was written correctly.\n3. **Directory creation** — `write_file` auto-creates parent directories;\n   the heredoc approach does not. Pre-create directories with\n   `mkdir -p path/to/dir` if needed.\n4. **Large files** — heredocs work well for files of any practical size;\n   there is no meaningful shell limit.\n5. **Escaping backslashes** — inside a single-quoted heredoc, backslashes\n   are treated literally (no escaping needed), which is usually desirable."}


skill_id: create-full-stack-panel-feature-enhanced-merged__v0_a0e4d4fa
name: full-stack-panel-authoring
path: example/skills/full-stack-panel-authoring/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: v2b-task-09
lineage_created_at: 2026-03-10T20:56:40.851004
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T20:56:40.851027
last_updated: 2026-03-10T20:56:40.851028
lineage_change_summary:
Merged both parent skills into a single unified full-stack panel authoring guide covering project exploration, server dispatch detection and wiring, vanilla TS Panel base class authoring with CircuitBreaker resilience, and registration — with added guidance on emoji-safe file writing.

lineage_content_snapshot:
{"SKILL.md": "---\nname: unified-full-stack-panel-authoring\ndescription: Single cohesive workflow for creating complete dashboard panel features in vanilla TypeScript — covers project-structure exploration, server dispatch pattern detection and wiring (Express or manual http.createServer), Panel base class component authoring with CircuitBreaker resilience and localStorage persistence, panel registration in index.ts, and safe file writing (including emoji workarounds).\n---\n\n# Unified Full-Stack Panel Authoring\n\nOne guide for the complete workflow: backend route → service → server wiring → Panel component with resilience → registration. Eliminates the need to consult separate skills for what is always a single cohesive task.\n\n---\n\n## When to Use\n\n- Adding a new panel type to a vanilla-TypeScript dashboard\n- Creating a feature that spans all layers: server route, service, component, registration\n- Ensuring pattern-consistency across the stack\n- Projects using a manual `http.createServer` dispatch loop **or** Express\n\n---\n\n## Quick Reference — File Checklist\n\n| # | File | Action |\n|---|------|--------|\n| 1 | `server/routes/{feature}-route.ts` | Create |\n| 2 | `src/services/{feature}.ts` | Create |\n| 3 | `src/components/{Feature}Panel.ts` | Create |\n| 4 | `src/components/index.ts` | Update — add export |\n| 5 | `server/index.ts` | Update — import + dispatch case |\n| 6 | `src/utils/circuit-breaker.ts` | Create if absent |\n| 7 | `src/utils/sparkline.ts` | Create if absent |\n\n---\n\n## Step 1 — Explore Project Structure\n\nRun these commands before writing a single line of code. Reading existing patterns prevents style drift and reveals the exact dispatch mechanism.\n\n```bash\n# Locate server entry point (critical for wiring)\nfind . -name \"index.ts\" -o -name \"index.js\" -o -name \"server.ts\" -o -name \"server.js\" \\\n  | grep -v node_modules | head -10\n\n# Find existing route handlers to use as pattern files\nfind server/routes -name \"*.ts\" -o -name \"*.js\" 2>/dev/null | head -10\n\n# Find existing service files\nfind src/services -name \"*.ts\" -o -name \"*.js\" 2>/dev/null | head -10\n\n# Find existing Panel components\nfind src/components -name \"*Panel.ts\" 2>/dev/null | head -10\n\n# Find the Panel base class\nfind src -name \"Panel.ts\" 2>/dev/null | head -5\n\n# Find component registration index\nfind src/components -name \"index.ts\" 2>/dev/null | head -3\n\n# Check for CircuitBreaker utility\nfind src/utils -name \"circuit-breaker.ts\" 2>/dev/null\n\n# Check framework (should NOT find React in vanilla TS projects)\ngrep -s '\"react\"' package.json | head -3\n```\n\n**Read 1-2 examples from each layer before generating any code:**\n\n```bash\ncat server/routes/<existing>-route.ts\ncat src/services/<existing>.ts\ncat src/components/<existing>Panel.ts\ncat src/components/index.ts\ncat server/index.ts          # most important: reveals dispatch pattern\n```\n\n---\n\n## Step 2 — Detect Server Dispatch Pattern\n\nRead `server/index.ts` (or `server/index.js`, `src/server.ts`) and classify:\n\n```bash\n# Express signals\ngrep -n \"express\\|app\\.use\\|router\\.\" server/index.ts 2>/dev/null\n\n# Manual http.createServer signals\ngrep -n \"createServer\\|switch.*url\\|switch.*pathname\\|req\\.url\\|case '/\" server/index.ts 2>/dev/null\n```\n\n| Pattern in file | Wiring method (Step 5) |\n|----------------|------------------------|\n| `app.use('/api/x', xRouter)` | **5A - Express** |\n| `switch(pathname) { case '/api/x':` | **5B - Manual switch/case** |\n| `if (url.startsWith('/api/x'))` | **5C - Manual if/else chain** |\n| `const handlers = { '/api/x': fn }` | **5D - Handler map** |\n\nNOTE: Misidentifying the dispatch pattern is the most common cause of silent 404s.\nIf unsure, read the full server entry point before proceeding.\n\n---\n\n## Step 3 — Create the Server Route Handler\n\nUse the **manual dispatch** template for `http.createServer` projects (most common in this codebase). Use the Express template only when Express is confirmed.\n\n### Manual dispatch (TypeScript) — preferred template\n\n```typescript\n// server/routes/{feature}-route.ts\nimport { IncomingMessage, ServerResponse } from 'http';\nimport { fetch{Feature}Data } from '../../src/services/{feature}';\n\nexport async function handle{Feature}Request(\n  req: IncomingMessage,\n  res: ServerResponse,\n  action?: string\n): Promise<void> {\n  try {\n    const data = await fetch{Feature}Data(action);\n    res.writeHead(200, { 'Content-Type': 'application/json' });\n    res.end(JSON.stringify({ success: true, data }));\n  } catch (err) {\n    res.writeHead(500, { 'Content-Type': 'application/json' });\n    res.end(JSON.stringify({\n      success: false,\n      error: err instanceof Error ? err.message : String(err)\n    }));\n  }\n}\n```\n\n### Express template (only when confirmed)\n\n```typescript\n// server/routes/{feature}-route.ts\nimport { Router } from 'express';\nimport { fetch{Feature}Data } from '../../src/services/{feature}';\n\nconst router = Router();\n\nrouter.get('/', async (req, res) => {\n  try {\n    const data = await fetch{Feature}Data(req.query.action as string | undefined);\n    res.json({ success: true, data });\n  } catch (err) {\n    res.status(500).json({ success: false, error: (err as Error).message });\n  }\n});\n\nexport default router;\n```\n\n---\n\n## Step 4 — Create the Service Layer\n\nThe service owns data fetching and transformation. The CircuitBreaker (Step 6) wraps the service call at the **component** level — keep the service itself simple.\n\n```typescript\n// src/services/{feature}.ts\n\nexport interface {Feature}Item {\n  id: string;\n  // define your data shape matching the API response\n}\n\n/**\n * Fetch {feature} data from the server API.\n * @param action  Optional sub-action / filter parameter.\n */\nexport async function fetch{Feature}Data(action?: string): Promise<{Feature}Item[]> {\n  const url = `/api/panels/{feature}${action ? `?action=${encodeURIComponent(action)}` : ''}`;\n  const res = await fetch(url, {\n    headers: { Authorization: `Bearer ${localStorage.getItem('token') ?? ''}` }\n  });\n  if (!res.ok) throw new Error(`HTTP ${res.status}`);\n  const { data } = await res.json() as { data: {Feature}Item[] };\n  return data;\n}\n```\n\n---\n\n## Step 5 — Wire the Route Handler into the Server\n\n**This step is the most frequently missed and causes features to silently return 404.**\n\nAfter creating the route file, open `server/index.ts` and add **both** an import and a dispatch entry.\n\n### 5A - Express\n\n```typescript\n// server/index.ts  -- excerpt\nimport {feature}Router from './routes/{feature}-route';\n\n// Add alongside other app.use() calls:\napp.use('/api/panels/{feature}', {feature}Router);\n```\n\n### 5B - Manual switch/case (most common pattern)\n\n```typescript\n// server/index.ts  -- excerpt showing the additions\n\n// 1. Add import near the top with other route imports\nimport { handle{Feature}Request } from './routes/{feature}-route';\n\n// 2. Inside the switch / if-else dispatch block, add a new case\n// Find the block that looks like:\n//   switch (pathname) {\n//     case '/api/panels/health': ...\n//     case '/api/panels/email':  ...\n// Add:\n    case '/api/panels/{feature}':\n      await handle{Feature}Request(req, res, action);\n      break;\n```\n\n### 5C - Manual if/else chain\n\n```typescript\n// server/index.ts  -- excerpt\nimport { handle{Feature}Request } from './routes/{feature}-route';\n\n// Add a new else-if branch:\n} else if (url.startsWith('/api/panels/{feature}')) {\n  await handle{Feature}Request(req, res, action);\n}\n```\n\n### 5D - Handler map\n\n```typescript\n// server/index.ts  -- excerpt\nimport { handle{Feature}Request } from './routes/{feature}-route';\n\nconst handlers: Record<string, HandlerFn> = {\n  // ... existing entries ...\n  '/api/panels/{feature}': handle{Feature}Request,\n};\n```\n\n**Verification:** After wiring, start the server and `curl /api/panels/{feature}` — you should receive JSON, not a 404.\n\n---\n\n## Step 6 — Create the Panel Component\n\n### Architecture overview\n\n```\nPanel (base class)\n+-- element: HTMLElement (.panel)\n    +-- header (.panel-header)\n    |   +-- headerLeft (.panel-header-left)\n    |   |   +-- title (.panel-title)\n    |   +-- countEl (.panel-count)  [optional]\n    +-- content (.panel-content)\n```\n\n### Resilience layers\n\n| Layer | When to use |\n|---|---|\n| `isFetching` guard | Always -- prevents duplicate concurrent requests |\n| `fetchWithRetry()` | When you own the raw fetch and want per-request retry |\n| `CircuitBreaker.execute()` | When a service may fail repeatedly; serves stale cache during cooldown |\n| `saveState()` / `loadState()` | When collapsed/size state should survive page reloads |\n\n### Complete Panel component template\n\n```typescript\n// src/components/{Feature}Panel.ts\nimport { Panel } from './Panel';\nimport { createCircuitBreaker } from '../utils/circuit-breaker';\nimport { fetch{Feature}Data, {Feature}Item } from '../services/{feature}';\n\n// Module-level breaker -- persists cache and failure state between re-renders\nconst breaker = createCircuitBreaker<{Feature}Item[]>({\n  name: '{Feature}',\n  cacheTtlMs: 60_000,      // serve cache for 1 min\n  cooldownMs: 5 * 60_000,  // open circuit for 5 min after maxFailures\n  maxFailures: 2,\n});\n\nexport class {Feature}Panel extends Panel {\n  private refreshTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor() {\n    super({ id: '{feature}', title: '{Feature Display Name}', showCount: true });\n    this.loadState();                                  // restore collapsed/size\n    this.fetchData();\n    this.refreshTimer = setInterval(() => this.fetchData(), 60_000);\n  }\n\n  private async fetchData(): Promise<void> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    try {\n      // One-liner: wraps existing service call with caching + circuit-break\n      const items = await breaker.execute(() => fetch{Feature}Data(), []);\n      this.setCount(items.length);\n      this.render(items);\n    } catch (err) {\n      this.showError(\n        err instanceof Error ? err.message : 'Failed to load',\n        () => {\n          this.retryAttempts = 0;\n          this.retryDelay = 1000;\n          this.fetchData();\n        }\n      );\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private render(items: {Feature}Item[]): void {\n    if (items.length === 0) {\n      this.setContent('<div class=\"panel-empty\">No items found.</div>');\n      return;\n    }\n\n    const container = document.createElement('div');\n    container.className = '{feature}-panel';\n\n    for (const item of items) {\n      const row = document.createElement('div');\n      row.className = '{feature}-panel__row';\n      // Build DOM from item fields -- no JSX, no innerHTML with untrusted data:\n      const label = document.createElement('span');\n      label.className = '{feature}-panel__label';\n      label.textContent = item.id;          // replace with real fields\n      row.appendChild(label);\n      container.appendChild(row);\n    }\n\n    this.content.innerHTML = '';\n    this.content.appendChild(container);\n    this.saveState();\n  }\n\n  destroy(): void {\n    if (this.refreshTimer !== null) clearInterval(this.refreshTimer);\n    super.destroy();\n  }\n}\n```\n\n**Key rules for vanilla TS panels (no React):**\n- Extend `Panel` base class; use `setContent()` / `showError()` / `setCount()` / `showLoading()` instead of React state.\n- Build DOM via `document.createElement` -- not JSX, not `innerHTML` with untrusted strings.\n- Lifecycle: `constructor` -> `fetchData()` -> `render()` -> `destroy()`. No `useEffect`.\n- File extension is `.ts`, not `.jsx`/`.tsx`.\n\n---\n\n## Step 7 — Register the Component in index.ts\n\nExport the new panel from the components barrel file so the dashboard can discover it.\n\n```typescript\n// src/components/index.ts  -- add alongside existing exports\nexport { {Feature}Panel } from './{Feature}Panel';\n```\n\nThen instantiate and mount in the dashboard bootstrap (follow the existing pattern):\n\n```typescript\n// src/index.ts or src/dashboard.ts  -- excerpt\nimport { {Feature}Panel } from './components';\n\nconst {feature}Panel = new {Feature}Panel();\ndocument.getElementById('{feature}-slot')?.appendChild({feature}Panel.getElement());\n```\n\n---\n\n## Step 8 — Add Utility Files (if absent)\n\nOnly create these if `find src/utils -name \"circuit-breaker.ts\"` returns nothing.\n\n### `src/utils/circuit-breaker.ts`\n\n```typescript\ninterface CircuitState { failures: number; cooldownUntil: number; }\ninterface CacheEntry<T> { data: T; timestamp: number; }\n\nexport interface CircuitBreakerOptions {\n  name: string;\n  maxFailures?: number;  // default 2\n  cooldownMs?: number;   // default 5 min\n  cacheTtlMs?: number;   // default 10 min\n}\n\nexport class CircuitBreaker<T> {\n  private state: CircuitState = { failures: 0, cooldownUntil: 0 };\n  private cache: CacheEntry<T> | null = null;\n  private readonly name: string;\n  private readonly maxFailures: number;\n  private readonly cooldownMs: number;\n  private readonly cacheTtlMs: number;\n\n  constructor(options: CircuitBreakerOptions) {\n    this.name        = options.name;\n    this.maxFailures = options.maxFailures ?? 2;\n    this.cooldownMs  = options.cooldownMs  ?? 5 * 60 * 1000;\n    this.cacheTtlMs  = options.cacheTtlMs  ?? 10 * 60 * 1000;\n  }\n\n  isOnCooldown(): boolean {\n    if (Date.now() < this.state.cooldownUntil) return true;\n    if (this.state.cooldownUntil > 0) this.state = { failures: 0, cooldownUntil: 0 };\n    return false;\n  }\n\n  getCached(): T | null {\n    if (this.cache && Date.now() - this.cache.timestamp < this.cacheTtlMs) return this.cache.data;\n    return null;\n  }\n\n  recordSuccess(data: T): void {\n    this.state = { failures: 0, cooldownUntil: 0 };\n    this.cache = { data, timestamp: Date.now() };\n  }\n\n  recordFailure(): void {\n    this.state.failures++;\n    if (this.state.failures >= this.maxFailures) {\n      this.state.cooldownUntil = Date.now() + this.cooldownMs;\n      console.warn(`[${this.name}] Circuit open -- cooldown ${this.cooldownMs / 1000}s`);\n    }\n  }\n\n  async execute<R extends T>(fn: () => Promise<R>, defaultValue: R): Promise<R> {\n    if (this.isOnCooldown()) return (this.getCached() as R) ?? defaultValue;\n    const cached = this.getCached();\n    if (cached !== null) return cached as R;\n    try {\n      const result = await fn();\n      this.recordSuccess(result);\n      return result;\n    } catch (e) {\n      console.error(`[${this.name}] Request failed:`, e);\n      this.recordFailure();\n      return defaultValue;\n    }\n  }\n}\n\nexport function createCircuitBreaker<T>(options: CircuitBreakerOptions): CircuitBreaker<T> {\n  return new CircuitBreaker<T>(options);\n}\n```\n\n### `src/utils/sparkline.ts` (optional — for numeric trend data)\n\n```typescript\n/**\n * Returns an inline SVG sparkline string, or '' if data is too short.\n * Embed directly in HTML template strings.\n */\nexport function miniSparkline(\n  data: number[] | undefined,\n  change: number | null,\n  w = 50,\n  h = 16\n): string {\n  if (!data || data.length < 2) return '';\n  const min   = Math.min(...data);\n  const max   = Math.max(...data);\n  const range = max - min || 1;\n  const color = change != null && change >= 0 ? 'var(--green)' : 'var(--red)';\n  const points = data.map((v, i) => {\n    const x = (i / (data.length - 1)) * w;\n    const y = h - ((v - min) / range) * (h - 2) - 1;\n    return `${x.toFixed(1)},${y.toFixed(1)}`;\n  }).join(' ');\n  return `<svg width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\">` +\n    `<polyline points=\"${points}\" fill=\"none\" stroke=\"${color}\" ` +\n    `stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`;\n}\n```\n\n---\n\n## Step 9 — Panel Base Class (if absent)\n\nOnly create `src/components/Panel.ts` if no existing base class is found.\n\n```typescript\n// src/components/Panel.ts\nexport interface PanelOptions {\n  id: string;\n  title: string;\n  showCount?: boolean;\n  className?: string;\n}\n\nexport class Panel {\n  protected element: HTMLElement;\n  protected content: HTMLElement;\n  protected header: HTMLElement;\n  protected countEl: HTMLElement | null = null;\n  protected panelId: string;\n  private _fetching = false;\n\n  // Retry state (used by fetchWithRetry)\n  protected retryAttempts = 0;\n  protected retryDelay = 1000;\n  private maxRetries = 3;\n\n  constructor(options: PanelOptions) {\n    this.panelId = options.id;\n    this.element = document.createElement('div');\n    this.element.className = `panel ${options.className ?? ''}`;\n    this.element.dataset.panel = options.id;\n\n    this.header = document.createElement('div');\n    this.header.className = 'panel-header';\n\n    const headerLeft = document.createElement('div');\n    headerLeft.className = 'panel-header-left';\n    const title = document.createElement('span');\n    title.className = 'panel-title';\n    title.textContent = options.title;\n    headerLeft.appendChild(title);\n    this.header.appendChild(headerLeft);\n\n    if (options.showCount) {\n      this.countEl = document.createElement('span');\n      this.countEl.className = 'panel-count';\n      this.countEl.textContent = '0';\n      this.header.appendChild(this.countEl);\n    }\n\n    this.content = document.createElement('div');\n    this.content.className = 'panel-content';\n    this.content.id = `${options.id}Content`;\n\n    this.element.appendChild(this.header);\n    this.element.appendChild(this.content);\n    this.showLoading();\n  }\n\n  public getElement(): HTMLElement { return this.element; }\n\n  public showLoading(message = 'Loading...'): void {\n    this.content.innerHTML = `\n      <div class=\"panel-loading\">\n        <div class=\"panel-loading-spinner\"></div>\n        <div class=\"panel-loading-text\">${message}</div>\n      </div>`;\n  }\n\n  public showError(message = 'Failed to load', onRetry?: () => void): void {\n    this.content.innerHTML = `\n      <div class=\"panel-error-state\">\n        <div class=\"panel-error-msg\">${message}</div>\n        ${onRetry ? '<button class=\"panel-retry-btn\" data-panel-retry>Retry</button>' : ''}\n      </div>`;\n    if (onRetry) {\n      this.content.querySelector('[data-panel-retry]')?.addEventListener('click', onRetry);\n    }\n  }\n\n  public setContent(html: string): void { this.content.innerHTML = html; }\n\n  public setCount(count: number): void {\n    if (this.countEl) this.countEl.textContent = count.toString();\n  }\n\n  public show(): void { this.element.classList.remove('hidden'); }\n  public hide(): void { this.element.classList.add('hidden'); }\n  public destroy(): void { this.element.remove(); }\n\n  protected setFetching(v: boolean): void { this._fetching = v; }\n  protected get isFetching(): boolean { return this._fetching; }\n\n  protected async fetchWithRetry(url: string): Promise<unknown> {\n    while (this.retryAttempts < this.maxRetries) {\n      try {\n        const response = await fetch(url);\n        if (!response.ok) throw new Error(`HTTP ${response.status}`);\n        return await response.json();\n      } catch (error) {\n        this.retryAttempts++;\n        if (this.retryAttempts >= this.maxRetries) {\n          throw new Error(`Failed after ${this.maxRetries} attempts: ${(error as Error).message}`);\n        }\n        await new Promise(resolve => setTimeout(resolve, this.retryDelay));\n        this.retryDelay *= 2;\n      }\n    }\n  }\n\n  public saveState(): void {\n    localStorage.setItem(`panelState_${this.panelId}`, JSON.stringify({\n      isExpanded: !this.element.classList.contains('collapsed'),\n      width: this.element.style.width,\n      height: this.element.style.height,\n    }));\n  }\n\n  public loadState(): void {\n    const raw = localStorage.getItem(`panelState_${this.panelId}`);\n    if (!raw) return;\n    const { isExpanded, width, height } = JSON.parse(raw) as {\n      isExpanded: boolean; width: string; height: string;\n    };\n    if (!isExpanded) this.element.classList.add('collapsed');\n    if (width)  this.element.style.width  = width;\n    if (height) this.element.style.height = height;\n  }\n}\n```\n\n---\n\n## Step 10 — Safe File Writing\n\n**Known tool issue:** `write_file` (and some shell tools) fail silently or with \"unknown error\" when file content contains multi-byte Unicode characters such as emoji (e.g., document, chart, checkmark emoji).\n\n### Rules for safe content\n\n1. **Never use emoji in generated source code.** Replace with plain-text equivalents:\n   - Document emoji -> `[doc]` or `(file)`\n   - Chart emoji -> `[chart]`\n   - Checkmark emoji -> `[ok]` or `(done)`\n   - Warning emoji -> `[warn]` or `WARNING:`\n\n2. **Use ASCII substitutes in comments** instead of Unicode box-drawing characters.\n\n3. **Test-write suspect content** to a throwaway file first if unsure:\n   ```bash\n   echo \"test content\" > /tmp/write_test.txt && cat /tmp/write_test.txt\n   ```\n\n4. **Recovery pattern** -- if a write_file call fails:\n   - Strip all non-ASCII characters from the content.\n   - Retry the write.\n   - If still failing, split the file into smaller chunks and concatenate with `cat`.\n\n---\n\n## Validation Checklist\n\nBefore considering the feature complete, verify each layer:\n\n```\nServer route\n  [ ] File created at server/routes/{feature}-route.ts\n  [ ] Exports handle{Feature}Request function\n  [ ] Returns { success: true, data } on success\n  [ ] Returns { success: false, error } on failure with correct HTTP status\n\nService\n  [ ] File created at src/services/{feature}.ts\n  [ ] Exports fetch{Feature}Data function\n  [ ] Throws on non-ok HTTP status (so CircuitBreaker can record failures)\n\nServer wiring\n  [ ] Import added to server/index.ts\n  [ ] Dispatch case/route added (matching the detected pattern)\n  [ ] curl /api/panels/{feature} returns JSON (not 404)\n\nPanel component\n  [ ] Extends Panel base class\n  [ ] Uses isFetching guard\n  [ ] CircuitBreaker wraps the service call\n  [ ] showError() called with retry callback on failure\n  [ ] destroy() clears interval and calls super.destroy()\n\nRegistration\n  [ ] Export added to src/components/index.ts\n  [ ] Panel instantiated and mounted in dashboard bootstrap\n\nFile safety\n  [ ] No emoji in any generated source file\n  [ ] All write_file calls succeeded (check file size > 0)\n```\n\n---\n\n## Common Mistakes\n\n| Mistake | Symptom | Fix |\n|---------|---------|-----|\n| Skipping Step 5 (server wiring) | Feature returns 404 | Add import + dispatch entry to server/index.ts |\n| Wrong dispatch pattern | 404 or runtime error | Re-read server/index.ts; use grep from Step 2 |\n| Emoji in source files | write_file silent failure | Replace all emoji with ASCII equivalents |\n| React template in vanilla TS project | Compile errors | Confirm no React in package.json; use Panel base class template |\n| Creating Panel.ts when one exists | Overwrites base class | Check with `find src -name \"Panel.ts\"` first |\n| Not calling `super.destroy()` | Memory leak | Always chain super.destroy() in subclass destroy() |\n| CircuitBreaker declared inside class | Cache lost between fetches | Declare `const breaker = ...` at module level outside the class |\n*** End Files\n\n<EVOLUTION_COMPLETE>\n"}


skill_id: safe-css-append-enhanced__v0_56e7280e
name: css-append-selector-verify
path: example/skills/css-append-selector-verify/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: v2c-11
lineage_created_at: 2026-03-10T21:19:37.324967
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T21:19:37.324984
last_updated: 2026-03-10T21:19:37.324985
description:
Safely append CSS styles from one file to another by identifying missing rules, adapting CSS variables, and verifying changes

lineage_change_summary:
Enhanced safe-css-append skill with explicit compound-selector partial-overlap detection, documentation guidance, and a mandatory overlap-comment convention to prevent future audit confusion.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,11 +1,11 @@
 ---
-name: safe-css-append
-description: Safely append CSS styles from one file to another by identifying missing rules, adapting CSS variables, and verifying changes
----
-
-# Safe CSS Append Workflow
-
-This skill provides a systematic approach for safely appending CSS styles from a source file to a destination file. The workflow ensures no content is overwritten, CSS variables are properly adapted, and all changes are verified.
+name: safe-css-append-overlap-aware
+description: Safely append CSS styles from one file to another, with explicit handling of compound-selector partial overlaps, variable adaptation, and thorough verification
+---
+
+# Safe CSS Append — Overlap-Aware Workflow
+
+This skill provides a systematic approach for safely appending CSS styles from a source file to a destination file. It extends the baseline append workflow with **explicit handling for the compound-selector partial-overlap edge case**, ensuring auditors always understand when a new standalone rule intentionally shares a selector with an existing multi-selector group.
 
 ## When to Use This Skill
 
@@ -13,18 +13,38 @@
 - Copying component styles while preserving existing rules
 - Adding new CSS rules without duplicating or conflicting with existing styles
 - Adapting styles to use a different set of CSS variables
+- Any situation where a selector may already appear inside a comma-separated group (`.a, .b, .c { … }`)
+
+---
+
+## Core Concept: Compound-Selector Partial Overlap
+
+A **compound-selector partial overlap** occurs when:
+
+1. You want to append a rule for selector `.foo`.
+2. The destination file already contains a multi-selector block such as `.foo, .bar, .baz { … }` that includes `.foo`.
+3. Your new rule for `.foo` adds **different** properties — it is not a duplicate of the shared block.
+
+This situation is **safe and non-redundant** because:
+- The existing compound rule applies shared properties to all listed selectors.
+- Your new standalone rule adds *additional* properties *only* to `.foo`.
+- CSS specificity is not altered (same selector, different declarations).
+
+However, this pattern looks suspicious during future audits if left unexplained. The skill therefore requires a **mandatory overlap comment** wherever this occurs.
+
+---
 
 ## Workflow Steps
 
 ### 1. Read and Analyze Source CSS
 
-First, examine the source CSS file to identify the selectors and rules you want to transfer:
+Examine the source CSS file to identify the selectors and rules you want to transfer:
 
 ```bash
 # Read the source CSS file
 cat /path/to/source/styles.css
 
-# Identify specific selectors if needed
+# Identify specific selectors
 grep "selector-pattern" /path/to/source/styles.css
 ```
 
@@ -33,6 +53,8 @@
 - CSS variables used (e.g., `--primary-color`, `--spacing-unit`)
 - Complete rule sets for each selector
 
+---
+
 ### 2. Read and Check Destination CSS
 
 Examine the destination file to understand existing rules and avoid duplication:
@@ -41,7 +63,7 @@
 # Read the destination CSS file
 cat /path/to/destination/styles.css
 
-# Check if specific selectors already exist
+# Check if a selector already exists as a standalone rule
 grep "selector-name" /path/to/destination/styles.css
 
 # Check for existing CSS variables
@@ -49,11 +71,71 @@
 ```
 
 **Identify:**
-- Existing selectors that might conflict
-- CSS variable naming conventions in the destination
-- Any existing rules for the target selectors
-
-### 3. Identify Missing Styles and Required Adaptations
+- Selectors that exist **only** as standalone rules → standard duplication check applies.
+- Selectors that appear **inside a compound group** → proceed to Step 3a.
+- CSS variable naming conventions in the destination.
+
+---
+
+### 3. Classify Each Selector — Standard or Partial-Overlap
+
+For **every** target selector, run:
+
+```bash
+# Detect compound groups containing the selector
+grep -n "selector-name" /path/to/destination/styles.css
+```
+
+Examine each match. Classify the result:
+
+| Match type | Example | Classification |
+|---|---|---|
+| Standalone rule | `.foo { … }` | **Standard** — check for duplicate properties |
+| Compound group member | `.foo, .bar { … }` | **Partial overlap** — safe but needs overlap comment |
+| No match | *(no output)* | **New selector** — append freely |
+
+Produce a classification table before writing any CSS, e.g.:
+
+```
+Selector         | Status           | Notes
+-----------------+------------------+------------------------------------
+.button-primary  | New              | Not found in destination
+.schedule-item   | Partial overlap  | Appears in ".schedule-item, .event-card { color: … }"
+.card-header     | Standalone dup   | Already has padding; only add missing props
+```
+
+---
+
+### 3a. Handling Partial-Overlap Selectors
+
+When a selector is classified as **partial overlap**:
+
+1. **Confirm the new properties are distinct** from those already in the compound block.  
+   If every property in your new rule already appears in the compound block → it **is** a duplicate; do not append.
+
+2. **Write the standalone rule** with an **overlap comment** directly above it.
+
+**Overlap comment format:**
+
+```css
+/* OVERLAP NOTE: .your-selector also appears in the compound group
+   "<full compound selector>" (line N of this file).
+   This rule intentionally adds properties not covered by that group. */
+.your-selector {
+    new-property: value;
+    another-property: var(--some-variable);
+}
+```
+
+The comment must include:
+- The word `OVERLAP NOTE` (makes it grep-able: `grep "OVERLAP NOTE" styles.css`)
+- The full text of the existing compound selector
+- The line number of the existing compound rule
+- A statement that the overlap is intentional
+
+---
+
+### 4. Identify Missing Styles and Required Variable Adaptations
 
 Compare source and destination to determine:
 - Which selectors are completely missing
@@ -62,120 +144,184 @@
 
 **Example variable mapping:**
 ```
-Source: --panel-bg → Destination: --background-secondary
-Source: --text-primary → Destination: --color-text-primary
-Source: --border-radius → Keep as-is (if already defined in destination)
-```
-
-### 4. Prepare Adapted CSS Content
-
-Create the CSS content to append, adapting variables as needed:
+Source: --panel-bg        → Destination: --background-secondary
+Source: --text-primary    → Destination: --color-text-primary
+Source: --border-radius   → Keep as-is (already defined in destination)
+```
+
+---
+
+### 5. Prepare Adapted CSS Content
+
+Assemble the full CSS block to append, respecting classifications from Step 3:
 
 ```css
-/* Example adapted CSS */
-.new-selector {
-    background-color: var(--background-secondary);  /* was --panel-bg */
-    color: var(--color-text-primary);               /* was --text-primary */
-    border-radius: var(--border-radius);            /* unchanged */
-    padding: 8px 16px;
-}
-
-.existing-selector {
-    /* Only add missing properties, not the entire rule */
-    new-property: value;
-}
-```
-
-### 5. Safely Append to Destination
-
-Use append operations (never overwrite) to add the new styles:
-
-```bash
-# Append new CSS rules
+/* ==========================================================
+   Styles appended from <source> — <date or ticket reference>
+   ========================================================== */
+
+/* New selector — no prior occurrence */
+.button-primary {
+    background-color: var(--primary-color);   /* was --btn-color */
+    padding: 10px 20px;
+    border-radius: 4px;
+    font-weight: 600;
+}
+
+/* OVERLAP NOTE: .schedule-item also appears in the compound group
+   ".schedule-item, .event-card { color: var(--text-primary); }" (line 47).
+   This rule intentionally adds layout properties not covered by that group. */
+.schedule-item {
+    display: flex;
+    gap: var(--spacing-sm);
+    align-items: center;
+}
+
+/* Partial addition to existing standalone rule — only missing props */
+.card-header {
+    font-size: var(--font-size-lg);   /* existing rule lacked font-size */
+}
+```
+
+---
+
+### 6. Safely Append to Destination
+
+Use append operations (**never** overwrite) to add the new styles:
+
+```bash
 cat >> /path/to/destination/styles.css << 'EOF'
 
-/* Added styles from source project */
-.selector-name {
-    property: value;
-    variable-property: var(--adapted-variable);
+/* === Appended styles from <source> === */
+
+/* OVERLAP NOTE: .schedule-item also appears in the compound group ... */
+.schedule-item {
+    display: flex;
+    gap: var(--spacing-sm);
 }
 EOF
 ```
 
-**Best practices:**
-- Add a comment indicating the source or purpose
-- Include a blank line before the new content for readability
-- Use `>>` (append) never `>` (overwrite)
-
-### 6. Verify the Changes
-
-Confirm that the styles were added correctly:
-
-```bash
-# View the last N lines to see appended content
-tail -n 20 /path/to/destination/styles.css
-
-# Verify specific selectors were added
-grep "new-selector-name" /path/to/destination/styles.css
-
-# Check the complete file if needed
-cat /path/to/destination/styles.css | tail -n 50
-```
+**Rules:**
+- Always use `>>` — never `>`.
+- Prepend a blank line for readability.
+- Keep overlap comments inline with the rule they describe.
+
+---
+
+### 7. Verify the Changes
+
+```bash
+# Confirm line count increased
+wc -l /path/to/destination/styles.css
+
+# View appended content
+tail -n 40 /path/to/destination/styles.css
+
+# Verify every target selector was added
+grep "button-primary\|schedule-item\|card-header" /path/to/destination/styles.css
+
+# Audit all overlap notes in the file
+grep -n "OVERLAP NOTE" /path/to/destination/styles.css
+```
+
+Confirm:
+- Line count increased by the expected amount.
+- All target selectors are present.
+- Every partial-overlap rule has a corresponding `OVERLAP NOTE` comment.
+- No unintended lines were removed (use `wc -l` before and after).
+
+---
 
 ## Complete Example
 
-Here's a full example workflow:
-
-```bash
-# 1. Read source CSS and identify target
-cat project-a/components.css | grep -A 10 ".button-primary"
-
-# 2. Check destination for conflicts
-grep ".button-primary" project-b/main.css
-grep "^[[:space:]]*--btn-" project-b/main.css
-
-# 3. Identify that .button-primary is missing, and variables need mapping:
-#    --btn-color → --primary-color (exists in destination)
-#    --btn-padding → create inline value
-
-# 4. Append adapted styles
-cat >> project-b/main.css << 'EOF'
-
-/* Button styles from project-a */
+```bash
+# 1. Read source and list target selectors
+cat project-a/components.css | grep -E "^\." | sort -u
+
+# 2. Check destination for each selector
+for sel in ".button-primary" ".schedule-item" ".card-header"; do
+    echo "=== $sel ==="; grep -n "$sel" project-b/main.css
+done
+
+# 3. Classify results:
+#    .button-primary  → Not found → New
+#    .schedule-item   → Found in ".schedule-item, .event-card { … }" line 47 → Partial overlap
+#    .card-header     → Found as standalone → Add only missing props
+
+# 4. Note compound line for overlap comment
+COMPOUND_LINE=47
+COMPOUND_SEL=".schedule-item, .event-card { color: var(--text-primary); }"
+
+# 5. Append
+wc -l project-b/main.css   # baseline: 93 lines
+
+cat >> project-b/main.css << EOF
+
+/* === Button & Schedule styles from project-a === */
+
+/* New selector */
 .button-primary {
     background-color: var(--primary-color);
     padding: 10px 20px;
     border-radius: 4px;
     font-weight: 600;
 }
+
+/* OVERLAP NOTE: .schedule-item also appears in the compound group
+   "$COMPOUND_SEL" (line $COMPOUND_LINE of this file).
+   This rule intentionally adds layout properties not covered by that group. */
+.schedule-item {
+    display: flex;
+    gap: var(--spacing-sm);
+    align-items: center;
+}
+
+/* Partial addition — existing .card-header lacked font-size */
+.card-header {
+    font-size: var(--font-size-lg);
+}
 EOF
 
-# 5. Verify
-tail -n 10 project-b/main.css
-grep ".button-primary" project-b/main.css
-```
+# 6. Verify
+wc -l project-b/main.css   # should be 93 + appended lines
+tail -n 30 project-b/main.css
+grep -n "OVERLAP NOTE" project-b/main.css
+```
+
+---
 
 ## Safety Checklist
 
 - [ ] Source file read and analyzed
 - [ ] Destination file checked for existing rules
-- [ ] Missing styles identified
+- [ ] Every target selector classified (New / Partial overlap / Standalone dup)
+- [ ] Compound-group matches recorded with their line numbers
+- [ ] Partial-overlap selectors confirmed to add distinct properties
+- [ ] Each partial-overlap rule has an `OVERLAP NOTE` comment
 - [ ] CSS variables mapped correctly
-- [ ] Append operation used (not overwrite)
-- [ ] Changes verified with tail/grep
-- [ ] No duplicate selectors created
+- [ ] Append operation used (`>>`), never overwrite (`>`)
+- [ ] `wc -l` baseline taken before appending
+- [ ] Changes verified with `tail`, `grep`, and `wc -l`
+- [ ] No duplicate selectors or duplicate properties created
 - [ ] All variable references exist in destination
 
+---
+
 ## Common Pitfalls
 
-1. **Overwriting instead of appending**: Always use `>>`, never `>`
-2. **Variable mismatch**: Ensure all CSS variables exist in the destination or are properly defined
-3. **Duplicate selectors**: Check for existing rules before adding complete selector blocks
-4. **Missing verification**: Always verify changes after appending
+1. **Overwriting instead of appending**: Always use `>>`, never `>`.
+2. **Mistaking a compound-group appearance for a full duplicate**: `.foo, .bar { … }` is NOT the same as a standalone `.foo { … }`. Check properties carefully.
+3. **Omitting the overlap comment**: Future developers (or automated linters) may flag the duplicate-looking selector as an error. The `OVERLAP NOTE` prevents false positives.
+4. **Variable mismatch**: Ensure all CSS variables exist in the destination or are properly defined.
+5. **Skipping verification**: Always verify with `wc -l` and `grep "OVERLAP NOTE"` after appending.
+
+---
 
 ## Tips
 
-- Use `wc -l` before and after to confirm lines were added
-- Keep a mapping document for complex variable transformations
-- Consider using a temporary file to preview changes before appending
-- Group related selectors together when appending multiple rules+- Run `grep -n "OVERLAP NOTE" styles.css` at any time to get a quick audit map of all intentional overlaps.
+- For large migrations, produce the classification table (Step 3) as a checklist comment at the top of the appended block so reviewers can cross-reference.
+- Use `wc -l` before **and** after every append to catch accidental truncation.
+- When multiple selectors share the same compound group, a single `OVERLAP NOTE` block may list all of them.
+- Consider storing the variable mapping as a comment in the appended block for traceability.

lineage_content_snapshot:
{"SKILL.md": "---\nname: safe-css-append-overlap-aware\ndescription: Safely append CSS styles from one file to another, with explicit handling of compound-selector partial overlaps, variable adaptation, and thorough verification\n---\n\n# Safe CSS Append — Overlap-Aware Workflow\n\nThis skill provides a systematic approach for safely appending CSS styles from a source file to a destination file. It extends the baseline append workflow with **explicit handling for the compound-selector partial-overlap edge case**, ensuring auditors always understand when a new standalone rule intentionally shares a selector with an existing multi-selector group.\n\n## When to Use This Skill\n\n- Migrating styles between projects\n- Copying component styles while preserving existing rules\n- Adding new CSS rules without duplicating or conflicting with existing styles\n- Adapting styles to use a different set of CSS variables\n- Any situation where a selector may already appear inside a comma-separated group (`.a, .b, .c { … }`)\n\n---\n\n## Core Concept: Compound-Selector Partial Overlap\n\nA **compound-selector partial overlap** occurs when:\n\n1. You want to append a rule for selector `.foo`.\n2. The destination file already contains a multi-selector block such as `.foo, .bar, .baz { … }` that includes `.foo`.\n3. Your new rule for `.foo` adds **different** properties — it is not a duplicate of the shared block.\n\nThis situation is **safe and non-redundant** because:\n- The existing compound rule applies shared properties to all listed selectors.\n- Your new standalone rule adds *additional* properties *only* to `.foo`.\n- CSS specificity is not altered (same selector, different declarations).\n\nHowever, this pattern looks suspicious during future audits if left unexplained. The skill therefore requires a **mandatory overlap comment** wherever this occurs.\n\n---\n\n## Workflow Steps\n\n### 1. Read and Analyze Source CSS\n\nExamine the source CSS file to identify the selectors and rules you want to transfer:\n\n```bash\n# Read the source CSS file\ncat /path/to/source/styles.css\n\n# Identify specific selectors\ngrep \"selector-pattern\" /path/to/source/styles.css\n```\n\n**Document:**\n- Target selectors (e.g., `.panel-tab`, `.header-nav`)\n- CSS variables used (e.g., `--primary-color`, `--spacing-unit`)\n- Complete rule sets for each selector\n\n---\n\n### 2. Read and Check Destination CSS\n\nExamine the destination file to understand existing rules and avoid duplication:\n\n```bash\n# Read the destination CSS file\ncat /path/to/destination/styles.css\n\n# Check if a selector already exists as a standalone rule\ngrep \"selector-name\" /path/to/destination/styles.css\n\n# Check for existing CSS variables\ngrep \"^[[:space:]]*--\" /path/to/destination/styles.css | head -20\n```\n\n**Identify:**\n- Selectors that exist **only** as standalone rules → standard duplication check applies.\n- Selectors that appear **inside a compound group** → proceed to Step 3a.\n- CSS variable naming conventions in the destination.\n\n---\n\n### 3. Classify Each Selector — Standard or Partial-Overlap\n\nFor **every** target selector, run:\n\n```bash\n# Detect compound groups containing the selector\ngrep -n \"selector-name\" /path/to/destination/styles.css\n```\n\nExamine each match. Classify the result:\n\n| Match type | Example | Classification |\n|---|---|---|\n| Standalone rule | `.foo { … }` | **Standard** — check for duplicate properties |\n| Compound group member | `.foo, .bar { … }` | **Partial overlap** — safe but needs overlap comment |\n| No match | *(no output)* | **New selector** — append freely |\n\nProduce a classification table before writing any CSS, e.g.:\n\n```\nSelector         | Status           | Notes\n-----------------+------------------+------------------------------------\n.button-primary  | New              | Not found in destination\n.schedule-item   | Partial overlap  | Appears in \".schedule-item, .event-card { color: … }\"\n.card-header     | Standalone dup   | Already has padding; only add missing props\n```\n\n---\n\n### 3a. Handling Partial-Overlap Selectors\n\nWhen a selector is classified as **partial overlap**:\n\n1. **Confirm the new properties are distinct** from those already in the compound block.  \n   If every property in your new rule already appears in the compound block → it **is** a duplicate; do not append.\n\n2. **Write the standalone rule** with an **overlap comment** directly above it.\n\n**Overlap comment format:**\n\n```css\n/* OVERLAP NOTE: .your-selector also appears in the compound group\n   \"<full compound selector>\" (line N of this file).\n   This rule intentionally adds properties not covered by that group. */\n.your-selector {\n    new-property: value;\n    another-property: var(--some-variable);\n}\n```\n\nThe comment must include:\n- The word `OVERLAP NOTE` (makes it grep-able: `grep \"OVERLAP NOTE\" styles.css`)\n- The full text of the existing compound selector\n- The line number of the existing compound rule\n- A statement that the overlap is intentional\n\n---\n\n### 4. Identify Missing Styles and Required Variable Adaptations\n\nCompare source and destination to determine:\n- Which selectors are completely missing\n- Which properties are missing from existing selectors\n- How CSS variables need to be mapped (source → destination)\n\n**Example variable mapping:**\n```\nSource: --panel-bg        → Destination: --background-secondary\nSource: --text-primary    → Destination: --color-text-primary\nSource: --border-radius   → Keep as-is (already defined in destination)\n```\n\n---\n\n### 5. Prepare Adapted CSS Content\n\nAssemble the full CSS block to append, respecting classifications from Step 3:\n\n```css\n/* ==========================================================\n   Styles appended from <source> — <date or ticket reference>\n   ========================================================== */\n\n/* New selector — no prior occurrence */\n.button-primary {\n    background-color: var(--primary-color);   /* was --btn-color */\n    padding: 10px 20px;\n    border-radius: 4px;\n    font-weight: 600;\n}\n\n/* OVERLAP NOTE: .schedule-item also appears in the compound group\n   \".schedule-item, .event-card { color: var(--text-primary); }\" (line 47).\n   This rule intentionally adds layout properties not covered by that group. */\n.schedule-item {\n    display: flex;\n    gap: var(--spacing-sm);\n    align-items: center;\n}\n\n/* Partial addition to existing standalone rule — only missing props */\n.card-header {\n    font-size: var(--font-size-lg);   /* existing rule lacked font-size */\n}\n```\n\n---\n\n### 6. Safely Append to Destination\n\nUse append operations (**never** overwrite) to add the new styles:\n\n```bash\ncat >> /path/to/destination/styles.css << 'EOF'\n\n/* === Appended styles from <source> === */\n\n/* OVERLAP NOTE: .schedule-item also appears in the compound group ... */\n.schedule-item {\n    display: flex;\n    gap: var(--spacing-sm);\n}\nEOF\n```\n\n**Rules:**\n- Always use `>>` — never `>`.\n- Prepend a blank line for readability.\n- Keep overlap comments inline with the rule they describe.\n\n---\n\n### 7. Verify the Changes\n\n```bash\n# Confirm line count increased\nwc -l /path/to/destination/styles.css\n\n# View appended content\ntail -n 40 /path/to/destination/styles.css\n\n# Verify every target selector was added\ngrep \"button-primary\\|schedule-item\\|card-header\" /path/to/destination/styles.css\n\n# Audit all overlap notes in the file\ngrep -n \"OVERLAP NOTE\" /path/to/destination/styles.css\n```\n\nConfirm:\n- Line count increased by the expected amount.\n- All target selectors are present.\n- Every partial-overlap rule has a corresponding `OVERLAP NOTE` comment.\n- No unintended lines were removed (use `wc -l` before and after).\n\n---\n\n## Complete Example\n\n```bash\n# 1. Read source and list target selectors\ncat project-a/components.css | grep -E \"^\\.\" | sort -u\n\n# 2. Check destination for each selector\nfor sel in \".button-primary\" \".schedule-item\" \".card-header\"; do\n    echo \"=== $sel ===\"; grep -n \"$sel\" project-b/main.css\ndone\n\n# 3. Classify results:\n#    .button-primary  → Not found → New\n#    .schedule-item   → Found in \".schedule-item, .event-card { … }\" line 47 → Partial overlap\n#    .card-header     → Found as standalone → Add only missing props\n\n# 4. Note compound line for overlap comment\nCOMPOUND_LINE=47\nCOMPOUND_SEL=\".schedule-item, .event-card { color: var(--text-primary); }\"\n\n# 5. Append\nwc -l project-b/main.css   # baseline: 93 lines\n\ncat >> project-b/main.css << EOF\n\n/* === Button & Schedule styles from project-a === */\n\n/* New selector */\n.button-primary {\n    background-color: var(--primary-color);\n    padding: 10px 20px;\n    border-radius: 4px;\n    font-weight: 600;\n}\n\n/* OVERLAP NOTE: .schedule-item also appears in the compound group\n   \"$COMPOUND_SEL\" (line $COMPOUND_LINE of this file).\n   This rule intentionally adds layout properties not covered by that group. */\n.schedule-item {\n    display: flex;\n    gap: var(--spacing-sm);\n    align-items: center;\n}\n\n/* Partial addition — existing .card-header lacked font-size */\n.card-header {\n    font-size: var(--font-size-lg);\n}\nEOF\n\n# 6. Verify\nwc -l project-b/main.css   # should be 93 + appended lines\ntail -n 30 project-b/main.css\ngrep -n \"OVERLAP NOTE\" project-b/main.css\n```\n\n---\n\n## Safety Checklist\n\n- [ ] Source file read and analyzed\n- [ ] Destination file checked for existing rules\n- [ ] Every target selector classified (New / Partial overlap / Standalone dup)\n- [ ] Compound-group matches recorded with their line numbers\n- [ ] Partial-overlap selectors confirmed to add distinct properties\n- [ ] Each partial-overlap rule has an `OVERLAP NOTE` comment\n- [ ] CSS variables mapped correctly\n- [ ] Append operation used (`>>`), never overwrite (`>`)\n- [ ] `wc -l` baseline taken before appending\n- [ ] Changes verified with `tail`, `grep`, and `wc -l`\n- [ ] No duplicate selectors or duplicate properties created\n- [ ] All variable references exist in destination\n\n---\n\n## Common Pitfalls\n\n1. **Overwriting instead of appending**: Always use `>>`, never `>`.\n2. **Mistaking a compound-group appearance for a full duplicate**: `.foo, .bar { … }` is NOT the same as a standalone `.foo { … }`. Check properties carefully.\n3. **Omitting the overlap comment**: Future developers (or automated linters) may flag the duplicate-looking selector as an error. The `OVERLAP NOTE` prevents false positives.\n4. **Variable mismatch**: Ensure all CSS variables exist in the destination or are properly defined.\n5. **Skipping verification**: Always verify with `wc -l` and `grep \"OVERLAP NOTE\"` after appending.\n\n---\n\n## Tips\n\n- Run `grep -n \"OVERLAP NOTE\" styles.css` at any time to get a quick audit map of all intentional overlaps.\n- For large migrations, produce the classification table (Step 3) as a checklist comment at the top of the appended block so reviewers can cross-reference.\n- Use `wc -l` before **and** after every append to catch accidental truncation.\n- When multiple selectors share the same compound group, a single `OVERLAP NOTE` block may list all of them.\n- Consider storing the variable mapping as a comment in the appended block for traceability.\n"}


skill_id: safe-css-append-enhanced__v0_1a73d06a
name: css-append-selector-verify
path: example/skills/css-append-selector-verify/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: v2c-11
lineage_created_at: 2026-03-10T21:19:51.963493
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T21:19:51.963508
last_updated: 2026-03-10T21:19:51.963510
description:
Safely append CSS styles from one file to another by identifying missing rules, adapting CSS variables, and verifying changes

lineage_change_summary:
Extended the verification step to grep every newly appended selector immediately after append, catching silent heredoc truncation that tail/wc-l alone would miss.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,11 +1,11 @@
 ---
-name: safe-css-append
-description: Safely append CSS styles from one file to another by identifying missing rules, adapting CSS variables, and verifying changes
+name: safe-css-append-verified
+description: Safely append CSS styles from one file to another, with per-selector grep verification after every append to catch silent truncation that tail or wc-l alone would miss
 ---
 
-# Safe CSS Append Workflow
-
-This skill provides a systematic approach for safely appending CSS styles from a source file to a destination file. The workflow ensures no content is overwritten, CSS variables are properly adapted, and all changes are verified.
+# Safe CSS Append Workflow (with Per-Selector Verification)
+
+This skill provides a systematic approach for safely appending CSS styles from a source file to a destination file. The workflow ensures no content is overwritten, CSS variables are properly adapted, and **every appended selector is individually confirmed present** — so silent heredoc truncation of long lines is caught immediately rather than being masked by a passing line-count check.
 
 ## When to Use This Skill
 
@@ -88,10 +88,17 @@
 
 ### 5. Safely Append to Destination
 
-Use append operations (never overwrite) to add the new styles:
-
-```bash
-# Append new CSS rules
+Use append operations (never overwrite) to add the new styles.
+
+**Record the line count BEFORE appending** (use this in step 6):
+
+```bash
+BEFORE=$(wc -l < /path/to/destination/styles.css)
+```
+
+Then append:
+
+```bash
 cat >> /path/to/destination/styles.css << 'EOF'
 
 /* Added styles from source project */
@@ -107,20 +114,62 @@
 - Include a blank line before the new content for readability
 - Use `>>` (append) never `>` (overwrite)
 
-### 6. Verify the Changes
-
-Confirm that the styles were added correctly:
-
-```bash
-# View the last N lines to see appended content
-tail -n 20 /path/to/destination/styles.css
-
-# Verify specific selectors were added
-grep "new-selector-name" /path/to/destination/styles.css
-
-# Check the complete file if needed
-cat /path/to/destination/styles.css | tail -n 50
-```
+### 6. Verify the Changes (Per-Selector Grep + Tail)
+
+Confirm that the styles were added correctly. **Do not rely on `tail` or `wc -l` alone** — a heredoc can silently truncate an overly long line, leaving a selector block incomplete while the line count still increases. The required approach is to grep for *every* selector that was just appended.
+
+#### 6a. Line-count delta
+
+```bash
+AFTER=$(wc -l < /path/to/destination/styles.css)
+echo "Lines added: $((AFTER - BEFORE))"
+```
+
+#### 6b. Per-selector grep — one grep per appended selector
+
+```bash
+# Repeat for every selector appended in step 5
+grep -n "\.selector-name"      /path/to/destination/styles.css || echo "MISSING: .selector-name"
+grep -n "\.another-selector"   /path/to/destination/styles.css || echo "MISSING: .another-selector"
+```
+
+If any grep prints "MISSING", the append was incomplete — re-run step 5.
+
+#### 6c. Automation helper for many selectors
+
+If many selectors were appended, use a loop:
+
+```bash
+DEST=/path/to/destination/styles.css
+MISSING=0
+for selector in \
+    ".selector-name" \
+    ".another-selector" \
+    ".yet-another"; do
+  if ! grep -q "$selector" "$DEST"; then
+    echo "MISSING SELECTOR: $selector"
+    MISSING=$((MISSING + 1))
+  fi
+done
+
+if [ "$MISSING" -gt 0 ]; then
+  echo "ERROR: $MISSING selector(s) not found — append may have been truncated."
+  exit 1
+else
+  echo "All selectors verified present."
+fi
+```
+
+#### 6d. Visual spot-check
+
+```bash
+tail -n 30 /path/to/destination/styles.css
+```
+
+**Why per-selector grep is required:**
+- `wc -l` increments even when a line is truncated — the count passes but content is lost.
+- `tail` shows recent lines but only reveals truncation if you happen to notice a cut-off line visually.
+- A `grep` for each selector name fails with a non-zero exit code if the selector is absent, making the failure explicit and automatable.
 
 ## Complete Example
 
@@ -138,7 +187,10 @@
 #    --btn-color → --primary-color (exists in destination)
 #    --btn-padding → create inline value
 
-# 4. Append adapted styles
+# 4. Record line count before append
+BEFORE=$(wc -l < project-b/main.css)
+
+# 5. Append adapted styles
 cat >> project-b/main.css << 'EOF'
 
 /* Button styles from project-a */
@@ -150,9 +202,17 @@
 }
 EOF
 
-# 5. Verify
+# 6a. Line-count delta
+AFTER=$(wc -l < project-b/main.css)
+echo "Lines before: $BEFORE  |  Lines after: $AFTER  |  Added: $((AFTER - BEFORE))"
+
+# 6b. Per-selector grep — catches truncation that wc -l and tail miss
+grep -n "\.button-primary" project-b/main.css \
+  && echo "OK: .button-primary" \
+  || echo "MISSING: .button-primary — possible truncation!"
+
+# 6c. Visual spot-check
 tail -n 10 project-b/main.css
-grep ".button-primary" project-b/main.css
 ```
 
 ## Safety Checklist
@@ -162,7 +222,8 @@
 - [ ] Missing styles identified
 - [ ] CSS variables mapped correctly
 - [ ] Append operation used (not overwrite)
-- [ ] Changes verified with tail/grep
+- [ ] Every appended selector individually confirmed with grep (not just tail/wc -l)
+- [ ] Automation loop (or per-selector greps) returned zero missing selectors
 - [ ] No duplicate selectors created
 - [ ] All variable references exist in destination
 
@@ -171,11 +232,16 @@
 1. **Overwriting instead of appending**: Always use `>>`, never `>`
 2. **Variable mismatch**: Ensure all CSS variables exist in the destination or are properly defined
 3. **Duplicate selectors**: Check for existing rules before adding complete selector blocks
-4. **Missing verification**: Always verify changes after appending
+4. **Incomplete verification**: Always grep for every appended selector by name — `tail` and `wc -l` do not detect mid-block truncation
+5. **Heredoc line-length truncation**: Very long CSS property values or data URIs inside a heredoc can be silently cut. If a line is extremely long, use a temporary file approach instead (see Tips)
 
 ## Tips
 
-- Use `wc -l` before and after to confirm lines were added
+- Use `wc -l` before and after as a *supplementary* sanity check, but never as the sole verification
+- Collect all selector names appended in a shell array at write time, then loop over them in the verification step — this eliminates the risk of forgetting to check a selector
 - Keep a mapping document for complex variable transformations
-- Consider using a temporary file to preview changes before appending
-- Group related selectors together when appending multiple rules+- For very long lines (e.g., embedded SVGs or data URIs), write the CSS to a temp file first and use `cat tmpfile >> dest.css` rather than a heredoc; then grep-verify as normal
+- Group related selectors together when appending multiple rules
+*** End Files
+
+<EVOLUTION_COMPLETE>

lineage_content_snapshot:
{"SKILL.md": "---\nname: safe-css-append-verified\ndescription: Safely append CSS styles from one file to another, with per-selector grep verification after every append to catch silent truncation that tail or wc-l alone would miss\n---\n\n# Safe CSS Append Workflow (with Per-Selector Verification)\n\nThis skill provides a systematic approach for safely appending CSS styles from a source file to a destination file. The workflow ensures no content is overwritten, CSS variables are properly adapted, and **every appended selector is individually confirmed present** — so silent heredoc truncation of long lines is caught immediately rather than being masked by a passing line-count check.\n\n## When to Use This Skill\n\n- Migrating styles between projects\n- Copying component styles while preserving existing rules\n- Adding new CSS rules without duplicating or conflicting with existing styles\n- Adapting styles to use a different set of CSS variables\n\n## Workflow Steps\n\n### 1. Read and Analyze Source CSS\n\nFirst, examine the source CSS file to identify the selectors and rules you want to transfer:\n\n```bash\n# Read the source CSS file\ncat /path/to/source/styles.css\n\n# Identify specific selectors if needed\ngrep \"selector-pattern\" /path/to/source/styles.css\n```\n\n**Document:**\n- Target selectors (e.g., `.panel-tab`, `.header-nav`)\n- CSS variables used (e.g., `--primary-color`, `--spacing-unit`)\n- Complete rule sets for each selector\n\n### 2. Read and Check Destination CSS\n\nExamine the destination file to understand existing rules and avoid duplication:\n\n```bash\n# Read the destination CSS file\ncat /path/to/destination/styles.css\n\n# Check if specific selectors already exist\ngrep \"selector-name\" /path/to/destination/styles.css\n\n# Check for existing CSS variables\ngrep \"^[[:space:]]*--\" /path/to/destination/styles.css | head -20\n```\n\n**Identify:**\n- Existing selectors that might conflict\n- CSS variable naming conventions in the destination\n- Any existing rules for the target selectors\n\n### 3. Identify Missing Styles and Required Adaptations\n\nCompare source and destination to determine:\n- Which selectors are completely missing\n- Which properties are missing from existing selectors\n- How CSS variables need to be mapped (source → destination)\n\n**Example variable mapping:**\n```\nSource: --panel-bg → Destination: --background-secondary\nSource: --text-primary → Destination: --color-text-primary\nSource: --border-radius → Keep as-is (if already defined in destination)\n```\n\n### 4. Prepare Adapted CSS Content\n\nCreate the CSS content to append, adapting variables as needed:\n\n```css\n/* Example adapted CSS */\n.new-selector {\n    background-color: var(--background-secondary);  /* was --panel-bg */\n    color: var(--color-text-primary);               /* was --text-primary */\n    border-radius: var(--border-radius);            /* unchanged */\n    padding: 8px 16px;\n}\n\n.existing-selector {\n    /* Only add missing properties, not the entire rule */\n    new-property: value;\n}\n```\n\n### 5. Safely Append to Destination\n\nUse append operations (never overwrite) to add the new styles.\n\n**Record the line count BEFORE appending** (use this in step 6):\n\n```bash\nBEFORE=$(wc -l < /path/to/destination/styles.css)\n```\n\nThen append:\n\n```bash\ncat >> /path/to/destination/styles.css << 'EOF'\n\n/* Added styles from source project */\n.selector-name {\n    property: value;\n    variable-property: var(--adapted-variable);\n}\nEOF\n```\n\n**Best practices:**\n- Add a comment indicating the source or purpose\n- Include a blank line before the new content for readability\n- Use `>>` (append) never `>` (overwrite)\n\n### 6. Verify the Changes (Per-Selector Grep + Tail)\n\nConfirm that the styles were added correctly. **Do not rely on `tail` or `wc -l` alone** — a heredoc can silently truncate an overly long line, leaving a selector block incomplete while the line count still increases. The required approach is to grep for *every* selector that was just appended.\n\n#### 6a. Line-count delta\n\n```bash\nAFTER=$(wc -l < /path/to/destination/styles.css)\necho \"Lines added: $((AFTER - BEFORE))\"\n```\n\n#### 6b. Per-selector grep — one grep per appended selector\n\n```bash\n# Repeat for every selector appended in step 5\ngrep -n \"\\.selector-name\"      /path/to/destination/styles.css || echo \"MISSING: .selector-name\"\ngrep -n \"\\.another-selector\"   /path/to/destination/styles.css || echo \"MISSING: .another-selector\"\n```\n\nIf any grep prints \"MISSING\", the append was incomplete — re-run step 5.\n\n#### 6c. Automation helper for many selectors\n\nIf many selectors were appended, use a loop:\n\n```bash\nDEST=/path/to/destination/styles.css\nMISSING=0\nfor selector in \\\n    \".selector-name\" \\\n    \".another-selector\" \\\n    \".yet-another\"; do\n  if ! grep -q \"$selector\" \"$DEST\"; then\n    echo \"MISSING SELECTOR: $selector\"\n    MISSING=$((MISSING + 1))\n  fi\ndone\n\nif [ \"$MISSING\" -gt 0 ]; then\n  echo \"ERROR: $MISSING selector(s) not found — append may have been truncated.\"\n  exit 1\nelse\n  echo \"All selectors verified present.\"\nfi\n```\n\n#### 6d. Visual spot-check\n\n```bash\ntail -n 30 /path/to/destination/styles.css\n```\n\n**Why per-selector grep is required:**\n- `wc -l` increments even when a line is truncated — the count passes but content is lost.\n- `tail` shows recent lines but only reveals truncation if you happen to notice a cut-off line visually.\n- A `grep` for each selector name fails with a non-zero exit code if the selector is absent, making the failure explicit and automatable.\n\n## Complete Example\n\nHere's a full example workflow:\n\n```bash\n# 1. Read source CSS and identify target\ncat project-a/components.css | grep -A 10 \".button-primary\"\n\n# 2. Check destination for conflicts\ngrep \".button-primary\" project-b/main.css\ngrep \"^[[:space:]]*--btn-\" project-b/main.css\n\n# 3. Identify that .button-primary is missing, and variables need mapping:\n#    --btn-color → --primary-color (exists in destination)\n#    --btn-padding → create inline value\n\n# 4. Record line count before append\nBEFORE=$(wc -l < project-b/main.css)\n\n# 5. Append adapted styles\ncat >> project-b/main.css << 'EOF'\n\n/* Button styles from project-a */\n.button-primary {\n    background-color: var(--primary-color);\n    padding: 10px 20px;\n    border-radius: 4px;\n    font-weight: 600;\n}\nEOF\n\n# 6a. Line-count delta\nAFTER=$(wc -l < project-b/main.css)\necho \"Lines before: $BEFORE  |  Lines after: $AFTER  |  Added: $((AFTER - BEFORE))\"\n\n# 6b. Per-selector grep — catches truncation that wc -l and tail miss\ngrep -n \"\\.button-primary\" project-b/main.css \\\n  && echo \"OK: .button-primary\" \\\n  || echo \"MISSING: .button-primary — possible truncation!\"\n\n# 6c. Visual spot-check\ntail -n 10 project-b/main.css\n```\n\n## Safety Checklist\n\n- [ ] Source file read and analyzed\n- [ ] Destination file checked for existing rules\n- [ ] Missing styles identified\n- [ ] CSS variables mapped correctly\n- [ ] Append operation used (not overwrite)\n- [ ] Every appended selector individually confirmed with grep (not just tail/wc -l)\n- [ ] Automation loop (or per-selector greps) returned zero missing selectors\n- [ ] No duplicate selectors created\n- [ ] All variable references exist in destination\n\n## Common Pitfalls\n\n1. **Overwriting instead of appending**: Always use `>>`, never `>`\n2. **Variable mismatch**: Ensure all CSS variables exist in the destination or are properly defined\n3. **Duplicate selectors**: Check for existing rules before adding complete selector blocks\n4. **Incomplete verification**: Always grep for every appended selector by name — `tail` and `wc -l` do not detect mid-block truncation\n5. **Heredoc line-length truncation**: Very long CSS property values or data URIs inside a heredoc can be silently cut. If a line is extremely long, use a temporary file approach instead (see Tips)\n\n## Tips\n\n- Use `wc -l` before and after as a *supplementary* sanity check, but never as the sole verification\n- Collect all selector names appended in a shell array at write time, then loop over them in the verification step — this eliminates the risk of forgetting to check a selector\n- Keep a mapping document for complex variable transformations\n- For very long lines (e.g., embedded SVGs or data URIs), write the CSS to a temp file first and use `cat tmpfile >> dest.css` rather than a heredoc; then grep-verify as normal\n- Group related selectors together when appending multiple rules\n*** End Files\n\n<EVOLUTION_COMPLETE>\n"}


skill_id: large-file-write-fallback-enhanced__v0_6ac68e45
name: large-file-write-mkdir
path: example/skills/large-file-write-mkdir/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: evolution-v3-task-01
lineage_created_at: 2026-03-10T22:08:32.083815
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T22:08:32.083848
last_updated: 2026-03-10T22:08:32.083850
description:
Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints.

lineage_change_summary:
Made post-write verification mandatory by adding an explicit required verification step with line-count validation after every heredoc write.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,9 +1,9 @@
 ---
-name: large-file-write-fallback
-description: Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints.
+name: large-file-write-verified
+description: Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints, with mandatory post-write verification of line count and file header.
 ---
 
-# Large File Write Fallback
+# Large File Write — Verified Heredoc Fallback
 
 ## Problem
 
@@ -53,10 +53,26 @@
    - The heredoc delimiter `EOF` must not appear on a line by itself inside
      the content (rename it to `PYEOF` or `FILEEOF` if needed).
 
-5. **Verify the write** by following up with a `run_shell` call such as:
+5. **Mandatory verification** — immediately after the heredoc write, you MUST
+   run a `run_shell` verification call:
    ```bash
    wc -l <TARGET PATH> && head -5 <TARGET PATH>
    ```
+   Then confirm **both** of the following before proceeding:
+   - The reported line count matches your expected line count (note the count
+     from a prior inspection, or count lines in the content you wrote).
+   - The first 5 lines shown by `head` match the intended start of the file.
+
+   **This step is not optional.** Do not mark the task complete until both
+   checks pass.
+
+6. **If verification fails:**
+   - Line count is 0 or file is missing → the heredoc command itself failed;
+     check for shell errors and retry.
+   - Line count is lower than expected → the content may have been truncated;
+     inspect with `tail -5 <TARGET PATH>` and re-write.
+   - First lines are wrong → an escaping issue corrupted the content; fix
+     escaping and re-write.
 
 ## Full Example
 
@@ -84,6 +100,16 @@
 Pass the above (without the surrounding code fence) as the `command`
 argument to `run_shell`.
 
+After the write, **immediately** run the mandatory verification:
+
+```bash
+wc -l src/components/Dashboard.ts && head -5 src/components/Dashboard.ts
+```
+
+Expected output should show the correct line count (10 lines for this example)
+and the first line should be `import { foo } from './foo';`. Only proceed if
+both match.
+
 ## When to Use This Pattern
 
 | Situation | Recommended tool |
@@ -92,6 +118,19 @@
 | Medium file, no errors yet | `write_file` (try first) |
 | Large file or `write_file` failed | `run_shell` + Python heredoc |
 | `shell_agent` also fails on large inline content | `run_shell` + Python heredoc |
+
+## Verification Quick Reference
+
+After every heredoc write, run:
+```bash
+wc -l <TARGET PATH> && head -5 <TARGET PATH>
+```
+
+| Check | Pass condition | Fail action |
+|---|---|---|
+| Line count | Matches expected count (±0) | Re-write; check for truncation |
+| Head output | First lines match intended content | Fix escaping; re-write |
+| File exists | `wc -l` returns a number, not an error | Check path and directory; retry |
 
 ## Notes
 
@@ -102,4 +141,9 @@
 - The heredoc delimiter (`EOF`, `PYEOF`, `FILEEOF`) can be any string not
   present as a standalone line in your content — choose accordingly.
 - This technique is also useful when content contains characters that would
-  need heavy shell escaping in a plain `echo` or `printf` approach.+  need heavy shell escaping in a plain `echo` or `printf` approach.
+- Before writing, note the expected line count (e.g. from a prior `wc -l`
+  or by counting the content). This makes step 5 verification unambiguous.
+*** End Files
+
+<EVOLUTION_COMPLETE>

lineage_content_snapshot:
{"SKILL.md": "---\nname: large-file-write-verified\ndescription: Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints, with mandatory post-write verification of line count and file header.\n---\n\n# Large File Write — Verified Heredoc Fallback\n\n## Problem\n\nWhen writing large files (typically several KB or more), two common tools may\nfail with `unknown error` due to internal payload size limits:\n\n- `write_file` — has a maximum content size it can handle in a single call.\n- `shell_agent` — may also hit payload limits when the task description\n  includes large inline content.\n\n## Solution\n\nUse `run_shell` with a **Python heredoc** pattern. This streams the file\ncontent through stdin directly into Python's `open()`, bypassing the payload\nconstraints of the other tools.\n\n## Template\n\n```\npython3 - << 'EOF'\ncontent = \"\"\"<FILE CONTENTS HERE>\"\"\"\nwith open(\"<TARGET PATH>\", \"w\") as f:\n    f.write(content)\nEOF\n```\n\nPass this as the `command` parameter to `run_shell`.\n\n## Step-by-Step Instructions\n\n1. **Attempt the normal write** using `write_file` first. If it succeeds, you\n   are done.\n\n2. **If `write_file` fails** (especially with `unknown error` or a timeout on\n   large content), do NOT retry with `shell_agent` using inline content —\n   it will likely fail for the same reason.\n\n3. **Use the `run_shell` heredoc fallback:**\n\n   - Embed the full file content inside a Python triple-quoted string.\n   - Specify the target path inside the `open()` call.\n   - Pass the entire block as the `command` to `run_shell`.\n\n4. **Escape carefully** inside the heredoc:\n   - Backslashes that should be literal in the file must be doubled (`\\\\`).\n   - Triple-quotes inside the content must be escaped (`\\\"\\\"\\\"`).\n   - The heredoc delimiter `EOF` must not appear on a line by itself inside\n     the content (rename it to `PYEOF` or `FILEEOF` if needed).\n\n5. **Mandatory verification** — immediately after the heredoc write, you MUST\n   run a `run_shell` verification call:\n   ```bash\n   wc -l <TARGET PATH> && head -5 <TARGET PATH>\n   ```\n   Then confirm **both** of the following before proceeding:\n   - The reported line count matches your expected line count (note the count\n     from a prior inspection, or count lines in the content you wrote).\n   - The first 5 lines shown by `head` match the intended start of the file.\n\n   **This step is not optional.** Do not mark the task complete until both\n   checks pass.\n\n6. **If verification fails:**\n   - Line count is 0 or file is missing → the heredoc command itself failed;\n     check for shell errors and retry.\n   - Line count is lower than expected → the content may have been truncated;\n     inspect with `tail -5 <TARGET PATH>` and re-write.\n   - First lines are wrong → an escaping issue corrupted the content; fix\n     escaping and re-write.\n\n## Full Example\n\nSuppose you need to write a large TypeScript file to\n`src/components/Dashboard.ts`:\n\n```\npython3 - << 'PYEOF'\ncontent = \"\"\"import { foo } from './foo';\n\nexport interface DashboardData {\n  title: string;\n  items: string[];\n}\n\nexport function createDashboard(data: DashboardData): string {\n  return `<div>${data.title}</div>`;\n}\n\"\"\"\nwith open(\"src/components/Dashboard.ts\", \"w\") as f:\n    f.write(content)\nPYEOF\n```\n\nPass the above (without the surrounding code fence) as the `command`\nargument to `run_shell`.\n\nAfter the write, **immediately** run the mandatory verification:\n\n```bash\nwc -l src/components/Dashboard.ts && head -5 src/components/Dashboard.ts\n```\n\nExpected output should show the correct line count (10 lines for this example)\nand the first line should be `import { foo } from './foo';`. Only proceed if\nboth match.\n\n## When to Use This Pattern\n\n| Situation | Recommended tool |\n|---|---|\n| Small file (< ~2 KB) | `write_file` |\n| Medium file, no errors yet | `write_file` (try first) |\n| Large file or `write_file` failed | `run_shell` + Python heredoc |\n| `shell_agent` also fails on large inline content | `run_shell` + Python heredoc |\n\n## Verification Quick Reference\n\nAfter every heredoc write, run:\n```bash\nwc -l <TARGET PATH> && head -5 <TARGET PATH>\n```\n\n| Check | Pass condition | Fail action |\n|---|---|---|\n| Line count | Matches expected count (±0) | Re-write; check for truncation |\n| Head output | First lines match intended content | Fix escaping; re-write |\n| File exists | `wc -l` returns a number, not an error | Check path and directory; retry |\n\n## Notes\n\n- This pattern works for **any text-based file** (TypeScript, Python, JSON,\n  YAML, Markdown, etc.).\n- For binary files, adapt the approach to use `base64` decoding inside the\n  Python script.\n- The heredoc delimiter (`EOF`, `PYEOF`, `FILEEOF`) can be any string not\n  present as a standalone line in your content — choose accordingly.\n- This technique is also useful when content contains characters that would\n  need heavy shell escaping in a plain `echo` or `printf` approach.\n- Before writing, note the expected line count (e.g. from a prior `wc -l`\n  or by counting the content). This makes step 5 verification unambiguous.\n*** End Files\n\n<EVOLUTION_COMPLETE>\n"}


skill_id: large-file-write-fallback-enhanced__v0_b33ce7d0
name: large-file-write-mkdir
path: example/skills/large-file-write-mkdir/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: evolution-v3-task-01
lineage_created_at: 2026-03-10T22:08:37.993346
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T22:08:37.993364
last_updated: 2026-03-10T22:08:37.993365
description:
Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints.

lineage_change_summary:
Added explicit `mkdir -p <parent_dir>` preparatory step to the template, step-by-step instructions, and full example to prevent failures when writing to non-existent directories.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,9 +1,9 @@
 ---
-name: large-file-write-fallback
-description: Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints.
+name: large-file-write-fallback-mkdir
+description: Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints, with an explicit mkdir -p step to ensure the target directory exists before writing.
 ---
 
-# Large File Write Fallback
+# Large File Write Fallback (with Directory Pre-creation)
 
 ## Problem
 
@@ -14,16 +14,21 @@
 - `shell_agent` — may also hit payload limits when the task description
   includes large inline content.
 
+A second class of failure occurs when the **target directory does not exist**:
+Python's `open()` will raise `FileNotFoundError` if any intermediate directory
+is missing.  Always pre-create the parent directory to prevent this.
+
 ## Solution
 
-Use `run_shell` with a **Python heredoc** pattern. This streams the file
-content through stdin directly into Python's `open()`, bypassing the payload
-constraints of the other tools.
+Use `run_shell` with a **`mkdir -p` prefix and a Python heredoc** pattern.
+The `mkdir -p` ensures the target directory exists; the heredoc streams the
+file content through stdin directly into Python's `open()`, bypassing the
+payload constraints of the other tools.
 
 ## Template
 
-```
-python3 - << 'EOF'
+```bash
+mkdir -p "<PARENT DIRECTORY OF TARGET PATH>" && python3 - << 'EOF'
 content = """<FILE CONTENTS HERE>"""
 with open("<TARGET PATH>", "w") as f:
     f.write(content)
@@ -31,6 +36,10 @@
 ```
 
 Pass this as the `command` parameter to `run_shell`.
+
+> **Tip:** If the parent directory is not known in advance, use the shell
+> `dirname` helper:
+> `mkdir -p "$(dirname '<TARGET PATH>')" && python3 - << 'EOF'`
 
 ## Step-by-Step Instructions
 
@@ -43,9 +52,13 @@
 
 3. **Use the `run_shell` heredoc fallback:**
 
+   - **Pre-create the parent directory** with `mkdir -p <parent_dir>`.  This
+     is a no-op when the directory already exists, so it is always safe to
+     include.  Skipping this step causes Python's `open()` to raise
+     `FileNotFoundError` for any new or deeply-nested path.
    - Embed the full file content inside a Python triple-quoted string.
    - Specify the target path inside the `open()` call.
-   - Pass the entire block as the `command` to `run_shell`.
+   - Pass the entire block (mkdir + heredoc) as the `command` to `run_shell`.
 
 4. **Escape carefully** inside the heredoc:
    - Backslashes that should be literal in the file must be doubled (`\\`).
@@ -63,8 +76,8 @@
 Suppose you need to write a large TypeScript file to
 `src/components/Dashboard.ts`:
 
-```
-python3 - << 'PYEOF'
+```bash
+mkdir -p "src/components" && python3 - << 'PYEOF'
 content = """import { foo } from './foo';
 
 export interface DashboardData {
@@ -90,8 +103,9 @@
 |---|---|
 | Small file (< ~2 KB) | `write_file` |
 | Medium file, no errors yet | `write_file` (try first) |
-| Large file or `write_file` failed | `run_shell` + Python heredoc |
-| `shell_agent` also fails on large inline content | `run_shell` + Python heredoc |
+| Large file or `write_file` failed | `run_shell` + `mkdir -p` + Python heredoc |
+| `shell_agent` also fails on large inline content | `run_shell` + `mkdir -p` + Python heredoc |
+| Target directory may not exist | Always add `mkdir -p <parent_dir>` first |
 
 ## Notes
 
@@ -102,4 +116,12 @@
 - The heredoc delimiter (`EOF`, `PYEOF`, `FILEEOF`) can be any string not
   present as a standalone line in your content — choose accordingly.
 - This technique is also useful when content contains characters that would
-  need heavy shell escaping in a plain `echo` or `printf` approach.+  need heavy shell escaping in a plain `echo` or `printf` approach.
+- The `mkdir -p` command is idempotent: it succeeds whether or not the
+  directory already exists, so it is always safe to include.
+- To derive the parent directory from a target path in shell:
+  `mkdir -p "$(dirname '<TARGET PATH>')"` works as a generic alternative
+  when the parent path is not already known.
+*** End Files
+
+<EVOLUTION_COMPLETE>

lineage_content_snapshot:
{"SKILL.md": "---\nname: large-file-write-fallback-mkdir\ndescription: Reliable fallback technique for writing large file contents when write_file and shell_agent both fail with 'unknown error' due to payload size limits — uses run_shell with a Python heredoc to bypass tool constraints, with an explicit mkdir -p step to ensure the target directory exists before writing.\n---\n\n# Large File Write Fallback (with Directory Pre-creation)\n\n## Problem\n\nWhen writing large files (typically several KB or more), two common tools may\nfail with `unknown error` due to internal payload size limits:\n\n- `write_file` — has a maximum content size it can handle in a single call.\n- `shell_agent` — may also hit payload limits when the task description\n  includes large inline content.\n\nA second class of failure occurs when the **target directory does not exist**:\nPython's `open()` will raise `FileNotFoundError` if any intermediate directory\nis missing.  Always pre-create the parent directory to prevent this.\n\n## Solution\n\nUse `run_shell` with a **`mkdir -p` prefix and a Python heredoc** pattern.\nThe `mkdir -p` ensures the target directory exists; the heredoc streams the\nfile content through stdin directly into Python's `open()`, bypassing the\npayload constraints of the other tools.\n\n## Template\n\n```bash\nmkdir -p \"<PARENT DIRECTORY OF TARGET PATH>\" && python3 - << 'EOF'\ncontent = \"\"\"<FILE CONTENTS HERE>\"\"\"\nwith open(\"<TARGET PATH>\", \"w\") as f:\n    f.write(content)\nEOF\n```\n\nPass this as the `command` parameter to `run_shell`.\n\n> **Tip:** If the parent directory is not known in advance, use the shell\n> `dirname` helper:\n> `mkdir -p \"$(dirname '<TARGET PATH>')\" && python3 - << 'EOF'`\n\n## Step-by-Step Instructions\n\n1. **Attempt the normal write** using `write_file` first. If it succeeds, you\n   are done.\n\n2. **If `write_file` fails** (especially with `unknown error` or a timeout on\n   large content), do NOT retry with `shell_agent` using inline content —\n   it will likely fail for the same reason.\n\n3. **Use the `run_shell` heredoc fallback:**\n\n   - **Pre-create the parent directory** with `mkdir -p <parent_dir>`.  This\n     is a no-op when the directory already exists, so it is always safe to\n     include.  Skipping this step causes Python's `open()` to raise\n     `FileNotFoundError` for any new or deeply-nested path.\n   - Embed the full file content inside a Python triple-quoted string.\n   - Specify the target path inside the `open()` call.\n   - Pass the entire block (mkdir + heredoc) as the `command` to `run_shell`.\n\n4. **Escape carefully** inside the heredoc:\n   - Backslashes that should be literal in the file must be doubled (`\\\\`).\n   - Triple-quotes inside the content must be escaped (`\\\"\\\"\\\"`).\n   - The heredoc delimiter `EOF` must not appear on a line by itself inside\n     the content (rename it to `PYEOF` or `FILEEOF` if needed).\n\n5. **Verify the write** by following up with a `run_shell` call such as:\n   ```bash\n   wc -l <TARGET PATH> && head -5 <TARGET PATH>\n   ```\n\n## Full Example\n\nSuppose you need to write a large TypeScript file to\n`src/components/Dashboard.ts`:\n\n```bash\nmkdir -p \"src/components\" && python3 - << 'PYEOF'\ncontent = \"\"\"import { foo } from './foo';\n\nexport interface DashboardData {\n  title: string;\n  items: string[];\n}\n\nexport function createDashboard(data: DashboardData): string {\n  return `<div>${data.title}</div>`;\n}\n\"\"\"\nwith open(\"src/components/Dashboard.ts\", \"w\") as f:\n    f.write(content)\nPYEOF\n```\n\nPass the above (without the surrounding code fence) as the `command`\nargument to `run_shell`.\n\n## When to Use This Pattern\n\n| Situation | Recommended tool |\n|---|---|\n| Small file (< ~2 KB) | `write_file` |\n| Medium file, no errors yet | `write_file` (try first) |\n| Large file or `write_file` failed | `run_shell` + `mkdir -p` + Python heredoc |\n| `shell_agent` also fails on large inline content | `run_shell` + `mkdir -p` + Python heredoc |\n| Target directory may not exist | Always add `mkdir -p <parent_dir>` first |\n\n## Notes\n\n- This pattern works for **any text-based file** (TypeScript, Python, JSON,\n  YAML, Markdown, etc.).\n- For binary files, adapt the approach to use `base64` decoding inside the\n  Python script.\n- The heredoc delimiter (`EOF`, `PYEOF`, `FILEEOF`) can be any string not\n  present as a standalone line in your content — choose accordingly.\n- This technique is also useful when content contains characters that would\n  need heavy shell escaping in a plain `echo` or `printf` approach.\n- The `mkdir -p` command is idempotent: it succeeds whether or not the\n  directory already exists, so it is always safe to include.\n- To derive the parent directory from a target path in shell:\n  `mkdir -p \"$(dirname '<TARGET PATH>')\"` works as a generic alternative\n  when the parent path is not already known.\n*** End Files\n\n<EVOLUTION_COMPLETE>\n"}


skill_id: idempotent-file-replace__v0_41fce5e6
name: idempotent-file-replace
path: example/skills/idempotent-file-replace/SKILL.md
category: workflow
visibility: private
lineage_origin: captured
lineage_source_task_id: evolution-v3-task-02
lineage_created_at: 2026-03-10T22:10:11.408007
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T22:10:11.408041
last_updated: 2026-03-10T22:10:11.408042
description:
Read-then-diff before writing a file to ensure the write is skipped when the content already matches, preventing unnecessary modifications and downstream side effects.

lineage_change_summary:
Before overwriting a file, read its current content and compare it to the target; skip the write if they already match, making the operation idempotent and avoiding unnecessary side effects.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -0,0 +1,152 @@
+---
+name: idempotent-file-replace
+description: Read-then-diff before writing a file to ensure the write is skipped when the content already matches, preventing unnecessary modifications and downstream side effects.
+---
+
+# Idempotent File Replace
+
+When tasked with **completely replacing** a file with specific content, always
+perform a read-and-compare step before writing. Only write when the file
+actually differs from the desired content.
+
+## Why This Matters
+
+| Risk of blind writes | Benefit of idempotent check |
+|---|---|
+| Triggers filesystem watchers / hot-reload loops | No spurious rebuild when content is unchanged |
+| Wastes a tool call on a no-op | One extra read saves a write + all downstream costs |
+| Pollutes VCS history with empty diffs | Clean commit history |
+| Breaks CI caching layers | Stable mtimes keep caches valid |
+
+---
+
+## Procedure
+
+### Step 1 — Read the current file
+
+Always read the file first, even when you are confident about the replacement.
+
+```
+read_file(path="<target_file>")
+```
+
+If the file does **not yet exist**, treat its current content as an empty
+string and proceed to Step 3.
+
+---
+
+### Step 2 — Compare current content to desired content
+
+Perform an exact string comparison (whitespace-sensitive).
+
+```python
+current  = <content returned by read_file>
+desired  = <full replacement content>
+
+needs_write = (current.strip() != desired.strip())
+# Use .strip() to tolerate a single trailing newline difference,
+# or compare verbatim if byte-exact output is required.
+```
+
+Alternatively, a unified diff gives a human-readable explanation of what
+would change and is useful for logging:
+
+```python
+import difflib, sys
+
+diff = list(difflib.unified_diff(
+    current.splitlines(keepends=True),
+    desired.splitlines(keepends=True),
+    fromfile="current",
+    tofile="desired",
+))
+
+if not diff:
+    print("Files are identical — skipping write.")
+    needs_write = False
+else:
+    print("Diff detected:\n" + "".join(diff))
+    needs_write = True
+```
+
+---
+
+### Step 3 — Conditional write
+
+```python
+if needs_write:
+    write_file(path="<target_file>", content=desired)
+    print("File written.")
+else:
+    # Report completion immediately — no write needed.
+    print("File already matches desired content. No action taken.")
+```
+
+---
+
+## Decision Flowchart
+
+```
+Task: "Replace <file> with <content>"
+        │
+        ▼
+   read_file(<file>)
+        │
+        ▼
+  content == desired?
+   ┌────┴────┐
+  YES       NO
+   │         │
+   ▼         ▼
+ SKIP     write_file(<file>, desired)
+ write        │
+   │          ▼
+   └──► Report COMPLETE
+```
+
+---
+
+## Complete Example
+
+```python
+TARGET = "src/components/Panel.ts"
+DESIRED_CONTENT = """// Auto-generated — do not edit
+export const Panel = () => { ... };
+"""
+
+# Step 1: read
+current = read_file(path=TARGET)          # tool call
+
+# Step 2: compare
+if current.strip() == DESIRED_CONTENT.strip():
+    print(f"{TARGET} already matches. Skipping write.")
+    # → COMPLETE
+else:
+    # Step 3: write
+    write_file(path=TARGET, content=DESIRED_CONTENT)   # tool call
+    print(f"{TARGET} updated.")
+    # → COMPLETE
+```
+
+---
+
+## Edge Cases
+
+| Situation | Handling |
+|---|---|
+| File does not exist | `read_file` raises / returns empty → treat as `""` → always write |
+| Encoding differences | Normalize to UTF-8 before comparison |
+| Line-ending differences (CRLF vs LF) | Normalize with `.replace("\r\n", "\n")` before comparing |
+| Byte-exact requirement | Skip `.strip()` and compare verbatim |
+| Large files | Compute SHA-256 hash of both strings for efficiency before full diff |
+
+---
+
+## Key Principle
+
+> **Never assume a file needs to be written. One cheap read prevents an
+> expensive (and potentially harmful) write.**
+
+This pattern applies to any "replace entire file" task regardless of file
+type: source code, configuration, templates, lock files, generated assets,
+etc.

lineage_content_snapshot:
{"SKILL.md": "---\nname: idempotent-file-replace\ndescription: Read-then-diff before writing a file to ensure the write is skipped when the content already matches, preventing unnecessary modifications and downstream side effects.\n---\n\n# Idempotent File Replace\n\nWhen tasked with **completely replacing** a file with specific content, always\nperform a read-and-compare step before writing. Only write when the file\nactually differs from the desired content.\n\n## Why This Matters\n\n| Risk of blind writes | Benefit of idempotent check |\n|---|---|\n| Triggers filesystem watchers / hot-reload loops | No spurious rebuild when content is unchanged |\n| Wastes a tool call on a no-op | One extra read saves a write + all downstream costs |\n| Pollutes VCS history with empty diffs | Clean commit history |\n| Breaks CI caching layers | Stable mtimes keep caches valid |\n\n---\n\n## Procedure\n\n### Step 1 — Read the current file\n\nAlways read the file first, even when you are confident about the replacement.\n\n```\nread_file(path=\"<target_file>\")\n```\n\nIf the file does **not yet exist**, treat its current content as an empty\nstring and proceed to Step 3.\n\n---\n\n### Step 2 — Compare current content to desired content\n\nPerform an exact string comparison (whitespace-sensitive).\n\n```python\ncurrent  = <content returned by read_file>\ndesired  = <full replacement content>\n\nneeds_write = (current.strip() != desired.strip())\n# Use .strip() to tolerate a single trailing newline difference,\n# or compare verbatim if byte-exact output is required.\n```\n\nAlternatively, a unified diff gives a human-readable explanation of what\nwould change and is useful for logging:\n\n```python\nimport difflib, sys\n\ndiff = list(difflib.unified_diff(\n    current.splitlines(keepends=True),\n    desired.splitlines(keepends=True),\n    fromfile=\"current\",\n    tofile=\"desired\",\n))\n\nif not diff:\n    print(\"Files are identical — skipping write.\")\n    needs_write = False\nelse:\n    print(\"Diff detected:\\n\" + \"\".join(diff))\n    needs_write = True\n```\n\n---\n\n### Step 3 — Conditional write\n\n```python\nif needs_write:\n    write_file(path=\"<target_file>\", content=desired)\n    print(\"File written.\")\nelse:\n    # Report completion immediately — no write needed.\n    print(\"File already matches desired content. No action taken.\")\n```\n\n---\n\n## Decision Flowchart\n\n```\nTask: \"Replace <file> with <content>\"\n        │\n        ▼\n   read_file(<file>)\n        │\n        ▼\n  content == desired?\n   ┌────┴────┐\n  YES       NO\n   │         │\n   ▼         ▼\n SKIP     write_file(<file>, desired)\n write        │\n   │          ▼\n   └──► Report COMPLETE\n```\n\n---\n\n## Complete Example\n\n```python\nTARGET = \"src/components/Panel.ts\"\nDESIRED_CONTENT = \"\"\"// Auto-generated — do not edit\nexport const Panel = () => { ... };\n\"\"\"\n\n# Step 1: read\ncurrent = read_file(path=TARGET)          # tool call\n\n# Step 2: compare\nif current.strip() == DESIRED_CONTENT.strip():\n    print(f\"{TARGET} already matches. Skipping write.\")\n    # → COMPLETE\nelse:\n    # Step 3: write\n    write_file(path=TARGET, content=DESIRED_CONTENT)   # tool call\n    print(f\"{TARGET} updated.\")\n    # → COMPLETE\n```\n\n---\n\n## Edge Cases\n\n| Situation | Handling |\n|---|---|\n| File does not exist | `read_file` raises / returns empty → treat as `\"\"` → always write |\n| Encoding differences | Normalize to UTF-8 before comparison |\n| Line-ending differences (CRLF vs LF) | Normalize with `.replace(\"\\r\\n\", \"\\n\")` before comparing |\n| Byte-exact requirement | Skip `.strip()` and compare verbatim |\n| Large files | Compute SHA-256 hash of both strings for efficiency before full diff |\n\n---\n\n## Key Principle\n\n> **Never assume a file needs to be written. One cheap read prevents an\n> expensive (and potentially harmful) write.**\n\nThis pattern applies to any \"replace entire file\" task regardless of file\ntype: source code, configuration, templates, lock files, generated assets,\netc."}


skill_id: panel-component-enhanced-merged_465bbc-enhanced__v0_a6046255
name: panel-base-advanced
path: example/skills/panel-base-advanced/SKILL.md
category: reference
visibility: private
lineage_origin: derived
lineage_source_task_id: evolution-v3-task-02
lineage_created_at: 2026-03-10T22:13:12.033778
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T22:13:12.033808
last_updated: 2026-03-10T22:13:12.033810
lineage_change_summary:
Updated Panel.ts reference implementation to reflect the authoritative production version with grid-span persistence, dual resize handles, radar loading animation, exponential-backoff error countdown, data/new badge methods, and full destroy() cleanup.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,9 +1,9 @@
 ---
-name: resilient-panel-unified
-description: Unified guide for authoring dashboard panel components in vanilla TypeScript — covers the Panel base class architecture, localStorage state persistence, exponential-backoff retry, and CircuitBreaker integration. Shows how to wrap any existing fetchX() service call with CircuitBreaker.execute() as a one-liner and clarifies when each resilience layer is appropriate.
----
-
-# Resilient Panel — Unified Pattern
+name: resilient-panel-unified-v2
+description: Unified guide for authoring dashboard panel components in vanilla TypeScript — covers the authoritative Panel base class (grid-span persistence, dual resize handles, radar loading animation, exponential-backoff error countdown, data/new badge helpers, full destroy() cleanup), localStorage state via loadMap/saveMap, exponential-backoff retry, and CircuitBreaker integration. Shows how to wrap any existing fetchX() service call with CircuitBreaker.execute() as a one-liner and clarifies when each resilience layer is appropriate.
+---
+
+# Resilient Panel — Unified Pattern (v2)
 
 Create dashboard panel components using **vanilla TypeScript** (no framework, no JSX).
 Each panel is a class extending a `Panel` base class and optionally wraps its data source with a `CircuitBreaker`.
@@ -18,11 +18,12 @@
 │   ├── header: HTMLElement (.panel-header)
 │   │   ├── headerLeft (.panel-header-left)
 │   │   │   ├── title (.panel-title)
-│   │   │   └── newBadge (.panel-new-badge) [optional]
-│   │   ├── statusBadge (.panel-data-badge) [optional]
+│   │   │   └── newBadge (.panel-new-badge) [optional, via setNewBadge()]
+│   │   ├── statusBadge (.panel-data-badge) [optional, via setDataBadge()]
 │   │   └── countEl (.panel-count) [optional]
 │   ├── content: HTMLElement (.panel-content)
-│   └── resizeHandle (.panel-resize-handle)
+│   ├── resizeHandleRow (.panel-resize-handle-row)   [bottom edge]
+│   └── resizeHandleCol (.panel-resize-handle-col)   [right edge]
 ```
 
 ### Resilience layers (choose what you need)
@@ -31,8 +32,9 @@
 |---|---|---|
 | **isFetching guard** | `Panel` base class | Always — prevents concurrent duplicate requests |
 | **Exponential-backoff retry** | `Panel.fetchWithRetry()` | When you own the fetch call and want per-request retry before surfacing an error |
+| **Error countdown in showError()** | `Panel` base class | Automatic retry countdown rendered inside the error state UI |
 | **CircuitBreaker** | `src/utils/circuit-breaker.ts` | When a service may fail repeatedly; stops hammering the API and serves stale cache instead |
-| **localStorage persistence** | `Panel.saveState()` / `loadState()` | When you want collapsed/size state to survive page reloads |
+| **localStorage persistence** | `Panel.saveMap()` / `loadMap()` | When you want grid-span (not pixel size) state to survive page reloads |
 
 > **Wrapping an existing service call with CircuitBreaker is a one-liner:**
 > ```typescript
@@ -46,6 +48,9 @@
 
 ## File 1 — `src/components/Panel.ts`
 
+> **Authoritative implementation.** Use this verbatim; do not revert to
+> the older `saveState()`/`loadState()` width/height approach.
+
 ```typescript
 export interface PanelOptions {
   id: string;
@@ -58,9 +63,15 @@
   protected element: HTMLElement;
   protected content: HTMLElement;
   protected header: HTMLElement;
+  protected headerLeft: HTMLElement;
   protected countEl: HTMLElement | null = null;
+  protected statusBadge: HTMLElement | null = null;
+  protected newBadge: HTMLElement | null = null;
   protected panelId: string;
+
   private _fetching = false;
+  private _countdownInterval: ReturnType<typeof setInterval> | null = null;
+  private _resizeListeners: Array<{ target: EventTarget; type: string; fn: EventListenerOrEventListenerObject }> = [];
 
   // Retry state (used by fetchWithRetry)
   protected retryAttempts = 0;
@@ -69,22 +80,23 @@
 
   constructor(options: PanelOptions) {
     this.panelId = options.id;
+
     this.element = document.createElement('div');
-    this.element.className = `panel ${options.className ?? ''}`;
+    this.element.className = `panel${options.className ? ' ' + options.className : ''}`;
     this.element.dataset.panel = options.id;
 
     // Header
     this.header = document.createElement('div');
     this.header.className = 'panel-header';
 
-    const headerLeft = document.createElement('div');
-    headerLeft.className = 'panel-header-left';
+    this.headerLeft = document.createElement('div');
+    this.headerLeft.className = 'panel-header-left';
 
     const title = document.createElement('span');
     title.className = 'panel-title';
     title.textContent = options.title;
-    headerLeft.appendChild(title);
-    this.header.appendChild(headerLeft);
+    this.headerLeft.appendChild(title);
+    this.header.appendChild(this.headerLeft);
 
     // Count badge (optional)
     if (options.showCount) {
@@ -99,54 +111,146 @@
     this.content.className = 'panel-content';
     this.content.id = `${options.id}Content`;
 
+    // Dual resize handles
+    const resizeHandleRow = document.createElement('div');
+    resizeHandleRow.className = 'panel-resize-handle-row';
+
+    const resizeHandleCol = document.createElement('div');
+    resizeHandleCol.className = 'panel-resize-handle-col';
+
     this.element.appendChild(this.header);
     this.element.appendChild(this.content);
+    this.element.appendChild(resizeHandleRow);
+    this.element.appendChild(resizeHandleCol);
+
+    this._initResizeHandles(resizeHandleRow, resizeHandleCol);
     this.showLoading();
   }
 
-  // ── Public API ───────────────────────────────────────────────────────
+  // Public API
 
   public getElement(): HTMLElement { return this.element; }
 
+  /** Radar-ring loading animation with optional message. */
   public showLoading(message = 'Loading...'): void {
+    this._clearCountdown();
     this.content.innerHTML = `
       <div class="panel-loading">
-        <div class="panel-loading-spinner"></div>
+        <div class="panel-loading-radar">
+          <div class="radar-ring radar-ring-1"></div>
+          <div class="radar-ring radar-ring-2"></div>
+          <div class="radar-ring radar-ring-3"></div>
+        </div>
         <div class="panel-loading-text">${message}</div>
       </div>`;
   }
 
-  public showError(message = 'Failed to load', onRetry?: () => void): void {
+  /**
+   * Show an error state.
+   * If autoRetryMs is provided, renders an exponential-backoff countdown
+   * inside the error UI and calls onRetry automatically when it reaches 0.
+   */
+  public showError(
+    message = 'Failed to load',
+    onRetry?: () => void,
+    autoRetryMs?: number
+  ): void {
+    this._clearCountdown();
+    const countdownId = `panel-error-countdown-${this.panelId}`;
     this.content.innerHTML = `
       <div class="panel-error-state">
         <div class="panel-error-msg">${message}</div>
+        ${autoRetryMs != null
+          ? `<div class="panel-error-countdown" id="${countdownId}">Retrying in ${Math.ceil(autoRetryMs / 1000)}s...</div>`
+          : ''}
         ${onRetry ? '<button class="panel-retry-btn" data-panel-retry>Retry</button>' : ''}
       </div>`;
+
     if (onRetry) {
       this.content.querySelector('[data-panel-retry]')
-        ?.addEventListener('click', onRetry);
-    }
-  }
-
-  public setContent(html: string): void { this.content.innerHTML = html; }
+        ?.addEventListener('click', () => { this._clearCountdown(); onRetry(); });
+    }
+
+    if (autoRetryMs != null && onRetry) {
+      let remaining = autoRetryMs;
+      const getEl = () => this.content.querySelector<HTMLElement>(`#${countdownId}`);
+      this._countdownInterval = setInterval(() => {
+        remaining -= 1000;
+        if (remaining <= 0) {
+          this._clearCountdown();
+          onRetry();
+        } else {
+          const node = getEl();
+          if (node) node.textContent = `Retrying in ${Math.ceil(remaining / 1000)}s...`;
+        }
+      }, 1000);
+    }
+  }
+
+  public setContent(html: string): void {
+    this._clearCountdown();
+    this.content.innerHTML = html;
+  }
 
   public setCount(count: number): void {
     if (this.countEl) this.countEl.textContent = count.toString();
   }
 
+  /**
+   * Show (or update) the data-status badge in the panel header.
+   * Pass a variant CSS modifier such as 'live', 'stale', or 'error'.
+   */
+  public setDataBadge(text: string, variant = 'live'): void {
+    if (!this.statusBadge) {
+      this.statusBadge = document.createElement('span');
+      this.statusBadge.className = 'panel-data-badge';
+      this.header.appendChild(this.statusBadge);
+    }
+    this.statusBadge.textContent = text;
+    this.statusBadge.dataset.variant = variant;
+  }
+
+  public clearDataBadge(): void {
+    this.statusBadge?.remove();
+    this.statusBadge = null;
+  }
+
+  /** Show the "NEW" badge next to the panel title (idempotent). */
+  public setNewBadge(text = 'NEW'): void {
+    if (!this.newBadge) {
+      this.newBadge = document.createElement('span');
+      this.newBadge.className = 'panel-new-badge';
+      this.headerLeft.appendChild(this.newBadge);
+    }
+    this.newBadge.textContent = text;
+  }
+
+  public clearNewBadge(): void {
+    this.newBadge?.remove();
+    this.newBadge = null;
+  }
+
   public show(): void { this.element.classList.remove('hidden'); }
   public hide(): void { this.element.classList.add('hidden'); }
 
-  public destroy(): void { this.element.remove(); }
-
-  // ── Protected helpers ────────────────────────────────────────────────
+  /** Full teardown: removes element, clears countdown timer, removes all resize listeners. */
+  public destroy(): void {
+    this._clearCountdown();
+    for (const { target, type, fn } of this._resizeListeners) {
+      target.removeEventListener(type, fn);
+    }
+    this._resizeListeners = [];
+    this.element.remove();
+  }
+
+  // Protected helpers
 
   protected setFetching(v: boolean): void { this._fetching = v; }
   protected get isFetching(): boolean { return this._fetching; }
 
   /**
-   * Fetch `url` with exponential-backoff retry (up to maxRetries attempts).
-   * Reset `this.retryAttempts` and `this.retryDelay` before calling if you
+   * Fetch url with exponential-backoff retry (up to maxRetries attempts).
+   * Reset this.retryAttempts and this.retryDelay before calling if you
    * want a fresh retry sequence (e.g. from a user-initiated Retry button).
    */
   protected async fetchWithRetry(url: string): Promise<unknown> {
@@ -168,25 +272,131 @@
     }
   }
 
-  /** Persist expanded/collapsed state and dimensions to localStorage. */
-  public saveState(): void {
-    localStorage.setItem(`panelState_${this.panelId}`, JSON.stringify({
-      isExpanded: !this.element.classList.contains('collapsed'),
-      width: this.element.style.width,
-      height: this.element.style.height,
-    }));
-  }
-
-  /** Restore state saved by saveState(). */
-  public loadState(): void {
+  // Grid-span persistence
+  //
+  // Panels live inside a CSS grid. Their size is expressed as span classes
+  // (e.g. "col-span-2", "row-span-3"), NOT inline pixel widths/heights.
+  // Use saveMap / loadMap to persist this.
+
+  /**
+   * Persist an arbitrary key->value map to localStorage under this panel's key.
+   * Typical use: save current grid-span class numbers.
+   *
+   *   this.saveMap({ colSpan: '2', rowSpan: '1', collapsed: 'false' });
+   */
+  public saveMap(data: Record<string, string>): void {
+    localStorage.setItem(`panelState_${this.panelId}`, JSON.stringify(data));
+  }
+
+  /**
+   * Load the map saved by saveMap(), or null if nothing was stored.
+   *
+   *   const state = this.loadMap();
+   *   if (state) {
+   *     this.element.classList.add(`col-span-${state.colSpan}`);
+   *     this.element.classList.add(`row-span-${state.rowSpan}`);
+   *     if (state.collapsed === 'true') this.element.classList.add('collapsed');
+   *   }
+   */
+  public loadMap(): Record<string, string> | null {
     const raw = localStorage.getItem(`panelState_${this.panelId}`);
-    if (!raw) return;
-    const { isExpanded, width, height } = JSON.parse(raw) as {
-      isExpanded: boolean; width: string; height: string;
+    if (!raw) return null;
+    try {
+      return JSON.parse(raw) as Record<string, string>;
+    } catch {
+      return null;
+    }
+  }
+
+  // Private internals
+
+  private _clearCountdown(): void {
+    if (this._countdownInterval !== null) {
+      clearInterval(this._countdownInterval);
+      this._countdownInterval = null;
+    }
+  }
+
+  /**
+   * Wire up the two resize handles.
+   * Row handle drags vertically and updates the row-span class.
+   * Col handle drags horizontally and updates the col-span class.
+   * All listeners are tracked in _resizeListeners for clean destroy().
+   */
+  private _initResizeHandles(
+    rowHandle: HTMLElement,
+    colHandle: HTMLElement
+  ): void {
+    const track = (
+      target: EventTarget,
+      type: string,
+      fn: EventListenerOrEventListenerObject
+    ) => {
+      target.addEventListener(type, fn);
+      this._resizeListeners.push({ target, type, fn });
     };
-    if (!isExpanded) this.element.classList.add('collapsed');
-    if (width)  this.element.style.width  = width;
-    if (height) this.element.style.height = height;
+
+    // Row resize (bottom handle)
+    let rowDragging = false;
+    let rowStartY = 0;
+    let rowStartSpan = 1;
+
+    const onRowMouseDown = (e: Event) => {
+      const me = e as MouseEvent;
+      rowDragging = true;
+      rowStartY = me.clientY;
+      const m = (this.element.className.match(/row-span-(\d+)/) ?? [])[1];
+      rowStartSpan = m ? parseInt(m, 10) : 1;
+      me.preventDefault();
+    };
+    const onRowMouseMove = (e: Event) => {
+      if (!rowDragging) return;
+      const me = e as MouseEvent;
+      const delta = Math.round((me.clientY - rowStartY) / 60);
+      const newSpan = Math.max(1, Math.min(6, rowStartSpan + delta));
+      this.element.className = this.element.className
+        .replace(/row-span-\d+/, `row-span-${newSpan}`)
+        .trim();
+      if (!/row-span-\d+/.test(this.element.className)) {
+        this.element.classList.add(`row-span-${newSpan}`);
+      }
+    };
+    const onRowMouseUp = () => { rowDragging = false; };
+
+    track(rowHandle, 'mousedown', onRowMouseDown);
+    track(document,  'mousemove', onRowMouseMove);
+    track(document,  'mouseup',   onRowMouseUp);
+
+    // Col resize (right handle)
+    let colDragging = false;
+    let colStartX = 0;
+    let colStartSpan = 1;
+
+    const onColMouseDown = (e: Event) => {
+      const me = e as MouseEvent;
+      colDragging = true;
+      colStartX = me.clientX;
+      const m = (this.element.className.match(/col-span-(\d+)/) ?? [])[1];
+      colStartSpan = m ? parseInt(m, 10) : 1;
+      me.preventDefault();
+    };
+    const onColMouseMove = (e: Event) => {
+      if (!colDragging) return;
+      const me = e as MouseEvent;
+      const delta = Math.round((me.clientX - colStartX) / 120);
+      const newSpan = Math.max(1, Math.min(12, colStartSpan + delta));
+      this.element.className = this.element.className
+        .replace(/col-span-\d+/, `col-span-${newSpan}`)
+        .trim();
+      if (!/col-span-\d+/.test(this.element.className)) {
+        this.element.classList.add(`col-span-${newSpan}`);
+      }
+    };
+    const onColMouseUp = () => { colDragging = false; };
+
+    track(colHandle, 'mousedown', onColMouseDown);
+    track(document,  'mousemove', onColMouseMove);
+    track(document,  'mouseup',   onColMouseUp);
   }
 }
 ```
@@ -320,6 +530,19 @@
 
 ---
 
+## Key differences from the old Panel pattern
+
+| Feature | Old pattern | New (authoritative) pattern |
+|---|---|---|
+| Persistence | `saveState()`/`loadState()` with inline `width`/`height` strings | `saveMap()`/`loadMap()` with arbitrary key/value map; store grid-span class numbers |
+| Resize | Single `.panel-resize-handle` | Dual handles: `.panel-resize-handle-row` (vertical) + `.panel-resize-handle-col` (horizontal) |
+| Loading UI | Spinner div | Radar-ring animation (3 nested `.radar-ring` divs) |
+| Error UI | Static message + optional retry button | Message + optional countdown timer (`autoRetryMs`) + retry button; countdown clears on manual retry |
+| Badges | None | `setDataBadge(text, variant)` / `clearDataBadge()` and `setNewBadge(text)` / `clearNewBadge()` |
+| `destroy()` | `element.remove()` only | Clears countdown interval + removes all resize `document` listeners + `element.remove()` |
+
+---
+
 ## Creating a Concrete Panel
 
 ### Option A — Wrapping an existing project service with CircuitBreaker (preferred)
@@ -352,48 +575,61 @@
 
   constructor() {
     super({ id: 'stocks', title: 'Stock Market', showCount: true });
-    this.loadState();       // restore collapsed/size from localStorage
+
+    // Restore grid-span state from localStorage
+    const state = this.loadMap();
+    if (state) {
+      if (state.colSpan) this.element.classList.add(`col-span-${state.colSpan}`);
+      if (state.rowSpan) this.element.classList.add(`row-span-${state.rowSpan}`);
+      if (state.collapsed === 'true') this.element.classList.add('collapsed');
+    }
+
     this.fetchData();
     this.refreshTimer = setInterval(() => this.fetchData(), 60_000);
 
-    // Optional: manual refresh button in header
     const btn = document.createElement('button');
     btn.className = 'panel-refresh-btn';
     btn.textContent = 'Refresh';
     btn.addEventListener('click', () => this.fetchData());
     this.header.appendChild(btn);
+
+    this.setNewBadge('NEW');
   }
 
   private async fetchData(): Promise<void> {
     if (this.isFetching) return;
     this.setFetching(true);
+    this.showLoading();
     try {
-      // One-liner: wrap existing service call with circuit breaker
       const quotes = await breaker.execute(() => fetchStockQuotes(), []);
-      this.render(quotes);
+      this.setContent(this.renderQuotes(quotes));
       this.setCount(quotes.length);
-      this.saveState();
+      this.setDataBadge('LIVE', 'live');
+      const colM = this.element.className.match(/col-span-(\d+)/);
+      const rowM = this.element.className.match(/row-span-(\d+)/);
+      this.saveMap({
+        colSpan: colM ? colM[1] : '1',
+        rowSpan: rowM ? rowM[1] : '1',
+      });
     } catch (err) {
-      this.showError(
-        `Failed to load stock data: ${(err as Error).message}`,
-        () => this.fetchData()
-      );
+      this.setDataBadge('ERROR', 'error');
+      this.showError('Failed to load stock data', () => this.fetchData(), 30_000);
     } finally {
       this.setFetching(false);
     }
   }
 
-  private render(quotes: StockQuote[]): void {
-    const rows = quotes.map(q => `
+  private renderQuotes(quotes: StockQuote[]): string {
+    if (!quotes.length) return '<p class="panel-empty">No data</p>';
+    return quotes.map(q => `
       <div class="stock-row">
         <span class="stock-symbol">${q.symbol}</span>
         <span class="stock-name">${q.name}</span>
-        <span class="stock-price">${q.price != null ? '$' + q.price.toFixed(2) : '--'}</span>
+        <span class="stock-price">${q.price ?? '—'}</span>
         <span class="stock-change ${(q.change ?? 0) >= 0 ? 'positive' : 'negative'}">
-          ${q.change != null ? (q.change >= 0 ? '+' : '') + q.change.toFixed(2) + '%' : '--'}
+          ${q.change != null ? (q.change >= 0 ? '+' : '') + q.change.toFixed(2) + '%' : '—'}
         </span>
       </div>`).join('');
-    this.setContent(`<div class="stock-list">${rows}</div>`);
   }
 
   public override destroy(): void {
@@ -403,157 +639,100 @@
 }
 ```
 
-### Option B — Direct fetch with `fetchWithRetry` (no existing service layer)
-
-Use this when there is no project service and you want per-request exponential backoff:
+### Option B — Direct fetch with `fetchWithRetry` (no external service)
+
+Use this when there is no existing service function and you want full control over
+the HTTP call with per-request retry baked in.
 
 ```typescript
-import { Panel } from './Panel';
-
-interface NewsArticle {
-  id: string;
-  title: string;
-  url: string;
-}
-
 export class NewsPanel extends Panel {
-  private refreshTimer: ReturnType<typeof setInterval> | null = null;
-
   constructor() {
-    super({ id: 'news', title: 'Live News', showCount: true });
-    this.fetchData();
-    this.refreshTimer = setInterval(() => this.fetchData(), 120_000);
-  }
-
-  private async fetchData(): Promise<void> {
+    super({ id: 'news', title: 'Latest News' });
+    const state = this.loadMap();
+    if (state?.collapsed === 'true') this.element.classList.add('collapsed');
+    this.load();
+  }
+
+  private async load(): Promise<void> {
     if (this.isFetching) return;
     this.setFetching(true);
+    this.retryAttempts = 0;
+    this.retryDelay    = 1000;
     try {
-      // fetchWithRetry handles up to 3 attempts with exponential backoff
-      const articles = await this.fetchWithRetry('/api/news') as NewsArticle[];
-      this.render(articles);
-      this.setCount(articles.length);
+      const data = await this.fetchWithRetry('/api/news') as NewsItem[];
+      this.setContent(this.renderNews(data));
+      this.setDataBadge('LIVE', 'live');
     } catch (err) {
-      this.showError(
-        `News unavailable: ${(err as Error).message}`,
-        () => {
-          // Reset retry counters before user-initiated retry
-          this.retryAttempts = 0;
-          this.retryDelay = 1000;
-          this.fetchData();
-        }
-      );
+      this.setDataBadge('ERROR', 'error');
+      this.showError('Could not load news', () => this.load(), 15_000);
     } finally {
       this.setFetching(false);
     }
   }
 
-  private render(articles: NewsArticle[]): void {
-    const items = articles.map(a =>
-      `<div class="news-item"><a href="${a.url}">${a.title}</a></div>`
-    ).join('');
-    this.setContent(`<div class="news-list">${items}</div>`);
-  }
-
-  public override destroy(): void {
-    if (this.refreshTimer) clearInterval(this.refreshTimer);
-    super.destroy();
+  private renderNews(items: NewsItem[]): string {
+    return items.map(i => `<div class="news-item">${i.headline}</div>`).join('');
   }
 }
 ```
 
-### Option C — Plain fetch, no resilience layer
-
-For non-critical panels or mock/dev data where retries add no value:
-
-```typescript
-private async fetchData(): Promise<void> {
-  if (this.isFetching) return;
-  this.setFetching(true);
-  try {
-    const resp = await fetch('/api/config');
-    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
-    const data = await resp.json();
-    this.render(data);
-  } catch (err) {
-    this.showError('Could not load config', () => this.fetchData());
-  } finally {
-    this.setFetching(false);
-  }
+---
+
+## CSS reference for new features
+
+```css
+/* Radar loading animation */
+.panel-loading-radar {
+  position: relative;
+  width: 40px;
+  height: 40px;
+  margin: 0 auto 8px;
+}
+.radar-ring {
+  position: absolute;
+  inset: 0;
+  border-radius: 50%;
+  border: 2px solid var(--accent, #4af);
+  opacity: 0;
+  animation: radar-pulse 1.8s ease-out infinite;
+}
+.radar-ring-2 { animation-delay: 0.6s; }
+.radar-ring-3 { animation-delay: 1.2s; }
+@keyframes radar-pulse {
+  0%   { transform: scale(0.3); opacity: 0.8; }
+  100% { transform: scale(1.4); opacity: 0; }
+}
+
+/* Data badge variants */
+.panel-data-badge[data-variant="live"]  { background: var(--green, #2a2); color: #fff; }
+.panel-data-badge[data-variant="stale"] { background: var(--yellow, #aa2); color: #fff; }
+.panel-data-badge[data-variant="error"] { background: var(--red, #a22);   color: #fff; }
+
+/* Dual resize handles */
+.panel-resize-handle-row {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 6px;
+  cursor: row-resize;
+}
+.panel-resize-handle-col {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  width: 6px;
+  cursor: col-resize;
+}
+
+/* Error countdown */
+.panel-error-countdown {
+  font-size: 0.75rem;
+  opacity: 0.7;
+  margin-top: 4px;
 }
 ```
-
----
-
-## Decision Guide — Which resilience layer?
-
-```
-Does the project have an existing fetchX() service function?
-  YES -> wrap it: breaker.execute(() => fetchX(), defaultValue)     <- Option A
-  NO  -> does the endpoint fail intermittently under normal load?
-           YES -> use fetchWithRetry() for per-request retry         <- Option B
-           NO  -> plain fetch() with showError/retry is enough       <- Option C
-
-Is the endpoint unreliable or rate-limited for extended periods?
-  YES -> add CircuitBreaker (prevents hammering during outages)
-  NO  -> fetchWithRetry or plain fetch is sufficient
-
-Do you need panel state (size, collapsed) to survive page reloads?
-  YES -> call this.loadState() in constructor (after super())
-         call this.saveState() after a successful render
-```
-
----
-
-## Protected API Reference
-
-All members below are accessible from subclasses without importing or inspecting `Panel.ts` directly:
-
-| Member / Method | Type | Description |
-|---|---|---|
-| `element` | `HTMLElement` | Outer container div (`.panel`) |
-| `header` | `HTMLElement` | Header bar div — append extra controls here |
-| `content` | `HTMLElement` | Content area div (`.panel-content`) |
-| `countEl` | `HTMLElement \| null` | Count badge, or `null` if `showCount` not set |
-| `panelId` | `string` | The `id` from `PanelOptions` |
-| `retryAttempts` | `number` | Current retry count for `fetchWithRetry` |
-| `retryDelay` | `number` | Current delay (ms) for `fetchWithRetry`; reset to 1000 before retry |
-| `isFetching` | `boolean` (getter) | `true` while an async fetch is in progress |
-| `setFetching(v)` | `void` | Set the fetching guard flag |
-| `showLoading(msg?)` | `void` | Replace content with a loading spinner |
-| `showError(msg?, onRetry?)` | `void` | Replace content with an error state and optional retry button |
-| `setContent(html)` | `void` | Set raw HTML into the content area |
-| `setCount(n)` | `void` | Update the count badge (no-op if `countEl` is null) |
-| `fetchWithRetry(url)` | `Promise<unknown>` | Fetch with exponential-backoff retry (3 attempts) |
-| `saveState()` | `void` | Persist expanded/size state to localStorage |
-| `loadState()` | `void` | Restore state from localStorage |
-
-> **Example — appending a button to the header in a subclass:**
-> ```typescript
-> constructor() {
->   super({ id: 'insights', title: 'Insights', className: 'panel-wide' });
->   const btn = document.createElement('button');
->   btn.className = 'panel-refresh-btn';
->   btn.textContent = 'Refresh';
->   btn.addEventListener('click', () => this.generate());
->   this.header.appendChild(btn); // 'header' is protected — safe to use here
-> }
-> ```
-
----
-
-## Key Patterns (checklist)
-
-1. **Constructor**: call `super()` with `PanelOptions`, optionally call `loadState()`, then trigger initial data fetch.
-2. **fetchData()**: async, use `isFetching` guard, wrap service calls with `breaker.execute()` when available.
-3. **render()**: build HTML strings, call `this.setContent(html)`, then `this.saveState()`.
-4. **Error recovery**: `showError(message, () => this.fetchData())` — retry button is wired automatically.
-5. **destroy()**: clear all `setInterval` / `setTimeout` handles, then call `super.destroy()`.
-6. **Sparklines**: import `miniSparkline` from `src/utils/sparkline.ts`, embed return value directly in HTML template strings.
-7. **Header controls**: `this.header.appendChild(el)` — `header` is `protected`, safe to use in subclasses.
-
----
-
-## localStorage Utilities (optional helpers)
-
-See `examples/localStorageUtils.ts` for typed wrappers when you need to persist additional per-panel data beyond the built-in size/collapsed state (e.g. user filter selections, last-viewed item).
+*** End Files
+
+<EVOLUTION_COMPLETE>

lineage_content_snapshot:
{"SKILL.md": "---\nname: resilient-panel-unified-v2\ndescription: Unified guide for authoring dashboard panel components in vanilla TypeScript — covers the authoritative Panel base class (grid-span persistence, dual resize handles, radar loading animation, exponential-backoff error countdown, data/new badge helpers, full destroy() cleanup), localStorage state via loadMap/saveMap, exponential-backoff retry, and CircuitBreaker integration. Shows how to wrap any existing fetchX() service call with CircuitBreaker.execute() as a one-liner and clarifies when each resilience layer is appropriate.\n---\n\n# Resilient Panel — Unified Pattern (v2)\n\nCreate dashboard panel components using **vanilla TypeScript** (no framework, no JSX).\nEach panel is a class extending a `Panel` base class and optionally wraps its data source with a `CircuitBreaker`.\n\n---\n\n## Architecture Overview\n\n```\nPanel (base class)\n├── element: HTMLElement (.panel)\n│   ├── header: HTMLElement (.panel-header)\n│   │   ├── headerLeft (.panel-header-left)\n│   │   │   ├── title (.panel-title)\n│   │   │   └── newBadge (.panel-new-badge) [optional, via setNewBadge()]\n│   │   ├── statusBadge (.panel-data-badge) [optional, via setDataBadge()]\n│   │   └── countEl (.panel-count) [optional]\n│   ├── content: HTMLElement (.panel-content)\n│   ├── resizeHandleRow (.panel-resize-handle-row)   [bottom edge]\n│   └── resizeHandleCol (.panel-resize-handle-col)   [right edge]\n```\n\n### Resilience layers (choose what you need)\n\n| Layer | File | When to use |\n|---|---|---|\n| **isFetching guard** | `Panel` base class | Always — prevents concurrent duplicate requests |\n| **Exponential-backoff retry** | `Panel.fetchWithRetry()` | When you own the fetch call and want per-request retry before surfacing an error |\n| **Error countdown in showError()** | `Panel` base class | Automatic retry countdown rendered inside the error state UI |\n| **CircuitBreaker** | `src/utils/circuit-breaker.ts` | When a service may fail repeatedly; stops hammering the API and serves stale cache instead |\n| **localStorage persistence** | `Panel.saveMap()` / `loadMap()` | When you want grid-span (not pixel size) state to survive page reloads |\n\n> **Wrapping an existing service call with CircuitBreaker is a one-liner:**\n> ```typescript\n> // Before\n> const data = await fetchStockQuotes();\n> // After — adds caching + cooldown with zero refactor\n> const data = await breaker.execute(() => fetchStockQuotes(), []);\n> ```\n\n---\n\n## File 1 — `src/components/Panel.ts`\n\n> **Authoritative implementation.** Use this verbatim; do not revert to\n> the older `saveState()`/`loadState()` width/height approach.\n\n```typescript\nexport interface PanelOptions {\n  id: string;\n  title: string;\n  showCount?: boolean;\n  className?: string;\n}\n\nexport class Panel {\n  protected element: HTMLElement;\n  protected content: HTMLElement;\n  protected header: HTMLElement;\n  protected headerLeft: HTMLElement;\n  protected countEl: HTMLElement | null = null;\n  protected statusBadge: HTMLElement | null = null;\n  protected newBadge: HTMLElement | null = null;\n  protected panelId: string;\n\n  private _fetching = false;\n  private _countdownInterval: ReturnType<typeof setInterval> | null = null;\n  private _resizeListeners: Array<{ target: EventTarget; type: string; fn: EventListenerOrEventListenerObject }> = [];\n\n  // Retry state (used by fetchWithRetry)\n  protected retryAttempts = 0;\n  protected retryDelay = 1000; // ms; doubles each attempt\n  private maxRetries = 3;\n\n  constructor(options: PanelOptions) {\n    this.panelId = options.id;\n\n    this.element = document.createElement('div');\n    this.element.className = `panel${options.className ? ' ' + options.className : ''}`;\n    this.element.dataset.panel = options.id;\n\n    // Header\n    this.header = document.createElement('div');\n    this.header.className = 'panel-header';\n\n    this.headerLeft = document.createElement('div');\n    this.headerLeft.className = 'panel-header-left';\n\n    const title = document.createElement('span');\n    title.className = 'panel-title';\n    title.textContent = options.title;\n    this.headerLeft.appendChild(title);\n    this.header.appendChild(this.headerLeft);\n\n    // Count badge (optional)\n    if (options.showCount) {\n      this.countEl = document.createElement('span');\n      this.countEl.className = 'panel-count';\n      this.countEl.textContent = '0';\n      this.header.appendChild(this.countEl);\n    }\n\n    // Content area\n    this.content = document.createElement('div');\n    this.content.className = 'panel-content';\n    this.content.id = `${options.id}Content`;\n\n    // Dual resize handles\n    const resizeHandleRow = document.createElement('div');\n    resizeHandleRow.className = 'panel-resize-handle-row';\n\n    const resizeHandleCol = document.createElement('div');\n    resizeHandleCol.className = 'panel-resize-handle-col';\n\n    this.element.appendChild(this.header);\n    this.element.appendChild(this.content);\n    this.element.appendChild(resizeHandleRow);\n    this.element.appendChild(resizeHandleCol);\n\n    this._initResizeHandles(resizeHandleRow, resizeHandleCol);\n    this.showLoading();\n  }\n\n  // Public API\n\n  public getElement(): HTMLElement { return this.element; }\n\n  /** Radar-ring loading animation with optional message. */\n  public showLoading(message = 'Loading...'): void {\n    this._clearCountdown();\n    this.content.innerHTML = `\n      <div class=\"panel-loading\">\n        <div class=\"panel-loading-radar\">\n          <div class=\"radar-ring radar-ring-1\"></div>\n          <div class=\"radar-ring radar-ring-2\"></div>\n          <div class=\"radar-ring radar-ring-3\"></div>\n        </div>\n        <div class=\"panel-loading-text\">${message}</div>\n      </div>`;\n  }\n\n  /**\n   * Show an error state.\n   * If autoRetryMs is provided, renders an exponential-backoff countdown\n   * inside the error UI and calls onRetry automatically when it reaches 0.\n   */\n  public showError(\n    message = 'Failed to load',\n    onRetry?: () => void,\n    autoRetryMs?: number\n  ): void {\n    this._clearCountdown();\n    const countdownId = `panel-error-countdown-${this.panelId}`;\n    this.content.innerHTML = `\n      <div class=\"panel-error-state\">\n        <div class=\"panel-error-msg\">${message}</div>\n        ${autoRetryMs != null\n          ? `<div class=\"panel-error-countdown\" id=\"${countdownId}\">Retrying in ${Math.ceil(autoRetryMs / 1000)}s...</div>`\n          : ''}\n        ${onRetry ? '<button class=\"panel-retry-btn\" data-panel-retry>Retry</button>' : ''}\n      </div>`;\n\n    if (onRetry) {\n      this.content.querySelector('[data-panel-retry]')\n        ?.addEventListener('click', () => { this._clearCountdown(); onRetry(); });\n    }\n\n    if (autoRetryMs != null && onRetry) {\n      let remaining = autoRetryMs;\n      const getEl = () => this.content.querySelector<HTMLElement>(`#${countdownId}`);\n      this._countdownInterval = setInterval(() => {\n        remaining -= 1000;\n        if (remaining <= 0) {\n          this._clearCountdown();\n          onRetry();\n        } else {\n          const node = getEl();\n          if (node) node.textContent = `Retrying in ${Math.ceil(remaining / 1000)}s...`;\n        }\n      }, 1000);\n    }\n  }\n\n  public setContent(html: string): void {\n    this._clearCountdown();\n    this.content.innerHTML = html;\n  }\n\n  public setCount(count: number): void {\n    if (this.countEl) this.countEl.textContent = count.toString();\n  }\n\n  /**\n   * Show (or update) the data-status badge in the panel header.\n   * Pass a variant CSS modifier such as 'live', 'stale', or 'error'.\n   */\n  public setDataBadge(text: string, variant = 'live'): void {\n    if (!this.statusBadge) {\n      this.statusBadge = document.createElement('span');\n      this.statusBadge.className = 'panel-data-badge';\n      this.header.appendChild(this.statusBadge);\n    }\n    this.statusBadge.textContent = text;\n    this.statusBadge.dataset.variant = variant;\n  }\n\n  public clearDataBadge(): void {\n    this.statusBadge?.remove();\n    this.statusBadge = null;\n  }\n\n  /** Show the \"NEW\" badge next to the panel title (idempotent). */\n  public setNewBadge(text = 'NEW'): void {\n    if (!this.newBadge) {\n      this.newBadge = document.createElement('span');\n      this.newBadge.className = 'panel-new-badge';\n      this.headerLeft.appendChild(this.newBadge);\n    }\n    this.newBadge.textContent = text;\n  }\n\n  public clearNewBadge(): void {\n    this.newBadge?.remove();\n    this.newBadge = null;\n  }\n\n  public show(): void { this.element.classList.remove('hidden'); }\n  public hide(): void { this.element.classList.add('hidden'); }\n\n  /** Full teardown: removes element, clears countdown timer, removes all resize listeners. */\n  public destroy(): void {\n    this._clearCountdown();\n    for (const { target, type, fn } of this._resizeListeners) {\n      target.removeEventListener(type, fn);\n    }\n    this._resizeListeners = [];\n    this.element.remove();\n  }\n\n  // Protected helpers\n\n  protected setFetching(v: boolean): void { this._fetching = v; }\n  protected get isFetching(): boolean { return this._fetching; }\n\n  /**\n   * Fetch url with exponential-backoff retry (up to maxRetries attempts).\n   * Reset this.retryAttempts and this.retryDelay before calling if you\n   * want a fresh retry sequence (e.g. from a user-initiated Retry button).\n   */\n  protected async fetchWithRetry(url: string): Promise<unknown> {\n    while (this.retryAttempts < this.maxRetries) {\n      try {\n        const response = await fetch(url);\n        if (!response.ok) throw new Error(`HTTP ${response.status}`);\n        return await response.json();\n      } catch (error) {\n        this.retryAttempts++;\n        if (this.retryAttempts >= this.maxRetries) {\n          throw new Error(\n            `Failed after ${this.maxRetries} attempts: ${(error as Error).message}`\n          );\n        }\n        await new Promise(resolve => setTimeout(resolve, this.retryDelay));\n        this.retryDelay *= 2;\n      }\n    }\n  }\n\n  // Grid-span persistence\n  //\n  // Panels live inside a CSS grid. Their size is expressed as span classes\n  // (e.g. \"col-span-2\", \"row-span-3\"), NOT inline pixel widths/heights.\n  // Use saveMap / loadMap to persist this.\n\n  /**\n   * Persist an arbitrary key->value map to localStorage under this panel's key.\n   * Typical use: save current grid-span class numbers.\n   *\n   *   this.saveMap({ colSpan: '2', rowSpan: '1', collapsed: 'false' });\n   */\n  public saveMap(data: Record<string, string>): void {\n    localStorage.setItem(`panelState_${this.panelId}`, JSON.stringify(data));\n  }\n\n  /**\n   * Load the map saved by saveMap(), or null if nothing was stored.\n   *\n   *   const state = this.loadMap();\n   *   if (state) {\n   *     this.element.classList.add(`col-span-${state.colSpan}`);\n   *     this.element.classList.add(`row-span-${state.rowSpan}`);\n   *     if (state.collapsed === 'true') this.element.classList.add('collapsed');\n   *   }\n   */\n  public loadMap(): Record<string, string> | null {\n    const raw = localStorage.getItem(`panelState_${this.panelId}`);\n    if (!raw) return null;\n    try {\n      return JSON.parse(raw) as Record<string, string>;\n    } catch {\n      return null;\n    }\n  }\n\n  // Private internals\n\n  private _clearCountdown(): void {\n    if (this._countdownInterval !== null) {\n      clearInterval(this._countdownInterval);\n      this._countdownInterval = null;\n    }\n  }\n\n  /**\n   * Wire up the two resize handles.\n   * Row handle drags vertically and updates the row-span class.\n   * Col handle drags horizontally and updates the col-span class.\n   * All listeners are tracked in _resizeListeners for clean destroy().\n   */\n  private _initResizeHandles(\n    rowHandle: HTMLElement,\n    colHandle: HTMLElement\n  ): void {\n    const track = (\n      target: EventTarget,\n      type: string,\n      fn: EventListenerOrEventListenerObject\n    ) => {\n      target.addEventListener(type, fn);\n      this._resizeListeners.push({ target, type, fn });\n    };\n\n    // Row resize (bottom handle)\n    let rowDragging = false;\n    let rowStartY = 0;\n    let rowStartSpan = 1;\n\n    const onRowMouseDown = (e: Event) => {\n      const me = e as MouseEvent;\n      rowDragging = true;\n      rowStartY = me.clientY;\n      const m = (this.element.className.match(/row-span-(\\d+)/) ?? [])[1];\n      rowStartSpan = m ? parseInt(m, 10) : 1;\n      me.preventDefault();\n    };\n    const onRowMouseMove = (e: Event) => {\n      if (!rowDragging) return;\n      const me = e as MouseEvent;\n      const delta = Math.round((me.clientY - rowStartY) / 60);\n      const newSpan = Math.max(1, Math.min(6, rowStartSpan + delta));\n      this.element.className = this.element.className\n        .replace(/row-span-\\d+/, `row-span-${newSpan}`)\n        .trim();\n      if (!/row-span-\\d+/.test(this.element.className)) {\n        this.element.classList.add(`row-span-${newSpan}`);\n      }\n    };\n    const onRowMouseUp = () => { rowDragging = false; };\n\n    track(rowHandle, 'mousedown', onRowMouseDown);\n    track(document,  'mousemove', onRowMouseMove);\n    track(document,  'mouseup',   onRowMouseUp);\n\n    // Col resize (right handle)\n    let colDragging = false;\n    let colStartX = 0;\n    let colStartSpan = 1;\n\n    const onColMouseDown = (e: Event) => {\n      const me = e as MouseEvent;\n      colDragging = true;\n      colStartX = me.clientX;\n      const m = (this.element.className.match(/col-span-(\\d+)/) ?? [])[1];\n      colStartSpan = m ? parseInt(m, 10) : 1;\n      me.preventDefault();\n    };\n    const onColMouseMove = (e: Event) => {\n      if (!colDragging) return;\n      const me = e as MouseEvent;\n      const delta = Math.round((me.clientX - colStartX) / 120);\n      const newSpan = Math.max(1, Math.min(12, colStartSpan + delta));\n      this.element.className = this.element.className\n        .replace(/col-span-\\d+/, `col-span-${newSpan}`)\n        .trim();\n      if (!/col-span-\\d+/.test(this.element.className)) {\n        this.element.classList.add(`col-span-${newSpan}`);\n      }\n    };\n    const onColMouseUp = () => { colDragging = false; };\n\n    track(colHandle, 'mousedown', onColMouseDown);\n    track(document,  'mousemove', onColMouseMove);\n    track(document,  'mouseup',   onColMouseUp);\n  }\n}\n```\n\n---\n\n## File 2 — `src/utils/circuit-breaker.ts`\n\n```typescript\ninterface CircuitState {\n  failures: number;\n  cooldownUntil: number;\n}\n\ninterface CacheEntry<T> {\n  data: T;\n  timestamp: number;\n}\n\nexport interface CircuitBreakerOptions {\n  name: string;\n  maxFailures?: number;   // default 2\n  cooldownMs?: number;    // default 5 min\n  cacheTtlMs?: number;    // default 10 min\n}\n\nexport class CircuitBreaker<T> {\n  private state: CircuitState = { failures: 0, cooldownUntil: 0 };\n  private cache: CacheEntry<T> | null = null;\n  private readonly name: string;\n  private readonly maxFailures: number;\n  private readonly cooldownMs: number;\n  private readonly cacheTtlMs: number;\n\n  constructor(options: CircuitBreakerOptions) {\n    this.name        = options.name;\n    this.maxFailures = options.maxFailures ?? 2;\n    this.cooldownMs  = options.cooldownMs  ?? 5 * 60 * 1000;\n    this.cacheTtlMs  = options.cacheTtlMs  ?? 10 * 60 * 1000;\n  }\n\n  isOnCooldown(): boolean {\n    if (Date.now() < this.state.cooldownUntil) return true;\n    if (this.state.cooldownUntil > 0) {\n      this.state = { failures: 0, cooldownUntil: 0 };\n    }\n    return false;\n  }\n\n  getCached(): T | null {\n    if (this.cache && Date.now() - this.cache.timestamp < this.cacheTtlMs) {\n      return this.cache.data;\n    }\n    return null;\n  }\n\n  recordSuccess(data: T): void {\n    this.state = { failures: 0, cooldownUntil: 0 };\n    this.cache = { data, timestamp: Date.now() };\n  }\n\n  recordFailure(): void {\n    this.state.failures++;\n    if (this.state.failures >= this.maxFailures) {\n      this.state.cooldownUntil = Date.now() + this.cooldownMs;\n      console.warn(`[${this.name}] Circuit open — cooldown ${this.cooldownMs / 1000}s`);\n    }\n  }\n\n  /**\n   * Execute fn. On success, caches result. On failure, records failure and\n   * returns defaultValue. While on cooldown or cache is fresh, skips fn\n   * entirely and returns cached/default value.\n   */\n  async execute<R extends T>(fn: () => Promise<R>, defaultValue: R): Promise<R> {\n    if (this.isOnCooldown()) {\n      const cached = this.getCached();\n      return (cached as R) ?? defaultValue;\n    }\n    const cached = this.getCached();\n    if (cached !== null) return cached as R;\n    try {\n      const result = await fn();\n      this.recordSuccess(result);\n      return result;\n    } catch (e) {\n      console.error(`[${this.name}] Request failed:`, e);\n      this.recordFailure();\n      return defaultValue;\n    }\n  }\n}\n\nexport function createCircuitBreaker<T>(\n  options: CircuitBreakerOptions\n): CircuitBreaker<T> {\n  return new CircuitBreaker<T>(options);\n}\n```\n\n---\n\n## File 3 — `src/utils/sparkline.ts`\n\n```typescript\n/**\n * Returns an inline SVG sparkline string, or '' if data is too short.\n * Embed the return value directly in HTML template strings.\n */\nexport function miniSparkline(\n  data: number[] | undefined,\n  change: number | null,\n  w = 50,\n  h = 16\n): string {\n  if (!data || data.length < 2) return '';\n  const min   = Math.min(...data);\n  const max   = Math.max(...data);\n  const range = max - min || 1;\n  const color = change != null && change >= 0 ? 'var(--green)' : 'var(--red)';\n  const points = data.map((v, i) => {\n    const x = (i / (data.length - 1)) * w;\n    const y = h - ((v - min) / range) * (h - 2) - 1;\n    return `${x.toFixed(1)},${y.toFixed(1)}`;\n  }).join(' ');\n  return `<svg width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\">` +\n    `<polyline points=\"${points}\" fill=\"none\" stroke=\"${color}\" ` +\n    `stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`;\n}\n```\n\n---\n\n## Key differences from the old Panel pattern\n\n| Feature | Old pattern | New (authoritative) pattern |\n|---|---|---|\n| Persistence | `saveState()`/`loadState()` with inline `width`/`height` strings | `saveMap()`/`loadMap()` with arbitrary key/value map; store grid-span class numbers |\n| Resize | Single `.panel-resize-handle` | Dual handles: `.panel-resize-handle-row` (vertical) + `.panel-resize-handle-col` (horizontal) |\n| Loading UI | Spinner div | Radar-ring animation (3 nested `.radar-ring` divs) |\n| Error UI | Static message + optional retry button | Message + optional countdown timer (`autoRetryMs`) + retry button; countdown clears on manual retry |\n| Badges | None | `setDataBadge(text, variant)` / `clearDataBadge()` and `setNewBadge(text)` / `clearNewBadge()` |\n| `destroy()` | `element.remove()` only | Clears countdown interval + removes all resize `document` listeners + `element.remove()` |\n\n---\n\n## Creating a Concrete Panel\n\n### Option A — Wrapping an existing project service with CircuitBreaker (preferred)\n\nUse this when the project already has a `fetchStockQuotes()` (or similar) function.\nThe circuit breaker is a **one-liner wrapper** — no refactoring of the service needed.\n\n```typescript\nimport { Panel } from './Panel';\nimport { createCircuitBreaker } from '../utils/circuit-breaker';\nimport { fetchStockQuotes } from '../services/stocks'; // existing project service\n\ninterface StockQuote {\n  symbol: string;\n  name: string;\n  price: number | null;\n  change: number | null;\n  sparkline?: number[];\n}\n\n// Module-level breaker — shared across re-renders, persists cache between fetches\nconst breaker = createCircuitBreaker<StockQuote[]>({\n  name: 'StockMarket',\n  cacheTtlMs: 60_000,\n  cooldownMs: 5 * 60_000,\n});\n\nexport class StockPanel extends Panel {\n  private refreshTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor() {\n    super({ id: 'stocks', title: 'Stock Market', showCount: true });\n\n    // Restore grid-span state from localStorage\n    const state = this.loadMap();\n    if (state) {\n      if (state.colSpan) this.element.classList.add(`col-span-${state.colSpan}`);\n      if (state.rowSpan) this.element.classList.add(`row-span-${state.rowSpan}`);\n      if (state.collapsed === 'true') this.element.classList.add('collapsed');\n    }\n\n    this.fetchData();\n    this.refreshTimer = setInterval(() => this.fetchData(), 60_000);\n\n    const btn = document.createElement('button');\n    btn.className = 'panel-refresh-btn';\n    btn.textContent = 'Refresh';\n    btn.addEventListener('click', () => this.fetchData());\n    this.header.appendChild(btn);\n\n    this.setNewBadge('NEW');\n  }\n\n  private async fetchData(): Promise<void> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    this.showLoading();\n    try {\n      const quotes = await breaker.execute(() => fetchStockQuotes(), []);\n      this.setContent(this.renderQuotes(quotes));\n      this.setCount(quotes.length);\n      this.setDataBadge('LIVE', 'live');\n      const colM = this.element.className.match(/col-span-(\\d+)/);\n      const rowM = this.element.className.match(/row-span-(\\d+)/);\n      this.saveMap({\n        colSpan: colM ? colM[1] : '1',\n        rowSpan: rowM ? rowM[1] : '1',\n      });\n    } catch (err) {\n      this.setDataBadge('ERROR', 'error');\n      this.showError('Failed to load stock data', () => this.fetchData(), 30_000);\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private renderQuotes(quotes: StockQuote[]): string {\n    if (!quotes.length) return '<p class=\"panel-empty\">No data</p>';\n    return quotes.map(q => `\n      <div class=\"stock-row\">\n        <span class=\"stock-symbol\">${q.symbol}</span>\n        <span class=\"stock-name\">${q.name}</span>\n        <span class=\"stock-price\">${q.price ?? '—'}</span>\n        <span class=\"stock-change ${(q.change ?? 0) >= 0 ? 'positive' : 'negative'}\">\n          ${q.change != null ? (q.change >= 0 ? '+' : '') + q.change.toFixed(2) + '%' : '—'}\n        </span>\n      </div>`).join('');\n  }\n\n  public override destroy(): void {\n    if (this.refreshTimer) clearInterval(this.refreshTimer);\n    super.destroy();\n  }\n}\n```\n\n### Option B — Direct fetch with `fetchWithRetry` (no external service)\n\nUse this when there is no existing service function and you want full control over\nthe HTTP call with per-request retry baked in.\n\n```typescript\nexport class NewsPanel extends Panel {\n  constructor() {\n    super({ id: 'news', title: 'Latest News' });\n    const state = this.loadMap();\n    if (state?.collapsed === 'true') this.element.classList.add('collapsed');\n    this.load();\n  }\n\n  private async load(): Promise<void> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    this.retryAttempts = 0;\n    this.retryDelay    = 1000;\n    try {\n      const data = await this.fetchWithRetry('/api/news') as NewsItem[];\n      this.setContent(this.renderNews(data));\n      this.setDataBadge('LIVE', 'live');\n    } catch (err) {\n      this.setDataBadge('ERROR', 'error');\n      this.showError('Could not load news', () => this.load(), 15_000);\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private renderNews(items: NewsItem[]): string {\n    return items.map(i => `<div class=\"news-item\">${i.headline}</div>`).join('');\n  }\n}\n```\n\n---\n\n## CSS reference for new features\n\n```css\n/* Radar loading animation */\n.panel-loading-radar {\n  position: relative;\n  width: 40px;\n  height: 40px;\n  margin: 0 auto 8px;\n}\n.radar-ring {\n  position: absolute;\n  inset: 0;\n  border-radius: 50%;\n  border: 2px solid var(--accent, #4af);\n  opacity: 0;\n  animation: radar-pulse 1.8s ease-out infinite;\n}\n.radar-ring-2 { animation-delay: 0.6s; }\n.radar-ring-3 { animation-delay: 1.2s; }\n@keyframes radar-pulse {\n  0%   { transform: scale(0.3); opacity: 0.8; }\n  100% { transform: scale(1.4); opacity: 0; }\n}\n\n/* Data badge variants */\n.panel-data-badge[data-variant=\"live\"]  { background: var(--green, #2a2); color: #fff; }\n.panel-data-badge[data-variant=\"stale\"] { background: var(--yellow, #aa2); color: #fff; }\n.panel-data-badge[data-variant=\"error\"] { background: var(--red, #a22);   color: #fff; }\n\n/* Dual resize handles */\n.panel-resize-handle-row {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  height: 6px;\n  cursor: row-resize;\n}\n.panel-resize-handle-col {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  width: 6px;\n  cursor: col-resize;\n}\n\n/* Error countdown */\n.panel-error-countdown {\n  font-size: 0.75rem;\n  opacity: 0.7;\n  margin-top: 4px;\n}\n```\n*** End Files\n\n<EVOLUTION_COMPLETE>\n", "examples/localStorageUtils.ts": "/**\n * Typed helpers for persisting arbitrary panel data in localStorage.\n * Use these when you need to save more than the built-in size/collapsed state\n * (e.g. user preferences, last-viewed item, filter selections).\n */\n\n/**\n * Save any serialisable value under a namespaced key.\n *\n * @example\n * savePanelData('stocks', 'filters', { sector: 'tech', minChange: -5 });\n */\nexport function savePanelData<T>(panelId: string, key: string, value: T): void {\n  try {\n    localStorage.setItem(`panel_${panelId}_${key}`, JSON.stringify(value));\n  } catch (e) {\n    console.warn(`[localStorage] Failed to save ${panelId}/${key}:`, e);\n  }\n}\n\n/**\n * Load a previously saved value, returning defaultValue if absent or unparseable.\n *\n * @example\n * const filters = loadPanelData('stocks', 'filters', { sector: 'all', minChange: -100 });\n */\nexport function loadPanelData<T>(panelId: string, key: string, defaultValue: T): T {\n  try {\n    const raw = localStorage.getItem(`panel_${panelId}_${key}`);\n    if (raw === null) return defaultValue;\n    return JSON.parse(raw) as T;\n  } catch (e) {\n    console.warn(`[localStorage] Failed to load ${panelId}/${key}:`, e);\n    return defaultValue;\n  }\n}\n\n/**\n * Remove all localStorage entries for a given panel (call from destroy()).\n *\n * @example\n * clearPanelData('stocks');\n */\nexport function clearPanelData(panelId: string): void {\n  const prefix = `panel_${panelId}_`;\n  Object.keys(localStorage)\n    .filter(k => k.startsWith(prefix))\n    .forEach(k => localStorage.removeItem(k));\n}\n*** End Files\n\n<EVOLUTION_COMPLETE>\n"}


skill_id: typescript-import-audit__v0_8e35a41f
name: typescript-import-audit
path: example/skills/typescript-import-audit/SKILL.md
category: workflow
visibility: private
lineage_origin: captured
lineage_source_task_id: evolution-v3-task-07
lineage_created_at: 2026-03-10T22:20:57.964997
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T22:20:57.965040
last_updated: 2026-03-10T22:23:22.541363
description:
Systematic workflow for auditing TypeScript codebases for import hygiene issues (value vs. type imports, duplicates), missing base class extensions, and incomplete barrel file re-exports, with actionable fix patterns.

lineage_change_summary:
Capture a reusable TypeScript codebase audit workflow for detecting and fixing import hygiene issues, missing base class extensions, and barrel file re-exports.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -0,0 +1,214 @@
+---
+name: typescript-import-audit
+description: Systematic workflow for auditing TypeScript codebases for import hygiene issues (value vs. type imports, duplicates), missing base class extensions, and incomplete barrel file re-exports, with actionable fix patterns.
+---
+
+# TypeScript Import & Structure Audit
+
+A step-by-step workflow for auditing TypeScript projects for common structural and import hygiene problems, then applying targeted fixes.
+
+---
+
+## Overview
+
+This skill covers four audit checks and their corresponding fixes:
+
+| # | Issue | Fix |
+|---|-------|-----|
+| 1 | Value imports used only as type annotations | Split with `import type` |
+| 2 | Duplicate imports from the same module | Consolidate into one import statement |
+| 3 | Classes missing expected base class extension | Add `extends BaseClass` |
+| 4 | Missing re-exports in barrel (`index.ts`) files | Add the missing `export` lines |
+
+---
+
+## Step 1 — Read All Relevant Files in Parallel
+
+Before making any changes, read all files that need to be audited in a single parallel batch. This avoids slow sequential reads.
+
+```
+Read in parallel:
+  - src/index.ts           (barrel file)
+  - src/**/*.ts            (all source files)
+  - Any explicitly named files from the task spec
+```
+
+Look for:
+- All `import` statements at the top of each file
+- Class declarations (`class Foo`)
+- Export lists in barrel files
+
+---
+
+## Step 2 — Audit Check 1: Value Imports Used Only as Types
+
+### Detection
+
+For every `import { A, B } from 'module'` statement, check whether each
+imported name is used **only** in type positions (`: TypeName`, `as TypeName`,
+`<TypeName>` generics, `implements TypeName`). If a name never appears in a
+value position (instantiation, function call, assignment), it should be a
+type-only import.
+
+```typescript
+// ❌ Bad — Foo is only used as a type annotation
+import { Foo, bar } from './module';
+const x: Foo = bar();
+
+// ✅ Good — split into value import + type import
+import { bar } from './module';
+import type { Foo } from './module';
+```
+
+### Fix Pattern
+
+1. Identify all names in the import that are type-only.
+2. Remove them from the value import (delete the value import entirely if all names are type-only).
+3. Add (or merge into an existing) `import type { ... } from 'module'` statement.
+
+---
+
+## Step 3 — Audit Check 2: Duplicate Imports from the Same Module
+
+### Detection
+
+Scan for two or more `import` statements that reference the **same module path**:
+
+```typescript
+// ❌ Bad — two imports from the same source
+import { ComponentA } from './components';
+import { ComponentB } from './components';
+```
+
+### Fix Pattern
+
+Merge all named imports from the same module into a single statement:
+
+```typescript
+// ✅ Good
+import { ComponentA, ComponentB } from './components';
+```
+
+If there is a mix of value and type imports from the same module, keep them as two separate lines (`import { ... }` and `import type { ... }`), but ensure there is at most one of each kind per module path.
+
+---
+
+## Step 4 — Audit Check 3: Classes That Should Extend a Base Class
+
+### Detection
+
+Look for class declarations that:
+- Implement an interface that implies a base class, OR
+- Are described in comments / documentation as extending something, OR
+- Contain methods/properties that are defined on a known base class
+
+```typescript
+// ❌ Missing base class
+class MyPanel implements PanelInterface {
+  render() { ... }
+}
+
+// ✅ Correct
+class MyPanel extends BasePanel implements PanelInterface {
+  render() { ... }
+}
+```
+
+### Fix Pattern
+
+Add `extends BaseClass` between the class name and any `implements` clause:
+
+```typescript
+class ClassName extends BaseClass implements InterfaceName { ... }
+```
+
+Ensure the base class is imported if it is not already.
+
+---
+
+## Step 5 — Audit Check 4: Missing Re-exports in Barrel Files
+
+### Detection
+
+A barrel file (`index.ts`) should re-export everything that is part of the
+public API. Common gaps:
+
+- A new file was added but never added to `index.ts`
+- A named export inside a file is not forwarded by the barrel
+
+```typescript
+// src/components/MyWidget.ts
+export class MyWidget { ... }
+export type MyWidgetProps = { ... }
+
+// src/components/index.ts — ❌ missing MyWidget
+export { SomeOtherThing } from './SomeOtherThing';
+```
+
+### Fix Pattern
+
+Add the missing re-export line:
+
+```typescript
+// src/components/index.ts — ✅ fixed
+export { SomeOtherThing } from './SomeOtherThing';
+export { MyWidget } from './MyWidget';
+export type { MyWidgetProps } from './MyWidget';
+```
+
+Use `export type { ... }` for type-only exports to maintain strict type/value separation.
+
+---
+
+## Step 6 — Apply Fixes and Verify
+
+### Order of operations
+
+1. Fix **duplicate imports** first (consolidation may surface type-only candidates).
+2. Fix **value vs. type imports** (split imports).
+3. Fix **missing base class** extensions.
+4. Fix **barrel re-exports** last (they depend on the other files being correct).
+
+### Verification checklist
+
+After edits, confirm:
+
+- [ ] No two `import` statements reference the same module path (same kind).
+- [ ] All names used only as types are in `import type { ... }` statements.
+- [ ] Every class that should extend a base class does so.
+- [ ] Every public export in every source file appears in the barrel `index.ts`.
+- [ ] The project compiles without errors (`tsc --noEmit`).
+
+---
+
+## Quick Reference: Import Patterns
+
+```typescript
+// Value import
+import { foo, bar } from './module';
+
+// Type-only import
+import type { Foo, Bar } from './module';
+
+// Mixed (value + type from same module — two lines)
+import { foo } from './module';
+import type { Foo } from './module';
+
+// Re-export value
+export { foo } from './module';
+
+// Re-export type
+export type { Foo } from './module';
+
+// Re-export everything
+export * from './module';
+export type * from './module';  // TypeScript 5.0+
+```
+
+---
+
+## Notes
+
+- Always prefer **explicit named exports** over `export *` in barrel files for better tree-shaking and discoverability.
+- If the project uses `verbatimModuleSyntax` or `isolatedModules` in `tsconfig.json`, `import type` is **required** for type-only imports — the compiler will error otherwise.
+- Run `tsc --noEmit` after each fix category to catch regressions early.

lineage_content_snapshot:
{"SKILL.md": "---\nname: typescript-import-audit\ndescription: Systematic workflow for auditing TypeScript codebases for import hygiene issues (value vs. type imports, duplicates), missing base class extensions, and incomplete barrel file re-exports, with actionable fix patterns.\n---\n\n# TypeScript Import & Structure Audit\n\nA step-by-step workflow for auditing TypeScript projects for common structural and import hygiene problems, then applying targeted fixes.\n\n---\n\n## Overview\n\nThis skill covers four audit checks and their corresponding fixes:\n\n| # | Issue | Fix |\n|---|-------|-----|\n| 1 | Value imports used only as type annotations | Split with `import type` |\n| 2 | Duplicate imports from the same module | Consolidate into one import statement |\n| 3 | Classes missing expected base class extension | Add `extends BaseClass` |\n| 4 | Missing re-exports in barrel (`index.ts`) files | Add the missing `export` lines |\n\n---\n\n## Step 1 — Read All Relevant Files in Parallel\n\nBefore making any changes, read all files that need to be audited in a single parallel batch. This avoids slow sequential reads.\n\n```\nRead in parallel:\n  - src/index.ts           (barrel file)\n  - src/**/*.ts            (all source files)\n  - Any explicitly named files from the task spec\n```\n\nLook for:\n- All `import` statements at the top of each file\n- Class declarations (`class Foo`)\n- Export lists in barrel files\n\n---\n\n## Step 2 — Audit Check 1: Value Imports Used Only as Types\n\n### Detection\n\nFor every `import { A, B } from 'module'` statement, check whether each\nimported name is used **only** in type positions (`: TypeName`, `as TypeName`,\n`<TypeName>` generics, `implements TypeName`). If a name never appears in a\nvalue position (instantiation, function call, assignment), it should be a\ntype-only import.\n\n```typescript\n// ❌ Bad — Foo is only used as a type annotation\nimport { Foo, bar } from './module';\nconst x: Foo = bar();\n\n// ✅ Good — split into value import + type import\nimport { bar } from './module';\nimport type { Foo } from './module';\n```\n\n### Fix Pattern\n\n1. Identify all names in the import that are type-only.\n2. Remove them from the value import (delete the value import entirely if all names are type-only).\n3. Add (or merge into an existing) `import type { ... } from 'module'` statement.\n\n---\n\n## Step 3 — Audit Check 2: Duplicate Imports from the Same Module\n\n### Detection\n\nScan for two or more `import` statements that reference the **same module path**:\n\n```typescript\n// ❌ Bad — two imports from the same source\nimport { ComponentA } from './components';\nimport { ComponentB } from './components';\n```\n\n### Fix Pattern\n\nMerge all named imports from the same module into a single statement:\n\n```typescript\n// ✅ Good\nimport { ComponentA, ComponentB } from './components';\n```\n\nIf there is a mix of value and type imports from the same module, keep them as two separate lines (`import { ... }` and `import type { ... }`), but ensure there is at most one of each kind per module path.\n\n---\n\n## Step 4 — Audit Check 3: Classes That Should Extend a Base Class\n\n### Detection\n\nLook for class declarations that:\n- Implement an interface that implies a base class, OR\n- Are described in comments / documentation as extending something, OR\n- Contain methods/properties that are defined on a known base class\n\n```typescript\n// ❌ Missing base class\nclass MyPanel implements PanelInterface {\n  render() { ... }\n}\n\n// ✅ Correct\nclass MyPanel extends BasePanel implements PanelInterface {\n  render() { ... }\n}\n```\n\n### Fix Pattern\n\nAdd `extends BaseClass` between the class name and any `implements` clause:\n\n```typescript\nclass ClassName extends BaseClass implements InterfaceName { ... }\n```\n\nEnsure the base class is imported if it is not already.\n\n---\n\n## Step 5 — Audit Check 4: Missing Re-exports in Barrel Files\n\n### Detection\n\nA barrel file (`index.ts`) should re-export everything that is part of the\npublic API. Common gaps:\n\n- A new file was added but never added to `index.ts`\n- A named export inside a file is not forwarded by the barrel\n\n```typescript\n// src/components/MyWidget.ts\nexport class MyWidget { ... }\nexport type MyWidgetProps = { ... }\n\n// src/components/index.ts — ❌ missing MyWidget\nexport { SomeOtherThing } from './SomeOtherThing';\n```\n\n### Fix Pattern\n\nAdd the missing re-export line:\n\n```typescript\n// src/components/index.ts — ✅ fixed\nexport { SomeOtherThing } from './SomeOtherThing';\nexport { MyWidget } from './MyWidget';\nexport type { MyWidgetProps } from './MyWidget';\n```\n\nUse `export type { ... }` for type-only exports to maintain strict type/value separation.\n\n---\n\n## Step 6 — Apply Fixes and Verify\n\n### Order of operations\n\n1. Fix **duplicate imports** first (consolidation may surface type-only candidates).\n2. Fix **value vs. type imports** (split imports).\n3. Fix **missing base class** extensions.\n4. Fix **barrel re-exports** last (they depend on the other files being correct).\n\n### Verification checklist\n\nAfter edits, confirm:\n\n- [ ] No two `import` statements reference the same module path (same kind).\n- [ ] All names used only as types are in `import type { ... }` statements.\n- [ ] Every class that should extend a base class does so.\n- [ ] Every public export in every source file appears in the barrel `index.ts`.\n- [ ] The project compiles without errors (`tsc --noEmit`).\n\n---\n\n## Quick Reference: Import Patterns\n\n```typescript\n// Value import\nimport { foo, bar } from './module';\n\n// Type-only import\nimport type { Foo, Bar } from './module';\n\n// Mixed (value + type from same module — two lines)\nimport { foo } from './module';\nimport type { Foo } from './module';\n\n// Re-export value\nexport { foo } from './module';\n\n// Re-export type\nexport type { Foo } from './module';\n\n// Re-export everything\nexport * from './module';\nexport type * from './module';  // TypeScript 5.0+\n```\n\n---\n\n## Notes\n\n- Always prefer **explicit named exports** over `export *` in barrel files for better tree-shaking and discoverability.\n- If the project uses `verbatimModuleSyntax` or `isolatedModules` in `tsconfig.json`, `import type` is **required** for type-only imports — the compiler will error otherwise.\n- Run `tsc --noEmit` after each fix category to catch regressions early."}


skill_id: verify-directory-structure-enhanced__v0_7d07078b
name: verify-directory-structure-parallel-audit
description: Diagnose and resolve file access issues by first verifying the directory structure.
path: example/skills/verify-directory-structure-parallel-audit/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: evolution-v3-task-07
lineage_created_at: 2026-03-10T22:22:03.311909
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T22:22:03.311951
last_updated: 2026-03-10T22:23:22.541363
lineage_change_summary:
Extended the skill to include parallel batch file reading after directory enumeration and added TypeScript import validation guidance as a structured fix category.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,29 +1,140 @@
 ---
-name: verify-directory-structure
-description: Diagnose and resolve file access issues by first verifying the directory structure.
+name: verify-directory-structure-parallel-audit
+description: Diagnose and resolve file access issues by verifying directory structure, then reading multiple files in parallel batches for efficient auditing, with built-in guidance for validating TypeScript import patterns.
 ---
 
-# Verify Directory Structure
+# Verify Directory Structure — Parallel Audit
 
-This skill provides a systematic approach to diagnosing and resolving file access issues by first verifying the directory structure. It is particularly useful when dealing with file operations, script executions, or any task that involves accessing files.
+This skill provides a systematic approach to diagnosing file access issues and auditing source trees efficiently. It extends basic directory verification with **parallel batch file reading** (dramatically reducing iteration count when many files must be inspected) and a dedicated **TypeScript import validation** phase that catches a common class of structural bugs.
+
+---
 
 ## Steps
 
-1. **Identify the Target Path**: Determine the file or directory path that is causing the access issue.
-2. **Check Directory Existence**: Use the `list_dir` tool to verify if the directory exists and inspect its contents.
-3. **Verify Permissions**: Ensure that the user has the necessary permissions to access the directory and its contents.
-4. **Resolve Path Issues**: If the directory does not exist, create it using appropriate commands or scripts. If permissions are insufficient, adjust them accordingly.
-5. **Retry the Operation**: After resolving the directory structure issues, retry the original operation.
+### Phase 1 — Directory Verification
 
-## Example
+1. **Identify Target Paths**: Collect every file or directory path involved in the operation. Prefer absolute paths to eliminate ambiguity.
 
-```bash
-# Example of using the list_dir tool to verify directory structure
-list_dir --path "/path/to/directory"
+2. **Enumerate with `list_dir`**: Call `list_dir` on each relevant directory. Record all file names, sizes, and modification dates. This is your ground truth before touching any file.
+
+3. **Verify Existence & Permissions**: Confirm the directory exists and that the current user has read (and, if needed, write/execute) permissions. If a directory is missing, create it before proceeding. If permissions are insufficient, adjust them before retrying.
+
+4. **Build a File Manifest**: From the `list_dir` output, compile the complete list of files to be audited. Group them logically (e.g., by subdirectory or file type) so they can be read in parallel batches.
+
+---
+
+### Phase 2 — Parallel Batch File Reading
+
+> **Why this matters**: Reading files sequentially costs one iteration per file. Reading them in parallel batches collapses N files into ⌈N/batch_size⌉ iterations — a significant speedup when auditing dozens of files.
+
+5. **Determine Batch Size**: Default to reading **5–10 files per batch**. Reduce the batch size if individual files are very large; increase it for small config/index files.
+
+6. **Issue Parallel `read_file` Calls**: Within each batch, invoke `read_file` for all files simultaneously (a single iteration). Do not wait for one file before requesting the next within the same batch.
+
+   ```
+   Batch 1 (parallel): read_file(src/components/A.tsx)
+                        read_file(src/components/B.tsx)
+                        read_file(src/components/C.tsx)
+                        read_file(src/components/D.tsx)
+                        read_file(src/components/E.tsx)
+
+   Batch 2 (parallel): read_file(src/components/F.tsx)
+                        ...
+   ```
+
+7. **Collect & Triage Results**: After each batch completes, scan results for errors (file-not-found, permission denied) and flag them immediately. Do not abort the remaining batches; continue and aggregate all findings.
+
+8. **Re-verify Missing Files**: For any file reported as missing, re-run `list_dir` on its parent directory to confirm whether the file truly does not exist or whether the path was wrong. Correct the path and retry the single file if necessary.
+
+---
+
+### Phase 3 — TypeScript Import Validation
+
+> This phase targets a common class of structural bugs found when auditing TypeScript source trees.
+
+9. **Identify Import Statements**: For every `.ts` / `.tsx` file read in Phase 2, extract all `import` lines.
+
+10. **Classify Import Kind — Value vs. Type**:
+
+    | Pattern | Correct usage |
+    |---|---|
+    | `import Foo from '...'` | Runtime value (class, function, object) |
+    | `import { Foo } from '...'` | Runtime value export |
+    | `import type Foo from '...'` | Type-only import (erased at compile time) |
+    | `import type { Foo } from '...'` | Type-only named import |
+    | `import { type Foo } from '...'` | Inline type modifier (TS 4.5+) |
+
+11. **Flag Value Imports Used Only as Types**: If an identifier imported without `type` is used **only** in type positions (`: Foo`, `as Foo`, `implements Foo`, generic parameters), it should be converted to `import type`. This prevents accidental runtime dependencies and satisfies `verbatimModuleSyntax` / `isolatedModules` compiler flags.
+
+    ```typescript
+    // ❌ Before — value import used only as a type
+    import { UserProfile } from './types';
+    const handler = (u: UserProfile) => { ... };
+
+    // ✅ After — type-only import
+    import type { UserProfile } from './types';
+    const handler = (u: UserProfile) => { ... };
+    ```
+
+12. **Flag Duplicate Imports**: Detect cases where the same module is imported more than once in a file (often from copy-paste). Merge them into a single import statement.
+
+    ```typescript
+    // ❌ Duplicate
+    import { A } from './utils';
+    import { B } from './utils';
+
+    // ✅ Merged
+    import { A, B } from './utils';
+    ```
+
+13. **Verify Barrel / Index Exports**: When a directory contains an `index.ts` (barrel file), confirm that every component or module expected to be publicly accessible is re-exported. Cross-reference against the file manifest from Phase 1.
+
+14. **Apply Fixes & Re-audit**: Apply all import corrections as a single write pass per file. After writing, re-read the affected files (parallel batch) to confirm the fixes took effect.
+
+---
+
+### Phase 4 — Final Validation & Report
+
+15. **Retry the Original Operation**: With the directory structure confirmed and import issues resolved, retry the operation that originally failed.
+
+16. **Log an Audit Report**: Produce a concise summary covering:
+    - Total files enumerated and read
+    - Number of batches used (highlight the iteration savings vs. sequential)
+    - Import issues found and fixed (value→type conversions, duplicate merges, missing barrel exports)
+    - Any remaining unresolved issues with recommended next steps
+
+---
+
+## Quick Reference: Batch Reading Pattern
+
 ```
+# Sequential (slow) — N iterations for N files
+read_file(file1) → wait → read_file(file2) → wait → ...
+
+# Parallel batch (fast) — ⌈N/B⌉ iterations for N files, batch size B
+[read_file(file1), read_file(file2), ..., read_file(fileB)]  ← 1 iteration
+[read_file(fileB+1), ...]                                     ← 1 iteration
+```
+
+---
 
 ## Best Practices
 
-- Always verify the directory structure before performing file operations.
-- Use absolute paths to avoid ambiguity.
-- Log the directory structure for debugging purposes if issues persist.+- **Always enumerate before reading**: `list_dir` first, then batch-read. Never guess file names.
+- **Use absolute paths** throughout to avoid working-directory ambiguity.
+- **Batch by locality**: Group files from the same directory in the same batch to keep context coherent.
+- **Type imports are zero-cost at runtime**: Prefer `import type` for any symbol used exclusively as a TypeScript type. This is especially important in projects with `isolatedModules: true`.
+- **Barrel files are contracts**: Treat `index.ts` exports as the public API of a module. Missing exports are bugs even if the underlying file is correct.
+- **Log intermediate findings**: If issues persist after Phase 3, dump the full audit log for offline debugging.
+
+---
+
+## Common Fix Categories (Checklist)
+
+- [ ] Directory missing → create with `mkdir -p`
+- [ ] Permissions insufficient → `chmod`/`chown` or escalate
+- [ ] Path typo → re-enumerate with `list_dir`, correct path
+- [ ] Value import used as type → add `import type`
+- [ ] Duplicate imports → merge into single statement
+- [ ] Missing barrel export → add re-export to `index.ts`
+- [ ] Circular import → restructure module boundaries

lineage_content_snapshot:
{"SKILL.md": "---\nname: verify-directory-structure-parallel-audit\ndescription: Diagnose and resolve file access issues by verifying directory structure, then reading multiple files in parallel batches for efficient auditing, with built-in guidance for validating TypeScript import patterns.\n---\n\n# Verify Directory Structure — Parallel Audit\n\nThis skill provides a systematic approach to diagnosing file access issues and auditing source trees efficiently. It extends basic directory verification with **parallel batch file reading** (dramatically reducing iteration count when many files must be inspected) and a dedicated **TypeScript import validation** phase that catches a common class of structural bugs.\n\n---\n\n## Steps\n\n### Phase 1 — Directory Verification\n\n1. **Identify Target Paths**: Collect every file or directory path involved in the operation. Prefer absolute paths to eliminate ambiguity.\n\n2. **Enumerate with `list_dir`**: Call `list_dir` on each relevant directory. Record all file names, sizes, and modification dates. This is your ground truth before touching any file.\n\n3. **Verify Existence & Permissions**: Confirm the directory exists and that the current user has read (and, if needed, write/execute) permissions. If a directory is missing, create it before proceeding. If permissions are insufficient, adjust them before retrying.\n\n4. **Build a File Manifest**: From the `list_dir` output, compile the complete list of files to be audited. Group them logically (e.g., by subdirectory or file type) so they can be read in parallel batches.\n\n---\n\n### Phase 2 — Parallel Batch File Reading\n\n> **Why this matters**: Reading files sequentially costs one iteration per file. Reading them in parallel batches collapses N files into ⌈N/batch_size⌉ iterations — a significant speedup when auditing dozens of files.\n\n5. **Determine Batch Size**: Default to reading **5–10 files per batch**. Reduce the batch size if individual files are very large; increase it for small config/index files.\n\n6. **Issue Parallel `read_file` Calls**: Within each batch, invoke `read_file` for all files simultaneously (a single iteration). Do not wait for one file before requesting the next within the same batch.\n\n   ```\n   Batch 1 (parallel): read_file(src/components/A.tsx)\n                        read_file(src/components/B.tsx)\n                        read_file(src/components/C.tsx)\n                        read_file(src/components/D.tsx)\n                        read_file(src/components/E.tsx)\n\n   Batch 2 (parallel): read_file(src/components/F.tsx)\n                        ...\n   ```\n\n7. **Collect & Triage Results**: After each batch completes, scan results for errors (file-not-found, permission denied) and flag them immediately. Do not abort the remaining batches; continue and aggregate all findings.\n\n8. **Re-verify Missing Files**: For any file reported as missing, re-run `list_dir` on its parent directory to confirm whether the file truly does not exist or whether the path was wrong. Correct the path and retry the single file if necessary.\n\n---\n\n### Phase 3 — TypeScript Import Validation\n\n> This phase targets a common class of structural bugs found when auditing TypeScript source trees.\n\n9. **Identify Import Statements**: For every `.ts` / `.tsx` file read in Phase 2, extract all `import` lines.\n\n10. **Classify Import Kind — Value vs. Type**:\n\n    | Pattern | Correct usage |\n    |---|---|\n    | `import Foo from '...'` | Runtime value (class, function, object) |\n    | `import { Foo } from '...'` | Runtime value export |\n    | `import type Foo from '...'` | Type-only import (erased at compile time) |\n    | `import type { Foo } from '...'` | Type-only named import |\n    | `import { type Foo } from '...'` | Inline type modifier (TS 4.5+) |\n\n11. **Flag Value Imports Used Only as Types**: If an identifier imported without `type` is used **only** in type positions (`: Foo`, `as Foo`, `implements Foo`, generic parameters), it should be converted to `import type`. This prevents accidental runtime dependencies and satisfies `verbatimModuleSyntax` / `isolatedModules` compiler flags.\n\n    ```typescript\n    // ❌ Before — value import used only as a type\n    import { UserProfile } from './types';\n    const handler = (u: UserProfile) => { ... };\n\n    // ✅ After — type-only import\n    import type { UserProfile } from './types';\n    const handler = (u: UserProfile) => { ... };\n    ```\n\n12. **Flag Duplicate Imports**: Detect cases where the same module is imported more than once in a file (often from copy-paste). Merge them into a single import statement.\n\n    ```typescript\n    // ❌ Duplicate\n    import { A } from './utils';\n    import { B } from './utils';\n\n    // ✅ Merged\n    import { A, B } from './utils';\n    ```\n\n13. **Verify Barrel / Index Exports**: When a directory contains an `index.ts` (barrel file), confirm that every component or module expected to be publicly accessible is re-exported. Cross-reference against the file manifest from Phase 1.\n\n14. **Apply Fixes & Re-audit**: Apply all import corrections as a single write pass per file. After writing, re-read the affected files (parallel batch) to confirm the fixes took effect.\n\n---\n\n### Phase 4 — Final Validation & Report\n\n15. **Retry the Original Operation**: With the directory structure confirmed and import issues resolved, retry the operation that originally failed.\n\n16. **Log an Audit Report**: Produce a concise summary covering:\n    - Total files enumerated and read\n    - Number of batches used (highlight the iteration savings vs. sequential)\n    - Import issues found and fixed (value→type conversions, duplicate merges, missing barrel exports)\n    - Any remaining unresolved issues with recommended next steps\n\n---\n\n## Quick Reference: Batch Reading Pattern\n\n```\n# Sequential (slow) — N iterations for N files\nread_file(file1) → wait → read_file(file2) → wait → ...\n\n# Parallel batch (fast) — ⌈N/B⌉ iterations for N files, batch size B\n[read_file(file1), read_file(file2), ..., read_file(fileB)]  ← 1 iteration\n[read_file(fileB+1), ...]                                     ← 1 iteration\n```\n\n---\n\n## Best Practices\n\n- **Always enumerate before reading**: `list_dir` first, then batch-read. Never guess file names.\n- **Use absolute paths** throughout to avoid working-directory ambiguity.\n- **Batch by locality**: Group files from the same directory in the same batch to keep context coherent.\n- **Type imports are zero-cost at runtime**: Prefer `import type` for any symbol used exclusively as a TypeScript type. This is especially important in projects with `isolatedModules: true`.\n- **Barrel files are contracts**: Treat `index.ts` exports as the public API of a module. Missing exports are bugs even if the underlying file is correct.\n- **Log intermediate findings**: If issues persist after Phase 3, dump the full audit log for offline debugging.\n\n---\n\n## Common Fix Categories (Checklist)\n\n- [ ] Directory missing → create with `mkdir -p`\n- [ ] Permissions insufficient → `chmod`/`chown` or escalate\n- [ ] Path typo → re-enumerate with `list_dir`, correct path\n- [ ] Value import used as type → add `import type`\n- [ ] Duplicate imports → merge into single statement\n- [ ] Missing barrel export → add re-export to `index.ts`\n- [ ] Circular import → restructure module boundaries\n"}


skill_id: combined-head-linecount-check__v0_7845f222
name: combined-head-linecount-check
path: example/skills/combined-head-linecount-check/SKILL.md
category: workflow
visibility: private
lineage_origin: captured
lineage_source_task_id: evolution-v3-task-08
lineage_created_at: 2026-03-10T22:23:44.751927
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T22:23:44.751995
last_updated: 2026-03-10T22:23:44.751996
description:
Verify a file's first N lines and total line count in one shell call using head and wc -l with an echo separator, minimizing read operations and shell iterations.

lineage_change_summary:
Capture the pattern of combining `head -N` and `wc -l` in a single shell call to simultaneously verify a file's first N lines and its total line count, reducing redundant read operations.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -0,0 +1,95 @@
+---
+name: combined-head-linecount-check
+description: Verify a file's first N lines and total line count in one shell call using head and wc -l with an echo separator, minimizing read operations and shell iterations.
+---
+
+# Combined Head + Line Count Check
+
+When you need to **both** inspect the top of a file and confirm its total line count, collapse the two checks into a single `run_shell` call instead of issuing two separate commands.
+
+## Why
+
+- Each shell call is a round-trip. Two separate calls (`head` then `wc -l`) cost two iterations.
+- A combined call costs one iteration and returns both pieces of information atomically.
+- When checking multiple files at once, each file still only needs one call (or all files can be parallelized in a single compound command).
+
+## Pattern
+
+```bash
+head -N <file> && echo "---" && wc -l <file>
+```
+
+The `echo "---"` separator makes the output easy to parse visually: everything above the dashes is content, everything below is the line count.
+
+## Examples
+
+### Single file — inspect first 10 lines and total count
+
+```bash
+head -10 src/main.ts && echo "---" && wc -l src/main.ts
+```
+
+Sample output:
+```
+import { createApp } from 'vue'
+import App from './App.vue'
+...
+---
+      142 src/main.ts
+```
+
+### Multiple files — fully parallelized
+
+When you need to check several files simultaneously, run them all in one shell call using `&&` chains or newline-separated subshells:
+
+```bash
+(head -10 src/main.ts     && echo "---" && wc -l src/main.ts)     && echo "===" && \
+(head -10 vite.config.ts  && echo "---" && wc -l vite.config.ts)  && echo "===" && \
+(head -5  package.json    && echo "---" && wc -l package.json)
+```
+
+Or, if order independence is acceptable, run them in parallel background jobs and wait:
+
+```bash
+{
+  head -10 src/main.ts    && echo "---" && wc -l src/main.ts
+} &
+{
+  head -10 vite.config.ts && echo "---" && wc -l vite.config.ts
+} &
+wait
+```
+
+## When to Apply
+
+| Situation | Recommendation |
+|-----------|---------------|
+| Verifying a generated/modified file was written correctly | ✅ Use combined call |
+| Checking multiple output files after a build step | ✅ Parallelize all in one shell call |
+| Only need line count (no content preview required) | Use plain `wc -l` |
+| Only need content (no line count required) | Use plain `head -N` |
+| File is extremely large and you want a middle section | Use `sed -n` or `awk` instead |
+
+## Integration with Parallel Reads
+
+This pattern compounds well with parallel file reads. In a single iteration you can:
+
+1. Read file contents with `cat` or `head` for multiple files.
+2. Simultaneously verify line counts with `wc -l`.
+3. Check for specific patterns with `grep -c`.
+
+Example of a 5-file parallel verification in one shell call:
+
+```bash
+echo "=== main.ts ===" && head -10 src/main.ts && echo "lines:" && wc -l < src/main.ts && \
+echo "=== config ===" && head -5 vite.config.ts && echo "lines:" && wc -l < vite.config.ts && \
+echo "=== package ===" && head -5 package.json && echo "lines:" && wc -l < package.json && \
+echo "=== index ===" && head -8 index.html && echo "lines:" && wc -l < index.html && \
+echo "=== readme ===" && head -5 README.md && echo "lines:" && wc -l < README.md
+```
+
+> **Tip:** Using `wc -l < file` (with input redirect) prints only the number, without the filename — cleaner output when the filename is already shown by the `echo` label above it.
+
+## Key Takeaway
+
+> **Never use two shell calls where one will do.** Combining `head -N` and `wc -l` in a single command is the minimal-overhead way to answer both "what does this file contain?" and "is it the right size?" simultaneously.

lineage_content_snapshot:
{"SKILL.md": "---\nname: combined-head-linecount-check\ndescription: Verify a file's first N lines and total line count in one shell call using head and wc -l with an echo separator, minimizing read operations and shell iterations.\n---\n\n# Combined Head + Line Count Check\n\nWhen you need to **both** inspect the top of a file and confirm its total line count, collapse the two checks into a single `run_shell` call instead of issuing two separate commands.\n\n## Why\n\n- Each shell call is a round-trip. Two separate calls (`head` then `wc -l`) cost two iterations.\n- A combined call costs one iteration and returns both pieces of information atomically.\n- When checking multiple files at once, each file still only needs one call (or all files can be parallelized in a single compound command).\n\n## Pattern\n\n```bash\nhead -N <file> && echo \"---\" && wc -l <file>\n```\n\nThe `echo \"---\"` separator makes the output easy to parse visually: everything above the dashes is content, everything below is the line count.\n\n## Examples\n\n### Single file — inspect first 10 lines and total count\n\n```bash\nhead -10 src/main.ts && echo \"---\" && wc -l src/main.ts\n```\n\nSample output:\n```\nimport { createApp } from 'vue'\nimport App from './App.vue'\n...\n---\n      142 src/main.ts\n```\n\n### Multiple files — fully parallelized\n\nWhen you need to check several files simultaneously, run them all in one shell call using `&&` chains or newline-separated subshells:\n\n```bash\n(head -10 src/main.ts     && echo \"---\" && wc -l src/main.ts)     && echo \"===\" && \\\n(head -10 vite.config.ts  && echo \"---\" && wc -l vite.config.ts)  && echo \"===\" && \\\n(head -5  package.json    && echo \"---\" && wc -l package.json)\n```\n\nOr, if order independence is acceptable, run them in parallel background jobs and wait:\n\n```bash\n{\n  head -10 src/main.ts    && echo \"---\" && wc -l src/main.ts\n} &\n{\n  head -10 vite.config.ts && echo \"---\" && wc -l vite.config.ts\n} &\nwait\n```\n\n## When to Apply\n\n| Situation | Recommendation |\n|-----------|---------------|\n| Verifying a generated/modified file was written correctly | ✅ Use combined call |\n| Checking multiple output files after a build step | ✅ Parallelize all in one shell call |\n| Only need line count (no content preview required) | Use plain `wc -l` |\n| Only need content (no line count required) | Use plain `head -N` |\n| File is extremely large and you want a middle section | Use `sed -n` or `awk` instead |\n\n## Integration with Parallel Reads\n\nThis pattern compounds well with parallel file reads. In a single iteration you can:\n\n1. Read file contents with `cat` or `head` for multiple files.\n2. Simultaneously verify line counts with `wc -l`.\n3. Check for specific patterns with `grep -c`.\n\nExample of a 5-file parallel verification in one shell call:\n\n```bash\necho \"=== main.ts ===\" && head -10 src/main.ts && echo \"lines:\" && wc -l < src/main.ts && \\\necho \"=== config ===\" && head -5 vite.config.ts && echo \"lines:\" && wc -l < vite.config.ts && \\\necho \"=== package ===\" && head -5 package.json && echo \"lines:\" && wc -l < package.json && \\\necho \"=== index ===\" && head -8 index.html && echo \"lines:\" && wc -l < index.html && \\\necho \"=== readme ===\" && head -5 README.md && echo \"lines:\" && wc -l < README.md\n```\n\n> **Tip:** Using `wc -l < file` (with input redirect) prints only the number, without the filename — cleaner output when the filename is already shown by the `echo` label above it.\n\n## Key Takeaway\n\n> **Never use two shell calls where one will do.** Combining `head -N` and `wc -l` in a single command is the minimal-overhead way to answer both \"what does this file contain?\" and \"is it the right size?\" simultaneously."}


skill_id: verify-directory-structure-enhanced-enhanced__v0_cf47d96f
name: dir-verify-parallel-shell-audit
description: Diagnose and resolve file access issues by first verifying the directory structure.
path: example/skills/dir-verify-parallel-shell-audit/SKILL.md
category: workflow
visibility: private
lineage_origin: derived
lineage_source_task_id: evolution-v3-task-08
lineage_created_at: 2026-03-10T22:24:16.297231
lineage_created_by: openrouter/anthropic/claude-sonnet-4.6
first_seen: 2026-03-10T22:24:16.297260
last_updated: 2026-03-10T22:24:16.297261
lineage_change_summary:
Added an explicit 'command-line file property checks' phase that integrates head -N and wc -l shell calls into a single run_shell invocation alongside read_file batches, eliminating a separate iteration for file property checks and enabling batching with directory listing in Phase 1.

lineage_content_diff:
--- a/SKILL.md
+++ b/SKILL.md
@@ -1,25 +1,47 @@
 ---
-name: verify-directory-structure-parallel-audit
-description: Diagnose and resolve file access issues by verifying directory structure, then reading multiple files in parallel batches for efficient auditing, with built-in guidance for validating TypeScript import patterns.
+name: verify-directory-structure-parallel-audit-with-shell-checks
+description: Diagnose and resolve file access issues by verifying directory structure, reading multiple files in parallel batches, running combined shell property checks (head/wc-l) in the same iteration as directory listing, and validating TypeScript import patterns — all with built-in guidance to minimize total iteration count.
 ---
 
-# Verify Directory Structure — Parallel Audit
+# Verify Directory Structure — Parallel Audit with Shell Property Checks
 
-This skill provides a systematic approach to diagnosing file access issues and auditing source trees efficiently. It extends basic directory verification with **parallel batch file reading** (dramatically reducing iteration count when many files must be inspected) and a dedicated **TypeScript import validation** phase that catches a common class of structural bugs.
+This skill provides a systematic approach to diagnosing file access issues and auditing source trees efficiently. It extends the parallel batch reading approach with an **integrated shell property check phase** that combines `head -N` and `wc -l` into a single `run_shell` call, co-scheduled with directory listing in Phase 1. This eliminates a common extra iteration that would otherwise be required to check file properties such as line counts and leading content (e.g., CSS/config file headers).
 
 ---
 
 ## Steps
 
-### Phase 1 — Directory Verification
+### Phase 1 — Directory Verification + Shell Property Checks (Combined)
 
-1. **Identify Target Paths**: Collect every file or directory path involved in the operation. Prefer absolute paths to eliminate ambiguity.
+> **Key optimization**: Fire `list_dir` calls **and** `run_shell` property checks in the same iteration. This avoids a dedicated iteration later just to learn "how many lines does this file have?" or "what is the first line of this CSS file?".
 
-2. **Enumerate with `list_dir`**: Call `list_dir` on each relevant directory. Record all file names, sizes, and modification dates. This is your ground truth before touching any file.
+1. **Identify Target Paths**: Collect every file or directory path involved in the operation. Prefer absolute paths to eliminate ambiguity. At this stage, also note any files whose properties (line count, header content) you already know you will need — typically CSS, config, or data files.
 
-3. **Verify Existence & Permissions**: Confirm the directory exists and that the current user has read (and, if needed, write/execute) permissions. If a directory is missing, create it before proceeding. If permissions are insufficient, adjust them before retrying.
+2. **Issue Parallel `list_dir` + `run_shell` Calls Simultaneously**:
 
-4. **Build a File Manifest**: From the `list_dir` output, compile the complete list of files to be audited. Group them logically (e.g., by subdirectory or file type) so they can be read in parallel batches.
+   - Call `list_dir` on each relevant directory.
+   - In the **same iteration**, issue one or more `run_shell` calls that bundle `head` and `wc -l` for known target files using a single compound shell command.
+
+   ```
+   Iteration 1 (parallel):
+     list_dir(src/components)
+     list_dir(src/styles)
+     run_shell("head -5 src/styles/main.css && echo '---' && wc -l src/styles/main.css")
+     run_shell("head -3 vite.config.ts && echo '---' && wc -l src/main.ts")
+   ```
+
+   **Compound shell pattern** (combine head + wc + any other checks for a file):
+   ```bash
+   # Single run_shell call — multiple files, multiple checks, zero extra iterations
+   head -10 path/to/file.css && echo "LINE_COUNT:" && wc -l path/to/file.css && \
+   echo "---" && head -5 path/to/config.ts && echo "LINE_COUNT:" && wc -l path/to/config.ts
+   ```
+
+3. **Verify Existence & Permissions**: Confirm directories exist and that the current user has read (and, if needed, write/execute) permissions. If a directory is missing, create it before proceeding (`mkdir -p`). If permissions are insufficient, adjust them before retrying.
+
+4. **Build a File Manifest**: From the `list_dir` output, compile the complete list of files to be audited. Group them logically (e.g., by subdirectory or file type) so they can be read in parallel batches in Phase 2.
+
+5. **Record Shell Check Results**: Parse and store the `head`/`wc -l` output alongside the manifest. This data is immediately available for the final audit report without any additional iterations.
 
 ---
 
@@ -27,9 +49,9 @@
 
 > **Why this matters**: Reading files sequentially costs one iteration per file. Reading them in parallel batches collapses N files into ⌈N/batch_size⌉ iterations — a significant speedup when auditing dozens of files.
 
-5. **Determine Batch Size**: Default to reading **5–10 files per batch**. Reduce the batch size if individual files are very large; increase it for small config/index files.
+6. **Determine Batch Size**: Default to reading **5–10 files per batch**. Reduce the batch size if individual files are very large; increase it for small config/index files. Files whose content was partially previewed via `head` in Phase 1 should still be fully read here if their complete content is needed.
 
-6. **Issue Parallel `read_file` Calls**: Within each batch, invoke `read_file` for all files simultaneously (a single iteration). Do not wait for one file before requesting the next within the same batch.
+7. **Issue Parallel `read_file` Calls**: Within each batch, invoke `read_file` for all files simultaneously (a single iteration). Do not wait for one file before requesting the next within the same batch.
 
    ```
    Batch 1 (parallel): read_file(src/components/A.tsx)
@@ -42,9 +64,9 @@
                         ...
    ```
 
-7. **Collect & Triage Results**: After each batch completes, scan results for errors (file-not-found, permission denied) and flag them immediately. Do not abort the remaining batches; continue and aggregate all findings.
+8. **Collect & Triage Results**: After each batch completes, scan results for errors (file-not-found, permission denied) and flag them immediately. Do not abort the remaining batches; continue and aggregate all findings.
 
-8. **Re-verify Missing Files**: For any file reported as missing, re-run `list_dir` on its parent directory to confirm whether the file truly does not exist or whether the path was wrong. Correct the path and retry the single file if necessary.
+9. **Re-verify Missing Files**: For any file reported as missing, re-run `list_dir` on its parent directory to confirm whether the file truly does not exist or whether the path was wrong. Correct the path and retry the single file if necessary.
 
 ---
 
@@ -52,9 +74,9 @@
 
 > This phase targets a common class of structural bugs found when auditing TypeScript source trees.
 
-9. **Identify Import Statements**: For every `.ts` / `.tsx` file read in Phase 2, extract all `import` lines.
+10. **Identify Import Statements**: For every `.ts` / `.tsx` file read in Phase 2, extract all `import` lines.
 
-10. **Classify Import Kind — Value vs. Type**:
+11. **Classify Import Kind — Value vs. Type**:
 
     | Pattern | Correct usage |
     |---|---|
@@ -64,7 +86,7 @@
     | `import type { Foo } from '...'` | Type-only named import |
     | `import { type Foo } from '...'` | Inline type modifier (TS 4.5+) |
 
-11. **Flag Value Imports Used Only as Types**: If an identifier imported without `type` is used **only** in type positions (`: Foo`, `as Foo`, `implements Foo`, generic parameters), it should be converted to `import type`. This prevents accidental runtime dependencies and satisfies `verbatimModuleSyntax` / `isolatedModules` compiler flags.
+12. **Flag Value Imports Used Only as Types**: If an identifier imported without `type` is used **only** in type positions (`: Foo`, `as Foo`, `implements Foo`, generic parameters), it should be converted to `import type`. This prevents accidental runtime dependencies and satisfies `verbatimModuleSyntax` / `isolatedModules` compiler flags.
 
     ```typescript
     // ❌ Before — value import used only as a type
@@ -76,7 +98,7 @@
     const handler = (u: UserProfile) => { ... };
     ```
 
-12. **Flag Duplicate Imports**: Detect cases where the same module is imported more than once in a file (often from copy-paste). Merge them into a single import statement.
+13. **Flag Duplicate Imports**: Detect cases where the same module is imported more than once in a file (often from copy-paste). Merge them into a single import statement.
 
     ```typescript
     // ❌ Duplicate
@@ -87,39 +109,65 @@
     import { A, B } from './utils';
     ```
 
-13. **Verify Barrel / Index Exports**: When a directory contains an `index.ts` (barrel file), confirm that every component or module expected to be publicly accessible is re-exported. Cross-reference against the file manifest from Phase 1.
+14. **Verify Barrel / Index Exports**: When a directory contains an `index.ts` (barrel file), confirm that every component or module expected to be publicly accessible is re-exported. Cross-reference against the file manifest from Phase 1.
 
-14. **Apply Fixes & Re-audit**: Apply all import corrections as a single write pass per file. After writing, re-read the affected files (parallel batch) to confirm the fixes took effect.
+15. **Apply Fixes & Re-audit**: Apply all import corrections as a single write pass per file. After writing, re-read the affected files (parallel batch) to confirm the fixes took effect.
 
 ---
 
 ### Phase 4 — Final Validation & Report
 
-15. **Retry the Original Operation**: With the directory structure confirmed and import issues resolved, retry the operation that originally failed.
+16. **Retry the Original Operation**: With the directory structure confirmed and import issues resolved, retry the operation that originally failed.
 
-16. **Log an Audit Report**: Produce a concise summary covering:
+17. **Log an Audit Report**: Produce a concise summary covering:
     - Total files enumerated and read
     - Number of batches used (highlight the iteration savings vs. sequential)
+    - Shell property check results (line counts, file headers) gathered in Phase 1 at zero extra iteration cost
     - Import issues found and fixed (value→type conversions, duplicate merges, missing barrel exports)
     - Any remaining unresolved issues with recommended next steps
 
 ---
 
-## Quick Reference: Batch Reading Pattern
+## Quick Reference: Iteration Collapse Patterns
 
 ```
-# Sequential (slow) — N iterations for N files
-read_file(file1) → wait → read_file(file2) → wait → ...
+# Sequential (slow) — N+K iterations for N files + K property checks
+list_dir(dir)         → wait
+run_shell(wc -l ...)  → wait  ← extra iteration that can be eliminated
+read_file(file1)      → wait
+read_file(file2)      → wait
+...
 
-# Parallel batch (fast) — ⌈N/B⌉ iterations for N files, batch size B
-[read_file(file1), read_file(file2), ..., read_file(fileB)]  ← 1 iteration
-[read_file(fileB+1), ...]                                     ← 1 iteration
+# Optimized (fast) — 1 + ⌈N/B⌉ iterations
+[list_dir(dir), run_shell("head -5 f1 && wc -l f1 && head -5 f2 && wc -l f2")]  ← 1 iteration (Phase 1)
+[read_file(file1), read_file(file2), ..., read_file(fileB)]                       ← 1 iteration (Phase 2, batch 1)
+[read_file(fileB+1), ...]                                                          ← 1 iteration (Phase 2, batch 2)
+```
+
+### Compound Shell Command Templates
+
+```bash
+# Check header + line count for a single file
+head -10 /path/to/file.css && echo "LINES:" && wc -l /path/to/file.css
+
+# Check multiple files in one run_shell call
+head -5 /path/to/a.css && echo "LINES_A:" && wc -l /path/to/a.css && \
+echo "===" && \
+head -5 /path/to/b.ts  && echo "LINES_B:" && wc -l /path/to/b.ts
+
+# Verify a file starts with expected content (e.g., CSS theme comment)
+head -1 /path/to/styles.css | grep -q "Theme Colors" && echo "OK" || echo "MISSING HEADER"
+
+# Count files matching a pattern in a directory
+ls src/components/*.tsx | wc -l
 ```
 
 ---
 
 ## Best Practices
 
+- **Co-schedule shell checks with `list_dir`**: Any `wc -l` or `head -N` call you know you'll need belongs in the same iteration as your initial `list_dir` calls — never in a dedicated follow-up iteration.
+- **Bundle multiple shell checks into one `run_shell`**: Use `&&` or `;` to chain head/wc/grep commands for multiple files into a single `run_shell` invocation. Each `run_shell` call costs one tool-call slot; batching them saves slots.
 - **Always enumerate before reading**: `list_dir` first, then batch-read. Never guess file names.
 - **Use absolute paths** throughout to avoid working-directory ambiguity.
 - **Batch by locality**: Group files from the same directory in the same batch to keep context coherent.
@@ -134,6 +182,8 @@
 - [ ] Directory missing → create with `mkdir -p`
 - [ ] Permissions insufficient → `chmod`/`chown` or escalate
 - [ ] Path typo → re-enumerate with `list_dir`, correct path
+- [ ] File has wrong line count → inspect with `wc -l` (already gathered in Phase 1 at no extra cost)
+- [ ] File has wrong header content → inspect with `head -N` (already gathered in Phase 1 at no extra cost)
 - [ ] Value import used as type → add `import type`
 - [ ] Duplicate imports → merge into single statement
 - [ ] Missing barrel export → add re-export to `index.ts`

lineage_content_snapshot:
{"SKILL.md": "---\nname: verify-directory-structure-parallel-audit-with-shell-checks\ndescription: Diagnose and resolve file access issues by verifying directory structure, reading multiple files in parallel batches, running combined shell property checks (head/wc-l) in the same iteration as directory listing, and validating TypeScript import patterns — all with built-in guidance to minimize total iteration count.\n---\n\n# Verify Directory Structure — Parallel Audit with Shell Property Checks\n\nThis skill provides a systematic approach to diagnosing file access issues and auditing source trees efficiently. It extends the parallel batch reading approach with an **integrated shell property check phase** that combines `head -N` and `wc -l` into a single `run_shell` call, co-scheduled with directory listing in Phase 1. This eliminates a common extra iteration that would otherwise be required to check file properties such as line counts and leading content (e.g., CSS/config file headers).\n\n---\n\n## Steps\n\n### Phase 1 — Directory Verification + Shell Property Checks (Combined)\n\n> **Key optimization**: Fire `list_dir` calls **and** `run_shell` property checks in the same iteration. This avoids a dedicated iteration later just to learn \"how many lines does this file have?\" or \"what is the first line of this CSS file?\".\n\n1. **Identify Target Paths**: Collect every file or directory path involved in the operation. Prefer absolute paths to eliminate ambiguity. At this stage, also note any files whose properties (line count, header content) you already know you will need — typically CSS, config, or data files.\n\n2. **Issue Parallel `list_dir` + `run_shell` Calls Simultaneously**:\n\n   - Call `list_dir` on each relevant directory.\n   - In the **same iteration**, issue one or more `run_shell` calls that bundle `head` and `wc -l` for known target files using a single compound shell command.\n\n   ```\n   Iteration 1 (parallel):\n     list_dir(src/components)\n     list_dir(src/styles)\n     run_shell(\"head -5 src/styles/main.css && echo '---' && wc -l src/styles/main.css\")\n     run_shell(\"head -3 vite.config.ts && echo '---' && wc -l src/main.ts\")\n   ```\n\n   **Compound shell pattern** (combine head + wc + any other checks for a file):\n   ```bash\n   # Single run_shell call — multiple files, multiple checks, zero extra iterations\n   head -10 path/to/file.css && echo \"LINE_COUNT:\" && wc -l path/to/file.css && \\\n   echo \"---\" && head -5 path/to/config.ts && echo \"LINE_COUNT:\" && wc -l path/to/config.ts\n   ```\n\n3. **Verify Existence & Permissions**: Confirm directories exist and that the current user has read (and, if needed, write/execute) permissions. If a directory is missing, create it before proceeding (`mkdir -p`). If permissions are insufficient, adjust them before retrying.\n\n4. **Build a File Manifest**: From the `list_dir` output, compile the complete list of files to be audited. Group them logically (e.g., by subdirectory or file type) so they can be read in parallel batches in Phase 2.\n\n5. **Record Shell Check Results**: Parse and store the `head`/`wc -l` output alongside the manifest. This data is immediately available for the final audit report without any additional iterations.\n\n---\n\n### Phase 2 — Parallel Batch File Reading\n\n> **Why this matters**: Reading files sequentially costs one iteration per file. Reading them in parallel batches collapses N files into ⌈N/batch_size⌉ iterations — a significant speedup when auditing dozens of files.\n\n6. **Determine Batch Size**: Default to reading **5–10 files per batch**. Reduce the batch size if individual files are very large; increase it for small config/index files. Files whose content was partially previewed via `head` in Phase 1 should still be fully read here if their complete content is needed.\n\n7. **Issue Parallel `read_file` Calls**: Within each batch, invoke `read_file` for all files simultaneously (a single iteration). Do not wait for one file before requesting the next within the same batch.\n\n   ```\n   Batch 1 (parallel): read_file(src/components/A.tsx)\n                        read_file(src/components/B.tsx)\n                        read_file(src/components/C.tsx)\n                        read_file(src/components/D.tsx)\n                        read_file(src/components/E.tsx)\n\n   Batch 2 (parallel): read_file(src/components/F.tsx)\n                        ...\n   ```\n\n8. **Collect & Triage Results**: After each batch completes, scan results for errors (file-not-found, permission denied) and flag them immediately. Do not abort the remaining batches; continue and aggregate all findings.\n\n9. **Re-verify Missing Files**: For any file reported as missing, re-run `list_dir` on its parent directory to confirm whether the file truly does not exist or whether the path was wrong. Correct the path and retry the single file if necessary.\n\n---\n\n### Phase 3 — TypeScript Import Validation\n\n> This phase targets a common class of structural bugs found when auditing TypeScript source trees.\n\n10. **Identify Import Statements**: For every `.ts` / `.tsx` file read in Phase 2, extract all `import` lines.\n\n11. **Classify Import Kind — Value vs. Type**:\n\n    | Pattern | Correct usage |\n    |---|---|\n    | `import Foo from '...'` | Runtime value (class, function, object) |\n    | `import { Foo } from '...'` | Runtime value export |\n    | `import type Foo from '...'` | Type-only import (erased at compile time) |\n    | `import type { Foo } from '...'` | Type-only named import |\n    | `import { type Foo } from '...'` | Inline type modifier (TS 4.5+) |\n\n12. **Flag Value Imports Used Only as Types**: If an identifier imported without `type` is used **only** in type positions (`: Foo`, `as Foo`, `implements Foo`, generic parameters), it should be converted to `import type`. This prevents accidental runtime dependencies and satisfies `verbatimModuleSyntax` / `isolatedModules` compiler flags.\n\n    ```typescript\n    // ❌ Before — value import used only as a type\n    import { UserProfile } from './types';\n    const handler = (u: UserProfile) => { ... };\n\n    // ✅ After — type-only import\n    import type { UserProfile } from './types';\n    const handler = (u: UserProfile) => { ... };\n    ```\n\n13. **Flag Duplicate Imports**: Detect cases where the same module is imported more than once in a file (often from copy-paste). Merge them into a single import statement.\n\n    ```typescript\n    // ❌ Duplicate\n    import { A } from './utils';\n    import { B } from './utils';\n\n    // ✅ Merged\n    import { A, B } from './utils';\n    ```\n\n14. **Verify Barrel / Index Exports**: When a directory contains an `index.ts` (barrel file), confirm that every component or module expected to be publicly accessible is re-exported. Cross-reference against the file manifest from Phase 1.\n\n15. **Apply Fixes & Re-audit**: Apply all import corrections as a single write pass per file. After writing, re-read the affected files (parallel batch) to confirm the fixes took effect.\n\n---\n\n### Phase 4 — Final Validation & Report\n\n16. **Retry the Original Operation**: With the directory structure confirmed and import issues resolved, retry the operation that originally failed.\n\n17. **Log an Audit Report**: Produce a concise summary covering:\n    - Total files enumerated and read\n    - Number of batches used (highlight the iteration savings vs. sequential)\n    - Shell property check results (line counts, file headers) gathered in Phase 1 at zero extra iteration cost\n    - Import issues found and fixed (value→type conversions, duplicate merges, missing barrel exports)\n    - Any remaining unresolved issues with recommended next steps\n\n---\n\n## Quick Reference: Iteration Collapse Patterns\n\n```\n# Sequential (slow) — N+K iterations for N files + K property checks\nlist_dir(dir)         → wait\nrun_shell(wc -l ...)  → wait  ← extra iteration that can be eliminated\nread_file(file1)      → wait\nread_file(file2)      → wait\n...\n\n# Optimized (fast) — 1 + ⌈N/B⌉ iterations\n[list_dir(dir), run_shell(\"head -5 f1 && wc -l f1 && head -5 f2 && wc -l f2\")]  ← 1 iteration (Phase 1)\n[read_file(file1), read_file(file2), ..., read_file(fileB)]                       ← 1 iteration (Phase 2, batch 1)\n[read_file(fileB+1), ...]                                                          ← 1 iteration (Phase 2, batch 2)\n```\n\n### Compound Shell Command Templates\n\n```bash\n# Check header + line count for a single file\nhead -10 /path/to/file.css && echo \"LINES:\" && wc -l /path/to/file.css\n\n# Check multiple files in one run_shell call\nhead -5 /path/to/a.css && echo \"LINES_A:\" && wc -l /path/to/a.css && \\\necho \"===\" && \\\nhead -5 /path/to/b.ts  && echo \"LINES_B:\" && wc -l /path/to/b.ts\n\n# Verify a file starts with expected content (e.g., CSS theme comment)\nhead -1 /path/to/styles.css | grep -q \"Theme Colors\" && echo \"OK\" || echo \"MISSING HEADER\"\n\n# Count files matching a pattern in a directory\nls src/components/*.tsx | wc -l\n```\n\n---\n\n## Best Practices\n\n- **Co-schedule shell checks with `list_dir`**: Any `wc -l` or `head -N` call you know you'll need belongs in the same iteration as your initial `list_dir` calls — never in a dedicated follow-up iteration.\n- **Bundle multiple shell checks into one `run_shell`**: Use `&&` or `;` to chain head/wc/grep commands for multiple files into a single `run_shell` invocation. Each `run_shell` call costs one tool-call slot; batching them saves slots.\n- **Always enumerate before reading**: `list_dir` first, then batch-read. Never guess file names.\n- **Use absolute paths** throughout to avoid working-directory ambiguity.\n- **Batch by locality**: Group files from the same directory in the same batch to keep context coherent.\n- **Type imports are zero-cost at runtime**: Prefer `import type` for any symbol used exclusively as a TypeScript type. This is especially important in projects with `isolatedModules: true`.\n- **Barrel files are contracts**: Treat `index.ts` exports as the public API of a module. Missing exports are bugs even if the underlying file is correct.\n- **Log intermediate findings**: If issues persist after Phase 3, dump the full audit log for offline debugging.\n\n---\n\n## Common Fix Categories (Checklist)\n\n- [ ] Directory missing → create with `mkdir -p`\n- [ ] Permissions insufficient → `chmod`/`chown` or escalate\n- [ ] Path typo → re-enumerate with `list_dir`, correct path\n- [ ] File has wrong line count → inspect with `wc -l` (already gathered in Phase 1 at no extra cost)\n- [ ] File has wrong header content → inspect with `head -N` (already gathered in Phase 1 at no extra cost)\n- [ ] Value import used as type → add `import type`\n- [ ] Duplicate imports → merge into single statement\n- [ ] Missing barrel export → add re-export to `index.ts`\n- [ ] Circular import → restructure module boundaries\n"}


