[object Object]

← 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

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 →