diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 8867409..81468cc 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,7 +1,7 @@ { "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", "name": "greybeard", - "description": "Greybeard \u2014 Karpathy-inspired coding-agent guidelines, the skillify / check-resolvable meta-skills, and the /pressure-test, /sidenote and /visualize commands.", + "description": "Greybeard \u2014 Karpathy-inspired coding-agent guidelines, the skillify / check-resolvable meta-skills, the verify-responsive practice skill, and the /pressure-test, /sidenote and /visualize commands.", "owner": { "name": "Clawnify", "url": "https://github.com/clawnify" @@ -9,7 +9,7 @@ "plugins": [ { "name": "greybeard", - "description": "skillify + check-resolvable skills and the /pressure-test, /sidenote and /visualize commands. Pair with the npx installer for the always-on guidelines.", + "description": "skillify + check-resolvable + verify-responsive skills and the /pressure-test, /sidenote and /visualize commands. Pair with the npx installer for the always-on guidelines.", "source": "./", "category": "productivity" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 46edb40..ddf4a6e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "greybeard", - "description": "Karpathy-inspired coding-agent skills: skillify (capture repeated work), check-resolvable (keep skills DRY + MECE), and the /pressure-test, /sidenote and /visualize commands.", + "description": "Karpathy-inspired coding-agent skills: skillify (capture repeated work), check-resolvable (keep skills DRY + MECE), verify-responsive (render a UI at real viewport widths), and the /pressure-test, /sidenote and /visualize commands.", "author": { "name": "Clawnify", "url": "https://github.com/clawnify" diff --git a/README.md b/README.md index 6c53aaa..429e92a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ > Built and maintained by [Clawnify](https://clawnify.com) — a managed platform that provisions AI agents with WhatsApp / Telegram / Email and browser capabilities for non-technical users. -A single `CLAUDE.md` file to improve AI coding-agent behavior, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls, plus three sections we added for the AI-assisted-coding era. Ships with two runnable [meta-skills](#skills-skillify-dry--mece-resolvers), its three commands — [`/pressure-test`](#the-pressure-test-command), the decision test, [`/sidenote`](#the-sidenote-command), park-a-thought, and [`/visualize`](#the-visualize-command), draw-the-real-shape — and a [one-command installer](#install) that fans it all out to every AI coding agent you use. +A single `CLAUDE.md` file to improve AI coding-agent behavior, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls, plus three sections we added for the AI-assisted-coding era. Ships with three runnable [skills](#skills-skillify-dry--mece-resolvers), its three commands — [`/pressure-test`](#the-pressure-test-command), the decision test, [`/sidenote`](#the-sidenote-command), park-a-thought, and [`/visualize`](#the-visualize-command), draw-the-real-shape — and a [one-command installer](#install) that fans it all out to every AI coding agent you use. ## The Problems @@ -184,6 +184,12 @@ The principle above ships as two runnable meta-skills in [`skills/`](./skills) | [`skillify`](./skills/skillify/SKILL.md) | Capture what you just did as a reusable, parameterized skill, then register it in the resolver. | | [`check-resolvable`](./skills/check-resolvable/SKILL.md) | Audit the whole library so it stays DRY and MECE — no duplicates, no overlaps, no gaps. | +And the **practice skills** those two produce — procedures captured with `skillify`, kept in the same index: + +| Skill | What it does | +|-------|--------------| +| [`verify-responsive`](./skills/verify-responsive/SKILL.md) | Render the real page at real viewport widths in fixed-width iframes, then drive and assert it from the parent. | + They're standard [Claude Code Agent Skills](https://code.claude.com/docs/en/skills) (portable to Cursor, Codex, OpenClaw). Install and usage: [`skills/README.md`](./skills/README.md). ## The three commands @@ -240,7 +246,7 @@ Then `/visualize `, or a bare `/visualize` to draw whatever's curr npx @clawnify/greybeard ``` -It detects the AI coding agents you actually use and installs the right files for each: the seven guidelines into the rule file each one reads (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.cursor/rules/`, `.windsurf/rules/`, `.clinerules/`, `.github/copilot-instructions.md`), plus the `skillify` / `check-resolvable` skills for Claude Code and OpenClaw, and the `/pressure-test`, `/sidenote` and `/visualize` commands for Claude Code. Shared files are edited between markers, so re-running is a safe no-op and your own content is preserved. +It detects the AI coding agents you actually use and installs the right files for each: the seven guidelines into the rule file each one reads (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.cursor/rules/`, `.windsurf/rules/`, `.clinerules/`, `.github/copilot-instructions.md`), plus the `skillify` / `check-resolvable` / `verify-responsive` skills for Claude Code and OpenClaw, and the `/pressure-test`, `/sidenote` and `/visualize` commands for Claude Code. Shared files are edited between markers, so re-running is a safe no-op and your own content is preserved. ```bash npx @clawnify/greybeard --list # show detected agents diff --git a/bin/install.js b/bin/install.js index bd43bb0..dbf876c 100644 --- a/bin/install.js +++ b/bin/install.js @@ -138,13 +138,14 @@ function rel(p) { return p.startsWith(HOME) ? '~' + p.slice(HOME.length) : path. // The canonical principles payload for fenced installs = the generated AGENTS.md. const PRINCIPLES = fs.readFileSync(path.join(PKG, 'AGENTS.md'), 'utf8'); +// Skill directories shipped to every provider that reads skills. Add a new one here only. +const SKILLS = ['skillify', 'check-resolvable', 'verify-responsive']; // ── Per-provider install / uninstall ──────────────────────────────────────── function installClaude(dir, un) { if (un) { removeFenceFrom(path.join(dir, 'CLAUDE.md')); - removePath(path.join(dir, 'skills', 'skillify')); - removePath(path.join(dir, 'skills', 'check-resolvable')); + for (const s of SKILLS) removePath(path.join(dir, 'skills', s)); removePath(path.join(dir, 'commands', 'scalable.md')); // legacy name, pre-rename removePath(path.join(dir, 'commands', 'pressure-test.md')); removePath(path.join(dir, 'commands', 'sidenote.md')); @@ -152,8 +153,7 @@ function installClaude(dir, un) { return; } fenceInto(path.join(dir, 'CLAUDE.md'), PRINCIPLES); - copyDir(path.join(PKG, 'skills', 'skillify'), path.join(dir, 'skills', 'skillify')); - copyDir(path.join(PKG, 'skills', 'check-resolvable'), path.join(dir, 'skills', 'check-resolvable')); + for (const s of SKILLS) copyDir(path.join(PKG, 'skills', s), path.join(dir, 'skills', s)); removePath(path.join(dir, 'commands', 'scalable.md')); // clean up the pre-rename command from prior installs writeFile(path.join(dir, 'commands', 'pressure-test.md'), fs.readFileSync(path.join(PKG, 'commands', 'pressure-test.md'), 'utf8')); writeFile(path.join(dir, 'commands', 'sidenote.md'), fs.readFileSync(path.join(PKG, 'commands', 'sidenote.md'), 'utf8')); @@ -164,9 +164,8 @@ function applyProvider(p, un) { if (p.kind === 'claude') return installClaude(expand('~/.claude'), un); if (p.kind === 'openclaw') { const base = expand('~/.openclaw/workspace/skills'); - if (un) { removePath(path.join(base, 'skillify')); removePath(path.join(base, 'check-resolvable')); return; } - copyDir(path.join(PKG, 'skills', 'skillify'), path.join(base, 'skillify')); - copyDir(path.join(PKG, 'skills', 'check-resolvable'), path.join(base, 'check-resolvable')); + if (un) { for (const s of SKILLS) removePath(path.join(base, s)); return; } + for (const s of SKILLS) copyDir(path.join(PKG, 'skills', s), path.join(base, s)); return; } if (p.kind === 'file') { diff --git a/package.json b/package.json index 6bcdfed..69cfc4a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@clawnify/greybeard", "version": "0.1.0", - "description": "Greybeard \u2014 Karpathy-inspired guardrails that make your AI coding agent behave like a principled senior engineer. Guidelines + skillify / check-resolvable skills + the /pressure-test, /sidenote and /visualize commands, installable across every AI coding agent.", + "description": "Greybeard \u2014 Karpathy-inspired guardrails that make your AI coding agent behave like a principled senior engineer. Guidelines + the skillify / check-resolvable / verify-responsive skills + the /pressure-test, /sidenote and /visualize commands, installable across every AI coding agent.", "license": "MIT", "homepage": "https://github.com/clawnify/greybeard", "repository": { diff --git a/skills/README.md b/skills/README.md index 4d3c4f2..e478c39 100644 --- a/skills/README.md +++ b/skills/README.md @@ -1,12 +1,20 @@ # Skills: Skillify, DRY & MECE Resolvers -Two meta-skills that make a coding agent *compound* — turning repeated work into reusable skills, and keeping the skill library clean as it grows. Derived from the YC conversation with Pete Koomen & Andrej Karpathy on how AI-native organizations build a "shared organizational brain." +Two **meta-skills** that make a coding agent *compound* — turning repeated work into reusable skills, and keeping the skill library clean as it grows — plus the **practice skills** they produce. The meta-skills are derived from the YC conversation with Pete Koomen & Andrej Karpathy on how AI-native organizations build a "shared organizational brain"; the practice skills are what came out of running `skillify` on our own work. | Skill | What it does | |-------|--------------| | [`skillify`](./skillify/SKILL.md) | Capture what you just did as a reusable, parameterized skill, then register it in the resolver. | | [`check-resolvable`](./check-resolvable/SKILL.md) | Audit the whole library so it stays **DRY** (no duplicates) and **MECE** (no overlaps, no gaps). | +## Practice skills + +Procedures captured with `skillify`. Each one exists because reasoning about the answer kept being wrong and rendering it was cheap. + +| Skill | What it does | +|-------|--------------| +| [`verify-responsive`](./verify-responsive/SKILL.md) | Render the real page at real viewport widths in fixed-width iframes, then drive and assert it from the parent. | + ## The decision test Behind every choice these skills make — *should I skillify this? generalize how far? merge or keep separate?* — is one question: @@ -34,14 +42,14 @@ These are standard [Claude Code Agent Skills](https://code.claude.com/docs/en/sk ```bash mkdir -p ~/.claude/skills -cp -R skillify check-resolvable ~/.claude/skills/ +cp -R skillify check-resolvable verify-responsive ~/.claude/skills/ ``` **One project only:** ```bash mkdir -p .claude/skills -cp -R skillify check-resolvable .claude/skills/ +cp -R skillify check-resolvable verify-responsive .claude/skills/ ``` Then add a one-line entry for each to your project's resolver (`AGENTS.md` / `CLAUDE.md`) so the agent — and your teammates — can find them. After that, just say *"skillify this"* when you finish something worth keeping. diff --git a/skills/verify-responsive/SKILL.md b/skills/verify-responsive/SKILL.md new file mode 100644 index 0000000..986e5f5 --- /dev/null +++ b/skills/verify-responsive/SKILL.md @@ -0,0 +1,74 @@ +--- +name: verify-responsive +description: The default way to check a responsive UI — render the real page at real viewport widths in fixed-width iframes, then drive and assert it from the parent. Use whenever you need to see or verify a layout at a given width: a mobile or tablet breakpoint, or small-screen-only behavior. Reach for it first, ahead of resizing a window. +--- + +# Verify Responsive + +"The CSS looks right" is not "it renders right at 390px." A media query only proves itself when something evaluates it at that width. This is §4 applied to UI — *define success criteria, loop until verified* — where the criterion is a rendered page at a real width, not a stylesheet that reads correctly. + +The tool is one throwaway HTML file: fixed-width ` +
1280 × 800
+
+ ``` + + Open `/_viewports.html` and screenshot it. + +4. **Drive it and assert from the parent.** Same-origin, so the frame is fully scriptable — you can reach past what a screenshot shows: + + ```js + const w = m.contentWindow, doc = m.contentDocument; + w.innerWidth; // 390 + w.matchMedia('(max-width: 640px)').matches; // true → the mobile arm really is live + doc.querySelector('[data-testid="menu"]').click(); + doc.querySelector('.drawer').className; // read the result back + w.localStorage.getItem('seen'); // and any state it wrote + ``` + + Assert the width from *inside* the frame before trusting a screenshot. "Looks narrow to me" is how you verify nothing. + +5. **Delete the harness before committing.** Anything in the static dir ships. `rm _viewports.html`, then confirm with `git status` — don't leave it for review to catch. + +## What this does not give you + +The iframe narrows the **viewport**. It does not emulate a device. Measured inside a 390px frame on a laptop: `pointer: fine` and `hover: hover` are both still true, `navigator.maxTouchPoints` is `0`, and `devicePixelRatio` is the host's. + +So a layout that branches on width is covered honestly. One that branches on `@media (hover: hover)`, sniffs the user agent, or swaps assets on DPR will render its **desktop** arm at 390px and mislead you with a screenshot that looks right. There is also no browser chrome, so `100dvh`-vs-`100vh` differences never show up. When the behavior under test depends on any of those, use real device emulation or a real device — this harness will lie to you. + +## Related + +- [`/visualize`](../../commands/visualize.md) draws the *structure* of the code. This renders the *running UI*. Use this one when the question is "what does it actually look like at this width."