From 6776727f55495647b25aa5b814eca7a19d65fd6d Mon Sep 17 00:00:00 2001 From: pallaoro Date: Fri, 17 Jul 2026 08:55:24 +0200 Subject: [PATCH 1/5] feat(commands): add /sidenote to park a thought without derailing the current task A passing thought dropped mid-task reads as a new request, so the agent rushes the big task to get to it. /sidenote reframes it as a parked later-task: log it verbatim to .claude/sidenotes.md, one-line ack, resume with scope and pace unchanged. Bare /sidenote flushes the list. Wired into the installer (copy on install, remove on uninstall) and documented in the README alongside /scalable, including the plain-text SIDENOTE: prefix for agents without slash commands. --- README.md | 18 +++++++++++++++++- bin/install.js | 2 ++ commands/sidenote.md | 21 +++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 commands/sidenote.md diff --git a/README.md b/README.md index 2b356f2..1833354 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,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), the [`/scalable`](#the-scalable-command) decision command, 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 two runnable [meta-skills](#skills-skillify-dry--mece-resolvers), the [`/scalable`](#the-scalable-command) decision command, the [`/sidenote`](#the-sidenote-command) park-a-thought command, and a [one-command installer](#install) that fans it all out to every AI coding agent you use. ## The Problems @@ -192,6 +192,22 @@ cp commands/scalable.md ~/.claude/commands/ # personal, all projects Then `/scalable` (tests the current direction) or `/scalable `. +## 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 `.claude/sidenotes.md` (durable across compaction and session end), 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 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 `.claude/sidenotes.md` — add it to `.gitignore` if you'd rather not track it. + +> **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. + ## Install **One command — every agent on your machine:** diff --git a/bin/install.js b/bin/install.js index e63c17b..2d88bf6 100644 --- a/bin/install.js +++ b/bin/install.js @@ -130,12 +130,14 @@ function installClaude(dir, un) { removePath(path.join(dir, 'skills', 'skillify')); removePath(path.join(dir, 'skills', 'check-resolvable')); removePath(path.join(dir, 'commands', 'scalable.md')); + removePath(path.join(dir, 'commands', 'sidenote.md')); 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')); writeFile(path.join(dir, 'commands', 'scalable.md'), fs.readFileSync(path.join(PKG, 'commands', 'scalable.md'), 'utf8')); + writeFile(path.join(dir, 'commands', 'sidenote.md'), fs.readFileSync(path.join(PKG, 'commands', 'sidenote.md'), 'utf8')); } function applyProvider(p, un) { diff --git a/commands/sidenote.md b/commands/sidenote.md new file mode 100644 index 0000000..1cb6a6d --- /dev/null +++ b/commands/sidenote.md @@ -0,0 +1,21 @@ +--- +description: Park a passing thought as a later-task without derailing the current one — logged, not acted on. +argument-hint: [thought to park — optional] +--- + +Park this thought — **$ARGUMENTS** — as a sidenote. A sidenote is a *later-task*, not a *now-task*. Your job is to record it and keep going, nothing more. + +**The contract — hold it exactly:** + +- **Do not act on it.** Don't investigate it, don't open the files it mentions, don't add it to your current plan, don't answer the question it poses. It is parked, not pending. +- **Do not let it touch the current task.** Its scope, plan, priorities, and *pace* stay exactly as they were. In particular: **do not speed up or cut corners to "get to" the note.** The whole point of parking it is that it costs the current work nothing. +- **Urgency doesn't override the contract.** If the note reads as urgent ("prod is down", "this is broken"), that's the wrong channel — a real emergency is a normal interruption, not a sidenote. Still just log it and resume; the human will escalate directly if they meant to. + +**Persist it so it survives this session** (context gets compacted; memory doesn't count as "logged"): + +1. Append one bullet to `.claude/sidenotes.md` at the project root — create the file (and `.claude/`) if missing. +2. Format: `- [] `. Don't paraphrase or "improve" it — capture what was said. + +Then: **one-line acknowledgement** (e.g. `Parked in .claude/sidenotes.md — continuing.`) and **resume exactly where you left off**, as if the note had never arrived. + +**If `$ARGUMENTS` is empty** (a bare `/sidenote`), there's nothing to park — treat it as a *flush*: read `.claude/sidenotes.md` and list the currently-open sidenotes back to me so I can decide what to pick up. Change nothing, act on nothing, then resume. From d67ea9f8179356986d355f6e4a4a2e7822aa8391 Mon Sep 17 00:00:00 2001 From: pallaoro Date: Fri, 17 Jul 2026 11:46:59 +0200 Subject: [PATCH 2/5] fix(install): don't duplicate guidelines into a hand-merged CLAUDE.md fenceInto only stripped its own block before re-appending, so running the installer against a file where the guidelines were hand-merged (verbatim '## 1. Think Before Coding' heading, no markers) appended a second full copy. Detect that heading outside our block and skip with a warning instead; wire up the previously-dead --force flag as the override. Mirrors the activate hook's dedup so both paths behave the same. --- bin/install.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/bin/install.js b/bin/install.js index 2d88bf6..6d684ab 100644 --- a/bin/install.js +++ b/bin/install.js @@ -24,6 +24,10 @@ const CWD = process.cwd(); const MARK_START = ''; const MARK_END = ''; const BLOCK_RE = new RegExp(`\\n*${MARK_START}[\\s\\S]*?${MARK_END}\\n?`); +// Verbatim heading that marks a file as already carrying the guidelines even +// without our fence markers — i.e. someone hand-merged them. Same string the +// activate hook keys on; keep in sync with hooks/greybeard-activate.js. +const HANDMERGE_MARK = '## 1. Think Before Coding'; // ── Provider matrix ───────────────────────────────────────────────────────── // detect: paths that, if present, mean the agent is in use (~ = home, ./ = cwd). @@ -76,10 +80,13 @@ const C = process.stdout.isTTY && !process.env.NO_COLOR; const dim = (s) => (C ? `\x1b[2m${s}\x1b[0m` : s); const green = (s) => (C ? `\x1b[32m${s}\x1b[0m` : s); const bold = (s) => (C ? `\x1b[1m${s}\x1b[0m` : s); +const yellow = (s) => (C ? `\x1b[33m${s}\x1b[0m` : s); // ── FS helpers (dry-run aware) ────────────────────────────────────────────── let DRY = false; +let FORCE = false; const actions = []; +const warnings = []; function writeFile(dest, content) { if (fs.existsSync(dest) && fs.readFileSync(dest, 'utf8') === content) return false; if (!DRY) { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.writeFileSync(dest, content); } @@ -92,6 +99,14 @@ function fenceInto(dest, payload) { // Strip any existing block, then re-append. Deterministic regardless of prior // state, so re-running is a true no-op and other content is preserved. const without = cur.replace(BLOCK_RE, '').trimEnd(); + // Duplication guard: if the guidelines are hand-merged into this file (verbatim + // heading, no fence markers), appending our block would create a second copy. + // Skip and warn unless --force. The heading only lives inside our own block in + // a managed file, so stripping it above keeps normal re-runs from tripping this. + if (!FORCE && without.includes(HANDMERGE_MARK)) { + warnings.push(`${rel(dest)} already has the guidelines hand-merged (no ${MARK_START} markers) — skipped to avoid a duplicate. Remove the hand-merged copy so the installer can manage it, or re-run with --force to append anyway.`); + return false; + } const next = without ? `${without}\n\n${block}` : block; return writeFile(dest, next); } @@ -166,6 +181,7 @@ function main() { const o = parseArgs(process.argv.slice(2)); if (o.help) return printHelp(); DRY = o.dryRun; + FORCE = o.force; if (o.list) { console.log(bold('Supported agents:')); @@ -186,7 +202,12 @@ function main() { console.log(bold(`${o.uninstall ? 'Uninstalling' : 'Installing'} for: ${chosen.map((p) => p.name).join(', ')}`)); for (const p of chosen) applyProvider(p, o.uninstall); - if (!actions.length) { console.log(green('Already up to date — nothing to change.')); return; } + for (const w of warnings) console.log(yellow(' ! ') + w); + + if (!actions.length) { + if (!warnings.length) console.log(green('Already up to date — nothing to change.')); + return; + } for (const a of actions) console.log(' ' + a); console.log(o.dryRun ? dim('\nDry run — no files written.') : green(`\nDone (${actions.length} change${actions.length > 1 ? 's' : ''}).`)); } @@ -201,6 +222,7 @@ Flags: --all install for every supported agent, detected or not --only install only for the given agent (repeatable) --uninstall remove what this installer added + --force append the guidelines even if a hand-merged copy is detected --dry-run show what would change without writing --list list supported agents and detection status --help this message From 747607587d983829e9c5a0f9d65d6984a7c64256 Mon Sep 17 00:00:00 2001 From: pallaoro Date: Fri, 17 Jul 2026 17:30:20 +0200 Subject: [PATCH 3/5] feat(sidenote): self-contained capture + checkbox lifecycle; ignore the parking file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A verbatim thought parked mid-task is full of 'this / here / it' that dangles when another session or agent reads it cold. Each entry now carries a one-line anchor (task in progress, the file it points at, branch@sha) drawn only from context already in hand — no investigating, so the park contract holds. Entries use - [ ] / - [x]; a bare /sidenote flushes everything not checked off, so older un-checkboxed bullets still surface. Add a repo .gitignore for .claude/sidenotes.md so 'git add -A' can't sweep personal parked thoughts into shared history, and sync the README to match. --- .gitignore | 2 ++ README.md | 4 ++-- commands/sidenote.md | 14 +++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3577c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Personal sidenote parking file (see /sidenote) — never committed +.claude/sidenotes.md diff --git a/README.md b/README.md index 9fdfe5b..e2efb45 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ Then `/scalable` (tests the current direction) or `/scalable ` to park one, or `/sidenote` to see what's parked. The notes land in `.claude/sidenotes.md` — add it to `.gitignore` if you'd rather not track it. +Then `/sidenote ` to park one, or `/sidenote` to see what's parked. The notes land in `.claude/sidenotes.md`, a personal parking file kept out of git (this repo's `.gitignore` already excludes it) — so it's per-checkout scratch, not shared history. > **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. diff --git a/commands/sidenote.md b/commands/sidenote.md index 1cb6a6d..5c75e74 100644 --- a/commands/sidenote.md +++ b/commands/sidenote.md @@ -13,9 +13,17 @@ Park this thought — **$ARGUMENTS** — as a sidenote. A sidenote is a *later-t **Persist it so it survives this session** (context gets compacted; memory doesn't count as "logged"): -1. Append one bullet to `.claude/sidenotes.md` at the project root — create the file (and `.claude/`) if missing. -2. Format: `- [] `. Don't paraphrase or "improve" it — capture what was said. +1. Append one entry to `.claude/sidenotes.md` at the project root — create the file (and `.claude/`) if missing. +2. Format — an open checkbox, the verbatim thought, then a one-line **anchor** so the note still makes sense when another session or agent reads it cold (the verbatim thought alone is full of "this / here / it" that dangles without the moment that produced it): + + ``` + - [ ] [] + ↳ · · @ + ``` + + - **Thought — verbatim.** Don't paraphrase or "improve" it; capture what was said. + - **Anchor — coordinates you already hold, not a research task.** Fill it only from what's already in front of you: the task in progress, the file/area currently in focus, the current branch and short SHA (already in your session's git context). **Do not open files, grep, or investigate the note's subject to complete it** — that would break the park contract. Any field you don't already know, omit. The anchor records where you were standing; it never sends you looking. Then: **one-line acknowledgement** (e.g. `Parked in .claude/sidenotes.md — continuing.`) and **resume exactly where you left off**, as if the note had never arrived. -**If `$ARGUMENTS` is empty** (a bare `/sidenote`), there's nothing to park — treat it as a *flush*: read `.claude/sidenotes.md` and list the currently-open sidenotes back to me so I can decide what to pick up. Change nothing, act on nothing, then resume. +**If `$ARGUMENTS` is empty** (a bare `/sidenote`), there's nothing to park — treat it as a *flush*: read `.claude/sidenotes.md` and list the **open** entries back to me so I can decide what to pick up. Open means *not checked off* — every entry except the ones marked done (`- [x]`), so older bullets without a checkbox still count. Change nothing, act on nothing, then resume. (Checking an item off to `- [x]` happens when it's actually handled — not part of parking or flushing.) From f885ffeb4cfde85e70c3702808df3f1fc17de198 Mon Sep 17 00:00:00 2001 From: pallaoro Date: Fri, 24 Jul 2026 13:14:54 +0200 Subject: [PATCH 4/5] ci(publish): add npm publish workflow; document updating + releasing The README advertised 'npx @clawnify/greybeard' but the package was never published (npm 404), so install/update had no working path. Add a release-driven publish workflow mirroring clawnify/clawnify's publish-cli / create-clawnify-app (release published + workflow_dispatch bootstrap, id-token: write, npm publish --access public --ignore-scripts via NPM_TOKEN), with a check-sync guard so drifted rule copies never ship. Document how to update each install path (npx @latest, the plugin marketplace-update/reinstall sequence, curl -o) and how maintainers cut a release. --- .github/workflows/publish-npm.yml | 29 +++++++++++++++++++++++++++++ README.md | 21 +++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .github/workflows/publish-npm.yml diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 0000000..3a63e3e --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,29 @@ +name: Publish to npm + +on: + release: + types: [published] + # Manual dispatch bootstraps the very first publish before this file is on main: + # gh workflow run publish-npm.yml --ref + workflow_dispatch: + +jobs: + publish: + # Release tags are version tags (v1.2.3); workflow_dispatch has no tag, so allow it too. + if: github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'v') + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # npm provenance / trusted publishing + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: https://registry.npmjs.org + # Never publish rule copies that drifted from CLAUDE.md (the single source). + - run: node scripts/build-rules.js --check + # Zero runtime deps, so no install step — publish the committed files as-is. + - run: npm publish --access public --ignore-scripts + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index e2efb45..8438828 100644 --- a/README.md +++ b/README.md @@ -247,8 +247,29 @@ curl -o CLAUDE.md https://raw.githubusercontent.com/clawnify/greybeard/main/CLAU curl https://raw.githubusercontent.com/clawnify/greybeard/main/CLAUDE.md >> CLAUDE.md ``` +**Updating** to the latest release: + +- **npx installer** — re-run it. `@latest` beats npx's own cache so you actually get the newest version, and the marker-based edits make it a safe no-op if nothing changed: + + ```bash + npx @clawnify/greybeard@latest + ``` + +- **Claude Code plugin** — there's no `/plugin update`; refresh the marketplace, then reinstall (third-party marketplaces don't auto-update by default). You can flip on auto-update in `/plugin` → **Marketplaces**: + + ``` + /plugin marketplace update greybeard + /plugin uninstall greybeard@greybeard + /plugin install greybeard@greybeard + /reload-plugins + ``` + +- **Manual (curl)** — re-run the `-o` form; it overwrites `CLAUDE.md` with the latest (the `>>` append form would duplicate). + > The per-agent rule files are generated from `CLAUDE.md` (the single source) by `scripts/build-rules.js`. Contributors: edit `CLAUDE.md`, run `npm run build`, commit. CI (`npm run check-sync`) fails if a copy drifts. +> **Releasing** (maintainers): bump `version` in `package.json`, then cut a GitHub Release tagged `v` (`gh release create v0.2.0 --generate-notes`). The [`Publish to npm`](.github/workflows/publish-npm.yml) workflow publishes `@clawnify/greybeard` on release, using the `NPM_TOKEN` repo secret. The first publish can be bootstrapped before the workflow is on `main` with `gh workflow run publish-npm.yml --ref `. + ## Key Insight From Andrej: From ed8668f1bf350d3abbf73aed738c4ba77ea9b404 Mon Sep 17 00:00:00 2001 From: pallaoro Date: Fri, 24 Jul 2026 13:16:32 +0200 Subject: [PATCH 5/5] ci(publish): drop duplicate workflow; point release docs at publish.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main already added .github/workflows/publish.yml (release-triggered npm publish with --provenance, build + check-sync + installer smoke test) while this branch was in flight. Remove the redundant publish-npm.yml this branch added — shipping both would double-publish on every release — and keep the README updating/releasing docs, repointed at publish.yml and without the workflow_dispatch bootstrap that publish.yml doesn't offer. --- .github/workflows/publish-npm.yml | 29 ----------------------------- README.md | 2 +- 2 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 .github/workflows/publish-npm.yml diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml deleted file mode 100644 index 3a63e3e..0000000 --- a/.github/workflows/publish-npm.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Publish to npm - -on: - release: - types: [published] - # Manual dispatch bootstraps the very first publish before this file is on main: - # gh workflow run publish-npm.yml --ref - workflow_dispatch: - -jobs: - publish: - # Release tags are version tags (v1.2.3); workflow_dispatch has no tag, so allow it too. - if: github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'v') - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write # npm provenance / trusted publishing - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '20' - registry-url: https://registry.npmjs.org - # Never publish rule copies that drifted from CLAUDE.md (the single source). - - run: node scripts/build-rules.js --check - # Zero runtime deps, so no install step — publish the committed files as-is. - - run: npm publish --access public --ignore-scripts - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index e3bd0be..d84ab0d 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ curl https://raw.githubusercontent.com/clawnify/greybeard/main/CLAUDE.md >> CLAU > The per-agent rule files are generated from `CLAUDE.md` (the single source) by `scripts/build-rules.js`. Contributors: edit `CLAUDE.md`, run `npm run build`, commit. CI (`npm run check-sync`) fails if a copy drifts. -> **Releasing** (maintainers): bump `version` in `package.json`, then cut a GitHub Release tagged `v` (`gh release create v0.2.0 --generate-notes`). The [`Publish to npm`](.github/workflows/publish-npm.yml) workflow publishes `@clawnify/greybeard` on release, using the `NPM_TOKEN` repo secret. The first publish can be bootstrapped before the workflow is on `main` with `gh workflow run publish-npm.yml --ref `. +> **Releasing** (maintainers): bump `version` in `package.json`, then cut a GitHub Release tagged `v` (`gh release create v0.2.0 --generate-notes`). The [`Publish to npm`](.github/workflows/publish.yml) workflow publishes `@clawnify/greybeard` on release (with npm provenance), using the `NPM_TOKEN` repo secret. ## Key Insight