← back to Bcherny Scout
initial scaffold: bcherny-scout skill (scout + automation-doctrine audit, seed post 2077460395279692197)
07d371146f750dca7615c3bb962341daf5cd6c0b · 2026-07-22 21:58:33 -0700 · Steve Abrams
Files touched
A .gitignoreA README.mdA SKILL.mdA references/retrieval.mdA references/seed-automation-doctrine.mdA scripts/hydrate-tweet.js
Diff
commit 07d371146f750dca7615c3bb962341daf5cd6c0b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 22 21:58:33 2026 -0700
initial scaffold: bcherny-scout skill (scout + automation-doctrine audit, seed post 2077460395279692197)
---
.gitignore | 10 +++
README.md | 18 ++++
SKILL.md | 112 +++++++++++++++++++++++
references/retrieval.md | 55 ++++++++++++
references/seed-automation-doctrine.md | 99 +++++++++++++++++++++
scripts/hydrate-tweet.js | 158 +++++++++++++++++++++++++++++++++
6 files changed, 452 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b827c36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+node_modules/
+.env
+.env.*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+runs/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a5dc4e0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# bcherny-scout
+
+Claude Code skill that scouts Boris Cherny's (@bcherny — creator of Claude
+Code) X posts for workflow doctrine, feature news, and agent patterns —
+verifying every claim, deduping against the existing stack, and drafting
+gated codify memos. Also ships his 2026-07-15 automation doctrine ("every
+second fix of the same issue class is a failure of automation") as an
+on-demand repo/session audit.
+
+Completes the scout trio: tiktok-architect (rules), gurisingh-scout (tools),
+bcherny-scout (primary source).
+
+- Live via symlink: `~/.claude/skills/bcherny-scout` → this repo
+- Entry point: `SKILL.md`
+- Retrieval: `scripts/hydrate-tweet.js` ($0; syndication endpoint + fxtwitter
+ fallback for long-form note tweets)
+- Seed doctrine: `references/seed-automation-doctrine.md`
+- Retrieval playbook: `references/retrieval.md`
diff --git a/SKILL.md b/SKILL.md
new file mode 100644
index 0000000..30e6353
--- /dev/null
+++ b/SKILL.md
@@ -0,0 +1,112 @@
+---
+name: bcherny-scout
+description: Scout what Boris Cherny (@bcherny on X, creator of Claude Code) is posting — workflow doctrine, Claude Code features, agent best practices — then verify each claim against how Claude Code actually behaves, dedupe against Steve's existing rules/skills, and draft gated codify memos for the keepers. Also carries his "automation doctrine" (2026-07-15 seed post) as an on-demand audit — find repeat-fix busywork in a repo or session and propose converting it to a lint rule, canary, hook, skill, or CLAUDE.md rule. Use when Steve says "bcherny scout", "/bcherny-scout", "what is Boris Cherny posting", "any new claude code doctrine from boris", "run the automation audit", "second-fix audit", or drops an x.com/bcherny link and asks what it says and whether to adopt it. Completes the scout trio with tiktok-architect (rules) and gurisingh-scout (tools) — this one covers the primary source.
+---
+
+# Bcherny Scout
+
+## Overview
+
+Boris Cherny created Claude Code, so his feed is the closest thing to primary
+doctrine about how to run agents well. This skill does two things:
+
+1. **Scout mode** — retrieve his recent posts (or one specific post), treat
+ each claim as a hypothesis to VERIFY, dedupe against what Steve already
+ has, and draft gated memos to codify the good ones into MEMORY.md /
+ CLAUDE.md / a skill.
+2. **Audit mode** — apply his automation doctrine (see
+ `references/seed-automation-doctrine.md`) to a repo or recent session:
+ find issue classes that got fixed more than once by hand or by agent, and
+ propose the automation (lint rule / CI step / canary / hook / skill /
+ CLAUDE.md rule) that kills each class permanently.
+
+Never a hype relay: posts are claims, not facts, and nothing is codified or
+installed without Steve's sign-off.
+
+## Scout mode
+
+### 1. Retrieve ($0)
+
+For a specific post:
+
+```bash
+node scripts/hydrate-tweet.js <tweet-id-or-url>
+```
+
+Check the output flags — `longform: true` means the full note-tweet text was
+recovered; `truncated: true` means only ~280 chars came back, so do NOT quote
+or analyze it as complete (see `references/retrieval.md` for fallbacks). For
+a discovery sweep of recent posts, follow the fan-out in
+`references/retrieval.md` (Exa + WebSearch + HN mirror), then hydrate each
+candidate id.
+
+### 2. Filter and classify
+
+Keep posts that are: workflow doctrine, Claude Code feature announcements or
+tips, agent/skill/CLAUDE.md patterns, or org-level practices for agentic
+teams. Drop personal chatter. Classify each keeper: `doctrine` | `feature` |
+`tip` | `pattern`.
+
+### 3. Verify before relaying
+
+Even the creator's posts get verified — features ship after posts, behavior
+differs by version, and doctrine may not fit Steve's stack:
+
+- Feature claims → check against the installed Claude Code version
+ (`claude --version`, changelog, or the claude-code-guide agent).
+- Behavior/workflow claims → test in-session where cheap, or mark UNVERIFIED.
+- Doctrine → assess against Steve's actual operating patterns; note where it
+ conflicts with a standing rule (the standing rule wins until Steve says
+ otherwise).
+
+### 4. Dedupe against the existing stack
+
+```bash
+grep -ril "<keyword>" ~/.claude/skills/*/SKILL.md 2>/dev/null
+grep -il "<keyword>" ~/.claude/projects/-Users-macstudio3/memory/*.md
+grep -i "<keyword>" ~/.claude/CLAUDE.md
+```
+
+If an equivalent rule/skill exists, the useful output is a **diff** (what the
+post adds or contradicts), not a redundant recommendation. Note: /checkup is
+already adapted from his /checkup — precedent for adapting rather than
+copying verbatim.
+
+### 5. Score and draft gated memos
+
+Score each keeper 1–5 on fit (matches Steve's stack), leverage (multiplies
+across sessions/agents), and confidence (verified vs unverified). For
+anything averaging ≥ 4 on fit+leverage, draft a codify memo to
+`~/.claude/yolo-queue/pending-approval/` naming: the claim, verification
+result, the exact target (MEMORY.md entry / CLAUDE.md rule / skill edit),
+and the proposed text. Steve approves; the scout never self-codifies.
+
+## Audit mode (the automation doctrine, applied)
+
+Trigger: "run the automation audit", "second-fix audit", or scoped to a repo
+("audit <project> for busywork"). Procedure:
+
+1. Read `references/seed-automation-doctrine.md` (the five distilled rules).
+2. Gather evidence of repeat fixes: `git log --oneline` for recurring
+ fix-verb commits on the same files, MEMORY.md entries describing recurring
+ gotchas, canary FAIL history, and recent session transcripts if in scope.
+3. For each issue class fixed ≥ 2 times, propose ONE automation in the
+ cheapest durable form, preference order: lint rule → CI/pre-commit hook →
+ canary → skill → CLAUDE.md/MEMORY.md rule (prose is the fallback, code is
+ the goal).
+4. Apply the zero-additional-context test to the repo: could an agent with no
+ prompt context contribute? List what tribal knowledge is missing from
+ CLAUDE.md/skills.
+5. Output a ranked table (class, times fixed, proposed automation, effort).
+ Reversible local automations may be built directly; anything touching
+ prod, CI on live repos, or settings.json drafts to pending-approval.
+
+## Hard rails
+
+- Propose-only for codification and installs — memos to pending-approval,
+ Steve decides.
+- Never quote a `truncated: true` hydration as the full post.
+- Posts are claims; show verification status (VERIFIED / UNVERIFIED /
+ CONFLICTS) next to every relayed claim.
+- $0 retrieval by default; metered fallbacks (Exa) show cost per Steve's
+ always-show-costs rule.
diff --git a/references/retrieval.md b/references/retrieval.md
new file mode 100644
index 0000000..d8824c5
--- /dev/null
+++ b/references/retrieval.md
@@ -0,0 +1,55 @@
+# Retrieval playbook — reading @bcherny without X auth, $0
+
+X blocks unauthenticated page reads (WebFetch → 402), and @bcherny posts
+mostly **long-form note tweets** that the common free endpoint truncates.
+Follow this chain in order; every step is free.
+
+## 1. Single known post (id or URL in hand)
+
+```bash
+node scripts/hydrate-tweet.js <tweet-id-or-url>
+```
+
+- Uses the syndication endpoint (`cdn.syndication.twimg.com/tweet-result`)
+ with a locally computed token — no API key.
+- **Note-tweet handling (the @bcherny-specific gotcha):** syndication
+ truncates long posts at ~280 chars and sets a `note_tweet` marker. The
+ script detects this and falls back to `api.fxtwitter.com/i/status/<id>`
+ for the full text. Output flags: `longform: true` = full text recovered;
+ `truncated: true` = fxtwitter was down, text is partial — retry later or
+ use step 3 mirrors before quoting the post.
+- Exit 3 = tombstone (deleted/unavailable) → go to step 3.
+
+## 2. Discovery sweep (what has he posted lately?)
+
+No free chronological-feed API survives reliably. Fan out:
+
+- Exa: `mcp__exa__web_search_exa` with `site:x.com bcherny` plus topic terms
+ ("Claude Code", "skills", "agents", "checkup"), date-restricted to the
+ sweep window.
+- WebSearch: `"bcherny" claude code <topic>` — engineering-blog and
+ newsletter coverage of his posts often quotes the full text and links the
+ original id.
+- Nitter instances come and go; try `nitter.net/bcherny` and current public
+ instances via a quick search, but treat as bonus, not backbone.
+- Hacker News mirrors: his bigger essays get submitted —
+ `https://hn.algolia.com/api/v1/search?query=bcherny` ($0, reliable).
+
+Collect candidate status ids from the sweep, then hydrate each via step 1
+for verbatim text.
+
+## 3. Tombstone / endpoint-down fallback
+
+- Thread Reader: `https://threadreaderapp.com/thread/<id>.html`
+- Google cache of the x.com URL via WebSearch.
+- HN/algolia and blog quotations (step 2) — for essays, coverage usually
+ preserves the full text.
+
+## Known-good anchors
+
+- Handle: `@bcherny` (Boris Cherny, user id 159337660). Account is stable —
+ unlike some scouted accounts, tombstones here usually mean a deleted post,
+ not a suspended account.
+- Seed post: id `2077460395279692197` (2026-07-15, automation doctrine) —
+ full text archived in `references/seed-automation-doctrine.md`, so never
+ re-fetch it.
diff --git a/references/seed-automation-doctrine.md b/references/seed-automation-doctrine.md
new file mode 100644
index 0000000..d610576
--- /dev/null
+++ b/references/seed-automation-doctrine.md
@@ -0,0 +1,99 @@
+# Seed post — the Automation Doctrine (2026-07-15)
+
+Source: https://x.com/bcherny/status/2077460395279692197
+Author: Boris Cherny (@bcherny), creator of Claude Code
+Engagement at capture (2026-07-22): ~9,939 likes, 388 replies
+Full text captured verbatim below (3,034 chars, via hydrate-tweet.js).
+
+## Verbatim text
+
+> Something I have been thinking about: in the past, the best engineers I knew
+> spent a lot of time automating their work in various ways. Better vim/emacs
+> automations, writing lint rules to catch repeat code issues, building up a
+> suite of e2e tests so they don't need to smoke test the app manually. These
+> kinds of things were the highest leverage activities an engineer could do,
+> because it multiplied their own output, which in turn meant they could build
+> more things.
+>
+> I think many of these automations have become even more important now. This
+> is true for a number of reasons.
+>
+> First, infra and DevX automation speeds you up. And if you are running an
+> army of agents, each of those agents will be sped up also. More automation ==
+> more output per unit of time.
+>
+> Second, moving things to code improves efficiency. Your agent could fix an
+> issue every time it sees that issue happen, but that uses tokens and might
+> miss cases. If Claude instead writes a lint rule, CI step, or routine, that
+> class of issue can be fully automated forever. This is really what people are
+> talking about when they talk about loops -- it's about automating entire
+> types of busywork rather than solving them one off. This isn't a new idea at
+> all. Engineers have been doing this for a long time!
+>
+> Third and most importantly, automation makes it possible for others to
+> contribute to the codebase more easily. Increasingly what I am seeing is
+> engineers are contributing to codebases on day one because Claude can
+> navigate the codebase for them, and that non-engineers are able to contribute
+> to a codebase as effectively as engineers can. What gets in the way of both
+> of these is domain knowledge that lives in peoples' heads rather than in
+> automation -- the stuff you used to have to learn when ramping up. What has
+> changed thanks to agents is the domain knowledge that can be encoded as
+> infrastructure is no longer limited to what is expressible in lint rules and
+> types and tests; it can now capture nearly all domain knowledge, encoded as
+> code comments and skills and CLAUDE.md rules and memories. If I put up a PR
+> for an iOS codebase I don't know and a code reviewer rejects it because it
+> doesn't use the right framework, or if a designer builds a new feature and it
+> gets rejected because it doesn't follow the right architectural patterns,
+> these are failures of automation.
+>
+> Every team should be writing the CLAUDE.md's, REVIEW.md's, skills, and docs
+> that enable agents to productively work in their codebase with zero
+> additional context from the prompter. This sounds crazy, and at the same time
+> is a natural extension of the stuff engineers have always done: automate, and
+> encode domain knowledge as infrastructure. As the model gets smarter and as
+> the harness matures, this task becomes easier. In the meantime, it is on
+> every team to look for ways to convert their domain knowledge to infra so
+> that Claude can write code better, so that code review catches issues
+> automatically, and so the next person working on your codebase can
+> contribute more easily.
+
+## Distilled doctrine (the operational rules)
+
+1. **Second-fix rule.** The first time an agent fixes an issue, it is a fix.
+ The second time it fixes the *same class* of issue, that is a failure of
+ automation — convert the class into a lint rule, CI step, canary, hook, or
+ routine so it can never recur silently. Token-spend on repeat fixes is
+ waste; code is free forever.
+2. **Rejection = missing infra.** Any time work is rejected in review for a
+ rule the author "should have known" (wrong framework, wrong pattern, house
+ convention), the fix is not "tell the author" — it is to encode that rule
+ where the agent reads it (CLAUDE.md, REVIEW.md, a skill, a memory).
+3. **Zero-additional-context test.** The bar for a codebase's automation:
+ an agent (or a day-one human) should be able to contribute productively
+ with *zero* extra context from the prompter. If a task needs tribal
+ knowledge delivered in the prompt, that knowledge belongs in infra.
+4. **Automation multiplies fleets.** A DevX speedup is no longer ×1 engineer;
+ it is ×N agents. Prioritize automation work accordingly — it compounds
+ across every parallel session.
+5. **The encoding surface is now everything.** Pre-agents, encodable domain
+ knowledge was limited to lint rules, types, and tests. Now nearly all of it
+ is encodable: code comments, skills, CLAUDE.md rules, memories, review
+ docs. "It can't be automated" is rarely true anymore.
+
+## How this maps onto Steve's stack (verification notes, 2026-07-22)
+
+Steve's setup already practices much of this — the doctrine mostly *names*
+what exists and sharpens the second-fix reflex:
+
+- Already-encoded examples: the canary fleet (dw-five-field-canary et al. are
+ literally "class-of-issue automated forever"), the MEMORY.md gotcha entries,
+ CLAUDE.md standing rules (sort+density, admin created-date chips, MAP
+ pricing), /best-practices pre-flight, /compound-engineering's
+ Plan→Work→Review→Compound loop, /dreaming (session mistakes → memory).
+- The sharpened habit this post adds: when a fix lands **twice** for the same
+ class, stop and ask "lint rule / canary / hook / skill / CLAUDE.md — which
+ one kills this class?" and draft that codification in the same session,
+ instead of a third fix later.
+- REVIEW.md is the one named artifact Steve's repos generally lack: a
+ per-repo file of what code review rejects, readable by /code-review and
+ /claude-codex panels. Candidate adoption, per-project, gated.
diff --git a/scripts/hydrate-tweet.js b/scripts/hydrate-tweet.js
new file mode 100755
index 0000000..418ac65
--- /dev/null
+++ b/scripts/hydrate-tweet.js
@@ -0,0 +1,158 @@
+#!/usr/bin/env node
+/**
+ * hydrate-tweet.js — resolve a single X/Twitter post to plain text WITHOUT auth.
+ *
+ * X blocks unauthenticated page reads (WebFetch → HTTP 402), so this uses the
+ * public syndication endpoint that powers embedded tweets. The endpoint needs a
+ * `token` that is a deterministic function of the tweet id — we compute it
+ * locally ($0), so no API key and no login are ever required.
+ *
+ * Usage:
+ * node hydrate-tweet.js <tweet-id-or-url>
+ *
+ * Examples:
+ * node hydrate-tweet.js 2055557527153332556
+ * node hydrate-tweet.js https://x.com/heygurisingh/status/2055557527153332556
+ *
+ * Exit codes:
+ * 0 ok — printed tweet JSON summary
+ * 3 tombstone — tweet/account is suspended, deleted, or age-gated (unreadable)
+ * 1 error — bad input or network/parse failure
+ *
+ * Output: a JSON object on stdout:
+ * { id, url, author, created_at, text, urls[], github[] }
+ * On tombstone: { id, tombstone: "<reason text>" } and exit 3.
+ */
+
+'use strict';
+
+function extractId(arg) {
+ if (!arg) return null;
+ // trailing run of digits handles both bare ids and .../status/<id>[?query]
+ const m = String(arg).match(/(\d{5,25})/g);
+ return m ? m[m.length - 1] : null;
+}
+
+// react-tweet's documented token derivation: (id / 1e15 * PI) in base-36,
+// with zeros and the dot stripped. Verified against live tweets 2026-07.
+function syndicationToken(id) {
+ return ((Number(id) / 1e15) * Math.PI)
+ .toString(36)
+ .replace(/(0+|\.)/g, '');
+}
+
+function findGithub(text, urls) {
+ const found = new Set();
+ const scan = (s) => {
+ if (!s) return;
+ const re = /https?:\/\/(?:www\.)?github\.com\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+/g;
+ let m;
+ while ((m = re.exec(s))) found.add(m[0].replace(/[.,)]+$/, ''));
+ };
+ scan(text);
+ (urls || []).forEach(scan);
+ return [...found];
+}
+
+async function main() {
+ const id = extractId(process.argv[2]);
+ if (!id) {
+ console.error('usage: node hydrate-tweet.js <tweet-id-or-url>');
+ process.exit(1);
+ }
+
+ const token = syndicationToken(id);
+ const endpoint =
+ `https://cdn.syndication.twimg.com/tweet-result?id=${id}&lang=en&token=${token}`;
+
+ let res;
+ try {
+ res = await fetch(endpoint, {
+ headers: {
+ // a browser-ish UA avoids the occasional bot 403 on this endpoint
+ 'User-Agent':
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ' +
+ '(KHTML, like Gecko) Chrome/125.0 Safari/537.36',
+ Accept: 'application/json',
+ },
+ });
+ } catch (e) {
+ console.error(`network error: ${e.message}`);
+ process.exit(1);
+ }
+
+ if (res.status === 404) {
+ console.log(JSON.stringify({ id, tombstone: 'not found (404)' }, null, 2));
+ process.exit(3);
+ }
+ if (!res.ok) {
+ console.error(`http ${res.status} from syndication endpoint`);
+ process.exit(1);
+ }
+
+ let data;
+ try {
+ data = await res.json();
+ } catch (e) {
+ console.error(`parse error: ${e.message}`);
+ process.exit(1);
+ }
+
+ // Tombstone shapes: suspended/deleted/age-gated tweets come back as a
+ // __typename of TweetTombstone (or an object with a `tombstone` field).
+ const tomb =
+ data.__typename === 'TweetTombstone' ||
+ data.tombstone ||
+ (data.text && /suspended account|Post is from a suspended|isn.?t available/i.test(data.text) && !data.user);
+ if (tomb) {
+ const reason =
+ (data.tombstone && (data.tombstone.text?.text || data.tombstone.text)) ||
+ data.text ||
+ 'unavailable (suspended / deleted / age-gated)';
+ console.log(JSON.stringify({ id, tombstone: reason }, null, 2));
+ process.exit(3);
+ }
+
+ const urls = (data.entities?.urls || []).map((u) => u.expanded_url || u.url);
+ let text = data.text || '';
+
+ // Long-form "note tweets" (most of @bcherny's essays) come back from the
+ // syndication endpoint TRUNCATED at ~280 chars with a `note_tweet` marker.
+ // Fall back to fxtwitter, which serves the full note text.
+ let longform = false;
+ if (data.note_tweet) {
+ try {
+ const fx = await fetch(`https://api.fxtwitter.com/i/status/${id}`, {
+ headers: { Accept: 'application/json' },
+ });
+ if (fx.ok) {
+ const fxData = await fx.json();
+ const fxText = fxData?.tweet?.text || '';
+ if (fxText.length > text.length) {
+ text = fxText;
+ longform = true;
+ }
+ }
+ } catch {
+ // keep the truncated syndication text; caller sees truncated:true
+ }
+ }
+
+ const out = {
+ id,
+ url: `https://x.com/${data.user?.screen_name || 'i'}/status/${id}`,
+ author: data.user
+ ? `@${data.user.screen_name} (${data.user.name})`
+ : null,
+ created_at: data.created_at || null,
+ longform,
+ truncated: Boolean(data.note_tweet) && !longform,
+ text,
+ urls,
+ github: findGithub(text, urls),
+ };
+ console.log(JSON.stringify(out, null, 2));
+ process.exit(0);
+}
+
+main();
(oldest)
·
back to Bcherny Scout
·
first sweep lessons: unrollnow thread mirror, exact cost-log 42d23c6 →