From 8a5b710f6e2ac754c76c058029d20a591a8b1e19 Mon Sep 17 00:00:00 2001 From: pallaoro Date: Fri, 11 Sep 2026 13:29:04 +0200 Subject: [PATCH 1/2] feat(wrap): /wrap rules on whether a session can close, or hands it off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The end-of-session question — can I close this and delete the worktree? — gets answered by feel, and both failure modes cost: closing on an unpushed branch or an hour of dead ends that only lived in the context, or leaving half-finished worktrees nobody dares delete. /wrap forces one of two rulings: finish it now, or wrap it into something durable and say it's safe to delete. The test is what dies when the worktree dies, not what feels important. Four hard stops force finishing (the world outside the repo is mid-change, the trunk is worse than you found it, what's left is minutes of work, or you can't write the brief). Otherwise it picks the cheapest durable form that carries the work — sidenote, committed doc, issue, or pushed branch plus draft PR — writes a five-line brief including the approaches already rejected, confirms the artifact landed outside the worktree, and ends on a verdict line. A PR needs at least one commit, so "a PR with only a description" is either an issue or a PR whose diff is the brief committed as a doc; the ladder routes accordingly. Installer: the three hardcoded command copies become a COMMANDS array, mirroring SKILLS, so the next command is a one-word edit. --- .claude-plugin/marketplace.json | 4 +- .claude-plugin/plugin.json | 2 +- README.md | 22 +++++++++-- bin/install.js | 12 +++--- commands/wrap.md | 70 +++++++++++++++++++++++++++++++++ package.json | 2 +- 6 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 commands/wrap.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 81468cc..d68b3dd 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, the verify-responsive practice skill, 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, /visualize and /wrap commands.", "owner": { "name": "Clawnify", "url": "https://github.com/clawnify" @@ -9,7 +9,7 @@ "plugins": [ { "name": "greybeard", - "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.", + "description": "skillify + check-resolvable + verify-responsive skills and the /pressure-test, /sidenote, /visualize and /wrap 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 ddf4a6e..317afa8 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), verify-responsive (render a UI at real viewport widths), 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, /visualize and /wrap commands.", "author": { "name": "Clawnify", "url": "https://github.com/clawnify" diff --git a/README.md b/README.md index 4a69eb6..14a1377 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 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. +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 four commands — [`/pressure-test`](#the-pressure-test-command), the decision test, [`/sidenote`](#the-sidenote-command), park-a-thought, [`/visualize`](#the-visualize-command), draw-the-real-shape, and [`/wrap`](#the-wrap-command), can-I-close-this — and a [one-command installer](#install) that fans it all out to every AI coding agent you use. ## The Problems @@ -192,9 +192,9 @@ And the **practice skills** those two produce — procedures captured with `skil 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 +## The four commands -Greybeard's slash commands are **`/pressure-test`**, **`/sidenote`** and **`/visualize`** — one guards the *quality* of a decision, one guards your *focus* while you make it, and one makes sure what you're shown is the code that's actually there. The skills and always-on guidelines back them up, but these are the three you'll reach for by hand. +Greybeard's slash commands are **`/pressure-test`**, **`/sidenote`**, **`/visualize`** and **`/wrap`** — one guards the *quality* of a decision, one guards your *focus* while you make it, one makes sure what you're shown is the code that's actually there, and one decides whether the session can end. The skills and always-on guidelines back them up, but these are the four you'll reach for by hand. ## The `/pressure-test` command @@ -238,6 +238,20 @@ cp commands/visualize.md ~/.claude/commands/ # personal, all projects Then `/visualize `, or a bare `/visualize` to draw whatever's currently on the table. +## The `/wrap` command + +The question at the end of every session: *can I close this and delete the worktree?* Get it wrong one way and you lose work — an unpushed branch, or the hour of dead ends that only ever existed in the context. Get it wrong the other way and sessions pile up, half-finished, each one a tab nobody dares close. + +[`commands/wrap.md`](./commands/wrap.md) makes the agent rule on it, and there are only two rulings: **finish it now**, or **wrap it** — turn what's here into something durable and say it's safe to delete. The test isn't *is this important*, it's **what dies when the worktree dies**: uncommitted edits, untracked files, commits that never left the machine, and the part no diff carries — what you tried, what you rejected, and why. Four hard stops force *finish now* (the world outside the repo is mid-change; the trunk is worse than you found it; what's left is minutes of work — §5's pre-AI estimate again; or you can't write the brief, which means you don't understand the state well enough to hand it off). Otherwise it picks the cheapest durable form that actually carries the work — a `/sidenote` entry, a committed doc, a GitHub issue, or the branch pushed with a draft PR — writes the five-line brief (goal / done / left / **rejected** / verify, anchored to `file:line`), shows you the artifact before creating it, and confirms it landed *outside* the worktree before answering. An unpushed commit is not a handoff. + +```bash +mkdir -p ~/.claude/commands +cp commands/wrap.md ~/.claude/commands/ # personal, all projects +# or: .claude/commands/ for one project +``` + +Then a bare `/wrap` at the point you'd otherwise close the tab. It ends on the verdict line — `Safe to close — carries it.` or `Not yet — has to close here first.` — and hands you the `git worktree remove` command rather than running it, since it's standing in the worktree it would delete. + ## Install **One command — every agent on your machine:** @@ -246,7 +260,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, at two tiers: what's **installed on your system** (Claude Code, OpenCode, OpenClaw) gets its global files once — the guidelines into `~/.claude/CLAUDE.md`, the `skillify` / `check-resolvable` / `verify-responsive` skills, the `/pressure-test`, `/sidenote` and `/visualize` commands, and the OpenCode guidelines plugin; what's **used in this repo** (its rule file or directory exists here — `AGENTS.md`, `GEMINI.md`, `copilot-instructions.md`, `.cursor/`, `.windsurf/`, `.clinerules/`) gets the seven guidelines in the format it reads. Having an agent installed on your machine never sprinkles rule files into repos that don't use it — adopt one there with `--all` or `--only `. 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, at two tiers: what's **installed on your system** (Claude Code, OpenCode, OpenClaw) gets its global files once — the guidelines into `~/.claude/CLAUDE.md`, the `skillify` / `check-resolvable` / `verify-responsive` skills, the `/pressure-test`, `/sidenote`, `/visualize` and `/wrap` commands, and the OpenCode guidelines plugin; what's **used in this repo** (its rule file or directory exists here — `AGENTS.md`, `GEMINI.md`, `copilot-instructions.md`, `.cursor/`, `.windsurf/`, `.clinerules/`) gets the seven guidelines in the format it reads. Having an agent installed on your machine never sprinkles rule files into repos that don't use it — adopt one there with `--all` or `--only `. 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 05d0298..2dc1d20 100644 --- a/bin/install.js +++ b/bin/install.js @@ -5,7 +5,7 @@ // // Detects the AI coding agents on your machine / in this project and installs // the Karpathy-inspired guidelines (+ Claude Code skills and the /pressure-test, -// /sidenote and /visualize commands) into each one's rule location. Pure Node +// /sidenote, /visualize and /wrap commands) into each one's rule location. Pure Node // stdlib, zero runtime deps. // // npx @clawnify/greybeard # install for every detected agent @@ -148,6 +148,8 @@ function rel(p) { return p.startsWith(HOME) ? '~' + p.slice(HOME.length) : path. 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']; +// Slash commands shipped to every provider that reads commands. Add a new one here only. +const COMMANDS = ['pressure-test', 'sidenote', 'visualize', 'wrap']; // ── Per-provider install / uninstall ──────────────────────────────────────── // The OpenCode plugin serves both `opencode` (V1) and `opencode2` (V2 beta): @@ -164,17 +166,13 @@ function installClaude(dir, un) { removeFenceFrom(path.join(dir, 'CLAUDE.md')); 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')); - removePath(path.join(dir, 'commands', 'visualize.md')); + for (const c of COMMANDS) removePath(path.join(dir, 'commands', `${c}.md`)); return; } fenceInto(path.join(dir, 'CLAUDE.md'), PRINCIPLES); 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')); - writeFile(path.join(dir, 'commands', 'visualize.md'), fs.readFileSync(path.join(PKG, 'commands', 'visualize.md'), 'utf8')); + for (const c of COMMANDS) writeFile(path.join(dir, 'commands', `${c}.md`), fs.readFileSync(path.join(PKG, 'commands', `${c}.md`), 'utf8')); } function applyProvider(p, un) { diff --git a/commands/wrap.md b/commands/wrap.md new file mode 100644 index 0000000..d66f87a --- /dev/null +++ b/commands/wrap.md @@ -0,0 +1,70 @@ +--- +description: Rule on whether this session can end here — finish the loop now, or make the work durable enough that another agent can pick it up. +argument-hint: [what to wrap — optional] +--- + +Answer one question about **$ARGUMENTS** — or, if that's empty, about this whole session: **can I close this and delete the worktree?** + +There are two rulings, and you owe me exactly one of them: **finish it now**, or **wrap it** — turn what's here into something durable and tell me it's safe to delete. "Mostly done, maybe keep it around" is not a ruling — it is the third outcome, the one you get by declining to choose, and it is the most expensive of the three: a worktree nobody will reopen and nobody dares delete. + +**The test is not "is this important" — it's "what dies when the worktree dies, and can I afford to lose it?"** A worktree is a checkout. It is cheap to recreate and carries nothing by itself. Everything load-bearing either lives somewhere outside it or is about to be deleted. + +## First: is there a hard stop? + +Four things override every other consideration. If any holds, the ruling is **finish now** — no artifact substitutes, and "I'll open an issue for it" is how it ships broken. + +- **The world outside the repo is mid-change.** A migration ran against a shared database, a flag got flipped, a deploy started, a resource or credential was created or rotated, a backfill is half-applied. A ticket in a backlog does not un-break a shared system. Finish it *or revert it* — both close the loop; walking away closes neither. +- **You left the trunk worse than you found it.** Pushed something broken, merged half a revert, left CI red on `main`. That cost lands on whoever starts next, who has none of your context. +- **What's left is minutes of work.** A handoff has a fixed re-entry cost — rebuilding the context, re-reading the files, re-deriving the options you already rejected — and it is rarely under half an hour. If that exceeds the work remaining, wrapping is the *more* expensive choice. "It needs another session" is usually a pre-AI estimate (§5): price the real thing before you park it. +- **You can't write the brief.** If you can't say in a paragraph what's left and how the next agent would know it's done, you don't understand the state well enough to hand it off — and they will understand it less. Work until it's describable, then wrap. + +## Then: inventory what's actually here + +Look, don't recall. Run the checks and report what they said: + +- `git status --porcelain` — uncommitted edits *and* untracked files. Untracked is where the new file you never `git add`ed is hiding. +- `git log --oneline @{upstream}..HEAD` — commits that exist only on this machine. `fatal: no upstream configured for branch` is not a failure, it's the answer: the branch has never been pushed, so **all** of it is local — `git log --oneline --not --remotes` lists it. +- `git stash list` — the stack is shared across worktrees; an entry pushed from here outlives the worktree but loses the only person who knew what it was. +- Scratch files, notes, or outputs you wrote outside the repo. +- Anything still running that this session started. + +Then the item that never shows up in any of those, and is the reason most handoffs are useless: **the context.** What you tried that didn't work, why the obvious approach is wrong, the reproduction that took twenty minutes to find, the file you ruled out. The diff survives; that dies with the session. It is the most expensive thing in the worktree and the only one with no automatic record. + +## The ladder — pick the cheapest form that actually carries it + +One artifact, not three. Match the form to what has to survive: + +| What's here | Where it goes | +|---|---| +| Nothing — merged, clean, no unpushed commits | Nowhere. Say so and close. | +| A lead or a hunch. No code, no decision | A `/sidenote` entry — it already lives in git's common dir, outside every worktree. | +| A decision, a design, knowledge the next change needs | A doc in the repo, committed **and pushed**. Memory is an index, not an archive (§7). | +| A defined next task, nothing written yet | A **GitHub issue**: title + the brief below. | +| Code exists and is incomplete | Commit it on the branch, **push it**, open a **draft PR** whose body is the brief. | + +Two notes on that last pair. A pull request needs at least one commit — there is no PR with an empty diff, so "a PR with just a description" is either an *issue*, or a PR whose diff **is** the brief committed as a doc. And once the branch is pushed, the branch is the artifact: deleting the worktree costs you nothing. Never delete a worktree whose branch is unpushed and unmerged — that is the one deletion that actually loses work. + +## The brief — the part the diff can't carry + +Whichever form you picked, the body is the same five lines, written for someone who was not here: + +``` +Goal — what this is for, in one line. +Done — what works now, and the check you actually ran to know it. +Left — the exact next step. Not "continue the refactor". +Rejected — approaches that don't work, and why. This is the part that dies with the context. +Verify — the command or check that says it's finished. +``` + +Anchor it: the `file:line` where the next agent starts reading. And keep it honest — if you never ran the test, "tests not run" goes in the brief. A caveat about your own work is an unmet success criterion (§4), not a footnote to bury; the next agent will trust this document instead of checking, exactly as you would. + +## Execute, verify, then answer the question + +1. **Show me the ruling and the artifact before you create it** — the form, the title, the body. Issues, PRs and pushes are outward-facing and awkward to take back; one confirmation, then go. +2. **Confirm it landed outside the worktree.** The push shows the branch on the remote; `gh issue create --title "…" --body-file …` and `gh pr create --draft --title "…" --body-file …` each print a URL — quote it. A commit that was never pushed is not a handoff, it is the same work in a smaller box (§4: it compiles is not it works). +3. **Don't run the deletion.** You are standing in the worktree; removing it from inside is how a session ends mid-sentence. Hand me the command — `git worktree remove `, run from another checkout — and say plainly if it needs `-f`, because a dirty tree means step 2 didn't finish. +4. **Close with the verdict on its own line**, because it's the question I asked: + - `Safe to close — carries it.` + - `Not yet — has to close here first.` + +**Where this sits next to `/sidenote`:** a sidenote parks a *thought* so the current task keeps its pace; `/wrap` rules on the *session*. An open sidenote never blocks a wrap — but if this branch was the only context that made one legible, fold it into the brief before the worktree goes. diff --git a/package.json b/package.json index f716c34..9ca6b64 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@clawnify/greybeard", "version": "0.3.0", - "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.", + "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, /visualize and /wrap commands, installable across every AI coding agent.", "license": "MIT", "homepage": "https://github.com/clawnify/greybeard", "repository": { From af553798cf7efacf8a3890aad562ad5036b795db Mon Sep 17 00:00:00 2001 From: pallaoro Date: Fri, 11 Sep 2026 14:16:56 +0200 Subject: [PATCH 2/2] fix(install): hand Claude Code to the plugin channel, stop copying duplicates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code could get greybeard two ways at once: the plugin (skills and commands, namespaced, updated with `claude plugin update`) and this installer copying the same files into ~/.claude/skills and ~/.claude/commands. Anyone who had both carried two copies of every command in the resolver, and they drift the moment one side updates — on the machine this was found on, the plugin was 11 commits behind while the copied files were current, so /pressure-test and greybeard:pressure-test were different commands. installClaude now fences the guidelines into CLAUDE.md and hands everything else to Claude Code's own plugin manager: `marketplace add` + `install` when the plugin is absent, `marketplace update` + `update` when it's there. State comes from `claude plugin list --json` rather than matched out of human output, and only a real version change is reported, so a re-run stays a true no-op. Without the claude CLI on PATH it warns with the two commands to run. The old copies are removed on every run, install or uninstall, so an existing machine de-duplicates itself on the next `npx @clawnify/greybeard` — same migration trick already used for the pre-rename scalable.md. Docs: both READMEs stopped telling people to cp the files into ~/.claude, which was the instruction that created the duplicate, and now point at the plugin (noting the commands arrive namespaced). Copying stays documented for harnesses that have no plugin manager. --- README.md | 44 ++++++++--------------- bin/install.js | 93 +++++++++++++++++++++++++++++++++++++++++------- skills/README.md | 14 ++++---- 3 files changed, 103 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 14a1377..1ee7c2f 100644 --- a/README.md +++ b/README.md @@ -196,17 +196,21 @@ They're standard [Claude Code Agent Skills](https://code.claude.com/docs/en/skil Greybeard's slash commands are **`/pressure-test`**, **`/sidenote`**, **`/visualize`** and **`/wrap`** — one guards the *quality* of a decision, one guards your *focus* while you make it, one makes sure what you're shown is the code that's actually there, and one decides whether the session can end. The skills and always-on guidelines back them up, but these are the four you'll reach for by hand. -## The `/pressure-test` command - -[`commands/pressure-test.md`](./commands/pressure-test.md) is a Claude Code slash command that runs the §5 decision test on demand: pressure-test the approach on the table against **scalable / long-term / efficient** — *whatever is scalable, long term, and cannot be done in a more efficient way* — and get one decisive recommendation, with stale-time-budget shortcuts called out. The main context runs the test and owns the ruling; subagents are scoped instruments, spawned only where a check benefits from being outside the session — challenging a belief the conversation already holds (a fresh context can't inherit its anchoring), or online research and docs verification that parallelize while the main agent keeps reasoning. Never the test wholesale: a main agent that only orchestrates stops being the main brain. Every ruling ships with a claims ledger: each load-bearing claim — one per plan item at minimum, every size-word counts — cited to the `file:line` actually opened, or tagged **ASSUMED**; no estimate may attach to an assumed claim. +**Getting them.** The commands ship inside the greybeard plugin, so Claude Code installs and updates them through its own plugin manager — `npx @clawnify/greybeard` does it for you, or do it by hand: ```bash -mkdir -p ~/.claude/commands -cp commands/pressure-test.md ~/.claude/commands/ # personal, all projects -# or: .claude/commands/ for one project +claude plugin marketplace add clawnify/greybeard +claude plugin install greybeard@greybeard +claude plugin update greybeard@greybeard # later, to pull new commands ``` -Then `/pressure-test ` to test one call in depth, or bare `/pressure-test` to sweep every solution the session proposed — an inventory with a verdict on each (the holds stated as plainly as the fails), plus the section only the sweep produces: the decisions that were never examined at all. +Don't also copy the files into `~/.claude/commands/`: you'd have two copies of every command in the resolver, drifting apart the moment one side updates. Plugin commands are namespaced — `/greybeard:pressure-test`, `/greybeard:wrap` — so the bare names used below are how they read in a harness where you drop the file in yourself. + +## The `/pressure-test` command + +[`commands/pressure-test.md`](./commands/pressure-test.md) is a Claude Code slash command that runs the §5 decision test on demand: pressure-test the approach on the table against **scalable / long-term / efficient** — *whatever is scalable, long term, and cannot be done in a more efficient way* — and get one decisive recommendation, with stale-time-budget shortcuts called out. The main context runs the test and owns the ruling; subagents are scoped instruments, spawned only where a check benefits from being outside the session — challenging a belief the conversation already holds (a fresh context can't inherit its anchoring), or online research and docs verification that parallelize while the main agent keeps reasoning. Never the test wholesale: a main agent that only orchestrates stops being the main brain. Every ruling ships with a claims ledger: each load-bearing claim — one per plan item at minimum, every size-word counts — cited to the `file:line` actually opened, or tagged **ASSUMED**; no estimate may attach to an assumed claim. + +Use `/pressure-test ` to test one call in depth, or bare `/pressure-test` to sweep every solution the session proposed — an inventory with a verdict on each (the holds stated as plainly as the fails), plus the section only the sweep produces: the decisions that were never examined at all. ## The `/sidenote` command @@ -214,13 +218,7 @@ You're mid-way through a big task and a passing thought hits — *"the retry log [`commands/sidenote.md`](./commands/sidenote.md) reframes that thought as a **parked later-task, not a now-task**. The agent logs it verbatim to a parking file (durable across compaction, session end, and worktree removal) — with a one-line anchor (task in progress, the file it points at, branch) so it still makes sense when another session or agent reads it cold — gives a one-line ack, and resumes *exactly* where it was — same scope, same pace, no cutting corners to reach the note. A bare `/sidenote` flushes the open list back to you. -```bash -mkdir -p ~/.claude/commands -cp commands/sidenote.md ~/.claude/commands/ # personal, all projects -# or: .claude/commands/ for one project -``` - -Then `/sidenote ` to park one, or `/sidenote` to see what's parked. The notes land in `$(git rev-parse --path-format=absolute --git-common-dir)/info/sidenotes.md` — inside `.git/`, so they are personal per-repo scratch that no ignore rule has to protect and no `git add -A` can sweep into a PR, shared across all worktrees of the repo. Outside a git repo the fallback is `.claude/sidenotes.md` at the project root. +Use `/sidenote ` to park one, or `/sidenote` to see what's parked. The notes land in `$(git rev-parse --path-format=absolute --git-common-dir)/info/sidenotes.md` — inside `.git/`, so they are personal per-repo scratch that no ignore rule has to protect and no `git add -A` can sweep into a PR, shared across all worktrees of the repo. Outside a git repo the fallback is `.claude/sidenotes.md` at the project root. > **No slash commands?** For agents that read a rule file but have no `/` commands (Cursor, Codex, Copilot…), the same contract works as a plain-text convention: prefix the message with `SIDENOTE:` and the agent parks it instead of acting. Add one line to your rule file so it's honored reliably — see [`commands/sidenote.md`](./commands/sidenote.md) for the exact contract. @@ -230,13 +228,7 @@ Ask an agent to explain how something works and you get paragraphs. Ask it to *d [`commands/visualize.md`](./commands/visualize.md) is the §7 rule applied to pictures. It gives the agent a menu of visual forms — pseudocode, call tree, component tree, file tree, text sequence diagrams, shaped `diff`s, or one focused HTML file — and tells it to pick the *smallest* one that makes the point, **matched to the surface it lands on**: a terminal has no diagram renderer, so a `mermaid` fence there just prints its own source; the text forms are the picture already. Mermaid is reserved for surfaces that actually render it. Then the Greybeard part: **read the code before drawing it** (the actual files, this session — if the diagram has five nodes, you opened five things), **anchor every node** to a real `file:line`, **trace the current shape with the real input** (an anchor proves the node exists, not that the arrow fires — if the concrete key dead-ends in the registry, the dead end is the picture), and **tag anything unread as ASSUMED** with the check that would settle it. A diagram that's 90% verified and 10% quietly invented is 100% untrustworthy. -```bash -mkdir -p ~/.claude/commands -cp commands/visualize.md ~/.claude/commands/ # personal, all projects -# or: .claude/commands/ for one project -``` - -Then `/visualize `, or a bare `/visualize` to draw whatever's currently on the table. +Use `/visualize `, or a bare `/visualize` to draw whatever's currently on the table. ## The `/wrap` command @@ -244,13 +236,7 @@ The question at the end of every session: *can I close this and delete the workt [`commands/wrap.md`](./commands/wrap.md) makes the agent rule on it, and there are only two rulings: **finish it now**, or **wrap it** — turn what's here into something durable and say it's safe to delete. The test isn't *is this important*, it's **what dies when the worktree dies**: uncommitted edits, untracked files, commits that never left the machine, and the part no diff carries — what you tried, what you rejected, and why. Four hard stops force *finish now* (the world outside the repo is mid-change; the trunk is worse than you found it; what's left is minutes of work — §5's pre-AI estimate again; or you can't write the brief, which means you don't understand the state well enough to hand it off). Otherwise it picks the cheapest durable form that actually carries the work — a `/sidenote` entry, a committed doc, a GitHub issue, or the branch pushed with a draft PR — writes the five-line brief (goal / done / left / **rejected** / verify, anchored to `file:line`), shows you the artifact before creating it, and confirms it landed *outside* the worktree before answering. An unpushed commit is not a handoff. -```bash -mkdir -p ~/.claude/commands -cp commands/wrap.md ~/.claude/commands/ # personal, all projects -# or: .claude/commands/ for one project -``` - -Then a bare `/wrap` at the point you'd otherwise close the tab. It ends on the verdict line — `Safe to close — carries it.` or `Not yet — has to close here first.` — and hands you the `git worktree remove` command rather than running it, since it's standing in the worktree it would delete. +Reach for a bare `/wrap` at the point you'd otherwise close the tab. It ends on the verdict line — `Safe to close — carries it.` or `Not yet — has to close here first.` — and hands you the `git worktree remove` command rather than running it, since it's standing in the worktree it would delete. ## Install @@ -260,7 +246,7 @@ Then a bare `/wrap` at the point you'd otherwise close the tab. It ends on the v npx @clawnify/greybeard ``` -It detects the AI coding agents you actually use and installs the right files for each, at two tiers: what's **installed on your system** (Claude Code, OpenCode, OpenClaw) gets its global files once — the guidelines into `~/.claude/CLAUDE.md`, the `skillify` / `check-resolvable` / `verify-responsive` skills, the `/pressure-test`, `/sidenote`, `/visualize` and `/wrap` commands, and the OpenCode guidelines plugin; what's **used in this repo** (its rule file or directory exists here — `AGENTS.md`, `GEMINI.md`, `copilot-instructions.md`, `.cursor/`, `.windsurf/`, `.clinerules/`) gets the seven guidelines in the format it reads. Having an agent installed on your machine never sprinkles rule files into repos that don't use it — adopt one there with `--all` or `--only `. 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, at two tiers: what's **installed on your system** (Claude Code, OpenCode, OpenClaw) gets its global files once — the guidelines into `~/.claude/CLAUDE.md`, the greybeard plugin installed (or updated) through Claude Code's own `claude plugin` CLI, which is where its skills and commands live, the `skillify` / `check-resolvable` / `verify-responsive` skills for OpenClaw, and the OpenCode guidelines plugin; what's **used in this repo** (its rule file or directory exists here — `AGENTS.md`, `GEMINI.md`, `copilot-instructions.md`, `.cursor/`, `.windsurf/`, `.clinerules/`) gets the seven guidelines in the format it reads. Having an agent installed on your machine never sprinkles rule files into repos that don't use it — adopt one there with `--all` or `--only `. 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 2dc1d20..c359ccf 100644 --- a/bin/install.js +++ b/bin/install.js @@ -4,9 +4,10 @@ // greybeard — cross-platform installer. // // Detects the AI coding agents on your machine / in this project and installs -// the Karpathy-inspired guidelines (+ Claude Code skills and the /pressure-test, -// /sidenote, /visualize and /wrap commands) into each one's rule location. Pure Node -// stdlib, zero runtime deps. +// the Karpathy-inspired guidelines into each one's rule location. Claude Code's +// skills and commands come from the greybeard plugin, installed and kept current +// through Claude Code's own `claude plugin` CLI. Pure Node stdlib, zero runtime +// deps. // // npx @clawnify/greybeard # install for every detected agent // npx @clawnify/greybeard --all # install for all agents, detected or not @@ -17,6 +18,7 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); +const { spawnSync } = require('child_process'); const PKG = path.resolve(__dirname, '..'); const HOME = os.homedir(); @@ -39,7 +41,7 @@ const HANDMERGE_MARK = '## 1. Think Before Coding'; // installed on the machine must not sprinkle rule files into every repo. // Use --all or --only to adopt an agent in a repo it doesn't use yet. // kind: how to install — -// claude global ~/.claude: principles block + skills/ + commands/ +// claude global ~/.claude: principles block + the greybeard plugin // openclaw global ~/.openclaw/workspace/skills: skills only // opencode global ~/.config/opencode/plugins: guidelines plugin // file project: write the dedicated rule file verbatim (overwrite) @@ -148,8 +150,11 @@ function rel(p) { return p.startsWith(HOME) ? '~' + p.slice(HOME.length) : path. 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']; -// Slash commands shipped to every provider that reads commands. Add a new one here only. -const COMMANDS = ['pressure-test', 'sidenote', 'visualize', 'wrap']; +// Command files earlier versions copied into ~/.claude/commands (plus 'scalable', +// the pre-rename name of pressure-test). The plugin ships these now; this list +// exists only to clear the copies off machines that still carry them. Nothing to +// add here for a new command — the plugin picks it up from commands/. +const LEGACY_COMMAND_COPIES = ['pressure-test', 'sidenote', 'visualize', 'wrap', 'scalable']; // ── Per-provider install / uninstall ──────────────────────────────────────── // The OpenCode plugin serves both `opencode` (V1) and `opencode2` (V2 beta): @@ -161,20 +166,82 @@ function installOpencode(un) { return writeFile(dest, fs.readFileSync(path.join(PKG, 'hooks', 'greybeard-opencode.js'), 'utf8')); } +// Claude Code gets the guidelines as a fenced block in ~/.claude/CLAUDE.md, and +// everything else — skills and commands — from the greybeard plugin, through +// Claude Code's own plugin manager. Earlier versions copied those files into +// ~/.claude/skills and ~/.claude/commands as well; anyone who also had the +// plugin ended up with two copies of every command in the resolver, which drift +// the moment one channel updates. The copies are removed on every run, install +// or uninstall, so an existing machine de-duplicates itself. function installClaude(dir, un) { + for (const s of SKILLS) removePath(path.join(dir, 'skills', s)); + for (const c of LEGACY_COMMAND_COPIES) removePath(path.join(dir, 'commands', `${c}.md`)); if (un) { removeFenceFrom(path.join(dir, 'CLAUDE.md')); - for (const s of SKILLS) removePath(path.join(dir, 'skills', s)); - removePath(path.join(dir, 'commands', 'scalable.md')); // legacy name, pre-rename - for (const c of COMMANDS) removePath(path.join(dir, 'commands', `${c}.md`)); - return; + return ensurePlugin(true); } fenceInto(path.join(dir, 'CLAUDE.md'), PRINCIPLES); - 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 - for (const c of COMMANDS) writeFile(path.join(dir, 'commands', `${c}.md`), fs.readFileSync(path.join(PKG, 'commands', `${c}.md`), 'utf8')); + return ensurePlugin(false); +} + +// ── The Claude Code plugin channel ────────────────────────────────────────── +const PLUGIN_ID = 'greybeard@greybeard'; +const MARKETPLACE_SRC = 'clawnify/greybeard'; // what `marketplace add` takes +const MARKETPLACE_NAME = 'greybeard'; // what it's called once added + +const lastLine = (s) => (s || '').split('\n').filter(Boolean).pop() || 'no output'; + +function claudeCli(args) { + const r = spawnSync('claude', args, { encoding: 'utf8', shell: process.platform === 'win32' }); + if (r.error) return { missing: r.error.code === 'ENOENT' }; + return { ok: r.status === 0, out: `${r.stdout || ''}${r.stderr || ''}`.trim() }; +} + +// Installed version at user scope, or null. Read from --json rather than matched +// out of human output, so a wording change upstream can't silently break this. +function installedVersion(probe) { + if (!probe.ok) return null; + try { + const hit = JSON.parse(probe.out).find((p) => p.id === PLUGIN_ID && p.scope === 'user'); + return hit ? hit.version : null; + } catch (e) { + return null; + } +} + +function ensurePlugin(un) { + const probe = claudeCli(['plugin', 'list', '--json']); + if (probe.missing) { + warnings.push(un + ? `claude CLI not on PATH — run \`claude plugin uninstall ${PLUGIN_ID}\` to remove the skills and commands.` + : `claude CLI not on PATH — run \`claude plugin marketplace add ${MARKETPLACE_SRC}\` then \`claude plugin install ${PLUGIN_ID}\` to get the skills and commands.`); + return; + } + const before = installedVersion(probe); + if (un) { + if (!before) return; + if (DRY) { actions.push('would uninstall the greybeard plugin'); return; } + const r = claudeCli(['plugin', 'uninstall', PLUGIN_ID, '-y', '--scope', 'user']); + if (!r.ok) return warnings.push(`claude plugin uninstall ${PLUGIN_ID} failed: ${lastLine(r.out)}`); + actions.push('uninstalled the greybeard plugin'); + return; + } + if (DRY) { actions.push(`would ${before ? 'update' : 'install'} the greybeard plugin`); return; } + const steps = before + ? [['plugin', 'marketplace', 'update', MARKETPLACE_NAME], ['plugin', 'update', PLUGIN_ID, '-y', '--scope', 'user']] + : [['plugin', 'marketplace', 'add', MARKETPLACE_SRC], ['plugin', 'install', PLUGIN_ID, '-y', '--scope', 'user']]; + for (const args of steps) { + const r = claudeCli(args); + if (!r.ok) return warnings.push(`claude ${args.join(' ')} failed: ${lastLine(r.out)}`); + } + // Report only a real change, so a re-run stays a true no-op in the summary. + const after = installedVersion(claudeCli(['plugin', 'list', '--json'])); + if (after && after !== before) { + actions.push(`${before ? 'updated' : 'installed'} the greybeard plugin \u2192 ${after} (restart Claude Code to apply)`); + } } + function applyProvider(p, un) { if (p.kind === 'claude') return installClaude(expand('~/.claude'), un); if (p.kind === 'opencode') return installOpencode(un); diff --git a/skills/README.md b/skills/README.md index e478c39..e4030a1 100644 --- a/skills/README.md +++ b/skills/README.md @@ -38,18 +38,20 @@ The library is only as valuable as the resolver is clean: These are standard [Claude Code Agent Skills](https://code.claude.com/docs/en/skills) — a directory with a `SKILL.md`. The `name` + `description` frontmatter is portable to any harness that reads skills (Cursor, Codex, OpenClaw). -**Personal (all your projects):** +**Claude Code** installs them as part of the greybeard plugin, through its own plugin manager — no copying, and `claude plugin update` keeps them current: ```bash -mkdir -p ~/.claude/skills -cp -R skillify check-resolvable verify-responsive ~/.claude/skills/ +claude plugin marketplace add clawnify/greybeard +claude plugin install greybeard@greybeard ``` -**One project only:** +(`npx @clawnify/greybeard` runs those two for you.) Don't also copy the directories into `~/.claude/skills/` — that puts two copies of every skill in the resolver, which is the exact thing `check-resolvable` exists to catch. + +**Any other harness** reads skills from a directory, so copy them in: ```bash -mkdir -p .claude/skills -cp -R skillify check-resolvable verify-responsive .claude/skills/ +mkdir -p ~/.claude/skills +cp -R skillify check-resolvable verify-responsive ~/.claude/skills/ # or .claude/skills/ for one project ``` 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.