Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 15 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,31 +196,29 @@ 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 <a specific decision>` 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 <a specific decision>` 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

You're mid-way through a big task and a passing thought hits — *"the retry logic probably has the same bug", "remind me to check the pricing later"* — something you don't want to forget. Drop it in the chat as-is and the agent reads it as a new request: it rushes the current task to "get to" your note, and the big task pays for it.

[`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 <the thought>` 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 <the thought>` 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.

Expand All @@ -230,27 +228,15 @@ 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 <what to draw>`, or a bare `/visualize` to draw whatever's currently on the table.
Use `/visualize <what to draw>`, 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 — <URL> carries it.` or `Not yet — <the one thing> 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 — <URL> carries it.` or `Not yet — <the one thing> 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

Expand All @@ -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 <agent>`. 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 <agent>`. 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
Expand Down
93 changes: 80 additions & 13 deletions bin/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
Expand All @@ -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 <id> 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)
Expand Down Expand Up @@ -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):
Expand All @@ -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);
Expand Down
Loading
Loading