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.